Development Progress

This file tracks major changes and milestones in the project.


Date: 2026-02-23

Follow-up polish after the experiment frame refactor. Added a light/dark theme system for design experiments with CSS custom properties and a toggle. Extracted a shared SiteFooter component and unified content width across pages using --content-max. Sticky notes got a light mode variant and footer-to-bottom layout fix. Font pairings switched to edge-to-edge layout with zero frame padding. Day-at-a-glance widened its layout and hardcoded demo time for consistent screenshots. Modular grid now shows its cyan grid overlay by default.

Key files:

  • app/components/SiteFooter/ -- extracted shared footer
  • app/design-experiments/(experiments)/layout.tsx -- theme toggle integration
  • app/globals.css -- --content-max variable, theme custom properties
  • app/design-experiments/(experiments)/sticky-notes/ -- light mode styles
  • app/design-experiments/(experiments)/font-pairings/ -- edge-to-edge layout
  • app/design-experiments/(experiments)/day-at-a-glance/ -- wider layout, demo time
  • app/design-experiments/(experiments)/modular-grid/ -- always-on grid overlay

Design Frame & Experiment Cleanup

Date: 2026-02-22

Unified the experiment viewing experience with a shared layout frame and cleaned up accumulated debt. Experiments now render inside a (experiments) route group with a consistent header (back link, title, tags) and footer (date, site nav). Global CSS variables (--site-bg, --site-text) replace hardcoded colors across the site. Scoped experiment stylesheets to prevent cross-page style leaking. Removed deprecated experiments (geist-pixel, spec-sheet). Fixed the font-pairings blank page bug -- fonts are now injected into <head> via useEffect with document.fonts.ready gating and a loading screen.

Key files:

  • app/design-experiments/(experiments)/layout.tsx -- shared experiment frame
  • app/design-experiments/(experiments)/font-pairings/page.tsx -- programmatic font loading with ready gate
  • app/globals.css -- site-wide CSS variables

Date: 2026-02-22

Added a curated links page at /recommended for sharing YouTube videos, GitHub repos, and web tools with personal commentary. Each link is a markdown file in app/(blog)/recommended/items/ with frontmatter (url, date, optional title) and a one-line comment.

Thumbnails resolve automatically at build time: YouTube titles and thumbnails via oEmbed API, GitHub repo names and OG images via GitHub API, and web link titles from frontmatter. Web links get manual screenshots via agent-browser. The loader (loadRecommended.ts) handles all source detection, thumbnail fetching, and caching to public/screenshots/recommended/.

Also created the /recommend skill for quick link capture from the CLI -- pass a URL and comment, and the skill creates the markdown file, takes screenshots for web links, and runs a build to verify.

Key files:

  • app/(blog)/recommended/page.tsx -- server component, card grid layout
  • app/(blog)/recommended/loadRecommended.ts -- markdown loading, oEmbed/OG image fetching, source detection
  • app/(blog)/recommended/types.ts -- RecommendedItem, SourceType types
  • app/(blog)/recommended/items/ -- individual link markdown files
  • app/(blog)/blog.module.css -- card styles for recommended items
  • .claude/skills/recommend/SKILL.md -- /recommend skill definition

Blog Post Light/Dark Mode Toggle

Date: 2026-02-22

Added a light/dark mode toggle to blog post pages. A sun/moon icon sits opposite the back link in the top row -- click to swap between dark (default) and a warm light reading mode. The toggle targets the blogLayout wrapper so the entire viewport background changes, not just the content column. On unmount (navigating away), the attribute is cleaned up so the blog index always stays dark.

Uses a -webkit-text-stroke: 0.25px trick in light mode to optically thicken body text for better readability on the lighter background without changing font-weight (which would cause line reflow). Preference persists via localStorage across post pages but resets when leaving the blog post context.

Key files:

  • app/(blog)/_components/ThemeToggle.tsx -- client component, localStorage persistence, cleanup on unmount
  • app/(blog)/blog.module.css -- .themeToggle styles, .blogLayout[data-theme="light"] variable overrides
  • app/(blog)/blog/[slug]/page.tsx -- ThemeToggle placed in backRow for both overlay and standard layouts

Image-Tinted Blog Cards

Date: 2026-02-22

Blog cards on the index page now extract the dominant color from each post's hero image and derive a per-card tinted palette. The effect is subtle but distinctive -- each card feels like it belongs to its image.

The technique: draw the hero image onto a 32x32 offscreen canvas (bilinear interpolation acts as a free blur), run k-means clustering (6 clusters, 5 iterations) on the filtered pixels, and pick the most dominant chromatic color. That single hue drives the entire card palette through HSL transforms at different saturation and lightness levels.

Color mapping (all same hue h from dominant):

  • Title: hsl(h, clamp(s, 0.45, 0.75), 0.75) -- bright, saturated
  • Subtitle: hsl(h, clamp(s, 0.35, 0.55), 0.85) -- lighter version of title
  • Date: matches subtitle
  • Card background: hsl(h, 0.24, 0.10) -- dark tint, visible against pure black page

Pre-filtering strips near-black and desaturated pixels before clustering so shadows don't dominate. Falls back to default palette when no image is present or extraction fails (CORS, canvas error).

Also removed the overlay blog layout option (renamed .overlay.md to .md), updated the design page headline, and added line-clamping to card titles/subtitles for consistent grid alignment.

Key files:

  • app/(blog)/_components/ImageTintProvider.tsx -- color extraction, k-means clustering, HSL palette derivation
  • app/(blog)/_components/BlogCard.tsx -- wraps each card in ImageTintProvider
  • app/(blog)/blog.module.css -- CSS custom property fallbacks (--card-bg, --subtitle-color)

Bitmap-to-Vector Skill Refinement

Date: 2026-02-21

Tested and confirmed the /bitmap-to-vector skill across three image types: dark subject on light background (line art), light subject on dark background (silhouette), and dark outlines on light background (illustrated icon). Identified and fixed core issues in the tracing pipeline.

Key changes:

  • Fixed bounding rectangle issue in trace.py -- potrace wraps traced regions in a full-viewBox rectangle that inverts the fill with evenodd. Script now auto-detects and strips this, producing clean icon-ready SVGs
  • Preview rendering changed from ambiguous white-on-black to unambiguous black-on-white
  • Added polarity reporting to JSON output so the agent knows what the script decided
  • Updated skill instructions with guidance on currentColor SVG usage (inline SVG or CSS mask, not <img>)
  • Organized skills documentation in README (grouped by pipeline, content, utilities) and added 4 previously undocumented skills
  • Updated docs/features/skills.md to match current skill inventory (12 skills total)

Key files:

  • .claude/skills/bitmap-to-vector/scripts/trace.py -- bounding rect removal, preview fix, polarity reporting
  • .claude/skills/bitmap-to-vector/skill.md -- updated instructions with usage guidance
  • README.md -- reorganized skills section
  • docs/features/skills.md -- full rewrite to match reality

SEO Foundation

Date: 2026-02-21

Added baseline SEO infrastructure to improve search visibility and crawlability.

Key changes:

  • Dynamic sitemap.ts covering all routes (blog posts, experiments, docs) with lastModified dates
  • robots.ts allowing all crawlers with sitemap reference
  • Root layout metadata upgraded: title template so every page includes the site name, metadataBase, Open Graph and Twitter card defaults, authors field
  • Person and WebSite JSON-LD structured data on the root layout
  • Blog posts enhanced with OG article type, publishedTime, and Article JSON-LD schema
  • Docs pages now export generateMetadata from document content
  • Design experiments section gets metadata via a layout file
  • Experiments data extracted to lib/experiments/data.ts (shared module, imported by gallery and sitemap)
  • Generated favicon via icon.tsx

Key files:

  • app/sitemap.ts, app/robots.ts, app/icon.tsx -- new crawlability and identity files
  • app/layout.tsx -- metadata upgrade and JSON-LD
  • app/design-experiments/layout.tsx -- section metadata
  • lib/experiments/data.ts -- shared experiments data
  • app/(blog)/blog/[slug]/page.tsx -- enhanced blog metadata and Article JSON-LD
  • app/(docs)/docs/[...slug]/page.tsx -- added generateMetadata

Sticky Notes: Portal-based Modal for Mobile

Date: 2026-02-21

The sticky note expanded overlay was broken on mobile because the blog page's .stickyNotesWrapper uses transform: scale(0.65) at small viewports. CSS transform on any ancestor creates a new containing block for position: fixed descendants, so the modal backdrop was sizing and positioning relative to the scaled wrapper instead of the viewport -- appearing clipped and off-center.

Fixed by rendering the modal via createPortal(jsx, document.body), which escapes all ancestor CSS containment (transform, z-index stacking contexts, overflow). The font variable (--font-marker) is forwarded to the portal by reading the computed style from the in-tree wrapper ref and inlining it on the backdrop element.

Key files:

  • app/design-experiments/sticky-notes/components/StickyNoteStack.tsx -- createPortal to document.body, wrapperRef for font variable forwarding

Initial Mobile Readiness Pass

Date: 2026-02-21

First pass at making design experiments usable on mobile. Focused on the experiments that were most broken at small screen sizes and added a back link to the shared SwissFrame component.

Key changes:

  • Retro Tech: Knobs reflow to 2x2 grid, faders span full width, toggles go horizontal, buttons become 2x2 grid at 520px. Track name display locked to single line with overflow ellipsis to prevent layout shift during scramble animation
  • CrossFit Bento: Three-column fixed grid collapses to single fluid column
  • Contact Sheet: Selection sidebar becomes a fixed 50vh bottom drawer on mobile, sliding up from below with independently scrollable file list and smaller thumbnails
  • SwissFrame (shared): Added inline back link above the top rule using CurtainLink with reverse curtain transition, defaults to /design-experiments

Key files:

  • app/design-experiments/retro-tech/components/RetroTechPanel.module.css -- mobile media query with .knobsRow and .mixRow targets
  • app/design-experiments/retro-tech/components/RetroTechPanel.tsx -- added explicit class names for mobile CSS targeting
  • app/design-experiments/crossfit-bento/page.module.css -- single column grid at 520px
  • app/design-experiments/contact-sheet/components/ContactSheet.module.css -- bottom drawer sidebar
  • app/design-experiments/components/SwissFrame/SwissFrame.tsx -- back link with backHref prop
  • app/design-experiments/components/SwissFrame/SwissFrame.module.css -- back link styles

Curtain Reveal Page Transitions

Date: 2026-02-20

Implemented theatrical wipe-style page transitions using the View Transitions API. The curtain effect creates a cinematic reveal between major sections -- forward navigation wipes up to unveil the new page, back navigation wipes down. Applied to all primary navigation paths (homepage to blog/design/docs, back buttons, docs sidebar). Also simplified the overall navigation architecture by removing redundant global UI (shared sidebar, hamburger menu, floating back button) in favor of focused in-page navigation. Gracefully degrades to standard navigation on unsupported browsers (Firefox, older Chrome/Safari).

Key changes:

  • Created CurtainLink component with clip-path mask animations (0.75s wipe)
  • View Transitions API for smooth, GPU-accelerated page transitions
  • Directional animation: forward wipes up, back wipes down
  • Applied to all section index pages (blog, design experiments, docs)
  • Removed redundant global navigation components for cleaner UI
  • Also added entrance animations to blog cards using proven pattern from design experiments (fade-in with upward movement, staggered timing, sessionStorage check)

Key files:

  • app/components/CurtainLink.tsx -- curtain transition component
  • app/globals.css -- View Transitions API styles with clip-path animations
  • app/layout.tsx -- removed global sidebar
  • app/page.tsx -- homepage navigation with curtain links
  • app/(blog)/blog/page.tsx, app/design-experiments/page.tsx, app/(docs)/_components/DocsSidebar.tsx -- curtain transitions and back links
  • app/(blog)/_components/BlogIndexContent.tsx -- blog entrance animations

Retro Tech: skeuomorphic audio interface

Date: 2026-02-20

Built a skeuomorphic audio control panel -- an RC-1 hardware interface rendered entirely in the browser. The experiment was driven through voice-dictated conversation with an AI agent: no wireframes, no mockups, just iterative refinement of shadows, knob physics, and display readouts until it felt like real hardware.

Key changes:

  • Interactive controls: four rotary knobs (gain, freq, resonance, mix) with horizontal drag via pointer capture, four vertical faders (vol, low, mid, high), and three toggles (filter, bypass, mute)
  • 32-bar EQ visualizer driven by fader values through overlapping bell curves, with volume as a base level
  • Track name display with Terminator-style text scramble effect (borrowed from the terminator experiment), auto-cycles every 12s with click-to-advance
  • Eased numeric readouts for frequency (5-digit zero-padded Hz) and gain (3-digit zero-padded percent) that interpolate smoothly on knob drag
  • LED cluster, recording state with REC badge, chassis screws, serial number -- details that sell the physicality
  • Extracted SwissFrame to shared component (app/design-experiments/components/SwissFrame/) with dark/light variants, reused by crossfit-bento
  • Extracted EditorialBrief to shared component (app/design-experiments/components/EditorialBrief/) for experiment write-ups with headline, lede, images, and body content
  • Custom hooks useKnob and useFader encapsulate all pointer interaction and state

Key files:

  • app/design-experiments/retro-tech/ -- page, components, hooks, types, barrel export
  • app/design-experiments/retro-tech/components/RetroTechPanel.tsx -- main panel with all controls and display logic
  • app/design-experiments/retro-tech/hooks/useControls.ts -- useKnob and useFader hooks
  • app/design-experiments/components/SwissFrame/ -- shared frame component
  • app/design-experiments/components/EditorialBrief/ -- shared editorial write-up component

Sticky Notes: portable design experiment with pinning

Date: 2026-02-18

Consolidated the sticky note stack into a self-contained design experiment at app/design-experiments/sticky-notes/ with its own showcase page. The component was previously scattered across lib/notes/, app/(blog)/_components/, and notes/. Now it lives where you'd expect to find it -- as a design experiment that other pages can import.

Key changes:

  • getAllNotes(notesDir) now accepts a path parameter -- the consumer decides where content lives, not the component. The experiment showcase uses demo notes in data/, the blog passes app/(blog)/notes/
  • Fixed text flash bug on swipe cycling: outgoing card now renders from a frozen ref snapshot so content doesn't change mid-animation. Entrance animation (scaleIn) is skipped after cycling to prevent the underneath card from flashing through
  • Swipe animation changed from lateral to vertical drop (200px down with fade)
  • Modal sized to match stack card aspect ratio (300x233, same ~1.3:1 as 180x140)
  • Stack now reflects the pinned note: whichever note you're viewing when you close becomes the top card, with a 250ms delayed update so the stack changes after the overlay dismisses
  • Stack teaser shows full note text at 11px proportional sizing instead of 3-line clamp
  • Updated ship-experiment skill to include homepage recentExperiments update step

Key files:

  • app/design-experiments/sticky-notes/ -- component, loader, types, showcase page, demo data
  • app/design-experiments/sticky-notes/components/StickyNoteStack.tsx -- client component
  • app/(blog)/notes/ -- blog's note content (moved from project root)
  • app/(blog)/blog/page.tsx -- imports from experiment, passes notes path
  • .claude/skills/ship-experiment/skill.md -- added homepage update step

Contact Sheet: multi-select with sidebar

Date: 2026-02-17

Added multi-select functionality to the Contact Sheet experiment. Click images to select them, a sidebar slides in showing thumbnails and filenames of selected images. Copy the full list to clipboard for pasting into LLM conversations. Removed the lightbox and per-image copy actions to focus the tool on its core use case: visually picking images and building filename lists.

Key files: app/design-experiments/contact-sheet/page.tsx, app/design-experiments/contact-sheet/styles.css


Day at a Glance: time-aware enhancement

Date: 2026-02-12 Issue: #9 - Day at a Glance: time-aware enhancement with dynamic now-line

Rebuilt the Day at a Glance experiment with a full 9am-5pm workday schedule, dynamic now-line that tracks real time, and a partial color fill effect on the current hour's event bar. Added accessibility for checkboxes and fixed hydration mismatch.

Key files: app/day-at-a-glance/page.tsx, app/day-at-a-glance/styles.css


Write-post skill and TypeScript cleanup

Date: 2026-02-12

Added /write-post skill for blog authoring and eliminated all any types from the codebase.

What changed:

  • New skill .claude/skills/write-post/SKILL.md for conversational blog post creation with auto-calculated reading time, slug derivation, and image handling
  • Removed @ts-nocheck from NetworkCanvas.tsx, added typed interfaces (CanvasNode, Organism, BlastAnimation)
  • Typed TextScramble class in terminator experiment
  • Replaced all any props in color-spec components (Cards.tsx, ColorSidebar.tsx, BrandColors.tsx, TypeInfo.tsx, ActivityWidget.tsx, AnalyticsWidget.tsx)
  • Typed blend recipe state and color scale utilities
  • Updated sanity-check skill to remove emojis
  • New feature doc: docs/features/skills.md

Key files: .claude/skills/write-post/SKILL.md, app/components/NetworkCanvas.tsx, app/color-spec/components/ColorSidebar.tsx


Docs Viewer

Date: 2026-02-09

Added a /docs route that renders markdown files from the docs/ directory with sidebar navigation, syntax highlighting, table of contents with scroll spy, and dark mode styling.

What changed:

  • New route group app/(docs)/ with layout, catch-all slug page, and index redirect
  • Utility library lib/docs/ for scanning the docs directory, parsing filenames, extracting headings
  • Client components for sidebar (collapsible categories, mobile drawer), TOC (IntersectionObserver scroll spy), and code blocks (react-syntax-highlighter with VS Code Dark+ theme)
  • Server-rendered markdown via next-mdx-remote/rsc with remark-gfm and rehype-slug
  • Dark mode CSS Modules with Bitter serif font on headings to match homepage brand
  • Homepage redesigned from single text link to two card-style nav links (Design Experiments + Docs)
  • Ported 3 reference docs from the notes vault: AI Dev Stack 2026, BYOK Pattern, Stack Overview
  • Files prefixed with _ are excluded from the sidebar (used for templates)
  • Subdirectories in docs/ become collapsible categories; numeric prefixes control sort order

Key files:

  • app/(docs)/layout.tsx - Docs layout with sidebar
  • app/(docs)/docs/[...slug]/page.tsx - Doc renderer with TOC
  • app/(docs)/_components/ - DocsSidebar, TableOfContents, CodeBlock, DocsContent
  • lib/docs/loadDocs.ts - Core logic for scanning and loading docs
  • app/(docs)/docs.module.css - All docs styling
  • app/page.tsx - Homepage with card nav links
  • docs/stack/ - Ported reference documentation

Dependencies added:

  • gray-matter, remark-gfm, rehype-slug, react-syntax-highlighter, next-mdx-remote, lucide-react

TypeScript Migration & Component Architecture

Date: 2026-02-08

Completed comprehensive TypeScript migration and established component extraction patterns for the design experiments sandbox.

What changed:

  • Converted all 7 experiments from .jsx to .tsx with full type safety
  • Added TypeScript type definitions (@types/react-dom, @types/chroma-js)
  • Refactored color-spec from monolithic 2000+ line file into modular component architecture
  • Established clear guidelines in CLAUDE.md for when to extract vs keep single-file

Key files:

  • All app/*/page.tsx - TypeScript conversion
  • app/color-spec/components/* - Extracted reusable components
  • app/color-spec/hooks/useColorScale.ts - Color generation utilities
  • app/color-spec/data/fontPairings.ts - Static data extraction
  • CLAUDE.md - Added component architecture guidance

Benefits:

  • Full type safety across all experiments
  • Easier to port components to other projects
  • Clear separation of concerns in complex experiments
  • Better AI agent collaboration on component extraction