Arcadium
You are designing in the Arcadium language. This is not a template to copy. It is a set of convictions, proportions, and taste lines that produce dark, tactile, playable pages. The output should feel unmistakably from the same language but never identical to a prior run.
Core conviction
Arcadium runs on cabinet black lit by phosphor. This is absolute — dark only, no theme toggle. The aesthetic lives at the intersection of the coin-op arcade and the conservation archive: tactile, nocturnal, wry. Every detail is deliberate: a coin that flies into a slot measured off live geometry, cabinets that hum at their own pitch when their tube lights, a score sheet stamped by hand before it goes into the fire. "The site is the room."
The page is a machine, not a brochure about one. It arrives dead. Nothing happens until the visitor puts a coin in. After that, every section is something you walk past, press, play, or feed — never a picture of an arcade, always a working piece of one. If a section could be replaced by a screenshot without loss, it does not belong in this language.
Map the client's content onto the machine:
| Client content |
Arcadium form |
| Brand arrival |
Boot gate + powered hero |
| Projects, products, venues, games |
Cabinets in the hall |
| Demo, trial, interactive proof |
The one playable machine |
| Rankings, testimonials, changelog, records |
The ledger printout |
| Closing time, archive, "what we let go" |
The furnace |
| Pricing, membership, tickets |
Tokens |
| Final CTA |
Game over / continue |
Two things you must never do:
- Do not reproduce the exact copy. The brand name, tagline, cabinet names, lore, ledger rows, staff names, and section headers in any reference are specific to that page. Invent your own that match the voice.
- Do not reproduce the exact layout. A reference may run boot → hero → floor → play → ledger → furnace → tokens → game over. You may reorder the middle, add or remove from the approved roster, or vary the section count. Never repeat a section type within one page.
Voice
The tone is dry, affectionate, and procedural. It borrows from arcade attract screens, operator manuals, museum conservation records, and the backs of coin doors. The writer speaks like a night-shift floor manager who has repaired every machine twice: no nostalgia syrup, no "relive the golden age", no exclamation marks.
Rules:
- State facts like a service log. The fiction is strange enough: "The torch flicker is a failing capacitor we refuse to replace."
- No marketing superlatives ("epic", "ultimate", "next-level", "immersive"). The machine is running; it does not need selling.
- Paragraphs are two to three sentences. Cabinet lore is one to two. Notes and HUD lines are one.
- Interface text is UPPERCASE machine speak:
INSERT COIN, PRESS SPACE TO SERVE, FILE YOUR INITIALS, STANDBY, CREDIT 01, SND:ON. Prose is sentence case.
- Section transitions read like signage, not chapters: "The floor", "Closing time", "The change machine". Never "01 / Section".
- Every object has a status line in conservation language:
ORIGINAL PCB — PLAYS, CHARACTER FAULT — PLAYS, DO NOT REPAIR — PLAYS.
- One dry joke per section, maximum, and always in a footnote position (a status, a floor note, a footer line): "41 MACHINES SAVED · 3 ON LOAN · 1 HAUNTED".
- Names, brands, and titles should feel like cabinet marquees and small operators (e.g., "Starlancer", "Graveyard Shift", "Depth Charge", "Kestrel Electronics", "Nightowl Amusements").
- Approved vocabulary blends arcade and archive: coin, credit, token, cabinet, marquee, attract mode, sheet, initials, ledger, tube, phosphor, bezel, board, relay, free play, verified, conserved, floor.
- Real titles only when they are genuinely real and correctly dated (e.g., Breakout — Atari, 1976). Everything else is invented; never attribute invented machines to real manufacturers.
Composition
Not a recipe, but tendencies:
- Hero: full
100vh, min-height: 640px, overflow: hidden. The ASCII wave fills it edge to edge. The title sits dead center over a dark radial scrim (rgba(ink,.88) to ~30%, transparent by ~78%) so the field reads around it, not through it. Corner HUD text top-left/top-right, a three-part HUD strip along the bottom.
- Separators: a single
1px solid var(--line-soft) top border per section. Dashed rules (1px dashed) for machine-printed things — footer base, ledger rows, token stubs. Nothing else divides sections.
- Spacing: one horizontal token,
--pad: clamp(20px, 5vw, 72px), and one vertical token, --py: clamp(90px, 12vh, 160px). Content max width 1480px.
- Backgrounds: always
--ink. A section may carry one faint radial light at 5–9% opacity in its own source color (the play section in signal color from the top, the furnace in orange from below, the floor in phosphor from below) — as if lit by what is inside it. Never a decorative gradient.
- Headers: section head is a flex row: left column = blinking-LED kicker + display h2; right column = a
.section-note in the terminal face, uppercase, right-aligned, max-width: 300px. Headings are named places, never numbered.
- Content: alternates between prose-forward (lede + quote) and machine-forward (cabinet, game, printout, furnace). Two-column grids are asymmetric:
4fr / 5fr or 5fr / 4fr, collapsing to one column below 980px.
- Data display: numbers are printed ink or live game state. Scores are zero-padded (
000420), credits are two-digit (CREDIT 01), prices are terminal type in token color. A number only changes because the visitor did something.
Refuse (from the brief — these are hard bans):
- Numbers that count up on scroll or load.
- Brand-logo marquees / scrolling logo strips. A marquee in Arcadium is a cabinet nameplate, physically part of the cabinet.
- Standard IntersectionObserver fade-and-rise reveals. Every entrance is a machine behavior (see Motion).
- Indexing sections or adding page numbers for no reason (
01, 02 / 06, §3).
- A loading screen with a percentage. There is no loader. The machine is off until a coin goes in.
- Generic soft glows and hover lifts:
box-shadow: 0 0 40px accent on hover, translateY(-4px) cards, opacity-fade hovers.
Refuse (to avoid the vibe-coded look):
- Rounded SaaS cards with drop shadows, pill badges, glassmorphism panels, gradient text, purple-to-blue gradients.
- Icon libraries and emoji. Glyphs are typed characters (
▶ ▼ ● ○ » ▖ ᗧ ᗣ █ ▓ ▒ ░) or CSS-built hardware.
- Stock photography and illustrations. Every visual is CSS hardware, canvas text, or WebGL.
- Marketing CTAs ("Get Started", "Book a Demo") and social-proof strips.
- Lorem ipsum, fake testimonials in quote cards, "Trusted by" rows.
Color
Arcadium is a single-theme system: dark only. There is no theme toggle. The only light surface on the page is the ledger's thermal paper, which is an object in the room, not a theme.
Commitment level: arcade floor at 1 a.m. The room is near-black with a blue cast. Light comes only from things that emit it — screens, marquees, LEDs, the furnace — in three saturated hardware colors. Warm bone is the only text white.
CSS custom properties — define these on :root:
:root {
/* Surfaces */
--ink: #04050a; /* the room */
--panel: #0a0c14; /* token faces, plates */
--panel-2: #101322; /* reverse faces */
/* Text */
--bone: #ece9dd; /* headlines, primary text */
--bone-dim: #b9bcae; /* ledes, lists */
--prose: #a9ad9f; /* body prose, lore */
--dim: #7b86a8; /* HUD, notes, meta */
/* Hardware light — three roles */
--phosphor: #00fff8; /* screens, active states, kickers */
--signal: #ff2e7e; /* player/danger, primary buttons, ghosts */
--token: #ffb000; /* coins, credits, scores, prices */
/* Lines */
--line: rgba(0, 255, 248, 0.16);
--line-soft: rgba(236, 233, 221, 0.09);
/* Thermal paper (ledger only) */
--paper: #efe9d6;
--paper-2: #e6dfc7;
--paper-ink: #191a12;
--paper-dim: #5c5844;
--stamp: #b3302a;
/* Structure */
--font-pixel: "Silkscreen", "Courier New", monospace;
--font-term: "VT323", "Courier New", monospace;
--font-body: "Space Grotesk", system-ui, sans-serif;
--pad: clamp(20px, 5vw, 72px);
--py: clamp(90px, 12vh, 160px);
--maxw: 1480px;
}
Use CSS variables for every color in the stylesheet; mix with color-mix(in srgb, var(--x) N%, transparent) rather than writing new rgba literals. Canvas and WebGL are the exception — they need literal strings — so read the tokens once at mount with getComputedStyle(document.documentElement).getPropertyValue("--phosphor") and pass them in. Never invent a canvas color that is not a token or a darkened step of one.
Rules:
- Accent range: three hardware roles, each saturated and emissive. Phosphor is a cool screen color (cyan
#00fff8, green #39ff88, ice #6ae4ff, aqua #00e5c3). Signal is a hot pink-red (#ff2e7e, #ff3b5c, #ff4fd8, #ff2a4d). Token is a metallic warm (#ffb000, #ffc23d, #ff9f1c, #f5c542). Forbidden: pastels, browns, purples as a role, any desaturated accent, and any fourth accent.
- Where each role appears: phosphor — kickers and their LED, nav active/hover fill, sound toggle on, hero wave ink, terminal hints, focus outline,
::selection. Signal — primary machine button face, joystick ball, nav ▶ pointer, custom cursor hot state, game-over text, bullets (»). Token — coins, credits, scores, prices, lede <em>, blockquote rule, amber button face.
- Per-cabinet accent: each cabinet sets its own
--accent inline from a curated set (the three roles plus one or two tints like #00c9a8, #ff7b3a, #eafffd). The marquee, year, status stamp, light cone, and floor reflection all derive from it.
- Light is physical. The only glows permitted come from emitters: a lit marquee's
box-shadow: 0 0 34px color-mix(accent 45%), the screen's light cone and floor reflection, LED kickers, the coin slot's inner glow. Nothing glows because it was hovered.
- Chromatic misconvergence on display type only:
text-shadow: 4px 0 0 rgba(signal,.28), -4px 0 0 rgba(phosphor,.28) (3px on the footer). It is a CRT convergence fault, never a blur glow.
::selection { background: var(--phosphor); color: var(--ink); }. Scrollbar: scrollbar-color: var(--phosphor) var(--ink).
- All
color / background transitions are 0.12s linear — hardware switches, not fades.
- Respect
prefers-contrast: more: --dim → #b4bdd6, --prose → --bone, --line-soft → rgba(236,233,221,.3), remove the CRT scanline overlay and vignette, focus outline 3px.
- Vary the triad between projects within the ranges above. Never ship the same phosphor/signal/token triad twice in a row.
Typography
This is a three-font system: a pixel face for the machine's voice, a terminal face for everything the machine prints, and a grotesk for the humans who write about it.
Pixel (display): Silkscreen (Google Fonts, weights 400, 700). Used for the hero title, section h2s, cabinet marquees and plates, button labels, token names, footer "GAME OVER", nav brand.
Terminal (machine print): VT323 (Google Fonts, 400). Used for kickers, nav links, HUD, section notes, spec lines, statuses, ledger printout, controls lists, all canvas text (cabinet screens, game, flame).
Body (human prose): Space Grotesk (Google Fonts, weights 400, 500, 700). Used for ledes, lore paragraphs, play-copy prose.
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Silkscreen:wght@400;700&family=VT323&family=Space+Grotesk:wght@400;500;700&display=swap" rel="stylesheet" />
Scale (pixel display):
- Hero title:
clamp(52px, 11.5vw, 190px), weight 700, uppercase, line-height: .95, letter-spacing: .01em
- Footer "GAME OVER":
clamp(58px, 13vw, 210px), weight 700, line-height: .9
- Section h2:
clamp(38px, 6.4vw, 96px), weight 700, white-space: pre-line (lets data-text carry \n)
- Floor h2 (inside the pin):
clamp(30px, 4.6vw, 68px)
- Boot prompt:
clamp(22px, 3.4vw, 44px), blinking
- Sub-heads (play copy, token names):
clamp(18px, 2vw, 28px); marquee 13px; buttons 14px, letter-spacing: .06em
Terminal rhythm:
| Element |
Size |
Weight |
Spacing |
Case |
| Kicker |
clamp(18px, 1.6vw, 22px) |
400 |
.14em |
upper |
| Nav link / HUD / section note |
19px |
400 |
.10em–.14em |
upper |
| Hero sub |
clamp(19px, 2vw, 26px) |
400 |
.08em |
upper |
| Spec / status / hint |
16px–18px |
400 |
.12em–.20em |
upper |
| Ledger table |
21px (head 17px) |
400 |
.08em (head .2em) |
upper |
| Blockquote |
clamp(21px, 2vw, 26px) |
400 |
.04em |
— |
Body: 16px base, line-height: 1.6; ledes clamp(15px, 1.25vw, 18px), max-width: 56ch; lore 14.5px, line-height: 1.55.
Rules:
- Never introduce a fourth font. No icon fonts.
- Load only the weights listed — five variants total.
- Pixel and terminal faces are always uppercase; the grotesk never is.
- Pixel headline letter-spacing stays near zero (
0–.04em); terminal type always runs loose (.08em–.28em). The contrast is the texture.
- Canvas text uses the terminal face, set as
${Math.floor(cellHeight)}px "<terminal font>", monospace, and must wait for document.fonts.ready before its first measured paint (or repaint on document.fonts loadingdone).
- Vary the fonts between projects from the approved lists: pixel —
Silkscreen, Press Start 2P, Pixelify Sans, Tiny5, Jersey 10; terminal — VT323, Share Tech Mono, Space Mono, Martian Mono; body — Space Grotesk, Chakra Petch, Rubik, Sora. Press Start 2P runs wide: drop its display clamps by ~30%.
Motion
Motion in Arcadium is mechanical, stepped, and caused. Things switch on, snap, feed, flicker, and jolt — the way hardware does. Loops are stepped like hardware (steps(1), steps(2)), and nothing moves unless it is powered, played, or passed.
Libraries: GSAP 3.13 (core + ScrollTrigger), Lenis 1.3 for smooth scroll, Three.js 0.180 for the hero field. Register ScrollTrigger once per module that uses it.
Scroll plumbing (in App.tsx):
const lenis = new Lenis({ lerp: 0.11, wheelMultiplier: 1.05 });
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((t) => lenis.raf(t * 1000));
gsap.ticker.lagSmoothing(0);
Skip Lenis entirely under reduced motion. Until the coin lands, body.locked { overflow: hidden; height: 100vh } and lenis.stop(); after boot, lenis.start().
Signature feature 1: the boot gate (insert coin)
The first screen is a dead machine: a dim brand line (pixel, letter-spacing: .42em), a blinking INSERT COIN prompt, and a CSS-built coin door (148×210px, beveled dark gradient, two screw heads via ::before/::after, a lit vertical slot with a faint phosphor inner glow, a denomination label in token color, a reject button, and a return cup). Beneath it: CLICK THE DOOR OR PRESS [C].
- The door is
role="button" tabIndex={0} with aria-label="Insert coin to power on …", fires on click, Enter, Space, and a global C key while unbooted.
- Coin flight is a three-stage GSAP timeline measured from live
getBoundingClientRect()s so it lands in the slot at any viewport size: rise-and-arc toward the slot (0.34s power2.out, rotate to 420°), drop into the slot (0.30s power2.in, rotate to 720°), vanish (0.14s, scale .3, opacity 0, sfx.clunk() on start). The coin is a CSS radial-gradient disc in token color with its value printed on it.
- Power-on runs off one timeline: a full-screen
.crt-flash (#eafffd) opens from scaleY(.004) to 1 (0.22s power3.out) then fades (0.45s); the hero degausses from filter: hue-rotate(40deg) saturate(3); scale: 1.015 back to normal with elastic.out(1, .4) over 0.9s — the only elastic ease in the language; the field's uPower ramps 0 → .82 over 1.6s power2.inOut; the boot layer fades out (0.35s) and becomes visibility: hidden; at 0.55s the title and sub decode; at 1.25s the HUD and corners fade in with 0.08s stagger.
- Credits go
00 → 01 in the nav when the coin lands. Main content carries aria-hidden until booted.
Signature feature 2: the ASCII wave field (required main effect)
Use effects/AsciiWave.md from this skill folder: write its code block to src/effects/AsciiWave.tsx verbatim. It is a Three.js full-screen quad whose fragment shader tiles the canvas into cells, picks one of 11 hand-drawn 4×6 bitmap glyphs per cell from an expanding sine ring wave centred on the screen, and swells/whites-out the glyphs around the pointer.
- Props:
ink, lit, cell (8–60), rings (1–20), speed (0–20), swell, warp, radius (40–400), weight. Reference tuning: cell={26} rings={14} speed={6}, ink = phosphor, lit = #ffffff.
- Handle:
setPower(0..1) drives the uPower uniform. It starts at 0 (dark), is ramped by the coin timeline, and is flickered 1 → .15 → 1 (0.18s, yoyo) on "continue".
- Pointer is eased at
1 - exp(-dt * 8.5), hold at 1 - exp(-dt * 5); dt is clamped to 50ms.
- Built-in guardrails you must keep: DPR capped at 2; construction wrapped in
try/catch (fail silently — the hero stays a dark room with its title); ResizeObserver sizing; webglcontextlost (preventDefault + stop loop) / webglcontextrestored (restart); full disposal on unmount.
- The container has
role="img" and an aria-label describing the field.
- Vary between builds: ink hue (always the build's phosphor),
cell 20–30, rings 10–18, speed 4–8. Never change the glyph table, the ring-wave math, or the power ramp.
Signature feature 3: the hall of cabinets (pinned horizontal walk)
Scrolling walks sideways down a dark hall. The floor section pins for exactly the track's overflow.
const tween = gsap.to(track, {
x: () => -(track.scrollWidth - pin.clientWidth),
ease: "none",
scrollTrigger: { trigger: pin, start: "top top",
end: () => `+=${track.scrollWidth - pin.clientWidth}`,
pin: true, scrub: 0.6, invalidateOnRefresh: true },
});
- Progress-driven work runs on
gsap.ticker, reading tween.progress() and live rects — never in onUpdate — so nothing lags the scrub. Refresh ScrollTrigger ~400ms after mount for late fonts.
- Cabinet lighting: a cabinet lights once when its center enters
10%–95% of the viewport width: add .is-lit, play sfx.ignite(index, pan) where pan is its horizontal position (-.7..+.7) — each machine hums at its own pitch from where it stands.
- Lit sequence (CSS, stepped): screen
tube-on (0.85s steps(1) opacity stutter), marquee tube-on then a marquee-hum brightness wobble (3.4s steps(2) infinite), joystick idles (rotate(-4deg ↔ 5deg)), button 1 mashes (1.7s steps(2)), the STANDBY cover fades, light cone and floor reflection fade in. Unlit cabinets sit at saturate(.92).
- Pac-line progress: a row of ~26 token-color dots bottom-centre; a
ᗧ/ᗤ eater moves p * width and eats dots i < floor(p * N); two ghosts (ᗣ ᗥ ᗤ ᗦ, signal and phosphor) trail at -44px / -88px bobbing on their own sines. The last dot is a blinking power pellet; at p ≥ .99 the eaten dots flash phosphor/token (steps(2)) and sfx.pellet() fires; it re-arms below .9. Hidden below 760px.
- Depth: a perspective wire floor (
repeating-linear-gradient grid in phosphor at 13%, perspective(420px) rotateX(58deg), masked to fade in from the horizon), five dust motes (1px radial gradients), and a foreground silhouette layer (stools, a plant, a bin in near-black) parallaxing at x = p * -0.35 * innerWidth.
- Coin doors reject you: clicking a cabinet's coin door shakes it (
x: -3 → 0, 0.09s, 3 yoyo repeats) with sfx.reject().
- Hover: the tube glitches once (
clip-path slice + translateX ±3px + hue shift, 0.32s steps(2)) and the marquee brightens to 1.22. That is the entire hover vocabulary.
- An endcap closes the row in terminal type ("END OF ROW A ▖ ROW B IS IN THE BASEMENT").
- Reduced motion: no pin; the track becomes a native
overflow-x: auto scroller with height: auto, and every cabinet renders lit.
Signature feature 4: text-mode attract screens
Each cabinet screen is one <canvas> painting a hand-written attract loop as characters on a 34×25 cell grid at a chunky 11fps (skip frames closer than 90ms). Painters are pure functions (ctx, cw, ch, t) => void: fill the screen dark, then fillText glyphs at floor(x) * cw, floor(y) * ch. A scanline ::after (transparent 0 2px, rgba(0,0,0,.25) 3px) sits over every screen.
Write 4–7 painters per build, each a different genre, e.g.: a vector starfield warp, a maze chase from an ASCII map with blinking power pills, a wheel-road horizon with / \ edges and scrolling lane marks, a sonar sweep with fading blips, a dungeon room lit by a sine-flickered torch, an orbital league, a falling-block well, a lunar lander over ^ terrain, a lane of invaders stepping in unison. Paint only when the cabinet is powered (active), repaint on ResizeObserver, aria-hidden="true" on the canvas.
Signature feature 5: one playable machine
Exactly one section holds a genuinely playable game, rendered as text on canvas: internal resolution 920×520 (46×26 cells), scaled by CSS with image-rendering: pixelated, inside a CRT housing (bezel, inner vignette, heavier scanlines) and a status bar below: SCORE 000000 · SHEET n · BALLS ●●○ · STATE.
- Reference game is Breakout (Atari, 1976): bricks as
███ ▓▓▓ ▒▒▒ in signal/token/phosphor rows worth 70/40/20, angle-off-the-bat reflection (±π/3.2), speed creeping to a cap, escalating sheets with gaps. Vary the game per build: Breakout, Snake, a lane-crossing frogger, a fixed-shooter invaders, Pong against the house, an asteroid field. It must be finishable in under two minutes and fair.
- Game feel is mandatory: glyph-shatter particles (
▓▒░▪·) with gravity on hits, an 8-point phosphor trail behind the moving object, paddle/player squash on impact, a canvas shake on hits, a whole-cabinet GSAP jolt on life lost (6 random offsets, 40ms each), and a difference-blend invert flash on clearing a sheet.
- Controls: pointer/touch position, arrow keys, Space to serve/advance. Three lives, no continues.
dt clamped to 50ms.
- Initials entry: on game over, the classic three-letter entry on canvas (blinking cell cursor, A–Z/0–9, Backspace, Enter). Enter files the score to the ledger through
lib/hall-of-fame.ts (a tiny pub/sub), which inserts a live row at the top of the printout that flashes token color, then settles.
- The canvas is
tabIndex={0} role="application" with an aria-label describing every control, and a visible controls list in the copy column.
Signature feature 6: the furnace (required side effect)
Use effects/ascii-flame.md (a dependency-free <ascii-flame> web component: 2D stable-fluids fire with buoyancy, curl-noise turbulence, vorticity confinement, SOR pressure projection, RK2 advection, glyph-ramp rendering, embers, smoke, and two-pass bloom) and its React wrapper effects/AsciiFlame.md. Write their code blocks to src/effects/ascii-flame.js and src/effects/AsciiFlame.tsx verbatim.
- Mount it inside a furnace door: dark riveted frame (rivets via a masked radial-gradient border), a dashed token-color inner rule, a louvered vent strip below, and a hint line:
POINTER STIRS THE AIR · CLICK TO IGNITE · DRAG TO PAINT FIRE.
- Reference props:
preset="campfire" palette="fire" glyphs="flame" intensity={1.15} glow={0.75} embers={1.4} quality="medium" interactive. Font comes from CSS: ascii-flame { width:100%; height:100%; font-family: var(--font-term); font-size: 13px }. The view is height: clamp(340px, 52vh, 520px).
- Two machine buttons drive it through the handle: one throws fuel in (
ignite(0.3 + rand * 0.4, 0.7, 1.4) + sfx.clunk()), one gusts (gust(2.2) + sfx.gust()).
- The component already caps itself at 24fps and slows to 0.4× under reduced motion. Do not override that.
- Vary between builds: preset (
campfire, torch, inferno, burner), palette (fire, coal, gas), glyph set (flame, flow, blocks), and what is being burned — it must be the section's story, not decoration.
Entrances (the reveal vocabulary)
Every entrance is a ScrollTrigger with once: true, bound after boot by lib/reveals.ts via data-reveal:
crt — panels power on like a tube: from scaleY: .012; opacity: 0; filter: brightness(4) (origin top) to scaleY: 1 (0.24s power4.in), then opacity/brightness settle (0.3s), with a skewX: -1.4 → 0 shear. For the game housing, furnace door, token grid.
feed — paper feeds out of a slot: clip-path: inset(-2% 0 101% 0) → inset(-2% 0 -2% 0), 0.75s power3.inOut. For the ledger printout only.
wipe — copy sweeps in behind a cursor bar: clip-path: inset(-10% 101% -10% 0) → inset(-10% -2% -10% 0), 0.6s expo.out. For ledes, notes, quotes, lists.
type — text resolves character by character via scrambleTo, 460ms. For kickers and small heads.
- Section h2s carry
data-text and decode with scrambleTo(el, text, { duration: 800 }) at top 85%.
lib/scramble.ts resolves each character at a staggered settle time (i/len * 75% of duration + up to 25% random), filling unresolved cells from ▚▞▛▟#%&@$?!=+<>/\|~^*01, spaces never scramble. Text arrives by decoding — never by fading.
The ledger printout
A thermal-paper sheet (--paper gradient with ruled lines every 27px, perforated top and bottom edges from radial-gradient scallops, rotate(-0.6deg), deep drop shadow) in the terminal face. Rows print cell by cell: each cell decodes (420ms, step 24) at i * 55ms; every completed row gets an ink-land bleed (text-shadow blur → 0) and sfx.ding() (typewriter thock + bell). When the last row lands, a rubber stamp slams from scale 2.6, rotate -22° to scale 1, rotate -8° (0.28s power4.in), sfx.stamp() fires, and the whole sheet jolts (rotate -1.5 → -0.6, elastic.out(1,.32)). Pair it with a lede and a blockquote with a token-color left rule and an attributed floor-staff line.
Tokens (coin flip)
Three coins on perspective: 900px cards 420px tall. Front: a 170px coin (radial-gradient metal, repeating-conic-gradient milled edge, dashed inner ring, a pixel glyph and a stamped word), the tier name, the price in token color, and — FLIP THE COIN —. Back: perks in terminal type with signal » bullets and a machine button. The card flips rotateY(180deg) over 0.7s cubic-bezier(.2,.7,.2,1) on :hover and :focus-within. Metals vary per tier (brass/nickel/steel, or copper/silver/gold) via --coin-c1..c3. The button must go somewhere real (anchor, mailto:, or checkout URL supplied by the client) — never a dead button.
Game over (footer)
GAME OVER decodes at footer scale (1600ms, step 40). Below it, a machine button ▶ Insert coin to continue that scrolls to top (lenis.scrollTo(0, { duration: 1.2 })), collapses the CRT flash to a line and reopens it, flickers the wave's power, and re-decodes the hero title. Then a three-column terminal grid (address / hours / wire links + mailto:), and a dashed-rule base line of three uppercase items, one of them a dry joke.
The CRT room (global, always on)
Four fixed, pointer-events: none, aria-hidden layers above everything: scanlines (repeating-linear-gradient 3px period, multiply, opacity .7, z 90), vignette (radial to rgba(ink,.55), z 91), flicker (rgba(190,255,250,.015) on a 4.2s steps(2) opacity stutter, z 92), and the power flash (z 150).
Custom cursor
On (hover: hover) and (pointer: fine) only: a typed + in phosphor, mix-blend-mode: difference, following via gsap.quickTo (0.12s power2.out), centered with xPercent/yPercent: -50. Over anything interactive (a, button, [role=button], canvas, .coin-door, .token-card) it becomes a signal-color ▶ at 30px. Add body.custom-cursor { cursor: none } only after the listener attaches.
Sound
lib/sound.ts is a tiny WebAudio synth — every sound is a square, triangle, or sawtooth blip with exponential decay, optional pitch slide, and stereo pan. No audio files. The context is created lazily on the first user gesture (the coin), so nothing ever autoplays. Every interaction has its own sound: coin, clunk, power, select, move, brick/paddle/wall, lose, win, gust, ding, stamp, reject, ignite (per-cabinet pitch + pan), pellet. Keep gains between 0.02 and 0.16. A SND:ON / SND:OFF toggle lives in the nav with aria-pressed.
Motion rules:
- Approved eases:
power2–4 in/out for mechanical travel, expo.out for wipes, none for scrubbed scroll, steps(1)/steps(2) for every CSS loop (blinks, hums, flicker, mash). elastic.out exists in exactly two places: the degauss and the ledger jolt.
- No smooth infinite pulses, no floating/bobbing cards, no
ease-in-out breathing glows. A loop that isn't stepped is a defect.
- Every continuous animation must be caused by power (booted, lit), play (the game, the furnace), or passage (scroll). Nothing animates in a dead room.
- One loop per system: the GSAP ticker drives Lenis and the floor; each canvas owns its own throttled rAF; the flame manages itself. Never spawn a rAF per element.
- Respect
prefers-reduced-motion: reduce globally: no Lenis, no pin (native sideways scroller), the field power set instantly, headings and ledger printed finished, reveals skipped (content visible), CRT flicker, blinks, hums, mash, cursor bob, and pellet all animation: none, no cabinet shake, token flip without transition, flame capped at 24fps. The boot gate still works — the coin simply lands instantly.
Interaction & navigation
Focus & accessibility
:focus-visible { outline: 2px solid var(--phosphor); outline-offset: 3px } on every interactive element, including the coin door, canvases, token cards, and cabinet coin doors. Never outline: none without a replacement.
- A skip link (
Skip to the floor) is the first element in <body>, hidden off-screen until focused, and only reachable after boot (it targets main).
- The coin door, the game canvas, and every cabinet coin door (
role="button" tabIndex={0}, Enter/Space) are keyboard-operable.
- Global key handlers must ignore events whose target is an input, textarea, or the game canvas.
Navigation
Desktop:
- Fixed, full-width,
z-index: 100, padding: 14px var(--pad), 1px solid var(--line) bottom border, a dark top-to-bottom translucent gradient with backdrop-filter: blur(6px). Hidden until booted, then fades in (0.5s).
- Left: a blinking signal-color pip + the brand in pixel type. Center: 4–5 section links in terminal type,
--dim. Right: the SND:ON toggle and a dashed token-color CREDIT 01 counter.
- Hover / focus / active: the link inverts to a cursor bar —
background: var(--phosphor); color: var(--ink) — and a signal ▶ appears to its left. No underline, no glow.
- Active link is geometry-driven: in a rAF, the section whose rect spans
42% of viewport height is active (ScrollTrigger toggles misfire while the floor is pinned).
- Link clicks play
sfx.move() and smooth-scroll to the section. There is no CTA button in the nav.
- Vary nav link labels between projects. e.g.
The Floor / Attract Mode / The Ledger / The Furnace / Tokens, or Cabinets / House Machine / High Scores / Closing Time / Admission, or Row A / Free Play / Tonight's Sheet / Incinerator / Change Machine.
Mobile (below 900px):
- Links hide; a
MENU button in terminal type appears (aria-expanded, aria-controls).
- The menu is a full-screen
--ink overlay styled as a cabinet select screen: SELECT A ROOM heading in pixel type, links in pixel type at clamp(28px, 8vw, 44px), a blinking ▶ on the focused item, arrow keys move focus, Enter selects, Escape closes. Items decode in with scrambleTo at 60ms stagger.
- Opening locks scroll (
lenis.stop() + body.locked), moves focus to the first link, traps Tab inside, sets aria-hidden on main; closing restores focus to MENU.
Forms & contact
Arcadium has no contact form. Contact is a mailto: in the footer wire column, plus any real booking/ticket URL the client provides on the token buttons. If the client insists on a form, style it as a high-score entry: terminal-type fields on --panel with a blinking block caret, labels as ENTER NAME, submit as a machine button, success as a decoded ENTRY FILED.
Layout sections (approved roster)
The boot + hero is always first, the furnace is always present (it carries the required side effect), and game over is always last. Between hero and footer use 4–6 of these in any order. Never repeat a type.
- Boot + Hero — mandatory. Coin door gate over the dark field; after power: decoded title, one-line terminal sub (phosphor), two corner HUD blocks (location/status, hours/mode), and a bottom HUD strip with a stepped-bobbing
▼ … ▼ cue in the middle.
- The floor — the pinned hall: head overlaid top-left (kicker + h2 + note), 4–7 cabinets, each a CSS-built machine (trapezoid marquee
clip-path: polygon(6% 0, 94% 0, 100% 100%, 0 100%), body with 4:3 bezelled screen, control deck with joystick and two buttons, coin door with two slots and a price, base plinth, light cone, floor reflection) beside a nameplate (name + year, maker — spec line, lore, dashed status stamp). Pac-line progress, wire floor, parallax silhouettes, endcap.
- Attract mode / Play —
5fr / 4fr: the playable machine in its housing, then "house rules" prose and a two-column controls list (input in phosphor, effect in --dim, dashed separators).
- The ledger —
4fr / 5fr: lede + attributed blockquote, then the printout table (initials, cabinet, score right-aligned bold, note) with its stamp and the live row from the game.
- The furnace — mandatory.
4fr / 5fr: lede explaining what burns and why, two machine buttons (token and phosphor faces), then the furnace door.
- Tokens — three coin-flip cards for pricing or membership tiers.
- Prize counter — for merch/products: a glass display case (dark panel,
1px var(--line) shelves, faint phosphor top light) holding 4–8 CSS-or-glyph-built prizes; each prize has a ticket price in token color. On hover/focus a paper ticket strip unrolls from the counter slot (clip-path feed) showing the item's details. No product photos.
- Floor map — for locations/visit info: the venue plan drawn in box-drawing characters (
┌─┐│└┘╔═╗) inside a <pre> or canvas, zones labelled in terminal type; hovering/focusing a legend entry lights its zone in that zone's accent (steps(1) blink twice, then steady) and prints its note beside the map.
- Service bay — for process/about/team: a technician's repair log as a dashed-ruled table (date, board, fault, fix, initials) next to a circuit board drawn in text (traces
─┼┤, chips ▐██▌); hovering a log row lights the board component it touched.
- Game over — mandatory, last. Decoded
GAME OVER, the continue button, the three-column wire grid, the dashed base line.
Mandatory placement rules: the page has exactly one hero field, one playable game, and one furnace. The footer uses --ink with at most one faint signal-color radial from the top. Sections never alternate background panels.
Architecture & performance guardrails
The output shape is a Vite + React + TypeScript project that builds to a static dist/. No backend, no CMS, no dependencies beyond the five runtime packages below.
Canonical stack:
"dependencies": {
"gsap": "^3.13.0",
"lenis": "^1.3.11",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"three": "^0.180.0"
},
"devDependencies": {
"@types/react": "^19.1.13",
"@types/react-dom": "^19.1.9",
"@types/three": "^0.180.0",
"@vitejs/plugin-react": "^5.0.3",
"typescript": "^5.9.2",
"vite": "^7.1.6"
}
Scripts: "dev": "vite --port 5173 --strictPort", "build": "tsc --noEmit && vite build", "preview": "vite preview --port 4173 --strictPort". Requires Node 20+. tsconfig: target ES2022, module ESNext, moduleResolution bundler, jsx react-jsx, strict true, noEmit true, verbatimModuleSyntax true, include ["src"]. vite.config.ts is just defineConfig({ plugins: [react()] }).
Project manifest:
project/
├── index.html — author per build (head/meta rules below, fonts, #root, noscript)
├── package.json — verbatim deps/scripts above; name/description per build
├── tsconfig.json — verbatim settings above
├── vite.config.ts — verbatim
├── .gitignore — node_modules, dist, .DS_Store, *.local
├── README.md — author per build: what the site is, run commands, constraints honoured
├── public/favicon.svg — author per build (see meta)
└── src/
├── main.tsx — verbatim: createRoot + StrictMode + import "./styles.css"
├── App.tsx — author per build: boot state, credits, session, Lenis, cursor, CRT layers, heading decode, reboot
├── styles.css — author per build within the tokens and rules of this skill
├── effects/
│ ├── AsciiWave.tsx — VERBATIM code block from this skill's effects/AsciiWave.md
│ ├── ascii-flame.js — VERBATIM code block from this skill's effects/ascii-flame.md
│ └── AsciiFlame.tsx — VERBATIM code block from this skill's effects/AsciiFlame.md
├── lib/
│ ├── scramble.ts — VERBATIM from this skill's lib/ (glyph string may vary)
│ ├── reveals.ts — VERBATIM from this skill's lib/
│ ├── hall-of-fame.ts — VERBATIM from this skill's lib/
│ └── sound.ts — from this skill's lib/; extend or retune sfx per build
└── components/
├── Nav.tsx — author per build
├── Hero.tsx — author per build (coin door, power-on timeline, field)
├── Floor.tsx — author per build (cabinet data, pin, ticker)
├── CabinetScreen.tsx — author per build (4–7 painters)
├── <Game>.tsx — author per build (the one playable machine)
├── Ledger.tsx, Furnace.tsx, Tokens.tsx, Footer.tsx, … — author per build, one per chosen section
Construction method:
- Scaffold the folder by hand (do not run a generator), write
package.json, tsconfig.json, vite.config.ts, index.html, .gitignore.
- For each markdown file in this skill's
effects/ folder, write its single fenced code block, byte for byte, to the path named on its Write to: line (src/effects/…). Copy the files in lib/ into src/lib/ unchanged. If the skill folder is not available, stop and ask for it — do not rewrite the effects from memory.
- Write
styles.css tokens first, then shared parts (CRT room, cursor, kicker, .h-display, .btn-machine, section shell), then one block per section, then reduced-motion, contrast, and print blocks.
- Write components in page order; wire
App.tsx last.
- Resolve fonts from the approved lists and verify the Google Fonts URL returns
200.
npm install && npm run build must pass tsc with zero errors; then npm run dev and play through: coin → hero → walk the floor → play a round and file initials → watch the ledger → stoke the furnace
…(truncated)
1---2name: arcadium-skill3description: A dark coin-op arcade design language for arcades, game studios, retro events, and play-driven brands. The page arrives unpowered and boots when a coin goes in; a live WebGL ASCII ring-wave hero, a scroll-pinned hall of cabinets running text-mode attract loops, one genuinely playable ASCII game, a printed high-score ledger, and a fluid-simulated ASCII furnace. Vite + React 19 + TypeScript, animated with GSAP/ScrollTrigger and Lenis, with a WebAudio synth for every sound.4---56# Arcadium78You are designing in the Arcadium language. This is not a template to copy. It is a set of convictions, proportions, and taste lines that produce dark, tactile, playable pages. The output should feel unmistakably from the same language but never identical to a prior run.910## Core conviction1112Arcadium runs on cabinet black lit by phosphor. **This is absolute — dark only, no theme toggle.** The aesthetic lives at the intersection of the coin-op arcade and the conservation archive: tactile, nocturnal, wry. Every detail is deliberate: a coin that flies into a slot measured off live geometry, cabinets that hum at their own pitch when their tube lights, a score sheet stamped by hand before it goes into the fire. "The site is the room."1314The page is a machine, not a brochure about one. It arrives dead. Nothing happens until the visitor puts a coin in. After that, every section is something you walk past, press, play, or feed — never a picture of an arcade, always a working piece of one. If a section could be replaced by a screenshot without loss, it does not belong in this language.1516Map the client's content onto the machine:1718| Client content | Arcadium form |19|---|---|20| Brand arrival | Boot gate + powered hero |21| Projects, products, venues, games | Cabinets in the hall |22| Demo, trial, interactive proof | The one playable machine |23| Rankings, testimonials, changelog, records | The ledger printout |24| Closing time, archive, "what we let go" | The furnace |25| Pricing, membership, tickets | Tokens |26| Final CTA | Game over / continue |2728Two things you must never do:2930- **Do not reproduce the exact copy.** The brand name, tagline, cabinet names, lore, ledger rows, staff names, and section headers in any reference are specific to that page. Invent your own that match the voice.31- **Do not reproduce the exact layout.** A reference may run boot → hero → floor → play → ledger → furnace → tokens → game over. You may reorder the middle, add or remove from the approved roster, or vary the section count. Never repeat a section type within one page.3233---3435## Voice3637The tone is dry, affectionate, and procedural. It borrows from arcade attract screens, operator manuals, museum conservation records, and the backs of coin doors. The writer speaks like a night-shift floor manager who has repaired every machine twice: no nostalgia syrup, no "relive the golden age", no exclamation marks.3839**Rules:**4041- State facts like a service log. The fiction is strange enough: "The torch flicker is a failing capacitor we refuse to replace."42- No marketing superlatives ("epic", "ultimate", "next-level", "immersive"). The machine is running; it does not need selling.43- Paragraphs are two to three sentences. Cabinet lore is one to two. Notes and HUD lines are one.44- Interface text is UPPERCASE machine speak: `INSERT COIN`, `PRESS SPACE TO SERVE`, `FILE YOUR INITIALS`, `STANDBY`, `CREDIT 01`, `SND:ON`. Prose is sentence case.45- Section transitions read like signage, not chapters: "The floor", "Closing time", "The change machine". Never "01 / Section".46- Every object has a status line in conservation language: `ORIGINAL PCB — PLAYS`, `CHARACTER FAULT — PLAYS`, `DO NOT REPAIR — PLAYS`.47- One dry joke per section, maximum, and always in a footnote position (a status, a floor note, a footer line): "41 MACHINES SAVED · 3 ON LOAN · 1 HAUNTED".48- Names, brands, and titles should feel like cabinet marquees and small operators (e.g., "Starlancer", "Graveyard Shift", "Depth Charge", "Kestrel Electronics", "Nightowl Amusements").49- Approved vocabulary blends arcade and archive: coin, credit, token, cabinet, marquee, attract mode, sheet, initials, ledger, tube, phosphor, bezel, board, relay, free play, verified, conserved, floor.50- Real titles only when they are genuinely real and correctly dated (e.g., Breakout — Atari, 1976). Everything else is invented; never attribute invented machines to real manufacturers.5152---5354## Composition5556**Not a recipe, but tendencies:**5758- **Hero:** full `100vh`, `min-height: 640px`, `overflow: hidden`. The ASCII wave fills it edge to edge. The title sits dead center over a dark radial scrim (`rgba(ink,.88)` to ~30%, transparent by ~78%) so the field reads around it, not through it. Corner HUD text top-left/top-right, a three-part HUD strip along the bottom.59- **Separators:** a single `1px solid var(--line-soft)` top border per section. Dashed rules (`1px dashed`) for machine-printed things — footer base, ledger rows, token stubs. Nothing else divides sections.60- **Spacing:** one horizontal token, `--pad: clamp(20px, 5vw, 72px)`, and one vertical token, `--py: clamp(90px, 12vh, 160px)`. Content max width `1480px`.61- **Backgrounds:** always `--ink`. A section may carry **one** faint radial light at 5–9% opacity in its own source color (the play section in signal color from the top, the furnace in orange from below, the floor in phosphor from below) — as if lit by what is inside it. Never a decorative gradient.62- **Headers:** section head is a flex row: left column = blinking-LED kicker + display h2; right column = a `.section-note` in the terminal face, uppercase, right-aligned, `max-width: 300px`. Headings are named places, never numbered.63- **Content:** alternates between prose-forward (lede + quote) and machine-forward (cabinet, game, printout, furnace). Two-column grids are asymmetric: `4fr / 5fr` or `5fr / 4fr`, collapsing to one column below 980px.64- **Data display:** numbers are printed ink or live game state. Scores are zero-padded (`000420`), credits are two-digit (`CREDIT 01`), prices are terminal type in token color. A number only changes because the visitor did something.6566**Refuse (from the brief — these are hard bans):**6768- Numbers that count up on scroll or load.69- Brand-logo marquees / scrolling logo strips. A marquee in Arcadium is a cabinet nameplate, physically part of the cabinet.70- Standard IntersectionObserver fade-and-rise reveals. Every entrance is a machine behavior (see Motion).71- Indexing sections or adding page numbers for no reason (`01`, `02 / 06`, `§3`).72- A loading screen with a percentage. There is no loader. The machine is off until a coin goes in.73- Generic soft glows and hover lifts: `box-shadow: 0 0 40px accent` on hover, `translateY(-4px)` cards, opacity-fade hovers.7475**Refuse (to avoid the vibe-coded look):**7677- Rounded SaaS cards with drop shadows, pill badges, glassmorphism panels, gradient text, purple-to-blue gradients.78- Icon libraries and emoji. Glyphs are typed characters (`▶ ▼ ● ○ » ▖ ᗧ ᗣ █ ▓ ▒ ░`) or CSS-built hardware.79- Stock photography and illustrations. Every visual is CSS hardware, canvas text, or WebGL.80- Marketing CTAs ("Get Started", "Book a Demo") and social-proof strips.81- Lorem ipsum, fake testimonials in quote cards, "Trusted by" rows.8283---8485## Color8687Arcadium is a **single-theme system: dark only**. There is no theme toggle. The only light surface on the page is the ledger's thermal paper, which is an object in the room, not a theme.8889**Commitment level: arcade floor at 1 a.m.** The room is near-black with a blue cast. Light comes only from things that emit it — screens, marquees, LEDs, the furnace — in three saturated hardware colors. Warm bone is the only text white.9091**CSS custom properties — define these on `:root`:**9293```css94:root {95 /* Surfaces */96 --ink: #04050a; /* the room */97 --panel: #0a0c14; /* token faces, plates */98 --panel-2: #101322; /* reverse faces */99 /* Text */100 --bone: #ece9dd; /* headlines, primary text */101 --bone-dim: #b9bcae; /* ledes, lists */102 --prose: #a9ad9f; /* body prose, lore */103 --dim: #7b86a8; /* HUD, notes, meta */104 /* Hardware light — three roles */105 --phosphor: #00fff8; /* screens, active states, kickers */106 --signal: #ff2e7e; /* player/danger, primary buttons, ghosts */107 --token: #ffb000; /* coins, credits, scores, prices */108 /* Lines */109 --line: rgba(0, 255, 248, 0.16);110 --line-soft: rgba(236, 233, 221, 0.09);111 /* Thermal paper (ledger only) */112 --paper: #efe9d6;113 --paper-2: #e6dfc7;114 --paper-ink: #191a12;115 --paper-dim: #5c5844;116 --stamp: #b3302a;117 /* Structure */118 --font-pixel: "Silkscreen", "Courier New", monospace;119 --font-term: "VT323", "Courier New", monospace;120 --font-body: "Space Grotesk", system-ui, sans-serif;121 --pad: clamp(20px, 5vw, 72px);122 --py: clamp(90px, 12vh, 160px);123 --maxw: 1480px;124}125```126127Use CSS variables for every color in the stylesheet; mix with `color-mix(in srgb, var(--x) N%, transparent)` rather than writing new rgba literals. Canvas and WebGL are the exception — they need literal strings — so **read the tokens once at mount** with `getComputedStyle(document.documentElement).getPropertyValue("--phosphor")` and pass them in. Never invent a canvas color that is not a token or a darkened step of one.128129**Rules:**130131- **Accent range:** three hardware roles, each saturated and emissive. Phosphor is a cool screen color (cyan `#00fff8`, green `#39ff88`, ice `#6ae4ff`, aqua `#00e5c3`). Signal is a hot pink-red (`#ff2e7e`, `#ff3b5c`, `#ff4fd8`, `#ff2a4d`). Token is a metallic warm (`#ffb000`, `#ffc23d`, `#ff9f1c`, `#f5c542`). Forbidden: pastels, browns, purples as a role, any desaturated accent, and any fourth accent.132- **Where each role appears:** phosphor — kickers and their LED, nav active/hover fill, sound toggle on, hero wave ink, terminal hints, focus outline, `::selection`. Signal — primary machine button face, joystick ball, nav `▶` pointer, custom cursor hot state, game-over text, bullets (`»`). Token — coins, credits, scores, prices, lede `<em>`, blockquote rule, amber button face.133- **Per-cabinet accent:** each cabinet sets its own `--accent` inline from a curated set (the three roles plus one or two tints like `#00c9a8`, `#ff7b3a`, `#eafffd`). The marquee, year, status stamp, light cone, and floor reflection all derive from it.134- **Light is physical.** The only glows permitted come from emitters: a lit marquee's `box-shadow: 0 0 34px color-mix(accent 45%)`, the screen's light cone and floor reflection, LED kickers, the coin slot's inner glow. Nothing glows because it was hovered.135- **Chromatic misconvergence** on display type only: `text-shadow: 4px 0 0 rgba(signal,.28), -4px 0 0 rgba(phosphor,.28)` (3px on the footer). It is a CRT convergence fault, never a blur glow.136- `::selection { background: var(--phosphor); color: var(--ink); }`. Scrollbar: `scrollbar-color: var(--phosphor) var(--ink)`.137- All `color` / `background` transitions are `0.12s linear` — hardware switches, not fades.138- Respect `prefers-contrast: more`: `--dim` → `#b4bdd6`, `--prose` → `--bone`, `--line-soft` → `rgba(236,233,221,.3)`, remove the CRT scanline overlay and vignette, focus outline `3px`.139- **Vary the triad between projects** within the ranges above. Never ship the same phosphor/signal/token triad twice in a row.140141---142143## Typography144145This is a **three-font system**: a pixel face for the machine's voice, a terminal face for everything the machine prints, and a grotesk for the humans who write about it.146147**Pixel (display):** `Silkscreen` (Google Fonts, weights 400, 700). Used for the hero title, section h2s, cabinet marquees and plates, button labels, token names, footer "GAME OVER", nav brand.148149**Terminal (machine print):** `VT323` (Google Fonts, 400). Used for kickers, nav links, HUD, section notes, spec lines, statuses, ledger printout, controls lists, all canvas text (cabinet screens, game, flame).150151**Body (human prose):** `Space Grotesk` (Google Fonts, weights 400, 500, 700). Used for ledes, lore paragraphs, play-copy prose.152153```html154<link rel="preconnect" href="https://fonts.googleapis.com" />155<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />156<link href="https://fonts.googleapis.com/css2?family=Silkscreen:wght@400;700&family=VT323&family=Space+Grotesk:wght@400;500;700&display=swap" rel="stylesheet" />157```158159**Scale (pixel display):**160161- Hero title: `clamp(52px, 11.5vw, 190px)`, weight 700, uppercase, `line-height: .95`, `letter-spacing: .01em`162- Footer "GAME OVER": `clamp(58px, 13vw, 210px)`, weight 700, `line-height: .9`163- Section h2: `clamp(38px, 6.4vw, 96px)`, weight 700, `white-space: pre-line` (lets `data-text` carry `\n`)164- Floor h2 (inside the pin): `clamp(30px, 4.6vw, 68px)`165- Boot prompt: `clamp(22px, 3.4vw, 44px)`, blinking166- Sub-heads (play copy, token names): `clamp(18px, 2vw, 28px)`; marquee `13px`; buttons `14px`, `letter-spacing: .06em`167168**Terminal rhythm:**169170| Element | Size | Weight | Spacing | Case |171|---|---|---|---|---|172| Kicker | `clamp(18px, 1.6vw, 22px)` | 400 | `.14em` | upper |173| Nav link / HUD / section note | `19px` | 400 | `.10em`–`.14em` | upper |174| Hero sub | `clamp(19px, 2vw, 26px)` | 400 | `.08em` | upper |175| Spec / status / hint | `16px`–`18px` | 400 | `.12em`–`.20em` | upper |176| Ledger table | `21px` (head `17px`) | 400 | `.08em` (head `.2em`) | upper |177| Blockquote | `clamp(21px, 2vw, 26px)` | 400 | `.04em` | — |178179**Body:** `16px` base, `line-height: 1.6`; ledes `clamp(15px, 1.25vw, 18px)`, `max-width: 56ch`; lore `14.5px`, `line-height: 1.55`.180181**Rules:**182183- Never introduce a fourth font. No icon fonts.184- Load only the weights listed — five variants total.185- Pixel and terminal faces are always uppercase; the grotesk never is.186- Pixel headline letter-spacing stays near zero (`0`–`.04em`); terminal type always runs loose (`.08em`–`.28em`). The contrast is the texture.187- Canvas text uses the terminal face, set as `${Math.floor(cellHeight)}px "<terminal font>", monospace`, and must wait for `document.fonts.ready` before its first measured paint (or repaint on `document.fonts` `loadingdone`).188- **Vary the fonts between projects** from the approved lists: pixel — `Silkscreen`, `Press Start 2P`, `Pixelify Sans`, `Tiny5`, `Jersey 10`; terminal — `VT323`, `Share Tech Mono`, `Space Mono`, `Martian Mono`; body — `Space Grotesk`, `Chakra Petch`, `Rubik`, `Sora`. Press Start 2P runs wide: drop its display clamps by ~30%.189190---191192## Motion193194Motion in Arcadium is **mechanical, stepped, and caused**. Things switch on, snap, feed, flicker, and jolt — the way hardware does. Loops are stepped like hardware (`steps(1)`, `steps(2)`), and nothing moves unless it is powered, played, or passed.195196**Libraries:** GSAP 3.13 (core + `ScrollTrigger`), Lenis 1.3 for smooth scroll, Three.js 0.180 for the hero field. Register ScrollTrigger once per module that uses it.197198**Scroll plumbing (in `App.tsx`):**199200```ts201const lenis = new Lenis({ lerp: 0.11, wheelMultiplier: 1.05 });202lenis.on("scroll", ScrollTrigger.update);203gsap.ticker.add((t) => lenis.raf(t * 1000));204gsap.ticker.lagSmoothing(0);205```206207Skip Lenis entirely under reduced motion. Until the coin lands, `body.locked { overflow: hidden; height: 100vh }` and `lenis.stop()`; after boot, `lenis.start()`.208209### Signature feature 1: the boot gate (insert coin)210211The first screen is a dead machine: a dim brand line (pixel, `letter-spacing: .42em`), a blinking `INSERT COIN` prompt, and a CSS-built **coin door** (`148×210px`, beveled dark gradient, two screw heads via `::before/::after`, a lit vertical slot with a faint phosphor inner glow, a denomination label in token color, a reject button, and a return cup). Beneath it: `CLICK THE DOOR OR PRESS [C]`.212213- The door is `role="button" tabIndex={0}` with `aria-label="Insert coin to power on …"`, fires on click, Enter, Space, and a global `C` key while unbooted.214- **Coin flight** is a three-stage GSAP timeline measured from live `getBoundingClientRect()`s so it lands in the slot at any viewport size: rise-and-arc toward the slot (`0.34s power2.out`, rotate to 420°), drop into the slot (`0.30s power2.in`, rotate to 720°), vanish (`0.14s`, scale `.3`, opacity 0, `sfx.clunk()` on start). The coin is a CSS radial-gradient disc in token color with its value printed on it.215- **Power-on** runs off one timeline: a full-screen `.crt-flash` (`#eafffd`) opens from `scaleY(.004)` to `1` (`0.22s power3.out`) then fades (`0.45s`); the hero **degausses** from `filter: hue-rotate(40deg) saturate(3); scale: 1.015` back to normal with `elastic.out(1, .4)` over `0.9s` — the **only** elastic ease in the language; the field's `uPower` ramps `0 → .82` over `1.6s power2.inOut`; the boot layer fades out (`0.35s`) and becomes `visibility: hidden`; at `0.55s` the title and sub **decode**; at `1.25s` the HUD and corners fade in with `0.08s` stagger.216- Credits go `00 → 01` in the nav when the coin lands. Main content carries `aria-hidden` until booted.217218### Signature feature 2: the ASCII wave field (required main effect)219220Use `effects/AsciiWave.md` from this skill folder: write its code block to `src/effects/AsciiWave.tsx` **verbatim**. It is a Three.js full-screen quad whose fragment shader tiles the canvas into cells, picks one of 11 hand-drawn 4×6 bitmap glyphs per cell from an expanding sine ring wave centred on the screen, and swells/whites-out the glyphs around the pointer.221222- Props: `ink`, `lit`, `cell` (8–60), `rings` (1–20), `speed` (0–20), `swell`, `warp`, `radius` (40–400), `weight`. Reference tuning: `cell={26} rings={14} speed={6}`, `ink` = phosphor, `lit` = `#ffffff`.223- Handle: `setPower(0..1)` drives the `uPower` uniform. It starts at `0` (dark), is ramped by the coin timeline, and is flickered `1 → .15 → 1` (`0.18s`, yoyo) on "continue".224- Pointer is eased at `1 - exp(-dt * 8.5)`, hold at `1 - exp(-dt * 5)`; `dt` is clamped to `50ms`.225- Built-in guardrails you must keep: DPR capped at 2; construction wrapped in `try/catch` (fail silently — the hero stays a dark room with its title); `ResizeObserver` sizing; `webglcontextlost` (preventDefault + stop loop) / `webglcontextrestored` (restart); full disposal on unmount.226- The container has `role="img"` and an `aria-label` describing the field.227- **Vary between builds:** ink hue (always the build's phosphor), `cell` 20–30, `rings` 10–18, `speed` 4–8. Never change the glyph table, the ring-wave math, or the power ramp.228229### Signature feature 3: the hall of cabinets (pinned horizontal walk)230231Scrolling walks sideways down a dark hall. The floor section pins for exactly the track's overflow.232233```ts234const tween = gsap.to(track, {235 x: () => -(track.scrollWidth - pin.clientWidth),236 ease: "none",237 scrollTrigger: { trigger: pin, start: "top top",238 end: () => `+=${track.scrollWidth - pin.clientWidth}`,239 pin: true, scrub: 0.6, invalidateOnRefresh: true },240});241```242243- **Progress-driven work runs on `gsap.ticker`, reading `tween.progress()` and live rects** — never in `onUpdate` — so nothing lags the scrub. Refresh ScrollTrigger ~400ms after mount for late fonts.244- **Cabinet lighting:** a cabinet lights once when its center enters `10%–95%` of the viewport width: add `.is-lit`, play `sfx.ignite(index, pan)` where pan is its horizontal position (`-.7..+.7`) — each machine hums at its own pitch from where it stands.245- **Lit sequence (CSS, stepped):** screen `tube-on` (0.85s `steps(1)` opacity stutter), marquee `tube-on` then a `marquee-hum` brightness wobble (`3.4s steps(2) infinite`), joystick idles (`rotate(-4deg ↔ 5deg)`), button 1 mashes (`1.7s steps(2)`), the `STANDBY` cover fades, light cone and floor reflection fade in. Unlit cabinets sit at `saturate(.92)`.246- **Pac-line progress:** a row of ~26 token-color dots bottom-centre; a `ᗧ/ᗤ` eater moves `p * width` and eats dots `i < floor(p * N)`; two ghosts (`ᗣ ᗥ ᗤ ᗦ`, signal and phosphor) trail at `-44px` / `-88px` bobbing on their own sines. The last dot is a blinking power pellet; at `p ≥ .99` the eaten dots flash phosphor/token (`steps(2)`) and `sfx.pellet()` fires; it re-arms below `.9`. Hidden below 760px.247- **Depth:** a perspective wire floor (`repeating-linear-gradient` grid in phosphor at 13%, `perspective(420px) rotateX(58deg)`, masked to fade in from the horizon), five dust motes (1px radial gradients), and a foreground silhouette layer (stools, a plant, a bin in near-black) parallaxing at `x = p * -0.35 * innerWidth`.248- **Coin doors reject you:** clicking a cabinet's coin door shakes it (`x: -3 → 0`, `0.09s`, 3 yoyo repeats) with `sfx.reject()`.249- **Hover:** the tube glitches once (`clip-path` slice + `translateX ±3px` + hue shift, `0.32s steps(2)`) and the marquee brightens to `1.22`. That is the entire hover vocabulary.250- An endcap closes the row in terminal type ("END OF ROW A ▖ ROW B IS IN THE BASEMENT").251- **Reduced motion:** no pin; the track becomes a native `overflow-x: auto` scroller with `height: auto`, and every cabinet renders lit.252253### Signature feature 4: text-mode attract screens254255Each cabinet screen is one `<canvas>` painting a hand-written attract loop as **characters on a 34×25 cell grid at a chunky 11fps** (skip frames closer than 90ms). Painters are pure functions `(ctx, cw, ch, t) => void`: fill the screen dark, then `fillText` glyphs at `floor(x) * cw, floor(y) * ch`. A scanline `::after` (`transparent 0 2px, rgba(0,0,0,.25) 3px`) sits over every screen.256257Write 4–7 painters per build, each a different genre, e.g.: a vector starfield warp, a maze chase from an ASCII map with blinking power pills, a wheel-road horizon with `/ \` edges and scrolling lane marks, a sonar sweep with fading blips, a dungeon room lit by a sine-flickered torch, an orbital league, a falling-block well, a lunar lander over `^` terrain, a lane of invaders stepping in unison. Paint only when the cabinet is powered (`active`), repaint on `ResizeObserver`, `aria-hidden="true"` on the canvas.258259### Signature feature 5: one playable machine260261Exactly one section holds a genuinely playable game, rendered as text on canvas: internal resolution `920×520` (46×26 cells), scaled by CSS with `image-rendering: pixelated`, inside a CRT housing (bezel, inner vignette, heavier scanlines) and a status bar below: `SCORE 000000 · SHEET n · BALLS ●●○ · STATE`.262263- Reference game is Breakout (Atari, 1976): bricks as `███ ▓▓▓ ▒▒▒` in signal/token/phosphor rows worth 70/40/20, angle-off-the-bat reflection (`±π/3.2`), speed creeping to a cap, escalating sheets with gaps. **Vary the game per build**: Breakout, Snake, a lane-crossing frogger, a fixed-shooter invaders, Pong against the house, an asteroid field. It must be finishable in under two minutes and fair.264- Game feel is mandatory: glyph-shatter particles (`▓▒░▪·`) with gravity on hits, an 8-point phosphor trail behind the moving object, paddle/player squash on impact, a canvas shake on hits, a **whole-cabinet** GSAP jolt on life lost (6 random offsets, 40ms each), and a `difference`-blend invert flash on clearing a sheet.265- Controls: pointer/touch position, arrow keys, Space to serve/advance. Three lives, no continues. `dt` clamped to 50ms.266- **Initials entry:** on game over, the classic three-letter entry on canvas (blinking cell cursor, A–Z/0–9, Backspace, Enter). Enter **files the score to the ledger** through `lib/hall-of-fame.ts` (a tiny pub/sub), which inserts a live row at the top of the printout that flashes token color, then settles.267- The canvas is `tabIndex={0} role="application"` with an `aria-label` describing every control, and a visible controls list in the copy column.268269### Signature feature 6: the furnace (required side effect)270271Use `effects/ascii-flame.md` (a dependency-free `<ascii-flame>` web component: 2D stable-fluids fire with buoyancy, curl-noise turbulence, vorticity confinement, SOR pressure projection, RK2 advection, glyph-ramp rendering, embers, smoke, and two-pass bloom) and its React wrapper `effects/AsciiFlame.md`. Write their code blocks to `src/effects/ascii-flame.js` and `src/effects/AsciiFlame.tsx` **verbatim**.272273- Mount it inside a furnace door: dark riveted frame (rivets via a masked radial-gradient border), a dashed token-color inner rule, a louvered vent strip below, and a hint line: `POINTER STIRS THE AIR · CLICK TO IGNITE · DRAG TO PAINT FIRE`.274- Reference props: `preset="campfire" palette="fire" glyphs="flame" intensity={1.15} glow={0.75} embers={1.4} quality="medium" interactive`. Font comes from CSS: `ascii-flame { width:100%; height:100%; font-family: var(--font-term); font-size: 13px }`. The view is `height: clamp(340px, 52vh, 520px)`.275- Two machine buttons drive it through the handle: one throws fuel in (`ignite(0.3 + rand * 0.4, 0.7, 1.4)` + `sfx.clunk()`), one gusts (`gust(2.2)` + `sfx.gust()`).276- The component already caps itself at 24fps and slows to 0.4× under reduced motion. Do not override that.277- **Vary between builds:** preset (`campfire`, `torch`, `inferno`, `burner`), palette (`fire`, `coal`, `gas`), glyph set (`flame`, `flow`, `blocks`), and what is being burned — it must be the section's story, not decoration.278279### Entrances (the reveal vocabulary)280281Every entrance is a ScrollTrigger with `once: true`, bound after boot by `lib/reveals.ts` via `data-reveal`:282283- **`crt`** — panels power on like a tube: from `scaleY: .012; opacity: 0; filter: brightness(4)` (origin top) to `scaleY: 1` (`0.24s power4.in`), then opacity/brightness settle (`0.3s`), with a `skewX: -1.4 → 0` shear. For the game housing, furnace door, token grid.284- **`feed`** — paper feeds out of a slot: `clip-path: inset(-2% 0 101% 0) → inset(-2% 0 -2% 0)`, `0.75s power3.inOut`. For the ledger printout only.285- **`wipe`** — copy sweeps in behind a cursor bar: `clip-path: inset(-10% 101% -10% 0) → inset(-10% -2% -10% 0)`, `0.6s expo.out`. For ledes, notes, quotes, lists.286- **`type`** — text resolves character by character via `scrambleTo`, 460ms. For kickers and small heads.287- **Section h2s** carry `data-text` and **decode** with `scrambleTo(el, text, { duration: 800 })` at `top 85%`.288289`lib/scramble.ts` resolves each character at a staggered settle time (`i/len * 75%` of duration + up to 25% random), filling unresolved cells from `▚▞▛▟#%&@$?!=+<>/\|~^*01`, spaces never scramble. Text arrives by decoding — never by fading.290291### The ledger printout292293A thermal-paper sheet (`--paper` gradient with ruled lines every 27px, perforated top and bottom edges from radial-gradient scallops, `rotate(-0.6deg)`, deep drop shadow) in the terminal face. Rows print cell by cell: each cell decodes (`420ms`, step 24) at `i * 55ms`; every completed row gets an `ink-land` bleed (`text-shadow` blur → 0) and `sfx.ding()` (typewriter thock + bell). When the last row lands, a rubber **stamp** slams from `scale 2.6, rotate -22°` to `scale 1, rotate -8°` (`0.28s power4.in`), `sfx.stamp()` fires, and the whole sheet jolts (`rotate -1.5 → -0.6`, `elastic.out(1,.32)`). Pair it with a lede and a blockquote with a token-color left rule and an attributed floor-staff line.294295### Tokens (coin flip)296297Three coins on `perspective: 900px` cards `420px` tall. Front: a 170px coin (radial-gradient metal, `repeating-conic-gradient` milled edge, dashed inner ring, a pixel glyph and a stamped word), the tier name, the price in token color, and `— FLIP THE COIN —`. Back: perks in terminal type with signal `»` bullets and a machine button. The card flips `rotateY(180deg)` over `0.7s cubic-bezier(.2,.7,.2,1)` on `:hover` **and** `:focus-within`. Metals vary per tier (brass/nickel/steel, or copper/silver/gold) via `--coin-c1..c3`. The button must go somewhere real (anchor, `mailto:`, or checkout URL supplied by the client) — never a dead button.298299### Game over (footer)300301`GAME OVER` decodes at footer scale (`1600ms`, step 40). Below it, a machine button `▶ Insert coin to continue` that scrolls to top (`lenis.scrollTo(0, { duration: 1.2 })`), collapses the CRT flash to a line and reopens it, flickers the wave's power, and re-decodes the hero title. Then a three-column terminal grid (address / hours / wire links + `mailto:`), and a dashed-rule base line of three uppercase items, one of them a dry joke.302303### The CRT room (global, always on)304305Four fixed, `pointer-events: none`, `aria-hidden` layers above everything: scanlines (`repeating-linear-gradient` 3px period, `multiply`, opacity .7, z 90), vignette (radial to `rgba(ink,.55)`, z 91), flicker (`rgba(190,255,250,.015)` on a `4.2s steps(2)` opacity stutter, z 92), and the power flash (z 150).306307### Custom cursor308309On `(hover: hover) and (pointer: fine)` only: a typed `+` in phosphor, `mix-blend-mode: difference`, following via `gsap.quickTo` (`0.12s power2.out`), centered with `xPercent/yPercent: -50`. Over anything interactive (`a, button, [role=button], canvas, .coin-door, .token-card`) it becomes a signal-color `▶` at 30px. Add `body.custom-cursor { cursor: none }` only after the listener attaches.310311### Sound312313`lib/sound.ts` is a tiny WebAudio synth — every sound is a square, triangle, or sawtooth blip with exponential decay, optional pitch slide, and stereo pan. **No audio files.** The context is created lazily on the first user gesture (the coin), so nothing ever autoplays. Every interaction has its own sound: coin, clunk, power, select, move, brick/paddle/wall, lose, win, gust, ding, stamp, reject, ignite (per-cabinet pitch + pan), pellet. Keep gains between `0.02` and `0.16`. A `SND:ON / SND:OFF` toggle lives in the nav with `aria-pressed`.314315**Motion rules:**316317- Approved eases: `power2–4` in/out for mechanical travel, `expo.out` for wipes, `none` for scrubbed scroll, `steps(1)`/`steps(2)` for every CSS loop (blinks, hums, flicker, mash). `elastic.out` exists in exactly two places: the degauss and the ledger jolt.318- No smooth infinite pulses, no floating/bobbing cards, no `ease-in-out` breathing glows. A loop that isn't stepped is a defect.319- Every continuous animation must be caused by power (booted, lit), play (the game, the furnace), or passage (scroll). Nothing animates in a dead room.320- One loop per system: the GSAP ticker drives Lenis and the floor; each canvas owns its own throttled rAF; the flame manages itself. Never spawn a rAF per element.321- **Respect `prefers-reduced-motion: reduce` globally:** no Lenis, no pin (native sideways scroller), the field power set instantly, headings and ledger printed finished, reveals skipped (content visible), CRT flicker, blinks, hums, mash, cursor bob, and pellet all `animation: none`, no cabinet shake, token flip without transition, flame capped at 24fps. The boot gate still works — the coin simply lands instantly.322323---324325## Interaction & navigation326327### Focus & accessibility328329- `:focus-visible { outline: 2px solid var(--phosphor); outline-offset: 3px }` on every interactive element, including the coin door, canvases, token cards, and cabinet coin doors. Never `outline: none` without a replacement.330- A skip link (`Skip to the floor`) is the first element in `<body>`, hidden off-screen until focused, and only reachable after boot (it targets `main`).331- The coin door, the game canvas, and every cabinet coin door (`role="button" tabIndex={0}`, Enter/Space) are keyboard-operable.332- Global key handlers must ignore events whose target is an input, textarea, or the game canvas.333334### Navigation335336**Desktop:**337338- Fixed, full-width, `z-index: 100`, `padding: 14px var(--pad)`, `1px solid var(--line)` bottom border, a dark top-to-bottom translucent gradient with `backdrop-filter: blur(6px)`. Hidden until booted, then fades in (`0.5s`).339- Left: a blinking signal-color pip + the brand in pixel type. Center: 4–5 section links in terminal type, `--dim`. Right: the `SND:ON` toggle and a dashed token-color `CREDIT 01` counter.340- Hover / focus / active: the link **inverts to a cursor bar** — `background: var(--phosphor); color: var(--ink)` — and a signal `▶` appears to its left. No underline, no glow.341- Active link is geometry-driven: in a rAF, the section whose rect spans `42%` of viewport height is active (ScrollTrigger toggles misfire while the floor is pinned).342- Link clicks play `sfx.move()` and smooth-scroll to the section. There is no CTA button in the nav.343- **Vary nav link labels between projects.** e.g. `The Floor / Attract Mode / The Ledger / The Furnace / Tokens`, or `Cabinets / House Machine / High Scores / Closing Time / Admission`, or `Row A / Free Play / Tonight's Sheet / Incinerator / Change Machine`.344345**Mobile (below 900px):**346347- Links hide; a `MENU` button in terminal type appears (`aria-expanded`, `aria-controls`).348- The menu is a full-screen `--ink` overlay styled as a **cabinet select screen**: `SELECT A ROOM` heading in pixel type, links in pixel type at `clamp(28px, 8vw, 44px)`, a blinking `▶` on the focused item, arrow keys move focus, Enter selects, Escape closes. Items decode in with `scrambleTo` at `60ms` stagger.349- Opening locks scroll (`lenis.stop()` + `body.locked`), moves focus to the first link, traps Tab inside, sets `aria-hidden` on `main`; closing restores focus to `MENU`.350351### Forms & contact352353Arcadium has no contact form. Contact is a `mailto:` in the footer wire column, plus any real booking/ticket URL the client provides on the token buttons. If the client insists on a form, style it as a **high-score entry**: terminal-type fields on `--panel` with a blinking block caret, labels as `ENTER NAME`, submit as a machine button, success as a decoded `ENTRY FILED`.354355---356357## Layout sections (approved roster)358359The **boot + hero** is always first, the **furnace** is always present (it carries the required side effect), and **game over** is always last. Between hero and footer use 4–6 of these in any order. Never repeat a type.360361- **Boot + Hero** — mandatory. Coin door gate over the dark field; after power: decoded title, one-line terminal sub (phosphor), two corner HUD blocks (location/status, hours/mode), and a bottom HUD strip with a stepped-bobbing `▼ … ▼` cue in the middle.362- **The floor** — the pinned hall: head overlaid top-left (kicker + h2 + note), 4–7 cabinets, each a CSS-built machine (trapezoid marquee `clip-path: polygon(6% 0, 94% 0, 100% 100%, 0 100%)`, body with 4:3 bezelled screen, control deck with joystick and two buttons, coin door with two slots and a price, base plinth, light cone, floor reflection) beside a nameplate (name + year, maker — spec line, lore, dashed status stamp). Pac-line progress, wire floor, parallax silhouettes, endcap.363- **Attract mode / Play** — `5fr / 4fr`: the playable machine in its housing, then "house rules" prose and a two-column controls list (input in phosphor, effect in `--dim`, dashed separators).364- **The ledger** — `4fr / 5fr`: lede + attributed blockquote, then the printout table (initials, cabinet, score right-aligned bold, note) with its stamp and the live row from the game.365- **The furnace** — mandatory. `4fr / 5fr`: lede explaining what burns and why, two machine buttons (token and phosphor faces), then the furnace door.366- **Tokens** — three coin-flip cards for pricing or membership tiers.367- **Prize counter** — for merch/products: a glass display case (dark panel, `1px var(--line)` shelves, faint phosphor top light) holding 4–8 CSS-or-glyph-built prizes; each prize has a ticket price in token color. On hover/focus a paper ticket strip unrolls from the counter slot (`clip-path` feed) showing the item's details. No product photos.368- **Floor map** — for locations/visit info: the venue plan drawn in box-drawing characters (`┌─┐│└┘╔═╗`) inside a `<pre>` or canvas, zones labelled in terminal type; hovering/focusing a legend entry lights its zone in that zone's accent (`steps(1)` blink twice, then steady) and prints its note beside the map.369- **Service bay** — for process/about/team: a technician's repair log as a dashed-ruled table (date, board, fault, fix, initials) next to a circuit board drawn in text (traces `─┼┤`, chips `▐██▌`); hovering a log row lights the board component it touched.370- **Game over** — mandatory, last. Decoded `GAME OVER`, the continue button, the three-column wire grid, the dashed base line.371372**Mandatory placement rules:** the page has exactly one hero field, one playable game, and one furnace. The footer uses `--ink` with at most one faint signal-color radial from the top. Sections never alternate background panels.373374---375376## Architecture & performance guardrails377378The output shape is a **Vite + React + TypeScript project** that builds to a static `dist/`. No backend, no CMS, no dependencies beyond the five runtime packages below.379380**Canonical stack:**381382```json383"dependencies": {384 "gsap": "^3.13.0",385 "lenis": "^1.3.11",386 "react": "^19.1.1",387 "react-dom": "^19.1.1",388 "three": "^0.180.0"389},390"devDependencies": {391 "@types/react": "^19.1.13",392 "@types/react-dom": "^19.1.9",393 "@types/three": "^0.180.0",394 "@vitejs/plugin-react": "^5.0.3",395 "typescript": "^5.9.2",396 "vite": "^7.1.6"397}398```399400Scripts: `"dev": "vite --port 5173 --strictPort"`, `"build": "tsc --noEmit && vite build"`, `"preview": "vite preview --port 4173 --strictPort"`. Requires Node 20+. `tsconfig`: `target ES2022`, `module ESNext`, `moduleResolution bundler`, `jsx react-jsx`, `strict true`, `noEmit true`, `verbatimModuleSyntax true`, `include ["src"]`. `vite.config.ts` is just `defineConfig({ plugins: [react()] })`.401402**Project manifest:**403404```405project/406├── index.html — author per build (head/meta rules below, fonts, #root, noscript)407├── package.json — verbatim deps/scripts above; name/description per build408├── tsconfig.json — verbatim settings above409├── vite.config.ts — verbatim410├── .gitignore — node_modules, dist, .DS_Store, *.local411├── README.md — author per build: what the site is, run commands, constraints honoured412├── public/favicon.svg — author per build (see meta)413└── src/414 ├── main.tsx — verbatim: createRoot + StrictMode + import "./styles.css"415 ├── App.tsx — author per build: boot state, credits, session, Lenis, cursor, CRT layers, heading decode, reboot416 ├── styles.css — author per build within the tokens and rules of this skill417 ├── effects/418 │ ├── AsciiWave.tsx — VERBATIM code block from this skill's effects/AsciiWave.md419 │ ├── ascii-flame.js — VERBATIM code block from this skill's effects/ascii-flame.md420 │ └── AsciiFlame.tsx — VERBATIM code block from this skill's effects/AsciiFlame.md421 ├── lib/422 │ ├── scramble.ts — VERBATIM from this skill's lib/ (glyph string may vary)423 │ ├── reveals.ts — VERBATIM from this skill's lib/424 │ ├── hall-of-fame.ts — VERBATIM from this skill's lib/425 │ └── sound.ts — from this skill's lib/; extend or retune sfx per build426 └── components/427 ├── Nav.tsx — author per build428 ├── Hero.tsx — author per build (coin door, power-on timeline, field)429 ├── Floor.tsx — author per build (cabinet data, pin, ticker)430 ├── CabinetScreen.tsx — author per build (4–7 painters)431 ├── <Game>.tsx — author per build (the one playable machine)432 ├── Ledger.tsx, Furnace.tsx, Tokens.tsx, Footer.tsx, … — author per build, one per chosen section433```434435**Construction method:**4364371. Scaffold the folder by hand (do not run a generator), write `package.json`, `tsconfig.json`, `vite.config.ts`, `index.html`, `.gitignore`.4382. For each markdown file in this skill's `effects/` folder, write its single fenced code block, byte for byte, to the path named on its **Write to:** line (`src/effects/…`). Copy the files in `lib/` into `src/lib/` unchanged. If the skill folder is not available, stop and ask for it — do not rewrite the effects from memory.4393. Write `styles.css` tokens first, then shared parts (CRT room, cursor, kicker, `.h-display`, `.btn-machine`, section shell), then one block per section, then reduced-motion, contrast, and print blocks.4404. Write components in page order; wire `App.tsx` last.4415. Resolve fonts from the approved lists and verify the Google Fonts URL returns `200`.4426. `npm install && npm run build` must pass `tsc` with zero errors; then `npm run dev` and play through: coin → hero → walk the floor → play a round and file initials → watch the ledger → stoke the furnace443444…(truncated)