
  /* ---------- boot screen: your car driving through a pixel town, no logo, no words ---------- */
  #bootscreen{position:fixed;inset:0;z-index:9999;overflow:hidden;
    background:linear-gradient(#F0EEE7 0 38%,#E8E5D8 38% 52%,#DEDACB 52% 100%);
    transition:opacity 220ms cubic-bezier(.22,.61,.36,1)}
  #bootscreen.hide{opacity:0;pointer-events:none}
  #bootscreen .bsk{position:absolute;left:0;right:0;background-repeat:repeat-x;background-position:0 100%;image-rendering:pixelated}
  #bootscreen .bfar{bottom:170px;height:150px;background-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='480' height='150' shape-rendering='crispEdges'><rect x='0' y='80' width='52' height='70' fill='%23C7C3B5'/><rect x='58' y='46' width='38' height='104' fill='%23C7C3B5'/><rect x='100' y='92' width='60' height='58' fill='%23C7C3B5'/><rect x='166' y='30' width='44' height='120' fill='%23C7C3B5'/><rect x='216' y='66' width='70' height='84' fill='%23C7C3B5'/><rect x='292' y='54' width='40' height='96' fill='%23C7C3B5'/><rect x='338' y='84' width='58' height='66' fill='%23C7C3B5'/><rect x='402' y='38' width='50' height='112' fill='%23C7C3B5'/><rect x='458' y='90' width='22' height='60' fill='%23C7C3B5'/></svg>");animation:bsc 22s linear infinite}
  #bootscreen .bnear{bottom:170px;height:130px;background-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='480' height='130' shape-rendering='crispEdges'><rect x='10' y='84' width='64' height='46' fill='%239AA6BA'/><rect x='18' y='92' width='48' height='6' fill='%23F0EEE7' opacity='0.9'/><rect x='18' y='114' width='48' height='6' fill='%23F0EEE7' opacity='0.35'/><rect x='84' y='60' width='40' height='70' fill='%239AA6BA'/><rect x='92' y='68' width='24' height='6' fill='%23F0EEE7' opacity='0.35'/><rect x='92' y='90' width='24' height='6' fill='%23F0EEE7' opacity='0.35'/><rect x='92' y='112' width='24' height='6' fill='%23F0EEE7' opacity='0.9'/><rect x='130' y='90' width='74' height='40' fill='%239AA6BA'/><rect x='138' y='98' width='58' height='6' fill='%23F0EEE7' opacity='0.35'/><rect x='214' y='66' width='50' height='64' fill='%239AA6BA'/><rect x='222' y='74' width='34' height='6' fill='%23F0EEE7' opacity='0.9'/><rect x='222' y='96' width='34' height='6' fill='%23F0EEE7' opacity='0.35'/><rect x='270' y='82' width='66' height='48' fill='%239AA6BA'/><rect x='278' y='90' width='50' height='6' fill='%23F0EEE7' opacity='0.35'/><rect x='278' y='112' width='50' height='6' fill='%23F0EEE7' opacity='0.35'/><rect x='346' y='52' width='44' height='78' fill='%239AA6BA'/><rect x='354' y='60' width='28' height='6' fill='%23F0EEE7' opacity='0.35'/><rect x='354' y='82' width='28' height='6' fill='%23F0EEE7' opacity='0.9'/><rect x='354' y='104' width='28' height='6' fill='%23F0EEE7' opacity='0.35'/><rect x='396' y='88' width='62' height='42' fill='%239AA6BA'/><rect x='404' y='96' width='46' height='6' fill='%23F0EEE7' opacity='0.9'/></svg>");animation:bsc 9s linear infinite}
  @keyframes bsc{to{background-position-x:-480px}}
  #bootscreen .bcloud{position:absolute;left:0;height:14px;background:#fff;opacity:.85;animation:bcl 40s linear infinite}
  #bootscreen .bcloud::before{content:"";position:absolute;left:20%;top:-10px;width:45%;height:12px;background:#fff}
  #bootscreen .bcloud.c1{top:17%;width:96px;animation-delay:-4s}
  #bootscreen .bcloud.c2{top:28%;width:64px;animation-delay:-19s}
  #bootscreen .bcloud.c3{top:10%;width:52px;animation-delay:-31s}
  @keyframes bcl{from{transform:translateX(100vw)}to{transform:translateX(-140px)}}
  #bootscreen .broad{position:absolute;left:0;right:0;bottom:0;height:170px;background:#33465F;border-top:6px solid #141C27}
  #bootscreen .broad::before{content:"";position:absolute;left:0;right:0;top:0;height:10px;background:#5C7AA0}
  #bootscreen .bdash{position:absolute;left:0;right:0;height:5px;background-repeat:repeat-x;animation:bdsh .52s linear infinite}
  #bootscreen .bdash.a{bottom:86px;background-image:repeating-linear-gradient(90deg,#C9A227 0 36px,transparent 36px 72px)}
  #bootscreen .bdash.b{bottom:22px;background-image:repeating-linear-gradient(90deg,#243247 0 36px,transparent 36px 96px);animation-duration:.7s}
  @keyframes bdsh{to{background-position-x:-72px}}
  #bootscreen .bcarwrap{position:absolute;left:0;right:0;bottom:118px;display:flex;justify-content:center}
  #bootscreen .bshadow{position:absolute;bottom:-8px;width:210px;height:14px;border-radius:50%;background:#000;opacity:.3;animation:bshd .66s ease-in-out infinite}
  #bootscreen .bcar{width:230px;height:120px;animation:bbob .66s ease-in-out infinite}
  #bootcar{width:100%;height:100%;object-fit:contain;object-position:bottom;image-rendering:pixelated}
  @keyframes bbob{0%,100%{transform:translateY(0) rotate(0)}30%{transform:translateY(-4px) rotate(-.9deg)}55%{transform:translateY(-1px)}}
  @keyframes bshd{0%,100%{transform:scaleX(1);opacity:.32}30%{transform:scaleX(.95);opacity:.24}}
  #bootscreen .bdots{position:absolute;left:0;right:0;bottom:44px;display:flex;justify-content:center;gap:6px}
  #bootscreen .bdots i{width:12px;height:12px;background:#C9A227;border:2px solid #141C27;opacity:.25;animation:bdt 1.1s steps(1) infinite}
  #bootscreen .bdots i:nth-child(2){animation-delay:.37s}#bootscreen .bdots i:nth-child(3){animation-delay:.74s}
  @keyframes bdt{0%{opacity:1}33%{opacity:.25}100%{opacity:.25}}
  /* only the fail-safe notes ever show text here */
  .boottip{position:absolute;left:0;right:0;bottom:14px;text-align:center;font-family:'Jersey 25','Silkscreen',monospace;font-size:18px;color:#33465F;letter-spacing:1px;opacity:0}
  @media (prefers-reduced-motion: reduce){#bootscreen *{animation:none!important}}
  :root{
    /* Field-guide device tokens. The old names are kept as the working
       vocabulary of this file and REPOINTED at the new palette, so seven
       thousand lines of rules inherit the restyle without being touched:
       ink = LCD near-black (all borders), navy = steel plastic, red = alert,
       gold = amber, panel/panel2/line = the readout, wht = readout text. */
    --red:#D8402F; --red-dk:#98261A; --alert:#D8402F; --paper:#F0EEE7; --paper2:#D9D6CC;
    --paper-edge:#C8C4B8; --paper-dot:#D9D6CC; --paper-wash:rgba(0,0,0,.02);
    --paper-overlay:rgba(230,226,214,.97);
    --ink:#141C27; --navy:#5C7AA0; --navy-dk:#33465F; --gold:#C9A227; --gold-dk:#8F7318;
    --steel-lit:#8CA6C4;
    --panel:#141C27; --panel2:#1E2833; --line:#2E3A48; --wht:#E4E7EC;
    --lcd-dim:#8A97A6;
    /* Dark mode only ever recolors the device's CASING (paper/paper2/edge)
       and the ink that reads against it — panel/navy/gold/red etc. are
       already the LCD-readout tones and stay identical in both themes.
       --ink-fixed is the escape hatch for the handful of spots where ink is
       doing "guaranteed-dark text/border on a permanently-bright accent"
       (a gold chip, the nav bar's own always-dark background) rather than
       "text on the casing" — those must NOT flip just because the casing
       does, so they read var(--ink-fixed) instead of var(--ink). */
    --ink-fixed:#141C27;
    /* Rarity per the spec sheet — the tier recolors the whole card frame.
       (Spec's rare is teal, not the earlier blue — flagged in the handoff.) */
    --c-common:#B3A98D; --c-uncommon:#7FA05C; --c-rare:#5FA89B; --c-epic:#8C6FB5;
    --c-legendary:#C9A227; --c-mythic:#D8402F; --c-grail:#E4DDFF;
    /* the reveal overlay's accent — folded into alert red, no pink in this world */
    --pink:#D8402F; --pink-dk:#98261A;
    --font-display:'Jersey 25','Press Start 2P',monospace;
    /* one font everywhere, by request — utility/prose used to be Silkscreen
       and Pixelify Sans respectively; both now alias straight to the same
       display face so every existing var(--font-utility)/var(--font-prose)
       usage in the file picks it up without hunting down each call site. */
    --font-utility:var(--font-display);
    --font-prose:var(--font-display);
    /* muted long-form text on the casing — .empty, .addnote and friends.
       Plain --navy (#5C7AA0) measured 3.8:1 on cream, under AA */
    --muted:#33465F;
    /* a faint 2x2 checkerboard, just enough grain to read as a pixel surface
       rather than a flat CSS fill — kept subtle on purpose; the real dithering
       (--dseam) is concentrated at the band seams below, where a sprite would
       actually blend two tones together */
    --dither:repeating-conic-gradient(rgba(255,255,255,.12) 0% 25%,rgba(0,0,0,.05) 0% 50%);
    --dither-dk:repeating-conic-gradient(rgba(0,0,0,.16) 0% 25%,rgba(255,255,255,.03) 0% 50%);
    /* a stronger, tighter checker used ONLY in a thin strip right at a
       color-band seam — real pixel art dithers to fake a blend between two
       tones, it doesn't grain-texture flat color */
    --dseam:repeating-conic-gradient(rgba(255,255,255,.4) 0% 25%,rgba(0,0,0,.3) 0% 50%);
    /* hard-stepped shading bands (no smooth interpolation) — a sprite lit
       from the top-left has 4 discrete tones, not a gradient */
    --band:linear-gradient(180deg,
      rgba(255,255,255,.12) 0%,rgba(255,255,255,.12) 20%,
      rgba(255,255,255,.04) 20%,rgba(255,255,255,.04) 45%,
      rgba(0,0,0,.05) 45%,rgba(0,0,0,.05) 72%,
      rgba(0,0,0,.15) 72%,rgba(0,0,0,.15) 100%);
    /* the small bright pixel every metal/enamel badge icon gets in its
       top-left corner — a specular glint, not a gradient highlight */
    --glint:radial-gradient(circle 3px at 7px 7px,rgba(255,255,255,.4),transparent 70%);
    /* a real 2-step pixel staircase corner, not a smooth 45° chamfer —
       the difference between "notched" and actually reading as pixel art */
    --notch:polygon(0 4px,2px 4px,2px 2px,4px 2px,4px 0,
      calc(100% - 4px) 0,calc(100% - 4px) 2px,calc(100% - 2px) 2px,calc(100% - 2px) 4px,100% 4px,
      100% calc(100% - 4px),calc(100% - 2px) calc(100% - 4px),calc(100% - 2px) calc(100% - 2px),calc(100% - 4px) calc(100% - 2px),calc(100% - 4px) 100%,
      4px 100%,4px calc(100% - 2px),2px calc(100% - 2px),2px calc(100% - 4px),0 calc(100% - 4px));
  }
  /* Dark mode — same field-guide device, a black-cased night edition instead
     of the cream one. Only the casing + the ink that reads against it move;
     the LCD-readout tones (panel/navy/gold/red/rarity colors) were already
     dark-surface-safe and stay put in both themes on purpose. Applied via
     data-theme so an early inline script (see <head>) can set it before
     first paint — a class/attribute flips instantly with no flash, where a
     media-query-only approach couldn't also respect an explicit in-app
     toggle over the OS setting. */
  /* Season-medal backgrounds — earned skins for the paper casing, light mode
     only (dark mode is its own committed look and wins when both are set).
     Each recolors the same three casing tokens dark mode already proves are
     safe to move. */
  :root:not([data-theme="dark"])[data-bg="sunset"]{--paper:#F4E4D4;--paper2:#E8CDB0;--paper-dot:#E8CDB0}
  :root:not([data-theme="dark"])[data-bg="forest"]{--paper:#E7EDDF;--paper2:#CFDCC2;--paper-dot:#CFDCC2}
  :root:not([data-theme="dark"])[data-bg="midnight"]{--paper:#E2E6F0;--paper2:#C8CFE2;--paper-dot:#C8CFE2}
  :root:not([data-theme="dark"])[data-bg="champagne"]{--paper:#F5EBD7;--paper2:#EADBB6;--paper-dot:#EADBB6}
  /* CLASSIC's own backdrop is a generated pixel-art scene rather than flat
     paper — a named BACKGROUND preset above still means "no photo, just
     tinted paper" (data-bg set → --bg-photo stays undefined → body::before
     falls back to none). Summer swaps the scene while light mode is CLASSIC;
     dark mode (below) overrides both, same precedence as every other casing
     token in this file. */
  :root:not([data-bg]){--bg-photo:url('bg-light-v2.jpg');--bg-scrim:rgba(240,238,231,.6)}
  :root:not([data-bg]):not([data-theme="dark"])[data-season="summer"]{--bg-photo:url('bg-summer-v2.jpg');--bg-scrim:rgba(248,236,203,.55)}
  /* Explicit BACKGROUND picks in Settings — GARAGE and SUMMER lock in one
     photo regardless of the real-world season, replacing the old SEASONAL
     option (a flat color tint with no art at all, which read as a downgrade
     once CLASSIC got a real photo — "the old shitty one"). Dark mode still
     wins for GARAGE, which has its own matching night version; SUMMER has
     no dark variant, so picking it means "show me the beach," full stop. */
  :root[data-bg="garage"]:not([data-theme="dark"]){--bg-photo:url('bg-light-v2.jpg');--bg-scrim:rgba(240,238,231,.6)}
  :root[data-bg="garage"][data-theme="dark"]{--bg-photo:url('bg-dark-v2.jpg');--bg-scrim:rgba(27,31,39,.62)}
  :root[data-bg="summer"]{--bg-photo:url('bg-summer-v2.jpg');--bg-scrim:rgba(248,236,203,.55)}
  /* Seasonal accent color only — --seasonc tints the Home season banner and
     its Trophy Room. No other seasonal art: a pennant-garland/beach-prop
     decoration line was tried across several builds and pulled entirely per
     explicit feedback ("bullshit and ugly... remove all of it") — do not
     reintroduce hung/pasted-on seasonal icons. */
  :root[data-season="winter"]{--seasonc:#6FA3C8}
  :root[data-season="spring"]{--seasonc:#7FA05C}
  :root[data-season="summer"]{--seasonc:#C9A227}
  :root[data-season="fall"]{--seasonc:#C0713A}
  :root[data-theme="dark"]{
    --paper:#1B1F27; --paper2:#242A35; --paper-edge:#0F1319;
    --paper-dot:#2A303C; --paper-wash:rgba(255,255,255,.025);
    --paper-overlay:rgba(27,31,39,.97);
    --ink:#E4E7EC;
    --muted:#A6B2C8;
  }
  :root:not([data-bg])[data-theme="dark"]{--bg-photo:url('bg-dark-v2.jpg');--bg-scrim:rgba(27,31,39,.62)}
  *{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
  /* A game, not a document: long-press select / copy (and the iOS callout
     menu that comes with it) is off everywhere except real text fields. */
  html,body{-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
  input,textarea,[contenteditable="true"]{-webkit-user-select:text;user-select:text;-webkit-touch-callout:default}
  img,canvas,video{-webkit-user-drag:none;-webkit-touch-callout:none}
  html,body{touch-action:pan-y;-ms-touch-action:pan-y}  /* no pinch/double-tap page zoom */
  .viewport{touch-action:none}                           /* pinch belongs to the camera */
  .camzoom{position:absolute;bottom:12px;left:12px;z-index:3;background:rgba(15,19,30,.85);
    border:2px solid var(--ink);color:var(--wht);font-size:12.6px;padding:5px 8px 4px;
    opacity:0;transition:opacity .2s}
  .camzoom.on{opacity:1}
  #combochip{position:absolute;top:34px;left:12px;z-index:3;background:rgba(15,19,30,.85);
    border:2px solid var(--gold);color:var(--gold);font-size:11.4px;padding:5px 8px 4px;
    pointer-events:none;display:none;letter-spacing:.1em}
  #multibox{position:absolute;inset:0;z-index:3;pointer-events:none}
  /* scan option toggles — top-right of the viewfinder */
  #scantoggles{position:absolute;top:10px;right:10px;z-index:5;display:flex;gap:5px}
  /* full-screen camera: the viewfinder takes the whole screen, the header/
     nav/title get out of the way, and the capture row floats over the feed
     instead of sitting in normal document flow below it. */
  body.fs-scan{overflow:hidden}
  body.fs-scan header,body.fs-scan nav,body.fs-scan #v-scan>h1.title{display:none}
  body.fs-scan #v-scan.on{padding:0;margin:0;transform:none!important}
  /* targets the #viewport ID, not just .viewport — #v-scan .viewport's own
     max-height:calc(100dvh - 360px) rule outranks a class-only selector on
     ID-vs-class specificity, so a plain ".viewport" override here loses and
     the camera stays capped at the small windowed height even in fullscreen */
  body.fs-scan #viewport{position:fixed!important;inset:0!important;z-index:70;
    width:100vw!important;height:100dvh!important;max-height:none!important;min-height:0!important;
    border:none;outline:none;margin:0;aspect-ratio:auto!important}
  body.fs-scan .scanrow{position:fixed;left:0;right:0;bottom:0;z-index:71;
    margin:0;padding:16px 14px calc(16px + env(safe-area-inset-bottom));
    background:linear-gradient(0deg,rgba(0,0,0,.6),transparent);justify-content:center}
  /* hardening, not the primary fix (that's #fsexit below): push the in-frame
     toggles clear of a notch/dynamic-island/status-bar area, and put them
     unambiguously above everything else in the fullscreen layer */
  body.fs-scan #scantoggles{top:calc(10px + env(safe-area-inset-top));z-index:80;pointer-events:auto}
  body.fs-scan #camopts{z-index:80;pointer-events:auto}
  /* #fsexit is the one reliable way out now — showing the small in-frame
     toggle too just invites tapping the one that might not respond */
  body.fs-scan #tog-fs{display:none}
  #fsexit{position:fixed;top:calc(10px + env(safe-area-inset-top));left:10px;z-index:200;
    background:rgba(15,19,30,.88);border:2px solid rgba(230,226,214,.6);color:#fff;
    font-family:var(--font-utility);font-size:11.4px;letter-spacing:1px;
    padding:8px 10px 7px;border-radius:3px;cursor:pointer}
  #tog-fs[aria-pressed="true"]{background:var(--red);border-color:#ff9a8c;color:#fff}
  .stog.ico{font-size:15.6px;padding:4px 8px 3px;line-height:1.1}
  .stog.dim{opacity:.45}
  /* camera tools drawer */
  #camopts{position:absolute;top:40px;right:10px;z-index:6;width:170px;
    background:rgba(10,13,20,.96);border:2px solid rgba(230,226,214,.4);border-radius:3px;
    padding:6px;display:flex;flex-direction:column;gap:4px}
  .copt{display:flex;align-items:center;justify-content:space-between;gap:6px;width:100%;
    background:none;border:0;color:#c9d2e6;font-family:inherit;font-size:9.6px;
    letter-spacing:1px;padding:9px 8px;cursor:pointer;text-align:left;line-height:1.4}
  .copt i{font-style:normal;color:#8e98b8;font-size:9.6px;flex:none}
  .copt[aria-pressed="true"]{background:var(--red);color:#fff}
  .copt[aria-pressed="true"] i{color:#fff}
  .copt.dim{opacity:.45}
  .copt:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}
  /* brief confirmation when the shot is framed for you */
  #aimtoast{position:absolute;left:50%;top:12px;transform:translateX(-50%);z-index:6;
    background:rgba(10,13,20,.92);border:2px solid #5dff9b;color:#5dff9b;
    font-size:9.6px;letter-spacing:1px;padding:6px 9px 5px;border-radius:3px;
    pointer-events:none;white-space:nowrap}
  /* aim + quality assist strip, along the bottom of the viewfinder */
  /* Opaque, not translucent: the camcorder HUD's timer and label sit directly
     underneath and were reading through the panel. */
  #assist{position:absolute;left:8px;right:8px;bottom:8px;z-index:4;pointer-events:none;
    background:rgba(10,13,20,.95);border:2px solid rgba(230,226,214,.30);border-radius:3px;
    padding:6px 8px 5px;display:flex;flex-direction:column;gap:5px}
  #assist .asrow{display:flex;align-items:center;gap:6px}
  #assist #astip{font-size:10.2px;letter-spacing:1px;color:#fff;line-height:1.5}
  #assist #astip.ok{color:#5dff9b}
  #assist #astip.warn{color:#ffd166}
  /* 5.5px was unreadable in a pixel font — the glyphs mush together at that
     size. These two labels are the whole point of the row, so they get enough
     size to actually be read at arm's length. */
  #assist .aslbl{font-size:9.6px;letter-spacing:1px;color:#8e98b8;flex:none;line-height:1.6}
  #assist .asbar{flex:1;min-width:0;height:6px;background:rgba(255,255,255,.16);
    border:2px solid rgba(0,0,0,.55);overflow:hidden}
  #assist .asbar i{display:block;height:100%;width:0%;background:#ff6b5a;
    transition:width .18s linear,background .18s linear}
  #assist #asverdict{font-size:9.6px;letter-spacing:1px;color:#c9d2e6;flex:none;min-width:40px;
    text-align:right;line-height:1.6}
  /* framing grid — thirds, drawn over the preview and never in the photo */
  #camgrid{position:absolute;inset:0;z-index:3;pointer-events:none}
  #camgrid i,#camgrid b{position:absolute;background:rgba(255,255,255,.34);
    box-shadow:0 0 0 1px rgba(8,10,16,.35)}
  #camgrid i{top:0;bottom:0;width:1px}
  #camgrid i:nth-of-type(1){left:33.333%} #camgrid i:nth-of-type(2){left:66.666%}
  #camgrid b{left:0;right:0;height:1px}
  #camgrid b:nth-of-type(1){top:33.333%} #camgrid b:nth-of-type(2){top:66.666%}
  .stog{background:rgba(15,19,30,.82);border:2px solid rgba(230,226,214,.55);
    color:#c9d2e6;font-family:inherit;font-size:10.2px;letter-spacing:1px;
    padding:6px 7px 5px;cursor:pointer;line-height:1;border-radius:3px}
  .stog[aria-pressed="true"]{background:var(--red);border-color:#ff9a8c;color:#fff;
    box-shadow:0 0 0 2px rgba(255,154,140,.25)}
  .stog:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  /* auto-scan countdown ring, centred under the reticle */
  #autoring{position:absolute;left:50%;bottom:12px;transform:translateX(-50%);z-index:4;
    background:rgba(15,19,30,.85);border:2px solid var(--red);color:#fff;font-size:10.2px;
    letter-spacing:1px;padding:5px 8px 4px;border-radius:3px;display:flex;
    align-items:center;gap:6px}
  #autoring i{display:block;width:44px;height:6px;background:rgba(255,255,255,.22);
    border:2px solid rgba(0,0,0,.5);position:relative;overflow:hidden}
  #autoring i::after{content:"";position:absolute;inset:0 auto 0 0;width:var(--p,0%);
    background:var(--red);transition:width .12s linear}
  /* ---------- backdrop ----------
     CLASSIC now shows a generated pixel-art scene (garage by default, beach
     garage in summer, its own night version in dark mode) pinned behind the
     page as body::before rather than scrolling with it — background-attachment:
     fixed is the obvious way to pin a body background, but iOS Safari has
     never implemented it reliably, so a fixed pseudo-element sized to the
     viewport is the actual cross-platform way to get "wallpaper behind
     scrolling content" on a PWA that has to work on the sideloaded iOS shell
     too. A tinted scrim (--bg-scrim, one per theme/season above) rides along
     with the photo in the same background-image so ink text drawn straight
     on the casing (not inside an opaque card) stays readable over it. Named
     BACKGROUND presets in Settings still render as flat tinted paper, no
     photo — see the [data-bg] guards above. */
  body::before{content:"";position:fixed;inset:0;z-index:-2;
    background-image:linear-gradient(var(--bg-scrim,transparent),var(--bg-scrim,transparent)),
      var(--bg-photo,none);
    background-size:cover,cover;background-position:center top,center top;
    background-repeat:no-repeat,no-repeat}
  body{background-color:var(--paper);color:var(--ink);
    font-family:var(--font-utility);font-weight:700;font-size:14.4px;line-height:1.6;
    letter-spacing:.02em;
    min-height:100vh;min-height:100dvh;padding-bottom:calc(84px + env(safe-area-inset-bottom))}
  /* the old dot-screen paper texture is dead weight wherever a photo is
     showing — the fixed layer in body::before fully covers it every time —
     so it only renders for an earned medal tint, where there is no photo. */
  :root[data-bg]:not([data-bg="garage"]):not([data-bg="summer"]) body{
    background-image:
      radial-gradient(var(--paper-dot) 1px,transparent 1px),
      repeating-linear-gradient(135deg,
        transparent 0 6px, var(--paper-wash) 6px 12px);
    background-size:8px 8px,auto}
  /* ---------- device discipline, enforced globally ----------
     Non-negotiable #1: nothing in this world has a rounded corner. One rule
     beats policing hundreds; !important is the point. Buttons never ease. */
  *,*::before,*::after{border-radius:0!important}
  button{transition:none!important}
  button{font-family:inherit;cursor:pointer}
  /* pixel bevel helpers */
  /* ---------- THE bevel ----------
     One primitive builds every button, chip, plate and frame: 2px lit edge
     top/left, 2px shadow edge bottom/right, and a HARD 3px drop — depth made
     only of offset rectangles, never blur. Pressing flips the light source
     and drops the plastic 2px down-right, instantly, no easing. */
  .px{border:2px solid var(--ink);box-shadow:3px 3px 0 rgba(0,0,0,.35)}
  /* Buttons are keycaps: flat plastic top, a 2px highlight where the light
     hits, a hard 3px wall underneath, and a press that travels the exact wall
     height. No gloss gradients, no dither — the same stamped language as the
     catch-screen actions, at standard size. The wall is ink-FIXED because it
     is the key's own dark side, not casing ink — it must not flip light in
     dark mode. */
  .btn{display:inline-block;border:2px solid var(--ink-fixed);position:relative;
       box-shadow:0 3px 0 var(--ink-fixed),
                  inset 0 2px 0 rgba(255,255,255,.30),inset 0 -3px 0 rgba(0,0,0,.28);
       padding:10px 12px 9px;font-size:16.8px;font-family:var(--font-utility);
       color:#fff;background:var(--navy);
       text-transform:uppercase;letter-spacing:.14em;user-select:none;
       text-shadow:1px 1px 0 rgba(0,0,0,.4)}
  .btn:active{transform:translateY(3px);
       box-shadow:0 0 0 var(--ink-fixed),inset 0 2px 0 rgba(0,0,0,.22)}
  .btn.red{background-color:var(--red)}
  .btn.gold{background-color:var(--gold);color:var(--ink-fixed);text-shadow:none}

  /* ---------- header ---------- */
  header{position:sticky;top:0;z-index:30;background:var(--red);border-bottom:4px solid var(--ink);
    padding:calc(8px + env(safe-area-inset-top)) 12px 8px;display:flex;align-items:center;gap:8px;
    box-shadow:inset 0 2px 0 rgba(255,255,255,.18),inset 0 -3px 0 rgba(0,0,0,.2),0 3px 0 rgba(0,0,0,.25);
    background-image:var(--band),var(--dither),repeating-linear-gradient(0deg,rgba(255,255,255,.035) 0 2px,transparent 2px 4px);
    background-size:100% 100%,6px 6px,auto}
  .cart{flex:1;background:var(--navy-dk);border:3px solid var(--ink);
    box-shadow:inset 2px 2px 0 rgba(255,255,255,.16),inset -2px -2px 0 rgba(0,0,0,.4),2px 2px 0 rgba(0,0,0,.3);
    background-image:var(--glint),var(--band),var(--dither),repeating-linear-gradient(0deg,rgba(255,255,255,.035) 0 2px,transparent 2px 4px);
    background-size:100% 100%,100% 100%,6px 6px,auto;
    background-repeat:no-repeat,no-repeat,repeat,repeat;
    clip-path:var(--notch);
    color:var(--wht);padding:7px 10px 6px;font-size:18px;font-family:var(--font-display);
    letter-spacing:.5px;display:flex;align-items:center;gap:6px;
    position:relative;overflow:hidden}
  .cart::before{content:"›";color:var(--gold);font-family:var(--font-utility)}
  .cart::after{content:"";position:absolute;inset:0;pointer-events:none;
    background:linear-gradient(115deg,transparent 38%,rgba(255,255,255,.55) 50%,transparent 62%);
    animation:holosheen 7s steps(16) infinite}
  .hbtn{width:34px;height:34px;background:var(--navy-dk);border:3px solid var(--ink);color:var(--wht);
    font-size:18px;display:flex;align-items:center;justify-content:center;cursor:pointer;
    box-shadow:inset 2px 2px 0 rgba(255,255,255,.16),inset -2px -2px 0 rgba(0,0,0,.4),2px 2px 0 rgba(0,0,0,.3);
    background-image:var(--glint),var(--band),var(--dither),repeating-linear-gradient(0deg,rgba(255,255,255,.035) 0 2px,transparent 2px 4px);
    background-size:100% 100%,100% 100%,6px 6px,auto;
    background-repeat:no-repeat,no-repeat,repeat,repeat;
    clip-path:var(--notch);
    position:relative;overflow:hidden}
  .hbtn::after{content:"";position:absolute;inset:0;pointer-events:none;
    background:linear-gradient(115deg,transparent 40%,rgba(255,255,255,.22) 50%,transparent 60%);
    animation:holosheen 44s steps(12) infinite}
  .hbtn:active{box-shadow:inset -2px -2px 0 rgba(255,255,255,.16),inset 2px 2px 0 rgba(0,0,0,.4);
    transform:translate(1px,1px)}
  /* the header's other entry point: smaller than the icon buttons on
     purpose, a plain text pill in the season's own color rather than a
     drawn icon — opens the Summer Shop */
  /* a plain icon, not a text pill — the wordmark bar already carries two
     different text treatments (CARBIX + GN EDITION); a third label-style
     button crammed onto the end of that same line was the actual clutter */
  .seasonicon{flex:none;width:26px;height:26px;background:var(--navy-dk);border:2px solid var(--ink);
    display:flex;align-items:center;justify-content:center;cursor:pointer;
    box-shadow:inset 1px 1px 0 rgba(255,255,255,.16),inset -1px -1px 0 rgba(0,0,0,.4),1px 1px 0 rgba(0,0,0,.3)}
  .seasonicon:active{transform:translate(1px,1px);
    box-shadow:inset -1px -1px 0 rgba(255,255,255,.16),inset 1px 1px 0 rgba(0,0,0,.4)}
  /* settings moved off the packed header onto Home — labeled, and pulsing a
     gold ring so a brand-new spotter finds it without having to be told */
  .settingshome{display:flex;align-items:center;justify-content:center;gap:7px;width:100%;
    background:var(--navy);border:3px solid var(--ink);color:#fff;font-family:var(--font-utility);
    font-weight:700;font-size:13.2px;letter-spacing:1.5px;padding:11px;margin-bottom:16px;cursor:pointer;
    box-shadow:inset 2px 2px 0 rgba(255,255,255,.16),inset -2px -2px 0 rgba(0,0,0,.4),3px 3px 0 rgba(0,0,0,.3);
    animation:settingspulse 1.8s steps(9) infinite}
  .settingshome:active{transform:translate(2px,2px);
    box-shadow:inset -2px -2px 0 rgba(255,255,255,.16),inset 2px 2px 0 rgba(0,0,0,.4)}
  @keyframes settingspulse{
    0%,60%,100%{box-shadow:inset 2px 2px 0 rgba(255,255,255,.16),inset -2px -2px 0 rgba(0,0,0,.4),
      3px 3px 0 rgba(0,0,0,.3),0 0 0 0 rgba(201,162,39,.55)}
    30%{box-shadow:inset 2px 2px 0 rgba(255,255,255,.16),inset -2px -2px 0 rgba(0,0,0,.4),
      3px 3px 0 rgba(0,0,0,.3),0 0 0 5px rgba(201,162,39,0)}}
  @media (prefers-reduced-motion: reduce){.settingshome{animation:none}}
  @media (prefers-reduced-motion: reduce){.cart::after,.hbtn::after{content:none}}

  main{max-width:560px;margin:0 auto;padding:18px 14px}
  .view{display:none}.view.on{display:block;animation:fadein 100ms steps(2)}
  @keyframes fadein{from{opacity:0;transform:translateY(4px)}}
  h1.title{font-family:var(--font-display);font-size:48px;font-weight:400;letter-spacing:0;line-height:1;display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:14px}
  .status{background-color:var(--navy);border:3px solid var(--ink);color:var(--wht);font-size:11.4px;
    padding:6px 9px 5px;letter-spacing:1px;
    box-shadow:inset 2px 2px 0 rgba(255,255,255,.16),inset -2px -2px 0 rgba(0,0,0,.4);
    background-image:var(--glint),var(--band),var(--dither);
    background-size:100% 100%,100% 100%,6px 6px;background-repeat:no-repeat,no-repeat,repeat;
    clip-path:var(--notch);position:relative}
  /* the rotating field-tip text lands here — cap it so a long line truncates
     instead of wrapping the chip and pushing the whole viewfinder down */
  #scanready{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:56vw;display:inline-block;vertical-align:bottom}
  .status::before{content:"▪ ";color:var(--gold)}

  /* ---------- IDENTIFY REVEAL (scan HUD -> stat callouts -> card mint) ---------- */
  /* Race identify: name types out over the viewfinder, no card, no stats —
     built to be read in the time it takes to lower the phone toward the next car. */
  .racetype{position:absolute;left:0;right:0;bottom:14%;z-index:9;
    display:flex;flex-direction:column;align-items:center;gap:4px;
    padding:0 8%;pointer-events:none;
    opacity:0;transform:translateY(8px);transition:none}
  .racetype.in{opacity:1;transform:none}
  .racetype.out{opacity:0;transform:translateY(-6px)}
  .racetype .rtrank{font-family:var(--font-display);font-size:11.4px;
    color:var(--gold);text-shadow:1px 1px 0 #000;letter-spacing:2px}
  .racetype .rtname{font-family:var(--font-display);font-size:18px;color:#fff;
    text-shadow:2px 2px 0 #000;text-align:center;white-space:nowrap;overflow:hidden;
    max-width:100%;text-overflow:clip}
  .racetype .rtcaret{display:inline-block;width:8px;height:13px;background:var(--pink);
    margin-left:2px;vertical-align:-2px;opacity:0}
  .racetype.typing .rtcaret{opacity:1;animation:rtblink .9s step-end infinite}
  @keyframes rtblink{50%{opacity:0}}
  .revwrap{position:absolute;inset:0;z-index:8;pointer-events:none;overflow:hidden;
    font-family:var(--font-display)}
  /* the analysed frame, frozen under the readout so the stats sit on the photo
     they describe rather than on a live camera that has already moved */
  .revshot{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
    z-index:0;display:block}
  /* This whole file otherwise stands on hard stepped motion (buttons never
     ease, sprite shading is banded, not gradiented) — steps() reads as
     "pixel art" for a one-shot state change. It reads as broken here instead:
     these four transitions are the visual output of a CONTINUOUS live signal
     (camera position, a car moving, a hand holding the phone), and a stepped
     transition discretizes continuous motion into a handful of visible
     teleports — the exact complaint that reported this as "buggy." A real
     ease is the correct choice for tracking something real-time, not a
     stylistic compromise. */
  .revwrap.dim::before{content:"";position:absolute;inset:0;z-index:1;background:rgba(8,10,16,.72);
    transition:opacity 220ms cubic-bezier(.22,.61,.36,1)}
  /* The four corners start at the frame edge, then travel inward and close on the
     car the AI actually found — so the reticle reads as locking onto THIS vehicle
     rather than sitting in a fixed decorative rectangle. They live in a full-size
     layer so the same transform can zoom them and the photo together, in step. */
  .revbox{position:absolute;inset:0;z-index:2;
    transition:transform 380ms cubic-bezier(.22,.61,.36,1)}
  /* Same reticle on the live viewfinder. It tracks whatever the detector can see
     before you've pressed anything — and never zooms; the push-in is the
     identify sequence's job, not the preview's. */
  .revbox.live{z-index:4;pointer-events:none}
  .revbox.live .revcnr{border-color:rgba(150,200,255,.75);width:20px;height:20px;
    transition:top 320ms cubic-bezier(.22,.61,.36,1),left 320ms cubic-bezier(.22,.61,.36,1),
      right 320ms cubic-bezier(.22,.61,.36,1),bottom 320ms cubic-bezier(.22,.61,.36,1),
      border-color .25s ease,width .2s ease,height .2s ease}
  .revbox.live.lock .revcnr{border-color:#5dff9b;
    filter:drop-shadow(2px 2px 0 rgba(0,0,0,.5))}
  #scancross.cs1 .revcnr{border-color:rgba(201,162,39,.85)}
  #scancross.cs1.lock .revcnr{border-color:#ffd84d}
  #scancross.cs2 .revcnr{border-color:rgba(216,64,47,.85)}
  #scancross.cs2.lock .revcnr{border-color:#ff8a5c}
  #scancross.cs3 .revcnr{border-color:rgba(255,255,255,.55)}
  #scancross.cs3.lock .revcnr{border-color:#ffffff}
  /* ---- lock-on flourish ----
     The moment the reticle first clamps on a car it ANNOUNCES it: the corners
     snap in from oversize, a sonar ring pushes out from the box, a LOCK tag
     pins to its top-left and a centre dot appears. While it keeps tracking the
     corners breathe slowly — alive, not blinking. Colours follow the
     crosshair setting (cs1/cs2/cs3) through --lockc. */
  #scancross{--lockc:#5dff9b}
  #scancross.cs1{--lockc:#ffd84d} #scancross.cs2{--lockc:#ff8a5c} #scancross.cs3{--lockc:#ffffff}
  #scancross .rlock{position:absolute;z-index:3;left:var(--bx,10px);top:max(2px,calc(var(--by,10px) - 16px));
    font-family:var(--font-utility);font-size:8.4px;letter-spacing:1.5px;color:#0b1408;
    background:var(--lockc);padding:2px 5px 1px;box-shadow:1px 1px 0 rgba(0,0,0,.6);
    opacity:0;transform:translateY(4px);transition:opacity .18s steps(3),transform .18s steps(3);pointer-events:none}
  #scancross.lock .rlock{opacity:1;transform:none}
  #scancross .rdot{position:absolute;z-index:2;width:4px;height:4px;background:var(--lockc);opacity:0;pointer-events:none;
    left:calc(var(--bx,0px) + var(--bw,0px)/2 - 2px);top:calc(var(--by,0px) + var(--bh,0px)/2 - 2px);
    box-shadow:1px 1px 0 rgba(0,0,0,.6)}
  #scancross.lock .rdot{opacity:.95}
  #scancross .rping{position:absolute;z-index:1;border:2px solid var(--lockc);opacity:0;pointer-events:none;
    left:calc(var(--bx,0px) + var(--bw,0px)/2);top:calc(var(--by,0px) + var(--bh,0px)/2);
    width:var(--bw,0px);height:var(--bh,0px);transform:translate(-50%,-50%) scale(.5)}
  #scancross.acq .rping{animation:rping .55s steps(6) forwards}
  @keyframes rping{from{opacity:.9;transform:translate(-50%,-50%) scale(.55)}to{opacity:0;transform:translate(-50%,-50%) scale(1.35)}}
  #scancross.acq .revcnr{animation:racq .32s steps(4)}
  @keyframes racq{0%{transform:scale(1.6);opacity:.35}100%{transform:scale(1);opacity:1}}
  #scancross.lock:not(.acq) .revcnr{animation:rbreathe 1.6s ease-in-out infinite}
  @keyframes rbreathe{50%{opacity:.7}}
  @media (prefers-reduced-motion: reduce){#scancross .revcnr,#scancross .rping{animation:none!important}}
  /* the push-in when a scan starts: the frame and the corners move together —
     a real ease, not steps(), for the same reason as the reticle above */
  #cam,#scanpreview{transition:transform 380ms cubic-bezier(.22,.61,.36,1)}
  /* Race strip. Overlays the top of the viewfinder rather than sitting above it —
     the old block pushed the capture button off-screen and had to be scrolled to. */
  #racebanner{position:absolute;left:0;right:0;top:0;z-index:6;
    display:flex;align-items:center;gap:8px;padding:5px 8px;
    background:rgba(196,42,42,.94);
    border-bottom:2px solid var(--ink);color:#fff;font-size:11.4px;letter-spacing:1px}
  #racebanner .rbflag{font-size:14.4px}
  #racebanner #racebtimer{font-size:16.8px;color:#ffe08a}
  #racebanner #racebxp{flex:1;text-align:right;font-size:11.4px;color:#ffe9e4}
  #racebanner #raceexit{background:rgba(0,0,0,.35);border:2px solid #fff;color:#fff;
    font-family:inherit;font-size:9.6px;padding:3px 5px 2px;flex:none}
  /* CHROME HOUR strip — chrome on ink, a slow shimmer sweeping the flag */
  #chromebanner{position:absolute;left:0;right:0;top:0;z-index:6;
    display:flex;align-items:center;gap:8px;padding:5px 8px;
    background:linear-gradient(90deg,#1b2330,#2e3a4d 45%,#1b2330);
    border-bottom:2px solid var(--ink);color:#e8edf5;font-size:11.4px;letter-spacing:1px}
  #chromebanner .cbicon{display:none;font-size:14.4px;color:#ffe08a;flex:none}
  #chromebanner .cbflag{font-size:13.2px;color:#f2f5fa;
    background:linear-gradient(90deg,#c9d3e0 0%,#ffffff 40%,#9fb0c6 60%,#e6ecf5 100%);
    background-size:200% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;
    animation:cbshimmer 2.6s linear infinite}
  #chromebanner #chromebtimer{font-size:16.8px;color:#ffe08a}
  #chromebanner .cbnote{flex:1;text-align:right;font-size:10.2px;color:#c9d3e0}
  #chromebanner .cbmin{flex:none;background:rgba(0,0,0,.35);border:2px solid rgba(255,255,255,.55);
    color:#fff;font-family:inherit;font-size:11.4px;line-height:1;padding:4px 7px 3px;cursor:pointer}
  #chromebanner .cbmin::before{content:"\2013"}
  @keyframes cbshimmer{from{background-position:200% 0}to{background-position:0 0}}
  @media (prefers-reduced-motion: reduce){#chromebanner .cbflag{animation:none;color:#f2f5fa;background:none}}
  /* Minimized: the full-width strip collapses to a small corner chip — just
     the icon and the countdown — so it stops covering the viewfinder. */
  #chromebanner.mini{left:auto;right:8px;top:6px;width:auto;border-bottom:2px solid var(--ink);
    border:2px solid var(--ink);background:var(--panel2);padding:4px 8px;gap:6px;
    box-shadow:2px 3px 0 rgba(0,0,0,.5)}
  #chromebanner.mini .cbicon{display:inline}
  #chromebanner.mini .cbflag,#chromebanner.mini .cbnote{display:none}
  #chromebanner.mini #chromebtimer{font-size:13.2px}
  #chromebanner.mini .cbmin::before{content:"\25B8"}
  /* the Home row for the same window — a quiet card until it's live */
  .chromecard{display:flex;align-items:center;gap:10px;background:var(--panel);
    border:3px solid var(--ink);box-shadow:4px 4px 0 var(--ink);padding:9px 11px;margin-bottom:12px;
    color:var(--wht);font-size:11.4px;letter-spacing:1px;width:100%;font-family:inherit;text-align:left;cursor:pointer}
  .chromecard.live{background:linear-gradient(90deg,#1b2330,#2e3a4d 45%,#1b2330)}
  .chromecard .cci{font-size:16.8px;color:#ffe08a;flex:none}
  .chromecard .cct{flex:1;line-height:1.35}
  .chromecard .cct b{display:block;color:#f2f5fa;font-size:12.6px}
  .chromecard .cct span{color:#9fb0c6;font-size:9.6px}
  .chromecard .ccr{font-size:13.2px;color:#ffe08a;flex:none}
  /* scored-in-a-race confirmation, when the prompt is switched off */
  .racetoast{position:fixed;left:50%;transform:translateX(-50%);bottom:76px;z-index:60;
    background:var(--navy);border:3px solid var(--ink);box-shadow:3px 3px 0 rgba(0,0,0,.45);
    color:#fff;font-size:11.4px;letter-spacing:1px;padding:8px 12px 7px;text-align:center;
    animation:rtin 120ms steps(3);transition:opacity 200ms steps(2),transform 200ms steps(2)}
  .racetoast b{color:var(--gold);font-size:15.6px;display:block;margin-bottom:3px}
  .racetoast span{display:block;font-size:9.6px;color:#9aa4bd;margin-top:3px}
  .racetoast.out{opacity:0;transform:translateX(-50%) translateY(8px)}
  @keyframes rtin{from{opacity:0;transform:translateX(-50%) translateY(10px)}}
  .rcskip{display:flex;align-items:center;gap:6px;margin-top:10px;font-size:9.6px;
    color:var(--navy);letter-spacing:1px;cursor:pointer}
  .rcskip input{width:14px;height:14px;accent-color:var(--red);flex:none}
  .revshot{transition:transform 400ms steps(5)}
  .revcnr{position:absolute;width:18px;height:18px;border:3px solid var(--pink);opacity:.9;
    transition:top 300ms steps(4),left 300ms steps(4),
      right 300ms steps(4),bottom 300ms steps(4),
      width .55s ease,height .55s ease,border-color .3s ease}
  .revcnr.tl{top:10px;left:10px;border-right:0;border-bottom:0}
  .revcnr.tr{top:10px;right:10px;border-left:0;border-bottom:0}
  .revcnr.bl{bottom:10px;left:10px;border-right:0;border-top:0}
  .revcnr.br{bottom:10px;right:10px;border-left:0;border-top:0}
  /* clamped on the car: brighter, tighter, with a glow so the lock is unmistakable */
  /* Corners only — no rectangle, no fill. The four marks say where the car is
     without drawing a frame over it. */
  .revbox.lock .revcnr{width:22px;height:22px;
    filter:drop-shadow(2px 2px 0 rgba(0,0,0,.5));opacity:1}
  /* MATCH FOUND: one hard flash of the box in the tier colour, glow bleeding out */
  .revwrap.hit .revbox.lock::after{content:"";position:absolute;left:var(--bx);top:var(--by);width:var(--bw);height:var(--bh);
    box-shadow:0 0 0 3px var(--pink),0 0 28px var(--pink);animation:rhit .6s steps(5) forwards;pointer-events:none}
  @keyframes rhit{from{opacity:1}to{opacity:0}}
  .revwrap.hit .revbox.lock .revcnr{animation:rflash .16s steps(2) 3}
  @keyframes rflash{50%{opacity:.2}}
  .revhead{position:absolute;top:14px;left:8%;right:8%;text-align:left}
  .revhead .rlbl{font-size:10.2px;color:var(--pink);letter-spacing:2px;text-shadow:1px 1px 0 #000}
  .revhead .rlbl i{display:inline-block;width:5px;height:5px;background:var(--pink);
    border-radius:50%;margin-right:5px;animation:blink 1s infinite;vertical-align:middle}
  .revhead .rnm{font-size:22.8px;color:#fff;text-shadow:2px 2px 0 #000;margin-top:4px;
    animation:popin 120ms steps(4)}
  .revconf{position:absolute;top:78px;left:8%;right:8%}
  .revconf .rcrow{display:flex;justify-content:space-between;font-size:9.6px;color:#dfe5f2;
    text-shadow:1px 1px 0 #000;letter-spacing:1px}
  .revconf .rcbar{height:5px;background:rgba(255,255,255,.18);margin-top:3px;border:1px solid rgba(0,0,0,.4)}
  .revconf .rcbar i{display:block;height:100%;background:var(--pink);width:0%;
    transition:width 400ms steps(10)}
  .cleanscan{margin-top:4px;font-size:10.2px;letter-spacing:1px;color:var(--gold);
    text-shadow:1px 1px 0 #000;animation:popin 160ms steps(4)}
  .revchecks{position:absolute;top:104px;left:8%;right:8%;display:flex;flex-direction:column;gap:3px}
  .revchecks div{background:rgba(8,10,16,.8);border-left:3px solid var(--pink);color:#fff;
    font-size:9.6px;letter-spacing:.5px;padding:5px 7px;opacity:0;transform:translateX(-8px);
    animation:revslide 150ms steps(3) forwards}
  .revtag{position:absolute;background:var(--pink);color:var(--pink-ink,#1c1030);font-size:10.2px;font-weight:bold;
    letter-spacing:1px;padding:4px 7px 3px;bottom:14%;left:8%;box-shadow:2px 2px 0 rgba(0,0,0,.5);
    animation:popin 120ms steps(4)}
  .revfound{position:absolute;bottom:14%;left:calc(8% + 62px);right:8%;background:var(--pink);
    color:var(--pink-ink,#1c1030);font-size:11.4px;font-weight:bold;letter-spacing:2px;text-align:center;
    padding:6px 8px 5px;box-shadow:2px 2px 0 rgba(0,0,0,.5);animation:popin 120ms steps(4)}
  .revstat{position:absolute;top:32%;left:8%;right:8%;text-align:center}
  .revstat .rslbl{font-size:10.2px;color:var(--pink);letter-spacing:3px;text-shadow:1px 1px 0 #000}
  .revstat .rsval{font-size:clamp(26px,9vw,52px);line-height:1;color:#fff;text-shadow:3px 3px 0 #000;margin:6px 0;
    animation:popin 120ms steps(4)}
  .revstat .rsex{font-size:9.6px;color:#c9cede;letter-spacing:2px;margin-top:10px}
  .revstat .rcbar{height:4px;background:rgba(255,255,255,.18);margin-top:4px;max-width:220px;margin-left:auto;margin-right:auto}
  .revstat .rcbar i{display:block;height:100%;background:var(--pink);width:0%;transition:width 1s linear}
  .revmint{position:absolute;z-index:2;top:14px;left:0;right:0;text-align:center;font-size:11.4px;
    color:var(--pink);letter-spacing:2px;text-shadow:1px 1px 0 #000}
  .revmint i{display:inline-block;width:5px;height:5px;background:var(--pink);border-radius:50%;
    margin-right:5px;animation:blink 1s infinite;vertical-align:middle}
  .revcardbox{position:absolute;z-index:2;top:44px;left:12%;right:12%;bottom:20px;border:3px solid var(--pink);
    box-shadow:0 0 0 2px rgba(236,31,120,.35);background:var(--panel);overflow:hidden;
    animation:revgrow 160ms steps(4)}
  .revcardbox .rcsec{opacity:0;transform:translateY(6px);animation:revslide 150ms steps(3) forwards}
  @keyframes revslide{to{opacity:1;transform:none}}
  @keyframes revgrow{from{transform:scaleY(.05)}to{transform:scaleY(1)}}
  .revskel{background:rgba(236,31,120,.16);border-radius:2px}
  /* the shot flying into the card's picture slot at the end of the reveal */
  .revfly{position:absolute;z-index:6;object-fit:cover;display:block;
    border:2px solid var(--pink);box-shadow:3px 3px 0 rgba(0,0,0,.45);
    transition-property:left,top,width,height;
    transition-timing-function:steps(6)}
  @media (prefers-reduced-motion: reduce){
    .revfly{transition-duration:1ms!important}
  }
  .revskel.head{height:34px;margin:12px 12px 0}
  .revskel.photo{height:130px;margin:8px 12px}
  .revskel.line{height:9px;margin:9px 12px}

  /* ---------- SCAN ---------- */
  .viewport{border:4px solid var(--red);outline:3px solid var(--ink);background:#0d1017;
    aspect-ratio:3/4;display:flex;align-items:center;justify-content:center;overflow:hidden;margin-bottom:16px;position:relative}
  /* Desktop/laptop only. A phone held up to shoot is naturally captured in
     portrait — the 3:4 box above is tuned for that. A laptop webcam is a
     fixed landscape sensor that can never rotate, so the SAME 3:4 crop took
     a 16:9 feed and kept only a tall, heavily-zoomed sliver out of the
     middle — that's the "proportions are wrong" report. This widens the box
     to match a real webcam's shape, on desktop only.
     Gated on (pointer:fine) and (hover:hover) PLUS a min-width: touch is the
     ONLY primary pointer a phone browser ever reports, on iOS Safari/WKWebView
     and on Android Chrome alike, with or without a mouse-emulating BT
     accessory attached — so this rule is structurally unreachable from either
     app and changes nothing there. min-width is belt-and-suspenders against
     the rare touch-and-mouse hybrid laptop. Specificity (#v-scan .viewport)
     already outranks the bare .viewport rule with no need to reorder anything. */
  @media (pointer:fine) and (hover:hover) and (min-width:900px){
    #v-scan .viewport{aspect-ratio:16/9;max-width:640px;margin-left:auto;margin-right:auto}
  }
  /* Samsung Internet-only fallback: some builds still don't support
     aspect-ratio at all (the same generation gap that already bit :has()
     elsewhere on this page). Without it, .viewport has no width/height of
     its own — its only children are position:absolute (removed from normal
     flow, so they can't establish the parent's size either) — so the box
     collapses to zero content and renders as a tiny square pinned to the
     top-left of the page. @supports scopes this to exactly the browsers
     that need it; anything that already renders correctly (Safari/WKWebView
     included) never sees this rule at all.
     First pass here just gave it a real height via the same leftover-space
     calc() used elsewhere, which stopped the collapse but had no reason to
     land anywhere near 3:4 — it rendered however tall that calc happened to
     come out, square on some screens. padding-bottom:133.333% is the classic
     pre-aspect-ratio trick for deriving height purely from width (the
     percentage always resolves against the containing block's WIDTH,
     regardless of box-sizing) — it reproduces the exact ratio aspect-ratio
     would give, no measurement needed. max-height keeps the existing ceiling
     for short/landscape viewports where even a correct 3:4 box wouldn't fit. */
  @supports not (aspect-ratio: 1 / 1){
    #v-scan .viewport{height:0;padding-bottom:133.333%;
      max-height:calc(100vh - 360px);max-height:calc(100dvh - 360px);
      max-height:calc(var(--vvh,100dvh) - 360px)}
  }
  .viewport video,.viewport img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
  /* the retro grade used to live on the video feed itself — contrast/saturate/
     sepia/hue-shift on the thing you're using to judge focus and framing.
     Looked stylized in a screenshot, looked like a bad camera in your hand.
     The chrome around the viewfinder carries the retro look now; the feed
     itself stays true color. */
  #cam{display:none}
  #scanpreview{display:none;z-index:1}
  /* retro camcorder HUD */
  #camhud{position:absolute;inset:0;z-index:2;pointer-events:none;
    font-family:var(--font-display);color:#e8f4e8;text-shadow:1px 1px 0 rgba(0,0,0,.7)}
  #camhud .vhslines{position:absolute;inset:0;
    background:repeating-linear-gradient(0deg,rgba(0,0,0,.14) 0 2px,transparent 2px 5px)}
  #camhud .vignette{position:absolute;inset:0;
    background:transparent}
  #camhud .hudrec{position:absolute;top:10px;left:10px;font-size:12.6px;letter-spacing:2px;
    display:flex;align-items:center;gap:6px}
  #camhud .recdot{width:10px;height:10px;background:#ff3b30;border-radius:50%;
    animation:blink 1s steps(2) infinite}
  #camhud .hudbat{position:absolute;top:14px;right:44px;font-size:12.6px;letter-spacing:1px}
  #camhud .hudtime{position:absolute;bottom:12px;right:14px;font-size:12.6px;letter-spacing:2px}
  #camhud .hudlabel{position:absolute;bottom:12px;left:14px;font-size:10.2px;letter-spacing:2px;opacity:.85}
  /* absolute + transform-centered rather than relying on .viewport's flex
     centering — the Samsung fallback below needs .viewport's own content
     height to be 0 (padding is what creates the visible box there), and a
     flex-centered child collapses to the top of a zero-height flex line
     instead of the middle of the padding-extended box. This centers exactly
     the same way either way. */
  .viewport .hint{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
    width:80%;color:#5b6480;text-align:center;font-size:12.6px;line-height:2}
  /* ---------- the scan page is a device screen, not a document ----------
     Everything fits inside one viewport and the page refuses to scroll — but
     ONLY while the scan tab is the one on screen; every other tab scrolls as
     normal. The viewfinder gives up height rather than pushing the buttons
     under the fold. */
  /* body.scan-open (set in show(), not :has()) — :has() support landed on
     Samsung Internet long after everything else here, and a phone running an
     older build just silently drops a :has() rule instead of erroring, which
     is exactly the kind of failure that never shows up in normal testing. */
  html.scan-open,body.scan-open{overflow:hidden;height:100vh;height:100dvh;
    height:var(--vvh,100dvh);overscroll-behavior:none}
  #v-scan .viewport{max-height:calc(100vh - 360px);max-height:calc(100dvh - 360px);
    max-height:calc(var(--vvh,100dvh) - 360px);min-height:280px}
  #v-scan h1.title{margin-bottom:16px}  /* room for the garland hanging under it */
  /* Manual camera-size override (Settings > Camera > CUSTOMIZE SCAN LAYOUT).
     Entirely opt-in: nothing here applies unless #v-scan carries
     data-customcam, so a spotter who never opens the editor gets the exact
     same auto-sizing as always. Overrides EVERY property either sizing path
     (aspect-ratio, or the Samsung padding-bottom fallback above) sets, so
     whichever one the browser is actually running loses cleanly either way.
     max-height/max-width are a SAFETY CEILING, not a suggestion — and
     --scanHMax/--scanWMax (set by scanMaxDims() in the script, right before
     the slider/drag values they gate) are the REAL numbers, measured from
     the actual header/editor/button-row/nav on screen right now. This used
     to be a flat calc(100vh - 400px) guess that disagreed with the drag
     handle's OWN flat guess (window.innerHeight-200) — the box would quietly
     stop growing taller well before the handle stopped moving, while width
     kept climbing toward the (unguessed, so honest) screen edge. Height and
     width ended up capped in the same rough range on most phones no matter
     what was dragged, which is the "it's always basically a square" bug.
     The calc() fallbacks only matter for the one frame before JS runs. */
  #v-scan[data-customcam="1"] .viewport{aspect-ratio:unset;
    min-height:0;padding-bottom:0;height:var(--scanH,340px);
    width:var(--scanW,100%);margin:0 auto;
    max-width:100%;max-width:var(--scanWMax,100%);
    max-height:calc(100vh - 400px);max-height:calc(100dvh - 400px);
    max-height:calc(var(--vvh,100dvh) - 400px);
    max-height:var(--scanHMax,calc(var(--vvh,100dvh) - 400px))}
  #v-scan .scanrow{transform:scale(var(--scanBtnScale,1));transform-origin:center}
  #camresize{position:absolute;right:-3px;bottom:-3px;z-index:8;width:28px;height:28px;
    background:var(--gold);border:3px solid var(--ink);cursor:nwse-resize;
    touch-action:none;display:none;box-shadow:2px 2px 0 rgba(0,0,0,.4)}
  #camresize::after{content:"";position:absolute;right:5px;bottom:5px;width:10px;height:10px;
    border-right:3px solid var(--ink);border-bottom:3px solid var(--ink)}
  #camedit{display:none;background:var(--panel);border:3px solid var(--ink);position:relative;
    padding:34px 12px 10px;margin-bottom:12px;color:var(--wht)}
  #camedit.on{display:block}
  #camedit .cerow{display:flex;align-items:center;gap:10px;margin-bottom:8px}
  #camedit .cerow span{font-size:10.2px;letter-spacing:1px;color:#9aa4bd;flex:none;width:82px}
  #camedit input[type="range"]{flex:1}
  #camedit .cebtns{display:flex;gap:8px;margin-top:2px}
  .ce-min{position:absolute;top:6px;right:6px;width:24px;height:24px;line-height:1;
    background:var(--red);color:#fff;border:3px solid var(--ink);font-size:13.2px;font-weight:900;
    box-shadow:2px 2px 0 rgba(0,0,0,.4)}
  /* the minimized state's whole point: get the sliders' panel out of the way
     without losing edit mode, so the corner-drag handle + a full viewport is
     all that's on screen while you actually judge the size by eye */
  #ce-restore{display:none;position:absolute;top:8px;right:8px;z-index:9;width:30px;height:30px;
    align-items:center;justify-content:center;background:var(--gold);color:var(--ink-fixed);
    border:3px solid var(--ink-fixed);font-size:15.6px;box-shadow:2px 2px 0 rgba(0,0,0,.4)}
  .scanrow{display:flex;gap:10px;justify-content:center;align-items:center}
  /* While the layout editor is open the bottom row is a placeholder, not a
     control — CAPTURE/UPLOAD/BURST don't do anything useful mid-resize, and a
     live button there is one more thing to accidentally tap. It still takes
     up its real space so the resize preview is accurate. */
  #v-scan[data-editingcam="1"] .scanrow .btn{background:transparent;background-image:none;
    box-shadow:none;border:2px dashed #6b7794;color:#6b7794;pointer-events:none;text-shadow:none}
  /* Pixel-cut treatment (notch + dither + glint) matching the rest of the
     app's chrome, on all three. */
  .scanrow .btn{clip-path:var(--notch);
    background-image:var(--glint),var(--band),var(--dither);
    background-size:100% 100%,100% 100%,6px 6px;background-repeat:no-repeat,no-repeat,repeat}
  /* 80px, not a smaller guess: UPLOAD and BURST relabel to PHONE CAM/PICK
     PHOTOS when there's no live camera (see scanState()), and PICK PHOTOS
     is the longest text either button ever has to hold on one line. */
  .scanrow .btn:not(.red){font-size:10.2px;padding:6px 3px 5px;width:80px;white-space:nowrap}
  .scanrow .btn.red{font-size:16.8px;padding:12px 18px 11px}
  .scanstatus{text-align:center;margin-top:14px;font-size:12.6px;min-height:16px;color:var(--navy)}
  .scanbar{display:inline-block;width:110px;height:10px;border:2px solid var(--ink);
    background:var(--panel);vertical-align:middle;margin-right:10px;overflow:hidden;position:relative}
  .scanbar i{position:absolute;top:0;bottom:0;width:32px;background:var(--red);
    animation:sweepbar .7s linear infinite alternate}
  @keyframes sweepbar{from{left:0}to{left:calc(100% - 32px)}}
  .scanstatus.err{color:var(--alert)}
  .blink{animation:blink 1s steps(2) infinite}
  @keyframes blink{50%{opacity:.25}}

  /* ---------- CAUGHT ---------- */
  /* CAUGHT gets the card's chrome shine — same hard-stop gradient, in gold, so
     the headline above the card and the model name on it read as one object
     rather than two different design languages stacked. */
  .caught-h{font-family:var(--font-display);font-size:52.8px;font-weight:400;letter-spacing:0;text-align:center;line-height:.95;
    background:linear-gradient(180deg,
      #fff3cf 0 40%, #f0c757 40% 58%, #d19a1c 58% 78%, #a97a12 78% 100%);
    -webkit-background-clip:text;background-clip:text;color:transparent;
    -webkit-text-fill-color:transparent;
    filter:drop-shadow(2px 2px 0 rgba(46,53,80,.85)) drop-shadow(0 0 1px rgba(46,53,80,.7))}
  .caught-sub{text-align:center;font-size:11.4px;color:var(--gold-dk);margin:6px 0 8px;letter-spacing:1px}
  /* first-time tag and the XP earned, side by side on one row */
  .caughtmeta{display:flex;align-items:stretch;justify-content:center;gap:6px;margin:0 auto 14px;
    width:max-content;max-width:100%;flex-wrap:wrap}
  .newdex{display:block;width:max-content;margin:0;background:var(--gold);border:3px solid var(--ink-fixed);
    color:var(--ink-fixed);font-size:11.4px;padding:6px 10px 5px;letter-spacing:1px;border-radius:2px;
    box-shadow:inset 0 2px 0 rgba(255,255,255,.5),inset 0 -2px 0 rgba(0,0,0,.18)}
  .newdex.dup{background:var(--c-common)}
  /* deliberately the quieter of the two — the catch is the news, not the XP */
  .shinytag{display:flex;align-items:center;background:#141C27;border:3px solid var(--gold);
    color:var(--gold);font-size:11.4px;padding:0 8px;letter-spacing:.14em;white-space:nowrap;
    animation:shinyblink 1s steps(2) infinite}
  @keyframes shinyblink{50%{color:#fff}}
  .tile .shinystar{position:absolute;top:4px;right:22px;z-index:2;color:var(--gold);
    font-size:14.4px;text-shadow:1px 0 0 var(--ink-fixed),-1px 0 0 var(--ink-fixed),
    0 1px 0 var(--ink-fixed),0 -1px 0 var(--ink-fixed)}
  /* fixed height so a NEW/×dup chip appearing can NEVER make one tile taller
     than its row — the caught/locked pixel-identical invariant includes chips */
  .tile .num{display:flex;align-items:center;justify-content:flex-end;gap:3px;height:16px}
  .tile .dupchip{display:inline-block;z-index:2;background:var(--navy);
    color:#fff;font-size:10.2px;line-height:1;padding:2px 4px 1px;border:2px solid rgba(255,255,255,.25);
    letter-spacing:.5px}
  .tile .newchip{display:inline-block;z-index:2;background:var(--red);
    color:#fff;font-size:10.2px;line-height:1;padding:2px 4px 1px;border:2px solid rgba(0,0,0,.35);letter-spacing:.5px}
  .caughtxp{display:flex;align-items:center;background:var(--navy);border:3px solid var(--ink);
    color:var(--gold);font-size:10.2px;padding:0 8px;letter-spacing:1px;white-space:nowrap;border-radius:2px;
    box-shadow:inset 0 2px 0 rgba(255,255,255,.22),inset 0 -2px 0 rgba(0,0,0,.3)}
  .card{background-color:var(--paper2);border:3px solid var(--ink);padding:10px;
    box-shadow:inset 2px 2px 0 rgba(255,255,255,.4),inset -2px -2px 0 rgba(0,0,0,.12),
               5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
               10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink);
    background-image:var(--band);
    background-size:100% 100%;background-repeat:no-repeat}
  .card .head{background-color:var(--gold);border:3px solid var(--ink-fixed);color:var(--ink-fixed);
    display:flex;align-items:baseline;gap:8px;
    padding:9px 10px 7px;margin-bottom:10px;position:relative;overflow:hidden;
    background-image:var(--glint),var(--band),var(--dither);
    background-size:100% 100%,100% 100%,6px 6px;background-repeat:no-repeat,no-repeat,repeat}
  .card .head b{font-size:19.2px} .card .head span{font-size:11.4px;color:#6b4e0d}
  .card .head .num{margin-left:auto;font-size:12.6px;color:#6b4e0d}
  .card .photo-wrap{border:3px solid var(--ink);position:relative;background:#000}
  .card .photo-wrap img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block}
  /* Opt-in, not a blanket img{} rule — a real photo over a slow connection
     used to just pop in the instant it decoded, same as every sprite icon.
     Only the handful of spots that actually show a real (non-instant,
     non-inline-SVG) photo get this class; see the single load/error
     listener near the bottom of the script that adds .imgin. */
  img.fadeimg{opacity:0;transition:opacity 180ms steps(4)}
  img.fadeimg.imgin{opacity:1}
  .corners i{position:absolute;width:16px;height:16px;border:4px solid var(--gold)}
  .corners i:nth-child(1){top:6px;left:6px;border-right:0;border-bottom:0}
  .corners i:nth-child(2){top:6px;right:6px;border-left:0;border-bottom:0}
  .corners i:nth-child(3){bottom:6px;left:6px;border-right:0;border-top:0}
  .corners i:nth-child(4){bottom:6px;right:6px;border-left:0;border-top:0}
  .trimrow{display:flex;align-items:center;margin:10px 2px 4px}
  .trimrow .trim{font-size:12.6px;letter-spacing:1px}
  .tierbadge{margin-left:auto;border:3px solid var(--ink);font-size:11.4px;padding:5px 9px 4px;
    letter-spacing:1px;color:var(--ink);text-transform:uppercase}
  .classline{font-size:11.4px;color:var(--navy);letter-spacing:2px;margin:2px 2px 10px}
  .spectable{background:var(--panel);border:3px solid var(--ink);color:var(--wht);padding:10px 12px;font-size:11.4px}
  .spectable .sechead{color:var(--gold);letter-spacing:2px;margin:4px 0 6px}
  .srow{display:flex;justify-content:space-between;gap:8px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.06)}
  .srow.pushcat{align-items:center;padding:6px 0}
  .srow.pushcat span{font-size:10.2px;letter-spacing:.4px;flex:1}
  .pushcatbtn{flex:none;font-size:9.6px;padding:5px 9px 4px;background:var(--navy-dk);
    border:2px solid var(--ink);color:#9aa4bd;letter-spacing:1px}
  .pushcatbtn.on{background:var(--c-uncommon);color:#fff;border-color:var(--ink)}
  /* A spotter's picture is one of their own caught cars, on the same dusk-road
     ground the bix tiles use so it reads as a bix sprite, not a photo. */
  .avat{flex:none;display:flex;align-items:center;justify-content:center;overflow:hidden;
    border:2px solid var(--ink);border-radius:3px;
    background:linear-gradient(180deg,#2b3550 0 26%,#333d5a 26% 52%,#39415c 52% 100%)}
  .avat img{width:100%;height:100%;object-fit:contain;image-rendering:pixelated;display:block}
  .avat span{font-size:12.6px;color:#8b96b8}
  .arow.spot{cursor:pointer;gap:6px}
  .arow.spot:active{filter:brightness(1.25)}
  .spotcard{--spc:var(--navy);background-color:var(--paper);border:3px solid var(--ink);
    box-shadow:inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),
               6px 6px 0 rgba(46,53,80,.8);
    background-image:var(--dither);background-size:6px 6px;
    padding:0 0 14px;width:100%;max-width:320px;overflow:hidden}
  .spotcard .spband{background-color:var(--spc);height:48px;border-bottom:3px solid var(--ink);
    box-shadow:inset 0 2px 0 rgba(255,255,255,.25),inset 0 -3px 0 rgba(0,0,0,.2);
    position:relative;overflow:hidden;
    background-image:var(--glint),var(--band),var(--dither),repeating-linear-gradient(0deg,rgba(255,255,255,.05) 0 2px,transparent 2px 4px);
    background-size:100% 100%,100% 100%,6px 6px,auto;background-repeat:no-repeat,no-repeat,repeat,repeat}
  .spothead{display:flex;align-items:flex-end;gap:12px;padding:0 14px;margin-top:-28px}
  /* only the AVATAR overlaps the color band — the text block anchors below
     it, otherwise every extra line (the ★ title was the straw) pushes the
     bottom-aligned name up INTO the band, dark-on-dark */
  .spothead .spnamewrap{align-self:flex-start;margin-top:32px}
  .spothead .avat{width:64px!important;height:64px!important;border:3px solid var(--ink);
    box-shadow:2px 2px 0 rgba(0,0,0,.35);background-color:var(--paper)}
  .spnamewrap{padding-bottom:2px}
  .spname{font-family:var(--font-display);font-size:28.8px;line-height:1;color:var(--ink);
    text-shadow:1px 1px 0 rgba(255,255,255,.6)}
  .splvl{font-size:12.6px;font-weight:700;color:var(--navy);margin-top:5px;letter-spacing:1px}
  .spfr{display:inline-block;font-size:9.6px;color:#fff;margin-top:6px;letter-spacing:1px;
    padding:3px 6px 2px;border:2px solid var(--ink)}
  .spgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:2px;margin:16px 14px 0;
    border:2px solid var(--line);background:var(--line)}
  .spgrid .spcell{background:var(--panel);padding:9px 10px;text-align:center}
  .spgrid .spcell span{display:block;font-size:9.6px;color:var(--lcd-dim);letter-spacing:1.5px;margin-bottom:4px}
  .spgrid .spcell b{font-family:var(--font-display);font-weight:400;font-size:22.8px;color:var(--gold)}
  .spbest{margin:8px 14px 0;background:var(--panel);border:2px solid var(--line);padding:9px 10px;
    text-align:center}
  .spbest span{display:block;font-size:9.6px;color:var(--lcd-dim);letter-spacing:1.5px;margin-bottom:4px}
  .spbest b{font-family:var(--font-display);font-weight:400;font-size:19.2px}
  /* picker: every car you've caught, as a grid of sprites */
  .avpick{display:grid;grid-template-columns:repeat(auto-fill,minmax(58px,1fr));gap:6px;
    max-height:46vh;overflow-y:auto;margin-top:8px;padding-right:2px}
  .avpick button{background:none;border:0;padding:0;cursor:pointer}
  .avpick .avat{width:100%;aspect-ratio:4/3;height:auto}
  .avpick .on .avat{outline:3px solid var(--gold);outline-offset:-3px}
  .avpick .cap{font-size:9.6px;color:#9aa4bd;letter-spacing:.5px;margin-top:2px;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* an incoming friend request is a notification, so it gets to look urgent */
  .frreq{border:2px solid var(--alert);background:rgba(214,74,74,.10);padding:8px;margin-bottom:8px}
  .frreq .frtxt{font-size:11.4px;line-height:1.7;margin-bottom:8px}
  .frreq .frtxt b{color:var(--gold)}
  .frreq .frbtns{display:flex;gap:6px}
  .frreq .frbtns .btn{flex:1;font-size:11.4px;padding:6px 4px}
  .srow span{color:#9aa4bd}.srow b{font-weight:normal}
  .srow.hl b{color:var(--gold)}
  .caught-actions{display:flex;flex-direction:column;gap:8px;margin:14px 0 6px;
    position:sticky;bottom:calc(78px + env(safe-area-inset-bottom));z-index:10;
    background:var(--paper);padding:8px 0 4px;border-top:3px solid var(--ink)}
  /* Buttons stamped the same way as the card: square edges and flat bevel
     bands instead of a soft gradient sheen. */
  .caught-actions .btn{text-align:center;font-size:18px;padding:20px;position:relative;overflow:hidden;
    border-radius:2px;letter-spacing:1px;
    box-shadow:0 6px 0 var(--ink-fixed),
               inset 0 3px 0 rgba(255,255,255,.34),inset 0 6px 0 rgba(255,255,255,.14),
               inset 0 -4px 0 rgba(0,0,0,.32),inset 0 -7px 0 rgba(0,0,0,.14);
    transition:none}
  /* hard-edged highlight band, not a fade */
  .caught-actions .btn::before{content:"";position:absolute;top:0;left:0;right:0;height:38%;
    background:linear-gradient(180deg,rgba(255,255,255,.26) 0 62%,rgba(255,255,255,.10) 62% 100%);
    pointer-events:none}
  .caught-actions .btn:active{transform:translate(2px,2px);
    box-shadow:0 0 0 var(--ink-fixed),inset 0 3px 0 rgba(0,0,0,.2)}
  .caught-actions .btn:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  .caught-actions #keep{background:var(--red)}
  .caught-actions #again{background:var(--navy)}

  /* CAUGHT: whole screen pinned to the viewport, only the spec sheet scrolls */
  html.caught-lock,body.caught-lock{overflow:hidden;height:100vh;height:100dvh;
    height:var(--vvh,100dvh);overscroll-behavior:none}
  .caught-discard{position:absolute;top:8px;right:8px;z-index:20;width:26px;height:26px;
    background:var(--red);border:2px solid var(--ink);border-radius:2px;
    box-shadow:0 3px 0 var(--ink),inset 0 2px 0 rgba(255,255,255,.3),inset 0 -2px 0 rgba(0,0,0,.25);
    color:#fff;font-family:inherit;font-size:12.6px}
  .caught-discard:active{transform:translateY(2px);box-shadow:0 1px 0 var(--ink)}
  /* A short viewport — landscape (a natural way to hold the phone for a wide
     side-on shot of a car), a big header, or larger accessibility text — could
     leave less room than the head+badge+buttons alone need. Previously the
     panel was overflow:hidden with only the stats area allowed to shrink, so on
     a short-enough screen the LAST thing in the column — the action buttons —
     got silently clipped outside the box: still in the DOM, never reachable by
     a real tap, with no way to scroll to them (the panel didn't scroll) and no
     visible sign anything was wrong. The panel itself now scrolls as a
     fallback, and the buttons are pinned to the bottom of that scroll area
     (see .caught-actions below) so they are always on screen without even
     needing to scroll, on any viewport, in either orientation. */
  #caughtui.locked{position:fixed;left:8px;right:8px;top:calc(var(--hdrh,54px) + 8px);
    bottom:calc(env(safe-area-inset-bottom) + 8px);
    max-width:560px;margin:0 auto;padding:12px 12px 0;background:var(--paper);
    border:3px solid var(--ink);border-radius:3px;
    box-shadow:0 4px 0 rgba(0,0,0,.22),inset 0 2px 0 rgba(255,255,255,.55),
               inset 0 -2px 0 rgba(46,53,80,.14);
    display:flex;flex-direction:column;
    overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;
    /* above the bottom nav (40) and header (30) — this card's buttons must never
       be reachable-but-covered by chrome that's meant to be hidden underneath it */
    z-index:45}
  #caughtui.locked .caught-h,#caughtui.locked .caught-sub,
  #caughtui.locked .caught-panel,#caughtui.locked .caughtmeta{flex:none}
  /* smaller in the locked panel, but keeps the same chrome treatment */
  #caughtui.locked .caught-h{font-size:31.2px;margin-bottom:2px;letter-spacing:0;
    filter:drop-shadow(1px 1px 0 rgba(46,53,80,.85))}
  #caughtui.locked .caughtmeta{margin:0 auto 5px;gap:5px}
  #caughtui.locked .newdex{margin:0;font-size:10.8px;padding:4px 8px 3px}
  #caughtui.locked .caughtxp{font-size:9.6px;padding:0 7px}
  /* no longer flex:1/min-height:0 — the OUTER panel is the one scroll region now,
     so the card just takes its natural height and the panel scrolls if needed */
  #caughtui.locked #caughtcardwrap{flex:none;display:flex;flex-direction:column;
    align-items:center}
  #caughtui.locked .tcard{width:100%;display:flex;flex-direction:column;max-width:420px}
  #caughtui.locked .tc-head,#caughtui.locked .tc-photo,
  #caughtui.locked .tc-trim,#caughtui.locked .tc-serial{flex:none}
  #caughtui.locked .tc-head b{font-size:19.2px}
  #caughtui.locked .tc-head .mk{font-size:10.2px}
  #caughtui.locked .tc-photo{aspect-ratio:16/7;height:auto;max-height:150px}
  #caughtui.locked .tc-trim .tr{font-size:11.4px}
  /* NOT a scroll container. This used to be overflow-y:auto + overscroll-behavior:
     contain nested INSIDE the panel (which scrolls itself) — and on iOS a finger
     that lands on the stat rows grabs the inner scroller, which has nothing to
     scroll, and "contain" then refuses to hand the gesture to the panel: the
     whole screen felt frozen the moment you tried to read the details. The
     panel is the one scroll region; the card just takes its natural height. */
  #caughtui.locked .tc-body{flex:none;min-height:0;overflow:visible;font-size:13.2px;line-height:1.5;
    padding:6px 10px}
  #caughtui.locked .tc-body,#caughtui.locked .tcard{touch-action:pan-y}
  #caughtui.locked .tc-line{font-size:11.4px;letter-spacing:1px;margin-bottom:4px}
  #caughtui.locked .g-row{margin:3px 0}
  #caughtui.locked .g-row span{width:40px;font-size:11.4px}
  #caughtui.locked .g-row b{width:48px;font-size:14.4px}
  #caughtui.locked .tc-sec{font-size:11.4px;margin:5px 0 3px;padding-bottom:2px;
    border-bottom:1px solid rgba(255,255,255,.14)}
  #caughtui.locked .tc-grid{gap:2px 12px}
  #caughtui.locked .tc-grid .srow{display:flex;justify-content:space-between;gap:6px}
  #caughtui.locked .tc-grid .srow span{color:#8e98b8;flex:none;font-size:11.4px}
  #caughtui.locked .tc-grid .srow b{text-align:right;color:#f2f4fa;font-size:11.4px}
  #caughtui.locked .tc-serial .sn{font-size:11.4px}
  /* Pinned to the bottom of the panel's own scroll area — opaque so scrolled
     content can't show through underneath it — rather than just being the last
     thing in the column. That's what actually guarantees these are reachable:
     sticky keeps them on screen without the spotter ever needing to find or
     use a scroll gesture, on any viewport height or orientation. */
  #caughtui.locked .caught-actions{flex:none;position:sticky;bottom:0;background:var(--paper);
    border-top:2px solid var(--ink);margin-top:6px;padding:6px 0 10px;gap:5px}
  #caughtui.locked .caught-actions .btn{padding:10px;font-size:14.4px;box-shadow:0 3px 0 var(--ink-fixed),
    inset 0 2px 0 rgba(255,255,255,.32),inset 0 -2px 0 rgba(0,0,0,.3)}
  #caughtui.locked .caught-actions .btn:active{box-shadow:0 0 0 var(--ink-fixed),inset 0 2px 0 rgba(0,0,0,.2)}
  #caughtui.locked .addnote{font-size:10.2px;margin-top:6px!important}

  /* ---------- GARAGE ---------- */
  .counter{font-size:16.8px;color:var(--gold-dk)}
  /* touch-action:pan-x (not the default "auto") tells Android immediately that
     this row only ever moves sideways, so a touch that starts on/near it hands
     vertical panning straight to the page instead of the browser having to
     guess the gesture's axis — the guess is exactly what was eating scrolls
     that started on the Settings sub-tab row. */
  .filters{display:flex;gap:8px;overflow-x:auto;padding:2px 0 12px;scrollbar-width:none;touch-action:pan-x}
  .filters::-webkit-scrollbar{display:none}
  .galmost{background:var(--navy-dk);border:2px solid var(--ink);color:var(--wht);
    font-size:10.2px;letter-spacing:.5px;padding:8px 10px;margin-bottom:12px;cursor:pointer}
  .galmost b{color:var(--gold)}
  .needtag{font-size:9.6px;letter-spacing:.5px;padding:2px 4px;border:1px solid var(--ink);
    text-transform:uppercase;vertical-align:middle}
  .needtag.need{background:var(--gold);color:var(--ink-fixed)}
  .needtag.got{background:var(--c-uncommon);color:#fff}
  .fbtn{flex:none;background-color:var(--navy);border:2px solid var(--ink);
    box-shadow:inset 2px 2px 0 var(--steel-lit),inset -2px -2px 0 var(--navy-dk),0 3px 0 var(--ink);
    color:var(--wht);font-size:11.4px;padding:8px 10px 7px;letter-spacing:1px;text-transform:uppercase;
    position:relative;overflow:hidden;clip-path:var(--notch);
    /* no --dither here — it's a text label, not artwork, and the grain made
       it harder to read at 8px */
    background-image:var(--glint),var(--band);
    background-size:100% 100%,100% 100%;background-repeat:no-repeat,no-repeat}
  .fbtn.on{background-color:var(--gold);color:var(--ink-fixed);transform:translateY(2px);box-shadow:0 1px 0 var(--ink-fixed)}
  .grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px 5px;padding-bottom:20px}
  /* offscreen tiles skip layout and paint entirely — long dex scrolls stop
     costing what the whole grid costs */
  .tile{position:relative;cursor:pointer;perspective:520px;background:none;border:none;padding:0;
    content-visibility:auto;contain-intrinsic-size:117px 153px}
  /* .tfront needs overflow:hidden for its own notch/dither clipping, so the
     card's outer "lifted off the page" shadow has to live on .tflip instead —
     a solid offset block, same language as the nav keys and .card stack.
     box-shadow (not filter:drop-shadow) on purpose: this grid runs to
     thousands of tiles and content-visibility can't cull a filter as cheaply. */
  /* two hard offset layers — a stepped pixel shadow, so the card reads as
     sitting a real height off the garage wall instead of floating on a blur */
  .tflip{box-shadow:3px 4px 0 rgba(0,0,0,.5),6px 8px 0 rgba(0,0,0,.2)}
  .fcard{content-visibility:auto;contain-intrinsic-size:auto 360px}
  .tflip{position:relative;transform-style:preserve-3d;
    transition:transform 280ms steps(5);width:100%;height:100%}
  .tile.flip .tflip{transform:rotateY(180deg)}
  .tface{backface-visibility:hidden;-webkit-backface-visibility:hidden}
  /* Bix tiles as steel garage cards racked on the garage wall: dark slab
     stock (the page behind them IS the garage room — cream card stock read
     as "white" and got vetoed), ink frame, hard offset shadow, and the tier
     as a colored top-binding — the same language as the tier shelf above the
     grid. A caught tile carries its tier color in --rc (set inline by
     tileHTML); a locked slot falls back to a neutral steel edge, so caught
     and locked stay pixel-identical in size and only the colors move. Colors
     here are theme-constant on purpose: the garage room doesn't change with
     the casing. */
  .tfront{background:#232E3F;border:4px solid #4A5568;
    border-top:7px solid var(--rc,#3A4456);position:relative;
    overflow:hidden;padding:4px 6px 6px;clip-path:var(--notch);
    /* embossed like a keycap: hard 2px lit edge top-left, 2px shade edge
       bottom-right, and the pixel-grain checker over the whole slab —
       plus the fuller cartridge frame: a near-black outer ring outside the
       blue-gray border, then a dark inner outline just inside it, so the
       card reads as a stack of rings instead of one thin line */
    background-image:var(--dither);background-size:4px 4px;
    box-shadow:0 0 0 2px #0d1119,
      inset 0 0 0 2px #10141f,
      inset 2px 2px 0 rgba(255,255,255,.16),inset -2px -2px 0 rgba(0,0,0,.42)}
  /* dither seam right under the tier band — pixel art blends two tones with
     a checker strip, not a hard line */
  .tfront::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;
    background:var(--dseam);opacity:.4;pointer-events:none}
  .tile .num{color:#8b96b8;font-size:10.2px;text-align:right;margin-bottom:3px;letter-spacing:1px}
  /* NB: no border or padding here — caught and locked slots must be pixel-identical,
     and anything set on .carbox alone makes a tile change size the moment it unlocks */
  /* Every art slot is the same box regardless of what goes in it — sprite,
     silhouette or the X — so the grid stays on a single rhythm. The artwork is
     centred and letterboxed inside rather than driving the tile's height. */
  .tfront .qbox,.tfront .carbox{position:relative;width:100%;aspect-ratio:4/3;
    display:flex;align-items:center;justify-content:center;padding:2px 4px;overflow:hidden;
    /* the art window is a recessed slide mounted in the paper card — ink
       frame plus a hard top inner shadow so it sits BELOW the card surface
       while the card itself sits above the page */
    border:2px solid var(--ink);
    box-shadow:inset 0 3px 0 rgba(0,0,0,.35),inset 0 -2px 0 rgba(255,255,255,.07)}
  /* A dusk sky over a road strip, so a cut-out sprite sits somewhere instead of
     floating on the panel. Pure CSS — no extra requests, and it tiles identically
     across every tile so the grid reads as one surface. Dithered at each hard
     color-band seam so the bands read as blended pixel shading, not flat CSS. */
  .tfront .carbox{
    /* every caught car is parked in its own mini garage bay — same room as
       the big bay up top: roller-door ribs on the wall, painted floor markings */
    background:
      repeating-linear-gradient(90deg,rgba(255,255,255,.05) 0 2px,transparent 2px 18px) top left/100% 56% no-repeat,
      linear-gradient(180deg,#1d2434 0 30%,#20283a 30% 56%,#2b3349 56% 80%,#222a3c 80% 100%)}
  .tfront .carbox::before{content:"";position:absolute;left:0;right:0;top:56%;height:2px;
    background:rgba(8,10,16,.5)}
  .tfront .carbox::after{content:"";position:absolute;left:0;right:0;bottom:14%;height:2px;
    background:repeating-linear-gradient(90deg,rgba(240,232,200,.4) 0 5px,transparent 5px 13px)}
  /* locked slots get the same ground but darker, so they read as "not yet" */
  .tfront .qbox{opacity:.9;
    background:linear-gradient(180deg,#20283c 0 24%,#242c42 24% 46%,#232a3e 46% 100%)}
  .tfront .qbox::after{content:"";position:absolute;left:0;right:0;bottom:22%;height:2px;
    background:repeating-linear-gradient(90deg,rgba(255,255,255,.10) 0 5px,transparent 5px 13px)}
  .tfront .qbox>*,.tfront .carbox>*{position:relative;z-index:1}
  .tfront .qbox img,.tfront .carbox img{width:100%;height:100%;
    object-fit:contain;display:block;aspect-ratio:auto}
  /* no generated sprite yet — a plain mark, never a misleading shape */
  .tfront .qbox.noart{opacity:1}
  .tfront .qbox .qx{font-size:24px;line-height:1;color:var(--lcd-dim);opacity:.8}
  .tfront .starbdg{position:absolute;top:6px;left:7px;color:var(--gold);font-size:12.6px;
    text-shadow:1px 0 0 var(--ink-fixed),-1px 0 0 var(--ink-fixed),
    0 1px 0 var(--ink-fixed),0 -1px 0 var(--ink-fixed);z-index:2}
  /* Rarity tag on a caught tile. Absolutely positioned inside .carbox on
     purpose: a caught slot and a locked slot have to stay pixel-identical in
     height, and anything added to the normal flow would make the grid reflow
     the moment a car unlocks. Small and low-contrast — it's a hint you read
     while scanning the grid, not the thing you look at. */
  /* chip look is global — the garage bay's plate uses the same tag outside
     any .tfront; only the in-tile positioning is scoped */
  .rartag{font-size:9.6px;letter-spacing:.8px;line-height:1;padding:3px 4px 2px;
    border:1.5px solid rgba(16,20,31,.85);border-radius:2px;
    text-transform:uppercase;font-weight:bold;
    box-shadow:0 1px 0 rgba(16,20,31,.5)}
  .tfront .rartag{position:absolute;left:3px;bottom:3px;z-index:2}
  .rt-common{background:var(--c-common);color:#fff}
  .rt-uncommon{background:var(--c-uncommon);color:#fff}
  .rt-rare{background:var(--c-rare);color:#fff}
  .rt-epic{background:var(--c-epic);color:#fff}
  .rt-legendary{background:var(--c-legendary);color:#2e3550}
  .rt-mythic{background:var(--c-mythic);color:#fff}
  .rt-grail{background:var(--c-grail);color:#1a1530}
  .tile img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block}
  /* the nameplate is a real plate, not just text — but ONLY visible when
     there's a real name in it. A truly locked slot's .nm is the one place
     tileHTML() renders aria-hidden="true" (its content is a bare &nbsp;,
     never a real model name), so that's the flag this keys off — not
     data-id, which only means "genuinely owned" and would wrongly hide the
     plate on a SHOWALL preview tile that has a real name but isn't owned.
     The padding/border live on the SHARED base rule, not just the visible
     variant — every tile pays the same box height for .nm whether the plate
     is shown or not, so a locked tile (border/background transparent) stays
     pixel-identical in size to a discovered one, and unlocking a car never
     reflows the grid. */
  .tile .nm{color:#242C3D;font-size:12px;font-weight:700;text-align:center;margin-top:5px;letter-spacing:.5px;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-transform:uppercase;
    background:transparent;border:1.5px solid transparent;padding:4px 2px 3px;
    box-sizing:border-box}
  .tile .nm:not([aria-hidden]){background:#B0A888;border-color:#10141f;
    box-shadow:inset 0 0 0 1px rgba(255,255,255,.35),inset 0 -2px 0 rgba(16,20,31,.18)}
  .tback{position:absolute;inset:0;transform:rotateY(180deg);background:var(--panel);
    border:3px solid var(--rc,#4b5470);box-shadow:3px 3px 0 rgba(46,53,80,.55);
    display:flex;flex-direction:column;justify-content:space-between;padding:7px 6px;overflow:hidden}
  .tback .bmk{font-size:10.2px;color:#9aa4bd;letter-spacing:1px;text-transform:uppercase}
  .tback .bnm{font-size:10.8px;color:var(--wht);text-transform:uppercase;line-height:1.7;
    overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
  .tback .brow{font-size:10.2px;color:var(--gold);line-height:1.8}
  .tback .bbtns{display:flex;gap:4px}
  .tback .bbtns button{flex:1;font-family:inherit;font-size:10.2px;padding:5px 2px 4px;
    background:var(--navy);border:2px solid var(--ink);color:var(--wht);letter-spacing:1px}
  .tback .bbtns button.gold{background:var(--gold);color:var(--ink-fixed)}
  .empty{text-align:center;color:var(--muted);padding:50px 16px;font-size:12.6px;line-height:2.2}

  /* ---------- DETAIL ---------- */
  .dbar{display:flex;align-items:center;gap:10px;margin-bottom:14px}
  .backbtn{background:var(--navy);border:3px solid var(--ink);color:var(--wht);font-size:11.4px;
    padding:8px 12px 7px;box-shadow:0 3px 0 var(--ink),inset 0 2px 0 rgba(255,255,255,.25)}
  .backbtn:active{transform:translateY(2px);box-shadow:0 1px 0 var(--ink)}
  .dbar .dexnum{margin-left:auto;font-size:14.4px;color:var(--gold-dk)}
  .dbar .dtitle{font-size:18px;letter-spacing:1px;color:var(--gold-dk)}
  .dbar .dtrash{margin-left:auto;background:var(--alert);border:3px solid var(--ink);color:#fff;
    font-size:16.8px;padding:7px 10px 6px;box-shadow:0 3px 0 var(--ink),inset 0 2px 0 rgba(255,255,255,.25)}
  .dbar .dtrash:active{transform:translateY(2px);box-shadow:0 1px 0 var(--ink)}
  body.nav-hidden nav{display:none}
  body.nav-hidden{padding-bottom:0}
  #v-detail.on{margin:0 -14px;padding:0 8px}
  .dcard{background-color:var(--paper2);border:4px solid var(--ink);margin-bottom:30px;position:relative;
    outline:2px solid var(--gold-dk);outline-offset:-8px;
    background-image:var(--band);
    background-size:100% 100%;background-repeat:no-repeat;
    box-shadow:5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
               10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .rivet{position:absolute;width:8px;height:8px;background:var(--gold-dk);border:2px solid var(--ink);
    border-radius:50%;z-index:6}
  .rivet.r1{top:10px;left:10px}.rivet.r2{top:10px;right:10px}
  .rivet.r3{bottom:10px;left:10px}.rivet.r4{bottom:10px;right:10px}
  .holostrip{height:7px;border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);
    background:repeating-linear-gradient(90deg,var(--rc,var(--gold)) 0 10px,#e8dcc0 10px 14px);position:relative;overflow:hidden}
  .holostrip::after{content:"";position:absolute;inset:0;
    background:linear-gradient(105deg,transparent 0 42%,rgba(255,255,255,.6) 42% 58%,transparent 58% 100%);
    animation:holosheen 39s steps(10) infinite}
  /* idle almost the whole cycle, one quick sweep near the end — with a long
     enough duration this reads as an occasional glint, not a constant shimmer */
  @keyframes holosheen{0%,90%{transform:translateX(-120%)}100%{transform:translateX(120%)}}
  .viewport.flash::after{content:"";position:absolute;inset:0;z-index:55;background:#fff;pointer-events:none;
    animation:shutterflash 180ms steps(4) forwards}
  @keyframes shutterflash{from{opacity:.85}to{opacity:0}}
  .dhero{overflow:hidden}
  .dhero::after{content:"";position:absolute;inset:0;pointer-events:none;z-index:4;
    background:linear-gradient(115deg,transparent 0 46%,rgba(255,255,255,.12) 46% 54%,transparent 54% 100%);
    animation:holosheen 50s steps(10) infinite}
  .stamp{position:absolute;right:16px;bottom:52px;z-index:6;transform:rotate(-11deg);
    border:3px double var(--red);color:var(--red);padding:6px 9px 5px;font-size:10.2px;
    letter-spacing:2px;opacity:.8;background:rgba(239,236,227,.6)}
  .cardfoot{display:flex;align-items:center;gap:10px;border-top:3px solid var(--ink);
    background:var(--paper);padding:8px 14px}
  .cardfoot .bc{flex:1;height:18px;
    background:repeating-linear-gradient(90deg,var(--ink) 0 2px,transparent 2px 5px,var(--ink) 5px 6px,transparent 6px 10px)}
  .cardfoot .sn{font-size:10.2px;letter-spacing:1px;color:var(--navy)}
  .catches img{cursor:pointer}
  .lightbox{position:fixed;inset:0;z-index:80;background:rgba(8,10,16,.94);display:flex;
    align-items:center;justify-content:center;padding:16px;flex-direction:column;gap:14px}
  .lightbox img{max-width:100%;max-height:82vh;max-height:82dvh;border:4px solid var(--paper);
    outline:3px solid var(--ink);animation:popin 120ms steps(4)}
  .dhero{position:relative;border-bottom:3px solid var(--ink)}
  .dhero .tierbadge{position:absolute;top:10px;right:10px;margin:0;z-index:5}
  .scene{position:relative;aspect-ratio:256/132;overflow:hidden;image-rendering:pixelated}
  .scene .sun{position:absolute;top:12%;width:16px;height:16px;background:#f7ecd2;
    box-shadow:0 0 0 3px rgba(247,236,210,.35)}
  .scene .ridge{position:absolute;left:0;right:0;height:72px;background-repeat:repeat-x;
    background-size:256px 72px;image-rendering:pixelated}
  /* the far treeline is hazed by distance; the near one stays crisp */
  .scene .ridge.far{bottom:46%;animation:scrollx 26s linear infinite;filter:blur(1.5px);opacity:.8}
  .scene .ridge.near{bottom:30%;animation:scrollx 12s linear infinite}
  .scene .road{position:absolute;left:0;right:0;bottom:0;height:30%;background:#3a4152;
    border-top:4px solid #6d7688}
  .scene .dashes{position:absolute;left:0;right:0;top:46%;height:4px;
    background:repeating-linear-gradient(90deg,#e3d9b8 0 16px,transparent 16px 44px);
    animation:scrollx .7s linear infinite}
  .scene .scenecar{position:absolute;left:50%;bottom:16%;width:88%;transform:translateX(-50%);
    animation:carbob .55s steps(2) infinite}
  .scene .scenecar img{width:100%;image-rendering:pixelated;display:block}
  @keyframes scrollx{to{background-position-x:-256px}}
  @keyframes carbob{50%{margin-bottom:2px}}
  .nameband{background-color:var(--gold);border-bottom:3px solid var(--ink-fixed);padding:10px 14px 8px;
    position:relative;overflow:hidden;
    background-image:var(--glint),var(--band),var(--dither);
    background-size:100% 100%,100% 100%,6px 6px;background-repeat:no-repeat,no-repeat,repeat}
  .nameband h2{font-size:18px;text-transform:uppercase;line-height:1.4;color:var(--ink-fixed)}
  .nameband .tagline{font-size:10.2px;color:#6b4e0d;letter-spacing:1px;text-transform:uppercase;margin-top:2px}
  .dbody{padding:12px}
  /* Section headers sit inside the readout, so they follow the readout rule:
     amber label, red double-chevron tick, dotted rule filling the rest. */
  .dsec{font-size:15.6px;color:var(--gold);letter-spacing:.18em;text-transform:uppercase;
    padding:12px 12px 4px;display:flex;align-items:center;gap:8px;font-family:var(--font-utility)}
  .dsec::before{content:"»";color:var(--red);font-weight:bold;letter-spacing:0}
  .dsec:first-child{padding-top:8px}
  .dsec::after{content:"";flex:1;height:2px;background:repeating-linear-gradient(90deg,var(--line) 0 4px,transparent 4px 8px)}
  .dpanel{background:var(--panel);border:3px solid var(--ink);color:var(--wht);font-size:11.4px;margin-bottom:14px;
  position:relative;overflow:hidden;
  box-shadow:inset 2px 2px 0 rgba(255,255,255,.06),inset -2px -2px 0 rgba(0,0,0,.35),3px 3px 0 rgba(0,0,0,.3);
  /* settings/home panels are dense with real numbers and labels — no --dither
     here, it was fighting the text */
  background-image:var(--band),repeating-linear-gradient(135deg,rgba(255,255,255,.02) 0 2px,transparent 2px 7px);
  background-size:100% 100%,auto;background-repeat:no-repeat,repeat}
  .dpanel .srow{padding:6px 12px;border:0}
  .dpanel .srow.alt{background:rgba(255,255,255,.045)}
  .dabout{padding:10px 12px 12px;line-height:1.55;color:#cdd4e4;font-family:var(--font-prose);font-weight:400;font-size:19.2px;letter-spacing:.01em}
  .dabout .ff{color:var(--gold);margin-top:8px}
  .catches{display:flex;gap:8px;overflow-x:auto;padding:2px 12px 12px;scrollbar-width:none}
  .catches::-webkit-scrollbar{display:none}
  .catches img{width:92px;aspect-ratio:4/3;object-fit:cover;border:3px solid var(--gold);flex:none}
  .del{display:block;margin:0 auto 34px;background:none;border:none;color:var(--alert);
    font-size:11.4px;letter-spacing:1px;text-decoration:underline;text-underline-offset:4px}

  /* ---------- CATCH CARD MODAL ---------- */
  /* Overlays own the whole screen in the iOS shell — including the strip under
     the Dynamic Island — so every one of them must claim the safe-area inset
     itself or its title ends up beneath the clock. */
  .cardwrap{position:fixed;inset:0;z-index:70;background:var(--paper-overlay);overflow-y:auto;
    padding:calc(env(safe-area-inset-top,0px) + 44px) 16px calc(env(safe-area-inset-bottom,0px) + 30px);
    display:flex;flex-direction:column;align-items:center;justify-content:safe center;gap:14px}
  /* Squared off and hard-edged — a 10px radius reads as a modern app chrome,
     which fought the pixel type sitting inside it. The bevel is stepped in two
     flat bands instead of one soft one, so the frame looks stamped. */
  .tcard{width:100%;max-width:320px;--snapin:1;--fr:#b9bcc2;--frl:#e3e5e9;--frd:#8a8d94;
    background:var(--fr);border:3px solid var(--ink);border-radius:3px;
    padding:7px;
    box-shadow:inset 2px 2px 0 var(--frl),inset -2px -2px 0 var(--frd),
               3px 3px 0 rgba(0,0,0,.35);
    image-rendering:pixelated;
    animation:popin 120ms steps(4)}
  /* epic+ card faces catch the light — same sheen the deck already uses */
  .tc-epic,.tc-legendary,.tc-mythic,.tc-grail{position:relative;overflow:hidden}
  .tc-epic::after,.tc-legendary::after,.tc-mythic::after,.tc-grail::after{content:"";position:absolute;inset:0;
    pointer-events:none;z-index:3;
    background:linear-gradient(115deg,transparent 40%,rgba(255,255,255,.22) 50%,transparent 60%);
    animation:holosheen 37s steps(16) infinite}
  @media (prefers-reduced-motion: reduce){
    .tc-epic::after,.tc-legendary::after,.tc-mythic::after,.tc-grail::after{content:none}}
  /* Frame metal tints by rarity. These follow the tier colours exactly —
     grey, green, blue, purple, gold — so the frame alone tells you what you
     caught before you read a word of it. */
  /* --rarc is the tier's own colour, reused by the RARITY label below so the
     word EPIC is purple, LEGENDARY is gold, and so on. --rari is the ink that
     stays readable on it. */
  .tc-common{--fr:#B3A98D;--frl:#D6CDB4;--frd:#8A8168;--rarc:#B3A98D;--rari:#141C27}
  .tc-uncommon{--fr:#7FA05C;--frl:#A6C285;--frd:#5B7A3F;--rarc:#7FA05C;--rari:#fff}
  .tc-rare{--fr:#5FA89B;--frl:#8FC9BE;--frd:#3F7A70;--rarc:#5FA89B;--rari:#fff}
  .tc-epic{--fr:#8C6FB5;--frl:#B49AD6;--frd:#654A8C;--rarc:#8C6FB5;--rari:#fff}
  .tc-legendary{--fr:#C9A227;--frl:#E2C55B;--frd:#8F7318;--rarc:#C9A227;--rari:#141C27}
  .tc-mythic{--fr:#D8402F;--frl:#E8705F;--frd:#98261A;--rarc:#D8402F;--rari:#fff}
  .tc-grail{--fr:#E4DDFF;--frl:#FFFFFF;--frd:#9C93D1;--rarc:#E4DDFF;--rari:#1a1530}
  .tc-ltd{font-style:normal;background:#E8C547;color:#141C27;border:2px solid #141C27;padding:0 5px;margin-left:6px;letter-spacing:1px}
  .tc-ltd.grail{background:#E4DDFF;color:#1a1530}
  .tc-head,.tc-serial{background:color-mix(in srgb,var(--fr) 70%,#666a72)}
  .tc-head{display:flex;align-items:baseline;gap:8px;background:#8f929a;border:2px solid var(--ink);
    border-radius:2px 2px 0 0;padding:8px 10px 6px;
    box-shadow:inset 0 2px 0 rgba(255,255,255,.45),inset 0 -2px 0 rgba(0,0,0,.22)}
  /* The model name is the card's headline, so it gets a chrome shine: a
     gradient with HARD stops rather than a smooth blend, because a smooth
     ramp reads as an airbrush while banded stops read as pixel art — the same
     trick 16-bit title screens used. The ink outline is a drop-shadow so it
     follows the glyphs instead of boxing them. */
  .tc-head b{font-family:var(--font-display);font-size:36px;font-weight:400;letter-spacing:0;line-height:.9;
    background:linear-gradient(180deg,
      #ffffff 0 42%, var(--frl,#dfe6f2) 42% 58%, var(--fr,#a9b6cd) 58% 78%, var(--frd,#8b98b0) 78% 100%);
    -webkit-background-clip:text;background-clip:text;color:transparent;
    filter:drop-shadow(2px 2px 0 rgba(16,20,31,.9)) drop-shadow(0 0 1px rgba(16,20,31,.9));
    -webkit-text-fill-color:transparent}
  .tc-head .mk{font-size:10.2px;color:#3c3f47;text-shadow:1px 1px 0 rgba(255,255,255,.25)}
  .tc-head .nn{margin-left:auto;font-size:11.4px;color:#3c3f47;
    text-shadow:1px 1px 0 rgba(255,255,255,.25);font-variant-numeric:tabular-nums}
  /* A shiny catch used to lose all trace of being shiny the moment you left
     the catch-confirmation screen — the tag lived on that screen's own meta
     row, never on the card itself, so reopening it later showed nothing.
     One small mark by the bix number is enough; it doesn't need a chip. */
  .tc-head .tc-shiny{color:var(--gold);font-size:13.2px;margin-left:5px;
    text-shadow:none;animation:shinyblink 1s steps(2) infinite}
  @media (prefers-reduced-motion: reduce){.tc-head .tc-shiny{animation:none}}
  /* Landscape frame, filled edge to edge. A scan photo is 4:3 or wider, so
     "contain" in a square left big black bars around a tiny car; "cover" on a
     car-shaped frame crops the dead sky/road instead and fills with the car. */
  .tc-photo{border:2px solid var(--ink);background:#0a0d14;overflow:hidden;position:relative;
    aspect-ratio:16/7;display:flex;align-items:center;justify-content:center}
  .tc-photo::before,.tc-photo::after{content:"";position:absolute;width:14px;height:14px;
    z-index:2;pointer-events:none}
  .tc-photo::before{top:4px;left:4px;border-top:3px solid var(--wht);border-left:3px solid var(--wht)}
  .tc-photo::after{bottom:4px;right:4px;border-bottom:3px solid var(--wht);border-right:3px solid var(--wht)}
  /* the frame itself pulses gold on a shiny catch — a border-colour cycle
     costs nothing layout-wise (no size/position change) so this is safe to
     run indefinitely, unlike an effect that would nudge anything around it */
  .tcard.shiny .tc-photo{animation:tcshinyframe 1.6s steps(4) infinite}
  @keyframes tcshinyframe{0%,100%{border-color:var(--ink)}50%{border-color:var(--gold)}}
  @media (prefers-reduced-motion: reduce){.tcard.shiny .tc-photo{animation:none;border-color:var(--gold)}}
  /* light posterize pulls user photos toward the sprite art; sprites are
     already quantised so they skip it */
  .tc-photo>img:not(.cardsprite){filter:url(#posterize)}
  /* the feed photo is a real camera shot with no posterize pass to match —
     pixelating just the scaling (without the color quantizing that makes it
     work on the catch card) reads as a blurry, aliased mess, not retro */
  .tc-photo>img,.tile img,.catches img{image-rendering:pixelated}
  .tc-photo>img{width:100%;height:100%;object-fit:contain;object-position:center;
    display:block}
  /* A sprite is artwork, not a photograph: show all of it, don't crop it to
     fill, and keep the pixels crisp instead of smoothed. */
  .tc-photo>img.cardsprite{object-fit:contain;object-position:center;
    image-rendering:pixelated;padding:4px}
  /* The card frame crops the shot to a letterbox, so tapping it opens the whole
     photo — the bit the crop cut off is often the point of the spot. */
  .tc-photo.zoomable{cursor:zoom-in;position:relative}
  .tc-photo.zoomable::after{content:"⤢";position:absolute;right:3px;bottom:2px;
    font-size:12.6px;color:#fff;text-shadow:0 0 3px #000,1px 1px 0 #000;opacity:.8;
    pointer-events:none}
  .photoview{position:fixed;inset:0;z-index:80;background:rgba(6,8,14,.94);
    display:flex;align-items:center;justify-content:center;padding:14px;
    animation:pvin 100ms steps(2);cursor:zoom-out}
  .photoview img{max-width:100%;max-height:100%;object-fit:contain;display:block;
    border:3px solid var(--ink);box-shadow:0 6px 0 rgba(0,0,0,.5);
    image-rendering:auto;animation:pvpop 120ms steps(4)}
  .photoview .pvhint{position:absolute;left:0;right:0;bottom:calc(env(safe-area-inset-bottom,0px) + 16px);text-align:center;
    font-size:10.2px;letter-spacing:2px;color:#9aa4bd;text-shadow:1px 1px 0 #000}
  @keyframes pvin{from{opacity:0}}
  @keyframes pvpop{from{transform:scale(.9);opacity:0}}
  /* ---- driving scene: parallax road + separately-spinning wheels ---- */
  .iconscene{position:relative;width:100%;height:100%;overflow:hidden;
    display:flex;align-items:flex-end;justify-content:center}
  .iconscene .isky{position:absolute;inset:0;
    background:linear-gradient(180deg,#0b1626 0 40%,#16233b 40% 70%,#243350 70% 100%)}
  .iconscene .ihills{position:absolute;left:0;right:0;bottom:34%;height:26%;opacity:.5;
    background:repeating-linear-gradient(90deg,transparent 0 18px,#1d2a44 18px 30px,transparent 30px 64px);
    animation:iparallax 2.6s linear infinite}
  .iconscene .ihills.h2{bottom:36%;height:16%;opacity:.32;
    background:repeating-linear-gradient(90deg,transparent 0 40px,#27395c 40px 58px,transparent 58px 96px);
    animation-duration:4.2s}
  .iconscene .iroad{position:absolute;left:0;right:0;bottom:0;height:34%;
    background:linear-gradient(180deg,#3b4358 0 50%,#2a3042 50% 100%);
    border-top:2px solid #6f7a91;box-shadow:inset 0 3px 0 rgba(0,0,0,.35)}
  .iconscene .ilines{position:absolute;left:0;right:0;top:52%;height:3px;
    background:repeating-linear-gradient(90deg,#efe3bb 0 14px,transparent 14px 34px);
    animation:iroadrun .34s linear infinite}
  .iconscene .ispeed{position:absolute;left:0;right:0;bottom:36%;height:30%;pointer-events:none;opacity:.35;
    background:repeating-linear-gradient(90deg,rgba(255,255,255,.5) 0 12px,transparent 12px 90px);
    animation:iroadrun .17s linear infinite}
  .iconscene .icar{position:relative;z-index:3;height:74%;margin-bottom:16%;
    animation:carBounce .42s steps(2) infinite}
  .iconscene .ibody{width:100%;height:100%;object-fit:contain;image-rendering:pixelated;display:block;
    filter:drop-shadow(3px 3px 0 rgba(0,0,0,.45))}
  .iconscene .iwheel{position:absolute;image-rendering:pixelated;
    animation:wheelspin .34s linear infinite}
  .iconscene .icar.realcar{height:78%;margin-bottom:11%;aspect-ratio:auto!important}
  .iconscene .ibody.real{width:auto;height:100%;max-width:none;
    filter:drop-shadow(3px 3px 0 rgba(0,0,0,.5)) contrast(1.05)}
  @keyframes iroadrun{from{background-position:0 0}to{background-position:-34px 0}}
  @keyframes iparallax{from{background-position:0 0}to{background-position:-64px 0}}
  @keyframes wheelspin{to{transform:rotate(-360deg)}}
  @keyframes carBounce{0%,100%{transform:translateY(0) rotate(0deg)}
    35%{transform:translateY(-1.5px) rotate(-.5deg)}70%{transform:translateY(.5px) rotate(.3deg)}}
  .tc-trim{display:flex;align-items:center;background:#20273a;border:2px solid var(--ink);
    padding:5px 8px 4px;margin-top:4px}
  .tc-trim .tr{font-size:11.4px;color:#cfd6e6;letter-spacing:1px}
  /* Card ground: a faint diagonal hatch plus a soft rarity-tinted glow behind the
     stats, so the panel reads as printed card stock instead of a flat rectangle.
     Kept low-contrast — the numbers still have to be the thing you look at. */
  .tc-body{background:#161c2c;border:2px solid var(--ink);margin-top:4px;padding:9px 11px;
    font-size:11.4px;color:#dfe5f2;position:relative;overflow:hidden;
    background-image:
      repeating-linear-gradient(135deg,rgba(255,255,255,.028) 0 2px,transparent 2px 7px)}
  .tc-body::after{content:"";position:absolute;inset:0;pointer-events:none;
    background:radial-gradient(ellipse at 50% 0%,var(--rarglow,transparent) 0%,transparent 72%)}
  .tc-body>*{position:relative;z-index:1}
  /* the glow picks up the card's rarity so a Legendary reads warmer than a Common */
  .tc-common   {--rarglow:rgba(150,160,180,.14)}
  .tc-uncommon {--rarglow:rgba(90,190,130,.16)}
  .tc-rare     {--rarglow:rgba(70,130,225,.20)}
  .tc-epic     {--rarglow:rgba(170,110,230,.18)}
  .tc-legendary{--rarglow:rgba(235,175,60,.20)}
  .tc-mythic   {--rarglow:rgba(235,90,90,.20)}
  .tc-grail    {--rarglow:rgba(190,180,255,.30)}
  .tc-line{color:#9aa4bd;letter-spacing:2px;font-size:10.2px;margin-bottom:8px}
  .g-row{display:flex;align-items:center;gap:8px;margin:4px 0}
  .g-row span{width:34px;color:#9aa4bd;font-size:10.2px}
  /* The signature element: never a continuous fill. A track of discrete
     slots; filled ones take the card's rarity colour, empty ones are LCD
     hairline. Count = round(value/max x slots), so the squares are countable
     and agree with the number beside them. */
  .g-row .dots{flex:1;display:flex;gap:2px}
  .g-row .dots i{width:6px;height:12px;background:var(--rarc,#E4E7EC);
    animation:slotIn 1ms steps(1) both}
  @keyframes slotIn{from{opacity:0}}
  .g-row .dots i:nth-child(1){animation-delay:0ms}
  .g-row .dots i:nth-child(2){animation-delay:30ms}
  .g-row .dots i:nth-child(3){animation-delay:60ms}
  .g-row .dots i:nth-child(4){animation-delay:90ms}
  .g-row .dots i:nth-child(5){animation-delay:120ms}
  .g-row .dots i:nth-child(6){animation-delay:150ms}
  .g-row .dots i:nth-child(7){animation-delay:180ms}
  .g-row .dots i:nth-child(8){animation-delay:210ms}
  .g-row .dots i:nth-child(9){animation-delay:240ms}
  .g-row .dots i:nth-child(10){animation-delay:270ms}
  .g-row .dots i:nth-child(11){animation-delay:300ms}
  .g-row .dots i:nth-child(12){animation-delay:330ms}
  .g-row .dots i:nth-child(13){animation-delay:360ms}
  .g-row .dots i:nth-child(14){animation-delay:390ms}
  .g-row .dots i:nth-child(15){animation-delay:420ms}
  .g-row .dots i:nth-child(16){animation-delay:450ms}
  .g-row .dots i:nth-child(17){animation-delay:480ms}
  .g-row .dots i:nth-child(18){animation-delay:510ms}
  .g-row .dots i:nth-child(19){animation-delay:540ms}
  .g-row .dots i:nth-child(20){animation-delay:570ms}
  @media (prefers-reduced-motion: reduce){.g-row .dots i{animation:none}}
  .g-row .dots i.off{background:var(--line)}
  .g-row b{width:44px;text-align:right;font-weight:normal}
  /* Section headers earn their space with a hairline rule instead of a gap, so
     the eye gets a clear break without the card growing taller. */
  .tc-sec{color:var(--gold);letter-spacing:2px;font-size:10.2px;margin:9px 0 4px;
    padding-bottom:3px;border-bottom:1px solid rgba(255,255,255,.10)}
  .tc-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px 14px}
  .tc-grid .srow{border:0;padding:2px 0}
  /* Values line up in a column and digits share a width, so the block scans as
     a table rather than as ragged text. */
  .tc-grid .srow b{font-variant-numeric:tabular-nums;text-align:right}
  .tc-xp{color:var(--gold)}
  .tc-serial{display:flex;align-items:center;background:#8f929a;border:2px solid var(--ink);
    border-radius:0 0 2px 2px;margin-top:4px;padding:5px 10px 4px}
  .tc-serial .sn{font-size:11.4px;color:#2e3340;letter-spacing:1px}
  /* six 8px pips are an honest visual (rarity tier, filled left to right) but
     a rough tap target — the same invisible 44px hit-area expansion used on
     the small header/nav buttons elsewhere, so tapping it to ask "what does
     this mean?" doesn't take three tries */
  .tc-serial .sq{margin-left:auto;display:flex;gap:2px;position:relative}
  .tc-serial .sq::before{content:"";position:absolute;left:50%;top:50%;
    transform:translate(-50%,-50%);width:max(100%,44px);height:44px}
  /* pips take the tier metal; hollow = the tiers above this catch */
  .tc-serial .sq i{width:8px;height:8px;background:var(--frd,#3F7A70);border:2px solid var(--ink)}
  .tc-serial .sq i.e{background:transparent}
  .addnote{font-size:11.4px;color:var(--muted);letter-spacing:2px;background:none;border:none}
  /* on the dark LCD panels/dialogs the casing-muted tone vanishes — lift it */
  .bboard .addnote,.gdlg .addnote,.dpanel .addnote,.tcard .addnote{color:#9db3d6}
  .notebox{width:100%;max-width:400px;background:var(--paper2);border:3px solid var(--ink);
    padding:9px;font-family:inherit;font-size:11.4px;color:var(--ink)}
  .cardbtns{display:flex;gap:10px}
  .cardbtns .btn{font-size:14.4px;padding:12px 16px}
  .cardbtns .sq2{padding:12px 13px}
  /* DECK: swipeable card carousel */
  .deckwrap{position:fixed;inset:0;z-index:70;background:var(--paper-overlay);
    display:flex;flex-direction:column;padding:18px 0 24px}
  .deckcount{text-align:center;font-size:15.6px;color:var(--ink);letter-spacing:2px;margin-bottom:10px}
  .deckrail{flex:1;display:flex;overflow-x:auto;scroll-snap-type:x mandatory;gap:18px;
    padding:0 8vw;scrollbar-width:none;align-items:flex-start}
  .deckrail::-webkit-scrollbar{display:none}
  .deckslot{flex:none;width:84vw;max-width:400px;scroll-snap-align:center}
  .deckempty{margin:auto;font-size:12.6px;color:var(--navy);letter-spacing:1px;line-height:2.4;text-align:center}

  /* ---------- HOME ---------- */
  /* Restyled as a vintage motor-oil-can label: riveted tin corners (reusing
     .rivet from the .dcard trading card), a checkered-flag strip, a foil
     pinstripe under the masthead, a chrome instrument bezel round the
     photo, and a wax-seal medallion — all built from tokens already used
     for "shiny" elsewhere (--glint/--band/--dither, the holosheen sweep)
     rather than new gradients, so it reads as the same object as every
     other panel in this world, just in its dressiest trim. The seal and
     checker are ::after/::before, not new elements — border-radius is
     forced to 0 sitewide (line 293), so the seal is notched, not round. */
  .license{background:var(--paper2);border:3px solid var(--ink);margin-bottom:14px;
    position:relative;
    box-shadow:4px 4px 0 -1px var(--paper2),4px 4px 0 1px var(--ink),
               8px 8px 0 -2px var(--paper2),8px 8px 0 0 var(--ink)}
  /* checkered-flag strip along the very top edge — fixed black/white on
     purpose (not --ink, which turns pale in dark mode and would wash the
     checker out to near-invisible) since a "checkered flag" has to stay
     black and white regardless of theme */
  .license::before{content:"";display:block;height:7px;border-bottom:2px solid var(--ink);
    background:conic-gradient(var(--ink-fixed) 90deg,var(--wht) 90deg 180deg,var(--ink-fixed) 180deg 270deg,var(--wht) 270deg);
    background-size:7px 7px}
  /* wax-seal medallion hung off the corner, like a quality seal foil-
     stamped onto a can — sits OUTSIDE the card's own box so it never
     collides with real content (the serial number, the photo, the rows).
     --ink-fixed/--gold instead of --ink/--paper: gold never changes with
     theme, so the text on it can't either. */
  .license::after{content:"\2605\A CERTIFIED";white-space:pre;position:absolute;top:-13px;right:-13px;z-index:6;
    transform:rotate(9deg);width:26px;height:26px;clip-path:var(--notch);
    display:flex;align-items:center;justify-content:center;text-align:center;
    background:var(--gold);background-image:var(--glint),var(--band);
    background-size:100% 100%,100% 100%;background-repeat:no-repeat,no-repeat;
    border:2px solid var(--ink-fixed);color:var(--ink-fixed);padding:1px;
    font-size:5.4px;font-weight:900;letter-spacing:.2px;line-height:1.3;
    box-shadow:2px 2px 0 rgba(0,0,0,.4)}
  .license .lh{background:var(--red);border-bottom:2px solid var(--ink);color:var(--wht);font-size:12.6px;
    padding:6px 9px 5px;letter-spacing:1px;position:relative;overflow:hidden;
    background-image:var(--glint),var(--band),var(--dither);
    background-size:100% 100%,100% 100%,6px 6px;background-repeat:no-repeat,no-repeat,repeat;
    box-shadow:0 2px 0 0 var(--gold),0 4px 0 0 var(--ink)}
  /* the same "occasional light sweep" already used on .holostrip/.dhero —
     a foil label catches the light once in a while, not constantly */
  .license .lh::before{content:"";position:absolute;inset:0;pointer-events:none;
    background:linear-gradient(105deg,transparent 0 42%,rgba(255,255,255,.55) 42% 58%,transparent 58% 100%);
    animation:holosheen 11s steps(10) infinite}
  .license .lb{display:flex;gap:8px;padding:10px 9px 7px}
  /* the photo sits in a chrome instrument bezel instead of a flat border —
     the same steel/band treatment as every gauge and badge in this world */
  .license .lphotoframe{flex:none;width:72px;height:56px;padding:3px;position:relative;
    background:#8C93A0;background-image:var(--glint),var(--band),var(--dither);
    background-size:100% 100%,100% 100%,5px 5px;background-repeat:no-repeat,no-repeat,repeat;
    border:2px solid var(--ink)}
  .license .lphoto{width:100%;height:100%;display:block;border:2px solid var(--ink);
    object-fit:cover;background:var(--panel)}
  .license .lrows{flex:1;font-size:9.6px;min-width:0}
  .lrow{display:flex;gap:5px;padding:1px 0}
  .lrow span{color:var(--red-dk);width:46px;flex:none}
  /* dark theme: the license keeps its dark-red labels (RANK/BIX/BADGES/SIGNATURE,
     all via --red-dk) but #98261A on the dark paper is near-invisible — brighten
     the token INSIDE the card only, so every label, inline or not, follows */
  :root[data-theme="dark"] .license{--red-dk:#E8705F}
  .lname{color:var(--gold-dk);font-size:12px;margin-bottom:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .barcode{height:14px;margin:0 9px 6px;background:repeating-linear-gradient(90deg,var(--ink) 0 2px,transparent 2px 5px,var(--ink) 5px 6px,transparent 6px 10px)}
  /* 24px side padding: the two bottom rivets (.rivet.r3/.r4, 8px squares at
     left/right 10px) used to sit on top of the first and last characters */
  .lfoot{display:flex;justify-content:space-between;background:var(--navy-dk);color:var(--wht);
    border-top:2px solid var(--ink);font-size:9.6px;padding:5px 24px 4px;letter-spacing:1px}
  .lxp{height:6px;margin:0 9px 6px;background:var(--panel);border:2px solid var(--ink);overflow:hidden}
  .lxp i{display:block;height:100%;background:var(--gold);transition:width 300ms steps(8)}
  /* the license itself ranks up: trim recolours at level 10/25/50/75/100 */
  .license.lt-bronze .lh{background:#8C5A2B}   .license.lt-bronze{border-color:#5a3a1c}
  .license.lt-silver .lh{background:#6E7B8A}   .license.lt-silver{border-color:#465059}
  .license.lt-gold .lh{background:var(--gold);color:#141C27} .license.lt-gold{border-color:#7a651a}
  .license.lt-plat .lh{background:#AFC5CE;color:#141C27}     .license.lt-plat{border-color:#5d7079}
  .license.lt-mythic .lh{background:var(--c-mythic)}              .license.lt-mythic{border-color:var(--c-mythic)}
  .license.lt-bronze .lh::after{content:" \00B7 BRONZE"}
  .license.lt-silver .lh::after{content:" \00B7 SILVER"}
  .license.lt-gold .lh::after{content:" \00B7 GOLD"}
  .license.lt-plat .lh::after{content:" \00B7 PLATINUM"}
  .license.lt-mythic .lh::after{content:" \00B7 MYTHIC"}
  /* ---------- home: sync strip + today board + dailies keypad ---------- */
  /* CLOUD SAVE instrument module — the LED, the state line and the link
     button live in one dark panel instead of floating loose on the casing */
  #savebox{background:var(--panel);border:3px solid var(--ink);margin-bottom:16px;
    padding:9px 12px 11px;color:var(--wht);
    box-shadow:inset 2px 2px 0 rgba(255,255,255,.06),inset -2px -2px 0 rgba(0,0,0,.35),
    3px 3px 0 rgba(0,0,0,.3)}
  #savebox .svcap{font-size:9.6px;color:var(--gold);letter-spacing:2px;margin-bottom:8px;
    display:flex;align-items:center;gap:8px}
  #savebox .svcap::after{content:"";flex:1;height:2px;
    background:repeating-linear-gradient(90deg,var(--line) 0 4px,transparent 4px 8px)}
  #syncrow{display:flex;gap:8px;align-items:center}
  #syncrow #syncstate{flex:1;font-size:9.6px;letter-spacing:1px;color:#9aa4bd;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* a real instrument light: square, hard border, steps() blink while working */
  #syncled{flex:none;width:9px;height:9px;border:2px solid var(--ink-fixed);background:#8A97A6;
    box-shadow:0 0 0 1px rgba(255,255,255,.12)}
  #syncled.ok{background:var(--c-uncommon)}
  #syncled.warn{background:var(--gold);animation:blink 1s steps(2) infinite}
  #syncled.err{background:var(--alert);animation:blink 1s steps(2) infinite}
  /* Today-board rows, all one grammar: an art box on the left (a real car
     sprite whenever the row is actually about one car — I Spy's target,
     Ghost's model, last year's catch, a rival's chosen ride — or a plain
     glyph when it isn't), a stamped color tag + title in the middle, and a
     stat stacked on the right. Same bones as .arow (the leaderboard row) so
     the whole board reads as one instrument instead of a pile of one-off
     lines. */
  .qcard{display:flex;align-items:center;gap:10px;padding:9px 12px;font-size:10.2px;
    color:var(--wht);background:rgba(255,255,255,.03);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.06)}
  .qcard .qicon{flex:none;width:44px;height:33px;border:2px solid var(--line);
    background:#1a2030;display:flex;align-items:center;justify-content:center;overflow:hidden}
  .qcard .qicon img{width:100%;height:100%;object-fit:cover;image-rendering:pixelated}
  .qcard .qicon b{font-family:var(--font-display);font-weight:400;font-size:22.8px;color:#fff;line-height:1}
  /* Call It has no single target car — a nested-square target stands in
     (rectilinear on purpose: the device never draws a curve) */
  .qcard .qtarget{width:22px;height:22px;background:var(--qc);position:relative}
  .qcard .qtarget i{position:absolute;inset:5px;background:#1a2030;display:block}
  .qcard .qtarget i::after{content:"";position:absolute;inset:4px;background:var(--qc)}
  .qcard .qi{flex:1;min-width:0;line-height:1.5}
  .qcard .qtag{display:inline-block;font-size:9px;letter-spacing:1px;font-weight:700;
    line-height:1;color:#fff;background:var(--qc,#8CA6C4);padding:3px 5px 2px;
    border:1.5px solid rgba(16,20,31,.85);box-shadow:0 1px 0 rgba(16,20,31,.5);margin-bottom:4px}
  /* white 7.5px text on the lighter accent plates (steel blue, uncommon
     green) measured 2.5-3:1 — darken the plate under the text, keep the
     accent identity. Plain background above stays as the pre-color-mix
     fallback for old engines. */
  @supports (background:color-mix(in srgb,red,black)){
    .qcard .qtag{background:color-mix(in srgb,var(--qc,#8CA6C4),#000 38%)}
  }
  .qcard .qtitle{color:#f2f4fa;letter-spacing:.3px;line-height:1.6}
  .qcard .qstat{flex:none;text-align:right;color:#8A97A6;font-size:9.6px;letter-spacing:.5px;line-height:1.6}
  .qcard .qstat b{display:block;font-size:12.6px;color:var(--gold);font-variant-numeric:tabular-nums}
  #ispyrow .qcard{--qc:var(--red)}
  #callrow .qcard{--qc:var(--c-uncommon)}
  #otdbox .qcard{--qc:var(--gold-dk)}
  #rivalbox .qcard{--qc:#5C7AA0}
  /* the featured car gets a faint gold wash — it's the board's headline */
  #cotd .arow{background:linear-gradient(90deg,rgba(201,162,39,.09),transparent 65%)}
  /* ---------- season banner ---------- */
  /* The quarter's event as a rally plate instead of a sentence: the season's
     motif chip in its accent color, the theme, and honest stepped progress
     bars for the season medal and the era-month stamp. The whole plate is a
     button into the Trophy Room. Everything here sits on the dark LCD panel,
     so the colors are theme-constant on purpose. */
  .seasonband{display:block;text-align:left;background:var(--panel2);
    border:2px solid var(--line);margin:4px 12px 10px;width:calc(100% - 24px);
    padding:0;font-family:inherit;color:var(--wht);cursor:pointer;
    box-shadow:inset 0 2px 0 rgba(255,255,255,.05),inset 0 -2px 0 rgba(0,0,0,.3)}
  .seasonband:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  .seasonband .sbtop{display:flex;align-items:center;gap:9px;padding:8px 10px;
    border-bottom:2px solid var(--line)}
  .seasonband .sbchip{flex:none;width:32px;height:32px;background:var(--seasonc,var(--gold));
    border:2px solid var(--ink-fixed);display:flex;align-items:center;justify-content:center;
    box-shadow:inset 2px 2px 0 rgba(255,255,255,.25),inset -2px -2px 0 rgba(0,0,0,.28)}
  .seasonband .sbchip svg{display:block}
  .seasonband .sbname{font-family:var(--font-display);font-weight:400;font-size:25.2px;
    line-height:1;color:var(--wht);display:block}
  .seasonband .sbsub{font-size:9px;color:var(--lcd-dim);letter-spacing:1.5px;
    display:block;margin-top:3px}
  .seasonband .sbdays{margin-left:auto;font-size:9.6px;color:var(--lcd-dim);
    letter-spacing:1px;text-align:right;line-height:1.9;flex:none}
  .seasonband .sbdays b{color:var(--gold);display:block;letter-spacing:1px}
  .seasonband .sbrows{display:flex;flex-direction:column;gap:8px;padding:8px 10px 10px}
  .seasonband .sblbl{display:flex;justify-content:space-between;align-items:baseline;
    font-size:9.6px;letter-spacing:1.2px;color:var(--lcd-dim);margin-bottom:4px}
  .seasonband .sblbl b{color:var(--wht);font-variant-numeric:tabular-nums;letter-spacing:.5px}
  .seasonband .sblbl b.sbdone{color:var(--gold)}
  .seasonband .sbbar{display:block;height:8px;background:#05070c;border:2px solid var(--ink-fixed);overflow:hidden}
  .seasonband .sbbar i{display:block;height:100%;background:var(--seasonc,var(--gold));
    transition:width .4s steps(8)}
  .seasonband .sbbar.gold i{background:var(--gold)}
  .dailygrid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin:10px 12px}
  .dtile{background:var(--panel2);border:2px solid var(--line);padding:9px 10px;cursor:pointer;
    box-shadow:inset 0 2px 0 rgba(255,255,255,.05),inset 0 -2px 0 rgba(0,0,0,.3)}
  .dtile .dt-n{font-size:9.6px;color:var(--lcd-dim);letter-spacing:1.5px}
  .dtile .dt-v{font-family:var(--font-display);font-size:19.2px;color:var(--gold);margin-top:3px;
    line-height:1.1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .dtile .dt-s{font-size:9.6px;color:#8A97A6;margin-top:3px;letter-spacing:.5px}
  .dtile.done{opacity:.55}
  .dtile.done .dt-v{color:var(--c-uncommon)}
  .dtile:active{transform:translate(1px,1px)}
  /* ---------- summer quests: a connected trail, not a checklist ---------- */
  .qhero{background:var(--panel2);border:2px solid var(--line);padding:10px 12px;
    margin:12px 12px 10px;
    box-shadow:inset 0 2px 0 rgba(255,255,255,.05),inset 0 -2px 0 rgba(0,0,0,.3)}
  .qherorow{display:flex;gap:16px}
  .qherostat{flex:1}
  .qherostat b{display:flex;align-items:center;gap:4px;font-family:var(--font-display);font-weight:400;
    font-size:24px;color:var(--gold);line-height:1;font-variant-numeric:tabular-nums}
  .qherostat span{display:block;font-size:8.4px;color:var(--lcd-dim);letter-spacing:1.5px;margin-top:4px}
  .qherobar{height:9px;background:#05070c;border:2px solid var(--ink-fixed);overflow:hidden;margin-top:10px}
  .qherobar i{display:block;height:100%;background:linear-gradient(90deg,var(--c-uncommon),var(--gold));
    transition:width .4s steps(10)}
  /* the trail itself: one continuous spine (the same dotted-rule technique
     .dsec already uses, just vertical) with every quest hanging off it as a
     badge. Every badge is the SAME size — milestones get a glow ring, not
     extra pixels — so the spine never has to bend around a bigger node. */
  .qtrail{position:relative;padding:6px 12px 10px}
  .qtrail::before{content:"";position:absolute;top:23px;bottom:23px;left:29px;width:3px;
    background:repeating-linear-gradient(180deg,var(--line) 0 4px,transparent 4px 8px)}
  .qnoderow+.qnoderow{margin-top:1px}
  .qnodehead{display:flex;align-items:center;gap:12px;padding:8px 4px;cursor:pointer;
    position:relative;z-index:1;background:none;border:none;width:100%;text-align:left;
    color:inherit;font-family:inherit;font-size:inherit}
  .qnodehead:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}
  .qbadge{flex:none;width:34px;height:34px;border:3px solid var(--line);background:var(--panel2);
    display:flex;align-items:center;justify-content:center;overflow:hidden;
    font-family:var(--font-display);font-weight:400;font-size:16.8px;color:var(--wht)}
  .qbadge img{width:100%;height:100%;object-fit:cover;image-rendering:pixelated}
  .qnoderow.done .qbadge{background:var(--c-uncommon);border-color:var(--c-uncommon);color:#fff}
  .qnoderow.current .qbadge{background:var(--gold);border-color:var(--gold);color:var(--ink-fixed)}
  .qnoderow.locked .qbadge{opacity:.55}
  .qnoderow.milestone .qbadge{box-shadow:0 0 0 3px rgba(201,162,39,.4)}
  .qnodetitle{flex:1;min-width:0;font-size:10.2px;color:var(--wht);letter-spacing:.2px;line-height:1.4}
  .qnoderow.done .qnodetitle{color:#9aa4bd}
  .qnoderow.locked .qnodetitle{color:#5c6579}
  .qnodemeta{flex:none;display:flex;align-items:center;gap:7px}
  .qnodemilestag{font-size:8.4px;letter-spacing:1px;color:var(--gold);font-weight:700}
  .qnodechev{flex:none;color:#6b7488;font-size:10.8px;transition:transform .15s steps(3)}
  .qnoderow.open .qnodechev{transform:rotate(90deg)}
  .qdetail{overflow:hidden;max-height:0;transition:max-height .25s steps(6);margin-left:46px}
  .qnoderow.open .qdetail{max-height:130px}
  .qdetailin{padding:9px 10px;margin:0 4px 8px;background:#10141f;border:2px solid var(--line)}
  .qdtitle{font-size:10.8px;color:#fff;letter-spacing:.3px;line-height:1.4;margin-bottom:6px}
  .qfbar{height:7px;background:#05070c;border:2px solid var(--ink-fixed);overflow:hidden}
  .qfbar i{display:block;height:100%;background:var(--gold);transition:width .3s steps(8)}
  .qfprog{font-size:9.6px;color:var(--gold);margin-top:4px;letter-spacing:.5px;font-variant-numeric:tabular-nums}
  .qftimer{font-size:9.6px;color:var(--alert);margin-top:5px;letter-spacing:.5px;font-variant-numeric:tabular-nums}
  .qfrewards{margin-top:7px;display:flex;gap:5px;flex-wrap:wrap}
  .qfdesc{font-size:9.6px;color:#c9d2e6;line-height:1.6;margin-bottom:6px}
  .qfalt{font-size:9px;color:var(--navy);line-height:1.6;margin-top:5px}
  .qchapterhead{display:flex;justify-content:space-between;align-items:baseline;
    margin:14px 4px 6px;padding:4px 8px;border-left:3px solid var(--sc-accent,var(--gold));
    background:rgba(255,255,255,.03)}
  .qnoderow:first-child+.qchapterhead,.qtrail>.qchapterhead:first-child{margin-top:0}
  .qchname{font-size:10.8px;letter-spacing:1px;color:var(--wht)}
  .qchrange{font-size:7.8px;color:#8e98b8;font-variant-numeric:tabular-nums}
  /* reward pills — one visual vocabulary for XP/Orbs/Badges everywhere they appear */
  .rchip{display:inline-flex;align-items:center;gap:3px;font-size:9px;letter-spacing:.5px;
    font-weight:700;padding:3px 6px 2px;border:1.5px solid rgba(16,20,31,.85);
    box-shadow:0 1px 0 rgba(16,20,31,.5);white-space:nowrap}
  .rc-xp{background:var(--navy);color:#fff}
  .rc-orbs{background:var(--gold);color:var(--ink-fixed)}
  .rc-badge{background:var(--c-legendary);color:#fff}
  /* ---------- summer shop: a loadout, not an equal-weight button grid ---------- */
  /* what's equipped right now is the hero of each track; the other two
     tiers sit below as a plain upgrade row, not co-equal with the one worn */
  .loadout{margin:2px 0 4px}
  .loadout-current{display:flex;align-items:center;gap:12px;background:#10141f;
    border:2px solid var(--line);padding:9px 10px;margin:0 12px 8px}
  .loadout-swatch{flex:none;width:40px;height:40px;border:3px solid rgba(16,20,31,.85)}
  .loadout-swatch.none{background:var(--panel2);display:flex;align-items:center;justify-content:center;
    color:#5c6579;font-size:19.2px;border-color:var(--line)}
  .loadout-info{flex:1;min-width:0}
  .loadout-info b{display:block;font-family:var(--font-display);font-weight:400;font-size:16.8px;
    color:#fff;letter-spacing:.5px}
  .loadout-info span{display:block;font-size:9px;color:#9aa4bd;letter-spacing:1px;margin-top:3px}
  .loadout-upgrades{display:flex;flex-wrap:wrap;gap:6px;margin:0 12px 8px}
  .loadout-upgrades .cositem{flex:1 1 64px}
  /* the 2 milestone-only tiers get their own row, visually set apart with a
     small label — buyable and earned-only shouldn't look like one undivided row */
  .loadout-exclusive{display:flex;gap:6px;margin:0 12px 14px;padding-top:8px;
    border-top:2px dashed var(--line);position:relative}
  .loadout-exclusive::before{content:"MILESTONE ONLY";position:absolute;top:-7px;left:0;
    background:var(--panel);font-size:7.8px;letter-spacing:1px;color:#9aa4bd;padding:0 4px}
  .loadout-exclusive .cositem{flex:1}
  /* the one seasonal item sits between the evergreen ladder and the
     milestone-only row — its own strip so it doesn't read as just a 5th
     rarity rung */
  .loadout-seasonal{display:flex;gap:6px;margin:0 12px 8px;padding-top:8px;
    border-top:2px dashed var(--line);position:relative}
  .loadout-seasonal::before{content:"THIS SEASON ONLY";position:absolute;top:-7px;left:0;
    background:var(--panel);font-size:7.8px;letter-spacing:1px;color:#9aa4bd;padding:0 4px}
  .loadout-seasonal .cositem{flex:1}
  .cositem{flex:1;background:var(--panel2);border:2px solid var(--line);padding:8px 6px 7px;
    text-align:center;cursor:pointer;color:var(--wht);font-family:inherit;
    box-shadow:inset 0 2px 0 rgba(255,255,255,.05),inset 0 -2px 0 rgba(0,0,0,.3)}
  .cositem:active{transform:translate(1px,1px)}
  .cositem:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  /* real material, not a flat paint chip: iron is brushed-steel diagonal
     striping, gold reuses the app's own embossed --band shading over a
     gold base, diamond is a hard-faceted diagonal gem pattern with a glow */
  .cosswatch{display:block;width:100%;height:14px;margin-bottom:5px;
    border:1.5px solid rgba(16,20,31,.85)}
  .cositem.cos-iron .cosswatch,.loadout-swatch.cos-iron{
    background:repeating-linear-gradient(115deg,#E4EBEF 0 3px,#AFC5CE 3px 6px,#8CA0AC 6px 9px)}
  .cositem.cos-gold .cosswatch,.loadout-swatch.cos-gold{
    background-color:var(--gold);background-image:var(--glint),var(--band);
    background-size:100% 100%,100% 100%;background-repeat:no-repeat,no-repeat}
  .cositem.cos-diamond .cosswatch,.loadout-swatch.cos-diamond{
    background:repeating-linear-gradient(45deg,#D8F3FF 0 3px,#6FA3C8 3px 6px,#3D7A99 6px 9px,#6FA3C8 9px 12px);
    box-shadow:0 0 6px rgba(111,163,200,.6)}
  .cositem .cosname{display:block;font-size:9.6px;letter-spacing:1px}
  .cositem .coscost{display:block;font-size:10.2px;color:var(--gold);margin-top:5px;
    font-variant-numeric:tabular-nums}
  /* the one-time explainer — a brand-new player has no reason to know what
     a 20-step ladder or an Orb even is before being told once */
  #su-intro{background:var(--panel2);border:2px solid var(--gold);margin-bottom:12px;
    padding:10px 12px;color:var(--wht);
    box-shadow:inset 0 2px 0 rgba(255,255,255,.05),inset 0 -2px 0 rgba(0,0,0,.3)}
  #su-intro b{display:block;font-size:11.4px;color:var(--gold);letter-spacing:1px;margin-bottom:6px}
  #su-intro p{font-size:10.2px;color:#c9d2e6;line-height:1.8;margin-bottom:10px}
  #su-intro .btn{width:100%}
  /* ---------- cosmetics: how a purchased tier actually shows up ---------- */
  /* profile frame — an outer ring added in FRONT of the license's existing
     stacked-paper shadow, so it reads as a case around the card, not a
     replacement for the level-tier header color (.lt-*, untouched) */
  /* a real 3-tone bezel (light/dark/color, like a machined metal or gem
     setting) instead of one flat outline — a single-color ring was the
     part that actually read as "lame" */
  .license.cos-iron{box-shadow:0 0 0 2px #E4EBEF,0 0 0 4px #7C93A0,0 0 0 6px #AFC5CE,
    5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .license.cos-gold{box-shadow:0 0 0 2px #F2DE8A,0 0 0 4px #8F7318,0 0 0 6px var(--gold),
    5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .license.cos-diamond{box-shadow:0 0 0 2px #EAFAFF,0 0 0 4px #3D7A99,0 0 0 6px #6FA3C8,
    0 0 18px 3px rgba(111,163,200,.6),
    5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .spotcard.cos-iron{box-shadow:0 0 0 2px #E4EBEF,0 0 0 4px #7C93A0,0 0 0 6px #AFC5CE,
    inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  .spotcard.cos-gold{box-shadow:0 0 0 2px #F2DE8A,0 0 0 4px #8F7318,0 0 0 6px var(--gold),
    inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  .spotcard.cos-diamond{box-shadow:0 0 0 2px #EAFAFF,0 0 0 4px #3D7A99,0 0 0 6px #6FA3C8,
    0 0 18px 3px rgba(111,163,200,.6),
    inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  /* leaderboard name — recolors just the name text */
  .nm.cos-iron-txt{color:#AFC5CE}
  .nm.cos-gold-txt{color:var(--gold)}
  .nm.cos-diamond-txt{color:#6FA3C8;text-shadow:0 0 5px rgba(111,163,200,.7)}
  /* app icon — the "›" mark by the wordmark changes glyph and color per
     tier; this is the in-app logo, not the phone's home-screen icon */
  .cart.icon-iron::before{content:"»";color:#AFC5CE}
  .cart.icon-gold::before{content:"★";color:var(--gold)}
  .cart.icon-diamond::before{content:"◆";color:#6FA3C8;text-shadow:0 0 5px rgba(111,163,200,.8)}
  /* ---------- THE VAULT: the shop's rarity ladder (replaces iron/gold/diamond) ----------
     Common/Uncommon/Rare/Epic are the only tiers Orbs can buy — same 3-tone bezel
     and diagonal-texture language as the old tiers, just walked up the bix's own
     rarity colors. Legendary/Mythic are never sold; they're earned from a quest
     milestone (see unlockExclusive() in the script) and get a hard on/off blink
     — steps(1), never a fade — so the two tiers nobody can buy their way into
     are also the only two that move. */
  .cositem.cos-common .cosswatch,.loadout-swatch.cos-common{
    background:repeating-linear-gradient(115deg,#E8E4D6 0 3px,#B3A98D 3px 6px,#8F8770 6px 9px)}
  .cositem.cos-uncommon .cosswatch,.loadout-swatch.cos-uncommon{
    background:repeating-linear-gradient(115deg,#D9ECC7 0 3px,#7FA05C 3px 6px,#4F6A3A 6px 9px)}
  .cositem.cos-rare .cosswatch,.loadout-swatch.cos-rare{
    background:repeating-linear-gradient(45deg,#CFF0EA 0 3px,#5FA89B 3px 6px,#2F5C54 6px 9px,#5FA89B 9px 12px);
    box-shadow:0 0 6px rgba(95,168,155,.55)}
  .cositem.cos-epic .cosswatch,.loadout-swatch.cos-epic{
    background:repeating-linear-gradient(45deg,#E6DCF2 0 3px,#8C6FB5 3px 6px,#4A3766 6px 9px,#8C6FB5 9px 12px);
    box-shadow:0 0 8px rgba(140,111,181,.6)}
  .cositem.cos-legendary .cosswatch,.loadout-swatch.cos-legendary{
    background-color:var(--c-legendary);background-image:var(--glint),var(--band);
    background-size:100% 100%,100% 100%;background-repeat:no-repeat,no-repeat}
  .cositem.cos-mythic .cosswatch,.loadout-swatch.cos-mythic{
    background-color:var(--c-mythic);background-image:var(--glint),var(--band);
    background-size:100% 100%,100% 100%;background-repeat:no-repeat,no-repeat}
  /* a not-yet-earned exclusive sits in the row but reads as out of reach */
  .cositem.locked{opacity:.55;filter:grayscale(.4)}
  .cositem.locked .coscost{color:#9aa4bd}
  @keyframes vaultblinkGold{0%,49%{outline-color:var(--c-legendary)}50%,100%{outline-color:transparent}}
  @keyframes vaultblinkRed{0%,49%{outline-color:var(--c-mythic)}50%,100%{outline-color:transparent}}
  .license.cos-common{box-shadow:0 0 0 2px #E8E4D6,0 0 0 4px #6B6350,0 0 0 6px var(--c-common),
    5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .license.cos-uncommon{box-shadow:0 0 0 2px #D9ECC7,0 0 0 4px #3E5C2C,0 0 0 6px var(--c-uncommon),
    5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .license.cos-rare{box-shadow:0 0 0 2px #CFF0EA,0 0 0 4px #2F5C54,0 0 0 6px var(--c-rare),
    0 0 14px 2px rgba(95,168,155,.55),
    5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .license.cos-epic{box-shadow:0 0 0 2px #E6DCF2,0 0 0 4px #4A3766,0 0 0 6px var(--c-epic),
    0 0 16px 2px rgba(140,111,181,.6),
    5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .license.cos-legendary{outline:3px solid var(--c-legendary);outline-offset:2px;
    animation:vaultblinkGold .9s steps(1) infinite;
    box-shadow:0 0 0 2px #F5E7A0,0 0 0 4px #7A5F10,0 0 0 6px var(--c-legendary),
    0 0 20px 3px rgba(201,162,39,.75),
    5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .license.cos-mythic{outline:3px solid var(--c-mythic);outline-offset:2px;
    animation:vaultblinkRed .6s steps(1) infinite;
    box-shadow:0 0 0 2px #F7C9C2,0 0 0 4px #6E1811,0 0 0 6px var(--c-mythic),
    0 0 26px 4px rgba(216,64,47,.85),
    5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .spotcard.cos-common{box-shadow:0 0 0 2px #E8E4D6,0 0 0 4px #6B6350,0 0 0 6px var(--c-common),
    inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  .spotcard.cos-uncommon{box-shadow:0 0 0 2px #D9ECC7,0 0 0 4px #3E5C2C,0 0 0 6px var(--c-uncommon),
    inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  .spotcard.cos-rare{box-shadow:0 0 0 2px #CFF0EA,0 0 0 4px #2F5C54,0 0 0 6px var(--c-rare),
    0 0 14px 2px rgba(95,168,155,.55),
    inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  .spotcard.cos-epic{box-shadow:0 0 0 2px #E6DCF2,0 0 0 4px #4A3766,0 0 0 6px var(--c-epic),
    0 0 16px 2px rgba(140,111,181,.6),
    inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  .spotcard.cos-legendary{outline:3px solid var(--c-legendary);outline-offset:2px;
    animation:vaultblinkGold .9s steps(1) infinite;
    box-shadow:0 0 0 2px #F5E7A0,0 0 0 4px #7A5F10,0 0 0 6px var(--c-legendary),
    0 0 20px 3px rgba(201,162,39,.75),
    inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  .spotcard.cos-mythic{outline:3px solid var(--c-mythic);outline-offset:2px;
    animation:vaultblinkRed .6s steps(1) infinite;
    box-shadow:0 0 0 2px #F7C9C2,0 0 0 4px #6E1811,0 0 0 6px var(--c-mythic),
    0 0 26px 4px rgba(216,64,47,.85),
    inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  .nm.cos-common-txt{color:var(--c-common)}
  .nm.cos-uncommon-txt{color:var(--c-uncommon)}
  .nm.cos-rare-txt{color:var(--c-rare);text-shadow:0 0 5px rgba(95,168,155,.6)}
  .nm.cos-epic-txt{color:var(--c-epic);text-shadow:0 0 5px rgba(140,111,181,.6)}
  .nm.cos-legendary-txt{color:var(--c-legendary);text-shadow:0 0 6px rgba(201,162,39,.8),1px 1px 0 var(--ink)}
  .nm.cos-mythic-txt{color:var(--c-mythic);text-shadow:0 0 8px rgba(216,64,47,.9),1px 1px 0 var(--ink)}
  /* ---------- Sunset Circuit: named license-frame skins share ONE
     parameterized bezel (colors set as inline custom properties — see
     applyLicenseFrame() / the leaderboard row template) instead of one
     hardcoded rule per skin ---------- */
  .license.cos-summerframe{box-shadow:0 0 0 2px var(--cf-light,#fff),0 0 0 4px var(--cf-dark,#333),
    0 0 0 6px var(--cf-mid,#999),
    5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .spotcard.cos-summerframe{box-shadow:0 0 0 2px var(--cf-light,#fff),0 0 0 4px var(--cf-dark,#999),
    0 0 0 6px var(--cf-mid,#999),
    inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  /* the two Sunset Circuit chapter-milestone exclusives — never sold,
     earned only from the Golden Hour and Midnight Cruise finales */
  /* Harvest Highway (Fall '26) milestone exclusives — same construction as the summer pair below */
  .license.cos-harvest-highway,.spotcard.cos-harvest-highway{outline:3px solid #C0713A;outline-offset:2px;
    animation:harvestHighwaySweep 3s steps(3) infinite;
    box-shadow:0 0 0 2px #F5E3CA,0 0 0 4px #6B3D1A,0 0 0 6px #C0713A,0 0 26px 4px rgba(192,113,58,.7)}
  .license.cos-harvest-highway{box-shadow:0 0 0 2px #F5E3CA,0 0 0 4px #6B3D1A,0 0 0 6px #C0713A,
    0 0 26px 4px rgba(192,113,58,.7),
    5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .spotcard.cos-harvest-highway{box-shadow:0 0 0 2px #F5E3CA,0 0 0 4px #6B3D1A,0 0 0 6px #C0713A,
    0 0 26px 4px rgba(192,113,58,.7),
    inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  @keyframes harvestHighwaySweep{0%,32%{outline-color:#C0713A}
    33%,65%{outline-color:#C9A227}66%,100%{outline-color:#8B2E2E}}
  .nm.cos-harvest-gradient-txt{background:linear-gradient(90deg,#D9532B,#C9A227,#8B2E2E,#D9532B);
    background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;
    animation:sunsetGradientShift 4s linear infinite}
  .license.cos-sunset-circuit,.spotcard.cos-sunset-circuit{outline:3px solid #E0704A;outline-offset:2px;
    animation:sunsetCircuitSweep 3s steps(3) infinite;
    box-shadow:0 0 0 2px #FFD9A0,0 0 0 4px #5B3B7A,0 0 0 6px #E0704A,0 0 26px 4px rgba(224,112,74,.7)}
  .license.cos-sunset-circuit{box-shadow:0 0 0 2px #FFD9A0,0 0 0 4px #5B3B7A,0 0 0 6px #E0704A,
    0 0 26px 4px rgba(224,112,74,.7),
    5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .spotcard.cos-sunset-circuit{box-shadow:0 0 0 2px #FFD9A0,0 0 0 4px #5B3B7A,0 0 0 6px #E0704A,
    0 0 26px 4px rgba(224,112,74,.7),
    inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  @keyframes sunsetCircuitSweep{0%,32%{outline-color:#E0704A}
    33%,65%{outline-color:#C9A227}66%,100%{outline-color:#8C6FB5}}
  .nm.cos-seafoam-txt{color:#5FA89B}
  .nm.cos-coral-txt{color:#E0704A}
  .nm.cos-lemonade-txt{color:#D9B94A}
  .nm.cos-ocean-blue-txt{color:#3E6FA8}
  .nm.cos-sunset-gradient-txt{background:linear-gradient(90deg,#E0704A,#C9A227,#8C6FB5,#E0704A);
    background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;
    animation:sunsetGradientShift 4s linear infinite}
  @keyframes sunsetGradientShift{0%{background-position:0% 50%}100%{background-position:300% 50%}}
  .cart.icon-common::before{content:"·";color:var(--c-common)}
  .cart.icon-uncommon::before{content:"»";color:var(--c-uncommon)}
  .cart.icon-rare::before{content:"●";color:var(--c-rare)}
  .cart.icon-epic::before{content:"★";color:var(--c-epic);text-shadow:0 0 5px rgba(140,111,181,.7)}
  .cart.icon-legendary::before{content:"◆";color:var(--c-legendary);text-shadow:0 0 6px rgba(201,162,39,.85)}
  .cart.icon-mythic::before{content:"✦";color:var(--c-mythic);text-shadow:0 0 8px rgba(216,64,47,.9)}
  /* ---------- the seasonal special: same 5 render surfaces as every other
     tier, ×4 fixed season looks that recur every year (see SEASON_VAULT_SPECIAL) ---------- */
  .cositem.cos-seasonal-winter .cosswatch,.loadout-swatch.cos-seasonal-winter{
    background:repeating-linear-gradient(115deg,#E4F1FB 0 3px,#6FA3C8 3px 6px,#2B5470 6px 9px)}
  .cositem.cos-seasonal-spring .cosswatch,.loadout-swatch.cos-seasonal-spring{
    background:repeating-linear-gradient(115deg,#EAF5DE 0 3px,#7FA05C 3px 6px,#355427 6px 9px)}
  .cositem.cos-seasonal-summer .cosswatch,.loadout-swatch.cos-seasonal-summer{
    background:repeating-linear-gradient(115deg,#FBE8D4 0 3px,#E0863A 3px 6px,#8F4A12 6px 9px)}
  .cositem.cos-seasonal-fall .cosswatch,.loadout-swatch.cos-seasonal-fall{
    background:repeating-linear-gradient(115deg,#F5E3CA 0 3px,#C0713A 3px 6px,#6B3D1A 6px 9px)}
  .license.cos-seasonal-winter{box-shadow:0 0 0 2px #E4F1FB,0 0 0 4px #2B5470,0 0 0 6px #6FA3C8,
    0 0 12px 2px rgba(111,163,200,.5),5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .license.cos-seasonal-spring{box-shadow:0 0 0 2px #EAF5DE,0 0 0 4px #355427,0 0 0 6px #7FA05C,
    0 0 12px 2px rgba(127,160,92,.5),5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .license.cos-seasonal-summer{box-shadow:0 0 0 2px #FBE8D4,0 0 0 4px #8F4A12,0 0 0 6px #E0863A,
    0 0 12px 2px rgba(224,134,58,.5),5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .license.cos-seasonal-fall{box-shadow:0 0 0 2px #F5E3CA,0 0 0 4px #6B3D1A,0 0 0 6px #C0713A,
    0 0 12px 2px rgba(192,113,58,.5),5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
    10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}
  .spotcard.cos-seasonal-winter{box-shadow:0 0 0 2px #E4F1FB,0 0 0 4px #2B5470,0 0 0 6px #6FA3C8,
    0 0 12px 2px rgba(111,163,200,.5),inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  .spotcard.cos-seasonal-spring{box-shadow:0 0 0 2px #EAF5DE,0 0 0 4px #355427,0 0 0 6px #7FA05C,
    0 0 12px 2px rgba(127,160,92,.5),inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  .spotcard.cos-seasonal-summer{box-shadow:0 0 0 2px #FBE8D4,0 0 0 4px #8F4A12,0 0 0 6px #E0863A,
    0 0 12px 2px rgba(224,134,58,.5),inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  .spotcard.cos-seasonal-fall{box-shadow:0 0 0 2px #F5E3CA,0 0 0 4px #6B3D1A,0 0 0 6px #C0713A,
    0 0 12px 2px rgba(192,113,58,.5),inset 2px 2px 0 rgba(255,255,255,.5),inset -2px -2px 0 rgba(0,0,0,.1),6px 6px 0 rgba(46,53,80,.8)}
  .nm.cos-seasonal-winter-txt{color:#6FA3C8;text-shadow:0 0 5px rgba(111,163,200,.6)}
  .nm.cos-seasonal-spring-txt{color:#7FA05C;text-shadow:0 0 5px rgba(127,160,92,.6)}
  .nm.cos-seasonal-summer-txt{color:#E0863A;text-shadow:0 0 5px rgba(224,134,58,.6)}
  .nm.cos-seasonal-fall-txt{color:#C0713A;text-shadow:0 0 5px rgba(192,113,58,.6)}
  .cart.icon-seasonal-winter::before{content:"●";color:#6FA3C8;text-shadow:0 0 5px rgba(111,163,200,.7)}
  .cart.icon-seasonal-spring::before{content:"·";color:#7FA05C}
  .cart.icon-seasonal-summer::before{content:"»";color:#E0863A}
  .cart.icon-seasonal-fall::before{content:"◆";color:#C0713A;text-shadow:0 0 5px rgba(192,113,58,.7)}
  /* ---------- Garage Perks: real Orb sinks that aren't a look ---------- */
  .perkgrid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin:0 12px 14px}
  .perkitem{background:var(--panel2);border:2px solid var(--line);padding:8px 8px 7px;
    text-align:left;cursor:pointer;color:var(--wht);font-family:inherit;
    box-shadow:inset 0 2px 0 rgba(255,255,255,.05),inset 0 -2px 0 rgba(0,0,0,.3)}
  .perkitem:active{transform:translate(1px,1px)}
  .perkitem:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  .perkname{display:block;font-size:9.6px;letter-spacing:1px;color:var(--gold)}
  .perkdesc{display:block;font-size:9px;color:#9aa4bd;line-height:1.5;margin-top:3px;min-height:22px}
  .perkcost{display:block;font-size:10.2px;color:var(--wht);margin-top:5px;font-variant-numeric:tabular-nums}
  .appicon-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:0 12px 14px}
  .appicon-item{background:var(--panel2);border:2px solid var(--line);padding:8px 8px 7px;
    text-align:center;cursor:pointer;color:var(--wht);font-family:inherit;
    box-shadow:inset 0 2px 0 rgba(255,255,255,.05),inset 0 -2px 0 rgba(0,0,0,.3)}
  .appicon-item:active{transform:translate(1px,1px)}
  .appicon-item:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  .appicon-item.on{border-color:var(--gold);box-shadow:inset 0 0 0 2px var(--gold)}
  .appicon-thumb{display:block;width:100%;aspect-ratio:1;image-rendering:pixelated;
    border:2px solid var(--line);margin-bottom:6px}
  .appicon-thumb-default{display:flex;align-items:center;justify-content:center;
    background:var(--panel);color:var(--gold);font-size:13.2px;letter-spacing:1px}
  .appicon-name{display:block;font-size:9px;letter-spacing:.5px;color:var(--gold);line-height:1.4}
  .appicon-cost{display:block;font-size:10.2px;color:var(--wht);margin-top:4px;font-variant-numeric:tabular-nums}
  /* ---------- Sunset Circuit: event hub, chapter journey, shop grid,
     rewards cards ---------- */
  .suhero{margin:0 0 12px;padding:16px 14px 14px;position:relative;overflow:hidden;
    background:linear-gradient(160deg,#3A2A52 0%,#7A3F5A 45%,#C9702E 78%,#E8A63E 100%);
    box-shadow:inset 0 -3px 0 rgba(0,0,0,.35),inset 0 2px 0 rgba(255,255,255,.12)}
  .suhero::before{content:"";position:absolute;inset:0;
    background:repeating-linear-gradient(180deg,rgba(255,255,255,.05) 0 2px,transparent 2px 4px);
    pointer-events:none}
  .suherotitle{font-family:var(--font-display);font-weight:400;font-size:20.4px;color:#FFF3D6;
    text-shadow:0 2px 0 rgba(0,0,0,.4);position:relative;letter-spacing:.5px}
  .suheroslogan{font-family:var(--font-prose);font-size:11.4px;color:#FBE8D4;margin-top:4px;
    font-style:italic;position:relative}
  .suherorow{display:flex;gap:14px;margin-top:14px;position:relative;flex-wrap:wrap}
  .suherostat{flex:1;min-width:80px}
  .suherostat b{display:block;font-family:var(--font-display);font-weight:400;font-size:18px;color:#fff;
    text-shadow:0 1px 0 rgba(0,0,0,.4);font-variant-numeric:tabular-nums}
  .suherostat span{display:block;font-size:7.8px;letter-spacing:1.2px;color:#FBE8D4cc;margin-top:2px}
  /* the route: four postcard-style destinations, horizontal on wide viewports,
     wrapping to a vertical stack on narrow phones — see the media query below */
  .scjourney{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin:0 12px 12px;position:relative}
  .scjourney::before{content:"";grid-column:1/-1;grid-row:1;height:3px;align-self:center;
    background:repeating-linear-gradient(90deg,var(--line) 0 6px,transparent 6px 12px);z-index:0}
  .scnode{grid-row:1;background:var(--panel2);border:2px solid var(--line);padding:8px 6px 7px;
    text-align:center;position:relative;z-index:1;border-top:3px solid var(--sc-accent,var(--line))}
  .scnode.locked{opacity:.6}
  .scnode.current{outline:2px solid var(--sc-accent,var(--gold));outline-offset:-2px}
  .scdot{width:22px;height:22px;margin:0 auto 5px;border-radius:50%;background:var(--panel);
    border:2px solid var(--sc-accent,var(--line));display:flex;align-items:center;justify-content:center;
    font-family:var(--font-display);font-weight:400;font-size:13.2px;color:var(--wht)}
  .scnode.done .scdot{background:var(--sc-accent,var(--c-uncommon));color:#0a0d14}
  .scname{font-size:8.4px;letter-spacing:.8px;color:var(--wht);line-height:1.3}
  .scrange{font-size:7.2px;color:#8e98b8;margin-top:2px;font-variant-numeric:tabular-nums}
  @media (max-width:420px){ .scjourney{grid-template-columns:1fr;gap:4px}
    .scjourney::before{display:none} }
  .szfilters{overflow-x:auto;flex-wrap:nowrap;-webkit-overflow-scrolling:touch}
  .szfilters .fbtn{flex:none;white-space:nowrap}
  .szgrid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin:0 12px 14px}
  @media (min-width:640px){ .szgrid{grid-template-columns:repeat(3,1fr)} }
  @media (min-width:960px){ .szgrid{grid-template-columns:repeat(4,1fr)} }
  .szcard{background:var(--panel2);border:2px solid var(--line);padding:8px 8px 7px;
    display:flex;flex-direction:column;gap:5px;
    box-shadow:inset 0 2px 0 rgba(255,255,255,.05),inset 0 -2px 0 rgba(0,0,0,.3)}
  .szcard.equipped{border-color:var(--gold);box-shadow:inset 0 0 0 2px var(--gold)}
  .szpreview{width:100%;height:52px;border:2px solid var(--line);overflow:hidden;
    display:flex;align-items:center;justify-content:center;color:var(--wht)}
  .szname{font-size:9.6px;letter-spacing:.5px;color:var(--wht)}
  .szcat{font-size:7.8px;letter-spacing:.5px;color:#8e98b8}
  .szdesc{font-size:9px;color:#9aa4bd;line-height:1.5}
  .szstate{font-size:8.4px;letter-spacing:.5px;font-weight:700}
  .szstate.eq{color:var(--gold)}
  .szstate.own{color:var(--c-uncommon)}
  .szrow{display:flex;gap:6px;margin-top:auto}
  .szbtn{flex:1;min-height:44px;display:flex;align-items:center;justify-content:center;
    background:var(--panel);border:2px solid var(--line);color:var(--wht);
    font-family:inherit;font-size:10.8px;letter-spacing:.5px;cursor:pointer}
  .szbtn:disabled{opacity:.55;cursor:default}
  .szbtn:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  .szbtn.accent{background:var(--gold);border-color:var(--gold);color:var(--ink-fixed);font-weight:700}
  .rwcard{background:var(--panel2);border:2px solid var(--line);border-top:3px solid var(--sc-accent,var(--line));
    padding:9px 10px;display:flex;flex-direction:column;gap:4px}
  .rwcard.earned{border-color:var(--c-uncommon)}
  .rwchip{font-size:7.8px;letter-spacing:1px;color:#8e98b8;font-weight:700}
  .rwcard.earned .rwchip{color:var(--c-uncommon)}
  .rwname{font-size:10.2px;color:var(--wht)}
  .rwdesc{font-size:9px;color:#9aa4bd;line-height:1.5}
  /* ---------- Sunset Circuit, the simple version: one big card, one big
     button, a row of dots. Big touch targets and big text throughout —
     this screen is meant to work for the least patient reader in the house. ---------- */
  .su-bigtabs{gap:8px}
  .su-bigtabs .fbtn{flex:1;font-size:14.4px;padding:12px 8px;min-height:44px}
  /* the dashboard: three stat blocks, same shape on both screens — a real
     number and a plain caption, no icon needed to read it correctly */
  .sudash{display:flex;justify-content:space-between;gap:8px;margin:0 12px 12px;
    padding:10px 4px;border-bottom:2px solid var(--line)}
  .sudashstat{flex:1;text-align:center}
  .sudashstat b{display:flex;align-items:center;justify-content:center;gap:3px;
    font-family:var(--font-display);font-weight:400;font-size:19.2px;color:var(--gold);
    font-variant-numeric:tabular-nums}
  .sudashstat span{display:block;font-size:7.8px;letter-spacing:1.5px;color:#8e98b8;margin-top:3px}
  .qbig{margin:0 12px 14px;padding:20px 16px;text-align:center;background:var(--panel2);
    border:3px solid var(--gold);box-shadow:inset 0 2px 0 rgba(255,255,255,.06),inset 0 -3px 0 rgba(0,0,0,.35)}
  .qbig.qbig-done{border-color:var(--c-legendary)}
  .qbigicon{font-size:52.8px;line-height:1;margin-bottom:8px}
  .qbigicon img{width:56px;height:56px;object-fit:cover;image-rendering:pixelated}
  .qbigicon-star{font-size:72px;color:var(--c-legendary);text-shadow:0 0 18px rgba(201,162,39,.55)}
  .qbigtitle{font-family:var(--font-display);font-weight:400;font-size:19.2px;color:var(--wht);
    line-height:1.3;text-wrap:balance}
  .qbigsub{font-size:11.4px;color:#c9d2e6;margin-top:6px;line-height:1.5}
  .qdotrow{display:flex;justify-content:center;gap:6px;margin-top:14px;flex-wrap:wrap}
  .qdot{width:18px;height:18px;border-radius:50%;background:var(--panel);border:2px solid var(--line);flex:none}
  .qdot.on{background:var(--c-uncommon);border-color:var(--c-uncommon)}
  .qdotcount{font-family:var(--font-display);font-weight:400;font-size:18px;color:var(--gold);margin-top:10px;display:block}
  .qsimpletimer{font-size:10.8px;color:var(--alert);margin-top:8px;letter-spacing:.5px}
  .qbigreward{display:flex;justify-content:center;gap:16px;margin-top:14px;font-family:var(--font-display);
    font-weight:400;font-size:16.8px;color:var(--gold)}
  .qbigbtn{width:100%;margin-top:16px;font-size:14.4px;min-height:48px}
  .qstrip{display:flex;flex-wrap:wrap;gap:4px;margin:0 12px 14px;justify-content:center}
  .qstripdot{width:8px;height:8px;border-radius:50%;background:var(--line);flex:none}
  .qstripdot.done{background:var(--c-uncommon)}
  .qstripdot.current{background:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.4)}
  .chall{font-size:11.4px;padding:8px 12px;color:var(--wht);position:relative;overflow:hidden;
  background:rgba(255,255,255,.03);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08),inset 0 -1px 0 rgba(0,0,0,.3)}
  .chall .cbar{height:8px;border:2px solid var(--line);margin-top:6px;background:var(--panel2)}
  .chall .cbar i{display:block;height:100%;background:var(--c-uncommon);transition:width 240ms steps(6)}
  .chall .xp{float:right;color:var(--gold)}
  /* daily-challenge rows get a small pressable pixel sprite instead of just a
     text row — the app draws its own icons the same way the header's speedo
     glyph is drawn (hand-placed <rect>s, crispEdges), not a bitmap asset */
  .challrow{display:flex;align-items:center;gap:10px}
  .challrow .chtext{flex:1;min-width:0}
  .chsprite{flex:none;width:30px;height:30px}
  .chsprite svg{width:100%;height:100%;display:block}
  .examspr{animation:exambob 1.4s steps(4) infinite}
  @keyframes exambob{0%,100%{transform:translateY(0)}50%{transform:translateY(-2px)}}
  .spinspr{animation:spinwheel 2.4s steps(8) infinite;transform-origin:50% 50%}
  @keyframes spinwheel{to{transform:rotate(360deg)}}
  @media (prefers-reduced-motion: reduce){.examspr,.spinspr,
    .license.cos-legendary,.license.cos-mythic,.spotcard.cos-legendary,.spotcard.cos-mythic,
    .license.cos-sunset-circuit,.spotcard.cos-sunset-circuit,.nm.cos-sunset-gradient-txt{animation:none}}
  .arow{display:flex;gap:10px;align-items:center;padding:8px 12px;font-size:11.4px;color:var(--wht)}
  /* separates the top ten from your own row when you rank below it */
  .lbgap{text-align:center;color:#5a6480;font-size:12.6px;letter-spacing:3px;padding:2px 0}
  .arow:nth-of-type(even){background:rgba(255,255,255,.045)}
  .arow img{width:56px;border:2px solid var(--line);flex:none;image-rendering:pixelated}
  .arow .ai{flex:1;line-height:1.9;min-width:0}
  .arow .ai .nm{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .arow .ai .d{color:#9aa4bd;font-size:10.2px}
  .arow .axp{color:var(--gold);font-size:12.6px;flex:none}

  /* ---------- FEED ---------- */
  /* The photo used to ride as a 110px thumbnail next to two cramped lines of
     text — everything about the catch got equal, tiny weight. Now the shot
     is the hero (full-width, square, real photo — not pixelated like the
     game's sprites) with the car's name and rarity overlaid on it, so the
     card reads at a glance the way an actual photo of a car should. */
  .fcard{background:var(--panel);border:3px solid var(--ink);
    box-shadow:inset 2px 2px 0 rgba(255,255,255,.08),inset -2px -2px 0 rgba(0,0,0,.4),4px 5px 0 rgba(20,25,38,.6);
    color:var(--wht);margin-bottom:18px;position:relative;overflow:hidden}
  /* the card's own border picks up the catch's rarity colour instead of a
     flat navy frame for every single post, common or mythic alike */
  .fcard[data-r="uncommon"]{border-color:var(--c-uncommon)}
  .fcard[data-r="rare"]{border-color:var(--c-rare)}
  .fcard[data-r="epic"]{border-color:var(--c-epic)}
  .fcard[data-r="legendary"]{border-color:var(--c-legendary)}
  .fcard[data-r="mythic"]{border-color:var(--c-mythic)}
  .fcard[data-r="grail"]{border-color:var(--c-grail)}
  .fphoto{position:relative;aspect-ratio:1;overflow:hidden;border-bottom:3px solid var(--ink);
    background-image:var(--band),var(--dither);background-size:100% 100%,6px 6px;background-repeat:no-repeat,repeat}
  .fphoto img{width:100%;height:100%;object-fit:cover;display:block}
  .fscrim{position:absolute;left:0;right:0;bottom:0;padding:24px 12px 9px;pointer-events:none;
    background:linear-gradient(to top,rgba(10,14,22,.94),rgba(10,14,22,.55) 65%,transparent)}
  .fscrim .fname{font-size:13.2px;font-weight:700;color:#fff;line-height:1.3}
  .fscrim .fmeta{display:flex;align-items:center;gap:8px;margin-top:6px;pointer-events:auto}
  .ffoot{display:flex;justify-content:space-between;align-items:baseline;padding:9px 12px 0;font-size:11.4px}
  .ffoot .fu{color:#9aa4bd}
  .ffoot .ftime{color:#5a6480;font-size:10.2px}
  .reacts{display:flex;gap:8px;padding:9px 12px 12px}
  .reacts button{flex:1;display:flex;align-items:center;justify-content:center;gap:5px;
    background:var(--panel2);border:2px solid var(--line);color:var(--wht);
    font-family:inherit;font-size:12.6px;transition:transform 100ms steps(2),background 120ms}
  .reacts button:active{transform:scale(.93)}
  .reacts button.loved{color:var(--alert);border-color:var(--red-dk);background:rgba(216,64,47,.16)}
  .reacts button.reacted{color:var(--gold);border-color:var(--gold-dk);background:rgba(201,162,39,.16)}
  .reacts button:disabled{cursor:default}
  /* emoji shower when a reaction lands — pure decoration, so reduced-motion
     users just get the count bump */
  .rburst{position:absolute;left:50%;top:0;pointer-events:none;font-size:15.6px;
    animation:rburst 1s ease-out forwards}
  @keyframes rburst{
    from{transform:translate(-50%,0) scale(.6);opacity:1}
    to{transform:translate(calc(-50% + var(--rx,0px)),-54px) scale(1.3) rotate(var(--rr,0deg));opacity:0}}
  @media (prefers-reduced-motion: reduce){.rburst{display:none}}

  /* ---------- RACE ---------- */
  .modeslide{position:relative;display:flex;background:var(--navy-dk);
    border:3px solid var(--ink);margin-bottom:14px;padding:3px;gap:3px}
  .modeslide button{flex:1;position:relative;z-index:1;background:none;border:none;
    color:#9aa4bd;font-family:var(--font-utility);font-weight:700;font-size:12.6px;
    letter-spacing:1.5px;padding:9px 0 8px;transition:color 150ms}
  .modeslide button.on{color:#fff}
  .modeslide-thumb{position:absolute;top:3px;bottom:3px;left:3px;width:calc(50% - 3px);
    background:var(--red);border:2px solid var(--ink);z-index:0;
    transition:transform 200ms cubic-bezier(.23,1,.32,1)}
  .modeslide[data-active="live"] .modeslide-thumb{transform:translateX(100%)}
  .racecard{background:var(--panel);border:3px solid var(--ink);
    box-shadow:5px 5px 0 -1px var(--paper),5px 5px 0 1px var(--ink);
    color:var(--wht);padding:12px;margin-bottom:16px}
  .racecode{font-size:26.4px;letter-spacing:4px;text-align:center;color:var(--gold);
    background:var(--panel2);border:3px dashed var(--gold-dk);padding:12px;margin:10px 0}
  .racedur{display:flex;gap:6px;flex-wrap:wrap;margin:8px 0 14px}
  .racedur button{flex:1;min-width:60px}
  .raceinp{width:100%;background:var(--navy-dk);border:3px solid var(--ink);padding:9px 10px;
    font-family:inherit;font-size:16.8px;letter-spacing:4px;text-align:center;text-transform:uppercase;
    color:var(--wht);margin-bottom:10px}
  .racetimer{text-align:center;font-size:24px;color:var(--gold);letter-spacing:2px;margin-bottom:12px}
  .racetimer.low{color:var(--alert)}
  .raceplayers{margin:10px 0}
  .raceprow{display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid rgba(255,255,255,.08);
    font-size:11.4px}
  .racestand{display:flex;align-items:center;gap:8px;padding:8px;border-bottom:1px solid rgba(255,255,255,.08);
    font-size:12.6px}
  .racestand .rk{width:22px;flex:none;color:var(--gold)}
  .racestand.win{background:rgba(227,169,44,.12)}
  .racemodes{display:flex;gap:6px;margin-bottom:10px}
  /* Live-sync badge. Sits under the header, says what it's doing, and gets out
     of the way — a pulsing dot while fetching, a green one when something new
     actually landed, then it fades. */
  #synco{position:fixed;top:calc(var(--hdrh,54px) + 6px);right:8px;z-index:40;
    display:flex;align-items:center;font-size:9.6px;letter-spacing:1.5px;
    background:var(--navy);color:#c8d0e6;border:2px solid var(--ink);
    box-shadow:2px 2px 0 rgba(0,0,0,.35);padding:4px 7px 3px;
    opacity:0;transform:translateY(-8px);pointer-events:none;
    transition:none}
  #synco.on{opacity:1;transform:none}
  #synco i{width:5px;height:5px;border-radius:50%;background:var(--gold);margin-right:5px;
    animation:syncpulse .75s steps(2) infinite}
  #synco.done{color:#bdf5d3}
  #synco.done i{background:#5dff9b;animation:syncpop 160ms steps(3)}
  @keyframes syncpulse{0%,100%{opacity:.3;transform:scale(.75)}50%{opacity:1;transform:scale(1.2)}}
  @keyframes syncpop{from{transform:scale(0);opacity:0}}
  @media (prefers-reduced-motion:reduce){
    #synco i{animation:none}
  }
  /* live race standings — a scoreboard, not a track */
  /* Scavenger GP spec sheet + Last Standing cut list */
  .rsheet{border:3px solid var(--ink);background:var(--panel);margin-bottom:10px;padding:8px 10px}
  .rsheet .rsh{display:flex;justify-content:space-between;font-size:9.6px;letter-spacing:1px;color:#9aa4bd;margin-bottom:6px}
  .rsbox{display:flex;align-items:center;gap:8px;padding:5px 0;border-bottom:1px solid rgba(255,255,255,.08);font-size:11.4px;color:var(--wht)}
  .rsbox:last-child{border-bottom:none}
  .rsbox i{width:16px;height:16px;border:2px solid var(--ink);background:#10141f;flex:none;
    display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-style:normal;color:#0e1a08}
  .rsbox.on i{background:var(--c-uncommon)}
  .rsbox.on{color:#c9e2b8}
  .rsbox .rsw{margin-left:auto;font-size:9.6px;color:#9aa4bd;letter-spacing:.5px}
  .relim{border:3px solid var(--ink);background:var(--panel);margin-bottom:10px;padding:8px 10px;font-size:10.8px;letter-spacing:1px}
  .relim .rnext{display:flex;justify-content:space-between;color:#ffe08a;font-size:12.6px}
  .relim .rnext.low{color:var(--alert);animation:rblink 1s steps(2) infinite}
  @keyframes rblink{50%{opacity:.35}}
  .relim .rcuts{margin-top:6px;color:#9aa4bd;font-size:9.6px;line-height:1.7}
  .relim .rcuts s{color:#6b7590}
  .rcut{background:var(--alert);border:3px solid var(--ink);color:#fff;text-align:center;padding:10px;
    margin-top:10px;font-size:11.4px;letter-spacing:1px}
  .rbrow.out{opacity:.45}
  .rbrow.out .rbname{text-decoration:line-through}
  /* Spotter League */
  .lladder{display:flex;gap:4px;margin:2px 0 8px}
  .ltier{flex:1;text-align:center;font-size:9.6px;letter-spacing:1px;padding:5px 0 4px;
    border:2px solid var(--ink);background:#10141f;color:#6b7590}
  .ltier.on{background:var(--lt);color:#0e1218;border-color:var(--ink);box-shadow:inset 0 -3px 0 rgba(0,0,0,.25)}
  .lrow .lpos{width:26px;flex:none;font-size:12.6px;color:#9aa4bd}
  .lrow.lup{box-shadow:inset 4px 0 0 var(--c-uncommon)}
  .lrow.ldown{box-shadow:inset 4px 0 0 var(--red)}
  .lrow.me{outline:2px solid var(--gold);outline-offset:-2px}
  .lmv{font-style:normal;font-size:9.6px;margin-left:4px}
  .lmv.up{color:var(--c-uncommon)} .lmv.down{color:var(--alert)}
  .lmove{font-size:10.2px;letter-spacing:1px;padding:6px 8px;border:2px solid var(--ink);margin-bottom:8px}
  .lmove.up{background:rgba(127,160,92,.18);color:#c9e2b8}
  .lmove.down{background:rgba(216,64,47,.18);color:#f3b9b0}
  /* Club Clash */
  .clash{border:3px solid var(--ink);background:#10141f;padding:8px 10px;margin-bottom:12px}
  .clhead{display:flex;justify-content:space-between;font-size:10.2px;letter-spacing:1px;color:#ffe08a;margin-bottom:6px}
  .clpair{padding:6px 0;border-top:1px solid rgba(255,255,255,.08)}
  .clpair.big{border-top:none;padding-top:2px}
  .clrow{display:flex;justify-content:space-between;align-items:center;gap:6px;font-size:11.4px;color:var(--wht)}
  .clrow .mine{color:var(--gold)}
  .clrow.sub{font-size:9px;color:#9aa4bd;letter-spacing:.5px;margin-top:3px}
  .clvs{font-size:9px;color:#6b7590;letter-spacing:2px;flex:none}
  .cltug{position:relative;height:12px;border:2px solid var(--ink);background:#33465F;margin-top:5px}
  .clpair.big .cltug{height:18px}
  .cltug i{display:block;height:100%;background:var(--gold);transition:width .6s cubic-bezier(.23,1,.32,1)}
  .cltug em{position:absolute;top:-4px;bottom:-4px;width:3px;background:#fff;transform:translateX(-50%);transition:left .6s cubic-bezier(.23,1,.32,1)}
  .clothers .clpair{opacity:.8}
  .clothers .clrow{font-size:9.6px}
  .cln{display:flex;justify-content:space-between;font-size:10.2px;color:var(--wht)}
  .cln span{color:#9aa4bd;font-size:9px}
  /* Bix duel */
  .cmpscore{display:flex;align-items:center;justify-content:space-between;margin:4px 0 6px}
  .cmpscore>div{text-align:center}
  .cmpscore b{display:block;font-size:26px;line-height:1;color:var(--wht)}
  .cmpscore span{font-size:9px;letter-spacing:1px;color:#9aa4bd}
  .cmpv{font-size:11.4px;letter-spacing:1px;border:2px solid var(--ink);padding:6px 8px;background:#10141f}
  .cmpgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}
  .cmptile{position:relative;background:#10141f;border:2px solid var(--ink);padding:4px 3px 3px;text-align:center}
  .cmptile.dim{opacity:.6}
  .cmptile img{width:100%;height:30px;object-fit:contain;image-rendering:pixelated}
  .cmpn{font-size:7.8px;letter-spacing:.5px;color:var(--wht);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}
  .cmpr{font-size:7.2px;letter-spacing:.5px}
  .cmpstar{position:absolute;top:-2px;right:-2px;width:20px;height:20px;background:#10141f;border:2px solid var(--ink);
    color:#9aa4bd;font-family:inherit;font-size:11px;padding:0;line-height:1}
  .cmpstar.on{color:var(--gold)}
  .rboard{border:3px solid var(--ink);background:var(--panel);margin-bottom:10px}
  .rbhead{display:flex;align-items:center;gap:6px;padding:6px 8px 5px;background:var(--navy);
    color:#c8d0e6;font-size:9.6px;letter-spacing:1.5px;border-bottom:2px solid var(--ink)}
  .rbrow{display:flex;align-items:center;gap:6px;padding:6px 8px;
    border-bottom:1px solid rgba(255,255,255,.07)}
  .rbrow:last-child{border-bottom:0}
  .rbrow.me{background:rgba(240,193,75,.13);outline:2px solid var(--gold);outline-offset:-2px}
  .rbpos{width:24px;flex:none;font-size:14.4px}
  .rbname{flex:1;min-width:0;font-size:12.6px;color:#e8ecf5;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .rbname i{color:var(--gold);font-style:normal;font-size:10.2px}
  .rbname em{display:block;font-style:normal;font-size:9.6px;color:#8b96b8;
    letter-spacing:1px;margin-top:2px}
  .rblv{width:52px;flex:none;text-align:right;font-size:11.4px;color:var(--gold)}
  .rblv i{display:block;font-style:normal;font-size:10.2px;color:var(--pink)}
  .rbxp{width:58px;flex:none;text-align:right;font-size:15.6px;color:#fff;
    font-variant-numeric:tabular-nums}

  /* ---------- MAP ---------- */
  .mapframe{border:4px solid var(--red);outline:3px solid var(--ink);position:relative;
    box-shadow:5px 5px 0 rgba(46,53,80,.7);overflow:hidden}
  /* the map tab must fit one screen: viewport minus header, title, legend,
     nav and frame chrome. clamp keeps it sane on short landscape screens. */
  #map{height:clamp(300px,calc(100vh - 330px),700px);height:clamp(300px,calc(100dvh - 330px),700px);background:#e9e5dc;z-index:1}
  #map.darkmap{background:#191d28}
  /* keep Leaflet's transform transition — overriding it kills animated zoom */
  #map .leaflet-tile-pane{filter:saturate(.75) contrast(1.06) brightness(1.02);
    transition:none}
  #map.darkmap .leaflet-tile-pane{filter:invert(1) hue-rotate(180deg) saturate(.6) brightness(.92) contrast(1.05)}
  #map.darkmap+.mapui #mdark, #map.darkmap~.mapui #mdark{background:var(--gold);color:var(--ink-fixed)}
  .leaflet-control-zoom{display:none}
  .mapui{position:absolute;top:10px;right:10px;z-index:1000;display:flex;flex-direction:column;
    align-items:center;gap:8px}
  .mapui button{width:36px;height:36px;background-color:var(--navy-dk);border:3px solid var(--ink);
    box-shadow:0 4px 0 var(--ink),inset 0 2px 0 rgba(255,255,255,.25);color:var(--wht);
    font-family:inherit;font-size:18px;line-height:1;position:relative;overflow:hidden;
    clip-path:var(--notch);
    background-image:var(--glint),var(--band),var(--dither);
    background-size:100% 100%,100% 100%,6px 6px;background-repeat:no-repeat,no-repeat,repeat}
  .mapui button:active{transform:translateY(3px);box-shadow:0 1px 0 var(--ink)}
  .mapui button.loc{background-color:var(--red)}
  .zoomrail{background-color:var(--navy-dk);border:3px solid var(--ink);
    box-shadow:inset 0 3px 0 rgba(0,0,0,.35);padding:8px 0;width:36px;
    display:flex;justify-content:center;position:relative;overflow:hidden;
    background-image:var(--band),var(--dither);
    background-size:100% 100%,6px 6px;background-repeat:no-repeat,repeat}
  #zoomslider{-webkit-appearance:none;appearance:none;width:150px;height:14px;
    background:transparent;transform:rotate(270deg);transform-origin:center;margin:68px -57px}
  #zoomslider::-webkit-slider-runnable-track{height:8px;background:var(--panel);
    border:2px solid var(--ink);
    background-image:repeating-linear-gradient(90deg,var(--line) 0 2px,transparent 2px 12px)}
  #zoomslider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:18px;height:22px;
    background:var(--red);border:3px solid var(--ink);margin-top:-9px;
    box-shadow:inset 0 2px 0 rgba(255,255,255,.3);cursor:pointer;
    transition:none}
  #zoomslider:active::-webkit-slider-thumb{transform:scale(1.25);
    box-shadow:inset 0 2px 0 rgba(255,255,255,.3)}
  #zoomslider::-moz-range-track{height:8px;background:var(--panel);border:2px solid var(--ink)}
  #zoomslider::-moz-range-thumb{width:14px;height:18px;border-radius:0;
    background:var(--red);border:3px solid var(--ink);cursor:pointer}
  .leaflet-popup-content-wrapper{background:var(--panel);color:var(--wht);border:3px solid var(--ink);
    border-radius:0;box-shadow:4px 4px 0 rgba(20,25,38,.5)}
  .leaflet-popup-content{margin:10px 12px;line-height:1.8}
  .leaflet-popup-tip{background:var(--ink);border-radius:0}
  .leaflet-popup-close-button{color:var(--wht)!important;font-size:19.2px!important;padding:6px!important}
  .leaflet-container{font-family:var(--font-display)}
  .leaflet-control-attribution{font-family:system-ui,sans-serif!important;font-size:12.6px!important;
    background:rgba(239,236,227,.85)!important}
  .mapnote{display:none}
  .pinbig{display:inline-flex;align-items:baseline;gap:6px}
  /* the .status bullet reads as a stray dot next to the big number — the id
     outranks whatever selector paints it */
  #pincount::before,.pinbig::before{content:none!important}
  .pinbig b{font-family:var(--font-display);font-weight:400;font-size:31.2px;line-height:1;
    color:var(--gold);font-variant-numeric:tabular-nums}
  .pinbig i{font-style:normal;font-size:12.6px;letter-spacing:.14em}
  /* key for the tier-coloured pins */
  .maplegend{display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px}
  .maplegend span{display:flex;align-items:center;gap:4px;background:var(--panel);
    border:2px solid var(--ink);color:#c9d2e6;font-size:10.2px;letter-spacing:1px;
    padding:4px 6px 3px;font-variant-numeric:tabular-nums}
  .maplegend i{width:8px;height:8px;display:block;border:2px solid rgba(8,10,16,.6)}
  /* map popups, in the app's own voice rather than Leaflet's default white box */
  .leaflet-popup-content-wrapper{background:var(--paper);border:3px solid var(--ink);
    border-radius:4px;box-shadow:3px 3px 0 rgba(46,53,80,.55)}
  .leaflet-popup-content{margin:9px 10px;font-family:var(--font-display)}
  .leaflet-popup-tip{background:var(--paper);border:3px solid var(--ink)}
  .mpop{min-width:132px;text-align:center}
  .mpop-art{border:2px solid var(--ink);background:#0a0d14;height:56px;margin-bottom:7px;
    display:flex;align-items:center;justify-content:center;overflow:hidden}
  .mpop-art img{width:100%;height:100%;object-fit:cover;display:block}
  .mpop-art img.sprite{object-fit:contain;image-rendering:pixelated;padding:3px}
  .mpop-mk{font-size:10.2px;color:var(--navy);letter-spacing:1.5px}
  .mpop-nm{font-size:11.4px;color:var(--ink);letter-spacing:.5px;margin:3px 0 6px;line-height:1.4}
  .mpop .tierbadge{margin:0 0 8px;font-size:9.6px;padding:4px 7px 3px;border-width:2px;
    display:inline-block}
  .mpop-btn{display:block;width:100%;font-size:10.2px;padding:8px 6px 7px;box-shadow:0 3px 0 var(--ink)}
  .mpop-amap{margin-top:6px;text-decoration:none;box-sizing:border-box;color:#fff!important}
  /* cluster popup: one row per car at the spot, every row opens its card */
  .mpop-multi{min-width:180px}
  .mpop-list{max-height:190px;overflow-y:auto;margin:8px 0 8px;border:2px solid var(--ink)}
  .mprow{display:flex;align-items:center;gap:7px;width:100%;background:var(--panel);
    border:0;border-bottom:2px solid var(--line);padding:6px 7px;cursor:pointer;
    font-family:var(--font-utility);text-align:left}
  .mprow:last-child{border-bottom:0}
  .mprow:active{background:var(--panel2)}
  .mprow-art{flex:none;width:44px;height:26px;background:#0a0d14;border:2px solid var(--line);
    display:flex;align-items:center;justify-content:center;overflow:hidden}
  .mprow-art img{width:100%;height:100%;object-fit:contain;image-rendering:pixelated}
  .mprow-nm{flex:1;min-width:0;font-size:10.2px;color:#8A97A6;letter-spacing:1px;line-height:1.6}
  .mprow-nm b{display:block;font-size:10.8px;color:var(--wht);white-space:nowrap;
    overflow:hidden;text-overflow:ellipsis}
  .mprow-tier{flex:none;width:10px;height:10px;border:2px solid var(--ink)}

  /* ---------- THE BAY ----------
     The garage used to open as a search box over a grid — an index, not a
     place. This is the room: your best car parked under a light, on a floor
     with bay markings, and what the collection adds up to written beside it.
     Built from sprites and tier colours that already exist, so it costs no
     new art and nothing extra to load. */
  .gbay{position:relative;margin-bottom:10px;border:3px solid var(--ink);
    border-radius:5px;overflow:hidden;background:#161b28;
    box-shadow:0 4px 0 rgba(16,20,31,.55),inset 0 2px 0 #39415c}
  .gbay .bayfloor{position:relative;height:148px;overflow:hidden;
    background:
      radial-gradient(120% 80% at 50% -10%,rgba(255,238,190,.14) 0 40%,transparent 40%),
      linear-gradient(180deg,#1d2434 0 27%,#20283a 27% 54%,#2b3349 54% 78%,#222a3c 78% 100%)}
  /* roller-door ribs on the back wall, so there's a room behind the car */
  .gbay .bayfloor::before{content:"";position:absolute;left:0;right:0;top:0;height:54%;
    background:repeating-linear-gradient(90deg,rgba(255,255,255,.05) 0 2px,transparent 2px 26px);
    border-bottom:2px solid rgba(8,10,16,.5)}
  /* painted bay markings on the floor */
  .gbay .bayfloor::after{content:"";position:absolute;left:0;right:0;bottom:13px;height:3px;
    background:repeating-linear-gradient(90deg,rgba(240,232,200,.40) 0 16px,transparent 16px 34px)}
  .gbay .baycar{position:absolute;left:50%;bottom:15px;transform:translateX(-50%);
    width:74%;max-width:270px;z-index:2;filter:drop-shadow(4px 4px 0 rgba(0,0,0,.45))}
  .gbay .baycar img{width:100%;display:block;image-rendering:pixelated}
  .gbay .bayempty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
    color:#7c86a4;font-size:11.4px;letter-spacing:2px;text-align:center;line-height:2.2;padding:0 22px}
  .gbay .bayplate{position:absolute;left:10px;top:9px;z-index:3;max-width:66%}
  .gbay .bayplate .bnm{color:#f2f4fa;font-size:14.4px;letter-spacing:1px;
    text-shadow:1px 1px 0 rgba(0,0,0,.8);line-height:1.4}
  .gbay .bayplate .bmk{color:#9aa4bd;font-size:9.6px;letter-spacing:2px;margin-bottom:3px}
  .gbay .bayplate .rartag{position:static;display:inline-block;margin-top:5px}
  .gbay .baystats{display:grid;grid-template-columns:repeat(3,1fr);
    background:#10141f;border-top:3px solid var(--ink)}
  .gbay .baystats div{padding:8px 6px 7px;text-align:center;
    border-right:1px solid rgba(255,255,255,.07)}
  .gbay .baystats div:last-child{border-right:0}
  .gbay .baystats b{display:block;color:var(--gold);font-family:var(--font-display);font-weight:400;font-size:26.4px;
    font-variant-numeric:tabular-nums;margin-bottom:3px}
  .gbay .baystats span{color:#8e98b8;font-size:10.2px;letter-spacing:1.5px}
  .gbay .bayprog{background:#10141f;padding:0 10px 10px}
  .gbay .bayprog .pbar{height:7px;background:#05070c;border:2px solid var(--ink);overflow:hidden}
  .gbay .bayprog .pbar i{display:block;height:100%;background:var(--gold);
    transition:width .4s steps(8)}
  .gbay .bayprog .plbl{display:flex;justify-content:space-between;
    color:#8e98b8;font-size:10.2px;letter-spacing:1.5px;margin-bottom:4px}
  /* tier shelf — the collection's breakdown, and a one-tap filter */
  .gshelf{display:flex;gap:4px;margin-bottom:12px}
  .gshelf button{flex:1;background:#1a2030;border:2px solid var(--ink);
    border-top:4px solid var(--sc,#B3A98D);color:#c9d2e6;font-family:inherit;
    padding:6px 2px 5px;cursor:pointer;line-height:1.5;border-radius:0 0 3px 3px}
  .gshelf button b{display:block;font-size:14.4px;color:#f2f4fa;font-variant-numeric:tabular-nums}
  .gshelf button span{font-size:9.6px;letter-spacing:.5px;color:#8e98b8}
  .gshelf button.on{background:var(--sc)}
  .gshelf button.on b,.gshelf button.on span{color:#fff}
  .gshelf button:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  /* a car the bix doesn't know: name only, and an honest blank */
  .tc-unknown .tc-unk{background:#161c2c;border:2px solid var(--ink);margin-top:4px;
    padding:20px 12px;text-align:center}
  .tc-unknown .unkline{color:var(--gold);font-size:14.4px;letter-spacing:3px}
  .tc-unknown .unksub{color:#8e98b8;font-size:9.6px;letter-spacing:1px;
    margin-top:9px;line-height:2}
  /* boosted-zoom ceiling picker */
  .zoomopts{display:flex;gap:6px}
  .zoomopts button{flex:1;background:var(--panel2);border:3px solid var(--ink);
    color:#c9d2e6;font-family:inherit;font-size:12.6px;padding:9px 4px 8px;cursor:pointer;
    box-shadow:0 3px 0 var(--ink)}
  .zoomopts button.on{background:var(--red);color:#fff;border-color:#ff9a8c}
  .zoomopts button:active{transform:translateY(3px);box-shadow:0 0 0 var(--ink)}
  .zoomopts button:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  /* ---------- in-page prompt / confirm ---------- */
  .askdlg{position:fixed;inset:0;z-index:120;background:rgba(15,19,30,.88);
    display:flex;align-items:center;justify-content:center;padding:20px}
  .askbox{background:var(--paper);border:3px solid var(--ink);border-radius:3px;
    box-shadow:6px 6px 0 rgba(46,53,80,.8);padding:16px;width:100%;max-width:330px}
  .asktitle{font-size:14.4px;letter-spacing:1px;line-height:1.7}
  .askbody{font-family:var(--font-prose);font-weight:400;font-size:18px;color:var(--ink);
    letter-spacing:.01em;line-height:1.6;margin-top:10px;text-transform:none}
  .askdlg input{width:100%;margin-top:12px;background:var(--navy-dk);border:3px solid var(--ink);
    padding:12px 10px;font-family:inherit;font-size:19.2px;letter-spacing:4px;color:var(--wht);
    text-align:center;text-transform:uppercase;box-sizing:border-box}
  .askrow{display:flex;gap:8px;margin-top:14px}
  .askrow .btn{flex:1;font-size:12.6px;padding:12px 8px}
  /* ---------- daily field report ---------- */
  .recapdlg{position:fixed;inset:0;z-index:65;background:rgba(20,24,38,.85);
    display:flex;align-items:center;justify-content:center;padding:16px;overflow-y:auto}
  .recapcard{background:var(--paper);border:3px solid var(--ink);border-radius:3px;
    box-shadow:6px 6px 0 rgba(46,53,80,.8);padding:16px;width:100%;max-width:360px;
    animation:reveal 160ms steps(4)}
  .recaphead{font-size:18px;letter-spacing:1px;display:flex;flex-direction:column;gap:2px}
  .recaphead span{font-size:10.2px;color:var(--navy);letter-spacing:2px}
  .recapstats{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-top:12px}
  .recapstats div{background:var(--panel);border:2px solid var(--ink);text-align:center;
    padding:8px 4px 7px;box-shadow:inset 2px 2px 0 rgba(255,255,255,.08),inset -2px -2px 0 rgba(0,0,0,.35)}
  .recapstats b{display:block;font-size:19.2px;color:var(--gold);font-variant-numeric:tabular-nums}
  .recapstats span{font-size:10.2px;color:#9aa4bd;letter-spacing:1px}
  .recapnotes{margin-top:10px;display:flex;flex-direction:column;gap:4px}
  .recapnote{font-size:10.2px;color:var(--navy);letter-spacing:.5px;line-height:1.6}
  .recapsec{font-size:11.4px;color:var(--navy);letter-spacing:2px;margin-top:14px;
    padding-bottom:4px;border-bottom:2px solid var(--ink)}
  .recaplist{display:flex;flex-direction:column;gap:2px;margin-top:6px}
  .recaprow{display:flex;align-items:center;gap:8px;padding:6px 2px;
    border-bottom:1px solid rgba(46,53,80,.15)}
  .recaprow:last-child{border-bottom:none}
  .recaprow>b{font-size:12.6px;color:#8e98b8;width:16px;flex:none}
  .recaprow .ri{flex:1;min-width:0}
  .recaprow .rn{font-size:11.4px;letter-spacing:.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .recaprow .rxp{font-size:11.4px;color:var(--gold-dk);flex:none;font-variant-numeric:tabular-nums}
  /* ---------- badge board ---------- */
  .bboard{background:var(--paper);border:3px solid var(--ink);
    box-shadow:6px 6px 0 rgba(46,53,80,.8);padding:14px;width:100%;max-width:380px}
  /* .chall was styled for the home page's dark panels — on the cream bboard
     its near-white text washes out. In these two dialogs each row becomes a
     little dark LCD readout instead, which is what the ticker wants anyway. */
  .sdwndlg .chall{background:var(--panel);border:2px solid var(--ink);
    margin-bottom:4px}
  .notifdlg .bboard{max-width:390px}
  /* Every popup dialog in this app (.gdlg) wraps its content in exactly one
     child box — askDialog, name-edit, guide, exam, spin, records, the rank
     ladder, etc. Most of those never got the pop-in the notifications tray
     and daily recap already had (including a real inconsistency: opening
     the badge board from Settings vs. from Notifications rendered the
     identical .bboard markup, but only one ancestor animated it). One rule
     covers all of them instead of hunting down 15 call sites. .cardwrap and
     .deckwrap are full-viewport overlays, not small centered boxes, so a
     scale-based pop-in would flash empty edges around them mid-animation —
     they get a plain fade+drift instead. */
  .gdlg>div{animation:popin 140ms steps(4)}
  .cardwrap,.deckwrap{animation:fadein 140ms steps(3)}
  .notifhead{display:flex;justify-content:space-between;align-items:baseline;
    margin-bottom:12px;padding-bottom:10px;border-bottom:3px solid var(--ink)}
  .notifhead .ttl{font-family:var(--font-display);font-size:26.4px;letter-spacing:.01em;
    color:var(--ink)}
  .notifsec{font-size:9.6px;letter-spacing:1.5px;color:var(--navy);margin:16px 0 8px;
    display:flex;align-items:center;gap:8px}
  .notifsec::after{content:"";flex:1;height:2px;background:var(--line)}
  .notifrow{display:flex;gap:10px;padding:10px 2px;border-bottom:2px solid var(--line);
    position:relative}
  .notifrow:last-of-type{border-bottom:0}
  .notifrow.unread{background:rgba(201,162,39,.1)}
  .notifrow.unread::before{content:"";position:absolute;left:-4px;top:8px;bottom:8px;
    width:3px;background:var(--gold)}
  .notificon{flex:none;width:26px;height:26px;border:3px solid var(--ink);
    display:flex;align-items:center;justify-content:center;font-family:var(--font-display);
    font-size:15.6px;line-height:1;color:#fff;box-shadow:2px 2px 0 rgba(46,53,80,.5);
    margin-top:1px}
  .notifbody{flex:1;min-width:0}
  .notiftype{font-size:10.2px;letter-spacing:1.5px;margin-bottom:3px;font-weight:700}
  .notiftitle{font-size:12.6px;color:var(--ink);font-weight:700;line-height:1.4}
  .notifdetail{font-size:10.2px;color:var(--navy);margin-top:3px;line-height:1.6}
  .notifwhen{font-size:10.2px;color:#9aa4bd;margin-top:5px;letter-spacing:.5px}
  /* a row that leads somewhere gets a chevron, so tappability is visible at
     a glance and not just discoverable by reading the small print below it */
  .notifrow.tappable{transition:background 100ms}
  .notifrow.tappable:active{background:rgba(201,162,39,.16)}
  .notifgo{flex:none;align-self:center;font-size:16.8px;color:var(--gold-dk);margin-left:2px}
  .notifempty{text-align:center;color:var(--navy);font-size:11.4px;padding:34px 10px;line-height:2}
  .notifempty .ne-ico{font-size:26.4px;color:var(--line);margin-bottom:6px}
  /* ---------- admin: screen-capture review queue ---------- */
  .reviewcard{background:var(--panel2);border:3px solid var(--ink);padding:10px;
    margin-bottom:10px;box-shadow:3px 3px 0 rgba(46,53,80,.4)}
  .reviewcard img{width:100%;max-height:170px;object-fit:cover;border:2px solid var(--ink);
    margin-bottom:8px;display:block;cursor:pointer}
  .reviewmeta{display:flex;justify-content:space-between;font-size:9.6px;color:#9aa4bd;
    letter-spacing:.5px;margin-bottom:8px}
  .reviewbtns{display:flex;gap:8px}
  .reviewbtns button{flex:1;font-size:10.8px;padding:8px 0 7px}
  .reviewbtn-deny{background:var(--alert)!important;color:#fff}
  .reviewbtn-accept{background:#5FA89B!important;color:#fff}
  .bbhead b{font-size:15.6px;display:block}
  .bbhead span{font-size:10.2px;color:var(--navy);letter-spacing:1px;
    font-variant-numeric:tabular-nums}
  .bbbar{height:8px;background:#c9c4b6;border:2px solid var(--ink);margin:8px 0 4px;overflow:hidden}
  .bbbar i{display:block;height:100%;background:var(--gold)}
  .bbsec{display:flex;align-items:baseline;justify-content:space-between;
    margin:14px 0 6px;padding-bottom:3px;border-bottom:2px solid var(--ink)}
  .bbsec span{font-size:11.4px;letter-spacing:2px}
  .bbsec i{font-style:normal;font-size:10.2px;color:var(--navy);font-variant-numeric:tabular-nums}
  .bbgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
  .bbadge{background:#d8d4c8;border:3px solid var(--ink);padding:9px 3px 7px;
    text-align:center;opacity:.5;position:relative;overflow:hidden;
    background-image:var(--dither);background-size:6px 6px}
  .bbadge .bbic{font-size:20.4px;filter:grayscale(1)}
  .bbadge .bbnm{font-size:9.6px;color:var(--navy);margin-top:5px;letter-spacing:.5px;
    line-height:1.6;word-break:break-word}
  .bbadge.got{background:var(--panel);opacity:1}
  .bbadge.got .bbic{filter:none}
  .bbadge.got .bbnm{color:var(--gold)}
  /* not earned yet but 80%+ there — a slow pulse says "almost" */
  .bbadge.near{animation:nearpulse 1.6s steps(8) infinite}
  .bbadge.near .bbic{filter:none;opacity:.85}
  .bbadge.near .bbnm{color:var(--c-uncommon)}
  @keyframes nearpulse{0%,100%{box-shadow:0 0 0 rgba(127,160,92,0)}
    50%{box-shadow:0 0 0 3px rgba(127,160,92,.4)}}
  @media (prefers-reduced-motion: reduce){.bbadge.near{animation:none}}
  @keyframes confall{to{transform:translateY(105vh) rotate(180deg)}}
  /* ---------- catch reveal ----------
     The card used to just pop in (120ms) with everything else already sat
     there. Now it lands in the order a real pack opening does: shutter flash
     → the card spins in edge-on and slams down → pixel sparks in the tier's
     own colour burst off its edges (more of them the rarer the tier; Epic+
     also kicks the whole panel) → CAUGHT stamps in → the XP line and the
     stat rows deal in one after another → the buttons arrive last, so the
     eye is on the card before there is anything to tap. Transform/opacity
     only, stepped like the rest of the UI. The sparks and the kick are the
     two physical bits and get real easing for it (same call as the streak
     flame and .rburst). */
  /* 2D only. The first version flipped the card with perspective+rotateY —
     a real 3D rendering context on a card that contains the catch photo —
     and shook the fixed, scrolling #caughtui panel itself; both are known
     WebKit GPU-fault territory on iOS, and the phone app was crashing on
     exactly the moment the card appeared. A horizontal squash-and-stretch
     reads as the same "spins in" beat with none of that. */
  #caughtui.catchin .tcard{animation:catchflip 460ms steps(8) both;transform-origin:50% 60%}
  @keyframes catchflip{
    0%{transform:scaleX(.06) scale(.82);opacity:0}
    45%{transform:scaleX(1.05) scale(1.07);opacity:1}
    70%{transform:scale(1.03)}
    100%{transform:none}}
  #caughtui.catchin .caught-h{animation:catchstamp 260ms steps(4) 420ms both}
  @keyframes catchstamp{0%{transform:scale(1.8);opacity:0}60%{transform:scale(.93);opacity:1}100%{transform:none}}
  #caughtui.catchin .caughtmeta{animation:catchrise 220ms steps(3) 540ms both}
  #caughtui.catchin .caughtmeta+div{animation:catchrise 220ms steps(3) 600ms both}
  #caughtui.catchin .caught-actions{animation:catchrise 240ms steps(4) 720ms both}
  #caughtui.catchin .tc-body>*{animation:catchrise 200ms steps(3) both;
    animation-delay:calc(560ms + var(--i,0)*45ms)}
  @keyframes catchrise{from{transform:translateY(10px);opacity:0}}
  .catchflash{position:fixed;inset:0;z-index:147;pointer-events:none;background:#fff;
    animation:catchflash 240ms steps(3) forwards}
  @keyframes catchflash{from{opacity:.75}to{opacity:0}}
  #caughtui.catchin.kick #caughtcardwrap{animation:catchkick 320ms cubic-bezier(.36,.07,.19,.97) 300ms}
  @keyframes catchkick{10%,90%{transform:translate(-1px,1px)}20%,80%{transform:translate(2px,-1px)}
    30%,50%,70%{transform:translate(-3px,2px)}40%,60%{transform:translate(3px,-2px)}}
  .catchsparks{position:fixed;left:0;top:0;z-index:146;pointer-events:none}
  /* CATCH FX leaves + embers (Shop > CATCH FX). Real easing and a sway on the
     leaves: they're physical, drifting things, same call as the sparks. */
  .catchsparks i.lf{width:var(--s,10px);height:var(--s,10px);
    clip-path:polygon(50% 0,62% 30%,100% 30%,70% 55%,82% 100%,50% 76%,18% 100%,30% 55%,0 30%,38% 30%);
    animation:leaffall var(--d) cubic-bezier(.3,.1,.5,1) var(--w) both}
  @keyframes leaffall{from{transform:translate(0,0) rotate(0);opacity:0}12%{opacity:1}
    50%{transform:translate(calc(var(--dx)*-.6),calc(var(--dy)*.5)) rotate(calc(var(--rot)*.5))}
    to{transform:translate(var(--dx),var(--dy)) rotate(var(--rot));opacity:0}}
  .catchsparks i.sf{border-radius:50%;animation:leaffall var(--d) linear var(--w) both}
  .catchsparks i.em{animation:emberrise var(--d) cubic-bezier(.2,.7,.4,1) var(--w) both}
  @keyframes emberrise{from{transform:translate(0,0) scale(1);opacity:0}15%{opacity:1}
    to{transform:translate(var(--dx),calc(var(--dy)*-1)) scale(.3);opacity:0}}
  .catchsparks i{position:absolute;width:var(--s,6px);height:var(--s,6px);background:var(--c);
    animation:sparkfly var(--d,700ms) cubic-bezier(.17,.84,.44,1) var(--w,0ms) both}
  @keyframes sparkfly{from{transform:translate(0,0) scale(1);opacity:1}
    to{transform:translate(var(--dx),var(--dy)) scale(.2);opacity:0}}
  /* ---------- CATCH FX: glow, ring, stars, ghosts, fireflies, moon, preview stage ---------- */
  .fxglow{position:fixed;z-index:145;pointer-events:none;border-radius:3px;
    box-shadow:0 0 0 2px var(--g),0 0 var(--gs,34px) calc(var(--gs,34px)/3) var(--g);
    animation:fxglow var(--gd,1400ms) ease-out both}
  @keyframes fxglow{0%{opacity:0;transform:scale(.97)}18%{opacity:.95}100%{opacity:0;transform:scale(1.03)}}
  .fxring{position:fixed;z-index:146;pointer-events:none;border:3px solid #FFD84D;border-radius:50%;
    width:var(--rs);height:var(--rs);margin:calc(var(--rs)/-2) 0 0 calc(var(--rs)/-2);
    animation:fxring 900ms cubic-bezier(.16,.84,.3,1) var(--w,0ms) both}
  @keyframes fxring{from{transform:scale(.15);opacity:.9}to{transform:scale(1.7);opacity:0}}
  .catchsparks i.stx{width:var(--s);height:var(--s);background:var(--c);
    clip-path:polygon(50% 0,62% 38%,100% 50%,62% 62%,50% 100%,38% 62%,0 50%,38% 38%);
    animation:sparkfly var(--d) cubic-bezier(.17,.84,.44,1) var(--w) both}
  .catchsparks i.em{box-shadow:0 0 6px var(--c)}
  .catchsparks i.gh{width:var(--s);height:calc(var(--s)*1.2);
    background:radial-gradient(circle at 36% 38%,#141C27 0 1.6px,transparent 1.9px),
      radial-gradient(circle at 64% 38%,#141C27 0 1.6px,transparent 1.9px),
      radial-gradient(ellipse at 50% 64%,#141C27 0 2px,transparent 2.4px),#f4f1ff;
    clip-path:polygon(50% 0,80% 10%,92% 40%,92% 100%,80% 88%,66% 100%,50% 88%,34% 100%,20% 88%,8% 100%,8% 40%,20% 10%);
    animation:ghostfloat var(--d) ease-in-out var(--w) both}
  @keyframes ghostfloat{from{transform:translate(0,0);opacity:0}15%{opacity:.95}
    50%{transform:translate(calc(var(--dx)*-.5),calc(var(--dy)*-.5))}
    to{transform:translate(var(--dx),calc(var(--dy)*-1));opacity:0}}
  .catchsparks i.ff{width:5px;height:5px;border-radius:50%;background:#FFF1A8;
    box-shadow:0 0 8px 3px rgba(255,233,138,.7);animation:firefly var(--d) ease-in-out var(--w) both}
  @keyframes firefly{0%{transform:translate(0,0);opacity:0}20%{opacity:1}40%{opacity:.25}
    60%{opacity:1}80%{opacity:.3}100%{transform:translate(var(--dx),var(--dy));opacity:0}}
  .fxhalo{position:fixed;z-index:145;pointer-events:none;border-radius:50%;
    background:radial-gradient(circle,rgba(255,244,196,.55) 0,rgba(255,244,196,.2) 42%,transparent 70%);
    animation:fxhalo 1900ms ease-in-out both}
  @keyframes fxhalo{0%{opacity:0;transform:scale(.7)}35%{opacity:1}100%{opacity:0;transform:scale(1.15)}}
  /* shop explainer + per-card description + the preview stage */
  .szdesc{font-size:9px;line-height:1.55;color:#9aa4bd;margin:3px 0 8px;letter-spacing:.2px}
  .fxintro{background:var(--panel2);border:2px solid var(--line);padding:10px 12px;margin:2px 0 12px;
    font-size:10.2px;line-height:1.7;color:#c9d2e6}
  .fxintro .fxi-h{font-family:var(--font-display);color:var(--gold);font-size:13.2px;letter-spacing:.14em;margin-bottom:4px}
  .fxintro p{margin:0 0 6px}
  .fxintro ul{margin:0 0 8px;padding-left:16px}
  .fxintro li{margin:2px 0}
  .fxintro .fxnow{color:#9aa4bd;letter-spacing:.1em}
  .fxintro .fxnow b{color:var(--wht)}
  .fxstage{position:fixed;inset:0;z-index:140;background:rgba(14,18,30,.88);display:flex;flex-direction:column;
    align-items:center;justify-content:center;gap:14px;padding:20px}
  .fxstage .fxname{font-family:var(--font-display);color:var(--gold);font-size:19.2px;letter-spacing:.14em}
  .fxmock{width:200px;height:250px;background:#161c2c;border:4px solid #8C6FB5;
    box-shadow:inset 0 0 0 2px rgba(255,255,255,.12),4px 4px 0 rgba(0,0,0,.45);padding:10px;
    display:flex;flex-direction:column;gap:8px;transition:border-color 120ms steps(2)}
  .fxmock .ph{flex:1;background:repeating-linear-gradient(135deg,#202a44 0 6px,#1b2338 6px 12px);border:2px solid #141C27}
  .fxmock .ln{height:9px;background:#2a3452}
  .fxmock .ln.s{width:60%}
  .fxchips{display:flex;gap:8px}
  .fxchip{background:var(--navy);border:2px solid var(--ink);color:var(--wht);font:inherit;font-size:10.8px;
    letter-spacing:.1em;padding:10px 12px;min-height:44px}
  .fxchip.on{background:var(--gold);color:var(--ink-fixed,#141C27)}
  .fxstage .fxhint{font-size:9.6px;letter-spacing:.14em;color:#8e98b8}
  @media (prefers-reduced-motion: reduce){
    #caughtui.catchin .tcard,#caughtui.catchin .caught-h,#caughtui.catchin .caughtmeta,
    #caughtui.catchin .caughtmeta+div,#caughtui.catchin .caught-actions,
    #caughtui.catchin .tc-body>*,#caughtui.catchin.kick #caughtcardwrap{animation:none}
    .catchflash,.catchsparks,.fxglow,.fxring,.fxhalo{display:none}}
  /* ---------- streak lit ---------- */
  /* A flame igniting reads as something physical catching, not a mechanical
     panel sliding in — same call as .rburst above: real easing, not steps().
     One continuous keyframe (pop in → hold → drift up and fade) rather than
     separate enter/exit states, since nothing can retrigger this mid-flight —
     it fires at most once per real-world day. */
  .streaklit{position:fixed;left:50%;top:38%;z-index:146;pointer-events:none;
    text-align:center;transform:translate(-50%,-50%) scale(.85);opacity:0;
    animation:streaklit 1.8s cubic-bezier(.23,1,.32,1) forwards}
  .streaklit .sl-flame{font-size:40px;line-height:1;
    filter:drop-shadow(0 0 10px rgba(255,106,61,.7))}
  .streaklit .sl-label{margin-top:4px;font-family:var(--font-utility);font-size:12.6px;
    font-weight:700;letter-spacing:1.5px;color:#fff;
    text-shadow:1px 1px 0 rgba(0,0,0,.6),-1px -1px 0 rgba(0,0,0,.6),
    1px -1px 0 rgba(0,0,0,.6),-1px 1px 0 rgba(0,0,0,.6)}
  @keyframes streaklit{
    0%{transform:translate(-50%,-50%) scale(.85);opacity:0}
    18%{transform:translate(-50%,-50%) scale(1.15);opacity:1}
    30%{transform:translate(-50%,-50%) scale(1);opacity:1}
    75%{transform:translate(-50%,-58%) scale(1);opacity:1}
    100%{transform:translate(-50%,-80%) scale(.95);opacity:0}}
  @media (prefers-reduced-motion: reduce){.streaklit{display:none}}
  /* ---------- burst haul deck ---------- */
  /* Season Rewind slides + the Home entry under the season band */
  .sbrewind{display:block;width:100%;margin:-6px 0 12px;background:#10141f;border:3px solid var(--ink);
    border-top:none;color:#ffe08a;font-family:inherit;font-size:9.6px;letter-spacing:1.5px;padding:7px 10px 6px;
    text-align:left;cursor:pointer}
  .sbrewind:active{transform:translateY(1px)}
  .rwcard{background:#10141f;border:3px solid var(--ink);box-shadow:6px 6px 0 rgba(0,0,0,.35);
    padding:22px 18px 20px;min-height:300px;display:flex;flex-direction:column;justify-content:center;text-align:center;color:var(--wht)}
  .rweb{font-size:9.6px;letter-spacing:3px;color:#ffe08a;margin-bottom:12px}
  .rwbig{font-family:var(--font-display);font-size:64px;line-height:.95;color:#f2f5fa;text-wrap:balance}
  .rwbig small{display:block;font-family:inherit;font-size:11px;letter-spacing:3px;color:#9fb0c6;margin-top:8px}
  .rwsub{font-size:10.2px;letter-spacing:1px;color:#c9d3e0;line-height:1.7;margin-top:14px}
  .rwhint{font-size:9px;letter-spacing:3px;color:#6b7590;margin-top:18px}
  .rwline{display:flex;align-items:center;justify-content:center;gap:8px;font-size:9.6px;letter-spacing:1px;color:#9fb0c6;margin-top:14px}
  .rwsprite{height:28px;image-rendering:pixelated}
  .rwphoto{width:100%;max-height:150px;object-fit:cover;border:3px solid;margin-top:14px}
  .batchwrap{gap:0;padding-top:18px}
  .batchwrap .bhead{width:100%;max-width:340px;display:flex;align-items:baseline;
    justify-content:space-between;margin-bottom:10px}
  .batchwrap .bhead b{font-size:15.6px;letter-spacing:1px}
  .batchwrap .bhead span{font-size:11.4px;color:var(--navy);font-variant-numeric:tabular-nums}
  /* One card per panel, snapped — a flick moves exactly one card, never half. */
  .batchwrap .bdeck{display:flex;width:100%;max-width:340px;
    overflow-x:auto;overflow-y:hidden;scroll-snap-type:x mandatory;
    -webkit-overflow-scrolling:touch;scrollbar-width:none;gap:0}
  .batchwrap .bdeck::-webkit-scrollbar{display:none}
  .batchwrap .bslide{flex:0 0 100%;scroll-snap-align:center;
    display:flex;justify-content:center;padding:0 2px}
  .batchwrap .bslide .tcard{max-width:100%}
  .batchwrap .bdots{display:flex;gap:5px;justify-content:center;margin-top:10px;flex-wrap:wrap}
  .batchwrap .bdots i{width:7px;height:7px;background:#b6b2a6;
    border:2px solid var(--ink);display:block}
  .batchwrap .bdots i.on{background:var(--red)}
  /* Rarity chip colours. The tier colours are mid-to-dark, so the label rides
     on white ink — except gold, which is light enough to keep the dark ink. */
  .rc-common{background:var(--c-common);color:var(--wht)}
  .rc-uncommon{background:var(--c-uncommon);color:var(--wht)}
  .rc-rare{background:var(--c-rare);color:var(--wht)}
  .rc-epic{background:var(--c-epic);color:var(--wht)}
  .rc-legendary{background:var(--c-legendary);color:var(--ink-fixed)}
  .rc-mythic{background:var(--c-mythic);color:var(--wht)}
  .rc-grail{background:var(--c-grail);color:#1a1530}

  /* ---------- bottom nav: flat blocky segmented tab bar ---------- */
  /* The nav's own keys are already hardcoded steel/navy regardless of theme
     (see below) — the container needs to match, or dark mode's lightened
     --ink would show as pale seams in the gaps between otherwise-dark keys. */
  nav{position:fixed;bottom:0;left:0;right:0;z-index:40;background:var(--ink-fixed);
    border-top:4px solid var(--ink-fixed);display:flex;gap:4px}
  /* five equal beveled steel tabs; the words carry it, no icons. Inactive
     tabs sit recessed (light flipped), the active one is an alert-red plate
     standing proud of the row. */
  /* Raised plastic keys, not recessed wells — each tab is a button you could
     feel with a thumbnail. The active key is the alert plate, standing 3px
     prouder than its neighbours; pressing any key sinks it. */
  /* Dark navy plastic keys with pixel-notched corners: the corners are cut in
     4px steps by the clip-path, which is what makes them read as sprites
     rather than CSS boxes. A faint 2px scanline sits over the key face. */
  nav button{flex:1;background:#2C3E58;border:2px solid var(--ink-fixed);
    color:#D7E2F0;font-size:17px;letter-spacing:.04em;font-family:var(--font-display);
    box-shadow:inset 2px 2px 0 #5D82AC,inset -2px -2px 0 #141C27;
    text-shadow:1px 1px 0 rgba(0,0,0,.6);
    background-image:
      radial-gradient(circle 4px at 8px 8px,rgba(255,255,255,1),transparent 72%),
      radial-gradient(circle 2px at 15px 6px,rgba(255,255,255,.65),transparent 75%),
      linear-gradient(180deg,
        rgba(255,255,255,.36) 0%,rgba(255,255,255,.36) 20%,
        rgba(255,255,255,.11) 20%,rgba(255,255,255,.11) 45%,
        rgba(0,0,0,.14) 45%,rgba(0,0,0,.14) 72%,
        rgba(0,0,0,.40) 72%,rgba(0,0,0,.40) 100%),
      repeating-conic-gradient(rgba(255,255,255,.1) 0% 25%,rgba(0,0,0,.05) 0% 50%),
      repeating-linear-gradient(0deg,rgba(255,255,255,.10) 0 2px,transparent 2px 4px);
    background-size:100% 100%,100% 100%,100% 100%,6px 6px,auto;
    background-repeat:no-repeat,no-repeat,no-repeat,repeat,repeat;
    clip-path:var(--notch);
    padding:14px 2px calc(14px + env(safe-area-inset-bottom));
    text-transform:uppercase;font-weight:400;cursor:pointer;box-sizing:border-box;
    transition:transform 80ms steps(3),box-shadow 80ms steps(3);
    position:relative;overflow:hidden}
  nav button::after{content:"";position:absolute;inset:0;pointer-events:none;
    background:linear-gradient(115deg,transparent 42%,rgba(255,255,255,.32) 50%,transparent 58%);
    animation:holosheen 9s steps(14) infinite}
  nav button:nth-child(2)::after{animation-delay:1.5s}
  nav button:nth-child(3)::after{animation-delay:3s}
  nav button:nth-child(4)::after{animation-delay:4.5s}
  nav button:nth-child(5)::after{animation-delay:6s}
  nav button:active{box-shadow:inset 2px 2px 0 #141C27,inset -2px -2px 0 #5D82AC;
    transform:translate(1px,1px)}
  nav button.on{background:var(--red);border-color:var(--ink-fixed);color:#fff;
    background-image:
      radial-gradient(circle 4px at 8px 8px,rgba(255,255,255,1),transparent 72%),
      radial-gradient(circle 2px at 15px 6px,rgba(255,255,255,.7),transparent 75%),
      linear-gradient(180deg,
        rgba(255,255,255,.36) 0%,rgba(255,255,255,.36) 20%,
        rgba(255,255,255,.11) 20%,rgba(255,255,255,.11) 45%,
        rgba(0,0,0,.14) 45%,rgba(0,0,0,.14) 72%,
        rgba(0,0,0,.40) 72%,rgba(0,0,0,.40) 100%),
      repeating-conic-gradient(rgba(0,0,0,.20) 0% 25%,rgba(255,255,255,.04) 0% 50%),
      repeating-linear-gradient(0deg,rgba(255,255,255,.12) 0 2px,transparent 2px 4px);
    background-size:100% 100%,100% 100%,100% 100%,6px 6px,auto;
    background-repeat:no-repeat,no-repeat,no-repeat,repeat,repeat;
    margin-top:-2px;padding-top:16px;
    box-shadow:inset 2px 2px 0 #E8705F,inset -2px -2px 0 var(--red-dk)}
  nav button.on:active{box-shadow:inset 2px 2px 0 var(--red-dk),inset -2px -2px 0 #E8705F;
    transform:translate(1px,1px)}
  nav button.on::after{background:linear-gradient(115deg,transparent 40%,rgba(255,255,255,.45) 50%,transparent 60%);
    animation-duration:6s;animation-delay:0s}
  nav button:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}
  @media (prefers-reduced-motion: reduce){nav button::after{content:none}}
  input[type=file]{display:none}
  .reveal{animation:reveal 160ms steps(4)}
  @keyframes reveal{from{transform:scale(.8);opacity:0}}
  /* ---------- retro CRT + motion ---------- */
  body::after{content:"";position:fixed;inset:0;z-index:95;pointer-events:none;
    background:repeating-linear-gradient(0deg,rgba(20,25,40,.05) 0 1px,transparent 1px 3px)}
  @keyframes popin{from{transform:scale(.9);opacity:0}}
  /* mini toast, take two: the single shared box was worse than it looked —
     a burst of catch bonuses (car-of-the-day, spotlight, rarity, shiny...)
     fires several of these within ~1s of each other, and a shared element
     just swapped its text mid-flight with no transition at all — a visible
     flicker/stutter on exactly the moments meant to feel celebratory. Each
     call now gets its OWN card in a real stack (#toaststack), so nothing
     ever interrupts anything else. Enter/exit share one vertical axis
     (down-and-fade in, up-and-fade out) rather than different paths, and
     the curve is a plain no-overshoot ease — a toast isn't a gesture the
     user threw, so bounce here would read as noise, not physicality. */
  #toaststack{position:fixed;left:50%;top:calc(env(safe-area-inset-top,0px) + 64px);
    transform:translateX(-50%);z-index:150;pointer-events:none;
    display:flex;flex-direction:column;gap:8px;align-items:center;
    width:min(88vw,320px)}
  .mtoast{display:flex;align-items:flex-start;gap:10px;width:100%;
    background:var(--ink-fixed);border:3px solid var(--mt-color,#C9A227);
    box-shadow:0 4px 0 rgba(0,0,0,.4),inset 0 2px 0 rgba(255,255,255,.14),inset 0 -3px 0 rgba(0,0,0,.35);
    padding:11px 14px 10px;opacity:0;transform:translateY(-14px);
    transition:opacity 260ms cubic-bezier(.16,1,.3,1),transform 260ms cubic-bezier(.16,1,.3,1)}
  .mtoast.show{opacity:1;transform:translateY(0)}
  .mtoast.leaving{opacity:0;transform:translateY(-10px);
    transition:opacity 180ms cubic-bezier(.4,0,1,1),transform 180ms cubic-bezier(.4,0,1,1)}
  .mtoast .mt-dot{width:9px;height:9px;flex:0 0 9px;margin-top:4px;
    background:var(--mt-color,#C9A227);box-shadow:0 0 0 2px rgba(0,0,0,.35)}
  .mtoast .mt-msg{font-family:var(--font-utility);font-weight:700;font-size:14.4px;
    line-height:1.45;letter-spacing:.05em;color:#F2F4F7;text-shadow:1px 1px 0 rgba(0,0,0,.4)}
  @media (prefers-reduced-motion: reduce){
    .mtoast,.mtoast.leaving{transition:opacity 150ms linear;transform:none}}
  .tile{animation:popin 120ms steps(4) backwards}
  .tile:nth-child(3n+1){animation-delay:.02s}.tile:nth-child(3n+2){animation-delay:.06s}
  .tile:nth-child(3n+3){animation-delay:.1s}
  .arow,.fcard{animation:popin 120ms steps(4) backwards}
  .arow:nth-child(2){animation-delay:.05s}.arow:nth-child(3){animation-delay:.1s}
  .arow:nth-child(4){animation-delay:.15s}.arow:nth-child(5){animation-delay:.2s}
  .fcard:nth-child(2){animation-delay:.07s}.fcard:nth-child(3){animation-delay:.14s}
  @keyframes shake{0%,100%{transform:none}20%{transform:translate(-3px,1px) rotate(-1deg)}
    40%{transform:translate(3px,-1px) rotate(1deg)}60%{transform:translate(-2px,0)}80%{transform:translate(2px,0)}}
  .shake{animation:shake .4s steps(4)}
  @keyframes countpop{50%{transform:scale(1.3);color:var(--red)}}
  .countpop{animation:countpop 200ms steps(2)}
  .lxp i,.chall .cbar i,.xpbar i{transition:width .4s steps(8)}
  /* ambient motion — subtle, everywhere */
  @keyframes float{0%,100%{transform:none}}
  .card{animation:reveal 160ms steps(4)}
  .license{animation:popin 120ms steps(4)}
  .flame{display:inline-block;animation:flick .55s steps(2) infinite;transform-origin:50% 88%}
  @keyframes flick{0%,100%{transform:scale(1) rotate(-4deg)}50%{transform:scale(1.18) rotate(4deg)}}
  @keyframes scanline{0%{top:0}100%{top:100%}}
  /* the sweep drags a translucent band behind it, and the frame under it gets
     a faint scanline grid — the frozen shot is visibly being READ */
  #scansweep::after{content:"";position:absolute;left:0;right:0;top:4px;height:34px;
    background:linear-gradient(180deg,rgba(74,180,255,.28),rgba(74,180,255,0))}
  #viewport.busy::after{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
    background:repeating-linear-gradient(0deg,rgba(74,180,255,.10) 0 1px,transparent 1px 4px)}
  .revsweep{position:absolute;left:0;right:0;height:3px;z-index:1;pointer-events:none;
    background:linear-gradient(90deg,transparent,var(--pink) 25%,#fff 50%,var(--pink) 75%,transparent);
    animation:scanline 1.1s linear infinite}
  .revsweep::after{content:"";position:absolute;left:0;right:0;top:3px;height:26px;
    background:linear-gradient(180deg,rgba(255,255,255,.16),transparent)}
  @keyframes racedotpulse{0%,100%{transform:scale(1) rotate(-6deg)}50%{transform:scale(1.18) rotate(-6deg)}}
  .caught-h{animation:popin 120ms steps(4)}
  .status::before{animation:blink 1.4s steps(2) infinite}
  .counter{display:inline-block;animation:none}
  @media (prefers-reduced-motion: reduce){
    *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;
      transition-duration:.01ms!important}
  }
  .btn,.fbtn,nav button,.hbtn{transition:none}
  .newdex{animation:popin 120ms 60ms steps(4) backwards}
  .caught-h{animation:popin 120ms steps(4)}

  /* ============================================================
     READABILITY, REACH AND ROOM
     Everything below is a foundation layer, deliberately last in the
     sheet so it settles arguments by cascade order instead of by
     !important or by editing seven hundred older rules. Three jobs:
     make sentences readable, make every control reachable by finger
     and by keyboard, and give a wide screen something to do.
     The device look is untouched — no radii, no easing, no new
     palette. Only size, spacing and focus.
     ============================================================ */

  /* ---------- prose ----------
     The pixel display face is a logo face: brilliant on SCAN, unreadable
     in a paragraph, and the uppercase + wide tracking that suits a
     three-letter label turns a safety notice into a wall. Sentences get
     the prose role (which already existed for .dabout and was simply
     never used anywhere else), sentence case, normal tracking, and
     leading that lets a line breathe. Never applied to a chip, a stat
     or a heading — those keep the display and utility faces. */
  .prose{font-family:var(--font-prose);font-weight:400;font-size:19.2px;line-height:1.65;
    letter-spacing:.01em;text-transform:none}
  .prose b,.prose strong{font-weight:700;color:var(--gold)}
  .prose p{margin:0 0 .8em}
  .prose p:last-child{margin-bottom:0}
  .prose ul{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:.7em}
  /* the marker is absolute, NOT a flex sibling — a flex li turns every text
     node into its own flex item and justifies gaps into the middle of the
     sentence. And it's a pseudo-element because a screen reader announcing
     a bullet glyph before every line is noise. */
  .prose li{position:relative;padding-left:18px}
  .prose li::before{content:"▪";color:var(--red);position:absolute;left:0;top:0;line-height:1.65}
  /* 65ch is the reading measure; on a phone the column is narrower than
     that anyway, so this only ever bites on tablet and desktop */
  .prose{max-width:66ch}
  .lede{font-family:var(--font-prose);font-weight:400;font-size:20.4px;line-height:1.55;
    letter-spacing:.01em;text-transform:none;color:var(--ink)}

  /* ---------- focus ----------
     One visible state for every focusable thing. The double ring (gold
     over ink) is so it survives on gold chips, red plates and the cream
     casing alike — a single-colour outline vanishes on at least one of
     those. Keyboard only: :focus-visible never fires on a finger tap. */
  :where(a,button,input,select,textarea,summary,[tabindex]):focus-visible{
    outline:3px solid var(--gold);outline-offset:2px;
    box-shadow:0 0 0 6px rgba(20,28,39,.55);position:relative;z-index:5}
  /* inside the dark LCD panels the ink halo disappears; use a light one */
  .dpanel :where(a,button,input,select,textarea,[tabindex]):focus-visible,
  nav button:focus-visible,.viewport :where(button,input):focus-visible{
    box-shadow:0 0 0 6px rgba(228,231,236,.35)}
  /* keyboard users get the skip link; it is off-canvas until focused */
  .skiplink{position:fixed;left:8px;top:-200px;z-index:300;background:var(--gold);
    color:var(--ink-fixed);border:3px solid var(--ink-fixed);padding:12px 16px;
    font-family:var(--font-utility);font-weight:700;font-size:14.4px;letter-spacing:.1em}
  .skiplink:focus{top:calc(8px + env(safe-area-inset-top))}
  /* announced to screen readers, never drawn */
  .sr-only{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

  /* ---------- reach ----------
     44x44 is the floor, but padding a chip to 44px would wreck rows that
     are deliberately dense. So the box stays whatever size it looks, and
     an invisible pseudo-element extends the HIT AREA out to 44 square.
     Nothing moves; the target just stops being a 26px pinprick. */
  .hbtn,.seasonicon,.fbtn,.stog,.copt,.addnote,.mapui button,.backbtn,
  .dtrash,#s-fcopy,#gsort,#gdice,#gmenubtn,.tshelf button{position:relative}
  .hbtn::before,.seasonicon::before,.fbtn::before,.stog::before,.addnote::before,
  .mapui button::before,.backbtn::before,.dtrash::before,#s-fcopy::before,
  #gsort::before,#gdice::before,#gmenubtn::before{
    content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    width:max(100%,44px);height:max(100%,44px);z-index:1}
  /* the bevel decorations on these are ::after, so ::before is free —
     except .cart/.hbtn where ::before is the "›" glyph; that one keeps
     its glyph and gets real padding instead */
  .cart::before{position:static;transform:none;width:auto;height:auto}
  .btn{min-height:44px}
  nav button{min-height:52px}
  input,select,textarea{min-height:44px}

  /* ---------- terminology ----------
     Every piece of in-house shorthand (BIX, POP, MVD, ORB, CLEAN SHOT)
     is a button that explains itself, rather than a word a new spotter
     has to already know. Dotted underline is the standing web signal for
     "there is a definition behind this". */
  .term{border-bottom:2px dotted currentColor;cursor:help;background:none;border-top:0;
    border-left:0;border-right:0;color:inherit;font:inherit;letter-spacing:inherit;
    padding:0;text-transform:inherit}
  .term:hover{color:var(--gold)}
  .termpop{position:fixed;z-index:210;max-width:min(320px,calc(100vw - 24px));
    background:var(--paper);border:3px solid var(--ink);
    box-shadow:6px 6px 0 rgba(46,53,80,.8);padding:12px 13px}
  .termpop .tp-t{font-family:var(--font-utility);font-weight:700;font-size:13.2px;
    letter-spacing:.12em;color:var(--red-dk);margin-bottom:6px}
  .termpop .tp-b{font-family:var(--font-prose);font-weight:400;font-size:18px;
    line-height:1.6;color:var(--ink);text-transform:none;letter-spacing:.01em}

  /* ---------- room ----------
     Three intentional layouts, not one stretched. Phone keeps the bottom
     nav and the single column it was designed for. Tablet gets a wider
     measure and denser grids. Desktop moves navigation to a permanent
     left rail — a 1440px screen showing a 560px column with a bar glued
     to the bottom edge is a phone screenshot, not a desktop app — and
     lets Home run two columns so the fold holds the whole briefing. */
  @media (min-width:760px){
    main{max-width:720px;padding:24px 20px}
    .grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:10px 8px}
    h1.title{font-size:55.2px}
    .dailygrid{grid-template-columns:repeat(4,minmax(0,1fr))}
  }
  @media (min-width:1080px){
    /* the rail lives in body padding so header AND main shift together,
       and main keeps its own auto-centring inside what is left */
    body{padding-left:184px;padding-bottom:32px}
    nav{top:0;bottom:0;right:auto;width:184px;flex-direction:column;
      justify-content:flex-start;gap:8px;padding:14px 10px;
      border-top:0;border-right:4px solid var(--ink-fixed)}
    nav button{flex:0 0 auto;padding:18px 14px;text-align:left;font-size:24px}
    nav button.on{margin-top:0;padding-top:18px}
    main{max-width:1040px;padding:28px 28px 40px}
    /* Home becomes a briefing board: the call to action and today's
       board span the full width, the standing panels sit side by side */
    #v-home.on{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
      gap:18px;align-items:start}
    #v-home.on>#scancta,#v-home.on>#todaypanel{grid-column:1/-1}
    #v-settings.on .dpanel{columns:2;column-gap:22px}
    #v-settings.on .dpanel>*{break-inside:avoid}
    .grid{grid-template-columns:repeat(6,minmax(0,1fr))}
    /* a fixed rail must not sit under the fullscreen camera */
    body.fs-scan{padding-left:0}
    body.fs-scan nav{display:none}
  }
  @media (min-width:1080px) and (prefers-reduced-motion:no-preference){
    nav button::after{animation:none}
  }
  /* an over-wide phone-shaped window (a tall thin desktop) must not get
     the rail — it is keyed to width, and the bottom nav is correct there */

  /* ---------- states ----------
     An empty screen, a denied permission and a dead connection are all
     screens a spotter can land on. Each says what happened, why, and
     the one thing to do next — a dead end with no action is a bug. */
  .statebox{background:var(--panel);border:3px solid var(--ink);padding:20px 16px;
    text-align:center;margin-bottom:14px}
  .statebox .st-h{font-family:var(--font-display);font-size:31.2px;line-height:1;
    color:var(--wht);margin-bottom:8px}
  .statebox .st-b{font-family:var(--font-prose);font-weight:400;font-size:18px;
    line-height:1.6;color:#c9d2e6;text-transform:none;letter-spacing:.01em;
    max-width:44ch;margin:0 auto 14px}
  .statebox .btn{min-width:180px}
  /* connection lost — a strip under the header, not a modal; you can
     keep scanning offline, catches queue up locally */
  #offlinebar{position:sticky;top:0;z-index:29;display:none;background:var(--gold);
    color:var(--ink-fixed);border-bottom:3px solid var(--ink-fixed);
    font-family:var(--font-utility);font-weight:700;font-size:12.6px;letter-spacing:.1em;
    padding:9px 12px;text-align:center}
  body.offline #offlinebar{display:block}

  /* ---------- the primary action ----------
     One control on Home outranks everything else: SCAN. Before the first
     catch it is a full hero that also teaches the loop; after that it
     shrinks to a strip but keeps the alert-red plate — nothing else on
     Home is allowed that color at that size, which is what makes it read
     as THE button. */
  .scanhero{display:block;width:100%;background:var(--red);border:3px solid var(--ink-fixed);
    color:#fff;text-align:left;padding:16px 16px 14px;margin-bottom:16px;cursor:pointer;
    box-shadow:0 4px 0 var(--ink-fixed),inset 0 2px 0 rgba(255,255,255,.30),inset 0 -3px 0 rgba(0,0,0,.28);
    background-image:var(--band),var(--dither);background-size:100% 100%,6px 6px}
  .scanhero:active{transform:translateY(3px);box-shadow:0 1px 0 var(--ink-fixed)}
  /* white, not a red tint — tinted text on the alert plate fell under AA */
  .scanhero .sh-eyebrow{display:block;font-family:var(--font-utility);font-weight:700;
    font-size:11.4px;letter-spacing:.2em;color:#fff;margin-bottom:4px}
  .scanhero .sh-title{display:flex;align-items:center;gap:10px;
    font-family:var(--font-display);font-size:45.6px;line-height:1;text-shadow:2px 2px 0 rgba(0,0,0,.35)}
  .scanhero .sh-loop{display:block;margin-top:10px;font-family:var(--font-prose);font-weight:400;
    font-size:18px;line-height:1.6;letter-spacing:.01em;text-transform:none;color:#fff}
  .scanhero .sh-loop b{color:#fff}
  .scanrowcta{display:flex;gap:10px;align-items:stretch;margin-bottom:16px}
  .scanrowcta .btn{flex:2;font-size:18px;display:flex;align-items:center;justify-content:center;gap:8px}
  .scanrowcta .ctastat{flex:1;background:var(--panel);border:3px solid var(--ink);
    display:flex;flex-direction:column;align-items:center;justify-content:center;padding:6px}
  .scanrowcta .ctastat b{font-family:var(--font-display);font-size:24px;color:var(--gold);line-height:1}
  .scanrowcta .ctastat span{font-size:9px;letter-spacing:.1em;color:#8e98b8}
  .unlocknote{font-family:var(--font-prose);font-weight:400;font-size:16.8px;line-height:1.5;
    letter-spacing:.01em;text-transform:none;color:var(--navy-dk);text-align:center;
    padding:4px 0 0;margin-bottom:14px}

  /* ---------- empty-garage intro ---------- */
  #gintro .gi-panel{background:var(--panel);border:3px solid var(--ink);padding:18px 16px;margin-bottom:14px}
  #gintro .gi-h{font-family:var(--font-display);font-size:33.6px;color:var(--wht);line-height:1;margin-bottom:8px}
  #gintro .gi-b{font-family:var(--font-prose);font-weight:400;font-size:18px;line-height:1.65;
    letter-spacing:.01em;text-transform:none;color:#c9d2e6;max-width:52ch;margin-bottom:14px}
  #gintro .gi-b b{color:var(--gold)}
  #gintro .gi-starters{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px 5px;margin-bottom:14px}
  @media (min-width:760px){#gintro .gi-starters{grid-template-columns:repeat(6,minmax(0,1fr))}}
  #gintro .gi-cap{font-size:10.2px;letter-spacing:.15em;color:var(--red-dk);margin-bottom:8px}

  /* ---------- third-pass polish ---------- */
  /* disabled chips read as disabled, not merely unlucky */
  .fbtn:disabled,.btn:disabled{opacity:.45;cursor:default}
  .fbtn:disabled::before{content:none}   /* no 44px hit halo on a dead control */
  /* feed reaction buttons were ~30x13px bare text — same invisible hit-area
     extension the other chips got */
  .reacts button{position:relative;padding:8px 6px}
  .reacts button::before{content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    width:max(100%,44px);height:max(100%,44px)}
  /* quest-trail fold rows: the collapsed past ("N done") and future ("view
     all") sit directly in the trail and dress like ordinary nodes */
  .qtrail>.qfold{display:flex;width:100%;align-items:center;gap:12px;padding:8px 4px;
    background:none;border:none;color:#8e98b8;font-family:inherit;font-size:11.4px;
    letter-spacing:.5px;cursor:pointer;text-align:left;position:relative;z-index:1}
  .qtrail>.qfold .qbadge{color:#8e98b8;font-size:14.4px}
  .qtrail>.qfold:hover .qnodetitle{color:#f2f4fa}
  .mapui button:disabled{opacity:.45;cursor:default}

  /* decorative CSS glyphs stay visual-only for screen readers where the
     alternative-text syntax is supported; older engines keep the plain form */
  @supports (content:"x"/"y"){
    .cart::before{content:"›"/""}
    .status::before{content:"▪ "/""}
    .dsec::before{content:"»"/""}
    .prose li::before{content:"▪"/""}
    .tc-photo.zoomable::after{content:"⤢"/""}
  }
  /* ================================================================
     DARK-MODE PATCH — same cabinet at night. Deep navy paper (#1B1F27 /
     #242A35), light ink, muted labels (--muted = #A6B2C8), the same red/gold
     accents lifted ONLY where they are text on a paper surface. Every rule is
     scoped to :root[data-theme="dark"]; light mode is untouched.
     Append at the end of the <style> block (after line ~3050).
     ================================================================ */

  /* -- tokens: paper containers that draw --gold-dk / --red-dk as TEXT get a
     lifted copy of those two tokens. Scoped, not global, because --gold-dk is
     also the rivet fill and the .racecode dashed border and --red-dk paints
     the nav's pressed bevel. Inline style="color:var(--gold-dk)" /
     var(--red-dk) inside the JS templates resolves through these ancestors,
     so the notification type label + TAP FOR THE FULL LOG, the UPDATE LOG
     build headings, HOW TO PLAY headings, EXAM ACE / 5-of-5 score, LUCKY SPIN
     75+ prize, SET COMPLETE, the spotter-card title + SIGNATURE RIDE, MOST
     WANTED + WANTED labels, RARE COLOR on the trading card and SUNSET CIRCUIT
     in the summer .dbar all lift with no JS edit. Also lifts .dbar .dtitle /
     .dexnum, .caught-sub, .lname, .recaprow .rxp and the .dcard gold pinstripe
     outline. (This supersedes the existing line-1617 .license rule, which can
     stay.) */
  :root[data-theme="dark"] :is(.dbar,.card,.dcard,.license,.spotcard,.bboard,.recapcard){--gold-dk:#E0B93A;--red-dk:#E8705F}
  :root[data-theme="dark"] #caughtui,:root[data-theme="dark"] #gwanted{--gold-dk:#E0B93A;--red-dk:#E8705F}

  /* -- page casing (things sitting straight on --paper) */
  :root[data-theme="dark"] .scanstatus:not(.err){color:var(--muted)}           /* :not keeps .scanstatus.err red */
  :root[data-theme="dark"] #gintro .gi-cap{color:#E8705F}                      /* caption sits OUTSIDE the dark .gi-panel */
  :root[data-theme="dark"] .mapframe{box-shadow:5px 5px 0 rgba(0,0,0,.55)}     /* rgba(46,53,80,.7) vanished on #1B1F27 */

  /* -- trading card (.card, --paper2) */
  :root[data-theme="dark"] .classline,
  :root[data-theme="dark"] .cardfoot .sn{color:var(--muted)}
  :root[data-theme="dark"] .card{box-shadow:inset 2px 2px 0 rgba(255,255,255,.08),inset -2px -2px 0 rgba(0,0,0,.4),
               5px 5px 0 -1px var(--paper2),5px 5px 0 1px var(--ink),
               10px 10px 0 -2px var(--paper2),10px 10px 0 0 var(--ink)}     /* was a .4 white bevel on a #242A35 card */

  /* -- pinned caught screen (#caughtui.locked, --paper) */
  :root[data-theme="dark"] #caughtui.locked{box-shadow:0 4px 0 rgba(0,0,0,.5),inset 0 2px 0 rgba(255,255,255,.08),inset 0 -2px 0 rgba(0,0,0,.4)}

  /* -- Spotter License / spotter card / race-choice card (--paper, --paper2) */
  :root[data-theme="dark"] .splvl,
  :root[data-theme="dark"] .rcskip{color:var(--muted)}
  :root[data-theme="dark"] .spname{text-shadow:1px 1px 0 rgba(0,0,0,.6)}      /* white emboss under light ink read as a ghost */
  :root[data-theme="dark"] .spotcard:not([class*="cos-"]){box-shadow:inset 2px 2px 0 rgba(255,255,255,.08),inset -2px -2px 0 rgba(0,0,0,.4),6px 6px 0 rgba(0,0,0,.6)}
                                                                                /* :not guard: .spotcard.cos-* frames own their box-shadow */

  /* -- paper dialogs: .bboard (notifications, badge board, guide, exam, spin,
     sets, changelog, scan history), askDialog, Field Report, term popover */
  :root[data-theme="dark"] :is(.bboard,.askbox,.recapcard,.termpop){box-shadow:6px 6px 0 rgba(0,0,0,.6)}
  :root[data-theme="dark"] .termpop .tp-t{color:#E8705F}                       /* #98261A title was ~2:1 on #1B1F27 */

  /* -- notification tray (.bboard) */
  :root[data-theme="dark"] .notifsec,
  :root[data-theme="dark"] .notifdetail,
  :root[data-theme="dark"] .notifempty{color:var(--muted)}
  :root[data-theme="dark"] .notifsec::after{background:#3A4152}                /* var(--line) #2E3A48 vanished on dark paper */
  :root[data-theme="dark"] .notifrow{border-bottom-color:#3A4152}              /* color only: :last-of-type's border 0 still wins */
  :root[data-theme="dark"] .notifempty .ne-ico{color:#4A5266}

  /* -- badge board (.bboard) */
  :root[data-theme="dark"] .bbhead span,
  :root[data-theme="dark"] .bbsec i,
  :root[data-theme="dark"] .bbadge:not(.got):not(.near) .bbnm{color:var(--muted)}   /* guards keep gold (.got) / green (.near) names */
  :root[data-theme="dark"] .bbbar{background-color:var(--paper-edge)}          /* #c9c4b6 cream track glared; recessed groove instead */
  :root[data-theme="dark"] .bbadge:not(.got){background-color:var(--paper-dot)} /* #d8d4c8 plates; :not(.got) so earned tiles keep --panel */

  /* -- daily Field Report (.recapcard) */
  :root[data-theme="dark"] .recaphead span,
  :root[data-theme="dark"] .recapnote,
  :root[data-theme="dark"] .recapsec{color:var(--muted)}
  :root[data-theme="dark"] .recaprow{border-bottom-color:rgba(228,231,236,.15)}

  /* -- full-screen overlays on --paper-overlay (deck, burst haul, Season Rewind) */
  :root[data-theme="dark"] .deckempty,
  :root[data-theme="dark"] .batchwrap .bhead span{color:var(--muted)}
  :root[data-theme="dark"] .batchwrap .bdots i:not(.on){background:#4A5266}   /* #b6b2a6 inactive dots outshone the red active one */

  /* -- map popups (.leaflet-popup-content-wrapper is --paper) */
  :root[data-theme="dark"] .mpop-mk{color:var(--muted)}
