/* Surviversus shared styles: menus wear the bossfeed parchment/ink/crimson identity, the HUD is dark. */
:root {
  --paper: #f5efe2;
  --ink: #2b2118;
  --ink-soft: #6a6253;
  --line: rgba(0, 0, 0, .18);
  --crimson: #b3242e;
  --gold: #a9802f;
  /* --go forward, --exit leave. An exit is NEVER filled and never crimson; two exit tones because slate that reads on parchment dies on the dark HUD. */
  --go: #b3242e;
  --exit: #66727e;
  --exit-dark: #a3b2be;
  /* The two sides of a team mode (gamedata/modes.js TEAM_IDS) = art-manifest.js TEAM_TINTS, so the lobby, the HUD and
     the tinted art agree. In play, index 0 is also YOUR side on your own screen (sideTint). */
  --team-blue: #3f8cff;
  --team-red: #e5484d;
  --hud-bg: rgba(16, 13, 10, .72);
  --hud-line: rgba(255, 255, 255, .14);
  --ui: Inter, system-ui, -apple-system, sans-serif;
  --display: 'EB Garamond', Georgia, serif;
}
* { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { margin: 0; padding: 0; }
body { font-family: var(--ui); background: #171310; color: var(--paper); }

body.landing {
  background: var(--paper); color: var(--ink);
  min-height: var(--app-h, 100vh); display: flex; flex-direction: column; align-items: center;
  padding: 24px 16px 60px;
  -webkit-tap-highlight-color: transparent;   
}
.logo {
  font-family: var(--display); font-weight: 700; letter-spacing: .5px;
  font-size: clamp(30px, calc(var(--app-w, 100vw) * 0.09), 84px); margin: 24px 0 4px; color: var(--ink);
  text-align: center; line-height: 1;
}
.logo .bang { color: var(--crimson); }
/* The site EXP bar is fixed and reserves NOTHING: it publishes --fb-bar-h with fb-has-bar on <html> (games/_back-to-games.js), and consuming it is this page's job. */
html.fb-has-bar body.landing { padding-top: calc(var(--fb-bar-h, 0px) + 12px); }
html.fb-has-bar .logo { margin-top: 0; }
.tagline { color: var(--ink-soft); margin: 0 0 34px; font-size: 15px; text-align: center; }
.statblock { margin: 0 0 26px; display: flex; flex-direction: column; gap: 6px; align-items: center; }
/* A grid on both rows, not flex: the reset button is a real column the all-time row leaves empty, so the three numbers line up. */
.statrow {
  display: grid; grid-template-columns: 84px repeat(3, 88px) 76px;
  gap: 10px; align-items: center; justify-content: center;
}
.statlabel {
  font: 700 11px var(--ui); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-soft); text-align: right;
}
.statreset {
  font: 600 11px var(--ui); letter-spacing: .06em; text-transform: uppercase;
  padding: 7px 12px; border-radius: 10px; border: 1.5px solid var(--line);
  background: transparent; color: var(--ink-soft); cursor: pointer;
}
.statreset:hover:not(:disabled) { background: #fff; color: var(--ink); }
.statreset:disabled { opacity: .35; cursor: default; }
.statnone { font: 500 12px var(--ui); color: var(--ink-soft); margin: 2px 0 0; text-align: center; }
@media (max-width: 560px) {
  .statrow { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; max-width: 340px; }
  .statlabel { grid-column: 1 / -1; width: auto; text-align: center; }
  .statreset { grid-column: 1 / -1; justify-self: center; margin-top: 2px; }
  .statcell { min-width: 0; padding: 6px 8px; }
}
.statcell {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-width: 76px; padding: 8px 14px; border-radius: 12px;
  background: #fff8; border: 1.5px solid var(--line);
}
.statcell b { font: 700 20px var(--display); color: var(--ink); line-height: 1; }
.statcell span { font: 600 11px var(--ui); letter-spacing: .06em; text-transform: uppercase; color: var(--ink-soft); }
body.landing.hasscene .statcell { background: #fdfaf2; }
.modehead {
  font: 700 12px var(--ui); letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--ink-soft); margin: 0 0 12px; text-align: center;
}
.modehead + .menucards { margin-bottom: 32px; }
.menucards { display: flex; flex-wrap: wrap; gap: 18px; justify-content: center; max-width: 900px; }
.menucard {
  background: #fff9; border: 1.5px solid var(--line); border-radius: 16px;
  /* max-width, not width: 290px overflows a 320px phone once the body padding is taken off. */
  padding: 22px 24px 20px; width: 290px; max-width: 100%; cursor: pointer; text-align: left;
  font-family: var(--ui); font-size: inherit; color: var(--ink);
  transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
  text-decoration: none; display: flex; flex-direction: column; align-items: stretch;
}
/* Every hover state here is gated on a pointer that CAN hover: a tap leaves :hover set until the next tap lands elsewhere. */
@media (hover: hover) {
  .menucard:hover { transform: translateY(-3px); box-shadow: 0 14px 30px -18px #0008; }
}
.menucard h2 { margin: 0 0 6px; font-family: var(--display); font-size: 26px; text-align: left; }
.menucard p { margin: 0; color: var(--ink-soft); font-size: 13.5px; line-height: 1.5; }
.menucard .emoji { font-size: 30px; display: block; margin-bottom: 8px; }
.modetag {
  align-self: flex-start; font: 700 10.5px var(--ui); letter-spacing: .7px;
  text-transform: uppercase; color: var(--ink-soft);
  background: #0000000a; border: 1px solid var(--line); border-radius: 999px;
  padding: 3px 9px; margin-bottom: 12px;
}
.modetag.online {
  color: var(--crimson);
  /* Plain rgba first, color-mix second: a browser too old for color-mix drops that line and keeps the fallback. */
  border-color: rgba(179, 36, 46, .45);
  border-color: color-mix(in srgb, var(--crimson) 45%, transparent);
  background: rgba(179, 36, 46, .07);
  background: color-mix(in srgb, var(--crimson) 7%, transparent);
}
.cardgo { margin-top: 14px; font: 700 13px var(--ui); color: var(--crimson); }
@media (hover: hover) { .menucard:hover .cardgo { text-decoration: underline; } }
.menucard:disabled { opacity: .75; cursor: default; transform: none; box-shadow: none; }
.menucard:focus-visible { outline: 2px solid var(--crimson); outline-offset: 3px; }

/* ONE control because it is ONE decision: survivor count and map are paired in js/presets.js (SIZES), and this only renders the pairing. */
.sizepick {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  justify-content: center; margin: 0 0 18px;
  background: #fff9; border: 1.5px solid var(--line); border-radius: 999px;
  padding: 8px 8px 8px 18px; max-width: 100%;
}
.sizepick > label {
  font: 700 11px var(--ui); letter-spacing: 1.1px; text-transform: uppercase;
  color: var(--ink-soft);
}
.sizepick select {
  font: 700 14px var(--ui); padding: 9px 14px; border-radius: 999px;
  border: 1.5px solid var(--line); background: #fff; color: var(--ink); cursor: pointer;
  max-width: min(320px, 62vw);
}
.sizenote {
  width: 100%; text-align: center; margin: -8px 0 26px;
  font: 400 12.5px/1.5 var(--ui); color: var(--ink-soft);
}
/* THE MODE TABS (ui/mode-tabs.js), above the size picker: a segmented control, one decision. The strip is not drawn while one mode is on. */
.modetabs {
  display: flex; gap: 4px; margin: 0 0 10px; padding: 4px; max-width: 100%;
  background: #fff9; border: 1.5px solid var(--line); border-radius: 999px;
}
.modetab {
  flex: 1 1 auto; min-height: 42px; padding: 0 20px; border: none; border-radius: 999px;
  font: 700 13px var(--ui); letter-spacing: .8px; text-transform: uppercase;
  background: transparent; color: var(--ink-soft); cursor: pointer;
  transition: background-color .12s ease, color .12s ease;
}
@media (hover: hover) { .modetab:hover:not([aria-selected="true"]) { background: #fff; color: var(--ink); } }
.modetab[aria-selected="true"] { background: var(--ink); color: var(--paper); cursor: default; }
.modetab:focus-visible { outline: 2px solid var(--crimson); outline-offset: 2px; }
.modeblurb {
  max-width: 520px; text-align: center; margin: 0 0 18px;
  font: 400 13px/1.5 var(--ui); color: var(--ink-soft);
}
/* body.narrow, not a media query: it is also true inside the phone frame, where the WINDOW is still wide. */
body.narrow .modetabs { width: 100%; }
body.narrow .modetab { padding: 0 8px; font-size: 12px; letter-spacing: .4px; }
.cardsize {
  align-self: flex-start; margin-top: 10px;
  font: 700 10.5px var(--ui); letter-spacing: .5px; text-transform: uppercase;
  color: var(--gold);
}

.howto { max-width: 620px; margin: 44px auto 0; text-align: left; }
.howto h2 {
  font: 700 12px var(--ui); letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--ink-soft); margin: 0 0 14px; text-align: center;
}
.howto ol {
  margin: 0; padding: 0; list-style: none;
  counter-reset: howstep; display: grid; gap: 10px;
}
.howto li {
  counter-increment: howstep; position: relative;
  padding: 0 0 0 34px; color: var(--ink-soft);
  font: 400 13.5px/1.55 var(--ui);
}
.howto li::before {
  content: counter(howstep); position: absolute; left: 0; top: 1px;
  width: 22px; height: 22px; border-radius: 50%;
  background: #0000000d; border: 1px solid var(--line);
  color: var(--ink); font: 800 11px var(--ui);
  display: flex; align-items: center; justify-content: center;
}
.howto b { color: var(--ink); font-weight: 700; }

.linkopts {
  display: flex; flex-wrap: wrap; gap: 14px; justify-content: center;
  margin: 0 0 24px; max-width: 700px;
}
.linkopts label {
  display: flex; flex-direction: column; gap: 5px;
  font: 700 11px var(--ui); letter-spacing: .6px; text-transform: uppercase;
  color: var(--ink-soft);
}
.linkopts .hint { text-transform: none; letter-spacing: 0; font-weight: 400; opacity: .8; }
.linkopts input, .linkopts select {
  font: 400 14px var(--ui); padding: 8px 10px; min-width: 150px;
  border: 1.5px solid var(--line); border-radius: 9px; background: #fff; color: var(--ink);
}
.linklist { display: grid; gap: 14px; width: 100%; max-width: 700px; }
.linkrow {
  background: #fff9; border: 1.5px solid var(--line); border-radius: 14px;
  padding: 18px 20px; text-align: left;
}
.linkrow h2 { margin: 0 0 4px; font-family: var(--display); font-size: 22px; }
.linkrow p { margin: 0; color: var(--ink-soft); font: 400 13.5px/1.5 var(--ui); }
.linkrow .modetag { margin-bottom: 8px; }
/* The URL must never widen the card: a query string has no spaces to break at. */
.linkurl {
  margin: 12px 0 10px; padding: 9px 11px;
  background: #0000000a; border: 1px solid var(--line); border-radius: 8px;
  font: 400 12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--ink); overflow-wrap: anywhere; user-select: all;
}
.linkacts { display: flex; gap: 8px; flex-wrap: wrap; }
.linkacts .ghostbtn2 { text-decoration: none; text-align: center; }

@media (max-width: 620px) {
  .menucard { padding: 18px 18px 16px; }
  .menucard h2 { font-size: 23px; }
  .menucard .emoji { font-size: 26px; }
  .modehead { margin-top: 4px; }
  .modehead + .menucards { margin-bottom: 26px; }
  .howto { margin-top: 34px; }
}
.joinrow { display: flex; gap: 8px; margin-top: 12px; }
.joinrow input {
  flex: 1; min-width: 0; font: 700 20px var(--ui); letter-spacing: 4px; text-transform: uppercase;
  padding: 10px 12px; border: 1.5px solid var(--line); border-radius: 10px; background: #fff;
  color: var(--ink); text-align: center;
}
.joinrow button {
  font: 600 15px var(--ui); padding: 10px 16px; border-radius: 10px; border: none;
  background: var(--crimson); color: #fff; cursor: pointer;
}
.footnote { margin-top: 40px; color: var(--ink-soft); font-size: 12.5px; text-align: center; line-height: 1.7; }
.footnote a { color: var(--crimson); }
.linkbtn {
  background: none; border: none; padding: 0; font: inherit; color: var(--crimson);
  text-decoration: underline; cursor: pointer;
}

/* Doubled class selectors on purpose: the generic dark .sheetcard rules are defined LATER, so a bare .tunecard loses to them. */
.tunesheet { align-items: flex-start; overflow-y: auto; padding: 20px 14px 40px; }
.sheetcard.tunecard {
  background: var(--paper); color: var(--ink); border-color: var(--line);
  width: min(600px, 96vw); max-height: none; gap: 0;
}
.tunehead { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.sheetcard.tunecard h2 { text-align: left; color: var(--ink); }
.tuneclose {
  background: none; border: none; font-size: 26px; line-height: 1; color: var(--ink-soft);
  cursor: pointer; padding: 0 4px;
}
.tuneintro { margin: 6px 0 4px; font-size: 13px; color: var(--ink-soft); line-height: 1.55; }
.tuneintro b { color: var(--ink); }
.tunebody { max-height: 58vh; overflow-y: auto; margin: 10px 0; padding-right: 6px; }
.tunecard .tune-group {
  font-family: var(--display); font-size: 18px; font-weight: 700; margin: 18px 0 6px;
  padding-bottom: 4px; border-bottom: 1px solid var(--line); color: var(--ink);
}
.tune-group:first-child { margin-top: 0; }
.tune-row { padding: 7px 0 9px; border-bottom: 1px solid var(--line-2, rgba(0,0,0,.06)); }
.tune-row.changed .tune-val { color: var(--crimson); }
.tune-row.changed .tune-label > span::after { content: " •"; color: var(--crimson); }
.tunecard .tune-label {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  font: 600 13px var(--ui); color: var(--ink);
}
.tune-label em { color: var(--ink-soft); font-weight: 400; font-style: italic; font-size: 11.5px; }
.tune-val { font-variant-numeric: tabular-nums; color: var(--ink-soft); }
.tune-row input[type="range"] { width: 100%; margin: 5px 0 0; accent-color: var(--crimson); }
.tune-help { margin: 3px 0 0; font-size: 11.5px; color: var(--ink-soft); line-height: 1.45; }
.tunestatus { min-height: 18px; font-size: 12.5px; color: var(--crimson); text-align: center; }
.tunejson {
  width: 100%; font: 11px/1.4 ui-monospace, Consolas, monospace; margin: 8px 0 0;
  border: 1px solid var(--line); border-radius: 8px; padding: 8px; background: #fff; color: var(--ink);
}
.itemtabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 2px; }
.itemtab {
  font: 600 12.5px var(--ui); padding: 7px 12px; border-radius: 999px;
  border: 1.5px solid var(--line); background: #fff8; color: var(--ink-soft); cursor: pointer;
}
.itemtab.on { border-color: var(--crimson); color: var(--crimson); background: #fff; }
.itemcard { border: 1px solid var(--line); border-radius: 10px; margin: 6px 0; background: #fff9; }
.itemcard.isnew { border-color: var(--gold); }
.itemcard > summary {
  cursor: pointer; padding: 8px 12px; font: 600 13.5px var(--ui); color: var(--ink);
  display: flex; align-items: baseline; gap: 8px;
}
.itemcard > summary::-webkit-details-marker { display: none; }
.itemid { font: 500 11px ui-monospace, Consolas, monospace; color: var(--ink-soft); }
.newtag {
  font: 700 9.5px var(--ui); letter-spacing: .08em; text-transform: uppercase;
  color: #fff; background: var(--gold); border-radius: 999px; padding: 2px 7px;
}
.itemfields {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px; padding: 4px 12px 10px;
}
.itemfield { display: flex; flex-direction: column; gap: 3px; font: 600 11px var(--ui); color: var(--ink-soft); }
.itemfield.wide { grid-column: 1 / -1; }
.itemfield select,
.itemfield input[type="text"], .itemfield input[type="number"] {
  font: 500 12.5px var(--ui); padding: 5px 8px; border-radius: 7px;
  border: 1.5px solid var(--line); background: #fff; color: var(--ink); width: 100%;
}
.itemfield input.bad { border-color: var(--crimson); background: #fff0ef; }
.rarityprev { margin: 0 12px 10px; padding: 8px 10px; border-radius: 8px; background: #00000008; border: 1px solid var(--line); }
.rarityhead { font: 500 11px/1.5 var(--ui); color: var(--ink-soft); margin-bottom: 5px; }
.raritytable { border-collapse: collapse; font: 500 11.5px var(--ui); color: var(--ink); }
.raritytable td { padding: 2px 12px 2px 0; white-space: nowrap; }
.raritytable .rr { font-weight: 800; text-transform: capitalize; }

.itemdel {
  margin: 0 12px 10px; font: 600 11.5px var(--ui); color: var(--crimson);
  background: none; border: 1px solid var(--line); border-radius: 7px; padding: 5px 10px; cursor: pointer;
}
@media (hover: hover) { .itemdel:hover { border-color: var(--crimson); } }

.tunebtns { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.tunebtns .bigbtn { width: auto; flex: 1 1 100%; }
.ghostbtn2 {
  flex: 1 1 46%; font: 600 13px var(--ui); padding: 10px 12px; border-radius: 10px;
  border: 1.5px solid var(--line); background: #fff8; color: var(--ink); cursor: pointer;
}
@media (hover: hover) { .ghostbtn2:hover { border-color: var(--crimson); color: var(--crimson); } }

/* The game surface must never scroll, rubber-band, text-select or tap-highlight: iOS Safari does all four by default. */
body.gamepage {
  overflow: hidden; position: fixed; inset: 0;
  /* --app-h is the VISUAL viewport (js/ui/viewport.js): 100% of a fixed box is measured with the URL bar retracted, hiding the dock. */
  width: var(--app-w, 100%); height: var(--app-h, 100%);
  touch-action: none; overscroll-behavior: none;
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
.joinscreen {
  position: fixed; inset: 0; z-index: 40; background: var(--paper); color: var(--ink);
  height: var(--app-h, 100%);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  /* center plus overflow pushes the first item off the TOP and no amount of scrolling reaches it. */
  justify-content: safe center;
  padding: 18px; gap: 8px; overflow-y: auto;
}
.joinscreen .logo { font-size: clamp(26px, calc(var(--app-w, 100vw) * 0.08), 56px); margin: 0 0 18px; }
.card {
  background: #fff9; border: 1.5px solid var(--line); border-radius: 16px;
  padding: 22px; width: min(360px, 92vw); display: flex; flex-direction: column; gap: 12px;
}
.fieldlabel { font-size: 12.5px; font-weight: 600; color: var(--ink-soft); letter-spacing: .4px; text-transform: uppercase; }
#nameInput {
  font: 700 22px var(--ui); letter-spacing: 2px; text-transform: uppercase;
  padding: 12px; border: 1.5px solid var(--line); border-radius: 10px; background: #fff; color: var(--ink);
  text-align: center; width: 100%;
}
.joininfo { font-size: 13.5px; color: var(--ink-soft); text-align: center; }
/* The look picker (ui/skin-picker.js): pixel sprites, so no smoothing; the chosen one wears the gold ring. */
.skinrow { display: flex; flex-direction: column; gap: 8px; align-items: center; }
.skinrow .fieldlabel { align-self: flex-start; }
.skinrow .skinname { color: var(--ink); text-transform: none; letter-spacing: 0; }
.skinpick { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }
.skinopt {
  width: 52px; height: 52px; padding: 0; border-radius: 12px; cursor: pointer;
  border: 1.5px solid var(--line); background: #fff; display: grid; place-items: center;
}
.skinopt canvas { width: 44px; height: 44px; image-rendering: pixelated; }
.skinopt[aria-checked="true"] { border: 2.5px solid var(--gold); box-shadow: 0 0 0 3px rgba(169,128,47,.18); }
/* The chosen look steps in place (skin-picker.js walks its frames); a small lift on the beat sells the step. */
.skinopt[aria-checked="true"] canvas { animation: skinstep .5s steps(2, jump-none) infinite; }
@keyframes skinstep { from { transform: translateY(0); } to { transform: translateY(-2px); } }
@media (prefers-reduced-motion: reduce) { .skinopt[aria-checked="true"] canvas { animation: none; } }
.skinopt:focus-visible { outline: 2px solid var(--crimson); outline-offset: 2px; }
.skinvault { font: 600 13px var(--ui); color: var(--ink-soft); text-decoration: none; border-bottom: 1px solid var(--crimson); }
.skinvault::before { content: "🗝 "; }
@media (hover: hover) { .skinvault:hover { color: var(--crimson); } }
/* THE BUTTON SYSTEM — the SHAPE carries the meaning, because colour alone fails on a TV at three metres.
     .bigbtn filled rect = GO FORWARD (one per screen) · .ghostbtn/.ghostbtn2 outlined rect = ADJUST
     .exitbtn outlined slate PILL = LEAVE · .iconbtn circle = A TOOL
   An exit is never filled and never crimson, anywhere in the game. */
.bigbtn {
  font: 700 18px var(--ui); padding: 14px 18px; border-radius: 12px;
  border: 2px solid var(--go); background: var(--go); color: #fff;
  cursor: pointer; width: 100%;
  transition: filter .14s ease, transform .1s ease;
}
@media (hover: hover) { .bigbtn:hover:not(:disabled) { filter: brightness(1.09); } }
.bigbtn:active:not(:disabled) { transform: translateY(1px) scale(.995); }
/* Disabled must read UNAVAILABLE, not broken: keep the outline, lose the fill. Half-opacity reads as loading. */
.bigbtn:disabled {
  opacity: 1; cursor: default;
  background: transparent; border-color: rgba(255,255,255,.22); color: #8b8577;
}
.landing .bigbtn:disabled, .tvlobby .bigbtn:disabled, .joinscreen .bigbtn:disabled {
  border-color: var(--line); color: var(--ink-soft); background: #0000000a;
}

.exitbtn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  font: 700 14px var(--ui); padding: 10px 20px;
  border-radius: 999px;
  border: 1.5px solid var(--exit); background: transparent; color: var(--exit);
  cursor: pointer; text-decoration: none; width: auto; margin: 0 auto;
  /* Both, on purpose: a stretched pill in a flex column would be the go button's silhouette, and margin auto centres it elsewhere. */
  align-self: center;
  transition: background .14s ease, border-color .14s ease;
}
.exitbtn::before { content: "←"; font-size: 15px; line-height: 1; }
@media (hover: hover) { .exitbtn:hover { background: rgba(102,114,126,.12); } }
/* Every DARK surface LISTED, not inferred: the light tone is unreadable on parchment and the dark tone on the HUD. body.lab is the icon lab. */
.sheet .exitbtn, .deathbar .exitbtn, .panechrome .exitbtn,
.tvover .exitbtn, #hud .exitbtn, .spotcol .exitbtn, body.lab .exitbtn {
  border-color: var(--exit-dark); color: var(--exit-dark);
}
@media (hover: hover) {
  .sheet .exitbtn:hover, .deathbar .exitbtn:hover, .panechrome .exitbtn:hover,
  .tvover .exitbtn:hover, #hud .exitbtn:hover, body.lab .exitbtn:hover {
    background: rgba(163,178,190,.14);
  }
}

.iconbtn {
  width: 40px; height: 40px; flex: none; padding: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--hud-line); background: var(--hud-bg); color: #e8ddc8;
  font-size: 18px; line-height: 1; cursor: pointer;
}
@media (hover: hover) { .iconbtn:hover { border-color: var(--gold); color: #ffd34d; } }

.bigbtn:focus-visible, .exitbtn:focus-visible, .ghostbtn:focus-visible,
.ghostbtn2:focus-visible, .iconbtn:focus-visible, .paneltab:focus-visible,
.linkbtn:focus-visible, .sizepick select:focus-visible, .joinrow input:focus-visible,
#nameInput:focus-visible, #codeInput:focus-visible {
  outline: 2px solid var(--crimson); outline-offset: 2px;
}
.joinscreen .exitbtn { color: var(--exit); border-color: var(--exit); }
.sheet .bigbtn:focus-visible, .sheet .exitbtn:focus-visible,
.deathbar .bigbtn:focus-visible, .deathbar .exitbtn:focus-visible,
.deathbar .iconbtn:focus-visible, #hud .iconbtn:focus-visible,
.panechrome .bigbtn:focus-visible, .panechrome .exitbtn:focus-visible,
.tvover .bigbtn:focus-visible, .tvover .exitbtn:focus-visible {
  outline-color: var(--gold);
}

/* Tertiary text actions only. NEVER for leaving: that is .exitbtn everywhere, so the way out has
   one appearance in the whole game. */
.textbtn { background: none; border: none; color: var(--ink-soft); font: 600 14px var(--ui); cursor: pointer; text-decoration: underline; text-align: center; display: block; margin: 4px auto 0; }
.sheet .textbtn { color: #cbbfa8; }
.joinstatus { font-size: 13.5px; color: var(--crimson); text-align: center; min-height: 18px; }
.backlink { color: var(--ink-soft); font-size: 13px; text-align: center; text-decoration: none; }
.lobbytitle { font-family: var(--display); font-size: 24px; font-weight: 700; text-align: center; }
.lobbyplayers { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.lobbytile {
  display: flex; align-items: center; gap: 7px; background: #fff; border: 1.5px solid var(--line);
  border-radius: 999px; padding: 6px 13px; font: 600 13.5px var(--ui);
}
.lobbytile .dot { width: 11px; height: 11px; border-radius: 50%; }
.lobbytile.bot { opacity: .65; }
/* THE TEAM LOBBY (main-play.js renderTeamLobby): a column per side, rows like the League custom-game lobby.
   Wider card, because two columns of ten-letter names do not fit in 360px. */
.card.teamlobby { width: min(520px, 94vw); }
.lobbyplayers > .teamcols { flex: 1 1 100%; }
.teamcols { display: grid; grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr)); gap: 10px; text-align: left; }
.teamcol { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.teamhead {
  font: 800 11.5px var(--ui); letter-spacing: .1em; text-transform: uppercase;
  padding: 0 2px 4px; border-bottom: 2px solid currentColor; color: var(--ink-soft);
}
.team-blue > .teamhead { color: var(--team-blue); }
.team-red > .teamhead { color: var(--team-red); }
.teamrow {
  display: flex; align-items: center; gap: 6px; min-width: 0; min-height: 34px;
  padding: 6px 8px; border-radius: 8px; background: #fff; border: 1.5px solid var(--line);
  font: 600 13.5px var(--ui); color: var(--ink);
}
.teamrow .dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.teamrow .nm { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.teamrow .tag { flex: none; font: 600 11px var(--ui); color: var(--ink-soft); }
.teamrow .tick, .tvtile .tick { flex: none; color: #2e7d4f; font-weight: 800; }
.teamrow.bot { opacity: .7; font-style: italic; }
.teamrow.off { opacity: .55; }
.teamrow.me { border-color: var(--gold); box-shadow: inset 0 0 0 1px var(--gold); }
.teamrow.empty { background: transparent; border-style: dashed; color: var(--ink-soft); font-weight: 500; }
/* Two sides: the seat dot is the side (main-play.js teamRow, main-host.js seatTile), not the personal colour. */
.team-blue .dot.side { background: var(--team-blue); }
.team-red .dot.side { background: var(--team-red); }
/* The other side's open seat (or its bot, when full) is a button: a tap asks the host for that side. */
button.teamrow { width: 100%; text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background .12s, transform .08s; }
.teamrow.empty.join { justify-content: center; font-weight: 700; }
.team-blue .teamrow.join { color: var(--team-blue); border-color: var(--team-blue); }
.team-red .teamrow.join { color: var(--team-red); border-color: var(--team-red); }
.team-blue .teamrow.join:hover, .team-blue .teamrow.join:active, .team-blue .teamrow.join.asked { background: rgba(63, 140, 255, .14); }
.team-red .teamrow.join:hover, .team-red .teamrow.join:active, .team-red .teamrow.join.asked { background: rgba(229, 72, 77, .14); }
.teamrow.join:active { transform: scale(.98); }
.lobbyclock { font: 700 15px var(--ui); color: var(--ink); text-align: center; font-variant-numeric: tabular-nums; }
/* Ready is the one GO on this card; once pressed it is an outline (adjust), the tap that undoes it. */
.readybtn.on { background: transparent; color: var(--go); }
/* Build stamp (js/ui/version-tag.js): fixed, pointer-inert, anchored off --app-h and never bottom, because a fixed box's bottom is the LAYOUT viewport. */
.vtag {
  position: fixed; z-index: 200; pointer-events: none; user-select: none;
  right: max(6px, env(safe-area-inset-right));
  top: var(--app-h, 100%); transform: translateY(-100%); margin-top: -3px;
  font: 600 9.5px/1 var(--ui); letter-spacing: .04em; color: #fff; opacity: .42;
  text-shadow: 0 1px 2px #000c; white-space: nowrap;
}
.vtag.coarse { margin-top: calc(-1 * max(2px, calc(env(safe-area-inset-bottom) - 12px))); }
/* Stays bottom-RIGHT and LIFTS above the item dock rather than moving. Not on the menu, which has no dock and SCROLLS. */
body.narrow:not(.landing) .vtag { margin-top: calc(-214px - max(16px, env(safe-area-inset-bottom))); }
body.narrow #hud.swapped ~ .vtag, body.narrow .vtag.swapped { margin-top: -3px; }
/* The join card and lobby have no dock: the stamp stays in the corner, not 214px up on the lobby's "Starting in" line. */
body.narrow #joinScreen:not([hidden]) ~ .vtag { margin-top: -3px; }
body.narrow #joinScreen:not([hidden]) ~ .vtag.coarse { margin-top: calc(-1 * max(2px, calc(env(safe-area-inset-bottom) - 12px))); }
.waitnote { color: var(--ink-soft); font-size: 13.5px; text-align: center; }
/* status() writes bad news here once you are seated (main-play.js): the join card is display:none by then. */
.waitnote.bad { color: var(--crimson); }

/* Both sized from the visual viewport: an inset:0 box is NOT the visible area on a phone and the
   HUD is anchored to its bottom edge. */
#game { position: fixed; inset: 0; width: var(--app-w, 100%); height: var(--app-h, 100%); display: block; background: #20261c; }

/* THE PHONE FRAME (ui/viewport.js). The transform is the whole trick: it makes this the containing block for
   EVERY fixed descendant, so sheets, cards, toasts, the death bar and the version tag need no rules of their own.
   The menu backdrop (ui/menu-scene.js) paints the game's own terrain behind the cards, scrim baked in. */
.menuscene {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  width: 100%; height: 100%; display: block;
}
/* BOTH menus must be named: client/index.html uses .card/.sizebox, the packaged template .menucard/.sizepick, and styling one leaves the other see-through over terrain. */
body.landing.hasscene { background: transparent; }
body.landing.hasscene .card,
body.landing.hasscene .menucard,
body.landing.hasscene .sizebox,
body.landing.hasscene .modetabs,
body.landing.hasscene .sizepick { background: #fdfaf2; box-shadow: 0 10px 30px -14px #0007; }
body.landing.hasscene .tagline,
body.landing.hasscene .sizenote,
body.landing.hasscene .modeblurb,
body.landing.hasscene .modehead,
body.landing.hasscene .footnote { text-shadow: 0 1px 0 #f4eee0, 0 0 8px #f4eee0; }
body.landing.hasscene .logo { text-shadow: 0 2px 0 #f4eee0, 0 0 18px #f4eee0cc; }
@media (prefers-reduced-motion: reduce) { .menuscene { display: none; } }

html.phoneframe { background: #0d0f0a; overflow: hidden; }
/* The caption lives in the MARGIN beside the phone, never across it; max-width IS the margin, so it vanishes when there is none. */
html.phoneframe::after {
  content: 'Phone view — built for a handset. Switch views from the menu, or in Settings.';
  position: fixed; left: 14px; bottom: 14px;
  max-width: calc((100vw - var(--app-w, 100vw)) / 2 - 28px);
  font: 500 12px var(--ui); color: #6f7364; pointer-events: none; letter-spacing: .2px;
  overflow: hidden;
}
body.phoneframe {
  position: fixed;
  left: var(--app-x, 0px); top: var(--app-y, 0px);
  width: var(--app-w, 100%); height: var(--app-h, 100%);
  transform: translateZ(0);      /* load-bearing: this transform is what makes the frame the containing block */
  overflow: hidden;
  border-radius: 18px;
  box-shadow: 0 0 0 2px #2b3324, 0 18px 60px rgba(0, 0, 0, .55);
}
/* The menu MUST scroll: on a short window its Start button is below the fold. Game pages never do. */
body.phoneframe.landing { overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; }

/* The menu has no settings sheet, so this is the only way out of phone view. Fixed inside the frame. */
.framebtn {
  /* BOTTOM-left: top-right is the embedding site's own header, bottom-right is the build stamp. */
  position: fixed; z-index: 120; bottom: max(10px, env(safe-area-inset-bottom));
  left: max(10px, env(safe-area-inset-left));
  padding: 7px 12px; border-radius: 999px; cursor: pointer; white-space: nowrap;
  font: 700 11.5px/1 var(--ui); letter-spacing: .04em; text-transform: uppercase;
  background: var(--ink); color: var(--paper); border: 1.5px solid var(--ink);
  box-shadow: 0 3px 12px -4px #0008;
}
.framebtn:hover { background: var(--crimson); border-color: var(--crimson); }
.framebtn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
#hud { position: fixed; inset: 0; width: var(--app-w, 100%); height: var(--app-h, 100%); pointer-events: none; z-index: 10; overflow: hidden; /* the floating stick ring may overhang the edge */ }
#hud > * { pointer-events: auto; }

/* A COLUMN, explicitly: these are shrink-to-fit absolute boxes, so a block zone chip STRETCHED to the build panel's width. */
.topbar {
  position: absolute; top: max(8px, env(safe-area-inset-top)); left: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center;
}
#hud > .topbar { pointer-events: none; }      /* The ID, not the class. Above the stick zone, a LATER sibling: a deep build's lowest coins reach into it and a tap would drive the stick. */
#hud > .topbar { z-index: 1; }
/* The phone layout keys on WIDTH, not a media query: body.narrow comes from --app-w (ui/viewport.js), so it is
   true on a handset AND inside the phone frame, where a max-width query never fires. Topbar pins RIGHT (its
   width follows the build panel); the FEED takes the free left column or it paints over the coins. */
body.narrow .topbar {
  top: calc(max(8px, env(safe-area-inset-top)) + 42px);
  left: auto; right: 8px; transform: none; align-items: flex-end;
}
body.narrow .chip { font-size: 12px; padding: 6px 12px; }
body.narrow .feed {
  top: calc(max(8px, env(safe-area-inset-top)) + 40px);
  right: auto; left: 10px; align-items: flex-start; max-width: 60%;
}
body.narrow .feed.below-toast { top: calc(max(8px, env(safe-area-inset-top)) + 94px); }
body.narrow .feedline { --feed-from: -12px; }   
/* A TEAM MODE on a phone: the minimap is on by default, so the right column STACKS — team chip, minimap, then the
   tabs and the build under it (beside it they ran into the feed) — and the feed drops below the ally bars. The
   selectors outrank the phone block's #minimap / .withmini rules whatever the order. */
body.narrow.teammode #hud #minimap { top: calc(max(8px, env(safe-area-inset-top)) + 80px); width: 104px; height: 104px; }
body.narrow.teammode #hud.withmini .paneltabs { margin-top: 118px; }
body.narrow.teammode #hud.withmini .stackpanel { max-width: min(420px, 84vw); }
body.narrow.teammode #hud.withmini .pickuppanel { margin-right: 0; }
body.narrow.teammode .feed { top: calc(max(8px, env(safe-area-inset-top)) + 56px); }
/* THE RAID'S BOSS BAR (hud.js updateRaid): top centre, with the top bar's column stepped down under it. On a phone it
   takes the row under your own bars, edge to edge, and the right column, the feed and the toasts step down past it.
   It holds the row all raid long (idle it names the next boss), so nothing jumps when a boss wakes. */
.bossbar {
  position: absolute; top: calc(max(8px, env(safe-area-inset-top)) + 2px); left: 50%; transform: translateX(-50%);
  width: min(400px, 44vw); padding: 5px 9px 7px; border-radius: 10px;
  background: var(--hud-bg); border: 1px solid rgba(229, 72, 77, .55);
}
#hud > .bossbar { pointer-events: none; }   /* the ID, not the class: #hud > * */
/* Idle, the CONTENT dims and the backing stays: a translucent box over lit rock was unreadable. */
.bossbar.idle { border-color: var(--hud-line); }
.bossbar.idle .bbhead, .bossbar.idle .bbtrack { opacity: .62; }
.bossbar.idle .bbfill { background: #7a4a44; }
.bbhead { display: flex; align-items: center; gap: 6px; font: 800 11px var(--ui); letter-spacing: .04em; text-transform: uppercase; color: #ffd9c7; }
.bossbar.idle .bbhead { color: #cbbfa8; }
.bbname { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bbpips { display: flex; gap: 3px; }
.bbpips i { width: 6px; height: 6px; border-radius: 50%; border: 1px solid rgba(255, 211, 77, .6); }
.bbpips i.on { background: #ffd34d; border-color: #ffd34d; }
.bbwipes { color: #ff8a80; }
.bbwipes:empty { display: none; }
.bbtrack { position: relative; height: 9px; margin-top: 4px; background: rgba(0, 0, 0, .6); border-radius: 3px; }
.bbfill { position: absolute; inset: 0; background: linear-gradient(#f06a60, #d33b34); border-radius: 3px; transform-origin: left; transition: transform .2s ease-out; }
.bbticks i { position: absolute; top: -2px; bottom: -2px; width: 2px; margin-left: -1px; background: rgba(255, 240, 210, .9); }
body.raidmode .topbar { top: calc(max(8px, env(safe-area-inset-top)) + 50px); }
body.raidmode .sv-toast { top: calc(max(8px, env(safe-area-inset-top)) + 122px); }
body.narrow .bossbar { top: calc(max(8px, env(safe-area-inset-top)) + 50px); left: 10px; right: 10px; width: auto; transform: none; }
body.narrow.raidmode .topbar { top: calc(max(8px, env(safe-area-inset-top)) + 94px); }
body.narrow.raidmode .feed { top: calc(max(8px, env(safe-area-inset-top)) + 96px); }
body.narrow.raidmode #hud #minimap { top: calc(max(8px, env(safe-area-inset-top)) + 132px); }
/* A team-mode phone's top is full (bars, ally bars, feed, chip, minimap, tabs; the raid adds its boss bar): a toast there
   covered the minimap and sat on the feed's text, so it takes the free band above the thumbs instead, centred. Anchored
   off --app-h, never bottom (see .vtag). The raid is a team mode (teams: 1), so this is its rule too. */
body.narrow.teammode .sv-toast {
  top: calc(var(--app-h, 100dvh) - max(16px, env(safe-area-inset-bottom)) - 232px); left: 50%; right: auto;
  transform: translate(-50%, -100%); max-width: calc(var(--app-w, 100vw) - 28px);
}
/* On a phone a feed line wraps to two rather than lose its end (a raid callout's "get behind a pillar!", a long
   "...raised M..." name), and the feed keeps its four newest so the column still ends above the play area's middle. */
body.narrow .feed > .feedline { white-space: normal; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; }
body.narrow .feed > .feedline:nth-last-child(n + 5) { display: none; }
/* A wipe (hud.js showRaidBanner): in fast, held, out slow. Its duration is RAID_BANNER_MS, set inline by showRaidBanner. */
/* 38% and z 3: below the build tabs a phone's column pushes down to ~240px, and above the topbar's z 1. */
.raidbanner {
  position: absolute; top: 38%; left: 50%; transform: translateX(-50%); z-index: 3;
  font: 900 26px var(--ui); letter-spacing: .06em; color: #ffb4b4; white-space: nowrap;
  background: rgba(40, 8, 8, .8); border: 2px solid #e5484d; border-radius: 12px; padding: 8px 22px;
  text-shadow: 0 2px 6px #000;
}
#hud > .raidbanner { pointer-events: none; }
/* A boss down: the same banner in the win gold, its kicker (BOSS DEFEATED, VICTORY) small over the name. */
.raidbanner.win { color: #fff1c2; background: rgba(40, 30, 6, .86); border-color: #ffd34d; text-align: center; box-shadow: 0 0 28px -6px rgba(255, 211, 77, .55); }
.raidbanner small { display: block; font: 800 11px var(--ui); letter-spacing: .14em; color: #ffd34d; margin-bottom: 2px; }
/* A match's end (hud.js showRaidBanner, hud-common BANNER_LOOK): someone else's Royale win is warm, a side's loss is
   quiet slate, never red; --tint is a side's colour or the winner's, on the edge and the kicker. The winner stands left. */
.raidbanner { display: flex; align-items: center; gap: 10px; }
.raidbanner .bbtext { display: block; }
.raidbanner .bbhero { width: 44px; height: 44px; image-rendering: pixelated; flex: none; }
.raidbanner.warm { color: #fff6e4; background: rgba(30, 24, 16, .86); border-color: #e8c37a; text-align: center; }
.raidbanner.warm small { color: #e8c37a; }
.raidbanner.loss { color: #dfe4ec; background: rgba(18, 20, 26, .86); border-color: #8a96a8; text-align: center; box-shadow: none; }
.raidbanner.loss small { color: #aab4c4; }
/* A round's goal (hud-common goalBanner): sky blue, so it never reads as a win, a loss or a wipe. */
.raidbanner.goal { color: #eaf8ff; background: rgba(8, 24, 34, .86); border-color: #5ad1ff; text-align: center; box-shadow: 0 0 24px -6px rgba(90, 209, 255, .5); }
.raidbanner.goal small { color: #5ad1ff; }
.raidbanner.tinted { border-color: var(--tint); }
.raidbanner.tinted small { color: var(--tint); }
.raidbanner[hidden] { display: none; }   /* the flex above would otherwise beat the hidden attribute */
body.narrow .raidbanner { font-size: 20px; padding: 7px 16px; }
.raidbanner.show { animation-name: raidBanner; animation-timing-function: ease; animation-fill-mode: forwards; }
@keyframes raidBanner {
  0% { opacity: 0; transform: translateX(-50%) scale(1.25); }
  8% { opacity: 1; transform: translateX(-50%) scale(1); }
  75% { opacity: 1; }
  100% { opacity: 0; transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) { .raidbanner.show { animation: none; } }   /* shown still; hud.js hides it */
/* The raid's results: one row per boss, how it went and its wipes (hud-common raidRowsHtml). */
.raidrows { display: flex; flex-direction: column; gap: 3px; margin: -2px 0 10px; font: 600 12.5px var(--ui); color: #cbbfa8; }
.raidrow { display: flex; justify-content: space-between; gap: 12px; padding: 3px 8px; border-radius: 6px; background: rgba(255, 255, 255, .04); }
.raidrow.cleared span:first-child { color: #9fe870; }
.raidrow.fought span:first-child { color: #ff8a80; }
.raidrow.unreached { opacity: .55; }
.chip {
  font: 700 14px var(--ui); color: #fff; background: var(--hud-bg);
  border: 1px solid var(--hud-line); border-radius: 999px; padding: 7px 16px; white-space: nowrap;
}
.chip .warn { color: #ff9a5c; }

.paneltabs { display: flex; gap: 4px; justify-content: center; margin-top: 6px; pointer-events: none; }
.paneltab {
  pointer-events: auto; cursor: pointer;
  font: 700 10px var(--ui); letter-spacing: .04em; text-transform: uppercase;
  color: #a99e88; background: var(--hud-bg); border: 1px solid var(--hud-line);
  border-radius: 999px; padding: 3px 10px;
}
.paneltab.on { color: #ffd34d; border-color: var(--gold); }
/* The coin may land off-screen in a scrolled or folded panel, so the always-visible TAB is what moves. */
.paneltab.gained { animation: tabGained .9s ease both; }
@keyframes tabGained {
  0%   { color: #ffd34d; border-color: var(--gold); transform: scale(1); box-shadow: 0 0 0 0 rgba(255,211,77,.6); }
  22%  { color: #fff3c4; border-color: #ffd34d; transform: scale(1.22); box-shadow: 0 0 0 6px rgba(255,211,77,0); }
  100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255,211,77,0); }
}
.stackpanel.gained { animation: panelGained .9s ease both; }
@keyframes panelGained {
  0%, 100% { transform: none; }
  25% { transform: scale(1.045); }
}
@media (prefers-reduced-motion: reduce) {
  .paneltab.gained, .stackpanel.gained { animation: none; }
  .paneltab.gained { color: #fff3c4; border-color: #ffd34d; }
}
/* Must clip horizontally: an equipment line is nowrap and pushed the panel wider than the screen. The pickup log keeps the old width. */
.pickuppanel {
  flex-direction: column; align-items: stretch; gap: 2px; overflow-x: hidden;
  max-width: min(560px, 92vw); max-height: 26vh;
}
.pickupline {
  pointer-events: auto; min-width: 0;
  font: 500 10.5px var(--ui); color: #e8ddc8; background: var(--hud-bg);
  border: 1px solid var(--hud-line); border-radius: 7px; padding: 2px 8px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pickupline.eq { border-left: 3px solid var(--gold); }
.pickupline.gn { border-left: 3px solid #b789ff; }
.pickupempty { font: 500 10.5px var(--ui); color: #a99e88; padding: 2px 8px; }

.stackpanel {
  margin-top: 6px; display: flex; flex-wrap: wrap; gap: 6px;
  justify-content: center; align-items: flex-start;
  /* A hard stop, not a hope: a 40-stack run occupies exactly the same rectangle as a 4-stack one. */
  max-width: min(420px, 84vw); max-height: 17vh; overflow-y: auto; overflow-x: hidden;
  scrollbar-width: thin;
  /* The panel stays click-through — right-click-to-move through the GAPS must keep working; only chips take pointer events. */
  pointer-events: none;
}
.stackpanel::-webkit-scrollbar { width: 5px; }
.stackpanel::-webkit-scrollbar-thumb { background: #ffffff26; border-radius: 3px; }
/* A stack chip is a COIN: one emoji, one count. Names and effects live in the tooltip, or a deep build covers the play area. */
.stackchip {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; cursor: pointer;
  font-size: 16px; line-height: 1;
  color: #e8ddc8; background: var(--hud-bg);
  border: 1px solid var(--hud-line); border-radius: 50%;
  pointer-events: auto;   
  transition: border-color .15s ease, transform .12s ease;
  /* Keep emoji off the UI face: several of them render as tofu. */
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", var(--ui);
}
.stackchip:focus-visible { border-color: var(--gold); transform: translateY(-1px); }
@media (hover: hover) { .stackchip:hover { border-color: var(--gold); transform: translateY(-1px); } }

/* A switched-off item stays in the editor, greyed: that is the whole difference from Remove from the game. */
.itemonoff { display:flex; align-items:center; gap:8px; margin:6px 0 2px; font:600 12.5px var(--ui); cursor:pointer }
.itemonoff input { accent-color: var(--gold) }
.itemcard.isoff > summary { opacity:.55 }
.itemcard.isoff .itemfields { opacity:.55 }
.offtag { margin-left:8px; font:700 9.5px var(--ui); letter-spacing:.1em; text-transform:uppercase;
  color:#8a7f6c; border:1px solid #c9bfa9; border-radius:999px; padding:1px 7px }

/* Hurt vignette: an INSET box-shadow, not a radial background, so mid-screen is untouched at any strength.
   #hud > .hurtvig, NOT .hurtvig: #hud > * sets pointer-events auto and an ID beats a class, and a full-screen
   overlay that accepts pointers swallows every tap. No z-index: FIRST child paints it under the HP bar. */
#hud > .hurtvig {
  position: absolute; inset: 0;
  pointer-events: none; opacity: 0;
  box-shadow: inset 0 0 12vmin 3vmin rgba(190, 20, 24, .85);
  transition: opacity var(--vig-ms, 450ms) ease-out;
}
/* With the ID: the rule above is (1,1,0) and a class pair (0,2,0) never won. */
#hud > .hurtvig.fatal { transition-duration: .12s; }
@media (prefers-reduced-motion: reduce) {
  #hud > .hurtvig { transition: opacity .3s linear; }
}

/* Weapon passives get a warm RING, not a new shape (circle already means stacking passive). On the chip's own border; the proc flicker uses ::before. */
.stackchip.wpn { border-color: rgba(255,178,86,.75); }
.stackchip.wpn:focus-visible { border-color: var(--gold); }
@media (hover: hover) { .stackchip.wpn:hover { border-color: var(--gold); } }

/* Passive-fired flicker (Sim.notePassive via hud.flickerPassives). On ::before, not the chip's border: A CSS
   ANIMATION BEATS A NORMAL DECLARATION, so animating border-color here deletes .stackchip:hover for 420ms. */
.stackchip::before {
  content: ""; position: absolute; inset: -3px;
  border-radius: 50%; pointer-events: none;
  border: 2px solid transparent;
  opacity: 0;
}
.stackchip.procced::before { animation: passiveProc var(--proc-ms, 420ms) ease-out; }
@keyframes passiveProc {
  0%   { opacity: 0;   border-color: rgba(255,211,77,0);   transform: scale(.82); }
  18%  { opacity: 1;   border-color: rgba(255,232,138,.95); transform: scale(1.06); }
  46%  { opacity: .25; border-color: rgba(255,211,77,.5);  transform: scale(1); }
  64%  { opacity: .9;  border-color: rgba(255,232,138,.8); transform: scale(1.03); }
  100% { opacity: 0;   border-color: rgba(255,211,77,0);   transform: scale(1); }
}
/* Reduced motion keeps the INFORMATION (which passive fired), held still instead of pulsed. */
@media (prefers-reduced-motion: reduce) {
  .stackchip.procced::before {
    animation: none; opacity: 1; border-color: rgba(255,211,77,.8);
    transition: opacity .3s ease .12s;
  }
}
.stackchip .sn {
  position: absolute; right: -3px; bottom: -3px; min-width: 15px; height: 15px;
  padding: 0 3px; border-radius: 8px; box-sizing: border-box;
  background: var(--gold); color: #1a1206;
  font: 800 10px var(--ui); font-variant-numeric: tabular-nums;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(0,0,0,.45);
}
.stackchip.s1 .sn { display: none; }

/* SHAPE TELLS YOU THE SLOT: circle stacking passive, triangle Active trinket, square gear. Colour is spent on rarity, and shape survives colour-blindness. */
.chip-tri {
  border-radius: 4px;
  clip-path: polygon(50% 2%, 98% 96%, 2% 96%);
  /* A triangle wastes its lower corners: the art sits low and small or it overflows the sloped edges. */
  align-items: flex-end; padding-bottom: 8%;
}
.chip-tri .si { inset: auto 22% 6% 22%; width: 56%; height: 56%; }
.chip-tri .sg { font-size: .78em; margin-bottom: -2px; }
.chip-tri .sn { right: -1px; bottom: -1px; }
.chip-sq { border-radius: 6px; }
/* Generated icon art, TRANSPARENT cut-outs (tools/surviversus-icons.mjs). A 404 removes the element and the emoji under it shows through. */
.stackchip .si {
  position: absolute; inset: 13%; width: 74%; height: 74%;
  object-fit: contain; pointer-events: none;
}

/* Compact mode for a deep run, toggled per device (#compactTog). */
.stackpanel.compact { gap: 3px; max-height: 11vh; max-width: min(340px, 80vw); }
.stackpanel.compact .stackchip { width: 22px; height: 22px; font-size: 12px; }
.stackpanel.compact .stackchip .sn {
  min-width: 12px; height: 12px; font-size: 8px; right: -2px; bottom: -2px; padding: 0 2px;
}
.compacttog { font-size: 12px; line-height: 1; padding: 3px 8px; }
.compacttog[aria-pressed="true"] { color: #ffd34d; border-color: var(--gold); }
/* Name and effect are tooltip-only, kept in the DOM and hidden visually: an aria-label would lose the effect text. */
.stackchip .st, .stackchip .se {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
@media (max-width: 620px) {
  .stackpanel { max-height: 15vh; gap: 3px; }
  .stackchip { width: 26px; height: 26px; font-size: 14px; }
  .stackchip .sn { min-width: 13px; height: 13px; font-size: 9px; }
}

.feed {
  position: absolute; top: calc(max(8px, env(safe-area-inset-top)) + 4px); right: max(10px, env(safe-area-inset-right));
  display: flex; flex-direction: column; gap: 3px; align-items: flex-end;
  max-width: 60vw;
}
#hud > .feed { pointer-events: none; }        /* The ID, not the class. SEE-THROUGH at 45%: at the HUD's 72% five lines were an opaque slab over the play area. */
.feedline {
  --feed-from: 12px;
  font: 600 12px var(--ui); color: #e8ddc8; background: rgba(16, 13, 10, .45);
  text-shadow: 0 1px 2px rgba(0, 0, 0, .9), 0 0 1px rgba(0, 0, 0, .7);
  border: 1px solid var(--hud-line); border-radius: 8px; padding: 4px 9px;
  animation: feedin .25s ease, feedout .5s ease 5.5s forwards;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.feedline .k { font-weight: 800; }
@keyframes feedin { from { opacity: 0; transform: translateX(var(--feed-from)); } }
@keyframes feedout { to { opacity: 0; } }

/* viewport-fit=cover means LEFT and RIGHT insets matter too, or landscape puts the gear in the corner radius. */
.statusrow {
  position: absolute; top: calc(max(8px, env(safe-area-inset-top)) + 2px); left: calc(max(10px, env(safe-area-inset-left)) + 46px);
  display: flex; align-items: center; gap: 8px;
}
#hud > .statusrow { pointer-events: none; }   /* the ID, not the class: #hud > * */
.lvlbadge {
  width: 34px; height: 34px; border-radius: 50%; background: var(--hud-bg); border: 1.5px solid var(--gold);
  color: #ffd34d; font: 800 15px var(--ui); display: flex; align-items: center; justify-content: center;
}
.bars { display: flex; flex-direction: column; gap: 3px; }
.hpbar { position: relative; width: 130px; height: 15px; background: rgba(0,0,0,.55); border-radius: 8px; overflow: hidden; border: 1px solid var(--hud-line); }
/* The pale GHOST trails the fill by a beat, so the size of a hit is readable off the bar. */
.hpghost { position: absolute; inset: 0; width: 100%; background: #f6c4b6; opacity: .6; transform-origin: left; transition: transform .45s ease-out .2s; }
.hpfill { position: absolute; inset: 0; width: 100%; background: linear-gradient(#f06a60, #d33b34); transform-origin: left; transition: transform .12s ease-out; }
.shfill { position: absolute; left: 0; top: 0; height: 5px; width: 0; background: #5ab0ff; border-radius: 0 3px 3px 0; }
.hptext { position: absolute; inset: 0; font: 800 10.5px var(--ui); color: #fff; display: flex; align-items: center; justify-content: center; text-shadow: 0 1px 2px #000; }
.xpbar { width: 130px; height: 5px; background: rgba(0,0,0,.55); border-radius: 4px; overflow: hidden; }
.xpfill { height: 100%; width: 0; background: #41e0d8; transition: width .18s ease-out; }
.lvlbadge.pop, .dmgbadge.pop { animation: badgePop .38s cubic-bezier(.2, 1.6, .4, 1) both; }
@keyframes badgePop { 0% { transform: scale(1); } 40% { transform: scale(1.35); } 100% { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) { .lvlbadge.pop, .dmgbadge.pop { animation: none; } .hpghost, .hpfill, .xpfill { transition: none; } }

#minimap {
  position: absolute; right: max(10px, env(safe-area-inset-right)); top: calc(max(8px, env(safe-area-inset-top)) + 40px);
  width: 130px; height: 130px; border-radius: 10px; border: 1px solid var(--hud-line);
  background: #000a; opacity: .92; pointer-events: none;
}

/* joystick (left by default; .swapped flips) */
/* INERT UNTIL THERE IS A THUMB: this invisible 46% x 55% box swallowed every click in the bottom-left third
   on a PC. hastouch is set by hud.js on a coarse pointer or the first real touch, never coarse alone.
   Height comes from CONFIG.stick.zoneTop; 75% is only the pre-run fallback. */
.joyzone { position: absolute; left: 0; right: 0; bottom: 0; height: 75%; touch-action: none; }
/* #hud > .joyzone, NOT .joyzone: #hud > * sets pointer-events auto and an ID outranks a class. Same trap as .hurtvig. */
#hud > .joyzone { pointer-events: none; }
#hud.hastouch > .joyzone { pointer-events: auto; }
#hud:not(.hastouch) .joybase { display: none; }
.joybase {
  position: absolute; border-radius: 50%;
  background: rgba(255,255,255,.07); border: 2px solid rgba(255,255,255,.20);
  pointer-events: none; transition: background .12s ease, border-color .12s ease;
}
.joybase.grabbed { background: rgba(255,255,255,.13); border-color: rgba(255,255,255,.38); }
.joynub {
  position: absolute; left: 50%; top: 50%; width: 46%; height: 46%; border-radius: 50%;
  background: rgba(255,255,255,.34); border: 2px solid rgba(255,255,255,.5);
  transform: translate(-50%, -50%);
}
.joybase.grabbed .joynub { background: rgba(255,255,255,.5); }

.itemdock {
  position: absolute; right: max(12px, env(safe-area-inset-right)); bottom: max(16px, env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 10px; align-items: flex-end;
}
#hud.swapped .itemdock { right: auto; left: max(12px, env(safe-area-inset-left)); align-items: flex-start; }
/* Only the dock's BUTTONS take a touch: its gaps and margin fall through to the stick zone under it. */
#hud > .itemdock { pointer-events: none; }
.itemdock button { pointer-events: auto; }
/* SQUARE, matching .chip-sq in the build panel: the dock and the panel describe the same items and
   must not draw them in two alphabets. */
.itemchip {
  width: 54px; height: 54px; border-radius: 7px; background: var(--hud-bg);
  border: 2px solid var(--hud-line); color: #fff; font: 700 10px var(--ui);
  display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0;
  overflow: visible; position: relative;
  touch-action: none; -webkit-user-select: none; user-select: none;
}
/* Chip and lock share a SHRINK-WRAPPING wrapper, so the dock stays one chip wide and the padlock keeps its corner. */
.slotwrap { position: relative; display: flex; }
/* The offered chip (2026-09-05): absolute, hung off the wrapper toward mid-screen, so the wrapper keeps the held chip's size. */
.itemchip.offerchip {
  position: absolute; top: 0; right: calc(100% + 10px); left: auto;
  touch-action: none; -webkit-user-select: none; user-select: none;
  animation: offerChipIn .18s cubic-bezier(.2, 1.4, .4, 1) both;
}
#hud.swapped .itemchip.offerchip { right: auto; left: calc(100% + 10px); }
@keyframes offerChipIn { from { opacity: 0; transform: scale(.7); } }
/* On ::before, same clock as .actbtn.offering: an animated box-shadow would beat the rarity border. */
.itemchip.offerchip::before {
  content: ""; position: absolute; inset: -4px; border-radius: 10px; pointer-events: none;
  border: 2px solid rgba(110, 231, 110, .7);
  animation: actOfferGlow 1.25s ease-in-out infinite;
}
/* The backpack (team modes' gear slice): shown, never pressed — a still dashed outline beside the weapon, one column
   further out while a weapon is offered. A div, so the dock's `button { pointer-events: auto }` never reaches it. */
.itemchip.packchip { position: absolute; top: 0; right: calc(100% + 10px); cursor: default; pointer-events: none; }
.slotwrap:has(> .offerchip:not([hidden])) > .itemchip.packchip { right: calc(200% + 20px); }
#hud.swapped .itemchip.packchip { right: auto; left: calc(100% + 10px); }
#hud.swapped .slotwrap:has(> .offerchip:not([hidden])) > .itemchip.packchip { left: calc(200% + 20px); }
.itemchip.packchip { outline: 2px dashed rgba(110, 231, 110, .55); outline-offset: 3px; }
.itemchip.packchip::before {
  content: "PACK"; position: absolute; bottom: calc(100% + 7px); left: 50%; transform: translateX(-50%);
  font: 800 9px var(--ui); letter-spacing: .08em; color: #9fe0a0; text-shadow: 0 1px 2px #000; pointer-events: none;
}
.offercap {
  position: absolute; bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%);
  font: 800 9px var(--ui); letter-spacing: .08em; color: #6ee76e; white-space: nowrap;
  text-shadow: 0 1px 2px #000; pointer-events: none;
}
.itemchip.holding { box-shadow: 0 0 0 3px rgba(255, 211, 77, .55) inset; }
@media (prefers-reduced-motion: reduce) { .itemchip.offerchip, .itemchip.offerchip::before { animation: none; } }
.slotlock {
  position: absolute; top: -9px; right: -9px; width: 24px; height: 24px; padding: 0;
  border-radius: 50%; border: 1.5px solid rgba(255,255,255,.35); background: rgba(16,13,10,.9);
  font-size: 12px; line-height: 1; color: #fff; opacity: .7; cursor: pointer;
}
.slotlock.on { border-color: var(--gold); background: rgba(90,70,20,.95); opacity: 1; box-shadow: 0 0 8px rgba(255,211,77,.35); }
/* Adrenaline is information, not a button: the burst fires on its own. A conic arc fills as --cd runs 0..1. */
.adrpip {
  width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  font-size: 12px; color: rgba(255,255,255,.4); background: rgba(0,0,0,.55);
  border: 2px solid rgba(255,211,77,.35); position: relative;
}
.adrpip::before {
  content: ""; position: absolute; inset: -2px; border-radius: 50%;
  background: conic-gradient(var(--gold) calc(var(--cd, 0) * 360deg), transparent 0);
  -webkit-mask: radial-gradient(circle, transparent 9px, #000 10px); mask: radial-gradient(circle, transparent 9px, #000 10px);
}
.adrpip.ready { color: var(--gold); border-color: var(--gold); }
.adrpip.on { color: #fff3b0; border-color: #fff3b0; animation: adrPulse .35s ease-in-out infinite alternate; }
@keyframes adrPulse { from { transform: scale(1); } to { transform: scale(1.18); } }
@media (prefers-reduced-motion: reduce) { .adrpip.on { animation: none; } }
/* The warp pip (team modes): the adrenaline pip's ring in cyan, the cooldown's seconds inside; white while channelling. */
.adrpip.warppip { border-color: rgba(95, 212, 255, .35); color: #5fd4ff; font: 800 10px var(--ui); }
.adrpip.warppip::before { background: conic-gradient(#5fd4ff calc(var(--cd, 0) * 360deg), transparent 0); }
.adrpip.warppip.on { color: #e9f6ff; border-color: #e9f6ff; animation: none; }
.adrpip.warppip.on::before { background: conic-gradient(#e9f6ff calc(var(--cd, 0) * 360deg), transparent 0); }
.lvlbadge.max { font-size: 10px; letter-spacing: -.02em; }
/* Your side's other heroes under your bars: a dot and a 20px HP bar each, or the respawn seconds (hud.js _allyBars). */
.allybars { display: flex; flex-wrap: wrap; gap: 3px 6px; max-width: 130px; height: 9px; overflow: hidden; }
.allybars .ally { display: flex; align-items: center; gap: 2px; }
.allybars .ally i { width: 6px; height: 6px; border-radius: 50%; box-shadow: 0 0 0 1px #000a; }
.allybars .ally > b { display: block; width: 20px; height: 4px; background: rgba(0,0,0,.6); border-radius: 2px; overflow: hidden; }
.allybars .ally > b > b { display: block; height: 100%; background: var(--team-blue); }
.allybars .ally.down { opacity: .75; }
.allybars .ally em { font: 800 9px var(--ui); font-style: normal; color: #bfe3ff; text-shadow: 0 1px 2px #000; }
/* A team mode's scores in the top chip and on the TV rail: each number in its side's colour. */
.tsc { font-weight: 800; }
/* A pseudo-element on purpose: hud.js rewrites these buttons' innerHTML twelve times a second and would wipe a real child. */
.itemchip::after, .actbtn::after {
  content: ""; position: absolute; top: -5px; left: -5px;
  width: 13px; height: 13px; pointer-events: none;
  background: #d9cdb4; box-shadow: 0 0 0 1px rgba(0,0,0,.55);
}
.itemchip::after { border-radius: 3px; }             /* square: gear */
.actbtn::after {                                     /* triangle: an Active */
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
  box-shadow: none; background: #ffd34d; width: 14px; height: 13px;
}
.actbtn.empty::after { background: rgba(255,255,255,.3); }
.itemchip.slotempty::after { background: rgba(255,255,255,.3); }
.itemchip canvas { width: 40px; height: 40px; image-rendering: pixelated; }
/* Generated gear art (assets/icons/weapons|armor) sits over the pixel sprite; a 404 removes it and the sprite shows. */
.itemchip .si {
  position: absolute; inset: 9%; width: 82%; height: 82%;
  object-fit: contain; pointer-events: none;
}
.itemchip .si ~ canvas, .itemchip .si ~ .chiptxt { visibility: hidden; }
/* The REAL per-hit damage after scaling and multipliers, not the item's printed figure, so it matches the floaters. */
.itemchip .dmgbadge {
  position: absolute; right: -3px; bottom: -4px;
  min-width: 20px; padding: 0 4px; height: 16px; box-sizing: border-box;
  border-radius: 8px; background: #d33b34; color: #fff;
  border: 1px solid rgba(0,0,0,.5);
  font: 800 10px var(--ui); font-variant-numeric: tabular-nums;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.itemchip .dmgbadge.arm { background: #3f7fbf; }
/* Five rarities, five rows: a missing common left the chip neutral while the canvas dock drew it
   grey. RARITY_COLORS (core/config.js). */
.itemchip.r-common { border-color: #b8b8b8; }
.itemchip.r-uncommon { border-color: #4ade80; }
.itemchip.r-rare { border-color: #38bdf8; }
.itemchip.r-epic { border-color: #c084fc; }
.itemchip.r-legendary { border-color: #fbbf24; }
.itemchip:empty { opacity: .4; }
/* an empty slot still shows its glyph, just faded — see _chip() in hud.js */
.itemchip.slotempty { border-style: dashed; }
.itemchip.slotempty canvas { opacity: .3; }
.actbtn {
  position: relative;   
  width: 92px; height: 92px; border-radius: 24px; border: 3px solid #ffd34d;
  background: radial-gradient(circle at 35% 30%, #4a3b17, #2b230e);
  color: #ffd34d; font: 800 13px var(--ui); letter-spacing: .5px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; text-align: center; padding: 6px;
  box-shadow: 0 6px 18px -6px #000c;
  touch-action: manipulation;
}
.actbtn:active { transform: scale(.94); }
/* Column layout so the art and the name never cover each other; the label settles ties between similar bombs. */
.actbtn { flex-direction: column; gap: 2px; line-height: 1.05; }
.acticon { width: 40px; height: 40px; display: none; }
/* A CLASS, not :has(): hud.js toggles state every frame, and :has is the one selector an older phone browser drops. */
.actbtn.withkey .acticon { width: 34px; height: 34px; }
.actbtn.withkey .acticon .ag { font-size: 26px; line-height: 34px; }
.acticon.on { display: block; }
.acticon img { width: 100%; height: 100%; object-fit: contain; display: block; }
.acticon img.pix { image-rendering: pixelated; }
.acticon .ag { font-size: 30px; line-height: 40px; }
.actbtn.offering .acticon { display: none; }
.actbtn:not(.empty) #actLabel { font-size: 10px; letter-spacing: .3px; }
.actbtn.offering #actLabel, .actbtn.empty #actLabel { font-size: 13px; }
.actbtn.empty { border-color: rgba(255,255,255,.2); color: rgba(255,255,255,.4); background: var(--hud-bg); }
.actbtn span { overflow: hidden; }

/* Top-left, NOT bottom: everything below CONFIG.stick.zoneTop is the joystick zone. Top-left in swapped mode too. */
.pausebtn {
  position: absolute; left: max(10px, env(safe-area-inset-left)); top: calc(max(8px, env(safe-area-inset-top)) - 1px);
  width: 40px; height: 40px; border-radius: 50%; background: var(--hud-bg); color: #cbbfa8;
  border: 1px solid var(--hud-line); font-size: 19px; cursor: pointer; z-index: 2;
}

/* Offer prompt: an ALTAR or LIAR'S CHEST only since 2026-09-05. 236 = the dock (92+10+54+10+54) + the padlock's 9px + 7. */
.offerprompt {
  position: absolute; z-index: 40;
  right: max(12px, env(safe-area-inset-right)); bottom: calc(max(16px, env(safe-area-inset-bottom)) + 236px);
  width: min(300px, calc(var(--app-w, 100vw) - 24px));
  background: var(--hud-bg); border: 1.5px solid var(--hud-line); border-left-width: 4px;
  border-radius: 14px; padding: 8px 12px; color: #f0e8d6;
  font: 500 12px/1.4 var(--ui); text-align: left;
  -webkit-user-select: none; user-select: none;
  box-shadow: 0 12px 32px -14px #000d;
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  animation: offerIn .16s ease both;
}
/* Inert, with the ID: #hud > * beats a class, and as a class this 300px card swallowed the thumb's landing. */
#hud > .offerprompt { pointer-events: none; }
#hud.swapped .offerprompt { right: auto; left: max(12px, env(safe-area-inset-left)); }
@keyframes offerIn { from { opacity: 0; transform: translateY(6px); } }
.offer-head { font: 700 10px var(--ui); letter-spacing: .12em; text-transform: uppercase; }
.offer-name { font: 700 14px var(--ui); margin: 1px 0 2px; }
.offer-stats { color: #cbbfa8; font-size: 11.5px; }
.offer-act { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--hud-line); font-size: 12px; color: #e8ddc8; }
.offer-act + .offer-act { margin-top: 4px; padding-top: 4px; border-top: none; }
.offer-act b { color: #ffd34d; }
.offer-act.bad b, .offer-act.bad { color: #ff8a80; }
@media (prefers-reduced-motion: reduce) { .offerprompt { animation: none; } }

/* The pop fires ONCE because hud.js adds the class only on the transition.
   THE GLOW LIVES ON A PSEUDO-ELEMENT: a running animation beats a normal declaration, so animating box-shadow
   on the button silently killed .actbtn.holding and .actbtn.held — the one state that needs it. */
.actbtn.offering { border-color: #6ee76e; color: #6ee76e; }
.actbtn.offering::before {
  content: ""; position: absolute; inset: -3px;
  border-radius: 27px; pointer-events: none;
  border: 2px solid rgba(110, 231, 110, .55);
  animation: actOfferPop .34s cubic-bezier(.2, 1.5, .4, 1) 1,
             actOfferGlow 1.25s ease-in-out .34s infinite;
}
@keyframes actOfferPop {
  0%   { transform: scale(1);    opacity: .95; border-width: 3px; }
  45%  { transform: scale(1.16); opacity: 0;   border-width: 2px; }
  100% { transform: scale(1);    opacity: .55; border-width: 2px; }
}
@keyframes actOfferGlow {
  0%, 100% { transform: scale(1);    opacity: .35; box-shadow: 0 0 8px 0 rgba(110,231,110,.25); }
  50%      { transform: scale(1.06); opacity: .95; box-shadow: 0 0 20px 2px rgba(110,231,110,.5); }
}
.actbtn.offering #actLabel { animation: actBreathe 1.25s ease-in-out infinite; }
@keyframes actBreathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.16); }
}
/* A wiggle when the button changes what it does (hud.js re-adds .ctx); it beats .actbtn:active's transform for 340ms. */
.actbtn.ctx { animation: actWiggle .34s ease both; }
@keyframes actWiggle {
  0%   { transform: rotate(0) scale(1); }
  25%  { transform: rotate(-4deg) scale(1.07); }
  55%  { transform: rotate(3deg) scale(1.05); }
  80%  { transform: rotate(-1.5deg) scale(1.01); }
  100% { transform: rotate(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .actbtn.offering::before {
    animation: none; opacity: .85; border-width: 3px;
  }
  .actbtn.offering #actLabel, .actbtn.ctx { animation: none; }
  .feedline { animation: feedout .5s ease 5.5s forwards; }
  .menucard:hover, .stackchip:hover { transform: none; }
}

/* Filled in by hud.js from the live bindings, and empty on a touch screen. */
.actkey {
  font: 800 8px var(--ui); letter-spacing: .06em; opacity: .7;
  line-height: 1.1; white-space: nowrap;
}
/* Measured: HOLD RIGHT-CLICK is 71px with this tracking on the fallback font, inside the narrow
   layout's 66px of content box. */
.actbtn.offering .actkey { letter-spacing: 0; }
/* a passive Active-slot item (the revive Ankh) — held, never pressed */
.actbtn.passiveitem { border-color: #b789ff; color: #b789ff; }
/* a Guardian Angel with no one down: kept on a press (hud-common angelState; panes.js drawAct dims the same) */
.actbtn.idle { border-color: rgba(255, 255, 255, .4); color: rgba(255, 255, 255, .55); }
.actbtn.idle .acticon { opacity: .45; }
.actbtn.holding { box-shadow: 0 0 0 3px rgba(255, 211, 77, .55) inset; }
.actbtn.held { box-shadow: 0 0 0 6px rgba(110, 231, 110, .7) inset; }

/* first-run coaching — shown once, dismissed by the first real input */
.firsthint {
  position: absolute; left: 50%; top: 42%; transform: translate(-50%, -50%);
  z-index: 55; width: min(340px, calc(var(--app-w, 100vw) - 40px));
  background: var(--hud-bg); border: 1.5px solid var(--gold); border-radius: 16px;
  padding: 18px 20px; text-align: center; color: #f0e8d6;
  box-shadow: 0 14px 40px -14px #000d;
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  animation: hintIn .35s ease both;
}
/* CLICK-THROUGH. #hud > .firsthint, NOT .firsthint: as a class this card swallowed the very drag it asks for.
   .hintgo is a grandchild, so > * never matches it and its own declaration stands. */
#hud > .firsthint { pointer-events: none; }
.hintgo { pointer-events: auto; }
.firsthint .hintline { margin: 0 0 9px; font: 500 14px/1.5 var(--ui); color: #cbbfa8; }
.firsthint .hintline b { color: #f0e8d6; font-weight: 700; }
.firsthint .hintline:first-child { font-size: 15px; color: #f0e8d6; }
/* What wins, in the goal banner's sky blue: the one line that must not read as small print. */
.firsthint .hintline.goal { color: #7fdcff; font-weight: 600; }
.hintgo {
  margin-top: 4px; font: 700 14px var(--ui); padding: 9px 22px; border-radius: 10px;
  border: 1px solid var(--gold); background: transparent; color: #ffd34d; cursor: pointer;
}
.hintgo:active { transform: scale(.96); }
@keyframes hintIn { from { opacity: 0; transform: translate(-50%, -46%); } }
.firsthint.out { animation: hintOut .3s ease forwards; }
@keyframes hintOut { to { opacity: 0; transform: translate(-50%, -54%); } }
@media (prefers-reduced-motion: reduce) {
  .firsthint, .firsthint.out { animation-duration: .01s; }
}

.tooltip {
  position: fixed; z-index: 60; background: var(--hud-bg); color: #f0e8d6;
  border: 1px solid var(--hud-line); border-radius: 12px; padding: 10px 14px;
  font: 500 13px/1.5 var(--ui); max-width: 260px; pointer-events: none;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
.tooltip .tname { font-weight: 800; margin-bottom: 3px; }
.tooltip .tline { color: #cbbfa8; }
/* The gear card that opens itself while you stand on a weapon or armor outside Royale (hud.js _offerCard): never takes a tap. */
.tooltip.offercard { pointer-events: none; max-width: 240px; }

/* toasts. 74px under the top inset clears the zone chip AND the Build/Picked-up tabs, which end at 67. */
.sv-toast {
  position: fixed; top: calc(max(8px, env(safe-area-inset-top)) + 74px); left: 50%;
  transform: translateX(-50%); z-index: 80;
  width: max-content; max-width: min(440px, calc(var(--app-w, 100vw) - 28px));
  font: 600 13.5px var(--ui); color: #f0e8d6; background: var(--hud-bg);
  border: 1.5px solid var(--gold); border-radius: 999px; padding: 9px 20px; margin: 0;
  box-shadow: 0 8px 24px -10px #000e; animation: svToast 3.4s ease forwards;
  pointer-events: none; text-align: center;
}
.sv-toast.bad { border-color: #d33b34; }
.sv-toast.info { border-color: var(--hud-line); }
/* The drop-in rides margin-top, not transform: the phone block pins the toast with transform none, and a running animation beats that. */
@keyframes svToast { 0% { opacity: 0; margin-top: -10px; } 10%, 80% { opacity: 1; margin-top: 0; } to { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .sv-toast { animation-duration: .01s; animation-delay: 3s; } }

.sheet {
  position: fixed; inset: 0; z-index: 90; background: rgba(10, 8, 6, .78);
  height: var(--app-h, 100%); overflow-y: auto;
  display: flex; align-items: center; justify-content: center; padding: 18px;
  align-items: safe center;
}
.sheetcard {
  background: #241d16; border: 1.5px solid #4a3f30; border-radius: 18px; padding: 24px;
  width: min(420px, 94vw);
  /* 88vh is 88% of the LARGE viewport, taller than a phone screen: the list ran off the bottom with
     no way to reach the Exit link under it. */
  max-height: min(88vh, calc(var(--app-h, 100vh) - 40px)); overflow-y: auto;
  display: flex; flex-direction: column; gap: 14px; color: #f0e8d6;
}
.sheetcard h2 { font-family: var(--display); margin: 0; font-size: 26px; text-align: center; }
.setrow { display: flex; align-items: center; justify-content: space-between; gap: 14px; font: 600 14px var(--ui); }
.setrow input[type="range"] { width: 130px; }
.setcode { display: flex; align-items: center; gap: 10px; }
.setcode b { font: 800 16px/1 ui-monospace, Consolas, monospace; letter-spacing: 3px; color: var(--crimson); }
.setcode .ghostbtn2 { flex: 0 0 auto; padding: 4px 10px; font-size: 12px; }
.setnote { font: 500 12px/1.35 var(--ui); color: #a99a80; margin: -6px 0 2px; }
/* THE LIST SCROLLS, THE BUTTONS DO NOT: the card stops scrolling (overflow hidden beats .sheetcard's auto) and the standings take the leftover height. */
.overcard { text-align: center; overflow: hidden; position: relative; }
/* A team win: which side took it, under your Victory / Defeat. */
.oversub { font: 700 14px var(--ui); color: #cbbfa8; margin: -4px 0 8px; }
.overcard .minbtn, .deathbar .minbtn { position: absolute; top: 10px; right: 10px; width: 34px; height: 34px; font-size: 18px; }
.overpill, .deathpill {
  /* Anchored off --app-h, never bottom: a FIXED box's bottom is the layout viewport. This pill is the only way back from a minimized card. */
  position: fixed; left: 50%; z-index: 95;
  top: var(--app-h, 100%); transform: translate(-50%, -100%);
  margin-top: calc(-1 * (max(16px, env(safe-area-inset-bottom)) + 12px));
  padding: 10px 18px; border-radius: 999px; border: 1.5px solid var(--gold);
  background: rgba(36, 29, 22, .96); color: #ffd34d; font: 700 14px var(--ui); letter-spacing: .04em;
  box-shadow: 0 10px 28px -12px #000e; cursor: pointer; pointer-events: auto;
}
@media (hover: hover) { .overpill:hover, .deathpill:hover { background: #2f2619; } }
/* Between the canvas and #hud, OUTSIDE #hud (a child would be a sixth #hud > * victim). The colour is OVER_VEIL in
   render/panes.js; test/panes.mjs (source repo) pins THIS EXACT selector standalone (^body\.overmin::after \{), so
   it cannot be comma-joined with .deathmin below even though the two rules are otherwise identical — deathmin
   shares the same colour by eye, not by that pin, since it minimizes a different card. */
body.overmin::after { content: ''; position: fixed; inset: 0; height: var(--app-h, 100%); background: rgba(30, 30, 34, 0.55); pointer-events: none; z-index: 5; }
body.deathmin::after { content: ''; position: fixed; inset: 0; height: var(--app-h, 100%); background: rgba(30, 30, 34, 0.55); pointer-events: none; z-index: 5; }
.overcard .standings { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-right: 2px; }
.crown { font-size: 44px; }
.standings { display: flex; flex-direction: column; gap: 5px; }
.standrow {
  display: grid; grid-template-columns: 30px 1fr auto auto; gap: 8px; align-items: center;
  font: 600 13.5px var(--ui); background: #00000030; padding: 7px 10px; border-radius: 9px; text-align: left;
}
.standrow .pl { color: #cbbfa8; font-weight: 800; }
.standrow.first { border: 1.5px solid var(--gold); }
.standrow.teamhead { font-weight: 800; margin-top: 4px; }
.standrow.teamhead.team-blue .pl, .standrow.teamhead.team-blue > span:nth-child(2) { color: var(--team-blue); }
.standrow.teamhead.team-red .pl, .standrow.teamhead.team-red > span:nth-child(2) { color: var(--team-red); }
.standrow .st { color: #a99a80; font-weight: 500; font-size: 12px; }

/* YOU ARE OUT: all three ways forward stay on screen, because a death screen reads as final and anything not shown is never discovered. */
.deathbar {
  /* fixed already establishes the containing block .minbtn's absolute position anchors to. */
  position: fixed; left: 50%; transform: translateX(-50%);
  /* --app-h, NOT a bare bottom: a fixed box anchors to the initial containing block and this page never scrolls, so Exit sat under the phone toolbar. */
  top: calc(var(--app-h, 100%) - max(18px, env(safe-area-inset-bottom)));
  transform: translate(-50%, -100%);
  z-index: 30; width: min(340px, calc(var(--app-w, 100vw) - 24px));
  background: rgba(16, 13, 10, .90); border: 1.5px solid #d33b34; border-radius: 18px;
  padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; align-items: stretch;
  color: #f0e8d6; font: 600 14px var(--ui);
  box-shadow: 0 16px 44px -18px #000e;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
/* padding-right clears .minbtn sitting in the top-right corner — showWatching()'s text
   ("watching until a bot frees up") is the long case that would otherwise run under it. */
.deathinfo { display: flex; gap: 10px; align-items: baseline; justify-content: center; padding-right: 30px; }
#deathPlace { font: 800 20px var(--ui); color: #ff8a80; }
/* Down in a respawn mode: a countdown and the camera the host picks — no takeover, no spectate arrows. */
.deathbar.respawning { border-color: #5ab0ff; }
.deathbar.respawning #deathPlace { color: #bfe3ff; }
.deathbar.respawning .spectrow .iconbtn, .deathbar.respawning #takeOverBtn, .deathbar.respawning #takeOverHint { display: none; }
.deathbar.respawning .spectrow { justify-content: center; padding: 8px 12px; }
.deathbar.respawning .deathinfo { flex-direction: column; align-items: center; gap: 2px; padding-right: 0; }
/* The raid's line names the checkpoint ("Back at the checkpoint in 3s"): two centred lines clear of ▾, not one under it. */
body.raidmode .deathbar.respawning .deathinfo { padding: 0 34px; }
body.raidmode .deathbar.respawning #deathPlace { font-size: 17px; text-align: center; line-height: 1.2; }
#deathBy { color: #cbbfa8; font-size: 13px; }
.spectrow {
  display: flex; gap: 8px; align-items: center; justify-content: space-between;
  font-size: 13px; color: #cbbfa8;
  background: #00000038; border: 1px solid var(--hud-line); border-radius: 999px; padding: 4px;
}
.spectrow #specName {
  flex: 1; min-width: 0; text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 44px where a finger does the pressing: the size test/panes.mjs holds the pane's Active button to. */
.spectrow .iconbtn { width: 34px; height: 34px; font-size: 17px; }
@media (pointer: coarse) {
  .spectrow .iconbtn { width: 44px; height: 44px; font-size: 20px; }
}
.deathbar .bigbtn { font-size: 15px; padding: 11px 14px; }
/* Present but empty the rest of the time, so nothing under it moves when the answer changes. */
.deathhint { font: 500 11.5px var(--ui); color: #a99e88; text-align: center; min-height: 14px; }

/* The TV (host.html) is ALSO A PHONE: Public Melee lands here with no room live, and it is the whole packaged itch.io build (?mode=offline). */
body.tv {
  background: #171310; color: var(--paper); min-height: var(--app-h, 100vh); overflow: hidden;
  overscroll-behavior: none;   
  -webkit-tap-highlight-color: transparent;
}
.tvlobby {
  position: fixed; inset: 0; height: var(--app-h, 100%);
  display: flex; flex-direction: column; align-items: center;
  gap: 20px; padding: 26px; text-align: center; background: var(--paper); color: var(--ink);
  overflow-y: auto; z-index: 20;
  /* safe center: plain centring plus overflow pushes the first item off the TOP. The plain value stays first as the fallback. */
  justify-content: center;
  justify-content: safe center;
}
.tvlobby .logo { margin: 0; }
/* Row gap FIRST: the 40px was chosen as the space BESIDE the QR, and portrait spends it vertically instead. */
.codebox { display: flex; gap: 14px 40px; align-items: center; flex-wrap: wrap; justify-content: center; }
/* text-indent = letter-spacing: the tracking trails the LAST glyph too. */
.roomcode { font: 800 clamp(70px, 12vw, 140px)/1 ui-monospace, Consolas, monospace; letter-spacing: 14px; text-indent: 14px; color: var(--crimson); }
.codelabel { font: 600 14px var(--ui); color: var(--ink-soft); letter-spacing: 1px; text-transform: uppercase; }
/* COUCH-SCANNABLE: a QR reads at ~10x its width, so a sofa three metres from a 55 inch panel needs ~560px at
   1080p. Same formula as boss-bluff and marble-rush, so all three TVs scan from one distance. */
.qr {
  width: clamp(200px, min(36vw, 46vh), 620px); height: auto; aspect-ratio: 1;
  border: 8px solid #fff; border-radius: 10px; box-shadow: 0 10px 30px -14px #0007;
}
.joinurl { font: 600 15px var(--ui); color: var(--ink-soft); }
.joinurl b { color: var(--ink); }
.sharerow { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 10px; }
.sharerow .bigbtn, .sharerow .ghostbtn2 { padding: 10px 16px; font-size: 15px; }
@media (pointer: coarse) { .sharerow .bigbtn, .sharerow .ghostbtn2 { min-height: 44px; } }
.sharesaid { font: 600 13px var(--ui); color: var(--ink-soft); }
.sharefallback {
  flex: 1 1 100%; min-width: 0; padding: 8px 10px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--paper); color: var(--ink);
  font: 500 13px ui-monospace, Consolas, monospace;
}
.tvplayers { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; max-width: 860px; }
.tvtile {
  display: flex; align-items: center; gap: 8px; background: #fff; border: 2px solid var(--line);
  border-radius: 999px; padding: 9px 18px; font: 700 17px var(--ui); color: var(--ink);
}
.tvtile .dot { width: 14px; height: 14px; border-radius: 50%; }
.tvtile.bot { opacity: .6; font-style: italic; }
.tvtile.empty { opacity: .35; border-style: dashed; font-weight: 500; }
/* A human seat with no socket: faded and NAMED, so someone kicking one of two identical names can tell which is the dead phone. */
.tvtile.off { opacity: .55; }
.tvtile.off .dot { box-shadow: inset 0 0 0 3px #fff; }
/* Kick. Circle = a tool; INSIDE the pill so it is not mistaken for a seat, and never crimson-filled. */
.tvtile .kick {
  margin: -4px -10px -4px 2px; width: 26px; height: 26px; flex: none; border-radius: 50%;
  border: 1px solid var(--line); background: transparent; color: var(--exit, #64748b);
  font: 700 14px var(--ui); line-height: 1; cursor: pointer; opacity: .5;
}
.tvtile .kick:hover, .tvtile .kick:focus-visible { opacity: 1; background: #fff; border-color: currentColor; }
.tvtile.empty.more { border-style: solid; opacity: .5; font-style: italic; }
/* A team mode's lobby: the mode leads the row, and each seat's border is its team (net/game-net.js assigns them). */
.tvtile.modepill { background: var(--ink); color: var(--paper); border-color: var(--ink); letter-spacing: .4px; }
.tvtile.team-blue { border-color: var(--team-blue); }
.tvtile.team-red { border-color: var(--team-red); }
/* The team columns (main-host.js renderTeamColumns): the pills stack per side instead of wrapping in one row.
   The lobby is a centred COLUMN flex, which sizes this row to its content: the columns need a width given. */
.tvplayers.teamlobby { width: min(860px, 100%); }
.tvteams { display: grid; grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr)); gap: 12px 22px; width: min(860px, 100%); flex: 1 1 100%; }
.tvteam { display: flex; flex-direction: column; gap: 8px; min-width: 0; text-align: left; }
.tvteam .teamhead { font-size: 13px; }
.tvteam .tvtile { justify-content: flex-start; }
.tvteam .tvtile .kick { margin-left: auto; }
.tvtile.ready { box-shadow: inset 0 0 0 1px #2e7d4f; }
.tvbtns { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
.tvbtns .bigbtn { width: auto; padding: 14px 30px; }
/* Defined unscoped so the class means the same thing anywhere it lands. */
.ghostbtn {
  font: 600 15px var(--ui); padding: 14px 22px; border-radius: 12px;
  border: 1.5px solid var(--line); background: #fff8; color: var(--ink); cursor: pointer;
  transition: border-color .14s ease, color .14s ease;
}
@media (hover: hover) { .ghostbtn:hover:not(:disabled) { border-color: var(--crimson); color: var(--crimson); } }
.ghostbtn:disabled { opacity: .45; cursor: default; }
.localplay {
  /* 100%, not 94vw: .tvlobby has 12-26px of padding per side and is a COLUMN flex container, so cross-axis shrink cannot rescue an over-wide item. */
  width: min(720px, 100%); border: 1.5px solid var(--line); border-radius: 16px;
  background: #fff6; padding: 16px 18px; text-align: left;
}
.localhead h2 { font-family: var(--display); font-size: 21px; margin: 0; color: var(--ink); }
.localhead p { margin: 2px 0 0; font-size: 13px; color: var(--ink-soft); }
/* main-host.js stamps body.coarse from isCoarse(): a media query cannot see ?touch=1. */
.coarse-only { display: none; }
body.coarse .coarse-only { display: block; }
body.coarse .fine-only { display: none; }
.localseats { display: flex; flex-direction: column; gap: 8px; margin: 12px 0 0; }
.localseats:empty { display: none; }
.localseat {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: #fff; border: 1.5px solid var(--line); border-radius: 12px; padding: 8px 12px;
}
.localseat .dot { width: 13px; height: 13px; border-radius: 50%; flex: none; }
.localseat .who { font: 700 14px var(--ui); color: var(--ink); min-width: 92px; }
.localseat input[type="text"] {
  font: 700 13px var(--ui); text-transform: uppercase; letter-spacing: 1px;
  padding: 6px 9px; border: 1.5px solid var(--line); border-radius: 8px;
  background: #fff; color: var(--ink); width: 130px;
}
.localseat select {
  font: 600 13px var(--ui); padding: 6px 9px; border-radius: 8px;
  border: 1.5px solid var(--line); background: #fff; color: var(--ink);
}
.localseat .taken { color: var(--crimson); font: 600 12px var(--ui); }
.localbtns { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.localbtns .ghostbtn { padding: 9px 16px; font-size: 14px; }

#tvCanvas { position: fixed; inset: 0; width: var(--app-w, 100%); height: var(--app-h, 100%); display: block; }
/* touch-action none is what makes the thumbstick work: without it the browser claims a vertical drag as a scroll and fires pointercancel. */
.panewrap {
  position: fixed; inset: 0; width: var(--app-w, 100%); height: var(--app-h, 100%);
  z-index: 5; background: #12100d;
  touch-action: none; overscroll-behavior: none;
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
}
.panewrap canvas {
  position: absolute; display: block;
  box-shadow: 0 0 0 1px rgba(240, 232, 214, .18);
}

/* THIS LAYER OWNS THE BOTTOM EDGE: a fixed child of <body> measures against the initial containing block, so
   anything bottom-anchored sat below the fold. Sized from --app-h, this box IS the visible area. */
.panechrome {
  position: fixed; left: 0; top: 0;
  width: var(--app-w, 100%); height: var(--app-h, 100%);
  z-index: 16; pointer-events: none;
}
.panechrome > * { pointer-events: auto; }
/* A COLUMN, because more than one can show at once (dead in a melee = jump-in AND the way out). The gear moved out 2026-08-28; see .panesetbtn. */
.panedock {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: max(10px, env(safe-area-inset-bottom));
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  pointer-events: none; max-width: calc(var(--app-w, 100vw) - 20px);
}
.panedockrow { display: flex; gap: 8px; align-items: center; }
.panedock > *, .panedockrow > * { pointer-events: auto; }

/* TOP-RIGHT, not bottom-centre: the bottom middle is where thumbs pass through to the stick and the Active button. */
.panesetbtn {
  position: absolute; z-index: 1;
  right: max(12px, env(safe-area-inset-right)); top: max(12px, env(safe-area-inset-top));
  width: 38px; height: 38px; font-size: 18px; touch-action: manipulation;
}
.panedock .exitbtn {
  background: rgba(16, 13, 10, .78); touch-action: manipulation;
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
.tvrail[data-compact="true"] {
  width: 190px; opacity: .92;
  top: calc(max(12px, env(safe-area-inset-top)) + 46px);
  /* Landscape phone: 175 is the band the dock owns; the feed clips its OLDEST lines (flex-end overflows at the start). */
  max-height: calc(var(--app-h, 100vh) - max(12px, env(safe-area-inset-top)) - 46px - 175px);
}
.tvrail[data-compact="true"] .tvroster { display: none; }
.tvrail[data-compact="true"] .tvfeed { flex: 0 1 auto; min-height: 0; overflow: hidden; justify-content: flex-end; }
.tvrail {
  position: fixed; top: 12px; right: 12px; width: 240px; display: flex; flex-direction: column; gap: 8px;
  z-index: 15; pointer-events: none;
}
/* THE SCREEN BOUNDS THE RAIL: 99 rows are 2650px on a 900px TV. The roster yields; the feed RESERVES its six lines so the fold does not breathe. */
.tvrail[data-compact="false"] { max-height: calc(var(--app-h, 100vh) - 24px); }
.tvrail[data-compact="false"] .tvroster { flex: 0 1 auto; min-height: 0; overflow: hidden; }
.tvrail[data-compact="false"] .tvfeed { flex: 0 0 auto; min-height: 171px; }
.tvrosterrow.tvmore { grid-template-columns: 1fr; text-align: center; color: #cbbfae; font-weight: 500; }
/* PORTRAIT: THE RAIL HAS NO 'BESIDE' TO SIT IN. The overview is a SQUARE fitted to the SHORT axis, so in
   portrait a side rail lands on the playfield and it goes ACROSS THE TOP instead. Keyed on ASPECT, not width:
   a 1024px portrait iPad sails past any max-width test. buildSpots gates the camera column in JS. */
@media (max-aspect-ratio: 1/1) {
  /* OVERVIEW ONLY, and the attribute is here for SPECIFICITY as much as scope: the later phone block sets
     .tvrail width 156px and won on every portrait phone. (0,2,0) beats (0,1,0) whatever the source order. */
  .tvrail[data-compact="false"] {
    left: 8px; right: 8px; width: auto;
    flex-direction: row; align-items: flex-start; gap: 8px;
  }
  .tvrail[data-compact="false"] .tvroster, .tvrail[data-compact="false"] .tvfeed { flex: 1 1 0; min-width: 0; }
  /* Across the top there is no column to bound: cap the roster at 38% or 99 rows blanket the map. */
  .tvrail[data-compact="false"] .tvroster { max-height: calc(var(--app-h, 100vh) * .38); }
  .tvrail[data-compact="false"] .tvfeed { min-height: 0; }
  .tvrail[data-compact="false"] .tvchip { flex: 0 0 auto; }
}
.tvchip { background: var(--hud-bg); border: 1px solid var(--hud-line); border-radius: 12px; padding: 10px 14px; font: 700 15px var(--ui); text-align: center; }
.tvroster { background: var(--hud-bg); border: 1px solid var(--hud-line); border-radius: 12px; padding: 8px; display: flex; flex-direction: column; gap: 4px; }
.tvrosterrow { display: grid; grid-template-columns: 12px 1fr auto; gap: 8px; align-items: center; font: 600 12.5px var(--ui); padding: 3px 6px; border-radius: 6px; }
.tvrosterrow .dot { width: 10px; height: 10px; border-radius: 50%; }
.tvrosterrow.dead { opacity: .38; text-decoration: line-through; }
/* A team mode: the side's colour down the row's edge, and a hero waiting to respawn dimmed, never struck through. */
.tvrosterrow.team-blue { box-shadow: inset 3px 0 0 var(--team-blue); }
.tvrosterrow.team-red { box-shadow: inset 3px 0 0 var(--team-red); }
.tvrosterrow.down { opacity: .6; }
.tvrosterrow .hp { color: #a9d38a; font-weight: 500; font-size: 11.5px; }
.tvfeed { display: flex; flex-direction: column; gap: 3px; align-items: stretch; }
.tvfeed .feedline { text-align: left; }
/* Populated by buildSpots() in main-host.js, which sets the count from window height, not the config. */
.spotcol {
  position: fixed; left: 12px; top: 12px; bottom: 12px; z-index: 15;
  display: flex; flex-direction: column; justify-content: flex-start; gap: 10px;
  pointer-events: none;
}
.spotlight {
  border: 2px solid var(--hud-line); border-radius: 14px;
  overflow: hidden; background: #000; box-shadow: 0 10px 30px -10px #000d;
  position: relative; flex: 0 0 auto;
}
.spotlight canvas { display: block; }   /* sized by buildSpots(): the margin beside the map decides */
.spotlabel {
  position: absolute; top: 6px; left: 8px; font: 700 13px var(--ui); color: #fff;
  text-shadow: 0 1px 3px #000; letter-spacing: .3px;
}
/* Overview: top-left, camera column under it (.spotcol.undercode). Split screen: top-centre, out of the HP corners. */
.tvcode {
  position: fixed; left: 12px; top: 12px; z-index: 15; pointer-events: none;
  background: var(--hud-bg); border: 1px solid var(--hud-line); border-radius: 12px;
  padding: 8px 14px; display: flex; flex-direction: column; align-items: center; gap: 1px;
}
.tvcodeval { font: 800 26px/1 ui-monospace, Consolas, monospace; letter-spacing: 6px; text-indent: 6px; color: var(--crimson); }
.tvcodehint { font: 600 11px var(--ui); color: #d9cbb4; opacity: .85; }
.tvcode.split { left: 50%; transform: translateX(-50%); top: max(8px, env(safe-area-inset-top)); padding: 4px 10px; }
.tvcode.split .tvcodeval { font-size: 18px; letter-spacing: 4px; text-indent: 4px; }
.tvcode.split .tvcodehint { display: none; }
/* surviversus.com/public/tv: a screen meant to run all day, code out of the way in the corner. */
.tvcode.corner { left: auto; top: auto; right: max(12px, env(safe-area-inset-right)); bottom: max(12px, env(safe-area-inset-bottom)); }
.spotcol.undercode { top: 76px; }
.tvover {
  position: fixed; inset: 0; z-index: 40; height: var(--app-h, 100%);
  display: flex; align-items: center; justify-content: center; align-items: safe center;
  overflow-y: auto; padding: 16px;
  background: rgba(10, 8, 6, .82);
}

@media (max-width: 560px) {
  /* flex-end, not the default centre: the topbar's width is set by the build panel, so a centred chip
     would slide left as the build grew. */
  .topbar { left: auto; right: 8px; transform: none; align-items: flex-end; }
  .chip { font-size: 12px; padding: 6px 12px; }
  /* THE FEED MOVES TO THE LEFT COLUMN ON A PHONE: .topbar is pinned top-right with the tabs and #stackPanel,
     and #feed is right-anchored and LATER in the DOM, so kill lines painted over the coins. */
  .feed {
    top: calc(max(8px, env(safe-area-inset-top)) + 40px);
    right: auto; left: 10px; align-items: flex-start; max-width: 60vw;
  }
  /* the toast band runs top-centre on a narrow screen, so the feed drops below it */
  .feed.below-toast { top: calc(max(8px, env(safe-area-inset-top)) + 94px); }
  .hpbar, .xpbar { width: 104px; }
  .lvlbadge { width: 30px; height: 30px; font-size: 13px; }
  .statusrow { left: 52px; }
  .actbtn { width: 84px; height: 84px; }
  /* 9px is the floor world text gets (MIN_TEXT_CSS); 7px smeared on a 3x phone. The icon hides while offering, so the height is there. */
  .actbtn.offering .actkey { font-size: 9px; white-space: normal; line-height: 1.05; max-width: 66px; }
  .itemchip { width: 48px; height: 48px; }
  #minimap { width: 104px; height: 104px; top: calc(max(8px, env(safe-area-inset-top)) + 76px); }
  /* 216 = dock (84 + 10 + 48 + 10 + 48) + 9 lock + 7. */
  .offerprompt {
    left: 10px; right: 10px; width: auto;
    bottom: calc(max(16px, env(safe-area-inset-bottom)) + 216px);
    font-size: 12px; padding: 7px 10px;
  }
  .paneltab { padding: 6px 12px; font-size: 11px; }
  /* Right-pinned so it stacks in the topbar's column. transform none only works because the keyframes
     carry no X: see svToast. */
  .sv-toast { left: auto; right: 8px; transform: none; max-width: 60vw; }
  #hud.swapped .offerprompt { left: 10px; right: 10px; }
  .offer-name { font-size: 13px; }
  /* ONLY the pickup log gets the margin: its lines are nowrap, so min-content IS its max-width and that is the
     floor of the shrink-to-fit topbar. The build grid WRAPS, so its min-content is one coin. */
  #hud.withmini .pickuppanel { margin-right: 112px; }
  #hud.withmini .stackpanel { max-width: calc(100% - 120px); }

  /* host.html on a phone: the rail is worth about a third of the width, not two thirds. */
  .tvrail { width: 156px; top: 8px; right: 8px; gap: 5px; }
  .tvrail[data-compact="true"] { width: 136px; }
  .tvchip { font-size: 11.5px; padding: 6px 9px; }
  .tvrosterrow { font-size: 11px; padding: 2px 4px; }
  .tvrail .feedline { font-size: 10.5px; padding: 3px 7px; }
  /* buildSpots also stops building cameras here, so this is belt and braces. */
  .spotcol { display: none; }
  .tvcode { padding: 5px 10px; }
  .tvcodeval { font-size: 18px; letter-spacing: 4px; text-indent: 4px; }
  .tvcodehint { display: none; }
  .tvlobby { padding: 16px 12px 28px; gap: 14px; }
  .tvlobby .logo { font-size: clamp(30px, 9vw, 44px); }
  .roomcode { letter-spacing: 8px; text-indent: 8px; }
  .localplay { padding: 12px 13px; }
  .localhead h2 { font-size: 18px; }
  .tvbtns .bigbtn { padding: 12px 22px; }
  .tvbtns .ghostbtn { padding: 10px 14px; font-size: 13.5px; }
  .localseat input[type="text"] { width: 104px; }
  .sizepick { padding: 8px 8px 8px 14px; gap: 8px; }
  .sizepick > label { font-size: 10px; letter-spacing: .8px; }
  .sizepick select { font-size: 13px; padding: 8px 11px; }
  .sizenote { margin: -6px 0 20px; font-size: 12px; }
  /* Above the safe area on a notched phone AND above the home-indicator strip, where a thumb
     reaching for exit lands. */
  .deathbar { width: min(340px, calc(var(--app-w, 100vw) - 16px)); padding: 12px 13px; gap: 8px; }
  .deathbar .bigbtn { font-size: 14px; padding: 10px 12px; }
}

/* LANDSCAPE PHONE: the offer card sits BESIDE the dock, or its top goes negative and #hud clips the item name. 104 = the 92px Active button + 12. */
@media (max-height: 480px) {
  .offerprompt {
    bottom: max(16px, env(safe-area-inset-bottom)); left: auto;
    right: calc(max(12px, env(safe-area-inset-right)) + 104px); max-width: 280px;
  }
  #hud.swapped .offerprompt { right: auto; left: calc(max(12px, env(safe-area-inset-left)) + 104px); }
}

.standrow.serieshead {
  margin-top: 10px; padding-top: 8px;
  border-top: 1px solid var(--hud-line); color: #ffd34d; font-weight: 800;
}

/* INK, not paper: both labels sit inside #tvLobby, a paper card, and were paper on paper (contrast 1.00). Both are over 7:1 now. */
.serieslabel {
  display: flex; align-items: flex-start; gap: 9px; cursor: pointer;
  margin: 12px auto 0; max-width: 460px; text-align: left;
  font: 600 13px var(--ui); color: var(--ink);
}
.serieslabel input { margin-top: 2px; accent-color: var(--gold); flex: none; }
.serieslabel em { display: block; font-style: normal; font-weight: 400; color: #5d5344; font-size: 12px; }
body.melee .serieslabel { display: none; }  /* a melee always runs a series */
body.offline #diagLabel, body.offline #p2pLabel { display: none; }  /* "on every phone" — offline has none */

/* Melee: take over a bot when you have nothing alive. Positioned by .panedock. */
.bigbtn.jumpin { max-width: 320px; width: auto; padding: 12px 22px; }

/* The latency monitor (ui/latency.js): under the phone's HP bar, the TV's bottom-left corner. Never takes a tap;
   plain text with a hard 1px shadow, no blur, because it repaints twice a second over a moving game. */
.latmon {
  position: fixed; z-index: 9000; pointer-events: none; white-space: pre;
  left: max(10px, env(safe-area-inset-left)); top: calc(max(8px, env(safe-area-inset-top)) + 46px);
  font: 600 10px/1.3 ui-monospace, Menlo, Consolas, monospace; color: #e9f5ff; opacity: .75;
  text-shadow: 0 1px 0 #000, 1px 0 0 #000;
}
.latmon.tv { top: auto; bottom: 8px; left: 8px; font-size: 12px; }   /* the top-left is the room-code chip */
.latmon.bad { color: #ffc27a; }

/* League's cooldown wheel (hud.js _cooldown, owner 2026-09-29): a dark wedge of the time left over the icon, sweeping
   clockwise, the seconds on top; a flash the moment it is ready. One element, its gradient written only on change. */
.cdwheel {
  position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  font: 800 19px var(--ui); color: #fff; font-variant-numeric: tabular-nums;
  text-shadow: 0 0 3px #000, 0 1px 2px #000;
}
.actbtn .cdwheel { font-size: 30px; }
.cdready { animation: cdReady .4s ease-out; }
@keyframes cdReady { from { box-shadow: 0 0 0 3px #fff, 0 0 18px 6px #fff8; } to { box-shadow: 0 0 0 0 #fff0, 0 0 0 0 #fff0; } }

/* The bots' difficulty (host.html #botLevelRow, ARAM and the Rift): a labelled five-step slider along the lobby's bottom. */
.botlevel { display: flex; align-items: center; gap: 14px; width: min(620px, 100%); margin: 4px auto 0; }
.botlevelhead { font: 800 12px var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-soft); }
.botlevelbar { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.botlevelbar input[type=range] { width: 100%; accent-color: var(--go); cursor: pointer; }
.botlevelticks { display: flex; justify-content: space-between; font: 600 11px var(--ui); color: var(--ink-soft); }
.botlevelticks span.on { color: var(--ink); font-weight: 800; }
.botlevelname { min-width: 84px; font: 800 15px var(--ui); color: var(--ink); }
.botlevelnote { font-weight: 700; }
