/* ═══════════════════════════════════════════
   best-boyfriend — the shell (build contract §7.2)

   The stage (a lit table in a deep red room), its three set-piece
   canvases, the camera world that holds the gold dust and every beat's
   <section>, the mute control, and the few rules the film spine
   (js/app.js) relies on. Beats style only their own prefixed classes;
   .screen, the stage and the canvases belong here.

   Stacking inside #stage (all position: absolute over one box):
     0  .aurora-bg        the room's light, outside the camera world
     0  #fx-back          the ambient bokeh (film.ambient), under the world
     1  #world            the camera world: three planes of gold dust, then every beat
     2  #finale-controls  hoisted after #world by finale's boot
     2  #fx-light         nameAssemble
     3  the hit flash     (.reveel-flash::after), blooming from --flash-x/-y
     4  #fx-burst         goldBurst, brightest of all
     6  #the-letter-controls  appended to #stage by the-letter's boot
     8  #stage-mute
   ═══════════════════════════════════════════ */

html,
body {
    height: 100%;
    margin: 0;
}

body {
    background: var(--void);
    color: var(--text-primary);
    font-family: var(--font-body);
    overflow: hidden;
    overscroll-behavior: none;
    -webkit-tap-highlight-color: transparent;
    -webkit-font-smoothing: antialiased;
}

button {
    font: inherit;
}

/* ───── The stage: ReveelStage's camera box, one phone screen tall ─────
   The room is the background: an oxblood pool lit at the table (where the
   envelope sits, --env-y), warm gold light on it, a claret haze far above,
   void at the edges. `overflow: clip`, not hidden: a hidden box is still a
   scroll container, and a scrollIntoView once scrolled it 109 px and moved
   the whole film. */
#stage {
    width: 100%;
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
    overflow: clip;
    background:
        radial-gradient(70% 42% at 50% 62%, var(--room-gold-light), transparent 72%),
        radial-gradient(120% 55% at 50% -10%, var(--room-claret-haze), transparent 70%),
        radial-gradient(115% 85% at 50% 58%, var(--room-pool) 0%, var(--room-mid) 46%, var(--room-edge) 100%);
    background-color: var(--room-edge);
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
}

/* The primitive's hit flash, its timing untouched: over the world and the
   name's light, under the burst sparks so they read brightest, blooming
   from wherever the set piece is (--flash-x/-y, set on #stage by the-seal
   at the crack and by the-award at the name) rather than the middle.
   finale.css narrows it further (.finale-flash-soft). */
.reveel-stage.reveel-flash::after {
    z-index: 3;
    background: radial-gradient(circle at var(--flash-x, 50%) var(--flash-y, 50%),
        var(--flash-core) 0%, var(--flash-mid) 40%, transparent 75%);
}

/* ───── The aurora: the room's light, recoloured ─────
   Outside the camera world, so no camera move drags the room's light with
   it. aurora.css hard-codes a purple haze, a pink glow and a navy haze;
   all three gradients are replaced from tokens.css (concept.md): a gold
   key light behind the table (blob-1), a low blush breath at the lower
   right (blob-2), a claret haze far above (blob-3). About 0.25 opacity on
   the ground: the blobs' own 0.35 times this layer's 0.72. Each blob stays
   a static blurred layer that only its drift (a transform) moves. */
.reveel-stage > .aurora-bg {
    position: absolute;
    inset: 0;
    z-index: 0;
    opacity: 0.72;
}
.reveel-stage > .aurora-bg .aurora-blob-1 {
    width: 84vw;
    height: 84vw;
    max-width: 760px;
    max-height: 760px;
    top: calc(60% - min(42vw, 380px));      /* centred on the table (--env-y) */
    left: calc(50% - min(42vw, 380px));
    background: radial-gradient(circle,
        var(--aurora-gold) 0%,
        var(--aurora-deep) 48%,
        transparent 70%);
}
.reveel-stage > .aurora-bg .aurora-blob-2 {
    background: radial-gradient(circle,
        var(--aurora-blush) 0%,
        var(--aurora-deep) 44%,
        transparent 70%);
}
.reveel-stage > .aurora-bg .aurora-blob-3 {
    width: 70vw;
    height: 70vw;
    max-width: 700px;
    max-height: 700px;
    top: 10%;
    background: radial-gradient(circle,
        var(--aurora-claret) 0%,
        var(--aurora-deep) 50%,
        transparent 70%);
}
/* Budget phones (app.js sets .stage-lean when the GPU probe says 2.5D or
   the device reports little memory): each blob is a 120 px blur, so the
   far haze goes. */
.reveel-stage.stage-lean > .aurora-bg .aurora-blob-3 {
    display: none;
}

/* ───── The set-piece canvases: inert, covering the stage ───── */
.fx {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}
.fx-back { z-index: 0; }
.fx-light { z-index: 2; }
.fx-burst { z-index: 4; }

/* ───── The world the camera moves ───── */
.reveel-stage-world {
    z-index: 1;
}

/* Gold dust in a lit room (concept.md): about 28 motes over three depth
   planes, all behind the beats (the sections sit at z 0), so every camera
   move has parallax and a light mote never lands on paper. Each plane is
   pushed back and scaled up by (900 + depth) / 900, so at rest it lies at
   its layout size. In the first frame, and never stopped. */
.stage-plane {
    position: absolute;
    inset: 0;
    pointer-events: none;
}
.stage-plane--far { transform: translateZ(-600px) scale(1.667); }
.stage-plane--mid { transform: translateZ(-300px) scale(1.333); }
.stage-plane--near { transform: translateZ(-120px) scale(1.133); }

/* Far: pin-small. Mid: sparkles.css's own mote. Near: fewer, larger. */
.stage-plane--far .sparkle {
    width: 2px;
    height: 2px;
    box-shadow: 0 0 4px 1px var(--accent-warm-glow-soft);
    animation-duration: 4.2s;
}
.stage-plane--mid .sparkle {
    animation-duration: 3.4s;
}
.stage-plane--near .sparkle {
    width: 5px;
    height: 5px;
    box-shadow: 0 0 10px 2px var(--accent-warm-glow);
    animation-duration: 5.2s;
}

/* Every beat is a full-bleed layer of the world, the stage's size. It
   never clips: several beats' room layers overscan past the section. Only
   the director changes which one is active (a hard cut on the beat
   boundary: each beat's exit composes the frame for the next and its
   entrance cue reveals it, so there is no fade and no entrance stagger). */
.screen {
    position: absolute;
    inset: 0;
    display: none;
    opacity: 0;
}
.screen.active {
    display: flex;
    opacity: 1;
}

/* Entry states are applied instantly (the spine's prepare on goto), never
   animated in. */
.instant,
.instant *,
.instant *::before,
.instant *::after {
    transition: none !important;
}

/* ───── Mute ───── */
.stage-mute {
    position: absolute;
    z-index: 8;
    top: calc(14px + env(safe-area-inset-top, 0px));
    right: 14px;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.5rem 0.8rem;
    font: 500 0.74rem/1 var(--font-body);
    letter-spacing: 0.06em;
    color: var(--text-primary);
    background: var(--glass);
    border: 1px solid var(--glass-border);
    border-radius: 999px;
    cursor: pointer;
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
}
.stage-mute-icon {
    width: 15px;
    height: 15px;
}
.stage-mute .stage-mute-x { display: none; }
.stage-mute[aria-pressed="true"] .stage-mute-x { display: inline; }
.stage-mute[aria-pressed="true"] .stage-mute-waves { display: none; }
.stage-mute[aria-pressed="true"] {
    color: var(--accent-warm-bright);
    border-color: var(--glass-border-hover);
}
.stage-mute:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 3px;
}

/* ───── Reduced motion: the film still plays in full, calmly ─────
   The camera and shake no-op inside ReveelStage; here the room holds
   still: no aurora drift, and the dust stops twinkling and rests at half
   its light. */
@media (prefers-reduced-motion: reduce) {
    .reveel-stage > .aurora-bg .aurora-blob {
        animation: none;
    }
    .stage-plane .sparkle {
        animation: none;
        opacity: 0.5;
        transform: none;
    }
}
