/* ═══════════════════════════════════════════════════════════════════════════
 * Login page redesign — split layout + right-panel carousel.
 * Frozen design spec: ~/.claude/evidence/DoorVault/2026-09-22-login-page-redesign/
 * fable-design-spec.md. `.login-page` / `.login-card` in style.css are left
 * intact — several other templates (register, forgot_password, demo_gate,
 * pro_login, etc.) still depend on them. This page no longer uses those classes.
 * ═══════════════════════════════════════════════════════════════════════════ */

.dv-login {
    display: grid;
    grid-template-columns: 44% 56%;
    min-height: var(--dv-app-vh, 100vh);
    min-height: var(--dv-app-dvh, 100dvh);
}

.dv-login__panel {
    position: relative;
}

.dv-login__panel--left {
    background: var(--dv-bg-sidebar);
    border-right: 1px solid var(--dv-border);
    display: flex;
    align-items: center;
    justify-content: center;
}

.dv-login__panel--right {
    background: var(--dv-bg-app);
    overflow: hidden;
}

.dv-login__left-inner {
    width: 460px;
    max-width: 100%;
    padding: 48px;
}

/* ── Logo / headline / subhead ─────────────────────────────────────────── */

.dv-login__logo {
    margin-bottom: 40px;
}

.dv-login__logo-img {
    height: 28px;
    width: auto;
    object-fit: contain;
    display: block;
}

.dv-login__headline {
    font-family: var(--dv-font-display);
    font-weight: 600;
    font-size: 34px;
    line-height: 1.2;
    letter-spacing: -0.03em;
    color: var(--dv-fg-1);
    margin: 0;
}

.dv-login__headline em {
    font-style: italic;
    color: var(--dv-accent);
}

.dv-login__subhead {
    font-family: var(--dv-font-sans);
    font-size: 15px;
    color: var(--dv-fg-2);
    margin: 8px 0 0;
}

/* ── Flashed messages ──────────────────────────────────────────────────── */

.dv-login__flash-stack {
    margin-top: 32px;
}

.dv-login__flash {
    background: var(--dv-bg-card);
    border: 1px solid var(--dv-border);
    border-radius: 6px;
    color: var(--dv-fg-1);
}

.dv-login__flash--danger {
    border-left: 2px solid var(--dv-danger);
}

.auth-provider-grid {
    margin-top: 32px;
}

/* ── Divider ────────────────────────────────────────────────────────────── */

.dv-login__divider {
    position: relative;
    text-align: center;
    margin: 24px 0;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
}

.dv-login__divider span {
    position: relative;
    top: -9px;
    background: var(--dv-bg-sidebar);
    padding: 0 10px;
    font-family: var(--dv-font-mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--dv-fg-3);
}

/* ── Form ───────────────────────────────────────────────────────────────── */

.dv-login__form {
    margin-top: 0;
}

.dv-login__field {
    margin-bottom: 16px;
}

.dv-login__label {
    display: block;
    font-family: var(--dv-font-sans);
    font-size: 13px;
    color: var(--dv-fg-2);
    margin-bottom: 6px;
}

.dv-login__input.form-control {
    height: 48px;
    background: var(--dv-bg-input);
    border: 1px solid var(--dv-border);
    border-radius: 6px;
    color: var(--dv-fg-1);
    font-size: 16px;
}

.dv-login__input.form-control:focus {
    background: var(--dv-bg-input);
    border-color: var(--dv-accent);
    color: var(--dv-fg-1);
    box-shadow: none;
}

/* Chrome/Safari paint autofilled inputs with a native light-blue background
   via an internal UA style that a plain `background` rule cannot override.
   Force the same dark input surface + light text back with the standard
   inset-box-shadow trick (fill the input visually before the "real"
   background paints underneath it). */
.dv-login__input.form-control:-webkit-autofill,
.dv-login__input.form-control:-webkit-autofill:hover,
.dv-login__input.form-control:-webkit-autofill:focus {
    -webkit-box-shadow: 0 0 0 1000px var(--dv-bg-input) inset;
    box-shadow: 0 0 0 1000px var(--dv-bg-input) inset;
    -webkit-text-fill-color: var(--dv-fg-1);
    caret-color: var(--dv-fg-1);
    transition: background-color 9999s ease-in-out 0s;
}

.dv-login__forgot {
    text-align: right;
    margin-top: 6px;
}

.dv-login__forgot a {
    font-family: var(--dv-font-sans);
    font-size: 13px;
    color: var(--dv-fg-2);
    text-decoration: none;
}

.dv-login__forgot a:hover {
    text-decoration: underline;
}

.dv-login__submit {
    width: 100%;
    height: 48px;
    margin-top: 16px;
    background: var(--dv-accent);
    color: var(--dv-bg-app);
    border: none;
    border-radius: 6px;
    font-family: var(--dv-font-sans);
    font-weight: 600;
    font-size: 15px;
    transition: filter var(--transition-fast, 0.15s ease);
}

.dv-login__submit:hover {
    filter: brightness(1.06);
}

/* ── Footer links ───────────────────────────────────────────────────────── */

.dv-login__footer-links {
    margin-top: 24px;
    text-align: center;
    font-family: var(--dv-font-sans);
    font-size: 13px;
    color: var(--dv-fg-2);
}

.dv-login__footer-links hr {
    border-color: var(--dv-border);
    margin-bottom: 16px;
}

.dv-login__footer-links p {
    margin: 0 0 6px;
}

.dv-login__footer-links a {
    color: var(--dv-fg-1);
    text-decoration: none;
}

.dv-login__footer-links a:hover {
    text-decoration: underline;
}

.dv-login__copyright {
    margin-top: 24px;
    text-align: center;
    color: var(--dv-fg-3);
    font-size: 12px;
}

/* ── Right panel background structure ──────────────────────────────────── */

.dv-login__right-grid {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image:
        repeating-linear-gradient(
            to bottom,
            var(--dv-border-hairline) 0,
            var(--dv-border-hairline) 1px,
            transparent 1px,
            transparent 48px
        ),
        linear-gradient(
            to right,
            transparent 0,
            transparent calc(12% - 1px),
            rgba(255, 255, 255, 0.06) calc(12% - 1px),
            rgba(255, 255, 255, 0.06) 12%,
            transparent 12%,
            transparent 88%,
            rgba(255, 255, 255, 0.06) 88%,
            rgba(255, 255, 255, 0.06) calc(88% + 1px),
            transparent calc(88% + 1px)
        );
}

.dv-login__right-inner {
    position: relative;
    height: 100%;
    padding: 64px;
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.dv-login__right-meta {
    position: absolute;
    top: 64px;
    left: 64px;
    right: 64px;
    display: flex;
    justify-content: space-between;
    font-family: var(--dv-font-mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--dv-fg-3);
}

/* ── Animated stage (round 3) ─────────────────────────────────────────────
   Replaces the 3-card crossfade stack with one fixed 440x264 design-px
   stage holding one of 4 fully animated scenes at a time. Frozen spec:
   fable-design-spec-round3.md.

   Architecture (implementation direction #1/#2 in that spec):
   - CSS keyframes run every beat; JS only toggles `.is-active` (which
     scene is visible) and `.is-playing` (restart a scene's beats via
     remove class -> force reflow -> re-add class). No rAF, no per-frame JS.
   - Every animated element's un-animated/base style IS its end frame, so
     `@media (prefers-reduced-motion: reduce)` only has to strip the
     `animation` property — the element already renders the correct static
     end-frame described in the spec, nothing else has to be written twice.
   - `--stage-scale` (set by a ResizeObserver in the JS below) is the one
     variable that scales the fixed-coordinate stage to fit its column at
     any width from 960px up; every path/position below is hand-authored
     in the 440x264 space and never measured at runtime.
   ────────────────────────────────────────────────────────────────────── */

.dv-stage-wrap {
    position: relative;
    width: 100%;
    max-width: 440px;
    height: calc(264px * var(--stage-scale, 1));
    margin-top: 4px;
}

.dv-stage {
    position: absolute;
    top: 0;
    left: 0;
    width: 440px;
    height: 264px;
    transform: scale(var(--stage-scale, 1));
    transform-origin: top left;
}

.dv-stage__glow {
    position: absolute;
    top: 132px;
    left: 220px;
    width: 480px;
    height: 480px;
    transform: translate(-50%, -50%);
    background: radial-gradient(circle, rgba(212, 168, 67, 0.07) 0%, rgba(212, 168, 67, 0) 70%);
    pointer-events: none;
}

.dv-scene {
    position: absolute;
    inset: 0;
    opacity: 0;
    visibility: hidden;
    transition: opacity 250ms var(--dv-ease-smooth), visibility 0s linear 250ms;
}

.dv-scene.is-active {
    opacity: 1;
    visibility: visible;
    transition: opacity 250ms var(--dv-ease-smooth);
}

.dv-scene__svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
}

/* Shared line-draw technique (cinema.js `buildThread()`, ported without its
   scroll-scrubbing math): `pathLength="1"` means every path's total length
   is normalized to 1, so `stroke-dasharray:1` + animating `stroke-dashoffset`
   from 1 to 0 draws it without ever measuring `getTotalLength()`. */
.dv-conn {
    fill: none;
    stroke: var(--dv-fg-3);
    stroke-width: 1;
    stroke-dasharray: 1;
    stroke-dashoffset: 0;
    opacity: 0.3;
}

/* ── Shared chip / tile styling, ported from cinema.css:231-315 (.fly,
   .fly.gold, .s-chan, .s-logo) onto --dv-* tokens ─────────────────────── */

.dv-chip {
    position: absolute;
    top: 0;
    left: 0;
    opacity: 0;
    display: flex;
    align-items: center;
    gap: 5px;
    font-family: var(--dv-font-mono);
    font-size: 11px;
    color: var(--dv-fg-1);
    background: color-mix(in srgb, var(--dv-bg-elevated) 92%, transparent);
    border: 1px solid var(--dv-border);
    border-radius: 6px;
    padding: 3px 8px;
    box-shadow: 0 8px 20px -8px rgba(0, 0, 0, 0.6);
    white-space: nowrap;
    pointer-events: none;
}

.dv-chip--gold {
    color: var(--dv-accent);
    border-color: color-mix(in srgb, var(--dv-accent) 45%, transparent);
}

.dv-chip--icon {
    padding: 3px 5px;
    font-size: 12px;
}

.dv-portal-tile,
.dv-chan-tile {
    position: absolute;
    width: 150px;
    height: 44px;
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 0 11px;
    border: 1px solid var(--dv-border);
    border-radius: 8px;
    background: var(--dv-bg-elevated);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
}

.dv-chan-tile {
    width: 170px;
    height: 50px;
    align-items: flex-start;
    padding: 10px 12px;
    gap: 10px;
}

/* Neutral, not gold: slide 4's gold beat is the last-piece cell + board
   outline pass — these two channel icons are never a gold moment. */
.dv-chan-tile > .bi {
    color: var(--dv-fg-2);
    font-size: 14px;
    margin-top: 1px;
}

.dv-chan-tile b {
    display: block;
    font-family: var(--dv-font-sans);
    font-size: 12px;
    font-weight: 600;
    color: var(--dv-fg-1);
}

.dv-chan-tile small {
    display: block;
    font-family: var(--dv-font-mono);
    font-size: 10px;
    color: var(--dv-fg-3);
}

/* Neutral, not gold: slide 1's gold beat is the portfolio value flash
   (dv-s1-value-3-gold) — these monogram tiles are never called out as a
   gold moment in the spec, so a permanent accent fill here would leave a
   second gold element on the resting frame at once. */
/* Real app icons (RentManager/Buildium/AppFolio), not a DoorVault-drawn
   monogram: shown as-is (object-fit:cover, no recolor) since each is
   already a solid-color square from its own official app icon — this
   factually indicates a real, existing DoorVault integration. */
.dv-portal-tile__mark {
    width: 28px;
    height: 28px;
    box-sizing: border-box;
    border-radius: 6px;
    flex-shrink: 0;
    display: block;
    object-fit: cover;
    border: 1px solid var(--dv-border);
}

.dv-portal-tile__name {
    font-family: var(--dv-font-sans);
    font-size: 12.5px;
    font-weight: 600;
    color: var(--dv-fg-1);
}

/* ── Shared scene text primitives ───────────────────────────────────────── */

.dv-scene-eyebrow {
    font-family: var(--dv-font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--dv-fg-3);
    display: flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

.dv-scene-eyebrow-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.dv-scene-eyebrow--right {
    text-align: right;
    margin-left: auto;
    opacity: 0;
}

.dv-scene-fg3 {
    color: var(--dv-fg-3);
}

.dv-scene-dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--dv-border);
    display: inline-block;
    flex-shrink: 0;
}

.dv-scene-dot--knox {
    background: var(--dv-knox);
}

.dv-scene-check {
    font-size: 12px;
    color: var(--dv-success);
    flex: 0 0 auto;
}

/* ═══ Slide 1: Manager portals ═══════════════════════════════════════════
   Beats (fable-design-spec-round3.md, slide 1), total action length 3.0s:
   0.00 fade in, value "—" · 0.30-2.00 RM/Buildium/AppFolio connectors draw,
   chips ride, rows + running value populate · 2.20-3.00 gold beat: value
   flashes accent then settles, synced eyebrow + delta line + success dots
   + dimmed connectors. Base/resting CSS below already matches that 3.00s+
   hold frame, so reduced motion needs no separate rule. */

/* Height 226 (not the original 200): measured via automated containment
   check that the 3-row stack plus the now-two-line eyebrow overflowed the
   card bottom by ~23px at 200px. */
/* width 214 not 210: with box-sizing:border-box the 1px border eats into
   the declared width too, so content width was actually 180px (210 minus
   28px padding minus 2px border) — 2px short of the 182px "$1,248,500"
   genuinely needs at 26px mono. Measured via scrollWidth vs clientWidth
   on the value container, invisible to box-containment (the value span
   never escaped the CARD's outer edge, just its own too-narrow content
   box by a couple of pixels). */
.dv-s1-card {
    position: absolute;
    left: 220px;
    top: 16px;
    width: 214px;
    height: 238px;
    box-sizing: border-box;
    padding: 12px 14px;
    border: 1px solid var(--dv-border);
    border-radius: 8px;
    background: var(--dv-bg-card);
    box-shadow: 0 20px 44px rgba(0, 0, 0, 0.4);
}

/* On its own line (not sharing the eyebrow row with "PORTFOLIO"): at full
   card content width (~182px) instead of whatever was left after
   "PORTFOLIO" + gap, this text needed the room — sharing the row
   overflowed the card's right edge by ~118px (measured). */
.dv-s1-synced {
    opacity: 1;
    margin-top: 4px;
    white-space: normal;
}

/* height 36 + line-height 1.3 (not 32/1.1): the mono font's actual glyph
   metrics at 26px need ~33px of vertical room, which a 1.1 line-height
   (28.6px) doesn't give — measured via scrollHeight vs clientHeight on
   the value spans, invisible to a pure box-containment check since
   nothing here technically escaped the CARD, just its own line box. */
.dv-s1-value {
    position: relative;
    width: 100%;
    height: 36px;
    margin-top: 6px;
    font-family: var(--dv-font-mono);
    font-size: 26px;
    line-height: 1.4;
}

.dv-s1-value-span {
    position: absolute;
    left: 0;
    top: 0;
    opacity: 0;
    color: var(--dv-fg-1);
    white-space: nowrap;
}

.dv-s1-value-3 { opacity: 1; }

.dv-s1-delta {
    font-family: var(--dv-font-mono);
    font-size: 12px;
    color: var(--dv-success);
    margin-top: 4px;
}

.dv-s1-row {
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--dv-font-sans);
    font-size: 12.5px;
    color: var(--dv-fg-2);
    margin-top: 10px;
    padding-top: 8px;
    border-top: 1px solid var(--dv-border-hairline);
}

.dv-s1-row .dv-scene-dot { background: var(--dv-success); }

.dv-s1-row-name { flex: 1 1 auto; }

.dv-s1-row-value {
    font-family: var(--dv-font-mono);
    color: var(--dv-fg-2);
}

.dv-s1-conn-rm, .dv-s1-conn-bu, .dv-s1-conn-af {
    stroke: var(--dv-fg-3);
    opacity: 0.3;
}

@media (prefers-reduced-motion: no-preference) {
    .dv-scene--1.is-playing .dv-s1-conn-rm { animation: dv-s1-conn-rm 3000ms var(--dv-ease-out) both; }
    .dv-scene--1.is-playing .dv-s1-conn-bu { animation: dv-s1-conn-bu 3000ms var(--dv-ease-out) both; }
    .dv-scene--1.is-playing .dv-s1-conn-af { animation: dv-s1-conn-af 3000ms var(--dv-ease-out) both; }
    .dv-scene--1.is-playing .dv-s1-chip-rm { animation: dv-s1-chip-rm 3000ms linear both; offset-path: path('M150,40 C186,40 186,125 220,125'); }
    .dv-scene--1.is-playing .dv-s1-chip-bu { animation: dv-s1-chip-bu 3000ms linear both; offset-path: path('M150,98 C186,98 186,147 220,147'); }
    .dv-scene--1.is-playing .dv-s1-chip-af { animation: dv-s1-chip-af 3000ms linear both; offset-path: path('M150,156 C186,156 186,169 220,169'); }
    .dv-scene--1.is-playing .dv-s1-row-rm { animation: dv-s1-row-rm 3000ms var(--dv-ease-out) both; }
    .dv-scene--1.is-playing .dv-s1-row-bu { animation: dv-s1-row-bu 3000ms var(--dv-ease-out) both; }
    .dv-scene--1.is-playing .dv-s1-row-af { animation: dv-s1-row-af 3000ms var(--dv-ease-out) both; }
    .dv-scene--1.is-playing .dv-s1-value-dash { animation: dv-s1-value-dash 3000ms linear both; }
    .dv-scene--1.is-playing .dv-s1-value-1 { animation: dv-s1-value-1 3000ms linear both; }
    .dv-scene--1.is-playing .dv-s1-value-2 { animation: dv-s1-value-2 3000ms linear both; }
    .dv-scene--1.is-playing .dv-s1-value-3 { animation: dv-s1-value-3-gold 3000ms linear both; }
    .dv-scene--1.is-playing .dv-s1-synced { animation: dv-scene-fade-in 3000ms linear both; animation-delay: 0s; animation-timing-function: steps(1, end); }
    .dv-scene--1.is-playing .dv-s1-delta { animation: dv-s1-delta 3000ms linear both; }

    @keyframes dv-s1-conn-rm {
        0%, 10% { opacity: 0; stroke-dashoffset: 1; }
        33.3% { opacity: 0.7; stroke-dashoffset: 0; stroke: var(--dv-fg-2); }
        73.3% { opacity: 0.7; stroke: var(--dv-fg-2); }
        100% { opacity: 0.3; stroke: var(--dv-fg-3); }
    }
    @keyframes dv-s1-conn-bu {
        0%, 21.7% { opacity: 0; stroke-dashoffset: 1; }
        45% { opacity: 0.7; stroke-dashoffset: 0; stroke: var(--dv-fg-2); }
        73.3% { opacity: 0.7; stroke: var(--dv-fg-2); }
        100% { opacity: 0.3; stroke: var(--dv-fg-3); }
    }
    @keyframes dv-s1-conn-af {
        0%, 33.3% { opacity: 0; stroke-dashoffset: 1; }
        56.7% { opacity: 0.7; stroke-dashoffset: 0; stroke: var(--dv-fg-2); }
        73.3% { opacity: 0.7; stroke: var(--dv-fg-2); }
        100% { opacity: 0.3; stroke: var(--dv-fg-3); }
    }
    @keyframes dv-s1-chip-rm {
        0%, 23.3% { opacity: 0; offset-distance: 0%; }
        43.3% { opacity: 1; offset-distance: 100%; }
        46.7%, 100% { opacity: 0; offset-distance: 100%; }
    }
    @keyframes dv-s1-chip-bu {
        0%, 35% { opacity: 0; offset-distance: 0%; }
        55% { opacity: 1; offset-distance: 100%; }
        58.3%, 100% { opacity: 0; offset-distance: 100%; }
    }
    @keyframes dv-s1-chip-af {
        0%, 46.7% { opacity: 0; offset-distance: 0%; }
        66.7% { opacity: 1; offset-distance: 100%; }
        70%, 100% { opacity: 0; offset-distance: 100%; }
    }
    @keyframes dv-s1-row-rm {
        0%, 43.3% { opacity: 0; transform: translateY(4px); }
        100% { opacity: 1; transform: translateY(0); }
    }
    @keyframes dv-s1-row-bu {
        0%, 55% { opacity: 0; transform: translateY(4px); }
        100% { opacity: 1; transform: translateY(0); }
    }
    @keyframes dv-s1-row-af {
        0%, 66.7% { opacity: 0; transform: translateY(4px); }
        100% { opacity: 1; transform: translateY(0); }
    }
    @keyframes dv-s1-value-dash {
        0% { opacity: 1; }
        43.3%, 100% { opacity: 0; }
    }
    @keyframes dv-s1-value-1 {
        0%, 43.3% { opacity: 0; }
        43.4%, 55% { opacity: 1; }
        55.1%, 100% { opacity: 0; }
    }
    @keyframes dv-s1-value-2 {
        0%, 55% { opacity: 0; }
        55.1%, 66.7% { opacity: 1; }
        66.8%, 100% { opacity: 0; }
    }
    @keyframes dv-s1-value-3-gold {
        0%, 66.7% { opacity: 0; color: var(--dv-fg-1); }
        66.8% { opacity: 1; color: var(--dv-fg-1); }
        73.3% { color: var(--dv-accent); }
        100% { opacity: 1; color: var(--dv-fg-1); }
    }
    @keyframes dv-s1-delta {
        0%, 73.3% { opacity: 0; }
        80% { opacity: 1; }
    }
    @keyframes dv-scene-fade-in {
        0%, 73.3% { opacity: 0; }
        80%, 100% { opacity: 1; }
    }

    .dv-scene--1:not(.is-playing) .dv-s1-conn-rm,
    .dv-scene--1:not(.is-playing) .dv-s1-conn-bu,
    .dv-scene--1:not(.is-playing) .dv-s1-conn-af {
        stroke: var(--dv-fg-3);
        opacity: 0.3;
    }
}

/* Fallback: no offset-path support -> chips never travel (stay hidden at
   their base opacity:0), destination rows still fade in on schedule. */
@supports not (offset-path: path('M0 0')) {
    .dv-s1-chip-rm, .dv-s1-chip-bu, .dv-s1-chip-af { display: none; }
}

/* ═══ Slide 2: Knox Inbox ═════════════════════════════════════════════════
   Total action length 3.5s. Attachments lift off the email, ride the
   connector, land as "Reading" rows that resolve to checked final lines,
   then the matched-properties footer (gold) and the "nothing posts"
   footnote settle in. Resting/base state = the 3.5s+ hold frame. */

.dv-s2-email {
    position: absolute;
    left: 0;
    top: 54px;
    width: 170px;
    /* 160 not 150: measured via automated containment check that the 3rd
       attachment chip sat ~5px below the card's bottom edge at 150. */
    height: 160px;
    box-sizing: border-box;
    padding: 10px 12px;
    border: 1px solid var(--dv-border);
    border-radius: 8px;
    background: var(--dv-bg-card);
    box-shadow: 0 20px 44px rgba(0, 0, 0, 0.4);
}

/* 9.5px not 10.5px: "To: you@doorvault.app" (a real-looking address, not
   the vaguer "your DoorVault address" this replaced) measured 9px wider
   than the card's ~144px content width at 10.5px — enough that ellipsis
   truncation was hiding the address itself, the one thing this line
   exists to show. */
.dv-s2-email__line {
    font-family: var(--dv-font-mono);
    font-size: 9.5px;
    color: var(--dv-fg-3);
    line-height: 1.5;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dv-s2-attach {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 8px;
    padding: 6px 8px;
    border: 1px solid var(--dv-border);
    border-radius: 6px;
    background: var(--dv-bg-elevated);
    font-family: var(--dv-font-mono);
    font-size: 10px;
    color: var(--dv-fg-2);
    opacity: 0.4;
}

/* Truncate, never overflow: "owner_statement.pdf" measured 14px wider than
   its ~142px chip content width. `min-width: 0` is the classic flexbox
   truncation gotcha — without it a flex child's min-width defaults to its
   content's natural (unwrapped) width, so overflow:hidden/text-overflow
   never actually get a chance to engage. */
.dv-s2-attach__name {
    min-width: 0;
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Neutral, not gold: the spec's only gold moment on this slide is the
   footer's "3 properties matched" phrase (which fades to --dv-fg-2 by
   3.5s) — these attachment icons are never called out as a gold beat, so
   a permanent accent color here would leave two gold elements on the
   resting frame at once. */
.dv-s2-attach .bi { color: var(--dv-fg-2); flex-shrink: 0; }

.dv-s2-inbox {
    position: absolute;
    left: 200px;
    top: 32px;
    width: 230px;
    /* At 12px/1.3 line-height in a 200px-wide row, the longest resolved
       line ("Owner statement · 412 Maple · 47 lines") wraps to 2 lines —
       194px (not 182px) keeps a real bottom padding margin under that
       wrapped 3-row stack instead of nearly consuming it (verified via a
       real headless capture: 3px of clearance at 182px, ~15px at 194px). */
    height: 190px;
    box-sizing: border-box;
    padding: 12px 14px;
    border: 1px solid var(--dv-border);
    border-radius: 8px;
    background: var(--dv-bg-card);
    box-shadow: 0 20px 44px rgba(0, 0, 0, 0.4);
}

.dv-s2-row {
    display: grid;
    margin-top: 10px;
    padding-top: 8px;
    border-top: 1px solid var(--dv-border-hairline);
    font-family: var(--dv-font-sans);
    font-size: 12px;
    line-height: 1.3;
}

.dv-s2-row-reading,
.dv-s2-row-final {
    grid-area: 1 / 1;
    display: flex;
    align-items: center;
    gap: 6px;
}

.dv-s2-row-reading { color: var(--dv-fg-3); opacity: 0; }
.dv-s2-row-final { color: var(--dv-fg-1); opacity: 1; }

.dv-s2-footer {
    position: absolute;
    left: 0;
    right: 0;
    top: 224px;
    font-family: var(--dv-font-sans);
}

/* line-height 1.4 not 1.2: the sans font's actual glyph metrics need more
   vertical room than a 1.2 line-height gives at these sizes — measured
   via scrollHeight vs clientHeight (2px short on each line), invisible to
   a pure box-containment check since the footer element itself never
   escaped the stage, just its own line box. */
.dv-s2-footer__line1 {
    font-size: 12.5px;
    line-height: 1.4;
    color: var(--dv-fg-2);
    opacity: 1;
}

.dv-s2-footer__gold { color: var(--dv-fg-2); }

.dv-s2-footer__line2 {
    font-size: 11px;
    line-height: 1.4;
    color: var(--dv-fg-3);
    margin-top: 2px;
    opacity: 1;
}

@media (prefers-reduced-motion: no-preference) {
    .dv-scene--2.is-playing .dv-s2-conn { animation: dv-s2-conn 3500ms var(--dv-ease-out) both; }
    .dv-scene--2.is-playing .dv-s2-attach--1 { animation: dv-s2-attach-1 3500ms linear both; }
    .dv-scene--2.is-playing .dv-s2-attach--2 { animation: dv-s2-attach-2 3500ms linear both; }
    .dv-scene--2.is-playing .dv-s2-attach--3 { animation: dv-s2-attach-3 3500ms linear both; }
    .dv-scene--2.is-playing .dv-s2-chip-1 { animation: dv-s2-chip-1 3500ms linear both; offset-path: path('M148,100 C178,105 190,118 210,122'); }
    .dv-scene--2.is-playing .dv-s2-chip-2 { animation: dv-s2-chip-2 3500ms linear both; offset-path: path('M148,132 C178,134 192,142 210,152'); }
    .dv-scene--2.is-playing .dv-s2-chip-3 { animation: dv-s2-chip-3 3500ms linear both; offset-path: path('M148,164 C182,160 194,168 210,182'); }
    .dv-scene--2.is-playing .dv-s2-row-1 .dv-s2-row-reading { animation: dv-s2-reading-1 3500ms linear both; }
    .dv-scene--2.is-playing .dv-s2-row-2 .dv-s2-row-reading { animation: dv-s2-reading-2 3500ms linear both; }
    .dv-scene--2.is-playing .dv-s2-row-3 .dv-s2-row-reading { animation: dv-s2-reading-3 3500ms linear both; }
    .dv-scene--2.is-playing .dv-s2-row-1 .dv-s2-row-final { animation: dv-s2-final-1 3500ms linear both; }
    .dv-scene--2.is-playing .dv-s2-row-2 .dv-s2-row-final { animation: dv-s2-final-2 3500ms linear both; }
    .dv-scene--2.is-playing .dv-s2-row-3 .dv-s2-row-final { animation: dv-s2-final-3 3500ms linear both; }
    .dv-scene--2.is-playing .dv-s2-footer__line1 { animation: dv-scene-fade-in-80 3500ms linear both; }
    .dv-scene--2.is-playing .dv-s2-footer__gold { animation: dv-s2-gold-settle 3500ms linear both; }
    .dv-scene--2.is-playing .dv-s2-footer__line2 { animation: dv-s2-footer-2 3500ms linear both; }

    @keyframes dv-s2-conn {
        0%, 11.4% { opacity: 0; stroke-dashoffset: 1; }
        28.6% { opacity: 0.6; stroke-dashoffset: 0; stroke: var(--dv-fg-2); }
        100% { opacity: 0.35; stroke: var(--dv-fg-3); }
    }
    @keyframes dv-s2-attach-1 { 0%, 25.7% { opacity: 1; } 100% { opacity: 0.4; } }
    @keyframes dv-s2-attach-2 { 0%, 32.9% { opacity: 1; } 100% { opacity: 0.4; } }
    @keyframes dv-s2-attach-3 { 0%, 40% { opacity: 1; } 100% { opacity: 0.4; } }
    @keyframes dv-s2-chip-1 {
        0%, 25.7% { opacity: 0; offset-distance: 0%; }
        42.9% { opacity: 1; offset-distance: 100%; }
        45%, 100% { opacity: 0; offset-distance: 100%; }
    }
    @keyframes dv-s2-chip-2 {
        0%, 32.9% { opacity: 0; offset-distance: 0%; }
        50% { opacity: 1; offset-distance: 100%; }
        52%, 100% { opacity: 0; offset-distance: 100%; }
    }
    @keyframes dv-s2-chip-3 {
        0%, 40% { opacity: 0; offset-distance: 0%; }
        57.1% { opacity: 1; offset-distance: 100%; }
        59%, 100% { opacity: 0; offset-distance: 100%; }
    }
    @keyframes dv-s2-reading-1 { 0%, 42.9% { opacity: 0; } 44%, 51.4% { opacity: 1; } 51.5%, 100% { opacity: 0; } }
    @keyframes dv-s2-reading-2 { 0%, 50% { opacity: 0; } 51%, 60% { opacity: 1; } 60.1%, 100% { opacity: 0; } }
    @keyframes dv-s2-reading-3 { 0%, 57.1% { opacity: 0; } 58%, 68.6% { opacity: 1; } 68.7%, 100% { opacity: 0; } }
    @keyframes dv-s2-final-1 { 0%, 51.4% { opacity: 0; } 51.5%, 100% { opacity: 1; } }
    @keyframes dv-s2-final-2 { 0%, 60% { opacity: 0; } 60.1%, 100% { opacity: 1; } }
    @keyframes dv-s2-final-3 { 0%, 68.6% { opacity: 0; } 68.7%, 100% { opacity: 1; } }
    @keyframes dv-scene-fade-in-80 { 0%, 80% { opacity: 0; } 85.7%, 100% { opacity: 1; } }
    @keyframes dv-s2-gold-settle {
        0%, 80% { opacity: 0; color: var(--dv-fg-2); }
        85.7% { opacity: 1; color: var(--dv-accent); }
        100% { opacity: 1; color: var(--dv-fg-2); }
    }
    @keyframes dv-s2-footer-2 { 0%, 85.7% { opacity: 0; } 90%, 100% { opacity: 1; } }
}

@supports not (offset-path: path('M0 0')) {
    .dv-s2-chip-1, .dv-s2-chip-2, .dv-s2-chip-3 { display: none; }
}

/* ═══ Slide 3: Reconciliation ═════════════════════════════════════════════
   Total action length 3.2s. A deposit drops into the bank card, Knox
   matches it to the statement, the match resolves gold, then a full
   status line settles in. Base state below = the 3.2s+ hold frame. */

.dv-s3-card {
    position: absolute;
    top: 40px;
    width: 170px;
    height: 122px;
    box-sizing: border-box;
    padding: 10px 12px;
    border: 1px solid var(--dv-border);
    border-radius: 8px;
    background: var(--dv-bg-card);
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.4);
}

.dv-s3-statement { left: 0; }
.dv-s3-bank { left: 270px; }

/* "OWNER STATEMENT #2261" measured 16px wider than its ~144px card
   content width at the shared .dv-scene-eyebrow's 10px/0.06em — a
   pre-existing overflow the old box-only containment check never saw
   (the eyebrow's own element didn't escape the card, it just rendered
   past its own edge). Scoped to both cards' eyebrows for consistency;
   the shorter "BANK ACCOUNT ••4021" already fit at 10px but reads fine
   smaller too. */
.dv-s3-card .dv-scene-eyebrow {
    font-size: 8.5px;
    letter-spacing: 0.03em;
}

.dv-s3-line {
    font-family: var(--dv-font-sans);
    font-size: 12px;
    color: var(--dv-fg-2);
    margin-top: 6px;
}

/* 11px not 12.5px: "Net to owner" + "$8,450.00" + the check icon needed
   ~161px at 12.5px against ~146px of card content width, so the flex row
   shrank the label span until its text wrapped, dropping its second line
   below the card (measured via automated containment check). */
.dv-s3-row {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 12px;
    font-family: var(--dv-font-sans);
    font-size: 11px;
    color: var(--dv-fg-2);
}

.dv-s3-amount {
    font-family: var(--dv-font-mono);
    color: var(--dv-fg-1);
    margin-left: auto;
}

.dv-s3-check-a, .dv-s3-check-b { opacity: 1; }

/* 34px not 20px: "Deposit · today +$8,450.00 [check]" needed ~198px at a
   single nowrap line against ~146px of card content width — a 52px
   overflow past the card's right edge (measured via automated containment
   check). Wrapping the filled state to 2 lines needs the taller slot;
   the short "No new deposits" empty state still renders as one line
   within it. */
.dv-s3-slot {
    position: relative;
    height: 34px;
    margin-top: 14px;
}

.dv-s3-slot-span {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    font-family: var(--dv-font-sans);
    font-size: 12.5px;
}

.dv-s3-slot-empty {
    color: var(--dv-fg-3);
    opacity: 0;
    display: flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

.dv-s3-slot-filled {
    color: var(--dv-fg-2);
    opacity: 1;
    display: block;
    line-height: 1.35;
}

.dv-s3-slot-filled .dv-s3-deposit-amount,
.dv-s3-slot-filled .dv-scene-check {
    white-space: nowrap;
}

.dv-s3-deposit-amount {
    font-family: var(--dv-font-mono);
    color: var(--dv-success);
}

.dv-s3-drop-chip { color: var(--dv-success); }

/* The dot sits in column 1; both label-text states share column 2 (same
   grid cell, so they crossfade in place instead of the dot and the text
   stacking into two visual rows). */
/* The statement/bank cards each need their full ~146px content width for
   their own eyebrow text (measured: 144px used at 146px available — no
   room to shrink the cards to make space for this label instead). At
   full width ("KNOX · RECONCILED" nowrap, ~139px) the label is wider than
   the ~100px gap between the two cards and overlaps both edges. Fixed
   width + `white-space: normal` lets it wrap to two short lines instead,
   comfortably inside the gap. */
.dv-s3-knox-label {
    position: absolute;
    left: 220px;
    top: 128px;
    width: 92px;
    transform: translateX(-50%);
    display: inline-grid;
    grid-template-columns: auto 1fr;
    align-items: start;
    gap: 5px;
    font-family: var(--dv-font-mono);
    font-size: 9px;
    letter-spacing: 0.02em;
    line-height: 1.4;
    color: var(--dv-fg-3);
    white-space: normal;
    text-align: left;
}

.dv-s3-knox-label > .dv-scene-dot { grid-area: 1 / 1; margin-top: 3px; }
.dv-s3-knox-label-span { grid-area: 1 / 2; }
.dv-s3-knox-label-matching { opacity: 0; }
.dv-s3-knox-label-done { opacity: 1; }

.dv-s3-match-chip {
    left: 220px;
    top: 84px;
    transform: translateX(-50%);
    opacity: 1;
    color: var(--dv-fg-2);
    border-color: var(--dv-border);
}

.dv-s3-status {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 10px;
    font-family: var(--dv-font-sans);
    font-size: 12px;
    color: var(--dv-fg-2);
    opacity: 1;
    text-align: center;
}

/* .dv-s3-match settles to a faint persistent connector (matches spec: the
   statement<->bank match line stays visible at 30% by the end frame). The
   drop line is a one-shot "the deposit just arrived" indicator with no
   persistent role — its own keyframe already ends at opacity:0, but that
   was being overridden by an unconditional base rule shared with
   .dv-s3-match, leaving a permanently-visible stray vertical stub above
   the bank card at rest and under reduced motion. */
.dv-s3-match { stroke: var(--dv-fg-3); opacity: 0.3; }
.dv-s3-drop { stroke: var(--dv-fg-3); opacity: 0; }

@media (prefers-reduced-motion: no-preference) {
    .dv-scene--3.is-playing .dv-s3-drop { animation: dv-s3-drop-line 3200ms linear both; }
    .dv-scene--3.is-playing .dv-s3-drop-chip { animation: dv-s3-drop-chip 3200ms linear both; offset-path: path('M300,8 L300,76'); }
    .dv-scene--3.is-playing .dv-s3-slot-empty { animation: dv-s3-slot-empty 3200ms linear both; }
    .dv-scene--3.is-playing .dv-s3-slot-filled { animation: dv-s3-slot-filled 3200ms linear both; }
    .dv-scene--3.is-playing .dv-s3-check-a { animation: dv-s3-check-a 3200ms linear both; }
    .dv-scene--3.is-playing .dv-s3-check-b { animation: dv-s3-check-b 3200ms linear both; }
    .dv-scene--3.is-playing .dv-s3-match { animation: dv-s3-match-line 3200ms var(--dv-ease-out) both; }
    .dv-scene--3.is-playing .dv-s3-knox-label-matching { animation: dv-s3-label-matching 3200ms linear both; }
    .dv-scene--3.is-playing .dv-s3-knox-label-done { animation: dv-s3-label-done 3200ms linear both; }
    .dv-scene--3.is-playing .dv-s3-match-chip { animation: dv-s3-match-chip 3200ms linear both; }
    .dv-scene--3.is-playing .dv-s3-status { animation: dv-scene-fade-in-81 3200ms linear both; }

    @keyframes dv-s3-drop-line {
        0%, 12.5% { opacity: 0; stroke-dashoffset: 1; }
        28.1% { opacity: 0.6; stroke-dashoffset: 0; }
        40%, 100% { opacity: 0; }
    }
    @keyframes dv-s3-drop-chip {
        0%, 12.5% { opacity: 0; offset-distance: 0%; }
        28.1% { opacity: 1; offset-distance: 100%; }
        30%, 100% { opacity: 0; offset-distance: 100%; }
    }
    @keyframes dv-s3-slot-empty { 0%, 28.1% { opacity: 1; } 28.2%, 100% { opacity: 0; } }
    @keyframes dv-s3-slot-filled { 0%, 28.1% { opacity: 0; } 28.2%, 100% { opacity: 1; } }
    @keyframes dv-s3-check-a { 0%, 62.5% { opacity: 0; } 62.6%, 100% { opacity: 1; } }
    @keyframes dv-s3-check-b { 0%, 62.5% { opacity: 0; } 62.6%, 100% { opacity: 1; } }
    @keyframes dv-s3-match-line {
        0%, 37.5% { opacity: 0; stroke-dashoffset: 1; stroke: var(--dv-fg-3); }
        60.9% { opacity: 0.8; stroke-dashoffset: 0; stroke: var(--dv-fg-2); }
        62.5% { opacity: 1; stroke: var(--dv-accent); }
        100% { opacity: 0.3; stroke: var(--dv-fg-3); }
    }
    @keyframes dv-s3-label-matching { 0%, 37.5% { opacity: 0; } 40%, 62.5% { opacity: 1; } 62.6%, 100% { opacity: 0; } }
    @keyframes dv-s3-label-done { 0%, 62.5% { opacity: 0; } 62.6%, 100% { opacity: 1; } }
    @keyframes dv-s3-match-chip {
        0%, 60.9% { opacity: 0; transform: translateX(-50%) translateY(4px); color: var(--dv-accent); border-color: color-mix(in srgb, var(--dv-accent) 45%, transparent); }
        62.5% { opacity: 1; transform: translateX(-50%) translateY(0); color: var(--dv-accent); border-color: color-mix(in srgb, var(--dv-accent) 45%, transparent); }
        100% { opacity: 1; transform: translateX(-50%) translateY(0); color: var(--dv-fg-2); border-color: var(--dv-border); }
    }
    @keyframes dv-scene-fade-in-81 { 0%, 81.25% { opacity: 0; } 87.5%, 100% { opacity: 1; } }
}

@supports not (offset-path: path('M0 0')) {
    .dv-s3-drop-chip { display: none; }
}

/* ═══ Slide 4: Onboarding ═════════════════════════════════════════════════
   Total action length 3.4s. 12 icon-only chips leave the two channels in
   scattered (non-reading) order and land across the 3x4 board; each row's
   4th piece reveals that row's label + value; the very last piece lands in
   a gold cell with a one-pass gold outline sweep. Base state below = the
   3.4s+ hold frame (full board, all labels lit, footer visible). */

.dv-s4-board {
    position: absolute;
    left: 190px;
    top: 20px;
    width: 250px;
    box-sizing: border-box;
    border: 1px solid var(--dv-border);
    border-radius: 6px;
    padding: 8px 8px 10px;
}

/* `minmax(0, 1fr)` (not bare `1fr`) on both this grid and `.dv-s4-row` below
   is required so the four data columns land at the SAME pixel width in both
   — a bare `1fr` track has an implicit min width of its content's
   min-content, so the wide "INSURANCE" head and the narrow icon cells below
   would otherwise size their tracks independently and drift out of column
   alignment. */
/* `gap: 4px` matches .dv-s4-row's own gap below — without it the header's
   columns had no breathing room between them (touching directly) AND
   didn't line up with the row grid's actual column boundaries, since a
   0-gap grid distributes all remaining space differently than a 4px-gap
   one across the same minmax(0, 1fr) tracks. */
.dv-s4-head {
    display: grid;
    grid-template-columns: 90px repeat(4, minmax(0, 1fr));
    gap: 4px;
    font-family: var(--dv-font-mono);
    font-size: 8.5px;
    letter-spacing: -0.06em;
    color: var(--dv-fg-3);
    text-align: center;
    overflow: hidden;
}

.dv-s4-head span {
    overflow: hidden;
    white-space: nowrap;
}

.dv-s4-head span:first-child { text-align: left; }

.dv-s4-row {
    display: grid;
    grid-template-columns: 90px repeat(4, minmax(0, 1fr));
    align-items: center;
    gap: 4px;
    margin-top: 8px;
    min-height: 40px;
}

/* The label column is sized for the longest address ("1420 Magnolia Ave")
   to wrap to at most 2 lines above its value line; the row's min-height
   above gives that 3-line worst case (2 name + 1 value) room without
   pushing the next row down into the footer below the board. */
.dv-s4-row-label {
    font-family: var(--dv-font-sans);
    font-size: 10px;
    line-height: 1.25;
    color: var(--dv-fg-1);
    display: flex;
    flex-direction: column;
}

.dv-s4-row-value {
    font-family: var(--dv-font-mono);
    font-size: 10px;
    color: var(--dv-fg-3);
    opacity: 1;
}

/* Base/resting state is the end frame (all 12 cells landed, per direction
   #2): solid elevated background, visible glyph. The keyframes below start
   each cell from the pre-fill look this comment used to hold (1px dashed
   `--dv-border`, transparent background, hidden glyph) and animate to this
   same resting style, so reduced motion — which never applies `.is-playing`
   — renders the full board with nothing extra to write. */
.dv-s4-cell {
    height: 26px;
    border: 1px solid var(--dv-border);
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--dv-bg-elevated);
    color: var(--dv-fg-2);
    font-size: 11px;
}

.dv-s4-footer {
    position: absolute;
    left: 190px;
    right: 0;
    bottom: 6px;
    font-family: var(--dv-font-sans);
    font-size: 11.5px;
    color: var(--dv-fg-2);
    opacity: 1;
}

/* 12 hand-authored short flight curves, one per document chip, from the
   two channel tiles to each board cell. Landing order is deliberately
   scattered (per spec example: cell 2-3 first, 1-1 third), not reading
   order — pieces arrive where they belong, not top-to-bottom. */
@media (prefers-reduced-motion: no-preference) {
    .dv-scene--4.is-playing .dv-s4-doc-1  { animation: dv-s4-doc-1  3400ms linear both; offset-path: path('M148,100 C185,95 205,95 262,88'); }
    .dv-scene--4.is-playing .dv-s4-doc-2  { animation: dv-s4-doc-2  3400ms linear both; offset-path: path('M148,105 C185,140 205,155 262,168'); }
    .dv-scene--4.is-playing .dv-s4-doc-3  { animation: dv-s4-doc-3  3400ms linear both; offset-path: path('M148,100 C190,80 210,65 262,58'); }
    .dv-scene--4.is-playing .dv-s4-doc-4  { animation: dv-s4-doc-4  3400ms linear both; offset-path: path('M148,105 C185,115 205,120 262,128'); }
    .dv-scene--4.is-playing .dv-s4-doc-5  { animation: dv-s4-doc-5  3400ms linear both; offset-path: path('M148,100 C195,90 380,65 388,58'); }
    .dv-scene--4.is-playing .dv-s4-doc-6  { animation: dv-s4-doc-6  3400ms linear both; offset-path: path('M148,105 C190,150 340,168 346,168'); }
    .dv-scene--4.is-playing .dv-s4-doc-7  { animation: dv-s4-doc-7  3400ms linear both; offset-path: path('M148,105 C195,120 380,128 388,128'); }
    .dv-scene--4.is-playing .dv-s4-doc-8  { animation: dv-s4-doc-8  3400ms linear both; offset-path: path('M148,100 C185,88 300,60 304,58'); }
    .dv-scene--4.is-playing .dv-s4-doc-9  { animation: dv-s4-doc-9  3400ms linear both; offset-path: path('M148,105 C185,150 300,168 304,168'); }
    .dv-scene--4.is-playing .dv-s4-doc-10 { animation: dv-s4-doc-10 3400ms linear both; offset-path: path('M148,105 C185,120 340,128 346,128'); }
    .dv-scene--4.is-playing .dv-s4-doc-11 { animation: dv-s4-doc-11 3400ms linear both; offset-path: path('M148,100 C195,70 340,60 346,58'); }
    .dv-scene--4.is-playing .dv-s4-doc-12 { animation: dv-s4-doc-12 3400ms linear both; offset-path: path('M148,105 C195,150 385,168 388,168'); }

    /* Twelve short, explicit keyframes (one per landing beat) keep every
       cell's fill timed to its own chip's arrival without any JS per-frame
       work — CSS keyframes can't reference a per-element time offset, so
       each cell gets its own named keyframe instead of a shared one. */
    .dv-scene--4.is-playing .dv-s4-cell {
        animation-duration: 3400ms;
        animation-timing-function: linear;
        animation-fill-mode: both;
    }
    .dv-scene--4.is-playing .dv-s4-cell-2-3 { animation-name: dv-s4-fill-25; }
    .dv-scene--4.is-playing .dv-s4-cell-3-1 { animation-name: dv-s4-fill-30; }
    .dv-scene--4.is-playing .dv-s4-cell-1-1 { animation-name: dv-s4-fill-36; }
    .dv-scene--4.is-playing .dv-s4-cell-2-1 { animation-name: dv-s4-fill-41; }
    .dv-scene--4.is-playing .dv-s4-cell-1-4 { animation-name: dv-s4-fill-46; }
    .dv-scene--4.is-playing .dv-s4-cell-3-3 { animation-name: dv-s4-fill-52; }
    .dv-scene--4.is-playing .dv-s4-cell-2-4 { animation-name: dv-s4-fill-57; }
    .dv-scene--4.is-playing .dv-s4-cell-1-2 { animation-name: dv-s4-fill-62; }
    .dv-scene--4.is-playing .dv-s4-cell-3-2 { animation-name: dv-s4-fill-67; }
    .dv-scene--4.is-playing .dv-s4-cell-2-2 { animation-name: dv-s4-fill-73; }
    .dv-scene--4.is-playing .dv-s4-cell-1-3 { animation-name: dv-s4-fill-78; }
    .dv-scene--4.is-playing .dv-s4-cell-3-4 { animation-name: dv-s4-fill-85-gold; }

    @keyframes dv-s4-fill-25  { 0%, 25%   { background: transparent; border-style: dashed; color: transparent; } 25.1%, 100% { background: var(--dv-bg-elevated); border-style: solid; color: var(--dv-fg-2); } }
    @keyframes dv-s4-fill-30  { 0%, 30.3% { background: transparent; border-style: dashed; color: transparent; } 30.4%, 100% { background: var(--dv-bg-elevated); border-style: solid; color: var(--dv-fg-2); } }
    @keyframes dv-s4-fill-36  { 0%, 35.6% { background: transparent; border-style: dashed; color: transparent; } 35.7%, 100% { background: var(--dv-bg-elevated); border-style: solid; color: var(--dv-fg-2); } }
    @keyframes dv-s4-fill-41  { 0%, 40.9% { background: transparent; border-style: dashed; color: transparent; } 41%,   100% { background: var(--dv-bg-elevated); border-style: solid; color: var(--dv-fg-2); } }
    @keyframes dv-s4-fill-46  { 0%, 46.2% { background: transparent; border-style: dashed; color: transparent; } 46.3%, 100% { background: var(--dv-bg-elevated); border-style: solid; color: var(--dv-fg-2); } }
    @keyframes dv-s4-fill-52  { 0%, 51.5% { background: transparent; border-style: dashed; color: transparent; } 51.6%, 100% { background: var(--dv-bg-elevated); border-style: solid; color: var(--dv-fg-2); } }
    @keyframes dv-s4-fill-57  { 0%, 56.8% { background: transparent; border-style: dashed; color: transparent; } 56.9%, 100% { background: var(--dv-bg-elevated); border-style: solid; color: var(--dv-fg-2); } }
    @keyframes dv-s4-fill-62  { 0%, 62%   { background: transparent; border-style: dashed; color: transparent; } 62.1%, 100% { background: var(--dv-bg-elevated); border-style: solid; color: var(--dv-fg-2); } }
    @keyframes dv-s4-fill-67  { 0%, 67.4% { background: transparent; border-style: dashed; color: transparent; } 67.5%, 100% { background: var(--dv-bg-elevated); border-style: solid; color: var(--dv-fg-2); } }
    @keyframes dv-s4-fill-73  { 0%, 72.6% { background: transparent; border-style: dashed; color: transparent; } 72.7%, 100% { background: var(--dv-bg-elevated); border-style: solid; color: var(--dv-fg-2); } }
    @keyframes dv-s4-fill-78  { 0%, 77.9% { background: transparent; border-style: dashed; color: transparent; } 78%,   100% { background: var(--dv-bg-elevated); border-style: solid; color: var(--dv-fg-2); } }
    @keyframes dv-s4-fill-85-gold {
        0%, 85.3% { background: transparent; border-style: dashed; color: transparent; }
        85.4% { background: var(--dv-accent-muted); border-color: var(--dv-accent); border-style: solid; color: var(--dv-fg-2); }
        97%, 100% { background: var(--dv-bg-elevated); border-color: var(--dv-border); border-style: solid; color: var(--dv-fg-2); }
    }

    @keyframes dv-s4-doc-1  { 0%, 8.8%  { opacity: 0; offset-distance: 0%; } 24.3% { opacity: 1; offset-distance: 100%; } 25%, 100% { opacity: 0; offset-distance: 100%; } }
    @keyframes dv-s4-doc-2  { 0%, 14.1% { opacity: 0; offset-distance: 0%; } 29.6% { opacity: 1; offset-distance: 100%; } 30.3%, 100% { opacity: 0; offset-distance: 100%; } }
    @keyframes dv-s4-doc-3  { 0%, 19.4% { opacity: 0; offset-distance: 0%; } 34.9% { opacity: 1; offset-distance: 100%; } 35.6%, 100% { opacity: 0; offset-distance: 100%; } }
    @keyframes dv-s4-doc-4  { 0%, 24.7% { opacity: 0; offset-distance: 0%; } 40.2% { opacity: 1; offset-distance: 100%; } 40.9%, 100% { opacity: 0; offset-distance: 100%; } }
    @keyframes dv-s4-doc-5  { 0%, 30%   { opacity: 0; offset-distance: 0%; } 45.5% { opacity: 1; offset-distance: 100%; } 46.2%, 100% { opacity: 0; offset-distance: 100%; } }
    @keyframes dv-s4-doc-6  { 0%, 35.3% { opacity: 0; offset-distance: 0%; } 50.8% { opacity: 1; offset-distance: 100%; } 51.5%, 100% { opacity: 0; offset-distance: 100%; } }
    @keyframes dv-s4-doc-7  { 0%, 40.6% { opacity: 0; offset-distance: 0%; } 56.1% { opacity: 1; offset-distance: 100%; } 56.8%, 100% { opacity: 0; offset-distance: 100%; } }
    @keyframes dv-s4-doc-8  { 0%, 45.9% { opacity: 0; offset-distance: 0%; } 61.3% { opacity: 1; offset-distance: 100%; } 62%,   100% { opacity: 0; offset-distance: 100%; } }
    @keyframes dv-s4-doc-9  { 0%, 51.2% { opacity: 0; offset-distance: 0%; } 66.7% { opacity: 1; offset-distance: 100%; } 67.4%, 100% { opacity: 0; offset-distance: 100%; } }
    @keyframes dv-s4-doc-10 { 0%, 56.5% { opacity: 0; offset-distance: 0%; } 72%   { opacity: 1; offset-distance: 100%; } 72.6%, 100% { opacity: 0; offset-distance: 100%; } }
    @keyframes dv-s4-doc-11 { 0%, 61.8% { opacity: 0; offset-distance: 0%; } 77.3% { opacity: 1; offset-distance: 100%; } 77.9%, 100% { opacity: 0; offset-distance: 100%; } }
    @keyframes dv-s4-doc-12 { 0%, 69.1% { opacity: 0; offset-distance: 0%; } 84.7% { opacity: 1; offset-distance: 100%; } 85.3%, 100% { opacity: 0; offset-distance: 100%; } }

    .dv-scene--4.is-playing .dv-s4-row-1 .dv-s4-row-label,
    .dv-scene--4.is-playing .dv-s4-row-2 .dv-s4-row-label,
    .dv-scene--4.is-playing .dv-s4-row-3 .dv-s4-row-label {
        color: var(--dv-fg-3);
        animation: dv-s4-label-lit 3400ms linear both;
    }
    .dv-scene--4.is-playing .dv-s4-row-1 .dv-s4-row-label { animation-name: dv-s4-label-78; }
    .dv-scene--4.is-playing .dv-s4-row-2 .dv-s4-row-label { animation-name: dv-s4-label-73; }
    .dv-scene--4.is-playing .dv-s4-row-3 .dv-s4-row-label { animation-name: dv-s4-label-85; }
    .dv-scene--4.is-playing .dv-s4-row-1 .dv-s4-row-value { animation: dv-s4-value-78 3400ms linear both; }
    .dv-scene--4.is-playing .dv-s4-row-2 .dv-s4-row-value { animation: dv-s4-value-73 3400ms linear both; }
    .dv-scene--4.is-playing .dv-s4-row-3 .dv-s4-row-value { animation: dv-s4-value-85 3400ms linear both; }

    @keyframes dv-s4-label-78 { 0%, 77.9% { color: var(--dv-fg-3); } 78%, 100% { color: var(--dv-fg-1); } }
    @keyframes dv-s4-label-73 { 0%, 72.6% { color: var(--dv-fg-3); } 72.7%, 100% { color: var(--dv-fg-1); } }
    @keyframes dv-s4-label-85 { 0%, 85.3% { color: var(--dv-fg-3); } 85.4%, 100% { color: var(--dv-fg-1); } }
    @keyframes dv-s4-value-78 { 0%, 77.9% { opacity: 0; } 78%, 100% { opacity: 1; } }
    @keyframes dv-s4-value-73 { 0%, 72.6% { opacity: 0; } 72.7%, 100% { opacity: 1; } }
    @keyframes dv-s4-value-85 { 0%, 85.3% { opacity: 0; } 85.4%, 100% { opacity: 1; } }

    .dv-scene--4.is-playing .dv-s4-footer { animation: dv-scene-fade-in-85 3400ms linear both; }
    @keyframes dv-scene-fade-in-85 { 0%, 85.3% { opacity: 0; } 91.2%, 100% { opacity: 1; } }
}

@supports not (offset-path: path('M0 0')) {
    .dv-s4-doc-1, .dv-s4-doc-2, .dv-s4-doc-3, .dv-s4-doc-4, .dv-s4-doc-5, .dv-s4-doc-6,
    .dv-s4-doc-7, .dv-s4-doc-8, .dv-s4-doc-9, .dv-s4-doc-10, .dv-s4-doc-11, .dv-s4-doc-12 {
        display: none;
    }
}

/* ── Right eyebrow / headline / subhead ─────────────────────────────────── */
/* `.dv-login__right-text` is a CSS grid where all four pairs share the
   same cell (grid-area 1/1) so the row auto-sizes to whichever pair is
   tallest AT THE CURRENT WIDTH. That permanently fixes the fixed-171px
   text-slot overflow bug from the round-1 flagship review (960-1024px
   widths): with a fixed height, the longest pair (Knox Inbox) overflowed
   onto the progress bar. Only one pair is opaque/visible at a time; both
   the outgoing and incoming pair are briefly in the DOM together during
   the 200/250ms crossfade, per spec. */

.dv-login__right-text {
    display: grid;
    margin-top: 40px;
}

.dv-login__right-text-pair {
    grid-area: 1 / 1;
    opacity: 0;
    visibility: hidden;
    transition: opacity 200ms ease, visibility 0s linear 200ms;
}

.dv-login__right-text-pair--active {
    opacity: 1;
    visibility: visible;
    transition: opacity 250ms ease;
}

.dv-login__right-eyebrow {
    font-family: var(--dv-font-mono);
    font-size: 11px;
    letter-spacing: 0.08em;
    color: var(--dv-fg-3);
    margin-bottom: 10px;
}

.dv-login__right-headline {
    font-family: var(--dv-font-display);
    font-weight: 600;
    font-size: 40px;
    line-height: 1.08;
    letter-spacing: -0.035em;
    max-width: 520px;
    color: var(--dv-fg-1);
    margin: 0;
}

.dv-login__right-headline em {
    font-style: italic;
    color: var(--dv-accent);
}

.dv-login__right-subhead {
    font-family: var(--dv-font-sans);
    font-size: 16px;
    color: var(--dv-fg-2);
    margin: 12px 0 0;
    max-width: 480px;
}

/* ── Progress bar pagination: 4 segments, 8s dwell fill ─────────────────── */

.dv-progress {
    display: flex;
    gap: 8px;
    margin-top: 24px;
}

.dv-progress__segment {
    width: 40px;
    height: 2px;
    padding: 0;
    border: none;
    background: rgba(255, 255, 255, 0.12);
    cursor: pointer;
    position: relative;
    overflow: hidden;
}

.dv-progress__segment::after {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--dv-fg-1);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 300ms linear;
}

/* The active segment fills 0 -> 100% over the full 8s dwell (matching the
   round-3 dwell time, up from 6s), linear, not a fast tab-indicator snap.
   `.dv-progress.is-paused` / `.dv-scene.is-paused` freeze via
   `animation-play-state: paused`, kept in lockstep by the shared
   hover/focus/tab-hidden pause logic in the JS below. */
@keyframes dv-progress-fill {
    from { transform: scaleX(0); }
    to { transform: scaleX(1); }
}

.dv-progress__segment--active::after {
    transform: scaleX(1);
    animation: dv-progress-fill 8000ms linear forwards;
}

.dv-progress.is-paused .dv-progress__segment--active::after {
    animation-play-state: paused;
}

.dv-scene.is-paused,
.dv-scene.is-paused * {
    animation-play-state: paused !important;
}

/* ── Motion: load-in + Knox pulse (autoplay only, never under reduced motion) */

@media (prefers-reduced-motion: no-preference) {
    @keyframes dv-fade-up {
        from { opacity: 0; transform: translateY(8px); }
        to { opacity: 1; transform: translateY(0); }
    }

    .dv-login__left-inner {
        animation: dv-fade-up 300ms ease-out both;
    }

    @keyframes dv-knox-pulse {
        0%, 100% { opacity: 1; }
        50% { opacity: 0.35; }
    }

    .dv-scene-dot--knox {
        animation: dv-knox-pulse 2s ease-in-out infinite;
    }
}

@media (prefers-reduced-motion: reduce) {
    .dv-login__right-text-pair,
    .dv-login__right-text-pair--active {
        transition: none;
    }

    /* Scene crossfade becomes an instant swap; no beat animation is ever
       applied (the `.is-playing` selectors above all live inside the
       `no-preference` media query), so every scene simply renders its
       base/resting styles — which are already the exact end frames
       specified for each slide. The Knox dot never pulses (no keyframe
       exists for it outside the `no-preference` block above). */
    .dv-scene {
        transition: none;
    }

    .dv-progress__segment::after {
        transition: none;
    }

    .dv-progress__segment--active::after {
        animation: none;
        transform: scaleX(1);
    }
}

/* ── Responsive: right panel (and the whole animated stage) removed
   entirely below 960px. The JS below gates its ResizeObserver, autoplay
   interval and pause listeners behind `matchMedia('(min-width: 960px)')`
   so a mobile visitor's browser sets none of that up in the first place —
   this rule is the visual backstop, not the only mechanism. ───────────── */

@media (max-width: 959.98px) {
    .dv-login {
        display: block;
    }

    .dv-login__panel--right {
        display: none;
    }

    .dv-login__panel--left {
        background: var(--dv-bg-sidebar);
        border-right: none;
        min-height: var(--dv-app-vh, 100vh);
        min-height: var(--dv-app-dvh, 100dvh);
        display: block;
        padding-top: 40px;
    }

    .dv-login__left-inner {
        width: 100%;
        max-width: 400px;
        margin: 0 auto;
        padding: 0 24px;
    }
}
