/* ============================================================================
 * slides/uc1-alice/style.css
 * ----------------------------------------------------------------------------
 * Module-specific overrides on top of the zen components. Responsibilities:
 *   1. Size the #phone and #browser per beat (default + is-small / is-full).
 *   2. Style the Entra ID card (4-square logo + brand + tagline).
 *   3. Visibility helper (.is-hidden) for elements that pop in per beat.
 *   4. Pulse animation on the Entra login CTA (beat 2).
 *   5. Explainer label (below-scene narration chip).
 *
 * No hardcoded absolute positions here — layout is driven by data-at +
 * anchor="center" on each element. This file only tunes dimensions and
 * local visual state.
 * ========================================================================== */

/* ──────────────────────────────────────────────────────────────────────────
 * 1. Phone sizing — hero center on beat 1, shrunk to corner on beats 2+
 * ────────────────────────────────────────────────────────────────────────── */

/* Beat 1 — hero size when phone is the focal point.
 * Overrides the component's default (360×720) via the public tokens.
 * Kept < 900px tall so at scale 1 (pre-camera) the phone doesn't eat the
 * space where the explainer lives (88% of scene). */
#phone {
    --pm-w: 420px;
    --pm-h: 840px;
}

/* Beats 2–3 — shrunk & parked in the corner as context. */
#phone.is-small {
    --pm-w: 240px;
    --pm-h: 480px;
}

/* ──────────────────────────────────────────────────────────────────────────
 * 2. Browser sizing — medium for the login (beat 2), full for resources (3)
 * ────────────────────────────────────────────────────────────────────────── */

/* Default = beat 2 · login view — leave room on the right for the Entra card. */
#browser {
    --bm-w: 1040px;
    --bm-h: 620px;
}

/* Beat 3 — authenticated resources view takes over the frame. */
#browser.is-full {
    --bm-w: 1600px;
    --bm-h: 900px;
}

/* ──────────────────────────────────────────────────────────────────────────
 * 3. Visibility helper — pop-in with a soft fade
 * ────────────────────────────────────────────────────────────────────────── */

.is-hidden {
    opacity: 0 !important;
    pointer-events: none;
}

/* ──────────────────────────────────────────────────────────────────────────
 * 3.1 Scene layers — physical world placement for camera-driven scrolling.
 *
 *     Two scene groups coexist in the world, spatially separated rather than
 *     stacked :
 *       · `.main-scene` + `.intro-scene` occupy the natural viewport
 *         (y = 0..1080 in world coords, via `inset: 0`).
 *       · `#auth-flow-root` sits DIRECTLY BELOW the main viewport
 *         (y = 1080..2160) — see the override further down.
 *
 *     With this layout the "scroll" between main and auth is a genuine
 *     camera pan on `.z-world` (translate up to bring auth into view) — no
 *     hide/show illusion, no per-scene transform. Scene layers stay visible
 *     in the DOM at all times; `.z-scene { overflow: hidden }` naturally
 *     clips whichever half is off-camera.
 *
 *     CRITICAL (still): scene-layer must be `position: absolute`. Its
 *     absolutely-positioned descendants (anything with `data-at`) resolve
 *     their `%` coords against the nearest positioned ancestor — scene-layer
 *     itself. The `inset: 0` / sizing below gives them a deterministic
 *     1920×1080 containing block so positioning stays identical whether the
 *     layer is on- or off-camera.
 * ────────────────────────────────────────────────────────────────────────── */
.scene-layer {
    position: absolute;
    inset: 0;
    transition: opacity 0.55s var(--ease-natural);
}

/* Auth-flow scene sits one viewport BELOW main in world coords (y = 100%..200%).
 * Camera pans up to reveal it — real translation of .z-world, not a staged
 * layer swap. `width/height: 100%` forces a 1920×1080 containing block for
 * the `data-at` children so their %-coords resolve the same way as they do
 * inside main-scene. `inset: 0` from the base `.scene-layer` rule is
 * overridden by `top: 100%; left: 0; right: auto; bottom: auto;` here. */
#auth-flow-root {
    top: 100%;
    left: 0;
    right: auto;
    bottom: auto;
    width: 100%;
    height: 100%;
}

/* Outro-pillars scene sits to the RIGHT of main (x = 100%..200%, y = 0..100%).
 * Camera pans horizontally from the audit view (beat 22, in main) to the
 * 4-punchline summary (beat 23). Keeping it on the same vertical band as
 * main avoids extending the canvas — beat 23 has no flow arrows anyway. */
#outro-pillars-root {
    top: 0;
    left: 100%;
    right: auto;
    bottom: auto;
    width: 100%;
    height: 100%;
}

/* Canvas must extend over BOTH scenes so flow arrows rendered in world
 * coordinates (up to y=200%) actually have paintable pixels. Without this,
 * drawLine happily computes path points at y=1577px but the underlying
 * canvas buffer is only 1080px tall and the stroke is silently clipped.
 *
 * See engine.resizeCanvas() override in index.html — we must also pin
 * cW/cH to world dims (1920×1080) so %→px math still targets world space,
 * not the now-oversized canvas. */
#cv {
    height: 200%;
}

/* ──────────────────────────────────────────────────────────────────────────
 * 3.2 Fluidity bump — phone + browser use the zen component's `transition:
 *     all 0.6s` as a baseline, but for beat transitions 1→2 and 8→10 the
 *     user-perceived flip of size+position feels almost instant. Bumping the
 *     duration to 0.85s (same easing) on the specific instances used here
 *     keeps every repositioning feeling intentional rather than snapped.
 * ────────────────────────────────────────────────────────────────────────── */
#phone, #browser, #bob-phone {
    transition-duration: 0.85s;
}

/* ──────────────────────────────────────────────────────────────────────────
 * 4. Entra ID card — 4-square logo + brand + tagline
 *    Stylized evocation of the Microsoft mark, not a reproduction.
 * ────────────────────────────────────────────────────────────────────────── */

.entra-card {
    width: 380px;
    height: 320px;
    padding: 32px 28px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 18px;
    text-align: center;
    background: rgba(13, 17, 23, 0.92);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 12px 42px rgba(0, 0, 0, 0.5);
}

.entra-card__logo {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr 1fr;
    gap: 6px;
    width: 96px;
    height: 96px;
}

.entra-card__logo .sq {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 2px;
}

.entra-card h3 {
    margin: 0;
    font-size: 1.35rem;
    font-weight: 600;
    color: var(--white);
    letter-spacing: 0.2px;
}

.entra-card .sub {
    margin: 0;
    font-size: 0.85rem;
    color: var(--muted, #8b949e);
    line-height: 1.35;
}

/* When the card is active, give it the violet glow (IdP role). */
.entra-card.a-active {
    border-color: var(--violet);
    box-shadow: 0 0 0 1px var(--violet),
                0 14px 48px rgba(167, 139, 250, 0.22);
}

/* ──────────────────────────────────────────────────────────────────────────
 * 5. Login CTA pulse (beat 2 · "click on Sign in with Entra ID")
 * ────────────────────────────────────────────────────────────────────────── */

@keyframes alice-cta-pulse {
    0%, 100% {
        box-shadow: 0 0 0 0 rgba(167, 139, 250, 0.55);
        transform: scale(1);
    }
    50% {
        box-shadow: 0 0 0 14px rgba(167, 139, 250, 0);
        transform: scale(1.035);
    }
}

.zen-browser-mock__login__cta.is-pulsing {
    animation: alice-cta-pulse 1.4s ease-in-out infinite;
    border-color: var(--violet);
    color: var(--violet);
}

/* ──────────────────────────────────────────────────────────────────────────
 * 5.1 Auth-success view — shown briefly inside the browser between beats 2
 *     and 3. Visual echo of the login view but in a "signed in" state: the
 *     Entra claim has landed, Teleport issued a cert, the catalog is about
 *     to open. A short-lived scene meant to give the eye a moment of closure
 *     before the camera pans down to reveal the architecture underneath.
 * ────────────────────────────────────────────────────────────────────────── */

.zen-browser-mock__auth-success {
    width: 100%; height: 100%;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 18px;
    padding: 40px 48px;
}

.zen-browser-mock__auth-success__avatar {
    width: 72px; height: 72px;
    border-radius: 50%;
    display: grid; place-items: center;
    font-size: 1.8rem; font-weight: 700;
    color: var(--white);
    background: linear-gradient(135deg, #A78BFA, #22D3EE);
    box-shadow: 0 10px 32px rgba(167, 139, 250, 0.35);
}

.zen-browser-mock__auth-success__title {
    font-size: 1.45rem; font-weight: 600;
    color: var(--white);
    letter-spacing: 0.2px;
}

.zen-browser-mock__auth-success__user {
    font-size: 0.95rem;
    color: var(--gray);
    font-family: 'Courier New', monospace;
}

.zen-browser-mock__auth-success__chips {
    display: flex; gap: 10px; margin-top: 4px;
}

.zen-browser-mock__auth-success__chip {
    padding: 5px 12px;
    font-size: 0.72rem; font-weight: 700;
    letter-spacing: 0.8px;
    text-transform: uppercase;
    border-radius: 999px;
    border: 1px solid rgba(52, 211, 153, 0.4);
    background: rgba(52, 211, 153, 0.1);
    color: #34D399;
}
.zen-browser-mock__auth-success__chip--cert {
    border-color: rgba(245, 158, 11, 0.45);
    background: rgba(245, 158, 11, 0.1);
    color: #F59E0B;
}

.zen-browser-mock__auth-success__redirect {
    margin-top: 10px;
    display: flex; align-items: center; gap: 10px;
    font-size: 0.88rem;
    color: var(--gray);
}
.zen-browser-mock__auth-success__redirect__spinner {
    width: 14px; height: 14px;
    border: 2px solid rgba(156, 163, 175, 0.25);
    border-top-color: var(--cyan);
    border-radius: 50%;
    animation: alice-spin 0.9s linear infinite;
}
@keyframes alice-spin {
    to { transform: rotate(360deg); }
}

/* ──────────────────────────────────────────────────────────────────────────
 * 6. Explainer label — floating narration bar, fixed at bottom of viewport
 *
 *    #lbl-beat lives OUTSIDE .z-world (see index.html) so the camera
 *    translate+scale never touches it. It is chrome, not scene content :
 *    always at the same screen position regardless of what the camera is
 *    framing. Hidden by default (via .zen-label no-show class) and revealed
 *    per beat via zen.state.show(lblBeat).
 * ────────────────────────────────────────────────────────────────────────── */

.zen-label.explainer {
    padding: 12px 26px;
    font-size: 1.05rem;
    font-weight: 500;
    letter-spacing: 0.2px;
    max-width: 1100px;
    white-space: normal;
    text-align: center;
    line-height: 1.35;
    background: rgba(13, 17, 23, 0.92);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}

.zen-label.beat-bar {
    position: fixed;
    left: 50%;
    bottom: 36px;
    transform: translateX(-50%);
    z-index: 30;
    pointer-events: none;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}

/* ──────────────────────────────────────────────────────────────────────────
 * 7. Intro card — beat 0 · static title card
 *    Inspired by slides/ep04's .intro-overlay but scoped to the zen scene
 *    (not a full-page overlay). Centered in world coordinates so the
 *    existing camera.reset() at beat 0 lands it naturally.
 * ────────────────────────────────────────────────────────────────────────── */

.intro-card {
    max-width: 980px;
    padding: 64px 80px;
    display: flex; flex-direction: column; align-items: center;
    gap: 22px;
    text-align: center;
    background: rgba(13, 17, 23, 0.94);
    border: 1px solid rgba(167, 139, 250, 0.22);
    border-radius: 20px;
    box-shadow: 0 24px 90px rgba(0, 0, 0, 0.55),
                0 0 0 1px rgba(167, 139, 250, 0.08);
}

.intro-card__eyebrow {
    text-transform: uppercase;
    letter-spacing: 3px;
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--violet);
    opacity: 0.85;
}

.intro-card__title {
    margin: 0;
    font-size: 4.2rem;
    font-weight: 800;
    letter-spacing: -1.8px;
    line-height: 1.05;
    background: linear-gradient(135deg, var(--white) 0%, var(--violet) 60%, var(--cyan) 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
}

.intro-card__sub {
    font-size: 1.35rem;
    font-weight: 400;
    line-height: 1.45;
    color: var(--gray, #8b949e);
    max-width: 760px;
}

.intro-card__rule {
    width: 260px;
    height: 2px;
    background: linear-gradient(90deg,
        transparent,
        rgba(167, 139, 250, 0.7),
        transparent);
    margin: 6px 0 2px;
}

.intro-card__hint {
    margin-top: 6px;
    padding: 10px 24px;
    border: 1px solid rgba(167, 139, 250, 0.4);
    border-radius: 999px;
    color: var(--violet);
    font-size: 0.95rem;
    font-weight: 500;
    letter-spacing: 0.6px;
    background: rgba(167, 139, 250, 0.06);
    animation: alice-intro-pulse 2.2s ease-in-out infinite;
}

@keyframes alice-intro-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(167, 139, 250, 0.35); }
    50%      { box-shadow: 0 0 0 10px rgba(167, 139, 250, 0); }
}

/* ──────────────────────────────────────────────────────────────────────────
 * 8. Access Request aside (beats 9-10)
 *    The component CSS handles layout; this section adds beat-specific state
 *    driven by [data-state] on the <aside> root. On submit, the form + table
 *    + actions fade out and a status banner ("PENDING" → "APPROVED") takes
 *    over the bottom of the aside.
 * ────────────────────────────────────────────────────────────────────────── */

/* Anchor for the status banner that slides in after submit. */
.zen-browser-mock__aside {
    position: relative;
}

/* Form chrome is visually muted once the request is in flight. Pointer
 * events off for visual clarity — we're past interactivity. */
.zen-browser-mock__aside[data-state="pending"]  .zen-browser-mock__form-row,
.zen-browser-mock__aside[data-state="pending"]  .zen-browser-mock__selected-table,
.zen-browser-mock__aside[data-state="pending"]  .zen-browser-mock__aside-actions,
.zen-browser-mock__aside[data-state="approved"] .zen-browser-mock__form-row,
.zen-browser-mock__aside[data-state="approved"] .zen-browser-mock__selected-table,
.zen-browser-mock__aside[data-state="approved"] .zen-browser-mock__aside-actions {
    opacity: 0.3;
    filter: saturate(0.3);
    pointer-events: none;
    transition: opacity 0.3s ease, filter 0.3s ease;
}

/* Status banner — hidden on form state, revealed on pending/approved. */
.aside-status {
    display: none;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 14px 18px;
    margin-top: 6px;
    border-radius: 10px;
    text-align: center;
}
.zen-browser-mock__aside[data-state="pending"]  .aside-status,
.zen-browser-mock__aside[data-state="approved"] .aside-status {
    display: flex;
    animation: alice-status-in 0.35s ease-out;
}
@keyframes alice-status-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
}

/* Pending — amber, evokes "waiting, attention pending". */
.zen-browser-mock__aside[data-state="pending"] .aside-status {
    background: linear-gradient(180deg,
        rgba(245, 158, 11, 0.18),
        rgba(245, 158, 11, 0.06));
    border: 1px solid rgba(245, 158, 11, 0.55);
    box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.12),
                0 10px 28px rgba(245, 158, 11, 0.1);
}
.zen-browser-mock__aside[data-state="pending"] .aside-status__badge {
    color: #F59E0B;
}

/* Approved — green, same glow recipe as phone-mock's approved stamp. */
.zen-browser-mock__aside[data-state="approved"] .aside-status {
    background: linear-gradient(180deg,
        rgba(0, 230, 118, 0.18),
        rgba(0, 230, 118, 0.06));
    border: 1px solid rgba(0, 230, 118, 0.55);
    box-shadow: 0 0 0 1px rgba(0, 230, 118, 0.12),
                0 10px 28px rgba(0, 230, 118, 0.12);
}
.zen-browser-mock__aside[data-state="approved"] .aside-status__badge {
    color: #34D399;
}

.aside-status__badge {
    font-size: 0.95rem;
    font-weight: 800;
    letter-spacing: 2px;
}
.aside-status__text {
    font-size: 0.82rem;
    font-weight: 500;
    color: rgba(255, 255, 255, 0.7);
    letter-spacing: 0.2px;
}

/* ──────────────────────────────────────────────────────────────────────────
 * 9. Browser variant — `.is-aside-open` (beats 9-10)
 *    Used whenever the Access Request aside is visible. Keeps the aside
 *    column present. Two sizes :
 *      — beat 9 (`.is-full.is-aside-open`)  = 1700×920, hero framing.
 *      — beat 10 (`.is-aside-open` solo)     = 1340×820, smaller so the
 *         camera can frame browser + Bob's phone side-by-side.
 * ────────────────────────────────────────────────────────────────────────── */

#browser.is-aside-open {
    --bm-w: 1340px;
    --bm-h: 820px;
}
#browser.is-full.is-aside-open {
    --bm-w: 1700px;
    --bm-h: 920px;
}

/* Pin the grid columns explicitly when the aside is visible. The default
 * track is `auto` for aside (sized to content), which can grow past
 * `--bm-aside-w` when a single form-row / label / textarea produces a long
 * intrinsic max-content — starving `main`'s `1fr` and collapsing the resource
 * cards to the min-content edge. `minmax(0, 1fr)` on main also defeats the
 * default `min-content` floor that otherwise prevents the 1fr track from
 * shrinking. */
#browser.is-aside-open .zen-browser-mock__layout {
    grid-template-columns: var(--bm-sidebar-w) minmax(0, 1fr) var(--bm-aside-w);
}

/* Defensive floor on the card grid: ensures the 10 resource cards have a
 * stable minimum row so they never collapse to near-zero height, and
 * anchors the grid to the top so the auto track doesn't stretch.
 *
 * RESPONSIVE COLUMN COUNT — the overridden defaults below replace the
 * component's `repeat(3, 1fr)` with :
 *   · 5 cols × 2 rows when the aside is hidden (`--no-aside`, beat 9
 *     overview) — every card visible, web-03 + db-main adjacent.
 *   · 4 cols × 3 rows when the aside is open  (`.is-aside-open`, beats
 *     10-12) — narrower main area means 4 cols fit; the last row has
 *     2 trailing empty cells, fine.
 *
 * The grid container is made `position: relative` so the absolutely-
 * positioned cursor overlay (`.zen-cursor-layer`) parks against it. */
#browser .zen-browser-mock__card-grid {
    grid-auto-rows: min-content;
    align-content: start;
    position: relative;
}
#browser.zen-browser-mock--no-aside .zen-browser-mock__card-grid {
    grid-template-columns: repeat(5, 1fr);
}
#browser.is-aside-open .zen-browser-mock__card-grid {
    grid-template-columns: repeat(4, 1fr);
}
#browser .zen-browser-mock__resource-card {
    min-height: 88px;
}

/* Locked resource-card · the default state for the 7 prod resources Alice
 * doesn't yet have access to. Whole card dimmed to ~55% so the 3 Connect
 * cards (bastion/grafana-staging/lab) stand out as clickable islands in a
 * sea of locked tiles. Hover lift is disabled here since the kiosk loop has
 * no real cursor interaction — the animated cursor is the ONLY cursor.
 * Mutated to `.is-granted` for web-03 + db-main on new beat 14 (the former
 * beat 11) to flip them to Connect green. */
.zen-browser-mock__resource-card.is-locked {
    opacity: 0.55;
}
.zen-browser-mock__resource-card.is-locked:hover {
    border-color: var(--bm-border);
    box-shadow: none;
}
/* The `.is-granted` flip overrides `.is-locked` dimming — once the role is
 * in the cert, the card is "live" and must look like it. */
.zen-browser-mock__resource-card.is-granted {
    opacity: 1;
}

/* Aside selected-table · dynamic row reveal. Each of the 6 cells that make
 * up the 2 data rows (web-03 + db-main) carries `.aside-row-cell` and an
 * `.is-row-web` / `.is-row-db` selector. Default = invisible (opacity 0)
 * while the grid still reserves the row height (padding on the cells).
 * Beat 10's JS adds `.is-revealed` to both cells of a row at the exact
 * moment the matching checkbox ticks — the two rows fade in as the aside
 * slides over from the right, so the user's eye connects the tick ↔ row
 * without a second animation. */
.zen-browser-mock__selected-table .aside-row-cell {
    opacity: 0;
    transition: opacity 0.35s ease;
}
.zen-browser-mock__selected-table .aside-row-cell.is-revealed {
    opacity: 1;
}

/* Cursor overlay · absolute child of `.zen-browser-mock__main` (placed
 * AFTER the card-grid in the DOM so it stacks above without z-index
 * gymnastics). Movement is driven by inline `transform = translate(Xpx,
 * Ypx)` computed at beat-10 runtime from `getBoundingClientRect()` of the
 * target checkbox vs the cursor-layer's own bounding rect — works through
 * camera zoom because both rects are measured in the same post-transform
 * pixel space. Transition is cubic-bezier 900ms — feels like a natural
 * hand movement, not a teleport. `pointer-events:none` everywhere so the
 * cursor never eats hovers on the cards below. */
.zen-cursor-layer {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 5;
}
.zen-cursor {
    position: absolute;
    top: 0; left: 0;
    transform: translate(-100px, -100px);
    opacity: 0;
    fill: #FFFFFF;
    stroke: #0A0E14;
    stroke-width: 1.6;
    stroke-linejoin: round;
    filter: drop-shadow(0 2px 4px rgba(0,0,0,0.45));
    transition:
        transform 900ms cubic-bezier(0.45, 0.05, 0.2, 1),
        opacity 250ms ease;
    will-change: transform, opacity;
}
.zen-cursor.is-visible {
    opacity: 1;
}
.zen-cursor.is-click {
    animation: alice-cursor-click 320ms ease;
}
@keyframes alice-cursor-click {
    0%, 100% { transform-origin: 2px 2px; }
    30%      { filter: drop-shadow(0 2px 4px rgba(0,0,0,0.45)) brightness(1.2); }
}
/* Click-pulse ring · spawned inline next to the checkbox when the cursor
 * "clicks". Appears briefly at the cursor tip — soft violet ripple,
 * consistent with `.is-pulse-btn` palette. */
.zen-click-pulse {
    position: absolute;
    width: 28px; height: 28px;
    border-radius: 50%;
    border: 2px solid rgba(167, 139, 250, 0.8);
    transform: translate(-14px, -14px) scale(0.4);
    opacity: 0.9;
    pointer-events: none;
    animation: alice-click-pulse 700ms ease-out forwards;
    z-index: 6;
}
@keyframes alice-click-pulse {
    to {
        transform: translate(-14px, -14px) scale(1.8);
        opacity: 0;
    }
}

/* Form-row highlight · used at beat 12 (form-fill) to softly ring whichever
 * aside row is being "filled in" in sequence (role, then duration, then
 * reason). Signal is subtle — a thin violet outline — because the primary
 * attention is on the text being typed, not the container. */
.zen-browser-mock__aside .zen-browser-mock__form-row.is-filling,
.zen-browser-mock__aside .zen-browser-mock__form-row.is-filled {
    transition: box-shadow 0.3s ease, background 0.3s ease;
}
.zen-browser-mock__aside .zen-browser-mock__form-row.is-filling {
    box-shadow: 0 0 0 1px rgba(167, 139, 250, 0.55);
    background: rgba(167, 139, 250, 0.06);
    border-radius: 6px;
}

/* ──────────────────────────────────────────────────────────────────────────
 * 10. Bob's phone — beat 10 · Slack approver.
 *     Slightly smaller than hero Alice phone (beat 1) to stay secondary
 *     to Alice's waiting browser on the left.
 * ────────────────────────────────────────────────────────────────────────── */

#bob-phone {
    --pm-w: 360px;
    --pm-h: 720px;
}

/* ──────────────────────────────────────────────────────────────────────────
 * 11. Generic button pulse — attention-grab for "click this now" buttons
 *     (Request Access at beat 9, Submit Request at beat 9, Approve at
 *     beat 10). Violet palette for cohesion with the rest of the module;
 *     buttons keep their native color, the glow is additive.
 * ────────────────────────────────────────────────────────────────────────── */

.is-pulse-btn {
    animation: alice-btn-pulse 1.1s ease-in-out infinite;
    position: relative;
    z-index: 2;
}

@keyframes alice-btn-pulse {
    0%, 100% {
        box-shadow: 0 0 0 0 rgba(167, 139, 250, 0.6);
        transform: scale(1);
    }
    50% {
        box-shadow: 0 0 0 14px rgba(167, 139, 250, 0);
        transform: scale(1.04);
    }
}

/* ──────────────────────────────────────────────────────────────────────────
 * 12. Beat 11 · VNet terminal + feature card
 *     Terminal is sized via data-min-w/h on the element. Two inline color
 *     classes (hl-green / hl-purple) are used inside raw-mode terminal
 *     lines to emphasise the server hostname and the Teleport tenant
 *     domain in the `ssh` command.
 *
 *     The feature card replaces the older bullet side-panel : it is
 *     deliberately compact and pitches VNet in one breath ("just ssh —
 *     no client, no desktop app"). Entrance is a single fade+lift via
 *     `.is-revealed`.
 * ────────────────────────────────────────────────────────────────────────── */

.vnet-term {
    /* Taller than the auth-flow terminal — with the added hostname/uptime
     * sanity command we now show ~10 lines (cmd1 + 2 out, cmd2 + 2 out,
     * cmd3 + 2 out, final prompt). */
    min-width: 780px;
    min-height: 400px;
}

/* Terminal inline highlights — applied inside raw-mode lines at beat 11.
 * Scoped to .vnet-term so we don't accidentally restyle the auth-flow
 * terminal that sits in the same document. */
.vnet-term .hl-green  { color: #34D399; font-weight: 600; }
.vnet-term .hl-purple { color: #A78BFA; font-weight: 600; }

/* ─── Feature card ──────────────────────────────────────────────────────── */

.vnet-card {
    /* Dark glass card — same visual family as the SCENE_BG so it feels
     * native to the module, but lifted by a subtle purple rim glow that
     * picks up the Teleport brand color used in the terminal. */
    padding: 26px 28px 24px;
    border-radius: 14px;
    background:
        linear-gradient(180deg,
            rgba(28, 20, 54, 0.85),
            rgba(14, 10, 30, 0.9));
    border: 1px solid rgba(167, 139, 250, 0.35);
    box-shadow:
        0 0 0 1px rgba(167, 139, 250, 0.12),
        0 24px 60px rgba(0, 0, 0, 0.5),
        0 0 80px -20px rgba(167, 139, 250, 0.35) inset;
    color: rgba(235, 230, 250, 0.92);

    display: flex;
    flex-direction: column;
    gap: 14px;

    /* Entrance animation — start hidden+offset, drop in on `.is-revealed`. */
    opacity: 0;
    transform: translateY(10px);
    transition: opacity 0.5s ease, transform 0.5s ease;
}
.vnet-card.is-revealed {
    opacity: 1;
    transform: translateY(0);
}

/* Head row : icon chip + eyebrow label. The icon sits in a small square
 * with the same purple rim for visual consistency with the card shell. */
.vnet-card__head {
    display: flex;
    align-items: center;
    gap: 12px;
}
.vnet-card__icon {
    width: 38px;
    height: 38px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 9px;
    background: linear-gradient(180deg,
        rgba(167, 139, 250, 0.22),
        rgba(124, 58, 237, 0.12));
    border: 1px solid rgba(167, 139, 250, 0.45);
    color: #C4B5FD;
}
.vnet-card__eyebrow {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 3px;
    color: #A78BFA;
    text-transform: uppercase;
}

/* Punchy headline. `<code class="zen-inline">ssh</code>` inherits the
 * terminal-ish monospace token, so the line reads "Juste `ssh`." with
 * the command rendered like code. */
.vnet-card__title {
    margin: 0;
    font-size: 30px;
    font-weight: 800;
    line-height: 1.15;
    color: #fff;
    letter-spacing: -0.3px;
}
.vnet-card__title code.zen-inline {
    /* Slightly larger than inline-default so the command doesn't look
     * shrunken next to the big headline. */
    font-size: 0.82em;
    padding: 2px 8px;
    vertical-align: 2px;
}

/* Body — one paragraph, reads in one breath. */
.vnet-card__body {
    margin: 0;
    font-size: 14.5px;
    line-height: 1.6;
    color: rgba(215, 210, 235, 0.78);
}

/* OS-scope footer — a thin divider + centered chip line saying "works
 * everywhere you might run ssh from". */
.vnet-card__scope {
    margin-top: 4px;
    padding-top: 12px;
    border-top: 1px solid rgba(167, 139, 250, 0.18);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 1.5px;
    color: rgba(196, 181, 253, 0.82);
    text-transform: uppercase;
    text-align: center;
}

/* ──────────────────────────────────────────────────────────────────────────
 * 13. Beats 12-13 · Transition cards (GitOps + Machine ID)
 *     These are chapter-break cards that fade in over the whole stage
 *     after beat 11, carrying the narration while the active props
 *     (terminal, feature card) are hidden. Shared `.transition-card`
 *     shell — modifier classes `.gitops-card` + `.machineid-intro`
 *     pick the accent color (green for GitOps rollout, amber/purple
 *     for Machine ID) without duplicating the glass-card base.
 *
 *     The visual family matches the VNet feature card (same purple
 *     shadow stack, same is-revealed entrance) so the transitions
 *     feel native — but cards sit center-stage instead of siding
 *     the terminal, and carry a chip/pill row instead of an OS-scope
 *     footer.
 * ────────────────────────────────────────────────────────────────────────── */

.transition-card {
    padding: 34px 40px 30px;
    border-radius: 16px;
    /* Dark glass — same gradient family as the VNet card so the cards
     * feel native to the scenario. Accent color is pushed through the
     * border + rim-glow only, so one shell covers both beats. */
    background:
        linear-gradient(180deg,
            rgba(28, 20, 54, 0.88),
            rgba(14, 10, 30, 0.92));
    border: 1px solid rgba(167, 139, 250, 0.35);
    box-shadow:
        0 0 0 1px rgba(167, 139, 250, 0.12),
        0 30px 80px rgba(0, 0, 0, 0.55),
        0 0 100px -20px rgba(167, 139, 250, 0.35) inset;
    color: rgba(235, 230, 250, 0.92);

    display: flex;
    flex-direction: column;
    gap: 18px;

    /* Entrance — same motion as .vnet-card. */
    opacity: 0;
    transform: translateY(10px);
    transition: opacity 0.55s ease, transform 0.55s ease;
}
.transition-card.is-revealed {
    opacity: 1;
    transform: translateY(0);
}

/* ─── Header row ───────────────────────────────────────────────────────── */
.transition-card__head {
    display: flex;
    align-items: center;
    gap: 14px;
}
.transition-card__icon {
    width: 42px;
    height: 42px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 10px;
    /* Default (purple) rim — overridden by the `--gitops` / `--machineid`
     * modifiers below to tint the accent per beat. */
    background: linear-gradient(180deg,
        rgba(167, 139, 250, 0.22),
        rgba(124, 58, 237, 0.12));
    border: 1px solid rgba(167, 139, 250, 0.45);
    color: #C4B5FD;
}
/* GitOps accent — green (matches the "active/healthy" hl-green used in
 * the systemctl output and hints at a successful redeploy). */
.transition-card__icon--gitops {
    background: linear-gradient(180deg,
        rgba(52, 211, 153, 0.22),
        rgba(16, 185, 129, 0.12));
    border: 1px solid rgba(52, 211, 153, 0.45);
    color: #6EE7B7;
}
/* Machine ID accent — amber/peach, reads as "robot / non-human" and
 * keeps visual distance from the human-authentication purple. */
.transition-card__icon--machineid {
    background: linear-gradient(180deg,
        rgba(251, 191, 36, 0.22),
        rgba(245, 158, 11, 0.12));
    border: 1px solid rgba(251, 191, 36, 0.45);
    color: #FCD34D;
}

.transition-card__eyebrow {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 3px;
    color: #A78BFA;
    text-transform: uppercase;
}
.gitops-card .transition-card__eyebrow    { color: #6EE7B7; }
.machineid-intro .transition-card__eyebrow { color: #FCD34D; }

/* ─── Headline + body copy ─────────────────────────────────────────────── */
.transition-card__title {
    margin: 0;
    font-size: 34px;
    font-weight: 800;
    line-height: 1.15;
    color: #fff;
    letter-spacing: -0.4px;
}
.transition-card__title code.zen-inline {
    font-size: 0.82em;
    padding: 2px 8px;
    vertical-align: 2px;
}
.transition-card__body {
    margin: 0;
    font-size: 16px;
    line-height: 1.6;
    color: rgba(215, 210, 235, 0.82);
    max-width: 68ch;
}
.transition-card__body b { color: rgba(245, 242, 255, 0.96); font-weight: 700; }

/* ─── Chip/pill rows ───────────────────────────────────────────────────── */
/* GitOps flow row — three chips + arrows, reading as a sequence. */
.transition-card__flow {
    margin-top: 4px;
    padding-top: 14px;
    border-top: 1px solid rgba(167, 139, 250, 0.18);
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}
.transition-card__chip {
    padding: 6px 12px;
    border-radius: 999px;
    font-size: 12.5px;
    font-weight: 600;
    letter-spacing: 0.3px;
    background: rgba(52, 211, 153, 0.08);
    border: 1px solid rgba(52, 211, 153, 0.18);
    color: rgba(167, 243, 208, 0.45);
    white-space: nowrap;
    /* Start dim — lit up sequentially via `.is-lit` during beat 12.
     * Transition covers both the opacity bump and the background
     * swap so the walk reads as continuous. */
    transition:
        opacity 0.35s ease,
        background 0.35s ease,
        border-color 0.35s ease,
        color 0.35s ease,
        transform 0.35s ease;
}
.transition-card__chip.is-lit {
    background: rgba(52, 211, 153, 0.18);
    border-color: rgba(52, 211, 153, 0.5);
    color: #A7F3D0;
    box-shadow: 0 0 0 4px rgba(52, 211, 153, 0.10);
    animation: gitopsChipPulse 0.55s ease;
}
@keyframes gitopsChipPulse {
    0%   { transform: scale(0.94); }
    55%  { transform: scale(1.06); }
    100% { transform: scale(1); }
}
.transition-card__arrow {
    color: rgba(167, 139, 250, 0.22);
    font-weight: 700;
    font-size: 14px;
    transition: color 0.35s ease, transform 0.35s ease;
}
.transition-card__arrow.is-lit {
    color: rgba(110, 231, 183, 0.85);
    transform: translateX(2px);
}

/* Machine ID pills — flat list of co-equal properties (no arrows). */
.transition-card__pills {
    margin-top: 4px;
    padding-top: 14px;
    border-top: 1px solid rgba(167, 139, 250, 0.18);
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}
.transition-card__pill {
    padding: 6px 12px;
    border-radius: 999px;
    font-size: 12.5px;
    font-weight: 600;
    letter-spacing: 0.3px;
    background: rgba(251, 191, 36, 0.12);
    border: 1px solid rgba(251, 191, 36, 0.35);
    color: #FDE68A;
    white-space: nowrap;
}

/* ──────────────────────────────────────────────────────────────────────────
 * 14. Beat 13 · Machine ID scene — terminal + card + CI/CD pipeline
 *     Three props sit on stage at beat 13 :
 *       · `.machineid-term`   — journalctl readout on web-03 (top-left)
 *       · `.machineid-intro`  — `.transition-card` (top-right)
 *       · `.mi-pipe`          — horizontal GitLab CI/CD storyboard (bottom)
 *
 *     The card styles live in §13. This section covers the terminal's
 *     `hl-red` highlighting and the whole pipeline — boxes, runner log,
 *     arrows, teleport brand-styled proxy, fork SVG to the two targets,
 *     and the audit tap+pane sitting in parallel on the far right.
 *
 *     Every box starts dim (opacity .35) and gets promoted to full
 *     brightness via `.is-lit` — the handler triggers these flags in a
 *     left-to-right sequence so the viewer sees the pipeline "boot up".
 * ────────────────────────────────────────────────────────────────────────── */

/* ── Terminal · inline highlights ─────────────────────────────────────── */
/* Shared palette with the VNet terminal — same hl-* convention inside
 * raw-mode lines. hl-red for deleted lines / error tokens, hl-green for
 * added lines / success tokens, hl-purple for Teleport-brand accents
 * (pipeline number, proxy mention). `muted` and `ok` come from zen's
 * base `.zen-term` styles so they work out of the box. */
.machineid-term .hl-red {
    color: #FCA5A5;
    font-weight: 700;
    letter-spacing: 0.2px;
}
.machineid-term .hl-green  { color: #6EE7B7; font-weight: 700; letter-spacing: 0.2px; }
.machineid-term .hl-purple { color: #A78BFA; font-weight: 600; }

/* ── Pipeline row shell ──────────────────────────────────────────────── */
.mi-pipe {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 0;
    font-family: var(--font-sans, "Inter", system-ui, sans-serif);
}
.mi-pipe.is-hidden {
    opacity: 0;
    transform: translateY(18px);
    pointer-events: none;
}
.mi-pipe.is-revealed {
    opacity: 1;
    transform: translateY(0);
    transition: opacity 0.55s ease, transform 0.55s ease;
}

/* ── Step (box) base ─────────────────────────────────────────────────── */
/* Shared chrome for every pipeline box. Dim until `.is-lit`. Modifiers
 * further below swap accent color + layout for `--job` (taller with the
 * runner-log), `--teleport` (brand purple + logo), `--audit` (far-right
 * parallel pane with dashed border), `--target` (smaller stacked). */
.mi-pipe__step {
    min-width: 160px;
    max-width: 176px;
    padding: 14px 14px 12px;
    border-radius: 14px;
    background:
        linear-gradient(180deg,
            rgba(28, 20, 54, 0.88),
            rgba(14, 10, 30, 0.92));
    border: 1px solid rgba(167, 139, 250, 0.22);
    box-shadow:
        0 0 0 1px rgba(167, 139, 250, 0.06),
        0 18px 40px rgba(0, 0, 0, 0.45);
    opacity: 0.35;
    transform: translateY(6px);
    transition: opacity 0.35s ease, transform 0.35s ease,
                border-color 0.35s ease, box-shadow 0.45s ease;
    text-align: left;
    position: relative;
}
.mi-pipe__step.is-lit {
    opacity: 1;
    transform: translateY(0);
    animation: miStepPulse 0.55s ease;
}
@keyframes miStepPulse {
    0%   { box-shadow: 0 0 0 0 rgba(167, 139, 250, 0.45),
                        0 18px 40px rgba(0, 0, 0, 0.45); }
    100% { box-shadow: 0 0 0 1px rgba(167, 139, 250, 0.12),
                        0 18px 40px rgba(0, 0, 0, 0.45); }
}

/* Role · title · sub typography — shared by every step. */
.mi-pipe__role {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 1.6px;
    text-transform: uppercase;
    color: rgba(196, 181, 253, 0.7);
    margin-bottom: 6px;
}
.mi-pipe__title {
    font-size: 15px;
    font-weight: 700;
    color: rgba(245, 242, 255, 0.98);
    letter-spacing: 0.1px;
}
.mi-pipe__sub {
    margin-top: 3px;
    font-size: 11.5px;
    font-weight: 500;
    color: rgba(205, 198, 230, 0.7);
}

/* ── Per-role accents (only visible once .is-lit) ────────────────────── */
/* Each box carries an accent. `--push` (GitLab orange), `--ci` (blue for
 * the pipeline trigger), `--job` (amber — the runner), `--teleport`
 * (brand purple, the star), `--web` (cyan), `--db` (teal), `--audit`
 * (muted green, parallel channel). */
.mi-pipe__step--push.is-lit {
    border-color: rgba(252, 109, 38, 0.55);
    box-shadow:
        0 0 0 1px rgba(252, 109, 38, 0.18),
        0 18px 40px rgba(0, 0, 0, 0.45),
        0 0 60px -15px rgba(252, 109, 38, 0.55);
}
.mi-pipe__step--push.is-lit .mi-pipe__role { color: #FDBA74; }

.mi-pipe__step--ci.is-lit {
    border-color: rgba(96, 165, 250, 0.55);
    box-shadow:
        0 0 0 1px rgba(96, 165, 250, 0.18),
        0 18px 40px rgba(0, 0, 0, 0.45),
        0 0 60px -15px rgba(96, 165, 250, 0.55);
}
.mi-pipe__step--ci.is-lit .mi-pipe__role { color: #93C5FD; }

.mi-pipe__step--job {
    /* Wider than standard steps — fits the three runner-log lines
     * without forcing them to wrap. */
    min-width: 250px;
    max-width: 270px;
}
.mi-pipe__step--job.is-lit {
    border-color: rgba(251, 191, 36, 0.6);
    box-shadow:
        0 0 0 1px rgba(251, 191, 36, 0.2),
        0 18px 40px rgba(0, 0, 0, 0.45),
        0 0 70px -15px rgba(251, 191, 36, 0.6);
}
.mi-pipe__step--job.is-lit .mi-pipe__role { color: #FCD34D; }

/* ── Teleport proxy · brand color + logomark ─────────────────────────── */
/* The only box that isn't purple-hinted on the base : when dim it still
 * reads as "teleport-flavored" so the palette remains readable. When
 * `.is-lit`, the brand violet takes over with a stronger rim. */
.mi-pipe__step--teleport {
    background:
        linear-gradient(180deg,
            rgba(80, 55, 175, 0.52),
            rgba(55, 34, 135, 0.72));
    border-color: rgba(167, 139, 250, 0.45);
    min-width: 170px;
    max-width: 186px;
    padding-top: 46px;  /* reserves space for the logo */
}
.mi-pipe__step--teleport.is-lit {
    background:
        linear-gradient(180deg,
            rgba(109, 72, 220, 0.92),
            rgba(66, 34, 170, 0.96));
    border-color: rgba(192, 168, 255, 0.75);
    box-shadow:
        0 0 0 1px rgba(192, 168, 255, 0.28),
        0 20px 44px rgba(0, 0, 0, 0.55),
        0 0 80px -15px rgba(167, 139, 250, 0.85);
}
.mi-pipe__step--teleport .mi-pipe__role  { color: rgba(230, 222, 255, 0.7); }
.mi-pipe__step--teleport.is-lit .mi-pipe__role { color: #E9E1FF; }
.mi-pipe__step--teleport .mi-pipe__title,
.mi-pipe__step--teleport .mi-pipe__sub { color: #F5F2FF; }
.mi-pipe__step--teleport .mi-pipe__sub { color: rgba(235, 230, 250, 0.85); }

.mi-pipe__logo {
    position: absolute;
    top: 12px;
    left: 14px;
    width: 22px;
    height: 22px;
    color: #F5F2FF;
    opacity: 0.9;
}
.mi-pipe__logo svg { width: 100%; height: 100%; display: block; }

/* ── Runner log (inside the job box) ─────────────────────────────────── */
/* Three lines that "stream" in sequence — each becomes visible when the
 * handler adds `.is-lit`. Monospace so it reads as a real gitlab-runner
 * log excerpt; tiny font to stay compact. */
.mi-pipe__runner-log {
    margin-top: 8px;
    padding: 8px 10px;
    border-radius: 8px;
    background: rgba(5, 3, 15, 0.7);
    border: 1px solid rgba(167, 139, 250, 0.14);
    font-family: "JetBrains Mono", "Menlo", monospace;
    font-size: 10.5px;
    line-height: 1.5;
    color: rgba(230, 225, 250, 0.55);
    min-height: 56px;
}
.mi-pipe__runner-line {
    opacity: 0;
    transform: translateY(2px);
    transition: opacity 0.3s ease, transform 0.3s ease;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mi-pipe__runner-line.is-lit {
    opacity: 1;
    transform: translateY(0);
}
.mi-pipe__runner-line--ok { color: #86EFAC; font-weight: 600; }

/* ── Arrows between main steps ───────────────────────────────────────── */
.mi-pipe__arrow {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    padding: 0 8px;
    font-size: 22px;
    font-weight: 700;
    color: rgba(167, 139, 250, 0.28);
    transition: color 0.35s ease, transform 0.35s ease;
}
.mi-pipe__arrow.is-lit {
    color: #FCD34D;
    transform: scale(1.1);
}

/* ── Fork SVG (Teleport → targets) ───────────────────────────────────── */
/* Two Bézier legs splitting from a single point on the left, each landing
 * at a target box on the right. Legs start dim and light up independently
 * — one per target — so a viewer can tell "the tunnel opens to both". */
.mi-pipe__fork {
    width: 56px;
    height: 132px;
    margin: 0 4px;
    flex-shrink: 0;
}
.mi-pipe__fork-leg {
    fill: none;
    stroke: rgba(167, 139, 250, 0.28);
    stroke-width: 2.5;
    stroke-linecap: round;
    transition: stroke 0.35s ease;
}
.mi-pipe__fork-leg.is-lit { stroke: #FCD34D; }
.mi-pipe__fork-head {
    fill: rgba(167, 139, 250, 0.28);
    transition: fill 0.35s ease;
}
.mi-pipe__fork-head.is-lit { fill: #FCD34D; }

/* ── Target stack (web-03 + db-main) ─────────────────────────────────── */
.mi-pipe__targets {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.mi-pipe__step--target {
    min-width: 160px;
    max-width: 176px;
    padding: 10px 12px;
}
.mi-pipe__step--target .mi-pipe__title { font-size: 14px; }
.mi-pipe__step--target .mi-pipe__sub   { font-size: 11px; }

.mi-pipe__step--web.is-lit {
    border-color: rgba(103, 232, 249, 0.55);
    box-shadow:
        0 0 0 1px rgba(103, 232, 249, 0.18),
        0 14px 32px rgba(0, 0, 0, 0.45),
        0 0 55px -15px rgba(103, 232, 249, 0.55);
}
.mi-pipe__step--web.is-lit .mi-pipe__role { color: #67E8F9; }

.mi-pipe__step--db.is-lit {
    border-color: rgba(45, 212, 191, 0.55);
    box-shadow:
        0 0 0 1px rgba(45, 212, 191, 0.18),
        0 14px 32px rgba(0, 0, 0, 0.45),
        0 0 55px -15px rgba(45, 212, 191, 0.55);
}
.mi-pipe__step--db.is-lit .mi-pipe__role { color: #5EEAD4; }

/* ── Audit tap + pane (parallel, far right) ──────────────────────────── */
/* The tap is a dashed SVG line running horizontally from the target
 * column into the audit pane, conveying "session recording runs in
 * parallel of the pipeline, tapped off the Proxy". The pane itself uses
 * a dashed border to reinforce the "parallel channel" metaphor. */
.mi-pipe__tap {
    width: 72px;
    height: 132px;
    margin: 0 2px;
    flex-shrink: 0;
}
.mi-pipe__tap-line {
    fill: none;
    stroke: rgba(167, 139, 250, 0.28);
    stroke-width: 2;
    stroke-dasharray: 5 4;
    stroke-linecap: round;
    transition: stroke 0.4s ease;
}
.mi-pipe__tap-line.is-lit { stroke: #86EFAC; }

.mi-pipe__step--audit {
    min-width: 170px;
    max-width: 188px;
    border-style: dashed;
    border-color: rgba(134, 239, 172, 0.35);
}
.mi-pipe__step--audit.is-lit {
    border-color: rgba(134, 239, 172, 0.65);
    box-shadow:
        0 0 0 1px rgba(134, 239, 172, 0.18),
        0 18px 40px rgba(0, 0, 0, 0.45),
        0 0 60px -15px rgba(134, 239, 172, 0.55);
}
.mi-pipe__step--audit.is-lit .mi-pipe__role { color: #86EFAC; }

/* ══════════════════════════════════════════════════════════════════════════
 * Beat 21 · AUDIT LOG view + OUTRO badge
 *   Closing beat : Teleport's audit-log dashboard listing every action
 *   Alice took since the alert (SSO login → command executed), SHA-256
 *   chained. The outro badge overlays the bottom with the resolution
 *   stamp + ZenOps tagline.
 *
 *   The audit view reuses the existing browser-mock chrome
 *   (.zen-browser-mock__layout — sidebar + topbar + main), so we only
 *   style the *inner* table and the chain chip here; sidebar/topbar
 *   visuals are inherited from the component CSS.
 * ══════════════════════════════════════════════════════════════════════════ */

/* Page main — tighter vertical padding than Resources, since the table
 * itself carries the visual weight. */
.audit-main {
    padding-top: 18px;
}

.audit-table {
    display: grid;
    grid-template-columns: 220px 1fr 140px;
    gap: 0;
    background: rgba(13, 17, 23, 0.55);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 8px;
    overflow: hidden;
}

.audit-row {
    display: contents;
}

/* Each row's children span 3 columns via display:contents; we style the
 * cells individually so hover / state can key off the span elements. */
.audit-row > span {
    padding: 12px 18px;
    font-size: 13px;
    line-height: 1.4;
    border-top: 1px solid rgba(255, 255, 255, 0.05);
    opacity: 0;
    transform: translateY(4px);
    transition: opacity 0.5s var(--ease-natural),
                transform 0.5s var(--ease-natural);
}

/* Header row — no top border, uppercase tracking. */
.audit-row--head > span {
    background: rgba(255, 255, 255, 0.035);
    border-top: none;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.8px;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.55);
    opacity: 1;
    transform: none;
}

.audit-row.is-lit > span {
    opacity: 1;
    transform: none;
}

/* Type chip — small rounded pill with role-specific color. */
.audit-type {
    display: inline-block;
    padding: 3px 10px !important;
    font-size: 11px !important;
    font-weight: 600;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    border-radius: 999px;
    border: 1px solid currentColor;
    background: transparent;
    margin: 6px 0 !important;
    align-self: center;
    width: max-content;
}

/* Role colors match the rest of the deck : violet (SSO/IDP), amber (cert),
 * green (approval), cyan (SSH), pink (command by user). */
.audit-type--sso   { color: #A78BFA; }
.audit-type--cert  { color: #F59E0B; }
.audit-type--ar    { color: #34D399; }
.audit-type--ssh   { color: #22D3EE; }
.audit-type--cmd   { color: #F472B6; }

.audit-desc {
    color: rgba(255, 255, 255, 0.85);
}
.audit-desc code,
.audit-desc .hl {
    font-family: 'JetBrains Mono', 'SF Mono', Menlo, monospace;
    font-size: 12px;
    padding: 1px 5px;
    background: rgba(255, 255, 255, 0.06);
    border-radius: 4px;
    color: #E2E8F0;
}

.audit-time {
    color: rgba(255, 255, 255, 0.55);
    font-family: 'JetBrains Mono', 'SF Mono', Menlo, monospace;
    font-size: 12px;
    text-align: right;
}

/* SHA-256 chain chip — anchored bottom-right of the main pane, signals
 * "everything above is tamper-evident". */
.audit-chain {
    align-self: flex-end;
    margin: 14px 0 0 auto;
    padding: 6px 12px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.4px;
    color: #86EFAC;
    border: 1px solid rgba(134, 239, 172, 0.35);
    border-radius: 999px;
    background: rgba(16, 185, 129, 0.06);
    opacity: 0;
    transition: opacity 0.6s var(--ease-natural);
}
.audit-chain.is-lit { opacity: 1; }

/* ── Outro badge ──────────────────────────────────────────────────────
 * Horizontal card anchored at 50% 92% in scene coords. Two blocks split
 * by a vertical divider : a green "Service rétabli · 03:24" stamp on
 * the left, the ZenOps tagline on the right. Hidden by default via
 * .is-hidden (set from the module's visibility helper). */
.outro-badge {
    position: absolute;
    display: flex;
    align-items: center;
    gap: 18px;
    padding: 14px 26px;
    border-radius: 14px;
    background: linear-gradient(
        145deg,
        rgba(13, 17, 23, 0.96),
        rgba(13, 17, 23, 0.88)
    );
    border: 1px solid rgba(134, 239, 172, 0.35);
    box-shadow:
        0 18px 48px rgba(0, 0, 0, 0.5),
        0 0 60px -20px rgba(134, 239, 172, 0.45);
    z-index: 5;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    transition: opacity 0.6s var(--ease-natural);
}

.outro-badge__stamp {
    font-size: 15px;
    font-weight: 700;
    letter-spacing: 0.3px;
    color: #86EFAC;
    white-space: nowrap;
}

.outro-badge__sep {
    width: 1px;
    height: 22px;
    background: rgba(255, 255, 255, 0.15);
}

.outro-badge__tagline {
    font-size: 13px;
    font-weight: 500;
    letter-spacing: 0.2px;
    color: rgba(255, 255, 255, 0.85);
    font-style: italic;
    white-space: nowrap;
}

/* ──────────────────────────────────────────────────────────────────────────
 * 12. Full-stage transition messages · beats 13 + 14
 *
 *     Deliberately NOT a card. No background, no border, no chrome.
 *     Just the narrator writing the pivot in large letters on a blank
 *     stage — that's the whole visual. Split in two beats so the sales
 *     rep at Devoxx can advance manually: state the fact first, then
 *     let the question hang in the air before the Machine ID chapter.
 *
 *     Sizing: stays inside the 1920×1080 stage with generous side
 *     margins so the text never kisses the edges. `text-wrap: balance`
 *     evens the rag for a more deliberate, "written" feel.
 *
 *     Entrance: same fade+lift as the VNet card (.is-revealed), just
 *     wider travel (18px → 0) and slower (800ms) to match the weight
 *     of a pure-text beat landing.
 * ────────────────────────────────────────────────────────────────────────── */
.transition-msg {
    width: min(1400px, 80%);
    text-align: center;
    color: var(--white, #f5f7fa);
    font-weight: 600;
    font-size: 3.6rem;
    line-height: 1.28;
    letter-spacing: -0.01em;
    text-wrap: balance;

    opacity: 0;
    transform: translateY(18px);
    transition:
        opacity   0.8s var(--ease-natural, cubic-bezier(.22,.61,.36,1)),
        transform 0.8s var(--ease-natural, cubic-bezier(.22,.61,.36,1));
    pointer-events: none;
}

.transition-msg.is-revealed {
    opacity: 1;
    transform: translateY(0);
}

/* Question variant — lighter weight + accent on the closing clause so
 * the "sans mot de passe?" lands as the actual question, not just a
 * trailing fragment. Keeps the same silhouette as the statement. */
.transition-msg--question {
    font-weight: 500;
    color: rgba(245, 247, 250, 0.92);
}

.transition-msg--question em {
    font-style: normal;
    color: #34D399;
    font-weight: 600;
}

/* ──────────────────────────────────────────────────────────────────────────
 * 13. VNet mini chip · beat 12 footnote
 *
 *     Small pinned chip at the stage bottom-left telling the tech-savvy
 *     viewer "the reason that plain `ssh` worked is VNet". Deliberately
 *     outside the camera's framing target (camera.frame on [vnetTerm])
 *     so it never steals focus — it's a footnote, not a narration beat.
 *
 *     Visible only in beat 12; `.is-hidden` elsewhere. Entrance is a
 *     simple opacity fade, late enough that it appears *after* the
 *     terminal has started typing (so the eye lands on the shell first).
 * ────────────────────────────────────────────────────────────────────────── */
.vnet-chip {
    font-size: 13px;
    font-weight: 500;
    letter-spacing: 0.3px;
    color: rgba(245, 247, 250, 0.58);
    background: rgba(13, 17, 23, 0.55);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    padding: 8px 14px;
    white-space: nowrap;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    transition: opacity 0.5s var(--ease-natural, cubic-bezier(.22,.61,.36,1));
    pointer-events: none;
}

/* ══════════════════════════════════════════════════════════════════════════
 *  AUTH-FLOW v2 · enriched architecture diagram (beats 4-8)
 *  ──────────────────────────────────────────────────────────────────────────
 *  Rebuild of ep01's arch slide on top of zen. Adds Internet pill,
 *  cert badge with pulse, 4 horizontal resource rows, passwordless
 *  parenthèse panel, reverse-tunnel chrome + color legend.
 *  All selectors scoped to .auth-flow-scope so they don't leak to hosts
 *  that inline this block elsewhere.
 * ══════════════════════════════════════════════════════════════════════════ */

/* ─── Internet pill (user → Proxy transit marker) ─────────────────────── */
.auth-flow-scope .af-internet {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 7px 14px;
    background: rgba(148,163,184,.1);
    border: 1px dashed rgba(148,163,184,.4);
    border-radius: 999px;
    color: rgba(226,232,240,.85);
    font-size: 17px; font-weight: 500; letter-spacing: 0.2px;
    white-space: nowrap;
    opacity: 0; transition: opacity 0.55s var(--ease-natural);
    pointer-events: none;
}
.auth-flow-scope .af-internet.is-revealed { opacity: 1; }
.auth-flow-scope .af-internet svg {
    width: 19px; height: 19px; stroke: currentColor;
    fill: none; stroke-width: 1.8;
}

/* ─── Cert badge · flashes at beat 7 (Auth signs X.509) ───────────────── */
.auth-flow-scope .af-cert-badge {
    width: 54px; height: 54px;
    display: flex; align-items: center; justify-content: center;
    background: radial-gradient(circle, rgba(245,158,11,.35), rgba(245,158,11,.05));
    border: 2px solid #F59E0B;
    border-radius: 50%;
    font-size: 28px; line-height: 1;
    box-shadow:
        0 0 0 4px rgba(245,158,11,.15),
        0 0 40px rgba(245,158,11,.4);
    opacity: 0; transform: scale(0.6);
    transition: opacity 0.5s var(--ease-natural), transform 0.5s var(--ease-natural);
    pointer-events: none;
}
.auth-flow-scope .af-cert-badge.is-revealed {
    opacity: 1; transform: scale(1);
    animation: cert-pulse 1.4s ease-in-out infinite 0.5s;
}
@keyframes cert-pulse {
    0%, 100% { transform: scale(1);    box-shadow: 0 0 0 4px rgba(245,158,11,.15), 0 0 40px rgba(245,158,11,.4); }
    50%      { transform: scale(1.06); box-shadow: 0 0 0 8px rgba(245,158,11,.08), 0 0 60px rgba(245,158,11,.55); }
}

/* ─── Resource rows · 4 zen-box--row on the right (beat 8) ────────────────
 *  Reusing the framework: each resource is a proper `.zen-box.zen-box--row`
 *  with `data-role="agent"` so it inherits the orange chrome/glow/title
 *  color from box.css. The scoped rules below only tweak:
 *    1. Initial invisibility (default zen-box idle is 0.15 opacity — we
 *       want these fully hidden until beat 8 so the cascade reads as
 *       "brand new elements appearing", not "something was dim then lit").
 *    2. The 12px slide-in transform that matches the original v2 design.
 *  `.a-active` (set by zen.state.set(el,'active')) restores the standard
 *  orange role styling from box.css unchanged.                            */
.auth-flow-scope .zen-box[data-role="agent"] {
    opacity: 0;
    transform: translateX(12px);
}
.auth-flow-scope .zen-box[data-role="agent"].a-active {
    opacity: 1;
    transform: none;
}

/* ─── Reverse-tunnel main label (under the arrows, beat 8) ────────────── */
.auth-flow-scope .af-rev-main {
    display: flex; flex-direction: column; gap: 2px;
    text-align: center;
    padding: 10px 18px;
    background: rgba(13,17,23,.75);
    border: 1px solid rgba(249,115,22,.25);
    border-radius: 10px;
    color: rgba(235,240,250,.9);
    font-family: Inter, system-ui, sans-serif;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    max-width: 460px;
    opacity: 0; transition: opacity 0.5s var(--ease-natural);
    pointer-events: none;
}
.auth-flow-scope .af-rev-main.is-revealed { opacity: 1; }
.auth-flow-scope .af-rev-main .af-rev-main__title {
    font-size: 16px; font-weight: 700; letter-spacing: 0.3px;
    color: #FDBA74;
}
.auth-flow-scope .af-rev-main .af-rev-main__sub {
    font-size: 13.5px; font-weight: 400;
    color: rgba(148,163,184,.9);
}

/* ─── Color legend (beat 8) ───────────────────────────────────────────── */
.auth-flow-scope .af-legend {
    display: flex; align-items: center; gap: 18px;
    padding: 8px 16px;
    background: rgba(13,17,23,.7);
    border: 1px solid rgba(148,163,184,.15);
    border-radius: 999px;
    color: rgba(235,240,250,.9);
    font-family: Inter, system-ui, sans-serif;
    font-size: 14.5px; font-weight: 500;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    white-space: nowrap;
    opacity: 0; transition: opacity 0.5s var(--ease-natural);
    pointer-events: none;
}
.auth-flow-scope .af-legend.is-revealed { opacity: 1; }
.auth-flow-scope .af-legend .af-legend__item {
    display: inline-flex; align-items: center; gap: 7px;
}
.auth-flow-scope .af-legend .af-legend__dot {
    width: 10px; height: 10px; border-radius: 50%;
    flex-shrink: 0;
}
.auth-flow-scope .af-legend .af-legend__item--muted { opacity: 0.5; }

/* ─── Passwordless parenthèse panel (beat 7) ─────────────────────────── */
.auth-flow-scope .af-pwless {
    width: 380px;
    padding: 22px 24px 20px;
    background: linear-gradient(180deg, rgba(28,20,54,.92), rgba(14,10,30,.96));
    border: 1px solid rgba(167,139,250,.4);
    border-radius: 14px;
    box-shadow:
        0 0 0 1px rgba(167,139,250,.1),
        0 20px 50px rgba(0,0,0,.5),
        0 0 80px -20px rgba(167,139,250,.35) inset;
    color: rgba(235,230,250,.92);
    font-family: Inter, system-ui, sans-serif;
    opacity: 0; transform: translateY(12px);
    transition: opacity 0.55s var(--ease-natural), transform 0.55s var(--ease-natural);
    pointer-events: none;
}
.auth-flow-scope .af-pwless.is-revealed { opacity: 1; transform: none; }
.auth-flow-scope .af-pwless__eyebrow {
    font-size: 12.5px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.15em;
    color: #A78BFA; margin-bottom: 4px;
}
.auth-flow-scope .af-pwless__title {
    font-size: 28px; font-weight: 700;
    color: #fff; letter-spacing: -0.02em; margin-bottom: 12px;
}
.auth-flow-scope .af-pwless__list {
    list-style: none; padding: 0; margin: 0 0 14px;
    display: flex; flex-direction: column; gap: 8px;
}
.auth-flow-scope .af-pwless__list li {
    display: flex; align-items: flex-start; gap: 10px;
    font-size: 16px; line-height: 1.4;
}
.auth-flow-scope .af-pwless__mark {
    flex-shrink: 0;
    width: 22px; height: 22px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 14px; font-weight: 700;
    border-radius: 50%;
}
.auth-flow-scope .af-pwless__mark--x {
    background: rgba(248,113,113,.15);
    color: #F87171;
    border: 1px solid rgba(248,113,113,.3);
}
.auth-flow-scope .af-pwless__mark--check {
    background: rgba(52,211,153,.2);
    color: #34D399;
    border: 1px solid rgba(52,211,153,.4);
}
.auth-flow-scope .af-pwless__kicker {
    font-size: 17px; font-weight: 600;
    color: #C4B5FD;
    padding-top: 12px;
    border-top: 1px solid rgba(167,139,250,.2);
    text-align: center;
    letter-spacing: -0.01em;
}

/* ─── Reverse-tunnel mini chips (one per resource, beat 8) ───────────── */
.auth-flow-scope .af-rev-chip {
    font-size: 12px; font-weight: 600; letter-spacing: 0.4px;
    padding: 4px 9px;
    background: rgba(13,17,23,.85);
    border: 1px solid rgba(249,115,22,.4);
    border-radius: 999px;
    color: #FDBA74; white-space: nowrap;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    opacity: 0; transition: opacity 0.5s var(--ease-natural);
    pointer-events: none;
}
.auth-flow-scope .af-rev-chip.is-revealed { opacity: 1; }

/* ──────────────────────────────────────────────────────────────────────────
 * 9. Outro-pillars scene (beat 23)
 *    Closing 2×2 grid summarizing Teleport's 4 guarantees.
 *    Rendered into `#outro-pillars-root` (placed at x=100%..200% in world
 *    coords — camera pans horizontally from audit to pillars).
 *
 *    Role unified to `auth` (green = validated promise) so the 4 cards read
 *    as a visual chorus, not 4 competing colors. Cascade reveal via
 *    `zen.state.set(card, 'active')` on a ~450ms stagger — but because
 *    zen-box's default opacity is 0.15 (pre-activated), we override to 0
 *    here so hidden pillars are fully invisible rather than a dim blur
 *    under the title. See js beat 23 handler for the stagger timing.
 * ────────────────────────────────────────────────────────────────────────── */

/* Scene title + subtitle — hero typography at the top of the frame. */
.outro-pillars-scope .outro-pillars__title {
    font-size: 46px;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: #fff;
    text-align: center;
    max-width: 80%;
    white-space: nowrap;
    opacity: 0;
    transform: translateY(-10px);
    transition: opacity 0.6s var(--ease-natural),
                transform 0.6s var(--ease-natural);
}
.outro-pillars-scope .outro-pillars__title.is-revealed {
    opacity: 1;
    transform: none;
}
.outro-pillars-scope .outro-pillars__sub {
    font-size: 20px;
    font-weight: 500;
    color: var(--gray);
    text-align: center;
    max-width: 70%;
    opacity: 0;
    transform: translateY(-6px);
    transition: opacity 0.6s var(--ease-natural),
                transform 0.6s var(--ease-natural);
}
.outro-pillars-scope .outro-pillars__sub.is-revealed {
    opacity: 1;
    transform: none;
}

/* Tagline — bottom of the scene, small + subtle. Read as a closing line. */
.outro-pillars-scope .outro-pillars__tagline {
    font-size: 22px;
    font-weight: 600;
    color: #C4F7DD;
    text-align: center;
    letter-spacing: 0.01em;
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 0.6s var(--ease-natural),
                transform 0.6s var(--ease-natural);
    white-space: nowrap;
}
.outro-pillars-scope .outro-pillars__tagline.is-revealed {
    opacity: 1;
    transform: none;
}

/* The zen-scope that wraps the 2×2 grid — force opacity:1. core.css sets
 * .zen-scope to opacity:0.35 by default and only ramps to 1 with `.a-active`.
 * We belt-and-suspenders it here so that a missing `a-active` class doesn't
 * put the 4 pillars + all their content under a 35% veil (the bug behind
 * the "tout est opaque" screenshot). Also kill the dashed frame + background
 * wash from the base zen-scope rule — we don't want a visible container
 * around the pillar grid in the outro. */
.outro-pillars-scope .outro-pillars__grid {
    opacity: 1 !important;
    border-color: transparent !important;
    background: none !important;
    box-shadow: none !important;
}

/* Pillar card · grid child.
 *
 * ── Visibility strategy ───────────────────────────────────────────────────
 *   Prior iteration faded opacity 0 → 1 on .a-active. Problem: the
 *   `.zen-box` base sets opacity:0.15 + `transition:all 0.8s`, and that
 *   inherited transition + the opacity ramp combined with camera motion
 *   made cards land around 40-60% opacity visually during the cascade —
 *   the screenshot caught them mid-fade and everything looked desaturated.
 *
 *   Fix: decouple reveal from opacity entirely. Use `visibility` (not
 *   animated) + `transform` (animated) for the cascade. The card is
 *   either fully visible or not visible at all — no intermediate state.
 *   All nested text/icon rules force `opacity:1` explicitly to immunize
 *   against any future surprise ancestor animation. */
.outro-pillars-scope .zen-box[data-role="auth"].outro-pillar {
    opacity: 1 !important;
    visibility: hidden;
    transform: translateY(16px);
    transition: transform 0.55s var(--ease-natural),
                border-color 0.3s var(--ease-natural),
                box-shadow   0.3s var(--ease-natural);
    padding: 44px 48px;
    gap: 20px;
    justify-content: center;
    text-align: center;
    /* Solid panel — near-black base, no gradient noise, so the bright
     * white text pops to full saturation. */
    background: #0A0E14 !important;
    border-width: 2px;
    /* Make the box itself define a color context for currentColor
     * propagation (icon stroke). Explicit green kills any inheritance
     * surprise from body/root. */
    color: var(--green);
}
.outro-pillars-scope .zen-box[data-role="auth"].outro-pillar.a-active {
    visibility: visible;
    transform: none;
}

/* Icon — big, explicit green stroke. The role-color rules in box.css
 * target `.a-icon` / `.zen-box__icon` selectors which don't match
 * `.outro-pillar__icon`, so we set color directly on the parent (above)
 * AND on the icon itself for belt-and-suspenders. Stroke uses
 * `currentColor` so color: var(--green) paints the strokes. */
.outro-pillars-scope .outro-pillar__icon {
    width: 88px !important;
    height: 88px !important;
    stroke-width: 1.8;
    margin-bottom: 4px;
    flex-shrink: 0;
    color: var(--green) !important;
    opacity: 1 !important;
}

/* Title — pure white, max weight, no transformations. Kills every
 * inherited rule (box.css uppercase, green, letter-spacing). */
.outro-pillars-scope .outro-pillar__title {
    font-size: 38px !important;
    font-weight: 800 !important;
    letter-spacing: -0.015em !important;
    text-transform: none !important;
    line-height: 1.1 !important;
    text-align: center !important;
    margin: 0 !important;
    color: #FFFFFF !important;
    opacity: 1 !important;
    white-space: nowrap;
}

/* Sub — supporting copy, 2–3 lines. Pure white at 85% effective
 * brightness via #F3F4F6 (was #E5E7EB which photographed too gray).
 * Monospace inheritance from box.css `.sub` killed explicitly. */
.outro-pillars-scope .outro-pillar__sub {
    font-size: 22px !important;
    line-height: 1.45 !important;
    font-weight: 500 !important;
    color: #F3F4F6 !important;
    opacity: 1 !important;
    font-family: inherit !important;
    text-align: center !important;
    max-width: 96%;
}
