  :root{
    --tri:#22d3ee; --circ:#f472b6; --sqr:#ff4444;
    --bg:#000705; --panel:#001210ee; --line:#0e6b5c; --text:#b8fff2; --dim:#5ba89c;
    --neon:#00fff2; --neon-dim:#00fff233; --matrix-green:#39ff14;
  }
  *{box-sizing:border-box; margin:0; padding:0;}
  html,body{width:100%;height:100%;overflow:hidden;background:var(--bg);font-family:'Share Tech Mono','Consolas','Courier New',monospace,'Segoe UI Emoji','Apple Color Emoji','Noto Color Emoji';}
  #game{display:block;background:radial-gradient(ellipse at center,#0a0f1f 0%,#05070f 70%);}

  /* ---------- MENU ---------- */
  #menu{
    position:fixed; inset:0;
    z-index:50;
    overflow-y:auto;
  }
  .infoSection{margin-bottom:20px; padding-bottom:16px; border-bottom:1px solid var(--line);}
  .infoSection h3{margin:0 0 10px 0; font-size:15px;}
  .infoGrid{display:grid; grid-template-columns:1fr 1fr; gap:8px 16px;}
  .infoPreviewRow{display:flex; align-items:center; gap:14px; margin-bottom:10px;}
  .infoPreviewCanvas{background:#05070f; border:1px solid var(--line); border-radius:8px; flex-shrink:0;}
  @media (max-width:640px){ .infoGrid{grid-template-columns:1fr;} }
  /* No fill. This used to be a near-opaque #001210ee panel sitting between the
     whole menu and the arena behind it, which defeated the point of the ring's
     aperture -- you were looking at the match through a green filter. The card
     now only provides layout. Views that are walls of text bring their own
     backing (see #scoreboardView), since those genuinely need it. */
  .menuCard{
    /* Above the terminal. The card creates a stacking context, so the ship
       hologram and its stat readout -- which sit out to the right of the ring,
       exactly where the terminal is -- were trapped underneath it however high
       their own z-index went. Raising the card lifts the whole ring with it,
       and the card paints no background of its own, so nothing is hidden. */
    position:relative; z-index:22; display:flow-root;
    width:min(680px,94vw); background:none; border:0;
    border-radius:18px; padding:0 20px; box-shadow:none;
    margin:40px auto;
  }
  .title{
    font-family:'Orbitron',sans-serif;
    font-size:44px; font-weight:900; letter-spacing:8px; text-align:center;
    background:linear-gradient(90deg,#3d1f7a,#6d3dff,#c0a0ff,#6d3dff,#3d1f7a);
    background-size:300% 100%;
    -webkit-background-clip:text; background-clip:text; color:transparent;
    text-shadow:0 0 24px #6d3dff88;
    animation:titleNebulaShift 6s ease-in-out infinite;
    margin-bottom:4px;
  }
  @keyframes titleNebulaShift{
    0%{background-position:0% 50%;}
    50%{background-position:100% 50%;}
    100%{background-position:0% 50%;}
  }
  .subtitle{text-align:center; color:var(--dim); font-size:13px; letter-spacing:2px; margin-bottom:28px; text-transform:uppercase;}
  .label{color:var(--dim); font-size:12px; letter-spacing:2px; text-transform:uppercase; margin:18px 0 10px;}
  #menuMainView .label{
    font-family:'Orbitron',sans-serif; font-weight:700; font-size:13px; letter-spacing:2.5px;
    color:var(--neon); text-shadow:0 0 6px var(--neon), 0 0 14px #00fff255;
  }
  .row{display:flex; gap:12px;}
  #shapeRow{flex-wrap:wrap;}
  #shapeRow .shapeBtn{flex:1 1 28%; min-width:90px;}
  #shapeDesc{margin-top:10px; padding:10px 12px; background:#00120fcc; border:1px solid var(--line); border-radius:8px; font-size:11.5px; color:var(--dim); line-height:1.6; min-height:16px;}
  #shapeDesc b{color:inherit;}
  .shapeBtn{
    flex:1; padding:18px 8px; background:#00120fcc; border:2px solid var(--line); border-radius:12px;
    color:var(--text); cursor:pointer; text-align:center; transition:.15s; font-size:13px; letter-spacing:1px;
  }
  .shapeBtn:hover{border-color:#3a4670;}
  .shapeBtn.selected{background:#141c33; box-shadow:0 0 0 2px var(--sel), 0 0 22px var(--sel);}
  .shapeBtn svg{display:block; margin:0 auto 8px;}
  .modeBtn{
    flex:1; padding:12px; background:#00120fcc; border:2px solid var(--line); border-radius:4px;
    color:var(--text); cursor:pointer; text-align:center; font-size:13px; letter-spacing:1px; text-transform:uppercase; transition:all 0.15s;
  }
  .modeBtn:hover{border-color:var(--neon); box-shadow:0 0 10px var(--neon-dim);}
  .modeBtn.selected{border-color:var(--neon); background:#013a2f; box-shadow:0 0 18px #00fff255, inset 0 0 12px #00fff222; color:var(--neon);}
  .modeBtn[data-mode="campaign"]:hover{border-color:#ffd166; box-shadow:0 0 10px #ffd16655;}
  .modeBtn[data-mode="campaign"].selected{border-color:#ffd166; background:#3a2f00; box-shadow:0 0 18px #ffd16688, inset 0 0 12px #ffd16633; color:#ffd166; text-shadow:0 0 8px #ffd166;}
  .modeBtn.locked{opacity:0.45; cursor:not-allowed; filter:grayscale(0.6);}
  .modeBtn.locked:hover{border-color:var(--line) !important; box-shadow:none !important;}
  .modeBtn[data-mode="team20"]:hover{border-color:#4dd0ff; box-shadow:0 0 10px #4dd0ff55;}
  .modeBtn[data-mode="team20"].selected{border-color:#4dd0ff; background:#002a3a; box-shadow:0 0 18px #4dd0ff88, inset 0 0 12px #4dd0ff33; color:#4dd0ff; text-shadow:0 0 8px #4dd0ff;}
  .modeBtn[data-mode="practice"]:hover{border-color:#3ddc71; box-shadow:0 0 10px #3ddc7155;}
  .modeBtn[data-mode="practice"].selected{border-color:#3ddc71; background:#003a1f; box-shadow:0 0 18px #3ddc7188, inset 0 0 12px #3ddc7133; color:#3ddc71; text-shadow:0 0 8px #3ddc71;}
  .modeBtn[data-mode="ffa"]:hover{border-color:#ff5470; box-shadow:0 0 10px #ff547055;}
  .modeBtn[data-mode="ffa"].selected{border-color:#ff5470; background:#3a0012; box-shadow:0 0 18px #ff547088, inset 0 0 12px #ff547033; color:#ff5470; text-shadow:0 0 8px #ff5470;}
  .sliderRow{display:flex; align-items:center; gap:14px;}
  .sliderRow input{flex:1; accent-color:#6d7fff;}
  .sliderVal{width:34px; text-align:right; color:var(--text); font-variant-numeric:tabular-nums;}
  .modeToggleBtn{flex:1; padding:12px 16px; border-radius:10px; border:2px solid var(--line); background:#151b32; color:#c9d4f5; font-weight:700; letter-spacing:0.5px; font-size:13px; cursor:pointer; transition:all .15s;}
  .modeToggleBtn.selected{border-color:#4dd0ff; background:linear-gradient(135deg,#1a2550,#20305f); color:#ffffff; box-shadow:0 0 14px #4dd0ff55;}
  #nameBtnRow{margin-top:6px;}
  .nameRow{display:flex; gap:10px; margin-bottom:4px;}
  #playerNameInput{flex:1; background:#00120fcc; border:1px solid var(--line); border-radius:4px; padding:9px 12px; color:var(--text); font-size:13px; font-family:'Share Tech Mono',monospace;}
  #playerNameInput:focus{outline:none; border-color:var(--neon); box-shadow:0 0 8px var(--neon-dim);}

  #scoreboardBtn, #infoMenuBtn, #settingsMenuBtn{padding:9px 16px; background:#00120fcc; border:1px solid var(--line); border-radius:4px; color:var(--text); font-size:12px; font-family:'Share Tech Mono',monospace; letter-spacing:0.5px; cursor:pointer; white-space:nowrap; transition:all 0.15s;}
  #scoreboardBtn:hover, #infoMenuBtn:hover, #settingsMenuBtn:hover{border-color:var(--neon); color:var(--neon); box-shadow:0 0 8px var(--neon-dim);}
  #skinsMenuBtn{width:100%; padding:10px 16px; background:#00120fcc; border:1px solid var(--line); border-radius:4px; color:var(--text); font-size:12.5px; font-family:'Share Tech Mono',monospace; letter-spacing:0.5px; cursor:pointer; transition:all 0.15s;}
  #squadRoomMenuBtn{width:100%; padding:10px 16px; background:#00120fcc; border:1px solid var(--line); border-radius:4px; color:var(--text); font-size:12.5px; font-family:'Share Tech Mono',monospace; letter-spacing:0.5px; cursor:pointer; transition:all 0.15s;}
  #squadRoomMenuBtn:hover{border-color:var(--neon); color:var(--neon); box-shadow:0 0 8px var(--neon-dim);}
  #squadRoomMenuBtn.squadActive{border-color:var(--neon); color:var(--neon); box-shadow:0 0 10px var(--neon), 0 0 22px var(--neon-dim); animation:squadRoomPulse 1.6s ease-in-out infinite;}
  @keyframes squadRoomPulse{0%,100%{box-shadow:0 0 10px var(--neon), 0 0 22px var(--neon-dim);} 50%{box-shadow:0 0 4px var(--neon), 0 0 10px var(--neon-dim);}}
  #skinsMenuBtn:hover{border-color:var(--neon); color:var(--neon); box-shadow:0 0 8px var(--neon-dim);}
  #scoreboardSummary{font-size:12px; color:var(--dim); margin:14px 0; text-align:center; line-height:1.7;}
  .skinShapeTab{padding:8px 16px; background:#00120fcc; border:1px solid var(--line); border-radius:4px; color:var(--text); font-size:12px; letter-spacing:0.5px; cursor:pointer; transition:all 0.15s;}
  .skinShapeTab:hover{border-color:var(--neon);}
  .skinShapeTab.selected{border-color:var(--neon); background:#013a2f; color:var(--neon); box-shadow:0 0 10px var(--neon-dim);}
  .skinCard{background:#00120fcc; border:2px solid var(--line); border-radius:8px; padding:12px 8px; text-align:center; cursor:pointer; transition:all 0.15s;}
  .skinCard:hover{border-color:var(--neon);}
  .skinCard.selected{box-shadow:0 0 14px currentColor;}
  .skinCard svg{width:48px; height:48px;}
  .skinCard .skinName{font-size:11px; color:var(--text); margin-top:6px;}
  .skinCard .skinCheck{font-size:10px; color:var(--neon); margin-top:2px; height:12px;}
  .squadPlayerRow{display:flex; align-items:center; gap:10px; padding:10px 12px; background:#00120f88; border:1px solid var(--line); border-radius:6px; margin-bottom:8px;}
  .squadPlayerRow.isMe{border-color:var(--neon);}
  .squadPlayerName{flex:1; font-size:13px; color:var(--text);}
  .squadPlayerCrown{font-size:16px;}
  .squadVoteBtn{padding:6px 14px; background:#00120fcc; border:1px solid var(--line); border-radius:4px; color:var(--text); font-size:11px; cursor:pointer; transition:all 0.15s;}
  .squadVoteBtn:hover{border-color:var(--neon); color:var(--neon);}
  .squadVoteBtn.voted{border-color:#3ddc71; color:#3ddc71; background:#013a2a;}
  #scoreboardSummary b{color:#6dfcf1;}
  /* ---------- MATCH HISTORY ----------
     One row per match played. Five columns, declared once here and shared by
     the header strip and every row, so the columns line up down the whole list
     instead of each row deciding for itself where its numbers go -- which is
     what a row of flex items with `margin-left:auto` on the kills actually did.

     The row carries its result in three ways at once, because the colour of one
     word is not enough to read from across a room: the chip is filled for a win
     and hollow for a loss, the left edge of the row is a thick lit rule in the
     result's colour, and the whole row's ground shifts with it.

     Behind all of that is srBar, a faint bar as wide as that match's kills
     against the best match in the list. It is the shape you read before you
     read anything -- a good night is a long bar, a bad one is a stub. */
  /* The hull leads the row. It is the fastest way to tell one night's matches
     apart, so it goes ahead of the mode rather than tucked in beside the kills. */
  /* Seven columns. The last two are Replay and Report -- two decisions about the
     same match, so they sit together at the end of the row. The kills column and
     the gap each gave up a few pixels to pay for the second button rather than
     the 1fr date column absorbing the whole cost. */
  #scoreboardHead, .scoreRow{
    display:grid; grid-template-columns:28px 74px 92px 1fr 88px 72px 76px;
    align-items:center; gap:8px;
  }
  /* 15 and 13, not 12 and 12: a row carries a 3px lit left edge and a 1px right
     border inside the same outer width, so a header padded symmetrically ends up
     with a content box four pixels wider than the rows and its 1fr column lands
     somewhere else. The header has to sit on the rows' columns, not near them. */
  #scoreboardHead{
    padding:0 13px 6px 15px; font-size:9.5px; letter-spacing:.14em; color:#3f7a70;
    border-bottom:1px solid #0e6b5c55; margin-bottom:8px;
  }
  #scoreboardHead span:nth-child(5){ text-align:right; }
  #scoreboardList{max-height:300px; overflow-y:auto; display:flex; flex-direction:column; gap:6px; margin-bottom:16px;}
  .scoreRow{
    position:relative; overflow:hidden;
    /* The list is a flex column with a 300px ceiling, so without this every row
       shrinks to share that space instead of the list scrolling: eleven matches
       squeezed each row from 46px down to 20px and clipped the text out of the
       bottom of it. It has been that way for as long as the list has had a
       max-height, and it is why the rows never looked like rows. */
    flex:none;
    background:#00120fcc; border:1px solid var(--line); border-left-width:3px;
    border-radius:8px; padding:9px 12px; font-size:11.5px; color:var(--dim);
    font-variant-numeric:tabular-nums;
  }
  .scoreRow > *{ position:relative; z-index:1; }
  /* The bar has a lit edge at its far end as well as a fill. A fill alone that
     is faint enough to read text over is too faint to find the END of, and the
     end is the part that carries the number -- so the fill says "roughly this
     much" and the edge says exactly where it stops. */
  .mhBar{
    position:absolute; left:0; top:0; bottom:0; z-index:0; display:block;
    border-radius:0 8px 8px 0; pointer-events:none;
  }
  .scoreRow.win{ border-color:#12796d; border-left-color:#6dfcf1; background:#00211ccc; }
  .scoreRow.win .mhBar{ background:linear-gradient(90deg,#6dfcf12b,#6dfcf114); border-right:2px solid #6dfcf159; }
  .scoreRow.loss{ border-color:#43222c; border-left-color:#ff547099; background:#12070bcc; }
  .scoreRow.loss .mhBar{ background:linear-gradient(90deg,#ff54701f,#ff54700f); border-right:2px solid #ff547047; }
  /* Nothing to draw for a match with no kills, not even the edge. */
  .scoreRow .mhBar[style*="width:0%"]{ border-right:none; }
  .scoreRow .mhMode{ color:var(--text); font-weight:700; letter-spacing:.03em; }
  .scoreRow .mhResult{
    justify-self:start; padding:3px 9px; border-radius:999px;
    font-size:9.5px; font-weight:800; letter-spacing:.14em; line-height:1.5;
  }
  .scoreRow.win .mhResult{ background:#6dfcf1; color:#00120f; box-shadow:0 0 12px #6dfcf155; }
  .scoreRow.loss .mhResult{ background:none; border:1px solid #ff547066; color:#ff8fa3; }
  /* The one elastic column, and the one that can be handed an unpredictable
     string: the date is written with toLocaleDateString, so a machine set to a
     long-form locale produces "27 August 2026" where another gives "27/08/2026".
     Without a floor of zero a grid item refuses to go below its own min-content,
     which pushes the whole 1fr track wider than the header's and takes the row
     with it -- the rows and the header then sit on different columns. It
     truncates instead. */
  .scoreRow .mhDate{
    color:#4e8b81; font-size:10.5px;
    min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  }
  .scoreRow .mhKills{ justify-self:end; color:#ffd166; white-space:nowrap; }
  .scoreRow .mhKills b{ font-size:15px; font-weight:800; }
  .scoreRow .mhFb{ color:#39c9ff; margin-left:6px; }
  .scoreRow .mhNoReplay, .scoreRow .mhNoReport{ justify-self:center; color:#2d514b; }
  /* The hull you flew, in the colour you flew it in: the same badge the ship
     picker draws, shrunk onto the row, so the row wears the ship you chose
     rather than a second drawing of it. */
  /* One fixed box whatever is in it, so the column lands on the same pixel in
     every row whether that match recorded a hull or not. */
  .scoreRow .mhShip{
    display:flex; align-items:center; justify-content:center;
    width:28px; height:28px; line-height:0;
  }
  .scoreRow .mhShip svg{ display:block; width:26px; height:26px; }
  /* A match recorded before the ship was kept. A hollow ring rather than an
     empty cell: it holds the column, and it reads as "this one does not say"
     instead of as a row that failed to draw. */
  .scoreRow .mhNoShip::before{
    content:''; width:13px; height:13px; box-sizing:border-box;
    border:1.5px dashed #2d514b; border-radius:50%;
  }
  /* The one row that is not a match. */
  .scoreRow.mhEmpty{ display:block; text-align:center; border-left-width:1px; padding:14px 12px; }

  /* ---- the three buttons on this screen, which are one family ----
     Same shape, same type, same corner: Back is the loud member down at the
     bottom, and Replay and Report are the same object shrunk onto a row. Report
     is the quieter of the pair -- outlined rather than lit -- because a row that
     shouted twice would have no primary action at all, and watching the match
     back is the more likely of the two. */
  #scoreboardActions{ display:grid; grid-template-columns:1fr; gap:10px; }
  #scoreboardActions button, .replayBtn, .reportBtn{
    font-family:inherit; text-transform:uppercase; font-weight:700; letter-spacing:1px;
    background:#001a15; border-radius:4px; cursor:pointer; transition:all .15s;
  }
  #scoreboardActions button{ padding:11px; font-size:12px; }
  #scoreboardBackBtn{
    width:100%; border:2px solid var(--neon); box-shadow:0 0 12px var(--neon-dim); color:var(--neon);
  }
  #scoreboardBackBtn:hover{ background:#00312a; box-shadow:0 0 18px var(--neon-dim); }
  /* Two buttons where there used to be one, on a row that was already full, so
     they are tighter than the family default: less padding and half the letter
     spacing. Both still read as words rather than as icons. */
  .replayBtn, .reportBtn{
    justify-self:stretch; padding:5px 6px; font-size:10px; letter-spacing:.5px;
    white-space:nowrap;
  }
  .replayBtn{ border:1px solid #12796d; color:var(--neon); }
  .replayBtn:hover, .replayBtn:focus-visible{
    background:#00312a; border-color:var(--neon); box-shadow:0 0 10px var(--neon-dim); outline:none;
  }
  .reportBtn{ border:1px solid var(--line); color:var(--text); }
  .reportBtn:hover, .reportBtn:focus-visible{
    border-color:var(--neon); color:var(--neon); box-shadow:0 0 10px var(--neon-dim); outline:none;
  }
  /* A narrow window drops the date rather than squeezing everything. Both
     buttons stay: a report you cannot ask for on a laptop is the problem this
     screen just fixed. */
  @media (max-width:720px){
    #scoreboardHead, .scoreRow{ grid-template-columns:26px 66px 84px 1fr 68px 70px 74px; }
    #scoreboardHead span:nth-child(4), .scoreRow .mhDate{ display:none; }
    .scoreRow .mhShip{ width:26px; height:26px; }
    .scoreRow .mhShip svg{ width:22px; height:22px; }
  }

  #nameShipsBtn, #randomNamesBtn, #countryNamesBtn, #zodiacNamesBtn,
  #rosterBtnRow button{
    flex:1; padding:10px; background:#00120fcc; border:1px solid var(--line); border-radius:10px;
    color:var(--text); font-size:12px; letter-spacing:1px; cursor:pointer;
  }
  #nameShipsBtn:hover, #randomNamesBtn:hover, #countryNamesBtn:hover, #zodiacNamesBtn:hover,
  #rosterBtnRow button:hover{border-color:#3a4670;}
  /* The three presets carry an icon before the label, at the label's own size.
     Drawn SVG through a <use>, like the help mark on the menu, rather than an
     emoji -- an emoji is a colour font and would ignore every colour below.
     The row is four buttons wide and must STAY one row, so the icons buy their
     space back out of the letter-spacing rather than out of the layout, and the
     labels are told not to break: tools/check-roster.js measures the row.
     "Name Ships (optional)" is deliberately left alone -- it was not asked for
     and it is the longest label in the row. */
  #randomNamesBtn, #countryNamesBtn, #zodiacNamesBtn{
    display:flex; align-items:center; justify-content:center;
    gap:0; letter-spacing:0.4px; white-space:nowrap; padding-left:6px; padding-right:6px;
  }
  #randomNamesBtn .btnIco, #countryNamesBtn .btnIco, #zodiacNamesBtn .btnIco{
    margin-right:0.34em;
  }
  .icoDie{   color:#ffd166; filter:drop-shadow(0 0 4px #ffd16655); }  /* the same gold as Deploy */
  .icoGlobe{ color:#5bc0ff; filter:drop-shadow(0 0 4px #5bc0ff55); }  /* an ocean blue */
  .icoStar{  color:#e6b3ff; filter:drop-shadow(0 0 4px #e6b3ff66); }  /* starlight violet */
  #randomNamesBtn:hover .icoDie{   filter:drop-shadow(0 0 8px #ffd166aa); }
  #countryNamesBtn:hover .icoGlobe{ filter:drop-shadow(0 0 8px #5bc0ffaa); }
  #zodiacNamesBtn:hover .icoStar{  filter:drop-shadow(0 0 8px #e6b3ffaa); }
  #nameShipsPanel{margin-top:10px; background:#00120fcc; border:1px solid var(--line); border-radius:10px; padding:12px;}
  #nameShipsList{max-height:220px; overflow-y:auto; margin:8px 0; padding-right:4px;}
  .nameRow{display:flex; align-items:center; gap:8px; margin-bottom:6px;}
  .nameRow span{width:64px; flex-shrink:0; font-size:11px; color:var(--dim);}
  /* The face for this ship, uploaded from its own row. A plain square button
     until there is a picture in it, then the picture, round. Same size as the
     input's height so the row still reads as one line. */
  .nameFace{
    width:30px; height:30px; flex:0 0 auto; padding:0; cursor:pointer;
    border-radius:50%; border:1px dashed var(--line-hot);
    background:#00120f; color:var(--dim); font-size:15px; line-height:1;
    display:flex; align-items:center; justify-content:center; overflow:hidden;
    transition:border-color .14s, color .14s;
  }
  .nameFace:hover{ border-color:var(--neon); color:var(--neon); }
  .nameFace.has{ border-style:solid; border-color:var(--neon); }
  .nameFace img{ width:100%; height:100%; object-fit:cover; display:block; }

  .nameRow input{
    flex:1; background:#05070f; border:1px solid var(--line); border-radius:6px; color:var(--text);
    padding:5px 8px; font-size:12px;
  }
  .nameRow input:focus{outline:none; border-color:#6d7fff;}
  #nameShipsCloseBtn{
    width:100%; padding:9px; background:#001a15; border:2px solid var(--neon); box-shadow:0 0 12px var(--neon-dim); border-radius:4px;
    color:#fff; font-weight:700; letter-spacing:1px; cursor:pointer;
  }

  #startBtn{
    margin-top:28px; width:100%; padding:16px; font-size:16px; font-weight:700; letter-spacing:4px;
    font-family:'Orbitron',sans-serif;
    background:#1a1400; border:2px solid #ffd166; border-radius:4px; color:#ffd166; cursor:pointer;
    text-transform:uppercase; transition:all 0.15s;
  }
  #startBtn:disabled{opacity:.35; cursor:not-allowed; box-shadow:none; border-color:var(--line); color:var(--dim); animation:none;}
  #startBtn:not(:disabled){animation:deployShine 1.6s ease-in-out infinite; text-shadow:0 0 8px #ffd166;}
  #startBtn:not(:disabled):hover{background:#2a2200;}
  @keyframes deployShine{
    0%, 100%{box-shadow:0 0 14px #ffd16666, inset 0 0 12px #ffd16622;}
    50%{box-shadow:0 0 30px #ffd166cc, inset 0 0 22px #ffd16655;}
  }
  .hint{color:var(--dim); font-size:11px; text-align:center; margin-top:14px; line-height:1.6;}
  #controlsPanel{margin-top:18px; display:flex; flex-direction:column; gap:14px;}
  .controlsSection{background:#00120faa; border:1px solid var(--line); border-radius:6px; padding:10px 14px;}
  .controlsSectionTitle{font-family:'Orbitron',sans-serif; font-size:12px; font-weight:700; letter-spacing:1.5px; color:var(--neon); text-shadow:0 0 6px var(--neon); margin-bottom:8px; text-transform:uppercase;}
  .controlsSectionTitle .dim{color:var(--dim); font-size:10px; font-weight:400; text-shadow:none; letter-spacing:0.5px; text-transform:none;}
  .controlsRow{display:flex; align-items:center; gap:8px; margin-bottom:7px; flex-wrap:wrap;}
  .controlsRow:last-child{margin-bottom:0;}
  .controlsDesc{color:var(--text); font-size:11.5px; line-height:1.4;}
  .controlsIcon{font-size:15px; width:22px; text-align:center; flex-shrink:0;}
  .kbKey{
    display:inline-flex; align-items:center; justify-content:center; min-width:22px; height:22px; padding:0 6px;
    background:linear-gradient(180deg,#0e2420,#00120f); border:1px solid var(--neon); border-bottom:2px solid var(--neon);
    border-radius:4px; color:var(--neon); font-family:'Share Tech Mono',monospace; font-size:11px; font-weight:700;
    box-shadow:0 0 6px var(--neon-dim); flex-shrink:0;
  }
  .kbKey.wide{min-width:56px; padding:0 10px;}
  .kbKey.mouse{border-color:#ffd166; color:#ffd166; box-shadow:0 0 6px #ffd16655;}

  /* ---------- HUD ---------- */
  #hud{position:fixed; inset:0; pointer-events:none; z-index:20; display:none; font-variant-numeric:tabular-nums;}
  #musicWidget{
    /* Below the full-screen panels, above the HUD.

       It was 210, which is above every overlay in the game at 200 -- so the
       music widget has been sitting on top of the Settings, Skins, Scoreboard
       and Squad Room panels the whole time, over whatever happened to be in
       that corner. Nothing else in the 205-210 band needs it above them: they
       are full-screen effects and modal waits. Muting from inside Settings
       still works, because that panel has its own copy of these controls. */
    position:fixed; top:54px; right:14px; z-index:195; display:flex; align-items:center; gap:8px;
    background:#00120fcc; border:1px solid var(--line); border-radius:20px; padding:6px 12px 6px 6px;
    transition:border-color 0.15s, box-shadow 0.15s;
  }
  #musicWidget:hover{border-color:var(--neon); box-shadow:0 0 10px var(--neon-dim);}

  /* ---- the first-visit music question, and the arrow that follows it ----

     Both sit above the menu but are only clickable themselves: the card has
     pointer-events and the wrapper around the arrow does not, so a player who
     ignores either one can still reach everything underneath. Top-centre, well
     clear of Deploy. */
  #musicAskCard{
    /* Bottom-centre. Top-centre covered the SHAPEWARS logo, which is the one
       thing a first-time visitor should certainly see; Deploy sits in the middle
       band on both a desktop and a phone, so the foot of the screen is the only
       place that is clear of both. */
    position:fixed; bottom:24px; left:50%; transform:translateX(-50%) translateY(8px);
    z-index:400; display:none; opacity:0; transition:opacity .18s, transform .18s;
    background:#00120fee; border:1px solid var(--line); border-radius:14px;
    padding:12px 16px 13px; text-align:center; max-width:min(92vw, 340px);
    box-shadow:0 6px 26px #000a, 0 0 18px var(--neon-dim);
  }
  #musicAskCard.on{ display:block; opacity:1; transform:translateX(-50%) translateY(0); }
  #musicAskTitle{
    font-size:15px; letter-spacing:2px; color:var(--neon); text-transform:uppercase;
    margin-bottom:3px; text-shadow:0 0 10px var(--neon-dim);
  }
  .musicAskSub{ font-size:11.5px; color:var(--dim); line-height:1.45; margin-bottom:10px; }
  .musicAskRow{ display:flex; gap:8px; justify-content:center; flex-wrap:wrap; }
  /* Both choices are the same weight. Nudging somebody toward the answer that
     costs them 3.4MB would be the wrong kind of design. */
  .musicAskRow button{
    font-size:12.5px; padding:8px 14px; border-radius:9px; cursor:pointer; flex:1 1 auto;
    min-width:120px; min-height:40px;              /* a real target on a phone */
  }

  #musicPointer{
    position:fixed; inset:0; z-index:399; display:none; pointer-events:none;
    opacity:0; transition:opacity .2s;
  }
  #musicPointer.on{ display:block; opacity:1; }
  #musicPointerBubble{
    position:absolute; pointer-events:auto; cursor:pointer;
    background:#00120fee; border:1px solid var(--neon); border-radius:11px;
    padding:8px 12px; font-size:11.5px; color:#dbe4ff; line-height:1.4;
    max-width:min(76vw, 230px); box-shadow:0 0 16px var(--neon-dim);
  }
  #musicPointerArrow{
    position:absolute; width:40px; height:40px; overflow:visible;
    filter:drop-shadow(0 0 6px var(--neon-dim));
  }
  #musicPointerArrow path{ animation:musicPointNudge 1.1s ease-in-out infinite; }
  @keyframes musicPointNudge{
    0%,100%{ transform:translate(0,0); }
    50%{ transform:translate(3px,-3px); }
  }
  @media (prefers-reduced-motion: reduce){ #musicPointerArrow path{ animation:none; } }
  #musicMuteBtn{
    width:28px; height:28px; border-radius:50%; border:1px solid var(--line); background:#00120f;
    color:var(--neon); font-size:14px; cursor:pointer; display:flex; align-items:center; justify-content:center; flex-shrink:0;
  }
  #musicMuteBtn:hover{border-color:var(--neon); box-shadow:0 0 8px var(--neon-dim);}
  #musicMuteBtn.needsClick{animation:musicNeedsClick 1s ease-in-out infinite;}
  @keyframes musicNeedsClick{
    0%, 100%{box-shadow:0 0 4px var(--neon-dim); border-color:var(--line);}
    50%{box-shadow:0 0 14px var(--neon); border-color:var(--neon);}
  }
  #musicVolumeSlider{width:70px; accent-color:var(--neon); cursor:pointer;}
  .barsWrap{width:300px; text-align:center;}
  .barLabel{color:var(--dim); font-size:10px; letter-spacing:2px; margin-bottom:4px;}
  .neonLabel{color:#5ff2ff; font-size:10px; letter-spacing:2px; margin-bottom:4px; text-shadow:0 0 6px #5ff2ffcc, 0 0 12px #5ff2ff66; font-weight:700;}
  /* ---- the shield row is ONE CIRCUIT, not several small bars ----

     One cell per point of shield with a 4px gap between them, and the gaps are
     the whole idea: a lit cell whose neighbour is also lit gets `.linked` and
     draws an arc ACROSS the gap, so the row reads as one conductor under charge.
     The current stops at the last lit cell, which means how far it reaches IS
     how much shield is left -- readable at a glance without counting cells.

     Everything is per-cell and index-driven (--i, and --n on the row), so
     sixteen cells cost sixteen times one cell and never more: no element knows
     about any other, and nothing is measured or laid out per frame.

     Colour is --shieldCol, set per frame from the ship being shown, so the bar
     matches the ship it belongs to instead of being green for everyone. */
  .shieldRow{display:flex; gap:4px; margin-bottom:14px;}
  .shieldCell{position:relative; flex:1; height:16px; background:#00120fcc; border:1px solid var(--line); border-radius:3px;}
  .shieldCell.on{
    background-color:var(--shieldCol, #2ecc71);
    /* A conductor rather than a block: shaded top and bottom with a white-hot
       core band down the middle, at exactly the height the arcs jump at. */
    background-image:linear-gradient(180deg,
      #ffffff00 0%, #ffffff22 26%, var(--cellHot, #fffdf2) 42%, var(--cellHot, #fffdf2) 58%,
      #00000022 74%, #00000055 100%);
    box-shadow:0 0 7px var(--shieldCol, #2ecc71);
  }
  .shieldCell.on.mothership{ --shieldCol:#d4a017; --cellHot:#fff6d0; }
  /* The spark running along the row. Same animation on every cell, offset by
     its index, so what you see is one bright point travelling the whole bar
     rather than every cell flashing on its own. */
  .shieldCell.on::before{
    content:''; position:absolute; inset:0; border-radius:2px; pointer-events:none;
    background:radial-gradient(ellipse 60% 170% at 50% 50%, #ffffffee 0%, #ffffff77 40%, #ffffff00 74%);
    opacity:.07;
    animation:shieldRun 1.15s linear infinite;
    animation-delay:calc((var(--n, 1) - 1 - var(--i, 0)) * -0.062s);
  }
  @keyframes shieldRun{
    0%, 66%{opacity:.06;} 78%{opacity:.7;} 88%{opacity:.22;} 100%{opacity:.06;}
  }
  /* The arc over the gap. Masked rather than drawn, so the one shape wears
     --shieldCol and the gold mothership variant needs no artwork of its own. */
  .shieldCell.on.linked::after{
    content:''; position:absolute; z-index:2; pointer-events:none;
    left:100%; width:6px; top:0; bottom:0;
    background:linear-gradient(90deg, var(--shieldCol, #2ecc71) 0%, var(--cellHot, #fffdf2) 50%, var(--shieldCol, #2ecc71) 100%);
    -webkit-mask-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='6' height='16' viewBox='0 0 6 16' preserveAspectRatio='none'><path d='M0 8 L1.3 4.4 L2.4 10.6 L3.6 5.4 L4.7 9.6 L6 8' fill='none' stroke='white' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/></svg>");
            mask-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='6' height='16' viewBox='0 0 6 16' preserveAspectRatio='none'><path d='M0 8 L1.3 4.4 L2.4 10.6 L3.6 5.4 L4.7 9.6 L6 8' fill='none' stroke='white' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/></svg>");
    -webkit-mask-size:100% 100%; mask-size:100% 100%;
    -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
    animation:shieldArc 0.74s steps(1, end) infinite;
    animation-delay:calc((var(--n, 1) - 1 - var(--i, 0)) * -0.062s);
  }
  @keyframes shieldArc{
    0%{opacity:.95; transform:scaleY(1);}
    18%{opacity:.34; transform:scaleY(.55);}
    34%{opacity:1;   transform:scaleY(1.18);}
    52%{opacity:.52; transform:scaleY(.8);}
    70%{opacity:.9;  transform:scaleY(1.05);}
    86%{opacity:.26; transform:scaleY(.6);}
    100%{opacity:.95; transform:scaleY(1);}
  }
  /* A hit spikes the whole circuit for a moment and then it settles: the arcs
     get fatter, taller and faster, and the spark stops being a passing glow. */
  .shieldRow.hit .shieldCell.on{ box-shadow:0 0 16px var(--shieldCol, #2ecc71); }
  .shieldRow.hit .shieldCell.on::before{
    animation-name:shieldRunHit; animation-duration:0.22s;
  }
  .shieldRow.hit .shieldCell.on.linked::after{
    animation-name:shieldArcHit; animation-duration:0.17s;
  }
  @keyframes shieldRunHit{
    0%{opacity:.86;} 26%{opacity:.2;} 52%{opacity:1;} 74%{opacity:.3;} 100%{opacity:.86;}
  }
  @keyframes shieldArcHit{
    0%{opacity:1;   transform:scale(1.3, 1.55);}
    24%{opacity:.55; transform:scale(1.05, .8);}
    48%{opacity:1;   transform:scale(1.45, 1.8);}
    72%{opacity:.4;  transform:scale(1.1, 1);}
    100%{opacity:1;  transform:scale(1.3, 1.55);}
  }
  /* Regenerating runs the current the other way: back INTO the row from the
     far end rather than out along it. */
  .shieldRow.regen .shieldCell.on::before,
  .shieldRow.regen .shieldCell.on.linked::after{ animation-direction:reverse; }
  /* Below a quarter it is a FAILING circuit, not a red one: cells drop out at
     uneven intervals, taking their arcs with them, and the arcs themselves get
     shorter and rougher. */
  .shieldRow.crit .shieldCell.on{
    animation:shieldSputter 0.53s steps(1, end) infinite;
    animation-delay:calc(var(--i, 0) * -0.13s);
  }
  .shieldRow.crit .shieldCell.on.linked::after{ animation-duration:0.4s; }
  @keyframes shieldSputter{
    0%{opacity:1;}   9%{opacity:.16;} 14%{opacity:1;}  41%{opacity:1;}
    46%{opacity:.1;} 52%{opacity:.8;} 58%{opacity:.18;} 63%{opacity:1;}
    88%{opacity:1;}  92%{opacity:.22;} 100%{opacity:1;}
  }
  @media (prefers-reduced-motion: reduce){
    /* The circuit keeps its arcs and its lit core -- it stops crackling. */
    .shieldCell.on::before, .shieldCell.on.linked::after,
    .shieldRow.hit .shieldCell.on::before, .shieldRow.hit .shieldCell.on.linked::after,
    .shieldRow.crit .shieldCell.on{ animation:none; }
  }
  /* ---- the status row ----

     One chip per thing that is currently happening TO the ship on screen, sat
     directly above the shield bar and lined up with its left edge.

     The wrapper is zero-height and the row is absolutely positioned inside it,
     which is the whole trick: the bottom HUD is anchored to the bottom of the
     screen, so anything that took real height here would shove the shield bar
     down the moment somebody set you on fire, and would leave a band of empty
     black under the ammo dial the rest of the time. This takes no space at all,
     ever. The chips are opaque and sit above the dial, so a full row of five
     overlaps the bottom of it rather than moving anything. */
  #statusRow{position:relative; height:0;}
  #statusRowInner{
    position:absolute; left:0; bottom:5px; z-index:3;
    display:flex; gap:5px; align-items:flex-end; pointer-events:none;
  }
  .fxChip{
    position:relative; width:34px; height:34px; flex:0 0 auto;
    border-radius:9px; background:#00120fee;
    box-shadow:0 0 10px -3px var(--fxCol, #6dfcf1);
  }
  .fxChip .fxArt{position:absolute; inset:0; width:34px; height:34px; display:block;}
  .fxChip .fxNum{
    position:absolute; left:0; right:0; bottom:2.5px; text-align:center;
    font-family:'Orbitron',sans-serif; font-size:9.5px; font-weight:800;
    line-height:1; color:var(--fxCol, #dbe4ff); font-variant-numeric:tabular-nums;
    text-shadow:0 0 5px #000705, 0 1px 2px #000705;
  }
  /* A count is not a countdown, so it does not get to look like one. The stack
     chip's border is three separate pieces that light up rather than one that
     drains, and its number is neutral rather than ice-coloured -- it is a
     warning about what happens on the third hit, not something running out. */
  .fxChip.fxCount .fxNum{color:#dbe4ff; font-size:9px;}
  .fxChip.fxWarn{animation:fxWarnPulse 0.9s ease-in-out infinite;}
  @keyframes fxWarnPulse{
    0%,100%{box-shadow:0 0 10px -3px var(--fxCol, #6dfcf1);}
    50%{box-shadow:0 0 17px 0 var(--fxCol, #6dfcf1);}
  }
  @media (prefers-reduced-motion:reduce){ .fxChip.fxWarn{animation:none;} }
  /* If everything in the game lands at once, the row says so rather than
     growing across the ammo dial. */
  .fxMore{
    align-self:center; padding-bottom:9px; flex:0 0 auto;
    font-family:'Orbitron',sans-serif; font-size:10px; font-weight:800;
    color:var(--dim); letter-spacing:0.5px;
  }
  .meterOuter{height:12px; background:#00120fcc; border:1px solid var(--line); border-radius:6px; overflow:hidden; margin-bottom:14px;}
  .meterInner{height:100%; width:100%; transition:width .08s linear;}
  #ammoMeter{background:linear-gradient(90deg,#ff6b6b,#e63946);}
  /* ---- the boost bar is a TANK WITH FUEL IN IT ----

     The body of the fuel is this element's own background and it stops 5px
     short of the level; the last 5px are the SURFACE, a wave standing where the
     fuel meets the empty part of the tank instead of a straight vertical cut.
     That one detail is most of what makes a bar read as liquid rather than as a
     fill, so it is the thing that gets the pixels.

     Nothing here animates on its own. updateBoostFuel() writes --slosh (how
     hard the fuel is moving), --wavePh (how far the wave has travelled up the
     surface), --bubblePh and --surge every frame from the ship on screen, which
     is what makes the movement MEAN something: still at rest, sloshing while
     boost is being spent, disturbed the other way while the tank refills. The
     phase is written rather than keyframed so the speed can change without the
     pattern jumping. Everything the browser does with it is a transform.

     The four --*Use variables below exist so the reduced-motion block can
     override them in one place: the wave keeps its shape and the bar still
     looks like fuel, it just stops moving. */
  #boostMeter{
    position:relative; overflow:visible;
    --sloshUse:var(--slosh, 0);
    --wavePhUse:var(--wavePh, 0px);
    --bubblePhUse:var(--bubblePh, 0px);
    --surgeUse:var(--surge, 0px);
    background-color:transparent;
    background-image:linear-gradient(90deg,#ffe066,#ffb703);
    background-repeat:no-repeat;
    background-position:left center;
    background-size:calc(100% - 5px) 100%;
  }
  /* Sparse motes drifting toward the surface. Seasoning, not the dish -- they
     stop where the body of the fuel stops. */
  #boostMeter .fuelBubbles{
    position:absolute; left:0; right:5px; top:0; bottom:0;
    overflow:hidden; pointer-events:none; opacity:.55;
  }
  #boostMeter .fuelBubbleRun{
    position:absolute; top:0; bottom:0; left:-96px; width:calc(100% + 192px);
    background-image:
      radial-gradient(circle 1.1px at 13px 72%, #fffbe0cc, #fffbe000),
      radial-gradient(circle 0.9px at 44px 34%, #fffbe0aa, #fffbe000),
      radial-gradient(circle 1.3px at 72px 56%, #fffbe0bb, #fffbe000);
    background-size:96px 100%;
    background-repeat:repeat-x;
    transform:translateX(var(--bubblePhUse, 0px));
  }
  /* The surface. The strip is 24px wide and sits so that the wave's mean line
     lands exactly on the level; scaleX about that line is what makes the wave
     flatten to a near-still ripple at rest and stand up when the fuel moves. */
  #boostMeter .fuelSurface{
    position:absolute; top:0; bottom:0; right:-6px; width:24px;
    overflow:hidden; pointer-events:none;
    opacity:var(--surfOp, 1);
    transform:translateX(var(--surgeUse, 0px));
  }
  #boostMeter .fuelWave{
    position:absolute; left:0; width:24px; top:-14px; bottom:-14px; display:block;
    background-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='9' viewBox='0 0 24 9'><path d='M0 0 L18.8 0 L19.5 0.5 L19.9 1 L19.8 1.5 L19.5 2 L19.2 2.5 L19.1 3 L19.1 3.5 L19.1 4 L18.8 4.5 L18.1 5 L17.2 5.5 L16.1 6 L15.4 6.5 L15.1 7 L15.5 7.5 L16.4 8 L17.6 8.5 L18.8 9 L0 9 Z' fill='%23ffb703'/><path d='M18.8 0 L19.5 0.5 L19.9 1 L19.8 1.5 L19.5 2 L19.2 2.5 L19.1 3 L19.1 3.5 L19.1 4 L18.8 4.5 L18.1 5 L17.2 5.5 L16.1 6 L15.4 6.5 L15.1 7 L15.5 7.5 L16.4 8 L17.6 8.5 L18.8 9' fill='none' stroke='%23fff4c2' stroke-width='1' stroke-linejoin='round' stroke-opacity='.75'/></svg>");
    background-repeat:repeat-y;
    background-size:24px 9px;
    background-position:left top;
    transform-origin:18px 50%;
    transform:translateY(var(--wavePhUse, 0px)) scaleX(calc(0.30 + var(--sloshUse, 0) * 0.70));
    will-change:transform;
  }
  @media (prefers-reduced-motion: reduce){
    /* The fuel keeps its surface -- held at a shape you can see -- and stops
       sloshing, drifting and surging. */
    #boostMeter{ --sloshUse:.55; --wavePhUse:0px; --bubblePhUse:0px; --surgeUse:0px; }
  }
  #ammoCircleWrap{position:relative; width:90px; height:90px; margin:0 auto 14px;}
  #ammoDotsSvg{position:absolute; inset:0; width:100%; height:100%;}
  #ammoNumberText{position:absolute; inset:0; display:flex; align-items:center; justify-content:center; color:var(--text); font-size:22px; font-weight:800; font-family:'Orbitron',sans-serif; text-align:center; line-height:1.05;}
  #ammoNumberText.reloadingText{font-size:11px; letter-spacing:1px;}
  .ammoDot{transition:opacity 0.15s;}

  #ffaStatusBar{display:none; background:#0b1020cc; border:1px solid var(--line); border-radius:10px; padding:7px 18px; font-size:12px; letter-spacing:0.5px; color:var(--text); white-space:nowrap;}
  #ffaStatusBar b{color:#ffd166;}
  #teamStatus{display:flex; gap:22px;}
  .teamPill{
    background:#0b1020cc; border:1px solid var(--line); border-radius:10px; padding:6px 14px;
    display:flex; align-items:center; gap:8px; font-size:13px; color:var(--text); pointer-events:auto; transition:border-color 0.15s;
  }
  .teamPill:hover{border-color:var(--neon);}
  .teamDot{width:10px;height:10px;border-radius:3px;}

  #scoreDisplay{background:#0b1020cc; border:1px solid var(--line); border-radius:10px; padding:6px 16px; font-size:13px; color:#fff; letter-spacing:1px; white-space:nowrap;}
  #ringTimerDisplay{background:#0b1020cc; border:1px solid var(--line); border-radius:10px; padding:5px 14px; font-size:12px; color:var(--dim); letter-spacing:1px; white-space:nowrap; transition:font-size .2s, padding .2s;}
  #spectateNameDisplay{display:none; font-size:22px; font-weight:800; color:#3ddc71; letter-spacing:2px; text-shadow:0 0 16px #3ddc7188; white-space:nowrap; pointer-events:none;}
  #spectateSubDisplay{display:none; font-size:12px; font-weight:700; color:#ffd166; letter-spacing:2px; text-shadow:0 0 10px #ffd16688; white-space:nowrap; pointer-events:none;}
  #ringTimerDisplay.urgent{font-size:19px; font-weight:800; color:#ff4d4d; padding:8px 18px; border-color:#ff4d4d; text-shadow:0 0 14px #ff4d4d99; background:#1a0b12ee;}

  #threatWarnings{display:flex; flex-direction:column; gap:6px; align-items:center; z-index:22;}
  .threatWarning{display:none; background:#2a0a0ae0; border:1px solid #ff4d4d; color:#ff8080; padding:6px 18px; border-radius:8px; font-weight:800; letter-spacing:1px; font-size:13px; text-shadow:0 0 8px #ff4d4d99; white-space:nowrap; animation:pulseWarn 0.6s infinite alternate;}
  #catLaserWarning{display:none; flex-direction:column; align-items:center; gap:4px;}
  #catLaserBarWrap{width:180px; height:8px; background:#1a0a0a; border:1px solid #ff4d4d; border-radius:4px; overflow:hidden;}
  #catLaserBarInner{height:100%; width:0%; background:linear-gradient(90deg,#ff4d4d,#ff2d55); transition:width .05s linear;}
  #whaleChargeBarWrap{width:180px; height:8px; background:#150a2a; border:1px solid #8a4dff; border-radius:4px; overflow:hidden;}
  #whaleChargeBarInner{height:100%; width:0%; background:linear-gradient(90deg,#8a4dff,#4dd0ff); transition:width .05s linear;}
  @keyframes pulseWarn{ from{opacity:0.72; transform:scale(1);} to{opacity:1; transform:scale(1.03);} }
  #ringTimerDisplay span{color:inherit; font-weight:700;}
  #scoreDisplay span{color:#6dfcf1; font-weight:700;}

  #toastStack{display:flex; flex-direction:column; align-items:center; gap:6px; z-index:25;}
  /* ---------- the top middle, as one column ----------
     Everything across the top of the screen is a child of this, so two things
     being up at once can no longer land on each other and the spacing between
     any two of them is the same number. A hidden child takes no space, so the
     stack closes up rather than leaving a hole where it would have been. */
  #topCentre{
    position:absolute; top:14px; left:50%; transform:translateX(-50%);
    display:flex; flex-direction:column; align-items:center; gap:9px;
    z-index:26; pointer-events:none; max-width:min(92vw, 760px);
  }
  #topCentre > *{ pointer-events:none; }
  /* A child with nothing in it still claimed a gap, which put phantom spacing
     between the two things that WERE up. An empty one stands down entirely. */
  #topCentre > *:empty{ display:none; }
  /* These two are wrappers around things that hide themselves individually, so
     they are never `:empty` however little they are showing. `display:contents`
     removes the wrapper from the layout and lets its children be rows of the
     column directly -- which also means a warning and a toast are spaced by the
     same number as everything else rather than by their own. */
  #topCentre > #threatWarnings, #topCentre > #toastStack{ display:contents; }
  #topCentre .teamPill, #topCentre button{ pointer-events:auto; }
  /* A killcam owns the strip while it is up, rather than sharing it. */
  body.killcamOn #topCentre{ display:none; }
  #killcamTop{
    position:absolute; top:14px; left:50%; transform:translateX(-50%);
    display:flex; flex-direction:column; align-items:center; gap:9px;
  }

  /* ---------- stage results ---------- */
  .stageMine{
    border:1px solid #1d5a3e; background:linear-gradient(180deg,#0a2318,#061410);
    border-radius:9px; padding:14px; margin:0 0 12px; text-align:center;
  }
  .stagePlace{
    font-family:'Orbitron',sans-serif; font-weight:900; font-size:34px;
    color:#3ddc71; line-height:1; text-shadow:0 0 18px #3ddc7155;
  }
  .stageOf{ font-size:10px; letter-spacing:.16em; text-transform:uppercase; color:var(--dim); margin-top:5px; }
  .stageRow{
    display:flex; justify-content:space-between; font-size:11.5px;
    margin-top:9px; padding-top:8px; border-top:1px solid #123a2a; color:var(--text);
  }
  .stageRow span:last-child{ color:#22d3ee; font-variant-numeric:tabular-nums; }
  .stageRest{
    font-size:9.5px; letter-spacing:.18em; text-transform:uppercase;
    color:var(--dim); margin:0 0 6px;
  }
  .stageBoard{ width:100%; border-collapse:collapse; font-variant-numeric:tabular-nums; }
  .stageBoard th{
    text-align:left; font-size:9px; letter-spacing:.16em; text-transform:uppercase;
    color:var(--dim); font-weight:400; padding:0 6px 5px; border-bottom:1px solid var(--line);
  }
  .stageBoard th.r, .stageBoard td.r{ text-align:right; }
  .stageBoard td{ padding:4px 6px; font-size:11.5px; border-bottom:1px solid #0d1720; color:#ffffff; }
  .stageBoard tr:last-child td{ border-bottom:none; }
  .stageBoard .rk{ color:var(--dim); width:26px; }
  .stageBoard .nm{ max-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .stageBoard .sq{ color:var(--dim); width:38px; }
  .stageBoard .sc{ color:#22d3ee; width:52px; }
  .stageBoard tr.me td{ background:#08201a; }
  .stageBoard tr.me .nm{ color:#3ddc71; }
  .stageBoard tr.ms .nm{ color:#ffd166; }

  /* ---------- the two readouts, side by side ---------- */
  #radarRow{ display:flex; gap:8px; align-items:flex-end; }
  #scopeCol, #mapCol{ display:flex; flex-direction:column; }
  #proxScope{ display:block; border-radius:6px; }

  /* ---------- editing what the squad answers to ---------- */
  .vtRow{
    display:grid; grid-template-columns:150px 1fr auto; gap:10px; align-items:center;
    padding:5px 0; border-bottom:1px solid #0f2a24;
  }
  .vtRow:last-child{ border-bottom:none; }
  .vtRow label{ font-size:12px; color:#8fd3dd; letter-spacing:.02em; }
  .vtRow input{
    background:#00160f; border:1px solid var(--line); border-radius:5px;
    padding:6px 9px; color:#d8fff2; font-family:'Share Tech Mono',monospace; font-size:12px;
    /* Chrome paints an autofilled field almost white, which on a panel this
       dark reads as a broken input rather than as a filled one. */
    -webkit-box-shadow:0 0 0 1000px #00160f inset;
    -webkit-text-fill-color:#d8fff2;
  }
  .vtRow input:focus{ outline:none; border-color:var(--neon); box-shadow:0 0 0 1000px #00160f inset, 0 0 10px -3px var(--neon); }
  .vtRow button{
    padding:5px 11px; font-size:11px; letter-spacing:.06em;
    background:#04140a; color:var(--neon); border:1px solid #1c4a2a; border-radius:4px; cursor:pointer;
  }
  .vtRow button[disabled]{ opacity:.32; cursor:default; }
  .vtRow button:not([disabled]):hover{ background:#07240f; border-color:var(--neon); }
  @media (max-width:640px){ .vtRow{ grid-template-columns:1fr; gap:4px; } }

  /* ---------- the order wheel ----------
     Held open by a key rather than clicked, so it is centred on the screen
     rather than on the cursor: the cursor is the pointer, and a menu that
     appears under its own pointer has nowhere to point from. */
  #orderWheel{
    position:fixed; inset:0; z-index:70; pointer-events:none;
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    opacity:0; transition:opacity .12s ease-out;
  }
  #orderWheel.on{ opacity:1; }
  /* Full screen, behind the wheel, so a heading can run to any corner. */
  #orderWheelDrag{
    position:absolute; inset:0; width:100%; height:100%;
    pointer-events:none; overflow:visible;
  }
  #orderWheelSvg{
    position:relative;
    width:min(46vmin, 420px); height:min(46vmin, 420px);
    font-family:'Share Tech Mono', monospace; font-weight:700;
    filter:drop-shadow(0 0 24px #00000099);
    transform:scale(.94); transition:transform .14s cubic-bezier(.2,.9,.3,1);
  }
  #orderWheel.on #orderWheelSvg{ transform:scale(1); }
  #orderWheelHint{
    margin-top:14px; font-size:11px; letter-spacing:.14em; text-transform:uppercase;
    color:#8fd3dd; text-shadow:0 0 10px #2c627399; background:#040a10cc;
    border:1px solid #1e3f4a; border-radius:4px; padding:5px 13px;
  }

  /* ---------- what 1-8 do ---------- */
  /* One line of it, until you want the rest.

     The full list was nine rows tall and it pushed the radar down until the map
     was barely on screen -- and a reference you have already learned is a
     reference you do not want occupying the corner of a match. So the list
     lives ABOVE the strip, out of the layout, and only appears when the pointer
     is on it. The strip itself costs one line. */
  #orderGuide{
    position:relative; margin-bottom:6px; width:190px;
    background:#00120fcc; border:1px solid var(--line); border-radius:6px;
    padding:5px 9px; pointer-events:auto;
  }
  .ogTitle{
    font-size:9px; letter-spacing:.18em; text-transform:uppercase;
    color:var(--neon); opacity:.85; text-shadow:0 0 8px currentColor;
    display:flex; justify-content:space-between; align-items:center; gap:8px;
  }
  .ogTitle .ogHint{ color:var(--dimmer); letter-spacing:.06em; text-transform:none; opacity:.9; }
  .ogTitle .ogHint kbd{
    display:inline-block; padding:0 3px; border:1px solid var(--line);
    border-radius:3px; background:#0d1622; color:var(--neon);
    font-family:inherit; font-size:9px;
  }
  #ogList{
    list-style:none; margin:0; padding:9px 10px; opacity:0; pointer-events:none;
    position:absolute; left:0; bottom:calc(100% + 5px); width:190px;
    background:#00120ff2; border:1px solid var(--line-hot); border-radius:6px;
    display:grid; grid-template-columns:1fr 1fr; gap:2px 8px;
    transition:opacity .14s ease-out; z-index:3;
  }
  #orderGuide:hover #ogList, #orderGuide:focus-within #ogList{ opacity:1; }
  #ogList li{ font-size:9.5px; color:var(--dim); letter-spacing:.02em; white-space:nowrap; }
  #ogList li b{
    display:inline-block; min-width:11px; color:var(--neon); font-weight:400; opacity:.85;
  }
  .ogMore{ display:none; }
  @media (max-width:900px){ #orderGuide{ display:none; } }

  /* Small captions over the panels that had none. Same treatment as the
     LOADOUT heading so they read as part of one interface. */
  .hudLabel{
    font-size:10px; letter-spacing:.22em; text-transform:uppercase;
    color:var(--neon); opacity:.75; margin:0 0 5px 2px;
    text-shadow:0 0 8px currentColor;
  }

  #shieldFlashOverlay{position:fixed; inset:0; z-index:60; pointer-events:none; opacity:0; transition:opacity .12s ease-out;}
  #asteroidFindBanner{display:none; background:#0b1020cc; border:1px solid #3ddc71; color:#3ddc71; text-shadow:0 0 10px #3ddc71cc; padding:4px 12px; border-radius:6px; font-size:10px; letter-spacing:0.5px; white-space:nowrap; z-index:25; opacity:0; transition:opacity .3s;}
  #asteroidFindBanner.show{opacity:1;}

  #campaignCinematic{position:fixed; inset:0; z-index:200; background:#05030f; display:flex; flex-direction:column; align-items:center; justify-content:center; cursor:pointer; overflow:hidden;}
  #cineVisual{position:absolute; inset:0; z-index:1; transition:opacity 0.6s ease-in-out;}
  #cineVisual .cineStar{position:absolute; width:2px; height:2px; background:#fff; border-radius:50%; opacity:0.7;}
  #cineVisual .cinePlanet{position:absolute; border-radius:50%; box-shadow:0 0 90px 30px currentColor inset, 0 0 140px 20px currentColor;}
  #cineVisual .cineTriGlyph{position:absolute; width:0; height:0; filter:drop-shadow(0 0 18px currentColor);}
  #cineVisual .cineFlashPurple{position:absolute; inset:0; background:radial-gradient(circle at 50% 45%, #b28dff88 0%, #6d3dff33 35%, transparent 70%);}
  #cineTextWrap{position:relative; z-index:5; max-width:720px; padding:0 40px; text-align:center; animation:cineTextIn 0.7s ease-out;}
  @keyframes cineTextIn{from{opacity:0; transform:translateY(14px);} to{opacity:1; transform:translateY(0);}}
  #cineTitle{font-size:26px; font-weight:800; letter-spacing:2px; color:#c0a0ff; text-shadow:0 0 16px #8a4dffcc; margin-bottom:18px;}
  #cineBody{font-size:16px; line-height:1.7; color:#e8e4ff; text-shadow:0 0 10px #00000099;}
  #cineContinueBtn{position:fixed; left:50%; bottom:64px; transform:translateX(-50%); z-index:5; padding:12px 30px; background:#141033cc; border:1px solid #8a4dff; color:#e8e4ff; border-radius:8px; font-size:14px; font-weight:700; letter-spacing:1px; cursor:pointer; box-shadow:0 0 18px #8a4dff55;}
  #cineContinueBtn:hover{background:#241a5ccc;}
  #cineSkipHint{position:fixed; left:50%; bottom:38px; transform:translateX(-50%); z-index:5; font-size:11px; color:#8a80b0;}

  #campaignChaseUI{position:fixed; inset:0; z-index:55; pointer-events:none;}
  #chaseCaption{position:absolute; top:70px; left:50%; transform:translateX(-50%); max-width:560px; text-align:center; background:#0b1020cc; border:1px solid #6d7fff; color:#e8e4ff; padding:8px 18px; border-radius:8px; font-size:14px; opacity:0; transition:opacity .3s;}
  #chaseCaption.show{opacity:1;}
  #chaseTutorialPrompt{position:absolute; bottom:120px; left:50%; transform:translateX(-50%); max-width:480px; text-align:center; background:#0b1020cc; border:1px solid #3ddc71; color:#3ddc71; text-shadow:0 0 8px #3ddc71aa; padding:8px 18px; border-radius:8px; font-size:14px; opacity:0; transition:opacity .3s;}
  #chaseTutorialPrompt.show{opacity:1;}
  #chaseWhaleWarning{position:absolute; top:110px; left:50%; transform:translateX(-50%); background:#1a0d0dcc; border:1px solid #ff4d6d; color:#ff8fa3; text-shadow:0 0 10px #ff4d6daa; padding:6px 16px; border-radius:8px; font-size:13px; font-weight:700; opacity:0; transition:opacity .25s;}
  #chaseWhaleWarning.show{opacity:1; animation:chaseWarnPulse 0.6s ease-in-out infinite;}
  @keyframes chaseWarnPulse{0%,100%{transform:translateX(-50%) scale(1);} 50%{transform:translateX(-50%) scale(1.05);}}
  #campaignFlash{position:fixed; inset:0; z-index:210; pointer-events:none; opacity:0; background:radial-gradient(circle, #ffffff 0%, #b28dff 45%, #4d2dff 100%); transition:opacity 0.35s ease-in-out;}
  #stageCountdownOverlay{display:none; position:fixed; inset:0; z-index:205; pointer-events:none; background:#05070fcc; flex-direction:column; align-items:center; justify-content:center; gap:14px;}
  #stageCountdownTitle{color:#ffd166; font-size:28px; font-weight:800; letter-spacing:2px; text-shadow:0 0 20px #ffd16688;}
  #stageCountdownNumber{color:#e8e4ff; font-size:80px; font-weight:800; text-shadow:0 0 30px #6d7fffaa; animation:stageCountdownPulse 1s ease-out;}
  @keyframes stageCountdownPulse{0%{transform:scale(1.6); opacity:0;} 30%{transform:scale(1); opacity:1;} 100%{transform:scale(1); opacity:1;}}
  #chaseDarkOverlay{position:fixed; inset:0; z-index:208; background:#000; opacity:0; pointer-events:none; transition:opacity 0.6s ease-in-out; display:flex; align-items:center; justify-content:center;}
  #chaseDarkOverlay.show{opacity:1;}
  #chaseDarkText{color:#e8e4ff; font-size:22px; font-style:italic; text-align:center; opacity:0; transition:opacity 0.8s ease-in-out; text-shadow:0 0 16px #8a4dff88;}
  #chaseDarkText.show{opacity:1;}

  #replayUI{position:fixed; inset:0; z-index:60; pointer-events:none; font-size:13px;}
  #replayTopBar{position:absolute; top:0; left:0; right:0; display:flex; align-items:center; gap:14px; padding:10px 16px; background:linear-gradient(#000a,transparent); pointer-events:auto;}
  #replayExitBtn{padding:7px 14px; background:#1c2440; border:1px solid #6d7fff; color:#e8e4ff; border-radius:6px; font-weight:700; cursor:pointer;}
  #replayHideToolsBtn{padding:7px 14px; background:#1c2440; border:1px solid #3a4670; color:#9fb0d8; border-radius:6px; font-weight:700; cursor:pointer;}
  #replayShowToolsBtn{display:none; position:fixed; top:10px; right:16px; z-index:65; padding:7px 14px; background:#1c2440dd; border:1px solid #6d7fff; color:#e8e4ff; border-radius:6px; font-weight:700; cursor:pointer; pointer-events:auto;}
  #replayErrorBanner{position:fixed; top:0; left:0; right:0; z-index:70; background:#5a0d0dee; color:#fff; padding:10px 16px; font-size:12px; font-weight:700; text-align:center; pointer-events:none;}
  #replayUI.toolsHidden #replayTopBar, #replayUI.toolsHidden #replaySpectateBar, #replayUI.toolsHidden #replayDeathBanner,
  #replayUI.toolsHidden #replayBottomBar, #replayUI.toolsHidden #replaySquadPanel, #replayUI.toolsHidden #replayLeaderboardPanel{display:none;}
  #replayMatchLabel{color:#9fd8ff; font-weight:700;}

  /* ROOM FOR BOTH.

     A replay shows the game's own HUD now, and the replay's bars own the very
     top and the very bottom of the screen -- so the playback controls landed on
     the ability slots and the title bar landed on the toasts. The HUD moves
     inward while a replay is open rather than the bars moving out, because the
     controls are the part you have to be able to hit.

     Dropped again by .toolsHidden: with the bars gone there is nothing to make
     room for, and a replay recorded for video should look exactly like the game. */
  /* ONE EDGE, NOT A LIST OF PANELS.

     #hud is `position:fixed; inset:0`, so every bottom-anchored panel in it --
     the ability row, the squad chat, the arena map, and whatever gets added
     next -- measures from THIS edge. Pulling it up lifts all of them together,
     by the same amount, and keeps the spacing between them exactly as it is in
     a live match. The first version of this named three elements instead and
     left the arena map under the scrubber, because a list of things to move is
     only right until somebody adds a fourth. */
  body.replayOpen #hud{ bottom:82px; }

  /* The top gets a number of its own: two replay bars stack up there, the title
     bar and the spectate bar, so there is no single edge to pull. */
  body.replayOpen #topCentre{ top:105px; }

  /* Tools hidden means there are no bars to make room for, and a replay
     recorded for video should look exactly like the game. */
  body.replayToolsHidden #hud{ bottom:0; }
  body.replayToolsHidden #topCentre{ top:14px; }

  /* 57px, not 110: it sits directly under the title bar. At 110 it landed on
     the score, the ring countdown and the team pills, which were not on
     screen in a replay when this number was chosen. */
  #replaySpectateBar{position:absolute; top:57px; left:50%; transform:translateX(-50%); display:flex; align-items:center; gap:10px; background:#00120fccdd; border:1px solid var(--line); border-radius:10px; padding:6px 10px; pointer-events:auto;}
  #replaySpectateBar button{padding:5px 10px; background:#1c2440; border:1px solid #3a4670; color:#e8e4ff; border-radius:6px; cursor:pointer; font-size:12px;}
  #replaySearchInput{width:120px; padding:5px 10px; background:#1c2440; border:1px solid #3a4670; color:#e8e4ff; border-radius:6px; font-size:12px;}
  #replaySearchResult{position:absolute; top:100%; left:0; right:0; margin-top:4px; background:#00120fccee; border:1px solid var(--line); border-radius:8px; max-height:160px; overflow-y:auto; display:none;}
  #replaySearchResult.show{display:block;}
  .replaySearchRow{padding:6px 10px; font-size:11px; cursor:pointer; color:var(--text);}
  .replaySearchRow:hover{background:#1c2440;}
  .replaySearchRow.dead{opacity:0.4; cursor:not-allowed;}
  #replaySpectateBar button:hover{border-color:#6d7fff;}
  #replayFreeCamBtn.active{border-color:#3ddc71; color:#3ddc71;}
  #replayFootballBtn.active{border-color:#39c9ff; color:#39c9ff;}
  #replaySpectateName{min-width:110px; text-align:center; font-weight:700; color:#e8e4ff;}
  #replayDeathBanner{position:absolute; top:110px; left:50%; transform:translateX(-50%); background:#1a0d0dcc; border:1px solid #ff4d6d; color:#ff8fa3; padding:6px 16px; border-radius:8px; font-weight:700; opacity:0; transition:opacity .3s; pointer-events:none;}
  #replayDeathBanner.show{opacity:1;}
  #replayBottomBar{position:absolute; bottom:0; left:0; right:0; display:flex; align-items:center; gap:14px; padding:12px 20px; background:linear-gradient(transparent,#000a); pointer-events:auto;}
  #replayPlayPauseBtn{width:38px; height:38px; flex-shrink:0; background:#1c2440; border:1px solid #6d7fff; color:#e8e4ff; border-radius:50%; cursor:pointer; font-size:15px;}
  #replaySpeedGroup{display:flex; gap:4px; flex-shrink:0;}
  .replaySpeedBtn{padding:5px 9px; background:#1c2440; border:1px solid #3a4670; color:#9fb0d8; border-radius:6px; cursor:pointer; font-size:11px;}
  .replaySpeedBtn.active{border-color:#3ddc71; color:#3ddc71;}
  #replayTimeDisplay{flex-shrink:0; color:#9fd8ff; font-variant-numeric:tabular-nums; width:96px;}
  #replayScrubTrack{position:relative; flex:1; height:8px; background:#1c2440; border-radius:4px; cursor:pointer;}
  #replayScrubFill{position:absolute; left:0; top:0; bottom:0; width:0%; background:#6d7fff; border-radius:4px; pointer-events:none;}
  #replayScrubHandle{position:absolute; top:50%; width:14px; height:14px; background:#e8e4ff; border-radius:50%; transform:translate(-50%,-50%); left:0%; box-shadow:0 0 8px #6d7fff; pointer-events:none;}
  .replayKillMarker{position:absolute; top:50%; width:6px; height:14px; background:#ff5470; border-radius:2px; transform:translate(-50%,-50%); cursor:pointer; z-index:2;}
  .replayKillMarker:hover{background:#ff8fa3; height:18px;}
  #replayMinimapWrap{display:none;}
  #replayStatBars{display:none;}
  .replayBarRow{display:flex; align-items:center; gap:6px; margin-bottom:5px;}
  .replayBarRow:last-child{margin-bottom:0;}
  .replayBarLabel{font-size:9px; color:#9fb0d8; width:38px; flex-shrink:0;}
  .replayBarOuter{flex:1; height:8px; background:#1c2440; border-radius:4px; overflow:hidden;}
  .replayBarInner{height:100%; width:100%; border-radius:4px; transition:width .15s;}
  #replayChatPanel{display:none;}
  .replayChatBubble{background:#00120fccdd; border:1px solid var(--line); border-radius:8px; padding:5px 9px; font-size:11px; color:var(--text);}
  .replayChatBubble b{margin-right:4px;}
  #replayStageSelect{padding:5px 8px; background:#1c2440; border:1px solid #3a4670; color:#e8e4ff; border-radius:6px; font-size:11px; cursor:pointer;}
  #replaySquadPanel{position:absolute; right:236px; top:100px; width:190px; max-height:22vh; overflow-y:auto; background:#00120fccdd; border:1px solid var(--line); border-radius:10px; padding:8px; pointer-events:auto; font-size:10.5px; color:var(--text);}
  #replayLeaderboardPanel{position:absolute; left:16px; top:330px; width:200px; background:#00120fccdd; border:1px solid var(--line); border-radius:10px; padding:8px; pointer-events:auto; font-size:10.5px; color:var(--text);}
  .replayPanelHeader{font-size:10px; font-weight:800; letter-spacing:1px; color:#9fb0d8; margin-bottom:6px; display:flex; justify-content:space-between; align-items:center;}
  #replayLeaderboardToggle{background:none; border:none; color:#9fb0d8; cursor:pointer; font-size:11px; padding:0 4px;}
  /* The nearest thing the game has to a live leaderboard -- it is the playback
     one, and on a twenty-ship match it ran off the bottom of the window with
     nothing to say so. It scrolls now, on the shared rail. */
  #replayLeaderboardList{max-height:26vh; overflow-y:auto;}
  #replayLeaderboardList.collapsed{display:none;}
  .replayRosterRow{display:flex; align-items:center; gap:6px; padding:3px 4px; border-radius:5px; cursor:pointer;}
  .replayRosterRow:hover{background:#1c2440;}
  .replayRosterRow.active{background:#1c3a2a; box-shadow:inset 0 0 0 1px #3ddc71;}
  .replayRosterRow.dead{opacity:0.45;}
  .replayRosterName{flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
  .replayRosterStat{color:#ffd166; flex-shrink:0;}
  #campaignFlash.show{opacity:1;}
  .toast{background:#0b1020cc; border:1px solid var(--line); padding:6px 14px; border-radius:8px; font-size:13px; font-weight:600; opacity:0; transform:translateY(-8px); transition:opacity .25s, transform .25s; white-space:nowrap;}
  .toast.neonToast{font-size:16px; font-weight:800; text-shadow:0 0 8px currentColor, 0 0 16px currentColor; letter-spacing:0.5px;}
  .toast.show{opacity:1; transform:translateY(0);}
  /* Was 26px with 3px of tracking, which is bigger than the wordmark and
     covered a third of the arena for a line you cannot act on anyway. */
  .toast.big{font-size:15px; font-weight:800; letter-spacing:1.4px; padding:6px 16px; text-shadow:0 0 12px currentColor;}

  /* ---------- the loadout, as tiles ----------
     Three across, each one the drop's own icon with three pips under it. The
     pips are driven entirely by the lvl1/lvl2/lvl3 classes the updater already
     sets, so nothing in the HUD code had to learn about them.

     The numbers have not gone: .statVal is still written every frame and shows
     on hover, which is when you want a figure rather than a glance. */
  /* minmax(0,1fr), not 1fr.

     A bare `1fr` is `minmax(auto, 1fr)`, so a track is never allowed narrower
     than its widest item's intrinsic width -- and with nowrap labels that meant
     one long word ("COOLDOWN") took a track to 88px while its neighbours got 41.
     A zero minimum makes the three equal and lets the label ellipsis instead. */
  #statTiles{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:5px; margin-top:9px; }
  .statTile{
    position:relative; background:#111c28; border:1px solid #1e2c3a; border-radius:5px;
    padding:7px 3px 6px; text-align:center; transition:border-color .18s, background .18s;
  }
  .statTile .stIc{
    display:block; width:22px; height:22px; margin:0 auto 5px;
    filter:grayscale(.45) opacity(.68); transition:filter .18s;
  }
  .statTile .stPips{ display:flex; gap:3px; justify-content:center; }
  .statTile .stPips i{ width:9px; height:4px; border-radius:1px; background:#25384a; }
  .statTile .stLab{
    display:block; font-size:8px; letter-spacing:.08em; text-transform:uppercase;
    color:#7d8b99; margin-top:5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  /* The figure, on hover. Positioned over the tile rather than in it, so it
     cannot change the grid's height when it appears. */
  .statTile .statVal{
    position:absolute; left:50%; bottom:calc(100% + 4px); transform:translateX(-50%);
    background:#040a10f2; border:1px solid var(--line-hot); border-radius:4px;
    padding:3px 7px; font-size:9.5px; white-space:nowrap; color:var(--text);
    opacity:0; pointer-events:none; transition:opacity .14s; z-index:4;
    font-variant-numeric:tabular-nums;
  }
  .statTile:hover{ border-color:var(--line-hot); background:#101c2a; }
  .statTile:hover .statVal{ opacity:1; }

  /* Levelled tiles light up: the icon comes out of greyscale and the pips fill.
     Colours match the row colours these replaced, so a level still reads the
     same at a glance as it always did. */
  .statTile.lvl1, .statTile.lvl2, .statTile.lvl3{ background:#0a1a12; border-color:#1d4a2c; }
  .statTile.lvl1 .stIc, .statTile.lvl2 .stIc, .statTile.lvl3 .stIc{ filter:none; }
  .statTile.lvl1 .stLab, .statTile.lvl2 .stLab, .statTile.lvl3 .stLab{ color:var(--dim); }
  .statTile.lvl1 .stPips i:nth-child(-n+1),
  .statTile.lvl2 .stPips i:nth-child(-n+2),
  .statTile.lvl3 .stPips i{ background:#9fd8ff; box-shadow:0 0 6px -1px #9fd8ff; }
  .statTile.lvl2 .stPips i:nth-child(-n+2){ background:#22d3ee; box-shadow:0 0 6px -1px #22d3ee; }
  .statTile.lvl3 .stPips i{ background:#ffd166; box-shadow:0 0 7px -1px #ffd166; }

  /* Maxed out. Three is the ceiling for every one of these tiles, so lvl3 IS
     maxed -- the pips were already gold, but you had to count them to know.
     The icon now carries a gold halo of its own and the frame warms up with it,
     so a finished stat reads at a glance from across the HUD. The icons are
     coloured data URIs, so the glow is a drop-shadow rather than a tint: the
     icon keeps its own colour and just gains the light around it. */
  .statTile.lvl3{ border-color:#7a5f1c; background:#171208; }
  .statTile.lvl3 .stIc{
    filter:drop-shadow(0 0 4px #ffd166) drop-shadow(0 0 9px rgba(255,176,46,.55));
    animation:statMaxGlow 2.4s ease-in-out infinite;
  }
  .statTile.lvl3 .stLab{ color:#ffd166; }
  .statTile.lvl3:hover{ border-color:#ffd166; background:#1e1709; }
  @keyframes statMaxGlow{
    0%,100%{ filter:drop-shadow(0 0 3px #ffd166) drop-shadow(0 0 7px rgba(255,176,46,.42)); }
    50%    { filter:drop-shadow(0 0 6px #ffe08a) drop-shadow(0 0 13px rgba(255,176,46,.7)); }
  }
  @media (prefers-reduced-motion:reduce){
    .statTile.lvl3 .stIc{ animation:none; filter:drop-shadow(0 0 5px #ffd166) drop-shadow(0 0 11px rgba(255,176,46,.55)); }
  }

  /* No window: the tiles are already boxes, and a box of boxes is one frame too
     many. Same treatment the squad list got. */
  #statsPanel{
    position:absolute; right:22px; top:100px; width:200px;
    background:none; border:none; padding:0;
  }
  #statsPanel .holoTick{ display:none; }
  #statsPanel .statsTitle{font-size:10px; color:var(--dim); letter-spacing:2px; margin-bottom:10px;}
  .statRow{display:flex; justify-content:space-between; align-items:baseline; font-size:10.5px; margin-bottom:8px; color:var(--dim); letter-spacing:0.5px; gap:8px;}
  .statRow span:first-child{flex-shrink:0;}
  .statRow .statVal{font-variant-numeric:tabular-nums; font-weight:700; text-align:right;}
  .statRow.lvl1{color:#9fd8ff;}
  .statRow.lvl2{color:#22d3ee; text-shadow:0 0 6px #22d3ee55;}
  .statRow.lvl3{color:#ffd166; text-shadow:0 0 8px #ffd16688;}
  .statRow.statWeapon{border-top:1px solid var(--line); padding-top:8px; margin-top:2px;}

  #bottomHudRow{position:absolute; left:50%; bottom:22px; transform:translateX(-50%); display:flex; align-items:flex-end; gap:26px;}

  /* ---------- the bottom HUD versus the Arena Map ----------

     The row above is centred on the window; #minimapWrap is pinned to the
     right of it. They therefore converge as the window narrows, and they did
     converge: 16px of overlap at 1366 and 187px at 1024, with the fuel and
     circuit gauges disappearing behind the map.

     --arenaMapW is what that corner actually occupies, taken from the pieces
     rather than guessed: the 120px proximity scope and the 190px map, a 1px
     border on each of them, and the 8px #radarRow gap between the two. If any
     of those change, this number has to change with them -- and the layout
     check asserts the two never overlap, so drifting apart fails a build
     rather than reaching a player. */
  :root{
    --arenaMapW: 322px;      /* 120 + 2 + 8 + 190 + 2 -- see #radarRow */
    --arenaMapGap: 26px;     /* the same breathing room the row uses internally */
  }

  /* Narrow enough that the two would touch: stop centring on the window and
     centre on the floor that is left beside the map instead. */
  @media (max-width: 1420px){
    #bottomHudRow{
      left:22px;
      right:calc(22px + var(--arenaMapW) + var(--arenaMapGap));
      transform:none;
      justify-content:center;
    }
  }

  /* Narrower still: the row is 710px wide and needs floor to stand on, so the
     room comes out of the CORNER rather than out of the row. The scope and the
     map stop sitting side by side and stack instead -- 322px becomes 192px,
     which is 130px handed back, and that is enough for the row to keep its
     full size down to 1024.

     Deliberately NOT scaling the row here. The first version of this fix did,
     and the layout check caught it: the ability slots came out 39px instead of
     56px, and those are meant to be the same size in a narrow window as in a
     wide one because they are what a player aims at in a fight. */
  @media (max-width: 1150px){
    #radarRow{ flex-direction:column; align-items:flex-end; gap:6px; }
    :root{ --arenaMapW: 192px; }    /* 190 + a border each side */
  }

  /* Below this nothing will make it fit at full size: 1000px of window leaves
     760px of floor once the stacked corner and the margins are taken, and the
     row wants 710px plus room to breathe. Scaled from its own bottom edge so
     it stays on the same line. This is under the width the slot-size guarantee
     covers, and a small legible HUD beats gauges behind a map. */
  @media (max-width: 1000px){
    #bottomHudRow{
      transform:scale(0.78);
      transform-origin:bottom center;
    }
  }
  #killFeed{position:absolute; left:22px; top:100px; width:240px; display:flex; flex-direction:column; gap:5px; z-index:20;}
  /* The label used to be a bare div with no position rule, which put it at the
     top-left of the HUD layer -- nowhere near the chat it names. Proximity and
     Arena Map get this right because they sit inside #minimapWrap; the chat now
     has the same thing. Anchored at the bottom, so the block grows upward and
     the label stays directly above the topmost bubble. */
  #squadChatWrap{
    position:absolute; left:22px; bottom:100px; width:280px; z-index:20;
    display:flex; flex-direction:column; pointer-events:none;
  }
  #squadChat{display:flex; flex-direction:column; gap:5px; pointer-events:none;}
  /* The search box and the rows are their own boxes, so the panel around them
     goes the way of the other two.

     It also moves up and gets a ceiling. It used to start 340px down and grow
     freely, while the squad chat sits 100px off the bottom and grows UPWARD --
     two panels growing toward each other in the same column, which on a short
     window means they meet. The max-height keeps this one clear of the chat and
     scrolls instead. */
  #playerSearchPanel{
    display:none; position:absolute; left:22px; top:296px; width:208px; z-index:20;
    background:none; border:none; padding:0; pointer-events:auto;
    max-height:calc(100vh - 296px - 250px); overflow-y:auto;
  }
  #playerSearchPanel .holoTick{ display:none; }
  /* No window. A frame around one row is mostly frame, and on a full squad it
     has to hold twelve -- so the rows carry themselves and the panel is just
     where they sit. */
  #squadInfoPanel{
    display:none; position:absolute; right:242px; top:100px; width:190px; z-index:20;
    color:#ffffff; max-height:44vh; overflow-y:auto; font-size:10.5px;
    pointer-events:auto; background:none; border:none; padding:0;
  }
  #squadInfoPanel .holoTick{ display:none; }
  .squadMate{ display:flex; flex-direction:column; gap:2px; margin-bottom:6px; }
  .squadMate .squadInfoRow{ display:flex; justify-content:space-between; align-items:center; gap:8px; }
  .squadInfoName{ display:flex; align-items:center; gap:5px; min-width:0; }
  .squadInfoName .sqName{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .sqHull{ flex-shrink:0; }
  .sqPts{ font-size:9.5px; color:var(--dimmer); font-variant-numeric:tabular-nums; flex-shrink:0; }
  .sqHp{ display:block; height:3px; background:#161d24; border-radius:2px; overflow:hidden; }
  .sqHp i{ display:block; height:100%; background:#3ddc71; box-shadow:0 0 7px -2px #3ddc71; transition:width .18s; }
  .sqHp i.hurt{ background:#ffd166; box-shadow:0 0 7px -2px #ffd166; }
  .sqHp i.bad{ background:#ff4d6d; box-shadow:0 0 7px -2px #ff4d6d; }
  .squadMate.squadInfoDead .sqHp{ opacity:.4; }
  #squadLootFloatWrap{position:fixed; inset:0; pointer-events:none; z-index:21;}
  .squadLootBadge{position:fixed; transform:translate(-100%,-50%); background:#00120fee; border:1px solid var(--neon); border-radius:5px; padding:2px 7px; font-size:9.5px; color:var(--neon); white-space:nowrap; max-width:150px; overflow:hidden; text-overflow:ellipsis; box-shadow:0 0 8px var(--neon-dim);}
  /* Qualifying is said by the badge inside the panel. It used to ALSO be said
     by a gold glow around the whole list, which on a windowless panel is a
     glow around nothing -- a soft rectangle hanging in space behind the rows. */
  /* Clear of the corner. `right:222px` cleared a 190px map; the corner now holds
     the proximity scope beside it and is 322px wide, so the old offset put this
     straight on top of the scope. */
  #spectateHelpPanel{display:none; position:absolute; right:356px; bottom:22px; width:170px; z-index:20; background:#00120fccdd; border:1px solid var(--line); border-radius:10px; padding:8px; color:#ffffff; font-size:9.5px; line-height:1.7;}
  #spectateHelpPanel .label{font-size:11px; letter-spacing:1px; color:#9fb0e0;}
  #squadInfoPanel .label{font-size:11px; letter-spacing:1px; color:#9fb0e0;}
  .squadInfoRow{display:flex; justify-content:space-between; gap:5px; font-size:10px; padding:3px 0; border-bottom:1px solid var(--line);}
  .squadInfoRow[style*="cursor: pointer"]:hover{background:#1a2440;}
  .squadInfoRow:last-child{border-bottom:none;}
  .squadInfoName{overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
  .squadInfoDead{color:#7c88ad; text-decoration:line-through;}
  #playerSearchInput{width:100%; background:#05070f; border:1px solid var(--line); border-radius:6px; padding:5px 8px; color:#ffffff; font-size:11px;}
  #playerSearchInput:focus{outline:none; border-color:#6d7fff;}
  #playerSearchResult{font-size:10.5px; color:#ffffff; margin-top:5px; min-height:0;}
  /* ---------- spectate: one row, one weight ----------
     The two motherships used to be gold gradient pills and the three constructs
     had no rule at all, so they rendered as the operating system's own buttons.
     All five are the same row now; the motherships keep a warmer border, which
     is as much emphasis as they need. */
  .specGroup{
    font-size:8.5px; letter-spacing:.2em; text-transform:uppercase;
    color:#7d8b99; margin:8px 0 4px;
  }
  .specGroup:first-of-type{ margin-top:5px; }
  .specRow{
    display:grid; grid-template-columns:auto 1fr auto; gap:9px; align-items:center;
    width:100%; margin:0 0 3px; padding:5px 8px; text-align:left; cursor:pointer;
    background:#0d1622; border:1px solid var(--line); border-radius:6px;
    color:var(--text); font-family:inherit; font-size:10.5px; letter-spacing:.03em;
    transition:border-color .15s, background .15s;
  }
  .specRow .ic{ font-size:13px; line-height:1; }
  .specRow .meta{
    font-size:9px; letter-spacing:.08em; text-transform:uppercase; color:var(--dimmer);
    white-space:nowrap;
  }
  .specRow:hover{ background:#122031; border-color:var(--line-hot); }
  .specRow:focus-visible{ outline:2px solid var(--neon); outline-offset:2px; }
  .specRow.hot{ border-color:#3a3416; background:#141207; }
  .specRow.hot:hover{ border-color:var(--gold, #ffd166); background:#1b1709; }
  /* ---------- adding somebody to a squad, live ----------
     Two boxes and a button, in the same materials as the rows above them --
     the same ground, the same border, the same radius -- because it belongs to
     that panel rather than being a control panel bolted onto it. The button is
     the only thing here allowed any colour: it is the one irreversible action
     in the window, and it should look like the thing you are about to press. */
  #addMemberBox{ display:flex; flex-direction:column; gap:5px; }
  #addMemberBox input{
    width:100%; background:#05070f; border:1px solid var(--line); border-radius:6px;
    padding:5px 8px; color:#ffffff; font-size:11px; font-family:inherit;
  }
  #addMemberBox input:focus{ outline:none; border-color:#6d7fff; }
  #addMemberBox input::placeholder{ color:#5b6b7d; }
  #addMemberBtn{
    width:100%; padding:6px 8px; border-radius:6px; cursor:pointer;
    border:1px solid #1d5c46; background:#0c2a21; color:#7ef0c0;
    font-family:inherit; font-size:10px; font-weight:800;
    letter-spacing:.14em; text-transform:uppercase;
    transition:background .15s, border-color .15s;
  }
  #addMemberBtn:hover{ background:#123a2d; border-color:#34d399; }
  #addMemberBtn:active{ background:#0a2019; }
  #addMemberBtn:focus-visible{ outline:2px solid var(--neon); outline-offset:2px; }
  /* Says what happened, in the colour of whether it worked. Hidden until there
     is something to say, so the panel does not carry an empty line around. */
  #addMemberMsg{
    display:none; font-size:10px; line-height:1.35; color:#34d399;
    margin-top:1px; word-break:break-word;
  }
  .chatBubble{background:#00120fccdd; border:1px solid var(--line); border-radius:10px; padding:7px 10px; font-size:11.5px; line-height:1.3; animation:chatFadeIn 0.25s ease-out; color:#ffffff;}
  .chatBubble .chatName{font-weight:800; letter-spacing:0.5px; margin-right:5px;}
  .chatBubble.fading{opacity:0; transition:opacity 0.8s ease-out;}
  @keyframes chatFadeIn{from{opacity:0; transform:translateY(6px);} to{opacity:1; transform:translateY(0);}}
  .killFeedEntry{background:#0b1020e0; border:1px solid var(--line); border-left:3px solid #ff4d4d; border-radius:6px; padding:6px 10px; font-size:11px; color:var(--text); opacity:0; transform:translateX(10px); transition:opacity .25s, transform .25s;}
  .killFeedEntry.show{opacity:1; transform:translateX(0);}
  .killFeedEntry b{color:#ffd166;}
  .killFeedEntry .kfWeapon{color:var(--dim); font-size:10px;}

  /* A container, not a box.

     It used to BE the map: 190x190, its own border, and overflow:hidden. So
     anything else put in the corner -- a caption, the order guide, a second
     readout -- was pushed against a fixed height and clipped, which is why the
     map went half-missing the moment the guide arrived. The chrome belongs to
     each canvas now, and the corner is free to hold more than one thing. */
  #minimapWrap{
    position:absolute; right:22px; bottom:22px;
    display:flex; flex-direction:column; align-items:flex-end;
    flex-shrink:0; pointer-events:auto;
  }
  #minimap, #proxScope{
    background:#05070fcc; border:1px solid var(--line); border-radius:10px; display:block;
  }
  /* The scope reads as the pilot's own instrument, so it gets the hull's glow
     rather than the map's neutral frame. */
  #proxScope{ border-color:var(--line-hot); }

  /* The gap is what the badges hang in. A key cap hangs --badgeOut to the left
     of its slot and the gem the same distance to the right of the one before
     it, so two badges share every gap and the gap has to be wider than both of
     them put together or a Q cap lands on the slot next door's gem. The margin
     is the same argument upwards, against the ABILITIES label. */
  #abilityBar{display:flex; gap:26px; flex-shrink:0; pointer-events:auto; margin-top:8px;}

  /* The bag: five slots shared between gems and consumables. Deliberately the
     same visual family as the ability slots below it, because a gem is dragged
     from one to the other and two different-looking grids would not read as
     somewhere you can drag between. */
  /* The HUD is pointer-events:none so it never swallows a click meant for the
     arena. Anything you are supposed to touch has to opt back IN -- the bag and
     the ability slots did not, so every drag and every click on a potion went
     straight through them into the game and nothing happened. */
  /* Same gap argument as the ability bar below it -- these slots wear the same
     caps, hung out by the same distance, so they need the same room to hang in.
     There is no gem on this row, so one badge per gap rather than two. */
  #inventoryBar{display:flex; gap:16px; flex-shrink:0; pointer-events:auto; margin-top:6px;}

  /* The gem prompt. Sits in the flow ABOVE the inventory rather than floating
     over it: it is a caption for that row, and a prompt covering the slots it
     is telling you to use would be self-defeating. It takes no height when it
     is not showing, so the row does not jump when it goes.

     pointer-events stays none -- you must be able to drag a gem straight past
     the words without them catching the drag. */
  #gemHint{
    pointer-events:none; opacity:0; max-height:0; overflow:hidden;
    font-size:12px; letter-spacing:0.4px; font-weight:600; white-space:nowrap;
    color:#ffe08a; text-shadow:0 0 10px #ffe08a88, 0 1px 2px #000;
    transition:opacity .35s ease, max-height .35s ease, margin .35s ease;
    margin:0; align-self:flex-start;
  }
  #gemHint.on{ opacity:1; max-height:22px; margin:0 0 3px; animation:gemHintPulse 1.6s ease-in-out infinite; }
  @keyframes gemHintPulse{ 0%,100%{ transform:translateY(0); } 50%{ transform:translateY(-2px); } }
  @media (prefers-reduced-motion: reduce){ #gemHint.on{ animation:none; } }
  /* Somebody else's bag, in spectate or a killcam. It is information, so it
     stays legible rather than being dimmed into uselessness -- it simply stops
     inviting a click. */
  #inventoryBar.notMine .invSlot{ cursor:default; }
  #inventoryBar.notMine .invSlot:hover{ transform:none; }
  .invSlot{
    width:46px; height:46px; border-radius:10px; position:relative; cursor:grab;
    background:rgba(255,255,255,.05); border:1px solid rgba(255,255,255,.18);
    display:grid; place-items:center; transition:border-color .12s, transform .12s;
  }
  .invSlot.empty{border-style:dashed; opacity:.45; cursor:default;}
  .invSlot img{width:34px; height:34px; pointer-events:none; display:block;}
  .invSlot .invQty{
    position:absolute; right:3px; bottom:1px; font-size:11px; font-weight:800;
    color:#fff; text-shadow:0 1px 3px #000; pointer-events:none;
  }
  .invSlot.dragging{opacity:.45; cursor:grabbing;}
  /* The other slot, while a drag is hovering it. Reordering costs nothing and
     loses nothing, so this is the calm confirmation colour rather than the
     gem bar's warning yellow. */
  .invSlot.dropTarget{border-color:#6dfcf1; box-shadow:0 0 12px -2px #6dfcf1;}
  /* The number key that reaches this slot. It carries the ability bar's own
     .slotKey class rather than a copy of it, so the cap is positioned by the
     same one rule -- there is exactly one key-cap style in the game and this
     is it. */
  /* An ability slot's cap sits on the slot's dark ground; an inventory slot's
     sits on the item's own artwork, which is a light object more often than
     not. Both wear the same shadow, and it is written once in .slotKey below --
     a second rule here is a second cap style waiting to drift out of step. */

  /* ---- "Throw away SHIELD BATTERY?" ----

     The same card as the first-visit music question, in the middle of the
     screen instead of at the foot of it: the bag this is about is along the
     bottom, and a question sitting on top of its own subject is worse than no
     question. Both answers are the same weight -- the game has no opinion
     about whether you should keep a potion, only that it should not throw one
     away because a drag ended two pixels off a slot. */
  #invThrowCard{
    position:fixed; top:50%; left:50%; transform:translate(-50%,-50%) scale(.97);
    z-index:420; display:none; opacity:0; transition:opacity .16s, transform .16s;
    background:#00120fee; border:1px solid var(--line); border-radius:14px;
    padding:14px 18px 15px; text-align:center; max-width:min(92vw, 340px);
    box-shadow:0 6px 26px #000a, 0 0 18px var(--neon-dim); pointer-events:auto;
  }
  #invThrowCard.on{ display:block; opacity:1; transform:translate(-50%,-50%) scale(1); }
  #invThrowTitle{
    font-size:15px; letter-spacing:2px; color:var(--neon); text-transform:uppercase;
    margin-bottom:3px; text-shadow:0 0 10px var(--neon-dim);
  }
  .invThrowSub{ font-size:11.5px; color:var(--dim); line-height:1.45; margin-bottom:11px; }
  .invThrowRow{ display:flex; gap:8px; justify-content:center; flex-wrap:wrap; }
  .invThrowRow button{
    font-size:12.5px; padding:8px 14px; border-radius:9px; cursor:pointer; flex:1 1 auto;
    min-width:120px; min-height:40px;              /* a real target on a phone */
    background:#00120fcc; border:1px solid var(--line); color:var(--text);
    font-family:inherit; letter-spacing:.06em; transition:border-color .15s, background .15s;
  }
  .invThrowRow button:hover{ border-color:#3a4670; background:#04241ccc; }
  /* Same weight, same size, same border -- only the word is coloured, and only
     so the irreversible one is not read by accident. */
  #invThrowYes{ color:#ff8c42; }
  /* Where a gem may legally land. Only slots that can actually take it light
     up, so the answer to "can this go here" is visible before you let go. */
  .slotBox.gemTarget{border-color:#ffe08a; box-shadow:0 0 12px -2px #ffe08a;}
  .slotBox.gemTargetFull{border-color:#ff5470; opacity:.55;}
  /* ---- WHAT IS EQUIPPED HERE, AND WHERE IT IS ALLOWED TO SAY SO ----

     The gem used to sit two pixels inside the top-right corner, which put it
     straight over the first letters of the name -- BOMBER read as BOMBE with a
     crystal on the R. It has a reserved corner of its own now, and it is the
     KEY BADGE'S TWIN: the same 22px square, the same corners, the same rim and
     the same fill, hung out over the border by the same ten pixels, on the
     opposite corner. Two matching stickers on the two top corners, so a gemmed
     slot can be picked out from across the HUD and the gem can never land on a
     word. The crystal itself sits inside the chip, on its own ground, instead
     of floating loose over whatever the slot happened to be drawing.

     --badgeOut, the rim and the fill are the same three numbers keyCapIconSvg
     draws with; the check measures the two against each other every run. */
  .slotBox, .invSlot{ --badgeOut:10px; --badgeRim:#96a0d6; --badgeFill:#08151f; }
  .slotBox .slotGem{
    position:absolute; right:0; top:0; width:22px; height:22px; z-index:3;
    box-sizing:border-box; padding:1.5px;
    background:var(--badgeFill); border:2px solid var(--badgeRim); border-radius:4px;
    /* right:0/top:0 sit on the PADDING box, one pixel inside the slot's border,
       so the hang is one pixel more than --badgeOut in each direction. */
    transform:translate(calc(var(--badgeOut) + 1px), calc(-1px - var(--badgeOut)));
    pointer-events:none;
    filter:drop-shadow(0 2px 2px #000c) drop-shadow(0 0 5px currentColor);
  }
  /* The background was #00120fcccc -- ten hex digits, which is not a colour, so
     every slot had quietly been drawing straight onto the arena. The badges hang
     over the border now and need a ground to sit on, so the slot gets its panel
     back. */
  .slotBox{width:56px; height:56px; background:#00120fcc; border:1px solid var(--line); border-radius:9px; position:relative; display:flex; align-items:center; justify-content:center; transition:border-color 0.2s, box-shadow 0.2s;}
  .slotBoxSmall{width:34px; height:34px; border-radius:7px;}
  #slotSquadAbility{width:auto; min-width:34px; height:34px; padding:0 8px;}
  #squadAbilityRow{display:flex; justify-content:center; width:100%;}
  /* ---- THE KEY BADGE ----

     One rule, for the ability bar and the bag alike -- there is exactly one
     key-cap style in the game and this is where it is put.

     It sits ON the top-left corner and hangs --badgeOut clear of the frame on
     both edges, like the tab on a folder or a stamp over the corner of an
     envelope: better than half of it is outside the slot, and what is left
     inside is what holds it on. That is what frees the inside of the slot for
     the ability itself, and it is also what stops the badge reading as part of
     the ability -- it belongs to the slot, not to the thing in the slot.

     v4.75.0 got this wrong in a way that is worth writing down, because it is
     invisible from the CSS: the badge's BOX hung over the corner but the cap
     drawn inside that box did not fill it, so what crossed the outline was
     empty space and the cap itself sat tucked inside the frame. The drawing
     starts at the box's own 0,0 now (see keyCapIconSvg) -- box and cap are the
     same rectangle, and hanging the box out hangs the cap out with it.

     NOTHING ABOVE THIS MAY CLIP. Every ancestor from the slot up to #hud is
     overflow:visible, and if one of them ever stops being, the overhang is
     quietly cut off and the badge is flat again with no other symptom. The
     check walks that chain every run.

     The shadow is what makes it a sticker rather than a printed label: a solid
     fill and a rim say "object", and the shadow puts air between the object and
     the surface behind it. */
  .slotKey{position:absolute; top:0; left:0; font-size:9px; color:var(--dim); letter-spacing:1px; transform:translate(-5px,-6px); z-index:3;}
  /* A slot wearing a drawn key icon instead of the letters. The box collapses to
     the drawing so the icon sits where the text did, and the text itself lives
     on in title/aria-label rather than being thrown away. The plain-text badge
     above keeps its old modest offset: a bare letter with no cap around it has
     no body to hang over an edge with. */
  .slotKey:has(.slotKeyIcon){
    font-size:0; line-height:0; letter-spacing:0;
    /* the extra pixel is the slot's own border: top/left 0 is the padding box */
    transform:translate(calc(-1px - var(--badgeOut)), calc(-1px - var(--badgeOut)));
    filter:drop-shadow(0 2px 2px #000c) drop-shadow(0 0 2px #000e);
  }
  .slotKeyIcon{display:block; overflow:visible;}
  /* ---- THE BODY: SYMBOL, NAME, LEVEL ----

     A fixed three-storey stack, identical in every slot, so the bar reads as one
     row of the same object instead of five cards that each solved the problem
     differently. Every storey reserves its height whether it has anything in it
     or not: what makes the row line up is that the name sits at the same height
     in all five, whatever else is or is not there.

     Two type sizes, and no more. The name is what a player reads mid-fight, so
     it takes the larger size and the bolder weight; the level and the countdown
     are secondary and share the smaller one. */
  .slotIcon{
    display:flex; flex-direction:column; align-items:center; justify-content:flex-start;
    width:100%; height:100%; padding:6px 3px 2px;
    color:#dbe4ff; text-align:center; overflow:hidden;
  }
  .slotGlyph{font-size:13px; line-height:13px; height:13px;}
  /* Two lines, always. LASER SWORD is SET on two rather than shrunk onto one, so
     it wears exactly the same type as DODGE, and a one-word name is centred in
     the same two-line box so the level underneath never moves. */
  .slotName{
    display:flex; flex-direction:column; justify-content:center;
    height:21px; font-size:10px; font-weight:700; line-height:10.5px; letter-spacing:0.3px;
  }
  .slotName i{display:block; font-style:normal; white-space:nowrap;}
  .slotMeta{font-size:8.5px; font-weight:600; line-height:9px; height:9px; margin-top:3px; color:var(--dim); letter-spacing:0.5px;}
  /* Nothing in this slot, said on purpose. Dashed like the empty pockets in the
     bag directly above it, so an empty ability slot and an empty bag slot are
     the same idea drawn the same way -- and emphatically not a slot that failed
     to paint. */
  .slotBox.slotEmpty{border-style:dashed; background:#00120f55;}
  .slotBox.slotEmpty .slotName{color:var(--dim); font-weight:600; letter-spacing:1px;}
  /* Extra state -- the bomber's two bombs, the flash's charges. Its own corner,
     diagonally opposite the key badge, rather than a "(2x)" tacked onto the end
     of a name wherever there happened to be room for it. */
  .slotBox .slotUses{
    position:absolute; right:0; bottom:0; transform:translate(3px,3px); z-index:3;
    display:none; padding:1px 3px; border-radius:5px;
    font-size:8.5px; font-weight:700; line-height:1; letter-spacing:0.3px;
    background:#031014ee; border:1px solid var(--line); color:#dbe4ff;
  }
  .slotBox.hasUses .slotUses{display:block;}
  /* The cooldown, as a sweep: one wedge of shade draining clockwise from the
     top, so how far off it is can be read without reading a number. The number
     goes on the meta line, in the level's place, which leaves the name legible
     the whole way through -- knowing WHICH ability is cooling matters more in a
     fight than the tenth of a second it has left. The badges and the gem sit
     above the veil, because which key this is does not change while it is down. */
  .slotCd{position:absolute; inset:0; background:#000a; border-radius:9px; display:none; align-items:center; justify-content:center; font-size:14px; color:#fff; font-weight:700; pointer-events:none; z-index:1;}
  .slotBox.cooling .slotMeta{color:#fff; text-shadow:0 1px 3px #000, 0 0 6px #000; position:relative; z-index:2;}
  #chargeBarWrap{position:absolute; left:50%; bottom:210px; width:220px; transform:translateX(-50%); height:8px; background:#00120fcc; border:1px solid var(--line); border-radius:4px; overflow:hidden; display:none;}
  #chargeBarInner{height:100%; width:0%; background:linear-gradient(90deg,#ff8c42,#ff4d6d);}

  #centerMsg{
    position:absolute; top:40%; left:50%; transform:translate(-50%,-50%); text-align:center;
    color:var(--text); font-size:22px; letter-spacing:2px; text-shadow:0 0 20px #000; display:none;
  }
  #centerMsg .big{font-size:52px; font-weight:800; letter-spacing:6px; margin-bottom:8px;}
  #centerMsg .small{color:var(--dim); font-size:14px;}
  .deathBtnRow{display:none; gap:12px; margin-top:18px; justify-content:center;}
  #cleanSpectateBtn.qualifiedGlow{border-color:#ffd166; box-shadow:0 0 16px #ffd166cc, 0 0 30px #ffd16666; color:#ffd166; animation:qualifiedPulse 1.6s ease-in-out infinite;}
  @keyframes qualifiedPulse{0%,100%{box-shadow:0 0 16px #ffd166cc, 0 0 30px #ffd16666;} 50%{box-shadow:0 0 24px #ffd166ee, 0 0 44px #ffd16699;}}
  #qualifiedNote{display:none; margin-top:10px; color:#ffd166; font-weight:700; letter-spacing:0.5px; text-shadow:0 0 10px #ffd16688;}
  #menuBtn{padding:11px 22px; background:#001a15; border:2px solid var(--neon); box-shadow:0 0 12px var(--neon-dim); border-radius:4px; color:var(--neon); font-weight:700; letter-spacing:1px; cursor:pointer; pointer-events:auto; font-size:13px;}
  #menuBtn:hover{filter:brightness(1.15);}
  #cleanSpectateBtn{padding:11px 22px; background:#00120fcc; border:1px solid var(--line); border-radius:10px; color:var(--text); font-weight:700; letter-spacing:1px; cursor:pointer; pointer-events:auto; font-size:13px;}
  #cleanSpectateBtn:hover{border-color:#3a4670;}
  #takeControlBtn{display:none; padding:11px 22px; background:linear-gradient(90deg,#34d399,#22a8c9); border:none; border-radius:10px; color:#05070f; font-weight:800; letter-spacing:1px; cursor:pointer; pointer-events:auto; font-size:13px;}
  #takeControlBtn:hover{filter:brightness(1.1);}
  #crosshair{position:fixed; width:0; height:0; z-index:19; pointer-events:none;}

  #endScreen{
    position:fixed; inset:0; z-index:60; display:none; align-items:center; justify-content:center;
    background:#000705cc; backdrop-filter:blur(4px);
  }
  #endScreen .card{background:var(--panel); border:1px solid var(--line); border-radius:4px; padding:44px 56px; text-align:center; box-shadow:0 0 30px #00fff21a;}
  #endScreen .big{font-family:'Orbitron',sans-serif; font-size:40px; font-weight:800; letter-spacing:5px; margin-bottom:10px; color:var(--neon); text-shadow:0 0 12px var(--neon);}
  #endStandings{margin-top:20px; width:320px; display:flex; flex-direction:column; gap:8px; text-align:left;}
  .standingRow{display:flex; align-items:center; gap:10px; background:#00120fcc; border:1px solid var(--line); border-radius:8px; padding:9px 14px; font-size:13px; color:var(--text);}
  .standingRow .medal{font-size:16px; width:22px; text-align:center;}
  .standingRow .stName{flex:1; font-weight:700;}
  .standingRow .stKills{color:#6dfcf1; font-variant-numeric:tabular-nums;}
  .standingRow.place1{border-color:#ffd166; box-shadow:0 0 14px #ffd16633;}
  #endScreen button{margin-top:24px; padding:12px 28px; background:#001a15; border:2px solid var(--neon); border-radius:4px; color:var(--neon); font-family:'Orbitron',sans-serif; font-weight:700; letter-spacing:2px; text-transform:uppercase; cursor:pointer; box-shadow:0 0 14px var(--neon-dim); transition:all 0.15s;}
  #endScreen button:hover{box-shadow:0 0 22px #00fff288; background:#002a22;}

  #pauseOverlay, #tournamentStageOverlay, #tournamentQualifiersOverlay, #tournamentFinalResultsOverlay{display:none; position:fixed; inset:0; z-index:50; background:#000705ee; align-items:center; justify-content:center;}
  .pauseBox{background:var(--panel); border:1px solid var(--line); border-radius:4px; padding:36px 48px; text-align:center; display:flex; flex-direction:column; gap:14px; align-items:center; color:var(--text); box-shadow:0 0 30px #00fff21a;}
  .pauseTitle{font-family:'Orbitron',sans-serif; font-size:26px; font-weight:800; letter-spacing:5px; color:var(--neon); text-shadow:0 0 10px var(--neon); margin-bottom:8px;}
  #pauseContinueBtn{padding:12px 32px; background:#001a15; border:2px solid var(--neon); box-shadow:0 0 12px var(--neon-dim); border-radius:4px; color:var(--neon); font-weight:700; letter-spacing:1px; cursor:pointer; width:220px;}
  #pauseMenuBtn, #pauseSettingsBtn, #pauseVoiceBtn, #pauseInfoBtn{padding:12px 32px; background:#00120fcc; border:1px solid var(--line); border-radius:4px; color:var(--text); font-family:'Share Tech Mono',monospace; font-weight:700; letter-spacing:2px; text-transform:uppercase; cursor:pointer; width:220px; transition:all 0.15s;}
  #tournamentStageStartBtn, #tournamentNextStageBtn{padding:12px 32px; background:linear-gradient(90deg,#ffd166,#ff8c42); border:none; border-radius:4px; color:#000705; font-family:'Orbitron',sans-serif; font-weight:800; letter-spacing:2px; text-transform:uppercase; cursor:pointer; width:260px; margin-top:6px; box-shadow:0 0 16px #ff8c4255;}
  #tournamentAbortBtn{padding:12px 32px; background:#00120fcc; border:1px solid var(--line); border-radius:4px; color:var(--text); font-family:'Share Tech Mono',monospace; font-weight:700; letter-spacing:1px; cursor:pointer; width:260px; margin-top:8px; transition:all 0.15s;}
  #tournamentAbortBtn:hover{border-color:var(--neon); color:var(--neon); box-shadow:0 0 8px var(--neon-dim);}
  #tournamentPodium{display:flex; align-items:flex-end; justify-content:center; gap:12px; margin:18px 0 10px; width:100%;}
  .podiumCol{background:#00120fcc; border:2px solid var(--line); border-radius:12px; padding:14px 16px 12px; text-align:center; display:flex; flex-direction:column; align-items:center; gap:5px; flex:1; min-width:0;}
  .podiumCol.gold{border-color:#ffd166; background:#1a1408; order:2; padding-top:24px; box-shadow:0 0 24px #ffd16644;}
  .podiumCol.silver{border-color:#c0c9e0; order:1;}
  .podiumCol.bronze{border-color:#cd8c52; order:3;}
  .podiumMedal{ line-height:0; }
  .podiumName{font-weight:800; font-size:14px; color:#fff; word-break:break-word;}
  .podiumCol.gold .podiumName{ color:#ffd166; font-size:16px; }
  .podiumSub{font-size:11px; color:#9fb0e0; line-height:1.5;}

  /* ---------- the podium, saying what it means ----------
     The rank, then WHY that rank -- the order is survival, and no number on the
     card explains it. Then the tournament total as the headline, with the
     final-stage score demoted to the detail row where it cannot be mistaken
     for the thing the ranking is based on. */
  .podiumRank{
    font-size:9px; letter-spacing:.2em; text-transform:uppercase;
    color:var(--dim); margin-top:-1px;
  }
  .podiumCol.gold .podiumRank{ color:#ffd166; opacity:.85; }
  .podiumWhy{ font-size:11px; color:#ffffff; line-height:1.35; }
  .podiumBig{
    font-family:'Orbitron',sans-serif; font-weight:900; font-size:22px;
    color:var(--neon); line-height:1; font-variant-numeric:tabular-nums; margin-top:3px;
  }
  .podiumCol.gold .podiumBig{ color:#ffd166; font-size:26px; }
  .podiumBigLab{
    font-size:8px; letter-spacing:.18em; text-transform:uppercase; color:var(--dim);
  }
  .podiumMeta{
    display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:4px;
    font-size:10px; color:var(--dim); margin-top:7px; padding-top:7px;
    border-top:1px solid var(--line); width:100%;
  }
  .podiumMeta .podDot{ opacity:.4; }
  .podHull{ display:inline-flex; align-items:center; justify-content:center; }
  .podHull svg{ width:100%; height:100%; display:block; }

  /* ---------- your own run, first ----------
     The tournament used to end by telling you about three strangers. */
  .tournTitle{ letter-spacing:2px; }
  .tournSubTitle{
    font-size:9.5px; letter-spacing:.2em; text-transform:uppercase;
    color:var(--dim); margin:14px 0 8px; text-align:center;
  }
  .myRun{
    display:grid; grid-template-columns:auto 1fr; gap:16px; align-items:center;
    border:1px solid #1d5a3e; border-radius:10px; padding:14px 16px;
    background:linear-gradient(180deg,#0a2318,#061410); margin:4px 0 2px;
  }
  .myPlace{ text-align:center; padding-right:16px; border-right:1px solid #123a2a; }
  .myPlace b{
    display:block; font-family:'Orbitron',sans-serif; font-weight:900;
    font-size:32px; color:#3ddc71; line-height:1; text-shadow:0 0 18px #3ddc7155;
  }
  .myPlace span{
    display:block; font-size:8.5px; letter-spacing:.16em; text-transform:uppercase;
    color:var(--dim); margin-top:5px;
  }
  .myGrid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(78px,1fr)); gap:10px; }
  .myCell b{
    display:block; font-family:'Orbitron',sans-serif; font-size:15px; color:#fff;
    line-height:1; font-variant-numeric:tabular-nums;
  }
  .myCell span{
    display:block; font-size:8px; letter-spacing:.13em; text-transform:uppercase;
    color:var(--dim); margin-top:4px;
  }
  #tournamentAwardsList .awardVal{ display:flex; align-items:center; gap:6px; }

  @media (max-width:520px){
    .myRun{ grid-template-columns:1fr; }
    .myPlace{ border-right:none; border-bottom:1px solid #123a2a; padding:0 0 10px; }
  }
  #tournamentAwardsList{text-align:left; font-size:13px; line-height:2; margin:14px 0; width:100%; color:#fff;}
  #tournamentAwardsList .awardRow{display:flex; justify-content:space-between; gap:10px; border-bottom:1px solid var(--line); padding:5px 2px;}
  #tournamentAwardsList .awardLabel{color:#9fb0e0;}
  #tournamentResultsMenuBtn{padding:12px 32px; background:#001a15; border:2px solid var(--neon); border-radius:4px; color:var(--neon); font-family:'Orbitron',sans-serif; font-weight:700; letter-spacing:2px; text-transform:uppercase; cursor:pointer; width:260px; margin-top:6px; box-shadow:0 0 14px var(--neon-dim); transition:all 0.15s;}
  #tournamentResultsMenuBtn:hover{box-shadow:0 0 22px #00fff288; background:#002a22;}
  #pauseMenuBtn:hover, #pauseSettingsBtn:hover, #pauseVoiceBtn:hover, #pauseInfoBtn:hover{border-color:var(--neon); color:var(--neon); box-shadow:0 0 12px var(--neon-dim), inset 0 0 12px var(--neon-dim);}

  #killcamUI{position:fixed; inset:0; z-index:30; display:none; pointer-events:none;}
  /* ============ WHO KILLED YOU, AND WHAT THEY DID ============

     The killcam always knew both and said so in a line of small text under the
     banner: readable at a desk, invisible in a clip on a phone.

     TWO BANDS, not one block. The name sits at 26% and the kill line at 45%,
     each positioned on its own, so they cannot drift into each other however
     they are animated -- and neither sits over the ship the camera is
     following, which dead centre did.

     They also LEAVE, after about five seconds of a replay that runs longer.
     The name goes up and the line goes down, so on the way out they part
     rather than cross. The banner and the timer stay for the whole thing. */
  /* The upper third, both of them. The killcam holds its subject around the
     middle of the screen, so anything at 45% lands on the ship -- measured, and
     it did. These clear the arena entirely. */
  #killcamNameBand{
    position:absolute; top:18%; left:0; right:0;
    display:flex; align-items:center; justify-content:center;
    pointer-events:none; opacity:0;
  }
  #killcamKillBand{
    position:absolute; top:31%; left:0; right:0;
    display:flex; flex-direction:column; align-items:center; gap:6px;
    pointer-events:none; opacity:0;
  }
  #killcamKillerName{
    font-family:'Orbitron',sans-serif; font-weight:900;
    font-size:clamp(30px, 5.6vw, 84px); letter-spacing:5px; text-transform:uppercase;
    color:#ff4d6d; white-space:nowrap; text-align:center;
    text-shadow:0 0 18px #ff4d6daa, 0 0 48px #ff4d6d55, 0 3px 0 #00000099;
  }
  /* Who died. Quiet, in the interface's own monospace -- it is the sentence
     around the two proper nouns, not a third headline. */
  #killcamKillLine{
    font-family:ui-monospace, 'Courier New', monospace;
    font-size:clamp(12px, 1.5vw, 22px); letter-spacing:4px; text-transform:uppercase;
    color:#9fb0d8; white-space:nowrap; text-shadow:0 2px 0 #00000099;
  }
  #killcamKillLine b{ color:#ffffff; font-weight:700; }
  /* And what with. */
  #killcamKillWeapon{
    font-family:'Orbitron',sans-serif; font-weight:700;
    font-size:clamp(16px, 2.4vw, 36px); letter-spacing:4px; text-transform:uppercase;
    color:#ffd9e0; white-space:nowrap; text-shadow:0 0 14px #ff4d6d77, 0 2px 0 #00000099;
  }
  #killcamNameBand.final #killcamKillerName{
    color:#ffd166; text-shadow:0 0 18px #ffd166aa, 0 0 48px #ffd16655, 0 3px 0 #00000099;
  }
  #killcamKillBand.final #killcamKillWeapon{ color:#f3e2b4; text-shadow:0 0 14px #ffd16677, 0 2px 0 #00000099; }
  #killcamNameBand.env #killcamKillerName{
    color:#ff8c42; text-shadow:0 0 18px #ff8c42aa, 0 0 48px #ff8c4255, 0 3px 0 #00000099;
  }
  #killcamKillBand.env #killcamKillWeapon{ color:#ffd2b0; text-shadow:0 0 14px #ff8c4277, 0 2px 0 #00000099; }

  /* The shockwave the name arrives on. */
  #killcamNameRing{
    position:absolute; top:50%; left:50%; width:10px; height:10px;
    transform:translate(-50%,-50%); border-radius:50%;
    border:3px solid #ff4d6d; opacity:0;
  }
  #killcamNameBand.final #killcamNameRing{ border-color:#ffd166; }
  #killcamNameBand.env #killcamNameRing{ border-color:#ff8c42; }

  /* ---- the beats ----
     0.00 name slams in with the banner
     1.05 the kill line pushes up
     1.30 the weapon lands
     5.20 the name goes up, the line goes down
     A second longer held than the first cut of this, at BAWZ's asking. */
  /* ONE animation each, across the whole 5.75s. Two animations with `both`
     fill on the same element do not stack the way they read: the later one's
     backwards fill is already applying its from-state during its delay and
     overrides the earlier one, which had the kill line at full opacity from
     the first frame instead of waiting its beat. */
  #killcamNameBand.run{ animation:kcNameSeq 5.75s linear both; }
  #killcamKillBand.run{ animation:kcKillSeq 5.75s linear both; }
  #killcamKillWeapon.run{ animation:kcWeapSeq 5.75s linear both; }
  #killcamNameRing.run{ animation:kcRing 1.1s ease-out both; }

  @keyframes kcNameSeq{
    0%  { opacity:0; transform:scale(2.4) translateY(0); filter:blur(5px); }
    5%  { opacity:1; transform:scale(.94) translateY(0); filter:blur(0); }
    8%  { opacity:1; transform:scale(1) translateY(0); }
    90% { opacity:1; transform:scale(1) translateY(0); }
    100%{ opacity:0; transform:scale(1.08) translateY(-46px); }
  }
  @keyframes kcKillSeq{
    0%  { opacity:0; transform:translateY(16px); }
    18% { opacity:0; transform:translateY(16px); }
    25% { opacity:1; transform:translateY(0); }
    90% { opacity:1; transform:translateY(0); }
    100%{ opacity:0; transform:translateY(40px); }
  }
  /* A beat behind its own line, so the weapon lands rather than arriving with
     the sentence that introduces it. */
  @keyframes kcWeapSeq{
    0%  { opacity:0; transform:translateY(12px); }
    23% { opacity:0; transform:translateY(12px); }
    30% { opacity:1; transform:translateY(0); }
    100%{ opacity:1; transform:translateY(0); }
  }
  @keyframes kcRing{ 0%{ opacity:0; width:10px; height:10px; } 14%{ opacity:.85; }
                     100%{ opacity:0; width:70vw; height:70vw; } }

  /* Nothing here carries meaning that the text does not, so a viewer who asked
     for less motion simply gets it held and then gone. */
  @media (prefers-reduced-motion: reduce){
    .killcamBanner.strike, .killcamBarWrap.strike, #killcamNameRing.run{ animation:none; opacity:1; }
    #killcamNameBand.run, #killcamKillBand.run{ animation:kcPlain 5.75s linear both; }
    @keyframes kcPlain{ 0%{opacity:0} 3%{opacity:1} 90%{opacity:1} 100%{opacity:0} }
  }
  /* Three times the size it was. At 14px this was a caption; a killcam is the
     one moment the game asks to be looked at, and these are increasingly
     watched as clips rather than played. Padding and tracking grow with it so
     it stays a banner rather than becoming large text in a small box. */
  .killcamBanner{background:#0b1020cc; border:2px solid #ff4d6d; color:#ff4d6d; padding:14px 44px; border-radius:14px; font-weight:800; letter-spacing:9px; font-size:42px; text-shadow:0 0 10px #ff4d6d88, 0 0 30px #ff4d6d44;}
  /* The blow that ended the match, in the gold the rest of the game already
     uses for it -- same object, different state. */
  .killcamBanner.final{border-color:#ffd166; color:#ffd166; text-shadow:0 0 14px #ffd166cc, 0 0 42px #ffd16666;}
  .killcamBanner.env{border-color:#ff8c42; color:#ff8c42; text-shadow:0 0 14px #ff8c42cc, 0 0 42px #ff8c4266;}
  /* IGNITION. A tube fighting to strike: hard on, off, on, one last stutter,
     then steady. Stepped rather than eased, because this is a thing switching
     on and a fade would read as it merely appearing. */
  .killcamBanner.strike{animation:kcStrike .95s steps(1,end) both;}
  @keyframes kcStrike{
    0%{opacity:0} 8%{opacity:1} 14%{opacity:0} 22%{opacity:1} 28%{opacity:0}
    36%{opacity:.4} 42%{opacity:0} 58%{opacity:1} 66%{opacity:.25} 74%{opacity:1} 100%{opacity:1}
  }
  /* The duration bar snaps open once the banner has settled. */
  .killcamBarWrap.strike{animation:kcBarOpen .35s cubic-bezier(.2,1.3,.3,1) .9s both;}
  @keyframes kcBarOpen{ from{opacity:0; transform:scaleY(.15)} to{opacity:1; transform:scaleY(1)} }
  #killcamWeaponInfo{background:#0b1020cc; border:1px solid var(--line); color:var(--text); padding:5px 16px; border-radius:8px; font-size:12px; letter-spacing:1px; white-space:nowrap;}
  #killcamKillerStats{background:#0b1020cc; border:1px solid var(--line); color:#9fd8ff; padding:5px 16px; border-radius:8px; font-size:11px; letter-spacing:0.5px; max-width:520px; text-align:center;}
  #killcamWeaponInfo b{color:#ff8c42;}
  .killcamBarWrap{width:280px; height:5px; background:#00120fcc; border:1px solid var(--line); border-radius:3px; overflow:hidden;}
  #killcamBar{height:100%; width:0%; background:linear-gradient(90deg,#ff8c42,#ff4d6d);}
  /* OUT OF THE MIDDLE. It sat at bottom-centre, which is where the shield bar,
     the boost bar and the ammo dial all live -- so the one button a killcam
     offers was printed on top of the readouts the killcam exists to show.
     The left edge at mid-height is clear of everything: the kill feed occupies
     the top of that column and the squad chat the bottom, and the ships
     themselves are centre-frame. */
  #killcamSkipBtn{position:absolute; top:50%; left:26px; transform:translateY(-50%); pointer-events:auto; padding:10px 24px; background:#001a15; border:2px solid var(--neon); box-shadow:0 0 12px var(--neon-dim); border-radius:4px; color:var(--neon); font-weight:700; letter-spacing:1px; cursor:pointer;}
  #killcamSkipBtn:hover{filter:brightness(1.15);}

  #weaponSwapPrompt, #abilitySlotPrompt{display:none; pointer-events:none;}
  .weaponSwapBox{pointer-events:auto; background:#0b1020f2; border:1px solid #fb7185; border-radius:12px; padding:16px 22px; text-align:center; box-shadow:0 0 30px #0009; max-width:340px;}
  #weaponSwapText{color:#6dfcf1; font-size:13px; font-weight:700; letter-spacing:0.5px; margin-bottom:12px;}
  #abilitySlotText{color:#6dfcf1; font-size:13px; font-weight:700; letter-spacing:0.5px; margin-bottom:12px;}
  .weaponSwapBtnRow{display:flex; gap:8px; justify-content:center; flex-wrap:wrap;}
  .weaponSwapBtnRow button{padding:8px 16px; border-radius:8px; border:none; font-weight:700; letter-spacing:0.5px; cursor:pointer; font-size:11px;}
  #weaponSwapYes{background:#001a15; border:2px solid var(--neon); box-shadow:0 0 12px var(--neon-dim); color:var(--neon);}
  #weaponSwapYes:hover{filter:brightness(1.15);}
  #weaponSwapNo{background:#00120fcc; border:1px solid var(--line); color:var(--text);}
  #weaponSwapNo:hover{border-color:#3a4670;}
  #weaponSwapRandom, #abilitySlotRandom, #abilitySlotQBtn, #abilitySlotEBtn{background:#1a1425; border:1px solid #c084fc; color:#e0c8ff;}
  #weaponSwapRandom:hover, #abilitySlotRandom:hover{filter:brightness(1.2);}

  #practiceHint{display:none; position:absolute; top:18px; left:22px; background:#0b1020cc; border:1px solid var(--line); color:var(--dim); padding:5px 12px; border-radius:8px; font-size:11px; letter-spacing:1px; z-index:15;}

  #sandboxPanel{display:none; position:fixed; top:50%; left:22px; transform:translateY(-50%); width:250px; max-height:80vh; overflow-y:auto; background:#0b1020f2; border:1px solid var(--line); border-radius:12px; padding:16px; z-index:35; pointer-events:auto;}
  .sandboxTitle{font-size:12px; font-weight:800; letter-spacing:2px; color:#ffd166; margin-bottom:10px;}
  .sandboxSection{font-size:10px; color:var(--dim); letter-spacing:1px; margin:10px 0 6px;}
  .sandboxRow{display:flex; flex-wrap:wrap; gap:6px;}
  .sandboxBtn{padding:6px 10px; background:#00120fcc; border:1px solid var(--line); border-radius:6px; color:var(--text); font-size:11px; font-weight:700; letter-spacing:0.5px; cursor:pointer;}
  .sandboxBtn:hover{border-color:#6d7fff; color:#fff;}
  #sandboxResetBtn{width:100%; margin-top:14px; padding:9px; background:linear-gradient(90deg,#ff4d4d,#ff8c42); border:none; border-radius:8px; color:var(--neon); font-weight:700; letter-spacing:1px; cursor:pointer;}
  .sandboxToggle{display:flex; align-items:center; gap:8px; font-size:11.5px; color:var(--text); margin:6px 0; cursor:pointer;}
  #sandboxActivateBtn{width:100%; margin-top:8px; padding:9px; background:linear-gradient(90deg,#34d399,#22a8c9); border:none; border-radius:8px; color:#05070f; font-weight:800; letter-spacing:1px; cursor:pointer;}
  #sandboxSpawnAllyBtn{width:100%; margin-top:8px; padding:9px; background:linear-gradient(90deg,#ffd166,#ff8c42); border:none; border-radius:8px; color:#05070f; font-weight:800; letter-spacing:1px; cursor:pointer;}
  .sandboxHint{margin-top:10px; font-size:10px; color:var(--dim); text-align:center;}

  /* Holographic frame accent — four small pulsing corner brackets added on top
     of a panel's existing look. Doesn't touch that panel's own background,
     border, radius, or box-shadow in any way. */
  .holoTick{ position:absolute; width:12px; height:12px; pointer-events:none; z-index:5;
    border:2px solid #6df3ff; filter:drop-shadow(0 0 4px #6df3ffbb);
    animation: holoTickPulse 3s ease-in-out infinite; opacity:0.75; }
  .holoTick.tl{ top:3px; left:3px; border-right:none; border-bottom:none; }
  .holoTick.tr{ top:3px; right:3px; border-left:none; border-bottom:none; }
  .holoTick.bl{ bottom:3px; left:3px; border-right:none; border-top:none; }
  .holoTick.br{ bottom:3px; right:3px; border-left:none; border-top:none; }
  @keyframes holoTickPulse{ 0%,100%{opacity:0.45;} 50%{opacity:1;} }

  /* Build version, top-right of the main menu. Bump GAME_VERSION on every update. */
  #versionBadge{
    position:absolute; top:14px; right:18px; z-index:30;
    font-family:'Share Tech Mono', monospace; font-size:11px; letter-spacing:1.5px;
    color:var(--neon); opacity:0.8; pointer-events:none; text-align:right; line-height:1.5;
    text-shadow:0 0 8px var(--neon-dim);
  }
  #versionBadge .vbDate{ display:block; font-size:9.5px; color:var(--dim); letter-spacing:1px; text-shadow:none; }

  /* Dev status + bug/suggestion feedback panel, top-left of the main menu. */
  /* Four windows, not one column.

     The agenda is what is being built; the counters are how much has been
     built; reporting a bug is a form; joining in is a pair of links. They were
     stacked in one box, which made the eye do the sorting. Now each has a frame
     of its own, they share one gutter, and the two columns share a top edge. */
  /* The dev status is the only thing here that is a window. Everything under it
     already carries its own box, and a box inside a box reads as clutter rather
     than as structure. */
  #devStatusPanel{
    background:#0b1020cc; border:1px solid var(--line); border-radius:10px;
    padding:12px 14px; font-size:11px; color:var(--text);
    position:relative;                 /* holoTick corners anchor to this */
  }
  #devStack{
    display:flex; flex-direction:column; gap:8px;
    font-size:11px; color:var(--text);
  }
  /* Widths are set by the ring, not by taste: the ring is a circle 540px across
     in the middle of the window, and at 1440 its left edge reaches x=450 at the
     height these panels occupy. The whole left cluster therefore has to finish
     before that, or the feedback panel sits on top of the SETTINGS and HELP
     segments -- which is exactly what it did. */
  /* One column, so the stack below follows the agenda however tall it is --
     and it changes height every time the list is folded away. */
  #devColumn{
    position:absolute; top:18px; left:18px; width:216px; z-index:20;
    display:flex; flex-direction:column; gap:10px;
  }
  /* The right-hand pair, stacked. A column rather than two absolute boxes,
     because the counters panel changes height with the numbers in it and a
     hard-coded top for the one below it would drift the moment it did. */

  /* A control, not a caption: it opens and closes the list under it. */
  .devStatusTitle{
    display:flex; align-items:center; justify-content:space-between; gap:8px;
    width:100%; padding:0; margin:0; background:none; border:none; cursor:pointer;
    font-family:inherit; font-size:12px; font-weight:700; letter-spacing:1px;
    color:var(--neon); text-align:left;
  }
  .devStatusTitle:hover .devStatusChevron{ transform:translateY(1px); }
  #devStatusPanel.open .devStatusTitle:hover .devStatusChevron{ transform:rotate(180deg) translateY(1px); }
  .devStatusTitle:focus-visible{ outline:1px solid var(--neon); outline-offset:3px; }
  /* Inboard of the corner tick, and bright enough to read as a control -- at
     10px and 80% it was a speck sitting underneath the panel's own corner
     decoration, which is not an affordance, it is a smudge. */
  .devStatusChevron{
    transition:transform .28s cubic-bezier(.4,0,.2,1);
    font-size:13px; line-height:1; opacity:1; margin-right:6px;
    text-shadow:0 0 8px var(--neon);
  }
  #devStatusPanel.open .devStatusChevron{ transform:rotate(180deg); }
  /* Folded on max-height rather than height: the list is as tall as the agenda
     happens to be, and a fixed height would clip it or leave a gap under it. */
  #devStatusListWrap{
    max-height:0; overflow:hidden; opacity:0;
    transition:max-height .32s cubic-bezier(.4,0,.2,1), opacity .22s ease;
  }
  #devStatusPanel.open #devStatusListWrap{ max-height:460px; opacity:1; }
  #devStatusList{ list-style:none; margin:8px 0 0; padding:0; display:flex; flex-direction:column; gap:5px; }
  #devStatusList li{ display:flex; gap:6px; align-items:flex-start; line-height:1.4; color:var(--dim); }
  #devStatusList li .devIcon{ flex-shrink:0; }
  #devStatusList li .devTag{
    font-size:8.5px; letter-spacing:0.5px; padding:1px 5px; border-radius:4px; flex-shrink:0;
    text-transform:uppercase; font-weight:700; margin-top:1px;
  }
  #devStatusList li .devTag.progress{ background:#ffd16622; color:#ffd166; border:1px solid #ffd16655; }
  #devStatusList li .devTag.soon{ background:#4dd0ff22; color:#4dd0ff; border:1px solid #4dd0ff55; }
  .devFeedbackModeRow{ display:flex; gap:5px; margin:6px 0 8px; }
  .devFeedbackModeBtn{
    flex:1; font-size:10px; padding:6px 4px; background:#00120fcc; border:1px solid var(--line);
    color:var(--dim); border-radius:6px; cursor:pointer; letter-spacing:0.3px;
  }
  .devFeedbackModeBtn.active{ background:var(--neon-dim); color:var(--neon); border-color:var(--neon); }
  #devFeedbackText{
    width:100%; background:#00120fcc; border:1px solid var(--line); border-radius:6px; color:var(--text);
    font-family:inherit; font-size:10.5px; padding:7px 8px; resize:vertical; margin-bottom:6px; box-sizing:border-box;
  }
  #devFeedbackName{
    width:100%; background:#00120fcc; border:1px solid var(--line); border-radius:6px; color:var(--text);
    font-family:inherit; font-size:10.5px; padding:6px 8px; margin-bottom:6px; box-sizing:border-box;
  }
  #devFeedbackImgLabel{
    display:block; font-size:10px; color:var(--dim); cursor:pointer; margin-bottom:8px;
    padding:6px 8px; border:1px dashed var(--line); border-radius:6px; text-align:center;
  }
  #devFeedbackImgLabel:hover{ border-color:var(--neon); color:var(--neon); }
  #devFeedbackSubmitBtn{
    width:100%; padding:7px; background:linear-gradient(90deg,var(--neon),#22a8c9); border:none;
    border-radius:6px; color:#05070f; font-weight:800; letter-spacing:0.5px; cursor:pointer; font-size:11px;
  }
  #devFeedbackSubmitBtn:disabled{ opacity:0.5; cursor:default; }
  #devFeedbackStatus{ font-size:10px; margin-top:6px; text-align:center; min-height:13px; }
  #devStatusList li .devTag.done{ background:#3ddc7122; color:#3ddc71; border:1px solid #3ddc7155; }
  #devStatusList li .devText{ flex:1; }

  /* Compact "how long has this taken" strip under the agenda. */
  /* Two by two rather than one row of four. In a 180px column a single row
     gives each counter about 34px, which is narrower than the four-digit
     numbers they are there to show. */
  #devStatsRow{ display:grid; grid-template-columns:1fr 1fr; gap:6px; margin:0; }
  #devStatsRow .devStat{
    min-width:0; text-align:center; padding:5px 1px; border:1px solid var(--line);
    border-radius:6px; background:#00120fcc;
  }
  /* Four digits have to fit: these totals only grow, and `changes` reached 999
     legitimately rather than being clamped there. Tabular figures stop the four
     boxes jittering as the numbers tick over. */
  #devStatsRow .devStat b{
    display:block; font-size:12.5px; color:var(--neon); line-height:1.15;
    font-variant-numeric:tabular-nums; white-space:nowrap;
  }
  #devStatsRow .devStat span{ font-size:7.5px; letter-spacing:0.3px; text-transform:uppercase; color:var(--dim); }
  #devCalBtn{
    width:100%; padding:6px; margin-bottom:8px; background:#00120fcc; border:1px solid var(--line);
    color:var(--dim); border-radius:6px; cursor:pointer; font-family:inherit; font-size:10.5px; letter-spacing:0.4px;
  }
  #devCalBtn:hover{ border-color:var(--neon); color:var(--neon); }

  /* ---- Dev calendar overlay ---- */
  #devCalOverlay{
    display:none; position:fixed; inset:0; background:#000705ee; z-index:400;
    align-items:center; justify-content:center; padding:24px 16px;
  }
  #devCalCard{
    width:100%; max-width:880px; max-height:88vh; overflow-y:auto;
    background:#00120fee; border:1px solid var(--line); border-radius:14px; padding:20px 22px;
    color:var(--text);
  }
  #devCalHead{ display:flex; justify-content:space-between; align-items:flex-start; gap:14px; margin-bottom:14px; }
  #devCalTitle{ font-size:20px; font-weight:800; color:var(--neon); letter-spacing:0.5px; }
  #devCalSummary{ font-size:11.5px; color:var(--dim); margin-top:5px; line-height:1.55; max-width:600px; }
  #devCalSummary b{ color:var(--text); }
  #devCalCloseBtn{ padding:7px 14px; flex-shrink:0; }
  #devCalBody{ display:flex; gap:18px; align-items:flex-start; flex-wrap:wrap; }
  #devCalLeft{ flex:1 1 320px; min-width:290px; }
  #devCalMonths{ display:flex; gap:18px; flex-wrap:wrap; }
  .devCalMonth{ flex:1 1 150px; }
  .devCalMonthName{ font-size:11px; letter-spacing:1px; text-transform:uppercase; color:var(--neon); margin-bottom:6px; }
  .devCalGrid{ display:grid; grid-template-columns:repeat(7,1fr); gap:3px; }
  .devCalDow{ font-size:8.5px; text-align:center; color:var(--dim); opacity:0.7; padding-bottom:2px; }
  .devCalCell{
    aspect-ratio:1; display:flex; align-items:center; justify-content:center;
    font-size:10.5px; border-radius:4px; border:1px solid transparent; color:var(--dim);
  }
  .devCalCell.empty{ border:none; }
  .devCalCell.outside{ opacity:0.18; }
  /* The cell colour is set from the split in script, so no background or
     border-colour here -- only what does not depend on the day. */
  .devCalCell.worked{ color:#eafff8; cursor:pointer; font-weight:700; }
  .devCalCell.worked:hover{ border-color:#ffffffaa; }
  .devCalCell.off{ background:#ffffff08; border-color:#ffffff14; cursor:pointer; }
  .devCalCell.off:hover{ border-color:var(--dim); }
  .devCalCell.isStart{ box-shadow:0 0 0 1px #ffd166aa inset; }
  .devCalCell.selected{ outline:2px solid var(--neon); outline-offset:1px; }
  #devCalLegend{ display:flex; flex-wrap:wrap; gap:12px; align-items:center; margin-top:12px; font-size:10px; color:var(--dim); }
  #devCalLegend .swatch{ display:inline-block; width:10px; height:10px; border-radius:3px; margin-right:4px; vertical-align:-1px; }
  #devCalLegend .swatch.worked{ background:rgba(0,255,157,0.45); border:1px solid #00ff9d55; }
  #devCalLegend .swatch.mech{ background:rgba(77,208,255,0.68); border:1px solid rgba(77,208,255,0.7); }
  #devCalLegend .swatch.vis{  background:rgba(192,132,252,0.68); border:1px solid rgba(192,132,252,0.7); }
  #devCalLegend .swatch.even{ background:rgba(192,132,252,0.12); border:1px solid rgba(192,132,252,0.35); }
  .devCalLegendNote{ opacity:.65; font-style:italic; }
  #devCalLegend .swatch.off{ background:#ffffff08; border:1px solid #ffffff14; }
  #devCalLegend .devCalHint{ opacity:0.75; font-style:italic; }
  #devCalDetail{
    flex:1 1 260px; min-width:240px; background:#0b1020cc; border:1px solid var(--line);
    border-radius:10px; padding:14px 15px; align-self:stretch;
  }
  .devCalDetailDate{ font-size:13px; font-weight:800; color:var(--neon); margin-bottom:8px; }
  .devCalFirstTag{
    font-size:8px; letter-spacing:0.6px; padding:2px 5px; border-radius:4px;
    background:#ffd16622; color:#ffd166; border:1px solid #ffd16655; vertical-align:2px;
  }
  .devCalDetailStats{ display:flex; flex-wrap:wrap; gap:6px 14px; margin-bottom:9px; font-size:10.5px; color:var(--dim); }
  .devCalDetailStats b{ color:var(--text); font-size:13px; }
  #devCalNote{
    margin-top:12px; padding-top:10px; border-top:1px solid var(--line);
    font-size:9.5px; line-height:1.6; color:var(--dim); opacity:0.85;
  }
  #devCalNote b{ color:var(--text); opacity:0.95; }
  .devCalDetailText{ font-size:11.5px; line-height:1.65; color:var(--dim); }
  .devCalDetailOff{ font-size:11.5px; color:var(--dim); font-style:italic; opacity:0.8; }
  .devCalDetailTitle{ font-size:14.5px; font-weight:800; color:var(--text); margin:-3px 0 9px; line-height:1.35; }
  .devCalPoints{ margin:2px 0 0; padding:0 0 0 16px; font-size:11.5px; line-height:1.65; color:var(--dim); }
  .devCalPoints li{ margin-bottom:5px; }
  .devCalPoints li::marker{ color:var(--neon); }

  /* Two numbers that always sum to 100 read better as one divided bar than as
     two bars with a legend. */
  .devSplit{ margin:4px 0 11px; }
  .devSplitBar{ display:flex; height:7px; border-radius:4px; overflow:hidden; background:#0b1a22; }
  .devSplitMech{ background:#4dd0ff; }
  .devSplitVis{ background:#c084fc; }
  .devSplitKey{ display:flex; gap:14px; margin-top:5px; font-size:10px; color:var(--dim); letter-spacing:.03em; }
  .devSplitKeyMech b{ color:#4dd0ff; font-size:12px; }
  .devSplitKeyVis b{ color:#c084fc; font-size:12px; }
  .devSplitKeyMech::before, .devSplitKeyVis::before{
    content:''; display:inline-block; width:7px; height:7px; border-radius:2px; margin-right:5px; vertical-align:middle;
  }
  .devSplitKeyMech::before{ background:#4dd0ff; }
  .devSplitKeyVis::before{ background:#c084fc; }
  .devSplitNote{ font-size:10.5px; color:var(--dim); opacity:.75; margin-top:2px; }

  /* Bugs read as a fact about the day, so they sit with the other counts rather
     than being dressed up as a warning. */
  .devBugs{ display:flex; flex-wrap:wrap; gap:5px 13px; margin:0 0 9px; font-size:10.5px; color:var(--dim); }
  .devBugs b{ color:var(--text); font-size:12px; }
  .devBugsFound b{ color:#ff8c42; }
  .devBugsFixed b{ color:#3ddc71; }
  .devBugsWho{ opacity:.85; }
  .devBugSummary{ font-size:11.5px; color:var(--dim); margin-top:7px; line-height:1.55; }
  .devBugSummary b{ color:var(--text); }

  /* The form is closed until one of the two buttons is pressed.

     Animated on max-height rather than height, because the form's real height
     depends on the textarea and on whether a filename is showing, and a fixed
     height would either clip it or leave a gap under it. The ceiling is
     generous on purpose -- it only has to be larger than the form ever gets,
     and while closed it is zero either way. */
  #devFeedbackForm{
    max-height:0; overflow:hidden;
    transition:max-height .32s cubic-bezier(.4,0,.2,1), opacity .22s ease;
    opacity:0;
  }
  #devStack.open #devFeedbackForm{ max-height:420px; opacity:1; }
  #devFeedbackFormInner{ padding-top:9px; }
  /* The two buttons carry the whole panel when it is closed, so they get the
     room a resting state deserves rather than being squeezed above a form. */
  #devStack:not(.open) .devFeedbackModeRow{ margin-bottom:0; }
  @media (prefers-reduced-motion:reduce){
    #devFeedbackForm{ transition:none; }
  }

  /* Its own box now, standing beside the counters rather than inside a panel. */
  #devBugRow{
    display:flex; flex-direction:column; gap:3px; margin:0;
    font-size:10px; color:var(--dim); letter-spacing:.02em;
    border:1px solid var(--line); border-radius:6px; background:#00120fcc;
    padding:8px 10px;
  }
  /* Label left, number hard right, so the four numbers read as one column. */
  .devBugLine{ display:flex; align-items:baseline; justify-content:space-between; gap:8px; }
  #devBugRow b{ color:var(--text); font-size:11.5px; font-variant-numeric:tabular-nums; flex:0 0 auto; }
  .devBugRowLabel{ text-transform:uppercase; letter-spacing:.1em; opacity:.7; }
  .devBugRowWho{ opacity:.85; }

  @media (max-width:900px){ #devColumn{ display:none; } }

/* ===== MENU BACKDROP =====================================================
   The main menu plays a real match behind its panels as a backdrop. These are
   the overlays that belong to a PLAYER rather than to the world, so while the
   backdrop is running they are hidden: a visitor who has not pressed Deploy has
   no loadout, no squad and nobody to spectate, and showing all of it at once is
   what made the first screen feel like a wall of text.

   The class is set only while menuArenaLiveMode is true, which the deploy path
   clears before any real match starts -- so this never reaches actual play.
   !important is deliberate: several of these panels have their display set
   inline by the HUD code, and without it they would fight back mid-frame.     */
body.menuBackdrop #statsPanel,          /* loadout / stat list        */
body.menuBackdrop #squadInfoPanel,      /* squad list                 */
/* The caption only, not the bubbles -- backdrop chatter is part of the menu
   already, but a "Squad Chat" heading over an empty corner is not. */
body.menuBackdrop #squadChatLabel,      /* squad chat caption         */
body.menuBackdrop #killFeed,            /* kill feed                  */
body.menuBackdrop #playerSearchPanel,   /* spectate search + the mothership and football buttons */
body.menuBackdrop #spectateHelpPanel,   /* spectate key hints         */
body.menuBackdrop #squadLootFloatWrap,  /* squad pickup toasts        */
/* The whole top-centre stack. Persistent readouts first -- score, ring timer
   and the mode status bars -- none of which mean anything before you deploy. */
body.menuBackdrop #scoreDisplay,        /* SCORE / KILLS / ASSISTS    */
body.menuBackdrop #ringTimerDisplay,    /* battle-royale ring timer   */
body.menuBackdrop #ffaStatusBar,        /* FFA stage status           */
body.menuBackdrop #teamStatus,          /* team score row             */
/* ...then the big green name of whoever the backdrop camera is following, and
   its subtitle. This is the largest text on the screen after the logo and it
   names an AI ship the visitor has no connection to. */
body.menuBackdrop #spectateNameDisplay,
body.menuBackdrop #spectateSubDisplay,
body.menuBackdrop #directorTag,
/* ...and the transient popups that share that space. They are the same wall of
   text arriving a line at a time, which is worse on a menu than all at once. */
body.menuBackdrop #toastStack,
body.menuBackdrop #threatWarnings,
body.menuBackdrop #asteroidFindBanner,
body.menuBackdrop #pickupCard,          /* what to press, for a pilot who is not there */
/* ...and the rest of that same strip: the big centred announcement, the whole
   killcam replay overlay (banner, weapon line, killer stats and its timer bar),
   and the two pickup prompts. All of them address a pilot, and on the menu
   there is no pilot -- an announcement nobody can act on and a killcam of a
   death that is not yours are the loudest things on a screen that is meant to
   be a backdrop. Hiding killcamUI at the parent takes its children with it. */
body.menuBackdrop #centerMsg,
body.menuBackdrop #killcamUI,
body.menuBackdrop #weaponSwapPrompt,
body.menuBackdrop #abilitySlotPrompt,
/* ...and the bottom row: ability slots, ammo, shield and boost. These belong to
   a ship the visitor is not flying, so they are readouts of nothing. Hiding the
   row rather than its parts keeps the group intact -- boost sits in the same
   row and would look orphaned left on its own. */
body.menuBackdrop #bottomHudRow,
body.menuBackdrop #chargeBarWrap
{ display: none !important; }

/* Squad chatter stays. It is the one overlay that makes the backdrop feel alive
   rather than like a screensaver, and it sits down the side rather than across
   the top, so it is not part of the clutter the rest of this rule removes. */


/* ===== RING MENU ==========================================================
   The ring is a view over the original controls, which stay in the DOM inside
   these wrappers so their listeners keep firing when a segment forwards a
   click. display:none is deliberate -- a hidden element still responds to
   .click(), and hiding the wrapper means the campaign branch toggling
   #shapeRow's own display cannot bring it back visually.                    */
#legacyPrimary, #legacyModes{ display:none; }

#ringMenu{
  position:relative; width:min(62vh, 540px); aspect-ratio:1;
  margin:calc(50vh - min(31vh, 270px) - 40px) auto 18px; flex:0 0 auto;
}
#ringArt{ position:absolute; inset:0; width:100%; height:100%; overflow:visible; }
.ringSeg{ cursor:pointer; transition:fill-opacity .18s, stroke-opacity .18s, filter .2s; }
.ringSeg:hover{ fill-opacity:.34; stroke-opacity:1; }
.ringSeg.ringLocked{ cursor:not-allowed; }
.ringSeg.ringLocked:hover{ fill-opacity:.06; }
.ringArt, .ringLbl{ pointer-events:none; }
/* Labels that only exist while you are pointing at their puck.

   The icons carry the meaning; the word is confirmation, so it arrives when it
   is asked for and leaves again. It comes in brighter and wider than a standing
   label would -- a thing that appears has to look deliberate, or it reads as a
   rendering fault. */
/* Two ids on purpose.

   The reveal that un-hides a section does `#ringMenu[data-stage=ready]
   [data-sec=squad] { opacity:1 }`, which has exactly the same weight as one id
   and two classes and sits later in the file -- so it was winning, and Join Room
   and Create Room kept their labels while the other four lost theirs. */
#ringMenu #ringDial .ringItemLbl.onHover{
  /* The reveal animates a whole section awake with `animation: ... both`, and an
     animated value outranks every normal declaration whatever its specificity --
     so the fill kept these labels at opacity 1 no matter what was written here.
     Opting out of the animation is what lets the rule below apply at all; the
     puck and the icon still wake normally, and a label nobody is pointing at has
     nothing to wake up for. */
  animation:none;
  opacity:0;
  letter-spacing:.28px;
  transition:opacity .16s ease, letter-spacing .22s cubic-bezier(.2,.8,.3,1);
}
#ringMenu #ringDial .ringItem:hover .ringItemLbl.onHover{
  opacity:1;
  letter-spacing:.12px;
  fill:#31e6d6;
  filter:drop-shadow(0 0 3px #31e6d6) drop-shadow(0 0 7px #31e6d688);
}
/* The locked one says so, and does not pretend to be pressable. */
#ringDial .ringItem.ringItemLocked .ringSeg{ cursor:not-allowed; }
@media (prefers-reduced-motion:reduce){
  #ringMenu #ringDial .ringItemLbl.onHover{ transition:opacity .16s ease; letter-spacing:.12px; }
}
.ringArt{ transition:filter .2s, opacity .2s; }
/* The value comes from the script; the reveal below can still override it,
   which an inline opacity would not allow. */
#ringMenu .ringArt{ opacity:var(--artOpacity, .76); }
#ringDial text{ font-family:'Share Tech Mono', ui-monospace, monospace; font-weight:700; }

/* The logo: the artwork as drawn, with an edge feather instead of a frame. The
   mask must reach transparent BEFORE the box edge, which is why it is
   closest-side -- a percentage ellipse puts the transparent stop outside the
   box on the flat sides and every edge stays a hard cut. */
#ringCrest{ position:absolute; left:50%; top:-28%; transform:translateX(-50%); z-index:6; line-height:0; }
#ringLogo{
  display:block; width:clamp(88px, 11vw, 118px); height:auto;
  -webkit-mask-image:radial-gradient(circle closest-side at 50% 50%, #000 52%, rgba(0,0,0,.9) 70%, rgba(0,0,0,.4) 87%, transparent 100%);
  mask-image:radial-gradient(circle closest-side at 50% 50%, #000 52%, rgba(0,0,0,.9) 70%, rgba(0,0,0,.4) 87%, transparent 100%);
}

/* Orbit puts both of these inside the core: the two things that are not
   choices sit together in the middle, and the tracks around them carry
   only things you pick. */
#ringNameSlot{ position:absolute; left:50%; top:44%; transform:translate(-50%,-50%); z-index:5; }
/* The name field, built as a readout rather than as a form input.

   The slot is the frame -- clipped corners in the same language as the panels,
   a hairline rule under the text and a tick at each end -- and the input itself
   is transparent inside it. A bordered rounded rectangle is the one shape on
   this screen that says "web form", which is exactly what it should not say. */
#ringNameSlot{
  --nameW:min(176px, 28vh);
  width:var(--nameW); padding:6px 14px 5px;
  background:linear-gradient(180deg, #08131c 0%, #050b12 100%);
  border:1px solid #2c6273;
  clip-path:polygon(8px 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%, 0 8px);
  box-shadow:inset 0 0 18px #31e6d611;
  transition:border-color .2s ease, box-shadow .2s ease;
}
/* The rule under the text, which brightens when the field is being used. */
#ringNameSlot::after{
  content:''; position:absolute; left:12px; right:12px; bottom:4px; height:1px;
  background:#2c6273; opacity:.8; transition:background .2s ease, opacity .2s ease;
}
#ringNameSlot:focus-within{ border-color:#31e6d6; box-shadow:inset 0 0 20px #31e6d622, 0 0 16px #31e6d633; }
#ringNameSlot:focus-within::after{ background:#31e6d6; opacity:1; }

#ringNameSlot #playerNameInput{
  width:100%; background:transparent; border:none; border-radius:0;
  padding:2px 0 3px; color:#dff4f2; font-family:'Orbitron',sans-serif;
  font-size:12px; font-weight:700; letter-spacing:.16em; text-transform:uppercase;
  text-align:center; flex:none;
}
#ringNameSlot #playerNameInput::placeholder{
  color:#4e7481; letter-spacing:.16em; font-weight:500; text-transform:uppercase;
}
#ringNameSlot #playerNameInput:focus{ outline:none; }

/* Chrome paints autofilled fields with its own near-white background and will
   not be talked out of it by `background` alone -- which is why this read as a
   white pill the moment a remembered name went in. An inset shadow the size of
   the box is the one thing that covers it. */
#ringNameSlot #playerNameInput:-webkit-autofill,
#ringNameSlot #playerNameInput:-webkit-autofill:hover,
#ringNameSlot #playerNameInput:-webkit-autofill:focus,
#ringNameSlot #playerNameInput:-webkit-autofill:active{
  -webkit-text-fill-color:#dff4f2;
  -webkit-box-shadow:0 0 0 1000px #060d15 inset;
  caret-color:#31e6d6;
  transition:background-color 9999s ease-in-out 0s;
}

#ringBonus{
  position:absolute; left:100%; top:60%; transform:translate(-50%,0); z-index:5;
  width:26%; text-align:center; pointer-events:none; opacity:0;
  transition:opacity .25s, left .3s cubic-bezier(.22,.9,.3,1), top .3s cubic-bezier(.22,.9,.3,1);
  font-size:11px; line-height:1.5; overflow-wrap:break-word;
}
#ringBonus.on{ opacity:1; }
#ringBonus b{ display:block; font-size:13px; letter-spacing:.08em; margin-bottom:2px; }

#ringHolo{
  position:absolute; left:100%; top:37%; transform:translate(-50%,-50%); z-index:5;
  width:20%; aspect-ratio:1; pointer-events:none; opacity:0;
  transition:opacity .22s, left .3s cubic-bezier(.22,.9,.3,1), top .3s cubic-bezier(.22,.9,.3,1);
}
#ringHolo.on{ opacity:1; animation:ringHoloFloat 3.4s ease-in-out infinite; }

/* The stat line under the preview puts the hull name on its own line, which
   leaves the separator stranded at the start of the next one. */
#ringBonus .statSep{ display:none; }
/* The qualifier gets its own line, quieter than the bonus it qualifies. */
#ringBonus .statNote{ display:block; font-size:9px; opacity:.72; margin-top:2px; }

/* The hull turns on the spot. A flat rotation is the right one here: the game
   is seen from above, so a ship turning about its own vertical axis IS a 2D
   rotation on screen. */
#ringHolo .holoSpin{
  transform-box:fill-box; transform-origin:50% 50%;
  animation:holoSpin 9s linear infinite;
}
@keyframes holoSpin{ from{ transform:rotate(0deg); } to{ transform:rotate(360deg); } }

/* And it drops out now and then. Almost all of the timeline is the resting
   state -- a glitch that is always happening is not a glitch, it is a texture. */
#ringHolo .holoGlitch{
  transform-box:fill-box; transform-origin:50% 50%;
  animation:holoGlitch 7.3s steps(1, end) infinite;
}
@keyframes holoGlitch{
  0%, 88%, 100%{ transform:translate(0,0) skewX(0deg); opacity:1; }
  89%{ transform:translate(2px,-1px) skewX(7deg); opacity:.65; }
  90%{ transform:translate(-3px,1px) skewX(-5deg); opacity:.9; }
  91%{ transform:translate(1px,0) skewX(2deg); opacity:.5; }
  92%{ transform:translate(0,0) skewX(0deg); opacity:1; }
  96%{ transform:translate(-1px,0) skewX(-3deg); opacity:.8; }
  97%{ transform:translate(0,0) skewX(0deg); opacity:1; }
}

/* The colour-split copies. Invisible except during the same frames. */
#ringHolo .holoGhost{ opacity:0; transform-box:fill-box; transform-origin:50% 50%; }
#ringHolo .holoGhost{ animation:holoGhostA 7.3s steps(1, end) infinite; }
#ringHolo .holoGhost2{ animation-name:holoGhostB; }
@keyframes holoGhostA{
  0%, 88%, 92%, 100%{ opacity:0; transform:translate(0,0); }
  89%, 91%{ opacity:.55; transform:translate(3px,0); }
  96%{ opacity:.4; transform:translate(2px,0); }
  97%{ opacity:0; transform:translate(0,0); }
}
@keyframes holoGhostB{
  0%, 88%, 92%, 100%{ opacity:0; transform:translate(0,0); }
  89%, 91%{ opacity:.55; transform:translate(-3px,0); }
  96%{ opacity:.4; transform:translate(-2px,0); }
  97%{ opacity:0; transform:translate(0,0); }
}

@media (prefers-reduced-motion:reduce){
  #ringHolo .holoSpin, #ringHolo .holoGlitch, #ringHolo .holoGhost{ animation:none; }
  #ringHolo .holoGhost{ opacity:0; }
}
#ringHolo svg{ width:100%; height:100%; overflow:visible; }
@keyframes ringHoloFloat{
  0%,100%{ transform:translate(-50%,-50%) translateY(-3px); }
  50%    { transform:translate(-50%,-50%) translateY(3px); }
}

/* Deploy is the real #startBtn, moved here. It stays grey and unusable until
   checkReady() enables it, and greens the moment it does -- the colour is
   driven by :disabled rather than by a class this file would have to maintain. */
#ringDeploySlot{ position:absolute; left:50%; top:60%; transform:translate(-50%,-50%); z-index:6; }
#ringDeploySlot #startBtn{
  width:clamp(74px, 14vh, 104px); height:clamp(74px, 14vh, 104px); border-radius:50%;
  padding:0; margin:0; display:flex; align-items:center; justify-content:center; text-align:center;
  font-size:clamp(11px, 1.5vh, 14px); letter-spacing:.14em; line-height:1;
  background:radial-gradient(circle at 50% 34%, #16283d, #060f1a 72%);
  border:2px solid #2c4a6e; color:#7f97b5; box-shadow:0 0 14px #22d3ee22;
  transition:.22s;
}
#ringDeploySlot #startBtn:not(:disabled){
  background:radial-gradient(circle at 50% 34%, #1c5e3a, #06180f 72%);
  border-color:#3ddc71; color:#e8fff2; cursor:pointer;
  box-shadow:0 0 30px #3ddc7188, inset 0 0 24px #3ddc7126;
}
#ringDeploySlot #startBtn:not(:disabled):hover{
  transform:scale(1.06); box-shadow:0 0 48px #3ddc71cc, inset 0 0 28px #3ddc7133;
}
@media (prefers-reduced-motion:reduce){ #ringHolo.on{ animation:none; } }

/* checkReady() writes real sentences into Deploy -- "Waiting for squad to pick
   ships…", "Pick your ship above" -- which a ~90px circle cannot hold. Rather
   than change checkReady (whose multiplayer branches are load-bearing), the
   button keeps the single word and the sentence is mirrored to this line. */
/* Inside the core, below the Deploy button rather than above the name field.

   They were sitting between the wordmark and the name box -- the busiest part
   of the core, and directly over the thing they are asking you to fill in. The
   band between Deploy and its status line was empty, and the chips read better
   as a footer under the button than as a header over the form. */
#ringChips{
  position:absolute; left:50%; top:73%; transform:translate(-50%,-50%); z-index:6;
  display:flex; gap:5px; pointer-events:none;
}
.ringChip{
  font-size:8px; letter-spacing:.14em; text-transform:uppercase; padding:3px 8px;
  border:1px solid #26405f; border-radius:20px; color:#54708f; background:#00121acc;
  transition:.2s; white-space:nowrap;
}
.ringChip.on{ border-color:#3ddc71; color:#3ddc71; background:#0a1c13cc; box-shadow:0 0 10px -3px #3ddc71; }

#ringDeployStatus{
  position:absolute; left:50%; top:86%; transform:translate(-50%,-50%); z-index:6;
  font-size:10px; letter-spacing:.1em; color:var(--dim); text-align:center;
  white-space:nowrap; pointer-events:none; max-width:46%; overflow:hidden; text-overflow:ellipsis;
}

/* The scoreboard shares .menuCard with the main menu, so it lost its panel when
   the fill was removed -- and unlike the ring it is dense text that needs one. */
#scoreboardView{
  background:var(--panel); border:1px solid var(--line); border-radius:18px;
  padding:30px 34px; box-shadow:0 0 60px #000a;
}
/* Same for the option panels below the ring: readable over a moving starfield
   without putting a sheet back across the aperture. */
#menuMainView > .label,
#menuMainView > .sliderRow,
#menuMainView > .row,
#menuMainView > #tournamentOptions,
#menuMainView > #team20TagsPanel,
#menuMainView > #nameShipsPanel,
#menuMainView > #controlsPanel,
#menuMainView > #campaignNote,
#menuMainView > #modeLockedHint{
  background:#001210cc; border-radius:10px; padding:8px 14px; margin-left:auto; margin-right:auto;
  max-width:620px;
}
#menuMainView > .label{ padding:6px 14px; }


/* ===================== MENU REDESIGN =====================
   The wordmark above the ring, the logo pinned over the dev panel, the setup
   sections revealing one group at a time, and the terminal that talks you
   through it. */

/* The crest above the ring is the wordmark now. It reuses the shift that the
   pre-ring menu title had, so the shine is the same one the game always had
   rather than a new effect invented for the occasion. */
#ringCrest{ width:max-content; }
#ringTitle{
  font-family:'Orbitron',sans-serif;
  font-size:clamp(26px, 3.4vw, 46px); font-weight:900; letter-spacing:.18em;
  white-space:nowrap; line-height:1;
  background:linear-gradient(90deg,#3d1f7a,#6d3dff,#c0a0ff,#6d3dff,#3d1f7a);
  background-size:300% 100%;
  -webkit-background-clip:text; background-clip:text; color:transparent;
  text-shadow:0 0 24px #6d3dff88;
  animation:titleNebulaShift 6s ease-in-out infinite;
}
@media (prefers-reduced-motion:reduce){ #ringTitle{ animation:none; } }

/* The byline sits under the wordmark and stays quiet.

   Deliberately not a second shining thing: two animated gradients stacked read
   as a mistake rather than as a credit. It borrows the wordmark's letter
   spacing so the two look like one lockup, and its width is capped to the
   crest so it can never grow wider than the name above it. */
/* One word in the credit is orange. Applied to the word rather than to the
   whole line so the line still reads as one credit -- the colour is a signature,
   not a highlight. */
.bylineClaude{ color:#ff9a3d; text-shadow:0 0 10px #ff9a3d55; }

#ringByline{
  /* #ringCrest sets line-height:0 so the wordmark sits tight against the ring.
     Anything else placed in there inherits it and collapses to nothing -- the
     text still paints, but it occupies no height, so whatever follows lands on
     top of it. Both children of the crest have to state their own. */
  line-height:1.25;
  font-family:'Orbitron',sans-serif;
  font-size:clamp(8px, .78vw, 11px); font-weight:600; letter-spacing:.34em;
  text-transform:uppercase; white-space:nowrap;
  color:#9a86d8; opacity:.72;
  margin-top:7px; text-align:center;
  text-shadow:0 0 10px #6d3dff44;
}

/* ------------------------------------------------------------ white rabbit --

   It is on screen from the first visit now: sitting in the name box, then above
   SHIPS, then under BATTLE MODE, moving as each step is done. Once everything is
   chosen it eats and bolts.

   Position animates on left/top rather than on a transform, because the perches
   are real elements whose screen positions are only knowable in page
   coordinates. The bounce is a separate transform animation on the same
   element, so the two never fight: the transition moves it, the keyframes give
   it the arc. */
#whiteRabbit{
  position:fixed;
  /* The drawing is 4:3 now, not square. */
  width:var(--size, 34px); height:calc(var(--size, 34px) * .75);
  z-index:22;                     /* above the panels, so it can sit on them */
  pointer-events:auto; cursor:pointer;
  opacity:0;
  /* One bound takes 300ms and then it stands still. The old three-quarters of a
     second of easing across the whole distance is what made it look like it was
     flying rather than hopping. */
  transition:left .3s cubic-bezier(.16,.72,.24,1),
             top  .3s cubic-bezier(.16,.72,.24,1),
             width .25s ease, height .25s ease, opacity .3s ease;
  filter:drop-shadow(0 0 8px #ffffffaa) drop-shadow(0 0 18px #6d3dff66);
}
#whiteRabbit svg{ width:100%; height:100%; display:block; }

/* One bound: gather, push off, a low fast arc, and a landing that squashes.

   --rx is 1 when it is travelling left, -1 when travelling right. The tilt is
   what sells it -- nose up leaving the ground, nose down coming back to it. */
#whiteRabbit.bounding{ animation:rabbitBound .3s cubic-bezier(.3,.6,.4,1); }
@keyframes rabbitBound{
  0%   { transform:scaleX(var(--rx,1)) translateY(2px)  scaleY(.82) scaleX(1.06) rotate(0deg); }
  18%  { transform:scaleX(var(--rx,1)) translateY(-15px) scaleY(1.12) rotate(-9deg); }
  55%  { transform:scaleX(var(--rx,1)) translateY(-18px) scaleY(1.04) rotate(2deg); }
  82%  { transform:scaleX(var(--rx,1)) translateY(-4px) scaleY(.98) rotate(7deg); }
  100% { transform:scaleX(var(--rx,1)) translateY(0)    scaleY(.9) scaleX(1.04) rotate(0deg); }
}

/* Clicked. Everything gets quicker and it stops looking comfortable. */
#whiteRabbit.panicking{
  transition:left .22s cubic-bezier(.2,.7,.3,1), top .22s cubic-bezier(.2,.7,.3,1),
             width .2s ease, height .2s ease, opacity .2s ease;
  filter:drop-shadow(0 0 10px #fff) drop-shadow(0 0 22px #ff5470aa);
}
#whiteRabbit.panicking.bounding{ animation:rabbitBound .22s cubic-bezier(.3,.6,.4,1); }

/* The last leg. Below the menu chrome, so the Kickstarter button covers it as
   it arrives -- it goes behind the button rather than fading out over it. */
#whiteRabbit.hiding{ z-index:3; }

/* Head-down, then up. Three of these and the carrot is gone. */
#whiteRabbit.nibble{ animation:rabbitNibble .8s ease-in-out; }
@keyframes rabbitNibble{
  0%,100%{ transform:scaleX(var(--rx,1)) translateY(0) rotate(0deg); }
  20%    { transform:scaleX(var(--rx,1)) translateY(3px) rotate(8deg); }
  45%    { transform:scaleX(var(--rx,1)) translateY(0) rotate(0deg); }
  65%    { transform:scaleX(var(--rx,1)) translateY(3px) rotate(8deg); }
}

/* Down the hole. */
#whiteRabbit.gone{
  opacity:0;
  transform:scale(.3) translateY(12px);
  transition:opacity .5s ease .12s, transform .6s cubic-bezier(.5,0,.75,0);
}

/* The carrot, disappearing a third at a time. */
#rabbitCarrot{
  position:fixed; width:30px; height:30px; z-index:21; pointer-events:none;
  filter:drop-shadow(0 0 8px #ff8c4288);
  transition:clip-path .4s ease, opacity .4s ease;
  clip-path:inset(calc(var(--eaten, 0) * 100%) 0 0 0);
  opacity:calc(1 - var(--eaten, 0) * .35);
}
#rabbitCarrot svg{ width:100%; height:100%; display:block; }

@media (prefers-reduced-motion:reduce){
  #whiteRabbit{ transition:opacity .3s ease; }
  #whiteRabbit.bounding, #whiteRabbit.nibble{ animation:none; }
}

/* Join-us links under the dev panel.

   Built as one pair rather than two separate buttons: equal width, one gap, the
   same height and the same corner cut, so the row reads as a single control
   instead of two things that happen to sit near each other. The icons are
   geometric glyphs rather than brand marks -- a real Discord or Kickstarter
   logo would be the only non-ShapeWars shape on the screen. */
/* Under the wordmark now, not in the dev panel. Width is capped so the pair
   cannot grow wider than the name above them, which is what kept the old lockup
   looking deliberate. */
#devLinksRow{
  max-width:268px; margin-left:auto; margin-right:auto;
  display:grid;
  /* minmax(0,1fr), not 1fr: a plain 1fr will not shrink a column below its
     longest word, so "KICKSTARTER" made its half wider than "DISCORD" and the
     pair stopped reading as one control. */
  grid-template-columns:repeat(2, minmax(0, 1fr)); gap:8px;
  margin-top:14px;
}
.devLinkBtn{
  line-height:1.1;                    /* see #ringByline: the crest zeroes it */
  display:flex; align-items:center; justify-content:center; gap:5px;
  padding:9px 4px; min-height:34px;
  /* Sized so the longer of the two labels fits whole. An ellipsis here would
     be the one truncated word on the screen, which is worse than a slightly
     smaller label. */
  font-family:'Orbitron',sans-serif; font-size:8.5px; font-weight:700;
  letter-spacing:.05em; text-transform:uppercase; text-decoration:none;
  color:#b8fff2; background:#00120f88;
  border:1px solid var(--line);
  /* The same clipped corner the rest of the panel chrome uses. */
  clip-path:polygon(7px 0, 100% 0, 100% calc(100% - 7px), calc(100% - 7px) 100%, 0 100%, 0 7px);
  transition:background .18s ease, border-color .18s ease, color .18s ease, transform .18s ease;
}
.devLinkBtn .devLinkIcon{ font-size:11px; line-height:1; opacity:.9; flex:0 0 auto; }
/* Lets the grid column actually take the width it is given. */
.devLinkBtn{ min-width:0; }
.devLinkBtn .devLinkText{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.devLinkBtn:hover{ transform:translateY(-1px); }
.devLinkBtn.discord:hover{ border-color:#7d8cff; color:#dfe4ff; background:#141a3a99; }
.devLinkBtn.kickstarter:hover{ border-color:#3ddc71; color:#d8ffe8; background:#0a241899; }
.devLinkBtn:focus-visible{ outline:2px solid var(--neon); outline-offset:2px; }
/* A link with nowhere to go should look like one, not like a dead button. */
.devLinkBtn.pending{ opacity:.55; cursor:default; }
.devLinkBtn.pending:hover{ transform:none; }

/* The logo, pinned above the dev panel like a badge nailed to the corner. */
/* devStatusPin lives above the panel, so the panel starts lower down to leave
   room for it -- pinning it at a negative offset put it off the top edge. */
#devStatusPin{
  position:absolute; top:10px; left:18px; width:216px; z-index:21;
  display:flex; justify-content:center; pointer-events:none;
}
/* The logo is pinned over the column, so the column starts below it. */
#devColumn{ top:96px; }
#devStatusLogo{
  display:block; width:76px; height:auto;
  filter:drop-shadow(0 0 14px #6d3dff66);
  -webkit-mask-image:radial-gradient(circle closest-side at 50% 50%, #000 52%, rgba(0,0,0,.9) 70%, rgba(0,0,0,.4) 87%, transparent 100%);
  mask-image:radial-gradient(circle closest-side at 50% 50%, #000 52%, rgba(0,0,0,.9) 70%, rgba(0,0,0,.4) 87%, transparent 100%);
}
@media (max-width:900px){ #devStatusPin{ display:none; } }

/* ---- the setup reveals itself one step at a time ----
   Hidden by opacity and pointer-events rather than by display, because the ring
   is one SVG whose geometry is laid out for five sections: removing a section
   would re-flow the whole wheel on every keystroke. This way the arcs simply
   are not there until they are earned, and the ring never moves. */
#ringMenu [data-sec="ship"],
#ringMenu [data-sec="mode"],
#ringMenu [data-sec="squad"]{
  opacity:0; pointer-events:none;
  transition:opacity .5s ease;
}
#ringMenu[data-stage="ship"] [data-sec="ship"],
#ringMenu[data-stage="ready"] [data-sec="ship"],
#ringMenu[data-stage="ready"] [data-sec="mode"],
#ringMenu[data-stage="ready"] [data-sec="squad"]{
  opacity:1; pointer-events:auto;
  animation:ringSectionWake .75s ease-out both;
}
@keyframes ringSectionWake{
  0%{ opacity:0; filter:brightness(3) blur(2px); }
  55%{ opacity:1; filter:brightness(1.8) blur(0); }
  100%{ opacity:1; filter:none; }
}
@media (prefers-reduced-motion:reduce){
  #ringMenu [data-sec]{ animation:none !important; transition:none; }
}

/* SHIPS, MORE, BATTLE MODE and SQUAD ROOM, lit like tubes.

   A neon sign is a white-hot core inside a coloured bloom -- not coloured text
   -- so the fill goes almost white and every bit of the colour comes from the
   glow around it. All four take the Orbit teal rather than their own section
   colours, which is the same decision the pucks made: one colour for the
   chrome, colour only in the icons.

   Drawn with text-shadow rather than a filter on purpose. The section wake
   animates `filter` and lands on `filter:none` with a `both` fill, so an
   animated value sits on that property for the rest of the session and a
   drop-shadow set here would never appear. text-shadow is untouched by it. */
#ringMenu #ringDial .ringHead{
  fill:#dafffb;
  text-shadow:
    0 0 1px #8ff8ec,
    0 0 4px #31e6d6dd,
    0 0 11px #31e6d699,
    0 0 24px #31e6d655;
}

/* ===================== THE TERMINAL =====================
   Green phosphor on black: the visitor is talked through setup by something
   that reads like a machine on the other end of a line, which is a friendlier
   way to explain a five-part ring than a wall of labels would be. */
/* Pushed down clear of the build badge and the volume control, which live in
   the same corner and were being covered. */
#matrixTerm{
  /* Pulled in toward the menu on a wide screen: out at the window edge it read
     as a separate widget rather than as the thing talking to you about the ring
     right next to it. Never closer to the edge than 18px on a narrow one. */
  position:absolute; top:112px; right:max(18px, calc(50% - 690px));
  width:330px; max-height:min(56vh, 500px);
  z-index:20; display:flex; flex-direction:column;
  /* Plain black, so the only colour in the box is the phosphor itself. The
     curved corners of a tube, a faint bloom of its own light on the glass, and
     nothing else. */
  background:#000; border:1px solid #00ff4128; border-radius:14px;
  box-shadow:0 0 26px #000a, 0 0 60px #00ff410f, inset 0 0 90px #00ff4108;
  font-family:'Share Tech Mono', ui-monospace, monospace;
  overflow:hidden;
}
#matrixTermBar{
  display:flex; align-items:center; gap:6px;
  padding:7px 10px; border-bottom:1px solid #00ff411f;
  background:#000;
}
.mtDot{ width:8px; height:8px; border-radius:50%; background:#00ff4133; flex:none; }
.mtDot:first-child{ background:#00ff4166; }
#matrixTermTitle{
  margin-left:6px; font-size:10.5px; letter-spacing:.18em; color:#00ff4177;
  text-transform:uppercase;
  text-shadow:0 0 8px #00ff4144;
}
#matrixTermBody{
  padding:12px 13px 14px; overflow-y:auto; flex:1;
  font-size:12.5px; line-height:1.65; color:#00ff41;
  text-shadow:0 0 6px #00ff41cc, 0 0 14px #00ff4166, 0 0 30px #00ff4126;
  animation:mtFlicker 5.5s steps(60) infinite;
  /* The rail it used to declare here is now the shared .scrollPanel rule at the
     bottom of this file -- this was the one panel in the game that had one, and
     it became the pattern for the other fifteen. Only its green thumb is still
     its own, and that override lives with the shared rule. */
}
.mtLine{ margin:0 0 3px; word-wrap:break-word; }
.mtLine.mtSys{ color:#00ff4177; font-size:11.5px; letter-spacing:.06em; }
.mtLine.mtYou{ color:#9fd8ff; }
.mtLine.mtWarn{ color:#ffd166; }
.mtLine::before{ content:''; }
.mtLine.mtSay::before{ content:'> '; color:#00ff4166; }
.mtCursor{
  display:inline-block; width:8px; height:1em; margin-left:2px;
  background:#00ff41; vertical-align:-2px;
  animation:mtBlink 1.05s steps(1) infinite;
}
@keyframes mtBlink{ 0%,49%{ opacity:1; } 50%,100%{ opacity:0; } }
@media (prefers-reduced-motion:reduce){ .mtCursor{ animation:none; } }

/* Scanlines, kept faint enough to read through. */
#matrixTerm::after{
  content:''; position:absolute; inset:0; pointer-events:none; border-radius:14px;
  /* Scanlines, plus a vignette so the corners fall away like curved glass. */
  background:
    repeating-linear-gradient(180deg, #00000066 0 1px, transparent 1px 3px),
    radial-gradient(ellipse at 50% 50%, transparent 55%, #000000aa 100%);
}

/* The refresh line, drifting down the tube every few seconds. Slow and faint --
   at any speed you would actually notice, it stops reading as a screen and
   starts reading as an effect. */
#matrixTerm::before{
  content:''; position:absolute; left:0; right:0; height:38%; pointer-events:none;
  z-index:2; border-radius:14px;
  background:linear-gradient(180deg, transparent, #00ff410a 45%, #00ff4114 50%, #00ff410a 55%, transparent);
  animation:mtSweep 7s linear infinite;
}
@keyframes mtSweep{
  0%{ transform:translateY(-120%); }
  100%{ transform:translateY(320%); }
}
/* Mains hum, essentially: a hair of brightness variation that never settles. */
@keyframes mtFlicker{
  0%,100%{ opacity:1; }
  8%{ opacity:.97; }
  9%{ opacity:1; }
  33%{ opacity:.985; }
  34%{ opacity:1; }
  71%{ opacity:.96; }
  72%{ opacity:1; }
}
@media (prefers-reduced-motion:reduce){
  #matrixTerm::before{ animation:none; opacity:0; }
  #matrixTermBody{ animation:none; }
}
@media (max-width:1180px){ #matrixTerm{ display:none; } }

/* ===================== THE HELP GUIDE =====================
   The card language that used to sit under the main menu, moved into Help where
   it belongs. On the menu it was a wall of text nobody had asked for; behind a
   button it is a reference you open when you want it.

   Laid out in columns rather than one long strip, because the guide has grown
   past what fits on a screen and a reference you have to scroll for is one
   people stop reading halfway down. */
#helpGuide{
  max-width:1180px; margin:0 auto;
  background:#00120fe0; border:1px solid var(--line); border-radius:14px;
  padding:22px 24px 18px;
}
#helpGuideHead{
  display:flex; justify-content:space-between; align-items:center;
  gap:16px; margin-bottom:18px;
  border-bottom:1px solid var(--line); padding-bottom:14px;
}
#helpGuideTitle{
  font-family:'Orbitron',sans-serif; font-size:clamp(18px,2.4vw,26px); font-weight:900;
  letter-spacing:.18em; color:var(--neon); text-shadow:0 0 14px var(--neon-dim);
}
#helpGuideCols{
  columns:3 320px; column-gap:16px;
}
#helpGuideCols .controlsSection{
  break-inside:avoid; margin:0 0 16px;
}
#helpGuideFoot{
  margin-top:6px; padding-top:12px; border-top:1px solid var(--line);
  text-align:center; color:var(--dim); font-size:11px; letter-spacing:.06em;
}
#helpGuideFoot .kbKey{ margin:0 2px; }
/* The battlefield and construct rows lead with an emoji rather than a keycap,
   so those "keys" get the plain treatment instead of the keyboard one. */
#helpGuideCols .kbKey{ font-size:11px; }
@media (max-width:820px){ #helpGuideCols{ columns:1; } }
/* A row is an icon (or a keycap) and its line of text, side by side. Left to
   wrap, a long description drops underneath its own icon and the column turns
   into a ragged list. */
#helpGuideCols .controlsRow{ flex-wrap:nowrap; align-items:flex-start; gap:9px; }
#helpGuideCols .controlsDesc{ flex:1; min-width:0; }
#helpGuideCols .controlsIcon{ font-size:15px; width:20px; line-height:1.4; }

/* ---- what you just picked up ----

   ANCHORED TO ITS SLOT. The card is placed directly above whichever slot on the
   bottom bar the pickup went into -- Q, E, the right mouse button, the laser --
   with a tail running down onto it and that slot lit while the card is up. It
   used to sit at bottom:30%, floating in the middle of the screen, naming a
   button that was 300px away and off to one side: the answer was in one place
   and the question in another.

   Everything about where it goes is written by showPickupCard, which measures
   the slot. Nothing here assumes a position -- there is no left:50% any more,
   because the card is not centred on anything except the slot it belongs to.

   It fades rather than blinks, and it never takes a click: you are still flying
   while you read it. */
#pickupCard{
  position:fixed; left:0; bottom:160px; transform:translateY(8px) scale(0.97);
  transform-origin:50% 100%;
  width:max-content; min-width:250px; max-width:420px; padding:9px 15px 8px;
  background:linear-gradient(180deg, #04141bfa, #020a0efa);
  border:1px solid var(--pcAccent, #c084fc);
  border-radius:10px; text-align:left; z-index:60;
  box-shadow:0 0 26px -4px var(--pcAccent, #c084fc), inset 0 0 24px -14px var(--pcAccent, #c084fc);
  opacity:0; pointer-events:none; transition:opacity .22s ease, transform .22s ease;
}
#pickupCard.show{opacity:1; transform:translateY(0) scale(1);}
/* The run of line from the bottom of the card down onto the slot, sat at
   --pcTailX, which showPickupCard sets to the slot's centre measured from the
   card's own left edge. A tail that always came out of the middle of the card
   would point at the slot only when the card happened to be centred over it. */
#pickupCardTail{
  position:absolute; top:100%; left:var(--pcTailX, 50%);
  width:2px; height:var(--pcTailH, 12px); margin-left:-1px;
  background:linear-gradient(180deg, var(--pcAccent, #c084fc), color-mix(in srgb, var(--pcAccent, #c084fc) 25%, transparent));
  box-shadow:0 0 8px -1px var(--pcAccent, #c084fc);
}
/* Kind and key on one line, pushed apart. */
#pickupCardTop{
  display:flex; align-items:center; justify-content:space-between; gap:16px;
}
#pickupCardKind{
  font-size:9px; letter-spacing:2.4px; text-transform:uppercase;
  color:var(--pcAccent, #c084fc); opacity:0.75; line-height:1.2;
}
#pickupCardName{
  font-size:18px; font-weight:800; letter-spacing:2px; line-height:1.25;
  color:#ffffff; text-shadow:0 0 14px var(--pcAccent, #c084fc); margin-top:2px;
}
#pickupCardWhat{ font-size:12px; color:var(--text); margin-top:3px; line-height:1.35; }
#pickupCardKeyRow{
  display:inline-flex; align-items:center; gap:6px; flex:none;
}
/* THE SAME BADGE THE SLOT WEARS. The svg inside is written by keyCapIconSvg --
   the one function the ability bar's five key caps come out of -- so the drawing
   on the card and the drawing on the slot underneath it are the same object and
   cannot drift apart. Drawing a lookalike a second way is the bug this repo
   keeps having. */
#pickupCardKeyBadge{ display:inline-flex; align-items:center; line-height:0; }
#pickupCardKeyBadge svg{ display:block; }
#pickupCardKey{
  display:inline-block; padding:2px 9px;
  border:1px solid var(--pcAccent, #c084fc); border-radius:6px;
  font-size:13px; font-weight:800; letter-spacing:1.4px; color:#ffffff;
  background:color-mix(in srgb, var(--pcAccent, #c084fc) 22%, transparent);
  text-shadow:0 0 12px var(--pcAccent, #c084fc);
}
#pickupCardKeyLabel{
  font-size:9px; letter-spacing:2.2px; text-transform:uppercase;
  color:var(--pcAccent, #c084fc); opacity:0.9;
}
/* THE SLOT ANSWERS BACK. Lit for as long as the card is up, in the card's own
   accent, so the two are visibly one thing with a line between them. */
.slotBox.slotCalling{
  border-color:var(--pcAccent, #c084fc) !important;
  box-shadow:0 0 20px -3px var(--pcAccent, #c084fc), inset 0 0 22px -12px var(--pcAccent, #c084fc);
  animation:slotCallPulse 1.15s ease-in-out infinite;
}
@keyframes slotCallPulse{
  0%,100%{ box-shadow:0 0 16px -4px var(--pcAccent, #c084fc), inset 0 0 22px -12px var(--pcAccent, #c084fc); }
  50%    { box-shadow:0 0 30px 0px var(--pcAccent, #c084fc), inset 0 0 26px -8px var(--pcAccent, #c084fc); }
}
@media (prefers-reduced-motion:reduce){ .slotBox.slotCalling{ animation:none; } }
/* The card itself takes no clicks -- you are flying while you read it -- so the
   one thing that IS clickable has to opt back in. */
/* A row of its own, centred, and a real target. It used to be a 10px chip
   tucked in beside the key cap -- a hard thing to hit with a mouse you are also
   steering with, and easy to miss entirely while you are reading the card. */
#pickupCardGotIt{
  pointer-events:auto; cursor:pointer;
  display:block; margin:8px 0 1px; min-width:0;
  font-family:inherit; font-size:12px; font-weight:700; letter-spacing:2.5px;
  text-transform:uppercase; color:#ffffff;
  background:color-mix(in srgb, var(--pcAccent, #c084fc) 20%, transparent);
  border:2px solid var(--pcAccent, #c084fc); border-radius:8px; padding:8px 22px;
  box-shadow:0 0 18px -6px var(--pcAccent, #c084fc), inset 0 -2px 0 #00000055;
  transition:background .15s, box-shadow .15s, transform .08s;
}
#pickupCardGotIt:hover, #pickupCardGotIt:focus-visible{
  background:color-mix(in srgb, var(--pcAccent, #c084fc) 42%, transparent);
  box-shadow:0 0 24px -4px var(--pcAccent, #c084fc), inset 0 -2px 0 #00000055;
  outline:none;
}
#pickupCardGotIt:active{ transform:translateY(1px); }
/* Short screens -- 768 is the commonest laptop height -- leave only about 123px
   between your ship and the HUD. Tighten the type rather than drop the detail
   line: the note is the part that teaches you the ability, so losing it defeats
   the point of the card. */
@media (max-height: 820px){
  #pickupCard{ padding:7px 14px 6px; }
  #pickupCardName{ font-size:17px; letter-spacing:1.5px; }
  #pickupCardWhat{ font-size:12px; margin-top:2px; }
  #pickupCardGotIt{ margin:6px 0 0; padding:5px 16px; font-size:11px; min-width:0; }
}
/* Small screens: the arena is tight, so narrower and quieter. */
@media (max-width: 820px){
  #pickupCard{ min-width:0; max-width:min(92vw, 360px); padding:8px 12px 7px; }
  #pickupCardName{ font-size:16px; }
  #pickupCardWhat{ font-size:11.5px; }
}
}

/* ---- settings panel rows ----
   One row shape for every labelled control, so sliders, values and captions
   line up down the whole panel instead of each section inventing its own. */
.setRow{ display:flex; align-items:center; gap:10px; margin:9px 0 2px; }
.setRow label{ flex:0 0 148px; color:var(--text); font-size:12.5px; }
.setRow input[type="range"]{ flex:1 1 auto; min-width:90px; accent-color:var(--neon); }
.setVal{
  flex:0 0 auto; min-width:62px; text-align:right;
  font-size:11.5px; color:var(--neon); letter-spacing:0.5px;
  font-variant-numeric:tabular-nums;
}
.setCheck{ display:flex; align-items:center; gap:8px; cursor:pointer; margin:9px 0 2px; font-size:12.5px; }
.setNote{ font-size:11px; color:var(--dim); line-height:1.5; margin:0 0 6px 0; }
/* Wraps because this row was built for three buttons and the replay capture
   quality puts five in it -- without it the readout beside them is cut off on a
   narrow settings panel. */
.gfxPresets{ display:flex; align-items:center; gap:7px; margin-bottom:4px; flex-wrap:wrap; }
.gfxPresetBtn{ padding:5px 13px; font-size:12px; }
.gfxPresetBtn.on{ border-color:var(--neon); color:var(--neon); box-shadow:0 0 10px -2px var(--neon); }
.gfxFps{
  float:right; font-size:11.5px; letter-spacing:1px; color:var(--dim);
  font-variant-numeric:tabular-nums; font-weight:400;
}
.setSubhead{
  font-size:10px; letter-spacing:3px; text-transform:uppercase; color:var(--dim);
  margin:14px 0 2px; padding-top:9px; border-top:1px solid #ffffff12;
}

/* ---- menu button icons ----
   Inline SVG rather than emoji, because an emoji is drawn from a colour font
   and ignores `color` completely -- there is no way to ask for a specific
   purple or a specific silver from one. These paint with currentColor, so the
   colour lives here with everything else. */
.btnIco{
  width:1.05em; height:1.05em; vertical-align:-0.17em; margin-right:0.42em;
  flex:0 0 auto;
}
.icoHelp{
  color:#c084fc;                                  /* the same purple as abilities */
  filter:drop-shadow(0 0 5px #c084fc66);
}
/* Silver is a gradient in real life, so it is one here too: a cool highlight at
   the top falling to a darker edge. A flat grey reads as "disabled", which is
   the last thing a settings button should look like. The steel itself lives as
   a gradient in the SVG, since currentColor cannot express brushed metal; this
   only supplies the glow it casts. */
.icoGear{
  color:#d7dee8;
  filter:drop-shadow(0 0 5px #aab6c880);
  transition:filter .2s ease;
}
#infoMenuBtn:hover .icoHelp{ filter:drop-shadow(0 0 9px #c084fcaa); }
#settingsMenuBtn:hover .icoGear{ filter:drop-shadow(0 0 10px #cdd8e6cc) brightness(1.12); }
/* A cog that actually turns -- slowly enough to be scenery rather than a
   distraction, and faster when you reach for it. Only the toothed group spins,
   so the sheen laid over it stays put and the teeth pass under the highlight. */
@keyframes swGearSpin{ from{ transform:rotate(0deg); } to{ transform:rotate(360deg); } }
.icoGear .gearSpin{
  transform-box:fill-box; transform-origin:50% 50%;
  animation:swGearSpin 12s linear infinite;
}
#settingsMenuBtn:hover .icoGear .gearSpin{ animation-duration:2.6s; }
@media (prefers-reduced-motion: reduce){
  .icoGear .gearSpin{ animation:none; }
}

/* ===================== SQUAD ROOM: LAUNCH CONSOLE =====================

   Two columns -- who is here, and how you go in -- with a readiness strip over
   the top. The panel has always been 820px wide and the old single column used
   about a third of it, which is why the four match modes had to be a dropdown:
   there was nowhere to put four descriptions. There is now. */
.srPanel{
  max-width:820px; margin:0 auto; padding:22px;
  background:#001210; border:1px solid var(--line); border-radius:14px;
  color:var(--text); font-size:13px; line-height:1.6;
}
.srTop{display:flex; justify-content:space-between; align-items:center; gap:14px; margin-bottom:16px;}
.srTopLeft{display:flex; align-items:center; gap:16px; flex-wrap:wrap;}
.srTitle{font-family:'Orbitron',sans-serif; font-weight:700; font-size:16px; letter-spacing:2px; color:#e8fffb;}
.srCodeWrap{display:flex; align-items:center; gap:9px;}
.srCode{
  font-family:'Orbitron',sans-serif; font-weight:900; font-size:22px; letter-spacing:6px;
  color:var(--neon); text-shadow:0 0 16px #00fff299;
}
.srBtn{
  font-family:'Share Tech Mono',monospace; font-size:12px; letter-spacing:.5px;
  background:#00120fcc; border:1px solid var(--line); border-radius:4px;
  color:var(--text); padding:8px 15px; cursor:pointer; transition:all .15s;
}
.srBtn:hover:not(:disabled){border-color:var(--neon); color:var(--neon);}
.srBtn:disabled{opacity:.45; cursor:default;}
.srBtnSm{padding:5px 11px; font-size:11px;}
.srBtnWide{width:100%; text-align:center;}
.srBtnGo{border-color:var(--neon); color:var(--neon); box-shadow:0 0 14px -4px var(--neon);}
.srBtnDanger{border-color:#5a2030; color:#ff5470;}
.srBtnDanger:hover:not(:disabled){border-color:#ff5470; color:#ff5470;}

/* the entry view */
.srLead{color:var(--dim); font-size:12px; margin-bottom:16px; text-align:center;}
.srOr{text-align:center; color:var(--dim); margin:14px 0;}
.srCodeInput{
  width:100%; text-transform:uppercase; padding:12px; margin-bottom:10px; text-align:center;
  font-size:16px; letter-spacing:3px; background:#00120fcc; border:1px solid var(--line);
  border-radius:4px; color:var(--text); font-family:'Share Tech Mono',monospace;
}
.srError{color:#ff5470; margin-top:12px; text-align:center; display:none;}

/* readiness, said once and in one place */
.srReady{
  display:flex; align-items:center; gap:12px; margin-bottom:16px;
  border:1px solid #123f36; border-radius:6px; background:#00160fbb; padding:9px 13px;
}
.srReadyText{flex:1; color:var(--text); font-size:12.5px;}
.srPill{
  font-size:10px; letter-spacing:2px; padding:2px 9px; border-radius:20px;
  border:1px solid currentColor; color:#7a8a86; white-space:nowrap;
}
.srPill.ok{color:#3ddc71;}
.srPill.warn{color:#ff8c42;}

.srCols{display:grid; grid-template-columns:1fr 1fr; gap:18px;}
.srCol{display:flex; flex-direction:column; gap:10px;}
.srSpacer{flex:1; min-height:4px;}
.srColHead{display:flex; align-items:center; justify-content:space-between; gap:8px;}
.srLbl{font-size:10.5px; letter-spacing:3px; color:var(--dim); text-transform:uppercase;}
.srLblFaint{color:#3f7a6f;}
.srNote{color:var(--dim); font-size:11.5px;}

/* the four modes, as cards rather than a browser dropdown */
.srModes{display:flex; flex-direction:column; gap:8px;}
.srMode{
  display:flex; gap:11px; align-items:flex-start; text-align:left; width:100%;
  border:1px solid #0d3b33; border-radius:6px; background:#00140ebb; padding:10px 12px;
  color:var(--text); font-family:'Share Tech Mono',monospace; cursor:pointer;
  transition:border-color .15s, background .15s;
}
.srMode:hover:not(:disabled){border-color:#1d6b5c;}
.srMode.on{border-color:var(--neon); background:#00201c; box-shadow:inset 0 0 26px -16px var(--neon);}
.srMode:disabled{cursor:default;}
.srModes.locked .srMode{opacity:.62;}
.srModes.locked .srMode.on{opacity:1;}
.srDot{
  width:11px; height:11px; border-radius:50%; border:1px solid var(--dim);
  margin-top:4px; flex:0 0 auto;
}
.srMode.on .srDot{border-color:var(--neon); background:var(--neon); box-shadow:0 0 9px var(--neon);}
.srModeText{display:flex; flex-direction:column; gap:2px;}
.srModeName{color:#e8fffb; font-size:12.5px;}
.srMode.on .srModeName{color:var(--neon);}
.srModeDesc{color:var(--dim); font-size:11.5px; line-height:1.45;}
.srOnly{font-size:10px; letter-spacing:2px; color:#3f7a6f; text-transform:uppercase; white-space:nowrap;}

/* the roster rows keep their class names -- the room builds them in JS */
.squadPlayerRow{background:#00160fbb; border-color:#0d3b33; margin-bottom:0;}
.squadPlayerRow.isMe{border-color:#2a6b5e; box-shadow:inset 0 0 22px -14px var(--neon);}
#squadRoomPlayerList{display:flex; flex-direction:column; gap:8px;}

@media (max-width:720px){
  .srCols{grid-template-columns:1fr;}
  .srPanel{padding:15px;}
  .srCode{font-size:19px; letter-spacing:4px;}
}

/* Which of these ships is yours, now that the row carries your name. */
.sqYou{ color:var(--dim); font-size:0.85em; }

/* ---- which songs and which sounds ----
   Thirty-odd rows would swamp the settings panel, so they fold away; and a list
   of names with no way to hear them is a list of guesses, so every row has a
   play button. */
.audioDetails{
  margin:8px 0 2px; border:1px solid #103b34; border-radius:6px;
  background:#00120fa0; overflow:hidden;
}
.audioDetails summary{
  cursor:pointer; list-style:none; padding:7px 11px; font-size:12px;
  color:var(--text); letter-spacing:.6px; user-select:none;
  display:flex; align-items:center; gap:8px;
}
.audioDetails summary::-webkit-details-marker{ display:none; }
.audioDetails summary::before{
  content:'\25B8'; color:var(--dim); font-size:10px; transition:transform .15s;
}
.audioDetails[open] summary::before{ transform:rotate(90deg); }
.audioDetails summary:hover{ color:var(--neon); }
.audioCount{ margin-left:auto; color:var(--dim); font-size:11px; font-variant-numeric:tabular-nums; }
.audioGrid{
  display:grid; grid-template-columns:repeat(auto-fill, minmax(184px, 1fr));
  gap:2px 10px; padding:2px 11px 9px;
}
.audioHead{
  grid-column:1/-1; font-size:9.5px; letter-spacing:2.2px; text-transform:uppercase;
  color:var(--dim); margin:7px 0 2px; padding-top:6px; border-top:1px solid #0b2d27;
}
.audioHead:first-child{ border-top:0; padding-top:0; margin-top:2px; }
.audioRow{
  display:flex; align-items:center; gap:7px; font-size:11.5px;
  color:var(--text); cursor:pointer; padding:2px 0; min-width:0;
}
.audioRow input{ accent-color:var(--neon); flex:0 0 auto; margin:0; }
.audioRow span{ flex:1 1 auto; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.audioRow.off span{ color:var(--dim); text-decoration:line-through; }
.audioPlay{
  flex:0 0 auto; width:19px; height:19px; padding:0; line-height:1;
  border:1px solid #14524a; border-radius:4px; background:#00120f;
  color:var(--dim); font-size:9px; cursor:pointer;
}
.audioPlay:hover{ border-color:var(--neon); color:var(--neon); }

/* ---- Pro Player Certificate ----
   Sits above every other overlay, because it is the last thing a run produces
   and the results podium is behind it. The sheet keeps its 8:5 shape at any
   window size so the layout drawn on the canvas is never squashed. */
#certificateOverlay{
  display:none; position:fixed; inset:0; z-index:240;
  background:#00050ae8; backdrop-filter:blur(4px);
  align-items:center; justify-content:center; padding:24px;
}
.certBox{ display:flex; flex-direction:column; align-items:center; gap:14px; max-width:100%; }
/* The sheet comes in two shapes now, and the wide one's rule -- a width
   derived from 1.6 -- would leave the phone sheet enormous and clipped. Each
   shape is fitted to the space by its own aspect. */
#certificateCanvas{
  width:min(92vw, calc((92vh - 90px) * 1.6));
  height:auto; max-height:calc(92vh - 90px);
  border-radius:10px; box-shadow:0 0 60px -8px #ffd16655, 0 0 0 1px #0e6b5c;
}
#certificateCanvas.certPhone{
  width:min(92vw, calc((92vh - 90px) * 0.5625));   /* 1080 / 1920 */
}
.certActions{ display:flex; gap:10px; align-items:center; flex-wrap:wrap; justify-content:center; }
.certActions button{
  font-family:'Share Tech Mono',monospace; font-size:13px; letter-spacing:.6px;
  padding:10px 20px; border-radius:5px; cursor:pointer;
  background:#00120fcc; border:1px solid var(--line); color:var(--text); transition:all .15s;
}
.certActions button:hover{ border-color:var(--neon); color:var(--neon); box-shadow:0 0 10px -2px var(--neon); }
/* ===================== DECLARING A STAGE WINNER =====================

   The end of a stage, which in a tournament is the second-biggest moment there
   is. It used to be a trophy emoji and a sentence. Now it arrives: the eyebrow
   first, the name rising into place with a gleam crossing it, the score
   counting up under it, and the permanent bonus last as its own beat.

   Gold throughout, because gold already means "won this" everywhere else in
   the game -- the mothership frame, the FINAL BLOW banner, the certificate. */
/* ---------- THE STAGE WINNER: a ticket stub ----------
   Replaced five centred blocks in a stack. The stage number runs down the side
   because it is the one value identical on every card and should not be taking
   a line at the top; WINNER is a real label rather than half of a dim
   hyphenated whisper; the name is sized to the room it has, which is what fixes
   a three-letter name floating in a wide card; and the figures sit on dotted
   leaders, label left and value right, the way a results sheet is read. */
.stageWinner{
  display:grid; grid-template-columns:44px 1fr;
  margin:0 0 16px; overflow:hidden;
  background:linear-gradient(180deg,#161107 0%, #0b0906 100%);
  border:1px solid #4a3c12; border-radius:10px;
  animation:swCard .45s cubic-bezier(.2,.9,.3,1) both;
}
.swSide{
  background:#1d1608; border-right:1px dashed #ffd16644;
  display:flex; align-items:center; justify-content:center;
  animation:swSide .4s cubic-bezier(.2,.9,.3,1) .06s both;
}
.swSide span{
  writing-mode:vertical-rl; transform:rotate(180deg);
  font-family:'Share Tech Mono',monospace; font-size:9.5px; letter-spacing:.3em;
  text-transform:uppercase; color:#ffd166; white-space:nowrap;
}
.swBody{ padding:15px 17px; min-width:0; }
.swWinner{
  font-family:'Orbitron',sans-serif; font-weight:700;
  font-size:12px; letter-spacing:.24em; text-transform:uppercase;
  color:#ffe9a8; text-shadow:0 0 14px #ffd16666;
  animation:swUp .4s ease-out .12s both;
}
.swName{
  font-family:'Orbitron',sans-serif; font-weight:900;
  color:#ffe9a8; line-height:1.04; margin:2px 0 4px;
  text-shadow:0 0 18px #ffd16666;
  /* One line, which is what the design wants and what makes the size
     measurable: a name allowed to wrap never reports an overflow, so the
     fitter has nothing to measure and leaves every name at maximum. */
  white-space:nowrap;
  animation:swName .5s cubic-bezier(.16,.9,.3,1) .18s both;
}
.swSub{
  font-family:'Share Tech Mono',monospace; font-size:10.5px; letter-spacing:.08em;
  color:#8a7c5c;
  animation:swUp .4s ease-out .3s both;
}
.swLines{ margin-top:12px; display:flex; flex-direction:column; gap:7px; }
.swLine{
  display:flex; align-items:baseline; gap:10px;
  font-family:'Share Tech Mono',monospace; font-size:10px;
  letter-spacing:.2em; text-transform:uppercase; color:#b08d3a;
}
/* The leader, which is what makes a row of figures scannable rather than two
   things floating near each other. */
.swLine::after{ content:''; flex:1; border-bottom:1px dotted #ffd16333; transform:translateY(-3px); }
.swLine b{
  order:3; font-size:20px; letter-spacing:0; color:#ffe9a8;
  text-transform:none; font-variant-numeric:tabular-nums;
}
.swLine b.g{ color:#3ddc71; font-size:12px; letter-spacing:.06em; text-transform:uppercase;
             text-shadow:0 0 12px #3ddc7155; }
.swLines .swLine:nth-child(1){ animation:swUp .42s ease-out .4s both; }
.swLines .swLine:nth-child(2){ animation:swUp .46s ease-out .62s both; }

@keyframes swCard{ from{ opacity:0; transform:translateY(12px) scale(.99); }
                   to  { opacity:1; transform:translateY(0) scale(1); } }
@keyframes swSide{ from{ transform:scaleY(0); transform-origin:top; }
                   to  { transform:scaleY(1); transform-origin:top; } }
@keyframes swUp{ from{ opacity:0; transform:translateY(8px); }
                 to  { opacity:1; transform:translateY(0); } }
@keyframes swName{
  0%  { opacity:0; transform:translateY(16px); }
  100%{ opacity:1; transform:translateY(0); }
}
@media (prefers-reduced-motion: reduce){
  .stageWinner, .swSide, .swWinner, .swName, .swSub, .swLine{ animation:none; opacity:1; transform:none; }

}

/* The number currently running is lit; it settles when it lands. Applies to
   the tournament sheet, whose numbers are elements -- the certificate does the
   same thing in canvas. */
.myRun b.counting, .tournTitle ~ * b.counting{ color:#ffffff; text-shadow:0 0 14px #ffffffaa; }
.certActions .certDownload{ border-color:#8a6a1e; color:#ffd166; }
.certActions .certDownload:hover{ border-color:#ffd166; color:#ffd166; box-shadow:0 0 14px -2px #ffd166; }

/* ---- locked skins ----
   A locked colour still shows its own colour, dimmed, with the challenge under
   it. Hiding it behind a grey box tells you nothing about whether it is worth
   chasing, which is the only reason to be on this screen. */
.skinsCount{ font-size:12px; font-weight:400; color:var(--dim); letter-spacing:1px; margin-left:8px; }
.skinCard.locked{ cursor:not-allowed; border-style:dashed; border-color:#123f36; opacity:0.72; }
.skinCard.locked:hover{ border-color:#1c6b5c; opacity:0.88; }
.skinCard.locked svg{ opacity:0.5; filter:saturate(0.7); }
.skinCard.locked .skinName{ color:var(--dim); }
.skinCard .skinLockHow{ font-size:10px; color:var(--dim); line-height:1.35; margin-top:5px; text-align:center; }
.skinCard .skinLockBar{ height:3px; border-radius:2px; background:#0b2d27; margin:7px 2px 4px; overflow:hidden; }
.skinCard .skinLockBar i{ display:block; height:100%; background:currentColor; opacity:0.8; }
.skinCard .skinLockNum{ font-size:9.5px; letter-spacing:1px; color:#3f7d72; font-variant-numeric:tabular-nums; }

/* ---- the Nyan Cat credit ----
   To the RIGHT of the dev status bar rather than under it, and with no window
   or box around it: it is an attribution, not another panel. The handle sits
   above, quiet and not clickable; the link is the bold, larger line, because
   the link is the part that was actually asked for.

   Anchored to the DEV STATUS panel itself (which is position:relative), so it
   is level with the BAR rather than with the column the bar sits in -- there is
   a logo pin above the bar, and lining up with the column put this 78px high. */
#menuCredits{
  position:absolute; top:0; left:100%; margin-left:12px; z-index:20;
  white-space:nowrap;
  display:flex; flex-direction:column; align-items:flex-start; gap:1px;
  font-family:'Share Tech Mono',monospace; pointer-events:auto;
}
.creditHandle{
  font-size:10px; letter-spacing:.6px; color:var(--dim);
  user-select:none;                     /* not a link, so it should not look like one */
}
.creditLink{
  font-size:15px; font-weight:700; letter-spacing:.4px;
  color:#ff9ede; text-decoration:none;
  text-shadow:0 0 10px #ff66cc55;
}
.creditLink:hover{ color:#ffc2e9; text-shadow:0 0 14px #ff66cc88; }
/* Goes with the dev column it sits beside. */
@media (max-width:900px){ #menuCredits{ display:none; } }


/* The legal line under the menu credit. Deliberately quiet -- it must be
   findable without competing with anything a player actually came here to do. */
#menuLegal{ margin-top:8px; font-size:10px; letter-spacing:.6px; line-height:1.7; }
#menuLegal a{ color:#7f88b4; text-decoration:none; }
#menuLegal a:hover{ color:#cdd3ff; text-decoration:underline; }
#menuLegal span{ color:#3f4665; margin:0 5px; }
#menuCopyright{ color:#5a6183; margin-top:2px; }


/* ===================== SCROLLING PANELS =====================

   One rail, everywhere. The game had exactly one styled scrollbar --
   #matrixTermBody's 6px green rail -- and a dozen other scrolling panels
   wearing whatever the operating system paints, which on Windows is a 17px
   light-grey slab down the side of a black holographic panel.

   `.scrollPanel` is the whole treatment and it is the ONLY copy of it. Every
   scrolling container in the game carries the class; nothing re-declares the
   widths or the colours locally. Firefox is served by scrollbar-width and
   scrollbar-color, WebKit by the ::-webkit-scrollbar pseudos, because neither
   one alone covers both.

   The bar is NOT hidden. A list that scrolls with no bar at all is worse than
   an ugly bar: there is nothing to tell you the list is longer than the window,
   and nothing to grab. It is quiet, not absent.

   The fade is the other half. `.scrollMore` is put on by refreshScrollPanels()
   in js/02-dom.js while there is content below the fold and taken off at the
   bottom, so the last row dissolving into the panel edge means "keep going" and
   a list you have reached the end of stops doing it. The mask sits on the
   element's own box rather than on the content, so it stays pinned to the
   bottom edge while the rows travel underneath it. */
:root{
  --rail:#22d3ee3d;                 /* the game's cyan, at rest */
  --railHover:#22d3eeab;            /* ...and under the pointer */
}
.scrollPanel{
  scrollbar-width:thin;
  scrollbar-color:var(--rail) transparent;
}
.scrollPanel::-webkit-scrollbar{ width:6px; height:6px; }
.scrollPanel::-webkit-scrollbar-track{ background:transparent; }
.scrollPanel::-webkit-scrollbar-thumb{ background:var(--rail); border-radius:3px; }
.scrollPanel::-webkit-scrollbar-thumb:hover{ background:var(--railHover); }
.scrollPanel::-webkit-scrollbar-corner{ background:transparent; }
.scrollPanel.scrollMore{
  -webkit-mask-image:linear-gradient(to bottom, #000 calc(100% - 38px), #0000 100%);
          mask-image:linear-gradient(to bottom, #000 calc(100% - 38px), #0000 100%);
}
/* The terminal keeps its own green thumb -- it is a different machine on the
   screen and always has been -- but it takes the shared widths, the transparent
   track and the fade from the rule above rather than restating them. */
#matrixTermBody{ scrollbar-color:#00ff4133 transparent; }
#matrixTermBody::-webkit-scrollbar-thumb{ background:#00ff4133; }
#matrixTermBody::-webkit-scrollbar-thumb:hover{ background:#00ff4188; }

/* The count under a list that has a natural number to it. Same voice as the
   scoreboard summary: dim, small, and never competing with the rows. */
.listCount{
  font-size:10.5px; letter-spacing:.08em; color:var(--dim);
  text-align:right; margin:-8px 0 12px; font-variant-numeric:tabular-nums;
}

/* ===================== THE DIRECTOR'S TAG =====================

   Who is on screen and why, for a viewer who did not choose the cut and has no
   idea why the camera just moved. Two lines: the reason in the game's amber,
   the name under it. Only ever visible while Tournament Spectate is running --
   it is created the first time the director speaks and hidden the rest of the
   time, so a build that never turns the mode on has the DOM it always had.

   Bottom left, where a broadcast puts its lower third -- and, more practically,
   the one part of this screen with nothing in it. The top strip already carries
   the spectate name, the ring clock and every toast; a second label up there
   lands on top of one of them within seconds of a match starting. */
#directorTag{
  display:none; position:fixed; left:20px; bottom:18px;
  z-index:26; pointer-events:none; text-align:left;
  background:#0b1020e0; border:1px solid #ffd16655;
  border-left:3px solid #ffd166; border-radius:4px 10px 10px 4px;
  padding:6px 16px 7px; white-space:nowrap;
}
#directorTag .dirWhy{
  display:block; font-size:10px; font-weight:800; letter-spacing:.18em;
  color:#ffd166; text-shadow:0 0 10px #ffd16677;
}
#directorTag .dirWho{
  display:block; font-size:14px; font-weight:800; letter-spacing:.06em;
  color:#dfe6ff; margin-top:1px;
}
/* The spectate row that toggles it, lit while it is on. */
#spectateDirectorBtn.on{ border-color:#ffd166; color:#ffd166; }
#spectateDirectorBtn.on .ic{ filter:drop-shadow(0 0 6px #ffd16699); }

/* ===================== THE TIKTOK RAFFLE =====================

   The raffle has no panel of its own any more. It IS the free-for-all setup
   screen with the answers already filled in, so everything it needs is already
   styled: .label, .sliderRow, .row, .scrollPanel, .nameRow and the Done button.
   All that is left here is the paste box, the drop state on the panel it lands
   in, and the menu button that opens the whole thing. */

/* The one control the setup screen did not already have: somewhere to put a
   hundred lines at once. Same field as a name bar, four rows tall. */
#rosterPaste{
  display:block; width:100%; box-sizing:border-box; resize:vertical; margin:8px 0 2px;
  background:#05070f; border:1px solid var(--line); border-radius:6px;
  color:var(--text); padding:6px 8px; font-size:12px; line-height:1.5;
  font-family:'Share Tech Mono',monospace;
}
#rosterPaste:focus{ outline:none; border-color:#6d7fff; }
/* Pictures can be dropped straight onto the bars they belong to, so the panel
   itself is the target and says so only while a file is over it. */
#nameShipsPanel.over{ border-color:var(--neon); box-shadow:0 0 14px -4px var(--neon); }

/* The menu button that opens it. It lives in the credit's own stack now, under
   the www.nyan.cat link and above Terms / Privacy / Credits, so it is sized and
   weighted like that furniture rather than like the dev stack it came from: a
   thin outline, small caps, no fill and no glow. It is still plainly pressable
   -- a border, a dot, and a hover that lights up -- just not shouting over the
   attribution above it, which is there as a condition of using the character.
   The margin above it is what keeps it off that link at every window size. */
#tiktokRaffleBtn{
  display:inline-flex; align-items:center; gap:6px; margin-top:9px;
  padding:5px 10px; border:1px solid #ff5fa23d; border-radius:999px;
  font-family:'Share Tech Mono',monospace; font-size:10px; letter-spacing:.9px;
  text-transform:uppercase; color:#c98fb4; text-decoration:none;
  background:transparent; cursor:pointer; transition:color .15s, border-color .15s;
}
#tiktokRaffleBtn .raffleEntryDot{ font-size:8px; line-height:1; opacity:.85; }
#tiktokRaffleBtn:hover{ color:#ffc2e9; border-color:#ff5fa2; }
#tiktokRaffleBtn:focus-visible{ outline:2px solid var(--neon); outline-offset:2px; }
