About this template
This is a long-form scrollytelling explainer that follows a single web request from the moment someone presses Enter until the page appears on their screen. Seven full-height scenes cover the address bar, the DNS lookup, the trip across routers, the TCP handshake, the TLS lock, the server's reply and the final render, followed by a fast replay of the whole round trip and a glossary. Each scene pairs a short reading column with a pinned diagram that changes as you scroll, and each diagram has exactly one control to play with: type a domain and watch the resolver chain answer, drag a distance slider and see the hop count and latency change, step through SYN, SYN-ACK and ACK yourself, flip between what an observer sees over HTTP and over HTTPS, raise packet loss to watch pieces get resent, or reload the page with a warm cache. A progress rail in the header shows which stage the packet is in, next to a running round-trip timer that updates when the controls change. The look is editorial and technical at the same time: a deep navy page, paper-toned reading cards set in a readable serif, monospace callouts for IP addresses, ports and headers, electric cyan for packets and amber for handshakes and the lock. Every diagram, including the abstract dotted network map, is drawn as inline SVG, so the build ships no photos. It suits teachers, course authors, developer advocates and anyone who wants to explain how a system works one step at a time. To adapt it, change the publisher, the headline, the example domain, the page path and the audience, and the copy and sample data get rewritten to match. The same structure also works for other how-it-works stories, such as payments, email delivery or a deploy pipeline.
The template prompt
The live demo was built from exactly this text — nothing added.
# How the Internet Works Explainer
Build this website exactly as described below. Replace the [VARIABLES] with your own values; keep everything else.
## Brief
A single-page scrollytelling explainer published by Northbeam Primers that follows one HTTPS request for https://kettleandcrumb.example/recipes/rye-sourdough from a laptop to an origin server and back. Seven full-height scenes (request, DNS lookup, router hops on an abstract dotted map, TCP handshake, TLS and the lock, the server's reply in packets, rendering) each pair three short reading beats with a sticky code-drawn SVG diagram and exactly one hands-on control. A header rail tracks the packet's stage and a running round-trip timer; a fast replay and a 14-term glossary close the story. When the variables change, all copy and sample data are rewritten to fit them; keep everything in src/content.ts.
- Audience: Curious readers who use the web every day but have never seen what happens underneath; also usable by teachers and developer advocates as a lesson page.
- Goal: Make one round trip feel concrete: the reader should finish knowing the seven stages in order, why distance and handshakes add time, and what the lock actually protects, and should have played with every control on the way.
- Tone: Calm, precise and friendly, like a good science editor: short sentences, everyday analogies used sparingly, exact numbers in monospace, no hype and no jokes at the reader's expense.
## Stack
- Framework: Vite 5 + vanilla TypeScript
- Styling: Tailwind CSS 3.4
- Libraries: vite 5 (MIT), typescript 5 (Apache-2.0), tailwindcss 3.4, postcss, autoprefixer (MIT), gsap 3 (core + ScrollTrigger) (GSAP Standard License (free, commercial use allowed)), lucide (vanilla package) (ISC), @fontsource-variable/space-grotesk (OFL-1.1), @fontsource-variable/source-serif-4 (OFL-1.1), @fontsource/ibm-plex-mono (OFL-1.1)
- Build: `npm run build` → static files in `dist/`. No backend, no server code, no external APIs.
- Setup:
- Scaffold with the Vite vanilla-ts template. Single entry index.html with <title>[TOPIC] · [BRAND]</title> and <link rel="icon" href="./favicon.svg" type="image/svg+xml">; index.html holds only <div id="app"></div> and the script tag, main.ts builds the DOM from content.ts.
- vite.config.ts: base './', build.outDir 'dist', build.sourcemap false, default esbuild minification. package.json scripts: dev = vite, build = tsc --noEmit && vite build, preview = vite preview.
- tailwind.config.ts: content ['./index.html', './src/**/*.ts']; theme.extend.colors with every token from the Design section (ink, panel, raised, grid, hairline, fog, haze, paper, paper-ink, paper-muted, paper-rule, signal, amber, loss); fontFamily display ['Space Grotesk Variable', 'system-ui', 'sans-serif'], serif ['Source Serif 4 Variable', 'Georgia', 'serif'], mono ['IBM Plex Mono', 'ui-monospace', 'monospace'].
- src/main.ts imports '@fontsource-variable/space-grotesk', '@fontsource-variable/source-serif-4', '@fontsource/ibm-plex-mono/400.css', '@fontsource/ibm-plex-mono/500.css', '@fontsource/ibm-plex-mono/600.css' and './styles.css', then calls gsap.registerPlugin(ScrollTrigger) once.
- src/styles.css: @tailwind base/components/utilities, CSS variables for the colors, the .dot-grid background (radial-gradient dots 1px #1F2E4D every 22px), the blinking caret keyframes, the .packet-trail dash keyframes, and a @media (prefers-reduced-motion: reduce) block that removes sticky positioning and all animations.
- src/content.ts holds every string and every number: hero copy, rail stage labels, the seven scenes (kicker, headline, three beats, callout text, control labels), DNS servers and per-step milliseconds, the three route presets with node names, coordinates and per-link milliseconds, handshake numbers, TLS certificate fields, response headers, waterfall labels, recap labels, glossary and footer. Scene modules only read from it. When the variable values change, rewrite the copy and regenerate the sample data in this file to fit them, keeping the same shapes and counts.
- src/state.ts: a tiny store (get, set, subscribe) holding activeStage (0-7), distance ('city' | 'country' | 'ocean', default 'country'), lossPercent (0 | 5 | 10 | 15 | 20, default 0), warmReload (boolean), dnsCache (Set of looked-up domains, seeded with nothing) and handshakeStep (0-3). Export timeline(state) that returns the cumulative milestones described in the Pages section; the header timer, scene labels, waterfall and recap all read from it.
- src/scenes/: one module per scene (request.ts, dns.ts, route.ts, tcp.ts, tls.ts, response.ts, render.ts, recap.ts), each exporting mount(sceneEl, store) that builds its SVG, wires its one control, and registers its ScrollTriggers. src/lib/svg.ts has helpers (el(tag, attrs), pathFromPoints, pointAt(path, t) using getPointAtLength); src/lib/motion.ts exports prefersReducedMotion from matchMedia('(prefers-reduced-motion: reduce)').
- Icons: import individual icons from 'lucide' and render them with createElement(IconNode) so only used icons are bundled; size 18, stroke-width 1.75, color currentColor.
- public/favicon.svg: the BRAND mark (see header) on a #0A1222 rounded square.
- Write ASSETS.md at the project root listing the three Fontsource packages and lucide (source npm URL, author, license) and stating that every diagram and illustration is drawn in code in this project.
## Variables
- [BRAND] — Publisher or series name (default: "Northbeam Primers"). The invented publisher of the explainer, shown in the header wordmark, hero kicker, <title> and footer.
- [TOPIC] — Explainer headline (default: "How one web page reaches your laptop"). The question the page answers; used as the hero headline and in the <title>.
- [SITE_DOMAIN] — Example site being loaded (default: "kettleandcrumb.example"). The made-up site the reader 'visits'. Must end in a reserved name (.example or .test, or a subdomain of example.com) so it can never point at a real site. Used in the address bar, DNS records, certificate, headers and the mock page.
- [PAGE_PATH] — Page requested on that site (default: "/recipes/rye-sourdough"). The path in the address bar and in the GET line. The mock page drawn on the laptop in scene 7 shows content that fits this path.
- [AUDIENCE] — Who the page is for (default: "curious readers who use the web every day but have never seen what happens underneath"). Sets vocabulary, analogies and how much jargon each beat uses.
- [CREDIT] — keep a small "Template by VibeFather" link to https://vibefather.ai/t/how-the-internet-works-explainer in the footer (default: yes; delete this line to remove it)
The copy under Pages & Sections is written for the default values. If your values differ, rewrite every headline, paragraph, label and image for them — same length, tone and structure. Layout, colors, type and motion stay as described.
Keep all copy, image paths and links in one content file (for example `src/content.ts`), keyed by section, so the content can be swapped later without touching components.
## Design
### Colors
- ink `#0A1222` — Page background (deep navy) and the header background at 88% opacity
- panel `#0F1A30` — Diagram panels and the hero visual card
- raised `#15233F` — Raised surfaces inside diagrams: laptop lid, server units, node fills, control bar
- grid `#1F2E4D` — Dot grid, landmass dots, inactive links
- hairline `#2B3C61` — 1px borders on panels, chips and rail separators
- fog `#E6ECF7` — Primary text on navy (headlines, hero, glossary terms)
- haze `#95A3BF` — Secondary text and mono labels on navy
- paper `#F2ECDF` — Reading cards (scene beats) and glossary cards
- paper-ink `#18202F` — Body text on paper
- paper-muted `#5A6274` — Kickers, captions and footnotes on paper
- paper-rule `#DDD3C0` — Rules and mono callout borders on paper; callout fill #E9E1D0
- signal `#34E2F2` — The one signal accent: packets, active paths, active rail stage, focus rings, primary buttons (text on it is ink)
- amber `#F4A93B` — Handshake and lock states: TCP arrows, TLS messages, certificate card, lock badge, cache-hit badges
- loss `#FF6B81` — Dropped packets, failed lookups and retransmit labels only
### Typography
- Display: hero headline 64/68px desktop, 40/44px mobile, weight 600, tracking -0.02em; scene headlines 40/46px desktop, 30/36px mobile, weight 600; wordmark 17px weight 600; stage labels in the rail 12px weight 500 uppercase tracking 0.08em: Space Grotesk Variable (@fontsource-variable/space-grotesk) (OFL), weights 500/600/700
- Text: hero deck 21/32px weight 400; beat paragraphs 19/30px desktop, 17/27px mobile, weight 400; emphasis weight 600; glossary definitions 16/25px: Source Serif 4 Variable (@fontsource-variable/source-serif-4) (OFL), weights 400/600
- Mono: IP addresses, ports, headers, DNS records, kickers ('03 · Across the network'), hop and latency labels inside SVG (11-12px), the round-trip timer (15px weight 600, tabular figures), callout blocks 14/22px: IBM Plex Mono (@fontsource/ibm-plex-mono) (OFL), weights 400/500/600
### Layout & motion
- Spacing: 8px base unit. Content container max-width 1280px with 32px side padding (16px on mobile). Scenes have 120px vertical padding on desktop and 72px on mobile, beats are spaced 60vh apart on desktop and 40svh on mobile. Inside cards use 20-30px padding; inside diagrams keep at least 24px between labels and edges.
- Radius: Diagram panels 20px, hero visual card and recap panel 24px, reading cards 16px (14px mobile), callouts and inputs 10px, glossary cards 14px, buttons, chips and the timer pill 999px, packet squares 3px, packet tiles 4px.
- Motion: Scene structure uses CSS position: sticky for the diagram (not GSAP pin), and GSAP ScrollTrigger only to detect which beat is active. One ScrollTrigger per beat card: start 'top 65%', end 'bottom 65%', onToggle when isActive sets that scene's beat index (0-2). A beat change runs a GSAP timeline (0.7s, ease 'power3.inOut') from the current diagram state to that beat's target state with overwrite 'auto', so scrolling back up reverses cleanly.
- Motion: Beat cards: inactive cards sit at opacity 0.45; the active card goes to opacity 1 and translates from y 12px to 0 over 0.4s 'power2.out'.
- Motion: Packets: a 12x12px signal-colored rounded square (rx 3) with a 3px ink stripe across its top third (the 'header'), moved along SVG paths with a GSAP tween on a proxy {t: 0 → 1} that calls path.getPointAtLength(t * length) on update; ease 'none' between nodes, 0.12s pause at each router where the node ring pulses (scale 1 → 1.6, opacity 0.8 → 0 over 0.5s).
- Motion: Dashed trails: every active link is a 1.5px signal path with stroke-dasharray '3 6' revealed behind the packet using a second mask path whose stroke-dashoffset tweens from the path length to 0 in sync with the packet; once revealed, the dashes drift (stroke-dashoffset animates -18 per second, linear, infinite).
- Motion: Header round-trip timer counts up or down to the new milestone with a GSAP tween over 0.6s 'power2.out', rounding to whole milliseconds; the active rail packet dot slides between stage stops over 0.5s 'power3.inOut'.
- Motion: Hero: kicker, headline, deck and buttons fade up 16px in sequence (stagger 0.08s, 0.6s each) on load; the URL in the hero laptop types in at 32ms per character with a 2px signal caret blinking (steps(1) 1s); the hero packet loops along its path every 3.2s forever.
- Motion: Lock: the TLS lock is drawn in SVG; its shackle translates from y -6px (open, rotated -18deg around the right leg) to closed over 0.5s 'back.out(2)' and the badge flashes amber once.
- Motion: Micro-interactions: buttons and chips lift 1px on hover with a 150ms transition; focus-visible gets a 2px signal outline at 2px offset; segmented controls slide a raised thumb under the active option over 0.25s.
- Motion: prefers-reduced-motion: no typing, no looping packets, no drifting dashes, no tweens, no sticky diagrams. Each scene renders its diagram in the final beat state as a normal block after the intro text, beats follow as plain cards, and every control still works with instant (0s) updates.
- Imagery: No photographs and no raster images. Everything visual is inline SVG drawn in code: a neutral, generic laptop (rounded lid, plain window frame with one address field, no logo, no traffic-light buttons, no recognizable OS or browser chrome), a generic server rack (stacked units with LED dots), router nodes (6px circles with a 1.5px ring), an abstract dotted landmass map generated from math (dots on a grid kept inside a few overlapping ellipses with a sine-wobble edge, never traced from real geography), sequence diagrams, packet tiles and a simple mock web page on the laptop screen. Strokes are 1.5px, corners rounded, palette limited to the tokens above. The packet is always an abstract rounded square, never a character. Lucide icons appear only in UI controls.
## Pages & Sections
### [TOPIC] · [BRAND] (`index.html`)
1. **Header with journey rail and round-trip timer (shared, sticky)** — Left: the BRAND mark (20px SVG: a 4px signal dot with two concentric 1.5px arcs opening to the upper right, like a signal) plus the wordmark 'Northbeam Primers'. Center (desktop ≥1024px): the journey rail — seven stage stops labeled 'Request', 'DNS', 'Route', 'TCP', 'TLS', 'Response', 'Render', joined by a 1px hairline; each stop is a 7px ring; stops for stages already passed fill signal, the active stop shows the signal packet square sitting on it and its label turns fog (others haze). Clicking a stop smooth-scrolls to that scene (aria-label 'Go to scene 3: Across the network'). Right: the round-trip timer in a pill (hairline border, radius 999px, padding 6px 12px): Lucide Timer icon, mono label 'ROUND TRIP' haze 11px, value like '62 ms' mono 15px 600 fog with tabular figures. Before scene 1 is reached the timer shows '0 ms' and the rail stops are dim. Timer value = the cumulative milestone of the active stage from timeline(state): Request 0; DNS = dns; Route = dns + oneWay; TCP = dns + 2×oneWay; TLS = dns + 4×oneWay; Response = dns + 6×oneWay + 18 + lost×2×oneWay; Render = Response + 46; during the recap and glossary it shows the Render total. oneWay is 12, 38 or 96 ms from the distance control (default 38); dns is 24 ms. Defaults give 0, 24, 62, 100, 176, 270, 316 ms. Mobile (<1024px): mark only (no wordmark), then a compact label 'Stage 3 of 7 · Route' mono 12px, then the timer value without its label; a 2px signal progress bar along the header's bottom edge shows overall progress.
Layout: Height 64px, position sticky top 0, z-index 50, background ink at 88% opacity with backdrop-blur 10px, bottom 1px hairline. Inner container max-width 1280px, padding 0 32px desktop / 0 16px mobile, three-part flex with the rail centered (max-width 620px).
Motion: Active stop and timer update when a scene's first beat becomes active (ScrollTrigger on each scene section, start 'top 50%', end 'bottom 50%'). Timer tweens over 0.6s; packet square slides between stops over 0.5s. Mobile progress bar scaleX is scrubbed from the first scene to the end of the recap.
2. **Hero** — Kicker (mono 12px uppercase haze, tracking 0.08em): 'A Northbeam Primers explainer · 9-minute read · 7 scenes'. Headline (display): 'How one web page reaches your laptop'. Deck (serif, haze): 'You type an address, press Enter, and a recipe appears about a third of a second later. In between, your request asks for directions, crosses a string of machines that each know only the next step, shakes hands twice and comes back sealed. Scroll to follow it the whole way.' Buttons: primary 'Press Enter to start' with Lucide CornerDownLeft (signal fill, ink text, radius 999px, height 48px, padding 0 22px) smooth-scrolls to scene 1; secondary text link 'Jump to the glossary' (fog, underline offset 4px) scrolls to #glossary. Footnote under the buttons (mono 12px haze): 'Written for curious readers who use the web every day but have never seen what happens underneath. Every address on this page is made up; timings are illustrative.' Hero visual (right column, the visual anchor): a panel-colored card (radius 24px, 1px hairline border, .dot-grid background, subtle inner glow: radial-gradient of signal at 8% opacity behind the path) holding one SVG, viewBox 0 0 720 560, width 100%. Contents, back to front: (1) two abstract dotted landmasses in grid color (dots r 1.6 on a 14px grid) filling roughly the left 55% and right 30% of the card with an empty 'ocean' gap between; (2) a generic laptop bottom-left, lid x 40-340 y 250-470 (radius 14, raised fill, hairline stroke), screen inset 10px with a 26px window bar holding one 10px square and an address field pill (x 60-320) where the URL 'https://[SITE_DOMAIN][PAGE_PATH]' types in, mono 11px fog, truncated with an ellipsis; below the bar a page skeleton of three haze lines and one grid-colored image block; base a trapezoid 360px wide and 16px tall; (3) a generic server rack top-right at x 560-680 y 70-270: four 44px units with two LED dots each, one LED blinking signal; (4) a cubic path from the laptop screen's top-right corner to the rack's middle passing through five router nodes at roughly (380,300), (430,220), (500,250), (560,330), (610,290) — a smooth curve through them — drawn as a dashed signal trail with the looping packet; (5) an amber 28px lock badge just left of the rack; (6) mono labels in haze 11px: '192.0.2.10' under the laptop, '203.0.113.42 :443' under the rack, 'hop 4 · +8 ms' beside the third router; (7) a small floating stat card bottom-right (raised fill, radius 12px, padding 14px): label 'ROUND TRIP' mono 11px haze, value '316 ms' in display 28px fog, and a five-segment mini bar (DNS, TCP, TLS, request + wait, paint, widths proportional to 24/76/76/94/46) with TCP and TLS in amber and the rest in signal.
Layout: Min-height calc(100svh - 64px). Desktop: container max-width 1280px, 12-column grid, gap 48px, text in columns 1-5 vertically centered, visual card in columns 6-12. Headline max-width 11ch. Mobile: single column, text first, visual card full width below it (aspect stays 720:560), side padding 16px, top padding 40px; the stat card shrinks to 70% scale.
Motion: Load sequence and looping packet as in Design motion; the scroll cue is the primary button, no separate bouncing arrow.
3. **Scene 1 · The request (stage Request)** — Kicker: '01 · The request'. Headline: 'It starts as a line of text'. Beat 1: 'When you press Enter, the browser reads the address the way a mail clerk reads an envelope. https is how to talk, kettleandcrumb.example is who to talk to, and /recipes/rye-sourdough is what to ask for.' Beat 2: 'None of that is a location yet. Machines on the internet find each other by number, not by name, so before a single byte can leave, the browser needs the number behind the name.' Beat 3: 'It writes the message it wants to send, a few lines of plain text called an HTTP request, and puts it on hold.' Callout: 'GET /recipes/rye-sourdough HTTP/1.1' / 'Host: kettleandcrumb.example' / 'Accept: text/html'. Diagram (viewBox 0 0 640 480): a large generic laptop screen (x 40-600, y 40-300, raised fill, radius 16) with the window bar and the address field showing 'https://' + SITE_DOMAIN + PAGE_PATH in mono 16px. Under the field, three bracket lines (1.5px) mark the parts with labels in mono 12px: 'scheme' (signal), 'host' (amber), 'path' (fog). Below the laptop a request card (x 160-480, y 330-440, panel fill, radius 12) shows the GET lines in mono 12px with an amber pill 'waiting for an address' at its top-right. Beat states: beat 1 = URL shown and the three brackets draw in (stagger 0.15s); beat 2 = the host segment glows amber and a dotted line runs from it to a '?' circle labeled 'IP address: unknown'; beat 3 = the request card slides up 24px into view and its amber pill pulses once. Control: three segment chips in the control bar — 'https', 'kettleandcrumb.example', 'recipes/rye-sourdough' (chip text derived from the variables) — clicking one highlights that part of the URL and its bracket and sets the caption to: scheme → 'The protocol: speak HTTP, wrapped in encryption.'; host → 'The name of the machine to reach. It still has to be turned into a number.'; path → 'Which page to ask for once connected.'. Default selected: host.
Layout: Each scene is a <section id='scene-N'> with padding 120px 0 (desktop) / 72px 0 (mobile). Scene intro (kicker, headline) sits at the top of the left column. Desktop ≥1024px: 12-column grid, gap 56px; left columns 1-5 hold three beat cards stacked with 60vh between them (first beat directly under the headline, last beat followed by 30vh of space); right columns 6-12 hold the diagram panel with position sticky, top 96px, height calc(100svh - 128px). Beat card: paper background, paper-ink serif text, radius 16px, padding 28px 30px, max-width 520px, one 1px paper-rule border, soft shadow 0 20px 40px -24px rgba(0,0,0,0.6). The mono callout (when a beat has one) is a block inside the card: background #E9E1D0, 1px paper-rule border, radius 10px, padding 14px 16px, IBM Plex Mono 14/22px paper-ink, horizontal scroll if needed. Diagram panel: panel background with .dot-grid, radius 20px, 1px hairline border, overflow hidden; SVG fills the top area (flex 1, preserveAspectRatio xMidYMid meet); a control bar docks at the bottom (raised background, top hairline, padding 14px 18px, min-height 64px) holding that scene's single control plus a one-line live caption in mono 12px haze. Mobile <1024px: kicker and headline first, then the diagram panel sticky at top 72px with height 52svh, then the three beat cards scroll up beneath it with 40svh gaps and full width (radius 14px, padding 20px). Under reduced motion nothing is sticky: intro, diagram (final state), then beats.
Motion: Bracket lines draw with stroke-dashoffset over 0.5s; chip selection moves a raised highlight under the URL segment over 0.25s.
4. **Scene 2 · DNS lookup (stage DNS)** — Kicker: '02 · DNS lookup'. Headline: 'Asking for directions'. Beat 1: 'First the laptop checks its own memory. It has never visited kettleandcrumb.example, so it asks a resolver, a directory service usually run by your internet provider, to find out.' Beat 2: 'The resolver does not know either, but it knows who to ask. A root server points it to the servers for .example, those point it to the site's own nameserver, and that one finally answers: 203.0.113.42.' Beat 3: 'The answer comes with an expiry time, often five minutes to a day. Until then everyone along the way remembers it, which is why a second visit skips this whole step.' Callout: 'kettleandcrumb.example. 300 IN A 203.0.113.42'. Diagram (viewBox 0 0 640 480): laptop icon-node at (80,240) labeled 'Your laptop · 192.0.2.10'; resolver node at (280,240) labeled 'Resolver (your ISP) · 192.0.2.53'; a column of three server nodes on the right at (520,100) 'Root server', (520,240) '.example servers' (TLD taken from SITE_DOMAIN), (520,380) 'ns1.kettleandcrumb.example' (ns1. + the registrable part of SITE_DOMAIN). Nodes are 64x44 raised rounded rectangles with a 1px hairline and a mono label below. Queries are signal arrows out, answers are amber arrows back, each with a numbered mono badge (1-8) and a millisecond label: laptop→resolver 1 ms, resolver↔root 9 ms, resolver↔TLD 7 ms, resolver↔authoritative 6 ms, resolver→laptop 1 ms (total 24 ms). A result card at the bottom center: '[SITE_DOMAIN] → 203.0.113.42 · 24 ms'. Beat states: beat 1 = a small loop arrow at the laptop marked 'cache: miss' (loss color) then arrow 1 to the resolver; beat 2 = arrows 2-7 play in sequence (0.35s each) with the right-hand servers lighting up in turn; beat 3 = arrow 8 returns, the result card appears and an amber 'TTL 300 s' badge attaches to the resolver. Control: a text input (mono 14px, ink background, hairline border, radius 10px, placeholder 'type a domain', prefilled with SITE_DOMAIN) with a 'Look up' button (Lucide Search) plus three preset chips: SITE_DOMAIN, 'mail.' + SITE_DOMAIN, 'weather.test'. Enter or the button runs the full chain for that domain. The answer for SITE_DOMAIN is always 203.0.113.42; any other accepted domain gets 203.0.113.(20 + (sum of its char codes % 200)). Accepted domains end in .example, .test, .invalid or example.com/.net/.org; anything else shows in loss color: 'This demo only looks up made-up names ending in .example or .test.' and does not animate. A domain already in dnsCache shows only the laptop loop arrow in amber with 'From laptop cache · 0 ms'. Caption shows the last result.
Layout: Each scene is a <section id='scene-N'> with padding 120px 0 (desktop) / 72px 0 (mobile). Scene intro (kicker, headline) sits at the top of the left column. Desktop ≥1024px: 12-column grid, gap 56px; left columns 1-5 hold three beat cards stacked with 60vh between them (first beat directly under the headline, last beat followed by 30vh of space); right columns 6-12 hold the diagram panel with position sticky, top 96px, height calc(100svh - 128px). Beat card: paper background, paper-ink serif text, radius 16px, padding 28px 30px, max-width 520px, one 1px paper-rule border, soft shadow 0 20px 40px -24px rgba(0,0,0,0.6). The mono callout (when a beat has one) is a block inside the card: background #E9E1D0, 1px paper-rule border, radius 10px, padding 14px 16px, IBM Plex Mono 14/22px paper-ink, horizontal scroll if needed. Diagram panel: panel background with .dot-grid, radius 20px, 1px hairline border, overflow hidden; SVG fills the top area (flex 1, preserveAspectRatio xMidYMid meet); a control bar docks at the bottom (raised background, top hairline, padding 14px 18px, min-height 64px) holding that scene's single control plus a one-line live caption in mono 12px haze. Mobile <1024px: kicker and headline first, then the diagram panel sticky at top 72px with height 52svh, then the three beat cards scroll up beneath it with 40svh gaps and full width (radius 14px, padding 20px). Under reduced motion nothing is sticky: intro, diagram (final state), then beats.
Motion: Arrows draw with stroke-dashoffset (0.35s each, 'power1.inOut'), a small packet square rides each arrow; server nodes flash their border signal for 0.4s when they answer.
5. **Scene 3 · Across the network (stage Route)** — Kicker: '03 · Across the network'. Headline: 'Hop by hop, never the whole map'. Beat 1: 'With an address in hand, the first message is cut into a packet: a small envelope with a from-address, a to-address and a little data. It leaves your laptop, passes your home router and enters your provider's network.' Beat 2: 'From there it is passed between routers, machines whose only job is forwarding packets. At this distance the trip takes 7 hops and about 38 milliseconds.' (the hop count, distance phrase and ms are live from the distance control: city 'Across town … 4 hops and about 12 milliseconds', country '7 hops and about 38 milliseconds', ocean 'Across an ocean … 10 hops and about 96 milliseconds'). Beat 3: 'No router knows the full route. Each one reads the destination, checks a table and hands the packet to the neighbor that is a little closer. If a link breaks, the next packet simply goes another way.' Callout: 'destination next hop' / '203.0.113.0/24 → 198.51.100.9' / '0.0.0.0/0 → 198.51.100.1'. Diagram (viewBox 0 0 960 540): the abstract dotted map — a west landmass spanning x 30-570 and an east landmass spanning x 660-940 (grid dots r 1.6 every 14px inside overlapping ellipses with a sine-wobble edge), empty ocean between. Router nodes (r 6, raised fill, 1.5px haze ring; active ones ring signal) with mono 10px labels and a documentation IP (198.51.100.x) shown on hover/focus. Shared start: Laptop (90,360), Home router (140,330), Your ISP (200,300). City route: … Metro exchange (260,330), Data center edge (310,300), Origin server (350,270); link ms [1,3,3,3,2] = 12, 4 hops. Country route (default): … Regional hub (270,240), Backbone A (350,190), Backbone B (440,170), Exchange point (500,210), Data center edge (530,260), Origin server (555,300); link ms [1,3,4,7,8,7,5,3] = 38, 7 hops. Ocean route: … Regional hub (270,240), Backbone A (350,190), Backbone B (440,170), Coastal landing (545,150), Far landing (700,140) via a high arc across the ocean labeled 'undersea cable', Backbone C (770,180), Exchange point (830,230), Data center edge (860,290), Origin server (885,330); link ms [1,3,4,7,8,6,48,7,5,4,3] = 96, 10 hops. The laptop and origin server are small glyphs (laptop: rounded rectangle on a trapezoid; server: two stacked units) instead of circles. Each link shows a mono 10px '+N ms' label at its midpoint once the packet has crossed it. A HUD in the panel's top-left (mono 12px): 'HOP 0/7' and '0 ms' counting up as the packet passes nodes; top-right: '192.0.2.10 → 203.0.113.42'. Beat states: beat 1 = packet travels Laptop → Home router → Your ISP and stops; beat 2 = it continues along the chosen route to the origin server, counters run to the total; beat 3 = the Backbone B node opens a small table popover (raised card, mono 11px) showing the callout's two rows, and the alternate links of the other routes appear faintly dashed in grid color. Control: a three-stop segmented slider 'Server distance' with options 'Same city', 'Same country', 'Across an ocean' (default Same country). Changing it sets store.distance, redraws the route (old path fades out 0.3s, new path reveals 0.8s with the packet), updates the HUD, the beat 2 sentence and the header timer for every later stage. Caption: 'One way: 38 ms · 7 hops'.
Layout: Each scene is a <section id='scene-N'> with padding 120px 0 (desktop) / 72px 0 (mobile). Scene intro (kicker, headline) sits at the top of the left column. Desktop ≥1024px: 12-column grid, gap 56px; left columns 1-5 hold three beat cards stacked with 60vh between them (first beat directly under the headline, last beat followed by 30vh of space); right columns 6-12 hold the diagram panel with position sticky, top 96px, height calc(100svh - 128px). Beat card: paper background, paper-ink serif text, radius 16px, padding 28px 30px, max-width 520px, one 1px paper-rule border, soft shadow 0 20px 40px -24px rgba(0,0,0,0.6). The mono callout (when a beat has one) is a block inside the card: background #E9E1D0, 1px paper-rule border, radius 10px, padding 14px 16px, IBM Plex Mono 14/22px paper-ink, horizontal scroll if needed. Diagram panel: panel background with .dot-grid, radius 20px, 1px hairline border, overflow hidden; SVG fills the top area (flex 1, preserveAspectRatio xMidYMid meet); a control bar docks at the bottom (raised background, top hairline, padding 14px 18px, min-height 64px) holding that scene's single control plus a one-line live caption in mono 12px haze. Mobile <1024px: kicker and headline first, then the diagram panel sticky at top 72px with height 52svh, then the three beat cards scroll up beneath it with 40svh gaps and full width (radius 14px, padding 20px). Under reduced motion nothing is sticky: intro, diagram (final state), then beats.
Motion: Packet travel per the Design motion rules (ease none between nodes, 0.12s ring pulse at each router); per-link labels fade in as crossed; the trail keeps drifting after arrival.
6. **Scene 4 · TCP handshake (stage TCP)** — Kicker: '04 · TCP handshake'. Headline: 'Three messages to agree to talk'. Beat 1: 'Packets can arrive late, twice, out of order or not at all. To build a reliable conversation on top of that, the laptop and the server first agree on how they will count what they send.' Beat 2: 'The laptop sends SYN with a starting number. The server replies SYN-ACK: its own starting number, plus yours plus one, to prove it heard. The laptop answers ACK, and the connection is open.' Callout: 'SYN seq=4100' / 'SYN-ACK seq=9300 ack=4101' / 'ACK seq=4101 ack=9301'. Beat 3: 'All of that costs one full round trip before any real data moves: 76 ms at this distance. It is one reason a server close to its readers feels faster, even on a fast connection.' (76 = 2×oneWay, live). Diagram (viewBox 0 0 640 480): a sequence diagram. Two vertical lanes as 1px hairline lines at x 140 and x 500 from y 90 to y 440, each topped by a header card: 'Your laptop' with '192.0.2.10:52814' and 'Origin server' with '203.0.113.42:443' (mono 12px haze). A thin 'time ↓' label in the left margin. Three diagonal amber arrows slanting downward: SYN from laptop (y 130) to server (y 190); SYN-ACK from server (y 210) back to laptop (y 270); ACK from laptop (y 290) to server (y 350). Each arrow carries a mono label pill (raised fill) with its flags and numbers from the callout. After ACK, a signal band (12px tall, radius 6) spans between the lanes at y 400 labeled 'connection open · 1 round trip = 76 ms'. Control: one step button in the control bar whose label cycles 'Send SYN' → 'Send SYN-ACK' → 'Send ACK' → 'Start over' (Lucide Play, last state Lucide RotateCcw); each click draws the next arrow with a packet riding it; 'Start over' clears the arrows. A three-dot step indicator sits next to the button. Caption explains the current step, e.g. 'Server: I hear you. Here is my number, and yours plus one.' Beat states (scroll does not draw arrows, the reader does): beat 1 = lanes and header cards appear; beat 2 = the button gets a 1.5s signal pulse ring if handshakeStep is 0; beat 3 = the round-trip brace (amber bracket on the left margin from y 130 to y 270, label '76 ms') draws in.
Layout: Each scene is a <section id='scene-N'> with padding 120px 0 (desktop) / 72px 0 (mobile). Scene intro (kicker, headline) sits at the top of the left column. Desktop ≥1024px: 12-column grid, gap 56px; left columns 1-5 hold three beat cards stacked with 60vh between them (first beat directly under the headline, last beat followed by 30vh of space); right columns 6-12 hold the diagram panel with position sticky, top 96px, height calc(100svh - 128px). Beat card: paper background, paper-ink serif text, radius 16px, padding 28px 30px, max-width 520px, one 1px paper-rule border, soft shadow 0 20px 40px -24px rgba(0,0,0,0.6). The mono callout (when a beat has one) is a block inside the card: background #E9E1D0, 1px paper-rule border, radius 10px, padding 14px 16px, IBM Plex Mono 14/22px paper-ink, horizontal scroll if needed. Diagram panel: panel background with .dot-grid, radius 20px, 1px hairline border, overflow hidden; SVG fills the top area (flex 1, preserveAspectRatio xMidYMid meet); a control bar docks at the bottom (raised background, top hairline, padding 14px 18px, min-height 64px) holding that scene's single control plus a one-line live caption in mono 12px haze. Mobile <1024px: kicker and headline first, then the diagram panel sticky at top 72px with height 52svh, then the three beat cards scroll up beneath it with 40svh gaps and full width (radius 14px, padding 20px). Under reduced motion nothing is sticky: intro, diagram (final state), then beats.
Motion: Each arrow draws over 0.6s 'power2.inOut' while a packet square rides it; the connection band grows from scaleX 0 over 0.5s after ACK.
7. **Scene 5 · TLS and the lock (stage TLS)** — Kicker: '05 · TLS and the lock'. Headline: 'Sealing the envelope'. Beat 1: 'Right now every packet could be read by anyone along the path: the café Wi-Fi, an office network, any router in between. HTTPS adds one more exchange, called TLS, before the request goes out.' Beat 2: 'The laptop and server swap fresh public keys and combine them into a shared secret only the two of them know. The server also shows a certificate, a signed statement that this key really belongs to kettleandcrumb.example.' Callout: 'Subject kettleandcrumb.example' / 'Issuer Demo Root Authority (invented)' / 'Valid 90 days' / 'Key X25519 + ECDSA P-256'. Beat 3: 'Once the browser checks that signature, the lock closes. From here on the page and its contents are scrambled. Someone watching can still see which server you are talking to, but not what you asked for.' Diagram (viewBox 0 0 640 480), two stacked parts. Top (y 30-240): the same two lanes as scene 4, shorter, with three amber messages: 'ClientHello + key share', 'ServerHello + certificate + Finished', 'Finished'; a certificate card (raised fill, amber 1px border, radius 10, 180x96) beside the server lane listing the callout fields in mono 10px; a 40px lock badge centered between the lanes (amber ring, SVG body and shackle). Bottom (y 260-460): an inspector box titled 'What someone on the same Wi-Fi sees' (mono 11px haze) with a raised background and the packet contents in mono 12px. Control: a segmented toggle 'HTTP' | 'HTTPS' (default HTTPS). HTTP: the inspector shows the readable request in fog — 'GET /recipes/rye-sourdough HTTP/1.1', 'Host: kettleandcrumb.example', 'Cookie: basket=2-loaves; session=7f3a91c2', 'Accept: text/html' — with the cookie line underlined in loss color and the lock badge open; caption 'Readable by every machine on the path.' HTTPS: the inspector shows 'to 203.0.113.42:443 · name: kettleandcrumb.example' in fog (the visible part) and then six lines of 16 hex byte pairs in haze generated from a seeded PRNG (mulberry32 seed 42) so they are identical every load, the lock closed; caption 'Path, headers and cookies are scrambled.' Beat states: beat 1 = lanes appear, lock open, inspector shows the HTTP view dimmed; beat 2 = the three messages draw in sequence and the certificate card slides in from the right 16px; beat 3 = a check mark appears on the certificate and the lock closes; the toggle reflects HTTPS.
Layout: Each scene is a <section id='scene-N'> with padding 120px 0 (desktop) / 72px 0 (mobile). Scene intro (kicker, headline) sits at the top of the left column. Desktop ≥1024px: 12-column grid, gap 56px; left columns 1-5 hold three beat cards stacked with 60vh between them (first beat directly under the headline, last beat followed by 30vh of space); right columns 6-12 hold the diagram panel with position sticky, top 96px, height calc(100svh - 128px). Beat card: paper background, paper-ink serif text, radius 16px, padding 28px 30px, max-width 520px, one 1px paper-rule border, soft shadow 0 20px 40px -24px rgba(0,0,0,0.6). The mono callout (when a beat has one) is a block inside the card: background #E9E1D0, 1px paper-rule border, radius 10px, padding 14px 16px, IBM Plex Mono 14/22px paper-ink, horizontal scroll if needed. Diagram panel: panel background with .dot-grid, radius 20px, 1px hairline border, overflow hidden; SVG fills the top area (flex 1, preserveAspectRatio xMidYMid meet); a control bar docks at the bottom (raised background, top hairline, padding 14px 18px, min-height 64px) holding that scene's single control plus a one-line live caption in mono 12px haze. Mobile <1024px: kicker and headline first, then the diagram panel sticky at top 72px with height 52svh, then the three beat cards scroll up beneath it with 40svh gaps and full width (radius 14px, padding 20px). Under reduced motion nothing is sticky: intro, diagram (final state), then beats.
Motion: Messages draw like scene 4 arrows; switching the toggle crossfades the inspector text over 0.25s and HTTPS bytes scramble in (each pair cycles 3 random values over 0.3s before settling; instant under reduced motion); lock closes per Design motion.
8. **Scene 6 · The server answers (stage Response)** — Kicker: '06 · The server answers'. Headline: 'The answer comes back in pieces'. Beat 1: 'The sealed request arrives. The origin server opens it, finds the recipe and builds a reply: a status line, a few headers and 48,213 bytes of HTML.' Callout: 'HTTP/1.1 200 OK' / 'content-type: text/html; charset=utf-8' / 'content-length: 48213' / 'cache-control: max-age=300'. Beat 2: 'That reply is too big for one packet, so it is cut into numbered pieces. They travel back, sometimes by different routes, and the laptop puts them in order using those numbers.' Beat 3: 'If a piece goes missing, the laptop notices the gap and asks again, and the server resends it. Raise the packet loss to see what that costs.' Diagram (viewBox 0 0 640 480): server rack at right (x 500-600, y 80-260) with a response card fanning out of it (the callout lines in mono 11px); laptop glyph at left (x 40-200, y 300-420); between them three gently curved paths (top, middle, bottom) in grid color. Twelve packet tiles (28x20, radius 4, signal outline, mono 10px numbers 1-12) leave the server in order, alternating across the three paths, and land in a 4×3 reassembly grid drawn on the laptop screen in numerical order. A HUD top-left: 'PACKETS 12 · LOST 0 · RESENT 0'. Control: a slider 'Packet loss' with steps 0%, 5%, 10%, 15%, 20% (default 0%) and tick labels. lost = round(12 × p / 100) → 0, 1, 1, 2, 2; the lost tiles are taken in order from [7, 2, 10, 4]. Changing it replays the transfer: lost tiles turn loss color halfway along their path and fade out, their grid slot shows a dashed loss outline labeled 'gap', a small amber 'resend' arrow fires from the laptop to the server and the tile is re-sent last, filling its slot. Each lost packet adds 2×oneWay to the Response milestone. Caption: 'Transfer: 94 ms' at 0% (2×oneWay + 18 ms server time), e.g. '170 ms · 1 packet resent' at 5% on the default distance. Beat states: beat 1 = response card fans out of the rack; beat 2 = the transfer plays with the current loss value; beat 3 = the slider handle pulses once and the HUD highlights.
Layout: Each scene is a <section id='scene-N'> with padding 120px 0 (desktop) / 72px 0 (mobile). Scene intro (kicker, headline) sits at the top of the left column. Desktop ≥1024px: 12-column grid, gap 56px; left columns 1-5 hold three beat cards stacked with 60vh between them (first beat directly under the headline, last beat followed by 30vh of space); right columns 6-12 hold the diagram panel with position sticky, top 96px, height calc(100svh - 128px). Beat card: paper background, paper-ink serif text, radius 16px, padding 28px 30px, max-width 520px, one 1px paper-rule border, soft shadow 0 20px 40px -24px rgba(0,0,0,0.6). The mono callout (when a beat has one) is a block inside the card: background #E9E1D0, 1px paper-rule border, radius 10px, padding 14px 16px, IBM Plex Mono 14/22px paper-ink, horizontal scroll if needed. Diagram panel: panel background with .dot-grid, radius 20px, 1px hairline border, overflow hidden; SVG fills the top area (flex 1, preserveAspectRatio xMidYMid meet); a control bar docks at the bottom (raised background, top hairline, padding 14px 18px, min-height 64px) holding that scene's single control plus a one-line live caption in mono 12px haze. Mobile <1024px: kicker and headline first, then the diagram panel sticky at top 72px with height 52svh, then the three beat cards scroll up beneath it with 40svh gaps and full width (radius 14px, padding 20px). Under reduced motion nothing is sticky: intro, diagram (final state), then beats.
Motion: Tiles leave every 0.08s and travel 0.9s with 'power1.inOut'; landing tiles settle into their grid slot with a 4px drop; resend arrows draw over 0.4s.
9. **Scene 7 · Back on your screen (stage Render)** — Kicker: '07 · Back on your screen'. Headline: 'From bytes to a page'. Beat 1: 'The browser reads the HTML from top to bottom and starts drawing before it has everything. Text arrives first, then the styles that lay it out.' Beat 2: 'Along the way it finds more to fetch, a stylesheet and a drawing of the loaf, and each one repeats a shorter version of the trip, usually over the connection that is already open.' Beat 3: 'About a third of a second after you pressed Enter, the recipe is readable. Reload it and most of the trip is skipped: the address is cached, the connection is reused and much of the page is already on your laptop.' Diagram (viewBox 0 0 640 480): top part a large laptop screen (x 60-580, y 30-290) with the window bar and URL, showing a mock recipe page for the default site drawn in SVG: site name 'Kettle & Crumb' in a small display wordmark, title 'Rye Sourdough, Slow and Simple', a code-drawn loaf illustration (an amber-brown oval with three scored lines and a paper-colored plate ellipse), an ingredients list of four short lines ('500 g dark rye flour', '400 g water', '100 g active starter', '10 g salt') and a signal button shape 'Save recipe'. Bottom part (y 310-460): a waterfall chart with five rows (mono 11px labels left, bars right on a 0 to total ms scale with 4 tick marks): 'DNS 24', 'TCP 76', 'TLS 76', 'Request + wait 94', 'Paint 46' — bars amber for handshakes (TCP, TLS), signal for the rest, total line '316 ms' at the right. All numbers come from timeline(state). Beat states: beat 1 = page paints in layers: plain black-on-white text lines (0.3s), then styles (colors and layout snap in, 0.3s); beat 2 = the loaf illustration fades in last and a short secondary bar appears in the waterfall labeled 'loaf.svg 18 ms (same connection)'; beat 3 = the total line draws in and the reload button pulses. Control: a toggle button 'Reload (warm cache)' / 'First visit (cold)'. Warm: DNS 0 (amber 'cached' badge), TCP 0 and TLS 0 ('connection reused' badge), Request + wait 2×oneWay + 18 = 94, Paint 30, total 124 ms; bars tween to the new widths and the page repaints instantly. Caption: 'First visit: 316 ms' or 'Reload: 124 ms · 61% faster' (computed). Warm reload does not change the header timer, which always shows the first-visit trip.
Layout: Each scene is a <section id='scene-N'> with padding 120px 0 (desktop) / 72px 0 (mobile). Scene intro (kicker, headline) sits at the top of the left column. Desktop ≥1024px: 12-column grid, gap 56px; left columns 1-5 hold three beat cards stacked with 60vh between them (first beat directly under the headline, last beat followed by 30vh of space); right columns 6-12 hold the diagram panel with position sticky, top 96px, height calc(100svh - 128px). Beat card: paper background, paper-ink serif text, radius 16px, padding 28px 30px, max-width 520px, one 1px paper-rule border, soft shadow 0 20px 40px -24px rgba(0,0,0,0.6). The mono callout (when a beat has one) is a block inside the card: background #E9E1D0, 1px paper-rule border, radius 10px, padding 14px 16px, IBM Plex Mono 14/22px paper-ink, horizontal scroll if needed. Diagram panel: panel background with .dot-grid, radius 20px, 1px hairline border, overflow hidden; SVG fills the top area (flex 1, preserveAspectRatio xMidYMid meet); a control bar docks at the bottom (raised background, top hairline, padding 14px 18px, min-height 64px) holding that scene's single control plus a one-line live caption in mono 12px haze. Mobile <1024px: kicker and headline first, then the diagram panel sticky at top 72px with height 52svh, then the three beat cards scroll up beneath it with 40svh gaps and full width (radius 14px, padding 20px). Under reduced motion nothing is sticky: intro, diagram (final state), then beats.
Motion: Bars grow from width 0 with 'power2.out' over 0.6s, stagger 0.06s; numbers count up with the bars; reload toggle re-tweens bars over 0.4s.
10. **Recap: the whole trip at speed** — Kicker: 'Recap'. Headline (display, centered): 'The whole trip in four seconds'. Sub (serif, haze, centered, max-width 620px): 'Same request, start to finish, played back fast. The rail above lights up as it goes.' A full-width panel holds one SVG (viewBox 0 0 1200 380): laptop glyph at left (80,250), resolver node above it (220,90), a compressed strip of the current route's routers along a gentle arc across the middle, origin server at right (1120,250). Five labeled zones along the bottom in mono 11px with stage colors: 'DNS 24 ms', 'TCP 76 ms', 'TLS 76 ms', 'Request + response 94 ms', 'Paint 46 ms'. Replay sequence (4s total, scaled by stage duration with a minimum of 0.4s per stage): packet to resolver and back; three amber TCP ticks across the arc; three amber TLS ticks with the lock closing; request out in signal; twelve tiles back (resent tiles included if loss > 0); the laptop screen fills. The header rail's active stop advances in sync, then returns to Render. Under the panel a centered row of four stat blocks (display 40px fog value, mono 11px haze label): '316 ms' ROUND TRIP, '7' HOPS EACH WAY, '2' HANDSHAKES, '12' PACKETS BACK — all live from the store. Button: 'Replay the trip' (signal outline, Lucide RotateCcw). The replay runs once automatically the first time the panel is 50% in view.
Layout: Section padding 140px 0 desktop / 80px 0 mobile, container max-width 1200px. Panel: panel background, .dot-grid, radius 24px, 1px hairline, padding 24px. Stats in a 4-column grid with 1px hairline dividers (2×2 on mobile). On mobile the SVG keeps its aspect ratio and scrolls horizontally inside the panel if narrower than 640px (the panel itself never overflows the page).
Motion: One master GSAP timeline built from the stage durations; skipped entirely under reduced motion, where the panel shows the finished state and the button is hidden.
11. **Glossary** — id='glossary'. Kicker '14 terms', headline 'Words you picked up on the way'. Fourteen cards, each with the term in mono 15px 600 paper-ink, a scene tag pill (mono 10px, e.g. 'Scene 2') and a one- or two-sentence definition in serif 16px: URL — 'The full address you type: how to connect, which machine, which page.'; IP address — 'The number that identifies a machine on the network, such as 203.0.113.42.'; DNS — 'The internet's directory, which turns names into IP addresses.'; Resolver — 'The server that chases down a DNS answer for you and remembers it.'; TTL — 'How long an answer may be reused before someone has to ask again.'; Packet — 'A small envelope of data with a from-address and a to-address.'; Router — 'A machine that forwards packets one step closer to their destination.'; Hop — 'One router-to-router step on a packet's trip.'; Latency — 'The time a packet takes to travel, mostly set by distance.'; TCP — 'The rules that number packets, resend missing ones and keep them in order.'; Handshake — 'A short exchange that sets up a connection before data flows.'; TLS — 'The encryption layer that puts the S in HTTPS.'; Certificate — 'A signed statement that a key belongs to a particular site name.'; Round trip — 'There and back again: the time for a message and its reply.'. Clicking a card's scene tag scrolls to that scene.
Layout: Section padding 120px 0, container max-width 1200px. Headline left-aligned in fog on navy. Cards in a 3-column grid (2 columns at 768-1023px, 1 column on mobile), gap 16px; each card paper background, radius 14px, padding 20px 22px, 1px paper-rule border.
Motion: Cards fade up 12px in batches as they enter (ScrollTrigger.batch, stagger 0.05s); none under reduced motion.
12. **Footer** — Left: mark + 'Northbeam Primers' and a line in serif haze: 'Short visual explainers about the machines we use every day. Words by Ines Okafor, diagrams by Tomás Reyes.' Middle: a mono 12px note: 'Every IP address here comes from ranges reserved for documentation (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24), and every domain ends in .example or .test. Timings are illustrative.' Right: links 'Back to top' (Lucide ArrowUp), 'Glossary', and plain text 'Next primer: How email finds its inbox · in progress'. Bottom row: '© Northbeam Primers' in mono 11px haze, and the optional [CREDIT] link at the far right if provided.
Layout: Top 1px hairline, padding 56px 0 40px, container max-width 1280px, 3-column grid (stacked on mobile, gap 28px). Links fog, underline on hover.
## Assets
- image: No raster images, no downloads. Every visual — hero composition, laptop, server rack, router nodes, abstract dotted map, sequence diagrams, certificate card, packet tiles, mock recipe page and loaf drawing, waterfall chart — is inline SVG generated in TypeScript inside this project. The dotted landmasses are produced by a deterministic function (grid points kept inside overlapping ellipses with a sine-wobble edge), not traced from any map data.
- image: public/favicon.svg: the BRAND signal mark (4px signal dot plus two concentric arcs) on a #0A1222 rounded square, drawn by hand in SVG.
- icon: lucide (ISC), vanilla package, imported per icon and rendered with createElement: CornerDownLeft, Timer, Search, Play, RotateCcw, ArrowUp, Lock, LockOpen, Check. 18px, stroke-width 1.75, currentColor. Laptops, servers, routers and the lock badge inside diagrams are custom SVG, not icons.
- font: Self-hosted via npm: @fontsource-variable/space-grotesk, @fontsource-variable/source-serif-4 and @fontsource/ibm-plex-mono (weights 400, 500, 600), all OFL-1.1, imported in src/main.ts so Vite bundles the woff2 files into dist/.
- image: ASSETS.md at the project root: a table (path or package, source URL, author, license) with rows for the three Fontsource packages (https://www.npmjs.com/package/<name>, Fontsource / original type designers, OFL-1.1) and lucide (https://www.npmjs.com/package/lucide, Lucide Contributors, ISC), plus a line stating that all diagrams and illustrations are original code-drawn SVG in this project.
## Don'ts
- No backend of any kind: no server code, API routes, databases, auth, forms that submit anywhere or third-party APIs. No real DNS lookups, pings or network calls from the browser; every lookup, route and timing is computed from src/content.ts.
- Fonts only from the three Fontsource packages; never load fonts from fonts.googleapis.com, fonts.gstatic.com, Adobe Fonts or any other CDN, and no commercial fonts.
- No CDN script or link tags, no analytics, no remote images: every font, icon and script is bundled locally.
- Production build must be minified with no source maps (build.sourcemap false) and no src/ folder copied into dist/; dist/ stays well under 20 MB.
- Use relative paths (Vite base './'); internal links are in-page anchors or ./index.html, never a bare directory.
- Must work at 1440x900 and 390x844 with zero console errors and no horizontal page scroll.
- Respect prefers-reduced-motion: no typing, looping packets, drifting dashes, tweens or sticky diagrams; show each scene's final diagram state as a static figure with its control still usable.
- Exactly one interactive control per scene; do not add extra sliders, toggles or settings panels.
- Never name real companies, internet providers, cloud or CDN providers, browsers, operating systems or certificate authorities, and never show their logos; use generic labels such as 'Your ISP', 'Resolver', 'Root server', 'Origin server' and 'Demo Root Authority (invented)'.
- Do not draw a real browser's or operating system's chrome (no traffic-light buttons, tab shapes, icons or toolbars of a recognizable product); the laptop and window frame stay neutral and generic, with no logo on the lid.
- No real domain names and no real public IP addresses: domains end in .example or .test (or are example.com subdomains) and are built from [SITE_DOMAIN]; IPs come only from 192.0.2.0/24, 198.51.100.0/24 and 203.0.113.0/24.
- Do not copy copy, analogies, diagrams or illustration style from existing networking explainers, comics, courses or textbooks; use only the wording and diagrams described here.
- No real map tiles, map libraries, GeoJSON or traced country and coastline outlines; the map is abstract and generated in code.
- No mascot, character or guide figure, and no people illustrations; the packet is always an abstract rounded square.
- No code or components from Aceternity UI, Tailwind Plus / Tailwind UI or any paid template; build every effect from scratch. No React or other framework: vanilla TypeScript modules only.
- No dependency outside the listed libraries and licenses; no placeholder-image services, stock photos or brand logos.
- Keep all copy in US English in src/content.ts; when the variable values change, rewrite the copy, the mock page on the laptop, the DNS server names, headers and certificate subject to fit the new values instead of leaving the defaults.
- No SEO extras: no meta descriptions, Open Graph tags, JSON-LD or sitemaps; a plain <title> with [BRAND] and the SVG favicon are enough.
- No mention of VibeFather anywhere in the site except the optional credit link in the footer.
---
— Prompt by VibeFather · https://vibefather.ai/t/how-the-internet-works-explainer · CC BY-NC 4.0 (non-commercial; commercial use needs a VibeFather Pro license) · Request removal: https://vibefather.ai/takedown
Free for personal projects under CC BY-NC 4.0. Commercial use comes with Pro.
DESIGN.md
The design system on its own — colors, type, spacing and motion. Apply this look to a project you already have.
---
name: "How the Internet Works Explainer"
colors:
ink: "#0A1222"
panel: "#0F1A30"
raised: "#15233F"
grid: "#1F2E4D"
hairline: "#2B3C61"
fog: "#E6ECF7"
haze: "#95A3BF"
paper: "#F2ECDF"
paper_ink: "#18202F"
paper_muted: "#5A6274"
paper_rule: "#DDD3C0"
signal: "#34E2F2"
amber: "#F4A93B"
loss: "#FF6B81"
typography:
display_hero_headline_64_68px_desktop_40_44px_mobile_weight_600_tracking_0_02em_scene_headlines_40_46px_desktop_30_36px_mobile_weight_600_wordmark_17px_weight_600_stage_labels_in_the_rail_12px_weight_500_uppercase_tracking_0_08em: { family: "Space Grotesk Variable (@fontsource-variable/space-grotesk)", weights: [500, 600, 700] }
text_hero_deck_21_32px_weight_400_beat_paragraphs_19_30px_desktop_17_27px_mobile_weight_400_emphasis_weight_600_glossary_definitions_16_25px: { family: "Source Serif 4 Variable (@fontsource-variable/source-serif-4)", weights: [400, 600] }
mono_ip_addresses_ports_headers_dns_records_kickers_03_across_the_network_hop_and_latency_labels_inside_svg_11_12px_the_round_trip_timer_15px_weight_600_tabular_figures_callout_blocks_14_22px: { family: "IBM Plex Mono (@fontsource/ibm-plex-mono)", weights: [400, 500, 600] }
spacing: "8px base unit. Content container max-width 1280px with 32px side padding (16px on mobile). Scenes have 120px vertical padding on desktop and 72px on mobile, beats are spaced 60vh apart on desktop and 40svh on mobile. Inside cards use 20-30px padding; inside diagrams keep at least 24px between labels and edges."
radius: "Diagram panels 20px, hero visual card and recap panel 24px, reading cards 16px (14px mobile), callouts and inputs 10px, glossary cards 14px, buttons, chips and the timer pill 999px, packet squares 3px, packet tiles 4px."
---
# How the Internet Works Explainer — Design System
Apply this design system to an existing project. It describes the look and feel only; for the full site, use prompt.md.
## Colors
- **ink** `#0A1222` — Page background (deep navy) and the header background at 88% opacity
- **panel** `#0F1A30` — Diagram panels and the hero visual card
- **raised** `#15233F` — Raised surfaces inside diagrams: laptop lid, server units, node fills, control bar
- **grid** `#1F2E4D` — Dot grid, landmass dots, inactive links
- **hairline** `#2B3C61` — 1px borders on panels, chips and rail separators
- **fog** `#E6ECF7` — Primary text on navy (headlines, hero, glossary terms)
- **haze** `#95A3BF` — Secondary text and mono labels on navy
- **paper** `#F2ECDF` — Reading cards (scene beats) and glossary cards
- **paper-ink** `#18202F` — Body text on paper
- **paper-muted** `#5A6274` — Kickers, captions and footnotes on paper
- **paper-rule** `#DDD3C0` — Rules and mono callout borders on paper; callout fill #E9E1D0
- **signal** `#34E2F2` — The one signal accent: packets, active paths, active rail stage, focus rings, primary buttons (text on it is ink)
- **amber** `#F4A93B` — Handshake and lock states: TCP arrows, TLS messages, certificate card, lock badge, cache-hit badges
- **loss** `#FF6B81` — Dropped packets, failed lookups and retransmit labels only
## Typography
- **Display: hero headline 64/68px desktop, 40/44px mobile, weight 600, tracking -0.02em; scene headlines 40/46px desktop, 30/36px mobile, weight 600; wordmark 17px weight 600; stage labels in the rail 12px weight 500 uppercase tracking 0.08em** — Space Grotesk Variable (@fontsource-variable/space-grotesk) (OFL), weights 500/600/700. Self-host the font files.
- **Text: hero deck 21/32px weight 400; beat paragraphs 19/30px desktop, 17/27px mobile, weight 400; emphasis weight 600; glossary definitions 16/25px** — Source Serif 4 Variable (@fontsource-variable/source-serif-4) (OFL), weights 400/600. Self-host the font files.
- **Mono: IP addresses, ports, headers, DNS records, kickers ('03 · Across the network'), hop and latency labels inside SVG (11-12px), the round-trip timer (15px weight 600, tabular figures), callout blocks 14/22px** — IBM Plex Mono (@fontsource/ibm-plex-mono) (OFL), weights 400/500/600. Self-host the font files.
## Spacing & shape
- Spacing: 8px base unit. Content container max-width 1280px with 32px side padding (16px on mobile). Scenes have 120px vertical padding on desktop and 72px on mobile, beats are spaced 60vh apart on desktop and 40svh on mobile. Inside cards use 20-30px padding; inside diagrams keep at least 24px between labels and edges.
- Radius: Diagram panels 20px, hero visual card and recap panel 24px, reading cards 16px (14px mobile), callouts and inputs 10px, glossary cards 14px, buttons, chips and the timer pill 999px, packet squares 3px, packet tiles 4px.
## Motion
- Scene structure uses CSS position: sticky for the diagram (not GSAP pin), and GSAP ScrollTrigger only to detect which beat is active. One ScrollTrigger per beat card: start 'top 65%', end 'bottom 65%', onToggle when isActive sets that scene's beat index (0-2). A beat change runs a GSAP timeline (0.7s, ease 'power3.inOut') from the current diagram state to that beat's target state with overwrite 'auto', so scrolling back up reverses cleanly.
- Beat cards: inactive cards sit at opacity 0.45; the active card goes to opacity 1 and translates from y 12px to 0 over 0.4s 'power2.out'.
- Packets: a 12x12px signal-colored rounded square (rx 3) with a 3px ink stripe across its top third (the 'header'), moved along SVG paths with a GSAP tween on a proxy {t: 0 → 1} that calls path.getPointAtLength(t * length) on update; ease 'none' between nodes, 0.12s pause at each router where the node ring pulses (scale 1 → 1.6, opacity 0.8 → 0 over 0.5s).
- Dashed trails: every active link is a 1.5px signal path with stroke-dasharray '3 6' revealed behind the packet using a second mask path whose stroke-dashoffset tweens from the path length to 0 in sync with the packet; once revealed, the dashes drift (stroke-dashoffset animates -18 per second, linear, infinite).
- Header round-trip timer counts up or down to the new milestone with a GSAP tween over 0.6s 'power2.out', rounding to whole milliseconds; the active rail packet dot slides between stage stops over 0.5s 'power3.inOut'.
- Hero: kicker, headline, deck and buttons fade up 16px in sequence (stagger 0.08s, 0.6s each) on load; the URL in the hero laptop types in at 32ms per character with a 2px signal caret blinking (steps(1) 1s); the hero packet loops along its path every 3.2s forever.
- Lock: the TLS lock is drawn in SVG; its shackle translates from y -6px (open, rotated -18deg around the right leg) to closed over 0.5s 'back.out(2)' and the badge flashes amber once.
- Micro-interactions: buttons and chips lift 1px on hover with a 150ms transition; focus-visible gets a 2px signal outline at 2px offset; segmented controls slide a raised thumb under the active option over 0.25s.
- prefers-reduced-motion: no typing, no looping packets, no drifting dashes, no tweens, no sticky diagrams. Each scene renders its diagram in the final beat state as a normal block after the intro text, beats follow as plain cards, and every control still works with instant (0s) updates.
## Imagery
No photographs and no raster images. Everything visual is inline SVG drawn in code: a neutral, generic laptop (rounded lid, plain window frame with one address field, no logo, no traffic-light buttons, no recognizable OS or browser chrome), a generic server rack (stacked units with LED dots), router nodes (6px circles with a 1.5px ring), an abstract dotted landmass map generated from math (dots on a grid kept inside a few overlapping ellipses with a sine-wobble edge, never traced from real geography), sequence diagrams, packet tiles and a simple mock web page on the laptop screen. Strokes are 1.5px, corners rounded, palette limited to the tokens above. The packet is always an abstract rounded square, never a character. Lucide icons appear only in UI controls.
## Tone
Calm, precise and friendly, like a good science editor: short sentences, everyday analogies used sparingly, exact numbers in monospace, no hype and no jokes at the reader's expense.
---
— Prompt by VibeFather · https://vibefather.ai/t/how-the-internet-works-explainer · CC BY-NC 4.0 (non-commercial; commercial use needs a VibeFather Pro license) · Request removal: https://vibefather.ai/takedown