/* ═══════════════════════════════════════════════════════════════════
   THE STAGE LAYOUT — ported from proto/index.html (HANDOFF-131)

   Every rule is scoped to html[data-layout="stage"], set by
   site/rips-boot-params.js from ?layout=stage. With the flag off this file
   changes nothing, so blockrips.com's current layout stays reachable the
   whole time this is being built (HANDOFF-131 §5.2).

   NOTHING HERE REDEFINES A style.css CLASS. The standing rule in this repo
   is not to restyle v3's stylesheet to fit a module (HANDOFF-131 §3.8,
   HANDOFF-130 §3), so the pieces the product already owns — .stick, .rip,
   .hring, .turbo, .hit, .strip, .info — are REUSED as they are. Everything
   new to this layout carries an sl- prefix, which also keeps it clear of
   .rail / .rail--left / .rail--right, whose names the prototype reused for
   something else entirely.

   THE BACKGROUND IS NOT IN THIS FILE. Luke's 5 Sep plate replaced the old
   one in place — bg-plate.png, brand/stage/yellow-stage.png and the new
   portrait bg-plate-mobile.png — so both layouts wear it and there is one
   picture, not a stage-only copy. It is the same bright comic sky family as
   the plate it replaced (measured: rgb 125,194,246 vs 141,194,241), so the
   BRIGHT GROUND OVERRIDES block in style.css:1831 still holds and this
   stays a re-layout rather than a re-theme. Its centre is clean where the
   old plate carried a starburst, so — unlike the prototype's sky build —
   the plate is NOT offset horizontally to move a burst behind the hero.
   Never offset a background-size:cover layer horizontally: it slides off
   its own edge and --bg shows through as a band (HANDOFF-131 §3.6).
   ═══════════════════════════════════════════════════════════════════ */

/* THE PLATE IS PAINTED TWICE ON THIS SITE, and that is not obvious from
   .bg alone. pack3d draws its own copy as a backdrop mesh inside the scene
   (pack3d.js:707, ?bg=art -> brand/stage/yellow-stage.png, which is
   byte-identical to bg-plate.png — same sha256, and kept that way), and
   #stageLayer is a viewport-fixed opaque canvas at z-index 0, so while the
   3D pack is live the canvas IS the background and .bg is behind it.
   Luke's 5 Sep plate went into BOTH, so there is nothing to override here:
   style.css:99 and pack3d both already serve it, on either layout.

   THE HERO — HANDOFF-131 §4, route (b): stills while you browse, the 3D
   pack from the rip onward. The carousel is what you look at now, and the
   centred still already carries pack3d's own idle numbers. Keeping the live
   pack on browse (route (a)) means solving two things this avoids: the
   canvas centres on the VIEWPORT while the carousel centres on the page,
   which the 242px rail puts 121px apart; and the backdrop would need the
   new plate too, at the mesh's 1.538 aspect rather than the artwork's 16:9.
   One line switches this back, which is why it is a line. */
html[data-layout="stage"]:not([data-state="rip"]) #stageLayer>canvas{opacity:0}

/* ── THE FRAME ──────────────────────────────────────────────────────
   .fit is already display:flex, so the rail is simply its first child and
   .page (flex:1) keeps the rest. No new wrapper. */
html[data-layout="stage"] .shell{
  grid-template-columns:minmax(0,1fr);gap:0}
html[data-layout="stage"] .rail--left{display:none}
/* rank + crate move into the drawer; the rail itself stops being a column */
html[data-layout="stage"] .rail--right{display:none}

/* ── THE LEFT RAIL ──────────────────────────────────────────────────
   Luke, 4 Sep: no translucent panel here. A glass surface over a bright
   halftone reads as a grey smear (HANDOFF-131 §6), so the rail is dark ink
   straight on the plate and the active row is a solid sticker pill — the
   same device the site already uses for a selected tier. */
/* THE RAIL IS AN OVERLAY, AND THAT IS WHAT STOPS THE PACKS MOVING.
   Luke, 5 Sep: "when the left sidebar opens, I don't think that the card
   should shift at all... same with those icons, there's no need for them to
   slide." While the rail was a flex CHILD of .fit, every pixel it gave up went
   to .page, so collapsing it re-centred the whole stage and the packs and the
   collection ring slid across. Taken out of the flow it cannot do that.
   .page then carries a CONSTANT left inset of the rail's width, so the stage
   sits exactly where it does today and never moves again. The rail also keeps
   its box in both states now — nothing animates its width — because the logo
   has to stay put and only the buttons are allowed to travel. */
/* --sl-rail is the rail's visual FOOTPRINT, which is what the live strip has to
   clear. The rail ELEMENT keeps its 242px box in both states so the logo never
   moves; only this number changes, and only the strip reads it. */
html[data-layout="stage"]{--sl-rail:242px}
/* ── THE BOOT VEIL (Luke, 5 Sep 11:35): "on a reload ... the button starts
   off at the top when you press refresh and then jumps down to the bottom
   ... more like everything appears in one go in the correct location."
   Measured on a warm reload: the raw page paints at ~30ms with RIP PACK at
   y=224 (the old layout's spot), stage.js re-homes it to y=626 at ~260ms,
   the packs land at ~1.6s. Three separate arrivals. So until stage.js says
   the stage is built and the packs are in (html[data-booted]), everything
   but the plate is invisible — layout still happens, nothing is displayed —
   and then it all fades in together over .3s. data-layout is set on <html>
   and in <head> (rips-boot-params.js), so this applies from the very first
   frame. The plate (.bg, inside .page) stays visible so the sky is there
   at once. stage.js lifts only when the pack slots are in; a cap against
   the clock used to fire on a cold load before the catalogue, which is
   how incognito painted empty chrome on the sky (Luke, 21 Sep 20:00,
   after the 10s last-resort shipped). Opacity only: a transform on a
   body child would re-parent its fixed descendants for the length of
   the tween. */
html[data-layout="stage"]:not([data-booted]) body > *{visibility:hidden}
html[data-layout="stage"]:not([data-booted]) .bg{visibility:visible}
/* `backwards`, NOT `both` (Luke, 6 Sep 03:55: the crate toast "just stays
   there ... across the site ... the only way to remove it is to refresh").
   `both` kept the animated opacity of 1 on every body child for good, and an
   animation outranks an inline style — so wallet.js's toast, which hides by
   writing opacity:0 inline after 3.6s, could never fade once this layout
   shipped (measured headless: inline 0, computed 1 at 4.3s). `backwards`
   is the same .3s fade-in, then the element owns its opacity again. */
html[data-layout="stage"][data-booted] body > :not(.bg){animation:slBoot .3s ease-out backwards}
@keyframes slBoot{from{opacity:0}to{opacity:1}}
@media (prefers-reduced-motion:reduce){html[data-layout="stage"][data-booted] body > :not(.bg){animation:none}}

html[data-layout="stage"][data-side="collapsed"]{--sl-rail:84px}
/* Collapsed, the -23px slot alignment below puts LIVE's dot 3px off the
   toggle's edge (measured: toggle right 58, dot left 63 at every width), and
   the hovered toggle sits on it (Luke, 5 Sep 09:15: "way too close ...
   overlapping"). The toggle is fixed at x 20..58 in that state, so the strip
   starts 18px past it, at x=76, whatever the width. Same specificity rule as
   the .strip block: transform/opacity are untouched, only the margin. */
html[data-layout="stage"][data-side="collapsed"] .strip{
  margin-left:calc(76px - clamp(16px,2.2vw,40px))}
/* THE RAIL'S EMPTY SPACE IS NOT A CLICK TARGET (Luke, 5 Sep 06:39: "i cant
   click where it says sport... its like something invisible is over the top
   of it"). The box is 242px wide and full height at z-index 30, and at a
   1470px window the leftmost ring icon centres at x=235 — under it. Measured
   with elementFromPoint: the hit was aside#slSide, not the button. The box
   stays (the logo must not move, §8.5); only its CHILDREN take the pointer. */
html[data-layout="stage"] .sl-side,
html[data-layout="stage"] .sl-side .header__nav,
html[data-layout="stage"] .sl-side__foot{pointer-events:none}
html[data-layout="stage"] .sl-side__brand,
html[data-layout="stage"] .sl-side__toggle,
html[data-layout="stage"] .sl-side .sl-link{pointer-events:auto}
html[data-layout="stage"] .sl-side{
  /* 242px FLAT, never var(--sl-rail). That variable is the visual footprint the
     live strip clears, and pointing the element's own width at it narrowed the
     rail to 84px on collapse — which squashed the wordmark from 202px to 44px,
     because a flex item with width:auto shrinks. The box is fixed so the logo
     cannot move or resize; only the labels inside it collapse. */
  position:absolute;left:0;top:0;bottom:0;z-index:30;width:242px;
  display:flex;flex-direction:column;gap:6px;padding:14px 12px}
@media (min-width:1024px){
  /* NO LEFT INSET. Luke, 5 Sep: "the rip pack button isn't central on the
     screen, and the middle pack that you're on and whatever collection you're
     on should all be central in the middle of the screen. That left menu
     doesn't take up much space, so we can still make it central." Reserving
     the rail's width pushed the whole stage 119px right of centre. .page keeps
     the product's own symmetric padding now, so the hero, the ring and RIP
     PACK all sit on the viewport's centre line — and because the reservation
     is gone in BOTH states, nothing moves when the rail opens either.
     What stops the band running under the rail is a clearance term in
     stage.js layout(), not a layout inset. */
  /* the ONE thing Luke does want to move: the live strip slides right as the
     buttons come in, and back left over the empty column when they go */
  /* style.css:1370 gives .strip its transform+opacity transition, and that is
     what carries it off the top when a rip starts — SLIDE_MS depends on it.
     This is more specific, so the shorthand had to carry those two through or
     the strip would snap away instead of sliding. */
  html[data-layout="stage"] .strip{
    /* -23px: measured on the deployed build, the LIVE dot sat at x=244 while
       the leftmost collection label started at x=221. Luke wanted the dot on
       the F of Football. Tuned at 1512 with seven collections — the ring's
       stride follows the width and the label widths differ, so this lines up
       the leftmost SLOT rather than every possible label. */
    margin-left:calc(var(--sl-rail) - clamp(16px,2.2vw,40px) - 23px);
    transition:transform 520ms cubic-bezier(.4,0,.2,1),opacity 340ms ease-out,
               margin-left .3s cubic-bezier(.22,.8,.36,1)}
}

/* THE LOGO: fixed, full wordmark, identical in both states. */
html[data-layout="stage"] .sl-side__brand{
  height:clamp(46px,5.4vh,62px);display:flex;align-items:center;
  padding:0 8px;margin-bottom:10px;flex:0 0 auto}
html[data-layout="stage"] .sl-side__word{
  height:clamp(46px,5.4vh,62px);width:auto;display:block;flex:0 0 auto}

/* THE TOGGLE sits BELOW the logo (Luke: "we can bring that arrow down"), and
   is big enough to read as a control rather than a stray glyph. It never
   slides — it is what you press to bring the buttons back. */
html[data-layout="stage"] .sl-side__toggle{
  flex:0 0 auto;align-self:flex-start;margin:0 0 10px 8px;
  width:38px;height:38px;border-radius:50%;
  border:2px solid rgba(11,15,24,.22);background:#FFF;color:var(--key);
  box-shadow:0 3px 0 0 rgba(11,15,24,.16);
  display:grid;place-items:center;cursor:pointer;
  transition:border-color .16s,color .16s,transform .16s}
html[data-layout="stage"] .sl-side__toggle svg{width:21px;height:21px}
html[data-layout="stage"] .sl-side__toggle:hover{border-color:var(--acc);color:var(--acc)}
html[data-layout="stage"] .sl-side__toggle:active{transform:translateY(2px);box-shadow:none}

/* COLLAPSED KEEPS THE ICONS. Luke, 5 Sep: "now when you close the menu, there
   are no icons there, the menu just completely disappears." Sliding the whole
   group out was too far — what he asked for originally still stands: the icons
   hold their positions and only the LABELS go. The label carries the gap as
   its own margin so both collapse together and the icon never shifts by the
   14px the flex gap would otherwise leave behind. */
html[data-layout="stage"] .sl-side[data-collapsed="1"] .sl-link span{
  max-width:0;opacity:0;margin-left:0}

/* align-self:flex-start, so the pill is only as wide as the row. Luke, 5 Sep:
   "there's loads of empty space on the right of where it says OPEN, like pink
   empty space." A flex column stretches its children by default, so the active
   sticker ran the full width of the rail whatever the word was. */
/* B7, and Luke has now asked twice: the same soft white glow behind
   OPEN/VAULT/STORE/ODDS/INFO that the foot group has. It WAS here and my rail
   rebuild deleted it — the rewrite replaced everything between .sl-side and
   .sl-link, and this rule sat in that gap, which is why only the foot kept its
   fade. NOT the foot's numbers: that scrim is tuned to the deep blue cloud in
   the plate's bottom-left and the top of the rail sits on much lighter sky.
   Measured with the §5.2 harness (ink transparent, scrim in place): the bare
   ground under the top rows is already 6.19:1 to 13.29:1, so this is a lift
   for the eye, not a contrast rescue. */
html[data-layout="stage"] .sl-side .header__nav{position:relative}
html[data-layout="stage"] .sl-side .header__nav::before{
  content:"";position:absolute;inset:-22px -44px -30px -64px;z-index:-1;pointer-events:none;
  background:radial-gradient(farthest-side at 34% 50%,
    rgba(255,255,255,.62),rgba(255,255,255,.48) 44%,rgba(255,255,255,.24) 70%,transparent 100%)}

html[data-layout="stage"] .sl-link{
  align-self:flex-start;
  display:flex;align-items:center;gap:0;height:54px;padding:0 16px 0 14px;border-radius:13px;
  color:var(--key);text-decoration:none;font-family:var(--display);font-weight:800;
  font-style:italic;font-size:23px;letter-spacing:.02em;white-space:nowrap;overflow:hidden;
  border-bottom:0;transition:background .16s,color .16s}
html[data-layout="stage"] .sl-link svg{flex:0 0 auto;width:26px;height:26px;color:rgba(11,15,24,.62)}
html[data-layout="stage"] .sl-link span{
  /* min-width:0 matters: a flex item's default min-width:auto is a floor at its
     content size, so max-width:0 alone left every label at full width. */
  display:block;transform:var(--slant);margin-left:14px;
  max-width:240px;min-width:0;overflow:hidden;white-space:nowrap;
  transition:max-width .3s cubic-bezier(.22,.8,.36,1),
             margin-left .3s cubic-bezier(.22,.8,.36,1),opacity .2s ease}
html[data-layout="stage"] .sl-link:hover{background:rgba(255,255,255,.42);color:var(--key)}
html[data-layout="stage"] .sl-link.is-active{
  color:#FFF;
  background:linear-gradient(180deg,var(--acc-hi) 0%,var(--acc) 48%,var(--acc-lo) 100%);
  box-shadow:0 0 0 2px #FFF,0 3px 0 0 rgba(11,15,24,.24)}
html[data-layout="stage"] .sl-link.is-active svg{color:#FFF}
/* B6: the icon keeps ITS OWN size in both states. It grew 26 -> 29 when the
   rail closed, which is one more thing moving under Luke's eye. */

/* the quiet group at the foot.
   LUKE'S 5 SEP PLATE PUTS A DEEP BLUE CLOUD IN THE BOTTOM-LEFT CORNER —
   measured rgb(23,106,215), which is exactly where this group sits. Dark ink
   on it is 3.72:1 and this group's dimmed ink was 2.36:1; AA wants 4.5:1.
   A soft light scrim behind the group buys it back without putting a panel
   on the plate, which is the thing Luke rejected. */
html[data-layout="stage"] .sl-side__foot{
  position:relative;margin-top:auto;display:flex;flex-direction:column;gap:6px;
  padding-top:12px;border-top:1px solid rgba(11,15,24,.20)}
html[data-layout="stage"] .sl-side__foot::before{
  content:"";position:absolute;inset:-26px -44px -44px -64px;z-index:-1;pointer-events:none;
  background:radial-gradient(farthest-side at 34% 66%,
    rgba(255,255,255,.90),rgba(255,255,255,.74) 42%,rgba(255,255,255,.40) 68%,transparent 100%)}
/* solid ink, not the .72 the dark-ground prototype used: over that cloud the
   dimmed version measured 2.28:1. The group still reads as the quiet one —
   19px against the nav's 23px, and a lighter icon — without failing AA. */
html[data-layout="stage"] .sl-side__foot .sl-link{height:44px;font-size:19px;color:var(--key)}
html[data-layout="stage"] .sl-side__foot .sl-link svg{width:21px;height:21px;color:rgba(11,15,24,.62)}
html[data-layout="stage"] .sl-side__foot .sl-link:hover{color:var(--key)}

/* the header keeps its balances and account; the nav has moved to the rail,
   and the wordmark only appears here when the rail is collapsed */
/* the wordmark stays in the rail in BOTH states now (B6), so the header never
   takes it back — that pop was the prototype's behaviour and Luke has seen it
   and does not want it. */
html[data-layout="stage"] .header__brand{display:none}
html[data-layout="stage"] #navInk{display:none}
/* #nav is the SAME ELEMENT, moved — app.js listens on it — so its header
   rules come with it and have to be turned on their side here. .navlink's
   own slant is dropped because .sl-link puts it on the words alone, or the
   icon leans too. */
html[data-layout="stage"] .sl-side .header__nav{
  display:flex;flex-direction:column;gap:6px;flex:0 0 auto;font-size:inherit;
  letter-spacing:inherit;line-height:inherit}
html[data-layout="stage"] .sl-side .navlink{
  transform:none;display:flex;padding:0 14px}

/* ── THE COLLECTION RING ────────────────────────────────────────────
   A circular ring above the packs: the active one centred, larger, dropped
   ~15px. NO GOLD UNDERLINE — Luke had it removed (HANDOFF-131 §6).
   .sl-cat is absolutely positioned inside an overflow:hidden row, so the row
   height is what keeps the icons clear of the packs. */
html[data-layout="stage"] .sl-cats{
  /* 98, not 104: the ring's own content ends 98px down (measured) */
  position:relative;z-index:200;width:100%;height:98px;flex:0 0 auto;
  overflow:hidden;user-select:none}
html[data-layout="stage"] .sl-cat{
  position:absolute;top:6px;left:50%;display:flex;flex-direction:column;align-items:center;gap:5px;
  background:none;border:0;cursor:pointer;color:rgba(11,15,24,.62);padding:4px 6px 0;
  white-space:nowrap;transform-origin:50% 0}
html[data-layout="stage"] .sl-cat img,
html[data-layout="stage"] .sl-cat svg{width:40px;height:40px;display:block}
html[data-layout="stage"] .sl-cat b{
  font-family:var(--display);font-weight:900;font-style:italic;font-size:23px;
  letter-spacing:.02em;transform:var(--slant);display:block}
html[data-layout="stage"] .sl-cat[aria-current="true"]{color:var(--key)}
html[data-layout="stage"] .sl-cat:focus:not(:focus-visible){outline:none}

/* ── THE PACK CAROUSEL ──────────────────────────────────────────────
   Coverflow. Geometry measured off the reference stage — its slots sat at
   0, ±153, ±288, ±414, ±541 — and fitted to within a pixel by
   offset(d) = stride·d + bulge·tanh(d), stride 0.907·W, bulge 0.236·W.
   The maths lives in stage.js; this only has to not fight it. */
html[data-layout="stage"] .sl-rail{
  position:relative;width:100%;flex:1 1 auto;min-height:0;
  touch-action:pan-y;cursor:grab;user-select:none;overflow:hidden}
html[data-layout="stage"] .sl-rail.is-drag{cursor:grabbing}
html[data-layout="stage"] .sl-slot{
  position:absolute;top:50%;left:50%;width:0;height:0;
  display:flex;align-items:center;justify-content:center;will-change:transform}
html[data-layout="stage"] .sl-pack{
  position:relative;display:block;flex:0 0 auto;width:var(--pw,170px);transform-origin:50% 50%}
html[data-layout="stage"] .sl-pack img{
  width:100%;display:block;pointer-events:none;
  filter:drop-shadow(0 20px 26px rgba(11,15,24,.42))}
/* The arrows moved out of the rail and in beside the dots — see stage.js.
   Floating at the band's edges they were the binding constraint on how big a
   seven-pack band could be drawn, and once the packs grew they sat on the
   artwork. Here they cost the band nothing. */
/* the arrows sit either side of the tier name; the dots get their own thin
   line back. min-width holds the arrows still as the name changes length, so
   they do not jog about between "Pro" and "Challenger". */
html[data-layout="stage"] .sl-tierrow{
  display:flex;align-items:center;justify-content:center;gap:16px}
html[data-layout="stage"] .sl-tierrow .sl-tier{
  min-width:clamp(200px,24vw,340px);text-align:center;
  display:inline-flex;align-items:center;justify-content:center;gap:.28em}
/* the sports ring: the pack's sport beside its tier, one centred unit */
html[data-layout="stage"] .sl-tier .ficon{width:.92em;height:.92em;display:block;object-fit:contain;flex:0 0 auto}
html[data-layout="stage"] .sl-dotrow{
  display:flex;align-items:center;justify-content:center;gap:14px}
/* Luke, 5 Sep 09:10, option B of the four mocked: the badge circle — white,
   the 2.5px key stroke and the solid drop the pop-up's X and the crate use,
   a drawn chevron in the key colour. Hover lifts it and turns the chevron
   pink; press sits it down on its shadow. */
html[data-layout="stage"] .sl-arrow{
  width:44px;height:44px;border-radius:50%;border:2.5px solid var(--key);
  background:#FFF;color:var(--key);cursor:pointer;box-shadow:0 3px 0 var(--key);
  display:grid;place-items:center;padding:0;flex:0 0 auto;
  transition:color .16s,transform .16s cubic-bezier(.22,.8,.36,1),box-shadow .16s}
html[data-layout="stage"] .sl-arrow svg{display:block;width:22px;height:22px}
html[data-layout="stage"] .sl-arrow svg path{stroke-width:3.2}
html[data-layout="stage"] .sl-arrow:hover{color:var(--acc);transform:translateY(-2px);box-shadow:0 5px 0 var(--key)}
html[data-layout="stage"] .sl-arrow:active{transform:translateY(2px);box-shadow:0 1px 0 var(--key)}
html[data-layout="stage"] .sl-arrow:focus-visible{outline:3px solid var(--acc);outline-offset:3px}
/* one pack has nowhere to go — Sports, in the real catalogue */
html[data-layout="stage"] .sl-arrow[hidden]{display:none}

/* THE HOVER JITTER (Luke, 5 Sep 10:20: "if I hover right at the bottom of
   any of the buttons ... my mouse rapidly changes ... the button bounces
   rapidly"). Every lifted control moves its own box up on :hover, so a
   pointer resting on its bottom 2–3px is left outside the box, the hover
   ends, the box drops back under the pointer, the hover starts — a loop at
   frame rate. The pointer never left the control the user sees; only the
   box moved. Fix: a pseudo-element that hangs below the box by more than
   the lift, part of the element for hit-testing, invisible. The buttons
   are style.css's (.rip, .whatsinside, .hit); overridden here, never
   edited there. .cratebtn and .sl-arrow are this file's own. */
/* NOT .rip (Luke, 6 Sep 03:50: "on desktop it's the same button without the
   dots"). .stick clips its overflow, so a hit-zone hanging below RIP PACK
   never hit-tested (measured: the element 4px under the button is .sl-meta),
   and .hring>.rip{transform:none} (style.css:1295) already stops its hover
   lift — the only thing this ::after did to RIP PACK was replace .stick's
   halftone dots (style.css:126) with a transparent box. */
html[data-layout="stage"] .whatsinside,
html[data-layout="stage"] .hit,
html[data-layout="stage"] .sl-arrow,
html[data-layout="stage"] .sl-crate .cratebtn{position:relative}
html[data-layout="stage"] .whatsinside::after,
html[data-layout="stage"] .hit::after,
html[data-layout="stage"] .sl-arrow::after,
html[data-layout="stage"] .sl-crate .cratebtn::after{
  content:"";position:absolute;left:0;right:0;top:100%;height:8px;background:transparent}

/* ── UNDER THE PACK ─────────────────────────────────────────────────
   The TIER NAME, not the price: the price is already printed on the art
   (HANDOFF-131 §1). Then the dots, then RIP PACK, then TURBO.

   TURBO SITS UNDER RIP PACK ON DESKTOP — Luke, 5 Sep, choosing the
   prototype's order over the live site's. The new layout puts two more rows
   (tier name, dots) between the pack and the button than the old one did, so
   the button wants to stay as close to the pack as it can. The phone keeps
   the product's own order, turbo above, because RIP PACK has to be the
   bottom-most full-width target in the dock. */
html[data-layout="stage"] .sl-meta{
  display:flex;flex-direction:column;align-items:center;gap:6px;margin-top:auto;flex:0 0 auto;
  /* the crate makes room below (see .sl-meta's rule under [data-crate]) */
  transition:transform .42s cubic-bezier(.2,.8,.3,1)}

/* ── THE BOTTOM CLUSTER GETS OUT OF THE WAY (Luke, 10 Sep) ─────────────
   "I wanted it so that when the crate slid up to the middle, the two arrows
   either side of Rookie, the little lines underneath Rookie, the rip pack
   button, the [turbo] button, the record button, and the what's inside
   button all slid down ... and then when you press close, this whole
   section slides back up."

   Every one of those six is already inside ONE box — .sl-meta holds
   .sl-tierrow (the arrows and the name), .sl-dotrow (the little lines),
   .hring--rip and .turborow (turbo, record, what's inside) — so the whole
   section is one transform, and .sl-meta is pinned by margin-top:auto, so
   moving it moves nothing else.

   IT GOES ALL THE WAY DOWN, not part of the way. The first attempt at this
   nudged it 12px and lifted the crate 40px to make up the difference, which
   is backwards twice over: the 12px was invisible, and the crate already
   slides up to the middle on its own — that is the corner FLIP, and it is
   the motion he was describing, not a request for extra height. So the
   crate's own geometry is untouched and the section leaves the screen
   entirely: its own height plus 40px, where the distance it actually has
   to travel is its height plus the gap under it — 129 + 17 at 1147x655,
   201 + 28 at 1512x982. 24px was tried first and left a 4px sliver of the
   arrows showing on the taller window.
   Nothing can overlap CLOSE because nothing is left under it.

   crate-stage.js sets data-crate while the pop-up is up and drops it the
   moment CLOSE is pressed, so the section is already on its way back while
   the crate flies home. */
html[data-layout="stage"][data-crate] .sl-meta{transform:translateY(calc(100% + 40px))}
html[data-layout="stage"] .sl-tier{
  font-family:var(--display);font-weight:900;font-style:italic;
  font-size:clamp(30px,4.2vw,50px);line-height:.95;color:var(--key);
  font-variant-numeric:tabular-nums;transform:var(--slant);
  text-shadow:0 2px 0 rgba(255,255,255,.45)}
html[data-layout="stage"] .sl-dots{display:flex;gap:7px}
/* an author display: wins over the UA sheet's [hidden]{display:none}, so a
   one-pack collection kept showing its lone dot until this was spelled out —
   the same reason .sl-arrow[hidden] is spelled out below. */
html[data-layout="stage"] .sl-dots[hidden]{display:none}
html[data-layout="stage"] .sl-dot{
  width:7px;height:7px;border-radius:50%;background:rgba(11,15,24,.24);
  border:0;padding:0;cursor:pointer;transition:width .18s,background .18s}
html[data-layout="stage"] .sl-dot[aria-current="true"]{background:var(--acc);width:22px;border-radius:4px}
/* the stage's own children stop being centred by .stage's rules and take
   their order from .sl-meta instead */
/* A1, Luke 5 Sep: "the packs are too small" — he wants RIP PACK, TURBO and
   WHAT'S INSIDE further DOWN so the carousel can be taller. .sl-rail is the
   only flex:1 child, so every pixel taken back here goes to it and pushes the
   controls down by the same amount. Measured at 1512x900: 26+6+9+4 = 45px
   back, rail 383 -> 428.
   WHAT ACTUALLY BINDS THE PACK SIZE IS NOT ALWAYS THE HEIGHT. layout() takes
   min(w*0.205, (h*0.94)/ASPECT, 280) and then shrinks until the band fits
   across. For six of the seven collections the HEIGHT term binds and this
   section is the whole fix. For Pokemon — seven packs — the across-fit binds
   first, so its packs grow only as far as that rule allows (see stage.js
   layout(), where the clearance now measures the outermost pack at the size
   it is actually drawn). */
html[data-layout="stage"] .stage{padding-bottom:8px}
html[data-layout="stage"] .sl-meta .hring--rip{margin-top:clamp(6px,1vh,10px)}
html[data-layout="stage"] .stage__inner{display:none}
/* THE INFO DOT HAS TO COME BACK INLINE. rips-glue.css:406 pins .turborow__side
   at top:-38px so the dot rides above-left of the pill — which was right while
   turbo sat ABOVE the button, and lands the dot on the rip button now that it
   sits below. Turbo, its dot and What's Inside become one centred row, which
   is the prototype's arrangement and the dock's. */
html[data-layout="stage"] .sl-meta .turborow.stage__row{
  display:flex;align-items:center;justify-content:center;gap:12px;margin-top:6px}
html[data-layout="stage"] .sl-meta .turborow>[aria-hidden]{display:none}
html[data-layout="stage"] .sl-meta .turborow__side{
  position:static;margin:0;justify-self:auto}
html[data-layout="stage"] .sl-meta .turborow .turbo{transform:none}
html[data-layout="stage"] .pay{margin-top:10px}

/* ── COLLECTION BACKGROUNDS (Luke, 5 Sep) ──────────────────────────
   Two crossfading layers stage.js appends INSIDE .bg — under everything,
   clipped by .bg's overflow:hidden, painted over .bg's own plate. Both off
   means the default (Pokémon) plate shows. stage.js sets the file; the
   phone gets the portrait crop at the same 1023px break the plate uses. */
html[data-layout="stage"] .sl-bg{
  position:absolute;inset:0;background:center center/cover no-repeat;
  opacity:0;transition:opacity .5s ease;pointer-events:none}
html[data-layout="stage"] .sl-bg[data-on="1"]{opacity:1}
@media (prefers-reduced-motion:reduce){html[data-layout="stage"] .sl-bg{transition:none}}

/* ── THE CRATE, BOTTOM-RIGHT (HANDOFF-133 §4) ─────────────────────
   The Rank & Crate drawer is gone (Luke, 5 Sep: "i dont wanna have that
   thing on the right side that slides out anymore"). stage.js moves
   #v3CrateBtn and .daily — the same nodes rips-glue.js's mirrorRank() writes
   #timer and #v3CrateLbl into — to a fixed cluster in the corner, "with the
   timer like normal". The art is Luke's 5 Sep chest; DAILY CRATE and the
   digits keep style.css's own bright-ground treatment (white fill, --key
   stroke, style.css:1856) because they sit on the sky now, not in a panel.
   It leaves with the rest of the chrome on a rip. */
html[data-layout="stage"] .sl-crate{
  position:fixed;right:clamp(12px,1.4vw,22px);bottom:clamp(6px,1vh,12px);z-index:5;   /* in .page: over the packs (3), under the header (6) — stage.js */
  display:flex;flex-direction:row;align-items:flex-end;gap:clamp(6px,.8vw,12px);
  transition:transform .52s cubic-bezier(.22,.8,.36,1),opacity .52s ease}
/* Luke, 5 Sep 06:39: no DAILY CRATE title, and the crate sits to the LEFT of
   the timer, lower — a row, bottom-aligned, in the corner. #v3CrateLbl keeps
   the state sentence ("Rip a pack to unlock" / "Next crate") over the digits. */
html[data-layout="stage"] .sl-crate .cratebtn{
  display:block;width:clamp(130px,12.5vw,190px);padding:0;border:0;background:none;cursor:pointer;
  margin-bottom:-4px;transition:transform .18s cubic-bezier(.22,.8,.36,1)}
html[data-layout="stage"] .sl-crate .cratebtn:hover{transform:translateY(-3px)}
html[data-layout="stage"] .sl-crate .crate{
  display:block;width:100%;height:auto;max-height:none;margin:0;
  filter:drop-shadow(0 6px 0 rgba(11,15,24,.28))}
html[data-layout="stage"] .sl-crate .daily{margin:0 0 6px;text-align:right;position:static;min-height:0}
html[data-layout="stage"] .sl-crate .daily__t,
html[data-layout="stage"] .sl-crate .daily .swoosh{display:none}
html[data-layout="stage"] .sl-crate .daily__lbl{font-size:15px;margin:0;position:static;white-space:nowrap}
html[data-layout="stage"] .sl-crate .daily__timer{font-size:clamp(26px,2.5vw,36px);position:static;margin:-2px 0 0;white-space:nowrap}
html[data-layout="stage"][data-state="rip"] .sl-crate{transform:translateY(140%);opacity:0;pointer-events:none}

/* ── RANK IN THE HEADER (HANDOFF-133 §5) ──────────────────────────
   A pill at the right end of .header__right, drawn like the balance pills
   beside it: the tier badge and #v3RankName, which mirrorRank() keeps
   writing and rips-glue.css:494 keeps colouring off html[data-tier]. The
   balances shift left to make room, which is what Luke asked for. The XP
   bar and the ladder are NOT here yet — they are parked (stage.js §3)
   until he settles where they go. */
/* Luke, 5 Sep 08:05: the balances, +ADD, the avatar and the speaker go all
   the way back to the right; the rank (badge, name, bar) sits PERFECTLY
   CENTRAL in the header. So the block leaves the flex row and is pinned to
   the header's centre; .header__right keeps margin-left:auto and lands on
   the right edge again. */
html[data-layout="stage"] .header{position:relative}
html[data-layout="stage"] .sl-rank{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  height:auto;padding:0;margin:0;border:0;background:none;
  display:flex;align-items:center;gap:8px;cursor:pointer;color:#FFF;transition:transform .14s}
html[data-layout="stage"] .sl-rank:hover{transform:translate(-50%,-50%) translateY(-1px)}
/* the tier name in its own colour with the plate's dark keyline, the same
   pop-art treatment the rest of the sky-side type wears */
html[data-layout="stage"] .sl-rank .rank__name{
  font-size:24px;margin:0;line-height:1;letter-spacing:.01em;transform:var(--slant);display:block;
  -webkit-text-stroke:4px var(--key);paint-order:stroke fill;padding:2px 2px 0 0}
/* THE XP BAR (Luke, 5 Sep 07:10): thick enough to carry the amounts, the
   next tier's badge sitting ON its end. #v3RankBar / #v3RankFill / #v3RankVal
   are rank.js's nodes via mirrorRank(); only their box changes here. */
html[data-layout="stage"] .sl-xp{position:relative;display:flex;align-items:center;margin-right:14px}
html[data-layout="stage"] .sl-rank .rank__bar{
  width:clamp(130px,12vw,200px);height:22px;margin:0;border-radius:12px;box-sizing:border-box;
  background:rgba(11,15,24,.30);border:2.5px solid var(--key);overflow:hidden}
html[data-layout="stage"] .sl-rank .rank__bar i{
  display:block;height:100%;border-radius:10px;
  background:linear-gradient(90deg,var(--tier,var(--gold)),color-mix(in srgb,var(--tier,var(--gold)) 55%,#FFF));
  transition:width .6s cubic-bezier(.22,.8,.36,1)}
html[data-layout="stage"] .sl-rank .rank__val{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;margin:0;
  font-size:12.5px;font-weight:900;letter-spacing:.02em;color:#FFF;
  -webkit-text-stroke:2.5px var(--key);paint-order:stroke fill;pointer-events:none;white-space:nowrap}
html[data-layout="stage"] .sl-rank .rank__val b{color:#FFF}
html[data-layout="stage"] .sl-badge{display:inline-flex;align-items:center;justify-content:center;line-height:0}
html[data-layout="stage"] .sl-badge img,
html[data-layout="stage"] .sl-badge svg{display:block;filter:drop-shadow(0 2px 0 rgba(11,15,24,.28))}
html[data-layout="stage"] .sl-badge--header{width:44px;height:48px}
html[data-layout="stage"] .sl-badge--header img{width:44px;height:48px;object-fit:contain}
html[data-layout="stage"] .sl-badge--next{position:absolute;right:-15px;top:50%;transform:translateY(-50%);width:30px;height:34px}
html[data-layout="stage"] .sl-badge--next img{width:30px;height:34px;object-fit:contain}
html[data-layout="stage"] .sl-badge--next[hidden]{display:none}
/* centred, the block has the balances to its right and the logo to its
   left; under ~1360px the bar would run into the balances, so the badge and
   the name stay centred and the bar goes */
@media (max-width:1360px){html[data-layout="stage"] .sl-xp{display:none}}
/* the account menu's badge, top-right corner (§5.2 item 6). .pop is
   position:absolute, so it is the containing block. */
html[data-layout="stage"] .sl-badge--menu{position:absolute;top:10px;right:12px;width:44px;height:50px}
html[data-layout="stage"] .sl-badge--menu img{width:44px;height:50px;object-fit:contain}
html[data-layout="stage"] #acctMenu .menu__head{padding-right:68px}
/* signed out there is no rank to show, and the pill would sit next to
   SIGN IN saying BRONZE */
html[data-layout="stage"] .header__right:has(#acctBtn[hidden]) .sl-rank{display:none}

/* ── THE AVATAR (§5.2 item 5) ─────────────────────────────────────
   style.css:172 draws it as a pink outline with pink ink on a dark header,
   which is why Luke's "T" was hard to see. Filled pink, white letter, and
   one fixed box, face, size and weight, so any initial looks the same.
   Overridden here, never edited there (HANDOFF-130 §3). */
html[data-layout="stage"] .avatar{
  background:var(--acc);border:2px solid var(--acc);color:#FFF;
  font-family:var(--display);font-size:21px;font-weight:900;font-style:normal;
  line-height:1;letter-spacing:0;text-transform:uppercase;overflow:hidden}

/* ── WHAT'S INSIDE ──────────────────────────────────────────────────
   It lived on .rail--left, which this layout deletes (§3.1). The dock
   already pairs it with turbo on the phone, so it pairs with turbo here
   too — same precedent, same surface, grailpool.js untouched. */
html[data-layout="stage"] .sl-meta .whatsinside{margin-top:0;height:36px;padding:0 14px}

/* ── THE RIP ────────────────────────────────────────────────────────
   The carousel, the ring and the drawer all have to leave when a rip
   starts, or they sit over the reveal. Same pattern as style.css:1372 —
   things above go up, things below go down, and the whole set is
   pointer-inert while it is away. */
html[data-layout="stage"] .sl-side,
html[data-layout="stage"] .sl-cats,
html[data-layout="stage"] .sl-rail{
  transition:transform .52s cubic-bezier(.22,.8,.36,1),opacity .52s ease}
/* THE LOGO NEVER LEAVES (Luke, 5 Sep 23:53): the rail's buttons go, the
   wordmark stays put through the rip and the post-rip. */
html[data-layout="stage"] .sl-side>:not(.sl-side__brand){
  transition:transform .52s cubic-bezier(.22,.8,.36,1),opacity .52s ease}
html[data-layout="stage"][data-state="rip"] .sl-side>:not(.sl-side__brand){transform:translateX(-360px);opacity:0}
html[data-layout="stage"][data-state="rip"] .sl-cats{transform:translateY(-160%);opacity:0}
html[data-layout="stage"][data-state="rip"] .sl-tierrow,
html[data-layout="stage"][data-state="rip"] .sl-dotrow{transform:translateY(120px);opacity:0}
html[data-layout="stage"][data-state="rip"] .sl-side,
html[data-layout="stage"][data-state="rip"] .sl-cats,
html[data-layout="stage"][data-state="rip"] .sl-rail,
html[data-layout="stage"][data-state="rip"] .sl-tierrow,
html[data-layout="stage"][data-state="rip"] .sl-dotrow{pointer-events:none}
/* THE PACKS LEAVE ON THE REEL, AND STAGE.JS DRIVES IT (see EXIT_MS there).
   Nothing here may set their opacity or transform: paint() writes both inline
   on every frame and would win anyway — which is exactly why the plain
   `.sl-slot{opacity:0}` this replaces never did anything. The rail keeps its
   box, because the live pack draws in it and collapsing it would move the
   pack mid-rip. */
@media (prefers-reduced-motion:reduce){
  html[data-layout="stage"][data-state="rip"] .sl-side>:not(.sl-side__brand),
  html[data-layout="stage"][data-state="rip"] .sl-cats,
  html[data-layout="stage"][data-state="rip"] .sl-crate,
  html[data-layout="stage"][data-state="rip"] .sl-tierrow,
  html[data-layout="stage"][data-state="rip"] .sl-dotrow{transform:none}
}

/* ── THE PHONE ──────────────────────────────────────────────────────
   §3.9: the product already owns the phone — .mhead, .mcoll, .mdock and
   .mnav — and it wins. What comes across from the prototype is the CONTENT
   decision, not a second bottom bar: the ring and the carousel replace the
   ‹ POKÉMON $25 › row, turbo stays in the dock where it is, and What's
   Inside stays beside it there.

   THE BREAKPOINT IS 1023px, NOT THE PROTOTYPE'S 900. That is where
   style.css:864 turns the dock on, and a layout that changed at 900 would
   leave a 123px band showing the dock AND the desktop controls at once.

   AND THE DESKTOP CONTROLS MUST GO. style.css:900 hides them with
   `.stage>.hring--rip`, and its own comment (style.css:896) records why the
   child combinator is load-bearing: rip-hold.js wraps the button in .hring,
   so .rip is already a grandchild. Moving that .hring into .sl-meta breaks
   the selector again and puts the desktop RIP PACK back on the phone under
   the dock — which is exactly what it did. Hidden here by the new path. */
@media (max-width:1023px){
  html[data-layout="stage"] .sl-side{display:none}
  /* the phone has its own chest FAB and the Daily Rewards sheet (mobile.js) */
  html[data-layout="stage"] .sl-crate,
  html[data-layout="stage"] .sl-rank{display:none}
  /* the dock owns these on a phone */
  html[data-layout="stage"] .sl-meta .hring--rip,
  html[data-layout="stage"] .sl-meta .turborow.stage__row,
  html[data-layout="stage"] .sl-meta .pay{display:none}
  /* the ring and the dots do .mcoll's job now */
  html[data-layout="stage"] .mcoll{display:none}
  html[data-layout="stage"] .sl-cats{height:80px}
  html[data-layout="stage"] .sl-cat{gap:0;top:6px;padding:0}
  /* Luke, 6 Sep 01:45 (phone pass, from his screenshot): "the text One
     Piece / Sports / Pokémon needs to be bigger, the icon raised, the other
     two icons slightly bigger, and they need to always have the collection
     underneath them like on the real site, in the off-black". So the phone
     ring is the desktop ring, scaled: every icon named, the label in flow
     under it (the earlier absolute label at 15px sat on the neighbours'
     icons), the inactive two in the desktop's rgba(11,15,24,.62). The side
     scale (.84) and the centre dip (6px) are stage.js's phone values. */
  html[data-layout="stage"] .sl-cat{gap:4px;top:2px}
  html[data-layout="stage"] .sl-cat b{font-size:18px;letter-spacing:.015em;line-height:1.1}
  /* Luke, 6 Sep 02:05: "add the same arrows from the site to the left and
     right of where it says Starter". They already flank .sl-tier in
     .sl-tierrow (stage.js); the phone hid them for swipe. Shown again, a
     size down (36 for the phone's ~31px name; the desktop's 44 sits with
     50px type), and the hover lift is neutralised — a tap would leave it
     stuck pink until the next touch. [hidden] (one-pack Sports) still wins. */
  html[data-layout="stage"] .sl-arrow{width:36px;height:36px;box-shadow:0 2.5px 0 var(--key)}
  html[data-layout="stage"] .sl-arrow svg{width:18px;height:18px}
  html[data-layout="stage"] .sl-arrow:hover{color:var(--key);transform:none;box-shadow:0 2.5px 0 var(--key)}
  html[data-layout="stage"] .sl-arrow:active{transform:translateY(2px);box-shadow:0 .5px 0 var(--key)}
  html[data-layout="stage"] .sl-tierrow{gap:12px}
  html[data-layout="stage"] .sl-tierrow .sl-tier{min-width:150px}   /* 200 put the prev arrow against the info dot (x 47) */
  /* Luke, 6 Sep 02:05: "a lot of space between the icons at the top and the
     live feed ... move the icons up and then move the packs up and keep the
     Starter thing where it is". style.css:912 pads .shell by --mh-coll (44px)
     for the product's ‹ POKÉMON $25 › row, which this layout hides (.mcoll
     above) — so it was 44px of nothing under the strip. 8px stays as the
     breath under the strip; the 36px goes to .sl-rail (the flex:1 child), so
     the ring and the packs rise and .sl-meta, pinned to the stage's bottom by
     margin-top:auto, does not move. mobile.js's corridor reads .mcoll, not
     this padding, so the 3D pack's fit is untouched. */
  html[data-layout="stage"] .shell{padding-top:calc(var(--mh-top) + var(--mh-head) + var(--mh-strip) + 16px)}

  /* ── Luke, 6 Sep 02:20, second look at eval ─────────────────────────
     1. "the live feed bar is slightly too close to the sign-in button ...
        basically touching the white ring" — the strip's top was the header's
        bottom (style.css:971) and the pink stick's outer ring ends 4px above
        it. Down 8px; the shell padding above carries the ring with it.
     2. "remove the shadow behind the live feed bar" — the cards' drop and the
        slab's drop (stage.css:657/668) go on the phone; the keylines stay.
     3. "there's a shadow behind the packs ... completely remove the shadow on
        mobile" — the artwork's drop-shadow (stage.css:300).
     4. "the What's Inside button on mobile is different from the desktop
        site ... make it the same, without the shadow" — the desktop's on this
        layout is style.css:1873: the yellow chip, 2.5px key border, key ink.
        mobile.js's #mWi is untouched; only its paint changes. */
  html[data-layout="stage"] .strip{top:calc(var(--mh-top) + var(--mh-head) + 8px)}
  html[data-layout="stage"] .sl-feed .hit,
  html[data-layout="stage"] .sl-feed .hit[data-rarity],
  html[data-layout="stage"] .sl-feed .hit:hover{box-shadow:none}
  html[data-layout="stage"] .sl-feed .hit__img{box-shadow:none}
  html[data-layout="stage"] .sl-pack img{filter:none}
  html[data-layout="stage"] .mwi{background:var(--yellow);border:2.5px solid var(--key);color:var(--key);box-shadow:none}
  html[data-layout="stage"] .mwi b{color:var(--key)}
  html[data-layout="stage"] .mwi__q{border:2px solid var(--key);color:var(--key)}
  html[data-layout="stage"] .sl-meta{gap:4px;margin-top:0;padding-bottom:6px}
  html[data-layout="stage"] .sl-tier{font-size:clamp(26px,8vw,38px)}
  html[data-layout="stage"] .sl-dots{gap:5px}
  html[data-layout="stage"] .sl-dot{width:6px;height:6px}
  /* the stage is a flex column between the fixed chrome; the carousel takes
     the slack and nothing is allowed to widen the page */
  /* .shell goes justify-items:center on a phone (style.css:868) and the
     stage is a grid item, so without this it shrinks to its content — 88px
     once the desktop controls are hidden, which squeezes the ring to three
     icons and leaves the carousel with no neighbours. */
  html[data-layout="stage"] .stage{padding-bottom:0;min-height:0;
    width:100%;justify-self:stretch}
  html[data-layout="stage"] .sl-rail{min-height:0}
  html[data-layout="stage"] .sl-cats,
  html[data-layout="stage"] .sl-rail,
  html[data-layout="stage"] .sl-meta{max-width:100%}
}

/* rips-glue.js paints the reel glyphs once at boot, behind the page, and
   removes them five seconds later. On the live site the opaque #stageLayer
   canvas covers them; this layout hides that canvas while you browse, which
   left them legible at 2% over the plate. One layer further back puts .bg
   over them and restores exactly the live site's result — still painted, so
   the glyphs still warm. !important because rips-glue.js sets that z-index
   inline, and an inline style beats a stylesheet rule without it. */
html[data-layout="stage"] .glyphwarm{z-index:-2!important}

/* ── C1: LIVE, AND NOTHING BEHIND IT ────────────────────────────────
   Luke, 5 Sep: "can we just take that completely out of the box, with no pink
   thing behind it even? Just keep it just saying LIVE." So the navy fill
   (style.css:182 and again at :1864), the masked pink keyline (:189) and the
   fixed 132px box all go, and the word takes the dark --key ink instead of
   the pink --acc-hi it is given at :2090 — it sits on the bright plate now,
   not in a navy chip. Overridden here, never edited there: style.css is the
   product's and both layouts read it (HANDOFF-130 §3). The prototype settled
   this same look on the phone; this is that, on every width. */
html[data-layout="stage"] .strip__live{
  width:auto;flex:0 0 auto;background:none;border:0;border-radius:0;
  padding:0 6px 0 2px;color:var(--key)}
html[data-layout="stage"] .strip__live::before{display:none}

/* ── THE LIVE FEED CARDS (Luke, 5 Sep 11:20, from a ChatGPT mock): "sitting
   in a translucent card-type thing ... the name at the top, then in pink
   the price, and where they've put the number of seconds ago, just put the
   PSA grade. Don't put the number of hours ago." No bolts between them.
   style.css's .hit is a navy chip with a rarity-coloured keyline and glow;
   on the bright plate it reads as a dark block. Overridden here: a frosted
   white card with a soft drop, the slab a touch bigger, the name in the
   display italic in the key ink, the price first and pink, the grade in a
   quiet caps label, the time gone. Rarity no longer colours the frame —
   every card is the same white; the price and the slab carry the news. */
html[data-layout="stage"] .hit,
html[data-layout="stage"] .hit[data-rarity]{
  gap:12px;padding:0 16px 0 9px;border-radius:14px;
  background:linear-gradient(180deg,rgba(255,255,255,.46),rgba(255,255,255,.30));
  border:1.5px solid rgba(255,255,255,.72);
  box-shadow:0 8px 18px -10px rgba(11,15,24,.45),0 1px 0 rgba(255,255,255,.55) inset;
  }
/* NO backdrop-filter here. Luke picked the blurred mock (B), but a backdrop
   blur on a dozen cards inside a marquee re-blurs the moving plate every
   frame: measured 12 frames in 3s with it on, 181 without (5 Sep 11:55).
   The translucent white alone is what he saw as 'the same'. */
html[data-layout="stage"] .hit[data-rarity]:hover,
html[data-layout="stage"] .hit:hover{border-color:#FFF;
  box-shadow:0 12px 22px -10px rgba(11,15,24,.5),0 1px 0 rgba(255,255,255,.6) inset}
html[data-layout="stage"] .hit__img{width:40px;height:56px;border-radius:5px;
  border:1.5px solid rgba(255,255,255,.85);background:rgba(11,15,24,.12);
  box-shadow:0 3px 7px -2px rgba(11,15,24,.45)}
html[data-layout="stage"] .hit__n{
  font-family:var(--display);font-weight:800;font-style:italic;font-size:15px;line-height:1.05;
  color:var(--key);letter-spacing:.005em;max-width:170px}
html[data-layout="stage"] .hit__u{
  font-family:var(--display);font-size:12px;font-weight:900;font-style:italic;
  color:var(--acc-hi);line-height:1;max-width:170px}
html[data-layout="stage"] .hit__m{gap:8px;margin-top:4px;font-family:var(--display);font-style:italic;font-weight:800;font-size:14px;line-height:1}
html[data-layout="stage"] .hit__m b{order:-1;color:var(--acc);font-size:15px}
html[data-layout="stage"] .hit__grade{
  font-family:inherit;font-style:normal;font-weight:700;font-size:11.5px;letter-spacing:.06em;
  text-transform:uppercase;color:rgba(11,15,24,.58)}
html[data-layout="stage"] .hit__m i{display:none}

/* ── B2: THE SUPPORT PANEL ──────────────────────────────────────────
   The shell is the site's own .ov / .ov__panel, so the frame, the starburst
   dots, the head and the close button all come from style.css unchanged.
   Only the two rows inside it are new. */
html[data-layout="stage"] .sup__body{
  display:flex;flex-direction:column;gap:18px;align-items:flex-start}
html[data-layout="stage"] .sup__addr{
  font-family:var(--display);font-weight:900;font-style:italic;
  font-size:clamp(22px,2.6vw,34px);color:var(--acc);letter-spacing:.01em;
  word-break:break-all;user-select:all}
html[data-layout="stage"] .sup__row{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
html[data-layout="stage"] .sup__copy,
html[data-layout="stage"] .sup__mail{
  font-family:var(--display);font-weight:800;font-style:italic;font-size:17px;
  height:44px;padding:0 20px;border-radius:12px;display:inline-flex;align-items:center;
  cursor:pointer;text-decoration:none;border:2px solid var(--acc);
  transition:background .16s,color .16s}
html[data-layout="stage"] .sup__copy{background:var(--acc);color:#FFF}
html[data-layout="stage"] .sup__copy:hover{background:var(--acc-lo);border-color:var(--acc-lo)}
html[data-layout="stage"] .sup__mail{background:none;color:var(--acc)}
html[data-layout="stage"] .sup__mail:hover{background:rgba(var(--acc-rgb),.14)}

/* ── E2: THE BLUR BEHIND THE PRIVY MODAL ────────────────────────────
   Luke, 5 Sep: "the blur behind the Privy modal is horrible, it's making my
   eyes water."

   It is NOT ours. Measured on blockrips-eval with the modal open, the only
   visible element carrying a backdrop-filter is Privy's own
   #privy-dialog-backdrop: blur(3px) over a background of rgba(0,0,0,0) —
   a blur with NOTHING behind it. Every one of our overlays (.ov, .auth,
   .addf, .wd, .inspect, .codds ...) was hidden at zero opacity at the time,
   so rips-glue.css:85 is innocent.
   A 3px blur and no scrim is the worst possible treatment for this
   background: the plate is a high-frequency halftone under hard-edged
   lightning, and blurring it without darkening it sets the whole field
   shimmering. So: no blur, and a plain dark scrim instead — the thing the
   handoff suggested trying first.

   It is Privy's element in our document, so we can style it, but the id is
   theirs and a Privy version bump could rename it. If the blur ever comes
   back, re-run the probe: open Sign In and list every element whose computed
   backdrop-filter is not 'none'. !important because we do not control the
   specificity or origin of whatever Privy sets it with. */
html[data-layout="stage"] #privy-dialog-backdrop{
  -webkit-backdrop-filter:none!important;backdrop-filter:none!important;
  background:rgba(11,15,24,.62)!important}

/* ── PICK YOUR NAME (Luke, 6 Sep 06:33, his desktop screenshot). The sheet
   is auth.js's (do-not-edit): it injects its own <style> and builds
   #authNameForm. Two things were wrong and both are fixed from here:
   "a box inside a box" — rips-glue.css:199 puts a bordered ink box on
   `.auth input` while auth.js's .auth__row already draws a bordered box
   round the same input (plus the @ glyph), so each field painted twice.
   Now the ROW is the field (ink well, 2px key, 12px corners, pink on
   focus — the language of .pvcard/.addf__addr) and the input inside it is
   naked. And there is no X: the step is forced (stage.js nameStep), the
   only exit is the "Sign out" line stage.js appends. The panel rides the
   scale token like every other overlay panel (HANDOFF-141 §1); its vw
   clamps are pinned at their 1470 values for the same reason. */
html[data-layout="stage"] .auth__panel{
  zoom:var(--sl-s,1);width:400px;max-width:calc(92vw / var(--sl-s,1));
  padding:30px 28px 26px;text-align:center}
html[data-layout="stage"] .auth__x{display:none}
html[data-layout="stage"] .auth__title{margin:0;font-size:38px}
html[data-layout="stage"] .auth__sub{margin:12px 0 18px;font-size:15px;color:#A7B0BE}
html[data-layout="stage"] .auth .auth__row{
  gap:10px;padding:0 14px;border-radius:12px;border:2px solid var(--key);background:#0B0F18;
  transition:border-color .15s ease-out;text-align:left}
html[data-layout="stage"] .auth .auth__row:focus-within{border-color:var(--acc)}
html[data-layout="stage"] .auth .auth__row input,
html[data-layout="stage"] .auth .auth__row input:focus{
  background:transparent;border:0;border-radius:0;padding:12px 0;font-size:17px;font-weight:600;color:#fff;box-shadow:none}
html[data-layout="stage"] .auth .auth__row input::placeholder{color:#5F6B7D;font-weight:500}
html[data-layout="stage"] .auth .auth__row input:-webkit-autofill,
html[data-layout="stage"] .auth .auth__row input:-webkit-autofill:hover,
html[data-layout="stage"] .auth .auth__row input:-webkit-autofill:focus{
  -webkit-box-shadow:0 0 0 1000px #0B0F18 inset;box-shadow:0 0 0 1000px #0B0F18 inset}
html[data-layout="stage"] .auth__at{color:var(--acc-hi);font-weight:800;font-size:19px;line-height:1}
html[data-layout="stage"] .auth__field{
  margin:16px 0 8px;align-items:baseline;gap:8px;
  font-family:var(--display);font-weight:800;font-style:italic;font-size:17px;letter-spacing:.02em;color:#fff}
html[data-layout="stage"] .auth__field span{font-weight:600;font-size:14px;color:#A7B0BE}
html[data-layout="stage"] .auth__primary{margin-top:22px}
html[data-layout="stage"] .auth__err{margin-top:12px;font-size:13px}
html[data-layout="stage"] .auth__err:empty{display:none}
html[data-layout="stage"] .sl-auth__out{
  display:block;margin:14px auto 0;padding:4px 6px;border:0;background:none;
  font:inherit;font-size:14px;color:#A7B0BE;cursor:pointer;
  text-decoration:underline;text-underline-offset:3px}
html[data-layout="stage"] .sl-auth__out:hover{color:#fff}
/* signed in with no name: the sheet is up, the header offers no button
   (stage.js nameStep sets the class off #signInBtn reading "Pick a name") */
html[data-layout="stage"].sl-nameless #signInBtn{display:none!important}

/* ── THE POKÉDEX (HANDOFF-139 §3; site/pokedex.js) ────────────────────
   The rail row is an .sl-link inside #nav after OPEN, so every rail rule
   above already dresses it; only its count chip is new. The chip collapses
   with the label. The catch overlay (.catch-ov, z 1000) and the panel modal
   (.dexmodal, z 1100) are body children — above everything in .fit
   (HANDOFF-140 §2) — and the modal's panel rides the scale token with its
   vw/vh pinned, like every other overlay panel (HANDOFF-141 §1). */
html[data-layout="stage"] .sl-link--dex{position:relative}
html[data-layout="stage"] .sl-link__dot,
html[data-layout="stage"] .mnav__dot{
  position:absolute;width:11px;height:11px;border-radius:50%;background:#E5322D;
  border:2px solid #FFF;box-shadow:0 1px 0 rgba(11,15,24,.35);pointer-events:none}
html[data-layout="stage"] .sl-link__dot{left:30px;top:12px}
html[data-layout="stage"] .sl-link__dot[hidden],
html[data-layout="stage"] .mnav__dot[hidden]{display:none}
/* the stand-in anchor when the card has no image (rips-glue.css:340's box) */
html[data-layout="stage"] .sl-dexanchor{position:fixed;left:50%;top:calc(11vh + var(--postrip-settle,14.3vh));width:min(34vw,420px);height:58vh;transform:translateX(-50%);pointer-events:none;visibility:hidden}
@media (max-width:1023px){html[data-layout="stage"] .sl-dexanchor{top:9vh;width:64vw;height:40vh}}
/* the modal: the desktop card, scaled; the phone gets the same card in the .ov room */
html[data-layout="stage"] .dexmodal__panel{zoom:var(--sl-s,1);width:1180px;max-width:calc(96vw / var(--sl-s,1));height:calc(92vh / var(--sl-s,1));max-height:980px;
  background:#82C0F1 url(../bg-plate.png) center/cover no-repeat}
html[data-layout="stage"] .dexmodal__bar{border-bottom-width:3px}
@media (max-width:1023px){
  html[data-layout="stage"] .dexmodal{padding:calc(var(--mh-top,0px) + 12px) 12px calc(var(--mh-nav,0px) + var(--mh-bot,0px) + 12px);box-sizing:border-box}
  html[data-layout="stage"] .dexmodal__panel{width:100%;max-width:none;height:100%;max-height:none;border-radius:20px}
  html[data-layout="stage"] .dexmodal__body{padding:12px 12px 32px}
  html[data-layout="stage"] .dexmodal__title{font-size:26px}
  html[data-layout="stage"] .mnav [data-go="pokedex"]{position:relative}
  html[data-layout="stage"] .mnav__dot{left:calc(50% + 6px);top:6px}
}
/* the catch: the site's own display face is already on the page; the
   overlay's captions inherit nothing, so nothing to add — but the crate
   and the header must not paint over it: the overlay is a body child at
   z 1000 and .fit is a stacking context under it (HANDOFF-140 §2). */

/* ── THE LIVE STRIP'S OWN SCROLL ────────────────────────────────────
   style.css:215 animates the track to -50%, which only wraps seamlessly with
   exactly two copies. stage.js clones as many as the viewport needs, so the
   wrap distance is one COPY, in pixels, not a share of the track. Same speed:
   --sl-strip-dur carries app.js's own per-item pace through. */
@keyframes slStripScroll{
  from{transform:translateX(0)}
  to{transform:translateX(calc(0px - var(--sl-strip-shift,50%)))}}
/* app.js's own #stripList still gets every rewrite; it is the SOURCE now,
   not the picture (stage.js mirrorStrip). .sl-feed is what scrolls. */
html[data-layout="stage"] #stripList{display:none}
html[data-layout="stage"] .strip__list{
  animation:slStripScroll var(--sl-strip-dur,var(--strip-dur,90s)) linear infinite;
  /* HANDOFF-133 §4C.2: a 2856px track of 12 image chips computed
     will-change:auto and repainted every frame. Its own compositor layer. */
  will-change:transform;backface-visibility:hidden}

/* ── THE CRATE'S REVEAL, LAYERED ART (site/crate-stage.js) ───────────
   Luke, 5 Sep 09:33: option 2 of the mocks — no video plate, the crate art
   itself over the dimmed page, the header and logo still there — at the
   smallest of three sizes (C: 360px wide on a 1470 window). The pop-up is a
   scrim with one box in it; the box holds three still layers on a shared
   1247×1295 canvas (closed / open-back / open-front) plus what we draw:
   the burst, the beam, the motes, the prize. crate-stage.js sets --cv-w,
   --cv-h, --cv-rim, --cv-hinge, --cv-mouthx, --cv-beamfoot, --cv-lift,
   --cv-n, --cv-l from the box's measured width. */
html[data-layout="stage"] .cv{position:fixed;inset:0;z-index:var(--z-modal,900);display:none;
  opacity:0;transition:opacity 220ms ease;cursor:pointer;overflow:hidden;background:rgba(11,15,24,.55);
  --cv-w:clamp(250px,26.4vw,430px)}
html[data-layout="stage"] .cv.is-on{opacity:1}
html[data-layout="stage"] .cv[data-phase="opening"]{cursor:default}
/* ONE CRATE, THAT TRAVELS (Luke, 8 Sep 06:20 — "it duplicates"). The pop-up
   grows out of the corner crate, so the corner crate is hidden for the trip;
   that only works if the traveller is OPAQUE from its first frame, or the
   swap shows as a fade. .cv--flip therefore trades the overlay's own fade
   for the scrim's: the box is solid at the corner and the page dims around
   it. Without a corner to fly from (the phone, mid-rip) crate-stage.js does
   not add the class and nothing here applies. */
html[data-layout="stage"] .cv.cv--flip{opacity:1;background:rgba(11,15,24,0);transition:background-color 220ms ease}
/* AND THE BOOT FADE IS NOT THIS ELEMENT'S (stage.css:100). slBoot fades in
   every body child, and .cv is built lazily on the FIRST open — so it ran
   0 -> 1 over .3s from the moment it was inserted, and an animation beats a
   declaration, so the traveller was translucent for its whole trip on the
   first open of the session and opaque on every one after. It is never on
   screen at boot; it has never needed the fade. Measured 8 Sep: computed
   opacity 0 with .cv--flip on, animationName slBoot. */
html[data-layout="stage"][data-booted] body > .cv{animation:none}
/* ── AND NEITHER IS THE GENERATOR'S SLAB'S (HANDOFF-196 §4) ─────────────────
   The same bug, the same shape, on the other traveller. generator.js land()
   flies the finished slab from the ceremony's stage onto the card page by
   cloning the WebGL canvas into a fixed-position .gen__fly and appending it to
   BODY — so slBoot above caught it too, and an animation beats an inline
   declaration.

   Luke, 17 Sep: "when the slab slams into the card.... and it moves to that
   position in the 3rd screen shot. its not smooth. - its hard to eplain but
   not smooth at all." MEASURED, at 1147x655 and 1512x982, on the real hold:
   the slab was at computed opacity 0 on its first frame, faded IN to 1 over
   300ms — 58% of the way through a 520ms move — and then snapped to 0 on the
   next frame, because the `opacity:0` land() sets could never start a
   transition while the animation owned the property, so the moment the
   animation let go the specified value was already 0. Of 520ms of flight the
   slab was translucent for 300, opaque for one frame and gone for 200.

   It has never needed a boot fade: it is built at the end of a seven-second
   ceremony and lives for 600ms. */
html[data-layout="stage"][data-booted] body > .gen__fly{animation:none}
html[data-layout="stage"] .cv.cv--flip.is-on{background:rgba(11,15,24,.55)}
html[data-layout="stage"] .cv.cv--flip:not(.is-on) .cv__x,
html[data-layout="stage"] .cv.cv--flip:not(.is-on) .cv__hint{opacity:0}
html[data-layout="stage"] .cv.cv--flip .cv__x{transition:opacity 220ms ease}
/* THE TRIP HOME on CLOSE: the crate flies back into the corner while the
   page comes back, and the lid shuts on the way — is-open comes off, so the
   closed layer crossfades over the open pair and what lands is the crate the
   corner is about to show. Nothing on it is clickable while it flies. */
html[data-layout="stage"] .cv.is-home{pointer-events:none}
html[data-layout="stage"] .cv.cv--flip.is-home{background:rgba(11,15,24,0)}
html[data-layout="stage"] .cv.is-home .cv__stage{transition:transform .48s cubic-bezier(.3,.7,.4,1)}
html[data-layout="stage"] .cv.is-home .cv__stage img{transition:opacity .2s ease}
html[data-layout="stage"] .cv.is-home .cv__x,
html[data-layout="stage"] .cv.is-home .cv__hint,
html[data-layout="stage"] .cv.is-home .cv__sparks{opacity:0;transition:opacity .14s ease}
/* the corner cluster while the pop-up has its crate: visibility, so the
   countdown and the state keep running and its rect stays measurable */
html[data-layout="stage"] .sl-crate.is-away{visibility:hidden}
/* THE BOX: the closed crate is 92.7% of its width, so 26.4vw ≈ a 360px crate
   at 1470. Centred a touch above the middle; the corner-crate FLIP starts
   from an inline transform crate-stage.js writes and clears. */
html[data-layout="stage"] .cv__stage{position:absolute;left:50%;top:46%;width:var(--cv-w);aspect-ratio:1247/1295;
  margin:calc(var(--cv-h,430px) / -2) 0 0 calc(var(--cv-w) / -2);
  transition:transform .55s cubic-bezier(.22,.8,.36,1);will-change:transform}
html[data-layout="stage"] .cv__stage img{position:absolute;inset:0;width:100%;height:100%;display:block;pointer-events:none;user-select:none}
html[data-layout="stage"] .cv__closed{filter:drop-shadow(0 10px 0 rgba(11,15,24,.35));transform-origin:50% 92%}
html[data-layout="stage"] .cv__back,html[data-layout="stage"] .cv__front{opacity:0;filter:drop-shadow(0 10px 0 rgba(11,15,24,.35))}
/* the squash on the tap, then the swap under the burst */
html[data-layout="stage"] .cv[data-phase="opening"] .cv__closed{animation:cvSquash .42s cubic-bezier(.3,.7,.4,1.3) forwards}
@keyframes cvSquash{0%{transform:scale(1)}45%{transform:scale(1.06,.9)}100%{transform:scale(.97,1.05)}}
html[data-layout="stage"] .cv.is-open .cv__closed{opacity:0;animation:none}
html[data-layout="stage"] .cv.is-open .cv__front{opacity:1}
html[data-layout="stage"] .cv.is-open .cv__back{opacity:1;animation:cvLid .38s cubic-bezier(.2,.85,.3,1.15) forwards;transform-origin:50% var(--cv-hinge,42%)}
@keyframes cvLid{0%{transform:scaleY(.55) translateY(4%)}100%{transform:none}}
html[data-layout="stage"] .cv__flash{position:absolute;inset:-30%;border-radius:50%;pointer-events:none;opacity:0;
  background:radial-gradient(closest-side,#FFF 0%,rgba(255,255,255,.9) 30%,rgba(140,236,255,.45) 60%,transparent 100%)}
html[data-layout="stage"] .cv.is-open .cv__flash{animation:cvFlash .6s ease-out forwards}
@keyframes cvFlash{0%{opacity:0;transform:scale(.4)}18%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(1.25)}}
/* THE LIGHT: a glow behind the box and a beam standing up out of the mouth,
   never still — it breathes for as long as the pop-up is open */
html[data-layout="stage"] .cv__glow{position:absolute;left:calc(var(--cv-mouthx,50%) - var(--cv-w) * .6);top:calc(var(--cv-rim,55%) - var(--cv-w) * .6);
  width:calc(var(--cv-w) * 1.2);height:calc(var(--cv-w) * 1.2);border-radius:50%;pointer-events:none;opacity:0;
  background:radial-gradient(closest-side,rgba(255,255,255,.75),rgba(140,236,255,.4) 45%,transparent 72%);transition:opacity .5s ease}
html[data-layout="stage"] .cv.is-lit .cv__glow{opacity:1;animation:cvBreathe 2.4s ease-in-out infinite}
html[data-layout="stage"] .cv__beam{position:absolute;left:calc(var(--cv-mouthx,50%) - var(--cv-w) * .15);bottom:var(--cv-beamfoot,55%);
  width:calc(var(--cv-w) * .3);height:calc(var(--cv-h,430px) * .95);pointer-events:none;opacity:0;transform:scaleY(0);transform-origin:50% 100%;
  background:linear-gradient(to top,rgba(255,255,255,.98) 0%,rgba(190,245,255,.85) 28%,rgba(140,236,255,.45) 62%,transparent 100%);
  -webkit-mask:linear-gradient(to right,transparent,#000 22%,#000 78%,transparent);mask:linear-gradient(to right,transparent,#000 22%,#000 78%,transparent)}
html[data-layout="stage"] .cv.is-lit .cv__beam{opacity:1;transform:none;transition:transform .5s cubic-bezier(.2,.85,.3,1),opacity .3s;animation:cvBeam 1.8s ease-in-out .5s infinite}
@keyframes cvBeam{0%,100%{opacity:1;transform:scaleX(1)}50%{opacity:.78;transform:scaleX(.86)}}
@keyframes cvBreathe{0%,100%{transform:scale(1)}50%{transform:scale(1.12)}}
/* THE WELL is the space above the front rim; the prize lives at its bottom
   edge and climbs up into it, clipped at the rim, behind open-front */
/* it reaches a whole box-height ABOVE the box so the climb is never cut at
   the top — only its bottom edge, the rim, clips */
html[data-layout="stage"] .cv__well{position:absolute;left:calc(-1 * var(--cv-w));right:calc(-1 * var(--cv-w));top:calc(-1 * var(--cv-h,430px));
  height:calc(var(--cv-rim,55%) + var(--cv-h,430px));overflow:hidden;pointer-events:none}
html[data-layout="stage"] .cv__prize{margin-left:var(--cv-w)}
html[data-layout="stage"] .cv__prize{
  position:absolute;bottom:0;left:var(--cv-mouthx,50%);width:calc(var(--cv-w) * .95);transform:translate(-50%,100%) scale(.55);
  display:flex;flex-direction:column;align-items:center;gap:.2em;pointer-events:none;
  opacity:0;text-align:center;will-change:transform,opacity}
html[data-layout="stage"] .cv__prize.is-up{animation:cvRise 1.1s cubic-bezier(.2,.85,.3,1) forwards}
@keyframes cvRise{
  0%{transform:translate(-50%,100%) scale(.55);opacity:1}
  100%{transform:translate(-50%,calc(-1 * var(--cv-lift,200px))) scale(1);opacity:1}}
/* motes that never stop: they ring the mouth and drift up through the beam */
html[data-layout="stage"] .cv__sparks{position:absolute;inset:0;pointer-events:none;opacity:0;transition:opacity .6s ease}
html[data-layout="stage"] .cv.is-lit .cv__sparks{opacity:1}
html[data-layout="stage"] .cv__sparks i{
  position:absolute;transform:translate(-50%,-50%) scale(0);opacity:0;color:#FFF;font-style:normal;line-height:1;
  text-shadow:0 0 .35em rgba(140,236,255,.95),0 0 .8em rgba(255,255,255,.7);
  animation:cvSpark 2.6s ease-in-out infinite}
html[data-layout="stage"] .cv__sparks i::before{content:"✦"}
html[data-layout="stage"] .cv__sparks i:nth-child(3n)::before{content:"✧"}
@keyframes cvSpark{
  0%{transform:translate(-50%,-50%) translateY(0) scale(0) rotate(0deg);opacity:0}
  30%{transform:translate(-50%,-50%) translateY(-.6em) scale(1) rotate(35deg);opacity:1}
  60%{transform:translate(-50%,-50%) translateY(-1.4em) scale(.75) rotate(70deg);opacity:.9}
  100%{transform:translate(-50%,-50%) translateY(-2.4em) scale(0) rotate(110deg);opacity:0}}
html[data-layout="stage"] .cv__n{
  font-family:var(--display);font-weight:900;font-style:italic;line-height:.95;
  font-size:var(--cv-n,120px);color:#FFF;letter-spacing:.01em;
  -webkit-text-stroke:calc(var(--cv-n,120px) * .06) var(--key);paint-order:stroke fill;
  text-shadow:0 .06em 0 var(--key),0 0 .35em rgba(253,193,7,.75),0 0 .9em rgba(255,255,255,.65);
  transform:var(--slant)}
html[data-layout="stage"] .cv[data-result="win"] .cv__n{color:var(--gold)}
html[data-layout="stage"] .cv__l{
  font-family:var(--display);font-weight:900;font-style:italic;font-size:var(--cv-l,32px);
  color:#FFF;letter-spacing:.08em;-webkit-text-stroke:calc(var(--cv-l,32px) * .12) var(--key);
  paint-order:stroke fill;transform:var(--slant)}
html[data-layout="stage"] .cv[data-result="fail"] .cv__n{font-size:calc(var(--cv-n,120px) * .55);color:#FFF}
html[data-layout="stage"] .cv[data-result="fail"] .cv__l{
  font-style:normal;font-weight:800;letter-spacing:.01em;text-transform:none;
  -webkit-text-stroke:0;color:#FFF;font-family:inherit;font-size:clamp(15px,1.4vw,20px);
  max-width:min(80vw,560px);text-shadow:0 1px 0 rgba(11,15,24,.55)}
html[data-layout="stage"] .cv__hint{
  position:absolute;left:50%;top:calc(46% + var(--cv-h,430px) / 2 + 6px);transform:translateX(-50%);margin:0;z-index:3;
  font-family:var(--display);font-weight:900;font-style:italic;font-size:clamp(18px,1.8vw,26px);
  color:#FFF;-webkit-text-stroke:3px var(--key);paint-order:stroke fill;letter-spacing:.04em;
  white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .2s}
html[data-layout="stage"] .cv[data-phase="closed"] .cv__hint{opacity:1;animation:cvHint 2.2s ease-in-out infinite}
@keyframes cvHint{0%,100%{transform:translateX(-50%) translateY(0)}50%{transform:translateX(-50%) translateY(-6px)}}
html[data-layout="stage"] .cv__x{
  position:absolute;top:max(18px,env(safe-area-inset-top));right:22px;width:46px;height:46px;z-index:4;
  border:2.5px solid var(--key);border-radius:50%;background:#FFF;color:var(--key);
  font-size:26px;line-height:1;cursor:pointer;box-shadow:0 3px 0 var(--key)}
/* THE BIG CLOSE UNDER THE CRATE (Luke, 5 Sep 23:50: "a close button needs to
   appear underneath it, nice and big ... replace the X"). Sits where the
   "Tap the crate" hint sat, below the box, for every ending; the corner X
   goes once the crate is opening. RIP PACK's own pink stick look. */
html[data-layout="stage"] .cv__done{
  position:absolute;left:50%;top:calc(46% + var(--cv-h,430px) / 2 + 22px);transform:translateX(-50%);z-index:4;
  font-family:var(--display);font-weight:900;font-style:italic;font-size:clamp(26px,2.4vw,34px);letter-spacing:.04em;text-transform:uppercase;
  padding:14px 64px 12px;min-width:min(60vw,300px);
  border:3px solid var(--key);border-radius:16px;color:#FFF;cursor:pointer;
  background:linear-gradient(180deg,var(--acc-hi,#EC6DA0),var(--acc));
  box-shadow:0 6px 0 var(--key),0 14px 24px rgba(11,15,24,.35);
  animation:cvDoneIn .4s cubic-bezier(.2,.85,.3,1.2) both;transition:transform .14s}
html[data-layout="stage"] .cv__done:hover{transform:translateX(-50%) translateY(-2px)}
html[data-layout="stage"] .cv__done:active{transform:translateX(-50%) translateY(2px);box-shadow:0 3px 0 var(--key)}
@keyframes cvDoneIn{from{opacity:0;transform:translateX(-50%) translateY(14px)}to{opacity:1;transform:translateX(-50%)}}
html[data-layout="stage"] .cv[data-phase="opening"] .cv__x{display:none}
html[data-layout="stage"] .cv__done[hidden]{display:none}
html[data-layout="stage"] .cv__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
@media (max-width:1023px){
  html[data-layout="stage"] .cv{--cv-w:min(70vw,360px)}
  html[data-layout="stage"] .cv__stage{top:44%}}
@media (prefers-reduced-motion:reduce){html[data-layout="stage"] .cv__sparks i{animation:none;opacity:0}}

/* ── THE POST-RIP SURFACE (Luke, 5 Sep 23:25, after the first devnet rips
   on the new layout, on blockrips.com) ──────────────────────────────────
   1. NO TOAST OVER THE REEL. SELL & RIP sells, wallet.js toasts "Sold. $x
      USDC is in your balance" for 3.6s, and the next rip's reel ran under
      it. Hidden while the reel/reveal runs (state rip with no post-rip
      panel); on the panel itself (SELL NOW) it still shows. wallet.js sets
      opacity inline, hence !important; its own .3s transition fades it. */
html[data-layout="stage"][data-state="rip"]:not([data-postrip="1"]) #walletToast{
  opacity:0!important;pointer-events:none}
/* 2. TOP PRIZES LIVE starts OFF the screen and slides in — poolrail-rips.js
      fades it up from 72px left, which reads as "half on the screen". */
html[data-layout="stage"] .poolrail{
  opacity:1;transform:translateX(calc(-100% - 40px));
  transition:transform 560ms cubic-bezier(.22,.8,.36,1);
  /* the rail centres its column in its own box, and at Luke's 1147x655 that
     put STILL IN THE POOL at y=37, under the brand (y 11-47; measured 7 Sep).
     Start the box below the brand so the column centres in the room that is
     actually free: 60px off the top, content 580px, window 655 — it fits. */
  top:60px}
html[data-layout="stage"] .poolrail[data-on="1"]{transform:none}
/* 3. The amounts in the site's pink, not the rarity ramp (inline colour,
      hence !important). */
html[data-layout="stage"] .poolrail__val{color:var(--acc)!important}

/* ── THE SLAB DROPS, THE SKY DOES NOT (stage.js slabDrop) ─────────────
   rips-glue.css translates #stageLayer>canvas by --postrip-settle when the
   post-rip panel lands; on this layout the canvas is pinned and the scene's
   camera moves instead. The probe carries rips-glue's exact timing and
   distance so stage.js can read the travel per frame. .postrip__slabhit
   keeps following --postrip-settle, which is where the slab now lands. */
html[data-layout="stage"][data-state="rip"][data-postrip="1"] #stageLayer>canvas{transform:none}
.sl-settle{position:fixed;left:-9px;top:-9px;width:1px;height:1px;visibility:hidden;pointer-events:none}
@media (min-width:1024px){
  html[data-layout="stage"] .sl-settle{transition:transform .95s cubic-bezier(.16,.78,.29,1)}
  html[data-layout="stage"][data-state="rip"][data-postrip="1"] .sl-settle{transform:translateY(var(--postrip-settle,14.3vh))}
  @media (prefers-reduced-motion:reduce){ html[data-layout="stage"] .sl-settle{transition:none} }
}

/* ── THE ACCOUNT MENU OVER THE CRATE (Luke, 6 Sep 00:50: "the menu goes
   behind the crate"). The header's pops live inside .page (a stacking
   context at z-index 1) and the crate cluster is a body child at 40, so no
   pop can paint above it — the same fight rips-glue.css settles for the
   post-rip rail, and the same answer: while any header pop is open, the
   crate steps under the page. */
/* THE CRATE LIVES IN .page NOW (stage.js, 06:12), at z 5 under the header's
   6, so the account menu and the balance pops paint over it and the corner
   clock (.fit's child, z 0) stays under it — with no rule here at all. The
   four lift/hide rules that stood here (crate to 0, .page/.header/.fit to
   45, the clock hidden) are gone with the fight they were losing. */
/* Luke, 6 Sep 04:14: "the menu goes behind the crate" signed in on his
   laptop; 04:45: hiding the crate while the menu is open was wrong too ("the
   crate disappears"). So: visible, under the page, not clickable. Signed out
   and headless (1470×690) the sign-out row hit-tests over the crate; what
   covers it signed in is unreproduced here — needs his screenshot. */

/* ── THE PHONE PASS (Luke at the phone, 6 Sep 01:45, on blockrips.com) ──
   Four calls from his first screenshot of the live site. Everything here is
   an override of the product's phone chrome (style.css:862–1230, mobile.js
   untouched) scoped to this layout, per the house rule. */
@media (max-width:1023px){
  /* 1. "The cards are sticking out slightly" — the stage's 40×56 slab
     (stage.css:666, no media query) inside the phone's 38px chip. The chip
     grows to 44 in its 46px strip and the slab is cropped to it, keyline and
     corners intact: "wrap around the cards like it does on the site, with
     that white border all the way around it, even if it doesn't show the
     full slab". object-fit:cover keeps the centre of the slab. */
  html[data-layout="stage"] .sl-feed .hit,
  html[data-layout="stage"] .sl-feed .hit[data-rarity]{height:44px;gap:9px;padding:0 12px 0 7px}
  html[data-layout="stage"] .sl-feed .hit__img{width:28px;height:34px;border-radius:4px;object-fit:cover;object-position:50% 40%}
  html[data-layout="stage"] .sl-feed .hit__u{font-size:11px;max-width:130px}
  html[data-layout="stage"] .sl-feed .hit__n{font-size:13px;max-width:130px}
  html[data-layout="stage"] .sl-feed .hit__m{font-size:12px;margin-top:2px}
  html[data-layout="stage"] .sl-feed .hit__m b{font-size:13px}
  html[data-layout="stage"] .sl-feed .hit__grade{font-size:10.5px}

  /* 2. "I don't like the black fade behind RIP PACK" — the dock's gradient
     (style.css:1002) goes; the buttons sit on the sky like the desktop's. */
  html[data-layout="stage"] .mdock{background:none}

  /* 3. "I don't like that the menu bar is completely black" — the bar wears
     the LIVE cards' frosted white instead of #050505, its icons the off-black
     ink, the active one pink as before. No backdrop-filter (stage.css:659). */
  /* AND IT IS NOT TRANSLUCENT (Luke, 8 Sep 12:15: "the bottom nav bar is
     translucent. I think we should just make it not translucent ... There's a
     white line going across the top of it. Make sure that's not just a white
     line on an off-white colour"). It was 50%/36% white over the sky, so the
     art read straight through the labels and the 78%-white hairline had
     nothing to sit against. Opaque now, and still an ombre: white at the top
     down to a cool off-white at the bottom, which is the sky's own family
     rather than a grey. The line is the site's 2px navy keyline - the same one
     every pack, row and sticker wears - so it cannot be white-on-white. */
  html[data-layout="stage"] .mnav{
    background:linear-gradient(180deg,#FFFFFF 0,#EEF3FA 100%);
    border-top:2px solid var(--key);
    box-shadow:0 -10px 22px -12px rgba(11,15,24,.45)}
  html[data-layout="stage"] .mnav__i,
  html[data-layout="stage"] .mnav__mid{color:rgba(11,15,24,.62)}
  html[data-layout="stage"] .mnav__i.is-on,
  html[data-layout="stage"] .mnav__mid{color:var(--acc)}
  html[data-layout="stage"] .mnav__i:active{color:var(--key)}

  /* 4. "The sign-in button doesn't seem on brand like the desktop's" — signed
     out, the balance pill (a black chip with the $ coin, the desktop's
     signed-IN look) becomes the desktop's pink SIGN IN stick, no coin.
     stage.js adds .is-out/.stick while #mBalV reads SIGN IN; signed in the
     pill is the product's, untouched. */
  html[data-layout="stage"] .mhead__bal.is-out{
    height:36px;padding:0 15px;border-radius:12px;gap:0;
    font-family:var(--display);font-size:19px;font-weight:900;font-style:italic;letter-spacing:.01em;
    color:var(--stick-ink);border:var(--ring-w) solid var(--ring-in);
    background:linear-gradient(180deg,var(--acc-hi) 0%,var(--acc) 48%,var(--acc-lo) 100%);
    box-shadow:0 0 0 var(--ring-out-w) var(--ring-out),0 4px 0 0 rgba(11,15,24,.30)}
  html[data-layout="stage"] .mhead__bal.is-out:active{border-color:var(--ring-in)}
  /* Luke, 6 Sep 06:20: "the sign-in button on the mobile version should not
     have a USDC logo on there at all" — swapUsdc() had turned the pill's svg
     into an <img class="usdc-mark">, so the svg-only hide stopped matching */
  html[data-layout="stage"] .mhead__bal.is-out svg,
  html[data-layout="stage"] .mhead__bal.is-out .usdc-mark{display:none}
  html[data-layout="stage"] .mhead__bal.is-out b{transform:var(--slant);display:inline-block}
}

/* ── THE PHONE'S OVERLAYS ARE CARDS, NOT SHEETS (Luke, 6 Sep 02:30, on eval:
   "the background just looks like it's not on brand" (Odds), "My Vault is
   touching the top of the page and the bottom" ... "the whole thing is now
   off brand"). style.css:1084 makes every .ov panel a full-bleed, borderless,
   navy screen on a phone, and the module panels (vault.js, store.js,
   grailpool.js, crate-odds.js) each do their own bottom-sheet or 100vw/100dvh
   version under 760px. The desktop is the reference: the navy card, the 3px
   key border, 20px corners, the hard drop, floating in the dimmed room. So
   the phone gets exactly that card, 12px in from the sides, 12px under the
   safe area and 12px above the bottom bar; .mnav (z 70) stays live over the
   .ov shells as before, and the module scrims (z 100) still cover it. */
@media (max-width:1023px){
  html[data-layout="stage"] .ov{
    padding:calc(var(--mh-top) + 12px) 12px calc(var(--mh-nav) + var(--mh-bot) + 12px);place-items:center}
  html[data-layout="stage"] .ov__panel{
    width:100%;height:auto;max-height:100%;border-radius:20px;border:3px solid var(--key);
    padding-bottom:0;transform:translateY(10px) scale(.985)}
  html[data-layout="stage"] .ov[data-open="1"] .ov__panel{transform:none}
  html[data-layout="stage"] .ov__head{padding:16px 16px 12px}
  html[data-layout="stage"] .ov__body{padding-bottom:18px}
  html[data-layout="stage"] .ov__foot{padding-bottom:14px}
  /* the module overlays: the same room, the same card */
  html[data-layout="stage"] .vault,
  html[data-layout="stage"] .store,
  html[data-layout="stage"] .grail,
  html[data-layout="stage"] .codds{
    align-items:center;justify-content:center;box-sizing:border-box;
    padding:calc(var(--mh-top) + 12px) 12px calc(var(--mh-nav) + var(--mh-bot) + 12px)}
  html[data-layout="stage"] .vault__panel,
  html[data-layout="stage"] .store__panel,
  html[data-layout="stage"] .grail__panel,
  html[data-layout="stage"] .codds__panel{
    width:100%;max-width:none;max-height:100%;border-radius:20px;
    background:var(--navy);border:3px solid var(--key);color:#fff;
    box-shadow:0 14px 0 0 rgba(11,15,24,.34),0 40px 90px -30px rgba(11,15,24,.55);
    transform:translateY(10px) scale(.985)}
  html[data-layout="stage"] .vault[data-open="1"] .vault__panel,
  html[data-layout="stage"] .store[data-open="1"] .store__panel,
  html[data-layout="stage"] .grail[data-open="1"] .grail__panel,
  html[data-layout="stage"] .codds[data-open="1"] .codds__panel{transform:none}
  /* fixed-height boxes: the grail pool was 100dvh, the vault grows as its
     cards land ("it starts dead small and then grows large") — both take the
     whole card height and scroll inside */
  html[data-layout="stage"] .grail__panel,
  html[data-layout="stage"] .vault__panel{height:100%}

  /* ── LIVE PULL (Luke, 6 Sep 02:34, screenshot: "text covering the live
     pull ... absolutely awful"). style.css:769 gives the art a 681/1136
     aspect box, rips-glue caps it at 44dvh, and the two disagree: the box is
     capped, the image is not, and the name paints over the slab. One column:
     the slab centred in a 38dvh box that IS its height, the name under it in
     the display face, the rows, RIP ONE full width. */
  html[data-layout="stage"] .hitov__body{grid-template-columns:1fr;gap:12px;align-content:start}
  html[data-layout="stage"] .hitov__art{
    aspect-ratio:auto;height:38dvh;max-height:none;display:grid;place-items:center;
    background:none;border-radius:0;overflow:visible}
  /* a viewport height on the img itself: a percentage inside the grid's
     auto row resolved to nothing and the slab painted at 544px (measured) */
  html[data-layout="stage"] .hitov__art{height:auto}
  html[data-layout="stage"] .hitov__art img{
    width:auto;height:38dvh;max-width:100%;object-fit:contain;border-radius:10px}
  html[data-layout="stage"] .hitov__meta{gap:10px}
  html[data-layout="stage"] .hitov__meta h3{
    text-align:center;font-family:var(--display);font-weight:900;font-style:italic;font-size:22px;
    line-height:1.05;color:#FFF;-webkit-text-stroke:3px var(--key);paint-order:stroke fill;transform:var(--slant)}
  html[data-layout="stage"] .hitov__r{padding:7px 0}
  html[data-layout="stage"] .hitov__meta .redeem{align-self:stretch;margin-top:6px}

  /* ── ODDS (Luke, 6 Sep 02:30: "when you click Pokémon, One Piece or
     Basketball it goes yellow" and "the box that keeps them in there fades
     off the screen on the right just doesn't look the best"). The tabs wrap
     instead of scrolling behind rips-glue's fade (:316), and the active one
     is the site's pink, white type, the key keyline — not the yellow. */
  html[data-layout="stage"] .otabs{
    flex-wrap:wrap;overflow:visible;gap:5px;padding:6px;
    -webkit-mask-image:none;mask-image:none;border-color:var(--key);border-width:2px;background:rgba(11,15,24,.35)}
  html[data-layout="stage"] .otab{flex:1 1 auto;justify-content:center;padding:8px 9px;border-radius:9px}
  html[data-layout="stage"] .otab.is-on{color:#FFF;background:var(--acc);border-color:var(--key)}
  html[data-layout="stage"] .ov--odds .ov__body{-webkit-mask-image:none;mask-image:none}

  /* ── MY VAULT (Luke, 6 Sep 02:30: What's Inside has "a card on the left, a
     card on the right and two more underneath ... mimic that onto My Vault").
     Two up, like the grail pool's grid. */
  html[data-layout="stage"] .pvault__grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}

  /* ── MY PROFILE (Luke, 6 Sep 02:30) — see stage.js dressProfile(). The
     rank row: badge, tier name in its colour, the header's XP bar with the
     next badge on its end; the crate row: the new art with its label and
     LOCKED/timer to its RIGHT (rips-glue.css:56 made every .cratebtn a
     centred block, which is what stacked them). */
  html[data-layout="stage"] .mrank{display:flex;align-items:center;gap:14px}
  html[data-layout="stage"] .sl-badge--prof{width:56px;height:62px;flex:0 0 auto}
  html[data-layout="stage"] .sl-badge--prof img{width:56px;height:62px;object-fit:contain}
  html[data-layout="stage"] .mrank__b{flex:1 1 auto;min-width:0}
  html[data-layout="stage"] .mrank .rank__name{
    font-size:34px;line-height:1;margin:0 0 9px;letter-spacing:.01em;display:block;
    color:var(--tier,#FFF);-webkit-text-stroke:4px var(--key);paint-order:stroke fill;transform:var(--slant)}
  html[data-layout="stage"] .sl-xp--prof{display:flex;align-items:center;position:relative;margin:0 22px 0 0}
  html[data-layout="stage"] .sl-xp--prof .rank__bar{
    flex:1 1 auto;height:24px;margin:0;border-radius:13px;box-sizing:border-box;
    background:rgba(11,15,24,.30);border:2.5px solid var(--key);overflow:hidden}
  html[data-layout="stage"] .sl-xp--prof .rank__bar i{
    display:block;height:100%;border-radius:10px;
    background:linear-gradient(90deg,var(--tier,var(--gold)),color-mix(in srgb,var(--tier,var(--gold)) 55%,#FFF));
    transition:width .6s cubic-bezier(.22,.8,.36,1)}
  html[data-layout="stage"] .sl-xp--prof .rank__val{
    position:absolute;inset:0;display:flex;align-items:center;justify-content:center;margin:0;
    font-size:13px;font-weight:900;letter-spacing:.02em;color:#FFF;
    -webkit-text-stroke:2.5px var(--key);paint-order:stroke fill;pointer-events:none;white-space:nowrap}
  html[data-layout="stage"] .sl-xp--prof .rank__val b{color:#FFF}
  html[data-layout="stage"] .mrank .rank__next{margin:9px 0 0;font-size:14px}
  html[data-layout="stage"] .mcrate.cratebtn{display:flex;align-items:center;gap:16px;text-align:left;width:100%}
  html[data-layout="stage"] .mcrate img{width:124px;height:auto;flex:0 0 auto;filter:drop-shadow(0 4px 0 rgba(11,15,24,.28))}
  html[data-layout="stage"] .mcrate>div{flex:1 1 auto;min-width:0}
  html[data-layout="stage"] .mcrate .daily__lbl{font-size:15px;font-weight:700;color:#A7B0BE}
  html[data-layout="stage"] .mcrate .daily__timer{
    display:inline-block;font-size:30px;line-height:1;margin-top:4px;
    -webkit-text-stroke:4px var(--key);paint-order:stroke fill;transform:var(--slant)}

  /* Luke, 6 Sep 02:55, from the eval screenshot of the sheet */
  html[data-layout="stage"] .mprof__sec[hidden],
  html[data-layout="stage"] .mrank[hidden],
  html[data-layout="stage"] .mcrate[hidden]{display:none}
  /* one ring on the X: style.css gives .ov__x a 2px pink border AND a 2px
     pink outline (measured) */
  html[data-layout="stage"] .ov__x{outline:0}
  html[data-layout="stage"] .ov__x:focus-visible{outline:3px solid var(--acc);outline-offset:3px}
  /* the SIGN IN card was a black box (style.css:1178 .mprof__add); signed
     out it holds one button, so no box. Signed in it keeps the balance card. */
  html[data-layout="stage"] .mprof__add:not(:has(.mprof__lbl)){background:none;padding:0;border:0;box-shadow:none}
  /* the swoosh under RANK / DAILY CRATE / SETTINGS was a fixed 132px; now it
     is the word's own width. More air above each section and after the crate. */
  /* Luke, 6 Sep 08:29: "that line should be relative to the word". The
     flex column above still let the svg's own 132px (style.css:1187) set
     the max-content width, so RANK got a DAILY CRATE-length line. The
     swoosh is taken out of flow, so the h3 is exactly its word. */
  html[data-layout="stage"] .mprof__sec{position:relative;display:block;width:max-content;margin:34px 0 14px;padding-bottom:12px}
  html[data-layout="stage"] .mprof__sec .swoosh{position:absolute;left:0;bottom:0;width:100%;height:9px;margin:0}   /* a replaced element keeps its viewBox width under left+right, so 100% */
  /* the odds rows under the crate (stage.js dressProfile) */
  html[data-layout="stage"] .mprof__rows--odds{margin:10px 0 0}
  html[data-layout="stage"] .mprof__who + .mprof__add + .mprof__sec,
  html[data-layout="stage"] .mprof__who + .mprof__sec{margin-top:26px}
  html[data-layout="stage"] .mcrate{margin-bottom:6px}
  /* "$250 more to SILVER": it was the dark ink on the navy card and the
     tier ran into the words */
  html[data-layout="stage"] .mrank .rank__next{color:#A7B0BE;font-weight:700}
  html[data-layout="stage"] .mrank .rank__next b{margin-left:.3em;color:#FFF;letter-spacing:.04em}
  /* the official mark keeps the box the drawn circle had */
  html[data-layout="stage"] .usdc-mark{display:block;flex:0 0 auto}

  /* ── STORE (Luke, 6 Sep 02:30: "Redeem on the left side and History on the
     right side" — the tab bar was two small tabs on the left). */
  html[data-layout="stage"] .store__tab{flex:1 1 0;justify-content:center;padding:12px 8px;font-size:14px;letter-spacing:.08em}
}
/* the vault opened content-sized and then grew as its cards arrived (Luke,
   6 Sep 02:30: "starts dead small and then grows large ... on the desktop
   version as well"). A floor: the panel opens at the height it will need. */
html[data-layout="stage"] .vault__panel{min-height:min(620px,86vh)}

/* ── THE FEED CARDS' SHADOW ENDED IN A LINE (Luke, 6 Sep 02:40, desktop:
   "that shadow just draws a straight line underneath them ... make it fade
   out into the section below"). style.css:205 gives the marquee's viewport
   11px of padding for the hover lift, and clips there; the card's drop
   (0 8px 18px -10px) reaches 16px, so its last 5px were cut flat along the
   whole strip. Measured: card bottom 145.8, clip 156.8. Room for the whole
   shadow below, the margin giving the space back so nothing moves. */
@media (min-width:1024px){
  html[data-layout="stage"] .strip__viewport{padding:11px 0 32px;margin:-11px 0 -32px}
}

/* ── DURING THE RIP ON THE PHONE (Luke, 6 Sep 03:10, his first devnet rip on
   the phone): "you can see the circle from the menu button ... that
   shouldn't be visible while we're ripping a pack open", and on the Your
   Pull sheet. style.css:1411 slides .mnav down by 125% of its own 58px, but
   the RIP circle rises 18px + its ring above the bar, so ~7px of it stayed
   on screen on a phone with no bottom inset. Slide it by its height plus
   the circle. And the reel titles: "the title of the spinner needs to be
   moved up slightly, it's almost overlapping the top of the pack" — the
   reel block starts 13vh down (style.css:1513); 8vh here. */
@media (max-width:1023px){
  html[data-layout="stage"][data-state="rip"] .mnav{transform:translateY(calc(100% + 32px))}
  html[data-layout="stage"] .suspense{padding-top:8vh}
}

/* ── Luke, 6 Sep 03:50, on prod ─────────────────────────────────────────
   1. The vault's footer line ("Wallet DeQt… on devnet. On-chain pulls merge
      in as they land.", vault.js:521) "looks like it's for developers": off,
      every width (Luke, 04:45: the whole line, desktop too).
   2. The store's REDEEM / HISTORY row and the vault's sort row are sticky
      with their module's --surface-raised behind them (store.js:502,
      vault.js:125); on the phone the panel is the navy card, so the row read
      as a different colour. Navy on the phone, and the fade under them too. */
html[data-layout="stage"] #vaultNet,
html[data-layout="stage"] .vault__foot{display:none}
@media (max-width:1023px){
  html[data-layout="stage"] .store__tabs,
  html[data-layout="stage"] .vault__sort{background:var(--navy)}
  html[data-layout="stage"] .store__tabs::after,
  html[data-layout="stage"] .vault__sort::after{background:linear-gradient(180deg,var(--navy),transparent)}
}

/* ── THE DESKTOP ODDS AND FRIENDS (Luke, 6 Sep 04:14, screenshot of prod) ──
   1. "the one you click is yellow ... copy whatever the mobile version does":
      the active tab is the phone's — pink, white type, key keyline.
   2. "the right side of it is faded away": rips-glue.css:316 masks .otabs
      for a scroll hint; the seven tabs fit an 880px panel, so no mask, and
      they wrap if they ever do not.
   3. "click one with only two packs and the whole popup shrinks": the panel
      was content-sized (857px for Pokémon, 457 for Sports, measured); it
      fills its room now and the body scrolls inside.
   4. "at the bottom it says Grail Pool. Remove that": the per-pack toggle and
      its list (app.js:571).
   5. LIVE PULL's slab had a pink hairline and glow on desktop too. */
html[data-layout="stage"] .otabs{flex-wrap:wrap;overflow:visible;gap:4px;
  -webkit-mask-image:none;mask-image:none;border:2px solid var(--key);background:rgba(11,15,24,.35)}
html[data-layout="stage"] .otab{flex:1 1 auto;justify-content:center}
html[data-layout="stage"] .otab.is-on{color:#FFF;background:var(--acc);border-color:var(--key)}
html[data-layout="stage"] .opool,
html[data-layout="stage"] .opool__list{display:none}
html[data-layout="stage"] .hitov__art{border:0;box-shadow:none}
@media (min-width:1024px){
  html[data-layout="stage"] .ov--odds .ov__panel{height:100%}
}

/* the avatar's letter is held while it is still the email's initial
   (stage.js noGuessInitial) — the chip shows, the letter does not */
html[data-layout="stage"] #acctAv.is-guess{color:transparent}

/* ── GRAIL POOL (Luke, 6 Sep 05:34: "when I hover over one of the cards the
   top line of the box is cut off"). grailpool.js:137 lifts a hovered card
   2px and rings it 1px; the scroller (:95) clips at its top edge with no
   padding. Room for the lift, every width. */
html[data-layout="stage"] .grail__scroll{padding-top:6px}
/* Luke, 6 Sep 06:20, from the sweep: under 1024 the "POKÉMON 50 · AVAILABLE ·
   100 cards" line wrapped into three ragged lines at 375 and below — "just
   remove the word available so that everything fits on the same line". The
   span is grailpool.js's own (#grailAvail); hidden from here, file untouched. */
@media (max-width:1023px){
  html[data-layout="stage"] .grail__avail{display:none}
}

/* ═══════════════════════════════════════════════════════════════════
   THE SCALE TOKEN (Luke, 6 Sep 06:25, from the screen-size sweep) — see
   stage.js scaleToken(). --sl-s is 1.0 at his two references (the Mac window
   at 1470 wide, the iPhone 17) and follows the screen from there: 0.87 at
   1280×720, 1.31 at 1920×1080, 1.74 at 2560×1440, 0.8 at 320 wide, 1.17 on
   a portrait iPad. It is applied as `zoom` on each chrome GROUP, not on the
   page: the pack rail keeps its own fit (stage.js layout()), the 3D canvas
   and the crate pop-up are untouched, and stage.js's measurements stay
   consistent — inside a zoomed group clientWidth and transforms are in the
   group's own units, getBoundingClientRect is visual, and both scale
   together (measured in Chrome 152, scratchpad zoomtest).
   ONE TRAP, MEASURED: viewport units inside a zoomed element scale TWICE
   (10vw in zoom:2 painted 20% of the window). So every vw/vh clamp inside
   a zoomed group is pinned here to what it resolved to at 1470×690..800 —
   the reference window — and the zoom does the scaling from there.
   ═══════════════════════════════════════════════════════════════════ */
@media (min-width:1024px){
  html[data-layout="stage"] .sl-side,
  html[data-layout="stage"] .header,
  html[data-layout="stage"] .strip,
  html[data-layout="stage"] .sl-cats,
  html[data-layout="stage"] .sl-meta,
  html[data-layout="stage"] .sl-crate{zoom:var(--sl-s,1)}
  /* the vw/vh clamps inside those groups, pinned (see the trap above) */
  html[data-layout="stage"] .header{gap:35px;height:78px}
  html[data-layout="stage"] .strip{--strip-h:62px;margin-bottom:17px}
  html[data-layout="stage"] .strip{margin-left:calc(var(--sl-rail) - 32px - 23px)}
  html[data-layout="stage"][data-side="collapsed"] .strip{margin-left:calc(76px - 32px)}
  html[data-layout="stage"] .sl-side__brand,
  html[data-layout="stage"] .sl-side__word{height:46px}
  html[data-layout="stage"] .sl-tier{font-size:50px}
  html[data-layout="stage"] .sl-tierrow .sl-tier{min-width:340px}
  html[data-layout="stage"] .sl-meta .hring--rip{margin-top:7px}
  html[data-layout="stage"] .sl-crate{right:20px;bottom:8px;gap:12px}
  html[data-layout="stage"] .sl-crate .cratebtn{width:184px}
  html[data-layout="stage"] .sl-crate .daily__timer{font-size:36px}
  html[data-layout="stage"] .sl-rank .rank__bar{width:176px}
  /* style.css's own vw clamps that sit inside the zoomed header/strip */
  html[data-layout="stage"] .hring--rip{margin-top:17px}
}
/* THE OVERLAYS (Luke: "the Grail Pool text is too small ... the Live Pull
   box should get bigger and the text size should get bigger with the screen
   size"). The panel, not the scrim, so the room stays full-screen and the
   card grows in it. Every width: on the phone the token is 0.8–1.25, so a
   320 gets a smaller Odds table and a portrait iPad a bigger one. */
html[data-layout="stage"] .ov__panel,
html[data-layout="stage"] .vault__panel,
html[data-layout="stage"] .store__panel,
html[data-layout="stage"] .grail__panel,
html[data-layout="stage"] .codds__panel{zoom:var(--sl-s,1)}
@media (min-width:1024px){
  /* style.css's vw/vh clamps inside the panels, pinned at their 1470 values */
  html[data-layout="stage"] .ov__h{font-size:58px}
  html[data-layout="stage"] .ov__h--plain{font-size:44px}
  html[data-layout="stage"] .ov__title .swoosh{width:215px}
  html[data-layout="stage"] .ov__sub{font-size:16px}
  html[data-layout="stage"] .ov__x{width:62px;height:62px}
  html[data-layout="stage"] .redeem{height:62px;padding:0 34px}
  html[data-layout="stage"] .ov__foot{padding:14px 42px 17px}
  html[data-layout="stage"] .vault__count{padding:14px 42px 0;font-size:22px}
  /* the sort row sits UP AGAINST the count line (Luke, 11 Sep: "when I open my
     vault, High Value, High Value, Low Value, and Latest can be closer to where
     it says the number of cards and the total value"). It was 14px under a line
     that is itself only 14px under the title — three evenly spaced rows where
     two of them are one thing. */
  html[data-layout="stage"] .vault__tabs{gap:38px;padding:4px 42px 0}
  html[data-layout="stage"] .vault__grid{gap:22px}
  html[data-layout="stage"] .otabs{margin:14px 42px 0}
  html[data-layout="stage"] .hitov__body{gap:28px}
  html[data-layout="stage"] .hitov__meta h3{font-size:27px}
  html[data-layout="stage"] .grail__grid{gap:20px}
  html[data-layout="stage"] .sup__addr{font-size:34px}
  /* the vault's floor (stage.css above) is a vh and sits on the zoomed panel */
  html[data-layout="stage"] .vault__panel{min-height:620px}
  /* the module panels size themselves in vw/vh (grailpool.js:71, vault.js:89,
     store.js:458, crate-odds.js:159). Inside the zoomed panel a viewport unit
     paints --sl-s times too big (the grail pool clipped at 1920, measured), and
     dividing it by the token cancels that exactly: the panel keeps the same
     share of the window it had, and its px cap scales with everything else. */
  html[data-layout="stage"] .grail__panel{width:min(1160px,calc(96vw / var(--sl-s,1)));height:min(860px,calc(92vh / var(--sl-s,1)))}
  html[data-layout="stage"] .vault__panel{width:min(880px,calc(92vw / var(--sl-s,1)));max-height:calc(86vh / var(--sl-s,1))}
  html[data-layout="stage"] .store__panel{width:min(560px,calc(92vw / var(--sl-s,1)));max-height:calc(86vh / var(--sl-s,1))}
  html[data-layout="stage"] .codds__panel{width:min(680px,calc(92vw / var(--sl-s,1)));max-height:calc(86vh / var(--sl-s,1))}
}
/* THE STAGE IS ALWAYS FULL WIDTH. style.css:568 centres the shell's items
   under 1180px and the stage is a grid item, so between 1024 and 1180 wide
   it shrank to its content — the rail measured 384px at 1024 and 1100, then
   1147 at 1200 — and the pack rule saw a phone-width rail on a desktop
   window (one big pack, its neighbours sliced at the rail's edges, seen in
   the sweep). The ≤1023 block already stretches it; every width does now. */
html[data-layout="stage"] .stage{width:100%;justify-self:stretch}
/* on the phone the ring and the tier row take the token too (a portrait iPad
   gets the bigger ring Luke asked for; a 320 a smaller one); the product's
   fixed chrome — .mhead, .strip, .mdock, .mnav — does NOT, because their
   heights are the --mh-* variables the shell padding and the safe-area
   insets are built from, and env() under zoom is unmeasured. */
@media (max-width:1023px){
  html[data-layout="stage"] .sl-cats,
  html[data-layout="stage"] .sl-meta{zoom:var(--sl-s,1)}
  html[data-layout="stage"] .sl-tier{font-size:32px}   /* was clamp(26px,8vw,38px): 32 on the 402 reference, then the zoom */

  /* ODDS at 320 and 360 (Luke: "$13 to $30 ... is not visible. It's cut
     off"). style.css:723's grid is 96 + 52 + 140 + three 12px gaps = 336px
     of fixed columns in a panel whose body is 264px wide at 320. The bar
     takes what is left and the money column sizes to its text. */
  html[data-layout="stage"] .orow{grid-template-columns:minmax(64px,auto) minmax(0,1fr) 46px auto;gap:8px}
  /* MY VAULT at 320: REDEEM and the X are absolute in the head (vault.js:104/110)
     and sat on "MY VAULT". The title keeps clear of both and wraps if it must. */
  html[data-layout="stage"] .vault__title{padding-right:118px}
}

/* ── THE TABLET COLUMN (Luke, 6 Sep 07:20, the 1023×800 shots: "we're forcing
   ourselves to stretch everything out ... RIP PACK, turbo and What's Inside
   brought into the centre ... the Odds page, My Profile, the Live Pull's RIP
   ONE don't have to go from the left side all the way to the right").
   Between 600 and 1023 wide the phone layout stays (the dock is the
   product's and switches at 1023), but everything that was edge-to-edge
   sits in a centred column: 640px for the dock's controls and the reading
   panels (Odds, Profile, Live Pull, Store, Support), 900px for the two
   card grids (Grail Pool, Vault), which use the room. Under 600 the phone
   is unchanged. */
@media (min-width:600px) and (max-width:1023px){
  html[data-layout="stage"] .mdock{
    padding-left:max(var(--mh-pad),calc((100vw - 640px) / 2));
    padding-right:max(var(--mh-pad),calc((100vw - 640px) / 2))}
  html[data-layout="stage"] .ov,
  html[data-layout="stage"] .store,
  html[data-layout="stage"] .codds{
    padding-left:max(12px,calc((100vw - 640px) / 2));
    padding-right:max(12px,calc((100vw - 640px) / 2))}
  html[data-layout="stage"] .grail,
  html[data-layout="stage"] .vault{
    padding-left:max(12px,calc((100vw - 900px) / 2));
    padding-right:max(12px,calc((100vw - 900px) / 2))}
}
/* the grail pool's "POKÉMON 50 · 100 cards" line: no dangling dot while the
   count has not arrived (Luke, 6 Sep 07:20: "there's a dot ... it looks
   like something's missing"). grailpool.js:91 draws it before the count. */
html[data-layout="stage"] #grailCount:empty::before{content:none}

/* ══════════════════════════════════════════════════════════════════
   6. MIKE D'S ROUND (6 Sep 2026, #blockpacks-ops-tech 10:12–10:30) —
   docs/PLAN-MIKE-6SEP-UI.md. stage.js §6 carries the JS half.
   ══════════════════════════════════════════════════════════════════ */

/* 6.1 THE TAP BOUNCE (Mike 10:22). On the still <img>, never on .sl-pack:
   paint() rewrites .sl-pack's transform every frame. A spring: over, under,
   over, home. */
@keyframes slBounce{
  0%{transform:scale(1)}
  28%{transform:scale(1.09) translateY(-2%)}
  55%{transform:scale(.965)}
  78%{transform:scale(1.025)}
  100%{transform:scale(1)}}
html[data-layout="stage"] .sl-pack img.sl-bounce{animation:slBounce .52s cubic-bezier(.34,1.2,.44,1) both}
/* AND IT BOUNCES OVER THE COLLECTION NAME (Luke, 10 Sep). .sl-cats is z 200
   and paint() writes the hero slot's z-index inline at 100, so the label was
   painting on top of the pack at the top of the bounce — measured at
   1147x655: 18.6px of overlap, with elementFromPoint returning the label's
   own <b>. !important is the point of this rule, not laziness: paint() owns
   that inline z-index sixty times a second and must keep it, so the lift has
   to outrank the inline value for the half-second the keyframe runs (stage.js
   adds the class with the keyframe and drops it on animationend). */
html[data-layout="stage"] .sl-slot.is-bouncing{z-index:300!important}

/* 6.2 THE SHINE ON RIP PACK (Mike 10:15: "when sitting still maybe have a
   shine animation go over the button every couple of seconds"). Both
   shells' inner label boxes: .rip__in / .mrip__in already carry ::before
   (the turbo bolt), so ::after is free, and .stick's overflow:hidden clips
   it. A pass every 3.6s, lasting a quarter of that. */
@keyframes slSheen{
  0%{transform:translateX(-170%) skewX(-16deg)}
  30%{transform:translateX(460%) skewX(-16deg)}
  100%{transform:translateX(460%) skewX(-16deg)}}
html[data-layout="stage"] .rip__in::after,
html[data-layout="stage"] .mrip__in::after{
  content:'';position:absolute;top:-30%;bottom:-30%;left:0;width:30%;pointer-events:none;z-index:3;
  background:linear-gradient(100deg,rgba(255,255,255,0) 0%,rgba(255,255,255,.08) 30%,rgba(255,255,255,.62) 50%,rgba(255,255,255,.08) 70%,rgba(255,255,255,0) 100%);
  transform:translateX(-170%) skewX(-16deg);
  animation:slSheen 3.6s cubic-bezier(.45,0,.2,1) infinite;animation-delay:1.2s}
html[data-layout="stage"] .rip__in,
html[data-layout="stage"] .mrip__in{position:relative;overflow:hidden}
html[data-layout="stage"][data-state="rip"] .rip__in::after,
html[data-layout="stage"][data-state="rip"] .mrip__in::after{animation:none}

/* 6.3 TURBO IS ITS OWN COLOUR AND IT MOVES (Mike 10:21: "make the turbo
   mode button more exciting? ... a different colour and have it animated").
   The dock reads pink (RIP PACK), yellow (WHAT'S INSIDE) — turbo takes the
   third: an amber-to-orange charge with dark ink, the bolt pulsing and the
   knob's dot glowing. OFF stays the white ring (style.css:475), so one
   glance still tells the state. Cyan was the first idea and sank into the
   blue sky. */
@keyframes slBolt{
  0%,100%{transform:scale(1);opacity:.85}
  50%{transform:scale(1.3);opacity:1}}
@keyframes slKnob{
  0%,100%{transform:scale(1)}
  50%{transform:scale(1.18)}}
/* transform + opacity only (Luke, 6 Sep 13:00, the phone lag): the first
   version animated a filter drop-shadow and a box-shadow, both repaint every
   frame — the knob's glow is static now and the pulse is a scale */
html[data-layout="stage"] .mturbo[aria-checked="true"],
html[data-layout="stage"] .turbo[aria-checked="true"],
html[data-layout="stage"] .multi[data-on="1"]{
  background:linear-gradient(180deg,#FFC24A 0%,#FF9224 52%,#F0640F 100%);
  border-color:var(--key);color:#1E0A00;
  box-shadow:0 0 0 2px rgba(255,255,255,.55),0 0 20px -2px rgba(255,140,31,.9),0 4px 0 0 rgba(11,15,24,.28)}
html[data-layout="stage"] .mturbo[aria-checked="true"] .mturbo__bolt,
html[data-layout="stage"] .turbo[aria-checked="true"] .turbo__bolt,
html[data-layout="stage"] .multi[data-on="1"] .multi__bolt{
  fill:#1E0A00;transform-origin:50% 50%;animation:slBolt 1.3s ease-in-out infinite}
html[data-layout="stage"] .mturbo[aria-checked="true"] .mturbo__knob,
html[data-layout="stage"] .turbo[aria-checked="true"] .turbo__knob{
  background:#3A1500;border-color:rgba(0,0,0,.45)}
html[data-layout="stage"] .mturbo[aria-checked="true"] .mturbo__knob i,
html[data-layout="stage"] .turbo[aria-checked="true"] .turbo__knob i{
  background:linear-gradient(180deg,#FFF 0%,#FFE0A8 100%);animation:slKnob 1.3s ease-in-out infinite}
html[data-layout="stage"] .mturbo:active{transform:translateY(1px)}
/* OFF IS DARK ORANGE, NOT GREY (Luke, 6 Sep 13:00: "it's just boring before
   you click it ... maybe a darker shade of orange, and then when you click it,
   it goes that bright shade"). Same hue at rest, embers; the bright charge is
   the reward for turning it on. Both shells. */
html[data-layout="stage"] .mturbo[aria-checked="false"],
html[data-layout="stage"] .turbo[aria-checked="false"],
html[data-layout="stage"] .multi[data-on="0"]{
  background:linear-gradient(180deg,#C24F0E 0%,#8F3608 58%,#6A2606 100%);
  border-color:rgba(255,170,70,.9);color:#FFC97A;
  box-shadow:0 0 14px -6px rgba(255,140,31,.55),0 4px 0 0 rgba(11,15,24,.28)}
html[data-layout="stage"] .mturbo[aria-checked="false"] .mturbo__bolt,
html[data-layout="stage"] .turbo[aria-checked="false"] .turbo__bolt,
html[data-layout="stage"] .multi[data-on="0"] .multi__bolt{fill:#FFC97A;animation:none}
html[data-layout="stage"] .mturbo[aria-checked="false"] .mturbo__knob,
html[data-layout="stage"] .turbo[aria-checked="false"] .turbo__knob{background:#2A1200;border-color:rgba(255,158,58,.45)}
html[data-layout="stage"] .mturbo[aria-checked="false"] .mturbo__knob i,
html[data-layout="stage"] .turbo[aria-checked="false"] .turbo__knob i{background:#FFC97A;box-shadow:none;animation:none}

/* 6.4 THE PHONE'S OVERLAYS ARE BOTTOM SHEETS (Mike 10:23: "there's lots of
   pop ups, I'd consider on mobile testing out bottom sheets instead").
   The centred card of §"THE OVERLAYS" (stage.css:1129) becomes a sheet:
   pinned to the bottom bar, full width, rounded at the top only, a grip,
   and it rises in rather than scaling. Same for the module panels (vault,
   store, grail pool, crate odds). Sign-in (.auth) and the Pokédex keep
   their own cards. */
@media (max-width:1023px){
  html[data-layout="stage"] .ov{
    padding:calc(var(--mh-top) + 28px) 0 calc(var(--mh-nav) + var(--mh-bot));place-items:end stretch}
  html[data-layout="stage"] .ov__panel{
    width:100%;max-width:none;height:auto;max-height:100%;
    border-radius:24px 24px 0 0;border:3px solid var(--key);border-bottom:0;
    transform:translateY(48px);transition:transform .32s cubic-bezier(.22,.8,.36,1)}
  html[data-layout="stage"] .ov[data-open="1"] .ov__panel{transform:none}
  html[data-layout="stage"] .ov__panel::before{
    content:'';position:absolute;left:50%;top:9px;width:46px;height:5px;border-radius:3px;
    background:rgba(255,255,255,.38);transform:translateX(-50%);z-index:6;pointer-events:none}
  html[data-layout="stage"] .ov__head{padding-top:24px}
  html[data-layout="stage"] .vault,
  html[data-layout="stage"] .store,
  html[data-layout="stage"] .grail,
  html[data-layout="stage"] .codds{
    align-items:flex-end;padding:calc(var(--mh-top) + 28px) 0 calc(var(--mh-nav) + var(--mh-bot))}
  html[data-layout="stage"] .vault__panel,
  html[data-layout="stage"] .store__panel,
  html[data-layout="stage"] .grail__panel,
  html[data-layout="stage"] .codds__panel{
    border-radius:24px 24px 0 0;border-bottom:0;position:relative;
    transform:translateY(48px);transition:transform .32s cubic-bezier(.22,.8,.36,1)}
  html[data-layout="stage"] .vault__panel::before,
  html[data-layout="stage"] .store__panel::before,
  html[data-layout="stage"] .grail__panel::before,
  html[data-layout="stage"] .codds__panel::before{
    content:'';position:absolute;left:50%;top:9px;width:46px;height:5px;border-radius:3px;
    background:rgba(255,255,255,.38);transform:translateX(-50%);z-index:6;pointer-events:none}
}

/* 6.4b THE HERO SHADOW WAS CUT IN A STRAIGHT LINE (Luke, 6 Sep 11:50: "it
   should fade at the bottom, not just be a straight line ... like the word
   Expert has got an invisible box around it"). Measured: the pack's bottom
   at 584, .sl-rail's overflow:hidden edge at 607 — the tier row's top — and
   the shadow (18px down, 22px blur) reaching ~624. The rail keeps clipping
   sideways (the ring's wrap) and lets the shadow through underneath. */
html[data-layout="stage"] .sl-rail{overflow-x:clip;overflow-y:visible}   /* both shells since 12:05 (the desktop hero wears it too) */

/* 6.5 THE VIGNETTE IS IN THE ART NOW. The first pass was a navy gradient
   layer over the plate (.bg::after). Luke, 6 Sep 12:05, reading Mike's
   "even better if the actual design itself is darker in those areas rather
   than forcing a gradient over it": the three mobile plates carry the darker
   top and bottom bands themselves — tools-bg-bands.py, revertible from
   _bg-collections/originals-mobile/. No overlay layer here any more. */

/* 6.6 THE SKY TAKES THE PACK'S COLOUR (stage.js §6.2). Two layers in .bg
   between the collection plates (z 0, .sl-bg) and the vignette (z 3). */
html[data-layout="stage"] .sl-tint{
  position:absolute;inset:0;z-index:1;pointer-events:none;
  background:var(--sl-tint,transparent);mix-blend-mode:soft-light;opacity:0;
  transition:background-color .7s ease,opacity .7s ease}
html[data-layout="stage"] .sl-tint[data-on="1"]{opacity:.9}
@keyframes slDrift{
  0%{transform:translate(-7%,-5%) scale(1)}
  100%{transform:translate(7%,6%) scale(1.18)}}
html[data-layout="stage"] .sl-drift{
  position:absolute;inset:-30%;z-index:2;pointer-events:none;
  background:radial-gradient(closest-side at 50% 48%,var(--sl-tint,transparent) 0%,transparent 72%);
  mix-blend-mode:screen;opacity:0;transition:opacity 1s ease;will-change:transform;
  animation:slDrift 26s ease-in-out infinite alternate}
html[data-layout="stage"] .sl-drift[data-on="1"]{opacity:.42}
@media (prefers-reduced-motion:reduce){
  html[data-layout="stage"] .sl-drift{animation:none}
  html[data-layout="stage"] .sl-tint{transition:none}
  html[data-layout="stage"] .rip__in::after,
  html[data-layout="stage"] .mrip__in::after{animation:none;display:none}
  html[data-layout="stage"] .mturbo__bolt,
  html[data-layout="stage"] .turbo__bolt,
  html[data-layout="stage"] .multi__bolt,
  html[data-layout="stage"] .mturbo__knob i,
  html[data-layout="stage"] .turbo__knob i{animation:none}
  html[data-layout="stage"] .sl-pack img.sl-bounce{animation:none}
}

/* 6.7 THE CARD, NOT THE CASE (Mike 10:16; stage.js mirrorStrip wraps each
   feed image in .hit__pic). The window is the card's own 5:7; the photo is
   scaled to 128% of it and pulled up by 26% of its own height so the case
   and the grade label are outside the frame — measured on two real slabs.
   The window inherits the old tile's frame; the img loses it. */
html[data-layout="stage"] .hit__pic{
  position:relative;display:block;overflow:hidden;flex:0 0 auto;width:40px;height:56px;border-radius:5px;
  border:1.5px solid rgba(255,255,255,.85);background:rgba(11,15,24,.12);
  box-shadow:0 3px 7px -2px rgba(11,15,24,.45)}
html[data-layout="stage"] .hit__pic .hit__img{
  position:absolute;left:-14%;top:0;width:128%;height:auto;max-width:none;
  border:0;border-radius:0;box-shadow:none;background:none;object-fit:fill;
  transform:translateY(-26%)}
@media (max-width:1023px){
  html[data-layout="stage"] .sl-feed .hit__pic{width:26px;height:36px;border-radius:4px;box-shadow:none}
  html[data-layout="stage"] .sl-feed .hit__pic .hit__img{width:128%;height:auto;object-position:0 0}
}
/* the card itself (api/cardart.js): no crop, the whole print fills the 5:7 window */
html[data-layout="stage"] .hit__pic--art .hit__img{
  position:static;width:100%;height:100%;max-width:none;object-fit:cover;transform:none}

/* 6.8 NO STARS BEHIND THE PANELS (Luke, 6 Sep 12:40: "I don't like the star
   effect behind it. Remove that from every page that has that star on it").
   style.css:609 draws a gold star-outline burst with a halftone lobe in the
   bottom-right of the Vault, What's Inside and How It Works panels, and the
   four-point white/pink sparks (.spark) sit on their titles. All gone, on
   every overlay, both shells. The dots (.ov__dots) are not stars and stay. */
html[data-layout="stage"] .ov__burst,
html[data-layout="stage"] .ov__panel .spark{display:none}

/* 6.9 THE SHEET FADES AT THE BOTTOM (Luke, 6 Sep 12:40: "it just needs to
   fade at the bottom rather than just cut off behind nothing"). The
   scrolling body wears a 40px fade on its lower edge; the extra bottom
   padding means the last card clears the fade once you reach the end. The
   odds tabs' horizontal fade stays off (stage.css:1195) — this is vertical. */
html[data-layout="stage"] .ov__body{
  -webkit-mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 40px),transparent 100%);
  mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 40px),transparent 100%);
  padding-bottom:44px}
html[data-layout="stage"] .ov--odds .ov__body{
  -webkit-mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 40px),transparent 100%);
  mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 40px),transparent 100%)}

/* ══════════════════════════════════════════════════════════════════
   6.11 LUKE'S PHONE TEST (6 Sep 13:00) — stage.js §6.10 carries the swipe.
   ══════════════════════════════════════════════════════════════════ */

/* THE PACKS: no filters per frame (stage.js loadCollection / paint). The
   shadow is a rounded box behind the art whose opacity is the bump; the
   dimming is a veil masked to the art's silhouette. style.css/stage.css:310
   gave every still a drop-shadow filter — off. */
html[data-layout="stage"] .sl-pack img{filter:none}
html[data-layout="stage"] .sl-shadow{
  position:absolute;left:4%;right:4%;top:3%;bottom:3%;border-radius:8px;pointer-events:none;
  box-shadow:0 18px 22px rgba(11,15,24,.55);opacity:0;will-change:opacity}
html[data-layout="stage"] img.sl-dim{
  position:absolute;inset:0;width:100%;height:100%;pointer-events:none;opacity:0;will-change:opacity;
  filter:brightness(.66)}          /* static — rasterised once; only the opacity moves (18:20) */
/* the finger owns the rail on the phone: with pan-y the browser could claim a
   swipe that drifted vertically as a scroll and cancel our pointer — "you have
   to do it slowly for it to work". The page does not scroll, so nothing is lost. */
@media (max-width:1023px){
  html[data-layout="stage"] .sl-rail{touch-action:none}
}

/* THE SHEETS: to the bottom of the screen, the bar slides away under them,
   they rise with an overshoot and drop on close. Overrides §6.4. */
@media (max-width:1023px){
  html[data-layout="stage"] .mnav{transition:transform .32s cubic-bezier(.4,0,.2,1),opacity .34s ease-out}
  html[data-layout="stage"][data-ov="1"] .mnav,
  html[data-layout="stage"] body:has(.vault[data-open="1"],.store[data-open="1"],.grail[data-open="1"],.codds[data-open="1"]) .mnav{
    transform:translateY(calc(100% + 32px))}
  html[data-layout="stage"] .ov{
    padding-bottom:0;transition:opacity .26s ease-out,visibility 0s linear .36s}
  html[data-layout="stage"] .ov[data-open="1"]{transition:opacity .2s ease-out,visibility 0s}
  html[data-layout="stage"] .ov__panel{
    box-shadow:none;padding-bottom:var(--mh-bot);
    transform:translateY(100%);transition:transform .34s cubic-bezier(.4,0,.2,1)}
  html[data-layout="stage"] .ov[data-open="1"] .ov__panel{
    transform:none;transition:transform .5s cubic-bezier(.2,1.25,.3,1)}      /* the overshoot is the bounce */
  html[data-layout="stage"] .ov__panel.is-dragging,
  html[data-layout="stage"] .ov[data-open="1"] .ov__panel.is-dragging{transition:none}   /* the open state's bounce outranked the bare class */
  html[data-layout="stage"] .ov__head,
  html[data-layout="stage"] .grail__head,
  html[data-layout="stage"] .vault__head,
  html[data-layout="stage"] .store__head,
  html[data-layout="stage"] .codds__head{touch-action:none}
  html[data-layout="stage"] .vault,
  html[data-layout="stage"] .store,
  html[data-layout="stage"] .grail,
  html[data-layout="stage"] .codds{
    padding-bottom:0;transition:opacity .26s ease-out,visibility 0s linear .36s}
  html[data-layout="stage"] .vault[data-open="1"],
  html[data-layout="stage"] .store[data-open="1"],
  html[data-layout="stage"] .grail[data-open="1"],
  html[data-layout="stage"] .codds[data-open="1"]{transition:opacity .2s ease-out,visibility 0s}
  html[data-layout="stage"] .vault__panel,
  html[data-layout="stage"] .store__panel,
  html[data-layout="stage"] .grail__panel,
  html[data-layout="stage"] .codds__panel{
    box-shadow:none;padding-bottom:var(--mh-bot);
    transform:translateY(100%);transition:transform .34s cubic-bezier(.4,0,.2,1)}
  html[data-layout="stage"] .vault[data-open="1"] .vault__panel,
  html[data-layout="stage"] .store[data-open="1"] .store__panel,
  html[data-layout="stage"] .grail[data-open="1"] .grail__panel,
  html[data-layout="stage"] .codds[data-open="1"] .codds__panel{
    transform:none;transition:transform .5s cubic-bezier(.2,1.25,.3,1)}
  html[data-layout="stage"] .vault__panel.is-dragging,
  html[data-layout="stage"] .store__panel.is-dragging,
  html[data-layout="stage"] .grail__panel.is-dragging,
  html[data-layout="stage"] .codds__panel.is-dragging,
  html[data-layout="stage"] .vault[data-open="1"] .vault__panel.is-dragging,
  html[data-layout="stage"] .store[data-open="1"] .store__panel.is-dragging,
  html[data-layout="stage"] .grail[data-open="1"] .grail__panel.is-dragging,
  html[data-layout="stage"] .codds[data-open="1"] .codds__panel.is-dragging{transition:none}
  /* "the back button is tiny" */
  html[data-layout="stage"] .grail__back{font-size:16px;min-height:44px;padding:8px 12px 8px 0;letter-spacing:.06em}
  /* LIVE PULL's RIP ONE (Luke: "huge and stretches from one side to the other
     ... the words are tiny") — sized to its word, bigger type, centred */
  html[data-layout="stage"] .hitov__meta .redeem{
    align-self:center;width:auto;min-width:210px;height:56px;padding:0 38px;font-size:27px;margin-top:12px}
  /* THE TYPE (Luke: "the fonts are too small ... like the small print on legal
     text"). The module overlays size their text from the tokens, so the tokens
     move together on the phone; the few hard-coded small ones follow. */
  html[data-layout="stage"]{--fs-xs:14px;--fs-sm:15px;--fs-md:17px;--fs-lg:19px}
  html[data-layout="stage"] .ov__sub{font-size:15px}
  html[data-layout="stage"] .mprof__note{font-size:15px}
  html[data-layout="stage"] .pgrail__note{font-size:15px}
  html[data-layout="stage"] .ov__foot{font-size:15px}
  /* MY PROFILE (Luke: "where it says your email, it's absolutely tiny") */
  html[data-layout="stage"] .mprof__who>div b{font-size:22px}
  html[data-layout="stage"] .mprof__who>div span{font-size:16px;margin-top:3px}
  html[data-layout="stage"] .mbal__k{font-size:13px}
  html[data-layout="stage"] .mbal__v{font-size:23px}
  html[data-layout="stage"] .mrank__b .rank__val{font-size:16px}
  html[data-layout="stage"] .mrank__b .rank__next{font-size:16px}
  html[data-layout="stage"] .mrow{font-size:16px}
  html[data-layout="stage"] .hitov__r dt{font-size:16px}
  html[data-layout="stage"] .hitov__r dd{font-size:17px}
}
@media (prefers-reduced-motion:reduce){
  html[data-layout="stage"] .ov__panel,
  html[data-layout="stage"] .vault__panel,
  html[data-layout="stage"] .store__panel,
  html[data-layout="stage"] .grail__panel,
  html[data-layout="stage"] .codds__panel{transition:none}
}

/* 6.12 THE CRATE'S STATE LINE IS WHITE (Luke, 6 Sep 18:55, desktop, the new
   background: "where it says 'Rip a pack to unlock', you can't see it").
   Navy type sat on the art's navy silhouettes. White, with LOCKED's own
   key-ink outline so it reads on any part of the plate. */
html[data-layout="stage"] #v3CrateLbl,
html[data-layout="stage"] .sl-crate #rrCrateV{
  color:#FFF;-webkit-text-stroke:2.5px var(--key);paint-order:stroke fill;
  text-shadow:0 2px 0 rgba(11,15,24,.35)}

/* 6.13 THE PHONE'S LIVE FEED KEPT VANISHING (Luke, 6 Sep 18:55, iPhone:
   "the live feed keeps disappearing ... flashing away and then coming
   back"). Not reproducible headless — Chrome and WebKit both held it for
   90s with the animation running — which leaves iOS compositing, and the
   one thing on this strip that is a known WebKit flicker: style.css:207
   puts a -webkit-mask-image (the edge fade) on .strip__viewport while its
   child animates a transform. A masked container over a composited,
   moving child is exactly the case where Safari drops frames of the whole
   box. So on the phone the mask comes OFF (the edges clip instead of
   fading — the left one sits under the LIVE label anyway), the box gets
   paint containment, the list a 3D context, and :hover — which sticks
   after a tap on iOS and was pausing the marquee (style.css:214) — no
   longer pauses it. Unverified on a device: Luke to confirm. */
@media (max-width:1023px){
  html[data-layout="stage"] .strip__viewport{
    -webkit-mask-image:none;mask-image:none;overflow:hidden;contain:paint;
    -webkit-perspective:1000px;perspective:1000px}
  html[data-layout="stage"] .strip__viewport:hover .strip__list{animation-play-state:running}
  html[data-layout="stage"] .sl-feed{transform:translate3d(0,0,0);-webkit-backface-visibility:hidden;backface-visibility:hidden}
}

/* ══════════════════════════════════════════════════════════════════
   6.14 LUKE, 6 Sep 18:48 — desktop from the QA's ultra-wide, the rail,
   the menu, the phone type, the feed.
   ══════════════════════════════════════════════════════════════════ */

/* The foot group is not mounted any more (stage.js): no Support/Legal/Refer
   a friend rows, no line above them, no white fade behind them. */
html[data-layout="stage"] .sl-side__foot{display:none}

/* THE RAIL'S PILLS ARE ONE WIDTH ("the silhouette should always be the same
   size as vault or store, whichever one's bigger ... when it's clicked the
   pink box should always be the same size"). The nav column is as wide as
   its widest row and every row stretches to it — the hover silhouette, the
   active sticker and OPEN included. Collapsed, the words shrink to 0 and the
   column follows them down to the icon. Replaces the align-self:flex-start
   hug of 5 Sep. */
html[data-layout="stage"] .sl-side .header__nav{align-items:stretch;width:max-content}
html[data-layout="stage"] .sl-side .header__nav .sl-link{align-self:auto;width:auto}

/* THE DOCK ROW ("What's Inside should always be the same size as the Turbo
   button ... it needs to be fatter"): both pills 210px, centred content. */
@media (min-width:1024px){
  html[data-layout="stage"] .sl-meta .turborow .turbo,
  html[data-layout="stage"] .sl-meta .turborow .multi,
  html[data-layout="stage"] .sl-meta .whatsinside{width:210px;box-sizing:border-box;justify-content:center}
}

/* THE ULTRA-WIDE / SHORT SCREEN (the QA's monitor: "the section with Pro,
   RIP PACK, Turbo and What's Inside is too large compared to the pack").
   Under 760px of height the chrome under the packs gives up ~70px — smaller
   tier name, a shorter RIP PACK, slimmer pills, tighter gaps — and the ring
   above loses 24px. layout() sizes the pack from the rail's height, so all
   of that goes to the pack. */
@media (min-width:1024px) and (max-height:760px){
  html[data-layout="stage"] .sl-cats{height:88px}   /* 74 put the hero's top over the active name (measured 1540x572) */
  html[data-layout="stage"] .sl-cat img,html[data-layout="stage"] .sl-cat svg{width:32px;height:32px}
  html[data-layout="stage"] .sl-cat b{font-size:19px}
  html[data-layout="stage"] .sl-tier{font-size:32px}
  html[data-layout="stage"] .sl-tierrow .sl-tier{min-width:220px}
  html[data-layout="stage"] .sl-meta .hring--rip{margin-top:4px}
  html[data-layout="stage"] .hring>.rip{height:52px}
  html[data-layout="stage"] .rip__in{font-size:32px}
  html[data-layout="stage"] .sl-meta .turborow.stage__row{margin-top:4px}
  html[data-layout="stage"] .sl-meta .turborow .turbo,
  html[data-layout="stage"] .sl-meta .turborow .multi,
  html[data-layout="stage"] .sl-meta .whatsinside{height:32px;font-size:16px}
  html[data-layout="stage"] .stage{padding-bottom:4px}
}

/* THE PHONE'S TYPE, PROPERLY THIS TIME (Luke: "loads of the fonts are way
   too small ... the email address on your profile page ... every font size,
   even Pokémon, One Piece, Sports and Live at the top ... the store, all the
   text is tiny"). Tokens up another step, and every hard-coded size in the
   sheets and the strip raised by hand. */
@media (max-width:1023px){
  html[data-layout="stage"]{--fs-xs:15px;--fs-sm:16px;--fs-md:18px;--fs-lg:20px;--fs-xl:26px}
  /* the top: collection names, LIVE, the feed cards */
  html[data-layout="stage"] .sl-cat b{font-size:20px}
  /* Luke, 7 Sep 01:50: "on mobile the live feed doesn't go over to the left
     enough ... bring LIVE over to the left so there is more space for the
     carousel". Measured at 390: the strip began at 13px (--mh-pad), LIVE was
     a centred 72px box, the feed started at 92. Now the strip runs 6px from
     the left edge to the right edge (the feed's own fade ends it), LIVE is
     its word's width — the feed starts at ~62 and gains ~40px. */
  html[data-layout="stage"] .strip{left:6px;right:0}
  html[data-layout="stage"] .strip__live{font-size:17px;width:auto;padding:0 8px 0 6px;justify-content:flex-start}
  html[data-layout="stage"] .sl-feed .hit__n{font-size:15px;max-width:150px}
  html[data-layout="stage"] .sl-feed .hit__m{font-size:14px}
  html[data-layout="stage"] .sl-feed .hit__m b{font-size:15px}
  html[data-layout="stage"] .sl-feed .hit__grade{font-size:12.5px}
  html[data-layout="stage"] .sl-tier{font-size:34px}
  /* my profile */
  html[data-layout="stage"] .mprof__who>div b{font-size:24px}
  html[data-layout="stage"] .mprof__who>div span{font-size:17px;margin-top:3px}
  html[data-layout="stage"] .mbal__k{font-size:14px}
  html[data-layout="stage"] .mbal__v{font-size:25px}
  html[data-layout="stage"] .mprof__lbl{font-size:22px}
  html[data-layout="stage"] .mprof__note{font-size:16px;line-height:1.4}
  html[data-layout="stage"] .mprof__sec{font-size:27px}
  html[data-layout="stage"] .mrank__b .rank__name{font-size:38px}
  html[data-layout="stage"] .mrank__b .rank__val{font-size:17px}
  html[data-layout="stage"] .mrank__b .rank__next{font-size:17px}
  html[data-layout="stage"] .mcrate .daily__lbl{font-size:17px}
  html[data-layout="stage"] .mrow{font-size:17px;height:56px}
  html[data-layout="stage"] .mrow__v{font-size:16px}
  html[data-layout="stage"] .addamt{font-size:17px}
  html[data-layout="stage"] .addgo{font-size:22px}
  /* the module sheets' hard-coded small print */
  html[data-layout="stage"] .pvcard__src{font-size:13px}
  html[data-layout="stage"] .ctier__avg{font-size:14px}
  html[data-layout="stage"] .rdm__card small,
  html[data-layout="stage"] .rdm__f label,
  html[data-layout="stage"] .rdm__ord i,
  html[data-layout="stage"] .rdm__mini figcaption{font-size:13.5px}
  html[data-layout="stage"] .ov__sub{font-size:16px}
  html[data-layout="stage"] .steps p{font-size:17px}
  html[data-layout="stage"] .fact span{font-size:16px}
  html[data-layout="stage"] .fact b{font-size:22px}
  html[data-layout="stage"] .ov__foot{font-size:16px}
  html[data-layout="stage"] .hitov__r dt{font-size:17px}
  html[data-layout="stage"] .hitov__r dd{font-size:18px}
  html[data-layout="stage"] .mpack__p{font-size:21px}
}

/* THE PHONE FEED, SECOND PASS (Luke: still vanishing with the mask off, and
   "it doesn't fade behind LIVE ... doesn't fade in from the right"). The mask
   was not the cause, so the fades come back. What IS new in the strip since
   the morning is a transform inside every tile (the slab crop) — a moving
   composited list full of transformed, overflow-clipped children is a
   layer-per-tile on iOS. The crop is done with plain offsets now, no
   transform anywhere inside the marquee; the containment hacks of 6.13 go. */
@media (max-width:1023px){
  html[data-layout="stage"] .strip__viewport{
    -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 34px,#000 calc(100% - 44px),transparent 100%);
    mask-image:linear-gradient(90deg,transparent 0,#000 34px,#000 calc(100% - 44px),transparent 100%);
    contain:none;-webkit-perspective:none;perspective:none}
}
html[data-layout="stage"] .hit__pic .hit__img{transform:none;top:-39.3%}

/* ══════════════════════════════════════════════════════════════════
   6.15 THE DESKTOP HEADER, REORDERED (Luke, 6 Sep 19:10): "put the live
   feed right at the top, and bring everything in the header down ... remove
   the big add button ... the sound button, the rip points, the USDC balance
   and the profile button on the same row as the live feed is now ... the
   rank and the progress bar underneath on the right ... keep the BlockRips
   logo the same ... completely remove the timer from the bottom-right."
   .page is a flex column, so the strip takes order:-1 and becomes row one,
   slimmer, starting where the rail's wordmark ends (in both rail states —
   the logo never moves). The header is row two: the cluster on the right,
   +ADD gone (funds live under the USDC pill and in the menu). The rank block
   hangs under the cluster, right-aligned. */
@media (min-width:1024px){
  html[data-layout="stage"] .strip{
    order:-1;--strip-h:46px;margin:14px 0 4px calc(242px + 6px)}
  html[data-layout="stage"][data-side="collapsed"] .strip{margin-left:calc(242px + 6px)}
  html[data-layout="stage"] .strip__live{width:92px;font-size:15px}
  html[data-layout="stage"] .sl-feed .hit,
  html[data-layout="stage"] .sl-feed .hit[data-rarity]{height:46px;gap:9px;padding:0 12px 0 7px}
  html[data-layout="stage"] .sl-feed .hit__pic{width:28px;height:39px;border-radius:4px}
  html[data-layout="stage"] .sl-feed .hit__u{font-size:11px;max-width:150px}
  html[data-layout="stage"] .sl-feed .hit__n{font-size:14px;max-width:150px}
  html[data-layout="stage"] .sl-feed .hit__m{font-size:13px;margin-top:2px}
  html[data-layout="stage"] .sl-feed .hit__m b{font-size:14px}
  html[data-layout="stage"] .sl-feed .hit__grade{font-size:11px}
  html[data-layout="stage"] .strip__viewport{padding:11px 0 20px;margin:-11px 0 -20px}

  html[data-layout="stage"] .header{height:60px;margin-bottom:6px}
  html[data-layout="stage"] #addWrap,
  html[data-layout="stage"] #addBtn{display:none}
  html[data-layout="stage"] .sl-rank{
    left:auto;right:0;top:100%;transform:none;margin-top:6px}
  html[data-layout="stage"] .sl-rank:hover{transform:translateY(-1px)}
  /* the ring row spans the width at the rank's height; its empty space must
     not swallow the rank's clicks */
  html[data-layout="stage"] .sl-cats{pointer-events:none}
  html[data-layout="stage"] .sl-cat{pointer-events:auto}

  /* THE CLOCK IS GONE (Luke 19:10: "completely remove the timer from the
     bottom-right corner, like the clock"): the stopwatch drawing behind the
     crate (.bg__clock) and the countdown digits. LOCKED / OPEN still show —
     they are the crate's state, not a clock; stage.js flags the countdown. */
  html[data-layout="stage"] .bg__clock{display:none}
  /* 7 Sep 15:05: the digits are BACK — see §6.35 (the label alone "doesn't make any sense") */
}

/* 6.16 THE RING RISES INTO THE HEADER ROW (Luke, 6 Sep 19:54: "we haven't
   made use of the space above Sports, Pokémon and One Piece ... all that can
   be moved up, and the packs made bigger"). Row two's centre is empty now
   that the rank hangs on the right, so the ring is pulled up by the header's
   height and the rail below it grows by the same. The header's cluster is
   at the right edge, the ring is centred; they do not meet under 1024. */
@media (min-width:1024px){
  html[data-layout="stage"] .sl-cats{margin-top:-66px}
  /* the header (z 6) paints over the ring (inside .shell, z 2) and was
     swallowing the collection buttons' clicks — measured: elementFromPoint on
     Sports returned the header. The header's empty width lets clicks through;
     its cluster and the rank keep theirs. */
  html[data-layout="stage"] .header{pointer-events:none}
  html[data-layout="stage"] .header__right,
  html[data-layout="stage"] .sl-rank{pointer-events:auto}
}

/* 6.17 Luke, 6 Sep 20:05 — three more. */
/* THE FEED STARTS NEARER THE LOGO ("the live feed doesn't go over to the left
   enough ... the word LIVE can move left, with a decent gap between it and
   the logo"). The wordmark ends at x=220 (1512); the strip's margin cleared
   the whole 242px rail AND sat inside .page's 33px padding, so LIVE began at
   288. Now the strip's left edge lands at 246 whatever the page padding, and
   LIVE is a 70px label with the dot at its left. */
@media (min-width:1024px){
  html[data-layout="stage"] .strip,
  html[data-layout="stage"][data-side="collapsed"] .strip{margin-left:calc(246px - clamp(16px,2.2vw,40px))}
  html[data-layout="stage"] .strip__live{width:70px;justify-content:flex-start;padding-left:4px}
}
/* THE PHONE'S TYPE FLOOR, AGAIN (Luke 20:00: "how many times ... the font
   is tiny"). Measured on eval with real data after the 18:48 pass: the
   only text under 16px left in the sheets was the odds rows/tabs (15), the
   grail pool card names (15) and the feed cards (15/12.5). Tokens go to
   16/17/19/21 and those four follow; nothing in a sheet is under 16px now. */
@media (max-width:1023px){
  html[data-layout="stage"]{--fs-xs:16px;--fs-sm:17px;--fs-md:19px;--fs-lg:21px;--fs-xl:27px}
  html[data-layout="stage"] .otab{font-size:16px}
  html[data-layout="stage"] .orow__t,
  html[data-layout="stage"] .orow__p,
  html[data-layout="stage"] .orow__v{font-size:17px}
  html[data-layout="stage"] .pgcard__name{font-size:17px}
  html[data-layout="stage"] .sl-feed .hit__u{font-size:12px;max-width:160px}
  html[data-layout="stage"] .sl-feed .hit__n{font-size:16px;max-width:160px}
  html[data-layout="stage"] .sl-feed .hit__m{font-size:15px}
  html[data-layout="stage"] .sl-feed .hit__m b{font-size:16px}
  html[data-layout="stage"] .sl-feed .hit__grade{font-size:14px}
  html[data-layout="stage"] .sl-feed .hit,
  html[data-layout="stage"] .sl-feed .hit[data-rarity]{height:50px}
  html[data-layout="stage"] .mwi__q{width:20px;height:20px;font-size:13px}
  html[data-layout="stage"] .ov__panel,
  html[data-layout="stage"] .vault__panel,
  html[data-layout="stage"] .store__panel,
  html[data-layout="stage"] .grail__panel,
  html[data-layout="stage"] .codds__panel{font-size:17px}
}

/* 6.18 SIGNED IN AT LAST (Luke, 6 Sep 20:18: "are you logged in here? ...
   Otherwise what are we doing"). Every audit before this one was signed
   out. Measured signed in (@test1 on eval, the phone rules, real store /
   profile / crate content): the store's REDEEM / HISTORY tabs were 14px,
   the aisle's "4 OPTIONS" 10px, CHOOSE A PACK's "7 packs" 13px, the
   profile's RIP POINTS / USDC labels 14px, the crate modal's YOU chip
   10px and its "avg 504 points" 14px. Nothing else in a sheet is under
   16px. The vault with cards, redeem past step one and the rip flow are
   still unmeasured: this account holds no cards and eval cannot rip. */
@media (max-width:1023px){
  /* THE STORE (Luke 20:18): "the REDEEM / HISTORY tabs are titles and read
     too small" — the sheet title's own face (Barlow Condensed 900) at 20px;
     and "the tab strip is not connected to the header above it — a small
     gap": the balance cards' 16px bottom margin and the strip's 8px top
     padding were the gap. The strip now sits flush under the cards, a
     hairline above and below, so it reads as one band of the header. */
  html[data-layout="stage"] .store__bal{margin-bottom:0}
  html[data-layout="stage"] .store__tabs{margin-top:0;padding-top:0;border-top:1px solid var(--hairline)}
  html[data-layout="stage"] .store__tab{font-weight:900;font-size:20px;letter-spacing:.04em;padding:14px 8px 11px}
  /* the rest of the floor */
  html[data-layout="stage"] .saisle__count{font-size:16px;letter-spacing:.06em}
  html[data-layout="stage"] .mcat__c{font-size:16px}
  html[data-layout="stage"] .mbal__k{font-size:16px}
  html[data-layout="stage"] .ctier__you{font-size:15px;letter-spacing:.08em;padding:2px 9px}
  html[data-layout="stage"] .ctier__avg{font-size:16px}
}
/* TWO OUTLINES AROUND THE X, "SOMETIMES" (Luke 20:18, the vault and the
   profile sheets). Each module focuses its close button as it opens
   (vault.js:562, store.js:2068, crate-odds.js:458, grailpool.js:284;
   app.js:408 for the .ov family): after a tap, iOS sometimes treats that
   scripted focus as keyboard focus and paints a focus ring — the UA's own
   on .vault__close / .store__close / .codds__close, the 3px pink one of
   1243 on .ov__x — outside the 2px pink border: two rings. A finger never
   needs a focus ring, so on coarse pointers the close buttons draw none;
   a keyboard on the desktop keeps it. */
html[data-layout="stage"] :is(.ov__x,.vault__close,.store__close,.grail__back,.codds__close):focus:not(:focus-visible){outline:0}
@media (pointer:coarse){
  html[data-layout="stage"] :is(.ov__x,.vault__close,.store__close,.grail__back,.codds__close):focus-visible{outline:0}
}

/* 6.19 THE POKÉDEX IS A PAGE — and since 10 Sep it is one of FOUR, so the
   frame that used to live here is site/page.css and this section keeps only
   what is the Pokédex's alone.

   What moved, verbatim in behaviour: the pinned box (--dex-* became --pg-*),
   the fixed-head/scrolling-body split, the 14px feather Luke picked on 7 Sep
   ("option C"), the lobby stepping back, the phone's bars sliding out on
   scroll, and .fit handing its clicks to the header and the rail. Every one
   of those is now keyed on `.pg` / `html[data-page]` instead of
   `.dexmodal--page` / `[data-dexpage="1"]`, so the leaderboard, the clans and
   the profile get them too. site/pokedex.js hands its .dexmodal to
   PAGE.open(..., { mode: 'scroll' }) and keeps the .dexmodal--page class only
   for the two rules below. */
html[data-layout="stage"] .dexmodal--page .dexmodal__scrim,
html[data-layout="stage"] .dexmodal--page .dexmodal__bar{display:none}
/* the collection plates fade so the base Pokémon sky is what shows behind the
   grid, whatever collection was centred when the page opened */
@media (max-width:1023px){
  html[data-layout="stage"] .dexmodal--page .top__hint{display:none}   /* Luke 23:50: "on mobile dont do this hint" */
}
/* Luke, 6 Sep 23:45: "the sound button, USDC amount, profile circle etc. needs
   to be in the top right on this page with the rank XP bar on the header,
   central — ONLY ON THE POKÉDEX PAGE THOUGH". That scoped it to one page.
   11 Sep he widened it himself: "when I click into the clans page, the gold
   rank at the bottom and the bar should move up to the header, central in
   between the Blockrip logo and the notification icon. Same when I click the
   leaderboards page. They all need to be in the same position that it goes to
   when I go onto the POKÉDEX page ... the exact same position."
   So it is EVERY page now, not a list of names — a page is a destination with
   the lobby behind it, and the rank belongs to the header there. The rank
   leaves its bottom-left corner (§6.22) for the header's centre, the way it sat
   before §6.15. On the lobby it stays where it is.
   (The PROFILE page is in this too. Luke named the clans and the boards; the
   profile is the only page that would have been left with the bar sitting over
   its plate, which is the same thing he is pointing at. One selector to undo.) */
@media (min-width:1024px){
  html[data-layout="stage"][data-page] .sl-rank{
    position:absolute;left:50%;top:50%;right:auto;bottom:auto;margin:0;
    transform:translate(-50%,-50%);z-index:7}
  html[data-layout="stage"][data-page] .sl-rank:hover{transform:translate(-50%,-50%) translateY(-1px)}
}

/* 6.20 THE PROFILE DROPDOWN NEVER TOUCHES THE BOTTOM (Luke, 6 Sep 20:18):
   #acctMenu hangs 14px under the 60px header (style.css:792 .pop); on a short
   screen its rows ran past the viewport. It is capped to the room under it
   and scrolls inside.
   AND THE CAP HAD THE ZOOM BUG §6.14 WARNS ABOUT (16 Sep). The menu is inside
   .header, which carries zoom:var(--sl-s) — so 100vh computed to the window's
   height and then the zoom PAINTED it at 100vh x --sl-s. Above the reference
   window the cap was therefore bigger than the window: measured at 1512x982
   (--sl-s 1.029) the menu's own box ended at y=989 against a 982px viewport,
   7px under the bottom edge, as soon as it had enough rows to reach the cap —
   which the Language list (site/i18n.js) now gives it. Dividing by the zoom is
   the whole fix; 150px keeps its meaning (the menu's ~129px top plus air).
   After: bottom 965 at 1512x982, 606 at his 1147x655. */
@media (min-width:1024px){
  html[data-layout="stage"] #acctMenu{max-height:calc((100vh - 150px) / var(--sl-s,1));
    overflow:auto;overscroll-behavior:contain}
}
/* 6.21 THE RING A TOUCH BIGGER, THE PACKS A TOUCH SMALLER (Luke, 6 Sep 20:18,
   desktop: "Sports / One Piece / Pokémon slightly bigger", the packs
   "slightly smaller"). Names 23 -> 25px, icons 40 -> 44px; the pack's
   height term in stage.js layout() 0.94 -> 0.88 (hero 244 -> ~228 at 1512). */
@media (min-width:1024px) and (min-height:761px){   /* the short-screen sizes at 6.14 stay */
  html[data-layout="stage"] .sl-cat img,
  html[data-layout="stage"] .sl-cat svg{width:54px;height:54px}   /* 44 was "a bit too small now" (Luke 23:20) */
  html[data-layout="stage"] .sl-cat b{font-size:25px}
  /* the centred name (its icon sits 15px lower) ran 16px past the ring row's
     101px box and was clipped; the row clips sideways only, like the rail */
  html[data-layout="stage"] .sl-cats{overflow-x:clip;overflow-y:visible}
}

/* 6.22 THE RANK BAR, BOTTOM-LEFT (Luke, 6 Sep 23:15: "the rank should be in
   bottom left corner btw lets go with bottom left" — option B of the mock).
   It leaves the header's right (6.15) for the corner the foot group used to
   hold, 15% bigger, over the grass. Off during a rip, like the crate. */
@media (min-width:1024px){
  html[data-layout="stage"] .sl-rank{
    position:fixed;left:26px;bottom:22px;top:auto;right:auto;margin:0;
    transform:scale(1.15);transform-origin:0 100%;z-index:31;
    transition:transform .14s,opacity .3s ease}
  html[data-layout="stage"] .sl-rank:hover{transform:scale(1.15) translateY(-1px)}
  html[data-layout="stage"][data-state="rip"] .sl-rank{opacity:0;pointer-events:none}
  /* the rail (z 30, 242px wide, transparent) lies over the rank's left half
     and was taking its clicks (measured: elementFromPoint on the rank gave
     the rail). The rail's empty box passes clicks through; its rows keep them. */
  html[data-layout="stage"] .sl-side{pointer-events:none}
  html[data-layout="stage"] .sl-side > *{pointer-events:auto}
}

/* ═══════════════════════════════════════════════════════════════════════
   6.23 EVERY POP-UP ON BRAND — "1+", THE SKY SHEET WITH THE PINK KEYLINE
   (Luke, 6 Sep 20:24: "the vault looks dead, the store looks dead, the odds
   table ... doesn't feel on brand any more, info looks dead and boring";
   23:30 "leaning towards 2, or 1 with a pink border"; 23:45 "lets go with 1
   with the pink border"). The sheet is a piece of the stage: the sky with
   its halftone inside the pink keyline the sheets already wore, a white
   inner ring, frosted white cards with the key-ink outline and the hard
   drop the stage's buttons use, titles white with the key stroke, tabs as
   stickers (pink when on). The modules' own navy tokens are flipped on
   the panel so their rows and labels re-ink themselves; the hard-coded
   light inks from style.css's AA pass are re-inked here. Both shells. */
html[data-layout="stage"] .ov__panel,
html[data-layout="stage"] .vault__panel,
html[data-layout="stage"] .store__panel,
html[data-layout="stage"] .grail__panel,
html[data-layout="stage"] .codds__panel{
  --surface:#FFFFFF;--surface-raised:rgba(255,255,255,.90);--surface-sunk:#FFFFFF;--page-bg:#FFFFFF;
  --text:var(--key);--text-dim:#3A4354;--text-faint:#5A6478;
  --hairline:rgba(11,15,24,.14);--hairline-str:rgba(11,15,24,.30);
  --ink-dim:#3A4354;--ink-faint:#5A6478;--panel:rgba(255,255,255,.90);--line:rgba(11,15,24,.28);
  --sky-sheet:#6FB5F2;
  background:var(--sky-sheet) radial-gradient(circle,rgba(255,255,255,.34) 1.2px,transparent 1.5px) 0 0/8px 8px;
  border:3px solid var(--acc);color:var(--key);
  box-shadow:inset 0 0 0 2px rgba(255,255,255,.55),0 14px 0 0 rgba(11,15,24,.34),0 40px 90px -30px rgba(11,15,24,.55)}
/* the grip on the phone sheets: ink on sky, not white on navy */
html[data-layout="stage"] .ov__panel::before,
html[data-layout="stage"] .vault__panel::before,
html[data-layout="stage"] .store__panel::before,
html[data-layout="stage"] .grail__panel::before,
html[data-layout="stage"] .codds__panel::before{background:rgba(11,15,24,.35)}
/* titles: the module ones were the brand face at weight 400 on navy */
html[data-layout="stage"] .store__title,
html[data-layout="stage"] .vault__title,
html[data-layout="stage"] .grail__title,
html[data-layout="stage"] .codds__title{
  font-weight:900;font-style:italic;letter-spacing:.01em;color:#FFF;
  -webkit-text-stroke:4px var(--key);paint-order:stroke fill;transform:var(--slant);transform-origin:0 100%}
html[data-layout="stage"] .store__sub,
html[data-layout="stage"] .codds__sub,
html[data-layout="stage"] .vault__sub{color:var(--text-dim);font-weight:600}
/* close buttons and BACK: white stickers */
html[data-layout="stage"] .ov__x,
html[data-layout="stage"] .vault__close,
html[data-layout="stage"] .store__close,
html[data-layout="stage"] .codds__close{
  background:#FFF;border:2.5px solid var(--key);color:var(--key);border-radius:12px;box-shadow:0 3px 0 var(--key)}
html[data-layout="stage"] .ov__x:hover,
html[data-layout="stage"] .vault__close:hover,
html[data-layout="stage"] .store__close:hover,
html[data-layout="stage"] .codds__close:hover{background:var(--yellow);color:var(--key);border-color:var(--key)}
html[data-layout="stage"] .grail__back{color:var(--key);font-weight:900}
/* THE CARDS: frosted white, key outline, hard drop */
html[data-layout="stage"] .sbal,
html[data-layout="stage"] .srung,
html[data-layout="stage"] .pvcard,
html[data-layout="stage"] .pgcard,
html[data-layout="stage"] .storemsg,
html[data-layout="stage"] .fact,
html[data-layout="stage"] .steps li,
html[data-layout="stage"] .mprof__add,
html[data-layout="stage"] .mbal,
html[data-layout="stage"] .mrow,
html[data-layout="stage"] .ctier,
html[data-layout="stage"] .clad{
  background:rgba(255,255,255,.90);border:2.5px solid var(--key);border-radius:16px;
  box-shadow:0 4px 0 var(--key);color:var(--key)}
html[data-layout="stage"] .ctier,
html[data-layout="stage"] .clad{padding:12px 14px;margin:0 0 10px}
html[data-layout="stage"] .ctier:first-child,
html[data-layout="stage"] .clad:first-child{padding-top:12px;border-top:2.5px solid var(--key)}
html[data-layout="stage"] .ctier--you,
html[data-layout="stage"] .clad--you{background:var(--yellow)}
html[data-layout="stage"] .sbal--lead{border-color:var(--key)}
html[data-layout="stage"] .sbal--lead::before{display:none}
html[data-layout="stage"] .sbal--lead .sbal__v{color:var(--acc-lo)}
html[data-layout="stage"] .steps li{border-color:var(--key)}
/* rows that stay rows: the odds table's, the crate bands, the pull's facts */
html[data-layout="stage"] .orow{background:rgba(255,255,255,.84);border:2px solid var(--key);border-radius:12px}
html[data-layout="stage"] .orow__v,
html[data-layout="stage"] .orow__p{color:#3A4354}
html[data-layout="stage"] .orow__t{color:var(--key)}
html[data-layout="stage"] .cband__pct{color:var(--key)}
html[data-layout="stage"] .cband__tok{color:#3A4354}
/* the light inks the AA pass hard-coded for navy */
html[data-layout="stage"] .vault__count,
html[data-layout="stage"] .ov__foot,
html[data-layout="stage"] .ov__sub,
html[data-layout="stage"] .mrank .rank__next,
html[data-layout="stage"] .mcrate .daily__lbl,
html[data-layout="stage"] .clad__next,
html[data-layout="stage"] .codds__err,
html[data-layout="stage"] .codds__you{color:#3A4354}
html[data-layout="stage"] .mrank .rank__next b,
html[data-layout="stage"] .clad__next b,
html[data-layout="stage"] .mprof__who>div b{color:var(--key)}
html[data-layout="stage"] .ov__panel b,
html[data-layout="stage"] .ov__sub b,
html[data-layout="stage"] .vault__count b{color:var(--acc-lo)}
html[data-layout="stage"] .ov__h em,
html[data-layout="stage"] .ov__h b{color:var(--acc-hi)}
/* TABS AS STICKERS, on both sides of the strip; the strips lose their bar */
html[data-layout="stage"] .store__tabs,
html[data-layout="stage"] .vault__sort,
html[data-layout="stage"] .codds__tabs,
html[data-layout="stage"] .otabs{
  display:flex;justify-content:space-between;align-items:center;gap:10px;
  background:var(--sky-sheet) radial-gradient(circle,rgba(255,255,255,.34) 1.2px,transparent 1.5px) 0 0/8px 8px;
  border:0;padding:6px 0 12px}
html[data-layout="stage"] .store__tabs::after,
html[data-layout="stage"] .vault__sort::after,
html[data-layout="stage"] .codds__tabs::after{background:linear-gradient(180deg,var(--sky-sheet),transparent)}
html[data-layout="stage"] .store__tab,
html[data-layout="stage"] .vault__stab,
html[data-layout="stage"] .codds__tab,
html[data-layout="stage"] .otab{
  flex:0 0 auto;margin:0;padding:9px 14px;border-radius:12px;
  font-family:var(--display);font-weight:900;font-style:italic;font-size:17px;letter-spacing:.03em;text-transform:uppercase;
  color:var(--key);background:rgba(255,255,255,.92);border:2px solid var(--key);box-shadow:0 3px 0 var(--key);
  transform:var(--slant);transition:background .15s,color .15s}
html[data-layout="stage"] .store__tab:hover,
html[data-layout="stage"] .vault__stab:hover,
html[data-layout="stage"] .codds__tab:hover,
html[data-layout="stage"] .otab:hover{color:var(--key);background:var(--yellow)}
html[data-layout="stage"] .store__tab[aria-selected="true"],
html[data-layout="stage"] .vault__stab[aria-selected="true"],
html[data-layout="stage"] .codds__tab[aria-selected="true"],
html[data-layout="stage"] .otab.is-on{
  color:#FFF;border-color:var(--key);
  background:linear-gradient(180deg,var(--acc-hi) 0%,var(--acc) 48%,var(--acc-lo) 100%);
  box-shadow:0 0 0 2px #FFF,0 3px 0 2px var(--key)}
/* the crate/rank sheet (item 49): the new badge art, the SOON chip on the
   one-time rewards the server does not grant yet, the "you" chip */
html[data-layout="stage"] .cbadge{width:40px;height:40px;object-fit:contain;flex:0 0 auto;filter:drop-shadow(0 2px 0 rgba(11,15,24,.25))}
html[data-layout="stage"] .ctier__soon{
  display:inline-block;margin-left:6px;vertical-align:1px;padding:2px 7px;border-radius:999px;
  font:800 11px/1 var(--display);letter-spacing:.08em;color:var(--acc-lo);background:#FFF;border:2px solid var(--acc)}
html[data-layout="stage"] .ctier__you{background:var(--acc);color:#FFF;border:2px solid var(--key)}
@media (max-width:1023px){
  html[data-layout="stage"] .store__tab,
  html[data-layout="stage"] .codds__tab{font-size:20px;padding:10px 16px}
  html[data-layout="stage"] .store__tabs{margin-top:8px;border-top:0}
}
/* 6.23b the CHOOSE A PACK rows (mobile.js .mcat) were white type on the new
   white rows; and the sheet's "you" row and the current collection go yellow */
html[data-layout="stage"] .mcat{background:rgba(255,255,255,.90);border:2.5px solid var(--key);border-radius:14px;box-shadow:0 3px 0 var(--key)}
html[data-layout="stage"] .mcat__n{color:var(--key)}
html[data-layout="stage"] .mcat__c{color:#3A4354}
html[data-layout="stage"] .mcat[aria-current="true"]{background:var(--yellow);border-color:var(--key)}
html[data-layout="stage"] .mcat[aria-current="true"] .mcat__n{color:var(--acc-lo)}
/* 6.23c type that sits straight on the sky, not on a card (the contrast scan,
   scratchpad lowcontrast.js): section titles take the key stroke the stage's
   words wear, small print takes the key ink */
html[data-layout="stage"] .oblk__h h3,
html[data-layout="stage"] .hitov__meta h3,
html[data-layout="stage"] .mprof__sec{color:#FFF;-webkit-text-stroke:4px var(--key);paint-order:stroke fill}
html[data-layout="stage"] .oblk__h b,
html[data-layout="stage"] .grail__sub b{color:#FFF;-webkit-text-stroke:3px var(--key);paint-order:stroke fill}
html[data-layout="stage"] .hitov__r dd,
html[data-layout="stage"] .hitov__r dt,
html[data-layout="stage"] .grail__sub,
html[data-layout="stage"] .pgrail__note,
html[data-layout="stage"] .mprof__who>div span,
html[data-layout="stage"] .mprof__note{color:var(--key)}
html[data-layout="stage"] .hitov__r dt,
html[data-layout="stage"] .pgrail__note{opacity:.78;font-weight:600}
/* the crate bands' Common / Uncommon / Rare kept their green / blue / pink as
   INK; on white the green fails — the bar carries the colour, the word is ink */
html[data-layout="stage"] .cband__tier{color:var(--key)}
/* 6.23d signed in (the hidden-tab scan): the profile's balance figures and the
   USDC mark were white on the white card; the rank bar's amount stays white
   with its stroke */
html[data-layout="stage"] .mbal,
html[data-layout="stage"] .mbal__v,
html[data-layout="stage"] .mbal svg{color:var(--key)}
html[data-layout="stage"] .mrank .rank__val,
html[data-layout="stage"] .mrank .rank__val b{color:#FFF}
/* 6.23e the store's notes and blurbs sit straight on the sky (no card), so
   the faint ink steps up to read there (4.6:1 on the sky); the crate sheet's
   Try again is a sticker, not pink type on blue */
html[data-layout="stage"] .ov__panel,
html[data-layout="stage"] .vault__panel,
html[data-layout="stage"] .store__panel,
html[data-layout="stage"] .grail__panel,
html[data-layout="stage"] .codds__panel{--text-faint:#3A4354;--text-dim:#1F2633;--ink-faint:#3A4354;--ink-dim:#1F2633}
html[data-layout="stage"] .codds__retry{
  display:inline-block;margin-left:8px;padding:6px 12px;border-radius:10px;font:900 15px var(--display);font-style:italic;text-transform:uppercase;
  color:var(--key);background:#FFF;border:2px solid var(--key);box-shadow:0 3px 0 var(--key);cursor:pointer}
/* 6.23f LUKE'S DESKTOP LOOK AT THE NEW SHEETS (7 Sep 00:45, four screenshots):
   - the tab strips inherited their modules' bleed (margin 0 -32px) and lost
     their padding, so HIGH VALUE / LATEST and REDEEM / HISTORY touched the
     walls: the strips sit on the content's own edges now, still one tab at
     each end (his 6 Sep 02:30 ask for the store; the crate sheet's two sides);
   - the odds' franchise tabs wrapped with space-between, so Soccer and Sports
     sat at opposite ends: left-aligned, one gap, like a row of stickers;
   - "0 cards · $0 total value" was 13px; the odds rows had no padding so
     Common touched the card's edge; the fact cards' hard drop ate the grid gap;
   - "everything should fade behind [the header] ... everything needs to drop
     down before we put the fade in": the .ov bodies (odds, how it works, live
     pull, profile) get the Pokédex page's treatment — the content rests 64px
     down and one long eased ramp fades it out as it climbs. */
html[data-layout="stage"] .store__tabs,
html[data-layout="stage"] .vault__sort,
html[data-layout="stage"] .codds__tabs{margin-left:0;margin-right:0;padding:6px 2px 14px}
html[data-layout="stage"] .otabs{justify-content:flex-start;flex-wrap:wrap;gap:10px 12px;padding:6px 2px 10px}
html[data-layout="stage"] .vault__sub{font-size:18px;font-weight:600}
html[data-layout="stage"] .orow{padding:7px 14px 7px 12px}
html[data-layout="stage"] .facts{gap:18px 14px;margin-top:6px}
html[data-layout="stage"] .steps{gap:16px}
@media (min-width:1024px){
  html[data-layout="stage"] .ov__body{
    padding-top:64px;
    -webkit-mask-image:linear-gradient(180deg,transparent 0,rgba(0,0,0,.06) 20px,rgba(0,0,0,.3) 38px,rgba(0,0,0,.72) 52px,#000 64px,#000 calc(100% - 40px),transparent 100%);
    mask-image:linear-gradient(180deg,transparent 0,rgba(0,0,0,.06) 20px,rgba(0,0,0,.3) 38px,rgba(0,0,0,.72) 52px,#000 64px,#000 calc(100% - 40px),transparent 100%)}
  html[data-layout="stage"] .ov--odds .ov__body{
    -webkit-mask-image:linear-gradient(180deg,transparent 0,rgba(0,0,0,.06) 20px,rgba(0,0,0,.3) 38px,rgba(0,0,0,.72) 52px,#000 64px,#000 calc(100% - 40px),transparent 100%);
    mask-image:linear-gradient(180deg,transparent 0,rgba(0,0,0,.06) 20px,rgba(0,0,0,.3) 38px,rgba(0,0,0,.72) 52px,#000 64px,#000 calc(100% - 40px),transparent 100%)}
  /* the head's swoosh and the odds' tab row sit right on the body's edge, so
     the body starts flush under them and the ramp does the separating */
  html[data-layout="stage"] .ov__head{padding-bottom:0}

  /* ...BUT NOT WHEN THE SHEET HAS ITS OWN HEADER (Luke, 11 Sep, off the card
     picker: "this pop up has loads of space below the header. looks daft. we
     need to be careful of this across the site").
     The 64px above is for the sheets whose BODY is the top of the content — it
     holds them clear of the site header, which is what Luke asked for on 7 Sep
     ("everything should fade behind it ... everything needs to drop down"). A
     sheet with an .ov__head has already done that clearing with the head, so
     the 64px lands between the TITLE and the content instead. Measured, header
     bottom edge to the first ink in the body, at 1512x982 before this:
       Choose a card 72px · How It Works 66px   (55 / 50 at 1147x655)
     18px, not 0: the head's own padding-bottom is 0 (the rule above), so at zero
     the first row butts straight onto the title — measured, How It Works came
     out flush. 18 is a deliberate gap rather than an inherited one.
     NOT THE ODDS SHEET, whose 156px I first blamed on this and then measured
     properly: 10px of tab margin + a 105px two-row TAB STRIP + its own 36px.
     The strip is content. Only its 36px is this rule, and it becomes 18 too.
     The ramp goes with the padding for the same reason — with nothing to fade
     under, it was dissolving the first row at rest. The bottom fade every sheet
     wears (§6.9) stays. */
  html[data-layout="stage"] .ov__head ~ .ov__body{
    padding-top:18px;
    -webkit-mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 40px),transparent 100%);
    mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 40px),transparent 100%)}
}
/* 6.23g LUKE, 7 Sep 00:50 (Crate & Rank screenshot): "the whole thing kind
   of moves left to right ... the dots are all in line with each other
   because of the header ... maybe the header should just be transparent
   where the odds and the crate thing sit. This probably should be the same
   on every page"; "same issue with the crate odds and the rank ladder being
   on the left and right sides"; "the X button sometimes has a purple ring
   around it ... that ring should never happen on any of the X buttons".
   So, on every sheet: the tab strips are plain rows of stickers, left-aligned
   with one gap (no more two sides); they scroll with the content instead of
   sticking with their own halftone over it (that was the shifting dots); the
   scroll boxes keep a stable gutter so a scrollbar never shifts the content,
   wear the pink thumb, and get the drop-then-fade under the header; and no
   close button draws a focus ring, pointer or keyboard. */
html[data-layout="stage"] .store__tabs,
html[data-layout="stage"] .vault__sort,
html[data-layout="stage"] .codds__tabs,
html[data-layout="stage"] .otabs{
  position:static;justify-content:flex-start;flex-wrap:wrap;gap:10px 12px;
  background:none;padding:4px 2px 16px;margin-bottom:0}
html[data-layout="stage"] .store__tabs::after,
html[data-layout="stage"] .vault__sort::after,
html[data-layout="stage"] .codds__tabs::after{display:none}
html[data-layout="stage"] .store__scroll,
html[data-layout="stage"] .vault__body,
html[data-layout="stage"] .codds__scroll,
html[data-layout="stage"] .grail__scroll,
html[data-layout="stage"] .ov__body{scrollbar-gutter:stable;scrollbar-width:thin;scrollbar-color:var(--acc) transparent}
html[data-layout="stage"] .store__scroll::-webkit-scrollbar,
html[data-layout="stage"] .vault__body::-webkit-scrollbar,
html[data-layout="stage"] .codds__scroll::-webkit-scrollbar,
html[data-layout="stage"] .grail__scroll::-webkit-scrollbar,
html[data-layout="stage"] .ov__body::-webkit-scrollbar{width:12px;height:12px}
html[data-layout="stage"] .store__scroll::-webkit-scrollbar-track,
html[data-layout="stage"] .vault__body::-webkit-scrollbar-track,
html[data-layout="stage"] .codds__scroll::-webkit-scrollbar-track,
html[data-layout="stage"] .grail__scroll::-webkit-scrollbar-track,
html[data-layout="stage"] .ov__body::-webkit-scrollbar-track{background:transparent}
html[data-layout="stage"] .store__scroll::-webkit-scrollbar-thumb,
html[data-layout="stage"] .vault__body::-webkit-scrollbar-thumb,
html[data-layout="stage"] .codds__scroll::-webkit-scrollbar-thumb,
html[data-layout="stage"] .grail__scroll::-webkit-scrollbar-thumb,
html[data-layout="stage"] .ov__body::-webkit-scrollbar-thumb{
  background:linear-gradient(180deg,var(--acc-hi),var(--acc) 48%,var(--acc-lo));border:2px solid var(--key);border-radius:8px;
  box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.55)}
@media (min-width:1024px){
  html[data-layout="stage"] .store__scroll,
  html[data-layout="stage"] .vault__body,
  html[data-layout="stage"] .codds__scroll,
  html[data-layout="stage"] .grail__scroll{
    padding-top:48px;
    -webkit-mask-image:linear-gradient(180deg,transparent 0,rgba(0,0,0,.06) 14px,rgba(0,0,0,.3) 28px,rgba(0,0,0,.72) 40px,#000 48px,#000 calc(100% - 40px),transparent 100%);
    mask-image:linear-gradient(180deg,transparent 0,rgba(0,0,0,.06) 14px,rgba(0,0,0,.3) 28px,rgba(0,0,0,.72) 40px,#000 48px,#000 calc(100% - 40px),transparent 100%)}
  /* the head's sub line sits right on the scroll box's edge; the ramp separates */
  html[data-layout="stage"] .store__sub,
  html[data-layout="stage"] .codds__sub,
  html[data-layout="stage"] .vault__sub{margin-bottom:0}

  /* THE VAULT TAKES THE 14px FEATHER, NOT THE 48px RAMP (Luke, 11 Sep 01:55:
     "plenty of space between the high-value, low-value, latest, and the text
     that's above it ... when there's only one row of cards the thing's not
     actually scrollable").
     MEASURED, because the obvious suspect is innocent: the gap is NOT
     .vault__tabs' padding — `.vault__tabs` and `.vault__count` belong to
     app.js's MOCK vault (#vaultOv), which is a closed overlay nobody sees
     while site/vault.js is loaded (app.js:613 hands over to window.VAULT).
     Tuning those two, as HANDOFF-180 §3.1 reports doing, moved nothing.
     The real band is this block's own 48px runway: sub bottom 291.9 ->
     sort row top 341.3 at 1512x982, all of it .vault__body's padding-top.
     The runway exists so the sort row (position:static at stage, :2529) can
     fade as it climbs — so it cannot just be deleted, or the row would sit
     inside the ramp and paint at 6% alpha. It is re-cut to the feather Luke
     already picked for the pages on 7 Sep ("option C", page.css:197): the
     same shape over 14px instead of 48. Reclaims 34px above the cards and
     leaves the bottom fade alone. The store, odds and grail sheets keep the
     48px — he did not complain about those, and the odds sheet's band is
     tuned (HANDOFF-180 §6). */
  html[data-layout="stage"] .vault__body{
    padding-top:14px;
    -webkit-mask-image:linear-gradient(180deg,transparent 0,rgba(0,0,0,.5) 7px,#000 14px,#000 calc(100% - 40px),transparent 100%);
    mask-image:linear-gradient(180deg,transparent 0,rgba(0,0,0,.5) 7px,#000 14px,#000 calc(100% - 40px),transparent 100%)}
}
/* no focus ring on any close button, ever (Luke): the border is the button */
html[data-layout="stage"] .ov__x:focus,
html[data-layout="stage"] .ov__x:focus-visible,
html[data-layout="stage"] .vault__close:focus,
html[data-layout="stage"] .vault__close:focus-visible,
html[data-layout="stage"] .store__close:focus,
html[data-layout="stage"] .store__close:focus-visible,
html[data-layout="stage"] .codds__close:focus,
html[data-layout="stage"] .codds__close:focus-visible,
html[data-layout="stage"] .grail__back:focus,
html[data-layout="stage"] .grail__back:focus-visible,
html[data-layout="stage"] .dexmodal__close:focus-visible{outline:0 !important;box-shadow:0 3px 0 var(--key)}

/* 6.23h Luke, 7 Sep 01:05 (four more screenshots): the odds sheet had "a
   massive gap" between the tabs and the first pack (the body's 64px drop on
   top of the tab row's own padding and the block's); the How It Works gaps
   were not equal (steps 16, steps-to-facts 26, fact rows 18, all minus the
   4px drop). One rhythm: 20px everywhere, 16 visible. */
@media (min-width:1024px){
  html[data-layout="stage"] .ov--odds .ov__body{
    padding-top:36px;
    -webkit-mask-image:linear-gradient(180deg,transparent 0,rgba(0,0,0,.3) 16px,#000 36px,#000 calc(100% - 40px),transparent 100%);
    mask-image:linear-gradient(180deg,transparent 0,rgba(0,0,0,.3) 16px,#000 36px,#000 calc(100% - 40px),transparent 100%)}
  html[data-layout="stage"] .ov--odds .otabs{padding-bottom:4px}
  html[data-layout="stage"] .oblk:first-child{padding-top:0}
}
html[data-layout="stage"] .steps{gap:20px;margin-bottom:20px}
html[data-layout="stage"] .facts{gap:20px;margin-top:0}
/* Luke, 7 Sep 01:19 (screenshot, "said this 4 times now"): the bottom row
   (Graded / Published odds / No KYC) touched the row above (Tiers / Turbo).
   They are TWO grids — stage.js §6.1 inserts its .facts.sl-howto after the
   steps and app.js's .facts follows it with margin-top:0 — so the grid gap
   never applied between them. The second grid keeps the same 20px air. */
html[data-layout="stage"] .facts + .facts{margin-top:20px}

/* ═══════════════════════════════════════════════════════════════════════
   6.24 THE STORE, REDESIGNED (Luke, 7 Sep 01:05: "the store looks shit ...
   needs a complete redesign. The fonts are dead tiny on there and on the
   mobile version as well. These pills look shit. When you tap the pills,
   everything disappears and then flashes back"). store.js keeps its data
   and its markup (the morph fixes the flash); this is the look: a wider
   sheet on the desktop, type a size up across the board, the four credit
   denominations as big stickers in a row (pink when picked), one big REDEEM
   sticker, the gear shelf as white cards with sticker sizes, every note in
   ink at 16px or more. Same rhythm as the rest of the sheets: 20px between
   blocks, 2.5px key outline, 0 4px key drop. */
html[data-layout="stage"] .store__panel{--fs-xs:16px;--fs-sm:17px;--fs-md:19px;--fs-lg:22px;--fs-xl:30px}
@media (min-width:1024px){
  html[data-layout="stage"] .store__panel{width:min(820px,92vw)}
}
html[data-layout="stage"] .store__sub{font-size:17px}
/* the two balances */
html[data-layout="stage"] .sbal{padding:14px 18px}
html[data-layout="stage"] .sbal__k{font-size:14px;letter-spacing:.1em;color:#3A4354}
html[data-layout="stage"] .sbal__v{font:900 32px/1.1 var(--display);font-style:italic;color:var(--key);margin-top:4px}
html[data-layout="stage"] .sbal--lead .sbal__v{color:var(--acc-lo)}
/* the aisle head */
html[data-layout="stage"] .saisle{padding-top:20px;margin-top:20px;border-top:2px solid rgba(11,15,24,.18)}
html[data-layout="stage"] .saisle:first-child{padding-top:0;margin-top:0;border-top:0}
html[data-layout="stage"] .saisle__head{align-items:center;margin-bottom:4px}
html[data-layout="stage"] .saisle__name{font:900 26px/1 var(--display);font-style:italic;letter-spacing:.01em;color:var(--key);transform:var(--slant)}
html[data-layout="stage"] .saisle__count{font:800 14px/1 var(--display);letter-spacing:.08em;color:var(--key);background:#FFF;border:2px solid var(--key);border-radius:999px;padding:5px 10px}
html[data-layout="stage"] .saisle__blurb{font-size:17px;color:#1F2633;margin-bottom:16px}
/* the credit card and its four denominations */
html[data-layout="stage"] .scard{gap:16px}
html[data-layout="stage"] .scard__art{border:2.5px solid var(--key);border-radius:18px;box-shadow:0 4px 0 var(--key);background:#0B0F18}
html[data-layout="stage"] .scard__side{gap:14px;align-items:stretch}
html[data-layout="stage"] .scard__blurb{font-size:17px;color:#1F2633}
html[data-layout="stage"] .scard__denoms{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}
html[data-layout="stage"] .sdn{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
  padding:12px 6px 10px;border-radius:14px;
  background:rgba(255,255,255,.92);border:2.5px solid var(--key);box-shadow:0 4px 0 var(--key);
  transition:transform .12s,background .15s}
html[data-layout="stage"] .sdn b{font:900 26px/1 var(--display);font-style:italic;color:var(--key);transform:var(--slant)}
html[data-layout="stage"] .sdn s{font:700 15px/1.2 var(--ui);color:#3A4354;text-decoration:none}
html[data-layout="stage"] .sdn s::after{content:" pts"}
html[data-layout="stage"] .sdn:hover:not(:disabled){border-color:var(--key);background:var(--yellow)}
html[data-layout="stage"] .sdn:active:not(:disabled){transform:translateY(2px);box-shadow:0 2px 0 var(--key)}
html[data-layout="stage"] .sdn--on,
html[data-layout="stage"] .sdn--on:hover:not(:disabled){
  background:linear-gradient(180deg,var(--acc-hi) 0%,var(--acc) 48%,var(--acc-lo) 100%);
  box-shadow:0 0 0 2px #FFF,0 4px 0 2px var(--key)}
html[data-layout="stage"] .sdn--on b,
html[data-layout="stage"] .sdn--on s{color:#FFF}
html[data-layout="stage"] .sdn:disabled{opacity:.6}
/* one big REDEEM, or its honest off state */
html[data-layout="stage"] .scard__cta,
html[data-layout="stage"] .sitem__go,
html[data-layout="stage"] .srung__go{
  display:inline-flex;align-items:center;justify-content:center;
  font:900 20px/1 var(--display);font-style:italic;text-transform:uppercase;letter-spacing:.03em;
  color:#FFF;background:linear-gradient(180deg,var(--acc-hi) 0%,var(--acc) 48%,var(--acc-lo) 100%);
  border:2.5px solid var(--key);border-radius:14px;padding:14px 26px;cursor:pointer;
  box-shadow:0 0 0 2px #FFF,0 4px 0 2px var(--key);transform:var(--slant)}
html[data-layout="stage"] .scard__cta{align-self:stretch;padding:16px 26px;font-size:24px}
html[data-layout="stage"] .scard__cta:hover:not(:disabled),
html[data-layout="stage"] .sitem__go:hover:not(:disabled),
html[data-layout="stage"] .srung__go:hover:not(:disabled){background:var(--acc-hi)}
html[data-layout="stage"] .scard__cta--off,
html[data-layout="stage"] .sitem__go--off{
  background:rgba(255,255,255,.7);color:#3A4354;box-shadow:0 4px 0 var(--key);cursor:default}
html[data-layout="stage"] .scard__under{font-size:17px;font-weight:700;color:var(--key);text-align:center}
html[data-layout="stage"] .scard__why{font-size:16px;color:#1F2633;text-align:center}
html[data-layout="stage"] .scard__ask{
  background:rgba(255,255,255,.92);border:2.5px solid var(--key);border-radius:16px;box-shadow:0 4px 0 var(--key);
  padding:14px 16px;margin-top:4px;border-top-width:2.5px}
html[data-layout="stage"] .scard__ask p{font-size:17px;color:var(--key)}
html[data-layout="stage"] .srung__no{
  font:900 17px/1 var(--display);font-style:italic;text-transform:uppercase;color:var(--key);
  background:#FFF;border:2px solid var(--key);border-radius:12px;padding:11px 16px;box-shadow:0 3px 0 var(--key);cursor:pointer}
/* the gear shelf */
html[data-layout="stage"] .scat{display:flex;flex-wrap:wrap;gap:10px 12px;margin:0 0 16px}
html[data-layout="stage"] .sct{
  display:inline-flex;align-items:center;gap:8px;padding:9px 14px;border-radius:12px;
  font:900 17px/1 var(--display);font-style:italic;text-transform:uppercase;letter-spacing:.03em;
  color:var(--key);background:rgba(255,255,255,.92);border:2px solid var(--key);box-shadow:0 3px 0 var(--key);cursor:pointer;transform:var(--slant)}
html[data-layout="stage"] .sct:hover{background:var(--yellow)}
html[data-layout="stage"] .sct--on{color:#FFF;background:linear-gradient(180deg,var(--acc-hi) 0%,var(--acc) 48%,var(--acc-lo) 100%);box-shadow:0 0 0 2px #FFF,0 3px 0 2px var(--key)}
html[data-layout="stage"] .sgear{gap:20px}
html[data-layout="stage"] .sitem{
  grid-template-columns:150px 1fr;gap:16px;
  background:rgba(255,255,255,.92);border:2.5px solid var(--key);border-radius:18px;box-shadow:0 4px 0 var(--key)}
html[data-layout="stage"] .sitem__shot{background:#FFF;border-right:2.5px solid var(--key)}
html[data-layout="stage"] .sitem__body{padding:14px 16px 14px 0;gap:8px}
html[data-layout="stage"] .sitem__nm{font:900 22px/1.1 var(--display);font-style:italic;color:var(--key)}
html[data-layout="stage"] .sitem__ds{font-size:16px;color:#1F2633}
html[data-layout="stage"] .sitem__px .t{font:900 20px/1 var(--display);color:var(--key)}
html[data-layout="stage"] .sitem__px .d{font-size:16px;color:#3A4354}
html[data-layout="stage"] .ssz{display:flex;flex-wrap:wrap;gap:8px}
html[data-layout="stage"] .ssz__b{
  min-width:44px;padding:8px 12px;border-radius:10px;font:900 16px/1 var(--display);color:var(--key);
  background:#FFF;border:2px solid var(--key);box-shadow:0 3px 0 var(--key);cursor:pointer}
html[data-layout="stage"] .ssz__b--on{color:#FFF;background:linear-gradient(180deg,var(--acc-hi) 0%,var(--acc) 48%,var(--acc-lo) 100%)}
html[data-layout="stage"] .ssz__b:disabled{opacity:.45;cursor:default}
html[data-layout="stage"] .sitem__one{font-size:16px;color:#3A4354}
html[data-layout="stage"] .sitem__bar{height:10px;border-radius:6px;background:rgba(11,15,24,.85);border:2px solid var(--key);box-sizing:border-box}
html[data-layout="stage"] .sitem__bar i{background:linear-gradient(90deg,var(--acc-lo),var(--acc-hi))}
html[data-layout="stage"] .sitem__pl{font-size:16px;color:#3A4354}
html[data-layout="stage"] .sitem__pl b{color:var(--key)}
html[data-layout="stage"] .sitem__why{font-size:16px;color:#1F2633}
html[data-layout="stage"] .sitem__go{padding:11px 18px;font-size:17px;margin-top:6px}
html[data-layout="stage"] .sgear__foot{font-size:16px;color:#1F2633;margin-top:16px}
html[data-layout="stage"] .storemsg,
html[data-layout="stage"] .storestate{font-size:17px;color:var(--key)}
html[data-layout="stage"] .store__note{font-size:16px;color:#1F2633}
/* 6.31 COMING SOON (Luke, 16 Sep: "we need to make whole store just say
   coming soon"): with the aisles gone the sheet holds one object, so that
   object has to be furniture this stage already owns. The headline is a
   STICKER — the yellow one, key outline, hard drop, the shell's slant —
   because store.js's own brand-face line is a 400-weight on a sky sheet next
   to a 900 italic title and reads as type that failed to load. YELLOW AND NOT
   THE PINK GRADIENT: pink is this panel's REDEEM, and a pink slab in the
   middle of an empty sheet is a button that does nothing when you press it. */
html[data-layout="stage"] .storesoon__h{
  display:inline-block;font:900 22px/1 var(--display);font-style:italic;
  text-transform:uppercase;letter-spacing:.03em;color:var(--key);background:var(--yellow);
  border:2.5px solid var(--key);border-radius:14px;padding:12px 22px;
  box-shadow:0 0 0 2px #FFF,0 4px 0 2px var(--key);transform:var(--slant)}
html[data-layout="stage"] .storesoon__p{font-size:17px;font-weight:600;color:var(--text-dim);max-width:32ch}
@media (max-width:1023px){
  html[data-layout="stage"] .scard__denoms{grid-template-columns:repeat(2,minmax(0,1fr))}
  html[data-layout="stage"] .sitem{grid-template-columns:120px 1fr}
  /* Luke, 7 Sep 01:50: "shop looks awful on mobile, the sizes are overlapping".
     store.js's own <=420px rule gives .sitem__shot aspect-ratio:1/1 for its
     one-column layout; inside THIS two-column grid the shot is stretched to
     the row's height, so 1/1 made it as WIDE as the row is tall (365px) and
     the picture lay across the name, the sizes and the bar (measured at 390
     with the real payload). The shot keeps its 120px column. */
  html[data-layout="stage"] .sitem__shot{aspect-ratio:auto}
  html[data-layout="stage"] .saisle__name{font-size:24px}
}
/* 6.24b the last two 14px labels in the store (the balance keys, the aisle
   count chips) join the 16px floor */
html[data-layout="stage"] .sbal__k{font-size:16px}
html[data-layout="stage"] .saisle__count{font-size:16px;padding:6px 12px}

/* 6.25 THE COLLECTION BUTTONS, BIGGER (Luke, 7 Sep 00:19, desktop: the icons and
   names "still all too small on my screen ... needs to get made bigger").
   First pass put each one in a white sticker card; Luke 00:40: "noooo dont put
   the icons in that box ... they just needed to be made bigger". So: no box.
   Above 760px tall the icon goes 54 -> 66px and the name 25 -> 29px, the ring
   row grows to hold it, and stage.js's side scale is .86 (was .74) so the two
   unselected ones read at nearly the centre's size.
   Second step (Luke, 7 Sep, screenshot of the row: "these are not big enough"):
   66 -> 84px icons, 29 -> 36px names, the row 124 -> 152, and stage.js's
   desktop stride cap 168 -> 220 so the three names keep clear air. */
@media (min-width:1024px) and (min-height:761px){
  html[data-layout="stage"] .sl-cats{height:152px}   /* 124 held 66/29; 152 holds 84/36 with the centre's 15px dip */
  html[data-layout="stage"] .sl-cat img,
  html[data-layout="stage"] .sl-cat svg{width:84px;height:84px}   /* 66 was "not big enough" (Luke, 7 Sep, screenshot) */
  html[data-layout="stage"] .sl-cat b{font-size:36px}
}

/* 6.26 THE COLLECTION BUTTONS ON A SHORT SCREEN (Luke, 7 Sep 05:49 / 07:15 /
   08:02). His window is ~1147x655 (shell zoom .78), under §6.25's 761px, so he
   was getting §6.14's 32/19 (visual 25/15). The first answer grew the row to
   130px on every desktop; layout() sizes the hero from the rail's height, so
   the pack shrank (183 -> 178 at his window) — 07:15 "they've shrunk the size
   of the pack way too much"; 08:02 "the size of the packs here [prod] is
   agreed ... make the buttons as big as we can without making these packs go
   any smaller ... It doesn't mean making anything else smaller."
   MEASURED (scratchpad percat/cand, 7 Sep): with the row at §6.14's 88px the
   hero on this tree equals prod for every collection (--pw 143.19 / 143.19 /
   151.19 at 1147x655). One Piece is the tight case — three packs, height-
   bound, its hero top only 27px (visual) under the row — so the row STAYS at
   88 and the buttons grow into the space that is free: the row's own air
   (top 6 -> 2, gap 5 -> 4, the name's line box 1.2 -> 1) and the gap under
   it (the row no longer clips downward). 54px icons / 25px names land the
   centred name 15px above One Piece's hero at 1147x655, 14 at 1280x700,
   9.5 at 1470x690, 8 at 1540x572 (prod: 26-33; 56/26 left 6, 60/28 left 3).
   Hero unchanged at every one. Tall screens keep §6.25's 84/36/152. */
@media (min-width:1024px) and (max-height:760px){
  html[data-layout="stage"] .sl-cats{height:88px;overflow-x:clip;overflow-y:visible}   /* §6.14's 88: the rail keeps every pixel it has on prod */
  html[data-layout="stage"] .sl-cat{top:2px;gap:4px}
  html[data-layout="stage"] .sl-cat img,
  html[data-layout="stage"] .sl-cat svg{width:54px;height:54px}
  html[data-layout="stage"] .sl-cat b{font-size:25px;line-height:1}
}

/* 6.27 NO RING ON THE ADD FUNDS / WITHDRAW / REDEEM X EITHER (Luke, 7 Sep
   05:52, screenshot of Add Funds: "it's got two outlines around the X
   button"). §6.23g took the ring off every .ov__x and the module panels'
   close buttons, but addfunds.js / withdraw.js / redeem.js draw their own X
   (.addf__x, .wd__x, .rdm__x) and focus it on open, so the browser's focus
   ring sat outside the sticker's own key border. Never on any X. */
html[data-layout="stage"] .addf__x:focus,
html[data-layout="stage"] .wd__x:focus,
html[data-layout="stage"] .rdm__x:focus{outline:none;box-shadow:none}

/* 6.28 THE PINK DRAWN LINE IS GONE, EVERYWHERE (Luke, 7 Sep 06:10 — PLAN 79/80/82:
   "On the How It Works page get rid of that pink drawn line. Anywhere on the
   whole site that uses that pink drawn line needs to be gone"; My Profile:
   "a line underneath all of the titles that looks like it's drawn. Remove
   it"; Live Pull: "remove that pink line from the header"). The line is one
   traced SVG (.swoosh) that app.js / rip-flow.js / shell-rip.js / mobile.js
   inject under every sheet title (Vault, What's Inside, How It Works, Live
   Pull, The Shop, My Profile, Choose a Pack, the profile's RANK / DAILY CRATE
   / REWARDS / SETTINGS) and under YOUR PULL on the post-rip panel. One rule
   hides all of them; the two places that reserved room for it give it back. */
html[data-layout="stage"] .swoosh{display:none}
html[data-layout="stage"] .mprof__sec{padding-bottom:0}              /* the 12px under RANK etc. held the absolute swoosh (§6.9) */
html[data-layout="stage"] .postrip__head{margin-bottom:10px}         /* the swoosh's own 6px bottom margin (rips-glue) plus its 9px box */

/* 6.29 LIVE PULL'S CARD FACTS ON THE POST-RIP PANEL'S PATTERN (Luke, 7 Sep
   06:10, PLAN 81: "the information about the card where it says grade,
   rarity, insured value looks rubbish, and we should copy what's used after
   you rip a pack on the right-hand side, it's more prominent on that screen
   and easier to read"). The post-rip's facts are: a rarity sticker under the
   name (.postrip__grade, rarity-coloured, key-lined), then ruled rows with a
   small tracked key-ink label on the left and the value on the right, the
   money row in the display face — white with the key stroke, slanted. app.js
   openHit() now writes exactly that markup (the rarity as the same sticker,
   .hitov__chip); these rules give the rows the post-rip's dress, both shells. */
html[data-layout="stage"] .hitov__chip{margin:0 auto;display:block;width:max-content;
  border-width:3px;color:var(--key);box-shadow:0 0 0 3px #FFF,0 4px 0 0 rgba(11,15,24,.28)}
html[data-layout="stage"] .hitov__chip[data-rarity="common"]{background:#00E603}
html[data-layout="stage"] .hitov__chip[data-rarity="rare"]{background:#1E92FF}
html[data-layout="stage"] .hitov__chip[data-rarity="epic"]{background:#DD2BFF}
html[data-layout="stage"] .hitov__chip[data-rarity="legendary"]{background:#FFCE1B}
html[data-layout="stage"] .hitov__rows{display:block;gap:0;margin-top:2px}
html[data-layout="stage"] .hitov__r{display:flex;align-items:baseline;justify-content:space-between;gap:14px;
  padding:9px 0;border-bottom:1px solid rgba(11,15,24,.30)}
html[data-layout="stage"] .hitov__r:last-child{border-bottom:0}
html[data-layout="stage"] .hitov__r dt{font-family:var(--ui);font-size:13px;font-weight:800;letter-spacing:.19em;
  text-transform:uppercase;color:var(--key);opacity:1;white-space:nowrap}
html[data-layout="stage"] .hitov__r dd{font-family:var(--ui);font-size:18px;font-weight:800;color:var(--key);
  text-align:right;min-width:0;overflow-wrap:anywhere}
html[data-layout="stage"] .hitov__r--big dt{font-size:13px}
html[data-layout="stage"] .hitov__r--big dd{font-family:var(--display);font-style:italic;font-weight:900;font-size:30px;
  line-height:1;transform:var(--slant);transform-origin:right center;display:inline-block;
  color:#FFF;-webkit-text-stroke:5px var(--key);paint-order:stroke fill}

/* 6.30 THE VAULT'S TOTAL LINE (Luke, 7 Sep 06:10, PLAN 78, both shells: "where
   it says one card, $13 total value, that should be bigger and more visible").
   18px dim -> 26px key ink, the count and the money in the display face. */
html[data-layout="stage"] .vault__sub{font-size:26px;font-weight:800;color:var(--key);line-height:1.15}
html[data-layout="stage"] .vault__sub b{font-family:var(--display);font-style:italic;font-weight:900;font-size:30px;
  color:#FFF;-webkit-text-stroke:4px var(--key);paint-order:stroke fill;padding:0 2px}

/* 6.31 EVERY SCROLLBAR ON BRAND (Luke, 7 Sep 09:50: the account menu scrolls
   once Refer a friend drops the code in and "the scrollbar isn't on brand.
   Anytime there's a scrollbar anywhere, it needs to be on brand"). The
   Pokédex page already had the brand thumb (§6.19: a pink sticker in a key
   ring on no track); this is that thumb on every scroll container the site
   has — the account menu, every .ov sheet body, the vault, the store, What's
   Inside, Redeem, the crate odds, Add Funds, Withdraw, the phone's Choose a
   Pack list, the profile sheet. Two mechanics, measured in Chrome 152:
   1. Chrome ignores ::-webkit-scrollbar on an element whose scrollbar-color /
      scrollbar-width is not `auto` — store.js, grailpool.js, redeem.js and
      style.css set those (thin, white .18) — so they are reset to auto here
      at a specificity nothing else reaches (:is() with the id). Firefox has
      no ::-webkit-scrollbar and gets the standard pair under @supports.
   2. The containers that HIDE their bar on purpose (.rail, .packlist,
      .otabs, .postrip__info, the strip) keep their width:0 rules — they are
      not in the list. */
html[data-layout="stage"] :is(#acctMenu,.ov__body,.vault__body,.store__scroll,.grail__scroll,.rdm__body,
  .codds__scroll,.addf__panel,.wd__panel,.msheet__list,.mprof__body,.dexmodal__body){
  scrollbar-width:auto;scrollbar-color:auto}
html[data-layout="stage"] :is(#acctMenu,.ov__body,.vault__body,.store__scroll,.grail__scroll,.rdm__body,
  .codds__scroll,.addf__panel,.wd__panel,.msheet__list,.mprof__body,.dexmodal__body)::-webkit-scrollbar{width:12px;height:12px}
html[data-layout="stage"] :is(#acctMenu,.ov__body,.vault__body,.store__scroll,.grail__scroll,.rdm__body,
  .codds__scroll,.addf__panel,.wd__panel,.msheet__list,.mprof__body,.dexmodal__body)::-webkit-scrollbar-track{background:transparent}
html[data-layout="stage"] :is(#acctMenu,.ov__body,.vault__body,.store__scroll,.grail__scroll,.rdm__body,
  .codds__scroll,.addf__panel,.wd__panel,.msheet__list,.mprof__body,.dexmodal__body)::-webkit-scrollbar-thumb{
  background:linear-gradient(180deg,var(--acc-hi),var(--acc) 48%,var(--acc-lo));border:2px solid var(--key);border-radius:8px;
  box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.55);background-clip:border-box}
html[data-layout="stage"] :is(#acctMenu,.ov__body,.vault__body,.store__scroll,.grail__scroll,.rdm__body,
  .codds__scroll,.addf__panel,.wd__panel,.msheet__list,.mprof__body,.dexmodal__body)::-webkit-scrollbar-thumb:hover{background:var(--acc-hi)}
html[data-layout="stage"] :is(#acctMenu,.ov__body,.vault__body,.store__scroll,.grail__scroll,.rdm__body,
  .codds__scroll,.addf__panel,.wd__panel,.msheet__list,.mprof__body,.dexmodal__body)::-webkit-scrollbar-corner{background:transparent}
@supports not selector(::-webkit-scrollbar){
  html[data-layout="stage"] :is(#acctMenu,.ov__body,.vault__body,.store__scroll,.grail__scroll,.rdm__body,
    .codds__scroll,.addf__panel,.wd__panel,.msheet__list,.mprof__body,.dexmodal__body){
    scrollbar-width:thin;scrollbar-color:var(--acc) transparent}
}

/* 6.32 THE QUIET REPEAT NOTE (site/pokedex.js dupNote; PLAN 59 second pass,
   the mock Luke approved 7 Sep 11:20). One white sticker in the key line,
   the name in pink. Desktop: fixed above the slab, centred in the 14.3vh
   band the slab vacates when it settles (rips-glue: the slab's top lands at
   11vh + --postrip-settle), fades in and rises 6px when the slide has
   landed, gone after 6s. Phone: in the post-rip panel's flow between the
   sellback rows and VIEW CARD, one line, centred, and it stays. */
html[data-layout="stage"] .sl-dupnote{
  font-family:var(--ui);font-size:15px;font-weight:800;color:var(--key);line-height:1.25;text-align:center;white-space:nowrap;
  background:#FFF;border:3px solid var(--key);border-radius:12px;padding:9px 16px;
  box-shadow:0 4px 0 0 rgba(11,15,24,.28);
  opacity:0;transform:translateY(6px);transition:opacity .32s ease,transform .32s cubic-bezier(.2,.7,.3,1);pointer-events:none}
html[data-layout="stage"] .sl-dupnote b{color:var(--acc);font-weight:900}
html[data-layout="stage"] .sl-dupnote[data-on="1"]{opacity:1;transform:none}
@media (min-width:1024px){
  html[data-layout="stage"] .sl-dupnote{
    position:fixed;left:50%;z-index:37;
    top:calc(11vh + var(--postrip-settle,14.3vh) / 2);
    transform:translate(-50%,calc(-50% + 6px))}
  html[data-layout="stage"] .sl-dupnote[data-on="1"]{transform:translate(-50%,-50%)}
}
@media (max-width:1023px){
  html[data-layout="stage"] .sl-dupnote{display:block;width:max-content;max-width:100%;margin:12px auto 4px;font-size:14px;padding:8px 14px}
}
@media (prefers-reduced-motion:reduce){ html[data-layout="stage"] .sl-dupnote{transition:opacity .2s} }

/* 6.33 THE VAULT CARD'S INKS (Luke, 7 Sep 13:05, Ryan's vault on prod: "the
   text is white on that image ... can't read it"). vault.js paints
   .pvcard__name with --text and the grade/value/state lines with the navy
   theme's dim inks; on the stage the card is the frosted-white sticker
   (§6.23) and --text is white. Key ink on all of them; the grade as a small
   key-lined chip; the state line ("Held at supplier" etc.) in the dim ink. */
html[data-layout="stage"] .pvcard__name{color:var(--key);font-weight:800}
html[data-layout="stage"] .pvcard__grade{color:var(--key);border:2px solid var(--key);border-radius:8px;padding:2px 8px;font-weight:800;background:#FFF}
html[data-layout="stage"] .pvcard__val{color:var(--acc-lo);font-weight:900}
html[data-layout="stage"] .pvcard__closed{color:#3A4354;font-weight:700}

/* 6.34 THE TOAST IS A STICKER (Luke, 7 Sep 14:05: sold a card on prod, "didn't
   get a notification to say it was sold, got the money like"). wallet.js's
   #walletToast is the old shell's dark 13px pill, written inline, 3.6s over
   the packs' feet — it showed (measured: the exact SELL NOW order paints it
   at opacity 1 once the state is browse) and he still missed it. So on the
   stage it wears the site's sticker: white, the key line, the hard drop, 17px
   ink, 5.2s (wallet.js). Inline styles, hence !important.
   THE 176 IS NOW --toast-bot (style.css, Luke 11 Sep: the toast "comes up too
   high ... it should only be that high when you are on the open page"). THIS
   RULE IS THE ONE THAT DECIDES IT — the !important here beats the inline
   `bottom` both wallet.js and notify.js write, so changing those alone moved
   nothing (measured: 170.016px on a page either way, which is 176 * --sl-s).
   Anyone tuning the toast's height edits this line, not the two JS files. */
html[data-layout="stage"] #walletToast{
  background:#FFF!important;border:3px solid var(--key)!important;border-radius:14px!important;
  color:var(--key)!important;font:800 17px/1.3 var(--ui)!important;padding:14px 22px!important;
  box-shadow:0 5px 0 0 rgba(11,15,24,.32)!important;max-width:min(560px,90vw)!important;
  /* THE HIGHEST OF THREE (14 Sep 2026): its own height for the state, the clip bar's
     measured top (--clip-clear, record.js) and whatever button is in its columns
     (--toast-clear, notify.js place()). Luke's rule for it, said once: the foot of the
     page, "unless there is something below it (like the share clip on x thing)". */
  bottom:max(calc(var(--toast-bot, 176px) * var(--sl-s,1)), var(--clip-clear, 0px), var(--toast-clear, 0px))!important}
@media (max-width:1023px){ html[data-layout="stage"] #walletToast{font-size:16px!important;padding:12px 18px!important} }
/* 6.34a THE TOAST STANDS AT THE FOOT ON THE POST-RIP PANEL (Luke, 14 Sep 2026: "that
   notification should be central under the card ... but at the bottom of the page, unless
   there is something below it"). 176 is the height that clears the LOBBY's RIP PACK cluster
   — on the panel the foot of the stage is empty (the controls slide off; record.js tuck),
   so the toast had no reason to stand that high, and standing there is what made it fight
   RIP AGAIN at 1147x655 (record.css §36, now retired). --toast-bot is the documented seam
   for this, read by §6.34 above, so the number is all that changes. */
html[data-layout="stage"][data-postrip="1"]{--toast-bot:24px}

/* 6.34b WHAT LIFTS IT. --clip-clear is the clip bar's measured top (record.js tuck, dropped
   again when the bar goes) and --toast-clear is the lift past a button in the toast's own
   columns (notify.js place): on the panel at 1147x655 the foot position clipped RIP AGAIN
   by 35x10px, the same collision record.css §36 once solved with a column. Both are raw px
   — measured off real boxes — so neither is scaled by --sl-s the way the height above is,
   and §6.34's max() takes whichever is highest. */


/* 6.35 THE CRATE CORNER AFTER A CLAIM (Luke, 7 Sep 13:45: "Next crate" on its
   own "doesn't make any sense and looks shit"; 15:05: "I never want the crate
   dimmed. But what about just a timer ... when it's ready to open change it
   to red and make it shake like something is inside it trying to get out").
   The countdown digits come back on short screens (§6.15's hide is lifted:
   the label pointed at nothing), READY is red type over the key stroke, and
   the crate rattles — a burst every 2.8s on the PICTURE, not the button (the
   button's hover lift is a transform of its own), pivoting near its base so
   it reads as something knocking inside. Gold glow: rips-glue.css:58. */
html[data-layout="stage"] .sl-crate .cratebtn[data-state="ready"]~.daily .daily__timer{color:var(--danger)}
@keyframes sl-crate-rattle{
  0%,34%,100%{transform:none}
  3%{transform:translate(-2px,-1px) rotate(-3.5deg)}
  6%{transform:translate(2px,0) rotate(3.5deg)}
  9%{transform:translate(-2px,1px) rotate(-3deg)}
  12%{transform:translate(2px,-1px) rotate(3deg)}
  15%{transform:translate(-2px,0) rotate(-2.5deg)}
  18%{transform:translate(1px,-3px) rotate(2deg) scale(1.025)}
  21%{transform:translate(-1px,0) rotate(-1.5deg)}
  24%{transform:translate(1px,-1px) rotate(1deg)}
  27%{transform:translate(0,0) rotate(-.5deg)}
  30%{transform:translate(0,-1px)}
}
html[data-layout="stage"] .sl-crate .cratebtn[data-state="ready"] .crate{animation:sl-crate-rattle 2.8s ease-in-out infinite;transform-origin:50% 88%}
@media (prefers-reduced-motion:reduce){ html[data-layout="stage"] .sl-crate .cratebtn[data-state="ready"] .crate{animation:none} }

/* ── 6.10b THE DRAG DRAWS CHEAP (Luke, 9 Sep 22:51: the sheets "don't come
   down smoothly"). While the finger owns a sheet its body's mask comes off and
   the panel is promoted — a masked scrolling body inside a 14px-shadowed panel
   was re-rasterised every frame of the drag on the phone. overscroll-behavior
   keeps the body's own scroll from chaining into the page underneath. */
@media (max-width:1023px){
  html[data-layout="stage"] .ov__panel.is-dragging,
  html[data-layout="stage"] .vault__panel.is-dragging,
  html[data-layout="stage"] .store__panel.is-dragging,
  html[data-layout="stage"] .grail__panel.is-dragging,
  html[data-layout="stage"] .codds__panel.is-dragging{will-change:transform}
  html[data-layout="stage"] .ov__panel.is-dragging .ov__body{-webkit-mask-image:none;mask-image:none}
  html[data-layout="stage"] .ov__body,
  html[data-layout="stage"] .vault__body,
  html[data-layout="stage"] .store__body,
  html[data-layout="stage"] .grail__scroll,
  html[data-layout="stage"] .codds__body{overscroll-behavior:contain}
}

/* ══════════════════════════════════════════════════════════════════════
   6.36 THE CLUSTER RIDES THE LIVE FEED'S ROW (Luke, 17 Sep, screenshot of
   the bell / sound / points / USDC / avatar pills: "can we move all this up
   so the live feed goes behind it ... obv dont wanna be able to see the live
   feed behind it ... need to always be in the top position on the site, so
   like when we are ripping it at the top too").
   §6.15 read his 6 Sep list as two rows — the strip on row one, the cluster
   on row two — but the words there were "the sound button, the rip points,
   the USDC balance and the profile button ON THE SAME ROW as the live feed
   is now". This is that row, finished. Row two keeps its 66px of flow: the
   ring already lives in it (§6.16's -66px), so nothing else may move, and
   nothing does — .shell, .sl-cats, the hero rail and the rank all measure
   identical before and after at 1512x982 and at his 1147x655.

   (a) THE LIFT IS A MARGIN, not a transform and not position:absolute.
   .sl-rank is a CHILD of .header__right, and it is position:fixed on the
   lobby (§6.22) and absolute against .header on a page (§6.19). A transform
   here would make .header__right the containing block for the fixed one and
   re-anchor the absolute one — both would ride up with the pills, the rank
   leaving its bottom-left corner. A margin moves in-flow children only, so
   the rank never notices. -48px is the strip's own box: its height (46) plus
   its bottom margin (4), less the 2px that centres a 42px pill in a 46px
   band. Measured after: the pills' centre line is 38.0 against the chips'
   38.05 at 1512x982, and 28.9 against 28.85 at 1147x655. align-self is what
   lets the margin bite at all — the row is align-items:center.

   (b) THE FEED IS MASKED, not covered. The sky behind is a painting, so any
   opaque plate under the pills would read as a box sitting on it; and the
   14px gaps between the pills would show chips through anything less. The
   strip's right edge and the cluster's right edge are the same line (both
   stop at .page's padding), so `100% - --sl-hdr-w` IS the cluster's left
   edge: a chip is fully transparent before it reaches the bell, and because
   the marquee runs right-to-left the chips now come OUT from behind the
   buttons. --sl-hdr-w is stage.js's offsetWidth of .header__right, in the
   zoom group's own units (§6.14), re-measured on every width change —
   the balances' digits, the bell, signed-out's SIGN IN. */
@media (min-width:1024px){
  /* NOT ON A PAGE. page.css:181 hides the strip on the profile / leaderboard /
     clans / Pokédex pages, so the header is already row one there and the
     cluster already sits at the top — lifting it a second time put it at
     y=-49 (measured on the leaderboard), off the screen entirely. */
  html[data-layout="stage"]:not([data-page]) .header__right{align-self:flex-start;margin-top:-48px}
  html[data-layout="stage"] .strip__viewport{
    -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 56px,
      #000 calc(100% - var(--sl-hdr-w,320px) - 64px),transparent calc(100% - var(--sl-hdr-w,320px)));
    mask-image:linear-gradient(90deg,transparent 0,#000 56px,
      #000 calc(100% - var(--sl-hdr-w,320px) - 64px),transparent calc(100% - var(--sl-hdr-w,320px)))}
}
