Development Progress
This file tracks major changes and milestones in the project.
Theme System, SiteFooter, Layout Refinements
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 footerapp/design-experiments/(experiments)/layout.tsx-- theme toggle integrationapp/globals.css----content-maxvariable, theme custom propertiesapp/design-experiments/(experiments)/sticky-notes/-- light mode stylesapp/design-experiments/(experiments)/font-pairings/-- edge-to-edge layoutapp/design-experiments/(experiments)/day-at-a-glance/-- wider layout, demo timeapp/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 frameapp/design-experiments/(experiments)/font-pairings/page.tsx-- programmatic font loading with ready gateapp/globals.css-- site-wide CSS variables
Recommended Links Page
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 layoutapp/(blog)/recommended/loadRecommended.ts-- markdown loading, oEmbed/OG image fetching, source detectionapp/(blog)/recommended/types.ts-- RecommendedItem, SourceType typesapp/(blog)/recommended/items/-- individual link markdown filesapp/(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 unmountapp/(blog)/blog.module.css--.themeTogglestyles,.blogLayout[data-theme="light"]variable overridesapp/(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 derivationapp/(blog)/_components/BlogCard.tsx-- wraps each card in ImageTintProviderapp/(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 withevenodd. 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
currentColorSVG 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.mdto 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 guidanceREADME.md-- reorganized skills sectiondocs/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.tscovering all routes (blog posts, experiments, docs) withlastModifieddates robots.tsallowing 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,authorsfield - 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
generateMetadatafrom 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 filesapp/layout.tsx-- metadata upgrade and JSON-LDapp/design-experiments/layout.tsx-- section metadatalib/experiments/data.ts-- shared experiments dataapp/(blog)/blog/[slug]/page.tsx-- enhanced blog metadata and Article JSON-LDapp/(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.knobsRowand.mixRowtargetsapp/design-experiments/retro-tech/components/RetroTechPanel.tsx-- added explicit class names for mobile CSS targetingapp/design-experiments/crossfit-bento/page.module.css-- single column grid at 520pxapp/design-experiments/contact-sheet/components/ContactSheet.module.css-- bottom drawer sidebarapp/design-experiments/components/SwissFrame/SwissFrame.tsx-- back link withbackHrefpropapp/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
CurtainLinkcomponent 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 componentapp/globals.css-- View Transitions API styles with clip-path animationsapp/layout.tsx-- removed global sidebarapp/page.tsx-- homepage navigation with curtain linksapp/(blog)/blog/page.tsx,app/design-experiments/page.tsx,app/(docs)/_components/DocsSidebar.tsx-- curtain transitions and back linksapp/(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
SwissFrameto shared component (app/design-experiments/components/SwissFrame/) with dark/light variants, reused by crossfit-bento - Extracted
EditorialBriefto shared component (app/design-experiments/components/EditorialBrief/) for experiment write-ups with headline, lede, images, and body content - Custom hooks
useKnobanduseFaderencapsulate all pointer interaction and state
Key files:
app/design-experiments/retro-tech/-- page, components, hooks, types, barrel exportapp/design-experiments/retro-tech/components/RetroTechPanel.tsx-- main panel with all controls and display logicapp/design-experiments/retro-tech/hooks/useControls.ts--useKnobanduseFaderhooksapp/design-experiments/components/SwissFrame/-- shared frame componentapp/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 indata/, the blog passesapp/(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
recentExperimentsupdate step
Key files:
app/design-experiments/sticky-notes/-- component, loader, types, showcase page, demo dataapp/design-experiments/sticky-notes/components/StickyNoteStack.tsx-- client componentapp/(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.mdfor conversational blog post creation with auto-calculated reading time, slug derivation, and image handling - Removed
@ts-nocheckfromNetworkCanvas.tsx, added typed interfaces (CanvasNode,Organism,BlastAnimation) - Typed
TextScrambleclass interminatorexperiment - Replaced all
anyprops 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 sidebarapp/(docs)/docs/[...slug]/page.tsx- Doc renderer with TOCapp/(docs)/_components/- DocsSidebar, TableOfContents, CodeBlock, DocsContentlib/docs/loadDocs.ts- Core logic for scanning and loading docsapp/(docs)/docs.module.css- All docs stylingapp/page.tsx- Homepage with card nav linksdocs/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
.jsxto.tsxwith full type safety - Added TypeScript type definitions (
@types/react-dom,@types/chroma-js) - Refactored
color-specfrom 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 componentsapp/color-spec/hooks/useColorScale.ts- Color generation utilitiesapp/color-spec/data/fontPairings.ts- Static data extractionCLAUDE.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