
/* generated by src/tools/palette.py - do not edit by hand */
:root {
  --cyan-50: #EBFAFF; --cyan-100: #B8E9FD; --cyan-200: #90D6F4; --cyan-300: #6BC4EB; --cyan-400: #43B2E3; --cyan-500: #03A3DE; --cyan-600: #0085BD; --cyan-700: #00689C; --cyan-800: #004D7B; --cyan-900: #00335A; --cyan-950: #001C3A;
  --crimson-50: #FFF4F4; --crimson-100: #FFC9CD; --crimson-200: #F5A3A8; --crimson-300: #E58286; --crimson-400: #D36266; --crimson-500: #C34247; --crimson-600: #B51E2A; --crimson-700: #98071C; --crimson-800: #790012; --crimson-900: #5A000A; --crimson-950: #3C0004;
  --gold-50: #FCF7E4; --gold-100: #F5E3A3; --gold-200: #EDCF6B; --gold-300: #E8BE23; --gold-400: #D2A000; --gold-500: #B98300; --gold-600: #9E6900; --gold-700: #835000; --gold-800: #683900; --gold-900: #4C2400; --gold-950: #311100;
  --emerald-50: #E7FEEA; --emerald-100: #B3F5BC; --emerald-200: #88E995; --emerald-300: #5CDC6F; --emerald-400: #22D147; --emerald-500: #00B14B; --emerald-600: #009149; --emerald-700: #007341; --emerald-800: #005635; --emerald-900: #003B26; --emerald-950: #002316;
  --night-50: #F5F6FE; --night-100: #EBECF8; --night-200: #D6D7EA; --night-300: #B9BBD6; --night-400: #999BBD; --night-500: #7A7CA5; --night-600: #5C5E8D; --night-700: #414174; --night-800: #2A275E; --night-900: #1A1446; --night-950: #0E0731; --night-975: #060320;
  --ink: #010101; --sticker: #FDFDFA;
}

/* ==========================================================================
   RoGenie design tokens — "Ink & Lamplight"
   Seeds -> semantic tokens. Every page (and every app theme) sets only the
   eight --seed-* values plus data-scheme; everything else derives from them.
   ========================================================================== */
:root {
  /* ---- theme seeds: the brand "RoGenie Night", sampled from the mascot artwork ----
     These are the pre-JS values for the public pages. They are deliberately the brand
     palette (body #00A0DC, turban #B41E28, trim #F0BE1E, gem #20D040) rather than the app's
     saved theme: a visitor landing on / has no saved preference, and the marketing pages
     must be on-brand. The app applies its own saved theme on boot. */
  --seed-accent: #03A3DE;       /* genie cyan */
  --seed-accent-deep: #00689C;
  --seed-danger: #B51E2A;       /* turban crimson */
  --seed-gold: #E8BE23;         /* wish gold */
  --seed-gold-deep: #B98300;
  --seed-bg-deep: #0E0731;      /* night */
  --seed-bg-royal: #241A6E;     /* royal */
  --seed-bg-panel: #151041;
  --seed-success: #22D147;      /* turban gem (constant across themes) */
  --on-gold: #010101;           /* set per theme by JS (ink or white, whichever passes contrast) */
  --on-danger: #FDFDFA;
  --on-accent: #010101;

  /* ---- scheme helpers ---- */
  --mix-up: #FFFFFF;            /* lighten surfaces toward this */
  --fg: #FDFDFA;
  --ink: #010101;
  --sticker: #FDFDFA;

  /* ---- surfaces ---- */
  --bg: var(--seed-bg-deep);
  --bg-abyss: color-mix(in oklch, var(--seed-bg-deep) 62%, #000);
  --bg-royal: var(--seed-bg-royal);
  --panel: var(--seed-bg-panel);
  --panel-2: color-mix(in oklch, var(--seed-bg-panel), var(--mix-up) 5%);
  --panel-3: color-mix(in oklch, var(--seed-bg-panel), var(--mix-up) 10%);
  --glass: color-mix(in oklch, var(--seed-bg-panel) 76%, transparent);
  --glass-2: color-mix(in oklch, color-mix(in oklch, var(--seed-bg-panel), var(--mix-up) 6%) 82%, transparent);
  --scrim: color-mix(in oklch, var(--seed-bg-deep) 70%, transparent);
  --line: color-mix(in oklch, var(--fg) 13%, transparent);
  --line-2: color-mix(in oklch, var(--fg) 22%, transparent);
  --line-accent: color-mix(in oklch, var(--seed-accent) 45%, transparent);

  /* ---- text ---- */
  --text: var(--fg);
  --text-2: color-mix(in oklch, var(--fg) 84%, var(--seed-bg-panel));
  --text-3: color-mix(in oklch, var(--fg) 66%, var(--seed-bg-panel));
  --text-4: color-mix(in oklch, var(--fg) 46%, var(--seed-bg-panel)); /* decorative only, never body text */

  /* ---- accents (fills) ---- */
  --accent: var(--seed-accent);
  --accent-deep: var(--seed-accent-deep);
  --gold: var(--seed-gold);
  --gold-deep: var(--seed-gold-deep);
  --gold-hi: color-mix(in oklch, var(--seed-gold), #FFFFFF 55%);
  --danger: var(--seed-danger);
  --success: var(--seed-success);
  --warn: #F5C84B;

  /* ---- accents readable as text on the current scheme ---- */
  --accent-text: #6BC4EB;
  --gold-text: #F2CB45;
  --danger-text: #FF7B82;
  --success-text: #5CDC6F;
  --warn-text: #F5C84B;

  /* ---- tints (backgrounds for badges, rows, callouts) ---- */
  --accent-tint: color-mix(in oklch, var(--seed-accent) 16%, transparent);
  --gold-tint: color-mix(in oklch, var(--seed-gold) 16%, transparent);
  --danger-tint: color-mix(in oklch, var(--seed-danger) 22%, transparent);
  --success-tint: color-mix(in oklch, var(--seed-success) 15%, transparent);
  --warn-tint: color-mix(in oklch, #F5C84B 15%, transparent);

  /* ---- code (chat code blocks, diffs, the IDE editor). The theme's own night (its own paper in a
     light theme), and a syntax palette tinted toward the theme: each role keeps its own hue, so
     keywords, calls and strings stay apart in every theme, and leans to the theme's colours ---- */
  --code-bg: color-mix(in oklch, var(--bg-abyss) 88%, #000);
  --code-head: color-mix(in oklch, var(--fg) 4%, transparent);
  --code-fg: color-mix(in oklch, #D6D8F0 88%, var(--seed-accent));
  --code-kw: color-mix(in oklch, #FF8FD0 76%, var(--accent-text));
  --code-fn: color-mix(in oklch, #6BC4EB 50%, var(--accent-text));
  --code-str: color-mix(in oklch, #F5E3A3 64%, var(--gold-text));
  --code-num: color-mix(in oklch, #5CDC6F 80%, var(--accent-text));
  --code-com: color-mix(in oklch, #8C8EB5 80%, var(--accent-text));
  --code-bi: color-mix(in oklch, #B9A2F2 74%, var(--accent-text));
  --code-op: color-mix(in oklch, #C9CBE6 88%, var(--accent-text));
  --code-add: #A8F2B4; --code-add-bg: rgb(34 209 71 / .13);
  --code-del: #FFB9BD; --code-del-bg: rgb(255 123 130 / .13);

  /* ---- light & glow (glow is a material: light sources, not drop shadows) ---- */
  --glow-gold: 0 0 24px color-mix(in oklch, var(--seed-gold) 45%, transparent), 0 0 72px color-mix(in oklch, var(--seed-gold) 20%, transparent);
  --glow-accent: 0 0 22px color-mix(in oklch, var(--seed-accent) 45%, transparent), 0 0 64px color-mix(in oklch, var(--seed-accent) 18%, transparent);
  --glow-success: 0 0 16px color-mix(in oklch, var(--seed-success) 55%, transparent);
  --glow-danger: 0 0 16px color-mix(in oklch, var(--seed-danger) 60%, transparent);
  --rim: inset 0 1px 0 color-mix(in oklch, var(--mix-up) 9%, transparent);

  /* ---- elevation ---- */
  --e1: var(--rim), 0 1px 2px rgb(2 0 16 / .25), 0 8px 24px -14px rgb(2 0 16 / .6);
  --e2: var(--rim), 0 2px 6px rgb(2 0 16 / .25), 0 24px 60px -28px rgb(2 0 16 / .85);
  --e3: var(--rim), 0 4px 12px rgb(2 0 16 / .3), 0 40px 120px -36px rgb(2 0 16 / .95);
  --sticker-shadow: 4px 4px 0 var(--ink);
  --sticker-shadow-sm: 3px 3px 0 var(--ink);
  --sticker-shadow-lg: 6px 6px 0 var(--ink);

  /* ---- grain (kills banding on big gradients) ---- */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .55 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");

  /* ---- type ---- */
  --font-display: "Shrikhand", "Atkinson Hyperlegible Next", Georgia, serif;
  --font-text: "Atkinson Hyperlegible Next", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono: "Atkinson Hyperlegible Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
  --fs-hero: clamp(3.1rem, 1.6rem + 6.4vw, 8.4rem);
  --fs-d1: clamp(2.4rem, 1.5rem + 3.8vw, 5.4rem);
  --fs-d2: clamp(2rem, 1.45rem + 2.4vw, 3.9rem);
  --fs-d3: clamp(1.6rem, 1.3rem + 1.3vw, 2.5rem);
  --fs-h3: clamp(1.25rem, 1.1rem + .6vw, 1.625rem);
  --fs-h4: 1.125rem;
  --fs-lead: clamp(1.0625rem, 1rem + .35vw, 1.3125rem);
  --fs-body: 1rem;
  --fs-sm: .875rem;
  --fs-xs: .78125rem;
  --fs-micro: .6875rem;
  --lh-tight: 1.02;
  --lh-snug: 1.2;
  --lh-body: 1.58;
  --track-display: -0.012em;
  --track-eyebrow: .14em;

  /* ---- space (4-based) ---- */
  --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px; --s-5: 20px; --s-6: 24px; --s-7: 32px;
  --s-8: 40px; --s-9: 48px; --s-10: 64px; --s-11: 80px; --s-12: 96px; --s-13: 128px;
  --gutter: clamp(16px, 4vw, 48px);
  --section: clamp(88px, 12vw, 176px);
  --measure: 68ch;

  /* ---- radius ---- */
  --r-xs: 6px; --r-sm: 10px; --r-md: 14px; --r-lg: 20px; --r-xl: 28px; --r-pill: 999px;

  /* ---- motion ---- */
  --dur-instant: 90ms; --dur-quick: 160ms; --dur-base: 240ms; --dur-slow: 420ms; --dur-scene: 700ms; --dur-epic: 1200ms;
  --ease-out: cubic-bezier(.22, 1, .36, 1);
  --ease-in-out: cubic-bezier(.65, 0, .35, 1);
  --ease-in: cubic-bezier(.55, 0, 1, .45);
  --ease-smoke: cubic-bezier(.4, 0, .2, 1);
  --spring-snappy: linear(0, 0.034, 0.112, 0.215, 0.329, 0.434, 0.542, 0.639, 0.724, 0.791, 0.851, 0.899, 0.936, 0.962, 0.984, 0.998, 1.008, 1.014, 1.017, 1.018, 1.018, 1.017, 1.015, 1.013, 1.011, 1.009, 1.007, 1.006, 1.004, 1.003, 1.002, 1.001, 1.001, 1, 1);
  --dur-spring-snappy: 434ms;
  --spring-bouncy: linear(0, 0.077, 0.248, 0.462, 0.678, 0.867, 1.02, 1.117, 1.168, 1.182, 1.168, 1.136, 1.096, 1.057, 1.023, 0.996, 0.979, 0.969, 0.967, 0.97, 0.976, 0.983, 0.99, 0.996, 1.001, 1.004, 1.006, 1.006, 1.005, 1.004, 1.003, 1.002, 1.001, 1, 1);
  --dur-spring-bouncy: 754ms;
  --spring-soft: linear(0, 0.025, 0.085, 0.163, 0.25, 0.339, 0.425, 0.5, 0.574, 0.639, 0.697, 0.747, 0.79, 0.826, 0.856, 0.882, 0.904, 0.922, 0.937, 0.949, 0.959, 0.967, 0.974, 0.979, 0.984, 0.987, 0.99, 0.992, 0.994, 0.995, 0.996, 0.997, 0.998, 0.998, 1);
  --dur-spring-soft: 607ms;
  --stagger-list: 40ms; --stagger-card: 70ms; --stagger-letter: 22ms;

  /* ---- layers ---- */
  --z-sky: 0; --z-content: 1; --z-sticky: 20; --z-nav: 40; --z-dock: 50; --z-popover: 70; --z-sheet: 80;
  --z-dialog: 90; --z-toast: 100; --z-dev: 120;

  color-scheme: dark;
}

/* Relative colors keep hue and chroma while guaranteeing readable lightness.
   Browsers without them keep the static values above. */
@supports (color: oklch(from red l c h)) {
  :root {
    --accent-text: oklch(from var(--seed-accent) max(l, 0.76) min(c, 0.14) h);
    --gold-text: oklch(from var(--seed-gold) max(l, 0.82) c h);
    --danger-text: oklch(from var(--seed-danger) max(l, 0.74) max(c, 0.14) h);
    --success-text: oklch(from var(--seed-success) max(l, 0.8) c h);
  }
}

/* ---- light scheme (used by the 7 light themes in the app) ---- */
:root[data-scheme="light"] {
  --mix-up: #000000;
  --fg: #16123A;
  --bg-abyss: color-mix(in oklch, var(--seed-bg-deep) 94%, #000);
  --line: color-mix(in oklch, var(--fg) 12%, transparent);
  --line-2: color-mix(in oklch, var(--fg) 20%, transparent);
  --text-2: color-mix(in oklch, var(--fg) 82%, var(--seed-bg-panel));
  --text-3: color-mix(in oklch, var(--fg) 68%, var(--seed-bg-panel));
  --accent-text: #00689C; --gold-text: #835000; --danger-text: #98071C; --success-text: #007341;
  --warn-text: #835000;
  --glass: color-mix(in oklch, var(--seed-bg-panel) 82%, transparent);
  --rim: inset 0 1px 0 rgb(255 255 255 / .7);
  --e1: 0 1px 2px rgb(20 16 60 / .08), 0 8px 24px -14px rgb(20 16 60 / .25);
  --e2: 0 2px 6px rgb(20 16 60 / .08), 0 24px 60px -28px rgb(20 16 60 / .35);
  --e3: 0 4px 12px rgb(20 16 60 / .12), 0 40px 120px -36px rgb(20 16 60 / .45);
  /* code reads dark on the theme's own paper, never as a dark slab in a light theme */
  --code-bg: color-mix(in oklch, var(--seed-bg-royal) 42%, #FFFFFF);
  --code-head: color-mix(in oklch, var(--fg) 5%, transparent);
  --code-fg: color-mix(in oklch, #16123A 90%, var(--seed-accent));
  --code-kw: color-mix(in oklch, #A3125F 76%, var(--accent-text));
  --code-fn: color-mix(in oklch, #00628F 50%, var(--accent-text));
  --code-str: color-mix(in oklch, #6E4A00 64%, var(--gold-text));
  --code-num: color-mix(in oklch, #00693A 80%, var(--accent-text));
  --code-com: color-mix(in oklch, #5C5980 82%, var(--accent-text));
  --code-bi: color-mix(in oklch, #5233A6 74%, var(--accent-text));
  --code-op: color-mix(in oklch, #34305E 88%, var(--accent-text));
  --code-add: #0B6B2F; --code-add-bg: rgb(34 209 71 / .14);
  --code-del: #A3122A; --code-del-bg: rgb(229 72 77 / .12);
  color-scheme: light;
}
@supports (color: oklch(from red l c h)) {
  :root[data-scheme="light"] {
    --accent-text: oklch(from var(--seed-accent) min(l, 0.5) c h);
    --gold-text: oklch(from var(--seed-gold) min(l, 0.5) c h);
    --danger-text: oklch(from var(--seed-danger) min(l, 0.5) c h);
    --success-text: oklch(from var(--seed-success) min(l, 0.48) c h);
  }
}

/* ==========================================================================
   Base: reset, type, focus, selection, scrollbars, motion safety
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: color-mix(in oklch, var(--seed-accent) 22%, transparent);
  scrollbar-color: color-mix(in oklch, var(--fg) 24%, transparent) transparent;
  scrollbar-width: thin;
  /* (every rule that truncates one line with an ellipsis turns it off: Safari would hang a closing
     bracket or quote outside the box and clip it, as in "Auto (intelligent & cheap") */
  hanging-punctuation: first last;
}
body {
  min-height: 100dvh;
  background: var(--bg);
  color: var(--text);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  caret-color: var(--gold);
  overflow-x: clip;
}
img, picture, video, canvas, svg { display: block; max-width: 100%; }
img, video { height: auto; }
input, button, textarea, select { font: inherit; color: inherit; letter-spacing: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }
button:disabled { cursor: not-allowed; }
textarea { resize: vertical; }
p, h1, h2, h3, h4, h5, h6, li, dd, figcaption, blockquote { overflow-wrap: break-word; }
h1, h2, h3, h4 { line-height: var(--lh-snug); text-wrap: balance; font-weight: 800; }
p, li { text-wrap: pretty; }
ul[role="list"], ol[role="list"] { list-style: none; padding: 0; }
table { border-collapse: collapse; }
[hidden] { display: none !important; }
::selection { background: color-mix(in oklch, var(--seed-gold) 88%, transparent); color: var(--ink); }

a { color: var(--accent-text); text-decoration-thickness: .08em; text-underline-offset: .22em; transition: color var(--dur-quick) var(--ease-out); }
a:hover { color: var(--gold-text); }

code, kbd, samp, pre { font-family: var(--font-mono); font-size: .92em; }

/* ---- focus: a cyan ring that snaps in (drawn, never the browser default) ---- */
:focus { outline: none; }
:focus-visible {
  outline: 2.5px solid var(--accent-text);
  outline-offset: 3px;
  animation: focus-snap var(--dur-spring-snappy) var(--spring-snappy);
}
@keyframes focus-snap { from { outline-offset: 9px; outline-color: transparent; } }
.btn-wish:focus-visible, .btn-danger:focus-visible { outline-color: var(--sticker); box-shadow: var(--sticker-shadow), 0 0 0 7px color-mix(in oklch, var(--seed-accent) 55%, transparent); }

/* ---- scrollbars (WebKit / Blink fallback; Firefox + modern Chrome use scrollbar-color) ---- */
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: color-mix(in oklch, var(--fg) 20%, transparent); border-radius: 99px; border: 3px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background-color: color-mix(in oklch, var(--seed-gold) 70%, transparent); }
::-webkit-scrollbar-corner { background: transparent; }

/* ---- utilities ---- */
.sr-only { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }
.skip-link {
  position: fixed; left: 16px; top: 12px; z-index: var(--z-dev);
  padding: 12px 18px; border-radius: var(--r-md); background: var(--gold); color: var(--on-gold);
  border: 2px solid var(--ink); box-shadow: var(--sticker-shadow); font-weight: 800; text-decoration: none;
  transform: translateY(-160%); transition: transform var(--dur-base) var(--ease-out);
}
.skip-link:focus-visible { transform: none; }
.wrap { width: min(100% - 2 * var(--gutter), 1280px); margin-inline: auto; }
.wrap-wide { width: min(100% - 2 * var(--gutter), 1440px); margin-inline: auto; }
.mono { font-family: var(--font-mono); }
.tnum { font-variant-numeric: tabular-nums; }
.nowrap { white-space: nowrap; }
.grain { position: relative; isolation: isolate; }
.grain::after { content: ""; position: absolute; inset: 0; background: var(--grain); opacity: .055; mix-blend-mode: overlay; pointer-events: none; z-index: 1; border-radius: inherit; }

/* ---- type roles ---- */
/* Shrikhand kerns a round letter hard into the d: at 78.72px its `de`/`do`/`ds` pairs pull
   -6.3px (-.08em), five times its next-largest pair, so the d's bowl swallows the e's stem and
   "idea" reads as "ide a" (reported 2026-10-21). Nothing else in the face collides; the pairs
   that do get adjusted otherwise are <= 3.15px, which is invisible at these sizes. Turning
   kerning off for the display face only -- body text keeps it -- is what removes the collision
   without touching the letter fit anywhere else. qa/land-checks.mjs asserts every element that
   computes to Shrikhand has it off, so a new display rule cannot quietly miss this. Every rule
   that sets var(--font-display) carries it directly: a `font:` shorthand resets font-kerning to
   auto, so one shared rule elsewhere in the cascade would be undone by the later shorthand. */
.display { font-family: var(--font-display); font-weight: 400; letter-spacing: var(--track-display); line-height: var(--lh-tight); font-kerning: none; }
.sticker-type {
  font-family: var(--font-display); font-weight: 400; color: var(--gold);
  -webkit-text-stroke: .075em var(--ink); paint-order: stroke fill;
  text-shadow: .07em .07em 0 var(--ink);
  font-kerning: none;
}
.eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  font: 600 var(--fs-micro)/1 var(--font-mono); letter-spacing: var(--track-eyebrow); text-transform: uppercase;
  color: var(--accent-text);
}
.lead { font-size: var(--fs-lead); line-height: 1.55; color: var(--text-2); }
.muted { color: var(--text-3); }

/* ---- icons ---- */
.ic { width: 1.25em; height: 1.25em; flex: none; fill: none; stroke: currentColor; stroke-width: var(--ic-stroke, 1.85); stroke-linecap: round; stroke-linejoin: round; vertical-align: -.24em; overflow: visible; }
/* filled parts use presentation attributes: document CSS cannot reach into <use> shadow trees */
.ic-spin { animation: spin 900ms linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ---- touch: hide keyboard-shortcut hints ---- */
@media (hover: none), (pointer: coarse) {
  [data-kbd-hint], .kbd-hint { display: none !important; }
}

/* ---- motion safety ---- */
/* transitions are switched off outright (0s, so none is created): a 1ms transition on every element
   (transition-property defaults to all) could stick at its start value in WebKit. No script waits
   for transitionend. Animations keep 1ms so their end states still apply. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0s !important; transition-delay: 0ms !important; scroll-behavior: auto !important;
  }
}
html.motion-paused [data-ambient], html.motion-paused [data-ambient] * { animation-play-state: paused !important; }

/* ---- cross-document view transitions (landing -> login -> app) ---- */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 380ms; animation-timing-function: var(--ease-out); }
::view-transition-group(brand), ::view-transition-group(genie) { animation-duration: 520ms; animation-timing-function: var(--ease-out); }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* ==========================================================================
   Components (shared by all nine pages)
   ========================================================================== */

/* ---------- brand lockup ---------- */
.lockup { display: inline-flex; align-items: center; gap: 10px; color: var(--text); text-decoration: none; font: 800 1.22rem/1 var(--font-text); letter-spacing: -.01em; }
.vt-brand { view-transition-name: brand; }
.vt-genie { view-transition-name: genie; }
.lockup img { width: 38px; height: 38px; flex-shrink: 0; max-width: none; object-fit: contain; filter: drop-shadow(0 2px 0 var(--ink)); transition: transform var(--dur-spring-bouncy) var(--spring-bouncy); }
.lockup:hover { color: var(--text); }
.lockup:hover img { transform: rotate(-8deg) scale(1.06); }
.lockup .wordmark span { color: var(--gold-text); }
.lockup.compact { gap: 8px; font-size: 1.05rem; }
.lockup.compact img { width: 30px; height: 30px; }

/* ---------- buttons ---------- */
.btn {
  --btn-h: 44px;
  position: relative; isolation: isolate;
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: var(--btn-h); padding: 0 18px; border-radius: var(--r-md);
  border: 1px solid transparent; font: 700 .9375rem/1.1 var(--font-text); letter-spacing: .005em;
  text-decoration: none; white-space: nowrap; user-select: none; color: var(--text);
  transition: transform var(--dur-quick) var(--ease-out), box-shadow var(--dur-quick) var(--ease-out),
              background-color var(--dur-quick) var(--ease-out), border-color var(--dur-quick) var(--ease-out), color var(--dur-quick);
}
.btn .ic { width: 1.15em; height: 1.15em; }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .45; pointer-events: none; box-shadow: none; }
.btn-sm { --btn-h: 36px; padding: 0 12px; font-size: .875rem; border-radius: var(--r-sm); }
/* 44px touch target: the ring sits 4px past the border box (it is placed from the padding box,
   so it also clears the 1px border, or the 2px border of the sticker buttons) */
.btn-sm::before { content: ""; position: absolute; inset: -5px; }
.btn-wish.btn-sm::before, .btn-danger.btn-sm::before { inset: -6px; }
.btn-lg { --btn-h: 56px; padding: 0 26px; font-size: 1.0625rem; border-radius: 16px; }
.btn-xl { --btn-h: 64px; padding: 0 30px; font-size: 1.1875rem; border-radius: 18px; }
.btn-block { width: 100%; }
/* an icon button is a fixed square: without flex:none a cramped flex row (the phone topbar)
   shrinks it below its width, and the tap target silently falls under the 24px minimum */
.btn-icon { width: var(--btn-h); min-width: var(--btn-h); padding: 0; flex: none; }

/* The wish button: a gold sticker that presses into its own ink shadow */
.btn-wish {
  color: var(--on-gold); font-weight: 800;
  background: linear-gradient(180deg, color-mix(in oklch, var(--gold), #fff 26%) 0%, var(--gold) 46%, color-mix(in oklch, var(--gold), var(--gold-deep) 40%) 100%);
  border: 2px solid var(--ink); box-shadow: var(--sticker-shadow), inset 0 1px 0 rgb(255 255 255 / .55);
}
/* the shine: a band swept across the button's own box by background-position, so the button needs
   no overflow clip (a clip would also cut .btn-sm's 44px touch ring and make every wish button a
   scroll container). Same band, angle, path and timing as the moving-box version it replaces. */
.btn-wish::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; border-radius: inherit;
  background: linear-gradient(105deg, transparent 46.5%, rgb(255 255 255 / .6) 50%, transparent 53.5%) 100% 0 / 360% 100% no-repeat;
}
.btn-wish:hover { transform: translate(-1px, -1px); box-shadow: 5px 5px 0 var(--ink), inset 0 1px 0 rgb(255 255 255 / .55), var(--glow-gold); color: var(--on-gold); }
.btn-wish:hover::after { background-position: 0 0; transition: background-position 700ms var(--ease-out); }
.btn-wish:active { transform: translate(3px, 3px); box-shadow: 1px 1px 0 var(--ink), inset 0 1px 0 rgb(255 255 255 / .4); transition-duration: 60ms; }

.btn-glass { background: var(--glass-2); border-color: var(--line-2); box-shadow: var(--rim); backdrop-filter: blur(14px) saturate(140%); }
.btn-glass:hover { border-color: var(--line-accent); background: color-mix(in oklch, var(--panel-3) 85%, transparent); color: var(--text); }
.btn-glass:active { transform: translateY(1px) scale(.985); }

.btn-ghost { background: transparent; color: var(--text-2); }
.btn-ghost:hover { background: color-mix(in oklch, var(--fg) 8%, transparent); color: var(--text); }
.btn-ghost:active { transform: scale(.97); }
.btn-ghost[aria-pressed="true"], .btn-ghost[aria-expanded="true"] { background: color-mix(in oklch, var(--fg) 10%, transparent); color: var(--text); }

.btn-danger { color: var(--on-danger); font-weight: 800; background: var(--danger); border: 2px solid var(--ink); box-shadow: var(--sticker-shadow); }
.btn-danger:hover { transform: translate(-1px, -1px); box-shadow: 5px 5px 0 var(--ink), var(--glow-danger); color: var(--on-danger); }
.btn-danger:active { transform: translate(3px, 3px); box-shadow: 1px 1px 0 var(--ink); }
.btn-danger-quiet { color: var(--danger-text); }
.btn-danger-quiet:hover { background: var(--danger-tint); color: var(--danger-text); }

.btn[aria-busy="true"] { pointer-events: none; }
.btn[aria-busy="true"] .ic:not(.ic-spin) { display: none; }

/* ---------- fields ---------- */
.field { display: grid; gap: 7px; }
.field > label, .label { font: 700 .875rem/1.2 var(--font-text); color: var(--text-2); }
.hint { font-size: .8125rem; color: var(--text-3); line-height: 1.45; }
.error-text { font-size: .8125rem; color: var(--danger-text); display: flex; gap: 6px; align-items: flex-start; line-height: 1.4; }
.input, .textarea, .select {
  width: 100%; min-height: 46px; padding: 11px 14px; border-radius: var(--r-md);
  background: color-mix(in oklch, var(--bg-abyss) 55%, transparent); border: 1.5px solid var(--line-2); color: var(--text);
  transition: border-color var(--dur-quick), box-shadow var(--dur-quick), background var(--dur-quick);
}
.input::placeholder, .textarea::placeholder { color: var(--text-3); opacity: 1; }
.input:hover, .textarea:hover, .select:hover { border-color: color-mix(in oklch, var(--fg) 34%, transparent); }
.input:focus-visible, .textarea:focus-visible, .select:focus-visible { outline: none; border-color: var(--accent-text); box-shadow: 0 0 0 4px color-mix(in oklch, var(--seed-accent) 26%, transparent); animation: none; }
.input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--danger-text); }
.select { appearance: none; padding-right: 40px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23B9BBD6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; background-size: 18px; }
.select option { background: var(--panel); color: var(--text); }
.input-group { position: relative; display: flex; align-items: center; }
.input-group > .ic:first-child { position: absolute; left: 13px; color: var(--text-3); pointer-events: none; }
.input-group > .ic:first-child + .input { padding-left: 40px; }
.input-group .input-action { position: absolute; right: 5px; }

/* ---------- switch (button[role=switch]) ---------- */
.switch { position: relative; width: 48px; height: 28px; flex: none; border-radius: 99px; background: color-mix(in oklch, var(--fg) 16%, transparent); border: 1.5px solid var(--line-2); transition: background var(--dur-base) var(--ease-out), border-color var(--dur-base); }
.switch::before { content: ""; position: absolute; inset: -9px -2px; }
.switch::after { content: ""; position: absolute; left: 3px; top: 3px; width: 19px; height: 19px; border-radius: 50%; background: var(--text-2); box-shadow: 0 1px 3px rgb(0 0 0 / .4); transition: transform var(--dur-spring-snappy) var(--spring-snappy), background var(--dur-base); }
.switch[aria-checked="true"] { background: var(--success); border-color: var(--ink); }
.switch[aria-checked="true"]::after { transform: translateX(20px); background: var(--sticker); box-shadow: 0 0 0 1.5px var(--ink); }
.switch:disabled { opacity: .45; }
.toggle-row { display: flex; align-items: flex-start; gap: 14px; justify-content: space-between; padding: 14px 0; }
.toggle-row + .toggle-row { border-top: 1px solid var(--line); }
.toggle-row .copy { display: grid; gap: 3px; }
.toggle-row .copy b { font-size: .9375rem; }

/* ---------- checkbox ---------- */
.check { appearance: none; width: 20px; height: 20px; flex: none; border-radius: 6px; border: 1.5px solid var(--line-2); background: transparent; display: inline-grid; place-items: center; transition: background var(--dur-quick), border-color var(--dur-quick); }
.check::after { content: ""; width: 11px; height: 6px; border: solid var(--ink); border-width: 0 0 2.5px 2.5px; transform: rotate(-45deg) translate(1px, -1px) scale(0); transition: transform var(--dur-spring-snappy) var(--spring-snappy); }
.check:checked { background: var(--success); border-color: var(--ink); }
.check:checked::after { transform: rotate(-45deg) translate(1px, -1px) scale(1); }

/* ---------- segmented control ---------- */
.seg { display: inline-flex; padding: 4px; gap: 2px; border-radius: var(--r-md); background: color-mix(in oklch, var(--bg-abyss) 60%, transparent); border: 1px solid var(--line); }
.seg button { min-height: 36px; padding: 0 14px; border-radius: 10px; font-weight: 700; font-size: .875rem; color: var(--text-3); transition: color var(--dur-quick), background var(--dur-quick); }
.seg button:hover { color: var(--text); }
.seg button[aria-pressed="true"], .seg button[aria-selected="true"], .seg button[aria-checked="true"] { background: var(--panel-3); color: var(--text); box-shadow: var(--e1); }

/* ---------- chips, badges, pills ---------- */
.chip { display: inline-flex; align-items: center; gap: 7px; min-height: 36px; padding: 0 13px; border-radius: 99px; border: 1px solid var(--line-2); background: color-mix(in oklch, var(--fg) 4%, transparent); color: var(--text-2); font-size: .875rem; font-weight: 600; transition: all var(--dur-quick) var(--ease-out); }
.chip:hover { border-color: var(--line-accent); color: var(--text); }
.chip[aria-pressed="true"], .chip[aria-selected="true"] { background: var(--accent-tint); border-color: var(--line-accent); color: var(--text); }
.chip .count { font: 600 .75rem var(--font-mono); color: var(--text-3); }
.badge { display: inline-flex; align-items: center; gap: 5px; height: 22px; padding: 0 8px; border-radius: 7px; font: 800 .6875rem/1 var(--font-text); letter-spacing: .06em; text-transform: uppercase; white-space: nowrap; background: color-mix(in oklch, var(--fg) 10%, transparent); color: var(--text-2); }
.badge-gold { background: var(--gold-tint); color: var(--gold-text); }
.badge-accent { background: var(--accent-tint); color: var(--accent-text); }
.badge-success { background: var(--success-tint); color: var(--success-text); }
.badge-danger { background: var(--danger-tint); color: var(--danger-text); }
.badge-warn { background: var(--warn-tint); color: var(--warn-text); }
.badge-sticker { background: var(--gold); color: var(--on-gold); border: 2px solid var(--ink); box-shadow: 2px 2px 0 var(--ink); height: 24px; }
.status-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--text-3); }
.status-dot.ok { background: var(--success); box-shadow: var(--glow-success); }
.status-dot.warn { background: var(--warn); box-shadow: 0 0 12px color-mix(in oklch, var(--warn) 60%, transparent); }
.status-dot.bad { background: var(--danger-text); box-shadow: var(--glow-danger); }
.status-dot.busy { background: var(--gold); box-shadow: var(--glow-gold); animation: pulse-dot 1.4s var(--ease-in-out) infinite; }
@keyframes pulse-dot { 50% { transform: scale(.6); opacity: .6; } }

/* quota multiplier pill: cheap / par / costly / unknown */
.mult { display: inline-flex; align-items: center; gap: 4px; height: 24px; padding: 0 8px; border-radius: 99px; font: 700 .75rem/1 var(--font-mono); white-space: nowrap; font-variant-numeric: tabular-nums; }
.mult.cheap { background: var(--success-tint); color: var(--success-text); }
.mult.par { background: color-mix(in oklch, var(--fg) 11%, transparent); color: var(--text-2); }
.mult.costly { background: var(--gold-tint); color: var(--gold-text); box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--seed-gold) 40%, transparent); }
.mult.unknown { background: transparent; color: var(--text-3); box-shadow: inset 0 0 0 1px var(--line-2); }

/* verification stamps */
.stamp { width: 24px; height: 24px; flex: none; border-radius: 8px; display: inline-grid; place-items: center; border: 2px solid var(--ink); color: var(--ink); background: transparent; }
.stamp .ic { width: 14px; height: 14px; --ic-stroke: 3; }
.stamp.pass { background: var(--success); box-shadow: 2px 2px 0 var(--ink); }
.stamp.fail { background: var(--danger-text); box-shadow: 2px 2px 0 var(--ink); }
.stamp.run { background: var(--gold); box-shadow: 2px 2px 0 var(--ink); }
.stamp.pending { border-color: var(--line-2); color: var(--text-3); border-style: dashed; }
.stamp.in { animation: stamp-in var(--dur-spring-bouncy) var(--spring-bouncy); }
@keyframes stamp-in { from { transform: scale(.2) rotate(-25deg); opacity: 0; } }

.kbd { display: inline-flex; align-items: center; min-width: 22px; height: 22px; padding: 0 6px; border-radius: 6px; border: 1px solid var(--line-2); border-bottom-width: 2px; font: 600 .6875rem/1 var(--font-mono); color: var(--text-2); justify-content: center; background: color-mix(in oklch, var(--fg) 5%, transparent); }

/* ---------- cards & surfaces ---------- */
.card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--e1); }
.card-glass { background: var(--glass); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--e2); backdrop-filter: blur(18px) saturate(150%); -webkit-backdrop-filter: blur(18px) saturate(150%); }
.card-sticker { background: var(--sticker); color: #0E0731; border: 2.5px solid var(--ink); border-radius: var(--r-lg); box-shadow: var(--sticker-shadow-lg); }

/* ---------- tooltip (one floating element, managed by core.js) ---------- */
.tip {
  position: fixed; z-index: var(--z-toast); max-width: min(320px, calc(100vw - 24px)); padding: 8px 11px; border-radius: 10px;
  background: color-mix(in oklch, var(--panel-3) 96%, transparent); color: var(--text); border: 1px solid var(--line-2);
  font-size: .8125rem; line-height: 1.4; box-shadow: var(--e2); pointer-events: none; white-space: pre-line;
  opacity: 0; transform: translateY(4px) scale(.97); transition: opacity var(--dur-quick) var(--ease-out), transform var(--dur-quick) var(--ease-out);
}
.tip.show { opacity: 1; transform: none; }

/* ---------- popovers & menus ---------- */
.popover {
  position: fixed; z-index: var(--z-popover); min-width: 220px; max-width: min(420px, calc(100vw - 16px));
  max-height: min(70vh, 560px); overflow: auto; overscroll-behavior: contain;
  background: color-mix(in oklch, var(--panel-2) 94%, transparent); border: 1px solid var(--line-2); border-radius: var(--r-lg);
  box-shadow: var(--e3); backdrop-filter: blur(22px) saturate(160%); -webkit-backdrop-filter: blur(22px) saturate(160%);
  padding: 6px; transform-origin: var(--origin, top left);
  animation: pop-in var(--dur-spring-snappy) var(--spring-snappy);
}
.popover.closing { animation: pop-out var(--dur-quick) var(--ease-in) forwards; }
@keyframes pop-in { from { opacity: 0; transform: scale(.92) translateY(-4px); } }
@keyframes pop-out { to { opacity: 0; transform: scale(.96); } }
.menu-item { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 40px; padding: 8px 10px; border-radius: 10px; text-align: left; font-size: .9rem; color: var(--text-2); }
.menu-item:hover, .menu-item:focus-visible, .menu-item[data-active="true"] { background: color-mix(in oklch, var(--fg) 9%, transparent); color: var(--text); outline: none; animation: none; }
.menu-item.danger { color: var(--danger-text); }
.menu-item.danger:hover, .menu-item.danger:focus-visible { background: var(--danger-tint); }
.menu-item .ic { color: var(--text-3); }
.menu-item .kbd-hint { margin-left: auto; }
.menu-sep { height: 1px; background: var(--line); margin: 6px 4px; }
.menu-label { padding: 8px 10px 4px; font: 600 var(--fs-micro)/1 var(--font-mono); letter-spacing: .12em; text-transform: uppercase; color: var(--text-3); }

/* ---------- dialogs (native <dialog> with showModal: inert background + focus containment) ---------- */
dialog.dlg {
  /* margin:auto restores the UA centering that the `* { margin: 0 }` reset removes */
  margin: auto; width: min(520px, calc(100vw - 24px)); max-height: calc(100dvh - 32px); padding: 0; border: 1px solid var(--line-2); border-radius: var(--r-xl);
  background: linear-gradient(180deg, var(--panel-2), var(--panel)); color: var(--text); box-shadow: var(--e3), 0 0 0 1px rgb(0 0 0 / .2);
  overflow: hidden; transform-origin: var(--origin, 50% 50%);
}
dialog.dlg[open] { display: flex; flex-direction: column; animation: dlg-in var(--dur-spring-soft) var(--spring-soft); }
dialog.dlg.closing { animation: dlg-out var(--dur-quick) var(--ease-in) forwards; }
dialog.dlg::backdrop { background: color-mix(in oklch, var(--seed-bg-deep) 72%, transparent); backdrop-filter: blur(6px); animation: fade-in var(--dur-base) var(--ease-out); }
@keyframes dlg-in { from { opacity: 0; transform: translate(var(--from-x, 0), var(--from-y, 16px)) scale(.9); } }
@keyframes dlg-out { to { opacity: 0; transform: scale(.96); } }
@keyframes fade-in { from { opacity: 0; } }
.dlg-head { display: flex; align-items: flex-start; gap: 14px; padding: 22px 22px 4px; }
.dlg-head h2 { font-size: 1.25rem; line-height: 1.25; flex: 1; }
/* a long email or id in a title or a text paragraph wraps instead of widening the dialog past a
   phone screen (focus would then scroll the whole dialog sideways); tables keep their columns */
.dlg-head h2, .dlg-body > p { min-width: 0; overflow-wrap: anywhere; }
.dlg-icon { width: 44px; height: 44px; border-radius: 14px; display: grid; place-items: center; flex: none; background: var(--accent-tint); color: var(--accent-text); }
.dlg-icon.danger { background: var(--danger-tint); color: var(--danger-text); }
.dlg-icon.gold { background: var(--gold-tint); color: var(--gold-text); }
.dlg-body { padding: 8px 22px 18px; color: var(--text-2); display: grid; gap: 14px; align-content: start; grid-auto-rows: min-content; overflow: auto; }
.dlg-foot { display: flex; gap: 10px; justify-content: flex-end; padding: 14px 22px 20px; border-top: 1px solid var(--line); background: color-mix(in oklch, var(--bg-abyss) 30%, transparent); flex-wrap: wrap; }
.dlg-close { margin: -6px -8px 0 0; }
dialog.dlg.wide { width: min(880px, calc(100vw - 24px)); }
dialog.dlg.full { width: min(1240px, calc(100vw - 24px)); height: min(860px, calc(100dvh - 24px)); }

/* sheets: bottom on phones, side on desktop */
dialog.sheet { margin: 0; padding: 0; border: 0; max-width: none; max-height: none; color: var(--text); background: var(--panel); box-shadow: var(--e3); }
dialog.sheet::backdrop { background: color-mix(in oklch, var(--seed-bg-deep) 66%, transparent); backdrop-filter: blur(4px); }
dialog.sheet.side { inset: 0 0 0 auto; width: min(480px, 100vw); height: 100dvh; border-left: 1px solid var(--line-2); }
dialog.sheet.side[open] { display: flex; flex-direction: column; animation: sheet-side var(--dur-spring-soft) var(--spring-soft); }
dialog.sheet.bottom { inset: auto 0 0 0; width: 100vw; max-height: 92dvh; border-radius: 24px 24px 0 0; border-top: 1px solid var(--line-2); }
dialog.sheet.bottom[open] { display: flex; flex-direction: column; animation: sheet-up var(--dur-spring-soft) var(--spring-soft); }
@keyframes sheet-side { from { transform: translateX(100%); } }
@keyframes sheet-up { from { transform: translateY(100%); } }
.sheet-grab { width: 44px; height: 5px; border-radius: 9px; background: var(--line-2); margin: 10px auto 2px; flex: none; }

/* ---------- toasts ---------- */
.toasts { position: fixed; z-index: var(--z-toast); right: max(16px, env(safe-area-inset-right)); bottom: max(16px, env(safe-area-inset-bottom)); display: flex; flex-direction: column-reverse; gap: 10px; width: min(380px, calc(100vw - 32px)); pointer-events: none; }
.toast {
  pointer-events: auto; display: flex; align-items: flex-start; gap: 12px; padding: 13px 14px; border-radius: 16px;
  background: color-mix(in oklch, var(--panel-3) 96%, transparent); border: 1px solid var(--line-2); box-shadow: var(--e3);
  backdrop-filter: blur(16px); animation: toast-in var(--dur-spring-bouncy) var(--spring-bouncy); font-size: .9rem; line-height: 1.4;
}
.toast .t-ic { width: 28px; height: 28px; border-radius: 9px; display: grid; place-items: center; flex: none; border: 2px solid var(--ink); color: var(--ink); }
.toast.success .t-ic { background: var(--success); }
.toast.error .t-ic { background: var(--danger-text); }
.toast.info .t-ic { background: var(--accent); }
.toast .t-msg { flex: 1; padding-top: 3px; color: var(--text); }
.toast .t-act { margin: -2px 0; }
.toast.leaving { animation: toast-out var(--dur-base) var(--ease-in) forwards; }
@keyframes toast-in { from { opacity: 0; transform: translateY(18px) scale(.9); } }
@keyframes toast-out { to { opacity: 0; transform: translateX(40px); } }
@media (max-width: 600px) { .toasts { left: 16px; right: 16px; width: auto; bottom: max(12px, env(safe-area-inset-bottom)); } }
/* Toasts float in the top layer (RG.toast makes the host a manual popover), laid out against the page
   and above any open dialog. While a modal dialog is open the host lives inside it (outside it would
   be inert); as a plain position:fixed child it was laid out against the DIALOG while the dialog's
   open animation ran (a transform makes it the containing block), then jumped to the page corner
   when the animation ended (2026-10-24). The popover's own UA box (centred, bordered) is undone here. */
.toasts[popover] { top: auto; left: auto; height: auto; margin: 0; padding: 0; border: 0; background: transparent; color: inherit; overflow: visible; }
@media (max-width: 600px) { .toasts[popover] { left: 16px; } }
/* the big work surfaces (the IDE, full-size dialogs such as Settings) keep their toasts in their own
   corner, inside the panel the person is looking at, and they stay put there */
.toasts.anchored { position: absolute; right: 16px; bottom: 16px; left: auto; width: min(380px, calc(100% - 32px)); }
@media (max-width: 600px) { .toasts.anchored { left: 12px; right: 12px; width: auto; bottom: max(12px, env(safe-area-inset-bottom)); } }

/* ---------- skeletons ---------- */
.sk { display: block; border-radius: 8px; background: linear-gradient(90deg, color-mix(in oklch, var(--fg) 7%, transparent) 0%, color-mix(in oklch, var(--fg) 14%, transparent) 40%, color-mix(in oklch, var(--fg) 7%, transparent) 80%); background-size: 300% 100%; animation: sk 1.6s var(--ease-in-out) infinite; }
@keyframes sk { from { background-position: 100% 0; } to { background-position: -50% 0; } }
.sk-line { height: .8em; margin: .35em 0; }
.sk-circle { border-radius: 50%; }

/* ---------- meters ---------- */
.meter { position: relative; height: 8px; border-radius: 99px; background: color-mix(in oklch, var(--fg) 10%, transparent); overflow: hidden; }
.meter > i { position: absolute; inset: 0 auto 0 0; width: var(--v, 0%); border-radius: inherit; background: linear-gradient(90deg, var(--accent), color-mix(in oklch, var(--accent), #fff 30%)); transition: width var(--dur-slow) var(--ease-out); }
.meter.gold > i { background: linear-gradient(90deg, var(--gold-deep), var(--gold), var(--gold-hi)); }
.meter.success > i { background: linear-gradient(90deg, color-mix(in oklch, var(--success), #000 20%), var(--success)); }
.meter.warn > i { background: linear-gradient(90deg, #d2a000, var(--warn)); }
.meter.danger > i { background: linear-gradient(90deg, var(--danger), var(--danger-text)); }
.ring { --v: 0; --c: var(--accent); width: 28px; height: 28px; border-radius: 50%; flex: none; background: conic-gradient(var(--c) calc(var(--v) * 1%), color-mix(in oklch, var(--fg) 14%, transparent) 0); mask: radial-gradient(circle, transparent 52%, #000 54%); -webkit-mask: radial-gradient(circle, transparent 52%, #000 54%); transition: --v var(--dur-slow); }

/* ---------- tabs ---------- */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); overflow-x: auto; scrollbar-width: none; }
.tabs::-webkit-scrollbar { display: none; }
.tab { position: relative; min-height: 44px; padding: 0 14px; font-weight: 700; font-size: .9rem; color: var(--text-3); white-space: nowrap; }
.tab:hover { color: var(--text); }
.tab[aria-selected="true"] { color: var(--text); }
.tab[aria-selected="true"]::after { content: ""; position: absolute; left: 10px; right: 10px; bottom: -1px; height: 3px; border-radius: 3px 3px 0 0; background: var(--gold); box-shadow: var(--glow-gold); }

/* ---------- callouts & empty states ---------- */
.callout { display: flex; gap: 12px; align-items: flex-start; padding: 14px 16px; border-radius: var(--r-md); border: 1px solid var(--line-accent); background: var(--accent-tint); color: var(--text-2); font-size: .9rem; line-height: 1.5; }
.callout > .ic { color: var(--accent-text); margin-top: 1px; }
.callout.warn { border-color: color-mix(in oklch, var(--warn) 45%, transparent); background: var(--warn-tint); }
.callout.warn > .ic { color: var(--warn-text); }
.callout.danger { border-color: color-mix(in oklch, var(--seed-danger) 55%, transparent); background: var(--danger-tint); }
.callout.danger > .ic { color: var(--danger-text); }
.callout.success { border-color: color-mix(in oklch, var(--seed-success) 45%, transparent); background: var(--success-tint); }
.callout.success > .ic { color: var(--success-text); }
.empty { display: grid; justify-items: center; text-align: center; gap: 10px; padding: 28px 18px; color: var(--text-3); }
.empty img { width: 96px; height: 96px; object-fit: contain; }
.empty b { color: var(--text); font-size: 1rem; }

.avatar { width: 34px; height: 34px; border-radius: 50%; flex: none; display: grid; place-items: center; overflow: hidden; background: linear-gradient(135deg, var(--accent), var(--seed-bg-royal)); color: #fff; font-weight: 800; font-size: .85rem; border: 2px solid var(--ink); }
.avatar img { width: 100%; height: 100%; object-fit: cover; }
/* A real picture brings its own background: the tinted disc showed through every transparent
   pixel as a coloured halo behind the face. It stays for the initial-only fallback. */
.avatar.has-img { background: none; border-color: transparent; }

.btn-wish .spinner, .btn-danger .spinner { border-color: rgb(1 1 1 / .22); border-top-color: var(--ink); }
.spinner { width: 18px; height: 18px; border-radius: 50%; border: 2.5px solid color-mix(in oklch, var(--fg) 20%, transparent); border-top-color: var(--gold); animation: spin 800ms linear infinite; flex: none; }

/* ---------- tables (admin + comparisons) ---------- */
.table-wrap { overflow: auto; border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--panel); max-height: var(--table-max, none); }
.table { width: 100%; font-size: .875rem; font-variant-numeric: tabular-nums; }
.table th, .table td { padding: 11px 14px; text-align: left; border-bottom: 1px solid var(--line); white-space: nowrap; vertical-align: middle; }
.table thead th { position: sticky; top: 0; z-index: 2; background: color-mix(in oklch, var(--panel-2) 97%, transparent); font: 700 .75rem/1.2 var(--font-text); letter-spacing: .04em; text-transform: uppercase; color: var(--text-3); backdrop-filter: blur(10px); }
.table th .sort { display: inline-flex; align-items: center; gap: 6px; color: inherit; font: inherit; text-transform: inherit; letter-spacing: inherit; min-height: 32px; }
.table th .sort:hover { color: var(--text); }
.table th[aria-sort="ascending"] .sort, .table th[aria-sort="descending"] .sort { color: var(--gold-text); }
.table tbody tr { transition: background var(--dur-quick); }
.table tbody tr:hover { background: color-mix(in oklch, var(--fg) 4%, transparent); }
.table td.num, .table th.num { text-align: right; }

/* ---------- media placeholders (owner drops real files into /assets/media/) ---------- */
.ph { position: relative; overflow: hidden; border-radius: var(--r-lg); background: radial-gradient(120% 90% at 30% 20%, color-mix(in oklch, var(--seed-bg-royal) 80%, #fff 4%), var(--bg-abyss)); isolation: isolate; }
.ph::before { content: ""; position: absolute; inset: 0; z-index: 0; background-image: radial-gradient(1.5px 1.5px at 20% 30%, var(--gold) 50%, transparent 55%), radial-gradient(1px 1px at 70% 60%, var(--gold-hi) 50%, transparent 55%), radial-gradient(1.2px 1.2px at 45% 80%, var(--accent) 50%, transparent 55%), radial-gradient(1px 1px at 85% 25%, #fff 50%, transparent 55%); background-size: 180px 140px; opacity: .55; animation: ph-drift 14s linear infinite; }
.ph::after { content: ""; position: absolute; inset: 0; z-index: 0; background: linear-gradient(115deg, transparent 30%, color-mix(in oklch, var(--seed-gold) 12%, transparent) 50%, transparent 70%); background-size: 250% 100%; animation: ph-shimmer 5s var(--ease-in-out) infinite; }
@keyframes ph-drift { to { background-position: 180px -140px; } }
@keyframes ph-shimmer { from { background-position: 120% 0; } to { background-position: -120% 0; } }
.ph > img, .ph > video, .ph > picture, .ph > picture img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 1; }
.ph-art { position: absolute; left: 50%; top: 50%; translate: -50% -50%; z-index: 1; display: grid; justify-items: center; gap: 10px; color: var(--text-3); text-align: center; pointer-events: none; }
.ph-art .ic { width: 44px; height: 44px; padding: 10px; border-radius: 16px; color: var(--gold-text); background: color-mix(in oklch, var(--seed-gold) 12%, transparent); border: 1px solid color-mix(in oklch, var(--seed-gold) 35%, transparent); box-shadow: 0 0 40px -6px color-mix(in oklch, var(--seed-gold) 45%, transparent); --ic-stroke: 1.6; animation: ph-float 5s var(--ease-in-out) infinite; }
.ph-art span { font: 700 .8125rem/1.3 var(--font-text); max-width: 32ch; }
@keyframes ph-float { 50% { transform: translateY(-5px); } }
.ph.loaded .ph-art, html.clean .ph-art, .ph-bleed .ph-art { display: none; }
.ph .ph-label { position: absolute; left: 12px; bottom: 12px; z-index: 2; max-width: calc(100% - 24px); padding: 6px 10px; border-radius: 9px; background: color-mix(in oklch, var(--bg-abyss) 82%, transparent); border: 1px solid var(--line-2); font: 600 .6875rem/1.35 var(--font-mono); color: var(--text-2); }
.ph .ph-label b { color: var(--gold-text); }
/* The label text lives in data attributes and is drawn here, not in the markup: it is a
   designer note for the placeholder state, and as text nodes it led every crawler's text
   extract ("IMG-LAND-HERO-01 · 2880×1600 ..."). ::before matches the old <b> exactly. */
.ph .ph-label[data-ph-id]::before { content: attr(data-ph-id); color: var(--gold-text); font-weight: bolder; }
.ph .ph-label[data-ph-spec]::after { content: attr(data-ph-spec); }
.ph.loaded::before, .ph.loaded::after, .ph.loaded .ph-label { display: none; }
html.clean .ph-label { display: none; }
.ph-play { position: absolute; z-index: 3; left: 50%; top: 50%; translate: -50% -50%; }

/* ---------- mascot ---------- */
.genie { position: relative; display: block; }
.genie-stage { position: relative; display: block; width: 100%; aspect-ratio: 1; will-change: transform; }
.genie-stage img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; opacity: 0; transition: opacity 170ms var(--ease-out); user-select: none; -webkit-user-drag: none; pointer-events: none; }
.genie-stage img.on { opacity: 1; }
.rg-spark { position: absolute; width: 14px; height: 14px; background: var(--gold); pointer-events: none; z-index: 3; clip-path: polygon(50% 0, 61% 39%, 100% 50%, 61% 61%, 50% 100%, 39% 61%, 0 50%, 39% 39%); filter: drop-shadow(0 0 6px var(--gold)); }

/* ---------- dev / QA panel ---------- */
.dev-panel { position: fixed; left: 12px; bottom: 66px; z-index: var(--z-dev); display: flex; flex-direction: column-reverse; align-items: flex-start; gap: 8px; max-height: calc(100dvh - 24px); font-family: var(--font-text); }
.dev-fab { display: inline-flex; gap: 6px; align-items: center; height: 40px; padding: 0 14px; border-radius: 12px; background: #17151f; color: #fff; border: 2px solid var(--ink); box-shadow: 3px 3px 0 var(--ink); font: 800 13px var(--font-text); }
.dev-body { width: min(360px, calc(100vw - 24px)); max-height: 72dvh; overflow: auto; padding: 4px 14px 14px; border-radius: 16px; background: #15131d; border: 1px solid #ffffff24; box-shadow: var(--e3); color: #eee; font-size: 13px; }
.dev-sec h3 { font: 700 11px var(--font-mono); letter-spacing: .1em; text-transform: uppercase; color: #a6a9c9; margin: 12px 0 6px; }
.dev-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; justify-content: space-between; padding: 3px 0; }
.dev-row .select { min-height: 34px; padding: 4px 30px 4px 8px; width: auto; max-width: 200px; font-size: 13px; }

/* the genie's head by the wordmark follows the theme with the genie (js/genie.js): hidden for the
   moment its art takes to arrive, so it never flashes the official colours first */
html[data-genie-tint="pending"] img[data-genie-builtin="logo"] { visibility: hidden; }

/* ---- theme effects: the perspective grids of Tron Grid, Laser Grid and Synthwave 84 (js/effects.js,
   grid()), laid the way Design Studio's Tron lays its floor: a tiled plane tipped 85° under a CSS
   perspective, so every line keeps its true width and the far tiles blur together. The values are
   set per effect and per size from JS; the defaults are the reference's. */
.fx-grid { position: absolute; inset: 0; overflow: hidden; pointer-events: none; perspective: var(--g-persp, 300px); perspective-origin: var(--g-vx, 50%) var(--g-hy, 50%); animation: fx-grid-in .7s ease-out both; }
@keyframes fx-grid-in { from { opacity: 0; } }
/* the plane hangs --g-drop below the eye (the ceiling as far above it), so the camera rides above the
   floor instead of skimming it and the nearest tiles stay a sensible size */
.fx-grid .pl { position: absolute; left: 0; top: calc(var(--g-hy, 50%) + var(--g-drop, 0px)); width: 100%; height: 100%; transform: rotateX(85deg); transform-origin: 50% 0; transform-style: preserve-3d; }
.fx-grid .pl.ceiling { top: auto; bottom: calc(100% - var(--g-hy, 50%) + var(--g-drop, 0px)); transform: rotateX(-85deg); transform-origin: 50% 100%; }
.fx-grid .fl { position: absolute; top: -100%; left: -100%; width: 300%; height: 300%;
  background-image: linear-gradient(90deg, var(--g-line) var(--g-w, 2px), transparent var(--g-w, 2px)), linear-gradient(0deg, var(--g-line) var(--g-w, 2px), transparent var(--g-w, 2px));
  background-size: var(--g-tile, 60px) var(--g-tile, 60px);
  filter: drop-shadow(0 0 var(--g-blur, 4px) var(--g-glow));
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 30%, #000 100%); mask-image: linear-gradient(to bottom, transparent 0%, #000 30%, #000 100%);
  animation: fx-grid-floor var(--g-dur, 1s) linear infinite; }
.fx-grid .ceiling .fl { -webkit-mask-image: linear-gradient(to top, transparent 0%, #000 30%, #000 100%); mask-image: linear-gradient(to top, transparent 0%, #000 30%, #000 100%); animation-name: fx-grid-ceiling; }
@keyframes fx-grid-floor { to { transform: translateY(var(--g-tile, 60px)); } }
@keyframes fx-grid-ceiling { to { transform: translateY(calc(-1 * var(--g-tile, 60px))); } }
.fx-grid.paused .fl { animation-play-state: paused; }
.fx-grid .hz { position: absolute; inset: 0; }

/* ==========================================================================
   Public pages: living sky, nav, footer, sections, reveals, kinetic type
   ========================================================================== */
html { scroll-padding-top: 96px; background: var(--bg-abyss); }
/* LND-19 · zero layout shift on a cold load: until the web fonts arrive (display=swap), text
   is set in local faces resized to the web fonts' advance widths, ascent and descent (measured
   with canvas TextMetrics against the built pages), so the swap moves nothing. Each stack puts
   the matched fallback straight after its web font; the rest of the stack is tokens.css's. */
@font-face { font-family: "Shrikhand Fallback"; src: local("Georgia Bold"), local("Georgia-Bold"); font-weight: 400; size-adjust: 103.53%; ascent-override: 99.49%; descent-override: 41.53%; line-gap-override: 0%; }
@font-face { font-family: "Atkinson Next Fallback"; src: local("Arial"), local("ArialMT"); font-weight: 400; size-adjust: 97.3%; ascent-override: 100.72%; descent-override: 32.89%; line-gap-override: 0%; }
@font-face { font-family: "Atkinson Next Fallback"; src: local("Arial Bold"), local("Arial-BoldMT"); font-weight: 600; size-adjust: 97.76%; ascent-override: 100.24%; descent-override: 32.73%; line-gap-override: 0%; }
@font-face { font-family: "Atkinson Next Fallback"; src: local("Arial Bold"), local("Arial-BoldMT"); font-weight: 700; size-adjust: 98.37%; ascent-override: 99.62%; descent-override: 32.53%; line-gap-override: 0%; }
@font-face { font-family: "Atkinson Next Fallback"; src: local("Arial Bold"), local("Arial-BoldMT"); font-weight: 800 900; size-adjust: 103.9%; ascent-override: 94.32%; descent-override: 30.8%; line-gap-override: 0%; }
@font-face { font-family: "Atkinson Mono Fallback"; src: local("Courier New"), local("CourierNewPSMT"); font-weight: 400; size-adjust: 105.32%; ascent-override: 93.05%; descent-override: 30.38%; line-gap-override: 0%; }
@font-face { font-family: "Atkinson Mono Fallback"; src: local("Courier New Bold"), local("CourierNewPS-BoldMT"); font-weight: 600 900; size-adjust: 105.32%; ascent-override: 93.05%; descent-override: 30.38%; line-gap-override: 0%; }
:root {
  --font-display: "Shrikhand", "Shrikhand Fallback", "Atkinson Hyperlegible Next", Georgia, serif;
  --font-text: "Atkinson Hyperlegible Next", "Atkinson Next Fallback", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono: "Atkinson Hyperlegible Mono", "Atkinson Mono Fallback", ui-monospace, "Cascadia Mono", Consolas, monospace;
}
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-stopped { overflow: hidden; }
body.public { background: transparent; }

/* ---- the sky: one fixed world whose light evolves as you scroll ---- */
.sky { position: fixed; inset: 0; z-index: -2; pointer-events: none;
  --sky-top: #070423; --sky-mid: #1B1358; --sky-low: #3B2A86; --sky-glow: rgb(232 190 35 / .2); --mx: 60%; --my: 30%;
  background:
    radial-gradient(70% 55% at 50% 108%, var(--sky-glow), transparent 70%),
    linear-gradient(180deg, var(--sky-top) 0%, var(--sky-mid) 58%, var(--sky-low) 100%);
}
.sky::before { content: ""; position: absolute; inset: 0; background: var(--grain); opacity: .05; mix-blend-mode: overlay; }
.sky::after { content: ""; position: absolute; inset: 0; background: radial-gradient(520px circle at var(--mx) var(--my), rgb(232 190 35 / .075), transparent 70%); transition: opacity .6s; }
@media (hover: none) { .sky::after { opacity: 0; } }
.dust { position: fixed; inset: 0; z-index: -1; width: 100%; height: 100%; pointer-events: none; }

/* pause control for ambient motion (GLB-11, LND-18): an icon button docked in the nav bar, so it
   never sits over content at any width. The icon swaps pause/play; its label and tooltip say which. */
.motion-toggle { color: var(--text-2); }
.motion-toggle:hover { color: var(--text); }
/* phones: the compact lockup (GLB-3) leaves the nav room for the control */
@media (max-width: 480px) { .nav-bar > .lockup .wordmark { display: none; } }
@media (max-width: 360px) { .nav .nav-bar { gap: 8px; padding-left: 10px; } .nav .nav-actions { gap: 6px; } }
.nav-bar > .lockup { flex-shrink: 0; } /* the logo never squeezes to make room */
/* paused, looping decoration holds still too. base.css pauses [data-ambient] elements, but a
   pseudo-element's animation is out of that rule's reach, so the placeholder loops are named here */
html.still .ph::before, html.still .ph::after, html.still .ph-art .ic { animation-play-state: paused; }

/* ---- navigation ---- */
.nav { position: fixed; inset: 0 0 auto; z-index: var(--z-nav); padding: 14px var(--gutter) 0; pointer-events: none; container-type: inline-size; }
.nav-bar { pointer-events: auto; margin-inline: auto; max-width: 1360px; display: flex; align-items: center; gap: 18px; min-height: 64px; padding: 0 10px 0 16px;
  border-radius: 22px; border: 1px solid transparent;
  --dock: max(0px, (min(1360px, 100cqw) - 1180px) / 2); }
/* LND-19 · docking on scroll moves nothing in layout: the bar keeps its wide 64px box, the glass
   fades in on ::before at the docked size (1180px wide, 58px tall), and the two ends slide in
   by transform. --dock is how far each end travels (half of the wide bar less 1180px). */
.nav-bar::before { content: ""; position: absolute; inset: -1px calc(var(--dock) - 1px) 5px; z-index: -1; border-radius: inherit; border: 1px solid var(--line-2);
  background: color-mix(in oklch, var(--bg-abyss) 74%, transparent); box-shadow: var(--e2);
  backdrop-filter: blur(18px) saturate(160%); -webkit-backdrop-filter: blur(18px) saturate(160%); opacity: 0; transition: opacity .4s; pointer-events: none; }
.nav.scrolled .nav-bar::before { opacity: 1; }
.nav-bar > .lockup, .nav-bar > nav, .nav-bar > .nav-actions, .nav-bar > .motion-toggle { transition: transform .5s var(--ease-out); }
.nav.scrolled .nav-bar > nav { transform: translateY(-3px); }
.nav.scrolled .nav-bar > .lockup { transform: translate(var(--dock), -3px); }
.nav.scrolled .nav-bar > .nav-actions, .nav.scrolled .nav-bar > .motion-toggle { transform: translate(calc(-1 * var(--dock)), -3px); }
.nav-bar > nav { flex: 1; display: flex; justify-content: center; min-width: 0; }
.nav .links { display: flex; align-items: center; gap: 2px; position: relative; list-style: none; padding: 0; }
.nav .links a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 12px; border-radius: 12px; color: var(--text-2); text-decoration: none; font-weight: 700; font-size: .9375rem; transition: color var(--dur-quick); }
.nav .links a:hover, .nav .links a[aria-current="true"] { color: var(--text); }
.nav-ink { position: absolute; left: 0; bottom: 4px; height: 3px; width: 0; border-radius: 3px; background: var(--gold); box-shadow: var(--glow-gold); opacity: 0;
  transition: transform .45s var(--spring-snappy), width .45s var(--spring-snappy), opacity .3s; pointer-events: none; }
.nav-actions { display: flex; align-items: center; gap: 8px; }
.nav-progress { position: absolute; left: calc(22px + var(--dock)); right: calc(22px + var(--dock)); bottom: 5px; height: 2px; border-radius: 2px; overflow: hidden; opacity: 0; transition: opacity .4s; }
.nav.scrolled .nav-progress { opacity: 1; }
.nav-progress i { display: block; height: 100%; width: 100%; transform-origin: left; transform: scaleX(var(--p, 0)); background: linear-gradient(90deg, var(--accent), var(--gold)); }
.nav-bar { position: relative; }
.menu-btn { display: none; }
@media (max-width: 1180px) {
  .nav .links, .nav-actions .hide-sm { display: none; }
  .menu-btn { display: inline-flex; }
  .nav-actions { margin-left: auto; }
}
/* the slim nav (404, policy and auth pages) has no drawer, so its links stay visible all the way
   down; four links and the pause control (GLB-11) share the bar, tightened in steps on phones.
   auth.css restates its own values for the sign-in and invite pages. */
@media (max-width: 1180px) {
  .nav-slim .links { display: flex; }
  .nav-slim .nav-bar { gap: 10px; padding-right: 6px; }
  .nav-slim .links a { padding: 0 9px; font-size: .9rem; }
}
@media (max-width: 540px) { .nav-slim .lockup .wordmark { display: none; } }
@media (max-width: 480px) {
  .nav-slim .links a { padding: 0 6px; font-size: .82rem; }
  .nav-slim .nav-bar { padding-left: 10px; }
}
@media (max-width: 400px) {
  .nav-slim .nav-bar { gap: 6px; }
  .nav-slim .links a { padding: 0 4px; }
}
@media (max-width: 356px) {
  .nav-slim .nav-bar { padding-left: 6px; padding-right: 4px; gap: 4px; }
  .nav-slim .links { gap: 0; }
  .nav-slim .links a { padding: 0 3px; font-size: .75rem; }
}
dialog.menu-sheet { inset: 0; width: 100vw; height: 100dvh; max-width: none; max-height: none; margin: 0; padding: 0; border: 0; color: var(--text);
  background: radial-gradient(90% 60% at 80% 0%, #2a1f7c 0%, var(--bg) 55%, var(--bg-abyss) 100%); }
dialog.menu-sheet[open] { display: flex; flex-direction: column; animation: menu-in var(--dur-spring-soft) var(--spring-soft); }
@keyframes menu-in { from { opacity: 0; transform: translateY(-14px) scale(.98); } }
.menu-sheet .top { display: flex; align-items: center; justify-content: space-between; padding: 14px var(--gutter); min-height: 78px; }
.menu-sheet nav { flex: 1; overflow: auto; padding: 8px var(--gutter) 24px; }
.menu-sheet nav ul { list-style: none; padding: 0; display: grid; gap: 2px; }
.menu-sheet nav a { display: flex; align-items: center; justify-content: space-between; min-height: 58px; font: 400 clamp(1.7rem, 7vw, 2.4rem)/1 var(--font-display); color: var(--text); text-decoration: none; border-bottom: 1px solid var(--line); font-kerning: none; }
.menu-sheet nav a .ic { color: var(--gold); width: 22px; height: 22px; }
.menu-sheet .cta { display: grid; gap: 10px; padding: 16px var(--gutter) max(24px, env(safe-area-inset-bottom)); border-top: 1px solid var(--line); background: color-mix(in oklch, var(--bg-abyss) 60%, transparent); }

/* ---- sections ---- */
main { position: relative; z-index: var(--z-content); }
.sec { position: relative; padding-block: var(--section); }
.sec-head { display: grid; gap: 18px; max-width: 880px; margin-bottom: clamp(40px, 6vw, 72px); }
.sec-head.center { margin-inline: auto; text-align: center; justify-items: center; }
.sec-head h2 { font-family: var(--font-display); font-weight: 400; font-size: var(--fs-d1); line-height: 1.02; letter-spacing: var(--track-display); font-kerning: none; }
.sec-head h2 .gold { color: var(--gold); -webkit-text-stroke: .06em var(--ink); paint-order: stroke fill; text-shadow: .06em .06em 0 var(--ink); }
.sec-head h2 .cyan { color: var(--accent-text); }
.sec-head .lead { max-width: 64ch; }
/* an inline link in a sentence is exempt from target-size rules, but a little padding
   makes it comfortable on a phone without touching the line box */
.lead a, .sec-head p a, .sv-sub a, .fine a { padding-block: 4px; }

/* ---- reveals ---- */
[data-reveal] { opacity: 0; transform: translateY(26px); transition: opacity .8s var(--ease-out), transform .9s var(--ease-out); transition-delay: calc(var(--d, 0) * 1ms); }
[data-reveal].in { opacity: 1; transform: none; }
[data-reveal="scale"] { transform: translateY(18px) scale(.96); }
.no-js [data-reveal], html.still [data-reveal] { opacity: 1; transform: none; }
/* The word reveal needs a clip box. `overflow: hidden` gives an inline-block its bottom margin
   edge as baseline, so the only way to widen the box is a padding + negative margin pair -- and
   between two inline-blocks that pair is NOT layout-neutral: each side gives back .08em less than
   it takes, so a space loses 2 x .08em. At 78.72px that turned the font's own 14.81px word gap
   into 2.22px and headings read as "fromidea". word-spacing pays the debt back, and because it is
   in em it stays exact at every size. Keep the two numbers in step. */
.split { word-spacing: .16em; }
.split .w { display: inline-block; overflow: hidden; vertical-align: top;
  /* Shrikhand paints well outside its line box: measured on the landing at 78.72px, the ink ran
     15.3px above and 10.2px below it, so `overflow: hidden` (which the word reveal needs) was
     slicing the tops off the capitals and the tails off y, p and g. */
  padding: .26em .08em .25em; margin: -.26em -.08em -.25em; }
/* A letter reveal fades in (opacity 0 -> 1) and never slides, so it needs no clip box -- and the
   clip it was inheriting was slicing the text-stroke and drop shadow on "Make a wish." Neither
   does it need the padding above, so it also gives back the word-spacing that padding costs. */
.split .w:has(> .ch) { overflow: visible; padding: 0; margin: 0; }
.split:has(> .w > .ch) { word-spacing: normal; }
.split .wi { display: inline-block;
  /* 105% clears the word's own height but not the clip padding, which left the tops of the
     capitals showing before the reveal; the .52em covers .26em + .25em of it. */
  transform: translateY(calc(105% + .52em)) rotate(4deg);
  transition: transform .9s var(--ease-out); transition-delay: calc(var(--i) * 45ms + var(--d, 0) * 1ms); }
.split .ch { display: inline-block; transform: translateY(.5em) scale(.4) rotate(-12deg); opacity: 0; transition: transform var(--dur-spring-bouncy) var(--spring-bouncy), opacity .2s; transition-delay: calc(var(--i) * var(--stagger-letter) + var(--d, 0) * 1ms); }
[data-kinetic].in .split .wi, [data-kinetic].in .split .ch { transform: none; opacity: 1; }
html.still [data-kinetic] .split .wi, html.still [data-kinetic] .split .ch { transform: none; opacity: 1; transition: none; }

/* ---- footer (GLB-5) ---- */
.site-footer { position: relative; z-index: 1; padding: 56px 0 calc(84px + env(safe-area-inset-bottom)); border-top: 1px solid var(--line); background: linear-gradient(180deg, transparent, color-mix(in oklch, var(--bg-abyss) 80%, transparent)); }
.site-footer .wrap { display: grid; gap: 28px; grid-template-columns: 1fr auto; align-items: start; }
.site-footer .tag { color: var(--text-3); font-size: .9375rem; margin-top: 12px; max-width: 42ch; }
.site-footer ul { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 6px; justify-content: flex-end; }
.site-footer ul a { display: inline-flex; align-items: center; gap: 7px; min-height: 44px; padding: 0 12px; border-radius: 12px; color: var(--text-2); text-decoration: none; font-weight: 700; }
.site-footer ul a:hover { color: var(--text); background: color-mix(in oklch, var(--fg) 7%, transparent); }
.site-footer .legal { grid-column: 1 / -1; color: var(--text-3); font-size: .875rem; padding-top: 20px; border-top: 1px solid var(--line); }
@media (max-width: 760px) { .site-footer .wrap { grid-template-columns: 1fr; } .site-footer ul { justify-content: flex-start; } }

/* ---- shared public bits ---- */
.pill-row { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.proof { display: inline-flex; align-items: center; gap: 7px; color: var(--text-2); font-weight: 600; font-size: .9375rem; }
.proof .ic { color: var(--success-text); }
.sticker-tag { display: inline-flex; align-items: center; gap: 7px; padding: 7px 12px; border-radius: 12px; background: var(--gold); color: var(--on-gold); border: 2px solid var(--ink); box-shadow: var(--sticker-shadow-sm); font: 800 .8125rem/1 var(--font-text); }
.glass-panel { background: var(--glass); border: 1px solid var(--line); border-radius: var(--r-xl); box-shadow: var(--e2); backdrop-filter: blur(18px) saturate(150%); -webkit-backdrop-filter: blur(18px) saturate(150%); }

/* ==========================================================================
   The door into the lamp: /login (six views) and /invite (golden ticket)
   ========================================================================== */
body.auth { min-height: 100dvh; }
.auth-shell { min-height: 100dvh; display: grid; grid-template-columns: minmax(0, 1.02fr) minmax(0, .98fr); }
/* two columns: the bar stays over the world column (51%), so on a short screen the tall
   sign-up card never slides under its links or the pause control */
@media (min-width: 1001px) { .nav-world { right: 49%; } }

/* ---- left: the world (lamp, genie, promise) ---- */
.auth-world { position: relative; display: grid; align-content: center; gap: 22px; padding: clamp(28px, 5vw, 72px) clamp(24px, 4.5vw, 72px); overflow: hidden; }
.auth-world::after { content: ""; position: absolute; inset: 0 0 0 auto; width: 1px; background: linear-gradient(180deg, transparent, var(--line-2) 18%, var(--line-2) 82%, transparent); }
/* The scene is a grid row in the flow, after the footer, not an absolute backdrop: as a
   backdrop it had to be hand-fitted to whatever space was left, and 23px too tall at 768
   meant it clipped the footer text. In the flow it cannot overlap anything by construction. */
.auth-scene { position: relative; z-index: 1; height: clamp(104px, 18vh, 240px); width: min(52%, 400px);
  opacity: .95; }
/* the generated lamp is a square painting: keep it clear of the footer row it sits under */
/* the lamp is a square painting: park it bottom-left, clear of the footer row above it and
   of the genie on the right */
/* the footer row sits just above the bottom of this box, so the lamp is held to the strip
   below it: its own height stays under the space the footer leaves */
.auth-scene.has-art img { object-position: left center; filter: drop-shadow(0 10px 30px rgb(0 0 0 / .45)); }
/* once the generated lamp art is in, it replaces the voxel stand-in */
/* Narrow AND short is the one case with no room: the copy alone fills a 768px column below
   about 1300px wide, so the lamp would push the page into scrolling. It is decorative, and
   the genie already carries the visual there. */
@media (max-width: 1300px) and (max-height: 780px) { .auth-scene { display: none; } }

/* the scene stays invisible until JS has decided which art it is, so a refresh cannot flash
   the fallback before the generated art replaces it */
.auth-scene canvas { width: 100%; height: 100%; display: block; visibility: hidden; }
.auth-scene.has-art canvas, .auth-scene.voxel canvas { visibility: visible; }
/* once the generated lamp art is in, it replaces the voxel stand-in */
.auth-scene.has-art canvas { display: none; }
.auth-scene img { width: 100%; height: 100%; display: block; object-fit: contain; }
/* the painting is sized in JS to the room under the footer (bigLamp in js/auth.js) and hangs
   from the top-left of its box, so a larger lamp never moves anything else on the page */
.auth-scene.has-art img { position: absolute; left: 0; top: 0; max-width: none; }
.auth-world .lockup { position: relative; z-index: 1; }
.auth-world .eyebrow { justify-self: start; display: inline-flex; align-items: center; gap: 8px; align-self: start; padding: 5px 12px; border-radius: 99px; border: 1px solid color-mix(in oklch, var(--seed-gold) 45%, transparent); background: var(--gold-tint); color: var(--gold-text); font: 800 .75rem/1 var(--font-mono); letter-spacing: .12em; text-transform: uppercase; }
.auth-world h1 { font-family: var(--font-display); font-weight: 400; font-size: clamp(2.1rem, 1.2rem + 2.8vw, 3.6rem); line-height: 1.02; color: var(--gold); -webkit-text-stroke: .055em var(--ink); paint-order: stroke fill; text-shadow: .055em .055em 0 var(--ink); max-width: 14ch; font-kerning: none; }
.auth-world .lead { max-width: 46ch; font-size: 1.02rem; color: var(--text-2); }
.auth-points { list-style: none; padding: 0; display: grid; gap: 10px; max-width: 44ch; }
.auth-points li { display: flex; gap: 10px; align-items: flex-start; font-size: .9375rem; color: var(--text-2); }
.auth-points .ic { flex: none; margin-top: 2px; color: var(--gold-text); }
.auth-genie { position: absolute; right: clamp(-40px, -2vw, 0px); bottom: -8px; width: clamp(200px, 26vw, 330px); pointer-events: none; filter: drop-shadow(0 18px 36px rgb(3 1 20 / .55)); }
.auth-genie img { width: 100%; }
/* short laptop screens: the copy fills the column, so the genie is held to the space the
   footer leaves under it instead of standing over the points and the footer links. Three
   bands, measured: wide (lamp shown), narrow with the lamp, narrow without it (see .auth-scene) */
@media (min-width: 1301px) and (max-height: 900px) { .auth-genie { width: clamp(150px, calc(62vh - 245px), 330px); } }
@media (min-width: 1001px) and (max-width: 1300px) and (max-height: 900px) { .auth-genie { width: clamp(150px, calc(77vh - 438px), 330px); } }
@media (min-width: 1001px) and (max-width: 1300px) and (max-height: 780px) { .auth-genie { width: clamp(64px, calc(50vh - 280px), 330px); } }
.auth-foot { position: relative; z-index: 1; display: flex; gap: 14px; align-items: center; flex-wrap: wrap; font-size: .8125rem; color: var(--text-3); }
.auth-foot a { color: var(--text-2); display: inline-flex; align-items: center; min-height: 28px; padding: 2px 4px; }

/* ---- right: the card ---- */
.auth-panel { display: grid; align-content: center; justify-items: center; padding: clamp(20px, 4vw, 56px) clamp(16px, 3.5vw, 56px); background: color-mix(in oklch, var(--bg-abyss) 55%, transparent); }
.auth-card { width: min(470px, 100%); display: grid; gap: 16px; padding: clamp(20px, 3vw, 30px); border-radius: var(--r-xl); border: 1px solid var(--line-2); background: linear-gradient(180deg, color-mix(in oklch, var(--panel-2) 96%, transparent), color-mix(in oklch, var(--panel) 96%, transparent)); box-shadow: var(--e3), var(--rim); backdrop-filter: blur(16px); }
.auth-tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; padding: 4px; border-radius: var(--r-md); background: color-mix(in oklch, var(--bg-abyss) 65%, transparent); border: 1px solid var(--line); }
.auth-tabs button { min-height: 40px; border-radius: 10px; font-weight: 800; font-size: .9375rem; color: var(--text-3); transition: color var(--dur-quick), background var(--dur-quick); }
.auth-tabs button:hover { color: var(--text-2); }
.auth-tabs button[aria-selected="true"] { background: var(--gold); color: var(--on-gold); border: 2px solid var(--ink); box-shadow: var(--sticker-shadow-sm); }
.view { display: grid; gap: 14px; }
.view[hidden] { display: none; }
.view > header { display: grid; gap: 6px; }
.auth-card form { display: grid; gap: 15px; }
/* Two related fields share a row so a sign-up form fits a laptop screen without scrolling.
   Their rows are aligned to each other — label, control, then hint — so a field with no
   hint does not stretch its input to fill the gap and end up a different size and height
   from its neighbour. */
.field-pair { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; align-items: start; }
/* align-content:start keeps the rows at their content size — without it, a field stretched
   to match its taller neighbour inflates its own auto rows and pads the input out of shape */
.field-pair > .field { grid-template-rows: auto auto auto auto; align-self: stretch; align-content: start; }
.field-pair > .field > label { grid-row: 1; }
.field-pair > .field > .input, .field-pair > .field > .pw-wrap { grid-row: 2; }
.field-pair > .field > .hint-row { grid-row: 3; align-self: start; }
.field-pair > .field > .error-text { grid-row: 4; align-self: start; }
/* the strength meter gets its own line in a half-width column instead of fighting the text */
.field-pair .hint-row { flex-wrap: wrap; }
.field-pair .pw-bar { flex-basis: 100%; min-width: 0; }
.field-pair .error-text { font-size: .75rem; line-height: 1.35; }
@media (max-width: 520px) { .field-pair { grid-template-columns: minmax(0, 1fr); gap: 15px; } }
.ticket form { display: grid; gap: 14px; }
.view h2 { font-size: 1.35rem; }
.view header p { font-size: .9rem; color: var(--text-2); }
.auth-card .field label { display: flex; align-items: baseline; gap: 8px; }
.auth-card .field label .opt { font-weight: 600; font-size: .75rem; color: var(--text-3); }
.pw-wrap { position: relative; display: flex; }
.pw-wrap .input { flex: 1; padding-right: 46px; }
.pw-wrap .peek { position: absolute; right: 4px; top: 50%; translate: 0 -50%; --btn-h: 38px; width: 38px; color: var(--text-3); }
.pw-wrap .peek:hover { color: var(--text); }
.hint-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; font-size: .78rem; color: var(--text-3); min-height: 1.2em; }
.hint-row .caps { display: inline-flex; gap: 6px; align-items: center; color: var(--warn-text); font-weight: 700; }
.pw-bar { flex: 1; min-width: 80px; height: 5px; border-radius: 99px; background: color-mix(in oklch, var(--fg) 12%, transparent); overflow: hidden; }
.pw-bar i { display: block; height: 100%; width: var(--v, 0%); border-radius: inherit; background: linear-gradient(90deg, var(--danger-text), var(--warn), var(--success)); transition: width var(--dur-base) var(--ease-out); }
.auth-card .btn-wish { width: 100%; justify-content: center; min-height: 48px; font-size: 1rem; }
/* the Cloudflare check (js/auth.js): its space is held from the moment a site key is known, so the
   button never jumps when the widget fills in; the widget is "flexible", the width of the form */
.ts-slot { min-height: 65px; display: grid; align-items: center; border-radius: var(--r-sm); }
.ts-slot[hidden] { display: none; }
.ts-slot:empty { background: color-mix(in oklch, var(--fg) 5%, transparent); border: 1px dashed var(--line-2); }
.ts-slot > * { max-width: 100%; }
.ts-note { display: flex; gap: 8px; align-items: flex-start; margin-top: -6px; font-size: .8125rem; line-height: 1.4; color: var(--warn-text); }
.ts-note .ic { flex: none; margin-top: 1px; }
.or-line { display: flex; align-items: center; gap: 12px; color: var(--text-4); font: 600 .75rem var(--font-mono); letter-spacing: .1em; text-transform: uppercase; }
.or-line::before, .or-line::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.oauth-btn { width: 100%; justify-content: center; min-height: 46px; }
/* off while a form is working (js/auth.js oauthLock): a press then made a second account beside the
   one being created */
.oauth-btn[aria-disabled="true"] { opacity: .5; pointer-events: none; cursor: not-allowed; }
/* "you already have an account with this email" (?error=google_account_exists): the ways on */
.google-exists { display: grid; gap: 8px; }
.google-exists[hidden] { display: none; }
.google-exists .btn { width: 100%; justify-content: center; min-height: 44px; }
.auth-links { display: flex; gap: 14px; flex-wrap: wrap; justify-content: space-between; font-size: .875rem; }
.auth-links a, .link-btn { color: var(--accent-text); font-weight: 700; background: none; display: inline-flex; align-items: center; min-height: 30px; padding: 2px 4px; border-radius: 8px; }
.auth-note a { display: inline-flex; align-items: center; min-height: 28px; }
.link-btn:hover, .auth-links a:hover { text-decoration: underline; }
.form-msg { display: flex; gap: 10px; align-items: flex-start; padding: 11px 13px; border-radius: var(--r-sm); font-size: .875rem; line-height: 1.45; }
.form-msg[hidden] { display: none; }
.form-msg.err { background: var(--danger-tint); color: var(--text); border: 1px solid color-mix(in oklch, var(--seed-danger) 45%, transparent); }
.form-msg.ok { background: var(--success-tint); color: var(--text); border: 1px solid color-mix(in oklch, var(--seed-success) 45%, transparent); }
.form-msg.info { background: var(--accent-tint); color: var(--text); border: 1px solid var(--line-accent); }
.form-msg .ic { flex: none; margin-top: 1px; }
.form-msg.err .ic { color: var(--danger-text); } .form-msg.ok .ic { color: var(--success-text); } .form-msg.info .ic { color: var(--accent-text); }
.ctx-banner { display: flex; gap: 12px; align-items: center; padding: 12px 14px; border-radius: var(--r-md); border: 1.5px solid color-mix(in oklch, var(--seed-gold) 50%, transparent); background: linear-gradient(135deg, var(--gold-tint), transparent 70%); }
.ctx-banner .ic { color: var(--gold-text); flex: none; }
.ctx-banner b { display: block; font-size: .9375rem; }
.ctx-banner span { font-size: .8125rem; color: var(--text-2); }
.invite-chip { display: inline-flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 99px; background: var(--gold-tint); border: 1px solid color-mix(in oklch, var(--seed-gold) 45%, transparent); font: 700 .8125rem var(--font-mono); color: var(--gold-text); }
.auth-note { font-size: .8125rem; color: var(--text-3); }
.auth-legal { font-size: .75rem; color: var(--text-4); text-align: center; }
.auth-legal a { color: var(--text-3); }

/* ---- the invite ticket ---- */
.ticket-wrap { display: grid; place-items: center; min-height: 100dvh; padding: 96px var(--gutter) 60px; }
.ticket-stage { position: relative; width: min(560px, 100%); }
.ticket { position: relative; width: 100%; display: grid; gap: 18px; padding: clamp(22px, 4vw, 38px); border-radius: 26px; border: 2px solid var(--ink);
  background: linear-gradient(160deg, color-mix(in oklch, var(--seed-gold) 20%, var(--panel-2)), var(--panel) 62%); box-shadow: var(--sticker-shadow), 0 40px 90px -50px #000; }
.ticket::before, .ticket::after { content: ""; position: absolute; top: 50%; width: 30px; height: 30px; border-radius: 50%; background: var(--bg-abyss); border: 2px solid var(--ink); translate: 0 -50%; }
.ticket::before { left: -17px; } .ticket::after { right: -17px; }
.ticket .perf { position: absolute; inset: 0 26px; border-left: 2px dashed color-mix(in oklch, var(--ink) 55%, transparent); pointer-events: none; display: none; }
.ticket-head { display: flex; align-items: center; gap: 14px; }
.ticket-head .stub { width: 56px; height: 56px; border-radius: 16px; display: grid; place-items: center; background: var(--gold); color: var(--ink); border: 2px solid var(--ink); box-shadow: var(--sticker-shadow-sm); }
.ticket-head .stub .ic { width: 28px; height: 28px; --ic-stroke: 2.2; }
.ticket h1 { font-family: var(--font-display); font-weight: 400; font-size: clamp(1.7rem, 1.2rem + 1.6vw, 2.5rem); line-height: 1.05; color: var(--gold); -webkit-text-stroke: .05em var(--ink); paint-order: stroke fill; text-shadow: .05em .05em 0 var(--ink); font-kerning: none; }
.ticket .sub { color: var(--text-2); font-size: .9375rem; }
.ticket .code-field .input { font: 700 1.15rem/1 var(--font-mono); letter-spacing: .14em; text-align: center; min-height: 56px; text-transform: uppercase; }
.ticket .btn-wish { width: 100%; justify-content: center; min-height: 50px; font-size: 1.02rem; }
.ticket-alt { display: grid; gap: 8px; padding-top: 4px; }
.ticket .auth-note { padding-right: 104px; }
.ticket-genie { position: absolute; right: -30px; bottom: -34px; width: 136px; pointer-events: none; filter: drop-shadow(0 14px 26px rgb(3 1 20 / .5)); }
.ticket-genie img { width: 100%; }
.ticket-foot { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; align-items: center; font-size: .8125rem; color: var(--text-3); }
.ticket-foot a { display: inline-flex; align-items: center; min-height: 32px; padding: 2px 8px; border-radius: 8px; }
.ticket-foot a:hover { background: color-mix(in oklch, var(--fg) 8%, transparent); }

/* a sign-up form should never need scrolling: on a short screen the panel gives back
   its own padding before the card gives up any of its fields */
@media (min-width: 1001px) and (max-height: 920px) {
  .auth-panel { padding-block: clamp(14px, 2.2vh, 30px); }
  .auth-card { gap: 13px; }
  .auth-card form { gap: 13px; }
}
/* short laptop screens: tighten further, because pressing Create account adds a validation
   message under every field and the form must still fit without scrolling */
@media (min-width: 1001px) and (max-height: 820px) {
  .auth-panel { padding-block: 8px; }
  .auth-card { gap: 11px; }
  .auth-card form { gap: 11px; }
  .view { gap: 11px; } /* room for the AUTH-3 sign-up note */
  .auth-tabs button { min-height: 36px; }
  .auth-card .btn-wish { min-height: 44px; }
  .oauth-btn { min-height: 42px; }
  .auth-card .field label .opt { font-size: .6875rem; }
}

/* ---- responsive ---- */
@media (max-width: 1000px) {
  .auth-shell { grid-template-columns: minmax(0, 1fr); }
  .auth-world { padding-top: 88px; padding-bottom: 26px; gap: 16px; }
  .auth-world::after { inset: auto 0 0 0; width: auto; height: 1px; background: linear-gradient(90deg, transparent, var(--line-2) 20%, var(--line-2) 80%, transparent); }
  .auth-world h1 { max-width: 20ch; }
  .auth-genie { width: 150px; right: 6px; bottom: 34px; opacity: .92; }
  .auth-points { display: none; }
  .auth-panel { padding-top: 22px; padding-bottom: 96px; background: transparent; }
  .auth-scene { display: none; } /* the genie carries the world on phones */
  .auth-foot { padding-right: 148px; }
  .nav-slim .links { display: flex; }
  .nav-slim .nav-bar { gap: 10px; padding-right: 6px; }
  .nav-slim .links a { padding: 0 8px; font-size: .875rem; }
}
@media (max-width: 420px) {
  .nav-slim .links a { padding: 0 6px; font-size: .84rem; }
  .nav-slim .nav-bar { padding-left: 10px; }
}
/* the pause control (GLB-11) shares the bar: tighten it on the smallest phones */
@media (max-width: 340px) {
  .nav-slim .nav-bar { gap: 6px; padding-left: 6px; }
  .nav-slim .links { gap: 0; }
  .nav-slim .links a { padding: 0 5px; }
}
@media (max-width: 560px) {
  .auth-world { padding-top: 78px; }
  .auth-world .lead { font-size: .9375rem; }
  .auth-genie { width: 120px; opacity: .75; }
  .auth-card { padding: 18px 16px; }
  .ticket-wrap { padding-top: 84px; }
  .ticket-genie { width: 96px; right: -8px; bottom: -18px; }
}

