/* ═══════════════════════════════════════════
   finale — the years become stars
   Every play state is a class on #finale-container (finale-is-*), toggled
   by the beat's cues; boot rebuilds only the tiles and stars inside it,
   so a config keystroke never undoes what the film has revealed.
   ═══════════════════════════════════════════ */

.finale-container {
    position: absolute;
    inset: 0;
    overflow: hidden;
    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;
}

/* ───── The sky: the new stars, around the name ───── */
.finale-sky {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.finale-new-stars {
    position: absolute;
    inset: 0;
}

/* The years, become stars: brighter than the starfield. Ignited (a wave
   from the name across the whole sky), twinkled and pulsed by Web
   Animations in finale.js, which fire no events for the badge guard. */
.finale-new-star {
    position: absolute;
    width: 12px;
    height: 12px;
    margin: -6px 0 0 -6px;
    border-radius: 50%;
    background: radial-gradient(circle, var(--light-core) 0 30%, var(--spark) 55%, transparent 74%);
    box-shadow: var(--photo-star-glow);
    opacity: 0;
}

.finale-new-star-halo {
    position: absolute;
    inset: -10px;
    border-radius: 50%;
    background: radial-gradient(circle, var(--halo) 0%, transparent 70%);
    opacity: 0.6;
}

/* The first photo the recipient placed (years-in-order's memory): the brightest. */
.finale-new-star.finale-new-star-first {
    width: 18px;
    height: 18px;
    margin: -9px 0 0 -9px;
    background: radial-gradient(circle, var(--light-core) 0 36%, var(--accent-warm-bright) 60%, transparent 76%);
    box-shadow: 0 0 22px 7px rgba(242, 196, 160, 0.7), 0 0 60px 16px rgba(232, 168, 124, 0.3);
}

.finale-new-star.finale-new-star-first .finale-new-star-halo {
    inset: -22px;
}

/* ───── The peak: a ring of light expanding from the photos' rising light ─────
   White-gold at its core, fading to a bright band at its edge; scaled by a
   Web Animation from a point to 260vmax, so the ring passes every screen
   edge. Under the burst's sparks (it lives in the world's sky). */
.finale-shock {
    position: absolute;
    left: var(--finale-bx, 50%);
    top: var(--finale-by, 38%);
    width: 260vmax;
    height: 260vmax;
    border-radius: 50%;
    background: radial-gradient(circle closest-side,
        var(--light-core) 0%,
        var(--accent-warm-glow) 18%,
        transparent 40%,
        transparent 74%,
        var(--accent-warm-glow) 86%,
        var(--light-core) 92%,
        var(--flash-mid) 96%,
        transparent 100%);
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.04);
    pointer-events: none;
}

/* The primitive's flash at the peak: an even white-gold lift over the whole
   stage (no centred bloom, which is the cap toss's picture); the ring
   carries the shape. */
.reveel-stage.finale-peak.reveel-flash::after {
    background: radial-gradient(circle farthest-corner at var(--flash-x, 50%) var(--flash-y, 38%),
        var(--flash-mid) 0%, var(--accent-warm-glow) 60%, var(--accent-warm-glow-soft) 100%);
}

/* ───── The mosaic ─────
   Size and tile boxes are written by layoutFinale() (justified rows, 2px
   seams, every photo at its own ratio). Before the gather each tile is a
   faint photo-star overhead: translated to its star and scaled to a dot.
   Its top (35%) sits just below the persistent masthead's finale pose
   (name-in-lights: centre at 25%, 0.64x), so the lifted light (~130px up)
   meets the name's halo. */
.finale-mosaic-wrap {
    position: absolute;
    top: 35%;
    left: 50%;
    width: var(--finale-mw, 70vw);
    height: var(--finale-mh, 30vh);
    margin-left: calc(var(--finale-mw, 70vw) / -2);
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
}

.finale-mosaic {
    position: absolute;
    inset: 0;
    transition: opacity 0.5s var(--ease-silk), transform 1.4s var(--ease-expo-out);
}

/* The gilt hairline around the whole mosaic, once it has gathered. */
.finale-mosaic::before {
    content: '';
    position: absolute;
    inset: -1px;
    border: 1px solid var(--frame);
    box-shadow: var(--frame-shadow);
    opacity: 0;
    transition: opacity 1.2s var(--ease-silk) 4.2s;
    pointer-events: none;
}

/* The brightening from its edges, on completion of the hold. */
.finale-mosaic::after {
    content: '';
    position: absolute;
    inset: 0;
    box-shadow:
        inset 0 0 22px 6px var(--light-core),
        inset 0 0 60px 18px var(--accent-warm-glow),
        0 0 36px 6px var(--accent-warm-glow);
    opacity: 0;
    transition: opacity 0.55s var(--ease-silk);
    pointer-events: none;
    z-index: 2;
}

.finale-tile {
    position: absolute;
    left: var(--finale-l, 0);
    top: var(--finale-t, 0);
    width: var(--finale-w, 0);
    height: var(--finale-h, 0);
    overflow: hidden;
    border-radius: 50%;
    background: var(--bg-elevated);
    opacity: 0.5;
    transform: translate(var(--finale-fx, 0), var(--finale-fy, 0)) scale(var(--finale-sx, 0.05), var(--finale-sy, 0.05));
    transition:
        transform 2.4s var(--ease-drift) var(--finale-d, 0s),
        border-radius 2.4s var(--ease-drift) var(--finale-d, 0s),
        opacity 1.4s var(--ease-silk) var(--finale-d, 0s);
}

/* The star each tile is before it becomes a photo again. */
.finale-tile::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
    background: radial-gradient(circle, var(--light-core) 0%, var(--spark) 45%, var(--accent-warm-deep) 100%);
    opacity: 1;
    transition: opacity 1.5s var(--ease-silk) calc(var(--finale-d, 0s) + 0.8s);
}

.finale-tile-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover; /* crops only to square off the rows */
    pointer-events: none;
}

.finale-container.finale-is-gathered .finale-tile {
    opacity: 1;
    border-radius: 1px;
    transform: none;
}

.finale-container.finale-is-gathered .finale-tile::before {
    opacity: 0;
}

.finale-container.finale-is-gathered .finale-mosaic::before {
    opacity: 1;
}

.finale-container.finale-is-bright .finale-mosaic::after {
    opacity: 1;
}

.finale-container.finale-is-dissolved .finale-mosaic {
    opacity: 0;
    transform: translateY(-16px);
}

/* Once the photos are light, the hold surface leaves (its data-reveel-avoid
   would otherwise linger, moving with the crane). */
.finale-container.finale-is-dissolved .finale-mosaic-wrap {
    visibility: hidden;
    transition: visibility 0s linear 1.4s;
}

/* ───── The hold 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;
    z-index: 3;
}

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

.finale-ring-track {
    fill: rgba(5, 5, 17, 0.28);
    stroke: var(--gilt-line);
    stroke-width: 2;
}

.finale-ring-arc {
    fill: none;
    stroke: var(--light-core);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-dasharray: 226.2;
    stroke-dashoffset: 226.2;
    filter: drop-shadow(0 0 5px var(--accent-warm-glow));
    /* lifting early drains it gently */
    transition: stroke-dashoffset 0.8s 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.35);
    transition: opacity 0.6s var(--ease-silk), transform 0.6s var(--ease-expo-out);
}

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

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

.reveel-stage:has(#screen-finale.active) > .finale-controls,
#screen-finale > .finale-controls {
    display: block;
}

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

.finale-hint {
    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;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.6s var(--ease-silk), visibility 0s linear 0.6s;
}

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

.finale-hint-text {
    margin: 0;
    font: 500 0.74rem/1.3 var(--font-body);
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

.finale-btn {
    min-height: 44px;
    padding: 0.8rem 1.35rem;
    font: 500 0.88rem/1 var(--font-body);
    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(--gilt-line);
    color: var(--accent-warm-bright);
}

/* ───── The closing titles, where the mosaic was ───── */
.finale-titles {
    position: absolute;
    top: 37%;
    left: 6%;
    right: 6%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.15rem;
    text-align: center;
    pointer-events: none;
}

.finale-congrats {
    margin: 0;
    padding: 0.1em 0.35em 0.2em;
    font-family: var(--font-script);
    font-weight: 400;
    font-size: clamp(2.8rem, 14vw, 4.6rem);
    line-height: 1.1;
    background: var(--gilt-gradient);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    clip-path: inset(0 100% 0 0);
    transition: clip-path 2.5s var(--ease-silk);
}

.finale-container.finale-is-congrats .finale-congrats {
    clip-path: inset(0 0 0 0);
}

.finale-plaque {
    margin: 0;
    padding: 0.8rem 1.3rem;
    max-width: 22rem;
    font-family: var(--font-display);
    font-style: italic;
    font-weight: 400;
    font-size: clamp(1rem, 4.4vw, 1.2rem);
    line-height: 1.35;
    color: var(--text-primary);
    background: var(--plaque-fill);
    border: 1px solid var(--gilt-line);
    border-radius: 4px;
    box-shadow: var(--plaque-inset);
    opacity: 0;
    transform: translateY(10px);
    transition: opacity 0.9s var(--ease-silk), transform 1.2s var(--ease-expo-out);
}

.finale-container.finale-is-closing .finale-plaque {
    opacity: 1;
    transform: none;
}

.finale-signoff {
    margin: 0.2rem 0 0;
    font-family: var(--font-script);
    font-size: clamp(1.6rem, 7vw, 2.1rem);
    line-height: 1.2;
    color: var(--accent-warm-bright);
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 1.4s var(--ease-silk), transform 1.6s var(--ease-expo-out);
}

.finale-container.finale-is-signed .finale-signoff {
    opacity: 1;
    transform: none;
}

/* ───── Watch again ───── */
.finale-replay {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(8% + env(safe-area-inset-bottom, 0px));
    display: flex;
    justify-content: center;
    opacity: 0;
    visibility: hidden;
    transition: opacity 1.2s var(--ease-silk), visibility 0s linear 1.2s;
}

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

/* ───── Reduced motion: every step still plays, calmly ─────
   The camera no-ops in ReveelStage and the dissolve takes its calm
   variant; here the photos fade
   in in place, the mosaic cross-fades into the stars, text fades in. */
@media (prefers-reduced-motion: reduce) {
    .finale-tile {
        transform: none;
        border-radius: 1px;
        opacity: 0;
        transition: opacity 1.2s var(--ease-silk) var(--finale-d, 0s);
    }

    .finale-tile::before {
        display: none;
    }

    .finale-mosaic,
    .finale-container.finale-is-dissolved .finale-mosaic {
        transform: none;
        transition: opacity 1.2s var(--ease-silk);
    }

    .finale-ring {
        display: none;
    }

    .finale-congrats {
        clip-path: none;
        opacity: 0;
        transition: opacity 1.4s var(--ease-silk);
    }

    .finale-container.finale-is-congrats .finale-congrats {
        clip-path: none;
        opacity: 1;
    }

    .finale-plaque,
    .finale-signoff,
    .finale-container.finale-is-closing .finale-plaque,
    .finale-container.finale-is-signed .finale-signoff {
        transform: none;
    }
}
