/* ============================================================
   Intro page — "Modal" terminal skin
   ------------------------------------------------------------
   A CRT-terminal aesthetic for the landing page only: a void-black
   canvas, pale blue-white type, and a single rationed Semos-blue accent
   that behaves like an LED status light. The accent is the app's own
   --cf-primary (#1150E6) so the intro reads as the same product as the
   rest of the site; --volt-lit is that blue lifted for legibility as
   *type* on black, where the brand blue alone is too dark.

   Both themes: every colour on the page comes from the token block below,
   so the light palette is a second token block (search "LIGHT THEME") that
   flips the page when the visitor picks Light in Settings. Only the handful
   of rules that can't be expressed as a token — the translucent navbar, the
   inverted logo, a couple of hover fills — are restated there.

   EVERYTHING here is scoped under `body.modal-intro`, set by home.html
   via base.html's {% block body_class %}. No rule leaks to the rest of
   the Semos-blue app. This file loads after theme.css, so a
   `.modal-intro .thing` selector (two classes) reliably outranks the
   theme's `[data-bs-theme] .thing`.

   Substitutes: Modal's display face is Goga (unavailable, and no CDN
   fonts allowed by our CSP). Its documented fallback is a tight
   geometric sans — we use the self-hosted Inter at weight 500 with the
   spec's negative tracking, which is what carries the "Goga" feel here.
   Code windows use JetBrains Mono (already self-hosted).
   ============================================================ */

body.modal-intro {
    /* ── Palette (dark — the app's default theme) ─────────── */
    --void:      #000000;
    --ground:    #0f1218;
    --carbon:    #161b24;
    --circuit:   #33415c;
    --blueblack: #16203a;
    --rust:      #1b1f27;
    --volt:      #1150e6;   /* the app's --cf-primary: solid fills, white type on top */
    --volt-lit:  #6aa5ff;   /* the same blue lit for type on black: LEDs, carets, keywords */
    --volt-rgb:  106, 165, 255;
    --phosphor:  #dceaff;
    --mint:      #e4ecff;
    --sage60:    #8ba3c7;
    --sage40:    #62779b;
    --moss70:    #9db4d8;
    --moss80:    #b4c8e8;
    --fern:      #8496b3;
    --deepfern:  #66788f;
    --pine15:    #3c4a63;

    --mi-display: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --mi-mono:    'JetBrains Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;

    /* One easing for every colour/state change, per the spec's
       "single coherent feel" rule. */
    --mi-ease:   cubic-bezier(0.4, 0, 0.2, 1);
    --mi-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

    background: var(--void) !important;
    background-image: none !important;
    color: var(--sage60);
    font-family: var(--mi-display);
    font-feature-settings: 'cv11' 1;   /* Inter alt punctuation — part of the identity */
    -webkit-font-smoothing: antialiased;
}

/* ─────────────────────────────────────────────────────────
   LIGHT THEME — the same page with the void turned inside out
   ------------------------------------------------------------
   base.html stamps data-bs-theme on <html> from the visitor's Settings
   choice, so this block is all it takes to flip the page. Two notes on the
   values: --volt-lit drops back to the brand blue, because the lifted
   #6aa5ff was chosen to carry on black and is far too pale to read as type
   on white; and --phosphor (headings, code ink) becomes near-black ink,
   which is what makes the "phosphor" names keep working in reverse.
   ───────────────────────────────────────────────────────── */
[data-bs-theme="light"] body.modal-intro {
    --void:      #ffffff;
    --ground:    #f4f7fc;   /* cards, code window */
    --carbon:    #e9eff8;   /* code title bar, icon tiles */
    --circuit:   #c6d3e8;   /* borders */
    --blueblack: #dde5f2;   /* hairlines */
    --rust:      #eef2f8;
    --volt:      #1150e6;
    --volt-lit:  #1150e6;
    --volt-rgb:  17, 80, 230;
    --phosphor:  #0b1830;   /* ink */
    --mint:      #e4ecff;
    --sage60:    #4a5b75;
    --sage40:    #6b7d99;
    --moss70:    #52678a;
    --moss80:    #3f5375;
    --fern:      #56698a;
    --deepfern:  #6b7d99;
    --pine15:    #c6d3e8;
}

/* The few rules a token can't reach. */
[data-bs-theme="light"] body.modal-intro .cf-navbar {
    background: rgba(255, 255, 255, 0.72);
}
[data-bs-theme="light"] body.modal-intro .cf-navbar.cf-scrolled {
    background: rgba(255, 255, 255, 0.92);
}
/* The logo art is a black wordmark: it only needs inverting on the void. */
[data-bs-theme="light"] body.modal-intro .cf-brand-logo { filter: none; }
[data-bs-theme="light"] body.modal-intro a.mi-card:hover,
[data-bs-theme="light"] body.modal-intro .mi-btn-filled:hover { background: #eaf0f9; }
/* .mi-light carries its --circuit border in both themes now (see the breath
   section below), so the band keeps its edge on near-white mint too. */
/* The cube needs no light-theme override: black object, graphite hairlines —
   its finish belongs to the object, not the page. */

body.modal-intro ::selection {
    background: var(--volt);
    color: #fff;
}

/* ─────────────────────────────────────────────────────────
   Backdrop — a circuit board, lit from behind
   ------------------------------------------------------------
   Fixed behind the page. z-index:-1 puts it above the body's background but
   below every in-flow section, so no content needs a stacking context of its
   own.

   Four layers, bottom to top: the dot lattice, the traces and their pulses
   (canvas, drawn by home_modal.js), the bloom, and the vignette that fades all
   of it out before it reaches the type. The idea is a board you are looking
   down at with light moving through it — not a wallpaper pattern, and pointedly
   not a Matrix screensaver.
   ───────────────────────────────────────────────────────── */
body.modal-intro .mi-backdrop {
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    overflow: hidden;
    /* Deliberately NOT promoted with a transform/will-change: this is a fixed,
       negative-z layer, and forcing it onto its own compositor layer made
       Chromium raster the sections above it at the wrong offsets. The animated
       children below carry their own layers instead, which is where the frames
       were actually being lost. */
}

/* The solder lattice the traces are routed along. Dots rather than ruled lines:
   a full grid of lines reads as a wireframe and fights the type, while a dot
   field reads as a *surface*. Its 30px pitch is half the 60px trace pitch in
   home_modal.js, so every trace corner lands on a dot.

   Static on purpose. This is the layer that covers the whole viewport, and the
   two full-screen CSS animations that used to live here (a drifting grid and a
   scanline) were between them costing more frames than everything else on the
   page combined. The motion budget is spent on the pulses instead, which touch
   a few hundred pixels a frame rather than two million.

   Named .mi-bg-dots, NOT .mi-dots — .mi-dot is already the code window's
   traffic lights, and the near-miss with .mi-grid earlier cost an afternoon. */
body.modal-intro .mi-bg-dots {
    position: absolute;
    inset: 0;
    background-image: radial-gradient(circle,
                      rgba(var(--volt-rgb),0.17) 1px, transparent 1.6px);
    background-size: 30px 30px;
}

/* The board: an <svg> of traces + solder pads, and one element per pulse.
   home_modal.js routes the paths and fills this in.

   Not a canvas, deliberately. A canvas has to re-upload a viewport-sized texture
   on every frame it changes — 30fps of that dragged the page from 60fps to 24
   even on a GPU, and the traces themselves never move. As SVG the board is
   painted once and never again, and the pulses ride a motion path as their own
   composited layers: the frame touches a handful of small elements instead of
   two million pixels. Colours come from the tokens, so the theme flip is free. */
body.modal-intro .mi-board {
    position: absolute;
    inset: 0;
}
body.modal-intro .mi-board svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}
body.modal-intro .mi-trace {
    fill: none;
    stroke: rgba(var(--volt-rgb),0.13);
    stroke-width: 1;
    stroke-linecap: round;
    stroke-linejoin: round;
}
body.modal-intro .mi-pad { fill: rgba(var(--volt-rgb),0.22); }

/* A packet: a bright head with a short gradient wake behind it. offset-anchor
   pins the head (the element's right edge) to the path, so the wake trails. */
body.modal-intro .mi-pulse {
    position: absolute;
    top: 0;
    left: 0;
    width: 54px;
    height: 2px;
    border-radius: 2px;
    background: linear-gradient(to right,
                transparent, rgba(var(--volt-rgb),0.75) 82%, rgba(var(--volt-rgb),0.95));
    filter: drop-shadow(0 0 6px rgba(var(--volt-rgb),0.5));
    offset-rotate: auto;
    offset-anchor: 100% 50%;
    animation-name: mi-pulse-run;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
    will-change: offset-distance;
}
/* Fading in and out at the ends keeps packets from popping into existence at a
   board edge — they arrive from off-board and leave the same way. */
@keyframes mi-pulse-run {
    0%        { offset-distance: 0%;   opacity: 0; }
    10%, 88%  { opacity: 1; }
    100%      { offset-distance: 100%; opacity: 0; }
}

/* Depth. A single soft light source behind the headline — this is what stops the
   board reading as flat wallpaper, and it does that whether or not it moves.

   It used to breathe (a slow opacity cycle). Measured, that one animation cost
   22fps on a machine with no GPU compositing: opacity on an element this size
   repaints a viewport of gradient every frame. The packets already carry the
   page's sense of life, so the bloom holds still and the backdrop comes out
   effectively free — 60fps on a GPU, ~56 in software. */
body.modal-intro .mi-bloom {
    position: absolute;
    top: -20vh;
    left: 50%;
    width: min(1200px, 130vw);
    height: 95vh;
    transform: translateX(-50%);
    background: radial-gradient(ellipse at center,
                rgba(var(--volt-rgb),0.14) 0%,
                rgba(var(--volt-rgb),0.05) 45%,
                transparent 72%);
}

/* Paints over the board in the page's own colour, so the lattice and the traces
   fade out before they reach the type. A plain gradient does the job a
   mask-image was doing, without forcing the layer to be re-composited. */
body.modal-intro .mi-vignette {
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse 92% 74% at 50% 24%,
                transparent 0%, transparent 42%, var(--void) 100%);
}

/* Headings are the display face, phosphor, with the signature tight
   negative tracking. Weight 500 stands in for Goga Medium. */
body.modal-intro h1,
body.modal-intro h2,
body.modal-intro h3,
body.modal-intro h4,
body.modal-intro .mi-display {
    font-family: var(--mi-display);
    color: var(--phosphor);
    font-weight: 500;
    letter-spacing: -0.02em;
}

body.modal-intro main {
    /* Modal keeps content in a 1280px column; base.html's .container is
       narrower, so widen it just for this page. */
    max-width: 1280px;
}

/* Kill the blue flash-alert chrome bleeding onto the void, keep it legible. */
body.modal-intro .alert {
    background: var(--carbon);
    border: 1px solid var(--circuit);
    color: var(--phosphor);
}

/* ─────────────────────────────────────────────────────────
   Navigation — recolour the shared navbar to Carbon phosphor
   ───────────────────────────────────────────────────────── */
body.modal-intro .cf-navbar {
    background: rgba(33, 37, 37, 0.72);   /* Carbon Veil + blur */
    backdrop-filter: saturate(140%) blur(10px);
    -webkit-backdrop-filter: saturate(140%) blur(10px);
    border-bottom: 1px solid var(--blueblack);
}
body.modal-intro .cf-navbar.cf-scrolled {
    background: rgba(24, 24, 24, 0.9);
    box-shadow: rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.1) 0 4px 6px -4px;
}

/* Logo PNG has black wordmark art; brightness(0) invert(1) makes it
   phosphor-white on the dark bar (theme.css already does this in dark
   mode — restate it so it holds even if the visitor picked light mode). */
body.modal-intro .cf-brand-logo { filter: brightness(0) invert(1); }

body.modal-intro .cf-nav-link {
    color: var(--sage60) !important;
    font-family: var(--mi-display);
    letter-spacing: -0.014em;
}
body.modal-intro .cf-nav-link:hover {
    color: var(--phosphor) !important;
    background: rgba(var(--volt-rgb),0.06);
}
body.modal-intro .cf-nav-link::after { background: var(--volt-lit); }
body.modal-intro .cf-nav-link.active { color: var(--phosphor) !important; }

body.modal-intro .cf-nav-search,
body.modal-intro .cf-nav-bell { color: var(--sage60); }
body.modal-intro .cf-nav-search:hover,
body.modal-intro .cf-nav-bell:hover { color: var(--phosphor); }
body.modal-intro .cf-nav-bell-badge { background: var(--volt); color: #fff; }

body.modal-intro .cf-user-trigger { color: var(--phosphor); }
body.modal-intro .cf-user-caret { color: var(--sage60); }
body.modal-intro .cf-avatar {
    background: var(--ground);
    color: var(--volt-lit);
    box-shadow: inset 0 0 0 1px var(--circuit);
}
body.modal-intro .cf-role-badge {
    background: rgba(var(--volt-rgb),0.12);
    color: var(--volt-lit);
}

/* The one solid blue fill in the navbar: the Login / primary pill. */
body.modal-intro .cf-navbar .btn-cf-primary {
    background: var(--volt);
    color: #fff;
    border-radius: 9999px;
    font-family: var(--mi-display);
    box-shadow: none;
}
body.modal-intro .cf-navbar .btn-cf-primary::before { display: none; }
body.modal-intro .cf-navbar .btn-cf-primary:hover:not(:disabled) {
    color: #fff;
    filter: brightness(1.14);
    box-shadow: 0 0 0 1px rgba(var(--volt-rgb),0.45);
}
body.modal-intro .cf-navbar .btn-cf-ghost-light {
    background: transparent;
    border: 1px solid var(--circuit);
    color: var(--phosphor);
    border-radius: 12px;
}
body.modal-intro .cf-navbar .btn-cf-ghost-light:hover:not(:disabled) {
    background: rgba(var(--volt-rgb),0.06);
    border-color: var(--volt-lit);
    color: var(--phosphor);
}
body.modal-intro .cf-navbar .navbar-toggler-icon { background-color: var(--phosphor); }

/* ─────────────────────────────────────────────────────────
   Shared building blocks
   ───────────────────────────────────────────────────────── */
body.modal-intro .mi-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-family: var(--mi-display);
    font-size: 12px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--moss70);
    margin-bottom: 12px;
}
body.modal-intro .mi-eyebrow .mi-led {
    width: 7px; height: 7px;
    border-radius: 9999px;
    background: var(--volt-lit);
    box-shadow: 0 0 8px rgba(var(--volt-rgb),0.8);
    animation: mi-blink 2.6s steps(1, end) infinite;
}
@keyframes mi-blink { 0%, 70% { opacity: 1; } 71%, 100% { opacity: 0.35; } }

body.modal-intro .mi-h2 {
    font-size: clamp(28px, 4vw, 42px);
    line-height: 1.05;
    letter-spacing: -0.008em;
    margin: 0 0 16px;
}
body.modal-intro .mi-lead {
    font-family: var(--mi-display);
    font-size: 18px;
    line-height: 1.5;
    letter-spacing: -0.011em;
    color: var(--moss80);
    max-width: 640px;
}
body.modal-intro .mi-body {
    color: var(--sage60);
    font-size: 16px;
    line-height: 1.5;
    letter-spacing: -0.011em;
}
body.modal-intro .mi-accent { color: var(--volt-lit); }

body.modal-intro .mi-section { padding: 80px 0; }
body.modal-intro .mi-divider {
    border: 0;
    border-top: 1px solid var(--blueblack);
    margin: 0;
}

/* ── Buttons ─────────────────────────────────────────────── */
body.modal-intro .mi-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-family: var(--mi-display);
    font-size: 16px;
    font-weight: 500;
    letter-spacing: -0.011em;
    text-decoration: none;
    cursor: pointer;
    transition: color .3s var(--mi-ease), background-color .3s var(--mi-ease),
                border-color .3s var(--mi-ease), filter .3s var(--mi-ease),
                box-shadow .3s var(--mi-ease);
}
/* The single rationed blue fill — one per viewport. */
body.modal-intro .mi-btn-accent {
    background: var(--volt);
    color: #fff;
    border: 1px solid var(--volt);
    border-radius: 9999px;
    padding: 14px 26px;
}
body.modal-intro .mi-btn-accent:hover {
    filter: brightness(1.14);
    box-shadow: 0 0 0 4px rgba(var(--volt-rgb),0.2);
}
/* Ghost outline — the phosphor border keeps the glow metaphor. */
body.modal-intro .mi-btn-ghost {
    background: transparent;
    color: var(--phosphor);
    border: 1px solid var(--phosphor);
    border-radius: 12px;
    padding: 13px 26px;
}
body.modal-intro .mi-btn-ghost:hover {
    background: rgba(var(--volt-rgb),0.08);
    border-color: var(--phosphor);
}
/* "Engraved into the dark" filled action. */
body.modal-intro .mi-btn-filled {
    background: var(--ground);
    color: var(--phosphor);
    border: 1px solid var(--circuit);
    border-radius: 12px;
    padding: 13px 26px;
}
body.modal-intro .mi-btn-filled:hover {
    border-color: var(--phosphor);
    background: #1c1c1c;
}

/* ── Tag / chip ──────────────────────────────────────────── */
body.modal-intro .mi-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--mi-display);
    font-size: 12px;
    font-weight: 500;
    letter-spacing: -0.006em;
    padding: 5px 12px;
    border-radius: 9999px;
    background: transparent;
    border: 1px solid var(--circuit);
    color: var(--moss80);
}
body.modal-intro .mi-tag i { color: var(--volt-lit); }
body.modal-intro .mi-tag-live {
    background: var(--volt);
    border-color: var(--volt);
    color: #fff;
}
body.modal-intro .mi-tag-live i { color: #fff; }

/* ─────────────────────────────────────────────────────────
   Hero — centered stack over the void, with the signature cube
   ───────────────────────────────────────────────────────── */
body.modal-intro .mi-hero {
    position: relative;
    text-align: center;
    padding: 64px 0 40px;
    display: flex;
    flex-direction: column;
    align-items: center;
}
body.modal-intro .mi-title {
    font-size: clamp(40px, 7.4vw, 64px);
    line-height: 1.06;
    letter-spacing: -0.007em;
    margin: 0 0 20px;
    /* Two fixed rows: the static lead on top, the rotating word on its own line
       below. Keeping them on separate lines means a longer word cycling in
       ("tricky algorithms", "daily challenges") never grows the headline to a
       third row — the height stays put no matter which word is showing. */
    min-height: 2.12em;
}
/* Line 1 — the static lead sits on its own line so line 2 is always just the
   rotating word. */
body.modal-intro .mi-title-lead { display: block; }
/* Line 2 — the rotating challenge word, the "status LED" of the headline. */
body.modal-intro .mi-rotator {
    color: var(--volt-lit);
    display: block;
    min-height: 1em;
}
/* Phones: shrink the headline just enough that the static lead still fits on
   one line, so the two-row lockup holds here too rather than wrapping the lead
   onto a third row. Desktop/tablet keep the full clamp above. */
@media (max-width: 480px) {
    body.modal-intro .mi-title { font-size: clamp(26px, 7vw, 34px); }
}
body.modal-intro .mi-rotator::after {
    content: '';
    display: inline-block;
    width: 3px;
    height: 0.92em;
    margin-left: 4px;
    vertical-align: -0.08em;
    background: var(--volt-lit);
    animation: mi-caret 1.05s steps(1, end) infinite;
}
@keyframes mi-caret { 50% { opacity: 0; } }

body.modal-intro .mi-hero .mi-lead { margin: 0 auto 28px; }

body.modal-intro .mi-cta-row {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    justify-content: center;
}
body.modal-intro .mi-chip-row {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    justify-content: center;
    margin-bottom: 24px;
}

/* Stats row */
body.modal-intro .mi-stats {
    display: flex;
    gap: 48px;
    flex-wrap: wrap;
    justify-content: center;
    margin-top: 40px;
}
body.modal-intro .mi-stat-value {
    display: block;
    font-family: var(--mi-display);
    font-size: 30px;
    font-weight: 500;
    line-height: 1;
    letter-spacing: -0.012em;
    color: var(--phosphor);
}
body.modal-intro .mi-stat-label {
    display: block;
    margin-top: 6px;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--deepfern);
}

/* ── The signature cube — a black Rubik's cube, WebGL ────────
   The canvas is the real cube (home_modal.js): 27 matte black cubelets
   with #292d30 hairline edges, softly lit so faces catch the light as
   they turn, one slice rotating a slow quarter-turn every few seconds.
   Transparent canvas, no glow, no shadow. The CSS cube below it is the
   no-JS / no-WebGL fallback in the same finish — its 3×3 facelet grid
   is drawn with hairline gradients; .is-gl (set once the context is
   live) swaps them. */
body.modal-intro .mi-cube-stage {
    position: relative;
    width: 348px;
    height: 348px;
    margin: 56px auto 8px;
    display: grid;
    place-items: center;
    perspective: 800px;
}
body.modal-intro .mi-cube-gl {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
}
body.modal-intro .mi-cube-stage.is-gl .mi-cube { display: none; }
body.modal-intro .mi-cube {
    position: relative;
    width: 198px;
    height: 198px;
    transform-style: preserve-3d;
    animation: mi-cube-spin 26s linear infinite;
}
@keyframes mi-cube-spin {
    from { transform: rotateX(-24deg) rotateY(0deg); }
    to   { transform: rotateX(-24deg) rotateY(360deg); }
}
body.modal-intro .mi-face {
    position: absolute;
    inset: 0;
    background-color: #000;
    /* 3×3 facelets, cracks lit blue from inside — the static echo of the
       WebGL cube's glowing core. */
    background-image:
        repeating-linear-gradient(to right,  rgba(59, 158, 255, 0.55) 0 1px, transparent 1px calc(100% / 3)),
        repeating-linear-gradient(to bottom, rgba(59, 158, 255, 0.55) 0 1px, transparent 1px calc(100% / 3));
    border: 1px solid #292d30;
}
body.modal-intro .mi-face-f  { transform: translateZ(99px); }
body.modal-intro .mi-face-b  { transform: rotateY(180deg) translateZ(99px); }
body.modal-intro .mi-face-r  { transform: rotateY(90deg)  translateZ(99px); }
body.modal-intro .mi-face-l  { transform: rotateY(-90deg) translateZ(99px); }
body.modal-intro .mi-face-t  { transform: rotateX(90deg)  translateZ(99px); }
body.modal-intro .mi-face-bo { transform: rotateX(-90deg) translateZ(99px); }

/* ─────────────────────────────────────────────────────────
   Code window card
   ───────────────────────────────────────────────────────── */
body.modal-intro .mi-code {
    position: relative;
    background: var(--ground);
    border: 1px solid var(--circuit);
    border-radius: 14px;
    overflow: hidden;
    /* Rest-state depth: without it the panel reads as a flat rectangle until
       the cursor tilts it. Black drop for weight, faint volt wash for glow. */
    box-shadow:
        0 32px 70px -38px rgba(0, 0, 0, 0.85),
        0 18px 60px -30px rgba(var(--volt-rgb), 0.22);
}

/* ── The code window as a physical panel ─────────────────────
   .mi-tilt is the perspective stage; home_modal.js writes the angles as
   custom properties from the cursor's position over the panel. Positive
   rotateY pushes the right edge away from the viewer and positive rotateX
   pushes the top away, so the corner under the cursor is the one that sinks
   back — the panel behaves as if the pointer were pressing into it.

   The panel follows the cursor but does not chase it: while the cursor is on
   it the transform eases over .3s, so it trails the pointer just enough to
   read as weight rather than snapping to every twitch. Leaving it is a slower
   settle back to flat. */
body.modal-intro .mi-tilt { perspective: 1000px; }
body.modal-intro .mi-tilt .mi-code {
    transform: rotateX(var(--mi-rx, 0deg)) rotateY(var(--mi-ry, 0deg));
    transition: transform .6s var(--mi-ease), box-shadow .5s var(--mi-ease);
    will-change: transform;
}
body.modal-intro .mi-tilt.is-tilting .mi-code {
    transition: transform .3s var(--mi-ease), box-shadow .3s var(--mi-ease);
    box-shadow: 0 28px 60px -34px rgba(var(--volt-rgb),0.48);
}
/* A specular that follows the cursor: without it the rotation reads as a
   moving picture rather than as light falling on a surface. */
body.modal-intro .mi-tilt .mi-code::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    opacity: 0;
    transition: opacity .4s var(--mi-ease);
    /* Wide and soft: a sheen sliding over the panel rather than a hot spot
       pinned to the cursor. (Not transitioned — animating a gradient repaints
       the whole panel every frame.) */
    background: radial-gradient(280px circle at var(--mi-gx, 50%) var(--mi-gy, 50%),
                rgba(var(--volt-rgb),0.115), transparent 73%);
}
body.modal-intro .mi-tilt.is-tilting .mi-code::after { opacity: 1; }
body.modal-intro .mi-code-bar {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 14px 18px;
    background: var(--carbon);
    border-bottom: 1px solid var(--blueblack);
    /* Hairline top highlight — light catching the panel's upper edge. */
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
body.modal-intro .mi-dot { width: 10px; height: 10px; border-radius: 9999px; }
body.modal-intro .mi-dot-r { background: #d16a63; }
body.modal-intro .mi-dot-y { background: #cbb26a; }
/* Window-chrome traffic lights, not accents — they stay red/amber/green. */
body.modal-intro .mi-dot-g { background: #5ec26a; }
body.modal-intro .mi-code-file {
    margin-left: 6px;
    font-family: var(--mi-display);
    font-size: 14.5px;
    color: var(--moss70);
}
/* "Accepted" status pill on the right of the window chrome (photo-2 look). */
body.modal-intro .mi-code-badge {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 11px;
    border-radius: 9999px;
    font-family: var(--mi-display);
    font-size: 12px;
    font-weight: 500;
    color: #5ec26a;
    background: rgba(94, 194, 106, 0.12);
    border: 1px solid rgba(94, 194, 106, 0.30);
}
body.modal-intro .mi-code pre {
    margin: 0;
    padding: 30px 34px;
    font-family: var(--mi-mono);
    font-size: 18px;
    line-height: 1.7;
    color: var(--phosphor);
    white-space: pre;
    overflow-x: auto;
    /* Seven lines of snippet + the padding: the window is already its final
       size before the first character lands, so nothing under it shifts as
       the code types in. min-height is in em, so it tracks the font size. */
    min-height: calc(13 * 1.7em + 60px);
}
body.modal-intro .mi-code .k { color: var(--volt-lit); }        /* keyword */
body.modal-intro .mi-code .s { color: var(--phosphor); }    /* string  */
body.modal-intro .mi-code .f { color: var(--moss80); }      /* function */
body.modal-intro .mi-code .n { color: var(--moss70); }      /* number  */
body.modal-intro .mi-code .c { color: var(--sage40); }      /* comment */
body.modal-intro .mi-type-caret {
    display: inline-block;
    width: 8px;
    height: 1.05em;
    vertical-align: -0.18em;
    background: var(--volt-lit);
    animation: mi-caret 1.05s steps(1, end) infinite;
}

/* Terminal narration under the code */
body.modal-intro .mi-term {
    position: relative;
    border-top: 1px solid var(--blueblack);
    padding: 20px 34px;
    font-family: var(--mi-mono);
    font-size: 15.5px;
    line-height: 1.8;
    color: var(--sage40);
    /* A shade darker than the editor above it, so the empty strip reads as a
       terminal pane waiting for output rather than an unfinished card. */
    background: rgba(0, 0, 0, 0.28);
}
/* Idle prompt while the verdict lines are still hidden (scrub mode only —
   the no-JS fallback shows the lines outright). Absolutely positioned over
   the invisible first line, so its arrival changes nothing about the layout;
   :has() retires it the moment the first real line lands. */
body.modal-intro .mi-typewrap.is-scrub .mi-term::before {
    content: '$';
    position: absolute;
    top: 20px;
    left: 34px;
    color: var(--volt-lit);
}
body.modal-intro .mi-typewrap.is-scrub .mi-term::after {
    content: '';
    position: absolute;
    top: calc(20px + 0.35em);
    left: calc(34px + 1.6ch);
    width: 8px;
    height: 1.05em;
    background: var(--volt-lit);
    animation: mi-caret 1.05s steps(1, end) infinite;
}
body.modal-intro .mi-typewrap.is-scrub .mi-term:has(.mi-term-line.is-shown)::before,
body.modal-intro .mi-typewrap.is-scrub .mi-term:has(.mi-term-line.is-shown)::after {
    display: none;
}
body.modal-intro .mi-term .mi-prompt { color: var(--volt-lit); margin-right: 8px; }
body.modal-intro .mi-term-ok { color: var(--moss80); }
body.modal-intro .mi-term-accept { color: var(--volt-lit); }

/* ── Scroll-scrubbed code section ────────────────────────────
   The section is a tall runway and the panel inside it is pinned, so the
   page appears to stop while the wheel types the snippet out character by
   character; the terminal verdict lands on the last stretch, and only then
   does the runway run out and the page move on.

   home_modal.js adds .is-scrub — without JS (or under reduced motion, or on
   a viewport too short to pin against) the section stays an ordinary block
   with the full snippet already in it. */
body.modal-intro .mi-typewrap.is-scrub {
    padding: 0;
    height: 260vh;
}
body.modal-intro .mi-typewrap.is-scrub .mi-sticky {
    position: sticky;
    top: 88px;            /* clears the sticky navbar */
    padding: 32px 0 20px;
}
body.modal-intro .mi-typewrap.is-scrub .mi-term-line {
    opacity: 0;
    transform: translateY(4px);
    transition: opacity .35s var(--mi-ease), transform .35s var(--mi-ease);
}
body.modal-intro .mi-typewrap.is-scrub .mi-term-line.is-shown {
    opacity: 1;
    transform: none;
}
/* All four lines get .is-shown at the same instant (home_modal.js) — this is
   what cascades them in one after another instead of popping in together.
   A *timed* stagger rather than the old *scroll-distance* one: it always
   finishes the same fraction of a second after typing ends, so it can't be
   caught half-revealed by a zoom level that changes how scroll pixels map to
   progress (see home_modal.js update() for the full story). */
body.modal-intro .mi-typewrap.is-scrub .mi-term-line:nth-child(1) { transition-delay: 0s; }
body.modal-intro .mi-typewrap.is-scrub .mi-term-line:nth-child(2) { transition-delay: .12s; }
body.modal-intro .mi-typewrap.is-scrub .mi-term-line:nth-child(3) { transition-delay: .24s; }
body.modal-intro .mi-typewrap.is-scrub .mi-term-line:nth-child(4) { transition-delay: .36s; }

/* Two-column text + code layout */
body.modal-intro .mi-split {
    display: grid;
    /* 5fr:8fr, not 4fr:8fr — at full size the code column's longest line
       (~529px) left ~194px of dead space on the right inside the panel at
       4fr:8fr. This still keeps the column meaningfully wider than it was
       before the panel was widened (688px), it's just no longer wider than
       the snippet actually needs. */
    grid-template-columns: minmax(0, 5fr) minmax(0, 8fr);
    gap: 64px;
    align-items: center;
}
/* This section is the page's centrepiece, so its type runs a size up from
   the sibling sections. Scoped to .mi-typewrap — the shared .mi-h2/.mi-body
   sizes elsewhere stay as they are. */
body.modal-intro .mi-typewrap .mi-h2 {
    font-size: clamp(32px, 4.6vw, 54px);
    margin-bottom: 20px;
}
body.modal-intro .mi-typewrap .mi-body {
    font-size: 18px;
    line-height: 1.55;
    max-width: 44ch;
}
body.modal-intro .mi-steps { list-style: none; margin: 28px 0 0; padding: 0; }
body.modal-intro .mi-step {
    display: flex;
    gap: 16px;
    padding: 15px 0;
    border-top: 1px solid var(--blueblack);
}
body.modal-intro .mi-step-num {
    font-family: var(--mi-mono);
    font-size: 14px;
    color: var(--volt-lit);
    padding-top: 3px;
}
body.modal-intro .mi-step strong {
    display: block;
    color: var(--phosphor);
    font-weight: 500;
    font-size: 18px;
    letter-spacing: -0.011em;
}
body.modal-intro .mi-step small {
    display: block;
    color: var(--sage40);
    font-size: 15px;
    letter-spacing: -0.013em;
    margin-top: 2px;
}

/* ─────────────────────────────────────────────────────────
   Workload / category cards — 3-col grid
   ───────────────────────────────────────────────────────── */
body.modal-intro .mi-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 20px;
}
/* Category cards: four across so the eight cards land in two rows (its own
   class, since .mi-grid above is shared with the 3-up "How It Works" section). */
body.modal-intro .mi-grid-cats {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 20px;
}
@media (max-width: 1100px) {
    body.modal-intro .mi-grid-cats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
    body.modal-intro .mi-grid-cats { grid-template-columns: 1fr; }
}
body.modal-intro .mi-card {
    position: relative;
    display: flex;
    flex-direction: column;
    background: var(--ground);
    border: 1px solid var(--blueblack);
    border-radius: 8px;
    padding: 32px;
    text-decoration: none;
    transition: border-color .3s var(--mi-ease), background-color .3s var(--mi-ease);
}
body.modal-intro a.mi-card:hover {
    border-color: var(--circuit);
    background: #1c1c1c;
}
/* Isometric-ish icon ground: a blue mark on a dark tile. Squared corners
   (radius 0) per Modal's icon rule. */
body.modal-intro .mi-icon {
    width: 56px;
    height: 56px;
    display: grid;
    place-items: center;
    background: var(--carbon);
    border: 1px solid var(--circuit);
    border-radius: 0;
    color: var(--volt-lit);
    font-size: 26px;
    margin-bottom: 20px;
    transition: transform .3s var(--mi-spring);
}
body.modal-intro a.mi-card:hover .mi-icon { transform: translateY(-2px); }
body.modal-intro .mi-card-title {
    font-family: var(--mi-display);
    font-weight: 500;
    font-size: 24px;
    line-height: 1.3;
    letter-spacing: -0.013em;
    color: var(--phosphor);
    margin: 0 0 8px;
}
body.modal-intro .mi-card-desc {
    color: var(--sage60);
    font-size: 16px;
    line-height: 1.5;
    letter-spacing: -0.011em;
    margin: 0;
}
body.modal-intro .mi-card-meta {
    margin-top: auto;
    padding-top: 20px;
    font-family: var(--mi-mono);
    font-size: 12.5px;
    color: var(--moss70);
    display: flex;
    align-items: center;
    gap: 8px;
}
body.modal-intro .mi-card-arrow {
    margin-left: auto;
    color: var(--sage40);
    transition: transform .3s var(--mi-ease), color .3s var(--mi-ease);
}
body.modal-intro a.mi-card:hover .mi-card-arrow { color: var(--volt-lit); transform: translateX(3px); }

/* The dashed "explore everything" tail card */
body.modal-intro .mi-card-explore {
    align-items: flex-start;
    background: transparent;
    border: 1px dashed var(--circuit);
}

/* ── Muted proof strip ───────────────────────────────────── */
body.modal-intro .mi-proof {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 12px 40px;
    color: var(--deepfern);
    font-family: var(--mi-mono);
    font-size: 13px;
    letter-spacing: 0.02em;
}
body.modal-intro .mi-proof span { white-space: nowrap; }

/* ─────────────────────────────────────────────────────────
   Light "breath" section — the single bright band mid-page
   ───────────────────────────────────────────────────────── */
/* Dark (default): the "breath" inverts — a deep, brand-tinted panel that
   lifts off the void without glaring, frosted blue cards, phosphor type.
   The light-band values live in the [data-bs-theme="light"] block below. */
body.modal-intro .mi-light {
    background: #0c162e;
    border: 1px solid var(--circuit);
    border-radius: 8px;
    padding: 56px 40px;
}
body.modal-intro .mi-light .mi-eyebrow { color: var(--volt-lit); }
body.modal-intro .mi-light .mi-h2 { color: var(--phosphor); }
body.modal-intro .mi-quote-card {
    background: rgba(var(--volt-rgb), 0.05);
    border: 1px solid rgba(var(--volt-rgb), 0.18);
    border-radius: 8px;
    padding: 28px;
    height: 100%;
}
body.modal-intro .mi-quote-card p {
    color: var(--moss80);
    font-size: 16px;
    line-height: 1.5;
    letter-spacing: -0.011em;
}
body.modal-intro .mi-quote-star { color: var(--volt-lit); }
body.modal-intro .mi-quote-av {
    width: 34px; height: 34px;
    display: grid; place-items: center;
    border-radius: 9999px;
    background: rgba(var(--volt-rgb), 0.16);
    color: var(--volt-lit);
    font-weight: 600;
    font-size: 14px;
}
body.modal-intro .mi-quote-name { color: var(--phosphor); font-weight: 500; }
body.modal-intro .mi-quote-role { color: var(--sage60); }

/* Light theme: the original bright "breath" band — mint panel, frosted-white
   cards, ink type. (In dark these flip to the deep-panel values above.) */
[data-bs-theme="light"] body.modal-intro .mi-light {
    background: var(--mint);
}
[data-bs-theme="light"] body.modal-intro .mi-light .mi-eyebrow { color: #3d5a8c; }
[data-bs-theme="light"] body.modal-intro .mi-light .mi-h2 { color: #08122a; }
[data-bs-theme="light"] body.modal-intro .mi-quote-card {
    background: rgba(255, 255, 255, 0.55);
    border: 1px solid rgba(17, 80, 230, 0.18);
}
[data-bs-theme="light"] body.modal-intro .mi-quote-card p { color: #16233d; }
[data-bs-theme="light"] body.modal-intro .mi-quote-star { color: var(--volt); }
[data-bs-theme="light"] body.modal-intro .mi-quote-av {
    background: #c3d4ff;
    color: #0a2e8f;
}
[data-bs-theme="light"] body.modal-intro .mi-quote-name { color: #0b1830; }
[data-bs-theme="light"] body.modal-intro .mi-quote-role { color: #3d5a8c; }

/* ─────────────────────────────────────────────────────────
   Final CTA
   ───────────────────────────────────────────────────────── */
body.modal-intro .mi-cta {
    position: relative;
    text-align: center;
    background: var(--ground);
    border: 1px solid var(--blueblack);
    border-radius: 8px;
    padding: 64px 32px;
    overflow: hidden;
}
body.modal-intro .mi-cta::before {
    content: '';
    position: absolute;
    top: -120px; left: 50%;
    width: 420px; height: 300px;
    transform: translateX(-50%);
    background: radial-gradient(circle, rgba(var(--volt-rgb),0.16), transparent 70%);
    pointer-events: none;
}
body.modal-intro .mi-cta > * { position: relative; }

/* ─────────────────────────────────────────────────────────
   Footer — recoloured to the void
   ───────────────────────────────────────────────────────── */
body.modal-intro .cf-footer {
    background: var(--void);
    border-top: 1px solid var(--blueblack);
    color: var(--sage40);
}
body.modal-intro .cf-footer-head { color: var(--phosphor); }
body.modal-intro .cf-footer-links a,
body.modal-intro .cf-footer-links span { color: var(--fern); }
body.modal-intro .cf-footer-links a:hover { color: var(--volt-lit); }
body.modal-intro .cf-footer-social a {
    color: var(--sage40);
    border-color: var(--circuit);
}
body.modal-intro .cf-footer-social a:hover { color: var(--volt-lit); border-color: var(--volt-lit); }
body.modal-intro .cf-footer-bottom { border-top-color: var(--blueblack); color: var(--deepfern); }

/* ─────────────────────────────────────────────────────────
   Reveal-on-scroll (progressive enhancement; JS adds .is-in)
   ───────────────────────────────────────────────────────── */
body.modal-intro.js-mi .mi-rev {
    opacity: 0;
    transform: translateY(20px);
    transition: opacity .7s var(--mi-ease), transform .7s var(--mi-ease);
}
body.modal-intro.js-mi .mi-rev.is-in {
    opacity: 1;
    transform: none;
}

/* ── Responsive ──────────────────────────────────────────── */
@media (max-width: 900px) {
    /* Stacked, the panel is taller than the phone viewport — there is nothing
       to pin it against, so the section goes back to being a plain block.
       (home_modal.js drops .is-scrub here too and types on reveal instead.) */
    body.modal-intro .mi-typewrap.is-scrub { height: auto; padding: 56px 0; }
    body.modal-intro .mi-typewrap.is-scrub .mi-sticky { position: static; padding: 0; }
    body.modal-intro .mi-typewrap.is-scrub .mi-term-line { opacity: 1; transform: none; }
    body.modal-intro .mi-split { grid-template-columns: 1fr; gap: 32px; }
    body.modal-intro .mi-grid { grid-template-columns: 1fr; }
    body.modal-intro .mi-section { padding: 56px 0; }
    body.modal-intro .mi-stats { gap: 28px; }
}
@media (min-width: 901px) and (max-width: 1100px) {
    body.modal-intro .mi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ── Short viewports: compact the pinned panel so it still fits ──────
   The scrub only runs when the whole panel can sit pinned under the navbar.
   At full size the panel is ~667px and needs ~807px of viewport (see the
   canPin comment in home_modal.js) — which a 1366x768 laptop does not have,
   and neither does any browser window under ~810px tall. The old guard
   simply refused to pin there, so the page's centrepiece animation was dead
   on some very common screens.

   So scale the panel down with the viewport instead of demanding the
   viewport grow. Both mono blocks are sized in vh with a px floor, and the
   pre's min-height is in em, so the window still reserves its full height
   before the first character lands and nothing shifts as the code types in.
   home_modal.js's canPin query is kept in step with the 810px floor here —
   change one and you must change the other.

   The vh coefficients are chosen so each font-size is CONTINUOUS with its
   full (unshrunk) value right at this query's own 900px boundary — e.g. the
   code font is 2vh, and 2vh of a 900px-tall viewport is exactly 18px, its
   full size. The previous coefficients (1.55vh, 1.35vh) didn't have this
   property: they were tuned for an earlier, shorter version of this panel
   and never rekeyed when the panel grew, so at a 900px-tall viewport the
   code font suddenly dropped to ~14px — a visible, unintended shrink on the
   most common "100% zoom" desktop heights (864, 900), where nothing should
   have been shrinking at all. */
@media (min-width: 901px) and (max-height: 900px) {
    body.modal-intro .mi-typewrap.is-scrub .mi-sticky {
        top: 72px;
        padding: 16px 0 12px;
    }
    body.modal-intro .mi-typewrap .mi-h2 {
        font-size: clamp(26px, 3.6vh, 44px);
        margin-bottom: 12px;
    }
    body.modal-intro .mi-typewrap .mi-body { font-size: 15px; line-height: 1.45; }
    body.modal-intro .mi-typewrap .mi-steps { margin-top: 12px; }
    body.modal-intro .mi-typewrap .mi-step { padding: 8px 0; }
    body.modal-intro .mi-typewrap .mi-step strong { font-size: 15px; }
    body.modal-intro .mi-typewrap .mi-step small { font-size: 13px; }

    /* A touch narrower than the base 5fr:8fr — the code column doesn't need
       quite as much room once the font above is shrinking with viewport
       height, so this keeps the panel from developing its own empty gutter
       on top of the base ratio's. */
    body.modal-intro .mi-split { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
    body.modal-intro .mi-typewrap .mi-code-bar { padding: 9px 14px; }
    body.modal-intro .mi-typewrap .mi-code pre {
        font-size: clamp(10px, 2vh, 18px);
        padding: 16px 20px;
        min-height: calc(13 * 1.7em + 32px);
    }
    body.modal-intro .mi-typewrap .mi-term {
        font-size: clamp(10px, 1.72vh, 15.5px);
        padding: 10px 20px;
        line-height: 1.6;
    }
}

/* ── Reduced motion: hold everything still ──────────────── */
@media (prefers-reduced-motion: reduce) {
    /* The WebGL cube draws one frame and holds it (home_modal.js);
       the CSS fallback cube stops the same way. */
    body.modal-intro .mi-cube { animation: none; }
    body.modal-intro .mi-led { animation: none; }
    /* The board stays — it is texture, not motion. The lattice, the traces and the
       bloom are all static anyway; home_modal.js simply never launches the packets,
       which are the only moving part. */
    body.modal-intro .mi-pulse { display: none; }
    body.modal-intro .mi-tilt .mi-code { transition: none; }
    body.modal-intro .mi-rotator::after,
    body.modal-intro .mi-type-caret { animation: none; }
    body.modal-intro.js-mi .mi-rev { opacity: 1; transform: none; transition: none; }
}
