/* ═══════════════════════════════════════════
   finale — One last thing (the last beat)
   shots.md › finale: 66 s, verb hold, hold `seal` at 8 s.

   Every play state is a class on #finale-container (finale-is-*), and the
   same class on the hoisted #finale-controls layer, toggled by the beat's
   cues. Boot rebuilds only the strips, the tickets and the halo's cards,
   so a config keystroke never undoes what the film has revealed.

   Geometry: every layer is placed from tokens.css's continuity geometry
   (--env-x/-y/-w/-h, --flap-depth, --seal-d), so the closed envelope sits
   exactly where the film's envelope sat, and the tableau is laid out in
   envelope widths (E = --env-w) around it:
     certificate   front-left, exactly where the-award's exit left it (24% / 72%)
     strips        front-right, leaning on the envelope's right end
     tickets       front-right, below the envelope
     record        behind the envelope's right end, only its gold edge showing
     halo          overhead, above the top of the frame (the crane reveals it)
     copy          between the halo and the envelope, never over the tableau
   Paint order is DOM order (no z-index inside the scene).
   ═══════════════════════════════════════════ */

.finale-container {
    position: absolute;
    inset: 0;
    color: var(--text-primary);
    font-family: var(--font-body);
}

/* Entry states are applied instantly (prepare), never animated in. */
.finale-instant,
.finale-instant *,
.finale-instant *::before,
.finale-instant *::after {
    transition: none !important;
}

.finale-scene {
    --finale-env-top: calc(var(--env-y) - var(--env-h) / 2);
    --finale-env-bottom: calc(var(--env-y) + var(--env-h) / 2);
    position: absolute;
    inset: 0;
    pointer-events: none;
}

/* ───── The table: one candle-warm pool, and the step brighter at the seal ───── */
.finale-table {
    position: absolute;
    inset: 0;
}

.finale-table-glow,
.finale-table-lit {
    position: absolute;
    left: calc(var(--env-x) - var(--env-w) * 1.3);
    top: calc(var(--env-y) - var(--env-w) * 0.45);
    width: calc(var(--env-w) * 2.6);
    height: calc(var(--env-w) * 1.3);
}

.finale-table-glow {
    background: radial-gradient(ellipse 50% 50% at 50% 55%,
        rgba(246, 220, 154, 0.2) 0%,
        rgba(226, 177, 85, 0.08) 45%,
        transparent 72%);
}

.finale-table-lit {
    background: radial-gradient(ellipse 50% 50% at 50% 55%,
        rgba(249, 238, 219, 0.16) 0%,
        rgba(246, 220, 154, 0.12) 38%,
        rgba(226, 177, 85, 0.05) 60%,
        transparent 78%);
    opacity: 0;
    transition: opacity 1.8s var(--ease-silk);
}

.finale-container.finale-is-bloom .finale-table-lit {
    opacity: 1;
}

/* ───── The record: behind the envelope's right end, its gold edge peeking ───── */
.finale-record {
    position: absolute;
    left: calc(var(--env-x) + var(--env-w) * 0.52);
    top: calc(var(--finale-env-top) + var(--env-w) * 0.2);
    width: 0;
    height: 0;
}

.finale-record-disc {
    position: absolute;
    left: calc(var(--env-w) * -0.29);
    top: calc(var(--env-w) * -0.29);
    width: calc(var(--env-w) * 0.58);
    height: calc(var(--env-w) * 0.58);
    border-radius: 50%;
    background:
        radial-gradient(circle, var(--wine) 0 16%, var(--accent-warm) 16.5% 18%, transparent 18.5%),
        repeating-radial-gradient(circle, rgba(22, 5, 9, 1) 0 1.5px, rgba(50, 13, 22, 1) 1.5px 3px);
    box-shadow:
        0 0 0 2px var(--accent-warm-deep),
        0 0 0 3px rgba(246, 220, 154, 0.55),
        0 14px 30px rgba(22, 5, 9, 0.6);
}

/* A static sheen across the grooves. */
.finale-record-disc::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: conic-gradient(from 20deg,
        transparent 0 12%, rgba(246, 220, 154, 0.16) 20%, transparent 28% 62%,
        rgba(246, 220, 154, 0.11) 70%, transparent 78%);
}

/* ───── The closed envelope ───── */
.finale-env,
.finale-env-top {
    position: absolute;
    left: calc(var(--env-x) - var(--env-w) / 2);
    top: var(--finale-env-top);
    width: var(--env-w);
    height: var(--env-h);
}

/* It slides into the light from below (stage, 3 s). */
.finale-env-rise {
    position: absolute;
    inset: 0;
    transform: translateY(calc(50vh + var(--env-h)));
}

.finale-container.finale-is-in .finale-env-rise {
    transform: none;
    transition: transform 3s var(--ease-stage);
}

.finale-env-body {
    position: absolute;
    inset: 0;
    border-radius: 3px;
    background: var(--envelope-body);
    box-shadow: var(--envelope-shadow), inset 0 0 0 1px var(--envelope-edge);
}

.finale-env-folds,
.finale-env-edge {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
}

.finale-env-fold {
    fill: none;
    stroke: var(--foil-line-soft);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}

/* The flap, shut: hinged at the top edge, its tip at --flap-depth. */
.finale-env-flap {
    position: absolute;
    inset: 0;
    background: var(--envelope-flap);
    clip-path: polygon(0 0, 100% 0, 50% var(--flap-depth));
}

/* The foil hairline along the flap's edges (a 2px stroke, half of it seen
   inside the flap's clip; the path's tip is --flap-depth: 56%). */
.finale-env-edge path {
    fill: none;
    stroke: var(--envelope-edge);
    stroke-width: 2px;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}

/* ───── The molten wax, centred on the flap's tip ───── */
.finale-wax {
    position: absolute;
    left: calc(50% - var(--seal-d) / 2);
    top: calc(var(--flap-depth) - var(--seal-d) / 2);
    width: var(--seal-d);
    height: var(--seal-d);
}

.finale-wax > span {
    position: absolute;
    display: block;
}

/* Pre-painted: only its opacity ever moves. */
.finale-wax-glow {
    inset: -55%;
    border-radius: 50%;
    background: radial-gradient(circle,
        rgba(238, 106, 69, 0.42) 0%,
        rgba(226, 177, 85, 0.22) 34%,
        transparent 68%);
    opacity: 0.45;
    animation: finale-breathe 3.2s var(--ease-breath) infinite alternate;
}

/* The heat spilling round the stamp while the ring fills (pre-painted). */
.finale-wax-flare {
    inset: -40%;
    border-radius: 50%;
    background: radial-gradient(circle,
        rgba(249, 238, 219, 0.85) 0%,
        rgba(246, 220, 154, 0.55) 24%,
        rgba(238, 106, 69, 0.3) 44%,
        transparent 70%);
    opacity: 0;
    transition: opacity 0.6s var(--ease-silk);
}

.finale-wax-rim {
    inset: -10%;
    border-radius: 48% 52% 46% 54% / 53% 47% 55% 45%;
    background: var(--wax-gold);
    box-shadow: var(--wax-rim);
    opacity: 0;
    transform: scale(0.86);
}

/* A pool, not a bead: a little wider than tall, an uneven edge, and a soft
   ripple ring on the gold so it reads as liquid. */
.finale-wax-pool {
    inset: 3% -5% 1% -5%;
    border-radius: 56% 44% 58% 42% / 46% 58% 42% 54%;
    background:
        radial-gradient(ellipse 50% 46% at 52% 56%, transparent 0 58%, rgba(246, 220, 154, 0.38) 66%, transparent 76%),
        var(--wax-gold);
    box-shadow: var(--wax-rim), 0 4px 10px rgba(22, 5, 9, 0.45);
}

/* Its gloss, and a drip running off the edge: it is still molten. */
.finale-wax-pool::before {
    content: '';
    position: absolute;
    left: 20%;
    top: 14%;
    width: 38%;
    height: 26%;
    border-radius: 50%;
    background: radial-gradient(ellipse, rgba(249, 238, 219, 0.75), transparent 70%);
}

.finale-wax-pool::after {
    content: '';
    position: absolute;
    right: 9%;
    bottom: -9%;
    width: 19%;
    height: 24%;
    border-radius: 46% 54% 50% 50% / 34% 34% 66% 66%;
    background: var(--wax-gold);
    box-shadow: var(--wax-rim);
}

.finale-wax-heat {
    inset: 6%;
    border-radius: 50%;
    background: var(--wax-hot);
    opacity: 0;
    transition: opacity 0.6s var(--ease-silk);
}

/* The two-initial monogram, pressed into the wax (debossed). */
.finale-wax-mark {
    inset: 0;
    opacity: 0;
}

.finale-wax-ring {
    position: absolute;
    inset: 10%;
    border-radius: 50%;
    border: 1px solid rgba(184, 134, 47, 0.95);
    box-shadow: 0 1px 0 rgba(246, 220, 154, 0.55), inset 0 1px 1px rgba(22, 5, 9, 0.25);
}

.finale-wax-ring::after {
    content: '';
    position: absolute;
    inset: 9%;
    border-radius: 50%;
    border: 1px solid rgba(184, 134, 47, 0.8);
}

.finale-wax-mark .finale-mono-a,
.finale-wax-mark .finale-mono-b {
    color: var(--accent-warm-deep);
    text-shadow: 0 1px 0 rgba(246, 220, 154, 0.75), 0 -1px 0 rgba(22, 5, 9, 0.3);
}

.finale-wax-mark-glow {
    inset: -50%;
    border-radius: 50%;
    background: radial-gradient(circle,
        rgba(249, 238, 219, 0.9) 0%,
        rgba(246, 220, 154, 0.5) 26%,
        rgba(226, 177, 85, 0.16) 48%,
        transparent 68%);
    opacity: 0;
}

/* The initials, on the wax and on the stamp: Playfair italic, interlaced. */
.finale-mono-a,
.finale-mono-b {
    position: absolute;
    width: 46%;
    font-family: var(--font-display);
    font-style: italic;
    font-weight: 400;
    font-size: calc(var(--seal-d) * 0.36);
    line-height: 1;
    text-align: center;
    white-space: nowrap;
}

.finale-mono-a {
    left: 14%;
    top: 20%;
}

.finale-mono-b {
    left: 40%;
    top: 42%;
}

/* ───── The certificate the-award made, leaning at the left (0.46×) ───── */
/* Exactly where the-award's exit leaves it (js/the-award.js EXIT_*): its
   centre at 24% / 72% of the stage, tilted back 18deg in a 900px perspective,
   leaning -3deg, at 0.46x, about its centre. The same camera pose spans the
   cut, so the certificate does not move as the beat changes. */
.finale-cert {
    position: absolute;
    left: 24%;
    top: 72%;
    width: 0;
    height: 0;
}

.finale-cert.finale-cert-empty {
    display: none;
}

/* the-award's certificate width (shots.md: min(88vw, 66vh x 0.72), capped at
   520px), and its fixed 18:25 face, so the wrapper is centred on the anchor. */
.finale-cert-lean {
    --finale-cert-w: min(88vw, 520px, (96.5vh - 201px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px)) / 1.59);
    position: absolute;
    left: calc(var(--finale-cert-w) / -2);
    top: calc(var(--finale-cert-w) / -1.44);
    width: var(--finale-cert-w);
    transform-origin: 50% 50%;
    transform: perspective(900px) rotateX(18deg) rotateZ(-3deg) scale(0.46);
}

@supports (height: 1svh) {
    .finale-cert-lean {
        --finale-cert-w: min(88vw, 520px, (96.5svh - 201px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px)) / 1.59);
    }
}

.finale-cert-host {
    position: relative;
}

/* The glint ReveelStage.lightSweep runs across the certificate at 18 s. */
.finale-cert-lean .finale-cert-sheen {
    position: absolute;
    inset: 0;
    border-radius: 4px;
    pointer-events: none;
}

/* ───── The photo strips: 1–2 small booth strips, leaning on the envelope ───── */
.finale-strips {
    position: absolute;
    left: calc(var(--env-x) + var(--env-w) * 0.43);
    top: calc(var(--finale-env-bottom) + var(--env-w) * 0.05);
    width: 0;
    height: 0;
}

.finale-strips-host {
    position: absolute;
    left: 0;
    top: 0;
    width: 0;
    height: 0;
}

.finale-strip {
    position: absolute;
    bottom: 0;
    left: calc(var(--env-w) * -0.07);
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: calc(var(--env-w) * 0.012);
    width: calc(var(--env-w) * 0.14);
    padding: calc(var(--env-w) * 0.012);
    box-sizing: border-box;
    background: var(--paper);
    border-radius: 2px;
    box-shadow: 0 8px 18px rgba(22, 5, 9, 0.55), inset 0 0 0 1px var(--paper-edge);
    transform-origin: 50% 100%;
    transform: rotate(8deg);
}

/* The second strip stands behind, at the first one's left, leaning on the
   envelope's face and a little less: the pair fans out from the right end.
   The first strip's top corner (0.59 E right of the envelope's centre) is
   the tableau's right-most point, inside the frame (0.66 E at 76vw) at
   every camera pose. Its foot is on the envelope's lower edge, clear of the
   front ticket below. */
.finale-strip + .finale-strip {
    left: calc(var(--env-w) * -0.19);
    bottom: calc(var(--env-w) * 0.05);
    z-index: 1;
    transform: rotate(3deg);
}

.finale-strip-frame {
    position: relative;
    width: 100%;
    aspect-ratio: 4 / 5;
    overflow: hidden;
    background: var(--strip-frame);
}

.finale-strip-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* ───── The ticket stack: 1–12 tickets, his redeemed stamps on them ───── */
.finale-tickets {
    position: absolute;
    left: calc(var(--env-x) + var(--env-w) * 0.22);
    top: calc(var(--finale-env-bottom) + var(--env-w) * 0.12);
    width: 0;
    height: 0;
}

.finale-tickets-host {
    position: absolute;
    left: 0;
    top: 0;
    width: 0;
    height: 0;
}

.finale-ticket {
    position: absolute;
    left: calc(var(--env-w) * -0.23);
    top: calc(var(--env-w) * -0.1);
    display: flex;
    width: calc(var(--env-w) * 0.46);
    height: calc(var(--env-w) * 0.2);
    overflow: hidden;
    background: var(--paper);
    border-radius: 3px;
    box-shadow: 0 6px 14px rgba(22, 5, 9, 0.5), inset 0 0 0 1px var(--foil-line-soft);
}

.finale-ticket-stub {
    flex: 0 0 22%;
    border-right: 1.5px dashed var(--coupon-perf);
    background: linear-gradient(180deg, rgba(226, 177, 85, 0.24), rgba(184, 134, 47, 0.18));
}

.finale-ticket-body {
    position: relative;
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: calc(var(--env-w) * 0.008);
    padding: 0 7%;
}

.finale-ticket-kicker {
    font-family: var(--font-body);
    font-weight: 600;
    font-size: calc(var(--env-w) * 0.019);
    line-height: 1;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--wine);
}

/* The whole promise (up to 80 characters): four lines fit the ticket's
   height (4 x 0.036 E + the kicker < 0.2 E). finale.js › fitTicket() steps
   the front ticket's size down (--finale-ticket-k, to a 0.025 E floor) if a
   promise would still run past four; the clamp is only the last resort. */
.finale-ticket-text {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 4;
    font-family: var(--font-body);
    font-weight: 500;
    font-size: calc(var(--env-w) * var(--finale-ticket-k, 0.03));
    line-height: 1.2;
    color: var(--ink);
    overflow-wrap: anywhere;
}

/* The stamp he made in three-gifts: claret ink, thudded on askew. */
.finale-ticket-stamp {
    position: absolute;
    right: 5%;
    top: 50%;
    max-width: 92%;
    padding: 0.32em 0.5em;
    overflow: hidden;
    border: 1.5px solid var(--coupon-stamp-ring);
    border-radius: 3px;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: calc(var(--env-w) * 0.024);
    line-height: 1;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--coupon-stamp);
    opacity: 0.92;
    transform: translateY(-50%) rotate(-9deg);
}

/* ───── The dark at the edges ─────
   One pre-painted layer, centred on the envelope (whose centre maps to
   ((env-x + 40%) / 1.8, (env-y + 40%) / 1.8) in this 1.8x box), clear over
   the envelope and darkening outward. Its opacity is the only thing that
   moves: the tableau waits barely lit, and the table brightens a step. */
.finale-vignette {
    position: absolute;
    inset: -40%;
    background: radial-gradient(
        ellipse calc(var(--env-w) * 0.95) calc(var(--env-w) * 0.66)
            at calc((var(--env-x) + 40%) / 1.8) calc((var(--env-y) + 40%) / 1.8),
        transparent 0%,
        transparent 52%,
        rgba(22, 5, 9, 0.6) 100%,
        rgba(22, 5, 9, 0.84) 165%);
    transition: opacity 2.4s var(--ease-silk);
}

.finale-container.finale-is-bloom .finale-vignette {
    opacity: 0.38;
}

/* ───── Glints: the strips, the record, the tickets, in turn ───── */
.finale-glints {
    position: absolute;
    inset: 0;
}

.finale-glint {
    position: absolute;
    width: 34px;
    height: 34px;
    margin: -17px 0 0 -17px;
    border-radius: 50%;
    background: radial-gradient(circle,
        rgba(249, 238, 219, 0.95) 0%,
        rgba(246, 220, 154, 0.5) 18%,
        transparent 60%);
    opacity: 0;
}

.finale-glint::before,
.finale-glint::after {
    content: '';
    position: absolute;
    left: -30%;
    top: 50%;
    width: 160%;
    height: 2px;
    margin-top: -1px;
    background: linear-gradient(90deg, transparent, rgba(249, 238, 219, 0.95), transparent);
}

.finale-glint::after {
    transform: rotate(90deg);
}

.finale-glint-strips {
    left: calc(var(--env-x) + var(--env-w) * 0.47);
    top: calc(var(--finale-env-bottom) - var(--env-w) * 0.3);
}

.finale-glint-record {
    left: calc(var(--env-x) + var(--env-w) * 0.665);
    top: calc(var(--finale-env-top) - var(--env-w) * 0.05);
}

.finale-glint-tickets {
    left: calc(var(--env-x) + var(--env-w) * 0.39);
    top: calc(var(--finale-env-bottom) + var(--env-w) * 0.08);
}

.finale-container.finale-is-glint-strips .finale-glint-strips,
.finale-container.finale-is-glint-record .finale-glint-record,
.finale-container.finale-is-glint-tickets .finale-glint-tickets {
    animation: finale-glint 1.5s var(--ease-expo-out) forwards;
}

/* ───── Overhead: the reasons' wreath, a faint halo ───── */
.finale-halo {
    position: absolute;
    left: calc(var(--env-x) - var(--env-w) * 0.65);
    top: calc(-3% - var(--env-w) * 0.11);
    width: calc(var(--env-w) * 1.3);
    height: calc(var(--env-w) * 0.22);
    opacity: 0.38;
    transition: opacity 2.4s var(--ease-silk);
}

.finale-halo-glow {
    position: absolute;
    left: -30%;
    right: -30%;
    top: -140%;
    bottom: -300%;
    background: radial-gradient(ellipse 50% 50% at 50% 36%,
        rgba(246, 220, 154, 0.2) 0%,
        rgba(226, 177, 85, 0.07) 46%,
        transparent 76%);
}

.finale-halo-ring {
    position: absolute;
    inset: 0;
}

.finale-halo-card {
    position: absolute;
    width: calc(var(--env-w) * 0.026);
    height: calc(var(--env-w) * 0.034);
    margin: calc(var(--env-w) * -0.017) 0 0 calc(var(--env-w) * -0.013);
    border-radius: 1px;
    background: var(--paper);
    box-shadow: 0 0 6px 2px rgba(246, 220, 154, 0.55), 0 0 16px 4px rgba(226, 177, 85, 0.22);
}

.finale-container.finale-is-bloom .finale-halo {
    opacity: 0.85;
}

/* ───── A dark pool behind the words (keeps the bokeh's light off them) ───── */
.finale-reading {
    position: absolute;
    left: -8%;
    right: -8%;
    top: 8%;
    bottom: calc(100% - var(--finale-env-top) - 10px);
    background: radial-gradient(ellipse 50% 50% at 50% 56%,
        rgba(22, 5, 9, 0.5) 0%,
        rgba(22, 5, 9, 0.3) 52%,
        transparent 100%);
}

/* ───── Over the wax: the ring of light, the seal-stamp, the hold surface ───── */
.finale-anchor {
    position: absolute;
    left: 50%;
    top: var(--flap-depth);
    width: 0;
    height: 0;
}

/* A ring of light expanding from the wax (--chime-ring, 900 ms). Pre-painted;
   it never reaches the words above the envelope. */
.finale-chime {
    position: absolute;
    left: calc(var(--env-w) * -0.52);
    top: calc(var(--env-w) * -0.52);
    width: calc(var(--env-w) * 1.04);
    height: calc(var(--env-w) * 1.04);
    border-radius: 50%;
    background:
        radial-gradient(circle closest-side,
            transparent 0 84%,
            var(--chime-ring) 91%,
            rgba(246, 220, 154, 0.3) 95%,
            transparent 100%),
        radial-gradient(circle closest-side, rgba(246, 220, 154, 0.16), transparent 62%);
    opacity: 0;
    transform: scale(0.08);
}

.finale-container.finale-is-bloom .finale-chime {
    animation: finale-chime 0.9s var(--ease-expo-out) forwards;
}

.finale-stamp-shadow {
    position: absolute;
    left: calc(var(--seal-d) * -0.6);
    top: calc(var(--seal-d) * -0.22);
    width: calc(var(--seal-d) * 1.3);
    height: calc(var(--seal-d) * 0.7);
    border-radius: 50%;
    background: radial-gradient(ellipse closest-side, rgba(22, 5, 9, 0.6), transparent);
    opacity: 0;
    transition: opacity 0.6s var(--ease-silk);
}

.finale-container.finale-is-stamp .finale-stamp-shadow {
    opacity: 0.3;
    transition: opacity 1.6s var(--ease-silk);
}

.finale-container.finale-is-pressing .finale-stamp-shadow {
    opacity: 0.6;
    transition: opacity 1.4s var(--ease-breath);
}

.finale-container.finale-is-pressed .finale-stamp-shadow {
    opacity: 0.75;
    transition: opacity 0.18s var(--ease-snap);
}

.finale-container.finale-is-lifted .finale-stamp-shadow {
    opacity: 0;
    transition: opacity 1s var(--ease-silk);
}

.finale-stamp {
    --finale-hover: calc(var(--seal-d) * -1.25);
    position: absolute;
    left: calc(var(--seal-d) / -2);
    top: calc(var(--seal-d) / -2);
    width: var(--seal-d);
    height: var(--seal-d);
}

/* Its poses, in film order: above the frame; lowering to hover over the wax
   (expo-out, 1.6 s); descending while the ring fills (breath, 1.4 s) or
   rising back on an early lift (0.6 s); pressed (snap, 180 ms); lifted away
   (1.2 s). Later rules win while several classes are present. */
.finale-stamp-body {
    position: absolute;
    inset: 0;
    opacity: 0;
    transform: translateY(-62vh) rotate(-16deg) scale(1.08);
}

.finale-container.finale-is-stamp .finale-stamp-body {
    opacity: 1;
    transform: translateY(var(--finale-hover)) rotate(-5deg) scale(1.08);
    transition: transform 1.6s var(--ease-expo-out), opacity 0.6s var(--ease-silk);
}

.finale-container.finale-is-armed .finale-stamp-body {
    transition: transform 0.6s var(--ease-expo-out), opacity 0.6s var(--ease-silk);
}

.finale-container.finale-is-pressing .finale-stamp-body {
    transform: translateY(calc(var(--finale-hover) * 0.2)) rotate(-1deg) scale(1.03);
    transition: transform 1.4s var(--ease-breath);
}

.finale-container.finale-is-pressed .finale-stamp-body {
    opacity: 1;
    transform: scale(0.97);
    transition: transform 0.18s var(--ease-snap);
}

.finale-container.finale-is-lifted .finale-stamp-body {
    opacity: 0;
    transform: translateY(calc(var(--seal-d) * -1.7)) rotate(4deg) scale(1.2);
    transition: transform 1.2s var(--ease-expo-out), opacity 1.2s var(--ease-silk);
}

/* Tilting gently while it hovers (a looping ornament, parked otherwise). */
.finale-stamp-tilt {
    position: absolute;
    inset: 0;
}

.finale-container.finale-is-stamp:not(.finale-is-pressing):not(.finale-is-pressed) .finale-stamp-tilt {
    animation: finale-tilt 2.67s var(--ease-breath) infinite alternate;
}

.finale-stamp-knob,
.finale-stamp-neck,
.finale-stamp-collar,
.finale-stamp-face {
    position: absolute;
    display: block;
}

.finale-stamp-knob {
    left: 31%;
    top: calc(var(--seal-d) * -0.74);
    width: calc(var(--seal-d) * 0.38);
    height: calc(var(--seal-d) * 0.38);
    border-radius: 50%;
    background: radial-gradient(circle at 36% 30%, var(--accent-warm-bright) 0%, var(--accent-warm) 46%, var(--accent-warm-deep) 100%);
    box-shadow: inset 0 -2px 4px rgba(22, 5, 9, 0.3);
}

.finale-stamp-neck {
    left: 40%;
    top: calc(var(--seal-d) * -0.44);
    width: 20%;
    height: calc(var(--seal-d) * 0.4);
    background: linear-gradient(90deg, var(--accent-warm-deep) 0%, var(--accent-warm-bright) 40%, var(--accent-warm) 64%, var(--accent-warm-deep) 100%);
}

.finale-stamp-collar {
    left: 18%;
    top: calc(var(--seal-d) * -0.12);
    width: 64%;
    height: calc(var(--seal-d) * 0.22);
    border-radius: 40% 40% 8% 8% / 60% 60% 12% 12%;
    background: linear-gradient(90deg, var(--accent-warm-deep) 0%, var(--accent-warm-bright) 38%, var(--accent-warm) 62%, var(--accent-warm-deep) 100%);
}

/* Polished metal, not wax: a brushed conic sheen and a hard, dark rim, so the
   stamp never reads as a second blob of wax beside the pool. */
.finale-stamp-face {
    inset: 0;
    border-radius: 50%;
    background:
        radial-gradient(circle at 34% 28%, rgba(249, 238, 219, 0.55) 0%, transparent 42%),
        conic-gradient(from 200deg,
            var(--accent-warm-deep) 0%, var(--accent-warm-bright) 16%, var(--accent-warm) 30%,
            var(--accent-warm-deep) 50%, var(--accent-warm-bright) 68%, var(--accent-warm) 84%,
            var(--accent-warm-deep) 100%);
    box-shadow:
        inset 0 0 0 2px rgba(116, 82, 26, 0.85),
        inset 0 -4px 8px rgba(22, 5, 9, 0.32),
        inset 0 2px 4px rgba(249, 238, 219, 0.6),
        0 10px 22px rgba(22, 5, 9, 0.55);
}

.finale-stamp-ring {
    position: absolute;
    inset: 9%;
    border-radius: 50%;
    border: 1.5px solid rgba(184, 134, 47, 0.9);
}

.finale-stamp-ring::after {
    content: '';
    position: absolute;
    inset: 8%;
    border-radius: 50%;
    border: 1px solid rgba(184, 134, 47, 0.75);
}

.finale-stamp-face .finale-mono-a,
.finale-stamp-face .finale-mono-b {
    color: var(--accent-warm-deep);
    text-shadow: 0 -1px 0 rgba(249, 238, 219, 0.6), 0 1px 1px rgba(22, 5, 9, 0.25);
}

/* The hold surface: the wax and a thumb's width around it. Not a control
   (the button is), so it carries data-reveel-avoid. Enabled with the hint. */
.finale-press {
    position: absolute;
    left: 0;
    top: 0;
    width: max(96px, calc(var(--seal-d) * 1.9));
    height: max(96px, calc(var(--seal-d) * 1.9));
    border-radius: 50%;
    transform: translate(-50%, -50%);
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
    cursor: pointer;
    pointer-events: none;
    visibility: hidden;
}

.finale-container.finale-is-hint .finale-press {
    pointer-events: auto;
    visibility: visible;
}

.finale-container.finale-is-pressed .finale-press {
    pointer-events: none;
    visibility: hidden;
    transition: visibility 0s linear 0.6s;
}

/* The ring, around the thumb. */
.finale-ring {
    position: absolute;
    left: var(--finale-rx, 50%);
    top: var(--finale-ry, 50%);
    width: 88px;
    height: 88px;
    margin: -44px 0 0 -44px;
    opacity: 0;
    transform: scale(0.86);
    transition: opacity 0.35s var(--ease-silk), transform 0.35s var(--ease-expo-out);
    pointer-events: none;
}

.finale-ring::before {
    content: '';
    position: absolute;
    inset: -14px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(246, 220, 154, 0.26), transparent 66%);
}

.finale-ring-svg {
    position: relative;
    display: block;
    width: 100%;
    height: 100%;
    overflow: visible;
    transform: rotate(-90deg);
}

.finale-ring-track {
    fill: rgba(22, 5, 9, 0.22);
    stroke: var(--ring-track);
    stroke-width: 2;
}

.finale-ring-arc {
    fill: none;
    stroke: var(--ring-fill);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-dasharray: 238.8;
    stroke-dashoffset: 238.8;
    /* an early lift drains it over 0.6 s; nothing is lost */
    transition: stroke-dashoffset 0.6s var(--ease-silk);
}

.finale-ring.finale-ring-filling {
    opacity: 1;
    transform: none;
}

.finale-ring.finale-ring-filling .finale-ring-arc {
    stroke-dashoffset: 0;
    transition: stroke-dashoffset 1.4s var(--ease-breath);
}

.finale-ring.finale-ring-done {
    opacity: 0;
    transform: scale(1.3);
    transition: opacity 0.5s var(--ease-silk), transform 0.5s var(--ease-expo-out);
}

.finale-ring.finale-ring-done .finale-ring-arc {
    stroke-dashoffset: 0;
}

/* ───── The wax: heat while pressed, then the squeeze and the monogram ───── */
.finale-container.finale-is-pressing .finale-wax-heat {
    opacity: 0.85;
    transition: opacity 1.4s var(--ease-breath);
}

.finale-container.finale-is-pressing .finale-wax-flare {
    opacity: 0.9;
    transition: opacity 1.4s var(--ease-breath);
}

.finale-container.finale-is-pressed .finale-wax-flare {
    opacity: 0;
    transition: opacity 1.2s var(--ease-silk);
}

.finale-container.finale-is-pressed .finale-wax-heat {
    opacity: 0;
    transition: opacity 1.4s var(--ease-silk);
}

.finale-container.finale-is-pressed .finale-wax-pool {
    animation: finale-squeeze 0.42s var(--ease-snap) forwards;
}

.finale-container.finale-is-pressed .finale-wax-rim {
    opacity: 1;
    transform: scale(1.04);
    transition: opacity 0.18s var(--ease-snap), transform 0.18s var(--ease-snap);
}

.finale-container.finale-is-pressed .finale-wax-mark {
    opacity: 1;
    transition: opacity 0.25s var(--ease-silk);
}

.finale-container.finale-is-pressed .finale-wax-mark-glow {
    animation: finale-mark-glow 2.6s var(--ease-expo-out) forwards;
}

/* ───── "One last thing." (3 s, Playfair italic, on a gilt plaque) ───── */
.finale-oneline {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(100% - var(--finale-env-top) + var(--seal-d) * 1.5);
    margin: 0;
    padding: 0 16px;
    text-align: center;
    opacity: 0;
    transform: translateY(8px);
}

.finale-oneline-plaque {
    position: relative;
    display: inline-block;
    padding: 0.5rem 1.25rem 0.56rem;
    font-family: var(--font-display);
    font-style: italic;
    font-weight: 400;
    font-size: clamp(1.1rem, 4.8vw, 1.45rem);
    line-height: 1.2;
    color: var(--text-primary);
    background: var(--plaque-fill);
    border: 1px solid var(--foil-line-soft);
    border-radius: 999px;
    box-shadow: var(--plaque-inset);
}

.finale-container.finale-is-oneline .finale-oneline {
    opacity: 1;
    transform: none;
    transition: opacity 0.9s var(--ease-silk), transform 1.2s var(--ease-expo-out);
}

/* It has said its piece once the stamp presses. */
.finale-container.finale-is-pressed .finale-oneline {
    opacity: 0;
    transition: opacity 0.35s var(--ease-silk);
}

/* ───── The words: the flourish, the line, the signature ─────
   Between the halo and the envelope, bottom-aligned onto the envelope.
   The wipes are transform-only: the window (overflow: hidden) slides in
   from the left while its contents slide the other way, so the text
   stands still and is revealed left to right. */
.finale-copy {
    position: absolute;
    left: 0;
    right: 0;
    top: 14%;
    bottom: calc(100% - var(--finale-env-top) + 14px);
    width: min(88%, 34rem);
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    text-align: center;
}

/* Script ink runs past its letters' boxes: a closing N, W or T reaches
   0.67em past its advance, an opening j 0.57em before it, an accent on
   the top line ~0.3em above the line box. The window clips at its padding
   box, so it is padded out past the inner by the bleeds below and pulled
   back by the same negative margins: its layout box is the inner's, as
   before. The size lives here (fitGreeting() sets it on the window) so
   the bleeds scale with the words. */
.finale-greet {
    --finale-greet-bleed-x: 0.35em;
    --finale-greet-bleed-y: 0.5em;
    position: relative;
    flex-shrink: 0;               /* measured at its natural height by fitGreeting() */
    max-width: calc(100% + 2 * var(--finale-greet-bleed-x));
    margin: calc(-1 * var(--finale-greet-bleed-y)) calc(-1 * var(--finale-greet-bleed-x));
    padding: var(--finale-greet-bleed-y) var(--finale-greet-bleed-x);
    font-size: var(--finale-greet-size, clamp(2rem, 11vw, 3.4rem));
    overflow: hidden;
    transform: translateX(-101%);
}

.finale-greet-inner {
    position: relative;
    display: grid;                /* the greeting and its shine share one cell: one box, one wrap */
    padding: 0.12em 0.42em 0.22em;
    /* the window is two bleeds wider than this: the same distance, so the words stand still */
    transform: translateX(calc(101% + 2.02 * var(--finale-greet-bleed-x)));
}

/* A background-clip: text layer paints only inside its own box, so both
   gilt layers are padded out to the window's edges (the inner's padding
   plus the bleeds), and pulled back by the same negative margins: the
   words' box is the inner's content box, as before. */
.finale-greeting,
.finale-greeting-shine {
    grid-area: 1 / 1;
    margin: calc(-0.12em - var(--finale-greet-bleed-y)) calc(-0.42em - var(--finale-greet-bleed-x)) calc(-0.22em - var(--finale-greet-bleed-y));
    padding: calc(0.12em + var(--finale-greet-bleed-y)) calc(0.42em + var(--finale-greet-bleed-x)) calc(0.22em + var(--finale-greet-bleed-y));
    font-family: var(--font-script);
    font-weight: 400;
    font-size: 1em;
    line-height: 1.14;
    text-align: center;
    text-wrap: balance;
    overflow-wrap: anywhere;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
}

/* The gradients' stops are moved in by that padding's reach along the
   100deg line, so every colour falls where it did on the unpadded boxes:
   the foil is var(--foil-gradient)'s stops (0 42 52 100%) on the words'
   box, the shine its own (0 50 100%) on the inner's padding box. */
.finale-greeting {
    background-image: linear-gradient(100deg,
        var(--accent-warm-deep) 0.866em,
        var(--accent-warm-bright) calc(42% + 0.1313em),
        var(--text-primary) calc(52% - 0.0437em),
        var(--accent-warm) calc(100% - 0.8833em));
    filter: drop-shadow(0 0 12px rgba(226, 177, 85, 0.32));
}

/* A brighter twin over it: its opacity is the shimmer. */
.finale-greeting-shine {
    position: relative;           /* painted over the greeting, as when it was absolute */
    background-image: linear-gradient(100deg, var(--accent-warm-bright) 0.4315em, var(--light-core) 50%, var(--accent-warm-bright) calc(100% - 0.4315em));
    opacity: 0;
}

.finale-container.finale-is-greeted .finale-greet,
.finale-container.finale-is-greeted .finale-greet-inner {
    transform: none;
    transition: transform 3s var(--ease-silk);
}

.finale-container.finale-is-greeted .finale-greeting-shine {
    animation: finale-shine 4.8s var(--ease-breath) 3.4s infinite alternate;
}

.finale-foot {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    max-width: 100%;
    padding-top: 0.45rem;
}

.finale-lucky {
    margin: 0;
    font-family: var(--font-display);
    font-style: italic;
    font-weight: 400;
    /* the film's closing line: second only to the greeting (about 22 px at 360) */
    font-size: clamp(1.34rem, 6vw, 1.56rem);
    line-height: 1.3;
    color: var(--text-primary);
    text-wrap: balance;
    text-shadow: 0 1px 12px rgba(22, 5, 9, 0.7);
    opacity: 0;
    transform: translateY(10px);
}

.finale-container.finale-is-lucky .finale-lucky {
    opacity: 1;
    transform: none;
    transition: opacity 1.2s var(--ease-silk), transform 1.2s var(--ease-expo-out);
}

/* The same for the name: its last stroke (an N, a d, an f) runs up to
   0.67 of its size past the letter, an accent above the line. The window
   is padded out at the right, top and bottom (never the left: the dash
   leads) by bleeds sized from the signature's unfitted size, which a fit
   only shrinks, and pulled back by matching negative margins. */
.finale-sign {
    --finale-sign-bleed-r: calc(clamp(1.7rem, 7.6vw, 2.3rem) * 0.65);
    --finale-sign-bleed-y: calc(clamp(1.7rem, 7.6vw, 2.3rem) * 0.4);
    position: relative;
    max-width: calc(100% + var(--finale-sign-bleed-r));
    margin: calc(0.2rem - var(--finale-sign-bleed-y)) calc(-1 * var(--finale-sign-bleed-r)) calc(-1 * var(--finale-sign-bleed-y)) 0;
    padding: var(--finale-sign-bleed-y) var(--finale-sign-bleed-r) var(--finale-sign-bleed-y) 0;
    overflow: hidden;
    transform: translateX(-101%);
}

.finale-sign-inner {
    padding: 0.08em 0.4em 0.2em;
    transform: translateX(calc(101% + 1.01 * var(--finale-sign-bleed-r)));
}

.finale-signature {
    margin: 0;
    font-family: var(--font-script);
    font-weight: 400;
    font-size: var(--finale-sign-size, clamp(1.7rem, 7.6vw, 2.3rem));
    line-height: 1.15;
    white-space: nowrap;
    color: var(--accent-warm-bright);
    text-shadow: 0 0 14px rgba(226, 177, 85, 0.3);
}

.finale-container.finale-is-signed .finale-sign,
.finale-container.finale-is-signed .finale-sign-inner {
    transform: none;
    transition: transform 1.8s var(--ease-silk);
}

/* ───── The soft gold flash (ReveelStage.flash), blooming from the wax ─────
   Bounded to about an envelope's width around it, so its light never
   reaches the words. finale.js sets --flash-x/-y and this class. */
.reveel-stage.finale-flash-soft.reveel-flash::after {
    background: radial-gradient(circle calc(var(--env-w) * 0.8) at var(--flash-x, 50%) var(--flash-y, 55%),
        rgba(249, 238, 219, 0.62) 0%,
        rgba(246, 220, 154, 0.34) 30%,
        rgba(226, 177, 85, 0.12) 62%,
        transparent 100%);
}

/* ───── Screen-space controls (badge rule) ─────
   Boot hoists #finale-controls into #stage, outside the camera's world, so
   the button and "Watch again" never move while the camera does; they only
   fade. Shown from the beat's entrance (finale-is-in) until prepare(). */
.finale-controls {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: none;
    pointer-events: none;
}

.finale-controls.finale-is-in {
    display: block;
}

.finale-controls .finale-btn {
    pointer-events: auto;
}

.finale-hint,
.finale-replay {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(8% + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.6rem;
    padding: 0 16px;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.6s var(--ease-silk), visibility 0s linear 0.6s;
}

.finale-controls.finale-is-hint .finale-hint {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.6s var(--ease-silk);
}

.finale-controls.finale-is-pressed .finale-hint {
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.4s var(--ease-silk), visibility 0s linear 0.4s;
}

.finale-controls.finale-is-again .finale-replay {
    opacity: 1;
    visibility: visible;
    transition: opacity 1.4s var(--ease-silk);
}

.finale-hint-text {
    margin: 0;
    font-family: var(--font-body);
    font-weight: 500;
    font-size: 0.74rem;
    line-height: 1.3;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    text-align: center;
    color: var(--text-secondary);
    text-shadow: 0 1px 10px rgba(22, 5, 9, 0.8);
}

.finale-btn {
    min-height: 44px;
    padding: 0.8rem 1.4rem;
    font-family: var(--font-body);
    font-weight: 500;
    font-size: 0.88rem;
    line-height: 1;
    letter-spacing: 0.04em;
    color: var(--text-primary);
    background: var(--glass);
    border: 1px solid var(--glass-border-hover);
    border-radius: 999px;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.finale-btn:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 3px;
}

.finale-btn-ghost {
    background: transparent;
    border-color: var(--btn-ghost-border);
    color: var(--accent-warm-bright);
}

/* ───── Keyframes (finale- namespace) ───── */
@keyframes finale-breathe {
    from { opacity: 0.35; }
    to { opacity: 0.85; }
}

@keyframes finale-tilt {
    from { transform: rotate(-2deg) translateY(0); }
    to { transform: rotate(2deg) translateY(-3px); }
}

@keyframes finale-squeeze {
    0% { transform: scale(1); }
    40% { transform: scale(1.14, 0.9); }
    100% { transform: scale(1.06); }
}

@keyframes finale-mark-glow {
    0% { opacity: 0; transform: scale(0.6); }
    28% { opacity: 1; transform: scale(1.1); }
    100% { opacity: 0.45; transform: scale(1); }
}

@keyframes finale-chime {
    0% { opacity: 1; transform: scale(0.08); }
    55% { opacity: 0.9; }
    100% { opacity: 0; transform: scale(1); }
}

@keyframes finale-glint {
    0% { opacity: 0; transform: scale(0.2) rotate(0deg); }
    35% { opacity: 1; transform: scale(1.15) rotate(30deg); }
    100% { opacity: 0; transform: scale(0.5) rotate(60deg); }
}

@keyframes finale-shine {
    from { opacity: 0; }
    to { opacity: 0.5; }
}

@keyframes finale-fade-pulse {
    0% { opacity: 0; }
    30% { opacity: 0.7; }
    100% { opacity: 0; }
}

/* ───── Reduced motion: every step still plays, calmly ─────
   The camera no-ops in ReveelStage, so there is no pull-back or crane: the
   whole still life is simply there, framed (the scene sits at 0.8x so the
   tableau at the edges is in frame), and lit at the seal. The envelope
   fades up in place; the stamp cross-fades onto the wax (no descent); the
   flourish and lines fade in whole; nothing loops. finale.js mirrors the
   0.8 in REDUCED_SCALE. */
@media (prefers-reduced-motion: reduce) {
    .finale-scene {
        transform: scale(0.8);
    }

    .finale-env-rise {
        transform: none;
        opacity: 0;
    }

    .finale-container.finale-is-in .finale-env-rise {
        opacity: 1;
        transition: opacity 1.6s var(--ease-silk);
    }

    .finale-stamp-body,
    .finale-container.finale-is-stamp .finale-stamp-body,
    .finale-container.finale-is-armed .finale-stamp-body,
    .finale-container.finale-is-pressing .finale-stamp-body,
    .finale-container.finale-is-pressed .finale-stamp-body,
    .finale-container.finale-is-lifted .finale-stamp-body {
        transform: translateY(var(--finale-hover)) scale(1.08);
    }

    .finale-container.finale-is-stamp .finale-stamp-body {
        transition: opacity 1.2s var(--ease-silk);
    }

    .finale-container.finale-is-pressed .finale-stamp-body,
    .finale-container.finale-is-lifted .finale-stamp-body {
        opacity: 0;
        transition: opacity 0.8s var(--ease-silk);
    }

    .finale-container.finale-is-stamp:not(.finale-is-pressing):not(.finale-is-pressed) .finale-stamp-tilt,
    .finale-wax-glow,
    .finale-container.finale-is-greeted .finale-greeting-shine {
        animation: none;
    }

    .finale-container.finale-is-pressed .finale-wax-pool {
        animation: none;
    }

    .finale-container.finale-is-pressed .finale-wax-rim {
        transform: none;
    }

    .finale-container.finale-is-pressed .finale-wax-mark {
        transition: opacity 0.8s var(--ease-silk);
    }

    .finale-container.finale-is-pressed .finale-wax-mark-glow,
    .finale-container.finale-is-bloom .finale-chime,
    .finale-container.finale-is-glint-strips .finale-glint-strips,
    .finale-container.finale-is-glint-record .finale-glint-record,
    .finale-container.finale-is-glint-tickets .finale-glint-tickets {
        transform: none;
        animation: finale-fade-pulse 1.6s var(--ease-silk) forwards;
    }

    .finale-ring {
        display: none;
    }

    .finale-oneline,
    .finale-lucky {
        transform: none;
    }

    .finale-greet,
    .finale-greet-inner,
    .finale-sign,
    .finale-sign-inner {
        transform: none;
    }

    .finale-greet,
    .finale-sign {
        opacity: 0;
    }

    .finale-container.finale-is-greeted .finale-greet,
    .finale-container.finale-is-signed .finale-sign {
        opacity: 1;
        transition: opacity 1.6s var(--ease-silk);
    }

    .finale-container.finale-is-greeted .finale-greet-inner,
    .finale-container.finale-is-signed .finale-sign-inner {
        transition: none;
    }
}
