/* The rubber-band belongs to the root scroller, so overscroll-behavior has to
   sit here -- on the panel it does nothing for the page bounce. */
.opening-pending { overscroll-behavior: none; }
.opening-pending .journey { visibility: hidden; }
.opening-pending .label-ink,
.opening-pending .opening-lines span,
.opening-pending .opening-cue { animation: none !important; }
body.gift-locked { position: fixed; inset: 0; overflow: hidden; }
/* Registered so the mask radius is a real length the browser can tween;
   an unregistered custom property would jump straight to its end value. */
@property --gift-portal-radius {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}

.gift-opening[hidden] { display: none; }
.gift-opening {
  position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; overflow: hidden; background: transparent;
  /* The opening is a modal gesture surface, not part of the scroll journey.
     Claim panning on the whole panel so a touch that lands beside the small
     paper tab cannot be promoted to Safari/Chrome's native page gesture. */
  touch-action: none;
  overscroll-behavior: none;
  /* Everything here is dragged, not read: a tear that highlights the printing
     it sweeps over looks like a web page again. */
  -webkit-user-select: none; user-select: none;
  --gift-portal-x: 50%;
  --gift-portal-y: 50%;
  --gift-portal-radius: 0px;
  /* The tap opens a window in this panel. The journey is already painted
     behind it, so widening the hole is what reveals the story. The feather is
     a ratio of the radius, so at radius 0 every stop collapses onto the same
     point and the panel stays completely solid before the tap. */
  -webkit-mask-image: radial-gradient(circle at var(--gift-portal-x) var(--gift-portal-y), #0000 var(--gift-portal-radius), #000 calc(var(--gift-portal-radius) * 1.16));
  mask-image: radial-gradient(circle at var(--gift-portal-x) var(--gift-portal-y), #0000 var(--gift-portal-radius), #000 calc(var(--gift-portal-radius) * 1.16));
  /* A steady opening, not the expo curve the rest of the box uses: that one
     reaches 87% in its first third and then crawls, which reads as a snap. */
  transition: --gift-portal-radius 820ms cubic-bezier(.35, .1, .5, .95);
}

/* The glow riding the rim of that window. It sits just outside the mask's
   feather, so the hole never erases the brightest part of the ring. */
.gift-portal {
  position: absolute;
  inset: 0;
  z-index: 30;
  pointer-events: none;
  background: radial-gradient(circle at var(--gift-portal-x) var(--gift-portal-y),
    #0000 calc(var(--gift-portal-radius) * 1.02),
    #fff8ec calc(var(--gift-portal-radius) * 1.14),
    #f4ce83 calc(var(--gift-portal-radius) * 1.22),
    #f6cdeb99 calc(var(--gift-portal-radius) * 1.34),
    #f6cdeb00 calc(var(--gift-portal-radius) * 1.52));
}
.gift-opening::before {
  content: ''; position: absolute; inset: 0; opacity: 1;
  background:
    radial-gradient(ellipse at 50% 65%, #34203855, transparent 60%),
    radial-gradient(circle at 20% 17%, #f4ce834d 0 1px, transparent 2px),
    radial-gradient(circle at 80% 30%, #ffffff4d 0 1px, transparent 2px),
    #0b0d20;
  background-size: auto, 197px 241px, 281px 319px, auto;
  transition: opacity 650ms ease;
}
.gift-stage {
  --bw: min(64vw, 244px, 36svh);
  --bh: calc(var(--bw) * 1.18);
  --bd: calc(var(--bw) * .56);
  /* Pinned in svh, not centred in the panel. iOS grows the layout viewport when
     the address bar collapses, and a panel centred with inset:0 pushed the box
     ~32px down mid-tear -- the tab slid out from under her finger exactly while
     she was dragging it. svh ignores the toolbar, so the box holds still.
     The panel itself still fills the screen, so nothing shows through. */
  position: absolute;
  left: 50%;
  top: max(0px, calc((100svh - min(650px, 92svh)) / 2));
  width: min(94vw, 520px);
  height: min(650px, 92svh);
  margin-left: calc(min(94vw, 520px) / -2);
}
.gift-shadow { position: absolute; z-index: 1; left: 50%; top: 72%; width: calc(var(--bw) * 1.2); height: calc(var(--bd) * .75); margin-left: calc(var(--bw) * -.6); border-radius: 50%; background: radial-gradient(ellipse, #0000007a, #0000 70%); filter: blur(7px); }

/* A real cuboid, so every angle of the turn stays correct. */
.gift-scene, .gift-front-scene { position: absolute; left: 50%; top: 54%; width: 0; height: 0; perspective: 1150px; perspective-origin: 50% 38%; }
.gift-scene { z-index: 3; }
.gift-front-scene { z-index: 10; pointer-events: none; }
.gift-box, .gift-front-box { position: absolute; left: 0; top: 0; width: 0; height: 0; transform-style: preserve-3d; transform: rotateX(-16deg) rotateY(-26deg); }
.gift-face, .gift-cap, .gift-well { position: absolute; left: 0; top: 0; transform-style: preserve-3d; }
.gift-face { overflow: hidden; border-radius: 3px; background: linear-gradient(120deg, #f7eedb, #e3d2b0); box-shadow: inset 0 0 0 1px #ffffff4d; }
.gift-face--front, .gift-face--back { width: var(--bw); height: var(--bh); margin: calc(var(--bh) / -2) 0 0 calc(var(--bw) / -2); }
.gift-face--left, .gift-face--right { width: var(--bd); height: var(--bh); margin: calc(var(--bh) / -2) 0 0 calc(var(--bd) / -2); }
.gift-face--bottom { width: var(--bw); height: var(--bd); margin: calc(var(--bd) / -2) 0 0 calc(var(--bw) / -2); }
/* The lid overhangs the body, so it lays a thin shadow across the top of every
   printed face. Without it the lid reads as a card resting on the box. */
.gift-face--front { transform: translateZ(calc(var(--bd) / 2)); background: url('../images/box-face-front.webp') center / 100% 100% no-repeat, #cfeaf6; box-shadow: inset 0 0 0 1px #ffffff40, inset 0 5px 7px -5px #00000080; }
.gift-face--back { transform: rotateY(180deg) translateZ(calc(var(--bd) / 2)); background: linear-gradient(180deg, #f4ead3, #ddcaa6); }
.gift-face--right { transform: rotateY(90deg) translateZ(calc(var(--bw) / 2)); }
.gift-face--bottom { transform: rotateX(-90deg) translateZ(calc(var(--bh) / 2)); background: linear-gradient(#b9a583, #8d7a5c); }
/* Use the photographed instruction panel as-is on either side. */
.gift-face--right, .gift-face--left { background: url('../images/box-face-side.webp') center / 100% 100% no-repeat, #efe0c2; box-shadow: inset 0 0 0 1px #ffffff4d, inset 0 5px 7px -5px #00000080; }
.gift-face--left { transform: rotateY(-90deg) translateZ(calc(var(--bw) / 2)) scaleX(-1); }

/* Top face: the tear surface, and the flap that swings open. */
.gift-cap { width: var(--bw); height: var(--bd); margin: calc(var(--bd) / -2) 0 0 calc(var(--bw) / -2); transform: rotateX(90deg) translateZ(calc(var(--bh) / 2)); }
/* Cream sampled straight off box-face-side.png (#e2e2c4) so the lid and the
   photographed faces are the same paper. The old warm tan sat a whole hue away
   from the sides, which is what made the top read as a sticker. The fine rules
   run along the box the way the printed ones do, and the lid carries no drop
   shadow of its own: it is the top of the box, not a card lying on it. */
.gift-cap__leaf {
  position: absolute; inset: 0; border-radius: 3px; overflow: hidden;
  background-image:
    /* the sky-blue trim the real box carries along both long edges and around
       the left corner, sampled off blind-box.png (#bfdef4) */
    linear-gradient(#bfdef4, #bfdef4),
    linear-gradient(#bfdef4, #bfdef4),
    linear-gradient(#a9d3ef, #c6e2f6),
    repeating-linear-gradient(180deg, #00000008 0 1px, #0000 1px 9px),
    radial-gradient(125% 95% at 24% 6%, #fffdf1, #0000 64%),
    linear-gradient(168deg, #e9e9d4, #dfdfc2 56%, #d1d1b3);
  background-repeat: no-repeat;
  background-size: 100% 3px, 100% 3px, 13px 100%, auto, auto, auto;
  background-position: 0 0, 0 100%, 0 0, 0 0, 0 0, 0 0;
  box-shadow: inset 0 0 0 1px #ffffff59;
  transform-origin: 50% 0; transform-style: preserve-3d;
  transition: transform 950ms cubic-bezier(.3,.95,.35,1);
}
/* The printed series name, in the same ink as the photographed box art. */
.gift-cap__print {
  position: absolute; left: 13px; right: 0; top: 24%;
  font-family: 'Fredoka', 'Nunito', system-ui, sans-serif;
  /* Fredoka stops at 700, so the extra weight the printed logo has comes from
     a hairline stroke rather than a heavier face. */
  font-weight: 700; font-size: clamp(.82rem, 5vw, 1.36rem);
  -webkit-text-stroke: .5px currentColor;
  letter-spacing: .03em; text-align: center; white-space: nowrap;
  color: #4a7ea6;
  text-shadow: 0 1px 0 #ffffffa6;
  transform: skewX(-5deg);
  pointer-events: none;
}
.gift-opening:is([data-state='opening'],[data-state='peek'],[data-state='portal']) .gift-cap__leaf { transform: rotateX(114deg); transition-delay: 700ms; }
/* Once the lid is past upright we are looking at its underside, which on a real
   box is blank board. Drop the printing just before it turns over, so it never
   shows through back to front. */
.gift-opening:not([data-state='sealed']) .gift-cap__print { opacity: 0; transition: opacity 240ms 780ms; }
.gift-well {
  width: calc(var(--bw) - 2px); height: calc(var(--bd) - 2px);
  margin: calc((var(--bd) - 2px) / -2) 0 0 calc((var(--bw) - 2px) / -2);
  transform: rotateX(90deg) translateZ(calc(var(--bh) / 2 - 5px));
  box-sizing: border-box; border: 3px solid #e9dcc0; border-radius: 3px;
  background: radial-gradient(ellipse at 50% 35%, #140b1d, #241531 62%, #3a2542);
  box-shadow: inset 0 0 20px #000d;
  opacity: 0; transition: opacity 450ms 700ms;
}
.gift-opening:not([data-state='sealed']) .gift-well { opacity: 1; }
@keyframes gift-present {
  0% { transform: rotateX(-16deg) rotateY(-26deg) scale(.9); }
  24% { transform: rotateX(-16deg) rotateY(-26deg) scale(1); }
  100% { transform: rotateX(-62deg) rotateY(-6deg) scale(1); }
}
@keyframes gift-settle {
  0% { transform: rotateX(-62deg) rotateY(-6deg); }
  100% { transform: rotateX(-20deg) rotateY(-20deg); }
}
.gift-opening[data-state='sealed'] :is(.gift-box, .gift-front-box) { animation: gift-present 1600ms cubic-bezier(.5,0,.2,1) both; }
.gift-opening[data-state='opening'] :is(.gift-box, .gift-front-box) { animation: gift-settle 1000ms 620ms cubic-bezier(.4,0,.2,1) both; }
.gift-opening:is([data-state='peek'],[data-state='portal']) :is(.gift-box, .gift-front-box) { transform: rotateX(-20deg) rotateY(-20deg); }

/* Die-cut tear strip, sitting just inside the lid's front edge where a real
   box opens. It is the same paper as the lid, so all that marks it is the
   perforation itself: no coloured band, no drop shadow, nothing laid on top.
   The ragged profiles below are hand-drawn rather than a row of punched
   circles, because a real tear leaves uneven fibre, not stamp scallops. */
.gift-seal {
  --tear-h: 18px;
  /* Paper rips a little ahead of the fingers pulling it. Just enough lead to
     carry the tear clean off the far edge rather than stranding a stub there,
     and small enough that the rip still runs through the paper corner. */
  --tear-lead: calc(24px * var(--progress, 0));
  --tear-top: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='18' viewBox='0 0 120 18' preserveAspectRatio='none'%3E%3Cpath d='M0 3.2 L5 2.9 L9 4.2 L14 3.1 L22 2.7 L25 4.6 L28 3 L36 3.3 L41 2.1 L45 3.5 L54 3.2 L58 4.4 L62 3.6 L66 2.8 L74 3.1 L78 2 L82 3.9 L88 3.2 L96 2.9 L100 4.3 L104 3.1 L112 3.4 L116 2.5 L120 3.2 L120 18 L0 18 Z' fill='%23000'/%3E%3C/svg%3E");
  --tear-bottom: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='18' viewBox='0 0 120 18' preserveAspectRatio='none'%3E%3Cpath d='M0 14.8 L6 15.2 L10 13.8 L15 15 L23 15.3 L27 13.6 L31 14.9 L38 14.6 L43 15.9 L47 14.8 L56 15.1 L60 13.7 L64 14.9 L71 15.3 L76 15.8 L80 14.6 L87 14.9 L92 13.8 L96 15.1 L103 14.7 L108 15.7 L113 14.9 L120 14.8 L120 0 L0 0 Z' fill='%23000'/%3E%3C/svg%3E");
  position: absolute; z-index: 2; left: 0; right: 0; top: calc(100% - 30px);
  height: 44px; margin-top: -22px;
}
.gift-seal__track,
.gift-seal__edge,
.gift-seal__rest {
  position: absolute; left: 0; top: calc((44px - var(--tear-h)) / 2); height: var(--tear-h);
}
/* Only what has actually been torn carries a ragged edge. The intact band
   stays a smooth paper strip so it never reads as already opened. */
.gift-seal__track,
.gift-seal__edge {
  -webkit-mask: var(--tear-top), var(--tear-bottom);
  mask: var(--tear-top), var(--tear-bottom);
  -webkit-mask-size: 120px 100%; mask-size: 120px 100%;
  -webkit-mask-repeat: repeat-x; mask-repeat: repeat-x;
  -webkit-mask-composite: source-in; mask-composite: intersect;
}
.gift-seal__track { width: 100%; background: linear-gradient(180deg, #0d0715, #1c1128); box-shadow: inset 0 4px 8px #000c, inset 0 -2px 4px #0009; clip-path: inset(0 calc(100% - var(--drag, 0px) - var(--tear-lead)) 0 0); }
/* The intact strip paints no fill at all, so the lid's own paper and ruled
   lines run straight through it. Two perforation lines are the only mark. */
.gift-seal__rest {
  width: 100%;
  background:
    repeating-linear-gradient(90deg, #0000002e 0 3px, #0000 3px 8px) 0 0 / 100% 1px no-repeat,
    repeating-linear-gradient(90deg, #0000002e 0 3px, #0000 3px 8px) 0 100% / 100% 1px no-repeat;
  color: #6d6146;
  clip-path: inset(0 0 0 calc(var(--drag, 0px) + var(--tear-lead)));
}
.gift-seal__rest > span { position: absolute; inset: 0 8px 0 40px; display: grid; place-items: center; font-size: clamp(.56rem, 2.1vw, .68rem); letter-spacing: .18em; text-transform: uppercase; opacity: .68; }
.gift-seal__edge { width: 11px; left: calc(var(--drag, 0px) + var(--tear-lead) - 5px); background: linear-gradient(180deg, #f7f7e7, #c8c8aa); opacity: min(1, calc(var(--progress, 0) * 26)); }
/* A little lifted paper corner to pinch, sized to a real finger target. */
.gift-tab {
  position: absolute; z-index: 3; top: 50%; left: 0; width: 44px; height: 44px; margin-top: -22px;
  padding: 0; border: 0; background: none; cursor: grab; touch-action: none;
  transform: translateX(var(--drag, 0px));
}
.gift-tab::before {
  content: ''; position: absolute; top: calc((44px - var(--tear-h)) / 2); left: 4px; width: 17px; height: var(--tear-h);
  border-radius: 1px 5px 5px 1px;
  background: linear-gradient(150deg, #e9e9d5, #dadabd 52%, #c3c3a5);
  box-shadow: 3px 2px 5px #00000063, inset 0 1px 0 #fdfdea;
  clip-path: polygon(0 0, 100% 12%, 100% 88%, 0 100%);
  transform: rotate(-4deg);
  transition: transform 180ms ease, box-shadow 180ms ease;
}
.gift-tab:active { cursor: grabbing; }
/* The tab's hit area is a 44px square; ring the paper corner, not the square. */
.gift-opening .gift-tab:focus-visible { outline: none; }
.gift-tab:focus-visible::before { box-shadow: 0 0 0 2px #fff8ec, 2px 3px 7px #0000005e, inset 0 1px 0 #fffff2; }
.gift-seal[data-tearing] .gift-tab::before { transform: rotate(-9deg) translate(1px, -2px); box-shadow: 3px 5px 11px #00000073, inset 0 1px 0 #fffff2; }
.gift-opening button:focus-visible { outline: 3px solid #fff8ec; outline-offset: 5px; }
/* Lulu's hit area is far bigger than her art. Safari can paint its native
   focus ring after scripted focus, so suppress that square for every focus
   state and keep the accessible focus cue on the transparent artwork. */
.gift-opening .gift-lulu:focus,
.gift-opening .gift-lulu:focus-visible { outline: none; box-shadow: none; }
.gift-lulu:focus img,
.gift-lulu:focus-visible img { filter: drop-shadow(0 0 9px #fff8ec) drop-shadow(0 0 22px #f4ce83aa) drop-shadow(0 0 34px #fff8ec80); }
.gift-opening[data-state='opening'] .gift-seal__rest { animation: gift-strip-fly 780ms cubic-bezier(.33,.1,.67,1) both; }
.gift-opening[data-state='opening'] .gift-seal__edge { animation: gift-fade 240ms both; }
.gift-opening[data-state='opening'] .gift-seal__track,
.gift-opening[data-state='opening'] #giftHandle { animation: gift-fade 420ms 220ms both; }
.gift-opening:not([data-state='sealed']) .gift-seal { pointer-events: none; }
.gift-opening:is([data-state='peek'],[data-state='portal']) .gift-seal { opacity: 0; }
@keyframes gift-fade { to { opacity: 0; } }
@keyframes gift-strip-fly {
  0% { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  30% { transform: translate(26px, -24px) rotate(-13deg); opacity: 1; }
  100% { transform: translate(150px, 60svh) rotate(340deg); opacity: 0; }
}

/* Genie plume rolling out of the open box. */
.gift-genie { position: absolute; z-index: 7; left: 50%; top: calc(54% - var(--bh) * .40); width: calc(var(--bw) * .8); height: 10px; transform: translateX(-50%); pointer-events: none; }
.gift-genie i { position: absolute; left: 50%; bottom: 0; width: calc(var(--bw) * .36); height: calc(var(--bw) * .36); margin-left: calc(var(--bw) * -.18); border-radius: 50%; opacity: 0; filter: blur(17px); background: radial-gradient(circle at 50% 55%, #f6cdeb 0%, #e0a3dd 42%, #b49ce8 72%, #b49ce800 79%); }
.gift-genie i:nth-child(1) { --x0: -14px; --sway: -46px; --spin: -40deg; animation-duration: 2000ms; animation-delay: 560ms; }
.gift-genie i:nth-child(2) { --x0: 12px; --sway: 42px; --spin: 34deg; animation-duration: 1750ms; animation-delay: 700ms; background: radial-gradient(circle at 50% 55%, #fde2f4 0%, #d6a7e8 45%, #a9a0ea 74%, #a9a0ea00 80%); }
.gift-genie i:nth-child(3) { --x0: 6px; --sway: -22px; --spin: 26deg; animation-duration: 2200ms; animation-delay: 840ms; }
.gift-genie i:nth-child(4) { --x0: -6px; --sway: 54px; --spin: -30deg; animation-duration: 1900ms; animation-delay: 990ms; background: radial-gradient(circle at 50% 55%, #ffeaf8 0%, #e6b6ea 45%, #bda6f0 74%, #bda6f000 80%); }
.gift-opening:is([data-state='opening'],[data-state='peek']) .gift-genie i { animation-name: gift-smoke; animation-timing-function: cubic-bezier(.3,.5,.5,1); animation-fill-mode: both; }
@keyframes gift-smoke {
  0% { opacity: 0; transform: translate(var(--x0, 0px), 12px) scale(.22) rotate(0deg); }
  20% { opacity: .58; }
  60% { opacity: .38; }
  100% { opacity: 0; transform: translate(var(--sway), -32svh) scale(1.75) rotate(var(--spin)); }
}

/* Lulu sits between the open box and a synchronized copy of its front face.
   The front face hides her until she has actually cleared the rim. */
.gift-pit {
  --lulu: min(calc(var(--bw) * 1.08), 32svh);
  position: absolute; inset: 0; z-index: 9; pointer-events: none;
}
.gift-lulu { position: absolute; z-index: 1; width: var(--lulu); height: var(--lulu); left: 50%; bottom: calc(46% + var(--bh) * .40); transform: translate(-50%, 34%) scale(.38); transform-origin: 50% 100%; -webkit-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent; border: 0; border-radius: 50%; padding: 0; background: none; box-shadow: none; opacity: 0; cursor: pointer; pointer-events: auto; transition: transform 900ms cubic-bezier(.22,1,.36,1), opacity 600ms; }
.gift-lulu[disabled] { pointer-events: none; }
.gift-lulu::before {
  content: '';
  position: absolute;
  z-index: 0;
  inset: 12% 8%;
  border: 1px solid #fff8ec99;
  border-radius: 50%;
  background: radial-gradient(ellipse, #fff8ec14 38%, #f4ce831c 62%, #f4ce8300 72%);
  box-shadow: 0 0 18px #fff8ec30, inset 0 0 18px #fff8ec18;
  opacity: 0;
  transform: scale(.88);
  pointer-events: none;
}
.gift-lulu img { position: relative; z-index: 1; display: block; width: 100%; height: 100%; border: 0; outline: none; object-fit: contain; filter: drop-shadow(0 0 9px #fff8ec) drop-shadow(0 0 22px #f4ce83aa); transition: filter 150ms ease; }
.gift-opening[data-state='opening'] .gift-lulu { animation: gift-rise 750ms 1650ms cubic-bezier(.22,1,.36,1) both; }
.gift-opening:is([data-state='peek'],[data-state='portal']) .gift-lulu { opacity: 1; transform-origin: 50% 50%; transform: translate(-50%, -12px) scale(1); }
/* Keep interaction on the button and the glow on the artwork. Never blur
   the button's rectangular surface while its transparent image is rising. */
.gift-opening[data-state='peek'] .gift-lulu { transition: transform 160ms ease-out, opacity 600ms; }
.gift-opening[data-state='peek'] .gift-lulu::before { animation: gift-button-pulse 1800ms ease-in-out infinite; }
@media (hover: hover) and (pointer: fine) {
  .gift-opening[data-state='peek'] .gift-lulu:enabled:hover { transform: translate(-50%, -12px) scale(1.045); }
  .gift-opening[data-state='peek'] .gift-lulu:enabled:hover::before { border-color: #fff8ecdd; box-shadow: 0 0 26px #fff8ec55, inset 0 0 20px #fff8ec24; }
}
.gift-opening[data-state='peek'] .gift-lulu:enabled:active { transform: translate(-50%, -12px) scale(.96); }
.gift-opening[data-state='peek'] .gift-lulu[data-activating] { animation: gift-button-press 180ms cubic-bezier(.2,.8,.3,1) both; }
.gift-opening[data-state='peek'] .gift-lulu[data-activating]::before { animation: gift-button-confirm 180ms ease-out both; }
.gift-opening[data-state='peek'] .gift-lulu[data-activating] img { filter: brightness(1.12) drop-shadow(0 0 12px #fff8ec) drop-shadow(0 0 28px #f4ce83dd); }
.gift-opening[data-state='peek'] .gift-lulu img { animation: gift-float 3600ms 200ms ease-in-out infinite; }
.gift-opening[data-state='portal'] :is(.gift-scene, .gift-front-scene, .gift-shadow, .gift-genie, .gift-sky, .gift-spark, .gift-hint, .gift-credit, .gift-lulu) {
  opacity: 0;
  transition: opacity 380ms ease;
}
@keyframes gift-rise {
  0% { opacity: 0; transform: translate(-50%, 34%) scale(.38); }
  14% { opacity: 1; }
  62% { opacity: 1; transform: translate(-50%, -8px) scale(.9); }
  80% { opacity: 1; transform: translate(-50%, -20px) scale(1.03); }
  100% { opacity: 1; transform: translate(-50%, -12px) scale(1); }
}
@keyframes gift-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
@keyframes gift-button-pulse {
  0%, 100% { opacity: .42; transform: scale(.9); }
  50% { opacity: .82; transform: scale(1.02); }
}
@keyframes gift-button-press {
  0% { transform: translate(-50%, -12px) scale(1); }
  45% { transform: translate(-50%, -7px) scale(.91); }
  100% { transform: translate(-50%, -14px) scale(1.035); }
}
@keyframes gift-button-confirm {
  0% { opacity: .75; transform: scale(.96); }
  100% { opacity: 0; transform: scale(1.2); }
}

/* Anchored to the box, not the screen. Pinned to the bottom edge it drifted
   between 53px and 70px from the box depending on viewport height, and the only
   clean ground near Lulu is the dark band directly under the box -- below her
   is the box art itself, where gold text disappears. 54% is the scene origin
   and the box bottom lands ~0.60 of its height past it on every size tested. */
.gift-hint { position: absolute; width: 100%; left: 0; top: calc(54% + var(--bh) * 0.60 + 1.6rem); margin: 0; text-align: center; color: #f4ce83; letter-spacing: .08em; font-size: .95rem; line-height: 1.1; }
.gift-credit {
  position: absolute;
  z-index: 20;
  left: 50%;
  bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
  width: max-content;
  max-width: 90vw;
  margin: 0;
  color: rgba(255, 255, 255, .4);
  font-size: .8rem;
  line-height: 1.1;
  text-align: center;
  transform: translateX(-50%);
  pointer-events: none;
}
.gift-spark { position: absolute; z-index: 6; width: 6px; height: 22px; border-radius: 50%; background: #fff5cb; box-shadow: 0 0 18px 5px #f4ce8370; top: 50%; left: 70%; opacity: 0; }
[data-state='opening'] .gift-spark { animation: gift-launch 1100ms ease-out both; }
@keyframes gift-launch { 0% { opacity: 0; transform: translate(0,0) scale(.5); } 15% { opacity: 1; } 100% { opacity: 0; transform: translate(-40px,-35svh) scale(.2); } }
.gift-sky { position: absolute; inset: 0; pointer-events: none; }
.gift-sky i { position: absolute; top: 12%; left: 15%; width: min(35vw,220px); aspect-ratio: 1; background: repeating-conic-gradient(#f4ce83 0deg 1.5deg,transparent 2deg 18deg); mask-image: radial-gradient(circle, transparent 26%,black 27% 29%,transparent 31% 42%,black 43% 44%,transparent 46%); opacity: 0; animation: gift-firework 10.5s cubic-bezier(.22,1,.36,1) infinite; }
.gift-sky i:nth-child(2) { left: auto; right: 8%; top: 24%; animation-delay: 3.4s; }
.gift-sky i:nth-child(3) { top: 8%; left: 60%; width: min(23vw,140px); animation-delay: 6.4s; }
@keyframes gift-firework {
  0%, 10% { opacity: 0; transform: scale(.15); }
  20%, 46% { opacity: .6; transform: scale(1); }
  68%, 100% { opacity: 0; transform: scale(1.18); }
}
@media (max-height: 480px) { .gift-stage { --bw: 27svh; } }
@media (prefers-reduced-motion: reduce) {
  .gift-opening, .gift-opening *, .gift-opening *::before { animation: none !important; transition: none !important; }
  .gift-genie { display: none; }
  .gift-opening:not([data-state='sealed']) .gift-seal__rest,
  .gift-opening:not([data-state='sealed']) .gift-seal__edge,
  .gift-opening:not([data-state='sealed']) .gift-seal__track,
  .gift-opening:not([data-state='sealed']) #giftHandle { opacity: 0; }
  .gift-opening:is([data-state='peek'],[data-state='portal']) .gift-lulu { opacity: 1; transform: translate(-50%, -12px) scale(1); filter: none; }
  .gift-opening:is([data-state='opening'],[data-state='peek'],[data-state='portal']) .gift-cap__leaf { transform: rotateX(114deg); }
  .gift-opening :is(.gift-box, .gift-front-box) { transform: rotateX(-20deg) rotateY(-20deg); }
}
