[object Object]

← back to Goodquestion Ai

feat: refresh goodquestion.ai — catch up blog, modernize design, refresh projects/pages, v1.2.0

336a3beff957ff586a5b877656f032714ebf4e6d · 2026-07-27 09:56:33 -0700 · Steve

- Blog: +9 posts (2026-05-06 → 07-26) authored from real CNCP wins, closing
  the ~15-week gap since the last post (2026-04-12). Public-safe, honest metrics.
- Design: modernized global.css (fluid type scale, motion/radius/shadow tokens,
  added light mode + focus-visible), refreshed hero + post-list + reading layout
  in index.astro & layouts. All original CSS var names preserved.
- Projects/pages: +7 real shipped projects in projects.ts (GovArbitrage, National
  Real Estate Explorer, Client Growth Portals, AI Wallpaper, Directories, 90210
  Guide, Command Center); sharpened about.astro + hire.astro positioning.

Build-verified: 71 pages (was 54), 0 errors. Dev-verified: all routes 200.

Files touched

Diff

commit 336a3beff957ff586a5b877656f032714ebf4e6d
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jul 27 09:56:33 2026 -0700

    feat: refresh goodquestion.ai — catch up blog, modernize design, refresh projects/pages, v1.2.0
    
    - Blog: +9 posts (2026-05-06 → 07-26) authored from real CNCP wins, closing
      the ~15-week gap since the last post (2026-04-12). Public-safe, honest metrics.
    - Design: modernized global.css (fluid type scale, motion/radius/shadow tokens,
      added light mode + focus-visible), refreshed hero + post-list + reading layout
      in index.astro & layouts. All original CSS var names preserved.
    - Projects/pages: +7 real shipped projects in projects.ts (GovArbitrage, National
      Real Estate Explorer, Client Growth Portals, AI Wallpaper, Directories, 90210
      Guide, Command Center); sharpened about.astro + hire.astro positioning.
    
    Build-verified: 71 pages (was 54), 0 errors. Dev-verified: all routes 200.
---
 package-lock.json                                  |   4 +-
 package.json                                       |   2 +-
 ...n-that-was-quietly-serving-the-wrong-website.md |  58 ++
 ...-surgery-shrinking-a-repo-from-18gb-to-571mb.md |  70 +++
 ...0-teaching-my-monitoring-to-stop-crying-wolf.md |  60 +++
 ...were-broken-for-weeks-and-no-audit-caught-it.md |  62 +++
 ...acking-a-fleet-of-broken-scrapers-in-one-day.md |  57 ++
 ...g-a-real-estate-deal-ranker-from-public-data.md |  58 ++
 ...a-command-center-that-tracks-its-own-p-and-l.md |  63 +++
 ...the-four-layer-bug-that-hid-behind-a-comment.md |  55 ++
 ...a-social-command-center-for-a-local-business.md |  64 +++
 ...review-that-caught-what-my-own-checks-missed.md |  60 +++
 src/data/projects.ts                               | 105 ++++
 src/layouts/BlogLayout.astro                       |  33 +-
 src/layouts/PostLayout.astro                       |  74 ++-
 src/pages/about.astro                              |  17 +-
 src/pages/hire.astro                               |  79 +--
 src/pages/index.astro                              | 593 ++++++++++++++-------
 src/styles/global.css                              | 448 +++++++++++-----
 19 files changed, 1571 insertions(+), 391 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 44575cf..641dfff 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "goodquestion-ai",
-  "version": "1.1.1",
+  "version": "1.2.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "goodquestion-ai",
-      "version": "1.1.1",
+      "version": "1.2.0",
       "dependencies": {
         "@astrojs/mdx": "^7.0.4",
         "@astrojs/sitemap": "^3.7.3",
diff --git a/package.json b/package.json
index 16ba245..85559ab 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "goodquestion-ai",
   "type": "module",
-  "version": "1.1.1",
+  "version": "1.2.0",
   "description": "Agent Abrams - A developer's journey with Claude Code",
   "scripts": {
     "dev": "astro dev --host",
diff --git a/src/content/posts/2026-05-06-the-domain-that-was-quietly-serving-the-wrong-website.md b/src/content/posts/2026-05-06-the-domain-that-was-quietly-serving-the-wrong-website.md
new file mode 100644
index 0000000..da56a04
--- /dev/null
+++ b/src/content/posts/2026-05-06-the-domain-that-was-quietly-serving-the-wrong-website.md
@@ -0,0 +1,58 @@
+---
+title: "The Domain That Was Quietly Serving the Wrong Website"
+description: "A community site had been showing another project's content for a week, and nobody noticed — because nobody was looking."
+date: 2026-05-06
+tags: ["debugging", "nginx", "infrastructure", "site-factory", "small-business"]
+---
+
+Today I found a domain that had been serving the wrong website for at least a week. Not down. Not throwing errors. Just quietly rendering someone else's product under its own brand — and no monitoring caught it because "the site returns 200" is not the same as "the site is correct."
+
+## The Symptom
+
+One of my community-site domains was supposed to be a local neighborhood board. Instead, anyone who hit the URL saw a completely different project's content: an address-history archive, complete with random thumbnail images pulled from an unrelated data source, all wearing the wrong site's branding.
+
+To a visitor, it looked like a broken, off-brand site. To my uptime checks, it looked perfectly healthy. That gap is the whole story.
+
+## The Root Cause
+
+The site was built off a shared "site factory" template, and the nginx vhost had a subtle defect: two `proxy_pass` directives, one of which pointed at a *foreign upstream* — a completely different app on a different port. So requests were bleeding across projects. One template bug, silently copied into who-knows-how-many other vhosts.
+
+This is the kind of failure that static monitoring will never catch. The process is up. The port answers. The HTTP status is 200. Everything green. And the customer sees garbage.
+
+## The Fix
+
+Two parts:
+
+1. **Stop the bleed.** I killed the cross-proxy directive so the domain only talks to its own app. Immediately the foreign header content disappeared.
+2. **Build the actual thing.** The domain had never had a real product behind it — just a stripped auth/payment stub. So I replaced it with what it was always supposed to be: a real community board with announcements, a local-events calendar, neighborhood-scoped listings, and a founding-cohort post gate. Eight routes, all returning 200, all serving the correct content.
+
+## The Lesson That Actually Matters
+
+"Is the site up?" is the wrong question. The right question is "is the site showing the right thing?" — and those are answered by completely different tools.
+
+An HTTP 200 tells you a process is listening. It tells you nothing about whether the bytes coming back are the *correct* bytes. I've started treating "returns 200" as table stakes, not a pass. The real check is: does the rendered page contain the content this specific domain is supposed to serve, and *not* content that belongs to a sibling project?
+
+I also flagged the dual-`proxy_pass` pattern as something to sweep across the rest of the fleet, because a template bug is never in just one place. If you copy-paste your infrastructure, you copy-paste your bugs too.
+
+## Why This Matters for Business Owners
+
+If you have more than a handful of sites, you cannot personally eyeball each one every day. But you also can't trust a naive uptime pinger — it will happily report a hijacked, mislabeled, or blank page as "healthy" and let it rot for a week.
+
+The move is content-aware monitoring: assert that each page contains the text and assets it's *supposed* to, and screams when it drifts. It costs a little more to set up. It costs a lot less than a customer landing on your brand and seeing someone else's broken movie-poster thumbnails.
+
+## Let's Connect
+
+I build AI-powered automation for real businesses — not demos, not prototypes, production systems that run 24/7.
+
+If you're a **founder, entrepreneur, or small business owner** looking to automate operations with AI, let's talk:
+
+- [**@agentabrams on YouTube**](https://youtube.com/@AgentAbrams) — walkthroughs and demos
+- [**@agentabrams on X**](https://x.com/agentabrams) — DMs open
+- [**@agentabrams on Bluesky**](https://bsky.app/profile/agentabrams.bsky.social) — follow along
+- [**goodquestion.ai**](https://goodquestion.ai) — you're here
+
+**Advisory & Board Opportunities:** I'm actively looking to join boards where AI automation can drive real business value. If your company is exploring AI-driven operations, data pipelines, or autonomous agent systems — I'd love to contribute as a board member or advisor. Reach out on any platform above.
+
+---
+
+*Built with [Claude Code](https://claude.ai). Shipped in production. Every day. This is what one founder + AI looks like.*
diff --git a/src/content/posts/2026-05-18-git-surgery-shrinking-a-repo-from-18gb-to-571mb.md b/src/content/posts/2026-05-18-git-surgery-shrinking-a-repo-from-18gb-to-571mb.md
new file mode 100644
index 0000000..c32d837
--- /dev/null
+++ b/src/content/posts/2026-05-18-git-surgery-shrinking-a-repo-from-18gb-to-571mb.md
@@ -0,0 +1,70 @@
+---
+title: "Git Surgery: Shrinking a Repo from 18GB to 571MB"
+description: "How binary junk and leaked secrets quietly bloat a repo — and the afternoon of history rewriting it takes to fix both."
+date: 2026-05-18
+tags: ["git", "security", "data-quality", "infrastructure", "founder-log"]
+---
+
+My main commerce repo had a `.git` directory of 18 gigabytes. Not the working tree — the *history*. Today I got it down to 571 MB and, in the same pass, scrubbed a pile of leaked credentials out of every commit. Net reclaimed: about 21 GB of disk. Here's the actual work.
+
+## How a Repo Gets to 18GB
+
+The working tree wasn't 18GB. The *history* was. Over 14 commits, a bunch of things had been committed that never should have been version-controlled:
+
+- Rendered room-setting images (regenerable — never belong in git)
+- Shopify backup dumps
+- Debug output dumps
+- A 298 MB runtime database JSON
+- Whole directories of collection images and design assets
+
+The problem with binary junk in git is that deleting it from the current commit does *nothing* to the repo size. Git keeps every version of every file forever. The 18GB is baked into the history. The only way to reclaim it is to rewrite the history itself.
+
+## The Surgery
+
+I used `git filter-repo` to strip those paths out of all 14 commits — not just the latest, all of them. Then I updated `.gitignore` so the junk can never sneak back in, deleted the 18GB room-settings directory from disk entirely, and kept a full backup of the original `.git` in case anything went sideways.
+
+Result: `.git` went 18GB → 571MB, and the machine went from 23GB free to 44GB free. Since this repo has no remote configured, nothing was ever pushed anywhere — the rewrite was purely local and safe.
+
+## The Part That Actually Scared Me: Leaked Secrets
+
+While I was in the history anyway, I audited for hardcoded credentials. It was bad:
+
+- A Shopify admin API token, hardcoded across **208 files**
+- A basic-auth password, hardcoded across **78 files**
+- A base64-encoded credential blob
+- Six stale `.backup` files sitting there holding plaintext secrets
+
+Step one was refactoring every one of those hardcoded values to read from `process.env` instead. Step two was `git filter-repo --replace-text` to wipe the actual token, password, and cred string out of all 18 commits. Step three was verification: `git fsck` clean, and a scan confirmed **zero** secret blobs anywhere in history.
+
+The saving grace: this repo never had a remote, so the secrets were never publicly exposed. But "never exposed because of luck" is not a security posture. The only leftover item — rotating the token as a belt-and-suspenders move — is a one-click thing I left for later.
+
+## What I Took Away
+
+**Binary artifacts and secrets are the two things that quietly poison a git repo,** and both are invisible in a normal `git status`. You have to go looking.
+
+- If a file is *regenerable* (rendered images, DB dumps, build output), it does not belong in git. Put it in `.gitignore` on day one, because getting it *out* later means rewriting history.
+- Hardcoded secrets don't announce themselves. They hide in fallback values, backup files, and test fixtures. Grep your whole tree, not just the file you're editing.
+- History rewriting is scary but reversible if you back up `.git` first. Always back up `.git` first.
+
+## Why This Matters for Business Owners
+
+That 18GB wasn't costing me money directly — but it was slowing every clone, every backup, every diff. And the leaked credentials were a genuine liability that had simply not been triggered yet. The whole cleanup was one focused afternoon. The alternative — discovering a leaked admin token *after* someone else did — is not an afternoon. It's a very bad week.
+
+If you ship fast, do a history-and-secrets audit periodically. It's cheap insurance against expensive problems.
+
+## Let's Connect
+
+I build AI-powered automation for real businesses — not demos, not prototypes, production systems that run 24/7.
+
+If you're a **founder, entrepreneur, or small business owner** looking to automate operations with AI, let's talk:
+
+- [**@agentabrams on YouTube**](https://youtube.com/@AgentAbrams) — walkthroughs and demos
+- [**@agentabrams on X**](https://x.com/agentabrams) — DMs open
+- [**@agentabrams on Bluesky**](https://bsky.app/profile/agentabrams.bsky.social) — follow along
+- [**goodquestion.ai**](https://goodquestion.ai) — you're here
+
+**Advisory & Board Opportunities:** I'm actively looking to join boards where AI automation can drive real business value. If your company is exploring AI-driven operations, data pipelines, or autonomous agent systems — I'd love to contribute as a board member or advisor. Reach out on any platform above.
+
+---
+
+*Built with [Claude Code](https://claude.ai). Shipped in production. Every day. This is what one founder + AI looks like.*
diff --git a/src/content/posts/2026-05-30-teaching-my-monitoring-to-stop-crying-wolf.md b/src/content/posts/2026-05-30-teaching-my-monitoring-to-stop-crying-wolf.md
new file mode 100644
index 0000000..e6bfc88
--- /dev/null
+++ b/src/content/posts/2026-05-30-teaching-my-monitoring-to-stop-crying-wolf.md
@@ -0,0 +1,60 @@
+---
+title: "Teaching My Monitoring to Stop Crying Wolf"
+description: "A day spent making alerts trustworthy again — because a monitor that fires 291 times a day is a monitor you've already learned to ignore."
+date: 2026-05-30
+tags: ["monitoring", "reliability", "automation", "observability", "founder-log"]
+---
+
+Two problems today, one theme: the systems that are supposed to tell you when something's wrong will happily lie to you if you let them. A monitor that alerts constantly is functionally the same as no monitor at all — because you stop reading it.
+
+## Problem One: The Security Alert That Was Nothing
+
+My command center kept surfacing a high-severity "bad key share" alert. High severity gets your attention. So I dug in.
+
+The root cause was completely benign: a VPN scanner out on the internet was sending a malformed TLS handshake — a client-side reject that nginx correctly bounced. It's background noise. The internet is full of scanners poking at every open port. This one just happened to trip a log pattern that got classified as scary.
+
+The fix had two layers. There was already a "benign filter" written to catch exactly this kind of thing — it just was never actually wired into the log collector. So I wired it in. Then I added a durable suppression rule at the ingestion layer, so future TLS-probe noise auto-dismisses the moment it arrives, no manual triage. I tested it by feeding a synthetic copy of the alert through and confirming it got dismissed automatically.
+
+## Problem Two: The Monitor That Failed 291 Times a Day
+
+On a different box, a service-monitor was flooding the ledger with **291 events**. The monitor was checking whether services were alive — and it was the monitor *itself* that kept failing.
+
+Here's the trap: the monitor detected a defunct entry (a port with nothing listening and no process behind it) and *exited non-zero* to signal "something's wrong." But the supervisor interpreted a non-zero exit as "the monitor unit failed" — and logged an event. Every two minutes. Forever.
+
+So a *correctly detected* problem was being recorded as a *monitor failure*, 291 times over. The signal and the noise had swapped places.
+
+The fix path: harden the monitor so that detecting an outage no longer makes the monitor mark *itself* as failed, plus remove the stale defunct entry it was tripping on. (I also caught a plaintext token leaking in one of the service unit files while I was in there — a separate finding, but that's how it goes when you actually read the logs instead of skimming them.)
+
+## The Real Lesson
+
+**Alert fatigue is a bug, not a personality trait.** When your monitoring cries wolf 291 times, the failure isn't that you started ignoring it — the failure is the monitoring.
+
+Two principles I keep relearning:
+
+1. **A monitor's own failure mode must be distinct from the failures it detects.** If "I found a problem" and "I broke" produce the same signal, you've built a machine that can't tell you anything.
+2. **Suppression has to be durable and self-verified.** Dismissing one noisy alert by hand is a chore you'll be doing again tomorrow. Wiring the suppression into ingestion — and proving it works with a synthetic test — means the noise is gone for good.
+
+## Bonus: Hands-Free Recovery
+
+Same day, I also built a small thing I'd wanted for a while: a watcher that detects when a session hits a rate limit and automatically resumes it once the limit clears. It reads the pane, matches the throttle message (even when the terminal hard-wraps it mid-word), respects a cooldown, and has a kill-switch. Small quality-of-life win, but it means stalled work recovers itself instead of waiting for me to notice.
+
+## Why This Matters for Business Owners
+
+The whole point of monitoring is to earn your trust so you can *stop* watching. A noisy monitor does the opposite — it demands attention while providing none. If your dashboards are a wall of red you've learned to scroll past, they're not protecting you. They're theater. Tuning them so that a red light *actually means something* is some of the highest-leverage work you can do.
+
+## Let's Connect
+
+I build AI-powered automation for real businesses — not demos, not prototypes, production systems that run 24/7.
+
+If you're a **founder, entrepreneur, or small business owner** looking to automate operations with AI, let's talk:
+
+- [**@agentabrams on YouTube**](https://youtube.com/@AgentAbrams) — walkthroughs and demos
+- [**@agentabrams on X**](https://x.com/agentabrams) — DMs open
+- [**@agentabrams on Bluesky**](https://bsky.app/profile/agentabrams.bsky.social) — follow along
+- [**goodquestion.ai**](https://goodquestion.ai) — you're here
+
+**Advisory & Board Opportunities:** I'm actively looking to join boards where AI automation can drive real business value. If your company is exploring AI-driven operations, data pipelines, or autonomous agent systems — I'd love to contribute as a board member or advisor. Reach out on any platform above.
+
+---
+
+*Built with [Claude Code](https://claude.ai). Shipped in production. Every day. This is what one founder + AI looks like.*
diff --git a/src/content/posts/2026-06-03-the-images-were-broken-for-weeks-and-no-audit-caught-it.md b/src/content/posts/2026-06-03-the-images-were-broken-for-weeks-and-no-audit-caught-it.md
new file mode 100644
index 0000000..c005165
--- /dev/null
+++ b/src/content/posts/2026-06-03-the-images-were-broken-for-weeks-and-no-audit-caught-it.md
@@ -0,0 +1,62 @@
+---
+title: "The Images Were Broken for Weeks and No Audit Caught It"
+description: "Why static checks miss real outages, and how one round of actually clicking through the site found four broken images that had been dead since May."
+date: 2026-06-03
+tags: ["debugging", "testing", "reliability", "infrastructure", "founder-log"]
+---
+
+On one of my catalog sites, four product images on a live page had been returning 502 errors for weeks. My health checks were green the whole time. The lesson is old but I keep having to relearn it: static audits tell you the server is up; they do not tell you the page *works*.
+
+## What "Green" Was Hiding
+
+The site reported healthy. The process was running, the homepage loaded, the API answered. By every automated measure I had, everything was fine.
+
+Then I did something my automation wasn't doing: I actually loaded a catalog page and looked at it. Four art-archive images on that page were 502ing. Dead. Had been for weeks.
+
+## The Chain of Failure
+
+The images on that page didn't live on the same server as the site. They were served through a bridge — a little network relay that forwarded image requests from the public site to a separate machine on my private network that actually held the JPEGs.
+
+Two things had broken, and both had to break for the images to fail:
+
+1. **The bridge got orphaned.** Weeks earlier, the relay process had been left running against a stale network address after a machine migration. It was pointing at nothing.
+2. **The source app was down.** The local app that actually serves the image files off disk wasn't running either.
+
+So the request path was: public site → dead bridge → dead source app → 502. Three hops, two of them broken, and none of my health checks looked at any of them because they only checked the front door.
+
+## The Fix
+
+Once I could see the actual problem, the fix was straightforward: restart the local image app so it serves the files again, and reload the bridge so it points at the right address. Then verify *end to end* — not "is the process up" but "does the actual image URL return a 200 with `image/jpeg` content type, all the way through the chain." All four images came back.
+
+## The Lesson: Test the Thing the Customer Does
+
+**A static audit checks components. A functional test checks outcomes.** They are not the same, and the gap between them is exactly where multi-week silent outages live.
+
+My health checks were asking "is the server alive?" The customer was asking "can I see the picture?" Those questions have different answers, and only one of them mattered.
+
+The move I've adopted: for any user-facing surface, at least one check has to exercise the *whole path a real visitor takes*. Load the page. Follow the image URLs. Assert the content type. If the picture the customer would see is broken, the check must be red — even if every individual component reports healthy.
+
+It's more work than pinging a port. It's also the only kind of check that would have caught this.
+
+## Why This Matters for Business Owners
+
+The most dangerous outage isn't the one that pages you at 3am. It's the one that never pages you at all — the quiet one where your monitoring says everything's fine while customers hit broken pages for a month. Those are the ones that cost you sales you never even know you lost.
+
+If your monitoring only checks that servers are running, upgrade at least a few of your checks to *do what a customer does*. It's the difference between "we're up" and "we work."
+
+## Let's Connect
+
+I build AI-powered automation for real businesses — not demos, not prototypes, production systems that run 24/7.
+
+If you're a **founder, entrepreneur, or small business owner** looking to automate operations with AI, let's talk:
+
+- [**@agentabrams on YouTube**](https://youtube.com/@AgentAbrams) — walkthroughs and demos
+- [**@agentabrams on X**](https://x.com/agentabrams) — DMs open
+- [**@agentabrams on Bluesky**](https://bsky.app/profile/agentabrams.bsky.social) — follow along
+- [**goodquestion.ai**](https://goodquestion.ai) — you're here
+
+**Advisory & Board Opportunities:** I'm actively looking to join boards where AI automation can drive real business value. If your company is exploring AI-driven operations, data pipelines, or autonomous agent systems — I'd love to contribute as a board member or advisor. Reach out on any platform above.
+
+---
+
+*Built with [Claude Code](https://claude.ai). Shipped in production. Every day. This is what one founder + AI looks like.*
diff --git a/src/content/posts/2026-06-10-cracking-a-fleet-of-broken-scrapers-in-one-day.md b/src/content/posts/2026-06-10-cracking-a-fleet-of-broken-scrapers-in-one-day.md
new file mode 100644
index 0000000..0203a13
--- /dev/null
+++ b/src/content/posts/2026-06-10-cracking-a-fleet-of-broken-scrapers-in-one-day.md
@@ -0,0 +1,57 @@
+---
+title: "Cracking a Fleet of Broken Scrapers in One Day"
+description: "How I got 166 vendor catalog scrapers working again by hitting the data feed underneath the page instead of fighting the page itself."
+date: 2026-06-10
+tags: ["scraping", "data-pipeline", "ai-automation", "e-commerce", "founder-log"]
+---
+
+I maintain scrapers for a large fleet of product vendors. Over time, a lot of them had quietly rotted — sites redesign, selectors break, and a scraper that grabs "the third div under the second section" is one CSS refactor away from returning nothing. Today I got the whole fleet — 166 vendors — back to a working state, and the trick was to stop scraping the *page* and start hitting the *feed* underneath it.
+
+## Why Scrapers Rot
+
+The fragile way to scrape a site is to render its HTML and dig through the DOM for the data you want. It works right up until the site changes its layout, at which point your carefully-tuned selectors point at nothing and the scraper silently returns zero products. Multiply that across 166 vendors on different platforms with different redesign schedules, and you have a fleet that's always partially broken.
+
+The durable insight: almost every modern storefront is a thin visual shell over a *structured data feed*. Shopify stores expose `products.json`. WooCommerce has a store API. Others carry the whole catalog in an in-page JSON blob or a search index. That feed changes far less often than the visual layout, and it hands you clean structured data instead of forcing you to parse markup.
+
+## The Work
+
+The day was a systematic sweep across three tiers of scraping (new products, collections, general catalog), fixing whole classes of failure rather than one vendor at a time:
+
+- **A chokepoint fix healed ~20 vendors at once.** Several scrapers were returning flat product rows where the code expected a collections structure, so extraction got zero. Normalizing that at the single point where all vendors funnel through fixed all of them in one change.
+- **The blocked vendors got cracked without paid proxies.** Six vendors had been stuck behind anti-bot walls, previously requiring an expensive proxy service. One by one: one had its scraper pointed at the *wrong domain entirely* (a real find — it was scraping an unrelated site); others needed a native browser fingerprint to pass the wall; one exposed a clean in-page API. All six now work, and the paid proxy dependency is gone completely.
+- **The first full-fleet run pulled 2,227 genuinely-new products** from 135 vendors after deduping against what I already had — from a single command.
+
+## The Guardrail: Canary-First
+
+The scary part of running writes across thousands of rows is corrupting good data. So every recrawl ran through a **canary first**: a dry run against a small sample that checks for known defect classes before any real write happens.
+
+That canary earned its keep. On one vendor it caught social-media share links leaking into the image field. On another it caught cookie-consent table junk being parsed as product specs. Both would have written thousands of corrupt rows. The canary caught them in the dry run, I fixed the parsing, and the real run was clean.
+
+There was also a subtle one worth remembering: a nightly consolidation job was *overwriting* freshly-fixed image data from a stale source table. Fixing the images in the wrong tier meant they'd get silently wiped at 2am. The durable fix was to write to the *source*, not the intermediate gate — a "write to the source of truth, not the cache" rule I promptly wrote down so I'd never do it again.
+
+## The Lesson
+
+**When a scraper breaks, don't patch the selector — find the feed.** Fighting the rendered DOM is a losing game because the layout is *designed* to change. The data feed underneath is the stable contract. Feed-first scraping turned a fleet that was perpetually half-broken into one that's resilient to redesigns.
+
+And whenever you're about to run a bulk write over real data, run a canary first. A dry run that checks a handful of rows for known failure modes is the cheapest insurance you'll ever buy against corrupting the whole table.
+
+## Why This Matters for Business Owners
+
+A product catalog is only as good as the data flowing into it. If your ingestion is fragile, you don't find out gradually — you find out when a vendor's whole line silently vanishes from your store because their site got a facelift. Building ingestion on the stable feed instead of the shifting page is the difference between a pipeline you babysit and one that just runs.
+
+## Let's Connect
+
+I build AI-powered automation for real businesses — not demos, not prototypes, production systems that run 24/7.
+
+If you're a **founder, entrepreneur, or small business owner** looking to automate operations with AI, let's talk:
+
+- [**@agentabrams on YouTube**](https://youtube.com/@AgentAbrams) — walkthroughs and demos
+- [**@agentabrams on X**](https://x.com/agentabrams) — DMs open
+- [**@agentabrams on Bluesky**](https://bsky.app/profile/agentabrams.bsky.social) — follow along
+- [**goodquestion.ai**](https://goodquestion.ai) — you're here
+
+**Advisory & Board Opportunities:** I'm actively looking to join boards where AI automation can drive real business value. If your company is exploring AI-driven operations, data pipelines, or autonomous agent systems — I'd love to contribute as a board member or advisor. Reach out on any platform above.
+
+---
+
+*Built with [Claude Code](https://claude.ai). Shipped in production. Every day. This is what one founder + AI looks like.*
diff --git a/src/content/posts/2026-06-21-building-a-real-estate-deal-ranker-from-public-data.md b/src/content/posts/2026-06-21-building-a-real-estate-deal-ranker-from-public-data.md
new file mode 100644
index 0000000..7ce9f3f
--- /dev/null
+++ b/src/content/posts/2026-06-21-building-a-real-estate-deal-ranker-from-public-data.md
@@ -0,0 +1,58 @@
+---
+title: "Building a Real Estate Deal Ranker from Public Data"
+description: "A queryable investment tool built from free government data — and why the honest answer was worth more than the impressive-looking one."
+date: 2026-06-21
+tags: ["real-estate", "data-pipeline", "ai-automation", "public-data", "founder-log"]
+---
+
+I built an investment-property ranker this week — a tool that pulls real commercial listings, runs deterministic financial math on each one, and ranks them into a live, queryable grid. The interesting part isn't the build. It's what happened when I made the tool tell the *truth* instead of the *headline*.
+
+## The Build
+
+The pipeline is straightforward in shape:
+
+1. Pull real listings for the target region.
+2. Run leverage and cash-flow math on each — deterministic calculations, not vibes.
+3. Rank them and drop them into a viewer with a grid you can sort and search.
+4. Bolt on a chat box that answers questions about the data *and* can drive the grid (ask it to filter and the grid re-ranks).
+
+I layered in real public data underneath it all: I ingested the full county assessor roll — **2.4 million parcels** — so that every property-history lookup resolves to real year-built, beds, baths, and assessed values instead of guesses. (Ingesting 2.4M rows had its own gotcha: the paginated download silently stopped early at 1.8M because of an offset bug. Switching to key-set pagination — "give me everything after this ID" — fixed the false stop and pulled the complete roll.)
+
+Then I automated the whole thing: a daily 6am email with the top ranked deals, and a weekly refresh that sweeps for new listings, dedupes, re-ranks, and only notifies me if something new showed up. Hands-off deal flow into my inbox.
+
+## The Part That Mattered: Ranking Integrity
+
+Here's where it got real. When I deep-verified the top-ranked properties against the actual MLS records, several of them were lying — or rather, the *listings* were.
+
+A headline listing would advertise, say, a 7.8% cap rate. Verified against MLS, the *real* number was closer to 6.0%. The listing was quoting a *projected* cap (what the property could earn if everything went right) as if it were the *actual* cap (what it earns today). Those are very different numbers, and the difference is the whole investment decision.
+
+A ranker that trusts headline numbers will confidently put the most *aggressively-marketed* deal at #1 — which is exactly backwards. The deals with the flashiest projected returns are often the ones hiding the weakest actual economics.
+
+So I added a **confidence penalty**: a property with an undisclosed or unverifiable cap rate gets docked, so it can't outrank a deal with verified, real economics. A modest but honest 6% now beats a loud, unverifiable 8%. The tool stopped steering toward the best *marketing* and started steering toward the best *deal*.
+
+## The Lesson
+
+**A tool that tells you what you want to hear is worse than no tool,** because it launders a bad decision through the appearance of analysis. The valuable version tells you what's *true*, even when the truth is less exciting.
+
+The technical version of this: never let unverified inputs rank above verified ones. If you can't confirm a number, the ranking has to *account for that uncertainty*, not pretend it away. A #1 result carries authority — it will drive a real decision — so earning that #1 spot has to require real, checkable data, not the most optimistic press release.
+
+## Why This Matters for Business Owners
+
+Every dashboard, ranker, and "AI recommendation" you build is going to be *believed*. That's the point of building it. Which means the integrity of what it surfaces matters more than how slick it looks. A ranking that quietly favors whoever markets hardest isn't a decision tool — it's a way to get talked into bad decisions faster. Build the penalty for uncertainty *in*, and your tools become something you can actually trust with real money.
+
+## Let's Connect
+
+I build AI-powered automation for real businesses — not demos, not prototypes, production systems that run 24/7.
+
+If you're a **founder, entrepreneur, or small business owner** looking to automate operations with AI, let's talk:
+
+- [**@agentabrams on YouTube**](https://youtube.com/@AgentAbrams) — walkthroughs and demos
+- [**@agentabrams on X**](https://x.com/agentabrams) — DMs open
+- [**@agentabrams on Bluesky**](https://bsky.app/profile/agentabrams.bsky.social) — follow along
+- [**goodquestion.ai**](https://goodquestion.ai) — you're here
+
+**Advisory & Board Opportunities:** I'm actively looking to join boards where AI automation can drive real business value. If your company is exploring AI-driven operations, data pipelines, or autonomous agent systems — I'd love to contribute as a board member or advisor. Reach out on any platform above.
+
+---
+
+*Built with [Claude Code](https://claude.ai). Shipped in production. Every day. This is what one founder + AI looks like.*
diff --git a/src/content/posts/2026-07-01-i-built-a-command-center-that-tracks-its-own-p-and-l.md b/src/content/posts/2026-07-01-i-built-a-command-center-that-tracks-its-own-p-and-l.md
new file mode 100644
index 0000000..2fae837
--- /dev/null
+++ b/src/content/posts/2026-07-01-i-built-a-command-center-that-tracks-its-own-p-and-l.md
@@ -0,0 +1,63 @@
+---
+title: "I Built a Command Center That Tracks Its Own P&L"
+description: "A single dashboard over my whole fleet — with a live profit-and-loss that shows the agents earning back the pennies they cost to run."
+date: 2026-07-01
+tags: ["ai-agents", "dashboards", "cost-tracking", "automation", "founder-log"]
+---
+
+I run a lot of autonomous processes. This week I built the thing I'd been missing: one command center over the entire fleet, with a live P&L that answers a question I actually care about — *is this paying for itself?*
+
+## The Problem: Situational Awareness
+
+When you're one person running hundreds of processes across a couple of machines, the hardest thing isn't building — it's *knowing what's going on*. Which jobs ran overnight? Which are healthy? What did today cost? Which of my monitors is quietly dead?
+
+Answering that meant checking a dozen different places. So I built a single dashboard that collects it all: system status, every managed process across both machines, the health of all my monitoring canaries, scheduled jobs, and the recent wins log. One glance, whole fleet.
+
+## The Interesting Part: A Self-Funding P&L
+
+Here's the piece I'm proud of. The dashboard doesn't just show *status* — it shows *money*.
+
+On the cost side, it reads my token-usage ledger (every paid API call gets logged there) and adds an energy estimate, giving a real daily burn rate. At the time I built it, that came to about **$0.74 a day** — call it $23 a month — to run the entire operation.
+
+On the income side, there's an endpoint that records real revenue as it comes in. Each recorded sale folds into a per-window P&L, and the dashboard shows a live self-funding percentage: what share of the fleet's running cost the fleet has earned back.
+
+The break-even target is now a *number I can see* — roughly $0.76/day, $22.95/month. The moment any revenue engine records a sale, the self-funding percentage jumps in real time and I can watch the operation move toward paying for its own compute and electricity.
+
+That reframes everything. It's no longer "I have a pile of automation." It's "I have an operation with a cost, a target, and a runway, and here's exactly how close it is to break-even."
+
+## The Honesty Rules
+
+Building this surfaced a few bugs that were quietly *lying*, and fixing them was the whole point:
+
+- A background task was charging some costs **twice** on the money path — a double-count that would have made the operation look more expensive than it was.
+- One paid tool was mislabeled as **"$0 local"** when it actually costs money per call. Free-vs-paid has to be honest or the P&L is fiction.
+- A monitoring check was showing a **failure as `null`** — an actual live failure hidden as "no data." I made failed states surface as failed, loudly.
+
+If a cost dashboard rounds in your favor, it's worse than no dashboard, because it gives you false confidence. Every one of those fixes made the number *less* flattering and *more* true.
+
+## The Lesson
+
+**Autonomy without a P&L is just spending.** It's easy to build a fleet of agents that *do things*. It's much more useful to build one that tells you what it *costs* and what it *earns*, so "should I keep running this?" has a real answer instead of a vibe.
+
+And the honesty rules aren't optional. A P&L that quietly undercounts costs or hides failures isn't a lighter version of the truth — it's a different, wrong number that will steer you into bad calls. Make the dashboard tell you the uncomfortable version.
+
+## Why This Matters for Business Owners
+
+If you're investing in automation, "it works" is not the metric. "It works, it costs $X/month, and it's earning $Y toward that" is the metric. Wiring cost and revenue into the same view turns a cool tech project into a business you can actually reason about — and it makes the go/no-go decision on every new agent obvious instead of emotional.
+
+## Let's Connect
+
+I build AI-powered automation for real businesses — not demos, not prototypes, production systems that run 24/7.
+
+If you're a **founder, entrepreneur, or small business owner** looking to automate operations with AI, let's talk:
+
+- [**@agentabrams on YouTube**](https://youtube.com/@AgentAbrams) — walkthroughs and demos
+- [**@agentabrams on X**](https://x.com/agentabrams) — DMs open
+- [**@agentabrams on Bluesky**](https://bsky.app/profile/agentabrams.bsky.social) — follow along
+- [**goodquestion.ai**](https://goodquestion.ai) — you're here
+
+**Advisory & Board Opportunities:** I'm actively looking to join boards where AI automation can drive real business value. If your company is exploring AI-driven operations, data pipelines, or autonomous agent systems — I'd love to contribute as a board member or advisor. Reach out on any platform above.
+
+---
+
+*Built with [Claude Code](https://claude.ai). Shipped in production. Every day. This is what one founder + AI looks like.*
diff --git a/src/content/posts/2026-07-02-the-four-layer-bug-that-hid-behind-a-comment.md b/src/content/posts/2026-07-02-the-four-layer-bug-that-hid-behind-a-comment.md
new file mode 100644
index 0000000..7752c42
--- /dev/null
+++ b/src/content/posts/2026-07-02-the-four-layer-bug-that-hid-behind-a-comment.md
@@ -0,0 +1,55 @@
+---
+title: "The Four-Layer Bug That Hid Behind a Commented-Out Line"
+description: "A quantity stepper that snapped back to 1 was blocking real orders — and the fix required peeling back four separate bugs stacked on top of each other."
+date: 2026-07-02
+tags: ["debugging", "shopify", "frontend", "e-commerce", "founder-log"]
+---
+
+Some products in my store are supposed to be ordered in increments of two, starting at two — a minimum-order rule the manufacturer requires. The product page had a quantity stepper for exactly this. It didn't work. You'd set it to 4, and it would snap back to 1. And because it silently defaulted to 1, it was quietly *blocking every order larger than a single unit.* Here's how four separate bugs stacked up to cause it — and why I only found them by recording my own screen.
+
+## Why This One Was Hard
+
+I'd already "fixed" this once. I'd set the right data on the products — the minimum-order metafields were correct. So when it still didn't work, my first instinct was that the data was wrong. It wasn't. The data was fine. The bug was somewhere in the theme, and it was actually *four* bugs wearing a trenchcoat.
+
+The breakthrough was giving up on reading code and instead **screen-recording the page as it loaded**, frame by frame, watching the quantity value change. That's what exposed the real sequence.
+
+## The Four Layers
+
+**Layer 1 — a JavaScript error killed initialization.** A function that drew the color swatches had a reference error (`c is not defined`). That error threw *before* the quantity-stepper setup code ran, so the stepper never initialized at all. One unrelated typo, and the whole feature was dead on arrival.
+
+**Layer 2 — the Sample variant poisoned the values.** Each product has a low-cost "Sample" variant with a min and step of 1. Selecting through the variants let the Sample's 1/1 values overwrite the roll's 2/2 values. So even when the stepper *did* initialize, it got contaminated with the wrong minimums.
+
+**Layer 3 — the increment button was commented out.** The actual `+`/`-` click handler had been *commented out* in a previous attempt. Why? Because someone (past me) was trying to hide the symptom of a deeper problem — the value kept snapping back, so disabling the buttons made the snap-back less visible. Classic: a workaround that masks a bug becomes a new bug.
+
+**Layer 4 — the real culprit.** Buried in the theme was an old min-quantity block that ran on a **400-millisecond interval plus a mutation observer**, aggressively re-forcing the quantity back down to its floor. So every time the stepper set the value to 4, this zombie block yanked it back to 1 a fraction of a second later. That was the snap-back. That was the thing all the other workarounds had been fighting without anyone identifying it.
+
+The fix was to guard that legacy block so it yields when the modern stepper is present, then clean up the three bugs layered on top. Verified with a fresh screen recording: 2 → 4 → 6, the minus button floors at 2, and it *holds*.
+
+## The Lesson
+
+**When "I fixed the data" doesn't fix the bug, stop trusting your assumptions and go watch what actually happens.** I lost time because I was sure the problem was the data — I'd just fixed the data! But the data was never the problem. The screen recording didn't care what I assumed; it showed me the value snapping back in real time, which pointed straight at the interval loop.
+
+The other lesson is about **workarounds that mask bugs.** The commented-out button and the poisoned values weren't the root cause — they were *previous attempts to hide the root cause*, and each one made the real bug harder to see. Layered workarounds don't fix anything; they just add sediment on top of the actual problem. Sometimes the fastest path forward is to peel *back* the previous fixes until you can see the original break.
+
+## Why This Matters for Business Owners
+
+This bug wasn't cosmetic. It was on the checkout path, and it silently prevented customers from ordering the correct quantities on a whole category of products. Orders larger than one unit just... didn't happen. That's revenue leaking through a bug that every automated check called "fine" because the page loaded and returned 200.
+
+The money surface deserves the paranoid version of testing — actually clicking through a purchase, actually watching the numbers, on a real browser. That's the only kind of test that would have caught four stacked bugs quietly costing sales.
+
+## Let's Connect
+
+I build AI-powered automation for real businesses — not demos, not prototypes, production systems that run 24/7.
+
+If you're a **founder, entrepreneur, or small business owner** looking to automate operations with AI, let's talk:
+
+- [**@agentabrams on YouTube**](https://youtube.com/@AgentAbrams) — walkthroughs and demos
+- [**@agentabrams on X**](https://x.com/agentabrams) — DMs open
+- [**@agentabrams on Bluesky**](https://bsky.app/profile/agentabrams.bsky.social) — follow along
+- [**goodquestion.ai**](https://goodquestion.ai) — you're here
+
+**Advisory & Board Opportunities:** I'm actively looking to join boards where AI automation can drive real business value. If your company is exploring AI-driven operations, data pipelines, or autonomous agent systems — I'd love to contribute as a board member or advisor. Reach out on any platform above.
+
+---
+
+*Built with [Claude Code](https://claude.ai). Shipped in production. Every day. This is what one founder + AI looks like.*
diff --git a/src/content/posts/2026-07-22-a-social-command-center-for-a-local-business.md b/src/content/posts/2026-07-22-a-social-command-center-for-a-local-business.md
new file mode 100644
index 0000000..5804db2
--- /dev/null
+++ b/src/content/posts/2026-07-22-a-social-command-center-for-a-local-business.md
@@ -0,0 +1,64 @@
+---
+title: "A Social Command Center for a Local Business"
+description: "Turning a small business's admin panel into a one-screen social workflow — and the discipline it takes to deploy to production without breaking it."
+date: 2026-07-22
+tags: ["small-business", "marketing-automation", "deployment", "product", "founder-log"]
+---
+
+This week I've been building out a growth command center for a local service business — a car wash. The theme running through it: a small business owner doesn't need ten tools, they need *one screen* where the whole marketing workflow lives. And getting that screen onto a live production box without breaking the thing the owner logs into every day takes more discipline than the building does.
+
+## The Problem With "Use These Ten Apps"
+
+The standard advice to a small business is a stack of subscriptions: one tool to schedule posts, another for the media library, another for captions, another for analytics. Each one is a login, a monthly fee, and a context switch. For an owner who's actually running the business, that's not a solution — it's homework they'll never do.
+
+So the build went the other direction: collapse it into the admin panel they already use.
+
+## What Went In
+
+Over several sessions the admin grew into a real social workflow:
+
+- **A composer that reads from the business's own media library** — pick an asset, drop in a caption from a template, add a hashtag set, and post. One screen, no asset-hunting across folders.
+- **A scheduled queue** so posts can be planned ahead across multiple accounts, with best-time hints per platform.
+- **Promo videos surfaced right in the admin** — the brand films get their own tab, each streaming on demand, each one click away from being attached to a post. No more digging through sibling render folders.
+
+The design rule throughout: whatever exists gets rendered, honestly, and nothing pretends to be more than it is. The auto-posting machinery, for instance, ships *inert* — it exists and is wired up, but it posts nothing until real account connections are added. It's "one token-drop away from live," not "secretly half-broken." That distinction matters: the owner should never wonder whether a feature is real.
+
+## The Deploy Discipline
+
+Here's the part that's easy to underweight. This admin runs on a live production box that the owner logs into. An earlier code deploy had broken production once. That's the kind of mistake you make exactly once before you build a ritual around never repeating it.
+
+So the deploy pattern became almost paranoid, and deliberately so:
+
+- **Back up first**, every time, before touching the live file.
+- **Surgical single-file deploys** where possible — change one static file, not rsync the world, so the blast radius of a mistake is tiny.
+- **Verify every endpoint** after the deploy returns its expected status, plus a real headless browser render to confirm the page actually works — not just that the server answered.
+- For a credential change, do the smallest possible thing (an environment-only update and a restart) rather than pushing code at all.
+
+It's slower per deploy. It's also the reason the owner's login has stayed up while the admin behind it kept growing.
+
+## The Lesson
+
+**For a small business, consolidation beats capability.** A feature the owner will never find across five tools is worth less than a slightly simpler feature that lives on the one screen they already open. Build toward the single pane of glass, not the impressive integration count.
+
+And on the ops side: **the ritual is the feature.** Backup-first, small-blast-radius, verify-end-to-end isn't bureaucracy — it's what lets you keep shipping to a live business without the shipping itself becoming the risk. The one time you skip it is the one time it bites.
+
+## Why This Matters for Business Owners
+
+If you run a local business, the highest-value software isn't the one with the most features — it's the one that fits into a workflow you'll actually keep up. And if someone is deploying changes to the system you depend on daily, ask them how they deploy. "Carefully, with a backup and a verification step" is the answer that keeps your business running while it improves.
+
+## Let's Connect
+
+I build AI-powered automation for real businesses — not demos, not prototypes, production systems that run 24/7.
+
+If you're a **founder, entrepreneur, or small business owner** looking to automate operations with AI, let's talk:
+
+- [**@agentabrams on YouTube**](https://youtube.com/@AgentAbrams) — walkthroughs and demos
+- [**@agentabrams on X**](https://x.com/agentabrams) — DMs open
+- [**@agentabrams on Bluesky**](https://bsky.app/profile/agentabrams.bsky.social) — follow along
+- [**goodquestion.ai**](https://goodquestion.ai) — you're here
+
+**Advisory & Board Opportunities:** I'm actively looking to join boards where AI automation can drive real business value. If your company is exploring AI-driven operations, data pipelines, or autonomous agent systems — I'd love to contribute as a board member or advisor. Reach out on any platform above.
+
+---
+
+*Built with [Claude Code](https://claude.ai). Shipped in production. Every day. This is what one founder + AI looks like.*
diff --git a/src/content/posts/2026-07-26-the-review-that-caught-what-my-own-checks-missed.md b/src/content/posts/2026-07-26-the-review-that-caught-what-my-own-checks-missed.md
new file mode 100644
index 0000000..de1ae91
--- /dev/null
+++ b/src/content/posts/2026-07-26-the-review-that-caught-what-my-own-checks-missed.md
@@ -0,0 +1,60 @@
+---
+title: "The Review That Caught What My Own Checks Missed"
+description: "344 luxury products were displaying a $4.25 sample fee as their price — my activation checks passed, and an adversarial review caught it anyway."
+date: 2026-07-26
+tags: ["e-commerce", "code-review", "quality", "shopify", "founder-log"]
+---
+
+I have a habit now that keeps saving me: after I build or activate something, I run it past an adversarial reviewer whose whole job is to *tear it apart*. Today that habit caught a defect that would have made 344 luxury, quote-only products display a **$4.25 sample fee as their price** on the live storefront — a defect my own activation checks had cheerfully waved through.
+
+## The Setup
+
+Some products in my catalog are quote-only. They don't have a public per-unit price — you contact us for a quote. But they *do* have a cheap "sample" variant, so a designer can order a physical swatch for $4.25 before requesting a quote on the full order.
+
+The danger with these products is obvious once you say it out loud: if the storefront picks the *sample* variant's price to display, the product page shows **"$4.25"** as if that's the price of the item. On a luxury, quote-only line, that's not just wrong — it makes premium products look like they cost less than a coffee.
+
+I'd activated a batch of these. My activation checks passed. Backend tags were correct. Everything looked done.
+
+## What the Review Caught
+
+Then the adversarial review found the hole my checks missed. The products had the *backend* tag that marks them quote-only — but not the specific *storefront* tag that the theme actually reads to render the "contact for price" treatment instead of a price.
+
+So on the backend, everything was labeled correctly. On the front end, the theme didn't know these were quote-only, fell through to the default behavior, and displayed the $4.25 sample as the price. **344 products.** All live. All showing a swatch fee where a "contact us" should be.
+
+My checks looked at the data model and said "correct." The customer's browser rendered the theme and said "$4.25." The gap between those two was exactly where the bug lived — and I only found it because something was actively trying to prove me wrong.
+
+## The Fix
+
+First, *reproduce it* — I confirmed against the live theme that the price section only renders the quote-only treatment when the storefront tag is present, and that these products didn't have it. Then, additively (nothing destructive, fully reversible), I added the missing storefront tag to all 344. Now the pages correctly show "Price Per Unit Unavailable Online — Contact Us About This SKU" instead of a fake price.
+
+## The Lesson
+
+**Your own checks share your own blind spots.** I wrote the activation checks. They validated exactly the things I thought mattered — and they were silent on the thing I hadn't thought of, because if I'd thought of it, I'd have built a check for it. That's the fundamental limit of self-review: you can't catch the mistake you don't know you're capable of making.
+
+An adversarial reviewer — a second pass whose explicit goal is to *find what's broken*, not confirm what works — breaks that symmetry. It's not trying to validate my model of "done." It's trying to prove "done" is a lie. And often enough, it is.
+
+Two things I now treat as non-negotiable:
+
+1. **Backend-correct is not customer-correct.** The tag being right in the database means nothing if the *rendered page* the customer sees is wrong. Verify at the surface, not the source.
+2. **Reproduce before you fix.** The review didn't just assert a problem — I confirmed it against the live theme before touching anything. A fix for a bug you haven't actually reproduced is a guess.
+
+## Why This Matters for Business Owners
+
+The most expensive bugs are the ones that pass every check you wrote and go live looking fine. On a premium brand, a $4.25 price tag on a luxury product isn't a typo — it's a trust-killer that runs 24/7 until someone notices. A cheap, ruthless second review — one that's *trying* to find the embarrassing thing — is one of the highest-ROI steps you can bolt onto any launch. It catches the mistakes your confidence hides.
+
+## Let's Connect
+
+I build AI-powered automation for real businesses — not demos, not prototypes, production systems that run 24/7.
+
+If you're a **founder, entrepreneur, or small business owner** looking to automate operations with AI, let's talk:
+
+- [**@agentabrams on YouTube**](https://youtube.com/@AgentAbrams) — walkthroughs and demos
+- [**@agentabrams on X**](https://x.com/agentabrams) — DMs open
+- [**@agentabrams on Bluesky**](https://bsky.app/profile/agentabrams.bsky.social) — follow along
+- [**goodquestion.ai**](https://goodquestion.ai) — you're here
+
+**Advisory & Board Opportunities:** I'm actively looking to join boards where AI automation can drive real business value. If your company is exploring AI-driven operations, data pipelines, or autonomous agent systems — I'd love to contribute as a board member or advisor. Reach out on any platform above.
+
+---
+
+*Built with [Claude Code](https://claude.ai). Shipped in production. Every day. This is what one founder + AI looks like.*
diff --git a/src/data/projects.ts b/src/data/projects.ts
index 541eeff..bc4072e 100644
--- a/src/data/projects.ts
+++ b/src/data/projects.ts
@@ -86,4 +86,109 @@ export const projects: Project[] = [
     techStack: ['Shopify', 'Liquid', 'JavaScript', 'Email Marketing', 'Mobile-First Design'],
     screenshot: '/images/projects/rose-and-harrys.png',
   },
+  {
+    slug: 'govarbitrage',
+    name: 'GovArbitrage',
+    url: 'https://auctions.agentabrams.com',
+    tagline: 'Surface underpriced government surplus and seized-asset auctions before the crowd finds them.',
+    description: 'GovArbitrage aggregates public government auctions — surplus, seized, and foreclosure listings scattered across dozens of agency sites — into one searchable feed, then flags the lots most likely to be underpriced relative to market. It turns a fragmented, hard-to-monitor corner of the public market into a single opportunity dashboard.',
+    challenge: 'Government auction data lives in dozens of inconsistent formats across agency portals, with no unified schema and no way to compare a lot against its real-world resale value. On top of that, the app needed a clean subscription flow with tiered access and a login that Just Works — without a heavyweight identity provider.',
+    solutions: [
+      'Built a normalized auction ingest pipeline that pulls listings from many sources into one consistent, searchable schema',
+      'Added opportunity scoring that flags lots priced below comparable market value so users see the best deals first',
+      'Shipped tiered subscription plans with a streamlined basic-auth login and a Stripe-backed checkout',
+    ],
+    techStack: ['Next.js', 'TypeScript', 'Node.js', 'PostgreSQL', 'Stripe', 'Cloudflare'],
+    screenshot: '/images/projects/govarbitrage.png',
+  },
+  {
+    slug: 'national-real-estate-explorer',
+    name: 'National Real Estate Explorer',
+    url: 'https://nationalrealestate.agentabrams.com',
+    tagline: 'A free, nationwide market-data explorer built on open real-estate and parcel datasets.',
+    description: 'A national real-estate data explorer that stitches together open government housing datasets — parcels, deeds, market indices, and a broker registry — into one fast, browsable interface. It lets anyone explore markets across the country without a paid data subscription.',
+    challenge: 'The parcel table alone runs past five million rows, and county data schemas vary wildly. Naive queries against nested JSON fields caused multi-minute table scans and stalled the whole app; state and county naming conventions had to be reconciled before anything could be joined.',
+    solutions: [
+      'Engineered concurrent, non-blocking database indexes on a multi-million-row parcel table to kill slow JSON sequential scans',
+      'Reconciled inconsistent county and state schemas into a unified, queryable national dataset',
+      'Built a broker registry and market-index views that stay fast even under heavy concurrent load',
+    ],
+    techStack: ['Node.js', 'TypeScript', 'PostgreSQL', 'Open Data APIs', 'Data Pipelines'],
+    screenshot: '/images/projects/national-real-estate-explorer.png',
+  },
+  {
+    slug: 'client-growth-portals',
+    name: 'Client Growth Portals',
+    url: 'https://prestige.agentabrams.com',
+    tagline: 'A consulting-portal system that spins up a full growth command center per client from a single intake.',
+    description: 'A repeatable consulting engine that turns a short client intake into a complete, per-client growth portal — a private command center with competitor analysis, best-times-to-post data, social and ad signals, a content calendar, costed growth ideas, and even fresh website concepts. What used to be a bespoke engagement becomes a scaffold-and-ship template.',
+    challenge: 'Every consulting client needs the same deliverables assembled from scratch — research, positioning, a site concept, a social plan — which is slow and hard to standardize. The system had to generate a genuinely useful, client-branded portal on day one, including honest analytics rather than vanity numbers.',
+    solutions: [
+      'Built an intake-to-portal scaffolder that generates a branded per-client command center from a single questionnaire',
+      'Integrated competitor research, ad-signal detection, content calendars, and costed growth ideas with printable action briefs',
+      'Shipped first-party, cookie-free analytics so clients see an honest view-to-conversion rate without a consent banner',
+    ],
+    techStack: ['Node.js', 'Express', 'PostgreSQL', 'AI Agents', 'Cloudflare'],
+    screenshot: '/images/projects/client-growth-portals.png',
+  },
+  {
+    slug: 'ai-wallpaper-storefront',
+    name: 'AI Wallpaper Studio',
+    url: 'https://designerwallcoverings.com',
+    tagline: 'An original AI-generated wallpaper collection with a full seamless-tile quality pipeline.',
+    description: 'A storefront for original, AI-generated wallpaper designs — each pattern generated in-house, quality-gated for seamless tiling, recolored across colorways, and rendered at print resolution. It pairs a generative design pipeline with a real commerce catalog, so novel patterns can go from prompt to purchasable product.',
+    challenge: 'AI image models happily produce beautiful patterns that do not actually tile — a visible seam ruins a wallpaper. Designs also needed automated color and composition checks, full-resolution print masters, and at-scale mural previews that stay crisp, all before anything could ship to customers.',
+    solutions: [
+      'Built a multi-lens seamless-tile scanner that measures edge and midpoint discontinuity and rejects patterns that would seam on a wall',
+      'Automated palette, composition, and defect checks plus a regenerate-until-clean loop so only production-quality designs ship',
+      'Generated full-resolution print masters and crisp at-scale mural previews with per-colorway recoloring',
+    ],
+    techStack: ['Node.js', 'Python', 'Generative AI', 'Computer Vision', 'PostgreSQL', 'Shopify'],
+    screenshot: '/images/projects/ai-wallpaper-storefront.png',
+  },
+  {
+    slug: 'professional-directories',
+    name: 'Professional Directories',
+    url: 'https://barber.agentabrams.com',
+    tagline: 'Compliance-first local directories for lawyers, medical practices, animal services, and more.',
+    description: 'A family of large-scale local directory platforms — spanning legal, medical, pet-services, and neighborhood verticals — each built on public data, enriched with discovered websites and contact details, and wrapped in a monetization funnel (a site-upgrade SaaS upsell and lead-gen). One directory alone indexes tens of thousands of firms and professionals.',
+    challenge: 'Building a directory that is both comprehensive and compliant means reconciling messy public records, crawling and verifying business websites at scale, and respecting suppression and contact rules — while keeping every listing searchable down to the individual professional.',
+    solutions: [
+      'Assembled directories from public records into searchable databases of tens of thousands of firms and licensed professionals',
+      'Ran concurrent website-discovery and automated site-audit crawlers to enrich each listing with real contact data',
+      'Wired a compliance gate (suppression list + contact-attempt audit log) that every outbound touch checks first',
+    ],
+    techStack: ['TypeScript', 'Node.js', 'PostgreSQL', 'Playwright', 'Local LLMs', 'Data Enrichment'],
+    screenshot: '/images/projects/professional-directories.png',
+  },
+  {
+    slug: 'ninety-two-ten-guide',
+    name: 'The Unofficial 90210 Guide',
+    url: 'https://90210.agentabrams.com',
+    tagline: 'A full-stack fan guide to a classic TV series — every episode, cast, news, and mini-games.',
+    description: 'A complete fan destination for a beloved TV series: 114 episodes with recaps, cast profiles, a rolling news feed, and a handful of playable mini-games, all wrapped in a modern liquid-gradient design. Built as a full-stack Next.js app and shipped end-to-end in a single session.',
+    challenge: 'A fan guide only earns repeat visits if the content is deep and the experience feels current. That meant assembling a large episode and cast dataset, a live news feed, and interactive games — then designing something that reads as fresh rather than a dusty archive, and deploying the whole stack cleanly.',
+    solutions: [
+      'Built a full-stack app covering 100+ episode recaps, cast profiles, and a 700+ item news feed backed by a real database',
+      'Added a set of playable mini-games and a modern liquid-gradient visual system for a living, not archival, feel',
+      'Deployed the complete stack behind a CDN and verified it cross-browser in one build-to-live pass',
+    ],
+    techStack: ['Next.js', 'React', 'PostgreSQL', 'Node.js', 'Cloudflare'],
+    screenshot: '/images/projects/ninety-two-ten-guide.png',
+  },
+  {
+    slug: 'command-center',
+    name: 'Ops Command Center',
+    url: 'https://goodquestion.ai',
+    tagline: 'A live command center that tracks every build, win, and gated action across a fleet of AI agents.',
+    description: 'A custom operations dashboard that gives a single-operator studio air-traffic control over dozens of projects and autonomous agents at once — tracking shipped wins, surfacing gated actions that need human sign-off, and rendering a live view of which agent is working on what. It is the nerve center that makes running many parallel AI workstreams sane.',
+    challenge: 'Running dozens of autonomous agents in parallel generates a flood of state: work in flight, actions awaiting approval, jobs that silently stall. Without one canonical surface, approvals get lost in limbo, stale items masquerade as active, and there is no honest picture of what actually shipped.',
+    solutions: [
+      'Built a same-origin approvals surface that regenerates the gated queue from disk and lets a human approve, reject, or revise in one place',
+      'Unified fragmented state stores into a single canonical lifecycle so every item shows a truthful status chip',
+      'Added a self-healing sweep that auto-closes orphaned approval limbo and a live view of which agent is working where',
+    ],
+    techStack: ['Node.js', 'Express', 'JavaScript', 'Real-Time Dashboards', 'AI Agents'],
+    screenshot: '/images/projects/command-center.png',
+  },
 ];
diff --git a/src/layouts/BlogLayout.astro b/src/layouts/BlogLayout.astro
index 01dd476..088a19d 100644
--- a/src/layouts/BlogLayout.astro
+++ b/src/layouts/BlogLayout.astro
@@ -30,34 +30,49 @@ const currentPath = Astro.url.pathname;
   <meta property="og:site_name" content="goodquestion.ai" />
 
   <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+
+  <!-- Google Fonts — DM Sans (body) + Playfair Display (display) -->
   <link rel="preconnect" href="https://fonts.googleapis.com" />
   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
-  <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&family=Playfair+Display:wght@700;800;900&display=swap" rel="stylesheet" />
+  <link
+    href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=Playfair+Display:ital,wght@0,700;0,800;0,900;1,700&display=swap"
+    rel="stylesheet"
+  />
+
   <title>{fullTitle}</title>
 </head>
 <body>
-  <header class="site-header">
+  <header class="site-header" role="banner">
     <div class="container">
-      <a href="/" class="site-logo">goodquestion.ai</a>
-      <nav class="site-nav">
-        <a href="/" class:list={[{ active: currentPath === '/' }]}>Posts</a>
+      <a href="/" class="site-logo" aria-label="goodquestion.ai — home">goodquestion.ai</a>
+      <nav class="site-nav" aria-label="Primary navigation">
+        <a href="/" class:list={[{ active: currentPath === '/' || currentPath === '' }]}>Posts</a>
         <a href="/about" class:list={[{ active: currentPath === '/about/' || currentPath === '/about' }]}>About</a>
         <a href="/projects" class:list={[{ active: currentPath.startsWith('/projects') }]}>Projects</a>
         <a href="/hire" class:list={[{ active: currentPath === '/hire/' || currentPath === '/hire' }]}>Hire</a>
-        <a href="https://bsky.app/profile/agentabrams.bsky.social" target="_blank" rel="noopener">Bluesky</a>
+        <a
+          href="https://bsky.app/profile/agentabrams.bsky.social"
+          target="_blank"
+          rel="noopener noreferrer"
+          aria-label="Agent Abrams on Bluesky (opens in new tab)"
+        >Bluesky&thinsp;↗</a>
       </nav>
     </div>
   </header>
 
-  <main>
+  <main id="main-content">
     <div class="container">
       <slot />
     </div>
   </main>
 
-  <footer class="site-footer">
+  <footer class="site-footer" role="contentinfo">
     <div class="container">
-      <p>Agent Abrams &copy; {new Date().getFullYear()} &mdash; Built with <a href="https://astro.build">Astro</a> and too much coffee.</p>
+      <p>
+        Agent Abrams &copy; {new Date().getFullYear()}
+        &mdash; Built with <a href="https://astro.build" target="_blank" rel="noopener">Astro</a>
+        and too much coffee.
+      </p>
     </div>
   </footer>
 </body>
diff --git a/src/layouts/PostLayout.astro b/src/layouts/PostLayout.astro
index 1d6e721..54f98ad 100644
--- a/src/layouts/PostLayout.astro
+++ b/src/layouts/PostLayout.astro
@@ -24,25 +24,79 @@ const readingTime = Math.max(1, Math.ceil(words / 250));
 ---
 
 <BlogLayout title={title} description={description}>
-  <article>
-    <div class="post-header">
-      <h1 class="post-title">{title}</h1>
-      <div class="post-meta">
-        <span>{formattedDate}</span>
-        <span>{readingTime} min read</span>
-        <span>{author}</span>
+  <article itemscope itemtype="https://schema.org/BlogPosting">
+    <header class="post-header">
+      <h1 class="post-title" itemprop="headline">{title}</h1>
+
+      <div class="post-meta" aria-label="Post metadata">
+        <time datetime={date.toISOString()} itemprop="datePublished">{formattedDate}</time>
+        <span aria-label={`${readingTime} minute read`}>{readingTime} min read</span>
+        <span itemprop="author">{author}</span>
       </div>
+
       {tags.length > 0 && (
-        <div class="post-card-tags" style="margin-top: 0.75rem;">
+        <div class="post-card-tags" aria-label="Tags">
           {tags.map(tag => (
             <span class="tag">{tag}</span>
           ))}
         </div>
       )}
-    </div>
+    </header>
 
-    <div class="post-content">
+    <div class="post-content" itemprop="articleBody">
       <slot />
     </div>
+
+    <!-- Back link -->
+    <nav class="post-footer-nav" aria-label="Post navigation">
+      <a href="/" class="back-link">
+        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
+          <polyline points="15 18 9 12 15 6"/>
+        </svg>
+        All posts
+      </a>
+    </nav>
   </article>
 </BlogLayout>
+
+<style>
+  /* Back-link nav */
+  .post-footer-nav {
+    margin-top: 4rem;
+    padding-top: 2rem;
+    border-top: 1px solid var(--border);
+  }
+
+  .back-link {
+    display: inline-flex;
+    align-items: center;
+    gap: 0.4rem;
+    font-size: 0.9rem;
+    font-weight: 500;
+    color: var(--text-secondary);
+    text-decoration: none;
+    padding: 0.4rem 0.75rem 0.4rem 0.5rem;
+    border-radius: var(--radius-sm);
+    border: 1px solid var(--border);
+    background: var(--bg-secondary);
+    transition: color var(--duration-fast) var(--ease-out),
+                border-color var(--duration-fast) var(--ease-out),
+                background var(--duration-fast) var(--ease-out);
+  }
+
+  .back-link:hover {
+    color: var(--accent);
+    border-color: var(--accent);
+    background: var(--accent-subtle);
+    text-decoration: none;
+  }
+
+  /* Separator dots in post-meta */
+  .post-meta span + span::before,
+  .post-meta time + span::before {
+    content: '·';
+    margin: 0 0.5rem;
+    color: var(--text-muted);
+    opacity: 0.5;
+  }
+</style>
diff --git a/src/pages/about.astro b/src/pages/about.astro
index a8fd47f..ff38e32 100644
--- a/src/pages/about.astro
+++ b/src/pages/about.astro
@@ -8,15 +8,18 @@ import BlogLayout from '../layouts/BlogLayout.astro';
     <p class="about-subtitle">A developer alias. A real human. A Claude Code obsession.</p>
 
     <p>
-      I'm a developer who builds things with AI every single day. Not weekend experiments or
-      proof-of-concepts -- production systems, real infrastructure, actual products that serve real users.
+      I'm a solo founder who ships production software with AI agents every single day. Not weekend
+      experiments or proof-of-concepts -- real infrastructure, live storefronts, data platforms, and
+      directories that serve real users, built at a pace that used to take a whole team.
     </p>
 
     <p>
       Agent Abrams is my dev alias. The name started as an inside joke with a project naming convention
       and stuck. Behind the alias is an entrepreneur with over 40 years of building businesses -- started
-      early, never stopped. Now deep in the trenches of AI-assisted development with
-      <a href="https://claude.ai">Claude Code</a>, building production systems every single day.
+      early, never stopped. Today I run a one-person studio orchestrating dozens of parallel projects and
+      autonomous agents through <a href="https://claude.com/claude-code">Claude Code</a>: e-commerce
+      platforms, AI-generated product catalogs, government-auction and real-estate data explorers,
+      consulting portals, and large-scale local directories -- all in production.
     </p>
 
     <h2>Board Advisory</h2>
@@ -31,10 +34,10 @@ import BlogLayout from '../layouts/BlogLayout.astro';
     <h2>What This Blog Is</h2>
 
     <p>
-      A daily journal of what it's actually like to build software with an AI coding assistant.
+      A working journal of what it's actually like to build and ship software with AI agents at scale.
       Not the polished demo. Not the marketing pitch. The real thing -- the bugs, the breakthroughs,
-      the moments where it saves you three hours, and the moments where you spend three hours
-      fixing what it broke.
+      the moments where it saves you three hours, the moments where you spend three hours fixing what
+      it broke, and the systems you build so it doesn't break the same way twice.
     </p>
 
     <p>Topics you'll find here:</p>
diff --git a/src/pages/hire.astro b/src/pages/hire.astro
index 0ab11d1..248cf94 100644
--- a/src/pages/hire.astro
+++ b/src/pages/hire.astro
@@ -1366,17 +1366,18 @@
             Agent<br /><span class="accent">Abrams</span>
           </h1>
           <p class="hero-subtitle">
-            I build production systems that replace entire dev teams. AI-powered automation,
-            full-stack applications, and e-commerce platforms &mdash; shipped fast, built to last.
+            Solo founder shipping production software fast with AI agents. E-commerce platforms,
+            AI-generated product catalogs, data explorers, and large-scale directories &mdash;
+            the output of a whole team, run by one operator.
           </p>
           <div class="hero-ctas">
             <a href="#projects" class="btn-solid btn-primary">View Projects</a>
             <a href="#inquiry" class="btn-solid btn-outline">Get In Touch</a>
           </div>
           <div class="hero-stats-row">
-            <div class="hero-stat"><span class="hero-stat-num">12+</span><span class="hero-stat-label">Years</span></div>
-            <div class="hero-stat"><span class="hero-stat-num">50+</span><span class="hero-stat-label">Projects</span></div>
-            <div class="hero-stat"><span class="hero-stat-num">74</span><span class="hero-stat-label">AI Agents</span></div>
+            <div class="hero-stat"><span class="hero-stat-num">40+</span><span class="hero-stat-label">Years Building</span></div>
+            <div class="hero-stat"><span class="hero-stat-num">100+</span><span class="hero-stat-label">Projects Shipped</span></div>
+            <div class="hero-stat"><span class="hero-stat-num">Dozens</span><span class="hero-stat-label">AI Agents</span></div>
           </div>
         </div>
 
@@ -1464,17 +1465,17 @@
       <p class="section-label">SELECTED WORK</p>
       <h2 class="section-title">Projects</h2>
 
-      <!-- Featured: Shumi -->
+      <!-- Featured: GovArbitrage -->
       <div class="project-featured">
         <div class="project-featured-content">
           <div class="project-tags">
-            <span class="project-tag">React</span>
+            <span class="project-tag">Next.js</span>
             <span class="project-tag">Full Stack</span>
-            <span class="project-tag">Consumer</span>
+            <span class="project-tag">Data</span>
           </div>
-          <h3 class="project-title">Shumi</h3>
-          <p class="project-desc">A hobby and collection tracking platform. Organize, track, and share your collections with a community of enthusiasts.</p>
-          <span class="project-link" style="opacity: 0.5; cursor: default;">Private Project</span>
+          <h3 class="project-title">GovArbitrage</h3>
+          <p class="project-desc">Aggregates government surplus and seized-asset auctions from dozens of agency sites into one searchable feed, then flags the lots most likely to be underpriced. Tiered subscriptions, Stripe checkout, opportunity scoring.</p>
+          <a href="https://auctions.agentabrams.com" target="_blank" rel="noopener" class="project-link">auctions.agentabrams.com <span>&rarr;</span></a>
         </div>
         <div class="project-featured-visual" aria-hidden="true">
           <svg width="120" height="120" viewBox="0 0 120 120" fill="none" stroke="rgba(232,93,38,0.3)" stroke-width="1" aria-hidden="true">
@@ -1491,24 +1492,24 @@
       <div class="projects-row">
         <div class="project-card">
           <div class="project-tags">
-            <span class="project-tag">Crypto</span>
-            <span class="project-tag">React</span>
-            <span class="project-tag">AI</span>
+            <span class="project-tag">Data</span>
+            <span class="project-tag">PostgreSQL</span>
+            <span class="project-tag">Open Data</span>
           </div>
-          <h3 class="project-title" style="margin-top: 12px;">CoinRotator</h3>
-          <p class="project-desc">Crypto screener with trend analysis, AI-powered insights, custom alerts, and TradingView integration.</p>
-          <a href="https://coinrotator.app" target="_blank" rel="noopener" class="project-link">coinrotator.app <span>&rarr;</span></a>
+          <h3 class="project-title" style="margin-top: 12px;">National Real Estate Explorer</h3>
+          <p class="project-desc">A free, nationwide market-data explorer over open parcel, deed, and housing-index datasets &mdash; multi-million-row tables kept fast with concurrent indexing.</p>
+          <a href="https://nationalrealestate.agentabrams.com" target="_blank" rel="noopener" class="project-link">nationalrealestate.agentabrams.com <span>&rarr;</span></a>
         </div>
 
         <div class="project-card">
           <div class="project-tags">
-            <span class="project-tag">Health</span>
-            <span class="project-tag">Marketplace</span>
-            <span class="project-tag">Full Stack</span>
+            <span class="project-tag">AI</span>
+            <span class="project-tag">Computer Vision</span>
+            <span class="project-tag">Commerce</span>
           </div>
-          <h3 class="project-title" style="margin-top: 12px;">Theta Health</h3>
-          <p class="project-desc">Psychedelic-assisted therapy provider marketplace. Connecting patients with verified practitioners.</p>
-          <a href="https://theta.health" target="_blank" rel="noopener" class="project-link">theta.health <span>&rarr;</span></a>
+          <h3 class="project-title" style="margin-top: 12px;">AI Wallpaper Studio</h3>
+          <p class="project-desc">Original AI-generated wallpaper designs with a seamless-tile quality pipeline &mdash; edge-scanning, regenerate-until-clean, print-resolution masters, and per-colorway recoloring.</p>
+          <a href="https://designerwallcoverings.com" target="_blank" rel="noopener" class="project-link">designerwallcoverings.com <span>&rarr;</span></a>
         </div>
       </div>
 
@@ -1516,24 +1517,24 @@
       <div class="projects-row">
         <div class="project-card">
           <div class="project-tags">
-            <span class="project-tag">NFT</span>
-            <span class="project-tag">Web3</span>
-            <span class="project-tag">Community</span>
+            <span class="project-tag">Consulting</span>
+            <span class="project-tag">Full Stack</span>
+            <span class="project-tag">Growth</span>
           </div>
-          <h3 class="project-title" style="margin-top: 12px;">Crypto Design Club</h3>
-          <p class="project-desc">An NFT and design community bridging creative professionals with blockchain technology.</p>
-          <span class="project-link" style="opacity: 0.5; cursor: default;">Private Project</span>
+          <h3 class="project-title" style="margin-top: 12px;">Client Growth Portals</h3>
+          <p class="project-desc">A consulting engine that turns a short intake into a full per-client growth command center &mdash; competitor research, ad signals, content calendar, costed ideas, and honest first-party analytics.</p>
+          <a href="https://prestige.agentabrams.com" target="_blank" rel="noopener" class="project-link">prestige.agentabrams.com <span>&rarr;</span></a>
         </div>
 
         <div class="project-card">
           <div class="project-tags">
-            <span class="project-tag">Travel</span>
-            <span class="project-tag">Utility</span>
-            <span class="project-tag">Data</span>
+            <span class="project-tag">Next.js</span>
+            <span class="project-tag">Full Stack</span>
+            <span class="project-tag">Consumer</span>
           </div>
-          <h3 class="project-title" style="margin-top: 12px;">Is Water Safe to Drink?</h3>
-          <p class="project-desc">A travel utility providing global water quality data, safety ratings, and purification recommendations by country.</p>
-          <span class="project-link" style="opacity: 0.5; cursor: default;">Private Project</span>
+          <h3 class="project-title" style="margin-top: 12px;">The Unofficial 90210 Guide</h3>
+          <p class="project-desc">A full-stack fan guide to a classic TV series &mdash; 100+ episode recaps, cast profiles, a 700+ item news feed, and playable mini-games. Built and deployed end-to-end in a single session.</p>
+          <a href="https://90210.agentabrams.com" target="_blank" rel="noopener" class="project-link">90210.agentabrams.com <span>&rarr;</span></a>
         </div>
       </div>
     </div>
@@ -1545,8 +1546,8 @@
       <p class="section-label">BUILT WITH AI</p>
       <h2 class="section-title">Claude Code Creations</h2>
       <p class="claude-desc">
-        Production tools built for the interior design trade &mdash; AI-powered applications that automate
-        what used to take entire teams. Click any screenshot to enlarge.
+        Production tools built solo with AI agents &mdash; applications that automate what used to take
+        an entire team, from commerce catalogs to autonomous data pipelines. Click any screenshot to enlarge.
       </p>
 
       <div class="claude-grid">
@@ -1609,8 +1610,8 @@
         <div class="claude-card">
           <img src="/images/tools/blog-agent.jpg" alt="Blog Agent dashboard screenshot" class="claude-card-image" loading="lazy" onclick="openLightbox(this)" />
           <div class="claude-card-body">
-            <h3 class="claude-card-title">74-Agent Orchestrator</h3>
-            <p class="claude-card-desc">Manages 74 autonomous Node.js processes on a single VPS. Auto-restart, memory isolation, failure containment.</p>
+            <h3 class="claude-card-title">Agent Orchestrator</h3>
+            <p class="claude-card-desc">Manages dozens of autonomous Node.js processes on a single VPS. Auto-restart, memory isolation, failure containment.</p>
           </div>
         </div>
 
diff --git a/src/pages/index.astro b/src/pages/index.astro
index 018ef83..4e8b73c 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -21,323 +21,546 @@ function extractNativeVideo(body: string | undefined): string | null {
 ---
 
 <BlogLayout>
-  <!-- Hero -->
-  <div class="hero-banner">
+  <!-- ── Hero ─────────────────────────────────────────────────── -->
+  <section class="hero-banner" aria-label="Introduction">
     <div class="hero-left">
+      <p class="hero-eyebrow">Build in public</p>
       <h1 class="hero-title">Agent<br/>Abrams</h1>
-      <p class="hero-tagline">Building in public with Claude Code.<br/>Bugs, breakthroughs, and everything in between.</p>
-      <div class="hero-cta">
-        <a href="https://x.com/agentabrams" target="_blank" rel="noopener noreferrer" class="btn btn-primary">
-          <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
-          Follow @agentabrams
+      <p class="hero-tagline">A solo developer shipping real software with Claude Code and AI agents — documenting every bug, breakthrough, and late-night decision along the way.</p>
+      <div class="hero-cta" role="list">
+        <a
+          href="https://x.com/agentabrams"
+          target="_blank"
+          rel="noopener noreferrer"
+          class="btn btn-primary"
+          role="listitem"
+          aria-label="Follow Agent Abrams on X (Twitter)"
+        >
+          <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
+          @agentabrams
         </a>
-        <a href="https://bsky.app/profile/agentabrams.bsky.social" target="_blank" rel="noopener noreferrer" class="btn btn-secondary">
-          <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.596 6.449.764 2.81 3.495 3.508 5.98 3.27-4.34.422-8.141 1.822-3.6 6.445 4.946 4.667 6.766-1.2 7.024-2.5.258 1.3 2.078 7.167 7.024 2.5 4.541-4.623.74-6.023-3.6-6.445 2.485.238 5.216-.46 5.98-3.27C19.622 9.418 20 4.458 20 3.768c0-.688-.139-1.86-.902-2.203-.659-.299-1.664-.621-4.3 1.24C12.046 4.747 9.087 8.686 8 10.8z" transform="translate(2 2)"/></svg>
+        <a
+          href="https://bsky.app/profile/agentabrams.bsky.social"
+          target="_blank"
+          rel="noopener noreferrer"
+          class="btn btn-ghost"
+          role="listitem"
+          aria-label="Follow Agent Abrams on Bluesky"
+        >
+          <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.596 6.449.764 2.81 3.495 3.508 5.98 3.27-4.34.422-8.141 1.822-3.6 6.445 4.946 4.667 6.766-1.2 7.024-2.5.258 1.3 2.078 7.167 7.024 2.5 4.541-4.623.74-6.023-3.6-6.445 2.485.238 5.216-.46 5.98-3.27C19.622 9.418 20 4.458 20 3.768c0-.688-.139-1.86-.902-2.203-.659-.299-1.664-.621-4.3 1.24C12.046 4.747 9.087 8.686 8 10.8z" transform="translate(2 2)"/></svg>
           Bluesky
         </a>
-        <a href="https://youtube.com/@agentabrams" target="_blank" rel="noopener noreferrer" class="btn btn-youtube">
-          <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
+        <a
+          href="https://youtube.com/@agentabrams"
+          target="_blank"
+          rel="noopener noreferrer"
+          class="btn btn-ghost"
+          role="listitem"
+          aria-label="Watch Agent Abrams on YouTube"
+        >
+          <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
           YouTube
         </a>
       </div>
     </div>
-    <div class="hero-right">
-      <img src="/images/launch-meme.png" alt="Agent Abrams Launch Day" class="hero-image" />
+    <div class="hero-right" aria-hidden="true">
+      <div class="hero-image-frame">
+        <img src="/images/launch-meme.png" alt="Agent Abrams Launch Day" class="hero-image" width="220" height="220" />
+      </div>
     </div>
-  </div>
-
-  <!-- Posts -->
-  <h2 class="section-heading">Latest Posts</h2>
-
-  {posts.length === 0 && (
-    <p style="color: var(--text-muted); text-align: center; padding: 3rem 0;">Posts coming soon.</p>
-  )}
-
-  <ul class="post-list">
-    {posts.map((post) => {
-      const formattedDate = post.data.date.toLocaleDateString('en-US', {
-        year: 'numeric',
-        month: 'short',
-        day: 'numeric',
-      });
-      const slug = post.id.replace(/^\d{4}-\d{2}-\d{2}-/, '');
-      const heroImage = `/images/heroes/${slug}.png`;
-      const videoId = extractYouTubeId(post.body);
-      const nativeVideo = extractNativeVideo(post.body);
-      return (
-        <li>
-          <a href={`/posts/${slug}/`} class="post-card">
-            <div class="post-card-thumb">
-              <img src={heroImage} alt={post.data.title} loading="lazy" onerror="this.parentElement.style.display='none'" />
-            </div>
-            <div class="post-card-content">
-              <div class="post-card-meta">{formattedDate}</div>
-              <div class="post-card-title">{post.data.title}</div>
-              <div class="post-card-description">{post.data.description}</div>
-              {post.data.tags.length > 0 && (
-                <div class="post-card-tags">
-                  {post.data.tags.map(tag => <span class="tag">{tag}</span>)}
+  </section>
+
+  <!-- ── Latest Posts ─────────────────────────────────────────── -->
+  <section aria-labelledby="posts-heading">
+    <h2 class="section-heading" id="posts-heading">Latest Posts</h2>
+
+    {posts.length === 0 && (
+      <p class="empty-state">Posts coming soon.</p>
+    )}
+
+    <ul class="post-list" role="list">
+      {posts.map((post) => {
+        const formattedDate = post.data.date.toLocaleDateString('en-US', {
+          year: 'numeric',
+          month: 'short',
+          day: 'numeric',
+        });
+        const slug = post.id.replace(/^\d{4}-\d{2}-\d{2}-/, '');
+        const heroImage = `/images/heroes/${slug}.png`;
+        const videoId = extractYouTubeId(post.body);
+        const nativeVideo = extractNativeVideo(post.body);
+        return (
+          <li role="listitem">
+            <a href={`/posts/${slug}/`} class="post-card" aria-label={`Read: ${post.data.title}`}>
+              <div class="post-card-thumb" aria-hidden="true">
+                <img src={heroImage} alt="" loading="lazy" decoding="async" onerror="this.parentElement.style.display='none'" />
+              </div>
+              <div class="post-card-content">
+                <div class="post-card-meta">
+                  <time>{formattedDate}</time>
                 </div>
-              )}
-            </div>
-          </a>
-        </li>
-      );
-    })}
-  </ul>
-
-  <!-- Contact CTA -->
-  <div class="contact-section">
-    <h2>Get In Touch</h2>
-    <p>No gatekeeping. No paywalls. Just a developer sharing the journey.</p>
-    <div class="contact-grid">
-      <a href="https://x.com/agentabrams" target="_blank" rel="noopener noreferrer" class="contact-card">
-        <div class="contact-icon x">
-          <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
+                <div class="post-card-title">{post.data.title}</div>
+                <div class="post-card-description">{post.data.description}</div>
+                {post.data.tags.length > 0 && (
+                  <div class="post-card-tags" aria-label="Tags">
+                    {post.data.tags.map(tag => <span class="tag">{tag}</span>)}
+                  </div>
+                )}
+              </div>
+              <div class="post-card-arrow" aria-hidden="true">→</div>
+            </a>
+          </li>
+        );
+      })}
+    </ul>
+  </section>
+
+  <!-- ── Contact CTA ──────────────────────────────────────────── -->
+  <section class="contact-section" aria-label="Get in touch">
+    <div class="contact-section-header">
+      <h2 class="contact-heading">Get In Touch</h2>
+      <p class="contact-sub">No gatekeeping. No paywalls. Just a developer sharing the journey.</p>
+    </div>
+    <div class="contact-grid" role="list">
+      <a href="https://x.com/agentabrams" target="_blank" rel="noopener noreferrer" class="contact-card" role="listitem" aria-label="Follow Agent Abrams on X">
+        <div class="contact-icon">
+          <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
         </div>
         <div class="contact-label">@agentabrams</div>
-        <div class="contact-desc">Follow for daily updates</div>
+        <div class="contact-desc">Daily updates on X</div>
       </a>
-      <a href="https://bsky.app/profile/agentabrams.bsky.social" target="_blank" rel="noopener noreferrer" class="contact-card">
-        <div class="contact-icon github">
-          <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.596 6.449.764 2.81 3.495 3.508 5.98 3.27-4.34.422-8.141 1.822-3.6 6.445 4.946 4.667 6.766-1.2 7.024-2.5.258 1.3 2.078 7.167 7.024 2.5 4.541-4.623.74-6.023-3.6-6.445 2.485.238 5.216-.46 5.98-3.27C19.622 9.418 20 4.458 20 3.768c0-.688-.139-1.86-.902-2.203-.659-.299-1.664-.621-4.3 1.24C12.046 4.747 9.087 8.686 8 10.8z" transform="translate(2 2)"/></svg>
+      <a href="https://bsky.app/profile/agentabrams.bsky.social" target="_blank" rel="noopener noreferrer" class="contact-card" role="listitem" aria-label="Follow Agent Abrams on Bluesky">
+        <div class="contact-icon">
+          <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.596 6.449.764 2.81 3.495 3.508 5.98 3.27-4.34.422-8.141 1.822-3.6 6.445 4.946 4.667 6.766-1.2 7.024-2.5.258 1.3 2.078 7.167 7.024 2.5 4.541-4.623.74-6.023-3.6-6.445 2.485.238 5.216-.46 5.98-3.27C19.622 9.418 20 4.458 20 3.768c0-.688-.139-1.86-.902-2.203-.659-.299-1.664-.621-4.3 1.24C12.046 4.747 9.087 8.686 8 10.8z" transform="translate(2 2)"/></svg>
         </div>
         <div class="contact-label">@agentabrams</div>
         <div class="contact-desc">Follow on Bluesky</div>
       </a>
-      <a href="https://youtube.com/@agentabrams" target="_blank" rel="noopener noreferrer" class="contact-card">
-        <div class="contact-icon youtube">
-          <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
+      <a href="https://youtube.com/@agentabrams" target="_blank" rel="noopener noreferrer" class="contact-card" role="listitem" aria-label="Watch Agent Abrams on YouTube">
+        <div class="contact-icon">
+          <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
         </div>
         <div class="contact-label">YouTube</div>
-        <div class="contact-desc">Walkthroughs and demos</div>
+        <div class="contact-desc">Walkthroughs & demos</div>
       </a>
-      <a href="mailto:agent@goodquestion.ai" class="contact-card">
-        <div class="contact-icon email">
-          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>
+      <a href="mailto:agent@goodquestion.ai" class="contact-card" role="listitem" aria-label="Email Agent Abrams">
+        <div class="contact-icon">
+          <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>
         </div>
         <div class="contact-label">Email</div>
         <div class="contact-desc">agent@goodquestion.ai</div>
       </a>
     </div>
-  </div>
+  </section>
 </BlogLayout>
 
 <style>
-  /* Hero Banner */
+  /* ── Hero ─────────────────────────────────────────────────── */
   .hero-banner {
     display: flex;
     align-items: center;
-    gap: 2rem;
-    margin-bottom: 2.5rem;
-    padding: 2rem 0;
+    gap: 3rem;
+    padding: 3.5rem 0 3rem;
+    margin-bottom: 0;
+  }
+
+  .hero-left {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .hero-right {
+    flex-shrink: 0;
+  }
+
+  /* "Build in public" eyebrow */
+  .hero-eyebrow {
+    display: inline-block;
+    font-family: var(--font-mono);
+    font-size: 0.75rem;
+    font-weight: 600;
+    letter-spacing: 0.14em;
+    text-transform: uppercase;
+    color: var(--accent);
+    background: var(--accent-subtle);
+    border: 1px solid rgba(232, 93, 38, 0.2);
+    padding: 0.25em 0.75em;
+    border-radius: 999px;
+    margin-bottom: 1.1rem;
   }
-  .hero-left { flex: 1; }
-  .hero-right { flex-shrink: 0; }
+
   .hero-title {
-    font-family: 'Playfair Display', serif;
-    font-size: 3.5rem;
+    font-family: var(--font-display);
+    font-size: clamp(3rem, 8vw, 5rem);
     font-weight: 900;
-    line-height: 1;
-    margin: 0 0 0.75rem;
-    color: #E85D26;
+    line-height: 0.95;
+    letter-spacing: -0.04em;
+    margin: 0 0 1rem;
+    color: var(--text-primary);
+    /* Subtle gradient shimmer on the name */
+    background: linear-gradient(135deg, var(--text-primary) 60%, var(--accent) 130%);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
   }
+
   .hero-tagline {
-    font-family: 'Inter', sans-serif;
+    font-family: var(--font-body);
     color: var(--text-secondary);
-    font-size: 1.05rem;
-    margin-bottom: 1.5rem;
-    line-height: 1.6;
+    font-size: clamp(0.97rem, 2vw, 1.08rem);
+    line-height: 1.7;
+    margin-bottom: 2rem;
+    max-width: 46ch;
+  }
+
+  /* Hero image */
+  .hero-image-frame {
+    position: relative;
+    display: inline-block;
+  }
+
+  .hero-image-frame::before {
+    content: '';
+    position: absolute;
+    inset: -3px;
+    border-radius: calc(var(--radius-lg) + 3px);
+    background: linear-gradient(135deg, var(--accent) 0%, transparent 60%);
+    opacity: 0.4;
+    z-index: 0;
   }
+
   .hero-image {
-    width: 220px;
-    height: 220px;
-    border-radius: 16px;
-    border: 2px solid var(--border);
+    position: relative;
+    z-index: 1;
+    width: 200px;
+    height: 200px;
+    border-radius: var(--radius-lg);
+    border: 1px solid var(--border-strong);
     object-fit: cover;
-    box-shadow: 0 8px 32px rgba(0,0,0,0.4);
+    box-shadow: var(--shadow-lg);
+    display: block;
   }
 
   /* CTA Buttons */
   .hero-cta {
     display: flex;
-    gap: 0.75rem;
+    gap: 0.6rem;
     flex-wrap: wrap;
+    align-items: center;
   }
+
   .btn {
     display: inline-flex;
     align-items: center;
-    gap: 0.5rem;
-    padding: 0.6rem 1.2rem;
-    border-radius: 8px;
-    font-size: 1rem;
+    gap: 0.45rem;
+    padding: 0.55rem 1.1rem;
+    border-radius: var(--radius-md);
+    font-size: 0.88rem;
     font-weight: 600;
     text-decoration: none;
-    transition: all 0.2s;
+    transition: background var(--duration-fast) var(--ease-out),
+                color var(--duration-fast) var(--ease-out),
+                border-color var(--duration-fast) var(--ease-out),
+                box-shadow var(--duration-fast) var(--ease-out),
+                transform var(--duration-fast) var(--ease-out);
     border: 1px solid transparent;
+    white-space: nowrap;
   }
-  .btn:hover { transform: translateY(-1px); text-decoration: none; }
+
+  .btn:hover {
+    transform: translateY(-1px);
+    text-decoration: none;
+  }
+
   .btn-primary {
-    background: #fff;
-    color: #000;
-    border-color: #fff;
+    background: var(--text-primary);
+    color: var(--bg-primary);
+    border-color: var(--text-primary);
   }
-  .btn-primary:hover { background: #e0e0e0; color: #000; }
-  .btn-secondary {
-    background: transparent;
-    color: var(--text-primary);
-    border-color: var(--border);
+
+  .btn-primary:hover {
+    background: var(--accent);
+    border-color: var(--accent);
+    color: #fff;
+    box-shadow: 0 4px 14px rgba(232, 93, 38, 0.35);
   }
-  .btn-secondary:hover { border-color: var(--accent); color: var(--accent); }
-  .btn-youtube {
+
+  .btn-ghost {
     background: transparent;
-    color: var(--text-primary);
-    border-color: var(--border);
+    color: var(--text-secondary);
+    border-color: var(--border-strong);
+  }
+
+  .btn-ghost:hover {
+    border-color: var(--accent);
+    color: var(--accent);
+    background: var(--accent-subtle);
+  }
+
+  /* ── Section heading override for posts ─────────────────── */
+  .section-heading {
+    font-size: 0.72rem;
+    letter-spacing: 0.12em;
+    text-transform: uppercase;
+    color: var(--text-muted);
+    font-weight: 600;
+    font-family: var(--font-mono);
+    margin: 3rem 0 1rem;
+    padding: 0;
+    border: none;
+    border-bottom: 1px solid var(--border);
+    padding-bottom: 0.75rem;
+  }
+
+  /* ── Post Cards ──────────────────────────────────────────── */
+  .post-list {
+    gap: 0.5rem;
   }
-  .btn-youtube:hover { border-color: var(--accent); color: var(--accent); }
 
-  /* Post Card with Thumbnail */
+  /* Layout override — thumbnail left, content right, arrow right */
   .post-card {
     display: flex;
     gap: 1.25rem;
     align-items: flex-start;
+    padding: 1.1rem 1.25rem;
+    border-radius: var(--radius-md);
   }
+
   .post-card-thumb {
     flex-shrink: 0;
-    width: 200px;
-    height: 112px;
-    border-radius: 10px;
+    width: 180px;
+    height: 101px;
+    border-radius: var(--radius-sm);
     overflow: hidden;
     border: 1px solid var(--border);
-    background: var(--bg-secondary);
+    background: var(--bg-tertiary);
   }
+
   .post-card-thumb img {
     width: 100%;
     height: 100%;
     object-fit: cover;
-    transition: transform 0.3s;
+    transition: transform var(--duration-base) var(--ease-out);
+    display: block;
   }
+
   .post-card:hover .post-card-thumb img {
-    transform: scale(1.05);
+    transform: scale(1.04);
   }
+
   .post-card-content {
     flex: 1;
     min-width: 0;
   }
 
-  /* Post Video Embed */
-  .post-video {
-    margin-top: 0.75rem;
-    border-radius: 10px;
-    overflow: hidden;
-    border: 1px solid var(--border);
+  .post-card-meta time {
+    font-family: var(--font-mono);
+    font-size: 0.78rem;
+    color: var(--text-muted);
+    letter-spacing: 0.03em;
   }
-  .youtube-embed {
-    position: relative;
-    padding-bottom: 56.25%; /* 16:9 */
-    height: 0;
+
+  .post-card-title {
+    font-size: 1.05rem;
+    margin-bottom: 0.3rem;
+    margin-top: 0.25rem;
+  }
+
+  .post-card-description {
+    font-size: 0.9rem;
+    color: var(--text-secondary);
+    line-height: 1.55;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
     overflow: hidden;
   }
-  .youtube-embed iframe {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    border: none;
+
+  /* Trailing arrow — visual affordance */
+  .post-card-arrow {
+    flex-shrink: 0;
+    align-self: center;
+    color: var(--text-muted);
+    font-size: 1.1rem;
+    transition: color var(--duration-fast) var(--ease-out),
+                transform var(--duration-fast) var(--ease-out);
+    margin-left: 0.25rem;
   }
-  .post-video video {
-    width: 100%;
-    max-height: 400px;
-    background: #000;
-    display: block;
+
+  .post-card:hover .post-card-arrow {
+    color: var(--accent);
+    transform: translateX(3px);
   }
 
-  /* Section Heading */
-  .section-heading {
-    font-family: 'Playfair Display', serif;
-    font-weight: 900;
-    font-size: 1.5rem;
-    margin: 2rem 0 1rem;
-    padding-bottom: 0.5rem;
-    border-bottom: 1px solid var(--border);
+  /* ── Empty state ─────────────────────────────────────────── */
+  .empty-state {
+    color: var(--text-muted);
+    text-align: center;
+    padding: 3.5rem 0;
+    font-size: 0.95rem;
   }
 
-  /* Contact Section */
+  /* ── Contact Section ─────────────────────────────────────── */
   .contact-section {
-    margin-top: 3rem;
-    padding: 2rem;
+    margin-top: 4rem;
+    padding: 2.25rem 2rem;
     background: var(--bg-secondary);
     border: 1px solid var(--border);
-    border-radius: 12px;
+    border-radius: var(--radius-xl);
+    position: relative;
+    overflow: hidden;
+  }
+
+  /* Subtle accent glow behind the section */
+  .contact-section::before {
+    content: '';
+    position: absolute;
+    top: -40px;
+    right: -40px;
+    width: 200px;
+    height: 200px;
+    background: radial-gradient(circle, rgba(232, 93, 38, 0.08) 0%, transparent 70%);
+    pointer-events: none;
+  }
+
+  .contact-section-header {
+    margin-bottom: 1.5rem;
   }
-  .contact-section h2 {
-    font-family: 'Playfair Display', serif;
+
+  .contact-heading {
+    font-family: var(--font-display);
     font-weight: 900;
-    font-size: 1.5rem;
-    margin: 0 0 0.25rem;
+    font-size: clamp(1.3rem, 3vw, 1.6rem);
+    margin: 0 0 0.35rem;
     border: none;
     padding: 0;
+    letter-spacing: -0.03em;
   }
-  .contact-section > p {
+
+  .contact-sub {
     color: var(--text-secondary);
-    font-size: 1rem;
-    margin-bottom: 1.5rem;
+    font-size: 0.95rem;
+    margin: 0;
+    line-height: 1.6;
   }
+
   .contact-grid {
     display: grid;
     grid-template-columns: repeat(4, 1fr);
-    gap: 1rem;
+    gap: 0.75rem;
   }
+
   .contact-card {
     display: flex;
     flex-direction: column;
     align-items: center;
     text-align: center;
-    padding: 1.25rem 0.75rem;
-    border-radius: 10px;
+    padding: 1.25rem 0.75rem 1rem;
+    border-radius: var(--radius-md);
     border: 1px solid var(--border);
     background: var(--bg-primary);
     text-decoration: none;
-    transition: all 0.2s;
+    transition: border-color var(--duration-base) var(--ease-out),
+                background var(--duration-base) var(--ease-out),
+                box-shadow var(--duration-base) var(--ease-out),
+                transform var(--duration-base) var(--ease-out);
   }
+
   .contact-card:hover {
     border-color: var(--accent);
+    background: var(--accent-subtle);
+    box-shadow: var(--shadow-sm);
     transform: translateY(-2px);
     text-decoration: none;
   }
+
   .contact-icon {
-    width: 48px;
-    height: 48px;
-    border-radius: 12px;
+    width: 44px;
+    height: 44px;
+    border-radius: var(--radius-md);
     display: flex;
     align-items: center;
     justify-content: center;
-    margin-bottom: 0.75rem;
+    margin-bottom: 0.65rem;
+    background: var(--accent-subtle);
+    color: var(--accent);
+    transition: background var(--duration-fast), transform var(--duration-fast);
+  }
+
+  .contact-card:hover .contact-icon {
+    background: rgba(232, 93, 38, 0.2);
+    transform: scale(1.05);
   }
-  .contact-icon.x { background: rgba(232,93,38,0.1); color: #E85D26; }
-  .contact-icon.github { background: rgba(232,93,38,0.1); color: #E85D26; }
-  .contact-icon.youtube { background: rgba(232,93,38,0.1); color: #E85D26; }
-  .contact-icon.email { background: rgba(232,93,38,0.1); color: #E85D26; }
+
   .contact-label {
     font-weight: 600;
-    font-size: 1rem;
+    font-size: 0.85rem;
     color: var(--text-primary);
-    margin-bottom: 0.25rem;
+    margin-bottom: 0.2rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    max-width: 100%;
   }
+
   .contact-desc {
-    font-size: 1rem;
+    font-size: 0.78rem;
     color: var(--text-muted);
+    line-height: 1.4;
   }
 
-  /* Responsive */
+  /* ── Responsive ──────────────────────────────────────────── */
   @media (max-width: 640px) {
-    .hero-banner { flex-direction: column-reverse; text-align: center; }
-    .hero-image { width: 160px; height: 160px; }
-    .hero-cta { justify-content: center; }
-    .hero-title { font-size: 2.2rem; }
-    .contact-grid { grid-template-columns: repeat(2, 1fr); }
-    .post-card { flex-direction: column; }
-    .post-card-thumb { width: 100%; height: 180px; }
+    .hero-banner {
+      flex-direction: column-reverse;
+      text-align: center;
+      gap: 1.75rem;
+      padding: 2rem 0 2rem;
+    }
+
+    .hero-title {
+      font-size: clamp(2.8rem, 14vw, 3.5rem);
+    }
+
+    .hero-tagline {
+      max-width: 100%;
+    }
+
+    .hero-image {
+      width: 150px;
+      height: 150px;
+    }
+
+    .hero-cta {
+      justify-content: center;
+    }
+
+    .post-card {
+      flex-direction: column;
+      gap: 0.85rem;
+    }
+
+    .post-card-thumb {
+      width: 100%;
+      height: 160px;
+    }
+
+    .post-card-arrow {
+      display: none;
+    }
+
+    .contact-grid {
+      grid-template-columns: repeat(2, 1fr);
+    }
+
+    .contact-section {
+      padding: 1.5rem 1.25rem;
+    }
+  }
+
+  @media (max-width: 400px) {
+    .hero-title {
+      font-size: 2.5rem;
+    }
   }
 </style>
diff --git a/src/styles/global.css b/src/styles/global.css
index e13c514..8ae25c8 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -1,67 +1,140 @@
 /* === goodquestion.ai — Global Styles === */
-/* GitHub-dark inspired developer blog theme */
+/* Modern indie-hacker / developer-blog aesthetic — 2024 refresh */
 
+/* ─── Design tokens ─────────────────────────────────────────── */
 :root {
-  --bg-primary: #0A0A0A;
-  --bg-secondary: #111111;
-  --bg-tertiary: #1A1A1A;
-  --text-primary: #FFFFFF;
-  --text-secondary: #AAAAAA;
-  --text-muted: #AAAAAA;
-  --accent: #E85D26;
-  --accent-hover: #FF7040;
-  --border: #1A1A1A;
-  --code-bg: #111111;
-  --code-inline-bg: #151515;
-  --success: #3fb950;
-  --warning: #d29922;
-  --danger: #f85149;
-  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
-  --font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
-  --max-width: 720px;
-  --header-height: 64px;
+  /* Dark-mode palette (default) */
+  --bg-primary:       #0C0C0E;
+  --bg-secondary:     #13131A;
+  --bg-tertiary:      #1C1C28;
+  --bg-glass:         rgba(19, 19, 26, 0.80);
+
+  --text-primary:     #F0EEF8;
+  --text-secondary:   #9B99B5;
+  --text-muted:       #6B6985;
+
+  --accent:           #E85D26;
+  --accent-hover:     #FF7540;
+  --accent-subtle:    rgba(232, 93, 38, 0.10);
+  --accent-subtle-hover: rgba(232, 93, 38, 0.18);
+
+  --border:           rgba(255, 255, 255, 0.07);
+  --border-strong:    rgba(255, 255, 255, 0.14);
+
+  --code-bg:          #0F0F18;
+  --code-inline-bg:   #1A1A28;
+
+  --success:          #4ade80;
+  --warning:          #fbbf24;
+  --danger:           #f87171;
+
+  /* Typography */
+  --font-body:   'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
+  --font-display:'Playfair Display', Georgia, 'Times New Roman', serif;
+  --font-mono:   'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
+
+  /* Layout */
+  --max-width:      740px;
+  --content-width:  65ch;
+  --header-height:  60px;
+
+  /* Motion */
+  --ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
+  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
+  --duration-fast: 140ms;
+  --duration-base: 220ms;
+
+  /* Shadows */
+  --shadow-sm:  0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.6);
+  --shadow-md:  0 4px 16px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.4);
+  --shadow-lg:  0 12px 40px rgba(0, 0, 0, 0.6), 0 4px 12px rgba(0, 0, 0, 0.5);
+  --shadow-accent: 0 0 0 3px rgba(232, 93, 38, 0.35);
+
+  /* Border radius */
+  --radius-sm:  5px;
+  --radius-md:  10px;
+  --radius-lg:  16px;
+  --radius-xl:  22px;
+}
+
+/* Light mode — graceful degradation if user has light preference */
+@media (prefers-color-scheme: light) {
+  :root {
+    --bg-primary:    #FAFAF9;
+    --bg-secondary:  #F4F3F0;
+    --bg-tertiary:   #EEEDE9;
+    --bg-glass:      rgba(250, 250, 249, 0.88);
+
+    --text-primary:   #16141E;
+    --text-secondary: #595470;
+    --text-muted:     #8B88A6;
+
+    --border:        rgba(0, 0, 0, 0.07);
+    --border-strong: rgba(0, 0, 0, 0.14);
+
+    --code-bg:          #F0EFF8;
+    --code-inline-bg:   #ECEAF6;
+
+    --shadow-sm:  0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05);
+    --shadow-md:  0 4px 16px rgba(0, 0, 0, 0.10), 0 2px 6px rgba(0, 0, 0, 0.07);
+    --shadow-lg:  0 12px 40px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.08);
+  }
 }
 
+/* ─── Reset ──────────────────────────────────────────────────── */
 *, *::before, *::after {
   box-sizing: border-box;
   margin: 0;
   padding: 0;
 }
 
+/* ─── Base ───────────────────────────────────────────────────── */
 html {
   font-size: 16px;
   scroll-behavior: smooth;
+  text-rendering: optimizeLegibility;
+  -webkit-text-size-adjust: 100%;
 }
 
 body {
   font-family: var(--font-body);
-  font-size: 19px;
+  font-size: clamp(17px, 2.5vw, 19px);
   color: var(--text-primary);
   background-color: var(--bg-primary);
-  line-height: 1.7;
+  line-height: 1.75;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
+  /* subtle gradient wash behind everything */
+  background-image: radial-gradient(ellipse 80% 50% at 50% -10%, rgba(232, 93, 38, 0.06) 0%, transparent 70%);
+  background-attachment: fixed;
 }
 
-/* === Layout === */
+/* ─── Focus states (WCAG AA) ─────────────────────────────────── */
+:focus-visible {
+  outline: 2px solid var(--accent);
+  outline-offset: 3px;
+  border-radius: var(--radius-sm);
+}
+
+/* ─── Layout ─────────────────────────────────────────────────── */
 .container {
   max-width: var(--max-width);
   margin: 0 auto;
   padding: 0 1.5rem;
 }
 
-/* === Header === */
+/* ─── Header ─────────────────────────────────────────────────── */
 .site-header {
   position: sticky;
   top: 0;
   z-index: 100;
-  background: var(--bg-primary);
-  border-bottom: 1px solid var(--border);
   height: var(--header-height);
   display: flex;
   align-items: center;
-  backdrop-filter: blur(12px);
-  background: rgba(10, 10, 10, 0.92);
+  background: var(--bg-glass);
+  border-bottom: 1px solid var(--border);
+  backdrop-filter: blur(16px) saturate(180%);
+  -webkit-backdrop-filter: blur(16px) saturate(180%);
 }
 
 .site-header .container {
@@ -72,118 +145,161 @@ body {
 }
 
 .site-logo {
-  font-family: 'Playfair Display', serif;
-  font-size: 1.1rem;
+  font-family: var(--font-display);
+  font-size: 1.05rem;
   font-weight: 900;
   color: var(--accent);
   text-decoration: none;
-  letter-spacing: -0.5px;
+  letter-spacing: -0.3px;
+  transition: color var(--duration-fast) var(--ease-out),
+              opacity var(--duration-fast) var(--ease-out);
 }
 
 .site-logo:hover {
   color: var(--accent-hover);
+  opacity: 0.92;
+  text-decoration: none;
 }
 
 .site-nav {
   display: flex;
-  gap: 1.5rem;
+  gap: 0.25rem;
   align-items: center;
 }
 
 .site-nav a {
   color: var(--text-secondary);
   text-decoration: none;
-  font-size: 1rem;
-  transition: color 0.2s;
+  font-size: 0.9rem;
+  font-weight: 500;
+  padding: 0.35rem 0.7rem;
+  border-radius: var(--radius-sm);
+  transition: color var(--duration-fast) var(--ease-out),
+              background var(--duration-fast) var(--ease-out);
+}
+
+.site-nav a:hover {
+  color: var(--text-primary);
+  background: var(--border);
+  text-decoration: none;
 }
 
-.site-nav a:hover,
 .site-nav a.active {
   color: var(--accent);
+  background: var(--accent-subtle);
+}
+
+.site-nav a[target="_blank"] {
+  color: var(--text-muted);
+}
+.site-nav a[target="_blank"]:hover {
+  color: var(--text-secondary);
 }
 
-/* === Footer === */
+/* ─── Footer ─────────────────────────────────────────────────── */
 .site-footer {
   border-top: 1px solid var(--border);
-  padding: 2rem 0;
-  margin-top: 4rem;
+  padding: 2.5rem 0;
+  margin-top: 5rem;
   text-align: center;
   color: var(--text-muted);
-  font-size: 1rem;
+  font-size: 0.9rem;
 }
 
 .site-footer a {
   color: var(--text-secondary);
   text-decoration: none;
+  transition: color var(--duration-fast);
 }
 
 .site-footer a:hover {
   color: var(--accent);
 }
 
-/* === Main Content === */
+/* ─── Main Content ───────────────────────────────────────────── */
 main {
-  padding: 2rem 0;
-  min-height: calc(100vh - var(--header-height) - 120px);
+  padding: 2.5rem 0 4rem;
+  min-height: calc(100vh - var(--header-height) - 140px);
 }
 
-/* === Links === */
+/* ─── Links ──────────────────────────────────────────────────── */
 a {
   color: var(--accent);
-  text-decoration: none;
-  transition: color 0.2s;
+  text-decoration: underline;
+  text-decoration-color: rgba(232, 93, 38, 0.35);
+  text-underline-offset: 3px;
+  transition: color var(--duration-fast) var(--ease-out),
+              text-decoration-color var(--duration-fast) var(--ease-out);
 }
 
 a:hover {
   color: var(--accent-hover);
-  text-decoration: underline;
+  text-decoration-color: var(--accent-hover);
 }
 
-/* === Typography === */
+/* ─── Typography ─────────────────────────────────────────────── */
 h1, h2, h3, h4, h5, h6 {
-  font-family: 'Playfair Display', serif;
+  font-family: var(--font-display);
   color: var(--text-primary);
+  font-weight: 800;
+  line-height: 1.25;
+  letter-spacing: -0.02em;
+  margin-top: 2em;
+  margin-bottom: 0.6em;
+}
+
+h1 {
+  font-size: clamp(1.75rem, 4vw, 2.4rem);
   font-weight: 900;
-  line-height: 1.3;
-  margin-top: 1.5em;
-  margin-bottom: 0.5em;
+  margin-top: 0;
+  letter-spacing: -0.03em;
+}
+
+h2 {
+  font-size: clamp(1.3rem, 3vw, 1.65rem);
+  padding-bottom: 0.4em;
+  border-bottom: 1px solid var(--border);
 }
 
-h1 { font-size: 2rem; margin-top: 0; }
-h2 { font-size: 1.5rem; border-bottom: 1px solid var(--border); padding-bottom: 0.3em; }
-h3 { font-size: 1.25rem; }
+h3 { font-size: clamp(1.1rem, 2.5vw, 1.3rem); }
 h4 { font-size: 1.1rem; }
 
 p {
-  margin-bottom: 1em;
+  margin-bottom: 1.2em;
 }
 
 blockquote {
   border-left: 3px solid var(--accent);
-  margin: 1.5em 0;
-  padding: 0.5em 1em;
+  margin: 2em 0;
+  padding: 1rem 1.25rem;
   color: var(--text-secondary);
   background: var(--bg-secondary);
-  border-radius: 0 6px 6px 0;
+  border-radius: 0 var(--radius-md) var(--radius-md) 0;
+  font-style: italic;
 }
 
 blockquote p:last-child {
   margin-bottom: 0;
 }
 
+blockquote strong {
+  color: var(--text-primary);
+}
+
 hr {
   border: none;
   border-top: 1px solid var(--border);
-  margin: 2em 0;
+  margin: 2.5em 0;
 }
 
 ul, ol {
   padding-left: 1.5em;
-  margin-bottom: 1em;
+  margin-bottom: 1.2em;
 }
 
 li {
-  margin-bottom: 0.3em;
+  margin-bottom: 0.4em;
+  line-height: 1.65;
 }
 
 strong {
@@ -195,29 +311,31 @@ em {
   color: var(--text-secondary);
 }
 
-/* === Code Blocks === */
+/* ─── Code ───────────────────────────────────────────────────── */
 code {
   font-family: var(--font-mono);
-  font-size: 1em;
+  font-size: 0.9em;
 }
 
 :not(pre) > code {
   background: var(--code-inline-bg);
   color: var(--accent);
-  padding: 0.15em 0.4em;
-  border-radius: 4px;
-  border: 1px solid var(--border);
+  padding: 0.15em 0.45em;
+  border-radius: var(--radius-sm);
+  border: 1px solid var(--border-strong);
+  font-size: 0.875em;
 }
 
 pre {
   background: var(--code-bg) !important;
   border: 1px solid var(--border);
-  border-radius: 8px;
-  padding: 1.25rem;
+  border-radius: var(--radius-md);
+  padding: 1.4rem 1.5rem;
   overflow-x: auto;
-  margin: 1.5em 0;
-  line-height: 1.5;
-  font-size: 1rem;
+  margin: 1.75em 0;
+  line-height: 1.6;
+  font-size: 0.9rem;
+  box-shadow: var(--shadow-sm);
 }
 
 pre code {
@@ -225,114 +343,154 @@ pre code {
   border: none;
   padding: 0;
   color: var(--text-primary);
+  font-size: 1em;
 }
 
-/* === Post Index === */
+/* ─── Post card list (shared across pages) ───────────────────── */
 .post-list {
   list-style: none;
   padding: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 0.75rem;
 }
 
+/* Base .post-card — overrides from index.astro scoped styles build on this */
 .post-card {
   display: block;
-  padding: 1.5rem;
-  margin-bottom: 1rem;
+  padding: 1.5rem 1.75rem;
   border: 1px solid var(--border);
-  border-radius: 8px;
+  border-radius: var(--radius-lg);
   background: var(--bg-secondary);
   text-decoration: none;
-  transition: border-color 0.2s, transform 0.15s;
+  transition: border-color var(--duration-base) var(--ease-out),
+              box-shadow var(--duration-base) var(--ease-out),
+              transform var(--duration-base) var(--ease-out);
 }
 
 .post-card:hover {
-  border-color: var(--accent);
+  border-color: var(--border-strong);
+  box-shadow: var(--shadow-md);
   transform: translateY(-2px);
   text-decoration: none;
 }
 
 .post-card-title {
-  font-family: 'Playfair Display', serif;
-  font-size: 1.25rem;
+  font-family: var(--font-display);
+  font-size: 1.2rem;
   font-weight: 800;
   color: var(--text-primary);
-  margin-bottom: 0.5rem;
+  margin-bottom: 0.4rem;
   line-height: 1.3;
+  letter-spacing: -0.02em;
 }
 
 .post-card-meta {
-  font-size: 1rem;
+  font-size: 0.8rem;
   color: var(--text-muted);
   margin-bottom: 0.5rem;
   font-family: var(--font-mono);
+  letter-spacing: 0.02em;
 }
 
 .post-card-description {
   color: var(--text-secondary);
-  font-size: 1rem;
-  line-height: 1.5;
+  font-size: 0.95rem;
+  line-height: 1.6;
 }
 
 .post-card-tags {
   display: flex;
-  gap: 0.5rem;
-  margin-top: 0.75rem;
+  gap: 0.4rem;
+  margin-top: 0.85rem;
   flex-wrap: wrap;
 }
 
-/* === Tags === */
+/* ─── Tags ───────────────────────────────────────────────────── */
 .tag {
   display: inline-block;
-  font-size: 1rem;
+  font-size: 0.72rem;
   font-family: var(--font-mono);
-  padding: 0.15em 0.6em;
-  border-radius: 12px;
-  background: var(--bg-tertiary);
+  font-weight: 500;
+  letter-spacing: 0.04em;
+  padding: 0.2em 0.65em;
+  border-radius: 999px;
+  background: var(--accent-subtle);
   color: var(--accent);
-  border: 1px solid var(--border);
+  border: 1px solid rgba(232, 93, 38, 0.2);
+  transition: background var(--duration-fast), border-color var(--duration-fast);
+}
+
+.tag:hover {
+  background: var(--accent-subtle-hover);
 }
 
-/* === Post Layout === */
+/* ─── Post layout (reading view) ────────────────────────────── */
 .post-header {
-  margin-bottom: 2rem;
-  padding-bottom: 1.5rem;
+  margin-bottom: 2.5rem;
+  padding-bottom: 2rem;
   border-bottom: 1px solid var(--border);
 }
 
 .post-title {
-  font-size: 2rem;
-  font-weight: 700;
-  line-height: 1.2;
-  margin-bottom: 0.75rem;
+  font-family: var(--font-display);
+  font-size: clamp(1.85rem, 5vw, 2.75rem);
+  font-weight: 900;
+  line-height: 1.15;
+  letter-spacing: -0.03em;
+  margin-bottom: 1rem;
+  color: var(--text-primary);
 }
 
 .post-meta {
   color: var(--text-muted);
-  font-size: 1rem;
+  font-size: 0.85rem;
   font-family: var(--font-mono);
   display: flex;
-  gap: 1.5rem;
+  gap: 1.25rem;
   flex-wrap: wrap;
   align-items: center;
+  letter-spacing: 0.02em;
 }
 
+/* Separator dots — applied in PostLayout scoped styles for specificity */
+
+/* Reading column — capped at comfortable measure */
 .post-content {
-  font-size: 1.1rem;
+  font-size: clamp(1rem, 2vw, 1.1rem);
+  max-width: var(--content-width);
 }
 
 .post-content img {
   max-width: 100%;
-  border-radius: 8px;
+  border-radius: var(--radius-md);
   border: 1px solid var(--border);
-  margin: 1.5em 0;
+  margin: 2em 0;
+  box-shadow: var(--shadow-md);
+}
+
+.post-content a {
+  font-weight: 500;
 }
 
-/* === About Page === */
+/* In-post headings — slightly smaller than page h1 */
+.post-content h2 {
+  font-size: clamp(1.25rem, 2.8vw, 1.5rem);
+  margin-top: 2.5em;
+}
+
+.post-content h3 {
+  font-size: clamp(1.05rem, 2.3vw, 1.2rem);
+  margin-top: 2em;
+}
+
+/* ─── About page ─────────────────────────────────────────────── */
 .about-content {
   max-width: 600px;
 }
 
 .about-content h1 {
-  font-size: 2rem;
+  font-size: clamp(1.75rem, 4vw, 2.4rem);
   margin-bottom: 0.5rem;
 }
 
@@ -343,16 +501,17 @@ pre code {
   font-style: italic;
 }
 
-/* === Hero / Index Header === */
+/* ─── Hero / Index ───────────────────────────────────────────── */
+/* Kept for backward compat (used by pages that render a .hero div) */
 .hero {
-  margin-bottom: 2.5rem;
-  padding-bottom: 1.5rem;
+  margin-bottom: 3rem;
+  padding-bottom: 2rem;
   border-bottom: 1px solid var(--border);
 }
 
 .hero h1 {
-  font-size: 1.8rem;
-  margin-bottom: 0.5rem;
+  font-size: clamp(1.6rem, 4vw, 2.2rem);
+  margin-bottom: 0.6rem;
 }
 
 .hero p {
@@ -360,50 +519,63 @@ pre code {
   font-size: 1.05rem;
 }
 
-/* === Responsive === */
-@media (max-width: 640px) {
-  :root {
-    --max-width: 100%;
-  }
+/* ─── Section heading ────────────────────────────────────────── */
+.section-heading {
+  font-family: var(--font-display);
+  font-weight: 900;
+  font-size: 1.1rem;
+  letter-spacing: 0.06em;
+  text-transform: uppercase;
+  color: var(--text-muted);
+  margin: 2.5rem 0 1.25rem;
+  padding-bottom: 0;
+  border-bottom: none;
+}
 
+/* ─── Responsive ─────────────────────────────────────────────── */
+@media (max-width: 640px) {
   .container {
-    padding: 0 1rem;
+    padding: 0 1.1rem;
   }
 
-  h1, .post-title {
-    font-size: 1.5rem;
+  .post-meta {
+    flex-direction: column;
+    gap: 0.35rem;
+    align-items: flex-start;
   }
 
-  h2 {
-    font-size: 1.25rem;
+  .post-meta span + span::before {
+    display: none;
   }
 
-  .post-meta {
-    flex-direction: column;
-    gap: 0.5rem;
+  .site-nav {
+    gap: 0;
   }
 
-  .site-nav {
-    gap: 1rem;
+  .site-nav a {
+    padding: 0.35rem 0.5rem;
+    font-size: 0.83rem;
   }
 
   pre {
-    font-size: 1rem;
+    font-size: 0.82rem;
     padding: 1rem;
+    border-radius: var(--radius-sm);
   }
 }
 
-/* === Video === */
+/* ─── Video embeds ───────────────────────────────────────────── */
 .video-section {
   margin: 2rem 0;
   padding: 1.5rem;
   background: var(--bg-secondary);
   border: 1px solid var(--border);
-  border-radius: 8px;
+  border-radius: var(--radius-lg);
 }
 
 .video-section h2 {
-  font-size: 1.2rem;
+  font-size: 1.1rem;
+  font-weight: 700;
   margin-top: 0;
   margin-bottom: 1rem;
   border-bottom: none;
@@ -413,7 +585,7 @@ pre code {
 .video-wrapper {
   position: relative;
   width: 100%;
-  border-radius: 6px;
+  border-radius: var(--radius-md);
   overflow: hidden;
   border: 1px solid var(--border);
 }
@@ -422,18 +594,17 @@ pre code {
 .video-wrapper iframe {
   width: 100%;
   display: block;
-  border-radius: 6px;
-  aspect-ratio: 16/9;
+  border-radius: var(--radius-md);
+  aspect-ratio: 16 / 9;
 }
 
 .video-wrapper video {
   background: #000;
 }
 
-/* YouTube embed responsive */
 .youtube-embed {
   position: relative;
-  padding-bottom: 56.25%; /* 16:9 */
+  padding-bottom: 56.25%;
   height: 0;
   overflow: hidden;
 }
@@ -447,21 +618,22 @@ pre code {
   border: 0;
 }
 
-/* Native video embed (blog posts) */
 .video-embed {
-  margin: 1.5rem 0;
-  border-radius: 8px;
+  margin: 1.75rem 0;
+  border-radius: var(--radius-md);
   overflow: hidden;
   border: 1px solid var(--border);
+  box-shadow: var(--shadow-sm);
 }
+
 .video-embed video {
   width: 100%;
   display: block;
   background: #000;
-  aspect-ratio: 16/9;
+  aspect-ratio: 16 / 9;
 }
 
-/* === Utilities === */
+/* ─── Utilities ──────────────────────────────────────────────── */
 .sr-only {
   position: absolute;
   width: 1px;

← a7e17c3 chore(security): 6 vulns -> 0, remove unused 'serve' dep, v1  ·  back to Goodquestion Ai  ·  security: scrub personal + sensitive info from public site, a277da0 →