/* WINGED — the shell around the match: title screen, collection, settings,
   card detail. The board itself lives in index.html; this is everything that
   makes it feel like a game you launched rather than a page you loaded. */

/* ---------------- title screen ----------------
   THE FRONT DOOR HAS TO FIT, AND WHEN IT CANNOT IT HAS TO SCROLL.
   Nine entries, a wordmark, an account strip and a footer came to 896.6px of
   content, and this screen was `justify-content:center` over `overflow:hidden`.
   Measured in a 1000x600 window before the fix: the wordmark started at
   y=-148.3px and the footer ended at y=748.3px — clipped at BOTH ends, and
   centring is what made the overflow unreachable, because the part above the
   fold sits at a negative offset no scrollbar can ever reach. It only ever
   looked fine because it was built on a tall window; every button added since
   (PLAY A FRIEND, THE CIRCUIT) pushed it further past a size nobody re-tested.
   Issue #143.

   Two fixes, and it needs both — either alone still fails:
   1. The vertical rhythm is fluid in `vh`, so the whole screen fits a 570px
      window (896.6px -> 556px of content, signed in) rather than needing ~900px.
      Every value clamps to exactly what it was before, so a tall window is
      unchanged — but "tall" means ~1080px+, and BETWEEN there the screen really
      does tighten: at 1000px it is 12px button padding rather than 13px, and a
      16px screen margin rather than 24px. That is the point, and it is worth
      saying out loud because the obvious reading of "clamped to the old values"
      is that nothing moved until it had to, which is not true.
   2. `.tinner` is capped at the height of the screen and scrolls itself, so
      when the content genuinely cannot fit — a 400px window, a phone in
      landscape — every button is reachable instead of merely off-screen. The
      scroller is `.tinner` and NOT `#title` on purpose: `.tbg`/`.tveil` are
      absolutely positioned against `#title`, so scrolling `#title` would drag
      the background art up the screen and expose flat `#05080b` beneath it.

   UPDATE, issue #389. The #143 ramp was tuned against a 570px floor, but
   nothing re-measured it once THE CIRCUIT and the account strip (`.wgstrip`,
   account.js) landed on top of the original nine rows: a real MacBook browser
   window (~700-900px of actual page viewport once toolbar/tab chrome is
   subtracted, not the ~900px outer window size playtesters read off the OS)
   still needed `.tinner` to scroll — measured at 700px, .tinner carried
   768px of content, and at 900px it still carried 64px more than it had
   room for. That is exactly the "looks broken, not intentional" case #389
   was filed against.

   A plain lower MIN does nothing here — read `.tbtn`'s old padding rule,
   `clamp(7px,1.2vh,13px)`: at 700px, `1.2vh` is already 8.4px, comfortably
   above even a 5px floor, so tightening the floor alone leaves the rendered
   value untouched. And a plain lower coefficient (`0.85vh` instead of
   `1.2vh`) fixes 700px but pushes the height at which the rule reaches its
   13px MAX from ~1083px out past 1500px — the exact regression a first pass
   at this fix shipped and this comment is now warning off: measured at
   1080px, button padding dropped to 9.18px, a visible shrink of the "tall
   window unchanged" screen the #143 comment above promises.

   So every ramp below (button padding, primary-button padding, the menu and
   `.tinner` gaps, the tagline/footer/account-strip margins) uses the
   wordmark's two-point technique instead of a bare coefficient:
   `clamp(MIN, calc(A*vh - Bpx), MAX)`, with A and B solved so the curve
   passes through the OLD rule's own value at 1080px (not just its MAX —
   several of these, e.g. `.tinner`'s gap and the tagline margin, were still
   short of their MAX at 1080px under the old coefficient, so matching MAX
   would have been a regression in the other direction) and through a new,
   materially smaller value at 700px. Below 700px the same line keeps
   falling until the MIN catches it; above 1080px it is pinned at the old
   MAX, same as before. A 1080px+ window is therefore still pixel-identical
   post-#389, and 700-900px is measurably tighter without a new mechanism.

   Separately, `btnFriend`'s label was found wrapping onto two lines whenever
   the guest-only "Sign in to play a friend" aside was present (`.tbtn-why`,
   filled by `paintFriendEntry` in menu.js) — the label was a bare text node
   with no `white-space:nowrap` of its own, so IT was the flex item that gave
   way once the aside claimed its width, not the aside next to it. Wrapped in
   `.lbl` and given `flex-wrap` on `.tbtn` so the aside drops to its own row
   below the (never-wrapping) title instead of breaking the title mid-word;
   this alone was worth a full line of height in the single most common
   (signed-out) menu state. */
#title{position:fixed;inset:0;z-index:150;display:none;flex-direction:column;align-items:center;justify-content:center;
  padding:clamp(8px,1.6vh,24px) 24px;overflow:hidden;background:#05080b}
#title.on{display:flex}
#title .tbg{position:absolute;inset:-2%;background:#05080b center/cover no-repeat;
  background-image:var(--board,none);filter:saturate(.9);
  animation:tdrift 38s ease-in-out infinite alternate}
@keyframes tdrift{0%{transform:scale(1.06) translate(-1%,0)}100%{transform:scale(1.14) translate(1%,-1.5%)}}
#title .tveil{position:absolute;inset:0;
  background:radial-gradient(ellipse at 50% 42%,rgba(4,7,10,.24),rgba(3,5,8,.9) 72%),
             linear-gradient(180deg,rgba(3,5,8,.78),rgba(3,5,8,.35) 34%,rgba(3,5,8,.92))}
#title .tinner{position:relative;z-index:2;display:flex;flex-direction:column;align-items:center;
  gap:clamp(1px,calc(0.8263vh - 2.99px),6px);max-width:520px;width:100%;
  max-height:100%;overflow-y:auto;overscroll-behavior:contain}

/* The wordmark is the largest single block on the screen, and the old rule was
   width-only — so it grew to its 86px maximum on a wide window that had no
   vertical room for it at all. It now answers to height as well, on a ramp
   anchored at both ends rather than a plain `Nvh`:
     570px tall -> 39.9px   the cramped case, where 20px of type is 20px of menu
     820px tall -> 85.9px   full size, so a 1440x900 laptop is NOT shrunk
   A plain coefficient cannot do both — `7vh` fits 570 and then leaves a
   1440x900 laptop at 63px, a visibly smaller logo than the one that shipped.
   `min()` keeps the original width guard, so a narrow window still wins. */
.wordmark{font-size:clamp(32px,min(9vw,calc(18.4vh - 65px)),86px);letter-spacing:clamp(10px,2.4vw,22px);font-weight:normal;margin:0 0 0 clamp(10px,2.4vw,22px);
  background:linear-gradient(180deg,#fff8e2,var(--gold-l) 26%,var(--gold) 54%,#9d7526 78%,#6d4f18);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 4px 18px rgba(0,0,0,.95)) drop-shadow(0 0 34px rgba(224,185,95,.28));
  line-height:1.05;text-align:center}
.wordrule{display:flex;align-items:center;gap:11px;width:min(340px,86%);margin:2px 0 clamp(2px,0.6vh,4px);opacity:.9}
.wordrule::before,.wordrule::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,transparent,var(--gold-d),var(--gold),var(--gold-d),transparent)}
.wordrule i{width:7px;height:7px;transform:rotate(45deg);background:linear-gradient(135deg,var(--gold-l),var(--gold-d));box-shadow:0 0 10px rgba(224,185,95,.7)}
.tagline{font-size:clamp(11px,1.5vw,13px);letter-spacing:5px;opacity:.5;text-transform:uppercase;
  margin-bottom:clamp(4px,calc(2.9053vh - 11.94px),22px);text-align:center}

.tmenu{display:flex;flex-direction:column;gap:clamp(3px,calc(1.2632vh - 4.64px),9px);width:min(310px,84%)}
.tbtn{position:relative;display:flex;flex-wrap:wrap;align-items:center;gap:12px;width:100%;padding:clamp(5px,calc(1.8447vh - 6.96px),13px) 18px;border-radius:10px;cursor:pointer;
  font-family:inherit;font-size:15px;letter-spacing:2.5px;text-align:left;color:var(--cream);font-weight:normal;text-shadow:0 1px 3px rgba(0,0,0,.9);
  background:linear-gradient(180deg,rgba(28,40,50,.9),rgba(12,19,26,.94));
  border:1px solid rgba(224,185,95,.3);box-shadow:0 5px 18px rgba(0,0,0,.6),inset 0 1px 0 rgba(255,255,255,.07);
  transition:transform .13s,border-color .13s,box-shadow .13s}
/* `.lbl` used to be a bare text node, and a flex item with no intrinsic
   min-width shrinks-to-fit before it wraps — so on btnFriend, once `.tbtn-why`
   claimed its "Sign in to play a friend" width, the LABEL "PLAY A FRIEND" was
   what broke across two lines, not the aside it was fighting with (issue
   #389). `flex-wrap:wrap` above plus `flex-basis:100%` here forces the aside
   onto its own second row instead, so the title never wraps mid-word. */
.tbtn .lbl{white-space:nowrap;flex:none}
.tbtn-why:not(:empty){flex-basis:100%;margin-left:34px;margin-top:-2px}
/* The icon, not the label, sets each row's height: the emoji's line box is
   24.5px against a 15px label, so nine rows carry ~85px of emoji. It shrinks
   with the screen for the same reason the padding does. */
.tbtn .ic{font-size:clamp(13px,2.1vh,18px);width:22px;text-align:center;flex:none;filter:drop-shadow(0 2px 3px rgba(0,0,0,.8))}
.tbtn:hover{transform:translateX(4px);border-color:var(--gold);box-shadow:0 0 22px rgba(224,185,95,.22),0 5px 18px rgba(0,0,0,.6)}
.tbtn.primary{padding:clamp(6px,calc(2.1842vh - 7.59px),16px) 18px;font-size:17px;letter-spacing:4px;color:#241a05;font-weight:bold;text-shadow:0 1px 0 rgba(255,255,255,.28);
  background:linear-gradient(180deg,#fbeec2,var(--gold) 46%,#9b7327);border-color:var(--gold-l);
  box-shadow:0 0 26px rgba(224,185,95,.34),0 6px 20px rgba(0,0,0,.7),inset 0 1px 0 rgba(255,255,255,.6)}
.tbtn.primary .ic{filter:none}
/* A refused entry says WHY, on the button. `disabled` alone reads as broken —
   and the one thing a player needs here (sign in / you are offline) is exactly
   what a greyed-out button cannot tell them. See design/pvp/README.md §4b. */
.tbtn:disabled{cursor:default;opacity:.6;filter:grayscale(.4);border-style:dashed}
.tbtn:disabled:hover{transform:none;border-color:rgba(224,185,95,.3);box-shadow:0 5px 18px rgba(0,0,0,.6),inset 0 1px 0 rgba(255,255,255,.07)}
.tbtn-why{margin-left:auto;font-style:normal;font-size:10.5px;letter-spacing:.9px;opacity:.8;text-transform:none;white-space:nowrap}
.tbtn-why:empty{display:none}
.tfoot{margin-top:clamp(4px,calc(2.8684vh - 10.98px),20px);display:flex;align-items:center;gap:10px;font-size:11px;opacity:.45;letter-spacing:1px}
/* Below 660px the screen is out of room and something has to go. This goes
   first: 26.4px, and the wordmark two hand-spans above it already reads WINGED.
   It goes before any way IN to the game, and before nine buttons are cramped to
   34px rows to keep a byline.
   The build number used to live ONLY here, so hiding this made it unreadable on
   a short screen (issue #144). It now also lives in SETTINGS (#setBuild), filled
   from the same ASSET_VERSION. Do NOT answer a missing build by un-hiding this
   and pushing the account strip off instead — that is the trade issue #143
   already rejected. */
@media (max-height:660px){.tfoot{display:none}}
/* Deliberately its own row, not folded into `.tfoot` above — issue #390. It
   carries the one piece of `.tfoot`'s old content that is legally required
   (Privacy & Imprint, issue #90) plus the build string, so it must survive
   the 660px cutoff `.tfoot` does not: no entry in the media query above, on
   purpose. Same visual weight as `.tfoot` (11px, low opacity) so it still
   reads as a footer, not a second menu button. */
.tmini{margin-top:clamp(2px,calc(1.4px + 0.6vh),10px);display:flex;align-items:center;gap:8px;font-size:11px;opacity:.55;letter-spacing:1px}
.tmini .lk{color:var(--gold);cursor:pointer;text-decoration:underline;text-underline-offset:2px;
  background:none;border:0;padding:0;margin:0;font:inherit;letter-spacing:inherit;opacity:1}
.tmini .lk:hover,.tmini .lk:focus-visible{color:var(--gold-l)}
.tacct{margin-top:clamp(3px,calc(1.6526vh - 5.97px),14px);display:flex;align-items:center;gap:9px;font-size:12px;padding:clamp(4px,0.8vh,8px) 14px;border-radius:9px;
  background:rgba(8,13,18,.7);border:1px solid rgba(224,185,95,.16)}
.tacct b{color:var(--gold-l);font-weight:normal}
/* Also a <button> since issue #127 — the level segment is the door to the ladder,
   so it has to be tabbable and announced as an action, not a styled span. The
   resets are what stop the UA button chrome (grey face, border, 13.3px system
   font) landing in the middle of a 12px strip. */
.tacct .lk{color:var(--gold);cursor:pointer;text-decoration:underline;text-underline-offset:2px;
  background:none;border:0;padding:0;margin:0;font:inherit;letter-spacing:inherit}
.tacct button.lk:hover,.tacct button.lk:focus-visible{color:var(--gold-l)}
/* The reward a fast click nearly threw away (issue #126). Its own line under the
   record, because it is about ONE duel and everything left of it is lifetime.
   `display:block` INSIDE the record span rather than a third flex item: as a
   sibling it needed flex-wrap on `.tacct`, and wrap stopped the two existing
   items shrinking — measured at 390px, the ⚑ ended up alone on the first line. */
.tacctrw{display:block;margin-top:3px;color:var(--gold-l);opacity:.92}
.tacctrw b{color:var(--gold)}

/* ---------------- generic full screen (collection / settings) ---------------- */
.screen{position:fixed;inset:0;z-index:155;display:none;flex-direction:column;background:rgba(4,7,10,.97)}
.screen.on{display:flex}
.screen .shead{display:flex;align-items:center;gap:14px;padding:11px 18px;flex:none;
  background:linear-gradient(180deg,rgba(10,15,20,.96),rgba(8,12,16,.72));border-bottom:1px solid var(--line)}
.screen .shead h2{margin:0;font-size:15px;letter-spacing:5px;font-weight:normal;
  background:linear-gradient(180deg,var(--gold-l),var(--gold) 50%,#a57d2c);-webkit-background-clip:text;background-clip:text;color:transparent}
.screen .shead .sp{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.screen .sbody{flex:1 1 auto;min-height:0;overflow-y:auto;padding:16px 18px 26px}
.screen .sbody::-webkit-scrollbar{width:8px}
.screen .sbody::-webkit-scrollbar-thumb{background:#3a4b57;border-radius:4px}

/* ---------------- rarity ----------------
   ONE palette, used by the collection border, the filter chips, the tally and
   the inspector. Rarity in WINGED says who you are looking at, never how strong
   the card is — Common is the land, Uncommon is an animal, Rare is somebody.
   The ladder, the per-card reasoning and the measurements live at the top of
   menu.js.

   Every rung carries a SHAPE as well as a colour (● ◆ ★ ✦ ✹) and a letter
   (C U R FA SR), for the same reason the board carries redundant shape cues
   further down this file: colour alone is not an accessible cue. */
:root{
  --r-common:#8d9aa5;   --r-common-d:#39434c;
  --r-uncommon:#dfeaf2; --r-uncommon-d:#5d6f7d;
  --r-rare:#f2cd6e;     --r-rare-d:#8a6a25;
  --r-fullart:#d29bf5;  --r-fullart-d:#5b2c78;
  --r-secret:#ffe9a8;   --r-secret-d:#7a5a12;
}
.r-common{--rc:var(--r-common);--rcd:var(--r-common-d)}
.r-uncommon{--rc:var(--r-uncommon);--rcd:var(--r-uncommon-d)}
.r-rare{--rc:var(--r-rare);--rcd:var(--r-rare-d)}
.r-fullart{--rc:var(--r-fullart);--rcd:var(--r-fullart-d)}
.r-secret{--rc:var(--r-secret);--rcd:var(--r-secret-d)}
.ccount .ctal span,.chip.rchip{color:var(--rc)}

/* ---------------- collection ---------------- */
.cfilters{display:flex;gap:7px;flex-wrap:wrap;align-items:center;margin-bottom:14px}
.chip{padding:5px 12px;border-radius:20px;font-size:11.5px;letter-spacing:.6px;cursor:pointer;
  background:rgba(20,30,38,.85);border:1px solid rgba(224,185,95,.2);color:var(--cream);transition:.13s}
.chip:hover{border-color:var(--gold)}
.chip.on{background:linear-gradient(180deg,var(--gold-l),var(--gold));color:#241a05;font-weight:bold;border-color:var(--gold-l)}
.chip.rchip{border-color:color-mix(in srgb,var(--rc) 45%,transparent)}
.chip.rchip:hover{border-color:var(--rc)}
.chip.rchip.on{background:linear-gradient(180deg,var(--rc),var(--rcd));color:#0b1016;border-color:var(--rc);text-shadow:0 1px 0 rgba(255,255,255,.25)}
.ccount{margin-left:auto;font-size:11.5px;opacity:.62;display:flex;align-items:center;gap:10px}
.ccount .ctal{display:flex;gap:9px;font-size:11.5px;letter-spacing:.4px}
.cgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:13px}

/* ---------------- rarity legend ----------------
   Michael: "I dont get the rarrity (C, R, U in the grid) somehow it makes no
   sence." He was right twice over. The ladder was never stated anywhere a
   player could read it — and the rule it stated was "how much of the set's
   identity the image carries", a judgement invisible from outside the source
   file, which no player could derive, predict or check.

   Both halves are now fixed. The rungs were re-derived onto a rule a player can
   apply to a card they have never seen (menu.js has the argument and the
   numbers), and this prints it. The one sentence — "Common is the land,
   Uncommon is an animal, Rare is somebody" — sits in the TOGGLE, so it is read
   even when the panel is folded, which is its default on a phone. The body
   splits the ladder by its TWO AXES, because "C U R" and "FA SR" being
   independent scales rather than one five-step ladder is the part that makes
   the whole thing click. Collapsible, and it remembers. */
.rlegend{margin:0 0 15px;border-radius:11px;overflow:hidden;
  background:linear-gradient(180deg,rgba(16,24,32,.85),rgba(10,16,22,.72));
  border:1px solid rgba(224,185,95,.16)}
.rltoggle{display:flex;align-items:center;gap:9px;width:100%;padding:9px 13px;cursor:pointer;
  font-family:inherit;font-size:12px;letter-spacing:1.1px;color:var(--gold-l);text-align:left;
  background:none;border:0}
.rltoggle:hover{background:rgba(224,185,95,.06)}
/* The whole ladder in one sentence, and it lives in the toggle so it is on
   screen even when the legend is folded — which is the default on a phone.
   Set larger than the surrounding chrome: if a player reads one line on this
   screen, this is the line it has to be. */
.rltoggle .rlrule{font-size:14.5px;letter-spacing:.2px;line-height:1.35;text-align:left;
  color:var(--gold-l);padding:2px 0}
@media (max-width:760px){ .rltoggle .rlrule{font-size:12.5px} }
.rltoggle .rlq{font-style:normal;width:16px;height:16px;border-radius:50%;flex:none;display:grid;place-items:center;
  font-size:11px;font-weight:bold;background:linear-gradient(180deg,var(--gold-l),var(--gold));color:#241a05}
.rltoggle .rlchev{margin-left:auto;font-style:normal;opacity:.6;transition:transform .16s}
.rlegend.shut .rltoggle .rlchev{transform:rotate(-90deg)}
.rlegend.shut .rlbody{display:none}
/* THE ONE-TIME LOOK, the same bargain Learn to Play makes: say it once, on the
   first visit, then get out of the way for ever. `.firstlook` is set on the
   first open of the collection and the flag is written in the same breath
   (menu.js LEGEND_SEEN), so this can play exactly once per browser.
   It breathes rather than flashes, and it stops on its own after three passes —
   a highlight that never ends is just a border, and one that ends only on click
   punishes the player who read it and moved on. `prefers-reduced-motion` gets
   the ring with no animation, which is the same information at rest. */
.rlegend.firstlook{border-color:rgba(224,185,95,.62);
  animation:rlbreathe 1.7s ease-in-out 3}
@keyframes rlbreathe{
  0%,100%{box-shadow:0 0 0 0 rgba(224,185,95,0)}
  50%{box-shadow:0 0 0 4px rgba(224,185,95,.17),0 0 22px rgba(224,185,95,.2)}}
@media (prefers-reduced-motion:reduce){.rlegend.firstlook{animation:none}}
.rlbody{padding:2px 13px 13px;display:flex;flex-wrap:wrap;gap:13px 26px}
.rlnote{flex:1 0 100%;margin:0 0 2px;font-size:11.5px;line-height:1.55;opacity:.74}
.rlnote b{color:var(--gold-l);font-weight:normal}
.rlcol{flex:1 1 250px;min-width:0}
.rlcol h4{margin:0 0 7px;font-size:10px;letter-spacing:2px;text-transform:uppercase;
  font-weight:normal;color:var(--gold-l);opacity:.85}
.rlcol h4 span{display:block;margin-top:3px;letter-spacing:.3px;text-transform:none;
  font-size:10.5px;opacity:.6;color:var(--cream)}
.rlcol ul{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:6px}
.rlcol li{display:grid;grid-template-columns:14px 22px minmax(62px,auto) 1fr;align-items:baseline;gap:8px;font-size:11.5px}
.rlcol .rlmark{font-style:normal;color:var(--rc);text-align:center}
.rlcol .rlshort{font-size:9.5px;font-weight:bold;text-align:center;border-radius:4px;padding:1px 0;color:#0b1016;
  background:linear-gradient(180deg,var(--rc),var(--rcd));text-shadow:0 1px 0 rgba(255,255,255,.28)}
.rlcol .rlname{color:var(--rc)}
.rlcol .rlblurb{opacity:.62;line-height:1.45}
@media (max-width:760px){
  .rlcol li{grid-template-columns:14px 22px 1fr;row-gap:1px}
  .rlcol .rlblurb{grid-column:3/-1}
}

/* The border IS the rarity — same job the printed keyline does. Kind moved to
   the small tag below, because a collection screen exists to show rarity. */
.ccard{position:relative;cursor:pointer;border-radius:10px;overflow:hidden;transition:transform .15s,box-shadow .15s;
  background:linear-gradient(160deg,var(--rcd),var(--rc) 34%,#ffffff 50%,var(--rc) 62%,var(--rcd));padding:2.5px;
  box-shadow:0 5px 16px rgba(0,0,0,.7)}
.ccard.r-common{background:linear-gradient(160deg,#333c45,#7d8b96 40%,#aab6c0 50%,#69757f 60%,#2c343c)}
.ccard:hover{transform:translateY(-5px) scale(1.03);
  box-shadow:0 0 22px color-mix(in srgb,var(--rc) 45%,transparent),0 10px 24px rgba(0,0,0,.8)}
.ccard .ci{border-radius:8px;overflow:hidden;background:#0e151b}

/* rarity stamp, top-right: mark + letter, so it reads without colour */
.ccard .crar{position:absolute;top:-4px;right:-4px;z-index:4;display:flex;align-items:center;gap:3px;
  padding:2px 7px 2px 5px;border-radius:0 9px 0 10px;font-size:9.5px;font-weight:bold;letter-spacing:.6px;
  background:linear-gradient(180deg,var(--rc),var(--rcd));color:#0b1016;text-shadow:0 1px 0 rgba(255,255,255,.3);
  box-shadow:0 2px 7px rgba(0,0,0,.85)}
.ccard .crar i{font-style:normal;font-size:8.5px;line-height:1}
/* The chase tag: this card ALSO exists as a Full Art / Secret Rare.
   It rides the TOP of the art, inset past the cost bubble on the left and the
   rarity stamp on the right. It started at the bottom and collided with the
   Wings/Roots chips — on Loam Digger and Thunder Falcon the ribbon sat right
   through them and neither was readable. */
.ccard .cprem{position:absolute;left:25px;right:33px;top:3px;z-index:4;text-align:center;
  font-size:8px;font-weight:bold;letter-spacing:.5px;padding:2px 0;border-radius:3px;color:#150b20;
  background:linear-gradient(90deg,transparent,var(--r-fullart) 22%,#fff 50%,var(--r-fullart) 78%,transparent);
  text-shadow:0 1px 0 rgba(255,255,255,.4);white-space:nowrap;overflow:hidden}
.ccard .cprem.p-secret{color:#1d1406;
  background:linear-gradient(90deg,transparent,var(--r-secret) 18%,#fff 50%,var(--r-secret) 82%,transparent)}
.ccard.has-secret{box-shadow:0 0 18px rgba(255,233,168,.34),0 5px 16px rgba(0,0,0,.7)}
/* A travelling sheen so a chase card catches the eye while scrolling.
   Calibrated three times before anyone measured it, and all three calibrations
   were beside the point, because the band was never moving at all.

   WHY THIS IS A TRANSFORM AND NOT A BACKGROUND — the actual bug, measured.
   The sheen used to be a gradient inside a background 300% of the tile wide,
   slid with `background-position`. The shared rule set `background-size:300%
   100%`; the two per-tier rules under it then set `background:` — the
   SHORTHAND — which silently resets every background longhand it does not
   mention, `background-size` included, back to `auto`. For a linear-gradient
   `auto` means "exactly the element's size", and a percentage
   background-position resolves to (containerWidth − imageWidth) × pct, i.e.
   0 × pct = 0px at EVERY percentage. Measured on build 0.9.14: driving the
   animation by hand moved the computed position 190% → 50% → −90% while the
   painted pixels stayed byte-identical. The band was nailed to the tile. That
   is precisely what Michael kept reporting as a static shimmer, and it was
   never his eyes.

   The same shorthand reset had killed `.fafoil.fa-lite::after` further down,
   which is why Full Art never shimmered anywhere. The Secret Rare detail foil
   survived for one accidental reason: it re-declares `background-size` AFTER
   its shorthand, in the same rule.

   A bar moved with `translateX` cannot fail that way — there is no shorthand
   that can reset a transform — and it rides the compositor instead of
   repainting every chase tile on every frame. The geometry is shared with the
   detail-view sheen below, so there is now ONE sheen mechanism in this file.

   A glint reads as a glint when it is roughly a sixth of the tile wide and
   there are frames with no band at all, so the bar is 26% of the tile with a
   soft-edged core and travels clear off both edges. Bright in the middle,
   tinted at the edges — pure white looks like a rendering artefact, a
   warm-to-cool shift looks like light on foil. */
.ccard.has-secret::after,.ccard.has-fullart::after,.cvhost .fasheen{
  content:"";position:absolute;top:-6%;bottom:-6%;left:0;width:26%;
  pointer-events:none;transform:translateX(-200%) skewX(-16deg);
  will-change:transform;animation:rsheen 2s linear infinite}
.ccard.has-secret::after,.ccard.has-fullart::after{z-index:5}
.ccard.has-secret::after{
  background:linear-gradient(90deg,transparent,
    color-mix(in srgb,var(--r-secret) 62%,transparent) 30%,rgba(255,255,255,.72) 50%,
    color-mix(in srgb,#9fd8ff 58%,transparent) 70%,transparent)}
/* Full Art gets the same language one rung quieter, so the two chase tiers are
   distinguishable at a glance instead of only by their ribbon. */
.ccard.has-fullart::after{animation-duration:2.4s;
  background:linear-gradient(90deg,transparent,
    color-mix(in srgb,var(--r-fullart) 60%,transparent) 30%,rgba(255,255,255,.5) 50%,
    color-mix(in srgb,var(--r-fullart) 60%,transparent) 70%,transparent)}
/* Reduce motion is already a setting; honour it here rather than inventing one.
   The bar is parked mid-tile — at its start position it sits off the edge, so
   `animation:none` alone would have hidden the treatment entirely. */
.ccard.still::after{animation:none;transform:translateX(145%) skewX(-16deg);opacity:.5}
@keyframes rsheen{from{transform:translateX(-200%) skewX(-16deg)}
                  to{transform:translateX(490%) skewX(-16deg)}}
/* 38% down, not centred — see the note on `.being .art` in index.html.
   Same anchor as the print renderer, so a card looks the same on the table
   and on screen. */
.ccard .cart{display:block;width:100%;height:104px;object-fit:cover;object-position:center 38%;background:#0e151b}
/* The name plate keeps the KIND colour — gold Being, blue Gale, green Guard —
   now that the border has been handed over to rarity. */
.ccard .cnm{font-size:10px;text-align:center;padding:5px 6px;line-height:1.2;min-height:28px;display:flex;align-items:center;justify-content:center;
  background:linear-gradient(180deg,#4a3a17,#241b09);color:var(--gold-l);text-shadow:0 1px 2px #000}
.ccard.gale .cnm{background:linear-gradient(180deg,#24384a,#111c26)}
.ccard.gd .cnm{background:linear-gradient(180deg,#1f4227,#0e2213)}
.ccard .cco{position:absolute;top:-5px;left:-5px;width:29px;height:29px;display:grid;place-items:center;z-index:3;
  background:radial-gradient(circle at 36% 28%,#d5f4ff,#39a9e0 52%,#11445f);border-radius:50%;
  font-size:13.5px;font-weight:bold;color:#fff;text-shadow:0 1px 2px #000;box-shadow:0 2px 7px rgba(0,0,0,.85)}
.ccard .cst{position:absolute;right:4px;bottom:32px;display:flex;gap:3px;z-index:3}
.ccard .cst span{font-size:10px;font-weight:bold;padding:1px 5px;border-radius:7px;box-shadow:0 2px 5px rgba(0,0,0,.8)}
.ccard .cst .w{background:linear-gradient(180deg,#bfe3f8,#3f7fa5);color:#06222f}
.ccard .cst .r{background:linear-gradient(180deg,#d8edb5,#4d7a2c);color:#0f2408}

/* ---------------- card detail (also the touch inspector) ---------------- */
#cardview{position:fixed;inset:0;z-index:170;display:none;align-items:center;justify-content:center;padding:20px;
  background:rgba(3,5,8,.9);backdrop-filter:blur(5px)}
#cardview.on{display:flex}
.cvhost .cv{width:min(330px,92vw);border-radius:15px;padding:3px;position:relative;
  background:linear-gradient(160deg,var(--rcd),var(--rc) 34%,#ffffff 50%,var(--rc) 62%,var(--rcd));
  box-shadow:0 26px 70px rgba(0,0,0,.9);animation:cvin .2s cubic-bezier(.2,1.3,.5,1)}
.cvhost .cv.r-common{background:linear-gradient(160deg,#333c45,#7d8b96 40%,#aab6c0 50%,#69757f 60%,#2c343c)}
@keyframes cvin{0%{opacity:0;transform:scale(.9) translateY(14px)}100%{opacity:1;transform:none}}
.cvhost .cvi{border-radius:12px;overflow:hidden;background:#0e151b}
.cvhost .cvart{height:min(248px,34vh);background:#0e151b center 38%/cover no-repeat}
.cvhost .cvband{background:linear-gradient(180deg,#4a3a17,#241b09);border-top:1px solid rgba(240,225,180,.4);
  padding:9px 12px;text-align:center;font-size:16px;color:var(--gold-l);text-shadow:0 1px 3px #000}
/* Kind still tints the name band, matching the collection tile, now that the
   border round the whole card has been handed over to rarity. */
.cvhost .cv.gale .cvband{background:linear-gradient(180deg,#24384a,#111c26)}
.cvhost .cv.gd .cvband{background:linear-gradient(180deg,#1f4227,#0e2213)}
.cvhost .cvtype{font-size:9.5px;letter-spacing:2.5px;opacity:.55;margin-top:2px}
.cvhost .cvtx{padding:12px 15px 15px;font-size:13px;line-height:1.6;text-align:center;min-height:56px}
.cvhost .cvkw{display:flex;gap:6px;justify-content:center;flex-wrap:wrap;padding:0 12px 12px}
.cvhost .cvkw span{font-size:10px;letter-spacing:1px;padding:2px 9px;border-radius:9px;
  background:linear-gradient(180deg,var(--gold-l),var(--gold));color:#241a05;font-weight:bold}
.cvhost .cvcost{position:absolute;top:-13px;left:-13px;width:46px;height:46px;display:grid;place-items:center;z-index:3;
  background:radial-gradient(circle at 36% 28%,#d5f4ff,#39a9e0 52%,#11445f);border-radius:50%;
  font-size:21px;font-weight:bold;color:#fff;text-shadow:0 2px 3px #000;box-shadow:0 4px 12px rgba(0,0,0,.9)}
.cvhost .cvpair{display:flex;justify-content:center;gap:16px;padding:0 0 14px}
.cvhost .cvpair div{text-align:center}
.cvhost .cvpair b{display:block;font-size:24px;line-height:1.1}
.cvhost .cvpair i{font-style:normal;font-size:9.5px;letter-spacing:2px;opacity:.6}
.cvhost .cvpair .w b{color:#9fd4f2}
.cvhost .cvpair .r b{color:#c3e08f}
.cvhost .cvclose{position:absolute;top:-13px;right:-13px;width:34px;height:34px;border-radius:50%;cursor:pointer;
  display:grid;place-items:center;font-size:17px;z-index:6;color:var(--cream);
  background:linear-gradient(180deg,#2f3f4b,#161f27);border:1px solid rgba(224,185,95,.4);box-shadow:0 4px 12px rgba(0,0,0,.9)}

/* --- rarity row on the standard printing --- */
.cvhost .cvrar{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 13px 4px}
.cvhost .rpill{display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:bold;letter-spacing:1.3px;
  text-transform:uppercase;padding:3px 10px;border-radius:11px;color:#0b1016;
  background:linear-gradient(180deg,var(--rc),var(--rcd));text-shadow:0 1px 0 rgba(255,255,255,.28)}
.cvhost .rpill .rgem{width:9px;height:9px;transform:rotate(45deg);border-radius:1px;
  background:linear-gradient(135deg,#fff,var(--rcd));box-shadow:0 0 4px rgba(0,0,0,.5)}
.cvhost .cvnum{font-size:9.5px;letter-spacing:1.6px;opacity:.62;font-variant-numeric:tabular-nums}
.cvhost .cvnum em{font-style:normal;margin-left:7px;color:var(--rc);opacity:.95;font-weight:bold}

/* --- the swap button: a chase card you have to ASK to see ---
   The button and its note hang below the card, so the centring container has
   to be told they exist — otherwise the note falls off the bottom edge, which
   it did at 844 × 420 (a phone held landscape). */
.cvhost .cv.has-swap{margin-bottom:88px}
.cvhost .faswap{position:absolute;left:0;right:0;bottom:-46px;margin:0 auto;width:max-content;cursor:pointer;
  font-family:inherit;font-size:12px;letter-spacing:1.6px;padding:8px 18px;border-radius:10px;color:#241a05;
  background:linear-gradient(180deg,#fbeec2,var(--gold) 46%,#9b7327);border:1px solid var(--gold-l);
  box-shadow:0 6px 18px rgba(0,0,0,.75);transition:transform .13s,box-shadow .13s}
.cvhost .faswap:hover{transform:translateY(-2px);box-shadow:0 0 20px rgba(224,185,95,.45),0 6px 18px rgba(0,0,0,.75)}
.cvhost .fanote{position:absolute;left:-30px;right:-30px;bottom:-72px;text-align:center;
  font-size:10.5px;letter-spacing:.6px;opacity:.5;color:var(--cream)}

/* --- FULL ART: the frame gets out of the way ----------------------------
   Deliberately the same content in the same order as the standard printing —
   name, type, collector row, keywords, text, stats — because a Full Art is the
   SAME CARD and must not send anyone hunting for a number in a new place. What
   changes is that the art runs to all four edges of the panel and everything
   floats on top of it. This is print/make-cards.py's render_premium() on
   screen; the two were built to match. */
/* The premium card is a fixed 63.5:88.9 rectangle, so unlike the standard
   view — whose art panel is capped at 34vh and shrinks — its height follows
   its width. `54vh` is that ratio worked backwards (0.714 × 1.4 ≈ 1), and it
   is what keeps the card on screen in landscape on a phone. */
.cvhost .cv.prem{padding:2px;width:min(330px,92vw,54vh);
  background:linear-gradient(160deg,var(--rcd),var(--rc) 40%,#fff 50%,var(--rc) 60%,var(--rcd))}
/* The panel is its own container, and every text size below is expressed in
   `cqw` — a share of the CARD's width, not the viewport's. The card can be
   capped by 54vh (see above), and when it is, viewport-based type does not
   follow it down: measured at a 374 × 367 viewport the card came out 198 px
   wide and the collector row wrapped "SECRET RARE" onto a second line while
   the name still rendered at its full 23 px. The px value on each rule is the
   fallback for anything without container query units. */
.cvhost .cv.prem .cvi.fai{position:relative;border-radius:13px;overflow:hidden;background:#0b1016;
  aspect-ratio:63.5/88.9;container-type:inline-size}
/* The copy count on a collection tile. Two characters that say "you may play
   one of these"; it can never read ×3, because MAX_COPIES is 2 and there is no
   third slot to grant one into. */
#collection .ccard .ccop{position:absolute;left:4px;bottom:30px;z-index:2;
  font:600 11px/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.02em;
  color:#dfe8f2;background:rgba(8,12,18,.78);border:1px solid rgba(190,210,235,.25);
  border-radius:5px;padding:2px 4px}
#collection .ccard.unowned .ccop{display:none}
.cvhost .faart{position:absolute;inset:0;background:#0b1016 center 42%/cover no-repeat}
/* the scrim is what replaces the frame's job of separating art from text */
.cvhost .fascrim{position:absolute;inset:0;
  background:radial-gradient(ellipse at 50% 34%,transparent 34%,rgba(2,5,9,.66) 100%),
             linear-gradient(180deg,rgba(3,6,10,.5) 0%,transparent 22%,transparent 40%,rgba(3,6,10,.82) 62%,rgba(2,4,7,.96) 100%)}
.cvhost .fabody{position:absolute;left:0;right:0;bottom:0;padding:0 4cqw 4cqw;text-align:center;z-index:3}
.cvhost .faname{font-size:23px;font-size:clamp(14px,7.2cqw,25px);line-height:1.15;color:#fdf3d8;
  text-shadow:0 2px 5px #000,0 0 22px rgba(0,0,0,.9)}
.cvhost .cv.prem .cvtype{font-size:9.5px;font-size:clamp(7px,2.9cqw,10px);letter-spacing:.28em;
  opacity:.72;margin-top:3px;text-shadow:0 1px 3px #000}
.cvhost .cv.prem .cvnum{margin-top:5px;opacity:.82;text-shadow:0 1px 3px #000;white-space:nowrap;
  font-size:9.5px;font-size:clamp(6.5px,2.85cqw,9.5px);letter-spacing:.13em}
.cvhost .cv.prem .cvnum em{margin-left:.6em}
.cvhost .cv.prem .cvkw{padding:3cqw 0 0}
.cvhost .cv.prem .cvtx{padding:3cqw 1cqw 1cqw;min-height:0;text-shadow:0 1px 4px #000;
  font-size:13px;font-size:clamp(9px,3.9cqw,13px)}
.cvhost .cv.prem .cvpair{padding:2.5cqw 0 0}
.cvhost .cv.prem .cvpair b{text-shadow:0 2px 5px #000;font-size:24px;font-size:clamp(15px,7.3cqw,24px)}
.cvhost .cv.prem .cvpair i{font-size:9.5px;font-size:clamp(6.5px,2.85cqw,9.5px);white-space:nowrap;letter-spacing:.18em}

/* --- SECRET RARE: above Full Art. Prismatic foil + a slow drifting glint,
   which is the closest a screen gets to tilting a foil card under a lamp. */
.cvhost .cv.r-secret.prem{box-shadow:0 0 46px rgba(255,225,150,.36),0 26px 70px rgba(0,0,0,.95)}
/* `screen` at .4 was wrong for the same reason the print foil was wrong: it
   lightens EVERYTHING, and the Crest Watcher owl came out flat green. `overlay`
   keeps the painting's own light and dark and pushes colour through it, which
   is what foil actually does. */
/* TIMING IS THE WHOLE THING HERE, and the first version got it wrong. The hue
   layer drifted its background-position over 9s with ease-in-out, which spends
   most of the cycle nearly stationary — Michael reported the card as simply not
   shimmering, and he was right: at that speed it reads as a coloured overlay,
   not as light moving. Foil reads as foil when a bright band CROSSES the card
   at a speed the eye can follow: linear, never eased.

   Corrected once more after Michael called the whole card sluggish, and the
   culprit was the hue wash, not the band. `alternate` means the 5s was really a
   10s round trip, and a wash that takes ten seconds to cross does not read as
   slow motion — it reads as a stationary tint. Halved to 3.2s (6.4s round trip),
   which is about the tempo of turning a card in your hand. The band came down
   with it, to 1.8s, so a glint is a glint and not a searchlight. */
.cvhost .fafoil{position:absolute;inset:0;z-index:2;pointer-events:none;
  mix-blend-mode:overlay;opacity:.68;
  background:linear-gradient(115deg,#ff5f8f,#ffd76a 16%,#7dffb0 33%,#5fd0ff 50%,#a98cff 66%,#ff7ad9 83%,#ff5f8f);
  background-size:260% 260%;animation:rfoil 3.2s linear infinite alternate}
/* The specular sweep — the bit that reads as tilting the card under a lamp.
   IT IS A SIBLING OF .fafoil, NOT ITS CHILD, and that is deliberate. It used
   to be `.fafoil::after`, which put it inside a group that is composited at
   `mix-blend-mode:overlay` and — on Full Art — `opacity:.3`. Overlay against a
   dark painting is very nearly a no-op: overlay(0.15, 0.96) = 0.29, then 30%
   of the way from 0.15 to that is 0.19, a ~4% luminance lift on a card that is
   mostly forest shadow. Michael reported the Full Art foil as invisible and it
   effectively was. Blended `screen` straight onto the art it survives on dark
   and light alike, which is what a specular highlight does in the world.
   Geometry and keyframes are shared with the collection tile above. */
/* AND IT MUST NOT EAT THE RAINBOW. Moving the sweep out of the overlay group
   fixed its visibility and immediately cost something else, which is what
   Michael meant by "the gradient was nicer when it only worked on the Secret
   Rare" — the rainbow never changed, the thing sitting on top of it did.
   `screen` at .6 white is a desaturator: screen(c,.6) floors every channel at
   .6, so wherever the bar sits the prismatic layer's separation collapses
   toward white, and .16 flanks spanning 44% of the bar meant it sat over much of
   the card much of the time. Peak down to .34, flanks to .05, bright core pulled
   in to 40%–60%: the highlight still reads on dark art, the colour under it
   stays colour. */
.cvhost .fasheen{z-index:3;mix-blend-mode:screen;animation-duration:1.8s;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.05) 28%,
    rgba(255,255,255,.16) 40%,rgba(255,255,255,.34) 50%,rgba(255,255,255,.16) 60%,
    rgba(255,255,255,.05) 72%,transparent)}
/* Three layers at coprime sizes. Two layers at 74/43 px drew a visible
   rectangular lattice on the first proof — it read as a texture bug, not glitter. */
.cvhost .fastars{position:absolute;inset:0;z-index:2;pointer-events:none;
  background-image:radial-gradient(circle,#fff 0 .9px,transparent 1.4px),
                   radial-gradient(circle,#fff 0 .7px,transparent 1.2px),
                   radial-gradient(circle,#fff 0 1.2px,transparent 1.7px);
  background-size:97px 89px,61px 67px,143px 131px;
  background-position:11px 7px,38px 41px,73px 96px;
  animation:rtwinkle 2.4s ease-in-out infinite alternate}
@keyframes rfoil{0%{background-position:0% 50%}100%{background-position:100% 50%}}
@keyframes rtwinkle{0%{opacity:.3}100%{opacity:.72}}
/* Full Art gets the foil too — Michael asked for it, and he is right: a Full
   Art card with no light on it is just a big picture. One rung quieter than
   Secret: the hue wash is dialled back and there is no sparkle scatter, so the
   two treatments still read as different objects rather than the same effect.
   The SHEEN, though, is the part that has to be plainly visible on both — it is
   the whole reason Michael asked — so it is only slightly softer, not hidden. */
.cvhost .fafoil.fa-lite{opacity:.38}
/* The sheen's own lite variant. IT WAS NOT BEING APPLIED: this comment opened
   with no `/*`, so the CSS parser read three lines of English plus the rule's
   own selector as one long invalid selector and threw the whole block away.
   Confirmed rather than guessed — `.cvhost .fasheen.fa-lite` was absent from
   `document.styleSheets` while every neighbouring rule was present. So every
   Full Art card has been wearing the SECRET RARE's sheen (1.8s, .34 peak)
   since the tuning landed, which is the exact thing this block exists to stop.
   Its peak comes down with Secret's but not as far — .36, against a hue wash of
   only .38 there is far less colour for the bar to bleach, and this is the tier
   where the sheen going quiet once already read as "no foil at all". */
.cvhost .fasheen.fa-lite{animation-duration:2.2s;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.05) 28%,
    rgba(255,255,255,.17) 40%,rgba(255,255,255,.36) 50%,rgba(255,255,255,.17) 60%,
    rgba(255,255,255,.05) 72%,transparent)}
.cvhost .cv.still .fafoil,.cvhost .cv.still .fastars{animation:none}
.cvhost .cv.still .fasheen{animation:none;transform:translateX(145%) skewX(-16deg);opacity:.5}

/* ---------------- settings ---------------- */
.setgrid{display:flex;flex-direction:column;gap:16px;max-width:520px;margin:0 auto}
.setrow{display:flex;align-items:center;gap:14px;padding:12px 15px;border-radius:10px;
  background:rgba(14,21,28,.8);border:1px solid rgba(224,185,95,.14)}
.setrow .lab{flex:1;min-width:0}
.setrow .lab b{display:block;font-size:13.5px;color:var(--gold-l);font-weight:normal}
.setrow .lab span{display:block;font-size:11px;opacity:.6;margin-top:2px;line-height:1.4}
.setrow input[type=range]{width:130px;accent-color:var(--gold)}
.setrow select{min-width:118px}
/* The client build in SETTINGS (issue #144). Not a control — read-only, same
   muted weight as the title footer, so it does not look like a toggleable row. */
.setbuild{flex:none;font-size:12px;letter-spacing:1px;opacity:.55;color:var(--gold-l);font-variant-numeric:tabular-nums}
.swi{position:relative;width:46px;height:25px;border-radius:14px;cursor:pointer;flex:none;
  background:#1b2831;border:1px solid rgba(224,185,95,.25);transition:.16s}
.swi::after{content:"";position:absolute;top:2px;left:2px;width:19px;height:19px;border-radius:50%;
  background:linear-gradient(180deg,#8c9aa4,#5a6870);transition:.16s}
.swi.on{background:linear-gradient(180deg,var(--gold),#9b7327);border-color:var(--gold-l)}
.swi.on::after{left:23px;background:linear-gradient(180deg,#fff8e2,var(--gold-l))}

/* ---------------- the play background picker ----------------
   Its header is an ordinary .setrow so it reads as one of the settings; the
   tiles sit under it, full width, because a picture chosen from a 118px select
   is a picture nobody can see. The locked state is dashed border + grayscale +
   an explicit padlock LINE, not colour alone — the same three-cue rule the
   colour-blind section below states, and it is why the requirement is written
   out rather than implied by a dimmed tile. */
.setboards-h{margin-bottom:-8px}
/* One column, not two. Two fits four tiles on one screen and was tried first:
   inside .setgrid's 520px each tile is ~250px, the description wraps to five
   lines, and the locked tiles come out taller than the held one. A settings row
   is full width here for the same reason. */
.setboards{display:grid;grid-template-columns:1fr;gap:11px}
.setboard{display:flex;gap:12px;align-items:center;text-align:left;padding:9px 11px;border-radius:10px;
  background:rgba(14,21,28,.8);border:1px solid rgba(224,185,95,.14);cursor:pointer;color:inherit;font:inherit}
.setboard:hover:not(:disabled){border-color:rgba(224,185,95,.42)}
.setboard.on{border-color:var(--gold);box-shadow:0 0 0 1px var(--gold),0 0 20px rgba(224,185,95,.16)}
.setboard.locked{opacity:.6;border-style:dashed;cursor:default}
.setboard-img{width:82px;height:52px;flex:none;border-radius:6px;
  background:#0f171e center/cover no-repeat;border:1px solid rgba(224,185,95,.28)}
.setboard.locked .setboard-img{filter:grayscale(.85) brightness(.7)}
.setboard-tx{min-width:0;display:flex;flex-direction:column;gap:2px}
.setboard-tx b{font-size:12.5px;color:var(--gold-l);font-weight:normal}
.setboard-tx b em{font-style:normal;font-size:10px;letter-spacing:.6px;opacity:.7}
.setboard-tx i{font-style:normal;font-size:11px;opacity:.82;line-height:1.4}
.setboard-tx u{text-decoration:none;font-size:10.5px;letter-spacing:.3px;opacity:.72;margin-top:2px}
/* ---------------- the card back picker ----------------
   Two columns, because a card back is portrait and four of them side by side at
   this width would be thumbnails of nothing. The tile shows the art at the
   proportions it is actually seen at (2:3, the card), so what you pick is what
   you get. */
.setbacks-h{margin-bottom:-8px}
.setbacks{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px}
.setback{display:flex;gap:12px;align-items:center;text-align:left;padding:9px 11px;border-radius:10px;
  background:rgba(14,21,28,.8);border:1px solid rgba(224,185,95,.14);cursor:pointer;color:inherit;font:inherit;transition:.16s}
.setback:hover{border-color:rgba(224,185,95,.42)}
.setback.on{border-color:var(--gold);box-shadow:0 0 0 1px var(--gold),0 0 20px rgba(224,185,95,.16)}
.setback-img{width:44px;height:66px;flex:none;border-radius:5px;
  background:#0b1116 center/cover no-repeat;border:1px solid rgba(224,185,95,.22)}
.setback-tx{min-width:0;display:flex;flex-direction:column;gap:2px}
.setback-tx b{font-size:12.5px;color:var(--gold-l);font-weight:normal}
.setback-tx b em{font-style:normal;opacity:.62;font-size:11px}
.setback-tx i{font-style:normal;font-size:10.5px;opacity:.8;line-height:1.4}
@media (max-width:520px){.setbacks{grid-template-columns:1fr}}

/* ---------------- colour-blind cues ----------------
   The board codes everything by colour: green = you can act, red = you can
   attack it. These add a redundant SHAPE so the information survives without
   colour vision. Always on — they read as rank pips, not as an aid. */
.being.able::before,.being.target::before{position:absolute;top:-7px;left:-6px;z-index:6;
  width:17px;height:17px;border-radius:50%;display:grid;place-items:center;
  font-size:10px;font-weight:bold;line-height:1;box-shadow:0 2px 6px rgba(0,0,0,.9)}
.being.able::before{content:"▲";background:linear-gradient(180deg,#8ce087,#2f7a2c);color:#04240a}
.being.target::before{content:"✕";background:linear-gradient(180deg,#f0776a,#8e2b20);color:#fff}
.hero.target .hitchip::before{content:"✕ ";}
.card.playable .plate::after{content:"▲";position:absolute;top:-6px;right:-6px;z-index:5;
  width:17px;height:17px;border-radius:50%;display:grid;place-items:center;font-size:10px;font-weight:bold;
  background:linear-gradient(180deg,#8ce087,#2f7a2c);color:#04240a;box-shadow:0 2px 6px rgba(0,0,0,.9)}

/* ---------------- versus / match intro ---------------- */
#versus{position:fixed;inset:0;z-index:160;display:none;align-items:center;justify-content:center;
  background:rgba(3,5,8,.93);backdrop-filter:blur(3px)}
#versus.on{display:flex}
#versus .vrow{display:flex;align-items:center;gap:clamp(18px,6vw,64px);animation:vin .45s ease}
@keyframes vin{0%{opacity:0}100%{opacity:1}}
#versus .vside{display:flex;flex-direction:column;align-items:center;gap:9px;width:clamp(110px,26vw,190px)}
#versus .vside.a{animation:vleft .55s cubic-bezier(.2,1.2,.4,1)}
#versus .vside.b{animation:vright .55s cubic-bezier(.2,1.2,.4,1)}
@keyframes vleft{0%{transform:translateX(-60px);opacity:0}100%{transform:none;opacity:1}}
@keyframes vright{0%{transform:translateX(60px);opacity:0}100%{transform:none;opacity:1}}
#versus .vpor{width:clamp(92px,20vw,142px);height:clamp(92px,20vw,142px);border-radius:50%;
  background:#16222c center/cover no-repeat;
  box-shadow:0 0 0 3px var(--gold-d),0 0 0 6px rgba(0,0,0,.6),0 12px 36px rgba(0,0,0,.9)}
#versus .vside.you .vpor{box-shadow:0 0 0 3px var(--you),0 0 0 6px rgba(0,0,0,.6),0 0 40px rgba(95,182,212,.4),0 12px 36px rgba(0,0,0,.9)}
#versus .vside.them .vpor{box-shadow:0 0 0 3px var(--them),0 0 0 6px rgba(0,0,0,.6),0 0 40px rgba(212,112,95,.4),0 12px 36px rgba(0,0,0,.9)}
#versus .vnm{font-size:clamp(12px,1.7vw,15px);color:var(--gold-l);text-align:center;line-height:1.3}
#versus .vfac{font-size:10px;letter-spacing:2.5px;opacity:.55}
#versus .vs{font-size:clamp(26px,5vw,44px);letter-spacing:5px;
  background:linear-gradient(180deg,var(--gold-l),var(--gold) 50%,#8a6a25);-webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 3px 12px rgba(0,0,0,.9));animation:vspop .6s cubic-bezier(.2,1.5,.4,1)}
@keyframes vspop{0%{transform:scale(2.4);opacity:0}60%{transform:scale(.92)}100%{transform:none;opacity:1}}
#versus .vflip{position:absolute;bottom:12%;left:0;right:0;text-align:center;font-size:13px;opacity:0;
  color:var(--gold-l);letter-spacing:1.5px;animation:vfade .4s ease .75s forwards}
@keyframes vfade{to{opacity:.92}}

/* The opening exchange (GUARDIAN-VOICE.md §3A). The two sides have to grow to
   hold it — an opener runs to 18 words and `.vside` is sized for a name — so
   `.talking` widens them, and only then: a match with no exchange (an empty
   bag, or the setting off) keeps the old intro exactly as it was. The reply
   fades in after the opener, because it is a reply. */
#versus .vrow.talking .vside{width:clamp(150px,34vw,330px)}
#versus .vsay{font-size:clamp(11.5px,1.4vw,14px);line-height:1.45;font-style:italic;text-align:center;
  color:var(--gold-l);text-shadow:0 1px 4px rgba(0,0,0,.9);opacity:0;
  padding:8px 12px;border-radius:11px;border:1px solid rgba(224,185,95,.22);
  background:rgba(255,255,255,.035);animation:vfade .45s ease .55s forwards}
#versus .vside.b .vsay{animation-delay:1.5s}
/* Under about 620px two columns cannot both hold a sentence and stay readable,
   so the exchange gives up its padding rather than pushing the portraits off
   the sides of a 390px screen. */
@media (max-width:620px){
  #versus .vrow.talking{gap:12px}
  #versus .vrow.talking .vside{width:min(42vw,220px)}
  #versus .vsay{padding:6px 8px;font-size:11px}
}

@media (max-width:760px){
  .cgrid{grid-template-columns:repeat(auto-fill,minmax(108px,1fr));gap:10px}
  .ccard .cart{height:84px}
  /* 108 px tile minus the cost bubble and the rarity stamp leaves ~50 px */
  .ccard .cprem{left:22px;right:29px;font-size:7px;letter-spacing:0}
  /* the premium card needs no breakpoint — its type is sized in `cqw` */
  .screen .sbody{padding:12px 12px 22px}
  .screen .shead{padding:9px 12px;gap:9px}
  .screen .shead h2{font-size:13px;letter-spacing:3px}
  .tbtn{padding:12px 15px;font-size:14px}
  .tbtn.primary{padding:14px 15px;font-size:15.5px}
}

/* Keyboard focus was completely invisible across the shell. */
.tbtn:focus-visible,.chip:focus-visible,.ccard:focus-visible,button:focus-visible,
select:focus-visible,input:focus-visible,.swi:focus-visible{
  outline:3px solid var(--gold);outline-offset:2px;border-radius:8px}
@media (max-width:760px){.ccard .cart{height:84px}}

/* ---- a card you do not own yet -------------------------------------------
   GREY, PRESENT, COUNTABLE — never absent and never a padlock.
   Absent is fatal: you cannot want what is not on screen, and a grid that
   changes size destroys spatial memory (Skyfall is in the same place every
   time, or it is a new screen every time). The padlock is already spoken for:
   this game uses it for ACCESS you do not have — the DECK and COLLECTION
   buttons while signed out, and Skyholds 2/3/4 on the campaign map. A card is
   not access; it is a thing you have or have not.
   Desaturation is the house style for exactly this: a card in hand you cannot
   afford is dimmed and labelled "needs 3 Breeze", present and obviously
   yours-later.

   TWO THINGS THIS DELIBERATELY DOES NOT DO, both measured after doing them:
   1. It does not use ::after. `.ccard.has-fullart::after` IS THE SHEEN — an
      element gets one ::after, so an overlay here silently fought the foil on
      exactly the 25 Full Art and 6 Secret Rare tiles that are the best reason
      to open this screen. The dim is an inset shadow on the frame instead.
   2. It does not position the route line absolutely. `.cnm` is in flow, so an
      absolutely-positioned strip at bottom:0 lands on top of the card's name:
      measured 2.4px on a framed tile and 17.4px on a Full Art one, on all 158
      unowned tiles. It sits in flow, and it is rendered EMPTY on owned tiles so
      every tile in the grid keeps the same height and the rows stay level. */
.ccard.unowned .cart{filter:grayscale(1) brightness(.55)}
.ccard.unowned{box-shadow:inset 0 0 0 999px rgba(9,13,19,.34)}
.ccard.unowned .cco,.ccard.unowned .cst{opacity:.5}
/* The route out. "A collection that shows you a card and not the way to get it
   is a taunt." — design/economy/OWNERSHIP-VERDICT-first-hour.md §4 */
.cget{min-height:13px;padding:1px 5px 2px;font-size:9px;line-height:1.25;
  text-align:center;letter-spacing:.2px;color:#9fb0c2}
.ccard.unowned .cget{color:#cdd8e4}
.cget b{color:#e9c96b;font-weight:600}
.ccount .cown{font-variant-numeric:tabular-nums;color:#e9c96b;opacity:.95}

/* ---------------- a card whose art does not exist yet ----------------
   NOT the same state as "still loading", and the whole reason this rule exists.
   `.ci` is #0e151b, so a 404 left a black rectangle that is pixel-identical to a
   tile mid-fetch — Michael read the screen as "loading seams stuck" and reached
   for pagination. Measured 2026-08-05: 60 of the 333 cards in engine.js have no
   assets/cards/<id>.jpg, 15 of them in the first viewport.
   So the empty state says so, in the tile, once and quietly. Diagonal hatching
   rather than a spinner: a spinner is a promise that something is coming in the
   next second, and nothing is. */
.ccard.noart .ci{background:
  repeating-linear-gradient(135deg,#141d26 0 7px,#111922 7px 14px)}
.ccard.noart .cart{display:none}
.ccard.noart .ci::before{content:"Art coming";display:flex;align-items:center;
  justify-content:center;height:104px;font-size:9px;letter-spacing:1.1px;
  text-transform:uppercase;color:#5f7285}
@media (max-width:760px){.ccard.noart .ci::before{height:84px}}

/* An empty grid, which owned-by-default makes reachable from one chip click. */
.cempty{grid-column:1/-1;padding:34px 22px;text-align:center;border-radius:12px;
  border:1px dashed rgba(224,185,95,.22);background:rgba(14,21,27,.55)}
.cempty b{display:block;font-size:15px;color:#e9c96b;margin-bottom:7px}
.cempty span{font-size:12px;line-height:1.55;color:#9fb0c2}
.cempty span b{display:inline;font-size:inherit;margin:0}

/* The locked Season shelf — teased, never a tile. See paintSeason1Shelf(). */
.cseason1{display:flex;align-items:center;gap:9px;margin-bottom:13px;padding:11px 16px;
  border-radius:10px;border:1px dashed rgba(150,168,186,.22);background:rgba(14,21,27,.4);
  font-size:12px;color:#9fb0c2}
.cseason1:empty{display:none}
.cs1-lock{font-size:13px;opacity:.75}
.cs1-tx b{color:#e9c96b}

/* ---------------- consent gate (issue #90) ----------------
   Deliberately NOT `.modal`: no `inset:0` backdrop, so it never intercepts a
   click outside its own box — the title screen (or a live match) underneath
   stays fully clickable while this is up. AGENTS.md rule 9 is why: the banner
   gates the ANALYTICS load, never the game itself. z-index 156 sits above
   both `#title` (150) and `.screen` (155) — the choice has to stay visible
   and reachable even while SETTINGS or PRIVACY is open on top of it — but
   below `.modal` (158), so the one-time "How to play" dialog a brand-new
   player also sees does not fight it for the same pixels. */
#consentBanner{position:fixed;left:0;right:0;bottom:0;z-index:156;display:none;
  gap:14px;flex-wrap:wrap;align-items:center;justify-content:space-between;
  padding:14px 18px;background:var(--panel);border-top:1px solid var(--line);
  box-shadow:0 -6px 24px rgba(0,0,0,.5)}
#consentBanner.on{display:flex}
#consentBanner p{margin:0;flex:1 1 320px;max-width:640px;font-size:12.5px;line-height:1.55;color:var(--cream)}
#consentBanner .lk{background:none;border:0;padding:0;margin-left:4px;font:inherit;
  color:var(--gold);text-decoration:underline;text-underline-offset:2px;cursor:pointer}
#consentBanner .lk:hover,#consentBanner .lk:focus-visible{color:var(--gold-l)}
#consentBanner .cb-actions{display:flex;gap:10px;flex:0 0 auto}
/* #cbAccept and #cbDecline share EVERY rule below — same selector, same
   padding, same border, same font. That is the whole implementation of "a
   real decline costs exactly what an accept does": neither button may get a
   distinguishing `.primary` treatment the way #btnPlay does on the title
   screen. If you are tempted to make one of these stand out, don't — read
   the ruling on issue #90 first. */
#consentBanner .cb-actions button{padding:9px 20px;border-radius:9px;font-size:12.5px;
  letter-spacing:.4px;cursor:pointer;border:1px solid var(--line);
  background:rgba(10,14,18,.65);color:var(--cream);font-family:inherit}
#consentBanner .cb-actions button:hover,#consentBanner .cb-actions button:focus-visible{border-color:var(--gold)}
@media (max-width:620px){
  #consentBanner{padding:12px 14px}
  #consentBanner .cb-actions{width:100%}
  #consentBanner .cb-actions button{flex:1}
}

/* ---------------- privacy & imprint (issue #90) ----------------
   Plain prose inside the same `.screen`/`.sbody` shell SETTINGS and
   COLLECTION already use — no new UI paradigm, per AGENTS.md's simplicity
   rule. Static content (see the comment beside #privacy in index.html), so
   nothing here is painted from JS. */
.privbody{max-width:640px;margin:0 auto;font-size:13px;line-height:1.65}
.privbody h3{margin:22px 0 6px;font-size:13.5px;letter-spacing:1.5px;
  text-transform:uppercase;color:var(--gold-l);font-weight:normal}
.privbody h3:first-child{margin-top:0}
.privbody p{margin:0 0 10px;opacity:.92}
.privbody ul{margin:0 0 10px;padding-left:20px}
.privbody li{margin-bottom:8px}
.privbody code{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11.5px;
  background:rgba(224,185,95,.12);padding:1px 5px;border-radius:4px;color:var(--gold-l)}
