/* =========================================================================
   Stars of Ballina · Sponsor Scotty · live fundraising kiosk
   Responsive: portrait (9:16) and landscape (16:9), one URL. Every size is a
   multiple of --u (a viewport-scaled unit) so the board scales crisply from
   1080×1920 up to 2160×3840. Fonts self-hosted, no external requests at all.
   Palette taken from the campaign's own poster: neon cyan + gold on deep navy.
   Text/bg pairs verified with scripts/contrast.py.
   ========================================================================= */

@font-face{
  font-family:"Unbounded";
  src:url("assets/fonts/unbounded-var.woff2") format("woff2");
  font-weight:400 800; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Space Grotesk";
  src:url("assets/fonts/spacegrotesk-var.woff2") format("woff2");
  font-weight:300 700; font-style:normal; font-display:swap;
}

:root{
  --night:#080d2b; --night-mid:#0e1540; --night-2:#16205e; --night-deep:#050818;
  --text:#f2f8ff; --text-soft:#c3d4ef; --text-dim:#9fb2d4;
  --cyan:#5ce1ff; --cyan-soft:#a9ecff; --gold:#ffd447; --gold-warm:#ffb03a;
  --paper:#fff8ec; --ink:#0b1030; --ink-soft:#3a4275;

  --display:"Unbounded",system-ui,sans-serif;
  --data:"Space Grotesk",system-ui,sans-serif;

  /* Scaling unit: 1px at the reference height, scales with the viewport.
     Portrait reference = 1920 tall; landscape = 1080 (below).

     NOTHING MODERN BELONGS IN THIS DECLARATION. --u is load-bearing: if it fails to
     resolve, every calc(N * var(--u)) length in this sheet fails with it and the layout
     collapses (images fall back to their native pixel size, which on a kiosk means one
     asset filling the screen). So the base is calc() + vh only — universal — and each
     newer function is layered on as its own @supports upgrade: clamp() (Chrome 79+),
     then dvh (Chrome 108+). A WebView old enough to lack them keeps this base and still
     renders. The tutu board paid for this lesson twice on the very screens this kiosk
     is going onto — see kiosk-old-webview-compat. */
  --ref:1920;
  --u:calc(100vh / var(--ref));

  --r-lg:calc(30 * var(--u)); --r-md:calc(20 * var(--u)); --r-sm:calc(13 * var(--u));
  --gap:calc(15 * var(--u));
}

*{box-sizing:border-box; margin:0; padding:0;
  -webkit-user-select:none; user-select:none; -webkit-touch-callout:none;}

html,body{height:100%; overflow:hidden; background:var(--night);}
html::-webkit-scrollbar,body::-webkit-scrollbar{display:none;}

body{
  font-family:var(--data); color:var(--text);
  touch-action:manipulation; -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  background:
    radial-gradient(120% 78% at 50% -6%, #1b2a7a 0%, transparent 56%),
    radial-gradient(150% 90% at 50% 106%, #241a6b 0%, transparent 54%),
    linear-gradient(180deg, var(--night-deep) 0%, var(--night) 38%, var(--night-mid) 100%);
}

/* ---------- stage: grid (portrait areas) ------------------------------- */
.stage{
  position:relative; height:100vh; width:100%;
  max-width:min(100vw, calc(100vh * 0.62)); margin-inline:auto;
  padding:calc(30 * var(--u)) calc(34 * var(--u));
  overflow:hidden; isolation:isolate;
  display:grid; gap:var(--gap);
  grid-template-columns:1fr 1fr;
  grid-template-rows:auto auto 1fr auto auto 1fr auto auto;
  grid-template-areas:
    "head    head"
    "hook    hook"
    ".       thermo"
    "hero    thermo"
    "score   thermo"
    ".       thermo"
    "count   count"
    "qrcard  charity";
  animation:shift 120s ease-in-out infinite alternate;   /* burn-in guard */
}
/* Upgrade 1 — clamp() (Chrome 79+): bounds --u so an off-spec window height can't
   scale the design absurdly. The kiosk's own 1920/3840 land inside the bounds.
   ⚠️ The LOWER bound is not cosmetic. Anything above the proportional value scales the
   design bigger than its box and the board overflows — which is exactly what a small
   iframe on the club's website is. It was 0.42px (fine for a tower, fine for a desktop
   preview) and every portrait embed under ~810px tall overflowed, by 128px at 400×711.
   0.18px keeps realistic embeds proportional; below that the type is unreadable anyway,
   so a floor there costs nothing real. */
@supports (width:clamp(1px,2px,3px)){
  :root{ --u:clamp(0.18px, calc(100vh / var(--ref)), 2.4px); }
}
/* Upgrade 2 — dynamic viewport height (Chrome 108+, implies clamp): handles mobile
   browser chrome on a web embed. Old kiosk WebViews keep the vh base above. */
@supports (height:100dvh){
  :root{ --u:clamp(0.18px, calc(100dvh / var(--ref)), 2.4px); }
  .stage{ height:100dvh; max-width:min(100vw, calc(100dvh * 0.62)); }
}

/* ---------- ambient decoration ----------------------------------------- */
.ambient{position:absolute; inset:0; z-index:-1; overflow:hidden; pointer-events:none;}

/* Starfield: two tiled layers of dot-gradients rather than hundreds of DOM nodes or a
   bitmap — cheap to composite on a low-powered kiosk stick, and it scales to 4K without
   ever looking like resized pixels. */
.stars-far,.stars-near{position:absolute; inset:0;}
.stars-far{
  opacity:.5;
  background-image:
    radial-gradient(1.6px 1.6px at 18% 22%, #dff4ff 0%, transparent 100%),
    radial-gradient(1.4px 1.4px at 72% 12%, #ffffff 0%, transparent 100%),
    radial-gradient(1.2px 1.2px at 45% 66%, #cfeaff 0%, transparent 100%),
    radial-gradient(1.6px 1.6px at 88% 78%, #ffffff 0%, transparent 100%),
    radial-gradient(1.2px 1.2px at 8% 88%, #bfe6ff 0%, transparent 100%);
  background-size:340px 340px;
}
.stars-near{
  opacity:.75;
  background-image:
    radial-gradient(2.6px 2.6px at 30% 34%, #ffffff 0%, transparent 100%),
    radial-gradient(2.2px 2.2px at 82% 58%, #ffe9a8 0%, transparent 100%),
    radial-gradient(2.4px 2.4px at 58% 86%, #d8f4ff 0%, transparent 100%);
  background-size:560px 560px;
  animation:twinkle 7s ease-in-out infinite alternate;
}

.spot{position:absolute; border-radius:50%; filter:blur(calc(60 * var(--u))); opacity:.42; mix-blend-mode:screen;}
.spot-a{width:72%; aspect-ratio:1; left:-20%; top:4%;
  background:radial-gradient(circle, var(--cyan), transparent 66%); animation:drift-a 46s ease-in-out infinite alternate;}
.spot-b{width:66%; aspect-ratio:1; right:-22%; top:46%;
  background:radial-gradient(circle, #7b4bff, transparent 66%); animation:drift-b 58s ease-in-out infinite alternate;}
.floaties{position:absolute; inset:0;}
.floaties i{position:absolute; bottom:-6vh; border-radius:50%; background:var(--cyan-soft); opacity:0; animation:rise linear infinite;}

/* aspect-ratio is Chrome 88+. An engine old enough to miss it computes no height for the
   ambient glows, so give them an explicit one — scoped, so modern engines keep the ratio
   (a specified width AND height would beat aspect-ratio). */
@supports not (aspect-ratio:1){
  .spot-a{ height:calc(778 * var(--u)); }
  .spot-b{ height:calc(713 * var(--u)); }
}

@keyframes drift-a{to{transform:translate(10%,8%) scale(1.14);}}
@keyframes drift-b{to{transform:translate(-8%,-10%) scale(1.12);}}
@keyframes twinkle{0%{opacity:.45;} 100%{opacity:.85;}}
@keyframes rise{0%{transform:translateY(0) translateX(0) scale(.6); opacity:0;} 12%{opacity:.7;} 88%{opacity:.6;}
  100%{transform:translateY(-108vh) translateX(var(--drift,20px)) scale(1); opacity:0;}}
@keyframes shift{0%{transform:translate(0,0);} 50%{transform:translate(-3px,4px);} 100%{transform:translate(3px,-3px);}}

/* ---------- grid placement --------------------------------------------- */
.masthead{grid-area:head;}
.hook{grid-area:hook;}
.hero{grid-area:hero;}
.scoreboard{grid-area:score;}
.thermo-figure{grid-area:thermo;}
.countdown{grid-area:count;}
.qr-card{grid-area:qrcard;}
.charity{grid-area:charity;}

/* ---------- masthead ---------------------------------------------------- */
.masthead{display:flex; justify-content:space-between; align-items:flex-start;}
.cc-logo{height:calc(96 * var(--u)); width:auto; display:block;
  max-height:10vh; max-width:52vw;      /* hard guard: never render at native pixel size */
  filter:drop-shadow(0 calc(5*var(--u)) calc(16*var(--u)) rgba(0,0,0,.5));}
.mast-right{display:flex; flex-direction:column; align-items:flex-end; text-align:right;}
.rsl-logo{height:calc(86 * var(--u)); width:auto; display:block;
  max-height:11vh; max-width:34vw;
  filter:drop-shadow(0 calc(5*var(--u)) calc(16*var(--u)) rgba(0,0,0,.5));}
.freshness{font-size:calc(19 * var(--u)); color:var(--text-dim); font-variant-numeric:tabular-nums;
  margin-top:calc(8 * var(--u)); white-space:nowrap;}
/* an honestly-stale figure says so rather than pretending to be live */
.freshness[data-stale="1"]{color:var(--gold-warm);}

/* ---------- hook -------------------------------------------------------- */
.hook{text-align:center; align-self:center;}
.eyebrow{font-family:var(--display); font-weight:700; color:var(--cyan);
  font-size:calc(25 * var(--u)); letter-spacing:.13em; text-transform:uppercase; line-height:1;
  margin-bottom:calc(13 * var(--u));
  text-shadow:0 0 calc(8*var(--u)) rgba(92,225,255,.85), 0 0 calc(24*var(--u)) rgba(92,225,255,.45);}
.eb-star{color:var(--gold); text-shadow:0 0 calc(10*var(--u)) rgba(255,212,71,.9);}
.banner{display:none;}
.headline{font-family:var(--display); font-weight:800; line-height:.96; text-wrap:balance;
  font-size:calc(82 * var(--u)); letter-spacing:-.02em; color:var(--text);}
.headline em{font-style:normal; color:var(--gold); display:inline-block;
  text-shadow:0 0 calc(14*var(--u)) rgba(255,212,71,.55);}
.headline-goal{color:var(--gold); text-shadow:0 0 calc(20*var(--u)) rgba(255,212,71,.5);}
.headline-ended{color:var(--cyan); text-shadow:0 0 calc(20*var(--u)) rgba(92,225,255,.45);}
.subhead{font-weight:500; color:var(--text-soft); margin-top:.42em; text-wrap:balance;
  font-size:calc(30 * var(--u)); line-height:1.24; max-width:38ch; margin-inline:auto;}
.subhead strong{color:var(--text); font-weight:700;}

/* ---------- Scotty (cut out, standing on the neon floor) ---------------- */
/* The height is set in --u with a vh guard rather than left to max-height:100%: this
   sits in an `auto` grid row, so a percentage height has nothing definite to resolve
   against and the image would fall back to its 1400px native width. */
.hero{position:relative; display:flex; align-items:flex-end; justify-content:center;
  height:calc(556 * var(--u)); max-height:48vh; min-height:0;}
.hero-img{position:relative; z-index:1; height:100%; width:auto;
  max-height:48vh; max-width:100%; display:block;
  filter:drop-shadow(0 calc(10*var(--u)) calc(30*var(--u)) rgba(0,0,0,.55));
  /* The source frame ends at his thighs, so the cut-out has a hard horizontal bottom
     edge. Fading it out makes him read as standing in the light rather than cropped.
     -webkit- first: the unprefixed property is Chrome 120+, the prefix goes back years.
     An engine with neither just shows the straight edge — cosmetic, not broken. */
  -webkit-mask-image:linear-gradient(180deg, #000 76%, rgba(0,0,0,.35) 92%, transparent 100%);
  mask-image:linear-gradient(180deg, #000 76%, rgba(0,0,0,.35) 92%, transparent 100%);}
/* stage light behind him + a pool of light at his feet, so a cut-out doesn't float */
.hero-glow{position:absolute; z-index:0; left:-8%; right:-8%; bottom:calc(26 * var(--u)); height:88%;
  background:radial-gradient(ellipse 58% 52% at 50% 62%,
    rgba(92,225,255,.30), rgba(123,75,255,.16) 46%, transparent 74%);
  pointer-events:none;}
.hero-floor{position:absolute; z-index:0; left:2%; right:2%; bottom:0; height:calc(74 * var(--u));
  background:radial-gradient(ellipse 46% 50% at 50% 84%,
    rgba(92,225,255,.5), rgba(92,225,255,.14) 52%, transparent 78%);
  pointer-events:none;}

/* ---------- scoreboard -------------------------------------------------- */
.scoreboard{text-align:left; align-self:start;}
.raised-label{font-weight:500; letter-spacing:.14em; text-transform:uppercase; color:var(--text-dim); font-size:calc(26 * var(--u));}
.raised{line-height:1; margin:.02em 0 .14em;}
.raised-amt{font-family:var(--data); font-weight:700; font-size:calc(104 * var(--u)); letter-spacing:-.03em;
  font-variant-numeric:tabular-nums; color:var(--text); text-shadow:0 0 calc(34*var(--u)) rgba(92,225,255,.4);}
.goalrow{display:flex; align-items:center; flex-wrap:wrap; gap:calc(16 * var(--u));}
.of-goal{color:var(--text-soft); font-weight:500; font-size:calc(39 * var(--u));}
.of-goal b{color:var(--cyan); font-weight:700;}
.pct-pill{font-family:var(--display); font-weight:700; color:var(--ink);
  background:linear-gradient(120deg, var(--gold), var(--gold-warm)); padding:.16em .58em; border-radius:999px;
  font-size:calc(42 * var(--u)); font-variant-numeric:tabular-nums;
  box-shadow:0 calc(6*var(--u)) calc(22*var(--u)) rgba(255,212,71,.36);}

/* ---------- thermometer ------------------------------------------------- */
.thermo-figure{position:relative; isolation:isolate; min-width:0; min-height:0;
  display:flex; align-items:center; justify-content:center;}
.thermo-figure::before{content:""; position:absolute; z-index:-1; inset:-5% -4% -4% -8%;
  background:radial-gradient(54% 56% at 74% 58%,
    rgba(92,225,255,.30), rgba(123,75,255,.15) 42%, rgba(255,212,71,.07) 60%, transparent 78%);
  pointer-events:none;}
.thermo{width:100%; height:100%; display:block; overflow:visible;}
/* NOTE: the mercury rect's y/height are set by app.js as ATTRIBUTES and tweened in JS.
   Do NOT move them into CSS — SVG geometry properties are Chrome 80+ and one of the two
   Ballina towers is older, which would leave this tube stuck rendering full. */
.gloss{mix-blend-mode:screen;}
.bulb-glass{opacity:.5;}
.ticks line{stroke:#a9ecff; stroke-opacity:.45; stroke-width:2.4; stroke-linecap:round;}
.ticks text{font-family:var(--data); font-weight:600; font-size:29px; fill:var(--text-soft);
  dominant-baseline:central; text-anchor:end;}
.goal-mark line{stroke:var(--gold); stroke-opacity:.9; stroke-width:2.6; stroke-dasharray:6 6;}
.goal-mark text{font-family:var(--data); font-weight:700; font-size:31px; fill:var(--gold);
  dominant-baseline:central; text-anchor:end;}
.sparkles circle{fill:#fff; opacity:0; animation:bubble 3.6s ease-in-out infinite;
  transform-box:fill-box; transform-origin:center;}
.sparkles circle:nth-child(2n){animation-delay:.6s;} .sparkles circle:nth-child(3n){animation-delay:1.3s;}
@keyframes bubble{0%{opacity:0; transform:translateY(6px) scale(.6);} 50%{opacity:.7;} 100%{opacity:0; transform:translateY(-12px) scale(1);}}
/* star topper: dim outline until the goal lands */
.topper-star{fill:#101a45; stroke:var(--gold); stroke-opacity:.55; stroke-width:3; opacity:.4; transition:opacity .5s ease;}

/* ---------- countdown --------------------------------------------------- */
.countdown{width:100%; text-align:center; align-self:center;
  background:linear-gradient(180deg, rgba(92,225,255,.07), rgba(92,225,255,.02));
  border:1px solid rgba(92,225,255,.20); border-radius:var(--r-md);
  padding:calc(14 * var(--u)) calc(20 * var(--u));}
.cd-label{font-weight:500; letter-spacing:.12em; text-transform:uppercase; color:var(--text-dim);
  font-size:calc(23 * var(--u)); margin-bottom:.25em;}
.cd-clock{display:flex; align-items:baseline; justify-content:center; gap:calc(8 * var(--u));}
.cd-unit{display:flex; flex-direction:column; align-items:center; min-width:1.5em;}
.cd-num{font-family:var(--data); font-weight:700; color:var(--text); font-size:calc(52 * var(--u));
  line-height:1; letter-spacing:-.02em; font-variant-numeric:tabular-nums;}
.cd-cap{font-weight:500; letter-spacing:.1em; text-transform:uppercase; color:var(--cyan);
  font-size:calc(18 * var(--u)); margin-top:.35em;}
.cd-sep{font-family:var(--data); font-weight:700; color:var(--text-dim); font-size:calc(40 * var(--u));
  line-height:1; transform:translateY(-.12em);}
.cd-danced{display:none; font-weight:500; color:var(--text-soft); font-size:calc(30 * var(--u)); line-height:1.24;}
.cd-danced b{color:var(--cyan); font-weight:700;}

/* ---------- donate + charity ------------------------------------------- */
.qr-card{background:var(--paper); color:var(--ink); border-radius:var(--r-lg); padding:calc(17 * var(--u));
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:calc(12 * var(--u));
  box-shadow:0 calc(18*var(--u)) calc(44*var(--u)) rgba(0,0,0,.45); align-self:stretch;}
.qr-holder{background:#fff; border-radius:var(--r-sm); padding:calc(14 * var(--u)); line-height:0;}
.qr-holder img{display:block; width:calc(184 * var(--u)); height:auto;}
.qr-cta{text-align:center;}
.qr-lead{font-family:var(--display); font-weight:800; color:var(--ink); font-size:calc(44 * var(--u));
  line-height:1; letter-spacing:-.01em;}
.qr-sub{color:var(--ink-soft); font-weight:500; margin-top:.25em; font-size:calc(23 * var(--u));}
/* =========================================================================
   EMBED MODE (?embed=1) — the board in an iframe on ballinarsl.com.au.
   Worth stating plainly: everything in this block may use modern CSS freely. The
   pre-Chrome-77 constraint that shapes the rest of this sheet is about the two Ballina
   towers; an embed is read by a website visitor on a current browser. And because these
   rules are scoped to html[data-embed="1"], an engine that drops them just falls back to
   the kiosk sizing — so the towers can't be hurt by anything in here.
   ========================================================================= */

/* NO absolute font-size floors here, deliberately — I tried and reverted them.
   Pinning minimums with max() blows the type past what the frame can hold: at 380x214 the
   grid's flexible rows collapsed and the headline, total and donate card all landed on top
   of each other. And it did NOT show up as overflow, because a clipped `overflow:hidden`
   grid doesn't grow scrollHeight — the check that caught it compares every element's rect
   against the stage box and against its siblings (scripts/check-layout.py).
   Pure proportional scaling fits at ANY size by construction. Legibility is therefore the
   embed's job, not the stylesheet's: the WordPress plugin keeps the frame big enough
   (portrait aspect on narrow screens, min-height floor) rather than fighting the board. */

/* the whole donate card becomes one big tap target. The anchor is injected by app.js,
   so the kiosk markup stays inert — see armEmbedLink(). */
html[data-embed="1"] .qr-card{position:relative; cursor:pointer;}
.qr-hit{position:absolute; inset:0; border-radius:inherit; z-index:2;
  -webkit-tap-highlight-color:rgba(92,225,255,.25);}
.qr-hit:focus-visible{outline:4px solid var(--cyan); outline-offset:3px;}
html[data-embed="1"] .qr-card:hover{box-shadow:0 calc(18*var(--u)) calc(50*var(--u)) rgba(0,0,0,.5),
  0 0 0 calc(3*var(--u)) rgba(92,225,255,.55);}

.charity{display:flex; flex-direction:column; justify-content:center; gap:calc(16 * var(--u));}
.charity-line{color:var(--text); font-weight:500; line-height:1.22; font-size:calc(32 * var(--u));}
.charity-line strong{color:var(--gold); font-weight:700;}
.event-line{color:var(--text-soft); font-weight:500; line-height:1.22; font-size:calc(26 * var(--u));
  padding-top:calc(12 * var(--u)); border-top:1px solid rgba(92,225,255,.16);}
.event-line b{color:var(--cyan); font-weight:700;}

/* =========================================================================
   LANDSCAPE (16:9) — reference height 1080, three-column composition
   ========================================================================= */
@media (orientation:landscape){
  :root{ --ref:1080; }
  .stage{
    max-width:none;
    padding:calc(26 * var(--u)) calc(40 * var(--u));
    column-gap:calc(34 * var(--u));
    grid-template-columns:1fr 1.15fr 1fr;
    grid-template-rows:auto auto minmax(0,1fr) minmax(0,1fr) minmax(0,1fr) auto;
    grid-template-areas:
      "head    head    head"
      "hook    hook    hook"
      "hero    score   thermo"
      "hero    count   thermo"
      "hero    qrcard  thermo"
      "charity charity charity";
  }
  /* hero spans the three centre rows, so its height comes from the grid, not --u */
  .hero{height:100%; max-height:none; align-items:flex-end;}
  .hero-img{max-height:100%;}
  .scoreboard{align-self:end; text-align:center; display:flex; flex-direction:column; align-items:center; gap:calc(6*var(--u));}
  .goalrow{justify-content:center;}
  .countdown{align-self:center; min-height:0;}
  .qr-card{align-self:center; flex:none; min-height:0; flex-direction:row; align-items:center;
    gap:calc(20*var(--u)); padding:calc(16*var(--u));}
  .qr-cta{text-align:left;}
  .charity{flex-direction:row; gap:calc(30*var(--u)); justify-content:center; align-items:center; text-align:center;
    padding-top:calc(10*var(--u)); border-top:1px solid rgba(92,225,255,.16);}
  .event-line{border:0; padding:0;}

  /* landscape sizing (1080-tall reference) */
  .cc-logo{height:calc(66*var(--u));} .rsl-logo{height:calc(72*var(--u));}
  .eyebrow{font-size:calc(22*var(--u)); margin-bottom:calc(12*var(--u));}
  .headline{font-size:calc(72*var(--u));}
  .subhead{font-size:calc(26*var(--u)); max-width:46ch;}
  .raised-amt{font-size:calc(98*var(--u));}
  .raised-label{font-size:calc(23*var(--u));}
  .of-goal{font-size:calc(33*var(--u));} .pct-pill{font-size:calc(36*var(--u));}
  .cd-num{font-size:calc(48*var(--u));} .cd-sep{font-size:calc(38*var(--u));} .cd-label{font-size:calc(21*var(--u));}
  .cd-danced{font-size:calc(25*var(--u));}
  .qr-holder img{width:calc(150*var(--u));}
  .qr-lead{font-size:calc(36*var(--u));} .qr-sub{font-size:calc(20*var(--u));}
  .charity-line{font-size:calc(27*var(--u)); max-width:48ch;}
  .event-line{font-size:calc(24*var(--u)); max-width:40ch;}
}

/* =========================================================================
   STATE MACHINE
   data-state: loading | default | goal | ended      (where the money is up to)
   data-phase: before  | danced  | wrapped           (where the calendar is up to)
   ========================================================================= */
body[data-state="loading"] .banner-default,
body[data-state="default"] .banner-default{display:block;}
body[data-state="goal"]    .banner-goal{display:block;}
body[data-state="ended"]   .banner-ended{display:block;}

/* countdown clock only before the show; the "he danced" note only after it */
body[data-phase="danced"] .cd-live{display:none;}
body[data-phase="danced"] .cd-danced{display:block;}
body[data-state="ended"] .countdown,
body[data-phase="wrapped"] .countdown{display:none;}

body[data-state="goal"] .topper-star{fill:var(--gold); stroke-opacity:1; opacity:1;
  animation:star-pulse 1.8s ease-in-out infinite;}
body[data-state="goal"] .spot{opacity:.66;}
body[data-state="goal"] .raised-amt{color:var(--gold); text-shadow:0 0 calc(44*var(--u)) rgba(255,212,71,.55);}
body[data-state="ended"] .raised-amt{color:var(--cyan);}
@keyframes star-pulse{
  0%,100%{filter:drop-shadow(0 0 6px rgba(255,212,71,.7));}
  50%{filter:drop-shadow(0 0 26px rgba(255,212,71,1));}
}

/* ---------- confetti ---------------------------------------------------- */
.confetti{position:absolute; inset:0; z-index:5; pointer-events:none; overflow:hidden; display:none;}
body[data-state="goal"] .confetti{display:block;}
.confetti i{position:absolute; top:-8vh; width:14px; height:20px; border-radius:3px; opacity:.95;
  animation:fall linear infinite;}
@keyframes fall{0%{transform:translateY(0) rotate(0); opacity:0;} 8%{opacity:1;}
  100%{transform:translateY(112vh) rotate(720deg); opacity:.9;}}

/* =========================================================================
   ACCESSIBILITY
   ========================================================================= */
:focus-visible{outline:4px solid var(--cyan); outline-offset:4px; border-radius:6px;}
/* Respect reduce-motion — EXCEPT when the kiosk forces it on (?motion=on), because a
   kiosk OS often has reduce-motion enabled system-wide and would kill every animation. */
@media (prefers-reduced-motion:reduce){
  html:not([data-motion="on"]) *,
  html:not([data-motion="on"]) *::before,
  html:not([data-motion="on"]) *::after{animation:none !important; transition:none !important;}
  html:not([data-motion="on"]) .sparkles circle{opacity:.5;}
  html:not([data-motion="on"]) body[data-state="goal"] .topper-star{opacity:1;}
}
