/*
 * Full-page portal theme. data-bs-theme lives on <html> (server-rendered from
 * portalTheme, kept in sync by portal-theme.js), so every page follows the
 * global theme. .portal-page-themed remains as a per-<main> sync point for
 * Bootstrap variables and legacy selectors.
 *
 * Canvas: neutral near-black with a purple whisper (dark) / neutral paper
 * (light). The dark canvas carries a quiet aurora wash — decorative and
 * cheap (two background layers, no extra elements).
 */

/* The canvas is near-black, not dark grey. #131218 until 2026-08-13, which
 * left only ~7/255 between the page and the glass sitting on it — so panels
 * had to be outlined in white to be seen at all, and the whole app read as
 * grey-on-grey. Dropping the canvas is what buys the glass its contrast: the
 * surface stays a real step lighter, the keyline can go black, and the aurora
 * reads as colour through the pane instead of a wash on a grey field. Not
 * pure #000 — the purple whisper survives, and pure black makes both the
 * shadow scale and OLED smearing worse.
 *
 * Landed at #100f18 after one correction. The pass first went to #0a090f,
 * which read as too dark AND took the corner glow with it: the aurora alphas
 * were unchanged, and the composite delta was unchanged too, but colourfulness
 * falls with luminance, so the purple and teal corners visibly went out. The
 * canvas came back up ~6/255 and the two aurora alphas grew by about half
 * (portal-theme.css). Those three numbers are ONE decision — moving the canvas
 * without re-deriving the aurora is what produced the wrong-looking state.
 *
 * Now #0d0c14, on Brian's "a tiny bit darker" before the deploy. HALF the step
 * that failed: 3/255 down from #100f18, where #0a090f was 6/255 down. The
 * aurora went up with it for the reason above — violet .15→.175, teal
 * .07→.085, magenta .06→.07, roughly a quarter, which is the same
 * proportion-per-step the correction used in the other direction. Measured
 * after: the canvas→surface luma step widens 15.6→17.8, so the glass separates
 * from the page MORE at the darker canvas rather than less, and body text on
 * the canvas goes 15.5:1→15.8:1. Checked against the corner glow before being
 * believed, because that is the half the numbers did not catch last time. */
html[data-bs-theme="dark"] body {
    background-color: #0d0c14;
    background-image:
        radial-gradient(ellipse var(--portal-aurora-violet-size) at var(--portal-aurora-violet-pos), var(--portal-aurora-violet), transparent 72%),
        radial-gradient(ellipse var(--portal-aurora-teal-size) at var(--portal-aurora-teal-pos), var(--portal-aurora-teal), transparent 70%);
    background-size: 100% 100%, 100% 100%;
    background-attachment: fixed;
    color: #e8e7ee;
}

/* Light gets an aurora too, at roughly half the dark alphas. Not decoration:
 * translucent white glass over a FLAT white page has nothing to be glass of —
 * cards separated only by a hairline and an 8%-alpha shadow. A cool
 * gray-violet breath behind them is what makes the surface read as a surface.
 * Half-alpha keeps it atmosphere, not colour: this is the daytime work theme. */
html[data-bs-theme="light"] body {
    background-color: #f4f4f6;
    background-image:
        radial-gradient(ellipse var(--portal-aurora-violet-size) at var(--portal-aurora-violet-pos), var(--portal-aurora-violet-light), transparent 72%),
        radial-gradient(ellipse var(--portal-aurora-teal-size) at var(--portal-aurora-teal-pos), var(--portal-aurora-teal-light), transparent 70%);
    background-size: 100% 100%, 100% 100%;
    background-attachment: fixed;
    color: #1c1b24;
}

/* Reduced-motion users: fixed background-attachment can shimmer, so let the
 * canvas scroll with the page. The aurora still renders, it just isn't pinned. */
@media (prefers-reduced-motion: reduce) {
    html[data-bs-theme="dark"] body,
    html[data-bs-theme="light"] body {
        background-attachment: scroll;
    }
}

/* ── The Light pillar, on touch ────────────────────────────────────────
 *
 * Coarse pointers used to land in the rule above, and `scroll` is not a
 * fallback so much as a different design: the aurora scrolls away about 400px
 * into the document, so every competitor phone and every pit iPad spent the
 * rest of the page looking at flat #0d0c14 with floating glass over nothing.
 * The Light pillar reached exactly the devices least likely to see it.
 *
 * The original objection to `fixed` there was real — mobile Safari repaints
 * the body background on every scroll frame — but it applies to a BODY
 * background, not to a fixed pseudo-element, which is composited once and
 * never repainted while scrolling.
 *
 * So: one fixed layer under the content, and the body paints no image at all.
 * Same two gradients, same geometry tokens, so a retune moves both.
 *
 * Reduced motion keeps `scroll` (the rule above still applies) — this is
 * additive, and someone who asked for less movement gets the simpler answer.
 * If it costs frames on the slowest pit iPad, the knob is this layer's alpha,
 * not its existence. */
@media (pointer: coarse) and (prefers-reduced-motion: no-preference) {
    html[data-bs-theme="dark"] body,
    html[data-bs-theme="light"] body {
        background-image: none;
    }

    html[data-bs-theme="dark"] body::before,
    html[data-bs-theme="light"] body::before {
        content: "";
        position: fixed;
        inset: 0;
        z-index: -1;
        pointer-events: none;
        background-repeat: no-repeat;
    }

    html[data-bs-theme="dark"] body::before {
        background-image:
            radial-gradient(ellipse var(--portal-aurora-violet-size) at var(--portal-aurora-violet-pos), var(--portal-aurora-violet), transparent 72%),
            radial-gradient(ellipse var(--portal-aurora-teal-size) at var(--portal-aurora-teal-pos), var(--portal-aurora-teal), transparent 70%);
    }

    html[data-bs-theme="light"] body::before {
        background-image:
            radial-gradient(ellipse var(--portal-aurora-violet-size) at var(--portal-aurora-violet-pos), var(--portal-aurora-violet-light), transparent 72%),
            radial-gradient(ellipse var(--portal-aurora-teal-size) at var(--portal-aurora-teal-pos), var(--portal-aurora-teal-light), transparent 70%);
    }

    /* Pinned under test by the same stamp that flattens the canvas — one
       extra selector, so the gallery's baselines stay position-independent
       on a phone viewport too. */
    html[data-portal-canvas="flat"] body::before {
        background-image: none;
    }
}

/* Under the screenshot gate only, and only for /style-guide: drop the aurora so
 * the gallery's baselines stop depending on where a section happens to sit in
 * the document.
 *
 * The canvas is background-attachment: fixed, so what shows through a card's
 * glass is a function of the section's VIEWPORT position. That makes every
 * baseline a hostage to page height: insert, delete or reorder one section and
 * every section below it re-baselines with a few dozen changed pixels that mean
 * nothing. It is why the gallery grew by appending rather than slotting.
 *
 * background-attachment: scroll does NOT fix this (the gradient still paints,
 * just anchored elsewhere) — the image has to go. Colour stays, so the two
 * canvas-luma assertions in dark-material.spec.js are unaffected.
 *
 * Stamped by stampHtml() in tests/visual/portal-page.js, which only
 * style-guide.spec.js asks for. (0,1,2) — ties the theme rules above and wins
 * on source order, so it must stay below them. */
html[data-portal-canvas="flat"] body {
    background-image: none;
}

body:has(.portal-page-themed[data-bs-theme="dark"]) {
    background-color: #0d0c14;
    color: #e8e7ee;
}

body:has(.portal-page-themed[data-bs-theme="light"]) {
    background-color: #f4f4f6;
    color: #1c1b24;
}

/* Bootstrap data-bs-theme sets --bs-body-bg to #fff on themed <main>; match page canvas.
 * Surfaces are translucent glass over the aurora (portal-components.css); the
 * aurora peaks at 0.15 alpha and surfaces sit at 0.72, so text keeps ≈12.9:1
 * and muted text ≈6.4:1 over the brightest aurora spot — the worked figures
 * are in portal-components.css beside --portal-c-surface, and they are what
 * bounds how far the aurora can be pushed. --bs-body-bg stays the OPAQUE
 * canvas color — it is the occlusion color for sticky headers scrolling over
 * content. */
/* .portal-theme-scope joins both blocks so a NESTED scope — an element
 * painting in a theme its page is not in — carries the right canvas too, not
 * just the right glass. Without it a scope inherits the PAGE's --bs-body-bg,
 * and everything that reads the canvas as an occlusion colour (sticky heads,
 * the parts of Bootstrap that fill before they tint) paints the wrong theme's
 * background inside a scope that otherwise looks correct. The tokens and the
 * three glass gates already carry it; this is the third of the three places
 * the plan names. */
.portal-page-themed[data-bs-theme="dark"],
.portal-theme-scope[data-bs-theme="dark"] {
    --bs-body-bg: #0d0c14;
    --bs-body-color: #e8e7ee;
}

.portal-page-themed[data-bs-theme="light"],
.portal-theme-scope[data-bs-theme="light"] {
    --bs-body-bg: #f4f4f6;
    --bs-body-color: #1c1b24;
}

body:has(.portal-page-themed) .padding-top {
    display: none;
}

/* Standard bottom breathing room for app pages — replaces the per-page
 * <div style="padding-bottom: 40px;"> spacer divs. */
main.portal-page-themed {
    padding-bottom: 2.5rem;
}


/* ── Reading measure (opt-in) ─────────────────────────────────────────
 * There is no breakpoint above 992px anywhere in the shell, so on a 27"
 * monitor a documentation page runs prose the full width of the display.
 * Data pages genuinely want that bleed — a fight table with twelve columns
 * should use every pixel — so this is opt-in per page rather than a default:
 *   {% block mainClass %}portal-page-measure{% endblock %}
 *
 * Implemented as symmetric PADDING, not max-width + margin auto: main's
 * margin-left is how the shell offsets the sidebar (navigation.css), and
 * overwriting it would drop the content column on top of the rail. Because
 * 100% here is main's own width, which already excludes the sidebar, the
 * centring stays correct in both the expanded and collapsed states and
 * animates with them for free.
 *
 * 1320px is Bootstrap's xxl container: wide enough for a two-column form,
 * narrow enough to keep prose at a readable line length. */
:root {
    --portal-page-measure: 1320px;
}

@media (min-width: 1200px) {
    /* Scoped through the shell to OUT-SPECIFY navigation.css's own
       `body.portal-has-chrome .portal-shell ~ main` padding rule (0,2,1) —
       a bare `main.portal-page-measure` is (0,1,1) and loses, silently, in a
       sheet that also happens to load first. The max() also keeps the
       safe-area inset from that rule intact on a notched landscape iPad. */
    body.portal-has-chrome .portal-shell ~ main.portal-page-measure,
    body:has(.portal-shell) .portal-shell ~ main.portal-page-measure {
        /* The sidebar width has to come out explicitly. Percentage padding
           resolves against the CONTAINING BLOCK (body), not the element, so
           `(100% - measure) / 2` on a sidebar-offset main centres the text
           against the viewport and lands the column exactly one sidebar too
           narrow — measured at 1080px instead of 1320px on a 2560px display.
           --portal-sidebar-active-width is 0 when there is no sidebar — below
           992px, or for an account with the preference off — and the slab's
           full column when there is, so this stays right either way without
           knowing which. */
        padding-left: max(0.75rem, var(--portal-safe-left), calc((100% - var(--portal-sidebar-active-width) - var(--portal-page-measure)) / 2));
        padding-right: max(0.75rem, var(--portal-safe-right), calc((100% - var(--portal-sidebar-active-width) - var(--portal-page-measure)) / 2));
    }
}
