SPB Git forge

spb/spinza

Public
8commits 1branches 0releases
1.6 MBsize
maindefault branch
16 days agolast push
TypeScript 97.6% SQL 1.4% JavaScript 0.5%
145.5 KB · 2,423 lines tsx
Raw Blame History
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