/* ==== fonts.css ==== */
/* fonts.css — Chakra Petch, SELF-HOSTED. Generated from the Google Fonts CSS, then pointed at public/fonts/.
 *
 * ⚠ WHY IT IS NOT A <link> TO fonts.googleapis.com ANY MORE. That stylesheet is RENDER-BLOCKING and lives on
 * someone else's server: the browser cannot paint the page until it arrives, so every player waited on a third
 * party before seeing anything — and where that host is slow or blocked, they waited a long time. The usual
 * workaround (media="print" onload="this.media='all'") is an inline event handler, which this site's own CSP
 * forbids, so the real fix is to stop making the request at all. Removing it also drops two DNS lookups, two TLS
 * handshakes and the only third party that could see a request from every player.
 *
 * `unicode-range` is kept exactly as Google generated it, and it is the reason shipping all four subsets (latin,
 * latin-ext, thai, vietnamese) costs nothing: a browser downloads only the files for the characters it is actually
 * asked to draw. Measured on the lobby: TWO files, 19 KB — not the 117 KB on disk.
 *
 * The face is genuinely in use, which is worth recording because most of the references look conditional:
 * announce/chat/clan/party/profile ask for it as `var(--site-ui-font, 'Chakra Petch')`, and --site-ui-font is set
 * to Segoe UI only under html.lobby-active — so in a MATCH the fallback is what renders. game.css, lobby.css,
 * tutorial.css and devlog.css name it outright.
 *
 * `font-display: swap` keeps text visible in a fallback face while the woff2 loads, so the font can never hide the
 * interface. Same setting the &display=swap parameter used to request.
 *
 * LICENCE: Chakra Petch is SIL Open Font License 1.1 (Cadson Demak). OFL permits redistribution and self-hosting
 * provided the licence travels with the font: see public/fonts/OFL.txt. The font is not sold and is not renamed.
 */
/* thai */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-400-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
/* vietnamese */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-400-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* thai */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-500-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
/* vietnamese */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-500-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* thai */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-600-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
/* vietnamese */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-600-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-600-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* thai */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-700-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
/* vietnamese */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-700-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-700-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/public/fonts/chakrapetch-700-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


/* ==== game.css ==== */
  :root { --bg:#0e1014; --ink:#e6e8ec; --muted:#9aa3b2; --blue:#2e5e8c; --green:#2c7a2c; }
  * { box-sizing:border-box; }
  html,body { margin:0; height:100%; background:#7a7d80; color:var(--ink); font:14px/1.4 system-ui,Segoe UI,sans-serif; overflow:hidden; user-select:none; -webkit-tap-highlight-color:transparent; }   /* -webkit-tap-highlight is mouse-inert -> desktop-safe; pull-to-refresh suppression is under html.is-touch (keeps desktop trackpad swipe-back) */

  /* ---------- LOBBY + MULTIPLAYER HUB + CLAN PAGE ---------- styled in public/styles/lobby.css and clanpage.css
     (the old #mpModal pop-up was replaced by the inline FFA/Teams hub; the clan modal by the full-screen #clanPage). */

  /* ---------- GAME ---------- */
  #game { position:fixed; inset:0; }
  canvas { position:absolute; inset:0; width:100%; height:100%; image-rendering:pixelated; background:#0a0b0e; cursor:crosshair; touch-action:none; -webkit-touch-callout:none; -webkit-user-select:none; }   /* no long-press callout on the map */

  .panel { position:absolute; background:#000a; border:1px solid #2a2e37; color:var(--ink); }

  /* leaderboard (left) */
  #lb { top:10px; left:10px; width:228px; border-radius:4px; overflow:hidden; }
  #lb .hd { background:var(--blue); padding:6px 11px; font:800 13px system-ui; letter-spacing:.5px; }
  /* About ten desktop rows; all remaining ranks scroll inside the same panel. */
  #lbRows { max-height:min(240px, 55vh); overflow-y:auto; overflow-x:hidden; overscroll-behavior:contain; touch-action:pan-y; scrollbar-width:none; }
  #lbRows::-webkit-scrollbar { display:none; }
  #lb .row.pinned-self { position:sticky; bottom:0; z-index:1; box-shadow:0 -1px #456145; }
  #lb .row { display:flex; align-items:center; gap:6px; padding:3px 10px; font-size:12px; border-top:1px solid #ffffff10; cursor:pointer; }
  #lb .row:hover { background:#ffffff14; }
  #lb .row.me { background:#1f5e1f; }
  #lb .row.me:hover { background:#256e25; }
  #lb .row .rk { min-width:19px; color:var(--muted); flex:none; }   /* min-width so 4-digit ranks (1000.) don't clip the swatch */
  #lb .row .nm { flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  #lb .row .sw { width:9px; height:9px; border-radius:2px; flex:none; }
  /* chosen unit flag — the SAME art the battle-front banner draws, and therefore the SAME SHAPE: 3:2, filled.
     It used to be max-width/max-height, which keeps each PNG's own aspect — so a square flag like JP was a circle
     here and an ellipse on the map, i.e. one flag with two appearances on the same screen. The banner is the truth;
     every other surface follows it. The box renders even when empty so flagless players' names do not shift left. */
  #lb .row .fl { width:15px; height:10px; flex:none; display:flex; align-items:center; justify-content:center; }
  #lb .row .fl img { width:100%; height:100%; object-fit:fill; display:block; border-radius:1px; }
  #lb .row .val { font-variant-numeric:tabular-nums; }
  /* ALL / CLAN / TEAM filter tabs across the bottom (shown/hidden + driven by main.mjs) */
  #lbTabs { display:none; border-top:1px solid #ffffff1c; }
  #lbTabs button { flex:1; background:#0e1726; border:none; color:#8ea6c4; font:800 13px system-ui; letter-spacing:.06em; padding:9px 4px; cursor:pointer; border-right:1px solid #ffffff14; }
  #lbTabs button:last-child { border-right:none; }
  #lbTabs button:hover { background:#16233a; color:#cdd9ea; }
  #lbTabs button.on { background:var(--blue); color:#fff; }

  /* stats (top-right) */
  #stats { top:10px; right:10px; min-width:210px; border-radius:4px; padding:8px 11px; font:13px/1.55 ui-monospace,monospace; }
  #stats .r { display:flex; justify-content:space-between; gap:18px; }
  #stats .r b { font-weight:700; }
  #stats #stRowMax, #stats #stRowDen { display:none; }
  #stats #stRowMax.tut-spot, #stats #stRowDen.tut-spot { display:flex; }
  #stats .r .red { color:#e2624a; }
  /* THE AUDIT KEY, and it is read and copied rather than glanced at. Monospace so a base64url id can be
     transcribed without confusing l/1/I, dimmer than the board above it because it is never part of playing, and
     selectable — typing "21-We9knKxgW1kF7ee6" by hand into a log search is exactly how the wrong match gets
     audited. It is allowed to wrap: truncating an identifier makes it useless.
     It sits in the LEADERBOARD because that panel survives being eliminated, while #stats does not — see the
     comment beside it in index.html. */
  #lbMatch { display:flex; align-items:baseline; gap:8px; margin-top:6px; padding-top:6px;
             border-top:1px solid rgba(255,255,255,.10); }
  #lbMatch span { font:700 9px/1 system-ui; letter-spacing:.6px; color:#7d8696; flex:0 0 auto; }
  #lbMatch b { font:600 10px/1.35 ui-monospace,SFMono-Regular,Menlo,monospace; color:#9aa3b2; font-weight:600;
               user-select:text; -webkit-user-select:text; word-break:break-all; }
  #stats .bar { height:5px; background:#333a; border-radius:3px; margin-top:4px; overflow:hidden; }
  #stats .bar>div { height:100%; background:#5bd35b; width:0%; }

  /* balance bar (top-center) */
  #balbar { top:10px; left:50%; transform:translateX(-50%); display:flex; align-items:center; background:var(--green); border:2px solid #1c4d1c; border-radius:6px; padding:6px 14px; font:800 26px ui-monospace,monospace; gap:10px; }
  #balbar.red { background:#a83232; border-color:#5a1818; }   /* peak income reached -> expand/attack */
  /* transient feedback after sending a clan signal */
  #sigToast { position:absolute; bottom:120px; left:50%; transform:translateX(-50%) translateY(8px); background:#16203aee; border:1px solid #3f56a0;
    color:#fff; padding:8px 16px; border-radius:20px; font:700 13px system-ui; z-index:22; opacity:0; pointer-events:none; transition:opacity .2s, transform .2s; white-space:nowrap; }
  #sigToast.show { opacity:1; transform:translateX(-50%) translateY(0); }
  #sigToast.warn { background:#3a1d1dee; border-color:#a83232; }
  /* bottom-RIGHT toast: troops a teammate donated to me */
  #donateToast { position:absolute; right:14px; bottom:120px; z-index:42; max-width:60vw; padding:10px 16px; border-radius:10px;
    background:#13241aee; border:1px solid #2f6f4f; color:#bff0d8; font:800 14px system-ui; box-shadow:0 4px 16px #000a;
    opacity:0; transform:translateY(8px); transition:opacity .2s, transform .2s; pointer-events:none; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  #donateToast.show { opacity:1; transform:translateY(0); }
  #emojiToast { position:absolute; right:14px; bottom:172px; z-index:42; max-width:60vw; padding:10px 16px; border-radius:10px;   /* sits above #donateToast (120px) so a private emoji + a donation can show at once */
    background:#1d1830ee; border:1px solid #6a51a0; color:#e2d4ff; font:800 16px system-ui; box-shadow:0 4px 16px #000a;
    opacity:0; transform:translateY(8px); transition:opacity .2s, transform .2s; pointer-events:none; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  #emojiToast.show { opacity:1; transform:translateY(0); }
  /* ---- the three lines on the troop box (territorial.io's widget cO) --------------------------------------
     Thickness follows theirs: 1 + 0.13 * box height. The colour is one light tone for all three, as theirs uses a
     single line colour, and it has to read on BOTH box states (green, and red at peak income). */
  /* ⚠ NO `position` HERE. .panel above is already position:absolute, which is both what centres this pill and what
     makes it a containing block for the three lines. Writing `position:relative` overrode the absolute and dropped
     the box back into normal flow, where a flex container fills the line — it stretched across the whole top of
     the screen. The lines needed a containing block; they already had one. */
  #balbar { --bal-line:4px; --cyc:0; --peak:0; overflow:hidden; }
  #balbar .cyc, #balbar .peak { position:absolute; background:rgba(255,255,255,.78); pointer-events:none; }
  /* the sides GROW UPWARD from the bottom: a stub of exactly one line-thickness at the start of the cycle, the
     full height by the end. Same shape as theirs — height = t + (k - t) * step/9. */
  #balbar .cyc { bottom:0; width:var(--bal-line); height:calc(var(--bal-line) + (100% - var(--bal-line)) * var(--cyc)); }
  #balbar .cyc.l { left:0; }
  #balbar .cyc.r { right:0; }
  /* the bottom line is inset by the two side lines, exactly as theirs is (it starts at aAv and is j-2*aAv wide) */
  #balbar .peak { left:var(--bal-line); bottom:0; height:var(--bal-line);
    width:calc((100% - 2 * var(--bal-line)) * var(--peak)); }
  #balbar .rk { font-size:15px; opacity:.85; }
  #balbar .inc { font-size:15px; color:#bfe6bf; }

  /* attack ratio bar (bottom-center) */
  /* ---- THE ATTACK SLIDER, and the rules that divide it -------------------------------------------------------
     ⚠ THE LINES ARE FUNCTIONAL, NOT DECORATION, and they are territorial.io's (t-src.js, the cL widget's a9W draw
     pass). Theirs paints, in one line colour, a 1px frame plus THREE vertical rules: at the inner edge of the "−"
     zone, at the inner edge of the "+" zone, and at the FILL EDGE. The first two say where the two buttons stop
     being buttons and the track starts — the whole strip is one control, so without them a player cannot see that
     the ends are pressable and the middle is draggable. The third is the read-out: it marks the exact ratio, which
     a soft gradient edge does not. Ours had none of the three, which is what this restores.
     (Their end zones are 1.5x the widget height and the widget is 12:1; ours are 46px on a 44px bar, close enough
     that matching the proportion exactly would move the control for no gain. The RULES are the ask.) */
  #atk { --atk-rule:rgba(255,255,255,.55); bottom:14px; left:50%; transform:translateX(-50%); display:flex; align-items:center; gap:0; background:#0c0d10; border:1px solid #2a2e37; border-radius:8px; overflow:hidden; }
  #atk button { background:#11141a; color:#fff; border:none; width:46px; height:44px; font-size:22px; cursor:pointer; }
  #atk button:hover { background:#1a1f28; }
  /* ⚠ THE DIVIDERS ARE PSEUDO-ELEMENTS ON THE TRACK, not shadows on the buttons. The buttons are unpositioned flex
     items and .track is position:relative, so anything the buttons paint at their inner edge lands UNDERNEATH the
     track's own background — measured: both rules were invisible while the fill-edge rule (a positioned child) was
     not. Painting them from inside the track puts them in the same layer as the fill. */
  #atk .track::before, #atk .track::after { content:""; position:absolute; top:0; bottom:0; width:1px;
    background:var(--atk-rule); z-index:2; pointer-events:none; }
  #atk .track::before { left:0; }
  #atk .track::after  { right:0; }
  #atk .track { position:relative; width:300px; height:44px; cursor:pointer; background:#10141b; }
  /* ⚠ box-shadow, not border-right: the fill's width IS the value, and a border would add a pixel to it. */
  #atk .fill { position:absolute; inset:0 auto 0 0; background:linear-gradient(90deg,#1f4a6e,#2f7fb0); width:30%;
               box-shadow:1px 0 0 var(--atk-rule); }
  #atk .lab { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; font:700 16px ui-monospace,monospace; }

  /* zoom buttons (right) */
  #zoom { position:absolute; right:18px; top:50%; transform:translateY(-50%); background:none; border:none; display:flex; flex-direction:column; gap:14px; z-index:16; }
  #zoom button { width:54px; height:54px; border-radius:50%; background:#0009; border:1px solid #2a2e37; color:#fff; font-size:26px; cursor:pointer; }
  #zoom button:hover { background:#000c; }

  /* cursor info (bottom-right) */
  #cur { bottom:14px; right:14px; padding:5px 10px; border-radius:4px; font:13px ui-monospace,monospace; }

  /* leave button (bottom-left) */
  #leaveBtn { position:absolute; bottom:14px; left:14px; background:#0009; border:1px solid #2a2e37; color:#fff; border-radius:6px; padding:11px 16px; min-height:44px; font:600 13px system-ui; cursor:pointer; z-index:20; }
  #leaveBtn:hover { background:#a83232; border-color:#5a1818; }

  /* clan signal toggle (sits above Leave; shown only in multiplayer while in a clan) */
  #sigBtn { position:absolute; bottom:70px; left:14px; width:50px; height:50px; border-radius:50%; background:#0e1626e6; border:1px solid #2a3346; color:#cdd9ea; cursor:pointer; z-index:20; display:none; align-items:center; justify-content:center; }
  #sigBtn:hover { background:#000c; }
  #sigBtn.on { background:#2c6ed5; border-color:#7ab0ff; color:#fff; box-shadow:0 0 12px #2c6ed5aa; }
  /* clan-signal type picker (the "ping wheel"): a small toolbar right of the signal button; shown only while signal mode is on */
  #sigPicker { position:absolute; bottom:72px; left:74px; z-index:21; display:none; gap:6px; padding:6px 8px;
    background:#0e1322ee; border:1px solid #3a4570; border-radius:12px; box-shadow:0 6px 22px #000b; }
  #sigPicker.show { display:flex; }
  #sigPicker button { width:44px; height:44px; border-radius:10px; border:1px solid #2b3354; background:#161d33; cursor:pointer;
    display:flex; align-items:center; justify-content:center; padding:0; transition:transform .08s, background .12s; }
  #sigPicker button:hover { background:#222c4d; transform:translateY(-2px); }
  #sigPicker button.sel { border-color:#7ab0ff; box-shadow:0 0 10px #2c6ed5aa; background:#223056; }
  #sigPicker button .ic { width:23px; height:23px; }
  .banner{ position:absolute; top:64px; left:50%; transform:translateX(-50%); background:#000b; padding:10px 18px; border-radius:10px; font-size:16px; display:none; z-index:20; }

  /* spawn-selection window (P4 #3): a slim top banner with a live countdown + Start; the map underneath is clickable to place the spawn */
  /* THE SPAWN PROMPT, territorial.io's shape rather than ours. Theirs is a line in the corner ticker with a thin
     unlabelled progress bar on a panel edge — peripheral, unhurried, no digits. Ours was a 340px card pinned
     top-centre with a 22px seconds counter that turned red at 5, which reads as a countdown you are losing.
     Same information, opposite pressure. */
  #spawnBar{ position:absolute; right:14px; bottom:14px; width:min(300px,60%); background:#0e1626cc; border:1px solid #2a3346;
             border-radius:8px; padding:8px 12px 9px; z-index:40; box-shadow:0 6px 20px #0008; }
  #spawnBar[hidden]{ display:none; }
  #spawnBar .sp-hint{ color:var(--ink); font-size:13px; font-weight:600; letter-spacing:.2px; margin:0 0 7px; }
  #spawnBar .sp-track{ height:3px; background:#ffffff1f; border-radius:2px; overflow:hidden; }
  #spawnBar .sp-fill{ height:100%; width:0%; background:#5cc8ff; border-radius:2px; }
  #netStatus{ position:absolute; top:110px; left:50%; transform:translateX(-50%); background:#a83232; color:#fff; padding:8px 16px; border-radius:8px; font:600 14px system-ui; display:none; z-index:30; }
  /* attack forces in flight, stacked below the balance bar (max 5) */
  #atkForce{ position:absolute; top:62px; left:50%; transform:translateX(-50%); display:none; flex-direction:column; gap:6px; align-items:center; z-index:36; }
  /* OUR colours: gray pill with a red ✕ (left) and green + (right) end-cap; troop number centred with breathing room.
     62px top leaves a clean gap below the balance bar. */
  #atkForce .row{ display:flex; align-items:stretch; height:32px; overflow:hidden; border:2px solid #3a3f4a; border-radius:7px;
    background:linear-gradient(#6e7686,#565d6b); box-shadow:0 2px 6px #0007; color:#fff; font:800 18px ui-monospace,monospace; white-space:nowrap; }
  #atkForce .row.boat{ background:linear-gradient(#5a78aa,#3f5680); border-color:#27344c; }
  #atkForce .row .amt{ display:flex; align-items:center; justify-content:center; padding:0 18px; letter-spacing:.5px; }   /* breathing room around the number */
  #atkForce .row .cx, #atkForce .row .pl{ width:30px; display:flex; align-items:center; justify-content:center; cursor:pointer;
    font:900 19px system-ui; line-height:1; user-select:none; transition:filter .12s; }
  #atkForce .row .cx{ background:#bb3a2c; }   /* red ✕ — left */
  #atkForce .row .pl{ background:#2f8a2f; }   /* green + — right */
  #atkForce .row .cx:hover, #atkForce .row .pl:hover{ filter:brightness(1.18); }
  #atkForce .row .cx:active, #atkForce .row .pl:active{ filter:brightness(.86); }
  #atkForce .row.more{ height:auto; padding:2px 12px; justify-content:center; background:#2b2f38d0; border-color:#222; font-weight:600; font-size:13px; }
  #boatTip{ position:fixed; transform:translate(14px,14px); background:#000d; color:#fff; padding:4px 9px; border-radius:5px; font:600 13px ui-monospace,monospace; pointer-events:none; z-index:44; display:none; }
  /* click feedback: a brief expanding ring in the action's colour (no cartoon emoji) */
  .clickFx{ position:fixed; transform:translate(-50%,-50%); width:14px; height:14px; border-radius:50%; border:2.5px solid currentColor; pointer-events:none; z-index:45; animation:clickPop .5s ease-out forwards; }
  @keyframes clickPop{ 0%{opacity:.9; transform:translate(-50%,-50%) scale(.4);} 100%{opacity:0; transform:translate(-50%,-50%) scale(2.6);} }
  /* click action chooser: only what a click can't express (boat / donate / react). The clicked POINT is left clear —
     the boat offsets upward — because the second click of a double-click (= attack) has to reach the map. */
  #actMenu{ position:fixed; display:none; z-index:46; }
  #actMenu.show{ display:block; }
  /* shared monochrome line icons — tinted via the button's `color` (currentColor) */
  .ic { width:22px; height:22px; display:block; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
  .ic-sm { width:15px; height:15px; display:inline-block; vertical-align:-3px; }
  /* traced-silhouette icons (#i-sword, #i-ally): the artwork already contains its outline, so it is FILLED and never
     stroked — stroking a traced outline draws every edge twice and turns to mush at button size. They also need a
     slightly bigger box than a line icon: a filled mark reads smaller at the same width, and the handshake's negative
     space is what makes it legible. */
  .ic.ic-solid { fill:currentColor; stroke:none; }
  /* colour art (#i-emoji): each layer brings its own fill, so the only thing to undo is the inherited stroke — a
     2px outline in the button's colour around every layer is what `.ic` would otherwise give it. */
  .ic.ic-art { stroke:none; }
  #actMenu button{ position:absolute; left:0; top:0; width:50px; height:50px; border-radius:50%; border:1px solid #2a3346; background:#0e1626e6; color:#cdd9ea; cursor:pointer; display:flex; align-items:center; justify-content:center; box-shadow:0 3px 10px #000a; }
  #actMenu button:hover{ background:#16233a; border-color:#3a4762; }
  #actMenu button .ic{ width:23px; height:23px; }
  #actMenu button .ic-solid, #actMenu button .ic-art{ width:26px; height:26px; }
  /* no .atk rule: there is no attack icon — attacking is a click (single when it's the only action, double always) */
  #actMenu .atk{ color:#e2624a; }   /* the one warm colour in the cluster: it is the only destructive thing in it */
  #actMenu .boat{ color:#5b9bd3; } #actMenu .donate{ color:#7fd1a8; }   /* intent read via a restrained tint, not a cartoon fill */
  #actMenu .emoji{ color:#b9c3d1; }   /* …except this one: #i-emoji is colour art by operator direction, so the tint reaches only the focus ring */
  #actMenu #actDonateLbl{ position:absolute; left:0; top:0; display:none; font:800 12px ui-monospace,monospace; color:#bff0d8; background:#000d; padding:2px 8px; border-radius:9px; white-space:nowrap; pointer-events:none; text-shadow:0 1px 2px #000; }   /* live post-tax donate preview under the 💰 */
  /* emoji picker: a centered popup bar of emoji buttons; opens on tapping YOUR land (broadcast) or a player's 😀 (private) */
  /* ⚠ --px IS THE HORIZONTAL CENTRING, as a variable, because the picker has two modes now. Unanchored it stays
     pinned to the bottom centre (left:50% + a -50% pull); opened from the action menu, main.mjs sets left/top to the
     button it came from and --px to 0, having done the centring itself with real numbers so it can also clamp to the
     viewport. One rule either way, so the slide-in is written once rather than once per mode. */
  #emojiPicker{ position:fixed; left:50%; bottom:120px; --px:-50%; transform:translate(var(--px),10px); z-index:48; display:none; flex-wrap:wrap; justify-content:center; gap:6px; padding:8px 10px;
    width:max-content; max-width:min(440px,92vw); background:#0e1322ee; border:1px solid #3a4570; border-radius:14px; box-shadow:0 6px 22px #000b; opacity:0; transition:opacity .15s, transform .15s; }
  #emojiPicker.show{ display:flex; opacity:1; transform:translate(var(--px),0); }
  #emojiPicker{ box-sizing:border-box; max-height:calc(100dvh - 24px); overflow-y:auto; overscroll-behavior:contain; }
  #emojiPicker button{ width:46px; height:46px; border-radius:11px; border:1px solid #2b3354; background:#161d33; font-size:26px; line-height:1; cursor:pointer; padding:0; transition:transform .08s, background .12s; }
  #emojiPicker button:hover{ background:#222c4d; transform:translateY(-2px) scale(1.08); }
  #emojiPicker .em-art{ padding:4px; }                    /* an ARROW reaction: art, not a character — it fills the button instead of sitting on a text baseline */
  #emojiPicker .em-art .ic{ width:100%; height:100%; }
  #emojiPicker .em-clanlogo{ overflow:hidden; box-shadow:0 0 0 2px #5cc8ff55; }   /* the clan-logo reaction stands out from the glyphs */
  #emojiPicker .em-clanlogo img{ width:100%; height:100%; object-fit:cover; display:block; }

  /* ---------- leave menu: the bottom-left button opens a small stack (Leave match + Call peace vote) growing UPWARD ---------- */
  #leaveMenu{ position:absolute; left:14px; bottom:62px; z-index:30; display:flex; flex-direction:column; min-width:200px;
    background:#0c0d10; border:1px solid #2a2e37; border-radius:8px; overflow:hidden; box-shadow:0 6px 20px #000a; }
  #leaveMenu[hidden]{ display:none; }
  #leaveMenu button{ background:transparent; color:#fff; border:none; border-bottom:1px solid #1c2027; padding:11px 14px;
    min-height:44px; text-align:left; font:600 13px system-ui; cursor:pointer; }
  #leaveMenu button:last-child{ border-bottom:none; }
  #lmLeave:hover{ background:#a83232; }
  #lmCall:hover{ background:#1a1f28; }
  #lmCall:disabled{ color:#5d6672; cursor:not-allowed; }              /* a vote you may not call yet — the reason is PRINTED, see #lmWhy */
  /* the reason, in the menu itself. A tooltip is a hover affordance and a phone has no hover, so on touch the
     disabled item used to explain nothing whatsoever. */
  #lmCall{ display:flex; flex-wrap:wrap; align-items:center; gap:6px; text-align:left; }
  #lmWhy{ flex:1 0 100%; font:400 10.5px system-ui; font-style:normal; color:#7f8b99; letter-spacing:.1px; }
  #lmWhy[hidden]{ display:none; }
  #lmCall:disabled:hover{ background:transparent; }

  /* ---------- THE DECISION DOCK: one anchor for the two in-match panels that ask the player a question (the
     alliance offer and the peace vote). See index.html for why it exists.
     ⚠ IT NOW OWNS THE DESKTOP PLACEMENT TOO, and that is the fix for the second report of "it looks stuck on".
     The dock used to be `display:contents` here, which left each panel positioning ITSELF: the offer at
     `bottom:196px`. 196 is not a relationship to anything — it was the height of the peace vote plus a gap, so
     with the vote hidden (the normal case) the offer hung in the middle of the right-hand side of the map, level
     with nothing, touching nothing. Measured on a 1440x900 desktop: 196px of empty map below it and the zoom
     cluster far above.
     One anchor instead: the dock sits just above the bottom HUD band, and whatever is open stacks UPWARD from
     there. A single panel is then flush with the band, which is what makes it read as part of the frame rather
     than as a card dropped on the map. `bottom:70px` is the attack bar's height plus a gap — the same clearance
     #peaceVote used, now stated once.
     THE ORDER: the peace vote is the time-critical personal decision and takes the slot nearest the bottom; the
     alliance offer sits above it. DOM order is offer-then-vote, so a plain column gives exactly that. ---------- */
  #decisionDock{ position:absolute; right:14px; bottom:70px; z-index:40; display:flex; flex-direction:column;
    align-items:flex-end; gap:8px; width:min(300px, calc(100vw - 28px)); pointer-events:none; }
  /* ⚠ THE DOCK IS A LAYOUT BOX, NOT A SURFACE — the gap between two panels is MAP, and a strip of dead pointer
     events across it is CLAUDE.md §4b rule 5 (this game has shipped that bug twice). The panels take the events
     back; the box itself never does. */
  #decisionDock > *{ position:static; right:auto; bottom:auto; width:100%; pointer-events:auto; }

  /* ---------- peace-vote panel (SP + MP). Three rows: what this vote DOES + who called it + the countdown; the track,
     whose fill is the agreed share of the land (a frozen snapshot) with a NOTCH at the target it must reach; the buttons.
     The target is drawn rather than hidden because it now SLIDES with the leading team's share of the map — an invisible
     moving goal is one a player cannot judge a vote against. BOTTOM-RIGHT — above #atk + #cur, clear of #zoom. ---------- */
  /* same ground and hairline as the offer it shares the dock with — two panels side by side in two different
     visual languages is the thing that read as pasted-on. Its track and buttons were already HUD-shaped. */
  /* no placement of its own any more — the dock above owns it (that is the whole point of the dock) */
  #peaceVote{ display:flex; flex-direction:column;
    gap:7px; background:#000a; border:1px solid #2a2e37; border-radius:4px; padding:9px 10px; }
  /* ⚠ !important, AND IT IS LOAD-BEARING. `hidden` is the ONLY thing that says whether a vote is running, and this
     rule is a bare id + attribute (1,1,0) — so the touch layout below, `html.is-touch #peaceVote` (1,1,1), quietly
     out-specified it and the panel rendered on every phone from the first second of the match: an empty vote with
     two buttons that did nothing, permanently over the map. Shipped by me and reported within the hour.
     A panel that the client has marked hidden must be gone whatever any later layout rule says, so the two decision
     panels state that once, here, instead of every future layout rule having to remember it. */
  #peaceVote[hidden], #pactBox[hidden]{ display:none !important; }

  /* ---------- ALLIANCE OFFER (#pactBox) — the same corner, ABOVE the peace vote, and never a modal.
     ⚠ IT SITS ABOVE #peaceVote ON PURPOSE. The vote is the time-critical personal decision and belongs nearest the
     thumb; an alliance offer can wait twenty seconds. Both are `position:absolute; right:14px`, so the offer takes
     the higher `bottom` and the two can never cover each other however the vote's height changes.
     ⚠ AND ITS BUTTONS IGNORE POINTER EVENTS FOR 400ms AFTER IT APPEARS. It arrives on the SERVER's clock, so the
     player can be mid double-click, mid pinch or in signal mode when it does — and a control that materialises
     under a finger already in motion is a binding alliance nobody chose. `.pb-armed` is added by the client once
     that window has passed; without the class the buttons are inert but visible, so nothing flickers. */
  /* ⚠ THE STYLE IS THE OTHER HALF OF "IT LOOKS STUCK ON". The panel used to be a #161a22ee card with a GOLD outline,
     a 10px radius and a drop shadow, in system-ui, with two large pill buttons. Every one of those is a decision the
     rest of this HUD does not make: the leaderboard, the stats panel and the leave menu are all the .panel ground
     (#000a over a 1px #2a2e37 hairline), a 4px radius, no shadow, monospace numerals, and a coloured HEAD BAR when
     they need a title. Reported as looking like functionality from a different game pasted on top, which is exactly
     what a second visual language inside one HUD is.
     So the offer is built like the leaderboard: full-bleed head bar in the alliance gold, hairline body, chip-sized
     buttons. The gold is the same ink the ally mark on the map uses, so the panel and the thing it creates match. */
  #pactBox{ display:flex; flex-direction:column;
    gap:0; background:#000a; border:1px solid #2a2e37; border-radius:4px; padding:0; overflow:hidden; }
  /* (its hidden rule is stated with the vote's, above — one place, one !important) */
  /* the head bar — the same construction as #lb .hd, in the alliance gold rather than the leaderboard blue.
     ⚠ AND AT THE LEADERBOARD'S WEIGHT, NOT AT FULL SATURATION. It was #c9a227 with near-black text: against a HUD
     whose loudest surface is a #2e5e8c bar, a saturated yellow band is the brightest thing on the screen — brighter
     than the player's own territory — and brightness is what "pasted on from another game" actually looks like when
     you measure it. Reported twice. Same hue, same construction, the leaderboard's luminance: the gold now names
     the alliance instead of shouting it, and the 3px top rule keeps the full-strength ink present as an accent, the
     way #lb .row.me uses green. */
  .pb-head{ display:flex; align-items:baseline; gap:7px; background:#6b5618; color:#f2e4bd; padding:5px 10px;
    border-top:3px solid #c9a227; font:800 12px system-ui; letter-spacing:.3px; }
  #pbWho{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  /* ⚠ THE ASKER'S NAME IS A CONTROL — main.mjs wires it to fitToPlayer, the same call a leaderboard row makes — so
     it has to LOOK like one. An underline in the head bar's OWN ink: no second colour, no button chrome, no pill.
     A third visual language inside this panel is the exact defect recorded above it, and the affordance a name
     needs is the one the web already spells with an underline.
     ⚠ AND IT IS INERT UNTIL .pb-armed, with the buttons. Same 400ms, same reason: the offer lands on the server's
     clock, and a camera that flies somewhere mid-fight was no more chosen than an alliance would have been. */
  #pbName{ cursor:pointer; pointer-events:none;
    text-decoration:underline; text-decoration-color:#f2e4bd66; text-underline-offset:2px; }
  #pactBox.pb-armed #pbName{ pointer-events:auto; }
  #pbName:hover{ color:#fff; text-decoration-color:#fff; }
  #pbClock{ flex:0 0 auto; font:700 11px ui-monospace,monospace; color:#f2e4bd; opacity:.72; }
  /* PROSE is system-ui and NUMBERS are monospace — that split is the rest of the HUD (#lb rows vs #lb .hd, #stats
     values vs its labels), and setting a sentence in monospace was the one thing here still speaking out of turn. */
  .pb-body{ padding:7px 10px 0; font:400 11px/1.45 system-ui; color:#8fa3b3; }
  .pb-btns{ display:flex; gap:6px; padding:8px; }
  /* chip-sized, like #atkChips and #lbTabs — not app-sized pills. Still inert until .pb-armed (see above). */
  .pb-btns button{ flex:1 1 0; min-height:30px; padding:0 8px; border-radius:4px; border:1px solid #2a2e37; background:#0c0d10cc; color:#cdd9ea;
    font:700 12px ui-monospace,monospace; cursor:pointer; pointer-events:none; opacity:.55; }
  #pactBox.pb-armed .pb-btns button{ pointer-events:auto; opacity:1; }
  /* the accept colour is the one this HUD already uses for YOU: #lb .row.me is #1f5e1f, hovering to #256e25 */
  /* ⚠ .pb-btns .pb-yes, not .pb-yes: the base rule above is (0,1,1) and would otherwise win the background back,
     which is how the accept button rendered as plain black with a green outline. */
  .pb-btns .pb-yes{ background:#1f5e1f; border-color:#2c7a2c; color:#fff; }
  .pb-btns .pb-yes:hover{ background:#256e25; }
  .pb-btns .pb-no:hover{ background:#1a1f28; }
  /* ⚠ NO PLACEMENT HERE ANY MORE — the dock owns it (see #decisionDock). The line that used to sit here read
     `bottom:calc(298px + env(safe-area-inset-bottom))`, which is the defect: 298px is most of a landscape phone.
     The explainer line stays hidden in LANDSCAPE only. It was hidden on every phone because the old placement
     left no room anywhere; in portrait the dock has room, and an alliance is the one offer in this game a player
     can accept without understanding what they just agreed to. */

  /* The alliance control in the action menu. It takes the HORIZONTAL axis, which nothing else uses: the boat and the
     😀 own above/below, donate owns the cursor point itself, and the clicked point must stay clear because the second
     click of a double-click — the gesture that commits a land attack — lands there. */
  #actPact{ border-color:#7a6a2a !important; }
  #actPact.pact-blocked{ opacity:.45; cursor:default; }
  /* THE DOOR IS OPEN: the control shows its own state, because the player who declared has no other way to tell.
     A ring rather than a fill — the icon still has to read as the same alliance icon it is when closed. */
  #actPact.pact-on{ border-color:#e2c84a; box-shadow:0 0 0 2px #e2c84a55, 0 3px 10px #000a; color:#e2c84a; }
  #actPact.pact-break{ border-color:#a83232 !important; color:#e6a3a3; }
  #actPact .pact-ring{ position:absolute; inset:-3px; border-radius:50%; border:2px solid #e0a23a; clip-path:inset(0 0 100% 0); pointer-events:none; }
  /* ⚠ A PLATE, LIKE THE DONATE PREVIEW, AND IT WRAPS. This was bare text with a shadow and `nowrap`: over a lit
     coastline an 11px string is close to invisible, and a long refusal ran off the side of the window instead of
     folding. It is the only thing that ever says WHY the alliance control is dimmed, so it has to survive any
     terrain under it. The width is FIXED so main.mjs can centre and clamp it without measuring the element. */
  #actPactLbl{ position:absolute; left:0; top:0; width:132px; margin-left:-66px; box-sizing:border-box; text-align:center;
    pointer-events:none; font:700 11px/1.3 system-ui; color:#e6dcb4; background:#000d; padding:3px 7px; border-radius:9px;
    text-shadow:0 1px 2px #000; }
  #peaceVote.pv-draw{ border-color:#c9a227; }                          /* a DRAW is a different outcome, so it is a different colour */
  .pv-head{ display:flex; align-items:baseline; gap:7px; font:700 12px system-ui; color:#dce6ee; }
  #pvTitle{ flex:0 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  #pvWho{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:500; color:#8fa3b3; }
  #pvClock{ flex:0 0 auto; font:700 12px ui-monospace,monospace; color:#9fb6c2; }
  .pv-btns{ display:flex; align-items:center; gap:8px; }
  #pvMine{ font:700 12px system-ui; color:#9fb6c2; white-space:nowrap; }   /* shown in place of the buttons once you've locked your vote */
  .pv-track{ position:relative; height:30px; background:#11141a; border:1px solid #2a2e37; border-radius:6px; overflow:hidden; }
  .pv-fill{ position:absolute; inset:0 auto 0 0; width:0%; background:#2a6f8e; transition:width .18s linear; }
  #peaceVote.pv-draw .pv-fill{ background:#8a7218; }
  .pv-goal{ position:absolute; top:0; bottom:0; left:100%; width:2px; margin-left:-1px; background:#eaf4ff; box-shadow:0 0 4px #000; z-index:1; }
  .pv-num{ position:absolute; inset:0; display:flex; align-items:center; padding-left:9px; font:700 13px ui-monospace,monospace;
    color:#fff; text-shadow:0 1px 2px #000,0 0 3px #000; z-index:2; pointer-events:none; }
  #peaceYes,#peaceNo{ flex:1; }
  #peaceYes,#peaceNo{ background:#222a36; color:#cdd5e2; border:1px solid #3a4658; border-radius:7px; padding:7px 10px;
    min-height:34px; font:700 12px system-ui; cursor:pointer; white-space:nowrap; }
  #peaceYes:hover,#peaceNo:hover{ filter:brightness(1.15); }
  #peaceYes.on{ background:var(--green); color:#fff; border-color:transparent; }
  #peaceNo.on{ background:#a23a3a; color:#fff; border-color:transparent; }

  /* ---------- end-of-game SCREEN (victory / defeat): big centred title + a Leave button at the bottom-centre ---------- */
  #overEnd{ position:absolute; inset:0; display:none; background:#0a0b0edd; z-index:60; }
  #overEnd.show{ display:block; animation:overFade .3s ease-out; }
  @keyframes overFade{ from{opacity:0;} to{opacity:1;} }
  #overEnd .mid{ position:absolute; top:42%; left:50%; transform:translate(-50%,-50%); text-align:center; width:min(560px,92%); }
  #overEnd .ttl{ font:800 clamp(36px,7vw,58px) system-ui; letter-spacing:.5px; margin-bottom:10px; text-shadow:0 6px 30px #000; }
  #overEnd .sub{ color:#cdd3df; font-size:clamp(14px,2.4vw,18px); }
  /* ladder-points badge (appears a moment after a ranked win, when the server's replay awards the clan) */
  #overEnd .pts{ display:none; margin:18px auto 0; padding:10px 18px; border:1px solid #2a6f8e; border-radius:11px; background:#12222b;
    color:#bfe2f2; font:800 clamp(15px,2.6vw,19px) system-ui; box-shadow:0 4px 18px #0008; animation:overFade .35s ease-out; }
  #overEnd .pts.show{ display:inline-block; }
  #overEnd .pts b{ color:#7fd1a8; }
  /* YOUR OWN rank points, on their own line under the clan badge. A different number from a different ladder — the
     clan pool is zero-sum, this is additive and lifetime — so they are never merged into one badge. Gold rather
     than the clan badge's teal, matching the insignia everywhere else the ladder appears. */
  #overEnd .pts.rank{ margin-top:10px; border-color:#8a6f2a; background:#221c10; }
  /* A SPECTATOR'S OWN PANELS. Not "hidden because empty" — hidden because they are about a player who is no longer
     in the match, and an attack slider that cannot attack is worse than no slider. !important because these panels
     are shown from several paths (the HUD toggles, the touch layout) and any of them would put them back. */
  #game .spec-off{ display:none !important; }
  /* THE WATCH BAR. Docked top-centre, out of the way of the HUD panels and the action menu, and narrow enough that
     it never becomes the thing covering the map — the defect it exists to fix. */
  #specBar{ position:absolute; top:12px; left:50%; transform:translateX(-50%); z-index:58; display:flex;
            align-items:center; gap:12px; padding:7px 10px 7px 14px; border-radius:11px;
            background:#0e1626e8; border:1px solid #24344f; box-shadow:0 8px 24px #0007;
            font:600 13px 'Chakra Petch', system-ui, sans-serif; color:#cdd9ea; max-width:min(560px,92vw); }
  #specBar[hidden]{ display:none; }
  #specBar #specPts{ font-weight:700; color:#ffc93c; }
  #specBar #specPts:empty{ display:none; }
  #specBar button{ font:600 12px 'Chakra Petch', system-ui, sans-serif; color:#cdd9ea; cursor:pointer;
                   background:#1b2740; border:1px solid #2d4160; border-radius:8px; padding:5px 11px; }
  #specBar button:hover{ background:#24344f; }
  /* The badge lines under the rank line. Joined with '' in the markup, so without this two tiers earned in the same
     match ran together as one string — "🎖️ Veteran Bronze🔥 Hot Streak Bronze" — on the card that is the moment. */
  #overEnd .pts.rank .earned{ display:flex; flex-wrap:wrap; justify-content:center; gap:4px 14px; margin-top:7px;
                              font-size:12px; font-weight:600; color:#e8d9a8; }
  #overEnd .pts.rank b{ color:#ffc93c; }
  #overEnd.win .ttl{ color:#f0d889; }
  #overEnd.loss .ttl{ color:#e2624a; }
  /* TWO actions now, side by side and centred as a pair — Leave, and stay to watch. The card is inset:0 at 87%
     opacity, so "keep watching" is not a nicety: it is the only way the map underneath is ever seen again. */
  #overEnd #overLobby, #overEnd #overWatch{ position:absolute; bottom:9%; background:#2c3242; color:#fff; border:none;
    border-radius:12px; padding:14px 30px; font:700 17px system-ui; cursor:pointer; box-shadow:0 6px 22px #000a; }
  #overEnd #overLobby{ left:50%; transform:translateX(calc(-100% - 7px)); }
  #overEnd #overWatch{ left:50%; transform:translateX(7px); background:#1f3a4d; }
  #overEnd #overLobby:hover, #overEnd #overWatch:hover{ filter:brightness(1.18); }
  /* Narrow: stack them rather than letting two 30px-padded buttons run off the edge. */
  @media (max-width: 520px){
    #overEnd #overLobby, #overEnd #overWatch{ padding:12px 20px; font-size:15px; }
    #overEnd #overWatch{ bottom:calc(9% + 52px); transform:translateX(-50%); }
    #overEnd #overLobby{ transform:translateX(-50%); }
  }

  /* ---------- TOUCH / mobile layout — gated on html.is-touch (main.mjs sets it from pointer:coarse && hover:none; see
     PLAN_MOBILE.md §0). Replaces the old @media(max-width:520px): now fires on EVERY phone incl. LANDSCAPE (844px wide, which
     the width query never caught -> desktop HUD), and NEVER on a mouse desktop (any width) so PC stays byte-identical. ---------- */
  html.is-touch input, html.is-touch textarea, html.is-touch select { font-size:16px; }   /* kill iOS focus-zoom, independent of the viewport meta */
  html.is-touch, html.is-touch body { overscroll-behavior:none; }   /* pull-to-refresh off mid-match — TOUCH only, so desktop trackpad swipe-back stays untouched */
  /* safe-area on ALL edge panels (viewport-fit=cover extends under the notch; in LANDSCAPE the notch is on the SIDES where #lb/#stats live) */
  /* MOBILE HUD FOOTPRINT = the desktop ~12%, NOT a shrunk-inline ~60%: leaderboard + stats are HIDDEN and represented by two
     44x44 CORNER BUTTONS (#lbToggle top-left, #statsToggle top-right). Tapping a button opens the FULL panel as an OVERLAY over
     the map at a readable font; tapping again or the map closes it. So the map centre stays free — only two small corner buttons. */
  /* MOBILE: leaderboard + stats stay VISIBLE by default but COMPACT, so their footprint ≈ the desktop % (not the ~60% a
     full-size panel eats on a phone). A 44x44 corner button (just above each panel) HIDES/SHOWS it so you can clear the screen. */
  .hud-toggle{ display:none; }
  html.is-touch .hud-toggle{ display:flex; align-items:center; justify-content:center; position:absolute; padding:0; z-index:22; cursor:pointer; border-radius:8px; }
  /* panel SHOWN (.on): a SMALL unobtrusive chip BESIDE the panel (right of the leaderboard / left of stats) */
  html.is-touch .hud-toggle.on{ width:30px; height:30px; background:#0e162699; border:1px solid #2a3346; color:#8ea6c4; opacity:.9; }
  html.is-touch .hud-toggle.on svg{ width:16px; height:16px; }
  html.is-touch #lbToggle.on{ top:calc(14px + env(safe-area-inset-top)); left:calc(150px + env(safe-area-inset-left)); }
  html.is-touch #statsToggle.on{ top:calc(14px + env(safe-area-inset-top)); right:calc(139px + env(safe-area-inset-right)); }
  /* panel HIDDEN (:not(.on)): full 44x44 PROMINENT button back at the corner (its "place") -> easy to find + tap to restore */
  html.is-touch .hud-toggle:not(.on){ width:44px; height:44px; background:var(--blue); border:1px solid #3a6ea0; color:#fff; }
  html.is-touch .hud-toggle:not(.on) svg{ width:22px; height:22px; }
  html.is-touch #lbToggle:not(.on){ top:calc(10px + env(safe-area-inset-top)); left:calc(10px + env(safe-area-inset-left)); }
  html.is-touch #statsToggle:not(.on){ top:calc(10px + env(safe-area-inset-top)); right:calc(10px + env(safe-area-inset-right)); }
  /* keep the DESKTOP panel styling but SCALE the whole panel down, so lb+stats occupy ~the same % of the phone screen as on
     desktop (measured: 1080p top-HUD ~5%, 1366 ~10%). Same look, same corners — just proportionally small. Tune the scale below. */
  html.is-touch #lb{ left:calc(10px + env(safe-area-inset-left)); top:calc(10px + env(safe-area-inset-top)); transform:scale(0.6); transform-origin:top left; }
  html.is-touch #lbRows{ max-height:150px; overflow-y:auto; }              /* cap so the leaderboard is a SHORT panel (scroll for the rest), not a full-side column */
  html.is-touch #stats{ right:calc(10px + env(safe-area-inset-right)); top:calc(10px + env(safe-area-inset-top)); transform:scale(0.6); transform-origin:top right; }
  html.is-touch #lb.hud-hidden, html.is-touch #stats.hud-hidden{ display:none; }   /* the corner button hides them */
  html.is-touch #balbar{ font-size:11px; padding:3px 8px; gap:4px; border-width:1px; top:calc(8px + env(safe-area-inset-top)); }   /* ~2x smaller: the troop-count pill stops crowding the top-centre so the map shows through */
  html.is-touch #balbar .rk, html.is-touch #balbar .inc{ font-size:8px; }
  html.is-touch #balbar{ --bal-line:2px; }   /* the pill is ~2x smaller on a phone, so the lines are too */
  html.is-touch #atk{ bottom:calc(10px + env(safe-area-inset-bottom)); }
  html.is-touch #atk .track{ width:min(168px,50vw); height:30px; }
  html.is-touch #atk button{ width:34px; height:30px; font-size:15px; }
  html.is-touch #atk .lab{ font-size:14px; }
  html.is-touch #zoom{ right:calc(10px + env(safe-area-inset-right)); gap:10px; }
  html.is-touch #zoom button{ width:46px; height:46px; font-size:22px; }
  html.is-touch #atkForce{ top:calc(40px + env(safe-area-inset-top)); }   /* notch-aware; tucked just under the now-smaller pill so the map centre stays open */
  html.is-touch #atkForce .row{ height:20px; font-size:11px; border-width:1px; }   /* ~2x smaller in-flight rows -> map centre visible */
  html.is-touch #atkForce .row .cx, html.is-touch #atkForce .row .pl{ width:26px; }   /* still finger-tappable ✕ / + (the ✕ is the safety net that cancels a 1-tap attack) */
  html.is-touch #atkForce .row .amt{ padding:0 9px; }   /* tighter number so the whole in-flight pill is small */
  html.is-touch #cur{ display:none; }   /* the bare coordinate readout is the first thing to drop on a phone */
  /* ⚠ ONE ANCHOR, NOT TWO HAND-COUNTED OFFSETS. The bottom HUD band on a phone tops out at ~166px (the zoom stack
     at 64-166 on the right, the % chips at 54-80 in the centre, the signal button at 70 on the left), so the dock
     starts just above it and everything it holds goes UP from there. The panels no longer position themselves at
     all, which is what makes the stack impossible to get wrong: adding a third one costs no arithmetic.
     The ORDER matters: the peace vote is the time-critical personal decision and takes the
     slot nearest the thumb, the alliance offer sits above it. That was the intent of the old 172/298 pair too; it
     just had to be re-derived by hand every time either panel changed height. */
  /* ⚠ ON A PHONE THESE ARE STRIPS, NOT CARDS — in BOTH orientations. Operator report, portrait: the offer "comes up
     in the middle and takes so much room that it actually gets in the way of playing". It was measured at 300x100
     on a 390x844 screen: 77% of the width and a tenth of the whole playfield, for a question with two answers.
     A phone gets the three things the decision is made of — WHO, HOW LONG, and the two buttons — on one row, and
     the map keeps the rest. 250x40 is a third of the area, and a decision that big is still a thumb-sized target.
     ⚠ THE EXPLAINER LINE GOES ON TOUCH, not just in landscape as before. It was kept in portrait on the argument
     that an alliance is the one offer a player can accept without understanding it — but the sentence is a third of
     the panel's height, and the panel is over the map the player is trying to read. It stays on desktop, where the
     space costs nothing. */
  html.is-touch #decisionDock{ right:calc(10px + env(safe-area-inset-right)); bottom:calc(174px + env(safe-area-inset-bottom));
    width:min(280px, 74vw); max-height:calc(100dvh - 280px); }
  html.is-touch #pactBox{ flex-direction:row; align-items:stretch; }
  html.is-touch #pactBox .pb-head{ flex:1 1 auto; min-width:0; align-items:center; gap:6px; padding:4px 8px; border-top:0; border-left:3px solid #c9a227; }
  html.is-touch #pactBox .pb-body{ display:none; }
  html.is-touch #pactBox .pb-btns{ flex:0 0 auto; padding:4px; gap:4px; }
  /* ⚠ THE BUTTONS GIVE WAY TO THE NAME, not the other way round. At their card size the pair ate 154 of a 283px
     strip and "SimonClark" rendered as "SimonClar…" — and WHO is offering is the entire decision; the two verbs
     stay legible at 11px because they also carry a ✓ and a ✕. */
  html.is-touch #pactBox .pb-btns button{ flex:0 0 auto; min-width:0; padding:0 7px; font-size:11px; }
  /* THE VOTE IS THE SAME QUESTION IN A DIFFERENT SHAPE, so it gets the same treatment: the title line stays (it is
     the one thing that says WHICH vote this is — a draw and a concession are not the same outcome), and the track
     shares its row with the two answers instead of standing on its own. ~62px instead of ~100. */
  html.is-touch #peaceVote{ display:grid; grid-template-columns:minmax(0,1fr) auto; grid-template-areas:"head head" "track btns";
    gap:5px 6px; padding:6px 7px; }
  html.is-touch .pv-head{ grid-area:head; font-size:11px; }
  html.is-touch #pvClock{ font-size:11px; }
  html.is-touch .pv-track{ grid-area:track; height:26px; }
  html.is-touch .pv-num{ font-size:11px; padding-left:7px; }
  html.is-touch .pv-btns{ grid-area:btns; gap:5px; }
  html.is-touch #peaceYes, html.is-touch #peaceNo{ flex:0 0 auto; min-height:30px; padding:4px 7px; border-radius:4px; }   /* 30px is this HUD's own touch chip (#atkChips), not an arbitrary shrink */
  /* LANDSCAPE has width to spare and almost no height — ~390px, of which the top HUD takes ~95 and the bottom band
     ~170. Stacking two panels there does not fit; side by side they both do, at the same single anchor. */
  @media (orientation:landscape){
    /* ⚠ LANDSCAPE HAS TWO SEPARATE HOLES, NOT ONE STACK, and pretending otherwise is what left the offer floating.
       MEASURED at 844x390 (every number below is a rect off the live HUD, not an estimate):
         #atk        303-541 x, 10-42 up     the attack bar, bottom-centre
         #atkChips   307-537 x, 54-80 up     the % chips above it
         #zoom       788-834 x, 64-166 up    the zoom stack — it owns the RIGHT EDGE of the bottom band
       So the bottom-right CORNER is free below 64, and there is a second hole above the chips and left of the zoom
       column. Two panels, two holes: the offer — one short strip — takes the corner, flush with the screen edge and
       level with the attack bar, which is the placement the operator asked for and the one that reads as HUD. The
       vote is three times as tall and cannot fit under the zoom stack, so it keeps the slot above the chips.
       Each number here is a clearance against a NAMED piece of furniture, never against the other panel — which is
       what the dock was built to stop, and it still holds in portrait, where there is one hole and a real stack. */
    html.is-touch #decisionDock{ display:contents; }
    html.is-touch #pactBox{ position:absolute; z-index:40;
      right:calc(10px + env(safe-area-inset-right)); bottom:calc(14px + env(safe-area-inset-bottom));
      width:min(300px, calc(50vw - 139px)); }   /* 50vw-139 = the gap between the centred attack bar's right edge and the screen */
    html.is-touch #peaceVote{ position:absolute; z-index:40;
      right:calc(68px + env(safe-area-inset-right)); bottom:calc(90px + env(safe-area-inset-bottom));
      width:min(290px, calc(100vw - 160px)); }  /* 68 = the zoom column plus a gap; 90 = clear of the chips band */
    /* the strip shape itself is set once for touch, above — landscape only chooses WHERE it goes */
  }
  html.is-touch #peaceYes, html.is-touch #peaceNo{ font-size:11px; }
  html.is-touch #leaveBtn{ left:calc(14px + env(safe-area-inset-left)); bottom:calc(14px + env(safe-area-inset-bottom)); }   /* bottom-left, clear of the side notch + home bar */
  html.is-touch #sigBtn{ left:calc(14px + env(safe-area-inset-left)); bottom:calc(70px + env(safe-area-inset-bottom)); }

  /* ---- Step 3: rebuild the in-match HUD for touch — de-collide + shrink space-hogs (PLAN_MOBILE.md Step 3). Existing DOM, reflowed. ---- */
  html.is-touch .panel{ background:#000e; }                                  /* denser overlay bg so the full panel reads over the pixel map */
  /* 3a: keep the three thumb controls in NON-overlapping bottom zones -> [☰ menu | left]  [%-track | centre]  [zoom | right] */
  html.is-touch #leaveBtn{ width:46px; padding:0; text-align:center; overflow:hidden; }   /* icon-only ☰ so it clears the centred % track */
  html.is-touch #leaveBtn .mtxt{ display:none; }
  html.is-touch #zoom{ top:auto; transform:none; bottom:calc(64px + env(safe-area-inset-bottom)); right:calc(10px + env(safe-area-inset-right)); }   /* out of the mid-right map zone -> bottom-right, above the % track */
  /* Step 2b: quick-% chips above the track (touch only) — set attack strength once with a thumb, then just tap targets */
  #atkChips{ display:none; }
  html.is-touch #atkChips{ display:flex; gap:6px; position:absolute; left:50%; transform:translateX(-50%); bottom:calc(54px + env(safe-area-inset-bottom)); z-index:16; width:min(230px, calc(100vw - 150px)); }   /* bottom raised so there's a clear SPACE between the %-quick-pick chips and the attack-amount slider below them; clamp keeps the row off the side #zoom/#sigBtn clusters */
  html.is-touch #atkChips button{ flex:1 1 0; min-width:0; height:26px; padding:0 5px; background:#0c0d10cc; border:1px solid #2a2e37; color:#cdd9ea; border-radius:6px; font:700 12px ui-monospace,monospace; cursor:pointer; }
  html.is-touch #atkChips button.on{ background:var(--blue); color:#fff; border-color:#3a6ea0; }
  /* LANDSCAPE (short height): shrink the corner panels more + cap the leaderboard shorter so the play area isn't squeezed vertically */
  @media (orientation:landscape){
    html.is-touch #lb, html.is-touch #stats{ transform:scale(0.5); }
    html.is-touch #lbRows{ max-height:130px; }   /* compact scrollable roster on short screens */
    html.is-touch #lbToggle.on{ left:calc(127px + env(safe-area-inset-left)); }       /* panels are 0.5x here -> narrower -> the chip sits closer beside them */
    html.is-touch #statsToggle.on{ right:calc(118px + env(safe-area-inset-right)); }
  }
  /* PORTRAIT (narrow): the centre balance pill fills the very top row, so a flush-corner panel + its beside-chip would collide with the pill. Drop panels + chips below the pill here (landscape stays flush at the corner). */
  @media (orientation:portrait){
    /* PORTRAIT: leaderboard + stats flush in the TOP CORNERS, with the centred troop-count pill (+ the in-flight attack row) BETWEEN them. 0.5x keeps the panels narrow enough to flank the pill even on a ~390px phone. */
    html.is-touch #lb, html.is-touch #stats{ top:calc(10px + env(safe-area-inset-top)); transform:scale(0.5); }
    html.is-touch #lbRows{ max-height:130px; }
    /* the SHOWN "hide" chip drops BELOW its panel (not beside it) so it never sits in the centre where the pill lives */
    html.is-touch #lbToggle.on{ top:calc(102px + env(safe-area-inset-top)); left:calc(16px + env(safe-area-inset-left)); }
    html.is-touch #statsToggle.on{ top:calc(102px + env(safe-area-inset-top)); right:calc(16px + env(safe-area-inset-right)); }
  }

/* ============ PWA install banner (src/client/pwa.mjs) — lobby-only, dismissible; render-only, no gameplay impact ============ */
.pwa-hide{ display:none !important; }
#pwaBanner{
  position:fixed; left:56px; bottom:calc(16px + env(safe-area-inset-bottom)); z-index:90; pointer-events:none;   /* bottom-LEFT (right of the #dlBtn bug), clear of the centred JOIN/Play CTAs + the bottom-right chat dock; pointer-events:none so the banner NEVER steals a click from anything behind it — only its buttons (below) re-enable clicks */
  display:flex; align-items:center; gap:12px;
  padding:10px 12px 10px 16px; max-width:min(360px, calc(100vw - 96px));
  background:#0e1626f2; border:1px solid #2a3346; border-radius:12px; box-shadow:0 8px 30px #000a;
  color:#cdd9ea; font:600 14px system-ui, sans-serif; animation:pwaSlideUp .28s ease;
}
@keyframes pwaSlideUp{ from{ opacity:0; transform:translateY(14px); } to{ opacity:1; transform:translateY(0); } }
#pwaBanner .pwa-cta, #pwaBanner .pwa-x{ pointer-events:auto; }   /* only the buttons capture clicks -> a lobby CTA behind the banner body stays clickable */
#pwaBanner .pwa-msg{ white-space:nowrap; }
#pwaBanner .pwa-msg b{ color:#fff; }
#pwaBanner .pwa-cta{ padding:8px 16px; border-radius:8px; cursor:pointer; background:var(--blue,#2f6fb0); border:1px solid #3a6ea0; color:#fff; font:700 14px system-ui, sans-serif; }
#pwaBanner .pwa-cta:hover{ filter:brightness(1.1); }
#pwaBanner .pwa-x{ width:28px; height:28px; flex:0 0 auto; border-radius:7px; cursor:pointer; background:#0000; border:1px solid #2a3346; color:#8ea6c4; font-size:16px; line-height:1; }
#pwaBanner .pwa-x:hover{ background:#1a2434; color:#cdd9ea; }
/* iOS "Add to Home Screen" hint toast (built in JS; iOS has no install prompt) */
#pwaIosHint{ position:fixed; left:50%; bottom:calc(72px + env(safe-area-inset-bottom)); transform:translateX(-50%); z-index:61; max-width:min(320px, calc(100vw - 28px)); padding:12px 16px; border-radius:12px; background:#0e1626f7; border:1px solid #2a3346; color:#cdd9ea; font:500 13.5px system-ui, sans-serif; line-height:1.5; text-align:center; box-shadow:0 8px 30px #000a; }
#pwaIosHint b{ color:#fff; }
/* touch: anchor bottom-LEFT, clear of the corner clutter — starts right of the #dlBtn bug (left:10, z200), and
   the max-width keeps its right edge left of the chat fab (portrait ~x302) and the open chat dock (landscape ~x454). */
html.is-touch #pwaBanner{ left:56px; right:auto; bottom:calc(12px + env(safe-area-inset-bottom)); transform:none; max-width:min(340px, calc(100vw - 152px)); animation:none; }
html.is-touch #pwaBanner .pwa-msg{ white-space:normal; flex:1 1 auto; line-height:1.25; }
/* A map preview has no army yet. Reveal these only after the new match's first HUD update. */
#game.match-pending :is(#balbar, #stats, #statsToggle, #atk, #atkChips, #atkForce, #lb, #lbToggle, #sigBtn, #sigPicker, #pactBox, #peaceVote) { visibility:hidden !important; pointer-events:none !important; }


/* ==== account.css ==== */
/* account.css — lobby account bar + account modal (create / manage / sign-in). Matches the dark lobby theme. */
#acctBar { display: flex; align-items: center; gap: 10px; width: min(360px,100%); margin-top: 14px; padding: 10px 14px;
  background: var(--site-ui-panel, #1b1f29); border: 1px solid var(--site-ui-line, #2c3242); border-radius: 10px; color: var(--site-ui-ink, #cdd3df); font-size: 14px; }
#acctName { flex: 1; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#acctBar button { border: none; border-radius: 8px; padding: 8px 12px; font: 700 13px system-ui; cursor: pointer; background: var(--site-ui-accent, #2e5e8c); color: var(--site-ui-ink, #fff); }
#acctBar #acctGear { background: var(--site-ui-panel, #2c3242); font-size: 15px; padding: 8px 11px; }
#acctBar button:hover { filter: brightness(1.1); }

#acctModal { position: fixed; inset: 0; background: var(--site-ui-panel, #0b0d12ee); display: none; align-items: center; justify-content: center; z-index: 90; padding: 22px; }
#acctModal.show { display: flex; }
#acctModal .card { position: relative; background: var(--site-ui-panel, #161a22); border: 1px solid var(--site-ui-line, #2c3242); border-radius: 16px; width: min(380px,100%);
  padding: 24px 24px 20px; box-shadow: 0 24px 70px var(--site-ui-shadow, #000c); }
#acctModal .close { position: absolute; top: 10px; right: 14px; background: none; border: none; color: var(--site-ui-muted, #9aa3b2); font-size: 24px; cursor: pointer; }
#acctModal .close:hover { color: var(--site-ui-ink, #fff); }
#acctModal h2 { margin: 0 0 16px; font-size: 20px; }
#acctModal label { display: flex; flex-direction: column; gap: 5px; font-size: 12px; color: var(--site-ui-muted, #9aa3b2); margin-bottom: 12px; }
#acctModal input { background: var(--site-ui-panel, #0f1219); color: var(--site-ui-ink, #e6e8ec); border: 1px solid var(--site-ui-line, #333b47); border-radius: 7px; padding: 10px; font: 15px system-ui; }
#acctModal button { border: none; border-radius: 9px; padding: 12px; font: 700 15px system-ui; cursor: pointer; background: var(--site-ui-panel, #2c7a2c); color: var(--site-ui-ink, #fff); width: 100%; }
#acctModal button:hover { filter: brightness(1.1); }
#acctModal .row { display: flex; gap: 10px; }
#acctModal .row button { flex: 1; }
#acctModal button.ghost { background: var(--site-ui-panel, #2c3242); color: var(--site-ui-ink, #cdd3df); }
#acctModal .msg { min-height: 16px; font-size: 12px; color: var(--site-ui-muted, #9aa3b2); margin: -2px 0 12px; }
#acctModal .alt { text-align: center; margin-top: 14px; }
#acctModal button.link { background: none; color: var(--site-ui-ink, #5b9bd3); font: 600 13px system-ui; padding: 4px; width: auto; }
#acctModal button.link:hover { text-decoration: underline; filter: none; }

/* Discord avatar image inside ANY letter-avatar container (chat / roster / account bar) — global rule */
.avimg { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; display: block; }
/* account-bar avatar (Discord image or coloured initial) — injected by account.mjs, no markup in index.html */
#acctBar #acctAv { flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%; overflow: hidden; display: flex; align-items: center; justify-content: center; font: 800 13px system-ui; color: var(--site-ui-ink, #fff); }

/* Continue with Discord — brand blurple, the primary path at the top of the auth view */
#acctModal #acAuthDiscord { background: var(--site-ui-accent, #5865F2); }
/* "or" divider between the Discord button and the username/password form */
#acctModal .or { display: flex; align-items: center; gap: 10px; margin: 14px 0; color: var(--site-ui-muted, #6b7280); font-size: 12px; text-transform: uppercase; letter-spacing: .5px; }
#acctModal .or::before, #acctModal .or::after { content: ""; flex: 1; height: 1px; background: var(--site-ui-panel, #2c3242); }
/* The Discord connect/unlink row moved to the profile page (public/styles/profile.css .pf-conn) along with the rest of
   account management; this modal is now the AUTH view only. */


/* ==== notice.css ==== */
/* notice.css — the page's own message pop-up (src/client/ui/notice.mjs).
   Replaces the browser's native alert(): that dialog is drawn OUTSIDE the page, in the browser's style, prefixed
   "pixelwar.io says", and it blocks every script until dismissed. This is part of the game. */
#pwNotices {
  position: fixed; z-index: 9000; top: 14px; left: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column; gap: 8px; align-items: center;
  width: min(560px, calc(100vw - 24px));
  pointer-events: none;                    /* the stack never blocks the page — only the cards themselves take clicks */
}
.pw-notice {
  pointer-events: auto; cursor: pointer;
  display: flex; align-items: flex-start; gap: 10px; width: 100%; box-sizing: border-box;
  background: var(--site-ui-panel, #101b2e); border: 1px solid var(--site-ui-line, #28395a); border-left: 3px solid var(--site-ui-accent, #5cc8ff); border-radius: 10px;
  padding: 11px 13px; color: var(--site-ui-ink, #e8eef7);
  /* emoji font last — it owns the ASCII digits; see lobby-shell.css */
  font: 500 13.5px/1.5 var(--site-ui-font, 'Chakra Petch'), 'Segoe UI', system-ui, sans-serif, 'Twemoji Mozilla';
  box-shadow: 0 12px 34px var(--site-ui-shadow, #000a);
  animation: pw-notice-in .18s ease-out;
}
.pw-notice.pw-warn { border-left-color: var(--site-ui-accent, #e0533d); background: var(--site-ui-panel, #1c1420); }
.pw-notice.pw-ok   { border-left-color: var(--site-ui-accent, #46c08a); }
.pw-nic { flex: 0 0 auto; font-size: 14px; line-height: 1.45; }
.pw-notice.pw-warn .pw-nic { color: var(--site-ui-danger, #ff9d8c); }
.pw-notice.pw-ok   .pw-nic { color: var(--site-ui-accent, #46c08a); }
.pw-ntx { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.pw-nx {
  flex: 0 0 auto; background: none; border: 0; color: var(--site-ui-muted, #8da2be); font: 700 16px system-ui;
  line-height: 1; padding: 0 2px; cursor: pointer;
}
.pw-nx:hover { color: var(--site-ui-ink, #fff); }
.pw-notice.pw-out { animation: pw-notice-out .2s ease-in forwards; }
@keyframes pw-notice-in  { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
@keyframes pw-notice-out { to { opacity: 0; transform: translateY(-8px); } }
/* a phone gets the full width and a bit more breathing room */
@media (max-width: 560px) { #pwNotices { top: 10px; width: calc(100vw - 16px); } .pw-notice { font-size: 13px; } }

/* askPassword — a masked credential field in the page, replacing window.prompt() (which does not mask its input) */
.pw-ask { position: fixed; inset: 0; z-index: 9100; display: grid; place-items: center; background: var(--site-ui-panel, #000a); }
.pw-askcard { width: min(420px, calc(100vw - 32px)); background: var(--site-ui-panel, #101b2e); border: 1px solid var(--site-ui-line, #28395a); border-radius: 12px;
  padding: 18px; box-shadow: 0 18px 50px var(--site-ui-shadow, #000b);
  font: 500 13.5px/1.55 var(--site-ui-font, 'Chakra Petch'), 'Segoe UI', system-ui, sans-serif; color: var(--site-ui-ink, #e8eef7); }
.pw-askh { margin: 0 0 8px; font: 700 16px var(--site-ui-font, 'Chakra Petch'), system-ui; color: var(--site-ui-ink, #fff); }
.pw-askb { margin: 0 0 14px; color: var(--site-ui-ink, #a9bad2); }
.pw-askin { width: 100%; box-sizing: border-box; background: var(--site-ui-panel, #0b1220); color: var(--site-ui-ink, #e8eef7); border: 1px solid var(--site-ui-line, #1d2c45);
  border-radius: 8px; padding: 10px 12px; font: 14px var(--site-ui-font, 'Chakra Petch'), system-ui; outline: none; }
.pw-askin:focus { border-color: var(--site-ui-accent, #2f86d6); }
.pw-askrow { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
.pw-askx, .pw-askgo { border-radius: 8px; padding: 8px 15px; font: 600 13px var(--site-ui-font, 'Chakra Petch'), system-ui; cursor: pointer; }
.pw-askx { background: none; border: 1px solid var(--site-ui-line, #28395a); color: var(--site-ui-ink, #9fb2cc); }
.pw-askx:hover { color: var(--site-ui-ink, #fff); border-color: var(--site-ui-accent, #4a6fa5); }
.pw-askgo { background: var(--site-ui-accent, #2f86d6); border: 1px solid var(--site-ui-accent, #2f86d6); color: var(--site-ui-ink, #fff); }
.pw-askgo:hover { background: var(--site-ui-accent, #3f96e6); }


/* ==== chathub.css ==== */
/* chathub.css — the lobby chat dock (Global / Recruit / Clan / DMs) + its floating launcher. Lobby-only. */
/* Twemoji (Discord-style) colour emoji, bundled locally — one COLR font, works offline. Used across both chat UIs.
   ⚠ IT GOES LAST IN EVERY TEXT STACK, NOT SECOND. "After the text font" is not enough: font fallback is PER GLYPH,
   and this file has glyphs for the ten ASCII DIGITS (the keycap bases). --site-ui-font is 'Segoe UI' in the lobby,
   which does not exist on Android or iOS — so on a phone every number in the chat and the lobby resolved to a blank
   keycap glyph while the letters beside it rendered normally. Reported as "the countdown and the player count do not
   show". Only .em and the reaction buttons may name it FIRST: there the emoji font is the point. */
@font-face { font-family: 'Twemoji Mozilla'; src:url('/public/twemoji/TwemojiMozilla.ttf') format('truetype'); font-display: swap; }
/* emoji in messages are wrapped by fmtBody() and rendered LARGER than the text (Discord-style), forced to Twemoji.
   jumbo = an emoji-only message. Global so both chat UIs (dock + clan page) get it. */
.em { font-family: 'Twemoji Mozilla','Segoe UI Emoji','Apple Color Emoji',sans-serif; font-size: 1.45em; line-height: 1; vertical-align: -0.18em; }
.em.jumbo { font-size: 2.5em; vertical-align: -0.3em; }
/* keep emoji modest inside quotes / reply bars (don't jumbo a one-line preview) */
#chatHubMsgs .rquote .em, #clanPage .cp-rquote .em, #chatReplyBar .em, #cpReplyBar .em { font-size: 1em; vertical-align: -0.1em; }
/* launcher styled to match the dark "command" lobby (cyan-accented panel tab) */
#chatFab { position: fixed; right: 18px; bottom: 18px; padding: 11px 16px; border-radius: 10px; background: var(--site-ui-panel, #101b2e); color: var(--site-ui-ink, #5cc8ff);
  border: 1px solid var(--site-ui-line, #28395a); font: 700 12px var(--site-ui-font, 'Chakra Petch'),'Segoe UI',system-ui; letter-spacing: .12em; text-transform: uppercase;
  cursor: pointer; z-index: 70; box-shadow: 0 6px 20px var(--site-ui-shadow, rgba(0,0,0,.5)); display: none; align-items: center; justify-content: center; }
#chatFab:hover { background: var(--site-ui-panel, #16243a); border-color: var(--site-ui-accent, #5cc8ff); color: var(--site-ui-ink, #fff); filter: none; }
#chatFab:focus-visible { outline: 2px solid var(--site-ui-accent, #5cc8ff); outline-offset: 2px; }
#chatFab[data-n]:not([data-n=""])::after { content: attr(data-n); position: absolute; top: -3px; right: -3px; min-width: 18px; height: 18px;
  padding: 0 4px; box-sizing: border-box; border-radius: 9px; background: var(--site-ui-accent, #e0433b); color: var(--site-ui-ink, #fff); font: 700 11px system-ui; display: flex; align-items: center; justify-content: center; }

/* dark "command" panel — flat, cyan-accented, matches the lobby + clan page (no toy colours / chunky pills) */
#chatDock { position: fixed; right: 18px; bottom: 18px; width: min(372px,94vw); height: min(520px,74vh); background: var(--site-ui-panel, #0e1626);
  border: 1px solid var(--site-ui-line, #1d2c45); border-radius: 12px; z-index: 70; display: none; flex-direction: column; overflow: hidden;
  box-shadow: 0 20px 60px var(--site-ui-shadow, rgba(0,0,0,.55)); font-family: var(--site-ui-font, 'Chakra Petch'),'Segoe UI',system-ui,'Twemoji Mozilla'; }
.chatHubHead { display: flex; align-items: center; gap: 4px; padding: 5px 8px; border-bottom: 1px solid var(--site-ui-line, #1d2c45); background: var(--site-ui-panel, #0b1220); }
.chatTabs { display: flex; gap: 2px; flex: 1; }
.chatTabs button { flex: 1; background: none; color: var(--site-ui-muted, #8da2be); border: none; border-bottom: 2px solid transparent; padding: 9px 3px 8px;
  font: 600 12px var(--site-ui-font, 'Chakra Petch'),system-ui,'Twemoji Mozilla'; letter-spacing: .04em; text-transform: uppercase; cursor: pointer; position: relative; white-space: nowrap; transition: color .12s, border-color .12s; }
.chatTabs button:hover { color: var(--site-ui-ink, #cdd9ea); }
.chatTabs button.active { color: var(--site-ui-ink, #5cc8ff); border-bottom-color: var(--site-ui-accent, #5cc8ff); }
.chatTabs button.unread::after { content: ""; position: absolute; top: 5px; right: 5px; width: 6px; height: 6px; border-radius: 50%; background: var(--site-ui-gold-well, #e0533d); }
#chatHubClose { background: none; border: none; color: var(--site-ui-muted, #5f6f88); font-size: 18px; cursor: pointer; padding: 2px 8px; }
#chatHubClose:hover { color: var(--site-ui-ink, #cdd9ea); }

#chatHubMsgs { flex: 1; overflow-y: auto; display: flex; flex-direction: column; padding: 10px 2px 12px; }
#chatHubMsgs::-webkit-scrollbar { width: 7px; } #chatHubMsgs::-webkit-scrollbar-thumb { background: var(--site-ui-hover, #ffffff1f); border-radius: 4px; }
#chatHubMsgs .empty { color: var(--site-ui-muted, #5f6f88); font-size: 13px; text-align: center; margin: auto; line-height: 1.5; max-width: 82%; }
/* Discord-style row: [avatar] [body]. Grouped continuation rows drop the avatar+name header and tighten up. */
#chatHubMsgs .cmsg { position: relative; display: flex; gap: 9px; padding: 1px 10px; font: 400 13px/1.4 var(--site-ui-font, 'Chakra Petch'),system-ui,'Twemoji Mozilla'; word-break: break-word; }
#chatHubMsgs .cmsg.head { margin-top: 9px; }
#chatHubMsgs .cmsg.head:first-child, #chatHubMsgs .cdivider + .cmsg.head { margin-top: 2px; }
#chatHubMsgs .cmsg:hover { background: var(--site-ui-hover, #ffffff08); }
#chatHubMsgs .cav { flex: 0 0 26px; width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--site-ui-ink, #fff); font: 700 12px var(--site-ui-font, 'Chakra Petch'),system-ui; margin-top: 2px; }
#chatHubMsgs .cav.sp { background: none !important; align-items: flex-start; justify-content: center; }
#chatHubMsgs .cav.sp .hts { display: none; font-size: 10px; color: var(--site-ui-muted, #4f5d75); line-height: 2.2; }
#chatHubMsgs .cmsg.cont:hover .cav.sp .hts { display: block; }   /* grouped rows reveal a tiny timestamp on hover */
#chatHubMsgs .cbody { flex: 1; min-width: 0; }
#chatHubMsgs .chead { display: flex; align-items: baseline; gap: 8px; margin-bottom: 1px; }
#chatHubMsgs .who { color: var(--site-ui-ink, #9bb6ff); font-weight: 700; }
#chatHubMsgs .who.lnk { cursor: pointer; }
#chatHubMsgs .who.lnk:hover { text-decoration: underline; }
#chatHubMsgs .cmsg.me .who { color: var(--site-ui-ink, #5cc8ff); }
#chatHubMsgs .txt { color: var(--site-ui-ink, #dfe6f0); }
#chatHubMsgs .ts { color: var(--site-ui-muted, #4f5d75); font-size: 11px; font-weight: 400; }
/* DM inbox: one card per partner */
#chatHubMsgs .cmsg.inbox .txt { color: var(--site-ui-muted, #8da2be); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#chatHubMsgs .cmsg.inbox .unreadn { background: var(--site-ui-gold-well, #e0533d); color: var(--site-ui-ink, #fff); font: 700 11px system-ui; border-radius: 9px; padding: 1px 7px; }
/* A CONVERSATION ROW IS A BUTTON. The whole row opens the thread — a 60px name is not a touch target, and this is
   the primary way into every DM. An unread thread is brighter and keeps its name in full white, so the list can be
   read at a glance instead of scanned for red pills. */
#chatHubMsgs .cmsg.inbox[data-open] { cursor: pointer; border-radius: 8px; transition: background .12s; }
#chatHubMsgs .cmsg.inbox[data-open]:hover { background: var(--site-ui-hover, #5cc8ff12); }
#chatHubMsgs .cmsg.inbox.un .who { color: var(--site-ui-ink, #fff); font-weight: 700; }
#chatHubMsgs .cmsg.inbox.un .txt { color: var(--site-ui-ink, #c3d3e8); }
/* date divider */
#chatHubMsgs .cdivider { display: flex; align-items: center; gap: 10px; margin: 10px 12px 4px; color: var(--site-ui-muted, #4f5d75); font: 600 10px var(--site-ui-font, 'Chakra Petch'),system-ui; letter-spacing: .1em; text-transform: uppercase; }
#chatHubMsgs .cdivider::before, #chatHubMsgs .cdivider::after { content: ""; flex: 1; height: 1px; background: var(--site-ui-panel, #1d2c45); }

#dmToWrap { display: flex; gap: 6px; padding: 8px 10px 0; }
#dmToWrap input { flex: 1; min-width: 0; box-sizing: border-box; background: var(--site-ui-panel, #0b1220); color: var(--site-ui-ink, #e8eef7); border: 1px solid var(--site-ui-line, #1d2c45); border-radius: 8px; padding: 7px 10px; font: 12px var(--site-ui-font, 'Chakra Petch'),system-ui,'Twemoji Mozilla'; outline: none; }
#dmToWrap input:focus { border-color: var(--site-ui-accent, #2f86d6); }
#chatDock .chatbar { display: flex; gap: 7px; padding: 9px 10px 5px; }
#chatDock .chatbar input { flex: 1; min-width: 0; background: var(--site-ui-panel, #0b1220); color: var(--site-ui-ink, #e8eef7); border: 1px solid var(--site-ui-line, #1d2c45); border-radius: 8px; padding: 8px 11px; font: 13px var(--site-ui-font, 'Chakra Petch'),system-ui,'Twemoji Mozilla'; outline: none; }
#chatDock .chatbar input:focus { border-color: var(--site-ui-accent, #2f86d6); }
#chatDock .chatbar input:disabled { opacity: .5; }
#chatDock .chatbar button#chatHubSend { border: 1px solid var(--site-ui-line, #1d2c45); border-radius: 8px; padding: 0 15px; background: var(--site-ui-panel, #12263e); color: var(--site-ui-ink, #5cc8ff); font-size: 15px; cursor: pointer; }
#chatDock .chatbar button#chatHubSend:hover { background: var(--site-ui-panel, #173352); color: var(--site-ui-ink, #fff); border-color: var(--site-ui-accent, #2f86d6); }
#chatDock .chatbar button:disabled { opacity: .45; cursor: default; }
#chatHubHint { color: var(--site-ui-muted, #5f6f88); font-size: 11px; padding: 0 10px 8px; min-height: 14px; }

/* ---- Phase-1 UX ---- */
/* clickable links + @mention tags inside a message body */
#chatHubMsgs .txt a { color: var(--site-ui-ink, #5cc8ff); text-decoration: underline; word-break: break-all; }
#chatHubMsgs .txt .mtag { color: var(--site-ui-danger, #ffd16b); font-weight: 700; }
/* light markdown (bold/italic/strike render natively; code + quote need styling) — shared by both chat UIs */
#chatHubMsgs .txt code, #clanPage .cp-ctxt code, .rquote code, .cp-rquote code { font-family: ui-monospace,'Cascadia Code',monospace; background: var(--site-ui-hover, #00000055); padding: 1px 5px; border-radius: 4px; font-size: .88em; }
#chatHubMsgs .txt .mdquote, #clanPage .cp-ctxt .mdquote { display: inline-block; border-left: 3px solid var(--site-ui-line, #3a4570); padding-left: 8px; color: var(--site-ui-ink, #b8c4d8); }
/* a message that pings ME stands out (inset accent, not a full box — plays nice with the flex row) */
#chatHubMsgs .cmsg.mention { background: var(--site-ui-hover, #3a2f1233); box-shadow: inset 3px 0 0 var(--site-ui-shadow, #ffcc4d); }
/* clan system line spans the full width, centered */
#chatHubMsgs .cmsg.sys { display: block; text-align: center; }
/* "(edited)" marker */
#chatHubMsgs .cmsg .ed { color: var(--site-ui-muted, #4f5d75); font-size: 11px; margin-left: 5px; font-style: italic; }
/* ⋯ action-menu button — floats at the row's top-right (Discord-style); hover to reveal, faint on touch */
#chatHubMsgs .cmenu { position: absolute; top: 0; right: 4px; display: none; background: var(--site-ui-panel, #0e1626); border: 1px solid var(--site-ui-line, #1d2c45); color: var(--site-ui-muted, #8da2be); cursor: pointer; font-size: 15px; line-height: 1; padding: 1px 6px; border-radius: 6px; }
#chatHubMsgs .cmsg:hover .cmenu { display: block; }
#chatHubMsgs .cmenu:hover { color: var(--site-ui-ink, #5cc8ff); background: var(--site-ui-panel, #17233a); }
@media (hover: none) { #chatHubMsgs .cmenu { display: block; opacity: .5; } }
/* Discord-style quoted reply above a message */
/* A reply quote is a LINK to the original — it has to look like one or nobody tries it, and it did not.
   The hover state is the whole affordance: same line, brighter, with the connector picked out. */
#chatHubMsgs .rquote { font-size: 12px; color: var(--site-ui-muted, #8da2be); margin-bottom: 2px; padding-left: 8px; border-left: 2px solid var(--site-ui-line, #2f4468);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; border-radius: 0 4px 4px 0; transition: color .12s, background .12s, border-color .12s; }
#chatHubMsgs .rquote:hover { color: var(--site-ui-ink, #cfe0f5); background: var(--site-ui-hover, #5cc8ff14); border-left-color: var(--site-ui-accent, #5cc8ff); }
#chatHubMsgs .rquote b { color: var(--site-ui-ink, #9bb6ff); font-weight: 700; }
#chatHubMsgs .rquote a { color: var(--site-ui-ink, #5cc8ff); }

/* THE JUMP TARGET. Scrolling to a message without saying which one is barely better than not scrolling: in a wall of
   similar rows the eye has nothing to land on. A brief highlight is the answer, and it fades on its own so it never
   becomes permanent state to clean up. */
#chatHubMsgs .cmsg.jumped, #clanPage .cp-cmsg.jumped { animation: pw-jumped 1.6s ease-out; border-radius: 6px; }
@keyframes pw-jumped {
  0%   { background: var(--site-ui-hover, #5cc8ff38); box-shadow: inset 3px 0 0 var(--site-ui-shadow, #5cc8ff); }
  70%  { background: var(--site-ui-hover, #5cc8ff20); box-shadow: inset 3px 0 0 var(--site-ui-shadow, #5cc8ff); }
  100% { background: transparent; box-shadow: none; }
}
/* the original is older than the loaded page — shake, so the click is visibly RECEIVED rather than ignored */
#chatHubMsgs .rquote.nojump, #clanPage .cp-rquote.nojump { animation: pw-nojump .38s ease-in-out; }
@keyframes pw-nojump { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }

/* floating message action menu (Reply / Edit / Delete) */
#chatMsgMenu { position: fixed; z-index: 90; min-width: 134px; background: var(--site-ui-panel, #0e1626); border: 1px solid var(--site-ui-line, #1d2c45); border-radius: 9px; padding: 5px; box-shadow: 0 10px 30px var(--site-ui-shadow, #000c); font-family: var(--site-ui-font, 'Chakra Petch'),system-ui; }
#chatMsgMenu[hidden] { display: none; }
#chatMsgMenu button { display: block; width: 100%; text-align: left; background: none; border: none; color: var(--site-ui-ink, #cdd9ea); font: 600 13px var(--site-ui-font, 'Chakra Petch'),system-ui,'Twemoji Mozilla'; padding: 8px 11px; border-radius: 6px; cursor: pointer; }
#chatMsgMenu button:hover { background: var(--site-ui-panel, #17233a); color: var(--site-ui-ink, #fff); }
#chatMsgMenu button[data-mact="del"]:hover { background: var(--site-ui-panel, #3a1d1d); color: var(--site-ui-danger, #ffb4b4); }
/* quick-react emoji row at the top of the action menu */
#chatMsgMenu .amreacts { display: flex; gap: 1px; padding: 2px 2px 6px; margin-bottom: 4px; border-bottom: 1px solid var(--site-ui-line, #1d2c45); }
#chatMsgMenu .amreacts button { background: none; border: none; cursor: pointer; font: 15px 'Twemoji Mozilla','Segoe UI Emoji',sans-serif; line-height: 1; padding: 4px 5px; border-radius: 6px; }
#chatMsgMenu .amreacts button:hover { background: var(--site-ui-panel, #17233a); }
/* reaction chips under a message */
#chatHubMsgs .creacts { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
#chatHubMsgs .creact { display: inline-flex; align-items: center; gap: 3px; background: var(--site-ui-panel, #12233a); border: 1px solid var(--site-ui-line, #1d2c45); border-radius: 10px; padding: 1px 7px; color: var(--site-ui-ink, #cdd9ea); font: 600 11px var(--site-ui-font, 'Chakra Petch'),system-ui; cursor: pointer; }
#chatHubMsgs .creact .re { font: 12px 'Twemoji Mozilla','Segoe UI Emoji',sans-serif; }
#chatHubMsgs .creact:hover { border-color: var(--site-ui-accent, #2f86d6); }
#chatHubMsgs .creact.mine { background: var(--site-ui-panel, #16324f); border-color: var(--site-ui-accent, #5cc8ff); color: var(--site-ui-ink, #bfe4ff); }

/* "↩ Replying to …" bar above the composer */
#chatReplyBar { display: flex; align-items: center; gap: 8px; margin: 6px 10px 0; padding: 6px 10px; background: var(--site-ui-panel, #0b1a2c); border: 1px solid var(--site-ui-line, #1d2c45); border-left: 3px solid var(--site-ui-accent, #2f86d6); border-radius: 8px; }
#chatReplyBar[hidden] { display: none; }
#chatReplyBar .rt { flex: 1; min-width: 0; color: var(--site-ui-ink, #9fb2cc); font: 600 12px var(--site-ui-font, 'Chakra Petch'),system-ui,'Twemoji Mozilla'; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#chatReplyBar #chatReplyX { background: none; border: none; color: var(--site-ui-muted, #8da2be); cursor: pointer; font-size: 13px; padding: 0 2px; }
#chatReplyBar #chatReplyX:hover { color: var(--site-ui-ink, #fff); }
/* DM block toggle in the thread header */
#dmToWrap #dmBlockBtn { flex: 0 0 auto; margin-left: 6px; background: var(--site-ui-panel, #2a2030); color: var(--site-ui-danger, #e6b4b4); border: 1px solid var(--site-ui-line, #5a3a4a); border-radius: 8px; padding: 0 10px; font: 700 11px system-ui; cursor: pointer; }
/* ← back to the conversation list. Shown only while a thread is open (updateBlockBtn), because with no thread open
   the list IS the view and a back arrow would point at nothing. */
#dmToWrap #dmBackBtn { flex: 0 0 auto; background: var(--site-ui-panel, #0b1220); color: var(--site-ui-ink, #9fb2cc); border: 1px solid var(--site-ui-line, #1d2c45); border-radius: 8px; padding: 0 11px; font: 700 14px system-ui; cursor: pointer; line-height: 1; }
#dmToWrap #dmBackBtn:hover { color: var(--site-ui-ink, #fff); border-color: var(--site-ui-accent, #2f86d6); }
#dmToWrap #dmBackBtn[hidden] { display: none; }
#dmToWrap #dmBlockBtn.on { background: var(--site-ui-panel, #5a2a2a); color: var(--site-ui-ink, #fff); border-color: var(--site-ui-accent, #a83232); }
#dmToWrap #dmBlockBtn[hidden] { display: none; }
/* system messages (clan joined/left/etc.) render centered + muted */
#chatHubMsgs .cmsg.sys { color: var(--site-ui-muted, #7e8aa0); font-style: italic; text-align: center; font-size: 12px; }
#chatHubMsgs .cmsg.sys .txt { color: var(--site-ui-muted, #7e8aa0); }
/* recruit -> one-click Join card */
#chatHubMsgs .rcard { margin-top: 4px; }
#chatHubMsgs .rjoin { background: var(--site-ui-panel, #173a2a); color: var(--site-ui-accent, #8ff0bd); border: 1px solid var(--site-ui-accent, #2f7f57); border-radius: 8px; padding: 5px 12px; font: 700 12px system-ui; cursor: pointer; }
#chatHubMsgs .rjoin:hover { background: var(--site-ui-panel, #1d5238); color: var(--site-ui-ink, #fff); }
#chatHubMsgs .rjoin:disabled { opacity: .6; cursor: default; }
/* "↑ Load older" pager at the top of clan/global/recruit */
#chatHubMsgs .loadold { display: block; margin: 0 auto 6px; background: var(--site-ui-panel, #12233a); color: var(--site-ui-muted, #8da2be); border: 1px solid var(--site-ui-line, #1d2c45); border-radius: 8px; padding: 5px 12px; font: 600 10.5px var(--site-ui-font, 'Chakra Petch'),system-ui; letter-spacing: .05em; text-transform: uppercase; cursor: pointer; }
#chatHubMsgs .loadold:hover { background: var(--site-ui-panel, #173352); color: var(--site-ui-ink, #5cc8ff); }
/* pinned clan MOTD */
#chatPin { padding: 8px 12px; border-bottom: 1px solid var(--site-ui-line, #1d2c45); background: var(--site-ui-panel, #0b1a2c); color: var(--site-ui-ink, #bcd0f5); font: 600 12px var(--site-ui-font, 'Chakra Petch'),system-ui; line-height: 1.4; word-break: break-word; }
#chatPin[hidden] { display: none; }
#chatPin a { color: var(--site-ui-ink, #5cc8ff); }

/* "N new ↓" jump pill — only shown when scrolled up */
#chatNewPill { position: absolute; left: 50%; transform: translateX(-50%); bottom: 96px; z-index: 3; border: 1px solid var(--site-ui-accent, #2f86d6); cursor: pointer;
  background: var(--site-ui-panel, #12263e); color: var(--site-ui-ink, #5cc8ff); font: 600 11px var(--site-ui-font, 'Chakra Petch'),system-ui; letter-spacing: .04em; padding: 6px 14px; border-radius: 14px; box-shadow: 0 4px 14px var(--site-ui-shadow, #0008); }
#chatNewPill:hover { background: var(--site-ui-panel, #173352); color: var(--site-ui-ink, #fff); }
#chatNewPill[hidden] { display: none; }

/* inline error / cooldown toast just above the composer */
.chatToast { margin: 0 10px; min-height: 0; max-height: 0; overflow: hidden; opacity: 0; transition: opacity .15s, max-height .15s, padding .15s;
  background: var(--site-ui-panel, #13241a); border-radius: 8px; color: var(--site-ui-accent, #bff0d8); font: 700 12px system-ui; padding: 0 10px; }
.chatToast.show { opacity: 1; max-height: 40px; padding: 7px 10px; }
.chatToast.warn { background: var(--site-ui-panel, #3a1d1d); color: var(--site-ui-danger, #ffb4b4); }

/* emoji button + popup in the composer */
#chatDock .chatbar #chatEmojiBtn { background: var(--site-ui-panel, #0b1220); border: 1px solid var(--site-ui-line, #1d2c45); border-radius: 8px; padding: 0 10px; font-size: 16px; cursor: pointer; flex: 0 0 auto; }
#chatDock .chatbar #chatEmojiBtn:hover { border-color: var(--site-ui-accent, #2f86d6); }
#chatEmojiPop { display: none; position: absolute; right: 10px; bottom: 56px; z-index: 4; flex-wrap: wrap; width: min(250px,84vw); gap: 3px; padding: 8px;
  background: var(--site-ui-panel, #0b1220); border: 1px solid var(--site-ui-line, #1d2c45); border-radius: 12px; box-shadow: 0 8px 24px var(--site-ui-shadow, #000b); }
#chatEmojiPop.show { display: flex; }
#chatEmojiPop button { width: 34px; height: 34px; border-radius: 8px; border: 1px solid var(--site-ui-line, #1d2c45); background: var(--site-ui-panel, #0e1626); font-size: 19px; line-height: 1; cursor: pointer; padding: 0; }
#chatEmojiPop button:hover { background: var(--site-ui-panel, #16233a); }
/* clan-logo emoji: the picker button + the inline logo image rendered inside a chat message (shared by lobby + clan-page chat) */
#chatEmojiPop .em-clanlogo, #cpEmojiPop .em-clanlogo { overflow: hidden; box-shadow: 0 0 0 2px var(--site-ui-shadow, #5cc8ff55); }
#chatEmojiPop .em-clanlogo img, #cpEmojiPop .em-clanlogo img { width: 100%; height: 100%; object-fit: cover; border-radius: 6px; display: block; }
.chat-clanlogo { width: 1.5em; height: 1.5em; object-fit: cover; border-radius: 4px; vertical-align: -0.35em; margin: 0 1px; box-shadow: 0 0 0 1px var(--site-ui-shadow, #ffffff22) inset; }
.em.jumbo ~ .chat-clanlogo, .chat-clanlogo:only-child { width: 2.2em; height: 2.2em; }

/* clickable player names (chat / clan roster / party) -> the shared player-profile popover */
.who.lnk, .pp-clickname { cursor: pointer; }
.who.lnk:hover, .pp-clickname:hover { text-decoration: underline; }
#playerProfile { position: fixed; z-index: 98; width: 234px; background: var(--site-ui-panel, #0e1626); border: 1px solid var(--site-ui-line, #24344f); border-radius: 12px; box-shadow: 0 12px 34px var(--site-ui-shadow, #0009); padding: 12px; font: 13px var(--site-ui-font, 'Chakra Petch'),system-ui,'Twemoji Mozilla'; color: var(--site-ui-ink, #e8eef7); }
#playerProfile .pp-empty { color: var(--site-ui-muted, #8ba0bd); text-align: center; padding: 10px 4px; }
#playerProfile .pp-head { display: flex; align-items: center; gap: 10px; }
#playerProfile .pp-av { width: 40px; height: 40px; border-radius: 50%; flex: none; display: flex; align-items: center; justify-content: center; font: 800 17px system-ui; color: var(--site-ui-ink, #fff); overflow: hidden; }
#playerProfile .pp-av img, #playerProfile .pp-av .avimg { width: 100%; height: 100%; object-fit: cover; }
#playerProfile .pp-name { font: 800 15px var(--site-ui-font, 'Chakra Petch'),system-ui; color: var(--site-ui-ink, #fff); word-break: break-word; }
/* chosen unit flag, beside the name — drawn at the BANNER's 3:2 and filled, so a flag looks the same here as it does
   on the map. (max-width/max-height kept each PNG's own aspect, which made a square flag a different shape in every
   place it appeared.) */
#playerProfile .pp-flag { width: 21px; height: 14px; object-fit: fill; vertical-align: middle; margin-left: 7px; border-radius: 2px; }
#playerProfile .pp-since { font-size: 11px; color: var(--site-ui-muted, #8ba0bd); }
#playerProfile .pp-clan { display: flex; align-items: center; gap: 6px; margin: 11px 0 9px; font-size: 12.5px; color: var(--site-ui-ink, #cdd9ea); }
#playerProfile .pp-clan b { color: var(--site-ui-ink, #fff); }
#playerProfile .pp-clanlogo { width: 20px; height: 20px; border-radius: 5px; object-fit: cover; flex: none; }
#playerProfile .pp-role { margin-left: auto; font-size: 11px; color: var(--site-ui-muted, #8ba0bd); text-transform: capitalize; }
#playerProfile .pp-noclan { color: var(--site-ui-muted, #7f8da8); font-style: italic; }
#playerProfile .pp-stats { display: flex; gap: 8px; }
#playerProfile .pp-stat { flex: 1; background: var(--site-ui-panel, #0b1220); border: 1px solid var(--site-ui-line, #1d2c45); border-radius: 8px; padding: 7px 3px; text-align: center; }
#playerProfile .pp-stat b { display: block; font: 800 16px var(--site-ui-font, 'Chakra Petch'),system-ui; color: var(--site-ui-ink, #5cc8ff); }
#playerProfile .pp-stat span { font-size: 10px; color: var(--site-ui-muted, #8ba0bd); }
#playerProfile .pp-msg { width: 100%; margin-top: 11px; background: var(--site-ui-panel, #1b3a5c); color: var(--site-ui-ink, #dff0ff); border: 1px solid var(--site-ui-accent, #2c6390); border-radius: 8px; padding: 8px; font: 700 13px var(--site-ui-font, 'Chakra Petch'),system-ui; cursor: pointer; }
#playerProfile .pp-msg:hover { background: var(--site-ui-panel, #22496f); }
/* opener-supplied context actions (clan roster: promote / roles / mute / kick) */
#playerProfile .pp-actions { display: flex; flex-direction: column; gap: 5px; margin-top: 9px; padding-top: 9px; border-top: 1px solid var(--site-ui-line, #1d2c45); }
#playerProfile .pp-act { text-align: left; background: var(--site-ui-panel, #12203a); color: var(--site-ui-ink, #d6e3f5); border: 1px solid var(--site-ui-line, #24344f); border-radius: 8px; padding: 7px 9px; font: 600 12.5px var(--site-ui-font, 'Chakra Petch'),system-ui,'Twemoji Mozilla'; cursor: pointer; }
#playerProfile .pp-act:hover { background: var(--site-ui-panel, #1a2c4c); border-color: var(--site-ui-accent, #33507a); }
#playerProfile .pp-act-danger { color: var(--site-ui-danger, #ff9d9d); border-color: var(--site-ui-line, #5a2530); }
#playerProfile .pp-act-danger:hover { background: var(--site-ui-panel, #3a1620); border-color: var(--site-ui-accent, #7d3340); }

/* floating PM / @mention notifications (work even when the dock is closed) */
#chatNotify { position: fixed; right: 18px; bottom: 74px; z-index: 80; display: flex; flex-direction: column; gap: 8px; align-items: flex-end; pointer-events: none; }
#chatNotify .chatNote { pointer-events: auto; cursor: pointer; max-width: 300px; background: var(--site-ui-panel, #16203a); border: 1px solid var(--site-ui-accent, #3f56a0); color: var(--site-ui-ink, #dbe6ff);
  font: 600 13px system-ui; padding: 10px 14px; border-radius: 10px; box-shadow: 0 6px 20px var(--site-ui-shadow, #000a); opacity: 0; transform: translateY(8px); transition: opacity .2s, transform .2s; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#chatNotify .chatNote.in { opacity: 1; transform: translateY(0); }

/* ---- mobile: full-screen sheet under 560px, keyboard-safe composer ---- */
@media (max-width:560px) {
  #chatDock { right: 0; left: 0; bottom: 0; top: 0; width: 100%; height: 100dvh; border-radius: 0; border: none; }
  .chatTabs button { padding: 9px 3px; font-size: 12px; }
  #chatHubMsgs { padding: 12px; }
  #chatDock .chatbar { padding: 8px 10px calc(8px + env(safe-area-inset-bottom)); }
  #chatDock .chatbar input { font-size: 16px; }   /* >=16px stops iOS auto-zoom on focus */
  #chatEmojiPop { width: calc(100vw - 20px); right: 10px; left: 10px; }
  #chatNewPill { bottom: 108px; }
}

/* "X is writing…" — the one signal that separates an occupied room from an archive. Zero height when empty so an
   idle channel carries no blank strip; it only takes space when somebody is actually typing. */
#chatDock .chatTyping { height: 0; overflow: hidden; opacity: 0; color: var(--site-ui-ink, #8fb6d9); font: 500 11.5px system-ui, "Segoe UI";
  padding: 0 12px; white-space: nowrap; text-overflow: ellipsis; transition: height .12s ease, opacity .12s ease; }
#chatDock .chatTyping.show { height: 17px; opacity: .85; }

/* A bridged image / GIF / Discord custom emoji rendered inline. Capped in BOTH axes so one large upload cannot push
   a conversation off the screen, and given an explicit min-height so the row does not collapse and then jump when
   the image finally loads — a chat that sticks to the bottom is exactly where late layout shift is felt. */
.chat-img { display: block; max-width: min(260px, 88%); max-height: 200px; min-height: 20px;
  border-radius: 8px; margin: 3px 0 1px; background: var(--site-ui-panel, #0b0f16); object-fit: contain; }
/* A lone custom emoji is a glyph, not a picture: keep it inline and text-sized. Discord serves those from /emojis/. */
.chat-img[src*="/emojis/"] { display: inline-block; max-width: 26px; max-height: 26px; min-height: 0;
  vertical-align: -6px; margin: 0 1px; border-radius: 4px; background: none; }

/* GIF PICKER. Positioned like the emoji popup and hidden until the server confirms a key is configured, so the
   button is absent rather than present-and-broken. */
#chatGifBtn, #cpGifBtn { font: 700 11px system-ui, "Segoe UI"; letter-spacing: .04em; }
/* ⚠ THE SAME MARKUP HANGS IN TWO DIFFERENT BOXES, and a fixed size can only be right in one of them. In the lobby
   dock it sits in a ~372px column that is min(520px, 74vh) tall and OVERFLOW: HIDDEN; on the clan page it sits in
   .cp-compose, which is the full conversation column. Measured with the first draft of these rules:
     · at 900x420 (a landscape phone — this repo's primary mobile orientation) the 342px-tall panel started 32px
       ABOVE the dock, so the dock's overflow:hidden cut off the top — including the entire search field. A picker
       with no search box only ever offers whatever is trending.
     · on the clan page the same panel inflated to 744px wide and, with the columns hard-wired to two, each tile
       became 360x270 inside a 232px scroll window: no whole tile was ever visible, and a 220px preview was being
       upscaled 1.6x.
   So: cap the WIDTH (the tile size then follows from it), and bound the HEIGHT by the viewport rather than by a
   constant. 100%/calc against the container is not usable here — the two containers are a 311px dock and a 56px
   composer row. Guarded by test/gifpicker-browser.mjs, which measures both. */
.gp-pop { display: none; position: absolute; bottom: 52px; left: 8px; right: 8px; max-width: 380px; box-sizing: border-box;
  background: var(--site-ui-panel, #10151d); border: 1px solid var(--site-ui-line, #2a3242); border-radius: 10px; padding: 8px; z-index: 40; }
.gp-pop.show { display: block; }
.gp-pop .gp-q { width: 100%; box-sizing: border-box; background: var(--site-ui-panel, #0b0f16); color: var(--site-ui-ink, #e6e8ec); border: 1px solid var(--site-ui-line, #2a3242);
  border-radius: 7px; padding: 7px 9px; font: 13px system-ui, "Segoe UI"; margin-bottom: 7px; }
/* Columns by WIDTH, not by count: the panel is not the same width on both surfaces, and "two columns" is a rule
   about how big a tile should be, written as if it were a rule about how many fit. min-height:0 lets a flex child
   actually shrink — without it the grid keeps its content height and pushes the search field back out of view.
   Stills only: a grid of ANIMATIONS would start two dozen decoders on the machine that is about to run a match. */
/* ⚠ A MAX-HEIGHT, NEVER A DEFINITE ONE. The first attempt made the grid a flex child (`flex:1 1 auto; min-height: 0`)
   so it would absorb the leftover space — and a grid with a DEFINITE height sizes its auto rows to fit that height.
   The rows computed to "56px 56px 56px 56px" while each tile still painted 124px, so every tile sat on top of the
   one below and elementFromPoint on the first returned the third. align-content:start did not help; the height
   being definite at all was the cause. A max-height lets the rows stay content-sized and simply scrolls.
   The viewport term is what keeps the whole panel inside the dock on a short screen — see the box above. */
.gp-pop .gp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 6px;
  max-height: min(232px, calc(100vh - 260px)); overflow-y: auto; }
/* ⚠ THE ASPECT-RATIO GOES ON THE IMG, NOT ON THE CELL, and this is not a style preference. With `aspect-ratio` on
   the button and `height:100%` on the img, the img's height is a percentage of a height that is itself being
   resolved — so the cell's intrinsic height contribution is ZERO. The grid then sized its rows at 56px while every
   cell still PAINTED itself 124px tall, and each tile overlapped the one below: elementFromPoint on the first tile
   returned the third. Putting the ratio on the img gives it a definite height from its definite column width, the
   cell inherits that as real content height, and the row is 124px because the content is.
   It also removes the load-time layout shift: the box is the right size before the preview arrives, or if it never
   does. Measured by test/gifpicker-browser.mjs, which hit-tests a cell rather than asserting it exists. */
.gp-pop .gp-cell { padding: 0; border: 1px solid var(--site-ui-line, #232b38); border-radius: 7px; overflow: hidden; background: var(--site-ui-panel, #0b0f16);
  cursor: pointer; display: block; }
.gp-pop .gp-cell:hover { border-color: var(--site-ui-accent, #4a9eda); }
.gp-pop .gp-cell img { width: 100%; aspect-ratio: 4 / 3; height: auto; object-fit: cover; display: block; }
.gp-pop .gp-note { grid-column: 1 / -1; color: var(--site-ui-muted, #8b93a3); font: 12px system-ui, "Segoe UI"; padding: 14px 4px; text-align: center; }


/* ==== clanpage.css ==== */
/* clanpage.css — the full-screen clan page (Discord-style 3 columns). Render-only; matches the dark "command" lobby.
   Left = identity + channels + functions · Centre = channel chat + pinned MOTD · Right = role-grouped roster. */

#clanPage {
  --cp-bg: var(--site-ui-panel, #0a0f1a); --cp-rail: var(--site-ui-panel, #0c1422); --cp-center: var(--site-ui-panel, #0e1626); --cp-panel: var(--site-ui-panel, #101b2e);
  --cp-line: var(--site-ui-line, #1d2c45); --cp-line2: var(--site-ui-line, #28395a); --cp-ink: var(--site-ui-ink, #e8eef7); --cp-muted: var(--site-ui-muted, #8da2be); --cp-faint: var(--site-ui-muted, #5f6f88);
  --cp-cyan: var(--site-ui-ink, #5cc8ff); --cp-blue: var(--site-ui-ink, #2f86d6); --cp-good: var(--site-ui-accent, #46c08a); --cp-danger: var(--site-ui-danger, #e0533d);
  /* Twemoji LAST: before the fallback it wins the ASCII digits (keycap bases) wherever the family in front of it
     is missing — which is every phone, since --site-ui-font is 'Segoe UI' in the lobby. See lobby-shell.css. */
  --cp-font: var(--site-ui-font, 'Chakra Petch'), 'Segoe UI', system-ui, sans-serif, 'Twemoji Mozilla';
  position: fixed; inset: 0; z-index: 75; display: none; flex-direction: column;
  background: var(--cp-bg); color: var(--cp-ink); font-family: var(--cp-font); -webkit-font-smoothing: antialiased;
}
#clanPage.show { display: flex; }

/* ---- top bar ---- */
#clanPage .cp-top { display: flex; align-items: center; gap: 16px; padding: 11px clamp(14px, 2.5vw, 26px); background: var(--site-ui-panel, rgba(8, 12, 20, .85)); border-bottom: 1px solid var(--cp-line); }
#clanPage .cp-back { background: none; border: 1px solid var(--cp-line2); color: var(--cp-muted); border-radius: 8px; padding: 7px 13px; font: 600 12px var(--cp-font); letter-spacing: .06em; text-transform: uppercase; cursor: pointer; }
#clanPage .cp-back:hover { color: var(--site-ui-ink, #fff); border-color: var(--cp-cyan); background: var(--site-ui-hover, rgba(92, 200, 255, .1)); }
#clanPage .cp-brand { display: flex; align-items: baseline; gap: 8px; }
#clanPage .lb-logo { font: 700 22px var(--cp-font); letter-spacing: .04em; color: var(--cp-cyan); text-shadow: 0 0 18px var(--site-ui-shadow, rgba(92, 200, 255, .5)); }
#clanPage .cp-brandsub { font: 600 11px var(--cp-font); letter-spacing: .22em; text-transform: uppercase; color: var(--cp-muted); }
#clanPage .cp-acct { margin-left: auto; font: 600 12px var(--cp-font); color: var(--cp-muted); }

/* ---- 3-column grid (in-clan) ---- */
#clanPage[data-view="clan"] .cp-grid { display: grid; }
#clanPage[data-view="discover"] .cp-grid { display: none; }
#clanPage .cp-grid { flex: 1; min-height: 0; grid-template-columns: 232px 1fr 208px; }
#clanPage .cp-left { background: var(--cp-rail); border-right: 1px solid var(--cp-line); padding: 16px 12px; overflow-y: auto; }
#clanPage .cp-center { background: var(--cp-center); display: flex; flex-direction: column; min-height: 0; }
#clanPage .cp-right { background: var(--cp-rail); border-left: 1px solid var(--cp-line); padding: 14px 10px; overflow-y: auto; }
#clanPage ::-webkit-scrollbar { width: 8px; } #clanPage ::-webkit-scrollbar-thumb { background: var(--site-ui-hover, #ffffff1a); border-radius: 4px; }

/* ---- left: identity + channels + functions ---- */
#clanPage .cp-id { display: flex; align-items: center; gap: 11px; margin-bottom: 10px; }
#clanPage .cp-emblem { width: 44px; height: 44px; border-radius: 12px; flex: none; display: flex; align-items: center; justify-content: center; font: 800 16px var(--cp-font); color: var(--site-ui-muted, #07121f); box-shadow: 0 0 0 1px var(--site-ui-shadow, #ffffff22) inset; background: var(--cp-accent, var(--cp-cyan)); }
/* clan LOGO (migration 0020): the header emblem becomes the uploaded image; small logos sit before the name in the browse list */
#clanPage .cp-emblem-img { object-fit: cover; background: var(--site-ui-panel, #0d1520); padding: 0; }
#clanPage .cp-logo { object-fit: cover; border-radius: 6px; flex: none; box-shadow: 0 0 0 1px var(--site-ui-shadow, #ffffff1c) inset; background: var(--site-ui-panel, #0d1520); vertical-align: middle; }
#clanPage .cp-logo-sm { width: 22px; height: 22px; margin-right: 3px; }
#clanPage .cp-cardname { display: inline-flex; align-items: center; gap: 5px; }
/* settings: logo preview + upload/remove */
#clanPage .cp-logorow { display: flex; align-items: center; gap: 14px; }
#clanPage .cp-logoprev { width: 64px; height: 64px; border-radius: 14px; flex: none; overflow: hidden; box-shadow: 0 0 0 1px var(--site-ui-shadow, #ffffff22) inset; background: var(--site-ui-panel, #0d1520); }
#clanPage .cp-logoprev img { width: 100%; height: 100%; object-fit: cover; }
#clanPage .cp-logoph { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font: 800 22px var(--cp-font); color: var(--site-ui-muted, #07121f); background: var(--cp-accent, var(--cp-cyan)); }
#clanPage .cp-logoctrl { display: flex; flex-direction: column; gap: 7px; align-items: flex-start; }
#clanPage .cp-logoup { cursor: pointer; }
#clanPage .cp-logohint { color: var(--cp-muted); font-size: 11.5px; max-width: 260px; }
#clanPage .cp-idname { font: 700 15px var(--cp-font); color: var(--site-ui-ink, #fff); line-height: 1.15; }
#clanPage .cp-idsub { font: 500 11px var(--cp-font); color: var(--cp-muted); margin-top: 2px; }
#clanPage .cp-bio { margin: 0 0 14px; font: 400 12px/1.5 var(--cp-font); color: var(--site-ui-ink, #b9c7dc); white-space: pre-wrap; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden; }
#clanPage .cp-sect { font: 700 10px var(--cp-font); letter-spacing: .16em; text-transform: uppercase; color: var(--cp-faint); margin: 14px 4px 7px; }
#clanPage .cp-channels { display: flex; flex-direction: column; gap: 3px; }
#clanPage .cp-chan { text-align: left; background: none; border: none; border-radius: 7px; padding: 8px 10px; color: var(--cp-muted); font: 600 13px var(--cp-font); letter-spacing: .02em; cursor: pointer; }
#clanPage .cp-chan:hover { background: var(--site-ui-hover, #ffffff0a); color: var(--cp-ink); }
#clanPage .cp-chan.on { background: var(--site-ui-panel, #1b2c49); color: var(--site-ui-ink, #fff); }
#clanPage .cp-funcs { display: flex; flex-direction: column; gap: 3px; }
#clanPage .cp-func { position: relative; text-align: left; background: none; border: none; border-radius: 7px; padding: 9px 10px; color: var(--cp-muted); font: 600 13px var(--cp-font); cursor: pointer; display: flex; align-items: center; gap: 8px; }
#clanPage .cp-func:hover { background: var(--site-ui-hover, #ffffff0a); color: var(--cp-ink); }
#clanPage .cp-func.cp-danger { color: var(--site-ui-danger, #d98a7e); } #clanPage .cp-func.cp-danger:hover { background: var(--site-ui-hover, rgba(224, 83, 61, .12)); color: var(--site-ui-danger, #ffb4a6); }
#clanPage .cp-badge { margin-left: auto; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px; background: var(--cp-danger); color: var(--site-ui-ink, #fff); font: 700 11px var(--cp-font); display: flex; align-items: center; justify-content: center; }

/* ---- centre: chat ---- */
/* the chat wrapper FILLS the centre column and is itself a flex column, so .cp-msgs (flex:1) takes the middle and the
   composer stays PINNED to the bottom — it never rides up/down with the message count (Discord-style). */
#clanPage .cp-chat { flex: 1; min-height: 0; display: flex; flex-direction: column; }
#clanPage .cp-chathead { flex: none; padding: 12px 18px; border-bottom: 1px solid var(--cp-line); }
#clanPage #cpChanName { font: 700 15px var(--cp-font); color: var(--site-ui-ink, #fff); }
#clanPage .cp-motd { margin-top: 7px; font: 500 12.5px var(--cp-font); color: var(--site-ui-gold, #c9b27a); background: var(--site-ui-hover, rgba(201, 178, 122, .08)); border: 1px solid var(--site-ui-accent, rgba(201, 178, 122, .2)); border-radius: 8px; padding: 7px 11px; }
#clanPage .cp-motd:empty { display: none; }
#clanPage .cp-msgs { flex: 1; min-height: 0; overflow-y: auto; padding: 14px 18px; display: flex; flex-direction: column; gap: 11px; }
#clanPage .cp-cmsg { display: flex; gap: 11px; padding: 1px 2px; margin-top: 9px; border-radius: 5px; }
#clanPage .cp-cmsg.cont { margin-top: 0; }   /* grouped continuation rows tighten up under the header */
#clanPage .cp-cmsg:hover { background: var(--site-ui-hover, #ffffff08); }
#clanPage .cp-av { width: 27px; height: 27px; border-radius: 50%; flex: none; display: flex; align-items: center; justify-content: center; font: 700 12px var(--cp-font); color: var(--site-ui-ink, #fff); background: var(--site-ui-accent, #4a6fa5); }
#clanPage .cp-cmsg.me .cp-av { background: var(--cp-cyan); }
#clanPage .cp-av.sp { background: none !important; align-items: flex-start; }
#clanPage .cp-av.sp .cp-hts { display: none; font-size: 10px; color: var(--cp-faint); line-height: 1.7; }
#clanPage .cp-cmsg.cont:hover .cp-av.sp .cp-hts { display: block; }   /* grouped rows reveal a tiny timestamp on hover */
#clanPage .cp-cmeta { display: flex; align-items: baseline; gap: 8px; }
#clanPage .cp-cwho { font: 700 13px var(--cp-font); color: var(--site-ui-ink, #cdd9ea); }
#clanPage .cp-cwho.lnk { cursor: pointer; }
#clanPage .cp-cwho.lnk:hover { text-decoration: underline; }
#clanPage .cp-cmsg.me .cp-cwho { color: var(--cp-cyan); }
#clanPage .cp-cts { font: 500 10px var(--cp-font); color: var(--cp-faint); }
#clanPage .cp-ctxt { font: 400 13.5px/1.45 var(--cp-font); color: var(--cp-ink); word-break: break-word; }   /* emoji enlarged via the shared .em spans */
#clanPage .cp-empty { margin: auto; }
#clanPage .cp-compose { position: relative; flex: none; display: flex; gap: 9px; padding: 12px 16px; border-top: 1px solid var(--cp-line); }
#clanPage .cp-compose input { flex: 1; min-width: 0; background: var(--cp-panel); border: 1px solid var(--cp-line2); border-radius: 9px; color: var(--cp-ink); font: 500 13px var(--cp-font); padding: 10px 12px; outline: none; }
#clanPage .cp-compose input:focus { border-color: var(--cp-cyan); }
#clanPage .cp-compose #cpEmojiBtn { flex: none; background: var(--cp-panel); border: 1px solid var(--cp-line2); border-radius: 9px; color: var(--cp-ink); font-size: 16px; cursor: pointer; padding: 0 10px; }
#clanPage .cp-compose #cpEmojiBtn:hover { border-color: var(--cp-cyan); }
#clanPage #cpEmojiPop { display: none; position: absolute; left: 16px; bottom: 56px; z-index: 5; flex-wrap: wrap; width: min(250px, 80%); gap: 3px; padding: 8px; background: var(--cp-panel); border: 1px solid var(--cp-line2); border-radius: 12px; box-shadow: 0 8px 24px var(--site-ui-shadow, #000b); }
#clanPage #cpEmojiPop.show { display: flex; }
#clanPage #cpEmojiPop button { width: 34px; height: 34px; border-radius: 8px; border: 1px solid var(--cp-line2); background: var(--cp-center); font-size: 19px; line-height: 1; cursor: pointer; padding: 0; }
#clanPage #cpEmojiPop button:hover { background: var(--site-ui-hover, #ffffff14); }
/* clickable links + @mentions inside clan-page messages */
#clanPage .cp-ctxt a { color: var(--cp-cyan); text-decoration: underline; word-break: break-all; }
#clanPage .cp-ctxt .mtag { color: var(--site-ui-danger, #ffd16b); font-weight: 700; }
/* "↩ Replying to …" bar above the clan-page composer */
#clanPage #cpReplyBar { display: flex; align-items: center; gap: 8px; margin: 0 16px; padding: 7px 11px; background: var(--cp-panel); border: 1px solid var(--cp-line2); border-left: 3px solid var(--cp-blue); border-radius: 8px; }
#clanPage #cpReplyBar[hidden] { display: none; }
#clanPage #cpReplyBar .rt { flex: 1; min-width: 0; color: var(--cp-muted); font: 600 12px var(--cp-font); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#clanPage #cpReplyBar #cpReplyX { background: none; border: none; color: var(--cp-muted); cursor: pointer; font-size: 13px; }
#clanPage #cpReplyBar #cpReplyX:hover { color: var(--site-ui-ink, #fff); }

/* ---- right: roster ---- */
#clanPage .cp-rtop { font: 700 11px var(--cp-font); letter-spacing: .1em; text-transform: uppercase; color: var(--cp-muted); padding: 4px 8px 12px; border-bottom: 1px solid var(--cp-line); margin-bottom: 8px; }
#clanPage .cp-rtop b { color: var(--site-ui-ink, #fff); } #clanPage .cp-on { color: var(--cp-good); }
#clanPage .cp-rgroup { margin-bottom: 12px; }
#clanPage .cp-rhead { font: 700 10px var(--cp-font); letter-spacing: .14em; text-transform: uppercase; color: var(--cp-faint); padding: 4px 8px; }
#clanPage .cp-rhead i { font-style: normal; }
#clanPage .cp-mrow { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 7px; opacity: .5; }
#clanPage .cp-mrow.on { opacity: 1; }
#clanPage .cp-mrow.cp-mclick { cursor: pointer; }
#clanPage .cp-mrow:hover { background: var(--site-ui-hover, #ffffff0a); }
#clanPage .cp-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--site-ui-panel, #41506b); flex: none; }
#clanPage .cp-mrow.on .cp-dot, #clanPage .cp-dot.on { background: var(--cp-good); box-shadow: 0 0 7px var(--cp-good); }
#clanPage button:disabled { opacity: .55; cursor: default; }
#clanPage .cp-mname { flex: 1; font: 600 12.5px var(--cp-font); color: var(--cp-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#clanPage .cp-mname i { color: var(--cp-faint); font-weight: 400; }
#clanPage .cp-mini { background: var(--site-ui-hover, #ffffff12); border: none; border-radius: 5px; color: var(--site-ui-ink, #cdd9ea); width: 22px; height: 22px; font-size: 11px; cursor: pointer; padding: 0; }
#clanPage .cp-mini:hover { background: var(--cp-cyan); color: var(--site-ui-muted, #07121f); }
#clanPage .cp-mini.cp-muted { background: var(--site-ui-hover, rgba(224, 83, 61, .25)); color: var(--site-ui-danger, #ffb3a6); }   /* this member's in-game signals are muted */

/* ---- shared small controls ---- */
#clanPage .cp-ok, #clanPage .cp-ghost { border: none; border-radius: 8px; padding: 9px 15px; font: 700 12.5px var(--cp-font); letter-spacing: .03em; cursor: pointer; }
#clanPage .cp-ok { background: var(--cp-blue); color: var(--site-ui-ink, #fff); } #clanPage .cp-ok:hover { background: var(--site-ui-accent, #3a97ea); }
#clanPage .cp-ghost { background: var(--site-ui-hover, #ffffff12); color: var(--site-ui-ink, #cdd9ea); } #clanPage .cp-ghost:hover { background: var(--site-ui-hover, #ffffff20); }
#clanPage .cp-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 4px; border-top: 1px solid var(--cp-line); font: 500 13px var(--cp-font); }
#clanPage .cp-row:first-child { border-top: none; }
#clanPage .cp-ctrl { display: flex; gap: 6px; }
#clanPage .cp-hint { color: var(--cp-muted); font: 500 13px var(--cp-font); padding: 6px 2px; }
#clanPage .cp-msg { color: var(--cp-cyan); font: 600 12px var(--cp-font); min-height: 16px; margin-top: 6px; }
#clanPage .cp-lock { color: var(--cp-faint); }

/* ---- discover (signed-out / no-clan) ---- */
#clanPage[data-view="discover"] .cp-discover { display: block; }
#clanPage .cp-discover { display: none; flex: 1; overflow-y: auto; padding: clamp(20px, 4vw, 48px); }
#clanPage .cp-discover > * { max-width: 620px; margin-left: auto; margin-right: auto; }
#clanPage .cp-panel { background: var(--cp-panel); border: 1px solid var(--cp-line); border-radius: 14px; padding: 18px 20px; margin-bottom: 16px; }
#clanPage .cp-panel h2 { margin: 0 0 12px; font: 700 19px var(--cp-font); }
#clanPage .cp-panel h3 { margin: 0 0 10px; font: 700 14px var(--cp-font); color: var(--cp-muted); }
#clanPage .cp-create { display: flex; gap: 8px; flex-wrap: wrap; }
#clanPage .cp-create input { flex: 1; min-width: 120px; background: var(--cp-bg); border: 1px solid var(--cp-line2); border-radius: 8px; color: var(--cp-ink); font: 500 14px var(--cp-font); padding: 10px 12px; outline: none; }
#clanPage .cp-create input:focus { border-color: var(--cp-cyan); }
#clanPage .cp-search { width: 100%; box-sizing: border-box; background: var(--cp-bg); border: 1px solid var(--cp-line2); border-radius: 8px; color: var(--cp-ink); font: 500 14px var(--cp-font); padding: 10px 12px; outline: none; margin-bottom: 10px; }
#clanPage .cp-search:focus { border-color: var(--cp-cyan); }

/* ---- overlay sub-panel (settings / party / requests / invite) ---- */
#clanPage #cpOverlay { position: absolute; inset: 0; z-index: 5; display: none; align-items: center; justify-content: center; background: var(--site-ui-panel, #05080faa); padding: 24px; }
#clanPage #cpOverlay.show { display: flex; }
#clanPage .cp-ovcard { width: min(440px, 100%); max-height: 86%; overflow-y: auto; background: var(--cp-panel); border: 1px solid var(--cp-line2); border-radius: 14px; }
#clanPage .cp-ovhead { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid var(--cp-line); }
#clanPage .cp-ovhead h3 { margin: 0; font: 700 16px var(--cp-font); }
#clanPage .cp-ovx { background: none; border: none; color: var(--cp-muted); font-size: 22px; line-height: 1; cursor: pointer; }
#clanPage .cp-ovx:hover { color: var(--site-ui-ink, #fff); }
#clanPage .cp-ovbody { padding: 16px 18px; display: flex; flex-direction: column; gap: 12px; }
#clanPage .cp-list { display: flex; flex-direction: column; }

/* --- browse: expandable clan card + inline detail (Join / Request / Cancel) --- */
#clanPage .cp-clancard { border: 1px solid var(--cp-line); border-radius: 10px; margin-bottom: 8px; overflow: hidden; background: var(--site-ui-hover, rgba(255, 255, 255, .015)); }
#clanPage .cp-clancard.exp { border-color: var(--cp-line2); }
#clanPage .cp-clanhead { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 11px 13px; cursor: pointer; border-top: none; }
#clanPage .cp-clanhead:hover { background: var(--site-ui-hover, rgba(92, 200, 255, .06)); }
#clanPage .cp-clanhead b { color: var(--site-ui-ink, #fff); } #clanPage .cp-clanhead i { color: var(--cp-muted); font-style: normal; font-size: 12px; }
#clanPage .cp-caret { color: var(--cp-faint); font-size: 12px; }
#clanPage .cp-reqtag { font: 700 9px var(--cp-font); letter-spacing: .08em; text-transform: uppercase; color: var(--site-ui-muted, #07121f); background: var(--cp-cyan); padding: 2px 6px; border-radius: 6px; }
#clanPage .cp-clandetail { padding: 2px 14px 14px; border-top: 1px solid var(--cp-line); }
/* the clan's public "About" blurb — the recruitment advertisement the leader writes (replaces the old member roster) */
#clanPage .cp-dabout { margin: 12px 0 10px; padding: 11px 13px; background: var(--site-ui-hover, rgba(255, 255, 255, .03)); border: 1px solid var(--cp-line); border-left: 3px solid var(--cp-cyan); border-radius: 8px; color: var(--site-ui-ink, #cdd8ea); font: 400 13px/1.55 var(--cp-font); white-space: pre-wrap; overflow-wrap: anywhere; }
#clanPage .cp-dabout-empty { color: var(--cp-faint); font-style: italic; border-left-color: var(--cp-line2); }
#clanPage .cp-drank { margin: 8px 0; color: var(--cp-muted); font: 600 12px var(--cp-font); }
#clanPage .cp-daction { display: flex; flex-direction: column; gap: 8px; }
#clanPage .cp-reqd { color: var(--cp-good); font: 600 12px var(--cp-font); }
/* officer join-request review rows (name + the requester's note + an optional decline reason) */
#clanPage .cp-reqrow { display: flex; flex-direction: column; gap: 7px; padding: 11px 4px; border-top: 1px solid var(--cp-line); }
#clanPage .cp-reqrow:first-child { border-top: none; }
#clanPage .cp-reqtop { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
#clanPage .cp-reqwho { font: 500 13px var(--cp-font); color: var(--cp-ink); }
#clanPage .cp-reqnote { color: var(--cp-faint); font: italic 400 12px var(--cp-font); background: var(--cp-bg); border-left: 2px solid var(--cp-line2); border-radius: 4px; padding: 6px 9px; overflow-wrap: anywhere; }
#clanPage .cp-denymsg { width: 100%; box-sizing: border-box; background: var(--cp-bg); border: 1px solid var(--cp-line2); border-radius: 8px; color: var(--cp-ink); font: 400 12px var(--cp-font); padding: 7px 10px; outline: none; }
#clanPage .cp-denymsg:focus { border-color: var(--cp-cyan); }
#clanPage .cp-field { display: flex; flex-direction: column; gap: 5px; font: 700 11px var(--cp-font); letter-spacing: .08em; text-transform: uppercase; color: var(--cp-muted); }
#clanPage .cp-field i { color: var(--cp-faint); text-transform: none; letter-spacing: 0; }
#clanPage .cp-field textarea { background: var(--cp-bg); border: 1px solid var(--cp-line2); border-radius: 8px; color: var(--cp-ink); font: 400 13px var(--cp-font); padding: 9px 11px; outline: none; resize: vertical; min-height: 56px; }
#clanPage .cp-field textarea:focus { border-color: var(--cp-cyan); }
#clanPage .cp-field textarea.cp-bigfield { min-height: 104px; line-height: 1.5; }   /* the public About blurb gets room for a paragraph */
/* Discord bridge section in clan settings (blurple accent, separated from the fields above) */
#clanPage .cp-discord { gap: 8px; padding-top: 12px; margin-top: 4px; border-top: 1px solid var(--cp-line2); }
#clanPage .cp-discord button { align-self: flex-start; }
#clanPage .cp-discord > span { color: var(--site-ui-ink, #7b83ec); }
#clanPage .cp-discord .cp-hint b { color: var(--cp-ink); }
/* easy-flow: "Add bot to Discord" (blurple) + the type-in-Discord code */
#clanPage .cp-dlink { align-self: flex-start; display: inline-block; text-decoration: none; background: var(--site-ui-accent, #5865F2); color: var(--site-ui-ink, #fff); border-radius: 8px; padding: 9px 13px; font: 700 13px var(--cp-font); }
#clanPage .cp-dlink:hover { filter: brightness(1.1); }
#clanPage .cp-dcode { font: 800 22px ui-monospace, monospace; letter-spacing: 3px; color: var(--site-ui-ink, #fff); background: var(--cp-bg); border: 1px dashed var(--site-ui-accent, #7b83ec); border-radius: 8px; padding: 10px 14px; text-align: center; margin: 4px 0; user-select: all; }
#clanPage .cp-copy { align-self: flex-start; font-size: 12px; padding: 6px 10px; }
/* roster member avatar (Discord image or coloured initial), small circle next to the online dot */
#clanPage .cp-mav { flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; overflow: hidden; display: inline-flex; align-items: center; justify-content: center; font: 800 11px var(--cp-font); color: var(--site-ui-ink, #fff); }
#clanPage .cp-adv { margin-top: 6px; }
#clanPage .cp-adv summary { cursor: pointer; color: var(--cp-muted); font: 600 12px var(--cp-font); }
#clanPage .cp-adv input { margin-top: 8px; }
#clanPage .cp-colour input { width: 54px; height: 34px; padding: 2px; background: var(--cp-bg); border: 1px solid var(--cp-line2); border-radius: 8px; cursor: pointer; }
#clanPage .cp-chk { display: flex; align-items: flex-start; gap: 9px; font: 500 13px/1.4 var(--cp-font); color: var(--cp-ink); cursor: pointer; }
#clanPage .cp-chk input { margin-top: 2px; }

/* ---- role chips (roster) + roles & permissions editor ---- */
#clanPage .cp-mbody { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
#clanPage .cp-mbody .cp-mname { flex: none; }
#clanPage .cp-mchips { display: flex; flex-wrap: wrap; gap: 3px; }
#clanPage .cp-rchip { display: inline-block; padding: 1px 7px; border-radius: 10px; font: 700 9px var(--cp-font); letter-spacing: .04em; text-transform: uppercase; color: var(--rc, var(--cp-cyan)); border: 1px solid var(--rc, var(--cp-cyan)); }
#clanPage .cp-rdot { width: 11px; height: 11px; border-radius: 3px; background: var(--rc, var(--cp-cyan)); flex: none; display: inline-block; vertical-align: middle; }
#clanPage .cp-rrow { gap: 10px; }
#clanPage .cp-rname { flex: 1; font: 600 13px var(--cp-font); }
#clanPage .cp-rname i { color: var(--cp-faint); font-weight: 400; font-style: normal; font-size: 11px; }
#clanPage .cp-rname small { color: var(--cp-muted); font-size: 11px; }
#clanPage .cp-perms { display: flex; flex-direction: column; gap: 8px; }
#clanPage .cp-perm { display: flex; align-items: center; gap: 8px; font: 500 13px var(--cp-font); color: var(--cp-ink); cursor: pointer; }
#clanPage .cp-perm i { color: var(--cp-faint); font-style: normal; }
#clanPage .cp-perm input[disabled] { opacity: .4; }
#clanPage .cp-frow { display: flex; gap: 10px; align-items: flex-end; }
#clanPage .cp-frow .cp-field { flex: 1; }
#clanPage .cp-frow input[type=number] { width: 100%; box-sizing: border-box; background: var(--cp-bg); border: 1px solid var(--cp-line2); border-radius: 8px; color: var(--cp-ink); font: 600 14px var(--cp-font); padding: 9px 11px; outline: none; }
#clanPage .cp-field input[type=text], #clanPage .cp-field > input:not([type]) { background: var(--cp-bg); border: 1px solid var(--cp-line2); border-radius: 8px; color: var(--cp-ink); font: 500 14px var(--cp-font); padding: 9px 11px; outline: none; }
#clanPage .cp-field input:focus { border-color: var(--cp-cyan); }
#clanPage .cp-sect2 { font: 700 10px var(--cp-font); letter-spacing: .14em; text-transform: uppercase; color: var(--cp-faint); margin-top: 2px; }

/* ---- announcements ---- */
#clanPage .cp-annin { width: 100%; box-sizing: border-box; background: var(--cp-bg); border: 1px solid var(--cp-line2); border-radius: 8px; color: var(--cp-ink); font: 500 14px var(--cp-font); padding: 9px 11px; outline: none; }
#clanPage textarea.cp-annin { min-height: 64px; resize: vertical; }
#clanPage .cp-annin:focus { border-color: var(--cp-cyan); }
#clanPage .cp-iconpick { display: flex; gap: 8px; flex: 1; align-items: center; }
#clanPage .cp-ic { display: inline-flex; align-items: center; font-size: 17px; cursor: pointer; opacity: .5; }
#clanPage .cp-ic input { display: none; }
#clanPage .cp-ic:has(input:checked) { opacity: 1; transform: scale(1.15); }
#clanPage .cp-anns { display: flex; flex-direction: column; gap: 9px; }
#clanPage .cp-ann { background: var(--cp-bg); border: 1px solid var(--cp-line); border-left: 3px solid var(--cp-cyan); border-radius: 9px; padding: 9px 12px; }
#clanPage .cp-annhead { font: 600 12px var(--cp-font); color: var(--site-ui-ink, #cdd9ea); }
#clanPage .cp-annhead b { color: var(--cp-cyan); }
#clanPage .cp-anntime { color: var(--cp-faint); font-weight: 400; float: right; }
#clanPage .cp-anntext { margin-top: 4px; font: 500 13px/1.45 var(--cp-font); color: var(--cp-ink); word-break: break-word; }

/* ---- chat polish: load-older, day separators, edited marker, hover edit/delete ---- */
#clanPage .cp-loadold { display: block; margin: 0 auto 8px; background: none; border: 1px solid var(--cp-line2); color: var(--cp-muted); border-radius: 8px; padding: 5px 14px; font: 600 11px var(--cp-font); letter-spacing: .06em; text-transform: uppercase; cursor: pointer; }
#clanPage .cp-loadold:hover { color: var(--cp-ink); border-color: var(--cp-cyan); }
#clanPage .cp-sys { text-align: center; color: var(--cp-faint); font: italic 500 12px var(--cp-font); margin: 3px 0; }
#clanPage .cp-daysep { display: flex; align-items: center; gap: 10px; margin: 8px 0; color: var(--cp-faint); font: 700 9px var(--cp-font); letter-spacing: .14em; text-transform: uppercase; }
#clanPage .cp-daysep::before, #clanPage .cp-daysep::after { content: ""; flex: 1; height: 1px; background: var(--cp-line); }
#clanPage .cp-cedit { color: var(--cp-faint); font-size: 10px; font-style: italic; margin-left: 5px; }
#clanPage .cp-cmsg { position: relative; }
#clanPage .cp-cmenu { display: none; position: absolute; top: -2px; right: 0; background: var(--cp-center); border: 1px solid var(--cp-line2); border-radius: 6px; color: var(--cp-muted); font-size: 15px; line-height: 1; padding: 1px 7px; cursor: pointer; }
#clanPage .cp-cmsg:hover .cp-cmenu { display: block; }
#clanPage .cp-cmenu:hover { color: var(--cp-cyan); }
@media (hover: none) { #clanPage .cp-cmenu { display: block; opacity: .5; } }
/* clickable, same as the dock's .rquote — see the note there. The jump/shake keyframes live in chathub.css, which
   both surfaces load, so the two cannot drift into different animations. */
#clanPage .cp-rquote { font-size: 12px; color: var(--cp-muted); margin: 1px 0 3px; padding-left: 8px; border-left: 2px solid var(--cp-line2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; border-radius: 0 4px 4px 0; transition: color .12s, background .12s, border-color .12s; }
#clanPage .cp-rquote:hover { color: var(--cp-ink); background: var(--site-ui-hover, #5cc8ff14); border-left-color: var(--cp-cyan); }
#clanPage .cp-rquote b { color: var(--cp-cyan); }
#clanPage .cp-rquote a { color: var(--cp-cyan); }
#clanPage .cp-reacts { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; }
#clanPage .cp-react { display: inline-flex; align-items: center; gap: 3px; background: var(--cp-panel); border: 1px solid var(--cp-line2); border-radius: 10px; padding: 1px 7px; color: var(--cp-ink); font: 600 11px var(--cp-font); cursor: pointer; }
#clanPage .cp-react .re { font-size: 12px; }
#clanPage .cp-react:hover { border-color: var(--cp-cyan); }
#clanPage .cp-react.mine { background: var(--site-ui-panel, #12324a); border-color: var(--cp-cyan); color: var(--site-ui-ink, #bfe4ff); }

/* (the party banner + member-list popup moved to the GLOBAL public/styles/party.css) */

/* ---- responsive ---- */
@media (max-width: 760px) {
  #clanPage .cp-grid { grid-template-columns: 1fr; grid-auto-rows: min-content; overflow-y: auto; }
  #clanPage .cp-left { border-right: none; border-bottom: 1px solid var(--cp-line); max-height: 50vh; }    /* keep independent scroll so bottom functions stay reachable */
  #clanPage .cp-center { min-height: 60vh; }
  #clanPage .cp-right { border-left: none; border-top: 1px solid var(--cp-line); max-height: 40vh; }
}

/* Persistent desktop rails; compact navigation on smaller screens. */
#clanPage [hidden] { display:none !important; }
#clanPage[data-view="discover"] .cp-workspace-head { display:none; }
#clanPage .cp-workspace-head { padding:10px 16px; border-bottom:1px solid var(--cp-line); flex:none; }
#clanPage .cp-id { margin:0; gap:10px; }
#clanPage .cp-emblem { width:32px; height:32px; border-radius:8px; font-size:12px; }
#clanPage .cp-idmeta { min-width:0; }
#clanPage .cp-idname { font-size:15px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
#clanPage .cp-idsub { font-size:11px; margin-top:2px; }
#clanPage .cp-tabs { display:none; }
#clanPage button:focus-visible { outline:2px solid var(--site-ui-gold,#bca784); outline-offset:-2px; }
#clanPage[data-view="clan"] .cp-grid { width:100%; margin:0; padding:0; display:grid; grid-template-columns:200px minmax(0,1fr) 200px; overflow:hidden; }
#clanPage .cp-grid > [role="tabpanel"] { max-height:none; min-height:0; min-width:0; box-sizing:border-box; }
#clanPage #cpLeft,#clanPage #cpRight { padding:16px 12px; overflow-y:auto; background:var(--site-ui-hover,#ffffff04); }
#clanPage #cpCenter { overflow:hidden; border:0; border-radius:0; }
#clanPage .cp-chan { min-height:40px; }
#clanPage .cp-chan.on { background:var(--site-ui-hover,#ffffff12); }
#clanPage .cp-chan:disabled { opacity:.4; cursor:default; }
#clanPage .cp-func { min-height:40px; font-size:12px; }
#clanPage .cp-mrow { min-height:44px; }
#clanPage .cp-compose { border-top:0; padding:12px 16px 18px; }
#clanPage .cp-compose input { background:var(--site-ui-hover,#ffffff09); }
@media (max-width:999px) {
  #clanPage .cp-workspace-head { padding:8px 12px 0; }
  #clanPage .cp-tabs { display:flex; width:100%; gap:4px; margin-top:6px; }
  #clanPage .cp-tabs button { flex:1; min-height:40px; border:0; border-bottom:2px solid transparent; background:none; color:var(--cp-muted); font:600 12px var(--cp-font); cursor:pointer; }
  #clanPage .cp-tabs button[aria-selected="true"] { color:var(--cp-ink); border-bottom-color:var(--site-ui-gold,#bca784); }
  #clanPage[data-view="clan"] .cp-grid { display:flex; flex-direction:column; }
  .lobby-active #clanPage .cp-grid > [role="tabpanel"] { flex:1; min-height:0; max-height:none; width:100%; border:0; }
  #clanPage .cp-compose { padding:10px; gap:6px; }
  #clanPage .cp-compose input { font-size:16px; }
  #clanPage .cp-top { gap:12px; }
  #clanPage .cp-brandsub { display:none; }
}

/* ---- join mode: settings choice, browse chips, and the applicant dialog (feature: screening question) ---- */
#clanPage .cp-joinmode { gap: 8px; }
#clanPage .cp-radio { display: grid; grid-template-columns: auto 1fr; gap: 3px 9px; align-items: start; padding: 9px 11px; border: 1px solid var(--cp-line); border-radius: 9px; cursor: pointer; }
#clanPage .cp-radio:hover { border-color: var(--cp-line2); }
#clanPage .cp-radio input { margin-top: 3px; grid-row: span 2; }
#clanPage .cp-radio b { color: var(--cp-ink); font: 600 13px var(--cp-font); }
#clanPage .cp-radio i { color: var(--cp-muted); font: 400 11.5px/1.45 var(--cp-font); font-style: normal; }
#clanPage .cp-radio:has(input:checked) { border-color: var(--cp-cyan); background: var(--site-ui-hover, rgba(92, 200, 255, .05)); }
#clanPage .cp-qbox { margin: 2px 0 0 24px; padding-left: 11px; border-left: 2px solid var(--cp-line2); display: flex; flex-direction: column; gap: 7px; }
#clanPage .cp-qbox.off { opacity: .38; pointer-events: none; }   /* the question only applies in approval mode */
/* browse card: BOTH modes carry a chip, so "open" is never signalled by an absent badge */
#clanPage .cp-mode { font: 700 9px var(--cp-font); letter-spacing: .07em; padding: 2px 6px; border-radius: 6px; vertical-align: middle; }
#clanPage .cp-mode-open { color: var(--site-ui-muted, #0c1a12); background: var(--site-ui-accent, #56c98a); }
#clanPage .cp-mode-req { color: var(--site-ui-muted, #1e1607); background: var(--site-ui-gold-well, #e0b053); }
/* officer review: the question the applicant answered, next to their answer */
#clanPage .cp-reqqa { display: flex; flex-direction: column; gap: 5px; }
#clanPage .cp-reqq { color: var(--cp-muted); font: 600 12px/1.45 var(--cp-font); overflow-wrap: anywhere; }
#clanPage .cp-reqdrift { color: var(--cp-faint); font: italic 400 11px var(--cp-font); }
#clanPage .cp-reqa { color: var(--cp-ink); font: 400 12.5px/1.5 var(--cp-font); background: var(--cp-bg); border-left: 2px solid var(--cp-cyan); border-radius: 4px; padding: 7px 10px; white-space: pre-wrap; overflow-wrap: anywhere; }
#clanPage .cp-blockrow { color: var(--cp-muted); font: 500 12px var(--cp-font); }
#clanPage .cp-blockrow i { color: var(--cp-faint); font-style: normal; }

/* the apply dialog lives on <body> (opened from the clan page AND the chat dock), so it is NOT scoped to #clanPage */
#applyModal { position: fixed; inset: 0; z-index: 90; display: none; align-items: center; justify-content: center; background: var(--site-ui-panel, rgba(4, 8, 15, .68)); padding: 18px; }
#applyModal.show { display: flex; }
#applyModal .ap-card { width: min(520px, 100%); max-height: 86vh; overflow: auto; background: var(--site-ui-panel, #0e1626); border: 1px solid var(--site-ui-line, #28395a); border-radius: 14px; box-shadow: 0 18px 46px var(--site-ui-shadow, rgba(0, 0, 0, .5)); }
#applyModal .ap-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--site-ui-line, #1d2c45); }
#applyModal .ap-head h3 { margin: 0; color: var(--site-ui-ink, #e8eef7); font: 600 15px var(--site-ui-font, 'Chakra Petch'), system-ui; }
#applyModal .ap-x { background: none; border: none; color: var(--site-ui-muted, #8da2be); font-size: 20px; line-height: 1; cursor: pointer; }
#applyModal .ap-body { padding: 15px 16px 17px; display: flex; flex-direction: column; gap: 12px; }
#applyModal .ap-hint { margin: 0; color: var(--site-ui-muted, #8da2be); font: 400 12.5px var(--site-ui-font, 'Chakra Petch'), system-ui; }
#applyModal .ap-q { background: var(--site-ui-hover, rgba(92, 200, 255, .07)); border-left: 3px solid var(--site-ui-accent, #5cc8ff); border-radius: 8px; padding: 10px 12px; }
#applyModal .ap-qlabel { display: block; color: var(--site-ui-muted, #8da2be); font: 600 10px var(--site-ui-font, 'Chakra Petch'), system-ui; letter-spacing: .08em; text-transform: uppercase; margin-bottom: 4px; }
#applyModal .ap-qtext { color: var(--site-ui-ink, #fff); font: 500 13.5px/1.5 var(--site-ui-font, 'Chakra Petch'), system-ui; white-space: pre-wrap; overflow-wrap: anywhere; }
#applyModal .ap-field { display: flex; flex-direction: column; gap: 6px; position: relative; }
#applyModal .ap-field > span { color: var(--site-ui-muted, #8da2be); font: 600 11.5px var(--site-ui-font, 'Chakra Petch'), system-ui; }
#applyModal .ap-field i { color: var(--site-ui-muted, #5f6f88); font-style: normal; }
#applyModal .ap-field textarea { min-height: 104px; resize: vertical; background: var(--site-ui-panel, #0b1220); border: 1px solid var(--site-ui-line, #28395a); border-radius: 8px; color: var(--site-ui-ink, #e8eef7); font: 400 13px var(--site-ui-font, 'Chakra Petch'), system-ui; padding: 9px 11px; outline: none; }
#applyModal .ap-field textarea:focus { border-color: var(--site-ui-accent, #5cc8ff); }
#applyModal .ap-count { align-self: flex-end; color: var(--site-ui-muted, #5f6f88); font: 500 11px var(--site-ui-font, 'Chakra Petch'), system-ui; }
#applyModal .ap-count.over { color: var(--site-ui-danger, #ff7b72); }
#applyModal .ap-msg { color: var(--site-ui-muted, #8da2be); font: 500 12px var(--site-ui-font, 'Chakra Petch'), system-ui; min-height: 16px; }
#applyModal .ap-msg.warn { color: var(--site-ui-danger, #ffb454); }
#applyModal .ap-actions { display: flex; gap: 9px; }
#applyModal .ap-ok { flex: 1; background: var(--site-ui-accent, #2f86d6); border: none; border-radius: 9px; color: var(--site-ui-ink, #fff); font: 700 13px var(--site-ui-font, 'Chakra Petch'), system-ui; padding: 11px 14px; cursor: pointer; }
#applyModal .ap-ok:disabled { opacity: .55; cursor: default; }
#applyModal .ap-ghost { background: none; border: 1px solid var(--site-ui-line, #28395a); border-radius: 9px; color: var(--site-ui-muted, #8da2be); font: 600 13px var(--site-ui-font, 'Chakra Petch'), system-ui; padding: 11px 16px; cursor: pointer; }

/* THE PAGE-LEVEL FEEDBACK LINE. #cpMsg used to be emitted only inside the discover markup, which renderClan() wipes
   on the way into a clan — so for anyone IN a clan the element every error was written to did not exist and the
   text was dropped silently (Leave clan, Disband, Post recruitment, a refused kick/promote/mute). It is now mounted
   in index.html's static shell, which is why it needs its own placement here rather than inheriting the in-flow
   .cp-msg above: it floats over the grid instead of pushing it, and it is invisible until there is something to say
   so it never occupies space in the normal case. */
#clanPage .cp-msg-page {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%) translateY(8px);
  max-width: min(560px, 90vw); padding: 10px 16px; border-radius: 10px;
  background: var(--site-ui-panel, #10151c); border: 1px solid var(--cp-cyan); color: var(--cp-cyan);
  font: 600 13px var(--cp-font); text-align: center; margin-top: 0;
  opacity: 0; pointer-events: none; transition: opacity .15s ease, transform .15s ease; z-index: 80;
}
#clanPage .cp-msg-page.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* IN A MATCH. Distinct from the online dot on purpose — "reachable" and "playing right now" are different answers,
   and on a game whose matches last 1-4 minutes the second is the one a member is actually looking for. Amber rather
   than a second shade of green so it reads at a glance in a roster of twenty; the soft pulse is what makes it
   findable without scanning every row. */
#clanPage .cp-dot.playing { background: var(--site-ui-gold-well, #e0a33a); box-shadow: 0 0 0 3px var(--site-ui-shadow, rgba(224,163,58,.18)); animation: cpPlay 1.9s ease-in-out infinite; }
@keyframes cpPlay { 0%,100% { opacity: 1 } 50% { opacity: .55 } }
#clanPage .cp-play { color: var(--site-ui-danger, #e0a33a); }
/* …and the same on the MEMBER ROW, as TEXT, as its OWN element — NOT inside .cp-mname.
   That was the first attempt and it rendered perfectly and was invisible: .cp-mname truncates with an ellipsis in a
   narrow column (CrimsonDrake319…), so anything appended after the name is clipped away with it. A sibling on its
   own line survives. Same reason the party panel keeps its status as a sibling rather than part of the name.
   Original note: A 9px dot alone was the first attempt and it is invisible in a roster of
   twenty — the operator could see the header count and still not find WHO. The party panel already solved this the
   right way (party.mjs renders "● in a match" as a label, not a colour), so this matches it rather than inventing a
   second visual language for the same fact. */
#clanPage .cp-inmatch { display: block; color: var(--site-ui-danger, #e0a33a); font: 600 10.5px var(--cp-font); letter-spacing: .02em; margin-top: 1px; }

/* "X is writing…" on the clan page — same contract as the dock: zero height when nobody is typing, so an idle
   channel carries no blank strip above the composer. */
#clanPage .cp-typing { height: 0; overflow: hidden; opacity: 0; color: var(--site-ui-ink, #8fb6d9); font: 500 11.5px var(--cp-font);
  padding: 0 18px; white-space: nowrap; text-overflow: ellipsis; transition: height .12s ease, opacity .12s ease; }
#clanPage .cp-typing.show { height: 17px; opacity: .85; }

/* ---- DIRECT MESSAGES IN THE LEFT COLUMN ----
   The clan page is full-screen, so answering a DM used to mean leaving it, going through the lobby, and coming back.
   That round trip is the difference between replying and meaning to reply later. Same socket, same conversation list
   the lobby dock uses (chat.service.dmConversations), so the two surfaces cannot disagree about who or how many. */
#clanPage .cp-dmhead { margin: 14px 0 6px; padding: 0 4px; color: var(--cp-muted); font: 700 10px var(--cp-font); letter-spacing: .12em; text-transform: uppercase; }
/* Collapsed the list is at most 5 rows, so the CLAN controls below it keep a predictable position. Expanded it is
   still capped and scrolls inside itself — never pushing the rest of the column off the fold. */
#clanPage .cp-dms { display: flex; flex-direction: column; gap: 2px; max-height: 30vh; overflow-y: auto; }
#clanPage .cp-dmmore { margin-top: 3px; background: none; border: 0; color: var(--cp-faint); font: 600 11px var(--cp-font);
  text-align: left; padding: 4px 7px; cursor: pointer; border-radius: 6px; }
#clanPage .cp-dmmore:hover { color: var(--cp-cyan); background: var(--site-ui-hover, #ffffff0a); }
#clanPage .cp-dm { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; background: none; border: 0;
  border-radius: 7px; padding: 5px 7px; color: var(--cp-muted); font: 600 13px var(--cp-font); cursor: pointer; }
#clanPage .cp-dm:hover { background: var(--site-ui-hover, #ffffff0d); color: var(--cp-ink); }
#clanPage .cp-dm.on { background: var(--site-ui-hover, #5cc8ff1f); color: var(--cp-ink); }
/* an unread thread is white and bold — the list should be readable at a glance, not scanned for red pills */
#clanPage .cp-dm.un { color: var(--site-ui-ink, #fff); font-weight: 700; }
#clanPage .cp-dmav { flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  color: var(--site-ui-ink, #fff); font: 700 11px var(--cp-font); }
#clanPage .cp-dmname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#clanPage .cp-dmn { flex: 0 0 auto; background: var(--site-ui-gold-well, #e0533d); color: var(--site-ui-ink, #fff); font: 700 10px system-ui; border-radius: 9px; padding: 1px 6px; }
#clanPage .cp-dmempty { padding: 6px 7px; color: var(--cp-muted); font: 12px var(--cp-font); opacity: .75; }
/* the full conversation list, in the page's own overlay — older threads are a place you visit, not something that
   expands the sidebar you were using */
#clanPage .cp-dmall { display: flex; flex-direction: column; gap: 3px; max-height: 56vh; overflow-y: auto; }
#clanPage .cp-dmall .cp-dm { padding: 8px 10px; font-size: 13.5px; }
#clanPage .cp-dmall .cp-dmav { width: 26px; height: 26px; font-size: 12px; }


/* ==== profile.css ==== */
/* profile.css — the full-screen USER PROFILE page. Render-only; deliberately reuses the clan page's dark "command"
   palette so the two full-screen surfaces read as one product. Single centred column (not the clan page's 3 rails):
   this is a settings surface, and settings read best as one narrow column of stacked cards. */

#profilePage {
  --pf-bg: var(--site-ui-panel, #0a0f1a); --pf-card: var(--site-ui-panel, #101b2e); --pf-line: var(--site-ui-line, #1d2c45); --pf-line2: var(--site-ui-line, #28395a);
  --pf-ink: var(--site-ui-ink, #e8eef7); --pf-muted: var(--site-ui-muted, #8da2be); --pf-faint: var(--site-ui-muted, #5f6f88);
  --pf-cyan: var(--site-ui-ink, #5cc8ff); --pf-good: var(--site-ui-accent, #46c08a); --pf-danger: var(--site-ui-danger, #e0533d);
  /* emoji font last — it owns the ASCII digits; see lobby-shell.css */
  --pf-font: var(--site-ui-font, 'Chakra Petch'), 'Segoe UI', system-ui, sans-serif, 'Twemoji Mozilla';
  position: fixed; inset: 0; z-index: 75; display: none; flex-direction: column;
  background: var(--pf-bg); color: var(--pf-ink); font-family: var(--pf-font); -webkit-font-smoothing: antialiased;
}
#profilePage.show { display: flex; }

/* ---- top bar (same shape + z-order as the clan page's, so the two never look like different apps) ---- */
#profilePage .pf-top { display: flex; align-items: center; gap: 16px; padding: 11px clamp(14px, 2.5vw, 26px);
  background: var(--site-ui-panel, rgba(8, 12, 20, .85)); border-bottom: 1px solid var(--pf-line); }
#profilePage .pf-back { background: none; border: 1px solid var(--pf-line2); color: var(--pf-muted); border-radius: 8px;
  padding: 7px 13px; font: 600 12px var(--pf-font); letter-spacing: .06em; text-transform: uppercase; cursor: pointer; }
#profilePage .pf-back:hover { color: var(--site-ui-ink, #fff); border-color: var(--pf-cyan); background: var(--site-ui-hover, rgba(92, 200, 255, .1)); }
#profilePage .pf-brand { display: flex; align-items: baseline; gap: 8px; }
#profilePage .lb-logo { font: 700 22px var(--pf-font); letter-spacing: .04em; color: var(--pf-cyan); text-shadow: 0 0 18px var(--site-ui-shadow, rgba(92, 200, 255, .5)); }
#profilePage .pf-brandsub { font: 600 11px var(--pf-font); letter-spacing: .22em; text-transform: uppercase; color: var(--pf-muted); }

#profilePage .pf-scroll { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 26px 16px 60px; }
#profilePage .pf-col { max-width: 620px; margin: 0 auto; display: flex; flex-direction: column; gap: 18px; }

/* ---- identity header ---- */
#profilePage .pf-head { display: flex; align-items: center; gap: 16px; }
#profilePage .pf-av { width: 68px; height: 68px; flex: none; border-radius: 16px; display: grid; place-items: center;
  font: 700 28px var(--pf-font); color: var(--site-ui-muted, #0a0f1a); overflow: hidden; }
#profilePage .pf-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
#profilePage .pf-who { min-width: 0; }
#profilePage .pf-name { font: 700 25px var(--pf-font); letter-spacing: .02em; overflow-wrap: anywhere; }
#profilePage .pf-headflag { width: 30px; height: 20px; object-fit: fill; vertical-align: middle; margin-left: 10px; border-radius: 2px; }   /* 3:2 + fill — the banner's shape, so a flag looks identical everywhere it appears */
#profilePage .pf-sub { font: 500 12.5px var(--pf-font); color: var(--pf-muted); margin-top: 3px; }
#profilePage .pf-clantag { color: var(--pf-cyan); font-weight: 700; }

/* ---- stat row (the SAME numbers the public profile card shows — one query, one truth) ---- */
#profilePage .pf-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
#profilePage .pf-stat { background: var(--pf-card); border: 1px solid var(--pf-line); border-radius: 12px; padding: 12px 14px; }
#profilePage .pf-stat b { display: block; font: 700 21px var(--pf-font); color: var(--pf-cyan); }
#profilePage .pf-stat span { font: 600 10.5px var(--pf-font); letter-spacing: .1em; text-transform: uppercase; color: var(--pf-faint); }

/* ---- section cards ---- */
#profilePage .pf-card { background: var(--pf-card); border: 1px solid var(--pf-line); border-radius: 14px; padding: 18px 20px; }
#profilePage .pf-card > h2 { margin: 0 0 4px; font: 700 15px var(--pf-font); letter-spacing: .05em; text-transform: uppercase; color: var(--pf-ink); }
#profilePage .pf-card > .pf-hint { margin: 0 0 14px; font: 500 12px var(--pf-font); color: var(--pf-faint); }
#profilePage label.pf-field { display: block; margin-bottom: 13px; font: 600 11px var(--pf-font); letter-spacing: .1em; text-transform: uppercase; color: var(--pf-muted); }
#profilePage label.pf-field i { display: block; font: italic 400 11px var(--pf-font); letter-spacing: 0; text-transform: none; color: var(--pf-faint); margin-top: 3px; }
#profilePage .pf-field input { width: 100%; margin-top: 6px; box-sizing: border-box; background: var(--site-ui-panel, #0a1220); border: 1px solid var(--pf-line2);
  border-radius: 9px; padding: 10px 12px; color: var(--pf-ink); font: 500 14px var(--pf-font); }
#profilePage .pf-field input:focus { outline: none; border-color: var(--pf-cyan); box-shadow: 0 0 0 3px var(--site-ui-shadow, rgba(92, 200, 255, .13)); }

#profilePage .pf-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 4px; }
#profilePage button.pf-btn { background: var(--pf-cyan); border: none; color: var(--site-ui-muted, #06121f); border-radius: 9px; padding: 10px 20px;
  font: 700 13px var(--pf-font); letter-spacing: .04em; cursor: pointer; }
#profilePage button.pf-btn:hover { filter: brightness(1.1); }
#profilePage button.pf-btn:disabled { opacity: .5; cursor: default; filter: none; }
#profilePage button.pf-ghost { background: none; border: 1px solid var(--pf-line2); color: var(--pf-muted); border-radius: 9px;
  padding: 10px 18px; font: 600 13px var(--pf-font); cursor: pointer; }
#profilePage button.pf-ghost:hover { color: var(--site-ui-ink, #fff); border-color: var(--pf-muted); }
#profilePage button.pf-danger:hover { color: var(--pf-danger); border-color: var(--pf-danger); background: var(--site-ui-hover, rgba(224, 83, 61, .08)); }
#profilePage .pf-msg { min-height: 17px; font: 600 12px var(--pf-font); color: var(--pf-muted); margin-top: 2px; }
#profilePage .pf-msg.ok { color: var(--pf-good); }
#profilePage .pf-msg.err { color: var(--pf-danger); }

/* ---- unit-flag picker ----
   A dense grid so every option is visible without a scroll trap inside a scrolling page.
   The tile is the BANNER'S box (3:2) and the art fills it, because that is exactly what the battle banner does — a
   preview drawn at each PNG's own aspect would show the player a flag they will never see on the map. */
#profilePage .pf-flagsel { font: 600 13px var(--pf-font); color: var(--pf-cyan); margin-bottom: 12px; }
#profilePage .pf-flaggrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: 8px; }
#profilePage .pf-flag { display: flex; flex-direction: column; align-items: center; gap: 5px; padding: 8px 4px;
  background: var(--site-ui-panel, #0a1220); border: 1px solid var(--pf-line); border-radius: 10px; cursor: pointer; }
#profilePage .pf-flag:hover { border-color: var(--pf-line2); background: var(--site-ui-panel, #0d1728); }
#profilePage .pf-flag.on { border-color: var(--pf-cyan); background: var(--site-ui-hover, rgba(92, 200, 255, .12)); box-shadow: 0 0 0 1px var(--pf-cyan) inset; }
#profilePage .pf-flagart { width: 36px; height: 24px; display: grid; place-items: center; }   /* 3:2 — the banner's box */
#profilePage .pf-flagart img { width: 100%; height: 100%; object-fit: fill; display: block; border-radius: 1px; }   /* fill, NOT contain: the banner stretches to 3:2, so the preview must too */
#profilePage .pf-flagnone { color: var(--pf-faint); font: 700 16px var(--pf-font); }
#profilePage .pf-flaglbl { font: 600 9.5px var(--pf-font); letter-spacing: .04em; color: var(--pf-muted); text-align: center;
  line-height: 1.15; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#profilePage .pf-flag.on .pf-flaglbl { color: var(--pf-ink); }

/* ---- match history ---- */
#profilePage .pf-mstat { font: 600 13px var(--pf-font); color: var(--pf-muted); margin-bottom: 11px; }
#profilePage .pf-mstat b { color: var(--pf-cyan); }
#profilePage .pf-matches { display: flex; flex-direction: column; gap: 5px; }
#profilePage .pf-match { display: grid; grid-template-columns: 46px 1fr auto auto auto; gap: 10px; align-items: center;
  background: var(--site-ui-panel, #0a1220); border: 1px solid var(--pf-line); border-left: 3px solid var(--pf-danger); border-radius: 8px;
  padding: 8px 11px; font: 600 12px var(--pf-font); color: var(--pf-muted); }
#profilePage .pf-match.won { border-left-color: var(--pf-good); }
#profilePage .pf-mres { font: 800 10.5px var(--pf-font); letter-spacing: .08em; color: var(--pf-danger); }
#profilePage .pf-match.won .pf-mres { color: var(--pf-good); }
#profilePage .pf-mmap { color: var(--pf-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-transform: capitalize; }
#profilePage .pf-mtiles::after { content: " tiles"; color: var(--pf-faint); font-weight: 500; }
#profilePage .pf-mafk { font: 700 9px var(--pf-font); letter-spacing: .08em; color: var(--site-ui-muted, #0a0f1a); background: var(--pf-faint);
  border-radius: 3px; padding: 1px 4px; vertical-align: middle; font-style: normal; }
#profilePage .pf-mwhen { color: var(--pf-faint); min-width: 62px; text-align: right; }
@media (max-width: 560px) {
  #profilePage .pf-match { grid-template-columns: 42px 1fr auto; }
  #profilePage .pf-mtiles, #profilePage .pf-mwhen { display: none; }
}

/* ---- avatar row ---- */
#profilePage .pf-avrow { display: flex; gap: 16px; align-items: flex-start; margin-bottom: 16px; }
#profilePage .pf-avprev { width: 72px; height: 72px; flex: none; border-radius: 16px; display: grid; place-items: center;
  font: 700 28px var(--pf-font); color: var(--site-ui-muted, #0a0f1a); overflow: hidden; }
#profilePage .pf-avprev img { width: 100%; height: 100%; object-fit: cover; display: block; }
#profilePage .pf-avacts { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
#profilePage .pf-avacts .pf-hint { margin: 0; }
#profilePage label.pf-file { cursor: pointer; display: inline-block; }
#profilePage label.pf-file input { display: none; }

/* ---- preference toggles ---- */
#profilePage label.pf-toggle { display: flex; align-items: flex-start; gap: 11px; margin-bottom: 13px; cursor: pointer; }
#profilePage label.pf-toggle input { margin: 2px 0 0; width: 17px; height: 17px; flex: none; accent-color: var(--pf-cyan); cursor: pointer; }
#profilePage label.pf-toggle span { font: 600 13.5px var(--pf-font); color: var(--pf-ink); }
#profilePage label.pf-toggle i { display: block; font: italic 400 11.5px var(--pf-font); color: var(--pf-faint); margin-top: 2px; }

/* ---- connections (Discord) ---- */
#profilePage .pf-conn { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
#profilePage .pf-conn span { font: 600 13px var(--pf-font); color: var(--pf-muted); }
#profilePage button.pf-link { background: none; border: none; color: var(--pf-cyan); font: 600 13px var(--pf-font); cursor: pointer; padding: 0; text-decoration: underline; }

/* ---- the lobby name box, once the profile owns it ---- */
.lb-tag.isLocked input { opacity: .72; cursor: default; }
.lb-namehint { font: 500 11px var(--site-ui-font, 'Chakra Petch'), system-ui, sans-serif; color: var(--site-ui-muted, #5f6f88); margin-left: 8px; }
.lb-namehint button { background: none; border: none; color: var(--site-ui-ink, #5cc8ff); font: inherit; cursor: pointer; text-decoration: underline; padding: 0; }

@media (max-width: 560px) {
  #profilePage .pf-stats { grid-template-columns: 1fr; }
  #profilePage .pf-scroll { padding: 18px 12px 48px; }
  #profilePage .pf-name { font-size: 21px; }
}

/* "Message on Discord" — a door, not a delivery: it opens Discord at that player so they write it themselves.
   The dimmed variant means "they are on Discord, but connect yours first" — presence is disclosed only to somebody
   who has disclosed theirs, so the button exists but does not point anywhere yet. */
#playerProfile .pp-dscbtn { background: var(--site-ui-accent, #4a54c4); border-color: var(--site-ui-accent, #4a54c4); color: var(--site-ui-ink, #fff); }
#playerProfile .pp-dscbtn:hover { background: var(--site-ui-accent, #5a64d4); }
#playerProfile .pp-dscbtn.pp-dscoff { background: transparent; border: 1px solid var(--site-ui-line, #3a4570); color: var(--site-ui-muted, #8da2be); }
#playerProfile .pp-dscbtn.pp-dscoff:hover { color: var(--site-ui-ink, #e8eef7); border-color: var(--site-ui-accent, #4a54c4); }

/* the Discord-alerts opt-out, beside the connection it belongs to */
#pfProfile .pf-connopt, .pf-connopt { display: flex; align-items: flex-start; gap: 8px; width: 100%; margin-top: 9px;
  color: var(--site-ui-ink, #9fb2cc); font: 500 12.5px/1.45 var(--site-ui-font, 'Chakra Petch'), system-ui, sans-serif; cursor: pointer; }
.pf-connopt input { margin: 2px 0 0; accent-color: #2f86d6; cursor: pointer; }
.pf-connopt:hover { color: var(--site-ui-ink, #d7e3f4); }

/* ---- THE RANK LADDER (migration 0030) ---------------------------------------------------------------------------
   Two surfaces, one visual language: the full card on your own profile page, and a single compact line inside the
   shared player popover. The insignia is an inline SVG on a small integer grid, so it MUST stay pixel-crisp —
   `image-rendering: pixelated` is the whole reason it reads as pixel art rather than a blurry sticker. */
.pf-card.pf-rank { display: flex; flex-direction: column; gap: 12px; }
.pf-rhead { display: flex; align-items: center; gap: 16px; }
.pf-rins { flex: none; display: flex; align-items: center; justify-content: center; width: 64px; height: 56px; }
.pf-rins svg, .pp-ins svg { image-rendering: pixelated; display: block; }
.pf-rid { flex: 1 1 auto; min-width: 0; }
.pf-rname { font-size: 20px; font-weight: 700; line-height: 1.2; }
.pf-rtier { font-size: 12px; opacity: .62; margin-top: 2px; }
.pf-rpts { text-align: right; flex: none; }
.pf-rpts b { display: block; font-size: 20px; font-variant-numeric: tabular-nums; }
.pf-rpts span { font-size: 11px; opacity: .62; text-transform: uppercase; letter-spacing: 1px; }

.pf-rnext { display: flex; flex-direction: column; gap: 6px; font-size: 12px; opacity: .78; }
.pf-rbar { height: 6px; border-radius: 3px; background: var(--site-ui-hover, rgba(255, 255, 255, .09)); overflow: hidden; }
.pf-rbar i { display: block; height: 100%; border-radius: 3px; }

/* the popover's one line: insignia, rank, total */
.pp-rank { display: flex; align-items: center; gap: 10px; padding: 8px 12px; margin: 8px 0 0;
           background: var(--site-ui-hover, rgba(255, 255, 255, .04)); border-radius: 8px; }
.pp-ins { flex: none; display: flex; align-items: center; justify-content: center; width: 30px; height: 26px; }
.pp-ins svg { max-width: 30px; max-height: 26px; }
.pp-rank b { font-size: 13px; font-weight: 600; }
.pp-rpts { margin-left: auto; font-size: 12px; opacity: .6; font-variant-numeric: tabular-nums; }

/* THE RANK BESIDE A NAME IN CHAT — used by BOTH renderers (the dock's .cmsg and the clan page's .cp-cmeta), which is
   why it lives here with the rest of the rank artwork rather than being written twice in two stylesheets.
   The svg is already scaled to the box by insigniaFor(rank, 20, 15) — the numbers below only place it, because CSS
   cannot preserve an inline svg's aspect ratio when the element carries both a width and a height attribute. */
/* ⚠ A FIXED BOX WITH line-height:0 AND vertical-align:middle, not a baseline-aligned flex. As an inline-FLEX the
   span was aligned by its own last line box, so a short insignia hung below the text baseline and the name row's
   HEIGHT depended on the author's rank — measured 17.00px for a Recruit against 19.63px for a Private in the same
   column. Rows that jitter as people talk is a worse artefact than the mark is a feature.
   Both chat headers are flex rows with gap:8px, so the -2px lands the mark 6px from the name on BOTH surfaces: the
   dock measured 12px and the clan page 4px, the second correcting for a difference that was never there.
   The pull is on the LEFT edge because the mark sits AFTER the name — it belongs to the name on its left, not to
   the timestamp on its right, and the two gaps have to say so. */
.whorank { display: inline-block; width: 28px; height: 16px; line-height: 0; flex: none;
           margin-left: -2px; vertical-align: middle; text-align: center; }
.whorank svg { display: inline-block; vertical-align: middle; }

/* ---- BADGES (migration 0031) --------------------------------------------------------------------------------------
   One card per badge, three tiers each. An UNEARNED tier is drawn greyed in the same silhouette as an earned one — an
   empty shape is a goal, a blank space is nothing — and a badge whose counter is not kept yet says so rather than
   pretending to be merely unearned. */
.pf-badges { display: grid; grid-template-columns: repeat(auto-fill, minmax(258px, 1fr)); gap: 12px; }
.pf-badge { background: var(--site-ui-hover, rgba(255,255,255,.03)); border: 1px solid var(--site-ui-line, rgba(255,255,255,.09)); border-radius: 10px;
  padding: 12px 14px; display: flex; flex-direction: column; gap: 10px; }
.pf-badge.earned { border-color: var(--site-ui-accent, rgba(255,201,60,.35)); background: var(--site-ui-hover, rgba(255,201,60,.05)); }
.pf-badge.locked { opacity: .55; }
.pf-bhead { display: flex; align-items: flex-start; gap: 10px; }
.pf-bicon { font-size: 22px; line-height: 1; flex: none; }
.pf-bname { font-weight: 700; font-size: 14px; }
.pf-bdesc { font-size: 12px; opacity: .62; line-height: 1.4; margin-top: 2px; }
.pf-btiers { display: flex; flex-direction: column; gap: 5px; }
.pf-btier { display: flex; align-items: center; gap: 8px; font-size: 12px; opacity: .38; }
.pf-btier.got { opacity: 1; }
.pf-bmed { flex: none; width: 20px; display: flex; justify-content: center; }
.pf-bmed svg { height: 22px; width: auto; image-rendering: pixelated; display: block; }
.pf-btier:not(.got) .pf-bmed svg { filter: grayscale(1); }   /* the silhouette stays, the metal goes */
.pf-breq { flex: 1 1 auto; }
.pf-bmetal { font-size: 10px; text-transform: uppercase; letter-spacing: 1px; font-weight: 700; }
.pf-bnext { font-size: 11.5px; opacity: .6; font-variant-numeric: tabular-nums; }
.pf-bnext.pf-blocked { opacity: .5; font-style: italic; }

/* the popover's compact row: icon + the metal they reached, nothing else */
.pp-badges { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 0; }
.pp-badge { display: inline-flex; align-items: center; gap: 2px; padding: 3px 6px; border-radius: 7px;
  background: var(--site-ui-hover, rgba(255,255,255,.05)); }
.pp-bicon { font-size: 14px; line-height: 1; }
.pp-bmed svg { height: 15px; width: auto; image-rendering: pixelated; display: block; }


/* ==== leaderboard.css ==== */
/* leaderboard.css — the full-screen clan LADDER (opened from the lobby nav). Render-only; data from /api/clans/leaderboard.
   Reuses the clan page's .cp-top nav. Separate from the in-match leaderboard (that one lives in game.css). */
#leaderPage { position: fixed; inset: 0; z-index: 60; display: none; flex-direction: column; background: var(--site-ui-panel, #0d1017); color: var(--site-ui-ink, #e6ebf2); }
#leaderPage.show { display: flex; }
/* nav bar — the clanpage's .cp-* rules are scoped to #clanPage, so re-state them here (self-contained, no shared vars) */
#leaderPage .cp-top { flex: 0 0 auto; display: flex; align-items: center; gap: 16px; padding: 11px clamp(14px,2.5vw,26px); background: var(--site-ui-panel, rgba(8,12,20,.85)); border-bottom: 1px solid var(--site-ui-line, #ffffff14); }
#leaderPage .cp-back { background: none; border: 1px solid var(--site-ui-line, #ffffff26); color: var(--site-ui-muted, #9aa3b2); border-radius: 8px; padding: 7px 13px; font: 600 12px system-ui; letter-spacing: .06em; text-transform: uppercase; cursor: pointer; }
#leaderPage .cp-back:hover { color: var(--site-ui-ink, #fff); border-color: var(--site-ui-accent, #5cc8ff); background: var(--site-ui-hover, rgba(92,200,255,.1)); }
#leaderPage .cp-brand { display: flex; align-items: baseline; gap: 8px; }
#leaderPage .cp-brandsub { font: 600 11px system-ui; letter-spacing: .22em; text-transform: uppercase; color: var(--site-ui-ink, #8fb6cc); }
#leaderPage .lp-body { flex: 1; overflow-y: auto; width: 100%; max-width: 760px; margin: 0 auto; padding: 18px 16px 48px; }

/* "your clan" + "your impact" header (only when signed in + in a clan) */
.lp-mine { display: none; margin-bottom: 16px; padding: 14px 16px; border: 1px solid var(--site-ui-line, #2a4d63); border-radius: 12px; background: linear-gradient(135deg,var(--site-ui-panel, #15212b),var(--site-ui-panel, #161a22)); }
.lp-mine.show { display: block; }
.lp-mine .lp-mtitle { font: 600 11px/1 system-ui; color: var(--site-ui-ink, #8fb6cc); text-transform: uppercase; letter-spacing: .07em; margin-bottom: 9px; }
.lp-mine .lp-mrow { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.lp-mine .lp-mtag { font: 800 16px system-ui; color: var(--site-ui-ink, #bfe2f2); }
.lp-mine .lp-mrank { font: 700 14px system-ui; color: var(--site-ui-gold, #f0d889); }
.lp-mine .lp-mpts { font: 800 18px system-ui; color: var(--site-ui-ink, #fff); margin-left: auto; }
.lp-mine .lp-impact { width: 100%; margin-top: 10px; font: 600 13px system-ui; color: var(--site-ui-muted, #9aa3b2); }
.lp-mine .lp-impact b { color: var(--site-ui-accent, #7fd1a8); }

.lp-head, .lp-row { display: grid; grid-template-columns: 48px 1fr 92px; align-items: center; gap: 8px; padding: 9px 12px; }
.lp-head { font: 600 11px system-ui; color: var(--site-ui-muted, #6e7a8a); text-transform: uppercase; letter-spacing: .05em; border-bottom: 1px solid var(--site-ui-line, #1e2530); }
.lp-row { border-bottom: 1px solid var(--site-ui-line, #161b24); }
.lp-row:hover { background: var(--site-ui-panel, #131822); }
.lp-row.me { background: var(--site-ui-panel, #16242e); border-left: 3px solid var(--site-ui-accent, #2c9fd6); }
.lp-rk { font: 800 15px system-ui; color: var(--site-ui-muted, #8a93a3); text-align: center; }
.lp-row.pod1 .lp-rk { color: var(--site-ui-gold, #f0d889); }   /* gold */
.lp-row.pod2 .lp-rk { color: var(--site-ui-ink, #cdd5e2); }   /* silver */
.lp-row.pod3 .lp-rk { color: var(--site-ui-danger, #cd9b6a); }   /* bronze */
.lp-clan { display: flex; flex-direction: row; align-items: center; gap: 9px; min-width: 0; }
.lp-cntext { display: flex; flex-direction: column; min-width: 0; }
/* clan LOGO (migration 0020) — before the clan name in the ladder; empty same-size slot keeps rows aligned when a clan has none */
.lp-logo { width: 26px; height: 26px; border-radius: 6px; object-fit: cover; flex: none; box-shadow: 0 0 0 1px var(--site-ui-shadow, #ffffff1c) inset; background: var(--site-ui-panel, #0d1520); }
.lp-logo-none { background: transparent; box-shadow: none; }
.lp-mine .lp-mrow .lp-logo { width: 28px; height: 28px; }
.lp-clan .lp-tag { font: 800 14px system-ui; color: var(--site-ui-ink, #e6ebf2); }
.lp-clan .lp-name { font: 500 12px system-ui; color: var(--site-ui-muted, #8a93a3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lp-pts { font: 800 16px system-ui; color: var(--site-ui-ink, #bfe2f2); text-align: right; }
.lp-empty { padding: 44px 12px; text-align: center; color: var(--site-ui-muted, #6e7a8a); font: 500 14px system-ui; }

@media (max-width:640px){
  .lp-head, .lp-row { grid-template-columns: 38px 1fr 72px; padding: 8px 6px; gap: 6px; }
  .lp-mine .lp-mpts { font-size: 16px; }
}

/* ---- TWO BOARDS, ONE PAGE ---------------------------------------------------------------------------------------
   Clans rank on a zero-sum pool; players on lifetime points that are never lost. The tabs are the only place the
   two meet, and they never share a list or a column. */
.lp-tabs { display: flex; gap: 8px; margin: 0 0 14px; }
.lp-tab { flex: 0 0 auto; padding: 7px 18px; border: 1px solid var(--site-ui-line, rgba(255,255,255,.14)); border-radius: 999px;
  background: transparent; color: var(--site-ui-ink, #9fb2cc); font: 600 13px var(--site-ui-font, 'Chakra Petch'), system-ui, sans-serif;
  letter-spacing: .4px; cursor: pointer; }
.lp-tab:hover { color: var(--site-ui-ink, #d7e3f4); border-color: var(--site-ui-line, rgba(255,255,255,.28)); }
.lp-tab.on { background: var(--site-ui-hover, rgba(255,201,60,.14)); border-color: var(--site-ui-accent, rgba(255,201,60,.5)); color: var(--site-ui-danger, #ffc93c); }
.lp-tab:focus-visible { outline: 2px solid var(--site-ui-accent, #ffc93c); outline-offset: 2px; }

/* a player row: insignia where the clan crest sits, so both boards keep one grid */
.lp-prow { cursor: pointer; }
.lp-ins { flex: none; display: flex; align-items: center; justify-content: center; width: 28px; height: 24px; }
.lp-ins svg { max-width: 28px; max-height: 24px; image-rendering: pixelated; display: block; }
.lp-rankname { display: block; font-size: 11.5px; opacity: .85; line-height: 1.25; }


/* ==== announce.css ==== */
/* announce.css — floating clan-announcement toasts (top-right). Render-only, off the lockstep. */
#announceToasts { position: fixed; top: 14px; right: 14px; z-index: 200; display: flex; flex-direction: column; gap: 10px; max-width: 360px; pointer-events: none; }
.announceToast {
  pointer-events: auto; position: relative; display: flex; gap: 11px; align-items: flex-start;
  background: var(--site-ui-panel, #101b2e); border: 1px solid var(--site-ui-accent, #2f86d6); border-left: 4px solid var(--site-ui-accent, #5cc8ff); border-radius: 12px;
  padding: 12px 30px 12px 14px; box-shadow: 0 10px 30px var(--site-ui-shadow, rgba(0, 0, 0, .5)); color: var(--site-ui-ink, #e8eef7);
  font-family: var(--site-ui-font, 'Chakra Petch'), 'Segoe UI', system-ui, sans-serif; cursor: pointer;
  transform: translateX(120%); opacity: 0; transition: transform .25s ease, opacity .25s ease;
}
.announceToast.in { transform: translateX(0); opacity: 1; }
.announceToast .atIcon { font-size: 20px; line-height: 1.2; flex: none; }
.announceToast .atHead { font: 700 12px var(--site-ui-font, 'Chakra Petch'), sans-serif; color: var(--site-ui-ink, #5cc8ff); letter-spacing: .02em; margin-bottom: 3px; }
.announceToast .atText { font: 500 13px/1.45 var(--site-ui-font, 'Chakra Petch'), sans-serif; word-break: break-word; }
.announceToast .atMute { position: absolute; top: 6px; right: 8px; background: none; border: none; cursor: pointer; font-size: 13px; opacity: .7; }
.announceToast .atMute:hover { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .announceToast { transition: opacity .2s ease; transform: none; } }


/* ==== party.css ==== */
/* party.css — the global floating party pill + its member-list popup. Render-only, off the lockstep. */
#partyBar { position: fixed; top: 58px; left: 50%; transform: translateX(-50%); z-index: 90; display: none; }
#partyBar.show { display: block; }
.pb-pill { display: flex; align-items: center; gap: 8px; cursor: pointer; border-radius: 10px; padding: 8px 16px; font: 700 13px var(--site-ui-font, 'Chakra Petch'), 'Segoe UI', system-ui, sans-serif; color: var(--site-ui-ink, #cdd9ea); background: var(--site-ui-panel, #101b2e); border: 1px solid var(--site-ui-line, #28395a); box-shadow: 0 6px 18px var(--site-ui-shadow, rgba(0,0,0,.45)); }
.pb-pill b { font-weight: 800; color: var(--site-ui-ink, #5cc8ff); }
.pb-pill:hover { border-color: var(--site-ui-accent, #5cc8ff); color: var(--site-ui-ink, #fff); }

#partyPanel { position: fixed; inset: 0; z-index: 95; display: none; align-items: center; justify-content: center; background: var(--site-ui-panel, #05080faa); padding: 24px; }
#partyPanel.show { display: flex; }
.pp-card { width: min(420px, 100%); background: var(--site-ui-panel, #101b2e); border: 1px solid var(--site-ui-line, #28395a); border-radius: 14px; font-family: var(--site-ui-font, 'Chakra Petch'), 'Segoe UI', system-ui, sans-serif; color: var(--site-ui-ink, #e8eef7); }
.pp-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid var(--site-ui-line, #1d2c45); }
.pp-head h3 { margin: 0; font: 700 16px var(--site-ui-font, 'Chakra Petch'); }
.pp-close { background: none; border: none; color: var(--site-ui-muted, #8da2be); font-size: 22px; line-height: 1; cursor: pointer; }
.pp-close:hover { color: var(--site-ui-ink, #fff); }
.pp-body { padding: 16px 18px; display: flex; flex-direction: column; gap: 12px; }
.pp-check { background: var(--site-ui-hover, rgba(224, 162, 58, .12)); border: 1px solid var(--site-ui-accent, rgba(224, 162, 58, .3)); color: var(--site-ui-danger, #e8c07a); border-radius: 9px; padding: 9px 12px; font: 600 12.5px var(--site-ui-font, 'Chakra Petch'); }
.pp-cool { background: var(--site-ui-hover, #ffffff0a); border: 1px solid var(--site-ui-line, #2a3a59); color: var(--site-ui-muted, #8da2be); border-radius: 9px; padding: 8px 12px; font: 600 12px var(--site-ui-font, 'Chakra Petch'); }
.pp-queue { background: var(--site-ui-hover, rgba(70, 192, 138, .1)); border: 1px solid var(--site-ui-accent, rgba(70, 192, 138, .28)); color: var(--site-ui-accent, #8fe0bb); border-radius: 9px; padding: 8px 12px; font: 600 12px var(--site-ui-font, 'Chakra Petch'); }
.pp-queue i { color: var(--site-ui-muted, #6f93b0); font-style: normal; font-weight: 500; }
.pp-list { display: flex; flex-direction: column; }
.pp-row { display: flex; align-items: center; gap: 9px; padding: 8px 4px; border-top: 1px solid var(--site-ui-line, #1d2c45); font: 500 13px var(--site-ui-font, 'Chakra Petch'); }
.pp-row:first-child { border-top: none; }
.pp-nm { flex: 1; } .pp-nm i { color: var(--site-ui-muted, #5f6f88); font-style: normal; }
.pp-st { font: 700 10px var(--site-ui-font, 'Chakra Petch'); letter-spacing: .04em; white-space: nowrap; }
.pp-st.m { color: var(--site-ui-accent, #46c08a); } .pp-st.on { color: var(--site-ui-ink, #5cc8ff); } .pp-st.off { color: var(--site-ui-muted, #5f6f88); } .pp-st.pend { color: var(--site-ui-danger, #e0a23a); }
.pp-x { background: var(--site-ui-hover, #ffffff12); border: none; border-radius: 5px; color: var(--site-ui-ink, #cdd9ea); width: 22px; height: 22px; font-size: 11px; cursor: pointer; }
.pp-x:hover { background: var(--site-ui-gold-well, #e0533d); color: var(--site-ui-ink, #fff); }
.pp-crown:hover { background: var(--site-ui-gold-well, #e0a23a); }   /* make-leader is a 'promote', not a destructive action */
.pp-x.pp-muted { background: var(--site-ui-hover, rgba(224, 83, 61, .25)); color: var(--site-ui-danger, #ffb3a6); }   /* this member's in-game signals are muted */
.pp-rename { background: none; border: none; cursor: pointer; font-size: 13px; opacity: .65; padding: 0 2px; }
.pp-rename:hover { opacity: 1; }
.pp-cta { display: flex; gap: 10px; }
.pp-ok, .pp-ghost { border: none; border-radius: 9px; padding: 10px 15px; font: 700 13px var(--site-ui-font, 'Chakra Petch'); cursor: pointer; }
.pp-ok { background: var(--site-ui-accent, #2f86d6); color: var(--site-ui-ink, #fff); } .pp-ok:hover { background: var(--site-ui-accent, #3a97ea); }
.pp-ready { background: var(--site-ui-accent, #46c08a); } .pp-ready:hover { filter: brightness(1.08); }
.pp-ghost { background: var(--site-ui-hover, #ffffff12); color: var(--site-ui-ink, #cdd9ea); } .pp-ghost:hover { background: var(--site-ui-hover, #ffffff20); }
.pp-danger { background: var(--site-ui-hover, rgba(224, 83, 61, .14)); color: var(--site-ui-danger, #e89a8e); } .pp-danger:hover { background: var(--site-ui-hover, rgba(224, 83, 61, .26)); color: var(--site-ui-ink, #fff); }
.pp-ok[disabled], .pp-ghost[disabled] { opacity: .45; cursor: not-allowed; pointer-events: none; }
.pp-hint { margin: 0; color: var(--site-ui-muted, #8da2be); font: 500 12px var(--site-ui-font, 'Chakra Petch'); line-height: 1.5; }
@media (max-width: 600px) { #partyBar { top: 52px; } }


/* ==== voice.css ==== */
/* voice.css — clan voice dock (ui/voice). Dark, matches the party pill / clan page look. Render-only UI.
   Placement: BOTTOM-LEFT above the in-game leave (14px) + signal (70-120px) buttons — the bottom-RIGHT corner belongs
   to the chat dock (z70, up to ~540px tall). z-index 78: above the clan page (75, where the lounge lives) and the chat
   dock, below action menus (90). */
#vcDock { position: fixed; left: 14px; bottom: 130px; z-index: 78; font-family: inherit; }
#vcPill { width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--site-ui-line, #2c3a4d); background: var(--site-ui-panel, #16202c); color: var(--site-ui-ink, #dfe9f5);
  font-size: 18px; cursor: pointer; position: relative; box-shadow: 0 4px 14px var(--site-ui-shadow, rgba(0,0,0,.45)); }
#vcPill:hover { background: var(--site-ui-panel, #1c2938); }
#vcPill.vc-live { border-color: var(--site-ui-accent, #34d17b); box-shadow: 0 0 0 2px var(--site-ui-shadow, rgba(52,209,123,.25)), 0 4px 14px var(--site-ui-shadow, rgba(0,0,0,.45)); }
#vcCount { position: absolute; top: -5px; right: -5px; min-width: 17px; height: 17px; border-radius: 9px; background: var(--site-ui-accent, #34d17b);
  color: var(--site-ui-muted, #08131f); font-size: 11px; font-weight: 700; line-height: 17px; padding: 0 3px; }
#vcCount:empty { display: none; }
#vcPanel { position: absolute; left: 0; bottom: 52px; width: 250px; background: var(--site-ui-panel, #101825); border: 1px solid var(--site-ui-line, #263447);
  border-radius: 10px; padding: 10px; box-shadow: 0 10px 30px var(--site-ui-shadow, rgba(0,0,0,.5)); }
#vcPanel.hidden { display: none; }
.vc-head { display: flex; justify-content: space-between; align-items: center; color: var(--site-ui-ink, #dfe9f5); margin-bottom: 6px; }
.vc-head b { font-size: 13px; letter-spacing: .4px; }
#vcClose { background: none; border: none; color: var(--site-ui-muted, #8fa3ba); font-size: 16px; cursor: pointer; }
#vcBadge { color: var(--site-ui-muted, #8fa3ba); font-size: 11px; margin-bottom: 5px; }
#vcBadge:empty { display: none; }
#vcNotice { color: var(--site-ui-danger, #f0b455); font-size: 11px; min-height: 0; margin-bottom: 4px; }
#vcNotice:empty { display: none; }
#vcActions { display: flex; gap: 6px; margin-bottom: 8px; }
.vc-btn { flex: 1; padding: 7px 8px; border-radius: 7px; border: 1px solid var(--site-ui-line, #2c3a4d); background: var(--site-ui-panel, #182432); color: var(--site-ui-ink, #dfe9f5);
  font-size: 12px; cursor: pointer; }
.vc-btn:hover:not(:disabled) { background: var(--site-ui-panel, #203044); }
.vc-btn:disabled { opacity: .55; cursor: default; }
.vc-btn.vc-on { border-color: var(--site-ui-accent, #34d17b); color: var(--site-ui-accent, #7ce8ab); }
.vc-btn.vc-leave { flex: 0 0 auto; color: var(--site-ui-danger, #ff8d7e); }
#vcList { max-height: 220px; overflow-y: auto; }
.vc-row { display: flex; align-items: center; gap: 6px; color: var(--site-ui-ink, #c8d6e5); font-size: 12px; padding: 4px 2px; border-radius: 6px; }
.vc-row.vc-me { color: var(--site-ui-ink, #eaf3fc); }
.vc-row.vc-empty { color: var(--site-ui-muted, #77899d); font-style: italic; }
.vc-st { width: 16px; text-align: center; }
.vc-fail { color: var(--site-ui-danger, #f0b455); font-style: normal; }
.vc-tag { color: var(--site-ui-muted, #7f93aa); font-style: normal; font-size: 10px; }
.vc-ctl { margin-left: auto; display: inline-flex; gap: 4px; }
.vc-mini { background: none; border: 1px solid var(--site-ui-line, #2c3a4d); border-radius: 5px; color: var(--site-ui-ink, #9fb2c8);
  font-size: 10px; cursor: pointer; padding: 1px 5px; }
.vc-mini:hover { background: var(--site-ui-panel, #203044); }
.vc-mini.vc-muted { border-color: var(--site-ui-line, #7a3a3a); color: var(--site-ui-danger, #ff9a8a); }
.vc-foot { margin-top: 7px; color: var(--site-ui-muted, #5d7085); font-size: 10px; text-align: center; }


/* ==== devlog.css ==== */
/* devlog.css — the diagnostics log button + panel (render-only overlay). */
#dlBtn {
  position: fixed; left: 10px; bottom: 10px; z-index: 200; width: 38px; height: 38px; border-radius: 50%;
  border: 1px solid #2a3a59; background: #101b2e; color: #cdd9ea; font-size: 17px; cursor: pointer; opacity: .55;
  box-shadow: 0 4px 14px rgba(0,0,0,.4);
}
#dlBtn:hover { opacity: 1; }
#dlBtn[data-n]:not([data-n="0"])::after {
  content: attr(data-n); position: absolute; top: -5px; right: -5px; min-width: 16px; height: 16px; padding: 0 3px;
  border-radius: 8px; background: #2f86d6; color: #fff; font: 700 9px 'Chakra Petch','Segoe UI',system-ui,sans-serif;
  line-height: 16px; box-sizing: border-box;
}

#dlPanel {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 201; height: 46vh; display: none; flex-direction: column;
  /* OPAQUE, and no backdrop-filter. A blur whose backdrop is the live game canvas can never be cached: the compositor
     reads back and re-blurs ~1920x435px EVERY frame, which lands in browserPaint with idle -> 0. The Download button
     that produces a diagnostics log lives inside this panel, so the log was measuring a cost the panel itself caused.
     #pfPanel (perf.css) is opaque for exactly this reason. A diagnostic must never be able to cause what it reports. */
  background: #070d17; border-top: 1px solid #28395a;
  font-family: ui-monospace, 'Cascadia Code', 'Consolas', monospace;
}
#dlPanel.show { display: flex; }
.dl-head { display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-bottom: 1px solid #1d2c45; color: #e8eef7; }
.dl-head b { font: 700 13px 'Chakra Petch','Segoe UI',system-ui,sans-serif; margin-right: 4px; }
.dl-head .dl-search { flex: 1; max-width: 280px; background: #0c1626; border: 1px solid #28395a; border-radius: 6px; color: #cdd9ea; font: 500 12px ui-monospace,monospace; padding: 5px 8px; outline: none; }
.dl-head .dl-flash { color: #8fe0bb; font: 600 11px 'Chakra Petch',sans-serif; opacity: 0; transition: opacity .2s; min-width: 60px; }
.dl-head .dl-flash.on { opacity: 1; }
.dl-head button[data-dl] { background: #16233a; border: 1px solid #28395a; border-radius: 6px; color: #cdd9ea; font: 600 11px 'Chakra Petch',sans-serif; padding: 5px 9px; cursor: pointer; }
.dl-head button[data-dl]:hover { background: #1e3052; color: #fff; }
.dl-head button[data-dl].on { background: #2f86d6; color: #fff; border-color: #2f86d6; }
.dl-head button[data-dl="close"] { font-size: 16px; padding: 2px 8px; }

.dl-body { flex: 1; overflow-y: auto; padding: 4px 0; font-size: 11.5px; line-height: 1.5; }
.dl-row { display: flex; gap: 8px; padding: 1px 10px; white-space: pre-wrap; word-break: break-word; border-top: 1px solid #ffffff08; }
.dl-row:hover { background: #ffffff08; }
.dl-t { color: #5f7290; flex: 0 0 auto; }
.dl-c { color: #8da2be; flex: 0 0 92px; font-weight: 700; }
.dl-m { color: #d3deec; flex: 1; }
.dl-m i { color: #7f93b0; font-style: normal; }
/* category tints */
.dl-row.dl-net .dl-c { color: #5cc8ff; }
.dl-row.dl-social .dl-c { color: #8fe0bb; }
.dl-row.dl-signal .dl-c { color: #e0a23a; }
.dl-row.dl-queue .dl-c { color: #c79bff; }
.dl-row.dl-party .dl-c { color: #ff9ecb; }
.dl-row.dl-api .dl-c { color: #7fd4c0; }
.dl-row.dl-clan .dl-c { color: #b8a6ff; }
.dl-row.dl-game .dl-c { color: #ffd27a; }
.dl-row.dl-chat .dl-c { color: #9fd0a0; }
.dl-row.dl-lobby .dl-c { color: #88b6e6; }
.dl-row.dl-account .dl-c { color: #d0a6ff; }
.dl-row.dl-error .dl-c, .dl-row.dl-console .dl-c { color: #ff7a6b; }
.dl-row.dl-error .dl-m, .dl-row.dl-console .dl-m { color: #ffb3a6; }


/* ==== perf.css ==== */
/* perf.css — the ADMIN frame-profiler button + panel (render-only overlay). Sibling of devlog.css; same palette, and
   deliberately docked to the RIGHT so the 📊 button never sits on top of the 🐞 one. */
#pfBtn {
  position: fixed; right: 10px; bottom: 10px; z-index: 200; width: 38px; height: 38px; border-radius: 50%;
  border: 1px solid #2a3a59; background: #101b2e; color: #cdd9ea; font-size: 17px; cursor: pointer; opacity: .55;
  box-shadow: 0 4px 14px rgba(0,0,0,.4);
}
#pfBtn:hover, #pfBtn.on { opacity: 1; border-color: #3f6fae; }

#pfPanel {
  position: fixed; right: 0; top: 0; bottom: 0; z-index: 201; width: min(560px, 96vw); display: none; flex-direction: column;
  background: #070d17; border-left: 1px solid #28395a;
  font-family: ui-monospace, 'Cascadia Code', 'Consolas', monospace; font-size: 11px; color: #cdd9ea;
}
#pfPanel.on { display: flex; }
.pf-bar-top { display: flex; align-items: center; gap: 6px; padding: 7px 10px; border-bottom: 1px solid #1d2c45; color: #e8eef7; flex: none; }
.pf-bar-top b { margin-right: auto; font-size: 12px; }
.pf-bar-top button {
  border: 1px solid #2a3a59; background: #0e1828; color: #b8c7dc; border-radius: 5px; padding: 3px 7px; cursor: pointer; font: inherit;
}
.pf-bar-top button:hover { background: #16233a; color: #fff; }
.pf-flash { opacity: 0; transition: opacity .25s; color: #7ad17a; font-size: 10px; }
.pf-flash.on { opacity: 1; }

.pf-body { overflow: auto; padding: 8px 10px 24px; flex: 1 1 auto; }
.pf-empty { opacity: .6; padding: 20px 0; text-align: center; }

.pf-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
.pf-big { font-size: 26px; font-weight: 700; line-height: 1; }
.pf-big i { font-size: 11px; font-style: normal; opacity: .6; margin-left: 3px; }
.pf-kv { font-size: 11px; }
.pf-kv b { display: block; opacity: .5; font-weight: 400; font-size: 9px; text-transform: uppercase; letter-spacing: .04em; }

/* one shared heat scale everywhere: green under a 60fps frame, amber approaching it, red over */
.cool { color: #6fce7f; } .warm { color: #e3c05a; } .hot { color: #f0716a; }
tr.cool td:first-child { border-left: 2px solid #6fce7f; } tr.warm td:first-child { border-left: 2px solid #e3c05a; }
tr.hot td:first-child { border-left: 2px solid #f0716a; }

.pf-verdict { border: 1px solid #24344f; background: #0c1524; border-radius: 6px; padding: 7px 9px; margin-bottom: 10px; line-height: 1.55; }
.pf-verdict div + div { margin-top: 3px; opacity: .92; }

.pf-t { width: 100%; border-collapse: collapse; margin: 4px 0 6px; }
.pf-t th { text-align: right; font-weight: 400; opacity: .45; font-size: 9px; text-transform: uppercase; letter-spacing: .04em; padding: 2px 4px; border-bottom: 1px solid #1d2c45; }
.pf-t th:first-child, .pf-t th:nth-child(2) { text-align: left; }
.pf-t td { text-align: right; padding: 2px 4px; border-bottom: 1px solid #121d30; white-space: nowrap; }
.pf-t td:first-child { text-align: left; padding-left: 6px; }
.pf-t td:nth-child(2) { text-align: left; }
.pf-n { color: #e8eef7; }
.pf-bar { display: inline-block; width: 110px; height: 7px; background: #14203400; box-shadow: inset 0 0 0 1px #1d2c45; border-radius: 3px; vertical-align: middle; overflow: hidden; }
.pf-bar i { display: block; height: 100%; background: linear-gradient(90deg, #2f86d6, #6fa8e0); }
.pf-t u { text-decoration: none; opacity: .55; margin-left: 5px; font-size: 10px; }

h4 { margin: 12px 0 2px; font-size: 10px; text-transform: uppercase; letter-spacing: .05em; opacity: .5; font-weight: 600; }
.pf-note { opacity: .45; font-size: 9.5px; line-height: 1.5; margin: 2px 0 4px; }
.pf-drv { padding: 2px 0; }
.pf-drv b { color: #e8eef7; font-weight: 600; }
.pf-drv u { text-decoration: none; opacity: .55; }
.pf-lt { line-height: 1.7; word-break: break-word; opacity: .85; }
.pf-lt u { text-decoration: none; opacity: .5; margin-left: 3px; }

@media (max-width: 620px) { #pfPanel { width: 100vw; } .pf-bar { width: 60px; } }

/* ---- the always-on corner readout (F3): a game-style fps counter that leads with the numbers a stutter shows up in.
   Top-LEFT, under the account bar, deliberately away from the 🐞/📊 buttons and the bottom HUD. pointer-events stay on
   so clicking it opens the full panel, but it never blocks the map: it is only as wide as its own text. */
#pfHud {
  position: fixed; left: 10px; top: 56px; z-index: 199; display: none; gap: 9px; align-items: baseline;
  padding: 4px 9px; border-radius: 7px; border: 1px solid #24344f; background: #0a1120;
  font-family: ui-monospace, 'Cascadia Code', 'Consolas', monospace; font-size: 10.5px; color: #9fb0c6;
  cursor: pointer; user-select: none; white-space: nowrap; max-width: calc(100vw - 20px); overflow: hidden;
}
#pfHud.on { display: flex; }
#pfHud b { font-size: 15px; font-weight: 700; line-height: 1; }
#pfHud b i { font-size: 9px; font-style: normal; opacity: .55; margin-left: 2px; }
#pfHud em { font-style: normal; font-weight: 600; color: #e8eef7; }
#pfHud .pf-dim { opacity: .5; }


/* ==== fxpanel.css ==== */
/* fxpanel.css — the ?fxdebug front-flag tuning panel. Debug-only: nothing here renders without the query flag,
   and #fxPanel is created by src/client/fxpanel.mjs rather than living in index.html.
   INPUT SAFETY (CLAUDE.md 4b rule 5): the panel is the ONLY thing that takes pointer events. Its wrapper does not,
   so the map behind it stays clickable everywhere the panel itself is not. */
#fxPanel{
  /* below the stats HUD, which owns the top-right corner. Draggable from the title bar if it still covers something. */
  position:fixed; top:186px; right:12px; z-index:70;
  /* Deliberately a CORNER panel, not a sidebar: it scrolls internally rather than growing down the screen, because a
     full-height fixed element covers a fifth of the map and every click that lands on it is a click the game lost. */
  width:268px; max-height:min(58vh,560px);
  display:flex; flex-direction:column;
  font:12px/1.35 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  color:#dfe4ee; background:rgba(14,17,26,.94); border:1px solid #2c3446; border-radius:10px;
  box-shadow:0 10px 30px rgba(0,0,0,.5); overflow:hidden;
}
#fxPanel.min{ max-height:34px; }
#fxPanel .fxp-head{
  display:flex; align-items:center; gap:8px; padding:8px 10px; cursor:move;
  background:#182033; border-bottom:1px solid #2c3446; user-select:none; flex:0 0 auto;
}
#fxPanel .fxp-head b{ font-size:12px; letter-spacing:.4px; color:#8fd0ff; font-weight:700; }
#fxPanel .fxp-head .fxp-sp{ margin-left:auto; }
#fxPanel .fxp-head button{
  background:#243049; color:#cfe0f5; border:1px solid #35415c; border-radius:5px;
  font:11px ui-monospace,monospace; padding:2px 7px; cursor:pointer;
}
#fxPanel .fxp-head button:hover{ background:#2e3c5c; }
#fxPanel .fxp-body{ padding:9px 10px 11px; overflow-y:auto; flex:1 1 auto; }
#fxPanel.min .fxp-body{ display:none; }

#fxPanel .fxp-sec{ margin:0 0 9px; }
#fxPanel .fxp-sec>h4{
  margin:0 0 5px; font-size:10px; font-weight:700; letter-spacing:.8px;
  text-transform:uppercase; color:#7d8aa3;
}
#fxPanel .fxp-row{ display:flex; align-items:center; gap:7px; margin:0 0 4px; }
#fxPanel .fxp-row label{ flex:1 1 auto; color:#b6c1d4; }
#fxPanel .fxp-row input[type=number],#fxPanel .fxp-row select{
  width:74px; flex:0 0 auto; background:#0e1420; color:#eaf0fa;
  border:1px solid #33405a; border-radius:5px; padding:2px 5px;
  font:12px ui-monospace,monospace; text-align:right;
}
#fxPanel .fxp-row input[type=range]{ flex:1 1 auto; width:auto; accent-color:#4d9dff; }
#fxPanel .fxp-row .fxp-val{ flex:0 0 44px; text-align:right; color:#8fd0ff; }

#fxPanel table{ width:100%; border-collapse:collapse; }
#fxPanel th{ font-size:10px; font-weight:700; color:#7d8aa3; text-align:right; padding:0 0 3px; }
#fxPanel th:first-child{ text-align:left; }
#fxPanel td{ padding:1px 0; }
#fxPanel td:first-child{ color:#8a95ab; }
#fxPanel table input{
  width:100%; background:#0e1420; color:#eaf0fa; border:1px solid #33405a;
  border-radius:4px; padding:1px 4px; font:11px ui-monospace,monospace; text-align:right;
}
#fxPanel td.floor input{ border-color:#7a5a2a; color:#ffcd7a; }   /* pitch is at the lattice floor: finer buys nothing */

#fxPanel .fxp-live{
  background:#0d121d; border:1px solid #232d42; border-radius:6px;
  padding:5px 7px; color:#9fb0c8; white-space:pre; overflow-x:auto; min-height:34px;
}
#fxPanel .fxp-note{ color:#7d8aa3; margin:5px 0 0; }
#fxPanel .fxp-note b{ color:#ffcd7a; font-weight:400; }
#fxPanel textarea{
  width:100%; height:78px; resize:vertical; background:#0d121d; color:#cfe0f5;
  border:1px solid #232d42; border-radius:6px; padding:5px 6px;
  font:11px/1.3 ui-monospace,monospace;
}
#fxPanel .fxp-btns{ display:flex; gap:6px; margin-top:6px; }
#fxPanel .fxp-btns button{
  flex:1 1 0; background:#243049; color:#cfe0f5; border:1px solid #35415c;
  border-radius:5px; padding:4px 6px; font:11px ui-monospace,monospace; cursor:pointer;
}
#fxPanel .fxp-btns button:hover{ background:#2e3c5c; }


/* ==== tutorial.css ==== */
/* tutorial.css — the guided tutorial's four surfaces (src/client/ui/tutorial/): the lobby "Learn to play" card,
   the in-match coach card, the mission-result screen, and the DOM highlight applied to existing HUD controls.
   Render-only: nothing here is read by the sim. Link this AFTER game.css and lobby.css — the lobby card leans on the
   --lb-* properties #lobby declares, and the in-match card has to win over game.css's #game descendants. */

/* ============================== 1. LOBBY — the second card in #spPanel ============================== */
/* .lb-card-body carries margin-top:-36px so it can ride up over .lb-card-img's fade. This card has NO image, so that
   negative margin would drag the whole body out of the card's top edge and under .lb-chip. Zero it and pay for the
   chip's own height instead (chip: top 12px + ~21px tall). */
#lobby .lb-card-learn .lb-card-body { margin-top: 0; padding-top: 44px; gap: 12px; }
#lobby .lb-card-learn { border-color: rgba(92, 200, 255, .28); }
#lobby .lb-card-learn:hover { border-color: var(--lb-cyan); }
#lobby .lb-card-learn .lb-chip { color: #bfe6ff; border-color: rgba(92, 200, 255, .45); }
#lobby .lb-card-learn .lb-card-sub { min-height: 16px; }

/* completion tick — inline in the heading and in each mission's title, shown by removing [hidden] */
#lobby .lb-done { color: var(--lb-good); font-size: .72em; margin-left: 7px; vertical-align: middle; }
#lobby .lb-done[hidden] { display: none; }

/* mission list: a vertical stack of buttons, each a title line + one line of "what you'll do" */
#lobby .lb-missions { display: flex; flex-direction: column; gap: 7px; }
#lobby .lb-mission {
  display: flex; flex-direction: column; gap: 2px; text-align: left; cursor: pointer;
  background: rgba(255, 255, 255, .03); border: 1px solid var(--lb-line); border-radius: 9px; padding: 9px 12px;
  color: var(--lb-ink); font-family: var(--lb-font);
}
#lobby .lb-mission:hover { border-color: var(--lb-line2); background: rgba(92, 200, 255, .08); }
#lobby .lb-mission:focus-visible { outline: 2px solid var(--lb-cyan); outline-offset: 2px; }
#lobby .lb-mission b { font: 700 13px var(--lb-font); letter-spacing: .02em; }
#lobby .lb-mission span { font: 500 11.5px var(--lb-font); color: var(--lb-muted); }
#lobby .lb-mission[disabled] { opacity: .45; cursor: not-allowed; }   /* a mission the player has not unlocked yet */
#lobby .lb-mission.done b { color: #bdf0d6; }
#lobby #tutResetBtn { align-self: flex-start; padding-left: 0; }      /* .lb-back centres itself; here it hangs off the CTA */
#lobby #tutResetBtn[hidden] { display: none; }

/* ============================== 1b. THE LEADER LINE ==============================
   Drawn from the card to the control the card names (src/client/ui/tutorial/pointer.mjs). z49: under #tutCard (50) so
   the card is never crossed by its own line, over every HUD panel (top is #emojiPicker 48) so the line is not buried
   under the thing it points at. pointer-events:none on the SVG AND on every child — the steps that draw a line are
   the steps that ask the player to click the target. */
#tutLine { position: absolute; inset: 0; z-index: 49; width: 100%; height: 100%; overflow: visible;
  pointer-events: none; }
#tutLine[hidden] { display: none !important; }
#tutLine * { pointer-events: none; vector-effect: non-scaling-stroke; }
#tutLinePath { fill: none; stroke: #5cc8ff; stroke-width: 2; stroke-linecap: round;
  filter: drop-shadow(0 0 6px rgba(92, 200, 255, .55)); }
#tutLineDot { fill: #5cc8ff; }
#tutLineRing { fill: none; stroke: #5cc8ff; stroke-width: 1.5; opacity: .45; }
/* Draw ON, once, when the line moves to a new target. The path carries pathLength="1", so one unit IS the whole line
   however long it happens to be — a fixed pixel dasharray was shorter than a card-to-corner line and left it visibly
   half-drawn. Paired with the camera glide s 620 ms. */
#tutLine.tut-line-in #tutLinePath { stroke-dasharray: 1; animation: tut-draw .42s ease-out forwards; }
#tutLine.tut-line-in #tutLineRing { animation: tut-ring 1.6s ease-in-out .3s infinite; }
@keyframes tut-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes tut-ring { 0%, 100% { r: 9; opacity: .45; } 50% { r: 13; opacity: .12; } }

/* The live reading (#tutVal): the step's own number, as it stands, in the player's own game. Monospace and tabular so
   a value that ticks does not reflow the sentence around it — a number that jitters horizontally is a number nobody
   reads. Given its own weight because it is the sentence that does the teaching, not the prose above it. */
.tut-val { margin: 0; padding: 6px 9px; border-radius: 7px;
  background: rgba(92, 200, 255, .08); border-left: 2px solid #5cc8ff;
  font: 600 12.5px ui-monospace, 'Cascadia Mono', Menlo, monospace; font-variant-numeric: tabular-nums;
  color: #cfe9fb; }
.tut-val[hidden] { display: none; }

/* ============================== 2. IN-MATCH — the coach card ============================== */
/* z-index 50 puts the card over EVERY HUD panel: the ladder in game.css tops out at #emojiPicker 48 (then #actMenu 46,
   .clickFx 45, #boatTip 44, the toasts 42, #spawnBar/#peaceVote 40, #atkForce 36 …). It stays BELOW #overEnd (60) on
   purpose — when the match actually ends, the end screen owns the screen, not the tutor. */
#tutCard {
  position: absolute; z-index: 50; box-sizing: border-box;
  width: min(330px, calc(100vw - 28px));
  background: #101b2e; border: 1px solid #28395a; border-left: 3px solid #5cc8ff; border-radius: 12px;
  padding: 12px 14px 13px; color: #e8eef7;
  font-family: 'Chakra Petch', 'Segoe UI', system-ui, sans-serif;
  box-shadow: 0 14px 38px rgba(0, 0, 0, .55);
  transition: opacity .16s ease, transform .18s ease;
}
/* There is NO scrim and NO cut-out: the card is the only thing that takes a pointer, so the map, the attack slider and
   every panel the step is TALKING about stay clickable underneath it. */
#tutCard { pointer-events: auto; }
/* If this stylesheet 404s, an unhidden absolute card is a nuisance; an unhidden #tutEnd (inset:0, opaque) would cover
   every live match. #leaderPage already shipped exactly that bug, so both surfaces carry their own !important hide. */
#tutCard[hidden] { display: none !important; }

/* ---- placement: one rule per data-card value, set by the step definition ---- */
#tutCard[data-card="left"]   { left: 14px;  top: 50%; transform: translateY(-50%); }
/* 88px, not 14px, and this is a bug fix rather than a margin. #zoom is right:18px and 54px wide (game.css), i.e. it
   occupies 18-72px from the right edge at exactly this vertical position — so a right-slotted card at 14px sits ON
   the + and − buttons at z50 against their z16, and elementFromPoint at #zIn's own centre returned the card at both
   1280x800 and 1024x640. Seven steps use this slot, including the two that ask the player to click a marked island
   and a marked teammate. There used to be a [data-spot="zoom"] variant holding 88px for this case; it was dead code,
   because the one step that highlights the zoom column is left-slotted. The whole slot clears the column now. */
#tutCard[data-card="right"]  { right: 88px; top: 50%; transform: translateY(-50%); }
#tutCard[data-card="top"]    { top: 100px;  left: 50%; transform: translateX(-50%); }   /* clears #balbar + #atkForce (top 62) */
#tutCard[data-card="bottom"] { bottom: 84px; left: 50%; transform: translateX(-50%); }  /* clears #atk (bottom 14 + 44 tall) */
#tutCard[data-card="center"] { top: 50%; left: 50%; transform: translate(-50%, -50%); }
/* DOCKED: the card has been placed against the element it explains (pointer.mjs dockCard), so its position comes from
   inline left/top and nothing here may fight it. The named slots above are the fallback, for steps whose subject is
   the MAP rather than a control — and for touch, where the card is a full-width sheet with nowhere to dock. */
/* ⚠ AND NO TRANSFORM TRANSITION WHILE DOCKED. Every slot above positions the card with a translate; docking
   positions it with left/top and cancels the translate. With `transition: transform .18s` still in force, that
   cancellation ANIMATES — so for 180ms the card is drawn up to 165px left and 105px above where its own geometry
   says it is, and pointer.mjs measures it in exactly that window. Measured: the reserve card reported a box whose
   bottom was 109px when it was really at 12px, and the leader line was drawn from a point in open space. */
#tutCard[data-card="dock"] { transform: none; transition: opacity .16s ease; }

/* ---- yielded: the action menu or the emoji picker opened UNDER the card. Fade rather than hide, so the player keeps
   the sentence they were reading, and drop pointer-events so the card cannot eat the tap it just asked for. ---- */
#tutCard.tut-yield { pointer-events: none; }

/* ---- head: chapter · counter · overflow ---- */
.tut-hd { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
.tut-ch { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 700 10px 'Chakra Petch', sans-serif; letter-spacing: .14em; text-transform: uppercase; color: #5cc8ff; }
.tut-count { font: 700 11px ui-monospace, monospace; color: #6f82a0; font-variant-numeric: tabular-nums; }
#tutMenuBtn { background: none; border: none; color: #8da2be; font: 700 16px system-ui; line-height: 1;
  padding: 0 2px; cursor: pointer; }
#tutMenuBtn:hover { color: #fff; }

.tut-ttl  { margin: 0 0 5px; font: 700 16px 'Chakra Petch', system-ui; color: #fff; letter-spacing: .01em; }
.tut-body { margin: 0; font: 500 13.5px/1.5 'Chakra Petch', system-ui; color: #cdd9ea; }
/* the hint is the second-chance line ("stuck? try…") — amber so it reads as a different voice from the instruction */
.tut-hint { margin: 9px 0 0; padding: 7px 10px; border-left: 2px solid rgba(224, 162, 58, .55);
  background: rgba(224, 162, 58, .1); border-radius: 0 7px 7px 0;
  font: 500 12.5px/1.45 'Chakra Petch', system-ui; color: #e8c07a; }
.tut-hint[hidden] { display: none; }

/* ---- progress meter for "do it N times" objectives; same shape as .pv-track so it reads as the same product ---- */
.tut-meter { position: relative; height: 22px; margin-top: 10px; background: #0c1424; border: 1px solid #1d2c45;
  border-radius: 6px; overflow: hidden; }
.tut-meter[hidden] { display: none; }
.tut-fill { position: absolute; inset: 0 auto 0 0; width: 0%; background: #2a6f8e; transition: width .18s linear; }
/* ⚠ A BINARY OBJECTIVE GETS A READOUT, NOT A PROGRESS BAR. "Wipe out Rook" has no partial credit before you engage
   him, so every bar drawn for it has been a lie in one direction or the other (see the note in missions.mjs). A row
   that declares a LABEL but no METER keeps the number and loses the track. */
.tut-meter[data-bar="none"] { background: #0a1120; border-color: #16243a; }
.tut-meter[data-bar="none"] .tut-fill { display: none; }
.tut-lab { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font: 700 11px ui-monospace, monospace; color: #fff; text-shadow: 0 1px 2px #000; pointer-events: none; }

/* ---- actions ---- */
.tut-act { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
.tut-ghost, .tut-go { border-radius: 8px; padding: 8px 14px; min-height: 34px;
  font: 700 12.5px 'Chakra Petch', system-ui; cursor: pointer; }
.tut-ghost { background: rgba(255, 255, 255, .06); border: 1px solid #28395a; color: #9fb2cc; }
.tut-ghost:hover { background: rgba(255, 255, 255, .12); color: #fff; }
.tut-ghost[hidden] { display: none; }
.tut-ghost[disabled] { opacity: .4; cursor: not-allowed; pointer-events: none; }
.tut-go { margin-left: auto; background: #2f86d6; border: 1px solid #2f86d6; color: #fff; }
.tut-go:hover { background: #3a97ea; }
/* "Continue" while the objective is unmet: still visible, obviously inert — a greyed button the player can see is a
   promise the step will end, where a hidden one reads as a dead tutorial. */
.tut-go[disabled] { background: #1e2c44; border-color: #24344f; color: #6f86a6; cursor: not-allowed; }
#tutCard button:focus-visible { outline: 2px solid #5cc8ff; outline-offset: 2px; }

/* ---- ⋯ overflow menu, anchored inside the card so it inherits its stacking position ---- */
#tutMenu { position: absolute; top: 34px; right: 10px; z-index: 1; display: flex; flex-direction: column;
  min-width: 186px; background: #0c1424; border: 1px solid #28395a; border-radius: 9px; overflow: hidden;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .6); }
#tutMenu[hidden] { display: none !important; }
#tutMenu button { background: none; border: none; border-bottom: 1px solid #1d2c45; color: #cdd9ea;
  padding: 10px 13px; min-height: 40px; text-align: left; font: 600 12.5px 'Chakra Petch', system-ui; cursor: pointer; }
#tutMenu button:last-child { border-bottom: none; }
#tutMenu button:hover { background: rgba(255, 255, 255, .07); color: #fff; }
#tutMQuit:hover { background: rgba(224, 83, 61, .35); color: #fff; }

/* ============================== 3. IN-MATCH — the mission result ============================== */
/* 62 is ABOVE #overEnd (60): a tutorial mission can end because the underlying match ended, and the player must see
   "mission complete / retry", not the generic victory screen with the tutor's buttons trapped behind it. */
#tutEnd {
  position: absolute; inset: 0; z-index: 62; background: rgba(10, 11, 14, .93);
  color: #e8eef7; font-family: 'Chakra Petch', 'Segoe UI', system-ui, sans-serif;
  animation: tut-fade .25s ease-out;
}
#tutEnd[hidden] { display: none !important; }
#tutEnd .mid { position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%);
  text-align: center; width: min(560px, 92%); }
#tutEnd .ttl { font: 800 clamp(30px, 6vw, 50px) 'Chakra Petch', system-ui; letter-spacing: .02em;
  margin-bottom: 10px; color: #f0d889; text-shadow: 0 6px 30px #000; }
#tutEnd .sub { color: #cdd3df; font-size: clamp(14px, 2.4vw, 18px); line-height: 1.5; }
#tutEnd.fail .ttl { color: #e2624a; }
.tut-end-act { position: absolute; bottom: 12%; left: 50%; transform: translateX(-50%);
  display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; width: min(560px, 92%); }
.tut-end-act button { border: 1px solid #28395a; border-radius: 10px; background: rgba(255, 255, 255, .07);
  color: #cdd9ea; padding: 12px 22px; min-height: 44px; font: 700 14px 'Chakra Petch', system-ui; cursor: pointer; }
.tut-end-act button:hover { background: rgba(255, 255, 255, .14); color: #fff; }
.tut-end-act button:focus-visible { outline: 2px solid #5cc8ff; outline-offset: 2px; }
#tutNextMis { background: #2f86d6; border-color: #2f86d6; color: #fff; }
#tutNextMis:hover { background: #3a97ea; }
#tutNextMis[hidden], #tutRetry[hidden] { display: none; }
@keyframes tut-fade { from { opacity: 0; } to { opacity: 1; } }

/* ============================== 4. THE DOM HIGHLIGHT ============================== */
/* .tut-spot is stamped on elements this stylesheet does NOT own (#atk, #zoom, #leaveBtn, a leaderboard row, a chat tab).
   It is therefore restricted to outline + box-shadow + border-radius, all of which paint outside the border box:
     · no position / margin / padding / width  -> it cannot reflow the HUD it is pointing at (a highlight that moves the
       button is a highlight that teaches the wrong place to press);
     · no z-index / transform / opacity / filter / will-change -> none of those create a stacking context here, so the
       target keeps its own place in the ladder and elementFromPoint(centre) still returns the control, not the tutor.
   The pulse animates box-shadow only, for the same reason. */
.tut-spot {
  outline: 2px solid #5cc8ff; outline-offset: 3px; border-radius: 10px;
  box-shadow: 0 0 0 3px rgba(92, 200, 255, .2), 0 0 22px 4px rgba(92, 200, 255, .4);
  animation: tut-pulse 1.5s ease-in-out infinite;
}
@keyframes tut-pulse {
  50% { box-shadow: 0 0 0 6px rgba(92, 200, 255, .08), 0 0 30px 8px rgba(92, 200, 255, .62); }
}

/* ============================== 5. TOUCH ============================== */
/* Gated on html.is-touch (main.mjs sets it from pointer:coarse), never on a width query: a phone in LANDSCAPE is 844px
   wide and every max-width rule this project ever wrote missed it, handing a thumb the desktop HUD. See game.css §TOUCH. */
html.is-touch #tutCard {
  left: 8px; right: 8px; width: auto; top: auto;
  bottom: calc(96px + env(safe-area-inset-bottom));   /* above #atk (10px) and #atkChips (54px), both safe-area-shifted */
  transform: none; max-height: 38vh; overflow: auto; -webkit-overflow-scrolling: touch;
  padding: 11px 13px 12px;
}
/* When the step is pointing at the bottom furniture, a bottom sheet is standing ON the answer. Go to the top instead —
   #balbar shrinks on touch and #atkForce tucks under it, so the top strip is the free one. */
html.is-touch #tutCard[data-spot="atk"],
html.is-touch #tutCard[data-spot="atkchips"],
html.is-touch #tutCard[data-spot="zoom"] {
  top: calc(64px + env(safe-area-inset-top)); bottom: auto; max-height: 34vh;
}
html.is-touch #tutCard button { min-height: 44px; min-width: 44px; }
html.is-touch #tutMenuBtn { padding: 0 10px; }
html.is-touch .tut-act { gap: 10px; }
html.is-touch .tut-ttl { font-size: 15px; }
html.is-touch #tutMenu { right: 8px; min-width: 200px; }
html.is-touch #tutMenu button { min-height: 46px; }
html.is-touch #tutEnd .mid { top: 36%; }
html.is-touch .tut-end-act { bottom: calc(8% + env(safe-area-inset-bottom)); }
html.is-touch .tut-spot { outline-width: 3px; }   /* a 2px ring is invisible under a finger at arm's length */

/* ============================== 6. REDUCED MOTION ============================== */
/* The pulse and the card's slide are decoration; the ring and the placement carry the meaning, so only the movement goes. */
@media (prefers-reduced-motion: reduce) {
  .tut-spot { animation: none; }
  #tutCard { transition: none; }
  #tutEnd { animation: none; }
  .tut-fill { transition: none; }
}

/* Optional explanations stay collapsed; one task remains visible on a small screen. */
#tutDetails { margin-top: 8px; color: #bbcbdc; font: 12px/1.55 system-ui, sans-serif; }
#tutDetails summary { color: #8fd6ff; cursor: pointer; padding: 7px 0; }
#tutDetails p { margin: 4px 0; max-height: 145px; overflow: auto; }
#tutDetails[hidden], #tutPromotion[hidden], #tutClaim[hidden], #tutRewardBtn[hidden] { display: none !important; }
#tutEnd { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 22px; padding: 24px 16px; box-sizing: border-box; overflow-y: auto; }
#tutEnd .mid, #tutEnd .tut-end-act { position: static; transform: none; flex-shrink: 0; }
#tutPromotion { width: min(420px, 95%); text-align: center; padding: 18px; box-sizing: border-box;
  background: #111d2c; border: 1px solid #8c7440; border-radius: 16px; }
#tutInsignia svg { width: 76px; height: 76px; }
#tutRankName { display: block; color: #f0d889; font-size: 24px; }
#tutRewardStatus { font: 14px/1.5 system-ui, sans-serif; color: #cdd9ea; }
#tutClaim { min-height: 44px; padding: 10px 18px; border: 1px solid #a6d5ff; border-radius: 8px;
  background: #226da8; color: white; cursor: pointer; }
#tutClaim:disabled { opacity: .6; cursor: wait; }
@media(max-height:600px) { #tutEnd { justify-content: flex-start; gap: 12px; } #tutEnd .ttl { font-size: 28px; } }

/* The instruction stays readable while the real map and controls remain interactive. */
.tut-state { margin: 6px 0; color: #bce4d1; font: 600 11px system-ui, sans-serif; letter-spacing: .04em; }
.tut-state[hidden] { display: none; }


/* ==== lobby-features.css ==== */
/* Feature styles consume these only while the lobby is visible. Their fallback palette remains the match palette. */
html.lobby-active {
  --site-ui-ink:var(--site-ink); --site-ui-muted:var(--site-muted); --site-ui-line:var(--site-line);
  --site-ui-accent:var(--site-accent); --site-ui-panel:var(--site-panel); --site-ui-hover:var(--site-hover);
  --site-ui-danger:var(--site-danger); --site-ui-gold:var(--site-gold); --site-ui-gold-well:var(--site-gold-well);
  --site-ui-shadow:var(--site-shadow); --site-ui-font:'Segoe UI';
}
.lobby-active #clanPage { --cp-font:var(--site-font); --cp-bg:var(--site-bg); --cp-rail:var(--site-chat); --cp-center:var(--site-panel); --cp-panel:var(--site-well); --cp-cyan:var(--site-accent); --cp-blue:var(--site-accent); --cp-line2:var(--site-line); }
.lobby-active #profilePage { --pf-font:var(--site-font); --pf-bg:var(--site-bg); --pf-card:var(--site-panel); --pf-cyan:var(--site-accent); --pf-line2:var(--site-line); }
.lobby-active :is(#clanPage,#profilePage,#leaderPage) { font-size:14px; }
.lobby-active :is(#clanPage,#profilePage,#leaderPage) :is(input,select,textarea) { color:var(--site-ink); background:var(--site-well); border-color:var(--site-line); border-radius:4px; font-size:13px; }
.lobby-active :is(#clanPage,#profilePage,#leaderPage) :is(input,select,textarea)::placeholder { color:var(--site-muted); }
.lobby-active :is(#clanPage,#profilePage) :is(h1,h2,h3) { letter-spacing:-.3px; }
.lobby-active #profilePage .pf-card { border-radius:5px; padding:22px; box-shadow:none; }
.lobby-active #profilePage .pf-title { font-size:26px; }
.lobby-active #profilePage .pf-stat { border-radius:5px; padding:16px; }
.lobby-active #profilePage .pf-stat b { color:var(--site-accent); }
.lobby-active #profilePage .pf-rank { background:var(--site-well); }
.lobby-active #profilePage .pf-rname { color:var(--site-ink) !important; }
.lobby-active #profilePage .pf-rins { filter:brightness(.65); }
.lobby-active[data-lobby-theme="dusk"] #profilePage .pf-rins { filter:none; }
.lobby-active #profilePage .pf-rbar { background:var(--site-line); }
.lobby-active #profilePage .pf-rbar i { background:var(--site-gold) !important; }
.lobby-active #clanPage :is(.cp-card,.cp-ovcard,.cp-form,.cp-logoprev,.cp-emblem) { border-radius:5px; box-shadow:none; }
.lobby-active #clanPage .cp-sect { color:var(--site-muted); letter-spacing:1px; }
.lobby-active #clanPage :is(.cp-chan,.cp-func) { border-radius:3px; font-size:12px; }
.lobby-active #clanPage .cp-chan.on { background:var(--site-hover); color:var(--site-accent); border-left:2px solid var(--site-accent); }
.lobby-active #clanPage .cp-motd { background:var(--site-well); color:var(--site-ink); border-color:var(--site-line); border-radius:4px; }
.lobby-active #clanPage .cp-idname { color:var(--site-ink); }
.lobby-active #clanPage :is(.cp-emblem,.cp-logoph) { color:var(--site-ink); }
.lobby-active #clanPage .cp-badge { background:var(--site-gold); color:var(--site-on-accent); }
.lobby-active #leaderPage :is(.lp-mine,.lp-myplayer) { background:var(--site-hover); border-color:var(--site-line); border-radius:5px; }
.lobby-active #leaderPage .lp-row.me { background:var(--site-hover); border-left-color:var(--site-accent); }
.lobby-active #leaderPage .lp-tabs { margin-bottom:22px; }
.lobby-active #leaderPage .lp-tab { background:none; border:0; border-bottom:2px solid transparent; border-radius:0; color:var(--site-muted); }
.lobby-active #leaderPage .lp-tab.on { color:var(--site-accent); border-bottom-color:var(--site-accent); }
.lobby-active #leaderPage .lp-row { padding-top:15px; padding-bottom:15px; }
.lobby-active #leaderPage .lp-rank { color:var(--site-muted) !important; }
.lobby-active :is(#acctModal,#partyPanel,#clanPage #cpOverlay,#applyModal) { background:var(--site-overlay); }
.lobby-active #acctModal .card { border-radius:7px; box-shadow:0 18px 60px var(--site-shadow); padding:30px; color:var(--site-ink); }
.lobby-active #acctModal :is(#acAuthSubmit,#acAuthDiscord),
.lobby-active #profilePage :is(.pf-btn:not(.ghost):not(.danger)),
.lobby-active #clanPage :is(.cp-btn:not(.ghost):not(.cp-danger)),
.lobby-active :is(.pp-ok,.pp-ready) { background:var(--site-action); color:var(--site-on-action); border-color:var(--site-action); border-radius:4px; }
.lobby-active #acctModal .close { width:32px; padding:0; background:none; color:var(--site-muted); }
.lobby-active :is(#partyPanel .pp-card,#vcPanel) { background:var(--site-panel); border-radius:6px; box-shadow:0 15px 40px var(--site-shadow); }
.lobby-active #partyBar .pb-pill { background:var(--site-well); border-color:var(--site-line); color:var(--site-ink); border-radius:4px; box-shadow:none; }
.lobby-active #partyBar .pb-pill b { color:var(--site-ink); }
.lobby-active #chatDock .chatTabs button { font-family:var(--site-font); text-transform:none; letter-spacing:0; font-size:12px; }
.lobby-active #chatDock .chatTabs button.active { color:var(--site-accent); border-color:var(--site-accent); }
.lobby-active #chatHubMsgs .txt { color:var(--site-ink); font-size:14px; line-height:1.55; }
.lobby-active #chatHubMsgs .who { color:var(--site-ink); }
.lobby-active :is(#chatHubMsgs .cav,#clanPage .cp-av) { color:#fff; }
.lobby-active :is(#chatHubInput,#dmToInput,#cpInput) { color:var(--site-ink); background:var(--site-panel); border-color:var(--site-line); border-radius:4px; }
.lobby-active :is(#chatHubSend,#cpSend) { background:var(--site-action); color:var(--site-on-action); border-radius:4px; }
.lobby-active :is(#chatEmojiBtn,#chatGifBtn) { background:none; color:var(--site-muted); border-color:var(--site-line); border-radius:4px; }
.lobby-active :is(.chatMenu,.cmenu,.prof-card,.profile-card) { background:var(--site-panel); color:var(--site-ink); border:1px solid var(--site-line); border-radius:5px; }
.lobby-active :is(.pw-notice,.pw-ask-card) { background:var(--site-panel); color:var(--site-ink); border-color:var(--site-line); }
.lobby-active #pwaBanner { position:relative; background:var(--site-well); color:var(--site-ink); border-top:1px solid var(--site-line); }
.lobby-active #pwaBanner button { background:var(--site-action); color:var(--site-on-action); }
.lobby-active #clanPage .cp-rchip { color:var(--site-ink); border-color:var(--site-line); background:var(--site-panel); border-radius:3px; }
.lobby-active #clanPage .cp-rchip::before { content:''; display:inline-block; width:5px; height:5px; margin-right:4px; border-radius:50%; background:var(--rc); }
.lobby-active #clanPage .cp-dot { box-shadow:none; border:1px solid var(--site-line); background:transparent; }
.lobby-active #clanPage .cp-mrow.on .cp-dot { box-shadow:none; background:var(--site-accent); border-color:var(--site-accent); }

/* Different insignia silhouettes must not change a chat header height. */
.lobby-active #chatHubMsgs .chead { align-items:center; min-height:20px; }
.lobby-active #chatHubMsgs .who { display:inline-flex; align-items:center; line-height:20px; min-height:20px; }

/* Quiet chrome; uploaded avatars, flags and medals retain their identity colours. */
.lobby-active #chatHubMsgs .cmsg.head { margin-top:16px; }
.lobby-active #chatHubMsgs .cdivider { margin-bottom:12px; letter-spacing:.04em; }
.lobby-active #chatHubMsgs .cdivider::before,.lobby-active #chatHubMsgs .cdivider::after { background:var(--site-line); }
.lobby-active #chatHubMsgs .cav { border-radius:4px; }
.lobby-active #chatHubMsgs .cav:not(:has(img)):not(.sp) { background:var(--site-hover) !important; color:var(--site-muted); border:1px solid var(--site-line); box-sizing:border-box; }
.lobby-active #chatHubSend { background:transparent; color:var(--site-accent); border-color:var(--site-line); }
.lobby-active #profilePage .pf-rank { border-left:3px solid var(--site-gold); }
.lobby-active :is(#partyBar .pb-pill,#vcPill) { font-family:var(--site-font); }

/* A single drawn icon vocabulary replaces decorative emoji in the lobby controls. */
.ui-icon { display:inline-block; width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round; vertical-align:middle; flex:none; }
.vc-lobby-icon { display:none; }
.lobby-active .vc-game-icon { display:none; }
.lobby-active .vc-lobby-icon { display:inline-flex; }
.lobby-active #vcPill { display:flex; align-items:center; justify-content:center; }
.lobby-active #clanPage .cp-func { display:flex; gap:9px; align-items:center; font-size:12px; }
.lobby-active #clanPage .cp-func .ui-icon { width:15px; height:15px; color:var(--site-muted); }
.lobby-active #clanPage .cp-lock .ui-icon { width:12px; height:12px; }
.lobby-active #clanPage .cp-motd .ui-icon { width:14px; height:14px; margin-right:5px; color:var(--site-muted); }
.lobby-active #clanPage .cp-sect { letter-spacing:.06em; }
.lobby-active #clanPage .cp-chan { font-size:13px; }
.lobby-active #partyBar .pb-pill b { font-weight:600; }
.lobby-active #partyPanel .pp-head h3 { font-size:15px; font-weight:600; }
.lobby-active :is(#chatEmojiBtn,#cpEmojiBtn,#chatHubSend) { display:flex; align-items:center; justify-content:center; }


/* ==== lobby-shell.css ==== */
/* Lobby-only colour and layout system. Gameplay keeps its original style tree. */
:root {
  --site-bg:#e6e7e9; --site-panel:#f3f4f4; --site-well:#eceeef; --site-chat:#dde0e3;
  --site-ink:#272b30; --site-muted:#59616a; --site-line:#c7cbd0; --site-hover:#d9dde2;
  --site-accent:#345f94; --site-accent-hover:#284d7b; --site-on-accent:#ffffff;
  --site-action:var(--site-accent); --site-action-hover:var(--site-accent-hover); --site-on-action:#ffffff;
  --site-gold:#736038; --site-gold-well:#e9e5dc; --site-danger:#a63e3b;
  --site-rail:#292c30; --site-rail-ink:#edf0f2; --site-rail-muted:#b7bdc5;
  --site-rail-hover:#353a40; --site-rail-line:#444a52; --site-rail-active:#3d4652;
  --site-map:#303438; --site-shadow:#14181d18; --site-overlay:#171b226e;
  --site-side:184px; --site-chat-width:310px; --site-chat-space:0px; --site-top:60px;
  /* ⚠ THE EMOJI FONT GOES LAST, AND THIS LINE IS WHY THE NUMBERS VANISHED ON PHONES. TwemojiMozilla.ttf carries
     glyphs for all ten ASCII DIGITS — they are the bases of the keycap emoji — so listed SECOND it supplied them
     to anything that could not find the family in front of it. 'Segoe UI' is Windows-only: on a desktop the digits
     came from Segoe UI and everything looked right, while on Android and iOS the countdown, the player count and
     the "+N more" rendered as blank keycap glyphs. Letters were unaffected (Twemoji has none), which is what made
     it look like missing data rather than a font: "0:28" showed as a lone colon. `font-display:swap` finished the
     illusion — the digits painted in the fallback for a moment and then disappeared as the font loaded.
     VERIFIED by reading the TTF's cmap: 10/10 digits present. Test: test/emoji-font-stack.test.mjs. */
  --site-font:'Segoe UI',system-ui,sans-serif,'Twemoji Mozilla';
}
:root[data-lobby-theme="dusk"] {
  --site-bg:#303338; --site-panel:#393d43; --site-well:#34383e; --site-chat:#2b2f34;
  --site-ink:#e6e9ed; --site-muted:#b2bac4; --site-line:#505761; --site-hover:#454c56;
  --site-accent:#a0bee5; --site-accent-hover:#bbd1ed; --site-on-accent:#20364f;
  --site-action:#456d9e; --site-action-hover:#507aaa; --site-on-action:#f2f4f6;
  --site-gold:#c9b583; --site-gold-well:#444139; --site-danger:#eeaaa5;
  --site-map:#262a2f; --site-shadow:#11141930;
}
html.lobby-active { background:var(--site-bg); color:var(--site-ink); color-scheme:light; }
html.lobby-active[data-lobby-theme="dusk"] { color-scheme:dark; }
html.lobby-active.dock-open { --site-chat-space:var(--site-chat-width); }
html.lobby-active[data-lobby-page="clanPage"] { --site-chat-space:0px; }
html.lobby-active :is(#siteSidebar,#siteHeader,#lobby,#clanPage,#profilePage,#leaderPage,#chatDock,#siteSettings) { font-family:var(--site-font); color:var(--site-ink); }
html.lobby-active :is(button,input,select,textarea) { font-family:var(--site-font); }
html.lobby-active button { cursor:pointer; }
html.lobby-active :is(button,a,input,select,textarea):focus-visible { outline:2px solid var(--site-accent); outline-offset:3px; }
.site-icon { width:19px; height:19px; flex:none; fill:none; stroke:currentColor; stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round; }
#siteSidebar,#siteHeader { display:none; box-sizing:border-box; }
html.lobby-active #siteSidebar { display:flex; position:fixed; inset:0 auto 0 0; width:var(--site-side); z-index:80; flex-direction:column; padding:24px 14px 18px; gap:26px; background:var(--site-rail); color:var(--site-rail-ink); border-right:1px solid var(--site-rail); }
#siteHome { display:flex; align-items:center; gap:10px; background:none; border:0; color:inherit; padding:0 4px; font-size:24px; font-weight:750; letter-spacing:-1.3px; text-align:left; }
.site-mark { display:block; width:20px; height:21px; background:var(--site-rail-ink); mask:url('/public/brand/pixelwar-mark.svg') center/contain no-repeat; -webkit-mask:url('/public/brand/pixelwar-mark.svg') center/contain no-repeat; }
.site-nav { display:flex; flex-direction:column; gap:6px; }
.site-nav a { box-sizing:border-box; text-decoration:none; font-family:inherit; line-height:normal; }
.site-nav :is(button,a) { display:flex; align-items:center; gap:12px; width:100%; padding:12px; background:none; border:1px solid transparent; border-radius:5px; color:var(--site-rail-muted); text-align:left; font-size:14px; font-weight:500; }
.site-nav :is(button,a):hover { background:var(--site-rail-hover); color:var(--site-rail-ink); }
.site-nav :is(button,a)[aria-current="page"] { background:var(--site-rail-active); border-color:transparent; color:var(--site-rail-ink); box-shadow:inset 2px 0 #a9c0de; }
.site-community { border-top:1px solid var(--site-rail-line); padding:22px 10px 0; }
.site-community small { color:var(--site-rail-muted); font-size:10px; letter-spacing:1.5px; }
.site-community p { color:var(--site-rail-muted); font-size:12px; line-height:1.65; }
.site-tools { margin-top:auto; }
#siteSidebar #acctBar { display:flex; flex-wrap:wrap; align-items:center; gap:10px; width:100%; padding:18px 4px 0; margin:0; border:0; border-top:1px solid var(--site-rail-line); background:none; border-radius:0; box-sizing:border-box; }
#siteSidebar #acctName { color:var(--site-rail-ink); font-size:14px; }
#siteSidebar #acctRank { display:grid; grid-template-columns:32px 1fr; align-items:center; gap:5px 9px; width:100%; margin-bottom:16px; color:var(--site-rail-muted); }
#siteSidebar #acctRank svg { width:27px; height:27px; grid-row:1/3; }
#siteSidebar #acctRank b { color:var(--site-rail-ink) !important; font-size:12px; }
#siteSidebar .acct-rpts { font-size:25px; color:var(--site-rail-ink); font-style:normal; }
#siteSidebar .acct-rpts::after { content:' points'; font-size:10px; color:var(--site-rail-muted); }
#siteSidebar .acct-rnext { grid-column:1/-1; font-size:10px; font-style:normal; color:var(--site-rail-muted); }
#siteSidebar #acctBar button { background:var(--site-rail-active); border:1px solid var(--site-rail-line); border-radius:4px; padding:8px 11px; color:var(--site-rail-ink); font-size:11px; }
#siteSidebar #acctAuthBtn { width:100%; order:4; }
#siteSidebar #acctAv { order:1; } #siteSidebar #acctName { order:2; } #siteSidebar #acctGear { order:3; }
#siteSidebar #acctAv { background:var(--site-rail-hover) !important; color:var(--site-rail-ink); }
#siteSidebar .isHidden,#siteHeader .isHidden { display:none !important; }
/* Present to a screen reader and to a crawler, absent from the picture — for the lobby's <h1>, whose job is to give
   the document an outline without adding a second wordmark beside the sidebar's.
   ⚠ NOT `display:none` and NOT `visibility:hidden`: both remove the element from the accessibility tree, which
   defeats the entire purpose, and Google discounts text it can see is hidden that way. The clip-rect form is the one
   that keeps the text real. `white-space:nowrap` stops the 1px box from wrapping the heading into a tall column that
   can still scroll the page. */
.a11y-only { position:absolute !important; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0; }
.lobby-active #siteHeader { display:flex; position:fixed; left:var(--site-side); right:var(--site-chat-space); top:0; height:var(--site-top); align-items:center; justify-content:space-between; gap:16px; padding:0 28px; border-bottom:1px solid var(--site-line); background:var(--site-bg); z-index:80; }
#siteTitle { font-size:14px; font-weight:500; }
.site-header-actions { display:flex; align-items:center; gap:20px; }
.site-status { color:var(--site-muted); font-size:10px; letter-spacing:1.3px; }
.site-status::before { content:''; display:inline-block; width:5px; height:5px; margin-right:8px; background:var(--site-accent); border-radius:50%; }
#siteChat,#notifBell { display:flex; align-items:center; gap:7px; padding:7px 0; border:0; background:none; color:var(--site-muted); font-size:12px; }
/* The header's own unread mark — same count chathub.mjs stamps on the floating button, in the place that is on
   screen at every width (the FAB is hidden on a phone; see the note in the max-width:759px block). */
#siteChat { position:relative; }
#siteChat[data-n]:not([data-n=""])::after { content:attr(data-n); position:absolute; right:-9px; top:-3px; min-width:16px; height:16px;
  padding:0 3px; box-sizing:border-box; display:flex; align-items:center; justify-content:center;
  background:var(--site-gold); color:var(--site-rail-ink); border-radius:8px; font-size:10px; font-weight:600; }
#notifWrap { position:relative; }
#notifBadge { display:none; position:absolute; right:-7px; top:-4px; min-width:16px; line-height:16px; padding:0 2px; background:var(--site-gold); color:var(--site-rail-ink); border-radius:50%; font-size:10px; }
#notifBadge.show { display:block; }
#notifPanel { display:none; position:absolute; top:40px; right:0; width:330px; max-width:calc(100vw - 28px); max-height:65vh; overflow:auto; border:1px solid var(--site-line); background:var(--site-panel); box-shadow:0 15px 40px var(--site-shadow); border-radius:6px; }
#notifPanel.show { display:block; }
.nt-head { display:flex; justify-content:space-between; align-items:center; padding:16px; border-bottom:1px solid var(--site-line); font-size:13px; }
#notifMarkAll { border:0; background:none; color:var(--site-accent); font-size:11px; }
#notifBody { font-size:13px; }
#notifBody .nt-time,#notifBody .nt-empty { color:var(--site-muted); font-size:11px; padding:10px; }
.lobby-active #lobby { position:fixed; display:flex; flex-direction:column; align-items:stretch; justify-content:flex-start; padding:0; inset:var(--site-top) var(--site-chat-space) 0 var(--site-side); background:var(--site-bg); background-image:none; z-index:50; overflow:auto; }
#lobby .lb-main { width:100%; max-width:960px; margin:0 auto; padding:20px 28px 38px; box-sizing:border-box; display:block; }
.site-modebar { display:flex; align-items:center; gap:24px; border-bottom:1px solid var(--site-line); margin-bottom:14px; }
.site-modebar button { background:none; border:0; border-bottom:2px solid transparent; padding:0 0 12px; font-size:13px; color:var(--site-muted); }
#lobby:not(.sp-mode) #siteMultiplayer,#lobby.sp-mode #spBtn { color:var(--site-accent); border-bottom-color:var(--site-accent); font-weight:700; }
#lobby .lb-tag { display:flex; align-items:center; gap:12px; margin:0; font-size:12px; color:var(--site-muted); }
#lobby #nameIn { width:180px; max-width:65%; padding:8px 10px; color:var(--site-ink); background:var(--site-panel); border:1px solid var(--site-line); border-radius:4px; font-size:13px; }
.site-player { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:14px; }
#lobby .lb-namehint { flex:none; font:11px var(--site-font); color:var(--site-muted); }
#lobby .lb-namehint button { font:inherit; color:var(--site-accent); background:none; border:0; text-decoration:underline; padding:6px 0 6px 3px; }
#lobby .lb-tag.isLocked #nameIn { background:transparent; border-color:transparent; padding-left:0; width:130px; font-weight:600; }
#lobby .lb-tag:has(+.lb-namehint) #siteSignedNote { display:none; }
#siteSignedNote { margin-left:auto; text-align:right; font-size:10px; max-width:195px; line-height:1.5; }
#lobby .lb-cards { display:grid; grid-template-columns:1fr; gap:22px; }
#lobby .lb-card { position:relative; border:1px solid var(--site-line); border-radius:6px; background:var(--site-panel); overflow:hidden; padding:0; box-shadow:0 3px 8px var(--site-shadow); }
#lobby .lb-card-wait { display:none; }
.site-match-head { display:flex; align-items:flex-start; justify-content:space-between; gap:14px; flex-wrap:wrap; padding:19px 22px 17px; }
.site-overline { font-size:10px; letter-spacing:1.2px; color:var(--site-muted); margin:0 0 7px; font-weight:600; }
#lobby .lb-card-name { margin:0; color:var(--site-ink); font-size:28px; line-height:1.15; font-weight:650; letter-spacing:-.7px; }
.site-mode-chip { font-size:11px; color:var(--site-muted); background:transparent; border:1px solid var(--site-line); border-radius:3px; padding:5px 9px; }

/* THE COUNTDOWN IS THE OTHER HALF OF THE HEAD. It used to be a small "Starts in 0:24" buried in the body row next
   to the player count, which is the one number a player in the lobby is actually watching. It now mirrors the map
   name at the opposite end of the same row, at the SAME SIZE (it inherits .lb-card-name and only changes
   alignment), so the card reads as one line: this map, starting in this long. */
/* THE HEAD IS THREE COLUMNS: what is starting, what follows it, how long you have. Each is an overline over a
   value, so the row reads left to right as one sentence. The rotation only helps a player who can SEE it — the
   alternative is sitting a round out to discover what comes next — and it belongs between the two, because it is
   the thing you weigh against the clock when deciding whether to press Join. */
.site-match-now { min-width:0; flex:0 1 auto; }
.site-match-upnext { flex:1 1 auto; text-align:center; min-width:0; padding:0 10px; }
/* SAME SIZE AS THE MAP NAME. It inherits .lb-card-name and overrides nothing about the type, so the three values
   in the head are one size and cannot drift apart when that size is next changed. */
#lobby .lb-card-next { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.lb-next-row { display:flex; align-items:center; justify-content:center; gap:10px; min-width:0; }
/* The same treatment as the big card image — same grayscale, same brightness — because it is the same map, and a
   thumbnail that looked different would read as a different picture rather than a smaller one. */
.lb-next-thumb { flex:0 0 auto; width:52px; height:34px; border-radius:3px; border:1px solid var(--site-line);
  background:var(--site-map) center/cover no-repeat; filter:grayscale(1) brightness(.85) contrast(1.15); opacity:.9; }
.site-match-when { text-align:right; flex:0 0 auto; }
.site-match-when .site-overline { display:flex; align-items:center; justify-content:flex-end; gap:8px; margin-bottom:7px; }
/* tabular figures so the last digit does not shuffle the whole number every second */
#lobby .lb-timer { font-variant-numeric:tabular-nums; white-space:nowrap; }
/* the final ten seconds are the only moment the number changes what a player DOES */
#lobby .lb-timer.is-soon { color:var(--site-danger); }
.site-map-wrap { position:relative; background:var(--site-map); }
#lobby .lb-card-img { position:relative; height:214px; background-position:center; background-repeat:no-repeat; background-size:contain; filter:grayscale(1) brightness(.85) contrast(1.15); opacity:.9; }
.site-map-caption { position:absolute; left:24px; bottom:16px; padding:6px 10px; background:#272c32e8; color:#d8dde4; font-size:10px; border:1px solid #59626e; border-radius:3px; }
#lobby .lb-card-body { padding:0; }
.site-joinrow { display:flex; justify-content:space-between; gap:15px; align-items:center; padding:19px 22px; }
#lobby .lb-card-sub { margin:0 0 6px; font-size:13px; color:var(--site-ink); }
#lobby .lb-card-sub b { font-weight:650; }
.site-countdown { font-size:11px; color:var(--site-muted); }
.site-countdown strong { color:var(--site-gold); font-weight:600; font-variant-numeric:tabular-nums; }
#lobby .lb-cta { flex:none; min-width:155px; min-height:46px; padding:12px 20px; background:var(--site-action); color:var(--site-on-action); border:1px solid var(--site-action); border-radius:4px; box-shadow:none; font-size:14px; font-weight:600; text-transform:none; letter-spacing:0; }
#lobby .lb-cta:hover { background:var(--site-action-hover); filter:none; }
#lobby .lb-cta.joined { background:var(--site-gold-well); color:var(--site-gold); border-color:var(--site-gold); }
#lobby .lb-cta:disabled { opacity:.6; cursor:default; }
#lobby .lb-queue { margin:0; padding:12px 22px; border-top:1px solid var(--site-line); font-size:11px; color:var(--site-muted); line-height:1.6; }
#lobby .lb-mpfoot { margin:12px 0 20px; }
#mpStatus2 { font-size:12px; color:var(--site-muted); }
/* ⚠ THE NUMBERS ARE ONLY AS LIVE AS THE SOCKET THAT FEEDS THEM. One {t:"lobby"} message per second carries the
   countdown, the player count and the queue names; when it stops — a suspended phone loses its socket without ever
   closing it — the card kept showing its last values as if they were current. Marked instead: the three volatile
   values go quiet while main.mjs re-dials (see the lobby watchdog), and #mpStatus2 says so in words. */
#lobby #mpHub.lb-stale :is(.lb-timer, .lb-card-sub b, .lb-queue) { opacity:.4; }
#mpReconnect { display:none; background:var(--site-gold-well); border:1px solid var(--site-line); color:var(--site-gold); padding:11px 15px; border-radius:4px; margin-top:10px; }
.site-party-section { margin-top:23px; padding-top:20px; border-top:1px solid var(--site-line); }
.site-party-section h2 { font-size:14px; font-weight:600; margin:0 0 14px; }
.site-party-row { display:flex; gap:14px; align-items:center; }
.site-party-symbol { padding:10px; background:var(--site-panel); border:1px solid var(--site-line); border-radius:4px; color:var(--site-muted); }
#sitePartyText { flex:1; font-size:13px; color:var(--site-muted); }
.site-secondary { border:1px solid var(--site-line); background:var(--site-panel); color:var(--site-ink); border-radius:4px; padding:9px 13px; font-size:12px; }
#lobby #spPanel { display:none; } #lobby.sp-mode #spPanel { display:block; } #lobby.sp-mode #mpHub,#lobby.sp-mode .lb-mpfoot { display:none; }
#lobby .lb-setup { display:flex; gap:20px; padding:20px 24px 0; }
#lobby .lb-ctrl { display:flex; gap:7px; flex-direction:column; font-size:11px; color:var(--site-muted); }
#lobby .lb-ctrl :is(select,input) { background:var(--site-well); border:1px solid var(--site-line); border-radius:4px; padding:9px 11px; color:var(--site-ink); font-size:13px; max-width:210px; }
#lobby #botCountIn { width:95px; }
#lobby .lb-card-learn { margin-top:22px; padding:24px; background:var(--site-panel); border-left:3px solid var(--site-line); }
#lobby .lb-card-learn .lb-card-name { font-size:21px; }
#lobby .lb-card-learn .lb-card-sub { margin:9px 0 18px; line-height:1.6; }
#lobby .lb-card-learn .lb-cta { min-width:0; }
#lobby .lb-missions { margin-top:20px; }
#lobby .lb-missions button { background:var(--site-panel); color:var(--site-ink); border:1px solid var(--site-line); border-radius:4px; padding:12px; margin:3px; }
#lobby .lb-back { background:none; border:0; color:var(--site-muted); padding:15px 0; font-size:12px; }
#lobby .lb-chip { color:var(--site-gold); font-size:10px; }
#siteSettings { background:var(--site-panel); color:var(--site-ink); border:1px solid var(--site-line); border-radius:8px; width:min(460px,calc(100vw - 32px)); padding:28px; box-sizing:border-box; max-height:calc(100dvh - 32px); }
#siteSettings::backdrop { background:var(--site-overlay); }
.site-dialog-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:24px; }
.site-dialog-head h2 { margin:0; font-size:21px; }
#siteSettingsClose { border:0; color:var(--site-muted); background:none; font-size:25px; }
.site-setting { display:flex; flex-direction:column; gap:9px; font-size:13px; margin:20px 0; }
.site-setting select { padding:10px; background:var(--site-well); color:var(--site-ink); border:1px solid var(--site-line); border-radius:4px; font-size:13px; }
.site-setting-check { display:flex; gap:10px; font-size:13px; margin:24px 0; }
#siteSettings input { accent-color:var(--site-accent); }
.site-dialog-note { color:var(--site-muted); font-size:12px; line-height:1.7; border-top:1px solid var(--site-line); padding-top:17px; }
.lobby-active #partyBar .pb-pill { max-width:100%; font-size:11px; padding:9px 13px; }
.lobby-active #chatDock { inset:0 0 0 auto; width:var(--site-chat-width); height:100dvh; max-height:none; border:0; border-left:1px solid var(--site-line); border-radius:0; box-shadow:none; background:var(--site-chat); z-index:79; }
.lobby-active #chatDock::before { content:'Chat'; min-height:var(--site-top); display:flex; align-items:center; padding:0 20px; font-size:14px; font-weight:600; border-bottom:1px solid var(--site-line); box-sizing:border-box; }
.lobby-active .chatHubHead { background:transparent; padding:10px 12px; }
.lobby-active #chatHubMsgs { padding:16px 7px 22px; }
.lobby-active #chatDock .chatbar { padding:16px 12px; border-top:1px solid var(--site-line); }
.lobby-active #chatHubClose { position:absolute; right:15px; top:19px; font-size:20px; }
.lobby-active #chatFab { right:20px; bottom:18px; background:var(--site-action); color:var(--site-on-action); box-shadow:none; border:0; border-radius:4px; font-family:var(--site-font); }
.lobby-active[data-lobby-page="clanPage"] :is(#chatDock,#chatFab) { display:none !important; }
.lobby-active :is(#profilePage,#leaderPage,#clanPage) { top:var(--site-top); left:var(--site-side); right:var(--site-chat-space); background:var(--site-bg); }
.lobby-active :is(#profilePage .pf-top,#leaderPage .cp-top,#clanPage .cp-top) { display:flex; padding:10px 24px 0; background:var(--site-bg); border:0; }
.lobby-active :is(.pf-brand,.cp-brand,.cp-acct) { display:none !important; }
.lobby-active :is(#pfBack,#leaderBack,#clanBack) { border:0; padding:4px 0; font:11px var(--site-font); color:var(--site-muted); background:none; text-transform:none; }
.lobby-active #profilePage .pf-col { max-width:820px; padding:28px 30px 80px; }
.lobby-active #leaderPage .lp-body { padding:28px 30px 80px; }
.lobby-active #clanPage .cp-grid { grid-template-columns:205px minmax(180px,1fr) 190px; }
.lobby-active #clanPage .cp-left { padding:22px 12px; }
.lobby-active #clanPage .cp-discover { padding:28px 30px 80px; }
.lobby-active[data-lobby-density="compact"] #lobby .lb-main { padding-top:18px; }
.lobby-active[data-lobby-density="compact"] #lobby .lb-card-img { height:185px; }
@media(min-width:1440px) { :root { --site-side:200px; --site-chat-width:330px; } #lobby .lb-card-img { height:250px; } }
@media(max-width:1100px) { :root { --site-side:160px; --site-chat-width:285px; } .lobby-active #siteSidebar { padding:23px 10px 16px; } #siteHome { font-size:23px; } #lobby .lb-main { padding:22px; } #siteSignedNote { display:none; } .site-joinrow { padding:18px; } #lobby .lb-cta { min-width:110px; padding:12px; } .lobby-active #clanPage .cp-grid { grid-template-columns:180px minmax(140px,1fr) 165px; } }
@media(max-width:759px) {
  :root { --site-side:0px; --site-top:58px; --site-chat-space:0px !important; }
  .lobby-active #siteSidebar { inset:auto 0 0; width:100%; height:64px; padding:5px 8px calc(5px + env(safe-area-inset-bottom)); flex-direction:row; gap:2px; z-index:82; border:0; }
  #siteHome,.site-community,#siteSidebar #acctRank,#siteSidebar #acctName,#siteSidebar #acctAv,#siteLearn { display:none !important; }
  .site-nav { display:contents; }
  .site-nav :is(button,a) { flex:1; padding:7px 4px; width:auto; flex-direction:column; gap:3px; font-size:9px; border:0; }
  .site-nav :is(button,a)[aria-current="page"] { box-shadow:inset 0 -2px #a9c0de; }
  #siteSidebar #acctBar { display:contents; }
  #siteSidebar #acctBar button { flex:1; width:auto; padding:7px 4px; font-size:10px; max-width:76px; background:none; border:0; }
  .lobby-active #siteHeader { left:0; right:0; padding:0 18px; }
  .site-status { display:none; }
  /* …and every page stops where that bar now really starts */
  .lobby-active #lobby,.lobby-active :is(#clanPage,#profilePage,#leaderPage) { left:0; right:0; bottom:calc(64px + env(safe-area-inset-bottom)); }
  #lobby .lb-main { padding:18px 16px 48px; }
  .site-modebar { margin-bottom:17px; }
  .site-player { gap:8px; flex-wrap:wrap; margin-bottom:16px; }
  #lobby .lb-tag { gap:10px; }
  #lobby .lb-namehint { font-size:10px; }
  /* ⚠ THE PHONE HEAD IS NOT HERE. It used to be: `.site-match-head{padding:18px}` and
     `#lobby .lb-card-name{font-size:24px}` sat on these two lines and did NOTHING — lobby-colour-study.css is
     linked after this file and sets both with one more class in the selector, and a media query adds no
     specificity to argue with that. Measured on a 390px viewport, the name computed to the desktop 30px. The
     phone layout now lives in public/styles/lobby-mobile.css, linked LAST, where it can win. */
  #lobby .lb-card-img { height:180px; }
  .site-map-caption { left:18px; bottom:12px; }
  .site-joinrow { padding:16px; gap:10px; }
  #lobby .lb-card-sub { font-size:12px; }
  .site-countdown { font-size:10px; }
  #lobby .lb-cta { min-width:95px; padding:12px 10px; font-size:12px; }
  #lobby .lb-queue { padding:12px 16px; font-size:10px; }
  .site-party-row { flex-wrap:wrap; }
  #sitePartyText { min-width:140px; }
  #sitePartyAction { margin-left:auto; }
  .lobby-active #chatDock { inset:0; width:100vw; height:100dvh; z-index:85; }
  /* ⚠ NO FLOATING CHAT BUTTON ON A PHONE, AND IT IS THE JOIN BUTTONS THAT DECIDED IT. It is position:fixed above
     the bottom bar, which is exactly the band the queue cards scroll through. Swept at 390px in 40px steps it was
     found on top of #mpJoinFfa (scrollTop 240-280), #mpJoinTeams (640-680) and two countdowns — so a tap meant for
     the queue opened chat instead, which is the bug CLAUDE.md §4b rule 5 exists for and the same one the chat DOCK
     already shipped over these buttons once. At this width it is also a DUPLICATE: the fixed header carries Chat on
     every lobby page, and chathub.mjs `badges()` stamps the unread count on #siteChat too, so hiding this hides no
     news. `!important` because chathub.mjs sets `style.display` on it directly and an inline style beats a plain
     rule. Gated by test/lobby-mobile-browser.mjs (the scroll sweep) and test/lobby-hittest-browser.mjs. */
  .lobby-active #chatFab { display:none !important; }
  .lobby-active #profilePage .pf-col,.lobby-active #leaderPage .lp-body,.lobby-active #clanPage .cp-discover { padding:20px 16px 75px; }
  .lobby-active #clanPage .cp-grid { display:flex; flex-direction:column; overflow:auto; }
  .lobby-active #clanPage[data-view="discover"] .cp-grid { display:none; }
  .lobby-active #clanPage .cp-left { overflow:visible; padding:16px; flex:none; border-right:0; }
  .lobby-active #clanPage .cp-channels,.lobby-active #clanPage .cp-funcs { flex-direction:row; flex-wrap:wrap; }
  .lobby-active #clanPage .cp-center { min-height:420px; flex:none; }
  .lobby-active #clanPage .cp-right { display:block; overflow:visible; border:0; padding:16px; }
}
@media(prefers-reduced-motion:reduce) { .lobby-active :is(#siteSidebar,#siteHeader,#lobby,#chatDock,#clanPage,#profilePage,#leaderPage) * { scroll-behavior:auto !important; transition:none !important; animation:none !important; } }

/* Live party belongs to the shared header, leaving every page's own controls reachable. */
#sitePartyMount { min-width:0; margin-left:auto; max-width:235px; }
.lobby-active #sitePartyMount #partyBar { position:static; transform:none; max-width:100%; }
.lobby-active #sitePartyMount .pb-pill { overflow:hidden; max-width:100%; white-space:nowrap; text-overflow:ellipsis; gap:5px; }
.lobby-active #sitePartyMount .pb-pill b { overflow:hidden; text-overflow:ellipsis; }
.lobby-active #siteHeader:has(#partyBar.show) .site-status { display:none; }
.lobby-active #vcPill { width:34px; height:34px; font-size:16px; border-radius:4px; box-shadow:none; background:var(--site-hover); color:var(--site-accent); border-color:var(--site-line); }
.lobby-active #vcPanel { top:43px; bottom:auto; right:0; left:auto; }
.lobby-active #siteHeader .site-status { font-size:9px; }
.lobby-active #profilePage .pf-jumpnav { display:flex; flex-wrap:wrap; gap:18px; padding:15px 0; border-bottom:1px solid var(--site-line); margin:0 0 22px; }
.lobby-active #profilePage .pf-jumpnav button { color:var(--site-accent); font:12px var(--site-font); border:0; background:none; padding:0; }
.lobby-active #profilePage .pf-card { scroll-margin-top:20px; }
.lobby-active #profilePage h2 { text-transform:none; letter-spacing:0; font-size:16px; }
.lobby-active #profilePage .pf-field { text-transform:none; letter-spacing:0; font-size:12px; }
.lobby-active #clanPage .cp-mrow { opacity:1; }
.lobby-active #clanPage .cp-mrow:not(.on) { color:var(--site-muted); }
.lobby-active :is(#profilePage .pf-av,#profilePage .pf-avprev,#clanPage .cp-mav) { background:var(--site-hover) !important; color:var(--site-accent); }
.lobby-active #clanPage :is(.cp-ok,#cpSend) { background:var(--site-action); color:var(--site-on-action); border-radius:4px; }
#notifBody .nt-row { display:flex; gap:10px; padding:13px 16px; border-bottom:1px solid var(--site-line); }
#notifBody .nt-row.nt-nav { cursor:pointer; }
#notifBody .nt-row.nt-unread { background:var(--site-hover); }
#notifBody .nt-txt { display:flex; flex:1; flex-direction:column; gap:5px; }
#notifBody .nt-txt b { font-size:12px; font-weight:600; }
#notifBody .nt-txt i { color:var(--site-muted); font-style:normal; font-size:11px; }
#notifBody .nt-time { padding:0; }
@media(max-width:1200px) { #siteHeader .site-status { display:none; } #sitePartyMount { max-width:185px; } }
@media(max-width:759px) {
  /* ⚠ THE SAFE AREA LIVES HERE, NOT 80 LINES UP. The earlier phone rule sets a bottom padding of
     calc(5px + env(safe-area-inset-bottom)) with ".lobby-active #siteSidebar" (an id and a class); this one is
     "html.lobby-active #siteSidebar" — one element more specific — and it was re-declaring "padding:5px 8px", so
     on every device with a home indicator the inset was thrown away and the nav icons sat under it. In an INSTALLED
     PWA that is the whole bottom strip of the app. The box grows by the inset as well, or the padding would just
     squeeze the icons into a 64px bar. env() is 0 where there is no inset, so nothing moves on a desktop or on a
     phone without one. */
  html.lobby-active #siteSidebar { inset:auto 0 0; width:100%; height:calc(64px + env(safe-area-inset-bottom));
    padding:5px 8px calc(5px + env(safe-area-inset-bottom)); flex-direction:row; gap:2px; }
  #sitePartyMount { max-width:115px; }
  .site-header-actions { gap:10px; }
  .lobby-active #sitePartyMount .pb-pill { font-size:9px; padding:8px 5px; }
}

/* Voice is mounted here only in the lobby; the shell returns it to body before gameplay. */
#siteTitle { flex:none; }
.site-header-actions { flex:none; gap:14px; }
#siteVoiceMount { display:contents; }
.lobby-active #siteVoiceMount #vcDock { position:relative; inset:auto; }
@media(max-width:759px) {
  #sitePartyMount { flex:1; min-width:0; }
  .site-header-actions { gap:10px; }
  .lobby-active #siteHeader { gap:10px; }
  .lobby-active #siteChat { margin:0; }
}


/* ---- the waiting placeholder ------------------------------------------------------------------------------
   It occupies the card's shape while the socket is still answering. Deliberately NOT animated: a shimmer that
   runs for a second on every refresh is more noticeable than the wait it is covering, and this sits in front of
   every player on every load. Flat blocks in the panel colour, and gone as soon as there is anything real. */
#lobby .lb-card-skel { pointer-events:none; }
#lobby .lb-card-skel .skel { display:block; border-radius:3px; background:var(--site-hover); }
#lobby .lb-card-skel .skel-sm { width:78px; height:9px; margin-bottom:9px; }
#lobby .lb-card-skel .skel-lg { width:170px; height:26px; }
#lobby .lb-card-skel .site-match-when .skel { margin-left:auto; }
#lobby .lb-card-skel .site-match-when .skel-lg { width:92px; }
/* the panel tone, not the map tone: --site-map is the deep water colour and reads as a black box rather than as
   an empty space waiting to be filled */
#lobby .lb-card-skel .skel-img { background:var(--site-hover); filter:none; opacity:1; }
#lobby .lb-card-skel .skel-md { width:132px; height:13px; }
#lobby .lb-card-skel .skel-btn { width:132px; height:40px; border-radius:4px; }
#lobby .lb-card-skel .site-joinrow { align-items:center; }

#siteHome .site-wordmark { display:flex; flex-direction:column; align-items:flex-start; gap:3px; }
#siteHome .site-beta { padding:2px 4px; border:1px solid currentColor; border-radius:3px; color:var(--site-gold); font-size:8px; line-height:1.2; font-weight:600; letter-spacing:.5px; }

/* Clan desktop workspace: one header and an expandable navigation rail. */
@media(min-width:1000px) {
  html.lobby-active[data-lobby-page="clanPage"] { --site-side:60px; --site-top:48px; }
  html.lobby-active[data-lobby-page="clanPage"] #siteSidebar { width:60px; padding:14px 6px; gap:18px; z-index:85; overflow-x:hidden; }
  html.lobby-active[data-lobby-page="clanPage"] #siteSidebar:is(:hover,:focus-within) { width:220px; box-shadow:8px 0 24px #0004; }
  html.lobby-active[data-lobby-page="clanPage"] #siteSidebar:not(:hover):not(:focus-within) :is(.site-wordmark,.site-nav span) { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); }
  html.lobby-active[data-lobby-page="clanPage"] #siteSidebar:not(:hover):not(:focus-within) :is(.site-community,#acctRank,#acctName,#acctAv) { display:none; }
  html.lobby-active[data-lobby-page="clanPage"] #siteSidebar .site-nav :is(button,a) { white-space:nowrap; min-height:44px; }
  html.lobby-active[data-lobby-page="clanPage"] #siteSidebar .site-icon { flex-shrink:0; }
  html.lobby-active[data-lobby-page="clanPage"] #siteSidebar #acctBar { padding:10px 0 0; }
  html.lobby-active[data-lobby-page="clanPage"] #siteSidebar #acctBar button { padding:8px 2px; font-size:10px; overflow-wrap:anywhere; }
  html.lobby-active[data-lobby-page="clanPage"] #siteHeader { background:transparent; border:0; pointer-events:none; padding:0 16px; }
  html.lobby-active[data-lobby-page="clanPage"] #siteHeader #siteTitle { visibility:hidden; }
  html.lobby-active[data-lobby-page="clanPage"] #siteHeader :is(#sitePartyMount,.site-header-actions) { pointer-events:auto; }
  html.lobby-active[data-lobby-page="clanPage"] #clanPage[data-view="clan"] { top:0; }
  html.lobby-active[data-lobby-page="clanPage"] #clanPage[data-view="clan"] .cp-workspace-head { height:48px; padding:7px 390px 7px 76px; box-sizing:border-box; }
  html.lobby-active[data-lobby-page="clanPage"] #clanPage[data-view="clan"] .cp-top { position:absolute; left:12px; top:14px; padding:0; }
  html.lobby-active[data-lobby-page="clanPage"] #clanPage[data-view="clan"] .cp-idname { font-size:13px; }
  html.lobby-active[data-lobby-page="clanPage"] #clanPage[data-view="clan"] .cp-idsub { font-size:10px; }
}


/* ==== lobby-colour-study.css ==== */
/* Isolated colour study. The accepted main-project palette is unchanged.
   Geography supplies the large colour field; social and rank details use small, distinct accents. */
html.lobby-active {
  --study-social-bg:#e1e7ee;
  --study-social-line:#becbd9;
  --study-social-ink:#415d7b;
  --study-rank-bg:#eeebe5;
  --study-rank-line:#a58a62;
  --study-mark:#bca784;
}
html.lobby-active[data-lobby-theme="dusk"] {
  --study-social-bg:#343f4c;
  --study-social-line:#526477;
  --study-social-ink:#c0cfdf;
  --study-rank-bg:#403e39;
  --study-rank-line:#a58a62;
}

/* Retain some of the actual map's ocean/land colour instead of painting the entire UI. */
.lobby-active #lobby .lb-card-img { filter:saturate(.42) brightness(.93) contrast(1.02); opacity:.95; }
.lobby-active .site-mark { background:var(--study-mark); }
.lobby-active .site-mode-chip { color:var(--study-social-ink); background:var(--study-social-bg); border-color:var(--study-social-line); }
.lobby-active .site-party-symbol { color:var(--study-social-ink); background:var(--study-social-bg); border-color:var(--study-social-line); }
.lobby-active #partyBar .pb-pill { color:var(--study-social-ink); background:var(--study-social-bg); border-color:var(--study-social-line); }
.lobby-active #partyBar .pb-pill b { color:inherit; }
.lobby-active #clanPage .cp-motd { color:var(--study-social-ink); background:var(--study-social-bg); border-color:var(--study-social-line); }
.lobby-active #profilePage .pf-rank { background:var(--study-rank-bg); border-left-color:var(--study-rank-line); }
.lobby-active #leaderPage :is(.lp-mine,.lp-myplayer,.lp-row.me) { background:var(--study-social-bg); border-color:var(--study-social-line); }
.lobby-active #notifBody .nt-row.nt-unread { background:var(--study-social-bg); }

/* Let the map and the join action establish the hierarchy. */
.lobby-active #lobby .lb-card { border-radius:3px; box-shadow:none; }
.lobby-active .site-overline { letter-spacing:.07em; }
.lobby-active #lobby .lb-card-name { font-size:30px; font-weight:600; letter-spacing:-.6px; }
.lobby-active .site-match-head { padding:18px 22px; }
.lobby-active .site-map-caption { border:0; border-radius:0; left:0; bottom:0; padding:8px 14px; }
.lobby-active .site-party-section { margin-top:20px; padding-top:16px; }
.lobby-active .site-party-section h2 { margin-bottom:10px; }
.lobby-active .site-party-symbol { background:transparent; border:0; padding:0; }
.lobby-active #sitePartyText { line-height:1.5; }
.lobby-active .site-header-actions { gap:18px; }
@media(min-width:760px) and (max-height:800px) {
  .lobby-active #lobby .lb-card-img { height:180px; }
  .lobby-active .site-joinrow { padding-top:15px; padding-bottom:15px; }
}


/* ==== community.css ==== */
/* Desktop navigation roster: its own scroll region protects the persistent account/tools below. */
html.lobby-active #siteCommunity { flex:1 1 0; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding:12px 0 4px; scrollbar-width:thin; scrollbar-color:var(--site-rail-line) transparent; }
#siteCommunity details + details { margin-top:17px; }
#siteCommunity summary { cursor:pointer; padding:5px 7px 8px; color:var(--site-rail-muted); font:600 10px var(--site-font); letter-spacing:.06em; }
#siteCommunity summary span { opacity:.65; margin-left:4px; }
#siteCommunity .community-player { display:flex; gap:8px; align-items:center; width:100%; padding:7px 5px; border:0; border-radius:4px; background:transparent; color:var(--site-rail-ink); text-align:left; font-family:var(--site-font); }
#siteCommunity .community-player:hover { background:var(--site-rail-hover); }
#siteCommunity .community-player:disabled { opacity:.65; cursor:default; }
.community-symbol { display:flex; align-items:center; justify-content:center; width:25px; height:29px; flex:none; }
.community-symbol svg { max-width:25px; max-height:27px; }
.community-crest { height:25px; border:1px solid var(--site-rail-line); border-radius:3px; font-size:8px; color:var(--site-rail-muted); }
.community-crest img { width:100%; height:100%; object-fit:contain; }
.community-identity { min-width:0; flex:1; }
.community-identity b { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; font-weight:600; }
.community-identity small { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:3px; color:var(--site-rail-muted); font-size:10px; letter-spacing:0; }
.community-place { color:var(--site-rail-muted); font-size:10px; font-variant-numeric:tabular-nums; }
#siteCommunity .community-empty { font:11px/1.5 var(--site-font); color:var(--site-rail-muted); margin:6px 7px 12px; }
.community-empty button { color:inherit; background:none; border:0; text-decoration:underline; font:inherit; padding:0; }
html.lobby-active #siteSidebar:has(#siteCommunity) { gap:20px; }
html.lobby-active #siteSidebar:has(#siteCommunity) > :not(#siteCommunity) { flex-shrink:0; }
@media(max-width:759px) { html.lobby-active #siteSidebar:has(#siteCommunity) { gap:2px; } html.lobby-active #siteCommunity { display:none; } }

/* Keep a group label visible while browsing a long ladder. */
#siteCommunity summary { position:sticky; top:0; z-index:1; background:var(--site-rail); }
#siteCommunity .community-player { min-height:40px; padding-top:5px; padding-bottom:5px; }
@media(min-width:760px) and (max-height:800px) {
  html.lobby-active #siteSidebar:has(#siteCommunity) { gap:12px; padding-top:18px; padding-bottom:12px; }
  #siteSidebar .site-nav { gap:2px; }
  #siteSidebar .site-nav button { padding-top:9px; padding-bottom:9px; }
  #siteSidebar #acctBar { padding-top:12px; gap:8px; }
  #siteSidebar #acctRank { margin-bottom:4px; gap:3px 9px; }
  #siteSidebar .acct-rpts { font-size:18px; }
  #siteSidebar .acct-rnext { grid-column:2; }
  #siteCommunity details + details { margin-top:10px; }
}


/* ==== lobby-mobile.css ==== */
/* lobby-mobile.css — THE MULTIPLAYER QUEUE CARD WHEN IT IS NARROW.
   Layout only, render-only: which of the head's facts sits where, and how large. No colour system of its own.

   ⚠ LINKED LAST OF THE LOBBY STYLESHEETS, AND THAT IS WHY THIS IS A FILE RATHER THAN A FEW MORE LINES IN
   lobby-shell.css. Its @media(max-width:759px) block already carried `#lobby .lb-card-name{font-size:24px}` and
   `.site-match-head{padding:18px}` — an id+class and a bare class. lobby-colour-study.css is linked after it and
   sets the same two properties with `.lobby-active #lobby .lb-card-name` and `.lobby-active .site-match-head`, one
   more class each. A MEDIA QUERY ADDS NO SPECIFICITY, so both phone rules lost every cascade they ever entered:
   measured on a 390px viewport, the name still computed to the desktop's 30px. The phone was rendering the desktop
   head — which is exactly what the operator reported, that the countdown, the queue and the two map names do not
   show properly on mobile. Ordering is a contract here: this must stay the LAST lobby <link> in index.html.

   ⚠ AND THE TRIGGER IS THE CARD'S WIDTH, NOT THE WINDOW'S. The head does not care how big the screen is; it cares
   whether ~500px of its own row is available for three columns. A viewport media query answers a different
   question and gets it wrong in both directions: measured, a 390px phone and an 844px landscape phone with the
   chat dock open (the dock is position:fixed and takes 285-330px off the lobby) produce the SAME too-narrow head,
   and only one of them is a small screen. So the card is the container and the query asks the card. Same rule as
   CLAUDE.md §3.6 one level up: what a thing looks like is a function of the space it is actually in.

   The @supports branch at the bottom is for engines without container queries (Safari < 16). They get a plain
   stacked head — not this layout, just a correct one. */

.lobby-active #lobby .lb-card { container-type: inline-size; container-name: lbcard; }

/* 575px of card = ~530px of head row. The widest card needs 525 (162px map name + 190px "next map" + 145px for
   Free-for-all's chip and label + two 14px gaps), so no card is ever left wrapping raggedly, and all three switch
   at the same moment — three queue cards in three different shapes on one screen would read as a fault. */
@container lbcard (max-width: 575px) {
  /* TWO ROWS, NOT THREE COLUMNS SQUEEZED INTO THREE RAGGED ONES. Wrapping is what the desktop head was already
     doing here, and it wrapped wherever the longest map name of the day pushed it: measured at 390px the name went
     left, "NEXT MAP  World" centred under it and "Hybrid  STARTS IN" right with its 1:59 alone below that; at 360px
     it was three rows and 244px of head, which pushed the player count, the Join button AND the queue line off the
     bottom of the screen. The two facts a player standing in the lobby is reading — which map is starting, how long
     they have — go side by side on the first row exactly as they do on a desktop; what comes after this match is one
     quiet line under them. */
  .lobby-active #lobby .site-match-head { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px; padding: 16px 18px; }
  /* ⚠ THE ROW BREAK IS MADE BY A FULL-WIDTH BASIS AND AN `order`, NOT BY HOPING THE WRAP LANDS SOMEWHERE SENSIBLE.
     A 100% basis says WHERE the break is; `order` puts the countdown back beside the map name after it (the markup
     runs now -> up-next -> when, so without it the clock would land on a third line of its own). */
  .lobby-active #lobby .site-match-now { flex: 0 0 auto; min-width: 0; }
  /* ⚠ THE MAP NAME DOES NOT SHRINK AND THE CLOCK DOES, which is the difference between these lines and a card that
     reads "Thronela…". The name is one unbreakable word at 23px (125px on the nose); the countdown column is a
     LABEL over a number and has a second line to give, so it is the one that bends.
     ⚠ AND IT BENDS BY GROWING FROM A SMALL BASIS, NOT BY SHRINKING FROM A LARGE ONE — `flex:0 1 auto` reads as if
     it would, and does not: flex WRAPS a line before it SHRINKS it, comparing each item at its max-content size.
     At 320px that is 125 + 12 + 117 against 250px of room, so the clock jumped to a third row (measured, left
     aligned, under everything) rather than give up the four pixels. A 60px basis always fits beside the name and
     then grows into whatever is left: the full width at 390px, two label lines at 320px, one row at both. */
  .lobby-active #lobby .site-match-when { flex: 1 1 60px; min-width: 0; order: 1; }
  .lobby-active #lobby .site-match-when .site-overline { flex-wrap: wrap; row-gap: 4px; }
  /* NEXT MAP, its thumbnail and its name on ONE line under a hairline: the same three values as the desktop head,
     in the order a narrow card can read them — what is starting, how long, then what follows. */
  .lobby-active #lobby .site-match-upnext {
    flex: 1 0 100%; order: 2; display: flex; align-items: center; gap: 9px; min-width: 0;
    text-align: left; padding: 11px 0 0; border-top: 1px solid var(--site-line);
  }
  .lobby-active #lobby .site-match-upnext .site-overline { margin: 0; flex: none; }
  .lobby-active #lobby .lb-next-row { justify-content: flex-start; gap: 9px; }
  .lobby-active #lobby .lb-next-thumb { width: 44px; height: 29px; }
  /* One size for all three head values, as on the desktop card — the size a 360px screen can hold. */
  .lobby-active #lobby .lb-card-name { font-size: 23px; letter-spacing: -.4px; }
  .lobby-active #lobby .site-overline { font-size: 9.5px; margin-bottom: 5px; }

  /* WHO IS WAITING, legibly. 10px grey is a line a player skims past, and this is the answer to "is anybody in
     there" — so it gets the body size and wraps onto as many lines as the names need (it is the only
     variable-length thing on the card, and clipping it to one line hides everybody after the second name). */
  .lobby-active #lobby .lb-queue { padding: 11px 18px; font-size: 11.5px; line-height: 1.55; }

  /* The plate is the biggest single block on the card and the least load-bearing of the four: shorter here is what
     keeps the countdown, the Join button and the queue line on ONE screen of a phone. */
  .lobby-active #lobby .lb-card-img { height: 148px; }
  .lobby-active #lobby .site-joinrow { padding: 15px 18px; }
}

/* ---- A LANDSCAPE PHONE HAS ~330px OF COLUMN, AND THE CARD HAS TO FIT IN IT -------------------------------------
   Reported as "you cannot see the countdown or how many players are in". MEASURED on the live site at 844x390, and
   the countdown was not the half that was missing: it sat at y=280 and was fine, while **60 players waiting** (y=521),
   the queue names (571) and the **Join match** button (510-556) were all below a 390px fold. The player had to
   scroll a lobby to find out whether anyone was in the match and to press the one button on the page.
   The height is spent before the card starts: the shell's own header is 60px, then a mode bar, a sentence of
   marketing copy and the name field take ~150 more. That paragraph is worth its space on a desktop and is worth
   nothing on a screen where it costs the Join button — so on THIS viewport only (never at a width a crawler
   renders) the preamble collapses and every band of the card gives up a few pixels.
   Measured after: at 844x390 the countdown, the player count and Join are all above the fold. */
@media (max-height: 560px) and (orientation: landscape) {
  /* MEASURED, and this is where the height actually went: the card did not start until y=153 — a 60px shell header,
     then the mode bar, the marketing sentence and the name field. The header is chrome for a page the player is
     looking past; 14px of it buys the top of the Join button. */
  :root { --site-top: 46px; }
  .lobby-active #lobby .lb-main { padding: 10px 16px 24px; }
  .lobby-active #lobby .lb-main > p { display: none; }
  .lobby-active .site-modebar { margin-bottom: 8px; }
  .lobby-active #lobby .site-player { margin-bottom: 8px; }
  .lobby-active #lobby .site-match-head { padding: 8px 16px; gap: 8px; }
  .lobby-active #lobby .lb-card-name { font-size: 20px; }
  .lobby-active #lobby .site-overline { margin-bottom: 3px; }
  .lobby-active #lobby .lb-card-img { height: 70px; }
  /* the up-next line is the secondary fact on the card and the one that can pay for the Join button: the same
     information, at the size a 390px-tall screen can spare for it */
  .lobby-active #lobby .lb-next-thumb { width: 34px; height: 22px; }
  .lobby-active #lobby .lb-card-next { font-size: 14px; }
  .lobby-active #lobby .site-match-upnext { padding-top: 7px; }
  .lobby-active #lobby .site-map-caption { padding: 4px 10px; }
  .lobby-active #lobby .site-joinrow { padding: 8px 16px; }
  .lobby-active #lobby .lb-cta { min-height: 36px; padding: 7px 14px; }
  .lobby-active #lobby .lb-queue { padding: 8px 16px; }
  .lobby-active #lobby .lb-cards { gap: 12px; }
  .lobby-active #lobby .site-party-section { display: none; }   /* a second screen's worth of chrome under the cards */
}

/* ⚠ THE FALLBACK IS NOT THE LAYOUT ABOVE, AND IT IS NOT MEANT TO BE. Engines without container queries (Safari
   below 16) cannot ask the card how wide it is, so they get the one shape that is right at every narrow width
   without measuring anything: each block on its own full-width line, all left-aligned, in markup order. Plainer
   than the two-row head, and the thing it replaces — three columns wrapping into three differently-aligned rows —
   is the defect this file exists to remove. */
@supports not (container-type: inline-size) {
  @media (max-width: 680px) {
    .lobby-active #lobby .site-match-head { display: block; padding: 16px 18px; }
    .lobby-active #lobby .site-match-upnext,
    .lobby-active #lobby .site-match-when { text-align: left; padding: 12px 0 0; }
    .lobby-active #lobby .site-match-when .site-overline,
    .lobby-active #lobby .lb-next-row { justify-content: flex-start; }
    .lobby-active #lobby .lb-card-name { font-size: 23px; letter-spacing: -.4px; }
    .lobby-active #lobby .lb-queue { padding: 11px 18px; font-size: 11.5px; line-height: 1.55; }
    .lobby-active #lobby .lb-card-img { height: 148px; }
  }
}
