*,*::before,*::after{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0; color:var(--text); font-family:var(--font-body); font-size:var(--fs-md);
  line-height:1.5; height:100svh; overflow:hidden; display:flex; flex-direction:column;
  -webkit-font-smoothing:antialiased; background:var(--surface-sunk);
}

/* ── the background: whole page, slow drift, sits under everything ── */
/* Luke's hall (brand/stage/blockbet-hall.png) is drawn by the 3D layer now, so
   the blob gradient is gone. The flat colour stays as the backstop behind it
   while the art loads, and if the art ever fails. */
.bg{position:fixed;inset:0;z-index:-1;overflow:hidden;pointer-events:none;background:#08080A}
.bg__blob,.bg__veil{display:none}
.bg__blob{position:absolute;border-radius:50%;filter:blur(90px);opacity:.5;will-change:transform}
.bg__blob--a{width:70vmax;height:70vmax;left:-18vmax;top:-24vmax;
  background:radial-gradient(circle at 50% 50%, rgba(var(--accent-rgb),.20), rgba(var(--accent-rgb),0) 65%);
  animation:driftA 34s ease-in-out infinite alternate}
.bg__blob--b{width:60vmax;height:60vmax;right:-16vmax;top:8vmax;
  background:radial-gradient(circle at 50% 50%, rgba(53,214,214,.16), rgba(53,214,214,0) 65%);
  animation:driftB 44s ease-in-out infinite alternate}
.bg__blob--c{width:64vmax;height:64vmax;left:22vmax;bottom:-32vmax;
  background:radial-gradient(circle at 50% 50%, rgba(14,69,69,.55), rgba(14,69,69,0) 70%);
  animation:driftC 52s ease-in-out infinite alternate}
.bg__veil{position:absolute;inset:0;background:
  radial-gradient(120% 90% at 50% 40%, rgba(0,0,0,0) 30%, rgba(0,0,0,.62) 100%)}
@keyframes driftA{to{transform:translate3d(12vmax,8vmax,0) scale(1.12)}}
@keyframes driftB{to{transform:translate3d(-10vmax,12vmax,0) scale(1.08)}}
@keyframes driftC{to{transform:translate3d(8vmax,-10vmax,0) scale(1.14)}}
@media (prefers-reduced-motion:reduce){.bg__blob{animation:none}}

.skip{position:absolute;left:-9999px;top:0;z-index:var(--z-modal);
  background:var(--accent);color:#000;padding:var(--s2) var(--s4);font-weight:600}
.skip:focus{left:0}
:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:var(--r-sm)}

/* ── header — the one translucent surface ── */
.header{
  position:relative;z-index:var(--z-header);flex:0 0 auto;
  height:var(--h-header);min-height:56px;
  display:flex;align-items:center;gap:var(--s5);padding:0 var(--s6);
  background:rgba(10,10,12,.55);
  -webkit-backdrop-filter:blur(22px) saturate(140%);
  backdrop-filter:blur(22px) saturate(140%);
  border-bottom:1px solid rgba(255,255,255,.07);
}
.wordmark{font-family:var(--font-brand);font-weight:400;letter-spacing:.04em;font-size:var(--fs-lg);text-transform:uppercase}
.wordmark b{color:var(--accent)}
/* the printed-logo wordmark (brand/blockpacks-wordmark-*.png, 837x105) */
.wordmark-img{display:block;height:17px;width:auto}
.wordmark-img--cyan{display:none}
html[data-brand="cyan"] .wordmark-img--yellow{display:none}
html[data-brand="cyan"] .wordmark-img--cyan{display:block}
.header__nav{display:flex;gap:var(--s2);margin-left:var(--s5)}
.navlink{color:var(--text-dim);text-decoration:none;font-size:var(--fs-sm);font-weight:400;
  font-family:var(--font-brand);text-transform:uppercase;letter-spacing:.08em;
  padding:var(--s2) var(--s3);min-height:44px;display:flex;align-items:center;
  transition:color var(--dur-fast) var(--ease-out)}
.navlink:hover{color:var(--text)}
.navlink.is-active{color:var(--accent)}
.header__right{margin-left:auto;position:relative;display:flex;align-items:center;gap:var(--s2)}

/* ── header balances + wallet (slabz's pattern, our brand — RESEARCH-14 §2) ── */
/* Everything in the header band is 30px tall. `.header__right .btn` outranks
   the base `.btn{min-height:46px}` that ships later in this file — the first
   cut relied on order and lost, which is why Luke saw fat buttons (20 Aug). */
.header__right .btn{min-height:30px;height:30px;padding:0 var(--s3);
  font-size:var(--fs-xs);border-radius:var(--r-md)}
.header__right .btn--primary{box-shadow:none}   /* the stage glow is FAR too loud at 30px */
/* the display rules here would beat the hidden attribute — never let them */
.header__right [hidden]{display:none!important}
.hbal{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 var(--s3);
  cursor:pointer;font:inherit;font-size:var(--fs-xs);font-weight:600;color:var(--text);
  background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.10);
  border-radius:var(--r-md);font-variant-numeric:tabular-nums;
  transition:border-color var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out)}
.hbal:hover{border-color:rgba(var(--accent-rgb),.55);background:rgba(var(--accent-rgb),.06)}
.hbal__ico--tok{color:var(--accent)}
.hbal__ico--usdc{color:#2775CA}   /* USDC blue; simplified mark — swap for the official asset pre-launch */
.hbal b{font-weight:600}
/* connected wallet chip: address + green dot */
.btn--wallet{display:inline-flex;align-items:center;gap:8px;font-variant-numeric:tabular-nums}
.btn--wallet .wdot{width:7px;height:7px;border-radius:50%;background:var(--success);
  box-shadow:0 0 6px rgba(0,230,3,.7);flex:0 0 auto}

/* detail popovers — anchored under THEIR OWN pill (wallet.js sets left per
   pill; Luke: "the drop down should appear under them, not in the top right
   corner"). The ::before is an invisible hover bridge over the 10px gap so
   travelling pointer never "leaves" the pair mid-crossing. */
.hpop{position:absolute;top:calc(100% + 10px);right:auto;z-index:var(--z-modal);
  width:min(280px,86vw);padding:var(--s4);
  background:rgba(18,18,22,.96);-webkit-backdrop-filter:blur(22px);backdrop-filter:blur(22px);
  border:1px solid rgba(255,255,255,.10);border-radius:var(--r-lg);
  box-shadow:0 24px 60px -20px rgba(0,0,0,.8)}
.hpop::before{content:"";position:absolute;top:-12px;left:0;right:0;height:12px}
.hpop__row .btn{min-height:32px;height:32px;font-size:var(--fs-xs);box-shadow:none}
.hpop__k{margin:0 0 var(--s1);font-size:var(--fs-xs);color:var(--text-faint);
  letter-spacing:.14em;text-transform:uppercase;font-weight:600}
.hpop__big{margin:0 0 var(--s2);font-family:var(--font-display);font-weight:700;
  font-size:26px;color:var(--accent);font-variant-numeric:tabular-nums}
.hpop__unit{font-size:var(--fs-md);color:var(--text-faint);font-weight:600}
.hpop__note{margin:0 0 var(--s2);font-size:var(--fs-sm);color:var(--text-dim);line-height:1.45}
.hpop__note b{color:var(--text)}
.hpop__sub{margin:var(--s2) 0 0;font-size:var(--fs-xs);color:var(--text-faint)}
.hpop__row{display:flex;gap:var(--s2)}
.hpop__row .btn{flex:1}

/* wallet dropdown menu (right-anchored — it belongs to the rightmost chip) */
.hmenu::before{content:"";position:absolute;top:-12px;left:0;right:0;height:12px}
.hmenu{position:absolute;top:calc(100% + 10px);right:0;z-index:var(--z-modal);
  width:230px;padding:var(--s1) 0;overflow:hidden;
  background:rgba(18,18,22,.96);-webkit-backdrop-filter:blur(22px);backdrop-filter:blur(22px);
  border:1px solid rgba(255,255,255,.10);border-radius:var(--r-lg);
  box-shadow:0 24px 60px -20px rgba(0,0,0,.8)}
.hmenu__head{display:flex;align-items:center;gap:6px;padding:var(--s3) var(--s4);
  border-bottom:1px solid rgba(255,255,255,.07)}
.hmenu__addr{margin:0 auto 0 0;font-size:var(--fs-xs);color:var(--text-dim);
  font-variant-numeric:tabular-nums;letter-spacing:.02em}
.hmenu__dot{width:6px;height:6px;border-radius:50%;background:var(--success);animation:pulse 2.4s ease-out infinite}
.hmenu__live{font-size:10px;color:var(--success)}
.hmenu__item{display:block;width:100%;padding:var(--s2) var(--s4);cursor:pointer;text-align:left;
  font:inherit;font-size:var(--fs-sm);color:var(--text-dim);background:none;border:0;
  transition:background var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out)}
.hmenu__item:hover{background:rgba(var(--accent-rgb),.08);color:var(--text)}
.hmenu__item--danger{color:var(--danger);border-top:1px solid rgba(255,255,255,.07);margin-top:var(--s1)}
.hmenu__item--danger:hover{background:rgba(255,63,33,.10);color:var(--danger)}

/* ── everything below the header sits ON the background, no boxes ── */
.strip{position:relative;z-index:var(--z-rail);flex:0 0 auto;min-height:0;
  display:flex;align-items:center;gap:var(--s4);padding:var(--s3) var(--s6) 0;overflow:visible;
  background:transparent}
/* the nav's block type, still accent (Luke, 04:15: match OPEN VAULT ODDS INFO) */
.strip__label{font-family:var(--font-brand);font-size:var(--fs-sm);font-weight:400;
  letter-spacing:.08em;text-transform:uppercase;
  color:var(--accent);display:flex;align-items:center;gap:var(--s2);flex:0 0 auto}
.strip__label::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--accent);
  box-shadow:0 0 0 0 rgba(var(--accent-rgb),.6);animation:pulse 2.4s ease-out infinite}
@keyframes pulse{70%{box-shadow:0 0 0 9px rgba(var(--accent-rgb),0)}100%{box-shadow:0 0 0 0 rgba(var(--accent-rgb),0)}}
/* The marquee viewport. Items FADE off both edges (Luke: never hard-clip) —
   the mask lives HERE, not on .strip, so the pinned LIVE label never dims. */
.strip__viewport{flex:1 1 auto;min-width:0;overflow:hidden;
  -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:var(--s3);padding-right:var(--s3);flex:0 0 auto}
.strip__viewport:hover .strip__list{animation-play-state:paused}
/* the track holds TWO copies of the feed; -50% is exactly one copy, so the
   wrap is seamless by construction */
@keyframes stripScroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:reduce){
  .strip__list{animation:none}
  .strip__viewport{overflow-x:auto}
}
/* each pull is its own object on the background, not a row inside a bar */
.hit{display:flex;align-items:center;gap:var(--s2);padding:var(--s2) var(--s3);
  background:rgba(255,255,255,.05);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  border:1px solid rgba(255,255,255,.08);border-radius:var(--r-pill);font-size:var(--fs-xs)}
.strip__empty{color:var(--text-faint);font-size:var(--fs-sm);margin:0}

.grid{position:relative;z-index:var(--z-stage);flex:1 1 auto;display:grid;min-height:0;padding-top:var(--s5);
  grid-template-columns:210px 1fr 210px;grid-template-rows:minmax(0,1fr);
  gap:var(--s6);padding:var(--s2) var(--s6) var(--s3)}
/* NO container box. Luke: "you've put the buttons and the dropdown box thing
   inside another box ... I didn't tell you to do that ... Every button needs to
   be free and independent." The rails are pure layout; each button carries its
   own surface. I had added a scrim here for legibility over the hall art — it
   was not asked for, and it boxed the rails in. Gone. */
.rail{min-width:0;min-height:0;display:flex;flex-direction:column;gap:var(--s4);overflow:visible;
  background:none;border:0;padding:0;-webkit-backdrop-filter:none;backdrop-filter:none}
.rail__title{font-family:var(--font-display);font-size:var(--fs-xs);font-weight:600;
  letter-spacing:.12em;text-transform:uppercase;color:var(--text-faint);margin:0}

/* category select — sits on the background, no card */
.picker{position:relative;display:flex;align-items:center}
/* (old native-select chevron removed — the custom picker draws its own) */

/* pack rows — no box, generous separation */
.packlist{display:flex;flex-direction:column;gap:var(--s4);min-height:0}
.pack{
  width:100%;cursor:pointer;font:inherit;color:var(--text-dim);
  background:rgba(255,255,255,.045);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  border:1px solid rgba(255,255,255,.07);border-radius:var(--r-md);
  padding:var(--s3) var(--s4);min-height:52px;
  display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  transition:color var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out),transform var(--dur-fast) var(--ease-out);
}
.pack:hover{color:var(--text);background:rgba(255,255,255,.08);
  border-color:rgba(255,255,255,.16);transform:translateX(3px)}
.pack[aria-pressed="true"]{color:var(--text);border-color:var(--accent);
  background:rgba(var(--accent-rgb),.10);box-shadow:0 0 0 1px rgba(var(--accent-rgb),.25) inset}
.pack__name{font-weight:500;font-size:var(--fs-sm)}
.pack__price{font-family:var(--font-display);font-weight:600;font-variant-numeric:tabular-nums;
  font-size:var(--fs-sm);color:var(--text-faint)}
.pack[aria-pressed="true"] .pack__price,.pack:hover .pack__price{color:var(--accent)}

/* rewards — alive, still no box */
.reward{display:flex;flex-direction:column;gap:var(--s2)}
.reward__k{margin:0;font-size:var(--fs-xs);color:var(--text-faint);letter-spacing:.04em}
.reward__v{margin:0;font-family:var(--font-display);font-weight:600;font-size:var(--fs-lg);
  font-variant-numeric:tabular-nums}
.reward__v--live{color:var(--accent)}
.meter{height:3px;background:rgba(255,255,255,.10);overflow:hidden}
.meter__fill{height:100%;background:var(--accent);width:0;transition:width 900ms var(--ease-out)}
.reward__note{margin:0;font-size:var(--fs-xs);color:var(--text-faint)}

/* stage — no border, no box */
/* The controls sit low. Luke: "there's a decent amount of space between the pay
   buttons at the bottom and the bottom of the page that we could take up by
   moving the pay buttons down with the rip pack button as well. We don't wanna
   move them right down to the bottom ... but we wanna take it close."
   space-between pushes the control stack to the floor of the stage; the pack
   itself is positioned in 3D and does not care where this column sits. */
.stage{min-width:0;display:flex;flex-direction:column;align-items:center;
  justify-content:space-between;
  gap:var(--s3);position:relative;background:transparent;border:0}
.stage__inner{flex:1 1 auto;display:grid;place-items:center;width:100%;min-height:0}
.stage__placeholder{display:grid;place-items:center;gap:var(--s4)}
.stage__pack{width:clamp(96px,15vh,164px);aspect-ratio:.62;border-radius:var(--r-sm);
  background:linear-gradient(160deg,#2A2A35,#101015);
  box-shadow:0 30px 70px -22px rgba(0,0,0,.95),0 0 0 1px rgba(255,255,255,.06),
             0 0 60px -18px rgba(var(--accent-rgb),.28);
  animation:float 7s ease-in-out infinite alternate}
@keyframes float{to{transform:translateY(-10px)}}
@media (prefers-reduced-motion:reduce){.stage__pack{animation:none}}
.stage__hint{margin:0;color:var(--text-faint);font-size:var(--fs-sm)}
.stage__actions{flex:0 0 auto;display:flex;gap:var(--s3);padding-bottom:0;align-items:center}

/* ── desktop turbo (Luke's brief off the slabz reference, 21 Aug 03:57) ──
   the pill is DEAD CENTRE (grid, 1fr|auto|1fr — the info dot lives in the
   third column so it never shoves the pill off centre, his complaint about
   slabz's). The WHOLE pill lights when on. */
.turborow{flex:0 0 auto;display:grid;grid-template-columns:1fr auto 1fr;
  align-items:center;width:100%;margin-bottom:2px}
.turborow__side{position:relative;justify-self:start;margin-left:10px;display:flex}
.dturbo{display:flex;align-items:center;gap:8px;cursor:pointer;
  padding:7px 13px;border-radius:12px;
  background:rgba(255,255,255,.04);border:1px solid var(--hairline);
  /* the nav's block type exactly (Luke, 04:14: match OPEN VAULT ODDS INFO) */
  font:400 var(--fs-sm) var(--font-brand);text-transform:uppercase;letter-spacing:.08em;
  color:var(--text-dim);
  transition:color var(--dur-fast) var(--ease-out),border-color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out),box-shadow var(--dur-fast) var(--ease-out)}
.dturbo:hover{color:var(--text);border-color:var(--hairline-str)}
.dturbo__bolt{flex:0 0 auto;opacity:.7}
.dturbo__track{width:34px;height:19px;border-radius:10px;position:relative;
  background:rgba(255,255,255,.12);transition:background var(--dur-fast) var(--ease-out)}
.dturbo__knob{position:absolute;top:2px;left:2px;width:15px;height:15px;border-radius:50%;
  background:rgba(255,255,255,.55);transition:transform var(--dur-fast) var(--ease-out),
  background var(--dur-fast) var(--ease-out)}
.dturbo[aria-checked="true"]{color:var(--accent);border-color:rgba(var(--accent-rgb),.55);
  background:rgba(var(--accent-rgb),.10);
  box-shadow:0 0 22px -6px rgba(var(--accent-rgb),.55)}
.dturbo[aria-checked="true"] .dturbo__bolt{opacity:1}
.dturbo[aria-checked="true"] .dturbo__track{background:rgba(var(--accent-rgb),.45)}
.dturbo[aria-checked="true"] .dturbo__knob{transform:translateX(15px);background:var(--accent)}

/* the info dot + hover card (slabz's little eye) */
.dturbo__info{width:20px;height:20px;border-radius:50%;cursor:help;
  background:none;border:1px solid var(--hairline-str);color:var(--text-faint);
  font:600 11px/1 Inter,system-ui,sans-serif;
  transition:color var(--dur-fast) var(--ease-out),border-color var(--dur-fast) var(--ease-out)}
.dturbo__info:hover,.dturbo__info:focus-visible{color:var(--accent);
  border-color:rgba(var(--accent-rgb),.6)}
.dturbo__tip{position:absolute;bottom:calc(100% + 10px);left:50%;transform:translateX(-50%);
  width:238px;padding:12px 14px;border-radius:12px;z-index:40;
  background:rgba(14,14,18,.97);border:1px solid var(--hairline-str);
  box-shadow:0 18px 44px -14px rgba(0,0,0,.85);
  font:400 var(--fs-xs)/1.55 Inter,system-ui,sans-serif;color:var(--text-dim);
  text-align:left;pointer-events:none;opacity:0;translate:0 4px;
  transition:opacity var(--dur-fast) var(--ease-out),translate var(--dur-fast) var(--ease-out)}
.dturbo__tip b{color:var(--text);font-family:var(--font-display);
  text-transform:uppercase;letter-spacing:.08em;font-weight:600}
.dturbo__info:hover + .dturbo__tip,.dturbo__info:focus-visible + .dturbo__tip{opacity:1;translate:0 0}

/* ── the bolt STRIKES onto the rip button when turbo is on ──
   CSS-only (::before) so rip-hold.js's label rebuilds can never wipe it,
   and transform-only motion so the button's box — which the hold trace is
   measured from — never changes size. The label slides right to make room. */
#ripBtn{position:relative}
#ripBtn .rip__labels{transition:transform 240ms var(--ease-out)}
[data-turbo="1"] #ripBtn .rip__labels{transform:translateX(9px)}
[data-turbo="1"] #ripBtn::before{content:'';position:absolute;left:calc(50% - 55px);top:50%;
  width:13px;height:17px;transform:translateY(-50%);
  background:currentColor;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 16'%3E%3Cpath d='M7.2 0 1 9.2h3.4L4.8 16 11 6.4H7.4L7.2 0z'/%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 12 16'%3E%3Cpath d='M7.2 0 1 9.2h3.4L4.8 16 11 6.4H7.4L7.2 0z'/%3E%3C/svg%3E") center/contain no-repeat;
  animation:boltstrike 220ms var(--ease-out)}
/* RIP ANOTHER carries the same bolt while turbo is armed (Luke, 04:18) —
   inline flow here, its width is not trace-measured like #ripBtn's */
[data-turbo="1"] #prAgain::before{content:'';display:inline-block;width:12px;height:16px;
  margin-right:8px;vertical-align:-2px;background:currentColor;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 16'%3E%3Cpath d='M7.2 0 1 9.2h3.4L4.8 16 11 6.4H7.4L7.2 0z'/%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 12 16'%3E%3Cpath d='M7.2 0 1 9.2h3.4L4.8 16 11 6.4H7.4L7.2 0z'/%3E%3C/svg%3E") center/contain no-repeat}

@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){
  [data-turbo="1"] #ripBtn::before{animation:none}
  #ripBtn .rip__labels{transition:none}}

/* What's Inside — bottom of the left rail, slabz's placement (Luke, 04:03) */
.whatsinside{display:flex;align-items:center;justify-content:center;gap:8px;
  width:100%;margin-top:var(--s4);padding:11px 0;cursor:pointer;
  background:none;border:1px solid rgba(var(--accent-rgb),.4);border-radius:12px;
  font:600 var(--fs-xs) var(--font-display);text-transform:uppercase;letter-spacing:.1em;
  color:var(--accent);transition:border-color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out)}
.whatsinside:hover{border-color:var(--accent);background:rgba(var(--accent-rgb),.08)}

/* NO canvas lift machinery any more (Luke, 04:44): the pack itself lives
   at the sequence's frame-0 position in browse (pack3d.js BROWSE_DROP = 0),
   so the canvas never transforms, nothing rises on the hold, and the reveal
   starts exactly where the pack stands. */

/* SHORT desktop windows: the control stack compresses (mobile's compact-dock
   rule, HANDOFF-25, brought to desktop) so the frame-0 pack keeps daylight
   above the turbo pill — measured: without this, 780-tall touches by 4px
   and 720 by 17. Reclaimed pixels go to the pack's feet automatically. */
@media (min-width: 1024px) and (max-height: 860px){
  .stage{gap:var(--s2)}
  /* the grid's bottom padding is the last reclaimable space on short
     windows — the whole control stack rides lower, the pack keeps its
     daylight */
  .grid{padding-bottom:3px}
  /* the RIP button keeps its FULL size (Luke, 04:58) and the hold ring
     needs ~10px of air on both sides: the actions row carries its own
     margins so the ring can never touch the pill above or the pay row
     below. Pill and pay buttons stay slim. */
  .stage__actions{margin-top:6px;margin-bottom:6px}
  .turborow{margin-bottom:0}
  .dturbo{padding:4px 11px}
  .pay .pay__btn{padding-top:5px;padding-bottom:5px}
}

/* the very shortest desktop windows squeeze once more — measured to keep
   the pack's feet just clear of the pill even at 720 tall */
@media (min-width: 1024px) and (max-height: 750px){
  .dturbo{padding:3px 10px}
  .grid{padding-bottom:0}
  .pay .pay__btn{padding-top:4px;padding-bottom:4px}
}

.btn{font:inherit;font-weight:600;cursor:pointer;min-height:46px;
  padding:var(--s3) var(--s6);border-radius:var(--r-pill);border:1px solid transparent;
  transition:transform var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out)}
.btn:active{transform:scale(.97)}
.btn--primary{background:var(--accent);color:#04191A;font-family:var(--font-display);
  box-shadow:0 10px 34px -12px rgba(var(--accent-rgb),.6)}
.btn--primary:hover{background:var(--brand-400)}
.btn--ghost{background:transparent;color:var(--text);border-color:rgba(255,255,255,.18)}
.btn--ghost:hover{border-color:var(--accent);color:var(--accent)}
.btn--quiet{background:transparent;color:var(--text-faint);border-color:transparent}
.btn--quiet:hover{color:var(--text)}
/* the header pills are a fixed 30px band, so a label that wraps spills out
   of its own button: at 768 "Connect Wallet" broke over two lines (iPad
   portrait, pre-existing). Scoped to .btn--hdr on purpose — measured at the
   real held frame, no post-rip or stage button wraps at 375/390/768/1024,
   so a global nowrap would only widen the blast radius (codex round 1 P1). */
.btn--hdr{white-space:nowrap}

/* ── rip: the chrome clears ── */
[data-state="rip"] .strip,[data-state="rip"] .rail{opacity:0;pointer-events:none}
[data-state="rip"] .strip{height:0;min-height:0}
[data-state="rip"] .grid{grid-template-columns:0 1fr 0;gap:0;padding:0 0 var(--s5)}
.strip,.rail,.grid{transition:opacity var(--dur-mid) var(--ease-out),
  height var(--dur-mid) var(--ease-out),grid-template-columns var(--dur-mid) var(--ease-out),
  padding var(--dur-mid) var(--ease-out),gap var(--dur-mid) var(--ease-out)}

/* ── THE SHELL LINE, 1024 ────────────────────────────────────────────────
   Luke, 21 Aug: "just copy slabz.com on all of the sizes." Measured, not
   assumed — slabz.com driven headless with an iPad user agent and swept
   760->1200 in 15 steps: its content sidebar appears at EXACTLY 1024. Below
   that, at 768 and 834, it serves the phone shell (tab row, bottom dock
   with the chip scroller, rewards FAB top right); at 1024 and 1194 it
   serves the full desktop three-column layout. hypedrop.com does the same
   with its line at ~960; stake.com drops its bottom nav at ~768 and widens
   its rail at 1200.
   The standards agree on the METHOD — width, never orientation. Material 3:
   medium 600-839 ("93.73% of tablets in portrait", single pane), expanded
   840-1199 ("97.22% of tablets in landscape", full multi-pane). Bootstrap
   5: md 768 "Tablets", lg 992 "Desktops". Tailwind: lg 1024. Apple's HIG
   deliberately does NOT split iPad by orientation (a full-screen iPad app
   is Regular width both ways) and says adapt to available space, because
   Split View and Stage Manager can hand you any width at any orientation.
   MDN: the `orientation` media query tests the VIEWPORT, not the device —
   a soft keyboard can flip a portrait iPad to "landscape".
   So: ONE line at 1024. Orientation is the consequence, not the mechanism —
   iPad portrait is 768-834 and lands on the phone shell, iPad landscape is
   1024-1194 and lands on the desktop. The 761-1024 hybrid that lived here
   is deleted: it was the squeezed-desktop shape none of the three
   references ship. ─────────────────────────────────────────────────────── */

/* category symbol beside the picker */
.picker{gap:var(--s3)}
.picker__icon{flex:0 0 auto;width:22px;height:22px;color:var(--accent);display:block}
.picker__icon svg{width:100%;height:100%;display:block}

/* the real pack artwork on the stage */
.stage__art{
  display:block;height:min(52vh,460px);width:auto;max-width:100%;
  filter:drop-shadow(0 34px 60px rgba(0,0,0,.85)) drop-shadow(0 0 70px rgba(var(--accent-rgb),.18));
  animation:float 7s ease-in-out infinite alternate;
  transition:opacity var(--dur-mid) var(--ease-out);
}
.stage__placeholder{margin:0}
.stage__art.is-pending{opacity:.45;filter:grayscale(.5) drop-shadow(0 24px 50px rgba(0,0,0,.8))}
@media (prefers-reduced-motion:reduce){.stage__art{animation:none}}
.stage__hint{margin:var(--s4) 0 0;text-align:center}
.stage__hint b{color:var(--warn);font-weight:600}

/* ── on-brand category picker ── */
.picker{position:relative;display:block}
.picker__btn{
  width:100%;display:flex;align-items:center;gap:var(--s3);cursor:pointer;
  font:inherit;font-family:var(--font-display);font-weight:600;font-size:var(--fs-md);
  color:var(--text);background:transparent;border:0;
  border-bottom:1px solid var(--hairline-str);padding:var(--s2) 0;min-height:46px;
  transition:border-color var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out);
}
.picker__btn:hover,.picker__btn[aria-expanded="true"]{color:var(--accent);border-color:var(--accent)}
.picker__label{flex:1 1 auto;text-align:left}
.picker__chev{width:7px;height:7px;flex:0 0 auto;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:translateY(-2px) rotate(45deg);transition:transform var(--dur-fast) var(--ease-out)}
.picker__btn[aria-expanded="true"] .picker__chev{transform:translateY(2px) rotate(-135deg)}
.picker__menu{
  position:absolute;left:0;right:0;top:calc(100% + 8px);z-index:var(--z-modal);
  margin:0;padding:var(--s2);list-style:none;
  background:rgba(12,12,15,.78);
  -webkit-backdrop-filter:blur(22px) saturate(140%);backdrop-filter:blur(22px) saturate(140%);
  border:1px solid rgba(255,255,255,.10);border-radius:var(--r-md);
  box-shadow:0 26px 60px -20px rgba(0,0,0,.9);
  animation:menuIn var(--dur-fast) var(--ease-out);
}
@keyframes menuIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
.picker__opt{
  display:flex;align-items:center;gap:var(--s3);width:100%;cursor:pointer;
  font:inherit;font-size:var(--fs-sm);color:var(--text-dim);
  background:transparent;border:0;border-radius:var(--r-sm);
  padding:var(--s3);min-height:44px;text-align:left;
  transition:color var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out);
}
.picker__opt:hover{color:var(--text);background:rgba(255,255,255,.06)}
.picker__opt[aria-selected="true"]{color:var(--accent)}
.picker__opt .picker__icon{color:inherit}
.picker__icon{flex:0 0 auto;width:22px;height:22px;display:block;color:var(--accent)}
.picker__icon svg,.picker__icon img{width:100%;height:100%;display:block;object-fit:contain}

/* ── the chrome leaves the room ──────────────────────────────────────────
   Not a fade: each piece exits to its own edge, rails first and the header
   last. The delays live only in the rip rules, so coming back is one clean
   simultaneous move. Total out ≈ 520ms — rip-hold.js SLIDE_MS matches. */
.strip,.rail--left,.rail--right{
  transition:transform 400ms var(--ease-out),opacity 260ms var(--ease-out),
             height 260ms var(--ease-out),min-height 260ms var(--ease-out)}
[data-state="rip"] .rail--left {transform:translateX(-320px)}
[data-state="rip"] .rail--right{transform:translateX(320px)}
[data-state="rip"] .strip{transform:translateY(-120px);
  transition:transform 400ms var(--ease-out) 50ms,opacity 240ms var(--ease-out) 50ms,
             height 260ms var(--ease-out) 200ms,min-height 260ms var(--ease-out) 200ms}
/* The header STAYS. Luke, 19 Aug: "when you press rip, the header at the top of
   the page shouldn't disappear. Everything else should, but not the header."
   It used to slide up 80px, fade out and collapse to height 0. */
/* Travel is in px, not %. A percentage translate resolves against the
   element's own box, and these boxes collapse to zero in the same beat — the
   slide would shrink away with them and never leave the screen.
   The columns also hold their width until the rails are nearly gone, so no
   rail squashes on its way out. */
.grid{transition:grid-template-columns 280ms var(--ease-out),
      padding 280ms var(--ease-out),gap 280ms var(--ease-out)}
[data-state="rip"] .grid{transition-delay:220ms}
/* while the room clears, the controls stand down and come back relabelled */
.stage__actions,.turborow{transition:opacity 220ms var(--ease-out),transform 220ms var(--ease-out)}
.is-ripping .stage__actions,.is-ripping .turborow{opacity:0;transform:translateY(14px);pointer-events:none}

/* ── hold to rip ─────────────────────────────────────────────────────────
   The pill is the button; the traced line is an SVG sibling sitting
   --rip-gap OUTSIDE it, so it reads as a ring closing around the button
   rather than a border fattening on it. rip-hold.js builds this markup and
   rebuilds the path from the button's measured box; it drives two custom
   properties — --p, linear and honest, for the trace, and --e, eased, for
   the glow and the press-in. */
.rip{--rip-gap:7px;--p:0;--e:0;
  position:relative;display:inline-flex;
  transform:scale(calc(1 - .03 * var(--e)))}
.rip .rip__btn{
  position:relative;z-index:1;touch-action:none;
  -webkit-user-select:none;user-select:none;-webkit-touch-callout:none;
  box-shadow:0 10px 34px -12px rgba(var(--accent-rgb),calc(.55 + .45 * var(--e))),
             0 0 calc(8px + 30px * var(--e)) rgba(var(--accent-rgb),calc(.14 * var(--e)))}
.rip .rip__btn:active{transform:none}      /* the wrapper does the press-in */
/* the global :focus-visible squares the pill off; keep it a pill, and push
   the focus ring clear of the traced line */
.rip .rip__btn:focus-visible{border-radius:var(--r-pill);
  outline-offset:calc(var(--rip-gap) + 4px)}

.rip__ring{position:absolute;inset:calc(-1 * var(--rip-gap));
  overflow:visible;pointer-events:none;transform-origin:50% 50%;
  opacity:0;transition:opacity 140ms var(--ease-out)}
.rip.is-holding .rip__ring{opacity:1}
.rip__track{fill:none;stroke:rgba(255,255,255,.14);stroke-width:2}
.rip__line{fill:none;stroke:var(--brand-400);stroke-width:2;stroke-linecap:round;
  filter:drop-shadow(0 0 5px rgba(var(--accent-rgb),.8))}

/* Every label sits in the same grid cell, so the pill's width never moves.
   It has to not move: the traced path is measured off that box. */
.rip__labels{display:grid;place-items:center}
.rip__label{grid-area:1/1;white-space:nowrap;opacity:0;transform:translateY(3px);
  transition:opacity 130ms var(--ease-out),transform 130ms var(--ease-out)}
.rip__label.is-on{opacity:1;transform:none}
.rip__label[data-l="hold"]{letter-spacing:.12em;
  color:rgba(4,25,26,calc(.55 + .45 * var(--e)))}

/* the two ends touch: the ring blooms outward and lets go */
@keyframes ripBloom{
  0%  {transform:scale(1);opacity:1}
  100%{transform:scale(1.16);opacity:0}}
@keyframes ripFlash{
  0%  {filter:brightness(1)}
  28% {filter:brightness(1.5)}
  100%{filter:brightness(1)}}
.rip.is-done .rip__ring{opacity:1;animation:ripBloom 420ms var(--ease-out) forwards}
.rip.is-done .rip__line{stroke:#FFFFFF;filter:drop-shadow(0 0 12px rgba(var(--accent-rgb),.95))}
.rip.is-done .rip__btn{animation:ripFlash 380ms var(--ease-out)}

/* reduced motion: no trace, nothing to hold — a plain click rips it */
@media (prefers-reduced-motion:reduce){
  .rip{transform:none}
  .rip__ring{display:none}
  .rip .rip__btn{box-shadow:0 10px 34px -12px rgba(var(--accent-rgb),.6)}
}

/* ── the rip button, properly ──────────────────────────
   A physical key: lit from above, sitting on a solid edge that compresses
   when you press it. The hold-ring in rip-hold.js traces the same pill. */
.btn--primary{
  position:relative;overflow:hidden;isolation:isolate;
  font-family:var(--font-brand);text-transform:uppercase;
  font-size:var(--fs-lg);letter-spacing:.05em;
  padding:var(--s4) var(--s8);min-height:58px;
  color:#04282A;
  background:linear-gradient(180deg,var(--brand-200) 0%,var(--brand-400) 38%,var(--brand-500) 62%,var(--brand-600) 100%);
  border:0;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.95),
    inset 0 -2px 2px rgba(0,40,42,.28),
    0 5px 0 var(--brand-800),
    0 10px 0 rgba(0,0,0,.28),
    0 22px 46px -14px rgba(var(--accent-rgb),.6),
    0 0 60px -18px rgba(var(--accent-rgb),.5);
  transition:transform 110ms var(--ease-out),box-shadow 110ms var(--ease-out),
             filter var(--dur-fast) var(--ease-out);
}
.btn--primary:hover{filter:brightness(1.06)}
.btn--primary:active,.btn--primary[data-holding="true"]{
  transform:translateY(5px);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.8),
    inset 0 -1px 2px rgba(0,40,42,.3),
    0 0 0 var(--brand-800),
    0 2px 0 rgba(0,0,0,.3),
    0 10px 26px -12px rgba(var(--accent-rgb),.55);
}
/* sheen sweep */
.btn--primary::after{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:linear-gradient(105deg,transparent 35%,rgba(255,255,255,.55) 50%,transparent 65%);
  transform:translateX(-120%);
}
.btn--primary:hover::after{animation:sheen 900ms var(--ease-out)}
@keyframes sheen{to{transform:translateX(120%)}}
@media (prefers-reduced-motion:reduce){.btn--primary::after{display:none}}


/* ── the pack owns the stage ──────────────────────────
   The canvas was inheriting the flat image's height cap (460px) and clipping
   the pack's own top. It fills the stage box now. */
.stage__placeholder{width:100%;height:100%;display:grid;place-items:center}
.stage__art{height:auto;max-height:100%;max-width:78%}
.pack3d{width:100%;height:100%;display:grid;place-items:center}
.pack3d canvas{width:100%!important;height:100%!important;display:block}
/* no pack name, no drag hint — the button carries the meaning */
.stage__hint,#packCaption,.pack3d__hint,[class*="drag-to-spin"]{display:none!important}
/* The canvas no longer lives in the stage column — pack3d.js mounts it into
   #stageLayer, fixed and full-viewport — so these height overrides now apply
   only to the flat fallback image's host. The `!important` sizing that used to
   force the canvas to track the stage box is gone with it; that coupling is
   what made the drawing buffer reallocate every frame while the chrome cleared. */
.stage__placeholder,.pack3d{width:100%;height:100%;max-height:none}

/* EVERYTHING sits over the background. Luke: "the two sidebars and the live
   packs ... all sit over the top of the background as well." */
/* NOT .skip — it is position:absolute at left:-9999px and only comes back on
   focus. Forcing it to position:relative put it back in normal flow at the top
   of the body and pushed the header and the live bar down the page. */
.header,.strip,.rail,.stage,.sheet{position:relative;z-index:2}
/* the header must beat the strip: its popovers/menus hang over the live bar
   (this later block was flattening everything to 2, so DOM order won) */
.header{z-index:var(--z-header)}
.skip{z-index:var(--z-modal)}
.stage__actions,.pay{position:relative;z-index:3}
/* the stage column itself must not paint over the hall */
.stage,.stage__inner,.stage__placeholder{background:transparent}


/* ── loading: hold the stage until the 3D pack is genuinely ready ── */
.stage__art{opacity:0;transition:opacity 220ms var(--ease-out)}   /* never flash the flat art */
[data-pack="fallback"] .stage__art{opacity:1}                      /* unless 3D failed */
/* and never once the real pack is on the stage, whatever pack3d's own sheet
   says -- two packs drawn at once is the fault Luke reported as the design
   dragging off the pack. */
[data-stage3d="live"] .stage__art{opacity:0!important;visibility:hidden!important}
.loader{position:absolute;inset:0;display:grid;place-items:center;gap:var(--s4);
  align-content:center;pointer-events:none;transition:opacity 260ms var(--ease-out)}
[data-pack="live"] .loader,[data-pack="fallback"] .loader{opacity:0;visibility:hidden}
.loader__ring{position:relative;width:74px;height:74px}
.loader__ring span{position:absolute;inset:0;border-radius:50%;
  border:2px solid transparent;border-top-color:var(--accent);
  animation:spin 1.15s linear infinite}
.loader__ring span:nth-child(2){inset:9px;border-top-color:var(--brand-400);animation-duration:1.6s;animation-direction:reverse}
.loader__ring span:nth-child(3){inset:18px;border-top-color:var(--brand-700);animation-duration:2.1s}
@keyframes spin{to{transform:rotate(360deg)}}
.loader__txt{margin:0;font-size:var(--fs-xs);letter-spacing:.14em;text-transform:uppercase;
  color:var(--text-faint);animation:breathe 1.8s ease-in-out infinite}
@keyframes breathe{50%{opacity:.45}}
@media (prefers-reduced-motion:reduce){
  .loader__ring span{animation:none;border-top-color:var(--accent)}
  .loader__txt{animation:none}
}

/* ── the pull feed gets room to breathe ── */
/* The live bar sits closer under the header, and its boxes are smaller.
   Luke: "There's quite a decent amount of space between them, individual boxes,
   and the header at the top. Up that slightly and then try and decrease the
   size of those boxes slightly." Was 24px above / 16px below. */
.strip{padding:var(--s2) var(--s6) var(--s3)}
.grid{padding-top:var(--s3)}
.rail__title{margin-top:var(--s2)}
.hit__name{color:var(--text);font-weight:500}
.hit__grade{color:var(--accent);font-variant-numeric:tabular-nums}
.hit__val{color:var(--text-faint);font-variant-numeric:tabular-nums}
.hit__dot{width:5px;height:5px;border-radius:50%;background:var(--accent);flex:0 0 auto}

/* ── the post-rip moment: identity, value, sell/RIP ANOTHER/vault ──
   No box (Luke, 20 Aug): the elements sit straight on the stage like the
   rest of the chrome. Grade front and centre. RIP ANOTHER is the star —
   it wears the accent and the brand face; Sell (exact figure) and Vault
   flank it as matched equals. */
/* PLAN-24 C2: the post-reveal RAIL — slides in from the right on
   reveal-held (Luke: "it can't just pop up"). No containing box: type and
   hairline rows straight on the stage. Mock rips keep the approved
   bottom-centre layout (data-mock) — nothing real was dealt there. */
.postrip{position:absolute;top:0;right:0;bottom:0;width:min(440px,38vw);
  z-index:31;display:flex;align-items:center;padding-right:min(4vw,56px);
  background:none;border:0;pointer-events:none}
/* display:flex above would silently beat the hidden attribute — the exact
   header-pill bug again, and how a "vaulted" panel stayed painted over the
   home page (Luke's screenshot, 20 Aug). Never again. */
.postrip[hidden]{display:none!important}
.postrip__rail{pointer-events:auto;display:flex;flex-direction:column;
  align-items:stretch;gap:var(--s4);width:100%;text-align:left;
  animation:railIn 480ms var(--ease-out)}
@keyframes railIn{from{opacity:0;transform:translateX(72px)}to{opacity:1;transform:none}}
.postrip__eyebrow{margin:0;font:400 var(--fs-xs)/1 var(--font-display);
  letter-spacing:.32em;text-transform:uppercase;color:var(--text-faint)}
.postrip__name{margin:0;font-family:var(--font-display);font-weight:700;font-size:var(--fs-2xl);
  color:var(--text);line-height:1.22;text-shadow:0 2px 18px rgba(0,0,0,.85)}
/* the chip carries the RARITY (C2): a rounded rectangle, never a pill
   (Luke: "I hate pills"), block font, glowing in its tier colour */
.postrip__grade{margin:0;align-self:flex-start;font-family:var(--font-brand);
  text-transform:uppercase;letter-spacing:.1em;font-size:var(--fs-sm);color:var(--accent);
  padding:6px 16px;border:1px solid rgba(var(--accent-rgb),.4);border-radius:8px;
  background:rgba(var(--accent-rgb),.08);text-shadow:0 0 14px rgba(var(--accent-rgb),.35);
  box-shadow:0 0 16px -2px rgba(var(--accent-rgb),.45)}
/* the chip wears its tier colour (Luke's mapping — same ramp as the feed) */
.postrip__grade[data-rarity="common"]{--tierc:0,230,3}
.postrip__grade[data-rarity="rare"]{--tierc:30,146,255}
.postrip__grade[data-rarity="epic"]{--tierc:221,43,255}
.postrip__grade[data-rarity="legendary"]{--tierc:255,206,27}
.postrip__grade[data-rarity="common"],.postrip__grade[data-rarity="rare"],
.postrip__grade[data-rarity="epic"],.postrip__grade[data-rarity="legendary"]{
  color:rgb(var(--tierc));border-color:rgba(var(--tierc),.45);
  background:rgba(var(--tierc),.08);text-shadow:0 0 14px rgba(var(--tierc),.4)}
/* one rhythm for every row (Luke: "all the sections are different sizes
   ... all the boxes should be evenly spaced"): identical height, identical
   labels, one value size for the facts, one for the money */
.postrip__rows,.postrip__vals{margin:0;display:flex;flex-direction:column}
.postrip__r{display:flex;justify-content:space-between;align-items:center;gap:var(--s4);
  padding:16px 0;border-bottom:1px solid var(--hairline)}
.postrip__r dt{font:400 var(--fs-xs)/1 var(--font-display);letter-spacing:.18em;
  text-transform:uppercase;color:var(--text-faint)}
.postrip__r dd{margin:0;font-size:var(--fs-md);font-weight:600;color:var(--text);
  font-variant-numeric:tabular-nums;text-align:right;line-height:1;
  text-shadow:0 1px 10px rgba(0,0,0,.8)}
/* every row the SAME thickness (Luke, 04:18: "everything should be the
   same kind of thickness") — the value rows keep only the accent colour */
.postrip__vals .postrip__r:last-child dd{color:var(--accent)}

/* Share on X sits just under the held card, centre stage (Luke, 20 Aug
   review 2 — never in the rail's corner) */
.postrip__share{position:absolute;left:50%;bottom:clamp(120px,20vh,220px);
  transform:translate(-50%,0);z-index:31;
  display:inline-flex;align-items:center;gap:7px;
  font:600 var(--fs-xs)/1 var(--font-display);letter-spacing:.16em;text-transform:uppercase;
  color:var(--text-dim);text-decoration:none;padding:9px 16px;
  border:1px solid var(--hairline-str);border-radius:10px;
  animation:shareIn 480ms var(--ease-out)}
@keyframes shareIn{from{opacity:0;transform:translate(-50%,12px)}to{opacity:1;transform:translate(-50%,0)}}
.postrip__share:hover{color:var(--accent);border-color:rgba(var(--accent-rgb),.5)}
.postrip__share[hidden]{display:none!important}
/* the turbo auto-sold / window-closed line replaces the sell actions */
.postrip__note{margin:var(--s1) 0 0;font-size:var(--fs-sm);color:var(--brand-200);
  font-weight:600;text-shadow:0 1px 10px rgba(0,0,0,.8)}
/* anything hidden inside the panel STAYS hidden, whatever display rules say */
.postrip [hidden]{display:none!important}
/* actions in slabz's shape: the hero spans, Vault It + Sell Now pair under
   it; a lone survivor of the pair takes the full row. Room to breathe —
   Luke: "the buttons look like they're too close together" */
.postrip__row{display:grid;grid-template-columns:1fr 1fr;gap:var(--s3);margin-top:var(--s3)}
.postrip__row .postrip__hero{grid-column:1/-1;width:100%;min-height:56px}
.postrip__row .postrip__side{width:100%;min-height:48px}
.postrip__row:has(.postrip__side[hidden]) .postrip__side:not([hidden]){grid-column:1/-1}
/* C5: the inspect affordance under the held slab, centre stage */
.postrip__inspect{position:absolute;left:50%;bottom:var(--s6);transform:translateX(-50%);
  z-index:31;cursor:pointer;font:600 var(--fs-xs)/1 var(--font-display);
  letter-spacing:.22em;text-transform:uppercase;color:var(--text-dim);
  background:none;border:0;padding:10px 16px;
  text-shadow:0 1px 10px rgba(0,0,0,.85);
  animation:railIn 480ms var(--ease-out);transition:color var(--dur-fast) var(--ease-out)}
.postrip__inspect:hover{color:var(--accent)}
.postrip__inspect[hidden]{display:none!important}
/* the slab itself is the tap target: an invisible zone over the held
   slab's screen area (the canvas underneath takes no pointer events) */
.postrip__slabhit{position:absolute;left:50%;top:11vh;transform:translateX(-50%);
  width:min(340px,34vw);height:62vh;z-index:30;cursor:pointer;
  background:none;border:0;padding:0}
.postrip__slabhit[hidden]{display:none!important}
@media (max-width:1023px){
  .postrip__slabhit{position:fixed;top:calc(env(safe-area-inset-top,0px) + 100px);
    height:44vh;width:72vw}
}
/* ── mock rip keeps the approved bottom-centre shape: hero dead centre ── */
.postrip[data-mock="1"]{top:auto;right:auto;left:50%;bottom:var(--s5);width:auto;
  transform:translateX(-50%);padding:0;align-items:center;
  animation:postripIn .45s var(--ease-out)}
@keyframes postripIn{from{opacity:0;transform:translate(-50%,16px)}to{opacity:1;transform:translate(-50%,0)}}
.postrip[data-mock="1"] .postrip__rail{animation:none;align-items:center;text-align:center}
/* the hero sits on the PAGE's centre line (Luke's rule), whatever the side
   button's width: 1fr/auto/1fr with the visible survivors pinned */
.postrip[data-mock="1"] .postrip__row{display:grid;grid-template-columns:1fr auto 1fr;
  width:min(720px,80vw);gap:var(--s4)}
.postrip[data-mock="1"] .postrip__row .postrip__hero{grid-column:2;width:auto}
.postrip[data-mock="1"] .postrip__row .postrip__side:not([hidden]){grid-column:3;width:auto;justify-self:start}
/* the hero: same voice as RIP PACK itself */
.postrip__hero{min-height:52px;padding:0 var(--s7);font-family:var(--font-brand);
  text-transform:uppercase;letter-spacing:.08em;font-size:var(--fs-md)}
/* the flanks: matched pair, quiet but alive */
.postrip__side{min-height:44px;padding:0 var(--s5);background:rgba(255,255,255,.06);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  color:var(--text);border:1px solid rgba(255,255,255,.16);
  font-variant-numeric:tabular-nums}
.postrip__side:hover{border-color:rgba(var(--accent-rgb),.6);color:var(--accent)}
.postrip__side:disabled{opacity:.55;cursor:default}
/* while the panel is up, the Back/Skip pair stands down (Luke: they have no
   business at the end of a rip) */
[data-postrip="1"] .stage__actions,[data-postrip="1"] .turborow{opacity:0;pointer-events:none}
/* Back/Skip are gone from the product (Luke, 21 Aug): the rip pill stands
   down for the ENTIRE rip, not just purchases — the panel is the only exit */
[data-state="rip"] .stage__actions,[data-state="rip"] .turborow{opacity:0;pointer-events:none}

/* ── pay row, under the rip button ── */
/* Three equal cells, so the buttons are the same size BY CONSTRUCTION rather
   than by a min-width somebody tuned once against the word "Card". */
.pay{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s2);
  padding-bottom:var(--s1)}
[data-state="rip"] .pay{opacity:0;pointer-events:none;transition:opacity var(--dur-fast) var(--ease-out)}
.pay__btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;cursor:pointer;
  font:inherit;font-weight:600;font-size:var(--fs-sm);color:var(--text);
  background:rgba(255,255,255,.06);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  border:1px solid rgba(255,255,255,.12);border-radius:var(--r-md);
  padding:9px var(--s3);min-height:40px;width:100%;
  transition:border-color var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out)}
.pay__btn:hover{border-color:var(--accent);background:rgba(var(--accent-rgb),.08)}
.pay__g{font-family:var(--font-display);font-weight:700;font-size:15px;line-height:1}
.pay__btn--card{color:var(--text-dim)}

/* ── live pulls: rectangles with card images, not pills ── */
.hit{display:flex;align-items:center;gap:var(--s2);padding:5px var(--s3) 5px 5px;cursor:pointer;
  font:inherit;text-align:left;
  background:rgba(255,255,255,.05);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  border:1px solid rgba(255,255,255,.08);border-radius:var(--r-md);
  font-size:var(--fs-xs);white-space:nowrap;
  transition:border-color var(--dur-fast) var(--ease-out),transform var(--dur-fast) var(--ease-out)}
.hit:hover{border-color:rgba(255,255,255,.2);transform:translateY(-2px)}
/* rarity-coloured border + glow per pull (Luke, 20 Aug: common green,
   rare blue, epic purple, legendary gold — the box indicates the tier) */
.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),.45);
  box-shadow:0 0 14px -2px rgba(var(--hitc),.35),inset 0 0 10px -6px rgba(var(--hitc),.3)}
.hit[data-rarity]:hover{border-color:rgba(var(--hitc),.8);
  box-shadow:0 0 20px -2px rgba(var(--hitc),.5),inset 0 0 10px -6px rgba(var(--hitc),.3)}
.hit__img{width:34px;height:57px;object-fit:contain;border-radius:4px;flex:0 0 auto;
  background:rgba(0,0,0,.35);padding:2px;box-shadow:0 2px 10px rgba(0,0,0,.6)}
.hit__meta{display:flex;flex-direction:column;gap:1px;min-width:0}
.hit__time{color:var(--text-faint);font-variant-numeric:tabular-nums;font-size:11px}
.sheet__inspect{margin-top:var(--s3);min-height:40px;width:100%;color:var(--text);
  background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.16)}
.sheet__inspect:hover{border-color:rgba(var(--accent-rgb),.6);color:var(--accent)}
.hit__name{color:var(--text);font-weight:500;font-size:var(--fs-xs);
  max-width:150px;overflow:hidden;text-overflow:ellipsis}
.hit__sub{display:flex;gap:6px;align-items:baseline}
.hit__grade{color:var(--text-faint);font-variant-numeric:tabular-nums;font-size:11px}
.hit__val{color:var(--accent);font-variant-numeric:tabular-nums;font-weight:600;font-size:11px}
.hit__dot{display:none}


/* ── a pull opens ── */
.sheet{position:fixed;inset:0;z-index:var(--z-modal);display:grid;place-items:center;
  background:rgba(4,4,6,.72);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  opacity:0;visibility:hidden;transition:opacity 200ms var(--ease-out),visibility 200ms}
.sheet[data-open="1"]{opacity:1;visibility:visible}
.sheet__panel{display:flex;gap:var(--s6);align-items:center;max-width:min(680px,92vw);
  padding:var(--s6);border-radius:var(--r-lg);
  background:rgba(18,18,22,.86);border:1px solid rgba(255,255,255,.12);
  box-shadow:0 40px 90px -30px rgba(0,0,0,.9);
  transform:translateY(10px) scale(.98);transition:transform 220ms var(--ease-out)}
.sheet[data-open="1"] .sheet__panel{transform:none}
.sheet__img{width:min(230px,40vw);height:auto;border-radius:var(--r-sm);display:block;
  box-shadow:0 20px 50px -18px rgba(0,0,0,.9)}
.sheet__name{margin:0 0 var(--s2);font-family:var(--font-display);font-size:var(--fs-xl);font-weight:600}
.sheet__row{display:flex;gap:var(--s5);margin:0 0 var(--s4)}
.sheet__k{display:block;font-size:var(--fs-xs);color:var(--text-faint)}
.sheet__v{font-family:var(--font-display);font-weight:600;font-variant-numeric:tabular-nums}
.sheet__v--val{color:var(--accent)}
.sheet__close{position:absolute;top:var(--s5);right:var(--s6);cursor:pointer;font:inherit;
  background:transparent;border:0;color:var(--text-faint);font-size:26px;line-height:1;min-height:44px}
.sheet__close:hover{color:var(--text)}
@media (max-width:620px){.sheet__panel{flex-direction:column;text-align:center}}

/* below the shell line the stage owns the whole column: mobile.js has
   relocated the pack list into the dock and the rewards rail into its
   Daily Rewards sheet, so neither rail belongs in the grid any more.
   .rail--right also stays hidden if that script never runs. */
@media (max-width:1023px){
  .grid{grid-template-columns:1fr;grid-template-rows:auto minmax(0,1fr)}
  /* only the RIGHT rail is hidden here. mobile.css hides the left one too,
     but keeping that in the shell script's own sheet means a mobile.js
     failure still leaves a usable pack list rather than no way to pick a
     pack at all. */
  .rail--right{display:none}
}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ── POST-RIP SLAB CLEARANCE, 1024–1200 — AT END OF FILE ON PURPOSE ─────
   This is a geometry problem, not a layout tier, so it has its OWN line.
   .postrip and .poolrail are both absolute in #stage at min(440px,38vw),
   so at 1024 they want 389 + 389 = 778px around a held slab that is only
   ~235px wide — they close on the card from both sides (Luke on the first
   iPad landscape frame: "looks awful"). Between the shell line and ~1200
   the rails narrow to 304, take tighter padding, and the panel's content
   is pulled UP off the vertical centre so the slab stands clear with ~88px
   of air. Above 1200 the desktop values have room and take over.

   END OF FILE on purpose — the same cascade trap codex round 18 caught for
   the big-glass block: the base .postrip rules ship later in source order
   at equal specificity, so an override written further up silently loses
   (measured: .postrip stayed at min(440px,38vw) at 1024). ─────────────── */
@media (min-width:1024px) and (max-width:1200px){
  .postrip{width:min(304px,30vw);padding-right:var(--s4);align-items:flex-start}
  .postrip__rail{padding-top:9vh;gap:var(--s3)}
  .postrip__name{font-size:var(--fs-xl);line-height:1.16}
  .postrip__r{padding:9px 0}
  .postrip__hero{min-height:50px;padding:0 var(--s5)}
  .postrip__side{min-height:44px;padding:0 var(--s4)}
  /* the pool rail's matching geometry lives in poolrail.js — its
     stylesheet is injected at runtime and always ships after this file */
}

/* ── BIG GLASS: the chrome scales as whole layers ────────────────────────
   ONE mechanism, one dial (--ui, tokens.css). `zoom` scales an element and
   everything inside it — type, icons, gaps, strokes, SVG badges, the crate
   art — so the HUD grows as a piece instead of through a list of per-
   property overrides that could never agree with each other (that list was
   the ~1.5x attempt Luke rejected on sight).

   WHAT DOES NOT SCALE, and why the FRAMING CONTRACT is safe: the pack is
   drawn into #stageLayer, a fixed full-viewport canvas that pack3d.js
   prepends to <body> — outside .grid, so no zoom of ours can reach the
   camera, the fov or the pack's scale. The pack keeps riding viewport
   height exactly as before; only the chrome around it grows.

   Full-screen overlays (vault, odds, grail, how-it-works, inspect) are
   deliberately NOT zoomed: their panels are capped in vh (86vh/92vh) and
   vh resolves against the real viewport inside a zoomed box, so scaling
   them would push those caps past the screen. They already grow with the
   viewport through their own min()/vh sizing. ─────────────────────────── */
.header,.strip,.grid{zoom:var(--ui,1)}
/* the toast is a <body> child positioned inline by wallet.js (bottom:176px,
   over the pack's feet, never over the buttons) — zoom it so that 176px
   keeps clearing the now-taller control stack */
#walletToast{zoom:var(--ui,1)}
/* Everything else the tiers need lives in ONE query. Note the two
   corrections inside it:
   - `vh` inside a zoomed box still resolves against the REAL viewport, so
     the affordances positioned in vh to line up with the UNZOOMED 3D slab
     divide the scale back out (the share button under the held card and
     the invisible slab tap zone). .postrip / .poolrail themselves are
     edge-anchored and SHOULD grow, so they are left alone. These are
     scoped to the tier on purpose: unscoped, they ship at the end of the
     file and re-assert `bottom` over the tablet band's `bottom:auto`,
     which stretched the share button to 157px tall on iPad portrait.
   - the header band is the one chrome height written in svh (6.4svh) and
     would therefore scale twice, eating 12% of an ultrawide screen; it is
     pinned to the reference band (6.4svh of the 968-tall reference = 62px)
     so --ui scales it exactly once. */
@media (min-width:2000px){
  .postrip__share{bottom:calc(clamp(120px,20vh,220px) / var(--ui,1))}
  .postrip__slabhit{top:calc(11vh / var(--ui,1));height:calc(62vh / var(--ui,1));
    width:calc(min(340px,34vw) / var(--ui,1))}
  .header{height:62px}
  /* a rail can never be clipped by the scale-up: it scrolls before it cuts */
  .rail{overflow-y:auto;scrollbar-width:thin;
    scrollbar-color:rgba(255,255,255,.18) transparent}
  .rail > *{flex-shrink:0}
}
