Skip to main content
Skip to content
Back to Atlas
Built with Claude Code

Atlas, built in a single Claude Code session.

A field guide to YC's AI generation, designed and shipped in dialogue with Claude Code. Every screen drawn in code, every easing curve felt. Below is a curated summary of how it came together, in the order it happened. Linked SHAs are the actual commits.

31
atlas commits
12
polish passes
2,017
companies indexed
2,000
founders enriched

The work · chronological

  1. 01

    Brief

    Single-page AI-native field guide to YC's AI generation. Apple × OpenAI aesthetic, off-white #FAFAF7 / charcoal #0A0A0A, one accent (YC orange #FF6600, used ≤7 times). Newsreader serif + Inter Tight body + JetBrains Mono numerals scoped to /yc only. Spring physics 200-400 ms. Reduced-motion respected. Cloudflare-only deploy.

  2. 02

    Data pipeline

    Three scripts. atlas:fetch pulls 5,925 YC companies from yc-oss, narrows to 2,017 AI rows W21→X26. atlas:enrich classifies into 9 sub-verticals via a Cloudflare Workers AI → Claude Haiku 4.5 cascade with caching. atlas:trends rolls up per-batch + global aggregates and emits trends.json / seeds.json / companies.json. A follow-on atlas:founders scraper enriches with founder names, photos, LinkedIn/X links (99 % coverage, 3,384 photos mirrored locally).

    commit · b9f81c68
  3. 03

    Route + design system

    Atlas fonts isolated in lib/atlas-fonts.ts (don't share with site fonts, would leak the chunk into every other route). [data-atlas] CSS-var scope for tokens. Dark mode flips bg/fg; accent + brand colors are theme-independent. Build-time OG image via app/yc/opengraph-image.tsx + next/og ImageResponse (1200×630 PNG, ~51 KB). JSON-LD WebApplication + Dataset schema for GEO.

  4. 04

    Hero with constellation

    2,017 dots positioned via Halton low-discrepancy sequence, so the same company lands on the same dot across deploys. Sinusoidal drift + spring magnetism within 120 px of the cursor on desktop; static compact path on mobile for perf. prefers-reduced-motion → single paint, no RAF. Word-by-word headline reveal via CSS keyframes (framer-motion stalled inside Radix Dialog earlier; same fix here, plain CSS).

  5. 05

    Command bar + SSE endpoint

    POST /api/atlas-query → text/event-stream. Two Claude Sonnet 4.6 calls: tool_use for forced JSON filter spec, then streaming synthesis with [[slug]] citations. companies.fn.json imported at build time, no runtime fetch. ⌘K hotkey, single YC-orange focus border, suggestions hoisted above the bar. Followed by a five-stage streaming UX in the response panel: parsing → searching → reading N matches → writing → done.

  6. 06

    Grid + filters + URL sync

    Lazy-fetched cohort, module-cached. Filter pills as a state-of-industry snapshot: per-pill cohort counts (W25 · 135, LLM apps · 404, Active · 1831). Three filter dimensions + sort. URL sync via window.history.replaceState (no history pollution). Selected card uses YC orange border + soft wash instead of an accent rail.

  7. 07

    Profile + methodology panels

    Shared floating-panel widget. Desktop: slides in from the right at 640 px max-width with edge gap matching the command bar's outer padding (28 px). Mobile: slides up as a bottom sheet with a drag handle, sits above the command bar (which floats at z-50). Single butter-smooth cubic-bezier(0.22, 0.61, 0.36, 1) curve in both directions. Profile shows lede → meta → founders row → related companies; methodology is the same widget reused.

    commit · 7bad1675
  8. 08

    Infographic block

    Atlas at a glance: 4 KPI tiles, companies-per-batch bar chart with motion.g + scaleY (animating SVG y/height attributes directly stalled in framer, so a wrapper element + transform was the fix), Apple Watch ActivityRings-style top-verticals wheel. Each ring + legend dot uses a distinct YC orange shade or white accent. Numbers count up via RAF. Hover any ring or bar → highlight; click → pin and light up matching dots on the constellation backdrop, auto-cleared via IntersectionObserver when the section scrolls out of view.

    commit · d262eed1
  9. 09

    Top header + slide-deck scroll

    Sticky glass header with Charts · Filters · About nav, appearing past scrollY 480 px. Each nav button auto-scrolls to its anchored section. scroll-snap-type: y mandatory + atlas-slide class on hero + charts so the page reads as discrete full-screen slides. Hero parallax tied to its own scrollYProgress (not window scrollY) so the headline stays full-opacity until the section is actively leaving. Section-relative is the only way parallax + snap coexist cleanly.

    commit · 83df6a49
  10. 10

    Founders pipeline

    yc-oss doesn't ship founder data. Built a scraper for the ycombinator.com per-company Inertia data-page JSON: extracts full_name + title + avatar_thumb_url (presigned S3, 1-hour TTL → mirror locally so deploy survives expiry) + LinkedIn + Twitter. Throttled 1 req/s out of respect; SIGINT-safe checkpoint every 25 companies. First pass got 29 % (run hit a timeout); second pass to 99 %. 3,384 photos in public/atlas/founders/, ~55 MB.

    commit · f0609436
  11. 11

    Bug hunt + production

    Three parallel Explore agents audited responsiveness, parallax integrity, interaction states. Punch list returned: header pointer-events race (per-frame useTransform string could turn a button non-clickable mid-click; fix: stable React boolean state from a passive scroll listener); scrollIntoView to #atlas-charts failing in query mode (section unmounted; fix: flip browse mode first, then rAF the scroll); grid card aria-pressed always-boolean for screen readers. Then promoted to production: removed robots:noindex, added googleBot directives, verified the apex domain serves the build.

    commit · f6f62339

What I learned about working with Claude Code

  • Vibe coding is real, and it's specifically a craft skill, not a shortcut. You're still naming spacing scales, picking easing curves, weighing a 0.78 spring damping against a 0.85. The tools just compress the loop between intent and shipped pixel. After the third pass I noticed I'd stopped describing components in prose and started giving Claude the exact CSS-var token names and the framer transition props I wanted. Vocabulary mattered more than instruction count.
  • Two animation regressions came from the same root cause: framer-motion stalling when asked to interpolate SVG attribute values (the bar chart, the panel slide). Both fixed by wrapping with a <g> or a CSS-keyframed parent and animating the transform on the wrapper. After the second one I knew to reach for it instinctively. Pattern recognition compounded. I started writing the prompt as if I'd already debugged it once.
  • Tried scroll-snap twice. First attempt used `proximity` + `scroll-snap-stop: always`. Those two settings conflict (proximity allows in-between positions; stop:always forbids them). Removing snap entirely was the wrong overreaction; the cleaner pairing is `mandatory` alone. Naming the slide opt-out class `atlas-slide` made the intent obvious in the JSX.
  • Parallax + snap can coexist, but only if the parallax is section-relative (`useScroll({ target: ref })`). Window-scrollY parallax interpolates from 0→400 px while the snap is jumping from 0→100vh; the math doesn't compose. Once both read from the same section's scroll progress, the parallax animates DURING the snap transition.
  • The constellation drift was set to 1 px amplitude, invisible after the spring physics damped it. Bumping to 2.8 px gave the field the breathing motion the user noticed was missing. Small numbers compound through physics chains; pixel-perfect is a function of math, not just measurement.