/* build 84efa6d · main · 2026-09-06T09:14:24Z · 201350df */
/* The layered board (layered-board spec §4).
 *
 * `.cel` sits on top of `.board` inside `#stage` and never hides it — the
 * sponsor page render stays on screen underneath for the whole run. Two
 * layers become visible together, instantly, the moment a mark is touched:
 * `.cel__bg` (the deck's own background page, no marks or heading) and
 * `.cel__marks` (one frame per placement at its board position, holding the
 * same artwork the render shows). Because both layers reproduce exactly what
 * is already on screen, that swap is invisible — the check that matters is
 * in the spec, not in this file: a screenshot at that instant must match the
 * board within 1% (§5).
 *
 * From there, exactly two things animate: `.cel__marks-others` (everything
 * but the chosen mark, moved as one composited layer per v1.1 §4) and the
 * one frame inside `.cel__mark-chosen`. Both move on `transform` and
 * `opacity` only.
 *
 * 🔴 Every value a transition ends at is a plain CSS rule keyed off
 * `[data-phase]`, never a `@keyframes` percentage. Finding AA: a hidden page
 * renders no animation frames, so a `@keyframes` animation parks wherever its
 * *active* frame happens to be, and that shipped as a config panel stuck
 * off-screen. A `transition` has no such frame to park at — its target value
 * is a real style the moment `data-phase` changes, so a page that skips every
 * intermediate paint still ends up showing the correct final state. */

.cel {
  position: absolute;
  inset: 0;
  pointer-events: none;     /* a closed celebration must let taps reach the board */
}
.cel[data-phase] { pointer-events: auto; }

/* ---- the instant swap: layer 2 (background) + layer 3 (marks) ---------- */

.cel__bg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: fill;         /* the render IS the page, exactly like .board__render */
  opacity: 0;
  transition: none;         /* an instant snap, never an animated fade (spec §4, t=0) */
}
.cel__marks {
  position: absolute; inset: 0;
  opacity: 0;
  transition: none;
}
.cel[data-phase] .cel__bg,
.cel[data-phase] .cel__marks { opacity: 1; }

/* ---- layer 3a: every mark but the chosen one, as one composited layer -- */

.cel__marks-others {
  position: absolute; inset: 0;
  opacity: 1;
  transform: none;
  transition: opacity 400ms ease-out, transform 400ms ease-out;
}
.cel[data-phase="open"] .cel__marks-others { opacity: 0; transform: scale(0.96); }
.cel[data-phase="closing"] .cel__marks-others {
  opacity: 1; transform: none;
  transition: opacity 600ms ease-in, transform 600ms ease-in;
}

/* ---- layer 3b: the chosen mark ------------------------------------------ */

.cel__mark-chosen { position: absolute; inset: 0; }

/* Position/size are set per-mark as inline styles (percentages of the page,
 * exactly like board.js's targets) so the frame starts at its BOARD rect
 * whichever container currently holds it. `transform-origin: 0 0` is what
 * makes the FLIP math in celebration-board.js (translate + scale from the
 * top-left corner) match what actually renders. */
.cel__mark {
  position: absolute;
  transform-origin: 0 0;
}
.cel__mark-chosen .cel__mark {
  transition: transform 900ms cubic-bezier(0.16, 1, 0.3, 1);   /* ease-out */
}
.cel[data-phase="open"] .cel__mark-chosen .cel__mark {
  transform:
    translate(calc(var(--u) * var(--cel-dx, 0)), calc(var(--u) * var(--cel-dy, 0)))
    scale(var(--cel-sx, 1), var(--cel-sy, 1));
}
.cel[data-phase="closing"] .cel__mark-chosen .cel__mark {
  transform: none;
  transition: transform 600ms ease-in;
}

.cel__mark-img { position: absolute; object-fit: fill; }

.cel__mark-plate {
  position: absolute; inset: 0;
  opacity: 0;
  transition: opacity 900ms ease-out;
}
.cel[data-phase="open"] .cel__mark-chosen .cel__mark-plate { opacity: 1; }
.cel[data-phase="closing"] .cel__mark-chosen .cel__mark-plate {
  opacity: 0;
  transition: opacity 600ms ease-in;
}

.cel__mark-text {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  text-align: center;
  overflow-wrap: anywhere;
  padding: 4%;
  color: #14141a;
  font-weight: 700;
  font-size: calc(var(--u) * var(--cel-size, 60));
}

/* ---- layer 4: the live heading ------------------------------------------ */

.cel__heading-line {
  position: absolute;
  display: flex;
  align-items: center;
  overflow-wrap: anywhere;
  font-size: calc(var(--u) * var(--cel-size, 60));
  /* Canva reports the board's display face as weight "normal" because the face is
     heavy by design; the substitute is not, so the live heading rendered visibly
     lighter than the title it replaces (seen 2026-09-06). Bold, whatever the field says. */
  font-weight: 700;
  opacity: 0;
  transition: opacity 400ms ease-out;
}
.cel[data-phase="open"] .cel__heading-line--top { opacity: 1; }
.cel[data-phase="open"] .cel__heading-line--bottom {
  opacity: 1;
  transition-duration: 600ms;
  transition-delay: 300ms;
}
.cel[data-phase="closing"] .cel__heading-line {
  opacity: 0;
  transition: opacity 600ms ease-in;
}

/* ---- layer 5: confetti --------------------------------------------------- */

.cel__confetti { position: absolute; inset: 0; width: 100%; height: 100%; }

/* ---- layer 6: the ripple ------------------------------------------------- */

/* One reusable element, moved to the touch point and replayed — never a fresh
 * element per tap, which is what would leak on a kiosk that runs for days. */
.cel__ripple {
  position: absolute;
  width: calc(var(--u) * 160);
  height: calc(var(--u) * 160);
  margin-left: calc(var(--u) * -80);
  margin-top: calc(var(--u) * -80);
  border-radius: 50%;
  background: radial-gradient(circle, rgb(255 255 255 / .5), rgb(255 255 255 / 0) 70%);
  opacity: 0;
  transform: scale(0.3);
  pointer-events: none;
  transition: opacity 500ms ease-out, transform 500ms ease-out;
}
.cel__ripple.cel__ripple--active { opacity: 1; transform: scale(1); }

@media (prefers-reduced-motion: reduce) {
  .cel__marks-others,
  .cel__mark-chosen .cel__mark,
  .cel__mark-plate,
  .cel__heading-line,
  .cel__ripple { transition: none; }
}
