/* ==========================================================================
   DAGLINGS — the pack opening sequence
   Tear, glow, reveal. Everything animates on transform and opacity so it
   composites; nothing animates a layout property.
   ========================================================================== */

.opening {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  background:
    radial-gradient(60% 50% at 50% 42%, rgba(12, 32, 40, 0.86), rgba(4, 7, 12, 0.97) 70%),
    rgba(4, 7, 12, 0.97);
  backdrop-filter: blur(3px);
  animation: opening-in 240ms ease-out both;
}
@keyframes opening-in { from { opacity: 0 } to { opacity: 1 } }

.opening__stage {
  position: relative;
  width: min(1180px, 94vw);
  height: 100%;
  display: grid;
  place-items: center;
}

.opening__mute {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 16px);
  right: 18px;
  padding: 7px 14px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: rgba(226, 255, 250, 0.72);
  font: 600 11px/1 "IBM Plex Sans", system-ui, sans-serif;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;
}
.opening__mute:hover { background: rgba(255, 255, 255, 0.12); }

.opening--failed { background: rgba(4, 7, 12, 0.8); }
.opening__error {
  padding: 16px 22px;
  background: #16202e;
  border-left: 2px solid #ff6a3d;
  color: #eef3fb;
  font: 400 15px/1.5 "IBM Plex Sans", system-ui, sans-serif;
}

/* --- the pack, mid-tear --------------------------------------------------- */

.opening__pack {
  position: relative;
  width: clamp(200px, 30vh, 320px);
  aspect-ratio: 2 / 3;
  /* The grip point is the lower third — that is where a hand would be, and
     shaking about the centre reads as a jitter rather than as effort. */
  transform-origin: 50% 72%;
  transition: transform 520ms cubic-bezier(.4, 0, .7, .4), opacity 520ms ease-in;
}
.opening__pack { perspective: 1200px; }
.opening__pack > .pack { position: absolute; inset: 0; }
/* The pack is being torn, not sitting on a shelf — the idle breathe reads as
   a wobble under the shake and fights the peel. */
.opening__pack .pack__body { animation: none; }

/* Effortful, not jittery: a slow-ish oscillation with unequal swings, as if
   something is resisting. --shake-ms is set per beat from open.js. */
.opening__pack.is-shaking {
  animation: pack-struggle var(--shake-ms, 350ms) cubic-bezier(.36, .07, .19, .97) both;
}
@keyframes pack-struggle {
  0%   { transform: rotate(0deg) translateX(0); }
  14%  { transform: rotate(-1.9deg) translateX(-4px); }
  30%  { transform: rotate(1.5deg) translateX(3px); }
  46%  { transform: rotate(-2.3deg) translateX(-5px); }
  62%  { transform: rotate(1.2deg) translateX(3px); }
  80%  { transform: rotate(-0.9deg) translateX(-2px); }
  100% { transform: rotate(0deg) translateX(0); }
}

/* The dark inside of the pouch, seen through the opening. */
.opening__interior {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, #01060a 0%, #04121a 40%, #010609 100%);
}

/* THE CURLING STRIP — one piece of foil that BENDS.

   A clipped copy is flat and cannot bend, so the curl is nested hinged
   segments: each .opening__seg holds its own slice of the wrapper AND the
   next segment, so the rotations compose down the chain. The slice at the
   tear-front barely turns, the free end has folded right back, and every join
   between them is a hinge the two sides share rather than a cut.

   Two things keep the seam shut, and both matter:
     - every hinge sits ON the tear line (--tear) at its own x (--hinge), and
       the slices are clipped at interpolated points on that same line, so no
       join can open a gap however far the segments rotate;
     - nothing TRANSLATES. An earlier pass lifted the flap off its hinge,
       which opened the seam by exactly the lift distance and made the strip
       read as separate pieces. All separation is rotation now.

   preserve-3d matters too: without it each nested rotateY is flattened into
   its parent's plane and the curl collapses back into a single flat kink. */
.opening__flap {
  position: absolute;
  inset: 0;
  transform-origin: var(--front, 0%) var(--tear, 9.6%);
  transform-style: preserve-3d;
  transition:
    transform 340ms cubic-bezier(.3, .72, .28, 1),
    opacity 340ms linear;
  will-change: transform;
}

.opening__seg {
  position: absolute;
  inset: 0;
  transform-origin: var(--hinge, 0%) var(--tear, 9.6%);
  transform: rotate(var(--sz, 0deg)) rotateY(var(--sy, 0deg));
  transform-style: preserve-3d;
  transition: transform 340ms cubic-bezier(.3, .72, .28, 1);
}

/* The clip transitions too — that is what makes the tear-front TRAVEL across
   the width instead of jumping to the next position. */
.opening__segpack {
  position: absolute;
  inset: 0;
  transition: clip-path 340ms cubic-bezier(.3, .72, .28, 1);
  /* The shadow goes on the slices, not on the flap: a filter forces its own
     subtree flat, and on the flap it would undo the entire nested 3D curl. */
  filter: drop-shadow(-2px 5px 9px rgba(0, 0, 0, 0.7));
  /* No backface-visibility: hidden here. A segment that turns past 90deg
     would vanish outright rather than showing the back of the foil, which is
     a hole in the middle of the strip. The angles are capped instead. */
}

/* THE STRUGGLE, in the strip. During each shake the lifted part tugs around
   its hinge — short pulls that get nowhere, because the tear has not given
   yet. The planted end never moves, which is what makes it read as fingers
   working at a flap rather than the whole pack wobbling. */
.opening__pack.is-shaking .opening__flap {
  animation: flap-tug var(--shake-ms, 350ms) cubic-bezier(.36, .07, .19, .97) both;
}
@keyframes flap-tug {
  0%   { transform: rotate(0deg); }
  16%  { transform: rotate(3.4deg) translateX(-1.5px); }
  34%  { transform: rotate(-1.2deg); }
  52%  { transform: rotate(4.3deg) translateX(-2.5px); }
  70%  { transform: rotate(-0.7deg); }
  86%  { transform: rotate(2.1deg) translateX(-1px); }
  100% { transform: rotate(0deg); }
}

/* Letting go. It comes off the last corner and drops a little under its own
   weight first; only then does it tumble away. */
.opening__flap.is-loose {
  transition: transform 180ms cubic-bezier(.4, 0, .9, .6);
  transform: rotate(9deg) translate(1.5%, 3%);
}
.opening__flap.is-gone {
  transition:
    transform 840ms cubic-bezier(.3, .08, .6, 1),
    opacity 840ms ease-in 420ms;
  transform: translate(9%, -132vh) rotate(104deg);
  opacity: 0;
}

/* The still-sealed remainder of the strip does not move at all. It is the
   reference the peel is measured against — if it drifted, nothing would read
   as attached. */
.opening__sealed { position: absolute; inset: 0; }

/* --- the glow beat -------------------------------------------------------- */

.opening__glow {
  position: absolute;
  left: -30%;
  right: -30%;
  top: calc(var(--tear-y, 10%) - 26%);
  height: 56%;
  pointer-events: none;
  background: radial-gradient(
    50% 46% at 50% 62%,
    rgba(73, 224, 192, 0.55) 0%,
    rgba(73, 224, 192, 0.22) 34%,
    transparent 72%
  );
  mix-blend-mode: screen;
  animation: opening-bloom 620ms ease-out both;
  transition: filter 180ms ease-out;
}
@keyframes opening-bloom {
  0%   { opacity: 0; transform: scale(0.72); }
  40%  { opacity: 1; transform: scale(1.04); }
  100% { opacity: 0.86; transform: scale(1); }
}
/* The tell, mid-glow. No class at all when the pack is all plain — the teal
   bloom on its own means exactly that, and it has to stay honest now there is
   no guarantee. Holo is a brightness shimmer with no hue shift; red and gold
   swing the colour, and gold swings hardest.

   A filter, NOT a second animation: .opening__glow is already running
   opening-bloom, and an `animation` on the modifier would replace it and kill
   the bloom mid-flight. */
.opening__glow.is-holo  { filter: brightness(1.42) saturate(1.2); }
.opening__glow.is-red   { filter: hue-rotate(-150deg) saturate(1.7) brightness(1.35); }
.opening__glow.is-gold  { filter: hue-rotate(-70deg) saturate(1.8) brightness(1.5); }
/* Purple outranks gold, so it swings furthest and burns brightest. */
.opening__glow.is-purple {
  filter: hue-rotate(155deg) saturate(2.1) brightness(1.65);
}

.opening__ember {
  position: absolute;
  left: var(--x);
  bottom: 38%;
  width: var(--s);
  height: var(--s);
  background: #9ffbe6;
  box-shadow: 0 0 6px rgba(73, 224, 192, 0.9);
  animation: opening-ember var(--dur) ease-out var(--delay) both;
}
@keyframes opening-ember {
  0%   { opacity: 0; transform: translate(0, 0) scale(0.6); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), -190px) scale(0.3); }
}

/* --- the reveal ----------------------------------------------------------- */

.opening__stage.is-revealing { cursor: pointer; }

.opening__deck {
  position: absolute;
  top: 6vh;
  display: grid;
  justify-items: center;
  transition: opacity 380ms ease-out, transform 380ms ease-in;
}
.opening__deck.is-empty { opacity: 0; transform: scale(0.94); }

/* While the stack is still coming out, the deck lives INSIDE the pack holder
   and is ordered before the pack front, so the front occludes whatever has
   not cleared the tear yet. --rise is measured in open.js. */
.opening__deck--emerging {
  position: absolute;
  top: 16%;
  left: 50%;
  /* Scaled to fit the pack's mouth — see emerge() in open.js, which measures
     both and sets --emerge-scale. It grows back to 1 during the glide. */
  transform:
    translateX(-50%)
    translateY(var(--rise, 0px))
    scale(var(--emerge-scale, 1));
  transition: transform 700ms cubic-bezier(.24, .76, .28, 1);
}
/* The remaining-count belongs to the deck once it is on stage, not to a stack
   still climbing out of a wrapper — it sits below the cards, so during the
   rise it lands on the pack front and reads as printed on it. */
.opening__deck--emerging .opening__count { opacity: 0; }

/* The pack sinks as the stack is drawn out of it. */
.opening__pack {
  transition:
    transform 700ms cubic-bezier(.24, .76, .28, 1),
    opacity 700ms ease-in;
}
.opening__pack.is-emptying { transform: translateY(13vh); }

/* Swept aside: dropped off the bottom-left with a tumble. */
.opening__pack.is-swept {
  transition:
    transform 780ms cubic-bezier(.4, .05, .75, .6),
    opacity 780ms ease-in 280ms;
  transform: translate(-64vw, 44vh) rotate(-26deg) scale(0.84);
  opacity: 0;
}

/* The rest of the deck behind the front card. */
.opening__stack { position: relative; width: 0; height: 0; }
.opening__slab {
  position: absolute;
  width: var(--seat-w);
  aspect-ratio: 5 / 7;
  left: calc(var(--seat-w) / -2);
  background: linear-gradient(170deg, #26334a, #151d2c);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.6);
}
.opening__slab:nth-child(1) { transform: translate(6px, 8px) rotate(1.6deg); }
.opening__slab:nth-child(2) { transform: translate(-5px, 5px) rotate(-1.2deg); }
.opening__slab:nth-child(3) { transform: translate(3px, 3px) rotate(0.7deg); }
.opening__slab:nth-child(4) { transform: translate(-2px, 1px) rotate(-0.4deg); }

.opening__seat {
  --seat-w: clamp(180px, 27vh, 260px);
  width: var(--seat-w);
}
.opening__stack { --seat-w: clamp(180px, 27vh, 260px); }

.opening__card { transform-origin: 50% 50%; }
/* A rare card holding centre stage before it lands. */
.opening__card.is-shown {
  animation: opening-hold 520ms ease-out both;
}
.opening__card.is-grand { animation-duration: 900ms; }
@keyframes opening-hold {
  0%   { transform: scale(1); filter: none; }
  30%  { transform: scale(1.14) translateY(-10px);
         filter: drop-shadow(0 0 26px rgba(120, 240, 220, 0.5)); }
  100% { transform: scale(1.1) translateY(-8px);
         filter: drop-shadow(0 0 18px rgba(120, 240, 220, 0.35)); }
}

.opening__count {
  margin: 14px 0 0;
  font: 600 11px/1 "IBM Plex Sans", system-ui, sans-serif;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(226, 255, 250, 0.55);
}

.opening__hint {
  position: absolute;
  bottom: 4vh;
  margin: 0;
  font: 600 11px/1 "IBM Plex Sans", system-ui, sans-serif;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(226, 255, 250, 0.4);
  animation: opening-pulse 1.8s ease-in-out infinite;
}
@keyframes opening-pulse { 50% { opacity: 0.45 } }

/* The grid: five across, rows growing UPWARD from the bottom of the stage.
   Card width is derived from viewport height so four rows plus the deck fit a
   laptop screen without scrolling. */
/* Five across, and --g-rows deep. A ten-card pack is two rows, so the cards
   can be nearly twice the size the four-row layout allowed. */
.opening__grid {
  --g-rows: 2;
  --g-w: clamp(76px, 13vh, 150px);
  --g-gap: clamp(6px, 1vh, 14px);
  position: absolute;
  left: 50%;
  bottom: 2.5vh;
  width: calc(var(--g-w) * 5 + var(--g-gap) * 4);
  transform: translateX(-50%);
  height: calc((var(--g-w) * 1.4 + var(--g-gap)) * var(--g-rows));
}
.opening__slot {
  position: absolute;
  width: var(--g-w);
  aspect-ratio: 5 / 7;
  left: calc((var(--g-w) + var(--g-gap)) * var(--col));
  bottom: calc((var(--g-w) * 1.4 + var(--g-gap)) * var(--row));
}
.opening__landed { will-change: transform; }
/* The landed card is a live component in a tilt scene, so the scene has to
   fill the slot rather than take site.css's grid max-width. */
.opening__slot .tilt-scene { width: 100%; max-width: none; }

.opening__summary {
  position: absolute;
  /* Sits just above the grid. Mirrors the grid's own sizing rather than
     hard-coding a row count, so changing PACK_SIZE moves both together. */
  bottom: calc(2.5vh + (clamp(76px, 13vh, 150px) * 1.4 + clamp(6px, 1vh, 14px)) * 2 + 16px);
  display: flex;
  align-items: center;
  gap: 14px;
  animation: opening-in 320ms ease-out both;
}
.opening__tally {
  margin: 0;
  font: 600 13px/1 "Chakra Petch", system-ui, sans-serif;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #9ffbe6;
}
.opening__again,
.opening__done {
  padding: 9px 18px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.06);
  color: #eef3fb;
  font: 600 11px/1 "IBM Plex Sans", system-ui, sans-serif;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
}
.opening__again {
  background: var(--kaspa-teal, #49e0c0);
  border-color: transparent;
  color: #04120f;
}
.opening__again:hover { filter: brightness(1.1); }
.opening__done:hover { background: rgba(255, 255, 255, 0.12); }

@media (prefers-reduced-motion: reduce) {
  .opening,
  .opening__deck,
  .opening__glow,
  .opening__ember,
  .opening__hint,
  .opening__summary,
  .opening__card.is-shown { animation: none; }
  .opening__pack,
  .opening__flap { transition: opacity 200ms linear; }
  .opening__pack.is-shaking .opening__flap { animation: none; }
  .opening__flap,
  .opening__seg,
  .opening__pack.is-emptying { transform: none; }
  .opening__segpack { filter: none; }
  /* No emergence: the deck is simply placed centred and the pack fades. */
  .opening__deck--emerging { transition: none; }
  .opening__ember { display: none; }
}
