/* ==========================================================================
   Bar Hunt — the play surface.

   Hand-written CSS. No framework, no preprocessor, no build step, no CDN and
   no light theme. This is the phone-in-a-dark-bar surface: near-black ground,
   one electric cyan accent, red for the klaxon, gold for money. Everything is
   loud, and every control clears the 56px tap floor.

   The token layer (section 1) is lifted from the frozen app's public/app.css.
   Those metrics were tuned on a real crawl and are right for THIS surface —
   `--tap: 56px` because a thumb in a dark bar misses a 44px target, and
   `clamp(2.5rem, 11vw, 4.5rem)` display type because the phone is at arm's
   length across a table. The editor (a laptop, indoors, sober) does not share
   them and must not inherit them.

   EVERY RULE IN THIS FILE IS SCOPED TO `#app`, and that is load-bearing:
   `stylesheet_link_tag :app` in the layout includes EVERY css file under
   `app/assets` (Propshaft::Helper), so this file is served on every
   page in the application whether or not it is the play page. An unscoped
   `html, body { overflow: hidden }` here would silently break scrolling in the
   editor. The two rules that genuinely need the document element use
   `:has(#app[data-play-boot])` and hard-code their colours, because a custom
   property declared on `#app` does not travel upwards.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens — declared on the play root, not on :root
   -------------------------------------------------------------------------- */

#app[data-play-boot] {
  color-scheme: dark;

  /* Ground + surfaces */
  --ground:       #08080b;
  --ground-lift:  #101017;
  --surface:      #17171f;
  --surface-hi:   #21212c;
  --line:         #2e2e3d;
  --line-hot:     #45455c;

  /* Ink */
  --ink:          #f6f6fa;
  --ink-dim:      #a8a8bd;
  --ink-faint:    #6c6c85;

  /* Signal colours */
  --accent:       #00e5ff;
  --accent-deep:  #00a6bd;
  --accent-ink:   #00181d;
  --danger:       #ff2b45;
  --danger-deep:  #b3001a;
  --danger-ink:   #ffffff;
  --gold:         #ffc844;
  --gold-deep:    #c99400;
  --gold-ink:     #241900;
  --warn:         #ff7a1a;
  --good:         #35e07a;

  /* Glows — used sparingly, they are the whole look */
  --glow-accent:  0 0 28px rgba(0, 229, 255, .45);
  --glow-danger:  0 0 32px rgba(255, 43, 69, .5);
  --glow-gold:    0 0 26px rgba(255, 200, 68, .4);

  /* Type */
  --font-display: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-body:    system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono:    ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --fs-timer:   clamp(4rem,    22vw,  9rem);
  --fs-slam:    clamp(3rem,    15vw,  6rem);
  --fs-display: clamp(2.5rem,  11vw,  4.5rem);
  --fs-title:   clamp(1.75rem,  7.5vw, 2.75rem);
  --fs-lead:    clamp(1.25rem,  5vw,   1.75rem);
  --fs-body:    clamp(1.0625rem, 4.2vw, 1.25rem);
  --fs-small:   clamp(0.8125rem, 3.2vw, 0.9375rem);
  --fs-micro:   clamp(0.6875rem, 2.8vw, 0.8125rem);

  /* Metrics — --tap is the floor for every interactive control */
  --tap:      56px;
  --gap:      12px;
  --pad:      20px;
  --radius:   14px;
  --radius-lg: 22px;
  --statusbar-h: 52px;

  /* Motion */
  --dur-instant: 90ms;
  --dur-fast:    160ms;
  --dur-screen:  200ms;   /* screens.js mirrors this as SCREEN_FADE_MS */
  --dur-slam:    320ms;
  --ease-out:    cubic-bezier(.22, .61, .36, 1);
  --ease-slam:   cubic-bezier(.16, 1.4, .4, 1);

  /* Layers */
  --z-screen:   1;
  --z-status:   50;
  --z-heckle:   90;   /* over the screens and the strip, under the takeover */
  --z-takeover: 100;

  display: flex;
  flex-direction: column;
  height: 100dvh;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.35;
}

/* --------------------------------------------------------------------------
   2. Reset + mobile gesture hardening
   -------------------------------------------------------------------------- */

#app[data-play-boot] *,
#app[data-play-boot] *::before,
#app[data-play-boot] *::after { box-sizing: border-box; }

#app[data-play-boot] [hidden] { display: none !important; }

/* The only two rules that reach past #app. `:has()` keeps them off every other
   page in the app; the colours are literals because a custom property declared
   on #app does not travel up to <body>. */
html:has(#app[data-play-boot]),
body:has(#app[data-play-boot]) {
  height: 100dvh;              /* the dynamic viewport unit only — the URL bar lies */
  margin: 0;
  padding: 0;
  overscroll-behavior: none;   /* kills pull-to-refresh mid-round */
  touch-action: manipulation;  /* kills double-tap zoom */
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-text-size-adjust: 100%;
  overflow: hidden;
}

body:has(#app[data-play-boot]) {
  background-color: #08080b;
  background-image:
    radial-gradient(115% 70% at 50% -10%, #1a1a27 0%, rgba(26,26,39,0) 62%),
    radial-gradient(90% 55% at 50% 108%, rgba(0,229,255,.10) 0%, rgba(0,229,255,0) 60%);
  color: #f6f6fa;
}

#app h1, #app h2, #app h3, #app p, #app ul, #app ol, #app figure { margin: 0; }
#app ul, #app ol { padding: 0; list-style: none; }
#app img, #app svg, #app canvas { display: block; max-width: 100%; }

#app .sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

#app :focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

/* --------------------------------------------------------------------------
   3. Status strip — persistent, never hidden by showScreen()
   -------------------------------------------------------------------------- */

#app .statusbar {
  position: relative;
  z-index: var(--z-status);
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--statusbar-h);
  padding: 6px max(12px, env(safe-area-inset-right)) 6px max(12px, env(safe-area-inset-left));
  padding-top: calc(6px + env(safe-area-inset-top));
  background: linear-gradient(to bottom, #0d0d14, #0a0a10);
  border-bottom: 1px solid var(--line);
}

#app .statusbar__conn {
  flex: 0 0 auto;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--ink-faint);
  box-shadow: 0 0 0 3px rgba(255,255,255,.04);
  transition: background-color var(--dur-fast) linear, box-shadow var(--dur-fast) linear;
}
#app .statusbar__conn[data-state="online"]     { background: var(--good);   box-shadow: 0 0 12px var(--good); }
#app .statusbar__conn[data-state="connecting"] { background: var(--warn);   box-shadow: 0 0 12px var(--warn); animation: barhunt-pulse-dot 1.1s ease-in-out infinite; }
#app .statusbar__conn[data-state="offline"]    { background: var(--danger); box-shadow: 0 0 12px var(--danger); }

@keyframes barhunt-pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }

#app .statusbar__title {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-display);
  font-size: var(--fs-small);
  font-weight: 800;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

#app .statusbar__score {
  flex: 0 0 auto;
  padding: 5px 9px;
  border: 1px solid color-mix(in srgb, var(--gold) 55%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--gold) 12%, transparent);
  color: var(--gold);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

#app .statusbar__badge,
#app .statusbar__room {
  flex: 0 0 auto;
  padding: 5px 9px;
  border: 1px solid var(--line-hot);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-dim);
  background: var(--ground-lift);
  white-space: nowrap;
}
#app .statusbar__badge[data-role="mc"]        { color: var(--accent-ink); background: var(--accent);  border-color: var(--accent); }
#app .statusbar__badge[data-role="hero"]      { color: var(--gold-ink);   background: var(--gold);    border-color: var(--gold); }
#app .statusbar__badge[data-role="spectator"] { color: var(--ink);        background: var(--surface); border-color: var(--line-hot); }
#app .statusbar__room { color: var(--gold); letter-spacing: .22em; }

/* The lost-action banner. It does not go away on its own — see play.js. */
#app .failure {
  flex: 0 0 auto;
  margin: 0;
  padding: 12px var(--pad);
  background: var(--danger-deep);
  color: var(--danger-ink);
  font-size: var(--fs-small);
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   4. Screens — exactly one visible, cross-faded
   -------------------------------------------------------------------------- */

#app .screens {
  position: relative;
  z-index: var(--z-screen);
  flex: 1 1 auto;
  min-height: 0;
}

#app .screen {
  position: absolute;
  inset: 0;
  display: none;              /* the guarantee: nothing shows until showScreen() */
  flex-direction: column;
  gap: var(--gap);
  padding: var(--pad);
  padding-bottom: max(var(--pad), env(safe-area-inset-bottom));
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  opacity: 0;
  transition: opacity var(--dur-screen) var(--ease-out);
}

#app .screen.is-entering { display: flex; opacity: 0; }
#app .screen.is-active   { display: flex; opacity: 1; }
#app .screen.is-leaving  { display: flex; opacity: 0; pointer-events: none; }

#app .screen--alarm { background: radial-gradient(120% 70% at 50% 0%, rgba(255,43,69,.22), transparent 70%); }

/* --------------------------------------------------------------------------
   5. Typography helpers
   -------------------------------------------------------------------------- */

#app .display,
#app .title {
  font-family: var(--font-display);
  font-weight: 900;
  line-height: .92;
  letter-spacing: -.03em;
  text-transform: uppercase;
  text-wrap: balance;
}
#app .display { font-size: var(--fs-display); }
#app .title   { font-size: var(--fs-title); }

#app .eyebrow {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-family: var(--font-display);
  font-size: var(--fs-small);
  font-weight: 800;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

#app .lead  { font-size: var(--fs-lead); color: var(--ink-dim); text-wrap: pretty; }
#app .muted { color: var(--ink-dim); font-size: var(--fs-small); }
#app .micro { font-size: var(--fs-micro); word-break: break-all; }
#app .dim   { color: var(--ink-faint); }
#app .mono  { font-family: var(--font-mono); font-variant-numeric: tabular-nums; letter-spacing: normal; }

#app .score {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--gold);
  text-shadow: var(--glow-gold);
}
#app .score--huge { font-size: var(--fs-timer); line-height: 1; }

#app .glow-accent { color: var(--accent); text-shadow: var(--glow-accent); }
#app .glow-danger { color: var(--danger); text-shadow: var(--glow-danger); }
#app .glow-gold   { color: var(--gold);   text-shadow: var(--glow-gold); }

#app .prompt {
  font-family: var(--font-display);
  font-size: var(--fs-title);
  font-weight: 800;
  line-height: 1.08;
  text-wrap: pretty;
}

#app .flag {
  padding: 10px 14px;
  border-left: 4px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--ink);
  font-size: var(--fs-small);
}

/* --------------------------------------------------------------------------
   6. Controls — every one of them is at least --tap tall
   -------------------------------------------------------------------------- */

#app button, #app input { font-family: inherit; touch-action: manipulation; -webkit-tap-highlight-color: transparent; }

#app .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: var(--tap);
  padding: 14px 20px;
  border: 2px solid var(--line-hot);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-display);
  font-size: var(--fs-body);
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
  text-align: center;
  cursor: pointer;
  transition:
    transform var(--dur-instant) var(--ease-out),
    background-color var(--dur-fast) linear,
    border-color var(--dur-fast) linear,
    box-shadow var(--dur-fast) linear;
}
#app .btn:active { transform: scale(.97); background: var(--surface-hi); }

#app .btn--block { width: 100%; }
#app .btn--huge  { min-height: 76px; font-size: var(--fs-lead); border-radius: var(--radius-lg); }

#app .btn--primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); box-shadow: var(--glow-accent); }
#app .btn--primary:active { background: var(--accent-deep); border-color: var(--accent-deep); }
#app .btn--gold    { background: var(--gold); border-color: var(--gold); color: var(--gold-ink); box-shadow: var(--glow-gold); }
#app .btn--ghost   { background: transparent; border-color: var(--line-hot); color: var(--ink-dim); box-shadow: none; }

#app input[type="text"] {
  min-height: var(--tap);
  padding: 12px 14px;
  border: 2px solid var(--line-hot);
  border-radius: var(--radius);
  background: var(--ground-lift);
  color: var(--ink);
  font-size: var(--fs-body);
  -webkit-user-select: text;
  user-select: text;
}

#app .stack   { display: flex; flex-direction: column; gap: var(--gap); }
#app .spacer  { flex: 1 1 auto; min-height: var(--gap); }

#app .card {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  padding: var(--pad);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

/* The MC's action stack. T11 (heckles) must never draw over this — see the
   PROTECTED selector list in the notepad. */
#app [data-mc-slot] { display: flex; flex-direction: column; gap: var(--gap); }
#app [data-mc-slot]:empty { display: none; }

/* --------------------------------------------------------------------------
   7. Roster
   --------------------------------------------------------------------------
   Nothing to style: the roster is a count rendered into `#intro-roster-count`
   by the eyebrow rules above. The row/name/role styles a lobby list would need
   were deleted with the list itself — M1's guardrail is a count, nothing more.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   8. Map + route overlay
   -------------------------------------------------------------------------- */

#app .map {
  position: relative;
  /* Matches MapRenderer::WIDTH x HEIGHT exactly, so the overlay's viewBox and
     the artwork line up 1:1 at any size. `flex: none` is load-bearing: a screen
     is a flex column, and a shrinking flex item would lose height while keeping
     width, which letterboxes the artwork and pulls the overlay out of register
     with the pins underneath it. */
  flex: none;
  aspect-ratio: 2000 / 1400;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
#app .map svg { width: 100%; height: 100%; }

#app .map__overlay { position: absolute; inset: 0; pointer-events: none; }

#app .route__leg {
  stroke: var(--accent);
  stroke-width: 12;
  stroke-linecap: round;
  stroke-dasharray: 34 26;
  filter: drop-shadow(0 0 16px rgba(0,229,255,.8));
  animation: barhunt-route-march 1.4s linear infinite;
}
@keyframes barhunt-route-march { to { stroke-dashoffset: -60; } }

#app .route__here {
  fill: none;
  stroke: var(--accent);
  stroke-width: 8;
  opacity: .9;
  transform-box: fill-box;
  transform-origin: center;
  animation: barhunt-route-pulse 1.8s cubic-bezier(.22, .61, .36, 1) infinite;
}
@keyframes barhunt-route-pulse {
  0%   { transform: scale(.7); opacity: .95; }
  100% { transform: scale(1.35); opacity: 0; }
}

/* --------------------------------------------------------------------------
   9. Clock
   -------------------------------------------------------------------------- */

#app .clockbox { display: flex; flex-direction: column; align-items: center; gap: 4px; }

#app .clock {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-timer);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.03em;
  color: var(--ink);
  text-align: center;
}
#app .clock--small { font-size: var(--fs-title); color: var(--ink-dim); }
#app .clock--over  { color: var(--danger); text-shadow: var(--glow-danger); }

body[data-timer="warn"]     #app .clock { color: var(--warn); text-shadow: 0 0 24px rgba(255,122,26,.5); }
body[data-timer="critical"] #app .clock { color: var(--danger); text-shadow: var(--glow-danger); }
body[data-timer="over"]     #app .clock { color: var(--danger); text-shadow: var(--glow-danger); }

#app .wakelock {
  min-height: 44px;
  padding: 8px 14px;
  font-size: var(--fs-small);
  text-transform: none;
  letter-spacing: 0;
}

/* --------------------------------------------------------------------------
   10. Challenge
   -------------------------------------------------------------------------- */

#app .worth { display: flex; align-items: baseline; gap: 10px; }
#app .worth .score { font-size: var(--fs-title); }

/* --------------------------------------------------------------------------
   11. Finale
   -------------------------------------------------------------------------- */

#app .confetti { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2; }

#app .finale__total { display: flex; flex-direction: column; align-items: center; gap: 2px; }
#app .finale__total .eyebrow { letter-spacing: .2em; }

#app .stats { display: flex; gap: var(--gap); flex-wrap: wrap; }
#app .stat {
  flex: 1 1 40%;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--ground-lift);
}
#app .stat .mono { font-size: var(--fs-lead); color: var(--ink); }

#app .recap { display: flex; flex-direction: column; gap: 8px; }
#app .recap__row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}
#app .recap__num {
  flex: 0 0 auto;
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--ground-lift);
  color: var(--accent);
  font-weight: 700;
}
#app .recap__name { flex: 1 1 auto; min-width: 0; font-weight: 700; }
#app .recap__pts  { flex: 0 0 auto; }
#app .recap__chip {
  flex: 0 0 auto;
  padding: 3px 8px;
  border: 1px solid var(--line-hot);
  border-radius: 999px;
  color: var(--ink-faint);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: .1em;
}

#app .occasion { align-items: center; text-align: center; cursor: pointer; position: relative; z-index: 3; }

/* --------------------------------------------------------------------------
   12. QR
   -------------------------------------------------------------------------- */

#app .qr { align-items: center; text-align: center; }
#app .qr__canvas { border-radius: 8px; background: #fff; }
#app .qr__label { font-weight: 700; }

/* --------------------------------------------------------------------------
   13. Takeover card — the one component that covers everything
   -------------------------------------------------------------------------- */

#app .takeover {
  position: fixed;
  inset: 0;
  z-index: var(--z-takeover);
  display: none;
  align-items: center;
  justify-content: center;
  padding: max(var(--pad), env(safe-area-inset-top)) var(--pad)
           max(var(--pad), env(safe-area-inset-bottom));
  background: var(--to-ground, var(--ground));
  color: var(--to-ink, var(--ink));
  text-align: center;
}
#app .takeover.is-open { display: flex; animation: barhunt-takeover-in var(--dur-slam) var(--ease-slam) both; }

#app .takeover[data-variant="timeup"] {
  --to-ground: #1a0500;
  --to-ink: #fff;
  background-image: radial-gradient(90% 60% at 50% 40%, var(--warn) 0%, #7a1a00 65%, #1a0500 100%);
}
#app .takeover[data-variant="correct"] {
  --to-ground: #001d22;
  background-image: radial-gradient(90% 60% at 50% 40%, rgba(0,229,255,.35) 0%, #001d22 68%);
}
#app .takeover[data-variant="wrong"] {
  --to-ground: var(--danger-deep);
  --to-ink: #fff;
  background-image: radial-gradient(90% 60% at 50% 40%, var(--danger) 0%, var(--danger-deep) 70%);
}

@keyframes barhunt-takeover-in {
  from { opacity: 0; transform: scale(1.12); }
  to   { opacity: 1; transform: scale(1); }
}

#app .takeover__card { display: flex; flex-direction: column; align-items: center; gap: 14px; max-width: 34rem; width: 100%; }

#app .takeover__eyebrow {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fs-small);
  font-weight: 800;
  letter-spacing: .3em;
  text-transform: uppercase;
  opacity: .75;
  min-height: 1em;
}
#app .takeover__title {
  font-family: var(--font-display);
  font-size: var(--fs-slam);
  font-weight: 900;
  line-height: .88;
  letter-spacing: -.035em;
  text-transform: uppercase;
  text-wrap: balance;
  text-shadow: 0 6px 0 rgba(0,0,0,.35);
}
#app .takeover__sub { font-size: var(--fs-lead); opacity: .9; }
#app .takeover__dismiss {
  font-size: var(--fs-micro);
  letter-spacing: .24em;
  text-transform: uppercase;
  opacity: .6;
}

/* --------------------------------------------------------------------------
   14. Heckles — the bar everybody throws from, and the layer every phone
       catches on.

   THE BAR IS A FLEX CHILD OF #app, NOT A FIXED OVERLAY. The frozen app pinned
   it to the bottom edge (public/app.css:1648) and then bought the space back
   with `body[data-heckles="true"] .screen { padding-bottom: ... }`. That trade
   is wrong here: the MC's action stack sits at the BOTTOM of every screen,
   pushed there by `.spacer`, so a bottom-pinned bar overlaps the one control a
   night cannot afford to lose. In flow, `#screens` (flex: 1 1 auto) simply
   shrinks by the bar's height, and "a heckle never covers the MC's primary
   control" stops being something to remember and starts being something the
   layout cannot do.

   The LAYER is fixed and takes no taps. heckles.js spawns every burst above
   the protected controls and every keyframe travels UP, so the layer's job
   here is only to be a stacking context that cannot be interacted with.
   -------------------------------------------------------------------------- */

#app .hecklebar {
  position: relative;
  z-index: var(--z-status);
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px max(8px, env(safe-area-inset-left))
           calc(8px + env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-right));
  background: linear-gradient(to top, #0a0a10 60%, rgba(10, 10, 16, .86));
  border-top: 1px solid var(--line);
}

#app .hecklebar__row {
  display: flex;
  gap: 6px;
}

#app .hecklebar__btn {
  flex: 1 1 0;
  min-width: 0;
  min-height: var(--tap);
  padding: 6px 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-size: 28px;
  line-height: 1;
  transition: transform var(--dur-instant) var(--ease-out),
              opacity var(--dur-fast) linear;
}

#app .hecklebar__btn:active { transform: scale(.94); background: var(--surface-hi); }

/* One shared cooldown, because the rate limit is per participation and not per
   button. It is a courtesy: the server refuses the second throw whatever this
   phone believes, and says so with `retry_in_ms`. */
#app .hecklebar[data-cooling="true"] .hecklebar__btn {
  opacity: .4;
  pointer-events: none;
}

#app .hecklebar__note {
  margin: 0;
  min-height: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--gold);
  text-align: center;
}

#app .hecklelayer {
  position: fixed;
  inset: 0;
  z-index: var(--z-heckle);
  pointer-events: none;
  overflow: hidden;
}

/* A launch POINT, not a box. Every glyph is absolutely positioned on top of it
   so the pack can fly apart, which is why heckles.js places a cluster from a
   constant nominal size rather than measuring this, which is 0x0. */
#app .heckle {
  position: absolute;
  width: 0;
  height: 0;
  pointer-events: none;
}

#app .heckle__emoji {
  position: absolute;
  left: 0;
  top: 0;
  font-size: 40px;
  line-height: 1;
  transform-origin: 50% 50%;
  filter: drop-shadow(0 3px 10px rgba(0, 0, 0, .65));
  will-change: transform, opacity;
  opacity: 0;                   /* the animation owns this; fill:both covers t<0 */
}

#app .heckle__who {
  position: absolute;
  left: 0;
  top: 48px;
  transform: translateX(-50%);
  max-width: 40vw;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-shadow: 0 1px 6px #000, 0 0 14px #000;
  will-change: transform, opacity;
  opacity: 0;
}

/* prefers-reduced-motion: heckles.js positions the glyphs by hand and sets
   opacity inline, so nothing here may fight it. */
#app .heckle[data-still="true"] .heckle__emoji,
#app .heckle[data-still="true"] .heckle__who {
  transition: opacity var(--dur-screen) linear;
}

/* --------------------------------------------------------------------------
   15. Reduced motion — the cross-fade is already skipped in JS
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  #app *, #app *::before, #app *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}
