:root{
    --void:#08060f;
    --pit:#0d0a18;
    --stone:#171326;
    --edge:#2a2340;
    --chalk:#ece6ff;
    --muted:#8b81a8;
    --spark:#3fe7ff;
    --rive:#a97cff;
    --hex:#ff8f3a;
    --ward:#5dffab;
    --beam:#ff3f7a;
    --grasp:#ffd24a;
    --blood:#ff4d5e;
    --mana:#5aa9ff;
    --display:"Cinzel",Georgia,serif;
    --mono:"IBM Plex Mono",ui-monospace,Menlo,monospace;
    --prose:"Spectral",Georgia,serif;
  }
  *{box-sizing:border-box}
  body{
    background:
      radial-gradient(120% 80% at 50% -10%, #1a1030 0%, transparent 60%),
      var(--void);
    color:var(--chalk);
    font-family:var(--mono);
    min-height:100vh;
    padding:10px 14px 18px;
    display:flex;
    justify-content:center;
  }
  .shell{width:100%;max-width:1000px;display:flex;flex-direction:column;gap:10px}

  /* No masthead: the game window shows the name itself, so this is only the
     music toggle, parked on one short right-aligned row. */
  header.crest{display:flex;justify-content:flex-end;align-items:center;min-height:0}

  /* The health plates belong to a match, not to a menu. They keep their space
     either way so the arena does not resize the moment a duel begins. */
  .rails{display:flex;gap:8px;align-items:stretch;flex-wrap:wrap;
    opacity:0;pointer-events:none;transition:opacity .55s ease}
  .rails.live{opacity:1;pointer-events:auto}
  .rails .plate{flex:1 1 150px}
  .plate{
    background:linear-gradient(180deg,rgba(23,19,38,.9),rgba(13,10,24,.9));
    border:1px solid var(--edge);
    border-radius:3px;
    padding:9px 12px;
    display:flex;flex-direction:column;gap:5px;
    min-width:0;
  }
  .plate{border-left:2px solid var(--tint,var(--edge))}
  .plate .who{display:flex;align-items:baseline;gap:8px;font-size:11px;letter-spacing:.2em;text-transform:uppercase}
  /* health number and round pips, on their own line under the name */
  .plate .line{display:flex;align-items:center;gap:9px;line-height:1;
    font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--muted)}
  .plate .who b{color:var(--tint,var(--chalk))}
  .plate .who b{font-family:var(--display);font-size:15px;letter-spacing:.08em;font-weight:600}
  .plate .line span{color:var(--muted)}
  .meter{width:100%;height:9px;background:#0a0714;border:1px solid var(--edge);border-radius:2px;overflow:hidden;position:relative}
  .meter i{display:block;height:100%;width:100%;transform-origin:left center;transition:transform .12s linear}
  .plate.out{opacity:.4}
  .plate.out .who b{text-decoration:line-through}
  .plate.surge{box-shadow:inset 0 0 24px -12px var(--grasp)}
  .plate.surge .meter.mp i{background:linear-gradient(90deg,#ffb43a,var(--grasp));box-shadow:0 0 12px var(--grasp)}
  .plate.locked{box-shadow:inset 0 0 0 1px var(--tint)}
  .meter.hp i{background:linear-gradient(90deg,#ff8a6a,var(--blood))}
  .meter.mp{height:5px}
  .meter.mp i{background:linear-gradient(90deg,#3f7fff,var(--mana))}
  .pips{display:flex;gap:6px;justify-content:center}
  .pips span{width:11px;height:11px;border:1px solid var(--edge);transform:rotate(45deg);background:#0a0714}
  .pips span.won{background:var(--chalk);box-shadow:0 0 10px rgba(236,230,255,.6)}
  .plate .wins{display:flex;gap:4px}
  .plate .wins i{width:7px;height:7px;transform:rotate(45deg);border:1px solid var(--edge);background:#0a0714}
  .plate .wins i.on{background:var(--tint,var(--chalk));border-color:var(--tint,var(--chalk))}
  /* Like the plates: it fades rather than disappearing, so its space is
     reserved on the menu too and the arena is exactly the same size before and
     during a duel. Hiding it outright pushed the spell book off a short screen
     the moment a match began. */
  .roundlabel{font-size:10px;letter-spacing:.24em;color:var(--muted);text-align:center;
    text-transform:uppercase;margin-top:2px;opacity:0;transition:opacity .55s ease}
  .roundlabel.live{opacity:1}

  .stage{position:relative;margin-inline:auto;width:100%;border:1px solid var(--edge);border-radius:4px;overflow:hidden;background:var(--pit);box-shadow:0 30px 80px -40px #000, inset 0 0 90px rgba(0,0,0,.8)}
  canvas{display:block;width:100%;height:auto;touch-action:none}

  .curtain{
    position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;
    background:radial-gradient(70% 70% at 50% 45%, rgba(13,10,24,.86), rgba(6,4,12,.97));
    backdrop-filter:blur(2px);
    text-align:center;padding:18px;
    /* The end screen must fit the arena, not scroll inside it — fitCurtain()
       shrinks the content if a short window would otherwise overflow. This stays
       as a last resort for anything it cannot reach. */
    overflow-y:auto;
  }
  /* the match report and the records board, shoulder to shoulder */
  .over-cols{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-start;
    justify-content:center;width:100%}
  .over-cols:not(:has(> *:not([hidden]))){display:none}
  .over-cols>*{flex:0 1 auto}
  /* Everything in the curtain lives in one box, so a short window can scale the
     whole end screen down as a unit instead of growing a scrollbar. */
  .curtain-inner{display:flex;flex-direction:column;align-items:center;justify-content:center;
    gap:12px;width:100%;transform-origin:top center}
  .curtain-inner>*{margin:auto}
  .curtain>*{margin:auto}
  .curtain[hidden]{display:none}
  .curtain h2{font-family:var(--display);font-weight:900;text-transform:uppercase;letter-spacing:.08em;font-size:clamp(22px,4vw,36px);margin:0;text-wrap:balance}
  .curtain p{font-family:var(--prose);font-size:15px;color:#c4bade;max-width:46ch;margin:0;line-height:1.55}
  .stats[hidden]{display:none}
  .stats{width:min(400px,92%);margin:0}
  .statline{border-collapse:collapse;width:100%;font-family:var(--mono);font-size:12px}
  .statline caption{font-family:var(--display);font-size:11px;letter-spacing:.3em;text-transform:uppercase;color:var(--muted);padding-bottom:6px}
  .statline th{font-size:9px;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);font-weight:400;padding:4px 8px;border-bottom:1px solid var(--edge);text-align:right}
  .statline th:first-child{text-align:left}
  .statline td{padding:5px 8px;border-bottom:1px solid color-mix(in srgb, var(--edge) 55%, transparent);text-align:right;color:#c4bade;white-space:nowrap}
  .statline td.who{text-align:left;font-weight:600;color:var(--chalk)}
  .statline td.who i{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--c,#fff);margin-right:8px;vertical-align:baseline;box-shadow:0 0 8px var(--c,#fff)}
  .statline td.who em{font-style:normal;color:var(--spark);font-size:9px;letter-spacing:.12em;margin-left:6px}
  .statline tr.best td{color:#fff}
  .statline tr.best td.who{color:var(--spark)}
  .statline tr.best td{text-shadow:0 0 10px rgba(63,231,255,.35)}
  .choices{display:flex;gap:8px;flex-wrap:wrap;justify-content:center}
  button{
    font-family:var(--mono);font-size:12px;letter-spacing:.16em;text-transform:uppercase;
    background:#120f1f;color:var(--chalk);border:1px solid var(--edge);border-radius:2px;
    padding:10px 16px;cursor:pointer;transition:border-color .15s,background .15s,color .15s;
  }
  button:hover{border-color:var(--rive);background:#1a1430}
  button:focus-visible{outline:2px solid var(--spark);outline-offset:2px}
  button.sel{border-color:var(--rive);color:#fff;background:#241a44;box-shadow:0 0 18px -4px var(--rive)}
  fieldset.levels{border:1px solid var(--edge);border-radius:3px;padding:10px 14px 12px;margin:0;
    display:flex;gap:8px;flex-wrap:wrap;justify-content:center;background:rgba(13,10,24,.5)}
  fieldset.levels legend{font-size:10px;letter-spacing:.26em;text-transform:uppercase;color:var(--muted);padding:0 8px}
  .lv0{--tint:#5dffab} .lv1{--tint:#4aa3ff} .lv2{--tint:#a97cff} .lv3{--tint:#ff4d5e}
  fieldset.levels button{border-color:color-mix(in srgb, var(--tint) 45%, #1c1730);color:var(--tint);
    display:flex;flex-direction:column;align-items:center;gap:3px;line-height:1.1}
  fieldset.levels button em{font-style:normal;font-size:9px;letter-spacing:.2em;opacity:.62}
  fieldset.levels button:hover{border-color:var(--tint);background:color-mix(in srgb, var(--tint) 12%, transparent)}
  fieldset.levels button.sel{border-color:var(--tint);color:#fff;
    background:color-mix(in srgb, var(--tint) 26%, #120f1f);box-shadow:0 0 20px -6px var(--tint)}
  /* The label floats to the left of the box; the box itself is centered on the
     curtain. (Before this, the label+input row was centered as a unit, which
     pushed the input box off to the right of the screen center by half the label
     width — visible on every menu screen.) */
  /* who you are playing as — one compact strip, because the end-of-match
     screen has to fit the arena without scrolling and this sits on it too */
  .whoami{display:flex;justify-content:center;width:100%;margin:0 0 2px}
  .whoami[hidden]{display:none}
  .who-guest,.who-card{display:flex;align-items:center;gap:9px;padding:5px 11px;
    border:1px solid var(--edge);border-radius:3px;background:rgba(10,7,18,.6);
    font-size:11px;letter-spacing:.05em;color:var(--muted)}
  .who-card[hidden]{display:none}
  .who-guest[hidden]{display:none}
  .who-guest b{color:var(--chalk);font-weight:600}
  .whoami button{font-family:var(--display);font-size:10px;letter-spacing:.16em;text-transform:uppercase;
    color:var(--muted);background:none;border:1px solid var(--edge);border-radius:2px;
    padding:3px 9px;cursor:pointer}
  .whoami button:hover{color:var(--chalk);border-color:var(--spark)}
  .who-name{font-family:var(--display);font-size:13px;letter-spacing:.04em;color:var(--chalk);font-weight:600}
  .who-lv{font-family:var(--mono);font-size:10px;letter-spacing:.06em;color:var(--spark);
    border:1px solid var(--spark);border-radius:2px;padding:1px 6px;white-space:nowrap}
  .xpbar{position:relative;display:block;width:120px;height:5px;border-radius:3px;
    background:#1a1530;overflow:hidden}
  .xpbar i{position:absolute;inset:0 auto 0 0;width:0;border-radius:3px;
    background:linear-gradient(90deg,var(--rive),var(--spark));transition:width .5s ease}
  .who-xp,.who-rec{font-family:var(--mono);font-size:10px;color:var(--muted);white-space:nowrap}
  /* the level-up flash on the strip after a match that earned one */
  .who-card.rankup{animation:rankup 1.5s ease}
  @keyframes rankup{
    0%{box-shadow:0 0 0 0 rgba(169,124,255,0)}
    25%{box-shadow:0 0 22px -4px var(--spark);border-color:var(--spark)}
    100%{box-shadow:0 0 0 0 rgba(169,124,255,0)}
  }

  /* the sign-in form */
  /* ---------------------------------------------------- the cloak track
     A battlepass strip under the landing buttons: what levelling up is for.
     Earned tiers are lit, the next one is ringed, the rest sit dim. It scrolls
     sideways rather than growing, so the curtain still fits the arena. */
  .pass{width:100%;max-width:850px;margin:14px auto 0;text-align:left}
  .pass[hidden]{display:none}
  .pass-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin:0 2px 7px}
  .pass-head h4{margin:0;font-family:var(--display);font-size:11px;letter-spacing:.2em;
    text-transform:uppercase;color:var(--muted);font-weight:600}
  .pass-head span{font-family:var(--mono);font-size:10.5px;color:#6b628c;white-space:nowrap}
  /* All twelve fit side by side on a normal screen — a ladder you can see the
     whole of. It only becomes a scroller on something narrow, and there the
     fade at each edge says so. */
  .pass-track{list-style:none;display:flex;gap:6px;margin:0;padding:2px 2px 8px;
    width:fit-content;max-width:100%;margin-inline:auto;
    overflow-x:auto;scroll-behavior:smooth;scrollbar-width:thin;
    scrollbar-color:#2a2450 transparent;
    -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 10px,#000 calc(100% - 10px),transparent 100%);
    mask-image:linear-gradient(90deg,transparent 0,#000 10px,#000 calc(100% - 10px),transparent 100%)}
  .pass-track::-webkit-scrollbar{height:5px}
  .pass-track::-webkit-scrollbar-thumb{background:#2a2450;border-radius:3px}
  .pass-track::-webkit-scrollbar-track{background:transparent}

  .tier{flex:0 0 auto;width:54px;display:flex;flex-direction:column;align-items:center;gap:5px;
    padding:8px 4px 7px;border:1px solid var(--edge);border-radius:3px;
    background:rgba(10,7,18,.6);text-align:center}
  .tier .lv{font-family:var(--mono);font-size:9.5px;letter-spacing:.08em;color:#5d5580}
  .tier .nm{font-family:var(--prose);font-size:9.5px;line-height:1.25;color:#5d5580;
    display:flex;align-items:center;flex:1 1 auto}
  /* The stone itself, drawn on a little canvas by paintJewel() in src/game.js
     so the ladder shows each cut exactly as it appears on a cape. Dark until
     it is earned. */
  .tier .jewel{display:block;width:30px;height:30px;
    filter:grayscale(1) brightness(.36)}

  .tier.earned{border-color:#3a3358;background:rgba(255,255,255,.03)}
  .tier.earned .jewel{filter:none}
  .tier.earned .lv{color:var(--spark)}
  .tier.earned .nm{color:var(--chalk)}
  .tier.next{border-color:var(--spark);box-shadow:0 0 16px -8px var(--spark)}
  .tier.next .lv{color:var(--spark)}
  .tier.next .nm{color:#a79dc8}
  .tier.next .jewel{filter:grayscale(.7) brightness(.6)}

  /* the sign-in form: label above its box, the way every other login form on
     the web is laid out. Ordinary shapes matter here — a password field in an
     unusual arrangement is one of the things phishing heuristics count. */
  .field{display:block;width:100%;margin:0 0 5px;text-align:left;font-size:10px;
    letter-spacing:.2em;text-transform:uppercase;color:var(--muted);cursor:pointer}
  .fieldbox{display:block;width:100%;box-sizing:border-box;margin:0 0 11px;
    font-family:var(--mono);font-size:13px;letter-spacing:.06em;color:var(--chalk);
    background:#0f0c1c;border:1px solid var(--edge);border-radius:2px;padding:8px 10px}
  .fieldbox:focus{outline:none;border-color:var(--spark);box-shadow:0 0 14px -6px var(--spark)}
  #authForm .note{text-align:left;margin:0 0 12px}
  #authForm{width:100%}
  /* Sign in and Sign up sit side by side and carry equal weight — one of them
     is the right answer for you and the page should not be guessing which.
     Different colours so they never read as the same button twice. */
  .go.alt{border-color:var(--rive);color:var(--rive)}
  .go.alt:hover{background:rgba(169,124,255,.12)}
  .backrow{margin-top:2px}
  .backrow button{flex:0 0 auto;min-width:120px}
  .linky{background:none;border:0;padding:4px 0 0;cursor:pointer;color:var(--rive);
    font-family:var(--display);font-size:11px;letter-spacing:.06em;text-decoration:underline;
    text-underline-offset:3px}
  .linky:hover{color:var(--spark)}
  .note.bad{color:#ff6b7a}
  .note.good{color:var(--spark)}
  .room{width:100%;max-width:430px;border:1px solid var(--edge);border-radius:3px;background:rgba(10,7,18,.75);padding:10px 13px;display:flex;flex-direction:column;gap:7px}
  .room[hidden]{display:none}
  .opts{display:grid;grid-template-columns:1fr 1fr;gap:7px 12px;width:100%}
  .opts .rrow.full{grid-column:1 / -1}
  .opts .rrow{margin:0}
  .room .note{margin-top:2px}
  .room .roster{max-height:168px;overflow-y:auto}
  /* the advanced knobs live behind a disclosure so the lobby is a code, a size
     and a roster — not a wall of sliders */
  /* Match options reads as a select control: a raised bar with a caret on the
     right, and a panel that drops out of it like a real menu. It used to be a
     flat bordered box that nobody could tell was clickable. */
  .moreopts{width:100%;position:relative}
  .moreopts>summary{
    list-style:none;cursor:pointer;user-select:none;
    display:flex;align-items:center;justify-content:space-between;gap:10px;
    padding:9px 12px;font-size:11px;letter-spacing:.18em;text-transform:uppercase;
    color:var(--chalk);font-weight:600;
    border:1px solid var(--edge);border-radius:3px;
    background:linear-gradient(rgba(255,255,255,.045), rgba(255,255,255,0)) , rgba(10,7,18,.9);
    box-shadow:0 1px 0 rgba(255,255,255,.04) inset;
    transition:border-color .14s, color .14s, background .14s}
  .moreopts>summary::-webkit-details-marker{display:none}
  .moreopts>summary::after{
    content:"";width:0;height:0;flex:none;
    border-left:4.5px solid transparent;border-right:4.5px solid transparent;
    border-top:5px solid currentColor;opacity:.75;transition:transform .16s}
  .moreopts>summary:hover{border-color:#5a4f7a;background:rgba(63,231,255,.06)}
  .moreopts>summary:focus-visible{outline:none;border-color:var(--spark);
    box-shadow:0 0 0 1px var(--spark), 0 0 16px -6px var(--spark)}
  .moreopts[open]>summary{border-color:var(--spark);color:var(--spark);
    border-bottom-left-radius:0;border-bottom-right-radius:0}
  .moreopts[open]>summary::after{transform:rotate(180deg)}
  /* the panel hangs off the bottom of the control, sharing its edge */
  .moreopts[open]>.opts{
    border:1px solid var(--spark);border-top:0;
    border-bottom-left-radius:3px;border-bottom-right-radius:3px;
    background:rgba(6,4,12,.96);
    box-shadow:0 10px 24px -14px #000;
    padding:11px 12px 12px;gap:9px 12px}
  .room-actions{display:flex;gap:8px;justify-content:center;width:100%}
  .room-actions button{flex:1;max-width:180px}
  .room h4{margin:0;font-family:var(--display);font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--muted);font-weight:600}
  .rrow{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:10.5px;letter-spacing:.08em;color:#a99fc4;text-transform:uppercase}
  /* display:flex outranks the browser's own [hidden]{display:none}, so without
     this the Lives slider sat under the Rounds slider even in Rounds mode —
     both at once, one of them doing nothing. */
  .rrow[hidden]{display:none}
  .rrow b{color:var(--chalk);font-weight:600}
  /* min-width:0 is what actually lets flex-wrap fire: without it the row keeps
     its content width and six preset names push straight out of the panel */
  .seg{display:flex;gap:4px;flex-wrap:wrap;justify-content:flex-end;min-width:0}
  /* the solo arena picker gets the label above the names — six of them never sit
     beside a label at panel width */
  /* max-width keeps the solo panel honest: it has no 430px room box around it,
     so an unconstrained grid stretched the six names across the whole screen */
  .arenapick{width:100%;max-width:430px;margin-inline:auto;
    padding-top:9px;border-top:1px solid rgba(42,35,64,.7)}
  .arenapick .rrow{flex-direction:column;align-items:center;gap:7px}
  /* Six names never divide evenly across a 430px row — left to wrap they came
     out five and a lonely one. A fixed three-wide grid gives two even rows of
     equal-width buttons instead. */
  .arenapick .seg{display:grid;grid-template-columns:repeat(3,1fr);gap:5px;width:100%}
  .arenapick .seg button{padding:7px 6px}
  /* solo has no panel border to sit under */
  #soloPanel .arenapick{border-top:0;padding-top:0}
  .seg button{padding:4px 9px;font-size:10.5px;letter-spacing:.04em}
  .seg button.sel{border-color:var(--spark);color:var(--spark);background:rgba(63,231,255,.12)}
  .rrow input[type=range]{flex:1;max-width:130px;accent-color:var(--spark);background:transparent;cursor:pointer}
  .plate.hidden-wiz{opacity:.12;filter:blur(1.5px)}
  .room .note{font-family:var(--prose);font-size:12.5px;line-height:1.5;color:var(--muted);margin:0;text-transform:none;letter-spacing:0}
  /* the curtain is a stack of panels; exactly one is ever unhidden */
  .panel{display:flex;flex-direction:column;align-items:center;gap:11px;width:100%}
  .panel[hidden]{display:none}
  button.back{border-color:var(--edge);color:var(--muted);padding:9px 16px}
  button.back:hover{border-color:#5a4f7a;color:var(--chalk)}
  /* the invite code — the one thing on the host screen you have to be able to read across a room */
  .invite{display:flex;align-items:center;justify-content:space-between;gap:12px;
    border:1px solid color-mix(in srgb, var(--spark) 40%, var(--edge));border-radius:3px;
    background:rgba(63,231,255,.06);padding:9px 12px}
  .invite>span{font-size:10px;letter-spacing:.24em;text-transform:uppercase;color:var(--muted);white-space:nowrap}
  .invite b{font-family:var(--mono);font-size:27px;letter-spacing:.34em;font-weight:700;color:var(--spark);
    text-shadow:0 0 22px color-mix(in srgb, var(--spark) 55%, transparent);line-height:1;padding-left:.34em}
  .invite b.waiting{color:var(--muted);text-shadow:none;letter-spacing:.2em;font-size:20px}
  .invite button{padding:6px 12px;font-size:10px;letter-spacing:.14em}
  .invite button.done{border-color:var(--ward);color:var(--ward)}
  .codefield{display:flex;align-items:center;gap:10px;font-size:10px;letter-spacing:.22em;text-transform:uppercase;color:var(--muted)}
  .codefield input{font-family:var(--mono);font-size:24px;letter-spacing:.3em;text-transform:uppercase;
    color:var(--spark);background:rgba(63,231,255,.05);border:1px solid var(--edge);border-radius:3px;
    padding:7px 10px;width:150px;text-align:center;padding-left:calc(10px + .3em)}
  .codefield input:focus{outline:none;border-color:var(--spark);box-shadow:0 0 14px -6px var(--spark)}
  /* Who has actually turned up — as blocks, so the answer is a colour rather
     than a sentence: dashed and dim is an empty seat, amber is someone who has
     arrived, green is someone ready. */
  .roster{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));
    gap:6px;width:100%}
  .roster:empty{display:none}
  .seat{display:flex;align-items:center;gap:8px;min-width:0;
    padding:7px 9px;border-radius:4px;
    border:1px solid var(--edge);background:rgba(255,255,255,.02);
    transition:border-color .18s, background .18s, color .18s}
  .seat-ico{width:15px;height:15px;flex:0 0 auto;opacity:.85}
  .seat-body{display:flex;flex-direction:column;gap:1px;min-width:0}
  .seat-name{font-size:12px;font-weight:600;color:#cfc6e6;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .seat i{font-style:normal;font-size:9px;letter-spacing:.16em;text-transform:uppercase;
    color:var(--muted)}
  .roster em.lv{font-style:normal;font-family:var(--mono);font-size:9.5px;letter-spacing:.06em;
    color:var(--spark);border:1px solid var(--edge);border-radius:2px;padding:0 4px;
    margin-left:auto;flex:0 0 auto}

  /* an empty seat: dashed, quiet, and wearing the mark of what it becomes */
  .seat.open{border-style:dashed;background:transparent;color:var(--muted)}
  .seat.open .seat-name{color:var(--muted);font-weight:400;font-style:italic}
  .seat.open .seat-ico{opacity:.45}

  /* somebody is here, and the lobby is waiting on them */
  .seat.here{border-color:var(--hex);background:rgba(255,143,58,.10);color:var(--hex)}
  .seat.here .seat-name{color:#ffd0a6}
  .seat.here i{color:var(--hex)}

  /* and they have said they are ready */
  .seat.ready{border-color:var(--ward);background:rgba(93,255,171,.10);color:var(--ward)}
  .seat.ready .seat-name{color:#bdffe0}
  .seat.ready i{color:var(--ward)}

  .seat.me{box-shadow:0 0 0 1px rgba(255,255,255,.16) inset}
  .note.bad{color:var(--blood)}
  /* the lobby note carries errors now and nothing else, so it should occupy
     nothing at all when there is no error to carry */
  .note:empty{display:none}

  /* The round trip, in the corner of the arena. Above the curtain so the lobby
     shows it too — knowing the link is bad is most useful BEFORE you start. */
  .pingtag{position:absolute;top:8px;right:10px;z-index:4;pointer-events:none;
    font-family:var(--mono);font-size:10px;letter-spacing:.1em;
    padding:3px 7px;border-radius:3px;
    border:1px solid var(--edge);background:rgba(6,4,12,.66);color:var(--muted)}
  .pingtag[hidden]{display:none}
  .pingtag.good{color:var(--ward);border-color:color-mix(in srgb, var(--ward) 40%, var(--edge))}
  .pingtag.far{color:var(--hex);border-color:color-mix(in srgb, var(--hex) 40%, var(--edge))}
  .pingtag.bad{color:var(--blood);border-color:color-mix(in srgb, var(--blood) 40%, var(--edge))}
  .pausebox{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;
    background:rgba(6,4,12,.82);backdrop-filter:blur(2px);z-index:3}
  .pausebox[hidden]{display:none}
  .pausebox h3{font-family:var(--display);font-weight:900;text-transform:uppercase;letter-spacing:.14em;font-size:26px;margin:0;color:var(--chalk)}
  .board{width:min(400px,92%);border:1px solid var(--edge);border-radius:3px;background:rgba(10,7,18,.72);padding:9px 12px}
  .board[hidden]{display:none}
  .board h4{margin:0 0 8px;font-family:var(--display);font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--muted);font-weight:600}
  .board ol{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:2px}
  .board li{display:grid;grid-template-columns:18px 1fr auto;gap:10px;font-size:12px;font-variant-numeric:tabular-nums;color:#a99fc4;padding:3px 0;border-bottom:1px solid rgba(42,35,64,.5);text-align:left}
  .board li:last-child{border-bottom:0}
  .board li.fresh{color:#fff}
  .board li.fresh b{color:var(--blood)}
  .board li b{font-weight:600;color:#c4bade}
  .board .none{font-size:12px;color:var(--muted)}
  button.go{border-color:var(--spark);color:var(--spark);padding:12px 26px}
  button.go:hover{background:rgba(63,231,255,.12)}

  .sound{margin-top:4px;padding:5px 11px;font-size:10px;letter-spacing:.18em;opacity:.72}
  .sound:hover{opacity:1}
  .book{display:grid;grid-template-columns:repeat(7,1fr);gap:8px}
  .card{
    position:relative;border:1px solid var(--edge);border-radius:3px;background:#0f0c1c;
    padding:8px 8px 9px;display:flex;flex-direction:column;gap:5px;overflow:hidden;min-width:0;
  }
  .card .ttl{display:flex;align-items:center;gap:6px}
  .card::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--c);transform:scaleX(var(--chg,0));transform-origin:left;transition:transform .05s linear}
  .card.lit{border-color:var(--c);box-shadow:inset 0 0 26px -14px var(--c)}
  .card.broke{opacity:.36}
  .card .k{
    font-size:11px;text-transform:uppercase;width:18px;height:18px;display:grid;place-items:center;
    border:1px solid var(--c);color:var(--c);border-radius:2px;
  }
  .card .n{font-family:var(--display);font-size:13px;letter-spacing:.04em;color:var(--chalk);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .card .m{font-size:10px;color:var(--muted);letter-spacing:.06em;display:flex;justify-content:space-between;gap:6px;font-variant-numeric:tabular-nums}
  .wt{display:flex;gap:3px}
  .wt i{width:5px;height:5px;background:var(--c);border-radius:50%;opacity:.85}
  .wt i.hollow{background:transparent;border:1px solid var(--c);opacity:.5}

  details.manual{border:1px solid var(--edge);border-radius:3px;background:#0d0a18}
  details.manual summary{cursor:pointer;padding:11px 14px;font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--muted);list-style:none}
  details.manual summary::-webkit-details-marker{display:none}
  details.manual summary::before{content:"▸ ";color:var(--rive)}
  details.manual[open] summary::before{content:"▾ "}
  details.manual summary:hover{color:var(--chalk)}
  .manual-body{padding:0 16px 18px;font-family:var(--prose);font-size:14.5px;line-height:1.65;color:#c8bfe0;display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(250px,1fr))}
  .manual-body h3{font-family:var(--display);font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:var(--chalk);margin:0 0 5px;font-weight:600}
  .manual-body ul{margin:0;padding-left:17px}
  .manual-body li{margin-bottom:5px}
  .manual-body em{color:var(--spark);font-style:normal;font-family:var(--mono);font-size:12.5px}
  /* The guide reads top to bottom: three steps, then the ideas, then the fine
     print. Each picture slot shows the path to drop a file at until one exists. */
  .manual-body{display:block}
  .guide{margin:0 0 18px}
  .guide h3{margin-bottom:10px}
  .steps{list-style:none;margin:0;padding:0;display:grid;gap:12px;align-items:start;
    grid-template-columns:repeat(auto-fit,minmax(280px,1fr));counter-reset:step}
  .steps>li{display:flex;flex-direction:column;gap:10px;counter-increment:step;
    border:1px solid var(--edge);border-radius:3px;background:rgba(255,255,255,.018);padding:12px 13px}
  .steps .step-txt b{display:block;font-family:var(--display);font-size:13.5px;
    letter-spacing:.03em;color:var(--chalk);margin-bottom:4px;font-weight:600}
  .steps .step-txt b::before{content:counter(step);display:inline-flex;align-items:center;
    justify-content:center;width:19px;height:19px;margin-right:8px;border-radius:50%;
    border:1px solid var(--spark);color:var(--spark);font-family:var(--mono);font-size:11px;
    vertical-align:2px}
  .manual-body p{margin:0 0 6px}
  .manual-body p:last-child{margin-bottom:0}
  .keys{font-size:13px;color:var(--muted)}
  .steps>li .shot{order:-1}
  .spells{list-style:none;margin:6px 0 0;padding:0}
  .spells li{margin:4px 0 0;padding-left:0;color:#c8bfe0;font-family:var(--prose);font-size:14px;line-height:1.5}
  .spells li kbd{min-width:18px;text-align:center}
  /* a picture slot: the image when there is one, a labelled space when there is not */
  .shot{margin:0;border-radius:3px;overflow:hidden;border:1px solid var(--edge);background:#0a0714}
  .shot img{display:block;width:100%;aspect-ratio:16/9;object-fit:contain;background:#0a0714}
  .shot figcaption{display:none}
  .shot.empty{border-style:dashed;border-color:#3a3358;background:
    repeating-linear-gradient(45deg,rgba(255,255,255,.012) 0 8px,transparent 8px 16px),#0a0714}
  .shot.empty img{display:none}
  .shot.empty figcaption{display:flex;flex-direction:column;align-items:center;justify-content:center;
    gap:5px;aspect-ratio:16/9;text-align:center;padding:8px;
    font-family:var(--display);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:#6b628c}
  .shot.empty figcaption span{font-family:var(--mono);font-size:10px;letter-spacing:.02em;
    text-transform:none;color:#4a4368}
  kbd{font-family:var(--mono);font-size:11px;border:1px solid var(--edge);border-bottom-width:2px;border-radius:3px;padding:1px 5px;background:#15112a;color:var(--chalk)}

  @media (max-width:720px){
    .book{grid-template-columns:repeat(4,1fr)}
    .rails .plate{flex:1 1 46%}
  }
  @media (prefers-reduced-motion:reduce){*{transition-duration:.01ms !important}}

  /* ======================================================= touch controls

     Everything below is inert until the game adds `touch` to <html>. A desktop
     never gets that class, so none of these rules can reach it — the pad and
     the rotate screen keep their `hidden` attribute and the layout above is
     exactly what it always was.

     The pad is DRAWN ON, never clicked. It is pointer-events:none and the
     sticks are hit-tested in window coordinates by the game, which means a
     full-screen overlay can sit above the arena without ever swallowing a tap
     meant for a menu button underneath it. */
  .pad[hidden]{display:none}
  .pad{position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:40}
  /* `display:flex` here beats the UA stylesheet's [hidden]{display:none}, so
     setting el.hidden = true would leave this screen sitting over the game in
     landscape with nothing to say why. The same trap already cost this project
     the Rounds and Lives sliders once. Any author rule that sets display must
     restate the hidden case. */
  .rotate[hidden]{display:none}
  .rotate{position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;
    background:radial-gradient(70% 70% at 50% 45%, rgba(13,10,24,.96), rgba(6,4,12,.99));
    text-align:center;padding:24px}
  .rotate-inner{max-width:22em;display:flex;flex-direction:column;align-items:center;gap:14px}
  .rotate h2{font-family:var(--serif,serif);font-size:20px;margin:0;color:var(--chalk)}
  .rotate p{margin:0;font-size:12px;line-height:1.6;color:var(--muted)}
  /* a phone, turning */
  .rotate-phone{width:44px;height:72px;border:2px solid var(--muted);border-radius:8px;
    animation:rotate-hint 2.4s ease-in-out infinite}
  @keyframes rotate-hint{
    0%,30%{transform:rotate(0deg)}
    55%,80%{transform:rotate(-90deg)}
    100%{transform:rotate(0deg)}
  }

  /* On a phone the page is the game: no scrolling, no rubber-banding, no
     double-tap zoom fighting the sticks. */
  html.touch, html.touch body{height:100%;overflow:hidden;overscroll-behavior:none}
  html.touch body{margin:0;padding:0;touch-action:none;-webkit-user-select:none;user-select:none}
  /* dvh, so the arena does not sit under the browser's own chrome */
  html.touch .shell{max-width:none;height:100vh;height:100dvh;gap:2px;padding:2px 4px}
  html.touch header.crest{display:none}
  html.touch .roundlabel{margin-top:0}

  /* Fit the arena to the height that is left, so it lands in the middle of the
     screen with clear gutters either side. That is where the sticks go: on a
     phone-shaped screen they sit BESIDE the arena rather than on top of it.

     The stage takes its height from `flex`, NOT from height:100%. A percentage
     height on a flex item whose only child is also height:100% is circular —
     the child asks the parent, the parent asks the child, and both settle on
     nothing. That collapsed the whole arena to its two border pixels. */
  html.touch .stage{flex:1 1 auto;min-height:0;width:auto;align-self:center;
    display:flex;align-items:center;justify-content:center}
  html.touch canvas#game{width:auto;height:100%;max-width:100%;object-fit:contain}
  html.touch .rails{display:none}
  /* The reference book and the cloak track are a desk-sized read. On a phone
     the page IS the arena — the menu inside the curtain carries everything a
     player needs to start a duel. */
  /* The reference strip names the six spells and their costs. So does the
     spell stick, in the same colours, without spending a sixth of a phone
     screen on it. */
  html.touch .pass, html.touch #book{display:none}

  /* ------------------------------------------------- How to play, on a phone

     The manual stays, because a player who cannot find out what Hexstone does
     is playing a worse game. But the page is exactly one screen tall here and
     the arena fills it, so the manual cannot live in the flow: the SUMMARY is a
     slim bar under the arena, and opening it lifts the body out into a
     scrollable sheet over the top of everything.

     Steps one and two name controls, so they are told twice: `.by-key` for a
     keyboard, `.by-touch` for thumbs. Step three is the same list on both —
     only its key badges are keyboard-only, because "Y" means nothing to a
     thumb but the spell it names means everything.

     Note the element on each selector. Written as a bare descendant rule,
     `li .by-touch` also matched the step paragraphs — they live inside <li> —
     and rendered them inline, running two steps together with no break. */
  .by-touch{display:none}
  html.touch p.by-touch{display:block}
  html.touch p.by-key,
  html.touch .spells kbd.by-key{display:none}
  /* the spell list carries its own indent once the badges are gone */
  html.touch .spells{padding-left:2px;list-style:none}
  /* The cooldown figure is a SPAN and not a <b> on purpose: `.steps .step-txt b`
     is the step-title rule, counter and all, so a <b> anywhere in this text
     turns into a numbered heading mid-sentence. */
  .dash-cd{font-family:var(--mono);color:var(--spark)}

  /* Above the menu, or the full-screen curtain covers the one control that is
     supposed to sit under it and How to play becomes untappable on a phone. */
  html.touch details.manual{flex:0 0 auto;border:0;border-top:1px solid var(--edge);
    border-radius:0;background:#0b0816;position:relative;z-index:55}
  html.touch details.manual summary{padding:5px 10px;font-size:10px;letter-spacing:.2em;
    text-align:center;color:var(--muted)}
  /* Opening it must not shove the arena off the screen, so the body leaves the
     flow entirely and becomes a sheet. */
  html.touch details.manual[open] .manual-body{
    position:fixed;left:0;right:0;top:0;bottom:0;z-index:70;
    margin:0;padding:16px 18px 26px;
    background:radial-gradient(80% 70% at 50% 0%, rgba(23,19,38,.99), rgba(6,4,12,.995));
    overflow-y:auto;-webkit-overflow-scrolling:touch;touch-action:pan-y;
    display:block}
  /* and it needs its own way out, since the summary is now behind it */
  html.touch details.manual[open] summary{
    position:fixed;top:0;left:0;right:0;z-index:71;
    padding:9px 10px;background:#120e22;border-bottom:1px solid var(--edge);
    color:var(--chalk)}
  html.touch details.manual[open] .manual-body{padding-top:42px}
  html.touch .manual-body{font-size:13.5px}
  html.touch .steps{grid-template-columns:1fr;gap:16px}
  /* One step per row, and the picture sits BESIDE its words rather than above
     them: full-width screenshots turn three steps into a very long scroll on a
     412px-tall screen. */
  html.touch .steps>li{flex-direction:row;align-items:flex-start;gap:12px}
  html.touch .step-txt{flex:1 1 auto;min-width:0;order:1}
  html.touch .shot{flex:0 0 34%;max-width:190px;order:2;margin:0}
  html.touch .shot figcaption{font-size:9.5px}

  /* ------------------------------------------------------- the menu, on a phone

     The menu was a panel inside the arena's picture frame, and on a phone in
     landscape that frame is about 250px tall — less than the menu needs. What
     you got was a title scrolled off the top of its own box and no way to reach
     it, because the old fit trick SCALED the content with a transform: a
     transform does not change layout, so centred content spills equally above
     and below, and nothing can scroll above zero.

     So on a phone the menu is a SCREEN, not a panel: fixed to the viewport,
     anchored to the top, scrolling honestly when it has more to say than fits.
     Sitting above the pad also means the thumb sticks can never show through a
     menu, whatever the game thinks it is doing behind it. */
  html.touch .curtain{position:fixed;inset:0;z-index:50;
    justify-content:flex-start;align-items:center;
    /* breathing room at the top on a taller screen, none to spare on a short
       one — a short screen needs every pixel for the panel itself */
    padding:clamp(10px,7vh,44px) 16px 18px;gap:8px;overflow-y:auto;
    -webkit-overflow-scrolling:touch;touch-action:pan-y;
    background:radial-gradient(80% 70% at 50% 0%, rgba(19,15,34,.97), rgba(6,4,12,.99))}
  /* margin:auto on the children is what centred them vertically; top-anchored
     now, or the first thing in a tall panel is the thing you cannot see */
  html.touch .curtain>*{margin:0 auto;flex:0 0 auto}
  html.touch .curtain h2{font-size:clamp(17px,3.4vh,26px);letter-spacing:.06em}
  html.touch .curtain p{font-size:12.5px;line-height:1.45;max-width:52ch}
  html.touch #curtainText{font-size:12px;color:#a99fc8}
  html.touch .panel{gap:6px}
  html.touch .whoami{margin-bottom:2px}

  /* Landscape is the playable orientation; portrait gets the rotate screen and
     the pad is pointless there. */
  @media (orientation:portrait){
    html.touch .pad{display:none}
  }
