This website - the living workshop

v0.8 adds a Keystatic editor over the existing MDX files, global search with a command palette, a trust index, and a round of SEO plumbing. Still a folder of files underneath.

Active · v0.8View liveRepo

Why One home for research notes, projects, and writing. Lightweight, fast, easy to grow. This site is a living workshop where I build in the open and keep improving the craft.

Stack Overview

  • Core: Next.js 15 (App Router), MDX from the filesystem, Tailwind, shadcn/ui.
  • Editing: Keystatic in local mode, mounted at /keystatic in development only. It edits the same MDX files Git already tracks.
  • Content tooling: remark-gfm, rehype-slug, autolinked headings.
  • Theming: CSS @property registration for animatable accent color transitions, route-driven data-theme attributes, reactive canvas background.
  • Generative art: Deterministic canvas thumbnails (honeycomb, flower-of-life, isometric blocks) seeded by content slug.
  • PDF: pdf-lib (portable, no headless browser), with WinAnsi-safe text.
  • Contact sharing: server-rendered QR (SVG) with HMAC tokens, enforced by Edge middleware.
  • State & limits: serverless key-value store over HTTP for single-use tokens and gentle rate limits.
  • Hosting: Vercel.

What changed in v0.8

An editor, not a CMS

I spent a year planning a custom CMS with auth, passkeys, and a database, then added Keystatic in an evening instead. It mounts inside the app, reads the existing MDX files, and writes them back when I save. Every collection (blog, research, projects, pages, now) has its frontmatter spelled out as a schema so the editor shows the right controls, and a couple of legacy keys are marked ignored so they survive a save untouched. ProjectHeader and CTA show up as proper blocks in the editor. It only exists in development; production has no admin surface at all, and the only way content changes is still a Git commit.

A real /search page with styled results, search in the command palette, and a search field in the desktop navbar. The site's JSON-LD advertises a SearchAction so search engines know the page exists.

Trust index and canonical URLs

/trust is now an index with cards for Contact, Privacy, and Terms, with breadcrumbs instead of a duplicated nav. The www host redirects to the bare domain and the trust pages carry proper canonicals, which cleaned up a duplicate-content warning.

SEO plumbing

A dynamic sitemap, richer index-page metadata, CollectionPage JSON-LD on the listing pages, and keyword signals for the name people actually search for.

Worktree fixes

Small things that only matter if you work out of Git worktrees on Windows: the Turbopack root is pinned so a nested checkout doesn't resolve modules against the parent, and pnpm's dependency folder names are capped so paths stay under the 260-character limit. The content validator script runs again, too.


What changed in v0.7

Animated card thumbnails

Homepage artifact cards now feature animated canvas thumbnails that drift gently in their theme color. Each content type gets its own palette (amber for blog, cyan for projects, indigo for research), and the animation pauses when off-screen via IntersectionObserver to save resources.

Mission canvas

The mission card gained an animated canvas background with a soft gradient overlay. The quote and body text stagger in on scroll. The body text stretches wider across the card for better readability.

CV polish: brand logos and career timeline

Every experience card now displays the company's actual brand logo in a badge (BD, Warner Bros. Discovery, Tricentis, Alfresco, Vertafore, Encompass). Logos are sourced as SVG/PNG from official brand resources for crisp rendering at any size.

The career timeline is draggable with a grab cursor, uses exact month offsets so adjacent tenures (like Encompass to Vertafore) sit flush without overlap, and shows brand badge icons instead of text labels. Hovering any bar surfaces a tooltip with company name, role, and date range. Tooltips render via React portal so they escape the scrollable container and display cleanly over page content.

Content grounding

Replaced abstract shipping and launch metaphors across the site with grounded, direct language. The tone reads more like a person talking, less like a press release.

180 Life Church project

Added the 180 Life Church digital renovation as a portfolio project.


What changed in v0.6

Route-aware accent theming

The entire page environment now shifts color based on what you are looking at. Blog pages go warm amber. Projects go cool cyan. Research goes indigo. The transition between views is a soft 1.6-second crossfade, not a hard cut.

Under the hood: CSS @property declarations register --accent-h, --accent-s, --accent-l as animatable numbers. A tiny client component (ThemeSync) sets a data-theme attribute on <html> using useLayoutEffect so the swap happens before paint. The background canvas animation reads the already-transitioning CSS value every few frames rather than running its own interpolation. One smoothing layer, zero flicker.

Generative algorithmic thumbnails

Every card now gets a unique, deterministic thumbnail generated on canvas. Blog posts get a honeycomb pattern. Projects get a flower-of-life geometry. Research gets isometric data blocks. Each one is seeded from the content slug so the same post always produces the same visual. No stock photos, no placeholder images.

Wider cards, sharper corners

Cards are wider on large viewports (center column pushed to 64rem, container to 1440px). Border radius dialed down from rounded-lg to rounded-md across FancyCard, mission card, and list view items. Titles get more room. Summaries show up to three lines.

Per-type color identity everywhere

Sidebar category grids, search/view toggles, page headers, and card badges all respond to the current accent color. Previously these were hardcoded to amber regardless of content type. Now they follow the theme automatically via Tailwind's accent token backed by CSS custom properties with alpha-value support.

Content tone pass

Removed m-dashes across all project content. Rewrote in a more direct, conversational register. Less documentation voice, more “here is what this does and why.”


What changed in v0.5

Private contact card (QR-first, NFC later)

I tightened the sharing flow so I control who sees my contact details and when.

  • /card is private. /card/qr is challenge-gated (simple PIN, versioned cookie).
  • Every QR encodes a short-lived, signed token. The vCard endpoint requires the token (cookies are ignored).
  • Tokens are single-use and gently rate-limited to discourage scraping. A second scan returns 410 Gone.
  • The code auto-rotates (refreshes) before expiry.
  • The QR now points directly to the vCard download, and the vCard includes my avatar inline.
  • If I unlock from /card/qr, the flow sends me back to that page after the PIN using a safe next parameter.
  • I also fixed form submissions under my Content Security Policy and optionally canonicalize the host to avoid apex/www mismatches.

Note on ops: In public posts I keep security at a high level. I do not publish environment details. Those live in my private runbook.

Proposal PDF (executive-ready)

The proposal export moved to a portable generator so it works the same everywhere and looks good.

  • Dedicated Mission and Executive Summary page with my statement.
  • Experience renders time, scope, metrics, and highlights with dividers and breathing room.
  • Skills follow experience in clean two columns.
  • Table of Contents with dotted leaders and page numbers.
  • Supports a circular avatar (prefers a transparent PNG, falls back to JPG).
  • Consistent 1-inch margins, subtle rules, and tidy type scale. Text is sanitized so there are no encoding surprises.

CV and UI polish

  • The Download actions show a clear “Preparing...” busy state.
  • The QR page scales nicely on mobile and stays visually centered.

Routing and content fixes

  • Middleware is Edge-safe (WebCrypto) and typed.
  • MDX page rendering is stabilized (server serialization + component map).
  • I removed brittle build flags and simplified the deployment config.

Highlights (implementation notes)

Endpoints and files

  • Middleware: gated /card + /card/qr, token-only check on the vCard endpoint, optional host canonicalization.
  • Tokens: compact HMAC SHA-256 payloads (base64url) with short TTL and a nonce for one-time use.
  • KV store: small helpers to “burn” tokens on first use and throttle downloads per IP.
  • Contact: PIN to versioned cookie, safe next redirect, avatar embedded in the vCard.
  • QR: server-generated SVG with an auto-refresh interval.
  • Proposal PDF: mission, ToC, experience, and skills with page numbers and consistent spacing.
  • CV header: busy state during downloads.

Security posture (high-level)

  • Private contact area with a simple unlock, single-use links, and short TTL.
  • Rate-limits on sensitive endpoints.
  • Instant revoke by rotating a secret or bumping a version value in config.

Before / After (at a glance)

  • Before: QR could be reached without a fresh challenge. PDF export was brittle. The cover layout felt cramped. Experience read like a wall of text.
  • After: QR is private, rotating, and single-use. The proposal PDF is clean and portable with a ToC. The cover and contact row have breathing room. Experience is broken up with dividers and spacing.

Known issues / Next up

  • Add NFC / Apple Wallet pass that opens the same tokenized flow.
  • Optional exec one-pager PDF theme (summary-first).
  • Brand accents for PDFs (color, link styling), clickable email/URL.
  • Consider moving the KV usage to a native provider in my host for simpler ops.
  • Keystatic GitHub storage mode, so I can edit from a browser away from this machine.
  • Cover images as real uploads in the editor instead of hand-typed paths.

Changelog

  • v0.8 (2026-10-14): Keystatic editor over the existing MDX files (dev-only, no database), global search page with command palette and navbar search, trust index with breadcrumbs and canonicals, dynamic sitemap and CollectionPage JSON-LD, worktree build fixes, earliest posts rewritten in a more honest voice.
  • v0.7 (2026-03-25): Animated card thumbnails with theme-colored canvas, mission canvas background, CV brand logo badges (BD, WBD, Tricentis, Alfresco, Vertafore, Encompass), draggable career timeline with icon badges and portal tooltips, content grounding pass, 180 Life Church project.
  • v0.6 (2026-03-06): Route-aware accent theming with CSS @property crossfade (amber/cyan/indigo), generative algorithmic thumbnails (honeycomb/flower-of-life/isometric), wider card layouts with sharper corners, per-type color identity across all navigation and sidebar components, content tone humanization.
  • v0.5 (2025-09-10): Private contact flow (auth-gated QR, single-use tokens, rate-limits), proposal PDF with ToC/mission/clean typography, circular avatar support, safer redirects and CSP, CV download busy state, QR layout fixes.
  • v0.4 (2025-09-05): CV overhaul (Exec Summary, scope/metrics, mobile collapse), robust PDF export with live canvas capture, scrim refinement, sidebar spacing, export/import fixes.
  • v0.3 (2025-09-03): Centered reading layout with column-blur scrim, descriptive sidebar grids and mobile Filters sheet, Blog/Research/Projects overhauled, Next 15 fixes, stricter taxonomy.
  • v0.2 (2025-08-28): Trust pages, mobile command palette, JSON-LD, lazy-loading, a11y touches.