Development Progress
This file tracks major changes and milestones in the project.
Chatroom — Two AI Agents and a Visitor on a Cloudflare Durable Object
Date: 2026-04-28
A new design experiment that pulls together threads from monono (cost-protected LLM personas) and leaderboard (visual language) into a realtime, server-driven chat. Two AI characters — Maya Chen and Jordan Park — open a conversation on a curated topic, then pause for the visitor to join as the third participant. The defining design choice is pacing: the room waits whenever it's the visitor's turn rather than racing to a turn cap. After the openers there's a single auto-nudge ("hey [name], what do you think?"); if the visitor still doesn't engage, the room goes idle and never auto-prompts again. Two user-controlled buttons replace nagging: "change topic" pivots the conversation mid-flight (with a free-form prompt and a 3-change-per-room cap), and "ask me something" lets the visitor summon a fresh question from an agent. Cost ceiling for a non-engaging visitor is 3 LLM calls; an engaged one trades replies as long as they want.
This is the most architecturally ambitious experiment in the sandbox to date — two deploy targets (Vercel for the session route + Cloudflare Workers for the chat actor), a Hibernatable Durable Object that stays alive on a $-cheap idle WebSocket, SQLite-backed conversation state that survives hibernation, and a phase state machine in the DO's alarm() heartbeat (opening → awaiting_user → nudging → idle, with responding interleaved when the user types). Cost is gated in two places: a per-IP session counter and a global monthly $ cap (both via Upstash, namespace chatroom), and signed HMAC tickets gate every WS upgrade — the worker can't be hit directly to bypass the entry checks. The ticket signs the dev/prod mode into its payload, so a NODE_ENV === "development" session route mints dev tickets that lift caps locally and trigger an inline "production cutoff" divider when the conversation crosses the prod limit (80 turns). Worker secrets live in Cloudflare; the same TICKET_SECRET mirrors to Vercel.
Visual language ports cleanly from leaderboard: Sora typography, the radial-gradient page background, the 56/44/1fr/auto row grid, SPRING and SPRINGY presets for motion/react entrances, and the gradient-with-inset-border avatar treatment. Maya and Jordan reuse leaderboard avatars 01 and 02 by reference (no duplication); the visitor picks from 03–08 plus an initials fallback via a pencil-icon → modal-grid edit path, with their identity persisted to localStorage. System messages (topic-change pivots, the dev cutoff divider) render as italic centered dividers — visually distinct from agent and user messages. The persona system prompts are intentionally loose ("AI enthusiast, woman/man in their 30s") rather than the original optimist/skeptic stance steering — voice emerges from the model interpreting the topic, not from canned framing.
Lots of fine-tuning landed during the build: dropping the visible "topic opener" system message in favor of letting whichever agent goes first deliver the opener in their voice; lifting the 500-char input cap to 8000 server-side and removing the visible char counter; raising production caps to MAX_TURNS=80 / GLOBAL_SOFT_CAP_USD=$8 / SESSION_LIMIT=6 with worst-case math at ~$0.05 per maxed session; and the "Ask Me Something" button as the user-driven replacement for the auto-pick-back-up cycle that was making the room feel naggy.
Leaderboard — Springy Avatars and Profile Modals
Date: 2026-04-27
A new playful leaderboard experiment with eight runners, illustrated avatars, and a profile modal — a showcase for Motion's layout prop and spring physics. Hovering a row makes its avatar bubble up to 1.55× scale with a rubber-band spring (low damping for visible overshoot); the row's z-index lifts on hover so the avatar can overlay neighbors. Clicking anywhere on the row (whole row is the click target) opens a profile modal with player stats, a weekly bar chart, and badge pills. The modal avatar pops in with an even more exaggerated spring (scale from 0.25, -22° rotation), and the rank badge stamps in after like a sticker landing. The single "Award random points" button picks 1–3 distinct players, awards each a random score, and staggers the score-pops 220ms apart so the row reorders cascade.
Avatars are sourced from a hand-curated set in public/design-experiments/avatars/ and pre-cropped to 512×512 squares via sips. The Player type has an optional image?: string, with initials over the gradient circle as the fallback — adding a player without an image just works.
Also added the gen-image skill at .claude/skills/gen-image/SKILL.md for future avatar / icon / hero art generation: a procedural sheet that teaches Claude to call FAL via the FAL MCP (already installed in the user environment), upload reference images, batch-submit with prompt variation, and save to public/<experiment>/<topic>/NN.png. Zero credentials in the repo, no @fal-ai/client dependency.
Key files:
app/design-experiments/(experiments)/leaderboard/page.tsx— the experimentapp/design-experiments/(experiments)/leaderboard/styles.csspublic/leaderboard/avatars/01.jpg…08.jpg— cropped avatars.claude/skills/gen-image/SKILL.md— image generation skill
Monono — First AI-Backed Experiment
Date: 2026-04-16
Added the first experiment that calls an external AI model: Monono, a sarcastic J-pop idol chat character inspired by Monono Aware from M.R. Carey's Book of Koli. Introduces a reusable pattern for cheap, safe, public-facing AI experiments:
- Claude Haiku 4.5 via Anthropic SDK, called from a Next.js route handler (
app/api/monono/route.ts) so the API key stays server-side - Anthropic prompt caching on the character system prompt — near-zero input cost after the first call per cache window
- Per-IP session cap (60 messages/month) and global monthly spend tracking via Upstash Redis (
lib/ai/rate-limit.ts) - Anthropic workspace hard cap as the final wall
- Canned Monono-voice strings (
data/voice.ts) power greeting, idle nudges, session cutoff, and refresh-blocked moments — zero model cost for any non-conversational moment - Rolling 10-turn memory window keeps late-session cost flat
Cost envelope: ~$0.002 per turn, ~$0.12 per fully-maxed user, $5 monthly Anthropic cap. Tags: AI Chat, Character, Claude Haiku, Interactive.
Camera Rig — Promoted to Importable Component
Date: 2026-04-04
Promoted the Camera Rig experiment to a reusable, importable component. Extracted 4 components (CameraRig, Viewport3D, CameraView, ParamSlider) from monolithic page.tsx into components/ with CSS Modules. Added barrel export (index.ts) with public API: CameraRig component, CameraRigProps, CameraState type, and DEFAULT_CAMERA constant. Theme variables scoped to component root — no global style leakage.
Key files:
app/design-experiments/(experiments)/camera-rig/index.ts— barrel exportapp/design-experiments/(experiments)/camera-rig/components/CameraRig.tsx— composed componentapp/design-experiments/(experiments)/camera-rig/types.ts— shared types
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