/* ═══════════════════════════════════════════════════════════════════════════
   patriot.plus LIVE — THEMES, the variable layer.

   live.css owns the two registers. This file adds no look of its own: it
   re-expresses the colours live.css (and the projector's show.css) hard-coded
   as literals so they become custom properties, each defaulting to the exact
   value it has today. Load this file and nothing changes. Set a variable and
   the register repaints.

   ── LOAD ORDER ────────────────────────────────────────────────────────────
   Link this LAST in <head>, after live.css / show.css / controller.css and
   after any page's own <style> block:

       <link rel="stylesheet" href="/live/assets/live.css?v=7">
       <link rel="stylesheet" href="/live/assets/show.css?v=1">       (projector)
       <link rel="stylesheet" href="/live/assets/controller.css?v=1"> (phone)
       <link rel="stylesheet" href="/live/assets/themes.css?v=1">     <-- last

   Every selector here is one element-name heavier than the rule it replaces
   (`body.sky .panel` against live.css's `.sky .panel`), so this file still
   wins if it is ever linked out of order, or if a page adds its own <style>.

   ── THE CONTRACT ──────────────────────────────────────────────────────────
   Nothing here sets a colour that is not a var(). Nothing here is applied by a
   class, a data-attribute, or any media query other than reduced-motion.
   themes.js changes a theme by writing custom properties onto ONE element and
   doing nothing else, so a theme can be applied before first paint.

   ── THE STATE COLOURS ARE MEANING, NOT DECORATION ─────────────────────────
   A theme may recolour these. A theme may not invert what they say.
     --good / --good-lift / --green   correct, gained, live, up          GREEN
     --bad  / --bad-lift  / --red     wrong, lost, danger, down          RED
     --orange-ink / --timer-low       warning, running out               AMBER
     --yellow                         the thing you have selected        (a bg)
     --gold                           the winner / the thing to look at
   themes.js `auditTheme()` enforces both the hue bands and the contrast floors.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── the sky itself (body.sky::before) ─────────────────────────────────
     Held as four stops plus two ellipse glows, with the geometry left in the
     rule, so the composite behind any given line of projector type can be
     computed exactly rather than guessed at. */
  --sky-glow-a: rgba(250, 70, 22, .55);   /* the horizon burn */
  --sky-glow-b: rgba(240, 168, 48, .18);  /* the wider warm haze */
  --sky-1: #05070f;   /* 0%   zenith */
  --sky-2: #0b1240;   /* 38%  upper */
  --sky-3: #1a1a5e;   /* 55%  the band the glow sits in */
  --sky-4: #0A0E1A;   /* 60%+ the floor */

  /* ── the grid floor (body.sky::after) ── */
  --grid-ink: rgba(250, 70, 22, .85);
  --grid-x: 120px;
  --grid-y: 60px;
  --grid-haze: rgba(10, 14, 26, .2);
  --grid-floor: var(--dark);
  --grid-opacity: .75;
  --grid-tilt: 58deg;
  --grid-persp: 520px;

  /* ── the striped sun ── */
  --sun-bg: linear-gradient(180deg, #ffd166 0%, #ff8a3d 45%, #ff3d7f 100%);
  --sun-glow: 0 0 80px rgba(255, 120, 60, .45);
  --sun-opacity: 1;

  /* ── the CRT ── */
  --scan-ink: rgba(0, 0, 0, .28);
  --scan-blend: multiply;
  --scan-opacity: 1;

  /* ── display type ──
     --blue is already the hard offset shadow in every rule that has one, in
     both live.css and show.css. Set it close to the ground to retire the
     offset without disturbing anything else. */
  --display-ink: #fff;                          /* .sky .h-display  */
  --bloom: 0 0 22px rgba(255, 90, 31, .45);     /* the halo, phone-side */
  --eyebrow-ink: var(--gold);

  /* ── sky edges, from hairline to heavy. Each maps to a literal live.css
        uses at that exact alpha; keeping them separate is what lets the
        house theme come out pixel-identical. ── */
  --sky-line-2: rgba(255, 255, 255, .2);    /* .sky .shift-row            */
  --sky-edge-1: rgba(255, 255, 255, .3);    /* inputs, toasts, lb rows    */
  --sky-edge-2: rgba(255, 255, 255, .4);    /* .progress, shift head, img */
  --sky-edge-3: rgba(255, 255, 255, .55);   /* bar track, pj-opt, podium  */
  --sky-ghost-line: rgba(255, 255, 255, .35);
  --sky-ghost-hover: rgba(255, 255, 255, .08);

  /* ── sky fills ── */
  --sky-well-1: rgba(0, 0, 0, .3);     /* .progress          */
  --sky-well-2: rgba(0, 0, 0, .35);    /* .bar .track        */
  --sky-well-3: rgba(0, 0, 0, .4);     /* .input             */
  --sky-row-bg: rgba(0, 12, 36, .55);  /* .lb-row, .pj-opt   */
  --sky-card-bg: rgba(0, 12, 36, .6);  /* .rank              */
  --sky-panel: rgba(0, 12, 36, .72);   /* .panel             */
  --sky-toast-bg: rgba(0, 12, 36, .92);
  --sky-btn-hover: #ffc35a;

  /* ── result bars ── */
  --bar-fill: linear-gradient(90deg, var(--orange), #ff8a3d);
  --bar-glow: 0 0 18px rgba(250, 70, 22, .6);
  --bar-win-fill: linear-gradient(90deg, var(--gold), #ffd77a);
  --bar-win-glow: 0 0 22px rgba(240, 168, 48, .7);
  --bar-unsure: #bdbdbd;
  /* On the sky register live.css's `.bar.unsure .track > i` (#bdbdbd) and
     `.sky .bar .track > i` (the orange fill) have IDENTICAL specificity, and
     the sky rule is written later — so today a projector paints "Not sure"
     with the same ramp as the leading answer. That is a state colour saying
     the wrong thing, but it is what the house looks like, so the default here
     reproduces it exactly and each new theme opts into grey instead. Worth
     fixing properly in live.css; see the report. */
  --bar-unsure-sky: var(--bar-fill);
  --bar-unsure-glow: var(--bar-glow);

  /* ── state, lifted onto the dark register ── */
  --good-lift: #7dffb0;   /* .sky .shift .up,   .sky .pill.on  */
  --bad-lift:  #ff9d8a;   /* .sky .shift .down, .sky .pill.bad */
  --timer-low: var(--orange-ink);   /* .timer.low .countdown   */

  /* ── state washes on paper. live.css only; controller.css never uses these,
        so recolouring them cannot desync the phone's two stylesheets. ── */
  --good-wash: #d8f3e1;
  --danger-wash: #ffd9d2;
  --danger-wash-hover: #ff9d8a;
  --select-wash: #fff6bf;

  /* ── the projector ── */
  --pj-edge: var(--show-edge);
  --pj-card: var(--show-card);
  --pj-bloom: 0 0 30px rgba(255, 90, 31, .45);      /* big-screen halo  */
  --pj-bloom-gold: 0 0 6vh rgba(240, 168, 48, .85); /* the winner's halo */
  --pj-podium: linear-gradient(180deg, rgba(0, 33, 165, .55), rgba(0, 12, 36, .8));
  --hist-fill: linear-gradient(180deg, var(--gold), var(--orange));

  /* A QR code needs a light quiet zone or a phone camera will not find it.
     A theme may tint this; auditTheme() refuses anything dark. */
  --qr-mat: #fff;

  /* ── motion (live/THEMES.md 2) ──
     The same values themes.js holds as the house defaults, restated here for
     the same reason as everything else in this block: a page that links this
     file and applies no theme has to behave exactly as it did before.
     --motion-scale and --motion-run are derived from --motion by themes.js,
     never authored; see the MOTION section at the foot of this file. */
  --tr-kind: rise;
  --tr-dur: .5s;
  --tr-ease: cubic-bezier(.16, 1, .3, 1);
  --ph-tr-kind: fade;
  --ph-tr-dur: .3s;
  --motion: calm;
  --motion-scale: 1;
  --motion-run: running;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SKY — the ground.
   Each rule overrides only the properties it names; position, z-index, sizing
   and masking all stay with live.css.
   ═══════════════════════════════════════════════════════════════════════════ */
body.sky { color: var(--display-ink); background: var(--dark); }

body.sky::before {
  background:
    radial-gradient(ellipse 70% 42% at 50% 62%, var(--sky-glow-a), transparent 70%),
    radial-gradient(ellipse 90% 60% at 50% 70%, var(--sky-glow-b), transparent 70%),
    linear-gradient(180deg, var(--sky-1) 0%, var(--sky-2) 38%, var(--sky-3) 55%, var(--sky-4) 60%);
}

body.sky::after {
  background:
    repeating-linear-gradient(90deg, var(--grid-ink) 0 2px, transparent 2px var(--grid-x)),
    repeating-linear-gradient(0deg,  var(--grid-ink) 0 2px, transparent 2px var(--grid-y)),
    linear-gradient(180deg, var(--grid-haze), var(--grid-floor) 90%);
  transform: perspective(var(--grid-persp)) rotateX(var(--grid-tilt));
  opacity: var(--grid-opacity);
}

body .sun { background: var(--sun-bg); box-shadow: var(--sun-glow); opacity: var(--sun-opacity); }

body .scanlines {
  background: repeating-linear-gradient(0deg, var(--scan-ink) 0 2px, rgba(0, 0, 0, 0) 2px 4px);
  mix-blend-mode: var(--scan-blend);
  opacity: var(--scan-opacity);
}

/* ═══════════════════════════════════════════════════════════════════════════
   SKY — type and furniture
   ═══════════════════════════════════════════════════════════════════════════ */
body.sky .eyebrow { color: var(--eyebrow-ink); }
body.sky .h-display { color: var(--display-ink); text-shadow: var(--bloom), 4px 4px 0 var(--blue); }
body.sky .lede, body.sky .muted { color: var(--sky-muted); }
body.sky .faint { color: var(--sky-faint); }
body.sky .countdown { text-shadow: 3px 3px 0 var(--blue); }

body.sky .panel { background: var(--sky-panel); border-color: var(--sky-line); }
body.sky .btn:hover { background: var(--sky-btn-hover); }
body.sky .btn-ghost { color: var(--display-ink); border-color: var(--sky-ghost-line); background: transparent; }
body.sky .btn-ghost:hover { background: var(--sky-ghost-hover); }
body.sky .input { background: var(--sky-well-3); border-color: var(--sky-edge-1); color: var(--display-ink); }
body.sky .input:focus { border-color: var(--gold); }
body.sky .toast { background: var(--sky-toast-bg); color: var(--display-ink); border-color: var(--sky-edge-1); }
body.sky .wait-dots i { background: var(--gold); }
body.sky .ev { border-top-color: var(--sky-line); }

/* ═══════════════════════════════════════════════════════════════════════════
   SKY — state.
   The pill rules also beat the copies inside live/index.html's own <style>,
   which is the reason every selector here carries `body`.
   ═══════════════════════════════════════════════════════════════════════════ */
body.sky .shift .up,
body.sky .pill.on      { color: var(--good-lift); }
body.sky .pill.on      { border-color: var(--good-lift); }
body.sky .shift .down,
body.sky .pill.bad,
body.sky .rail-mv.down { color: var(--bad-lift); }
body.sky .pill.bad     { border-color: var(--bad-lift); }
body.sky .pill.warn    { color: var(--gold); border-color: var(--gold); }
body.sky .timer.low .countdown { color: var(--timer-low); }

body.sky .progress { border-color: var(--sky-edge-2); background: var(--sky-well-1); }
body.sky .progress > i { background: var(--bar-fill); }
body.sky .bar .track { border-color: var(--sky-edge-3); background: var(--sky-well-2); }
body.sky .bar .track > i { background: var(--bar-fill); box-shadow: var(--bar-glow); }
body.sky .bar.win .track > i { background: var(--bar-win-fill); box-shadow: var(--bar-win-glow); }
body.sky .bar.unsure .track > i { background: var(--bar-unsure-sky); box-shadow: var(--bar-unsure-glow); }
body.sky .bar .opt .key { border-color: var(--gold); color: var(--gold); }
body.sky .shift-head { color: var(--gold); border-color: var(--sky-edge-2); }
body.sky .shift-row { border-color: var(--sky-line-2); }

/* ═══════════════════════════════════════════════════════════════════════════
   PROJECTOR — the .pj-* set, which lives in live.css
   ═══════════════════════════════════════════════════════════════════════════ */
body.sky .pj-kicker { color: var(--eyebrow-ink); }
body.sky .pj-sub, body.sky .pj-foot, body.sky .rank-meta, body.sky .hist-col span { color: var(--show-dim); }
body.sky .pj-body { color: var(--show-ink); }
body.sky .pj-opt { border-color: var(--sky-edge-3); background: var(--sky-row-bg); }
body.sky .pj-opt .key { border-color: var(--gold); color: var(--gold); }
body.sky .pj-img { border-color: var(--sky-edge-2); }
body.sky .big-num { color: var(--show-ink); text-shadow: 3px 3px 0 var(--blue); }
body.sky .pj-quote { color: var(--show-ink); text-shadow: 3px 3px 0 var(--blue); }
body.sky .pj-author, body.sky .pj-foot .gold { color: var(--gold); }
body.sky .rank { border-color: var(--pj-edge); background: var(--sky-card-bg); }
body.sky .rank[data-rank="1"] { border-color: var(--gold); }
body.sky .rank-no { color: var(--gold); text-shadow: 3px 3px 0 var(--blue); }
body.sky .rank-text { color: var(--show-ink); }
body.sky .hist-col i { background: var(--hist-fill); border-color: var(--pj-edge); }
body.sky .cloud span { text-shadow: 2px 2px 0 var(--blue); }
body.sky .lb-row { border-color: var(--sky-edge-1); background: var(--sky-row-bg); }
body.sky .lb-row:nth-child(1) { border-color: var(--gold); }
body.sky .lb-no { color: var(--gold); }
body .qr { background: var(--qr-mat); border-color: var(--qr-mat); }

/* ═══════════════════════════════════════════════════════════════════════════
   PROJECTOR — show.css.
   show.css already routes every hard offset through var(--blue) and every
   accent through var(--gold), so only two things need restating here: the
   display ink it writes as #fff, and the warm halo it writes as a literal.
   Offsets are repeated at their exact original sizes so the house theme comes
   out identical.
   ═══════════════════════════════════════════════════════════════════════════ */
body.sky .lob-title,
body.sky .lob-num,
body.sky .tr-title,
body.sky .vs-side,
body.sky .cd-num,
body.sky .wh-name,
body.sky .bz-open,
body.sky .bz-name,
body.sky .pod-no,
body.sky .celeb-text,
body.sky .qcard-q,
body.sky .dr-artist,
body.sky .pj-code { color: var(--show-ink); }

body.sky .lob-title  { text-shadow: var(--pj-bloom), .5vh .5vh 0 var(--blue); }
body.sky .lob-num    { text-shadow: var(--pj-bloom), .5vh .5vh 0 var(--blue); }
body.sky .pj-code    { text-shadow: var(--pj-bloom), .55vh .55vh 0 var(--blue); }
body.sky .tr-title   { text-shadow: var(--pj-bloom), .6vh .6vh 0 var(--blue); }
body.sky .vs-side    { text-shadow: var(--pj-bloom), .55vh .55vh 0 var(--blue); }
body.sky .vs-mid     { text-shadow: var(--pj-bloom), .5vh .5vh 0 var(--blue); }
body.sky .cd-num     { text-shadow: var(--pj-bloom), 1vh 1vh 0 var(--blue); }
body.sky .bz-open    { text-shadow: var(--pj-bloom), 1vh 1vh 0 var(--blue); }
body.sky .pod-no     { text-shadow: var(--pj-bloom), .6vh .6vh 0 var(--blue); }
body.sky .qcard-q    { text-shadow: var(--pj-bloom), .5vh .5vh 0 var(--blue); }
body.sky .tr-double .tr-title { text-shadow: var(--pj-bloom-gold), .6vh .6vh 0 var(--blue); }
body.sky .wh-name    { text-shadow: var(--pj-bloom-gold), .55vh .55vh 0 var(--blue); }
body.sky .bz-name    { text-shadow: var(--pj-bloom-gold), 1vh 1vh 0 var(--blue); }
body.sky .celeb-text { text-shadow: var(--pj-bloom-gold), .5vh .5vh 0 var(--blue); border-color: var(--gold); }
body.sky .pod-block  { border-color: var(--sky-edge-3); background: var(--pj-podium); }
body.sky .bz-tag     { color: var(--show-dim); border-color: var(--sky-edge-1); background: var(--sky-card-bg); }

/* ═══════════════════════════════════════════════════════════════════════════
   PAPER — the phone, once it has joined.

   Deliberately thin. The joined phone's chrome is drawn by controller.css,
   which is not ours and which hard-codes #fff cards and #000 drop-shadows in
   51 places. Re-routing live.css's copies of those literals here would leave
   live.css's cards and controller.css's cards two different colours on the
   same screen, so they stay as they are, and the paper register is themed
   through the variables both files already share: --paper --ink --ink-2 --grey
   --yellow --orange --orange-ink --gold --good --bad --bevel-color and the
   three fonts. The only literals restated are the four state washes that
   controller.css does not use, so "wrong" can be recoloured without desync.
   ═══════════════════════════════════════════════════════════════════════════ */
body.paper .btn-good   { background: var(--good-wash); }
body.paper .btn-danger { background: var(--danger-wash); }
body.paper .btn-danger:hover { background: var(--danger-wash-hover); }
body.paper .toast.good { background: var(--good-wash); }
body.paper .toast.bad  { background: var(--danger-wash); }
body.paper .conn.bad   { background: var(--danger-wash); }
body.paper .item.on,
body.paper .selected-card { background: var(--select-wash); }
body.paper .bar.unsure .track > i { background: var(--bar-unsure); }
body.paper .timer.low .countdown { color: var(--timer-low); }

/* ═══════════════════════════════════════════════════════════════════════════
   MOTION
   live/THEMES.md 2. Six tokens: --tr-kind / --tr-dur / --tr-ease on the
   projector, --ph-tr-kind / --ph-tr-dur on the phone, --motion for the idle
   screen. themes.js holds the house values, which are what these pages do
   today, so linking this file still changes nothing.

   HOW A KIND IS CHOSEN. `animation-name: var(--tr-kind)`: the token's value
   is spent directly as the name of one of the @keyframes below. No class to
   toggle, no branch in JavaScript, nothing to keep in step: a theme names a
   kind and the browser looks it up. A kind nobody declared animates nothing,
   which fails to a cut in front of a room rather than to a broken screen.

   WHAT NEVER MOVES. None of this ever gates input. The animations run on the
   container, the projector's stage or the phone's <main>, and the container
   is in the document with its buttons live from the first frame. Nothing here
   sets pointer-events, disables a control or delays a draw. A phone's answer
   buttons work the instant the round opens, whatever the screen is doing.
   ═══════════════════════════════════════════════════════════════════════════ */

@keyframes cut    { from { opacity: 1; } to { opacity: 1; } }
@keyframes fade   { from { opacity: 0; } to { opacity: 1; } }
/* `rise` is show.css's trIn to the decimal: it is what a round change has
   always looked like, and the house theme names it so nothing moved today. */
@keyframes rise   { from { opacity: 0; transform: scale(.94); } to { opacity: 1; transform: none; } }
@keyframes slide  { from { opacity: 0; transform: translateX(5%); } to { opacity: 1; transform: none; } }
/* Percentages, not vw: one set of keyframes serves a 100-inch screen and a
   phone honestly, because each is measured against the thing that is moving. */
@keyframes liquid {
  from { opacity: 0; filter: blur(16px); transform: scale(1.02); }
  55%  { opacity: 1; }
  to   { opacity: 1; filter: blur(0); transform: none; }
}
@keyframes flip {
  from { opacity: 0; transform: perspective(1400px) rotateY(-14deg) scale(.96); }
  to   { opacity: 1; transform: none; }
}
/* The other half of the cross-dissolve: the outgoing picture, on the ghost
   the projector leaves behind for exactly one transition. */
@keyframes liquid-out {
  from { opacity: 1; filter: blur(0); transform: none; }
  to   { opacity: 0; filter: blur(16px); transform: scale(1.03); }
}

/* ── the projector ────────────────────────────────────────────────────────
   Two places a round gives way to the next: the takeover cards show.css
   already animates, and the stage itself, which display/page.js marks on a
   round change. Both rules are one element heavier than the ones they
   replace, so this file wins wherever it is linked. */
body .tr-in,
body .pj-stage.tr-swap {
  animation: var(--tr-kind, rise) var(--tr-dur, .5s) var(--tr-ease, cubic-bezier(.16, 1, .3, 1)) both;
}
/* The outgoing stage, cloned, inert and above the incoming one. It exists for
   the length of one `liquid` transition and page.js removes it; `cut` and the
   rest never make one. Written `.pj-stage.pj-ghost` and not `.pj-ghost` so
   that it outweighs the swap rule above whatever page.js leaves on the clone:
   the outgoing picture must never play the arriving animation. */
body .pj-stage.pj-ghost {
  position: fixed; inset: 0; z-index: var(--z-frame, 2); pointer-events: none;
  animation: liquid-out var(--tr-dur, .5s) var(--tr-ease, cubic-bezier(.16, 1, .3, 1)) both;
}

/* ── the phone ───────────────────────────────────────────────────────────── */
body #main.ph-swap {
  animation: var(--ph-tr-kind, fade) var(--ph-tr-dur, .3s) var(--tr-ease, cubic-bezier(.16, 1, .3, 1)) both;
}

/* ── --motion, on the loops that run when nothing is happening ─────────────
   themes.js turns the keyword into the two things a loop is made of: a
   duration multiplier and a play state. `still` parks each loop on its first
   frame, and every one of these rests on its first frame, so still is still
   and not merely slow. Durations are restated because a multiplier has to
   multiply something; each is the number the rule it modifies already has. */
body .pill.live .dot,
body .corner-pill .dot,
body .lob-sub .dots i,
body .bz-idle .bz-open { animation-duration: calc(1.4s * var(--motion-scale, 1)); }
body .wait-dots i { animation-duration: calc(1.2s * var(--motion-scale, 1)); }
body .pill.live .dot,
body .corner-pill .dot,
body .lob-sub .dots i,
body .bz-idle .bz-open,
body .wait-dots i,
body .sun.drift,
body.sky.drift::after { animation-play-state: var(--motion-run, running); }

/* ── reduced motion, and there are no exceptions ───────────────────────────
   Every kind is a fade at .15s, --motion is still. `!important` is what lets
   a stylesheet overrule the tokens applyTheme writes inline onto <html>: an
   important author declaration outranks a normal inline one, and nothing
   themes.js writes is important.

   live.css already clamps every animation-duration to 0.01ms under this
   query, which would turn the .15s fade into no fade at all. The rules below
   carry more weight than its `*`, so the contract's .15s survives.

   The rest of this block is older than the tokens and stays: an infinite
   animation clamped to 0.01ms still runs, it just runs impossibly fast, so
   the ambient loops are stopped outright and the ticker is parked where it
   can be read. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --tr-kind: fade !important;
    --ph-tr-kind: fade !important;
    --tr-dur: .15s !important;
    --ph-tr-dur: .15s !important;
    --motion: still !important;
    --motion-scale: 1 !important;
    --motion-run: paused !important;
  }
  body .tr-in,
  body .pj-stage.tr-swap,
  body #main.ph-swap {
    animation-name: fade !important;
    animation-duration: .15s !important;
    animation-timing-function: linear !important;
  }
  /* A second copy of the screen dissolving away is the one thing a reader who
     asked for less motion certainly did not ask for. */
  body .pj-stage.pj-ghost { display: none !important; }

  body.sky::after,
  body.sky.drift::after,
  body .sun.drift,
  body.sky .bz-open,
  body.sky .tr-double .tr-title,
  body .pill.live .dot,
  body .timer.low .countdown,
  body .wait-dots i { animation: none !important; }
  body .ticker > span { animation: none !important; padding-left: 0; }
}
