/* ═══════════════════════════════════════════════════════════════════
   BLOCKRIPS — style layer for the BlockPacks shell.
   Class names mirror site/index.html so this ports straight into the
   real repo. Every value here was MEASURED off the approved mock, not
   eyeballed; the measurement is noted beside anything non-obvious.
   ═══════════════════════════════════════════════════════════════════ */
:root{
  /* ── V3: POP-ART, BRIGHT ───────────────────────────────────────────
     Every value here was EYEDROPPERED off the pull.fun hero banner
     (screenshot 29 Aug 23:34), not chosen. v1 and v2 were dark grounds
     with a bright accent; this one inverts that — the ground is the
     bright thing and the UI sits on it in navy chips with heavy black
     keylines, which is what makes the reference read as comic art. */
  --acc-hi:#EC6DA0; --acc:#D8498D; --acc-lo:#9F376D; --acc-deep:#5E1F40;
  --acc-rgb:216,73,141;
  --acc-warm-rgb:236,109,160;
  --stick-dot-rgb:94,31,64;

  --sky:#5190F7; --sky-mid:#82C0F1; --sky-pale:#C0D8F8;
  --yellow:#FFFE9A; --gold-line:#EDB673;
  --navy:#1F2432; --navy-hi:#2B3242;

  --violet:#8B3FF0; --violet-rgb:139,63,240;
  --blue:#5190F7;   --blue-rgb:81,144,247;
  --cyan:#22D3EE;   --cyan-rgb:34,211,238;

  --gold:#FDC107; --gold-rgb:253,193,7;

  /* THE KEYLINE IS THE STYLE. Comic art is black-outlined, so the ring goes
     back to the original construction — fill, then BLACK, then WHITE going
     outward — which is what the gold sticker always was. v2's gradient ring
     is dropped here on purpose; a gradient edge is not a comic edge. */
  --ring-w:3.5px; --ring-glow:0px;
  --ring-in:#000; --ring-out:#FFF; --ring-out-w:3px;
  --key:#0B0F18;                /* the keyline on chips and panels */

  --stick-ink:#FFFFFF;
  --swoosh:#D8498D;
  --swoosh-deep:#9F376D;

  /* panels are NAVY on a bright ground — the reference's chips exactly */
  --fill-deepest:#1F2432;
  --fill-live:#1F2432;
  --fill-strip:#1F2432;
  --fill-turbo:#1F2432;
  --fill-hover:#2B3242;
  --fill-side-hover:#2B3242;
  --fill-knob:#141A26;
  --knob-hi:#FFFFFF;
  --live-edge-hi:#D8498D;
  --live-edge-lo:#1F2432;

  --rank-gold:#FDC107; --rank-gold-fill:#141002;

  /* INK SPLITS IN TWO on a bright ground: --ink is what sits ON THE SKY and
     must be dark; --ink-panel is what sits in a navy chip and stays white. */
  --ink:#141A28; --ink-panel:#FFFFFF;
  --ink-dim:#3A4354; --ink-faint:#5A6478;
  --bg:#82C0F1; --panel:#1F2432; --line:#0B0F18;

  --display:'Barlow Condensed',system-ui,sans-serif;
  --ui:'Barlow Semi Condensed',system-ui,sans-serif;
  --slant:skewX(-7deg);
  --head-tilt:-2.51deg;
  --rip-skew:-8.36deg;

  --r-btn:15px; --r-card:13px;
  --rail-l:312px; --rail-r:340px; --gap:18px;
}
*{box-sizing:border-box;margin:0;padding:0}
/* NO RUBBERBAND (Luke, 11 Sep: "I feel like the whole site rubberbands a
   little bit. On the leaderboard page, it rubberbands left to right. That
   shouldn't happen.").
   Nothing on the site actually overflows sideways — measured, documentElement
   scrollWidth == clientWidth on the lobby and on all four pages, and no element
   can scroll on x at all. So the bounce is the BROWSER's own overscroll on the
   document, which `overflow:hidden` does not stop: a two-finger swipe still
   rubberbands the whole page. `overscroll-behavior:none` is the rule that does,
   on both axes. The inner scrollers already carry `contain` (page.css), so
   nothing about scrolling INSIDE a page changes. */
html,body{height:100%;overflow:hidden;overscroll-behavior:none}
/* html carries the black; BODY MUST NOT.
   .bg sits at z-index:-1, and in the painting order a negative-z-index
   descendant is drawn BEFORE the in-flow block backgrounds of the stacking
   context — so an opaque `background` on <body> paints straight over it. That
   is why the ground plate was invisible while every check said it had loaded,
   decoded and was visible. html's background is the canvas backdrop and paints
   below everything, which is where the black belongs. */
html{background:var(--bg)}
body{background:transparent;color:var(--ink);font-family:var(--ui);-webkit-font-smoothing:antialiased}
.fit{position:fixed;inset:0;overflow:hidden;display:flex}
img{display:block;max-width:100%}
a{text-decoration:none;color:inherit}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
:focus-visible{outline:2px solid var(--acc);outline-offset:3px;border-radius:6px}

/* ── decorative ground ─────────────────────────────────────────── */
/* THE GROUND IS A GENERATED PLATE, NOT CSS — see tools-bg.py.
   The mock's background is a HALFTONE: the dots grow and brighten toward the
   glow behind the pack and again toward the top-right corner. CSS can repeat
   one dot size and fade it with a mask, which is what this used to do and is
   exactly the "uniform dots" that read as wrong. Dot-size modulation needs a
   raster, so the plate is drawn from the mock's own measured light field —
   verified to within 0.57 luminance levels across eight radial bands.
   z-index:-1 IS LOAD-BEARING: pack3d.js inserts #stageLayer at z-index:0 as
   body child 0, and an opaque .bg at the same level would paint over the
   WebGL canvas (that cost the previous session hours). */
.bg{position:fixed;inset:0;z-index:-1;pointer-events:none;overflow:hidden;
  background:var(--bg) url(bg-plate.png) center center/cover no-repeat}
/* THE STOPWATCH. In the mock it is part of the GROUND, not of the right rail:
   it sits bottom-right, tilted, and runs off the page edge. Living in .bg lets
   it bleed past the edge without the rail clipping it or gaining a scrollbar.
   Measured off the mock: centroid ~(94%, 90%), ~220px across at 1556 wide. */
/* measured off the mock: centroid (94%, 90%), ~220px across at 1556 wide
   (14.1% of the width), tilted, and running off the right edge */
.bg__clock{position:fixed;right:-2.6%;bottom:-5.5%;z-index:0;pointer-events:none;
  width:clamp(150px,14.1vw,240px);height:auto;
  color:#3A1C6E;transform:rotate(13deg);transform-origin:center}
.page{position:relative;z-index:1;flex:1;min-width:0;min-height:0;
  display:flex;flex-direction:column;padding:0 clamp(16px,2.2vw,40px)}

/* ── the sticker treatment, shared by every gold control ───────── */
/* THE RING IS NOW A GRADIENT. Under gold this was a flat black keyline plus a
   flat white outer ring — border + box-shadow, in that order. A box-shadow
   cannot hold a gradient, so the BORDER ITSELF carries it: the fill paints to
   the padding box and the ring gradient to the border box. That keeps the
   radius, keeps overflow:hidden, and leaves ::before (the gloss) and ::after
   (the halftone) untouched, which the two-pseudo-element budget required. */
.stick{position:relative;overflow:hidden;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 5px 0 0 rgba(11,15,24,.30)}
.stick::before{content:"";position:absolute;left:5px;right:5px;top:2px;height:36%;pointer-events:none;
  background:linear-gradient(180deg,rgba(255,255,255,.66) 0%,rgba(255,255,255,.24) 62%,transparent 100%);
  border-radius:10px 10px 46px 46px/10px 10px 13px 13px}
.stick::after{content:"";position:absolute;inset:0;pointer-events:none;
  background-image:radial-gradient(circle at 1.8px 1.8px,rgba(var(--stick-dot-rgb),.62) 1.45px,transparent 1.65px);
  background-size:6px 6px;
  -webkit-mask-image:radial-gradient(58% 105% at 93% 112%,#000 0%,transparent 58%),radial-gradient(44% 98% at 6% 114%,#000 0%,transparent 55%);
  mask-image:radial-gradient(58% 105% at 93% 112%,#000 0%,transparent 58%),radial-gradient(44% 98% at 6% 114%,#000 0%,transparent 55%)}
.stick>*{position:relative;z-index:2}

/* ── inline-SVG accents, driven from tokens ────────────────────────
   The swooshes and the two gold-stroked header/rail icons are inline SVG
   with fill=/stroke= PRESENTATION ATTRIBUTES, which CSS outranks. Restating
   them here means a recolour is a token edit and never a sweep through the
   markup — and, critically, it lets the .ladder's GOLD tier keep its gold
   while the accent moves, because these selectors are scoped and the ladder
   is not one of them. */
.swoosh path:not([fill^="url"]){fill:var(--swoosh)}
/* the LEFT rail's swoosh is not flat — it is a 3-stop gradient (#swg).
   CSS stop-color drives the stops so it stays tokenised too. */
#swg stop:nth-child(1){stop-color:var(--acc-hi)}
#swg stop:nth-child(2){stop-color:var(--swoosh)}
#swg stop:nth-child(3){stop-color:var(--swoosh-deep)}
.hbal [stroke="#FDC107"],.odds [stroke="#FDC107"]{stroke:var(--acc)}

/* ── headings + the traced swoosh ──────────────────────────────── */
.rail__title{font-family:var(--display);font-weight:900;font-style:italic;line-height:1;
  letter-spacing:-.004em;white-space:nowrap;font-size:28px;
  transform:rotate(var(--head-tilt)) var(--slant) scaleX(.96);transform-origin:left center}
.swoosh{display:block;pointer-events:none;transform:rotate(-0.94deg);transform-origin:left center}

/* ── header ────────────────────────────────────────────────────── */
.header{display:flex;align-items:center;gap:clamp(20px,2.4vw,44px);height:clamp(78px,9vh,104px);flex:0 0 auto;position:relative;z-index:2}
.header__brand img{height:clamp(46px,5.4vh,62px);width:auto;flex:0 0 auto}
.header__nav{display:flex;gap:36px;position:relative;font-family:var(--display);
  font-size:23px;font-weight:800;font-style:italic;letter-spacing:.015em;line-height:1}
.navlink{color:#E2E2E2;text-decoration:none;transform:var(--slant);display:inline-block;transition:color .16s}
.navlink:hover{color:#fff}
.navlink.is-active{color:var(--gold)}
/* the underline is ONE element that slides — measured 53px wide, -0.665deg */
.nav__ink{position:absolute;bottom:-9px;height:4px;border-radius:2px;background:var(--gold);
  transform:rotate(-0.665deg);transition:left .28s cubic-bezier(.22,.8,.36,1),width .28s cubic-bezier(.22,.8,.36,1)}
.header__right{margin-left:auto;display:flex;align-items:center;gap:14px}
.hbal{height:42px;padding:0 16px;border-radius:11px;background:#080808;
  border:1.5px solid rgba(255,255,255,.28);display:flex;align-items:center;gap:9px;font-size:15px;font-weight:700}
.hbal--icon{width:48px;padding:0;justify-content:center}
.add{height:44px;padding:0 20px;border-radius:12px;font-family:var(--display);
  font-size:24px;font-weight:900;font-style:italic;display:flex;align-items:center;justify-content:center}
.add span{transform:var(--slant);display:inline-block}
.avatar{width:42px;height:42px;border-radius:50%;border:2px solid var(--acc);color:var(--acc);
  font-weight:800;display:flex;align-items:center;justify-content:center}

/* ── live strip ────────────────────────────────────────────────── */
/* --strip-h is the chip height AND the bar height, so the marquee's padding
   (which exists only to stop the hover lift being clipped) cannot shrink the
   chips. overflow is VISIBLE here: the viewport does the clipping now. */
.strip{--strip-h:clamp(60px,7.2vh,74px);
  display:flex;gap:10px;height:var(--strip-h);flex:0 0 auto;
  margin-bottom:clamp(14px,2.4vh,26px);overflow:visible;position:relative;z-index:2}
.strip__live{width:132px;flex:0 0 auto;border-radius:var(--r-card);display:flex;align-items:center;
  justify-content:center;gap:9px;background:var(--fill-strip);font-family:var(--display);font-size:21px;
  font-weight:800;font-style:italic;letter-spacing:.04em;color:var(--acc);
  /* the mock's LIVE box FADES left->right: var(--live-edge-hi) -> var(--live-edge-lo) */
  border:none;position:relative}
/* the mock's LIVE box has NO right side: left/top/bottom are drawn and simply
   fade to nothing on the way right. One masked ring does that honestly. */
.strip__live::before{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  border:1.5px solid var(--acc);
  -webkit-mask-image:linear-gradient(90deg,#000 0%,#000 26%,rgba(0,0,0,.55) 58%,transparent 88%);
  mask-image:linear-gradient(90deg,#000 0%,#000 26%,rgba(0,0,0,.55) 58%,transparent 88%)}
.strip__live i{width:9px;height:9px;border-radius:50%;background:var(--gold);
  box-shadow:0 0 9px 2px rgba(var(--acc-rgb),.75);animation:pulse 1.8s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
/* THE MARQUEE. Ported from site/shell.css:268-294.
   The mask lives on the VIEWPORT, not on .strip, so the pinned LIVE box never
   dims — chips fade out behind it on the left and off the right edge, and are
   never hard-clipped. 56px in from the left is the real site's measurement and
   it is exactly the overlap that reads as "passing behind" the LIVE box.
   The viewport clips horizontally, which clips VERTICALLY too — overflow
   cannot be hidden on one axis and visible on the other — and a chip lifts 2px
   on hover and carries a rarity glow, so the padding buys the clip box room
   and the equal negative margin gives the space back to the layout. */
.strip__viewport{flex:1 1 auto;min-width:0;overflow:hidden;
  padding:11px 0;margin:-11px 0;
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 56px,#000 calc(100% - 72px),transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0,#000 56px,#000 calc(100% - 72px),transparent 100%)}
.strip__list{display:flex;margin:0;padding:0;list-style:none;flex-wrap:nowrap;width:max-content;
  animation:stripScroll var(--strip-dur,90s) linear infinite}
/* one copy of the feed; the track holds two, so -50% wraps seamlessly.
   padding-right doubles as the gap ACROSS the copy boundary. */
.strip__half{display:flex;gap:10px;padding-right:10px;flex:0 0 auto}
.strip__viewport:hover .strip__list{animation-play-state:paused}
@keyframes stripScroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:reduce){
  .strip__list{animation:none}
  .strip__viewport{overflow-x:auto}
}
.hit{flex:0 0 auto;display:flex;align-items:center;gap:10px;padding:0 11px;height:var(--strip-h);
  border-radius:var(--r-card);cursor:pointer;font:inherit;text-align:left;white-space:nowrap;
  background:var(--fill-live);border:1.5px solid rgba(var(--acc-rgb),.40);
  transition:border-color .14s ease-out,transform .14s ease-out,box-shadow .14s ease-out}
/* RARITY IS THE OUTLINE. Same four tokens as the real site (shell.css:1361):
   common green, rare blue, epic purple, legendary gold — the box tells you the
   tier before you read the row. Gold-bordered chips in the strip are the
   legendary pulls, which is why some glow harder than others. */
.hit[data-rarity="common"]{--hitc:0,230,3}
.hit[data-rarity="rare"]{--hitc:30,146,255}
.hit[data-rarity="epic"]{--hitc:221,43,255}
.hit[data-rarity="legendary"]{--hitc:255,206,27}
.hit[data-rarity]{border-color:rgba(var(--hitc),.55);
  box-shadow:0 0 15px -4px rgba(var(--hitc),.45),inset 0 0 12px -7px rgba(var(--hitc),.35)}
.hit:hover,.hit:focus-visible{transform:translateY(-2px)}
.hit[data-rarity]:hover{border-color:rgba(var(--hitc),.95);
  box-shadow:0 0 22px -3px rgba(var(--hitc),.6),inset 0 0 12px -7px rgba(var(--hitc),.35)}
.hit__img{width:34px;height:48px;border-radius:3px;object-fit:cover;flex:0 0 auto;
  border:1px solid rgba(255,255,255,.30);background:rgba(0,0,0,.35)}
.hit__meta{display:flex;flex-direction:column;min-width:0}
.hit__u{font-size:11px;font-weight:800;line-height:1;color:var(--acc);white-space:nowrap;
  max-width:158px;overflow:hidden;text-overflow:ellipsis;cursor:pointer}
.hit__u:hover{color:var(--acc-hi)}
.hit__n{font-size:14px;font-weight:600;color:#EDEDED;white-space:nowrap;max-width:158px;overflow:hidden;text-overflow:ellipsis}
.hit__m{display:flex;gap:6px;align-items:baseline;
  font-size:13px;font-weight:600;color:var(--ink-dim);margin-top:3px;white-space:nowrap}
.hit__m b{color:var(--acc)}
.hit__m i{color:var(--ink-faint);font-style:normal;margin-left:0}

/* ── the three-column shell ────────────────────────────────────── */
.shell{display:grid;grid-template-columns:var(--rail-l) minmax(0,1fr) var(--rail-r);
  gap:clamp(16px,2vw,40px);align-items:stretch;position:relative;z-index:2;
  flex:1 1 auto;min-height:0;padding-bottom:clamp(10px,2vh,28px)}

/* ── left rail ─────────────────────────────────────────────────── */
.rail{min-height:0;overflow-y:auto;overscroll-behavior:contain;
  scrollbar-width:none;-ms-overflow-style:none}
.rail::-webkit-scrollbar{width:0;height:0}
.rail--left{padding-left:21px;padding-right:6px}
.rail--left .rail__title{margin-left:16px}
.rail--left .swoosh{width:180px;height:9px;margin:6px 0 22px 11px}
.picker{position:relative;margin-bottom:26px}
.picker__btn{width:250px;height:42px;display:flex;align-items:center;gap:12px;color:#fff}
/* THE PICKER'S ICON. A slot, not a drawing — app.js fills it from
   icons.js on every render(), so the mark beside the label is always the
   category's own. It used to be a hardcoded Pokeball, which is why picking
   One Piece left a Pokeball sitting next to the words "One Piece". */
.picker__icon{width:26px;height:26px;flex:0 0 auto;display:block}
.picker__icon .ficon,.picker__optIcon .ficon{width:100%;height:100%;display:block;object-fit:contain}
.picker__optIcon{width:22px;height:22px;flex:0 0 auto;display:block}
/* the generated art already carries its own white sticker border, so it needs
   no help sitting on black — just a touch of lift, like the buttons have */
.ficon{filter:drop-shadow(0 1px 2px rgba(0,0,0,.65))}
.picker__label{flex:1;text-align:left;font-size:16.5px;font-weight:700}
.picker__chev{transition:transform .2s}
.picker__btn[aria-expanded="true"] .picker__chev{transform:rotate(180deg)}
.picker__rule{width:250px;height:1.5px;background:linear-gradient(90deg,rgba(255,255,255,.28),rgba(255,255,255,.08))}
.picker__menu{position:absolute;top:52px;left:0;width:250px;z-index:20;list-style:none;
  background:#0C0C0C;border:2px solid var(--acc);border-radius:12px;padding:6px;
  box-shadow:0 14px 40px -10px rgba(0,0,0,.9),0 0 20px -6px rgba(var(--acc-rgb),.4)}
.picker__menu[hidden]{display:none}
.picker__opt{width:100%;display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:8px;
  font-size:15px;font-weight:600;text-align:left;transition:background .14s}
.picker__opt:hover,.picker__opt[aria-selected="true"]{background:rgba(var(--acc-rgb),.14);color:var(--acc)}

/* THE RAIL IS A COLUMN WITH A FIXED FOOT.
   .packlist takes all the slack and scrolls inside itself; the cue and What's
   Inside are flex:0 0 auto below it. That is what makes What's Inside STAY PUT
   — Pokémon has 7 tiers and Sports has 1, and the button sits in exactly the
   same place for both, because the list absorbs the difference instead of
   pushing the button around. */
.rail--left{display:flex;flex-direction:column;overflow:hidden;padding-bottom:4px}
/* ROOM FOR THE STICKER RING.
   The selected tier is a .stick, and .stick's white outer ring is a box-shadow
   SPREAD — it lives 2.5px outside the button's own box. overflow-y:auto makes
   this a scroll container, and a scroll container clips BOTH axes (overflow
   cannot be hidden vertically and visible horizontally), so the ring was being
   sliced off the left edge of every selected row and off the top of the first
   one. The padding buys the clip box room for the ring; the equal negative
   margin hands the space back, so nothing moves. Same trick, same reason, as
   .strip__viewport. */
.packlist{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  display:flex;flex-direction:column;gap:clamp(12px,2.4vh,26px);
  padding:7px 9px 9px;margin:-7px -9px -7px;
  scrollbar-width:none;-ms-overflow-style:none}
.packlist::-webkit-scrollbar{width:0;height:0}
/* The fade exists ONLY while app.js says something is hidden in that
   direction — no attribute, no mask node at all. This is what makes the
   half-visible row under the fold: it is dimmed, not cut, so it reads as
   "there is more down here". Ported from site/shell.css:374-385. */
.packlist[data-fade]{--fade-t:0px;--fade-b:0px;
  -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 var(--fade-t),
    #000 calc(100% - var(--fade-b)),transparent 100%);
  mask-image:linear-gradient(180deg,transparent 0,#000 var(--fade-t),
    #000 calc(100% - var(--fade-b)),transparent 100%)}
.packlist[data-fade="bottom"]{--fade-b:26px}
.packlist[data-fade="top"]{--fade-t:34px}
.packlist[data-fade="both"]{--fade-t:34px;--fade-b:26px}
.tier{flex:0 0 auto;width:270px;height:58px;border-radius:var(--r-btn);display:flex;align-items:center;
  justify-content:space-between;padding:0 19px;font-family:var(--display);font-size:23px;
  font-weight:800;font-style:italic;letter-spacing:-.005em;
  transition:transform .14s cubic-bezier(.22,.8,.36,1),border-color .14s}
.tier span{display:inline-block;transform:var(--slant)}
.tier__price{font-weight:900;font-size:26px}
/* EVERY TIER GETS ITS OWN COLOUR. The mock rings each button in a different
   hue rather than one accent, and rings it with a gradient rather than a flat
   line. Cycled on nth-of-type so it holds for any tier count — the mock shows
   four, Pokemon ships six. --tier-ring is set per position and read by the
   background above it, both on the same element. */
.tier:not(.is-on){color:#fff;border:1.6px solid transparent;
  background:linear-gradient(var(--panel),var(--panel)) padding-box,var(--tier-ring) border-box}
.tier:not(.is-on):nth-of-type(4n+1){--tier-ring:linear-gradient(135deg,#2E9BF0,#29D3F0);--tier-tint:var(--blue-rgb)}
.tier:not(.is-on):nth-of-type(4n+2){--tier-ring:linear-gradient(135deg,#7B3FE4,#B84DF0);--tier-tint:var(--violet-rgb)}
.tier:not(.is-on):nth-of-type(4n+3){--tier-ring:linear-gradient(135deg,#FF2E88,#FF6FB4);--tier-tint:var(--acc-rgb)}
.tier:not(.is-on):nth-of-type(4n+4){--tier-ring:linear-gradient(135deg,#B84DF0,#5A2ED0);--tier-tint:var(--violet-rgb)}
.tier:not(.is-on):hover{transform:translateY(-1px);box-shadow:0 0 16px -3px rgba(var(--tier-tint),.6)}
.tier.is-on .tier__price{color:var(--stick-ink)}
.tier:active{transform:translateY(2px)}
/* THE SCROLL CUE.
   It says "there is more below", so it is only ever on screen when there IS —
   [data-fade=bottom|both] on the list, which is the single source of truth
   (site/shell.css:598-614). The height is FIXED and reserved whether or not
   the arrow shows, so the list box never resizes when it appears and What's
   Inside cannot twitch.
   It bounces AND flashes: the real site moves it -2px..3px while the opacity
   runs .45..0.95, so the same 2.6s beat carries both. Kept at the big
   on-brand size Luke asked for rather than the real site's 9px chevron. */
.packscroll{flex:0 0 auto;display:flex;justify-content:center;align-items:center;
  width:270px;height:26px;margin:clamp(8px,1.5vh,16px) 0 clamp(6px,1.2vh,12px);
  color:#C9C9C9;pointer-events:none;
  visibility:hidden;opacity:0;transition:opacity .28s ease-out}
.packlist[data-fade="bottom"] ~ .packscroll,
.packlist[data-fade="both"]   ~ .packscroll{visibility:visible;opacity:1}
.packscroll svg{width:34px;height:19px;display:block;
  animation:packScrollBounce 2.6s cubic-bezier(.22,.8,.36,1) infinite}
@keyframes packScrollBounce{
  0%,100%{transform:translateY(-3px);opacity:.45}
  50%    {transform:translateY(5px); opacity:1}}
@media (prefers-reduced-motion:reduce){
  .packscroll svg{animation:none;transform:none;opacity:.85}}
.whatsinside{flex:0 0 auto;position:relative;width:270px;height:58px;margin-bottom:2px;border-radius:17px;
  background:linear-gradient(var(--fill-deepest),var(--fill-deepest)) padding-box,var(--ring-grad) border-box;
  border:2.5px solid transparent;display:flex;align-items:center;justify-content:center;gap:14px;
  box-shadow:0 0 18px -2px rgba(var(--acc-rgb),.55),inset 0 0 14px -6px rgba(var(--acc-rgb),.6)}
.whatsinside b{font-family:var(--display);font-size:23px;font-weight:800;font-style:italic;
  color:var(--acc);transform:var(--slant)}
.whatsinside i{width:24px;height:24px;border-radius:6px;border:2px solid var(--acc);color:var(--acc);
  font-size:14px;font-weight:900;font-style:normal;display:flex;align-items:center;justify-content:center}
.sparkle{position:absolute;pointer-events:none;transform-origin:50% 50%;
  clip-path:polygon(50% 0,58% 42%,100% 50%,58% 58%,50% 100%,42% 58%,0 50%,42% 42%);
  animation:twinkle 3.4s ease-in-out infinite}
/* THE STAGGER IS THE POINT, not the animation. Peaking together reads as an
   alert pulse; a third of a cycle apart it just glints, which is what the mark
   is for. Deliberately quieter and slower than .packscroll's 2.6s bounce
   directly above it (12px away, measured) — that cue is FUNCTIONAL, it means
   there are more packs below the fold, and decoration must not out-shout it.
   Amplitude is small for the same reason: opacity does the work, scale only
   keeps it from reading as a flat fade. */
.sparkle:nth-of-type(2){animation-delay:1.13s}
.sparkle:nth-of-type(3){animation-delay:2.26s}
@keyframes twinkle{
  0%,100%{opacity:.32;transform:scale(.82)}
  50%    {opacity:1;  transform:scale(1.06)}}
@media (prefers-reduced-motion:reduce){
  .sparkle{animation:none;opacity:1;transform:none}}

/* ── stage ─────────────────────────────────────────────────────── */
.stage{display:flex;flex-direction:column;align-items:center;justify-content:flex-start;min-height:0;gap:0}
/* idle motion copied from site/pack3d.js:1710 — rotation.y 0.28*sin(0.8t),
   rotation.x -0.04 rad, small in-phase bob, period 2pi/0.8 = 7.854s */
/* stage structure copied from the real site (site/shell.css:422-435, 977-1002) */
.stage__inner{flex:1 1 auto;display:grid;place-items:center;width:100%;min-height:0}
.stage__placeholder{width:100%;height:100%;display:grid;place-items:center;margin:0}
.stage__art{height:auto;max-height:100%;max-width:78%;object-fit:contain}
.pack3d{width:100%;height:100%;display:grid;place-items:center}
.pack3d canvas{width:100%!important;height:100%!important;display:block}
.stage,.stage__inner,.stage__placeholder{background:transparent}
/* chrome must sit above the fixed #stageLayer canvas */
.stage__actions,.pay,.turborow,.rail,.header,.strip{position:relative;z-index:3}
/* THE HEADER MUST BEAT THE STRIP. Both sat at z-index:3, so DOM order won and
   the live bar painted over the header's popovers — the balance tooltips and
   the account menu were sliced in half by the chips. Same fix, and the same
   reason, as site/shell.css:998. */
.header{z-index:6}

/* ── loading: hold the stage until the 3D pack is genuinely ready ──
   Ported from site/shell.css:1005-1046. The flat art is the FALLBACK, not the
   first frame: the 3D pack is a GLB + Draco + three textures and takes seconds,
   and painting the flat art meanwhile then popping the real pack over it is the
   flash Luke reported. So .stage__art starts invisible and only ever appears if
   the 3D actually failed.
     data-pack   — loading | live | fallback, set by app.js off PACK3D.state
     data-stage3d— set to "live" by pack3d.js:1804 itself
   Both are needed: data-stage3d is pack3d's own signal and carries !important
   so two packs can never be drawn at once (site/shell.css:1011). */
.stage__art{opacity:0;transition:opacity 220ms ease-out}   /* never flash the flat art */
[data-pack="fallback"] .stage__art{opacity:1}              /* unless 3D failed */
[data-stage3d="live"] .stage__art{opacity:0!important;visibility:hidden!important}

/* CENTRED ON THE PACK, NOT ON THE COLUMN — the pack is drawn into #stageLayer,
   which is fixed inset:0, so the viewport is the pack's frame. .stage runs from
   under the live bar down past RIP PACK, so centring on it would sit the ring
   low. `fixed` is the honest containing block (site/shell.css:1012-1028).
   z-index:4 clears the chrome at 3 and #stageLayer at 0; pointer-events:none so
   a full-viewport overlay can never eat a click. */
.loader{position:fixed;inset:0;z-index:4;display:grid;place-items:center;
  align-content:center;pointer-events:none;transition:opacity 260ms ease-out}
[data-pack="live"] .loader,[data-pack="fallback"] .loader{opacity:0;visibility:hidden}
.loader__ring,.loader__txt{grid-area:1/1}
.loader__txt{transform:translateY(62px)}
.loader__ring{position:relative;width:74px;height:74px}
/* three concentric rings, counter-rotating. The real site runs accent/cyan/teal
   off its own brand; BlockRips has no cyan, so this walks the gold ramp. */
.loader__ring span{position:absolute;inset:0;border-radius:50%;
  border:2px solid transparent;border-top-color:var(--acc);
  animation:loaderspin 1.15s linear infinite}
.loader__ring span:nth-child(2){inset:9px;border-top-color:var(--acc-hi);
  animation-duration:1.6s;animation-direction:reverse}
.loader__ring span:nth-child(3){inset:18px;border-top-color:var(--acc-deep);
  animation-duration:2.1s}
@keyframes loaderspin{to{transform:rotate(360deg)}}
.loader__txt{margin:0;font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-faint);animation:loaderbreathe 1.8s ease-in-out infinite}
@keyframes loaderbreathe{50%{opacity:.45}}
@media (prefers-reduced-motion:reduce){
  .loader__ring span{animation:none;border-top-color:var(--acc)}
  .loader__txt{animation:none}
}
@keyframes packIdle{
  0%,100%{transform:perspective(1400px) rotateX(-2.29deg) rotateY(0)        translateY(0)}
  25%    {transform:perspective(1400px) rotateX(-2.29deg) rotateY(16.04deg) translateY(-7px)}
  50%    {transform:perspective(1400px) rotateX(-2.29deg) rotateY(0)        translateY(0)}
  75%    {transform:perspective(1400px) rotateX(-2.29deg) rotateY(-16.04deg)translateY(7px)}}
.stage__row{display:flex;align-items:center;gap:12px;margin-top:clamp(12px,2.2vh,26px);flex:0 0 auto}
/* THE PILL IS CENTRED, NOT THE ROW.
   Measured on the live page: RIP PACK's centre sat at 742 and the turbo pill's
   at 722 — 20px left, which is exactly half the info dot (28px) plus its 12px
   gap. Centring a flex row centres the GROUP, so anything added beside the pill
   shoves it off the axis it is supposed to share with the pack above and the
   RIP button below. A 1fr/auto/1fr grid puts the pill in the middle column, so
   the dot can sit beside it without moving it. */
.turborow.stage__row{display:grid;grid-template-columns:1fr auto 1fr;
  align-items:center;gap:0;width:100%}
.turborow__side{justify-self:start;margin-left:12px;display:flex;align-items:center}
.turbo{width:246px;height:clamp(36px,4.2vh,42px);border-radius:12px;background:var(--fill-turbo);border:2px solid rgba(var(--acc-rgb),.85);
  display:flex;align-items:center;justify-content:center;gap:10px;font-family:var(--display);
  font-size:20px;font-weight:800;font-style:italic;color:var(--acc);box-shadow:0 0 14px -5px rgba(var(--acc-rgb),.5)}
.turbo__knob{width:44px;height:24px;border-radius:14px;background:var(--fill-knob);position:relative;
  border:1.5px solid rgba(var(--acc-rgb),.55);transition:background .2s}
.turbo__knob i{position:absolute;top:2px;right:2px;width:18px;height:18px;border-radius:50%;
  background:linear-gradient(180deg,var(--knob-hi),var(--acc-lo));box-shadow:0 0 9px rgba(var(--acc-rgb),.85);
  transition:right .2s cubic-bezier(.22,.8,.36,1),background .2s ease-out,box-shadow .2s ease-out}

/* TURBO OFF IS A DIFFERENT COLOUR, NOT A DIMMER ONE.
   Armed, the pill is gold and glows — it is the live state and it should look
   it. Off, the gold reads as "still on, just quieter", which is exactly the
   ambiguity to kill: so the ring, the text and the bolt all go WHITE and the
   glow goes out entirely. One glance tells you which state you are in. */
.turbo{transition:border-color .22s ease-out,color .22s ease-out,box-shadow .22s ease-out}
.turbo[aria-checked="false"]{border-color:rgba(255,255,255,.72);color:#FFF;box-shadow:none}
.turbo[aria-checked="false"] .turbo__bolt{fill:#FFF}
.turbo[aria-checked="false"] .turbo__knob{border-color:rgba(255,255,255,.5)}
.turbo[aria-checked="false"] .turbo__knob i{right:calc(100% - 20px);
  background:#D2D2D2;box-shadow:none}
/* The Turbo switch is retired on EVERY build (16 Sep 2026): multi-demo.js
   replaces both nodes with the Multibuy control at boot; until it runs they
   must not paint. The info dots stay (HOW IT WORKS on the phone). */
#turbo,#mTurbo{visibility:hidden;pointer-events:none}
.multiPostrip__note{margin:6px 0 0;font:700 12px/1.35 var(--font-ui,inherit);color:#fff;opacity:.85;
  text-transform:none;letter-spacing:0}
.multi[data-forced="1"]{opacity:.85}
.multi[data-forced="1"] .multi__words b{letter-spacing:.02em}
.multi{width:246px;height:clamp(36px,4.2vh,42px);border-radius:12px;background:var(--fill-turbo);
  border:2px solid rgba(var(--acc-rgb),.85);display:grid;grid-template-columns:28px minmax(0,1fr) 28px;
  align-items:stretch;overflow:hidden;color:var(--acc);box-shadow:0 0 14px -5px rgba(var(--acc-rgb),.5);
  transition:border-color .22s ease-out,color .22s ease-out,box-shadow .22s ease-out;touch-action:manipulation}
.multi__step,.multi__main{appearance:none;border:0;background:transparent;color:inherit;font-family:var(--display);
  font-weight:900;font-style:italic;min-width:0;touch-action:manipulation}
.multi__step{display:flex;align-items:center;justify-content:center;font-size:24px;line-height:1;
  border-color:rgba(var(--acc-rgb),.24);border-style:solid;border-width:0 1px 0 0;cursor:pointer}
.multi__step:disabled{opacity:.4;cursor:default}
.multi__step--plus{border-width:0 0 0 1px}
.multi__main{display:flex;align-items:center;justify-content:center;gap:6px;padding:0 5px}
.multi__bolt{flex:0 0 auto}
.multi__words{display:flex;align-items:baseline;justify-content:center;gap:5px;flex:0 0 auto;transform:var(--slant)}
.multi__words b,.multi__words span{font-family:var(--display);font-size:18px;font-weight:900;
  font-style:italic;line-height:1.1;white-space:nowrap;flex:0 0 auto}
/* Italic digits extend past their advance width; leave their ink room to paint. */
.multi__words span{color:inherit;opacity:1;padding-right:.16em;overflow:visible}
.multi__words span:empty{display:none}
.multi[data-on="0"]{border-color:rgba(255,255,255,.72);color:#FFF;box-shadow:none}
.multi[data-on="0"] .multi__bolt{fill:#FFF}
.multi__step:not(:disabled):active{background:rgba(255,255,255,.08)}
.multi__main--picker{width:100%;height:100%;cursor:pointer}
.multiPicker{position:fixed;inset:0;z-index:90;display:flex;align-items:center;justify-content:center;
  padding:calc(env(safe-area-inset-top,0px) + 14px) var(--mh-pad,13px) calc(env(safe-area-inset-bottom,0px) + 14px);
  background:rgba(11,15,24,.58);opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .16s ease-out,visibility 0s linear .16s;touch-action:none}
.multiPicker[data-open="1"]{opacity:1;visibility:visible;pointer-events:auto;transition-delay:0s}
.multiPicker__panel{position:relative;width:min(100%,344px);overflow:hidden;border:3px solid var(--key);
  border-radius:16px;background:linear-gradient(180deg,#2B3C72 0%,#172144 58%,#0B1021 100%);color:#FFF;
  box-shadow:0 20px 0 -12px rgba(11,15,24,.82),0 18px 44px rgba(0,0,0,.58),0 0 0 2px rgba(255,255,255,.42);
  padding:17px 15px 15px;font-family:var(--display);font-style:italic;text-align:center;touch-action:none}
.multiPicker__panel::before{content:"";position:absolute;inset:0 0 auto auto;width:58%;height:118px;pointer-events:none;opacity:.44;
  background-image:radial-gradient(circle at 2px 2px,rgba(var(--acc-warm-rgb),.58) 1.45px,transparent 1.8px);
  background-size:11px 11px;
  -webkit-mask-image:radial-gradient(120% 105% at 100% 0,#000 0%,transparent 72%);
  mask-image:radial-gradient(120% 105% at 100% 0,#000 0%,transparent 72%)}
.multiPicker__panel>*{position:relative;z-index:1}
.multiPicker__close{position:absolute;right:9px;top:9px;width:38px;height:38px;border:2px solid rgba(255,255,255,.82);border-radius:12px;
  background:rgba(0,0,0,.18);color:#FFF;font:900 27px/1 var(--display);cursor:pointer;box-shadow:0 3px 0 rgba(11,15,24,.36)}
.multiPicker__eyebrow{font-size:13px;font-weight:900;color:var(--acc);letter-spacing:.12em;transform:var(--slant)}
.multiPicker__title{margin:1px 44px 12px;font-size:25px;line-height:1;font-weight:900;letter-spacing:0;
  text-transform:uppercase;transform:var(--slant)}
.multiPicker__toggle{width:min(100%,214px);height:42px;margin:0 auto 12px;border:2px solid rgba(255,170,70,.9);border-radius:12px;
  display:flex;align-items:center;justify-content:center;gap:8px;padding:0 10px;background:linear-gradient(180deg,#C24F0E 0%,#8F3608 58%,#6A2606 100%);
  color:#FFC97A;font:900 17px/1 var(--display);font-style:italic;text-transform:uppercase;
  box-shadow:0 0 14px -6px rgba(255,140,31,.55),0 4px 0 0 rgba(11,15,24,.35)}
.multiPicker__toggleText{transform:var(--slant)}
.multiPicker__bolt{width:13px;height:16px;fill:currentColor;flex:0 0 auto}
.multiPicker__knob{width:34px;height:19px;border-radius:11px;background:#2A1200;position:relative;
  flex:0 0 auto;border:1.5px solid rgba(255,158,58,.45);transition:background .2s,border-color .2s}
.multiPicker__knob i{position:absolute;top:1.5px;right:calc(100% - 15.5px);width:14px;height:14px;border-radius:50%;
  background:#FFC97A;box-shadow:none;transition:right .2s cubic-bezier(.22,.8,.36,1),background .2s ease-out,box-shadow .2s ease-out}
.multiPicker__toggle[aria-checked="true"]{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,.35)}
.multiPicker__toggle[aria-checked="true"] .multiPicker__knob{background:#3A1500;border-color:rgba(0,0,0,.45)}
.multiPicker__toggle[aria-checked="true"] .multiPicker__knob i{right:1.5px;background:linear-gradient(180deg,#FFF 0%,#FFE0A8 100%);box-shadow:0 0 7px rgba(255,255,255,.8)}
.multiPicker__controls{display:grid;grid-template-columns:68px minmax(0,1fr) 68px;gap:9px;align-items:stretch}
.multiPicker__step{height:64px;position:relative;overflow:hidden;border:3px solid var(--key);border-radius:16px;
  background:linear-gradient(180deg,#FF8FC4 0%,var(--acc) 46%,#B91066 100%);color:#FFF;font:900 42px/1 var(--display);
  cursor:pointer;box-shadow:0 0 0 3px #FFF,0 5px 0 0 rgba(11,15,24,.44);text-shadow:0 2px 0 var(--key);
  transform:skewX(var(--rip-skew));touch-action:none}
.multiPicker__step::before,.multiPicker__done::before{content:"";position:absolute;left:5px;right:5px;top:3px;height:34%;pointer-events:none;
  border-radius:12px 12px 34px 34px;background:linear-gradient(180deg,rgba(255,255,255,.7),rgba(255,255,255,.18) 72%,transparent)}
.multiPicker__step::after,.multiPicker__done::after{content:"";position:absolute;inset:0;pointer-events:none;
  background-image:radial-gradient(circle at 1.8px 1.8px,rgba(255,255,255,.42) 1.35px,transparent 1.65px);
  background-size:6px 6px;opacity:.36;
  -webkit-mask-image:radial-gradient(72% 110% at 84% 108%,#000 0%,transparent 62%);
  mask-image:radial-gradient(72% 110% at 84% 108%,#000 0%,transparent 62%)}
.multiPicker__step span,.multiPicker__done span{display:inline-block;position:relative;z-index:1;transform:skewX(calc(var(--rip-skew) * -1)) var(--slant)}
.multiPicker__step:active,.multiPicker__done:active{transform:skewX(var(--rip-skew)) translateY(2px);box-shadow:0 0 0 3px #FFF,0 3px 0 0 rgba(11,15,24,.44)}
.multiPicker__step:disabled{opacity:.38;cursor:default;filter:saturate(.55);box-shadow:0 0 0 3px rgba(255,255,255,.55)}
.multiPicker__number{min-width:0;height:64px;border:2.5px solid rgba(255,255,255,.95);border-radius:14px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;background:linear-gradient(180deg,rgba(255,255,255,.18),rgba(255,255,255,.07));
  box-shadow:0 1px 0 rgba(255,255,255,.55) inset}
.multiPicker__number b{font:900 46px/.86 var(--display);color:#FFF;-webkit-text-stroke:5px var(--key);paint-order:stroke fill;transform:var(--slant)}
.multiPicker__number span{margin-top:7px;font:900 14px/1 var(--display);letter-spacing:.04em;text-transform:uppercase;color:var(--acc);white-space:nowrap;transform:var(--slant)}
.multiPicker__number span:empty{display:none}
.multiPicker__done{width:100%;height:48px;margin-top:13px;position:relative;overflow:hidden;border:3px solid var(--key);border-radius:16px;
  background:linear-gradient(180deg,#FF8FC4 0%,var(--acc) 46%,#B91066 100%);color:#FFF;
  font:900 21px/1 var(--display);font-style:italic;text-transform:uppercase;box-shadow:0 0 0 3px #FFF,0 5px 0 0 rgba(11,15,24,.44);
  text-shadow:0 2px 0 var(--key);transform:skewX(var(--rip-skew))}
.info{width:28px;height:28px;border-radius:50%;border:1.5px solid rgba(255,255,255,.38);
  display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800;color:#CFCFCF}
/* RIP PACK is a PARALLELOGRAM in the mock — sides lean -8.36deg off vertical */
.rip{width:min(358px,100%);height:clamp(56px,7vh,74px);margin-top:clamp(12px,2vh,22px);flex:0 0 auto;border-radius:20px;
  transform:skewX(var(--rip-skew));
  transition:transform .14s cubic-bezier(.22,.8,.36,1)}
.rip__in{display:flex;align-items:center;justify-content:center;gap:13px;height:100%;
  transform:skewX(calc(var(--rip-skew) * -1));font-family:var(--display);font-size:44px;
  font-weight:900;font-style:italic;letter-spacing:-.005em}
.rip__in span{transform:var(--slant);display:inline-block}
.rip:hover{transform:skewX(var(--rip-skew)) translateY(-2px)}
.rip:active{transform:skewX(var(--rip-skew)) translateY(2px)}

/* THE TURBO BOLT ON THE RIP BUTTON.
   The bolt is NOT in the markup — <html data-turbo="1"> is the whole switch,
   exactly as the real site does it (shell.css:488-496, shell.js:441). It only
   ever appears while turbo is armed.
   It is a FLEX ITEM that grows from 0 width rather than an absolutely
   positioned overlay, which buys the motion Luke described for free: the row
   is centred, so a bolt appearing on the left slides the label right, and
   turning turbo off collapses the bolt and slides the label back to centre.
   The button's own width is fixed, so it never resizes either way.
   .rip carries `stick`, whose ::before and ::after are already the gloss and
   the halftone — hence .rip__in::before rather than .rip::before. */
.rip__in::before{content:'';flex:0 0 auto;width:0;height:33px;opacity:0;
  margin-right:-13px;              /* cancels .rip__in's gap while collapsed */
  background:var(--stick-ink);transform:var(--slant);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 32'%3E%3Cpath d='M13.5 0 2 18h7l-1.5 14L22 13h-8l2.5-13z'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 32'%3E%3Cpath d='M13.5 0 2 18h7l-1.5 14L22 13h-8l2.5-13z'/%3E%3C/svg%3E") center/contain no-repeat;
  transition:width 240ms cubic-bezier(.22,.8,.36,1),
             margin-right 240ms cubic-bezier(.22,.8,.36,1),
             opacity 150ms ease-out}
[data-turbo="1"] .rip__in::before{width:26px;margin-right:0;opacity:1;
  animation:boltstrike 220ms ease-out}
/* the strike: the bolt wipes in top-to-bottom and flashes once */
@keyframes boltstrike{
  0%  {clip-path:inset(0 0 100% 0);filter:brightness(3)}
  55% {clip-path:inset(0);filter:brightness(3)}
  100%{clip-path:inset(0);filter:brightness(1)}}
@media (prefers-reduced-motion:reduce){
  .rip__in::before{transition:none}
  [data-turbo="1"] .rip__in::before{animation:none}}
.pay{display:flex;gap:12px;margin-top:clamp(14px,2.4vh,28px);flex:0 0 auto}
.pay button{height:clamp(40px,4.6vh,48px);padding:0 clamp(14px,1.6vw,22px);border-radius:13px;background:var(--panel);
  border:1.5px solid rgba(255,255,255,.32);display:flex;align-items:center;gap:9px;
  font-size:17px;font-weight:600;color:#F2F2F2;transition:border-color .16s}
.pay button:hover{border-color:rgba(var(--acc-rgb),.6)}

/* ── right rail ────────────────────────────────────────────────── */
.rail--right .rail__title{font-size:27px}
.rail--right .swoosh{width:84px;height:9px;margin:5px 0 16px 1px}
.rank{display:flex;align-items:center;gap:18px;margin-bottom:18px}
.rank__shield{width:70px;height:78px;flex:0 0 auto;filter:drop-shadow(0 0 9px rgba(255,255,255,.28))}
.rank__name{font-family:var(--display);font-size:46px;font-weight:900;font-style:italic;line-height:1;
  letter-spacing:-.055em;font-kerning:none;font-feature-settings:'kern' 0;
  transform:rotate(-1.6deg) skewX(-8deg);transform-origin:left}
.rank__val{font-size:16px;font-weight:800;color:#CFCFCF;margin-top:9px}
.rank__val b{color:#fff}
.rank__bar{height:11px;border-radius:7px;background:#2E2E2E;overflow:hidden;margin-bottom:12px}
.rank__bar i{display:block;height:100%;border-radius:7px;
  background:linear-gradient(90deg,var(--acc) 0%,#B84DF0 58%,var(--blue) 100%);box-shadow:0 0 12px rgba(var(--acc-rgb),.7)}
.rank__next{font-size:15px;font-weight:700;color:#D5D5D5;margin-bottom:26px}
.rank__next b{color:var(--acc);font-weight:900}
.ladder{display:flex;justify-content:space-between;list-style:none;margin-bottom:26px}
.ladder li{text-align:center;font-size:12px;font-weight:800;color:#B4B4B4}
.ladder svg{display:block;margin:0 auto 7px}
.odds{display:inline-flex;align-items:center;gap:7px;text-decoration:none;font-family:var(--display);font-size:21px;
  font-weight:800;font-style:italic;color:var(--acc);transform:rotate(-1.4deg) var(--slant);
  transform-origin:left;margin-bottom:22px}
.crate{width:min(247px,100%);height:auto;max-height:22vh;object-fit:contain;margin:0 auto 12px}
.daily{margin-top:6px}
.daily__t{font-family:var(--display);font-size:29px;font-weight:900;font-style:italic;line-height:1;
  transform:rotate(var(--head-tilt)) var(--slant) scaleX(.96);transform-origin:left center}
.daily .swoosh{width:153px;height:9px;margin:7px 0 16px 1px}
.daily__lbl{font-size:20px;font-weight:700;color:#F0F0F0;letter-spacing:-.005em}
.daily__timer{font-family:var(--display);font-size:34px;font-weight:900;font-style:normal;
  color:var(--gold);font-variant-numeric:tabular-nums;letter-spacing:-.01em;line-height:1.2}
.rail--right{position:relative;padding-right:4px;padding-bottom:20px}
.rail--right .clock{position:absolute;right:2px;bottom:6px;width:clamp(78px,7.5vw,120px);height:auto;opacity:1;pointer-events:none}

@media (prefers-reduced-motion:reduce){
  .pack{animation:none;transform:perspective(1400px) rotateX(-2.29deg)}
  *,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important}
}
/* Desktop-only for now. Below 1100 the grid would need the rails to
   collapse into the mobile dock the real site already has — that work
   lands when we port onto site/mobile.css. */
@media (max-width:1180px){
  .shell{grid-template-columns:1fr;justify-items:center}
  .rail--left,.rail--right{width:312px}
}

/* ═══════════════════════════════════════════════════════════════════
   OVERLAYS — MY VAULT, ODDS, INFO, and a pull's detail.
   One shell (.ov / .ov__panel) so all four share the same backdrop, the same
   close affordance and the same brand furniture: the traced swoosh, the
   halftone corner, the starburst and the sparkles that the mocks put on every
   surface. Built from _refs/vault-mock.png and _refs/odds-mock.png.
   ═══════════════════════════════════════════════════════════════════ */
.ov{position:fixed;inset:0;z-index:60;display:grid;place-items:center;padding:clamp(12px,2.4vh,30px);
  background:rgba(0,0,0,.72);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  opacity:0;visibility:hidden;transition:opacity .22s ease-out,visibility .22s}
.ov[data-open="1"]{opacity:1;visibility:visible}
.ov__panel{position:relative;overflow:hidden;display:flex;flex-direction:column;
  width:min(1180px,100%);max-height:100%;
  background:#050505;border:2px solid var(--acc);border-radius:20px;
  box-shadow:0 30px 90px -20px #000,0 0 60px -18px rgba(var(--acc-rgb),.45);
  transform:translateY(10px) scale(.985);transition:transform .24s cubic-bezier(.22,.8,.36,1)}
.ov[data-open="1"] .ov__panel{transform:none}
.ov__panel--sm{width:min(680px,100%)}

/* BRAND FURNITURE, SHARED — AND IT LIVES BEHIND THE CONTENT.
   These are absolutely positioned; .ov__head and .ov__body were not positioned
   at all, and a positioned element paints above a non-positioned one whatever
   the source order. So the starburst was drawn OVER the vault's cards and over
   the info steps' text. Decoration is z-index:0, everything readable is
   z-index:1 or higher (see .ov__head / .ov__body below). */
.ov__dots{position:absolute;z-index:0;pointer-events:none;
  background-image:radial-gradient(circle at 2px 2px,rgba(var(--acc-warm-rgb),.42) 1.6px,transparent 1.85px);
  background-size:13px 13px}
.ov__dots--tr{right:0;top:0;width:min(46%,430px);height:200px;
  -webkit-mask-image:radial-gradient(120% 130% at 100% 0,#000 0%,transparent 72%);
  mask-image:radial-gradient(120% 130% at 100% 0,#000 0%,transparent 72%)}
/* THE STARBURST the mock parks in the bottom-right, half off the panel.
   First attempt masked a halftone field with a star-shaped mask, which drew a
   star made OF dots — it read as scattered noise, not a graphic. The mock's is
   a clean gold OUTLINE with a halftone field sitting behind part of it, so
   that is what this is: the stroke is its own layer, drawn full strength. */
.ov__burst{position:absolute;z-index:0;right:-64px;bottom:-96px;width:520px;height:520px;pointer-events:none;
  opacity:.34;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M50 3 57.5 19.5 71 7.5 72.5 25.5 89 18.5 83.5 36 99 38.5 87 50 99 64.5 82.5 67 87.5 84.5 70.5 79 68 96.5 55 86 46.5 99.5 38.5 84.5 23 93 22 75.5 5.5 78.5 13.5 62 1.5 51 14.5 41.5 6.5 25 24.5 26 24.5 8.5 40 17.5z' fill='none' stroke='%23F3C108' stroke-width='1.6' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat}
/* the halftone that sits inside the burst's lower lobe */
.ov__burst::after{content:"";position:absolute;inset:14%;
  background-image:radial-gradient(circle at 2px 2px,rgba(var(--acc-warm-rgb),.55) 1.5px,transparent 1.75px);
  background-size:11px 11px;
  -webkit-mask-image:radial-gradient(70% 70% at 78% 78%,#000 0%,transparent 70%);
  mask-image:radial-gradient(70% 70% at 78% 78%,#000 0%,transparent 70%)}
.spark{position:absolute;z-index:0;pointer-events:none;background:#FFF;
  clip-path:polygon(50% 0,58% 42%,100% 50%,58% 58%,50% 100%,42% 58%,0 50%,42% 42%)}
.spark--a{width:34px;height:34px;left:26px;top:22px}
.spark--b{width:20px;height:20px;left:16px;bottom:64px;background:var(--acc-hi)}

/* header */
.ov__head{position:relative;z-index:1;flex:0 0 auto;display:flex;align-items:flex-start;justify-content:space-between;
  gap:20px;padding:clamp(20px,3vh,34px) clamp(22px,3vw,42px) 0}
.ov__title{position:relative}
.ov__h{font-family:var(--display);font-size:clamp(38px,4.6vw,58px);font-weight:900;font-style:italic;
  line-height:.98;letter-spacing:-.01em;transform:var(--slant);transform-origin:left center}
.ov__h em{font-style:inherit;color:#FFF}
.ov__h b{font-weight:inherit;color:var(--acc)}
/* the card sheet's title when it was opened from a profile: the person, as a
   button back to them (app.js openHit, 10 Sep). Inherits the h2's face; a name
   over 12 characters shrinks so the title stays on one line over the swoosh. */
.hitov__who{appearance:none;background:none;border:0;padding:0;margin:0;font:inherit;color:inherit;text-transform:uppercase;cursor:pointer;text-align:left;white-space:nowrap}
.hitov__who--long{font-size:.74em}
.hitov__who:hover em{text-decoration:underline;text-decoration-thickness:3px;text-underline-offset:5px}
.ov__h--plain{color:#FFF;font-size:clamp(32px,3.4vw,44px)}
.ov__title .swoosh{width:clamp(150px,17vw,215px);height:9px;margin:5px 0 0 6px}
.ov__sub{margin-top:9px;font-size:clamp(13px,1.1vw,16px);color:var(--ink-dim);max-width:62ch}
.ov__head--odds{align-items:flex-start}
.ov__acts{display:flex;align-items:center;gap:14px;flex:0 0 auto}
.ov__x{width:clamp(48px,4.4vw,62px);height:clamp(48px,4.4vw,62px);border-radius:14px;flex:0 0 auto;
  border:2px solid var(--acc);color:#FFF;display:flex;align-items:center;justify-content:center;
  transition:background .16s,transform .12s}
.ov__x svg{width:52%;height:52%}
.ov__x:hover{background:rgba(var(--acc-rgb),.16)}
.ov__x:active{transform:translateY(2px)}
.redeem{height:clamp(48px,4.4vw,62px);padding:0 clamp(20px,2.4vw,34px);border-radius:14px;
  font-family:var(--display);font-size:clamp(22px,2.2vw,30px);font-weight:900;font-style:italic;
  display:flex;align-items:center;justify-content:center}
.redeem span{transform:var(--slant);display:inline-block}
.redeem:active{transform:translateY(2px)}

/* body scrolls; the head and foot stay put */
.ov__body{position:relative;z-index:1;flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  padding:clamp(16px,2.4vh,26px) clamp(22px,3vw,42px);scrollbar-width:thin;
  scrollbar-color:rgba(var(--acc-rgb),.45) transparent}
.ov__body::-webkit-scrollbar{width:8px}
.ov__body::-webkit-scrollbar-thumb{background:rgba(var(--acc-rgb),.4);border-radius:4px}
.ov__foot{flex:0 0 auto;padding:14px clamp(22px,3vw,42px) clamp(16px,2.4vh,24px);
  border-top:1px solid rgba(255,255,255,.10);color:var(--ink-faint);font-size:14px;position:relative;z-index:2}

/* ── MY VAULT ─────────────────────────────────────────────────── */
.vault__count{padding:14px clamp(22px,3vw,42px) 0;font-size:clamp(17px,1.5vw,22px);color:var(--ink-dim);position:relative;z-index:2}
.vault__count b{color:var(--acc);font-weight:800}
.vault__tabs{display:flex;gap:clamp(20px,2.6vw,42px);padding:clamp(14px,2vh,22px) clamp(22px,3vw,42px) 0;
  border-bottom:1px solid rgba(255,255,255,.12);position:relative;z-index:2}
.vtab{position:relative;padding-bottom:14px;font-family:var(--display);
  font-size:clamp(19px,1.9vw,26px);font-weight:800;font-style:italic;color:#FFF;
  transform:var(--slant);transition:color .16s}
.vtab:hover{color:var(--acc-hi)}
.vtab.is-on{color:var(--acc)}
/* the active tab wears the same swoosh the headings do, not a plain rule */
.vtab.is-on::after{content:"";position:absolute;left:-2px;right:-2px;bottom:6px;height:5px;
  border-radius:3px;background:linear-gradient(90deg,var(--acc-hi),var(--acc-lo));
  transform:rotate(-1.1deg)}
.vault__grid{display:grid;gap:clamp(14px,1.6vw,22px);
  grid-template-columns:repeat(auto-fill,minmax(240px,1fr))}
.vcard{display:flex;flex-direction:column;gap:11px;padding:14px;border-radius:16px;
  background:#0A0A0A;border:1.5px solid rgba(var(--acc-rgb),.55);
  box-shadow:0 0 26px -12px rgba(var(--acc-rgb),.55);transition:border-color .16s,transform .16s}
.vcard:hover{border-color:var(--acc);transform:translateY(-3px)}
/* THE WHOLE SLAB, NEVER A CROP. The tile was 2.5/3.5 — the ratio of a CARD —
   but these photographs are of SLABS, which are 3.25x5.25 and measure 0.599
   w/h in the files (681x1136). A taller image in a wider box with
   object-fit:cover loses its top and bottom, which is why the PSA label and
   the base of every slab were being cut off. The box is the slab's own ratio
   now, and `contain` guarantees nothing is ever cropped even if a photo
   arrives at a slightly different size. */
.vcard__art{aspect-ratio:681/1136;border-radius:9px;overflow:hidden;background:#050505;
  border:1px solid rgba(255,255,255,.14);display:grid;place-items:center}
.vcard__art img{width:100%;height:100%;object-fit:contain}
.vcard__n{font-size:19px;font-weight:700;line-height:1.15;color:#FFF}
.vcard__n span{display:block;font-size:16px;font-weight:600;color:var(--ink-dim);margin-top:2px}
.vcard__row{display:flex;align-items:center;justify-content:space-between;gap:10px}
.vcard__grade{padding:5px 12px;border-radius:10px;border:1.5px solid var(--acc);color:var(--acc);
  font-size:15px;font-weight:800;letter-spacing:.01em}
.vcard__val{font-size:21px;font-weight:700;color:#FFF}
.vcard__sell{height:46px;border-radius:12px;background:#111;border:1.5px solid rgba(255,255,255,.20);
  font-size:17px;font-weight:600;color:#F2F2F2;transition:border-color .16s,background .16s}
.vcard__sell:hover{border-color:var(--acc);background:rgba(var(--acc-rgb),.10);color:var(--acc)}

/* ── ODDS ─────────────────────────────────────────────────────── */
/* namespaced: a bare `.odds` here would collide with the RANKS & CRATE ODDS
   link above, whose rotate+skew tilted the entire modal. */
.ov--odds .ov__panel{width:min(880px,100%)}
.otabs{flex:0 0 auto;display:flex;gap:1px;margin:clamp(14px,2vh,20px) clamp(22px,3vw,42px) 0;
  padding:5px;border:1.5px solid rgba(var(--acc-rgb),.55);border-radius:14px;background:#080808;
  overflow-x:auto;scrollbar-width:none;position:relative;z-index:2}
.otabs::-webkit-scrollbar{height:0}
.otab{flex:0 0 auto;display:flex;align-items:center;gap:6px;padding:9px 10px;border-radius:10px;
  font-family:var(--display);font-size:15px;font-weight:800;font-style:italic;letter-spacing:.01em;
  color:var(--ink-dim);white-space:nowrap;transition:color .16s,background .16s}
/* seven tabs have to fit the 880px panel; the mock only had six */
.otab__i{width:20px;height:20px;flex:0 0 auto;display:block}
.otab__i .ficon{width:100%;height:100%;display:block;object-fit:contain}
.otab:hover{color:#FFF;background:rgba(255,255,255,.06)}
.otab.is-on{color:var(--acc);background:rgba(var(--acc-rgb),.13);
  box-shadow:inset 0 -3px 0 -0.5px var(--acc)}
.oblk{padding:clamp(14px,2vh,20px) 0}
.oblk + .oblk{border-top:1px solid rgba(255,255,255,.12)}
.oblk__h{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:14px}
.oblk__h h3{font-family:var(--display);font-size:clamp(21px,2.1vw,27px);font-weight:900;font-style:italic;
  color:#FFF;transform:var(--slant);transform-origin:left center}
.oblk__h b{font-family:var(--display);font-size:clamp(21px,2.1vw,27px);font-weight:900;font-style:italic;
  color:var(--acc)}
/* the four rarity rows. Same tokens as the strip's chips, so a Legendary bar
   here and a Legendary outline up there are the same gold. */
.orow{display:grid;grid-template-columns:96px minmax(0,1fr) 52px 140px;align-items:center;gap:12px;
  padding:5px 0;font-size:15px}
.orow--common{--tier:#00E603}
.orow--rare{--tier:#1E92FF}
.orow--epic{--tier:#DD2BFF}
.orow--legendary{--tier:#FFCE1B}
.orow__t{color:var(--tier);font-weight:700}
.orow__bar{height:8px;border-radius:5px;background:#1C1C1C;overflow:hidden}
.orow__bar i{display:block;height:100%;border-radius:5px;background:var(--tier);
  box-shadow:0 0 10px -2px var(--tier)}
.orow__p{text-align:right;font-weight:700;color:#FFF;font-variant-numeric:tabular-nums}
.orow__v{text-align:right;color:var(--ink-dim);font-variant-numeric:tabular-nums}
.opool{display:flex;align-items:center;gap:9px;margin-top:10px;padding:8px 0;
  font-family:var(--display);font-size:15px;font-weight:800;font-style:italic;letter-spacing:.12em;
  color:var(--ink-faint);transition:color .16s}
.opool:hover,.opool[aria-expanded="true"]{color:var(--acc)}
.opool__chev{width:7px;height:7px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:rotate(-45deg);transition:transform .18s ease-out}
.opool[aria-expanded="true"] .opool__chev{transform:rotate(45deg)}
.opool__list{margin-top:2px}
.prow{display:grid;grid-template-columns:30px minmax(0,1fr) auto;align-items:center;gap:12px;
  padding:7px 0;border-top:1px solid rgba(255,255,255,.09);font-size:15px}
.prow__r{color:var(--ink-faint);font-variant-numeric:tabular-nums}
.prow__n{color:#EDEDED;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.prow__v{color:var(--acc);font-weight:700;font-variant-numeric:tabular-nums}

/* ── INFO ─────────────────────────────────────────────────────── */
.steps{list-style:none;display:grid;gap:clamp(12px,1.6vh,20px);margin:0 0 clamp(16px,2.2vh,26px)}
.steps li{position:relative;padding:clamp(14px,2vh,20px) clamp(16px,2vw,24px) clamp(14px,2vh,20px) 84px;
  border-radius:16px;background:#0A0A0A;border:1.5px solid rgba(var(--acc-rgb),.42)}
.steps__n{position:absolute;left:20px;top:50%;translate:0 -50%;
  width:46px;height:46px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  font-family:var(--display);font-size:26px;font-weight:900;font-style:italic;color:var(--stick-ink);
  background:linear-gradient(180deg,var(--acc-hi),var(--acc-lo));border:2.5px solid #000;
  box-shadow:0 0 0 2px #FFF}
.steps h3{font-family:var(--display);font-size:23px;font-weight:900;font-style:italic;color:var(--acc);
  transform:var(--slant);transform-origin:left center;margin-bottom:4px}
.steps p{color:var(--ink-dim);font-size:16px;line-height:1.45;max-width:70ch}
.facts{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(210px,1fr))}
.fact{padding:14px 16px;border-radius:14px;background:#080808;border:1px solid rgba(255,255,255,.14)}
.fact b{display:block;font-family:var(--display);font-size:20px;font-weight:800;font-style:italic;
  color:#FFF;transform:var(--slant);transform-origin:left center;margin-bottom:4px}
.fact span{color:var(--ink-dim);font-size:15px;line-height:1.4}

/* ── a pull's detail ──────────────────────────────────────────── */
.hitov__body{display:grid;grid-template-columns:minmax(0,200px) minmax(0,1fr);gap:clamp(16px,2.4vw,28px)}
.hitov__art{aspect-ratio:681/1136;border-radius:12px;overflow:hidden;background:#050505;
  border:1.5px solid rgba(var(--acc-rgb),.5);box-shadow:0 0 30px -12px rgba(var(--acc-rgb),.6)}
.hitov__art img{width:100%;height:100%;object-fit:contain}
.hitov__meta{display:flex;flex-direction:column;gap:14px;min-width:0}
.hitov__meta h3{font-size:clamp(20px,2.2vw,27px);font-weight:700;color:#FFF;line-height:1.15}
.hitov__rows{display:grid;gap:2px}
.hitov__r{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  padding:9px 0;border-top:1px solid rgba(255,255,255,.10)}
.hitov__r dt{color:var(--ink-faint);font-size:15px}
.hitov__r dd{color:#EDEDED;font-size:16px;font-weight:600}
.hitov__r--big dd{font-family:var(--display);font-size:28px;font-weight:900;font-style:italic;color:var(--acc)}
.rar{text-transform:capitalize;font-weight:700}
.rar--common{color:#00E603}.rar--rare{color:#1E92FF}
.rar--epic{color:#DD2BFF}.rar--legendary{color:#FFCE1B}
.hitov__meta .redeem{align-self:flex-start;margin-top:2px}
@media (max-width:640px){.hitov__body{grid-template-columns:1fr}}

/* ═══════════════════════════════════════════════════════════════════
   HEADER SURFACES — hovers, the add-funds popover and the account menu.
   All four were dead controls. They share one popover shell so the header
   speaks with one voice: black panel, gold keyline, a little notch pointing
   at whatever opened it.
   ═══════════════════════════════════════════════════════════════════ */
.pop{position:absolute;top:calc(100% + 14px);right:0;z-index:40;width:max-content;
  min-width:210px;max-width:320px;padding:13px 15px;border-radius:14px;
  background:#0A0A0A;border:2px solid var(--acc);text-align:left;
  box-shadow:0 18px 44px -12px #000,0 0 24px -8px rgba(var(--acc-rgb),.45);
  opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity .16s ease-out,transform .16s ease-out,visibility .16s}
.pop::before{content:"";position:absolute;top:-8px;right:22px;width:13px;height:13px;
  background:#0A0A0A;border-left:2px solid var(--acc);border-top:2px solid var(--acc);
  transform:rotate(45deg)}
.pop__t{font-family:var(--display);font-size:19px;font-weight:900;font-style:italic;
  color:var(--acc);transform:var(--slant);transform-origin:left center;margin-bottom:3px}
.pop__d{font-size:14px;color:var(--ink-dim);line-height:1.35}
.pop__d b{color:#FFF;font-weight:700}
/* hover-opened popovers: the balances. Kept open while the pointer is on
   either the trigger or the panel, so the panel is readable. */
.hbal{position:relative}
.hbal:hover .pop,.hbal:focus-within .pop,.pop:hover{opacity:1;visibility:visible;transform:none}
.hbal{transition:border-color .16s,box-shadow .16s}
.hbal:hover{border-color:rgba(var(--acc-rgb),.75);box-shadow:0 0 16px -5px rgba(var(--acc-rgb),.6)}
.hbal--icon:hover{border-color:rgba(255,255,255,.6);box-shadow:none}
/* click-opened popovers: + ADD and the account menu */
.acct{position:relative;display:flex;align-items:center;gap:10px}
.acct__btn{display:flex;align-items:center;gap:10px;padding:0;border-radius:24px;
  transition:opacity .16s}
.acct__btn:hover{opacity:.82}
.acct__chev{transition:transform .2s}
.acct__btn[aria-expanded="true"] .acct__chev{transform:rotate(180deg)}
.pop[data-open="1"]{opacity:1;visibility:visible;transform:none}
.pop--menu{padding:7px;min-width:230px}
.menu__i{display:flex;align-items:center;gap:11px;width:100%;padding:10px 11px;border-radius:9px;
  font-size:15.5px;font-weight:600;color:#EDEDED;text-align:left;transition:background .14s,color .14s}
.menu__i:hover{background:rgba(var(--acc-rgb),.14);color:var(--acc)}
.menu__i svg{width:17px;height:17px;flex:0 0 auto}
.menu__sep{height:1px;margin:6px 4px;background:rgba(255,255,255,.13)}
.menu__head{padding:9px 11px 4px;font-size:13px;color:var(--ink-faint)}
.menu__head b{display:block;font-size:16px;color:#FFF;font-weight:700}
/* + ADD: amount chips then a sticker CTA, so the popover looks like the page */
.pop--add{min-width:264px}
.addamts{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin:9px 0 11px}
.addamt{height:38px;border-radius:10px;background:#111;border:1.5px solid rgba(255,255,255,.20);
  font-size:16px;font-weight:700;color:#EDEDED;transition:border-color .14s,background .14s,color .14s}
.addamt:hover{border-color:var(--acc);color:var(--acc)}
.addamt.is-on{background:rgba(var(--acc-rgb),.16);border-color:var(--acc);color:var(--acc)}
.addgo{width:100%;height:46px;border-radius:12px;font-family:var(--display);
  font-size:23px;font-weight:900;font-style:italic;display:flex;align-items:center;justify-content:center}
.addgo span{transform:var(--slant);display:inline-block}
/* WHAT'S INSIDE finally reacts to the pointer */
.whatsinside{transition:transform .14s cubic-bezier(.22,.8,.36,1),box-shadow .16s,background .16s}
.whatsinside:hover{transform:translateY(-2px);
  background:linear-gradient(var(--fill-hover),var(--fill-hover)) padding-box,var(--ring-grad) border-box;
  box-shadow:0 0 26px -2px rgba(var(--acc-rgb),.75),inset 0 0 16px -6px rgba(var(--acc-rgb),.8)}
.whatsinside:hover b{color:var(--acc-hi)}
.whatsinside:active{transform:translateY(2px)}

/* ═══════════════════════════════════════════════════════════════════
   MOBILE — the phone shell.
   EVERYTHING BELOW IS ADDITIVE. The desktop layout is signed off, so
   this block adds new selectors and hides desktop ones inside a media
   query; it never re-tunes an existing desktop rule and never touches
   :root's desktop values. The only rules outside the query are the
   display:none defaults immediately below, which is what makes "desktop
   is unchanged" provable with a before/after screenshot diff rather
   than argued.

   THE SHELL LINE IS 1023px AND IT IS NOT MINE TO PICK. site/pack3d.js
   changes its own behaviour at exactly `max-width: 1023px` — pixel
   ratio, shadows, drag-to-rotate and, critically, the pack's browse
   scale (pack3d.js:1296). A different breakpoint here would put the CSS
   shell and the 3D module's idea of "phone" out of step.
   ═══════════════════════════════════════════════════════════════════ */
.mhead,.mcoll,.mdock,.mnav{display:none}

@media (max-width:1023px){
  :root{
    /* THE FURNITURE BUDGET, and it is the whole design.
       Every one of these is height the pack does not get, so each is
       the smallest number that still does its job:
         head  52  logo at 34px + 9px of air either side
         strip 46  a chip with a slab thumb, a name and a price. The
                   desktop's 60-74 is a desk-sized bar; 46 still reads
                   and gives 28px back.
         coll  44  a 26px cap-height title between two 34px targets
         nav   58  24px icon + 11px label + padding, and 58 keeps the
                   whole slot over the 44px tap minimum
       Verified against the solve: at 390x844 these leave a 474px
       corridor, and the pack needs 455 to reach full desktop scale. */
    --mh-head:52px; --mh-strip:46px; --mh-coll:44px; --mh-nav:58px;
    --mh-top:env(safe-area-inset-top,0px);
    --mh-bot:env(safe-area-inset-bottom,0px);
    --mh-pad:13px;
  }
  /* Short viewport — an SE, or any phone with both browser bars showing.
     The chrome shrinks before the pack does. */
  @media (max-height:720px){
    :root{--mh-head:46px; --mh-strip:42px; --mh-coll:40px; --mh-nav:54px}
  }

  /* ── what the phone does not get ──────────────────────────────── */
  .header{display:none}                 /* replaced by .mhead: logo + USDC */
  .rail{display:none}                   /* the rails' jobs moved to the dock,
                                           the collection row and the sheets */
  /* THE SPECIFICITY HAS TO MATCH, not just the name: .turborow.stage__row
     is 0,2,0 and sets display:grid, so a bare .turborow inside a media
     query still loses — media queries do not raise specificity. */
  /* `.stage>.hring--rip`, not `.stage>.rip`: rip-hold.js wraps the button in
     .hring, so .rip is a grandchild of .stage now and the old child selector
     stopped matching — which would put the DESKTOP rip button back on the
     phone, under the dock. Same 0,2,0 specificity, for the reason above. */
  .turborow.stage__row,.stage>.hring--rip,.stage__actions.pay{display:none}
  .bg__clock{display:none}               /* decoration with no job on a phone,
                                            and it lands under the dock */
  .mhead,.mcoll,.mdock,.mnav{display:flex}

  /* ── the app shell: fixed, never scrolls ──────────────────────── */
  /* A bottom bar and a scrolling document fight each other, and the pack
     band has to be a known quantity for the corridor solve. So the page
     is a fixed frame and everything that needs to scroll (the sheets)
     scrolls inside itself. --mh-* are the only vertical unknowns. */
  .page{padding:0}
  .shell{grid-template-columns:1fr;gap:0;padding-bottom:0;
    padding-top:calc(var(--mh-top) + var(--mh-head) + var(--mh-strip) + var(--mh-coll));
    padding-bottom:calc(var(--mh-bot) + var(--mh-nav) + var(--mh-dock,150px))}
  .stage{min-height:0}
  .stage__inner{min-height:0}
  .stage__art{max-width:56%}            /* the flat fallback only */

  /* ── header: logo + USDC, nothing else ────────────────────────── */
  .mhead{position:fixed;left:0;right:0;top:0;z-index:6;
    height:calc(var(--mh-head) + var(--mh-top));padding:var(--mh-top) var(--mh-pad) 0;
    align-items:center;justify-content:space-between;gap:12px;
    background:linear-gradient(180deg,rgba(0,0,0,.88) 0%,rgba(0,0,0,.72) 62%,transparent 100%)}
  .mhead__logo{height:34px;width:auto}
  .mhead__bal{height:34px;padding:0 12px;border-radius:10px;background:#080808;
    border:1.5px solid rgba(255,255,255,.28);display:flex;align-items:center;gap:7px;
    font-size:15px;font-weight:700;color:#fff}
  .mhead__bal:active{border-color:var(--acc)}

  /* ── the collection row ───────────────────────────────────────── */
  /* Luke's second screenshot. This is the ONLY tier control on mobile —
     the desktop's picker plus its scrolling list of up to seven chips
     collapse into one 44px row, which is where most of the reclaimed
     height comes from. The name is the franchise sheet's trigger, so
     one row changes both axes of the catalogue. */
  .mcoll{position:fixed;left:0;right:0;z-index:5;
    top:calc(var(--mh-top) + var(--mh-head) + var(--mh-strip));
    height:var(--mh-coll);align-items:center;justify-content:center;gap:14px;
    padding:0 var(--mh-pad)}
  .marrow{width:34px;height:34px;flex:0 0 auto;border-radius:10px;
    border:2px solid var(--acc);color:var(--acc);background:rgba(0,0,0,.55);
    display:flex;align-items:center;justify-content:center;
    transition:background .14s,transform .12s,opacity .16s}
  .marrow svg{width:11px;height:16px}
  .marrow:active{transform:scale(.92);background:rgba(var(--acc-rgb),.18)}
  /* ONE PACK IS NOT A BROKEN ARROW. Sports has exactly one tier in the
     real catalogue, so both arrows are dead there. They stay in the
     layout, dimmed and inert, rather than being removed: pulling them
     re-centres the title and makes the row jump between franchises. */
  .marrow[aria-disabled="true"]{opacity:.22;border-color:rgba(255,255,255,.5);
    color:#fff;pointer-events:none}
  .mtitle{flex:0 1 auto;min-width:0;display:flex;align-items:baseline;gap:7px;
    padding:0 2px;max-width:calc(100% - 110px)}
  .mtitle__t{font-family:var(--display);font-size:27px;font-weight:900;font-style:italic;
    line-height:1;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;
    text-overflow:ellipsis;transform:var(--slant);transform-origin:left center;color:#fff}
  .mtitle__t b{font-weight:inherit;color:var(--acc)}
  .mtitle__chev{width:13px;height:8px;flex:0 0 auto;color:var(--acc);
    transition:transform .2s;align-self:center}
  .mtitle[aria-expanded="true"] .mtitle__chev{transform:rotate(180deg)}
  .mtitle:active .mtitle__t{color:var(--acc-hi)}

  /* ── the live strip, compacted ────────────────────────────────── */
  /* IT EARNS 46px, NOT 74. Luke did not rule on the strip, so this is
     my call: it is the only live signal on the page and the product's
     whole social proof, so deleting it to buy 46px of pack would be
     trading the reason to rip for the room to rip. At 46 it is a
     tenant; at the desktop's 74 it was a landlord. The chip drops its
     age stamp and shrinks its slab — the name, grade and price stay,
     because those are the parts that make you want one. */
  .strip{position:fixed;z-index:5;left:var(--mh-pad);right:var(--mh-pad);
    top:calc(var(--mh-top) + var(--mh-head));
    --strip-h:calc(var(--mh-strip) - 8px);height:var(--mh-strip);
    margin:0;align-items:center;gap:7px}
  .strip__live{width:66px;font-size:15px;gap:6px;border-radius:10px}
  .strip__live i{width:7px;height:7px}
  .strip__viewport{padding:6px 0;margin:-6px 0;
    -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 30px,#000 calc(100% - 34px),transparent 100%);
    mask-image:linear-gradient(90deg,transparent 0,#000 30px,#000 calc(100% - 34px),transparent 100%)}
  .hit{gap:7px;padding:0 8px;border-radius:10px}
  .hit__img{width:26px;height:34px;border-radius:4px}
  .hit__u{font-size:10.5px;max-width:104px}
  .hit__n{font-size:13px;max-width:104px}
  .hit__m{font-size:12px;gap:6px}
  .hit__grade{font-size:11px;padding:1px 5px}
  .hit__m i{display:none}               /* the age stamp is the first thing
                                           worth 30px on a phone */

  /* ── the dock ─────────────────────────────────────────────────── */
  /* Luke's fourth screenshot, "redone in a way that fills the space more
     effectively". The desktop's failure there is that TURBO and the info
     dot float in a wide row with WHAT'S INSIDE in a different rail
     entirely; here the two share one full-width row and each takes half
     of it, so the row is dense instead of sparse and nothing is a lone
     small object in a big gap. RIP PACK then spans the whole width. */
  .mdock{position:fixed;left:0;right:0;z-index:5;
    bottom:calc(var(--mh-nav) + var(--mh-bot));
    /* THE BOTTOM PADDING IS THE RAISED BUTTON'S CLEARANCE, not slack.
        The nav's centre RIP circle rises 18px above the bar into exactly
        this strip, and two gold stickers that touch read as one blob —
        RIP PACK's white ring and the circle's white ring are 2.5px each,
        so 32px of padding against an 18px rise leaves ~9px of visible
        black between them. */
    flex-direction:column;gap:9px;padding:14px var(--mh-pad) 32px;
    background:linear-gradient(180deg,transparent 0%,rgba(0,0,0,.62) 22%,rgba(0,0,0,.9) 62%,rgba(0,0,0,.94) 100%)}
  .mdock__row{display:flex;gap:9px;align-items:stretch}
  .mturbo,.mwi{flex:1 1 0;min-width:0;height:40px;border-radius:12px;
    display:flex;align-items:center;justify-content:center;gap:8px;
    font-family:var(--display);font-size:17px;font-weight:800;font-style:italic;
    letter-spacing:.005em;white-space:nowrap}
  .mturbo{background:var(--fill-turbo);border:2px solid rgba(var(--acc-rgb),.85);color:var(--acc);
    box-shadow:0 0 12px -5px rgba(var(--acc-rgb),.5);
    transition:border-color .22s ease-out,color .22s ease-out,box-shadow .22s ease-out}
  .mturbo__knob{width:34px;height:19px;border-radius:11px;background:var(--fill-knob);position:relative;
    flex:0 0 auto;border:1.5px solid rgba(var(--acc-rgb),.55);transition:background .2s}
  .mturbo__knob i{position:absolute;top:1.5px;right:1.5px;width:14px;height:14px;border-radius:50%;
    background:linear-gradient(180deg,var(--knob-hi),var(--acc-lo));box-shadow:0 0 7px rgba(var(--acc-rgb),.85);
    transition:right .2s cubic-bezier(.22,.8,.36,1),background .2s ease-out,box-shadow .2s ease-out}
  /* off is a DIFFERENT colour, not a dimmer one — same rule as desktop */
  .mturbo[aria-checked="false"]{border-color:rgba(255,255,255,.72);color:#FFF;box-shadow:none}
  .mturbo[aria-checked="false"] .mturbo__bolt{fill:#FFF}
  .mturbo[aria-checked="false"] .mturbo__knob{border-color:rgba(255,255,255,.5)}
  .mturbo[aria-checked="false"] .mturbo__knob i{right:calc(100% - 15.5px);
    background:#D2D2D2;box-shadow:none}
.multi--mobile{flex:1 1 0;width:auto;min-width:0;height:40px;grid-template-columns:minmax(0,1fr)}
  .multi--mobile .multi__step{display:none}
  .multi--mobile .multi__main{padding:0 5px;gap:5px}
  .multi--mobile .multi__bolt{width:12px;height:16px}
  .multi--mobile .multi__words{gap:3px}
  .multi--mobile .multi__words b,.multi--mobile .multi__words span{font-size:15.5px}
  .mwi{position:relative;background:var(--fill-deepest);border:2px solid var(--acc);color:var(--acc);
    box-shadow:0 0 14px -4px rgba(var(--acc-rgb),.55),inset 0 0 12px -6px rgba(var(--acc-rgb),.6)}
  .mwi b{font-weight:800;transform:var(--slant);display:inline-block}
  .mwi__q{width:18px;height:18px;flex:0 0 auto;border-radius:5px;border:1.5px solid var(--acc);
    font-size:11px;font-weight:900;font-style:normal;display:flex;align-items:center;justify-content:center}
  .mturbo:active,.mwi:active{transform:translateY(1px)}

  /* THE RIP BUTTON IS THE PRODUCT, so it is the biggest thing on screen:
     full width, 62px, the sticker ring, the same -8.36deg parallelogram
     as desktop. The skew pushes the corners out by tan(8.36)*h/2 = 4.6px
     each side, which the dock's 13px of padding covers. */
  .mrip{width:100%;height:62px;flex:0 0 auto;border-radius:18px;
    transform:skewX(var(--rip-skew));transition:transform .14s cubic-bezier(.22,.8,.36,1)}
  .mrip__in{display:flex;align-items:center;justify-content:center;gap:11px;height:100%;
    transform:skewX(calc(var(--rip-skew) * -1));font-family:var(--display);font-size:36px;
    font-weight:900;font-style:italic;letter-spacing:-.005em}
  .mrip__in span{transform:var(--slant);display:inline-block}
  .mrip:active{transform:skewX(var(--rip-skew)) translateY(2px)}
  /* the turbo bolt, same contract as desktop: no bolt in the markup,
     <html data-turbo="1"> is the whole switch and the label slides */
  .mrip__in::before{content:'';flex:0 0 auto;width:0;height:27px;opacity:0;margin-right:-11px;
    background:var(--stick-ink);transform:var(--slant);
    -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 32'%3E%3Cpath d='M13.5 0 2 18h7l-1.5 14L22 13h-8l2.5-13z'/%3E%3C/svg%3E") center/contain no-repeat;
    mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 32'%3E%3Cpath d='M13.5 0 2 18h7l-1.5 14L22 13h-8l2.5-13z'/%3E%3C/svg%3E") center/contain no-repeat;
    transition:width 240ms cubic-bezier(.22,.8,.36,1),margin-right 240ms cubic-bezier(.22,.8,.36,1),opacity 150ms ease-out}
  [data-turbo="1"] .mrip__in::before{width:21px;margin-right:0;opacity:1}
  /* The pay marks survive as a 22px line rather than three 48px buttons.
     "I can pay with Apple Pay" is a conversion signal and matters MORE on
     a phone, but choosing the method belongs in a checkout sheet, not on
     the home screen — so the signal stays and the controls go. */
  .mpay{display:flex;align-items:center;justify-content:center;gap:17px;height:19px;
    color:#7E7E7E}
  .mpay svg{width:17px;height:17px;display:block}

  /* ── the bottom bar ───────────────────────────────────────────── */
  .mnav{position:fixed;left:0;right:0;bottom:0;z-index:7;
    height:calc(var(--mh-nav) + var(--mh-bot));padding-bottom:var(--mh-bot);
    align-items:stretch;
    background:#050505;border-top:1.5px solid rgba(255,255,255,.14);
    box-shadow:0 -14px 34px -14px #000}
  .mnav__i,.mnav__mid{flex:1 1 0;min-width:0;display:flex;flex-direction:column;
    align-items:center;justify-content:center;gap:3px;color:var(--ink-faint);
    transition:color .16s}
  .mnav__i svg{width:23px;height:23px;display:block}
  .mnav__i span,.mnav__lbl{font-family:var(--display);font-size:11px;font-weight:800;
    font-style:italic;letter-spacing:.045em;transform:var(--slant);display:block}
  .mnav__i.is-on{color:var(--acc)}
  .mnav__i:active{color:#fff}
  /* the centre slot: raised, gold, the zig-zag with RIP under it —
     Luke's words for the one part of screenshot 1 he wanted kept */
  .mnav__mid{justify-content:flex-end;padding-bottom:5px;color:var(--acc);gap:4px}
  .mnav__rip{width:52px;height:52px;border-radius:50%;flex:0 0 auto;color:var(--stick-ink);
    margin-top:-18px;display:flex;align-items:center;justify-content:center;
    transition:transform .14s cubic-bezier(.22,.8,.36,1)}
  .mnav__rip svg{width:19px;height:25px;display:block;transform:var(--slant)}
  .mnav__mid:active .mnav__rip{transform:translateY(2px)}
  .mnav__lbl{color:var(--acc)}

  /* ── overlays go full-bleed ───────────────────────────────────── */
  /* The desktop's centred panel in a blurred room is a desktop idea. On
     a phone a surface either is the screen or it is not worth opening. */
  .ov{padding:0;place-items:stretch}
  .ov__panel{width:100%;max-height:none;height:100%;border-radius:0;border-width:0;
    border-top:2px solid var(--acc);transform:translateY(14px)}
  .ov__head{padding:calc(var(--mh-top) + 16px) 16px 12px;align-items:center}
  .ov__h{font-size:34px}
  .ov__h--plain{font-size:28px}
  .ov__title .swoosh{width:132px;margin-top:3px}
  .ov__sub{font-size:13px;margin-top:6px}
  .ov__x{width:44px;height:44px;border-radius:12px}
  .ov__body{padding:0 16px 20px;
    padding-bottom:calc(20px + var(--mh-bot))}
  .ov__foot{padding:12px 16px calc(14px + var(--mh-bot))}
  .ov__burst{width:330px;height:330px;right:-88px;bottom:-118px;opacity:.24}
  .ov--odds .ov__panel,.ov__panel--sm{width:100%}
  /* THE BAR STAYS LIVE OVER A SHEET. A bottom bar that a modal covers is
     a modal's bottom bar, not the app's: switching from SHOP to VAULT
     would cost a close plus a tap. So .mnav clears the overlays (z 60)
     and the panel reserves its height, which also keeps the last row of
     any scrolling body off the home indicator. */
  .mnav{z-index:70}
  /* +22 for the RAISED centre button, which rises 18px above the bar into
     the panel's last rows plus its 2.5px ring — without it the daily
     crate's timer sat behind the gold circle. */
  .ov__panel{padding-bottom:calc(var(--mh-nav) + var(--mh-bot) + 22px)}

  /* ── the franchise sheet ──────────────────────────────────────── */
  /* A bottom sheet, not a centred card: it is opened by a control at the
     TOP of the screen but answered with the thumb, so it rises to meet
     the hand rather than making it reach. */
  .ov--sheet{place-items:end stretch;background:rgba(0,0,0,.62)}
  .ov--sheet .ov__panel{height:auto;max-height:82%;
    padding:10px 0 calc(14px + var(--mh-nav) + var(--mh-bot) + 22px);
    border-radius:22px 22px 0 0;border:2px solid var(--acc);border-bottom:0;
    transform:translateY(26px)}
  .msheet__grip{width:44px;height:4px;border-radius:3px;background:rgba(255,255,255,.32);
    margin:0 auto 12px;flex:0 0 auto}
  .msheet__h{position:relative;z-index:1;flex:0 0 auto;padding:0 18px 4px;
    font-family:var(--display);font-size:26px;font-weight:900;font-style:italic;
    transform:rotate(var(--head-tilt)) var(--slant);transform-origin:left center}
  .msheet__h .swoosh{width:150px;height:9px;margin:4px 0 0 2px}
  .msheet__list{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
    padding:12px 14px 0;display:flex;flex-direction:column;gap:8px}
  .msheet__x{position:absolute;right:12px;top:12px;z-index:2}
  .mcat{display:flex;align-items:center;gap:12px;flex:0 0 auto;height:56px;padding:0 14px;
    border-radius:14px;background:var(--panel);border:1.5px solid var(--line);text-align:left}
  .mcat__i{width:30px;height:30px;flex:0 0 auto}
  .mcat__i .ficon{width:100%;height:100%;object-fit:contain}
  .mcat__n{flex:1 1 auto;font-family:var(--display);font-size:22px;font-weight:800;
    font-style:italic;transform:var(--slant);color:#fff}
  .mcat__c{flex:0 0 auto;font-size:13px;font-weight:700;color:var(--ink-faint)}
  .mcat[aria-current="true"]{border-color:var(--acc);background:rgba(var(--acc-rgb),.10)}
  .mcat[aria-current="true"] .mcat__n{color:var(--acc)}
  .mcat:active{transform:translateY(1px)}

  /* ── SHOP ─────────────────────────────────────────────────────── */
  .mshop__g{margin-bottom:22px}
  .mshop__h{display:flex;align-items:center;gap:9px;margin:0 0 11px;
    font-family:var(--display);font-size:23px;font-weight:900;font-style:italic;
    transform:var(--slant);transform-origin:left center;color:#fff}
  .mshop__i{width:22px;height:22px;flex:0 0 auto;transform:skewX(7deg)}
  .mshop__i .ficon{width:100%;height:100%;object-fit:contain}
  /* THREE COLUMNS, NOT TWO. A pack is 0.546 w/h, so a two-up tile is
     311px tall on a 390 phone and twenty packs became twelve screens of
     scrolling. Three-up is 209px and the art still reads — and the tile
     drops its name, because the franchise heading directly above it
     already says Pokémon and the price is the only thing that varies. */
  .mshop__grid{display:grid;grid-template-columns:repeat(3,1fr);gap:9px}
  .mpack{display:flex;flex-direction:column;gap:6px;padding:7px;border-radius:13px;
    background:#0A0A0A;border:1.5px solid rgba(255,255,255,.16);text-align:left}
  .mpack[aria-current="true"]{border-color:var(--acc);box-shadow:0 0 16px -6px rgba(var(--acc-rgb),.7)}
  .mpack:active{transform:translateY(1px)}
  /* the pack fronts are 927x1697 — matching the box to the ARTWORK'S real
     aspect rather than to a guess is HANDOFF §6.14, and `contain` means a
     missing thumbnail degrades to empty space, not to a cropped smear */
  .mpack__art{aspect-ratio:927/1697;border-radius:9px;overflow:hidden;background:#050505;
    display:grid;place-items:center}
  .mpack__art img{width:100%;height:100%;object-fit:contain}
  .mpack__art[data-noart] img{display:none}
  .mpack__p{font-family:var(--display);font-size:19px;font-weight:900;font-style:italic;
    transform:var(--slant);transform-origin:left;color:var(--acc);line-height:1;
    text-align:center;align-self:center}

  /* ── PROFILE ──────────────────────────────────────────────────── */
  .mprof__who{display:flex;align-items:center;gap:13px;margin-bottom:18px}
  /* SCOPED TO THE TEXT BLOCK, not to any span in the row. `.mprof__who
     span` (0,1,1) beat `.avatar` (0,1,0) and reset the avatar to
     display:block, which dropped its letter out of the circle. */
  .mprof__who>div b{display:block;font-size:20px;font-weight:800;color:#fff}
  .mprof__who>div span{display:block;font-size:13px;font-weight:600;color:var(--ink-faint);margin-top:2px}
  .mprof__who .avatar{flex:0 0 auto}
  .mprof__bals{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:18px}
  .mbal{padding:11px 13px;border-radius:14px;background:var(--panel);border:1.5px solid var(--line)}
  .mbal__k{display:block;font-size:11px;font-weight:800;letter-spacing:.10em;color:var(--ink-faint)}
  .mbal__v{display:flex;align-items:center;gap:7px;margin-top:5px;font-size:21px;font-weight:800;color:#fff}
  .mprof__add{padding:14px;border-radius:16px;background:#080808;
    border:1.5px solid rgba(255,255,255,.16);margin-bottom:8px}
  .mprof__lbl{font-family:var(--display);font-size:19px;font-weight:900;font-style:italic;
    transform:var(--slant);transform-origin:left;color:var(--acc)}
  .maddamts{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:11px 0 12px}
  .mprof__note{margin-top:9px;font-size:12.5px;color:var(--ink-faint)}
  .mprof__sec{position:relative;z-index:1;margin:24px 0 12px;
    font-family:var(--display);font-size:25px;font-weight:900;font-style:italic;
    transform:rotate(var(--head-tilt)) var(--slant);transform-origin:left center;color:#fff}
  .mprof__sec .swoosh{width:132px;height:9px;margin:4px 0 0 2px}
  .mrank{display:flex;align-items:center;gap:15px}
  .mrank .rank__shield{width:56px;height:63px}
  .mrank__b{flex:1 1 auto;min-width:0}
  .mrank__b .rank__name{font-size:36px}
  .mrank__b .rank__val{margin:5px 0 9px;font-size:15px}
  .mrank__b .rank__next{margin:9px 0 0;font-size:14px}
  .mcrate{display:flex;align-items:center;gap:14px}
  .mcrate img{width:126px;height:auto;flex:0 0 auto}
  .mcrate .daily__timer{font-size:31px}
  .mprof__rows{display:flex;flex-direction:column;gap:8px}
  .mrow{display:flex;align-items:center;gap:12px;height:52px;padding:0 14px;border-radius:14px;
    background:var(--panel);border:1.5px solid var(--line);color:var(--acc);text-align:left}
  .mrow svg{width:21px;height:21px;flex:0 0 auto}
  .mrow b{flex:1 1 auto;font-size:17px;font-weight:700;color:#fff}
  .mrow__v{flex:0 0 auto;display:flex;align-items:center;gap:8px;
    font-size:14px;font-weight:700;color:var(--ink-faint)}
  /* the Language row's flag — .mrow svg above is a 21px square, and a flag is 3:2.
     The ring is --hairline-str, the sheet's own DARK hairline (stage.css re-inks
     it on .ov__panel): the phone's profile sheet is a near-white panel, where a
     white flag (JP, KR) has no edge at all and the menu's white hairline would
     be just as invisible. */
  .mrow__v .flag{width:21px;height:14px;border-radius:2px;
    box-shadow:0 0 0 .5px var(--hairline-str,rgba(255,255,255,.42))}
  .mrow:active{border-color:var(--acc);background:rgba(var(--acc-rgb),.08)}
  /* the vault grid is three columns of 300px cards on a desktop */
  /* the vault is a 3-up grid of 300px cards on a desktop; two columns of
     slab-aspect tiles is the phone's version of the same page */
  .vault__grid{grid-template-columns:repeat(2,1fr);gap:11px}
  .vcard{padding:10px;gap:8px}
  .vcard__n{font-size:15px}
  .vcard__n span{font-size:13px}
  .vcard__val{font-size:17px}
  .vcard__grade{font-size:12px;padding:3px 9px}
  .vcard__sell{height:40px;font-size:15px}
}

/* ── genuinely short screens: an old SE, or a phone with both browser
      bars up. MEASURED at 320x568 the dock was 185px of a 568px screen —
      33% of the display for four controls — and the solve fell to a 0.614
      pack, BELOW the flat 0.694 it was replacing. On a screen this short
      the pay marks are the first thing to go and the RIP button gives up
      8px; that hands ~50px straight back to the pack. ────────────────── */
@media (max-width:1023px) and (max-height:640px){
  .mpay{display:none}
  .mdock{padding:10px var(--mh-pad) 26px;gap:7px}
  .mturbo,.mwi{height:36px;font-size:15px}
  .multi--mobile{height:36px}
  .multi--mobile .multi__words b,.multi--mobile .multi__words span{font-size:14.5px}
  .multiPicker__step,.multiPicker__number{height:52px}
  .multiPicker__number b{font-size:40px}
  .multiPicker__toggle{height:40px;margin-bottom:8px}
  .multiPicker__done{height:42px;margin-top:10px}
  .mrip{height:54px}
  .mrip__in{font-size:31px}
  .mrip__in::before{height:24px}
}

/* ── phones narrower than a 14/15 (a 360 Android, an SE) ─────────── */
@media (max-width:379px){
  .mtitle__t{font-size:24px}
  .mrip__in{font-size:32px}
  .mturbo,.mwi{font-size:15.5px;gap:6px}
  .multi--mobile{min-width:0}
  .multi--mobile .multi__bolt{display:none}
  .multi--mobile .multi__words{gap:2px}
  .multiPicker__controls{grid-template-columns:58px minmax(0,1fr) 58px;gap:7px}
  .multiPicker__number b{font-size:38px}
  .multiPicker__number span{font-size:13px}
  .mnav__i span,.mnav__lbl{font-size:10px;letter-spacing:.02em}
  .hit__u{max-width:88px}
  .hit__n{max-width:88px}
}

/* ── landscape: there is no room for a pack, so say so ───────────── */
/* Not laziness — measured. pack3d centres the pack at exactly 0.5 of the
   CANVAS WIDTH and exposes no horizontal knob (pack3d.js:1201), so a
   side-by-side landscape layout would draw the pack straight through the
   controls; and a stacked one has 390px of height to hold ~350px of
   chrome. A phone rotated is a phone that cannot show this screen.
   THE BASE RULE COMES FIRST. It was written after the media query, and
   at equal specificity source order wins — so the prompt stayed
   display:none in landscape. Measured at 844x390: elementFromPoint at
   the centre returned .shell. */
.mrot{display:none;position:fixed;inset:0;z-index:90;flex-direction:column;
  align-items:center;justify-content:center;gap:14px;background:#000;text-align:center;padding:24px}
@media (max-width:1023px) and (max-height:480px) and (orientation:landscape){
  .mrot{display:flex}
}
.mrot b{font-family:var(--display);font-size:32px;font-weight:900;font-style:italic;
  transform:var(--slant);color:var(--acc)}
.mrot span{font-size:15px;color:var(--ink-dim);max-width:34ch}

/* ═══════════════════════════════════════════════════════════════════
   THE RIP — the hold, the chrome leaving, the reels, the win panel.
   HANDOFF-RIP §4. Everything below is BlockRips' surface over
   blockpacks.gg's signed-off mechanics; the mechanics live in
   site/rip-hold.js, site/hold.js, site/suspense.js and site/shell-rip.js
   and none of them was redesigned.
   ═══════════════════════════════════════════════════════════════════ */

/* ── 0. .sr-only ───────────────────────────────────────────────────
   rip-hold.js and hold.js both build `<p class="sr-only">` help text and an
   aria-live region — four of them once the panel exists — and this project
   had no such class, so every one of them PAINTED: "Press and hold this
   button until the line closes all the way around it" three times down the
   win panel and once across the stage. Found in the first screenshot of the
   panel, which is what screenshots are for.
   The clip-path shape, not display:none — a hidden element is not announced,
   and the aria-live region has to be in the accessibility tree to speak. */
.sr-only{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* ── 1. THE HOLD RING ──────────────────────────────────────────────
   `.hring`, not `.rip` — this project's `.rip` is the RIP PACK BUTTON,
   a parallelogram with a fixed height and the sticker treatment, and
   `.rip__in` / `.rip__label` are taken too (HANDOFF-RIP §5.1). One
   block serves rip-hold.js and hold.js, which is the property the two-
   file split exists to protect.

   THE WRAPPER CARRIES THE SKEW, NOT THE BUTTON. The traced path is
   measured off offsetWidth/offsetHeight, which ignore transforms — so a
   ring drawn around a skewed button would be an upright rectangle
   around a parallelogram. Skewing the WRAPPER puts the ring in the same
   sheared space as the pill and it traces the real edges for free. The
   button's own skew is cancelled here rather than removed from `.rip`,
   so the desktop rule stays exactly as it was signed off. */
.hring{--hring-gap:7px;--p:0;--e:0;position:relative;display:block;
  transform:skewX(var(--rip-skew)) scale(calc(1 - .03 * var(--e)))}
.hring>.rip,.hring>.mrip{display:block;transform:none;margin-top:0;width:100%}
.hring>.rip:hover{transform:translateY(-2px)}
.hring>.rip:active,.hring>.mrip:active{transform:translateY(2px)}
/* the geometry .rip used to own as a flex child of .stage moves out to the
   wrapper, so the button's box — and every pixel of it — is unchanged */
.hring--rip{width:min(358px,100%);margin-top:clamp(12px,2vh,22px);flex:0 0 auto}
.hring--mrip{width:100%;flex:0 0 auto}
.hring__btn{touch-action:none;-webkit-user-select:none;user-select:none;
  -webkit-touch-callout:none;position:relative;z-index:1}
/* the gold deepens and throws light as the hold builds. --e is eased, --p is
   linear: the ring tells the truth, the glow builds the tension.
   .stick's white outer ring is a box-shadow, so it has to be restated FIRST
   or overriding box-shadow deletes the sticker treatment. */
/* AT REST THIS MUST BE EXACTLY .stick's SHADOW AND NOTHING ELSE. The first
   cut opened at .35 alpha, which put a permanent gold halo on the browse
   button — 765 pixels of it either side, in the pixel diff against the build
   Luke signed off. The glow is a HOLD state, so both extra layers start at
   zero alpha and are drawn only by --e. */
.hring>.rip,.hring>.mrip{
  box-shadow:0 0 0 var(--ring-out-w) var(--ring-out),0 5px 0 0 rgba(11,15,24,.30),
             0 14px 40px -16px rgba(var(--acc-rgb),calc(.85 * var(--e))),
             0 0 calc(6px + 38px * var(--e)) rgba(var(--acc-rgb),calc(.42 * var(--e)))}

/* AN SVG IS A REPLACED ELEMENT AND `inset` ALONE CANNOT SIZE ONE — with
   width/height auto it takes its ratio from the viewBox and only one axis
   comes from the insets. Both axes are written. (The real site measured this
   the hard way: a 352x64 inset box rendered 352x123.) */
.hring__ring{position:absolute;left:calc(-1 * var(--hring-gap));top:calc(-1 * var(--hring-gap));
  width:calc(100% + 2 * var(--hring-gap));height:calc(100% + 2 * var(--hring-gap));
  overflow:visible;pointer-events:none;transform-origin:50% 50%;
  opacity:0;transition:opacity 140ms ease-out;z-index:2}
.hring.is-holding .hring__ring{opacity:1}
.hring__track{fill:none;stroke:rgba(255,255,255,.16);stroke-width:2}
.hring__line{fill:none;stroke:var(--acc);stroke-width:2;stroke-linecap:round;
  filter:drop-shadow(0 0 6px rgba(var(--acc-rgb),.9))}

/* Every label sits in ONE grid cell, so the pill's width never moves — it has
   to not move, because the traced path is measured off that box. The slant
   rides the grid box, not the labels, or it fights their travel.

   `.hring .hring__labels`, NOT `.hring__labels`. This project already carries
   `.rip__in span{display:inline-block}` for the desktop button's own label —
   specificity 0,1,1 — and the labels box IS a span inside it, so a bare
   0,1,0 rule lost `display` and the three labels stacked VERTICALLY inside a
   57px pill with overflow:hidden. RIP PACK went blank in browse and only the
   turbo bolt showed; the middle label (HOLD) happened to land in the visible
   band, which is why the hold state looked perfect and nothing else did.
   Caught by tools-probe-pixdiff.mjs, 21,164 pixels, all of them in the
   button. HANDOFF §6.9's family of bug, in a new place. */
.hring .hring__labels{display:grid;place-items:center;transform:var(--slant)}
.hring__labels .hring__label{grid-area:1/1;white-space:nowrap;display:block;
  opacity:0;transform:translateY(3px);
  transition:opacity 130ms ease-out,transform 130ms ease-out}
.hring__labels .hring__label.is-on{opacity:1;transform:none}
.hring__labels .hring__label[data-l="hold"],
.hring__labels .hring__label[data-l="tap"]{letter-spacing:.09em}

/* the two ends touch: the ring blooms outward and lets go */
@keyframes hringBloom{0%{transform:scale(1);opacity:1}100%{transform:scale(1.17);opacity:0}}
@keyframes hringFlash{0%{filter:brightness(1)}26%{filter:brightness(1.55)}100%{filter:brightness(1)}}
.hring.is-done .hring__ring{opacity:1;animation:hringBloom 420ms ease-out forwards}
.hring.is-done .hring__line{stroke:#FFF;filter:drop-shadow(0 0 14px rgba(var(--acc-rgb),.95))}
.hring.is-done .hring__btn{animation:hringFlash 380ms ease-out}
/* Reduced motion keeps the RING: the hold still runs for everyone (rip-hold.js
   explains why), and a ring that fills in proportion to a gesture is a progress
   indicator, not decoration. Only the travel stands down. */
@media (prefers-reduced-motion:reduce){
  .hring{transform:skewX(var(--rip-skew))}
  .hring.is-done .hring__ring{animation:none;opacity:0}
  .hring.is-done .hring__btn{animation:none}}

/* ── 2. THE CHROME LEAVING ─────────────────────────────────────────
   SLIDE_MS = 520 in rip-hold.js MUST MATCH these transitions: it waits
   exactly that long after setState('rip') before firing onRipComplete,
   so a slower transition would start the reveal over moving furniture. */
.rail,.strip,.turborow,.pay,.hring--rip{
  transition:transform 520ms cubic-bezier(.4,0,.2,1),opacity 340ms ease-out}
[data-state="rip"] .rail--left{transform:translateX(-360px);opacity:0}
[data-state="rip"] .rail--right{transform:translateX(360px);opacity:0}
[data-state="rip"] .strip{transform:translateY(-160%);opacity:0}
[data-state="rip"] .turborow,[data-state="rip"] .pay{transform:translateY(120px);opacity:0}
[data-state="rip"] .hring--rip{
  transform:skewX(var(--rip-skew)) translateY(140px);opacity:0}
[data-state="rip"] .rail,[data-state="rip"] .strip,
[data-state="rip"] .turborow,[data-state="rip"] .pay,
[data-state="rip"] .hring--rip{pointer-events:none}
/* the stopwatch is ground, not chrome — it stays, dimmed, so the room still
   has a floor while the pack has the stage to itself */
.bg__clock{transition:opacity 520ms ease-out}
[data-state="rip"] .bg__clock{opacity:.35}
@media (prefers-reduced-motion:reduce){
  .rail,.strip,.turborow,.pay,.hring--rip{transition:opacity 340ms ease-out}
  [data-state="rip"] .rail--left,[data-state="rip"] .rail--right,
  [data-state="rip"] .strip,[data-state="rip"] .turborow,
  [data-state="rip"] .pay,[data-state="rip"] .hring--rip{transform:none}}

/* ── 2b. THE CHROME LEAVING, ON THE PHONE ──────────────────────────
   Every piece of mobile chrome is position:fixed, so a transform moves
   it without touching the layout — which matters more here than it
   looks. mobile.js watches .mdock and .mcoll with ResizeObservers and
   solves the pack's size against the gap between them; a transform
   changes getBoundingClientRect but NOT the content box, so the
   observers stay quiet. What could still fire placeChrome mid-rip is
   visualViewport (the phone's own bars) — and that is what mobile.js's
   `ripping` guard is for (HANDOFF-RIP §5.3). Hiding the dock outright
   would be worse still: a display:none element measures all zeros, the
   corridor goes degenerate, the guard correctly refuses to publish it
   and the pack silently reverts to the flat 0.694 with no error
   anywhere (§5.4). Nothing here uses display. */
@media (max-width:1023px){
  .mhead,.mcoll,.mdock,.mnav{
    transition:transform 520ms cubic-bezier(.4,0,.2,1),opacity 340ms ease-out}
  [data-state="rip"] .mhead{transform:translateY(-130%);opacity:0}
  [data-state="rip"] .strip{transform:translateY(-260%);opacity:0}
  [data-state="rip"] .mcoll{transform:translateY(-420%);opacity:0}
  [data-state="rip"] .mdock{transform:translateY(125%);opacity:0}
  [data-state="rip"] .mnav{transform:translateY(125%);opacity:0}
  [data-state="rip"] .mhead,[data-state="rip"] .mcoll,
  [data-state="rip"] .mdock,[data-state="rip"] .mnav{pointer-events:none}
  @media (prefers-reduced-motion:reduce){
    .mhead,.mcoll,.mdock,.mnav{transition:opacity 340ms ease-out}
    [data-state="rip"] .mhead,[data-state="rip"] .strip,[data-state="rip"] .mcoll,
    [data-state="rip"] .mdock,[data-state="rip"] .mnav{transform:none}}
}

/* ── 3. THE SUSPENSE REELS ─────────────────────────────────────────
   suspense.js is the real site's, unchanged — its act structure, its
   reel physics, its synthesized ticks and its "spin blind, settle in
   sequence" contract are Luke's signed-off brief and there was no
   reason to re-derive them. THIS is the re-skin (HANDOFF-RIP §4 stage
   3, option b): the cyan-era type and its own font stack are gone, and
   the reels wear Barlow Condensed 900 italic on the house slant, with
   a gold glow, over the halftone ground.

   NO VEIL, deliberately, and that is the real site's call too: the pack
   must not dim while the reels spin. They sit at the sides and below
   it, and the type carries its own dark halo instead. */
.suspense{position:fixed;inset:0;z-index:35;display:grid;
  grid-template-columns:1fr auto 1fr;grid-template-areas:"year rarity company";
  align-items:center;justify-items:center;padding:0 4%;
  background:none;opacity:0;pointer-events:none;
  transition:opacity 300ms cubic-bezier(.22,.8,.36,1)}
.suspense[data-on="1"]{opacity:1}

.sus{display:flex;flex-direction:column;align-items:center;gap:10px;padding:14px 20px;
  /* one act at a time: every zone starts unseen and rises in when its act
     begins — suspense.js adds .sus--in */
  opacity:0;transform:translateY(14px);
  transition:opacity 320ms cubic-bezier(.22,.8,.36,1),transform 320ms cubic-bezier(.22,.8,.36,1)}
.sus--in{opacity:1;transform:none}
.sus--year{grid-area:year}
.sus--company{grid-area:company}
.sus--rarity{grid-area:rarity}
.sus--off{opacity:0!important;transition:opacity 350ms ease-out}  /* null field: fade, never settle */

.sus__label{font-family:var(--ui);font-size:12px;font-weight:700;line-height:1;
  letter-spacing:.32em;text-transform:uppercase;color:var(--ink-faint);white-space:nowrap;
  text-shadow:0 2px 8px #000}

/* the reels: a masked viewport over a travelling strip. The band is wide on
   purpose — a digit passing through a narrow fade zone reads as an EMPTY slot
   ("how can a year only be three digits"). */
.sus__row{display:flex;gap:.06em}
.sus__reel{position:relative;overflow:hidden;
  font-family:var(--display);font-weight:900;font-style:italic;letter-spacing:-.01em;
  text-transform:uppercase;line-height:1.15;height:1.15em;transform:var(--slant);
  -webkit-mask-image:linear-gradient(180deg,rgba(0,0,0,.25),#000 26%,#000 74%,rgba(0,0,0,.25));
  mask-image:linear-gradient(180deg,rgba(0,0,0,.25),#000 26%,#000 74%,rgba(0,0,0,.25))}
.sus__strip{display:flex;flex-direction:column;will-change:transform}
.sus__item{height:1.15em;display:flex;align-items:center;justify-content:center;
  color:var(--ink);white-space:nowrap;padding:0 .06em;
  /* the dark halo separates the type from the lit pack behind it */
  text-shadow:0 2px 6px rgba(0,0,0,.92),0 0 26px rgba(0,0,0,.75)}

/* blind spin: the strip's content is repeated 3x, so one cycle is -33.3333%.
   The rarity reel spins DOWN like a real slot; the year keeps spinning up so
   the numbers read as counting up. */
@keyframes sus-spin{from{transform:translateY(0)}to{transform:translateY(-33.3333%)}}
.sus__strip[data-spin="1"]{animation:sus-spin var(--sus-dur,.72s) linear infinite}
.sus__strip[data-dir="down"][data-spin="1"]{animation-direction:reverse}

/* year and grade the SAME size; rarity the biggest, and at the BOTTOM — below
   the pack, never across its middle */
.sus--year .sus__reel,.sus--company .sus__reel{font-size:clamp(32px,4.8vh,58px)}
.sus--rarity .sus__reel{font-size:clamp(54px,9vh,110px)}
.sus--rarity{align-self:end;margin-bottom:calc(5vh + 30px)}

/* the four rarity colours — the same tokens the LIVE strip's chip outlines and
   the ODDS bars already use, so the reel cannot disagree with them */
.sus__item[data-tier="common"]{color:#00E603}
.sus__item[data-tier="uncommon"]{color:#AEAFBB}
.sus__item[data-tier="rare"]{color:#1E92FF}
.sus__item[data-tier="epic"]{color:#DD2BFF}
.sus__item[data-tier="legendary"]{color:#FFCE1B}   /* the product's legendary gold
  (.hit, .orow--legendary, .rar--legendary, .postrip__grade all use 255,206,27) — this
  reel was the one place legendary painted in the brand pink, which only showed once
  legendary pulls became common (Luke, 14 Sep: "legendary spinner should be gold right?
  not pink?"). Common/rare/epic above already match the product exactly. */

/* the settle: a locked reel stops being a window — the losing rows go, the
   clip box and the mask come off, and the winner's glow blooms with nothing
   to cut it */
.sus__reel[data-set="1"] .sus__strip{will-change:auto}
.sus__reel[data-set="1"]{overflow:visible;-webkit-mask-image:none;mask-image:none}
.sus__reel[data-set="1"] .sus__item:not([data-hit="1"]){opacity:0}
.sus__reel[data-set="1"] .sus__item:not([data-tier]){color:var(--ink)}
.sus__reel[data-set="1"] .sus__item[data-hit="1"]:not([data-tier]){color:var(--acc)}
.sus__reel[data-set="1"] .sus__item[data-hit="1"]{
  text-shadow:0 0 18px currentColor,0 2px 10px rgba(0,0,0,.9);
  filter:drop-shadow(0 0 26px currentColor)}
@keyframes sus-lock{0%{transform:scale(1)}45%{transform:scale(1.08)}100%{transform:scale(1)}}
.sus__reel[data-set="1"] .sus__item[data-hit="1"]{animation:sus-lock 260ms cubic-bezier(.22,.8,.36,1)}

/* the status line rides small under the rarity reel; shell-rip.js never
   writes one (there is no mint to narrate here), so it is normally empty */
.sus__status{grid-area:rarity;align-self:end;margin-bottom:max(1.6vh,12px);
  font-family:var(--ui);font-size:13px;font-weight:600;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-faint);min-height:1.4em;text-align:center}

@media (max-width:1023px){
  .sus__label{letter-spacing:.22em;font-size:11px}
  .suspense{grid-template-columns:1fr 1fr;grid-template-rows:auto 1fr;
    grid-template-areas:"year company" "rarity rarity";padding:13vh 5% 0;align-items:start}
  .sus--year .sus__reel,.sus--company .sus__reel{font-size:clamp(28px,7.4vw,42px)}
  .sus--rarity .sus__reel{font-size:clamp(42px,12vw,68px)}
  .sus--rarity{align-self:end;margin-bottom:calc(13vh + env(safe-area-inset-bottom,0px))}
  .sus__status{margin-bottom:calc(6vh + env(safe-area-inset-bottom,0px))}
}
@media (prefers-reduced-motion:reduce){
  .sus__strip[data-spin="1"]{animation:none}
  .sus__reel[data-set="1"] .sus__item[data-hit="1"]{animation:none}}

/* ── 4. THE POST-RIP PANEL ─────────────────────────────────────────
   HANDOFF-RIP §4 stage 5, and this is the redesign. The live site's ids
   are kept verbatim (#postrip / #prName / #prGrade / #prAgain …) so the
   surface is recognisable to anyone coming from that repo; everything
   visible is BlockRips'. It borrows the overlay vocabulary this project
   already owns — the .ov__panel shell, the halftone corner, the die-cut
   sparkles, the traced swoosh — because a win panel that looked like a
   different product would be the loudest thing on screen. */
.multiPickLayer{position:fixed;inset:0;z-index:4;pointer-events:none}
.multiPickMark{position:absolute;z-index:1;left:0;top:0;width:20px;height:20px;border-radius:50%;
  display:grid;place-items:center;background:var(--acc);color:#FFF;border:2px solid #FFF;
  box-shadow:0 2px 0 rgba(11,15,24,.28),0 0 0 1px var(--key);
  font:900 13px/1 var(--display);opacity:0;transform:translate(-100%,-100%)}
.multiPickMark[data-size="small"]{width:14px;height:14px;border-width:1.5px;font-size:10px}
.multiPickMark[data-size="large"]{width:22px;height:22px;font-size:15px}
.multiPickMark[data-on="1"]{opacity:1}
.multiPickLayer .multiRarity.postrip__grade{position:absolute;display:flex;align-items:center;justify-content:center;
  margin:0;padding:0 6px;height:24px;border-width:2px;border-radius:9px;
  font-size:14px;letter-spacing:0;white-space:nowrap;transform:translate(-50%,-50%);
  box-shadow:0 0 0 2px #FFF,0 3px 0 rgba(11,15,24,.28);opacity:0}
.multiPickLayer .multiRarity[data-size="small"]{height:18px;padding:0 3px;font-size:10px;border-width:1.5px;border-radius:6px;
  box-shadow:0 0 0 1.5px #FFF,0 2px 0 rgba(11,15,24,.28)}
.multiPickLayer .multiRarity[data-size="large"]{height:30px;padding:0 10px;font-size:18px}
html[data-multi-postrip="1"] .multiRarity[data-on="1"]{opacity:1}
html[data-multi-postrip="1"] #stageLayer>canvas{pointer-events:auto}
html[data-brand="rips"][data-multi-reveal="1"] .fit{z-index:5;pointer-events:none}
html[data-brand="rips"][data-multi-reveal="1"] .header,
html[data-brand="rips"][data-multi-reveal="1"] .header__right,
html[data-brand="rips"][data-multi-reveal="1"] .sl-side__brand,
html[data-brand="rips"][data-multi-reveal="1"] .sl-side__toggle,
html[data-brand="rips"][data-multi-reveal="1"] .mhead{pointer-events:auto}
.multiReveal__hud{display:none;position:absolute;left:clamp(16px,2.5vw,34px);top:calc(18px + env(safe-area-inset-top,0px));
  z-index:2;display:flex;align-items:baseline;gap:10px;font-family:var(--display);font-style:italic;
  color:#FFF;text-shadow:0 2px 0 var(--key),0 0 18px rgba(0,0,0,.55);pointer-events:none;
  transition:transform 520ms cubic-bezier(.18,.82,.26,1),opacity 240ms ease-out}
.multiReveal__hud b{font-size:clamp(24px,2.8vw,42px);font-weight:900;color:var(--acc)}
.multiReveal__hud span{font-size:clamp(14px,1.5vw,22px);font-weight:800;text-transform:uppercase}
.multiReveal[data-results="1"] .multiReveal__hud{opacity:.28}
.multiPostrip{position:fixed;inset:0;z-index:86;pointer-events:none;
  padding:calc(16px + env(safe-area-inset-top,0px)) clamp(14px,2vw,30px) calc(16px + env(safe-area-inset-bottom,0px))}
.postrip.multiPostrip{z-index:86}
.multiPostrip[hidden]{display:none!important}
.multiPostrip::before{content:"";display:none}
.multiPostrip__rail{opacity:0;transform:translateY(-50%) translateX(26px)}
[data-multi-postrip="1"] .multiPostrip__rail{transform:translateY(-50%);opacity:1}
.postrip.multiPostrip .multiPostrip__rail{display:flex;flex-direction:column;justify-content:flex-end;
  height:min(520px,calc(100vh - 116px));overflow:visible}
.multiPostrip__selected{visibility:hidden}
.multiPostrip__selected[data-on="1"]{visibility:visible}
.multiPostrip__hint{position:fixed;margin:0;padding:0 6px;text-align:center;
  font:italic 900 24px/1.15 var(--display);letter-spacing:0;text-transform:uppercase;color:#FFF;
  -webkit-text-stroke:3px var(--key);paint-order:stroke fill;
  opacity:0;transition:opacity 320ms ease-out;pointer-events:none}
.multiPostrip__hot{color:var(--acc)}
[data-multi-postrip="1"] .multiPostrip__hint{opacity:1}
@media (max-width:1023px){
  .multiPostrip__hint{font-size:18px;min-height:2.3em;-webkit-text-stroke:2.5px var(--key)}
}
.multiPostrip__actions button:disabled{opacity:.48;cursor:not-allowed}
.postrip{position:fixed;inset:0;z-index:36;pointer-events:none}
.postrip__rail{position:absolute;right:clamp(16px,2.2vw,40px);top:50%;
  width:min(440px,38vw);max-height:calc(100vh - 32px);overflow:auto;
  transform:translateY(-50%) translateX(26px);opacity:0;pointer-events:auto;
  padding:clamp(20px,2.4vw,30px) clamp(20px,2.2vw,28px) clamp(18px,2vh,24px);
  background:#050505;border:2px solid var(--acc);border-radius:20px;
  box-shadow:0 30px 90px -20px #000,0 0 64px -18px rgba(var(--acc-rgb),.5);
  transition:transform 420ms cubic-bezier(.22,.8,.36,1),opacity 320ms ease-out;
  scrollbar-width:none}
.postrip__rail::-webkit-scrollbar{width:0}
[data-postrip="1"] .postrip__rail{transform:translateY(-50%);opacity:1}
/* THE POST-RIP STEPS ASIDE FOR THE CATCH (Luke, 8 Sep 00:30, picking B of the
   three mocks: "buttons sliding away, making sure that they slide straight
   back but never overlap"; Mike 7 Sep 20:25: "buttons on top of buttons").
   site/pokedex.js sets data-catch-up while a catch is mounted: the rail
   (name, values, SELL / VAULT IT / RIP AGAIN) drops 70px and fades, the prize
   rail leaves to the left, SHARE fades; the slab stays, the catch plays over
   the sky. The flag comes off only after the overlay has faded out
   (onDone), so the rail's slide back never shares a frame with VIEW POKÉDEX /
   CONTINUE. The rail's own transition (above) carries both directions. */
html[data-catch-up="1"][data-postrip="1"] .postrip__rail{transform:translateY(-50%) translateY(70px);opacity:0;pointer-events:none}
html[data-layout="stage"][data-catch-up="1"] .poolrail[data-on="1"]{transform:translateX(-120%);opacity:0;pointer-events:none;   /* over stage.css's .poolrail[data-on="1"]{transform:none} */
  transition:transform 420ms cubic-bezier(.22,.8,.36,1),opacity 320ms ease-out}
html[data-catch-up="1"] .postrip__share{opacity:0;pointer-events:none;transition:opacity 320ms ease-out}
@media (max-width:1023px){
  /* the phone's rail is bottom-anchored with no resting transform (the phone
     rules above): it drops away below the fold */
  html[data-catch-up="1"][data-postrip="1"] .postrip__rail{transform:translateY(120px)}
}
/* the halftone corner, same graphic as the overlays */
.postrip__rail::before{content:"";position:absolute;z-index:0;pointer-events:none;
  right:0;top:0;width:min(58%,240px);height:150px;
  background-image:radial-gradient(circle at 2px 2px,rgba(var(--acc-warm-rgb),.42) 1.6px,transparent 1.85px);
  background-size:13px 13px;
  -webkit-mask-image:radial-gradient(120% 130% at 100% 0,#000 0%,transparent 72%);
  mask-image:radial-gradient(120% 130% at 100% 0,#000 0%,transparent 72%)}
.postrip__rail>*{position:relative;z-index:1}
.postrip__rail .spark{z-index:0}
/* behind the title, exactly where .ov__panel puts them. First cut parked
   them bottom-right, which is under the action grid — they never showed. */
.postrip__rail .spark--a{width:26px;height:26px;left:14px;top:12px;opacity:.9}
.postrip__rail .spark--b{width:16px;height:16px;left:6px;top:50px;bottom:auto}

.postrip__head{position:relative;z-index:1}
.postrip__eyebrow{font-family:var(--display);font-size:clamp(26px,2.5vw,34px);
  font-weight:900;font-style:italic;line-height:1;letter-spacing:-.004em;
  transform:rotate(var(--head-tilt)) var(--slant) scaleX(.96);transform-origin:left center;
  color:#FFF;margin-left:2px}
.postrip__eyebrow em{font-style:inherit;color:#FFF}
.postrip__eyebrow b{font-weight:inherit;color:var(--acc)}
.postrip__head .swoosh{width:118px;height:9px;margin:5px 0 16px 3px}

.postrip__name{font-family:var(--display);font-weight:900;font-style:italic;
  font-size:clamp(28px,2.9vw,40px);line-height:1.02;letter-spacing:-.012em;
  transform:var(--slant);transform-origin:left center;color:#FFF;
  text-wrap:balance;margin-bottom:7px}
.postrip__sub{font-family:var(--ui);font-size:12.5px;font-weight:600;
  letter-spacing:.13em;text-transform:uppercase;color:var(--ink-faint);line-height:1.5}

/* the rarity chip. The four colours are the project's own — the LIVE strip's
   chip outlines and the ODDS bars already run on them, so three surfaces
   agree or none do. */
.postrip__grade{display:inline-block;margin:14px 0 4px;padding:6px 15px 5px;
  border-radius:9px;border:2px solid rgba(var(--prc),.75);
  background:rgba(var(--prc),.09);color:rgb(var(--prc));
  font-family:var(--display);font-size:20px;font-weight:900;font-style:italic;
  line-height:1;letter-spacing:.09em;
  box-shadow:0 0 20px -4px rgba(var(--prc),.55),inset 0 0 14px -8px rgba(var(--prc),.9)}
.postrip__grade[data-rarity="common"]{--prc:0,230,3}
.postrip__grade[data-rarity="rare"]{--prc:30,146,255}
.postrip__grade[data-rarity="epic"]{--prc:221,43,255}
.postrip__grade[data-rarity="legendary"]{--prc:255,206,27}

/* THE COLLAPSE IS A PHONE CONTROL AND ONLY A PHONE CONTROL.
   Measured at 1512 / 1280 / 1100 / 1024: the rail's left edge clears the
   slab's right edge by 192 / 70 / 20 / 17px, so on desktop there is nothing
   to get out of the way of. The clip and the inner stack are inert blocks
   here — the children keep their own margins — and the control row is not
   drawn at all. Everything that moves lives in the media query below. */
.postrip__infoclip{display:block}
.postrip__info{display:block}
.postrip__collapse{display:none}

.postrip__rows,.postrip__vals{margin-top:16px}
.postrip__vals{margin-top:12px}
.postrip__r{display:flex;align-items:baseline;justify-content:space-between;gap:14px;
  padding:9px 0;border-bottom:1px solid rgba(255,255,255,.11)}
.postrip__r dt{font-family:var(--ui);font-size:11.5px;font-weight:700;letter-spacing:.19em;
  text-transform:uppercase;color:var(--ink-faint);white-space:nowrap}
.postrip__r dd{font-family:var(--ui);font-size:16px;font-weight:700;color:#FFF;
  text-align:right;min-width:0;overflow-wrap:anywhere}
.postrip__r--big dt{font-size:12px}
.postrip__r--big dd{font-family:var(--display);font-style:italic;font-weight:900;
  font-size:clamp(24px,2.2vw,30px);line-height:1;transform:var(--slant);
  transform-origin:right center;display:inline-block}
.postrip__r--big dd.is-gold{color:var(--acc)}
.postrip__vals .postrip__r:last-child{border-bottom:0}

.postrip__note{margin-top:12px;font-family:var(--ui);font-size:13.5px;
  color:var(--acc-hi);letter-spacing:.02em}

/* ── the actions: a 2x2 grid, heroes on top ──────────────────────
   hold.js wraps every attached button in `.hring.hring--inline`, so the
   GRID CHILD is the wrapper, not the button — everything about the cell
   has to be said on `.hring--inline` or the row collapses. --hring-gap
   drops to 4px here: two 7px rings side by side in a 12px gutter leave
   2px of air and read as one smudge. */
.postrip__row{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:20px}
.postrip__row .hring--inline{--hring-gap:4px;display:block;width:100%;min-width:0}
.postrip__hero{width:100%;height:54px;border-radius:16px;
  transform:none;                       /* the .hring wrapper carries the skew */
  font-family:var(--display);font-size:24px;font-weight:900;font-style:italic;
  letter-spacing:.005em;color:var(--stick-ink);display:flex;align-items:center;justify-content:center}
.hring--inline>button{width:100%}
.postrip__hero .hring__labels{width:100%;padding:0 8px}
.postrip__hero .hring__labels .hring__label{white-space:normal;text-wrap:balance;
  line-height:1.02;font-size:clamp(17px,1.55vw,23px)}
/* the turbo bolt rides RIP AGAIN, exactly as it rides RIP PACK — the whole
   switch is <html data-turbo="1"> and there is no bolt in the markup.
   IT HANGS OFF THE IDLE LABEL, not off the labels box. Every label shares
   one grid cell so the pill's width never moves (the traced ring is measured
   off that box), and making the box a flex row would lay the three labels out
   side by side and widen it under the ring. Only the idle label carries a
   bolt, which is also correct: HOLD does not need one. */
#prAgain .hring__labels .hring__label{display:flex;align-items:center;
  justify-content:center;gap:9px}
#prAgain .hring__label[data-l="idle"]::before{content:'';flex:0 0 auto;width:0;height:22px;opacity:0;
  margin-right:-9px;background:var(--stick-ink);transform:var(--slant);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 32'%3E%3Cpath d='M13.5 0 2 18h7l-1.5 14L22 13h-8l2.5-13z'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 32'%3E%3Cpath d='M13.5 0 2 18h7l-1.5 14L22 13h-8l2.5-13z'/%3E%3C/svg%3E") center/contain no-repeat;
  transition:width 240ms cubic-bezier(.22,.8,.36,1),margin-right 240ms cubic-bezier(.22,.8,.36,1),opacity 150ms ease-out}
[data-turbo="1"] #prAgain .hring__label[data-l="idle"]::before{width:18px;margin-right:0;opacity:1}

/* CLOSE SITS BESIDE RIP AGAIN NOW (Luke, 8 Sep 02:40: "it should say 'Rip
   Again' and then a 'Close' button next to it"), so it must not read as a
   second call to action. It keeps the hero's geometry — same height, radius
   and type, so the pair is even in the two-column row — and takes
   .postrip__side's dark skin, which is this row's own language for the
   secondary (VAULT IT / SELL NOW). .stick paints a pink gradient plus two
   gloss pseudo-elements, hence the overrides and the display:none. */
.postrip__close.stick{background:#0A0A0A;border:2px solid rgba(255,255,255,.3);
  box-shadow:none;color:#FFF}
.postrip__close.stick::before,.postrip__close.stick::after{display:none}
.postrip__close.stick:hover{border-color:var(--acc);color:var(--acc);
  background:var(--fill-side-hover)}
.postrip__close.stick:active{transform:translateY(1px)}

.postrip__side{width:100%;height:46px;border-radius:14px;background:#0A0A0A;
  border:2px solid rgba(255,255,255,.3);color:#FFF;
  font-family:var(--display);font-size:21px;font-weight:800;font-style:italic;
  letter-spacing:.02em;display:flex;align-items:center;justify-content:center;
  transition:border-color .18s ease-out,color .18s ease-out,background .18s ease-out}
.postrip__side:hover{border-color:var(--acc);color:var(--acc);background:var(--fill-side-hover)}
.postrip__side:active{transform:translateY(1px)}
/* the sides are not parallelograms — they keep the press-in, drop the skew */
.postrip__row .hring--inline:has(>.postrip__side){transform:scale(calc(1 - .03 * var(--e)))}
.postrip__side .hring__labels,.postrip__side>span{transform:var(--slant);display:inline-block}
.postrip__side .hring__labels{display:grid;place-items:center}

/* the gesture needs saying once — it is not discoverable, and a user who taps
   and sees nothing happen thinks the button is broken */
.postrip__holdhint{margin-top:12px;text-align:center;font-family:var(--ui);
  font-size:12.5px;letter-spacing:.04em;color:var(--ink-faint)}

/* ── the phone: a bottom sheet, not a rail ───────────────────────
   Same panel, same order, docked to the bottom edge and capped so it
   never becomes a full-screen wall of text on a tall phone. */
@media (max-width:1023px){
  .multiReveal[data-results="1"] .multiReveal__hud{opacity:.24}
  .multiReveal__hud{left:16px;top:calc(12px + env(safe-area-inset-top,0px));gap:7px}
  .multiReveal__hud b{font-size:24px}
  .multiReveal__hud span{font-size:13px}
  .multiPostrip{padding:0}
  .multiPostrip__rail{right:auto;left:0;top:auto;bottom:0;width:100%;
    max-width:560px;margin:0 auto;max-height:70vh;
    border-width:2px 0 0;border-radius:22px 22px 0 0;
    padding:15px 18px calc(14px + env(safe-area-inset-bottom,0px));
    transform:translateY(26px)}
  .postrip.multiPostrip .multiPostrip__rail{height:auto;overflow:auto}
  [data-multi-postrip="1"] .multiPostrip__rail{transform:none}
  .multiPostrip__actions{grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;margin-top:12px}
  .multiPostrip__again,.multiPostrip__close{height:46px;font-size:18px;border-radius:14px}
  .postrip{z-index:72}                 /* above .mnav's 70, which is leaving */
  .postrip__rail{right:auto;left:0;top:auto;bottom:0;width:100%;
    max-width:560px;margin:0 auto;max-height:82vh;
    border-width:2px 0 0;border-radius:22px 22px 0 0;
    padding:18px 18px calc(18px + env(safe-area-inset-bottom,0px));
    transform:translateY(26px);
    box-shadow:0 -22px 70px -18px #000,0 0 60px -20px rgba(var(--acc-rgb),.5)}
  [data-postrip="1"] .postrip__rail{transform:none}
  /* EVERY ROW OF THIS SHEET IS SLAB THE USER CANNOT SEE. The reveal ends
     with the slab fixed at frame 0's place (pack3d asserts it — "the
     animation always needs to start in the same place"), so the panel cannot
     be moved out of its way and the only lever is its own height. Measured at
     390x844: 572px of sheet, 68% of the screen, with the graded label just
     clear of it. This block gives ~60px back and keeps the label, the card's
     top third and the whole panel. */
  .postrip__eyebrow{font-size:24px}
  .postrip__head .swoosh{width:104px;margin:4px 0 8px 3px}
  .postrip__name{font-size:clamp(23px,6.6vw,30px);margin-bottom:5px}
  .postrip__sub{font-size:11px;letter-spacing:.1em;line-height:1.45}
  .postrip__grade{margin:10px 0 2px;padding:5px 13px 4px;font-size:18px}
  .postrip__rows,.postrip__vals{margin-top:9px}
  .postrip__r{padding:6px 0}
  .postrip__r dd{font-size:15px}
  .postrip__r--big dd{font-size:22px}
  .postrip__row{margin-top:12px;gap:10px}
  .postrip__hero{height:48px}
  .postrip__side{height:42px}
  .postrip__holdhint{margin-top:9px;font-size:11.5px}
  .postrip__rail{padding:15px 18px calc(14px + env(safe-area-inset-bottom,0px))}
  .postrip__rail .spark{display:none}

  /* ── THE INFO COLLAPSE ─────────────────────────────────────────
     Luke, 29 Aug: a control that drops the card's info down so you can
     actually see the card. The clip animates grid-template-rows 1fr -> 0fr,
     so the PANEL hands the height back and the buttons keep their place at
     the bottom of the screen; the inner stack translates DOWN and fades, so
     it visibly leaves behind the button row instead of blinking out. The
     chevron rotates to point back up. The mirrored sellback line keeps the
     one number worth keeping while the stack is away. Same motion as the
     live site's, which he signed off ("It's good, it's cool"). */
  .postrip__infoclip{display:grid;grid-template-rows:1fr;
    transition:grid-template-rows 420ms cubic-bezier(.22,.8,.36,1)}
  .postrip__info{overflow:hidden;min-height:0;
    transition:transform 420ms cubic-bezier(.22,.8,.36,1),opacity 300ms ease-out}
  .postrip[data-collapsed="1"] .postrip__infoclip{grid-template-rows:0fr}
  .postrip[data-collapsed="1"] .postrip__info{transform:translateY(64px);opacity:0;
    pointer-events:none}
  .postrip__collapse{display:flex;align-items:center;justify-content:flex-end;
    gap:12px;min-height:34px;margin-top:2px}
  .postrip__sellmini{margin-right:auto;font-family:var(--display);font-size:13px;
    font-weight:900;font-style:italic;letter-spacing:.05em;color:var(--acc);
    transform:var(--slant);font-variant-numeric:tabular-nums;
    text-shadow:0 1px 10px rgba(0,0,0,.85)}
  .postrip__sellmini[hidden]{display:none!important}
  .postrip__collapsebtn{display:inline-flex;align-items:center;gap:8px;cursor:pointer;
    background:none;border:0;padding:8px 2px;color:var(--ink-dim);
    font-family:var(--display);font-size:14px;font-weight:800;font-style:italic;
    letter-spacing:.13em;text-transform:uppercase;
    transition:color .16s ease-out}
  .postrip__collapsebtn:active{color:var(--acc)}
  .postrip__collapsebtn>span{transform:var(--slant);display:inline-block}
  .postrip__collapsebtn svg{display:block;flex:0 0 auto;
    transition:transform 300ms cubic-bezier(.22,.8,.36,1)}
  .postrip[data-collapsed="1"] .postrip__collapsebtn svg{transform:rotate(180deg)}
  /* reduced motion: no travel — the stack holds position and fades fast, and
     the height settles in the same beat */
  @media (prefers-reduced-motion:reduce){
    .postrip__infoclip{transition-duration:140ms}
    .postrip__info{transition-property:opacity;transition-duration:140ms;transform:none}
    .postrip[data-collapsed="1"] .postrip__info{transform:none}
  }
}
@media (prefers-reduced-motion:reduce){
  .postrip__rail,[data-postrip="1"] .postrip__rail{
    transition:opacity 320ms ease-out;transform:translateY(-50%)}
  @media (max-width:1023px){
    .postrip__rail,[data-postrip="1"] .postrip__rail{transform:none}}
}

/* ── 5. THE MUTE ───────────────────────────────────────────────────
   The header speaker was decoration until the rip landed; shell-rip.js
   switches it now. Muted = the sound waves dim and a slash crosses the
   button, which is the only reading of that icon nobody has to learn. */
.hbal--icon{position:relative;overflow:visible}
.hbal--icon svg path:last-child{transition:opacity .16s ease-out}
.hbal--icon[data-muted="1"] svg path:last-child{opacity:.18}
.hbal--icon[data-muted="1"]::after{content:"";position:absolute;left:9px;right:9px;top:50%;
  height:2px;margin-top:-1px;border-radius:2px;background:#E8E8E8;
  transform:rotate(-36deg);pointer-events:none}

/* ── 6. THE TOAST ──────────────────────────────────────────────────
   Selling and vaulting both end with the panel LEAVING, so the panel
   cannot carry its own confirmation — the first cut held it on screen
   for 1100ms so the line could be read, which is 1100ms of a screen
   that looks stuck. This says what happened and outlives the panel,
   which is what the live site's WALLET.toast does in the same breath
   as hide() + setState('browse'). */
.toast{position:fixed;left:50%;bottom:clamp(20px,4vh,44px);z-index:80;
  transform:translateX(-50%) translateY(14px);opacity:0;pointer-events:none;
  max-width:min(440px,calc(100vw - 32px));padding:13px 22px;
  background:#050505;border:2px solid var(--acc);border-radius:14px;
  box-shadow:0 20px 60px -18px #000,0 0 40px -16px rgba(var(--acc-rgb),.55);
  font-family:var(--ui);font-size:15px;font-weight:600;color:var(--ink);
  text-align:center;letter-spacing:.01em;
  transition:opacity 220ms ease-out,transform 320ms cubic-bezier(.22,.8,.36,1)}
.toast[data-on="1"]{opacity:1;transform:translateX(-50%)}

/* THE TOAST IS ONLY THAT HIGH ON THE OPEN PAGE (Luke, 11 Sep: "when the toasts
   pop up to say 'clan invite accepted' or 'you accepted the trade', that comes
   up too high. It should only be that high when a toast is popping up when you
   are on the open page. On the open page, it's that high because it's covering
   the buttons").
   The site's one notice is #walletToast (wallet.js:1408, and notify.js paints
   the clan and trade lines into the same element). Its bottom was the literal
   176px in both files — the height that clears the lobby's RIP PACK cluster and
   the phone dock. Measured at 390x844: identical on the lobby and on
   /leaderboard, 623-674 with 170px of empty screen under it, where the only
   furniture left is the 58px bottom nav.
   NOT --mh-dock. That variable belongs to the `.toast` CLASS (below), which is
   worn by the rip-flow and achievements FALLBACK toasts only — the ones that
   fire when WALLET.toast is missing. #walletToast carries no class at all, so
   nothing in that rule has ever applied to the toast Luke is describing.
   WHERE THE NUMBER IS ACTUALLY APPLIED: stage.css §6.34, which sets
   `bottom:calc(var(--toast-bot,176px) * var(--sl-s,1))!important` and beats the
   inline `bottom` both JS files write. This variable is read there. The two JS
   files pass the same var() inline so the fallback path agrees with the rule.
   A page is the whole of "not the lobby": <html data-page> is set by
   site/page.js for every one of them and by nothing else. */
/* 12, not 34 (Luke, 14 Sep 2026: "when you scroll, it should stick to the bottom
   menu ... it should stay where it is on your screen"). It is position:fixed, so it
   already holds still; the 34px gap is what made it read as floating over the page's
   buttons rather than sitting on the dock. Nothing lifts it off what is under it —
   his rule for that case is that you scroll and the buttons come out from behind it. */
html[data-page]{--toast-bot:calc(var(--mh-nav, 0px) + var(--mh-bot, 0px) + 12px)}
@media (max-width:1023px){
  /* CLEAR OF THE WHOLE DOCK, not just the nav bar. At nav+16 it landed
     across RIP PACK — the one control the user is most likely to want next.
     --mh-dock is published by mobile.js from the dock's measured height. */
  .toast{bottom:calc(var(--mh-nav) + var(--mh-bot) + var(--mh-dock,150px) + 12px);
    font-size:14px;padding:11px 18px}
}
@media (prefers-reduced-motion:reduce){
  .toast{transition:opacity 220ms ease-out;transform:translateX(-50%)}
}

/* ── 7. WHAT'S INSIDE — the grail pool ─────────────────────────────
   The live site's grailpool.js, in this brand: "a full grid of the
   pack's REAL card photos, VALUE SHOUTING IN THE BLOCK FONT, exact
   count, per pack." So the value is the loudest thing in the tile —
   display face, italic, gold — and the tile's outline is the rarity,
   the same four tokens the LIVE strip's chips and the ODDS bars use.
   It replaces the odds table on both shells; the odds table is still
   one tap away under ODDS, which is where it belongs. */
.grail__grid{display:grid;gap:clamp(12px,1.5vw,20px);
  grid-template-columns:repeat(auto-fill,minmax(clamp(150px,15vw,196px),1fr))}
.gcard{position:relative;display:flex;flex-direction:column;gap:8px;padding:11px;
  border-radius:15px;background:#0A0A0A;text-align:left;cursor:pointer;
  border:1.5px solid rgba(var(--gc,253,193,7),.55);
  box-shadow:0 0 24px -14px rgba(var(--gc,253,193,7),.7);
  transition:border-color .16s ease-out,transform .16s ease-out,box-shadow .16s ease-out}
.gcard:hover{border-color:rgba(var(--gc,253,193,7),.95);transform:translateY(-3px);
  box-shadow:0 0 34px -10px rgba(var(--gc,253,193,7),.85)}
.gcard:active{transform:translateY(0)}
.gcard[data-rarity="common"]{--gc:0,230,3}
.gcard[data-rarity="rare"]{--gc:30,146,255}
.gcard[data-rarity="epic"]{--gc:221,43,255}
.gcard[data-rarity="legendary"]{--gc:255,206,27}
/* the WHOLE slab, never a crop — same rule and the same ratio as .vcard__art:
   these are photographs of slabs (681x1136), not of cards */
.gcard__art{aspect-ratio:681/1136;border-radius:9px;overflow:hidden;background:#050505;
  border:1px solid rgba(255,255,255,.13);display:grid;place-items:center}
.gcard__art img{width:100%;height:100%;object-fit:contain}
.gcard__val{font-family:var(--display);font-size:clamp(24px,2.2vw,31px);font-weight:900;
  font-style:italic;line-height:1;letter-spacing:-.01em;color:rgb(var(--gc,253,193,7));
  transform:var(--slant);transform-origin:left center;
  text-shadow:0 0 22px rgba(var(--gc,253,193,7),.45)}
.gcard__n{font-family:var(--ui);font-size:14px;font-weight:700;line-height:1.2;color:#FFF;
  overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.gcard__g{font-family:var(--ui);font-size:12px;font-weight:600;letter-spacing:.13em;
  text-transform:uppercase;color:var(--ink-faint)}
.grail__note{padding:20px 0;color:var(--ink-dim);font-size:16px}
.ov--grail .ov__sub{margin-top:9px}
.ov--grail .ov__sub b{color:var(--acc);font-weight:800}
@media (max-width:1023px){
  .grail__grid{grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:11px}
  .gcard{padding:9px;border-radius:13px;gap:6px}
  .gcard__val{font-size:23px}
  .gcard__n{font-size:13px}
  .gcard__g{font-size:11px}
}


/* ═══════════════════════════════════════════════════════════════════
   V3 — BRIGHT GROUND OVERRIDES

   On a dark ground almost everything could be white on near-black and it
   worked. On a bright sky none of that holds: white text on #82C0F1 has no
   contrast, and a panel that was "slightly lighter than black" is now
   "slightly lighter than the sky". So the rules below re-decide, per surface,
   whether it sits ON the sky (dark ink, or white with a black comic stroke)
   or IN a navy chip (white ink). Appended rather than edited in place so the
   v1/v2 rules above stay readable as one system.
   ═══════════════════════════════════════════════════════════════════ */

/* headings sit on the sky — white with a black comic stroke, exactly the
   reference's headline treatment. paint-order is what puts the stroke
   BEHIND the fill; without it the stroke eats half the letterform. */
.rail__title{color:#FFF;-webkit-text-stroke:6px var(--key);paint-order:stroke fill;
  text-shadow:0 4px 0 rgba(11,15,24,.28)}

/* nav + small labels sit on the sky and go dark */
.navlink{color:var(--ink)}
.navlink:hover{color:#000}
.navlink.is-active{color:var(--acc)}
.nav__ink{background:var(--acc)}
.rank__next,.daily__lbl,.ladder li{color:var(--ink)}
.rank__next b{color:var(--acc-lo)}
.daily__t{color:#FFF;-webkit-text-stroke:5px var(--key);paint-order:stroke fill}
.daily__timer{color:#FFF;-webkit-text-stroke:5px var(--key);paint-order:stroke fill}

/* every chip becomes a navy pill with a black keyline — the reference's
   check-list chips are exactly this */
.hbal{background:var(--navy);border:2.5px solid var(--key);color:var(--ink-panel)}
.hit{background:var(--navy);border:2.5px solid var(--key)}
.hit__n{color:var(--ink-panel)}
.strip__live{background:var(--navy);border:2.5px solid var(--key)}
.strip{background:transparent}
.tier:not(.is-on){background:var(--navy);border:2.5px solid var(--key);color:var(--ink-panel)}
.tier:not(.is-on):hover{box-shadow:0 5px 0 0 rgba(11,15,24,.30)}
.turbo{background:var(--navy);border:2.5px solid var(--key);color:var(--ink-panel)}
.picker__btn{color:var(--ink)}
.packscroll svg{color:var(--ink)}

/* WHAT'S INSIDE takes the reference's yellow, with dark ink on it */
.whatsinside{background:var(--yellow);border:2.5px solid var(--key);
  box-shadow:0 5px 0 0 rgba(11,15,24,.30)}
.whatsinside,.whatsinside b{color:var(--key)}
.whatsinside:hover{background:#FFFFC4}
.whatsinside:hover b{color:var(--key)}

/* pay row: white cards with a keyline, not dark ones */
.pay button{background:#FFF;border:2.5px solid var(--key);color:var(--key)}
.pay button:hover{background:var(--sky-pale)}

/* the rank track needs to read against the sky, not against black */
.rank__bar{background:var(--navy)}

/* the stopwatch is ground furniture and was a near-black; on sky it becomes a
   deeper blue so it still reads as a watermark rather than a hole */
.bg__clock{color:#69A8EE}

/* ── contrast pass: anything sitting on the sky ────────────────────
   Caught by looking at the render, not by the probes — a probe compares
   colours between builds, it does not know that #CFCFCF on #82C0F1 is
   unreadable. These four were all fine on near-black and are not on sky. */
.rank__val{color:var(--ink-dim)}
.rank__val b{color:var(--key)}
.odds{color:var(--key)}
.odds svg path{stroke:var(--acc-lo)}
.navlink.is-active{color:var(--acc-lo)}
.nav__ink{background:var(--acc-lo)}
.picker__btn{color:var(--ink)}

/* Both of these are AUTO-WIDTH, so thickening their keyline from 1.5px to
   2.5px grew them by 2px each and pushed .header__right and .pay 4-6px wide.
   The style wants the heavier line, so the padding gives the 1px per side
   back and the outer box lands exactly where v1 put it. */
.hbal{padding:0 15px}
.pay button{padding:0 clamp(13px,1.6vw,21px)}

/* ── v3 fixes, round 2 ─────────────────────────────────────────────

   THE CLIPPED HEADINGS. `.rail{overflow-y:auto}` and
   `.rail--left{overflow:hidden}` clip flush at the rail edge, and the comic
   stroke extends 3px OUTSIDE the glyph — so "CHOOSE A PACK" lost its top and
   "RANK" / "DAILY CRATE" lost their left. Padding the rails would fix it but
   would move every box in them. `overflow:clip` + `overflow-clip-margin`
   expands the clip region WITHOUT affecting layout or scrolling, which is
   exactly the tool for this. */
.rail--left{overflow:clip;overflow-clip-margin:14px}
.rail--right{overflow-x:clip;overflow-clip-margin:14px}

/* the stroke is a touch lighter now it is not fighting a clip edge */
.rail__title{-webkit-text-stroke:5px var(--key)}

/* THE PICKER. Its rule and chevron were white-on-near-black; on sky they
   vanished. The label was 16.5px of black on a halftone, which is the
   "small black text is hard to read" note — the DOTS behind it are what cost
   the legibility, so it gains weight and size as well as contrast. */
.picker__rule{background:linear-gradient(90deg,rgba(11,15,24,.62),rgba(11,15,24,.14))}
.picker__chev{color:var(--key)}
.picker__chev path{stroke:var(--key)}
.picker__label{font-size:18px;font-weight:800;color:var(--key)}

/* THE RANK LADDER. 12px of #B4B4B4 became 12px of dark on a dotted sky —
   still the hardest text on the page to read. Heavier and darker; the size
   goes up only 1px because these sit in a space-between row and a bigger
   jump starts moving the shields. */
.ladder li{font-size:13px;font-weight:900;color:var(--key)}
.rank__next{font-weight:800;color:var(--key)}
.rank__val{color:var(--key)}
.daily__lbl{font-weight:800;color:var(--key)}

/* the 13px ladder labels grew the row from 61px to 63px and pushed .odds,
   .crate, .daily and the timer down 2px each. The row gives it back. */
.ladder{margin-bottom:24px}

/* THE PAY ICONS WERE INVISIBLE. Apple/Google/card are inline SVG at #F2F2F2
   and #E8E8E8 — light grey, which was right on a near-black button and is
   nothing at all on v3's white one. :not([fill="none"]) matters: the card
   icon is stroke-only and filling its paths would blob it. */
.pay button svg [fill]:not([fill="none"]){fill:var(--key)}
.pay button svg [stroke]{stroke:var(--key)}

/* ── dark surfaces on a bright site ────────────────────────────────
   --ink means "text on the SKY" in v3, so it is dark. The toast and the
   post-rip panel are near-black cards, and they inherited it — dark text on
   #050505, i.e. unreadable. They take --ink-panel like every other dark chip.
   Same root cause as the contrast pass above, one layer further in. */
.toast,.postrip__rail{color:var(--ink-panel)}
.toast{border-color:var(--key);box-shadow:0 8px 0 0 rgba(11,15,24,.34)}
.postrip__rail{border-color:var(--key)}

/* THE PANEL OVERLAPPED THE HEADER. It is vertically centred with a
   max-height of the full viewport, so a tall panel runs under the logo and
   the balances. Dropping the hold hint freed a line; the rest is giving the
   header its height back and re-centring in what is left. */
.postrip__rail{top:calc(50% + 40px);max-height:calc(100vh - 148px)}

/* THE CRATE: CUT OUT, WITH ITS GLOW GIVEN BACK AS LIGHT.
   Three attempts, and the lesson is in the middle one. Cutting it out went
   FLAT — not because the mask was bad but because the bloom lives in the
   black plate, so removing the plate removed the light. Framing it put the
   light back but read as a box sitting on the page. The bloom is light, so
   it comes back as light: a drop-shadow in the crate's own hue, which follows
   the alpha silhouette rather than the element box, so there is no edge to
   see. No panel, no fringe, and the object still sits IN the scene. */
/* THE CRATE IS NOW GENERATED ART, not the 3D render lifted off its plate
   (tools-crate-gen.py). It ships with real alpha and its own black keyline,
   so it needs no glow standing in for a lost bloom and no frame standing in
   for a missing edge — just the hard comic drop the other v3 surfaces have. */
.crate{filter:drop-shadow(0 5px 0 rgba(11,15,24,.30))}

/* ── contrast audit findings (tools-probe-contrast.mjs) ────────────
   Same root cause a third and fourth time: --ink and --ink-dim mean "on the
   SKY" in v3, and every dark panel that inherits them goes dark-on-dark.
   The franchise dropdown measured 1.13:1 — invisible unless highlighted,
   which is exactly what Luke saw. One --ink-dim cannot serve both grounds
   (it is 5.1:1 on sky and 1.5:1 on navy), so the dark panels are named. */
.picker__menu,.picker__opt{color:var(--ink-panel)}
.picker__opt:hover,.picker__opt[aria-selected="true"]{color:var(--acc-hi)}
.hit__grade{color:#AEB7C6}
.hit__m i{color:#9AA3B2}

/* the landscape-rotate overlay is a black sheet, so its body copy needs the
   panel ink too (mobile-only surface; caught by the audit, not by an eye) */
.mrot span{color:#C4CBD6}

/* ═══════════════════════════════════════════════════════════════════
   V3 — THE POST-RIP PAGE

   It was still wearing v1's clothes: two dark cards floating on a bright
   comic ground, which is exactly the "boxes" note. The real site does not
   box either side — both are content standing on the stage — and the pop
   ground makes that not just possible but necessary, because a dark panel
   on sky reads as a hole punched in the page.

   So the panels come off and every surface is re-decided the same way the
   home screen was: headings are white with the black comic stroke, body
   copy is dark on the sky, and the only filled shapes left are the sticker
   buttons and the rarity chip.
   ═══════════════════════════════════════════════════════════════════ */
.postrip__rail{background:none;border:0;box-shadow:none;
  padding:0 clamp(10px,1.2vw,18px);
  /* the buttons were cut off: this is a fixed-height scroll box, and with
     v3's chunkier type the stack no longer fitted. Losing the card padding
     and the panel chrome gives the height back. */
  top:calc(50% + 30px);max-height:calc(100vh - 116px)}
.postrip__rail::before{display:none}          /* the card's inner glow */

.postrip__eyebrow{color:#FFF;-webkit-text-stroke:5px var(--key);paint-order:stroke fill}
.postrip__eyebrow em,.postrip__eyebrow b{color:var(--acc)}
.postrip__name{color:#FFF;-webkit-text-stroke:6px var(--key);paint-order:stroke fill}
.postrip__note{color:var(--key);font-weight:800}

/* the info stack: dark ink on sky, with solid rules instead of hairlines
   that disappear against a halftone */
.postrip__r{border-color:rgba(11,15,24,.30)}
.postrip__r dt{color:var(--key);font-weight:800}
.postrip__r dd{color:var(--key);font-weight:800}
.postrip__r--big dt{color:var(--key)}
.postrip__r--big dd{color:#FFF;-webkit-text-stroke:5px var(--key);paint-order:stroke fill}
.postrip__r--big dd.is-gold{color:var(--acc-hi)}

/* the rarity chip keeps its load-bearing colour and gains the keyline every
   other v3 control wears, so it reads as a sticker rather than an outline */
.postrip__grade{border-width:3px;color:var(--key);
  box-shadow:0 0 0 3px #FFF,0 4px 0 0 rgba(11,15,24,.28)}
.postrip__grade[data-rarity="common"]{background:#00E603}
.postrip__grade[data-rarity="rare"]{background:#1E92FF}
.postrip__grade[data-rarity="epic"]{background:#DD2BFF}
.postrip__grade[data-rarity="legendary"]{background:#FFCE1B}

/* the collapse control and the sparkles were tuned for a black card */
.postrip__collapse{color:var(--key)}
.postrip__rail .spark{display:none}

/* ═══════════════════════════════════════════════════════════════════
   V3 — THE OVERLAY VIEWS (vault, odds, info, what's inside)

   These were still v1: a near-black sheet with a thin pink border, floating
   over a bright comic page. That is the "off-brand" note, and the contrast
   audit found the other half of it — 13 surfaces of --ink-dim on near-black,
   because --ink-dim means "on the SKY" in v3.

   Both are fixed at the container: the sheet becomes a navy card with the
   same 3px black keyline and hard drop every other v3 surface wears, and it
   REDEFINES --ink-dim / --ink-faint for its own subtree. Redefining the token
   on the container fixes every descendant at once instead of naming them.
   ═══════════════════════════════════════════════════════════════════ */
.ov__panel{
  --ink-dim:#A7B0BE; --ink-faint:#8A94A4;
  background:var(--navy);border:3px solid var(--key);
  box-shadow:0 14px 0 0 rgba(11,15,24,.34),0 40px 90px -30px rgba(11,15,24,.55)}
.ov{background:rgba(11,15,24,.55)}

/* headings inside the sheets get the same comic stroke as the page's */
.ov__h{color:#FFF;-webkit-text-stroke:5px var(--key);paint-order:stroke fill}
.ov__h em,.ov__h b{color:var(--acc)}

/* the franchise tabs read as chips, not as text links */
.otab{color:#A7B0BE;border:2px solid transparent}
.otab:hover{color:#FFF}
.otab.is-on{color:var(--key);background:var(--yellow);border-color:var(--key)}

/* the odds rows keep their load-bearing tier colours; only the ink moves */
.orow__v,.orow__p{color:#D4DAE4}
.orow__t{color:#FFF}

.vault__count{color:#A7B0BE}
.ov__foot{color:#A7B0BE}

/* ── WCAG AA pass (tools-probe-contrast.mjs 4412 4.5) ──────────────
   3:1 was too lenient for small type on a halftone. At AA the real
   offenders are: the pull's meta line (3.05:1 at 12.5px — the "hard to
   read" note), the accent pink used as INK rather than as a fill, and the
   deep pink on navy chips. Pink stays the brand colour; it just stops being
   asked to carry small text. */
.postrip__sub{color:var(--key);font-size:14px;font-weight:800;opacity:1}
.navlink.is-active{color:var(--acc-deep)}
.rank__next b{color:var(--acc-deep)}
.strip__live,.vtab.is-on,.hit__m b{color:var(--acc-hi)}

/* ── THE SUSPENSE REELS ────────────────────────────────────────────
   Built for a black stage: the labels are --ink-faint with a BLACK halo and
   the digits carry a heavy black text-shadow, both of which are there to
   separate type from a lit pack in the dark. On sky the halo reads as dirt
   and the labels wash out. v3 swaps the halo for the comic stroke the rest
   of the page uses, and the type goes up substantially — this is the moment
   the whole rip is building to, so it should be the biggest thing on screen
   after the pack itself. */
.sus__label{color:var(--key);font-size:15px;font-weight:800;opacity:.78;
  text-shadow:none;letter-spacing:.30em}
.sus__item{text-shadow:none;-webkit-text-stroke:7px var(--key);paint-order:stroke fill}

/* MUCH bigger, but the columns are 1fr/auto/1fr with the pack in the middle,
   so the side reels grow into their own columns and never reach it. */
.sus--year .sus__reel,.sus--company .sus__reel{font-size:clamp(48px,7.4vh,92px)}
/* the rarity is centre-column and bottom-aligned, i.e. UNDER the pack — the
   margin is what keeps it off the pack as the type grows */
.sus--rarity .sus__reel{font-size:clamp(64px,11vh,138px)}
.sus--rarity{margin-bottom:calc(3vh + 8px)}

/* the locked reel's glow is a COLOURED BLOOM plus a black halo — both read as
   fuzz on a bright ground, and the bloom was washing out the comic stroke.
   Matched specificity so it actually wins. */
.sus__reel[data-set="1"] .sus__item[data-hit="1"]{
  text-shadow:none;
  filter:drop-shadow(0 5px 0 rgba(11,15,24,.30));
  -webkit-text-stroke:8px var(--key);paint-order:stroke fill}

/* last of the AA pass: the loader sits on the sky, and the emphasised values
   inside the navy sheets were the base accent (3.88:1) rather than the light
   one. Pink stays; it just uses the rung that reads on navy. */
.loader__txt{color:var(--key);opacity:.85}
.ov__panel b,.ov__sub b,.vault__count b,.postrip__rail b{color:var(--acc-hi)}

/* .ov--grail .ov__sub b is more specific than .ov__sub b, so the line above
   never reached the "40 cards in the pool" count */
.ov--grail .ov__sub b{color:var(--acc-hi)}

/* ── THE DAILY CRATE COUNTDOWN WAS CLIPPED ─────────────────────────
   Measured: the right rail overflowed its own height by 45px and the timer's
   bottom sat 8px past the viewport, so "Next crate" showed and the digits did
   not. v3's chunkier type (13px ladder, stroked headings, the framed crate)
   is what used the room up.
   The crate itself is NOT shrunk — Luke likes it bigger. The 48px comes back
   out of the gaps above it instead, which is the "move everything up" he
   asked for. */
.rail--right .swoosh{margin-bottom:8px}
.rank__next{margin-bottom:14px}
.ladder{margin-bottom:12px}
.crate{margin-bottom:6px}
.rail--right{padding-bottom:8px}

/* ── AND IT WAS STILL BELOW THE FOLD ON A SHORT WINDOW ─────────────
   The 48px reclaimed above is enough at 820px of viewport and not at 750,
   which is what Chrome actually gets on a 15" Air. Measured there: the
   timer's box ended at 767 against a 750px viewport, so "Next crate" showed
   and the digits did not — and the page does not scroll to reach them
   (documentElement.scrollHeight === innerHeight), so they are simply gone.
   Taking another 20px out of the gaps would start crushing the crate, which
   Luke wants big.
   So the stack stops being a stack. "DAILY CRATE" and its swoosh keep the
   left; "Next crate" and the timer move to the right of them, over the
   .bg__clock art that was already sitting in that corner doing nothing.
   Luke, 1 Sep: "bring where it says Daily Crate down slightly and then put
   Next crate and the time over to the right, so it's overlapping the clock".
   The mobile shell has done exactly this since the phone pass —
   .mcrate is a flex row of art + text — so this is the desktop catching up
   rather than a new idea.
   Scoped to .rail--right so .mcrate keeps its own rules. The timer already
   carries tabular-nums (line 557), so right-aligning it cannot make the
   digits jitter as they tick. */
.rail--right .daily{position:relative;margin-top:18px;min-height:70px}
.rail--right .daily .swoosh{margin-bottom:0}
.rail--right .daily__lbl,
.rail--right .daily__timer{position:absolute;right:4px;margin:0;text-align:right;white-space:nowrap}
.rail--right .daily__lbl{top:0}
.rail--right .daily__timer{top:24px}

/* ── THE RIGHT RAIL'S HEADINGS WERE STILL LOSING THEIR LEFT EDGE ───
   Luke, 1 Sep: "the left side of the R in Rank and the D in Daily Crate are
   cut off". They were, by 3px, and the fix above at "THE CLIPPED HEADINGS"
   has been inert on this rail since the day it was written.
   WHY. That block gives .rail--left `overflow:clip` on BOTH axes, which is
   what makes overflow-clip-margin apply, and gives .rail--right only
   `overflow-x:clip` — while .rail (line 252) sets `overflow-y:auto`. When one
   axis scrolls, the other's `clip` computes to `hidden`, and clip-margin does
   nothing on hidden. Measured: getComputedStyle(.rail--right).overflowX is
   "hidden", and the leftmost keyline pixel of RANK sits at x1084, exactly the
   rail's padding edge, where the glyph wants x1081.
   The 3px is the 5px comic stroke's outer half. So the headings move in by
   the width of their own stroke and the rail keeps its scrolling — which the
   left rail already does the same way, .rail--left .rail__title carries a
   16px indent (line 256) and nobody has ever read it as misaligned. The
   swooshes are hand-drawn underlines and were never flush with the type
   there either. */
.rail--right .rail__title,
.rail--right .daily__t{margin-left:4px}

/* ── LANGUAGE (site/i18n.js, PLAN-I18N-16SEP) ─────────────────────────
   Luke, 16 Sep: "the homepage is a little globe icon ... put that in the
   profile dropdown rather than live right on the homepage. Also put a flag
   next to each language, not just the plain text." Then, on the signed-out
   gap that left: "ok signed out the little icon can be there."

   SO THERE ARE TWO TRIGGERS AND ONE LIST. #acctLang is a row in #acctMenu
   that opens its list IN PLACE — a popover hanging off a popover would put
   the list somewhere the pointer has to cross dead space to reach. #langBtn
   is the globe beside Sound, and it only exists while there is no account
   menu to hold the row: html[data-had-acct] hides it, and site/i18n.js
   stamps that in the <head> off the device's own memory, so a signed-in
   header never paints a globe it is about to take away again. i18n.js fills
   both lists with the same .lang__i rows.

   THE FLAGS ARE INLINE SVG, NOT EMOJI, and that is the whole reason this
   costs more than one character per language: Windows ships no flag glyphs
   in Segoe UI Emoji, so a Spanish flag emoji renders there as the letters
   "ES". A site that reads "ES Español" on every Windows machine is worse
   than no flag at all, and CLAUDE.md forbids emoji in the product anyway.
   Drawn at 3:2 with a hairline ring, because a white flag (JP, KR) has no
   edge of its own against #0A0A0A. The phone's row draws the same SVG; only
   the native <select> it opens stays plain text, an <option> holding nothing
   else (mobile.js paintProfile). */
.lang{position:relative}
.lang .pop--menu{min-width:214px}
/* the globe is the signed-out header's. The attribute is set in the <head>,
   before this element is parsed, so there is no frame in which it shows. */
html[data-had-acct] #langWrap{display:none}
/* #8A94A4, not var(--ink-faint). --ink-faint is #5A6478 — "on the SKY", as the
   note at .ov__panel puts it — and on this near-black popover it measures
   3.33:1, under the 4.5 normal text needs. #8A94A4 is the value that same
   block already redefines --ink-faint to for a dark surface; here it measures
   6.4:1. (The menu's other faint text has the same problem and is older than
   this row; it is not touched here.) */
.menu__v{margin-left:auto;display:flex;align-items:center;gap:7px;
  font-size:13.5px;font-weight:600;color:#8A94A4;white-space:nowrap}
.menu__chev{transition:transform .2s;color:#8A94A4}
/* .menu__i svg is 17px square (line 932); these two are neither */
.menu__i .menu__chev{width:11px;height:7px}
.menu__i .flag,.lang__i .flag{width:19px;height:12.7px;flex:0 0 auto;border-radius:2px;
  box-shadow:0 0 0 .5px rgba(255,255,255,.42)}
.menu__i[aria-expanded="true"] .menu__chev{transform:rotate(180deg)}
/* the list, indented under its row with the rule the menu's own separators use */
.lang__list{margin:1px 0 4px 19px;padding-left:8px;border-left:1px solid rgba(255,255,255,.13)}
/* 40px tall, the same as .menu__i above: on a touch machine wide enough for
   this header (an iPad in landscape clears the 1024px line) the rows in here
   are the tap targets, and a 32px sub-row was the smallest one in the menu.
   The hierarchy is carried by the indent, the rule and the 14.5px type, not
   by a cramped row. */
.lang__i{display:flex;align-items:center;gap:10px;width:100%;padding:9px 9px;
  min-height:40px;border-radius:8px;
  font-size:14.5px;font-weight:600;color:#DCDCDC;text-align:left;
  transition:background .14s,color .14s}
.lang__i:hover{background:rgba(var(--acc-rgb),.14);color:var(--acc)}
.lang__i.is-on{color:var(--acc)}
.lang__i.is-on::after{content:"";width:7px;height:7px;border-radius:50%;background:var(--acc);
  flex:0 0 auto;margin-left:auto}
/* the phone's Language row: a real <select> over the whole row, invisible,
   so the tap opens the OS picker and the row keeps the sheet's look */
.mrow--sel{position:relative}
.mrow__sel{position:absolute;inset:0;width:100%;height:100%;opacity:0;font-size:16px;cursor:pointer;-webkit-appearance:none;appearance:none}
/* a translated LIVE ("EN VIVO", "AO VIVO") is two words; the tag is a flex row and
   would wrap them */
html[data-layout="stage"] .strip__live{white-space:nowrap}
