
  /* =========================================================================
     Rift-Tracker — design system
     Tokens → primitives → app shell → views → overlays → responsive.
     ========================================================================= */

  :root{
    /* surfaces, dark to light */
    --bg:#0d1116;
    --rail:#090c11;
    --panel:#171d26;
    --panel-hi:#1c232d;      /* top of the panel gradient */
    --panel-2:#212934;       /* hover / raised */
    --inset:#0f141a;         /* wells: inputs, art beds, tracks */
    --line:#242c37;
    --line-2:#333d4b;
    /* ink */
    --txt:#e8eef5;
    --dim:#8a97a8;
    --dim-2:#5f6b7a;
    /* brand + status */
    --accent:#c8963e;
    --accent-lit:#e6bd72;
    --accent-ink:#150f06;
    --good:#46b95c;
    --bad:#ef5f57;
    --info:#5f8fd0;
    --accent-wash:color-mix(in srgb, var(--accent) 15%, transparent);
    --good-wash:color-mix(in srgb, var(--good) 15%, transparent);
    --bad-wash:color-mix(in srgb, var(--bad) 15%, transparent);
    /* legacy aliases still referenced by older markup */
    --muted:var(--dim); --border:var(--line); --fg:var(--txt);
    /* shape */
    --r-xs:6px; --r-sm:9px; --r-md:12px; --r-lg:14px; --r-xl:20px; --pill:999px;
    /* elevation */
    --sh-1:0 1px 2px rgba(0,0,0,.4);
    --sh-2:0 10px 30px rgba(0,0,0,.4);
    --sh-3:0 30px 70px rgba(0,0,0,.55);
    /* a panel's surface: gradient + hairline top highlight */
    --surface:linear-gradient(180deg,var(--panel-hi),var(--panel));
    --edge:inset 0 1px 0 rgba(255,255,255,.045);
    /* the same top-edge trick on a saturated fill, which needs more of it to
       register at all — this is what gives the brass controls their dimension
       now that they are flat */
    --edge-lit:inset 0 1px 0 rgba(255,255,255,.16);
    /* motion */
    --ease:cubic-bezier(.2,.7,.3,1);
    --t:.15s var(--ease);
    /* metrics */
    --side-w:236px;
    --ctrl-h:36px;
    /* Wide enough that the collection grid can draw a card big enough to read.
       Riot's master art is 744px, and a gallery tile was only ever handing 168 of
       those to the screen at 1920 — the grid was capped at 914px inside 1684px of
       room. The cap is the legibility limit here, not the source resolution. */
    --page:1720px;
    --main-pad:32px;  /* main's side gutter; .bleed cancels it to run full-width */
  }

  *,*::before,*::after{ box-sizing:border-box; }
  [hidden]{ display:none !important; }   /* beats display rules like .gallery{display:grid} */

  /* Reserve the vertical scrollbar's track at all times. Without this, any
     render that shortens the page below the viewport (filtering to no matches,
     switching to a short tab, an empty state) drops the scrollbar and the
     centred `main` slides sideways by half its width. */
  html{ -webkit-text-size-adjust:100%; scrollbar-gutter:stable; }
  body{
    /* clip (not hidden) so the 100vw .bleed bands can't add a horizontal
       scrollbar while position:sticky still resolves against the viewport */
    margin:0; background:var(--bg); color:var(--txt); min-height:100vh; overflow-x:clip;
    font:14.5px/1.55 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
    -webkit-font-smoothing:antialiased;
  }
  /* ambient wash so the dark UI has some depth instead of reading flat */
  body::before{
    content:""; position:fixed; inset:0; pointer-events:none; z-index:0;
    background:
      radial-gradient(720px 380px at 22% -6%, color-mix(in srgb,var(--accent) 8%,transparent), transparent 70%),
      radial-gradient(620px 420px at 100% 4%, color-mix(in srgb,var(--info) 7%,transparent), transparent 70%);
  }
  h1,h2,h3,h4{ margin:0; font-weight:640; letter-spacing:-.015em; line-height:1.25; }
  p{ margin:0; }
  a{ color:var(--accent-lit); text-decoration:none; }
  a:hover{ text-decoration:underline; }
  ::selection{ background:color-mix(in srgb,var(--accent) 34%,transparent); }
  :focus-visible{ outline:2px solid color-mix(in srgb,var(--accent) 75%,transparent); outline-offset:2px; }
  .num,.v,.value,td.num,th.num,.tabular{ font-variant-numeric:tabular-nums; }

  /* ---- icons -------------------------------------------------------------- */
  .ic{ width:16px; height:16px; flex:none; display:inline-block; vertical-align:-3px;
       fill:none; stroke:currentColor; stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round; }
  .ic-sm{ width:14px; height:14px; vertical-align:-2px; }
  /* A flag paints itself; .ic's stroke-and-no-fill would empty it out. The hair
     border keeps the white half of the Polish and French flags off a light
     panel edge. */
  .flg{ width:20px; height:13.5px; flex:none; display:inline-block; vertical-align:-2px;
        border-radius:2px; box-shadow:0 0 0 1px color-mix(in srgb,var(--txt) 22%,transparent);
        fill:initial; stroke:none; }
  .ic-lg{ width:20px; height:20px; vertical-align:-4px; }
  .icon-tile{ width:38px; height:38px; border-radius:var(--r-md); flex:none;
              display:flex; align-items:center; justify-content:center;
              background:var(--accent-wash); color:var(--accent-lit);
              border:1px solid color-mix(in srgb,var(--accent) 24%,transparent); }
  .icon-tile .ic{ width:19px; height:19px; vertical-align:0; }

  /* ---- buttons ------------------------------------------------------------ */
  /* The primary is one flat brass face, and it is exactly --accent — the same
     value as the rail marker, the chip edges and your own chat bubbles, so a
     button and the accents around it finally read as one colour instead of two
     nearby golds. Depth is a hairline top highlight, not a fill gradient: over
     36px a light-to-dark ramp doesn't read as a lit surface, it reads as a
     sticker, and the dark keyline the ramp needed to sit on made it worse. */
  /* `.aslink` marks a link that IS a control: navigation that goes to a real URL
     is rendered as an <a> so middle-click and "open in new tab" work on it, and
     it has to be indistinguishable from the button it replaces — so it wears the
     same base, and every class rule below it applies to both.

     The class sits inside :where() so the selector weighs exactly what a bare
     `button` weighs. Written plainly, `a:where(.aslink)` would outrank every one-class
     rule in this sheet (.side-brand, .userchip, .home-link…) and repaint each of
     them as a primary button — the opposite of the point. */
  button,a:where(.aslink){
    display:inline-flex; align-items:center; justify-content:center; gap:7px;
    min-height:var(--ctrl-h); padding:0 14px; border-radius:var(--r-sm);
    background:var(--accent); color:var(--accent-ink); border:1px solid var(--accent);
    font:inherit; font-size:13.5px; font-weight:600; letter-spacing:-.005em;
    cursor:pointer; white-space:nowrap; box-shadow:var(--edge-lit),0 1px 1px rgba(0,0,0,.28);
    transition:filter var(--t),background var(--t),border-color var(--t),color var(--t),transform var(--t);
  }
  /* Brightness rather than a background swap, deliberately: it composes, so the
     variants below and the several dozen one-off button classes further down all
     inherit a working hover from this one rule. On a flat fill it resolves to
     "the same brass, a step up", which is the state we'd have hand-picked. */
  /* A control is not prose: `a:hover{text-decoration:underline}` above is for
     links written into a sentence, and an .aslink is a button that happens to
     have an address. Same weight as that rule (a + one pseudo-class) and later
     in the sheet, so it wins without needing !important. */
  button:hover,a:where(.aslink):hover{ filter:brightness(1.06); text-decoration:none; }
  button:active{ transform:translateY(1px); }
  button:disabled{ opacity:.45; cursor:not-allowed; transform:none; filter:none; }
  /* Every variant answers to a:where(.aslink) as well, so converting a control to a link
     for the sake of "open in new tab" never changes how it looks. */
  button.ghost,a:where(.aslink).ghost{ background:var(--panel); color:var(--txt); border-color:var(--line); box-shadow:var(--edge); }
  button.ghost:hover,a:where(.aslink).ghost:hover{ background:var(--panel-2); border-color:var(--line-2); filter:none; }
  button.subtle,a:where(.aslink).subtle{ background:transparent; color:var(--dim); border-color:transparent; box-shadow:none; }
  button.subtle:hover,a:where(.aslink).subtle:hover{ color:var(--txt); background:var(--panel-2); }
  /* same construction as the primary, in the destructive hue */
  button.danger,a:where(.aslink).danger{ background:var(--bad); border-color:var(--bad); color:#fff; box-shadow:var(--edge-lit),0 1px 1px rgba(0,0,0,.28); }
  /* quiet destructive: reads as a normal control until you reach for it */
  button.ghost.danger,a:where(.aslink).ghost.danger{ background:var(--panel); color:var(--dim); border-color:var(--line); }
  button.ghost.danger:hover,a:where(.aslink).ghost.danger:hover{ background:var(--bad-wash); color:var(--bad);
                             border-color:color-mix(in srgb,var(--bad) 42%,transparent); }
  button.link,a:where(.aslink).link{ background:none; border:0; box-shadow:none; color:var(--dim); padding:0; min-height:0; font-weight:500; }
  button.link:hover,a:where(.aslink).link:hover{ color:var(--accent-lit); }
  .btn-xs{ min-height:29px; padding:0 10px; font-size:12.5px; border-radius:var(--r-xs); }
  .btn-lg{ min-height:44px; padding:0 20px; font-size:15px; border-radius:var(--r-md); }
  .iconbtn{ width:var(--ctrl-h); height:var(--ctrl-h); min-height:0; padding:0; border-radius:var(--r-sm);
            background:transparent; color:var(--dim); border:1px solid transparent; box-shadow:none; }
  .iconbtn:hover{ background:var(--panel-2); color:var(--txt); filter:none; }
  .iconbtn.bordered{ border-color:var(--line); background:var(--panel); box-shadow:var(--edge); }
  .iconbtn .ic{ vertical-align:0; }

  /* ---- form controls ------------------------------------------------------ */
  input,select,textarea{
    min-height:var(--ctrl-h); padding:7px 12px; border-radius:var(--r-sm);
    background:var(--inset); border:1px solid var(--line); color:var(--txt);
    font:inherit; font-size:13.5px; transition:border-color var(--t),box-shadow var(--t),background var(--t);
  }
  textarea{ line-height:1.6; }
  input::placeholder,textarea::placeholder{ color:var(--dim-2); }
  input:hover,select:hover,textarea:hover{ border-color:var(--line-2); }
  input:focus,select:focus,textarea:focus{
    outline:none; border-color:color-mix(in srgb,var(--accent) 60%,var(--line));
    box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 15%,transparent);
  }
  select{ appearance:none; padding-right:30px; cursor:pointer;
    background-image:linear-gradient(45deg,transparent 50%,var(--dim) 50%),linear-gradient(135deg,var(--dim) 50%,transparent 50%);
    background-position:calc(100% - 15px) calc(50% + 1px),calc(100% - 10px) calc(50% + 1px);
    background-size:5px 5px,5px 5px; background-repeat:no-repeat; }
  input[type=checkbox]{ width:16px; height:16px; min-height:0; padding:0; accent-color:var(--accent); cursor:pointer; }
  label{ cursor:pointer; }
  /* input with a leading icon */
  .field{ position:relative; display:flex; align-items:center; }
  .field > .ic{ position:absolute; left:11px; color:var(--dim-2); pointer-events:none; transition:color var(--t); }
  .field > input{ width:100%; padding-left:34px; }
  .field > input:focus ~ .ic{ color:var(--accent-lit); }
  /* trailing clear button — surfaces only once the field holds text and you're
     on it, so the idle toolbar stays as quiet as it was */
  .field.has-clear > input{ padding-right:32px; }
  .field > .clr{ position:absolute; right:5px; display:none; width:24px; height:24px; min-height:0;
                 padding:0; align-items:center; justify-content:center; border-radius:6px;
                 background:transparent; border:0; box-shadow:none; color:var(--dim-2); }
  .field > .clr .ic{ width:13px; height:13px; vertical-align:0; }
  .field > .clr:hover{ color:var(--txt); background:var(--panel-2); filter:none; }
  .field.has-val:hover > .clr,.field.has-val:focus-within > .clr{ display:flex; }

  /* ---- text helpers ------------------------------------------------------- */
  .eyebrow{ font-size:10.5px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--dim); }
  .note,.hint{ color:var(--dim); font-size:13px; }
  .muted{ color:var(--dim); }
  .mono{ font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }

  /* ---- surfaces ----------------------------------------------------------- */
  .panel{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); box-shadow:var(--edge),var(--sh-1); }
  .panel-pad{ padding:18px 20px; }
  /* small caps section rule */
  .sec-head{ display:flex; align-items:center; gap:12px; margin:28px 0 14px; }
  .sec-head h3{ font-size:11px; font-weight:700; letter-spacing:.12em; text-transform:uppercase; color:var(--dim); }
  .sec-head::after{ content:""; flex:1; height:1px; background:linear-gradient(90deg,var(--line),transparent); }
  .sec-head .sh-extra{ color:var(--dim); font-size:12px; }

  /* =========================================================================
     App shell: left rail + page column
     ========================================================================= */
  .app{ position:relative; z-index:1; display:grid; grid-template-columns:var(--side-w) minmax(0,1fr); min-height:100vh; }
  body.anon .app{ grid-template-columns:minmax(0,1fr); }
  body.anon .side{ display:none; }

  .side{
    position:sticky; top:0; align-self:start; height:100vh; z-index:40;
    display:flex; flex-direction:column; gap:6px; padding:18px 14px 16px;
    background:var(--rail); border-right:1px solid var(--line);
  }
  /* ---- brand lockup -------------------------------------------------------
     Drawn, not an image file: it inherits the accent token, stays crisp at any
     size, and costs no request. A rune-shaped mark beside two-tone type. */
  .brandmark{ display:inline-flex; align-items:center; gap:9px; line-height:1; }
  .bm-mark{ width:29px; height:29px; border-radius:9px; flex:none;
            display:flex; align-items:center; justify-content:center; color:var(--accent-lit);
            background:linear-gradient(150deg,color-mix(in srgb,var(--accent) 30%,transparent),transparent 72%),
                       var(--panel-2);
            border:1px solid color-mix(in srgb,var(--accent) 40%,transparent);
            box-shadow:inset 0 1px 0 rgba(255,255,255,.08), 0 2px 9px rgba(0,0,0,.45);
            transition:border-color var(--t),box-shadow var(--t); }
  .bm-mark .ic{ width:17px; height:17px; stroke-width:1.5; vertical-align:0; }
  /* the rift itself reads heavier than its frame, or it muddies at 17px */
  .bm-mark .ic path + path{ stroke-width:2.2; }
  .bm-wm{ font-size:14px; font-weight:600; letter-spacing:.15em; color:var(--txt); white-space:nowrap; }
  /* "RIFT" carries the gold; "TRACKER" is the quiet half of the pair */
  .bm-wm b{ font-weight:800; letter-spacing:.11em;
            background:linear-gradient(96deg,var(--accent-lit),var(--accent) 70%);
            -webkit-background-clip:text; background-clip:text; color:transparent; }
  .bm-wm i{ font-style:normal; color:var(--dim-2); padding:0 1px; }
  .brandmark.sm .bm-mark{ width:26px; height:26px; border-radius:8px; }
  .brandmark.sm .bm-mark .ic{ width:15px; height:15px; }
  .brandmark.sm .bm-wm{ font-size:12.5px; }
  .brandmark.lg{ gap:12px; }
  .brandmark.lg .bm-mark{ width:40px; height:40px; border-radius:12px; }
  .brandmark.lg .bm-mark .ic{ width:23px; height:23px; }
  .brandmark.lg .bm-wm{ font-size:18px; }
  button:hover > .brandmark .bm-mark,a:hover > .brandmark .bm-mark{
    border-color:color-mix(in srgb,var(--accent) 68%,transparent);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 0 0 3px color-mix(in srgb,var(--accent) 12%,transparent); }

  .side-brand{
    display:flex; align-items:center; height:44px; padding:0 6px; margin-bottom:12px;
    background:none; border:0; box-shadow:none; color:var(--txt); cursor:pointer; font-weight:inherit;
  }
  .side-brand:hover{ background:none; filter:none; }

  /* Scanner entry point, parked right under the wordmark so it reads as a
     companion app rather than another nav tab. */
  .scanner-link{
    display:flex; align-items:center; gap:10px; padding:8px 10px; margin-bottom:16px;
    border-radius:var(--r-md); background:var(--accent-wash);
    border:1px solid color-mix(in srgb,var(--accent) 20%,transparent);
    color:var(--txt); transition:border-color var(--t),background var(--t);
  }
  .scanner-link:hover{
    text-decoration:none; background:color-mix(in srgb,var(--accent) 15%,transparent);
    border-color:color-mix(in srgb,var(--accent) 42%,transparent);
  }
  .sl-mark{ width:30px; height:30px; border-radius:9px; flex:none; display:flex;
            align-items:center; justify-content:center; color:var(--accent-lit);
            background:color-mix(in srgb,var(--accent) 16%,transparent);
            border:1px solid color-mix(in srgb,var(--accent) 26%,transparent); }
  .sl-mark .ic{ width:17px; height:17px; vertical-align:0; }
  .scanner-link:hover .sl-mark{ border-color:color-mix(in srgb,var(--accent) 55%,transparent); }
  .sl-tx{ display:flex; flex-direction:column; gap:1px; min-width:0; }
  .sl-tx b{ font-size:12.5px; font-weight:700; letter-spacing:.02em; }
  .sl-tx span{ font-size:10.5px; color:var(--dim); white-space:nowrap;
               overflow:hidden; text-overflow:ellipsis; }
  .scanner-link[hidden]{ display:none; }   /* author display:flex would beat the UA [hidden] rule */

  .side-nav{ display:flex; flex-direction:column; gap:2px; }
  .side-nav a{
    position:relative; justify-content:flex-start; width:100%; height:38px; min-height:38px; padding:0 12px;
    border-radius:var(--r-sm); background:transparent; border:0; box-shadow:none;
    color:var(--dim); font-size:13.5px; font-weight:600; gap:11px;
  }
  .side-nav a:hover{ background:var(--panel); color:var(--txt); filter:none; }
  .side-nav a.on{ background:var(--accent-wash); color:var(--accent-lit); }
  .side-nav a.on::before{
    content:""; position:absolute; left:0; top:9px; bottom:9px; width:3px;
    border-radius:0 3px 3px 0; background:var(--accent);
  }
  .side-nav a .ic{ opacity:.95; }
  .side-nav a .tab-badge{ margin-left:auto; }

  .side-foot{ margin-top:auto; display:flex; flex-direction:column; gap:10px; padding-top:14px; }
  .side-val{ padding:12px 13px; border-radius:var(--r-md); background:var(--surface);
             border:1px solid var(--line); box-shadow:var(--edge); }
  .side-val .k{ display:block; font-size:10px; font-weight:700; letter-spacing:.11em;
                text-transform:uppercase; color:var(--dim); }
  .side-val .total-val{ display:block; font-size:20px; font-weight:700; letter-spacing:-.02em;
                        font-variant-numeric:tabular-nums; margin-top:3px; color:var(--accent-lit); }
  .side-val .s{ display:block; color:var(--dim); font-size:11px; margin-top:2px;
                overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  /* EU/US marketplace switch, under the collection value */
  .mkt{ display:flex; gap:2px; margin-top:8px; padding:2px; border-radius:var(--r-sm);
        background:var(--bg-2, rgba(255,255,255,.04)); border:1px solid var(--line, rgba(255,255,255,.08)); }
  .mkt button{ flex:1; height:24px; padding:0; border:0; background:transparent; box-shadow:none; cursor:pointer;
        color:var(--dim); font-size:11px; font-weight:700; letter-spacing:.03em; border-radius:calc(var(--r-sm) - 2px);
        transition:color var(--t), background var(--t); }
  .mkt button:hover{ color:var(--txt); }
  /* the accent wash, like every other "selected" segment in the app — the plain
     white tint this used to carry made the switch look unrelated to the rest */
  .mkt button.on{ background:var(--accent-wash); color:var(--accent-lit); }
  .mkt button[disabled]{ opacity:.4; cursor:not-allowed; }
  /* A price that just moved pulses once, green up / red down, then settles. */
  @keyframes pmove-up   { 0%{ background:rgba(64,200,120,.30); } 100%{ background:transparent; } }
  @keyframes pmove-down { 0%{ background:rgba(230,90,90,.30); }  100%{ background:transparent; } }
  .pmoved{ border-radius:5px; animation:pmove-up 2.4s ease-out 1; }
  .pmoved.down{ animation-name:pmove-down; }
  @media (prefers-reduced-motion: reduce){ .pmoved{ animation:none; } }
  .userchip{
    display:flex; align-items:center; gap:9px; height:44px; width:100%; justify-content:flex-start;
    background:transparent; border:1px solid transparent; box-shadow:none; border-radius:var(--r-md);
    padding:0 10px 0 6px; cursor:pointer; color:var(--txt); font-weight:600;
  }
  .userchip:hover{ background:var(--panel); border-color:var(--line); filter:none; }
  .userchip.noav{ padding:0 10px; }   /* no photo → even padding */
  .userchip img{ width:30px; height:30px; border-radius:50%; object-fit:cover; background:var(--inset); flex:none; }
  .userchip #userName{ font-size:13px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .userchip .uc-gear{ display:flex; margin-left:auto; color:var(--dim-2); }
  .userchip:hover .uc-gear{ color:var(--accent-lit); }

  /* compact top bar — narrow screens only */
  #lbarSignIn .lb-short{ display:none; }

  .langpick{ position:relative; display:inline-block; }
  .lp-btn{ display:inline-flex; align-items:center; gap:7px;
    padding:5px 9px; border:1px solid var(--line); border-radius:var(--r-sm);
    background:none; box-shadow:none; min-height:0;
    color:var(--dim); font-size:13px; font-weight:500; line-height:1; cursor:pointer;
    white-space:nowrap; transition:color var(--t),background var(--t),border-color var(--t); }
  .lp-btn:hover{ color:var(--txt); background:var(--panel-2); }
  .lp-btn[aria-expanded="true"]{ color:var(--txt); background:var(--panel-2); border-color:var(--line-2); }
  .lp-chev{ width:13px; height:13px; opacity:.65; }
  .langpick.compact .lp-full{ display:none; }

  /* The list. It opens upward in the rail, where the control sits at the bottom
     of the screen, and downward everywhere else. */
  .lp-menu{ position:absolute; z-index:80; right:0; top:calc(100% + 6px);
    margin:0; padding:5px; list-style:none; min-width:168px;
    background:var(--panel); border:1px solid var(--line-2); border-radius:var(--r-md);
    box-shadow:0 18px 40px -12px rgba(0,0,0,.6); }
  .lp-menu li{ display:flex; align-items:center; gap:9px; padding:8px 10px;
    border-radius:var(--r-sm); color:var(--dim); font-size:13.5px; cursor:pointer; outline:0; }
  .lp-menu li:hover,.lp-menu li:focus{ color:var(--txt); background:var(--panel-2); }
  .lp-menu li:focus{ box-shadow:inset 0 0 0 1px var(--line-2); }
  .lp-menu li[aria-selected="true"]{ color:var(--txt); font-weight:600; }
  .lp-menu li span{ flex:1; }
  .lp-tick{ width:14px; height:14px; color:var(--accent); }
  .side-foot .langpick{ width:100%; }
  .side-foot .lp-btn{ width:100%; justify-content:flex-start; }
  .side-foot .lp-menu{ bottom:calc(100% + 6px); top:auto; left:0; right:auto; }

  .mobar{ display:none; }
  .mo-brand{ background:none; border:0; box-shadow:none; padding:0; min-height:0; cursor:pointer; }
  .mo-val{ margin-left:auto; min-width:0; display:flex; align-items:center; gap:8px; }
  .mo-num{ min-width:0; text-align:right; line-height:1.2; }
  .mo-num .total-val{ white-space:nowrap; }
  /* Compact form of the rail's switch: beside the value here, under it there. */
  .mo-val .mkt{ flex:none; margin-top:0; }
  /* min-height:0 as well as height: every button on the page carries the global
     --ctrl-h minimum, which is what the rail's own switch quietly sits at. */
  .mo-val .mkt button{ height:26px; min-height:0; padding:0 7px; font-size:10px; }
  .mo-val .total-val{ font-size:15px; font-weight:700; color:var(--accent-lit); font-variant-numeric:tabular-nums; }
  .mo-val .s{ display:block; font-size:10px; color:var(--dim); }
  /* The rail's scanner tile has no room in a bottom tab bar, so on narrow
     screens the entry point lives here instead — present on every signed-in
     page, not just the ones that happen to render a CTA. */
  .mo-scan{
    flex:none; display:inline-flex; align-items:center; gap:6px; height:34px; padding:0 11px;
    border-radius:var(--r-sm); background:var(--accent-wash); color:var(--accent-lit);
    border:1px solid color-mix(in srgb,var(--accent) 30%,transparent);
    font-size:12px; font-weight:700; letter-spacing:.02em;
  }
  .mo-scan:hover{ text-decoration:none; background:color-mix(in srgb,var(--accent) 15%,transparent); }
  .mo-scan .ic{ width:16px; height:16px; vertical-align:0; }
  /* Feedback on a phone: the bottom rail is already full at six labels, so this
     tab gets a top-bar icon instead of a seventh cramped bottom button. */
  .mo-fb{
    flex:none; position:relative; display:inline-flex; align-items:center; justify-content:center;
    width:34px; height:34px; padding:0; min-height:0; border-radius:var(--r-sm);
    background:var(--panel); border:1px solid var(--line); color:var(--dim); box-shadow:none;
  }
  .mo-fb:hover{ filter:none; background:var(--panel-2); color:var(--txt); }
  .mo-fb .ic{ width:17px; height:17px; }
  .mo-fb .tab-badge{ position:absolute; top:-6px; right:-6px; box-shadow:0 0 0 2px var(--bg); }
  /* Same job for the tab pages: desktop already has the rail tile, so this one
     only earns its place once the rail has collapsed. */
  .scan-cta{ display:none; }

  .page{ min-width:0; display:flex; flex-direction:column; }
  main{ width:100%; max-width:var(--page); margin:0 auto; padding:30px var(--main-pad) 90px; flex:1; }
  /* run a band edge-to-edge by cancelling main's gutter. With the rail hidden
     main is simply centred, so the public page can go the whole viewport. */
  .bleed{ margin-inline:calc(var(--main-pad) * -1); }
  body.anon .bleed{ width:100vw; margin-inline:calc(50% - 50vw); }
  body.anon main{ padding-top:0; }  /* the landing's own sticky bar starts the page */

  .tab-badge{ display:inline-flex; align-items:center; justify-content:center; min-width:18px; height:18px;
              padding:0 5px; font-size:10.5px; font-weight:700; border-radius:var(--pill);
              background:var(--bad); color:#fff; }

  /* ---- page header -------------------------------------------------------- */
  .page-head{ display:flex; align-items:flex-end; gap:16px; flex-wrap:wrap; margin-bottom:22px; }
  .page-head .ph-main{ min-width:0; }
  .page-head h2{ font-size:25px; letter-spacing:-.03em; font-weight:700; }
  .page-head .ph-sub{ color:var(--dim); font-size:13.5px; margin-top:5px; }
  .page-head .ph-actions{ margin-left:auto; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }

  .banner{ display:flex; gap:10px; align-items:flex-start; background:var(--accent-wash);
           border:1px solid color-mix(in srgb,var(--accent) 28%,transparent);
           padding:12px 14px; border-radius:var(--r-md); margin-bottom:18px; font-size:13.5px; }
  .banner code{ background:rgba(0,0,0,.35); padding:1px 6px; border-radius:5px; }

  /* ---- stat tiles ---------------------------------------------------------
     The owner dashboard and the account page still lay their stats out as a
     grid of boxes, where the numbers are genuinely peers and there are a dozen
     of them. Wishlist and Summary do not — see .statband below. */
  .dash-grid,.acc-stats{
    display:grid; grid-template-columns:repeat(auto-fit,minmax(158px,1fr)); gap:12px;
  }
  .tile,.acc-stat{
    position:relative; overflow:hidden; padding:15px 16px; border-radius:var(--r-lg);
    background:var(--surface); border:1px solid var(--line); box-shadow:var(--edge),var(--sh-1);
  }
  .tile .k,.acc-stat .k{
    color:var(--dim); font-size:10.5px; font-weight:700; letter-spacing:.11em; text-transform:uppercase;
  }
  .tile .v,.acc-stat .v{
    font-size:23px; font-weight:700; margin-top:6px; line-height:1.12; font-variant-numeric:tabular-nums;
    letter-spacing:-.025em; overflow-wrap:anywhere;
  }
  .tile .v small{ font-size:.58em; color:var(--dim); font-weight:600; letter-spacing:0; }
  .tile .s{ color:var(--dim); font-size:11.5px; margin-top:5px; }
  .tile.warn{ border-color:color-mix(in srgb,var(--accent) 42%,var(--line)); }
  .tile.warn .v{ color:var(--accent-lit); }
  .tile.bad{ border-color:color-mix(in srgb,var(--bad) 42%,var(--line)); }
  .tile.bad .v{ color:var(--bad); }
  .tile.good .v{ color:var(--good); }

  /* ---- stat band ----------------------------------------------------------
     What Wishlist and Summary use instead of a row of equal, equally loud
     boxes. Six tiles of the same weight say nothing about which number matters,
     and half of them were only there because the grid had room -- "Sets: 3",
     "Priced: 9/9".

     No surface and no dividers. A panel here would have been a box of numbers
     stacked above the boxes that hold the actual content, and ruling between
     the columns only redraws a grid the eye already reads from the alignment.
     What is left is type: the headline figure large and brass, its supporting
     figures small, one hairline closing the whole row off from the page. The
     row belongs to the page header, not to the content below it. */
  .statband{ display:flex; flex-wrap:wrap; gap:18px 44px; align-items:baseline;
             margin:-4px 0 24px; padding-bottom:20px; border-bottom:1px solid var(--line); }
  /* Sized to their own content, not to an equal-width track: a track would have
     spread three stats differently from four, and the Wishlist's row and the
     Summary's row would have had nothing in common. The supporting figures ride
     in their own group instead, held to the right edge — which also means that
     when the window is too narrow for one line they drop TOGETHER, rather than
     the row shedding its last stat onto a line of its own. */
  .sb-item{ flex:0 1 auto; min-width:0; }
  .sb-rest{ display:flex; flex-wrap:wrap; align-items:baseline; gap:18px 44px;
            justify-content:flex-end; min-width:0; margin-left:auto; }
  .sb-item .k{ color:var(--dim-2); font-size:10px; font-weight:700;
               letter-spacing:.13em; text-transform:uppercase; }
  /* Figures are one line and clip: a long card name belongs on the .s line,
     never wrapping the row into two different heights. */
  .sb-item .v{ margin-top:7px; font-size:19px; font-weight:700; line-height:1.2; letter-spacing:-.02em;
               font-variant-numeric:tabular-nums; color:var(--txt);
               white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .sb-item .s{ margin-top:4px; color:var(--dim); font-size:11.5px; line-height:1.35;
               white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .sb-item .s b{ color:var(--txt); font-weight:600; }
  .sb-item .v small{ font-size:.6em; color:var(--dim); font-weight:600; letter-spacing:0; }
  .sb-item .s .sep{ color:var(--dim-2); margin:0 2px; }
  /* The lead carries the emphasis on its own -- size and colour, no wash and no
     box -- and it is the one item that grows, so every bit of slack in the row
     opens between the number the page is about and the detail behind it. */
  .sb-lead{ flex:1 1 auto; }
  .sb-lead .v{ font-size:31px; letter-spacing:-.035em; color:var(--accent-lit); }
  .sb-item.up .v{ color:var(--good); }
  .sb-item.down .v{ color:var(--bad); }
  .sb-item .v .ic{ width:15px; height:15px; vertical-align:-2px; margin-right:2px; }

  /* ---- pills, tags, chips, dots ------------------------------------------- */
  .pill{ display:inline-flex; align-items:center; gap:5px; font-size:11px; color:var(--dim);
         border:1px solid var(--line); padding:1px 8px; border-radius:var(--pill); white-space:nowrap; }
  .tag{ display:inline-flex; align-items:center; gap:6px; font-size:12px; padding:3px 10px;
        border-radius:var(--pill); border:1px solid var(--line); background:var(--inset); color:var(--txt); }
  .tag .sw{ width:9px; height:9px; border-radius:50%; box-shadow:0 0 0 1px rgba(255,255,255,.2) inset; }
  .dot{ display:inline-block; width:9px; height:9px; border-radius:50%; margin-right:2px; vertical-align:middle;
        background:currentColor; box-shadow:0 0 0 1px rgba(255,255,255,.16) inset; }
  .rune{ width:16px; height:16px; display:inline-block; vertical-align:middle; object-fit:contain; }

  .prog{ display:block; background:var(--inset); border-radius:var(--pill); height:6px; overflow:hidden;
         box-shadow:inset 0 1px 2px rgba(0,0,0,.5); }
  .prog .fill{ display:block; height:100%; border-radius:var(--pill); transition:width .3s var(--ease);
               background:linear-gradient(90deg,color-mix(in srgb,var(--good) 70%,#fff),var(--good)); }
  .prog.part .fill{ background:linear-gradient(90deg,var(--accent-lit),var(--accent)); }

  /* ---- empty states ------------------------------------------------------- */
  .empty{ display:flex; flex-direction:column; align-items:center; justify-content:center; gap:5px;
          text-align:center; color:var(--dim); font-size:13.5px; padding:60px 24px;
          border:1px dashed var(--line); border-radius:var(--r-lg); background:rgba(23,29,38,.45); }
  .empty > .ic{ width:22px; height:22px; color:var(--accent); margin-bottom:12px; padding:10px;
                box-sizing:content-box; border-radius:var(--r-md); background:var(--accent-wash);
                border:1px solid color-mix(in srgb,var(--accent) 22%,transparent); }
  .empty b{ display:block; color:var(--txt); font-size:15px; font-weight:650; margin-bottom:2px; }
  /* The rich variant: an empty tab is the one screen where the app has to teach
     itself, so it carries the next action and the two or three things worth
     knowing about the tab rather than a single grey sentence. */
  .empty.rich{ padding:44px 24px; gap:0; }
  .empty.rich > .ic{ width:24px; height:24px; }
  .empty.rich b{ font-size:19px; letter-spacing:-.02em; margin-bottom:7px; }
  .empty .ez-tag{ color:var(--dim); font-size:13.5px; line-height:1.6; max-width:46ch; }
  .empty .ez-cta{ display:flex; flex-wrap:wrap; justify-content:center; gap:10px; margin-top:20px; }
  /* min(190px,100%), not a bare 190px. minmax()'s floor is a hard one: three
     steps demand 3*190+2*10 = 590px of min-content no matter how narrow the box
     is, and a `1fr` parent track honours that instead of wrapping -- which is
     what dragged the Friends column to 640px on a 390px phone for anyone whose
     list is empty. Wrapping the floor in min(...,100%) lets a column give up and
     fall to one-per-row. This block is shared by every empty state, so the same
     trap was armed on Wishlist and Decks too. */
  .empty .ez-steps{ display:grid; grid-template-columns:repeat(auto-fit,minmax(min(190px,100%),1fr)); gap:10px;
                    width:100%; max-width:660px; margin-top:26px; text-align:left; }
  .empty .ez-step{ display:flex; gap:11px; padding:13px 14px; border-radius:var(--r-md);
                   background:var(--inset); border:1px solid var(--line); }
  .empty .ez-step .n{ flex:none; width:21px; height:21px; border-radius:50%; display:flex; align-items:center;
                      justify-content:center; font-size:11px; font-weight:700; color:var(--accent-ink);
                      background:var(--accent); }
  .empty .ez-step .ic{ flex:none; color:var(--accent); width:17px; height:17px; margin-top:1px; }
  .empty .ez-step b{ font-size:13px; margin-bottom:3px; }
  .empty .ez-step .d{ color:var(--dim); font-size:12px; line-height:1.5; }
  .empty .ez-note{ margin-top:18px; color:var(--dim-2); font-size:12px; }
  /* The table hides itself but its .table-wrap frame stays, so in gallery view
     and on an empty list it collapses to a hairline under the toolbar. Whatever
     follows it — the gallery grid or an empty state — used to sit flush on that
     line; this keeps them apart. */
  .table-wrap:has(> table[hidden]){ margin-bottom:16px; }

  /* ---- add-card search + dropdown ----------------------------------------- */
  .search{ position:relative; }
  .search input{ width:100%; height:var(--ctrl-h); padding-left:34px; }
  .search > .ic{ position:absolute; left:11px; top:calc(50% - 8px); color:var(--dim-2); pointer-events:none; }
  .search input:focus ~ .ic{ color:var(--accent-lit); }
  .add-search{ width:250px; }
  /* The dropdown outgrew its input: it now carries art, an owned count and a
     stepper, and it stays open while you click. It hangs off the RIGHT edge —
     the search sits at the end of the page head, so growing leftwards is the
     only direction with room. */
  .results{ position:absolute; z-index:30; left:auto; right:0; top:calc(100% + 6px); text-align:left;
            width:max(100%, 400px); max-width:calc(100vw - var(--main-pad,28px) * 2);
            background:var(--panel); border:1px solid var(--line-2); border-radius:var(--r-md);
            max-height:400px; overflow:auto; scrollbar-gutter:stable; box-shadow:var(--sh-2); padding:5px; }
  .results .nores{ padding:9px 10px; color:var(--dim); font-size:13px; }
  /* The front page's inspect search borrows the same shell but not the same
     rows: it opens the detail sheet rather than adding, so there is nothing to
     count and nothing to step, and a plain one-liner is the whole result. */
  #homeResults div{ padding:8px 10px; border-radius:var(--r-xs); cursor:pointer; display:flex;
                    gap:10px; align-items:center; font-size:13.5px; }
  #homeResults div:hover{ background:var(--panel-2); }
  #homeResults .nm{ flex:1; min-width:0; }
  #homeResults .pr{ color:var(--dim); font-size:12px; text-transform:capitalize; }
  /* One result. Clicking anywhere that isn't a button adds `step` copies and
     leaves the row exactly where it is, so a 3-of is three clicks in one place
     instead of three trips through the search box. */
  .rrow{ padding:5px 7px; border-radius:var(--r-xs); cursor:pointer; display:flex; gap:9px;
         align-items:center; font-size:13.5px; }
  .rrow:hover,.rrow.on{ background:var(--panel-2); }
  .rrow .rart{ width:31px; height:43px; flex:none; border-radius:4px; background:var(--inset);
               object-fit:cover; display:block; }
  .rrow .rmain{ flex:1; min-width:0; }
  .rrow .rnm{ display:flex; align-items:center; gap:5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .rrow .rsub{ display:block; color:var(--dim); font-size:11.5px; margin-top:2px; text-transform:capitalize;
               white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  /* The count that was missing entirely: what you already hold, ticking up in
     place as you click. Dim at zero, lit the moment you own any. */
  .rrow .rown{ flex:none; font-size:11px; font-weight:700; padding:2px 7px; border-radius:999px;
               background:var(--inset); color:var(--dim-2); border:1px solid var(--line);
               font-variant-numeric:tabular-nums; }
  .rrow .rown.has{ color:var(--accent-lit); border-color:color-mix(in srgb,var(--accent) 55%,transparent); }
  .rrow .rbtns{ flex:none; display:flex; align-items:center; gap:3px; }
  .rrow .rbtns button{ width:25px; height:25px; min-height:0; padding:0; display:grid; place-items:center; }
  .rrow .rbtns button .ic{ width:13px; height:13px; }
  .rrow .rbtns button[disabled]{ opacity:.32; cursor:default; }
  .results .rhint{ padding:6px 9px 3px; color:var(--dim-2); font-size:11px; display:flex; gap:10px;
                   flex-wrap:wrap; border-top:1px solid var(--line); margin-top:4px; }
  .results .rhint kbd{ font:inherit; background:var(--inset); border:1px solid var(--line);
                       border-radius:4px; padding:0 4px; color:var(--dim); }
  /* "3x rally" — the step the row's + button and a plain click both use. */
  .search:has(.stepbadge:not([hidden])) input{ padding-right:46px; }
  .search .stepbadge{ position:absolute; right:9px; top:calc(50% - 9px); height:18px; padding:0 6px;
                      border-radius:999px; font-size:10.5px; font-weight:700; line-height:18px;
                      background:var(--accent); color:var(--accent-ink); pointer-events:none; }

  /* ---- add sheet ----------------------------------------------------------
     Behind the ⋯ on a result: the one place finish, condition and quantity can
     be chosen before a card lands, since a plain click always adds a non-foil
     Near Mint copy. */
  #addSheet{ position:fixed; inset:0; background:rgba(0,0,0,.76); display:none; align-items:center;
             justify-content:center; z-index:120; padding:20px; }
  #addSheet .box{ position:relative; background:var(--surface); border:1px solid var(--line);
                  border-radius:var(--r-xl); box-shadow:var(--sh-3,var(--sh-2)); padding:22px;
                  width:min(560px,100%); max-height:calc(100vh - 40px); overflow:auto;
                  display:flex; gap:20px; }
  #addSheet .aclose{ position:absolute; top:14px; right:14px; width:30px; height:30px; min-height:0;
                     padding:0; display:grid; place-items:center; }
  #addSheet .aart{ width:150px; flex:none; }
  #addSheet .aart img{ width:100%; border-radius:var(--r-md); background:var(--inset); display:block; }
  #addSheet .aart .ph{ width:100%; aspect-ratio:5/7; border-radius:var(--r-md); background:var(--inset);
                       display:flex; align-items:center; justify-content:center; color:var(--dim-2); }
  #addSheet .ainfo{ flex:1; min-width:0; padding-right:22px; }
  #addSheet .atitle{ font-size:19px; font-weight:700; margin:0 0 2px; }
  #addSheet .asub{ color:var(--dim); font-size:12.5px; margin-bottom:4px; }
  #addSheet .aprice{ font-size:20px; font-weight:750; color:var(--accent-lit);
                     font-variant-numeric:tabular-nums; margin-bottom:16px; }
  #addSheet .arow{ display:flex; align-items:center; gap:10px; margin-bottom:11px; font-size:13px; }
  #addSheet .arow > span:first-child{ width:78px; flex:none; color:var(--dim); }
  #addSheet .aqty{ display:flex; align-items:center; gap:4px; }
  #addSheet .aqty button{ width:28px; height:28px; min-height:0; padding:0; display:grid; place-items:center; }
  #addSheet .aqty input{ width:56px; text-align:center; font-variant-numeric:tabular-nums; font-weight:700; }
  #addSheet .awhy{ color:var(--dim-2); font-size:11.5px; }
  #addSheet .aowned{ color:var(--dim); font-size:12.5px; margin:16px 0 0; }
  #addSheet .aacts{ display:flex; gap:9px; margin-top:14px; }
  @media (max-width:560px){
    #addSheet .box{ flex-direction:column; padding:18px; }
    #addSheet .aart{ width:112px; }
    #addSheet .ainfo{ padding-right:0; }
  }

  /* =========================================================================
     Collection
     ========================================================================= */
  .layout{ display:flex; gap:22px; align-items:flex-start; }
  .filters{ width:240px; flex:none; position:sticky; top:24px; border-radius:var(--r-lg);
            background:var(--surface); border:1px solid var(--line); box-shadow:var(--edge),var(--sh-1);
            overflow:hidden; }
  .filters .fpanel-head{ display:flex; align-items:center; justify-content:flex-start; gap:8px; width:100%;
                         padding:12px 14px; min-height:0; border:0; border-bottom:1px solid var(--line);
                         border-radius:0; background:transparent; box-shadow:none; cursor:default; }
  .filters .fpanel-head b{ margin-right:auto; }
  .filters .fpanel-head:hover{ filter:none; }
  .filters .fchev{ display:none; margin-left:9px; color:var(--dim); }
  .filters .fpanel-head .ic{ width:14px; height:14px; color:var(--accent); }
  .filters .fpanel-head b{ font-size:11px; font-weight:700; letter-spacing:.12em; text-transform:uppercase; color:var(--dim); }
  .filters .fpanel-head .fcount{ font-size:10.5px; padding:1px 8px; }
  .fgroup{ padding:14px; border-bottom:1px solid var(--line); }
  .fgroup:last-of-type{ border-bottom:0; }
  .fhead{ display:flex; align-items:center; font-size:10.5px; font-weight:700; letter-spacing:.12em;
          text-transform:uppercase; color:var(--dim-2); margin-bottom:9px; }
  .fhead .fclear{ margin-left:auto; background:none; border:0; box-shadow:none; color:var(--accent);
                  font-size:10.5px; font-weight:700; letter-spacing:.06em; padding:0; min-height:0; text-transform:uppercase; }
  .fhead .fclear:hover{ color:var(--accent-lit); background:none; }
  .chips{ display:flex; flex-wrap:wrap; gap:6px; }
  /* Each chip is only as wide as its own icon + name + count, so the rows come
     out ragged the way a tag cloud does. Stretching them to a flush edge made
     the rail read as a table of equal-weight cells and buried the fact that
     "Order" is a short word and "Spiritforged" is a long one. Shrinking (not
     growing) is still allowed, so a set name longer than the rail truncates
     instead of pushing out of the panel. */
  .chip{ display:inline-flex; flex:0 1 auto; max-width:100%; align-items:center; gap:5px; min-width:0;
         /* the selected colour, defaulted to the brand gold; the Colors group
            overrides it per domain inline so a live filter wears its own hue */
         --c:var(--accent);
         padding:4px 9px; border-radius:var(--pill);
         border:1px solid var(--line); background:var(--inset); color:var(--txt);
         font-size:12px; cursor:pointer; user-select:none;
         transition:border-color var(--t),background var(--t),color var(--t),box-shadow var(--t); }
  .chip:hover{ border-color:var(--line-2); background:var(--panel-2); }
  .chip.on{ background:color-mix(in srgb,var(--c) 14%,transparent);
            border-color:color-mix(in srgb,var(--c) 55%,var(--line));
            color:color-mix(in srgb,var(--c) 72%,#fff);
            box-shadow:0 0 0 1px color-mix(in srgb,var(--c) 20%,transparent); }
  .chip.on:hover{ border-color:color-mix(in srgb,var(--c) 78%,var(--line));
                  background:color-mix(in srgb,var(--c) 20%,transparent); }
  .chip.off{ opacity:.34; cursor:default; }
  .chip.off:hover{ border-color:var(--line); background:var(--inset); }
  /* runes sit muted until their filter is live, so the lit ones read at a glance */
  .chip .rune{ width:14px; height:14px; display:block; filter:drop-shadow(0 0 1px rgba(0,0,0,.6));
               opacity:.6; transition:opacity var(--t); }
  /* .off already dims the whole chip — don't dim the rune twice on top of it */
  .chip:hover .rune,.chip.on .rune,.chip.off .rune{ opacity:1; }
  .chip .sw{ width:8px; height:8px; border-radius:50%; flex:none; }
  /* min-width so a count dropping from two digits to one doesn't resize the
     chip and reflow every chip after it — these re-render on every keystroke */
  .chip .cnt{ padding-left:2px; color:var(--dim-2); font-size:10.5px;
              min-width:2ch; text-align:right; font-variant-numeric:tabular-nums; }
  .chip > span:not(.cnt){ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  /* inherit the lit chip colour rather than re-mixing it — the domain hues vary
     enough in lightness that a fixed alpha reads fine on some and washes out on
     others (Order's yellow against Mind's blue) */
  .chip.on .cnt{ color:inherit; opacity:.72; }
  .filters .reset{ width:calc(100% - 28px); margin:0 14px 14px; }

  .content{ flex:1; min-width:0; }
  .toolbar{ display:flex; gap:10px; align-items:center; margin-bottom:14px; flex-wrap:wrap; }
  .toolbar .colsearch{ width:220px; }
  .toolbar .field{ flex:0 1 auto; }
  .toolbar .msg,.toolbar .shown{ color:var(--dim); font-size:12.5px; }
  /* Used by the collection toolbar AND the deck bar, so it must not be scoped
     to .toolbar — the deck bar's Group/Sort labels rendered as plain body text
     while the collection's wore the small-caps treatment. */
  label.sortlbl{ color:var(--dim-2); font-size:11px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; }
  .toolbar select{ height:var(--ctrl-h); }
  .toolbar .sep{ width:1px; height:20px; background:var(--line); }
  .toolbar .toggle{ margin-left:auto; }

  .toggle{ display:flex; gap:2px; padding:3px; background:var(--inset); border:1px solid var(--line); border-radius:var(--r-sm); }
  .toggle button{ min-height:28px; padding:0 11px; border-radius:6px; background:transparent; border:0;
                  box-shadow:none; color:var(--dim); font-size:12.5px; }
  .toggle button:hover{ color:var(--txt); background:var(--panel-2); filter:none; }
  .toggle button.on{ background:var(--panel-2); color:var(--accent-lit); box-shadow:var(--edge); }
  .toggle button .ic{ width:14px; height:14px; vertical-align:0; }

  /* ---- table -------------------------------------------------------------- */
  .table-wrap{ overflow-x:auto; -webkit-overflow-scrolling:touch; border:1px solid var(--line);
               border-radius:var(--r-lg); background:var(--panel); box-shadow:var(--edge),var(--sh-1); }
  table{ width:100%; border-collapse:collapse; }
  th,td{ padding:10px 14px; text-align:left; font-variant-numeric:tabular-nums; }
  thead th{ background:var(--inset); color:var(--dim-2); font-size:10.5px; font-weight:700;
            letter-spacing:.11em; text-transform:uppercase; border-bottom:1px solid var(--line);
            cursor:pointer; white-space:nowrap; }
  thead th:hover{ color:var(--txt); }
  tbody tr{ border-bottom:1px solid color-mix(in srgb,var(--line) 60%,transparent); }
  tbody tr:last-child{ border-bottom:0; }
  tbody td{ position:relative; transition:background var(--t); }
  tbody tr:hover td{ background:rgba(255,255,255,.022); }
  tbody tr:hover td:first-child::before{ content:""; position:absolute; left:0; top:0; bottom:0; width:2px; background:var(--accent); }
  td.num,th.num{ text-align:right; }
  td .qty{ width:58px; text-align:right; padding:4px 8px; min-height:28px; font-size:13px; }
  .del{ width:28px; height:28px; min-height:0; padding:0; border-radius:var(--r-xs);
        background:transparent; color:var(--dim-2); border:1px solid transparent; box-shadow:none; }
  .del:hover{ color:var(--bad); background:var(--bad-wash); border-color:color-mix(in srgb,var(--bad) 32%,transparent); filter:none; }
  .thumb{ width:36px; height:50px; object-fit:cover; border-radius:5px; background:var(--inset);
          border:1px solid var(--line); display:block; cursor:zoom-in; }
  .thumb.ph{ display:flex; align-items:center; justify-content:center; color:var(--dim-2); }
  /* An auto-layout table re-measures its columns on every render, so filtering
     or a price refresh slid the numbers sideways. Pin the columns whose content
     is a known size and let name/set absorb what's left; widths are floors, so
     an unusually long value still gets the room it needs. */
  @media (min-width:701px){
    .c-art{ width:64px; }
    .c-cost{ width:74px; }
    .c-cond{ width:96px; }
    .c-qty{ width:96px; }
    .c-unit{ width:104px; }
    .c-val{ width:112px; }
    .c-del{ width:60px; }
  }

  /* ---- tables as cards on a phone ----------------------------------------
     A nine-column table can't be read at 390px, and side-scrolling a list you
     have to scan is worse than useless. Below this width each row becomes a
     self-contained card: art, name, the folded-away columns as one meta line
     (data-meta on the name cell), then the line value and the qty stepper.
     Cells are placed by their c-* role because the two tables order their
     columns differently. */
  @media (max-width:700px){
    .table-wrap{ border:0; background:none; box-shadow:none; overflow-x:visible; }
    .table-wrap table,.table-wrap tbody{ display:block; min-width:0; width:100%; }
    .table-wrap thead{ display:none; }        /* sorting stays on the toolbar */
    .table-wrap tbody tr{
      display:grid; grid-template-columns:52px minmax(0,1fr) auto;
      grid-template-areas:"art name del" "art val qty";
      gap:6px 12px; align-items:center; margin-bottom:10px; padding:11px;
      border:1px solid var(--line); border-bottom:1px solid var(--line);
      border-radius:var(--r-lg); background:var(--surface); box-shadow:var(--edge),var(--sh-1);
    }
    .table-wrap tbody td{ display:block; padding:0; border:0; background:none; text-align:left; }
    .table-wrap tbody tr:hover td{ background:none; }
    .table-wrap tbody tr:hover td:first-child::before{ display:none; }
    /* all scoped to .table-wrap tbody so they outrank the td reset above */
    .table-wrap tbody .c-art{ grid-area:art; }
    .table-wrap tbody .c-art .thumb{ width:52px; height:73px; }
    .table-wrap tbody .c-name{ grid-area:name; min-width:0; font-weight:600; line-height:1.35; }
    /* the columns we dropped, restated in one quiet line */
    .table-wrap tbody .c-name::after{ content:attr(data-meta); display:block; margin-top:3px;
                                      color:var(--dim); font-size:11.5px; font-weight:400; }
    .table-wrap tbody .c-val{ grid-area:val; font-size:15px; font-weight:700; color:var(--accent-lit); }
    .table-wrap tbody .c-qty{ grid-area:qty; }
    .table-wrap tbody .c-qty .qty{ width:64px; }
    .table-wrap tbody .c-del{ grid-area:del; align-self:start; }
    .table-wrap tbody .c-set,.table-wrap tbody .c-cost,
    .table-wrap tbody .c-cond,.table-wrap tbody .c-unit{ display:none; }
    /* the wishlist's "who owns this" drawer spans the whole card */
    .table-wrap tbody tr.own-row{ display:block; margin-top:-6px; }
    .table-wrap tbody .c-full{ grid-column:1/-1; }
  }

  /* ---- gallery ------------------------------------------------------------ */
  .gallery{ display:grid; grid-template-columns:repeat(auto-fill,minmax(228px,1fr)); gap:16px; }
  .gcard{ position:relative; display:flex; flex-direction:column; border-radius:var(--r-lg); overflow:hidden;
          background:var(--surface); border:1px solid var(--line); box-shadow:var(--edge),var(--sh-1);
          transition:border-color var(--t),transform var(--t),box-shadow var(--t); }
  .gcard:hover{ border-color:var(--line-2); transform:translateY(-3px); box-shadow:var(--edge),var(--sh-2); }
  .gcard .art{ width:100%; aspect-ratio:5/7; object-fit:cover; background:var(--inset); cursor:zoom-in;
               display:flex; align-items:center; justify-content:center; color:var(--dim-2); }
  .gcard .art .ic{ width:28px; height:28px; }
  /* Battlefields are landscape (7:5) art. Rotate 90° so they stand vertically in
     the portrait tile — the rotated shape (5:7) matches the tile exactly. */
  .gcard.land .art{ width:140%; aspect-ratio:7/5; margin:20% 0 20% -20%;
                    transform:rotate(90deg); transform-origin:center; }
  .gcard .meta{ padding:11px 12px 12px; display:flex; flex-direction:column; flex:1; }
  .gcard .gname{ font-weight:640; font-size:13px; line-height:1.32; min-height:2.64em;
                 display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
  .gcard .gsub{ color:var(--dim); font-size:11px; margin-top:3px;
                white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .gcard .grow{ display:flex; justify-content:space-between; align-items:center; margin-top:auto;
                padding-top:11px; gap:8px; }
  .gcard .gprice{ color:var(--accent-lit); font-weight:700; font-size:13.5px; font-variant-numeric:tabular-nums; }
  /* "No offers" stands where a price would: same slot, but read as an absence
     rather than as a number — dimmed, unemphasised, and never accent-coloured,
     so a wall of unsold product doesn't look like a wall of value. */
  .noprice{ color:var(--dim); font-weight:500; font-size:12px; letter-spacing:.01em;
            font-variant-numeric:normal; white-space:nowrap; }
  .gcard .gqty,.gcard .grow .n{ background:var(--inset); color:var(--dim); border:1px solid var(--line);
                                border-radius:var(--pill); font-size:11.5px; padding:2px 9px; font-variant-numeric:tabular-nums; }
  .gcard .gdel{ position:absolute; top:8px; right:8px; width:26px; height:26px; min-height:0; padding:0;
                background:rgba(8,11,15,.78); color:#fff; border:1px solid rgba(255,255,255,.16);
                border-radius:var(--r-xs); box-shadow:none; opacity:0; transition:opacity var(--t),background var(--t); }
  .gcard:hover .gdel{ opacity:1; }
  .gcard .gdel:hover{ background:var(--bad); border-color:var(--bad); filter:none; }
  .gcard .cdots{ position:absolute; top:9px; left:9px; display:flex; gap:3px; }
  .gcard .cdots .dot{ margin:0; width:10px; height:10px; box-shadow:0 0 0 1.5px rgba(0,0,0,.6); }
  .gcard .foilbadge{ position:absolute; top:9px; left:9px; display:inline-flex; align-items:center; gap:4px;
                     transform:translateY(21px); background:rgba(8,11,15,.78); color:var(--accent-lit);
                     border-radius:var(--pill); font-size:10px; font-weight:700; padding:2px 8px; letter-spacing:.04em; }
  .foilbadge .ic{ width:11px; height:11px; vertical-align:0; }

  /* ---- sealed product ------------------------------------------------------
     Boxes reuse the card tile, with the one thing that can't carry over: art.
     A card is 5:7 and can be cropped to fill; a booster box is a product photo
     of whatever shape the photographer chose, so it is CONTAINED rather than
     cover-cropped — cropping a box shot cuts the set name off the lid, which is
     the only way to tell two of them apart at tile size.

     The photo arrives with its white studio background already knocked out
     (makeSealedThumb), so what lands here is the box on transparency. It gets a
     faint pool of light to stand in and a shadow to stand on, which is what
     stops a product shot reading as a sticker on a dark panel.

     Square, because the source shots are square and the products are not all the
     same way up: a Pre-Rift Kit is portrait, a Vault is landscape, and a 4:3
     frame would shrink every tall box to fit a width it never needed. */
  .sgallery .gcard .art{ aspect-ratio:1/1; padding:16px; cursor:zoom-in;
                         background:radial-gradient(78% 62% at 50% 40%,
                           color-mix(in srgb,var(--line) 62%,transparent), transparent 72%), var(--inset); }
  .sgallery .gcard .art img{ width:100%; height:100%; object-fit:contain;
                             filter:drop-shadow(0 10px 13px rgba(0,0,0,.55)) drop-shadow(0 2px 3px rgba(0,0,0,.45)); }
  .sgallery .gcard .gname{ min-height:2.64em; }
  /* Contain, not cover: the row thumbnail keeps the singles column width so the
     two tables line up, and a box scaled into it must not be cropped to a strip. */
  .thumb.sthumb{ object-fit:contain; padding:2px; }
  .langbadge{ display:inline-flex; align-items:center; gap:4px; background:var(--inset); color:var(--dim);
              border:1px solid var(--line); border-radius:var(--pill); font-size:10.5px; font-weight:700;
              padding:2px 8px; letter-spacing:.03em; }
  .gcard .langbadge{ position:absolute; top:9px; left:9px; background:rgba(8,11,15,.78); }
  .catbadge{ background:var(--inset); color:var(--dim); border:1px solid var(--line);
             border-radius:var(--pill); font-size:10.5px; padding:2px 8px; white-space:nowrap; }

  /* ---- held but not counted ------------------------------------------------
     A box you own and don't want in the figures: one being kept for a friend,
     one already sold and waiting to ship, one bought to open rather than to
     hold. It stays in the list, priced, filtered and sorted like everything
     else -- only the totals look away.

     Which is exactly why the tile has to SAY so, three ways at once: faded art,
     a struck-through price and a badge. A holding that quietly subtracted
     itself from a number somewhere else on the screen would read as a bug in
     the totals, and the whole point of the switch is to be trusted. */
  .gcard .gex{ position:absolute; top:8px; right:38px; width:26px; height:26px; min-height:0; padding:0;
               background:rgba(8,11,15,.78); color:#fff; border:1px solid rgba(255,255,255,.16);
               border-radius:var(--r-xs); box-shadow:none; opacity:0;
               transition:opacity var(--t),background var(--t),color var(--t); }
  .gcard:hover .gex{ opacity:1; }
  .gcard .gex:hover{ background:var(--panel-2); border-color:var(--line-2); filter:none; }
  /* Once a tile IS excluded its button stops hiding: a state you can only reach
     by hovering must not also be a state you can only LEAVE by hovering. */
  .gcard.exed .gex{ opacity:1; color:var(--accent-lit); }
  .gcard.exed .art img{ opacity:.42; filter:grayscale(.75) drop-shadow(0 6px 9px rgba(0,0,0,.45)); }
  .gcard.exed .gname,.gcard.exed .gsub{ color:var(--dim-2); }
  .gcard.exed .gprice{ color:var(--dim); text-decoration:line-through; text-decoration-thickness:1px; }
  /* Inside the art rather than over the tile: the art is the one box on the
     card whose size is known, so the badge can sit on its floor at every
     column width without landing on the name underneath. */
  .gcard .art{ position:relative; }
  .exbadge{ display:inline-flex; align-items:center; gap:4px; background:rgba(8,11,15,.82);
            color:var(--dim); border:1px solid var(--line-2); border-radius:var(--pill);
            font-size:10px; font-weight:700; padding:2px 8px; letter-spacing:.03em; white-space:nowrap; }
  .exbadge .ic{ width:11px; height:11px; vertical-align:0; }
  .gcard .exbadge{ position:absolute; left:8px; bottom:8px; z-index:1; }
  /* The table says it in the same three ways, minus the art it doesn't have. */
  #sealedRows tr.exed .c-name,#sealedRows tr.exed .c-set,#sealedRows tr.exed .c-cond{ color:var(--dim-2); }
  #sealedRows tr.exed .thumb{ opacity:.42; filter:grayscale(.75); }
  #sealedRows tr.exed .c-val{ color:var(--dim); text-decoration:line-through; text-decoration-thickness:1px; }
  /* Shares .del's shape -- same 28px square in the same cell -- but not its
     colour: removing a box is destructive and reads red, parking one is not. */
  #sealedRows .exbtn:hover{ color:var(--accent-lit); background:var(--accent-wash);
                            border-color:color-mix(in srgb,var(--accent) 32%,transparent); }
  #sealedRows .exbtn.on{ color:var(--accent-lit); border-color:var(--line-2); }
  #sealedRows .c-del{ white-space:nowrap; }
  /* The add dropdown gains one control the card one has no use for: which of the
     four printings you are holding. It sits in the row because the language IS
     the price — picking it afterwards would mean adding the wrong thing first. */
  .rrow .rlang{ min-height:26px; padding:0 6px; font-size:11.5px; max-width:96px; }
  /* A card thumbnail is cropped to fill because every card is 5:7; a box photo
     cropped to that shape is an unreadable sliver of cardboard. The plate goes
     too — the art arrives on transparency, so it sits in the row unframed. */
  #sealedResults .rart{ width:40px; height:40px; object-fit:contain; background:none; border-radius:0; }
  .rrow .rlang:disabled{ opacity:.5; }

  /* ---- pointer-tracked card art -------------------------------------------
     .tilt wraps the art of EVERY card and is what the pointer handler hunts
     for; it carries the four custom properties that drive both the tilt and
     the foil effect, so a move is one style write and the rest stays on the
     compositor — no layout, no re-paint of the art itself.

     .holo is a modifier on top of it, added only to foils: same tracking, plus
     the rainbow layer and glare below. A plain card therefore moves exactly
     like a foil does but is never painted over. Foils also sit at a low
     resting sheen so they read as foil before you touch them. */
  .tilt{ --mx:50%; --my:50%; --rx:0deg; --ry:0deg;
         position:relative; display:block; isolation:isolate; }
  /* There is exactly ONE .holo-fx element on the page; the pointer handler
     parks it inside whichever card the cursor is over. A blended layer still
     composites at opacity:0, so leaving one per card cost ~2.3x the scroll
     paint on a 376-card gallery — this way a resting gallery paints nothing
     extra at all, and the live effect is a single element no matter how many
     foils you own. */
  .holo-fx{ position:absolute; inset:0; z-index:2; pointer-events:none;
            border-radius:inherit; overflow:hidden; animation:holoIn .25s var(--ease); }
  @keyframes holoIn{ from{ opacity:0 } to{ opacity:1 } }
  /* the bands. colour-dodge lifts what's already bright in the art instead of
     painting over it, which is what makes it read as foil rather than a filter */
  /* Overlay, not colour-dodge: dodge blows out bright art and disappears on
     dark art, whereas overlay keeps the illustration's own structure legible
     while still tinting it. */
  /* The pattern is 3x the box and shifted by 0-100%, so it always covers and
     never has to tile. Anything past 100% makes the background repeat, and the
     seam between tiles shows up as hard lines across the card — which is what
     happened when the cursor reached the bottom or right edge. */
  .holo-fx::before{
    content:""; position:absolute; inset:0; mix-blend-mode:overlay; opacity:.40;
    background:repeating-linear-gradient(-22deg,
      rgba(255,64,124,.85) 0%, rgba(255,192,64,.85) 6%, rgba(88,255,182,.85) 12%,
      rgba(72,168,255,.85) 18%, rgba(188,104,255,.85) 24%, rgba(255,64,124,.85) 30%);
    background-size:300% 300%; background-repeat:no-repeat;
    background-position:var(--mx) var(--my);
    filter:saturate(1.5);
  }
  /* Two lights on screen blend: a pool under the cursor and a hard sheen streak
     raked across the card, which is the part that actually reads as "foil".
     inset:0 matters — --mx/--my are measured against .tilt, so an inset box
     would put the glare somewhere the cursor isn't. */
  .holo-fx::after{
    content:""; position:absolute; inset:0; mix-blend-mode:screen; opacity:.55;
    background:
      radial-gradient(26% 26% at var(--mx) var(--my),
        rgba(255,255,255,.7), rgba(255,255,255,.1) 45%, transparent 70%),
      linear-gradient(calc(var(--ry) * 3 + 105deg),
        transparent 38%, rgba(255,255,255,.22) 49%, transparent 61%);
  }
  /* The tilt goes on .tilt's PARENT, not on .tilt — the parent is the element
     that draws the card's border and shadow, so tilting the inner art alone
     left that frame hanging in the original position. See .holo-tilt below,
     which lives late in the sheet so it outranks the hover lifts it replaces. */
  /* A cheap static sheen — one gradient, no pointer tracking — so foils read as
     foil at rest on the surfaces that only ever show a handful of them. The
     dense gallery is deliberately excluded; its Foil badge does that job. */
  #detail .dart .holo::after,.cf .holo::after,.hcard .fr .holo::after{
    content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
    border-radius:inherit; mix-blend-mode:overlay; opacity:.45;
    transition:opacity .25s var(--ease);
    background:linear-gradient(112deg, transparent 32%, rgba(255,132,214,.55) 43%,
               rgba(255,226,120,.5) 50%, rgba(126,214,255,.55) 57%, transparent 68%);
  }
  .holo.lit::after{ opacity:0; }   /* hand off to the live effect */

  /* per-surface fit */
  .gcard .tilt{ border-radius:0; }
  .gcard.land .holo-fx{ inset:20% 0; }
  #detail .dart .tilt{ border-radius:var(--r-md); }
  /* these two size their art with height:100%, which needs a definite height to
     resolve against — the wrapper has to pass the card's own height through */
  .cf .tilt,.hcard .fr .tilt{ height:100%; }
  .cf .holo-fx,.hcard .fr .holo-fx{ border-radius:inherit; }
  /* inline marker on a table row — the badge would crowd a text line */
  .foilmark{ display:inline-flex; color:var(--accent-lit); margin-left:2px; }
  .foilmark .ic{ width:12px; height:12px; vertical-align:-1px; }
  .foil-toggle{ display:inline-flex; align-items:center; gap:8px; cursor:pointer; user-select:none; }
  .foil-toggle span{ display:inline-flex; align-items:center; gap:5px; }
  .foil-toggle input:checked ~ span{ color:var(--accent-lit); }
  .foil-toggle .ic{ width:13px; height:13px; vertical-align:0; color:var(--accent); }
  .finish{ display:inline-flex; align-items:center; gap:5px; }
  .finish.is-foil{ color:var(--accent-lit); font-weight:600; }
  .finish .ic{ width:13px; height:13px; vertical-align:0; }
  /* why we know — keeps the claim honest without a tooltip */
  .why{ display:block; color:var(--dim-2); font-size:11.5px; font-weight:400; margin-top:2px; }
  .stepper{ display:inline-flex; align-items:center; border:1px solid var(--line); border-radius:var(--pill);
            overflow:hidden; background:var(--inset); }
  .stepper button{ background:transparent; color:var(--dim); border:0; box-shadow:none;
                   width:26px; height:24px; min-height:0; padding:0; border-radius:0; }
  .stepper button:hover{ background:var(--panel-2); color:var(--accent-lit); filter:none; }
  .stepper button .ic{ width:13px; height:13px; vertical-align:0; }
  .stepper .n{ min-width:26px; text-align:center; font-size:12px; font-variant-numeric:tabular-nums; color:var(--txt); }

  /* =========================================================================
     Wishlist extras
     ========================================================================= */
  .own-btn{ margin-left:8px; padding:1px 9px; min-height:22px; font-size:11.5px; border-radius:var(--pill);
            border:1px solid var(--line); background:transparent; color:var(--dim); font-weight:600; box-shadow:none; }
  .own-btn:hover{ color:var(--txt); border-color:var(--line-2); background:var(--panel-2); filter:none; }
  .own-btn.on{ color:var(--accent-lit); border-color:color-mix(in srgb,var(--accent) 45%,var(--line)); background:var(--accent-wash); }
  .own-btn .ic{ width:12px; height:12px; vertical-align:-2px; }
  .own-row td{ background:color-mix(in srgb,var(--accent) 5%,transparent); padding-top:4px !important; padding-bottom:10px !important; }
  .own-list{ display:flex; flex-wrap:wrap; gap:6px; }
  .own-chip{ display:inline-flex; align-items:center; gap:7px; padding:3px 10px 3px 3px; border-radius:var(--pill);
             border:1px solid var(--line); background:var(--inset); font-size:12px; }
  .own-chip .fr-av{ width:22px; height:22px; font-size:11px; }
  .own-chip .own-name{ font-weight:600; }
  .own-chip .own-q{ color:var(--dim); font-variant-numeric:tabular-nums; }

  /* =========================================================================
     Decks
     ========================================================================= */
  .deck-add{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg);
             padding:18px 20px; margin-bottom:6px; box-shadow:var(--edge),var(--sh-1); }
  .deck-add code{ background:var(--inset); border:1px solid var(--line); padding:1px 5px; border-radius:4px; font-size:11.5px; }
  .deck-add h3{ font-size:14px; }
  .deck-add .desc{ color:var(--dim); font-size:12.5px; margin-top:4px; }
  .deck-add textarea{ width:100%; min-height:120px; margin-top:14px; resize:vertical;
              font:12.5px/1.65 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
  .deck-add .row{ display:flex; gap:10px; align-items:center; margin-top:12px; flex-wrap:wrap; }
  .deck-add .row input{ flex:1; min-width:200px; }
  .deck-add .hint{ color:var(--dim); font-size:12.5px; margin-top:10px; }
  .deck-add .hint.err{ color:var(--bad); }
  .deck-add-top{ display:flex; align-items:flex-start; justify-content:space-between; gap:12px; }

  /* ---- deck list: stat strip + tools bar ---------------------------------
     Two banded rows, not one wrapping line. The bar used to put the rollup and
     all five controls in a single flex row: past about 1100px of content it
     wrapped twice, and because .deck-tools kept `margin-left:auto` the wrapped
     lines landed right-aligned and ragged against a left-aligned stat block.
     Now the summary is its own band and the controls are their own, split into
     two groups that each stay whole: "what to show" (search + status chips) and
     "how to arrange it" (group + sort + fold). They sit on one line when there
     is room and break to two clean left-aligned lines when there isn't -- the
     groups are the only things allowed to wrap, so a chip can never end up
     stranded on a line of its own. */
  .deck-bar{ margin:0 0 18px; border-radius:var(--r-lg); overflow:hidden;
             border:1px solid var(--line); background:var(--surface); box-shadow:var(--edge),var(--sh-1); }
  .dstats{ display:flex; align-items:center; gap:26px; flex-wrap:wrap;
           padding:12px 15px; border-bottom:1px solid var(--line); background:var(--inset); }
  .dstat{ display:flex; flex-direction:column; gap:1px; line-height:1.2; }
  .dstat b{ font-size:17px; font-weight:700; font-variant-numeric:tabular-nums; color:var(--txt); }
  .dstat span{ font-size:10px; font-weight:600; letter-spacing:.06em; text-transform:uppercase; color:var(--dim-2); }
  .dstat.good b{ color:var(--good); }
  .dstat.info b{ color:var(--info); }
  /* space-between parts the two groups on a shared line and leaves each of them
     flush left once they wrap onto lines of their own */
  .deck-tools{ display:flex; align-items:center; justify-content:space-between;
               flex-wrap:wrap; gap:10px 22px; padding:11px 15px; }
  .dt-group{ display:flex; align-items:center; gap:10px; flex:0 1 auto; min-width:0; }
  .deck-tools .field{ flex:0 1 auto; }
  .deck-tools .colsearch{ width:230px; }
  .deck-tools .chips{ flex-wrap:nowrap; }
  .dsel{ display:flex; align-items:center; gap:6px; }
  .dsel select{ height:var(--ctrl-h); }
  /* a hair of air before the fold toggle so it reads as an action, not a third
     dropdown in the sort cluster */
  .fold-all{ white-space:nowrap; margin-left:4px; }

  /* ---- legend / domain groups ------------------------------------------- */
  .deck-group{ margin:0 0 22px; }
  /* Sticky so the archetype you are scrolling through stays named. It sits
     below the mobile top bar, which is why the offset is a variable. */
  .dg-head{ position:sticky; top:var(--dg-top,0px); z-index:12;
            display:flex; align-items:center; gap:11px; width:100%; margin-bottom:12px;
            padding:8px 12px 8px 8px; border-radius:var(--r-md);
            border:1px solid var(--line); background:color-mix(in srgb,var(--rail) 92%,transparent);
            backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
            box-shadow:none; color:var(--txt); text-align:left; cursor:pointer; min-height:0; }
  .dg-head:hover{ border-color:var(--line-2); background:color-mix(in srgb,var(--rail) 96%,transparent); filter:none; }
  .glart{ width:30px; flex:none; aspect-ratio:5/7; object-fit:cover; border-radius:var(--r-xs);
          border:1px solid var(--line); background:var(--inset); }
  .glart.ph{ display:flex; align-items:center; justify-content:center; color:var(--dim-2); }
  .glart.ph .ic{ width:15px; height:15px; }
  .dg-tx{ display:flex; flex-direction:column; gap:2px; min-width:0; flex:1; }
  .dg-tx b{ font-size:13.5px; font-weight:650; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .dg-sub{ display:flex; align-items:center; gap:5px; color:var(--dim); font-size:11px; }
  .dg-sub .rune{ width:13px; height:13px; }
  .dg-chev{ flex:none; display:flex; color:var(--dim-2); transition:transform var(--t); }
  .dg-chev .ic{ width:17px; height:17px; }
  .deck-group.folded .dg-chev{ transform:rotate(-90deg); }
  .deck-group.folded .deck-grid{ display:none; }
  .deck-group.folded .dg-head{ margin-bottom:0; }

  .deck-none{ display:flex; flex-direction:column; align-items:center; gap:8px;
              padding:44px 20px; border-radius:var(--r-lg); border:1px dashed var(--line-2);
              color:var(--dim); font-size:13px; text-align:center; }
  .deck-none .ic{ width:26px; height:26px; color:var(--dim-2); }
  .deck-none b{ color:var(--txt); font-size:14.5px; font-weight:650; }
  .deck-none button{ margin-top:6px; }

  .deck-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(262px,1fr)); gap:14px; }
  .deck-tile{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); padding:15px;
              cursor:pointer; position:relative; box-shadow:var(--edge),var(--sh-1);
              transition:border-color var(--t),transform var(--t),box-shadow var(--t),background var(--t); }
  .deck-tile:hover{ border-color:var(--line-2); transform:translateY(-3px); box-shadow:var(--edge),var(--sh-2); }
  .deck-tile .dhead{ display:flex; gap:13px; align-items:center; margin-bottom:14px; }
  .deck-tile .dhead .info{ min-width:0; flex:1; }
  .deck-tile .lart{ width:44px; flex:none; aspect-ratio:5/7; object-fit:cover; border-radius:var(--r-xs);
                    border:1px solid var(--line); background:var(--inset); }
  .deck-tile .lart.ph{ display:flex; align-items:center; justify-content:center; color:var(--dim-2); }
  /* padding clears the corner delete button (26px, less the tile's own 15px
     padding) so a long name never slides under it */
  .deck-tile .dn{ font-weight:650; font-size:14.5px; margin-bottom:3px; padding-right:26px;
                  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .deck-tile .dmeta{ color:var(--dim); font-size:11.5px; }
  /* ---- tile corner actions ------------------------------------------------
     Delete is the only thing in the corner now, and it stays out of the way
     until you hover the tile. */
  .deck-tile .dacts{ position:absolute; top:10px; right:10px; z-index:1; display:flex; gap:2px; }
  .deck-tile .ddel{ width:26px; height:26px; min-height:0; padding:0;
                    background:transparent; color:var(--dim-2); border:0; box-shadow:none; border-radius:var(--r-xs);
                    opacity:0; transition:opacity var(--t),color var(--t),background var(--t); }
  .deck-tile:hover .ddel{ opacity:1; }
  .deck-tile .ddel .ic{ width:15px; height:15px; vertical-align:0; }
  .deck-tile .ddel:hover{ color:var(--bad); background:var(--bad-wash); filter:none; }
  /* ---- active toggle ------------------------------------------------------
     It lives in its own footer row rather than the corner cluster: it is a
     state you set deliberately, not a destructive action, and sitting next to
     Delete both hid it (hover-only, so invisible on touch) and put it one slip
     away from the wrong button. Labelled and always visible, so the tile reads
     its own state even before the wash registers. */
  /* The tile's link is laid over the whole card rather than wrapped around it:
     the delete and Set-active buttons are interactive content, which may not sit
     inside an anchor. They keep their own layer above it. */
  .deck-tile .tlink{ position:absolute; inset:0; z-index:0; border-radius:inherit; text-decoration:none; }
  .deck-tile .dfoot{ display:flex; margin-top:11px; padding-top:10px; border-top:1px solid var(--line);
                     position:relative; z-index:1; }
  .deck-tile .dheld + .dfoot{ margin-top:9px; padding-top:0; border-top:0; }
  .deck-tile .dact{ display:inline-flex; align-items:center; gap:5px; min-height:0; padding:4px 10px;
                    border-radius:999px; font-size:11px; font-weight:650; letter-spacing:.02em;
                    color:var(--dim-2); background:transparent; border:1px solid var(--line); box-shadow:none;
                    transition:color var(--t),background var(--t),border-color var(--t); }
  .deck-tile .dact .ic{ width:13px; height:13px; vertical-align:0; }
  .deck-tile .dact:hover{ color:var(--good); background:var(--good-wash);
                          border-color:color-mix(in srgb,var(--good) 42%,var(--line)); filter:none; }
  .deck-tile .dact.on{ color:var(--good); background:var(--good-wash);
                       border-color:color-mix(in srgb,var(--good) 45%,var(--line)); }
  .deck-tile .pct{ display:flex; justify-content:space-between; margin-top:9px; font-size:11.5px; color:var(--dim); }
  .deck-tile .pct b{ color:var(--txt); font-variant-numeric:tabular-nums; }
  .deck-tile .pct .p{ color:var(--accent-lit); font-weight:700; }
  .deck-tile.complete .pct .p{ color:var(--good); }

  /* Frozen decks read cold (--info) rather than accent-coloured, so "reserved"
     never reads as "selected". */
  .deck-tile.frozen{ border-color:color-mix(in srgb,var(--info) 34%,var(--line)); }
  .deck-tile.frozen:hover{ border-color:color-mix(in srgb,var(--info) 58%,var(--line)); }
  /* Active decks: a bookmark for "these are the ones I'm playing right now".
     It changes no numbers — freezing is the thing that moves cards around — so
     it only paints: the whole tile goes green, wash and edge together. A deck
     can be both frozen and active, and then green wins the border — the Frozen
     chip on the name still says so in words, so nothing is lost. Any number of
     decks can be lit. */
  .deck-tile.active{ background:color-mix(in srgb,var(--good) 9%,var(--surface));
                     border-color:color-mix(in srgb,var(--good) 40%,var(--line)); }
  .deck-tile.active:hover{ border-color:color-mix(in srgb,var(--good) 62%,var(--line)); }

  /* the chip has to survive the name's ellipsis, so the row becomes a flex
     container and only the name itself truncates */
  .deck-tile .dn{ display:flex; align-items:center; gap:7px; }
  .deck-tile .dn .nm{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .frz-chip{ flex:none; display:inline-flex; align-items:center; gap:3px; padding:2px 7px 2px 5px;
             border-radius:999px; font-size:9.5px; font-weight:700; letter-spacing:.04em; text-transform:uppercase;
             color:var(--info); background:color-mix(in srgb,var(--info) 15%,transparent);
             border:1px solid color-mix(in srgb,var(--info) 30%,transparent); }
  .frz-chip .ic{ width:11px; height:11px; vertical-align:0; }
  .deck-tile .dheld{ display:flex; align-items:center; gap:6px; margin-top:10px; padding-top:9px;
                     border-top:1px solid var(--line); color:var(--dim); font-size:11px; line-height:1.35; }
  .deck-tile .dheld .ic{ width:13px; height:13px; flex:none; color:var(--info); vertical-align:0; }

  .frz-btn.on{ color:var(--info); border-color:color-mix(in srgb,var(--info) 42%,transparent);
               background:color-mix(in srgb,var(--info) 14%,transparent); }
  .frz-btn.on:hover{ background:color-mix(in srgb,var(--info) 21%,transparent); filter:none; }

  .frz-note{ display:flex; align-items:flex-start; gap:9px; margin:0 0 20px; padding:10px 13px;
             border-radius:var(--r-md); border:1px solid var(--line); background:var(--panel);
             color:var(--dim); font-size:12.5px; line-height:1.5; }
  .frz-note b{ color:var(--txt); font-weight:650; }
  .frz-note .ic{ width:16px; height:16px; flex:none; margin-top:1px; color:var(--info); vertical-align:0; }
  .frz-note.on{ border-color:color-mix(in srgb,var(--info) 30%,transparent);
                background:color-mix(in srgb,var(--info) 10%,transparent); }

  /* Freeze is the one control here whose effect isn't obvious from its label,
     so it carries a bubble: on hover for a pointer, pinned by the ? for touch. */
  .frz-tip{ position:relative; display:inline-flex; align-items:center; gap:4px; }
  .frz-q{ width:26px; height:26px; color:var(--dim-2); }
  .frz-q .ic{ width:15px; height:15px; }
  .frz-tipbox{
    position:absolute; top:calc(100% + 8px); right:0; z-index:50; width:max(260px,min(320px,78vw));
    padding:11px 13px; border-radius:var(--r-md); border:1px solid var(--line-2);
    background:var(--surface); box-shadow:var(--edge),var(--sh-2);
    color:var(--dim); font-size:12.5px; font-weight:400; line-height:1.55; text-align:left;
    white-space:normal; opacity:0; visibility:hidden; transform:translateY(-4px);
    transition:opacity var(--t),transform var(--t),visibility var(--t);
  }
  .frz-tipbox b{ display:block; margin-bottom:4px; color:var(--txt); font-weight:650; }
  .frz-tip:hover .frz-tipbox,.frz-tip.open .frz-tipbox{ opacity:1; visibility:visible; transform:none; }
  /* :not(.open) so a sticky post-tap :hover can't fight the pinned state */
  @media (hover:none){ .frz-tip:not(.open):hover .frz-tipbox{ opacity:0; visibility:hidden; } }

  .deck-build .build-total{ color:var(--dim); font-size:13px; font-variant-numeric:tabular-nums; }
  .deck-build .build-total b{ color:var(--txt); }
  .deck-build .namerow{ margin-bottom:20px; max-width:420px; }
  .deck-build .namerow input{ width:100%; }
  .build-sec{ margin-bottom:14px; border:1px solid var(--line); border-radius:var(--r-lg);
              padding:15px 17px 17px; background:var(--surface); box-shadow:var(--edge); }
  .build-sec .bh{ display:flex; align-items:center; gap:10px; margin-bottom:12px; }
  .build-sec .bh h3{ font-size:11px; text-transform:uppercase; letter-spacing:.12em; color:var(--dim); }
  .build-sec .bn{ font-weight:700; font-size:13px; color:var(--txt); font-variant-numeric:tabular-nums; }
  .build-sec .bn.full{ color:var(--good); }
  .build-sec .btarget{ color:var(--dim-2); font-size:12px; font-variant-numeric:tabular-nums; }
  .build-sec .bprog{ display:block; flex:1; max-width:150px; margin-left:auto; }
  .bsearch{ position:relative; }
  .bsearch input{ width:100%; }
  .bresults{ position:absolute; z-index:20; left:0; right:0; top:calc(100% + 6px); background:var(--panel);
             border:1px solid var(--line-2); border-radius:var(--r-md); max-height:420px; overflow:auto; scrollbar-gutter:stable; display:none;
             box-shadow:var(--sh-2); padding:14px; grid-template-columns:repeat(auto-fill,minmax(140px,1fr)); gap:12px; }
  .bresults.open{ display:grid; }
  .bres{ width:100%; aspect-ratio:5/7; object-fit:cover; border-radius:var(--r-sm); cursor:pointer;
         border:1px solid var(--line); background:var(--inset); transition:border-color var(--t),transform var(--t); }
  .bres:hover{ border-color:var(--accent); transform:translateY(-2px); }
  .bres.ph{ display:flex; align-items:center; justify-content:center; color:var(--dim-2); }
  .bres.empty{ grid-column:1/-1; aspect-ratio:auto; color:var(--dim); cursor:default; padding:10px;
               background:none; border:0; font-size:13px; }
  .bthumb{ width:30px; flex:none; aspect-ratio:5/7; object-fit:cover; border-radius:4px;
           border:1px solid var(--line); background:var(--inset); }
  .bthumb.ph{ display:flex; align-items:center; justify-content:center; color:var(--dim-2); }
  .bchosen{ display:grid; grid-template-columns:repeat(auto-fill,minmax(126px,1fr)); gap:12px; margin-top:14px; }
  .bchosen:empty{ display:none; }
  .bcard{ position:relative; border-radius:var(--r-sm); overflow:hidden; border:1px solid var(--line); background:var(--inset); }
  .bcard img{ width:100%; aspect-ratio:5/7; object-fit:cover; display:block; }
  .bcard .ph{ width:100%; aspect-ratio:5/7; display:flex; align-items:center; justify-content:center; color:var(--dim-2); }
  .bqbadge{ position:absolute; left:6px; top:6px; background:rgba(8,11,15,.8); color:#fff;
            border-radius:var(--pill); font-size:11px; font-weight:700; padding:1px 7px; }
  .bctrl{ position:absolute; right:6px; top:6px; display:flex; gap:3px; opacity:0; transition:opacity var(--t); }
  .bcard:hover .bctrl,.bcard:focus-within .bctrl{ opacity:1; }
  .bctrl .bstep{ width:24px; height:24px; min-height:0; padding:0; border-radius:var(--r-xs); box-shadow:none;
                 background:rgba(8,11,15,.8); color:#fff; border:1px solid rgba(255,255,255,.16); }
  .bctrl .bstep:hover{ background:var(--accent); color:var(--accent-ink); filter:none; }
  .bctrl .bstep.del:hover{ background:var(--bad); border-color:var(--bad); color:#fff; }
  .bctrl .bstep .ic{ width:13px; height:13px; vertical-align:0; }

  .deck-detail .dsummary{ display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:12px; margin-bottom:24px; }
  .back{ background:transparent; color:var(--dim); border:1px solid var(--line); box-shadow:none; margin-bottom:16px; }
  .back:hover{ color:var(--txt); background:var(--panel-2); border-color:var(--line-2); filter:none; }
  .deck-section{ margin-bottom:26px; }
  .deck-section h3{ font-size:11px; text-transform:uppercase; letter-spacing:.12em; color:var(--dim);
                    margin:0 0 12px; display:flex; align-items:center; gap:10px; }
  .deck-section h3::after{ content:""; flex:1; height:1px; background:linear-gradient(90deg,var(--line),transparent); }
  .deck-section h3 .sc{ color:var(--txt); font-weight:700; font-variant-numeric:tabular-nums; }
  /* Units / Spells / Gear inside the main deck: the same header as a section but
     a step quieter, so it separates the three groups without competing with it. */
  .deck-sub + .deck-sub{ margin-top:18px; }
  .deck-sub h4{ font-size:10px; text-transform:uppercase; letter-spacing:.1em; color:var(--dim-2);
                font-weight:650; margin:0 0 10px; display:flex; align-items:center; gap:9px; }
  .deck-sub h4::after{ content:""; flex:1; height:1px; background:var(--line); opacity:.55; }
  .deck-sub h4 .sc{ color:var(--dim); font-weight:700; font-variant-numeric:tabular-nums; }
  /* ---- deck stats -------------------------------------------------------- */
  /* One block of markup, two homes: inline on the deck page, and inside the
     phone-only sheet the floating button opens. The media queries near the end
     of this sheet decide which of the two is on screen -- never both.
     Two panels, no more: the shape of the curve, and what the 40 is made of. */
  .dkstats{ display:grid; grid-template-columns:minmax(0,1.3fr) minmax(0,1fr); gap:14px; margin-bottom:24px; }
  .dkbox{ position:relative; display:flex; flex-direction:column; min-width:0;
          background:var(--surface); border:1px solid var(--line); border-radius:var(--r-xl);
          padding:15px 17px 16px; box-shadow:var(--edge),var(--sh-1); }
  /* a brass filament along the top edge -- the same trick the hero band uses to
     stop a dark panel from reading as a plain rectangle */
  .dkbox::before{ content:""; position:absolute; inset:0 0 auto; height:1px;
                  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--accent) 60%,transparent),transparent); }
  .dkhead{ display:flex; align-items:center; gap:10px 12px; flex-wrap:wrap; margin-bottom:15px; }
  .dkhead h4{ display:flex; align-items:center; gap:7px; margin:0; font-size:11px; font-weight:650;
              text-transform:uppercase; letter-spacing:.12em; color:var(--dim); }
  .dkhead h4 .ic{ width:14px; height:14px; color:var(--accent); vertical-align:0; }
  .dkpills{ display:flex; align-items:center; gap:6px; margin-left:auto; flex-wrap:wrap; }
  .dkpill{ display:inline-flex; align-items:baseline; gap:5px; padding:3px 10px; white-space:nowrap;
           border:1px solid var(--line); border-radius:var(--pill); background:var(--inset);
           font-size:11px; color:var(--dim); }
  .dkpill b{ font-size:12.5px; font-weight:700; color:var(--txt); font-variant-numeric:tabular-nums; }
  .dkpill.lit{ background:var(--accent-wash); border-color:color-mix(in srgb,var(--accent) 30%,transparent); }
  .dkpill.lit b{ color:var(--accent-lit); }
  .dknote{ color:var(--dim); font-size:12.5px; margin:0; }
  .dkfoot{ color:var(--dim-2); font-size:11px; margin:13px 0 0; }
  /* A histogram, drawn like one: square columns standing on a common baseline,
     no wells, no gridlines and no y-axis -- every column prints its own count on
     top, which says everything a rung would with none of the ink. The columns
     take the whole width of the panel and cap out only so a four-column deck
     can't stretch four bars into slabs. Colour is the cost bracket, cool for the
     early drops through to ember for the top end, and the legend under the plot
     names each one, so the hue is never the only thing carrying it. */
  .dkplot{ display:flex; align-items:flex-end; justify-content:center; gap:0;
           width:100%; margin-top:auto; }
  .dkcol{ flex:1 1 0; min-width:0; max-width:88px; display:flex; flex-direction:column;
          align-items:center; gap:9px; }
  .dkval{ font-size:11.5px; font-weight:700; line-height:1; color:var(--txt); font-variant-numeric:tabular-nums; }
  .dkcol.zero .dkval{ color:var(--dim-2); font-weight:600; }
  /* the baseline is the tracks' own bottom edge, so it runs unbroken under every
     column -- the gap between bars comes from the bar's inset, not from the grid */
  .dktrack{ position:relative; width:100%; height:124px; border-bottom:1px solid var(--line-2); }
  .dkbar{ position:absolute; left:3px; right:3px; bottom:0; min-height:2px; background:var(--c,var(--accent));
          box-shadow:inset 0 1px 0 rgba(255,255,255,.16); transform-origin:50% 100%;
          animation:dkgrow .4s var(--ease) both; transition:filter var(--t); }
  .dkcol.zero .dkbar{ display:none; }
  .dkcol:hover .dkbar{ filter:brightness(1.15); }
  .dkkey{ font-size:11px; color:var(--dim); font-variant-numeric:tabular-nums; transition:color var(--t); }
  .dkcol:hover .dkkey,.dkcol:hover .dkval{ color:var(--accent-lit); }
  @keyframes dkgrow{ from{ transform:scaleY(0); } to{ transform:scaleY(1); } }
  /* what the three colours mean, and how much of the deck sits in each */
  .dkband{ display:flex; flex-wrap:wrap; gap:8px 20px; margin-top:15px; padding-top:13px;
           border-top:1px solid var(--line); font-size:11.5px; color:var(--dim); }
  .dkbnd{ display:inline-flex; align-items:center; gap:7px; }
  .dkbnd i{ width:9px; height:9px; flex:none; background:var(--c); }
  .dkbnd em{ font-style:normal; color:var(--dim-2); font-variant-numeric:tabular-nums; }
  .dkbnd b{ color:var(--txt); font-weight:700; font-variant-numeric:tabular-nums; }
  .dkbnd.off{ opacity:.42; }
  /* The mix as one segmented band, then a labelled row per type. The icon and
     the label carry the identity, which leaves colour free to be decoration
     rather than the only thing telling two rows apart. */
  .dkmix{ display:flex; gap:3px; height:10px; margin-bottom:16px; }
  .dkseg{ min-width:5px; border-radius:var(--pill); background:var(--c,var(--accent)); }
  /* the rows share out whatever height the curve beside them sets, so a deck of
     one type doesn't leave a row stranded at the bottom of an empty panel */
  .dkrows{ display:flex; flex-direction:column; justify-content:space-evenly; gap:12px; flex:1 1 auto; }
  .dkrow{ display:grid; grid-template-columns:minmax(0,auto) 1fr auto; align-items:center;
          gap:10px; font-size:12.5px; }
  .dkk{ display:flex; align-items:center; gap:7px; color:var(--txt); white-space:nowrap; }
  .dkk .ic{ width:14px; height:14px; flex:none; color:var(--c,var(--accent)); vertical-align:0; }
  .dktr{ height:7px; border-radius:var(--pill); background:var(--inset); overflow:hidden; }
  .dkfill{ display:block; height:100%; border-radius:var(--pill); background:var(--c,var(--accent));
           transform-origin:0 50%; animation:dkslide .45s var(--ease) both; }
  .dkv{ min-width:32px; text-align:right; font-weight:700; color:var(--txt); font-variant-numeric:tabular-nums; }
  .dkv small{ display:block; font-size:10px; font-weight:500; line-height:1.3; color:var(--dim-2); }
  @keyframes dkslide{ from{ transform:scaleX(0); } to{ transform:scaleX(1); } }
  #deckStatsFab{ width:46px; height:46px; border-radius:var(--r-md); padding:0; background:var(--panel-2);
                 color:var(--accent-lit); border:1px solid var(--line-2); box-shadow:var(--sh-2); }
  #deckStatsFab:hover{ background:var(--panel); border-color:var(--accent); filter:none; }
  /* a heavier stroke than the icon set's default: four bars at 21px need the
     weight to read as a histogram rather than as hairlines */
  #deckStatsFab .ic{ width:22px; height:22px; vertical-align:0; stroke-width:2.3; }
  .dcards{ display:grid; grid-template-columns:repeat(auto-fill,minmax(210px,1fr)); gap:14px; }
  .dcard{ display:flex; flex-direction:column; background:var(--surface); border:1px solid var(--line);
          border-radius:var(--r-lg); overflow:hidden; padding-bottom:13px; box-shadow:var(--edge); transition:border-color var(--t); }
  .dcard.done{ border-color:color-mix(in srgb,var(--good) 38%,var(--line)); }
  .dcard.missing{ border-color:color-mix(in srgb,var(--bad) 30%,var(--line)); }
  .dcard .dt{ width:100%; aspect-ratio:5/7; object-fit:cover; background:var(--inset); cursor:zoom-in;
              display:flex; align-items:center; justify-content:center; color:var(--dim-2); }
  .dcard .dt .ic{ width:32px; height:32px; }
  .dcard.land .dt{ aspect-ratio:7/5; }
  .dcards.bf{ grid-template-columns:repeat(var(--n,3),1fr); }
  .dcard .dfoot{ padding:12px 13px 0; display:flex; flex-direction:column; gap:8px; min-width:0; }
  .dcard .dnm{ font-size:14px; font-weight:650; line-height:1.35; display:flex; gap:8px; }
  .dcard .dqty{ font-weight:700; font-variant-numeric:tabular-nums; color:var(--accent-lit); flex:none; }
  .dcard .dsub{ color:var(--dim); font-size:11.5px; display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
  .dcard .own{ font-size:11.5px; font-weight:700; border-radius:var(--pill); padding:3px 9px; white-space:nowrap;
               display:inline-flex; align-items:center; gap:5px; }
  .dcard .own .ic{ width:12px; height:12px; vertical-align:0; }
  .dcard .downrow{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
  .dcard .dprice{ font-size:13px; font-weight:700; color:var(--txt); font-variant-numeric:tabular-nums; }
  .dcard .dprice .ea{ font-size:10.5px; font-weight:500; color:var(--dim); margin-left:3px; }
  .dcard .dprice.none{ color:var(--dim); font-weight:400; font-size:12.5px; }
  .dcard .wish-add{ min-height:26px; padding:0 10px; background:transparent; color:var(--accent-lit); font-size:11.5px;
                    box-shadow:none; border:1px solid color-mix(in srgb,var(--accent) 40%,var(--line)); border-radius:var(--pill); }
  .dcard .wish-add:hover{ background:var(--accent-wash); filter:none; }
  .dcard .wish-add:disabled{ opacity:1; }
  .dcard .wish-add.done{ color:var(--good); border-color:color-mix(in srgb,var(--good) 45%,var(--line)); background:var(--good-wash); }
  .dcard .own.ok{ color:var(--good); background:var(--good-wash); }
  .dcard .own.no{ color:var(--bad); background:var(--bad-wash); }
  .dcard .own.some{ color:var(--accent-lit); background:var(--accent-wash); }
  /* Held by a frozen deck: owned, just spoken for. Same cold blue the frozen
     chip and button use, so the two read as one state across the tab. */
  .dcard.held{ border-color:color-mix(in srgb,var(--info) 34%,var(--line)); }
  .dcard .own.held{ color:var(--info); background:color-mix(in srgb,var(--info) 15%,transparent); }
  .dcard .own.held .ic{ width:11px; height:11px; margin-right:1px; }

  /* =========================================================================
     Summary
     ========================================================================= */
  /* align-items:start, so a panel is as tall as its own rows. Stretched to a
     shared row height, the four-row "By card type" card carried two rows of
     empty floor just because "By color" has six. */
  /* Two stacked panels now, not a two-column grid of five: the breakdowns moved
     into one panel of their own (see .cuts). */
  .panels{ display:flex; flex-direction:column; gap:14px; }
  .card-panel{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg);
               padding:20px; box-shadow:var(--edge),var(--sh-1); }
  .card-panel h3{ font-size:13.5px; }
  .card-panel .p-sub{ color:var(--dim); font-size:12px; margin:3px 0 18px; }
  .card-panel > h3 + *:not(.p-sub){ margin-top:18px; }

  /* ---- value breakdown ----------------------------------------------------
     Three cuts of one total, side by side. Each is a single bar -- the whole
     collection, divided -- with a legend under it doing the naming and the
     numbers.

     What this replaced: three cards holding thirteen individual bars, each one
     a track of its own. Thirteen bars is thirteen things to compare and no
     answer to the question the panel is actually asked, which is how the
     collection splits. One bar per cut says it in a glance, and the three bars
     are the same width, so the three cuts are finally comparable to each other
     as well.

     There are no rules between the segments and no strokes around them: 2px of
     the panel showing through is what separates them. */
  .cuts{ display:grid; grid-template-columns:repeat(auto-fit,minmax(min(250px,100%),1fr)); gap:28px 34px; }
  .cut{ min-width:0; }
  .cut-h{ display:flex; align-items:baseline; gap:8px; margin-bottom:12px; min-width:0; }
  .cut-h .k{ flex:none; font-size:10px; font-weight:700; letter-spacing:.13em;
             text-transform:uppercase; color:var(--dim); }
  .cut-h .s{ min-width:0; font-size:11px; color:var(--dim-2);
             overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .stack{ display:flex; gap:2px; height:13px; border-radius:7px; overflow:hidden; }
  .stack .seg{ flex:1 1 0; min-width:3px; transition:opacity .13s var(--ease); }
  .keys{ display:flex; flex-direction:column; gap:1px; margin-top:13px; }
  .key{ display:grid; grid-template-columns:16px minmax(0,1fr) auto auto; align-items:center; gap:10px;
        padding:4px 7px; margin:0 -7px; border-radius:var(--r-sm); font-size:12.5px;
        transition:opacity .13s var(--ease); }
  .key .sw{ width:10px; height:10px; border-radius:3px; justify-self:center; }
  .key .rune{ width:15px; height:15px; justify-self:center; }
  .key .nm{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .key .v{ font-variant-numeric:tabular-nums; font-weight:600; color:var(--txt); }
  .key .p{ font-variant-numeric:tabular-nums; color:var(--dim); min-width:44px; text-align:right; }
  /* Hovering either half of a cut lights the pair and mutes the rest, so a
     segment and its row find each other in both directions. Nothing is hidden
     behind it -- every number is already on the page. */
  .cut.lit .seg:not(.on){ opacity:.28; }
  /* opacity on the whole row, not colour on its text: the rune icons are images
     and would have stayed at full brightness over dimmed names. */
  .cut.lit .key:not(.on){ opacity:.4; }
  .key.on{ background:var(--panel-2); }
  .cut .note{ color:var(--dim); font-size:12px; margin-top:12px; }
  .cuts > .note{ grid-column:1 / -1; color:var(--dim); font-size:12.5px; }

  /* ---- biggest movers -----------------------------------------------------
     The value chart says what the collection did; this board says which of it
     did that. Two columns rather than one signed ranking: mixed together, a
     good week pushes the worst news to the bottom of the panel and a bad one
     hides the best of it entirely, which is the half a reader most wants.

     Each row carries the move three ways — the line it drew, the percentage,
     and what that was worth across the stack — because none of the three
     answers for the others. A percentage says how far a price travelled and
     nothing about how it got there: one jump on the morning of a sweep and a
     fortnight of steady climb are the same number, and only the line tells
     them apart.

     Rows are buttons. A move you didn't expect is one click from the inspector
     that draws its full price trend, so the panel never has to explain itself. */
  .mv-head{ display:flex; align-items:flex-start; gap:14px; flex-wrap:wrap; }
  .mv-head .mv-title{ flex:1 1 240px; min-width:0; }
  .mv-head .p-sub{ margin-bottom:0; }
  /* Same size and disabled treatment as the chart's range picker directly
     above it — three pickers on one page that read as one control. */
  .mv-picks{ display:flex; flex-wrap:wrap; justify-content:flex-end; gap:8px; flex:none; }
  .mv-head .toggle{ flex:none; }
  .mv-head .toggle button{ min-height:26px; padding:0 10px; font-size:11.5px;
                           font-variant-numeric:tabular-nums; }
  .mv-head .toggle button:disabled{ opacity:.34; }
  .mv-head .toggle button:disabled:hover{ background:transparent; color:var(--dim); }

  /* 400px before the board splits in two. Art, sparkline and numbers cost a
     fixed ~230px of every row, so a narrower column pays for the split out of
     the card name — and one full-width column of ten beats two columns of five
     that both end in an ellipsis. */
  .mv-cols{ display:grid; grid-template-columns:repeat(auto-fit,minmax(min(400px,100%),1fr));
            gap:24px 32px; margin-top:20px; }
  .mv-col{ min-width:0; }

  /* The direction is said three ways in the head — a tinted glyph, the word,
     and the sign on the total — so the two halves are told apart without the
     red and the green having to carry it alone. */
  .mv-ch{ display:flex; align-items:center; gap:9px; padding-bottom:10px; margin-bottom:5px;
          border-bottom:1px solid var(--line); }
  .mv-ch .b{ display:flex; align-items:center; justify-content:center; flex:none;
             width:21px; height:21px; border-radius:50%; }
  .mv-ch .b .ic{ width:12px; height:12px; }
  .mv-ch.up .b{ background:var(--good-wash); color:var(--good); }
  .mv-ch.down .b{ background:var(--bad-wash); color:var(--bad); }
  .mv-ch .t{ font-size:10px; font-weight:700; letter-spacing:.13em;
             text-transform:uppercase; color:var(--dim); }
  /* The whole column's worth, not the five rows on screen — so a board showing
     its top five never reads as if that were all of it. */
  .mv-ch .tot{ margin-left:auto; min-width:0; font-size:12px; font-weight:650;
               color:var(--txt); font-variant-numeric:tabular-nums; white-space:nowrap;
               overflow:hidden; text-overflow:ellipsis; }
  .mv-ch .tot .o{ color:var(--dim-2); font-weight:500; }

  .mv-list{ display:flex; flex-direction:column; }
  /* Art, name, the shape of the move, then the numbers. Name and sparkline both
     flex, roughly three to one, so the slack of a wide single-column board goes
     into a longer line instead of opening a dead band between the two — while
     the numbers stay a fixed track and read straight down the list rather than
     ragging along the ends of the names. */
  .mv-row{ display:grid; grid-template-columns:32px minmax(0,1fr) minmax(84px,0.3fr) auto;
           align-items:center;
           gap:12px; width:100%; min-height:0; margin:0 -8px; padding:7px 8px;
           background:transparent; border:0; box-shadow:none; border-radius:var(--r-sm);
           text-align:left; font-weight:500; white-space:normal; }
  /* The hover carries the row's own direction rather than a neutral grey: it is
     the one surface in the panel big enough to tint without shouting. */
  .mv-row.up:hover{ background:color-mix(in srgb,var(--good) 9%,var(--panel-2)); filter:none; }
  .mv-row.down:hover{ background:color-mix(in srgb,var(--bad) 9%,var(--panel-2)); filter:none; }
  /* A ring and a shadow rather than a bare rounded rectangle: at 32px a card
     with no edge to it reads as a sticker printed onto the panel. */
  .mv-art{ width:32px; height:45px; border-radius:4px; overflow:hidden; flex:none;
           background:var(--inset); display:flex; align-items:center; justify-content:center;
           color:var(--dim-2); box-shadow:0 0 0 1px var(--line-2),0 1px 3px rgba(0,0,0,.4); }
  .mv-art img{ width:100%; height:100%; object-fit:cover; display:block; }
  .mv-art .ic{ width:14px; height:14px; }
  .mv-nm{ min-width:0; display:flex; flex-direction:column; gap:3px; }
  .mv-nm .n{ font-size:12.5px; font-weight:600; color:var(--txt);
             overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .mv-nm .s{ font-size:11px; color:var(--dim);
             overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  /* Stretched to the track, not drawn at a fixed size — so the same markup
     fills a 500px column and a 360px one. Both marks take their colour from
     the row, which is where the direction is already decided; the wash under
     the line is what gives the row something to sit on without adding a rule. */
  .mv-spark{ display:block; width:100%; height:22px; }
  .mv-spark .ln{ fill:none; stroke:currentColor; stroke-width:1.5;
                 stroke-linecap:round; stroke-linejoin:round; }
  .mv-spark .ar{ fill:currentColor; stroke:none; opacity:.22;
                 -webkit-mask-image:linear-gradient(to bottom,#000,transparent);
                 mask-image:linear-gradient(to bottom,#000,transparent); }
  .mv-row.up .mv-spark{ color:var(--good); }
  .mv-row.down .mv-spark{ color:var(--bad); }
  .mv-num{ display:flex; flex-direction:column; align-items:flex-end; gap:3px;
           font-variant-numeric:tabular-nums; }
  /* The percentage is the number the board is ranked by, so it gets the one
     filled shape in the row; the money under it is the consequence and stays
     quiet. Colouring both would put two competing signals in every line. */
  .mv-num .c{ padding:2px 8px; border-radius:999px; line-height:1.5;
              font-size:11.5px; font-weight:700; }
  .mv-row.up .mv-num .c{ background:var(--good-wash); color:var(--good); }
  .mv-row.down .mv-num .c{ background:var(--bad-wash); color:var(--bad); }
  .mv-num .s{ font-size:11px; color:var(--dim); padding-right:1px; }
  .mv-note{ color:var(--dim); font-size:12px; margin-top:10px; }
  /* Ruled off rather than floating: it is the panel's small print, not a third
     column of it. */
  .mv-foot{ color:var(--dim-2); font-size:11.5px; margin-top:20px;
            padding-top:14px; border-top:1px solid var(--line); }
  @media (max-width:560px){
    /* Each picker takes a row to itself rather than wrapping mid-control —
       equal full-width targets are the one arrangement you can't mis-tap. */
    .mv-picks{ width:100%; }
    .mv-head .toggle{ width:100%; }
    .mv-head .toggle button{ flex:1 1 0; min-height:32px; padding:0 4px; }
  }
  /* Past here the row is choosing between an ellipsised name and the sparkline,
     and the name is what the row is FOR. */
  @media (max-width:430px){
    .mv-row{ grid-template-columns:32px minmax(0,1fr) auto; }
    .mv-row .mv-spark{ display:none; }
  }

  /* The portfolio chart and every per-card price trend share one renderer, so
     they share one set of rules — .linechart is the mount point for both.
     Only the chart's own svg stretches; icons inside the panel must not. */
  .linechart{ position:relative; }
  .linechart > svg{ width:100%; height:auto; display:block; touch-action:pan-y; }
  .linechart .axis{ stroke:var(--line); }
  /* No font-size here: the renderer sets it on the <svg> and measures the money
     labels with that exact size to work out the gutter they need. A rule on the
     text elements themselves would beat that inheritance and the gutter would
     stop matching the type it was sized for. */
  .linechart text{ fill:var(--dim-2); font-variant-numeric:tabular-nums; }
  .linechart .hit{ fill:transparent; cursor:crosshair; }
  .linechart .cross line{ stroke:var(--dim); stroke-width:1; stroke-dasharray:3 4; }
  .linechart .cross circle{ stroke:var(--bg); stroke-width:2; }
  /* Time-range picker. It rides INSIDE the chart mount, so the summary chart and
     every card sheet get it from the one renderer that already draws them both.
     A range the data cannot fill is disabled rather than dropped: a row that
     reflows between four buttons and two is a row you mis-tap. */
  .linechart .rangetog{ display:flex; justify-content:flex-end; margin:0 0 10px; }
  .linechart .rangetog .toggle{ flex:none; }
  .linechart .rangetog button{ min-height:26px; padding:0 10px; font-size:11.5px;
                               font-variant-numeric:tabular-nums; }
  .linechart .rangetog button:disabled{ opacity:.34; }
  .linechart .rangetog button:disabled:hover{ background:transparent; color:var(--dim); }
  .charttip{ position:absolute; pointer-events:none; transform:translate(-50%,-100%);
             background:var(--panel-2); border:1px solid var(--line-2); border-radius:var(--r-sm); padding:7px 11px;
             font-size:12px; white-space:nowrap; box-shadow:var(--sh-2); display:none; z-index:2; }
  .charttip .v{ font-weight:700; font-variant-numeric:tabular-nums; color:var(--accent-lit); }
  .charttip .d{ color:var(--dim); margin-top:2px; }
  .trend-delta{ display:inline-flex; align-items:center; gap:6px; font-weight:700; font-variant-numeric:tabular-nums;
                padding:3px 10px; border-radius:var(--pill); background:var(--inset); border:1px solid var(--line); }
  .trend-delta .ic{ width:13px; height:13px; vertical-align:0; }

  /* =========================================================================
     Owner dashboard
     ========================================================================= */
  .dash-loading{ color:var(--dim); padding:30px 0; }
  .dash-updated{ color:var(--dim); font-size:12px; }
  .dash-grid{ margin-bottom:4px; }
  .dash-grid .tile .v{ font-size:19px; }
  #dashLive .s{ color:var(--dim); font-size:12.5px; }
  .dash-sec{ display:flex; align-items:center; gap:12px; margin:28px 0 12px;
             font-size:11px; font-weight:700; color:var(--dim); text-transform:uppercase; letter-spacing:.12em; }
  .dash-sec::after{ content:""; flex:1; height:1px; background:linear-gradient(90deg,var(--line),transparent); }
  .dash-sec .ic{ width:14px; height:14px; color:var(--accent); margin-right:-4px; }
  /* A control sitting in a section heading has to opt out of the heading's own
     uppercase/tracked type, or the option list inherits it and reads as shouting. */
  .dash-pick{ height:29px; min-height:0; padding:0 30px 0 10px; font-size:12.5px;
              text-transform:none; letter-spacing:0; font-weight:600; max-width:240px; }
  .dash-table-wrap{ overflow-x:auto; border:1px solid var(--line); border-radius:var(--r-lg);
                    background:var(--panel); box-shadow:var(--edge),var(--sh-1); }
  .dash-table{ width:100%; border-collapse:collapse; font-size:12.5px; }
  .dash-table th,.dash-table td{ padding:9px 13px; text-align:left; white-space:nowrap; }
  .dash-table thead th{ background:var(--inset); color:var(--dim-2); font-size:10px; font-weight:700;
                        letter-spacing:.11em; text-transform:uppercase; border-bottom:1px solid var(--line); }
  .dash-table tbody tr{ border-bottom:1px solid color-mix(in srgb,var(--line) 60%,transparent); }
  .dash-table tbody tr:last-child{ border-bottom:0; }
  .dash-table td.num,.dash-table th.num{ text-align:right; font-variant-numeric:tabular-nums; }
  .dash-table tr:hover td{ background:rgba(255,255,255,.022); }
  .dash-table .s{ color:var(--dim); font-size:11px; }
  .dash-table code{ background:var(--inset); border:1px solid var(--line); padding:1px 6px; border-radius:5px; font-size:11.5px; }
  .urow-av{ width:22px; height:22px; border-radius:50%; object-fit:cover; vertical-align:-6px; margin-right:7px; }
  .rolebadge{ font-size:10.5px; font-weight:700; text-transform:uppercase; letter-spacing:.07em;
              padding:2px 8px; border-radius:var(--pill); background:var(--inset); border:1px solid var(--line); color:var(--dim); }
  .rolebadge.owner{ color:var(--accent-lit); border-color:color-mix(in srgb,var(--accent) 40%,var(--line)); background:var(--accent-wash); }
  .dash-mini{ display:flex; gap:10px; margin-top:8px; font-size:11px; }
  .dashbar{ display:flex; height:8px; border-radius:var(--pill); overflow:hidden; background:var(--inset); margin-top:10px; }
  .dashbar > i{ display:block; height:100%; }
  .reqbar{ display:flex; height:8px; border-radius:var(--pill); overflow:hidden; background:var(--inset); }
  .reqbar > span{ display:block; height:100%; }
  .reqlegend{ display:flex; flex-wrap:wrap; gap:12px; margin-top:9px; font-size:11.5px; color:var(--dim); }
  .reqlegend b{ color:var(--txt); }
  .reqlegend .dot{ width:7px; height:7px; margin-right:5px; }
  .reqsub{ color:var(--dim); font-size:11.5px; margin-top:6px; }
  .dash-chart{ width:100%; height:110px; display:block; }
  .dash-two{ display:grid; grid-template-columns:1fr 1fr; gap:18px; align-items:start; }
  .livedot{ display:inline-block; width:7px; height:7px; border-radius:50%; background:var(--good);
            box-shadow:0 0 0 3px color-mix(in srgb,var(--good) 22%,transparent); animation:pulse 2s var(--ease) infinite; }
  @keyframes pulse{ 0%,100%{ opacity:1 } 50%{ opacity:.35 } }

  /* =========================================================================
     Home / front page
     ========================================================================= */
  .home{ display:flex; flex-direction:column; gap:34px; }
  .home section{ min-width:0; }
  /* the rail is hidden for a logged-out visitor, so the landing carries its own
     header — otherwise the public page has no branding and no way in */
  .lbar{ position:sticky; top:0; z-index:40; margin-bottom:-14px;
         background:color-mix(in srgb,var(--bg) 82%,transparent);
         backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
         border-bottom:1px solid var(--line); }
  .lbar-in{ max-width:var(--page); margin:0 auto; padding:11px var(--main-pad);
            display:flex; align-items:center; gap:10px; }
  .lbar-spacer{ margin-right:auto; }
  .lbar nav{ display:flex; align-items:center; gap:6px; }
  .lbar nav a:not(.abtn){ color:var(--dim); font-size:13px; font-weight:500; padding:6px 10px;
                          border-radius:var(--r-sm); }
  .lbar nav a:not(.abtn):hover{ color:var(--txt); background:var(--panel-2); text-decoration:none; }
  /* On a phone only the lockup and the way in survive — the three of them
     together pushed the bar wider than the viewport. Both links are repeated
     in the page footer. */
  @media (max-width:560px){
    .lbar nav a:not(.abtn){ display:none; }
    /* The links go, but the language has to stay reachable from the first
       screen a visitor sees — so the button keeps the flag and drops the name. */
    .lbar .lp-full{ display:none; }
    /* …and the call to action drops to its short form, which is what keeps the
       bar inside the viewport once the label is German or Polish. */
    #lbarSignIn .lb-long{ display:none; }
    #lbarSignIn .lb-short{ display:inline; }
    /* Even the short form is "Iniciar sesión" in Spanish and "Se connecter" in
       French, so the picker gives up its chevron and some padding here. The
       globe and the two-letter code are enough to say what it is and what it is
       set to, and they fit next to the longest of those labels on a 360px
       screen — which is the narrowest phone worth designing for. */
    .lbar .lp-btn{ padding:5px 7px; gap:5px; }
    .lbar .lp-chev{ display:none; }
    /* "Iniciar sesión" and "Se connecter" are half again as wide as "Sign in",
       and a 360px screen has no slack left after the mark and the picker. The
       button gives up the difference in type size rather than in words: a
       shorter Spanish label would have to be a worse one. */
    #lbarSignIn{ font-size:12.5px; padding-inline:12px; }
  }
  /* Below this the wordmark goes and the rune stays. Three things have to share
     the bar now instead of two, and on a 360px screen the longest sign-in label
     of the six languages does not fit beside a spelled-out RIFT-TRACKER. The
     mark alone still says whose site this is, and the name is in the hero two
     lines down. */
  @media (max-width:400px){
    .lbar .bm-wm{ display:none; }
    .lbar .bm-mark{ width:26px; height:26px; border-radius:8px; }
    .lbar .bm-mark .ic{ width:15px; height:15px; }
    .lbar .bm-wm{ font-size:12.5px; }
  }

  /* ---- hero: copy on the left, real card art fanned on the right ---------- */
  .hero{ position:relative; display:grid; grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
         gap:40px; align-items:center; padding:30px 0 6px; }
  .hero-tx{ min-width:0; }
  .hero .eyebrow{ display:inline-flex; align-items:center; gap:8px; margin-bottom:18px; color:var(--accent);
                  padding:5px 11px 5px 9px; border-radius:var(--pill); background:var(--accent-wash);
                  border:1px solid color-mix(in srgb,var(--accent) 24%,transparent); }
  .home-title{ font-size:clamp(30px,3.6vw,50px); letter-spacing:-.038em; font-weight:760; margin:0 0 14px; line-height:1.03; }
  .home-title span{ display:block;
                    background:linear-gradient(96deg,var(--accent-lit),var(--accent) 58%,#a97c2c);
                    -webkit-background-clip:text; background-clip:text; color:transparent; }
  .home-tag{ color:var(--dim); font-size:15.5px; margin:0 0 26px; max-width:44ch; line-height:1.6; }
  .home-search{ max-width:520px; text-align:left; }
  .home-search input{ height:48px; font-size:14.5px; padding-left:42px; border-radius:var(--r-md); }
  .home-search > .ic{ left:15px; top:16px; width:17px; height:17px; }
  .hero-meta{ display:flex; flex-wrap:wrap; align-items:center; gap:8px 18px; margin-top:16px;
              color:var(--dim-2); font-size:12.5px; }
  .hero-meta b{ color:var(--dim); font-weight:600; }
  .hero-runes{ display:flex; align-items:center; gap:7px; }
  .hero-runes .rune{ width:19px; height:19px; opacity:.85; }
  .hero-cta{ display:flex; flex-wrap:wrap; gap:10px; margin-top:4px; }
  /* a link that has to sit next to a button and match it */
  .abtn{ display:inline-flex; align-items:center; justify-content:center; gap:7px;
         min-height:var(--ctrl-h); padding:0 14px; border-radius:var(--r-sm);
         background:var(--accent); color:var(--accent-ink); border:1px solid var(--accent);
         font-size:13.5px; font-weight:600;
         box-shadow:var(--edge-lit),0 1px 1px rgba(0,0,0,.28); transition:filter var(--t),background var(--t); }
  .abtn:hover{ filter:brightness(1.06); text-decoration:none; }
  .abtn.ghost{ background:var(--panel); color:var(--txt); border-color:var(--line); box-shadow:var(--edge); }
  .abtn.ghost:hover{ background:var(--panel-2); border-color:var(--line-2); filter:none; }

  /* the fan itself: three printings overlapped, spreading apart on hover */
  .cfan{ --cw:min(196px,21vw); position:relative; display:flex; justify-content:center;
         align-items:center; padding:26px 0; }
  .cfan::before{ content:""; position:absolute; inset:-10% -2%; pointer-events:none; border-radius:50%;
                 background:radial-gradient(52% 52% at 50% 48%,
                            color-mix(in srgb,var(--accent) 34%,transparent), transparent 72%); }
  .cf{ position:relative; width:var(--cw); aspect-ratio:.716; flex:none; border-radius:12px; overflow:hidden;
       padding:0; min-height:0; cursor:pointer;
       background:var(--inset); border:1px solid rgba(255,255,255,.10);
       box-shadow:0 26px 54px rgba(0,0,0,.62), var(--edge);
       transition:transform .4s var(--ease), box-shadow .4s var(--ease); }
  .cf img{ width:100%; height:100%; object-fit:cover; display:block; }
  .cf + .cf{ margin-left:calc(var(--cw) * -.22); }
  .cf:nth-child(1){ transform:rotate(-9deg) translateY(16px) scale(.93); z-index:1; transform-origin:bottom right; }
  .cf:nth-child(2){ transform:translateY(-14px); z-index:3; }
  .cf:nth-child(3){ transform:rotate(9deg) translateY(16px) scale(.93); z-index:2; transform-origin:bottom left; }
  .cfan:hover .cf:nth-child(1){ transform:rotate(-13deg) translate(-16px,10px) scale(.95); }
  .cfan:hover .cf:nth-child(2){ transform:translateY(-24px) scale(1.03); box-shadow:0 34px 64px rgba(0,0,0,.7); }
  .cfan:hover .cf:nth-child(3){ transform:rotate(13deg) translate(16px,10px) scale(.95); }

  /* ---- full-bleed drifting strip of catalog art -------------------------- */
  .mq{ position:relative; overflow:hidden; padding:18px 0; border-block:1px solid var(--line);
       background:linear-gradient(180deg,color-mix(in srgb,var(--panel) 55%,transparent),transparent);
       /* the mask takes the hairlines and background with it, so the band dissolves
          at both ends instead of stopping dead */
       -webkit-mask-image:linear-gradient(90deg,transparent,#000 13%,#000 87%,transparent);
               mask-image:linear-gradient(90deg,transparent,#000 13%,#000 87%,transparent); }
  .mq-track{ display:flex; gap:11px; width:max-content; animation:mq 78s linear infinite; }
  /* Each frame is a button, not a bare img: the band opens the detail sheet the
     same way the hero fan does, so art that lifts under the cursor isn't a dead
     end. Resets padding/min-height/background off the base button. */
  .mq-card{ width:96px; aspect-ratio:.716; flex:none; padding:0; min-height:0; overflow:hidden;
            border-radius:8px; border:1px solid var(--line); background:var(--inset);
            box-shadow:none; opacity:.74; cursor:pointer;
            transition:opacity var(--t),transform var(--t); }
  .mq-card img{ width:100%; height:100%; object-fit:cover; display:block; }
  .mq-card:hover{ opacity:1; transform:translateY(-5px); filter:none; }
  /* A moving target is a bad click target — hold the drift while a pointer is
     over the band, or while a keyboard is tabbing through it. */
  .mq:hover .mq-track,.mq:focus-within .mq-track{ animation-play-state:paused; }
  .mq.rev .mq-track{ animation-direction:reverse; }
  @keyframes mq{ to{ transform:translateX(calc(-50% - 5.5px)); } }
  @media (prefers-reduced-motion:reduce){ .mq-track{ animation:none; } }

  /* ---- section heading shared by the home bands -------------------------- */
  .home-h{ display:flex; align-items:baseline; gap:12px; margin-bottom:14px; }
  .home-h h3{ font-size:15px; font-weight:650; }
  .home-h .sub{ color:var(--dim); font-size:12.5px; }
  .home-h .more{ margin-left:auto; }

  /* ---- highlight grid: the user's own art ------------------------------- */
  /* fixed column counts, not auto-fill: the row is always exactly full */
  .hgrid{ display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); gap:18px 14px; }
  .hcard{ display:block; padding:0; border:0; background:none; box-shadow:none; min-height:0; font-weight:400;
          text-align:left; color:inherit; cursor:pointer; min-width:0; }
  .hcard:hover{ filter:none; }
  .hcard .nm,.hcard .pr,.hcard .fr{ display:block; }
  .hcard .fr{ position:relative; aspect-ratio:.716; border-radius:10px; overflow:hidden; background:var(--inset);
              border:1px solid var(--line); box-shadow:var(--sh-1);
              transition:transform var(--t),border-color var(--t),box-shadow var(--t); }
  .hcard:hover .fr{ transform:translateY(-5px); box-shadow:var(--sh-2);
                    border-color:color-mix(in srgb,var(--accent) 48%,transparent); }
  .hcard .fr img{ width:100%; height:100%; object-fit:cover; display:block; }
  .hcard .fr .ph{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center; color:var(--dim-2); }
  .hcard .nm{ font-size:12.5px; font-weight:600; margin-top:9px; overflow:hidden;
              text-overflow:ellipsis; white-space:nowrap; }
  /* quantity rides with the price, not on the art — every corner of a Riftbound
     card frame is already occupied by cost, domain, or set line */
  .hcard .pr{ font-size:12px; color:var(--accent-lit); font-variant-numeric:tabular-nums; margin-top:1px; }
  .hcard .pr .qty{ color:var(--dim); font-weight:600; }

  /* ---- section links, each carrying a peek at its own art ---------------- */
  .home-links{ display:grid; grid-template-columns:repeat(2,1fr); gap:12px; }
  .home-link{ display:flex; align-items:center; gap:14px; text-align:left; cursor:pointer; color:inherit;
              background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); padding:14px 16px;
              min-height:0; font-weight:400; box-shadow:var(--edge),var(--sh-1); overflow:hidden;
              transition:border-color var(--t),transform var(--t); }
  .home-link:hover{ border-color:var(--line-2); transform:translateY(-3px); filter:none; }
  /* takes the slack so the chevron always lands on the right edge, whether or
     not the tile has a stack to push it there */
  .home-link .hl-tx{ flex:1 1 auto; min-width:0; }
  .home-link .hl-t{ font-weight:650; font-size:14.5px; }
  .home-link .hl-d{ color:var(--dim); font-size:12.5px; margin-top:3px; white-space:normal; line-height:1.45; }
  /* the counts that replaced the old stat strip: number bright and tabular so it
     reads as data, the unit stays quiet */
  .home-link .hl-d b{ color:var(--txt); font-weight:650; font-variant-numeric:tabular-nums; }
  /* The tracker is a phone tool — the handset goes flat on the table between two
     players — so the ways in only exist where the app is already in its phone
     layout, at the same breakpoint the rail becomes a bottom bar. The /score URL
     still resolves anywhere, so a shortcut saved to a home screen keeps working.
     On the signed-in home it takes the full width of the two-column grid rather
     than sitting as a fifth tile orphaned on its own row. */
  .score-cta,.score-band{ display:none; }
  @media (max-width:860px){
    .score-cta{ display:flex; grid-column:1/-1; }
    .score-band{ display:block; }
  }
  .hl-flag{ color:var(--accent-lit); }
  .hl-flag b{ color:var(--accent-lit) !important; }
  .hl-stack{ display:flex; margin-left:auto; padding-left:10px; flex:none; }
  /* card-shaped, so scoped away from the avatar variant below — an <img> avatar
     would otherwise inherit the card aspect and corner radius and read as square */
  .hl-stack:not(.av) img,.hl-stack .ph{
    width:34px; aspect-ratio:.716; object-fit:cover; border-radius:5px; display:block;
    border:1px solid var(--line-2); background:var(--inset); box-shadow:var(--sh-1);
    transition:transform var(--t); }
  /* The sealed fallback goes in the same slot, but a box photo cover-cropped to
     a card's shape is an unreadable sliver of cardboard — so it is contained on
     the plate instead of filling it. */
  .hl-stack.sealed:not(.av) img{ object-fit:contain; padding:2px; }
  .hl-stack > * + *{ margin-left:-15px; }
  .hl-stack:not(.av) > :nth-child(1){ transform:rotate(-7deg); }
  .hl-stack:not(.av) > :nth-child(3){ transform:rotate(7deg); }
  .home-link:hover .hl-stack:not(.av) > :nth-child(1){ transform:rotate(-11deg) translateX(-4px); }
  .home-link:hover .hl-stack:not(.av) > :nth-child(3){ transform:rotate(11deg) translateX(4px); }
  /* people, not cards: round, upright, ringed against the tile so the overlap reads */
  .hl-stack.av > * + *{ margin-left:-11px; }
  .hl-stack.av .fr-av{ width:31px; height:31px; aspect-ratio:1; border-radius:50%; object-fit:cover;
                       display:flex; font-size:12.5px; border:2px solid var(--panel-hi);
                       background:var(--panel-2); color:var(--txt); box-shadow:var(--sh-1);
                       transition:transform var(--t); }
  .hl-stack.av .fr-av.more{ background:var(--panel-2); color:var(--dim); font-size:11px; font-weight:700; }
  .home-link:hover .hl-stack.av .fr-av{ transform:translateY(-2px); }
  .home-link .hl-go{ color:var(--dim-2); display:flex; margin-left:10px;
                     transition:color var(--t),transform var(--t); }
  .home-link:hover .hl-go{ color:var(--accent-lit); transform:translateX(3px); }

  /* ---- signed-out feature grid ------------------------------------------ */
  .home-feats{ display:grid; grid-template-columns:repeat(4,1fr); gap:12px; }
  .home-feat{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); padding:18px;
              box-shadow:var(--edge),var(--sh-1); }
  .home-feat .hl-t{ font-weight:650; font-size:14.5px; margin-top:14px; }
  .home-feat .hl-d{ color:var(--dim); font-size:13px; margin-top:5px; line-height:1.5; }
  .signin-split{ display:grid; grid-template-columns:minmax(0,1fr) 390px; gap:36px; align-items:center;
                 max-width:1020px; margin:0 auto; width:100%;
                 background:var(--surface); border:1px solid var(--line); border-radius:var(--r-xl);
                 padding:28px 30px; box-shadow:var(--edge),var(--sh-2); }
  .signin-split .ss-tx h3{ font-size:21px; letter-spacing:-.02em; }
  .signin-split .ss-tx p{ color:var(--dim); font-size:13.5px; margin-top:8px; line-height:1.6; max-width:42ch; }
  .ss-points{ list-style:none; margin:16px 0 0; padding:0; display:flex; flex-direction:column; gap:9px;
              color:var(--dim); font-size:13px; }
  .ss-points li{ display:flex; align-items:flex-start; gap:9px; }
  .ss-points .ic{ color:var(--accent); margin-top:2px; }
  .home-foot{ display:flex; flex-wrap:wrap; align-items:center; justify-content:center; gap:8px 16px;
              color:var(--dim-2); font-size:12px; padding-top:6px; border-top:1px solid var(--line); }

  /* ---- "this is free" ----------------------------------------------------
     The first question a visitor has is whether the page is going to ask for
     money, and an answer further down is an answer they never read. So it's
     said twice above the fold: a chip on the hero, and a band directly under it
     spelling out what free means here. */
  .eyebrow-row{ display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin-bottom:18px; }
  .eyebrow-row .eyebrow{ margin-bottom:0; }
  .hero .eyebrow.free{ color:var(--good); background:var(--good-wash);
                       border-color:color-mix(in srgb,var(--good) 32%,transparent); }
  .free-band{ display:flex; flex-wrap:wrap; align-items:center; gap:16px 26px; padding:17px 22px;
              border-radius:var(--r-xl); box-shadow:var(--edge),var(--sh-1);
              background:linear-gradient(100deg,color-mix(in srgb,var(--good) 12%,var(--surface)),var(--surface) 58%);
              border:1px solid color-mix(in srgb,var(--good) 26%,var(--line)); }
  .free-band .fb-mark{ display:flex; align-items:center; gap:13px; flex:none; }
  .free-band .fb-mark > .ic{ width:20px; height:20px; color:var(--good); padding:10px; box-sizing:content-box;
              border-radius:var(--r-md); background:var(--good-wash);
              border:1px solid color-mix(in srgb,var(--good) 26%,transparent); }
  .free-band .fb-t{ font-size:17px; font-weight:750; letter-spacing:-.022em; }
  .free-band .fb-s{ color:var(--dim); font-size:12.5px; margin-top:3px; }
  .free-band .fb-items{ display:flex; flex-wrap:wrap; gap:9px 22px; margin-left:auto; }
  .free-band .fb-item{ display:flex; align-items:center; gap:8px; font-size:13px; color:var(--dim); }
  .free-band .fb-item .ic{ width:15px; height:15px; color:var(--good); flex:none; }
  .free-band .fb-item b{ color:var(--txt); font-weight:600; }
  @media (max-width:900px){ .free-band .fb-items{ margin-left:0; } }

  .home-cta{ background:var(--inset); border:1px solid var(--line);
             border-radius:var(--r-lg); padding:22px 22px 20px; }
  .home-cta h3{ font-size:16px; text-align:center; }
  .home-cta .cta-sub{ color:var(--dim); font-size:13px; text-align:center; margin:6px 0 18px; }
  .home-cta-sub{ color:var(--dim); font-size:12.5px; margin:14px 0 0; text-align:center; line-height:1.55; }
  .magic-msg{ margin-top:12px; font-size:13px; padding:11px 13px; border-radius:var(--r-sm); text-align:left; line-height:1.55; }
  .magic-msg.ok{ background:var(--good-wash); border:1px solid color-mix(in srgb,var(--good) 26%,transparent); }
  .magic-msg.err{ background:var(--bad-wash); border:1px solid color-mix(in srgb,var(--bad) 26%,transparent); }
  .magic-msg a{ word-break:break-all; }
  .magic-msg .ic{ color:currentColor; opacity:.85; margin-right:7px; vertical-align:-2px; }

  /* ---- auth panel --------------------------------------------------------
     Sits in the .home-cta well. An accent bloom off the top edge lifts it out
     of the flat inset, echoing the login gate's radial. */
  .home-cta{ position:relative; overflow:hidden; }
  .home-cta::before{ content:""; position:absolute; left:0; right:0; top:0; height:150px; pointer-events:none;
    background:radial-gradient(70% 100% at 50% 0,var(--accent-wash),transparent 72%); }
  .home-cta > *{ position:relative; }
  .home-cta h3{ font-size:17px; letter-spacing:-.015em; }

  /* Sign in / Create account switch: a pill track with one sliding tile, so the
     two modes read as one control rather than two competing buttons. */
  .auth-seg{ position:relative; display:grid; grid-template-columns:1fr 1fr; padding:3px; margin-bottom:16px;
    border-radius:var(--pill); background:var(--bg); border:1px solid var(--line); box-shadow:inset 0 1px 3px rgba(0,0,0,.35); }
  .auth-seg-ind{ position:absolute; top:3px; bottom:3px; left:3px; width:calc(50% - 3px); border-radius:var(--pill);
    background:linear-gradient(180deg,var(--panel-2),var(--panel)); border:1px solid var(--line-2);
    box-shadow:var(--edge),var(--sh-1); transition:transform .24s var(--ease); }
  .auth-seg.is-up .auth-seg-ind{ transform:translateX(100%); }
  .auth-tab{ position:relative; z-index:1; height:32px; min-height:0; padding:0 8px; border:0; border-radius:var(--pill);
    background:none; box-shadow:none; color:var(--dim); font:inherit; font-size:13px; font-weight:650; cursor:pointer; }
  .auth-tab:hover{ color:var(--txt); background:none; filter:none; }
  .auth-tab.is-on{ color:var(--accent-lit); }

  .auth-form{ display:flex; flex-direction:column; gap:10px; }
  .auth-form .field > input{ width:100%; height:46px; padding-left:38px; padding-right:12px; font-size:14px; }
  .auth-form .field > .ic{ left:13px; }
  .auth-form .field.has-eye > input{ padding-right:44px; }
  .auth-form .btn-lg{ width:100%; margin-top:2px; }
  /* reveal toggle, parked inside the password well */
  .pw-eye{ position:absolute; right:6px; width:34px; height:34px; min-height:0; padding:0; border-radius:var(--r-xs);
    background:none; border:1px solid transparent; box-shadow:none; color:var(--dim-2); }
  .pw-eye:hover{ color:var(--txt); background:var(--panel-2); filter:none; }
  .pw-eye .ic{ vertical-align:0; }

  /* Advisory strength read-out. Four segments, coloured through the same status
     ramp the rest of the app uses. */
  .pw-meter{ display:flex; flex-direction:column; gap:6px; padding:2px 2px 0; }
  .pw-bars{ display:grid; grid-template-columns:repeat(4,1fr); gap:4px; }
  .pw-bars i{ height:3px; border-radius:var(--pill); background:var(--line); transition:background var(--t); }
  .pw-meter .pw-lbl{ display:flex; justify-content:space-between; align-items:center; gap:10px;
    color:var(--dim-2); font-size:11.5px; }
  .pw-meter .pw-lbl b{ font-weight:650; color:var(--dim); }
  .pw-meter[data-score="1"] .pw-bars i:nth-child(-n+1){ background:var(--bad); }
  .pw-meter[data-score="2"] .pw-bars i:nth-child(-n+2){ background:#d9963a; }
  .pw-meter[data-score="3"] .pw-bars i:nth-child(-n+3){ background:var(--accent); }
  .pw-meter[data-score="4"] .pw-bars i{ background:var(--good); }
  .pw-meter[data-score="1"] .pw-lbl b{ color:var(--bad); }
  .pw-meter[data-score="2"] .pw-lbl b{ color:#e0a74e; }
  .pw-meter[data-score="3"] .pw-lbl b{ color:var(--accent-lit); }
  .pw-meter[data-score="4"] .pw-lbl b{ color:var(--good); }

  .auth-hint{ display:flex; align-items:center; gap:6px; color:var(--dim-2); font-size:11.5px; padding:0 2px; }
  .auth-hint.ok{ color:var(--good); }
  .auth-hint.bad{ color:var(--bad); }
  .auth-hint .ic{ width:13px; height:13px; vertical-align:0; }

  .auth-alt{ display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap;
    margin-top:14px; padding-top:13px; border-top:1px solid var(--line); }
  .auth-alt button{ display:inline-flex; align-items:center; gap:6px; min-height:0; padding:0; border:0;
    background:none; box-shadow:none; color:var(--dim); font:inherit; font-size:12.5px; font-weight:600; cursor:pointer; }
  .auth-alt button:hover{ color:var(--accent-lit); background:none; filter:none; }
  .auth-alt .ic{ width:14px; height:14px; vertical-align:0; }
  /* quiet reassurance under the fold of the card */
  .auth-trust{ display:flex; align-items:center; justify-content:center; gap:7px; margin-top:16px;
    color:var(--dim-2); font-size:11.5px; text-align:center; }
  .auth-trust .ic{ width:13px; height:13px; color:var(--accent); vertical-align:0; }
  .or-sep{ display:flex; align-items:center; gap:12px; color:var(--dim-2); font-size:11px;
           text-transform:uppercase; letter-spacing:.12em; margin:18px 0; }
  .or-sep::before,.or-sep::after{ content:""; flex:1; height:1px; background:var(--line); }
  .google-btn{ display:inline-flex; align-items:center; justify-content:center; gap:10px; width:100%; height:44px;
               background:#fff; color:#1f1f1f; font-weight:600; font-size:14.5px; border-radius:var(--r-sm);
               transition:filter var(--t); }
  .google-btn:hover{ filter:brightness(.94); text-decoration:none; }
  .google-btn .g{ display:inline-flex; align-items:center; justify-content:center; width:20px; height:20px;
                  border-radius:50%; background:#fff; color:#4285F4; font-weight:800;
                  font-family:Arial,sans-serif; border:1px solid #e0e0e0; font-size:13px; }

  /* First-run checklist. It sits above every tab rather than on the home page
     alone, because the step you have not done yet is the one you need pointed
     at from wherever you have wandered to. Each row is a real query against the
     account (see db.onboardingState), so it cannot congratulate you for
     something you did not do, and it retires itself once the list is done. */
  .gs{ margin:0 0 22px; border-radius:var(--r-lg); background:var(--surface);
       border:1px solid color-mix(in srgb,var(--accent) 26%,var(--line));
       box-shadow:var(--sh-1),var(--edge); overflow:hidden; }
  .gs-head{ display:flex; align-items:center; gap:13px; padding:14px 16px 13px; }
  .gs-head > .ic{ flex:none; width:19px; height:19px; color:var(--accent); }
  .gs-htx{ flex:1; min-width:0; }
  .gs-htx b{ display:block; font-size:14.5px; letter-spacing:-.01em; }
  .gs-htx span{ color:var(--dim); font-size:12.5px; }
  .gs-count{ flex:none; color:var(--dim); font-size:12px; font-variant-numeric:tabular-nums; }
  .gs-bar{ flex:none; width:76px; height:5px; border-radius:var(--pill); background:var(--inset);
           overflow:hidden; }
  .gs-bar i{ display:block; height:100%; border-radius:var(--pill); background:var(--accent);
             transition:width .45s var(--ease); }
  .gs-x{ flex:none; width:30px; height:30px; min-height:0; padding:0; background:transparent;
         color:var(--dim); border:0; box-shadow:none; }
  .gs-x:hover{ background:rgba(0,0,0,.25); color:var(--txt); filter:none; }
  .gs-x .ic{ width:15px; height:15px; }

  .gs-steps{ border-top:1px solid var(--line); }
  .gs-step{ display:flex; align-items:flex-start; gap:12px; padding:13px 16px;
            border-bottom:1px solid var(--line); }
  .gs-step:last-child{ border-bottom:0; }
  .gs-mark{ flex:none; width:22px; height:22px; margin-top:1px; border-radius:50%;
            display:flex; align-items:center; justify-content:center; font-size:11.5px;
            font-weight:700; color:var(--dim); background:var(--inset);
            border:1px solid var(--line-2); }
  .gs-mark .ic{ width:12px; height:12px; }
  .gs-step.done .gs-mark{ color:var(--good); background:var(--good-wash);
                          border-color:color-mix(in srgb,var(--good) 40%,transparent); }
  .gs-tx{ flex:1; min-width:0; }
  .gs-tx b{ display:block; font-size:13.5px; margin-bottom:2px; }
  .gs-step.done .gs-tx b{ color:var(--dim); }
  .gs-tx .d{ color:var(--dim); font-size:12.5px; line-height:1.55; }
  .gs-acts{ flex:none; display:flex; flex-wrap:wrap; gap:7px; align-self:center; }
  .gs-acts button{ min-height:32px; padding:0 12px; font-size:12.5px; }
  .gs-acts .abtn{ min-height:32px; padding:0 12px; font-size:12.5px; }
  .gs-step.done .gs-acts{ display:none; }

  /* Narrow screens: the buttons drop under the text rather than squeezing it to
     a two-word column. */
  @media (max-width:720px){
    .gs-step{ flex-wrap:wrap; }
    .gs-acts{ width:100%; padding-left:34px; align-self:auto; }
    .gs-head{ flex-wrap:wrap; }
    .gs-htx{ flex:1 1 100%; order:-1; }
    .gs-bar{ flex:1; width:auto; }
  }

  /* =========================================================================
     Friends
     ========================================================================= */
  /* minmax(0,…) on every track, for the reason .deck-grid already carries: a bare
     1fr keeps the track's automatic minimum, so anything inside with a wide
     min-content stretches the grid past the viewport instead of being made to
     fit. */
  .fr-grid{ display:grid; grid-template-columns:340px minmax(0,1fr); gap:20px; align-items:start; }
  .fr-panel{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg);
             padding:18px; margin-bottom:14px; box-shadow:var(--edge),var(--sh-1); }
  .fr-panel h3{ font-size:11px; font-weight:700; letter-spacing:.12em; text-transform:uppercase;
                color:var(--dim); margin-bottom:12px; }
  .fr-code{ display:flex; align-items:center; gap:10px; }
  .fr-code code{ flex:1; font-family:ui-monospace,Menlo,Consolas,monospace; font-size:17px; font-weight:700;
                 letter-spacing:.13em; color:var(--accent-lit); background:var(--inset);
                 border:1px solid var(--line); border-radius:var(--r-sm); padding:8px 12px; text-align:center; }
  .fr-addrow{ display:flex; gap:8px; margin-top:12px; }
  /* width:0, not just min-width:0. An <input> without a `size` attribute carries
     a ~20-character intrinsic width, and a flex container's min-content is the
     SUM of its items' min-content contributions -- min-width only lets the item
     shrink once layout runs, it does not take the input out of that sum. So the
     friend-code panel had a 331px floor, and the page a 363px one: wider than a
     375pt iPhone, where Safari answers an over-wide document by shrink-to-fitting
     the whole page (fixed bottom bar included) to a scale no other tab used.
     Zeroing the width drops the panel's floor to 216px and the page's to 258px,
     which leaves room for iOS's wider system font. Same rows on Account. */
  .fr-addrow input{ flex:1; min-width:0; width:0; }
  .fr-list{ display:flex; flex-direction:column; gap:4px; }
  .fr-row{ display:flex; align-items:center; gap:11px; padding:8px; border-radius:var(--r-sm); flex-wrap:wrap; }
  .fr-row:hover{ background:var(--panel-2); }
  .fr-av{ width:34px; height:34px; border-radius:50%; object-fit:cover; flex:none; background:var(--inset);
          border:1px solid var(--line); display:flex; align-items:center; justify-content:center;
          font-size:13px; font-weight:700; color:var(--dim); }
  .fr-meta{ flex:1 1 120px; min-width:0; }
  .fr-meta .nm{ font-weight:600; font-size:13.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .fr-meta .sub{ color:var(--dim); font-size:11.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .fr-actions{ display:flex; gap:6px; margin-left:auto; }
  .unread{ display:inline-flex; align-items:center; justify-content:center; min-width:18px; height:18px;
           padding:0 5px; border-radius:var(--pill); background:var(--bad); color:#fff; font-size:10.5px; font-weight:700; }
  .fr-empty{ color:var(--dim); font-size:13px; padding:12px 4px; }
  .fr-shares{ display:flex; flex-wrap:wrap; gap:14px; }
  .fr-shares label{ display:flex; align-items:center; gap:7px; font-size:13px; }
  .fr-viewbtns{ display:flex; flex-wrap:wrap; gap:8px; }
  .fr-cards{ display:grid; grid-template-columns:repeat(auto-fill,minmax(238px,1fr)); gap:12px; }
  .fr-card{ display:flex; align-items:center; gap:12px; padding:13px; text-align:left; cursor:pointer;
            background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); color:inherit;
            min-height:0; font-weight:400; box-shadow:var(--edge),var(--sh-1);
            transition:border-color var(--t),transform var(--t); }
  .fr-card:hover{ border-color:var(--line-2); transform:translateY(-2px); filter:none; }
  .fr-card .fr-meta{ min-width:0; }
  .fr-card .fr-chev{ color:var(--dim-2); display:flex; transition:color var(--t),transform var(--t); }
  .fr-card:hover .fr-chev{ color:var(--accent-lit); transform:translateX(3px); }
  .fr-linkrow{ display:flex; gap:8px; margin-top:10px; }
  .fr-linkrow input{ flex:1; min-width:0; width:0; font-size:12px; color:var(--dim); }
  .fr-detail-head{ display:flex; align-items:center; gap:12px; margin-bottom:14px; }

  /* ---- profile banner ----------------------------------------------------
     Battlefields are the only landscape art in the catalog and they already sit
     in public/cards for the card pages, so a preset banner costs no new asset.
     What ships is the WHOLE card though — black bevel, frame rails, title plate
     and rules box — and none of that is banner material. On the 1038x744
     battlefield template the bare art window is x 44…994, y 164…426: centred
     horizontally, 39.65% of the way down. So a preset is blown up until that
     window covers the box, then hung off the window's own centre, which parks
     every piece of frame outside the box on all four sides.
       width  = max(1038/950 of the box width, 744/262·1038/744 of its height)
       shift  = half the image across, (164+262/2)/744 = 39.65% of it up
     The cq units make that a real max() of two different axes, so the crop is
     exact at whatever shape the box takes; the flat percentage before it is the
     fallback for a browser without container queries — pinned to the width and
     zoomed enough that a box down to 2.9:1 still lands inside the art. --pbn-
     bleed is a hair of overscan so no rounding leaves a hairline of frame. An
     upload is already cropped to the banner's own shape, so it just covers.
     --pbn-scrim seats whatever sits on the art (avatar, label, name). */
  /* contain:paint as well as overflow:hidden. The art inside is absolutely
     positioned, 400-500px wide inside a ~350px box, and carries a transform --
     the exact shape WebKit has long failed to clip: the overflow rule hides it
     visually, but the transformed box still counts towards the document's
     scrollable width, so an iPhone gets a sideways scroll on the only two
     surfaces that paint a banner (Friends and Account). Paint containment is
     the spec's "clip everything, transformed and out-of-flow descendants
     included", and it also makes .pbn their containing block. No effect in
     Chrome, where overflow:hidden already clips. */
  .pbn{ position:relative; overflow:hidden; contain:paint;
        background:var(--inset); container-type:size; }
  .pbn img{ width:100%; height:100%; object-fit:cover; object-position:50% 50%; display:block; }
  .pbn img.preset{ --pbn-bleed:1.02;
                   position:absolute; left:50%; top:50%; height:auto; max-width:none;
                   width:138%;
                   width:calc(max(109.2632cqw,396.1893cqh) * var(--pbn-bleed));
                   transform:translate(-50%,-39.6505%); }
  .pbn::after{ content:''; position:absolute; inset:0; pointer-events:none;
               background:var(--pbn-scrim,linear-gradient(180deg,rgba(9,12,17,0) 38%,rgba(9,12,17,.28) 70%,rgba(10,13,18,.82) 100%)); }

  .fr-profile{ max-width:760px; }
  .pr-back{ margin-bottom:14px; }
  .pr-card{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-xl);
            overflow:hidden; box-shadow:var(--edge),var(--sh-2); }
  /* The banner keeps one shape at every width instead of a height that drifts
     against it, so the art is framed the same on a phone and on a desktop.
     The explicit width matters: with a min-height in play, `aspect-ratio` alone
     would run the ratio the other way and derive an over-wide box from it. */
  .pr-banner{ width:100%; aspect-ratio:3.9; min-height:112px; max-height:210px; }
  /* the avatar rides the seam between art and card, so the two read as one
     object; the name and meta clear the art entirely and sit on the card. */
  .pr-head{ display:flex; align-items:flex-end; flex-wrap:wrap; gap:8px 16px;
            padding:0 22px 18px; margin-top:-42px; position:relative; }
  .pr-av{ flex:none; }
  .pr-av .fr-av{ width:92px; height:92px; font-size:34px; border:3px solid var(--panel-hi);
                 box-shadow:var(--sh-2); background:var(--panel-2); color:var(--txt); }
  .pr-id{ flex:1 1 200px; min-width:0; }
  .pr-id h2{ font-size:23px; letter-spacing:-.02em; line-height:1.25;
             overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  /* `dot` is taken globally by a 9px bullet, so the meta items carry their own
     name — the profile ones are text with a lead-in dot, not the dot itself. */
  .pr-meta{ display:flex; flex-wrap:wrap; align-items:center; gap:3px 14px;
            color:var(--dim); font-size:12.5px; margin-top:5px; }
  .pr-meta .pm{ display:inline-flex; align-items:center; gap:7px; white-space:nowrap; }
  .pr-meta .pm::before{ content:''; flex:none; width:6px; height:6px; border-radius:50%; background:var(--dim-2); }
  .pr-meta .pm.live{ color:var(--good); }
  .pr-meta .pm.live::before{ background:var(--good); box-shadow:0 0 0 3px color-mix(in srgb,var(--good) 22%,transparent); }
  .pr-actions{ display:flex; gap:8px; margin-left:auto; }
  .pr-body{ padding:8px 22px 22px; }
  .pr-bio{ font-size:14px; line-height:1.65; margin:0; }
  .pr-bio.muted{ color:var(--dim-2); font-style:italic; }

  .pr-stats{ display:grid; grid-template-columns:repeat(auto-fit,minmax(112px,1fr)); gap:10px; margin-top:18px; }
  .pr-stat{ background:var(--inset); border:1px solid var(--line); border-radius:var(--r-md); padding:12px 14px; }
  .pr-stat .k{ color:var(--dim-2); font-size:10.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; }
  .pr-stat .v{ font-size:19px; font-weight:700; margin-top:4px; font-variant-numeric:tabular-nums; letter-spacing:-.02em; }
  .pr-stat.val .v{ color:var(--accent-lit); }

  .pr-sec{ border-top:1px solid var(--line); padding-top:18px; margin-top:20px; }
  .pr-sec h3{ font-size:11px; font-weight:700; letter-spacing:.12em; text-transform:uppercase;
              color:var(--dim); margin-bottom:12px; }
  .pr-badges{ display:flex; flex-wrap:wrap; gap:8px; }
  .pr-badge{ display:inline-flex; align-items:center; gap:8px; padding:7px 13px; border-radius:var(--pill);
             background:var(--inset); border:1px solid var(--line); font-size:12.5px; font-weight:600; }
  .pr-badge .ic{ color:var(--accent-lit); }

  /* what they let you open — one row per shareable thing, locked rows kept in
     place (greyed) so the profile shows the whole shape of what sharing offers */
  .pr-opens{ display:flex; flex-direction:column; gap:8px; }
  .pr-open{ display:flex; align-items:center; gap:13px; width:100%; text-align:left; padding:12px 14px;
            border-radius:var(--r-md); background:var(--inset); border:1px solid var(--line);
            color:inherit; font-weight:400; min-height:0; box-shadow:none; cursor:pointer;
            transition:border-color var(--t),background var(--t),transform var(--t); }
  .pr-open:hover{ background:var(--panel-2); border-color:var(--line-2); filter:none; transform:translateX(2px); }
  .pr-open .icon-tile{ flex:none; }
  .pr-open .t{ flex:1; min-width:0; }
  .pr-open .t b{ display:block; font-size:13.5px; font-weight:650; }
  .pr-open .t span{ display:block; color:var(--dim); font-size:12px; margin-top:2px; }
  .pr-open .go{ color:var(--dim-2); display:flex; transition:color var(--t),transform var(--t); }
  .pr-open:hover .go{ color:var(--accent-lit); transform:translateX(3px); }
  .pr-open.locked{ cursor:default; opacity:.62; }
  .pr-open.locked:hover{ background:var(--inset); border-color:var(--line); transform:none; }
  .pr-open.locked .icon-tile{ background:var(--panel-2); border-color:var(--line); color:var(--dim-2); }

  /* what you send back — a switch reads as a setting, a checkbox reads as a form */
  .pr-togs{ display:flex; flex-direction:column; gap:8px; }
  .pr-tog{ display:flex; align-items:center; gap:13px; padding:11px 14px; border-radius:var(--r-md);
           background:var(--inset); border:1px solid var(--line); cursor:pointer; font-size:13.5px;
           transition:border-color var(--t); }
  .pr-tog:hover{ border-color:var(--line-2); }
  .pr-tog .t{ flex:1; min-width:0; }
  .pr-tog .t span{ display:block; color:var(--dim); font-size:12px; margin-top:2px; }
  .pr-tog input[type=checkbox]{ appearance:none; -webkit-appearance:none; position:relative; flex:none; margin:0;
    width:40px; height:23px; border-radius:var(--pill); cursor:pointer;
    background:var(--panel-2); border:1px solid var(--line-2); transition:background var(--t),border-color var(--t); }
  .pr-tog input[type=checkbox]::after{ content:''; position:absolute; top:2px; left:2px; width:15px; height:15px;
    border-radius:50%; background:var(--dim); transition:transform var(--t),background var(--t); }
  .pr-tog input[type=checkbox]:checked{ background:var(--accent-wash);
    border-color:color-mix(in srgb,var(--accent) 52%,transparent); }
  .pr-tog input[type=checkbox]:checked::after{ transform:translateX(17px); background:var(--accent-lit); }

  /* the hub cards borrow the same art, dimmed to a texture behind the name */
  .fr-card{ position:relative; overflow:hidden; }
  .fr-card .fr-card-bn{ position:absolute; inset:0; z-index:0; opacity:.20;
                        --pbn-scrim:linear-gradient(90deg,rgba(13,17,22,.62),rgba(13,17,22,.94)); }
  .fr-card:hover .fr-card-bn{ opacity:.30; }
  .fr-card > *:not(.fr-card-bn){ position:relative; z-index:1; }

  /* =========================================================================
     Account settings
     ========================================================================= */
  #viewAccount{ max-width:760px; }
  /* The account header is the same object friends see, so it's built from the
     same parts: banner, avatar on the seam, identity underneath. */
  .acc-hero{ position:relative; border:1px solid var(--line); border-radius:var(--r-xl); overflow:hidden;
             background:var(--surface); box-shadow:var(--edge),var(--sh-2); margin-bottom:16px; }
  .acc-hero .pr-banner{ aspect-ratio:5; min-height:96px; max-height:160px; }
  .acc-head{ display:flex; align-items:flex-end; flex-wrap:wrap; gap:8px 18px;
             padding:0 20px 18px; margin-top:-38px; position:relative; }
  .acc-head .who{ flex:1 1 200px; min-width:0; }
  .acc-head img,.acc-head .ph{ width:84px; height:84px; box-sizing:border-box; border-radius:50%; object-fit:cover;
                    border:3px solid var(--panel-hi); background:var(--panel-2); flex:none; box-shadow:var(--sh-2);
                    display:flex; align-items:center; justify-content:center; font-size:31px; color:var(--txt); font-weight:700; }
  .acc-avatar{ position:relative; width:84px; height:84px; flex:none; border-radius:50%; cursor:pointer;
               padding:0; border:0; background:none; box-shadow:none; display:block; min-height:0; }
  /* banner edit affordance, parked on the art itself */
  .acc-bn-edit{ position:absolute; top:12px; right:12px; z-index:2; min-height:0; padding:7px 12px;
                font-size:12.5px; background:rgba(9,12,17,.66); color:#fff; border:1px solid rgba(255,255,255,.18);
                backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px); box-shadow:none; }
  .acc-bn-edit:hover{ background:rgba(9,12,17,.85); filter:none; }
  /* preset picker: the art itself is the label, so the name rides a band of its
     own along the bottom rather than floating in the middle of the picture */
  .bn-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(168px,1fr)); gap:10px; }
  .bn-opt{ position:relative; padding:0; border:1px solid var(--line); border-radius:var(--r-md); overflow:hidden;
           cursor:pointer; background:var(--inset); min-height:0; box-shadow:none; aspect-ratio:3.1;
           transition:border-color var(--t),transform var(--t); }
  .bn-opt:hover{ border-color:var(--line-2); transform:translateY(-2px); filter:none; }
  .bn-opt .pbn{ position:absolute; inset:0; --pbn-scrim:none; }
  .bn-opt .nm{ position:absolute; left:0; right:0; bottom:0; z-index:1; text-align:left;
               padding:16px 10px 6px; font-size:11px; font-weight:600; color:#fff;
               background:linear-gradient(180deg,rgba(8,11,15,0),rgba(8,11,15,.55) 45%,rgba(8,11,15,.88));
               white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .bn-opt.on{ border-color:var(--accent); box-shadow:0 0 0 2px color-mix(in srgb,var(--accent) 38%,transparent); }
  .bn-opt.on::after{ content:''; position:absolute; top:7px; right:7px; z-index:1; width:9px; height:9px;
                     border-radius:50%; background:var(--accent-lit); box-shadow:0 0 0 3px rgba(9,12,17,.55); }
  .acc-avatar-edit{ position:absolute; inset:0; border-radius:50%; display:flex; align-items:center; justify-content:center;
                    background:rgba(6,9,13,.68); color:#fff; opacity:0; transition:opacity var(--t); }
  .acc-avatar:hover .acc-avatar-edit,.acc-avatar:focus-visible .acc-avatar-edit{ opacity:1; }
  .acc-head .who h2{ font-size:19px; display:flex; align-items:center; gap:9px; }
  .acc-head .who .em{ color:var(--dim); font-size:12.5px; margin-top:4px; }
  .acc-head .acc-photo{ margin-top:7px; }
  .acc-role{ font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.09em;
             padding:2px 8px; border-radius:var(--pill); color:var(--accent-lit);
             background:var(--accent-wash); border:1px solid color-mix(in srgb,var(--accent) 38%,transparent); }
  .acc-sec{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg);
            padding:18px 20px; margin-bottom:14px; box-shadow:var(--edge),var(--sh-1); }
  .acc-sec h3{ font-size:14px; }
  .acc-sec .desc{ color:var(--dim); font-size:12.5px; margin:5px 0 14px; line-height:1.55; }
  .acc-sec .sub{ font-size:11px; font-weight:700; letter-spacing:.11em; text-transform:uppercase; color:var(--dim); }
  .acc-row{ display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
  .acc-row input[type=text]{ flex:1; min-width:200px; }
  .acc-bio{ width:100%; resize:vertical; min-height:74px; }
  .acc-checks{ display:flex; flex-wrap:wrap; gap:16px; }
  .acc-langs{ display:flex; flex-wrap:wrap; gap:8px; }
  .acc-checks label{ display:flex; align-items:center; gap:8px; font-size:13.5px; }
  .acc-danger{ border-color:color-mix(in srgb,var(--bad) 34%,var(--line)); }
  .acc-danger h3{ color:var(--bad); }
  .acc-danger > button{ background:var(--bad); border-color:var(--bad); color:#fff;
                        box-shadow:var(--edge-lit),0 1px 1px rgba(0,0,0,.28); }
  .acc-link{ display:inline-flex; align-items:center; gap:6px; background:none; border:0; box-shadow:none;
             color:var(--dim); font-size:12.5px; cursor:pointer; padding:0; min-height:0; font-weight:600; }
  .acc-link:hover{ color:var(--accent-lit); background:none; filter:none; }
  .acc-link .ic{ width:14px; height:14px; vertical-align:0; }
  .acc-msg{ color:var(--dim); font-size:12.5px; min-height:16px; margin-top:10px; }
  .acc-ach{ display:flex; flex-direction:column; gap:8px; margin-bottom:10px; }
  .ach-row{ display:flex; gap:8px; align-items:center; }
  .ach-row .ach-prev{ width:34px; height:34px; flex:none; display:flex; align-items:center; justify-content:center;
                      border-radius:var(--r-sm); background:var(--accent-wash); color:var(--accent-lit);
                      border:1px solid color-mix(in srgb,var(--accent) 24%,transparent); }
  .ach-row .ach-prev .ic{ vertical-align:0; }
  .ach-row .ach-icon{ width:132px; flex:none; }
  .ach-row .ach-label{ flex:1; min-width:0; }
  .ach-row .ach-del{ width:32px; height:32px; min-height:0; padding:0; border:0; box-shadow:none;
                     background:none; color:var(--dim-2); }
  .ach-row .ach-del:hover{ color:var(--bad); background:var(--bad-wash); filter:none; }

  /* crop editor — one modal, two shapes: a circle for the avatar and the
     banner's own 3.9:1 frame. The mask is the only difference on screen. */
  .ave{ position:fixed; inset:0; z-index:10000; display:flex; align-items:center; justify-content:center;
        background:rgba(0,0,0,.68); padding:16px; }
  .ave-box{ background:var(--panel); border:1px solid var(--line); border-radius:var(--r-xl); padding:20px;
            width:min(380px,100%); box-shadow:var(--sh-3); }
  .ave-box.wide{ width:min(620px,100%); }
  .ave-box h3{ font-size:16px; }
  .ave-box .desc{ color:var(--dim); font-size:13px; margin:5px 0 16px; }
  .ave-stage{ position:relative; width:300px; aspect-ratio:1; max-width:100%; margin:0 auto; border-radius:var(--r-md);
              overflow:hidden; background:var(--inset); touch-action:none; cursor:grab; user-select:none; }
  .ave-stage.wide{ width:100%; aspect-ratio:3.9; }
  .ave-stage:active{ cursor:grabbing; }
  .ave-stage canvas{ display:block; width:100%; height:100%; }
  .ave-mask{ position:absolute; inset:0; pointer-events:none; border-radius:50%; box-shadow:0 0 0 9999px rgba(0,0,0,.6); }
  .ave-mask::after{ content:''; position:absolute; inset:0; border-radius:50%; border:2px solid rgba(255,255,255,.75); }
  /* the wide frame has nothing to cut away — the whole stage is the crop, so the
     mask is just an edge, plus the same scrim the real banner carries */
  .ave-stage.wide .ave-mask{ border-radius:var(--r-md); box-shadow:none; }
  .ave-stage.wide .ave-mask::after{ border-radius:var(--r-md); border-color:rgba(255,255,255,.35);
    background:linear-gradient(180deg,rgba(9,12,17,0) 38%,rgba(9,12,17,.28) 70%,rgba(10,13,18,.82) 100%); }
  .ave-zoom{ display:flex; align-items:center; gap:12px; margin:18px 2px 4px; color:var(--dim); }
  .ave-zoom input[type=range]{ flex:1; min-height:0; padding:0; background:none; border:0; accent-color:var(--accent); }
  .ave-actions{ display:flex; justify-content:flex-end; gap:8px; margin-top:16px; }

  /* =========================================================================
     Support tickets (user list on Account, owner inbox on /admin/support)
     ========================================================================= */
  /* Status colour is the whole language of the queue: amber = waiting on the
     owner, green = answered, grey = done. Same palette on both surfaces. */
  .sup-st{ font-size:10.5px; font-weight:700; text-transform:uppercase; letter-spacing:.07em;
           padding:2px 8px; border-radius:var(--pill); white-space:nowrap;
           background:var(--inset); border:1px solid var(--line); color:var(--dim); }
  .sup-st.open{ color:var(--accent-lit); border-color:color-mix(in srgb,var(--accent) 40%,var(--line)); background:var(--accent-wash); }
  .sup-st.answered{ color:var(--good); border-color:color-mix(in srgb,var(--good) 40%,var(--line)); background:var(--good-wash); }

  .sup-list{ display:flex; flex-direction:column; gap:8px; }
  .sup-row{ display:flex; align-items:center; gap:10px; width:100%; text-align:left; cursor:pointer;
            padding:11px 13px; border-radius:var(--r-sm); border:1px solid var(--line);
            background:var(--panel); color:var(--txt); min-height:0; box-shadow:none; }
  .sup-row:hover{ background:var(--panel-2); filter:none; border-color:var(--line-2); }
  .sup-row.on{ border-color:color-mix(in srgb,var(--accent) 45%,var(--line)); background:var(--accent-wash); }
  .sup-row-main{ flex:1; min-width:0; }
  .sup-row-sub{ display:block; color:var(--dim); font-size:11.5px; margin-top:3px;
                white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .sup-row b{ display:block; font-size:13px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .sup-unread{ width:8px; height:8px; border-radius:50%; background:var(--bad); flex:none; }
  .sup-empty{ color:var(--dim); font-size:13px; line-height:1.6; padding:6px 0 2px; }

  /* Thread + composer. .sup-msgs mirrors .chat-log so the .bubble rules apply. */
  .sup-thread{ margin-top:12px; border:1px solid var(--line); border-radius:var(--r-sm);
               background:var(--inset); padding:12px; }
  /* Base gap is the tight one, for bubbles inside a run; a bubble that starts a
     new run pays for its own breathing room. That keeps one speaker's messages
     visibly together instead of an evenly-spaced ladder. */
  /* A floor as well as a ceiling. Without the floor a one-message thread renders
     as a sliver barely taller than its own reply box; with only a ceiling the
     pane also jumped in height on every send. The band is narrow on purpose —
     the log is closer to a fixed pane (the DM dock's .chat is outright
     height:min(60vh,520px)) than to a box that grows with the transcript. */
  .sup-msgs{ display:flex; flex-direction:column; gap:2px;
             min-height:280px; max-height:min(46vh,420px);
             overflow-y:auto; scrollbar-gutter:stable; padding:2px; }
  .sup-msgs .bubble{ max-width:80%; }
  .sup-msgs .bubble:not(.cont){ margin-top:10px; }
  /* Sit the transcript on the floor of the pane so a short thread hugs the reply
     box and the slack opens above it, the way every chat behaves. auto margins
     only absorb *free* space, so once the log overflows this resolves to 0 and
     scrolling still reaches the first message — which justify-content:flex-end
     would not guarantee. */
  .sup-msgs .bubble:first-child{ margin-top:auto; }
  /* Squared-off inner corners along a run, so consecutive bubbles read as one
     block rather than as separate cards. */
  .sup-msgs .bubble.cont.them{ border-top-left-radius:5px; }
  .sup-msgs .bubble.cont.me{ border-top-right-radius:5px; }
  /* The name is the one place colour still does identification work, now that
     both fills carry --txt ink: brass for your side, muted for theirs. */
  .sup-msgs .bubble .who{ display:block; font-size:10px; font-weight:700; letter-spacing:.06em;
                          text-transform:uppercase; margin-bottom:3px; }
  .sup-msgs .bubble.them .who{ color:var(--dim); }
  .sup-msgs .bubble.me .who{ color:var(--accent-lit); }
  .sup-reply{ display:flex; gap:8px; margin-top:10px; align-items:flex-end; }
  .sup-reply textarea{ flex:1; min-width:0; resize:vertical; min-height:38px; }
  /* Icon-only send: square it to the textarea's height so the pair reads as one
     control instead of a text field next to a stray pill. */
  .sup-reply button{ flex:none; width:38px; min-height:38px; padding:0; }
  .sup-actions{ display:flex; gap:8px; flex-wrap:wrap; margin-top:10px; }
  .sup-form{ display:flex; flex-direction:column; gap:10px; margin-top:12px; }
  .sup-form .row{ display:flex; gap:8px; flex-wrap:wrap; }
  .sup-form input[type=text]{ flex:1; min-width:190px; }
  .sup-form textarea{ resize:vertical; min-height:96px; }

  /* Owner inbox: list left, thread right; collapses to one column on narrow. */
  #viewAdminSupport .sup-inbox{ display:grid; grid-template-columns:330px 1fr; gap:18px; align-items:start; }
  .sup-pane{ background:var(--panel); border:1px solid var(--line); border-radius:var(--r-lg);
             box-shadow:var(--edge),var(--sh-1); padding:14px; }
  .sup-scroll{ max-height:min(62vh,620px); overflow-y:auto; scrollbar-gutter:stable; }
  .sup-filters{ display:flex; gap:6px; flex-wrap:wrap; margin-bottom:12px; }
  .sup-filters button.on{ color:var(--accent-lit); background:var(--accent-wash);
                          border-color:color-mix(in srgb,var(--accent) 45%,var(--line)); }
  /* The dashboard's section rule (::after) is a flex item too — order it before
     the action button so the heading reads: label ——— [button]. */
  .dash-sec::after{ order:1; }
  .dash-sec > button{ order:2; }
  .sup-detail-head{ display:flex; align-items:flex-start; gap:12px; flex-wrap:wrap;
                    padding-bottom:12px; margin-bottom:4px; border-bottom:1px solid var(--line); }
  .sup-detail-head .who{ flex:1; min-width:0; }
  .sup-detail-head h3{ font-size:15px; margin:0 0 4px; }
  .sup-detail-head .s{ color:var(--dim); font-size:12px; }
  @media (max-width:900px){
    #viewAdminSupport .sup-inbox{ grid-template-columns:1fr; }
  }
  /* The desktop floor is generous on purpose; on a phone that much empty pane
     would push the reply box under the fold, so trade some of it back. */
  @media (max-width:640px){
    .sup-msgs{ min-height:200px; max-height:min(52vh,360px); }
    .sup-thread.from-us .sup-msgs{ max-height:min(58vh,420px); }
  }
  /* A conversation reads badly full-bleed on a wide monitor. */
  #viewFeedback .fb-panel{ max-width:840px; }

  /* --- broadcasts ---------------------------------------------------------
     A thread we opened is a different kind of object from a ticket, on both
     surfaces. The accent rail is the whole signal: it marks "this came from
     Rift-Tracker" without another badge competing with the status pill. */
  .sup-sec + .sup-sec{ margin-top:20px; }
  .sup-sec > h3{ font-size:12px; font-weight:700; letter-spacing:.07em; text-transform:uppercase;
                 color:var(--dim); margin:0 0 9px; }
  .sup-grouphead{ font-size:11px; font-weight:700; letter-spacing:.07em; text-transform:uppercase;
                  color:var(--dim); margin:14px 0 8px; }
  .sup-grouphead:first-child{ margin-top:0; }
  .sup-row.bcast{ border-left:3px solid color-mix(in srgb,var(--accent) 55%,var(--line)); }
  .sup-st.bcast{ color:var(--accent-lit); border-color:color-mix(in srgb,var(--accent) 40%,var(--line));
                 background:var(--accent-wash); }
  .sup-thread.from-us{ border-left:3px solid color-mix(in srgb,var(--accent) 55%,var(--line)); }
  /* An announcement runs longer than a ticket reply, so the log gets more room
     before it starts scrolling. */
  .sup-thread.from-us .sup-msgs{ max-height:min(56vh,560px); }

  /* Composer preview: the recipient's bubble, boxed off from the form fields so
     it reads as output rather than another input. */
  .sup-preview{ border:1px solid var(--line); border-radius:var(--r-sm); background:var(--inset); padding:10px 12px; }
  .sup-preview-lbl{ font-size:10.5px; font-weight:700; letter-spacing:.07em; text-transform:uppercase;
                    color:var(--dim); margin-bottom:8px; }
  .sup-preview .sup-msgs{ min-height:0; max-height:240px; }
  .sup-preview .sup-msgs .bubble:first-child{ margin-top:0; }

  /* =========================================================================
     Overlays: login gate, lightbox, modals
     ========================================================================= */
  .login-gate{ position:fixed; inset:0; z-index:1000; display:flex; align-items:center; justify-content:center;
               background:radial-gradient(900px 500px at 50% -12%, #1b2431, var(--bg)); padding:24px; }
  .login-card{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r-xl); padding:34px 32px;
               max-width:420px; width:100%; text-align:center; box-shadow:var(--sh-3); }
  .login-brand{ display:flex; justify-content:center; margin-bottom:22px; }
  .login-card h2{ margin:0 0 8px; font-size:19px; }
  .login-card p{ color:var(--dim); font-size:13.5px; margin:0 0 24px; line-height:1.55; }
  .login-err{ margin-top:16px; color:var(--bad); font-size:13px; }
  .login-note{ margin-top:18px; color:var(--dim-2); font-size:12px; line-height:1.6; }
  .login-note code{ background:var(--inset); border:1px solid var(--line); padding:1px 6px; border-radius:5px; }

  /* Above every other overlay: the lightbox is always opened *from* one of them
     (the detail sheet, a friend's collection popup), so it has to outrank them
     all — detail and the chat dock sit at 60, .fmodal at 70. */
  #lightbox{ position:fixed; inset:0; background:rgba(0,0,0,.88); display:none;
             align-items:center; justify-content:center; z-index:80; cursor:zoom-out; padding:24px; }
  #lightbox img{ max-height:88vh; max-width:90vw; border-radius:var(--r-md); box-shadow:var(--sh-3); }

  #detail{ position:fixed; inset:0; background:rgba(0,0,0,.76); display:none; align-items:center;
           justify-content:center; z-index:60; padding:24px; backdrop-filter:blur(4px); }
  #detail .box{ position:relative; background:var(--surface); border:1px solid var(--line); border-radius:var(--r-xl);
                max-width:780px; width:100%; max-height:90vh; overflow:auto; display:flex; gap:24px;
                padding:24px; box-shadow:var(--sh-3); }
  #detail .dart{ width:244px; flex:none; }
  #detail .dart img{ width:100%; border-radius:var(--r-md); background:var(--inset); cursor:zoom-in; display:block; }
  #detail .dart .ph{ width:100%; aspect-ratio:5/7; border-radius:var(--r-md); background:var(--inset); display:flex;
                     align-items:center; justify-content:center; color:var(--dim-2); }
  #detail .dart .ph .ic{ width:38px; height:38px; }
  /* Sealed in the inspector gets the gallery tile's treatment rather than the
     card's: the photo is contained in a square pool of light instead of filling
     a 5:7 frame, and the shadow goes on the photo alone so it traces the box
     rather than the plate behind it. */
  #detail .dart.sart .plate{ aspect-ratio:1/1; display:flex; align-items:center; justify-content:center;
                             padding:16px; border-radius:var(--r-md);
                             background:radial-gradient(78% 62% at 50% 40%,
                               color-mix(in srgb,var(--line) 62%,transparent), transparent 72%), var(--inset); }
  #detail .dart.sart .plate img{ width:100%; height:100%; object-fit:contain; background:none; border-radius:0;
                                 filter:drop-shadow(0 10px 13px rgba(0,0,0,.55)) drop-shadow(0 2px 3px rgba(0,0,0,.45)); }
  #detail .dart.sart .ph{ aspect-ratio:1/1; }
  #detail .dinfo{ flex:1; min-width:0; padding-right:26px; }
  #detail .dtitle{ font-size:21px; font-weight:700; margin:0 0 3px; }
  #detail .dsub{ color:var(--dim); font-size:13px; margin-bottom:16px; }
  #detail .dtags{ display:flex; flex-wrap:wrap; gap:6px; margin-bottom:18px; }
  #detail .props{ display:grid; grid-template-columns:auto 1fr; gap:7px 18px; font-size:13px; margin-bottom:20px; }
  #detail .props dt{ color:var(--dim); }
  #detail .props dd{ margin:0; font-variant-numeric:tabular-nums; }
  #detail .dclose{ position:absolute; top:16px; right:16px; width:32px; height:32px; min-height:0; padding:0;
                   background:transparent; color:var(--dim); border:0; box-shadow:none; border-radius:var(--r-xs); }
  #detail .dclose:hover{ background:var(--panel-2); color:var(--txt); filter:none; }
  #detail .price-hero{ font-size:28px; font-weight:750; color:var(--accent-lit); font-variant-numeric:tabular-nums;
                       letter-spacing:-.025em; display:flex; align-items:baseline; gap:9px; }
  #detail .price-hero .ph-note{ font-size:12.5px; color:var(--dim); font-weight:500; letter-spacing:0; }
  /* The hero is 28px; "No offers" there would shout. Bring it down to a label. */
  #detail .price-hero .noprice{ font-size:15px; letter-spacing:0; }
  /* 24h move, when the source reports one (TCGplayer does; CardTrader doesn't) */
  #detail .price-hero .ph-chg{ font-size:13px; font-weight:650; letter-spacing:0; }
  #detail .price-hero .ph-chg.up{ color:var(--good, #46c47c); }
  #detail .price-hero .ph-chg.down{ color:var(--bad, #e05a5a); }
  .spread{ display:flex; align-items:center; gap:18px; margin:12px 0 16px; font-size:12.5px; color:var(--dim); flex-wrap:wrap; }
  .spread b{ color:var(--txt); font-variant-numeric:tabular-nums; }
  /* "Buy this card" — one button per marketplace, sitting right under the price
     so the number and the place you can act on it read as one block. Each
     marketplace carries its own hue, tinted off one --mk custom property, so
     which side of the Atlantic you are about to open reads before the label
     does. Both stay clear of the brass accent on purpose: these are somewhere to
     go, not the modal's primary action. */
  #detail .dbuy{ display:flex; gap:8px; flex-wrap:wrap; margin:0 0 20px; }
  /* The sheet carries the switch too -- it is the one route to it that needs
     neither a hover nor the table, which is what a phone has. */
  #detail .dex{ margin:0 0 20px; }
  #detail .dex.on{ color:var(--accent-lit); border-color:var(--line-2); }
  #detail .dbuy a{ --mk:var(--info); display:inline-flex; align-items:center; gap:8px; text-decoration:none;
    padding:9px 13px; border-radius:var(--r-md); font-size:12.5px; font-weight:650; line-height:1;
    background:color-mix(in srgb, var(--mk) 20%, var(--panel));
    border:1px solid color-mix(in srgb, var(--mk) 52%, transparent);
    color:color-mix(in srgb, var(--mk) 42%, var(--txt));
    box-shadow:var(--edge);
    transition:background var(--t), border-color var(--t), color var(--t); }
  #detail .dbuy a:hover{ background:color-mix(in srgb, var(--mk) 36%, var(--panel));
    border-color:var(--mk); color:var(--txt); }
  #detail .dbuy a.eu{ --mk:#4d92dd; }   /* Cardmarket */
  #detail .dbuy a.us{ --mk:#e0813c; }   /* TCGplayer */
  #detail .dbuy a.ww{ --mk:#7fae21; }   /* eBay */
  #detail .dbuy a .ic{ width:14px; height:14px; opacity:.8; }
  /* the region rides as a chip rather than dim text — it is the fastest thing
     to scan when both buttons are present */
  #detail .dbuy a .where{ font-size:10px; font-weight:750; letter-spacing:.08em;
    padding:3px 6px; border-radius:var(--pill); color:var(--txt);
    background:color-mix(in srgb, var(--mk) 42%, transparent); }
  /* affiliate disclosure: its own line under the buttons, quiet but readable --
     it has to be legible to count as a disclosure, so it does not go below 11px */
  #detail .dbuy .aff{ flex-basis:100%; font-size:11px; line-height:1.45; color:var(--dim); }
  #detail .trend{ border-top:1px solid var(--line); padding-top:16px; }
  #detail .trend h4{ margin:0 0 10px; font-size:10.5px; text-transform:uppercase; letter-spacing:.12em; color:var(--dim); font-weight:700; }

  /* Shared by the wishlist and deck exports -- both are "here is the plain text,
     take it away", so they are one component wearing two titles. */
  .expmodal{ position:fixed; inset:0; background:rgba(0,0,0,.76); display:none; align-items:center;
             justify-content:center; z-index:60; padding:24px; backdrop-filter:blur(4px); }
  .expmodal .ebox{ background:var(--panel); border:1px solid var(--line); border-radius:var(--r-xl); width:100%;
                   max-width:580px; max-height:86vh; display:flex; flex-direction:column; box-shadow:var(--sh-3); }
  .expmodal .ehead{ display:flex; align-items:center; gap:8px; padding:16px 18px; border-bottom:1px solid var(--line); }
  /* the heading takes the slack, so any number of actions sit flush right */
  .expmodal .ehead .etitle{ margin-right:auto; min-width:0; }
  .expmodal .ehead h3{ font-size:15px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .expmodal .ehead .esub{ color:var(--dim); font-size:11.5px; margin-top:2px; }
  .expmodal .ecopy.ok{ background:var(--good); border-color:var(--good); color:#04240d; }
  .expmodal .eclose{ width:32px; height:32px; min-height:0; padding:0; background:transparent;
                     color:var(--dim); border:0; box-shadow:none; }
  .expmodal .eclose:hover{ background:var(--panel-2); color:var(--txt); filter:none; }
  .expmodal textarea{ flex:1; min-height:300px; resize:none; margin:16px 18px 18px; background:var(--inset);
                      font:12.5px/1.7 ui-monospace,Menlo,Consolas,monospace; white-space:pre; overflow:auto; }

  /* --- share: the wishlist behind a link --------------------------------
     Shorter than the export box because there is nothing to scroll: a
     sentence about what publishing means, the link, and a mock of the card
     that chat apps will draw from it. */
  .sbody{ overflow-y:auto; padding:16px 18px 18px; display:flex; flex-direction:column; gap:13px; }
  .s-lead{ color:var(--dim); font-size:13px; line-height:1.6; }
  .s-lead b{ color:var(--txt); font-weight:600; }
  .s-note{ color:var(--dim-2); font-size:12px; line-height:1.6; }
  .s-note b{ color:var(--dim); font-weight:600; }
  .s-act{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
  .s-act .sp{ margin-left:auto; }
  /* The preview mock. Every chat client draws this differently, so this copies
     none of them exactly -- it is the accent rail, the title and the list,
     which is the part that is actually ours to control. Rendered from the
     wishlist already in memory, by the same rules the server uses, so what it
     shows is what gets pasted rather than a guess. */
  .s-prev{ display:flex; gap:11px; padding:12px 14px; border-radius:var(--r-md);
           background:var(--inset); border:1px solid var(--line); }
  .s-prev::before{ content:""; flex:none; width:4px; border-radius:2px; background:var(--accent); }
  .s-prev-in{ min-width:0; flex:1 1 auto; }
  .s-prev-site{ font-size:11px; color:var(--dim-2); }
  .s-prev-title{ margin-top:3px; font-size:13.5px; font-weight:650; color:var(--info); }
  .s-prev-body{ margin-top:6px; font-size:12.5px; line-height:1.55; color:var(--dim);
                white-space:pre-wrap; overflow-wrap:anywhere; }
  .s-prev-empty{ color:var(--dim-2); font-style:italic; }

  /* --- import: load a list or a CSV file ---------------------------------
     Wider than the export boxes because the preview is the point: every row
     shows what the file said next to what it resolved to, and a name that has
     to wrap twice is a row nobody checks. */
  .impmodal .ebox{ max-width:820px; }
  .impmodal .ibody{ overflow-y:auto; padding:16px 18px; display:flex; flex-direction:column; gap:14px; }
  .impmodal .ifoot{ display:flex; align-items:center; gap:10px; padding:14px 18px;
                    border-top:1px solid var(--line); flex-wrap:wrap; }
  .impmodal .ifoot .sp{ margin-left:auto; }
  .impmodal .ifoot .imp-hint{ font-size:12px; }
  /* The drop zone is a <button>, so it has to shed the app's button styling
     before it can look like a target you drag onto. */
  .imp-drop{ display:flex; flex-direction:column; align-items:center; justify-content:center; gap:7px;
             width:100%; min-height:186px; padding:28px 24px; text-align:center; cursor:pointer;
             font:inherit; color:var(--dim); background:var(--inset); box-shadow:none;
             border:1px dashed var(--line); border-radius:var(--r-md);
             transition:border-color .15s, background .15s; }
  .imp-drop:hover{ border-color:var(--accent); background:var(--accent-wash); filter:none; }
  .imp-drop.over{ border-color:var(--accent); background:var(--accent-wash); }
  .imp-drop .ic{ width:26px; height:26px; color:var(--accent); }
  .imp-drop b{ color:var(--txt); font-size:15px; font-weight:650; }
  .imp-drop .d{ font-size:12.5px; line-height:1.55; max-width:42ch; }
  .imp-drop.busy{ pointer-events:none; opacity:.65; }
  .imp-hint{ color:var(--dim); font-size:12.5px; line-height:1.65; }
  .imp-hint code{ background:var(--inset); padding:1px 5px; border-radius:4px; font-size:11.5px; }
  .imp-err{ color:var(--bad); font-size:13px; }

  /* summary strip above the preview */
  .imp-sum{ display:flex; flex-wrap:wrap; gap:8px; }
  .imp-chip{ display:inline-flex; align-items:center; gap:6px; padding:5px 10px; border-radius:999px;
             background:var(--inset); border:1px solid var(--line); font-size:12.5px; color:var(--dim); }
  .imp-chip b{ color:var(--txt); font-weight:650; }
  .imp-chip.good{ border-color:color-mix(in srgb,var(--good) 40%,var(--line)); }
  .imp-chip.warn{ border-color:color-mix(in srgb,var(--bad) 45%,var(--line)); color:var(--bad); }
  .imp-chip.warn b{ color:var(--bad); }

  /* the preview list. Fixed row height and a scroll cap: a 500-card import must
     not turn the modal into a page you scroll past to reach its own button. */
  .imp-rows{ border:1px solid var(--line); border-radius:var(--r-md); overflow:auto;
             max-height:min(46vh,380px); background:var(--inset); }
  .imp-row{ display:grid; grid-template-columns:44px minmax(0,1fr) 16px minmax(0,1.15fr);
            align-items:center; gap:10px; padding:7px 12px; border-top:1px solid var(--line); font-size:12.5px; }
  .imp-row:first-child{ border-top:0; }
  .imp-row .q{ color:var(--dim); text-align:right; font-variant-numeric:tabular-nums; }
  .imp-row .src{ color:var(--dim); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .imp-row .arw{ color:var(--dim-2); }
  .imp-row .dst{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .imp-row .dst b{ font-weight:600; }
  .imp-row .dst .m{ color:var(--dim); }
  .imp-row.bad{ background:color-mix(in srgb,var(--bad) 9%,transparent); }
  .imp-row.bad .dst{ color:var(--bad); }
  .imp-row.flag{ background:color-mix(in srgb,var(--bad) 5%,transparent); }
  .imp-tag{ display:inline-block; margin-left:6px; padding:1px 6px; border-radius:999px; font-size:10.5px;
            background:var(--panel-2); color:var(--dim); vertical-align:1px; }
  .imp-tag.foil{ color:var(--accent-lit); background:var(--accent-wash); }
  .imp-tag.warn{ color:var(--bad); background:color-mix(in srgb,var(--bad) 14%,transparent); }
  .imp-more{ padding:8px 12px; color:var(--dim); font-size:12px; border-top:1px solid var(--line); }

  /* merge vs replace. Replace can empty a shelf, so it says so in the label
     rather than in a confirm() the user clicks through on reflex. */
  .imp-mode{ display:flex; flex-direction:column; gap:8px; }
  .imp-mode label{ display:flex; gap:10px; align-items:flex-start; padding:10px 12px; cursor:pointer;
                   border:1px solid var(--line); border-radius:var(--r-md); background:var(--inset); }
  .imp-mode label:has(input:checked){ border-color:var(--accent); background:var(--accent-wash); }
  .imp-mode input{ margin-top:2px; flex:none; }
  .imp-mode .t{ font-size:13px; font-weight:600; }
  .imp-mode .d{ color:var(--dim); font-size:12px; line-height:1.55; margin-top:2px; }
  @media (max-width:560px){
    .imp-row{ grid-template-columns:34px minmax(0,1fr); gap:4px 8px; }
    .imp-row .arw{ display:none; }
    .imp-row .dst{ grid-column:2; }
  }

  .fmodal{ position:fixed; inset:0; background:rgba(0,0,0,.7); display:flex; align-items:center;
           justify-content:center; z-index:70; padding:20px; backdrop-filter:blur(4px); }
  .fmodal-box{ background:var(--panel); border:1px solid var(--line); border-radius:var(--r-xl); width:min(980px,96vw);
               max-height:88vh; display:flex; flex-direction:column; overflow:hidden; box-shadow:var(--sh-3); }
  .fmodal-head{ display:flex; align-items:center; gap:10px; padding:14px 16px; border-bottom:1px solid var(--line); }
  .fmodal-head b{ flex:1; min-width:0; font-size:15px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .fmodal-head .field{ flex:0 1 240px; }
  .fmodal-body{ overflow-y:auto; scrollbar-gutter:stable; padding:16px; }
  .fmodal-body .fm-count{ color:var(--dim); font-size:12.5px; margin-bottom:12px; }
  .fmodal-body .gcard.ro{ cursor:default; }
  .fmodal-body .deck-tile{ cursor:pointer; }

  /* =========================================================================
     Chat: dock, threads, toasts
     ========================================================================= */
  .chat{ display:flex; flex-direction:column; height:min(60vh,520px); }
  .chat-log{ flex:1; overflow-y:auto; scrollbar-gutter:stable; display:flex; flex-direction:column; gap:8px; padding:6px 4px; }
  /* Bubbles are panels, not swatches. Both sides get the same material — a soft
     vertical gradient, a hairline border and the top-edge highlight the rest of
     the app's surfaces use — and differ only in hue. The outgoing side used to be
     a solid brass slab, which turned any thread longer than two messages into a
     wall of yellow; it is now a brass *tint* over the panel colour, so the text
     stays --txt and the colour reads as an accent rather than as a highlighter. */
  .bubble{ max-width:75%; padding:9px 13px; border-radius:15px; font-size:13.5px; line-height:1.45;
           word-wrap:break-word; white-space:pre-wrap; border:1px solid transparent; }
  .bubble-row{ display:flex; align-items:flex-end; gap:7px; align-self:flex-start; max-width:85%; }
  .bubble-row .bubble{ max-width:100%; }
  .chat-av{ width:26px; height:26px; border-radius:50%; object-fit:cover; flex:none; cursor:pointer;
            border:1px solid var(--line); background:var(--inset); display:flex; align-items:center; justify-content:center;
            font-size:11px; font-weight:700; color:var(--dim); }
  .chat-av:hover{ border-color:var(--accent); }
  .bubble.them{ align-self:flex-start; border-bottom-left-radius:5px;
                background:linear-gradient(180deg,var(--panel-2),var(--panel));
                border-color:var(--line-2); box-shadow:var(--edge),var(--sh-1); }
  .bubble.me{ align-self:flex-end; border-bottom-right-radius:5px; color:var(--txt);
              background:linear-gradient(180deg,
                color-mix(in srgb,var(--accent) 26%,var(--panel)),
                color-mix(in srgb,var(--accent) 13%,var(--panel)));
              border-color:color-mix(in srgb,var(--accent) 38%,var(--line));
              box-shadow:inset 0 1px 0 color-mix(in srgb,var(--accent) 20%,transparent),var(--sh-1); }
  /* The timestamp rides on whatever ink the bubble uses, so opacity beats a
     fixed colour here — it stays legible on both fills. */
  .bubble .t{ display:block; font-size:10px; opacity:.55; margin-top:4px; }
  .chat-day{ align-self:center; font-size:11px; color:var(--dim-2); margin:8px 0; }
  .chat-form{ display:flex; gap:8px; margin-top:10px; }
  .chat-form input{ flex:1; min-width:0; }
  .chat-form button{ width:var(--ctrl-h); padding:0; flex:none; }
  .chat-form button .ic{ vertical-align:0; }

  #chatDock{ position:fixed; right:22px; bottom:22px; z-index:60; display:flex; flex-direction:column; align-items:flex-end; gap:12px; }
  #chatFab{ width:46px; height:46px; border-radius:var(--r-md); padding:0; position:relative;
            background:var(--panel-2); color:var(--accent-lit); border:1px solid var(--line-2); box-shadow:var(--sh-2); }
  #chatFab:hover{ background:var(--panel); color:var(--accent-lit); filter:none; border-color:var(--accent); }
  #chatFab .ic{ width:21px; height:21px; vertical-align:0; }
  #chatFab .tab-badge{ position:absolute; top:-5px; right:-5px; box-shadow:0 0 0 2px var(--bg); }
  #chatPanel{ width:min(348px,calc(100vw - 40px)); height:min(480px,calc(100vh - 130px));
              background:var(--panel); border:1px solid var(--line); border-radius:var(--r-lg); overflow:hidden;
              display:flex; flex-direction:column; box-shadow:var(--sh-3); }
  .cd-head{ display:flex; align-items:center; gap:8px; padding:12px; border-bottom:1px solid var(--line); }
  .cd-head #cdTitle{ font-weight:700; font-size:14px; flex:1; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .cd-iconbtn{ width:30px; height:30px; min-height:0; padding:0; background:transparent; color:var(--dim);
               border:0; box-shadow:none; border-radius:var(--r-xs); }
  .cd-iconbtn:hover{ color:var(--txt); background:var(--panel-2); filter:none; }
  #cdBody{ flex:1; overflow:hidden; display:flex; flex-direction:column; min-height:0; }
  .cd-empty{ color:var(--dim); font-size:13px; padding:20px 16px; text-align:center; line-height:1.6; }
  .cd-list{ overflow-y:auto; scrollbar-gutter:stable; flex:1; padding:6px; }
  .cd-conv{ display:flex; align-items:center; gap:10px; padding:9px 10px; cursor:pointer; border-radius:var(--r-sm); }
  .cd-conv:hover{ background:var(--panel-2); }
  .cd-conv .fr-av{ width:36px; height:36px; }
  .cd-conv-meta{ flex:1; min-width:0; }
  .cd-conv-top{ display:flex; justify-content:space-between; gap:8px; }
  .cd-conv-top .nm{ font-weight:600; font-size:13.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .cd-conv-top .tm{ color:var(--dim-2); font-size:11px; flex:none; }
  .cd-prev{ color:var(--dim); font-size:12.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .cd-conv .unread{ flex:none; }
  .cd-chat{ height:100%; padding:10px 12px 12px; box-sizing:border-box; }
  .cd-chat .chat-log{ padding:4px 0; }

  #toasts{ position:fixed; top:16px; right:16px; z-index:9999; display:flex; flex-direction:column; gap:10px;
           width:min(348px, calc(100vw - 32px)); pointer-events:none; }
  .toast{ pointer-events:auto; display:flex; gap:12px; align-items:flex-start; padding:12px 13px;
          border-radius:var(--r-md); border:1px solid var(--line-2); background:var(--panel);
          box-shadow:var(--sh-2); transform:translateX(120%); opacity:0;
          transition:transform .3s var(--ease), opacity .3s; }
  .toast.in{ transform:translateX(0); opacity:1; }
  .toast .toast-ic{ width:36px; height:36px; border-radius:50%; flex:none; display:flex; align-items:center;
                    justify-content:center; background:var(--accent-wash); color:var(--accent-lit);
                    border:1px solid color-mix(in srgb,var(--accent) 24%,transparent); }
  .toast .toast-ic .ic{ width:17px; height:17px; vertical-align:0; }
  .toast .fr-av{ flex:none; }
  .toast-main{ min-width:0; flex:1; }
  .toast-title{ font-weight:650; font-size:13.5px; line-height:1.35; }
  .toast-body{ color:var(--dim); font-size:12.5px; margin-top:3px; overflow:hidden; text-overflow:ellipsis;
               display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
  .toast-actions{ display:flex; gap:6px; margin-top:10px; }
  .toast-actions button{ min-height:28px; padding:0 12px; font-size:12px; border-radius:var(--r-xs);
                         border:1px solid var(--line); background:transparent; box-shadow:none; color:var(--txt); font-weight:600; }
  .toast-actions button.primary{ background:var(--accent); border-color:var(--accent); color:var(--accent-ink); }
  .toast-actions button:hover{ border-color:var(--line-2); background:var(--panel-2); filter:none; }
  /* one composed step up, matching what brightness(1.06) does to the primary
     everywhere else — a jump straight to --accent-lit read as a different button */
  .toast-actions button.primary:hover{ background:color-mix(in srgb,var(--accent-lit) 40%,var(--accent));
                                       border-color:color-mix(in srgb,var(--accent-lit) 40%,var(--accent)); }
  .toast .toast-x{ flex:none; width:24px; height:24px; min-height:0; padding:0; border:0; background:none;
                   box-shadow:none; color:var(--dim-2); border-radius:var(--r-xs); }
  .toast .toast-x:hover{ color:var(--txt); background:var(--panel-2); filter:none; }

  /* themed scrollbars on the dense scrolling surfaces */
  .chat-log,.cd-list,#cdBody,.fmodal-body,.results,.bresults,.table-wrap,.dash-table-wrap,
  .sup-msgs,.sup-scroll{
    scrollbar-width:thin; scrollbar-color:var(--line-2) transparent; }
  .chat-log::-webkit-scrollbar,.cd-list::-webkit-scrollbar,#cdBody::-webkit-scrollbar,
  .fmodal-body::-webkit-scrollbar,.results::-webkit-scrollbar,.bresults::-webkit-scrollbar,
  .table-wrap::-webkit-scrollbar,.dash-table-wrap::-webkit-scrollbar,
  .sup-msgs::-webkit-scrollbar,.sup-scroll::-webkit-scrollbar{ width:10px; height:10px; }
  .chat-log::-webkit-scrollbar-thumb,.cd-list::-webkit-scrollbar-thumb,#cdBody::-webkit-scrollbar-thumb,
  .fmodal-body::-webkit-scrollbar-thumb,.results::-webkit-scrollbar-thumb,.bresults::-webkit-scrollbar-thumb,
  .table-wrap::-webkit-scrollbar-thumb,.dash-table-wrap::-webkit-scrollbar-thumb{
    background:var(--line-2); border-radius:var(--pill); border:3px solid var(--panel); }
  /* Same thumb, but these two sit on a well rather than on a panel, so the
     spacer ring has to match the surface behind them or it shows as a seam. */
  .sup-msgs::-webkit-scrollbar-thumb{ background:var(--line-2); border-radius:var(--pill);
                                      border:3px solid var(--inset); }
  .sup-scroll::-webkit-scrollbar-thumb{ background:var(--line-2); border-radius:var(--pill);
                                        border:3px solid var(--panel); }

  /* ---- card tilt ----------------------------------------------------------
     Placed here, after the card styles, so it beats the :hover lift rules it
     stands in for at equal specificity. Applied by the pointer handler to the
     bordered box around the art, so frame, shadow and art all move together.
     Every card tilts, foil or not — the foil layers are a separate thing that
     rides on the same pointer tracking.
     Only a real mouse ever sets it (see cardTracking), so a touchscreen can't
     leave a card stuck at an angle — which is why this isn't gated on
     (pointer:fine), a query that reports the *primary* input and would miss a
     mouse plugged into a touchscreen laptop.

     The hero fan is deliberately absent: its cards already fan apart on hover,
     and a second transform on the same element would just fight that one. It
     still gets the sheen, only not the tilt.

     .gcard and .hcard .fr already transition transform for their hover lift;
     only the detail art needs one added, and re-declaring the shorthand on the
     other two would drop their border/shadow transitions. */
  #detail .dart{ transition:transform .35s var(--ease); }
  .gcard.holo-tilt,.hcard .fr.holo-tilt,#detail .dart.holo-tilt{
    transform:perspective(820px) rotateX(var(--rx)) rotateY(var(--ry));
    transition:transform .07s linear;
  }
  @media (prefers-reduced-motion:reduce){
    .gcard.holo-tilt,.hcard .fr.holo-tilt,#detail .dart.holo-tilt{ transform:none; }
  }

  /* ---- score tracker ------------------------------------------------------
     A chess clock for points. The screen splits in two, each player owns a
     half, and tapping your half scores you one — first to 8 takes the game.
     It is an overlay rather than a tab because it has to cover the bottom tab
     bar and the top bar, and because it holds a screen wake lock for as long
     as it is open: a game runs for minutes between taps and the phone must not
     lock in the middle of one (see keepAwake).

     Everything here is drawn out of the design tokens rather than hand-picked
     colour, so the tracker reads as the same product as the rest of the app: a
     half is a panel surface with the ambient wash the tokens already use, the
     two players are the brand's brass and the system's info blue, and the
     controls are the app's own .ghost / .iconbtn buttons with no restyling.

     Portrait stacks the halves and rotates the far one 180deg so it reads
     right-way-up from the other side of the table; landscape splits them left
     and right, where both players already read them upright. The control bar
     is absolutely centred rather than a flex row, so the same markup sits on
     the divider in either orientation. */
  #scoreT{
    position:fixed; inset:0; z-index:90; display:flex; flex-direction:column;
    background:var(--bg); overscroll-behavior:none; touch-action:manipulation;
    -webkit-user-select:none; user-select:none;
    /* iOS: the notch/home-indicator gutters, so nothing lands under them */
    padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
  }
  /* stop the page behind from scrolling (and from pull-to-refreshing) under it */
  body.score-on{ overflow:hidden; overscroll-behavior:none; }

  /* A side is the flex child; the tappable half fills it and the take-one-back
     button sits on top (a <button> cannot nest inside a <button>). Rotating the
     side rather than the half turns that button with it, so each player's
     controls read the right way up from their own seat. */
  .sc-side{ flex:1 1 0; min-height:0; min-width:0; position:relative; overflow:hidden; }
  .sc-a{ --pc:var(--accent); --pc-lit:var(--accent-lit); transform:rotate(180deg); }
  .sc-b{ --pc:var(--info);   --pc-lit:color-mix(in srgb,var(--info) 55%,#e4efff);
         border-top:1px solid var(--line); }

  /* The half is a <button> so it answers a keyboard and a screen reader too,
     not just a thumb — which means unwinding most of the base button styling.
     Its face is the panel surface plus the same kind of low-percentage radial
     wash body::before lays over the whole app, tinted to the player's colour.
     Because the far half is rotated, both surfaces are lit from the divider,
     which turns the seam between them into one continuous panel edge. */
  .sc-half{
    position:absolute; inset:0; width:100%; min-height:0;
    display:flex; flex-direction:column; align-items:center; justify-content:center; gap:12px;
    padding:20px; border:0; border-radius:0; white-space:normal;
    color:var(--txt); font:inherit; letter-spacing:normal; -webkit-tap-highlight-color:transparent;
    background:
      radial-gradient(680px 380px at 50% 44%, color-mix(in srgb,var(--pc) 10%,transparent), transparent 72%),
      var(--surface);
    box-shadow:var(--edge);
    transition:background .25s var(--ease);
  }
  /* the base button's hover brighten and active nudge would fight the rotation
     and the win state, so both are cancelled here */
  .sc-half:hover{ filter:none; }
  .sc-half:active,.sc-half:disabled{ transform:none; opacity:1; cursor:default; }

  .sc-who{ font-size:10.5px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
           color:var(--pc-lit); }
  .sc-n{ font-size:clamp(64px,21vh,168px); line-height:.9; font-weight:750;
         font-variant-numeric:tabular-nums; letter-spacing:-.03em; color:var(--txt); }
  /* the tap feedback: the number itself kicks, so a score registers even when
     you are looking at the board and not the phone */
  .sc-n.bump{ animation:scBump .22s var(--ease); }
  @keyframes scBump{ 0%{ transform:scale(1) } 38%{ transform:scale(1.14) } 100%{ transform:scale(1) } }
  /* eight pips, built like the app's .dot swatches: the target is readable at a
     glance without printing "of 8" anywhere */
  .sc-pips{ display:flex; gap:7px; }
  .sc-pips i{ width:9px; height:9px; border-radius:50%; background:var(--line-2);
              box-shadow:0 0 0 1px rgba(255,255,255,.16) inset;
              transition:background .18s var(--ease); }
  .sc-pips i.on{ background:var(--pc-lit); }
  /* one slot under the pips, so swapping the hint for the win badge can't shift
     the number above it */
  .sc-foot{ display:flex; align-items:center; justify-content:center; min-height:22px; }
  .sc-hint{ font-size:12px; color:var(--dim-2); }

  /* won: the wash comes up and the number takes the player's colour */
  .sc-half.won{
    background:
      radial-gradient(680px 380px at 50% 44%, color-mix(in srgb,var(--pc) 24%,transparent), transparent 74%),
      var(--surface);
  }
  .sc-half.won .sc-n{ color:var(--pc-lit); }
  /* the app's eyebrow, in the winner's colour and on its wash */
  .sc-win{ display:inline-flex; align-items:center; gap:7px; padding:3px 11px; border-radius:var(--pill);
           font-size:10.5px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
           color:var(--pc-lit); background:color-mix(in srgb,var(--pc) 15%,transparent);
           border:1px solid color-mix(in srgb,var(--pc) 30%,transparent); }
  .sc-win .ic{ width:14px; height:14px; vertical-align:0; }

  /* The take-one-back button, in each half's OUTER corner — far from both the
     middle of the half (where the scoring thumb lands) and the control bar. In
     portrait the 180deg rotation maps both to the same corner of their own
     half, which puts them diagonally opposite on screen. */
  .sc-undo{ position:absolute; bottom:14px; right:14px; z-index:1; }

  /* ---- the control bar, floating on the divider ---- */
  .sc-bar{ position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); z-index:2;
           display:flex; flex-direction:column; gap:8px; padding:8px; max-width:min(92vw,360px);
           border-radius:var(--r-lg); background:var(--surface);
           border:1px solid var(--line); box-shadow:var(--edge),var(--sh-2); }
  .sc-row{ display:flex; align-items:center; gap:7px; }
  /* reset asks once: the first press arms it, the second inside 3s does it — a
     confirm() dialog mid-game is worse than a button that changes its mind.
     Armed is the app's own quiet-destructive hover state, held on. */
  #scReset.armed{ background:var(--bad-wash); color:var(--bad);
                  border-color:color-mix(in srgb,var(--bad) 42%,transparent); }
  /* The wake-lock light. Deliberately NOT a button — it reports, it doesn't do,
     and sitting in a row of real buttons was reading as one. Working is the
     quiet state (a panel chip with a lit bolt, no louder than the controls it
     sits beside); the loud one is reserved for the case that actually needs
     you, which is the screen NOT being held. */
  .sc-awake{ display:inline-flex; align-items:center; gap:6px; flex:none;
             height:var(--ctrl-h); padding:0 11px; border-radius:var(--r-sm);
             font-size:12px; font-weight:600; white-space:nowrap;
             background:var(--panel); color:var(--dim); border:1px solid var(--line);
             box-shadow:var(--edge); }
  .sc-awake .ic{ width:15px; height:15px; vertical-align:0; color:var(--accent-lit); }
  .sc-awake.off{ background:var(--bad-wash); color:var(--bad);
                 border-color:color-mix(in srgb,var(--bad) 42%,transparent); box-shadow:none; }
  .sc-awake.off .ic{ color:var(--bad); }

  /* Only shown when we could NOT hold the screen awake, so nothing intrudes
     while everything is working. It lives INSIDE the bar rather than floating
     under it: the bar is the one box centred on the divider, so anything it
     grows by pushes into both halves equally and can never land on a number. */
  .sc-warn{ text-align:center; font-size:12px; line-height:1.5; color:var(--dim);
            background:var(--inset); border:1px solid var(--line);
            border-radius:var(--r-md); padding:7px 11px; }

  @media (orientation:landscape){
    #scoreT{ flex-direction:row; }
    .sc-a{ transform:none; }               /* side by side reads upright to both */
    .sc-b{ border-top:0; border-left:1px solid var(--line); }
    /* nothing is rotated here, so the far half's button has to be moved by hand
       to keep both of them on the outer edges */
    .sc-a .sc-undo{ right:auto; left:14px; }
    .sc-n{ font-size:clamp(56px,26vh,168px); }
    .sc-bar{ max-width:min(70vw,420px); }
  }
  /* the bar is the one thing that must never overflow a narrow phone */
  @media (max-width:400px){
    .sc-bar{ padding:6px; }
    .sc-row{ gap:5px; }
    .sc-awake{ padding:0 9px; }
  }
  @media (prefers-reduced-motion:reduce){
    .sc-n.bump{ animation:none; }
  }

  /* =========================================================================
     Responsive
     ========================================================================= */
  @media (max-width:1180px){
    :root{ --side-w:210px; --main-pad:24px; }
    .fr-grid{ grid-template-columns:300px minmax(0,1fr); }
  }
  @media (max-width:1024px){
    /* Between the tablet break and the phone break the rail is still eating
       210px, leaving ~600px of content. Trimming the deck search keeps the
       filter group (search + four chips) inside one line instead of letting
       "Needs cards" shrink into an ellipsis. */
    .deck-tools .colsearch{ width:190px; }
    .fr-grid{ grid-template-columns:minmax(0,1fr); }
    /* stretch, not flex-start: a column flex container sizes items to their
       content on the cross axis, so the table's min-width was widening the
       whole page instead of scrolling inside .table-wrap */
    .layout{ flex-direction:column; align-items:stretch; }
    .filters{ width:100%; position:static; }
    .filters .fgroup{ border-bottom:1px solid var(--line); }
    .dash-two{ grid-template-columns:minmax(0,1fr); }
    .home-feats{ grid-template-columns:repeat(2,1fr); }
    .hgrid{ grid-template-columns:repeat(4,minmax(0,1fr)); }
    .signin-split{ grid-template-columns:1fr; gap:24px; }
    .signin-split .ss-tx p{ max-width:none; }
  }
  /* below this the rail becomes a bottom tab bar and a slim top bar appears */
  @media (max-width:860px){
    /* Nothing in the phone layout is meant to scroll the PAGE sideways -- every
       surface that scrolls horizontally on purpose (.table-wrap, .dt-scroll)
       owns its own scroller. So anything that reaches past the viewport here is
       a bug, and clipping it beats letting it drag the whole document wider.
       Two rules, because they fail differently. On body, `clip` and NOT `hidden`:
       hidden would make body a scroll container, which forces overflow-y to auto
       and re-anchors every position:sticky header to body instead of the
       viewport. But `overflow:clip` is Safari 16+, and on anything older the
       declaration is dropped whole, leaving no guard at all -- so html carries a
       `hidden` that every engine understands. On the ROOT that is safe: the value
       propagates to the viewport and the root itself computes back to visible, so
       the scrollport every sticky header and fixed rail resolves against does not
       move. */
    html{ overflow-x:hidden; }
    body{ overflow-x:clip; }
    .app,body.anon .app{ grid-template-columns:minmax(0,1fr); }
    .side{
      position:fixed; top:auto; left:0; right:0; bottom:0; height:auto; z-index:60;
      flex-direction:row; align-items:center; gap:4px; padding:6px 8px calc(6px + env(safe-area-inset-bottom));
      border-right:0; border-top:1px solid var(--line);
      background:color-mix(in srgb,var(--rail) 94%,transparent);
      backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
    }
    body.anon .side{ display:none; }
    .side-brand,.side-val,.scanner-link{ display:none; }
    /* min-width:0 all the way down, or six labels ("Dashboard" is the widest)
       refuse to compress and push the account chip off a 360px screen */
    .side-nav{ flex:1; min-width:0; flex-direction:row; gap:2px; }
    .side-nav a{ flex:1 1 0; min-width:0; height:46px; min-height:46px; flex-direction:column;
                      justify-content:center; gap:3px; padding:0 2px; font-size:9.5px; letter-spacing:.02em; }
    .side-nav a .lbl{ display:block; max-width:100%; overflow:hidden;
                           text-overflow:ellipsis; white-space:nowrap; }
    .side-nav a.on::before{ left:50%; top:0; bottom:auto; width:18px; height:2px;
                                 transform:translateX(-50%); border-radius:0 0 2px 2px; }
    .side-nav a .tab-badge{ position:absolute; top:4px; right:calc(50% - 17px); margin:0; }
    #tabFeedback{ display:none; }   /* lives in .mobar on narrow screens */
    .side-foot{ margin:0; padding:0; flex:none; }
    .side-foot .langpick{ display:none; }
    .mobar .langpick{ flex:none; }
    .userchip{ width:44px; height:44px; padding:0; justify-content:center; }
    .userchip #userName,.userchip .uc-gear{ display:none; }
    .scan-cta{ display:inline-flex; }
    .mo-val .ts-src{ display:none; }
    .mobar{ display:flex; align-items:center; gap:10px; padding:10px 16px;
            border-bottom:1px solid var(--line); background:color-mix(in srgb,var(--bg) 88%,transparent);
            backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px); position:sticky; top:0; z-index:30; }
    :root{ --main-pad:16px; }
    main{ padding:20px var(--main-pad) 104px; }
    #chatDock{ right:16px; bottom:76px; }
    .page-head h2{ font-size:21px; }
    .page-head .ph-actions{ width:100%; margin-left:0; }
    /* The results dropdown is ~400px wide and hangs off the input's RIGHT edge
       -- on a desktop page head the search sits at the end of the row, so
       leftwards is the only direction with room. On a phone the input shares
       its row with Sync / Import / Export, which puts that right edge in the
       middle of the row and opens the whole menu off the left of the screen.
       Give the search a line of its own: the input is then as wide as the page
       head, the dropdown matches it, and both land on screen. */
    .add-search{ flex:1 0 100%; width:auto; min-width:0; }
    .home-links,.home-feats{ grid-template-columns:1fr; }
    /* stack the hero and let the fan lead, sized off the viewport not the column */
    .home{ gap:28px; }
    .hero{ grid-template-columns:1fr; gap:8px; padding:14px 0 0; }
    .hero-art{ order:-1; }
    .cf{ width:min(158px,30vw); }
    .home-tag{ max-width:none; }
    .home-search{ max-width:none; }
    .hgrid{ grid-template-columns:repeat(3,minmax(0,1fr)); gap:14px 10px; }
    .signin-split{ padding:22px 18px; }
    #detail .box{ flex-direction:column; padding:18px; gap:18px; }
    #detail .dart{ width:180px; margin:0 auto; }
    #detail .dinfo{ padding-right:0; }
    th,td{ padding:9px 11px; }
    .gallery{ grid-template-columns:repeat(auto-fill,minmax(136px,1fr)); gap:12px; }
    .key{ gap:8px; font-size:12px; }
    .key .p{ min-width:40px; }
    .dash-grid{ grid-template-columns:repeat(2,1fr); gap:10px; }
    .tile .v{ font-size:19px; }
    /* Folds to a 2-up grid, the lead keeping the whole first row. display:contents
       dissolves the group so its stats become grid items in their own right --
       the grouping only exists to control how the desktop row wraps. */
    .statband{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:16px 14px; }
    .sb-rest{ display:contents; }
    .sb-lead{ grid-column:1 / -1; }
    .sb-item .v{ font-size:18px; }
    .sb-lead .v{ font-size:27px; }
    /* minmax(0,…), not 1fr: a bare 1fr keeps the track's automatic minimum, so
       one long deck name stretched the grid past the viewport */
    .deck-grid{ grid-template-columns:minmax(0,1fr); }
    .dcards,.bchosen,.bresults.open{ grid-template-columns:repeat(auto-fill,minmax(110px,1fr)); }
    .dcards.bf{ grid-template-columns:1fr; }
    .deck-detail .dsummary{ grid-template-columns:repeat(2,1fr); }
    .toolbar .colsearch{ width:100%; }
    .toolbar .field{ flex:1 1 100%; }
    .toolbar .toggle{ margin-left:0; }
    /* A right-aligned huddle of four 30px chips is not a tap target. On a phone
       the range picker takes the full width as four equal segments, above both
       the summary chart and the one in the card sheet. */
    .linechart .rangetog{ margin-bottom:12px; }
    .linechart .rangetog .toggle{ flex:1 1 auto; }
    .linechart .rangetog button{ flex:1 1 0; min-height:32px; padding:0 4px; }
    /* Four actions will not sit on a phone row. Edit and Freeze keep their
       labels because they are the ones you come here to press; Rename and
       Delete keep only their icons (and their title text). */
    .act-min .blbl{ display:none; }
    .act-min{ padding:0 11px; }
    /* the sticky group header clears the mobile top bar */
    .deck-group .dg-head{ --dg-top:52px; }
    .dstats{ gap:18px; padding:11px 12px; }
    .dstat b{ font-size:15.5px; }
    /* On a phone the search takes the full width and the two chip/control rows
       each become their own horizontal scroller. They bleed to the CARD's edge
       -- they live inside its border -- where the old rule bled by --main-pad,
       i.e. to the PAGE edge, pushing the row out through the rounded corner. */
    .deck-tools{ display:block; padding:11px 0; }
    .dt-filter{ display:block; }
    /* margin, NOT padding: .field is the positioning context for its own search
       icon and clear button, and those anchor to its PADDING box -- padding here
       would slide the input across without moving the icon sitting on top of it. */
    .dt-find{ margin:0 12px 10px; }
    .deck-tools .colsearch{ width:100%; }
    .dt-scroll{ display:flex; align-items:center; gap:8px; flex-wrap:nowrap; padding:0 12px;
                overflow-x:auto; scrollbar-width:none; }
    .dt-scroll::-webkit-scrollbar{ display:none; }
    .dt-scroll > *{ flex:none; }
    .dt-arrange{ margin-top:10px; }
    .fold-all{ margin-left:0; }
    .filters .fpanel-head{ cursor:pointer; }
    .filters .fchev{ display:flex; transition:transform var(--t); }
    .filters .fgroup,.filters .reset{ display:none; }
    .filters.open .fgroup{ display:block; }
    .filters.open .reset{ display:flex; }
    .filters.open .fchev{ transform:rotate(180deg); }
    .filters .fpanel-head{ border-bottom:0; }
    .filters.open .fpanel-head{ border-bottom:1px solid var(--line); }
  }
  /* Four items on one bar — brand, value, EU/US switch, scan — stop fitting
     around here; both marks read on their own, so the words are what go. The
     cut-off is set by the widest bar this can hold: a six-figure total beside
     the wordmark and the "Scan" label needs about 600px. */
  @media (max-width:600px){
    .mo-brand .bm-wm{ display:none; }
    .mo-scan{ padding:0 9px; font-size:0; gap:0; }
  }
  /* Between these widths the table is still a table, so let it keep its shape
     and scroll inside .table-wrap; below 700px it becomes cards instead. */
  @media (min-width:701px) and (max-width:860px){
    .table-wrap table{ min-width:640px; }
  }
  /* A profile head is an avatar, a name and two buttons on one line; below this
     the three of them stop fitting, so they stack and the buttons go full width.
     Stacking flips the flex axis, so every `flex:… <basis>` set for the row has
     to be cleared here or the basis lands on the height instead. */
  @media (max-width:640px){
    /* squarer art, since a stacked head no longer sits beside it */
    .pr-banner{ aspect-ratio:2.9; min-height:0; }
    .pr-head{ flex-direction:column; align-items:flex-start; gap:10px; padding:0 16px 16px; margin-top:-40px; }
    .pr-av .fr-av{ width:76px; height:76px; font-size:28px; }
    .pr-id{ flex:0 0 auto; width:100%; }
    .pr-id h2{ font-size:20px; }
    .pr-actions{ width:100%; margin-left:0; }
    .pr-actions button{ flex:1; justify-content:center; }
    .pr-body{ padding:6px 16px 20px; }
    .pr-stats{ grid-template-columns:repeat(2,1fr); }
    .acc-hero .pr-banner{ aspect-ratio:3.6; }
    .acc-head{ flex-direction:column; align-items:flex-start; gap:12px; padding:0 16px 16px; }
    .acc-head .who{ flex:0 0 auto; width:100%; }
    .free-band{ padding:16px; }
    .free-band .fb-items{ gap:8px 18px; }
  }
  /* The deck's stats are one thing shown two ways: an inline panel where there
     is room for it, and a sheet behind a floating button where there isn't.
     Never both — 861px is the same line the sidebar folds on. */
  @media (min-width:861px){ #deckStatsFab{ display:none; } }
  @media (max-width:860px){ #deckStatsPanel{ display:none; } }
  /* In the sheet the boxes get a column each rather than a third of a phone. */
  @media (max-width:700px){ .dkstats{ grid-template-columns:1fr; gap:12px; } }
  /* On a phone the sheet is only as wide as the screen, so nine columns have to
     give up their gaps before the bars get too thin to see. */
  @media (max-width:520px){
    .dkbox{ padding:14px 14px 15px; }
    .dkhead{ margin-bottom:13px; }
    .dkpills{ margin-left:0; width:100%; }
    .dkplot{ margin-inline:-2px; }
    .dkcol{ gap:7px; }
    .dktrack{ height:104px; }
    .dkbar{ left:2px; right:2px; }
    .dkband{ gap:7px 16px; }
  }
  @media (max-width:520px){
    .gallery,.dcards,.bchosen,.bresults.open{ grid-template-columns:1fr 1fr; }
    .dcards.bf{ grid-template-columns:1fr; }
    .fmodal-head .field{ display:none; }
    .fr-cards{ grid-template-columns:1fr; }
  }
  /* Mobile Safari zooms the page in whenever a field it focuses renders text
     under 16px, and it does not zoom back out when the field is left -- so one
     tap on any search box leaves the rest of the app oversized. The fix is the
     font size, not `maximum-scale`, which would also take away deliberate
     pinch-zoom. Touch pointers get 16px everywhere a caret can land; mice keep
     the tighter sizes. Listed one by one because the sizes below are set by
     equally specific rules earlier in this sheet. */
  @media (pointer:coarse){
    input,select,textarea,
    .home-search input,
    .auth-form .field > input,
    .fr-linkrow input{ font-size:16px; }
  }
  @media (prefers-reduced-motion:reduce){
    *,*::before,*::after{ animation-duration:.001ms !important; transition-duration:.001ms !important; }
  }
