Compare commits

..

4 Commits

Author SHA1 Message Date
Eric Wagoner
3b30bd0323 Add two new blog posts and support preview frontmatter
- Add "I Thought I Had 15 Minutes" post about standup automation (draft)
- Add "Why I Self-Host My Social Media" post about YunoHost setup
- Update list.html to use preview param for homepage listings, falling back to description

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 20:56:21 -05:00
Eric Wagoner
2c5632651a Add new blog post: Why My Payment Agent Is Named George
Personal essay on naming AI sub-agents after inspiring humans as a practice
for staying intentional about who the work serves.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 22:24:41 -05:00
Eric Wagoner
1a432fd8c2 Add new blog post: Forty Years of Code, Still Can't Type
Personal reflection on impostor syndrome and neurodivergent coding experience. Discusses 40+ years of self-taught development while struggling with mechanical typing precision, the unsustainable "code frenzies" coping mechanism, and how AI-assisted development (Claude Code) eliminated the transcription bottleneck.

Also updates CLAUDE.md with comprehensive voice and style guide for future writing assistance.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 20:06:28 -05:00
Eric Wagoner
744235b47f Add LocallyGrown series part 6 and implement series navigation
- Add final part of LocallyGrown.net series: "From Survival to Sustainability"
- Implement Hugo series taxonomy for better content organization
- Add series navigation partial showing all 6 parts with prev/next links
- Update all LocallyGrown posts with series metadata and ordering
- Display series indicators on index page for connected content
- Style series navigation with responsive design and dark mode support
- Fix series links across all posts to use correct slugs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-29 01:12:22 -04:00
21 changed files with 1101 additions and 995 deletions

135
CLAUDE.md
View File

@@ -75,4 +75,137 @@ The site has four menu groups configured in `config.toml`:
- The Hugo server automatically watches for changes in `/archetypes`, `/assets`, `/content`, `/data`, `/layouts`, `/static`, and `/themes` - The Hugo server automatically watches for changes in `/archetypes`, `/assets`, `/content`, `/data`, `/layouts`, `/static`, and `/themes`
- Posts are created with draft status by default (controlled by archetype) - Posts are created with draft status by default (controlled by archetype)
- The site integrates with multiple social platforms and services in the Kestrel's Nest ecosystem - The site integrates with multiple social platforms and services in the Kestrel's Nest ecosystem
## Eric's Writing Voice & Style Guide
When writing or editing blog posts, maintain Eric's distinctive voice and structural patterns:
### Structural Patterns
1. **Opening Elements**
- Start with an italicized tagline that captures the core insight (e.g., "_Three thousand passing tests don't mean your system works_")
- Include a TL;DR section early in technical posts, using callout boxes for key summaries
- Use horizontal rules (`---`) to separate major sections
2. **Callout Boxes** (use Hugo shortcode syntax)
- Extensively use callout boxes for warnings, examples, quotes, and key insights
- Types: `info`, `warning`, `success`, `danger`, `note`, `quote`, `example`
- These are structural signposts, not decoration—they highlight critical information
- Example: `{{< callout type="info" title="Why This Matters" >}}...{{< /callout >}}`
3. **Section Structure**
- Use numbered breakdowns for complex topics (Bug Category #1, #2, etc.)
- End sections with concrete metrics or results
- Include "What's Next" or forward-looking sections in series posts
### Tone & Voice Characteristics
1. **Brutal Honesty with Technical Confidence**
- Be unflinchingly honest about failures and mistakes
- Never self-pitying—own mistakes but frame as learning
- Mix emotional vulnerability with technical precision
- Examples: "The Most Embarrassing Bug," "My morale was at an all-time low" paired with detailed technical analysis
2. **Concrete Over Abstract**
- Always ground statements in specific numbers, metrics, and examples
- Replace abstract lists with detailed scenarios showing actual time breakdowns
- Use real project names (LocallyGrown.net) and specific technologies (Rails, SvelteKit, Claude Code)
- Before/after comparisons with actual measurements: "8-12 commits per week" → "40-50 commits per week"
3. **Show the Work**
- Include code snippets (even wrong code) with explanations
- Provide specific numbers: "314 commits in 18 days," "$1.3 million in annual sales"
- Show actual dialogue or process breakdowns
- Time breakdowns: "Total: 25 minutes. The thinking took 2. The transcription took 23."
4. **Characteristic Phrases**
- "Here's what matters:"
- "The numbers tell the story:"
- "The real X was always Y"
- "This wasn't about X. It was about Y."
- Rhetorical questions followed by answers
- Extensive parenthetical asides: "(after comparing Prisma, TypeORM, and others)"
5. **Human Impact Focus**
- Always circle back to impact on real people—users, customers, communities
- Frame personal victories as community victories
- End with forward-looking perspective, not just resolution
- Connect technical decisions to human consequences
6. **Technical Communication**
- Never assume reader is technical—explain even when showing code
- Balance technical specifics with human impact
- Use technical details to prove points, not to show off
- Include both the code AND the why
### Content Patterns
1. **Examples Over Explanation**
- Transform abstract concepts into concrete scenarios
- Show specific workflows with timing breakdowns
- Include real-world examples from actual projects
- Use before/after comparisons with measurable differences
2. **Data-Driven Narratives**
- Support claims with specific metrics
- Track and report actual measurements
- Use Git logs, analytics, user counts as evidence
- Quantify time savings, performance improvements, impact
3. **Acknowledgment & Gratitude**
- Credit others extensively (market managers, designers, contributors)
- Acknowledge tools and their creators
- Recognize community contributions
- Human names for AI agents (Ray, Agatha, Ada) to maintain human-centered perspective
4. **Balanced Disclaimers**
- Use callout boxes for important context or disclaimers
- Acknowledge different approaches work for different people
- "What This Isn't" sections to clarify boundaries
- Respectful of different working styles and tools
### Series & Long-Form Posts
For multi-part series (like the LocallyGrown series):
- Include series navigation at the end
- Link to related posts with clear context
- Use consistent callout styles across the series
- Build narrative arc across posts
- Reference earlier posts when relevant
### Example Transformations
**Abstract → Concrete:**
Before: "Most of my time was spent on mechanical tasks"
After: "I tracked it once: 4 hours of actual coding work produced 3.5 hours of fixing typos, hunting syntax, and triple-checking. The ratio was backwards."
**General → Specific:**
Before: "The platform performs well"
After: "Response times are consistently under 2 seconds, even during peak Sunday night ordering when 200+ customers are browsing simultaneously."
**Personal → Community:**
Before: "I finally solved the problem"
After: "The fix went live Tuesday morning. By Wednesday, three market managers reported their volunteers could finally access the tools they needed. Physical workflow restored."
### Voice Checklist for Posts
- [ ] Opens with italicized tagline
- [ ] Includes TL;DR with metrics (for technical posts)
- [ ] Uses 2-4 callout boxes for key insights
- [ ] Contains specific numbers and measurements
- [ ] Shows concrete examples, not just abstractions
- [ ] Balances vulnerability with technical confidence
- [ ] Includes impact on real people/communities
- [ ] Ends with forward-looking perspective
- [ ] Credits others and acknowledges contributions
- [ ] Uses characteristic phrases ("Here's what matters," "The numbers tell the story")
This voice reflects someone who is deeply technical, brutally honest about challenges, focused on real-world impact, and committed to serving communities—not just building software.

View File

@@ -16,4 +16,174 @@
} }
// Import callout styles // Import callout styles
@import 'callouts'; @import 'callouts';
// Series indicator on list pages
.series-indicator {
display: inline-block;
padding: 0.25rem 0.75rem;
margin-left: 0.5rem;
background: var(--series-bg, #e3f2fd);
color: var(--series-text, #1976d2);
border-radius: 4px;
font-size: 0.85rem;
font-weight: 500;
vertical-align: middle;
svg {
width: 14px;
height: 14px;
margin-right: 0.25rem;
vertical-align: -2px;
}
}
@media (prefers-color-scheme: dark) {
.series-indicator {
background: rgba(33, 150, 243, 0.15);
color: rgba(144, 202, 249, 1);
}
}
// Series navigation styling
.series-box {
background: var(--card-bg, #f5f5f5);
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 8px;
padding: 1.5rem;
margin: 2rem 0;
.series-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
font-size: 1.1rem;
.series-part {
color: var(--secondary-text, #666);
font-size: 0.9rem;
}
}
.series-nav {
margin: 1rem 0;
.series-list {
list-style: none;
padding-left: 0;
margin: 0;
li {
padding: 0.5rem 0;
border-left: 3px solid transparent;
padding-left: 1rem;
&.current {
border-left-color: var(--primary-color, #007bff);
background: var(--highlight-bg, rgba(0, 123, 255, 0.05));
margin-left: -1rem;
padding-left: calc(1rem + 1rem);
margin-right: -1rem;
padding-right: 1rem;
}
a {
color: var(--link-color, #007bff);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
}
.series-navigation {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border-color, #e0e0e0);
.series-nav-links {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
.series-prev,
.series-next {
flex: 1;
padding: 0.5rem 1rem;
border-radius: 4px;
text-decoration: none;
transition: background-color 0.2s;
}
.series-prev {
text-align: left;
background: var(--button-bg, #f0f0f0);
color: var(--link-color, #007bff);
&:hover:not(.disabled) {
background: var(--button-hover-bg, #e0e0e0);
}
&.disabled {
color: var(--disabled-text, #999);
cursor: not-allowed;
}
}
.series-next {
text-align: right;
background: var(--button-bg, #f0f0f0);
color: var(--link-color, #007bff);
&:hover:not(.disabled) {
background: var(--button-hover-bg, #e0e0e0);
}
&.disabled {
color: var(--disabled-text, #999);
cursor: not-allowed;
}
}
}
}
}
// Dark mode support
@media (prefers-color-scheme: dark) {
.series-box {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.1);
.series-header .series-part {
color: rgba(255, 255, 255, 0.7);
}
.series-nav .series-list li.current {
background: rgba(255, 255, 255, 0.05);
}
.series-navigation {
border-top-color: rgba(255, 255, 255, 0.1);
.series-nav-links {
.series-prev,
.series-next {
background: rgba(255, 255, 255, 0.05);
&:hover:not(.disabled) {
background: rgba(255, 255, 255, 0.1);
}
&.disabled {
color: rgba(255, 255, 255, 0.3);
}
}
}
}
}
}

View File

@@ -2,6 +2,11 @@ baseURL = 'https://blog.kestrelsnest.social'
languageCode = 'en-us' languageCode = 'en-us'
title = "Kestrel's Nest" title = "Kestrel's Nest"
theme = "m10c" theme = "m10c"
[taxonomies]
tag = "tags"
category = "categories"
series = "series"
[params] [params]
author = "Eric Wagoner" author = "Eric Wagoner"
avatar = "face.jpg" avatar = "face.jpg"

View File

@@ -10,6 +10,8 @@ categories:
- locallygrown - locallygrown
lastmod: 2025-09-22T20:54:42.989Z lastmod: 2025-09-22T20:54:42.989Z
slug: locallygrown-origin-story slug: locallygrown-origin-story
series: ["LocallyGrown.net: The 23-Year Journey"]
series_order: 1
--- ---
_How a failed wholesale experiment in Athens, Georgia became the foundation for nationwide local food systems_ _How a failed wholesale experiment in Athens, Georgia became the foundation for nationwide local food systems_
@@ -251,4 +253,4 @@ _This is part one of a series documenting the creation, evolution, and rescue of
3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-rails-svelte-migration/) 3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-rails-svelte-migration/)
4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/) 4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/)
5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/) 5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/)
6. The Future: Building on Modern Foundations 6. [From Survival to Sustainability: The Next 20 Years for LocallyGrown.net](https://blog.kestrelsnest.social/posts/locallygrown-survival-to-sustainability/)

View File

@@ -12,6 +12,8 @@ lastmod: 2025-09-22T20:54:49.481Z
keywords: keywords:
- locallygrown - locallygrown
slug: locallygrown-rescue-mission slug: locallygrown-rescue-mission
series: ["LocallyGrown.net: The 23-Year Journey"]
series_order: 2
--- ---
_How a platform serving thousands of farmers and customers nearly died from its own success—and the impossible choice I had to make to save it_ _How a platform serving thousands of farmers and customers nearly died from its own success—and the impossible choice I had to make to save it_
@@ -253,4 +255,4 @@ _This is part two of a series documenting the rescue and modernization of Locall
3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-rails-svelte-migration/) 3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-rails-svelte-migration/)
4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/) 4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/)
5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/) 5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/)
6. The Future: Building on Modern Foundations 6. [From Survival to Sustainability: The Next 20 Years for LocallyGrown.net](https://blog.kestrelsnest.social/posts/locallygrown-survival-to-sustainability/)

View File

@@ -17,6 +17,8 @@ keywords:
- svelte - svelte
- rails - rails
slug: locallygrown-rails-svelte-migration slug: locallygrown-rails-svelte-migration
series: ["LocallyGrown.net: The 23-Year Journey"]
series_order: 3
--- ---
_How I migrated decades of complex ActiveRecord relationships, subdomain multitenancy, and business logic to a modern stack without losing a single feature_ _How I migrated decades of complex ActiveRecord relationships, subdomain multitenancy, and business logic to a modern stack without losing a single feature_
@@ -985,4 +987,4 @@ Part 4 is the messy part: real users, real money, and the bugs that only show up
3. **The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit**_You are here_ 3. **The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit**_You are here_
4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/) 4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/)
5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/) 5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/)
6. The Future: Building on Modern Foundations 6. [From Survival to Sustainability: The Next 20 Years for LocallyGrown.net](https://blog.kestrelsnest.social/posts/locallygrown-survival-to-sustainability/)

View File

@@ -17,6 +17,8 @@ keywords:
- svelte - svelte
- rails - rails
slug: locallygrown-reality-of-production slug: locallygrown-reality-of-production
series: ["LocallyGrown.net: The 23-Year Journey"]
series_order: 4
--- ---
_Three thousand passing tests don't mean your system works_ _Three thousand passing tests don't mean your system works_
@@ -351,4 +353,4 @@ _This is part four of a series documenting the rescue and modernization of Local
3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-architecture-challenge/) 3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-architecture-challenge/)
4. **The Reality of Production: When Hope Meets Live Users**_You are here_ 4. **The Reality of Production: When Hope Meets Live Users**_You are here_
5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/) 5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/)
6. The Future: Building on Modern Foundations 6. [From Survival to Sustainability: The Next 20 Years for LocallyGrown.net](https://blog.kestrelsnest.social/posts/locallygrown-survival-to-sustainability/)

View File

@@ -22,6 +22,8 @@ keywords:
- rails - rails
- claude - claude
slug: locallygrown-lessons slug: locallygrown-lessons
series: ["LocallyGrown.net: The 23-Year Journey"]
series_order: 5
--- ---
_Advanced Developer Tools, Real Tradeoffs_ _Advanced Developer Tools, Real Tradeoffs_
@@ -1177,4 +1179,4 @@ _This is part five of a series documenting the rescue and modernization of Local
3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-architecture-challenge/) 3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-architecture-challenge/)
4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/) 4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/)
5. **Lessons from the Solo Developer Using Modern Tools**_You are here_ 5. **Lessons from the Solo Developer Using Modern Tools**_You are here_
6. The Future: Building on Modern Foundations 6. [From Survival to Sustainability: The Next 20 Years for LocallyGrown.net](https://blog.kestrelsnest.social/posts/locallygrown-survival-to-sustainability/)

View File

@@ -0,0 +1,241 @@
---
title: "From Survival to Sustainability: The Next 20 Years for LocallyGrown.net"
description: Six weeks after the rebuild, the crisis is over. Heres the focused roadmap—EBT access, delivery tools, and quiet reliability—for community-run, multi-grower markets.
date: 2025-09-29T04:24:13.598Z
preview: ""
draft: false
tags:
- locallygrown
categories:
- locallygrown
lastmod: 2025-10-03T16:29:08.958Z
keywords:
- locallygrown
slug: locallygrown-survival-to-sustainability
series:
- "LocallyGrown.net: The 23-Year Journey"
series_order: 6
---
_Why saving a 23-year-old farmers market platform matters more now than ever—and how modern architecture enables the next chapter of local food systems_
---
## TL;DR
{{< callout type="warning" title="The Path Forward" >}}
Six weeks post-launch, I'm still squashing bugs daily, but the existential crisis is over. The platform that was one hardware failure away from extinction now has a solid foundation. With a clear niche (community-run, multi-grower cooperative markets), I'm focused on thoughtful improvements: automation that removes friction, EBT support for equitable access, and delivery tools for the last mile. This is about preserving vital community infrastructure that hundreds of growers and thousands of customers rely on.
{{< /callout >}}
---
Over the last five posts, I've walked you through a 23-year journey. I started with an accidental discovery that became pioneering infrastructure for a movement that barely existed. I sat with you in the crushing weight of a 14-year technical stagnation that brought the platform to the brink of extinction. I dissected the six-month, part-time rescue mission to rebuild everything from scratch, the architectural choices that made it possible, and the modern tools that made it feasible for a solo developer. And I lived through the brutal, humbling reality of the post-launch "bug apocalypse".
Six weeks after launch, after hundreds of commits and countless fixes, the platform has stabilized. I'm still fixing bugs (two just today) and keeping an eagle eye on Sentry metrics while refining performance. But the constant, gnawing anxiety of imminent server failure that I lived with for years is gone. The old Rails app that could die at any moment has been replaced by a modern system that, while still needing attention, is fundamentally sound. For the first time in a long time, I can think beyond just keeping the lights on. I can think about the future.
But the world LocallyGrown.net has been reborn into is not the one it left behind. The local food movement I helped nurture is now a vibrant, competitive industry. So, for this final post in the series, I want to talk about what's next. It's time to look at the crowded landscape I now find myself in, define my place in it, and chart a course for the future.
## Finding My Place in a Crowded Field
When I started this in 2002, the idea of an online farmers market was a novelty. Today, it's a standard. Dozens of companies, many of them well-funded, now offer sophisticated platforms for selling local food. To understand where I go from here, I first have to understand where I fit.
The competitive landscape is diverse, with platforms specializing in different parts of the local food ecosystem:
- **Platforms for Individual Farms:** Solutions like GrazeCart are built "by farmers for farmers," offering an all-in-one system for a single farm to manage its e-commerce and in-person POS sales, with a strong focus on features like selling meat by weight.
- **Comprehensive Food Hub Software:** Competitors like Local Food Marketplace provide powerful, all-in-one SaaS solutions for established food hubs, farms, and CSAs that need to manage multiple sales channels, from direct-to-consumer to wholesale.
- **The Modern SaaS Powerhouse:** Then there's a competitor like Local Line, a feature-rich "farm-to-fork commerce platform" that serves both suppliers and wholesale buyers. With sophisticated tools for subscriptions, custom price lists for different customer types (retail vs. wholesale), and advanced inventory management, it's a powerful option for farms ready to scale their operations.
Seeing this, it would be easy to feel outmatched. But that analysis is missing the point. It's also missing my history. These platforms aren't competitors so much as fellow travelers, each serving different needs in the growing local food ecosystem. The diversity of solutions reflects the diversity of communities we all serve. Were not competing to replace each other so much as completing the ecosystem from different angles.
LocallyGrown.net was never just a generic e-commerce platform. It was built to solve a specific problem for a specific type of organization: the community-run, multi-grower, cooperative online market. My core innovation wasn't just a shopping cart; it was the "Predictable Harvesting" model that the software enabled. Farmers harvest only what's been pre-ordered, eliminating waste while giving customers the full selection of a farmers market with the convenience of online shopping. I helped de-risk small-scale farming by eliminating waste and gave customers unprecedented choice and convenience, combining the best of farmers markets, CSAs, and buying clubs.
That is still my niche. That is still my strength. I am not trying to build the best platform for a single farm selling meat, nor am I building a sourcing platform for national grocery chains. I am, and will remain, focused on building the best platform for a community that wants to build its own online market together. My 23 years of experience are focused on that single, powerful goal.
## The Path Forward: From Stability to Service
The rescue was about survival. The future is about service. Having a stable, modern platform means I can finally stop patching holes and start building bridges. I can add features that make a real difference in the day-to-day lives of the market managers and growers who depend on this system. I can't match the big competitors feature for feature, but I can be smarter and more focused. My roadmap is about adding thoughtful improvements that align with the core mission of the platform.
### 1. Small Automations, Big Impact
One of the most consistent pieces of feedback I get revolves around the manual work of running a market cycle. A simple but powerful improvement is to automate the market schedule. Market managers should be able to set their ordering windows (when the market opens for shopping and when it closes) on a recurring schedule and then forget about it. No more late-night logins to manually open the market. This is a perfect example of a subtle automation that removes a recurring point of friction and frees up a manager's time for more important work.
### 2. Expanding Access with Online EBT
A core principle of local food is that it should be for everyone. A major step toward that goal is accepting SNAP/EBT payments online. This is not a simple feature to add. It involves a rigorous authorization process with the USDA's Food and Nutrition Service (FNS) to become an approved online SNAP retailer. An e-commerce platform must meet specific technical requirements for secure, encrypted PIN entry and submit a letter of intent to the FNS to even begin the process. While SNAP benefits can only be used for eligible food items and not for delivery or service fees, offering this payment option is a critical quality-of-life improvement that makes local food more accessible to more members of the community. It's a complex challenge, but it's one that goes to the very heart of what LocallyGrown.net is about.
{{< callout type="info" title="Why this matters" >}}
Nationally, SNAP participation skews rural, and rural broadband can be spotty. Enabling secure online EBT reduces travel burdens, expands choice, and keeps dollars circulating locally.
{{< /callout >}}
### 3. Solving the Last Mile: Better Delivery Support
As more markets explore delivery, the logistical challenges become clear. It's one thing to have customers pick up from a central point; it's another to efficiently route deliveries to dozens of individual homes. While I have no intention of building a full-blown transportation management system like those used by large-scale haulers, there is a clear need for better delivery tools. This means features like delivery route management and optimization, which are common in CSA-focused software. The goal is to give market managers the tools they need to plan efficient routes, manage drivers, and provide customers with accurate delivery information, making home delivery a viable and scalable option.
## A Renewed Commitment
Knowing who I am is the first step. The next is committing to a path that honors my past while ensuring a sustainable future. The rescue mission was a success, but it came with hard-won lessons. The existential risk is no longer technical failure. It's ensuring that local food remains accessible to all communities, not just those with privilege. The next challenge is about equity, reach, and sustainability in the truest sense.
Moving forward, my commitment is threefold:
**First, a commitment to stability and trust.** Six weeks post-launch, I'm still fixing bugs and watching metrics like a hawk. The "bug apocalypse" taught me that my code has real-world consequences for people running their businesses. My first priority will always be quiet reliability and responding quickly to the needs of my users.
**Second, a commitment to my story.** I can't outspend or out-develop the competition, and I won't try. My strength is my authenticity: 23 years of being built from within the local food movement, by a grower who lived the problems he was solving. I will continue to build a platform that reflects that unique experience.
**Finally, a commitment to never again.** The 14-year stagnation was a crisis born from perpetually deferred maintenance. That will not happen again. A portion of my development time is now permanently allocated to the unglamorous but essential work of keeping the platform healthy. I will never again be one hardware failure away from extinction.
## The Current State: By the Numbers
As of September 2025, LocallyGrown.net serves:
- 25+ active markets across North America
- 500+ growers and food producers
- Thousands of customers weekly
- Over $1.3 million in annual sales flowing directly to small farms
These aren't Silicon Valley numbers, and they never will be. But they represent real communities, real farms, and real families making a living from the land.
## The Technical Foundation for the Future
The modern SvelteKit architecture I've built isn't just about escaping Rails. It's about enabling capabilities that were impossible before. The new, decoupled service layer means I can integrate complex third-party APIs for things like EBT and delivery logistics without risking the stability of the core platform—a task that would have been impossible on the old Rails monolith. Every integration point is now isolated, testable, and replaceable. A failed Stripe webhook can't bring down the shopping experience. A slow USDA API call for EBT verification won't freeze the entire application. This architecture makes the ambitious roadmap above not just possible, but achievable for a solo developer.
### Mobile-First by Default
```svelte
<!-- Every interface component now adapts naturally -->
<div class="product-grid">
{#each products as product}
<ProductCard {product} {market} />
{/each}
</div>
```
The platform is now truly mobile-responsive, something the old Rails app never achieved.
### Real-Time Inventory Management
```typescript
// API endpoint farmers can call from their phones in the field
export const POST: RequestHandler = async ({ request, locals }) => {
const { productId, quantity } = await request.json();
// Verify the farmer owns this product
if (!canEditProduct(locals.user, productId)) {
throw error(403, 'Unauthorized');
}
// Update through our service layer
const updated = await ProductService.updateQuantity(productId, quantity);
return json({ success: true, product: updated });
}
```
Farmers can now update availability from their phones in the field, and customers see the changes on their next page load.
### Performance at Scale
With Redis caching and database optimizations, the platform handles traffic spikes gracefully:
```typescript
// Market data cached in Redis for fast retrieval
export async function getMarketProducts(marketId: number) {
const cacheKey = `market:${marketId}:products`;
// Check Redis cache first
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// Fall back to database with optimized query
const products = await ProductService.getByMarket(marketId);
// Cache for next request
await redis.setex(cacheKey, 300, JSON.stringify(products));
return products;
}
```
Response times are consistently fast, even during peak Sunday night ordering.
## The 12-Month Roadmap
### Q4 2025: Foundation Solidification
*Building the bedrock of trust that markets need to depend on this platform for another 20 years*
-**Rails migration complete**
- Continue bug fixes and performance optimization
- Achieve 90% test coverage
- Automated market scheduling
- Enhanced monitoring and alerting (building on Sentry)
### Q1 2026: Access and Equity
*Fulfilling the promise that local food is for everyone, not just those who can afford it*
- SNAP/EBT online payment integration
- Multi-language support (Spanish first)
- ADA compliance audit and fixes (WCAG 2.2 AA)
- Low-bandwidth mode for rural areas
### Q2 2026: Community Features
*Strengthening the human connections that transform a transaction into a relationship*
- Farmer storytelling tools
- Recipe sharing based on available products
- Bulk buying coordination
- Community-supported agriculture (CSA) management
### Q3 2026: Delivery and Logistics
*Solving the last-mile problem to bring the harvest to the doorsteps of those who can't make it to market*
- Route optimization tools
- Driver mobile app
- Customer delivery tracking
- Integration with existing delivery services
{{< callout type="info" title="Market Managers" >}}
If you'd like to pilot scheduling automation, EBT, or delivery tools, [reach out](mailto:support@locallygrown.net). I'm prioritizing real-world pilots over lab demos.
{{< /callout >}}
## The Business Model: Built for Communities
LocallyGrown.net has always operated on a simple, sustainable pricing model: 3% of sales, with nothing upfront. Markets pay on their own schedule. This approach means the platform scales naturally as markets grow, and payment is never an impediment to getting started or continuing to operate. It's pricing that reflects the community-first philosophy of local food itself.
## Future Extensibility
While I'd love to extract and open source components where it makes sense, I'm still identifying what those pieces might be. One area I'm exploring is the design system. I'm planning to expose the theming framework to make it easier for designers to create safe, resilient themes. This would revive the marketplace for LocallyGrown themes that existed in the early days, bringing back the customization options that markets loved while maintaining the stability and security of the modern platform.
## Closing: The Real Work Begins Again
This entire journey, from a failed cooperative in 2002 to a high-stakes rescue mission in 2025, was never just about saving a Rails application. It was about preserving vital community infrastructure that hundreds of growers and thousands of customers rely on. It was about ensuring that the accidental innovation that transformed so many local food economies would have a chance to serve the next generation.
The new foundation is solid. The code is modern, the servers are resilient, and the path forward is clear.
My deepest gratitude flows to the market managers who kept faith during the rocky transition, the growers who depend on this platform for their livelihoods, and the customers who choose local food week after week. Your patience and feedback have shaped every line of code.
The real work of growing begins again.
---
_This is the final part of a series documenting the rescue and modernization of LocallyGrown.net. Thank you for joining me on my retelling of how I managed to make the impossible merely difficult._
### The Complete Series
If you've come in at the end, here's the whole journey in order:
1. [From Accidental Discovery to Agricultural Infrastructure (2002-2011)](https://blog.kestrelsnest.social/posts/locallygrown-origin-story/)
2. [The 23-Year Rescue Mission: Saving Agricultural Innovation from Technical Extinction](https://blog.kestrelsnest.social/posts/locallygrown-rescue-mission/)
3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-rails-svelte-migration/)
4. [Crisis Response: When Launch Day Goes Wrong](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/)
5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/)
6. **From Survival to Sustainability: The Next 20 Years**_You are here_
---
**Want to follow the journey?** Subscribe to this blog or follow me on [Bluesky](https://bsky.app/profile/kestrelsnest.social) or [Mastodon](https://toots.kestrelsnest.social/@eric) for real-time development stories.
**Want to use LocallyGrown?** Visit [locallygrown.net](https://locallygrown.net) to find a market near you or start your own.
**Want to help?** I'm always looking for feedback from markets, growers, and customers. Reach out at <support@locallygrown.net>.

View File

@@ -0,0 +1,163 @@
---
title: "Forty Years of Code, Still Can't Type: A Developer's Confession"
description: After four decades of writing code, I still feel like an impostor. Not because I can't solve problems, but because the mechanical act of typing them has always been excruciating. Until now.
date: 2025-11-17T23:46:08.582Z
preview: ""
draft: false
tags:
- programming
- ai
- impostor-syndrome
- development
categories: []
lastmod: 2025-11-18T01:05:54.604Z
---
_After forty years of writing code, I finally realized the thing that made me feel like an impostor had nothing to do with my ability to code—and everything to do with my fingers._
---
## TL;DR
{{< callout type="info" title="The Bottleneck" >}}
After 40 years of coding while terrible at typing and spelling, I discovered the bottleneck wasn't my brain. It was my fingers. Using AI-assisted development tools didn't replace my expertise; it eliminated the mechanical transcription that was draining most of my energy. The typing was never the "real work." The thinking always was.
**The difference:** I get more done in the same hours. Features ship faster. Bugs get fixed quicker. And I still feel energized when I'm done coding, not drained from forced mechanical precision.
{{< /callout >}}
---
I've been writing code for over forty years. I've never taken a single computer class. And despite building countless projects that work, that solve real problems, that create value, I've spent most of those four decades feeling like a fraud.
Here's my confession: I'm terrible at the mechanical act of coding.
**This isn't a story about replacing developers with AI.** It's about removing an unnecessary handicap so I could finally code the way my brain was wired to.
## The Love and the Struggle
I love creating digital things from thin air. The moment when you imagine something that could exist, work through the logic in your head, and then, solely by thinking, make it real. That part is magic, and that part I'm good at.
{{< callout type="example" title="How I Learned to Code" >}}
I started young, typing code out of magazines on an Apple ][+ in the early 1980s. I'd read guides from The Beagle Bros. about PEEK and POKE, try things out, study source code on shareware programs from hand-me-down floppies. I learned by doing, by playing, by emulating what I found.
I'm neurodivergent, and this learning style (experiential, hands-on, pattern-based) has always been how my brain works best. I could see the logic, understand the systems, grasp the patterns. The thinking part came naturally.
The typing part never did.
{{< /callout >}}
But there's a gap between my brain and the computer. A chasm, really. And crossing it has always felt like walking through quicksand.
I literally cannot touch type. I've tried to learn, so many times, but my fingers don't work that way. Ironically, when typing natural language (like this sentence), my index and middle fingers on both hands race across the keys faster than you'd guess. The words flow from thought to screen (though often not spelled as they should).
But code? Code demands perfection: the right words, the right spelling, the right syntax. Every brace, every semicolon, every character in exactly the right place. One hundred percent accuracy, no exceptions. And I'm a terrible speller on top of it all.
My brain excels at problem-solving. It's great at seeing patterns, understanding systems, architecting solutions. But it struggles with mechanical precision. And for forty years, I've been trying to force a problem-solving brain to do transcription work that requires a level of exactness my fingers have never mastered.
## The Reality of My Days
Here's what implementing a simple authentication check actually looked like for me:
**The Thinking:**
"Need to verify user has admin role OR owns this resource." It was clear, simple, immediate.
**The Transcription:**
1. Look up the syntax for our authorization library
2. Type the condition slowly, fixing typos as I go: `if (user.hasRole('admin') || resource.ownerId == user.id)`
3. Catch the typo myself: should be `===` not `==`
4. Remember to add error handling; look up try/catch syntax again
5. Add the try/catch block
6. Run the linter, fix the typos it catches
7. Re-read everything one more time to make sure
The thinking was quick. The transcription felt endless. The ratio was backwards.
By the end of a coding session, I'd be exhausted. Not the good exhaustion of solving hard problems, but the draining fatigue of transcription work. Like an introvert after hours in a loud room, depleted from the wrong kind of effort.
And I kept thinking: _Real developers don't struggle like this. Real developers don't make these basic mistakes. Real developers can just... type._
**The typing was necessary work. I was just really, really bad at it.** So bad that I developed an unsustainable coping mechanism over the decades: code frenzies. I'd psych myself up, marshal all my energy and focus, and have these huge bursts of productivity where I'd push through the exhaustion and transcribe as much as I could. Then I'd crash. For days afterward, I'd have no energy for coding. I could research, plan, think through problems, but actually writing code? I had to wait until I'd recovered enough to force another frenzy.
For me, this is what "being a developer" meant. This was my normal.
To bosses, clients, and teammates, it probably looked fine. The work got done. I hit deadlines. But I was working myself to mental exhaustion just to produce what other developers seemed to do effortlessly.
## The Turning Point
Then I started using Claude Code. Not as a replacement for my thinking (I'm still the one solving the problems, making the architectural decisions, determining what the code needs to do) but as a partner that handles the mechanical transcription while I maintain creative control.
Something unexpected happened.
{{< callout type="success" title="I Could Suddenly Fly" >}}
**The same authentication check, with Claude Code:**
Me: "I need to add authorization logic to the resource edit endpoint in the API routes. Here's the requirement: a user should be able to edit a resource if they have the admin role OR if they own that specific resource. The user object already has a hasRole method we use elsewhere, so use that for the admin check. For ownership, compare resource.ownerId to user.id, and make sure you use strict equality not loose equality. The authorization check needs to happen before any data modifications. If it fails, throw a 403 Forbidden with a message like 'You do not have permission to edit this resource.' Wrap the whole thing in try/catch so we handle any errors gracefully. And make sure all the TypeScript types are explicit."
Claude Code: [Generates the function with correct syntax, proper comparison operators, error handling, and TypeScript types]
Me: [Reviews the implementation, verifies the logic matches my requirements, checks edge cases, approves, commits]
**What just happened:** I spent my energy on the thinking and the explaining. Claude Code spent its energy on the transcription. No typos. No syntax lookup. No exhaustion.
{{< /callout >}}
The bottleneck between my brain and the computer was gone. I could think through a problem and see it implemented correctly, immediately. No typos, no syntax errors, no hunting down the same function signature for the dozenth time. And best of all, no exhaustion from forced mechanical precision.
I was getting more done, feeling better at the end of the day, and most importantly, I was still writing code, still creating, still solving problems.
I just wasn't spending most of my energy on transcription anymore.
## What Changed
With Claude Code, the frenzies stopped. I could work at a steady pace. Think through a problem, explain it in detail, review the implementation, move on to the next problem. No psyching myself up. No crashes. No cycles of burst and recovery.
For forty years, I'd been measuring myself against the wrong metric. "Good developers" were people who could type code quickly and accurately. But that was confusing the medium with the message.
I was always a great developer. I was just programming on hard mode for four decades.
## What This Isn't
{{< callout type="note" title="Important Context" >}}
This isn't an argument that everyone should use AI tools. Some developers genuinely enjoy the mechanical aspects of coding. Some find the typing meditative. Some think at the speed of their fingers. That's wonderful—code however your brain works best.
This also isn't suggesting that formal training isn't valuable, or that AI will replace programmers, or that the craft of software development doesn't matter.
If anything, it's the opposite. By removing my personal bottleneck, I can finally focus more fully on the craft itself—on writing better software, not just writing software better.
{{< /callout >}}
## Working at the Speed of Thought
These days, I work differently. When I need to implement something, I think through the problem, explain what needs to happen, and Claude Code handles the transcription. I review it, validate it, adjust it. The code is still mine—I'm still the one creating it, making the decisions, taking responsibility for it.
I'm just not exhausting myself typing it anymore.
**A Recent Example:**
Recently, I rebuilt the search functionality for my LocallyGrown.net farmers market platform. The old Rails implementation was slow and inflexible. I needed to add filters, improve performance, and support multiple market contexts.
With Claude Code:
- Designed the new architecture (my decisions about caching strategy, query optimization, API structure)
- Explained the requirements and constraints
- Reviewed the generated TypeScript services and Svelte components
- Caught edge cases Claude missed (markets with no active products, search terms with special characters)
- Adjusted and shipped
The work that used to take days of exhausting transcription now takes hours of focused problem-solving. Not because the thinking is easier, but because the mechanical bottleneck is gone.
The projects I'm building now ship faster, work better, and reach more people. The code is not fundamentally different, and I'm not draining my energy on transcription anymore.
---
Will the impostor syndrome ever fully fade? Probably not. There will always be ways, real or imagined, to measure myself against my peers. That's not something a tool can fix.
But now I have one more coping mechanism stripped away, one more mask that was hiding my neurodivergence that I can remove. The code frenzies, the exhaustion, the forced mechanical precision—I don't need those anymore to keep up.
After forty years of trying to force my brain to work like everyone else's, I can finally apply my actual strengths: the thinking, the problem-solving, the architecture, the creation of digital things from thin air.
The typing was never going to be my strength. And that's okay now.
{{< callout type="quote" title="If You've Ever Felt Like an Impostor" >}}
If you've felt like an impostor because the mechanical parts of your craft don't come naturally, here's my takeaway: **the tools you use don't determine your legitimacy. The problems you solve and the things you create do.**
Find the tools that let your brain work the way it actually works. Strip away the masks. Apply your strengths.
{{< /callout >}}

View File

@@ -0,0 +1,116 @@
---
title: Why My Payment Agent Is Named George, Not stripe-agent
description: I name my AI sub-agents after people who inspire me—not whimsy, but a practice for remembering who the work is actually for.
date: 2025-12-14T03:12:58.910Z
preview: ""
draft: false
tags:
- ai
- programming
- development
- philosophy
categories: []
lastmod: 2025-12-14T03:22:45.477Z
---
_Most developers name their AI sub-agents things like stripe-agent or security-checker. I named mine George, Agatha, Ray, and Helen. This isn't whimsy; it's how I remember who the work is actually for._
---
When I need to focus on payment integrations, I don't invoke `stripe-agent` or `payment-flow-optimizer`. I invoke George.
George Washington Carver spent his life transforming overlooked resources into sustainable value. He found hundreds of uses for the peanut, the sweet potato, the soybean (crops that others dismissed). He turned simple inputs into systems that supported entire communities.
That's what a good payment system does. It takes the simple act of exchanging money for goods and turns it into something that sustains the people who depend on it. When I invoke George while designing webhook handlers and subscription flows, I'm thinking about Carver's legacy: patient, resourceful, turning small things into something that lasts.
This is how I've come to work with AI-assisted development tools. I name my sub-agents after the people who inspire me, not because the tools have personalities, but because I need to stay intentional about who I'm serving. These aren't chatbots with personalities; they're specialized configurations I invoke by name to focus my intent.
## The Problem With Abstraction
The tech industry loves to abstract away the human. Users become "MAUs." Problems become "pain points." Customers become "conversions." I've spent over forty years writing software, and I can't afford to think that way.
Almost all of my work has served real humans with names and lives. Biologists creating DNA from scratch who needed to manage their labs. Astronomers who needed searchable sky atlases built from radio telescope data. Legal professionals who needed portals into dense law journals. Rural electric engineers who were ready to leave the paper and pencil era behind. Regular people who just wanted to file their taxes without drowning in forms.
And for over twenty years, farmers. Market managers. Neighbors trying to feed their families something better than what the grocery store offers. That's the work I've written about most recently, but it's one thread in a much longer career of building tools for people I'd never meet but whose problems I came to understand.
Naming my tools after people who inspire me is a small ritual that keeps me from forgetting who I'm building for.
## Where It Started
The first agent I named was Ray, after Ray Eames.
I was working on visual design, trying to get an interface right, and the functional name (something like `design-system-agent`) felt hollow. It reduced the work to a skill to be optimized rather than a craft in service of people.
So I named her Ray instead. And something clicked.
Every time I invoked her, I thought about Ray Eames. About the design revolution she helped create. About her belief that design should serve human needs, not just look clever. About chairs built for sitting, not for winning awards.
The name changed how I approached the work. It wasn't just "run the design agent." It was "think about this the way Eames would want me to think about it." Form serving function. Function serving humans. Touch targets sized for hands that have been working all day. Interfaces that work in bright sunlight or a dim office.
That's when I realized the naming wasn't a quirk. It was a practice.
## How It Actually Works
I don't chat with Ray directly. Claude's sub-agents are specialized configurations that the main agent can invoke for particular tasks. They're always there in the background, ready to be called on.
But I can suggest them by name. When I know we're working on a task where I'd want to apply the principles embodied by their real-life human counterparts, I can ask Claude to do the same.
Agatha is a good example. I named her after Agatha Christie, and her focus is security audits. Christie built a career on finding what everyone else missed: the detail that doesn't fit, the clue hiding in plain sight, the pattern that only becomes clear when you look from the right angle.
When I say "have Agatha review this before we ship," I'm not asking for a generic security scan. I'm saying that I need to look for what I missed. I need to find the secret traveling farther than it should, the data leaking where it shouldn't, the assumption I made that an attacker won't make. I need to be paranoid on behalf of the users whose data and trust I'm protecting.
The names aren't just labels. They're invocations. They shape my intent before the work even starts.
## The Chorus
Over time, I've built a small roster of these named agents. Beyond George, Ray, and Agatha, there's Ada for performance, Erma for user-facing text, Maya for marketing, Diderot for documentation, and Helen for accessibility. Each is named for someone whose work I admire in that space.
Something interesting happens when I apply multiple agents to the same work. When I'm preparing a feature for launch, I might invoke several of them in sequence, each bringing a different lens. It's not a literal conversation between them, but their perspectives compound.
I'm applying a chorus of values: Christie's attention to the telling detail, Eames's insistence on function serving humans, Lovelace's vision of what a system could become, Bombeck's warmth in the everyday, Keller's understanding of barriers and how to remove them.
On one recent feature, invoking Helen after Ada meant slowing a transition animation that technically passed performance benchmarks but broke screen reader timing. Ada's lens said it was fast enough. Helen's lens said it wasn't accessible enough. The human need won.
These are people who cared about craft and cared about the humans their craft served. I can't live up to their standards, not really. But I can try, and invoking their names is a way of keeping their values present in the work.
## Helen's Story
I added Helen to my roster just this week.
I'd been writing a blog post for my company about Blue Beanie Day, the annual celebration of web standards and accessibility. The core argument was simple: accessibility should be built in from day one, not retrofitted. When you try to add it after the fact, you're fighting against architectural decisions that assumed mouse interaction, fixing color choices that were never tested for contrast, spending ten times the effort you would have spent doing it right initially.
After I finished writing, I realized I wasn't fully practicing what I'd preached.
Ray had accessibility as part of her purview. Her configuration mentions WCAG compliance, color contrast, and keyboard navigation. But accessibility wasn't her focus; it was one concern among many.
Helen Keller didn't just understand accessibility. She lived it, advocated for it, made it her life's work. If I wanted accessibility to get that same dedicated attention in my own work, I needed someone whose entire purpose was watching for it.
So I created Helen. I tuned her configuration with WCAG criteria, screen reader compatibility, keyboard navigation patterns, and the whole audit framework. Then I added her to my roster and ran a full audit.
The audit found things, issues I'd missed even with Ray's broader design lens. Focus states that weren't quite visible enough. A few interactive elements that weren't fully keyboard accessible. Color contrast that passed in most contexts but failed in a few edge cases.
The practice works. The name made me create the agent. The agent's lens caught real problems. The problems got fixed. The software is better for everyone now, not just users with disabilities, but everyone who benefits from clear focus states and logical tab order and sufficient contrast.
## Software as Medium
I don't think of myself as someone who writes software. I think of myself as someone who solves human problems, and my medium happens to be telling computers what to do.
A carpenter doesn't love wood for its own sake; they love what wood becomes in service of the people who'll use it. A chef doesn't cook to impress other chefs; they cook to nourish and delight the people who'll eat. The material and the technique matter, but they're not the point.
When I write code, I'm not trying to impress other programmers with elegance or cleverness. I'm trying to make something that works for the biologist tracking samples, the astronomer searching the sky, the lawyer chasing a precedent, the engineer planning a grid, the taxpayer trying to get through April, the farmer checking their order list.
Naming my tools after humans keeps that truth at the center. The code serves people. The tools serve the code. The names remind me which direction the service flows.
## A Small Ritual
I don't know if this practice would help anyone else. It might be too personal, too idiosyncratic, too tied to the specific way my brain works.
But I offer it anyway, in case it resonates: consider naming your tools after the people whose work you admire. Not as a gimmick, but as a practice. A small ritual that asks you, every time you invoke them, _what would they think of what I'm building? Who am I building it for? Am I doing work they'd be proud to be associated with?_
The tools don't care what you call them. But you might find that you care, and that caring might make you better at the work.
---
George, Agatha, Ray, Helen, and the rest of my chorus. They're people who made things that mattered. I can't match what they accomplished, but every time I invoke them, I remember what this is all for.
Software is a means, not an end. The code serves the people. The names help me remember.

View File

@@ -0,0 +1,74 @@
---
title: I Thought I Had 15 Minutes
date: 2025-12-30T12:00:00-05:00
draft: true
tags:
- Programming
- Neurodivergence
- Automation
- n8n
lastmod: 2025-12-21T01:54:55.065Z
description: I automated the five minutes before standup, and accidentally learned something about how my brain works.
---
*The five minutes before standup cost more than the meeting itself—and I didn't realize it until I stopped paying.*
---
I'm three lines away from fixing a bug that's been nagging me all morning. The solution finally clicked, I'm typing it out, and—
Slack ping. "You coming to standup?"
I thought I had fifteen minutes. I'm already a minute late.
So now I'm joining the call with my brain still wrapped around a database query while trying to remember what I even worked on yesterday. Or it's Monday and I'm scrolling through my commit history on mute, hoping someone else goes first so I can piece together where I was on Friday.
This isn't the meeting's fault. I actually find standups valuable. The rapid-fire "what I did, what I'm doing, my blockers" surfaces connections early, keeps work from drifting into isolation. On the kinds of projects I work on, touching multiple codebases and solving bits of different problems throughout the week, that situational awareness genuinely helps.
But there's a thing about standups nobody talks about: even when the meeting is on my calendar, the preparation costs more than it should.
---
Here's the thing about my brain: I thrive on deliberate context-switching. Rotating through multiple problems, solving bits of each in turn, building them up in parallel. A payment integration in the morning, a data pipeline after lunch, a frontend redesign the next day. That rhythm keeps me engaged in a way that single-project focus never has.
But there's a critical word in that sentence: *deliberate*.
An unexpected ping, a meeting I lost track of, someone pulling my attention while I'm holding a complex system in my head. That can be devastating. An hour's worth of thought, collapsed in an instant. The structure I was building just... gone. Sometimes I never fully reconstruct it.
The difference is control. Choosing to rotate through problems is energizing. Having context switches imposed on me is exhausting. And the cost isn't the interruption itself—it's the *reconstruction time* afterward.
Standup prep felt like a chosen context switch. It was on my calendar. I knew when it was coming. But it still carried that reconstruction cost. I had to stop holding whatever I was currently working on, shift into a different mental mode entirely, and piece together a narrative of my week from scattered artifacts. Commits in GitHub. Tickets in Jira. Memory of Friday half-erased by the weekend. Five minutes of clicking through systems trying to assemble a coherent answer to "what did you do?"
That's when I realized the meeting wasn't costing me. The preparation was.
---
I'd had n8n installed on a server for a while, an automation tool I hadn't found the right use for yet.
Then I watched [Leon van Zyl's tutorial on automated standups](https://www.youtube.com/watch?v=CJMaqmYhKqU) and something clicked. Not just "I could build that" but a broader realization about where tools like n8n actually fit. They're not for the big obvious workflows that justify dedicated engineering time. They're for the quiet friction points you've learned to live with. The stuff that's annoying but not annoying *enough* to fix properly.
Standup prep was exactly that kind of problem.
So I built a workflow. Every morning at 8 AM it gathers my activity from the past 24 hours: commits from GitHub, tickets from Jira. It merges them together and sends them to Claude with a prompt asking for a conversational summary. Something I could read aloud without sounding like a robot.
The result lands in my inbox before I've finished my coffee. A few paragraphs telling me what I worked on, what's in progress, what's still open.
Natural and accurate, *no archaeology required*.
---
I expected the time savings. I didn't expect how it would feel.
There's something quietly satisfying about getting that email every morning. A small summary of what I accomplished, delivered without any effort on my part. Even on days when it feels like I didn't get much done, seeing the actual commit history usually proves otherwise.
This matters more than it might seem. My brain thrives on rotating through problems, but that rotation makes it harder to maintain a coherent narrative of what I've actually *done*. When I'm deep in one problem, I'm not thinking about the three other problems I solved earlier in the week. The work feels fragmented because my attention is fragmented—by design, because that's how I work best.
Having a system that automatically reconstructs that narrative turned out to be surprisingly meaningful. Beyond saving time, it's a small daily reminder that *scattered doesn't mean unproductive*.
---
Since building this, I've noticed more of those quiet friction points. Minor annoyances I'd normalized because fixing them felt like overkill. Triaging support emails. Generating SQL queries from natural language. Even auto-inking my kids' pencil drawings. None of them big enough to justify a real engineering project, but real enough to be worth an hour of wiring things together.
The standup automation taught me to look for them. And it taught me something about my own brain in the process—about the hidden costs I was paying without realizing it, and the value of systems that do reconstruction work so I don't have to.
If you want the technical details (the workflow JSON, the setup, the quirks I discovered), I wrote that up for [Infinity Interactive's blog](https://www.iinteractive.com/notebook/2025/12/18/automating-standup-prep.html). This piece is the human context around it. Why a simple automation mattered more than it should have, and what it accidentally revealed about how I work.

View File

@@ -0,0 +1,65 @@
---
title: Why I Self-Host My Social Media (and You Could Too)
description: I started with a $12 droplet and YunoHost. Four years later, I run my entire social media presence on it.
preview: In 2021, I gave myself ketamine treatments for my 50th birthday. One of the first signs they were working was that I wanted to build again. I started with a $12 droplet and YunoHost. Four years later, I run my entire social media presence on it.
date: 2025-12-20T12:00:00-05:00
draft: false
tags:
- Self-Hosting
- Fediverse
- YunoHost
- Social Media
lastmod: 2025-12-21T01:53:59.581Z
---
*I started with a $12 droplet and YunoHost. Four years later, I run my entire social media presence on it.*
---
In November 2021, I spun up a $12-a-month DigitalOcean droplet and installed YunoHost on it. I just wanted to play around, see what self-hosting felt like these days. Four years and two server upgrades later, that experiment runs my entire social media presence: Mastodon, Pixelfed, BookWyrm, Castopod, Gitea, and this blog. Soon it'll run Loops too, a brand new federated short-video platform that's just coming online.
I didn't plan any of this. Like most decisions that end up mattering, it started somewhere else entirely.
---
Two months before I set up that server, I turned fifty. To mark the occasion, I gave myself a gift: ketamine treatments for lifelong depression. The kind that doesn't respond to the usual interventions. The kind you learn to route around rather than actually fix. The kind that becomes so baseline you forget you're carrying it until something finally lifts it and you realize, *oh, this is what people feel like normally.*
The treatments worked. The fog lifted. Colors came back. And one of the first tangible signs that something had changed was that I wanted to *build* again.
Not code for clients. Not features for deadlines. I wanted to tinker and play and create a digital home that belonged to me. Something I could point to and say: this is mine, I made this, nobody can take it away.
So I installed [YunoHost](https://yunohost.org) on a small server and started adding apps.
---
YunoHost makes self-hosting approachable. It's a Debian-based system with a friendly web interface for managing applications, users, domains, and backups. Install an app, point a subdomain at it, and YunoHost handles the certificates and reverse proxy. All the fiddly bits that usually make self-hosting a headache just... work.
Could I manage all this myself? Sure. I've been a full-stack developer for over forty years. I could wrangle nginx configs and Let's Encrypt renewals and database backups manually. But the combination of a semi-managed VPS at DigitalOcean and a maintained app management system like YunoHost hits a sweet spot. I get ownership and control without spending my weekends on sysadmin busywork. As my usage grew, I bumped up the server twice and added automated backups. The costs crept up a bit, but the ease stayed the same.
It's also open source and community-driven. People package apps, maintain integrations, help each other in the forums. I ended up contributing too—I wrote the BookWyrm integration that's now used by plenty of other YunoHost deployments. When Loops stabilizes enough, I'll probably package that one myself.
---
Twitter's long decline had already started my migration. I'd been on the platform since its earliest days, watching it gradually curdle. When ownership changed in late 2022, I already had somewhere to go.
My Mastodon instance at toots.kestrelsnest.social was running. My Pixelfed at pix.kestrelsnest.social was ready for photos. I could leave without losing connections entirely—the fediverse meant I could still follow and be followed by people scattered across other servers.
Then Facebook and Instagram started their own collapse, and I was glad I'd gotten ahead of it. My data lived on my server, in my database, in my storage. The platforms could implode and I'd still have everything.
---
The sidebar on this blog lists where you can find me online. Almost every link points to a subdomain of kestrelsnest.social. Mastodon for microblogging, Pixelfed for photos, BookWyrm for tracking what I'm reading, Castopod for podcasting whenever I get around to it, Gitea for my code repositories.
I grew up on the old web, where you could have a home on the internet that actually belonged to you. I ran this blog on my own server through the first decade of the 2000s. Then I got lazy, let Twitter and Facebook become my default, and watched years of my words disappear into timelines I didn't control.
Self-hosting is just going back to what the web was supposed to be.
---
Could you do this yourself? Maybe. YunoHost really does lower the barrier. A basic VPS, a domain name, and a weekend can get you running. Their forums are helpful when you get stuck. And you can always find me at any of my socials if you want to ask questions. You don't need decades of experience, just patience and a willingness to learn.
The real question is whether you care enough about owning your data to invest the time. The corporate platforms will always be more convenient if all you want is to post and scroll. That convenience has costs, though. You just won't know the full price until it's too late.
I'm glad I started when I did. The infrastructure was already in place when the platforms started crumbling. Every time I add a new app—BookWyrm last year, Loops coming soon—I'm building out a space that can weather whatever collapse comes next.
The ketamine treatments were the gift I gave myself for turning fifty. The server might be the gift that lasts longest.

View File

@@ -5,9 +5,19 @@
{{ range where .Paginator.Pages "Type" "!=" "page" }} {{ range where .Paginator.Pages "Type" "!=" "page" }}
<li class="posts-list-item"> <li class="posts-list-item">
<a class="posts-list-item-title" href="{{ .Permalink }}">{{ .Title }}</a> <a class="posts-list-item-title" href="{{ .Permalink }}">{{ .Title }}</a>
{{ if .Params.series }}
{{ $series := index .Params.series 0 }}
{{ $order := .Params.series_order }}
<span class="series-indicator">
{{ partial "icon.html" (dict "ctx" $ "name" "book") }}
Part {{ $order }} of {{ $series }}
</span>
{{ end }}
<span class="posts-list-item-description"> <span class="posts-list-item-description">
<div> <div>
{{ if .Description }} {{ if .Params.preview }}
<br />{{ .Params.preview }}<br />(more inside)<br /><br />
{{ else if .Description }}
<br />{{ .Description }}<br />(more inside)<br /><br /> <br />{{ .Description }}<br />(more inside)<br /><br />
{{ else }} {{ else }}
{{ .Content }} {{ .Content }}

View File

@@ -0,0 +1,40 @@
{{ define "main" }}
<article class="post">
<header class="post-header">
<h1 class ="post-title">{{ .Title }}</h1>
{{- if ne .Type "page" }}
<div class="post-meta">
<div>
{{ partial "icon.html" (dict "ctx" $ "name" "calendar") }}
{{ .PublishDate.Format "Jan 2, 2006" }}
</div>
<div>
{{ partial "icon.html" (dict "ctx" $ "name" "clock") }}
{{ .ReadingTime }} min read
</div>
{{- with .Params.tags }}
<div>
{{ partial "icon.html" (dict "ctx" $ "name" "tag") }}
{{- range . -}}
{{ with $.Site.GetPage (printf "/%s/%s" "tags" . ) }}
<a class="tag" href="{{ .RelPermalink }}">{{ .Title }}</a>
{{- end }}
{{- end }}
</div>
{{- end }}
</div>
{{- end }}
</header>
<div class="post-content">
{{ .Content }}
</div>
{{/* Series navigation at the bottom of the post */}}
{{ partial "series.html" . }}
<div class="post-footer">
{{ template "_internal/disqus.html" . }}
</div>
</article>
{{ end }}

View File

@@ -0,0 +1,66 @@
{{ with .Params.series }}
{{ $currentPage := $ }}
{{ $series := index . 0 }}
{{ $currentOrder := $.Params.series_order }}
{{/* Get all posts in this series */}}
{{ $seriesPosts := where $.Site.RegularPages "Params.series" "intersect" (slice $series) }}
{{ $seriesPosts = sort $seriesPosts "Params.series_order" }}
{{ $totalParts := len $seriesPosts }}
{{ if gt $totalParts 1 }}
<div class="series-box">
<div class="series-header">
<strong>{{ $series }}</strong>
<span class="series-part">Part {{ $currentOrder }} of {{ $totalParts }}</span>
</div>
<nav class="series-nav">
<ol class="series-list">
{{ range $seriesPosts }}
<li class="{{ if eq .RelPermalink $currentPage.RelPermalink }}current{{ end }}">
{{ if eq .RelPermalink $currentPage.RelPermalink }}
<strong>{{ .Title }}</strong>
{{ else }}
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
{{ end }}
</li>
{{ end }}
</ol>
</nav>
<div class="series-navigation">
{{/* Previous post */}}
{{ $prevPost := "" }}
{{ $nextPost := "" }}
{{ $foundCurrent := false }}
{{ range $seriesPosts }}
{{ if $foundCurrent }}
{{ if not $nextPost }}
{{ $nextPost = . }}
{{ end }}
{{ else if eq .RelPermalink $currentPage.RelPermalink }}
{{ $foundCurrent = true }}
{{ else }}
{{ $prevPost = . }}
{{ end }}
{{ end }}
<div class="series-nav-links">
{{ with $prevPost }}
<a href="{{ .RelPermalink }}" class="series-prev">← Previous: {{ .Title }}</a>
{{ else }}
<span class="series-prev disabled">← Previous</span>
{{ end }}
{{ with $nextPost }}
<a href="{{ .RelPermalink }}" class="series-next">Next: {{ .Title }} →</a>
{{ else }}
<span class="series-next disabled">Next →</span>
{{ end }}
</div>
</div>
</div>
{{ end }}
{{ end }}

View File

@@ -1 +0,0 @@
*{box-sizing:border-box}html{line-height:1.6}body{margin:0;font-family:sans-serif;background:#353b43;color:#afbac4}h1,h2,h3,h4,h5,h6{color:#fff}a{color:#57cc8a;transition:color .35s;text-decoration:none}a:hover{color:#fff}code{font-family:monospace,monospace;font-size:1em;color:rgba(175,186,196,.8)}pre{font-size:1rem;line-height:1.2em;margin:0;overflow:auto}pre code{font-size:.8em}::selection{background:rgba(175,186,196,.25)}::-moz-selection{background:rgba(175,186,196,.25)}.app-header{padding:2.5em;background:#242930;text-align:center}.app-header-avatar{width:15rem;height:15rem;border-radius:100%;border:.5rem solid #57cc8a}.app-container{padding:2.5rem}.app-header-social{display:flex;align-items:center;justify-content:center;font-size:2em;color:#fff}.app-header-social a:not(:last-child){margin-right:.4em}.app-header-title{color:#fff;display:block;font-size:2em;margin:.67em 0;font-weight:700}@media(min-width:940px){.app-header{position:fixed;top:0;left:0;width:20rem;min-height:100vh}.app-container{max-width:65rem;margin-left:20rem}}.error-404{text-align:center}.error-404-title{text-transform:uppercase}.icon{display:inline-block;width:1em;height:1em;margin-top:-.125em}.pagination{display:block;list-style:none;padding:0;font-size:.8em;text-align:center;margin:3em 0}.page-item{display:inline-block}.page-item .page-link{display:flex;align-items:center;justify-content:center;width:1.8rem;height:1.8rem}.page-item.active .page-link{color:#fff;border-radius:2em;background:#57cc8a}.post-title{color:#fff}.post-meta>div{display:flex;align-items:center;font-size:.8em}.post-meta>div>.icon{margin-right:.4em}.post-content>pre,.post-content .highlight{margin:1em 0}.post-content>pre,.post-content .highlight>pre,.post-content .highlight>div{border-left:.4em solid rgba(87,204,138,.8);padding:1em}.post-content blockquote{border-left:.4em solid rgba(87,204,138,.8);margin:1em 0;padding:.5em 1em;background:#242930}.post-content blockquote p{margin:.5em 0}.post-content img{max-width:100%}.posts-list{padding:0}.posts-list-item{list-style:none;padding:.4em 0}.posts-list-item:not(:last-child){border-bottom:1px dashed rgba(255,255,255,.3)}.posts-list-item-description{display:flex;align-items:center;font-size:.8em}.posts-list-item-description>.icon{margin-right:.4em}.posts-list-item-separator{margin:0 .4em}.tag{display:inline-block;margin-right:.2em;padding:0 .6em;font-size:.9em;border-radius:.2em;white-space:nowrap;background:rgba(255,255,255,.1);transition:color .35s,background .35s}.tag:hover{transition:color .25s,background .05s;background:rgba(255,255,255,.3)}.tags-list{padding:0}.tags-list-item{display:flex;align-items:center;list-style:none;padding:.4em 0}.tags-list-item>.icon{margin-right:.4em}.tags-list-item:not(:last-child){border-bottom:1px dashed rgba(255,255,255,.3)}@media(min-width:450px){.tags-list{display:flex;flex-wrap:wrap}.tags-list-item{width:calc(50% - 1em)}.tags-list-item:nth-child(even){margin-left:1em}.tags-list-item:nth-last-child(2){border:none}}.app-header-social a:not(:last-child){margin-right:.2em}

View File

@@ -1 +0,0 @@
{"Target":"css/main.min.094178a756796576f886abd7963ac38503256532ad258acaed956b7f683180f4.css","MediaType":"text/css","Data":{"Integrity":"sha256-CUF4p1Z5ZXb4hqvXljrDhQMlZTKtJYrK7ZVrf2gxgPQ="}}

View File

@@ -1,984 +0,0 @@
*,
*::before,
*::after {
box-sizing: border-box; }
* {
margin: 0;
padding: 0; }
html {
font-size: 100%; }
body {
line-height: 1.5; }
html,
body {
height: 100%; }
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
margin: 1rem 0; }
input,
button,
textarea,
select {
font: inherit; }
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word; }
.highlight .hll {
background-color: #49483e; }
.highlight,
pre {
background: #272822;
color: #f8f8f2; }
.highlight .c {
color: #75715e; }
/* Comment */
.highlight .err {
color: #960050;
background-color: #1e0010; }
/* Error */
.highlight .k {
color: #66d9ef; }
/* Keyword */
.highlight .l {
color: #ae81ff; }
/* Literal */
.highlight .n {
color: #f8f8f2; }
/* Name */
.highlight .o {
color: #f92672; }
/* Operator */
.highlight .p {
color: #f8f8f2; }
/* Punctuation */
.highlight .ch {
color: #75715e; }
/* Comment.Hashbang */
.highlight .cm {
color: #75715e; }
/* Comment.Multiline */
.highlight .cp {
color: #75715e; }
/* Comment.Preproc */
.highlight .cpf {
color: #75715e; }
/* Comment.PreprocFile */
.highlight .c1 {
color: #75715e; }
/* Comment.Single */
.highlight .cs {
color: #75715e; }
/* Comment.Special */
.highlight .gd {
color: #f92672; }
/* Generic.Deleted */
.highlight .ge {
font-style: italic; }
/* Generic.Emph */
.highlight .gi {
color: #a6e22e; }
/* Generic.Inserted */
.highlight .gs {
font-weight: bold; }
/* Generic.Strong */
.highlight .gu {
color: #75715e; }
/* Generic.Subheading */
.highlight .kc {
color: #66d9ef; }
/* Keyword.Constant */
.highlight .kd {
color: #66d9ef; }
/* Keyword.Declaration */
.highlight .kn {
color: #f92672; }
/* Keyword.Namespace */
.highlight .kp {
color: #66d9ef; }
/* Keyword.Pseudo */
.highlight .kr {
color: #66d9ef; }
/* Keyword.Reserved */
.highlight .kt {
color: #66d9ef; }
/* Keyword.Type */
.highlight .ld {
color: #e6db74; }
/* Literal.Date */
.highlight .m {
color: #ae81ff; }
/* Literal.Number */
.highlight .s {
color: #e6db74; }
/* Literal.String */
.highlight .na {
color: #a6e22e; }
/* Name.Attribute */
.highlight .nb {
color: #f8f8f2; }
/* Name.Builtin */
.highlight .nc {
color: #a6e22e; }
/* Name.Class */
.highlight .no {
color: #66d9ef; }
/* Name.Constant */
.highlight .nd {
color: #a6e22e; }
/* Name.Decorator */
.highlight .ni {
color: #f8f8f2; }
/* Name.Entity */
.highlight .ne {
color: #a6e22e; }
/* Name.Exception */
.highlight .nf {
color: #a6e22e; }
/* Name.Function */
.highlight .nl {
color: #f8f8f2; }
/* Name.Label */
.highlight .nn {
color: #f8f8f2; }
/* Name.Namespace */
.highlight .nx {
color: #a6e22e; }
/* Name.Other */
.highlight .py {
color: #f8f8f2; }
/* Name.Property */
.highlight .nt {
color: #f92672; }
/* Name.Tag */
.highlight .nv {
color: #f8f8f2; }
/* Name.Variable */
.highlight .ow {
color: #f92672; }
/* Operator.Word */
.highlight .w {
color: #f8f8f2; }
/* Text.Whitespace */
.highlight .mb {
color: #ae81ff; }
/* Literal.Number.Bin */
.highlight .mf {
color: #ae81ff; }
/* Literal.Number.Float */
.highlight .mh {
color: #ae81ff; }
/* Literal.Number.Hex */
.highlight .mi {
color: #ae81ff; }
/* Literal.Number.Integer */
.highlight .mo {
color: #ae81ff; }
/* Literal.Number.Oct */
.highlight .sa {
color: #e6db74; }
/* Literal.String.Affix */
.highlight .sb {
color: #e6db74; }
/* Literal.String.Backtick */
.highlight .sc {
color: #e6db74; }
/* Literal.String.Char */
.highlight .dl {
color: #e6db74; }
/* Literal.String.Delimiter */
.highlight .sd {
color: #e6db74; }
/* Literal.String.Doc */
.highlight .s2 {
color: #e6db74; }
/* Literal.String.Double */
.highlight .se {
color: #ae81ff; }
/* Literal.String.Escape */
.highlight .sh {
color: #e6db74; }
/* Literal.String.Heredoc */
.highlight .si {
color: #e6db74; }
/* Literal.String.Interpol */
.highlight .sx {
color: #e6db74; }
/* Literal.String.Other */
.highlight .sr {
color: #e6db74; }
/* Literal.String.Regex */
.highlight .s1 {
color: #e6db74; }
/* Literal.String.Single */
.highlight .ss {
color: #e6db74; }
/* Literal.String.Symbol */
.highlight .bp {
color: #f8f8f2; }
/* Name.Builtin.Pseudo */
.highlight .fm {
color: #a6e22e; }
/* Name.Function.Magic */
.highlight .vc {
color: #f8f8f2; }
/* Name.Variable.Class */
.highlight .vg {
color: #f8f8f2; }
/* Name.Variable.Global */
.highlight .vi {
color: #f8f8f2; }
/* Name.Variable.Instance */
.highlight .vm {
color: #f8f8f2; }
/* Name.Variable.Magic */
.highlight .il {
color: #ae81ff; }
/* Literal.Number.Integer.Long */
/*
* Gist + Github styles
* Adapted from https://github.com/lonekorean/gist-syntax-themes
*/
body .gist .gist-meta,
body .gist .highlight,
body .gist .gist-file,
body .gist .gist-file .gist-data {
background: #272822;
border: none; }
body .gist .blob-num,
body .gist .blob-code-inner,
body .gist .highlight,
body .gist .pl-enm,
body .gist .pl-ko,
body .gist .pl-mo,
body .gist .pl-mp1 .pl-sf,
body .gist .pl-ms,
body .gist .pl-pdc1,
body .gist .pl-scp,
body .gist .pl-smc,
body .gist .pl-som,
body .gist .pl-va,
body .gist .pl-vpf,
body .gist .pl-vpu,
body .gist .pl-mdr {
color: #aab1bf;
font-family: ui-monospace, "SF Mono", SFMono-Regular, "ibm-plex-mono", "IBM Plex Mono", "Consolas", monospace; }
body .gist .pl-mb,
body .gist .pl-pdb {
font-weight: 700; }
body .gist .pl-c,
body .gist .pl-c span,
body .gist .pl-pdc {
color: #5b6270;
font-style: italic; }
body .gist .pl-sr .pl-cce {
color: #56b5c2;
font-weight: 400; }
body .gist .pl-ef,
body .gist .pl-en,
body .gist .pl-enf,
body .gist .pl-eoai,
body .gist .pl-kos,
body .gist .pl-mh .pl-pdh,
body .gist .pl-mr {
color: #61afef; }
body .gist .pl-ens,
body .gist .pl-vi {
color: #be5046; }
body .gist .pl-enti,
body .gist .pl-mai .pl-sf,
body .gist .pl-ml,
body .gist .pl-sf,
body .gist .pl-sr,
body .gist .pl-sr .pl-sra,
body .gist .pl-src,
body .gist .pl-st,
body .gist .pl-vo {
color: #56b5c2; }
body .gist .pl-eoi,
body .gist .pl-mri,
body .gist .pl-pds,
body .gist .pl-pse .pl-s1,
body .gist .pl-s,
body .gist .pl-s1 {
color: #97c279; }
body .gist .pl-k,
body .gist .pl-kolp,
body .gist .pl-mc,
body .gist .pl-pde {
color: #c578dd; }
body .gist .pl-mi,
body .gist .pl-pdi {
color: #c578dd;
font-style: italic; }
body .gist .pl-mp,
body .gist .pl-stp {
color: #818896; }
body .gist .pl-mdh,
body .gist .pl-mdi,
body .gist .pl-mdr {
font-weight: 400; }
body .gist .pl-mdht,
body .gist .pl-mi1 {
color: #97c279;
background: #020; }
body .gist .pl-md,
body .gist .pl-mdhf {
color: #df6b75;
background: #200; }
body .gist .pl-corl {
color: #df6b75;
text-decoration: underline; }
body .gist .pl-ib {
background: #df6b75; }
body .gist .pl-ii {
background: #e0c184;
color: #fff; }
body .gist .pl-iu {
background: #e05151; }
body .gist .pl-ms1 {
color: #aab1bf;
background: #373b41; }
body .gist .pl-c1,
body .gist .pl-cn,
body .gist .pl-e,
body .gist .pl-eoa,
body .gist .pl-eoac,
body .gist .pl-eoac .pl-pde,
body .gist .pl-kou,
body .gist .pl-mm,
body .gist .pl-mp .pl-s3,
body .gist .pl-mq,
body .gist .pl-s3,
body .gist .pl-sok,
body .gist .pl-sv,
body .gist .pl-mb {
color: #d19965; }
body .gist .pl-enc,
body .gist .pl-entc,
body .gist .pl-pse .pl-s2,
body .gist .pl-s2,
body .gist .pl-sc,
body .gist .pl-smp,
body .gist .pl-sr .pl-sre,
body .gist .pl-stj,
body .gist .pl-v,
body .gist .pl-pdb {
color: #e4bf7a; }
body .gist .pl-ent,
body .gist .pl-entl,
body .gist .pl-entm,
body .gist .pl-mh,
body .gist .pl-pdv,
body .gist .pl-smi,
body .gist .pl-sol,
body .gist .pl-mdh,
body .gist .pl-mdi {
color: #df6b75; }
body {
font-family: "ibm-plex-serif", "IBM Plex Serif", ui-serif, Georgia, "Times New Roman", serif;
font-size: 20px;
color: #bbbbbb;
background-color: #2b343e;
-webkit-text-size-adjust: 100%; }
li > ul,
li > ol {
margin-bottom: 0; }
h1, h1 a,
h2, h2 a,
h3, h3 a,
h4, h4 a,
h5, h5 a,
h6, h6 a {
color: #c5cdd6;
font-weight: bold; }
figure {
margin-bottom: 24px; }
a {
color: #bbbbbb; }
a:hover {
text-decoration: #797979 underline; }
.icon > svg {
display: inline-block;
width: 16px;
height: 16px;
vertical-align: middle; }
.icon > svg path {
fill: #797979; }
blockquote {
color: #797979;
border-color: #797979;
border-left: 4px solid #797979;
padding-left: 18px;
padding-bottom: 8px;
margin-top: 12px;
margin-bottom: 24px;
font-style: italic; }
blockquote code {
font-size: 0.8rem; }
blockquote p {
margin: 0.5rem 0; }
blockquote > :last-child {
margin-bottom: 0; }
blockquote ul {
margin-top: 6px; }
.center {
text-align: center; }
.end {
text-align: end; }
.start {
text-align: start; }
#site-footer,
.post-index,
figure.full-bleed,
.post-header,
.post-content,
.post-meta,
#site-footer,
.post-index {
min-width: 375px;
display: grid;
grid-template-columns: 1fr min(65ch, calc(100% - 2 * 24px)) 1fr; }
#site-footer > *,
.post-index > *,
figure.full-bleed > *,
.post-header > *,
.post-content > *,
.post-meta > *,
#site-footer > *,
.post-index > * {
grid-column: 2; }
figure.full-bleed {
grid-template-columns: 0fr 100% 0fr; }
@media only screen and (min-width: 601px) {
figure.full-bleed {
grid-column: 1 / 4; }
figure.full-bleed img {
width: 100%;
max-height: 100vh; } }
@media only screen and (max-width: 600px) {
figure.full-bleed {
margin-left: -24px;
grid-template-columns: 0fr calc(100% + 24px) 0fr; } }
.goto {
display: none;
filter: alpha(opacity=0);
-moz-opacity: 0;
opacity: 0;
text-align: center;
position: fixed;
width: 35px;
right: 12px;
border-radius: 50%;
border: 2px solid #797979;
background-color: #2b343e;
-webkit-transition: opacity 1s ease-in;
-moz-transition: opacity 1s ease-in;
-ms-transition: opacity 1s ease-in;
-o-transition: opacity 1s ease-in;
transition: opacity 1s ease-in; }
.goto a {
text-decoration: none;
color: #bbbbbb;
width: 100%;
display: block; }
.goto.top {
top: 12px; }
.goto.bottom {
bottom: 12px; }
.post-index h2.post-list-header {
color: #404d5c;
font-family: ui-monospace, "SF Mono", SFMono-Regular, "ibm-plex-mono", "IBM Plex Mono", "Consolas", monospace;
font-size: 25px;
font-weight: normal;
margin-bottom: 24px; }
.post-index h2.post-list-header + ul {
margin: 0 0 24px 24px; }
.post-index ul.post-list-content {
float: left;
list-style: none;
margin: 0;
padding: 0; }
.post-index ul.post-list-content .post-link {
font-family: "ibm-plex-sans", "IBM Plex Sans", ui-sans-serif, sans-serif;
font-size: 22px;
display: block;
float: left;
clear: left;
margin: 8px 0; }
@media only screen and (max-width: 600px) {
.post-index ul.post-list-content .post-link {
font-size: 19.5px; } }
@media only screen and (max-width: 600px) {
.post-index ul.post-list-content .post-link {
margin: 6px 0; } }
.post-index ul.post-list-content .post-link .post-descriptor {
float: left;
width: 12px;
margin-left: -22px; }
.post-index ul.post-list-content .post-link .post-descriptor :first-child {
color: #797979;
padding-right: 6px;
text-decoration: none; }
.post-index ul.post-list-content .post-link .post-descriptor :first-child:hover {
color: #bbbbbb; }
.post-index ul.post-list-content .post-link .post-descriptor .linklog {
font-family: ui-monospace, "SF Mono", SFMono-Regular, "ibm-plex-mono", "IBM Plex Mono", "Consolas", monospace;
width: 100%;
display: block;
color: #797979; }
.post-index ul.post-list-content .post-link .post-descriptor .linklog:hover {
color: #d7675d; }
.post-index ul.post-list-content .post-link .post-link-date {
color: #656565;
margin: 0;
clear: left;
white-space: nowrap;
font-size: 17.5px; }
@media only screen and (max-width: 600px) {
.post-index ul.post-list-content .post-link .post-link-date {
font-size: 15px; } }
.post-index ul.post-list-content .post-link a.post-link-url {
color: whitesmoke;
text-decoration: none;
padding-right: 6px; }
.post-index ul.post-list-content .post-link a.post-link-url:hover {
color: #d7675d; }
.post {
padding-top: 12px;
font-size: 20px; }
@media only screen and (max-width: 600px) {
.post {
font-size: 17.5px; } }
@media only screen and (max-width: 600px) {
.post {
margin: 0 auto; } }
.post .post-header {
margin-top: 96px; }
@media only screen and (max-width: 600px) {
.post .post-header {
margin-top: 36px;
float: none; } }
.post .post-header .post-author {
color: #797979;
font-weight: normal;
font-style: italic; }
.post .post-header .post-title {
color: whitesmoke;
margin: 24px 0 3px 0;
font-weight: bold;
font-family: "ibm-plex-serif", "IBM Plex Serif", "ff-tisa-web-pro", Georgia, "Times New Roman", serif;
line-height: 1.2;
font-size: 40px; }
@media only screen and (max-width: 600px) {
.post .post-header .post-title {
font-size: 30px; } }
.post .post-header .post-title .article-link {
color: whitesmoke;
text-decoration: none; }
.post .post-header .post-title .article-link:hover {
text-decoration: underline; }
.post .post-header .post-title .article-link .linklog {
color: #d7675d; }
@media only screen and (max-width: 600px) {
.post .post-header .post-title {
margin-top: 0; } }
.post .post-content {
padding-top: 48px;
clear: left;
line-height: 1.5;
font-size: 20px; }
@media only screen and (max-width: 600px) {
.post .post-content {
padding-top: 24px; } }
@media only screen and (max-width: 600px) {
.post .post-content {
font-size: 17.5px; } }
.post .post-content > p, .post .post-content > ul, .post .post-content > ol {
margin-bottom: 18px; }
@media only screen and (max-width: 600px) {
.post .post-content > p, .post .post-content > ul, .post .post-content > ol {
margin-bottom: 12px; } }
.post .post-content ol, .post .post-content ul {
padding-left: 30px;
padding-right: 30px; }
@media only screen and (max-width: 600px) {
.post .post-content ol, .post .post-content ul {
padding-left: 15px;
padding-right: 15px; } }
.post .post-content a {
text-decoration-color: #797979;
text-underline-offset: 4px;
text-decoration-thickness: 2px; }
.post .post-content a:hover {
text-decoration: none; }
.post .post-content h1 {
font-size: 40px;
margin: 1.5rem 0 1rem 0rem; }
@media only screen and (max-width: 600px) {
.post .post-content h1 {
font-size: 30px; } }
.post .post-content > h1:first-child {
margin-top: 1rem; }
.post .post-content h2 {
font-size: 30px;
margin: 1rem 0 1rem 0; }
@media only screen and (max-width: 600px) {
.post .post-content h2 {
font-size: 28px; } }
.post .post-content h3 {
font-size: 1rem;
font-style: italic;
font-weight: normal;
font-size: 20px;
margin-bottom: 0.5rem; }
@media only screen and (max-width: 600px) {
.post .post-content h3 {
font-size: 17.5px; } }
.post .post-content img {
display: block; }
.post .post-content img.full-bleed {
border-left: none;
border-right: none; }
.post .post-content img :not(.full-bleed) {
display: block;
max-width: 100%; }
.post .post-content .highlight {
margin-bottom: 24px; }
.post .post-content hr {
display: none; }
.post .post-content hr + p:first-letter {
float: left;
font-family: "ibm-plex-serif", "IBM Plex Serif", "ff-tisa-web-pro", Georgia, "Times New Roman", serif;
line-height: 30px;
padding-right: 6px; }
@media only screen and (max-width: 600px) {
.post .post-content hr + p:first-letter {
font-size: 50px;
padding-top: 12px; } }
@media only screen and (min-width: 601px) {
.post .post-content hr + p:first-letter {
font-size: 56px;
padding-top: 17px; } }
.post .post-content figure figcaption {
font-size: 17.5px;
margin-top: 6px; }
.post .post-content figure:not(.full-bleed) a:not(.center) {
margin-left: 24px; }
.post .post-content figure.full-bleed figcaption {
text-align: center; }
.post .post-content .footnotes {
font-size: 17.5px;
padding: 24px 0 0 0; }
@media only screen and (max-width: 600px) {
.post .post-content .footnotes {
margin-bottom: 0; } }
.post .post-content .footnotes ol {
margin-left: 0; }
.post .post-meta {
text-transform: none;
padding-top: 48px; }
@media only screen and (max-width: 600px) {
.post .post-meta {
padding-top: 24px; } }
.post .post-meta .post-date {
color: #bbbbbb;
font-size: 20px; }
@media only screen and (max-width: 600px) {
.post .post-meta .post-date {
font-size: 17.5px; } }
.post .post-meta .post-permalink {
/* border-top: 1px solid $color-text; */
width: 20%;
margin-bottom: 0; }
.post .post-meta .post-permalink a {
font-size: 17.5px;
color: whitesmoke;
text-decoration: none; }
@media only screen and (max-width: 600px) {
.post .post-meta .post-permalink a {
font-size: 15px; } }
.post .post-meta .post-permalink a:hover {
color: #d7675d; }
.post .post-meta .post-taxonomies {
margin: 1rem 0 1.5rem 0;
display: flex;
justify-content: flex-start; }
.post .post-meta .post-related {
border: 3px solid #797979;
padding: 1rem;
margin-top: 24px; }
.post .post-meta .post-related:hover {
color: whitesmoke;
border-color: whitesmoke; }
.post .post-meta .post-related a {
color: inherit;
text-decoration: none; }
.post .post-meta .post-related a:hover {
color: #d7675d; }
.post .post-meta .post-related ol, .post .post-meta .post-related ul {
margin-left: 24px; }
.post .post-meta .post-taxonomy {
border: 2px solid #797979;
margin-right: 1rem;
text-align: center;
font-size: 1.2rem;
font-weight: 500; }
.post .post-meta .post-taxonomy:hover {
color: #d7675d;
border-color: #d7675d; }
.post .post-meta .post-taxonomy a {
display: block;
padding: 6px 12px;
color: inherit;
text-decoration: none; }
.post .post-meta .post-taxonomy a:hover {
color: inherit; }
.draft {
background-color: #d7675d;
border-radius: 12px;
border: 2px solid #000;
color: black;
font-size: 13.35px;
font-weight: bold;
text-transform: uppercase;
vertical-align: middle;
padding: 0 6px;
margin-right: 6px;
margin-top: 6px; }
.callout:not(figure) {
border: 2px solid #797979;
padding: 24px;
margin-bottom: 24px; }
ul.callout {
margin-left: 0;
padding-left: 30px; }
img.callout {
margin-bottom: 0; }
.sidenote {
padding-left: 12px;
margin-bottom: 1.5rem;
border-left: 1px solid #797979;
color: #797979;
font-style: italic;
font-size: 0.8rem; }
.sidenote-hover {
border-color: #d7675d;
color: #d7675d; }
code {
font-family: ui-monospace, "SF Mono", SFMono-Regular, "ibm-plex-mono", "IBM Plex Mono", "Consolas", monospace; }
article.post-content pre {
font-size: 0.75rem;
padding: 1rem;
overflow: auto; }
article.post-content ol code,
article.post-content ul code,
article.post-content p > code {
font-size: 0.98rem;
color: #e3d1b9; }
#site-footer {
padding: 24px 0; }
@media only screen and (min-width: 601px) {
#site-footer {
margin: 2rem; } }
#site-footer .newsletter {
grid-column: 2;
display: grid;
grid-gap: 6px;
margin-bottom: 48px; }
@media only screen and (max-width: 600px) {
#site-footer .newsletter {
padding: 0 12px; } }
@media only screen and (min-width: 601px) {
#site-footer .newsletter {
grid-template-columns: 48% 1fr 48%; } }
#site-footer .newsletter input {
text-align: center;
padding: 0.5rem; }
#site-footer .newsletter input[type="text"] {
border: 1px solid #797979; }
@media only screen and (min-width: 601px) {
#site-footer .newsletter input[type="text"] {
grid-column-start: 1; } }
#site-footer .newsletter input[type="submit"] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: lightgray;
color: black; }
@media only screen and (min-width: 601px) {
#site-footer .newsletter input[type="submit"] {
grid-column-start: 3; } }
.site-nav ol, .site-nav ul {
display: grid;
row-gap: 12px;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
list-style-type: none;
margin-left: 0;
text-align: center; }
@media only screen and (max-width: 600px) {
.site-nav ol, .site-nav ul {
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); } }
.site-nav ol .nav-link, .site-nav ul .nav-link {
display: block;
padding-top: 6px;
padding-bottom: 6px;
text-decoration: none;
font-size: 20px;
border-top: 2px solid transparent;
border-bottom: 2px solid transparent; }
@media only screen and (max-width: 600px) {
.site-nav ol .nav-link, .site-nav ul .nav-link {
font-size: 15px; } }
.site-nav ol .nav-link:hover, .site-nav ul .nav-link:hover {
border-top: 2px solid #d7675d;
border-bottom: 2px solid #d7675d; }
.powered-by {
font-size: 0.75rem;
text-align: center;
margin: 36px 24px; }
.powered-by > a {
text-decoration: none;
color: whitesmoke; }
.powered-by > a:hover {
color: #d7675d; }
/*# sourceMappingURL=style.css.map */

View File

@@ -1 +0,0 @@
{"Target":"css/style.css","MediaType":"text/css","Data":{}}

BIN
static/eggs.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 MiB