*,*::before,*::after{box-sizing:border-box;}

  :root{
    --ink:#0c0e13;
    --panel:#13161d;
    --asphalt:#1c1f27;
    --verge:#2b3040;
    --line:#232733;
    --chalk:#e9e7e1;
    --slate:#7f8797;   /* 5.0:1 on the panel; #767c8d measured 4.34 and failed AA */
    --sodium:#ffb020;
    --sodium-dim:#8a5f14;
    --brake:#ff4f5a;

    --r1:#e0605c;
    --r2:#4bbf95;
    --r3:#7f7ae0;
    --r4:#4b96d6;

    --display:"Chakra Petch",'Trebuchet MS',sans-serif;
    --ui:"Barlow Semi Condensed","Helvetica Neue",Arial,sans-serif;
    --mono:"JetBrains Mono",ui-monospace,"SF Mono",Menlo,monospace;
  }

  /* The drill panel opens in the standings column part-way through a slow race,
     the document grows past the window, and a scrollbar arrives — which narrows
     the viewport and slides the centred shell four pixels left, mid-word. That
     is the screen jumping while somebody is typing, the oldest bug in this
     project, in its horizontal form; it went unseen for as long as the check
     sampled only the word row's TOP. Reserve the gutter always so the page
     cannot change width while a race is running. */
  /* The body is centred with margin:0 auto inside a max-width, so ANY change in
     the page's usable width moves it sideways — a scrollbar arriving mid-race
     slid the whole shell 4px left while the player was typing. scrollbar-gutter
     reserves the space where it is supported; overflow-y:scroll forces the track
     to exist at all times and is the version no engine can decline. Both, so the
     width the shell is centred in is a constant from first paint. */
  html{scrollbar-gutter:stable;overflow-y:scroll;}

  body{
    background:var(--ink);
    color:var(--chalk);
    font-family:var(--ui);
    font-size:15px;
    line-height:1.45;
    -webkit-font-smoothing:antialiased;
  }

  .shell{
    max-width:1180px;
    margin:0 auto;
    padding:22px 20px 40px;
    display:flex;
    flex-direction:column;
    gap:18px;
  }

  /* ---------- header ---------- */
  .masthead{
    display:flex;
    align-items:flex-end;
    justify-content:space-between;
    gap:20px;
    flex-wrap:wrap;
    border-bottom:1px solid var(--line);
    padding-bottom:14px;
  }
  .brand{display:flex;flex-direction:column;gap:2px;}
  .brand h1{
    font-family:var(--display);
    font-weight:700;
    font-size:29px;
    letter-spacing:.02em;
    margin:0;
    text-wrap:balance;
  }
  .brand h1 em{font-style:normal;color:var(--sodium);}
  .brand p{
    margin:0;
    color:var(--slate);
    font-size:13px;
    letter-spacing:.14em;
    text-transform:uppercase;
    font-weight:500;
  }

  .readouts{display:flex;gap:26px;}
  .readout{display:flex;flex-direction:column;gap:1px;min-width:66px;}
  .readout .k{
    font-size:10px;letter-spacing:.16em;text-transform:uppercase;
    color:var(--slate);font-weight:600;
  }
  .readout .v{
    font-family:var(--display);font-weight:600;font-size:25px;
    font-variant-numeric:tabular-nums;line-height:1.1;
  }
  .readout .v small{font-size:14px;color:var(--slate);font-weight:500;}

  /* ---------- main grid ---------- */
  .arena{
    display:grid;
    grid-template-columns:1fr 268px;
    gap:18px;
    align-items:start;
  }
  /* grid items are min-width:auto by default, so a single wide child drags the
     whole column past the viewport — this is what broke the phone layout */
  .arena > *{min-width:0;}
  .panel-col,.strip,.standings,.track-wrap{min-width:0;}

  /* ---------- track ---------- */
  .track-wrap{
    position:relative;
    background:var(--panel);
    border:1px solid var(--line);
    border-radius:4px;
    overflow:hidden;
  }
  canvas{display:block;width:100%;height:auto;}
  /* The start screen floats inside this panel, and the panel's height comes from
     the canvas aspect ratio — so a tall, narrowish desktop window gave the
     overlay under 400px and buried the setup controls inside its own scroll.
     Reserve room where the window has room to give, and nowhere else: the min()
     collapses to nothing on a short laptop, which must not grow a scrollbar. */
  .track-wrap{min-height:min(560px, 62vh);}

  /* Race events live inside the word strip, in a row reserved at a fixed height
     so an arriving event can never reflow the words. They have to be here and
     not on the track panel: with a soft keyboard up, the phone shows only the
     header and the strip — the circuit is behind the keyboard. */
  .events{
    height:22px;flex:none;
    display:flex;justify-content:flex-end;align-items:center;gap:6px;
    overflow:hidden;pointer-events:none;
  }
  .ev{
    font-family:var(--display);font-weight:600;font-size:12.5px;
    flex:none;
    letter-spacing:.07em;text-transform:uppercase;
    padding:5px 11px 5px 9px;border-radius:2px;
    background:rgba(10,12,17,.88);
    border-left:3px solid var(--slate);
    color:var(--chalk);
    white-space:nowrap;
    animation:evin .18s ease-out;
  }
  .ev.good{border-left-color:var(--sodium);color:var(--sodium);}
  .ev.bad{border-left-color:var(--brake);color:var(--brake);}
  .ev.flag{border-left-color:var(--chalk);}
  @keyframes evin{
    from{opacity:0;transform:translateX(10px);}
    to{opacity:1;transform:none;}
  }
  .strip{position:relative;}

  /* The overlays and the canvas share ONE grid cell rather than the overlay
     floating at inset:0 with its own scrollbar. Absolute positioning fixes the
     overlay to the canvas's height, so anything taller than the canvas — the
     results, and now the records with the backup controls under them — put its
     own buttons below an internal fold where nobody finds them. That defect has
     been paid for three times already: once on phones (the overlay was made to
     flow underneath the circuit), once when the course and championship rows
     were squashed into single wrapping lines, and once when the results grew a
     sticky footer. Stacking instead lets the taller of the two set the panel
     height, so opening a disclosure pushes the panel down rather than hiding
     what it just revealed. */
  /* Two rows now: the word band, then the circuit. The band is a ROW and not a
     layer floating over the canvas, because every circuit is normalised to fill
     the canvas box — the largest car-free centred region any of the three leaves
     is about 36px of a 600px-tall canvas on Chicane, so an overlay large enough
     to read would sit on top of the cars. A reserved row cannot: the word's box
     and the canvas's box do not intersect, which is what the checks measure.
     The overlays span both rows so a setup or results screen covers the word
     band too. The canvas row is 1fr and not auto so that the panel's min-height
     on a tall window is absorbed BELOW the circuit: two auto rows both stretch,
     which grew the word band on a tall narrow window and pushed the circuit
     down with it. */
  .track-wrap{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto 1fr;}
  .track-wrap > .word-stage{grid-area:1 / 1;}
  /* The overlay deliberately spans BOTH rows so it can cover the circuit, which
     means the two genuinely overlap and paint order decides the winner. DOM
     order alone is not enough: iOS Safari promotes a canvas to its own
     compositing layer, and a composited layer paints above in-flow content that
     has no stacking context of its own. On a real iPhone that split the start
     card in half — the heading drawn over the word band, the championship line
     sliced by the canvas — while Chrome, Playwright's WebKit and this project's
     whole responsive matrix all showed it correct, because every one of those
     checks measures geometry and geometry cannot see compositing. Both layers
     are now named explicitly rather than left to the engine. */
  .track-wrap > canvas{grid-area:2 / 1;align-self:start;z-index:0;}
  .track-wrap > .overlay{grid-area:1 / 1 / 3 / 2;z-index:1;}
  .overlay{
    position:relative;
    display:flex;flex-direction:column;
    align-items:center;justify-content:safe center;
    gap:16px;
    background:rgba(8,10,14,.86);
    text-align:center;
    padding:24px;
    min-height:100%;
  }
  .overlay[hidden]{display:none;}
  .overlay h2:focus-visible{outline:2px solid var(--sodium);outline-offset:4px;}

  /* a heading that names the dialog for assistive tech without repeating the
     card's own title on screen */
  .sr-title{
    position:absolute;width:1px;height:1px;
    margin:-1px;padding:0;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap;
  }

  .lights{display:flex;gap:9px;}
  .lights span{
    width:26px;height:26px;border-radius:50%;
    background:#20242e;
    border:2px solid #2d323e;
    transition:background .1s linear,box-shadow .1s linear;
  }
  .lights span.on{
    background:var(--brake);
    border-color:#ff7a82;
    box-shadow:0 0 16px rgba(255,79,90,.75);
  }
  .go-word{
    font-family:var(--display);font-weight:700;font-size:52px;
    letter-spacing:.1em;color:var(--sodium);
    text-shadow:0 0 26px rgba(255,176,32,.45);
  }
  .overlay h2{
    font-family:var(--display);font-weight:700;font-size:31px;margin:0;
    letter-spacing:.01em;
  }
  .overlay .sub{color:var(--slate);max-width:min(44ch,100%);margin:0;font-size:14px;}

  /* THE WAYS IN. Free play, the course and the championship, stacked directly
     under the heading and above the settings. All three are primary actions, so
     none of them may be below the fold - which is what check-layout.mjs now
     asserts for every screen in the matrix rather than merely printing.
     The dividers come from .course/.champ themselves, so this is only the
     column that holds them. */
  .ways{
    display:flex;flex-direction:column;align-items:center;
    gap:10px;width:100%;max-width:100%;
  }

  /* The course: one row above the settings, because being told what to practise
     is the thing a beginner needs and choosing it is not. */
  /* One wrapping row, not three stacked lines. With two of these entries above
     the settings — the course and the championship — the stacked form pushed the
     grid buttons past the bottom of the overlay on a laptop screen, which is a
     control the player cannot reach. */
  .course{
    display:flex;flex-direction:row;flex-wrap:wrap;
    align-items:baseline;justify-content:center;gap:3px 10px;
    width:100%;max-width:100%;
    padding-bottom:7px;border-bottom:1px solid var(--line);
  }
  .course-line{
    margin:0;font-family:var(--display);font-size:14px;font-weight:600;
    color:var(--sodium);letter-spacing:.02em;text-align:center;
  }
  .course-bar{
    margin:0;font-size:12px;color:var(--slate);text-align:center;
    max-width:min(46ch,100%);
  }
  #btn-course,#btn-champ{font-size:12px;padding:8px 18px;}
  #btn-course[hidden],#btn-champ[hidden]{display:none;}

  /* The championship sits directly under the course: the same kind of offer,
     here is what to race next. It is laid out as one wrapping row rather than
     three stacked lines, because two full-width entries above the settings is
     what pushed the grid buttons out of the reach of a laptop screen. */
  .champ{
    flex-direction:row;flex-wrap:wrap;align-items:baseline;
    justify-content:center;gap:2px 10px;padding-bottom:7px;
  }
  .champ .course-line{font-size:13px;}
  .champ .course-bar{font-size:11.5px;line-height:1.35;max-width:min(52ch,100%);}
  .champ-result{
    display:flex;flex-direction:column;align-items:flex-start;gap:8px;
    width:100%;max-width:100%;
  }
  .champ-result[hidden]{display:none;}
  .champ-table{width:100%;max-width:min(38ch,100%);}
  .champ-table th{
    font-family:var(--display);font-size:11px;letter-spacing:.09em;
    text-transform:uppercase;color:var(--slate);text-align:left;padding-bottom:4px;
    border-bottom:1px solid var(--line);
  }

  .course-verdict{
    margin:0;max-width:min(60ch,100%);
    font-size:13px;line-height:1.5;color:var(--chalk);
    border-left:3px solid var(--slate);
    padding:2px 0 2px 11px;text-align:left;
  }
  .course-verdict[data-pass="true"],
  .course-verdict[data-done="true"]{border-left-color:var(--sodium);}
  .course-verdict[hidden]{display:none;}

  .setup{
    display:flex;flex-direction:column;align-items:center;gap:11px;
    width:100%;max-width:100%;
  }
  .setup-summary{
    margin:0;font-size:12.5px;color:var(--chalk);
    letter-spacing:.02em;text-align:center;
  }
  .setup-summary b{color:var(--sodium);font-weight:600;}
  .disclose{
    font-family:var(--ui);font-size:10px;font-weight:600;
    letter-spacing:.16em;text-transform:uppercase;
    background:none;border:0;padding:2px 0;cursor:pointer;
    color:var(--slate);
    text-decoration:underline;text-underline-offset:3px;
  }
  .disclose:hover{color:var(--chalk);}
  .disclose:focus-visible{outline:2px solid var(--sodium);outline-offset:3px;}
  .setup-panel{
    display:flex;flex-direction:column;align-items:center;gap:9px;
    width:100%;
  }
  .setup-panel[hidden]{display:none;}

  /* Nine record slots exist — three circuits by three distances — and until now
     you could only ever see the one you were sitting on. Each cell selects that
     combination, so a record is something you can go and attack. */
  .records{
    display:grid;grid-template-columns:auto repeat(3, 1fr);
    gap:4px;align-items:center;
    width:100%;max-width:420px;
    font-variant-numeric:tabular-nums;
  }
  .records[hidden]{display:none;}
  .records .rh{
    font-size:9px;letter-spacing:.14em;text-transform:uppercase;
    color:var(--slate);font-weight:600;text-align:center;padding-bottom:2px;
  }
  .records .rc{
    font-size:11px;color:var(--slate);font-weight:600;
    letter-spacing:.06em;text-align:right;padding-right:4px;
  }
  .records button{
    font-family:var(--mono);font-size:11.5px;
    background:var(--asphalt);color:var(--chalk);
    border:1px solid var(--line);border-radius:2px;
    padding:6px 4px;cursor:pointer;
  }
  .records button:hover{border-color:var(--slate);}
  .records button[aria-pressed="true"]{
    border-color:var(--sodium);color:var(--sodium);
  }
  .records button.empty{color:var(--verge);}
  .records button:focus-visible{outline:2px solid var(--sodium);outline-offset:2px;}

  /* All of the above lives in one browser and nowhere else. This is the way out
     of that: a code to copy, and a box to paste one back into. It sits under the
     records because that is where the progress it carries already is. */
  .backup{
    display:flex;flex-direction:column;align-items:center;gap:7px;
    width:100%;max-width:420px;
    border-top:1px solid var(--line);padding-top:11px;
  }
  .backup[hidden]{display:none;}
  .backup-h{
    margin:0;font-family:var(--display);font-weight:600;font-size:11px;
    letter-spacing:.16em;text-transform:uppercase;color:var(--slate);
  }
  .backup .setup-why{text-align:center;}
  .backup .share-link{margin-bottom:0;}
  .backup-acts{
    display:flex;gap:8px;flex-wrap:wrap;justify-content:center;width:100%;
  }
  .backup-acts .btn{font-size:12px;padding:8px 14px;letter-spacing:.08em;}
  .backup-restore{
    display:flex;flex-direction:column;align-items:center;gap:7px;width:100%;
    border-top:1px dashed var(--line);padding-top:9px;
  }
  .backup-restore[hidden]{display:none;}
  .backup-lab{
    font-size:10px;letter-spacing:.14em;text-transform:uppercase;
    color:var(--slate);font-weight:600;
  }
  .backup-warn b{color:var(--brake);}
  .backup .verdict{text-align:center;max-width:52ch;line-height:1.45;}

  .seg-row{
    display:flex;align-items:center;gap:11px;
    max-width:100%;flex-wrap:wrap;justify-content:center;
  }
  .seg-label{
    font-size:10px;letter-spacing:.16em;text-transform:uppercase;
    color:var(--slate);font-weight:600;
    width:58px;text-align:right;flex:none;
  }
  .seg{
    display:inline-flex;flex-wrap:wrap;border:1px solid var(--verge);
    border-radius:2px;overflow:hidden;max-width:100%;
  }
  .seg button{
    font-family:var(--ui);font-size:12px;font-weight:600;
    letter-spacing:.12em;text-transform:uppercase;
    background:none;border:0;color:var(--slate);
    padding:7px 16px;cursor:pointer;
    transition:background .12s ease,color .12s ease;
  }
  .seg button + button{border-left:1px solid var(--verge);}
  .seg button:hover:not(:disabled){color:var(--chalk);}
  .seg button[aria-pressed="true"]{background:var(--sodium);color:var(--ink);}
  .seg button:disabled{opacity:.4;cursor:not-allowed;}
  .seg button:focus-visible{outline:2px solid var(--chalk);outline-offset:-2px;}

  .grid-preview{
    display:flex;gap:14px;flex-wrap:wrap;justify-content:center;
    font-variant-numeric:tabular-nums;
  }
  .grid-preview div{display:flex;align-items:center;gap:6px;font-size:13px;}
  .grid-preview i{
    width:8px;height:8px;border-radius:1px;flex:none;font-style:normal;
  }
  .grid-preview b{
    font-family:var(--display);font-weight:600;font-size:14px;color:var(--chalk);
  }
  .grid-preview span{color:var(--slate);}
  /* How the car races, beside what it is rated at. Same ink as the name so it
     reads as one label rather than as a second column. */
  .grid-preview em{font-style:normal;color:var(--slate);font-size:12px;}
  .setup-why{
    margin:0;font-size:11px;color:var(--slate);max-width:min(52ch,100%);line-height:1.45;
  }

  .btn{
    font-family:var(--display);font-weight:600;font-size:15px;
    letter-spacing:.1em;text-transform:uppercase;
    color:var(--ink);background:var(--sodium);
    border:0;border-radius:2px;
    padding:11px 26px;cursor:pointer;
    transition:transform .12s ease,background .12s ease;
  }
  /* The results are taller than the track panel on a short window and the overlay
     scrolls, which put Race again below the fold on a 1024x620 laptop. Pin the
     action in a bar at the bottom of the scroll area so it is always reachable,
     with the content fading under it rather than colliding with it. */
  .overlay-foot{
    position:sticky;bottom:0;gap:10px;flex-wrap:wrap;
    width:100%;margin-top:2px;
    display:flex;justify-content:center;
    padding:14px 0 2px;
    background:linear-gradient(to top,
      rgba(8,10,14,.97) 0%, rgba(8,10,14,.97) 62%, rgba(8,10,14,0) 100%);
  }
  /* The results screen's actions are no longer a FOOT — they sit directly under
     the result, above the classification table and the key chart, because that
     is where the player is looking when the race ends. Sticking them to the
     bottom of a scroll area is the right answer for a screen whose action is
     genuinely last (the start card); it is the wrong answer for one whose
     action is the first thing wanted. */
  .fin-act{
    position:static;background:none;
    margin:2px 0 4px;padding:0;
  }

  .btn-quiet{
    background:none;color:var(--slate);
    box-shadow:inset 0 0 0 1px var(--verge);
  }
  .btn-quiet:hover{background:none;color:var(--chalk);box-shadow:inset 0 0 0 1px var(--slate);}

  .btn:hover{background:#ffc24d;transform:translateY(-1px);}
  .btn:focus-visible{outline:2px solid var(--chalk);outline-offset:3px;}

  /* results table */
  .results{
    width:100%;border-collapse:collapse;
    font-variant-numeric:tabular-nums;
  }
  .results th{
    font-size:10px;letter-spacing:.15em;text-transform:uppercase;
    color:var(--slate);text-align:left;font-weight:600;
    padding:0 0 6px;border-bottom:1px solid var(--line);
  }
  .results td{
    padding:7px 0;border-bottom:1px solid var(--line);
    font-size:14px;text-align:left;
  }
  .results td.num{text-align:right;font-family:var(--mono);font-size:13px;}
  .results tr.you td{color:var(--sodium);font-weight:600;}
  .results .pos{
    font-family:var(--display);font-weight:700;width:26px;color:var(--slate);
  }
  .results tr.you .pos{color:var(--sodium);}

  .tally{
    display:flex;gap:28px;justify-content:center;
    font-variant-numeric:tabular-nums;
  }
  .tally div{display:flex;flex-direction:column;gap:1px;}
  .tally .k{font-size:10px;letter-spacing:.15em;text-transform:uppercase;color:var(--slate);font-weight:600;}
  .tally .v{font-family:var(--display);font-weight:700;font-size:24px;}

  .fin-grid{
    display:grid;grid-template-columns:1fr 1fr;gap:30px;
    width:100%;max-width:800px;text-align:left;
  }
  .fin-col{display:flex;flex-direction:column;gap:10px;min-width:0;}
  .fin-col .panel-title{padding-bottom:7px;}

  .keychart{display:flex;flex-direction:column;gap:5px;}
  .krow{
    display:grid;grid-template-columns:16px 1fr 50px 42px 24px;
    gap:9px;align-items:center;
  }
  .kch{
    font-family:var(--mono);font-weight:700;font-size:14px;
    color:var(--chalk);text-align:center;
  }
  .ktrack{
    display:block;position:relative;height:13px;
    background:var(--asphalt);border-radius:2px;
  }
  .kbar{
    display:block;height:100%;background:var(--sodium);
    border-radius:0 3px 3px 0;
  }
  .kbase{
    position:absolute;top:-3px;bottom:-3px;width:1px;
    background:var(--slate);
  }
  .kval{
    font-family:var(--mono);font-size:11px;color:var(--slate);
    text-align:right;font-variant-numeric:tabular-nums;
  }
  .ktrend{
    font-family:var(--mono);font-size:10.5px;font-weight:700;
    color:var(--slate);text-align:right;
    font-variant-numeric:tabular-nums;
  }
  .ktrend.better{color:var(--sodium);}
  .ktrend.worse{color:var(--brake);}

  .kmiss{
    font-family:var(--mono);font-size:11px;font-weight:700;
    color:var(--brake);text-align:right;
  }
  .chart-note{
    margin:2px 0 0;font-size:11px;color:var(--slate);line-height:1.4;
  }
  .chart-empty{
    margin:0;font-size:13px;color:var(--slate);
  }

  /* Result card — sized and contrasted to survive being screenshotted and
     posted somewhere else, where none of this page's context comes with it. */
  .card{
    width:100%;max-width:420px;
    background:var(--panel);
    border:1px solid var(--verge);
    border-radius:4px;
    padding:16px 18px 18px;
    display:flex;flex-direction:column;gap:12px;
    text-align:left;
  }
  .card-top{
    display:flex;justify-content:space-between;align-items:baseline;gap:10px;
    font-size:10px;letter-spacing:.16em;text-transform:uppercase;font-weight:600;
  }
  .card-mark{color:var(--sodium);}
  .card-date{color:var(--slate);}
  .card-place{
    font-family:var(--display);font-weight:700;font-size:30px;
    line-height:1.05;letter-spacing:.01em;
  }
  .card-stats{
    display:flex;gap:22px;flex-wrap:wrap;
    border-top:1px solid var(--line);border-bottom:1px solid var(--line);
    padding:11px 0;
    font-variant-numeric:tabular-nums;
  }
  .card-stats div{display:flex;flex-direction:column;gap:1px;}
  .card-stats .k{
    font-size:9px;letter-spacing:.15em;text-transform:uppercase;
    color:var(--slate);font-weight:600;
  }
  .card-stats b{font-family:var(--display);font-weight:700;font-size:24px;}
  .card-stats .u{font-size:10px;color:var(--slate);margin-top:-2px;}
  .card-meta{font-size:12px;color:var(--slate);}
  .card-grid{
    display:flex;gap:10px;flex-wrap:wrap;
    font-size:11.5px;color:var(--slate);
    font-variant-numeric:tabular-nums;
  }
  .card-grid span{display:flex;align-items:center;gap:5px;}
  .card-grid i{width:7px;height:7px;border-radius:1px;font-style:normal;flex:none;}
  .card-grid b{color:var(--chalk);font-weight:600;}
  .card-grid .beaten{color:var(--sodium);}

  .share-link{
    width:100%;max-width:420px;margin-bottom:6px;
    font-family:var(--mono);font-size:11px;
    color:var(--slate);background:var(--asphalt);
    border:1px solid var(--line);border-radius:2px;
    padding:7px 9px;
  }
  .share-link:focus{outline:2px solid var(--sodium);outline-offset:1px;color:var(--chalk);}

  .coach{
    margin:0;max-width:min(60ch,100%);
    font-size:13px;line-height:1.5;color:var(--chalk);
    border-left:3px solid var(--sodium);
    padding:2px 0 2px 11px;text-align:left;
  }
  .coach:empty{display:none;}
  .coach-do{
    font-size:12px;padding:8px 16px;
    align-self:flex-start;margin:-4px 0 0 14px;
  }
  .coach-do[hidden]{display:none;}
  .coach-do.done{
    color:var(--sodium);box-shadow:inset 0 0 0 1px var(--sodium-dim);
    cursor:default;
  }

  .pb-note{
    margin:0;font-size:12px;font-weight:600;
    letter-spacing:.14em;text-transform:uppercase;
    color:var(--sodium);
  }
  .pb-note.off{color:var(--slate);}
  .pb-note:empty{display:none;}

  /* ---------- typing strip ---------- */
  .strip{
    background:var(--panel);
    border:1px solid var(--line);
    border-radius:4px;
    padding:18px 20px 16px;
    display:flex;flex-direction:column;gap:14px;
    margin-top:10px;
  }
  /* The band reserved for the word at the top of the play panel. Its height is
     the word row plus the guide, and neither depends on the word: the row is a
     fixed height and the word is a full-width block that centres its glyphs, so
     the element's box is identical for "at" and for "extraordinary" and
     identical on every keystroke within a word. */
  .word-stage{
    padding:14px 18px 12px;
    display:flex;flex-direction:column;gap:9px;
    border-bottom:1px solid var(--line);
  }
  .word-line{
    position:relative;
    display:flex;align-items:center;justify-content:center;
    height:56px;overflow:hidden;
  }
  .word{
    font-family:var(--mono);font-weight:700;
    font-size:40px;letter-spacing:.01em;
    white-space:nowrap;overflow:hidden;
    line-height:1.1;
    /* Centred across the whole band, with the sides kept clear of the queue that
       is parked at the right end of it. The padding is a percentage of a box
       whose width does not depend on the word, so the reserved area is the same
       for every word — it keeps the two apart at a narrow desktop window without
       either one being sized by its contents. */
    width:100%;text-align:center;padding:0 18%;
  }
  .word .done{color:var(--sodium);}
  .word .cur{
    color:var(--ink);background:var(--sodium);
    border-radius:2px;
    box-shadow:0 0 0 2px var(--sodium);
  }
  .word .rest{color:var(--chalk);opacity:.55;}
  /* a space you have to type has to be something you can see */
  .word .sp{
    border-bottom:2px solid var(--sodium);
    border-radius:0 0 2px 2px;
  }
  .word .rest.sp{border-bottom-color:var(--verge);}
  /* The error cue may not MOVE the word. This animation used to shake it
     sideways by up to 6px, and getBoundingClientRect reports a transform, so
     #word's left read 47 / 45 / 43 / 41 / 51 depending on how far into the
     .18s the sample landed. That is the "screen jumps while I start typing"
     bug this project was founded on, in the one place feedback was allowed to
     move a box. It hid for many iterations because the assertion measured only
     the word's top, and it only ever showed up in a full suite because the
     animation runs on real time: standalone, the next keystroke arrives within
     a millisecond of the mistake and the sample lands at translateX(0).
     So the cue is now colour only — background and shadow take part in no
     layout and are invisible to getBoundingClientRect. Same rule the key
     diagram already states: a highlight is a colour and a shadow, never a
     border, a padding or a nudge. */
  .word.err{animation:jolt .18s ease;}
  .word.err .cur{background:var(--brake);color:#fff;}
  @keyframes jolt{
    0%,100%{background-color:transparent;box-shadow:none;}
    35%{background-color:rgba(255,79,90,.26);
        box-shadow:0 0 0 2px rgba(255,79,90,.55) inset;}
  }
  /* Out of flow, at the right end of the fixed-height row: the queue's width
     changes every word, and in flow that width would have decided where the
     centred word sat. Its parent has an explicit height, so this is not the
     inset:0 overlay that has inherited a sibling's height four times here. */
  .queue{
    position:absolute;right:0;top:50%;transform:translateY(-50%);
    max-width:16%;
    display:flex;gap:14px;align-items:center;
    font-family:var(--mono);font-size:17px;color:var(--slate);
    white-space:nowrap;overflow:hidden;
  }
  .queue span:nth-child(2){opacity:.6;}
  .queue span:nth-child(3){opacity:.35;}

  /* ---------- home-position keyboard guide ----------
     Every key is the same size whether or not it is lit: the highlight is a
     colour and a shadow, never a border or a padding, because the one bug this
     project has shipped most is the screen moving while somebody is typing. */
  .kb{
    display:flex;flex-direction:column;gap:4px;align-items:center;
    padding:2px 0 0;
  }
  .kb[hidden]{display:none;}
  body.touch .kb{display:none;}
  body.touch .kg-row{display:none;}
  .kb-row{display:flex;gap:4px;justify-content:center;}
  /* display:flex would override the hidden attribute, and the digits row is
     hidden on every step but one — so the row has to say so itself. */
  .kb-row[hidden]{display:none;}
  .kbk{
    position:relative;
    width:26px;height:26px;line-height:24px;
    font-family:var(--mono);font-size:12px;font-weight:400;
    text-align:center;
    border-radius:3px;
    background:var(--asphalt);
    border:1px solid var(--line);
    color:#9aa2b2;
  }
  .kbk.home{color:var(--chalk);border-color:var(--verge);background:#22262f;}
  /* the two keys your index fingers find without looking */
  .kbk.anchor::after{
    content:"";position:absolute;left:7px;right:7px;bottom:3px;height:2px;
    background:var(--sodium);border-radius:1px;
  }
  .kb-space{width:132px;}
  .kb-shift{width:52px;font-size:9px;letter-spacing:.05em;}
  .kbk.on{
    background:var(--kbc);border-color:var(--kbc);
    color:var(--ink);font-weight:700;
    box-shadow:0 0 0 2px var(--kbc), 0 0 12px rgba(255,255,255,.18);
    animation:kbpulse 1.1s ease-in-out infinite;
  }
  .kbk.on.anchor::after{background:var(--ink);}
  .kbk[data-f="lp"]{--kbc:#e0605c;}
  .kbk[data-f="lr"]{--kbc:#e08a4c;}
  .kbk[data-f="lm"]{--kbc:#d7c052;}
  .kbk[data-f="li"]{--kbc:#8ac96a;}
  .kbk[data-f="ri"]{--kbc:#4bbf95;}
  .kbk[data-f="rm"]{--kbc:#4b96d6;}
  .kbk[data-f="rr"]{--kbc:#9a92ea;}
  .kbk[data-f="rp"]{--kbc:#d68ad0;}
  .kbk[data-f="th"]{--kbc:var(--sodium);}
  @keyframes kbpulse{
    0%,100%{box-shadow:0 0 0 2px var(--kbc), 0 0 12px rgba(255,255,255,.18);}
    50%{box-shadow:0 0 0 2px var(--kbc), 0 0 18px rgba(255,255,255,.34);}
  }

  /* speed meter */
  .meter{display:flex;flex-direction:column;gap:6px;}
  .meter-head{
    display:flex;justify-content:space-between;align-items:baseline;
    font-size:10px;letter-spacing:.16em;text-transform:uppercase;
    color:var(--slate);font-weight:600;
  }
  .meter-head b{
    font-family:var(--display);font-size:17px;color:var(--chalk);
    letter-spacing:.02em;font-variant-numeric:tabular-nums;
  }
  .meter-left{display:flex;align-items:baseline;gap:12px;}
  .meter-right{display:flex;align-items:baseline;gap:10px;}
  .sound{
    font-family:var(--ui);font-size:10px;font-weight:600;
    letter-spacing:.14em;text-transform:uppercase;
    background:none;border:0;padding:0;cursor:pointer;
    color:var(--slate);
  }
  .sound b{color:var(--sodium);font-weight:700;}
  .sound[aria-pressed="false"] b{color:var(--slate);}
  .sound:hover{color:var(--chalk);}
  .sound:focus-visible{outline:2px solid var(--sodium);outline-offset:3px;}

  .tow{
    font-family:var(--display);font-size:11px;font-weight:600;
    letter-spacing:.1em;text-transform:uppercase;
    color:var(--ink);background:var(--sodium);
    border-radius:2px;padding:2px 7px;
    font-variant-numeric:tabular-nums;
    box-shadow:0 0 14px rgba(255,176,32,.35);
  }
  .tow[hidden]{display:none;}

  .bar{
    position:relative;height:12px;border-radius:1px;
    background:var(--asphalt);
    border:1px solid var(--line);
    overflow:visible;
  }
  .fill{
    position:absolute;inset:1px auto 1px 1px;
    width:0;background:var(--sodium);
    border-radius:1px;
    box-shadow:0 0 12px rgba(255,176,32,.4);
  }
  .tick{
    position:absolute;top:-3px;bottom:-3px;width:2px;
    transform:translateX(-1px);
  }
  .tick i{
    position:absolute;top:-15px;left:50%;transform:translateX(-50%);
    font-style:normal;font-size:9px;font-weight:600;letter-spacing:.08em;
    white-space:nowrap;font-variant-numeric:tabular-nums;
  }

  /* ---------- standings ---------- */
  .standings{
    background:var(--panel);
    border:1px solid var(--line);
    border-radius:4px;
    padding:14px 15px 15px;
    display:flex;flex-direction:column;gap:12px;
  }
  .panel-title{
    font-size:10px;letter-spacing:.18em;text-transform:uppercase;
    color:var(--slate);font-weight:600;
    padding-bottom:9px;border-bottom:1px solid var(--line);
  }
  .grid-list{display:flex;flex-direction:column;gap:11px;}
  .entry{display:flex;flex-direction:column;gap:5px;}
  .entry-top{
    display:flex;align-items:center;gap:8px;
    font-size:14px;line-height:1.2;
  }
  .entry .pos{
    font-family:var(--display);font-weight:700;font-size:13px;
    color:var(--slate);width:15px;font-variant-numeric:tabular-nums;
  }
  .chip{width:9px;height:9px;border-radius:1px;flex:none;}
  .entry .nm{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .entry .gap{
    font-family:var(--mono);font-size:11px;color:var(--slate);
    font-variant-numeric:tabular-nums;
  }
  .entry.you .nm{color:var(--sodium);font-weight:600;}
  .entry.you .pos{color:var(--sodium);}
  .lapbar{
    position:relative;height:4px;background:var(--asphalt);border-radius:1px;
    overflow:hidden;
  }
  .lapbar span{position:absolute;inset:0 auto 0 0;width:0;border-radius:1px;}
  .lapbar u{
    position:absolute;top:0;bottom:0;width:1px;
    background:var(--line);text-decoration:none;
  }

  .form{
    border-top:1px solid var(--line);padding-top:11px;
    display:flex;flex-direction:column;gap:7px;
  }
  .form[hidden]{display:none;}
  .form-head{
    display:flex;justify-content:space-between;align-items:baseline;gap:8px;
    font-size:10px;letter-spacing:.16em;text-transform:uppercase;
    color:var(--slate);font-weight:600;
  }
  .spark{display:block;width:100%;height:auto;overflow:visible;}
  .form-stats{
    display:flex;justify-content:space-between;gap:8px;
    font-variant-numeric:tabular-nums;
  }
  .form-stats div{display:flex;flex-direction:column;gap:0;}
  .form-stats .k{
    font-size:9px;letter-spacing:.14em;text-transform:uppercase;
    color:var(--slate);font-weight:600;
  }
  .form-stats b{
    font-family:var(--display);font-weight:600;font-size:15px;color:var(--chalk);
  }
  .form-stats .up b{color:var(--sodium);}

  .career{
    font-size:11px;line-height:1.45;color:var(--slate);
    border-top:1px solid var(--line);padding-top:8px;
  }
  .career:empty{display:none;}
  .career b{color:var(--chalk);font-weight:600;}

  .drill{
    border-top:1px solid var(--line);padding-top:11px;
    display:flex;flex-direction:column;gap:7px;
  }
  .drill[hidden]{display:none;}
  .drill .hd{
    display:flex;justify-content:space-between;align-items:baseline;gap:8px;
    font-size:10px;letter-spacing:.16em;text-transform:uppercase;
    color:var(--slate);font-weight:600;
  }
  .drill .hd button{
    font-family:var(--ui);font-size:10px;letter-spacing:.1em;
    text-transform:uppercase;font-weight:600;
    background:none;border:0;padding:0;cursor:pointer;
    color:var(--slate);text-decoration:underline;text-underline-offset:2px;
  }
  .drill .hd button:hover{color:var(--chalk);}
  .drill .hd button:focus-visible{outline:1px solid var(--chalk);outline-offset:2px;}
  .keys{display:flex;gap:5px;flex-wrap:wrap;}
  .keys b{
    font-family:var(--mono);font-weight:700;font-size:13px;
    color:var(--sodium);
    background:var(--asphalt);
    border:1px solid var(--sodium-dim);
    border-radius:2px;
    min-width:22px;padding:2px 0;text-align:center;
  }
  .drill .why{font-size:11px;color:var(--slate);line-height:1.4;}
  .verdict{
    font-size:11px;font-weight:600;color:var(--slate);
    letter-spacing:.04em;
  }
  .verdict:empty{display:none;}
  .verdict.better{color:var(--sodium);}
  .verdict.worse{color:var(--brake);}

  .pb-line{
    border-top:1px solid var(--line);padding-top:11px;
    display:flex;justify-content:space-between;align-items:baseline;gap:10px;
    font-size:10px;letter-spacing:.16em;text-transform:uppercase;
    color:var(--slate);font-weight:600;
  }
  .pb-line b{
    font-family:var(--display);font-size:16px;color:var(--chalk);
    letter-spacing:.02em;font-variant-numeric:tabular-nums;
  }
  .pb-line b.none{color:var(--slate);}

  .entry.ghost .nm{opacity:.7;font-style:italic;}
  .chip.hollow{background:transparent !important;box-shadow:inset 0 0 0 1.5px var(--sodium);}

  .legend{
    border-top:1px solid var(--line);padding-top:11px;
    font-size:12px;color:var(--slate);line-height:1.5;
  }
  .legend b{color:var(--chalk);font-weight:600;}
  .legend kbd{
    font-family:var(--mono);font-size:11px;
    background:var(--asphalt);border:1px solid var(--verge);
    border-radius:2px;padding:1px 5px;color:var(--chalk);
  }

  .colophon{
    color:var(--slate);font-size:12px;
    border-top:1px solid var(--line);padding-top:12px;
    display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;
  }

  #catcher{
    position:fixed;top:0;left:0;
    width:100%;height:1px;
    opacity:.01;border:0;padding:0;margin:0;
    background:transparent;color:transparent;
    font-size:16px;            /* anything smaller and iOS zooms the page */
    pointer-events:none;
  }

  .tap-hint{
    display:none;
    font-family:var(--ui);font-size:13px;font-weight:600;
    letter-spacing:.1em;text-transform:uppercase;
    color:var(--sodium);
    background:var(--asphalt);
    border:1px dashed var(--sodium-dim);
    border-radius:3px;
    padding:11px 14px;width:100%;
    cursor:pointer;
  }
  body.touch .tap-hint:not([hidden]){display:block;}
  .tap-hint:focus-visible{outline:2px solid var(--chalk);outline-offset:2px;}

  @media (max-width:880px){
    .arena{grid-template-columns:1fr;}
    .brand h1{font-size:24px;}
    .readouts{gap:18px;}

    /* The soft keyboard swallows the bottom half of the screen, so the word
       being typed has to sit above the track, not below it. */
    .panel-col{display:flex;flex-direction:column;}
    .strip{order:-1;margin-top:0;margin-bottom:10px;}

    /* The setup card is taller than the track panel on a phone, so floating it
       over the canvas put START ENGINE below an internal scrollbar where nobody
       would find it. Let the overlays flow underneath the circuit instead: the
       panel grows to fit and nothing is ever hidden behind a fold. */
    .overlay{
      position:static;
      background:transparent;
      overflow:visible;
      padding:13px 2px 4px;
      margin-top:11px;
      border-top:1px solid var(--line);
    }
    .overlay-foot{position:static;background:none;padding:6px 0 0;}
    .overlay h2{font-size:21px;}
    .overlay .sub{font-size:13px;}
    .go-word{font-size:38px;}
    .setup-why{font-size:10.5px;}

    /* Centred on the play panel and no queue beside it, so the word can have the
       whole width and be read at a glance from where the thumbs are. */
    .word{font-size:clamp(25px,8vw,34px);padding:0;}
    .word-line{height:46px;}
    .word-stage{padding:11px 12px 10px;}
    .queue{display:none;}
    .ev{font-size:11px;padding:4px 8px 4px 7px;}
    /* the meter is narrow here, so the tow chip lands on top of the rival tick
       labels — drop the labels under the bar where nothing competes */
    .tick i{top:auto;bottom:-14px;}
    .strip{padding-bottom:28px;}
    .fin-grid{grid-template-columns:1fr;gap:20px;}
    .overlay{gap:12px;padding:16px;}
    .overlay h2{font-size:24px;}
    .tally{gap:18px;}
    .tally .v{font-size:19px;}
    .seg button{padding:7px 9px;font-size:10.5px;letter-spacing:.04em;}
    .seg-row{gap:8px;}
    .seg-label{width:auto;font-size:9px;letter-spacing:.1em;}
    .grid-preview{gap:9px;font-size:12px;}
    .shell{padding:14px 13px 28px;gap:13px;}
    .masthead{padding-bottom:10px;}
  }

  /* Short windows: the results are the tallest thing in the app, so trim them
     rather than make the reader scroll a scoreboard. */
  @media (max-height:720px){
    .overlay{padding:16px;gap:11px;}
    .overlay h2{font-size:25px;}
    /* Five setting rows now, and a short window is where they run out of panel */
    .setup-panel{gap:7px;}
    .setup{gap:9px;}
    .seg button{padding:6px 14px;}
    /* The course row is one more thing above Start engine, and a short window is
       exactly where the settings below it got pushed out of reach. */
    .course{
      flex-direction:row;flex-wrap:wrap;align-items:baseline;
      justify-content:center;gap:3px 10px;padding-bottom:7px;
    }
    .course-line{font-size:13px;}
    .course-bar{font-size:11px;line-height:1.35;}
    #btn-course,#btn-champ{padding:6px 14px;font-size:11px;}
    .champ-result{gap:6px;}
    .course-verdict{font-size:12px;line-height:1.4;}
    .tally{gap:20px;}
    .tally .v{font-size:20px;}
    .fin-grid{gap:22px;}
    .results td{padding:5px 0;font-size:13px;}
    .results th{padding-bottom:4px;}
    .keychart{gap:4px;}
    .ktrack{height:11px;}
    .chart-note{font-size:10px;}
    .overlay-foot{padding:10px 0 0;}
  }

  /* A laptop in a short window is where the start screen runs out of room
     altogether: two entries, five setting rows and a grid preview above the
     start button. Everything shrinks by a few pixels rather than anything being
     taken away, because a control that is off the bottom cannot be pressed and
     a control that is not there cannot be found. Ordered after the 720px block
     so it wins where both apply. */
  @media (max-height:660px){
    .overlay{padding:12px;gap:8px;}
    .overlay h2{font-size:22px;}
    .overlay .sub{font-size:12.5px;}
    .setup{gap:6px;}
    .setup-panel{gap:5px;}
    .seg button{padding:4px 11px;font-size:11px;}
    .seg-label{font-size:10px;}
    .setup-why{font-size:10.5px;}
    .course{gap:2px 9px;padding-bottom:5px;}
    /* And the results screen, which now carries a standings table under the
       verdict on every championship round. */
    .tally .v{font-size:18px;}
    .course-verdict{font-size:11.5px;line-height:1.35;}
    .champ-result{gap:4px;}
    .champ-table td{padding:2px 0;font-size:11.5px;}
    .champ-table th{padding-bottom:2px;font-size:10px;}
    .coach{font-size:12px;line-height:1.4;}
    .coach-do{padding:5px 12px;font-size:11px;}
    .results td{padding:4px 0;font-size:12px;}
    .keychart{gap:3px;}
    .fin-grid{gap:16px;}
  }

  /* Landscape phones. People turn the phone sideways because the keyboard is
     bigger — which leaves under 200px of height above it. Everything that is not
     the race has to go: the strapline, most of the header, and every spare pixel
     of padding. Measured target is the word, the event row and the pace bar all
     inside the top ~185px. */
  @media (orientation:landscape) and (max-height:460px){
    .shell{padding:5px 10px 14px;gap:5px;}
    .masthead{padding-bottom:4px;gap:10px;align-items:center;flex-wrap:nowrap;}
    .brand h1{font-size:14px;letter-spacing:0;}
    .brand p{display:none;}
    .readouts{gap:13px;}
    .readout{min-width:0;}
    .readout .k{font-size:8px;letter-spacing:.06em;}
    .readout .v{font-size:14px;}
    .readout .v small{font-size:9px;}

    /* A landscape phone leaves about 185px above the keyboard, and the word band
       now sits below the strip rather than inside it — so every pixel between
       the header and the band is one the word has to spend to stay visible. */
    .strip{padding:7px 11px 17px;gap:5px;margin-bottom:6px;}
    .word-stage{padding:4px 10px 4px;gap:4px;}
    .word-line{height:28px;}
    .word{font-size:clamp(16px,3.4vw,23px);}
    .events{height:16px;}
    .ev{font-size:9.5px;padding:2px 6px 2px 5px;}
    .meter{gap:3px;}
    .meter-head{font-size:9px;}
    .meter-head b{font-size:12px;}
    .sound{font-size:8.5px;letter-spacing:.08em;}
    .bar{height:9px;}
    .tick i{font-size:8px;bottom:-12px;}
    .tap-hint{padding:6px 10px;font-size:10.5px;}

    /* The overlay's box is as tall as the circuit panel beside it, and on a wide
       landscape phone the canvas is taller than the card - so "safe center" was
       spending 55px of the scarcest height in the app on centring the card in a
       box it does not fill. On a screen this short the card starts at the top.
       Measured: it moved the championship entry from 375px to 320px on an
       844x390 phone, where the fold is at 390px. */
    .overlay{gap:7px;padding:9px 2px 2px;margin-top:5px;justify-content:flex-start;}
    .overlay h2{font-size:18px;}
    /* A 44ch column of strapline in a 667px-wide window is four lines of text
       where two will do, and in landscape every one of those lines pushes the
       ways in towards the fold. Let it use the width the orientation gives. */
    .overlay .sub{font-size:11.5px;max-width:min(66ch,100%);}
    /* The three ways in have to clear 375px of height between them, the header
       and the strip, so they are packed tighter here than anywhere else. */
    .ways{gap:5px;}
    .course{gap:2px 8px;padding-bottom:3px;}
    .setup{gap:7px;}
    .setup-why{font-size:10px;}
    .card-place{font-size:22px;}
    .card-stats b{font-size:18px;}
  }

  /* Short phones: an iPhone SE leaves about 367px above the keyboard, and the
     race view has to fit in it. Every rule here buys vertical space. */
  @media (max-height:720px) and (max-width:560px){
    .shell{padding:10px 12px 22px;gap:9px;}
    .masthead{padding-bottom:8px;gap:8px;}
    .brand h1{font-size:19px;}
    .brand p{font-size:9.5px;letter-spacing:.06em;}
    .readout .v{font-size:17px;}
    .readout .v small{font-size:11px;}
    .readout .k{font-size:9px;letter-spacing:.1em;}
    .strip{padding:11px 13px 21px;gap:9px;}
    .word-stage{padding:8px 11px 8px;gap:6px;}
    .word-line{height:38px;}
    .word{font-size:clamp(22px,7vw,30px);}
    .events{height:19px;}
    .ev{font-size:10px;padding:3px 7px 3px 6px;}
    .meter{gap:4px;}
    .tap-hint{padding:8px 12px;font-size:11.5px;}
  }

  @media (max-width:520px){
    .brand p{font-size:11px;letter-spacing:.08em;}
    .readouts{gap:13px;width:100%;justify-content:space-between;}
    .readout{min-width:0;}
    .readout .v{font-size:20px;}
    .colophon{flex-direction:column;gap:5px;}
  }
  @media (prefers-reduced-motion:reduce){
    .ev{animation:none;}
    .btn{transition:none;}
    .btn:hover{transform:none;}
    .word.err{animation:none;}
    .kbk.on{animation:none;}
    .lights span{transition:none;}
    .seg button{transition:none;}
    *,*::before,*::after{scroll-behavior:auto;}
  }
