@charset "UTF-8";
  /* ============================================================
     SEPPY SIMULATOR — shell
     Full-bleed cinematic app shell. Everything is inline; no fonts,
     no assets, no network. Display type is a hand-built 5x7 bitmap
     font rendered as SVG rects (see pix() in the script).
     ============================================================ */
  :root{
    /* ink — the HUD substrate */
    --ink0:#04070d; --ink1:#0a1120; --ink2:#101c31; --ink3:#1a2942;
    /* the accent. one green, used everywhere, like the bar does */
    --acc:#43e483; --acc2:#16a04e; --acc3:#0c5c2d; --accd:#7fb896;
    --grn0:#082214; --grn1:#0d3520;
    /* meter hues */
    --oil:#f0b13c; --trk:#69d6e0; --mrl:#5fe08a; --grv:#ff6a45; --spt:#5aa8ff;
    --amber:#f2b23c; --red:#ff5c48; --blue:#57b6ff;
    /* text */
    --txt:#f2f7fb; --txt2:#a8bccd; --txt3:#6d8598; --txt4:#4a5f72;
    --line:rgba(150,205,255,.16);
    --ui:"Segoe UI Variable Text","Segoe UI",system-ui,-apple-system,"Helvetica Neue","Liberation Sans",Arial,sans-serif;
    --cond:"Bahnschrift","DIN Alternate","Segoe UI Variable Display","Franklin Gothic Medium","Roboto Condensed","Arial Narrow",var(--ui);
    /* stepped pixel corners, not 45-degree bevels — this is what makes the
       panels read as a game UI rather than a web card */
    --chamfer:polygon(
      0 8px,4px 8px,4px 4px,8px 4px,8px 0,
      calc(100% - 8px) 0,calc(100% - 8px) 4px,calc(100% - 4px) 4px,calc(100% - 4px) 8px,100% 8px,
      100% calc(100% - 8px),calc(100% - 4px) calc(100% - 8px),calc(100% - 4px) calc(100% - 4px),
      calc(100% - 8px) calc(100% - 4px),calc(100% - 8px) 100%,
      8px 100%,8px calc(100% - 4px),4px calc(100% - 4px),4px calc(100% - 8px),0 calc(100% - 8px));
    --chamfer-s:polygon(
      0 6px,3px 6px,3px 3px,6px 3px,6px 0,
      calc(100% - 6px) 0,calc(100% - 6px) 3px,calc(100% - 3px) 3px,calc(100% - 3px) 6px,100% 6px,
      100% calc(100% - 6px),calc(100% - 3px) calc(100% - 6px),calc(100% - 3px) calc(100% - 3px),
      calc(100% - 6px) calc(100% - 3px),calc(100% - 6px) 100%,
      6px 100%,6px calc(100% - 3px),3px calc(100% - 3px),3px calc(100% - 6px),0 calc(100% - 6px));
    --chamfer-tl:polygon(
      0 6px,3px 6px,3px 3px,6px 3px,6px 0,100% 0,
      100% calc(100% - 6px),calc(100% - 3px) calc(100% - 6px),calc(100% - 3px) calc(100% - 3px),
      calc(100% - 6px) calc(100% - 3px),calc(100% - 6px) 100%,0 100%);
  }
  *,*::before,*::after{box-sizing:border-box}
  html,body{margin:0;padding:0;height:100%;background:#04070d}
  body{
    font-family:var(--ui); color:var(--txt); overflow:hidden;
    -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  }
  ::selection{background:var(--acc2);color:#fff}
  .sr{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
      clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

  #app{position:fixed;inset:0;background:#04070d}
  /* ---------------- R9: the world outlives the screen ----------------
     The stage used to be the first child of #frame, so every click threw away
     a full-bleed filtered SVG, a flag field of up to 109,000 characters, four
     gradient washes, an feTurbulence grain plate and sixteen animated motes,
     and built them again. `.stage{animation:fadeIn}` then replayed on the new
     node, so the whole picture dipped to zero and faded back. Measured headed:
     116 of 346 painted frames below full opacity, darkest 0.000. That is the
     flash. The world now lives OUTSIDE the frame — #stagehost is a sibling
     behind it, written by syncStage() instead of by innerHTML. */
  #stagehost{position:absolute;inset:0;overflow:hidden;isolation:isolate;pointer-events:none}
  #frame{
    position:absolute;inset:0;overflow:hidden;isolation:isolate;
    display:flex;flex-direction:column;background:transparent;
  }

  /* ---------------- stage: the scene is the screen ---------------- */
  .stage{position:absolute;inset:0;z-index:0;overflow:hidden;background:#070c15}
  /* Three decks, all geometrically identical to .stage, so everything inside
     them lands exactly where it landed when they were direct children. They
     exist so each layer can be replaced — or left alone — on its own. */
  .scdeck,.flagdeck,.rigdeck{position:absolute;inset:0}
  /* A scene change is now a cross-fade instead of a cut: the incoming scene is
     laid over the outgoing one at zero and brought up. The old one is never
     faded down, so there is no moment where the screen is showing less than
     one complete picture. 220ms — the same window the old whole-stage fade
     used, so tools/shoot.js's 260ms settle still lands on a finished frame. */
  .scslot{position:absolute;inset:0}
  .sc{position:absolute;inset:0}
  /* ---------------- R12: the scene is a bitmap ----------------
     The scenes lane rendered all seven to WebP (see _gauntlet/notes/scenes-r12.md).
     `img` gets the same geometry the svg had; object-fit:cover IS
     preserveAspectRatio="xMidYMid slice", so the crop and the centring are
     identical and the flag overlay — still SVG on the same 1920x1080 box —
     lands on exactly the pixels it landed on before. */
  .sc svg,.sc img{position:absolute;inset:0;width:100%;height:100%;display:block}
  .sc img{object-fit:cover;object-position:50% 50%}
  /* ---------------- R13: ONE drifting layer, not two ----------------
     R12 put `drift` on `.sc.full svg,.sc.full img` — which is the scene plate
     in .scdeck AND the flag field in .flagdeck. A transform animation is
     compositor-driven by definition: Chromium promotes anything running one,
     will-change or no will-change. So the phone was holding two viewport-sized
     tile sets, and that is the budget it was blowing. Measured on the handset:
     ~60 "tile memory limits exceeded" per cold launch, all of them inside one
     800ms burst at first paint — a PEAK, not a leak.
     Both boxes are inset:0 of the same stage and both were being moved by the
     same number of pixels, so a single drifting parent is pixel-identical and
     costs one tile set. transform-origin has to live here now, and it is the
     same 52%/46% of the same box, so the motion is unchanged. */
  /* overflow:hidden bounds the layer. .fx-rays lives in here now and reaches
     20%/10% outside the box; .stage was already clipping that overflow, so
     nothing about the picture changes, but a promoted layer's tiles are sized
     from its content bounds and an unclipped rays plate would have grown this
     one to 1.2 x 1.4 of the viewport for pixels that were never visible. */
  .world{position:absolute;inset:0;overflow:hidden;transform-origin:52% 46%;
    animation:drift 34s ease-in-out infinite alternate;
    will-change:transform;backface-visibility:hidden}
  /* THE SCALE IS CONSTANT ACROSS THE ANIMATION, on purpose. The old keyframes
     went scale(1.02) -> scale(1.085), and a promoted layer whose SCALE changes
     has to be re-rasterised at each new scale to stay sharp — which would have
     handed back most of what promotion just bought. A fixed overscale with a
     pure translate inside it is a composited move and costs nothing.
     R13 trimmed 1.06 -> 1.035: the raster area of a promoted layer is the
     square of this, so 1.06 was buying 12.4% more pixels than the viewport and
     1.035 buys 7.1%. The overscale is 1.75% of margin on each edge and the
     travel is 0.42%, so no edge can come into frame — the safety margin is
     four times the travel, same as before. */
  @keyframes drift{
    from{transform:scale(1.035) translate3d(-.42%,-.3%,0)}
    to{transform:scale(1.035) translate3d(.42%,.3%,0)}}
  /* legacy strip scenes (aspect wider than ~2.4) get a bleed behind a
     letterboxed band. No scene has been that shape for several rounds — every
     one declares viewBox="0 0 1920 1080" — so this branch does not fire; the
     70px full-screen blur it used to carry is gone anyway, because if it ever
     DOES fire on a phone it would be the single most expensive thing on the
     screen. A hard overscale plus a dim reads the same at that blur radius. */
  .sc.bleed{transform:scale(1.6);filter:saturate(1.45) brightness(.5)}
  .sc.band{top:2%;height:74%;bottom:auto;
    -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 26%,#000 68%,transparent 100%);
            mask-image:linear-gradient(180deg,transparent 0,#000 26%,#000 68%,transparent 100%)}

  .fx-grade,.fx-vig,.fx-grain,.fx-top,.fx-bot,.fx-rays,.fx-motes,.fx-fg{position:absolute;pointer-events:none}
  /* out-of-focus foreground framing — reads as depth over any scene.
     Deliberately weak through the middle: the scenes now carry their own
     foreground, and a heavy plate here was crushing scene detail (the
     "pop. pending" sign on the republic scene) into mud. */
  /* R12 — the blur is gone. Every layer in this fx stack is built out of
     gradients, and a gradient is already smooth: a 3px blur on one changed
     nothing anybody could see and cost a full-width filter region on a layer
     that sits over the whole scene. Same for .fx-rays' 22px blur below. The
     stops were widened where the blur was doing any softening at all. */
  .fx-fg{left:-6%;right:-6%;bottom:-8%;height:34%;
    background:
      radial-gradient(58% 112% at 2% 106%,rgba(3,6,12,.9) 0,rgba(3,6,12,0) 64%),
      radial-gradient(58% 112% at 98% 106%,rgba(3,6,12,.9) 0,rgba(3,6,12,0) 64%),
      linear-gradient(0deg,rgba(3,6,12,.7) 0,rgba(3,6,12,0) 76%)}
  .fx-grade{inset:0;mix-blend-mode:soft-light;opacity:.9;
    background:linear-gradient(180deg,rgba(150,205,255,.55) 0,rgba(255,205,150,.16) 52%,rgba(20,12,44,.7) 100%)}
  .fx-warm .fx-grade{background:linear-gradient(180deg,rgba(255,208,138,.6) 0,rgba(255,140,68,.22) 50%,rgba(44,12,32,.72) 100%)}
  .fx-cold .fx-grade{background:linear-gradient(180deg,rgba(160,212,255,.55) 0,rgba(110,150,215,.18) 52%,rgba(8,16,44,.78) 100%)}
  .fx-dusk .fx-grade{background:linear-gradient(180deg,rgba(255,150,170,.5) 0,rgba(190,110,190,.2) 48%,rgba(16,8,42,.8) 100%)}
  /* The 22px blur here was the most expensive rule in the file after the scene
     itself: a filter region 140% x 120% of the viewport, over a layer that is
     invalidated by the drift underneath it. The blur existed to soften three
     hard-edged linear gradients; softening the STOPS does the same job for
     free, so each band now ramps over 9-11% instead of jumping over 6%. */
  /* .21, not the .16 this was: spreading the same light over a wider ramp
     instead of a 22px blur costs it some presence, and on a narrow portrait
     crop — where the 102deg bands are at their thinnest — the shafts had gone
     from visible to absent. Compared against the pre-R12 shot at 390x844 and
     952x427; the softer wide ramp reads better than the hard wedge the blur
     was there to hide. */
  .fx-rays{inset:-20% -10%;opacity:.21;mix-blend-mode:screen;
    background:
      linear-gradient(102deg,transparent 26%,rgba(255,240,200,.55) 33%,rgba(255,240,200,.9) 38%,
        rgba(255,240,200,.5) 43%,transparent 50%),
      linear-gradient(96deg,transparent 47%,rgba(255,240,210,.42) 53%,rgba(255,240,210,.7) 58%,
        rgba(255,240,210,.38) 62%,transparent 68%),
      linear-gradient(108deg,transparent 59%,rgba(255,235,190,.33) 65%,rgba(255,235,190,.55) 70%,
        rgba(255,235,190,.3) 74%,transparent 81%)}
  /* the header and dock are already solid — these only need to carry the eye
     from panel into scene, not black out the top third of the frame */
  .fx-top{left:0;right:0;top:0;height:20%;
    background:linear-gradient(180deg,rgba(4,7,14,.78) 0,rgba(4,7,14,.34) 44%,rgba(4,7,14,0) 100%)}
  .fx-bot{left:0;right:0;bottom:0;height:34%;
    background:linear-gradient(0deg,rgba(4,7,14,.82) 0,rgba(4,7,14,.34) 46%,rgba(4,7,14,0) 100%)}
  .fx-vig{inset:-3%;background:
    radial-gradient(125% 90% at 50% 40%,rgba(0,0,0,0) 32%,rgba(0,0,0,.40) 74%,rgba(0,0,0,.85) 100%)}
  .fx-grain{inset:0;opacity:.15;mix-blend-mode:overlay;background-repeat:repeat;background-size:180px 180px;
    background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='180' height='180' filter='url(%23n)'/></svg>")}
  .fx-motes{inset:0;overflow:hidden}
  .mote{position:absolute;border-radius:50%;background:rgba(255,240,205,.85);
    box-shadow:0 0 8px 2px rgba(255,235,190,.35);opacity:0;animation:mote linear infinite}
  @keyframes mote{0%{opacity:0;transform:translate3d(0,14px,0)}
    18%{opacity:.85}72%{opacity:.5}100%{opacity:0;transform:translate3d(24px,-52px,0)}}

  /* ---------------- the truck, standing on the scene's ground line ----------
     The rig is a layer of the STAGE, not a row in the field, so its contact
     point is pinned to a fraction of the SCREEN (--gy) and stops moving when
     the dock changes height. --foot is how far the truck's tyre line sits
     above the bottom of its own viewBox, as a percentage of the box, so the
     wheels land exactly on --gy instead of the SVG's bounding box doing. */
  /* R9 — the rig element itself now survives a screen change, so moving the
     truck between its trail mark (47%) and its outcome mark (74%) is a short
     drive rather than a teleport. `left` and `bottom` are driven by --rigx and
     --gy, and a transition on the used property animates fine even though a
     custom property feeds it.
     200ms, not the 380 this started at: tools/shoot.js settles for 260ms before
     it captures, and at 380 the truck was still moving when the shutter went —
     it photographed three per cent left of its mark and every art lane would
     have been comparing against a truck in motion. Anything the harness sees
     has to have finished by 260. */
  .rigline{position:absolute;left:var(--rigx,47%);bottom:calc(100% - var(--gy,62%));
    transform:translate(-50%,calc(var(--foot,15) * 1%));
    width:clamp(240px,31vw,520px);z-index:1;pointer-events:none;
    transition:left .2s cubic-bezier(.34,.02,.2,1),bottom .2s cubic-bezier(.34,.02,.2,1)}
  .rigline.end{width:clamp(200px,25vw,430px)}
  /* parked further down the scene, at a set-piece, under the party roster */
  .rigline.far{width:clamp(140px,16vw,290px)}
  /* R12 — the second promotion. juPaintRig() writes a transform on this SVG on
     every one of the loop's frames (the idle bob and the suspension), and the
     element also carries a drop-shadow. Unpromoted that is "run the filter,
     then move the result" sixty times a second on the largest sprite in the
     game; promoted it is one rasterisation and then a composited translate.
     This and .sc.full img are the only two will-change declarations in the
     file, because over-promotion costs layer memory — which is the exact
     budget the phone ran out of. */
  .rigline svg{display:block;width:100%;height:auto;
    filter:drop-shadow(0 18px 22px rgba(0,0,0,.5));
    will-change:transform}
  /* a low sheet of lit dust sitting exactly on the contact line. The reference
     solves ground contact this way — the dust eats the bottom of the tyres. */
  /* R12 — no blur. It is three radial gradients; there was nothing to soften.
     It also sits directly under the truck, which the loop moves every frame,
     so a filter here was being re-run inside a region that never settles. */
  .groundhaze{left:-4%;right:-4%;height:15vh;z-index:1;
    bottom:calc(100% - var(--gy,62%) - 5.5vh);
    background:
      radial-gradient(15% 24% at var(--hx,47%) 63%,rgba(255,244,222,.42) 0,rgba(255,244,222,0) 100%),
      radial-gradient(46% 96% at var(--hx,47%) 60%,rgba(255,238,208,.3) 0,rgba(255,238,208,0) 74%),
      linear-gradient(180deg,rgba(255,238,208,0) 0,rgba(255,238,208,.13) 56%,rgba(255,238,208,.05) 72%,rgba(255,238,208,0) 100%)}
  .fx-cold .groundhaze{background:
      radial-gradient(15% 24% at var(--hx,47%) 63%,rgba(214,232,255,.34) 0,rgba(214,232,255,0) 100%),
      radial-gradient(46% 96% at var(--hx,47%) 60%,rgba(206,226,255,.22) 0,rgba(206,226,255,0) 74%),
      linear-gradient(180deg,rgba(206,226,255,0) 0,rgba(206,226,255,.11) 56%,rgba(206,226,255,.04) 72%,rgba(206,226,255,0) 100%)}
  .fx-dusk .groundhaze{background:
      radial-gradient(15% 24% at var(--hx,47%) 63%,rgba(255,224,232,.36) 0,rgba(255,224,232,0) 100%),
      radial-gradient(46% 96% at var(--hx,47%) 60%,rgba(255,214,224,.24) 0,rgba(255,214,224,0) 74%),
      linear-gradient(180deg,rgba(255,214,224,0) 0,rgba(255,214,224,.12) 56%,rgba(255,214,224,.05) 72%,rgba(255,214,224,0) 100%)}

  /* ---------------- ending mood: readable at thumbnail size ----------------
     A win and a death must not be the same warm orange picture with a
     different chip on it. Triumph goes bright, saturated, gold-and-green.
     A death goes dark, desaturated and blue, with ash falling instead of
     embers rising. That difference survives a 120px-wide screenshot. */
  /* R12 — the mood filter moved from `.sc` (the wrapper) onto the picture
     itself. On a bitmap this is one shader pass over a texture that is already
     promoted, so it is nearly free; on the wrapper it forced a second render
     surface above the promoted one and had to be re-composited with it. */
  .stage.won .sc>img,.stage.won .sc>svg{filter:saturate(1.24) brightness(1.12) contrast(1.02)}
  .stage.won .fx-grade{opacity:1;background:linear-gradient(180deg,
    rgba(255,234,168,.62) 0,rgba(255,180,76,.26) 44%,rgba(102,48,10,.5) 100%)}
  .stage.won .fx-rays{opacity:.32}
  .stage.won .fx-vig{background:radial-gradient(122% 88% at 50% 40%,
    rgba(0,0,0,0) 42%,rgba(26,78,42,.3) 76%,rgba(4,28,14,.8) 100%)}
  .stage.won .fx-bot{background:linear-gradient(0deg,
    rgba(7,30,16,.78) 0,rgba(7,26,15,.3) 46%,rgba(7,26,15,0) 100%)}
  .stage.won .rigline svg{filter:drop-shadow(0 18px 22px rgba(0,0,0,.5)) saturate(1.1) brightness(1.06)}
  .stage.lost .sc>img,.stage.lost .sc>svg{filter:saturate(.3) brightness(.66) contrast(1.12)}
  .stage.lost .fx-grade{mix-blend-mode:normal;opacity:1;background:linear-gradient(180deg,
    rgba(28,46,80,.62) 0,rgba(30,20,46,.46) 46%,rgba(3,5,12,.9) 100%)}
  .stage.lost .fx-rays{opacity:.04}
  .stage.lost .fx-vig{background:radial-gradient(112% 84% at 50% 38%,
    rgba(0,0,0,0) 22%,rgba(0,0,0,.62) 68%,rgba(0,0,0,.96) 100%)}
  .stage.lost .fx-top{background:linear-gradient(180deg,rgba(2,4,9,.88) 0,rgba(2,4,9,.4) 44%,rgba(2,4,9,0) 100%)}
  .stage.lost .fx-bot{background:linear-gradient(0deg,rgba(2,4,9,.92) 0,rgba(2,4,9,.5) 44%,rgba(2,4,9,0) 100%)}
  .stage.lost .groundhaze{background:
      radial-gradient(15% 24% at var(--hx,47%) 63%,rgba(184,200,224,.26) 0,rgba(184,200,224,0) 100%),
      radial-gradient(46% 96% at var(--hx,47%) 60%,rgba(170,190,215,.15) 0,rgba(170,190,215,0) 74%),
      linear-gradient(180deg,rgba(170,190,215,0) 0,rgba(170,190,215,.1) 56%,rgba(170,190,215,0) 100%)}
  /* the truck stays the brightest object in a dead frame — it is the subject */
  .stage.lost .rigline svg{filter:drop-shadow(0 18px 22px rgba(0,0,0,.6)) saturate(.62) brightness(.96)}
  .stage.lost .mote{background:rgba(184,200,220,.6);box-shadow:none;animation-name:ash}
  @keyframes ash{0%{opacity:0;transform:translate3d(0,-34px,0)}
    16%{opacity:.62}70%{opacity:.38}100%{opacity:0;transform:translate3d(-20px,74px,0)}}
  .stage.won .sc.bleed,.stage.lost .sc.bleed{filter:saturate(1.2) brightness(.44)}

  /* ---------------- pixel display type ---------------- */
  .pix{display:block;width:auto;max-width:100%;height:auto;
       filter:drop-shadow(0 0 1px rgba(0,0,0,.9))}
  .pix.hd{height:clamp(15px,1.35vw,25px)}
  .pix.xl{height:clamp(30px,4.4vw,86px);filter:drop-shadow(0 0 34px rgba(0,0,0,.8)) drop-shadow(0 5px 0 rgba(0,0,0,.45))}
  .pix.lg{height:clamp(20px,2.1vw,38px)}
  .pix.md{height:clamp(12px,1.05vw,19px)}
  .pix.sm{height:clamp(8px,.62vw,11px)}
  .pixi{display:block;flex:none}

  /* ---------------- header ---------------- */
  .hdr{
    position:relative;z-index:4;flex:0 0 auto;
    display:grid;grid-template-columns:1fr minmax(0,auto) 1fr;align-items:center;
    gap:clamp(12px,1.6vw,32px);
    padding:clamp(9px,1.15vh,17px) clamp(18px,2.2vw,44px) clamp(9px,1.15vh,17px) clamp(78px,6.2vw,124px);
    min-height:clamp(58px,7.4vh,96px);
    background:linear-gradient(180deg,#0a1220 0,#070c17 100%);
    box-shadow:0 1px 0 rgba(150,205,255,.20),0 2px 0 rgba(0,0,0,.6),0 18px 34px rgba(0,0,0,.45);
  }
  /* terminal screens drop the live stats but keep the menu — it is the only
     way to the ending log, and the ending is exactly where you want it */
  .hdr.bare{min-height:clamp(46px,5.6vh,72px)}
  .sys{position:absolute;left:0;top:0;bottom:-2px;width:clamp(58px,4.6vw,92px);
    background:#04070d;display:flex;align-items:center;justify-content:center;
    box-shadow:inset -1px 0 0 rgba(150,205,255,.14)}
  .htitle{min-width:0;display:flex;flex-direction:column;align-items:center;gap:clamp(3px,.5vh,7px)}
  .hsub{font-family:var(--cond);font-weight:600;text-transform:uppercase;letter-spacing:.26em;
    font-size:clamp(9px,.62vw,12px);color:var(--txt2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
  .hleft{justify-self:start;min-width:0}
  .hright{justify-self:end;display:flex;align-items:center;gap:clamp(12px,1.5vw,30px)}
  .stat{display:flex;flex-direction:column;align-items:center;gap:3px;min-width:0}
  .stat .sv{font-family:var(--cond);font-weight:700;font-variant-numeric:tabular-nums;
    font-size:clamp(15px,1.15vw,23px);line-height:1;color:#fff;
    text-shadow:0 2px 0 rgba(0,0,0,.55)}
  .stat .sl{font-family:var(--cond);text-transform:uppercase;letter-spacing:.2em;
    font-size:clamp(8px,.52vw,10px);color:var(--txt3);white-space:nowrap}
  .stat .si{display:flex;align-items:center;gap:6px}

  /* ---------------- field ---------------- */
  .field{
    position:relative;z-index:2;flex:1 1 auto;min-height:0;overflow:hidden;
    padding:clamp(14px,2.4vh,40px) clamp(20px,3.2vw,70px);
    display:flex;flex-direction:column;justify-content:center;gap:clamp(10px,1.6vh,24px);
  }
  .field>*{flex:0 0 auto}            /* never let flex squash content into itself */
  .field.pad0{padding-top:clamp(10px,1.4vh,20px);padding-bottom:clamp(6px,1vh,14px)}
  .field.top{justify-content:flex-start;padding-top:clamp(16px,2.6vh,44px)}
  /* the alert cluster's margin-bottom:auto belongs to the trail's bottom-anchored
     stack; on a top-anchored field it would shove the prompt off the screen */
  .field.top>.alerts{margin-bottom:0;align-self:center}
  .field.top>.milepost{align-self:center;margin-bottom:clamp(1px,.5vh,9px)}
  .field.mid{justify-content:center;align-items:center;text-align:center}
  /* the trail: truck lives in the stage, so the field only carries the toast
     and the party row, both hung off the bottom edge like the reference's */
  .field.stack{justify-content:flex-end;align-items:center;text-align:center;
    padding-bottom:clamp(6px,1vh,14px);gap:clamp(7px,1vh,16px)}
  /* alerts ride up under the header so nothing ever lands on the truck's wheels */
  .alerts{display:flex;flex-direction:column;align-items:center;
    gap:clamp(6px,.8vh,12px);margin-bottom:auto;max-width:100%}
  .alerts:empty{display:none}
  .field.scroll{overflow-y:auto;scrollbar-width:none}
  .field.scroll::-webkit-scrollbar{width:0;height:0}

  /* ---------------- dock ---------------- */
  .dock{
    position:relative;z-index:4;flex:0 0 auto;
    padding:clamp(10px,1.3vh,17px) clamp(18px,2.6vw,48px) clamp(15px,2.1vh,28px);
    display:flex;flex-direction:column;gap:clamp(9px,1.15vh,15px);
    background:linear-gradient(180deg,rgba(6,10,20,.35) 0,rgba(5,8,16,.93) 34%,#050810 100%);
    box-shadow:0 -1px 0 rgba(150,205,255,.18),0 -22px 44px rgba(0,0,0,.5);
  }
  .dock>*{flex:0 0 auto}
  .dockrow{display:flex;gap:clamp(9px,.9vw,16px);align-items:stretch}
  .dockrow.grow>*{flex:1 1 0;min-width:0}

  /* ---------------- buttons ---------------- */
  .btn{
    --c1:#3ee477;--c2:#159a48;--tc:#03150a;
    position:relative;display:inline-flex;align-items:center;justify-content:center;
    gap:clamp(8px,.7vw,14px);border:0;cursor:pointer;
    font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.11em;
    font-size:clamp(13px,.95vw,19px);line-height:1;color:var(--tc);
    padding:clamp(13px,1.55vh,21px) clamp(18px,1.7vw,32px);
    background:linear-gradient(180deg,var(--c1) 0,var(--c2) 100%);
    box-shadow:inset 0 0 0 2px rgba(0,0,0,.32),inset 0 3px 0 rgba(255,255,255,.32),inset 0 -3px 0 rgba(0,0,0,.22);
    clip-path:var(--chamfer);
    filter:drop-shadow(0 8px 16px rgba(0,0,0,.55));
    transition:filter .12s ease,transform .08s ease;
  }
  .btn:hover{filter:drop-shadow(0 10px 20px rgba(0,0,0,.6)) brightness(1.12)}
  .btn:active{transform:translateY(2px)}
  .btn:focus-visible{outline:3px solid #fff;outline-offset:3px}
  .btn .chev{display:inline-block;width:0;height:0;border-left:7px solid currentColor;
    border-top:5px solid transparent;border-bottom:5px solid transparent;opacity:.75}
  .btn.ghost{--c1:#14351f;--c2:#0a1d13;--tc:var(--acc);
    box-shadow:inset 0 0 0 2px var(--acc2),inset 0 2px 0 rgba(255,255,255,.08)}
  .btn.ghost:hover{--c1:#1c4b2c;--c2:#0d2a1a}
  .btn.mute{--c1:#1d2737;--c2:#111a28;--tc:#93a7bb;
    box-shadow:inset 0 0 0 2px rgba(150,205,255,.20),inset 0 2px 0 rgba(255,255,255,.06)}
  .btn.mute:hover{--c1:#26344a;--c2:#16202f}
  .btn.big{font-size:clamp(15px,1.15vw,23px);padding:clamp(16px,2vh,26px) clamp(24px,2.4vw,44px);letter-spacing:.15em}
  .btn:disabled{filter:grayscale(.75) brightness(.55);cursor:not-allowed;transform:none}
  .btn.w{width:100%}

  /* ---------------- choices ---------------- */
  .choices{display:grid;gap:clamp(8px,.8vw,14px);grid-template-columns:repeat(2,1fr)}
  .choices.one{grid-template-columns:1fr}
  .choices.three{grid-template-columns:repeat(3,1fr)}
  .choice{
    position:relative;display:flex;flex-direction:column;align-items:flex-start;
    justify-content:center;gap:5px;min-height:clamp(54px,6.2vh,88px);
    text-align:left;cursor:pointer;border:0;font-family:var(--ui);
    font-size:clamp(13px,.94vw,18px);font-weight:500;line-height:1.34;color:#dceaf4;
    padding:clamp(11px,1.35vh,18px) clamp(14px,1.15vw,22px) clamp(11px,1.35vh,18px) clamp(19px,1.5vw,28px);
    background:linear-gradient(180deg,rgba(20,34,54,.94) 0,rgba(9,16,28,.96) 100%);
    box-shadow:inset 0 0 0 2px rgba(150,205,255,.17),inset 0 2px 0 rgba(255,255,255,.05);
    clip-path:var(--chamfer);
    filter:drop-shadow(0 8px 16px rgba(0,0,0,.5));
    transition:background .14s ease,transform .08s ease,filter .14s ease;
  }
  .choice::before{content:'';position:absolute;left:0;top:0;bottom:0;width:5px;
    background:linear-gradient(180deg,var(--acc),var(--acc2));opacity:.65;transition:opacity .14s}
  .choice:hover{background:linear-gradient(180deg,rgba(24,58,42,.96) 0,rgba(10,28,20,.97) 100%);
    filter:drop-shadow(0 12px 22px rgba(0,0,0,.6))}
  .choice:hover::before{opacity:1}
  .choice:active{transform:translateY(2px)}
  .choice:focus-visible{outline:3px solid var(--acc);outline-offset:3px}
  .choice b{display:block;font-family:var(--cond);font-weight:700;text-transform:uppercase;
    letter-spacing:.1em;font-size:clamp(12px,.86vw,16px);color:var(--acc);margin-bottom:1px}

  /* ---------------- meter rail ---------------- */
  /* R21b — the cash cell was the NARROWEST (.92fr) and it carries both the
     longest label in the rail and the only value whose width is not bounded.
     With the 11px floor on (P1-10) "HERITAGE FUND" needs 87px against a 70px
     box at 952x427; swept 760->1920 at two heights, it was cut from 920 all
     the way to 1320. Widening the column it was always going to need fixes
     every one of those widths in one declaration, and the five bounded meters
     lose 5% of a cell they were not using. */
  .rail{display:grid;grid-template-columns:repeat(5,minmax(0,1fr)) minmax(0,1.3fr);gap:clamp(7px,.7vw,14px)}
  .mtr{position:relative;padding:clamp(6px,.72vh,10px) clamp(9px,.72vw,14px) clamp(7px,.8vh,11px);
    background:linear-gradient(180deg,rgba(17,29,47,.94) 0,rgba(7,13,23,.95) 100%);
    box-shadow:inset 0 0 0 1px rgba(150,205,255,.16),inset 0 1px 0 rgba(255,255,255,.05);
    clip-path:var(--chamfer-tl)}
  .mtr .r1{display:flex;align-items:center;gap:clamp(5px,.45vw,9px);margin-bottom:clamp(5px,.6vh,8px)}
  .mtr .nm{flex:1;min-width:0;font-family:var(--cond);font-weight:600;text-transform:uppercase;
    letter-spacing:.18em;font-size:clamp(8px,.56vw,11px);color:var(--txt3);
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .mtr .vl{font-family:var(--cond);font-weight:700;font-variant-numeric:tabular-nums;
    font-size:clamp(14px,1.02vw,21px);line-height:1;color:#fff;text-shadow:0 2px 0 rgba(0,0,0,.5)}
  .mbar,.mcash{position:relative;height:clamp(9px,.9vh,12px);background:rgba(255,255,255,.06);
    box-shadow:inset 0 0 0 1px rgba(0,0,0,.5)}
  /* R10 — no transition here on purpose. The loop owns this width; a CSS
     transition on top of a JS tween is two easings fighting, and it was dead
     code anyway (render() replaced the node, so it never once fired). */
  /* R12 — the fill is a full-width bar that is SCALED, not a bar whose width is
     rewritten. The loop touches this on every frame a meter is moving, and a
     width write is a layout write; a scaleX is a composited transform. Same
     pixels, and getBoundingClientRect() still reports the scaled width, so
     tools/juice.js measures exactly what it measured before. */
  .mbar i{position:absolute;left:0;top:0;bottom:0;width:100%;display:block;
    transform-origin:0 50%;transform:scaleX(0)}
  /* the band the bar is travelling. Painted UNDER the fill, so a rise reads as
     the bar filling into ground that is already lit and a fall leaves the lost
     ground glowing behind it for about half a second. */
  .mbar u{position:absolute;top:0;bottom:0;left:0;width:0;opacity:0;display:block;
    pointer-events:none;mix-blend-mode:screen}
  .mbar u.up{background:linear-gradient(180deg,rgba(120,255,170,.85),rgba(60,200,120,.35))}
  .mbar u.dn{background:linear-gradient(180deg,rgba(255,140,110,.9),rgba(190,60,40,.4))}
  .mbar::after{content:'';position:absolute;inset:0;pointer-events:none;
    background:repeating-linear-gradient(90deg,transparent 0 calc(6.25% - 2px),rgba(5,9,17,.96) calc(6.25% - 2px) 6.25%)}
  /* The delta, floating up off the bar that just moved. Absolute, and
     right-aligned under the value, so it can never reflow the row when it
     appears — `.mtr` is clip-path'd, so it has to rise INSIDE the cell. */
  .mtr .dlt{position:absolute;left:0;right:clamp(9px,.72vw,14px);
    bottom:clamp(5px,.6vh,8px);text-align:right;pointer-events:none;z-index:2;
    font-family:var(--cond);font-weight:700;font-variant-numeric:tabular-nums;
    font-size:clamp(11px,.8vw,15px);line-height:1;opacity:0;white-space:nowrap;
    text-shadow:0 1px 4px rgba(0,0,0,1),0 0 10px rgba(0,0,0,.9)}
  .mtr .dlt.up{color:#7cf0a8}
  .mtr .dlt.dn{color:#ff9179}
  /* ---------------- R13: the low-meter pulse repaints no more --------------
     This animated BOX-SHADOW. A box-shadow is painted content, so every frame
     of the 1.9s loop invalidated the cell and forced a repaint — and `.mtr.low`
     is on whichever meters are low, which in a real run is most of them for
     most of the game. The scene got all the attention in R12 while five UI
     cells quietly repainted sixty times a second underneath it.
     Same look, no repaint: the calm state is a static shadow on the cell, and
     the bright state is a second static shadow on a pseudo-element whose
     OPACITY is animated. Opacity is a compositor property — it never touches
     the raster. `.mtr` is already position:relative and clip-path'd, so the
     overlay lands exactly on the cell and is trimmed by the same chamfer. */
  .mtr.low{box-shadow:inset 0 0 0 1px rgba(255,92,72,.5),0 0 16px rgba(255,80,60,.1)}
  /* z-index:-1, not 1. An inset box-shadow paints above the element's
     background and BELOW its content, and `.mtr` has a clip-path so it is
     already a stacking context — a negative child therefore lands in exactly
     that slot. At z-index:1 the ring would have been drawn over the number. */
  .mtr.low::before{content:'';position:absolute;inset:0;pointer-events:none;z-index:-1;
    box-shadow:inset 0 0 0 1px rgba(255,120,95,.9),0 0 30px rgba(255,80,60,.32);
    opacity:0;animation:lowpulse 1.9s ease-in-out infinite}
  @keyframes lowpulse{0%,100%{opacity:0}50%{opacity:1}}
  .mtr.low .vl{color:#ff9179}
  .mtr.low .nm{color:#c98476}
  .mtr.cash .vl{color:var(--acc);font-size:clamp(15px,1.12vw,23px)}
  /* P1-15 — one label, two lengths, and the crop picks. `.lgs` is the short
     one and it only exists where the long one does not fit. */
  .mtr .nm .lgs,.mtr .nm .lgt{display:none}
  /* R20 INTEGRATOR — this breakpoint was 430px, i.e. PORTRAIT ONLY, so the long
     label was still on at 844x390, where the cell is 53px and the word needs 55.
     The owner's second phone shape read HERITAGE FU… and the fit gate called it
     green: its ellipsis rule carried a +1px tolerance and the shortfall is
     exactly 1. Swept 700 -> 1000px at both landscape heights — the cell grows
     3px per 20px of viewport and the word does not fit until 860, so every
     landscape phone narrower than that was cut, not just the one named. 900 is
     the line, with 7px of headroom at the boundary for the Android fallback
     face (there is no @font-face in this build — P3-2). */
  @media (max-width:900px){
    .mtr .nm .lgw{display:none}
    .mtr .nm .lgs{display:inline}
  }
  .mtr.cash{box-shadow:inset 0 0 0 1px rgba(67,228,131,.28)}

  /* ---------------- trail track ---------------- */
  .track{position:relative;height:clamp(26px,3.4vh,40px);margin:0 clamp(4px,.6vw,14px)}
  .track .ln{position:absolute;left:0;right:0;top:38%;height:3px;background:rgba(255,255,255,.11);
    box-shadow:inset 0 0 0 1px rgba(0,0,0,.4)}
  /* same treatment as .mbar i — scaled, not resized (R12) */
  .track .fl{position:absolute;left:0;right:0;top:38%;height:3px;
    transform-origin:0 50%;transform:scaleX(0);
    background:linear-gradient(90deg,rgba(67,228,131,.2),var(--acc));box-shadow:0 0 14px rgba(67,228,131,.5)}
  .track .pip{position:absolute;top:38%;width:11px;height:11px;margin:-4px 0 0 -5px;
    background:#0a1220;box-shadow:inset 0 0 0 2px rgba(255,255,255,.28);transform:rotate(45deg)}
  .track .pip.done{background:var(--acc);box-shadow:inset 0 0 0 2px #0a1220,0 0 14px rgba(67,228,131,.8)}
  .track .lb{position:absolute;top:calc(38% + 12px);transform:translateX(-50%);
    font-family:var(--cond);font-weight:600;text-transform:uppercase;letter-spacing:.16em;
    font-size:clamp(7px,.5vw,10px);color:var(--txt4);white-space:nowrap}
  .track .lb.on{color:var(--accd)}
  .track .lb.end{transform:translateX(-100%)}
  .track .lb.start{transform:translateX(0)}
  .track .you{position:absolute;top:38%;transform:translate(-50%,-58%);
    filter:drop-shadow(0 0 10px rgba(67,228,131,.9))}

  /* ---------------- panels, tags, toasts ---------------- */
  .panel{position:relative;padding:clamp(14px,1.6vw,26px);
    background:linear-gradient(180deg,rgba(10,34,21,.92) 0,rgba(5,17,11,.95) 100%);
    box-shadow:inset 0 0 0 2px rgba(67,228,131,.42),inset 0 0 70px rgba(67,228,131,.05),0 22px 50px rgba(0,0,0,.55);
    clip-path:var(--chamfer)}
  .panel.dark{background:linear-gradient(180deg,rgba(13,22,38,.93) 0,rgba(5,10,19,.96) 100%);
    box-shadow:inset 0 0 0 2px rgba(150,205,255,.20),0 22px 50px rgba(0,0,0,.55)}
  .ph{display:flex;align-items:center;gap:10px;margin-bottom:clamp(9px,1.1vh,15px);
    padding-bottom:clamp(8px,.9vh,12px);border-bottom:1px solid rgba(67,228,131,.22)}
  .panel.dark .ph{border-bottom-color:rgba(150,205,255,.16)}

  .tag{display:inline-flex;align-items:center;gap:8px;padding:6px 12px 7px;
    background:rgba(4,10,17,.86);color:var(--acc);align-self:flex-start;
    box-shadow:inset 0 0 0 2px var(--acc2);clip-path:var(--chamfer-s)}
  .tag.amber{color:var(--amber);box-shadow:inset 0 0 0 2px rgba(242,178,60,.6)}
  .tag.red{color:#ff8a72;box-shadow:inset 0 0 0 2px rgba(255,92,72,.6)}

  .toast{display:inline-flex;align-items:center;gap:clamp(9px,.8vw,14px);align-self:center;
    padding:clamp(9px,1vh,14px) clamp(14px,1.3vw,24px);
    background:linear-gradient(180deg,rgba(12,42,24,.95),rgba(5,20,12,.96));
    box-shadow:inset 0 0 0 3px var(--acc),0 14px 34px rgba(0,0,0,.6),0 0 40px rgba(67,228,131,.18);
    clip-path:var(--chamfer-s)}
  .toast.warn{background:linear-gradient(180deg,rgba(48,18,12,.95),rgba(24,7,5,.96));
    box-shadow:inset 0 0 0 3px #ff6a4a,0 14px 34px rgba(0,0,0,.6),0 0 40px rgba(255,90,60,.18)}
  .toast .tt{font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.12em;
    font-size:clamp(11px,.8vw,15px);color:#fff}
  /* ============================================================
   R21b — THE BOX PANEL'S NAMES, WHICH ARE AT WIDTH ZERO ON EVERY PHONE

   NOT FIXED THIS PASS, AND THIS IS THE MEASUREMENT SO THE NEXT ONE DOES NOT
   HAVE TO FIND IT AGAIN. At 952x427, 844x390 and 390x844 the `.bn` label in
   every `.brow` measures 0-12px, so what a phone player sees in the truck bed
   is an emoji, a number and a red DROP. "Cans" needs 24px at 8px type, 33 at
   the 11px floor; "Airtime" needs 37 and 50. It is the same defect P2-2 names
   on the dock — the layout drops labels rather than reflowing — on the one
   panel P0-4 and P2-9 both point at, and it is not on this round's work order.

   I tried it and reverted it, which is worth more than the attempt: wrapping
   the row so the name gets its own line DOES work and costs the box strip
   ~14px, and that 14px comes straight out of `.storegrid` — the fit gate
   turned red with two items and two BUY buttons below the fold at 844x390 and
   390x844. Stretching the four rows to fill the strip (they are `flex:0 0 auto`
   at 109px inside an 803px panel) does not take, so something upstream is
   sizing them. Whoever picks this up: fix the strip's own width distribution
   first, and re-run store/outfit, store/landmark and store/rummage after.
   ============================================================ */

/* R21b — the route rail's second label row, at every phone crop rather than
   only upright, and far enough below the first that an 11px name cannot touch
   it. `stack` (game.js) decides when the class is emitted; these decide where
   the row sits and how much height the track reserves for it. Measured with
   the ten trail positions p2-check's P2-1 walks: worst overlap 49/51/58px
   before, 0 after. */
@media (max-width:1100px){
  :root:root .track.stack{height:38px}
  :root:root .track.compact.stack{height:36px}
  :root:root .track.compact .lb.alt{top:calc(38% + 21px)}
  :root:root .track .lb.alt{top:calc(38% + 25px)}
  /* ...and the two LANDMARKS share the second row, 18 points of track apart,
     which is 67px upright. At .1em tracking LETHBRIDGE and THE BORDER still
     met by 9px there and 2px at 844. Tracking is decoration; the name is not.
     Same trade the meter labels make under 1100px. */
  :root:root .track .lb{letter-spacing:0}
}

/* R21 — the last-day airtime credit, attributed on the count screen. */
.countwrap .cnote{margin-top:clamp(5px,.7vh,9px);font-family:var(--ui);
  font-size:clamp(11px,.76vw,14px);line-height:1.4;color:var(--txt3);text-align:center}
.countwrap .cnote b{color:var(--acc)}
  /* R21 — the count of the conditions the band did not promote. It is a
     number, not a name: the named one is beside it and the rest are on the
     meters, which already colour themselves `.low`. */
  .toast .tmore{flex:none;font-family:var(--cond);font-weight:700;letter-spacing:.06em;
    font-size:clamp(11px,.66vw,13px);color:#ffb3a2;opacity:.85;white-space:nowrap}
  /* the party's murmur is prose, not a status line — it gets sentence case */
  .toast.say{max-width:min(104ch,78vw);text-align:left;
    background:linear-gradient(180deg,rgba(9,26,17,.92),rgba(4,14,9,.94));
    box-shadow:inset 0 0 0 2px rgba(67,228,131,.55),0 12px 30px rgba(0,0,0,.55)}
  .toast.say .tt{font-family:var(--ui);font-weight:500;text-transform:none;letter-spacing:0;
    font-size:clamp(12px,.86vw,17px);line-height:1.4;color:#e6f2ea}

  /* ---------------- typography over the scene ---------------- */
  h1,h2,h3,p{margin:0}
  .ttl{font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.03em;
    font-size:clamp(24px,2.5vw,46px);line-height:1.02;color:#fff;font-stretch:88%;
    text-shadow:0 3px 0 rgba(0,0,0,.6),0 0 34px rgba(0,0,0,.85)}
  .ttl.huge{font-size:clamp(30px,3.7vw,70px);letter-spacing:.015em}
  .lead{font-size:clamp(15px,1.16vw,23px);line-height:1.46;font-weight:500;color:#f4f9fc;
    text-shadow:0 2px 3px rgba(0,0,0,.85),0 0 26px rgba(0,0,0,.75)}
  .lead.narrow{max-width:min(62ch,58vw)}
  .body2{font-size:clamp(13px,.95vw,18px);line-height:1.5;color:#d7e5ef;font-weight:500;
    text-shadow:0 2px 4px rgba(0,0,0,.9),0 0 20px rgba(0,0,0,.7)}
  /* a soft dark plate that keeps text legible over any scene, bright or dark */
  .scrimbox{position:relative;display:flex;flex-direction:column;align-items:center;
    gap:clamp(9px,1.3vh,20px);padding:clamp(16px,2vw,38px) clamp(20px,2.6vw,52px);
    /* R12 — was backdrop-filter:blur(8px) saturate(.85). See the note at the
       foot of this file: a backdrop-filter makes the compositor read back
       everything behind the element every time either one changes, and behind
       this one is a full-screen scene with a permanent drift animation on it.
       The blur was doing one job — keeping type legible over a busy painting —
       and a heavier scrim does that job at zero cost. Alpha .76/.6 -> .88/.76. */
    background:linear-gradient(180deg,rgba(4,9,17,.88) 0,rgba(4,9,17,.76) 100%);
    box-shadow:inset 0 0 0 1px rgba(150,205,255,.14),0 26px 60px rgba(0,0,0,.5);
    clip-path:var(--chamfer)}
  .scrimbox>*{flex:0 0 auto}
  .scrimbox.wide{width:min(1180px,84vw)}
  .kicker{font-family:var(--cond);font-weight:600;text-transform:uppercase;letter-spacing:.28em;
    font-size:clamp(9px,.62vw,12px);color:var(--accd)}
  .flavor{font-size:clamp(11px,.78vw,15px);color:#b9cbd8;font-style:italic;
    text-shadow:0 1px 3px rgba(0,0,0,.85),0 0 14px rgba(0,0,0,.7)}
  .em{color:var(--amber);font-weight:700;font-style:normal}

  /* ---------------- fx chips ---------------- */
  .fxrow{display:flex;flex-wrap:wrap;gap:clamp(6px,.5vw,10px)}
  .chip{display:inline-flex;align-items:center;gap:6px;padding:5px 10px 6px;
    font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.1em;
    font-size:clamp(10px,.7vw,14px);background:rgba(4,10,17,.82);clip-path:var(--chamfer-s)}
  .chip.up{color:#77f2a4;box-shadow:inset 0 0 0 1px rgba(67,228,131,.5)}
  .chip.dn{color:#ff9179;box-shadow:inset 0 0 0 1px rgba(255,92,72,.5)}

  /* ---------------- the rig (in-flow copies: title screen, store) ----------
     No pseudo-element blob shadow — the truck draws its own ground contact
     inside its viewBox, and two shadows read worse than one. */
  .rig{position:relative;display:flex;align-items:flex-end;justify-content:center}
  .rig svg{width:clamp(240px,34vw,560px);height:auto;display:block;
    filter:drop-shadow(0 20px 24px rgba(0,0,0,.5))}
  .rig.sm svg{width:clamp(180px,22vw,360px)}
  .rig.title svg{width:clamp(220px,28vw,480px)}
  .field.mid .tag.amber{margin-bottom:clamp(2px,.8vh,14px)}

  /* ---------------- leader cards ---------------- */
  .leaders{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(12px,1.5vw,30px);width:100%;
    max-width:1560px;margin:0 auto;align-items:stretch}
  .leader{position:relative;display:flex;flex-direction:column;gap:clamp(7px,1.1vh,17px);
    padding:clamp(14px,1.8vw,34px);cursor:pointer;border:0;text-align:center;font-family:var(--ui);
    background:linear-gradient(180deg,rgba(14,26,44,.92) 0,rgba(6,12,22,.95) 100%);
    box-shadow:inset 0 0 0 2px rgba(150,205,255,.18),0 20px 44px rgba(0,0,0,.55);
    clip-path:var(--chamfer);transition:transform .16s ease,box-shadow .16s ease,background .16s ease}
  .leader:hover{transform:translateY(-6px);background:linear-gradient(180deg,rgba(18,44,32,.94) 0,rgba(7,20,14,.96) 100%);
    box-shadow:inset 0 0 0 2px var(--acc),0 30px 60px rgba(0,0,0,.65),0 0 60px rgba(67,228,131,.14)}
  .leader:focus-visible{outline:3px solid var(--acc);outline-offset:3px}
  .leader .pf{position:relative;align-self:center;width:clamp(104px,13.6vw,268px);aspect-ratio:1;
    background:linear-gradient(180deg,rgba(4,10,17,.9),rgba(4,10,17,.5));
    box-shadow:inset 0 0 0 2px rgba(67,228,131,.35);clip-path:var(--chamfer-s);overflow:hidden}
  .leader .pf svg{position:absolute;inset:0;width:100%;height:100%}
  .leader .nm{font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.05em;
    font-size:clamp(15px,1.15vw,24px);color:#fff;line-height:1.05}
  .leader .rl{font-family:var(--cond);font-weight:600;text-transform:uppercase;letter-spacing:.16em;
    font-size:clamp(9px,.62vw,12px);color:var(--acc)}
  .leader .ds{font-size:clamp(11px,.78vw,15px);line-height:1.42;color:var(--txt2);flex:1}
  .leader .pk{margin-top:auto;padding:clamp(7px,.8vh,11px) 10px;font-size:clamp(10px,.7vw,13px);
    line-height:1.35;color:#bff3d3;background:rgba(9,40,23,.75);
    box-shadow:inset 0 0 0 1px rgba(67,228,131,.32);clip-path:var(--chamfer-s)}
  .leader .pk b{color:var(--acc);font-family:var(--cond);text-transform:uppercase;letter-spacing:.12em}
  .leader .st{display:flex;justify-content:center;gap:clamp(5px,.5vw,11px);flex-wrap:wrap}
  .leader .st span{font-family:var(--cond);font-weight:600;text-transform:uppercase;letter-spacing:.08em;
    font-size:clamp(8px,.56vw,11px);color:var(--txt3)}
  .leader .st b{color:#fff;font-weight:700}

  /* ---------------- store ---------------- */
  .storegrid{display:grid;grid-template-columns:1fr;gap:clamp(6px,.62vh,10px)}
  .item{display:flex;align-items:center;gap:clamp(9px,.8vw,15px);
    padding:clamp(8px,.9vh,13px) clamp(10px,.85vw,16px);
    background:linear-gradient(180deg,rgba(10,30,19,.75) 0,rgba(5,15,10,.8) 100%);
    box-shadow:inset 0 0 0 1px rgba(67,228,131,.20);clip-path:var(--chamfer-s)}
  .item .ic{flex:none;display:flex;align-items:center;justify-content:center;
    width:clamp(30px,2.4vw,44px);aspect-ratio:1;font-size:clamp(16px,1.3vw,25px);
    background:rgba(3,10,6,.7);box-shadow:inset 0 0 0 1px rgba(67,228,131,.25)}
  .item .info{flex:1;min-width:0}
  .item .nm{font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.06em;
    font-size:clamp(11px,.85vw,16px);color:#eaf6ef;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .item .ef{font-size:clamp(9px,.68vw,13px);color:var(--accd);margin-top:2px}
  .item .pr{font-family:var(--cond);font-weight:700;font-variant-numeric:tabular-nums;
    font-size:clamp(13px,1vw,19px);color:var(--acc);flex:none}
  .item .buy{flex:none;border:0;cursor:pointer;font-family:var(--cond);font-weight:700;
    text-transform:uppercase;letter-spacing:.12em;font-size:clamp(9px,.68vw,13px);
    padding:clamp(7px,.8vh,11px) clamp(10px,.9vw,17px);color:#03150a;
    background:linear-gradient(180deg,#3ee477,#159a48);clip-path:var(--chamfer-s);
    box-shadow:inset 0 0 0 2px rgba(0,0,0,.28),inset 0 2px 0 rgba(255,255,255,.3)}
  .item .buy:hover{filter:brightness(1.12)}
  .item .buy:disabled{filter:grayscale(.8) brightness(.5);cursor:not-allowed}

  /* ---------------- the ending: a poster, not a stack of bands -------------
     The headline IS the joke, so it is the largest thing on the screen and it
     sits above the fold on any crop. Everything else is subordinate: a small
     kicker chip, one short paragraph, and the party's closing line — which is
     usually the best sentence in the frame and used to be 11px grey italic on
     grass. It gets a plate and near-white type now. */
  .field.end{justify-content:flex-start;align-items:center;text-align:center;
    padding-top:clamp(8px,1.6vh,26px);padding-bottom:clamp(6px,1vh,14px);
    gap:clamp(7px,1.1vh,18px)}
  .endblock{position:relative;display:flex;flex-direction:column;align-items:center;
    gap:clamp(7px,1vh,17px);width:min(1500px,93vw)}
  /* A soft scrim under the type, not a panel. The ending screen composites
     display copy straight onto the painting, which was fine over a dusk field
     and stopped being fine the moment the flag overlay could put two 400px
     sheets of white cloth behind the body paragraph. Radial and fully
     transparent at the edges, so it darkens what is behind the words and does
     nothing at all to the frame. */
  .endblock::before{content:'';position:absolute;inset:-7% -9% -11%;pointer-events:none;z-index:0;
    background:radial-gradient(120% 88% at 50% 34%,rgba(3,8,14,.88) 0%,rgba(3,8,14,.74) 40%,
      rgba(3,8,14,.34) 66%,rgba(3,8,14,0) 87%)}
  .endblock>*{position:relative;z-index:1}
  .field.end .partybar{margin-top:auto}
  .endkick{display:inline-flex;align-items:center;gap:9px;padding:6px 14px 7px;
    color:var(--acc);background:rgba(4,10,17,.84);
    box-shadow:inset 0 0 0 2px var(--acc2);clip-path:var(--chamfer-s)}
  .endkick.bad{color:#ff9b83;box-shadow:inset 0 0 0 2px rgba(255,92,72,.7)}
  .endline{font-family:var(--cond);font-weight:700;text-transform:uppercase;
    font-size:var(--efs,clamp(30px,5.1vw,100px));line-height:.95;letter-spacing:-.005em;
    font-stretch:88%;color:#fff;max-width:100%;text-wrap:balance;
    text-shadow:0 4px 0 rgba(0,0,0,.55),0 0 14px rgba(0,0,0,.9),0 0 74px rgba(0,0,0,.75)}
  .endline.good{text-shadow:0 4px 0 rgba(0,0,0,.55),0 0 14px rgba(0,0,0,.85),0 0 86px rgba(67,228,131,.55)}
  .endline.bad{text-shadow:0 4px 0 rgba(0,0,0,.55),0 0 14px rgba(0,0,0,.85),0 0 86px rgba(255,80,60,.5)}
  .endbody{font-size:clamp(13px,1.02vw,20px);line-height:1.5;font-weight:500;color:#cfe0ee;
    max-width:min(84ch,66vw);text-wrap:pretty;
    text-shadow:0 2px 4px rgba(0,0,0,.92),0 0 22px rgba(0,0,0,.8)}
  .murmur{position:relative;text-align:left;max-width:min(94ch,70vw);
    padding:clamp(10px,1.3vh,18px) clamp(15px,1.4vw,26px) clamp(11px,1.4vh,19px) clamp(22px,1.9vw,36px);
    background:linear-gradient(180deg,rgba(7,13,22,.9) 0,rgba(4,8,15,.94) 100%);
    box-shadow:inset 0 0 0 1px rgba(150,205,255,.22),0 20px 44px rgba(0,0,0,.6);
    clip-path:var(--chamfer)}
  .murmur::before{content:'';position:absolute;left:0;top:0;bottom:0;width:5px;
    background:linear-gradient(180deg,var(--acc),var(--acc2))}
  .murmur.bad::before{background:linear-gradient(180deg,#ff8a72,#b8341f)}
  .murmur .mk{font-family:var(--cond);font-weight:600;text-transform:uppercase;
    letter-spacing:.26em;font-size:clamp(8px,.56vw,11px);color:var(--accd);margin-bottom:5px}
  .murmur.bad .mk{color:#d79c8c}
  .murmur .ml{font-size:clamp(15px,1.26vw,26px);line-height:1.38;color:#fff;font-style:italic;
    font-weight:500;text-shadow:0 1px 2px rgba(0,0,0,.8)}
  .murmur .mr{margin-top:clamp(5px,.6vh,9px);font-size:clamp(12px,.86vw,17px);line-height:1.5;
    color:#a9c0d3;font-style:italic}

  /* ---------------- report card (ending) ---------------- */
  .report{display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(8px,.8vw,16px)}
  /* R20b — the score card, hoisted INTO the type block on any short or narrow
     crop. It sits directly under the headline there, which is the only place
     on this screen that is guaranteed to be above the fold and out from under
     the dock (P1-3). On a wide screen this element is not emitted at all. */
  .endscore{width:100%;margin:clamp(1px,.4vh,6px) 0}
  .rc{padding:clamp(9px,1vh,15px) clamp(10px,.9vw,18px);text-align:left;
    background:linear-gradient(180deg,rgba(13,22,38,.92),rgba(5,10,19,.95));
    box-shadow:inset 0 0 0 1px rgba(150,205,255,.16);clip-path:var(--chamfer-tl)}
  .rc .k{font-family:var(--cond);font-weight:600;text-transform:uppercase;letter-spacing:.2em;
    font-size:clamp(8px,.55vw,11px);color:var(--txt3);margin-bottom:5px}
  .rc .v{font-family:var(--cond);font-weight:700;font-variant-numeric:tabular-nums;
    font-size:clamp(17px,1.5vw,32px);line-height:1;color:#fff}
  .rc.hero{background:linear-gradient(180deg,rgba(10,42,24,.92),rgba(4,18,11,.95));
    box-shadow:inset 0 0 0 2px rgba(67,228,131,.45),0 0 44px rgba(67,228,131,.12)}
  .rc.hero .v{color:var(--acc);text-shadow:0 0 26px rgba(67,228,131,.5)}
  .rc.hero .k{color:var(--accd)}

  /* ---------------- party strip (fed by partyHTML(), if it exists) ----------
     The strip carries its own CSS, injected at runtime into <head> AFTER this
     block, so every correction here is written at higher specificity on
     purpose. Nothing below reaches into the party lane's markup — it only
     re-levels the row so five cards read as one rhythm. */
  .partybar{display:flex;flex-wrap:wrap;align-items:stretch;justify-content:center;
    gap:clamp(7px,.7vw,14px);width:100%}
  .partybar svg{display:block}
  .partybar .pty-epi{color:#c6d6e2;text-shadow:0 1px 3px rgba(0,0,0,.85),0 0 14px rgba(0,0,0,.75)}
  /* the ending renders the epilogue itself, as a plate, above the fold */
  .partybar.memorial .pty-epi{display:none}
  /* on the trail the murmur is hoisted into the alert cluster at the top —
     left where it was, it landed square on the truck's wheels */
  .partybar.quiet .pty-line{display:none}
  .partybar.quiet .ptywrap{gap:0}
  /* R21 — THE ROSTER YIELDS TO THE REPORT, ON SHORT FIELDS ONLY.
     `report` is on the strip only while there is an outcome to read. On a
     landscape phone the road field is 137px once the header, the lifted status
     band and the dock have taken theirs, and the warning band, the report and
     five full party cards want ~190 of it; the block that lost was the prose,
     which is how P0-8's residue survived two rounds. So for as long as
     somebody is talking the cards drop to a face and a bar — the bar being the
     one thing that says which of them is about to get out — and the name comes
     back the moment the road is quiet again. The card is still a button and
     still opens the tip P2-5 added, so nothing here is unreachable, and the
     calm road, which is the screen you spend the run on, is untouched.
     Desktop and any tall screen keep the whole card in every state. */
  @media (max-height:560px),(max-width:430px){
    .field.road>.partybar.report .pty-nm,
    .field.road>.partybar.report .pty-rl,
    .field.road>.partybar.report .pty-ribbon,
    .field.road>.partybar.report .pty-lbl span{display:none}
    .field.road>.partybar.report .pty-lbl{margin-top:1px}
    .field.road>.partybar.report .ptyx>.pty-c{padding-top:2px;padding-bottom:2px}
    /* the face is what actually sets the card's height on a landscape phone —
       30px of portrait against 23px of everything else — so hiding the name
       alone moved the strip by nothing. Measured before writing this. */
    .field.road>.partybar.report .pty-face{width:24px;height:24px}
    /* and the last two: the warning band is one line now (see lowWarnings) and
       does not need 9px of padding above and below it, and the field's own
       bottom rail can give back half its gutter while a report is open. Each
       of these is worth 8px at 844x390, and the report needed 19. */
    .field.road{padding-bottom:10px}
    .field.road>.alerts .toast{padding-top:5px;padding-bottom:5px}
  }
  /* Dead cards were painted #212832 — LIGHTER than the live cards over a dark
     scene — so they bloomed and read as wider than their neighbours even
     though every card is the same 286px box. Same value for all five. */
  .partybar .ptyx>.pty-c.pty-out{
    background:linear-gradient(180deg,rgba(22,27,35,.94) 0,rgba(9,12,18,.95) 100%);
    box-shadow:inset 0 0 0 1px rgba(255,124,102,.24)}
  .partybar .ptyx>.pty-c{align-self:stretch}
  /* and the meta column centres regardless of whether it holds a meter or a
     headstone, so no card's contents ride a few pixels high */
  .partybar .ptyx>.pty-c>.pty-meta{display:flex;flex-direction:column;justify-content:center}
  .partybar .ptyx>.pty-c.pty-out .pty-gone{margin-top:clamp(3px,.4vh,7px)}
  /* the roster tile is a label, not a truncated sixth card — give it air */
  .partybar .ptyx>.pty-tile{margin-right:clamp(3px,.5vw,11px);align-self:stretch}

  /* ---------------- minigame ---------------- */
  .mgwrap{position:relative;align-self:center;padding:clamp(7px,.7vw,13px);
    background:linear-gradient(180deg,rgba(13,22,38,.9),rgba(5,10,19,.94));
    box-shadow:inset 0 0 0 2px rgba(67,228,131,.35),0 24px 50px rgba(0,0,0,.6);
    clip-path:var(--chamfer)}
  #mgcanvas{display:block;width:min(72vw,960px);height:auto;aspect-ratio:960/440;
    background:linear-gradient(180deg,#0d2a3f 0,#10405a 55%,#0b2a1c 100%);
    touch-action:none;cursor:crosshair}
  #mgscore{font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.16em;
    font-size:clamp(12px,.9vw,17px);color:var(--acc)}

  /* ---------------- layout atoms ---------------- */
  .pixw{position:relative;display:block;min-width:0;max-width:100%;color:inherit}
  .field.mid .tag,.field.mid .toast,.field.mid .speak{align-self:center}
  .herolock{display:flex;justify-content:center;width:100%}
  .rule{width:min(560px,44vw);height:2px;
    background:linear-gradient(90deg,transparent,rgba(67,228,131,.65),transparent);
    box-shadow:0 0 18px rgba(67,228,131,.45)}
  .dockhint{font-family:var(--cond);font-weight:600;text-transform:uppercase;letter-spacing:.22em;
    font-size:clamp(8px,.58vw,12px);color:var(--txt3);text-align:center;line-height:1.7;
    max-width:110ch;margin:0 auto}
  .speak{position:relative;display:flex;align-items:flex-start;gap:clamp(13px,1.4vw,30px);
    max-width:min(92ch,60vw);
    /* R20b — P1-9: `.field` HAD 158px OF REAL HORIZONTAL SCROLL EXTENT on the
       event, crossing and fork screens, and a touch swipe sheared the whole
       text block left while the scene plate stayed put ("NE BUYS A SECOND
       FLAGPOLE", body starting mid-word). DEFECTS blamed the 1px `.sr`
       screen-reader span. It is not: removing every `.sr` in the field leaves
       scrollWidth at 1110, unchanged. The cause is the plate declared directly
       below — `.speak::before` is position:absolute at right:-20%, i.e. 181px
       of deliberate soft exit past the box, and an absolutely positioned box
       contributes to its containing block's SCROLLABLE overflow whether or not
       anything is painted out there. Measured: `.speak` sw 1088 / cw 906, and
       blanking its entire subtree leaves 906 — no child is responsible.
       `overflow:clip` is the one value that stops the propagation without
       making this a scroll container, and `overflow-clip-margin` keeps the
       soft exit painted exactly as authored. 260px covers 20% of the widest
       `.speak` this layout can produce (min(92ch,60vw) at 1920 -> 1152).
       On the road `.field.road>.speak` sets overflow-y:auto later in this
       file, which per CSS Overflow 3 computes this clip to `hidden` — which is
       what that pane has had since r10, so nothing there moves. */
    overflow:clip;overflow-clip-margin:260px}
  /* the dialogue needs a real bounded plate, not a haze that happens to work
     because a tree is behind it. Hard on the reading edge, soft on the exit. */
  .speak::before{content:'';position:absolute;left:-9%;right:-20%;top:-26%;bottom:-32%;z-index:-1;
    background:
      radial-gradient(76% 92% at 26% 48%,rgba(3,7,14,.93) 0,rgba(3,7,14,.87) 36%,
        rgba(3,7,14,.48) 68%,rgba(3,7,14,0) 100%),
      radial-gradient(128% 150% at 18% 46%,rgba(3,7,14,.52) 0,rgba(3,7,14,0) 78%);
    /* Both plate gradients are off-centre, so neither reaches zero alpha before
       the pseudo-element's own box ends — the right and bottom edges were landing
       on the scene as hard grey lines (~.6 alpha at the bottom) on every event,
       result, crossing and fork screen. This mask is centred with 50% radii, so
       it is transparent at every point on the boundary no matter what the
       background layers are doing, while the asymmetric plate survives inside. */
    -webkit-mask-image:radial-gradient(50% 50% at 50% 50%,#000 0,#000 46%,transparent 100%);
            mask-image:radial-gradient(50% 50% at 50% 50%,#000 0,#000 46%,transparent 100%)}
  .speak .says{display:flex;flex-direction:column;align-items:flex-start;gap:clamp(7px,1vh,15px);min-width:0}
  .speak .lead{font-size:clamp(16px,1.36vw,29px);line-height:1.4;font-weight:600}
  .speak .ttl{font-size:clamp(23px,2.5vw,50px)}
  /* ---- THE SPEAKER COLUMN, R20 ----
     `.who` was a fixed-width column and `.plate` was allowed `max-width:170%`
     of it, centred. So on every phone the name plate hung ~9px past the
     column on BOTH sides — and `.field.road>.speak` sets overflow-y:auto,
     which in CSS forces overflow-x to `auto` as well, making `.speak` a
     clipping box. The left overhang landed exactly on that edge and was cut:
     measured 9px at 952x427, 9px left + 7px bottom at 844x390, 8px at
     390x844. On screen "Darlene Vasquez" rendered as ARLENE VASQUEZ and Chad
     Petrov as HAD PETROV, on the most-visited screen in the game.
     The column is shrink-to-fit now — it is as wide as the plate needs, up to
     170% of the PORTRAIT, which is the size the portrait keeps. So the plate
     can be wider than the face without ever leaving the column, and the
     column is inside `.speak` by construction. Every rule that used to set
     `.who{width:…}` sets `--pfw` instead. */
  .who{flex:none;display:flex;flex-direction:column;align-items:center;gap:clamp(6px,.7vh,10px);
    --pfw:clamp(78px,7.6vw,142px);
    width:auto;min-width:var(--pfw);max-width:calc(var(--pfw) * 1.7)}
  .pfx{position:relative;width:var(--pfw);flex:none;aspect-ratio:1;overflow:hidden;
    background:linear-gradient(180deg,rgba(6,12,20,.9),rgba(4,8,14,.95));
    box-shadow:inset 0 0 0 3px #fff,0 14px 30px rgba(0,0,0,.7)}
  .pfx svg{position:absolute;inset:0;width:100%;height:100%;display:block}
  .plate{padding:4px clamp(7px,.6vw,12px) 5px;background:rgba(5,10,17,.92);
    box-shadow:inset 0 0 0 2px rgba(255,255,255,.85);color:#fff;max-width:100%;
    font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.07em;
    font-size:clamp(8px,.6vw,12px);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .split{display:flex;gap:clamp(16px,2.4vw,48px);align-items:stretch;width:100%;height:100%;min-height:0}
  .split>.l{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;
    justify-content:flex-end;align-items:center;gap:clamp(8px,1.2vh,18px);text-align:center}
  .split>.r{flex:0 0 clamp(300px,31vw,540px);min-width:0;display:flex;flex-direction:column;justify-content:center}
  .split .panel{display:flex;flex-direction:column;min-height:0}
  .storegrid{overflow-y:auto;scrollbar-width:thin}

  .vote{display:flex;flex-direction:column;align-items:center;gap:clamp(6px,1vh,15px)}
  .votenum{font-family:var(--cond);font-weight:700;font-variant-numeric:tabular-nums;
    font-size:clamp(52px,8.4vw,164px);line-height:.86;color:#fff;letter-spacing:-.02em;
    text-shadow:0 6px 0 rgba(0,0,0,.5),0 0 70px rgba(90,168,255,.5)}
  .votebar{position:relative;width:min(780px,62vw);height:clamp(13px,1.5vh,22px);
    background:rgba(255,255,255,.07);box-shadow:inset 0 0 0 2px rgba(0,0,0,.55)}
  .votebar i{position:absolute;left:0;top:0;bottom:0;background:linear-gradient(90deg,#2f7ad6,#7cc0ff)}
  .votebar .th{position:absolute;top:-7px;bottom:-7px;width:3px;background:var(--acc);
    box-shadow:0 0 14px var(--acc);z-index:2}
  .votebar::after{content:'';position:absolute;inset:0;pointer-events:none;
    background:repeating-linear-gradient(90deg,transparent 0 calc(5% - 2px),rgba(5,9,17,.92) calc(5% - 2px) 5%)}
  .votekeys{display:flex;justify-content:space-between;width:min(780px,62vw);
    font-family:var(--cond);font-weight:600;text-transform:uppercase;letter-spacing:.18em;
    font-size:clamp(8px,.56vw,11px);color:var(--txt3)}

  .ttl.glow{text-shadow:0 3px 0 rgba(0,0,0,.6),0 0 60px rgba(67,228,131,.45),0 0 24px rgba(0,0,0,.9)}
  .ttl.glowbad{text-shadow:0 3px 0 rgba(0,0,0,.6),0 0 60px rgba(255,92,72,.4),0 0 24px rgba(0,0,0,.9)}

  /* ---------------- entrance motion (settles well under the 260ms shot delay) ----
     R9 — what moves, and what has the decency to stay put.
     Once the world stopped being rebuilt, a second flash was measurable: the
     header and dock animated in from opacity 0 on every click, so 80 of 282
     painted frames had the console between half-there and invisible. Twenty
     times a run the meter rail blinks out. That is furniture behaving like
     content. So the console is furniture now: the header does not move at all,
     and the dock settles eight pixels — enough that a change in its HEIGHT
     reads as a settle rather than a jump — without ever dropping below full
     opacity. Only the FIELD still fades, because the field really is a
     different thing on the new screen, and a dissolve over a scene that is
     still there is a transition rather than a flash. */
  .field{animation:riseIn .19s cubic-bezier(.2,.8,.3,1) .04s both}
  .dock{animation:settleU .19s cubic-bezier(.2,.8,.3,1) both}
  /* R10 — going somewhere else should not look the same as staying put. The
     rule above ran identically whether the game had turned a page or merely
     repainted the screen you were already on, so nothing on screen told you
     which had happened. `.turn` is set by shell() only when the SCREEN KIND
     changes: a slightly deeper arrival, a hair of scale, and the header's
     title comes with it. It still finishes inside the 260ms tools/shoot.js
     settles for, and it still never drops the dock below full opacity —
     which is the invariant round 9 bought with a whole round. */
  .frame.turn .field{animation:turnIn .2s cubic-bezier(.2,.8,.3,1) .03s both}
  .frame.turn .htitle{animation:turnHead .2s cubic-bezier(.2,.8,.3,1) both}
  @keyframes turnIn{from{opacity:0;transform:translate3d(0,22px,0) scale(.988)}
    to{opacity:1;transform:none}}
  @keyframes turnHead{from{opacity:.25;transform:translate3d(0,-6px,0)}to{opacity:1;transform:none}}
  /* the dock row that arrives when an event's options become the road's */
  .dockin{animation:dockIn .28s cubic-bezier(.2,.8,.3,1) both}
  @keyframes dockIn{from{opacity:0;transform:translate3d(0,16px,0)}to{opacity:1;transform:none}}
  /* the flag going up a level — the one meter you cannot buy your way out of */
  .stat.pulse{animation:flagPulse .6s ease-out both}
  @keyframes flagPulse{0%{transform:scale(1)}
    22%{transform:scale(1.22);filter:drop-shadow(0 0 10px rgba(232,99,109,.95))}
    100%{transform:scale(1)}}
  /* This still exists and it still says .22s, but it is a different rule now.
     The .stage node is built once per page load instead of once per click, so
     it runs exactly once — the world fades up when the game opens, and then
     never blinks again for the rest of the run. */
  .stage{animation:fadeIn .22s ease both}
  @keyframes fadeIn{from{opacity:0}to{opacity:1}}
  @keyframes riseIn{from{opacity:0;transform:translate3d(0,14px,0)}to{opacity:1;transform:none}}
  @keyframes settleU{from{transform:translate3d(0,8px,0)}to{transform:none}}
  @media (prefers-reduced-motion:reduce){
    *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;
      transition-duration:.01ms!important}
  }

  /* ================= R6 — the loop, the count, the menu =================
     Three additions, all of them subtractions in disguise.
     1. `.field.road` is the merged screen: what just happened, what the road
        looked like, who is left, all in one view with one button under it.
        There is no longer a separate "outcome" beat and "trail" beat.
     2. `.count*` is Referendum Day, which used to print the answer above the
        button that asks for it.
     3. `.menupanel` is the thing behind the hamburger, which for five rounds
        was a drawing of a button. */

  /* --- the road report --- */
  .field.road{justify-content:flex-start;align-items:stretch;
    gap:clamp(8px,1.2vh,18px);padding-top:clamp(12px,2vh,34px)}
  .field.road>.alerts{align-self:center;margin-bottom:0}
  .field.road>.speak{align-self:flex-start}
  .field.road>.partybar{margin-top:auto}
  /* the miles nobody had to click through, narrated instead of charged for */
  .milepost{align-self:center;max-width:min(92ch,66vw);text-align:center;
    padding:clamp(7px,.8vh,11px) clamp(13px,1.2vw,22px);
    background:linear-gradient(180deg,rgba(5,11,19,.72),rgba(3,7,13,.78));
    box-shadow:inset 0 0 0 1px rgba(150,205,255,.13);clip-path:var(--chamfer-s)}
  .milepost .mk{font-family:var(--cond);font-weight:600;text-transform:uppercase;
    letter-spacing:.24em;font-size:clamp(8px,.54vw,11px);color:var(--txt3);margin-bottom:4px}
  .milepost p{font-size:clamp(12px,.86vw,17px);line-height:1.45;color:#c9dbe8;font-style:italic;
    text-shadow:0 2px 4px rgba(0,0,0,.9)}
  .milepost p+p{margin-top:4px}

  /* ================= R7 — THE DRIVE CONSOLE =========================
     Round 6 halved the screens per run and left the honest problem standing:
     61% of every click in the game was still one button reading HIT THE ROAD,
     and it meant nothing except "next". This is Oregon Trail's pace and
     rations, put exactly where the travel click already was, so it costs
     neither a screen nor an extra press.

     Two rows, both in the dock, both above the fold at 390x844:

       .console — the standing policy on Double-Doubles, sharing its row with
                  the store door. Rations ARE a setting — one line, three
                  segments, the selected one lit — because a standing order on
                  the coffee is a policy, not a manoeuvre.
       .drive   — the travel action itself, split into the three ways to drive
                  it. There is deliberately NO selected state here: pace is not
                  a preference you set, it is the press. The middle cell still
                  says HIT THE ROAD, still carries the primary green, and still
                  means what it always meant, so every harness that clicks by
                  text keeps working and the muscle memory survives.

     Each cell is priced in the same delta chips the outcome screen uses, so
     the control speaks the game's own language of consequence rather than the
     language of a settings panel. The numbers are per leg, against Steady.
     ================================================================= */
  .console{display:flex;align-items:center;gap:clamp(8px,.8vw,14px)}
  /* the plate is snug around its content rather than stretched across the
     dock: three segments spread over 1500px would be the reference screen's
     own worst habit — big tiles holding three characters each */
  .rations{flex:0 1 auto;min-width:0;display:flex;align-items:stretch;
    gap:clamp(7px,.7vw,14px);
    padding:clamp(4px,.4vh,6px) clamp(8px,.7vw,14px);
    background:linear-gradient(180deg,rgba(17,29,47,.92) 0,rgba(7,13,23,.94) 100%);
    box-shadow:inset 0 0 0 1px rgba(150,205,255,.15);clip-path:var(--chamfer-tl)}
  .rations .rcap{flex:0 0 auto;display:flex;align-items:center;gap:7px;
    font-family:var(--cond);font-weight:600;text-transform:uppercase;letter-spacing:.2em;
    font-size:clamp(8px,.52vw,11px);color:var(--txt3);white-space:nowrap}
  .rations .rcap svg{color:var(--mrl);height:18px;width:18px}
  .segs{flex:0 1 auto;min-width:0;display:grid;grid-auto-flow:column;
    grid-auto-columns:minmax(0,max-content);gap:clamp(4px,.36vw,7px)}
  .seg{display:flex;flex-direction:column;align-items:center;justify-content:center;
    gap:2px;min-width:clamp(74px,8vw,148px);cursor:pointer;border:0;text-align:center;
    padding:clamp(4px,.5vh,7px) clamp(6px,.6vw,14px);
    background:rgba(4,10,18,.6);box-shadow:inset 0 0 0 1px rgba(150,205,255,.13);
    clip-path:var(--chamfer-s);transition:background .13s ease,box-shadow .13s ease}
  .seg .sn{font-family:var(--cond);font-weight:700;text-transform:uppercase;
    letter-spacing:.1em;font-size:clamp(9px,.66vw,13px);line-height:1;color:#93a7bb;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
  /* NOT `.sc` — that class is already the scene layer, and an `inset:0` on a
     child of a static parent parked this line across the whole dock. */
  .seg .scst{display:flex;align-items:center;justify-content:center;gap:5px;
    font-family:var(--cond);font-weight:600;font-variant-numeric:tabular-nums;
    letter-spacing:.05em;font-size:clamp(8px,.58vw,12px);line-height:1;color:var(--txt4);
    white-space:nowrap}
  .seg .scst svg{height:9px;width:9px;margin-right:2px}
  .seg .scst b{font-weight:700;color:#b6c7d6}
  .seg .scst .sx{display:inline-flex;align-items:center}
  /* R7, integrator — the baseline order carries no price, so it says so in the
     same words the middle pace cell does instead of printing a bare `$0` that
     reads as free-and-therefore-better. */
  .seg .scst .nil{color:#8fa6b8;font-weight:600;letter-spacing:.08em}
  .seg.on .scst .nil{color:#a8c6b6}
  .seg.on .scst b{color:#e6fff0}
  .seg:hover{background:rgba(14,30,48,.8);box-shadow:inset 0 0 0 1px rgba(150,205,255,.3)}
  .seg:focus-visible{outline:2px solid var(--acc);outline-offset:2px}
  .seg.on{background:linear-gradient(180deg,rgba(22,64,42,.95),rgba(8,32,21,.95));
    box-shadow:inset 0 0 0 1px rgba(67,228,131,.6),0 0 16px rgba(67,228,131,.13)}
  .seg.on .sn{color:var(--acc)}
  .seg.on .scst{color:#a8c6b6}
  .console .rummage{flex:0 0 auto;white-space:nowrap;margin-left:auto;
    padding:clamp(11px,1.25vh,15px) clamp(16px,1.5vw,28px)}
  /* The pace lane's fragment wraps rationsHTML() in `.pacecon` so it can hang
     a party-strain warning under the strip, and its own injected rules stretch
     the plate across the row. Held at content width here for the same reason
     the bare `.rations` is: three segments spread over 1500px is the empty-tile
     failure the reference screens get marked down for. Specificity, not source
     order, does the work — that stylesheet is injected into <head> after this
     one. */
  .console .pacecon{flex:0 1 auto;min-width:0;display:flex;flex-direction:column;
    align-items:flex-start;justify-content:center;gap:clamp(3px,.4vh,6px)}
  .console .pacecon>.rations{flex:0 1 auto}

  /* --- the travel action, which is now three travel actions ---
     R7, INTEGRATOR — the ratio was 1fr 1.16fr 1fr, which gave the most width
     to the cell carrying ONE chip and the least to the two carrying five and
     six. Measured rows of price chips per cell (easy/steady/hammer) before and
     after: 900px 3/1/2 -> 2/1/2, 390px 3/1/2 -> 2/1/2, and the phone dock drops
     359px -> 341px. No breakpoint gets worse. The middle cell keeps the primary
     green and its own kicker line, which is what makes it read as the default —
     the extra 16% of width was never what was doing that, and the shell lane's
     own note says the green cell already takes more of the eye than it should. */
  .drive{display:grid;grid-template-columns:1.08fr 1fr 1.08fr;
    gap:clamp(7px,.7vw,14px);align-items:stretch}
  /* `.pace` also carries `.choice`, because these ARE options — so a handful
     of the choice-panel rules have to be answered explicitly rather than just
     overridden by source order (::before in particular sets left/width for a
     side stripe and this one is a top rule). */
  .pace{position:relative;display:flex;flex-direction:column;align-items:center;
    justify-content:center;text-align:center;
    gap:clamp(4px,.55vh,8px);min-width:0;cursor:pointer;border:0;
    padding:clamp(9px,1.15vh,16px) clamp(8px,.9vw,20px);
    background:linear-gradient(180deg,var(--p1) 0,var(--p2) 100%);
    box-shadow:inset 0 0 0 2px rgba(0,0,0,.3),inset 0 3px 0 rgba(255,255,255,.13),
      inset 0 -3px 0 rgba(0,0,0,.22);
    clip-path:var(--chamfer);filter:drop-shadow(0 8px 16px rgba(0,0,0,.55));
    transition:filter .12s ease,transform .08s ease}
  .pace::before{content:'';position:absolute;left:8px;right:8px;top:0;bottom:auto;
    width:auto;height:3px;background:var(--pa);opacity:.9;transition:none}
  .pace:hover{filter:drop-shadow(0 12px 22px rgba(0,0,0,.62)) brightness(1.11)}
  .pace:active{transform:translateY(2px)}
  .pace:focus-visible{outline:3px solid #fff;outline-offset:3px}
  .pace .pn{display:flex;align-items:baseline;gap:clamp(6px,.6vw,12px);max-width:100%;
    font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.13em;
    font-size:clamp(13px,1.02vw,21px);line-height:1;color:var(--pt)}
  .pace .kick{font-family:var(--ui);font-weight:500;font-style:italic;text-transform:none;
    letter-spacing:0;font-size:clamp(10px,.7vw,14px);color:var(--pk);white-space:nowrap;
    overflow:hidden;text-overflow:ellipsis;min-width:0}
  .pace .pchips{display:flex;flex-wrap:wrap;justify-content:center;
    gap:clamp(3px,.34vw,7px);max-width:100%}
  .pchip{display:inline-flex;align-items:center;gap:4px;padding:2px 6px 3px;
    font-family:var(--cond);font-weight:700;font-variant-numeric:tabular-nums;
    text-transform:uppercase;letter-spacing:.07em;font-size:clamp(9px,.64vw,13px);
    line-height:1.15;background:rgba(3,8,14,.55);clip-path:var(--chamfer-s);white-space:nowrap}
  .pchip svg{height:9px;width:9px;display:block}
  .pchip>span:first-child{display:inline-flex;align-items:center}
  .pchip.up{color:#8ef7b3;box-shadow:inset 0 0 0 1px rgba(67,228,131,.42)}
  .pchip.dn{color:#ffa48f;box-shadow:inset 0 0 0 1px rgba(255,92,72,.42)}
  .pchip.nil{color:#8fa6b8;box-shadow:inset 0 0 0 1px rgba(150,205,255,.24)}
  /* one hue per pace, borrowed from the meter it hurts most: cyan is the
     truck, green is go, amber is the tank. Nobody has to read to know which
     end of the row is the expensive one. */
  .pace.easy  {--p1:#17414f;--p2:#0a2733;--pa:var(--trk);--pt:#9cecf5;--pk:#8ab6c1}
  .pace.steady{--p1:#3ee477;--p2:#159a48;--pa:#bdffd8;--pt:#03150a;--pk:#0a3d1f}
  .pace.hammer{--p1:#573316;--p2:#301a09;--pa:var(--oil);--pt:#ffcd77;--pk:#c69b71}
  .pace.steady .pchip.nil{color:#0a3d1f;background:rgba(255,255,255,.22);
    box-shadow:inset 0 0 0 1px rgba(3,21,10,.28)}

  /* --- an event resolving under its own setup, without navigating --- */
  .speak .outcome{position:relative;display:flex;flex-direction:column;width:100%;
    gap:clamp(6px,.9vh,13px);padding-top:clamp(10px,1.3vh,18px);
    animation:riseIn .24s cubic-bezier(.2,.8,.3,1) both}
  .speak .outcome::before{content:'';position:absolute;left:0;top:0;height:2px;
    width:min(230px,58%);background:linear-gradient(90deg,var(--acc),rgba(67,228,131,0))}
  /* once the outcome is in, the setup steps back — it is context now, not the
     thing being read. R10: this node is NOT rebuilt (a class is added to it),
     so unlike almost every transition in this file it can actually fire, and
     the recede is something the player watches happen. */
  .speak .says>.lead{transition:font-size .26s cubic-bezier(.2,.8,.3,1),
    color .26s ease,opacity .26s ease}
  .speak .says>.ttl{transition:font-size .26s cubic-bezier(.2,.8,.3,1),opacity .26s ease}
  .speak .says.resolved>.lead{font-size:clamp(13px,1vw,19px);line-height:1.44;
    font-weight:500;color:#8fa6b8;opacity:.85}
  .speak .says.resolved>.ttl{font-size:clamp(17px,1.5vw,30px);opacity:.8}
  .speak .says.resolved .outcome .lead{font-size:clamp(16px,1.36vw,29px);line-height:1.4;
    font-weight:600;color:#f4f9fc;opacity:1}
  .speak .says .outcome .fxrow{animation:chipsIn .34s cubic-bezier(.2,.8,.3,1) .1s both}
  @keyframes chipsIn{from{opacity:0;transform:translate3d(0,7px,0)}to{opacity:1;transform:none}}
  /* the speaker changing when somebody gets out, rather than a new screen */
  .who.swapin{animation:whoSwap .34s cubic-bezier(.2,.8,.3,1) both}
  @keyframes whoSwap{0%{opacity:0;transform:translate3d(-10px,0,0) scale(.94)}
    100%{opacity:1;transform:none}}

  /* --- referendum day: sealed, then counted --- */
  .sealbox{display:flex;flex-direction:column;align-items:center;gap:clamp(8px,1.3vh,20px)}
  .sealnum{font-family:var(--cond);font-weight:700;font-size:clamp(44px,7vw,132px);
    line-height:.9;color:#2c4260;letter-spacing:.06em;
    text-shadow:0 4px 0 rgba(0,0,0,.5)}
  /* the count is a broadcast graphic and it has to read over a field full of
     people and bunting, so it sits on its own plate rather than compositing
     straight onto the scene the way the dialogue screens do */
  .countwrap{position:relative;display:flex;flex-direction:column;align-items:center;
    width:min(880px,88vw);gap:clamp(5px,.9vh,15px);
    padding:clamp(13px,1.7vw,28px) clamp(15px,2vw,40px) clamp(15px,1.9vw,30px);
    /* R12 — was backdrop-filter:blur(9px) saturate(.85). Referendum Day is the
       screen with the most going on behind the plate (a field full of people
       and bunting) and therefore the most expensive readback in the game. */
    background:linear-gradient(180deg,rgba(4,9,17,.93) 0,rgba(4,9,17,.86) 100%);
    box-shadow:inset 0 0 0 1px rgba(150,205,255,.18),0 26px 60px rgba(0,0,0,.6);
    clip-path:var(--chamfer)}
  .countwrap>*{flex:0 0 auto}
  .countnum{font-family:var(--cond);font-weight:700;font-variant-numeric:tabular-nums;
    font-size:clamp(54px,8.6vw,168px);line-height:.86;letter-spacing:-.02em;color:#dbe9f6;
    text-shadow:0 6px 0 rgba(0,0,0,.5),0 0 70px rgba(90,168,255,.42)}
  .countnum span{font-size:.4em;opacity:.65}
  .countnum.yes{color:#7cf0a8;text-shadow:0 6px 0 rgba(0,0,0,.5),0 0 84px rgba(67,228,131,.6)}
  .countnum.no{color:#ff9179;text-shadow:0 6px 0 rgba(0,0,0,.5),0 0 84px rgba(255,90,60,.5)}
  .countbar{position:relative;width:100%;height:clamp(15px,1.9vh,26px);
    background:rgba(255,255,255,.07);box-shadow:inset 0 0 0 2px rgba(0,0,0,.55)}
  .countbar i{position:absolute;left:0;top:0;bottom:0;
    background:linear-gradient(90deg,#2f7ad6,#7cc0ff);
    transition:width .5s cubic-bezier(.2,.8,.3,1),background .4s ease}
  .countbar i.over{background:linear-gradient(90deg,#16a04e,#5fe08a)}
  .countbar .th{position:absolute;top:-8px;bottom:-8px;width:3px;background:#fff;
    box-shadow:0 0 15px rgba(255,255,255,.9);z-index:2}
  .countbar::after{content:'';position:absolute;inset:0;pointer-events:none;
    background:repeating-linear-gradient(90deg,transparent 0 calc(5% - 2px),rgba(5,9,17,.92) calc(5% - 2px) 5%)}
  .countkeys{display:flex;justify-content:space-between;width:100%;
    font-family:var(--cond);font-weight:600;text-transform:uppercase;letter-spacing:.18em;
    font-size:clamp(8px,.56vw,11px);color:var(--txt3)}
  /* R20b — THE PANEL IS BOUNDED BY THE FIELD, AND THE FEED IS WHAT GIVES.
     `#cfeed` reserves room for the districts as they report, and it measured
     157px of EMPTY box on a 276px field before a single one had come in — so
     the climax screen of a forty-day run opened with its own question sliced
     off the top (measured `.countwrap cut T42px`, and the line that went was
     "Yes — leave Confederation"). Every other child of the panel is one line
     of type and none of them may move; the reporting list is the one thing
     that can show four rows instead of six and still be the same screen. */
  .countwrap{max-height:100%}
  .field .countwrap>.feed{flex:1 1 auto;min-height:0;overflow:hidden;
    justify-content:flex-end}
  .feed{display:flex;flex-direction:column;gap:clamp(3px,.4vh,6px);width:100%}
  .feed .fr{display:flex;align-items:baseline;gap:clamp(8px,.8vw,14px);
    padding:clamp(4px,.5vh,7px) clamp(10px,.9vw,16px);
    background:rgba(4,10,17,.78);box-shadow:inset 0 0 0 1px rgba(150,205,255,.14);
    clip-path:var(--chamfer-s)}
  /* only the district that just came in animates — the four under it are the
     ones that already came in, and re-animating those reads as a glitch */
  .feed .fr:first-child{animation:feedin .26s ease both}
  @keyframes feedin{from{opacity:0;transform:translate3d(0,-9px,0)}to{opacity:1;transform:none}}
  .feed .fn{flex:1;min-width:0;font-family:var(--cond);font-weight:600;text-transform:uppercase;
    letter-spacing:.13em;font-size:clamp(9px,.66vw,13px);color:#c6d8e6;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .feed .fv{font-family:var(--cond);font-weight:700;font-variant-numeric:tabular-nums;
    font-size:clamp(11px,.82vw,16px)}
  .feed .fv.y{color:#7cf0a8}
  .feed .fv.n{color:#ff9179}
  .callline{font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.22em;
    font-size:clamp(10px,.78vw,16px);color:var(--accd);text-align:center;min-height:1.3em;
    text-shadow:0 2px 6px rgba(0,0,0,.9)}
  .callline.hot{color:#ffd479;text-shadow:0 2px 6px rgba(0,0,0,.9),0 0 26px rgba(255,190,90,.5)}
  .callline.yes{color:#7cf0a8}
  .callline.no{color:#ff9179}
  .polls{font-family:var(--cond);font-weight:600;text-transform:uppercase;letter-spacing:.2em;
    font-size:clamp(8px,.58vw,12px);color:var(--txt3);font-variant-numeric:tabular-nums}
  /* the stamp's box is reserved from the first frame of the count so the plate
     does not grow by 60px at the exact moment the player is reading it */
  .verdictbox{display:flex;align-items:center;justify-content:center;width:100%;
    min-height:clamp(30px,3.1vw,56px);margin-top:clamp(2px,.5vh,8px)}
  .verdict{font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.02em;
    font-size:clamp(21px,2.7vw,52px);line-height:1;color:#fff;font-stretch:88%;text-align:center;
    animation:stamp .34s cubic-bezier(.2,1.4,.4,1) both;
    text-shadow:0 4px 0 rgba(0,0,0,.55),0 0 40px rgba(0,0,0,.8)}
  .verdict.yes{color:#8bffb8;text-shadow:0 4px 0 rgba(0,0,0,.55),0 0 60px rgba(67,228,131,.6)}
  .verdict.no{color:#ff9d86;text-shadow:0 4px 0 rgba(0,0,0,.55),0 0 60px rgba(255,90,60,.55)}
  @keyframes stamp{from{opacity:0;transform:scale(1.5)}to{opacity:1;transform:none}}
  /* the support cell during the vote: the number is the thing being counted */
  .mtr.sealed .vl{color:#5a7186;font-size:clamp(10px,.72vw,14px);letter-spacing:.14em}
  .mtr.sealed .mbar i{background:repeating-linear-gradient(135deg,
    rgba(90,113,134,.5) 0 6px,rgba(90,113,134,.14) 6px 12px)!important}

  /* --- the menu that was a drawing of a menu --- */
  button.sys{border:0;padding:0;font:inherit;cursor:pointer;z-index:6}
  button.sys:hover{background:#0c1828}
  button.sys:focus-visible{outline:3px solid var(--acc);outline-offset:-4px}
  button.sys[aria-expanded="true"]{background:#10281b;color:#7cf0a8}
  /* R12 — was backdrop-filter:blur(7px). The menu can be opened over any
     screen in the game, including the minigame's canvas, so this one was a
     full-viewport readback of live content. */
  .menuwrap{position:absolute;inset:0;z-index:9;display:flex;align-items:flex-start;
    background:linear-gradient(180deg,rgba(2,5,11,.9) 0,rgba(2,5,11,.8) 100%);
    animation:fadeIn .14s ease both}
  .menupanel{margin:clamp(56px,7.2vh,98px) 0 0 clamp(8px,1.4vw,30px);
    width:min(440px,90vw);max-height:min(84vh,760px);overflow-y:auto;scrollbar-width:thin;
    padding:clamp(13px,1.4vw,22px);
    background:linear-gradient(180deg,rgba(13,23,39,.97) 0,rgba(5,10,19,.985) 100%);
    box-shadow:inset 0 0 0 2px rgba(150,205,255,.26),0 30px 70px rgba(0,0,0,.72);
    clip-path:var(--chamfer);animation:riseIn .17s cubic-bezier(.2,.8,.3,1) both}
  .menupanel .mh{display:flex;align-items:center;gap:9px;color:var(--acc);
    padding-bottom:clamp(7px,.8vh,11px);margin-bottom:clamp(8px,1vh,13px);
    border-bottom:1px solid rgba(150,205,255,.16)}
  .mrow{display:flex;align-items:center;gap:11px;width:100%;text-align:left;border:0;
    cursor:pointer;font-family:var(--cond);font-weight:700;text-transform:uppercase;
    letter-spacing:.13em;font-size:clamp(12px,.86vw,16px);color:#dceaf4;
    padding:clamp(9px,1.1vh,14px) clamp(11px,1vw,16px);margin-bottom:clamp(5px,.6vh,8px);
    background:linear-gradient(180deg,rgba(20,34,54,.9) 0,rgba(9,16,28,.94) 100%);
    box-shadow:inset 0 0 0 1px rgba(150,205,255,.18);clip-path:var(--chamfer-s)}
  /* R20c — P2-21. The root menu went from four rows to seven (settings,
     credits, and a safe QUIT TO TITLE beside the armed ABANDON), and every one
     of them has to stay ON SCREEN — fit-check's menu/root pane holds `own` on
     .mrow, which means the centre of every row must hit-test to itself, so a
     list that scrolls out of the panel is not an option. The panel is 413px
     wide in an 844px viewport: the room was sideways all along. Two columns on
     a short screen halves the height, and the destructive row spans, which is
     also what keeps it out of the path of a thumb going for the first row. */
  .mlist{display:grid;grid-template-columns:1fr;gap:0}
  @media (max-height:560px) and (min-width:601px){
    .menupanel{width:min(560px,92vw)}
    .mlist{grid-template-columns:1fr 1fr;column-gap:clamp(5px,.6vw,8px)}
    .mlist .mrow.wide{grid-column:1/-1}
  }
  /* the state a row carries, in the voice the Sound row already used */
  .mrow .auq{margin-left:auto;padding-left:9px;opacity:.62;font-weight:400;
    text-transform:none;letter-spacing:.02em;white-space:nowrap;overflow:hidden;
    text-overflow:ellipsis;min-width:0}
  /* R20c — P2-32. The shortcut, printed ON the control, the way the reference
     prints [Q]/[E] on its store tabs. Drawn only where there is a keyboard. */
  .kbd{margin-left:auto;flex:none;display:inline-flex;align-items:center;justify-content:center;
    min-width:20px;padding:2px 5px 3px;font-family:var(--cond);font-weight:700;
    font-size:clamp(9px,.62vw,12px);letter-spacing:.06em;color:var(--txt3);
    background:rgba(150,205,255,.07);box-shadow:inset 0 0 0 1px rgba(150,205,255,.22);
    clip-path:var(--chamfer-s)}
  .mrow .auq + .kbd{margin-left:9px}
  @media (hover:none),(pointer:coarse){ .kbd{display:none} }

  /* ---- settings, in the .seg grammar the ration group already uses ---- */
  .msetrow{margin-bottom:clamp(9px,1.2vh,15px)}
  .msetk{font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.16em;
    font-size:clamp(10px,.72vw,13px);color:var(--txt2);margin-bottom:5px}
  /* R21b — `grid-auto-columns:1fr` is `minmax(auto,1fr)`, so every track was at
     least its own MIN-CONTENT, and `.seg` carries `min-width:clamp(74px,8vw,148px)`
     for the ration group in the dock. Five volume segments at 148px is 764px of
     content inside a 396px panel: the Settings body scrolled SIDEWAYS 368px on
     a 1920x1080 monitor, which is the kind of thing a reviewer screenshots.
     `minmax(0,1fr)` lets the track shrink; releasing the floor lets the button
     shrink with it. Measured after: scrollWidth == clientWidth at all four. */
  .msetrow .segs{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);
    gap:clamp(3px,.35vw,6px)}
  .msettings .seg{min-width:0}
  .msetn{margin-top:5px;font-family:var(--ui);font-size:clamp(10px,.7vw,12px);
    line-height:1.4;color:var(--txt4)}
  .msettings .mrow{margin-bottom:clamp(9px,1.2vh,15px)}
  /* R21b — SETTINGS ON A LANDSCAPE PHONE.
     The panel is 560px wide and 328px tall at 844x390. Stacked, its four rows
     want 308px of a 213px body: the Motion row was cut 18px and the Text size
     row — the control P1-10's fix depends on being able to reach — was
     ENTIRELY OUTSIDE the panel. That is the circular dependency DEFECTS names,
     and it is a width problem wearing a height problem's clothes: the room on
     this crop has always been sideways, which is the same thing the root menu
     found when it went to two columns at exactly this media query. Sound and
     Volume span (one is a row, the other is five segments); Motion and Text
     size sit beside each other. Measured after: body 213px, content 187px. */
  @media (max-height:560px) and (min-width:601px){
    /* and the panel itself stops giving a 390px-tall phone 52px of empty scrim
       above a panel that is 28px short. Two-class selector on purpose: the
       margin it overrides is set at `@media (max-height:400px)` LATER in this
       file, and source order would otherwise win. The notch is respected. */
    .menuwrap .menupanel{margin-top:max(14px,calc(env(safe-area-inset-top,0px) + 8px));
      max-height:calc(100vh - 28px)}
    .msettings{display:grid;grid-template-columns:1fr 1fr;column-gap:clamp(8px,1vw,14px);
      align-content:start}
    .msettings>.mrow,
    .msettings>.msetrow[data-set="vol"]{grid-column:1/-1}
    .msettings>.mrow{margin-bottom:clamp(6px,.9vh,10px)}
    .msetrow{margin-bottom:clamp(6px,.9vh,10px)}
    .msetn{margin-top:3px;line-height:1.3}
  }

  .mrow:hover{background:linear-gradient(180deg,rgba(24,58,42,.94) 0,rgba(10,28,20,.96) 100%);
    color:#fff}
  .mrow:focus-visible{outline:3px solid var(--acc);outline-offset:2px}
  .mrow.danger{color:#ffb3a2}
  .mrow.danger:hover{background:linear-gradient(180deg,rgba(58,20,14,.94) 0,rgba(28,9,6,.96) 100%)}
  /* R20 — ARMED. The same signal WIPE THE RECORD already uses, on the control
     that costs a forty-day run rather than a list. One press changes the
     label and lights the row; only the second one destroys anything. */
  .mrow.danger.armed{color:#fff;
    background:linear-gradient(180deg,rgba(96,26,18,.97) 0,rgba(48,12,8,.98) 100%);
    box-shadow:inset 0 0 0 2px rgba(255,120,96,.9)}
  .mwarn{font-family:var(--cond);font-weight:600;text-transform:uppercase;
    letter-spacing:.1em;font-size:clamp(9px,.66vw,12px);color:#ffb3a2;
    padding:0 clamp(11px,1vw,16px) clamp(5px,.6vh,8px);line-height:1.4}
  /* the modal's visible, labelled exit. The panel claimed aria-modal and had
     no close control at all — only the first list row and the scrim, and the
     hamburger that opened it is inert behind the scrim now. */
  .menupanel .mh .mx{margin-left:auto;flex:none;min-width:34px;min-height:34px;
    display:flex;align-items:center;justify-content:center;border:0;cursor:pointer;
    font:700 21px/1 var(--cond,system-ui);color:var(--txt2,#a8bccd);
    background:rgba(150,205,255,.08);box-shadow:inset 0 0 0 1px rgba(150,205,255,.2);
    clip-path:var(--chamfer-s)}
  .menupanel .mh .mx:hover{color:#fff;background:rgba(150,205,255,.18)}
  .menupanel .mh .mx:focus-visible{outline:3px solid var(--acc);outline-offset:2px}

  /* ---- R20b: P1-7 AND P1-8, WHICH HAVE TO MOVE TOGETHER ------------------
     P1-7: every menu row measured 413x36 on both landscape phones. The 48px
     floor this file already sets was written into a `max-width:720px` arm —
     keyed on WIDTH — and a phone in landscape is 952 or 844 wide, so the one
     orientation the contract calls primary never got it. A thumb is a thumb
     at any width; the floor keys on the input device now.
     P1-8 is why nobody raised it before: How to Play already overflowed its
     own panel by 59px at 844x390, its BACK row rendered at y=389 in a 390px
     viewport, and 8 more pixels a row would have made that worse. So the
     panel becomes a column with ONE scroller in the middle of it — the prose
     — and the header and the BACK row are pinned outside it. The panel then
     cannot overflow at all: whatever is left after the two fixed ends is
     what the prose gets, and the exit is always on screen. */
  .menupanel{display:flex;flex-direction:column}
  .menupanel>.mh{flex:none}
  .menupanel>.mbody{flex:1 1 auto;min-height:0;overflow-y:auto;scrollbar-width:thin;
    /* the cue P1-8 says is missing: the last line fades instead of stopping */
    -webkit-mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 20px),transparent 100%);
    mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 20px),transparent 100%)}
  .menupanel>.mbody+.mrow{flex:none;margin-top:clamp(7px,.9vh,11px);margin-bottom:0}
  .menupanel>.mrow{flex:none}
  @media (hover:none),(pointer:coarse){
    .mrow{min-height:44px}
    .menupanel .mh .mx{min-width:44px;min-height:44px}
  }
  .msec{font-family:var(--cond);font-weight:600;text-transform:uppercase;letter-spacing:.24em;
    font-size:clamp(8px,.56vw,11px);color:var(--txt3);margin:clamp(9px,1.1vh,14px) 0 clamp(5px,.6vh,8px)}
  .mbody{font-size:clamp(11px,.8vw,15px);line-height:1.5;color:var(--txt2)}
  .mbody b{color:#e7f3fa}
  .mbody ul{margin:6px 0 0;padding-left:17px}
  .mbody li{margin-bottom:4px}
  .logbar{height:9px;background:rgba(255,255,255,.07);box-shadow:inset 0 0 0 1px rgba(0,0,0,.5);
    margin:5px 0 clamp(8px,1vh,13px)}
  .logbar i{display:block;height:100%;background:linear-gradient(90deg,#16a04e,#43e483)}
  .loglist{display:flex;flex-direction:column;gap:4px}
  .logrow{display:flex;align-items:baseline;gap:9px;padding:5px 10px 6px;
    background:rgba(4,10,17,.7);box-shadow:inset 0 0 0 1px rgba(150,205,255,.11);
    clip-path:var(--chamfer-s);font-size:clamp(10px,.74vw,14px);color:#dceaf4;line-height:1.3}
  .logrow.locked{color:var(--txt4);font-style:italic}
  .logrow .lt{flex:1;min-width:0}
  .logrow .lk{font-family:var(--cond);font-weight:700;text-transform:uppercase;
    letter-spacing:.14em;font-size:clamp(8px,.54vw,11px);color:var(--accd);flex:none}
  .logrow.win .lk{color:#7cf0a8}
  .logrow.lose .lk{color:#ff9179}

  /* the ending illustration, in the flow, for crops too tall to plant it in */
  .rig.endflow{margin:clamp(4px,.8vh,12px) 0}
  .rig.endflow svg{width:min(76vw,320px)}

  /* ---------------- responsive ---------------- */
  /* ==========================================================================
     R10 — MOBILE FIRST

     This file was authored at 1920x1080 and adapted down, and it shows. The
     target is an Android and an iPhone, so the layout DECISIONS below are
     taken at 390x844 first and the wide crop is what adapts. It is a pass,
     not a rewrite: the geometry is still driven by clamp()/vw, which scales
     both ways honestly. What changes is what the phone gets to keep.

     Measured at 390x844 on the road report before this block: the dock was
     429px of an 844px screen — 51% of the phone was HUD — the meter rail
     alone took 165px in three rows of two, the ration segments were 38px
     tall against a 44px minimum, and the outcome text was clipped mid-joke
     with no truck anywhere on screen to explain what it had cost.
     ========================================================================== */

  /* No 300ms tap delay, no blue flash, and no text selection when a thumb
     drags on a button. */
  button,.choice,.leader,.seg,.mrow,.pace,.btn,.item .buy{
    touch-action:manipulation;-webkit-tap-highlight-color:transparent;
    -webkit-user-select:none;user-select:none}

  /* On a touch screen :hover is not a state, it is a stain — it latches onto
     whatever was tapped last and stays there until something else is tapped.
     The ration segments were the real casualty: a tapped TWO EACH kept the lit
     plate that means "this is your standing order", so the dock could show two
     selected orders at once and neither of them was the one in force. Every
     hover declaration is restated at its base value here, and the feedback
     moves to :active, which is the only thing a thumb can actually produce. */
  @media (hover:none){
    .btn:hover{filter:drop-shadow(0 8px 16px rgba(0,0,0,.55))}
    .btn.ghost:hover{--c1:#14351f;--c2:#0a1d13}
    .btn.mute:hover{--c1:#1d2737;--c2:#111a28}
    .pace:hover{filter:drop-shadow(0 8px 16px rgba(0,0,0,.55))}
    .item .buy:hover{filter:none}
    .choice:hover{background:linear-gradient(180deg,rgba(20,34,54,.94) 0,rgba(9,16,28,.96) 100%);
      filter:drop-shadow(0 8px 16px rgba(0,0,0,.5))}
    .choice:hover::before{opacity:.65}
    .leader:hover{transform:none}
    .seg:hover{background:rgba(4,10,18,.6);box-shadow:inset 0 0 0 1px rgba(150,205,255,.13)}
    .seg.on:hover{background:linear-gradient(180deg,rgba(22,64,42,.95),rgba(8,32,21,.95))}
    .mrow:hover{background:linear-gradient(180deg,rgba(20,34,54,.9) 0,rgba(9,16,28,.94) 100%);color:#dceaf4}
    .mrow.danger:hover{background:linear-gradient(180deg,rgba(20,34,54,.9) 0,rgba(9,16,28,.94) 100%);color:#ffb3a2}
    button.sys:hover{background:transparent}
    /* press feedback a thumb can feel, since it can no longer hover for it */
    .choice:active{transform:translateY(2px);filter:brightness(1.22) drop-shadow(0 4px 10px rgba(0,0,0,.55))}
    .pace:active,.btn:active{transform:translateY(2px);filter:brightness(1.18) drop-shadow(0 4px 10px rgba(0,0,0,.5))}
    .seg:active{background:rgba(20,44,66,.9)}
    .mrow:active,.leader:active{filter:brightness(1.2)}
    .item .buy:active{filter:brightness(1.25)}
  }

  @media (max-width:1100px){
    .split{flex-direction:column;gap:12px}
    .split>.r{flex:1 1 auto;min-height:0}
    .split>.l{justify-content:center}
    /* R20b — and THIS is where the plate's soft exit has to be given up.
       `.speak::before` overhangs its box by right:-20%, which on a wide
       screen is composition — the plate fades out into the painting with
       room to spare inside `.field`. Below 1100 `.speak` becomes the full
       width of the field, so that same 181px of overhang lands past the
       field's own edge, where it is invisible AND where it is 158px of real
       horizontal scroll extent (P1-9): a touch swipe sheared the whole text
       block left while the scene plate stayed put. Measured with
       overflow-clip-margin still in force and it does NOT help — Blink keeps
       the clip rect in the scrollable overflow region, so the margin has to
       go to zero here. Nothing visible is lost: at these widths the part
       being clipped was already off the side of the screen. */
    /* 0px, not 0. Unitless zero is rejected by this property in Blink and the
       declaration is dropped silently — measured: the computed value stayed at
       260px and .field kept all 158px of shear. */
    .speak{max-width:100%;overflow-clip-margin:0px}
    /* The ending's type block carries the same species of scrim —
       `.endblock::before` at inset:-7% -9% -11% — and the same consequence:
       46px of horizontal scroll extent on `.field` at 952x427, on the screen
       everybody screenshots. The gradient is masked transparent at its own
       boundary, so clipping it at the box edge costs a few nearly-invisible
       pixels and buys back a text block that does not shear under a thumb. */
    .endblock{overflow:clip;overflow-clip-margin:0px}
    /* R6 — the founder cards keep their copy on a phone.
       The old rule was `.leader .ds{display:none}`, which deleted the funniest
       three sentences on the first interactive screen from every phone in the
       world, and left three cards that all read "name, role, four numbers,
       perk". Grid areas instead of a flex row: the portrait holds a column,
       the description spans underneath it, nothing is thrown away. */
    .leaders{grid-template-columns:1fr;gap:clamp(8px,1.2vh,12px);max-width:660px}
    .leader{display:grid;text-align:left;align-items:start;
      grid-template-columns:auto minmax(0,1fr);
      grid-template-areas:"pf nm" "pf rl" "pf st" "ds ds" "pk pk";
      column-gap:clamp(11px,2.4vw,18px);row-gap:clamp(4px,.7vh,8px);
      padding:clamp(11px,2.4vw,18px)}
    .leader:hover{transform:none}
    .leader .pf{grid-area:pf;width:clamp(62px,17vw,104px);align-self:start}
    .leader .nm{grid-area:nm;font-size:clamp(16px,4.2vw,23px)}
    .leader .rl{grid-area:rl}
    .leader .st{grid-area:st;justify-content:flex-start;gap:clamp(6px,2.2vw,13px);
      align-self:end;margin-top:2px}
    .leader .ds{grid-area:ds;display:block;flex:none;
      font-size:clamp(12px,3.1vw,15px);line-height:1.36;color:var(--txt2)}
    .leader .pk{grid-area:pk;margin-top:0;font-size:clamp(10px,2.7vw,13px)}
    .rail{grid-template-columns:repeat(3,minmax(0,1fr))}
    .report{grid-template-columns:repeat(2,1fr)}
    .choices{grid-template-columns:1fr}
    .choices.three{grid-template-columns:1fr}
    .field{padding:14px 18px}
    /* the pace cells stop being able to hold a name and an aside on one line
       long before the phone breakpoint — the aside drops first, the chips and
       the name never do, because those are the decision */
    .pace .kick{display:none}
    .endbody,.murmur{max-width:88vw}
    .rigline{width:min(64vw,420px)}
    .rigline.end{width:min(56vw,340px)}
  }
  @media (max-width:720px){
    .hdr{padding-left:clamp(52px,15vw,70px);padding-right:12px;
      grid-template-columns:minmax(0,1fr) auto;gap:8px}
    .hdr.bare{grid-template-columns:minmax(0,1fr)}
    .hleft{display:none}
    .hright{grid-column:2;gap:clamp(9px,3vw,14px)}
    .hdr.bare .htitle{align-items:center}
    .htitle{grid-column:1;align-items:flex-start;overflow:hidden}
    /* R6 — the header title. `.pix` is an SVG with a fixed height and an
       aspect-ratio, so a long title does not truncate, it letterboxes: at
       390px "APPROACHING THE BORDER" was scaled to 123px wide inside a 15px
       box and read as a smudge jammed against the day counter. Two fixes,
       both needed: short titles on a narrow crop (see hdrTitle()), and the
       pix box is allowed to use the header's full type height here so a
       short title is set at size instead of shrunk to fit a desktop metric. */
    .pix.hd{height:17px}
    .htitle .pixw{width:100%}
    .htitle .pix.hd{margin:0 auto 0 0}
    .hdr.bare .htitle .pix.hd{margin:0 auto}
    .hsub{letter-spacing:.18em}
    .sys{width:clamp(44px,12vw,58px)}
    /* R20c — P2-1. These two rules were the defect: every landmark name
       display:none below 1920, on three of the four viewports this ships
       to. trackHTML() drops the two NODES the truck is not near instead,
       and the four that are left keep their words, short form, on two
       staggered rows so they cannot collide at 390px. */
    /* the label row costs height in a dock that had none. It is bought back
       with a tighter offset and an 8px face rather than out of the field. */
    .track.compact{height:19px}
    .track.compact.stack{height:34px}
    .track.compact .lb{top:calc(38% + 8px);font-size:8px}
    .track.compact .lb.alt{top:calc(38% + 19px)}
    .track .lb{font-size:9px;letter-spacing:.1em;max-width:33vw;
      overflow:hidden;text-overflow:clip}
    .track .lb.alt{top:calc(38% + 23px)}
    .rail{grid-template-columns:repeat(2,minmax(0,1fr))}
    .rig svg{width:min(72vw,340px)}
    .field{overflow-y:auto}
    /* portrait: the scene's ground line sits higher in a tall crop, and the
       type block needs the room, so the truck shrinks and rides up with it */
    /* portrait: the roster wraps to three rows and eats the middle of the
       frame, so on the trail it moves up under the header and the truck takes
       the band above the dock instead of hiding behind the cards */
    .field.stack{justify-content:flex-start}
    .field.stack .alerts{margin-bottom:0}
    .rigline{width:min(66vw,270px);--gy:67%!important}
    .rigline.end{width:min(58vw,240px);--gy:59%!important}
    /* R7 — the road report's dock carries the drive console now, so its
       ground line comes up to clear it. Measured at 390x844: the dock is 331px
       with the console alone and 388px when the pace lane's strain warning
       wraps to three lines, so the line is set to clear the WORST case (top
       456) rather than the common one — 53% is 447px, and the tyres are never
       behind the meter rail. */
    .rigline.road{width:min(62vw,250px);--gy:53%!important}
    /* R10 — the set-piece truck (the river, the border) is stood down on a
       phone. Its ground line is 56-58% on a wide crop, which the `!important`
       above would drag to 67% — entirely behind a 490px dock — so it was
       rendering a 30KB SVG nobody could see. The crossing's own text fills the
       field at this width and there is no band left to park it in. */
    .rigline.far{display:none}
    .groundhaze{--gy:67%!important;height:11vh;bottom:calc(100% - 67% - 4vh)}
    .rigline.end~.groundhaze{--gy:59%!important;bottom:calc(100% - 59% - 4vh)}
    .rigline.road~.groundhaze{--gy:53%!important;bottom:calc(100% - 53% - 4vh)}
    .field.end{gap:10px}
    .endblock{gap:9px}
    .endbody,.murmur{max-width:100%}
    .murmur{text-align:left}
    #mgcanvas{width:88vw}
    /* R6 — the ending illustration. Measured at 390x844 the planted truck had
       exactly 80px of visible band between the murmur plate and the top of a
       362px dock: a sliver of a wheel arch on the game's one shareable screen.
       On a narrow crop renderEnding() stops planting it in the stage and hands
       it to the field instead (see rig.endflow), so it gets a real box and the
       whole thing scrolls. */
    .field.end .rig.endflow svg{width:min(66vw,250px)}
    .field.end .partybar{margin-top:clamp(4px,.8vh,10px)}
    .field.end{padding-bottom:16px}
    /* the road report on a phone: text first, then the roster, and the field
       scrolls rather than stacking on top of the truck */
    .field.road{padding-top:12px;gap:10px}
    .field.road>.partybar{margin-top:clamp(6px,1vh,12px)}
    .milepost{max-width:100%}
    /* R7 on a phone. 362px of usable dock, and the console has to fit the
       rations policy AND the store door on one line or it costs a row.
       Measured at 390: caption collapses to the cup, each segment gets ~66px
       (enough for ONE EACH at 9px and a dollar figure under it), and
       RUMMAGE THE TRUCK holds one line at 10px instead of wrapping to two. */
    .console{gap:7px}
    .console .pacecon{flex:1 1 auto;align-items:stretch}
    .console .pacecon>.rations{flex:1 1 auto}
    .rations{flex:1 1 auto;gap:7px;padding:4px 6px}
    .rations .rcap{letter-spacing:0}
    .rations .rcap .rw{display:inline}
    .segs{flex:1 1 auto;grid-auto-columns:minmax(0,1fr);gap:4px}
    .seg{min-width:0;padding:4px 3px}
    .seg .sn{font-size:9px;letter-spacing:.04em}
    /* R7, INTEGRATOR — `.sx` used to be display:none here, which dropped the
       grievance chip on a phone. That was defensible while the dial's only
       real axis was cash; it is not now. The ration retune moved the live
       trade onto grievance AND support, so hiding `.sx` left a phone player
       reading THE URN as "-3 morale" — the two numbers that actually decide
       the run, invisible, on the platform with the least room to guess. They
       are kept and the row is paid for instead: the chips wrap to a second
       line inside the segment, the caption cup goes, and the plate takes the
       full width it already had. Measured at 390x844 the dock still does not
       scroll, fresh or wrecked. */
    .seg .scst{font-size:9px;gap:3px;flex-wrap:wrap;row-gap:1px}
    .seg .scst svg{height:8px;width:8px;margin-right:1px}
    .rations .rcap{display:none}
    .console .rummage{font-size:10px;letter-spacing:.06em;padding:9px 9px;margin-left:0}
    .drive{grid-template-columns:1.08fr 1fr 1.08fr;gap:6px}
    .pace{padding:8px 5px;gap:4px}
    .pace .pn{font-size:11px;letter-spacing:.06em}
    .pchip{font-size:9px;padding:2px 4px 3px;gap:3px;letter-spacing:.02em}
        /* R20c — P2-2. THE LAYOUT DROPPED LABELS RATHER THAN REFLOWING.
       The word went and the icon stayed, so on the owner's phone the dock
       was three unlabelled segments and six anonymous icon-and-number
       chips — +2 +3 +1 -1 +1 -3% — whose meanings survived only in an
       aria-label and a title= tooltip, neither of which exists on a
       touch device. DEFECTS: fewer chips WITH words beats six chips
       without. The icon is what goes now: it is the redundant half once
       the word is there, and it is the cheap half in pixels. */
    .drive .pchip .pw{display:inline;letter-spacing:.02em}
    .drive .pchip>span:first-child{display:none}
    /* five chips WITH their words have to hold one row inside a 270px
       button, or the pace row grows 10px and the low-fuel warning goes off
       the top of the field. Measured: 290px of chips at the old metrics,
       252 at these. The icon is what pays for the word. */
    .drive .pchip{gap:2px;padding:1px 4px 2px;font-size:8px;letter-spacing:0}
    /* Six chips WITH their words are 295px of content in a 249px button, so
       the price row is two lines on a phone and that is the honest shape of
       it — DEFECTS asks for the words, not for one line. The 10px it costs
       comes out of the button's own padding, which had 14 top and bottom
       for a control that is already 54px tall against a 44px floor. */
    .drive .pace{padding-top:5px;padding-bottom:5px}
    .drive .pchips{row-gap:1px}
    .countwrap{width:100%;padding:12px 12px 15px;gap:7px}
    .menupanel{margin-top:clamp(66px,8.4vh,88px)}

    /* ---- R10, the phone pass ---- */

    /* 1. THE RAIL. Two columns of three rows was 165px of an 844px screen for
       six numbers. Three of two is 110px and gives the field 55px back, which
       is a line and a half of the outcome text — the thing the player is
       actually here for. Below 430 the label goes and the coloured icon
       carries it, because at 117px a cell can hold ONE of "GRIEVANCE" and
       "78" and the number is not the one to drop. The label is still there
       for a screen reader. */
    .rail{grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}
    /* R21b — the 11px floor against a 119px cell. Three declarations buy the
       label 6px (padding 8->6, row gap 5->3, tracking .04em->.01em) and
       GRIEVANCE goes from needing 62 in a 59 box to needing 59 in a 65 one.
       The cash cell cannot be bought that way — its value is unbounded — so it
       gets the third and shortest authored form of its own label instead. */
    .mtr{padding:5px 6px 6px}
    .mtr .nm{letter-spacing:.01em}
    .mtr .nm .lgw,.mtr .nm .lgs{display:none}
    .mtr .nm .lgt{display:inline}
    .mtr .r1{margin-bottom:4px;gap:3px}
    .mtr .vl{font-size:16px}
    .mtr.cash .vl{font-size:16px}
    .mbar,.mcash{height:8px}

    /* 2. TOUCH TARGETS. 44px is the floor on both platforms; the things you
       press most get more. Measured before: .seg 38px, .rummage 38px,
       .item .buy 34px — all under it. */
    .choice{min-height:56px}
    .pace{min-height:60px}
    .btn{min-height:50px}
    .seg{min-height:46px;padding:6px 4px}
    .console .rummage{min-height:46px}
    .item .buy{min-height:44px;min-width:66px}
    .mrow{min-height:48px}
    .sys{min-height:48px}
    /* Fitts on the two ends of the trail: the store list and the menu are the
       only places a phone player has to aim, so they get the room. */
    .item{padding-top:7px;padding-bottom:7px}

    /* 3. THE OUTCOME OWNS THE SCREEN. Once it is open the setup is context,
       so it clamps to three lines instead of pushing the payoff off the
       bottom — which is what was happening: the joke was cut mid-sentence on
       every event in the game. */
    .speak .says.resolved>.lead{display:-webkit-box;-webkit-line-clamp:3;
      -webkit-box-orient:vertical;overflow:hidden;font-size:13px}
    .speak .says.resolved>.ttl{font-size:16px}
    .speak .says.resolved .outcome .lead{font-size:17px;line-height:1.36}
    .field.road,.field.scroll,.field.end{overscroll-behavior:contain;
      -webkit-overflow-scrolling:touch}

    /* 4. THE TRUCK, on the one crop that never got to see it. See
       outcomeRig() — on a narrow screen the rig rides inside the outcome
       rather than behind it, so the panel it just shed is on screen next to
       the number that charged for it. */
    .rig.outflow{margin:2px 0 0;justify-content:center}
    .rig.outflow svg{width:min(62vw,240px)}

    /* 5. THE ENDING. The scorecard scrolls with the obituary; the dock keeps
       only what a thumb presses. */
    .endrec{width:100%;display:flex;flex-direction:column;
      gap:clamp(5px,.7vh,9px);margin-top:clamp(6px,1vh,12px)}
    .field.end .partybar{margin-top:clamp(4px,.8vh,10px);margin-bottom:2px}
  }

  /* R20b — THE RATION SEGMENTS, CRUSHED TO 10px BY A BUTTON THAT ARRIVED
     LATER. `.console` is one flex row: rations, then POUR A CAN NOW (added in
     r20 for P0-11), then RUMMAGE THE TRUCK. At 390 there is not room for all
     three, `.seg` had its min-width released to 0 for exactly this crop, and
     the three ration buttons collapsed to 10x71 — a control you cannot hit and
     cannot read, on the row that sets your standing policy. The row wraps
     instead: the rations take a line of their own and the two doors share the
     next one. Three segments across 340px is 106px each. */
  @media (max-width:560px){
    .console{flex-wrap:wrap}
    .console .pacecon{flex:1 1 100%;align-items:stretch}
    .console .pacecon>.rations{flex:1 1 auto;width:100%}
    .console .segs{flex:1 1 auto;grid-auto-columns:minmax(0,1fr)}
    .console .btn.use{flex:1 1 auto;margin-left:0}
    .console .rummage{flex:1 1 auto;margin-left:0}
  }

  /* the 390-wide band the whole thing is aimed at */
  @media (max-width:430px){
    /* 117px of cell. The label survives, at 7px with the tracking pulled in,
       because a rail of six coloured pixel icons and no words is a puzzle the
       first time you see it — and it is the first thing a new player looks at. */
    .mtr .nm{font-size:7px;letter-spacing:.04em}
    .mtr .vl{margin-left:auto;font-size:15px}
    .mtr.cash .vl{font-size:15px}
    .mtr .dlt{font-size:12px}
    .hsub{font-size:9px;letter-spacing:.1em}
  }
  @media (max-height:620px){
    .rig svg{width:clamp(180px,22vw,320px)}
    .rigline{width:clamp(190px,24vw,340px)}
    .field{padding-top:10px;padding-bottom:10px}
  }

/* ══════════════════════════════════════════════════════════════════════════
   R11 — LANDSCAPE IS THE PRIMARY ORIENTATION

   R10 made portrait primary. That was the wrong target: the reference game is
   landscape on a phone too, and every scene in this game is authored at
   1920x1080, where the horizontal parallax IS the depth. A portrait crop
   throws away the part of the picture that does the work.

   What was actually shipping, measured at 844x390 (an iPhone 14 on its side)
   before this block existed:

     screen          header   field   dock          open scene band
     trail             58px    16px   334px  86%      16px   4%
     road + outcome    58px    22px   334px  86%      22px   6%
     event             58px    26px   380px  97%      26px   7%
     crossing          58px    26px   472px 121%      26px   7%

   A 472px dock on a 390px screen. The event screen had NO TITLE AND NO SETUP
   TEXT on it at all — you were asked to choose with the question off-screen.
   That is not a landscape layout with problems, it is a portrait layout being
   shown sideways.

   The cause is that every adaptation in this file keys on WIDTH. 844 is not
   narrow, so none of the phone rules fire — but 390 tall is very short, and
   the only height rule in the file set three properties. The two costly
   width rules land exactly backwards on a landscape phone:

     @media (max-width:1100px){ .choices{grid-template-columns:1fr} }   -> +140px
     @media (max-width:1100px){ .split{flex-direction:column} }         -> text offscreen

   So this block is keyed on HEIGHT, and its job is to put back the wide
   composition that a wide screen is entitled to and then make every row of
   chrome shorter, so the picture gets the middle of the frame back.

   Budget it holds to at 844x390: header 44 · dock ~186 · SCENE BAND ~160
   (41% of the screen, up from 4%).

   `min-width:600px` keeps it off a short PORTRAIT window, where stacking is
   still right. Touch targets do not move: 44px is the floor in here too, and
   the rows that shrink are the ones nobody presses.
   ══════════════════════════════════════════════════════════════════════════ */
  @media (min-width:600px) and (max-height:620px){

    /* ---- 1. undo the width rules that assume a tall screen ---------------
       These are the whole ballgame. A landscape phone is WIDE; it should use
       columns, which cost width (of which there is plenty) instead of height
       (of which there is none). */
    .choices{grid-template-columns:repeat(2,1fr)}
    .choices.three{grid-template-columns:repeat(3,1fr)}
    .choices.one{grid-template-columns:1fr}
    .split{flex-direction:row;gap:clamp(12px,1.8vw,28px)}
    .split>.r{flex:0 0 clamp(240px,34vw,420px)}
    .split>.l{justify-content:flex-start}
    .rail{grid-template-columns:repeat(5,minmax(0,1fr)) minmax(0,1.3fr)}
    .report{grid-template-columns:repeat(4,1fr)}
    /* ---- R20b: REFERENDUM DAY, ON 427px OF HEIGHT ------------------------
       The count panel is centred in a field it did not fit, so it overflowed
       BOTH ways and the top went first: measured `.countwrap cut T42px`, and
       the line that was cut is the question — "Yes — leave Confederation" —
       on the screen that is the climax of a forty-day run. Nothing here is
       dropped; the number and its plate come down to what the field has. */
    .countwrap{gap:clamp(2px,.5vh,7px);padding:7px 13px 9px}
    .countnum{font-size:clamp(30px,4.2vw,62px);line-height:.88}
    .countbar{height:clamp(9px,1.2vh,14px)}
    .sealnum{font-size:clamp(26px,3.6vw,54px);line-height:.9}
    .callline{font-size:clamp(9px,.66vw,12px)}
    /* `safe center` falls back to flex-start the moment the content is taller
       than the box. Plain `center` splits the overflow across BOTH ends, and
       the end it took from here was the top — which is where the question
       lives, so `Yes — leave Confederation` was the line that got cut. */
    .field.mid{justify-content:safe center}
    /* P1-17 — the FIRST screen of the game had a sliced line at 844x390:
       `p.flavor` cut 3.5px through its descenders. The hero lockup and the
       truck are the two things with slack. */
    .field.mid .rig.title svg{width:clamp(170px,20vw,300px)}
    .field.mid .lead{font-size:clamp(12px,1vw,16px);line-height:1.34}
    .field.mid .herolock{margin:2px 0}
    /* three pixels, and they were the pause menu overflowing its own panel */
    .mrow{margin-bottom:clamp(3px,.5vh,6px)}
    /* ---- R20b: THE COMPACT ENDING (P1-3, P1-4) ----------------------------
       renderEnding() now moves the roster and both score blocks into the
       field on this shape, so the dock is three buttons. These are the rules
       that make the block above them fit the field that is left: measured at
       952x427 the endblock overflowed by 87px and the party strip was painted
       across the epitaph. */
    .endrec{width:100%;display:flex;flex-direction:column;gap:5px;margin-top:7px}
    .field.end{padding-bottom:10px}
    .field.end .endline{font-size:clamp(20px,3.3vw,40px)}
    .field.end .endbody{font-size:clamp(11px,.95vw,14px);line-height:1.36;max-width:none}
    .field.end .murmur{max-width:none;padding:7px 11px 8px 15px}
    .field.end .murmur .ml{font-size:clamp(12px,1.1vw,17px);line-height:1.3}
    .field.end .murmur .mr{font-size:clamp(10px,.8vw,13px);line-height:1.35;margin-top:3px}
    .field.end .endkick{padding:3px 10px 4px}
    .field.end .partybar{margin-top:7px}
    .field.end .report .rc{padding:5px 9px 6px}
    .field.end .report .k{font-size:7px;letter-spacing:.14em;margin-bottom:2px}
    .field.end .report .v{font-size:clamp(15px,1.5vw,22px)}
    .leaders{grid-template-columns:repeat(3,1fr);max-width:none;gap:clamp(7px,1vw,14px)}
    .leader{display:flex;flex-direction:column;text-align:center;gap:5px;padding:8px 9px}
    .leader .pf{width:clamp(46px,7vw,72px);align-self:center}
    .leader .nm{font-size:clamp(12px,1.3vw,17px)}
    .leader .st{justify-content:center;gap:clamp(5px,.9vw,10px)}
    .leader .ds{font-size:clamp(9px,1vw,12px);line-height:1.3;
      display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
    .leader .pk{font-size:clamp(8px,.9vw,11px)}
    .pace .kick{display:none}

    /* ---- 2. the header gives the scene 14px back --------------------------
       44px flat, which is also the touch floor, so the menu button is still a
       legal target at exactly the height of the bar it lives in. */
    .hdr{min-height:44px;padding:4px clamp(12px,1.8vw,26px) 5px clamp(58px,7vw,74px);
      gap:clamp(8px,1.4vw,20px)}
    .hdr.bare{min-height:40px}
    .sys{width:56px;min-height:44px}
    .pix.hd{height:14px}
    .hsub{font-size:8px;letter-spacing:.16em}
    .stat .sv{font-size:15px}
    .stat .sl{font-size:7px;letter-spacing:.14em}

    /* ---- 3. the field: the picture's band, and a scroll valve -------------
       Short padding, and the field may scroll rather than push the dock off
       the bottom — the failure mode this replaces was the dock growing past
       the viewport and the choices going with it. */
    .field{padding:8px clamp(16px,2.4vw,40px);gap:7px;overflow-y:auto;scrollbar-width:none}
    .field::-webkit-scrollbar{width:0;height:0}
    .field.top{padding-top:9px}
    .field.stack{justify-content:flex-end;padding-bottom:5px;gap:6px}
    .alerts{gap:5px}

    /* ---- 4. the dock: five rows, all of them shorter ---------------------- */
    /* R20c — the dock pays for the two things this round put into it: the
       landmark names on the rail (8px) and the words on the pace chips
       (10px). Four gaps at 3px instead of 5, and 3px off the padding, is
       exactly 10 — taken from the space between rows rather than from the
       field, which on this crop is the picture. */
    .dock{padding:5px clamp(14px,2vw,34px) 6px;gap:3px}
    .dockrow{gap:6px}

    /* the trail track keeps its pips and its position marker and loses the
       landmark captions, which are the tall part and are named again in the
       header subtitle two rows above */
    .track{height:14px;margin:0 6px}
    /* R20c — P2-1. These two rules were the defect: every landmark name
       display:none below 1920, on three of the four viewports this ships
       to. trackHTML() drops the two NODES the truck is not near instead,
       and the four that are left keep their words, short form, on two
       staggered rows so they cannot collide at 390px. */
    /* the label row costs height in a dock that had none. It is bought back
       with a tighter offset and an 8px face rather than out of the field. */
    .track.compact{height:19px}
    .track.compact.stack{height:34px}
    .track.compact .lb{top:calc(38% + 8px);font-size:8px}
    .track.compact .lb.alt{top:calc(38% + 19px)}
    .track .lb{font-size:9px;letter-spacing:.1em;max-width:33vw;
      overflow:hidden;text-overflow:clip}
    .track .lb.alt{top:calc(38% + 23px)}

    /* the meter rail, one line per meter: icon, name, number, bar. 30px. */
    .rail{gap:5px}
    .mtr{padding:3px 7px 4px}
    .mtr .r1{margin-bottom:3px;gap:4px}
    .mtr .nm{font-size:7px;letter-spacing:.05em}
    .mtr .vl{font-size:14px}
    .mtr.cash .vl{font-size:14px}
    .mtr .dlt{font-size:11px;bottom:3px;right:7px}
    .mbar,.mcash{height:6px}

    /* what is in the box, on one line */
    .loadbar{gap:4px;padding:2px 6px}
    .lchip{padding:2px 6px 3px}
    .lchip .lw{display:inline}
    /* R20c — P2-2. The words come back; the row has to stay one row, or
       the strip costs 21px of a dock that is already 55% of this crop.
       Tighter tracking and no caption word buys it back exactly. */
    .loadbar{flex-wrap:nowrap;gap:5px}
    .loadbar .lcap .lw{display:none}
    .lchip{padding:2px 5px 3px;gap:3px}
    .lchip .lw{letter-spacing:.02em}

    /* rations + the store door */
    .console{gap:6px}
    .rations{gap:6px;padding:3px 6px}
    .rations .rcap{display:flex}
    .segs{gap:4px}
    .seg{min-height:44px;padding:4px 5px}
    .seg .sn{font-size:9px;letter-spacing:.03em}
    .seg .scst{font-size:8px;gap:3px}
    .seg .scst svg{height:8px;width:8px}
    .console .rummage{min-height:44px;font-size:10px;letter-spacing:.06em;padding:6px 10px}

    /* the three travel speeds, side by side, still 44px of thumb */
    .drive{gap:6px}
    .pace{min-height:44px;padding:5px 6px;gap:3px}
    .pace .pn{font-size:11px;letter-spacing:.05em}
    .pchip{font-size:8px;padding:1px 4px 2px;gap:2px}
        /* R20c — P2-2. THE LAYOUT DROPPED LABELS RATHER THAN REFLOWING.
       The word went and the icon stayed, so on the owner's phone the dock
       was three unlabelled segments and six anonymous icon-and-number
       chips — +2 +3 +1 -1 +1 -3% — whose meanings survived only in an
       aria-label and a title= tooltip, neither of which exists on a
       touch device. DEFECTS: fewer chips WITH words beats six chips
       without. The icon is what goes now: it is the redundant half once
       the word is there, and it is the cheap half in pixels. */
    .drive .pchip .pw{display:inline;letter-spacing:.02em}
    .drive .pchip>span:first-child{display:none}
    /* five chips WITH their words have to hold one row inside a 270px
       button, or the pace row grows 10px and the low-fuel warning goes off
       the top of the field. Measured: 290px of chips at the old metrics,
       252 at these. The icon is what pays for the word. */
    .drive .pchip{gap:2px;padding:1px 4px 2px;font-size:8px;letter-spacing:0}
    /* Six chips WITH their words are 295px of content in a 249px button, so
       the price row is two lines on a phone and that is the honest shape of
       it — DEFECTS asks for the words, not for one line. The 10px it costs
       comes out of the button's own padding, which had 14 top and bottom
       for a control that is already 54px tall against a 44px floor. */
    .drive .pace{padding-top:5px;padding-bottom:5px}
    .drive .pchips{row-gap:1px}

    /* choices: two or three across, 44px floor, one line of chips */
    .choice{min-height:44px;font-size:12px;line-height:1.3;gap:3px;
      padding:6px 10px 7px 14px}
    .choice b{font-size:11px;letter-spacing:.07em}
    .btn{min-height:44px;font-size:12px;padding:9px 16px}
    .btn.big{min-height:46px;font-size:14px;padding:10px 20px}
    .dockhint{font-size:8px;line-height:1.5}

    /* ---- 5. the truck stands on the band, not behind the dock ------------
       Dock top lands near 50% of the screen and the roster strip sits on the
       last 44px of the field above it, so the contact line goes to 44% — the
       roster crosses the tyres the way it does at 1920, instead of covering a
       third of the rig. */
    .rigline{width:min(30vw,250px);--gy:44%!important}
    .rigline.road{width:min(28vw,230px);--gy:44%!important}
    .rigline.end{width:min(26vw,210px);--gy:42%!important}
    .rigline.far{display:none}
    .groundhaze{--gy:44%!important;height:9vh;bottom:calc(100% - 44% - 3vh)}
    .rigline.end~.groundhaze{--gy:42%!important;bottom:calc(100% - 42% - 3vh)}
    .rig svg{width:min(30vw,250px)}

    /* the roster keeps a reserved band here for the same reason it does in
       portrait — see the PORTRAIT block below for the measurement */
    .field.road{overflow:hidden;padding-bottom:6px}
    .field.road>.speak{flex:1 1 auto;min-height:0;overflow-y:auto;
      scrollbar-width:none;overscroll-behavior:contain}
    .field.road>.speak::-webkit-scrollbar{width:0;height:0}
    .field.road>.partybar{flex:0 0 auto;margin-top:5px}

    /* ---- 6. the dialogue plate: wide, not tall ---------------------------
       `.speak` was capped at 60vw so it would not run the width of a cinema
       screen. On a 844px screen that cap is 506px and the text had nowhere to
       go but down, into a field that was 26px tall. Off the leash here. */
    .speak{max-width:none;gap:clamp(9px,1.2vw,20px)}
    .speak .lead{font-size:clamp(12px,1.5vw,16px);line-height:1.36}
    .speak .ttl{font-size:clamp(17px,2.4vw,26px)}
    .speak .says{gap:5px}
    .who{gap:4px}
    /* THE ROAD REPORT'S PLATE. Measured at 844x390 before this: the plate
       wanted 114px inside a 79px box, so the second line of the joke — the
       payoff, the reason the screen exists — was sliced through the middle of
       the glyphs. Everything around the words gives and the words do not: the
       kicker tag, the title, the effect chips and the four gaps come down, and
       the speaker's bust drops to a size that fits the band beside them.
       Result: 83px of plate in an 88px box, so the common outcome lands whole
       and only a genuinely long one scrolls. */
    .field.road .speak{gap:9px}
    .field.road .speak .says{gap:3px}
    /* The kicker goes, and it is the right 16px to spend. It labels a screen
       that has a headline, a resolved outcome and a row of deltas on it — the
       one element here that tells you nothing the rest of the plate is not
       already shouting. Keeping it cost the delta chips their bottom half,
       which read as a rendering fault rather than as "scroll for more". It is
       still on every taller crop. */
    .field.road .speak>.says>.tag{display:none}
    .field.road .speak .ttl{font-size:15px}
    .field.road .speak .lead{font-size:12px;line-height:1.36}
    /* The road report's speaker column is inside `.speak`, and `.speak` is
       `flex:1 1 auto; min-height:0` — so on a 390px-tall phone with the
       warning band running it gets crushed to 67px, and a fixed 58px bust
       plus a 13px plate is 74. The plate was cut 7px through its descenders.
       The BUST gives and the NAME does not: the portrait is a flex item that
       may shrink, and because its width follows its height through
       aspect-ratio it scales down uniformly instead of squashing. The plate
       is flex:none, so it is the last thing in the column to lose a pixel. */
    .field.road .who{--pfw:58px;gap:3px;max-height:100%;min-height:0;min-width:0}
    .field.road .pfx{flex:0 1 auto;width:auto;height:var(--pfw);min-height:0}
    .field.road .plate{flex:none}
    /* ...and the floor. On a 390px-tall phone with all five warnings running,
       `.speak` is crushed to 15px — measured — and a 13px plate plus a 3px gap
       is 16. The bust is already gone by then, so the last two pixels come out
       of the plate's own padding rather than out of the name. Everything above
       this height keeps the roomier plate. */
    @media (max-height:400px){
      .field.road .who{gap:2px}
      .field.road .plate{padding:1px 5px 1px}
    }
    .field.road .plate{padding:2px 5px 3px;font-size:7px;letter-spacing:.04em}
    .fxrow{gap:4px}
    .field.road .fxrow .chip{padding:2px 6px 3px;font-size:9px}
    .milepost{max-width:none}
    .milepost p{font-size:12px;line-height:1.35}
    .endbody,.murmur{max-width:none;font-size:12px;line-height:1.4}
    .field.end{gap:6px}
    .endblock{gap:5px}
    .countwrap{padding:9px 12px 11px;gap:6px}
    .menupanel{margin-top:52px}
    #mgcanvas{width:min(56vw,620px)}

    /* ---- 7. the roster, on one line ---------------------------------------
       Five cards wrapped to three rows is 157px, which is 40% of a landscape
       phone. One row of five is 56px and shows every face. */
    .partybar .ptyx{flex-wrap:nowrap;gap:4px}
    .partybar .ptyx>.pty-c{flex:1 1 0;max-width:none;gap:5px;padding:3px 6px 4px 4px}
    .partybar .ptyx>.pty-tile{width:44px;margin-right:5px;padding:3px 2px}
    .partybar .pty-face{width:30px;height:30px}
    .partybar .pty-nm{font-size:10px}
    .partybar .pty-rl{display:none}
    .partybar .pty-bar{height:5px}
    .partybar .pty-lbl{margin-top:2px}
    .partybar .pty-lbl span{font-size:6px;letter-spacing:.1em}
    .partybar .pty-lbl b{font-size:9px}
    .partybar .pty-line{font-size:10px;padding:4px 9px;max-width:none}
    .partybar .pty-epi{font-size:10px}
  }

  /* A 390-tall phone on its side is the tightest real target. Everything above
     still applies; these are the last few pixels. */
  @media (min-width:600px) and (max-height:430px){
    .track{height:11px}
    /* the label stays. At 844 wide a rail cell is 130px, which holds
       "GRIEVANCE" and "78" together with room over — this is the opposite of
       the 390-wide portrait case, where the cell is 117px and one of them has
       to go. A rail of six coloured icons and no words is a puzzle. */
    .mtr .nm{font-size:7px;letter-spacing:.04em}
    .mtr .vl{margin-left:auto}
    /* The last nine pixels, taken out of chrome and given to the road
       report's plate — which was 88px of outcome inside an 88px box with the
       9px of gap between the two still to pay for. Nothing here is a target
       and nothing here is a word: bar heights, chip padding, dock padding. */
    .loadbar{padding:1px 4px}
    .lchip{padding:1px 5px 2px}
    .mtr .vl,.mtr.cash .vl{font-size:13px}
    .mbar,.mcash{height:5px}
    .field{padding-top:6px;padding-bottom:6px;gap:5px}
    .dock{gap:4px;padding-top:5px;padding-bottom:6px}
    .speak .lead{font-size:12px}
    .rigline,.rigline.road{width:min(26vw,215px)}

    /* the ending's dock carries a scorecard, a breakdown and three buttons.
       Compressed here so the obituary — the thing anybody screenshots — gets
       more than two lines of the field. */
    .report{gap:5px}
    .field.end .endline{font-size:clamp(18px,3vw,32px)}
    .field.end .endbody{font-size:11px;line-height:1.32}
    .field.end .murmur .ml{font-size:12px;line-height:1.28}
    .report .rc{padding:4px 8px 5px}
    .report .k{font-size:7px;letter-spacing:.12em}
    .report .v{font-size:15px}
    .endrec{gap:4px}

    /* the founder screen is the densest in the game: a bust, six backstories
       and six supply steppers. Three backstory columns instead of two puts
       all six on screen at once, which is the whole decision. */
    .field.top{padding-top:6px}
  }

/* ══════════════════════════════════════════════════════════════════════════
   R11 — PORTRAIT IS THE ADAPTATION, AND THE ROSTER SURVIVES IT

   Two defects R10 left, both measured at 390x844:

   1. THE ROSTER GETS BURIED. On the road report, once an outcome resolves,
      R10's inline truck (`.rig.outflow`, 240px wide) opens above the party
      strip and pushes it under the dock. Measured: roster 157px, 124px of it
      covered, 33px visible — one row sliced in half. On the ending card,
      88px covered of 157.

      Two things were wrong and both are fixed here. The roster was in the
      SCROLLING part of the screen with no way to know it was there, and it
      was three wrapped rows tall in the one place that had no room for it.
      So: the text scrolls inside its own box and the roster keeps a reserved
      band at the bottom of the field, and on a narrow crop the strip goes to
      one row of five — which shows MORE of the party than the buried version
      did, not less.

   2. THE DROP BUTTONS ARE 18px. Fixed below, at every width, because it is
      the only destructive control in the game.
   ══════════════════════════════════════════════════════════════════════════ */
  @media (max-width:720px){
    /* The field stops being one long scrolling column and becomes two parts:
       a scroller that holds the words, and a reserved band that holds the
       people. `min-height:0` is what lets the scroller actually shrink —
       without it a flex item refuses to go below its content height and the
       roster gets pushed out again exactly as before. */
    .field.road{overflow:hidden;padding-bottom:8px}
    .field.road>.speak{flex:1 1 auto;min-height:0;overflow-y:auto;
      scrollbar-width:none;overscroll-behavior:contain}
    .field.road>.speak::-webkit-scrollbar{width:0;height:0}
    .field.road>.partybar{flex:0 0 auto;margin-top:6px}

    /* One row of five. The face stays big enough to recognise, the name and
       the number stay, the role goes — it is on the card's title attribute
       and it is the only line here that is not load-bearing. */
    /* R20b — P1-16. This rule was written as `.field.road` and `.field.end`,
       and the trail's field on a phone held UPRIGHT is `.field.stack` — so on
       the one crop the rule was aimed at, it never matched. What the player
       got instead was the party lane's own `.pty-c{flex:1 1 44%}`: five cards
       laid out 2 + 2 + 1 with the orphan full-width, so The Lawyer's stat bar
       was twice as long as everyone else's for the same kind of value and the
       same number read as a different quantity depending on which row you
       landed in. It also cost about 130px of field, which is what pushed the
       fifth card under the fold whenever an ask was live.
       Scoped to `.partybar` instead of to three field classes that have to be
       kept in step by hand — one grid, five equal columns, everywhere the
       strip is narrow. */
    .partybar .ptyx{flex-wrap:nowrap;gap:4px}
    .partybar .ptyx>.pty-c{flex:1 1 0;max-width:none;
      flex-direction:column;align-items:stretch;gap:4px;padding:5px 5px 6px}
    .partybar .ptyx>.pty-tile{display:none}
    .partybar .pty-face{width:100%;height:auto;aspect-ratio:1/1}
    .partybar .pty-nm{font-size:9px;letter-spacing:.01em}
    .partybar .pty-rl{display:none}
    .partybar .pty-bar{height:5px;margin-top:3px}
    .partybar .pty-lbl{margin-top:2px}
    .field.road .partybar .pty-lbl span,
    .field.end .partybar .pty-lbl span{display:none}
    .field.road .partybar .pty-lbl b,
    .field.end .partybar .pty-lbl b{font-size:10px;margin:0 auto}
    .field.road .partybar .pty-gone,
    .field.end .partybar .pty-gone{margin-top:2px;justify-content:center}
    .field.road .partybar .pty-goneword,
    .field.end .partybar .pty-goneword{font-size:6px;letter-spacing:.08em;text-align:center}
    .field.road .partybar .pty-stone,
    .field.end .partybar .pty-stone{width:16px}

    /* the inline truck is the gag R10 bought this with; it keeps its place,
       at the size a plate that also owes the roster 100px can afford */
    .rig.outflow svg{width:min(46vw,180px)}

    /* THE ENDING CARD. Different shape of the same defect, and it does NOT
       take the same fix: measured at 390x844 the ending's field is 610px of
       visible band and its four children want 1048 — obituary 384, truck 125,
       roster 103, scorecard 406. Reserving a band for the roster here would
       mean shrinking the obituary, and a first attempt at exactly that
       collapsed it to zero height. The obituary is the payoff; it is not the
       thing that gives.

       So the card stays one scroller and the fixed furniture above the roster
       comes down instead: the truck goes 250 -> 128px wide (125px tall to
       64px), the three gaps and the bottom padding each give a couple, and the
       roster is already 54px lighter than it was. The obituary keeps its
       natural height for every ending currently in the deck — 46vh is 388px
       and the longest measures 393 — so the bound only engages if a future
       ending runs longer than any of them, which is the case it exists for.
       Swept over all 28 endings at 1920x1080, 844x390 and 390x844
       (_gauntlet/.roster-sweep-r11.js): nothing covers the roster and nothing
       lands past the dock, at any of them. The scorecard sits below the fold
       and that is correct — it is a receipt, and the memorial is not. */
    .field.end{gap:8px;padding-bottom:8px}
    /* R20b — THE SECOND SCROLLER IS GONE. This was `max-height:46vh;
       overflow-y:auto`, written in r11 when the block held a kicker, a
       headline, the obituary and the murmur and the longest of the 28
       measured 393px against a 388px bound — so it never engaged, and the
       note above says so. It engages now: the compact ending moves the
       four-tile score card INTO this block, which puts every ending over the
       bound, and the result was the epitaph sliced through its x-height with
       the truck drawn across the part that overflowed — measured, the block
       reported scrollHeight 509 / clientHeight 388 and `.murmur` hit-tested
       to an SVG path. DEFECTS asks for exactly one thing here: "one scroller,
       not two". `.field` is the scroller; this is content. */
    .field.end>.endblock{max-height:none;overflow:clip;overflow-clip-margin:0px;
      /* 93vw was 9px wider than the field's content box at 390, which is the
         last 4px of `.field x+` on this crop */
      width:min(1500px,100%)}
    .field.end .rig.endflow svg{width:min(34vw,128px)}
    .field.end>.partybar{margin-top:6px;margin-bottom:0}
  }

/* ══════════════════════════════════════════════════════════════════════════
   R11 — THE 44px FLOOR, IN BOTH ORIENTATIONS

   R10 raised the floor across the game and left three holes, all of them the
   same shape: the rule that raised a control was keyed on `max-width`, so a
   LANDSCAPE phone — 844 wide — never got it. Measured at 844x390 before this
   block: ten controls under the floor on the founder screen alone, and the
   ration segments at 28px on the trail.

   The worst of them is DROP. `.brow .bdrop` ships from the supplies lane's own
   injected stylesheet, which lands in <head> after this file, so a correction
   here has to outrank it rather than follow it: three classes beats two, and
   `#frame` beats one. Nothing below reaches into another lane's markup or its
   file — it only re-levels the box.

   Measured before: DROP 42x18. It is the smallest control in the game and the
   only destructive one — there is no undo and no refund, the note in the panel
   says so. 18px.
   ══════════════════════════════════════════════════════════════════════════ */

  /* a mouse deserves better than 18px too, so these two do not wait for touch */
  .boxpanel .brow .bdrop{min-height:32px;min-width:56px;padding:6px 11px}
  #frame .fnd-r button{width:34px;height:34px}

  @media (hover:none),(pointer:coarse),(max-width:760px),(max-height:620px){
    /* the destructive one */
    .boxpanel .brow .bdrop{min-height:46px;min-width:62px}
    .boxpanel .brow{padding:4px 7px;gap:7px}
    /* the store */
    #frame .item .buy{min-height:46px;min-width:58px}
    /* the founder screen — dials, seat tabs, quick-picks, name field and the
       supply steppers, none of which the founders lane's own 760px rule can
       see from a landscape phone */
    #frame .fnd-d,#frame .fnd-t{min-height:44px;padding:9px 4px}
    #frame .fnd-qb{min-height:46px}
    #frame .fnd-nm{min-height:44px}
    #frame .fnd-r button{width:44px;height:44px;font-size:17px}
    /* the rations dial on the trail */
    #frame .seg{min-height:44px}
    /* R11 integrator: the store door. Its two floors are keyed on max-width:720px
       (line ~1278) and on max-height:620px (line ~1449), so a TOUCH TABLET IN
       LANDSCAPE — 1024x768, wide enough to miss the first and tall enough to miss
       the second — got neither and measured 186x35. Same shape as the three holes
       this block was written to close; this is the fourth. */
    #frame .console .rummage{min-height:44px}
  }

/* ══════════════════════════════════════════════════════════════════════════
   R12 — THE HANDSET ROUND

   The owner played this on a Pixel 10 Pro (1280x2856 at density 480, so a
   952x427 CSS box in landscape — a 2.23:1 crop, wider than anything any
   harness in this repo tests). Three device-only defects came back, and none
   of them are visible headless at 1600x900:

     1. "the background is flashing like crazy"
     2. the Android status bar drawn ON TOP of the header
     3. the store clipped mid-row with nothing saying it could scroll

   1 is handled above and in game.js (bitmap scenes, two will-change
   declarations, no backdrop-filter, no per-frame layout). 2 and 3 are here.
   ══════════════════════════════════════════════════════════════════════════ */

  /* ---- 1. THE SAFE AREA -------------------------------------------------
     Nothing in this file consumed env(safe-area-inset-*) before this round,
     which is why a battery indicator was sitting on TO FREEDOM.

     Read this before changing the floors. On Android, Chromium's
     env(safe-area-inset-*) reports the DISPLAY CUTOUT and not the system
     bars. Under the edge-to-edge that Android 15+ enforces, the status bar
     and the gesture pill still draw over the WebView while env() reports 0
     for both — in landscape the cutout is on a SIDE edge, so inset-top is
     genuinely zero and the status bar is genuinely on top of the header.
     The native way to close that gap is Capacitor 8's SystemBars plugin with
     insetsHandling:"css", which injects --safe-area-inset-* covering the bars
     as well; capacitor.config.json already CONFIGURES it but
     @capacitor/system-bars is not in package.json, so it never runs. That
     install is the real fix and it is the capacitor lane's file, not this
     one. Until it lands, --sa-min-* is a floor so the UI clears the bars
     anyway, and max() means the floor costs nothing the moment env() — or
     the plugin — starts reporting something larger. */
  :root{
    --sa-t:env(safe-area-inset-top,0px);
    --sa-r:env(safe-area-inset-right,0px);
    --sa-b:env(safe-area-inset-bottom,0px);
    --sa-l:env(safe-area-inset-left,0px);
    --sa-min-t:0px; --sa-min-b:0px;
    --pt:max(var(--sa-t),var(--sa-min-t)); --pr:var(--sa-r);
    --pb:max(var(--sa-b),var(--sa-min-b)); --pl:var(--sa-l);
  }
  /* .is-native is set on <html> by mobile/native-shim.js, and only inside a
     Capacitor WebView. On the web build every value above is 0px and this
     whole section computes to no change whatsoever — which is why none of the
     headless harnesses move. */
  html.is-native{--sa-min-t:24px;--sa-min-b:14px}
  @media (orientation:portrait){ html.is-native{--sa-min-t:32px;--sa-min-b:20px} }

  /* THE PADDING GOES ON #frame, NOT ON #app. #app holds the stage as well as
     the UI, so padding it letterboxes the PICTURE — a black bar across the
     top of a cinematic scene to make room for a status bar that is already
     drawn there. Insetting #frame keeps the art edge to edge and moves only
     the chrome, which is the thing that was being covered. */
  #frame{padding:var(--pt) var(--pr) var(--pb) var(--pl)}
  /* mobile/native-shim.css pads #app for the same reason; two insets applied
     to nested boxes would double-count, so its rule is answered here rather
     than left to fight. Three components beat its two. */
  html.is-native #app{padding:0}

  /* The header still has to READ as though it reaches the top of the screen,
     or the status bar ends up floating on the sky with the header a bar's
     height below it. This is the header's plate, extended up through the
     inset to the true top edge, and it is zero-height on every screen with no
     inset — which is all of them except a handset.

     NO NEGATIVE OFFSETS HERE, and the reason is the one that cost this fix a
     wrong first attempt: an absolutely positioned child is laid out against
     its ancestor's PADDING box, not its content box. The header is a flex
     item, so it starts below the padding; this is not, so top:0 already means
     the true top edge of the frame. The first version offset it by
     -var(--pt) to "escape" the padding, which put it entirely ABOVE the frame
     where overflow:hidden ate it — and the strip silently did not paint, on a
     screen nobody but the handset ever renders. */
  #frame::before{content:'';position:absolute;z-index:5;pointer-events:none;
    left:0;right:0;top:0;height:var(--pt);
    background:#0a1220;box-shadow:inset 0 -1px 0 rgba(150,205,255,.10)}

  /* ---- 2. SCROLL CUES ---------------------------------------------------
     "clipped with no scroll cue" — his words, and the accurate half is the
     second one. The content was always reachable. Nothing said so, so a row
     sliced through the middle read as a rendering fault.

     Each cue is one pseudo-element carrying BOTH the fade and the chevron,
     painted by an element that does not scroll — the dock for the field, the
     panel for the supplies list, THE BOX for its own contents — so it stays
     pinned to the cut edge instead of scrolling away with the content it is
     describing. game.js/scueScan() toggles .smore. */
  .dock::before,.panel.smore::after,.boxpanel.smore::after{
    content:'\25BE';position:absolute;pointer-events:none;z-index:2;
    display:flex;align-items:flex-end;justify-content:center;
    font-size:13px;line-height:1.05;color:var(--acc);
    text-shadow:0 1px 3px rgba(0,0,0,.9)}
  /* A still chevron reads as decoration; a moving one reads as an instruction.
     Two pixels of travel on a 13px glyph — the smallest animated thing in the
     game, and it exists only while there is genuinely more to see. */
  .field.smore~.dock::before,.panel.smore::after,.boxpanel.smore::after{
    animation:scuenudge 1.7s ease-in-out infinite}
  @keyframes scuenudge{0%,72%,100%{transform:translateY(0)}
    86%{transform:translateY(-2px)}}
  /* the field's cue, painted over the last strip of the field by the dock —
     its next sibling, and always solid */
  .dock::before{left:0;right:0;bottom:100%;height:clamp(20px,2.8vh,34px);opacity:0;
    transition:opacity .18s ease;
    background:linear-gradient(0deg,rgba(4,7,14,.94) 0,rgba(4,7,14,.5) 46%,rgba(4,7,14,0) 100%)}
  .field.smore~.dock::before{opacity:1}
  .panel.smore::after,.boxpanel.smore::after{left:1px;right:1px;bottom:1px;
    height:clamp(18px,2.4vh,28px)}
  .panel.smore::after{background:linear-gradient(0deg,rgba(5,17,11,.97) 0,rgba(5,17,11,.72) 48%,rgba(5,17,11,0) 100%)}
  .boxpanel.smore::after{color:var(--txt2);
    background:linear-gradient(0deg,rgba(5,11,19,.97) 0,rgba(5,11,19,.72) 48%,rgba(5,11,19,0) 100%)}
  /* THE BOX ships from the supplies lane and is not positioned; it has to be
     to hold its own cue. One property, and it changes nothing else. */
  #frame .boxpanel{position:relative}

  /* ---- 3. VERY WIDE, VERY SHORT ----------------------------------------
     2.23:1. The R11 landscape block is keyed on (min-width:600px) and
     (max-height:620px), and this device satisfies both, so all of it applies
     — these are the corrections on top, for the two screens a new player
     meets before anything else. Keyed on ASPECT so it cannot fire on a
     desktop window that merely happens to be short. */
  @media (min-aspect-ratio:19/10) and (max-height:560px){

    /* ---- THE STORE, R13 -----------------------------------------------
       R12 widened the panel to 44vw and let the name wrap, and measured the
       result as fixed. It was not: at 952x427 the supplies list is 402px of
       rows inside a 165px window, so FOUR of the six goods were under the
       fold and the third was cut through the middle. Widening a column does
       nothing about a list three times taller than its box.

       The arithmetic, since it is the whole design: the field is 243px and a
       row is 62px, so one column can never show more than three of six. Six
       need two columns of three. Two columns only keep a row at 62px if each
       is wide enough for the effect line to wrap in two — about 430px — and
       two times 430 is the whole frame. So the left column has to go.

       What was in it: a lead paragraph, and THE BOX. The paragraph is a joke
       that the dock hint already carries. THE BOX is real, and it moves — it
       becomes a strip across the top of the supplies panel, which is where a
       shopper wants a basket anyway, and where it costs 46px instead of 470.
       Nothing is lost: the counts, the capacity bar and every DROP button
       come with it. */
    /* The two-column list, which is the part that needs 900px of width. The
       rest of the store treatment is shared with upright phones and lives in
       section 7 below. */
    .storegrid{grid-template-columns:repeat(2,minmax(0,1fr));
      gap:clamp(5px,.7vh,9px) clamp(8px,1vw,14px)}

    /* THE BOX, in ONE line: heading, capacity, then the four goods across.
       No capacity bar — "4 / 17 slots" beside the heading says the same thing
       without a 10px band under it. The DROP buttons keep their 44px, because
       dropping a thing you paid for is the one control on this screen you must
       not be able to hit by accident. */
    #frame .boxpanel{flex-direction:row;align-items:center;flex-wrap:nowrap;
      gap:clamp(8px,1vw,16px);padding:3px clamp(8px,.8vw,14px)}
    #frame .boxpanel .bh{flex:0 0 auto;margin-bottom:0;gap:6px}
    #frame .boxpanel .bh .bcap{margin-left:6px}
    #frame .boxpanel .bgrid{flex:1 1 auto;min-width:0;display:flex;
      flex-direction:row;gap:clamp(4px,.5vw,8px)}
    #frame .boxpanel .brow{flex:1 1 0;min-width:0;gap:5px;padding:2px 6px}
    /* the field's own padding, which at 427px tall is 28px of nothing */
    .field{padding-top:clamp(6px,1.1vh,14px);padding-bottom:clamp(6px,1.1vh,14px)}

    /* THE FOUNDER SCREEN. Same slice, same cause — six backstory cards in a
       TWO-column grid inside a 427px field, because the 1180px rule that
       drops them to two is keyed on width and 952 is narrow by that measure
       and enormous by this one. Three columns, and the last row stops being
       cut in half. */
    #frame .fnd-pasts{grid-template-columns:repeat(3,1fr)}
    #frame .fnd-seats{grid-template-columns:repeat(5,1fr)}
    /* R14 — 100%, NOT 97vw. A vw ignores the field's own padding: at 952 wide
       the field's content box is 906px and 97vw is 923, so this rule — which
       outranks the founders region's own width — hung the whole founder screen
       17px past its scroller on every landscape phone. The region's stylesheet
       says min(1640px,100%) and this must agree with it, not overrule it. */
    #frame .fnd{width:min(1640px,100%)}
    /* R13 deleted the three-line clamp that used to sit here. Three columns was
       still about 200px of card more than a 286px field can hold — measured,
       three of the six cut through their payoff line — so the founders region
       now opens only the CHOSEN card and leaves the other five as titles. A
       clamp on top of that would truncate the one paragraph the player has
       actually asked to read: the round-12 mistake in a different hat. */
    /* the field is the scroller on these screens, so it owes the dock's cue
       somewhere to sit that is not a card's last line */
    .field.scroll,.field.top{padding-bottom:clamp(20px,3vh,34px)}
  }

  /* ---- 4. THE OTHER THREE backdrop-filters ------------------------------
     Six shipped. Three were in this file and are gone above. These three are
     inside @gauntlet regions this lane must not edit, so they are answered
     here instead — a backdrop-filter makes the compositor read back
     everything behind the element whenever either one changes, and on a
     phone it is one of the most expensive things a WebView can be asked to
     do. All three sat over the full-screen scene, which never stops moving.
     Each replacement is the same plate with the alpha raised to do the work
     the blur was doing. The lanes' own files are untouched, so when one of
     them next re-renders its stylesheet these overrides keep working. */
  #frame .sp-brief,.shr-veil,.lg-wrap{
    -webkit-backdrop-filter:none !important;backdrop-filter:none !important}
  /* the minigame's brief plate — the one of the three whose blur was doing
     visible work, over a live arena at .58 alpha */
  #frame .sp-brief{background:linear-gradient(180deg,rgba(4,9,17,.94) 0,rgba(4,9,17,.82) 100%) !important}
  .shr-veil{background:rgba(2,5,10,.95) !important}
  .lg-wrap{background:radial-gradient(120% 100% at 50% 0,rgba(4,10,18,.95),rgba(2,5,10,.985)) !important}

  /* ---- 5. FILM GRAIN IS A DESKTOP LUXURY --------------------------------
     A full-screen mix-blend-mode:overlay layer whose background image is an
     SVG feTurbulence tile. Blending forces the compositor to keep everything
     under it readable, and under it is the scene, which drifts forever. On a
     six-inch panel at 3x the tile is sub-pixel and the effect is invisible:
     pure cost on the one device that could not afford it. Kept in full on
     anything with a mouse. */
  /* ---- 7. R13: THE STORE, WHEREVER IT IS CRAMPED ------------------------
     Everything the outfitting screen needs on a phone in EITHER orientation.
     The two-column list is the only part that is landscape-only; it lives in
     section 3 above, because two columns need about 900px of frame.

     Upright was worse than landscape and for the opposite reason. `.split` is
     a flex COLUMN on a narrow screen, and its base rule gives the left side
     `flex:1 1 auto` and the panel `flex:0 0 <basis>` — sized for a ROW, where
     that basis is a width. Turned vertical, the same declarations mean the
     BASKET grows to fill the screen and the SHOP is pinned to 155px. Measured
     at 390x844: the box took 340px of a 507px split and the supplies list got
     a 91px window onto 405px of goods — five of six unreachable. The store is
     the point of the store. */
  @media (max-width:760px),(max-height:560px){
    .split{flex-direction:column;gap:clamp(4px,.7vh,9px)}
    .split>.l{flex:0 0 auto;min-width:0;justify-content:flex-start}
    .split>.r{flex:1 1 auto;min-height:0;justify-content:flex-start}
    .split .panel{max-height:100%;
      padding:clamp(7px,1vh,14px) clamp(9px,1vw,16px)}
    .split .panel .ph{margin-bottom:clamp(4px,.6vh,8px);
      padding-bottom:clamp(4px,.5vh,7px)}
    /* the heading's own furniture: an 18px icon beside a 12px wordmark */
    .split .panel .ph svg{height:13px;width:auto}
    .storegrid{min-height:0}
    #frame .item{padding:clamp(4px,.5vh,8px) clamp(9px,.8vw,14px)}
    #frame .item .nm{white-space:normal;overflow:hidden;text-overflow:clip;
      display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}

    /* the lead paragraph goes: "the prairie does not take returns" is the same
       joke the DROP button's own copy makes, two lines further down */
    #frame .split>.l>.body2{display:none}

    /* THE BOX stops being a column of its own and becomes a status strip
       across the top of the shop, which is where a shopper wants a basket.
       It keeps every count and every DROP; it loses the capacity bar, which
       drew the same number the caption prints in words. */
    #frame .boxpanel{display:flex;margin-top:0;width:100%;max-width:none;
      max-height:none;align-self:stretch}
    #frame .boxpanel .bbar{display:none}
    #frame .boxpanel .bnote{display:none}
    #frame .boxpanel .brow .bs{display:none}
    #frame .boxpanel .brow .bdrop{min-height:44px}
    /* it is one strip now — it can never hold more than it shows, so round
       12's scroll cue on it would be describing a scroll that cannot happen */
    #frame .boxpanel.smore::after{display:none}

    /* THE DUPLICATES. The dock's load bar is the same four counts and the same
       capacity as the strip, printed smaller and 300px lower; and at the
       counter the route track is 11px of a journey you have not started, under
       a header already printing "0% TO FREEDOM". :has(.boxpanel) scopes both
       to the store — the trail screen has a load bar, no box strip, and keeps
       everything. */
    #frame:has(.boxpanel) .loadbar{display:none}
    #frame:has(.boxpanel) .track{display:none}
  }
  /* Upright, the four goods go two-up under the heading rather than four
     across: at 354px a four-across strip gives each good 80px, and a DROP
     button plus a count does not fit in 80px.
     R14 — minmax(0,1fr), not 1fr. A bare 1fr track refuses to shrink below
     its content's min-content, and a row's floor here is icon + count + a
     62px DROP: ~165px. Two of those in a ~310px panel is how the store stuck
     28px out of the right edge of a phone held upright. minmax(0,1fr) pins
     both tracks to half the panel; the name span already ellipsizes. */
  @media (max-width:760px){
    #frame .boxpanel .bgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:4px}
    #frame .boxpanel .brow{min-width:0;gap:5px}
  }
  /* A tier below the Pixel: an iPhone in landscape is 390 CSS tall, 37 short
     of the Pixel, and 37px is most of a supplies row. The 44px controls do not
     move — everything wrapped around them gives up a pixel or two instead. */
  @media (max-height:470px){
    #frame .item{padding:3px clamp(8px,.8vw,13px)}
    .split .panel{padding:5px clamp(8px,.9vw,14px)}
    .split{gap:4px}
    .storegrid{gap:3px clamp(8px,1vw,14px)}
    #frame .rail .mtr{padding:3px 7px}
    #frame .rail .mtr .r1{margin-bottom:3px}
    #frame .boxpanel{padding-top:2px;padding-bottom:2px}
    .field{padding-top:4px;padding-bottom:4px}
    /* 44, not 46. The floor is 44 and this screen needs the two pixels; every
       other control on it is still at or above 44 and nothing here goes under. */
    #frame .item .buy{min-height:44px}
  }

  /* ---- 6. R13: THE DOCK GIVES BACK ITS DECORATION ----------------------
     On a 427px-tall screen the dock was 97px — 23% of the display — and 20 of
     those were the ending-log teaser: a non-interactive pill that says the log
     remembers what you find. True, and not worth a fifth of the space between
     the player and the card they are trying to read. The buttons and the hint
     line stay; only the pill goes, and only where the screen is short.

     The #frame prefix is required: .rec-strip's display:flex ships inside an
     injected <style> in game.js, which lands in <head> AFTER this file's
     <link>, so a bare .rec-strip here ties on specificity and loses on order.

     (This comment was briefly TWO comments with live prose between them. CSS
     error recovery does not skip that prose — it reads it as a selector and
     keeps reading until the next brace, which was this @media's, so the whole
     block was silently swallowed and the strip stayed on screen while the
     stylesheet reported no error at all.) */
  @media (max-height:560px){
    #frame .rec-strip{display:none}
  }

  @media (pointer:coarse),(max-height:560px){
    .fx-grain{display:none}

    /* ---- 6. R13: SIXTEEN MOTES IS SIXTEEN LAYERS ----------------------
       Each mote runs an infinite transform/opacity animation, and a transform
       animation is compositor-driven whether or not anybody wrote
       will-change — so the phone was carrying sixteen separate composited
       layers of drifting dust over an already-tight tile budget. Eight of them
       on a 6" panel is a difference nobody can see; a display:none mote also
       stops running its animation, so this is not a paint saving dressed up as
       a layer saving. The desktop keeps all sixteen. */
    .fx-motes .mote:nth-child(2n){display:none}
  }

/* ============================================================
   R21b — P1-10: THE FLOOR IS THE DEFAULT NOW, NOT A SETTING

   R20c built this list as an OPT-IN under `data-tsize="large"`, and it was the
   right list — it is every label P1-10 names. But an opt-in floor is not a
   floor. P1-10's acceptance sentence is "a hard floor of ~11px for any text
   that carries meaning", and the trail still opened at 6px on PARTY / POLLING
   / PATIENCE for every player who never went looking in a menu. Worse, the
   round that shipped the control shipped it on the one panel whose Text size
   row scrolled off the bottom at 844x390 — the fix for the small type was
   itself unreachable on the crop that needed it most. That circle is broken
   from both ends this round: the floor applies unasked, and Settings fits.

   So these selectors are unconditional, and `large` is a SECOND step above
   them for anyone who wants more. Both carry `:root:root` — two pseudo-classes
   — because the sizes they override live in @media blocks injected by game.js
   at runtime, which land after this stylesheet in the document; specificity is
   the only thing that survives that, and a media query adds none.

   Every value is `max(11px, <the vw term the rule already had>)`, so a wide
   screen keeps the size it was measured at and only the phones move. The list
   is not a guess: it is the (parent > class) text leaves a probe found under
   11px inside .hdr / .hudband / .dock / .partybar / .console at 1920, 952x427,
   844x390 and 390x844, on the calm road and on the outcome report.
   ============================================================ */
/* --- the floor, unconditional --------------------------------------- */
:root:root .mtr .nm{font-size:max(11px,.56vw)}
/* tracking is decoration; size is legibility. Under 1100px the label gives up
   the letter-spacing rather than the millimetre — swept 760->1920, GRIEVANCE
   was the last one cut, at 760 and 780. */
@media (max-width:1100px){ :root:root .mtr .nm{letter-spacing:.01em} }
:root:root .stat .sl{font-size:max(11px,.52vw)}
:root:root .htitle .hsub{font-size:max(11px,.62vw)}
:root:root .track .lb{font-size:max(11px,.5vw)}
:root:root .pchip{font-size:max(11px,.62vw)}
:root:root .seg .sn{font-size:max(11px,.66vw)}
:root:root .seg .scst{font-size:max(11px,.58vw)}
:root:root .rations .rcap,
:root:root .rations .rcap .rw{font-size:max(11px,.52vw)}
:root:root .lchip .lw,
:root:root .loadbar .lcap .lw,
:root:root .loadbar .lroom .lw,
:root:root .loadbar .lroom .lnum{font-size:max(11px,.5vw)}
:root:root .mcash .cmore{font-size:max(11px,.62vw)}
:root:root .pty-lbl span{font-size:max(11px,.47vw)}
/* `.partybar` is in this one on purpose: the portrait arm at ~line 2086 writes
   `.field.road .partybar .pty-lbl b` — four classes — and a floor that loses to
   a rule inside the layout it is meant to floor is not a floor. Measured: the
   party number stayed 10px at 390x844 until this selector matched its weight. */
:root:root .partybar .pty-lbl b,
:root:root .pty-lbl b{font-size:max(11px,.72vw)}
:root:root .pty-nm{font-size:max(11px,.82vw)}
/* Upright, five cards share 390px, so the name box is 59 and GRANDMA ENID
   needs 71 at the floor. The name is the one label on the card that has to
   read, so it wraps instead of ellipsising — two lines of 11px beat one line
   of "GRANDMA EN…", and nothing is dropped to get there. The strip pays 11px
   for it on the one crop that has the height to spare. */
@media (max-width:430px){
  :root:root .partybar .pty-nm{white-space:normal;overflow:visible;line-height:1.04}
  /* R21b — TWO THINGS THE FLOOR BROKE UPRIGHT, FIXED RATHER THAN EXEMPTED.
     Raising type makes labels need more room, and on the one 390px-wide crop
     two of them ran out of it. Both were caught by a sweep of every clipping
     text leaf on four screens at four viewports, not by eye, and both are
     fixed by giving the label the width it needs instead of the size back.

     1. The party stat label sat on one line with its number
        (`justify-content:space-between`) in a 42px column: POLLING needed 58,
        STANDING 67. It stacks above the number, which is the full 58px card,
        and gives up its tracking — 55 and 46. */
  :root:root .partybar .pty-lbl{flex-direction:column;align-items:flex-start;gap:0}
  :root:root .partybar .pty-lbl span{letter-spacing:.02em}
  /*  2. The header subtitle — the line that says which landmark is next and
        how far — had 63px of a two-column header whose right cluster takes
        240 for DAY, the flag level and the distance. "→ LETHBRIDGE · 20%"
        needs 124. Upright the header goes to two rows: the title and its
        subtitle get the full width, the three stats spread along the row
        under them. P2-31 asks for exactly this — a width-stable header slot
        that keeps the distance number — and it costs 14px of a 844px screen
        (chrome 36% -> 38%, bar 40%). */
  :root:root .hdr{grid-template-columns:minmax(0,1fr);row-gap:1px;
    padding-top:2px;padding-bottom:3px;
    padding-left:clamp(58px,7vw,74px);align-items:start}
  /* the second row has to be paid for, and it is paid for here rather than out
     of the picture: the header's own gutters and the stat column's inner gap.
     Measured at 390x844 — the crossing and the fork went to 41% with the row
     in and no trim, which is over P1-14's bar by a point. */
  :root:root .hdr .stat{gap:0}
  :root:root .hdr .htitle{gap:1px}
  :root:root .hdr>.htitle{grid-column:1;grid-row:1;align-items:flex-start}
  :root:root .hdr>.hright{grid-column:1;grid-row:2;justify-self:stretch;
    justify-content:space-between;gap:8px}
  :root:root .hdr.bare>.hright{display:none}
  :root:root .htitle .hsub{letter-spacing:.08em}
  /* the danger badge is one word and cannot wrap, so upright it gives up its
     tracking instead of its height: REVIEWING needs 67px of a 59px column at
     .1em and 57 at none. Same trade the meter labels make under 1100px. */
  :root:root .partybar .pty-ribbon{letter-spacing:0}
  /* the load strip ran 22px past its own box upright once its labels were on
     the floor, and `3/14 SLOTS` was the end that got sliced by the screen
     edge. Paid for out of the gaps and the chip padding rather than out of
     the band's height, which the field would have paid for. */
  :root:root .loadbar{gap:4px}
  :root:root .loadbar .lchip{padding-left:4px;padding-right:4px}
  :root:root .loadbar .lcap{margin-right:0}
}
:root:root .pty-rl,
:root:root .pty-ribbon,
:root:root .pty-tile-l{font-size:max(11px,.47vw)}
:root:root .console .btn.rummage{font-size:max(11px,.95vw)}
:root:root .kbd{font-size:max(11px,.62vw)}
:root:root .milepost .mk,
:root:root .toast .tt{font-size:max(11px,.6vw)}
/* --- and the step above it ------------------------------------------- */
:root:root[data-tsize="large"] .mtr .nm,
:root:root[data-tsize="large"] .stat .sl,
:root:root[data-tsize="large"] .htitle .hsub,
:root:root[data-tsize="large"] .track .lb,
:root:root[data-tsize="large"] .pchip,
:root:root[data-tsize="large"] .seg .sn,
:root:root[data-tsize="large"] .seg .scst,
:root:root[data-tsize="large"] .rations .rcap,
:root:root[data-tsize="large"] .rations .rcap .rw,
:root:root[data-tsize="large"] .lchip .lw,
:root:root[data-tsize="large"] .loadbar .lcap .lw,
:root:root[data-tsize="large"] .loadbar .lroom .lw,
:root:root[data-tsize="large"] .loadbar .lroom .lnum,
:root:root[data-tsize="large"] .mcash .cmore,
:root:root[data-tsize="large"] .pty-lbl span,
:root:root[data-tsize="large"] .pty-nm,
:root:root[data-tsize="large"] .pty-rl,
:root:root[data-tsize="large"] .pty-ribbon,
:root:root[data-tsize="large"] .pty-tile-l,
:root:root[data-tsize="large"] .milepost .mk,
:root:root[data-tsize="large"] .toast .tt{font-size:max(13px,.72vw)}
:root:root[data-tsize="large"] .pty-lbl b{font-size:max(14px,.82vw)}

/* R20c — P2-32. The pace shortcut, printed on the pace button. Absolute so it
   cannot move a single box in a dock three rounds of fit measurements went
   into, and gone entirely where there is no keyboard to press. */
.pace{position:relative}
.pace > .kbd{position:absolute;top:5px;right:6px;margin:0;opacity:.55;pointer-events:none}

/* ============================================================
   R20c — P2-3 / P2-5: THE TIP, and the two controls it serves

   The six meters and the five party cards were the only things on the play
   screen carrying real information and the only things you could not ask a
   question of. Both are <button> now; this is what answers.
   ============================================================ */
/* A <button> is not a <div> with a handler: Blink gives its anonymous content
   box an inline strut, and the cell measured FOUR PIXELS taller than the div it
   replaced — 36 against 32 — which is six cells times four pixels of dock, and
   a store grid three pixels short of its own items at 844x390. `display:flex`
   removes the strut, so the cell measures exactly what its children do.
   Measured both ways before and after; the numbers are in p2-r20.md. */
.mtr{-webkit-appearance:none;appearance:none;border:0;width:100%;text-align:left;
  font:inherit;color:inherit;cursor:pointer;
  display:flex;flex-direction:column;justify-content:center}
.mtr:focus-visible{outline:3px solid var(--acc);outline-offset:2px}
.mtr.tipon{box-shadow:inset 0 0 0 2px var(--acc),0 0 22px rgba(67,228,131,.22)}
.mtr:hover .nm{color:var(--txt2)}
@media (hover:none),(pointer:coarse){ .mtr:hover .nm{color:var(--txt3)} }
.pty-c{-webkit-appearance:none;appearance:none;border:0;font:inherit;color:inherit;cursor:pointer}
.pty-c:focus-visible{outline:3px solid var(--acc);outline-offset:2px}
.pty-c.tipon{box-shadow:inset 0 0 0 2px var(--acc),0 0 22px rgba(67,228,131,.22)}

/* P2-4 — the line the referendum is decided on, drawn from day one on the one
   meter that has a threshold. The count screen already draws it; the run did
   not, so a player at 47 with two legs left could not see they were short. */
.mbar .th{position:absolute;left:50%;top:-2px;bottom:-2px;width:2px;margin-left:-1px;
  background:rgba(255,255,255,.85);box-shadow:0 0 6px rgba(0,0,0,.8);
  pointer-events:none;z-index:2}

/* P2-26 — cash counts, it does not fill. One notch per hundred dollars. */
/* the fund's strip is the same height as a meter bar at EVERY breakpoint —
   .rail is a grid, so one cell 4px taller stretches all six and costs 4px
   of dock on the crop with the least of it. */
.mcash{display:flex;align-items:stretch;gap:2px;background:none}
.mcash .cn{flex:1 1 0;min-width:0;background:rgba(255,255,255,.07);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.35)}
.mcash .cn.on{background:linear-gradient(180deg,#43e483,#16a04e)}
.mcash .cn.part{background:linear-gradient(90deg,#16a04e var(--cp,0%),rgba(255,255,255,.07) var(--cp,0%))}
.mcash .cmore{flex:0 0 auto;align-self:center;padding-left:3px;font-family:var(--cond);
  font-weight:700;font-size:clamp(9px,.62vw,12px);line-height:1;color:rgba(255,255,255,.14)}
.mcash .cmore.on{color:var(--acc)}

/* P2-26 — capacity as a picture, the way the reference draws its weight */
.loadbar .lroom{display:inline-flex;align-items:center;gap:6px}
.loadbar .lroom .slots{display:inline-flex;gap:2px;align-items:center}
.loadbar .lroom .slot{width:4px;height:11px;background:rgba(150,205,255,.16);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.4)}
.loadbar .lroom .slot.on{background:var(--acc);box-shadow:inset 0 0 0 1px rgba(0,0,0,.4),0 0 6px rgba(67,228,131,.5)}
.loadbar .lroom.full .slot.on{background:#ffcd77;box-shadow:inset 0 0 0 1px rgba(0,0,0,.4),0 0 6px rgba(240,177,60,.6)}
.loadbar .lroom .lnum{font-variant-numeric:tabular-nums}
@media (max-width:900px){ .loadbar .lroom .slots{display:none} }

/* the popover itself */
.tipbox{position:fixed;z-index:130;width:min(340px,84vw);
  background:linear-gradient(180deg,rgba(13,23,39,.985) 0,rgba(5,10,19,.99) 100%);
  box-shadow:inset 0 0 0 2px rgba(67,228,131,.42),0 26px 60px rgba(0,0,0,.7);
  clip-path:var(--chamfer);animation:riseIn .14s cubic-bezier(.2,.8,.3,1) both}
.tiph{display:flex;align-items:center;gap:9px;padding:10px 10px 7px 14px;
  font-family:var(--cond);font-weight:700;text-transform:uppercase;letter-spacing:.16em;
  font-size:clamp(11px,.8vw,14px);color:var(--acc);
  border-bottom:1px solid rgba(67,228,131,.22)}
.tipx{margin-left:auto;flex:none;min-width:30px;min-height:30px;display:flex;
  align-items:center;justify-content:center;border:0;cursor:pointer;
  font:700 19px/1 var(--cond,system-ui);color:var(--txt2);
  background:rgba(150,205,255,.08);box-shadow:inset 0 0 0 1px rgba(150,205,255,.2);
  clip-path:var(--chamfer-s)}
.tipx:focus-visible{outline:3px solid var(--acc);outline-offset:2px}
/* R21 — 40px, not 44. The contract says "44px minimum regardless of
   orientation" and this is the tip's only labelled exit; it is the same defect
   P1-5 raised against the ending log's 52x20 CLOSE, four pixels smaller and on
   a component no harness had ever opened. Found by the two `tip/*` panes the
   moment they existed, which is the whole argument for adding them. */
@media (hover:none),(pointer:coarse){ .tipx{min-width:44px;min-height:44px} }
.tipb,.tipf{padding:9px 14px;font-family:var(--ui);font-size:clamp(11px,.8vw,14px);
  line-height:1.45;color:var(--txt2)}
.tipb p,.tipf p{margin:0 0 6px}
.tipb p:last-child,.tipf p:last-child{margin-bottom:0}
.tipf{border-top:1px solid rgba(150,205,255,.14);color:#dceaf4}
.tipf b{color:var(--acc)}
.tipperk{color:var(--acc)}
.tipnow{color:var(--txt4);font-variant-numeric:tabular-nums}

/* ============================================================
   R20c — P2-13: the coach line. One line, in place, no press of its own.
   It sits in the field rather than the dock deliberately: the dock is the
   thing a 390px-tall crop has least of, and three rounds of fit measurement
   went into its height.
   ============================================================ */
.coach{display:flex;align-items:flex-start;gap:9px;
  padding:clamp(6px,.7vh,10px) clamp(9px,.8vw,14px);
  margin-bottom:clamp(5px,.6vh,9px);
  background:linear-gradient(180deg,rgba(18,44,30,.92) 0,rgba(7,20,14,.94) 100%);
  box-shadow:inset 0 0 0 1px rgba(67,228,131,.34);clip-path:var(--chamfer-tl);
  animation:riseIn .18s cubic-bezier(.2,.8,.3,1) both}
.coach .ci{flex:none;color:var(--acc);line-height:1}
.coach .ct{flex:1 1 auto;min-width:0;font-family:var(--ui);
  font-size:clamp(11px,.78vw,14px);line-height:1.4;color:#cfe6d9}
.coach .ct b{color:var(--acc);font-weight:700}
.coach-x{flex:none;min-width:24px;min-height:24px;display:flex;align-items:center;
  justify-content:center;border:0;cursor:pointer;font:700 17px/1 var(--cond,system-ui);
  color:var(--txt3);background:none;box-shadow:inset 0 0 0 1px rgba(150,205,255,.2);
  clip-path:var(--chamfer-s)}
.coach-x:focus-visible{outline:2px solid var(--acc);outline-offset:2px}
@media (hover:none),(pointer:coarse){ .coach-x{min-width:32px;min-height:32px} }

/* R20c — P2-31. The header's flag slot reserves the widest label the scale can
   produce, so the top-right block stops changing width between screens. Both
   spans occupy the same grid cell; the hidden one wins the column. */
.stat .sl.stable{display:grid;justify-items:center}
.stat .sl.stable>span{grid-area:1/1;white-space:nowrap}
.stat .sl.stable .slg{visibility:hidden;pointer-events:none}
/* the distance stat keeps its number at every width and shortens its LABEL
   on the one crop that cannot hold the words */
.stat .sl .lgs{display:none}
@media (max-width:430px){
  .stat .sl .lgw{display:none}
  .stat .sl .lgs{display:inline}
}

/* R20c — P2-6. The mechanical half of a departure, in the same chips as every
   other outcome. It sits under the effect row, not instead of it. */
.lossrow{display:flex;align-items:center;flex-wrap:wrap;gap:clamp(5px,.5vw,9px);margin-top:5px}
.lossrow .lt{font-family:var(--ui);font-style:italic;font-size:clamp(10px,.72vw,13px);
  line-height:1.35;color:var(--txt3);flex:1 1 18ch;min-width:0}

/* ============================================================
   R20c — the founder's controls (P2-14, P2-15, P2-24, P2-25)
   ============================================================ */
/* P2-14 — the value goes on the face of the dial. Two lines inside a control
   that was already 44px tall for one 9px line, so the grid does not move. */
.fnd-d{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;
  line-height:1.1}
.fnd-d .dk{font-weight:700;letter-spacing:.1em}
.fnd-d .dv{font-family:var(--ui,system-ui);font-weight:500;font-style:normal;
  text-transform:none;letter-spacing:0;font-size:.82em;opacity:.72;
  white-space:nowrap;overflow:hidden;text-overflow:clip;max-width:100%}
.fnd-d.fnd-undo{color:var(--txt3,#6d8598)}
.fnd-d.fnd-undo:disabled{opacity:.4;cursor:default}

/* P2-15 — a text input reads as a text input */
.fnd-nm{text-align:left}
.fnd-nm::placeholder{color:var(--txt4,#5a7186);font-weight:500;font-style:italic;
  text-transform:none;letter-spacing:.02em;opacity:1}
.fnd-nm:focus{caret-color:var(--acc,#43e483)}

/* P2-24 — the tap that changed nothing still says it landed */
@keyframes fndAck{0%{box-shadow:inset 0 0 0 2px var(--acc,#43e483)}
  100%{box-shadow:inset 0 0 0 2px rgba(67,228,131,0)}}
.fnd-ack{animation:fndAck .42s ease-out 1}
/* the pace the last press chose, stated rather than only remembered */
.pace.last .pn::after{content:'';display:inline-block;width:5px;height:5px;margin-left:7px;
  vertical-align:2px;background:var(--acc);opacity:.75;transform:rotate(45deg)}

/* P2-25 — the way back off step 0 */
.btn.quiet{color:var(--txt3,#6d8598);background:linear-gradient(180deg,#141d29,#0a1017);
  box-shadow:inset 0 0 0 1px rgba(150,205,255,.18)}
.btn.quiet:hover{color:#dceaf4;box-shadow:inset 0 0 0 1px rgba(150,205,255,.34)}
@media (hover:none),(pointer:coarse){
  .btn.quiet:hover{color:var(--txt3,#6d8598);box-shadow:inset 0 0 0 1px rgba(150,205,255,.18)}
  .btn.quiet:active{color:#dceaf4;box-shadow:inset 0 0 0 1px rgba(150,205,255,.34)}
}

/* R20c — P2-28. Where all five finished. A footnote under the score card, not
   a second scorecard: the headline is still FREEDOM SCORE, and this is the
   data behind "what would I do differently". Six small tiles in one row, the
   dying ones in the same red the rail uses. */
.endmeters{display:flex;flex-wrap:wrap;justify-content:center;
  gap:clamp(4px,.5vw,9px);margin-top:clamp(5px,.7vh,10px)}
.endmeters .em{display:flex;align-items:baseline;gap:6px;
  padding:clamp(3px,.4vh,6px) clamp(7px,.6vw,12px);
  background:rgba(8,14,24,.72);box-shadow:inset 0 0 0 1px rgba(150,205,255,.16);
  clip-path:var(--chamfer-s)}
.endmeters .ek{font-family:var(--cond);font-weight:600;text-transform:uppercase;
  letter-spacing:.16em;font-size:clamp(8px,.56vw,11px);color:var(--txt3)}
.endmeters .ev{font-family:var(--cond);font-weight:700;font-variant-numeric:tabular-nums;
  font-size:clamp(11px,.82vw,15px);color:#e8f2f8}
.endmeters .em.low{box-shadow:inset 0 0 0 1px rgba(255,92,72,.45)}
.endmeters .em.low .ev{color:#ff9179}
.endmeters .em.cash .ev{color:var(--acc)}

/* ══════════════════════════════════════════════════════════════════════════
   R21 — P1-14 · THE CHROME. The HUD comes off the picture's back.

   MEASURED BEFORE, chrome = (.hdr + .dock) / innerHeight, at
   952x427 / 844x390 / 390x844:

     trail     58% / 63% / 56%
     crossing  57% / 63% / 68%
     fork      48% / 57% / 59%          bar: 40%

   The consequences were not cosmetic. At 844x390 with five meters in the red
   the road report's `.speak` collapsed to clientHeight 4 against scrollHeight
   108 and the speaker's plate was cut 11px — the whole residue of P0-8. The
   truck's contact shadow was behind the roster band on the road screen (rig
   bottom 66.6% against roster top 58.1%). And seven Blender scene plates were
   being shown through a ~150px letterbox with the horizon cropped away.

   `_gauntlet/reference/ot_761dabb3.jpg` settles the shape of the answer, and
   it is more radical than "make the rows shorter": THE REFERENCE RESERVES NO
   BAND AT ALL. The calendar, the pace, the rations, the weight and the cash
   are set straight on the sky with no plate under them; the party cards sit on
   the ground; the painting runs edge to edge behind every one of them.

   So the dock is split by what it is FOR. Three rows are standing status —
   the trail track, the meter rail, the load strip — read and never pressed.
   hudLift() takes them out of the dock and hangs them on the sky under the
   header, in a band with NO LAYOUT HEIGHT of its own. What is left in the dock
   is what a thumb presses: the ration policy, the store door and the three
   pace buttons, or the screen's choices.

   Everything here hangs off `#frame.hudlift`, stamped by shell() from
   HUDLIFT_SCREENS — not the store and not the count, whose fields are panels
   rather than paintings.

   --rt/--rr/--rl are the three lifted rows' own measured heights and --rg the
   gap between them; the ribbon total is derived rather than typed twice.
   ══════════════════════════════════════════════════════════════════════════ */
#frame{--rt:37px;--rr:54px;--rl:34px;--rg:9px;
  --ribbon:calc(var(--rt) + var(--rr) + var(--rl) + var(--rg) * 4);
  --ribx:clamp(18px,2.6vw,48px)}

/* Zero-height flex item between the header and the field. Its child hangs off
   it, so the band costs the layout NOTHING and the field below it is the full
   remaining height of the frame — which is what makes the scene taller rather
   than merely differently arranged. */
.hudband{position:relative;height:0;flex:0 0 auto;z-index:4}
.hudin{position:absolute;left:var(--ribx);right:var(--ribx);top:var(--rg);
  display:flex;flex-direction:column;gap:var(--rg);pointer-events:auto}
.hudin>.track{margin:0}
/* the load strip's own plate goes: on the sky it read as a second dock rail
   floating in mid-air. The chips keep theirs, which is what carries the
   numbers; the strip itself becomes type on a painting, as in the reference. */
.hudin>.loadbar{background:none;box-shadow:none;padding-left:0;padding-right:0}
.hudin>.track,.hudin>.loadbar{text-shadow:0 1px 3px rgba(0,0,0,.9)}
/* order: the meters first — they are what a player checks — then the box,
   then the trail rail nearest the picture. */
.hudin>.rail{order:1}
.hudin>.loadbar{order:2}
.hudin>.track{order:3}

/* One soft gradient under the whole band, fading out at its bottom edge. It
   is NOT a letterbox: the scene runs behind it at full height and it is
   transparent at the horizon end. `.fx-top` already darkens the top of every
   plate, so this is a few per cent on top of a wash the art was built with. */
.hudband::before{content:'';position:absolute;left:0;right:0;top:calc(-1 * var(--rg));
  height:calc(var(--ribbon) + var(--rg) * 2);z-index:-1;pointer-events:none;
  background:linear-gradient(180deg,rgba(5,8,16,.86) 0%,rgba(5,8,16,.62) 46%,
    rgba(5,8,16,.26) 78%,rgba(5,8,16,0) 100%)}

/* The field keeps its own content out from under the band. The PICTURE does
   not — the field is transparent and the stage is behind it — so this padding
   is scene, not letterbox, which is the whole difference between this and the
   reserved band it replaces. */
#frame.hudlift .field{padding-top:calc(var(--ribbon) + var(--rg) * 2)}
#frame.hudlift .field.top{padding-top:calc(var(--ribbon) + var(--rg) * 2)}

/* ---- landscape phones ---------------------------------------------------- */
@media (min-width:600px) and (max-height:620px){
  #frame{--rt:19px;--rr:32px;--rl:25px;--rg:5px;--ribx:clamp(14px,2vw,34px)}
  /* the two control rows are the whole dock now, so they can afford to be
     tight against each other and against its edges */
  #frame.hudlift .dock{padding-top:4px;padding-bottom:5px;gap:3px}
  /* the ration plate's own padding was a second box inside a box; the
     segments are 44px in their own right and carry the row */
  #frame.hudlift .dock .rations{padding-top:0;padding-bottom:0}
}

/* ---- portrait phone ----------------------------------------------------- */
@media (max-width:720px){
  #frame{--rt:34px;--rr:87px;--rl:23px;--rg:6px;--ribx:12px}
  #frame.hudlift .dock{padding-top:8px;padding-bottom:12px;gap:7px}
}

/* ---- P1-14 · the crossing's four options ---------------------------------
   `.choices.four` inherits the two-across default everywhere it has the
   height for it; these are the two crops where it did not. */
@media (min-width:600px) and (max-height:620px){
  /* one row of four. At 844 that is 199px a cell against a 44px touch floor,
     and it takes the river's dock from 102px to one row. */
  .choices.four{grid-template-columns:repeat(4,minmax(0,1fr))}
}
@media (max-width:720px){
  /* portrait cannot hold four across; two-by-two is still less than half of
     the 308px the one-column stack was costing. */
  .choices.four{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* ---- the band's own shape ------------------------------------------------
   Two rows, not three: the meters own the top line, and the trail rail and
   the box strip share the second because both are one line of small type and
   a landscape phone is wide. 62px instead of 96px on the two crops with the
   least of it — 34px straight back to the painting, which is the difference
   between the truck standing clear of the band and standing behind it. */
@media (min-width:600px) and (max-height:620px){
  .hudin{display:grid;grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:"rail rail" "track load";align-items:center;
    column-gap:clamp(10px,1.4vw,26px)}
  .hudin>.rail{grid-area:rail}
  .hudin>.track{grid-area:track;align-self:center}
  .hudin>.loadbar{grid-area:load;justify-content:flex-end;flex-wrap:nowrap}
  #frame{--ribbon:calc(var(--rr) + max(var(--rt),var(--rl)) + var(--rg) * 2)}
}

/* ---- the truck stands on open ground -------------------------------------
   The ground line was 44% and the dock was 202px: the truck stood on the top
   edge of the meter rail because there was nowhere else. With the standing
   rows on the sky the band between them and the roster is the shot, so the
   rig moves down into it and comes in a size that fits between the two.
   Measured at 844x390: band bottom 111, roster top 236, and a 215px-wide rig
   is 108 tall — it clears both ends by 8 and 9px, and its contact shadow and
   its dust sheet are on open scene for the first time. */
@media (min-width:600px) and (max-height:620px){
  .rigline{width:min(24vw,196px);--gy:51%!important}
  .rigline.road{width:min(24vw,196px);--gy:51%!important}
  .groundhaze{--gy:51%!important;bottom:calc(100% - 51% - 3vh)}
}
/* portrait keeps its roster under the header, so the truck's band is the one
   between the roster's last card and the dock. Measured at 390x844 with the
   warning band AND a lesson up — the worst case — the roster ends at 494 and
   the dock starts at 618: a 210px-wide rig is 105 tall and stands in the
   middle of that with its shadow on open ground at both ends. */
@media (max-width:720px){
  #frame.hudlift .rigline.road{width:min(54vw,210px);--gy:70%!important}
  #frame.hudlift .rigline.road~.groundhaze{--gy:70%!important;bottom:calc(100% - 70% - 4vh)}
}

/* ============================================================
   R23 — THE ROAD MOVES

   Owner: "when the truck moves down the road... it should be like oregon
   trail... we should have our scene sort of move."

   Four parallax bands, far to near, that exist only while a leg is playing.
   The rules they are built to, all three of them bought with earlier rounds:

   · ONLY TRANSFORM AND OPACITY MOVE.  No filter, no backdrop-filter, no
     background-position, no width, no left — nothing on this deck can force a
     layout or a repaint. The loop writes one translate3d per band and one
     opacity on the deck, and that is the entire per-frame cost.

   · IT IS FREE WHEN PARKED.  `display:none` is not "invisible", it is ABSENT:
     a display:none subtree has no composited layer, no tiles and no raster.
     Between legs this deck costs the phone precisely nothing, which is the
     whole reason it is allowed to be four full-width layers for a second.
     will-change is scoped to `.go` for the same reason #app's is scoped to a
     live shake — a permanent promotion is permanent memory.

   · THE WRAP IS INVISIBLE.  A band is 200% wide and holds the SAME tile twice,
     so translating it by one tile — 50% of its own box — lands on a picture
     identical to the one it started on. There is no seam because there is
     nothing to seam.

   Bands sit UNDER fx-top / fx-bot / fx-vig / fx-grain, so they take the
   scene's own grade, vignette and grain for free and read as part of the
   painting rather than as a strip laid over it. The truck (.rigline, z-index:1)
   stays above all of them.
   ============================================================ */
.drvdeck{position:absolute;inset:0;overflow:hidden;pointer-events:none;
  display:none;opacity:0;--gy:62%}
.drvdeck.go{display:block}
.drvb{position:absolute;left:0;width:200%;backface-visibility:hidden}
.drvdeck.go .drvb{will-change:transform}
.drvb>i{position:absolute;left:0;top:0;bottom:0;width:50%;display:block}
.drvb>i+i{left:50%}
.drvb svg{display:block;width:100%;height:100%}
/* The four depths. Everything is hung off --gy — the ground line the truck's
   tyres are standing on — because that line moves: two media queries above
   force it to 51% on a landscape phone and 70% on a portrait one, and a road
   that ignored them would be sliding past at the wrong altitude on both of the
   crops the owner plays. drvGround() reads the winning value.

   HEIGHTS ARE SET BY WHAT THE DOCK LEAVES. Measured at 1280x720 on the trail:
   ground line 454px, dock top 570px — 116px, sixteen per cent of the screen, is
   the entire strip of road a player can see. The first pass hung the road band
   from gy+8% to the bottom of the viewport and put four fifths of it behind the
   controls. It lives in the visible strip now. The mid band goes the other way
   and rises well ABOVE the horizon, because a silhouette needs sky behind it. */
.drvb.far {bottom:calc(100% - var(--gy) + 1%);height:12%;color:#93a6bd;opacity:.4}
.drvb.mid {bottom:calc(100% - var(--gy));height:31%;opacity:.86}
.drvb.near{top:var(--gy);height:9%;opacity:.7}
.drvb.fore{top:calc(var(--gy) + 6%);height:17%;opacity:.8}
/* the distance takes the hour's colour — a cool haze under a Montana sky and a
   warm one under a Calgary chinook is the difference between a layer that
   belongs in the picture and a layer sitting on it. Only the FAR band is tinted
   this way: atmospheric perspective is the one thing distance reliably does,
   and it is why the far band is pale where every other band is dark. */
.fx-warm .drvb.far{color:#c6a184}
.fx-cold .drvb.far{color:#9db4d0}
.fx-dusk .drvb.far{color:#b498b8}
/* a dead run does not get a lively road */
.stage.lost .drvb{opacity:.4}

/* ============================================================
   R23 — WEATHER

   "Maybe theres fog? snow? whatever."

   Three ingredients, and the cost of each is the reason it is shaped the way
   it is:

   · .wxwash — ONE full-screen gradient that is never transformed and never
     animated. No transform means no promotion means no layer of its own: it
     paints into the stage exactly once, when the weather changes. It is the
     colour of the day and it is effectively free.
   · .wx u  — one or two soft banks drifting sideways on a transform. ELLIPTICAL
     on purpose: a linear band has two hard edges and reads as a stripe ruled
     across the painting, where a radial one has no edge to find.
   · .wx i  — flakes, drizzle and grit, built exactly the way the dust motes
     above are built. Each is four pixels of element; a promoted layer costs
     what it covers, so thirty of these together cost less than one sheet.

   The kind is DERIVED from the scene and the run state, never rolled, so
   tools/shoot.js photographs the same weather twice and a scene lane comparing
   two renders is comparing two renders.
   ============================================================ */
.wxwash{position:absolute;inset:0;pointer-events:none}
.wx{position:absolute;inset:0;overflow:hidden;pointer-events:none;
  transition:transform .55s cubic-bezier(.3,0,.2,1)}
/* snow and grit lie down when the truck is under power. One transform on a
   container that is already composited — the particles are untouched, so
   nothing restarts and nothing pops back to the top of the screen. */
.wx.blow{transform:skewX(-15deg) scaleY(.94)}
.wx i{position:absolute;display:block;opacity:0;
  animation-timing-function:linear;animation-iteration-count:infinite}
.wx u{position:absolute;left:-13%;right:-13%;display:block;
  animation:wxdrift linear infinite alternate}
@keyframes wxdrift{from{transform:translate3d(-4.5%,0,0)}to{transform:translate3d(4.5%,0,0)}}

/* --- snow: Red Deer in November, the border in February ------------------- */
.w-snow{background:linear-gradient(180deg,rgba(206,226,248,.18) 0,
  rgba(190,214,240,.07) 46%,rgba(214,230,248,.15) 100%)}
.k-snow i{top:-9%;width:var(--sz);height:var(--sz);border-radius:50%;
  background:rgba(240,249,255,.94);animation-name:wxfall}
@keyframes wxfall{0%{opacity:0;transform:translate3d(0,0,0)}
  9%{opacity:.95}82%{opacity:.8}100%{opacity:0;transform:translate3d(-7vw,120vh,0)}}

/* --- whiteout: the last hour of the QE2, and you cannot see the border ---- */
.w-whiteout{background:linear-gradient(180deg,rgba(230,241,252,.36) 0,
  rgba(216,232,248,.26) 50%,rgba(226,238,250,.34) 100%)}
.k-whiteout u{background:radial-gradient(74% 116% at 46% 50%,rgba(238,246,255,.5) 0,
  rgba(232,242,254,.24) 44%,rgba(230,241,253,0) 78%)}
.k-whiteout u.a{top:30%;height:30%;animation-duration:13s}
.k-whiteout u.b{top:54%;height:28%;animation-duration:19s;animation-direction:alternate-reverse}
.k-whiteout i{top:-9%;width:var(--sz);height:var(--sz);border-radius:50%;
  background:rgba(248,253,255,.96);animation-name:wxsquall}
@keyframes wxsquall{0%{opacity:0;transform:translate3d(24vw,0,0)}
  10%{opacity:1}84%{opacity:.85}100%{opacity:0;transform:translate3d(-118vw,64vh,0)}}

/* --- drizzle: Edmonton, any month with an R in it ------------------------- */
.w-drizzle{background:linear-gradient(180deg,rgba(150,178,208,.17) 0,
  rgba(130,158,188,.08) 55%,rgba(120,146,176,.15) 100%)}
.k-drizzle i{top:-11%;width:1px;height:var(--sz);animation-name:wxrain;
  background:linear-gradient(180deg,rgba(196,222,255,0),rgba(206,230,255,.8))}
@keyframes wxrain{0%{opacity:0;transform:translate3d(0,0,0)}
  10%{opacity:.85}90%{opacity:.6}100%{opacity:0;transform:translate3d(-5vw,124vh,0)}}

/* --- dust: Lethbridge, where the wind is a municipal service -------------- */
.w-dust{background:linear-gradient(180deg,rgba(224,180,112,.15) 0,
  rgba(206,160,96,.08) 55%,rgba(190,146,86,.19) 100%)}
.k-dust u.a{top:48%;height:30%;animation-duration:11s;
  background:radial-gradient(68% 112% at 52% 52%,rgba(228,192,132,.32) 0,
    rgba(220,180,120,.15) 46%,rgba(214,172,112,0) 78%)}
.k-dust i{width:var(--sz);height:var(--sz);border-radius:50%;top:38%;
  background:rgba(232,198,140,.8);animation-name:wxgrit}
@keyframes wxgrit{0%{opacity:0;transform:translate3d(26vw,0,0)}
  12%{opacity:.85}78%{opacity:.5}100%{opacity:0;transform:translate3d(-120vw,-9vh,0)}}

/* --- fog: the river valley at six in the morning -------------------------- */
.w-fog{background:linear-gradient(180deg,rgba(188,204,222,.22) 0,
  rgba(196,210,226,.12) 52%,rgba(176,192,210,.26) 100%)}
.k-fog u{background:radial-gradient(64% 110% at 44% 52%,rgba(204,218,234,.36) 0,
  rgba(202,216,232,.17) 46%,rgba(200,214,230,0) 78%)}
.k-fog u.a{top:38%;height:28%;animation-duration:21s}
.k-fog u.b{top:56%;height:26%;animation-duration:31s;animation-direction:alternate-reverse}

/* --- chinook: the arch, and the only warm wind in Canada ------------------ */
.w-chinook{background:linear-gradient(180deg,rgba(255,208,136,.17) 0,
  rgba(255,180,98,.07) 44%,rgba(255,206,150,.03) 100%)}
.k-chinook u.a{top:8%;height:28%;animation-duration:27s;
  background:radial-gradient(72% 118% at 50% 96%,rgba(255,222,166,.34) 0,
    rgba(255,206,140,.15) 42%,rgba(255,198,128,0) 74%)}

/* --- smoke: the province is on fire, and so are you ----------------------- */
.w-smoke{background:linear-gradient(180deg,rgba(216,124,46,.24) 0,
  rgba(180,98,44,.15) 50%,rgba(122,66,34,.24) 100%)}
.k-smoke u{background:radial-gradient(76% 116% at 48% 50%,rgba(226,146,66,.3) 0,
  rgba(198,116,52,.14) 46%,rgba(180,102,46,0) 78%)}
.k-smoke u.a{top:16%;height:24%;animation-duration:37s}
.k-smoke u.b{top:44%;height:22%;animation-duration:47s;animation-direction:alternate-reverse}
