/* Festive Batface Halloween — components/bundle.css, copied by tools/halloween/make-tokens.mjs (do not edit; the Google Fonts @import is removed because the pages load the fonts) */
/* Festive Batface Halloween: component styles. Load after tokens.css.
   Every value is a token; the few literals left (0, 50%, 1, .96, transparent, currentColor) are geometry, not design values.
   Class names: .bf-<component>, .bf-<component>__<part>, .bf-<component>--<variant>; state from ARIA/attributes first. */

/* Metric-matched fallback so Fredoka swapping in does not move the layout (from the site's critical CSS). */
@font-face{font-family:"FredokaFallback";src:local("Arial Rounded MT Bold"),local("Arial");size-adjust:104%;ascent-override:92%;descent-override:22%;line-gap-override:0%}

/* ---------- Foundations ---------- */
html{color-scheme:dark;background:var(--night-950);-webkit-text-size-adjust:100%;text-size-adjust:100%}
body{margin:0;background:var(--bg);color:var(--text);font:var(--fw-medium) var(--fs-16)/var(--lh-body) var(--font-ui);-webkit-tap-highlight-color:transparent}
*,*::before,*::after{box-sizing:border-box}
button,input,select,textarea{font:inherit;color:inherit}
button{cursor:pointer}
button,a,[role="button"],input,select,textarea,label{touch-action:manipulation}
[hidden]{display:none!important}

/* Creepster is display only, 24px and up. */
.bf-display{font-family:var(--font-display);font-weight:var(--fw-regular);line-height:var(--lh-tight);letter-spacing:var(--track-display)}
/* Fredoka has no reliable tabular figures: wrap changing digits in spans inside .bf-digits. */
.bf-num{font-variant-numeric:tabular-nums}
.bf-digits>span{display:inline-block;width:1ch;text-align:center}
.bf-vh{position:absolute!important;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}

/* Icons: 24px line icons from the sprite, drawn in currentColor. */
.bf-ico{width:24px;height:24px;flex:none;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.bf-ico--sm{width:20px;height:20px}

/* Focus: one ring everywhere, slime, 3px, offset 3px. */
:focus{outline:none}
:focus-visible{outline:var(--ring) solid var(--focus);outline-offset:var(--ring-offset)}

/* Press: every control dips to .96 while held. */
:where(button,a,[role="button"],[role="switch"],label[class*="bf-"]){transition:scale var(--dur-press) var(--ease-out)}
:where(button,a,[role="button"],[role="switch"],label[class*="bf-"]):active{scale:.96}

/* Disabled: one opacity, both spellings. */
:where(button,input,select,textarea,[role="button"]):disabled,[aria-disabled="true"]{opacity:var(--opacity-disabled);cursor:not-allowed}

/* Decoration never takes a tap. */
.bf-deco{pointer-events:none}

/* Calm (the in-app switch) and reduced motion: movement goes, fades stay. */
@media (prefers-reduced-motion: reduce){:root{--dur-rise:1ms;--dur-gate:1ms;--dur-peek:1ms}}
html[data-calm] *,html[data-calm] *::before,html[data-calm] *::after{animation-duration:1ms!important;animation-iteration-count:1!important;transition-duration:1ms!important}
/* ---------- Button ---------- */
/* replaces: #playCta, #playCta .cta-main, #playCta small (index.html:135–137), #playCta:hover (app.css:61),
   .btn-primary, .btn-primary[aria-disabled="true"] (sheets.css:23–24), .btn-secondary (sheets.css:25),
   .btn-secondary:hover, .party-share:not(.btn-primary):hover (sheets.css:27),
   .party-share, .party-share.btn-primary (sheets.css:121–122), .party-gcal (sheets.css:120), .party-install-btn (sheets.css:128),
   .party-actions .btn-primary (sheets.css:119), .res-form .btn-primary (game.css:163), .bag-haunt .btn-secondary (extras.css:99),
   .bag-show, .bag-show:hover (extras.css:49–50), .res-saved (game.css:150).
   Looks it also serves, claimed by the composing component: .sheet-close (Sheet), .board-retry, .board-retry:hover (InlineState),
   .install-actions button, .install-go (Toast), .pause-btn, .res-btn (DialogCard), .sd-join (StageBanner),
   #pauseBtn (ArenaHud), .tv-wake (TvPanel). */

/* Shared box: 44px hit, sp-4 sides, hairline edge (transparent unless the variant draws it), 600 16/1.2 (t-button). No margin: the container spaces buttons. */
.bf-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);min-width:var(--hit);min-height:var(--hit);margin:0;padding:0 var(--sp-4);border:var(--hairline) solid transparent;border-radius:var(--r-btn);background:transparent;color:var(--text);font:var(--fw-semibold) var(--fs-16)/var(--lh-btn) var(--font-ui);text-align:center;text-decoration:none;-webkit-appearance:none;appearance:none}
.bf-btn--block{display:flex;width:100%}

/* Hero layout: icon + label on one line, the 600 13/16 sub-line (t-micro) under it (the Play CTA). */
.bf-btn__main{display:inline-flex;align-items:center;gap:var(--sp-2)}
.bf-btn__sub{font:var(--fw-semibold) var(--fs-13)/var(--lh-16) var(--font-ui)}
.bf-btn:has(> .bf-btn__sub){flex-direction:column;gap:0}

/* Secondary: night fill, plate-line edge, r-btn. */
.bf-btn--secondary{background:var(--bg);border-color:var(--plate-line)}

/* Pill: small utility actions (Close, Retry, Show me, Not now). 600 14/1. Declared before primary so pill + primary keeps the primary colors. */
.bf-btn--pill{border-radius:var(--r-chip);background:var(--bg);border-color:var(--plate-line);font-size:var(--fs-14);line-height:var(--lh-solid);white-space:nowrap}

/* Danger: words carry it, the edge repeats it. */
.bf-btn--danger{background:var(--bg);border-color:var(--danger);color:var(--danger)}

/* Link: lilac, underlined, still a 44px box; 600 16/1.3 as built. */
.bf-btn--link{padding:0 var(--sp-3);color:var(--lilac-300);line-height:var(--lh-ui);text-decoration:underline;text-underline-offset:3px} /* 3px: underline clears Fredoka's descenders (site value) */

/* Icon: 44px circle on surface; the label lives in aria-label. */
.bf-btn--icon{padding:0;border-radius:var(--r-chip);background:var(--surface);border-color:var(--plate-line)}

/* Primary: the one action color, 56px, 700 18/22 (t-button-lg), candle glow. */
.bf-btn--primary{min-height:var(--cta-h);background:var(--accent);border-color:transparent;color:var(--on-accent);font-weight:var(--fw-bold);font-size:var(--fs-18);line-height:var(--lh-22);box-shadow:var(--glow-candle)}
/* Compact primary (44px rows) and the pill-shaped primary beside pills: 700 15, no glow. */
.bf-btn--primary.bf-btn--md{min-height:var(--hit);font-size:var(--fs-15);line-height:var(--lh-20);box-shadow:none}
.bf-btn--pill.bf-btn--primary{min-height:var(--hit);font-size:var(--fs-15);line-height:var(--lh-solid);box-shadow:none}

/* Live: the Showdown join, 700 15/18. Slime fill; candle once the Showdown is open. */
.bf-btn--live{padding:var(--sp-1) var(--sp-3);background:var(--live);color:var(--on-accent);font-weight:var(--fw-bold);font-size:var(--fs-15);line-height:var(--lh-18)}
.bf-btn--live.bf-btn--open{background:var(--candle-300)}

/* Outline-live: spectral words on a live-line edge (install, keep screen awake). 700 15/1. */
.bf-btn--outline-live{border-radius:var(--r-chip);border-color:var(--live-line);color:var(--live);font-weight:var(--fw-bold);font-size:var(--fs-15);line-height:var(--lh-solid);white-space:nowrap}

/* States. Disabled opacity and cursor come from the base; a dimmed primary also loses its glow. */
.bf-btn--primary:disabled,.bf-btn--primary[aria-disabled="true"]{box-shadow:none}
.bf-btn[aria-busy="true"]{cursor:progress}

/* Hover: primary → accent-hover fill; neutral outlined (secondary, pill, icon) → lilac-300 edge;
   tone-edged (danger, outline-live) keep their edge and take a surface-2 fill; link → text; live has none. */
@media (hover:hover){
  .bf-btn--primary:hover:not(:disabled):not([aria-disabled="true"]){background:var(--accent-hover)}
  .bf-btn:is(.bf-btn--secondary,.bf-btn--pill,.bf-btn--icon):not(.bf-btn--primary):hover:not(:disabled):not([aria-disabled="true"]){border-color:var(--lilac-300)}
  .bf-btn:is(.bf-btn--danger,.bf-btn--outline-live):hover:not(:disabled):not([aria-disabled="true"]){background:var(--surface-2)}
  .bf-btn--link:hover:not(:disabled):not([aria-disabled="true"]){color:var(--text)}
}

/* Pressed primary deepens to the painted lantern orange (declared after hover so it wins while held). */
.bf-btn--primary:active:not(:disabled):not([aria-disabled="true"]){background:var(--pumpkin-600)}
/* ---------- PlatePill ---------- */
/* replaces: #chips>button, #brandChip, #soundBtn pointer-events (index.html:91), #chips button, #chips button::before (index.html:141–142),
   #brandChip, #soundBtn and their ::before (index.html:146–147), #brandChip .ico (index.html:148),
   the chips' and chrome pills' hover (app.css:63), #topbar.has-center .brand-label, #topbar.has-center #brandChip (app.css:74–75),
   #topbar.has-both #soundLabel, #topbar.has-both #soundBtn (app.css:78–79), #soundBtn .eq, .eq i, @keyframes eq-bounce (app.css:82–89),
   #hypeVal, #goalNow (app.css:181), #chips button > span:first-child (app.css:182), #goalChip.is-empty > span:first-child (app.css:183),
   #goalNow display (scene.css:125). The fill inside a stat chip is Meter's (.chip-fill, .hype-fill); only its placement is here,
   including the empty chip hiding it (#goalChip.is-empty .chip-fill, app.css:184). */

/* A 44px hit box with the visible plate pill drawn on ::before. --bf-plate-pill-h is the plate's visible height:
   pill-h (32) for chrome pills, chip-h (40) for stat chips. */
.bf-plate-pill{--bf-plate-pill-h:var(--pill-h);position:relative;display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);min-width:var(--hit);min-height:var(--hit);margin:0;padding:0 var(--sp-3);border:0;background:none;color:var(--text);font:var(--fw-semibold) var(--fs-14)/var(--lh-18) var(--font-ui);text-decoration:none;white-space:nowrap;isolation:isolate;pointer-events:auto}
.bf-plate-pill::before{content:"";position:absolute;inset:calc(50% - var(--bf-plate-pill-h) / 2) 0;z-index:-1;border-radius:var(--r-chip);background:var(--plate);border:var(--hairline) solid var(--plate-line)} /* z-index -1: under the label, inside the isolated pill */

.bf-plate-pill__label{min-width:0;overflow:hidden;text-overflow:ellipsis}
/* inline-block so the page's number pop (a transform) applies. */
.bf-plate-pill__value{display:inline-block;font-weight:var(--fw-bold);font-variant-numeric:tabular-nums}

/* Icon-only at narrow widths: the label stays for screen readers (the .bf-vh recipe). */
.bf-plate-pill--icon .bf-plate-pill__label{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap} /* 1px: visually hidden, still announced */

/* Stat chip: shares its row, 40px plate, a Meter along the plate's bottom edge. */
.bf-plate-pill--stat{--bf-plate-pill-h:var(--chip-h);flex:1 1 0;height:var(--hit)}
.bf-plate-pill .bf-plate-pill__meter{position:absolute;left:var(--sp-4);right:var(--sp-4);bottom:var(--sp-2);width:auto;margin:0}
/* No count yet: the prompt wraps to two centered 600 13/16 lines (t-micro metrics) and the meter hides. */
/* Documented 13px exception: a two-line prompt that must fit the 40px plate at 320px. */
.bf-plate-pill.is-empty .bf-plate-pill__label{white-space:normal;text-align:center;font-size:var(--fs-13);line-height:var(--lh-16)}
.bf-plate-pill.is-empty .bf-plate-pill__meter{display:none}

/* Sound toggle: three slime bars, shown while sound is on. Bar and box sizes are equalizer geometry from the build. */
.bf-plate-pill__eq{display:none;align-items:flex-end;gap:calc(var(--sp-1) / 2);height:14px} /* 14px: room for a 6px bar at scale 2.2; 2px gap is an optical nudge */
.bf-plate-pill__eq i{width:3px;height:6px;border-radius:var(--r-tick);background:var(--slime-400)} /* 3x6px bar geometry */
.bf-plate-pill[aria-pressed="true"] .bf-plate-pill__eq{display:inline-flex}
@media (prefers-reduced-motion: no-preference){
  @keyframes bf-plate-pill-eq{0%,100%{transform:scaleY(.5)}50%{transform:scaleY(2.2)}} /* bar bounce range from the build */
  .bf-plate-pill[aria-pressed="true"] .bf-plate-pill__eq i{transform-origin:bottom;animation:bf-plate-pill-eq var(--dur-pulse) var(--ease-loop) infinite}
  .bf-plate-pill[aria-pressed="true"] .bf-plate-pill__eq i:nth-child(2){animation-delay:calc(var(--dur-pulse) / -3)}
  .bf-plate-pill[aria-pressed="true"] .bf-plate-pill__eq i:nth-child(3){animation-delay:calc(var(--dur-pulse) * -2 / 3)}
}

/* Focus: the ring hugs the visible pill, not the 44px box. */
.bf-plate-pill:focus-visible{outline:none}
.bf-plate-pill:focus-visible::before{outline:var(--ring) solid var(--focus);outline-offset:var(--ring-offset)}

/* Hover: an outlined control, so the plate's edge takes moonlight. */
@media (hover:hover){
  .bf-plate-pill:hover::before{border-color:var(--lilac-300)}
}
/* ---------- Icon ---------- */
/* replaces: .ico (index.html:88); .install-tip .ico (sheets.css:150) becomes .bf-ico--sm, its nudge dropped. The icon box lives in 00-base.css as .bf-ico and .bf-ico--sm (24px and 20px, round stroke). */
/* Icons have no colors of their own: they take currentColor from the control or text beside them. Sized or tinted
   instances belong to the component that places them: #brandChip .ico (PlatePill); the coach icon's lilac is Toast's .bf-toast__icon. */
/* ---------- Dock ---------- */
/* replaces: #dock (index.html:153), .dock-item (index.html:154), .dock-item.is-active (index.html:155),
   .dock-item.is-active::after (index.html:156), .dock-item[data-route="play"] (index.html:157), .dock-item:hover (app.css:62),
   landscape-phone #dock, .dock-item, .dock-item.is-active::after (app.css:296-298),
   landscape-tablet #dock, .dock-item (app.css:307-308), portrait-tablet #dock padding (app.css:331) */

/* Portrait bar (the default shape). Fixed to the bottom edge over the art, near-opaque night. */
/* sp-3 above and below the dock-h items, so the page's --dock-total is sp-3 + dock-h + max(sp-3, sab). */
.bf-dock{position:fixed;z-index:var(--z-chrome);left:0;right:0;bottom:0;display:flex;justify-content:center;padding:var(--sp-3) calc(var(--sp-2) + var(--sar)) max(var(--sp-3),var(--sab)) calc(var(--sp-2) + var(--sal));background:var(--plate-solid);border-top:var(--hairline) solid var(--plate-line)}

/* Item: 24px icon over a 13px semibold label (t-micro). Items share the bar up to 96px each, so on tablets the row centers at 480px. */
.bf-dock__item{position:relative;flex:1 1 0;min-width:0;max-width:calc(2 * var(--sp-12));height:var(--dock-h);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:calc(var(--sp-1) / 2); /* 2px: optical, the icon sits tight on its label */
  margin:0;padding:0;border:0;border-radius:var(--r-row);background:none;color:var(--text-2);font:var(--fw-semibold) var(--fs-13)/var(--lh-16) var(--font-ui);text-decoration:none}
.bf-dock__label{max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* Where you are: bone label plus a ring-thick slime underline (r-tick). Gate sets aria-current="page"; sheet routes set aria-expanded="true". */
.bf-dock__item[aria-current="page"],.bf-dock__item[aria-expanded="true"]{color:var(--text)}
.bf-dock__item[aria-current="page"]::after,.bf-dock__item[aria-expanded="true"]::after{content:"";position:absolute;left:30%;right:30%;bottom:var(--ring);height:var(--ring);border-radius:var(--r-tick);background:var(--slime-400)}

/* Play is the action route: pumpkin at rest, pumpkin while open (the underline alone marks it), amber on hover. */
.bf-dock__item.bf-dock__item--play{color:var(--accent)}

@media (hover:hover){
  .bf-dock__item:hover{color:var(--text)}
  .bf-dock__item.bf-dock__item--play:hover{color:var(--accent-hover)}
}

/* Right rail: landscape phones (height <= 500px). Forced with .bf-dock--rail. */
.bf-dock--rail{top:0;left:auto;width:calc(var(--rail-w) + var(--sar));flex-direction:column;gap:calc(var(--sp-1) / 2); /* 2px optical gap between items, as built */
  padding:var(--sat) var(--sar) var(--sab) 0;border-top:0;border-left:var(--hairline) solid var(--plate-line)}
.bf-dock--rail .bf-dock__item{flex:0 0 auto;width:100%;max-width:none}
.bf-dock--rail .bf-dock__item::after{left:var(--ring);right:auto;top:var(--sp-3);bottom:var(--sp-3);width:var(--ring);height:auto}

/* Floating pill: landscape tablets and desktop (height >= 501px). Forced with .bf-dock--float. */
/* Items are 84px as built (sp-12 + sp-8 + sp-1); five make a 454px pill, shrinking toward 44px in narrow windows. */
.bf-dock--float{bottom:calc(var(--sp-6) + var(--sab));width:max-content;max-width:calc(100% - 2 * var(--gutter));height:var(--dock-h);margin-inline:auto;gap:var(--sp-1);padding:0 var(--sp-2);border:var(--hairline) solid var(--plate-line);border-radius:var(--r-chip);box-shadow:var(--shadow-2)}
.bf-dock--float .bf-dock__item{flex:0 1 auto;width:calc(var(--sp-12) + var(--sp-8) + var(--sp-1));min-width:var(--hit);max-width:none;height:100%}

/* The plain .bf-dock picks its shape by viewport; .bf-dock--bar, --rail or --float pin one shape. */
@media (orientation:landscape) and (max-height:500px){ /* layout breakpoint: landscape phone, as built */
  .bf-dock:not(.bf-dock--bar,.bf-dock--float){top:0;left:auto;width:calc(var(--rail-w) + var(--sar));flex-direction:column;gap:calc(var(--sp-1) / 2); /* 2px optical gap, as built */
    padding:var(--sat) var(--sar) var(--sab) 0;border-top:0;border-left:var(--hairline) solid var(--plate-line)}
  .bf-dock:not(.bf-dock--bar,.bf-dock--float) .bf-dock__item{flex:0 0 auto;width:100%;max-width:none}
  .bf-dock:not(.bf-dock--bar,.bf-dock--float) .bf-dock__item::after{left:var(--ring);right:auto;top:var(--sp-3);bottom:var(--sp-3);width:var(--ring);height:auto}
}
@media (orientation:landscape) and (min-height:501px){ /* layout breakpoint: landscape tablet and desktop, as built */
  .bf-dock:not(.bf-dock--bar,.bf-dock--rail){bottom:calc(var(--sp-6) + var(--sab));width:max-content;max-width:calc(100% - 2 * var(--gutter));height:var(--dock-h);margin-inline:auto;gap:var(--sp-1);padding:0 var(--sp-2);border:var(--hairline) solid var(--plate-line);border-radius:var(--r-chip);box-shadow:var(--shadow-2)}
  .bf-dock:not(.bf-dock--bar,.bf-dock--rail) .bf-dock__item{flex:0 1 auto;width:calc(var(--sp-12) + var(--sp-8) + var(--sp-1));min-width:var(--hit);max-width:none;height:100%} /* 84px float item, as built */
}
/* ---------- SegmentedControl ---------- */
/* replaces: .board-tabs (sheets.css:33), .board-tab (sheets.css:34), .board-tab[aria-selected="true"] (sheets.css:35),
   .board-tab:not([aria-selected="true"]):hover (sheets.css:28), .board-panel:focus, .board-panel:focus-visible (sheets.css:36-37, now the base ring),
   .party-seg (sheets.css:133), .party-seg-opt (sheets.css:134), .party-seg-opt input (sheets.css:135),
   .party-seg-opt span (sheets.css:136), .party-seg-opt input:checked + span (sheets.css:137),
   .party-seg-opt input:focus-visible + span (sheets.css:138) */

/* Track: night pill with a plum edge. Inline by default (radio groups at the end of a settings row). */
.bf-seg{display:inline-flex;gap:var(--sp-1);max-width:100%;margin:0;padding:var(--sp-1);border:var(--hairline) solid var(--line);border-radius:var(--r-chip);background:var(--bg)}
/* Full width with equal segments (tabs above a panel). */
.bf-seg--full{display:flex;width:100%}

/* Segment: a tab <button> or a radio <label>. 44px tall everywhere, t-label type. */
.bf-seg__item{position:relative;display:inline-flex;align-items:center;justify-content:center;min-width:calc(var(--hit) + var(--sp-4));min-height:var(--hit);margin:0;padding:0 var(--sp-2);border:0;border-radius:var(--r-chip);background:none;color:var(--text-2);font:var(--fw-semibold) var(--fs-14)/var(--lh-18) var(--font-ui);white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none}
.bf-seg--full .bf-seg__item{flex:1 1 0;min-width:0}
/* The radio fills its label, invisible, so the whole segment takes the tap and the label carries the look. */
.bf-seg__input{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:inherit}

/* Where you are: surface-2 fill, bone text, ring-thick slime underline. */
:is(.bf-seg__item[aria-selected="true"],.bf-seg__item:has(> .bf-seg__input:checked)){background:var(--surface-2);color:var(--text);box-shadow:inset 0 calc(-1 * var(--ring)) 0 var(--slime-400)}

/* Focus: tabs take the base ring on the button; a radio segment draws it on its label at the base offset. */
.bf-seg__item:has(> .bf-seg__input:focus-visible){outline:var(--ring) solid var(--focus);outline-offset:var(--ring-offset)}

/* Disabled: tabs use aria-disabled (base); a disabled radio dims its label. */
.bf-seg__item:has(> .bf-seg__input:disabled){opacity:var(--opacity-disabled);cursor:not-allowed}

@media (hover:hover){
  .bf-seg__item:not([aria-disabled="true"],:has(> .bf-seg__input:disabled)):hover{color:var(--text)}
}
/* ---------- Tag ---------- */
/* replaces: .top-chip (app.css:68), #liveChip (app.css:69), #liveChip::before (app.css:70), #presenceChip (app.css:71),
   #liveChip .live-rest (app.css:72), #previewRibbon (app.css:91), #netStatus .dot and @keyframes net-pulse (app.css:172-176),
   @keyframes live-pulse and its rule (scene.css:119-120), .board-youchip (sheets.css:56), .board-mode, .board-date (sheets.css:58-59),
   .shelf-tag, .shelf-tag.is-nexttag (sheets.css:175-176), .bag-streak (extras.css:54), .res-pb (game.css:138), .res-chips li (game.css:142).
   Composed, and claimed, by: TitleCard (#partyWhen), TvPanel (.tv-live, .tv-online, .tv-preview), ListRow (.party-facts li::before). */

/* Base: a 28px pill, t-label type. Not interactive. */
.bf-tag{display:inline-flex;align-items:center;gap:var(--sp-2);max-width:100%;height:calc(var(--sp-6) + var(--sp-1));padding:0 var(--sp-3);border:var(--hairline) solid transparent;border-radius:var(--r-chip);background:transparent;color:var(--text-2);font:var(--fw-semibold) var(--fs-14)/var(--lh-18) var(--font-ui);white-space:nowrap;overflow:hidden;vertical-align:middle} /* height: 28px pill (sp-6 + sp-1) */
/* Wrap text that can run long (LIVE from the party, presence, the preview line) so it ends in an ellipsis. */
.bf-tag__text{min-width:0;overflow:hidden;text-overflow:ellipsis}

/* Small: 20px, t-tag (12px bold, track-tag). Only for tags that repeat what the row already says. */
.bf-tag--sm{flex:none;gap:var(--sp-1);height:var(--sp-5);padding:0 var(--sp-2);font:var(--fw-bold) var(--fs-12)/var(--lh-solid) var(--font-ui);letter-spacing:var(--track-tag)}

/* Tones */
.bf-tag--plate{background:var(--plate);border-color:var(--plate-line);color:var(--text)}
.bf-tag--neutral{background:var(--bg);border-color:var(--line);color:var(--text)}
.bf-tag--you{background:var(--you);color:var(--on-accent)}
.bf-tag--reward{background:var(--reward);color:var(--on-accent)}
.bf-tag--candle{background:var(--candle-300);color:var(--on-accent)}
.bf-tag--live{background:var(--plate);border-color:var(--live-line);color:var(--live)}
.bf-tag--preview{background:var(--plate);border:var(--hairline) dashed var(--lilac-300);color:var(--amber-400)}
/* No tone: bare mist text (the Legends date). No pill, so no side padding. */
.bf-tag:not(.bf-tag--plate,.bf-tag--neutral,.bf-tag--you,.bf-tag--reward,.bf-tag--candle,.bf-tag--live,.bf-tag--preview){padding-inline:0}

/* ---------- StatusDot ---------- */
/* An 8px status light. Decorative: the words beside it carry the meaning. Always takes a tone. */
.bf-dot{display:inline-block;flex:none;width:var(--sp-2);height:var(--sp-2);border-radius:50%;background:var(--text-2)} /* 8px dot = sp-2 */
.bf-dot--live{background:var(--live);box-shadow:var(--glow-spectral)}
.bf-dot--warn{background:var(--amber-400)}
.bf-dot--bullet{background:var(--pumpkin-500)}

@media (prefers-reduced-motion: no-preference){
  @keyframes bf-tag-dot-pulse{50%{opacity:var(--opacity-pulse)}}
  .bf-dot--live,.bf-dot--warn{animation:bf-tag-dot-pulse var(--dur-pulse) var(--ease-loop) infinite}
}
/* ---------- Switch ---------- */
/* replaces: .set-row (sheets.css:141), .set-row label (sheets.css:142), .set-row input[type="checkbox"] and its ::before and :checked rules (sheets.css:143–146),
   .res-form .set-row (game.css:164), .lobby-lantern (game.css:22), .pause-row (game.css:132),
   .party-sound / .party-sound legend (sheets.css:131–132), .night-tint / .night-tint legend (extras.css:94–95), .party-sound-note (sheets.css:139) */

/* The settings row: a label on the left, the switch (or, in a fieldset row, a SegmentedControl) on the right. */
.bf-switch{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);min-height:var(--hit);margin:0;padding:0;border:0;border-bottom:var(--hairline) solid var(--line);min-inline-size:0}
.bf-switch__label{flex:1;align-self:stretch;display:flex;align-items:center;padding:var(--sp-3) 0;color:var(--text);font:var(--fw-medium) var(--fs-16)/var(--lh-body) var(--font-ui);cursor:pointer}

/* The switch: the input is the 44px-tall hit box; the 50×30 track is ::before, the 22px knob is ::after.
   Press (.96) comes from the base [role="switch"] rule. */
.bf-switch__input{--bf-switch-track-w:50px;--bf-switch-track-h:30px;--bf-switch-knob:22px; /* component geometry: 50×30 track, 22px knob, as built */
  position:relative;flex:none;width:var(--bf-switch-track-w);height:var(--hit);margin:0;padding:0;border:0;background:transparent;-webkit-appearance:none;appearance:none}
.bf-switch__input:enabled{cursor:pointer} /* enabled only, so the base's disabled cursor is not overridden */
.bf-switch__input::before{content:"";position:absolute;inset:calc((var(--hit) - var(--bf-switch-track-h)) / 2) 0;border:var(--hairline) solid var(--line);border-radius:var(--r-chip);background:var(--surface-2)}
.bf-switch__input::after{content:"";position:absolute;top:calc((var(--hit) - var(--bf-switch-knob)) / 2);left:calc((var(--bf-switch-track-h) - var(--bf-switch-knob)) / 2);width:var(--bf-switch-knob);height:var(--bf-switch-knob);border-radius:50%;background:var(--mist-300);transition:transform var(--dur-fast) var(--ease-out)}
.bf-switch__input:checked::before{background:var(--slime-700);border-color:var(--slime-400)}
.bf-switch__input:checked::after{transform:translateX(calc(var(--bf-switch-track-w) - var(--bf-switch-track-h)));background:var(--bone-50)}

/* Focus goes on the track, not on the taller hit box. */
.bf-switch__input:focus-visible{outline:none}
.bf-switch__input:focus-visible::before{outline:var(--ring) solid var(--focus);outline-offset:var(--ring-offset)}

/* Disabled: the base dims the input; the label follows. */
.bf-switch:has(>.bf-switch__input:disabled)>.bf-switch__label{opacity:var(--opacity-disabled);cursor:not-allowed}

/* Hover: an off track takes the lilac edge; an on track keeps its slime edge. */
@media (hover:hover){
  .bf-switch:hover>.bf-switch__input:enabled:not(:checked)::before{border-color:var(--lilac-300)}
}

/* Inside a form panel (Carve my name automatically): no divider. */
.bf-switch--flush{border-bottom:0}

/* Fieldset row (Sound, Ghost color): the legend is floated so it joins the flex row; the control sits at the end. */
.bf-switch--fieldset{padding:var(--sp-1) 0}
.bf-switch--fieldset>.bf-switch__label{float:left;flex:1;padding:var(--sp-3) 0;cursor:default}
.bf-switch--fieldset>:not(legend){float:right;flex:none;margin-inline-start:auto}

/* The helper line under a row ("Music loads only when you turn it on."): t-note in mist. */
.bf-switch__note{margin:var(--sp-2) 0 var(--sp-3);color:var(--text-2);font:var(--fw-medium) var(--fs-14)/var(--lh-20) var(--font-ui)}
/* ---------- ChoiceCard ---------- */
/* replaces: .lobby-modes (game.css:12), .lobby-mode (game.css:13), .lobby-mode input (game.css:14), .lobby-mode-card and its ::before, b, small (game.css:15–18),
   .lobby-mode input:checked + .lobby-mode-card and ::before (game.css:19–20), .lobby-mode input:focus-visible + .lobby-mode-card (game.css:21),
   .res-avatars (game.css:156), .res-av (game.css:157), .res-av input (game.css:158), .res-av:has(input:checked) (game.css:159),
   .res-av:has(input:focus-visible) (game.css:160); the avatar legend's markup (results.js:257; the .res-label rule itself is TextField's) */

/* The group: a borderless fieldset. Cards stack; tiles sit four to a row. */
.bf-choice-card__group{display:grid;gap:var(--sp-2);min-width:0;margin:0;padding:0;border:0}
.bf-choice-card__group--tiles{grid-template-columns:repeat(4,minmax(0,1fr))}
.bf-choice-card__legend{margin:0 0 var(--sp-2);padding:0;color:var(--text-2);font:var(--fw-semibold) var(--fs-14)/var(--lh-18) var(--font-ui)}

/* The card: a label holding an invisible radio that covers it, so the whole card takes the tap. */
.bf-choice-card{--bf-choice-card-dot:18px; /* radio dot, as built */
  position:relative;display:flex;flex-direction:column;justify-content:center;gap:calc(var(--sp-1) / 2); /* 2px: title to sub, as built */
  min-width:0;min-height:var(--hit);padding:var(--sp-3) var(--sp-4) var(--sp-3) calc(var(--sp-4) + var(--bf-choice-card-dot) + var(--sp-3));
  border:var(--stroke-bold) solid var(--line);border-radius:var(--r-row);background-color:var(--surface-2);color:var(--text);cursor:pointer}
.bf-choice-card__input{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:inherit}
.bf-choice-card__title{color:var(--text);font:var(--fw-bold) var(--fs-16)/var(--lh-ui) var(--font-ui)}
.bf-choice-card__sub{color:var(--text-2);font:var(--fw-medium) var(--fs-14)/var(--lh-ui) var(--font-ui)}

/* Radio dot, centered on the title's first line. */
.bf-choice-card::before{content:"";position:absolute;left:var(--sp-4);top:calc(var(--sp-3) + (var(--fs-16) * var(--lh-ui) - var(--bf-choice-card-dot)) / 2);width:var(--bf-choice-card-dot);height:var(--bf-choice-card-dot);border:var(--stroke-bold) solid var(--mist-300);border-radius:50%}

/* Tile: one emoji, no dot, no text column. */
.bf-choice-card--tile{align-items:center;padding:0;border-color:transparent}
.bf-choice-card--tile::before{content:none}
.bf-choice-card__emoji{font-size:var(--fs-24);line-height:var(--lh-solid)}

/* Your pick: you edge on a you-wash fill (over surface-2). */
.bf-choice-card:has(>.bf-choice-card__input:checked){border-color:var(--you);background-image:linear-gradient(var(--you-wash),var(--you-wash))}
.bf-choice-card:has(>.bf-choice-card__input:checked)::before{border-color:var(--you);background:radial-gradient(circle,var(--you) 0 var(--sp-1),transparent calc(var(--sp-1) + var(--hairline)))}

/* Focus: the base ring on the card, at the base offset (the build used 2px). */
.bf-choice-card:has(>.bf-choice-card__input:focus-visible){outline:var(--ring) solid var(--focus);outline-offset:var(--ring-offset)}

/* Disabled: the card dims, refuses the tap and does not dip. */
.bf-choice-card:has(>.bf-choice-card__input:disabled){opacity:var(--opacity-disabled);cursor:not-allowed;scale:1}

/* Hover: the lilac edge on an outlined card that is not your pick. */
@media (hover:hover){
  .bf-choice-card:hover:not(:has(>.bf-choice-card__input:checked)):not(:has(>.bf-choice-card__input:disabled)){border-color:var(--lilac-300)}
}
/* ---------- TextField ---------- */
/* replaces: .party-sharefield label (sheets.css:123), .party-sharefield input (sheets.css:124),
   .res-label (game.css:153), .res-form input[type="text"] (game.css:154), .res-form input[aria-invalid="true"] (game.css:155),
   .res-err, .res-err[hidden] (game.css:161–162) */

/* Label above, then the field, then the error: one sp-2 gap. */
.bf-text-field{display:grid;gap:var(--sp-2);min-width:0}
.bf-text-field__label{color:var(--text-2);font:var(--fw-semibold) var(--fs-14)/var(--lh-18) var(--font-ui)}

/* The field: a night-950 well, darker than any panel it sits on, with a plate-line edge; 16px text (no iOS zoom on focus). */
.bf-text-field__input{display:block;width:100%;min-width:0;min-height:var(--hit);margin:0;padding:0 var(--sp-3);border:var(--hairline) solid var(--plate-line);border-radius:var(--r-row);background:var(--night-950);color:var(--text);font:var(--fw-medium) var(--fs-16)/var(--lh-body) var(--font-ui);-webkit-appearance:none;appearance:none}
.bf-text-field__input::placeholder{color:var(--text-2);opacity:1}

/* Read-only (the share link to copy): the raised surface-2 fill, so a value to copy reads differently from a field to type in. */
.bf-text-field__input[readonly]{background:var(--surface-2)}

/* Invalid: a danger edge, paired with the error line. */
.bf-text-field__input[aria-invalid="true"]{border-color:var(--danger)}
.bf-text-field__error{margin:0;color:var(--danger);font:var(--fw-medium) var(--fs-15)/var(--lh-20) var(--font-ui)}

/* Hover: the lilac edge (outlined control); an invalid field keeps its danger edge. */
@media (hover:hover){
  .bf-text-field__input:enabled:hover:not([aria-invalid="true"]){border-color:var(--lilac-300)}
}
/* ---------- Slider ---------- */
/* replaces: .hype-slider (sheets.css:93), .hype-slider input[type="range"] (sheets.css:95), ::-webkit-slider-runnable-track (sheets.css:96),
   ::-webkit-slider-thumb with the inline SVG candy corn (sheets.css:97), ::-moz-range-track, ::-moz-range-progress, ::-moz-range-thumb (sheets.css:98–100),
   .hype-stops (sheets.css:101), .hype-caption (sheets.css:102); the --fill variable (hype.js:148) becomes --p.
   Composes, never restyles: DisplayNumeral (.bf-numeral--hype claims .hype-num, sheets.css:103) for the value. */

/* The block: the input and its stops. --p (0–1, value ÷ max) is set by the page on the input or the block; WebKit paints the fill from it, Firefox fills natively. */
.bf-slider{display:grid;min-width:0}
.bf-slider__input{--bf-slider-track:var(--sp-3); /* track = Meter --md */
  --bf-slider-corn-from:152.8deg;--bf-slider-corn-span:54.4deg; /* candy-corn wedge: 27.2deg either side of straight down, from the tip */
  --bf-slider-corn:
    conic-gradient(from var(--bf-slider-corn-from) at 50% 0,transparent,var(--bone-50) .4deg calc(var(--bf-slider-corn-span) - .4deg),transparent var(--bf-slider-corn-span)) 0 5px/100% 13px no-repeat, /* tip band, bone */
    conic-gradient(from var(--bf-slider-corn-from) at 50% 0,transparent,var(--pumpkin-500) .4deg calc(var(--bf-slider-corn-span) - .4deg),transparent var(--bf-slider-corn-span)) 0 5px/100% 24px no-repeat, /* middle band, pumpkin */
    conic-gradient(from var(--bf-slider-corn-from) at 50% 0,transparent,var(--candle-300) .4deg calc(var(--bf-slider-corn-span) - .4deg),transparent var(--bf-slider-corn-span)) 0 5px/100% 36px no-repeat, /* base band, candle: tip at 5px, 36px tall, 37px wide */
    conic-gradient(from var(--bf-slider-corn-from) at 50% 0,transparent,var(--night-950) .4deg calc(var(--bf-slider-corn-span) - .4deg),transparent var(--bf-slider-corn-span)) 0 .6px/100% 42.4px no-repeat; /* outline: the same wedge grown by 2px on every side */
  display:block;width:100%;height:var(--hit);margin:0;background:transparent;-webkit-appearance:none;appearance:none}
.bf-slider__input:enabled{cursor:pointer} /* enabled only, so the base's disabled cursor is not overridden */

/* WebKit / Blink: the fill ends at the thumb's center, which travels from hit/2 to 100% - hit/2. */
.bf-slider__input::-webkit-slider-runnable-track{box-sizing:border-box;height:var(--bf-slider-track);border:var(--hairline) solid var(--line);border-radius:var(--r-chip);background:linear-gradient(to right,var(--pumpkin-500) 0 calc(var(--hit) / 2 + (100% - var(--hit)) * var(--p,0)),var(--surface-2) 0)}
.bf-slider__input::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:var(--hit);height:var(--hit);margin-top:calc((var(--bf-slider-track) - 2 * var(--hairline) - var(--hit)) / 2);border:0;border-radius:0;background:var(--bf-slider-corn)}

/* Firefox */
.bf-slider__input::-moz-range-track{box-sizing:border-box;height:var(--bf-slider-track);border:var(--hairline) solid var(--line);border-radius:var(--r-chip);background:var(--surface-2)}
.bf-slider__input::-moz-range-progress{box-sizing:border-box;height:var(--bf-slider-track);border-radius:var(--r-chip);background:var(--pumpkin-500)}
.bf-slider__input::-moz-range-thumb{width:var(--hit);height:var(--hit);border:0;border-radius:0;background:var(--bf-slider-corn)}

/* Hover: the track takes the lilac edge (one rule per engine, so neither drops the other). */
@media (hover:hover){
  .bf-slider__input:enabled:hover::-webkit-slider-runnable-track{border-color:var(--lilac-300)}
  .bf-slider__input:enabled:hover::-moz-range-track{border-color:var(--lilac-300)}
}

/* The three labeled stops under the track (t-note), aria-hidden: the value is in aria-valuetext. */
.bf-slider__stops{display:flex;justify-content:space-between;padding:0 var(--sp-1);color:var(--text-2);font:var(--fw-medium) var(--fs-14)/var(--lh-20) var(--font-ui)}

/* The live readout (t-lead), aria-hidden: the numeral and the caption beside it. */
.bf-slider__caption{display:flex;align-items:baseline;justify-content:center;gap:var(--sp-3);margin:var(--sp-2) 0 var(--sp-1);color:var(--text);font:var(--fw-semibold) var(--fs-18)/var(--lh-ui) var(--font-ui)}
/* The numeral is DisplayNumeral's --hype (b.bf-numeral.bf-numeral--hype.bf-slider__value); Slider keeps only its slot. */
.bf-slider__value{min-width:1.2em;text-align:right} /* 1.2em: room for "10" so the caption does not jump, as built */
/* ---------- Surface ---------- */
/* replaces: .sheet-banner (sheets.css:18), .board-note (sheets.css:38, its margin stays with the board), .res-challenge (game.css:146), .bag-golden (extras.css:39), .res-form (game.css:152),
   .hype-bar (sheets.css:108), .party-install (sheets.css:125), .night-treat (extras.css:87).
   Composed by its owners, not claimed here: .gr-helper (ArenaHud, plate), .board-you (Leaderboard, raised pinned). */
/* One ground per job. Always pair .bf-surface with exactly one of plate, plate-solid, panel, row, callout or raised. */
.bf-surface{border:var(--hairline) solid var(--plate-line);color:var(--text)}
.bf-surface>:last-child{margin-bottom:0}

/* plate: the glass under small text on the painting; plate-solid over moving art. */
.bf-surface--plate,.bf-surface--plate-solid{padding:var(--sp-3) var(--sp-4);border-radius:var(--r-card);background:var(--plate)}
.bf-surface--plate-solid{background:var(--plate-solid)}

/* panel: a raised night card on the page ground; --lg is the sheet-scale radius and padding. */
.bf-surface--panel{padding:var(--sp-4);border-radius:var(--r-card);background:var(--surface)}
.bf-surface--panel.bf-surface--lg{padding:var(--sp-5);border-radius:var(--r-sheet)}

/* row: a block inside a sheet (the hype tally, the carve form, the night's treat, the Home Screen row). */
.bf-surface--row{padding:var(--sp-3);border-radius:var(--r-row);background:var(--bg);border-color:var(--line)}
.bf-surface--dashed{border-style:dashed;border-color:var(--plate-line)}

/* callout: a note that concerns you (a challenge, the boards note, the 2027 line). */
.bf-surface--callout{padding:var(--sp-3);border-radius:var(--r-row);background:var(--surface-2);border-color:var(--you)}

/* raised: your own line lifted off the list. --pinned sticks it to the foot of a scrolling sheet body,
   and the lift becomes an upward fade in the sheet's own color. */
.bf-surface--raised{padding:var(--sp-3);border-radius:var(--r-row);background:var(--surface-2);border-color:var(--you);box-shadow:var(--shadow-2)}
.bf-surface--raised.bf-surface--pinned{position:sticky;bottom:0;box-shadow:var(--shadow-pinned)}

/* reward edge: gold for rewards only (the Golden Spirit callout). Declared last so it wins. */
.bf-surface--reward{border-color:var(--reward)}
/* ---------- Sheet ---------- */
/* replaces: dialog reset (app.css:223-224), .sheet, .sheet::backdrop, .sheet-inner, .sheet-handle, .sheet-head, .sheet-head h2 (app.css:227-232),
   .sheet-body (app.css:234), dialog.dlg-fallback, .sheet.dlg-fallback .sheet-inner (app.css:237-238), the sheet keyframes and their
   [open] / .is-closing rules (app.css:239-250), the landscape drawers (app.css:299-301, 314-317), the landscape and Calm motion (app.css:319-327),
   .sheet-body p, .sheet-lead, .sheet-kicker, .sheet-note, .sheet-h3, .sheet-foot, .sheet-body a, .sheet-body ul (sheets.css:14-17, 19-22),
   .recap-h4 (sheets.css:185); .sheet-close, .sheet-close:hover (app.css:233, 64), drawn by Button's pill.
   Composes, never restyles: Button (.bf-btn--pill for Close), ListRow (.bf-list-row--fact for the invitation's facts). */

/* Root: always a <dialog>, opened with showModal(). Bottom sheet in portrait. */
dialog.bf-sheet{position:fixed;inset:auto 0 0 0;margin:0 auto;padding:0;border:0;background:none;color:var(--text);width:var(--sheet-w);max-width:none;overflow:visible}
dialog.bf-sheet{max-height:90vh;max-height:calc(100dvh - var(--sat) - var(--sp-6))} /* 90vh: fallback where dvh is missing */
dialog.bf-sheet::backdrop{background:var(--backdrop)}
/* No showModal(): the page sets [open] + .is-fallback and makes the shell inert; the panel paints its own backdrop. */
dialog.bf-sheet.is-fallback{z-index:var(--z-dialog)}
.bf-sheet.is-fallback .bf-sheet__panel{box-shadow:0 0 0 100vmax var(--backdrop),var(--shadow-sheet)} /* 100vmax: a spread that always covers the viewport */

.bf-sheet__panel{display:flex;flex-direction:column;max-height:inherit;padding-bottom:var(--sab);border:var(--hairline) solid var(--plate-line);border-bottom:0;border-radius:var(--r-sheet) var(--r-sheet) 0 0;background:var(--surface);box-shadow:var(--shadow-sheet);color:var(--text)}
.bf-sheet__handle{flex:none;margin:var(--sp-2) auto 0;border-radius:var(--r-tick);background:var(--line)}
.bf-sheet__handle{width:36px;height:4px} /* the grip's fixed 36x4 geometry */
.bf-sheet__head{flex:none;display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-1) var(--sp-2) var(--sp-1) var(--sp-5)}
.bf-sheet__title{flex:1;min-width:0;margin:0;color:var(--text)}
.bf-sheet__close{flex:none}
.bf-sheet__body{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;padding:var(--sp-1) var(--sp-5) var(--sp-6);font:var(--fw-medium) var(--fs-16)/var(--lh-body) var(--font-ui)}

/* Body flow at zero specificity, so any component class inside the body wins. */
:where(.bf-sheet__body p){margin:0 0 var(--sp-3)}
:where(.bf-sheet__body ul){margin:0 0 var(--sp-3);padding-left:var(--sp-5)}
:where(.bf-sheet__body a:not([class])){color:var(--lilac-300)}

/* Text parts: pair each with its type class (t-lead, t-kicker, t-note, t-subheading). */
.bf-sheet__lead{color:var(--text)}
.bf-sheet__kicker{margin:var(--sp-4) 0 var(--sp-2);color:var(--text-2);text-transform:uppercase}
.bf-sheet__kicker:first-child{margin-top:0}
.bf-sheet__note{color:var(--text-2)}
.bf-sheet__h3{margin:var(--sp-5) 0 var(--sp-2);color:var(--text)}
.bf-sheet__foot{margin-top:var(--sp-5);color:var(--text)}

/* Drawer: a right-hand panel. Forced with --drawer; automatic in landscape (phones and tablets). */
dialog.bf-sheet--drawer{inset:0 0 0 auto;margin:0;width:min(var(--drawer-w),60vw);height:100vh;height:100dvh;max-height:100vh;max-height:100dvh} /* 60vw keeps the stage visible on small landscape phones */
.bf-sheet--drawer .bf-sheet__panel{height:100%;padding:var(--sat) var(--sar) var(--sab) 0;border-bottom:var(--hairline) solid var(--plate-line);border-right:0;border-radius:var(--r-sheet) 0 0 var(--r-sheet)}
.bf-sheet--drawer .bf-sheet__handle{display:none}
@media (orientation:landscape){
  dialog.bf-sheet{inset:0 0 0 auto;margin:0;width:min(var(--drawer-w),60vw);height:100vh;height:100dvh;max-height:100vh;max-height:100dvh} /* 60vw as above */
  dialog.bf-sheet .bf-sheet__panel{height:100%;padding:var(--sat) var(--sar) var(--sab) 0;border-bottom:var(--hairline) solid var(--plate-line);border-right:0;border-radius:var(--r-sheet) 0 0 var(--r-sheet)}
  dialog.bf-sheet .bf-sheet__handle{display:none}
}

/* Motion: rise in, drop out; slide from the right as a drawer. Transform and opacity only. */
@media (prefers-reduced-motion: no-preference){
  @keyframes bf-sheet-rise{from{transform:translateY(100%)}}
  @keyframes bf-sheet-drop{to{transform:translateY(100%)}}
  @keyframes bf-sheet-in-right{from{transform:translateX(100%)}}
  @keyframes bf-sheet-out-right{to{transform:translateX(100%)}}
  @keyframes bf-sheet-fade-in{from{opacity:0}}
  @keyframes bf-sheet-fade-out{to{opacity:0}}
  dialog.bf-sheet[open] .bf-sheet__panel{animation:bf-sheet-rise var(--dur-sheet) var(--ease-out)}
  dialog.bf-sheet[open]::backdrop{animation:bf-sheet-fade-in var(--dur-sheet) var(--ease-out)}
  dialog.bf-sheet.is-closing .bf-sheet__panel{animation:bf-sheet-drop var(--dur-fast) var(--ease-in) forwards}
  dialog.bf-sheet.is-closing::backdrop{animation:bf-sheet-fade-out var(--dur-fast) var(--ease-in) forwards}
  dialog.bf-sheet--drawer[open] .bf-sheet__panel{animation-name:bf-sheet-in-right}
  dialog.bf-sheet--drawer.is-closing .bf-sheet__panel{animation-name:bf-sheet-out-right}
}
@media (orientation:landscape) and (prefers-reduced-motion: no-preference){
  dialog.bf-sheet[open] .bf-sheet__panel{animation-name:bf-sheet-in-right}
  dialog.bf-sheet.is-closing .bf-sheet__panel{animation-name:bf-sheet-out-right}
}
/* Calm keeps a fade (spec F06): the panel swaps to a pure opacity fade over dur-ui, exempt from the base Calm clamp
   (html[data-calm] * with an !important near-instant duration) the way the art fades are: higher specificity plus !important. */
@media (prefers-reduced-motion: no-preference){
  html[data-calm] dialog.bf-sheet[open] .bf-sheet__panel{animation-name:bf-sheet-fade-in;animation-duration:var(--dur-ui)!important;animation-timing-function:var(--ease-out)}
  html[data-calm] dialog.bf-sheet.is-closing .bf-sheet__panel{animation-name:bf-sheet-fade-out;animation-duration:var(--dur-ui)!important;animation-timing-function:var(--ease-in)}
  html[data-calm] dialog.bf-sheet[open]::backdrop{animation-duration:var(--dur-ui)!important}
}
/* ---------- DialogCard ---------- */
/* replaces: #results, #results::backdrop, .results-inner, #results-title (app.css:258-261; game.css:135), #pauseCard, #pauseCard[hidden] (game.css:126-127),
   .pause-inner, .pause-title (game.css:128-129), .res-tier (game.css:137), .res-zero (game.css:140), .res-chips (game.css:141),
   .res-lines, .res-rank, .res-gap (game.css:143-145), .res-again, .res-carve, .res-carve[hidden] (game.css:147-149), .res-saved-note (game.css:151),
   .res-row, .res-row .res-btn (game.css:165-166), the short-screen .res-chips and .results-inner rules (game.css:171-172), .pause-keys (arena.js:510);
   .pause-btn, .res-btn, .pause-btn.is-danger (game.css:130-131), drawn by Button (secondary, danger, block).
   Composes, never restyles: DisplayNumeral (.bf-numeral--score), Tag (--reward, --neutral), Button, Surface (callout, row), Switch, TextField. */
/* One centered card for Ghost Run! results and the pause card, always over the shroud. */

/* Root as a modal <dialog> (results). The root scrolls, so it carries the card's corners and shadow (a shadow on the panel would be clipped). */
dialog.bf-dialog-card{position:fixed;inset:0;margin:auto;padding:0;border:0;border-radius:var(--r-sheet);background:none;box-shadow:var(--shadow-2);color:var(--text);width:var(--dialog-w);max-width:none;height:fit-content;overflow:auto;overscroll-behavior:contain}
dialog.bf-dialog-card{max-height:calc(100vh - var(--sat) - var(--sab) - var(--sp-6));max-height:calc(100dvh - var(--sat) - var(--sab) - var(--sp-6))} /* 100vh: fallback where dvh is missing */
dialog.bf-dialog-card::backdrop{background:var(--shroud)}
dialog.bf-dialog-card.is-fallback{z-index:var(--z-dialog);box-shadow:var(--shadow-2),0 0 0 100vmax var(--shroud)} /* no showModal(): a 100vmax spread paints the shroud */

/* Root as an in-page layer (pause): covers its positioned container, the arena, above the HUD and callouts. */
.bf-dialog-card--inline{position:absolute;inset:0;z-index:var(--z-takeover);display:grid;overflow:auto;padding:calc(var(--sp-3) + var(--sat)) var(--sar) calc(var(--sp-3) + var(--sab)) var(--sal);background:var(--shroud);color:var(--text)}
.bf-dialog-card--inline .bf-dialog-card__panel{width:var(--dialog-w);margin:auto} /* auto margins center it, and keep the top reachable when it overflows */

.bf-dialog-card__panel{padding:var(--sp-5);border:var(--hairline) solid var(--plate-line);border-radius:var(--r-sheet);background:var(--surface);box-shadow:var(--shadow-2);color:var(--text)}

/* Text parts: pair the title and tier with t-heading, notes with t-body-sm. */
.bf-dialog-card__title{margin:0 0 var(--sp-2);text-align:center;color:var(--text)}
/* Score: a DisplayNumeral (.bf-numeral.bf-numeral--score) sets the look; the card centers it and keeps the count-up <b> at Creepster's one weight. */
.bf-dialog-card__score{text-align:center}
.bf-dialog-card__score b{font-weight:inherit}
.bf-dialog-card__tier{margin:var(--sp-1) 0 0;text-align:center;color:var(--candle-300)}
.bf-dialog-card__badge{display:flex;justify-content:center;margin:var(--sp-2) 0 0}
.bf-dialog-card__tags{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--sp-2);margin:var(--sp-3) 0;padding:0;list-style:none}
.bf-dialog-card__lines{margin:0 0 var(--sp-2);text-align:center}
.bf-dialog-card__rank{margin:0;color:var(--text);font:var(--fw-bold) var(--fs-18)/var(--lh-ui) var(--font-ui)}
.bf-dialog-card__note{margin:0;text-align:center;color:var(--text-2)}
.bf-dialog-card__lines>.bf-dialog-card__note{margin-top:calc(var(--sp-1) / 2)} /* 2px optical nudge under the rank line */
.bf-dialog-card__panel>.bf-dialog-card__note{margin-top:var(--sp-2)}
.bf-dialog-card__panel>.bf-surface--callout{margin:0 0 var(--sp-2);text-align:center}

/* Actions: a full-width stack sp-2 apart; __row puts two equal buttons side by side. */
.bf-dialog-card__actions{display:flex;flex-direction:column;gap:var(--sp-2)}
.bf-dialog-card__row{display:flex;gap:var(--sp-2)}
.bf-dialog-card__row>*{flex:1 1 0;min-width:0}

@media (max-height:700px){ /* short phones (375x667) and landscape phones, as the build; the score shrinks in DisplayNumeral */
  .bf-dialog-card__panel{padding:var(--sp-4)}
  .bf-dialog-card__tags{margin:var(--sp-2) 0}
}
/* ---------- Toast ---------- */
/* replaces: #toasts, .toast-host, .toast, .toast.is-alert, .toast.is-leaving, @keyframes toast-in (app.css:213-220),
   the landscape-phone #toasts, .toast-host shift (app.css:302); .install-tip, .install-tip p, .install-actions,
   .install-tip.is-ios::after, .install-tip.is-leaving, @keyframes install-in (sheets.css:148-159), the landscape
   .install-tip and .install-tip.is-ios::after placements (sheets.css:160-166).
   .install-actions button, .install-actions .install-go (sheets.css:152-153), drawn by Button (pill, pill primary).
   Composes, never restyles: Button (.bf-btn--pill, .bf-btn--primary), Icon (.bf-ico--sm). */

/* Host: the page's polite stack under the top bar, and one inside each dialog. Never takes a tap. */
.bf-toast-host{position:fixed;z-index:var(--z-toast);left:50%;transform:translateX(-50%);top:calc(var(--sat) + var(--topbar-h) + var(--sp-6) + var(--sp-1));width:var(--content-w);display:flex;flex-direction:column;align-items:center;gap:var(--sp-2);margin:0;padding:0;pointer-events:none} /* top: the build's sat + 72px, under the top bar and the LIVE chip */
/* Dock host: the coach mark floats sp-3 above the dock. --dock-total is the page's dock geometry; the fallback is its formula in tokens. */
.bf-toast-host--dock{top:auto;bottom:calc(var(--dock-total, calc(var(--sp-2) + var(--dock-h) + max(var(--sp-3), var(--sab)))) + var(--sp-3))}

/* Status toast: a short message on the highest night, shrink-wrapped and centered. t-body-sm. */
.bf-toast{max-width:100%;margin:0;padding:var(--sp-3) var(--sp-4);border-radius:var(--r-btn);background:var(--surface-2);border:var(--hairline) solid var(--plate-line);box-shadow:var(--shadow-2);color:var(--text);font:var(--fw-medium) var(--fs-15)/var(--lh-20) var(--font-ui);text-align:center;pointer-events:none}
/* Alert: only when the guest's own action failed; the role and the blood edge always travel together. */
.bf-toast[role="alert"]{border-color:var(--danger)}
/* Leaving: fade out over dur-fast, then the page removes the node. */
.bf-toast.is-leaving{opacity:0;transition:opacity var(--dur-fast) var(--ease-in)}

/* Coach mark: a one-time tip with actions (Add to Home Screen). Moonlit edge, card radius, takes taps. */
.bf-toast--coach{position:relative;width:100%;border-radius:var(--r-card);border-color:var(--lilac-300);text-align:start;pointer-events:auto}
.bf-toast__body{display:flex;align-items:flex-start;gap:var(--sp-2);margin:0 0 var(--sp-2)}
/* Coach icon: the share glyph lit in moonlight, the coach edge's light (the build's .install-tip .ico lilac, sheets.css:150). */
.bf-toast__icon{color:var(--lilac-300)}
.bf-toast__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--sp-2)}
/* Arrow: iOS only, points down at Safari's Share button. A square turned 45 degrees under the bottom edge. */
.bf-toast--arrow::after{content:"";position:absolute;left:50%;bottom:calc(-1 * var(--sp-2));width:var(--sp-4);height:var(--sp-4);background:var(--surface-2);border-right:var(--hairline) solid var(--lilac-300);border-bottom:var(--hairline) solid var(--lilac-300);transform:translateX(-50%) rotate(45deg)}

/* Landscape phones: the dock is a right-hand rail. Toasts center on the stage beside it; the coach mark moves under the top bar. */
@media (orientation:landscape) and (max-height:500px){ /* 500px: the build's landscape-phone breakpoint */
  .bf-toast-host{left:calc(50% - var(--rail-w) / 2)}
  .bf-toast-host--dock{left:auto;right:calc(var(--rail-w) + var(--sp-4) + var(--sar));top:calc(var(--sat) + var(--topbar-h) + var(--sp-3));bottom:auto;transform:none;width:min(var(--content-w), 50%)}
  .bf-toast--arrow::after{display:none}
}
/* Landscape tablets and desktop: the dock floats sp-6 off the bottom; the coach mark clears it by sp-4. */
@media (orientation:landscape) and (min-height:501px){ /* 501px: the build's tablet breakpoint, just past the phone one */
  .bf-toast-host--dock{bottom:calc(var(--sp-6) + var(--dock-h) + var(--sp-4) + var(--sab))}
}

/* Entry: toasts drop in from above; the coach mark rises. Calm and reduced motion: they simply appear. */
@media (prefers-reduced-motion: no-preference){
  @keyframes bf-toast-in{from{opacity:0;transform:translateY(calc(-1 * var(--sp-2)))}}
  @keyframes bf-toast-rise{from{opacity:0;transform:translateY(var(--sp-3))}}
  .bf-toast{animation:bf-toast-in var(--dur-ui) var(--ease-out)}
  .bf-toast--coach{animation:bf-toast-rise var(--dur-rise) var(--ease-out)}
}
/* ---------- StageBanner ---------- */
/* replaces: #liveBanner, #liveBanner p, #liveBannerSub, #liveBannerSub:empty (app.css:163-166);
   .sd-card, .sd-card.is-open, .sd-text, .sd-kicker, .sd-card.is-open .sd-kicker, .sd-msg (extras.css:13-18),
   the narrow-portrait .sd-kicker rule (extras.css:22), html.compact .sd-rest (extras.css:25);
   .witch-banner, .witch-banner.is-on, .witch-banner[hidden], .witch-moon, html[data-calm] .witch-banner (extras.css:27-31),
   html.arena-open .witch-banner (extras.css:33); .sd-join, .sd-card.is-open .sd-join (extras.css:19-20), drawn by Button (live, open).
   Composes, never restyles: Button (.bf-btn--live, .bf-btn--open) for Join. */

/* One card for every message laid on the stage art: content width, r-card, plate glass, hairline edge.
   The live and Witching cards never take a tap. */
.bf-stage-banner{display:flex;align-items:center;justify-content:center;gap:var(--sp-2);width:var(--content-w);margin-inline:auto;padding:var(--sp-2) var(--sp-3);border-radius:var(--r-card);background:var(--plate);border:var(--hairline) solid var(--plate-line);color:var(--text);font:var(--fw-semibold) var(--fs-14)/var(--lh-20) var(--font-ui);text-align:center;pointer-events:none}
.bf-stage-banner__text{flex:0 1 auto;min-width:0;margin:0}
.bf-stage-banner__title{display:block}
.bf-stage-banner__sub{display:block;color:var(--text-2);font-weight:var(--fw-medium)}
.bf-stage-banner__sub:empty{display:none}

/* Live: the party is on; spectral edge. */
.bf-stage-banner--live{border-color:var(--live-line)}

/* Showdown: kicker + message on the left, the Join button on the right. Spectral while it is coming up.
   The card takes taps (as built), so a near miss on Join never reaches the art behind it. */
:is(.bf-stage-banner--showdown,.bf-stage-banner--showdown-open){gap:var(--sp-3);padding:var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-4);border-color:var(--live-line);box-shadow:var(--glow-spectral);line-height:var(--lh-18);text-align:start;pointer-events:auto}
:is(.bf-stage-banner--showdown,.bf-stage-banner--showdown-open) .bf-stage-banner__text{flex:1 1 0}
/* Kicker: the t-kicker recipe (13/18 bold caps, track-caps), in the card's light. */
.bf-stage-banner__kicker{display:block;font:var(--fw-bold) var(--fs-13)/var(--lh-18) var(--font-ui);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--live)}
.bf-stage-banner__msg{display:block;font-variant-numeric:tabular-nums}
.bf-stage-banner__action{flex:none;max-width:44%}
/* Showdown open (late join): candle replaces spectral on the edge, the glow and the kicker. Pair the button with .bf-btn--open. */
.bf-stage-banner--showdown-open{border-color:var(--candle-300);box-shadow:var(--glow-candle)}
.bf-stage-banner--showdown-open .bf-stage-banner__kicker{color:var(--candle-300)}
/* Narrow portrait phones drop the kicker (the message already names the Showdown); compact drops the message's second sentence. */
@media (orientation:portrait) and (max-width:430px){.bf-stage-banner__kicker{display:none}} /* 430px: the widest phone in portrait (as built) */
html.compact .bf-stage-banner__rest{display:none}

/* Witching: a reward moment over moving art. Near-opaque night, gold edge and glow, bold 16/22. Fades in with .is-on. */
.bf-stage-banner--witching{padding:var(--sp-3) var(--sp-4);background:var(--plate-solid);border-color:var(--reward);box-shadow:var(--glow-gold);font-weight:var(--fw-bold);font-size:var(--fs-16);line-height:var(--lh-22);opacity:0;transition:opacity var(--dur-reveal) var(--ease-out)}
.bf-stage-banner--witching.is-on{opacity:1}
/* Calm keeps this fade (shortened); it is the only sign the banner is leaving. */
html[data-calm] .bf-stage-banner--witching{transition-duration:var(--dur-ui)!important}
/* The Arena owns the screen while a run is on; the banner waits. */
html.arena-open .bf-stage-banner--witching{visibility:hidden}
.bf-stage-banner__emoji{flex:none;font-size:var(--fs-20);line-height:var(--lh-solid)}
/* ---------- Takeover ---------- */
/* replaces: html.arena-open #takeover (app.css:197); #takeover, #takeover.is-on, #takeover .tk-dim (app.css:200-202);
   the layout half of #takeover .tk-num, #takeover .tk-call (app.css:203, 205: grid cell, padding, display);
   #takeover[data-mode="gates"|"banner"] .tk-num/.tk-call/.tk-dim (app.css:206-210).
   The numeral and call type recipes are DisplayNumeral's (.bf-numeral--takeover, .bf-numeral--callout);
   __num and __call repeat those values exactly, so the takeover reads as the same numeral system. */

/* The whole screen for a moment: the final minute (count) and the gates opening (gates, banner).
   Never interactive; screen readers hear the moment through a separate polite region. */
.bf-takeover{position:fixed;inset:0;z-index:var(--z-takeover);display:grid;place-items:center;pointer-events:none;opacity:0;visibility:hidden;transition:opacity var(--dur-ui) var(--ease-out),visibility 0s linear var(--dur-ui)}
.bf-takeover.is-on{opacity:1;visibility:visible;transition:opacity var(--dur-ui) var(--ease-out)}
/* The Arena owns the screen while it is open; the moment waits for the run. */
html.arena-open .bf-takeover{visibility:hidden}

/* Dim: the stage under the numerals, one scrim, in every mode. */
.bf-takeover__dim{position:absolute;inset:0;background:var(--scrim)}

/* Numeral and call share one grid cell, so either one sits dead center. Creepster on the dimmed art:
   the DisplayNumeral base (display font, regular, track-display, tabular figures, text-on-art ground). */
.bf-takeover__num,.bf-takeover__call{position:relative;grid-area:1/1;margin:0;font-family:var(--font-display);font-style:normal;font-weight:var(--fw-regular);letter-spacing:var(--track-display);font-variant-numeric:tabular-nums;text-align:center}
/* Count (default): 60 to 1, = .bf-numeral--takeover (t-numeral-xl, spectral). */
.bf-takeover__num{font-size:var(--fs-120);line-height:var(--lh-solid);color:var(--live);text-shadow:var(--text-on-art),var(--glow-spectral)}
/* Call: THE GATES ARE OPEN, = .bf-numeral--callout (t-callout, candle). */
.bf-takeover__call{display:none;padding:0 var(--gutter);font-size:var(--fs-44);line-height:var(--lh-tight);color:var(--candle-300);text-shadow:var(--text-on-art),var(--glow-candle)}

/* Gates and banner: the call replaces the numeral; the scrim stays. */
:is(.bf-takeover--gates,.bf-takeover--banner) .bf-takeover__num{display:none}
:is(.bf-takeover--gates,.bf-takeover--banner) .bf-takeover__call{display:block}
/* Banner (a late arrival): the call at the t-live size. */
.bf-takeover--banner .bf-takeover__call{font-size:var(--fs-32)}
/* ---------- InlineState ---------- */
/* replaces: .sheet-loading (app.css:235); .board-empty, .board-state, .board-state p, .board-stale (sheets.css:62-64);
   .board-row.is-skel, .board-row.is-skel > *, .board-row.is-skel .board-rank, .skel-bar, .skel-bar.is-short,
   @keyframes board-skel (sheets.css:72-80); .hype-status, .hype-status[data-kind="success"], .hype-status[data-kind="fail"]
   (sheets.css:105-107); .recap-empty (sheets.css:180); #ticker, #netStatus, #ticker:empty (index.html:138-139);
   #netStatus, #netStatus:not([hidden]) ~ #ticker (app.css:171, 177); .board-retry, .board-retry.is-inline (sheets.css:65-66) and
   .board-retry:hover (sheets.css:27), drawn by Button's pill (Retry at full hit size; the short inline Retry is retired).
   Composes, never restyles: Leaderboard (.bf-leaderboard__row, the skeleton row), Button (.bf-btn--pill, Retry),
   Tag (.bf-dot--warn, the network dot). */

/* Base: a quiet line where content will be (loading "Summoning…"), or is not (empty). Mist, centered, t-body. */
.bf-inline-state{margin:var(--sp-4) 0;color:var(--text-2);font:var(--fw-medium) var(--fs-16)/var(--lh-body) var(--font-ui);text-align:center}
.bf-inline-state__text{margin:0}

/* Skeleton: placeholder rows drawn on the Leaderboard row itself (li.bf-leaderboard__row.bf-inline-state__skel-row),
   so its grid, height, padding and fill match exactly. The parts take explicit columns: medal cell, avatar-to-combo bar, score bar. */
.bf-inline-state__skeleton{display:flex;flex-direction:column;gap:var(--sp-1);margin:0;padding:0;list-style:none}
.bf-inline-state__text + .bf-inline-state__skeleton{margin-top:var(--sp-3)}
.bf-inline-state__skel-dot{grid-column:1;justify-self:center;width:var(--sp-8);height:var(--sp-8);border-radius:50%;background:var(--surface-2)} /* sp-8 = 32px, the sheet-density medal (Medal --s) */
.bf-inline-state__skel-bar,.bf-inline-state__skel-end{display:block;height:var(--sp-3);border-radius:var(--r-chip);background:var(--surface-2)}
.bf-inline-state__skel-bar{grid-column:2 / -2}
.bf-inline-state__skel-end{grid-column:-2;justify-self:end;width:calc(var(--sp-8) + var(--sp-2))} /* 40px: the score column's minimum */

/* Error: content could not load and nothing is cached. Not the guest's doing, so it stays mist; Retry sits under it. */
.bf-inline-state--error{display:flex;flex-direction:column;align-items:center;gap:var(--sp-2)}

/* Stale: the board shows cached rows; a callout above them says so, Retry at the end of the line. t-note. */
.bf-inline-state--stale{display:flex;align-items:center;gap:var(--sp-2);margin:0 0 var(--sp-3);padding:var(--sp-2) var(--sp-3);border-radius:var(--r-row);background:var(--surface-2);font-size:var(--fs-14);line-height:var(--lh-20);text-align:start}
.bf-inline-state--stale .bf-inline-state__text{flex:1 1 0;min-width:0}
.bf-inline-state--stale .bf-inline-state__action{flex:none}

/* Result: the line under an action that says how it went (the hype vote). It keeps one line's height while empty,
   so nothing jumps when the words land. t-body-sm. Done is plain text; failed is danger (the guest's own action). */
.bf-inline-state--result{min-height:var(--lh-20);margin:0 0 var(--sp-3);font-size:var(--fs-15);line-height:var(--lh-20)}
.bf-inline-state--result.is-done{color:var(--text)}
.bf-inline-state--result.is-failed{color:var(--danger)}

/* Stage lines (on the painting): offline / reconnecting with a warn dot, and the rotating ticker. One t-note line on a plate pill. */
:is(.bf-inline-state--offline,.bf-inline-state--ticker){width:fit-content;max-width:100%;margin:0 auto;padding:var(--sp-1) var(--sp-3);border-radius:var(--r-chip);background:var(--plate);border:var(--hairline) solid var(--plate-line);font-size:var(--fs-14);line-height:var(--lh-20);white-space:nowrap;pointer-events:none}
.bf-inline-state--offline{display:flex;align-items:center;gap:var(--sp-2)}
.bf-inline-state--offline .bf-inline-state__text{min-width:0;overflow:hidden;text-overflow:ellipsis}
.bf-inline-state--ticker{display:block;overflow:hidden;text-overflow:ellipsis}
.bf-inline-state--ticker:empty{visibility:hidden}
/* They share one slot: while the network line shows, the ticker steps aside. */
.bf-inline-state--offline:not([hidden]) ~ .bf-inline-state--ticker{visibility:hidden}

@media (prefers-reduced-motion: no-preference){
  @keyframes bf-inline-state-pulse{50%{opacity:var(--opacity-skeleton)}}
  .bf-inline-state__skel-row{animation:bf-inline-state-pulse var(--dur-pulse) var(--ease-loop) infinite}
}
/* ---------- Meter ---------- */
/* replaces: the bar of .chip-fill (app.css:180), #hypeChip .hype-fill (sheets.css:83; hype.js:64), #goalChip.is-smashed .chip-fill (scene.css:124),
   .goal-bar, .goal-bar i (scene.css:126-127), .bag-meter, .bag-meter i (extras.css:37-38),
   .hype-meter, .hype-meter i (sheets.css:109-110), .tv-goal-bar, .tv-goal-bar i (extras.css:190-191),
   the bar of .gg-hp, .gg-hp i, .gg-boss.is-cracked .gg-hp i (game.css:89-91), the bar of #comboBar (game.css:56),
   the bar of .hud-timebar, #arena[data-phase="midnight"] / [data-phase="witching"] .hud-timebar (game.css:58-60);
   party dashboard .unlock-bar, .unlock-bar i (dashboard/dashboard.css:161-162; dashboard/log/log.css:11-12),
   .meter, .meter i, .meter.is-goal i (dashboard/dashboard.css:204-206).
   Placement stays with the host: PlatePill .bf-plate-pill__meter, ArenaHud __combo-meter / __timebar, Ghost __hp, TvPanel __goal. */

/* Track. --bf-meter-h is the bar height (the size class sets it); --p (0-1) is the value, set by the page on the meter,
   the fill or a host such as Big Boo (it inherits). */
.bf-meter{--bf-meter-h:var(--sp-2);--bf-meter-fill:linear-gradient(to right,var(--slime-700),var(--slime-400));position:relative;display:block;height:var(--bf-meter-h);border-radius:var(--r-chip);background:var(--surface-2);overflow:hidden}
.bf-meter__fill{display:block;width:100%;height:100%;border-radius:inherit;background:var(--bf-meter-fill);transform:scaleX(clamp(0,var(--p,0),1));transform-origin:left center}

/* Sizes */
.bf-meter--xs{--bf-meter-h:var(--meter-xs)}
.bf-meter--sm{--bf-meter-h:var(--sp-2)}
.bf-meter--md{--bf-meter-h:var(--sp-3)}
.bf-meter--tv{--bf-meter-h:clamp(10px,calc(1.4 * var(--bf-tv-vh,1vh)),22px)} /* TV geometry (extras.css:190): 1.4% of the TvPanel screen, held between 10 and 22px */

/* Tracks: default surface-2 (in sheets); plate on the painting, the playfield and the TV; bare inside a host that is the track (stat chip, HUD). */
.bf-meter--plate{background:var(--night-950);border:var(--hairline) solid var(--plate-line)}
.bf-meter--bare{background:transparent}

/* Tones (light language): spectral = live and game, reward = gold, candle = you and action, hype = Gourdon's scale, danger = blood. */
.bf-meter--spectral{--bf-meter-fill:linear-gradient(to right,var(--slime-700),var(--slime-400))}
.bf-meter--reward{--bf-meter-fill:linear-gradient(to right,var(--candle-300),var(--gold-400))}
.bf-meter--candle{--bf-meter-fill:var(--pumpkin-500)}
.bf-meter--hype{--bf-meter-fill:linear-gradient(to right,var(--slime-700),var(--slime-400) 45%,var(--candle-300) 75%,var(--pumpkin-500))}
.bf-meter--danger{--bf-meter-fill:var(--blood-400)}
/* Big Boo at half health: the HP bar cracks. A stroke-bold bloodmoon crack every sp-3 at the build's 115deg (game.css:91). */
.bf-meter--danger.is-cracked,.is-cracked .bf-meter--danger{--bf-meter-fill:repeating-linear-gradient(115deg,var(--blood-400) 0 calc(var(--sp-3) - var(--stroke-bold)),var(--bloodmoon) 0 var(--sp-3))}

/* Motion: fills glide on transform only. Sheets and TV reveal; game HP is quick; per-frame bars are instant. */
@media (prefers-reduced-motion: no-preference){
  .bf-meter__fill{transition:transform var(--dur-reveal) var(--ease-out)}
  .bf-meter--quick .bf-meter__fill{transition-duration:var(--dur-fast)}
}
.bf-meter--instant .bf-meter__fill{transition:none}

/* Forced colors drop gradients and fills: draw the fill in CanvasText and outline the track (an outline keeps the 3px bar's height). */
@media (forced-colors: active){
  .bf-meter:not(.bf-meter--plate){outline:var(--hairline) solid CanvasText}
  .bf-meter__fill{forced-color-adjust:none;background:CanvasText}
}
/* ---------- StatusCircle ---------- */
/* replaces: .goal-check, .goal-ms.is-done .goal-check, .goal-ms.is-done .goal-check::after (scene.css:131-133),
   .bag-ico, .bag-slot.is-found .bag-ico (extras.css:43-44) */

/* Pending: a moon ring in stroke-bold lilac-300 (5.8-6.4:1 on surface and bg; the build's violet-500 failed 3:1).
   Optional one-character glyph ("?"). --s is the box size. */
.bf-status-circle{--s:22px;position:relative;flex:none;display:inline-grid;place-items:center;width:var(--s);height:var(--s);border-radius:50%;border:var(--stroke-bold) solid var(--lilac-300);color:var(--text-2);font:var(--fw-bold) var(--fs-15)/var(--lh-solid) var(--font-ui)} /* 22px: the goal-milestone check (scene.css:131) */
.bf-status-circle--lg{--s:28px} /* 28px: the Haunted Hunt slot (extras.css:43) */

/* Done (spectral) and found (reward): a filled disc with an on-accent check; any glyph is hidden. */
.bf-status-circle.is-done{background:var(--slime-400);border-color:var(--slime-400);color:transparent}
.bf-status-circle.is-found{background:var(--reward);border-color:var(--reward);color:transparent}
.bf-status-circle.is-done::after,.bf-status-circle.is-found::after{content:"";position:absolute;inset:0;margin:auto;width:56%;height:28%;border:solid var(--on-accent);border-width:0 0 var(--stroke-bold) var(--stroke-bold);transform:translateY(calc(-1 * var(--hairline))) rotate(-45deg)} /* the build's 10x5 check, as % of the 18px inner box so it scales to 28px; lifted one hairline */
/* ---------- Medal ---------- */
/* replaces: .board-num (sheets.css:49), .board-medal, .board-medal.m1/.m2/.m3, .board-list.has-sprite .board-medal (sheets.css:51-55),
   medalStyle() inline sprite style (boards.js:279-283), .tv-medal, .tv-rank, .tv-medal.m1/.m2/.m3 (extras.css:177-182);
   party dashboard .medal, .medal.m2, .medal.m3, .row.r1 .medal, .rank-num (dashboard/dashboard.css:91-95) */

/* Fallback disc: solid metal, on-accent numeral (13.4 / 12.1 / 6.3:1), a stroke-bold shade line inside the bottom edge. --s is the box size. */
.bf-medal{--s:32px;flex:none;display:inline-grid;place-items:center;width:var(--s);height:var(--s);border-radius:50%;color:var(--on-accent);font:var(--fw-bold) var(--fs-14)/var(--lh-solid) var(--font-ui);font-variant-numeric:tabular-nums;box-shadow:inset 0 calc(-1 * var(--stroke-bold)) 0 var(--backdrop)} /* 32px: the board's rank column and sprite medal (sheets.css:40, 55) */
.bf-medal--1{background-color:var(--gold-400)}
.bf-medal--2{background-color:var(--silver-300)}
.bf-medal--3{background-color:var(--bronze-400)}

/* Rank 4 and below: the number alone, mist, in the same box so numbers and medals share one center. */
.bf-medal--num{color:var(--text-2);font-weight:var(--fw-semibold);font-size:var(--fs-16);box-shadow:none}

/* TV: the box scales with the Leaderboard TV row font, 1.7em at .8em as built; the numeral never drops below fs-14. */
.bf-medal--tv{--s:1.7em;font-size:max(var(--fs-14),.8em)} /* TV row geometry (extras.css:177) */

/* The medal: the painted sprite (384x128, three 128px cells: 1, 2, 3). The numeral stays in the DOM as the accessible name. */
.bf-medal--sprite{border-radius:0;color:transparent;background:var(--bf-medal-sprite) 0 0/300% 100% no-repeat;box-shadow:none} /* 300%: three cells side by side; radius 0 keeps the stem and leaves */
.bf-medal--sprite.bf-medal--2{background-position:50% 0}
.bf-medal--sprite.bf-medal--3{background-position:100% 0}

/* Forced colors: the disc loses its fill, so it gets an edge; the sprite keeps its art and its hidden numeral. */
@media (forced-colors: active){
  .bf-medal:is(.bf-medal--1,.bf-medal--2,.bf-medal--3):not(.bf-medal--sprite){border:var(--hairline) solid}
  .bf-medal--sprite{forced-color-adjust:none}
}
/* ---------- Leaderboard ---------- */
/* replaces: .board-list (sheets.css:39), .board-row, .board-row.is-you (sheets.css:40-41),
   .board-rank, .board-av, .board-name, .board-nm, .board-tagsfx, .board-combo, .board-score (sheets.css:42-48),
   .board-mark (sheets.css:57), .board-list.is-compact .board-row, .board-list.is-compact .board-combo (sheets.css:60-61),
   .board-you, .board-you p, .board-you.is-none (sheets.css:68-70), .board-row.is-top (boards.js:268, never styled: dropped),
   .tv-board, .tv-row, .tv-row:nth-child(odd), .tv-row.is-first, .tv-board.is-short .tv-row (extras.css:172-176),
   .tv-av (tv.js:186, never styled), .tv-name, .tv-marks, .tv-score (extras.css:183-185).
   Composes, never restyles: Medal (.bf-medal), Tag (.bf-tag--sm), Surface (.bf-surface--raised.bf-surface--pinned),
   InlineState (loading skeleton, empty, error, stale). */

/* The list: rows sp-1 apart. */
.bf-leaderboard{display:flex;flex-direction:column;gap:var(--sp-1);margin:0;padding:0;list-style:none}

/* One grid for every density: rank · avatar · name · combo · score. t-body type, as the sheet body sets it. */
.bf-leaderboard__row{display:grid;grid-template-columns:var(--sp-8) var(--sp-6) minmax(0,1fr) auto minmax(calc(var(--sp-8) + var(--sp-2)),auto);grid-template-areas:"rank av name combo score";align-items:center;column-gap:var(--sp-2);min-height:var(--sp-12);padding:var(--sp-1) var(--sp-3) var(--sp-1) var(--sp-2);border:var(--hairline) solid transparent;border-radius:var(--r-row);background:var(--bg);color:var(--text);font:var(--fw-medium) var(--fs-16)/var(--lh-body) var(--font-ui)}
.bf-leaderboard__rank{grid-area:rank;display:flex;justify-content:center}
.bf-leaderboard__av{grid-area:av;font-size:var(--fs-20);line-height:var(--lh-solid);text-align:center}
.bf-leaderboard__name{grid-area:name;display:flex;align-items:center;gap:var(--sp-2);min-width:0}
.bf-leaderboard__nm{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:var(--fw-semibold)}
.bf-leaderboard__suffix{color:var(--text-2);font-weight:var(--fw-medium)}
/* Marks, always in this order: Showdown winner, Lantern mode, Witching Hour run, Golden Spirit. */
.bf-leaderboard__marks{flex:none;display:inline-flex;gap:var(--sp-1);font-size:var(--fs-14);line-height:var(--lh-solid)}
.bf-leaderboard__combo{grid-area:combo;color:var(--text-2);font-size:var(--fs-14)}
.bf-leaderboard__score{grid-area:score;text-align:right;font:var(--fw-bold) var(--fs-18)/var(--lh-solid) var(--font-ui);font-variant-numeric:tabular-nums}

/* Your row: the your-pick rule. */
.bf-leaderboard__row.is-you{border-color:var(--you);background:var(--you-wash)}

/* Compact (Recap top 3): no combo column. */
.bf-leaderboard--compact .bf-leaderboard__row{grid-template-columns:var(--sp-8) var(--sp-6) minmax(0,1fr) minmax(calc(var(--sp-8) + var(--sp-2)),auto);grid-template-areas:"rank av name score"}
.bf-leaderboard--compact .bf-leaderboard__combo{display:none}

/* TV: the row font grows with the screen and every length is in em of it (TV geometry, extras.css:172-176).
   --bf-tv-vw / --bf-tv-vh come from TvPanel (1cqw / 1cqh of the screen); outside it they fall back to 1vw / 1vh. */
.bf-leaderboard--tv{gap:calc(.35 * var(--bf-tv-vh,1vh))}
.bf-leaderboard--tv .bf-leaderboard__row{grid-template-columns:2.2em 1.6em minmax(0,1fr) auto;grid-template-areas:"rank av name score";column-gap:.6em;min-height:0;padding:.1em .5em;background:transparent;font:var(--fw-semibold) clamp(var(--fs-16),calc(1.8 * var(--bf-tv-vw,1vw)),var(--fs-36))/var(--lh-heading) var(--font-ui)} /* em geometry as built; 1.8% of the screen (34.6px at 1920) and .1em padding so ten rows fit the panel at 1920x1080 */
.bf-leaderboard--tv.bf-leaderboard--short .bf-leaderboard__row{padding-block:.2em;font-size:clamp(var(--fs-16),calc(2.3 * var(--bf-tv-vw,1vw)),var(--fs-44))} /* five rows (Showdown standings), .2em as built */
.bf-leaderboard--tv .bf-leaderboard__row:nth-child(odd){background:var(--surface-2)}
.bf-leaderboard--tv .bf-leaderboard__row.is-first{border-color:var(--reward);background:var(--reward-wash)}
.bf-leaderboard--tv .bf-leaderboard__av{font-size:1em}
.bf-leaderboard--tv .bf-leaderboard__marks{font-size:.8em} /* the TV marks' .8em (extras.css:184) */
.bf-leaderboard--tv .bf-leaderboard__score{font-size:1em;line-height:inherit}

/* Pinned You: Surface draws it (raised + pinned: surface-2, you edge, sticky foot, shadow-pinned); this sets its place and weight. */
.bf-leaderboard__you{margin:var(--sp-3) 0 0;font-weight:var(--fw-semibold)}
.bf-leaderboard__you>p{margin:0}
.bf-leaderboard__you.is-none{border-color:var(--line);color:var(--text-2);font-weight:var(--fw-medium)}
/* ---------- ListRow ---------- */
/* replaces: .sheet-body .bag-list, .bag-slot, .bag-slot.is-found, .bag-txt, .bag-txt b, .bag-txt small, .bag-slot.is-found .bag-txt small (extras.css:40-42, 45-48),
   .sheet-body .goal-list, .goal-ms, .goal-ms.is-done (scene.css:128-130),
   .recap-stats, .recap-stat, .recap-stat.is-champ, .recap-ico (sheets.css:181-184),
   .recap-list, .recap-list li, .recap-list .num (sheets.css:186-188),
   .party-facts, .party-facts li, .party-facts li::before (sheets.css:115-117).
   Composes, never restyles: StatusCircle (.bf-status-circle), Button (.bf-btn--pill), StatusDot (.bf-dot--bullet).
   Composed by: Sheet (the invitation's facts). */

/* The list (ul or ol). */
.bf-list-row__group{display:flex;flex-direction:column;margin:0 0 var(--sp-3);padding:0;list-style:none}

/* Ruled row (default): one line of a sheet list, hairline rule under it, at least the 44px hit height. t-body type. */
.bf-list-row{display:flex;align-items:center;gap:var(--sp-3);min-height:var(--hit);padding:var(--sp-2) 0;border-bottom:var(--hairline) solid var(--line);color:var(--text);font:var(--fw-medium) var(--fs-16)/var(--lh-body) var(--font-ui)}

/* Boxed row: its own night block with a line edge. Boxed rows stack sp-2 apart. */
.bf-list-row--boxed{padding:var(--sp-3);border:var(--hairline) solid var(--line);border-radius:var(--r-row);background:var(--bg)}
.bf-list-row--boxed + .bf-list-row--boxed{margin-top:var(--sp-2)}

/* Parts */
.bf-list-row__icon{flex:none;width:var(--sp-6);font-size:var(--fs-20);line-height:var(--lh-solid);text-align:center}
.bf-list-row__text{flex:1 1 auto;min-width:0}
.bf-list-row__title,.bf-list-row__sub{display:block;line-height:var(--lh-heading)}
.bf-list-row__title{color:var(--text);font-size:var(--fs-15);font-weight:var(--fw-semibold)}
.bf-list-row__sub{color:var(--text-2);font-size:var(--fs-14)}
.bf-list-row__value{flex:none;margin-left:auto;font-weight:var(--fw-bold);font-variant-numeric:tabular-nums}
.bf-list-row__action{flex:none}

/* Status: StatusCircle + text (+ action). Pending words are mist; done or found turn bone. */
.bf-list-row--status .bf-list-row__text{color:var(--text-2)}
.bf-list-row--status.is-done .bf-list-row__text,.bf-list-row--status.is-found .bf-list-row__text{color:var(--text)}
.bf-list-row--status.is-found .bf-list-row__sub{color:var(--reward)}
/* Boxed with an action: 56px (hit + sp-3), the 44px pill sits sp-1 inside the edge. */
.bf-list-row--boxed.bf-list-row--status{min-height:calc(var(--hit) + var(--sp-3));padding:var(--sp-1) var(--sp-1) var(--sp-1) var(--sp-3)}

/* Stat: icon + a sentence that carries the number. */
.bf-list-row--stat{font-weight:var(--fw-semibold)}

/* Fact: a pumpkin bullet on the first line's baseline + one line of the invitation. */
.bf-list-row--fact{align-items:baseline;min-height:0;padding:var(--sp-3) 0 var(--sp-3) var(--sp-1)}

/* Gold is for rewards only. Champion stat: the reward edge. A found secret: reward edge + reward-wash. */
.bf-list-row--boxed.bf-list-row--reward{border-color:var(--reward)}
.bf-list-row--boxed.is-found{border-color:var(--reward);background:var(--reward-wash)}
/* ---------- TvPanel ---------- */
/* replaces: #tv, #tv[hidden] (extras.css:146-147), .tv-scrim (148), .tv-top, .tv-top [hidden] (149-150),
   .tv-live, .tv-online, .tv-preview sizing and .tv-live::before (151-155; look: Tag), .tv-stage (156),
   .tv-panel, .tv-panel.is-on, html[data-calm] .tv-panel, .tv-panel p/h2/ol (157-160), .tv-kicker (161), .tv-sub, .tv-top1 (164-165),
   .tv-digits, .tv-cell, .tv-cell i (167-168, 170), .tv-h (171), .tv-winner, .tv-next, .tv-delta (186-188), .tv-goal (189),
   .tv-qr, .tv-qr img, .tv-qr p, .tv-scan, .tv-url (192-196), .tv-foot, .tv-dots, .tv-dots i, .tv-dots i.is-on (197-200),
   .tv-wake sizing, html.tv .tv-wake, .tv-wake[hidden] (201-203; look: Button outline-live), the portrait block and .tv-clock (205-215), all extras.css.
   Composes, never restyles: Tag (--live, --plate, --preview, .bf-dot--live), DisplayNumeral (.bf-numeral, --gold: the title, big
   number and countdown recipe), Leaderboard (--tv, --short), Meter (--tv --plate --spectral), Button (--outline-live).
   TV sizes are clamp(token, n x screen width, max): each max is n% of a 1920-wide screen, commented where it is not a token. */

/* The screen: fixed over the stage art and a size container, so --bf-tv-vw/--bf-tv-vh are 1% of the screen (1vw/1vh without container units).
   It takes no pointer events, so the page hides the cursor (html.tv); the wake button brings it back. */
.bf-tv-panel{--bf-tv-vw:1vw;--bf-tv-vh:1vh;position:fixed;inset:0;z-index:var(--z-chrome);overflow:hidden;color:var(--text);font-family:var(--font-ui);pointer-events:none;container:bf-tv / size}
@supports (container-type:size){.bf-tv-panel{--bf-tv-vw:1cqw;--bf-tv-vh:1cqh}}

/* Scrim: plate to backdrop across the left half (under the panels), scrim along the bottom (under the foot). */
.bf-tv-panel__scrim{position:absolute;inset:0;background:linear-gradient(to right,var(--plate) 0,var(--backdrop) 46%,transparent 66%),linear-gradient(to top,var(--scrim) 0,transparent 30%)}

/* Top: the live, presence and preview Tags, scaled for the room. */
.bf-tv-panel__top{position:absolute;top:calc(3 * var(--bf-tv-vh) + var(--sat));left:calc(4 * var(--bf-tv-vw));right:calc(4 * var(--bf-tv-vw));display:flex;align-items:center;gap:calc(1.2 * var(--bf-tv-vw))}
.bf-tv-panel__top .bf-tag{height:auto;gap:.6em;padding:.35em .9em;font-size:clamp(var(--fs-14),calc(1.5 * var(--bf-tv-vw)),30px);font-weight:var(--fw-bold);line-height:var(--lh-btn)} /* 30px = 1.5% at 1920; em padding as built (extras.css:151) */
.bf-tv-panel__top .bf-dot{width:.6em;height:.6em} /* the dot grows with the tag (extras.css:153) */
.bf-tv-panel__top .bf-tag--preview{margin-left:auto}

/* Stage: the left column; the cards stack in it and crossfade. */
.bf-tv-panel__stage{position:absolute;top:calc(18 * var(--bf-tv-vh));bottom:calc(11 * var(--bf-tv-vh));left:calc(4 * var(--bf-tv-vw));width:min(calc(46 * var(--bf-tv-vw)),900px)} /* 900px: the column cap (extras.css:156) */
.bf-tv-panel__card{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;justify-content:safe center;overflow:hidden;padding:min(calc(3 * var(--bf-tv-vh)),var(--sp-8)) min(calc(3 * var(--bf-tv-vw)),var(--sp-12));border:var(--hairline) solid var(--plate-line);border-radius:clamp(var(--r-card),calc(1.6 * var(--bf-tv-vw)),calc(1.6 * var(--r-card)));background:var(--plate);box-shadow:var(--shadow-2);transition:opacity var(--dur-reveal) var(--ease-out)}
.bf-tv-panel__card[aria-hidden="true"]{opacity:0}
html[data-calm] .bf-tv-panel__card{transition-duration:var(--dur-ui)!important} /* Calm keeps the crossfade, shorter (extras.css:159) */

/* Card text. Margins in em of each line, as built. */
.bf-tv-panel__kicker{margin:0;color:var(--text-2);font:var(--fw-bold) clamp(var(--fs-16),calc(2 * var(--bf-tv-vw)),40px)/var(--lh-btn) var(--font-ui);letter-spacing:var(--track-caps);text-transform:uppercase} /* 40px = 2% at 1920 */
.bf-tv-panel__kicker--live{color:var(--live)}
/* The next-Showdown line under the standings (or under "No scores yet") lifts .8em (extras.css:187). */
:is(.bf-leaderboard,.bf-tv-panel__sub) + .bf-tv-panel__kicker{margin-top:.8em}
.bf-tv-panel__sub{margin:.5em 0 0;color:var(--text);font:var(--fw-semibold) clamp(var(--fs-16),calc(2 * var(--bf-tv-vw)),40px)/var(--lh-ui) var(--font-ui)} /* 40px = 2% at 1920 */
.bf-tv-panel__sub--candle{color:var(--candle-300)}
.bf-tv-panel__sub--reward{color:var(--reward)}
.bf-tv-panel__h{margin:0 0 .5em;color:var(--text);font:var(--fw-bold) clamp(var(--fs-20),calc(2.6 * var(--bf-tv-vw)),var(--fs-44))/var(--lh-btn) var(--font-ui)}

/* Display type: DisplayNumeral (.bf-numeral, + --gold for the Witching title) draws it; the TV sets size and margins.
   Scoped to the screen so these sizes beat .bf-numeral's own. */
.bf-tv-panel .bf-tv-panel__title{margin:.15em 0 .2em;font-size:clamp(var(--fs-32),calc(5.4 * var(--bf-tv-vw)),104px)} /* 104px = 5.4% at 1920; lh-tight from .bf-numeral */
.bf-tv-panel .bf-tv-panel__big{margin:0;font-size:clamp(var(--fs-64),calc(10 * var(--bf-tv-vw)),200px);line-height:var(--lh-solid)} /* 200px = 10% at 1920 */
.bf-tv-panel__digits{display:flex;gap:min(calc(1.6 * var(--bf-tv-vw)),var(--sp-8));margin:.3em 0}
.bf-tv-panel__cell{display:flex;flex-direction:column;align-items:center;min-width:1.2em;font-size:clamp(var(--fs-44),calc(7 * var(--bf-tv-vw)),140px)} /* 140px = 7% at 1920; 1.2em cells (1.6em as built pushed the fourth cell out of the panel) */
.bf-tv-panel .bf-tv-panel__num{font-size:1em;line-height:var(--lh-solid)}
.bf-tv-panel__unit{color:var(--text-2);font:var(--fw-semibold) clamp(var(--fs-14),calc(1.6 * var(--bf-tv-vw)),var(--fs-32))/var(--lh-btn) var(--font-ui)}
.bf-tv-panel__goal{margin-top:calc(2 * var(--bf-tv-vh))}

/* QR card: the site's one light surface. */
.bf-tv-panel__qr{position:absolute;right:calc(3 * var(--bf-tv-vw));bottom:calc(4 * var(--bf-tv-vh) + var(--sab));display:flex;flex-direction:column;align-items:center;gap:var(--sp-2);padding:var(--sp-4) var(--sp-4) var(--sp-3);border-radius:var(--r-card);background:var(--bone-50);color:var(--on-accent);box-shadow:var(--shadow-2)}
.bf-tv-panel__code{display:block;width:220px;height:220px;image-rendering:pixelated} /* the QR's scan size at 1080p (tv.js:10) */
.bf-tv-panel__scan{margin:0;text-align:center;font:var(--fw-bold) clamp(var(--fs-18),calc(1.6 * var(--bf-tv-vw)),var(--fs-32))/var(--lh-btn) var(--font-ui)}
.bf-tv-panel__url{margin:0;text-align:center;font:var(--fw-semibold) clamp(var(--fs-14),calc(.9 * var(--bf-tv-vw)),var(--fs-18))/var(--lh-btn) var(--font-ui)}

/* Foot: panel pager, Pacific clock, and Keep screen awake when the wake lock is refused. */
.bf-tv-panel__foot{position:absolute;left:calc(4 * var(--bf-tv-vw));bottom:calc(3 * var(--bf-tv-vh) + var(--sab));display:flex;align-items:center;gap:calc(1.5 * var(--bf-tv-vw));color:var(--text-2);font:var(--fw-semibold) clamp(var(--fs-14),calc(1.4 * var(--bf-tv-vw)),28px)/var(--lh-solid) var(--font-ui)} /* 28px = 1.4% at 1920 */
.bf-tv-panel__dots{display:flex;gap:var(--sp-2)}
.bf-tv-panel__dot{width:.4em;height:.4em;border-radius:50%;background:var(--lilac-300)} /* .4em: the 10px pager dot at 1920; moon lilac holds 4.3:1 on the foot (plum-700 as built vanished) */
.bf-tv-panel__dot[aria-current="true"]{background:var(--live)}
.bf-tv-panel__clock{white-space:nowrap;font-variant-numeric:tabular-nums}
.bf-tv-panel__wake{pointer-events:auto;background:var(--plate);font-size:clamp(var(--fs-14),calc(1.3 * var(--bf-tv-vw)),26px)} /* 26px = 1.3% at 1920 */
.bf-tv-panel .bf-tv-panel__wake{cursor:pointer} /* beats the page's html.tv * {cursor:none} */

/* Portrait (a phone or a rotated screen): stack the column, center a smaller QR, spread the foot. */
@container bf-tv (max-aspect-ratio: 1/1){
  .bf-tv-panel__scrim{background:linear-gradient(to top,var(--shroud) 0,var(--backdrop) 60%,transparent)}
  .bf-tv-panel__top{left:var(--gutter);right:var(--gutter);flex-wrap:wrap}
  .bf-tv-panel__stage{top:calc(var(--sat) + var(--sp-12) + var(--sp-6));bottom:calc(var(--sab) + 260px);left:var(--gutter);right:var(--gutter);width:auto} /* 260px clears the foot (56px) and the ~195px portrait QR card */
  .bf-tv-panel__card{padding:var(--sp-5)}
  .bf-tv-panel__qr{right:auto;left:50%;bottom:calc(var(--sab) + var(--hit) + var(--sp-3));padding:var(--sp-3) var(--sp-3) var(--sp-2);transform:translateX(-50%)}
  .bf-tv-panel__code{width:120px;height:120px} /* the portrait QR (extras.css:212) */
  .bf-tv-panel__foot{left:var(--gutter);right:var(--gutter);bottom:calc(var(--sab) + var(--sp-3));justify-content:space-between}
}
/* ---------- TitleCard ---------- */
/* replaces: #titleCard (index.html:90 pointer-events, 117), #kicker (index.html:118), #partyTitle, #partyTitle span (index.html:119-120),
   #partyWhen placement (index.html:121; the pill look is Tag), html[data-calm] #partyTitle (css/app.css:50),
   html[data-preview] #kicker (css/app.css:92), html.art-ready #partyTitle and its portrait undo (css/app.css:140, 144),
   html[data-phase="after"] #partyTitle (css/app.css:146), the after/offseason/live/witching kicker and date hides (css/app.css:147-148, 344),
   html.compact #kicker (css/app.css:187), landscape #kicker and #partyTitle type (css/app.css:272-273),
   html.art-portrait #partyTitle type (css/app.css:366, 381), #titleCard transition and html[data-peek] #titleCard (css/scene.css:113-114) */

/* The party's name over the painting: kicker, h1 and date, stacked and centered in the content column. Never interactive. */
.bf-title-card{display:flex;flex-direction:column;align-items:center;gap:var(--sp-2);width:var(--content-w);margin-inline:auto;text-align:center;pointer-events:none;transition:opacity var(--dur-ui) var(--ease-out)}
/* Peek: the stage text steps back so the painting shows. */
html[data-peek] .bf-title-card{opacity:var(--opacity-peek)}

/* Kicker: t-kicker (700 13/18, caps) is small text, so it sits on a plate pill (rule 9). The end padding gives back the trailing track. */
.bf-title-card__kicker{margin:0;padding:var(--sp-1) calc(var(--sp-3) - var(--track-caps)) var(--sp-1) var(--sp-3);border:var(--hairline) solid var(--plate-line);border-radius:var(--r-chip);background:var(--plate);font:var(--fw-bold) var(--fs-13)/var(--lh-18) var(--font-ui);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--text-2)}

/* Title: the page's one h1, t-title amber Creepster straight on the art with text-on-art + candle glow. */
.bf-title-card__title{margin:0;font:var(--fw-regular) var(--fs-36)/40px var(--font-display);letter-spacing:var(--track-display);color:var(--amber-400);text-shadow:var(--text-on-art),var(--glow-candle);transition:opacity var(--dur-title) var(--ease-out)} /* 40px: t-title's line box */
.bf-title-card__line{display:block}
/* Calm keeps this fade (shortened), beating the base clamp: the h1 never jumps in or out. */
html[data-calm] .bf-title-card__title{transition-duration:var(--dur-ui)!important}

/* Date: a .bf-tag.bf-tag--plate pill; the card only resets the paragraph margin. */
.bf-title-card__when{margin:0}

/* Painted: the art carries the lettering, so the h1 fades out (it stays in the accessibility tree). */
.bf-title-card.is-painted:not(.bf-title-card--after) .bf-title-card__title{opacity:0}

/* After: the h1 reads the thanks on a plate card in the gate; kicker and date are gone. */
.bf-title-card--after :is(.bf-title-card__kicker,.bf-title-card__when){display:none}
.bf-title-card--after .bf-title-card__title{width:100%;padding:var(--sp-3);border:var(--hairline) solid var(--plate-line);border-radius:var(--r-card);background:var(--plate);font-size:var(--fs-32);line-height:36px} /* 36px: t-live's line box at fs-32 */

/* Compact phones and landscape: the kicker goes. Hooked to the page's html.compact or the modifier. */
html.compact .bf-title-card__kicker,.bf-title-card--compact .bf-title-card__kicker{display:none}
/* ---------- CountdownPlate ---------- */
/* replaces: #countdownPlate, #narrator pointer-events (index.html:90), #countdownPlate (index.html:124), #cdLabel, .cd-narr (index.html:125-126),
   #countdown, #countdown .cell (index.html:127-128), #countdown i (index.html:130), #narrator (index.html:131; css/app.css:336-337),
   html.compact #narrator, html.compact .cd-narr, html.compact #countdown b (css/app.css:187-189), the phase rules (css/app.css:153-158, 160, 341),
   the Witching slot rule (css/app.css:348), html.art-portrait #countdownPlate and the live chip (css/app.css:368, 374-375),
   #countdownPlate, #narrator transition and html[data-peek] (css/scene.css:113-114); the digit drip (js/countdown.js:56-60).
   The digits are DisplayNumeral (.bf-numeral.bf-numeral--countdown claims #countdown b, index.html:129). */

/* The plate in the gate doors: label, four cells, and the narrator line, all on plate glass (rule 9). Never interactive. */
.bf-countdown-plate{width:var(--content-w);margin-inline:auto;padding:var(--sp-1) 0 var(--sp-2);border:var(--hairline) solid var(--plate-line);border-radius:var(--r-card);background:var(--plate);color:var(--text);text-align:center;pointer-events:none;transition:opacity var(--dur-ui) var(--ease-out)}
/* Peek: the stage text steps back so the painting shows. */
html[data-peek] .bf-countdown-plate{opacity:var(--opacity-peek)}
/* Aside: the plate keeps its slot but steps out of sight while something else owns it
   (the Takeover's final minute, the Witching banner). */
.bf-countdown-plate.is-aside{visibility:hidden}

/* Label: a lowercase fragment that finishes the digits ("until the gates open"). */
.bf-countdown-plate__label{margin:0;padding:0 var(--sp-4);font:var(--fw-medium) var(--fs-14)/var(--lh-18) var(--font-ui);color:var(--text-2)}

/* Timer: four cells, centered. A cell clips its numeral so a new digit drips in from above. */
.bf-countdown-plate__timer{display:flex;justify-content:center;margin-top:var(--sp-1)}
.bf-countdown-plate__cell{display:flex;flex-direction:column;align-items:center;width:calc(var(--sp-12) + var(--sp-4));overflow:hidden} /* 64px: one cell holds two 44px Creepster digits */
.bf-countdown-plate__unit{display:block;margin-top:calc(var(--sp-1) / 2);font:var(--fw-semibold) var(--fs-13)/var(--lh-16) var(--font-ui);font-style:normal;color:var(--text-2)} /* sp-1/2: the optical 2px under the digits */

/* Narrator: Batface's line for the day, up to two balanced lines, on the same plate. Hidden while empty. */
.bf-countdown-plate__narrator{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden;margin:var(--sp-1) var(--sp-4) 0;font:var(--fw-medium) var(--fs-15)/var(--lh-20) var(--font-ui);color:var(--text-2);text-wrap:balance}
.bf-countdown-plate__narrator:empty{display:none}

/* ---- Phase variants (one at a time) ---- */
/* Final hour: minutes and seconds only; the plate warms to candle. */
.bf-countdown-plate--final-hour{border-color:var(--pumpkin-500);box-shadow:var(--glow-candle)}
.bf-countdown-plate--final-hour .bf-countdown-plate__cell[data-unit="days"],.bf-countdown-plate--final-hour .bf-countdown-plate__cell[data-unit="hours"]{display:none}
/* Live and Witching: no digits; the label becomes the t-live call (the DisplayNumeral --live recipe, repeated here). */
.bf-countdown-plate--live .bf-countdown-plate__timer{display:none}
.bf-countdown-plate--live .bf-countdown-plate__label{padding:var(--sp-2) var(--sp-2) 0;font:var(--fw-regular) var(--fs-32)/36px var(--font-display);letter-spacing:var(--track-display);color:var(--live);text-shadow:var(--text-on-art),var(--glow-spectral)} /* 36px: t-live's line box */
/* After: no countdown; the plate carries only the narrator, under the title plate. */
.bf-countdown-plate--after :is(.bf-countdown-plate__label,.bf-countdown-plate__timer){display:none}
/* Compact phones (portrait under 740px tall) and landscape phones: smaller digits. Hooked to the page's html.compact or the modifier. */
html.compact .bf-countdown-plate .bf-numeral--countdown,.bf-countdown-plate--compact .bf-numeral--countdown{font-size:var(--fs-36);line-height:42px} /* 42px: t-numeral's compact line box */

/* ---- Drip: a changed numeral slides down sp-2 into its cell and fades up. Calm (base) makes it instant. ---- */
@media (prefers-reduced-motion: no-preference){
  @keyframes bf-countdown-plate-drip{from{transform:translateY(calc(-1 * var(--sp-2)));opacity:var(--opacity-faint)}}
  .bf-countdown-plate__cell .is-dripping{animation:bf-countdown-plate-drip var(--dur-ui) var(--ease-out)}
}
/* ---------- DisplayNumeral ---------- */
/* replaces: #countdown b (index.html:129), the type half of #takeover .tk-num, .tk-call (css/app.css:203-205: size, line height,
   color, text-shadow; Takeover's __num and __call repeat it), .hype-num type (css/sheets.css:103), .res-score and its
   short-screen size (css/game.css:136, 170), .night-num and its open/locked colors (css/extras.css:63, 65, 69); the type of
   #hudScore, .gr-callout, .gr-callout.is-big, #arenaIntro (css/game.css:51, 106, 108, 121; ArenaHud composes .bf-numeral) and of
   .tv-title, .tv-title.is-witching, .tv-big, .tv-cell b (css/extras.css:162-163, 166, 169; TvPanel composes .bf-numeral, titles keep lh-tight).
   Not claimed here: the live #cdLabel (CountdownPlate repeats the --live recipe in its own class). */

/* Creepster numbers and calls. One recipe per light: the ground picks the shadow (text-on-art on the painting,
   text-drop on the Ghost Run! playfield) and the light adds exactly one glow. Default: spectral slime on the painting.
   Always pair .bf-numeral with one size variant; add .bf-digits and wrap digits that tick in spans. */
.bf-numeral{--bf-numeral-ground:var(--text-on-art);--bf-numeral-glow:var(--glow-spectral);margin:0;font-family:var(--font-display);font-size:var(--fs-44);font-style:normal;font-weight:var(--fw-regular);line-height:var(--lh-tight);letter-spacing:var(--track-display);font-variant-numeric:tabular-nums;color:var(--live);text-shadow:var(--bf-numeral-ground),var(--bf-numeral-glow)}

/* ---- Sizes (each is one type style) ---- */
/* countdown: t-numeral, the stage plate's digits. */
.bf-numeral--countdown{font-size:var(--fs-44);line-height:50px} /* 50px: t-numeral's line box, room for Creepster's drips in the clipped cell */
/* takeover: t-numeral-xl, the final minute 60 to 1 and the arena 3-2-1. */
.bf-numeral--takeover{font-size:var(--fs-120);line-height:var(--lh-solid)}
/* live: t-live, "The gates are open" on the plate. */
.bf-numeral--live{font-size:var(--fs-32);line-height:36px} /* 36px: t-live's line box */
/* hud: the Ghost Run! score in the HUD; always on the playfield ground. */
.bf-numeral--hud{--bf-numeral-ground:var(--text-drop);font-size:var(--fs-32);line-height:var(--lh-solid)}
/* callout: t-callout, game callouts and the gates-open call; candle by default (add --gold for the big ones). */
.bf-numeral--callout{--bf-numeral-glow:var(--glow-candle);font-size:var(--fs-44);line-height:var(--lh-tight);color:var(--candle-300)}
/* score: t-score, the results score; always on the playfield ground. 44 on short screens. */
.bf-numeral--score{--bf-numeral-ground:var(--text-drop);font-size:var(--fs-64);line-height:var(--lh-solid)}
@media (max-height:700px){.bf-numeral--score{font-size:var(--fs-44)}} /* 700px: the results card's short-screen breakpoint (game.css:169) */
/* tile: t-tile, tombstone numerals, the smallest Creepster. Pair with a light: --bone (closed), --mist (locked), default (opened). */
.bf-numeral--tile{font-size:var(--fs-24);line-height:var(--lh-solid)}
/* hype: Gourdon's hype number in the Hype sheet, at the t-title size. Candle light, but a sheet is not the painting:
   no ground and no glow. */
.bf-numeral--hype{font-size:var(--fs-36);line-height:var(--lh-solid);color:var(--pumpkin-500);text-shadow:none}

/* ---- Ground ---- */
/* playfield: over moving game art the hard drop replaces text-on-art (arena 3-2-1, game callouts). */
.bf-numeral--playfield{--bf-numeral-ground:var(--text-drop)}

/* ---- Lights (declared after the sizes so they win) ---- */
.bf-numeral--candle{--bf-numeral-glow:var(--glow-candle);color:var(--candle-300)}
.bf-numeral--gold{--bf-numeral-glow:var(--glow-gold);color:var(--reward)}
/* No light: bone or mist numerals carry only their ground shadow. */
.bf-numeral--bone{color:var(--text);text-shadow:var(--bf-numeral-ground)}
.bf-numeral--mist{color:var(--text-2);text-shadow:var(--bf-numeral-ground)}
/* ---------- Ghost ---------- */
/* replaces: .ghost, .ghost::before, .ghost svg, .ghost path, .ghost circle, .ghost ellipse, .ghost[data-hit], .ghost.is-calm,
   .ghost.is-calm.is-out, html[data-calm] .ghost.is-calm, html[data-calm] .ghost[data-hit] (scene.css:9-19),
   :is(html.golden-spirit,html[data-milestones~="gold"]) .ghost path/svg (scene.css:21-22), html.blood-moon .ghost svg (scene.css:24),
   html[data-ghost-tint="lilac"|"slime"] .ghost path (extras.css:140-141),
   .gghost, .gghost::before, .gghost svg, .gghost[data-hit], .gghost[data-gone], .gghost:not(.gg-boss), .gghost:active,
   .gghost:focus-visible, .gg-body, .gg-eye, .gg-golden .gg-body, .gg-golden::after, .gg-cat, .gg-cat svg, .gg-boss,
   .gg-boss .gg-body, .gg-boss.is-cracked .gg-body, .gg-crown, .gg-hat, .gg-hp placement (game.css:67-89; the HP bar is Meter's),
   .gr-tutorial .gghost and @keyframes gr-bob (game.css:113, 115-117), #playfield.has-cast …, #playfield.has-boss … (game.css:184-201) */

/* One ghost. The painted cast sprite is the look; the drawn SVG is the fallback, always ecto-200 with ghost-eye eyes.
   Colors travel as custom properties so the drawing also works through <use>:
   --bf-ghost-body (fill), --bf-ghost-eye (eyes and mouth), --bf-ghost-glow (a whole filter value or none).
   --s is the visual size (JS writes it for game ghosts); --bf-ghost-cast / --bf-ghost-boss hold the sheet urls. */

/* Base = a game ghost: a square button the engine moves by transform. Live ghosts sit one layer above Big Boo. */
.bf-ghost{position:absolute;left:0;top:0;z-index:var(--z-ambient);width:var(--s,var(--ghost-ambient));height:var(--s,var(--ghost-ambient));margin:0;padding:0;border:0;border-radius:40%;background:none;color:inherit;cursor:pointer;touch-action:none;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none;will-change:transform,opacity} /* 40%: the focus ring rounds with the head */
/* Invisible square hit pad: ghost-hit-pad all round, grown so the whole target never drops under hit-game (Tiny Terrors). */
.bf-ghost::before{content:"";position:absolute;inset:min(calc(-1 * var(--ghost-hit-pad)),calc((var(--s,var(--ghost-ambient)) - var(--hit-game)) / 2))}
.bf-ghost:not(.bf-ghost--ambient):active{scale:none}
.bf-ghost[data-hit],.bf-ghost[data-gone]{pointer-events:none}

/* The drawing */
.bf-ghost__art{position:relative;display:block;width:100%;height:100%;overflow:visible;pointer-events:none;filter:var(--bf-ghost-glow,none)}
.bf-ghost__body{fill:var(--bf-ghost-body,var(--ecto-200))}
.bf-ghost__eye{fill:var(--bf-ghost-eye,var(--ghost-eye))}

/* Ambient: the stage ghost, 64 wide and 1.3x tall, lit by the act's moon. Starts invisible; JS or .is-calm fades it in. */
.bf-ghost--ambient{--bf-ghost-glow:drop-shadow(var(--glow-moon));width:var(--ghost-ambient);height:calc(var(--ghost-ambient) * 1.3);border-radius:45% 45% 30% 30%;opacity:0;touch-action:manipulation} /* 1.3: the ghost-ambient height ratio */
.bf-ghost--ambient::before{inset:calc(-1 * var(--ghost-hit-pad));border-radius:50%} /* the stage pad is round, as built (scene.css:10) */
.bf-ghost--ambient[data-hit]{opacity:0!important;transition:opacity var(--dur-fast) var(--ease-in)} /* !important: beats the inline opacity the drift loop writes */
.bf-ghost--ambient.is-calm{opacity:1;transition:opacity var(--dur-reveal) var(--ease-out)}
.bf-ghost--ambient.is-calm.is-out{opacity:0;pointer-events:none}
/* Calm keeps fades: the stage ghosts still come and go, slowly. */
html[data-calm] .bf-ghost--ambient.is-calm{transition-duration:var(--dur-reveal)!important}
html[data-calm] .bf-ghost--ambient[data-hit]{transition-duration:var(--dur-fast)!important}

/* Tints (13 Nights) and lights. Gold beats a tint; the Blood Moon glow beats gold's glow. */
.bf-ghost--lilac{--bf-ghost-body:var(--lilac-300)}
.bf-ghost--slime{--bf-ghost-body:var(--slime-400)}
.bf-ghost--golden{--bf-ghost-body:var(--gold-400);--bf-ghost-glow:drop-shadow(var(--glow-gold))}
.bf-ghost--blood{--bf-ghost-glow:drop-shadow(var(--glow-blood))}

/* Cat decoy (drawn): night cat with slime eyes on a night disc, ringed in danger. */
.bf-ghost--cat{--bf-ghost-body:var(--night-950);--bf-ghost-eye:var(--night-950);border-radius:50%;background:var(--night-800);box-shadow:0 0 0 var(--ring) var(--danger)}
.bf-ghost--cat .bf-ghost__art{width:86%;height:86%;margin:7%} /* the cat drawing inside its disc (game.css:82) */
.bf-ghost__iris{fill:var(--slime-400)}
.bf-ghost__nose{fill:var(--danger)}

/* Sprite (primary): the cast cell on a centered 4:5 layer. The drawing stays in the DOM, hidden, so size and label hold. */
.bf-ghost--sprite>:is(.bf-ghost__art,.bf-ghost__crown,.bf-ghost__hat){visibility:hidden}
.bf-ghost--sprite::after{content:"";position:absolute;top:0;left:50%;height:100%;aspect-ratio:4/5;transform:translateX(-50%);background:var(--bf-ghost-cast) no-repeat 0 0/600% 100%;filter:var(--bf-ghost-glow,none);pointer-events:none} /* cast sheet: 6 cells of 192x240 (4:5); cell n sits at n x 20% */
.bf-ghost--sprite[data-hit]::after{background-position-x:20%} /* classic, spooked */
.bf-ghost--sprite.bf-ghost--golden::after{background-position-x:40%} /* golden */
.bf-ghost--sprite.bf-ghost--golden[data-hit]::after{background-position-x:60%} /* golden, spooked */
.bf-ghost--sprite.bf-ghost--cat{--bf-ghost-glow:drop-shadow(var(--glow-moon));border-radius:40%;background:none;box-shadow:none}
.bf-ghost--sprite.bf-ghost--cat::after{background-position-x:80%} /* cat */
.bf-ghost--sprite.bf-ghost--cat[data-hit]::after{background-position-x:100%} /* cat, hissing */

/* Big Boo: the same ghost at boss size, one layer under the live ghosts, crown (or the Witching party hat) and a Meter for HP. */
.bf-ghost--boss{z-index:calc(var(--z-ambient) - 1)}
.bf-ghost--boss.is-cracked{--bf-ghost-body:var(--mist-300)}
.bf-ghost__crown{position:absolute;left:50%;top:-12%;width:34%;transform:translateX(-50%);fill:var(--reward);pointer-events:none}
.bf-ghost__hat{position:absolute;left:58%;top:-22%;width:26%;transform:rotate(14deg);pointer-events:none} /* the hat's tilt (game.css:88) */
.bf-ghost__hat-cone{fill:var(--pumpkin-500)}
.bf-ghost__hat-band{fill:var(--violet-500)}
.bf-ghost__hat-pom{fill:var(--lilac-300)}
.bf-ghost .bf-ghost__hp{position:absolute;left:12%;right:12%;bottom:calc(-1 * var(--sp-4));pointer-events:none}
.bf-ghost--boss.bf-ghost--sprite::after{left:0;width:100%;aspect-ratio:auto;transform:none;background:var(--bf-ghost-boss) no-repeat 0 0/300% 100%} /* boss sheet: 3 cells of 512x512 at 0, 50%, 100% */
.bf-ghost--boss.bf-ghost--sprite[data-gone="banished"]::after{background-position-x:50%} /* banished; an escape keeps his face */
.bf-ghost--boss.bf-ghost--hat.bf-ghost--sprite:not([data-gone="banished"])::after{background-position-x:100%} /* Witching party hat */

/* Tutorial: the one big ghost of a first run, bobbing until tapped. */
.bf-ghost--tutorial{--s:132px;position:relative} /* the build's tutorial size (game.css:113) */
@media (prefers-reduced-motion: no-preference){
  @keyframes bf-ghost-bob{50%{transform:translateY(calc(-1 * var(--sp-2)))}}
  .bf-ghost--tutorial{animation:bf-ghost-bob var(--dur-pulse) var(--ease-loop) infinite}
}
/* ---------- ArenaHud ---------- */
/* replaces: #arena (app.css:253; game.css:25), #arena::backdrop (app.css:254), #arena::after (game.css:29-34),
   #arena[data-variant="witching"]::after (game.css:35-38), html[data-lights="low"] #arena::after (game.css:39),
   #arena::before, #arena[data-variant="witching"]::before (game.css:40-41), #playfield (app.css:257; game.css:42),
   #arena.is-paused #playfield, #arena[data-state="paused"] #playfield (game.css:43), #playfield::after, #arena.is-tint #playfield::after (game.css:44-45),
   #arena.is-break #hudCombo (game.css:46), #hud (app.css:255; game.css:49), #hudTime (game.css:50), #hudScore placement (game.css:51),
   #hudCombo, #hudCombo.is-hot, .hud-flame, #hudCombo.is-hot .hud-flame (game.css:52-55), #comboBar placement (game.css:56),
   #pauseBtn (app.css:256; game.css:57; look: Button icon), .hud-timebar placement (game.css:58), .gr-rect, .gr-fx (game.css:63-64),
   .gr-lane, .gr-lane span (game.css:94-95), .gr-dot, .gr-dot.is-gold, .gr-dot.is-cat (game.css:98-100),
   .gr-pop, .gr-pop.is-bad, .gr-pop-big (game.css:101-103), .gr-callout placement, .gr-callout.is-on (game.css:106-107), .gr-helper (game.css:109),
   .gr-tutorial, .gr-tutorial p (game.css:112, 114), #arenaIntro placement, #arenaIntro.is-on, #arenaIntro.is-wait (game.css:121-123),
   landscape .gr-callout and #arenaIntro (game.css:174-177).
   Composed, claimed by their owners: the score, callout and 3-2-1 type (DisplayNumeral), the bars (Meter), Pause (Button),
   the helper's plate (Surface), the ghosts (Ghost), the pause card (DialogCard). */

/* The Ghost Run! arena: a full-screen <dialog> holding the HUD, the playfield and the juice layers. */
.bf-arena-hud{--bf-arena-hud-wash:none;position:fixed;inset:0;isolation:isolate;width:100%;height:100%;max-width:none;max-height:none;margin:0;padding:0;border:0;overflow:hidden;color:var(--text);font-family:var(--font-ui);background:radial-gradient(120% 70% at 50% 108%,var(--night-700) 0,var(--night-900) 45%,var(--night-950) 100%)}
.bf-arena-hud::backdrop{background:var(--night-950)}
/* The stage painting behind the ghosts (--bf-arena-hud-art, an absolute url), dimmed by night bands and a vignette.
   The middle band is plate, so text and text-2 hold 7.3:1 and 4.7:1 over the brightest paint; pops add their own plate (below).
   --bf-arena-hud-wash is a light laid on the painting under the bands (the Witching run's blood-moon wash). */
.bf-arena-hud::after{content:"";position:absolute;inset:0;z-index:calc(var(--z-art) - 1);pointer-events:none;background:linear-gradient(to bottom,var(--shroud) 0,var(--plate) 18%,var(--plate) 55%,var(--shroud) 100%),radial-gradient(120% 90% at 50% 45%,transparent 40%,var(--plate) 100%),var(--bf-arena-hud-wash),var(--bf-arena-hud-art,none) center/cover no-repeat}
html[data-lights="low"] .bf-arena-hud::after{opacity:var(--opacity-disabled)}
/* The act's moon: a faint glow off the top right. */
.bf-arena-hud::before{content:"";position:absolute;right:-60px;top:calc(var(--sat) + var(--sp-8) + var(--sp-2));width:220px;height:220px;border-radius:50%;background:radial-gradient(circle,var(--moon) 0,transparent 70%);opacity:var(--opacity-faint);pointer-events:none} /* 220px glow, 60px past the edge (game.css:40) */
/* A Witching run: the painting takes the blood-moon wash and the moon glows blood red, both at the token's own .3. */
.bf-arena-hud--witching{--bf-arena-hud-wash:linear-gradient(var(--bloodmoon-wash),var(--bloodmoon-wash))}
.bf-arena-hud--witching::before{background:radial-gradient(circle,var(--bloodmoon-wash) 0,transparent 70%);opacity:1}

/* HUD: time · score · combo · pause, hud-h under the top safe area, the time bar on its bottom edge. */
.bf-arena-hud__bar{position:absolute;left:0;right:0;top:0;z-index:var(--z-chrome);display:flex;align-items:center;gap:var(--sp-3);height:calc(var(--hud-h) + var(--sat));padding:var(--sat) calc(var(--sp-2) + var(--sar)) 0 calc(var(--sp-3) + var(--sal));color:var(--text);background:linear-gradient(var(--night-950),var(--scrim))}
.bf-arena-hud__time{min-width:var(--hit);font:var(--fw-bold) var(--fs-20)/var(--lh-solid) var(--font-ui);font-variant-numeric:tabular-nums}
/* The score is a DisplayNumeral (.bf-numeral.bf-numeral--hud); the HUD only centers it on the bar. */
.bf-arena-hud__score{position:absolute;left:50%;top:calc(var(--sat) + var(--hud-h) / 2);transform:translate(-50%,-50%);white-space:nowrap}
.bf-arena-hud__combo{position:relative;display:flex;align-items:center;justify-content:flex-end;gap:calc(var(--sp-1) / 2);min-width:calc(var(--hit) + var(--sp-3));margin-left:auto;color:var(--text-2);font:var(--fw-bold) var(--fs-20)/var(--lh-solid) var(--font-ui);font-variant-numeric:tabular-nums} /* 2px flame-to-×N gap: optical nudge */
.bf-arena-hud__combo.is-hot{color:var(--amber-400)}
.bf-arena-hud__flame{flex:none;width:18px;height:18px;fill:currentColor;opacity:var(--opacity-disabled)} /* the build's 18px flame glyph (arena.js:128) */
.bf-arena-hud__combo.is-hot .bf-arena-hud__flame{opacity:1}
.bf-arena-hud__combo .bf-arena-hud__combo-meter{position:absolute;left:0;right:0;bottom:calc(-1 * var(--sp-2))}
.bf-arena-hud__bar .bf-arena-hud__pause{flex:none;margin-left:var(--sp-1)}
.bf-arena-hud__bar .bf-arena-hud__timebar{position:absolute;left:0;right:0;bottom:0}

/* Playfield: everything under the HUD. Hidden while paused, so a pause never doubles as a peek. */
.bf-arena-hud__field{position:absolute;left:0;right:0;bottom:0;top:calc(var(--hud-h) + var(--sat));z-index:var(--z-ambient);touch-action:none}
.bf-arena-hud__field:focus-visible{outline:none} /* a focus parking spot (tabindex -1) the size of the screen, not a control (game.css:42) */
.bf-arena-hud.is-paused .bf-arena-hud__field{visibility:hidden}
/* Cat tap: one flat blood tint for 200ms (JS throttles to three a second). The chain break turns the combo blood for 200ms.
   Under Calm neither flashes: the -3 pop and the combo reset carry the news. Both are steps, never transitions. */
.bf-arena-hud__field::after{content:"";position:absolute;inset:0;pointer-events:none;background:var(--danger);opacity:0}
html:not([data-calm]) .bf-arena-hud.is-tint .bf-arena-hud__field::after{opacity:var(--opacity-faint)}
html:not([data-calm]) .bf-arena-hud.is-break .bf-arena-hud__combo{color:var(--danger)}
.bf-arena-hud__rect{position:absolute;left:6%;right:6%;top:var(--sp-3);bottom:calc(var(--sp-6) + var(--sab))}
.bf-arena-hud__fx{position:absolute;inset:0;overflow:hidden;pointer-events:none}

/* Lantern lanes: six numbered slots, 2 columns x 3 rows, placed by JS. */
.bf-arena-hud__lane{position:absolute;border:var(--hairline) dashed var(--lilac-300);border-radius:var(--r-card);pointer-events:none}
.bf-arena-hud__lane-num{position:absolute;left:var(--sp-2);top:var(--sp-1);font:var(--fw-bold) var(--fs-16)/var(--lh-solid) var(--font-ui);color:var(--text-2)}

/* Juice: poof dots and score pops (Fredoka). Over the playfield every number takes text-drop plus one glow.
   A pop also carries its own soft plate, so a -3 that lands on a painted lantern or the moon keeps 4.5:1 (the band alone gives blood 2.6:1 there). */
.bf-arena-hud__dot{position:absolute;left:0;top:0;width:var(--sp-3);height:var(--sp-3);margin:calc(var(--sp-3) / -2);border-radius:50%;background:var(--text);opacity:0}
.bf-arena-hud__dot--reward{background:var(--reward)}
.bf-arena-hud__dot--danger{background:var(--danger)}
.bf-arena-hud__pop{position:absolute;transform:translate(-50%,-50%);font:var(--fw-bold) var(--fs-24)/var(--lh-solid) var(--font-ui);color:var(--candle-300);text-shadow:var(--text-drop),var(--glow-candle);white-space:nowrap;pointer-events:none}
.bf-arena-hud__pop::before{content:"";position:absolute;inset:calc(-1 * var(--sp-3)) calc(-1 * var(--sp-4));z-index:-1;background:radial-gradient(closest-side,var(--plate) 60%,transparent)}
.bf-arena-hud__pop--danger{color:var(--danger);text-shadow:var(--text-drop),var(--glow-blood)}
.bf-arena-hud__pop--reward{font-size:var(--fs-36);color:var(--reward);text-shadow:var(--text-drop),var(--glow-gold)}
/* The pop rises a step and fades. Each pop is a new node, so the animation starts on insert; Calm keeps it still. */
@media (prefers-reduced-motion: no-preference){
  @keyframes bf-arena-hud-pop{to{transform:translate(-50%,calc(-50% - var(--sp-6)));opacity:0}}
  .bf-arena-hud__pop{animation:bf-arena-hud-pop var(--dur-reveal) var(--ease-out) forwards}
}
html[data-calm] .bf-arena-hud__pop{animation-name:none}

/* Callout: a DisplayNumeral (.bf-numeral--callout.bf-numeral--playfield, + --gold for a banish) on the 38% line. JS runs the pop-in. */
.bf-arena-hud__callout{position:absolute;left:50%;top:38%;z-index:var(--z-content);width:max-content;max-width:calc(100% - 2 * var(--gutter));transform:translate(-50%,-50%);text-align:center;pointer-events:none;opacity:0}
.bf-arena-hud__callout.is-on{opacity:1}

/* Helper line: a Surface plate near the thumbs ("Tap the ghosts before they float away!"). */
.bf-arena-hud__helper{position:absolute;left:50%;bottom:calc(var(--sp-8) + var(--sp-1) + var(--sab));z-index:var(--z-content);width:var(--content-w);margin:0;transform:translateX(-50%);font:var(--fw-medium) var(--fs-16)/var(--lh-body) var(--font-ui);text-align:center;pointer-events:none}

/* Tutorial (first run): one big ghost and its line (t-heading), centered. */
.bf-arena-hud__tutorial{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-4);padding-bottom:var(--sab)}
.bf-arena-hud__tutorial-text{margin:0;font:var(--fw-bold) var(--fs-20)/var(--lh-heading) var(--font-ui);text-align:center}

/* Intro: the 3-2-1 "Go!" is a DisplayNumeral (.bf-numeral--takeover.bf-numeral--playfield) filling the layer;
   the Showdown wait line takes its place in Fredoka. */
.bf-arena-hud__intro{position:absolute;inset:0;z-index:var(--z-content);display:none;place-items:center;padding:0 var(--sp-6);text-align:center;pointer-events:none}
.bf-arena-hud__intro.is-on{display:grid}
.bf-arena-hud__intro.is-wait{font:var(--fw-bold) var(--fs-32)/var(--lh-heading) var(--font-ui);letter-spacing:0;color:var(--text);text-shadow:var(--text-drop)}

@media (orientation:landscape) and (max-height:500px){ /* landscape phones, the build's breakpoint (game.css:174) */
  .bf-arena-hud__callout{top:44%}
  .bf-arena-hud__intro:not(.is-wait){font-size:min(30vh,var(--fs-120))}
}
