TypeScript 97.6%
SQL 1.4%
JavaScript 0.5%
1import * as React from "react";2import { ARCADE_GAMES, CRASH_GAMES, GAMES_BY_SLUG } from "@spinza/games/client";3import type { GameDefinition, SymbolShape, SymbolStyle, SymbolTier } from "@spinza/game-core/client";4import { cn } from "@/lib/utils";5import { SymbolSvg, svgArc, svgPolygon, svgStar } from "./symbol-svg";67/* ------------------------------------------------------------------------8 Spinza key art — 35 hand-composed posters, one scene per game.9 Slot posters are built from the game's own reel symbols (SymbolSvg mirrors10 the PixiJS renderer); the 10 Risk Games and 5 Beyond Slots originals have11 no reels, so their scenes are composed from primitives around each game's12 mechanic (rising multiplier ladder + the big-button verb as a solid pill).13 Pure inline SVG, deterministic (seeded PRNG only for star fields /14 particles), no images, filters used sparingly.15 ------------------------------------------------------------------------ */1617export type ArtVariant = "card" | "hero" | "banner" | "tile";1819export interface ArtPalette {20 primary: string;21 secondary: string;22 glow: string;23 bg: string;24}2526export interface GameArtProps {27 slug: string;28 name: string;29 palette: ArtPalette;30 backdrop?: string;31 particles?: string;32 variant?: ArtVariant;33 className?: string;34 /** Render the game name inside the artwork (default true). */35 showName?: boolean;36 /** Decorative by default; pass a label to expose the art to assistive tech. */37 label?: string;38}3940/** Ambience → particle system for the non-slot games (their definitions carry no `particles`). */41const AMBIENCE_PARTICLES: Record<string, string> = { lunar: "stars", ocean: "bubbles", space: "stars", heist: "coins", fire: "fire", void: "stars", reactor: "energy", obsidian: "dust", arcade: "confetti", quantum: "energy", jungle: "dust" };4243/** Presentation hints per game, derived from the game library (slots, risk games and originals). */44export const GAME_PRESENTATION: Record<string, { backdrop: string; particles: string }> = Object.fromEntries([45 ...[...GAMES_BY_SLUG.values()].map((g) => [g.slug, { backdrop: g.presentation.backdrop, particles: g.presentation.particles }] as const),46 ...[...CRASH_GAMES, ...ARCADE_GAMES].map((g) => [g.slug, { backdrop: g.presentation.scene, particles: AMBIENCE_PARTICLES[g.presentation.ambience] ?? "stars" }] as const),47]);4849/* ------------------------------------------------------------ seeded PRNG */5051function hashString(s: string): number {52 let h = 0x811c9dc5;53 for (let i = 0; i < s.length; i++) {54 h ^= s.charCodeAt(i);55 h = Math.imul(h, 0x01000193);56 }57 return h >>> 0;58}5960function mulberry32(seed: number): () => number {61 let a = seed >>> 0;62 return () => {63 a = (a + 0x6d2b79f5) >>> 0;64 let t = a;65 t = Math.imul(t ^ (t >>> 15), t | 1);66 t ^= t + Math.imul(t ^ (t >>> 7), t | 61);67 return ((t ^ (t >>> 14)) >>> 0) / 4294967296;68 };69}7071/* ------------------------------------------------------------------ stage */7273const FONT = "var(--font-geist-sans), Geist, Inter, ui-sans-serif, system-ui, sans-serif";74const MONO = "var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, monospace";75const f = (n: number) => Math.round(n * 100) / 100;7677interface Stage {78 W: number;79 H: number;80 v: ArtVariant;81 slug: string;82 /** Focal centre and unit radius of the composition. */83 cx: number;84 cy: number;85 R: number;86 p: ArtPalette;87 id: string;88 rnd: () => number;89 game?: GameDefinition;90 /** Reel symbol style by id (falls back to a palette gem). */91 sym: (id: string) => SymbolStyle;92 tier: (id: string) => SymbolTier;93 /** Tile variant: drop fine detail. */94 compact: boolean;95}9697const DIMS: Record<ArtVariant, { w: number; h: number }> = {98 card: { w: 360, h: 480 },99 tile: { w: 240, h: 240 },100 hero: { w: 1680, h: 720 },101 banner: { w: 1600, h: 600 },102};103104function focal(v: ArtVariant, W: number, H: number, showName: boolean): { cx: number; cy: number; R: number } {105 switch (v) {106 case "card":107 return { cx: W / 2, cy: showName ? H * 0.385 : H * 0.5, R: W * 0.405 };108 case "tile":109 return { cx: W / 2, cy: H / 2, R: W * 0.4 };110 case "hero":111 return { cx: W * 0.7, cy: H * 0.5, R: H * 0.42 };112 case "banner":113 return { cx: W * 0.72, cy: H * 0.5, R: H * 0.4 };114 }115}116117/* ---------------------------------------------------------------- helpers */118119/** Many small discs as a single path (star fields, snow, embers, bubbles). */120function dots(list: Array<[number, number, number]>): string {121 let d = "";122 for (const [x, y, r] of list) d += `M${f(x - r)} ${f(y)}a${f(r)} ${f(r)} 0 1 0 ${f(r * 2)} 0a${f(r)} ${f(r)} 0 1 0 ${f(-r * 2)} 0 `;123 return d;124}125126function scatterDots(s: Stage, n: number, box: [number, number, number, number], rMin: number, rMax: number): string {127 const list: Array<[number, number, number]> = [];128 for (let i = 0; i < n; i++) list.push([box[0] + s.rnd() * box[2], box[1] + s.rnd() * box[3], rMin + s.rnd() * (rMax - rMin)]);129 return dots(list);130}131132function Sym({ s, id, x, y, size, rotate, opacity, plate, halo }: { s: Stage; id: string; x: number; y: number; size: number; rotate?: number; opacity?: number; plate?: boolean; halo?: boolean }) {133 return <SymbolSvg style={s.sym(id)} tier={s.tier(id)} size={size} x={x} y={y} rotate={rotate} opacity={opacity} plate={plate} halo={halo} />;134}135136/** Small rounded label pill (feature tags, multiplier badges). */137function Pill({ x, y, text, color, bg = "#000", size = 12, w, ink = "#fff", opacity = 1 }: { x: number; y: number; text: string; color: string; bg?: string; size?: number; w?: number; ink?: string; opacity?: number }) {138 const width = w ?? text.length * size * 0.66 + size * 1.2;139 const h = size * 1.7;140 return (141 <g opacity={opacity}>142 <rect x={f(x - width / 2)} y={f(y - h / 2)} width={f(width)} height={f(h)} rx={f(h / 2)} fill={bg} fillOpacity={0.72} stroke={color} strokeOpacity={0.8} strokeWidth={1} />143 <text x={f(x)} y={f(y)} textAnchor="middle" dominantBaseline="central" fontSize={f(size)} fontWeight={700} fill={ink} style={{ fontFamily: FONT, letterSpacing: "0.08em" }}>144 {text}145 </text>146 </g>147 );148}149150function Small({ x, y, text, color, size = 12, anchor = "middle", weight = 600, tracking = "0.12em", opacity = 1, rotate }: { x: number; y: number; text: string; color: string; size?: number; anchor?: "start" | "middle" | "end"; weight?: number; tracking?: string; opacity?: number; rotate?: number }) {151 return (152 <text x={f(x)} y={f(y)} textAnchor={anchor} dominantBaseline="central" fontSize={f(size)} fontWeight={weight} fill={color} fillOpacity={opacity} style={{ fontFamily: FONT, letterSpacing: tracking }} transform={rotate ? `rotate(${rotate} ${f(x)} ${f(y)})` : undefined}>153 {text}154 </text>155 );156}157158function Radial({ id, color, o0 = 0.6, o1 = 0, mid }: { id: string; color: string; o0?: number; o1?: number; mid?: [number, string, number] }) {159 return (160 <radialGradient id={id} cx="0.5" cy="0.5" r="0.5">161 <stop offset="0" stopColor={color} stopOpacity={o0} />162 {mid ? <stop offset={mid[0]} stopColor={mid[1]} stopOpacity={mid[2]} /> : null}163 <stop offset="1" stopColor={color} stopOpacity={o1} />164 </radialGradient>165 );166}167168function Linear({ id, stops, dir = "v" }: { id: string; stops: Array<[number, string, number?]>; dir?: "v" | "h" | "d" }) {169 const c = dir === "v" ? { x1: 0, y1: 0, x2: 0, y2: 1 } : dir === "h" ? { x1: 0, y1: 0, x2: 1, y2: 0 } : { x1: 0, y1: 0, x2: 1, y2: 1 };170 return (171 <linearGradient id={id} {...c}>172 {stops.map(([o, col, op], i) => (173 <stop key={i} offset={o} stopColor={col} stopOpacity={op ?? 1} />174 ))}175 </linearGradient>176 );177}178179const PI = Math.PI;180181/* ======================================================================= */182/* SCENES */183/* ======================================================================= */184185/* 01 — NEON VAULT: cyan vault ring, Override Wild core, keycards scanning a grid */186function NeonVault(s: Stage) {187 const { W, H, cx, cy, R, id, p, compact } = s;188 const horizon = cy + R * 0.7;189 let grid = "";190 for (let i = -6; i <= 6; i++) grid += `M${f(cx)} ${f(horizon)}L${f(cx + i * W * 0.22)} ${f(H)} `;191 for (let k = 1; k <= 6; k++) {192 const t = k / 6;193 const y = horizon + (H - horizon) * t * t;194 grid += `M0 ${f(y)}H${f(W)} `;195 }196 let ticks = "";197 for (let i = 0; i < 12; i++) {198 const a = (i / 12) * PI * 2;199 ticks += `M${f(cx + Math.cos(a) * R * 0.86)} ${f(cy + Math.sin(a) * R * 0.86)}L${f(cx + Math.cos(a) * R * 1.02)} ${f(cy + Math.sin(a) * R * 1.02)} `;200 }201 const scanY = cy - R * 0.22;202 return (203 <g>204 <defs>205 <Radial id={`${id}-nv1`} color={p.secondary} o0={0.5} />206 <Radial id={`${id}-nv2`} color={p.primary} o0={0.55} />207 <Linear id={`${id}-nv3`} stops={[[0, p.secondary, 0], [0.5, p.secondary, 0.35], [1, p.secondary, 0]]} dir="h" />208 </defs>209 <circle cx={f(W * 0.85)} cy={f(H * 0.08)} r={f(R * 1.2)} fill={`url(#${id}-nv1)`} />210 <path d={grid} stroke={p.primary} strokeOpacity={0.22} strokeWidth={1.2} fill="none" />211 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.95)} fill={`url(#${id}-nv2)`} />212 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.94)} fill="none" stroke={p.primary} strokeOpacity={0.12} strokeWidth={f(R * 0.14)} />213 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.02)} fill="none" stroke={p.primary} strokeOpacity={0.5} strokeWidth={2} />214 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.72)} fill="none" stroke="#fff" strokeOpacity={0.12} strokeWidth={1.5} strokeDasharray="3 9" />215 <path d={ticks} stroke={p.primary} strokeOpacity={0.9} strokeWidth={3} strokeLinecap="round" fill="none" />216 <Sym s={s} id="W" x={cx} y={cy} size={R * 0.92} />217 {!compact ? (218 <>219 <Sym s={s} id="M1" x={cx - R * 0.8} y={cy + R * 0.42} size={R * 0.42} rotate={-14} />220 <Sym s={s} id="M1" x={cx + R * 0.82} y={cy - R * 0.55} size={R * 0.38} rotate={12} />221 <Sym s={s} id="S" x={cx + R * 0.78} y={cy + R * 0.5} size={R * 0.42} rotate={6} />222 <Sym s={s} id="V" x={cx - R * 0.78} y={cy - R * 0.5} size={R * 0.4} rotate={-8} />223 <rect x={f(cx - R * 1.1)} y={f(scanY - R * 0.05)} width={f(R * 2.2)} height={f(R * 0.1)} fill={`url(#${id}-nv3)`} />224 <line x1={f(cx - R * 1.1)} y1={f(scanY)} x2={f(cx + R * 1.1)} y2={f(scanY)} stroke={p.secondary} strokeWidth={1.5} strokeOpacity={0.9} />225 <Small x={cx} y={cy + R * 1.0} text="×2 ×3 ×5 ×8 ×12 ×20 ×30 ×50" color={p.primary} size={R * 0.075} tracking="0.18em" opacity={0.7} />226 </>227 ) : null}228 </g>229 );230}231232/* 02 — COSMIC COLLAPSE: symbols spiral into a black hole, gravity ladder ×1→×13 */233function CosmicCollapse(s: Stage) {234 const { W, H, cx, cy, R, id, p, compact } = s;235 const stars = scatterDots(s, 90, [0, 0, W, H], 0.5, 1.5);236 const bright = scatterDots(s, 10, [0, 0, W, H], 1.6, 2.6);237 const orbit = [238 { id: "P1", a: -0.55, d: 1.05, k: 0.5, rot: 28 },239 { id: "H1", a: 0.95, d: 0.98, k: 0.42, rot: -20 },240 { id: "H2", a: 2.35, d: 0.9, k: 0.36, rot: 35 },241 { id: "M1", a: 3.7, d: 0.78, k: 0.3, rot: -40 },242 { id: "M2", a: 4.9, d: 0.66, k: 0.24, rot: 15 },243 ];244 return (245 <g>246 <defs>247 <Radial id={`${id}-cc1`} color={p.primary} o0={0.4} />248 <Radial id={`${id}-cc2`} color={p.secondary} o0={0.3} />249 <Linear id={`${id}-cc3`} stops={[[0, p.secondary, 0], [0.3, "#fde68a", 0.95], [0.6, p.glow, 0.9], [1, p.primary, 0]]} dir="h" />250 <filter id={`${id}-blur`} x="-30%" y="-30%" width="160%" height="160%">251 <feGaussianBlur stdDeviation={f(R * 0.08)} />252 </filter>253 </defs>254 <ellipse cx={f(cx - R * 0.6)} cy={f(cy - R * 0.5)} rx={f(R * 1.3)} ry={f(R * 0.6)} fill={`url(#${id}-cc1)`} transform={`rotate(-25 ${f(cx)} ${f(cy)})`} />255 <ellipse cx={f(cx + R * 0.7)} cy={f(cy + R * 0.6)} rx={f(R * 1.1)} ry={f(R * 0.5)} fill={`url(#${id}-cc2)`} transform={`rotate(20 ${f(cx)} ${f(cy)})`} />256 <path d={stars} fill="#fff" fillOpacity={0.7} />257 <path d={bright} fill={p.glow} fillOpacity={0.9} />258 <g transform={`rotate(-22 ${f(cx)} ${f(cy)})`}>259 <ellipse cx={f(cx)} cy={f(cy)} rx={f(R * 1.05)} ry={f(R * 0.32)} fill="none" stroke={`url(#${id}-cc3)`} strokeWidth={f(R * 0.16)} filter={`url(#${id}-blur)`} />260 <ellipse cx={f(cx)} cy={f(cy)} rx={f(R * 1.05)} ry={f(R * 0.32)} fill="none" stroke={`url(#${id}-cc3)`} strokeWidth={f(R * 0.05)} />261 <ellipse cx={f(cx)} cy={f(cy)} rx={f(R * 0.74)} ry={f(R * 0.2)} fill="none" stroke="#fff" strokeOpacity={0.35} strokeWidth={1.5} strokeDasharray="2 7" />262 </g>263 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.4)} fill="#000" />264 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.41)} fill="none" stroke={p.glow} strokeWidth={2.5} strokeOpacity={0.9} />265 <Sym s={s} id="W" x={cx} y={cy} size={R * 0.95} plate={false} />266 {!compact267 ? orbit.map((o) => <Sym key={o.id} s={s} id={o.id} x={cx + Math.cos(o.a) * R * o.d} y={cy + Math.sin(o.a) * R * o.d * 0.72} size={R * o.k} rotate={o.rot} />)268 : null}269 {!compact ? (270 <text x={f(cx)} y={f(cy + R * 1.0)} textAnchor="middle" dominantBaseline="central" fontWeight={700} fill={p.primary} style={{ fontFamily: FONT, letterSpacing: "0.1em" }}>271 <tspan fontSize={f(R * 0.07)} fillOpacity={0.35}>×1 ×2 ×3 ×5 ×8 </tspan>272 <tspan fontSize={f(R * 0.11)} fill={p.glow} fillOpacity={0.9}>×13</tspan>273 </text>274 ) : null}275 </g>276 );277}278279/* 03 — GOLDEN EMPEROR: symmetrical imperial court, gold pillars, dragon pearls ×2 ×3 ×5 */280function GoldenEmperor(s: Stage) {281 const { H, cx, cy, R, id, p, compact } = s;282 const pw = R * 0.22;283 const px1 = cx - R * 1.02;284 const px2 = cx + R * 1.02 - pw;285 const top = cy - R * 1.15;286 const flecks = scatterDots(s, 30, [cx - R * 1.2, cy - R * 1.2, R * 2.4, R * 2.4], 0.8, 2.2);287 const pearls = [288 { x: cx - R * 0.66, m: "×2" },289 { x: cx, m: "×3" },290 { x: cx + R * 0.66, m: "×5" },291 ];292 const py = cy + R * 0.55;293 return (294 <g>295 <defs>296 <Radial id={`${id}-ge1`} color={p.secondary} o0={0.65} o1={0} />297 <Linear id={`${id}-ge2`} stops={[[0, "#3a2410"], [0.45, "#b8862e"], [0.55, "#f5d98a"], [1, "#4a2c10"]]} dir="h" />298 <Linear id={`${id}-ge3`} stops={[[0, "#5a0d16", 0.95], [1, "#2a0409", 0.98]]} />299 </defs>300 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.5)} fill={`url(#${id}-ge1)`} />301 <rect x={f(px1 + pw * 1.5)} y={f(top)} width={f(px2 - px1 - pw * 2)} height={f(H)} fill={`url(#${id}-ge3)`} />302 <rect x={f(px1 + pw * 1.5)} y={f(top)} width={f(px2 - px1 - pw * 2)} height={f(H)} fill="none" stroke={p.primary} strokeOpacity={0.35} strokeWidth={1.5} />303 <rect x={f(px1)} y={f(top)} width={f(pw)} height={f(H)} fill={`url(#${id}-ge2)`} />304 <rect x={f(px2)} y={f(top)} width={f(pw)} height={f(H)} fill={`url(#${id}-ge2)`} />305 <path d={`M${f(px1 - pw * 0.3)} ${f(top)}h${f(pw * 1.6)}v${f(pw * 0.35)}h${f(-pw * 1.6)}z M${f(px2 - pw * 0.3)} ${f(top)}h${f(pw * 1.6)}v${f(pw * 0.35)}h${f(-pw * 1.6)}z`} fill={p.primary} />306 <path d={`M${f(cx - R * 0.9)} ${f(cy - R * 0.98)}H${f(cx + R * 0.9)} M${f(cx - R * 0.9)} ${f(cy - R * 0.92)}H${f(cx + R * 0.9)}`} stroke={p.primary} strokeOpacity={0.7} strokeWidth={1.5} />307 <path d={flecks} fill={p.glow} fillOpacity={0.55} />308 <Sym s={s} id="P1" x={cx} y={cy - R * 0.3} size={R * 0.98} />309 {!compact ? (310 <>311 {pearls.map((o) => (312 <g key={o.m}>313 <Sym s={s} id="W" x={o.x} y={py} size={R * 0.5} />314 <Pill x={o.x} y={py + R * 0.36} text={o.m} color={p.primary} ink={p.glow} size={R * 0.085} />315 </g>316 ))}317 <Sym s={s} id="S" x={cx - R * 0.7} y={cy - R * 0.4} size={R * 0.4} />318 <Sym s={s} id="S" x={cx + R * 0.7} y={cy - R * 0.4} size={R * 0.4} />319 <path d={`M${f(cx)} ${f(cy - R * 0.83)}l${f(R * 0.03)} ${f(R * 0.03)}l${f(-R * 0.03)} ${f(R * 0.03)}l${f(-R * 0.03)} ${f(-R * 0.03)}z`} fill={p.primary} />320 </>321 ) : null}322 </g>323 );324}325326/* 04 — DIAMOND HEIST: laser-cut vault door, diamond coins with SC values, jackpot plaques */327function DiamondHeist(s: Stage) {328 const { W, H, cx, cy, R, id, p, compact } = s;329 let spokes = "";330 for (let i = 0; i < 24; i++) {331 const a = (i / 24) * PI * 2;332 spokes += `M${f(cx + Math.cos(a) * R * 0.58)} ${f(cy + Math.sin(a) * R * 0.58)}L${f(cx + Math.cos(a) * R * 0.98)} ${f(cy + Math.sin(a) * R * 0.98)} `;333 }334 const rivets: Array<[number, number, number]> = [];335 for (let i = 0; i < 12; i++) {336 const a = (i / 12) * PI * 2 + PI / 12;337 rivets.push([cx + Math.cos(a) * R * 1.06, cy + Math.sin(a) * R * 1.06, R * 0.025]);338 }339 const sparkle = (x: number, y: number, r: number) => `M${f(x)} ${f(y - r)}Q${f(x)} ${f(y)} ${f(x + r)} ${f(y)}Q${f(x)} ${f(y)} ${f(x)} ${f(y + r)}Q${f(x)} ${f(y)} ${f(x - r)} ${f(y)}Q${f(x)} ${f(y)} ${f(x)} ${f(y - r)}Z`;340 let sparkles = "";341 for (let i = 0; i < 14; i++) sparkles += sparkle(s.rnd() * W, s.rnd() * H, R * (0.03 + s.rnd() * 0.05));342 const lasers = `M${f(cx - R * 1.4)} ${f(cy - R * 1.3)}L${f(cx + R * 1.4)} ${f(cy + R * 0.2)} M${f(cx - R * 1.4)} ${f(cy + R * 0.9)}L${f(cx + R * 1.4)} ${f(cy - R * 1.1)}`;343 const tiers = ["MINI", "MINOR", "MAJOR", "GRAND"];344 const coins = [345 { x: cx - R * 0.72, y: cy + R * 0.5, v: "5×" },346 { x: cx, y: cy + R * 0.66, v: "20×" },347 { x: cx + R * 0.72, y: cy + R * 0.5, v: "10×" },348 ];349 return (350 <g>351 <defs>352 <pattern id={`${id}-dh0`} width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">353 <line x1="0" y1="0" x2="0" y2="10" stroke="#fff" strokeOpacity="0.06" strokeWidth="1" />354 </pattern>355 <Radial id={`${id}-dh1`} color="#2a3038" o0={1} o1={0} mid={[0.7, "#14181e", 1]} />356 <Radial id={`${id}-dh2`} color={p.glow} o0={0.5} />357 </defs>358 <rect width={W} height={H} fill={`url(#${id}-dh0)`} />359 <path d={lasers} stroke="#ff3b5c" strokeOpacity={0.35} strokeWidth={5} strokeLinecap="round" />360 <path d={lasers} stroke="#ff8fa3" strokeOpacity={0.9} strokeWidth={1.2} />361 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.1)} fill={`url(#${id}-dh1)`} stroke={p.secondary} strokeOpacity={0.5} strokeWidth={2} />362 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.0)} fill="none" stroke="#fff" strokeOpacity={0.1} strokeWidth={1} />363 <path d={spokes} stroke={p.primary} strokeOpacity={0.55} strokeWidth={1.5} fill="none" />364 <path d={dots(rivets)} fill={p.secondary} fillOpacity={0.8} />365 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.56)} fill="#0a0d12" stroke={p.primary} strokeWidth={2.5} strokeOpacity={0.95} />366 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.56)} fill={`url(#${id}-dh2)`} />367 <Sym s={s} id="D" x={cx} y={cy} size={R * 0.9} plate={false} />368 {!compact ? (369 <>370 {coins.map((c) => (371 <g key={c.v}>372 <Sym s={s} id="D" x={c.x} y={c.y} size={R * 0.4} />373 <Pill x={c.x} y={c.y + R * 0.28} text={c.v} color={p.primary} ink={p.glow} size={R * 0.08} />374 </g>375 ))}376 {tiers.map((t, i) => {377 const y = cy - R * 0.62 + i * R * 0.3;378 const grand = i === 3;379 return <Pill key={t} x={cx + R * 1.02} y={y} text={t} color={grand ? "#ffd166" : p.secondary} ink={grand ? "#ffd166" : "#dfe4ea"} bg={grand ? "#2a2008" : "#000"} size={R * 0.075} w={R * 0.5} />;380 })}381 <Sym s={s} id="W" x={cx - R * 1.0} y={cy - R * 0.62} size={R * 0.5} rotate={-30} />382 <Sym s={s} id="P1" x={cx + R * 1.0} y={cy - R * 1.0} size={R * 0.38} />383 </>384 ) : null}385 <path d={sparkles} fill="#fff" fillOpacity={0.85} />386 </g>387 );388}389390/* 05 — ARCTIC FORTUNE: aurora over a ridge, frozen sticky wilds in ice blocks, snowfall */391function ArcticFortune(s: Stage) {392 const { W, H, cx, cy, R, id, p, compact } = s;393 const stars = scatterDots(s, 40, [0, 0, W, H * 0.5], 0.5, 1.3);394 const snow = scatterDots(s, 45, [0, 0, W, H], 1, 3.2);395 const band = (y: number, amp: number) => `M${f(-W * 0.1)} ${f(y)} C${f(W * 0.2)} ${f(y - amp)} ${f(W * 0.4)} ${f(y + amp)} ${f(W * 0.6)} ${f(y)} S${f(W * 0.9)} ${f(y - amp)} ${f(W * 1.1)} ${f(y + amp * 0.4)}`;396 const ridgeY = cy + R * 0.95;397 const ridge = `M0 ${f(H)} L0 ${f(ridgeY + R * 0.1)} L${f(W * 0.18)} ${f(ridgeY - R * 0.25)} L${f(W * 0.32)} ${f(ridgeY)} L${f(W * 0.5)} ${f(ridgeY - R * 0.45)} L${f(W * 0.66)} ${f(ridgeY - R * 0.05)} L${f(W * 0.82)} ${f(ridgeY - R * 0.3)} L${f(W)} ${f(ridgeY + R * 0.05)} L${f(W)} ${f(H)}Z`;398 const blocks = [399 { x: cx - R * 0.72, y: cy + R * 0.35 },400 { x: cx - R * 0.1, y: cy + R * 0.05 },401 { x: cx + R * 0.55, y: cy - R * 0.25 },402 ];403 const bs = R * 0.56;404 return (405 <g>406 <defs>407 <Linear id={`${id}-af1`} stops={[[0, "#0b2a3a"], [0.6, p.bg], [1, "#02060d"]]} />408 <filter id={`${id}-blur`} x="-20%" y="-50%" width="140%" height="200%">409 <feGaussianBlur stdDeviation={f(R * 0.09)} />410 </filter>411 </defs>412 <rect width={W} height={H} fill={`url(#${id}-af1)`} />413 <path d={stars} fill="#fff" fillOpacity={0.8} />414 <path d={band(cy - R * 0.95, R * 0.25)} stroke="#34d399" strokeOpacity={0.55} strokeWidth={f(R * 0.22)} fill="none" strokeLinecap="round" filter={`url(#${id}-blur)`} />415 <path d={band(cy - R * 0.62, R * 0.2)} stroke={p.primary} strokeOpacity={0.5} strokeWidth={f(R * 0.2)} fill="none" strokeLinecap="round" filter={`url(#${id}-blur)`} />416 <path d={band(cy - R * 0.32, R * 0.16)} stroke={p.secondary} strokeOpacity={0.4} strokeWidth={f(R * 0.16)} fill="none" strokeLinecap="round" filter={`url(#${id}-blur)`} />417 <path d={ridge} fill="#050b16" stroke="#bfe9ff" strokeOpacity={0.35} strokeWidth={1.5} />418 {!compact ? (419 <>420 {blocks.map((b, i) => (421 <g key={i}>422 <rect x={f(b.x - bs / 2)} y={f(b.y - bs / 2)} width={f(bs)} height={f(bs)} rx={f(bs * 0.12)} fill="#bfe9ff" fillOpacity={0.14} stroke="#e8f8ff" strokeOpacity={0.75} strokeWidth={2} />423 <Sym s={s} id="W" x={b.x} y={b.y} size={bs * 0.92} plate={false} />424 </g>425 ))}426 <Sym s={s} id="P1" x={cx + R * 0.72} y={cy + R * 0.5} size={R * 0.62} />427 <Sym s={s} id="H1" x={cx - R * 0.78} y={cy - R * 0.45} size={R * 0.44} />428 <Sym s={s} id="S" x={cx + R * 0.1} y={cy - R * 0.7} size={R * 0.38} />429 <Small x={cx} y={cy + R * 1.0} text="×1 ×2 ×3 ×4 ×5" color="#e8f8ff" size={R * 0.08} tracking="0.16em" opacity={0.75} />430 </>431 ) : (432 <Sym s={s} id="W" x={cx} y={cy} size={R * 0.9} />433 )}434 <path d={snow} fill="#fff" fillOpacity={0.75} />435 </g>436 );437}438439/* 06 — INFERNO REELS: cracked lava floor, Magma Core shockwave, embers */440function InfernoReels(s: Stage) {441 const { W, H, cx, cy, R, id, p, compact } = s;442 const floorY = cy + R * 0.75;443 let cracks = "";444 for (let i = 0; i < 6; i++) {445 let x = W * (0.05 + i * 0.18) + s.rnd() * W * 0.08;446 let y = H;447 cracks += `M${f(x)} ${f(y)}`;448 while (y > floorY + R * 0.05) {449 x += (s.rnd() - 0.5) * R * 0.3;450 y -= R * (0.1 + s.rnd() * 0.15);451 cracks += `L${f(x)} ${f(y)}`;452 }453 cracks += " ";454 }455 const embers = scatterDots(s, 40, [cx - R * 1.3, cy - R * 1.3, R * 2.6, R * 2.4], 0.8, 2.6);456 const embers2 = scatterDots(s, 16, [cx - R * 1.2, cy - R * 1.2, R * 2.4, R * 2], 1.5, 3.5);457 const burst = svgStar(cx, cy, R * 0.98, R * 0.62, 14);458 return (459 <g>460 <defs>461 <Linear id={`${id}-ir1`} stops={[[0, p.primary, 0], [1, p.primary, 0.55]]} />462 <Radial id={`${id}-ir2`} color={p.glow} o0={0.75} mid={[0.45, p.primary, 0.35]} />463 <filter id={`${id}-tex`} x="0" y="0" width="100%" height="100%">464 <feTurbulence type="fractalNoise" baseFrequency="0.012 0.03" numOctaves="2" seed="7" />465 <feColorMatrix values="0 0 0 0 1 0 0 0 0 0.35 0 0 0 0 0.05 0 0 0 0.55 0" />466 </filter>467 </defs>468 <rect x={0} y={f(floorY - R * 0.4)} width={W} height={f(H - floorY + R * 0.4)} fill={`url(#${id}-ir1)`} />469 <rect x={0} y={f(floorY)} width={W} height={f(H - floorY)} fill="#160604" />470 <rect x={0} y={f(floorY)} width={W} height={f(H - floorY)} filter={`url(#${id}-tex)`} opacity={0.5} />471 <path d={cracks} stroke={p.glow} strokeOpacity={0.5} strokeWidth={7} strokeLinecap="round" strokeLinejoin="round" fill="none" />472 <path d={cracks} stroke="#ffd166" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" fill="none" />473 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.3)} fill={`url(#${id}-ir2)`} />474 <polygon points={burst} fill={p.glow} fillOpacity={0.28} />475 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.7)} fill="none" stroke={p.secondary} strokeOpacity={0.85} strokeWidth={3} />476 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.9)} fill="none" stroke={p.primary} strokeOpacity={0.5} strokeWidth={2} />477 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.12)} fill="none" stroke={p.primary} strokeOpacity={0.22} strokeWidth={1.5} />478 <Sym s={s} id="W" x={cx} y={cy} size={R * 0.95} plate={false} />479 {!compact ? (480 <>481 <Sym s={s} id="P1" x={cx - R * 0.92} y={cy - R * 0.72} size={R * 0.5} rotate={-18} />482 <Sym s={s} id="S" x={cx + R * 0.92} y={cy - R * 0.75} size={R * 0.46} rotate={16} />483 <Sym s={s} id="H1" x={cx - R * 0.95} y={cy + R * 0.55} size={R * 0.42} rotate={22} />484 <Sym s={s} id="H2" x={cx + R * 0.95} y={cy + R * 0.55} size={R * 0.42} rotate={-20} />485 <Small x={cx} y={cy + R * 1.0} text="×2 · ×3 · ×5 · ×10" color="#ffd166" size={R * 0.085} tracking="0.16em" weight={700} opacity={0.9} />486 </>487 ) : null}488 <path d={embers} fill={p.glow} fillOpacity={0.8} />489 <path d={embers2} fill="#ffd166" fillOpacity={0.85} />490 </g>491 );492}493494/* 07 — QUANTUM JACKPOT: a processor splitting into ghost copies ×2 ×3 ×4 over circuit traces */495function QuantumJackpot(s: Stage) {496 const { W, H, cx, cy, R, id, p, compact } = s;497 const step = R * 0.22;498 let t1 = "";499 let t2 = "";500 const pads: Array<[number, number, number]> = [];501 for (let i = 0; i < 14; i++) {502 let x = Math.round((s.rnd() * W) / step) * step;503 let y = Math.round((s.rnd() * H) / step) * step;504 let d = `M${f(x)} ${f(y)}`;505 for (let k = 0; k < 3; k++) {506 if (s.rnd() > 0.5) x += (s.rnd() > 0.5 ? 1 : -1) * step * (1 + Math.floor(s.rnd() * 3));507 else y += (s.rnd() > 0.5 ? 1 : -1) * step * (1 + Math.floor(s.rnd() * 3));508 d += `L${f(x)} ${f(y)}`;509 }510 if (i % 3 === 0) t2 += d + " ";511 else t1 += d + " ";512 pads.push([x, y, 3]);513 }514 const wave = `M0 ${f(cy + R * 1.05)} ${Array.from({ length: 12 }, (_, i) => `Q${f((W / 12) * (i + 0.5))} ${f(cy + R * (1.05 + (i % 2 ? 0.14 : -0.14)))} ${f((W / 12) * (i + 1))} ${f(cy + R * 1.05)}`).join(" ")}`;515 const gx = cx - R * 0.42;516 return (517 <g>518 <defs>519 <Radial id={`${id}-qj1`} color={p.secondary} o0={0.35} />520 <Radial id={`${id}-qj2`} color={p.primary} o0={0.35} />521 </defs>522 <circle cx={f(cx + R * 0.6)} cy={f(cy - R * 0.5)} r={f(R * 1.3)} fill={`url(#${id}-qj1)`} />523 <circle cx={f(cx - R * 0.8)} cy={f(cy + R * 0.7)} r={f(R * 1.1)} fill={`url(#${id}-qj2)`} />524 <path d={t1} stroke={p.primary} strokeOpacity={0.3} strokeWidth={1.6} fill="none" strokeLinejoin="round" />525 <path d={t2} stroke={p.secondary} strokeOpacity={0.35} strokeWidth={1.6} fill="none" strokeLinejoin="round" />526 <path d={dots(pads)} fill={p.bg} stroke={p.primary} strokeOpacity={0.6} strokeWidth={1.5} />527 <path d={wave} stroke={p.primary} strokeOpacity={0.55} strokeWidth={1.5} fill="none" />528 {!compact ? (529 <>530 <Sym s={s} id="P1" x={gx + R * 1.15} y={cy - R * 0.12} size={R * 0.7} opacity={0.22} />531 <Sym s={s} id="P1" x={gx + R * 0.78} y={cy - R * 0.08} size={R * 0.74} opacity={0.42} />532 <Sym s={s} id="P1" x={gx + R * 0.4} y={cy - R * 0.04} size={R * 0.78} opacity={0.7} />533 </>534 ) : null}535 <Sym s={s} id="P1" x={gx} y={cy} size={R * 0.84} />536 {!compact ? (537 <>538 <Pill x={gx + R * 0.4} y={cy + R * 0.55} text="×2" color={p.primary} ink={p.glow} size={R * 0.085} />539 <Pill x={gx + R * 0.78} y={cy + R * 0.55} text="×3" color={p.primary} ink={p.glow} size={R * 0.085} opacity={0.75} />540 <Pill x={gx + R * 1.15} y={cy + R * 0.55} text="×4" color={p.primary} ink={p.glow} size={R * 0.085} opacity={0.5} />541 <Sym s={s} id="W" x={cx - R * 0.85} y={cy - R * 0.85} size={R * 0.48} />542 <Sym s={s} id="H1" x={cx + R * 0.85} y={cy - R * 0.9} size={R * 0.44} />543 <Sym s={s} id="S" x={cx - R * 0.9} y={cy + R * 0.7} size={R * 0.4} />544 <Small x={cx} y={cy + R * 1.0} text="MINI 20× · MINOR 50× · MAJOR 200× · GRAND 1,000×" color={p.glow} size={R * 0.062} tracking="0.1em" opacity={0.8} />545 </>546 ) : null}547 </g>548 );549}550551/* 08 — MIDNIGHT TOKYO: rain-wet skyline, neon signage, stacked wild column */552function MidnightTokyo(s: Stage) {553 const { W, H, cx, cy, R, id, p, compact } = s;554 const horizon = cy + R * 0.85;555 let sky = `M0 ${f(horizon)}`;556 const wins: Array<[number, number, number]> = [];557 const wins2: Array<[number, number, number]> = [];558 let x = 0;559 while (x < W) {560 const bw = W * (0.04 + s.rnd() * 0.09);561 const bh = R * (0.4 + s.rnd() * 1.4);562 sky += `L${f(x)} ${f(horizon - bh)}L${f(x + bw)} ${f(horizon - bh)}`;563 for (let k = 0; k < bh / (R * 0.16) - 1; k++) {564 if (s.rnd() > 0.55) (s.rnd() > 0.6 ? wins2 : wins).push([x + bw * (0.25 + s.rnd() * 0.5), horizon - bh + R * 0.12 + k * R * 0.16, 1.6]);565 }566 x += bw + W * 0.008;567 }568 sky += `L${f(W)} ${f(horizon)}Z`;569 let rain = "";570 for (let i = 0; i < 45; i++) {571 const rx = s.rnd() * W;572 const ry = s.rnd() * H;573 rain += `M${f(rx)} ${f(ry)}l${f(-R * 0.05)} ${f(R * 0.22)} `;574 }575 const colX = cx + R * 0.72;576 const colH = R * 1.75;577 return (578 <g>579 <defs>580 <Linear id={`${id}-mt1`} stops={[[0, "#1a0630"], [0.55, p.bg], [1, "#3a0a3a"]]} />581 <Linear id={`${id}-mt2`} stops={[[0, p.primary, 0.35], [1, p.primary, 0]]} />582 <Radial id={`${id}-mt3`} color={p.primary} o0={0.55} />583 </defs>584 <rect width={W} height={H} fill={`url(#${id}-mt1)`} />585 <ellipse cx={f(cx)} cy={f(horizon)} rx={f(W * 0.7)} ry={f(R * 0.9)} fill={`url(#${id}-mt3)`} />586 <path d={sky} fill="#0a0514" stroke={p.primary} strokeOpacity={0.5} strokeWidth={1.2} />587 <path d={dots(wins)} fill="#ffd166" fillOpacity={0.8} />588 <path d={dots(wins2)} fill={p.secondary} fillOpacity={0.85} />589 <rect x={0} y={f(horizon)} width={W} height={f(H - horizon)} fill={`url(#${id}-mt2)`} />590 <line x1={0} y1={f(horizon)} x2={W} y2={f(horizon)} stroke={p.secondary} strokeWidth={1.5} strokeOpacity={0.9} />591 <path d={rain} stroke="#9be7ff" strokeOpacity={0.28} strokeWidth={1} fill="none" strokeLinecap="round" />592 {!compact ? (593 <>594 <rect x={f(colX - R * 0.32)} y={f(cy - colH / 2)} width={f(R * 0.64)} height={f(colH)} rx={f(R * 0.06)} fill="#120820" fillOpacity={0.9} stroke={p.primary} strokeOpacity={0.9} strokeWidth={2} />595 <Sym s={s} id="W" x={colX} y={cy - R * 0.58} size={R * 0.58} />596 <Sym s={s} id="W" x={colX} y={cy} size={R * 0.58} />597 <Sym s={s} id="W" x={colX} y={cy + R * 0.58} size={R * 0.58} />598 <rect x={f(cx - R * 1.12)} y={f(cy - R * 1.05)} width={f(R * 0.34)} height={f(R * 0.95)} rx={4} fill="#1a0a2a" stroke={p.secondary} strokeWidth={2} />599 <Small x={cx - R * 0.95} y={cy - R * 0.58} text="TOKYO" color={p.secondary} size={R * 0.13} weight={800} tracking="0.1em" rotate={-90} />600 <rect x={f(cx - R * 0.66)} y={f(cy - R * 1.0)} width={f(R * 0.9)} height={f(R * 0.24)} rx={4} fill={p.primary} fillOpacity={0.9} />601 <Small x={cx - R * 0.21} y={cy - R * 0.88} text="24H NEON" color="#fff" size={R * 0.1} weight={800} tracking="0.16em" />602 <Sym s={s} id="P1" x={cx - R * 0.25} y={cy + R * 0.02} size={R * 0.82} />603 <Sym s={s} id="H1" x={cx - R * 0.85} y={cy + R * 0.42} size={R * 0.44} rotate={-25} />604 <Sym s={s} id="S" x={cx + R * 0.12} y={cy - R * 0.62} size={R * 0.36} />605 </>606 ) : (607 <Sym s={s} id="P1" x={cx} y={cy} size={R * 0.9} />608 )}609 </g>610 );611}612613/* 09 — PHARAOH PROTOCOL: pyramid with a scanning laser, artifact meter */614function PharaohProtocol(s: Stage) {615 const { W, H, cx, cy, R, id, p, compact } = s;616 const base = cy + R * 0.7;617 const apex = cy - R * 0.98;618 const half = R * 1.05;619 const tri = `${f(cx - half)},${f(base)} ${f(cx)},${f(apex)} ${f(cx + half)},${f(base)}`;620 let bricks = "";621 for (let k = 1; k < 9; k++) bricks += `M${f(cx - half)} ${f(apex + (base - apex) * (k / 9))}H${f(cx + half)} `;622 const scanY = cy - R * 0.18;623 const dust = scatterDots(s, 35, [0, 0, W, H], 0.6, 1.8);624 const segs = 12;625 const mw = R * 1.4;626 const mx = cx - mw / 2;627 const my = cy + R * 0.86;628 let segOff = "";629 let segOn = "";630 for (let i = 0; i < segs; i++) {631 const d = `M${f(mx + (i * mw) / segs + 1.5)} ${f(my)}h${f(mw / segs - 3)}v${f(R * 0.06)}h${f(-(mw / segs - 3))}z `;632 if (i < 7) segOn += d;633 else segOff += d;634 }635 return (636 <g>637 <defs>638 <Linear id={`${id}-pp1`} stops={[[0, "#2a1608"], [0.45, p.bg], [1, "#1a1006"]]} />639 <Linear id={`${id}-pp2`} stops={[[0, "#8a5a1a"], [0.5, "#f1c86a"], [1, "#5a3a10"]]} dir="h" />640 <Radial id={`${id}-pp3`} color="#fb923c" o0={0.7} mid={[0.4, p.primary, 0.3]} />641 <clipPath id={`${id}-pp4`}>642 <polygon points={tri} />643 </clipPath>644 </defs>645 <rect width={W} height={H} fill={`url(#${id}-pp1)`} />646 <circle cx={f(cx + R * 0.78)} cy={f(cy - R * 0.8)} r={f(R * 0.55)} fill={`url(#${id}-pp3)`} />647 <circle cx={f(cx + R * 0.78)} cy={f(cy - R * 0.8)} r={f(R * 0.2)} fill="#fb923c" fillOpacity={0.9} />648 <ellipse cx={f(cx - R * 0.6)} cy={f(base + R * 0.15)} rx={f(W * 0.7)} ry={f(R * 0.3)} fill="#3a2810" />649 <ellipse cx={f(cx + R * 0.9)} cy={f(base + R * 0.25)} rx={f(W * 0.6)} ry={f(R * 0.28)} fill="#2a1c0a" />650 <polygon points={tri} fill={`url(#${id}-pp2)`} />651 <path d={bricks} stroke="#3a2408" strokeOpacity={0.7} strokeWidth={1.2} clipPath={`url(#${id}-pp4)`} />652 <polygon points={`${f(cx)},${f(apex)} ${f(cx + half)},${f(base)} ${f(cx)},${f(base)}`} fill="#000" fillOpacity={0.35} />653 <rect x={f(cx - half)} y={f(scanY - R * 0.12)} width={f(half * 2)} height={f(R * 0.24)} fill={p.secondary} fillOpacity={0.18} clipPath={`url(#${id}-pp4)`} />654 <line x1={f(cx - half * 1.1)} y1={f(scanY)} x2={f(cx + half * 1.1)} y2={f(scanY)} stroke={p.secondary} strokeWidth={2} strokeOpacity={0.95} />655 <Sym s={s} id="W" x={cx} y={apex - R * 0.05} size={R * 0.72} plate={false} />656 {!compact ? (657 <>658 <Sym s={s} id="P1" x={cx + R * 0.66} y={cy + R * 0.22} size={R * 0.62} />659 <Sym s={s} id="H1" x={cx - R * 0.82} y={cy + R * 0.18} size={R * 0.5} />660 <Sym s={s} id="AR" x={cx - R * 0.5} y={cy + R * 0.6} size={R * 0.36} />661 <Sym s={s} id="AR" x={cx - R * 0.15} y={cy + R * 0.64} size={R * 0.36} />662 <Sym s={s} id="AR" x={cx + R * 0.2} y={cy + R * 0.6} size={R * 0.36} />663 <path d={segOff} fill="#fff" fillOpacity={0.12} />664 <path d={segOn} fill={p.secondary} fillOpacity={0.95} />665 <Small x={cx} y={my + R * 0.15} text="ARTIFACT SCANNER · 7 / 12" color={p.secondary} size={R * 0.065} tracking="0.2em" opacity={0.85} />666 </>667 ) : null}668 <path d={dust} fill={p.primary} fillOpacity={0.5} />669 </g>670 );671}672673/* 10 — LUCKY CIRCUIT: retro arcade bezel, mystery "?" cartridge, confetti */674function LuckyCircuit(s: Stage) {675 const { W, H, cx, cy, R, id, p, compact } = s;676 const bw = R * 2.2;677 const bh = R * 1.9;678 const bx = cx - bw / 2;679 const by = cy - bh / 2 - R * 0.05;680 let scan = "";681 for (let y = by + 8; y < by + bh; y += 6) scan += `M${f(bx)} ${f(y)}H${f(bx + bw)} `;682 const confetti = (color: string, n: number) => {683 let d = "";684 for (let i = 0; i < n; i++) {685 const x = s.rnd() * W;686 const y = s.rnd() * H;687 const k = 3 + s.rnd() * 4;688 d += `M${f(x)} ${f(y)}h${f(k * 1.6)}v${f(k)}h${f(-k * 1.6)}z `;689 }690 return <path d={d} fill={color} fillOpacity={0.85} />;691 };692 return (693 <g>694 <defs>695 <pattern id={`${id}-lc0`} width="14" height="14" patternUnits="userSpaceOnUse">696 <circle cx="7" cy="7" r="1.2" fill={p.secondary} fillOpacity="0.35" />697 </pattern>698 <Radial id={`${id}-lc1`} color={p.glow} o0={0.5} />699 </defs>700 <rect width={W} height={H} fill={`url(#${id}-lc0)`} />701 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.4)} fill={`url(#${id}-lc1)`} />702 <rect x={f(bx - R * 0.1)} y={f(by - R * 0.1)} width={f(bw + R * 0.2)} height={f(bh + R * 0.2)} rx={f(R * 0.14)} fill="#1b1638" stroke={p.primary} strokeWidth={f(R * 0.06)} />703 <rect x={f(bx)} y={f(by)} width={f(bw)} height={f(bh)} rx={f(R * 0.06)} fill="#07061a" stroke={p.secondary} strokeWidth={2} />704 <path d={scan} stroke="#fff" strokeOpacity={0.05} strokeWidth={2} />705 {!compact ? confetti(p.primary, 14) : null}706 {!compact ? confetti(p.glow, 12) : null}707 {!compact ? confetti(p.secondary, 12) : null}708 <Sym s={s} id="MY" x={cx} y={cy - R * 0.08} size={R * 1.0} />709 {!compact ? (710 <>711 <Sym s={s} id="W" x={cx - R * 0.78} y={cy - R * 0.5} size={R * 0.48} rotate={-12} />712 <Sym s={s} id="P1" x={cx + R * 0.78} y={cy - R * 0.5} size={R * 0.48} rotate={10} />713 <Sym s={s} id="H1" x={cx - R * 0.78} y={cy + R * 0.6} size={R * 0.46} />714 <Sym s={s} id="M2" x={cx + R * 0.78} y={cy + R * 0.6} size={R * 0.46} />715 <Sym s={s} id="S" x={cx} y={cy + R * 0.72} size={R * 0.38} />716 <rect x={f(cx - R * 0.55)} y={f(by + R * 0.06)} width={f(R * 1.1)} height={f(R * 0.24)} rx={f(R * 0.05)} fill={p.glow} />717 <Small x={cx} y={by + R * 0.18} text="INSERT COIN" color="#fff" size={R * 0.1} weight={800} tracking="0.2em" />718 </>719 ) : null}720 </g>721 );722}723724/* 11 — VOID MINER: asteroid field, dark-matter core with level meter, drill beam */725function VoidMiner(s: Stage) {726 const { W, H, cx, cy, R, id, p, compact } = s;727 const stars = scatterDots(s, 60, [0, 0, W, H], 0.5, 1.3);728 const rock = (x: number, y: number, r: number, n: number) => {729 const list: string[] = [];730 for (let k = 0; k < n; k++) {731 const a = (k / n) * PI * 2;732 const rr = r * (0.72 + s.rnd() * 0.36);733 list.push(`${f(x + Math.cos(a) * rr)},${f(y + Math.sin(a) * rr)}`);734 }735 return list.join(" ");736 };737 const rocks = [738 [cx - R * 1.0, cy - R * 0.95, R * 0.28, 8],739 [cx + R * 1.0, cy - R * 0.2, R * 0.22, 7],740 [cx - R * 0.95, cy + R * 0.35, R * 0.2, 9],741 [cx + R * 0.7, cy + R * 0.95, R * 0.3, 8],742 [cx - R * 0.15, cy - R * 1.12, R * 0.16, 7],743 ] as const;744 const drillX = cx + R * 0.6;745 const drillY = cy - R * 0.58;746 const circ = 2 * PI * R * 0.66;747 const seg = circ / 5;748 return (749 <g>750 <defs>751 <Radial id={`${id}-vm1`} color={p.primary} o0={0.5} />752 <Radial id={`${id}-vm2`} color={p.secondary} o0={0.35} />753 </defs>754 <path d={stars} fill="#fff" fillOpacity={0.7} />755 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.15)} fill={`url(#${id}-vm1)`} />756 {rocks.map((r, i) => (757 <polygon key={i} points={rock(r[0], r[1], r[2], r[3])} fill={i % 2 ? "#171226" : "#0f0b1a"} stroke={p.primary} strokeOpacity={0.5} strokeWidth={1.5} strokeLinejoin="round" />758 ))}759 <circle cx={f(cx + R * 0.7)} cy={f(cy + R * 0.95)} r={f(R * 0.5)} fill={`url(#${id}-vm2)`} />760 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.66)} fill="none" stroke="#fff" strokeOpacity={0.12} strokeWidth={f(R * 0.07)} strokeDasharray={`${f(seg - 6)} 6`} transform={`rotate(-90 ${f(cx)} ${f(cy)})`} />761 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.66)} fill="none" stroke={p.secondary} strokeOpacity={0.95} strokeWidth={f(R * 0.07)} strokeDasharray={`${f(seg - 6)} 6 ${f(seg - 6)} 6 ${f(seg - 6)} ${f(circ)}`} transform={`rotate(-90 ${f(cx)} ${f(cy)})`} strokeLinecap="butt" />762 <Sym s={s} id="DM" x={cx} y={cy} size={R * 0.9} plate={false} />763 {!compact ? (764 <>765 <line x1={f(drillX)} y1={f(drillY)} x2={f(cx + R * 1.0)} y2={f(cy - R * 0.2)} stroke={p.secondary} strokeWidth={2} strokeOpacity={0.9} strokeDasharray="6 4" />766 <Sym s={s} id="W" x={drillX} y={drillY} size={R * 0.56} rotate={30} />767 <Sym s={s} id="P1" x={cx - R * 0.72} y={cy - R * 0.5} size={R * 0.56} />768 <Sym s={s} id="H1" x={cx - R * 0.55} y={cy + R * 0.8} size={R * 0.44} />769 <Sym s={s} id="H2" x={cx + R * 0.2} y={cy + R * 0.82} size={R * 0.4} />770 <Small x={cx + R * 0.02} y={cy - R * 0.98} text="CORE LV 3 / 5" color={p.secondary} size={R * 0.075} tracking="0.2em" weight={700} />771 <Small x={cx + R * 1.12} y={cy + R * 0.4} text="×1 ×2 ×3 ×4 ×5" color={p.primary} size={R * 0.07} tracking="0.16em" rotate={-90} opacity={0.75} />772 </>773 ) : null}774 </g>775 );776}777778/* 12 — ROYAL CIRCUIT: race-track sweep, lap counter, turbo boost ×2/×3 */779function RoyalCircuit(s: Stage) {780 const { W, cx, cy, R, id, p, compact } = s;781 const track = `M${f(cx - R * 1.6)} ${f(cy + R * 1.3)} C${f(cx - R * 0.6)} ${f(cy + R * 1.4)} ${f(cx - R * 0.8)} ${f(cy - R * 0.2)} ${f(cx + R * 0.2)} ${f(cy - R * 0.05)} S${f(cx + R * 1.8)} ${f(cy - R * 1.0)} ${f(cx + R * 1.9)} ${f(cy - R * 1.6)}`;782 let streaks = "";783 for (let i = 0; i < 9; i++) {784 const y = cy - R * 1.1 + s.rnd() * R * 2.2;785 const x0 = s.rnd() * W * 0.5;786 streaks += `M${f(x0)} ${f(y)}h${f(R * (0.4 + s.rnd() * 0.9))} `;787 }788 const lapX = cx - R * 0.75;789 const lapY = cy - R * 0.62;790 const lapR = R * 0.36;791 const lapC = 2 * PI * lapR;792 const lseg = lapC / 8;793 return (794 <g>795 <defs>796 <pattern id={`${id}-rc0`} width={f(R * 0.16)} height={f(R * 0.16)} patternUnits="userSpaceOnUse">797 <rect width={f(R * 0.08)} height={f(R * 0.08)} fill="#fff" fillOpacity="0.85" />798 <rect x={f(R * 0.08)} y={f(R * 0.08)} width={f(R * 0.08)} height={f(R * 0.08)} fill="#fff" fillOpacity="0.85" />799 </pattern>800 <Radial id={`${id}-rc1`} color={p.primary} o0={0.35} />801 </defs>802 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.3)} fill={`url(#${id}-rc1)`} />803 <path d={track} stroke="#1d1d26" strokeWidth={f(R * 0.62)} fill="none" strokeLinecap="round" />804 <path d={track} stroke="#f5f5f5" strokeWidth={f(R * 0.66)} fill="none" strokeDasharray={`${f(R * 0.14)} ${f(R * 0.14)}`} strokeOpacity={0.9} />805 <path d={track} stroke={p.secondary} strokeWidth={f(R * 0.66)} fill="none" strokeDasharray={`${f(R * 0.14)} ${f(R * 0.14)}`} strokeDashoffset={f(R * 0.14)} strokeOpacity={0.9} />806 <path d={track} stroke="#1d1d26" strokeWidth={f(R * 0.56)} fill="none" strokeLinecap="round" />807 <path d={track} stroke="#fff" strokeOpacity={0.35} strokeWidth={2} fill="none" strokeDasharray="14 18" />808 <path d={streaks} stroke={p.primary} strokeOpacity={0.55} strokeWidth={2} strokeLinecap="round" />809 <rect x={f(cx - R * 1.3)} y={f(cy + R * 0.9)} width={f(R * 2.6)} height={f(R * 0.14)} fill={`url(#${id}-rc0)`} />810 <Sym s={s} id="W" x={cx + R * 0.1} y={cy + R * 0.08} size={R * 0.96} rotate={-10} />811 {!compact ? (812 <>813 <Pill x={cx + R * 0.68} y={cy - R * 0.42} text="×2" color={p.primary} ink={p.primary} size={R * 0.1} />814 <Pill x={cx + R * 0.95} y={cy - R * 0.1} text="×3" color={p.secondary} ink="#fff" size={R * 0.1} />815 <circle cx={f(lapX)} cy={f(lapY)} r={f(lapR)} fill="#0b0b10" fillOpacity={0.85} stroke="#fff" strokeOpacity={0.1} strokeWidth={f(R * 0.07)} />816 <circle cx={f(lapX)} cy={f(lapY)} r={f(lapR)} fill="none" stroke={p.glow} strokeWidth={f(R * 0.07)} strokeDasharray={`${f(lseg - 4)} 4`} transform={`rotate(-90 ${f(lapX)} ${f(lapY)})`} strokeOpacity={0.95} />817 <circle cx={f(lapX)} cy={f(lapY)} r={f(lapR)} fill="none" stroke="#0b0b10" strokeWidth={f(R * 0.08)} strokeDasharray={`0 ${f(lseg * 6)} ${f(lapC)}`} transform={`rotate(-90 ${f(lapX)} ${f(lapY)})`} />818 <Small x={lapX} y={lapY - R * 0.05} text="LAP" color="#fff" size={R * 0.08} tracking="0.2em" opacity={0.7} />819 <Small x={lapX} y={lapY + R * 0.1} text="6 / 8" color={p.glow} size={R * 0.13} weight={800} tracking="0.02em" />820 <Sym s={s} id="S" x={cx + R * 0.85} y={cy - R * 0.98} size={R * 0.44} rotate={8} />821 <Sym s={s} id="P1" x={cx + R * 0.85} y={cy + R * 0.62} size={R * 0.5} />822 <Sym s={s} id="H1" x={cx - R * 0.85} y={cy + R * 0.5} size={R * 0.46} />823 </>824 ) : null}825 </g>826 );827}828829/* 13 — DRAGON CORE: reactor containment with a dragon charge gauge 0/20 */830function DragonCore(s: Stage) {831 const { cx, cy, R, id, p, compact } = s;832 let ticks = "";833 let lit = "";834 for (let i = 0; i < 20; i++) {835 const a = -PI * 1.25 + (i / 19) * PI * 1.5;836 const d = `M${f(cx + Math.cos(a) * R * 0.98)} ${f(cy + Math.sin(a) * R * 0.98)}L${f(cx + Math.cos(a) * R * 1.12)} ${f(cy + Math.sin(a) * R * 1.12)} `;837 if (i < 13) lit += d;838 else ticks += d;839 }840 const embers = scatterDots(s, 30, [cx - R * 1.3, cy - R * 1.3, R * 2.6, R * 2.6], 0.8, 2.4);841 const scales = [842 { x: cx + R * 0.95, y: cy + R * 0.45, r: 15 },843 { x: cx + R * 0.72, y: cy + R * 0.8, r: 30 },844 { x: cx + R * 0.4, y: cy + R * 0.8, r: 45 },845 ];846 return (847 <g>848 <defs>849 <Radial id={`${id}-dc1`} color={p.primary} o0={0.55} mid={[0.5, p.glow, 0.2]} />850 <Radial id={`${id}-dc2`} color={p.glow} o0={0.9} mid={[0.5, p.primary, 0.5]} />851 </defs>852 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.5)} fill={`url(#${id}-dc1)`} />853 <polygon points={svgPolygon(cx, cy, R * 1.28, 6, PI / 6)} fill="none" stroke={p.secondary} strokeOpacity={0.4} strokeWidth={2} />854 <polygon points={svgPolygon(cx, cy, R * 0.84, 6, PI / 6)} fill="#1a0608" fillOpacity={0.8} stroke={p.secondary} strokeOpacity={0.85} strokeWidth={2.5} />855 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.62)} fill={`url(#${id}-dc2)`} />856 <path d={ticks} stroke="#fff" strokeOpacity={0.18} strokeWidth={3} strokeLinecap="round" />857 <path d={lit} stroke={p.secondary} strokeWidth={3.5} strokeLinecap="round" />858 <Sym s={s} id="P1" x={cx} y={cy} size={R * 0.9} plate={false} />859 {!compact ? (860 <>861 {scales.map((sc, i) => (862 <Sym key={i} s={s} id="DC" x={sc.x} y={sc.y} size={R * 0.4} rotate={sc.r} />863 ))}864 <Sym s={s} id="W" x={cx - R * 0.9} y={cy + R * 0.7} size={R * 0.56} rotate={-12} />865 <Sym s={s} id="S" x={cx - R * 0.95} y={cy - R * 0.75} size={R * 0.42} />866 <Sym s={s} id="H1" x={cx + R * 0.95} y={cy - R * 0.75} size={R * 0.42} />867 <Small x={cx} y={cy + R * 1.0} text="DRAGON CHARGE 13 / 20" color={p.secondary} size={R * 0.075} tracking="0.2em" weight={700} opacity={0.9} />868 </>869 ) : null}870 <path d={embers} fill={p.secondary} fillOpacity={0.75} />871 </g>872 );873}874875/* 14 — DEEP TREASURE: descending depth levels with treasure chests, bubbles */876function DeepTreasure(s: Stage) {877 const { W, H, cx, cy, R, id, p, compact } = s;878 const bubbles: Array<[number, number, number]> = [];879 for (let i = 0; i < 26; i++) bubbles.push([s.rnd() * W, cy - R * 1.3 + s.rnd() * R * 2.6, 2 + s.rnd() * R * 0.04]);880 const levels = ["×1", "×2", "×3", "×4", "×6", "×8", "×10"];881 const top = cy - R * 1.05;882 const lh = R * 0.3;883 const chest = (x: number, y: number, w: number, key: string) => (884 <g key={key}>885 <rect x={f(x - w / 2)} y={f(y - w * 0.28)} width={f(w)} height={f(w * 0.62)} rx={f(w * 0.06)} fill="#7a4a12" stroke="#fbbf24" strokeWidth={1.5} />886 <path d={`M${f(x - w / 2)} ${f(y - w * 0.28)}a${f(w / 2)} ${f(w * 0.3)} 0 0 1 ${f(w)} 0z`} fill="#b8741c" stroke="#fbbf24" strokeWidth={1.5} />887 <circle cx={f(x)} cy={f(y + w * 0.02)} r={f(w * 0.08)} fill="#fde68a" />888 </g>889 );890 return (891 <g>892 <defs>893 <Linear id={`${id}-dt1`} stops={[[0, "#0a5a6e"], [0.35, "#063a4c"], [1, "#010610"]]} />894 <Linear id={`${id}-dt2`} stops={[[0, "#bff6ff", 0.32], [1, "#bff6ff", 0]]} />895 </defs>896 <rect width={W} height={H} fill={`url(#${id}-dt1)`} />897 <polygon points={`${f(cx - R * 0.9)},0 ${f(cx - R * 0.55)},0 ${f(cx + R * 0.1)},${f(H)} ${f(cx - R * 0.9)},${f(H)}`} fill={`url(#${id}-dt2)`} />898 <polygon points={`${f(cx + R * 0.1)},0 ${f(cx + R * 0.5)},0 ${f(cx + R * 1.5)},${f(H)} ${f(cx + R * 0.8)},${f(H)}`} fill={`url(#${id}-dt2)`} />899 {!compact900 ? levels.map((l, i) => (901 <g key={l}>902 <line x1={f(cx - R * 1.3)} y1={f(top + i * lh)} x2={f(cx + R * 1.3)} y2={f(top + i * lh)} stroke={p.primary} strokeOpacity={0.18 + i * 0.03} strokeWidth={1} />903 <Small x={cx + R * 1.12} y={top + i * lh + lh * 0.5} text={l} color={i === 6 ? p.secondary : p.primary} size={R * (i === 6 ? 0.11 : 0.08)} weight={700} tracking="0.05em" opacity={0.55 + i * 0.06} anchor="end" />904 </g>905 ))906 : null}907 {!compact ? chest(cx - R * 0.78, cy - R * 0.5, R * 0.42, "c1") : null}908 {!compact ? chest(cx - R * 0.3, cy + R * 0.15, R * 0.5, "c2") : null}909 {chest(cx + R * 0.3, cy + R * 0.72, R * 0.6, "c3")}910 <Sym s={s} id="P1" x={cx + R * 0.3} y={cy + R * 0.2} size={R * 0.9} />911 {!compact ? (912 <>913 <Sym s={s} id="W" x={cx - R * 0.55} y={cy - R * 0.95} size={R * 0.6} />914 <Sym s={s} id="H1" x={cx + R * 0.85} y={cy - R * 0.62} size={R * 0.46} />915 <Sym s={s} id="H2" x={cx - R * 0.95} y={cy + R * 0.78} size={R * 0.42} />916 <Sym s={s} id="S" x={cx + R * 0.32} y={cy - R * 0.95} size={R * 0.36} />917 </>918 ) : null}919 <path d={dots(bubbles)} fill="none" stroke="#dffaff" strokeOpacity={0.55} strokeWidth={1.2} />920 </g>921 );922}923924/* 15 — MOONBASE 77: retro lunar base, moon horizon, drifting wilds */925function Moonbase77(s: Stage) {926 const { W, H, cx, cy, R, id, p, compact } = s;927 const stars = scatterDots(s, 70, [0, 0, W, H * 0.8], 0.5, 1.4);928 const mR = R * 3.2;929 const mY = cy + R * 0.95 + mR;930 const craters: Array<[number, number, number]> = [];931 for (let i = 0; i < 10; i++) craters.push([cx - R * 1.3 + s.rnd() * R * 2.6, cy + R * 1.05 + s.rnd() * R * 0.5, R * (0.03 + s.rnd() * 0.08)]);932 const domeX = cx - R * 0.55;933 const domeY = cy + R * 0.95;934 const domeR = R * 0.55;935 let trail = "";936 for (const [x, y] of [937 [cx - R * 0.92, cy - R * 0.72],938 [cx - R * 0.15, cy - R * 0.9],939 ]) trail += `M${f(x + R * 0.2)} ${f(y + R * 0.1)}q${f(R * 0.25)} ${f(R * 0.1)} ${f(R * 0.55)} ${f(-R * 0.05)} `;940 return (941 <g>942 <defs>943 <Radial id={`${id}-mb1`} color="#3a3d4a" o0={1} o1={1} mid={[0.6, "#232633", 1]} />944 <Radial id={`${id}-mb2`} color="#60a5fa" o0={1} mid={[0.55, "#1e3a8a", 1]} o1={1} />945 <Linear id={`${id}-mb3`} stops={[[0, "#2b2f3d"], [1, "#151824"]]} />946 </defs>947 <path d={stars} fill="#fff" fillOpacity={0.8} />948 <circle cx={f(cx + R * 0.95)} cy={f(cy - R * 1.05)} r={f(R * 0.16)} fill={`url(#${id}-mb2)`} />949 <circle cx={f(cx)} cy={f(mY)} r={f(mR)} fill={`url(#${id}-mb1)`} />950 <path d={dots(craters)} fill="#000" fillOpacity={0.35} stroke="#9aa0b4" strokeOpacity={0.35} strokeWidth={1} />951 <path d={`M${f(domeX - domeR)} ${f(domeY)}a${f(domeR)} ${f(domeR)} 0 0 1 ${f(domeR * 2)} 0z`} fill={`url(#${id}-mb3)`} stroke={p.secondary} strokeOpacity={0.8} strokeWidth={2} />952 <path d={`M${f(domeX)} ${f(domeY - domeR)}V${f(domeY)} M${f(domeX - domeR * 0.7)} ${f(domeY - domeR * 0.7)}L${f(domeX + domeR * 0.7)} ${f(domeY - domeR * 0.7)}`} stroke={p.secondary} strokeOpacity={0.35} strokeWidth={1.2} />953 <rect x={f(domeX - domeR * 0.28)} y={f(domeY - domeR * 0.45)} width={f(domeR * 0.56)} height={f(domeR * 0.32)} rx={3} fill={p.primary} fillOpacity={0.9} />954 <path d={`M${f(domeX + domeR * 0.85)} ${f(domeY)}V${f(domeY - domeR * 1.6)}h${f(domeR * 0.25)}`} stroke="#c9cdd8" strokeWidth={2} fill="none" />955 <circle cx={f(domeX + domeR * 1.1)} cy={f(domeY - domeR * 1.6)} r={3} fill="#ff4d4d" />956 <path d={`M${f(cx - R * 1.3)} ${f(cy + R * 0.98)}H${f(cx + R * 1.3)}`} stroke={p.primary} strokeOpacity={0.9} strokeWidth={2} />957 <Sym s={s} id="P1" x={cx + R * 0.52} y={cy + R * 0.28} size={R * 0.98} />958 {!compact ? (959 <>960 <path d={trail} stroke="#fff" strokeOpacity={0.4} strokeWidth={1.5} strokeDasharray="2 6" fill="none" />961 <Sym s={s} id="W" x={cx - R * 0.92} y={cy - R * 0.72} size={R * 0.56} rotate={-14} />962 <Sym s={s} id="W" x={cx - R * 0.15} y={cy - R * 0.9} size={R * 0.42} rotate={10} />963 <Sym s={s} id="H1" x={cx - R * 0.55} y={cy + R * 0.15} size={R * 0.5} />964 <Sym s={s} id="S" x={cx + R * 0.35} y={cy - R * 0.62} size={R * 0.4} />965 <Sym s={s} id="H2" x={cx - R * 1.02} y={cy - R * 0.05} size={R * 0.38} />966 </>967 ) : null}968 </g>969 );970}971972/* 16 — WILD TEMPLE: stepped stone chambers, jaguar wild with moving arrows, torches */973function WildTemple(s: Stage) {974 const { W, H, cx, cy, R, id, p, compact } = s;975 let steps = "";976 const n = 5;977 const baseY = cy + R * 1.15;978 const sh = R * 0.3;979 for (let i = 0; i < n; i++) {980 const w = R * (2.7 - i * 0.48);981 steps += `M${f(cx - w / 2)} ${f(baseY - sh * (i + 1))}h${f(w)}v${f(sh)}h${f(-w)}z `;982 }983 let joints = "";984 for (let i = 0; i < n; i++) {985 const w = R * (2.7 - i * 0.48);986 const y = baseY - sh * (i + 1);987 for (let k = 1; k < 5; k++) joints += `M${f(cx - w / 2 + (w * k) / 5 + (i % 2 ? w / 10 : 0))} ${f(y)}v${f(sh)} `;988 }989 const motes = scatterDots(s, 30, [0, 0, W, H], 0.6, 1.8);990 const vines = `M${f(cx - R * 1.3)} ${f(H)}q${f(R * 0.2)} ${f(-R * 0.6)} ${f(R * 0.05)} ${f(-R * 1.2)} M${f(cx + R * 1.25)} ${f(H)}q${f(-R * 0.25)} ${f(-R * 0.5)} ${f(-R * 0.1)} ${f(-R * 1.1)} M${f(cx - R * 1.2)} ${f(cy - R * 1.3)}q${f(R * 0.3)} ${f(R * 0.4)} ${f(R * 0.1)} ${f(R * 0.9)}`;991 const chevron = (x: number, y: number, dir: 1 | -1, k: number) => `M${f(x - dir * R * 0.06)} ${f(y - R * 0.12 * k)}l${f(dir * R * 0.12 * k)} ${f(R * 0.12 * k)}l${f(-dir * R * 0.12 * k)} ${f(R * 0.12 * k)}`;992 const jx = cx;993 const jy = cy - R * 0.05;994 return (995 <g>996 <defs>997 <Radial id={`${id}-wt1`} color={p.secondary} o0={0.8} mid={[0.4, p.secondary, 0.25]} />998 <Linear id={`${id}-wt2`} stops={[[0, p.bg, 0], [1, "#000", 0.8]]} />999 <Radial id={`${id}-wt3`} color={p.glow} o0={0.35} />1000 </defs>1001 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.5)} fill={`url(#${id}-wt3)`} />1002 <ellipse cx={f(cx - R * 1.1)} cy={f(cy - R * 1.3)} rx={f(R * 0.9)} ry={f(R * 0.5)} fill="#041108" />1003 <ellipse cx={f(cx + R * 1.15)} cy={f(cy - R * 1.25)} rx={f(R * 0.85)} ry={f(R * 0.45)} fill="#041108" />1004 <circle cx={f(cx)} cy={f(cy - R * 1.02)} r={f(R * 0.5)} fill={`url(#${id}-wt1)`} />1005 <path d={steps} fill="#0d2417" stroke="#3fbf8a" strokeOpacity={0.55} strokeWidth={1.5} />1006 <path d={joints} stroke="#3fbf8a" strokeOpacity={0.22} strokeWidth={1} />1007 <rect x={f(cx - R * 0.42)} y={f(baseY - sh * 3)} width={f(R * 0.84)} height={f(sh * 3)} fill="#02100a" />1008 <rect x={f(cx - R * 0.42)} y={f(baseY - sh * 3)} width={f(R * 0.84)} height={f(sh * 3)} fill={`url(#${id}-wt2)`} />1009 <path d={vines} stroke="#22c55e" strokeOpacity={0.6} strokeWidth={2.5} fill="none" strokeLinecap="round" />1010 <Sym s={s} id="S" x={cx} y={cy - R * 1.02} size={R * 0.48} plate={false} />1011 <Sym s={s} id="W" x={jx} y={jy} size={R * 0.92} />1012 {!compact ? (1013 <>1014 <path d={`${chevron(jx - R * 0.62, jy, -1, 1)} ${chevron(jx + R * 0.62, jy, 1, 1)}`} stroke={p.secondary} strokeWidth={3} fill="none" strokeLinecap="round" strokeLinejoin="round" />1015 <path d={`${chevron(jx - R * 0.78, jy, -1, 1)} ${chevron(jx + R * 0.78, jy, 1, 1)}`} stroke={p.secondary} strokeWidth={3} fill="none" strokeLinecap="round" strokeLinejoin="round" opacity={0.45} />1016 <Sym s={s} id="P1" x={cx - R * 0.98} y={cy + R * 0.62} size={R * 0.54} />1017 <Sym s={s} id="H1" x={cx + R * 0.98} y={cy + R * 0.62} size={R * 0.5} />1018 <Sym s={s} id="H2" x={cx - R * 1.05} y={cy - R * 0.45} size={R * 0.4} />1019 <Sym s={s} id="H2" x={cx + R * 1.05} y={cy - R * 0.45} size={R * 0.4} />1020 <Sym s={s} id="M1" x={cx + R * 0.55} y={cy + R * 0.85} size={R * 0.34} rotate={30} />1021 </>1022 ) : null}1023 <path d={motes} fill={p.secondary} fillOpacity={0.55} />1024 </g>1025 );1026}10271028/* 17 — ZERO GRAVITY: three grids 5×3 → 6×4 → 7×5 floating in orbit */1029function ZeroGravity(s: Stage) {1030 const { W, H, cx, cy, R, id, p, compact } = s;1031 const stars = scatterDots(s, 70, [0, 0, W, H], 0.5, 1.4);1032 const grid = (x: number, y: number, cols: number, rows: number, cell: number, op: number, key: string, label: string, skew: number) => {1033 const w = cols * cell;1034 const h = rows * cell;1035 let lines = "";1036 for (let c = 1; c < cols; c++) lines += `M${f(x + c * cell)} ${f(y)}v${f(h)} `;1037 for (let r = 1; r < rows; r++) lines += `M${f(x)} ${f(y + r * cell)}h${f(w)} `;1038 return (1039 <g key={key} opacity={op} transform={`translate(${f(x + w / 2)} ${f(y + h / 2)}) skewY(${skew}) translate(${f(-(x + w / 2))} ${f(-(y + h / 2))})`}>1040 <rect x={f(x)} y={f(y)} width={f(w)} height={f(h)} rx={f(cell * 0.15)} fill={p.bg} fillOpacity={0.75} stroke={p.primary} strokeWidth={2} />1041 <path d={lines} stroke={p.primary} strokeOpacity={0.55} strokeWidth={1} />1042 <Small x={x + w / 2} y={y - cell * 0.45} text={label} color={p.glow} size={cell * 0.55} weight={800} tracking="0.05em" />1043 </g>1044 );1045 };1046 const c1 = R * 0.13;1047 const c2 = R * 0.155;1048 const c3 = R * 0.19;1049 return (1050 <g>1051 <defs>1052 <Radial id={`${id}-zg1`} color={p.secondary} o0={0.35} />1053 </defs>1054 <path d={stars} fill="#fff" fillOpacity={0.75} />1055 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.4)} fill={`url(#${id}-zg1)`} />1056 <ellipse cx={f(cx)} cy={f(cy)} rx={f(R * 1.35)} ry={f(R * 0.5)} fill="none" stroke={p.primary} strokeOpacity={0.35} strokeWidth={1.2} transform={`rotate(-18 ${f(cx)} ${f(cy)})`} />1057 <ellipse cx={f(cx)} cy={f(cy)} rx={f(R * 1.1)} ry={f(R * 0.75)} fill="none" stroke={p.secondary} strokeOpacity={0.3} strokeWidth={1.2} transform={`rotate(32 ${f(cx)} ${f(cy)})`} />1058 {!compact ? grid(cx - R * 1.12, cy - R * 1.02, 5, 3, c1, 0.5, "g1", "5×3", -8) : null}1059 {!compact ? grid(cx - R * 0.35, cy - R * 0.62, 6, 4, c2, 0.72, "g2", "6×4", -5) : null}1060 {grid(cx - R * 0.66, cy + R * 0.05, 7, 5, c3, 1, "g3", "7×5", -3)}1061 {!compact ? (1062 <path d={`M${f(cx - R * 0.42)} ${f(cy - R * 0.72)}l${f(R * 0.14)} ${f(R * 0.02)}l${f(-R * 0.05)} ${f(-R * 0.07)} M${f(cx + R * 0.02)} ${f(cy - R * 0.02)}l${f(R * 0.04)} ${f(R * 0.14)}l${f(-R * 0.1)} ${f(-R * 0.02)}`} stroke={p.glow} strokeWidth={2} fill="none" strokeLinecap="round" />1063 ) : null}1064 <Sym s={s} id="W" x={cx + R * 0.72} y={cy - R * 0.62} size={R * 0.82} plate={false} />1065 {!compact ? (1066 <>1067 <Sym s={s} id="P1" x={cx - R * 0.95} y={cy + R * 0.02} size={R * 0.48} />1068 <Sym s={s} id="S" x={cx + R * 1.05} y={cy + R * 0.42} size={R * 0.42} />1069 <Sym s={s} id="H2" x={cx + R * 0.02} y={cy - R * 1.12} size={R * 0.38} rotate={12} />1070 <Sym s={s} id="M1" x={cx + R * 1.0} y={cy + R * 0.85} size={R * 0.34} rotate={-30} />1071 </>1072 ) : null}1073 </g>1074 );1075}10761077/* 18 — REEL REACTOR: heat gauge 0–100 with meltdown zone, hazard stripes */1078function ReelReactor(s: Stage) {1079 const { W, cx, cy, R, id, p, compact } = s;1080 const gR = R * 1.0;1081 const a0 = PI * 0.78;1082 const a1 = PI * 2.22;1083 const at = (t: number) => a0 + (a1 - a0) * t;1084 const needle = at(0.72);1085 let hazard = "";1086 const hy = cy + R * 0.95;1087 for (let x = -R * 0.3; x < W + R; x += R * 0.22) hazard += `M${f(x)} ${f(hy)}h${f(R * 0.11)}l${f(-R * 0.1)} ${f(R * 0.12)}h${f(-R * 0.11)}z `;1088 const tick = (t: number, label: string, color: string) => {1089 const a = at(t);1090 return <Small key={label} x={cx + Math.cos(a) * gR * 1.22} y={cy + Math.sin(a) * gR * 1.22} text={label} color={color} size={R * 0.08} weight={700} tracking="0.02em" />;1091 };1092 return (1093 <g>1094 <defs>1095 <Radial id={`${id}-rr1`} color={p.glow} o0={0.55} />1096 </defs>1097 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.3)} fill={`url(#${id}-rr1)`} />1098 <path d={svgArc(cx, cy, gR, a0, a1)} fill="none" stroke="#0f1a0c" strokeWidth={f(R * 0.2)} strokeLinecap="round" />1099 <path d={svgArc(cx, cy, gR, at(0), at(0.4))} fill="none" stroke={p.glow} strokeWidth={f(R * 0.14)} />1100 <path d={svgArc(cx, cy, gR, at(0.4), at(0.8))} fill="none" stroke={p.secondary} strokeWidth={f(R * 0.14)} />1101 <path d={svgArc(cx, cy, gR, at(0.8), at(1))} fill="none" stroke="#ef4444" strokeWidth={f(R * 0.14)} />1102 <path d={svgArc(cx, cy, gR, at(0.8), at(1))} fill="none" stroke="#fff" strokeWidth={f(R * 0.14)} strokeDasharray="4 8" strokeOpacity={0.35} />1103 {!compact ? [tick(0, "0", "#c9d1c4"), tick(0.4, "40", p.secondary), tick(0.8, "80", "#ef4444"), tick(1, "100", "#ef4444")] : null}1104 <line x1={f(cx)} y1={f(cy)} x2={f(cx + Math.cos(needle) * gR * 0.92)} y2={f(cy + Math.sin(needle) * gR * 0.92)} stroke="#fff" strokeWidth={3} strokeLinecap="round" />1105 <Sym s={s} id="P1" x={cx} y={cy} size={R * 0.78} plate={false} />1106 {!compact ? (1107 <>1108 <Small x={cx} y={cy + R * 0.62} text="HEAT 72" color={p.secondary} size={R * 0.12} weight={800} tracking="0.06em" />1109 <Pill x={cx + R * 0.1} y={cy - R * 1.12} text="MELTDOWN" color="#ef4444" ink="#ff8a8a" bg="#2a0808" size={R * 0.07} />1110 <Sym s={s} id="W" x={cx - R * 0.9} y={cy + R * 0.62} size={R * 0.5} />1111 <Sym s={s} id="S" x={cx + R * 0.9} y={cy + R * 0.62} size={R * 0.5} rotate={15} />1112 <Sym s={s} id="H1" x={cx - R * 1.05} y={cy - R * 0.55} size={R * 0.4} />1113 <Sym s={s} id="M2" x={cx + R * 1.05} y={cy - R * 0.5} size={R * 0.4} />1114 </>1115 ) : null}1116 <path d={hazard} fill={p.secondary} fillOpacity={0.9} />1117 <rect x={0} y={f(hy)} width={W} height={f(R * 0.12)} fill="none" stroke="#000" strokeOpacity={0.6} strokeWidth={1} />1118 </g>1119 );1120}11211122/* 19 — OBSIDIAN: pure black, one thin gold line, one white-gold wild, a whisper of 50,000× */1123function Obsidian(s: Stage) {1124 const { W, H, cx, cy, R, p, compact } = s;1125 const dust = scatterDots(s, 14, [0, 0, W, H], 0.4, 1.0);1126 const lineY = cy + R * 0.62;1127 return (1128 <g>1129 <rect width={W} height={H} fill="#000" />1130 <path d={dust} fill={p.secondary} fillOpacity={0.4} />1131 <polygon points={svgPolygon(cx, cy - R * 0.05, R * 0.78, 6)} fill="#050505" stroke={p.primary} strokeOpacity={0.14} strokeWidth={1} />1132 <Sym s={s} id="W" x={cx} y={cy - R * 0.05} size={R * 0.72} plate={false} halo={false} />1133 <line x1={f(W * 0.12)} y1={f(lineY)} x2={f(W * 0.88)} y2={f(lineY)} stroke={p.secondary} strokeWidth={1} strokeOpacity={0.9} />1134 <circle cx={f(W * 0.88)} cy={f(lineY)} r={2} fill={p.primary} />1135 {!compact ? <Small x={W * 0.88} y={lineY + R * 0.14} text="50,000×" color={p.secondary} size={R * 0.07} tracking="0.3em" anchor="end" opacity={0.85} /> : null}1136 </g>1137 );1138}11391140/* 20 — SPINZA ORIGINAL: the Spinza ring mark, Mystery Orb, Mega Meter, cascading symbols */1141function SpinzaOriginal(s: Stage) {1142 const { W, H, cx, cy, R, id, p, compact } = s;1143 const stars = scatterDots(s, 60, [0, 0, W, H], 0.5, 1.4);1144 let rays = "";1145 for (let i = 0; i < 28; i++) {1146 const a = (i / 28) * PI * 2;1147 const len = R * (1.15 + s.rnd() * 0.7);1148 rays += `M${f(cx + Math.cos(a) * R * 0.75)} ${f(cy + Math.sin(a) * R * 0.75)}L${f(cx + Math.cos(a) * len)} ${f(cy + Math.sin(a) * len)} `;1149 }1150 const mR = R * 0.78;1151 const circ = 2 * PI * mR;1152 const seg = circ / 30;1153 const colX = cx + R * 0.98;1154 const fall = ["P1", "H1", "H2", "M1"];1155 return (1156 <g>1157 <defs>1158 <Radial id={`${id}-so1`} color={p.primary} o0={0.6} mid={[0.5, p.glow, 0.18]} />1159 <Linear id={`${id}-so2`} stops={[[0, p.primary], [0.5, "#fff"], [1, p.secondary]]} dir="d" />1160 </defs>1161 <path d={stars} fill="#fff" fillOpacity={0.75} />1162 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.5)} fill={`url(#${id}-so1)`} />1163 <path d={rays} stroke={p.primary} strokeOpacity={0.35} strokeWidth={1.5} strokeLinecap="round" />1164 <circle cx={f(cx)} cy={f(cy)} r={f(mR)} fill="none" stroke="#fff" strokeOpacity={0.12} strokeWidth={f(R * 0.06)} strokeDasharray={`${f(seg - 3)} 3`} transform={`rotate(-90 ${f(cx)} ${f(cy)})`} />1165 <circle cx={f(cx)} cy={f(cy)} r={f(mR)} fill="none" stroke={`url(#${id}-so2)`} strokeWidth={f(R * 0.06)} strokeDasharray={`${f(seg * 19 - 3)} ${f(circ)}`} transform={`rotate(-90 ${f(cx)} ${f(cy)})`} />1166 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.6)} fill="none" stroke={p.secondary} strokeOpacity={0.4} strokeWidth={1.5} strokeDasharray="4 8" />1167 <Sym s={s} id="W" x={cx} y={cy} size={R * 0.9} plate={false} />1168 {!compact ? (1169 <>1170 <Small x={cx} y={cy + R * 0.9} text="MEGA METER 19 / 30" color={p.glow} size={R * 0.07} tracking="0.22em" weight={700} opacity={0.9} />1171 <Sym s={s} id="MY" x={cx - R * 0.98} y={cy - R * 0.62} size={R * 0.62} />1172 <Sym s={s} id="Z" x={cx - R * 0.98} y={cy + R * 0.3} size={R * 0.5} />1173 <rect x={f(colX - R * 0.26)} y={f(cy - R * 1.12)} width={f(R * 0.52)} height={f(R * 2.12)} rx={f(R * 0.05)} fill="#fff" fillOpacity={0.04} stroke="#fff" strokeOpacity={0.12} />1174 {fall.map((idd, i) => (1175 <Sym key={idd} s={s} id={idd} x={colX} y={cy - R * 0.95 + i * R * 0.56} size={R * 0.46} opacity={0.55 + i * 0.15} />1176 ))}1177 <Pill x={cx - R * 0.28} y={cy - R * 1.04} text="WILD DROP" color={p.secondary} ink={p.secondary} size={R * 0.065} />1178 <Pill x={cx + R * 0.44} y={cy - R * 1.04} text="MULTIPLIER DROP" color={p.glow} ink={p.glow} size={R * 0.065} />1179 <Small x={cx} y={cy + R * 1.02} text="×1 ×2 ×3 ×4 ×6 ×8 ×10 ×15" color={p.primary} size={R * 0.07} tracking="0.16em" opacity={0.7} />1180 </>1181 ) : null}1182 </g>1183 );1184}11851186/* ======================================================================= */1187/* RISK GAMES + BEYOND SLOTS — no reel symbols. Shared grammar: a rising */1188/* multiplier ladder (`Rungs`) and the big-button verb as a solid pill */1189/* (`Verb`) so the cash-out / original category reads instantly. */1190/* ======================================================================= */11911192/** Ad-hoc glyph from the shared symbol vocabulary (no reel plate, no halo). */1193function Shape({ shape, color, accent, x, y, size, rotate, opacity, label }: { shape: SymbolShape; color: string; accent?: string; x: number; y: number; size: number; rotate?: number; opacity?: number; label?: string }) {1194 return <SymbolSvg style={{ shape, color, accent, label, glow: 0 }} size={size} x={x} y={y} rotate={rotate} opacity={opacity} plate={false} halo={false} />;1195}11961197/** The game's big-button verb as a solid pill with a "button" dot. */1198function Verb({ x, y, text, color, ink = "#0b0d14", size = 12, opacity = 1 }: { x: number; y: number; text: string; color: string; ink?: string; size?: number; opacity?: number }) {1199 const width = text.length * size * 0.7 + size * 2.6;1200 const h = size * 1.9;1201 const left = x - width / 2;1202 return (1203 <g opacity={opacity}>1204 <rect x={f(left)} y={f(y - h / 2)} width={f(width)} height={f(h)} rx={f(h / 2)} fill={color} stroke="#fff" strokeOpacity={0.35} strokeWidth={1} />1205 <circle cx={f(left + h * 0.58)} cy={f(y)} r={f(size * 0.24)} fill={ink} fillOpacity={0.75} />1206 <text x={f(x + size * 0.42)} y={f(y)} textAnchor="middle" dominantBaseline="central" fontSize={f(size)} fontWeight={800} fill={ink} style={{ fontFamily: FONT, letterSpacing: "0.1em" }}>1207 {text}1208 </text>1209 </g>1210 );1211}12121213/** Multiplier ladder labels: opacity and size grow along the list, the last rung is hot. */1214function Rungs({ items, color, hot, size, anchor = "middle", weight = 700 }: { items: Array<[number, number, string]>; color: string; hot: string; size: number; anchor?: "start" | "middle" | "end"; weight?: 600 | 700 | 800 }) {1215 const n = Math.max(1, items.length - 1);1216 return (1217 <>1218 {items.map(([x, y, t], i) => (1219 <Small key={`${t}${i}`} x={x} y={y} text={t} color={i === n ? hot : color} size={i === n ? size * 1.3 : size} anchor={anchor} weight={i === n ? 800 : weight} tracking="0.06em" opacity={0.5 + (i / n) * 0.5} />1220 ))}1221 </>1222 );1223}12241225/* 21 — SKYFALL: a jet climbs from the cloud deck through the storm into space; altitude rungs on the left */1226function Skyfall(s: Stage) {1227 const { W, H, cx, cy, R, id, p, compact } = s;1228 const stars = scatterDots(s, 55, [0, 0, W, Math.max(1, cy - R * 0.45)], 0.5, 1.4);1229 const cloudY = cy + R * 1.02;1230 const clouds: Array<[number, number, number]> = [];1231 for (let i = 0; i < 12; i++) clouds.push([cx - R * 1.4 + i * R * 0.26 + s.rnd() * R * 0.1, cloudY + s.rnd() * R * 0.2, R * (0.12 + s.rnd() * 0.15)]);1232 const jx = cx + R * 0.52;1233 const jy = cy - R * 0.48;1234 const trail = `M${f(cx - R * 1.15)} ${f(cy + R * 0.98)} C${f(cx - R * 0.1)} ${f(cy + R * 0.98)} ${f(cx + R * 0.05)} ${f(cy + R * 0.25)} ${f(jx - R * 0.1)} ${f(jy + R * 0.1)}`;1235 const jet = `M${f(R * 0.72)} 0 L${f(R * 0.12)} ${f(-R * 0.075)} L${f(-R * 0.56)} ${f(-R * 0.06)} L${f(-R * 0.62)} 0 L${f(-R * 0.56)} ${f(R * 0.06)} L${f(R * 0.12)} ${f(R * 0.075)} Z`;1236 const wings = `M${f(R * 0.06)} ${f(-R * 0.05)} L${f(-R * 0.48)} ${f(-R * 0.44)} L${f(-R * 0.62)} ${f(-R * 0.42)} L${f(-R * 0.44)} ${f(-R * 0.05)} Z M${f(R * 0.06)} ${f(R * 0.05)} L${f(-R * 0.48)} ${f(R * 0.44)} L${f(-R * 0.62)} ${f(R * 0.42)} L${f(-R * 0.44)} ${f(R * 0.05)} Z`;1237 const bolt = `M${f(cx - R * 0.62)} ${f(cy + R * 0.15)} l${f(R * 0.1)} ${f(R * 0.2)} l${f(-R * 0.09)} ${f(R * 0.02)} l${f(R * 0.14)} ${f(R * 0.26)}`;1238 const lx = cx - R * 1.14;1239 const rungs: Array<[number, number, string]> = [[lx, cy + R * 0.78, "1.5× CLOUDS"], [lx, cy + R * 0.38, "3× STORM FRONT"], [lx, cy - R * 0.02, "8× STRATOSPHERE"], [lx, cy - R * 0.42, "25× EDGE OF SPACE"], [lx, cy - R * 0.82, "100× ORBIT"]];1240 let ticks = "";1241 for (const [x, y] of rungs) ticks += `M${f(x - R * 0.06)} ${f(y)}h${f(R * 0.04)} M${f(x - R * 0.04)} ${f(y - R * 0.2)}v${f(R * 0.4)} `;1242 return (1243 <g>1244 <defs>1245 <Linear id={`${id}-sf1`} stops={[[0, "#020617"], [0.32, "#1e1b4b"], [0.58, "#334155"], [0.82, "#94a3b8"], [1, "#e2e8f0"]]} />1246 <Linear id={`${id}-sf2`} stops={[[0, p.secondary, 0], [1, p.secondary, 1]]} dir="h" />1247 <Radial id={`${id}-sf3`} color={p.secondary} o0={0.9} />1248 </defs>1249 <rect width={W} height={H} fill={`url(#${id}-sf1)`} />1250 <path d={stars} fill="#fff" fillOpacity={0.8} />1251 <rect x={0} y={f(cy + R * 0.05)} width={W} height={f(R * 0.7)} fill="#0f172a" fillOpacity={0.35} />1252 <path d={bolt} stroke={p.glow} strokeWidth={2} fill="none" strokeLinecap="round" strokeLinejoin="round" opacity={0.9} />1253 <path d={dots(clouds)} fill="#f8fafc" fillOpacity={0.9} />1254 <path d={dots(clouds.map(([x, y, r]) => [x + r * 0.2, y + r * 0.35, r * 0.8]))} fill="#cbd5e1" fillOpacity={0.6} />1255 <path d={trail} fill="none" stroke={`url(#${id}-sf2)`} strokeWidth={f(R * 0.05)} strokeLinecap="round" />1256 <path d={trail} fill="none" stroke="#fff" strokeOpacity={0.8} strokeWidth={1.5} strokeDasharray="3 8" />1257 {!compact ? <path d={ticks} stroke={p.primary} strokeOpacity={0.6} strokeWidth={1.5} fill="none" /> : null}1258 {!compact ? <Rungs items={rungs} color={p.primary} hot="#fff" size={R * 0.062} anchor="start" /> : null}1259 <circle cx={f(jx - R * 0.45 * Math.cos(0.61))} cy={f(jy + R * 0.45 * Math.sin(0.61))} r={f(R * 0.14)} fill={`url(#${id}-sf3)`} />1260 <g transform={`translate(${f(jx)} ${f(jy)}) rotate(-35)`}>1261 <path d={wings} fill="#94a3b8" stroke="#e2e8f0" strokeWidth={1} />1262 <path d={jet} fill="#f1f5f9" stroke="#64748b" strokeWidth={1} />1263 <ellipse cx={f(R * 0.28)} cy={f(-R * 0.02)} rx={f(R * 0.12)} ry={f(R * 0.045)} fill={p.secondary} />1264 <path d={`M${f(-R * 0.62)} 0 h${f(-R * 0.28)}`} stroke={p.secondary} strokeWidth={f(R * 0.05)} strokeLinecap="round" strokeOpacity={0.9} />1265 </g>1266 {!compact ? <Verb x={cx + R * 0.72} y={cy + R * 0.26} text="CASH OUT" color={p.secondary} size={R * 0.075} /> : null}1267 </g>1268 );1269}12701271/* 22 — DEEP DIVE: research sub sinking past depth markers, a leviathan waiting in the abyss */1272function DeepDive(s: Stage) {1273 const { W, H, cx, cy, R, id, p, compact } = s;1274 const surface = cy - R * 1.05;1275 const bubbles: Array<[number, number, number]> = [];1276 for (let i = 0; i < 22; i++) bubbles.push([cx - R * 1.1 + s.rnd() * R * 2.2, surface + R * 0.2 + s.rnd() * R * 2.0, 1.5 + s.rnd() * R * 0.03]);1277 let waves = `M0 ${f(surface)}`;1278 for (let i = 0; i < 10; i++) waves += ` q${f(W / 20)} ${f(-R * 0.06)} ${f(W / 10)} 0`;1279 const sx = cx - R * 0.3;1280 const sy = cy - R * 0.28;1281 const rx = cx + R * 1.12;1282 const rungs: Array<[number, number, string]> = [[rx, cy + R * 0.02, "1,000 m ×2"], [rx, cy + R * 0.38, "3,000 m ×5"], [rx, cy + R * 0.74, "ABYSS ×20"]];1283 let markers = "";1284 for (const [, y] of rungs) markers += `M${f(cx - R * 1.2)} ${f(y + R * 0.11)}H${f(cx + R * 1.2)} `;1285 const body = `M${f(cx + R * 1.45)} ${f(cy + R * 1.45)} C${f(cx + R * 1.0)} ${f(cy + R * 0.75)} ${f(cx + R * 0.35)} ${f(cy + R * 1.7)} ${f(cx - R * 0.35)} ${f(cy + R * 1.12)}`;1286 const spines = `M${f(cx + R * 0.95)} ${f(cy + R * 1.0)}l${f(R * 0.05)} ${f(-R * 0.24)}l${f(R * 0.1)} ${f(R * 0.2)} M${f(cx + R * 0.72)} ${f(cy + R * 1.0)}l${f(R * 0.03)} ${f(-R * 0.22)}l${f(R * 0.1)} ${f(R * 0.18)} M${f(cx + R * 1.2)} ${f(cy + R * 1.12)}l${f(R * 0.07)} ${f(-R * 0.22)}l${f(R * 0.09)} ${f(R * 0.22)}`;1287 return (1288 <g>1289 <defs>1290 <Linear id={`${id}-dd1`} stops={[[0, "#0e7490", 0.55], [0.3, "#0c4a6e", 0.35], [1, "#000", 0]]} />1291 <Linear id={`${id}-dd2`} stops={[[0, p.glow, 0.5], [1, p.glow, 0]]} />1292 <Radial id={`${id}-dd3`} color={p.secondary} o0={0.9} />1293 </defs>1294 <rect x={0} y={f(surface)} width={W} height={f(H - surface)} fill={`url(#${id}-dd1)`} />1295 <polygon points={`${f(cx - R * 0.9)},${f(surface)} ${f(cx - R * 0.45)},${f(surface)} ${f(cx + R * 0.2)},${f(cy + R * 1.3)} ${f(cx - R * 0.75)},${f(cy + R * 1.3)}`} fill={`url(#${id}-dd2)`} opacity={0.45} />1296 <path d={waves} fill="none" stroke="#e0fbff" strokeOpacity={0.9} strokeWidth={2} />1297 {!compact ? <path d={markers} stroke={p.primary} strokeOpacity={0.4} strokeWidth={1} strokeDasharray="6 6" /> : null}1298 {!compact ? <Rungs items={rungs} color={p.primary} hot={p.secondary} size={R * 0.065} anchor="end" /> : null}1299 <path d={body} fill="none" stroke={p.secondary} strokeOpacity={0.5} strokeWidth={f(R * 0.33)} strokeLinecap="round" />1300 <path d={body} fill="none" stroke="#02161f" strokeWidth={f(R * 0.3)} strokeLinecap="round" />1301 <path d={body} fill="none" stroke={p.secondary} strokeOpacity={0.3} strokeWidth={f(R * 0.3)} strokeLinecap="round" strokeDasharray="2 14" />1302 <path d={spines} fill="#02161f" stroke={p.secondary} strokeOpacity={0.7} strokeWidth={1.2} />1303 <ellipse cx={f(cx - R * 0.42)} cy={f(cy + R * 1.12)} rx={f(R * 0.3)} ry={f(R * 0.2)} fill="#02161f" stroke={p.secondary} strokeOpacity={0.5} strokeWidth={1.2} />1304 <polygon points={`${f(cx - R * 0.68)},${f(cy + R * 1.09)} ${f(cx - R * 0.98)},${f(cy + R * 1.25)} ${f(cx - R * 0.6)},${f(cy + R * 1.27)}`} fill="#02161f" stroke={p.secondary} strokeOpacity={0.5} strokeWidth={1.2} />1305 <circle cx={f(cx - R * 0.55)} cy={f(cy + R * 1.05)} r={f(R * 0.035)} fill={p.secondary} />1306 <circle cx={f(cx - R * 0.55)} cy={f(cy + R * 1.05)} r={f(R * 0.1)} fill={`url(#${id}-dd3)`} />1307 <polygon points={`${f(sx + R * 0.55)},${f(sy + R * 0.05)} ${f(sx + R * 1.25)},${f(sy + R * 0.65)} ${f(sx + R * 0.7)},${f(sy + R * 0.95)}`} fill={p.primary} fillOpacity={0.16} />1308 <g transform={`translate(${f(sx)} ${f(sy)}) rotate(14)`}>1309 <rect x={f(-R * 0.22)} y={f(-R * 0.42)} width={f(R * 0.26)} height={f(R * 0.3)} rx={f(R * 0.04)} fill="#0b3444" stroke={p.primary} strokeWidth={1.5} />1310 <path d={`M${f(-R * 0.09)} ${f(-R * 0.42)}v${f(-R * 0.16)}h${f(R * 0.14)}`} stroke={p.primary} strokeWidth={2} fill="none" />1311 <rect x={f(-R * 0.7)} y={f(-R * 0.2)} width={f(R * 1.35)} height={f(R * 0.42)} rx={f(R * 0.21)} fill="#0b3444" stroke={p.primary} strokeWidth={2} />1312 <path d={dots([[-R * 0.3, 0, R * 0.055], [-R * 0.05, 0, R * 0.055], [R * 0.2, 0, R * 0.055]])} fill={p.secondary} stroke="#e0fbff" strokeWidth={1} />1313 <path d={`M${f(-R * 0.7)} ${f(-R * 0.16)}l${f(-R * 0.16)} ${f(-R * 0.1)}v${f(R * 0.52)}l${f(R * 0.16)} ${f(-R * 0.1)}`} fill="#0b3444" stroke={p.primary} strokeWidth={1.5} />1314 <path d={`M${f(-R * 0.86)} ${f(-R * 0.16)}v${f(R * 0.32)}`} stroke={p.primary} strokeWidth={3} strokeLinecap="round" />1315 <circle cx={f(R * 0.6)} cy={f(R * 0.02)} r={f(R * 0.06)} fill={p.glow} />1316 </g>1317 <path d={dots(bubbles)} fill="none" stroke="#dffaff" strokeOpacity={0.5} strokeWidth={1.2} />1318 {!compact ? <Verb x={cx + R * 0.62} y={surface + R * 0.32} text="SURFACE" color={p.secondary} size={R * 0.075} /> : null}1319 </g>1320 );1321}13221323/* 23 — ROCKET RUN: launch pad, spent stage tumbling away, trajectory milestones toward a big Mars */1324function RocketRun(s: Stage) {1325 const { W, H, cx, cy, R, id, p, compact } = s;1326 const stars = scatterDots(s, 70, [0, 0, W, H], 0.5, 1.4);1327 const mx = cx + R * 0.86;1328 const my = cy - R * 0.84;1329 const mr = R * 0.5;1330 const ground = cy + R * 1.08;1331 const px = cx - R * 0.92;1332 const bez = (t: number): [number, number] => {1333 const x0 = px + R * 0.12;1334 const y0 = ground - R * 0.1;1335 const x1 = cx - R * 0.6;1336 const y1 = cy - R * 1.4;1337 const x2 = mx - mr * 0.55;1338 const y2 = my + mr * 0.75;1339 const u = 1 - t;1340 return [u * u * x0 + 2 * u * t * x1 + t * t * x2, u * u * y0 + 2 * u * t * y1 + t * t * y2];1341 };1342 const traj = `M${f(bez(0)[0])} ${f(bez(0)[1])} Q${f(cx - R * 0.6)} ${f(cy - R * 1.4)} ${f(bez(1)[0])} ${f(bez(1)[1])}`;1343 const ms: Array<{ t: number; l: string; side: 1 | 0 }> = [1344 { t: 0.14, l: "2× MAX-Q", side: 1 },1345 { t: 0.28, l: "5× SEPARATION", side: 1 },1346 { t: 0.74, l: "15× ESCAPE", side: 0 },1347 { t: 0.9, l: "60× TMI", side: 0 },1348 ];1349 const [rkx, rky] = bez(0.44);1350 const smoke = scatterDots(s, 14, [px - R * 0.3, ground - R * 0.35, R * 0.9, R * 0.35], R * 0.03, R * 0.1);1351 return (1352 <g>1353 <defs>1354 <Radial id={`${id}-rr1`} color="#f97316" o0={1} mid={[0.55, "#c2410c", 1]} o1={0.9} />1355 <Radial id={`${id}-rr2`} color={p.glow} o0={0.55} />1356 <Radial id={`${id}-rr3`} color={p.glow} o0={1} mid={[0.35, p.primary, 0.8]} />1357 </defs>1358 <path d={stars} fill="#fff" fillOpacity={0.75} />1359 <circle cx={f(mx)} cy={f(my)} r={f(mr * 1.7)} fill={`url(#${id}-rr2)`} />1360 <circle cx={f(mx)} cy={f(my)} r={f(mr)} fill={`url(#${id}-rr1)`} />1361 <ellipse cx={f(mx - mr * 0.25)} cy={f(my + mr * 0.2)} rx={f(mr * 0.42)} ry={f(mr * 0.18)} fill="#7c2d12" fillOpacity={0.7} />1362 <ellipse cx={f(mx + mr * 0.35)} cy={f(my - mr * 0.3)} rx={f(mr * 0.25)} ry={f(mr * 0.14)} fill="#7c2d12" fillOpacity={0.6} />1363 <ellipse cx={f(mx)} cy={f(my - mr * 0.88)} rx={f(mr * 0.3)} ry={f(mr * 0.08)} fill="#fff7ed" fillOpacity={0.8} />1364 <line x1={0} y1={f(ground)} x2={W} y2={f(ground)} stroke="#3b2a4a" strokeWidth={2} />1365 <rect x={f(px - R * 0.05)} y={f(ground - R * 0.95)} width={f(R * 0.1)} height={f(R * 0.95)} fill="#2a1d3a" stroke={p.primary} strokeOpacity={0.5} strokeWidth={1} />1366 <path d={`M${f(px)} ${f(ground - R * 0.85)}h${f(R * 0.3)} M${f(px)} ${f(ground - R * 0.6)}h${f(R * 0.3)} M${f(px)} ${f(ground - R * 0.35)}h${f(R * 0.3)}`} stroke={p.primary} strokeOpacity={0.55} strokeWidth={2} />1367 <path d={smoke} fill="#94a3b8" fillOpacity={0.35} />1368 <path d={traj} fill="none" stroke={p.primary} strokeOpacity={0.6} strokeWidth={2} strokeDasharray="5 9" />1369 {!compact ? <path d={dots(ms.map((m) => [bez(m.t)[0], bez(m.t)[1], R * 0.02]))} fill={p.glow} /> : null}1370 {!compact1371 ? ms.map((m, i) => {1372 const [x, y] = bez(m.t);1373 return <Small key={m.l} x={x + m.side * R * 0.09} y={m.side ? y - R * 0.06 : y + R * 0.15} text={m.l} color={i === 3 ? p.secondary : p.primary} size={R * (i === 3 ? 0.072 : 0.06)} anchor={m.side ? "start" : "middle"} weight={i === 3 ? 800 : 700} tracking="0.06em" opacity={0.6 + i * 0.13} />;1374 })1375 : null}1376 {!compact ? <Pill x={mx - mr * 0.1} y={my + mr * 0.35} text="300× MARS" color={p.secondary} ink="#fff" bg="#3b0a1a" size={R * 0.062} /> : null}1377 <g transform={`translate(${f(cx - R * 0.72)} ${f(cy + R * 0.5)}) rotate(70)`}>1378 <rect x={f(-R * 0.09)} y={f(-R * 0.22)} width={f(R * 0.18)} height={f(R * 0.44)} rx={f(R * 0.03)} fill="#c8ccd6" />1379 <polygon points={`${f(-R * 0.09)},${f(R * 0.1)} ${f(-R * 0.2)},${f(R * 0.25)} ${f(-R * 0.09)},${f(R * 0.22)} ${f(R * 0.09)},${f(R * 0.1)} ${f(R * 0.2)},${f(R * 0.25)} ${f(R * 0.09)},${f(R * 0.22)}`} fill={p.secondary} />1380 <path d={`M${f(-R * 0.05)} ${f(R * 0.22)}l${f(R * 0.05)} ${f(R * 0.18)}l${f(R * 0.05)} ${f(-R * 0.18)}`} fill="#ffb347" fillOpacity={0.85} />1381 </g>1382 <ellipse cx={f(rkx - R * 0.3)} cy={f(rky + R * 0.5)} rx={f(R * 0.12)} ry={f(R * 0.32)} fill={`url(#${id}-rr3)`} transform={`rotate(32 ${f(rkx - R * 0.3)} ${f(rky + R * 0.5)})`} />1383 <Shape shape="rocket" color="#f1f5f9" accent={p.secondary} x={rkx} y={rky} size={R * 1.15} rotate={32} />1384 {!compact ? <Verb x={cx + R * 0.5} y={cy + R * 0.85} text="SECURE PAYLOAD" color={p.secondary} size={R * 0.068} /> : null}1385 </g>1386 );1387}13881389/* 24 — BANK HEIST: inside the vault, safe-deposit wall, growing SC bags, red/blue siren sweep */1390function BankHeist(s: Stage) {1391 const { W, H, cx, cy, R, id, p, compact } = s;1392 const floor = cy + R * 0.98;1393 const lampX = cx + R * 0.1;1394 const lampY = cy - R * 1.22;1395 const bags = [1396 { x: cx - R * 0.82, y: floor - R * 0.02, k: 0.5, m: "×1.4" },1397 { x: cx - R * 0.28, y: floor - R * 0.06, k: 0.68, m: "×2" },1398 { x: cx + R * 0.45, y: floor - R * 0.12, k: 1.0, m: "×4" },1399 ];1400 let bars = "";1401 for (let i = 0; i < 6; i++) bars += `M${f(cx + R * 0.95 + (i % 3) * R * 0.02)} ${f(floor - R * 0.09 * (Math.floor(i / 3) + 1))}h${f(R * 0.26)}v${f(R * 0.08)}h${f(-R * 0.26)}z `;1402 return (1403 <g>1404 <defs>1405 <pattern id={`${id}-bh0`} width={f(R * 0.2)} height={f(R * 0.14)} patternUnits="userSpaceOnUse">1406 <rect x={1} y={1} width={f(R * 0.2 - 2)} height={f(R * 0.14 - 2)} rx={2} fill="#1b1811" stroke="#3a3324" strokeWidth={1} />1407 <circle cx={f(R * 0.1)} cy={f(R * 0.07)} r={1.2} fill={p.primary} fillOpacity={0.55} />1408 </pattern>1409 <Linear id={`${id}-bh1`} stops={[[0, "#3a2f1a"], [1, "#0b0a08"]]} />1410 <Radial id={`${id}-bh2`} color={p.glow} o0={0.5} />1411 </defs>1412 <rect x={0} y={0} width={W} height={f(floor)} fill={`url(#${id}-bh0)`} />1413 <rect x={0} y={f(floor)} width={W} height={f(H - floor)} fill={`url(#${id}-bh1)`} />1414 <line x1={0} y1={f(floor)} x2={W} y2={f(floor)} stroke={p.primary} strokeOpacity={0.7} strokeWidth={2} />1415 <polygon points={`${f(lampX)},${f(lampY)} ${f(cx - R * 1.5)},${f(floor + R * 0.5)} ${f(cx - R * 0.55)},${f(floor + R * 0.5)}`} fill="#ef4444" fillOpacity={0.22} />1416 <polygon points={`${f(lampX)},${f(lampY)} ${f(cx + R * 0.7)},${f(floor + R * 0.5)} ${f(cx + R * 1.6)},${f(floor + R * 0.5)}`} fill="#3b82f6" fillOpacity={0.22} />1417 <path d={`M${f(lampX - R * 0.16)} ${f(lampY + R * 0.1)}a${f(R * 0.16)} ${f(R * 0.16)} 0 0 1 ${f(R * 0.32)} 0z`} fill="#ef4444" />1418 <path d={`M${f(lampX)} ${f(lampY + R * 0.1)}a${f(R * 0.16)} ${f(R * 0.16)} 0 0 1 ${f(R * 0.16)} 0z`} fill="#3b82f6" />1419 <rect x={f(lampX - R * 0.2)} y={f(lampY + R * 0.1)} width={f(R * 0.4)} height={f(R * 0.05)} fill="#52525b" />1420 <circle cx={f(cx + R * 1.55)} cy={f(cy - R * 0.1)} r={f(R * 1.05)} fill="#14110c" stroke={p.primary} strokeWidth={f(R * 0.06)} />1421 <circle cx={f(cx + R * 1.55)} cy={f(cy - R * 0.1)} r={f(R * 0.86)} fill="none" stroke={p.primary} strokeOpacity={0.5} strokeWidth={2} />1422 <path d={dots([[cx + R * 0.55, cy - R * 0.1, R * 0.03], [cx + R * 0.66, cy - R * 0.62, R * 0.03], [cx + R * 0.66, cy + R * 0.42, R * 0.03]])} fill={p.glow} />1423 <circle cx={f(cx + R * 0.45)} cy={f(floor - R * 0.4)} r={f(R * 0.7)} fill={`url(#${id}-bh2)`} />1424 <path d={bars} fill={p.primary} stroke="#5a4520" strokeWidth={1} />1425 {(compact ? bags.slice(2) : bags).map((b) => (1426 <g key={b.m}>1427 <Shape shape="bag" color={p.primary} accent="#8a6a2a" x={b.x} y={b.y - R * 0.34 * b.k} size={R * 0.95 * b.k} />1428 <Small x={b.x} y={b.y - R * 0.3 * b.k} text="SC" color="#3a2a08" size={R * 0.16 * b.k} weight={800} tracking="0.02em" />1429 {!compact ? <Pill x={b.x} y={b.y + R * 0.12} text={b.m} color={p.primary} ink={p.glow} size={R * 0.07} /> : null}1430 </g>1431 ))}1432 {!compact ? <Small x={cx - R * 1.1} y={cy - R * 0.38} text="×1.1 ×1.4 ×2 ×3 ×4 ×12 ×50" color={p.glow} size={R * 0.062} tracking="0.12em" anchor="start" opacity={0.8} /> : null}1433 {!compact ? <Verb x={cx - R * 0.6} y={cy - R * 0.66} text="ESCAPE NOW" color={p.secondary} ink="#fff" size={R * 0.078} /> : null}1434 </g>1435 );1436}14371438/* 25 — VOLCANO: explorer on a rope in a crystal-lined shaft, lava rising from below */1439function Volcano(s: Stage) {1440 const { W, H, cx, cy, R, id, p, compact } = s;1441 const lavaY = cy + R * 0.82;1442 const wall = (side: 1 | -1) => {1443 let d = `M${f(cx + side * W)} 0 L${f(cx + side * R * 0.62)} 0`;1444 let y = 0;1445 while (y < H) {1446 y += R * 0.22;1447 d += ` L${f(cx + side * R * (0.7 + s.rnd() * 0.22))} ${f(y)}`;1448 }1449 return d + ` L${f(cx + side * W)} ${f(H)} Z`;1450 };1451 const left = wall(-1);1452 const right = wall(1);1453 const embers = scatterDots(s, 30, [cx - R * 0.8, lavaY - R * 1.4, R * 1.6, R * 1.4], 0.8, 2.4);1454 let lava = `M${f(cx - R * 1.3)} ${f(lavaY)}`;1455 for (let i = 0; i < 8; i++) lava += ` q${f(R * 0.16)} ${f(i % 2 ? R * 0.08 : -R * 0.08)} ${f(R * 0.33)} 0`;1456 lava += ` V${f(H)} H${f(cx - R * 1.3)} Z`;1457 const ex = cx - R * 0.12;1458 const ey = cy - R * 0.02;1459 const rx = cx + R * 0.5;1460 const rungs: Array<[number, number, string]> = [[rx, cy - R * 0.98, "1.5× ASH CAVES"], [rx, cy - R * 0.48, "3× OBSIDIAN"], [rx, cy + R * 0.02, "8× CRYSTALS"], [rx, cy + R * 0.52, "30× MAGMA"]];1461 return (1462 <g>1463 <defs>1464 <Linear id={`${id}-vc1`} stops={[[0, "#fde047"], [0.35, p.glow], [1, "#7f1d1d"]]} />1465 <Radial id={`${id}-vc2`} color={p.primary} o0={0.75} />1466 </defs>1467 <ellipse cx={f(cx)} cy={f(lavaY)} rx={f(R * 1.4)} ry={f(R * 1.1)} fill={`url(#${id}-vc2)`} />1468 <path d={left} fill="#170705" stroke="#4a1a10" strokeWidth={1.5} strokeLinejoin="round" />1469 <path d={right} fill="#170705" stroke="#4a1a10" strokeWidth={1.5} strokeLinejoin="round" />1470 {!compact ? (1471 <>1472 <Shape shape="crystal" color={p.secondary} accent="#fff7d6" x={cx - R * 0.82} y={cy - R * 0.62} size={R * 0.5} rotate={40} />1473 <Shape shape="crystal" color={p.secondary} accent="#fff7d6" x={cx + R * 0.86} y={cy + R * 0.32} size={R * 0.55} rotate={-38} />1474 <Shape shape="crystal" color="#f0abfc" accent="#fdf4ff" x={cx - R * 0.94} y={cy + R * 0.12} size={R * 0.4} rotate={62} />1475 <Shape shape="crystal" color={p.secondary} accent="#fff7d6" x={cx + R * 0.9} y={cy - R * 0.72} size={R * 0.36} rotate={-70} />1476 </>1477 ) : null}1478 <line x1={f(ex)} y1={0} x2={f(ex)} y2={f(ey - R * 0.3)} stroke="#d6b07a" strokeWidth={2.5} />1479 <line x1={f(ex)} y1={0} x2={f(ex)} y2={f(ey - R * 0.3)} stroke="#6b4a1a" strokeWidth={2.5} strokeDasharray="3 5" />1480 <g fill="#120404" stroke={p.glow} strokeWidth={1.2} strokeLinejoin="round">1481 <circle cx={f(ex)} cy={f(ey - R * 0.22)} r={f(R * 0.085)} />1482 <rect x={f(ex - R * 0.09)} y={f(ey - R * 0.13)} width={f(R * 0.18)} height={f(R * 0.3)} rx={f(R * 0.04)} />1483 <path d={`M${f(ex - R * 0.06)} ${f(ey - R * 0.1)}L${f(ex - R * 0.03)} ${f(ey - R * 0.38)} M${f(ex + R * 0.06)} ${f(ey - R * 0.1)}L${f(ex + R * 0.04)} ${f(ey - R * 0.38)} M${f(ex - R * 0.05)} ${f(ey + R * 0.17)}L${f(ex - R * 0.16)} ${f(ey + R * 0.42)} M${f(ex + R * 0.05)} ${f(ey + R * 0.17)}L${f(ex + R * 0.12)} ${f(ey + R * 0.44)}`} fill="none" strokeWidth={f(R * 0.05)} strokeLinecap="round" />1484 </g>1485 <polygon points={`${f(ex + R * 0.06)},${f(ey - R * 0.24)} ${f(ex + R * 0.55)},${f(ey - R * 0.02)} ${f(ex + R * 0.5)},${f(ey + R * 0.3)}`} fill="#fde68a" fillOpacity={0.22} />1486 <circle cx={f(ex + R * 0.07)} cy={f(ey - R * 0.24)} r={f(R * 0.03)} fill="#fde68a" />1487 <path d={lava} fill={`url(#${id}-vc1)`} />1488 <path d={lava} fill="none" stroke="#fff7d6" strokeOpacity={0.7} strokeWidth={2} />1489 <path d={`M${f(cx - R * 0.3)} ${f(lavaY - R * 0.3)}l${f(R * 0.1)} ${f(-R * 0.12)}l${f(R * 0.1)} ${f(R * 0.12)} M${f(cx + R * 0.15)} ${f(lavaY - R * 0.24)}l${f(R * 0.1)} ${f(-R * 0.12)}l${f(R * 0.1)} ${f(R * 0.12)}`} fill="none" stroke={p.secondary} strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" />1490 <path d={embers} fill={p.secondary} fillOpacity={0.85} />1491 {!compact ? <Rungs items={rungs} color={p.secondary} hot="#fff" size={R * 0.06} anchor="start" /> : null}1492 {!compact ? <Verb x={cx - R * 0.62} y={cy + R * 0.52} text="EVACUATE" color={p.secondary} size={R * 0.078} /> : null}1493 </g>1494 );1495}14961497/* 26 — BLACK HOLE: lensed disc, time-dilation streaks, a ship spiralling to the horizon */1498function BlackHole(s: Stage) {1499 const { W, H, cx, cy, R, id, p, compact } = s;1500 const bx = cx + R * 0.12;1501 const by = cy + R * 0.12;1502 const rb = R * 0.4;1503 const stars = scatterDots(s, 60, [0, 0, W, H], 0.5, 1.3);1504 let streaks = "";1505 for (let i = 0; i < 44; i++) {1506 const a = s.rnd() * PI * 2;1507 const r0 = rb * 1.7 + s.rnd() * R * 0.5;1508 const r1 = r0 + R * (0.25 + s.rnd() * 0.9);1509 streaks += `M${f(bx + Math.cos(a) * r0)} ${f(by + Math.sin(a) * r0)}L${f(bx + Math.cos(a) * r1)} ${f(by + Math.sin(a) * r1)} `;1510 }1511 const sp = (t: number): [number, number] => {1512 const r = R * 1.3 * Math.exp(-0.42 * t);1513 return [bx + Math.cos(t - 2.6) * r, by + Math.sin(t - 2.6) * r * 0.92];1514 };1515 let spiral = "";1516 for (let i = 0; i <= 60; i++) {1517 const [x, y] = sp((i / 60) * PI * 2.4);1518 spiral += `${i ? "L" : "M"}${f(x)} ${f(y)} `;1519 }1520 const [shx, shy] = sp(PI * 1.35);1521 const [nx, ny] = sp(PI * 1.42);1522 const ang = (Math.atan2(ny - shy, nx - shx) * 180) / PI;1523 const rungs: Array<[number, number, string]> = ([0.12, 0.5, 0.86, 1.15] as const).map((k, i) => {1524 const [x, y] = sp(PI * k);1525 return [x, y - R * 0.08, ["1.5×", "4×", "15×", "60×"][i]];1526 });1527 const disc = `rotate(-14 ${f(bx)} ${f(by)})`;1528 return (1529 <g>1530 <defs>1531 <Linear id={`${id}-bk1`} stops={[[0, p.primary, 0], [0.35, "#fff", 0.95], [0.7, p.secondary, 0.9], [1, p.primary, 0]]} dir="h" />1532 <Radial id={`${id}-bk2`} color={p.primary} o0={0.4} />1533 </defs>1534 <path d={stars} fill="#fff" fillOpacity={0.7} />1535 <circle cx={f(bx)} cy={f(by)} r={f(R * 1.5)} fill={`url(#${id}-bk2)`} />1536 <path d={streaks} stroke={p.primary} strokeOpacity={0.5} strokeWidth={1.2} strokeLinecap="round" />1537 <g transform={disc}>1538 <path d={`M${f(bx - R * 1.3)} ${f(by)} A${f(R * 1.3)} ${f(R * 0.24)} 0 0 1 ${f(bx + R * 1.3)} ${f(by)}`} fill="none" stroke={`url(#${id}-bk1)`} strokeWidth={f(R * 0.09)} strokeOpacity={0.65} />1539 </g>1540 <circle cx={f(bx)} cy={f(by)} r={f(rb * 1.12)} fill="none" stroke="#fff" strokeOpacity={0.95} strokeWidth={f(R * 0.02)} />1541 <circle cx={f(bx)} cy={f(by)} r={f(rb * 1.25)} fill="none" stroke={p.secondary} strokeOpacity={0.55} strokeWidth={f(R * 0.05)} />1542 <circle cx={f(bx)} cy={f(by)} r={f(rb)} fill="#000" />1543 <g transform={disc}>1544 <path d={`M${f(bx - R * 1.3)} ${f(by)} A${f(R * 1.3)} ${f(R * 0.24)} 0 0 0 ${f(bx + R * 1.3)} ${f(by)}`} fill="none" stroke={`url(#${id}-bk1)`} strokeWidth={f(R * 0.11)} />1545 <path d={`M${f(bx - R * 1.3)} ${f(by)} A${f(R * 1.3)} ${f(R * 0.24)} 0 0 0 ${f(bx + R * 1.3)} ${f(by)}`} fill="none" stroke="#fff" strokeOpacity={0.85} strokeWidth={1.5} />1546 </g>1547 <path d={spiral} fill="none" stroke={p.secondary} strokeOpacity={0.85} strokeWidth={1.6} strokeDasharray="4 6" />1548 {!compact ? <Rungs items={rungs} color={p.glow} hot="#fff" size={R * 0.07} /> : null}1549 <g transform={`translate(${f(shx)} ${f(shy)}) rotate(${f(ang)})`}>1550 <path d={`M${f(-R * 0.55)} 0 h${f(-R * 0.35)}`} stroke={p.secondary} strokeWidth={f(R * 0.05)} strokeLinecap="round" strokeOpacity={0.7} />1551 <polygon points={`${f(R * 0.16)},0 ${f(-R * 0.12)},${f(-R * 0.1)} ${f(-R * 0.06)},0 ${f(-R * 0.12)},${f(R * 0.1)}`} fill="#f5f3ff" stroke={p.primary} strokeWidth={1} />1552 </g>1553 {!compact ? <Verb x={cx - R * 0.75} y={cy + R * 0.88} text="WARP OUT" color={p.secondary} size={R * 0.078} /> : null}1554 </g>1555 );1556}15571558/* 27 — FREEFALL: skydiver above a patchwork of fields rushing up, ghost of the unopened chute */1559function Freefall(s: Stage) {1560 const { W, H, cx, cy, R, id, p, compact } = s;1561 const y0 = cy + R * 0.3;1562 const cols = 8;1563 const tints = ["#365314", "#65a30d", "#a16207", "#4d7c0f", "#ca8a04", "#1a2e05"];1564 const paths = tints.map(() => "");1565 for (let k = 0; k < 6; k++) {1566 const t0 = Math.pow(k / 6, 1.55);1567 const t1 = Math.pow((k + 1) / 6, 1.55);1568 const ya = y0 + (H - y0) * t0;1569 const yb = y0 + (H - y0) * t1;1570 const spreadA = 1 + k * 0.16;1571 const spreadB = 1 + (k + 1) * 0.16;1572 const cw = R * 0.36;1573 for (let j = 0; j < cols; j++) {1574 const c = Math.floor(s.rnd() * tints.length);1575 const xa0 = cx + (j - cols / 2) * cw * spreadA;1576 const xa1 = cx + (j + 1 - cols / 2) * cw * spreadA;1577 const xb0 = cx + (j - cols / 2) * cw * spreadB;1578 const xb1 = cx + (j + 1 - cols / 2) * cw * spreadB;1579 paths[c] += `M${f(xa0)} ${f(ya)}L${f(xa1)} ${f(ya)}L${f(xb1)} ${f(yb)}L${f(xb0)} ${f(yb)}Z `;1580 }1581 }1582 let speed = "";1583 for (let i = 0; i < 14; i++) {1584 const x = cx - R * 1.2 + s.rnd() * R * 2.4;1585 const y = cy - R * 1.2 + s.rnd() * R * 1.4;1586 speed += `M${f(x)} ${f(y)}v${f(R * (0.15 + s.rnd() * 0.35))} `;1587 }1588 const dx = cx;1589 const dy = cy - R * 0.32;1590 const rx = cx + R * 1.12;1591 const rungs: Array<[number, number, string]> = [[rx, cy - R * 0.95, "1.3× TERMINAL"], [rx, cy - R * 0.45, "3× CLOUD DECK"], [rx, cy + R * 0.05, "10× TREETOPS"], [rx, cy + R * 0.55, "40× GROUND RUSH"]];1592 return (1593 <g>1594 <defs>1595 <Linear id={`${id}-ff1`} stops={[[0, "#0b1a34"], [1, "#1e3a5f"]]} />1596 <Radial id={`${id}-ff2`} color="#fff" o0={0.18} />1597 </defs>1598 <rect width={W} height={H} fill={`url(#${id}-ff1)`} />1599 {paths.map((d, i) => (d ? <path key={i} d={d} fill={tints[i]} stroke="#0b1a1a" strokeWidth={1} /> : null))}1600 <rect x={0} y={f(y0)} width={W} height={f(H - y0)} fill={`url(#${id}-ff1)`} opacity={0.35} />1601 <ellipse cx={f(cx - R * 0.7)} cy={f(cy + R * 0.42)} rx={f(R * 0.5)} ry={f(R * 0.12)} fill={`url(#${id}-ff2)`} />1602 <ellipse cx={f(cx + R * 0.55)} cy={f(cy + R * 0.62)} rx={f(R * 0.42)} ry={f(R * 0.1)} fill={`url(#${id}-ff2)`} />1603 <path d={speed} stroke={p.glow} strokeOpacity={0.5} strokeWidth={1.5} strokeLinecap="round" />1604 {!compact ? (1605 <>1606 <path d={`M${f(dx - R * 0.72)} ${f(dy - R * 0.62)} A${f(R * 0.72)} ${f(R * 0.5)} 0 0 1 ${f(dx + R * 0.72)} ${f(dy - R * 0.62)}`} fill="none" stroke={p.secondary} strokeOpacity={0.75} strokeWidth={2} strokeDasharray="5 6" />1607 <path d={`M${f(dx - R * 0.72)} ${f(dy - R * 0.62)}L${f(dx - R * 0.1)} ${f(dy - R * 0.02)} M${f(dx + R * 0.72)} ${f(dy - R * 0.62)}L${f(dx + R * 0.1)} ${f(dy - R * 0.02)} M${f(dx - R * 0.25)} ${f(dy - R * 0.98)}L${f(dx - R * 0.03)} ${f(dy - R * 0.05)} M${f(dx + R * 0.25)} ${f(dy - R * 0.98)}L${f(dx + R * 0.03)} ${f(dy - R * 0.05)}`} stroke={p.secondary} strokeOpacity={0.45} strokeWidth={1} strokeDasharray="3 5" />1608 </>1609 ) : null}1610 <g stroke="#0b1a34" strokeWidth={1.2} strokeLinejoin="round" strokeLinecap="round">1611 <path d={`M${f(dx - R * 0.1)} ${f(dy - R * 0.04)}L${f(dx - R * 0.5)} ${f(dy - R * 0.3)} M${f(dx + R * 0.1)} ${f(dy - R * 0.04)}L${f(dx + R * 0.5)} ${f(dy - R * 0.3)} M${f(dx - R * 0.08)} ${f(dy + R * 0.3)}L${f(dx - R * 0.42)} ${f(dy + R * 0.62)} M${f(dx + R * 0.08)} ${f(dy + R * 0.3)}L${f(dx + R * 0.42)} ${f(dy + R * 0.62)}`} fill="none" stroke={p.secondary} strokeWidth={f(R * 0.09)} />1612 <rect x={f(dx - R * 0.14)} y={f(dy - R * 0.14)} width={f(R * 0.28)} height={f(R * 0.5)} rx={f(R * 0.08)} fill={p.secondary} />1613 <rect x={f(dx - R * 0.08)} y={f(dy - R * 0.1)} width={f(R * 0.16)} height={f(R * 0.42)} rx={f(R * 0.05)} fill="#1e293b" stroke="none" />1614 <circle cx={f(dx)} cy={f(dy - R * 0.27)} r={f(R * 0.12)} fill="#f8fafc" />1615 <path d={`M${f(dx - R * 0.1)} ${f(dy - R * 0.28)}h${f(R * 0.2)}`} stroke="#1e293b" strokeWidth={f(R * 0.06)} />1616 </g>1617 {!compact ? <Rungs items={rungs} color={p.glow} hot={p.secondary} size={R * 0.06} anchor="end" /> : null}1618 {!compact ? <Verb x={cx - R * 0.74} y={cy - R * 0.02} text="OPEN CHUTE" color={p.secondary} size={R * 0.078} /> : null}1619 </g>1620 );1621}16221623/* 28 — CORE MELTDOWN: reactor vessel with glowing fuel rods, vertical temperature gauge with a red limit */1624function CoreMeltdown(s: Stage) {1625 const { cx, cy, R, id, p, compact } = s;1626 const vx = cx - R * 0.32;1627 const vw = R * 1.1;1628 const top = cy - R * 1.02;1629 const bottom = cy + R * 0.98;1630 let rods = "";1631 for (let i = 0; i < 5; i++) rods += `M${f(vx - vw * 0.36 + i * vw * 0.18)} ${f(top + R * 0.25)}h${f(vw * 0.08)}v${f(bottom - top - R * 0.5)}h${f(-vw * 0.08)}z `;1632 let bands = "";1633 for (let i = 1; i < 4; i++) bands += `M${f(vx - vw / 2)} ${f(top + ((bottom - top) * i) / 4)}h${f(vw)} `;1634 const gx = cx + R * 0.7;1635 const gw = R * 0.22;1636 const gTop = cy - R * 1.02;1637 const gBot = cy + R * 0.9;1638 const level = gBot - (gBot - gTop) * 0.74;1639 const limit = gBot - (gBot - gTop) * 0.82;1640 let grad = "";1641 for (let i = 0; i <= 20; i++) grad += `M${f(gx + gw / 2 + 2)} ${f(gBot - ((gBot - gTop) * i) / 20)}h${f(i % 5 === 0 ? R * 0.07 : R * 0.035)} `;1642 const rungs: Array<[number, number, string]> = [[gx + gw / 2 + R * 0.12, gBot - (gBot - gTop) * 0.2, "1.5×"], [gx + gw / 2 + R * 0.12, gBot - (gBot - gTop) * 0.45, "3×"], [gx + gw / 2 + R * 0.12, gBot - (gBot - gTop) * 0.65, "8×"], [gx + gw / 2 + R * 0.12, gBot - (gBot - gTop) * 0.8, "25×"]];1643 let readouts = "";1644 for (let i = 0; i < 9; i++) readouts += `M${f(cx - R * 1.1 + i * R * 0.25)} ${f(bottom + R * 0.12)}h${f(R * 0.16)}v${f(R * 0.05)}h${f(-R * 0.16)}z `;1645 return (1646 <g>1647 <defs>1648 <Linear id={`${id}-cm1`} stops={[[0, "#ef4444"], [0.25, p.secondary], [1, p.primary]]} />1649 <Radial id={`${id}-cm2`} color={p.primary} o0={0.55} />1650 <Linear id={`${id}-cm3`} stops={[[0, "#fff", 0.35], [0.5, p.glow, 0.95], [1, "#fff", 0.35]]} dir="h" />1651 <pattern id={`${id}-cm4`} width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(-45)">1652 <rect width="3" height="6" fill="#ef4444" />1653 </pattern>1654 </defs>1655 <ellipse cx={f(vx)} cy={f(cy)} rx={f(R * 1.2)} ry={f(R * 1.3)} fill={`url(#${id}-cm2)`} />1656 <rect x={f(vx - vw / 2 - R * 0.1)} y={f(top - R * 0.12)} width={f(vw + R * 0.2)} height={f(R * 0.14)} rx={f(R * 0.03)} fill="#26332a" stroke={p.primary} strokeOpacity={0.5} strokeWidth={1} />1657 <rect x={f(vx - vw / 2 - R * 0.1)} y={f(bottom - R * 0.02)} width={f(vw + R * 0.2)} height={f(R * 0.14)} rx={f(R * 0.03)} fill="#26332a" stroke={p.primary} strokeOpacity={0.5} strokeWidth={1} />1658 <rect x={f(vx - vw / 2)} y={f(top)} width={f(vw)} height={f(bottom - top)} rx={f(R * 0.08)} fill="#0b1408" stroke={p.primary} strokeOpacity={0.9} strokeWidth={2} />1659 <path d={rods} fill={`url(#${id}-cm3)`} />1660 <path d={bands} stroke="#26332a" strokeWidth={f(R * 0.05)} />1661 <path d={bands} stroke={p.primary} strokeOpacity={0.4} strokeWidth={1} />1662 <path d={`M${f(vx - vw / 2)} ${f(cy - R * 0.5)}h${f(-R * 0.35)}v${f(R * 1.35)}h${f(R * 0.35)} M${f(vx + vw / 2)} ${f(cy - R * 0.2)}h${f(R * 0.22)}`} fill="none" stroke="#4b5563" strokeWidth={f(R * 0.06)} />1663 <rect x={f(gx - gw / 2)} y={f(gTop)} width={f(gw)} height={f(gBot - gTop)} rx={f(gw / 2)} fill="#0f1a0c" stroke="#3f4a3a" strokeWidth={2} />1664 <rect x={f(gx - gw / 2 + 3)} y={f(level)} width={f(gw - 6)} height={f(gBot - level - 3)} rx={f(gw / 2 - 3)} fill={`url(#${id}-cm1)`} />1665 <rect x={f(gx - gw / 2 + 3)} y={f(gTop + 3)} width={f(gw - 6)} height={f(limit - gTop - 3)} rx={f(gw / 2 - 3)} fill={`url(#${id}-cm4)`} opacity={0.75} />1666 <line x1={f(gx - gw / 2 - R * 0.06)} y1={f(limit)} x2={f(gx + gw / 2 + R * 0.06)} y2={f(limit)} stroke="#ef4444" strokeWidth={2.5} />1667 <path d={grad} stroke="#9ca3af" strokeOpacity={0.6} strokeWidth={1} />1668 {!compact ? <Rungs items={rungs} color={p.primary} hot={p.secondary} size={R * 0.065} anchor="start" /> : null}1669 {!compact ? <Small x={gx + gw / 2 + R * 0.12} y={limit - R * 0.12} text="LIMIT" color="#ef4444" size={R * 0.06} anchor="start" weight={800} tracking="0.2em" /> : null}1670 {!compact ? <Shape shape="warning" color={p.secondary} x={cx - R * 1.02} y={cy + R * 0.1} size={R * 0.46} /> : null}1671 {!compact ? <path d={readouts} fill={p.primary} fillOpacity={0.35} /> : null}1672 {!compact ? <Verb x={cx - R * 0.72} y={cy + R * 0.72} text="SHUT DOWN" color="#ef4444" ink="#fff" size={R * 0.078} /> : null}1673 </g>1674 );1675}16761677/* 29 — STORM CHASE: truck racing down a highway into a tornado, debris and lightning */1678function StormChase(s: Stage) {1679 const { W, H, cx, cy, R, id, p, compact } = s;1680 const ground = cy + R * 0.72;1681 const fx = cx + R * 0.42;1682 const funnel = `M${f(fx - R * 0.95)} ${f(cy - R * 1.1)} C${f(fx - R * 0.6)} ${f(cy - R * 0.4)} ${f(fx - R * 0.35)} ${f(cy + R * 0.2)} ${f(fx - R * 0.12)} ${f(ground)} L${f(fx + R * 0.12)} ${f(ground)} C${f(fx + R * 0.3)} ${f(cy + R * 0.2)} ${f(fx + R * 0.55)} ${f(cy - R * 0.4)} ${f(fx + R * 0.95)} ${f(cy - R * 1.1)} Z`;1683 let ribs = "";1684 for (let i = 1; i < 6; i++) {1685 const t = i / 6;1686 const y = cy - R * 1.1 + (ground - (cy - R * 1.1)) * t;1687 const hw = R * (0.95 - 0.83 * t);1688 ribs += `M${f(fx - hw)} ${f(y)} q${f(hw)} ${f(R * 0.1)} ${f(hw * 2)} 0 `;1689 }1690 let debris = "";1691 for (let i = 0; i < 22; i++) {1692 const a = s.rnd() * PI * 2;1693 const d = R * (0.35 + s.rnd() * 0.85);1694 const x = fx + Math.cos(a) * d;1695 const y = cy - R * 0.2 + Math.sin(a) * d * 0.7;1696 const k = R * (0.02 + s.rnd() * 0.04);1697 debris += `M${f(x)} ${f(y)}l${f(k * 1.5)} ${f(k * 0.6)}l${f(-k * 0.6)} ${f(k * 1.2)}l${f(-k * 1.5)} ${f(-k * 0.6)}z `;1698 }1699 const bolt = `M${f(cx - R * 0.72)} ${f(cy - R * 1.15)} l${f(R * 0.12)} ${f(R * 0.4)} l${f(-R * 0.14)} ${f(R * 0.04)} l${f(R * 0.2)} ${f(R * 0.5)} l${f(-R * 0.12)} ${f(0)} l${f(R * 0.14)} ${f(R * 0.45)}`;1700 const roadL = `M${f(cx - R * 1.6)} ${f(H)} L${f(fx - R * 0.1)} ${f(ground)} L${f(fx + R * 0.1)} ${f(ground)} L${f(cx + R * 0.7)} ${f(H)} Z`;1701 let dashes = "";1702 for (let i = 0; i < 7; i++) {1703 const t0 = Math.pow(i / 7, 1.4);1704 const t1 = Math.pow((i + 0.45) / 7, 1.4);1705 const px0 = cx - R * 0.45 + (fx - (cx - R * 0.45)) * t0;1706 const py0 = H + (ground - H) * t0;1707 const px1 = cx - R * 0.45 + (fx - (cx - R * 0.45)) * t1;1708 const py1 = H + (ground - H) * t1;1709 dashes += `M${f(px0)} ${f(py0)}L${f(px1)} ${f(py1)} `;1710 }1711 const tx = cx - R * 0.42;1712 const ty = cy + R * 1.05;1713 const rx = cx + R * 1.14;1714 const rungs: Array<[number, number, string]> = [[rx, cy + R * 1.06, "1.5× OUTFLOW"], [rx, cy + R * 0.88, "3× DEBRIS FIELD"], [rx, cy + R * 0.7, "8× WALL CLOUD"], [rx, cy + R * 0.5, "30× VORTEX"]];1715 return (1716 <g>1717 <defs>1718 <Linear id={`${id}-sc1`} stops={[[0, "#0b0f16"], [0.5, "#1f2937"], [1, "#0f172a"]]} />1719 <Linear id={`${id}-sc2`} stops={[[0, "#475569"], [1, "#0f172a"]]} />1720 <Radial id={`${id}-sc3`} color={p.secondary} o0={0.45} />1721 </defs>1722 <rect width={W} height={H} fill={`url(#${id}-sc1)`} />1723 <ellipse cx={f(cx - R * 0.2)} cy={f(cy - R * 1.2)} rx={f(R * 1.5)} ry={f(R * 0.45)} fill="#111827" />1724 <ellipse cx={f(fx + R * 0.3)} cy={f(cy - R * 1.05)} rx={f(R * 1.2)} ry={f(R * 0.4)} fill="#1f2937" />1725 <circle cx={f(cx - R * 0.6)} cy={f(cy - R * 0.4)} r={f(R * 0.7)} fill={`url(#${id}-sc3)`} />1726 <path d={bolt} fill="none" stroke={p.secondary} strokeOpacity={0.35} strokeWidth={7} strokeLinecap="round" strokeLinejoin="round" />1727 <path d={bolt} fill="none" stroke="#f7fee7" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />1728 <rect x={0} y={f(ground)} width={W} height={f(H - ground)} fill="#0b1210" />1729 <path d={funnel} fill={`url(#${id}-sc2)`} stroke={p.glow} strokeOpacity={0.55} strokeWidth={1.5} />1730 <path d={ribs} fill="none" stroke={p.glow} strokeOpacity={0.35} strokeWidth={1.2} />1731 <path d={debris} fill="#64748b" stroke="#cbd5e1" strokeOpacity={0.6} strokeWidth={0.8} />1732 <path d={roadL} fill="#1e293b" stroke="#475569" strokeWidth={1} />1733 <path d={dashes} stroke="#fde68a" strokeWidth={f(R * 0.03)} strokeLinecap="round" />1734 {!compact ? <Rungs items={rungs} color={p.glow} hot={p.secondary} size={R * 0.058} anchor="end" /> : null}1735 <polygon points={`${f(tx - R * 0.2)},${f(ty - R * 0.2)} ${f(tx + R * 0.2)},${f(ty - R * 0.2)} ${f(fx + R * 0.05)},${f(ground + R * 0.05)} ${f(fx - R * 0.35)},${f(ground + R * 0.05)}`} fill="#fef3c7" fillOpacity={0.12} />1736 <g>1737 <rect x={f(tx - R * 0.42)} y={f(ty - R * 0.28)} width={f(R * 0.84)} height={f(R * 0.4)} rx={f(R * 0.05)} fill="#1c1f26" stroke="#94a3b8" strokeWidth={1.5} />1738 <rect x={f(tx - R * 0.3)} y={f(ty - R * 0.55)} width={f(R * 0.6)} height={f(R * 0.3)} rx={f(R * 0.05)} fill="#1c1f26" stroke="#94a3b8" strokeWidth={1.5} />1739 <rect x={f(tx - R * 0.24)} y={f(ty - R * 0.5)} width={f(R * 0.48)} height={f(R * 0.18)} rx={f(R * 0.03)} fill="#334155" />1740 <path d={dots([[tx - R * 0.34, ty - R * 0.02, R * 0.04], [tx + R * 0.34, ty - R * 0.02, R * 0.04]])} fill="#ef4444" />1741 <rect x={f(tx - R * 0.44)} y={f(ty + R * 0.08)} width={f(R * 0.18)} height={f(R * 0.12)} rx={f(R * 0.03)} fill="#000" />1742 <rect x={f(tx + R * 0.26)} y={f(ty + R * 0.08)} width={f(R * 0.18)} height={f(R * 0.12)} rx={f(R * 0.03)} fill="#000" />1743 <path d={`M${f(tx - R * 0.3)} ${f(ty - R * 0.55)}v${f(-R * 0.22)}h${f(R * 0.06)}`} stroke="#94a3b8" strokeWidth={1.5} fill="none" />1744 <circle cx={f(tx - R * 0.24)} cy={f(ty - R * 0.78)} r={f(R * 0.03)} fill={p.secondary} />1745 </g>1746 {!compact ? <Verb x={cx - R * 0.7} y={cy - R * 0.02} text="ESCAPE" color={p.secondary} size={R * 0.078} /> : null}1747 </g>1748 );1749}17501751/* 30 — ELEVATOR 999: a lit cabin in an endless shaft, tall condensed floor numbers, express floors */1752function Elevator999(s: Stage) {1753 const { H, cx, cy, R, id, p, compact } = s;1754 const railL = cx - R * 0.66;1755 const railR = cx + R * 0.66;1756 let beams = "";1757 for (let y = -R * 0.1; y < H + R; y += R * 0.32) beams += `M${f(railL)} ${f(y)}H${f(railR)} `;1758 const cabW = R * 0.98;1759 const cabH = R * 1.22;1760 const cabY = cy + R * 0.02;1761 const floors = [1762 { n: "25", m: "×3.4", y: cy + R * 1.0 },1763 { n: "50", m: "×11", y: cy + R * 0.52 },1764 { n: "100", m: "×131", y: cy - R * 0.32 },1765 { n: "250", m: "×198k", y: cy - R * 0.76 },1766 ];1767 let chevrons = "";1768 for (let i = 0; i < 3; i++) chevrons += `M${f(railR + R * 0.2)} ${f(cy - R * 0.05 - i * R * 0.16)}l${f(R * 0.08)} ${f(-R * 0.09)}l${f(R * 0.08)} ${f(R * 0.09)} `;1769 return (1770 <g>1771 <defs>1772 <Linear id={`${id}-el1`} stops={[[0, "#000", 1], [0.28, p.bg, 0.1], [1, p.bg, 0]]} />1773 <Linear id={`${id}-el2`} stops={[[0, p.glow, 0.9], [1, p.primary, 0.2]]} />1774 <Radial id={`${id}-el3`} color={p.glow} o0={0.5} />1775 </defs>1776 <rect x={f(railL)} y={0} width={f(railR - railL)} height={H} fill="#0c0d13" />1777 <path d={beams} stroke="#2a2d3a" strokeWidth={1.5} />1778 <line x1={f(railL)} y1={0} x2={f(railL)} y2={H} stroke={p.primary} strokeOpacity={0.7} strokeWidth={3} />1779 <line x1={f(railR)} y1={0} x2={f(railR)} y2={H} stroke={p.primary} strokeOpacity={0.7} strokeWidth={3} />1780 <rect x={f(railL)} y={0} width={f(railR - railL)} height={f(cy)} fill={`url(#${id}-el1)`} />1781 <path d={`M${f(cx - R * 0.2)} 0V${f(cabY - cabH / 2)} M${f(cx + R * 0.2)} 0V${f(cabY - cabH / 2)}`} stroke="#9aa0b4" strokeWidth={1.5} />1782 <ellipse cx={f(cx)} cy={f(cabY)} rx={f(R * 0.9)} ry={f(R * 0.95)} fill={`url(#${id}-el3)`} />1783 <rect x={f(cx - cabW / 2)} y={f(cabY - cabH / 2)} width={f(cabW)} height={f(cabH)} rx={f(R * 0.05)} fill="#151821" stroke={p.primary} strokeWidth={2.5} />1784 <rect x={f(cx - cabW / 2 + R * 0.08)} y={f(cabY - cabH / 2 + R * 0.2)} width={f(cabW - R * 0.16)} height={f(cabH - R * 0.28)} fill={`url(#${id}-el2)`} />1785 <rect x={f(cx - cabW / 2 + R * 0.08)} y={f(cabY - cabH / 2 + R * 0.2)} width={f(cabW / 2 - R * 0.11)} height={f(cabH - R * 0.28)} fill="#1b1f2c" stroke={p.primary} strokeOpacity={0.6} strokeWidth={1} />1786 <rect x={f(cx + R * 0.03)} y={f(cabY - cabH / 2 + R * 0.2)} width={f(cabW / 2 - R * 0.11)} height={f(cabH - R * 0.28)} fill="#1b1f2c" stroke={p.primary} strokeOpacity={0.6} strokeWidth={1} />1787 <rect x={f(cx - cabW / 2 + R * 0.2)} y={f(cabY - cabH / 2 + R * 0.05)} width={f(cabW - R * 0.4)} height={f(R * 0.12)} rx={2} fill="#05060a" stroke={p.secondary} strokeOpacity={0.8} strokeWidth={1} />1788 <Small x={cx} y={cabY - cabH / 2 + R * 0.11} text="▲ 137" color={p.secondary} size={R * 0.075} weight={800} tracking="0.2em" />1789 {!compact ? (1790 <>1791 {floors.map((fl) => (1792 <g key={fl.n}>1793 <line x1={f(railL - R * 0.5)} y1={f(fl.y + R * 0.1)} x2={f(railL - R * 0.04)} y2={f(fl.y + R * 0.1)} stroke={p.primary} strokeOpacity={0.5} strokeWidth={1} />1794 <text x={f(railL - R * 0.08)} y={f(fl.y)} textAnchor="end" dominantBaseline="central" fontSize={f(R * 0.3)} fontWeight={800} fill={p.primary} fillOpacity={fl.n === "250" ? 1 : 0.75} transform={`translate(${f(railL - R * 0.08)} 0) scale(0.66 1) translate(${f(-(railL - R * 0.08))} 0)`} style={{ fontFamily: FONT, letterSpacing: "-0.04em" }}>1795 {fl.n}1796 </text>1797 <Small x={railR + R * 0.1} y={fl.y} text={fl.m} color={fl.n === "250" ? p.secondary : p.glow} size={R * (fl.n === "250" ? 0.085 : 0.065)} anchor="start" weight={700} tracking="0.04em" opacity={fl.n === "250" ? 1 : 0.7} />1798 </g>1799 ))}1800 <path d={chevrons} fill="none" stroke={p.secondary} strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" />1801 <Verb x={cx} y={cabY + cabH / 2 + R * 0.28} text="STEP OUT" color={p.secondary} size={R * 0.078} />1802 </>1803 ) : null}1804 </g>1805 );1806}18071808/* ---------------------------------------------------- Beyond Slots originals */18091810/* 31 — DROPZONE: capsule ricocheting through a peg tower, energy gates, value buckets, Deep Drop below */1811function Dropzone(s: Stage) {1812 const { cx, cy, R, id, p, compact } = s;1813 const rows = compact ? 6 : 9;1814 const gap = R * 0.2;1815 const top = cy - R * 1.02;1816 const pegs: Array<[number, number, number]> = [];1817 for (let r = 0; r < rows; r++) for (let k = 0; k <= r + 2; k++) pegs.push([cx + (k - (r + 2) / 2) * gap, top + r * gap, R * 0.022]);1818 let lane = 0;1819 let path = `M${f(cx)} ${f(top - gap * 0.8)}`;1820 const lit: Array<[number, number, number]> = [];1821 for (let r = 0; r < rows; r++) {1822 lane += s.rnd() > 0.5 ? 0.5 : -0.5;1823 const x = cx + lane * gap;1824 path += ` L${f(x)} ${f(top + r * gap + gap * 0.5)}`;1825 lit.push([x + (s.rnd() > 0.5 ? gap / 2 : -gap / 2), top + r * gap, R * 0.03]);1826 }1827 const capR = Math.min(rows - 1, 5);1828 const capX = cx + lane * gap * (capR / rows);1829 const capY = top + capR * gap + gap * 0.5;1830 const bucketY = top + rows * gap + gap * 0.3;1831 const values = ["25", "6", "2.5", "1.4", ".4", "1.4", "2.5", "6", "25"];1832 const bw = gap * 1.15;1833 const bx0 = cx - (values.length * bw) / 2;1834 const deepY = bucketY + gap * 1.35;1835 const deepPegs = scatterDots(s, 12, [cx - R * 0.9, deepY + gap * 0.3, R * 1.8, gap * 1.4], R * 0.02, R * 0.02);1836 return (1837 <g>1838 <defs>1839 <Radial id={`${id}-dz1`} color={p.primary} o0={0.35} />1840 <Radial id={`${id}-dz2`} color={p.glow} o0={1} mid={[0.4, p.primary, 0.7]} />1841 <Linear id={`${id}-dz3`} stops={[[0, p.secondary, 0.9], [0.5, p.primary, 0.25], [1, p.secondary, 0.9]]} dir="h" />1842 </defs>1843 <polygon points={`${f(cx - gap * 1.4)},${f(top - gap)} ${f(cx + gap * 1.4)},${f(top - gap)} ${f(cx + ((rows + 2) / 2) * gap + gap * 0.6)},${f(bucketY)} ${f(cx - ((rows + 2) / 2) * gap - gap * 0.6)},${f(bucketY)}`} fill={`url(#${id}-dz1)`} stroke={p.primary} strokeOpacity={0.35} strokeWidth={1} />1844 <path d={dots(pegs)} fill="#cbd5e1" fillOpacity={0.85} />1845 <path d={path} fill="none" stroke={p.primary} strokeOpacity={0.8} strokeWidth={1.5} strokeDasharray="3 4" />1846 <path d={dots(lit)} fill={p.glow} />1847 {!compact ? (1848 <>1849 <rect x={f(cx - gap * 1.3)} y={f(top + gap * 2.5 - 2)} width={f(gap * 1.1)} height={4} rx={2} fill={p.secondary} />1850 <Pill x={cx - gap * 1.85} y={top + gap * 2.5} text="×2" color={p.secondary} ink={p.secondary} size={R * 0.055} />1851 <rect x={f(cx + gap * 0.9)} y={f(top + gap * 6.5 - 2)} width={f(gap * 1.1)} height={4} rx={2} fill={p.secondary} />1852 <Pill x={cx + gap * 2.6} y={top + gap * 6.5} text="×3" color={p.secondary} ink={p.secondary} size={R * 0.055} />1853 </>1854 ) : null}1855 <circle cx={f(capX)} cy={f(capY)} r={f(gap * 0.5)} fill={`url(#${id}-dz2)`} opacity={0.8} />1856 <rect x={f(capX - gap * 0.16)} y={f(capY - gap * 0.3)} width={f(gap * 0.32)} height={f(gap * 0.6)} rx={f(gap * 0.16)} fill="#e0fbff" stroke={p.primary} strokeWidth={1.5} />1857 <rect x={f(bx0)} y={f(bucketY)} width={f(values.length * bw)} height={f(gap * 0.9)} fill={`url(#${id}-dz3)`} opacity={0.35} />1858 {values.map((v, i) => {1859 const edge = Math.abs(i - 4) / 4;1860 return (1861 <g key={i}>1862 <rect x={f(bx0 + i * bw + 1.5)} y={f(bucketY)} width={f(bw - 3)} height={f(gap * 0.9)} rx={3} fill="#050a14" fillOpacity={0.8} stroke={edge > 0.6 ? p.secondary : p.primary} strokeOpacity={0.4 + edge * 0.6} strokeWidth={1} />1863 {!compact ? <Small x={bx0 + i * bw + bw / 2} y={bucketY + gap * 0.45} text={v} color={edge > 0.6 ? p.secondary : "#e0fbff"} size={R * (edge > 0.6 ? 0.07 : 0.058)} weight={800} tracking="0" /> : null}1864 </g>1865 );1866 })}1867 {!compact ? (1868 <>1869 <rect x={f(cx - R * 1.0)} y={f(deepY)} width={f(R * 2.0)} height={f(gap * 2.2)} rx={4} fill={p.secondary} fillOpacity={0.06} stroke={p.secondary} strokeOpacity={0.6} strokeWidth={1} strokeDasharray="6 5" />1870 <path d={deepPegs} fill={p.secondary} fillOpacity={0.55} />1871 <Pill x={cx} y={deepY} text="DEEP DROP ×0 → ×10" color={p.secondary} ink={p.secondary} bg={p.bg} size={R * 0.058} />1872 <Verb x={cx + R * 0.92} y={cy - R * 0.5} text="DROP" color={p.primary} size={R * 0.078} />1873 </>1874 ) : null}1875 </g>1876 );1877}18781879/* 32 — GRID//BREAK: 8×8 neon block grid, a wave down one column, detonating cluster, ×2 ×4 ×8 chain */1880function GridBreak(s: Stage) {1881 const { W, H, cx, cy, R, id, p, compact } = s;1882 const n = 8;1883 const c = R * 0.225;1884 const gx = cx - (n * c) / 2 - R * 0.2;1885 const gy = cy - (n * c) / 2 - R * 0.05;1886 const col = 5;1887 const tints = [p.primary, p.secondary, "#a78bfa", "#f472b6", "#facc15", "#22d3ee"];1888 const paths = tints.map(() => "");1889 const blown = new Set(["5,3", "5,4", "4,4", "5,5", "6,4", "6,5"]);1890 for (let r = 0; r < n; r++) {1891 for (let k = 0; k < n; k++) {1892 if (blown.has(`${k},${r}`)) continue;1893 const t = Math.floor(s.rnd() * tints.length);1894 paths[t] += `M${f(gx + k * c + 2)} ${f(gy + r * c + 2)}h${f(c - 4)}v${f(c - 4)}h${f(-(c - 4))}z `;1895 }1896 }1897 const ex = gx + col * c + c / 2;1898 const ey = gy + 4 * c + c / 2;1899 let shards = "";1900 for (let i = 0; i < 12; i++) {1901 const a = s.rnd() * PI * 2;1902 const d = c * (0.9 + s.rnd() * 1.6);1903 const k = c * (0.12 + s.rnd() * 0.16);1904 shards += `M${f(ex + Math.cos(a) * d)} ${f(ey + Math.sin(a) * d)}h${f(k)}v${f(k)}h${f(-k)}z `;1905 }1906 let scan = "";1907 for (let y = 0; y < H; y += 5) scan += `M0 ${f(y)}H${f(W)} `;1908 return (1909 <g>1910 <defs>1911 <Linear id={`${id}-gb1`} stops={[[0, p.secondary, 0], [0.5, p.secondary, 0.55], [1, p.secondary, 0.15]]} />1912 <Radial id={`${id}-gb2`} color={p.glow} o0={0.9} />1913 </defs>1914 <path d={scan} stroke="#fff" strokeOpacity={0.035} strokeWidth={1.5} />1915 <rect x={f(gx - R * 0.08)} y={f(gy - R * 0.08)} width={f(n * c + R * 0.16)} height={f(n * c + R * 0.16)} rx={f(R * 0.06)} fill="#070b10" stroke={p.primary} strokeOpacity={0.5} strokeWidth={2} />1916 {paths.map((d, i) => (d ? <path key={i} d={d} fill={tints[i]} fillOpacity={0.82} /> : null))}1917 <rect x={f(gx + col * c)} y={f(gy - R * 0.3)} width={f(c)} height={f(n * c + R * 0.3)} fill={`url(#${id}-gb1)`} />1918 <line x1={f(ex)} y1={f(gy - R * 0.3)} x2={f(ex)} y2={f(gy + n * c)} stroke="#fff" strokeOpacity={0.85} strokeWidth={1.5} />1919 <polygon points={`${f(ex - c * 0.45)},${f(gy + n * c + R * 0.22)} ${f(ex + c * 0.45)},${f(gy + n * c + R * 0.22)} ${f(ex)},${f(gy + n * c + R * 0.06)}`} fill={p.secondary} />1920 <circle cx={f(ex)} cy={f(ey)} r={f(c * 1.5)} fill={`url(#${id}-gb2)`} />1921 <polygon points={svgStar(ex, ey, c * 1.7, c * 0.7, 8)} fill={p.glow} fillOpacity={0.9} />1922 <polygon points={svgStar(ex, ey, c * 0.9, c * 0.35, 8)} fill="#fff" />1923 <path d={shards} fill={p.primary} />1924 {!compact ? (1925 <>1926 <Pill x={gx + n * c + R * 0.28} y={gy + c * 5.5} text="×2" color={p.primary} ink={p.primary} size={R * 0.07} />1927 <Pill x={gx + n * c + R * 0.29} y={gy + c * 3.9} text="×4" color={p.primary} ink={p.glow} size={R * 0.085} />1928 <Pill x={gx + n * c + R * 0.3} y={gy + c * 2.0} text="×8" color={p.secondary} ink="#fff" bg="#0c2233" size={R * 0.1} />1929 <Small x={gx + n * c + R * 0.3} y={gy + c * 1.0} text="CHAIN" color={p.secondary} size={R * 0.06} tracking="0.24em" />1930 <Verb x={ex} y={gy + n * c + R * 0.45} text="FIRE WAVE" color={p.secondary} size={R * 0.072} />1931 </>1932 ) : null}1933 </g>1934 );1935}19361937/* 33 — THE VAULT: colossal door, five concentric locked layers, digits being revealed at the core */1938function TheVault(s: Stage) {1939 const { cx, cy, R, id, p, compact } = s;1940 const vx = cx;1941 const vy = cy + R * 0.08;1942 let bolts = "";1943 const bl: Array<[number, number, number]> = [];1944 for (let i = 0; i < 18; i++) {1945 const a = (i / 18) * PI * 2;1946 bl.push([vx + Math.cos(a) * R * 1.3, vy + Math.sin(a) * R * 1.3, R * 0.028]);1947 }1948 bolts = dots(bl);1949 let ticks = "";1950 for (let i = 0; i < 60; i++) {1951 const a = (i / 60) * PI * 2;1952 const len = i % 5 === 0 ? R * 0.1 : R * 0.05;1953 ticks += `M${f(vx + Math.cos(a) * R * 1.05)} ${f(vy + Math.sin(a) * R * 1.05)}L${f(vx + Math.cos(a) * (R * 1.05 - len))} ${f(vy + Math.sin(a) * (R * 1.05 - len))} `;1954 }1955 const gaugeA0 = PI * 0.75;1956 const gaugeA1 = PI * 2.05;1957 const layers = ["1.20×", "1.71×", "3.12×", "7.79×", "31.2×"];1958 const lx = cx - R * 1.12;1959 const rungs: Array<[number, number, string]> = layers.map((l, i) => [lx, cy + R * 0.9 - i * R * 0.36, l]);1960 const digits = ["4", "7", "?"];1961 return (1962 <g>1963 <defs>1964 <Radial id={`${id}-tv1`} color={p.glow} o0={0.95} mid={[0.5, p.primary, 0.45]} />1965 <Linear id={`${id}-tv2`} stops={[[0, "#2a2530"], [0.5, "#0f0d13"], [1, "#26212c"]]} dir="d" />1966 </defs>1967 <circle cx={f(vx)} cy={f(vy)} r={f(R * 1.42)} fill={`url(#${id}-tv2)`} stroke={p.primary} strokeOpacity={0.9} strokeWidth={f(R * 0.03)} />1968 <circle cx={f(vx)} cy={f(vy)} r={f(R * 1.18)} fill="#100e14" stroke={p.primary} strokeOpacity={0.5} strokeWidth={1.5} />1969 <path d={bolts} fill={p.primary} stroke="#5a4a20" strokeWidth={1} />1970 <path d={ticks} stroke={p.glow} strokeOpacity={0.6} strokeWidth={1.2} />1971 <circle cx={f(vx)} cy={f(vy)} r={f(R * 0.92)} fill="#0b0a0e" stroke={p.primary} strokeOpacity={0.7} strokeWidth={2} />1972 <circle cx={f(vx)} cy={f(vy)} r={f(R * 0.82)} fill="none" stroke={p.secondary} strokeOpacity={0.9} strokeWidth={f(R * 0.05)} strokeDasharray={`${f(R * 0.09)} ${f(R * 0.06)}`} />1973 <circle cx={f(vx)} cy={f(vy)} r={f(R * 0.82)} fill="none" stroke={p.secondary} strokeOpacity={0.35} strokeWidth={f(R * 0.12)} strokeDasharray={`${f(R * 0.4)} ${f(R * 0.9)}`} />1974 <circle cx={f(vx)} cy={f(vy)} r={f(R * 0.7)} fill="#141117" stroke={p.primary} strokeOpacity={0.6} strokeWidth={1.5} />1975 <path d={svgArc(vx, vy, R * 0.6, gaugeA0, gaugeA1)} fill="none" stroke="#2b2630" strokeWidth={f(R * 0.09)} strokeLinecap="round" />1976 <path d={svgArc(vx, vy, R * 0.6, gaugeA0, gaugeA0 + (gaugeA1 - gaugeA0) * 0.7)} fill="none" stroke={p.primary} strokeWidth={f(R * 0.09)} strokeLinecap="round" />1977 <circle cx={f(vx)} cy={f(vy)} r={f(R * 0.48)} fill={`url(#${id}-tv1)`} />1978 <circle cx={f(vx)} cy={f(vy)} r={f(R * 0.48)} fill="none" stroke={p.glow} strokeWidth={2} />1979 {digits.map((d, i) => (1980 <g key={i}>1981 <rect x={f(vx - R * 0.36 + i * R * 0.25)} y={f(vy - R * 0.16)} width={f(R * 0.22)} height={f(R * 0.32)} rx={f(R * 0.03)} fill="#0b0a0e" stroke={d === "?" ? p.secondary : p.primary} strokeWidth={1.5} />1982 <Small x={vx - R * 0.25 + i * R * 0.25} y={vy} text={d} color={d === "?" ? p.secondary : p.glow} size={R * 0.2} weight={800} tracking="0" />1983 </g>1984 ))}1985 <circle cx={f(vx)} cy={f(vy - R * 1.3)} r={f(R * 0.05)} fill="#ef4444" />1986 <circle cx={f(vx)} cy={f(vy - R * 1.3)} r={f(R * 0.11)} fill="#ef4444" fillOpacity={0.3} />1987 {!compact ? (1988 <>1989 <path d={`M${f(lx - R * 0.02)} ${f(cy + R * 1.0)}V${f(cy - R * 0.62)}`} stroke={p.primary} strokeOpacity={0.35} strokeWidth={1} />1990 <Rungs items={rungs} color={p.primary} hot={p.glow} size={R * 0.065} anchor="start" />1991 <Small x={cx + R * 1.02} y={cy - R * 0.68} text="LAYER 3 / 5" color={p.secondary} size={R * 0.062} tracking="0.22em" anchor="end" />1992 <Small x={cx + R * 1.02} y={cy - R * 0.54} text="BIOMETRIC RING" color={p.glow} size={R * 0.055} tracking="0.16em" anchor="end" opacity={0.75} />1993 <Verb x={cx - R * 0.42} y={cy + R * 1.14} text="SECURE" color={p.primary} size={R * 0.072} />1994 <Verb x={cx + R * 0.45} y={cy + R * 1.14} text="OPEN" color={p.secondary} size={R * 0.072} />1995 </>1996 ) : null}1997 </g>1998 );1999}20002001/* 34 — ORBIT: burning core, concentric orbits with planets/satellites/comets, an impulse beam, Supernova */2002function Orbit(s: Stage) {2003 const { W, H, cx, cy, R, id, p, compact } = s;2004 const stars = scatterDots(s, 60, [0, 0, W, H], 0.5, 1.3);2005 const ox = cx;2006 const oy = cy + R * 0.05;2007 const orbits = [0.42, 0.68, 0.94, 1.2];2008 const debris: Array<[number, number, number]> = [];2009 for (let i = 0; i < 16; i++) {2010 const r = R * orbits[Math.floor(s.rnd() * orbits.length)];2011 const a = s.rnd() * PI * 2;2012 debris.push([ox + Math.cos(a) * r, oy + Math.sin(a) * r, R * 0.018]);2013 }2014 const at = (k: number, a: number): [number, number] => [ox + Math.cos(a) * R * orbits[k], oy + Math.sin(a) * R * orbits[k]];2015 const [sx, sy] = at(0, -2.2);2016 const [px, py] = at(1, 0.6);2017 const [cmx, cmy] = at(2, -0.9);2018 const [ggx, ggy] = at(3, 2.62);2019 const [qx, qy] = at(3, -0.62);2020 const beamA = -1.05;2021 const beamLen = R * 1.45;2022 const bx1 = ox + Math.cos(beamA - 0.09) * beamLen;2023 const by1 = oy + Math.sin(beamA - 0.09) * beamLen;2024 const bx2 = ox + Math.cos(beamA + 0.09) * beamLen;2025 const by2 = oy + Math.sin(beamA + 0.09) * beamLen;2026 return (2027 <g>2028 <defs>2029 <Radial id={`${id}-ob1`} color={p.secondary} o0={0.8} mid={[0.25, p.secondary, 0.3]} />2030 <Radial id={`${id}-ob2`} color="#fff" o0={1} mid={[0.45, p.secondary, 1]} o1={0.9} />2031 <Linear id={`${id}-ob3`} stops={[[0, p.secondary, 0.7], [1, p.secondary, 0]]} />2032 </defs>2033 <path d={stars} fill="#fff" fillOpacity={0.7} />2034 <circle cx={f(ox)} cy={f(oy)} r={f(R * 0.75)} fill={`url(#${id}-ob1)`} />2035 {orbits.map((k, i) => (2036 <circle key={i} cx={f(ox)} cy={f(oy)} r={f(R * k)} fill="none" stroke={p.primary} strokeOpacity={i === 3 ? 0.45 : 0.6} strokeWidth={1.2} strokeDasharray={i === 3 ? "4 7" : undefined} />2037 ))}2038 <polygon points={`${f(ox)},${f(oy)} ${f(bx1)},${f(by1)} ${f(bx2)},${f(by2)}`} fill={`url(#${id}-ob3)`} opacity={0.65} />2039 <line x1={f(ox)} y1={f(oy)} x2={f(ox + Math.cos(beamA) * beamLen)} y2={f(oy + Math.sin(beamA) * beamLen)} stroke="#fff" strokeOpacity={0.9} strokeWidth={1.5} />2040 <circle cx={f(ox + Math.cos(beamA) * R * 0.94)} cy={f(oy + Math.sin(beamA) * R * 0.94)} r={f(R * 0.06)} fill="#fff" />2041 <polygon points={svgStar(ox, oy, R * 0.36, R * 0.18, 12)} fill={p.secondary} fillOpacity={0.6} />2042 <circle cx={f(ox)} cy={f(oy)} r={f(R * 0.2)} fill={`url(#${id}-ob2)`} />2043 <path d={dots(debris)} fill="#e2e8f0" fillOpacity={0.85} />2044 <Shape shape="satellite" color="#cbd5e1" accent={p.secondary} x={sx} y={sy} size={R * 0.34} rotate={-20} />2045 <Shape shape="planet" color="#60a5fa" accent={p.primary} x={px} y={py} size={R * 0.5} rotate={-15} />2046 <Shape shape="comet" color="#fef3c7" accent={p.secondary} x={cmx} y={cmy} size={R * 0.46} />2047 <Shape shape="planet" color="#fb923c" accent="#fde68a" x={ggx} y={ggy} size={R * 0.72} rotate={12} />2048 {!compact ? (2049 <>2050 <polygon points={svgStar(qx, qy, R * 0.16, R * 0.05, 4)} fill={p.primary} />2051 <Small x={sx} y={sy + R * 0.22} text="0.8×" color={p.glow} size={R * 0.055} weight={700} tracking="0.02em" />2052 <Small x={px} y={py + R * 0.3} text="2×" color={p.glow} size={R * 0.06} weight={700} tracking="0.02em" />2053 <Small x={cmx + R * 0.02} y={cmy - R * 0.26} text="4×" color={p.glow} size={R * 0.06} weight={700} tracking="0.02em" />2054 <Small x={ggx} y={ggy + R * 0.4} text="8×" color={p.glow} size={R * 0.065} weight={800} tracking="0.02em" />2055 <Small x={qx} y={qy - R * 0.22} text="QUASAR 25×" color={p.primary} size={R * 0.055} weight={800} tracking="0.1em" />2056 <Pill x={cx - R * 0.82} y={cy - R * 0.62} text="SUPERNOVA" color={p.secondary} ink={p.secondary} size={R * 0.062} />2057 <Verb x={cx + R * 0.7} y={cy + R * 1.08} text="FIRE IMPULSE" color={p.secondary} size={R * 0.072} />2058 </>2059 ) : null}2060 </g>2061 );2062}20632064/* 35 — ESCAPE 99: tower cross-section with rooms and a stairwell, checkpoint rail 10 · 25 · 50 · 75 · 99 */2065function Escape99(s: Stage) {2066 const { H, cx, cy, R, id, p, compact } = s;2067 const tw = R * 1.16;2068 const tx = cx - R * 0.3;2069 const top = cy - R * 1.12;2070 const fh = R * 0.29;2071 const nFloors = Math.floor((H - top) / fh) + 1;2072 let floors = "";2073 for (let i = 1; i < nFloors; i++) floors += `M${f(tx - tw / 2)} ${f(top + i * fh)}h${f(tw)} `;2074 let stairs = "";2075 for (let i = 0; i < nFloors; i++) {2076 const y = top + i * fh;2077 stairs += `M${f(tx - R * 0.1)} ${f(y + fh)} l${f(R * 0.05)} ${f(-fh * 0.25)} h${f(R * 0.05)} l${f(R * 0.05)} ${f(-fh * 0.25)} h${f(R * 0.05)} l${f(R * 0.05)} ${f(-fh * 0.25)} `;2078 }2079 const roomX = (side: -1 | 1) => tx + side * (R * 0.1 + (tw / 2 - R * 0.1) / 2);2080 const fy = (i: number) => top + i * fh + fh / 2;2081 let crenel = "";2082 for (let i = 0; i < 6; i++) crenel += `M${f(tx - tw / 2 + i * (tw / 6) + 2)} ${f(top)}v${f(-R * 0.08)}h${f(tw / 12)}v${f(R * 0.08)}z `;2083 let spikes = "";2084 for (let i = 0; i < 5; i++) spikes += `M${f(roomX(-1) - R * 0.14 + i * R * 0.07)} ${f(fy(4) + fh * 0.4)}l${f(R * 0.035)} ${f(-fh * 0.55)}l${f(R * 0.035)} ${f(fh * 0.55)}z `;2085 const hx = tx;2086 const hy = fy(3) + fh * 0.05;2087 const rx = cx + R * 0.9;2088 const cps = [2089 { n: "10", m: "×1.4", t: 0 },2090 { n: "25", m: "×2.9", t: 0.25 },2091 { n: "50", m: "×18", t: 0.5 },2092 { n: "75", m: "×190", t: 0.75 },2093 { n: "99", m: "×3k", t: 1 },2094 ];2095 const railTop = cy - R * 0.72;2096 const railBot = cy + R * 1.05;2097 const leaves = scatterDots(s, 16, [cx - R * 1.25, cy + R * 0.4, R * 0.7, R * 0.9], 1.2, 2.6);2098 return (2099 <g>2100 <defs>2101 <Radial id={`${id}-es1`} color={p.primary} o0={0.4} />2102 <Radial id={`${id}-es2`} color={p.secondary} o0={0.9} />2103 <Linear id={`${id}-es3`} stops={[[0, p.glow, 0.8], [1, p.primary, 0.1]]} />2104 </defs>2105 <ellipse cx={f(tx)} cy={f(top)} rx={f(R * 1.2)} ry={f(R * 0.7)} fill={`url(#${id}-es1)`} />2106 <rect x={f(tx - tw / 2)} y={f(top)} width={f(tw)} height={f(H - top)} fill="#0a1411" stroke={p.primary} strokeOpacity={0.75} strokeWidth={2} />2107 <path d={crenel} fill="#0a1411" stroke={p.primary} strokeOpacity={0.75} strokeWidth={1.5} />2108 <path d={floors} stroke={p.primary} strokeOpacity={0.35} strokeWidth={1} />2109 <rect x={f(tx - R * 0.1)} y={f(top)} width={f(R * 0.2)} height={f(H - top)} fill="#06090a" />2110 <path d={stairs} fill="none" stroke={p.primary} strokeOpacity={0.6} strokeWidth={1.2} />2111 <circle cx={f(tx)} cy={f(top + fh * 0.5)} r={f(R * 0.22)} fill={`url(#${id}-es2)`} />2112 <Shape shape="ring" color="#a78bfa" accent="#f5f3ff" x={roomX(1)} y={fy(1)} size={fh * 0.9} />2113 <Shape shape="chest" color="#b8741c" accent="#fde68a" x={roomX(-1)} y={fy(2) + fh * 0.05} size={fh * 0.9} />2114 <Shape shape="skull" color="#f8fafc" accent="#94a3b8" x={roomX(1)} y={fy(3)} size={fh * 0.8} />2115 <path d={spikes} fill={p.secondary} />2116 <Shape shape="hex" color={p.primary} accent={p.glow} x={roomX(1)} y={fy(5)} size={fh * 0.85} label="×" />2117 <path d={`M${f(roomX(-1))} ${f(fy(6) + fh * 0.35)}V${f(fy(6))} L${f(roomX(-1) - R * 0.14)} ${f(fy(6) - fh * 0.3)} M${f(roomX(-1))} ${f(fy(6))} L${f(roomX(-1) + R * 0.14)} ${f(fy(6) - fh * 0.3)}`} fill="none" stroke={p.secondary} strokeWidth={2.5} strokeLinecap="round" />2118 <Shape shape="chest" color="#b8741c" accent="#fde68a" x={roomX(1)} y={fy(7) + fh * 0.05} size={fh * 0.9} />2119 <g fill={p.glow} stroke="#06090a" strokeWidth={1}>2120 <circle cx={f(hx)} cy={f(hy - fh * 0.22)} r={f(fh * 0.13)} />2121 <rect x={f(hx - fh * 0.1)} y={f(hy - fh * 0.1)} width={f(fh * 0.2)} height={f(fh * 0.32)} rx={f(fh * 0.05)} />2122 <path d={`M${f(hx - fh * 0.06)} ${f(hy + fh * 0.2)}l${f(-fh * 0.1)} ${f(fh * 0.22)} M${f(hx + fh * 0.06)} ${f(hy + fh * 0.2)}l${f(fh * 0.12)} ${f(fh * 0.2)}`} stroke={p.glow} strokeWidth={f(fh * 0.09)} strokeLinecap="round" fill="none" />2123 </g>2124 <path d={leaves} fill={p.primary} fillOpacity={0.45} />2125 {!compact ? (2126 <>2127 <line x1={f(rx)} y1={f(railTop)} x2={f(rx)} y2={f(railBot)} stroke={`url(#${id}-es3)`} strokeWidth={3} strokeLinecap="round" />2128 {cps.map((cp) => {2129 const y = railBot - (railBot - railTop) * cp.t;2130 const summit = cp.n === "99";2131 return (2132 <g key={cp.n}>2133 <circle cx={f(rx)} cy={f(y)} r={f(R * (summit ? 0.05 : 0.03))} fill={summit ? p.secondary : p.glow} />2134 <Small x={rx - R * 0.09} y={y} text={cp.n} color={summit ? p.secondary : "#fff"} size={R * (summit ? 0.11 : 0.075)} anchor="end" weight={800} tracking="0" />2135 <Small x={rx + R * 0.09} y={y} text={cp.m} color={summit ? p.secondary : p.glow} size={R * (summit ? 0.07 : 0.055)} anchor="start" weight={700} tracking="0.04em" opacity={summit ? 1 : 0.75} />2136 </g>2137 );2138 })}2139 <Verb x={cx - R * 0.98} y={cy - R * 0.62} text="CLIMB" color={p.primary} size={R * 0.075} />2140 <Verb x={cx - R * 0.9} y={cy - R * 0.3} text="CASH OUT" color={p.secondary} size={R * 0.068} />2141 </>2142 ) : null}2143 </g>2144 );2145}21462147/* Fallback for unknown slugs: palette glow + the game's top symbols if known. */2148function Generic(s: Stage) {2149 const { W, H, cx, cy, R, id, p, compact } = s;2150 const stars = scatterDots(s, 40, [0, 0, W, H], 0.5, 1.4);2151 return (2152 <g>2153 <defs>2154 <Radial id={`${id}-gn1`} color={p.primary} o0={0.5} />2155 </defs>2156 <path d={stars} fill="#fff" fillOpacity={0.6} />2157 <circle cx={f(cx)} cy={f(cy)} r={f(R * 1.3)} fill={`url(#${id}-gn1)`} />2158 <circle cx={f(cx)} cy={f(cy)} r={f(R * 0.9)} fill="none" stroke={p.secondary} strokeOpacity={0.5} strokeWidth={2} />2159 <Sym s={s} id="W" x={cx} y={cy} size={R * 0.9} />2160 {!compact ? (2161 <>2162 <Sym s={s} id="P1" x={cx - R * 0.85} y={cy + R * 0.55} size={R * 0.5} />2163 <Sym s={s} id="S" x={cx + R * 0.85} y={cy - R * 0.55} size={R * 0.5} />2164 </>2165 ) : null}2166 </g>2167 );2168}21692170const SCENES: Record<string, (s: Stage) => React.ReactNode> = {2171 "neon-vault": NeonVault,2172 "cosmic-collapse": CosmicCollapse,2173 "golden-emperor": GoldenEmperor,2174 "diamond-heist": DiamondHeist,2175 "arctic-fortune": ArcticFortune,2176 "inferno-reels": InfernoReels,2177 "quantum-jackpot": QuantumJackpot,2178 "midnight-tokyo": MidnightTokyo,2179 "pharaoh-protocol": PharaohProtocol,2180 "lucky-circuit": LuckyCircuit,2181 "void-miner": VoidMiner,2182 "royal-circuit": RoyalCircuit,2183 "dragon-core": DragonCore,2184 "deep-treasure": DeepTreasure,2185 "moonbase-77": Moonbase77,2186 "wild-temple": WildTemple,2187 "zero-gravity": ZeroGravity,2188 "reel-reactor": ReelReactor,2189 obsidian: Obsidian,2190 "spinza-original": SpinzaOriginal,2191 /* Risk Games */2192 skyfall: Skyfall,2193 "deep-dive": DeepDive,2194 "rocket-run": RocketRun,2195 "bank-heist": BankHeist,2196 volcano: Volcano,2197 "black-hole": BlackHole,2198 freefall: Freefall,2199 "core-meltdown": CoreMeltdown,2200 "storm-chase": StormChase,2201 "elevator-999": Elevator999,2202 /* Beyond Slots */2203 dropzone: Dropzone,2204 "grid-break": GridBreak,2205 "the-vault": TheVault,2206 orbit: Orbit,2207 "escape-99": Escape99,2208};22092210/* ------------------------------------------------------------------- title */22112212interface TitleSpec {2213 caps?: boolean;2214 /** Letter spacing in em. */2215 tracking?: number;2216 weight?: 700 | 800;2217 /** Solid fill colour or a [from, to] gradient. */2218 fill?: string | [string, string];2219 glow?: string;2220 eyebrow?: string;2221 eyebrowColor?: string;2222 align?: "left" | "center";2223 /** Force two lines on the card (long names). */2224 split?: boolean;2225 /** Second line rendered small with wide tracking (e.g. PHARAOH / protocol). */2226 subline?: boolean;2227 skew?: number;2228 /** Hard offset shadow colour (retro). */2229 shadow?: string;2230 /** Carved outline. */2231 outline?: string;2232 /** Scale factor applied to the fitted size (Obsidian whispers). */2233 scale?: number;2234 /** Monospace face (control-room / puzzle-grid voices). */2235 mono?: boolean;2236 /** Horizontal glyph stretch: < 1 tall condensed, > 1 extended. */2237 stretch?: number;2238 /** A substring of the name rendered in another colour (GRID//BREAK). */2239 accent?: { text: string; fill: string };2240}22412242const TITLES: Record<string, TitleSpec> = {2243 "neon-vault": { caps: true, tracking: -0.03, weight: 800, glow: "#00f0ff", eyebrow: "CRACK THE GRID", eyebrowColor: "#ff2bd6" },2244 "cosmic-collapse": { caps: false, tracking: -0.05, weight: 800, fill: ["#ffffff", "#c084fc"], glow: "#a78bfa", eyebrow: "EVENT HORIZON", eyebrowColor: "#67e8f9" },2245 "golden-emperor": { caps: true, tracking: 0.14, weight: 700, fill: ["#fff1c2", "#d4a72c"], glow: "#ffd166", align: "center", eyebrow: "IMPERIAL DYNASTY", eyebrowColor: "#e8b4b8", split: true },2246 "diamond-heist": { caps: true, tracking: -0.02, weight: 800, fill: ["#ffffff", "#bfefff"], glow: "#7dd3fc", eyebrow: "MINI · MINOR · MAJOR · GRAND", eyebrowColor: "#c0c0c0" },2247 "arctic-fortune": { caps: true, tracking: 0.02, weight: 700, fill: ["#ffffff", "#bfe9ff"], glow: "#7dd3fc", eyebrow: "AVALANCHE MULTIPLIER", eyebrowColor: "#a78bfa" },2248 "inferno-reels": { caps: true, tracking: -0.04, weight: 800, fill: ["#ffd166", "#ff4500"], glow: "#ff7a1a", eyebrow: "EXPLODING WILDS", eyebrowColor: "#ffb347" },2249 "quantum-jackpot": { caps: false, tracking: -0.04, weight: 800, fill: ["#ffffff", "#67e8f9"], glow: "#22d3ee", eyebrow: "ONE SYMBOL · SEVERAL STATES", eyebrowColor: "#c084fc" },2250 "midnight-tokyo": { caps: true, tracking: 0.06, weight: 800, fill: ["#ffffff", "#ff2bd6"], glow: "#ff2bd6", eyebrow: "SHIBUYA · 02:00", eyebrowColor: "#22d3ee" },2251 "pharaoh-protocol": { caps: true, tracking: 0.04, weight: 800, fill: ["#fff7d6", "#ffd166"], glow: "#fbbf24", subline: true, eyebrowColor: "#22d3ee" },2252 "lucky-circuit": { caps: true, tracking: 0.0, weight: 800, fill: "#facc15", shadow: "#f472b6", eyebrow: "INSERT COIN · WIN OFTEN", eyebrowColor: "#22d3ee" },2253 "void-miner": { caps: true, tracking: 0.1, weight: 700, fill: ["#e9d5ff", "#fbbf24"], glow: "#a78bfa", eyebrow: "DARK MATTER CORE", eyebrowColor: "#fbbf24" },2254 "royal-circuit": { caps: true, tracking: -0.02, weight: 800, fill: ["#fff7d6", "#ffd166"], glow: "#ffd166", skew: -10, eyebrow: "8 LAPS TO THE PIT LANE", eyebrowColor: "#ef4444" },2255 "dragon-core": { caps: true, tracking: -0.03, weight: 800, fill: ["#ffd166", "#ef4444"], glow: "#f97316", eyebrow: "TWENTY SCALES", eyebrowColor: "#22d3ee" },2256 "deep-treasure": { caps: false, tracking: -0.04, weight: 800, fill: ["#dffaff", "#22d3ee"], glow: "#22d3ee", eyebrow: "DESCENT ×1 → ×10", eyebrowColor: "#fbbf24" },2257 "moonbase-77": { caps: true, tracking: 0.12, weight: 700, fill: "#fb923c", glow: "#fb923c", eyebrow: "LOW GRAVITY · WILDS DRIFT", eyebrowColor: "#22d3ee" },2258 "wild-temple": { caps: true, tracking: 0.05, weight: 800, fill: ["#fde68a", "#fbbf24"], outline: "#0a1a10", glow: "#34d399", eyebrow: "THE JAGUAR MOVES", eyebrowColor: "#34d399" },2259 "zero-gravity": { caps: true, tracking: 0.22, weight: 700, fill: ["#ffffff", "#a78bfa"], glow: "#22d3ee", eyebrow: "5×3 → 6×4 → 7×5", eyebrowColor: "#67e8f9" },2260 "reel-reactor": { caps: true, tracking: -0.03, weight: 800, fill: "#a3e635", glow: "#22c55e", eyebrow: "EVERY DEAD SPIN HEATS THE CORE", eyebrowColor: "#facc15" },2261 obsidian: { caps: true, tracking: 0.42, weight: 700, fill: "#f5e6c8", align: "center", scale: 0.62 },2262 "spinza-original": { caps: true, tracking: -0.02, weight: 800, fill: ["#ffffff", "#c4b5fd"], glow: "#8b5cf6", subline: true, eyebrowColor: "#22d3ee" },2263 /* Risk Games — each in its own voice */2264 skyfall: { caps: true, tracking: 0.2, weight: 700, fill: ["#ffffff", "#7dd3fc"], glow: "#7dd3fc", eyebrow: "ALTITUDE PAYS · RISK GAME", eyebrowColor: "#fbbf24" },2265 "deep-dive": { caps: true, tracking: 0.3, weight: 700, fill: ["#dffaff", "#22d3ee"], glow: "#0e7490", eyebrow: "1,000 M = ×2 · 3,000 M = ×5", eyebrowColor: "#34d399" },2266 "rocket-run": { caps: true, tracking: -0.05, weight: 800, skew: -9, fill: ["#fff7ed", "#fb923c"], glow: "#fb923c", eyebrow: "MARS OR BUST", eyebrowColor: "#f472b6" },2267 "bank-heist": { caps: true, tracking: 0.08, weight: 800, fill: ["#f5e6c8", "#c9a961"], glow: "#ef4444", eyebrow: "THE BAG KEEPS FILLING", eyebrowColor: "#ef4444" },2268 volcano: { caps: true, tracking: -0.01, weight: 800, fill: ["#ffd166", "#ff5c3d"], outline: "#2a0c07", glow: "#ff8a5c", eyebrow: "DEEPER CRYSTALS · RISING LAVA", eyebrowColor: "#fbbf24" },2269 "black-hole": { caps: true, tracking: 0.34, weight: 700, stretch: 1.14, fill: ["#ffffff", "#a78bfa"], glow: "#f0abfc", eyebrow: "TIME SLOWS · THE MULTIPLIER DOESN'T", eyebrowColor: "#f0abfc" },2270 freefall: { caps: true, tracking: -0.06, weight: 800, skew: 14, fill: ["#ffffff", "#60a5fa"], glow: "#60a5fa", eyebrow: "PULL LATE · NOT TOO LATE", eyebrowColor: "#f97316" },2271 "core-meltdown": { caps: true, tracking: 0.1, weight: 700, mono: true, fill: "#bef264", glow: "#a3e635", eyebrow: "TEMP RISING · LIMIT 100 %", eyebrowColor: "#facc15" },2272 "storm-chase": { caps: true, tracking: -0.03, weight: 800, skew: -6, fill: ["#f8fafc", "#94a3b8"], glow: "#a3e635", eyebrow: "DRIVE INTO THE WALL CLOUD", eyebrowColor: "#a3e635" },2273 "elevator-999": { caps: true, tracking: 0.04, weight: 800, stretch: 0.74, fill: ["#f3e2ad", "#c9a961"], glow: "#38bdf8", eyebrow: "EVERY FLOOR IS A DECISION", eyebrowColor: "#38bdf8", split: true },2274 /* Beyond Slots */2275 dropzone: { caps: true, tracking: 0.22, weight: 800, fill: ["#ffffff", "#22d3ee"], glow: "#f472b6", eyebrow: "RELEASE THE CAPSULE", eyebrowColor: "#f472b6" },2276 "grid-break": { caps: true, tracking: 0.02, weight: 700, mono: true, fill: "#bef264", accent: { text: "//", fill: "#38bdf8" }, glow: "#a3e635", eyebrow: "ONE WAVE · INFINITE CHAINS", eyebrowColor: "#38bdf8" },2277 "the-vault": { caps: true, tracking: 0.3, weight: 700, fill: ["#fff1c2", "#c9a961"], glow: "#c9a961", eyebrow: "FIVE LAYERS · ONE DECISION AT EACH", eyebrowColor: "#38bdf8" },2278 orbit: { caps: true, tracking: 0.5, weight: 700, fill: ["#ffffff", "#f0abfc"], glow: "#facc15", eyebrow: "FIRE THE IMPULSE · RIDE THE CHAOS", eyebrowColor: "#facc15" },2279 "escape-99": { caps: true, tracking: -0.02, weight: 800, fill: ["#d1fae5", "#34d399"], outline: "#06090a", glow: "#f97316", eyebrow: "99 FLOORS · CASH OUT ON ANY", eyebrowColor: "#f97316", split: true },2280};22812282function splitLines(name: string, spec: TitleSpec, v: ArtVariant): string[] {2283 const words = name.trim().split(/\s+/);2284 if (words.length < 2) return [name];2285 if (spec.subline) return [words[0], words.slice(1).join(" ")];2286 const twoLines = v === "card" ? spec.split || name.length > 9 : v === "hero" || v === "banner" ? name.length > 14 : name.length > 11;2287 if (!twoLines) return [name];2288 const mid = Math.ceil(words.length / 2);2289 return [words.slice(0, mid).join(" "), words.slice(mid).join(" ")];2290}22912292function estimateWidth(text: string, caps: boolean, tracking: number, mono = false, stretch = 1): number {2293 const cw = mono ? 0.64 : caps ? 0.7 : 0.6;2294 return (text.length * (cw + tracking) - tracking) * stretch;2295}22962297function Title({ s, name, showName }: { s: Stage; name: string; showName: boolean }) {2298 if (!showName) return null;2299 const { W, H, v, id, p } = s;2300 const spec = TITLES[s.slug] ?? { caps: true, tracking: -0.03, weight: 800, glow: p.glow };2301 const caps = spec.caps !== false;2302 const tracking = spec.tracking ?? -0.03;2303 const weight = spec.weight ?? 800;2304 const stretch = spec.stretch ?? 1;2305 const face = spec.mono ? MONO : FONT;2306 const rawLines = splitLines(name, spec, v);2307 const lines = caps ? rawLines.map((l) => l.toUpperCase()) : rawLines;2308 const cfg = v === "card" ? { pad: 22, maxFont: 54, avail: W - 44, eye: 11 } : v === "hero" ? { pad: 84, maxFont: 168, avail: W * 0.44, eye: 22 } : v === "banner" ? { pad: 56, maxFont: 112, avail: W * 0.42, eye: 18 } : { pad: 14, maxFont: 26, avail: W - 28, eye: 0 };2309 const fitLines = spec.subline ? [lines[0]] : lines;2310 const est = Math.max(...fitLines.map((l) => estimateWidth(l, caps, tracking, spec.mono, stretch)), 1);2311 let size = Math.min(cfg.maxFont, cfg.avail / est);2312 if (spec.scale) size *= spec.scale;2313 const subSize = size * 0.34;2314 const lineH = size * 0.94;2315 const totalH = spec.subline ? size + subSize * 1.9 : lineH * lines.length;2316 const center = spec.align === "center";2317 const x = center ? W / 2 : cfg.pad;2318 const anchor = center ? "middle" : "start";2319 const baseY = v === "card" || v === "tile" ? H - cfg.pad - (totalH - size) : H / 2 + size / 2 - (totalH - size) / 2;2320 const fill = Array.isArray(spec.fill) ? `url(#${id}-title)` : (spec.fill ?? "#fff");2321 const eyebrow = spec.eyebrow && cfg.eye > 0 && v !== "tile";2322 const eyeSize = v === "hero" ? size * 0.14 : v === "banner" ? size * 0.15 : Math.max(9.5, size * 0.2);2323 const fontStyle: React.CSSProperties = { fontFamily: face, letterSpacing: `${tracking}em` };23242325 const render = (props: React.SVGProps<SVGTextElement>, key: string, main = false) =>2326 lines.map((l, i) => {2327 const isSub = spec.subline && i === 1;2328 const y = isSub ? baseY + subSize * 1.7 : baseY + i * lineH;2329 const base: React.CSSProperties = isSub ? { fontFamily: face, letterSpacing: "0.32em" } : fontStyle;2330 const ops: string[] = [];2331 if (spec.skew) ops.push(`skewX(${spec.skew})`);2332 if (stretch !== 1 && !isSub) ops.push(`scale(${stretch} 1)`);2333 const transform = ops.length ? `translate(${f(x)} ${f(y)}) ${ops.join(" ")} translate(${f(-x)} ${f(-y)})` : undefined;2334 const acc = spec.accent;2335 const k = main && acc && !isSub ? l.indexOf(acc.text) : -1;2336 return (2337 <text key={`${key}${i}`} x={f(x)} y={f(y)} textAnchor={anchor} fontSize={f(isSub ? subSize : size)} fontWeight={isSub ? 700 : weight} transform={transform} {...props} style={{ ...base, ...props.style }}>2338 {k >= 0 && acc ? (2339 <>2340 {l.slice(0, k)}2341 <tspan fill={acc.fill}>{acc.text}</tspan>2342 {l.slice(k + acc.text.length)}2343 </>2344 ) : (2345 l2346 )}2347 </text>2348 );2349 });23502351 return (2352 <g>2353 {Array.isArray(spec.fill) ? (2354 <defs>2355 <Linear id={`${id}-title`} stops={[[0, spec.fill[0]], [1, spec.fill[1]]]} dir="v" />2356 </defs>2357 ) : null}2358 {eyebrow ? (2359 <text x={f(x)} y={f(baseY - size * 1.02)} textAnchor={anchor} fontSize={f(eyeSize)} fontWeight={700} fill={spec.eyebrowColor ?? p.secondary} style={{ fontFamily: face, letterSpacing: "0.22em" }}>2360 {spec.eyebrow}2361 </text>2362 ) : null}2363 {spec.glow && v !== "tile" ? render({ fill: spec.glow, fillOpacity: 0.5, style: { filter: `blur(${f(size * 0.16)}px)` } }, "g") : null}2364 {spec.shadow ? render({ fill: spec.shadow, transform: `translate(${f(size * 0.07)} ${f(size * 0.07)})` }, "s") : null}2365 {spec.outline ? render({ fill: "none", stroke: spec.outline, strokeWidth: f(size * 0.09), strokeLinejoin: "round" }, "o") : null}2366 {render({ fill }, "t", true)}2367 </g>2368 );2369}23702371/* ----------------------------------------------------------------- GameArt */23722373export function GameArt({ slug, name, palette, variant = "card", className, showName = true, label }: GameArtProps) {2374 const reactId = React.useId().replace(/[^a-zA-Z0-9]/g, "");2375 const id = `ga${reactId}`;2376 const game = GAMES_BY_SLUG.get(slug);2377 const { w: W, h: H } = DIMS[variant];2378 const showTitle = showName;2379 const { cx, cy, R } = focal(variant, W, H, showTitle && variant === "card");2380 const rnd = mulberry32(hashString(`${slug}:${variant}`));2381 const fallback: SymbolStyle = { shape: "gem", color: palette.primary, accent: palette.glow, glow: 0.8 };2382 const bySymbol = new Map((game?.symbols ?? []).map((x) => [x.id, x]));2383 const stage: Stage = {2384 W,2385 H,2386 v: variant,2387 slug,2388 cx,2389 cy,2390 R,2391 p: palette,2392 id,2393 rnd,2394 game,2395 sym: (sid) => bySymbol.get(sid)?.style ?? fallback,2396 tier: (sid) => bySymbol.get(sid)?.tier ?? "premium",2397 compact: variant === "tile",2398 };2399 const scene = SCENES[slug] ?? Generic;2400 const bgBottom = slug === "obsidian" ? "#000" : "#04050a";24012402 return (2403 <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid slice" className={cn("block h-full w-full", className)} role={label ? "img" : undefined} aria-label={label} aria-hidden={label ? undefined : true}>2404 <defs>2405 <linearGradient id={`${id}-bg`} x1="0" y1="0" x2="0.3" y2="1">2406 <stop offset="0" stopColor={palette.bg} />2407 <stop offset="1" stopColor={bgBottom} />2408 </linearGradient>2409 <linearGradient id={`${id}-vig`} x1="0" y1="0" x2="0" y2="1">2410 <stop offset="0.45" stopColor="#000" stopOpacity="0" />2411 <stop offset="1" stopColor="#020308" stopOpacity={variant === "card" ? 0.88 : 0.7} />2412 </linearGradient>2413 </defs>2414 <rect width={W} height={H} fill={`url(#${id}-bg)`} />2415 {/* Scenes are called as plain functions so the seeded PRNG is consumed exactly once per render (SSR == client, StrictMode-safe). */}2416 {scene(stage)}2417 {showTitle && variant !== "tile" ? <rect width={W} height={H} fill={`url(#${id}-vig)`} /> : null}2418 <rect x="0.5" y="0.5" width={W - 1} height={H - 1} fill="none" stroke="#fff" strokeOpacity="0.07" />2419 <Title s={stage} name={name} showName={showTitle} />2420 </svg>2421 );2422}2423