* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-touch-callout: none; }
:root {
  --a: #2e70df;
  --b: #e84b3c;
  --bg: #101713;
  --ink: #f7f0d7;
  --stone: #27312b;
  --gold: #ffcb43;
}
html, body {
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  overflow: hidden;
  user-select: none;
  touch-action: none;
}
input, textarea { -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text; -webkit-touch-callout: default; }
.screen { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; }
.hidden { display: none !important; }

/* ---- Start screen ---- */
.screen#start {
  background: #172219 url('/assets/block-stadium.webp') center/cover no-repeat;
  isolation: isolate;
}
.start-shade {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(90deg, rgba(7,13,9,.82), rgba(7,13,9,.38) 52%, rgba(7,13,9,.1)),
              linear-gradient(0deg, rgba(7,13,9,.72), transparent 45%);
}
.panel {
  width: min(90vw, 460px);
  background: rgba(19,27,22,.94);
  border: 4px solid #657264;
  border-radius: 2px;
  padding: 28px 28px 24px;
  box-shadow: inset 0 0 0 3px #182018, 0 10px 0 #101611, 0 20px 50px rgba(0,0,0,.55);
  text-align: center;
}
.block-panel { margin-left: max(5vw, 34px); margin-right: auto; }
.eyebrow { color: #c8d4bf; font-weight: 900; font-size: 12px; letter-spacing: 3px; display: flex; align-items: center; gap: 12px; justify-content: center; }
.eyebrow span { width: 36px; height: 3px; background: var(--gold); box-shadow: 0 3px #8f691b; }
.panel h1 {
  margin-top: 12px; color: #fff4ca; font: 900 clamp(40px, 7vw, 62px)/.78 "Arial Black", Impact, sans-serif;
  letter-spacing: -3px; text-shadow: 0 5px 0 #776d4d, 0 8px 0 #252a21;
}
.panel h1 span { color: var(--gold); text-shadow: 0 5px 0 #a56a16, 0 8px 0 #252a21; }
.sub { color: #c2cbb8; margin: 22px 0 18px; font-size: 11px; font-weight: 800; letter-spacing: 1px; }
.match-card { display: grid; grid-template-columns: 1fr 90px 1fr; align-items: center; background: #111812; border: 3px solid #39463b; padding: 10px; margin: 0 0 16px; }
.team-chip { font-weight: 900; font-size: 13px; display: flex; align-items: center; justify-content: center; gap: 8px; }
.team-chip i { display: block; width: 18px; height: 18px; background: currentColor; border: 3px solid #e9dfc0; box-shadow: 3px 3px 0 #050705; }
.team-chip.blue { color: #5b96ff; } .team-chip.red { color: #ff665b; }
.mode strong { display: block; color: #fff; font: 900 18px "Arial Black", sans-serif; }
.mode small { display: block; margin-top: 2px; color: #7f907f; font-size: 8px; font-weight: 900; }
.field-label { display: block; text-align: left; font-size: 12px; color: #8fa3c8; margin: 14px 2px 6px; text-transform: uppercase; letter-spacing: .5px; }
#name {
  width: 100%; padding: 13px 14px; border-radius: 12px;
  background: #0c1322; border: 1px solid #26324f; color: #fff; font-size: 16px;
}
#name:focus { outline: none; border-color: var(--a); }
.chars { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.char {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 16px 8px; border-radius: 14px; cursor: pointer;
  background: #0c1322; border: 2px solid #26324f; color: #fff; transition: .12s;
}
.char:hover { border-color: #3a4a72; }
.char.selected { border-color: var(--a); background: #10203f; }
.char-emoji { font-size: 30px; }
.char-name { font-weight: 700; font-size: 15px; }
.char-stats { font-size: 11px; color: #8fa3c8; }
.play {
  width: 100%; padding: 15px 18px; border: 3px solid #fff09d; border-radius: 0;
  background: #e3a92f; color: #302108; box-shadow: inset 0 -6px #a66a18, 0 6px 0 #0b0f0b;
  font: 900 18px "Arial Black", sans-serif; cursor: pointer; letter-spacing: 1px;
  display: flex; justify-content: center; align-items: center; position: relative;
}
.play b { position: absolute; right: 18px; font-size: 30px; top: 3px; }
.play:hover { filter: brightness(1.12); }
.play:active { transform: translateY(4px); box-shadow: inset 0 -3px #a66a18, 0 2px 0 #0b0f0b; }
/* THE PRIMARY ACTION MUST NOT HIDE BELOW THE FOLD. Measured at 874x402 (iPhone 17 Pro, landscape):
   .lobby-wrap stacks head -> teams -> chat -> play, which is 660px of content in a 402px viewport, so
   «שחק עכשיו» started ~200px past the fold and was reachable only after scrolling past BOTH team panels
   and the chat row (237px with the notch insets, 193px without — the insets add to it, they do not cause
   it; the stacking at .lobby-wrap's flex-direction:column override does, and it is unconditional).
   Sticky keeps the CTA on screen while the roster and chat scroll behind it — the Brawl Stars lobby
   shape. Scoped to #play-now, NOT .play, because .play is used by other buttons too (e.g. the
   invite sheet's now-removed play CTA, .fs-play) that are already above the fold and must keep
   scrolling normally rather than sticking.
   Sticky offsets resolve against the scrollport's PADDING box, so .lobby-wrap's safe-area bottom padding
   automatically keeps this clear of the home indicator — do not hard-code an inset here. */
#play-now { position: sticky; bottom: 0; z-index: 3; }
.hint { margin-top: 17px; font-size: 9px; color: #94a090; line-height: 1.6; }
.hint b { color: #e7dcae; }

/* ---- Game ---- */
/* 100dvh, not 100vh: on iOS 100vh is the LARGE viewport (browser chrome collapsed), so a full-bleed canvas
   overflows and the bottom of the pitch sits under the home indicator. dvh follows the live viewport. */
#canvas { display: block; width: 100vw; height: 100dvh; touch-action: none; -webkit-user-select: none; user-select: none; }
/* TOP HUD: three columns — MY TEAM's power cards · the clock/score/format stack · the OPPONENTS'
   power cards. A GRID (was a plain column) because the two card rails have to flank the centre stack
   and stay put whatever its width is; every child is placed by explicit grid-area, so DOM order in
   index.html is irrelevant (several agents live in that file). `.net` stays position:fixed and sits
   the grid out. An unplaced future child lands in an implicit 4th row, below everything — visible,
   never on top of the score. */
#hud {
  position: fixed; top: 0; left: 0; right: 0; padding: 8px 10px;
  display: grid; grid-template-columns: 1fr auto 1fr; grid-template-rows: auto auto auto;
  align-items: start; column-gap: clamp(6px, 1.6vw, 18px); row-gap: 4px;
  pointer-events: none; font-variant-numeric: tabular-nums;
  /* PHYSICAL columns. The page is RTL, which maps grid column 1 to the RIGHT — that put my own team
     on the right and the opponents on the left, the opposite of the spec. `direction: ltr` here pins
     column 1 = physically left; every Hebrew/ordered child below re-declares `direction: rtl` so
     nothing else about the HUD changes (the score keeps the exact order it has today). */
  direction: ltr;
}
#hud .score-fmt,#hud .net { direction: rtl; }
/* .score is deliberately NOT in that list. It holds no Hebrew — only numerals — and `direction: rtl`
   broke it twice: the flex row reversed (my team / blue landed on the RIGHT, opponent left), and
   every multi-digit score read backwards (12 → 21) because the glyph run reversed too. */
#hud .score { direction: ltr; }
/* Both numbers are drawn by public/pixel-digits.js as real pixel blocks in `currentColor`, so every
   colour rule below still applies — the glyph code knows nothing about colour. `--pd-px` is the size
   of ONE pixel block: the only knob for how big the clock / score are. */
.timer { grid-area: 1 / 2; justify-self: center; --pd-px: 2px; color: #cdd8ef;
  background: #182019e8; padding: 4px 7px; border: 2px solid #667061; border-radius: 0;
  box-shadow: 0 3px 0 rgba(0,0,0,.4); filter: drop-shadow(0 0 2px rgba(205,216,239,.45)); }
.score { grid-area: 2 / 2; justify-self: center; --pd-px: 3px; display: flex; gap: 4px; align-items: flex-start;
  background: #182019e8; padding: 4px 9px; border: 2px solid #667061; border-radius: 0;
  box-shadow: inset 0 -3px #0b100c, 0 3px 0 rgba(0,0,0,.4); }
.team-a { color: var(--a); } .team-b { color: var(--b); } .dash { color: #55637f; }
/* Match-format caption — third row of the same column (was position:fixed at top:62px, which the
   restacked HUD would have dropped on top of the score). */
.score-fmt { grid-area: 3 / 2; justify-self: center; text-align: center; pointer-events: none;
  font-size: 11px; font-weight: 700; letter-spacing: .5px; color: #8b9ab5; text-shadow: 0 2px 0 rgba(0,0,0,.5); }
.score-fmt.match-point { color: #ffd34d; animation: fmtPulse .9s ease-in-out infinite; }
@keyframes fmtPulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) { .score-fmt.match-point { animation: none; } }
/* (the old top-RIGHT .timer rule lived here — the clock is now the first row of the #hud column
   above, so a second `position: fixed` declaration would have pulled it straight back out) */
.net { position: fixed; bottom: 8px; left: 12px; font-size: 11px; color: #6b7ea3;
  background: rgba(11,16,12,.78); padding: 4px 8px; border-radius: 0; }

.banner {
  position: fixed; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: clamp(34px, 9vw, 72px); font-weight: 900; letter-spacing: -1px;
  text-shadow: 0 6px 24px rgba(0,0,0,.6); pointer-events: none;
}

/* ---- Joysticks (always-on, Brawl-Stars style: base parked at a fixed anchor) ---- */
.stick {
  position: fixed; width: 120px; height: 120px; border-radius: 10%; z-index: 4; pointer-events: none;
  background: rgba(15,23,16,.5); border: 3px solid rgba(240,228,185,.5); box-shadow: inset 0 0 0 5px rgba(0,0,0,.15);
  /* Idle opacity is player-tunable (--ctl-idle-op, controls editor); full while in use.
     CoD Mobile / Free Fire both ship a transparency slider; Unreal treats Active/InactiveOpacity
     as first-class joystick params. Fallback .5 keeps the pre-slider look if the var is unset. */
  opacity: var(--ctl-idle-op, .5); transition: opacity .12s ease;
}
.stick.active { opacity: 1; }
.stick .knob {
  position: absolute; left: 50%; top: 50%; width: 54px; height: 54px; margin: -27px 0 0 -27px;
  border-radius: 8%; background: rgba(226,201,126,.55); border: 3px solid rgba(255,246,209,.8); box-shadow: 4px 4px 0 rgba(0,0,0,.25);
}

/* Special-skill button (bomb) — bottom-right thumb cluster, Brawl-Stars style:
   sits on the right thumb's natural pivot arc so you can shoot then flick to it
   without re-gripping. Hugs the right EDGE and is bottom-anchored at thumb height
   (NOT the top corner) — an aim-drag started from center-right won't hit it, and
   button taps are excluded from the aim-stick claim in touchstart. Bomb is the
   lower of the two (nearest the corner = easiest reach). */
/* Bomb == Brawl "Super": the LARGE round primary-ability button, nearest the
   corner (easiest reach), and it PULSES when off-cooldown so you feel it's ready. */
.special-btn {
  position: fixed; right: 112px; bottom: 88px;
  width: 82px; height: 82px; border-radius: 10%; /* SQUARE (rounded) — matches the aim/move sticks */
  background: #d69122; border: 3px solid #ffe49a; color: #1a1200;
  box-shadow: inset 0 -8px #8b5117, 0 8px 20px rgba(0,0,0,.45);
  font-size: 40px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  touch-action: none; z-index: 5;
}
.special-btn:active { transform: scale(.92); }
.special-btn.cooling { filter: grayscale(.7) brightness(.6); }
/* Ready-glow — Brawl's charged-Super pulse. Toggle `.ready` from JS when off cooldown. */
.special-btn.ready { animation: btnReady 1.1s ease-in-out infinite; }
@keyframes btnReady {
  0%,100% { box-shadow: inset 0 -8px #8b5117, 0 8px 20px rgba(0,0,0,.45), 0 0 0 0 rgba(255,228,154,.0); }
  50%     { box-shadow: inset 0 -8px #8b5117, 0 8px 20px rgba(0,0,0,.45), 0 0 18px 5px rgba(255,228,154,.85); }
}

/* Wall == Brawl "Gadget": SMALLER round button tucked ABOVE + inset from the bomb,
   forming the up-left arc. Smaller = fewer accidental builds. Tap to build, drag to aim. */
.build-btn {
  position: fixed; right: 124px; bottom: 182px;
  width: 58px; height: 58px; border-radius: 10%; /* SQUARE (rounded) — matches the aim/move sticks */
  background: #b06a34; border: 3px solid #e6b483; color: #1a1200;
  box-shadow: inset 0 -6px #6e3d17, 0 8px 20px rgba(0,0,0,.45);
  font-size: 28px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  touch-action: none; z-index: 5;
}
.build-btn:active { transform: scale(.94); }
.build-btn.empty { filter: grayscale(.75) brightness(.55); }
.build-icon { pointer-events: none; }
.build-pips { position: absolute; top: 4px; left: 0; right: 0; display: flex; gap: 4px; justify-content: center; pointer-events: none; }
.build-pips i { width: 13px; height: 5px; border-radius: 1px; background: rgba(0,0,0,.4); border: 1px solid rgba(0,0,0,.45); }
.build-pips i.full { background: #ffd27a; border-color: #a86a1e; }
/* Old linear reload bar retired in favour of the radial ring below (JS still sets it — harmless). */
.build-cd { display: none; }
/* Reload / cooldown FRAME around the wall (build) + bomb (special) buttons: a gold conic arc that
   fills as the reload/cooldown recharges. Driven by --cd (0 = empty … 1 = ready). SQUARE frame
   (matches the square buttons) via the padding + mask-exclude "border" trick. */
.build-btn::after, .special-btn::after {
  content: ""; position: absolute; inset: -4px; border-radius: 12%; pointer-events: none; z-index: 6;
  background: conic-gradient(from -90deg, #ffe49a calc(var(--cd, 0) * 1turn), rgba(8,6,0,.32) 0);
  padding: 5px; /* ring thickness */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}
/* Drag-back-to-centre CANCEL cue (Brawl-Stars): red frame + ✕ on the button being aimed. */
.special-btn.cancel-armed, .build-btn.cancel-armed { outline: 3px solid #ff4d4d; outline-offset: 2px; filter: saturate(1.25) brightness(1.06); }
.special-btn.cancel-armed::before, .build-btn.cancel-armed::before {
  content: "✕"; position: absolute; inset: 0; z-index: 7; pointer-events: none;
  display: flex; align-items: center; justify-content: center;
  color: #ff4d4d; font-size: 1.35em; font-weight: 900; text-shadow: 0 1px 4px rgba(0,0,0,.7);
}

/* ---- Control-layout editor (training only, Brawl-Stars style) ---- */
/* Semi-translucent trigger button — sits in the top toolbar RIGHT BESIDE the
   pause/sound/music row (next free 40px slot at left:202) so it's easy to find. */
.edit-controls-btn {
  /* Third slot of the top-right cluster, still immediately beside ⚙ as intended: 60 (‹ לובי's edge)
     + 65 (its MEASURED width in Chrome at font 12 Arial Black with 12px padding — the eyeballed 56
     left a 1px overlap) + 8 gap = 133. Training ground only.
     Moved with the gear on 2026-07-26; it was anchored to the LEFT inset, which is the wrong inset
     for this edge. */
  position: fixed; right: max(133px, calc(env(safe-area-inset-right) + 129px)); top: 12px; z-index: 7;
  width: 40px; height: 40px; border-radius: 2px;
  background: rgba(24,32,25,.62); border: 2px solid rgba(240,228,185,.6);
  color: #f0e4b9; font-size: 18px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  touch-action: manipulation;
}
.edit-controls-btn:active { transform: scale(.94); }

/* ── IN-MATCH QUICK CHAT ──────────────────────────────────────────────────────────────────────
   Fourth slot of the top-right cluster: 12 (⚙) · 60 (‹ לובי) · 133 (🎛️) · 181 (here). The button
   carries .edit-controls-btn as well, so it inherits that exact box/border/blur and only the offset
   differs — the cluster stays visually uniform.
   The sheet drops DOWN from the cluster on purpose. COMMUNICATION_SET.md: "Never cover the ball,
   goal, score, or aiming controls" — the top-right corner is the only region that satisfies all four
   (the score is top-centre, the sticks are bottom-left/right, the ball is mid-pitch). */
.chat-btn { right: max(181px, calc(env(safe-area-inset-right) + 177px)); }
.chat-sheet {
  position: fixed; top: 58px; right: max(12px, env(safe-area-inset-right)); z-index: 8;
  display: flex; flex-direction: column; gap: 6px;
  padding: 8px; border-radius: 10px;
  background: rgba(12,18,13,.92); border: 2px solid rgba(240,228,185,.55);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  max-width: min(360px, 62vw);
}
.chat-sheet.hidden { display: none; }
.chat-row { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
/* Words: short pills. Sized so 4 fit a row on a 390px-tall landscape phone. */
.chat-words button {
  border: none; border-radius: 8px; padding: 6px 10px; cursor: pointer;
  background: #33413a; color: #fff; font: 800 13px "Arial Black", sans-serif;
  white-space: nowrap; touch-action: manipulation;
}
.chat-words button:active { transform: translateY(2px); }
/* Emotes: the art is the label, so no text. 38px keeps a comfortable thumb target. */
.chat-emotes button {
  width: 38px; height: 38px; border: none; border-radius: 8px; cursor: pointer;
  background: #2a3a30; display: grid; place-items: center; padding: 0;
  touch-action: manipulation;
}
.chat-emotes button:active { transform: translateY(2px); }
/* The emote sprite. This is the COMBINED 160-asset sheet (16x10 of 128px cells), which is a
   DIFFERENT file from the 96-pack icon-system.css uses — hence its own rule rather than .saltiz-icon.
   Deliberately defined here and not in icon-system.css: that file is regenerated whenever the pack is
   rebuilt, which would silently delete this. background-position is set per-button by client.js from
   shared/quick-chat.js, so the cell mapping lives in exactly one place. */
.qc-emote {
  width: 30px; height: 30px; display: block;
  background-image: url('/assets/pixel-icon-system-01/expansion-64/sprite-pack-160.webp?v=1');
  background-repeat: no-repeat; background-size: 1600% 1000%;
  image-rendering: auto;   /* same reason as the ability icons: the art is anti-aliased, and
                              nearest-neighbour magnifies its soft edges into holes */
}
.chat-cool { opacity: .45; pointer-events: none; }   /* during the anti-spam cooldown */


/* Full-screen editor overlay. Sits above the pitch; pucks are dragged to reposition. */
.controls-editor { position: fixed; inset: 0; z-index: 40; background: rgba(6,12,8,.55); touch-action: none; }
.ce-hint {
  position: absolute; top: 14px; left: 50%; transform: translateX(-50%);
  color: #f0e4b9; font-size: 13px; font-weight: 700; letter-spacing: .06em;
  background: rgba(0,0,0,.35); padding: 6px 14px; border-radius: 999px; pointer-events: none;
}
/* A puck is a GHOST OF THE REAL CONTROL — same silhouette, so what you place is what you play.
   Every live control (.stick, .special-btn, .build-btn) uses border-radius:10%, so the pucks do too.
   (They used to be 12px, and bomb/wall were forced to 50% via .ce-round — circles that matched no
   button on the pitch. .ce-round is retired; keep the class harmless if any markup still carries it.) */
.ce-puck {
  position: absolute; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center;
  background: rgba(226,201,126,.28); border: 2px dashed rgba(255,246,209,.9);
  color: #fff6d1; font-size: 15px; font-weight: 800; border-radius: 10%;
  touch-action: none; cursor: grab; user-select: none;
}
.ce-puck.ce-round { border-radius: 10%; }   /* retired — was 50%; kept so stale markup can't re-round a puck */
/* Ghost of the round knob that sits inside the two sticks, so the stick puck reads as a stick. */
.ce-puck.ce-stick::before {
  content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);
  width: 45%; height: 45%; border-radius: 8%;
  background: rgba(255,246,209,.22); border: 2px solid rgba(255,246,209,.55);
}
.ce-puck.dragging { cursor: grabbing; background: rgba(255,228,154,.42); }
.ce-puck span { pointer-events: none; position: relative; z-index: 1; }
/* ---- PULL SQUARE: the outer of the two nested squares on the bomb/wall pucks ----
   Inner square = the button you press. Outer square = how far your thumb travels from the centre
   to reach MAX reach (the sensitivity). Seeing them nested makes the relationship obvious: a small
   outer box = twitchy (a short drag already maxes out), a big one = long, precise throws.
   The puck must NOT clip it, so .ce-pull is allowed to overflow. */
.ce-pull { overflow: visible; }
.ce-sens {
  position: absolute; box-sizing: border-box; pointer-events: none; z-index: 0;
  border: 2px dashed rgba(120,200,255,.75); border-radius: 10%;
  background: rgba(78,160,255,.10);
}
.ce-sens::after {                       /* label sits on the outer box, not the button */
  content: attr(data-sens); position: absolute; left: 50%; top: -19px; transform: translateX(-50%);
  font: 800 10px ui-monospace, SFMono-Regular, Consolas, monospace; color: #a9d2ff;
  background: rgba(0,0,0,.62); padding: 1px 6px; border-radius: 6px; white-space: nowrap;
}
/* Grip on the pull square's top-left corner — deliberately the OPPOSITE corner from the button's
   size handle (bottom-right), so the two can never be grabbed by accident. */
.ce-sens-grip {
  position: absolute; left: -11px; top: -11px; width: 26px; height: 26px; border-radius: 50%;
  background: #2e70df; border: 2px solid #cfe6ff; box-shadow: 0 2px 6px rgba(0,0,0,.5);
  cursor: nwse-resize; pointer-events: auto; touch-action: none;
}
/* Live size readout on every puck (CoD Mobile / Free Fire both surface the number you're dragging
   to, so a layout can be reproduced instead of eyeballed). JS writes it to data-size. */
.ce-puck::after {
  content: attr(data-size); position: absolute; left: 50%; bottom: -20px; transform: translateX(-50%);
  font: 800 10px ui-monospace, SFMono-Regular, Consolas, monospace; color: #ffe49a;
  background: rgba(0,0,0,.62); padding: 1px 6px; border-radius: 6px; white-space: nowrap;
  pointer-events: none; opacity: 0; transition: opacity .12s ease;
}
.ce-puck.dragging::after { opacity: 1; }
/* Corner resize handle — drag to scale the control. */
.ce-resize {
  position: absolute; right: -9px; bottom: -9px; width: 26px; height: 26px;
  border-radius: 50%; background: #d69122; border: 2px solid #fff6d1;
  box-shadow: 0 2px 6px rgba(0,0,0,.5); cursor: nwse-resize;
}
/* Aim-feel sliders (sensitivity + per-control reach) — sit just above the action bar. */
.ce-sliders { position: absolute; bottom: 76px; left: 50%; transform: translateX(-50%); width: min(360px, 86vw); display: flex; flex-direction: column; gap: 8px; }
.ce-slider { display: flex; flex-direction: column; gap: 3px; color: #f0e4b9; font-size: 12px; font-weight: 700; background: rgba(0,0,0,.4); padding: 7px 12px; border-radius: 10px; }
.ce-slider span { display: flex; justify-content: space-between; }
.ce-slider b { color: #ffe49a; font-weight: 800; }
.ce-slider input { width: 100%; }
/* Bottom action bar */
.ce-bar { position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%); display: flex; gap: 10px; }
.ce-btn { padding: 10px 16px; border-radius: 10px; font-size: 14px; font-weight: 800; border: 2px solid; cursor: pointer; }
.ce-save   { background: #2f9e44; border-color: #b2f2bb; color: #fff; }
.ce-cancel { background: #495057; border-color: #ced4da; color: #fff; }
.ce-reset  { background: #a02020; border-color: #ffc9c9; color: #fff; }

/* "You're hidden" cue — lights up while standing in a bush (not carrying the ball). */
.stealth-cue {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
  background: rgba(18,54,28,.72); border: 1px solid rgba(126,255,240,.55);
  color: #c8ffe9; padding: 5px 14px; border-radius: 999px;
  font-size: 12px; font-weight: 800; letter-spacing: .12em;
  opacity: 0; transition: opacity .15s; pointer-events: none; z-index: 6;
}
.stealth-cue.on { opacity: 1; }

/* "Power charged" cue — full shot/kick is ready. */
.power-cue {
  position: fixed; left: 50%; bottom: 56px; transform: translateX(-50%);
  background: rgba(70,48,8,.78); border: 1px solid rgba(255,209,64,.7);
  color: #ffe49a; padding: 5px 14px; border-radius: 999px;
  font-size: 12px; font-weight: 800; letter-spacing: .12em;
  opacity: 0; transition: opacity .15s; pointer-events: none; z-index: 6;
}
.power-cue.on { opacity: 1; }

/* Crash banner — shows any runtime error on-device */
.fatal {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  background: rgba(150,0,0,.96); color: #fff;
  font: 12px/1.45 ui-monospace, Menlo, monospace;
  padding: 10px 12px; white-space: pre-wrap; word-break: break-word;
  max-height: 60vh; overflow: auto;
}

/* Pause button — top-left, just right of the native ✕ */
.pause-btn {
  /* TOP-RIGHT CLUSTER (user, 2026-07-26): in a match, לובי + הגדרות live together in the top right.
     ⚙ takes the far corner; ‹ לובי sits inboard of it; 🎛️ inboard of that. The max() keeps the row
     clear of the notch in landscape — viewport-fit=cover paints under it, and on the RIGHT edge that
     is env(safe-area-inset-right), which is the inset that is actually non-zero when the phone is
     rotated with the notch on the left. */
  position: fixed; right: max(12px, calc(env(safe-area-inset-right) + 8px)); top: 12px;
  width: 40px; height: 40px; border-radius: 2px;
  background: #182019e8; border: 2px solid #667061; box-shadow: 0 4px 0 rgba(0,0,0,.4);
  color: #dfe8fb; font-size: 16px; cursor: pointer; z-index: 6;
  display: flex; align-items: center; justify-content: center;
}
.pause-btn:active { transform: scale(.94); }

.sound-btn {
  position: fixed; left: 110px; top: 12px;
  width: 40px; height: 40px; border-radius: 2px;
  background: #182019e8; border: 2px solid #667061; box-shadow: 0 4px 0 rgba(0,0,0,.4);
  color: #dfe8fb; font-size: 17px; cursor: pointer; z-index: 6;
  display: flex; align-items: center; justify-content: center;
  touch-action: manipulation;
}
.sound-btn:active { transform: scale(.94); }
.sound-btn.muted { filter: grayscale(1); opacity: .65; }
/* 🎵 music toggle — sits next to the 🔊 SFX button */
.music-btn {
  position: fixed; left: 156px; top: 12px;
  width: 40px; height: 40px; border-radius: 2px;
  background: #182019e8; border: 2px solid #667061; box-shadow: 0 4px 0 rgba(0,0,0,.4);
  color: #dfe8fb; font-size: 16px; cursor: pointer; z-index: 6;
  display: flex; align-items: center; justify-content: center; touch-action: manipulation;
}
.music-btn:active { transform: scale(.94); }
.music-btn.muted { filter: grayscale(1); opacity: .55; }
.music-btn.muted::after { content: ''; position: absolute; left: 3px; right: 3px; top: 50%; height: 2px; background: #ff6a6a; transform: rotate(-22deg); box-shadow: 0 0 2px #000; }

/* Training ground HUD */
.train-tag {
  /* Moved right → left on 2026-07-26: the top-right is now the לובי + הגדרות cluster, and the gear's
     40px box (top 12–52) would have sat straight on top of this. The left edge is free now. */
  position: fixed; top: 16px; left: max(14px, calc(env(safe-area-inset-left) + 10px)); z-index: 6;
  font-size: 15px; font-weight: 800; color: #ffd76a;
  letter-spacing: .5px; text-shadow: 0 2px 0 rgba(0,0,0,.5);
}
.train-tag.hidden { display: none; }
/* Training-ground bot-level chip — under the 🎯 אימון tag, tappable (opens the difficulty grid).
   It is the training ground's answer to "what am I playing against, and how do I change it?". */
.train-diff {
  /* Follows .train-tag to the left, same reason — it collided with ‹ לובי, not just the gear. */
  position: fixed; top: 42px; left: max(14px, calc(env(safe-area-inset-left) + 10px)); z-index: 7; cursor: pointer;
  padding: 3px 9px; border-radius: 999px; background: rgba(24,32,25,.82); border: 2px solid #667061;
  color: #ffd76a; font: 800 11px sans-serif; letter-spacing: .3px; touch-action: manipulation;
}
.train-diff:active { transform: scale(.95); }
.train-diff.hidden { display: none; }
.reset-ball-btn {
  position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
  z-index: 6; padding: 9px 18px; border-radius: 3px;
  background: #182019e8; border: 2px solid #667061; box-shadow: 0 4px 0 rgba(0,0,0,.4);
  color: #ffd76a; font-size: 14px; font-weight: 700; cursor: pointer;
  touch-action: manipulation;
}
.reset-ball-btn:active { transform: translateX(-50%) scale(.95); }
.reset-ball-btn.hidden { display: none; }

/* Settings overlay */
.settings-panel {
  position: fixed; inset: 0; z-index: 20;
  background: rgba(4,8,18,.72); backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center;
}
.settings-card {
  width: min(94vw, 560px); max-height: 90vh; overflow-y: auto; /* #16: wider for the 2-col grid */
  background: #182019; border: 4px solid #667061; border-radius: 2px;
  padding: 16px 18px; box-shadow: inset 0 0 0 3px #0d120e, 0 10px 0 rgba(0,0,0,.65);
}
.settings-card h2 { font-size: 18px; margin-bottom: 12px; text-align: center; }
.setting { margin: 12px 0; }
.setting-row { display: flex; justify-content: space-between; font-size: 13px; color: #cdd8ef; margin-bottom: 6px; }
.setting-row span:last-child { color: #7CF0A9; font-variant-numeric: tabular-nums; font-weight: 700; }
.setting input[type=range] {
  width: 100%; height: 28px; -webkit-appearance: none; appearance: none; background: transparent;
}
.setting input[type=range]::-webkit-slider-runnable-track {
  height: 6px; border-radius: 3px; background: #26324f;
}
.setting input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; margin-top: -9px;
  width: 24px; height: 24px; border-radius: 50%; background: #4f8cff; border: 2px solid #fff;
}
.settings-actions { display: flex; gap: 10px; margin-top: 18px; }
.settings-actions button { flex: 1; padding: 13px; border-radius: 11px; font-size: 15px; font-weight: 700; cursor: pointer; border: 0; }
.resume { background: #d99f29; color: #231706; box-shadow: inset 0 -5px #8d5817; }
.ghost-btn { background: #0c1322; color: #9fb0d0; border: 1px solid #26324f !important; }

/* Bot difficulty selector (settings panel) */
/* Difficulty LADDER: a scrollable 2-column grid of levels (name + enemy/partner hint). */
.difficulty { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 6px; max-height: 190px; overflow-y: auto; padding: 2px; }
.diff-btn {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 8px 6px; border-radius: 10px; cursor: pointer;
  background: #0c1322; color: #9fb0d0; border: 1px solid #26324f;
}
.diff-btn .diff-name { font-size: 14px; font-weight: 800; }
.diff-btn .diff-hint { font-size: 10px; font-weight: 600; opacity: 0.7; }
.diff-btn.active { background: #d99f29; color: #231706; border-color: #ffe49a; box-shadow: inset 0 -4px #8d5817; }
.diff-btn.active .diff-hint { opacity: 0.85; }
/* EXTREME ("קטלני" / lethal) — a red danger tint so it reads as a boss tier. */
.diff-btn.diff-extreme { color: #ff9a8f; border-color: #6b2033; }
.diff-btn.diff-extreme.active { background: #c8322f; color: #fff2ee; border-color: #ff8f7a; box-shadow: inset 0 -4px #7a1414; }

@media (max-width: 700px) {
  .screen#start { background-position: 58% center; }
  .block-panel { margin: auto; padding: 22px 18px 20px; }
  .panel h1 { font-size: 45px; }
  .hint { max-width: 340px; margin-left: auto; margin-right: auto; }
}

/* ---- Lobby / waiting room ---- */
.screen#lobby {
  background: #172219 url('/assets/block-stadium.webp') center/cover no-repeat;
}
.lobby-wrap {
  position: relative; z-index: 1;
  display: flex; gap: 18px; align-items: stretch;
  width: 100%; max-width: 900px; max-height: 100%; overflow-y: auto;
  /* SAFE AREA: this page is NOT a .subpage, so it does not inherit that rule — and it is the page with
     «שחק עכשיו» and the chat composer at the bottom, which is exactly where the home indicator sits. */
  padding: max(20px, env(safe-area-inset-top)) max(20px, env(safe-area-inset-right))
           max(20px, env(safe-area-inset-bottom)) max(20px, env(safe-area-inset-left));
}
.lobby-main {
  flex: 1; text-align: center;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: rgba(19,27,22,.9); border: 3px solid #39463b;
  box-shadow: inset 0 -6px #0b100c, 0 8px 0 rgba(0,0,0,.35); padding: 22px 18px;
}
.lobby-title {
  margin: 10px 0 4px; color: #fff4ca;
  font: 900 clamp(30px, 5.4vw, 50px)/.8 "Arial Black", Impact, sans-serif;
  letter-spacing: 1px; text-shadow: 0 5px 0 #a56a16, 0 8px 0 #252a21;
}
.lobby-counts { display: flex; gap: 12px; margin: 16px 0 20px; }
.count-pill {
  min-width: 90px; padding: 8px 10px; background: #111812; border: 3px solid #39463b;
  box-shadow: 3px 3px 0 #050705;
}
.count-pill strong { display: block; color: var(--gold); font: 900 26px "Arial Black", sans-serif; line-height: 1; }
.count-pill small { display: block; margin-top: 4px; color: #8aa08c; font-size: 8px; font-weight: 900; letter-spacing: 1.5px; }
.play.armed { filter: grayscale(.5) brightness(.85); }

.lobby-side {
  width: clamp(200px, 30vw, 280px); display: flex; flex-direction: column;
  background: rgba(12,18,13,.92); border: 3px solid #39463b;
  box-shadow: inset 0 -6px #0b100c, 0 8px 0 rgba(0,0,0,.35);
}
.side-head {
  padding: 12px 14px; color: #c8d4bf; font-weight: 900; font-size: 11px; letter-spacing: 3px;
  border-bottom: 3px solid #2a352c; background: #141d15;
}
.member-list { flex: 1; overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.member-row {
  display: flex; align-items: center; gap: 10px; padding: 6px 8px;
  background: #101812; border: 2px solid #2a352c; border-left: 4px solid #2a352c;
}
.member-row.is-ready { border-left-color: var(--gold); }
.member-av {
  width: 44px; height: 52px; flex: none; overflow: hidden;
  background: #26332a; border: 2px solid #3c4a3e; color: #e7dcae;
  font: 900 12px "Arial Black", sans-serif;
  display: flex; align-items: center; justify-content: center;
}
.member-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.member-av canvas { display: block; width: 100%; height: 100%; image-rendering: pixelated; }
.member-name {
  flex: 1; min-width: 0; color: #eef2e6; font-weight: 800; font-size: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.member-status { color: var(--gold); font-size: 9px; font-weight: 900; letter-spacing: .5px; white-space: nowrap; }

.lobby-countdown {
  position: fixed; inset: 0; z-index: 5;
  display: flex; align-items: center; justify-content: center;
  background: rgba(7,13,9,.78);
  color: #fff4ca; font: 900 clamp(90px, 22vw, 200px)/1 "Arial Black", Impact, sans-serif;
  text-shadow: 0 8px 0 #a56a16, 0 14px 0 #252a21;
}

/* ---- Lobby team picker (overrides the single-column layout above) ---- */
.lobby-wrap { flex-direction: column; align-items: center; gap: 14px; max-width: 860px; }
.lobby-head { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.lobby-head .lobby-counts { margin: 0; }
.teams { display: flex; align-items: stretch; gap: 14px; width: 100%; }
.team-panel {
  flex: 1; padding: 10px; min-height: 150px; display: flex; flex-direction: column;
  background: rgba(12,18,13,.92); border: 3px solid #39463b;
  box-shadow: inset 0 -6px #0b100c, 0 8px 0 rgba(0,0,0,.35);
}
.team-panel.team1 { border-top: 5px solid var(--a); }
.team-panel.team2 { border-top: 5px solid var(--b); }
.team-title { font: 900 13px "Arial Black", sans-serif; letter-spacing: 2px; text-align: center; padding: 4px 0 8px; }
.team-panel.team1 .team-title { color: #7db0ff; }
.team-panel.team2 .team-title { color: #ff8f86; }
.team-members { flex: 1; display: flex; flex-direction: column; gap: 6px; overflow-y: auto;
  /* 34vh fitted two rows. THREE rows at 3v3 — each of which can carry a chat bubble on a second line —
     clipped the third member mid-name on a 390x844 phone. 46vh fits 3 rows with bubbles and still
     leaves the join button and «שחק עכשיו» on screen; it stays scrollable for anything taller. */
  max-height: 46vh; }
.team-join {
  margin-top: 8px; padding: 8px; cursor: pointer;
  border: 2px solid #39463b; background: #141d15; color: #c8d4bf;
  font: 900 12px "Arial Black", sans-serif; letter-spacing: 1px;
}
.team-join.current { background: var(--gold); color: #2a1c06; border-color: #a56a16; }
.vs { align-self: center; color: #8aa08c; font: 900 16px "Arial Black", sans-serif; }
.member-row.is-me { outline: 2px solid var(--gold); }

.timer.urgent { color: #ff5a4d; }
/* Golden goal: the clock is counting down the bounded overtime, not the match. */
.timer.overtime { color: #ffd34d; border-color: #ffd34d; }

/* ---- Home / friends menus ---- */
.screen#home {
  background: #172219 url('/assets/block-stadium.webp') center/cover no-repeat;
}
.home-wrap {
  position: relative; z-index: 1;
  width: min(92vw, 460px); padding: 16px;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  max-height: 100%; overflow-y: auto;
  background: rgba(19,27,22,.94); border: 4px solid #657264;
  box-shadow: inset 0 0 0 3px #182018, 0 10px 0 #101611, 0 18px 44px rgba(0,0,0,.55);
}
.char-card { display: flex; flex-direction: column; align-items: center; gap: 6px; margin: 6px 0 10px; }
.char-face {
  width: 96px; height: 96px; border: 4px solid #ffe49a; background: #26332a center/cover no-repeat;
  box-shadow: 5px 5px 0 #050705, inset 0 0 0 3px #2a1b12;
  display: flex; align-items: center; justify-content: center;
  color: #e7dcae; font: 900 34px "Arial Black", sans-serif;
}
.char-name-big { color: #fff4ca; font: 900 22px "Arial Black", sans-serif; letter-spacing: .5px; }
.char-sub { color: #8aa08c; font-size: 9px; font-weight: 900; letter-spacing: 2px; }
.play.ghost { background: #2c3a2e; color: #e7dcae; box-shadow: inset 0 -6px #1a241c, 0 6px 0 #0b0f0b; border-color: #4a5a4c; }
.play.small { padding: 10px 14px; font-size: 13px; }
.join-row { display: flex; gap: 10px; width: 100%; }
.code-input {
  flex: 1; min-width: 0; text-align: center; text-transform: uppercase; letter-spacing: 8px;
  font: 900 26px "Arial Black", sans-serif; color: #fff4ca;
  background: #111812; border: 3px solid #39463b; padding: 10px; outline: none;
}
.code-input:focus { border-color: var(--gold); }
.play.join-btn { width: auto; padding: 12px 22px; }

/* ---- Friends panel (search / requests / list + presence) ---- */
#friends-panel { display: flex; flex-direction: column; gap: 10px; width: 100%; max-width: 460px; flex: 1 1 auto; min-height: 0; }

/* Friends redesign: segmented tabs + panes. */
.fr-tabs { display: flex; gap: 6px; width: 100%; }
.fr-tab {
  flex: 1; padding: 11px 6px; border: 3px solid #46543f;
  background: rgba(19,27,22,.94); color: #c8d4bf; font: 900 13px "Arial Black", sans-serif;
  display: flex; align-items: center; justify-content: center; gap: 6px; cursor: pointer;
  box-shadow: 0 4px 0 #070b08;
}
.fr-tab.active { background: rgba(40,54,40,.98); color: #fff4ca; border-color: #7ea862; }
.fr-tab:active { transform: translateY(2px); box-shadow: 0 2px 0 #070b08; }
.fr-badge {
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
  background: #ff4d4d; color: #fff; font-size: 11px; line-height: 18px; text-align: center;
}
.fr-tab.active .fr-badge { background: #04240f; color: #fff; }
.fr-pane { width: 100%; flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.fr-pane .friend-list { max-height: none; }
.fr-room { display: flex; flex-direction: column; gap: 8px; width: 100%; margin-top: 4px; }

/* Party flow: invite-online-friends panel (in the private-room lobby). */
.party-invite { width: 100%; display: flex; flex-direction: column; gap: 6px; margin: 6px 0; }
.party-invite .pi-h { color: #aaa; font: 800 12px "Arial Black", sans-serif; text-align: right; }
.party-invite .pi-empty { color: #8aa08c; font-size: 12px; text-align: center; padding: 8px 6px; }
.party-invite .pi-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; background: #17171b; border-radius: 10px; }
.party-invite .pi-name { flex: 1; text-align: right; color: #fff; }

/* Party flow: game picker overlay (host taps "בחר משחק"). */
.game-select { position: fixed; inset: 0; z-index: 60; display: flex; align-items: center; justify-content: center;
  background: rgba(4,8,6,.78); backdrop-filter: blur(3px); }
/* min(520px) so the MINI picker cards (same look as the lobby's) fit one row here too. */
.game-select-card { width: min(520px, 92vw); background: #12151a; border: 2px solid #2c3830; border-radius: 16px;
  padding: 16px; display: flex; flex-direction: column; gap: 10px; box-shadow: 0 12px 40px rgba(0,0,0,.6); }
.game-select-head { display: flex; align-items: center; justify-content: space-between; }
.game-select-head h2 { margin: 0; color: #fff; font: 900 18px "Arial Black", sans-serif; }
.ps-or { display: flex; align-items: center; gap: 10px; color: #8aa08c; font-size: 12px; margin: 2px 0; }
.ps-or::before, .ps-or::after { content: ""; flex: 1; height: 1px; background: #2c3830; }
.friend-input { width: 100%; padding: 12px; border: 3px solid #46543f; background: rgba(11,16,12,.72); color: #fff4ca; text-align: right; font: 800 14px "Arial Black", sans-serif; box-shadow: 0 4px 0 #070b08; }
.friend-input:focus { outline: none; border-color: #7ea862; }
.friend-list { display: flex; flex-direction: column; gap: 10px; }
.friend-row { display: flex; direction: rtl; align-items: center; gap: 12px; padding: 12px; background: rgba(19,27,22,.94); border: 3px solid #46543f; box-shadow: 0 4px 0 #070b08; }
.friend-dot { width: 9px; height: 9px; border-radius: 50%; background: #555; flex: 0 0 auto; }
.friend-row.online .friend-dot { background: #35d06a; box-shadow: 0 0 6px #35d06a; }
.friend-pfp { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; background: #333; }
.friend-name { flex: 1; text-align: right; color: #fff4ca; font: 900 14px "Arial Black", sans-serif; }
.friend-act { padding: 8px 14px; border: 0; background: #35d06a; color: #04240f; font: 900 12px "Arial Black", sans-serif; box-shadow: 0 3px 0 #1a7a3f; }
.friend-act:active { transform: translateY(2px); box-shadow: 0 1px 0 #1a7a3f; }
.friend-act:disabled { background: #46543f; color: #8ea08a; box-shadow: 0 3px 0 #070b08; }
.friend-act.ghost { background: transparent; border: 3px solid #46543f; color: #c8d4bf; box-shadow: 0 3px 0 #070b08; }
/* Built-in bot friends: subtle distinct row + a tag instead of a challenge button. */
.friend-row.is-bot { background: rgba(20,32,26,.92); border-color: #3f5a44; }
.pi-row.is-bot { background: #14201a; }
.friend-bot-tag { font: 800 11px "Arial Black", sans-serif; color: #8fe0a6; background: #10241a;
  border: 1px solid #244b34; border-radius: 8px; padding: 5px 9px; white-space: nowrap; }

/* Rich friend card: pfp · (name/rank/xp + top-3 cards). */
.friend-card { display: flex; direction: rtl; gap: 12px; align-items: center; padding: 12px; background: rgba(19,27,22,.94);
  border: 3px solid #46543f; box-shadow: 0 4px 0 #070b08; }
.friend-card.is-bot { background: rgba(20,32,26,.92); border-color: #3f5a44; }
.fc-pfp { width: 44px; height: 44px; border-radius: 50%; flex: 0 0 auto; background: #333; color: #fff;
  display: flex; align-items: center; justify-content: center; font: 900 15px "Arial Black", sans-serif; overflow: hidden; }
.fc-pfp img { width: 100%; height: 100%; object-fit: cover; }
.fc-pfp.sm { width: 34px; height: 34px; font-size: 12px; }
.fc-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.fc-top { display: flex; align-items: center; gap: 6px; }
.fc-name { color: #fff4ca; font: 900 14px "Arial Black", sans-serif; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fc-meta { color: #b8caa8; font-size: 11px; font-weight: 800; }
.fc-cards { display: flex; gap: 4px; margin-top: 2px; }
.fc-card { width: 30px; height: 42px; border-radius: 5px; object-fit: cover; background: #0d0d10;
  border: 1.5px solid #333; image-rendering: pixelated; }
.fc-card.rarity-legendary { border-color: #ffb800; } .fc-card.rarity-epic { border-color: #b46bff; }
.fc-card.rarity-rare { border-color: #4ea0ff; } .fc-card.rarity-common { border-color: #9ab0c5; }

/* Friend-select (step 1) multi-select rows. */
.fs-list { display: flex; flex-direction: column; gap: 6px; max-height: 46vh; overflow-y: auto; width: 100%; }
.fs-row { display: flex; align-items: center; gap: 10px; padding: 7px 9px; width: 100%; text-align: right;
  background: #17171b; border: 2px solid #2c2c33; border-radius: 12px; color: #fff; cursor: pointer; }
.fs-row.sel { border-color: #35d06a; background: #14251b; }
.fs-row.is-bot { background: #14201a; }
.fs-name { flex: 1; color: #fff; font: 800 13px "Arial Black", sans-serif; text-align: right; }
.fs-chk { width: 22px; height: 22px; border-radius: 50%; flex: 0 0 auto; display: flex; align-items: center;
  justify-content: center; background: #0d0d10; border: 2px solid #35d06a; color: #35d06a; font-weight: 900; }
.friends-h { color: #aaa; margin: 8px 0 0; text-align: right; }

/* Global toast (top-level, outside every .screen) — hidden by the shared .hidden rule. */
.fp-toast {
  position: fixed; top: 18px; left: 50%; transform: translateX(-50%); z-index: 9999;
  background: #17171b; color: #fff; padding: 10px 16px; border-radius: 10px;
  direction: rtl; box-shadow: 0 4px 14px rgba(0,0,0,.4); max-width: 90vw; text-align: center;
}

/* ---- Team intro / VS overlay (match start + quick-match countdown) ----
   FIT RULE (rewritten 2026-07-26): the screen must fit 2v2, 3v3 and 5v5 at any phone height with
   ZERO overflow and zero per-format hand-tuning. Two things did the damage before:
     · the big 5..0 countdown sat IN the column flow, eating 58–128px of the very height the extra
       rows needed (at 3v3 on a 390px-tall landscape phone the stack came to ~401px);
     · rows had FIXED heights (40px avatar, 42px cards), so a third or fifth row simply ran off.
   Now: a 3-row grid (mode · columns · hint) where the columns row is the only flexible one, the
   countdown is ABSOLUTE over the centre gutter (where «נגד» sits — the Brawl Stars / Fortnite VS
   placement), and every row/avatar/card is sized by the height it actually gets (flex + aspect-ratio
   + vh clamps). Add a 7v7 tomorrow and it still fits. */
.team-intro {
  position: fixed; inset: 0; z-index: 70; display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto; justify-items: center; align-items: center;
  gap: clamp(4px, 1.2vh, 12px); padding: clamp(6px, 1.6vh, 16px) 10px; overflow: hidden;
  background: radial-gradient(circle at 50% 40%, rgba(14,22,40,.93), rgba(6,10,20,.97));
  opacity: 0; transition: opacity .34s; pointer-events: none;
}
.team-intro.show { opacity: 1; pointer-events: auto; }
.team-intro.hidden { display: none; }
/* align-SELF stretch (the grid sets align-items:center for the mode/hint rows): without it this row
   sizes to CONTENT, the columns collapse to ~34px and every card inside them disappears. The whole
   flex chain below needs a definite height to resolve `height:100%` against. */
/* grid-row PINNED (see the matching notes on .ti-mode / .ti-tap below): without it, auto-placement
   assigns rows by counting IN-FLOW siblings, and a hidden #ti-mode (display:none, so it contributes
   no box at all) leaves only 2 in-flow children for 3 row tracks — .ti-inner then lands in row 1
   (sized `auto`, i.e. to its own content) instead of row 2's `1fr`, collapsing the whole columns
   area to ~33px. Latent since #team-intro was built; only exposed once showSearching() (Task 5)
   became the first caller to show this overlay before #ti-mode has any content. Found via a real
   CDP screenshot, not assumed. */
.ti-inner { grid-row: 2; align-self: stretch; display: flex; align-items: stretch; justify-content: center; gap: clamp(6px, 1.4vw, 14px); width: min(94vw, 720px); max-height: 100%; min-height: 0; }
.ti-col {
  flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column;
  background: rgba(255,255,255,.04); border: 2px solid #26324c; border-radius: 12px;
  padding: clamp(5px, 1.2vh, 12px) clamp(6px, 1vw, 12px);
}
.ti-col.ti-home { border-color: #3d6fb5; box-shadow: inset 0 0 22px rgba(78,160,255,.22); }
.ti-col.ti-away { border-color: #b5474f; box-shadow: inset 0 0 22px rgba(255,91,106,.22); }
.ti-team-title { flex: none; text-align: center; font: 900 clamp(10px, 1.9vh, 13px)/1.1 "Arial Black", sans-serif; letter-spacing: 2px; margin-bottom: clamp(3px, 1vh, 10px); }
.ti-home .ti-team-title { color: #7fbcff; }
.ti-away .ti-team-title { color: #ff8a94; }
.ti-vs { flex: none; align-self: center; font: 900 clamp(15px, 3.6vh, 26px) "Arial Black", sans-serif; color: #ffe49a; text-shadow: 0 3px #0b0f0b; transition: opacity .2s; }
/* While the 5..0 counts down, the number OWNS the centre gutter — «נגד» would collide with it. */
.team-intro.counting .ti-vs { opacity: 0; }
.ti-rows { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: clamp(3px, 1vh, 10px); }
/* Each seat takes an equal share of whatever height the column got: avatar · hero · name on ONE line
   (the card row under the name went with the cards, 2026-10-10). */
.ti-row {
  flex: 1 1 0; min-height: 0; display: grid;
  grid-template-columns: auto auto minmax(0, 1fr); grid-template-rows: minmax(0, 1fr);
  gap: 0 clamp(5px, 1vw, 10px); align-items: center;
}
.ti-hero { height: 100%; max-height: 52px; aspect-ratio: 44 / 52; display: flex; align-items: flex-end; justify-content: center; }
.ti-hero canvas { height: 100%; width: auto; image-rendering: pixelated; }
.ti-col[data-size="5"] .ti-hero { max-height: 32px; }
.ti-av {
  height: 100%; aspect-ratio: 1; max-height: 40px; max-width: 22vw;
  border-radius: 8px; border: 2px solid #ffe49a;
  background: #26332a center/cover no-repeat; display: flex; align-items: center; justify-content: center;
  font: 900 clamp(10px, 2vh, 15px) "Arial Black", sans-serif; color: #e7dcae; overflow: hidden;
}
.ti-name { font: 800 clamp(9px, 1.9vh, 13px)/1.15 sans-serif; color: #eaf1ff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* grid-row: 3 PINNED — see the note on .ti-inner: auto-placement is not safe here once a sibling
   (#ti-mode) can be display:none. */
.ti-tap { grid-row: 3; color: #9fb0d0; font: 700 clamp(9px, 1.7vh, 11px) sans-serif; letter-spacing: 1px; }
/* Big 5..0 countdown — ABSOLUTE over the centre gutter (it replaces «נגד» while counting) instead of
   a row in the stack. In flow it cost up to 128px of height, which is exactly what pushed 3v3 off a
   short landscape screen. Sized in vh so a short phone gets a smaller number, never a taller page. */
.ti-count {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 2;
  pointer-events: none; text-align: center;
  font: 900 clamp(42px, 14vh, 128px)/1 "Arial Black", Impact, sans-serif;
  color: #fff4ca; text-shadow: 0 6px 0 #a56a16, 0 11px 0 #252a21;
}
.ti-count.hidden { display: none; }
/* Mode label above the countdown: WHICH mode + the rule that decides the win. Both live modes
   share this screen, so this line is the only thing distinguishing ראשון ל-3 from קרב על השער. */
/* grid-row: 1 PINNED — when this is display:none (e.g. during a matchmaking SEARCH, before there is a
   format/rule to show), it generates no box, so auto-placement would otherwise hand row 1 to whatever
   in-flow child comes next (.ti-inner) instead of leaving it empty. See the note on .ti-inner. */
.ti-mode { grid-row: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; text-align: center; }
.ti-mode.hidden { display: none; }
.ti-mode-name { font: 900 clamp(11px, 2.2vh, 15px) "Arial Black", sans-serif; color: #ffe49a; text-shadow: 0 2px #0b0f0b; letter-spacing: .5px; }
.ti-mode-rule {
  font: 800 clamp(9px, 1.7vh, 11px) sans-serif; color: #cfe0ff; letter-spacing: 1px; padding: 2px 10px;
  border: 1px solid #2f3f60; border-radius: 999px; background: rgba(255,255,255,.05);
}
/* Per-format nudges only — the FIT is handled by the flex/aspect-ratio sizing above, so these are
   cosmetic caps (a 2-seat column shouldn't blow its avatars up to fill a tall screen) rather than the
   load-bearing rule they used to be. data-size is set by fillIntroCol from the room's teamSize. */
.ti-col[data-size="2"] .ti-av { max-height: 44px; }
.ti-col[data-size="3"] .ti-av { max-height: 38px; }
/* 5v5: ten seats. Names go one line smaller so a long name still fits beside 3 cards. */
.ti-col[data-size="5"] .ti-av { max-height: 30px; }
.ti-col[data-size="5"] .ti-name { font-size: clamp(8px, 1.5vh, 11px); }
.ti-col[data-size="5"] .ti-lvl { display: none !important; } /* no room for the bot level line at 5v5 */

/* Home user screen: dancing character + small avatar/name row. */
.home-char { display: block; width: 150px; height: 158px; image-rendering: pixelated; margin: 0 0 -4px; }
.char-id { display: flex; align-items: center; gap: 10px; }
.home-avatar {
  width: 40px; height: 40px; border-radius: 10px; border: 3px solid #ffe49a; flex: none;
  background: #26332a center/cover no-repeat;
  display: flex; align-items: center; justify-content: center;
  color: #e7dcae; font: 900 15px "Arial Black", sans-serif;
}

/* Post-goal countdown number: match the main-menu countdown treatment. */
.banner.count {
  color: #fff4ca !important;
  font: 900 clamp(90px, 22vw, 200px)/1 "Arial Black", Impact, sans-serif;
  letter-spacing: 0; text-shadow: 0 8px 0 #a56a16, 0 14px 0 #252a21;
}

/* Short landscape (phones): compact the home menu so the carousel + both buttons
   fit without clipping. (The wrap also scrolls as a fallback.) */
@media (max-height: 480px) {
  .home-wrap { gap: 5px; padding: 9px; }
  .home-char { width: 90px; height: 95px; margin: 0; }
  .home-avatar { width: 30px; height: 30px; font-size: 13px; }
  .char-name-big { font-size: 17px; }
  .home-wrap .play { padding: 8px 18px; font-size: 15px; }
  .home-wrap .play b { font-size: 22px; top: 2px; }
}
.room-error { color: #ff7a6e; font-size: 12px; font-weight: 800; }

/* ---- Lobby header additions ---- */
.lobby-head { position: relative; }
.lobby-leave {
  position: absolute; left: 0; top: 0; background: #182019e8; border: 2px solid #667061;
  color: #dfe8fb; font: 900 11px "Arial Black", sans-serif; letter-spacing: 1px;
  padding: 6px 10px; cursor: pointer; box-shadow: 0 4px 0 rgba(0,0,0,.4);
}
.lobby-code-wrap { color: #c8d4bf; font-size: 11px; font-weight: 800; letter-spacing: 1px; margin-top: 6px; }
.lobby-code-wrap b { color: var(--gold); font: 900 22px "Arial Black", sans-serif; letter-spacing: 4px; margin: 0 6px; }
.lobby-code-wrap small { color: #8aa08c; font-size: 9px; letter-spacing: 1px; }
/* The same chip on the TEAM page (#party), where it is a <button> because tapping it copies the code.
   `.subpage-head` is a flex row holding «הקבוצה שלי», so the chip is pushed to the far end with
   margin-inline-start:auto rather than being given a width. Button chrome is stripped — it has to read
   as the same label it is in #lobby, not as a second action next to the heading — leaving the tap
   target and a pressed state as the only signs it is pressable. margin-top is cleared: the #lobby copy
   sits UNDER a title in a column, this one sits BESIDE one and would hang low. */
.party-code-wrap {
  appearance: none; background: none; border: 0; padding: 4px 6px; margin: 0 0 0 auto; cursor: pointer;
  /* ONLY the family is inherited. `font: inherit` was wrong here: it is shorthand, so it also reset the
     size/weight/letter-spacing that `.lobby-code-wrap` (same element, earlier rule) is supplying, and
     the chip came out at the body's default size. A button needs its family un-defaulted and nothing
     else. */
  font-family: inherit; text-align: end; line-height: 1.1;
}
.party-code-wrap:active { scale: .96; }
.party-code-wrap.hidden { display: none; }   /* the room has no code yet (or is not private) */

/* ---- Hero picker ---------------------------------------------------------- */
.pick-open {
  margin: 8px auto 0; display: inline-flex; align-items: center; gap: 6px;
  background: #2c3a2e; color: #e7dcae; border: 1px solid #4a5a4c; border-radius: 999px;
  padding: 7px 16px; font: 800 13px "Arial Black", sans-serif; letter-spacing: .5px;
  cursor: pointer; box-shadow: 0 4px 0 #0b0f0b;
}
.pick-open:active { transform: translateY(3px); box-shadow: 0 1px 0 #0b0f0b; }

.picker { position: fixed; inset: 0; z-index: 80; display: flex; align-items: center; justify-content: center;
  background: rgba(4,8,10,.78); backdrop-filter: blur(6px); padding: 16px; }
.picker.hidden { display: none; }
.picker-panel {
  width: 100%; max-width: 380px; max-height: 92vh; overflow-y: auto;
  background: var(--stone); border: 1px solid #3a473d; border-radius: 18px;
  padding: 16px; display: flex; flex-direction: column; gap: 12px; align-items: center;
  box-shadow: 0 24px 60px rgba(0,0,0,.5);
}
.picker-top { width: 100%; display: flex; align-items: center; justify-content: space-between; }
.picker-title { font: 900 18px "Arial Black", sans-serif; color: var(--ink); letter-spacing: .5px; }
.picker-x { background: none; border: 0; color: #9fb0a2; font-size: 20px; cursor: pointer; line-height: 1; padding: 4px; }
.picker-x:hover { color: var(--ink); }
.pick-preview { width: 150px; height: auto; image-rendering: pixelated;
  background: radial-gradient(circle at 50% 40%, #1f6b2f, #15481f); border-radius: 14px; border: 1px solid #3a473d; }
.pick-name { font: 900 15px "Arial Black", sans-serif; color: var(--gold); letter-spacing: .4px; text-align: center; min-height: 18px; }

.pick-tiers { display: flex; gap: 7px; flex-wrap: wrap; justify-content: center; }
.pick-tier { display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  background: #202a23; border: 1px solid #3a473d; border-radius: 999px; padding: 6px 12px;
  color: #b8c6ba; font: 800 12px "Arial Black", sans-serif; letter-spacing: .3px; }
.pick-tier .dot { width: 9px; height: 9px; border-radius: 50%; background: #9aa79f; }
.pick-tier.on { color: var(--ink); border-color: var(--gold); background: #2a352b; }
.pick-tier.r-Rare .dot { background: linear-gradient(135deg, #ffe08a, #e0a72e); }
.pick-tier.r-Epic .dot { background: conic-gradient(from 0deg, #ff5d5d, #ffd24d, #5dff8f, #4dd2ff, #b06bff, #ff5d5d); }
.pick-tier.r-Legendary .dot { background: #ffa24b; box-shadow: 0 0 6px #ffa24b; }

.pick-heroes { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; width: 100%; }
.pick-hero { display: flex; flex-direction: column; align-items: center; gap: 2px; cursor: pointer;
  background: #202a23; border: 2px solid transparent; border-radius: 12px; padding: 6px 2px 5px; }
.pick-hero canvas { width: 66px; height: 78px; image-rendering: pixelated; }
.pick-hero span { font: 800 11px "Arial Black", sans-serif; color: #9fb0a2; letter-spacing: .3px; }
.pick-hero.on { border-color: var(--gold); background: #2a352b; }
.pick-hero.on span { color: var(--ink); }
.picker-panel .play { width: 100%; margin-top: 2px; }

/* ================= Home hub (Brawl-style LANDSCAPE) =================
   Authored at a fixed 900x415 logical stage and scaled uniformly to fit the
   viewport via a JS transform (fitHub in client.js) — so the whole hub scales
   as one unit and never clips, like a game canvas. Every size here is in px
   against that fixed stage. All classes are namespaced hub-* so they never
   collide with the wardrobe/experience agents. */
.screen#home { align-items: center; justify-content: center; overflow: hidden; }
.hub {
  position: absolute; left: 50%; top: 50%; z-index: 1;
  width: 900px; height: 415px; transform-origin: center center;
  transform: translate(-50%, -50%);       /* fitHub() appends scale() */
  display: flex; flex-direction: column; overflow: hidden;
}
/* Perspective pitch stripes at the foot of the hub (matches the mockup) */
.hub-pitch {
  position: absolute; left: -14%; right: -14%; bottom: -4%; height: 40%; z-index: 0; pointer-events: none;
  background: repeating-linear-gradient(90deg, #123a20 0 46px, #0f2f19 46px 92px);
  transform: perspective(460px) rotateX(60deg); transform-origin: bottom center; opacity: .4;
  -webkit-mask-image: linear-gradient(0deg, #000 26%, transparent); mask-image: linear-gradient(0deg, #000 26%, transparent);
}

/* --- TOP BAR: friends/season/rank (physical left) · profile+XP (physical right) --- */
.hub-top { flex: 0 0 auto; position: relative; z-index: 4; display: flex; direction: ltr; justify-content: space-between; align-items: flex-start; padding: 8px 22px 0; gap: 8px; }
.hub-cluster { display: flex; direction: rtl; }
.hub-cluster.left { flex-direction: row; align-items: center; gap: 7px; }
.hub-cluster.right { flex-direction: column; align-items: flex-end; gap: 6px; }
/* compact icon buttons (exit, settings) */
.hub-icobtn {
  appearance: none; width: 38px; height: 38px; flex: none; cursor: pointer; display: grid; place-items: center;
  font: 900 17px "Arial Black", sans-serif; color: #eef3ea;
  background: rgba(19,27,22,.9); border: 3px solid #46543f; box-shadow: 0 4px 0 #070b08;
}
.hub-icobtn:active { transform: translateY(2px); box-shadow: 0 2px 0 #070b08; }
/* A BUTTON since 2026-08-03 (it opens #online, the connected-players page) — the UA resets keep it
   pixel-identical to the <span> it replaced, so the baked stage layout is unaffected. */
.hub-online {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  font: 900 10px "Arial Black", sans-serif; letter-spacing: .5px; color: #c8d4bf;
  background: rgba(11,16,12,.85); border: 3px solid #3a473c; padding: 6px 9px; box-shadow: 0 4px 0 #070b08;
  -webkit-appearance: none; appearance: none; border-radius: 0; cursor: pointer;
}
.hub-online:active { transform: translateY(2px); box-shadow: 0 2px 0 #070b08; }
/* The chip is ~26 logical px tall and the stage is scaled DOWN on a phone, so the visible box is a
   poor thumb target. This grows the touch area well past it without moving a single pixel of layout. */
.hub-online::after { content: ''; position: absolute; inset: -12px; }
.hub-online i { width: 9px; height: 9px; border-radius: 50%; background: #57e08a; box-shadow: 0 0 8px #57e08a; }
.hub-season {
  font: 900 10px "Arial Black", sans-serif; color: #2a1c05; padding: 6px 9px;
  background: linear-gradient(180deg, #ffe08a, #ffcb43); border: 2px solid #fff4ca; box-shadow: 0 3px 0 #a56a16;
}
/* ============================================================================
   PIXEL-ART RANK BADGES — the collector badge (#hub-rank, over the cards) and the
   rank badge (#hub-tier, over the hero) render as 8-bit emblems: notched pixel
   corners, a hard 2-band fill + raised bevel, icon-forward with minimal text.
   Tier colour = --c1/--c2 (set inline per tier by renderHubTier for #hub-tier;
   gold default for the collector). NOTE: clip-path clips box-shadow, so the chunky
   drop shadow is done with filter: drop-shadow instead.
   ============================================================================ */
/* Both badges share ONE fixed squarish box (60×50, set in the baked layout below) with
   a vertical icon-over-text layout, so they read as matching pixel emblems. */
.hub-tier {
  --c1: #ffe27a; --c2: #e0a92a;
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  position: relative; white-space: nowrap; box-sizing: border-box;
  color: #241a05; padding: 3px;
  background: linear-gradient(180deg, var(--c1) 0 50%, var(--c2) 50% 100%);
  /* Bevel removed (user, 2026-07-27 — the אדיר / 620 badges were the "thin lines" still visible after
     the .hub-chip and .hub-xpbar borders went). It was
       inset 2px 2px 0 rgba(255,255,255,.5), inset -2px -2px 0 rgba(0,0,0,.42)
     i.e. a white 2px line down the top-left and a black one down the bottom-right. On a small gold
     emblem those read as two stray hairlines rather than as depth. Nothing else depended on it: the
     180deg gradient (light top half, dark bottom half) still gives the badge its dimension, the
     clip-path keeps the chamfered pixel silhouette, and the drop-shadow keeps it lifted off the art. */
  filter: drop-shadow(0 3px 0 #070b08);
  image-rendering: pixelated;
  clip-path: polygon(
    0 3px, 3px 3px, 3px 0,
    calc(100% - 3px) 0, calc(100% - 3px) 3px, 100% 3px,
    100% calc(100% - 3px), calc(100% - 3px) calc(100% - 3px), calc(100% - 3px) 100%,
    3px 100%, 3px calc(100% - 3px), 0 calc(100% - 3px)
  );
}
.hub-tier .px-ic { font-size: 18px; line-height: 1; }
.hub-tier .px-sub { font: 900 9px "Arial Black", sans-serif; line-height: 1.05; text-shadow: 0 1px 0 rgba(255,255,255,.35); }
.hub-tier .hub-tier-lbl { display: inline-flex; flex-direction: column; align-items: center; gap: 1px; }
.hub-tier .px-sub { text-align: center; }
/* hero badge only: reserve the bottom strip for the slim sub-rank progress bar (square, pixel) */
.hub-tier { padding-bottom: 8px; }
.hub-tier .hub-tier-bar { position: absolute; left: 4px; right: 4px; bottom: 3px; height: 3px;
  background: rgba(0,0,0,.4); overflow: hidden; }
.hub-tier .hub-tier-bar b { display: block; height: 100%; width: 0%; background: #241a05; }
/* profile pill: face (right) + name/XP (left) */
.hub-id { display: flex; direction: rtl; align-items: center; gap: 8px; background: rgba(19,27,22,.85); border: 3px solid #3a473c; box-shadow: 0 4px 0 #070b08; padding: 5px 7px; }
.hub-idtext { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; min-width: 0; }
.hub-name { color: #fff4ca; font: 900 14px "Arial Black", sans-serif; letter-spacing: .3px; line-height: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 220px; }
.hub-pfp {
  width: 40px; height: 40px; flex: none; border: 3px solid #ffe49a;
  background: #26332a center/cover no-repeat; color: #e7dcae;
  font: 900 15px "Arial Black", sans-serif; display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 0 0 2px #0c1322;
}
/* inline XP bar beside the name (renderHubXp injects .hub-xp-top / .hub-xp-bar) */
.hub-xp { width: 158px; }
.hub-xp.hidden { display: none !important; }
.hub-xp-top { display: flex; align-items: center; justify-content: space-between; gap: 6px; font: 900 9px "Arial Black", sans-serif; color: #c8d4bf; }
/* Win-streak chip (renderHubStreak): a live 7x10 pixel fire + the count, sized to the bar's 9px
   type. Sits right after רמה N in the flex row; hidden entirely at streak 0. */
.hub-streak { display: inline-flex; align-items: center; gap: 2px; margin-inline-start: -2px; }
.hub-streak-fire { width: 8px; height: 11px; image-rendering: pixelated; image-rendering: crisp-edges; }
.hub-streak b { font: 900 10px "Arial Black", sans-serif; color: #ffb63c; text-shadow: 0 1px 0 #3a1602; }
.hub-xp-lvl b { color: var(--gold); font-size: 11px; }
.hub-xp-amt { color: #8aa08c; font-variant-numeric: tabular-nums; letter-spacing: .2px; direction: ltr; unicode-bidi: isolate; }
/* No border — same #34413a line removed from .hub-chip. This is the INNER progress track, so the pale
   outline showed up as a second thin line nested inside the trophies panel's own. The track still
   reads without it: its rgba(0,0,0,.5) groove is darker than the panel it sits in, and the gold fill
   supplies the contrast. box-sizing is border-box, so the track stays 8px tall and the fill simply
   occupies all 8 instead of 4 — slightly chunkier, which suits the pixel styling. */
.hub-xp-bar { height: 8px; margin-top: 3px; background: rgba(0,0,0,.5); overflow: hidden; }
.hub-xp-bar b { display: block; height: 100%; background: linear-gradient(90deg, #4ea0ff, #7b3ad0); box-shadow: 0 0 8px rgba(78,160,255,.7); }
/* album chips + collector badge — a row below the profile pic */
.hub-albumbar { display: flex; direction: rtl; align-items: center; gap: 5px; }

/* --- EDGE RAILS (absolute, vertically centered) --- */
.hub-siderail { position: absolute; top: 50%; transform: translateY(-50%); z-index: 5; display: flex; flex-direction: column; gap: 12px; }
.hub-siderail.left { left: 18px; }
.hub-siderail.right { right: 18px; }
.hub-sat {
  position: relative;
  appearance: none; width: 62px; height: 62px; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  background: rgba(19,27,22,.9); border: 3px solid #46543f; color: #eef3ea; box-shadow: 0 5px 0 #070b08;
}
.hub-sat .hub-sat-ic { font-size: 22px; line-height: 1; }
.hub-sat b { font: 900 9px "Arial Black", sans-serif; letter-spacing: .3px; }
.hub-sat:active { transform: translateY(3px); box-shadow: 0 2px 0 #070b08; }
.hub-sat-dot { position: absolute; top: 5px; right: 5px; width: 10px; height: 10px; border-radius: 50%; background: #25e06a; box-shadow: 0 0 6px #25e06a, 0 0 0 2px #0b120d; }
.hub-sat-dot.hidden { display: none; }

/* --- CENTER STAGE: small carousel (top) + bottom band (arena + play row).
   The big hero + card slots are absolute overlays anchored to .hub (below). --- */
.hub-stage { flex: 1 1 auto; position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; justify-content: space-between; gap: 2px; padding: 2px 100px 6px; min-height: 0; }

/* bottom band wrapper (legacy) — content relocated to absolute overlays below. */
.hub-bottom { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 5px; width: 100%; }

/* ---- BIG HERO — absolute overlay anchored to the 900x415 stage ----
   Height ≈ 240px (~58% of 415); feet sit at ~80% down (bottom:80 → feet ≈ 335px),
   head reaches up toward the carousel. It's the biggest thing on screen and can
   overlap the carousel/arena band for depth. */
.hub-hero {
  appearance: none; background: none; border: none; cursor: pointer; padding: 0;
  position: absolute; left: 50%; bottom: 42px; transform: translateX(-50%); z-index: 4;
  display: flex; align-items: flex-end; justify-content: center; pointer-events: auto;
}
.hub-hero::before {
  content: ''; position: absolute; left: 50%; bottom: 6px; transform: translateX(-50%);
  width: 78%; height: 26%; border-radius: 50%; pointer-events: none; z-index: 0;
  background: radial-gradient(closest-side, rgba(5,10,7,.75), rgba(5,10,7,.4) 60%, transparent 76%);
}
.hub-char {
  position: relative; z-index: 1; height: 360px; width: auto; image-rendering: pixelated;
  filter: drop-shadow(0 12px 10px rgba(0,0,0,.6));
}
.hub-herohint {
  position: absolute; bottom: -6px; left: 50%; transform: translateX(-50%); white-space: nowrap;
  font: 900 9px "Arial Black", sans-serif; letter-spacing: .4px; color: #ffcb43; opacity: .9;
}

/* arena / mode button — compact pill that sits immediately beside Quick Match; matches the
   Training button's height and is a bit wider than it. */
.hub-arena {
  appearance: none; flex: 0 0 auto; display: flex; direction: rtl; align-items: center; gap: 8px;
  width: 168px; height: 46px; padding: 4px 12px; cursor: pointer; position: relative; z-index: 3;
  background: rgba(15,22,17,.82); border: 2px solid #3a473c; color: #cdd8c8; box-shadow: 0 3px 0 #070b08; overflow: hidden;
}
.hub-arena:active { transform: translateY(2px); box-shadow: 0 1px 0 #070b08; }
.hub-arena-ic { font-size: 18px; flex: none; }
.hub-arena-tx { flex: 1; min-width: 0; text-align: right; }
.hub-arena-tx b { display: block; font: 900 12px "Arial Black", sans-serif; color: #e7dcae; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* subtitle + chevron hidden in the compact pill so the arena reads as a single line (like Training) */
.hub-arena-tx small { display: none; }
.hub-arena-chev { display: none; }

/* --- Bottom-RIGHT action cluster: arena (mode) + Quick Match, aligned to the Clubs rail line --- */
.hub-actions {
  position: absolute; right: 88px; top: 192px; z-index: 5;
  display: flex; direction: rtl; align-items: center; gap: 9px;
}

/* play row (legacy wrapper) — Quick Match is the dominant primary; Training is secondary */
.hub-plays { flex: 0 0 auto; display: flex; direction: rtl; gap: 10px; align-items: stretch; justify-content: center; position: relative; z-index: 5; }
.hub-mode {
  appearance: none; flex: none; cursor: pointer; text-align: right;
  display: flex; direction: rtl; align-items: center; gap: 8px; padding: 5px 13px;
  background: rgba(19,27,22,.92); border: 3px solid #39463b; color: #e7dcae;
  box-shadow: inset 0 -5px #0b100c, 0 5px 0 rgba(0,0,0,.35); overflow: hidden;
}
.hub-mode .hub-mic { width: 30px; height: 30px; flex: none; display: grid; place-items: center; font-size: 16px; background: rgba(0,0,0,.28); }
/* Label column clips to the button so long Hebrew strings never overflow the box. */
.hub-mtx { min-width: 0; overflow: hidden; }
.hub-mtx b { display: block; font: 900 13px "Arial Black", sans-serif; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hub-mtx small { display: block; color: #8aa08c; font-size: 9px; font-weight: 800; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hub-mode .hub-go { margin-right: auto; font: 900 20px "Arial Black", sans-serif; color: #8aa08c; }
.hub-mode:active { transform: translateY(3px); box-shadow: inset 0 -3px #0b100c, 0 2px 0 rgba(0,0,0,.35); }
/* secondary (Training): smaller + quieter than the primary; sits bottom-left. */
.hub-mode:not(.quick) { padding: 6px 12px; opacity: .96; }
.hub-mode:not(.quick) .hub-mic { width: 26px; height: 26px; font-size: 14px; }
.hub-mode:not(.quick) .hub-mtx b { font-size: 12px; }
.hub-training {
  position: absolute; left: 120px; bottom: 26px; z-index: 5;
  width: 120px; height: 46px; justify-content: center;
}
/* PRIMARY — Quick Match: the brightest, chunkiest gold element. Now a compact, squarer
   button (icon stacked over the label) rather than a long bar. */
.hub-mode.quick {
  flex-direction: column; justify-content: center; align-items: center; text-align: center;
  width: 132px; min-width: 0; height: 104px; padding: 8px 8px; gap: 3px;
  background: linear-gradient(180deg, #ffd53d, #f2b204); color: #2a1c05; border: 4px solid #fff2a0;
  box-shadow: inset 0 -8px #b9760f, 0 8px 0 #0b0f0b, 0 10px 20px rgba(0,0,0,.5);
}
.hub-mode.quick .hub-mic { width: 38px; height: 38px; font-size: 22px; background: rgba(0,0,0,.12); }
.hub-mode.quick .hub-mtx { text-align: center; width: 100%; }
.hub-mode.quick .hub-mtx b { font-size: 18px; letter-spacing: .5px; line-height: 1; }
.hub-mode.quick .hub-mtx small { color: #6a4a08; font-size: 8px; display: block; margin-top: 2px; }
.hub-mode.quick .hub-go { display: none; }
.hub-mode.quick:active { transform: translateY(4px); box-shadow: inset 0 -4px #b9760f, 0 4px 0 #0b0f0b, 0 6px 12px rgba(0,0,0,.5); }

/* --- Sub-screens (arena / news / shop / clubs / rank / friends) — all share the full-bleed
   stadium backdrop + stretched .subpage, so friends & rank match clubs' look/transparency. ---
   #friend-select is NOT in this list — it is a MODAL over #party now (see below), not a full-bleed
   page of its own. */
.screen#arena,.screen#news,.screen#shop,.screen#clubs,.screen#rank,.screen#friends,.screen#party {
  background: #172219 url('/assets/block-stadium.webp') center/cover no-repeat;
  align-items: stretch; justify-content: stretch;
}
/* Invite SHEET: a dim/blur backdrop over whatever is behind it (#party) with a floating card, the
   same modal language .game-select / .room-wait already use — not the stadium full-bleed look, so a
   screenshot reads unambiguously as "a sheet opened on top", not "we navigated to a new page". */
.screen#friend-select {
  background: rgba(4, 10, 7, .74); backdrop-filter: blur(3px);
}
.screen#friend-select .subpage {
  width: min(600px, 94vw); height: min(360px, 88vh); max-height: 88vh;
  background: #12181a; border: 3px solid #2c3830; border-radius: 14px;
  box-shadow: 0 16px 46px rgba(0,0,0,.55);
  padding: 12px max(14px, env(safe-area-inset-right)) max(12px, env(safe-area-inset-bottom)) max(14px, env(safe-area-inset-left));
}
.screen#friend-select .subpage-head { margin-bottom: 8px; }
.screen#friend-select .subpage-head h2 { font-size: 18px; }
/* BUG FOUND VIA CDP, NOT ASSUMED: at 844x390 the fixed-height card (88vh = 343px here) is shorter
   than .fs-actions' own content (code box + new-room + join-by-code form, ~343px on its own,
   measured) — .fs-actions' inherited `position:sticky` does nothing for its OWN overflow (sticky
   only affects positioning within a scrolling ANCESTOR), so the join button rendered ~46px below
   the card's visible bottom edge, past the rounded border, and was unclickable. Every other
   .screen#friend-select .subpage is short on room the same way on a landscape phone, so the actions
   column gets its own scroll independent of .fs-people's (which already scrolled on its own). */
.screen#friend-select .fs-actions { position: static; overflow-y: auto; max-height: 100%; padding-inline-end: 2px; }

/* ---- Play With Friends · party roster (YOU centered, party members flanking) ---- */
.party-roster { width: 100%; max-width: 640px; display: flex; flex-direction: row; flex-wrap: wrap;
  align-items: flex-end; justify-content: center; gap: 12px;
  padding-top: 6px; }
.pr-me, .pr-mate { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 8px 12px;
  background: rgba(19,27,22,.9); border: 2px solid #46543f; box-shadow: 0 3px 0 #070b08; }
.pr-me { border-color: #7ea862; background: rgba(28,40,28,.94); padding: 10px 16px; }
.pr-rank { font: 900 10px "Arial Black", sans-serif; color: #ffce4a; letter-spacing: .5px; }
.pr-name { font: 900 12px "Arial Black", sans-serif; color: #fff4ca; }
.pr-me .pr-name { font-size: 14px; }
.pr-hero { display: block; image-rendering: pixelated; }
.pr-hero-big { width: 92px; height: 108px; }
.pr-hero-sm { width: 56px; height: 66px; }
.pr-empty { align-self: center; color: #8ea08a; font: 800 12px "Arial Black", sans-serif; padding: 0 6px; }
/* + TILE (item 2): pixel-art `+` beside the hero, always present regardless of party size — tapping
   it opens the invite sheet (#friend-select) as a modal over this page. Uses the SHARED icon pack's
   `add` glyph (.build-icon > .saltiz-icon.si-add — see public/assets/pixel-icon-system-01), not new
   art, so it never falls back to a plain text glyph. */
.pr-add {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  min-height: 92px; padding: 10px 14px; cursor: pointer;
  background: rgba(19,27,22,.55); border: 2px dashed #5a6b53; color: #c8d4bf;
}
.pr-add:active { transform: translateY(2px); }
.pr-add-ic { background: rgba(255,255,255,.08); border-radius: 10px; }
.pr-add-label { font: 900 10px "Arial Black", sans-serif; color: #cfe0c4; white-space: nowrap; }

/* HOST-ONLY KICK (item 5) on each MATE block — never on .pr-me, you can't kick yourself. Confirm
   dialog matches removeFriend()'s pattern; the server enforces host-only regardless (kick is refused
   for a non-host there), so this is UI-only defense in depth, not the real guard. */
.pr-mate { position: relative; }
.pr-kick {
  position: absolute; top: -7px; right: -7px; width: 22px; height: 22px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; z-index: 2;
  background: #d43c3c; color: #fff4ca; border: 2px solid #2a0b0b;
  font: 900 11px/1 "Arial Black", sans-serif; cursor: pointer; box-shadow: 0 2px 0 #070b08;
}
.pr-kick:active { transform: scale(.9); }

/* PER-MODE VOTE GLOW (item 3): a member's tap is a preference — a small coloured dot per voter,
   never a decision. Only the HOST's tap becomes `.mode-picked` (solid gold), which is what actually
   starts the match. Several voters on one card show several dots, not one colour overwriting
   another. Dot colour comes from shared/saltiz-bots.js `colorForMemberId`. */
.pcard.mode-picked { border-color: var(--gold); box-shadow: 0 0 0 3px rgba(255,203,67,.55), 0 5px 0 #070b08; }
.pcard.pc-mini.mode-picked { box-shadow: 0 0 0 3px rgba(255,203,67,.55), 0 4px 0 #070b08; }
.pc-votes { position: absolute; top: 4px; right: 4px; display: flex; gap: 3px; z-index: 2; }
.pc-vote-dot { width: 9px; height: 9px; border-radius: 50%; border: 1px solid rgba(0,0,0,.55); box-shadow: 0 0 3px rgba(0,0,0,.6); }

/* Widened from 460 so four MINI picker cards sit in ONE row (4x104 + 3x8 gaps) instead of wrapping. */
.party-games { width: 100%; max-width: 520px; display: flex; flex-direction: column; gap: 8px; margin-top: 6px; }
.party-games-h { font: 900 13px "Arial Black", sans-serif; color: #fff4ca; text-align: right; }
/* VERTICAL FIT on a 390px landscape phone (#party is now the FIRST screen seen, not a page you
   arrive at after already scrolling past an invite step): the roster's own hero block is ~220px
   tall on its own (hero canvas + name + card row + the reserved speech-bubble slot, none of that new
   here), which left very little of the remaining ~356px content budget for the mode-list below it.
   #party-solo-hint already dropped its own margin in favour of this gap; .party-roster's top padding
   and .party-games's top margin are trimmed too — every px here was previously spent on whitespace,
   not content. Still may need a small scroll on a genuinely short phone; .subpage-body already
   scrolls (overflow-y:auto), so nothing is ever unreachable, just not all visible without it. */
#party .subpage-body { gap: 6px; }
#party .party-roster { padding-top: 0; }
#party-solo-hint { margin-top: 0; }
#party .party-games { margin-top: 0; }

/* ---- Team-page CHAT SHEET (revision of the original always-on right-hand rail: the user asked for
   a hovering half-screen sheet opened from a small bubble icon instead of a permanent column) ------
   A round pixel chat-bubble ICON (`.saltiz-icon.si-chat` — the SAME glyph #chat-btn already uses for
   in-match quick chat, see icon-system.css) sits bottom-left of the team page. Tapping it SLIDES this
   sheet up from the bottom edge to hover over the still-visible team page at ~half the viewport
   HEIGHT (the game is landscape @ 844x390, so height — not width — is what "half screen" means here,
   and it is also the axis the open/close motion runs on); tapping the icon again, or tapping the team
   page outside the sheet (via the existing isDismissBackdrop idiom — see partyEl's click handler in
   client.js), slides it back down. #party no longer reserves any width for a permanent column, so
   the roster/games list get the full width back.
   Composer (#party-chat, existing markup, untouched) is pinned at the sheet's base; the LOG
   (#party-chat-log, untouched) scrolls above it. Per-member bubbles keep rendering on each roster
   block as before (partyBlock/.pr-say) — the sheet is the scrolling history, not a replacement.
   SAFE AREA: still landscape, so env(safe-area-inset-left)/-right are the insets actually non-zero on
   a notched phone (see the note above .ti-search) and env(safe-area-inset-bottom) now matters too —
   the sheet's resting edge and the icon both sit above the home indicator. Same 53e594c convention:
   max(<fallback>, env(safe-area-inset-<side>)). #lobby-chat (a DIFFERENT id, reused for the
   public-match team page) is untouched — none of this is scoped to it. */
.party-chat-bubble {
  position: absolute; z-index: 6;
  right: max(10px, env(safe-area-inset-right)); bottom: max(10px, env(safe-area-inset-bottom));
  width: 44px; height: 44px; border-radius: 50%; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(19,27,22,.92); border: 3px solid #46543f; box-shadow: 0 4px 0 #070b08;
  cursor: pointer; touch-action: manipulation;
  transition: bottom .28s cubic-bezier(.22,.61,.36,1);
}
/* While the sheet is up, the icon RIDES ABOVE IT instead of floating over its composer. The sheet is
   full-width and 50% tall, and the icon sits at z-index 6 (above the sheet's 5), so leaving it at the
   bottom would park a 44px disc on top of the text input and the send button — the "interference" this
   fixes. Moving it also keeps it usable as the close toggle, which hiding it would not.
   `bottom` matches the sheet's own .28s ease so the two move as one gesture. The class is toggled in
   client.js (openPartyChatSheet/closePartyChatSheet) rather than derived with a CSS sibling selector,
   because the icon comes BEFORE the sheet in the DOM and `~` cannot look backwards. */
.party-chat-bubble.sheet-open { bottom: calc(50% + max(10px, env(safe-area-inset-bottom))); }
.party-chat-bubble:active { transform: translateY(2px); box-shadow: 0 2px 0 #070b08; }
.party-chat-bubble .saltiz-icon { --si-size: 22px; }
/* Unread hint: reuses the SAME `member.chat`/`chat.at` state the roster bubbles already read (see
   notePartyChatHistory in client.js) — a timestamp of when the sheet was last opened is the only new
   state. Pulses with the existing fmtPulse keyframe rather than a bespoke animation. */
.pchat-bubble-dot {
  position: absolute; top: -3px; right: -3px; width: 13px; height: 13px; border-radius: 50%;
  background: var(--gold); border: 2px solid #0b120d; box-shadow: 0 0 5px rgba(255,203,67,.75);
  display: none;
}
.party-chat-bubble.has-unread .pchat-bubble-dot { display: block; animation: fmtPulse 1.1s infinite; }
.party-chat-sheet {
  position: absolute; left: 0; right: 0; bottom: 0; height: 50%;
  z-index: 5; display: flex; flex-direction: column;
  /* FULLY OPAQUE on purpose. This started at .94, and 6% of a bright stadium backdrop plus the hero
     block behind it made the roster's cards and the player name legible INSIDE the chat panel — it read
     as broken rather than layered. Verified it was never a stacking bug: nothing inside #party carries
     z >= 5 except this sheet and the bubble, and elementFromPoint at nine points across the open sheet
     returns the log every time. It was purely alpha, and even .985 left a readable ghost of white text
     on the cards behind. The border-top, corner radius and drop shadow carry the "this is an overlay"
     signal on their own, so there is nothing to buy by staying translucent. A backdrop blur would also
     hide it, at the cost of a compositing pass every frame on a phone. */
  background: #0a0f0b; border-top: 3px solid #2c3830; border-radius: 16px 16px 0 0;
  box-shadow: 0 -6px 22px rgba(0,0,0,.4);
  padding: 10px max(10px, env(safe-area-inset-right)) max(10px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left));
  transform: translateY(100%); transition: transform .28s cubic-bezier(.22,.61,.36,1);
  pointer-events: none;
}
.party-chat-sheet.open { transform: translateY(0); pointer-events: auto; }
.party-chat-log { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; padding-right: 2px; }
.pchat-log-empty { color: #8ea08a; font: 700 11px sans-serif; text-align: center; padding: 10px 4px; }
.pchat-log-row { display: flex; flex-direction: column; gap: 2px; background: rgba(255,255,255,.06); border-radius: 8px; padding: 5px 8px; }
/* The sheet's own budget is ~195px (half of 390px landscape), and shrinks further under a real
   safe-area-inset-bottom — measured over CDP with a 44px override: the log fell to ~58px, enough for
   one full row plus a sliver of the next. Trimmed a few px of pure whitespace (never font-size) to
   claw some of that back: the composer separator and the gap/padding around each log row. */
.party-chat-sheet .party-chat-log { gap: 4px; }
.party-chat-sheet .pchat-log-row { padding: 4px 8px; }
.pchat-log-row.me { background: rgba(255,208,106,.16); }
.pchat-log-name { font: 800 10px "Arial Black", sans-serif; color: #9fe0a6; }
.pchat-log-msg { font: 600 12px/1.3 inherit; color: #fff; word-break: break-word; }
.pchat-log-msg .bub-emote { display: inline-block; width: 22px; height: 22px; background-repeat: no-repeat; image-rendering: pixelated; vertical-align: middle; }
/* The composer stays a fixed-size block pinned at the sheet's base — the log above is the only
   scroller. Scoped to the sheet specifically so #lobby-chat's own layout is untouched. Trimmed from
   the default (#lobby-chat's) sizing, but less aggressively than the old narrow rail: the sheet is
   full-width now, so the 16 presets wrap onto fewer lines and can afford to stay bigger while still
   leaving the log more than one line of room in the ~195px (half of 390px landscape) sheet — measured
   over CDP, see the report. */
.party-chat-sheet .party-chat { flex: none; margin-top: 4px; padding-top: 4px; border-top: 2px solid rgba(255,255,255,.14); gap: 5px; }
.party-chat-sheet .pchat-presets { gap: 4px; }
.party-chat-sheet .pchat-pre { padding: 5px 9px; font-size: 11.5px; }
.party-chat-sheet .pchat-pre.is-emote { width: 30px; height: 30px; }
.party-chat-sheet .pchat-input { font-size: 12.5px; padding: 6px 9px; }
.party-chat-sheet .pchat-send { padding: 6px 12px; font-size: 11.5px; }
/* Invite screen sections (online / pending / accepted) */
.fs-sections { display: flex; flex-direction: column; gap: 12px; margin-bottom: 10px; }
.fs-sec-h { font: 900 12px "Arial Black", sans-serif; color: #8fe0a6; text-align: right; margin-bottom: 6px; letter-spacing: .3px; }
/* Friend-request directions (received / sent) — same voice as .fs-sec-h, with room above so the two
   lists read as two groups rather than one long list. First one loses the top margin. */
.fr-sec-h { font: 900 12px "Arial Black", sans-serif; color: #8fe0a6; text-align: right; margin: 12px 0 6px; letter-spacing: .3px; }
.fr-sec-h:first-child { margin-top: 0; }

/* ---- CONNECTED PLAYERS (#online) ----------------------------------------------------------
   Rows reuse .friend-row wholesale, so this page inherits the friends list's look and only needs
   the three things it adds: the header hint, the hero·level subtitle, and the in-match marker. */
.on-hint { font: 800 11px "Arial Black", sans-serif; color: #8ea08a; text-align: right; margin: 0 0 10px; }
.on-sub { font: 800 11px "Arial Black", sans-serif; color: #8fe0a6; background: #10241a;
  border: 1px solid #244b34; border-radius: 8px; padding: 5px 9px; white-space: nowrap; }
/* Still invitable (ruling 2026-08-03) — the tag explains a slow answer, it does not block the row. */
.on-busy { font: 800 10px "Arial Black", sans-serif; color: #ffd88a; background: #2a2010;
  border: 1px solid #5a4620; border-radius: 8px; padding: 5px 8px; white-space: nowrap; }
.friend-row.in-match { border-color: #5a4620; }
/* Two actions per row on a narrow phone: let the name give up space before the buttons wrap. */
#online-list .friend-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
#online-list .friend-act { flex: 0 0 auto; padding: 8px 10px; }
/* SAFE AREA. `viewport-fit=cover` paints under the notch, the dynamic island and the home indicator,
   and this runs in a WebView inside the app — so every sub-page needs the insets, not just #cards and
   the builder (which were the only two that had them). max() keeps the old padding on a flat viewport
   and grows it only where the phone actually reserves space; physical left/right because env() insets
   are physical and the pages are RTL. */
.subpage { position: relative; z-index: 1; width: 100%; height: 100%; display: flex; flex-direction: column;
  padding: max(14px, env(safe-area-inset-top)) max(18px, env(safe-area-inset-right))
           max(20px, env(safe-area-inset-bottom)) max(18px, env(safe-area-inset-left)); }
.subpage-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.subpage-back {
  appearance: none; width: 44px; height: 44px; flex: none; display: grid; place-items: center; cursor: pointer;
  font: 900 22px "Arial Black", sans-serif; color: #fff4ca;
  background: rgba(19,27,22,.85); border: 3px solid #46543f; box-shadow: 0 4px 0 #070b08;
}
.subpage-back:active { transform: translateY(2px); box-shadow: 0 2px 0 #070b08; }
.subpage-head h2 { font: 900 22px "Arial Black", sans-serif; letter-spacing: .5px; color: #fff4ca; text-shadow: 0 3px 0 #23281f; }
.subpage-body { flex: 1; overflow-y: auto; display: flex; flex-direction: column; align-items: center; gap: 12px; }
/* Wrapper for the MODES-rendered cards. BOTH surfaces are a row of portrait picker cards now — the
   party ones (team page + host sheet) are the same card at `pc-mini` size, so picking a game with
   friends looks like picking one in the lobby. */
.mode-list { width: 100%; display: flex; flex-direction: column; align-items: center; gap: 10px; }
/* The lobby launch surface ("מה משחקים?") is a GRID OF SQUARES, not a one-row flex squeeze: five
   modes in a flex row left each card a sliver on a phone. auto-fill + aspect-ratio keeps every card
   the same square whatever the count; extra rows overflow into .subpage-body's scroll. */
.mode-list[data-modes="launch"] {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  align-items: stretch; gap: 12px;
  flex: none; padding: 2px 0 6px;
}
.mode-list[data-modes="launch"] .pcard { max-width: none; aspect-ratio: 1 / 1; }
.mode-list[data-modes="party"] {
  flex-direction: row; align-items: stretch; justify-content: center; flex-wrap: wrap;
  gap: 8px; padding: 2px 0 4px;
}
/* The «בחר משחק» overlay gets the same square-grid treatment, one size down. The grid (not the
   team page's wrap-row) is scoped to the overlay because #party's list shares a scroll box with
   the roster and must keep its fixed card height. Capped + scrollable so a growing mode list
   overflows inside the sheet instead of stretching it past a landscape phone. */
#game-select .mode-list[data-modes="party"] {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  justify-content: stretch; gap: 8px; padding: 2px;
  max-height: min(52vh, 360px); overflow-y: auto;
}
#game-select .pcard.pc-mini { max-width: none; min-width: 0; height: auto; aspect-ratio: 1 / 1; }

/* ---- picker: square pixel-art mode card ---------------------------------
   Cards live in a square grid (see .mode-list rules above) and overflow by scrolling.
   Anatomy top-to-bottom: coloured title band · pixel scene · rule strip. */
.pcard {
  appearance: none; padding: 0; cursor: pointer; position: relative; overflow: hidden;
  flex: 1 1 0; min-width: 0; max-width: 210px;
  display: flex; flex-direction: column; direction: rtl;
  background: rgba(19,27,22,.94); border: 3px solid #46543f; box-shadow: 0 5px 0 #070b08;
}
.pcard:active { transform: translateY(2px); box-shadow: 0 3px 0 #070b08; }
.pcard .pc-band {
  display: flex; align-items: center; gap: 7px; padding: 7px 9px 8px; text-align: right;
  background: linear-gradient(180deg, var(--band-hi), var(--band-lo));
}
.pcard .pc-ic { font-size: 20px; line-height: 1; flex: none; }
.pcard .pc-tx { min-width: 0; }
.pcard .pc-tx b { display: block; font: 900 13px "Arial Black", sans-serif; color: #0e1410;
  line-height: 1.05; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pcard .pc-tx small { display: block; font: 700 9px sans-serif; color: #12321a; opacity: .78;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pcard .pc-art { flex: 1; min-height: 0; display: block; background: #16221a; }
/* THE pixel-art trick: a tiny canvas (60x46) blown up with no smoothing, so every
   source pixel becomes a fat Minecraft-style block. */
.pcard .pc-cv {
  display: block; width: 100%; height: 100%;
  image-rendering: pixelated; image-rendering: crisp-edges;
}
.pcard .pc-meta { display: flex; justify-content: space-between; gap: 6px; padding: 5px 9px;
  background: #0d1310; font: 700 9px sans-serif; color: #93a595; }
.pcard .pc-soon { padding: 5px 9px; background: #0d1310; border-top: 2px solid #3b4738;
  font: 900 10px "Arial Black", sans-serif; color: #e7dcae; text-align: center; }
.pcard.lock .pc-band, .pcard.lock .pc-art { filter: grayscale(1) brightness(.62); }
.pcard.lock { opacity: .8; }

/* ---- MINI picker card (party surfaces) -------------------------------------
   Same anatomy, one size down, so it sits inside the team page's panel next to the roster instead of
   needing its own screen. Four still fit in one row on a landscape phone. */
/* Explicit HEIGHT, not min-height: this panel shares a scroll box with the party roster, so the card
   must be a known size instead of whatever its content adds up to (that came out 148px tall and made
   the team page scroll on a short phone). vh-scaled so a taller screen gets a slightly bigger card. */
.pcard.pc-mini { flex: 1 1 104px; min-width: 92px; max-width: 126px; height: clamp(92px, 21vh, 120px); border-width: 2px; box-shadow: 0 4px 0 #070b08; }
.pcard.pc-mini:active { box-shadow: 0 2px 0 #070b08; }
.pcard.pc-mini .pc-band { padding: 4px 6px 5px; gap: 5px; }
.pcard.pc-mini .pc-ic { font-size: 15px; }
.pcard.pc-mini .pc-tx b { font-size: 10px; }
.pcard.pc-mini .pc-tx small { font-size: 8px; }
.pcard.pc-mini .pc-art { min-height: 42px; }
.pcard.pc-mini .pc-meta { padding: 3px 6px; font-size: 8px; }
.pcard.pc-mini .pc-soon { padding: 3px 6px; font-size: 9px; }

@media (max-height: 420px) {
  .pcard .pc-tx b { font-size: 11px; }
  .pcard .pc-band { padding: 5px 7px 6px; }
  .pcard.pc-mini .pc-art { min-height: 30px; }
}
.modecard {
  appearance: none; display: flex; direction: rtl; align-items: center; gap: 12px; cursor: pointer;
  width: 100%; max-width: 420px; padding: 14px;
  background: rgba(19,27,22,.94); border: 3px solid #46543f; color: #e7dcae; box-shadow: 0 5px 0 #070b08;
}
.modecard:active { transform: translateY(2px); box-shadow: 0 3px 0 #070b08; }
/* Locked cards are still TAPPABLE — they explain themselves via a toast instead of
   being dead pixels, so the cursor must not claim otherwise. */
.modecard.lock { opacity: .55; cursor: pointer; }
.modecard.lock:active { transform: none; box-shadow: 0 5px 0 #070b08; }
.modecard .mc-ic { font-size: 30px; }
.modecard .mc-tx { flex: 1; text-align: right; }
.modecard .mc-tx b { display: block; font: 900 16px "Arial Black", sans-serif; color: #fff4ca; }
.modecard .mc-tx small { font-size: 10px; color: #8ea08a; font-weight: 800; }
.modecard .mc-go { font: 900 20px "Arial Black", sans-serif; color: var(--gold); }
.modecard .mc-go.soon { font-size: 11px; color: #6b7a66; }
.subpage-note { text-align: center; color: #8ea08a; font-size: 13px; margin-top: 24px; }
.subpage-note b { display: block; font: 900 20px "Arial Black", sans-serif; color: #fff4ca; margin-bottom: 6px; }

/* =============================================================================
   SOFT STADIUM — opt-in direction, enabled only by ?style=soft.

   This layer deliberately changes surfaces rather than behaviour: the existing fixed hub
   geometry, Hebrew content, DOM IDs, card rendering, and client-side interaction bindings stay
   exactly intact. A single compressed stadium backdrop does the expensive depth work once; all
   live UI remains solid/translucent CSS with no backdrop-filter, so it is cheap in WKWebView.
   Claude owns the animated renderer that will eventually replace the pixel hero inside this shell.
   ============================================================================= */
html.style-soft {
  --soft-ink: #f7fbf5;
  --soft-muted: #c6d8ce;
  --soft-panel: rgba(9, 30, 31, .82);
  --soft-panel-strong: rgba(7, 24, 27, .93);
  --soft-line: rgba(238, 255, 242, .23);
  --soft-gold: #ffd35b;
  --soft-gold-deep: #dc9c22;
  --soft-mint: #7ee5bd;
  --soft-shadow: rgba(2, 11, 15, .46);
}
html.style-soft body { background: #08282b; }
html.style-soft #home {
  isolation: isolate;
  background:
    linear-gradient(180deg, rgba(4, 20, 30, .16) 0%, rgba(4, 20, 30, .08) 40%, rgba(3, 17, 20, .54) 100%),
    url('/assets/soft/soft-stadium-hub-v1.webp') center / cover no-repeat,
    #0a3938;
}
/* the NEW hub covers #home with its own daily backdrop (#h2-bg, public/hub-backdrop.js, Adam 2026-10-16): the old stadium
   under it was a 191 KB first-launch download nobody saw — #home takes the day's scene too (--h2-scene) */
html.hub-v2.style-soft #home { background: var(--h2-scene, none) center / cover no-repeat, #0a3938; }
html.style-soft #home::before {
  content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(ellipse 44% 58% at 67% 53%, rgba(255, 235, 166, .13), transparent 72%),
    linear-gradient(90deg, rgba(3, 19, 25, .34), transparent 32%, transparent 72%, rgba(3, 19, 25, .2));
}
html.style-soft #home .start-shade { display: none; }
html.style-soft .hub-pitch { opacity: .12; mix-blend-mode: soft-light; }

/* Shared materials: rounded, soft, readable and compositing-friendly. */
html.style-soft .hub-icobtn,html.style-soft .hub-online,html.style-soft .hub-sat,html.style-soft .hub-mode,html.style-soft .hub-arena,html.style-soft .hub-cs {
  border-color: var(--soft-line);
  border-radius: 14px;
  box-shadow: 0 8px 18px var(--soft-shadow), inset 0 1px 0 rgba(255,255,255,.12);
}
html.style-soft .hub-icobtn,html.style-soft .hub-online,html.style-soft .hub-sat,html.style-soft .hub-mode:not(.quick),html.style-soft .hub-arena,html.style-soft .hub-cs {
  background: linear-gradient(145deg, rgba(22, 61, 62, .94), var(--soft-panel));
  color: var(--soft-ink);
}
html.style-soft .hub-icobtn:active,
html.style-soft .hub-online:active,
html.style-soft .hub-sat:active,
html.style-soft .hub-mode:active,
html.style-soft .hub-arena:active {
  box-shadow: 0 3px 9px rgba(2,11,15,.36), inset 0 1px 0 rgba(255,255,255,.10);
}
html.style-soft .hub-season,html.style-soft .hub-tier {
  border-radius: 12px;
  clip-path: none;
  filter: none;
  box-shadow: 0 7px 15px rgba(81, 45, 5, .27), inset 0 1px 0 rgba(255,255,255,.5);
}
html.style-soft .hub-season {
  color: #3c2706;
  background: linear-gradient(145deg, #fff0a5, var(--soft-gold));
  border-color: rgba(255,255,255,.6);
}
html.style-soft .hub-tier {
  background: linear-gradient(145deg, var(--c1), var(--c2));
}
html.style-soft .hub-tier .px-ic { filter: none; }
html.style-soft .hub-name { color: var(--soft-ink); }
html.style-soft .hub-xp-top,html.style-soft .hub-xp-amt { color: var(--soft-muted); }
html.style-soft .hub-xpbar,
html.style-soft .hub-xp-bar { border-radius: 999px; overflow: hidden; }
html.style-soft .hub-xp-bar { background: rgba(0,0,0,.26); }
html.style-soft .hub-xp-bar b { background: linear-gradient(90deg, #69ddb6, #71a9ff); box-shadow: none; }

/* The hero stays a clearly separated focal object while the future smooth sprite path lands. */
html.style-soft .hub-hero {
  border-radius: 50%;
  background: radial-gradient(circle at 50% 32%, rgba(254,248,210,.54), rgba(136,229,182,.17) 42%, rgba(5,39,39,0) 70%);
}
html.style-soft .hub-hero::before {
  border-radius: 50%;
  background: radial-gradient(ellipse at 50% 64%, rgba(2,16,17,.48), transparent 66%);
  box-shadow: none;
}

/* One dominant action. The fixed stage positioning and the CTA's existing accessible name remain. */
html.style-soft .hub-mode.quick {
  color: #392600;
  border-color: rgba(255,255,255,.6);
  border-radius: 18px;
  background: linear-gradient(145deg, #fff1a3 0%, #ffd558 48%, #f0ad2e 100%);
  box-shadow: 0 10px 20px rgba(68,42,0,.33), inset 0 1px 0 rgba(255,255,255,.7);
}
html.style-soft .hub-mode.quick .hub-mtx small { color: rgba(74,45,0,.76); }
html.style-soft .hub-mode.quick:active { box-shadow: 0 4px 10px rgba(68,42,0,.25), inset 0 1px 0 rgba(255,255,255,.5); }
html.style-soft .hub-sat-dot { box-shadow: 0 0 0 3px rgba(5,32,32,.8), 0 0 9px #55e4aa; }

/* The existing menu/social surfaces get the same clean world without touching the match screen. */
html.style-soft #arena,html.style-soft #season,html.style-soft #news,html.style-soft #shop,html.style-soft #clubs,html.style-soft #rank,html.style-soft #online,html.style-soft #friends,html.style-soft #thread,html.style-soft #profile,html.style-soft #lobby,html.style-soft #party,html.style-soft #friend-select {
  background:
    linear-gradient(135deg, rgba(5, 29, 33, .96), rgba(5, 22, 28, .88)),
    url('/assets/soft/soft-stadium-hub-v1.webp') center / cover no-repeat,
    #08282b;
}
html.style-soft .subpage-head h2 { color: var(--soft-ink); text-shadow: 0 2px 8px rgba(0,0,0,.32); }
html.style-soft .subpage-back,
html.style-soft .pcard,
html.style-soft .modecard,
html.style-soft .news-feature,
html.style-soft .news-item {
  border-color: var(--soft-line);
  border-radius: 16px;
  box-shadow: 0 8px 20px var(--soft-shadow), inset 0 1px 0 rgba(255,255,255,.1);
}
html.style-soft .pcard,
html.style-soft .modecard,
html.style-soft .news-item { background: linear-gradient(145deg, rgba(22,61,62,.96), var(--soft-panel-strong)); }
html.style-soft .news-feature { background: linear-gradient(135deg, rgba(52,110,98,.96), rgba(9,40,45,.96)); }
html.style-soft .pcard .pc-meta,
html.style-soft .pcard .pc-soon { background: rgba(4, 25, 28, .86); }

/* Wardrobe is a character studio, not a pixel/noise overlay. Its existing live canvas, hero selector,
   locks and persistence are retained; this only gives the rails a quiet material surface and lets the
   selected character stay visually first. */
html.style-soft .picker {
  background:
    linear-gradient(180deg, rgba(4, 24, 28, .74), rgba(3, 14, 20, .86)),
    url('/assets/soft/soft-stadium-hub-v1.webp') center / cover no-repeat,
    #08282b;
  backdrop-filter: none;
}
html.style-soft .picker .wr-fx { opacity: .24; }
html.style-soft .picker-panel.wardrobe {
  max-width: 620px;
  padding-inline: 22px;
}
html.style-soft .wardrobe .wr-section {
  padding: 7px 10px 5px;
  border: 1px solid var(--soft-line);
  border-radius: 16px;
  background: linear-gradient(145deg, rgba(31, 79, 76, .66), rgba(7, 27, 31, .74));
  box-shadow: 0 8px 20px rgba(2, 12, 15, .28), inset 0 1px 0 rgba(255,255,255,.1);
}
html.style-soft .wardrobe .wr-rail-label {
  top: 5px; right: 11px; color: var(--soft-muted); opacity: 1;
}
html.style-soft .wardrobe .wr-carousel { padding-top: 14px; }
html.style-soft .wardrobe .pick-tier,
html.style-soft .wardrobe .pick-hero {
  border-color: transparent;
  border-radius: 12px;
  background: rgba(5, 28, 31, .62);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.08);
}
html.style-soft .wardrobe .pick-tier.on,
html.style-soft .wardrobe .pick-hero.on {
  border-color: rgba(255, 224, 132, .85);
  background: linear-gradient(145deg, rgba(255, 213, 88, .38), rgba(11, 50, 47, .86));
  box-shadow: 0 5px 14px rgba(76, 48, 0, .22), inset 0 1px 0 rgba(255,255,255,.24);
}
html.style-soft .wardrobe .pick-tier .pick-lbl,
html.style-soft .wardrobe .pick-hero span { color: var(--soft-ink); }
html.style-soft .wardrobe .pick-name {
  padding: 4px 12px;
  color: #fff1ae;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 2px 8px rgba(0,0,0,.46);
}
html.style-soft .wardrobe .pick-hero.locked { opacity: .52; }

/* Match chrome is intentionally CSS-only: it makes the live game feel like the same product without
   touching the Canvas2D frame path Claude is profiling. No blur, filters, or canvas redraws are added. */
html.style-soft .timer,
html.style-soft .score {
  border: 1px solid rgba(235, 252, 242, .31);
  border-radius: 14px;
  background: linear-gradient(145deg, rgba(24, 62, 67, .92), rgba(5, 25, 31, .91));
  box-shadow: 0 7px 17px rgba(1, 10, 15, .34), inset 0 1px 0 rgba(255,255,255,.16);
}
html.style-soft .timer { color: #e9f9f0; filter: none; }
html.style-soft .score { box-shadow: 0 7px 17px rgba(1, 10, 15, .34), inset 0 1px 0 rgba(255,255,255,.16); }
html.style-soft .team-a { color: #81d7ff; }
html.style-soft .team-b { color: #ff9b8d; }
html.style-soft .dash,
html.style-soft .score-fmt { color: var(--soft-muted); text-shadow: 0 1px 5px rgba(0,0,0,.5); }
html.style-soft .net,
html.style-soft .train-tag,
html.style-soft .train-diff,
html.style-soft .reset-ball-btn,
html.style-soft .stealth-cue,
html.style-soft .power-cue {
  border: 1px solid var(--soft-line);
  border-radius: 999px;
  background: rgba(4, 27, 31, .86);
  box-shadow: 0 5px 14px rgba(1, 10, 14, .28), inset 0 1px 0 rgba(255,255,255,.1);
}
html.style-soft .train-tag,
html.style-soft .train-diff { color: #fff1ae; }
html.style-soft .reset-ball-btn { color: var(--soft-ink); }

/* Matte circular thumb controls are easier to parse over fast play than the old noisy squares. */
html.style-soft .stick {
  border-color: rgba(234, 251, 241, .3);
  border-radius: 50%;
  background: rgba(4, 30, 34, .38);
  box-shadow: inset 0 0 0 7px rgba(255,255,255,.045), 0 8px 18px rgba(1,10,14,.2);
}
html.style-soft .stick .knob {
  border-color: rgba(244, 255, 249, .7);
  border-radius: 50%;
  background: rgba(122, 223, 187, .46);
  box-shadow: 0 5px 12px rgba(1,10,14,.25), inset 0 1px 0 rgba(255,255,255,.3);
}
html.style-soft .special-btn,
html.style-soft .build-btn {
  border: 2px solid rgba(255, 247, 202, .78);
  border-radius: 50%;
  background: linear-gradient(145deg, #ffe18a, #d99027);
  box-shadow: 0 7px 16px rgba(50,31,0,.36), inset 0 2px 0 rgba(255,255,255,.55), inset 0 -6px 0 rgba(129,70,8,.42);
}
html.style-soft .build-btn { background: linear-gradient(145deg, #a5ead0, #3f9b85); }
html.style-soft .special-btn::after,
html.style-soft .build-btn::after { border-radius: 50%; }
html.style-soft .special-btn.ready { animation: softReady 1.8s ease-in-out infinite; }
@keyframes softReady {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.045); opacity: .94; }
}
html.style-soft .pause-btn,
html.style-soft .edit-controls-btn,
html.style-soft .leave-lobby-btn {
  border: 1px solid var(--soft-line);
  border-radius: 12px;
  background: rgba(5, 28, 32, .9);
  box-shadow: 0 5px 13px rgba(1, 10, 14, .3), inset 0 1px 0 rgba(255,255,255,.12);
  color: var(--soft-ink);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
html.style-soft .leave-lobby-btn { color: #fff1ae; }
html.style-soft .chat-sheet {
  border-color: var(--soft-line);
  border-radius: 16px;
  background: rgba(4, 26, 31, .96);
  box-shadow: 0 12px 28px rgba(1, 10, 14, .42);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
html.style-soft .chat-words button,
html.style-soft .chat-emotes button {
  border: 1px solid rgba(235, 252, 242, .18);
  border-radius: 10px;
  background: rgba(29, 79, 76, .88);
  color: var(--soft-ink);
}

@media (prefers-reduced-motion: reduce) {
  html.style-soft *, html.style-soft *::before, html.style-soft *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
}

/* ============================================================================
   LOBBY SUB-SCREEN BASIS (news / shop / clubs / rank) — scaffolds modelled on
   Brawl Stars + Fortnite + Roblox. Shared "coming soon" pill; per-screen blocks
   below. Everything lives inside .subpage-body (already scroll + centred).
   ============================================================================ */
.soon-pill { flex: none; font: 900 9px "Arial Black", sans-serif; color: #2a1c05; background: #8ea08a;
  padding: 2px 7px; border-radius: 5px; letter-spacing: .5px; align-self: center; }
.subpage-body .lock { opacity: .62; cursor: not-allowed; }
/* section wrapper reused by shop + rank */
.shop-section, .rank-section, .club-feats { width: 100%; max-width: 460px; }
.shop-sec-head, .rank-section { display: flex; align-items: baseline; justify-content: space-between; margin: 2px 2px 8px; }
.shop-sec-head b, .rank-section b { font: 900 15px "Arial Black", sans-serif; color: #fff4ca; }

/* --- NEWS: featured banner (MOTD) + feed of update cards --- */
.news-feature { width: 100%; max-width: 460px; padding: 16px; text-align: right; color: #e7dcae;
  background: linear-gradient(135deg, #1c3524, #102016); border: 3px solid #4c6b4f; box-shadow: 0 5px 0 #070b08; position: relative; }
.news-feature .news-tag { position: absolute; top: 10px; left: 12px; font: 900 9px "Arial Black", sans-serif;
  color: #2a1c05; background: var(--gold, #ffce4a); padding: 2px 7px; border-radius: 5px; }
.news-feature b { display: block; font: 900 17px "Arial Black", sans-serif; color: #fff4ca; margin-bottom: 4px; }
.news-feature small { font-size: 11px; color: #b8caa8; font-weight: 800; }
.feed { width: 100%; max-width: 460px; display: flex; flex-direction: column; gap: 10px; }
.news-item { display: flex; direction: rtl; align-items: center; gap: 12px; padding: 12px;
  background: rgba(19,27,22,.94); border: 3px solid #46543f; box-shadow: 0 4px 0 #070b08; }
.news-item .news-ic { font-size: 26px; flex: none; }
.news-item .news-tx { flex: 1; text-align: right; min-width: 0; }
.news-item .news-tx b { display: block; font: 900 14px "Arial Black", sans-serif; color: #fff4ca; }
.news-item .news-tx small { font-size: 10px; color: #8ea08a; font-weight: 800; }

/* --- SHOP: wallet header + daily-deal row (timer) + featured + category tiles --- */
.shop-wallet { margin-right: auto; display: flex; gap: 8px; }
.wallet-chip { font: 900 12px "Arial Black", sans-serif; color: #fff4ca; background: rgba(11,16,12,.72);
  border: 2px solid #34413a; border-radius: 8px; padding: 4px 8px; box-shadow: 0 3px 0 #070b08; }
.wallet-chip.gem { color: #9fe0ff; }
.shop-timer { font: 900 10px "Arial Black", sans-serif; color: #ffce4a; }
.shop-row { display: flex; gap: 10px; }
.shop-item { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 12px 8px;
  background: rgba(19,27,22,.94); border: 3px solid #46543f; box-shadow: 0 4px 0 #070b08; text-align: center; }
.shop-item b { font: 900 11px "Arial Black", sans-serif; color: #fff4ca; }
.shop-thumb { font-size: 30px; }
.shop-thumb.big { font-size: 40px; }
.shop-feature { display: flex; direction: rtl; align-items: center; gap: 12px; width: 100%; padding: 14px;
  background: linear-gradient(135deg, #2a2140, #171528); border: 3px solid #6b5aa0; box-shadow: 0 5px 0 #070b08; }
.shop-feat-tx { flex: 1; text-align: right; }
.shop-feat-tx b { display: block; font: 900 15px "Arial Black", sans-serif; color: #fff4ca; }
.shop-feat-tx small { font-size: 10px; color: #c9b8ff; font-weight: 800; }
.shop-cats { display: flex; gap: 10px; }
.shop-cat { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 5px; padding: 12px 6px;
  background: rgba(19,27,22,.94); border: 3px solid #46543f; box-shadow: 0 4px 0 #070b08; font-size: 24px; }
.shop-cat b { font: 900 11px "Arial Black", sans-serif; color: #fff4ca; }

/* --- CLUBS: «join a club» hero + create/find CTAs + feature preview list --- */
.club-hero { width: 100%; max-width: 460px; text-align: center; padding: 18px 14px; color: #e7dcae;
  background: linear-gradient(135deg, #23331d, #121c12); border: 3px solid #5a7a3f; box-shadow: 0 5px 0 #070b08; }
.club-hero-ic { font-size: 44px; }
.club-hero b { display: block; font: 900 18px "Arial Black", sans-serif; color: #fff4ca; margin-top: 6px; }
.club-hero small { display: block; font-size: 11px; color: #b8caa8; font-weight: 800; margin-top: 4px; }
.club-cta { appearance: none; width: 100%; max-width: 460px; display: flex; direction: rtl; align-items: center; gap: 12px;
  padding: 14px; background: rgba(19,27,22,.94); border: 3px solid #46543f; box-shadow: 0 5px 0 #070b08; }
.club-cta .club-cta-ic { font-size: 24px; }
.club-cta b { flex: 1; text-align: right; font: 900 15px "Arial Black", sans-serif; color: #fff4ca; }
.club-feats { display: flex; flex-direction: column; gap: 10px; }
.club-feat { display: flex; direction: rtl; align-items: center; gap: 12px; padding: 12px;
  background: rgba(15,22,17,.82); border: 2px solid #39463b; box-shadow: 0 3px 0 #070b08; }
.club-feat .club-feat-ic { font-size: 22px; flex: none; }
.club-feat-tx { flex: 1; text-align: right; }
.club-feat-tx b { display: block; font: 900 13px "Arial Black", sans-serif; color: #e7dcae; }
.club-feat-tx small { font-size: 10px; color: #8ea08a; font-weight: 800; }

/* --- RANK: live global position banner + tier ladder (Bronze→Master) --- */
.rank-me { width: 100%; max-width: 460px; display: flex; direction: rtl; align-items: center; gap: 14px; padding: 16px;
  background: linear-gradient(135deg, #3a2f12, #1a1608); border: 3px solid #a07d2a; box-shadow: 0 5px 0 #070b08; }
.rank-me-ic { font-size: 38px; flex: none; }
.rank-me-tx { flex: 1; text-align: right; }
.rank-me-tx b { display: block; font: 900 22px "Arial Black", sans-serif; color: #ffce4a; line-height: 1.05; }
.rank-me-tx small { display: block; font-size: 11px; color: #d8c98a; font-weight: 800; margin-top: 4px; }
.rank-sec-hint { font: 900 10px "Arial Black", sans-serif; color: #8ea08a; }
.rank-ladder { width: 100%; max-width: 460px; display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.rank-tier { flex: 0 0 auto; width: 60px; display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 10px 4px;
  background: rgba(19,27,22,.94); border: 2px solid #46543f; box-shadow: 0 3px 0 #070b08; font-size: 22px; opacity: .82; }
.rank-tier b { font: 900 10px "Arial Black", sans-serif; color: #e7dcae; }
.rank-tier.t-bronze  { border-color: #a6702f; }
.rank-tier.t-silver  { border-color: #b9c2cc; }
.rank-tier.t-gold    { border-color: #e6b422; }
.rank-tier.t-diamond { border-color: #4ec0e0; }
.rank-tier.t-mythic  { border-color: #b46bff; }
.rank-tier.t-legend  { border-color: #ff5b7f; }
.rank-tier.t-master  { border-color: #ffce4a; }
/* player's current tier: brought forward + lifted */
.rank-tier.on { opacity: 1; transform: translateY(-3px); background: rgba(40,54,40,.98); box-shadow: 0 6px 0 #070b08; }
.rank-tier.on b { color: #fff4ca; }
.rank-subnote { width: 100%; max-width: 460px; text-align: center; font: 900 10px "Arial Black", sans-serif;
  color: #8ea08a; line-height: 1.5; }
.rank-note { width: 100%; max-width: 460px; display: flex; align-items: center; justify-content: space-between; padding: 12px 14px;
  background: rgba(15,22,17,.82); border: 2px solid #39463b; box-shadow: 0 3px 0 #070b08; }
.rank-note b { font: 900 13px "Arial Black", sans-serif; color: #e7dcae; }

/* ================= Match-start promo cinematic ================= */
.promo {
  position: fixed; inset: 0; z-index: 75; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 14px;
  background: radial-gradient(circle at 50% 40%, rgba(10,18,28,.7), rgba(4,8,14,.93));
  opacity: 0; transition: opacity .3s; pointer-events: none; overflow: hidden;
}
.promo.show { opacity: 1; }
.promo-team { display: flex; flex-direction: column; align-items: center; gap: 12px; }
.promo-team-label { color: #ffe49a; font: 900 15px "Arial Black", sans-serif; letter-spacing: 3px; text-shadow: 0 3px 0 #0b0f0b; }
.promo-heroes { display: flex; gap: 44px; align-items: flex-start; max-width: 96vw; }
.promo-hero { display: flex; flex-direction: column; align-items: center; gap: 5px; animation: promoHeroIn .5s cubic-bezier(.2,.8,.3,1.3) both; }
.promo-hero:nth-child(2) { animation-delay: .12s; }
.promo-hero:nth-child(3) { animation-delay: .24s; } /* 3v3 — keep the reveal staggered, not simultaneous */
/* 3v3: three hero columns (2 x 132px card zones + gaps ≈ 484px) would push past a narrow
   landscape viewport, so tighten the gap and the card zones instead of letting it overflow. */
.promo[data-size="3"] .promo-heroes { gap: 22px; }
.promo[data-size="3"] .promo-hero-cv { width: 78px; height: 92px; }
.promo[data-size="3"] .promo-hero-name { font-size: 11px; padding: 2px 7px; }
@keyframes promoHeroIn { from { opacity: 0; transform: translateY(34px) scale(.8); } to { opacity: 1; transform: none; } }
.promo-hero-cv { width: 92px; height: 108px; image-rendering: pixelated; filter: drop-shadow(0 8px 8px rgba(0,0,0,.55)); }
.promo-hero-name { color: #fff4ca; font: 900 12px "Arial Black", sans-serif; letter-spacing: .3px; background: rgba(11,16,12,.7); border: 2px solid #39463b; padding: 3px 9px; }
/* Legendary fire — our pixel-fire sprite sheet (fire-sheet.png: 32 frames, 94x88 each),
   a wall of flickering flames around the card. Each tile plays the strip via steps(32). */
.promo-flames { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.fire-tile {
  position: absolute; bottom: 0;
  background-image: url('/assets/fire-sheet.png'); background-repeat: no-repeat; background-size: var(--sw, 3200%) 100%;
  image-rendering: pixelated; animation: fireSheet .8s steps(32) infinite;
}
@keyframes fireSheet { to { background-position-x: calc(-1 * var(--sw, 3200%)); } }
/* Very short landscape (e.g. iPhone SE ~320h): drop slot labels + the hero hint so the
   loadout column stays inside the main row without clipping. */
@media (max-height: 400px) {
  .hub-herohint { display: none; }
}




/* ============================================================================
   Parallel-edit batch — #13 compact room controls · #14 room requests/kick/waiting ·
   #15 (✕ removed in markup; outside-click in client.js) · #16 no scrollbars + fit ·
   #17 leave-to-lobby button · #3 friends states
   ============================================================================ */

/* #16 — hide scrollbars everywhere; content is laid out to fit, bars never show. */
*::-webkit-scrollbar { width: 0; height: 0; display: none; }
* { scrollbar-width: none; -ms-overflow-style: none; }

/* #13 — "play with friends": compact create/join controls (were oversized). */
.play.compact-room { padding: 11px 16px; font-size: 15px; }
.play.compact-room b { font-size: 22px; right: 14px; top: 2px; }
#friends .code-input { font: 900 18px "Arial Black", sans-serif; letter-spacing: 5px; padding: 8px 10px; }
#friends .play.join-btn { padding: 9px 16px; font-size: 14px; }

/* #17 — in-match / training "back to lobby" button (left edge, below the native close). */
.leave-lobby-btn {
  /* Second slot of the top-right cluster: 12 + 40 (the gear) + 8 (gap) = 60. Raised onto the gear's
     row from its old left/58px perch so the two things a player reaches for mid-match — leave, and
     settings — are one thumb-target apart instead of on opposite sides of the screen. */
  position: fixed; right: max(60px, calc(env(safe-area-inset-right) + 56px)); top: 12px;
  height: 40px; padding: 0 12px;
  display: flex; align-items: center; justify-content: center;
  background: #182019e8; border: 2px solid #667061; box-shadow: 0 4px 0 rgba(0,0,0,.4);
  color: #ffd76a; font: 900 12px "Arial Black", sans-serif; letter-spacing: 1px;
  border-radius: 2px; cursor: pointer; z-index: 7; touch-action: manipulation;
}
.leave-lobby-btn:active { transform: scale(.94); }

/* #14 — HOST pending join requests (accept/reject) above the team panels. */
.room-requests { width: 100%; display: flex; flex-direction: column; gap: 6px; }
.room-requests.hidden { display: none !important; }
.room-req-h { color: #ffd76a; font: 900 11px "Arial Black", sans-serif; letter-spacing: 1px; }
.room-req {
  display: flex; align-items: center; gap: 8px; padding: 6px 8px;
  background: rgba(12,18,13,.92); border: 2px solid #39463b; border-left: 4px solid var(--gold);
}
.room-req-av {
  width: 30px; height: 30px; flex: none; background: #26332a center/cover no-repeat;
  border: 2px solid #3c4a3e; color: #e7dcae; font: 900 11px "Arial Black", sans-serif;
  display: flex; align-items: center; justify-content: center;
}
.room-req-name { flex: 1; min-width: 0; color: #eef2e6; font-weight: 800; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.room-req-ok, .room-req-no { border: 0; border-radius: 6px; padding: 7px 12px; font: 900 12px "Arial Black", sans-serif; cursor: pointer; }
.room-req-ok { background: #35d06a; color: #04240f; }
.room-req-no { background: #3a2626; color: #ff9a8f; border: 1px solid #6b2033; }
.room-req-ok:active, .room-req-no:active { transform: translateY(2px); }

/* #14 — host kick control on a joined member row. */
.member-kick {
  flex: none; width: 24px; height: 24px; margin-inline-start: auto; border-radius: 6px; cursor: pointer;
  background: #3a2626; border: 1px solid #6b2033; color: #ff9a8f;
  font: 900 12px "Arial Black", sans-serif; line-height: 1; display: flex; align-items: center; justify-content: center;
}
.member-kick:active { transform: scale(.9); }
.member-kick.hidden { display: none !important; }

/* #14 — joiner "waiting for host approval" overlay. */
.room-wait {
  position: fixed; inset: 0; z-index: 85; display: flex; align-items: center; justify-content: center;
  background: rgba(4,8,10,.78); backdrop-filter: blur(4px); padding: 16px;
}
.room-wait.hidden { display: none !important; }
.room-wait-card {
  width: min(90vw, 340px); text-align: center; padding: 22px 20px;
  background: rgba(19,27,22,.98); border: 3px solid #4a5a4c; border-radius: 2px;
  box-shadow: inset 0 0 0 3px #182018, 0 14px 40px rgba(0,0,0,.6);
  display: flex; flex-direction: column; align-items: center; gap: 12px;
}
.room-wait-spin {
  width: 42px; height: 42px; border-radius: 50%;
  border: 4px solid rgba(255,203,67,.25); border-top-color: var(--gold);
  animation: roomSpin .9s linear infinite;
}
@keyframes roomSpin { to { transform: rotate(360deg); } }
.room-wait-title { color: #fff4ca; font: 900 16px "Arial Black", sans-serif; letter-spacing: .5px; }
.room-wait-sub { color: #c8d4bf; font-size: 12px; font-weight: 800; }
.room-wait-sub b { color: var(--gold); letter-spacing: 3px; }
.room-wait .play.ghost.small { width: auto; padding: 9px 20px; }

/* #16 — settings panel: responsive grid so every control fits (no scroll). */
.settings-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 2px 16px; }
.settings-grid .setting { margin: 6px 0; }
.settings-grid .setting-wide { grid-column: 1 / -1; }
.settings-grid .setting input[type=range] { height: 24px; }

/* #3 — friends panel loading / empty / error placeholder (+ tap-to-retry). */
.friend-empty { color: #8aa08c; font-size: 12px; text-align: center; padding: 10px 6px; }
.friend-retry { cursor: pointer; color: #ffd76a; text-decoration: underline; }

/* ============================================================================
   WARDROBE (hero-picker) — costumes carousel (top) · big hero (center) · heroes (bottom)
   ============================================================================ */
/* Landscape-safe: the panel is a fixed-height flex column — costumes (top) and heroes
   (bottom) take their natural height, the hero preview flex-grows to fill the middle
   (object-fit keeps its aspect), and the Save button is always in view (no scrolling). */
.picker-panel.wardrobe {
  max-width: 560px; width: 100%; height: 94vh; max-height: 94vh; gap: 3px; padding: 9px 14px;
  display: flex; flex-direction: column; overflow: visible;
  background: none; border: 0; box-shadow: none; /* drop the panel frame — only the blurred backdrop remains */
}
.wr-section { width: 100%; flex: 0 0 auto; position: relative; }
/* label overlays the section's top-right so it costs no vertical space */
.wr-rail-label { position: absolute; top: -1px; right: 3px; z-index: 3; font: 900 8px "Arial Black", sans-serif; color: #9fb0a2; letter-spacing: 1px; opacity: .75; pointer-events: none; }
.wr-carousel {
  display: flex; flex-wrap: nowrap; gap: 7px; overflow-x: auto; overflow-y: hidden;
  padding: 2px 2px 4px; scroll-snap-type: x proximity; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.wr-carousel::-webkit-scrollbar { display: none; }
.wr-center { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; }
.wardrobe .pick-preview { flex: 1 1 auto; min-height: 0; width: 100%; height: auto; object-fit: contain; }
.wardrobe .pick-name { flex: 0 0 auto; font-size: 13px; min-height: 15px; }
.picker-panel.wardrobe .play { flex: 0 0 auto; padding: 8px; }
/* costume swatches — a hero thumbnail per skin */
.wardrobe .pick-tiers.wr-carousel .pick-tier {
  flex: 0 0 auto; scroll-snap-align: start; flex-direction: column; gap: 1px; border-radius: 10px;
  padding: 3px 6px; min-width: 52px;
}
.wardrobe .pick-tier canvas { width: 36px; height: 44px; image-rendering: pixelated; }
.wardrobe .pick-tier .pick-lbl { display: inline-flex; align-items: center; gap: 3px; font: 800 9px "Arial Black", sans-serif; }
/* hero-type carousel (horizontal row instead of the old grid) */
.wardrobe .pick-heroes.wr-carousel {
  display: flex; grid-template-columns: none; width: 100%;
}
.wardrobe .pick-heroes .pick-hero { flex: 0 0 auto; scroll-snap-align: start; min-width: 62px; padding: 4px 2px 3px; }
.wardrobe .pick-heroes .pick-hero canvas { width: 44px; height: 52px; }
.wardrobe .pick-heroes .pick-hero span { font-size: 9px; }
.picker-panel.wardrobe .play { margin-top: 0; padding: 9px; }
/* req1: the big centre hero paints IN FRONT of both rails and is never clipped */
.wardrobe .wr-center { position: relative; z-index: 4; overflow: visible; }
/* full-screen per-hero ambience — frameless & translucent, behind the whole picker panel */
.picker .wr-fx { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0;
  image-rendering: pixelated; border: 0; border-radius: 0; pointer-events: none; }
.picker-panel.wardrobe { position: relative; z-index: 1; }
/* hero preview transparent so the ambience shows behind it (was a solid green disc) */
.wardrobe .pick-preview { background: none; border: 0; }
.wardrobe .pick-preview { position: relative; z-index: 60; overflow: visible; pointer-events: none;
  background: none; border: 0; border-radius: 0; } /* drop the box + green stage; hero paints above everything (even buttons), never clipped, clicks pass through */
.wardrobe .wr-section { z-index: 1; }
/* req5: heroes the player hasn't unlocked yet render as a dark shadow + lock badge */
.wardrobe .pick-heroes .pick-hero { position: relative; }
.pick-hero.locked { cursor: not-allowed; }
.pick-hero.locked canvas { filter: brightness(0) grayscale(1); opacity: .45; }
.pick-hero.locked span { color: #64726a; }
.pick-hero.locked::after { content: '🔒'; position: absolute; top: 50%; left: 50%; transform: translate(-50%,-60%);
  font-size: 15px; filter: none; opacity: .85; pointer-events: none; }
.fan-track { position: relative; height: 92px; }
.fan-card {
  position: absolute; bottom: 0; width: 66px; height: 92px; border-radius: 8px; overflow: hidden;
  border: 3px solid #2c3830; background: #10192e; box-shadow: 0 4px 10px rgba(0,0,0,.5);
  cursor: pointer; will-change: transform; transition: transform .14s, box-shadow .14s;
}
.fan-card-art { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; image-rendering: pixelated; }
.fan-card { transform-origin: bottom center; }
/* PEEK: the card the finger is currently over while swiping the fan — pops up a bit so you
   can see which card you're on (the resting fan shows only a 26px sliver per card). */
.fan-card.peek { transform: translateY(-4px) scale(1.12); z-index: 900; box-shadow: 0 10px 22px rgba(0,0,0,.6); }
.fan-card.revealed { transform: translateY(-2px) scale(1.16); z-index: 999 !important; box-shadow: 0 12px 26px rgba(0,0,0,.65); touch-action: none; }
.fan-card.equipped { outline: 3px solid var(--gold); outline-offset: -3px; }
.fan-card.rarity-legendary { border-color: #ffd23f; }
.fan-card.rarity-epic { border-color: #b46bff; }
.fan-card.rarity-rare { border-color: #4ea0ff; }
.fan-card.rarity-common { border-color: #ff5a4d; }
.fan-card-tag { position: absolute; top: 3px; right: 3px; z-index: 3; font: 900 9px "Arial Black", sans-serif; color: #fff; background: rgba(0,0,0,.6); padding: 1px 4px; border-radius: 5px; opacity: 0; transition: opacity .12s; }
.fan-card.revealed .fan-card-tag { opacity: 1; }

/* ============================================================================
   BAKED LOBBY LAYOUT (feat/lobby-redesign) — PERMANENT default arrangement.
   EVERY control is its own atomic box: a direct child of the fixed 900x415 .hub
   stage, positioned + sized in logical px here — so the layout holds WITHOUT the
   layout-edit script and WITHOUT any localStorage. In ?edit=1 every box is
   draggable AND resizable (uniform transform:scale), and drags from these baked
   positions. Values are the user's editor layout, POLISHED: related groups share
   baselines / equal sizes, evenly spaced, legible, no content-hiding overlaps.
   left/top/width/height are logical px on the stage.
   ============================================================================ */
/* Anchor every atomic box to the stage (0,0 = top-left of .hub). */
.hub > .hub-icobtn,.hub > .hub-season,.hub > .hub-online,.hub > .hub-pfp,.hub > .hub-name,.hub > .hub-xpbar,.hub > #hub-tier,.hub > .hub-sat,.hub > .hub-hero,.hub > .hub-mode,.hub > #play-strip { position: absolute; right: auto; bottom: auto; margin: 0; }

/* --- top row: exit · [copies·worth·cards] · XP bar · season · settings --- */
/* exit + settings: equal squares on a shared top baseline */
.hub > #hub-settings  { left: 809px; top: 15px; width: 38px; height: 38px; z-index: 6; }
.hub > .hub-season    { left: 743px; top: 13px; z-index: 6; }
.hub > .hub-online    { left: 757px; top: 60px; z-index: 6; }

/* profile pic (bigger) + name aligned on the left; name shadowed (pill is gone) */
.hub > .hub-pfp       { left: 95px; top: 15px; width: 55px; height: 55px; z-index: 6; }
.hub > .hub-name      { left: 95px; top: 80px; z-index: 6; max-width: none;
  text-shadow: 0 2px 3px rgba(0,0,0,.9), 0 0 3px rgba(0,0,0,.85); }

/* XP: clean horizontal top bar */
.hub > .hub-xpbar {
  left: 410px; top: 10px; width: 325px; z-index: 5;
  /* Border dropped with the rest of the #34413a outlines. The `box-shadow: 0 3px 0` already gives this
     panel a solid bottom edge, so it still reads as raised without a pale line tracing all four sides.
     border-box + explicit width keeps it 325px. */
  background: rgba(11,16,12,.72); border-radius: 8px;
  padding: 4px 10px; box-shadow: 0 3px 0 #070b08;
}
.hub-xpbar #hub-xp { width: 100%; }
/* hero rank badge: SAME 50×42 box, centred over the hero (centre x≈615 → left 590). z-index 3 < hero (.hub-hero z4)
   so the HERO paints OVER the badge — the badge peeks out from behind the hero. */
.hub > #hub-tier      { left: 590px; top: 58px; width: 50px; height: 42px; z-index: 3; }

/* edge controls: equal 52px squares; symmetric — news/shop left (x95), friends/clubs right (x795) */
.hub > .hub-sat { width: 52px; height: 52px; }
.hub > .hub-sat[data-open-screen="shop"]  { left: 95px;  top: 110px; z-index: 6; }
.hub > .hub-sat[data-open-screen="news"]  { left: 95px;  top: 175px; z-index: 6; }
.hub > #rank-btn                          { left: 95px;  top: 240px; z-index: 6; }
.hub > #friends-btn                       { left: 795px; top: 110px; z-index: 6; }
.hub > .hub-sat[data-open-screen="clubs"] { left: 795px; top: 175px; z-index: 6; }

/* hero — scaled to the user's ~0.73; canvas width drives the box (250 -> ~262 tall) */
.hub > .hub-hero { left: 490px; top: 75px; transform: none; }
.hub > .hub-hero .hub-char { width: 250px; height: auto; }
  /* slot2 */

/* quick match (primary, FIXED) + pick-best (own compact box) */
.hub > .hub-mode.quick  { left: 720px; top: 290px; }

/* --- BOTTOM SCROLLABLE STRIP (playStrip): one horizontal, horizontally-scrollable row of
   secondary buttons along the bottom-left, up to just before the quick-match box. dir=ltr so
   it scrolls left→right from training; the coming-soon buttons at the end are revealed by
   scrolling. In the editor it is ONE box (movable+resizable); buttons scroll inside it. --- */
.hub > #play-strip {
  left: 12px; top: 343px; width: 685px; height: 52px; z-index: 5;
  display: flex; flex-direction: row; align-items: center; gap: 10px;
  padding: 0; overflow-x: auto; overflow-y: hidden; white-space: nowrap;
  background: none; border: none; border-radius: 0;   /* clean: no box around the buttons */
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;   /* clean swipe: settles on a button, not mid-gap */
}
#play-strip > * { scroll-snap-align: start; }
.hub > #play-strip::-webkit-scrollbar { display: none; }  /* clean swipe, no visible scrollbar */
/* strip items: flow (override any absolute base), uniform height, natural width */
#play-strip > * {
  position: static; left: auto; top: auto; right: auto; bottom: auto;
  flex: 0 0 auto; height: 44px; margin: 0; z-index: auto; transform: none;
}
/* Exactly five entries now (בחר משחק · חברים · אימון · בונה מגרש · טורניר), so size them
   to FIT the 685px strip instead of overflowing into a swipe — nothing hides off-edge. */
#play-strip > * { flex: 1 1 0; min-width: 0; }
#play-strip { overflow-x: hidden; }
#play-strip .hub-mtx b, #play-strip .hub-cs-tx b, #play-strip .hub-arena-tx b {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#play-strip .hub-training { justify-content: center; }

/* --- secondary play buttons (pick-best / play-friends) --- */
.hub-secondary { justify-content: center; }
.hub-secondary .hub-mtx b { white-space: nowrap; }
/* compact variant (pick-best in its small fixed box) */
.hub-compact { padding: 3px 8px; }
.hub-compact .hub-mic { width: 20px; height: 20px; font-size: 12px; }
.hub-compact .hub-mtx b { font-size: 11px; white-space: nowrap; }

/* --- coming-soon locked pills (tournament / goal-brawl) --- */
.hub-cs {
  display: flex; direction: rtl; align-items: center; gap: 7px; cursor: not-allowed;
  padding: 5px 9px; opacity: .62; color: #cdd8c8; overflow: hidden;
  background: rgba(15,22,17,.82); border: 2px solid #39463b; box-shadow: 0 3px 0 #070b08;
}
.hub-cs .hub-mic { width: 22px; height: 22px; flex: none; display: grid; place-items: center; font-size: 14px; background: rgba(0,0,0,.28); }
.hub-cs .hub-cs-tx { flex: 1; min-width: 0; }
.hub-cs .hub-cs-tx b { display: block; font: 900 11px "Arial Black", sans-serif; color: #e7dcae; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hub-cs .hub-cs-soon { flex: none; font: 900 8px "Arial Black", sans-serif; color: #2a1c05; background: #8ea08a; padding: 2px 5px; border-radius: 4px; }

/* ===================== FIELD BUILDER ===================== */
#builder { display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start; background: #0e1a12; color: #eaf2ec; }
#builder.hidden { display: none; }
/* Body: actions rail (left) · field (center) · materials rail (right). dir:ltr fixes L/R order. */
/* Rails pulled in toward the centre with a side gap that matches the main lobby (its side
   buttons sit ~10% in from the edge), and never inside the landscape notch/rounded corners. */
/* Side padding is the SAFE AREA and nothing more (user, 2026-07-26: "it should be wider to fit the
   screen, up to the safe area"). It used to be max(10%, safe-area): on an 844x303 landscape phone
   that is 84px PER SIDE — 168px, a fifth of the screen — spent on empty margin, and the pitch was
   measured at 264x145. 8px is a floor for phones that report no inset at all. */
.builder-body { flex: 1; min-height: 0; display: flex; flex-direction: row; align-items: stretch; gap: 8px; direction: ltr;
  padding: 1px max(8px, env(safe-area-inset-right)) 1px max(8px, env(safe-area-inset-left)); }
/* WRAPPING column: the rail is one column on a tall screen and splits into a SECOND column when the
   buttons don't fit, instead of scrolling them out of sight. That conditional behaviour is exactly
   what `flex-flow: column wrap` + a bounded height gives for free — and it is why the two new tools
   (🏁/⚽) don't cost anyone access to ✋/🩹 on a 333px-tall viewport (measured; it was 40px over). */
.builder-side { flex: none; display: flex; flex-flow: column wrap; align-content: center; justify-content: center; gap: 4px; max-height: 100%; overflow: hidden; }
.builder-side .bact, .builder-side .btool { width: 88px; flex: none; }
/* The materials rail carries 8 tools now (🏁 start slots + ⚽ ball joined the four materials + move +
   eraser). At the old 8px/13px sizing that stack was ~306px and scrolled on a 420px-tall window,
   hiding ✋/🩹 below the fold. Both rails are compacted so every tool stays one tap away at phone
   height — measured with the CDP harness (scrollHeight === clientHeight on both rails at 900x420). */
.builder-side .btool, .builder-side .bact { padding: 6px 8px; font-size: 12px; }
/* Zoom controls, overlaid bottom-left of the (now scrollable) stage. */
/* Now that the pitch fills the stage (97% inset) this cluster necessarily OVERLAYS it, where it
   used to sit in the old 8% slack. Tucked hard into the corner, shrunk 38->30px and made
   translucent until touched, so it costs the least arena possible and the pitch reads through it.
   The bottom-left corner is the emptiest part of a football pitch. */
.builder-zoom { position: absolute; left: 2px; bottom: 2px; z-index: 6; display: flex; flex-direction: column; gap: 3px; opacity: .55; transition: opacity .15s; }
.builder-zoom:hover, .builder-zoom:active, .builder-stage:active .builder-zoom { opacity: 1; }
.builder-zoom button { width: 30px; height: 30px; border: none; border-radius: 9px; background: rgba(20,32,24,.92); color: #eaf2ec; font: 900 18px "Arial Black", sans-serif; box-shadow: 0 3px 0 #070b08; cursor: pointer; }
.builder-zoom button:active { transform: translateY(2px); box-shadow: 0 1px 0 #070b08; }
/* Extra top/bottom inset keeps back/play (top) and clear/save (bottom) a bit off the screen edges. */
/* Vertical padding trimmed (14/6 -> 6/3). Measured: the pitch is HEIGHT-limited, not width-limited
   — at 844x303 it renders 294x162, i.e. exactly the arena's 1.81 aspect with 275px of horizontal
   slack to spare. So every px returned here becomes ~1.8px of pitch WIDTH as well. The two bars were
   spending 110px of a 303px viewport. Kept as max(…, safe-area-inset-top) so a notched phone in
   landscape still clears the island; only the arbitrary floor shrank. Button sizes are deliberately
   NOT touched — .builder-btn is shared with the back pill on every sub-page. */
.builder-top { display: flex; align-items: center; justify-content: space-between; padding: max(4px, env(safe-area-inset-top)) 12px 2px; gap: 8px; }
/* Bottom bar mirrors the top bar layout: clear-all (left) under back, save (right) under play. */
/* Bottom bar is the tightest thing on screen: its buttons are smaller than the shared .builder-btn
   (user: "you can resize the lowest buttons a bit") and its padding is down to 2px, because every px
   here becomes ~1.8px of pitch WIDTH — the pitch is height-bound by aspect-ratio, not width-bound. */
.builder-bottom { display: flex; align-items: center; justify-content: space-between; padding: 2px 12px max(4px, env(safe-area-inset-bottom)); gap: 8px; }
.builder-bottom .builder-btn { padding: 4px 10px; font-size: 12px; }
/* 16px -> 12px: the heading is orientation, not content, and every px it gives back is arena. */
.builder-title { font-weight: 800; font-size: 12px; }
.builder-btn { border: none; border-radius: 10px; padding: 8px 14px; font-weight: 800; font-size: 14px; color: #fff; flex: none; }
.builder-back { background: #33413a; }
.builder-play { background: #2fbf5e; }
.builder-titlewrap { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.builder-fieldname { font-weight: 700; font-size: 13px; color: #ffd27a; opacity: .9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 40vw; }
.builder-top-right { display: flex; align-items: center; gap: 8px; }
.builder-clone { background: #3a6ea5; } /* opens the field picker */

/* Floating FIELD PICKER — a fixed overlay above the builder, reusing the friends .subpage look.
   NOTE: uses .field-modal (NOT .fp-modal — that prefix belongs to the friend-profile modal). */
.field-modal { position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center; background: rgba(3,6,4,.72); }
.field-modal.hidden { display: none; }
.field-modal .start-shade { position: absolute; inset: 0; }
#field-name-modal { z-index: 56; } /* name input sits above the picker */
.fp-sheet { position: relative; z-index: 1; width: min(560px, 92vw); max-height: 84vh; display: flex; flex-direction: column; }
.fp-name-sheet { width: min(380px, 90vw); }
.fp-name-actions { display: flex; gap: 8px; margin-top: 12px; }
.fp-name-actions .builder-btn { flex: 1; }
.fp-sheet .subpage-head { display: flex; align-items: center; justify-content: space-between; }
.fp-x { border: none; background: #33413a; color: #fff; font-weight: 800; border-radius: 10px; padding: 6px 12px; font-size: 16px; }
.fp-save-btn { background: #2fbf5e; width: 100%; margin-bottom: 10px; }
/* Field rows reuse .friend-row; the load/rename/delete actions sit on the left (row is rtl). */
.field-row-actions { display: flex; gap: 8px; margin-inline-start: auto; }
.field-load-btn { border: none; background: #3a6ea5; color: #fff; font-weight: 800; border-radius: 8px; padding: 8px 14px; }
.field-ren-btn { border: none; background: #4a5a4c; color: #fff; font-weight: 800; border-radius: 8px; padding: 8px 11px; }
.field-del-btn { border: none; background: #7a2b2b; color: #fff; font-weight: 800; border-radius: 8px; padding: 8px 11px; }
.field-row-name { font-weight: 800; }
.field-row-sub { font-size: 12px; opacity: .7; margin-inline-start: 8px; font-weight: 600; }
.field-row-active { border-color: #7ea862; box-shadow: 0 4px 0 #070b08, inset 0 0 0 2px #7ea862; } /* the loaded slot */
/* Stage scrolls (pan) when the field is zoomed past its fit size; block flow + margin:auto on
   the arena centres it when it fits and scrolls cleanly from the origin when it overflows. */
/* padding 6px -> 1px: this sat directly between the arena and the tool rails and cost 12px of the
   height the pitch is limited by (user: "very little slack between the arena and the buttons"). */
.builder-stage { flex: 1; min-width: 0; min-height: 0; position: relative; overflow: auto; padding: 1px; }
/* Uniform scale: aspect-ratio keeps px/world identical on both axes, so CSS rotate() is true. */
.builder-pitch { position: relative; width: 100%; max-width: 980px; aspect-ratio: var(--fb-aspect, 2000 / 1100); max-height: 100%;
  background: radial-gradient(circle at 50% 50%, #2f7d43, #256a38); border: 3px solid rgba(255,255,255,.5); border-radius: 8px;
  overflow: hidden; touch-action: none; }
.bp-goal { position: absolute; top: 50%; width: 1.6%; height: 27%; transform: translateY(-50%); background: rgba(255,255,255,.35); }
.bp-goal-l { left: 0; } .bp-goal-r { right: 0; }
.bp-mid { position: absolute; left: 50%; top: 0; bottom: 0; width: 2px; background: rgba(255,255,255,.3); transform: translateX(-50%); }
.bel { position: absolute; transform: translate(-50%, -50%) rotate(var(--ang, 0rad)); touch-action: none; cursor: grab; box-sizing: border-box; }
.bel.bush { background: rgba(60,180,90,.55); border: 2px dashed rgba(220,255,220,.7); border-radius: 10px; }
.bel.hard { background: linear-gradient(#8b93a1, #6b7280); border: 1px solid #43484f; border-radius: 3px; }
.bel.dry { background: linear-gradient(#b98a4a, #8a5a2c); border: 1px solid #5a3a1c; border-radius: 3px; }
/* Crate (single-cell wooden box): planks + X-brace, matches the in-game render. */
.bel.crate { background: repeating-linear-gradient(90deg, #b07d42 0 8px, #9c6d38 8px 9px), #a9773f; border: 2px solid #5f3d1c; border-radius: 3px;
  box-shadow: inset 2px 2px 0 rgba(255,240,200,.25), inset -2px -2px 0 rgba(0,0,0,.34); overflow: hidden; }
.bel.crate::before, .bel.crate::after { content: ""; position: absolute; left: -8%; top: 44%; width: 116%; height: 13%; background: #7a5327; }
.bel.crate::before { transform: rotate(42deg); }
.bel.crate::after { transform: rotate(-42deg); }
.bel.sel { outline: 3px solid #ffd54a; outline-offset: 2px; z-index: 5; }
/* KICKOFF markers (start slots + the ball). Discs, not slabs — they are not geometry, nothing
   collides with them, and they must read as "a player stands here" over any wall/bush behind them.
   z-index above the elements so a slot placed on a bush stays visible and grabbable. */
.bel.spawn, .bel.ball { width: 22px; height: 22px; border-radius: 50%; z-index: 4;
  display: flex; align-items: center; justify-content: center;
  font: 900 11px "Arial Black", sans-serif; box-shadow: 0 2px 6px rgba(0,0,0,.5); }
.bel.spawn::after { content: attr(data-lbl); color: #06210f; }
.bel.spawn.spawn-a { background: radial-gradient(circle at 40% 34%, #9ecbff, #4ea0ff 70%); border: 3px solid #eaf4ff; }
.bel.spawn.spawn-b { background: radial-gradient(circle at 40% 34%, #ffb0b8, #ff5b6a 70%); border: 3px solid #ffeaec; }
.bel.ball { background: radial-gradient(circle at 38% 32%, #fff, #cfd8e3 72%); border: 3px solid #1b2431; }
.bel.ball::after { content: "⚽"; font-size: 15px; line-height: 1; }
/* Layout capacity badge (min A/B slots). Warns when the two sides are uneven — that is the one
   authoring mistake the sim silently absorbs by seating fewer players than the map looks like. */
.bcap { flex: none; text-align: center; font: 800 11px sans-serif; color: #b7c9bd; background: #22302a; border-radius: 8px; padding: 3px 8px; line-height: 1.3; white-space: nowrap; }
.bcap.cap-warn { color: #ffd7a1; background: #3a2f22; }
.builder-palette { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 10px; justify-content: center; }
.btool, .bact { border: none; border-radius: 9px; padding: 8px 12px; font-weight: 700; font-size: 13px; background: #2a3a30; color: #eaf2ec; }
.btool.active { background: #2fbf5e; color: #06210f; }
.bact { background: #33413a; }
#b-clear { background: #6e2b2b; }
.builder-sep { width: 1px; align-self: stretch; background: rgba(255,255,255,.15); margin: 0 2px; }
/* The long "קיר: גרור לצייר קו…" instruction line is gone from the markup — it was 481x42px of
   permanent explanation in a 303px-tall viewport (user: "remove the explanation text, it takes too
   much screen space"). The ELEMENT stays, because it is not decoration: fbFlash() writes build
   errors into it (red) and the save handler writes «נשמר ✓» (green), and fbFlash() early-returns if
   it is missing — deleting it would have silently killed save confirmation.
   It is now an absolutely-positioned overlay, so an empty hint costs ZERO layout and a flash cannot
   reflow the bar mid-drag. `bottom` clears the bottom bar; pointer-events:none keeps it from eating
   taps aimed at נקה הכל / שמור. */
.builder-hint {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 68px; z-index: 8;
  max-width: 78%; text-align: center; font-size: 11px; font-weight: 800; opacity: .95;
  pointer-events: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.builder-hint:not(:empty) { padding: 4px 10px; border-radius: 8px; background: rgba(8,14,10,.82); }
/* Pitch markings must never intercept taps (placement/selection happen on the pitch). */
.bp-goal, .bp-mid { pointer-events: none; }
/* Wall resize handles (drag an end to lengthen/shorten + rotate). */
.bhandle { position: absolute; width: 20px; height: 20px; margin: -10px 0 0 -10px; border-radius: 50%;
  background: #ffd54a; border: 2px solid #7a5c00; box-shadow: 0 1px 3px rgba(0,0,0,.5); z-index: 6; touch-action: none; }
/* Stadium frame: seat-row stands around the play area (the pitch stays the coordinate space). */
/* Top-down STADIUM: a seat-dot bowl (tiled dots = packed stands) with team-tinted halves;
   the pitch covers the centre so seats show only in the surrounding stands ring. */
/* Fit rule: at zoom 1 the whole stadium must fit inside the stage (max-width caps it so a
   narrow/tall stage shrinks it instead of overflowing); zoom>1 scales both dims to enable pan. */
.builder-arena { position: relative; height: calc(100% * var(--bz, 1)); width: auto; aspect-ratio: var(--fb-aspect, 2000 / 1100); max-width: calc(100% * var(--bz, 1)); margin: auto; border-radius: 22px;
  display: flex; align-items: center; justify-content: center;
  background-color: #16232b;
  background-image:
    radial-gradient(circle, rgba(140,168,192,.95) 38%, transparent 43%),
    linear-gradient(90deg, rgba(59,130,246,.20), rgba(59,130,246,0) 42%, rgba(239,68,68,0) 58%, rgba(239,68,68,.20));
  background-size: 2.5% 4.3%, 100% 100%;
  box-shadow: inset 0 0 0 4px #2c4553, inset 0 0 70px rgba(0,0,0,.6), 0 8px 28px rgba(0,0,0,.45); }
/* Pitch inset inside the arena so the seat-dot stands wrap it on all sides. */
/* Pitch keeps EXACT 2000:1100 aspect (uniform world->screen scale => correct placement +
   un-skewed rotated walls). Inset 97%, not 92%: that 8% ring was the single biggest remaining gap
   between the arena and the controls (user: "increase the arena dimension… very little slack"), and
   because the pitch is height-bound by aspect-ratio, each percent of height returns ~1.8% of width.
   3% still shows the seat-bowl stands as a visible ring, so the stadium identity survives. */
.builder-arena .builder-pitch { position: relative; inset: auto; width: 97%; height: auto; max-width: 97%; max-height: 97%; aspect-ratio: var(--fb-aspect, 2000 / 1100); z-index: 1; box-shadow: 0 0 0 5px #0e3a1e, 0 0 0 7px rgba(255,255,255,.22);
  /* Brawl-style cell grid (20x11) drawn behind the placed elements. */
  background-image: linear-gradient(rgba(255,255,255,.11) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.11) 1px, transparent 1px), radial-gradient(circle at 50% 50%, #2f7d43, #256a38);
  background-size: 2.5% 4.5454%, 2.5% 4.5454%, 100% 100%; }
.btool[data-tool="eraser"].active { background: #6e2b2b; color: #fff; }
.bact:disabled { opacity: .35; }
#b-save { background: #2fbf5e; color: #06210f; }
/* Pitch markings (top-down): centre circle + halfway + penalty boxes + netted goals. */
.bp-circle { position: absolute; left: 50%; top: 50%; width: 17%; aspect-ratio: 1; transform: translate(-50%, -50%); border: 2px solid rgba(255,255,255,.4); border-radius: 50%; pointer-events: none; }
.bp-box { position: absolute; top: 50%; transform: translateY(-50%); height: 56%; width: 16%; border: 2px solid rgba(255,255,255,.4); pointer-events: none; }
.bp-box-l { left: 0; border-left: none; } .bp-box-r { right: 0; border-right: none; }
.bp-goal { background: rgba(255,255,255,.18); box-shadow: inset 0 0 0 2px rgba(255,255,255,.55);
  background-image: repeating-linear-gradient(0deg, rgba(255,255,255,.4) 0 1px, transparent 1px 5px), repeating-linear-gradient(90deg, rgba(255,255,255,.4) 0 1px, transparent 1px 5px); }

/* Training chooser + lean settings note */
.train-choose-opts { display: flex; gap: 12px; margin: 10px 0 4px; flex-wrap: wrap; }
.tc-opt { flex: 1; min-width: 150px; display: flex; flex-direction: column; gap: 4px; padding: 16px; border: none; border-radius: 14px; background: #2a3a30; color: #eaf2ec; text-align: center; }
.tc-opt b { font-size: 16px; } .tc-opt small { opacity: .8; font-size: 12px; }
#tc-bots { background: #2fbf5e; color: #06210f; }
.setting-empty { opacity: .55; font-size: 13px; padding: 10px; text-align: center; }

/* Keep builder rows clear of the iPhone notch/rounded corners (landscape safe-area). */
/* Was max(10%, …) here too — the same 168px tax on the bars as on the body. Safe area only. */
.builder-top, .builder-bottom, .builder-palette { padding-left: max(8px, env(safe-area-inset-left)); padding-right: max(8px, env(safe-area-inset-right)); }

/* Power-slots (and subpages): back button in the header. */
.subpage-head { display: flex; align-items: center; gap: 10px; }
.subpage-back { border: none; background: #33413a; color: #fff; border-radius: 10px; padding: 6px 12px; font-weight: 800; font-size: 14px; flex: 0 0 auto; }


.fp-modal { position: fixed; inset: 0; z-index: 60; display: flex; align-items: center; justify-content: center; background: rgba(3,6,4,.72); }
.fp-modal.hidden { display: none; }
/* max-height + scroll because the card grew a הסר חבר row: measured at 337px against a 333px landscape
   phone viewport (844x390 → ~333px usable), i.e. the button's bottom border was clipped off-screen. */
.fp-card { position: relative; width: min(88vw, 340px); max-height: 92vh; overflow-y: auto; background: #16211a; border: 3px solid #3a4a3c; border-radius: 14px; padding: 18px 16px; text-align: center; box-shadow: 0 10px 30px rgba(0,0,0,.5); }
.fp-close { position: absolute; top: 8px; left: 8px; width: 30px; height: 30px; border-radius: 50%; background: #2c3a2e; color: #e7dcae; border: 2px solid #4a5a4c; font-weight: 900; cursor: pointer; }
.fp-hero { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.fp-pfp { width: 72px; height: 72px; border-radius: 50%; overflow: hidden; background: #2c3a2e; display: flex; align-items: center; justify-content: center; font: 900 26px "Arial Black", sans-serif; color: #e7dcae; }
.fp-pfp img { width: 100%; height: 100%; object-fit: cover; }
.fp-online { font: 900 10px "Arial Black", sans-serif; color: #25e06a; }
.fp-name { font: 900 18px "Arial Black", sans-serif; color: #f2ecd0; margin-top: 8px; }
.fp-div { font: 900 12px "Arial Black", sans-serif; color: #b9c7ab; margin-top: 2px; }
.fp-stats { font: 900 11px "Arial Black", sans-serif; color: #9fb0a2; }
/* הסר חבר — destructive, so it reads as danger and sits BELOW the stats, away from the close ✕. */
.fp-remove { margin-top: 10px; width: 100%; padding: 9px 12px; cursor: pointer;
  font: 900 12px "Arial Black", sans-serif; color: #ffd9d9; background: #4a1b1e;
  border: 3px solid #7c2b30; box-shadow: 0 4px 0 #26090b; }
.fp-remove:active { transform: translateY(2px); box-shadow: 0 2px 0 #26090b; }
.fp-remove:disabled { opacity: .55; cursor: default; }

/* ---- IN-GAME CONFIRM (askConfirm in client.js) --------------------------------------------------
   Replaces window.confirm() for destructive taps. A WebView only shows a native JS dialog if the
   HOST app implements the confirm panel, so the browser one is not a guard we can rely on inside the
   app — this is. z-index sits above .fp-modal (60) because it is asked FROM that modal. */
.ask-modal { position: fixed; inset: 0; z-index: 80; display: flex; align-items: center; justify-content: center;
  background: rgba(3,6,4,.78); padding: 12px; }
.ask-card { width: min(90vw, 360px); max-height: 88vh; overflow-y: auto; background: #16211a;
  border: 3px solid #3a4a3c; border-radius: 14px; padding: 16px 14px; text-align: center;
  box-shadow: 0 10px 30px rgba(0,0,0,.55); }
.ask-title { font: 900 14px "Arial Black", sans-serif; color: #f2ead0; line-height: 1.35; }
.ask-body { margin-top: 8px; font: 900 11px "Arial Black", sans-serif; color: #9fb0a2; line-height: 1.5; }
.ask-row { display: flex; gap: 10px; margin-top: 14px; }
.ask-btn { flex: 1 1 0; padding: 11px 10px; cursor: pointer; font: 900 12px "Arial Black", sans-serif;
  color: #e7dcae; background: #2c3a2e; border: 3px solid #4a5a4c; box-shadow: 0 4px 0 #12190f;
  min-height: 44px; }                                  /* 44px: the smallest reliable touch target */
.ask-btn:active { transform: translateY(2px); box-shadow: 0 2px 0 #12190f; }
.ask-go { color: #d9ffe2; background: #1f4a2a; border-color: #2f7c40; box-shadow: 0 4px 0 #0b260f; }
.ask-danger { color: #ffd9d9; background: #4a1b1e; border-color: #7c2b30; box-shadow: 0 4px 0 #26090b; }

/* ============================================================================
   XP REWARD REVEAL — post-match hub overlay: full-screen confetti + a big
   centre number that meteors onto the #hub-xp bar (see playXpReveal in client.js).
   ============================================================================ */
#xp-reveal { position: fixed; inset: 0; z-index: 150; pointer-events: none; }
#xp-confetti { position: absolute; inset: 0; width: 100%; height: 100%; }
#xp-bignum {
  position: absolute; left: 50%; top: 50%; display: none; will-change: transform, opacity;
  font: 900 clamp(46px, 13vw, 118px)/1 "Arial Black", "Arial Hebrew", Impact, sans-serif;
  color: #ffcb43; letter-spacing: .01em; white-space: nowrap;
  unicode-bidi: isolate; font-variant-numeric: tabular-nums;
  text-shadow: 0 4px 0 #a56a16, 0 8px 0 #2a1c06, 0 12px 26px rgba(0,0,0,.6);
}
@media (prefers-reduced-motion: reduce) { #xp-confetti { display: none; } }

/* ==========================================================================
   FRIEND THREADS — preset messages + shared arenas (#thread)
   ========================================================================== */

/* Unread markers: a count chip on the friend card, and one on the hub friends button
   (the green .hub-sat-dot beside it stays presence-only). */
.fc-unread { margin-inline-start: auto; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
  background: #e0556b; color: #fff; font: 900 11px "Arial Black", sans-serif;
  display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 0 #6d1f2c; }
/* The last message in the thread, as a speech bubble TAILED AT THE BADGE and running left — it
   replaced a flat one-line preview here. Same plate as the lobby/in-match bubble (.pr-say) on
   purpose: one chat feature should look like one chat feature wherever it surfaces.
   The card is dir=rtl, so the badge is on the right and flex-start puts the bubble against it; the
   tail therefore points RIGHT, at the badge, and physical `right`/`border-left` are correct here
   rather than logical properties (this card has no LTR rendering).
   TWO triangles, not one: a single triangle on a 2px-bordered bubble draws an unoutlined tab, so the
   outer one is the border colour and the inner one is the plate colour, inset 3px behind it.
   No overflow:hidden and no line clamp anywhere on the bubble — either would cut the tail off, since
   the tail lives outside the box. A long message wraps and the row grows, which in a scrolling list
   is cheaper than a clipped bubble. */
.fc-meta.fc-say { display: flex; justify-content: flex-start; min-width: 0; margin-top: 1px; }
.fc-say > span { position: relative; display: inline-block; max-width: calc(100% - 8px);
  padding: 5px 9px; border-radius: 12px; background: #101a12; color: #fdfbe9;
  border: 2px solid #fdfbe9; font: 700 12px/1.3 system-ui, sans-serif; overflow-wrap: anywhere;
  box-shadow: 0 3px 0 rgba(0, 0, 0, .55); }
.fc-say > span::before, .fc-say > span::after {
  content: ''; position: absolute; top: 50%; width: 0; height: 0;
  border-top: 6px solid transparent; border-bottom: 6px solid transparent; }
.fc-say > span::before { right: -9px; margin-top: -6px; border-left: 9px solid #fdfbe9; }
.fc-say > span::after { right: -5px; margin-top: -4px;
  border-top-width: 4px; border-bottom-width: 4px; border-left: 6px solid #101a12; }
/* MY last message: same plate, NO tail, and the thread's own "mine" colours (.th-row.mine .th-bub),
   so the row still shows what was last said without a bubble sprouting from their badge to say it. */
.fc-meta.fc-say.mine > span { background: rgba(28,46,32,.96); border-color: #7f9a72; color: #dcecd0; }
.fc-meta.fc-say.mine > span::before, .fc-meta.fc-say.mine > span::after { display: none; }
/* Invite screen mini cards: the plate scales with the card, or a bubble in a 130px column wraps to
   four lines. */
.fc-mini .fc-say > span { font-size: 10.5px; padding: 4px 7px; border-width: 1.5px; }
.hub-sat-unread { position: absolute; top: 2px; left: 4px; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 8px; background: #e0556b; color: #fff; font: 900 10px "Arial Black", sans-serif; font-style: normal;
  display: flex; align-items: center; justify-content: center; box-shadow: 0 0 0 2px #0b120d; }
.hub-sat-unread.hidden { display: none; }

/* Header: back chevron · avatar + name + presence (tap = profile). */
.th-head { display: flex; align-items: center; gap: 10px; }
.th-back { background: none; border: 0; color: #fff4ca; font: 900 26px "Arial Black", sans-serif;
  line-height: 1; padding: 0 6px; cursor: pointer; }
.th-who { display: flex; align-items: center; gap: 8px; cursor: pointer; min-width: 0; }
.th-name { color: #fff4ca; font: 900 15px "Arial Black", sans-serif; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.th-who .friend-dot { background: #555; }
.th-who .friend-dot.online { background: #35d06a; box-shadow: 0 0 6px #35d06a; }

/* .subpage-body centres its children and scrolls itself; a chat needs full-width rows and an
   inner scroller instead, so both are overridden here. */
.th-body { display: flex; flex-direction: column; align-items: stretch; min-height: 0; gap: 10px; overflow: hidden; }
.th-msgs, .th-compose { width: 100%; max-width: 620px; align-self: center; }
.th-msgs { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; padding: 4px 2px; }
.th-empty { color: #98ab8b; font: 800 12px/1.5 system-ui, sans-serif; text-align: center; padding: 18px 10px; }

/* Bubbles mirror the RTL chat convention (WhatsApp/Telegram in Hebrew): messages I sent hug the
   LEFT edge, received ones hug the RIGHT. In an RTL row, flex-end IS the left edge. */
.th-row { display: flex; }
.th-row.mine { justify-content: flex-end; }
.th-row:not(.mine) { justify-content: flex-start; }
.th-bub { position: relative; max-width: 74%; padding: 9px 12px; border: 3px solid #46543f;
  background: rgba(19,27,22,.96); color: #eaf4e0; font: 800 13px/1.35 system-ui, sans-serif;
  box-shadow: 0 4px 0 #070b08; -webkit-user-select: none; user-select: none; }
.th-row.mine .th-bub { background: rgba(28,46,32,.96); border-color: #5d7a52; color: #f2ffe8; }
.th-bub.arena { padding: 8px; }
.th-reacts { margin-top: 5px; font-size: 13px; line-height: 1; letter-spacing: 2px; }

/* Shared-arena card inside a bubble. */
.th-arena { display: flex; flex-direction: column; gap: 5px; min-width: 148px; }
.th-arena-name { color: #fff4ca; font: 900 13px "Arial Black", sans-serif; }
.th-arena-sub { color: #b8caa8; font-size: 11px; font-weight: 800; }
.th-arena-acts { display: flex; gap: 6px; margin-top: 3px; }
.th-arena-btn { flex: 1; padding: 7px 4px; border: 3px solid #2f6f3a; background: #47b357; color: #06210c;
  font: 900 12px "Arial Black", sans-serif; box-shadow: 0 3px 0 #1d4a24; cursor: pointer; }
.th-arena-btn.ghost { background: #253026; border-color: #4a5a4c; color: #dff0d0; box-shadow: 0 3px 0 #0d140e; }
.th-arena-btn:active { transform: translateY(2px); box-shadow: none; }

/* Composer — a text field on its own line, then the phrase picker and the arena share.
   The field goes ABOVE the buttons rather than beside them: at 390px, three controls in one row left
   the input about 120px wide, which is narrower than the 20 characters it accepts.
   Field styling matches the party composer (.pchat-input) — same control, same two-repo cap. */
.th-compose { display: flex; flex-direction: column; gap: 8px; flex: 0 0 auto; }
.th-write { display: flex; align-items: center; gap: 6px; }
.th-input { flex: 1 1 auto; min-width: 0; font: 700 13px/1.2 system-ui, sans-serif; padding: 10px 11px;
  border: 3px solid #46543f; background: rgba(10,16,12,.9); color: #eaf4e0; }
.th-input::placeholder { color: #7d8f72; }
.th-left { font: 600 11px/1 ui-monospace, monospace; color: #98ab8b; min-width: 2ch; text-align: center; }
.th-left.over { color: #ff8a7a; }
.th-cbtns { display: flex; gap: 8px; }
.th-cbtn { flex: 1; padding: 11px 6px; border: 3px solid #46543f; background: rgba(28,40,30,.96);
  color: #fff4ca; font: 900 13px "Arial Black", sans-serif; box-shadow: 0 4px 0 #070b08; cursor: pointer; }
.th-cbtn.send { flex: 0 0 auto; padding: 11px 16px; border-color: #2f6f3a; background: #47b357;
  color: #06210c; box-shadow: 0 4px 0 #1d4a24; }
.th-cbtn:active { transform: translateY(2px); box-shadow: none; }
/* A typed line reads as words, not as a UI label — lighter weight than the preset bubbles, which are
   fixed phrases. */
.th-bub.typed { font-weight: 700; -webkit-user-select: text; user-select: text; }

/* Bottom sheets: phrase picker + saved-arena picker. */
.th-sheet { position: fixed; inset: 0; z-index: 60; background: rgba(0,0,0,.55);
  display: flex; align-items: flex-end; justify-content: center; }
.th-sheet.hidden { display: none; }
.th-sheet-card { width: min(560px, 100%); max-height: 78vh; overflow-y: auto; padding: 12px;
  background: #131b16; border-top: 4px solid #46543f; display: flex; flex-direction: column; gap: 10px; }
.th-sheet-title { color: #fff4ca; font: 900 14px "Arial Black", sans-serif; }
.th-sheet-tabs { display: flex; gap: 6px; flex-wrap: wrap; }
.th-sheet-tab { padding: 7px 11px; border: 3px solid #46543f; background: #1b271e; color: #cfe0c0;
  font: 900 12px "Arial Black", sans-serif; cursor: pointer; }
.th-sheet-tab.active { background: #47b357; border-color: #2f6f3a; color: #06210c; }
.th-sheet-list { display: flex; flex-direction: column; gap: 7px; }
.th-phrase { padding: 12px; border: 3px solid #46543f; background: rgba(24,34,26,.96); color: #eaf4e0;
  font: 800 14px system-ui, sans-serif; text-align: start; cursor: pointer; }
.th-phrase:active { transform: translateY(2px); }
.th-sheet-close { padding: 10px; border: 3px solid #5a4040; background: #2a1c1c; color: #ffc9c9;
  font: 900 13px "Arial Black", sans-serif; cursor: pointer; }

/* Reaction bar (long-press a bubble). Positioned in JS, clamped to the viewport. */
.th-react { position: fixed; z-index: 70; display: flex; gap: 4px; padding: 6px;
  background: #131b16; border: 3px solid #46543f; box-shadow: 0 4px 0 #070b08; }
.th-react.hidden { display: none; }
.th-react-btn { background: none; border: 0; font-size: 21px; line-height: 1; padding: 3px 5px; cursor: pointer; }
.th-react-btn:active { transform: scale(1.25); }

/* ---- party chat: preset row + 40-char free text, and the bubbles on the roster --------------- */
/* The bubble hangs above a member's hero. `position:relative` on the blocks is what anchors it, and
   it is deliberately allowed to shrink-wrap short messages so "מוכן?" is not a full-width banner. */
/* ---- CHAT BUBBLES: the in-match look, one size up -------------------------------------------
   The match draws its bubble on canvas: a dark rounded plate at ~72% opacity with light text, and
   emotes as hard-edged pixel sprites. This mirrors that in CSS so the lobby and the game read as the
   same feature, then makes it BIGGER AND MORE DEFINED as asked: 13px text instead of the in-game
   ~11px, a solid plate rather than a translucent one, and a 2px outline plus a drop shadow so it
   stands off the roster art instead of dissolving into it.
   IN FLOW, in a reserved slot: six blocks at 3v3 on a 390px phone had absolute bubbles overlapping
   and clipping each other. */
.pr-say { min-height: 24px; display: flex; align-items: flex-end; justify-content: center;
  max-width: 124px; }
.pr-me .pr-say { max-width: 152px; }
/* On a team row the bubble takes its OWN LINE (flex-basis 100% after a wrap) rather than sitting
   inline: inline, it stole width from the name — "Friend3" rendered as "…end3" — and a longer message
   was clipped by the row. The row is allowed to wrap for exactly this. */
.member-row { flex-wrap: wrap; }
.mr-say { display: none; }
.mr-say.has { display: flex; flex: 1 0 100%; justify-content: flex-start; margin-top: 4px; }
.pr-say.has > span, .mr-say.has > span {
  display: inline-block; max-width: 100%; padding: 5px 9px; border-radius: 12px;
  background: #101a12; color: #fdfbe9; border: 2px solid #fdfbe9;
  font: 700 13px/1.25 inherit; text-align: center; overflow-wrap: anywhere;
  box-shadow: 0 3px 0 rgba(0, 0, 0, .55);
  animation: bub-in .16s ease-out;
}
/* An emote bubble is the pixel sprite itself, sampled hard like the in-match one. */
.pr-say.has > span.bub-emote, .mr-say.has > span.bub-emote {
  width: 34px; height: 34px; padding: 3px; background-repeat: no-repeat;
  background-color: #101a12; image-rendering: pixelated;
}
@keyframes bub-in { from { opacity: 0; transform: translateY(4px) scale(.96); } }
@media (prefers-reduced-motion: reduce) { .pr-say.has > span, .mr-say.has > span { animation: none; } }

/* Empty seats + the per-side counter, so a 3v3 team page reads as 3-a-side before the bots arrive. */
.member-ghost { opacity: .45; }
.member-ghost .member-av { border-style: dashed; }
.member-ghost .member-name { font-style: italic; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.team-seats { font: 900 11px "Arial Black", sans-serif; color: #ffce4a; margin-inline-start: 6px; }

.party-chat { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.pchat-presets { display: flex; flex-wrap: wrap; gap: 6px; }
.pchat-pre {
  font: 600 12px/1 inherit; padding: 7px 10px; border-radius: 999px;
  border: 2px solid rgba(255, 255, 255, .22); background: rgba(255, 255, 255, .10); color: #fff;
  cursor: pointer;
}
.pchat-pre:active { transform: translateY(1px); }
.pchat-pre.is-emote {
  width: 38px; height: 38px; padding: 0; background-repeat: no-repeat; background-color: rgba(0, 0, 0, .3);
  image-rendering: pixelated; border-radius: 10px;
}
.pchat-row { display: flex; align-items: center; gap: 6px; }
.pchat-row.hidden { display: none; }
.pchat-input {
  flex: 1 1 auto; min-width: 0; font: 600 13px/1 inherit; padding: 9px 11px; border-radius: 10px;
  border: 2px solid rgba(255, 255, 255, .22); background: rgba(0, 0, 0, .28); color: #fff;
}
.pchat-input::placeholder { color: rgba(255, 255, 255, .45); }
.pchat-left { font: 600 11px/1 ui-monospace, monospace; color: rgba(255, 255, 255, .55); min-width: 2ch; text-align: center; }
.pchat-left.over { color: #ff8a7a; }
.pchat-send {
  font: 700 12px/1 inherit; padding: 9px 14px; border-radius: 10px; border: 2px solid #22303c;
  background: #ffd06a; color: #22303c; cursor: pointer;
}
.pchat-send:active { transform: translateY(1px); }


/* ---- PLAY WITH FRIENDS · invite screen: actions left (a quarter), people right ------------------
   The screen used to be one column: sections of friends, then the CTA, then join-by-code at the very
   bottom — so with a few friends online the primary action was pushed off a phone screen and the room
   code (the only way a NON-friend can join) was below that. Now the actions are a fixed quarter that
   cannot scroll away, and the people scroll beside them.
   RTL note: `direction: rtl` on the subpage means the FIRST grid column renders on the RIGHT. The
   actions are declared first in the markup and pinned to the LEFT with an explicit `order`, so the
   layout reads the way it was asked for in both directions. */
.fs-split {
  display: grid;
  /* a quarter, floored at 130px: 25% of a 390px phone is 98px, which cannot hold «שחק עכשיו» */
  grid-template-columns: 1fr minmax(130px, 25%);
  gap: 12px; align-items: stretch; min-height: 0;
  /* FILL the body and let the LIST scroll, rather than sizing the list with a vh number: `62vh` counts
     the notch and home-indicator strips as usable space, so on a notched phone the last card sat under
     the indicator. The body is already a flex column, so flex:1 + min-height:0 is the safe-area-correct
     way to say "whatever is left". */
  flex: 1; width: 100%; align-self: stretch;
}
.fs-actions { order: 2; display: flex; flex-direction: column; gap: 8px; position: sticky; top: 0; }
.fs-people { order: 1; min-width: 0; min-height: 0; }
/* The list is the only thing that scrolls. 62vh keeps the sticky column and the page head visible. */
.fs-people { display: flex; flex-direction: column; }
.fs-people .fs-sections { flex: 1; min-height: 0; overflow-y: auto; padding-inline-end: 4px;
  /* one last guard for the indicator on a phone that reports no inset (Android WebViews sometimes do) */
  padding-bottom: max(4px, env(safe-area-inset-bottom)); }
.fs-play { width: 100%; margin: 0; padding: 12px 6px; display: flex; align-items: center; justify-content: center; }
/* `.play` draws a chevron in a <b> sized for a full-width button; in a 130px column it landed ON TOP
   of the label. The label is the affordance here, so the chevron goes. */
.fs-play b { display: none; }
.fs-play span { font-size: 13px; letter-spacing: 0; }
.fs-play.is-waiting { opacity: .6; }
.fs-code-box { background: rgba(12,18,13,.92); border: 3px solid #46543f; box-shadow: 0 3px 0 #070b08; padding: 8px; text-align: center; }
.fs-code-h { font: 900 10px "Arial Black", sans-serif; color: #9fb08f; letter-spacing: .5px; }
.fs-code {
  display: block; width: 100%; margin: 4px 0 2px; padding: 6px 4px; cursor: pointer;
  background: #101a12; border: 2px solid #ffce4a; color: #ffce4a;
  font: 900 24px/1 ui-monospace, monospace; letter-spacing: 3px;
}
.fs-code:disabled { color: #6d7a68; border-color: #46543f; cursor: default; }
.fs-code-hint { font: 600 9px/1.25 inherit; color: #8e9c84; }
.fs-btn {
  width: 100%; padding: 9px 6px; cursor: pointer; border: 2px solid #39463b; background: #141d15;
  color: #c8d4bf; font: 900 11px "Arial Black", sans-serif; letter-spacing: .5px;
}
.fs-btn:active { transform: translateY(1px); }
.fs-btn-go { background: #ffd06a; border-color: #22303c; color: #22303c; }
.fs-join { display: flex; flex-direction: column; gap: 6px; background: rgba(12,18,13,.92);
  border: 3px solid #46543f; box-shadow: 0 3px 0 #070b08; padding: 8px; }
.fs-join-h { font: 900 10px "Arial Black", sans-serif; color: #9fb08f; letter-spacing: .5px; }
.fs-join .code-input { width: 100%; text-align: center; }

/* The friends-page card, one size down, plus its invite button. */
.fc-mini { padding: 8px; gap: 8px; position: relative; cursor: pointer; }
.fc-mini .fc-pfp { width: 34px; height: 34px; font-size: 12px; }
.fc-mini .fc-name { font-size: 12px; }
.fc-mini .fc-meta { font-size: 9.5px; }
.fc-mini .fc-invite {
  position: absolute; inset-inline-start: 8px; bottom: 8px; padding: 5px 10px; cursor: pointer;
  border: 2px solid #22303c; background: #ffd06a; color: #22303c;
  font: 900 10px "Arial Black", sans-serif;
}
.fc-mini .fc-invite.is-pending { background: #2b3a2c; border-color: #46543f; color: #9fb08f; }
@media (max-width: 340px) {                 /* a very narrow phone: stack rather than squeeze */
  .fs-split { grid-template-columns: 1fr; }
  .fs-actions { position: static; order: 1; }
  .fs-people { order: 2; }
}

/* MATCHMAKING SEARCH chrome (see docs/superpowers/specs/2026-07-27-matchmaking-countdown-design.md) */
/* SAFE ZONES: the game is LANDSCAPE, so safe-area-inset-left/right are the non-zero ones on a
   notched phone (see the note above .subpage-back). A flat `left: 0; right: 0` would run the band
   chip and the cancel button under the notch, which is what 53e594c fixed everywhere else. */
.ti-search { position: absolute; top: max(6px, env(safe-area-inset-top));
  left: max(8px, env(safe-area-inset-left)); right: max(8px, env(safe-area-inset-right));
  display: grid; gap: 2px; justify-items: center; pointer-events: none; z-index: 4; }
.ti-search-head { display: flex; align-items: center; gap: 8px; }
.ti-search-title { font-size: 13px; font-weight: 800; color: #ffe9a8; }
.ti-search-sub { font-size: 11px; font-weight: 700; opacity: .75; }
.ti-search-band { font-size: 11px; font-weight: 800; padding: 1px 6px; border-radius: 8px; background: rgba(255, 255, 255, .12); }
/* Small SECONDARY search-budget timer (⏱ N) — same scale as .ti-search-band on purpose, NOT the giant
   #ti-count kickoff number (clamp(42px,14vh,128px) — see the note on that rule). #ti-count means
   "kickoff is certain and imminent" everywhere else in this game; a search budget that can still
   widen, grace-extend or end in bots is a different, more tentative thing and must look it. */
.ti-search-timer { font-size: 11px; font-weight: 800; opacity: .85; white-space: nowrap; }
.ti-search-pips { display: inline-flex; gap: 3px; }
.ti-search-pips i { width: 7px; height: 7px; border-radius: 50%; background: rgba(255, 255, 255, .22); }
.ti-search-pips i.on { background: #7fd48f; }
/* An opponent slot with nobody in it yet: dashed, so an empty slot reads as "still looking" rather
   than as a broken row. Preview bots used to occupy these from the first tick. */
.ti-row.ti-empty .ti-av { border: 1px dashed rgba(255, 255, 255, .3); background: transparent; }
.ti-row.ti-empty .ti-name { opacity: .5; }
.ti-search.found .ti-search-title { color: #8ef2a3; }
.ti-search.alone .ti-search-title { color: #ffd06a; }
/* A resolution banner (found/alone) survives into the reveal per updateVsCountdown's keepBanner, which
   is also when #ti-mode (the ⚽ mode-name + rule pill) shows for the first time — both anchored at the
   same top-center spot, so without this they print on top of each other. Found via a real CDP
   screenshot: "אין שחקנים פנויים כרגע" over "⚽ כדורגל · 2 נגד 2", both illegible. #ti-mode measures
   34px tall at this viewport; 44px clears it with a few px to spare. */
#ti-mode:not(.hidden) ~ .ti-search { top: max(44px, calc(env(safe-area-inset-top) + 40px)); }
.ti-search-cancel { pointer-events: auto; margin-top: 2px; font-size: 11px; padding: 2px 8px; }
.ti-search.found .ti-search-cancel, .ti-search.alone .ti-search-cancel { display: none; }

/* =========================================================================
   TUTORIAL — the scripted first match (kids' onboarding)
   docs/superpowers/specs/2026-07-27-tutorial-onboarding-design.md

   A SILENT coach: no character, no dialogue, and it never pauses the game. Per step it dims
   everything except the one live control, animates a hand doing the actual gesture over it, and
   prints 1-2 Hebrew words. The audience is children, so the bar is that a kid who cannot read
   Hebrew still finishes: the HAND carries the lesson and the words only confirm it. That is
   Epic's "don't expect them to read" rule taken literally.

   z-index budget in #game: sticks 4, cues 5-6, top-bar buttons 7-8. The coach sits at 9 (above
   the pitch and the controls it points at, below nothing) and the finale at 30 (a real overlay).
   ========================================================================= */
.tutorial { position: fixed; inset: 0; z-index: 9; pointer-events: none; }
.tutorial.hidden { display: none; }

/* The veil dims the whole screen EXCEPT a soft hole over the live control. One radial gradient
   rather than four clipped panels: the hole can then be moved/resized every frame from the
   control's real anchor (a moved or resized stick keeps its spotlight) with no reflow.

   IT IS OFF BY DEFAULT NOW, and only `.tutorial.veiled` turns it on. The class is set in JS
   (tuRenderOverlay) under one rule, reported from a phone: dim ONLY on a step that hands the kid a
   button they have never had — walk, aim, 💣, 🧱, four steps in the whole tutorial — and stop the
   instant they press it. It used to be up for every step of every level, so a kid who already knew
   how to walk played half the tutorial through a dimmed screen. The hand and the caption are NOT
   part of this and still run on every step; only the dimming is conditional.

   `.tutorial.no-spot .tu-veil` — the flat even dim for the steps that point at the GRASS instead of
   a button — is GONE with the same change: those steps introduce nothing, and no veil means no veil.
   Opacity, not a background swap, is what fades it: the off-switch is a press, so the fade has to be
   quick enough to read as a response to that press (.14s) while still not being a hard cut. */
.tu-veil {
  position: absolute; inset: 0; opacity: 0;
  background: radial-gradient(circle var(--tu-r, 120px) at var(--tu-x, 50%) var(--tu-y, 50%),
              rgba(0,0,0,0) 0%, rgba(0,0,0,0) 62%, rgba(0,0,0,.52) 100%);
  transition: background .25s ease, opacity .14s ease;
}
.tutorial.veiled .tu-veil { opacity: 1; }

/* A GOAL in the tutorial now plays the same comic «גול!» the rest of the game plays, and that word
   lands mid-screen — right where the caption above sits. So the coach's words step aside for the
   beat the word is up (`celebrating`, toggled in tuTick) instead of being drawn under a starburst.
   Nothing is lost: at that moment the standing caption is an instruction the kid has just obeyed,
   and the NEXT step's caption pops in as the word fades. `visibility` as well as `opacity`, so the
   pips underneath fade with it rather than hanging in mid-air over the word.
   (Declared here, in the veil block, rather than beside the .tu-caption rules — those are being
   reworked for a layout fix in the same hour this was written and this rule has no business in the
   middle of that.) */
.tutorial.celebrating .tu-caption { opacity: 0; visibility: hidden; transition: opacity .12s ease; }

/* SKIP — the way out of the tutorial. Top-INLINE-END (physical left in this RTL page), which is the one
   corner the coach never uses: the caption owns top-centre, the sticks own both bottom corners, and the
   spotlight only ever points at a control. Small and low-contrast on purpose — an adult looking for it
   finds it, a seven-year-old reaching for the move stick does not hit it. `pointer-events: auto` because
   .tutorial itself is pointer-events: none so the coach never eats a touch meant for the pitch. */
.tu-skip {
  position: absolute; top: max(8px, calc(env(safe-area-inset-top) + 6px));
  inset-inline-end: max(10px, calc(env(safe-area-inset-right) + 8px));
  pointer-events: auto; cursor: pointer; appearance: none;
  font: 800 clamp(11px, min(2.6vh, 2.4vw), 15px)/1 system-ui, sans-serif; letter-spacing: .04em;
  color: #cbd6c8; background: rgba(9,14,11,.62); border: 1px solid rgba(203,214,200,.28);
  border-radius: 999px; padding: 7px 13px;
  /* A real touch target even though the label is small — 44px is the platform minimum and a cramped
     one is how you get mis-taps on the thing you were trying to avoid. */
  min-width: 60px; min-height: 34px;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.tu-skip:hover { color: #fff; border-color: rgba(203,214,200,.5); }
.tu-skip:active { scale: .96; }
.tu-skip:focus-visible { outline: 2px solid var(--gold, #ffd06a); outline-offset: 2px; }
/* Gone during the finale card and the goal beat — nothing to skip once the level is won, and the
   celebration owns the screen. */
.tutorial.celebrating .tu-skip { opacity: 0; pointer-events: none; }

/* The pointing hand. Parked over the live control by JS (--tu-x/--tu-y) and animated per gesture:
   `circle` walks it around the stick (move), `pull` shows hold -> drag -> release (aim/shoot). */
/* POSITIONED WITH left/top, NOT transform — and that is load-bearing, not style.
   It used to be `transform: translate(var(--tu-x), var(--tu-y))`. CSS applies the individual
   `translate`/`rotate`/`scale` properties BEFORE `transform`, so the gesture keyframes' `scale`
   multiplied the position vector itself: at scale .84 the hand rendered at 0.84x its target's
   coordinates — 110px short of a button on the right of the screen — and the `nudging` rule
   (scale 1.28) threw it ~190px the other way, at precisely the moment a stuck kid most needs the
   hand to be pointing at the right thing. Caught by handOnTarget() in _tu-verify.mjs, which
   measures the hand's real rect against its target's instead of trusting the CSS variable.
   With left/top owning the position, the keyframes own `translate`/`scale` alone and scale
   operates about the hand's own centre, which is what it was always meant to do. */
.tu-hand {
  position: absolute; left: var(--tu-x, 50%); top: var(--tu-y, 50%);
  width: 54px; height: 54px; margin: -27px;
  background: no-repeat center/contain;
  /* Drawn inline rather than loaded: the coach must never wait on a request, and the icon pack
     has no pointing hand (ASSET_HANDOFF: don't invent semantic ids for one-off UI). */
  background-image: url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'>\
<path d='M20 6c0-2 1.6-3.4 3.5-3.4S27 4 27 6v14h1.5V9.5c0-2 1.6-3.4 3.5-3.4s3.5 1.4 3.5 3.4V26h1.5v-6.5c0-2 1.6-3.4 3.5-3.4S44 17.5 44 19.5V33c0 7.7-5.6 13-13.5 13h-4C19 46 14 41.5 11.5 35L6 22.5c-.8-1.9 0-3.9 1.9-4.7 1.9-.8 4 .1 4.8 2L16 27h1.5V6z' fill='%23fff4ca' stroke='%23241f14' stroke-width='2.4' stroke-linejoin='round'/></svg>");
  filter: drop-shadow(0 4px 10px rgba(0,0,0,.65));
}
.tu-hand.gest-circle { animation: tu-circle 2s linear infinite; }
.tu-hand.gest-pull { animation: tu-pull 2.2s ease-in-out infinite; }
/* TAP — press and let go, no travel. The mime for "aim and tap": a short jab, twice, then a
   pause long enough that it reads as a separate action rather than a twitch. */
.tu-hand.gest-tap { animation: tu-tap 1.8s ease-in-out infinite; }
/* HOLD — press and STAY pressed, squeezing a little harder as it goes, which is the whole point:
   the longer you keep it down the more you get. Ends with the release that fires the shot. */
.tu-hand.gest-hold { animation: tu-hold 2.6s ease-in-out infinite; }
/* LOB — press the button, then carry it OUTWARD, slowly. Used by 💣 and 🧱, whose drag chooses
   where the thing lands. Deliberately slower than `pull` and travelling the opposite way: a kid
   copying this mime is doing the actual gesture, so it has to point where the throw goes. */
.tu-hand.gest-lob { animation: tu-lob 3.2s ease-in-out infinite; }
/* `tap` is the third gesture, and the ONLY one where the lesson is what you DON'T do: level 3's
   fly step needs the bomb dropped at your own feet, which is a plain press with no drag. Without
   its own keyframes the hand parked over 💣 and sat there motionless — a still hand on a screen
   where every other step's hand moves reads as "nothing here", exactly backwards. Two quick
   presses then a beat, so it's unmistakably a tap and unmistakably not the `pull` above. */
.tu-hand.gest-tap { animation: tu-tap 1.8s ease-in-out infinite; }
/* Stuck for a while: the same gesture, bigger and faster. An escalation the kid who is already
   doing fine never sees. */
.tutorial.nudging .tu-hand { scale: 1.28; animation-duration: 1.2s; }

/* RTL-safe: both keyframes move the hand in a shape, not toward a text direction. */
@keyframes tu-circle {
  0%   { translate: 0 0; }
  25%  { translate: 34px -12px; }
  50%  { translate: 0 26px; }
  75%  { translate: -34px -12px; }
  100% { translate: 0 0; }
}
@keyframes tu-tap {
  0%,  55%, 100% { translate: 0 0; scale: 1; }
  8%,  20%       { scale: .78; }          /* jab */
  14%            { scale: .78; }
  30%, 42%       { scale: .78; }          /* ...and again, so it reads as "tap", not "press" */
  36%            { scale: .78; }
}
@keyframes tu-hold {
  0%,  8%   { translate: 0 0; scale: 1; }
  18%       { scale: .82; }                /* down */
  70%       { scale: .72; }                /* ...and STAYS down, squeezing harder */
  78%       { scale: 1.1; }                /* release = the shot */
  92%, 100% { scale: 1; }
}
@keyframes tu-lob {
  0%,  8%   { translate: 0 0; scale: 1; }
  18%       { translate: 0 0; scale: .8; }      /* press the button */
  62%       { translate: 62px 0; scale: .8; }   /* carry it out, slowly, the way the throw goes */
  74%       { translate: 62px 0; scale: 1.08; } /* let go — that is where it lands */
  90%, 100% { translate: 0 0; scale: 1; }
}
@keyframes tu-pull {
  0%,  10%  { translate: 0 0; scale: 1; }
  30%       { translate: 0 0; scale: .84; }   /* press */
  60%       { translate: -46px 0; scale: .84; } /* drag out to aim */
  75%       { translate: -46px 0; scale: 1.06; } /* release = shoot */
  90%, 100% { translate: 0 0; scale: 1; }
}
/* Straight down and back up, twice, then a rest. It never translates sideways — that is the whole
   point of the gesture: this button is pressed where it is, not dragged somewhere. */
@keyframes tu-tap {
  0%,  8%   { translate: 0 0; scale: 1; }
  16%       { translate: 0 7px; scale: .82; }  /* press */
  26%       { translate: 0 0; scale: 1.04; }   /* release */
  36%       { translate: 0 7px; scale: .82; }  /* and again, so it reads as a TAP, not a hold */
  46%       { translate: 0 0; scale: 1.04; }
  60%, 100% { translate: 0 0; scale: 1; }      /* beat, so the two presses stay countable */
}

/* 1-2 Hebrew words, in the pitch's comic voice. Sits high-centre, clear of both thumbs.
   TWO NUMBERS HERE WERE WRONG ON A PHONE, both for the same reason — they were reasoned about on a
   desktop window, where a viewport is wide AND tall. A landscape phone is 844x390.
   * `top` was `max(64px, safe-area + 58px)`, reserving room for the score/clock cluster. In a
     TUTORIAL that cluster does not exist: enterMatch puts `tu-off` on #hud (client.js) precisely so
     a first-timer is not watching a countdown to nothing. So 64px was a hole held open for nothing,
     on a viewport only 390px tall — one sixth of the screen, and the reason the user reported the
     heading sitting too low. It now sits just under the safe area.
   * the font was sized in `vw`, and on a landscape phone vw is the LONG axis: 7.5vw of 844 = 63px,
     clamped to the 58px ceiling — a 58px cap line plus the pill plus the pips ran to ~90px of the
     390 available. Sized off `vh` now, with a vw term kept as a second ceiling so a short wide
     window cannot blow it up either: min() takes whichever axis is the tighter one.
   The scale ends up ~31px on a 390-tall phone and ~42px on a tablet, against 58px before. */
.tu-caption {
  position: absolute; left: 0; right: 0; top: max(10px, calc(env(safe-area-inset-top) + 8px));
  display: flex; flex-direction: column; align-items: center; gap: 3px; text-align: center;
}
.tu-caption b {
  font: 900 clamp(20px, min(8vh, 7vw), 42px)/1 "Arial Black", Impact, sans-serif; color: #fff4ca;
  /* The drop-shadow steps scale with the type. At 5px/9px they were tuned for a 58px cap line; left
     alone under a 31px one they read as a smudge under the letters rather than a comic outline. */
  text-shadow: 0 3px 0 #a56a16, 0 5px 0 #252a21, 0 8px 18px rgba(0,0,0,.55);
  animation: tu-pop .32s cubic-bezier(.2,1.5,.4,1);
}
/* The second line is now TWO different messages through one element: a step's standing `sub` (how
   the control works — "tap = under you, drag = where you point"), replaced by its `nudge` only once
   the kid is stuck. They must not look the same. This is the calm one: a quiet dark pill that a
   child who is doing fine can ignore. `max-width` matters here and did not before — a `sub` runs to
   three clauses where the old nudges were four words, and unconstrained it walks off both screen
   edges in landscape on a phone. */
.tu-caption small {
  /* Same vh-first sizing as the cap line above, and for the same reason: 3.2vw was 27px on a phone
     in landscape, i.e. only clamped down by the 19px ceiling, which made the "quiet" line nearly as
     loud as the heading it hangs under. ~14px on a 390-tall screen. */
  font: 700 clamp(11px, min(3.6vh, 3.2vw), 17px)/1.2 system-ui, sans-serif; color: #fff;
  background: rgba(0,0,0,.55); padding: 4px 12px; border-radius: 999px;
  max-width: min(86vw, 560px); text-wrap: balance;
  animation: tu-pop .3s cubic-bezier(.2,1.5,.4,1);
}
/* ...and this is the escalation. Same words-in-a-pill, but amber and breathing, matching the hand
   that grew and sped up at the same moment (`.nudging .tu-hand` above) — one signal, two places, so
   a kid who is stuck sees the screen change rather than having to notice new text. */
.tutorial.nudging .tu-caption small {
  color: #2a1c05; background: #ffd06a; box-shadow: 0 3px 12px rgba(255,208,106,.35);
  animation: tu-pop .3s cubic-bezier(.2,1.5,.4,1), tu-nudge-breathe 1.2s ease-in-out .3s infinite;
}
@keyframes tu-nudge-breathe { 0%, 100% { scale: 1; } 50% { scale: 1.06; } }
.tu-caption small.hidden { display: none; }
@keyframes tu-pop { from { scale: .6; opacity: 0; } to { scale: 1; opacity: 1; } }

/* Progress pips — four dots, no numbers. Reads at a glance and needs no reading age.
   Grouped UNDER THE CAPTION rather than along the bottom edge: down there they landed on the
   build tag and between both thumbs (caught in a screenshot). Up here all the coach's furniture
   is one thing in one place. */
.tu-pips { display: flex; justify-content: center; gap: clamp(5px, 1.4vh, 7px); margin-top: 2px; }
.tu-pips i {
  /* Scaled off the short axis with the rest of the cluster — fixed 9px dots under a heading that is
     now ~31px instead of 58px were the biggest thing left in the stack proportionally. */
  width: clamp(6px, 1.9vh, 9px); aspect-ratio: 1; border-radius: 50%; background: rgba(255,255,255,.28);
  box-shadow: 0 1px 3px rgba(0,0,0,.5); transition: background .2s ease, scale .2s ease;
}
.tu-pips i.on { background: #ffd06a; scale: 1.25; }
.tu-pips i.done { background: #7fd48f; }

/* ---- Finale. The only full-screen beat in the flow, and only after the winning goal. ---- */
.tu-done {
  position: fixed; inset: 0; z-index: 30; display: flex; align-items: center; justify-content: center;
  background: rgba(6,10,7,.72); backdrop-filter: blur(3px);
}
.tu-done.hidden { display: none; }
.tu-done-card {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 22px 34px; border-radius: 18px;
  background: linear-gradient(180deg, #2b3a2c, #1a241b); border: 3px solid rgba(240,228,185,.45);
  box-shadow: 0 18px 50px rgba(0,0,0,.6);
  animation: tu-pop .38s cubic-bezier(.2,1.5,.4,1);
}
.tu-done-ic { font-size: clamp(38px, 10vw, 64px); line-height: 1; }
.tu-done-card b {
  font: 900 clamp(24px, 6vw, 40px)/1 "Arial Black", Impact, sans-serif; color: #fff4ca;
  text-shadow: 0 4px 0 #a56a16, 0 7px 0 #252a21;
}
.tu-done-btn {
  margin-top: 4px; padding: 11px 22px; border: 0; border-radius: 12px; cursor: pointer;
  font: 800 clamp(14px, 3.6vw, 18px)/1 system-ui, sans-serif; color: #241f14;
  background: linear-gradient(180deg, #ffd77a, #e8a02f); box-shadow: 0 4px 0 #a56a16;
}
.tu-done-btn:active { transform: translateY(2px); box-shadow: 0 2px 0 #a56a16; }

/* Controls a step has not taught yet simply do not exist — hidden AND their input dropped
   (see tuGate in client.js). The bomb and fence buttons stay hidden for the whole tutorial. */
.tu-off { display: none !important; }

/* The finale's SECOND button. «המשך ל…» is the primary gold one; leaving for the hub is the
   quieter choice, so it reads as secondary rather than as an equal fork. */
.tu-done-alt {
  background: rgba(255,255,255,.10); color: #fff4ca; box-shadow: 0 3px 0 rgba(0,0,0,.35);
  font-size: clamp(12px, 3.1vw, 15px); padding: 8px 18px;
}
.tu-done-alt:active { box-shadow: 0 1px 0 rgba(0,0,0,.35); }
.tu-done-btn.hidden { display: none; }
/* the new hub's finale parts (rays / «סיימתם את …» / the lesson pips, styled by hub-pages.css): the legacy card has none */
.tu-done-rays, .tu-done-sub:empty, .tu-done-pips:empty { display: none; }

/* Replay entry in the אימון sheet. Tinted so it reads as the odd one out — it is the only
   option there that teaches rather than practises. */
.tc-howto { background: linear-gradient(180deg, rgba(255,215,122,.18), rgba(232,160,47,.12)); }

/* ---- Level picker (🎓 איך משחקים?) ---- */
/* A locked level stays TAPPABLE and says why, rather than being dead pixels — the same rule the
   MODES cards follow. */
.tu-lv.locked { opacity: .5; }
.tu-lv.locked b { filter: grayscale(1); }

/* ===== LEVEL 4 · מרכז — the hub tour ==============================================
   The pitch levels hide an untaught control with .tu-off (display:none). That is wrong here:
   the hub's furniture has to stay VISIBLE — the lesson is what the screen contains — it just
   must not be reachable. So the hub gate dims instead of hiding, and kills pointer-events, with
   the one live target whitelisted.

   ⚠️ Applied as a CLASS on <body>, with .tu-live set once per STEP CHANGE — never re-derived per
   frame. That is deliberate: 76686d6 (the arena builder's corners) set pointer-events at render
   time only, so a corner drawn with the wall tool stayed untappable until the next repaint, and
   no unit test caught it — only a real browser did. One class, applied once, cannot drift. */
.hub-tu-gate .hub > * { pointer-events: none; filter: grayscale(.7) brightness(.55); transition: filter .18s ease; }
.hub-tu-gate .hub .tu-live,
.hub-tu-gate .hub .tu-live * { pointer-events: auto; filter: none; }
/* z-index ONLY. An earlier version also set `position: relative` here, which quietly destroyed the
   layout: the BAKED LOBBY LAYOUT positions every hub box ABSOLUTELY, so overriding position sent
   the lit element back to its static flow position — the finale's ⚽ ended up at x=1457 in an
   844px viewport, off-screen, and the hand pointed faithfully at a button no kid could reach.
   The boxes are already positioned, so z-index alone does what this rule needs. */
.hub-tu-gate .hub .tu-live { z-index: 3; }
/* The coach's own overlay must stay above the lit target. */
.hub-tu-gate #tutorial { z-index: 40; }

/* The hub tour's own way out. The pitch levels dismiss with #leave-lobby-btn, which is a match
   control and is not on this screen; the gate makes everything else inert, so without this a kid
   who just wants to play is trapped on a lesson. Deliberately quiet — visible, not inviting. */
#tu-hub-skip {
  position: fixed; top: max(10px, env(safe-area-inset-top)); inset-inline-end: max(10px, env(safe-area-inset-right));
  z-index: 41; pointer-events: auto;
  font: 700 12px/1 system-ui, sans-serif; color: #dfe6ee;
  background: rgba(12, 16, 22, .72); border: 1px solid rgba(223, 230, 238, .28);
  border-radius: 999px; padding: 7px 12px; cursor: pointer;
}
#tu-hub-skip:active { transform: scale(.96); }
#tu-hub-skip.hidden { display: none; }

/* ===== LEVEL 4's MOCK LOBBY ========================================================
   A lobby the tutorial draws itself. It exists because the REAL hub proved to be a moving
   target: the wardrobe is an overlay not a screen, the carousel auto-rotates (and completed a
   step by itself), effectiveLoadout() pre-filled the slots, and three agents change this hub
   daily. A mock cannot drift out from under the lesson — and with no real setSlotCard in the
   loop, a lesson card can never reach a kid's actual cross-device loadout.
   Deliberately reads as a SIMPLIFIED hub, not a pixel copy: fewer things on screen is the whole
   point of a first lesson, and a near-copy that is subtly wrong is worse than an honest sketch. */
#tu-mock {
  position: fixed; inset: 0; z-index: 38;
  /* Content sits BELOW the caption band, not centred behind it. The coach's caption is anchored to
     the top (right for a pitch, where the action is underneath) and at full size it landed square
     on the trophy bar this very step is pointing at — the word covering the thing it names. */
  display: grid; align-items: start; justify-items: center;
  background: radial-gradient(120% 90% at 50% 0%, #1d2b3a 0%, #0e141c 70%);
  padding: max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-right)) max(8px, env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-left));
}
#tu-mock.hidden { display: none; }
.tum-stage {
  width: min(100%, 720px); display: flex; flex-direction: column; gap: 8px;
  padding-top: clamp(132px, 38vh, 156px);   /* clear of the caption band AND its step pips */
}
.tum-top { display: flex; align-items: center; gap: 10px; }

.tum-trophies {
  flex: 1; display: flex; align-items: center; gap: 6px;
  background: rgba(255, 255, 255, .06); border: 1px solid rgba(255, 208, 106, .35);
  border-radius: 10px; padding: 7px 10px; color: #ffd06a;
  font: 700 13px/1 system-ui, sans-serif;
}
.tum-tro-n { font-size: 15px; }
.tum-tro-l { color: #cfd8e3; font-weight: 600; font-size: 11px; }
.tum-tro-bar { flex: 1; height: 6px; background: rgba(255, 255, 255, .12); border-radius: 99px; overflow: hidden; }
.tum-tro-bar b { display: block; width: 62%; height: 100%; background: #ffd06a; }

.tum-side, .tum-hero {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  background: rgba(255, 255, 255, .07); border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 12px; padding: 8px 12px; color: #eaf0f7; cursor: pointer;
  font: 700 10px/1 system-ui, sans-serif;
}
.tum-side span, .tum-hero-ic { font-size: 20px; }
.tum-hero { padding: 10px 16px; }
.tum-hero-ic { font-size: 32px; }

.tum-mid { display: flex; align-items: center; gap: 12px; }

.tum-friendrow { display: flex; gap: 10px; justify-content: center; color: #cfd8e3; font: 600 11px/1 system-ui, sans-serif; }
.tum-friendrow.hidden { display: none; }

/* The coach and its skip button ride ABOVE the mock. */
#tutorial { z-index: 40; }
/* A pitch caption is shouted across a stadium; a lobby caption sits on top of the furniture it is
   naming, so it is sized down for the hub tour rather than left at pitch scale. */
.hub-tu-gate .tu-caption b { font-size: clamp(20px, 4.4vw, 32px); text-shadow: 0 3px 0 #a56a16, 0 6px 0 #252a21, 0 8px 18px rgba(0,0,0,.55); }

/* ---- SEASON BANNER (over the mode picker) ---------------------------------------------------
   The card language one notch wider: same block border and hard drop shadow as .pcard, but a
   landscape strip so the pixel scene reads as a PLACE rather than an icon. RTL like everything on
   this page — the art sits at the start edge (right), the text runs from it. */
.season-banner {
  width: 100%; max-width: 520px; direction: rtl;
  display: flex; align-items: stretch; gap: 0; overflow: hidden;
  background: rgba(19,27,22,.94); border: 3px solid #46543f; box-shadow: 0 5px 0 #070b08;
  /* TWO THINGS THIS RULE MUST DO, both learned by measuring the laid-out box (_season-ui-verify.mjs):
     flex:none  — .subpage-body is a COLUMN flex and the card list below wants all of it. Without
                  this the banner is a shrink candidate and lands at 6px tall: correct text, painted
                  canvas, invisible on screen.
     height     — explicit, because .sb-cv asks for height:100%. Against an auto-height parent that
                  resolves to 0, so the art measured 92x0 with every other assertion passing. */
  flex: none; height: 52px;
}
.season-banner:empty { display: none; }   /* before the first paint, take up no space at all */
/* ASPECT-LOCKED, not a fixed width: the canvas is 96x32 and a hand-picked px width stretches the
   scene sideways at one banner height and squashes it at another (the 92x36 the verifier caught).
   Deriving width from height keeps every block square on every phone. */
.season-banner .sb-art { flex: none; height: 100%; aspect-ratio: 96 / 32; background: #16221a; }
/* Same trick as .pc-cv: a 96x28 canvas blown up with no smoothing, so each source pixel is a block. */
.season-banner .sb-cv {
  display: block; width: 100%; height: 100%;
  image-rendering: pixelated; image-rendering: crisp-edges;
}
.season-banner .sb-tx { min-width: 0; flex: 1; padding: 8px 10px; align-self: center; text-align: right; }
.season-banner .sb-tx b {
  display: block; font: 900 14px "Arial Black", sans-serif; color: #fff4ca; line-height: 1.1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.season-banner .sb-tx small {
  display: block; margin-top: 3px; font: 700 10px sans-serif; color: #cfe3c4; opacity: .82;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A short landscape phone gives the picker very little height, and the cards are the point of the
   page — the banner gives its art back first rather than pushing a card row off the fold. */
@media (max-height: 430px) {
  .season-banner { height: 42px; }
  .season-banner .sb-tx { padding: 5px 8px; }
  .season-banner .sb-tx b { font-size: 12px; }
}

/* ---- SEASON SCREEN (#season) — the rotation, days ahead x minigames -------------------------
   Same block language as .pcard and .season-banner: hard 3px border, no radius, flat drop. The
   only colour on this page belongs to the WORLDS, so everything around them stays quiet. */
/* VERTICAL BUDGET. #season is read on a 390px-tall landscape phone, and the grid is five rows plus
   a header — measured at 7px cell padding it ate the whole fold and clipped the note off the bottom
   (_season-ui-verify.mjs now asserts the fit). Everything here is sized to leave the note and the
   dev bar on screen without scrolling. */
#season-page { width: 100%; max-width: 620px; display: flex; flex-direction: column; gap: 6px; }
.sr-head { display: flex; flex-direction: column; gap: 2px; text-align: right; }
.sr-head b { font: 900 15px/1.15 "Arial Black", sans-serif; color: #fff4ca; }
.sr-head small { font: 700 11px/1 sans-serif; color: #cfe3c4; opacity: .8; }
.sr-empty, .sr-note { margin: 0; text-align: right; font: 400 11px/1.4 sans-serif; color: #cfe3c4; opacity: .8; }

/* The table can outgrow a narrow phone; it scrolls in its own box so the page never does. */
.sr-wrap { overflow-x: auto; border: 3px solid #46543f; background: rgba(19,27,22,.94); box-shadow: 0 5px 0 #070b08; }
table.sr-grid { border-collapse: collapse; width: 100%; min-width: 460px; direction: rtl; }
.sr-grid th, .sr-grid td { padding: 3px 8px; text-align: right; vertical-align: middle; }
.sr-grid thead th { border-bottom: 2px solid #46543f; background: rgba(0,0,0,.22); }
.sr-mode { display: block; font: 900 11px/1.15 "Arial Black", sans-serif; color: #fff4ca; }
.sr-arena { display: block; margin-top: 2px; font: 700 9px/1.2 sans-serif; color: #cfe3c4; opacity: .75; }
.sr-grid tbody tr { border-bottom: 1px solid rgba(70,84,63,.6); }
.sr-grid tbody tr:last-child { border-bottom: 0; }
/* Today is the row the mode cards are already showing — it is the anchor for reading the rest. */
.sr-grid tbody tr.sr-today { background: rgba(255,203,67,.10); }
.sr-grid tbody tr.sr-today .sr-day b { color: var(--gold, #ffcb43); }
th.sr-day { border-inline-end: 1px solid rgba(70,84,63,.6); white-space: nowrap; width: 62px; }
th.sr-day b { display: block; font: 900 11px/1.15 "Arial Black", sans-serif; color: #fff4ca; }
th.sr-day small { display: block; margin-top: 2px; font: 700 9px/1 sans-serif; color: #cfe3c4; opacity: .7; }
/* The chip is the pitch's own four tones (field-surface.js SURFACES[id].tile) as a 2x2 of fat
   pixels — the same trick as the pixel art, so a world reads as itself at 18px. */
.sr-tile {
  display: inline-grid; grid-template: repeat(2, 1fr) / repeat(2, 1fr);
  width: 18px; height: 18px; vertical-align: middle; margin-inline-start: 6px;
  border: 1px solid rgba(0,0,0,.5);
}
.sr-tile i { display: block; }
.sr-world { font: 700 11px/1 sans-serif; color: #e9f0e3; vertical-align: middle; }

/* DEV BAR — LAN/localhost only, and the server ignores the message anywhere else. Deliberately
   plain and slightly ugly: it must never be mistaken for a player-facing control. */
.sr-dev {
  display: flex; flex-wrap: wrap; align-items: center; gap: 5px;
  padding: 5px 6px; border: 2px dashed #6a7a5f; background: rgba(0,0,0,.25);
}
.sr-dev-l { font: 700 9px/1 monospace; letter-spacing: .08em; color: #9db08f; }
.sr-dev-warn { font: 700 9px/1.3 monospace; color: #ffcb43; }
.sr-dev-b {
  appearance: none; cursor: pointer; padding: 4px 8px;
  font: 700 11px/1 sans-serif; color: #e9f0e3;
  background: rgba(19,27,22,.94); border: 2px solid #46543f;
}
.sr-dev-b:active { transform: translateY(1px); }
.sr-dev-b:focus-visible { outline: 2px solid #ffcb43; outline-offset: 1px; }
