/*
 * Analyze scenes (2026-10) - layout and motion for /app's scroll story.
 * Loaded only by index.html (base.html's head_extra block); the JS half
 * is analyze_scenes.js - see its docstring for the reveal/whenVisible
 * model these rules pair with.
 *
 * Unlayered on purpose, like style.css: rules here outrank Tailwind's
 * @layer utilities, so they only ever target scene-specific classes and
 * hooks, never bare utility classes.
 *
 * Motion policy: everything that hides content waits for `scenes-ready`
 * (set by analyze_scenes.js, never under reduced motion), so no-JS,
 * reduced-motion and print readers always get plain visible content.
 */

/* ------------------------------------------------------------------
   Story + scene shell
   ------------------------------------------------------------------ */
/* The horizon accent is a registered colour, so a horizon switch
   crossfades every accent at once - kickers, titles, aura, rail, dock -
   instead of snapping. Canvases read the fixed --color-horizon-* tokens
   instead: a mid-fade value must never be baked into a chart. */
@property --scene-accent {
    syntax: "<color>";
    inherits: true;
    initial-value: #a78bfa;
}
/* Room the docked horizon bar takes under the nav (analyze_wayfinding.css):
   jump targets and keyboard focus clear it (WCAG 2.4.11), and the "own
   page" scenes size to the space that's actually left. */
:root { --dock-space: 3.5rem; }
html { scroll-padding-top: calc(var(--nav-h, 8rem) + var(--dock-space) + 0.5rem); }
/* While a horizon switch holds the reader's place (analyze_wayfinding.js),
   the browser's own scroll anchoring stands down: two hands on the
   scroll position would double every correction. */
html.is-holding-place { overflow-anchor: none; }

.analyze-story {
    --scene-accent: var(--color-horizon-swing);
    position: relative;
    isolation: isolate; /* the aura below stays behind this story only */
    transition: --scene-accent 0.7s ease;
}
.analyze-story[data-horizon="day"] { --scene-accent: var(--color-horizon-day); }
.analyze-story[data-horizon="invest"] { --scene-accent: var(--color-horizon-invest); }
.analyze-story ::selection { background: color-mix(in srgb, var(--scene-accent) 38%, transparent); color: #fff; }

/* Horizon light: the page itself glows in the active horizon's colour,
   so even the edges of the screen say which lens the scenes are read in. */
.analyze-story::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background:
        radial-gradient(52rem 30rem at 92% -8%, color-mix(in srgb, var(--scene-accent) 12%, transparent), transparent 70%),
        radial-gradient(44rem 28rem at -6% 108%, color-mix(in srgb, var(--scene-accent) 8%, transparent), transparent 70%);
}

/* A switch is in flight: until the new horizon's read lands, the scenes
   still show the old one, so they step back (and the story is aria-busy)
   - stale numbers never pose as the new horizon's. */
.analyze-scene { transition: opacity 0.3s ease; }
.analyze-story.is-switching .analyze-scene:not(.analyze-scene--horizons) { opacity: 0.4; }
/* A section cleared for the incoming horizon (analyze_fragment_recovery.js
   holds its footprint) shows the wait itself, in the new horizon's colour,
   riding mid-screen wherever the reader is inside it. */
.analyze-story [aria-busy="true"]:empty::before {
    content: "";
    position: sticky;
    top: 50%;
    display: block;
    width: 2.25rem;
    height: 2.25rem;
    margin: 3rem auto;
    border-radius: 9999px;
    border: 2px solid color-mix(in srgb, var(--scene-accent) 25%, transparent);
    border-top-color: var(--scene-accent);
    animation: scene-wait 0.8s linear infinite;
}
@keyframes scene-wait { to { transform: rotate(360deg); } }

.analyze-scene {
    position: relative;
    padding-block: clamp(2.5rem, 6.5vh, 5rem);
}
.analyze-scene:first-of-type { padding-top: 0; } /* the rail <nav> precedes the scenes */
/* Programmatic focus target for chapter jumps (tabindex="-1"), never a
   tab stop - same treatment base.html gives <main>. */
.analyze-scene:focus { outline: none; }
.analyze-scene + .analyze-scene::before {
    content: "";
    position: absolute;
    top: 0;
    left: 8%;
    right: 8%;
    height: 1px;
    background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--scene-accent) 38%, transparent), transparent);
}

/* "Almost its own page": core scenes claim a full viewport under the
   sticky nav on desktop-sized screens. Short or narrow screens keep
   content-driven height - forcing 100vh there only adds scrolling. */
@media (min-width: 1024px) and (min-height: 680px) {
    .analyze-scene--full {
        min-height: calc(100vh - var(--nav-h, 4rem) - var(--dock-space));
        min-height: calc(100svh - var(--nav-h, 4rem) - var(--dock-space));
        display: flex;
        flex-direction: column;
        justify-content: center;
    }
}

/* ------------------------------------------------------------------
   Scene header - kicker / title / lede, shared by every scene
   ------------------------------------------------------------------ */
.scene-header {
    max-width: 52rem;
    margin-bottom: clamp(1.25rem, 3.5vh, 2.5rem);
}
.scene-kicker {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: var(--scene-accent);
}
.scene-kicker-num {
    font-family: var(--font-mono);
    letter-spacing: 0.06em;
    color: rgb(148 163 184);
}
.scene-kicker-rule {
    width: 2.5rem;
    height: 1px;
    background: currentColor;
    opacity: 0.55;
}
.scene-title {
    margin-top: 0.65rem;
    font-size: clamp(2rem, 1.1rem + 3.2vw, 3.9rem);
    line-height: 1.02;
    font-weight: 900;
    letter-spacing: -0.025em;
    color: #fff;
    text-wrap: balance;
}
.scene-title-accent {
    font-family: var(--font-serif);
    font-style: italic;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--scene-accent);
    padding-right: 0.06em; /* italic overhang */
}
.scene-lede {
    margin-top: 0.9rem;
    /* Wide enough that a one-sentence lede stays on one line on desktop,
       which keeps each scene's main visual inside the first viewport. */
    max-width: 50rem;
    font-size: clamp(0.95rem, 0.85rem + 0.35vw, 1.08rem);
    line-height: 1.6;
    color: rgb(148 163 184);
    text-wrap: pretty;
}

/* ------------------------------------------------------------------
   Reveal - [data-reveal] blocks ease in on first scroll into view.
   The revealed state declares NO transform on purpose: any transform
   (even identity) would turn the block into a containing block for
   position:fixed descendants (chart fullscreen, tooltips).
   ------------------------------------------------------------------ */
.scenes-ready [data-reveal] {
    transition:
        opacity 0.7s cubic-bezier(0.2, 0.7, 0.2, 1),
        transform 0.95s cubic-bezier(0.16, 1, 0.3, 1);
    transition-delay: calc(var(--reveal-i, 0) * 120ms);
}
.scenes-ready [data-reveal]:not(.is-revealed) {
    opacity: 0;
    transform: translate3d(0, 2.25rem, 0);
}
.scenes-ready [data-reveal="zoom"]:not(.is-revealed) { transform: scale(0.9); }

/* Cascade - the block itself stays put while its children deal in one
   after another (tile grids, peer cards). analyze_scenes.js gives each
   child its --cascade-delay, then marks the block is-settled so the
   children's own transitions (hover lifts) take over again. As above,
   nothing is transformed at rest. */
.scenes-ready [data-reveal="cascade"]:not(.is-revealed) { opacity: 1; transform: none; }
.scenes-ready [data-reveal="cascade"]:not(.is-settled) > * {
    transition:
        opacity 0.55s cubic-bezier(0.2, 0.7, 0.2, 1),
        transform 0.75s cubic-bezier(0.16, 1, 0.3, 1);
    transition-delay: var(--cascade-delay, 0ms);
}
.scenes-ready [data-reveal="cascade"]:not(.is-revealed) > * {
    opacity: 0;
    transform: translate3d(0, 1rem, 0) scale(0.96);
}

/* Scan - the price stack wipes in left to right, as if the history were
   being drawn, with a glowing edge in the horizon accent riding the
   wipe. analyze_scenes.js drives one --scan value per frame for both
   the clip and the edge (so they stay in lockstep), and clip-path
   exists only while is-scanning is set: at rest the stack is unclipped,
   so chart fullscreen and focus rings can never be cut off by it. */
.chart-scan { position: relative; }
.scenes-ready [data-reveal="scan"]:not(.is-revealed) {
    opacity: 1;
    transform: none;
    clip-path: inset(0 100% 0 0);
}
.scenes-ready [data-reveal="scan"].is-scanning { clip-path: inset(0 calc(100% - var(--scan, 0%)) 0 0); }
.scenes-ready [data-reveal="scan"]::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.35s ease-out;
    background: linear-gradient(90deg,
        transparent calc(100% - 8rem),
        color-mix(in srgb, var(--scene-accent) 14%, transparent) calc(100% - 3px),
        var(--scene-accent) calc(100% - 3px));
}
.scenes-ready [data-reveal="scan"].is-scanning::after {
    opacity: 1;
    transform: translateX(calc(var(--scan, 0%) - 100%));
}

/* Transform-only on purpose: the horizon cards are the page's FIRST
   content, so they land at full opacity - full contrast on every frame
   (axe audits right after load) and no fade delaying the five-second
   read. Only the motion is decorative. */
@keyframes scene-lift {
    from { transform: translate3d(0, 1.5rem, 0) scale(0.97); }
    to { transform: none; }
}

/* Chapter rail + horizon dock: analyze_wayfinding.css. */

/* Scroll cue under the opening scene (chevron bounce: style.css's
   .lp-scroll-cue, which already honors reduced motion). */
.scene-cue {
    display: flex;
    width: max-content;
    flex-direction: column;
    align-items: center;
    gap: 0.35rem;
    margin: clamp(1.5rem, 5vh, 3rem) auto 0;
    padding: 0.4rem 0.9rem;
    border-radius: 9999px;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: rgb(148 163 184);
    transition: color 0.2s ease;
}
.scene-cue:hover { color: #fff; }
.scene-cue:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
}

/* ------------------------------------------------------------------
   Scene 1 - Three Horizons. Scoped: horizon_view.html is shared with
   the landing demo, which must keep its compact look (hv-* hooks are
   inert outside a scene).
   ------------------------------------------------------------------ */
.analyze-scene--horizons .hv-title {
    font-size: clamp(2.1rem, 1.1rem + 3.6vw, 4.25rem);
    line-height: 1.02;
    font-weight: 900;
    letter-spacing: -0.025em;
    color: #fff;
    text-wrap: balance;
}
.analyze-scene--horizons .hv-lede {
    font-size: clamp(0.95rem, 0.85rem + 0.45vw, 1.15rem);
    color: rgb(148 163 184);
    max-width: 44rem;
}
.analyze-scene--horizons .hv-cards { gap: clamp(0.75rem, 1.6vw, 1.5rem); }
.analyze-scene--horizons .hv-card {
    padding: clamp(1.1rem, 2.4vw, 2rem) clamp(0.9rem, 1.6vw, 1.5rem);
    border-radius: 1.25rem;
    /* Top-packed wrapping row: block rows span the width, the stat
       chips (hv-meta) flow inline. Buttons vertically CENTER their
       content by default, which left cards with a shorter stat line
       visibly lower than their neighbours at this size. */
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-content: flex-start;
    /* backwards (not both): once finished, the card's own hover lift
       (.card-hover-lift) must own transform again. */
    animation: scene-lift 0.85s cubic-bezier(0.16, 1, 0.3, 1) 0.05s backwards;
}
.analyze-scene--horizons .hv-card > :not(.hv-meta) { flex-basis: 100%; }
.analyze-scene--horizons .hv-meta > span { font-size: 0.75rem; }
.analyze-scene--horizons .hv-card:nth-child(2) { animation-delay: 0.17s; }
.analyze-scene--horizons .hv-card:nth-child(3) { animation-delay: 0.29s; }
.analyze-scene--horizons .hv-card-label {
    font-size: 0.72rem;
    letter-spacing: 0.2em;
}
.analyze-scene--horizons .hv-score {
    font-size: clamp(2.9rem, 1.6rem + 4.2vw, 5rem);
    line-height: 1;
    letter-spacing: -0.03em;
    margin-block: 0.4rem 0.3rem;
    font-variant-numeric: tabular-nums;
}
.analyze-scene--horizons .hv-rating { font-size: 0.95rem; }
.analyze-scene--horizons .hv-alignment { font-size: clamp(0.95rem, 0.9rem + 0.25vw, 1.1rem); }

/* ------------------------------------------------------------------
   Scene 2 - Verdict: the gauge stage's rules are analyze_dial.css.
   ------------------------------------------------------------------ */

/* ------------------------------------------------------------------
   Scene 3 - Company Pillars: the ladder, tour chapters and card masonry
   are analyze_pillars.css.
   ------------------------------------------------------------------ */

/* ------------------------------------------------------------------
   Scene 2 - the Verdict's Risk outlook (risk_outlook.html), under the
   trade levels. It opened the Score Pool scene until that retired
   (2026-11-01). The wash follows the horizon, like the rest of the scene.
   ------------------------------------------------------------------ */
.risk-outlook {
    position: relative;
    border-radius: 1.5rem;
    border: 1px solid var(--color-border);
    background: var(--color-panel); /* fallback where color-mix() is unsupported */
    background:
        radial-gradient(120% 80% at 50% 100%, color-mix(in srgb, var(--scene-accent) 12%, transparent), transparent 70%),
        var(--color-panel);
    overflow: hidden;
}
/* A stat sizes to its own card, not the viewport: the watchlist rail
   narrows the cards while the viewport stays wide. No color here - this
   unlayered rule would beat a template's Tailwind color. */
.outlook-stat { container-type: inline-size; }
.outlook-stat-value {
    font-family: var(--font-mono);
    font-size: min(clamp(1.35rem, 1rem + 1.2vw, 2rem), 15cqi);
    font-weight: 700;
    line-height: 1.1;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.outlook-copy { text-wrap: pretty; } /* its sentences never strand a last word */
/* Band words in the Risk zone colors; the ruler puts all three bands'
   deepest drops on one scale (deepest = faintest). Other bands dim their
   BARS only - dimming the row would sink its text below AA contrast. */
.risk-band[data-band="Low"] { color: #3ddc97; }
.risk-band[data-band="Moderate"] { color: #fbbf24; }
.risk-band[data-band="Elevated"] { color: #fb7185; }
.outlook-ruler { display: grid; gap: 0.5rem; }
.outlook-ruler-row {
    display: grid;
    gap: 0.25rem 1rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid transparent;
    border-radius: 0.75rem;
}
.outlook-ruler-row.is-current {
    border-color: color-mix(in srgb, var(--scene-accent) 45%, transparent);
    background: color-mix(in srgb, var(--scene-accent) 8%, transparent);
}
.outlook-ruler-row:not(.is-current) .outlook-ruler-track { opacity: 0.5; }
.outlook-ruler-now {
    margin-left: 0.35rem;
    padding: 0.05rem 0.45rem;
    border-radius: 9999px;
    background: color-mix(in srgb, var(--scene-accent) 25%, transparent);
    color: #fff;
    font-size: 0.6875rem;
    font-weight: 600;
}
/* The live read, once it has left the official band: the same chip,
   outlined not filled - both readings on one ruler, without implying the
   outlook moved with it. An inset ring, not a border: same size as its twin. */
.outlook-ruler-now.is-live {
    background: transparent;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--scene-accent) 70%, transparent);
}
.outlook-ruler-seg { fill: #fb7185; }
.outlook-ruler-seg[data-seg="drop_1in10_pct"] { fill-opacity: 0.22; }
.outlook-ruler-seg[data-seg="drop_1in4_pct"] { fill-opacity: 0.45; }
.outlook-ruler-seg[data-seg="drop_typical_pct"] { fill-opacity: 0.9; }
.outlook-ruler-axis { padding: 0 calc(0.75rem + 1px); } /* lines up with the tracks */
.outlook-ruler-axis text { fill: #94a3b8; font-size: 11px; }
@media (min-width: 640px) {
    .outlook-ruler-row { grid-template-columns: 9.5rem minmax(0, 1fr); align-items: center; }
    .outlook-ruler-values { grid-column: 2; }
    .outlook-ruler-axis { padding-left: calc(9.5rem + 1rem + 0.75rem + 1px); }
}

/* ------------------------------------------------------------------
   Reduced motion + print
   ------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
    .analyze-story,
    .analyze-scene { transition: none; }
    /* Purely a motion effect (style.css's .hx-swap-fade ring precedent):
       the step-back and aria-busy carry the wait on their own. */
    .analyze-story [aria-busy="true"]:empty::before { display: none; }
    .analyze-scene--horizons .hv-card { animation: none; }
}
@media print {
    .scene-cue { display: none !important; }
    [data-reveal] {
        opacity: 1 !important;
        transform: none !important;
        clip-path: none !important;
        animation: none !important;
    }
    [data-reveal="cascade"] > * {
        opacity: 1 !important;
        transform: none !important;
    }
    [data-reveal="scan"]::after { display: none !important; }
}
