/* The real pack and the new reveal (Dodd, 2026-10-25). pack-gl.js draws the pack in a canvas (.pack-gl); app.js moves
   the cards with inline transforms (put()), so the old class transforms (stacked, peek, flipped, away) don't apply on
   the .v3 opening. Order: tear -> light -> the stack rises -> the pack sinks -> one click turns a card (or drag it; rare ones shine bigger, nothing is held) -> click or flick
   into the fanned hand -> the spread with the tier summary. */

/* ---------- the 3D pack on Today: the canvas bleeds past the button so the lean never clips ---------- */
.pack.gl-on { position: relative; }
.pack.gl-on > .pack-gl { position: absolute; inset: -12% -25%; width: 150%; height: 124%; pointer-events: none; animation: glIn .35s ease-out both; }
.pack.gl-on .booster { visibility: hidden; }
@keyframes glIn { from { opacity: 0; } }
/* #215 the still render of the 3D pack (same place and size as the live canvas) stands in on the ring; never the flat picture */
.pack.gl-snap { position: relative; }
.pack.gl-snap > .pack-snap { position: absolute; inset: -12% -25%; width: 150%; height: 124%; pointer-events: none; }
.pack.gl-snap .booster { visibility: hidden; }
.pack-ring > .ring-pack:not(.is-front) > .pack-snap { animation: ringFloat 5.2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .pack-ring > .ring-pack:not(.is-front) > .pack-snap { animation: none; } }

/* ---------- the opening screen ---------- */
.opening > .pack-gl { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 2; pointer-events: none; transition: opacity .3s; }
.opening.v3 .opening-top, .opening.v3 .opening-hint { position: relative; z-index: 3; }
/* QA: a long hint breaks into even lines on a phone, never one word left alone */
.opening-hint { text-wrap: balance; padding-inline: 16px; }
/* #149: the 3D pack is a touch taller than its stage, so its hint sits just clear of the bottom crimp */
.opening.v3.gl .opening-hint { translate: 0 16px; }
.opening.v3 .opening-stage { z-index: 1; touch-action: none; }
.opening.gl .tear-pack { opacity: 0; animation: none; }
.opening.gl .tear-pack:focus-visible { opacity: 1; outline: 0; }
.opening.gl .tear-pack:focus-visible > * { visibility: hidden; }
.opening.gl .tear-pack:focus-visible::after { content: ""; position: absolute; inset: -6px; border-radius: 10px; box-shadow: 0 0 0 2px color-mix(in srgb, var(--t-titan) 70%, transparent); }
.opening.gl .opening-stage { cursor: grab; }

/* ---------- the way in (#79): the pack flies in, the desk blurs and dims behind it ---------- */
/* the screen's own shade and blur move onto one veil (app.js packEntrance fades it in over the flight): a fixed,
   pre-blurred layer whose opacity rises is cheap, an animated blur radius is not. Under everything on this screen
   (z -1), so the Titan's dim still darkens over it. The pack's own entrance is the flight, so the old rise is off. */
.opening.v3 { background: none; backdrop-filter: none; -webkit-backdrop-filter: none; }
.opening > .open-veil { position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(70% 60% at 50% 45%, rgba(50, 8, 12, .5), rgba(4, 3, 4, .9));
  backdrop-filter: blur(16px) saturate(.8); -webkit-backdrop-filter: blur(16px) saturate(.8); }
.opening.v3 .tear-pack { animation: none; }

/* the cards: transforms come from app.js; the old class-driven motion is off here */
.opening.v3 .big-card { transition: none; animation: none; will-change: transform; touch-action: none; cursor: grab; }
.opening.v3 .big-card.away { opacity: 1; pointer-events: none; }
/* no black cloud as the stack comes out: pack3d.css gives every card back a big .7 drop shadow, and ten of them stacked
   inside the torn pack add up to a dark box round it. Only the card on top casts one, once it's out and waiting. */
.opening.v3 .big-card .card-down { box-shadow: 0 0 0 1px rgba(236, 228, 216, .08); transition: box-shadow .3s ease-out; }
.opening.v3 .big-card.tease .card-down { box-shadow: 0 2cqw 6cqw rgba(0, 0, 0, .5), 0 0 0 1px rgba(236, 228, 216, .08); }

/* the tease: before it's turned, the edge glows in the tier colour (stronger the rarer) */
.opening.v3 .big-card .back::before { content: ""; position: absolute; inset: -1px; z-index: 2; pointer-events: none; border-radius: 3.6% / 2.4%; opacity: 0; transition: opacity .35s;
  box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--c, #fff) calc(var(--tease, 0) * 90%), transparent),
    0 0 calc(10px + var(--tease, 0) * 40px) 0 color-mix(in srgb, var(--c, #fff) calc(20% + var(--tease, 0) * 45%), transparent); }
.opening.v3 .big-card.tease .back::before { opacity: 1; animation: teasePulse 2.4s ease-in-out infinite alternate; }
@keyframes teasePulse { from { opacity: .55; } to { opacity: 1; } }

/* the turn: one click and the card shines as it lands, a light sweep across the foil (softer on the common tiers);
   the rare ones also bloom in their tier colour (the Titan gets its own moment below) */
.opening.v3 .big-card.sweep .front::after { content: ""; position: absolute; inset: 0; z-index: 5; pointer-events: none; border-radius: 3.6% / 2.4%; mix-blend-mode: screen;
  background: linear-gradient(115deg, transparent 32%, rgba(255, 248, 230, .6) 47%, color-mix(in srgb, var(--c) 40%, transparent) 53%, transparent 68%) 0 0 / 260% 100% no-repeat;
  animation: foilSweep .85s cubic-bezier(.3, .6, .2, 1) both; }
.opening.v3 .big-card.sweep:is([data-rarity="mortal"], [data-rarity="hero"]) .front::after { opacity: .55; }
.opening.v3 .big-card.sweep:is([data-rarity="oracle"], [data-rarity="demigod"]) .front { animation: rareBloom 1s cubic-bezier(.23, 1, .32, 1) both; }
@keyframes foilSweep { from { background-position: 100% 0; } to { background-position: 0 0; } }
@keyframes rareBloom { from { box-shadow: 0 0 0 2px color-mix(in srgb, var(--c) 85%, transparent), 0 0 90px 16px color-mix(in srgb, var(--c) 50%, transparent); }
  to { box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent; } }

/* the Titan: the room dims, the stage shudders once, the card burns gold */
.opening.v3::before { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: 0;
  background: radial-gradient(60% 55% at 50% 45%, color-mix(in srgb, var(--t-titan) 14%, transparent), rgba(0, 0, 0, .72) 75%); }
.opening.v3.titan-moment::before { animation: titanDim 2.2s ease both; }
.opening.v3.titan-moment .opening-stage { animation: titanShake .45s cubic-bezier(.36, .07, .19, .97) .12s both; }
.opening.v3.titan-moment .big-card.flipped .front { box-shadow: 0 0 0 2px color-mix(in srgb, var(--t-titan) 80%, transparent), 0 0 80px 12px color-mix(in srgb, var(--t-titan) 45%, transparent); transition: box-shadow .4s; }
@keyframes titanDim { 0% { opacity: 0; } 15% { opacity: 1; } 70% { opacity: 1; } 100% { opacity: 0; } }
@keyframes titanShake { 10%, 90% { translate: -1px 0; } 20%, 80% { translate: 3px 1px; } 30%, 50%, 70% { translate: -5px -1px; } 40%, 60% { translate: 5px 1px; } }
/* the Mythic (board #167, "it doesn't get better than that"): the biggest moment in the app. app.js mythicMoment: the room
   goes darker and redder for longer than a Titan's, the stage shudders harder, the card burns red with a bloom twice the
   Titan's, and a red flare ring opens behind it. Reduced motion: none of it (the card's red frame says it). */
.opening.v3.mythic-moment::before { background: radial-gradient(70% 62% at 50% 45%, color-mix(in srgb, var(--t-mythic) 30%, transparent), rgba(8, 0, 2, .86) 78%); animation: titanDim 3.4s ease both; }
.opening.v3.mythic-moment .opening-stage { animation: mythicShake .7s cubic-bezier(.36, .07, .19, .97) .08s both; }
.opening.v3.mythic-moment .big-card.flipped .front { box-shadow: 0 0 0 3px color-mix(in srgb, var(--t-mythic) 90%, transparent), 0 0 160px 34px color-mix(in srgb, var(--t-mythic) 55%, transparent); transition: box-shadow .4s; }
.opening.v3 > .mythic-flare { position: absolute; left: var(--fx-x, 50%); top: var(--fx-y, 45%); z-index: 40; mix-blend-mode: screen; width: 40px; height: 40px; margin: -20px 0 0 -20px; border-radius: 50%; pointer-events: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--t-mythic) 85%, transparent), 0 0 60px 20px color-mix(in srgb, var(--t-mythic) 45%, transparent), inset 0 0 30px color-mix(in srgb, var(--t-mythic) 60%, transparent);
  animation: mythicFlare 1.6s cubic-bezier(.16, 1, .3, 1) both; }
@keyframes mythicFlare { 0% { opacity: 0; scale: .2; } 12% { opacity: 1; } 100% { opacity: 0; scale: 26; } }
@keyframes mythicShake { 8%, 92% { translate: -2px 0; } 18%, 82% { translate: 5px 2px; } 28%, 48%, 68% { translate: -8px -2px; } 38%, 58% { translate: 8px 2px; } }

/* ---------- the hand: the cards you've pulled, fanned at the bottom ---------- */
/* big enough to read, and always fully on screen: the row is as tall as a card plus the fan's droop (RevealRules.fan),
   and the card being turned gives up the height the hand needs on short screens */
.opening.v3 { --card-h: clamp(92px, 16vh, 150px); --hand-drop: calc(22px + var(--card-h) * .1); }
.opening.v3 .big-card { height: min(64vh, 640px, calc(100dvh - var(--card-h) - var(--hand-drop) - 170px)); }
.opening.v3 .pulled { position: relative; z-index: 3; display: block; width: min(720px, 100%); height: calc(var(--card-h) + var(--hand-drop)); min-height: 0; max-width: none; margin-top: 8px; }
.opening.v3 .pulled:empty { visibility: hidden; }
.hand-card { position: absolute; left: 50%; bottom: var(--hand-drop, 0px); height: var(--card-h, 108px); aspect-ratio: 1024 / 1536; margin-left: calc(var(--card-h, 108px) / -3); transform-origin: 50% 100%;
  border-radius: 6px; box-shadow: 0 6px 14px rgba(0, 0, 0, .45), 0 0 0 1px color-mix(in srgb, var(--c) 55%, transparent), 0 0 16px color-mix(in srgb, var(--c) 22%, transparent);
  transition: translate .2s cubic-bezier(.23, 1, .32, 1), scale .2s cubic-bezier(.23, 1, .32, 1); }
.hand-card .tc { height: 100%; }
/* style.css's old thumbnail rule (.pulled span: 34x51 boxes) hit every span inside the cards and blacked out their text
   boxes; undo just what it sets (the card's own span rules are more specific and still win) */
.pulled .hand-card :where(span) { width: auto; height: auto; border-radius: 0; box-shadow: none; animation: none; background: none; }
/* look at any card in the hand: it lifts out of the fan and grows enough to read */
@media (hover: hover) and (pointer: fine) {
  .opening.v3 .pulled .hand-card:hover { translate: 0 -12px; scale: 1.45; z-index: 40 !important; }
}

/* ---------- the end: the whole pack spread out, the tier summary, the best pull ---------- */
.opening.v3.spread-on .opening-stage, .opening.v3.spread-on .opening-hint, .opening.v3.spread-on .pulled { visibility: hidden; }
.opening.v3 .opening-done { z-index: 4; align-content: center; gap: 10px; background: radial-gradient(70% 60% at 50% 50%, rgba(8, 5, 6, .55), transparent); }
/* the done screen covers the whole stage: let clicks through its empty space so Close (top right) still works */
.opening.v3 .opening-done { pointer-events: none; } .opening.v3 .opening-done > * { pointer-events: auto; }
.opening.v3 .opening-top { z-index: 5; }
.opening.v3 .opening-done h2 { font-size: clamp(30px, 4vw, 52px); }
.opening.v3 .opening-done #doneTiers { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin: 0; }
.tier-chip { display: inline-flex; align-items: baseline; gap: 6px; padding: 5px 12px; border-radius: 999px; font-size: 14px; letter-spacing: .04em; color: var(--bone, #ece4d8);
  border: 1px solid color-mix(in srgb, var(--c) 55%, transparent); background: color-mix(in srgb, var(--c) 10%, rgba(10, 7, 8, .7)); }
.tier-chip b { color: var(--c); font-size: 16px; }
.done-best { margin: 0; font-size: 15px; color: var(--bone-2, #cfc5b8); }
.done-best b { color: var(--c); font-weight: 600; }
.done-market { display: block; margin-top: 4px; font-size: .9em; opacity: .85; }
.done-spread { display: grid; grid-template-columns: repeat(var(--cols, 5), var(--w, 120px)); gap: var(--gap, 12px); justify-content: center; margin: 8px 0 14px; }
.done-spread .hand-card { position: relative; left: auto; bottom: auto; margin: 0; width: var(--w, 120px); height: auto; transform: none; }
.done-spread .hand-card .tc { height: auto; width: 100%; }


/* reduced motion: nothing travels or turns on its own; faces and cards cross-fade */
.opening.v3.rm .big-card.flipped .front { animation: rmFade .2s ease-out both; }
.opening.v3.rm .big-card .back::before, .opening.v3.rm .opening-stage { animation: none; }
.opening.v3.rm .big-card.sweep .front::after { display: none; }
.opening.v3.rm .big-card.sweep:is([data-rarity]) .front { animation: rmFade .2s ease-out both; }
.opening.v3.rm .pulled .hand-card { transition: none; }
@keyframes rmFade { from { opacity: 0; } }

/* ---------- #111 quick by default: all ten deal out face up at once (app.js dealAll) ----------
   The spread is the done screen from the first frame: the cards fly in from the stack (inline WAAPI on translate /
   scale / rotate, the turn on transform), the words and buttons rise in under them. .fanned = each row leans out from its
   middle card (--fr) and droops at the ends (--fy). Every card shines once as it lands; the rare tiers bloom in their
   colour, the best pull (data-best) a little longer. */
.opening.v3.quick .opening-done { animation: none; }
.opening.v3.quick .opening-done > :not(.done-spread) { animation: dealUi .42s cubic-bezier(.23, 1, .32, 1) both; }
.opening.v3.quick .opening-done > h2 { animation-delay: .12s; }
.opening.v3.quick .opening-done > #doneTiers { animation-delay: .2s; }
.opening.v3.quick .opening-done > .done-best { animation-delay: .5s; }
.opening.v3.quick .opening-done > #startMessaging { animation-delay: .28s; }
@keyframes dealUi { from { opacity: 0; translate: 0 8px; } }
/* #148 the deal: the cards land face down in a neat 2 x 5 grid, then each turns over in reading order (app.js dealAll,
   turnCard). .down = still face down (the worn card back, pack3d.css .card-down, shows); .turning = mid-turn. A real 3D
   turn: .turn holds both faces back to back and turns about its height inside the card's own perspective. Nothing
   tells a card's tier until it's face up (no rim, no glow), and the words and "Start messaging" wait until every card
   is (.dealing on the opening screen). */
.opening.v3.quick.dealing .opening-done > :not(.done-spread) { visibility: hidden; animation: none; }
.done-spread .hand-card:has(> .turn) { perspective: 1100px; isolation: isolate; }
.done-spread .hand-card .turn { position: relative; transform-style: preserve-3d; transform-origin: 50% 50%; }
.done-spread .hand-card.down .turn { transform: rotateY(180deg); }
.done-spread .hand-card .turn-face { -webkit-backface-visibility: hidden; backface-visibility: hidden; }
.done-spread .hand-card .turn-back { position: absolute; inset: 0; transform: rotateY(180deg); pointer-events: none; }
.done-spread .hand-card .turn-back .card-down { box-shadow: 0 4px 12px rgba(0, 0, 0, .5), 0 0 0 1px rgba(236, 228, 216, .1); }
.done-spread.fanned .hand-card { rotate: var(--fr, 0deg); translate: 0 var(--fy, 0px); transform-origin: 50% 50%; isolation: isolate; }
/* no dark cloud while they fly: a card scaled up 3x from the stack would carry a 3x shadow and glow (see .big-card .card-down above) */
.done-spread .hand-card.flying { box-shadow: 0 0 0 1px rgba(236, 228, 216, .08); }
/* landing: a light sweep across the foil for every card (softer on the common tiers), a bloom for the rare ones */
.done-spread .hand-card.landed::after { content: ""; position: absolute; inset: 0; z-index: 5; pointer-events: none; border-radius: 6px; mix-blend-mode: screen;
  background: linear-gradient(115deg, transparent 32%, rgba(255, 248, 230, .55) 47%, color-mix(in srgb, var(--c) 45%, transparent) 53%, transparent 68%) 0 0 / 260% 100% no-repeat;
  animation: foilSweep .7s cubic-bezier(.3, .6, .2, 1) both; }
.done-spread .hand-card.landed:is([data-rarity="mortal"], [data-rarity="hero"])::after { opacity: .45; }
.done-spread .hand-card.landed:is([data-rarity="oracle"], [data-rarity="demigod"], [data-rarity="titan"], [data-rarity="mythic"]) { animation: dealBloom 1.1s cubic-bezier(.23, 1, .32, 1) both; }
.done-spread .hand-card.landed[data-best] { animation: dealBloomBest 1.8s cubic-bezier(.23, 1, .32, 1) both; z-index: 2; }
/* #167 a Mythic best pull: a red bloom more than twice the Titan's, held longer, then a deeper red rim that stays */
.done-spread .hand-card.landed[data-best][data-rarity="mythic"] { animation: mythicBloom 2.8s cubic-bezier(.2, 1, .3, 1) both; z-index: 3; }
.done-spread .hand-card[data-best][data-rarity="mythic"] { box-shadow: 0 6px 14px rgba(0, 0, 0, .45), 0 0 0 1.5px var(--c), 0 0 34px 4px color-mix(in srgb, var(--c) 45%, transparent); }
@keyframes mythicBloom {
  0% { box-shadow: 0 6px 14px rgba(0, 0, 0, .45), 0 0 0 3px var(--c), 0 0 160px 44px color-mix(in srgb, var(--c) 75%, transparent); }
  35% { box-shadow: 0 6px 14px rgba(0, 0, 0, .45), 0 0 0 2.5px var(--c), 0 0 120px 30px color-mix(in srgb, var(--c) 60%, transparent); }
  100% { box-shadow: 0 6px 14px rgba(0, 0, 0, .45), 0 0 0 1.5px var(--c), 0 0 34px 4px color-mix(in srgb, var(--c) 45%, transparent); } }
.done-spread .hand-card.landed[data-best]::after { animation-duration: 1.05s; opacity: 1; }
@keyframes dealBloom {
  from { box-shadow: 0 6px 14px rgba(0, 0, 0, .45), 0 0 0 2px color-mix(in srgb, var(--c) 90%, transparent), 0 0 46px 8px color-mix(in srgb, var(--c) 55%, transparent); } }
@keyframes dealBloomBest {
  0% { box-shadow: 0 6px 14px rgba(0, 0, 0, .45), 0 0 0 2px var(--c), 0 0 70px 18px color-mix(in srgb, var(--c) 65%, transparent); }
  60% { box-shadow: 0 6px 14px rgba(0, 0, 0, .45), 0 0 0 1.5px color-mix(in srgb, var(--c) 80%, transparent), 0 0 34px 6px color-mix(in srgb, var(--c) 35%, transparent); }
  100% { box-shadow: 0 6px 14px rgba(0, 0, 0, .45), 0 0 0 1px color-mix(in srgb, var(--c) 70%, transparent), 0 0 22px color-mix(in srgb, var(--c) 30%, transparent); } }
/* the best pull keeps a soft rim in its colour after the bloom, so the eye finds it again */
.done-spread .hand-card[data-best] { box-shadow: 0 6px 14px rgba(0, 0, 0, .45), 0 0 0 1px color-mix(in srgb, var(--c) 70%, transparent), 0 0 22px color-mix(in srgb, var(--c) 30%, transparent); }
/* face down or mid-turn: no tier rim (the faces carry their own shadow, which turns with them) */
.done-spread .hand-card:is(.down, .turning), .done-spread .hand-card[data-best]:is(.down, .turning) { box-shadow: none; }
@media (hover: hover) and (pointer: fine) {
  .done-spread.fanned .hand-card:hover { translate: 0 calc(var(--fy, 0px) - 8px); scale: 1.06; z-index: 3; }
}
.opening.v3.rm .done-spread .hand-card.landed, .opening.v3.rm .done-spread .hand-card.landed::after { animation: none; }
.opening.v3.rm.quick .opening-done > :not(.done-spread) { animation: rmFade .2s ease-out both; }
.opening.v3.rm.quick .done-spread { animation: rmFade .2s ease-out both; }
/* ---------- #156 the opening's light per pack type (app.js fxCharge / fxBurst; the numbers are RevealRules.OPEN) ----------
   --fx-c (the light) and --fx-c2 (its hot core) are set on #opening per pack: Daily white, Bonus green, Boosted
   blue-purple, Legendary gold, Mythic deep red. .fx-dim darkens the room round the pack while a rarer one charges (under
   the pack and the cards); .open-fx is the burst at the mouth (over the pack, under the words). Light and sparks only. */
.opening > .fx-dim { position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: 0;
  background: radial-gradient(58% 52% at 50% 46%, color-mix(in srgb, var(--fx-c, #fff) 14%, rgba(0, 0, 0, .25)), rgba(0, 0, 0, .94) 78%); }
.opening > .open-fx { position: absolute; inset: 0; z-index: 2; pointer-events: none; overflow: hidden; }
.opening > .open-fx.fx-back { z-index: 0; } /* the shafts, shockwaves and sparks: behind the pack, out from its edges */
.open-fx > i { position: absolute; display: block; pointer-events: none; opacity: 0; will-change: transform, opacity; }
.open-fx .fx-flash { inset: 0; transform: none;
  background: radial-gradient(75% 65% at var(--x) var(--y), color-mix(in srgb, var(--fx-c2) 75%, transparent), color-mix(in srgb, var(--fx-c) 45%, transparent) 38%, transparent 78%); }
.open-fx .fx-core { left: var(--x); top: var(--y); width: calc(var(--w) * 1.6); aspect-ratio: 1; border-radius: 50%; transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, #fff 0 6%, var(--fx-c2) 16%, color-mix(in srgb, var(--fx-c) 80%, transparent) 38%, color-mix(in srgb, var(--fx-c) 25%, transparent) 62%, transparent); }
.open-fx .fx-rays { left: var(--x); top: var(--y); width: calc(var(--w) * 4.6); aspect-ratio: 1; transform: translate(-50%, -50%);
  /* two sets of shafts at different counts and widths, so the burst is uneven like real light, not a drawn sunburst */
  background: repeating-conic-gradient(from -2deg, color-mix(in srgb, var(--fx-c2) 60%, transparent) 0deg 1.2deg, color-mix(in srgb, var(--fx-c) 40%, transparent) 1.2deg 3.6deg, transparent 5.5deg calc(360deg / var(--n))),
    repeating-conic-gradient(from 7deg, color-mix(in srgb, var(--fx-c) 22%, transparent) 0deg 6deg, transparent 9deg calc(360deg / var(--n) * 1.63));
  -webkit-mask: radial-gradient(closest-side, transparent 12%, #000 22%, rgba(0, 0, 0, .6) 50%, transparent 78%); mask: radial-gradient(closest-side, transparent 12%, #000 22%, rgba(0, 0, 0, .6) 50%, transparent 78%); }
.open-fx .fx-ring { left: var(--x); top: var(--y); width: calc(var(--w) * 1.1); aspect-ratio: 1; border-radius: 50%; transform: translate(-50%, -50%);
  /* a soft band of light, not a drawn line: a shockwave of air */
  background: radial-gradient(closest-side, transparent 64%, color-mix(in srgb, var(--fx-c) 30%, transparent) 78%, color-mix(in srgb, var(--fx-c2) 55%, transparent) 88%, color-mix(in srgb, var(--fx-c) 25%, transparent) 94%, transparent); }
.open-fx .fx-ember { left: var(--x); top: var(--y); width: 3px; height: 3px; border-radius: 50%; transform: translate(-50%, -50%);
  background: var(--fx-c); box-shadow: 0 0 6px 1px var(--fx-c); }
.open-fx .fx-ember.hot { background: #fff; box-shadow: 0 0 5px 1px var(--fx-c2), 0 0 12px 3px var(--fx-c); }
@media (prefers-reduced-motion: reduce) { .open-fx .fx-rays, .open-fx .fx-ring, .open-fx .fx-ember { display: none; } .opening > .fx-dim { display: none; } }
/* #156 a phone's spread is 2 across and 5 down (RevealRules.spread): taller than the screen, so the end screen scrolls
   (app.js follows the card that's turning); it starts at the top instead of the middle */
.opening.v3 .opening-done.scrolls { inset: 106px 0 0; place-content: start center; align-content: start; overflow-y: auto; overscroll-behavior: contain; pointer-events: auto;
  padding: 10px 12px calc(28px + env(safe-area-inset-bottom)); scrollbar-width: none;
  -webkit-mask-image: linear-gradient(transparent, #000 18px); mask-image: linear-gradient(transparent, #000 18px); } /* scrolls under a soft edge below the top bar, never over Close */
.opening.v3 .opening-done.scrolls h2 { font-size: clamp(28px, 8vw, 40px); }
.opening.v3 .opening-done.scrolls::-webkit-scrollbar { display: none; }

/* ---------- #166 a bigger, simpler opening (Dodd, 10-26: "enlarge it all, it's really tiny"; the top text "get rid of
   that completely, simplify this menu as much as possible"; "Start messaging and the Close button both at the bottom") ----------
   The pack's name line, the done headline and the tier chips are gone from sight (kept for screen readers). Close sits
   alone top right while the pack opens and the cards deal; "Reveal the rest" waits at the bottom while they deal (app.js
   moves it into .done-acts), and Start messaging and Close then come in side by side in the same place. .roomy = the
   quick deal (no hand at the bottom): the pack a touch bigger (+10%), the cards much bigger. */
.opening.v3 { grid-template-rows: 1fr auto auto; }
.opening.v3 .opening-top { position: absolute; top: 0; left: 0; right: 0; width: auto; min-height: 0; padding: 16px 20px; display: flex; justify-content: flex-end; gap: 10px; pointer-events: none; }
.opening.v3 .opening-top > * { pointer-events: auto; }
.opening.v3 #openCount { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; margin: 0; }
.opening.v3.roomy .pulled { display: none; }
/* the pack: the size it always had, plus 10% (Dodd, 10-26: "a little awkwardly too big... make it like 10% larger
   than [the original], that's all"); app.js packH follows the same 1.1 */
.opening.v3.roomy .big-card, .opening.v3.roomy .tear-pack { height: min(calc(1.1 * min(64vh, 640px, calc(100dvh - var(--card-h) - var(--hand-drop) - 170px))), calc((100vw - 32px) * 1.5)); }
/* the done screen: the cards, a quiet best-pull line, the two buttons */
.opening.v3 .opening-done { padding: 64px 16px 24px; gap: 10px; }
.opening.v3 .done-spread { margin: 0; }
.opening.v3 .done-best { font-size: clamp(14px, 1.25vh, 18px); letter-spacing: .02em; line-height: 1.3; }
.done-acts { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 12px; }
/* the buttons keep pace with the cards on a big screen (they looked tiny under 360 px cards at 2560) */
.done-acts .btn { min-width: clamp(168px, 11vw, 240px); min-height: clamp(48px, 4.2vh, 62px); font-size: clamp(15px, 1.3vh, 19px); white-space: nowrap; }
.done-acts #revealAll { min-width: clamp(200px, 13vw, 280px); }
/* once the cards are all face up the top Close steps aside (the bottom one takes over); while they deal it stays */
.opening.v3.spread-on:not(.dealing) .opening-top #closePack { visibility: hidden; }
.opening.v3.quick.dealing .opening-done > .done-acts { visibility: visible; animation: dealUi .42s cubic-bezier(.23, 1, .32, 1) .5s both; }
.opening.v3.quick.dealing .done-acts > :not(#revealAll) { display: none; }
.opening.v3.quick .opening-done > .done-acts { animation-delay: .16s; }
.opening.v3.quick .opening-done > .done-best { animation-delay: .3s; }
/* a phone: the grid scrolls under the top bar; the buttons stay pinned at the bottom */
.opening.v3 .opening-done.scrolls { inset: 64px 0 0; padding: 6px 12px 0; }
.opening.v3 .opening-done.scrolls > .done-acts { position: sticky; bottom: 0; z-index: 2; justify-self: stretch; padding: 14px 4px calc(16px + env(safe-area-inset-bottom));
  background: linear-gradient(transparent, rgba(8, 5, 6, .86) 34%); }
.opening.v3 .opening-done.scrolls .done-acts .btn { min-width: 0; flex: 1 1 0; padding-inline: 10px; font-size: 15px; }
@media (prefers-reduced-motion: reduce) { .opening.v3.quick.dealing .opening-done > .done-acts { animation: none; } }
/* #166 (Dodd: "the background is darkened out and waiting to load in... make it look like they're already loaded in
   and moving"): the world behind the opening stays lit and keeps moving; the veil only softens it (a lighter shade
   and blur, in quickly), enough for the pack and the cards to stand out */
.opening > .open-veil { background: radial-gradient(75% 65% at 50% 45%, rgba(40, 8, 12, .22), rgba(4, 3, 4, .62));
  backdrop-filter: blur(9px) saturate(.92); -webkit-backdrop-filter: blur(9px) saturate(.92); }
.opening.v3 .opening-done { background: radial-gradient(70% 60% at 50% 50%, rgba(8, 5, 6, .42), transparent); }
/* the 3D pack fades in over the 2D one (never through an empty, dark gap) */
.pack.gl-on .booster { transition: visibility 0s linear .36s; }
