/* ============================================================
 * user_ui — global background chrome
 * ============================================================
 *
 * Global background pass -> CSS-first background pass ->
 * dashed-field restoration. Styles for the site-wide background
 * rendered by `background/_background.html` (the single entry
 * point included by base.html's `background` block).
 *
 * WHAT THIS IS: a field of DASHED diagonal line segments —
 * short coloured strokes with long gaps — drifting along their
 * own axis, which reads as left-to-right travel because the
 * lines are near-horizontal. Real `<line>` elements in a real
 * `<svg>`, dashed with `stroke-dasharray` and animated on
 * `stroke-dashoffset`. The markup is STATIC; there is no
 * JavaScript anywhere in this feature.
 *
 * ---- THE THREE SHAPES, AND WHY THIS IS THE THIRD -----------
 *
 * Read this before changing anything here. Two earlier shapes
 * shipped and both were rejected in production, for opposite
 * reasons:
 *
 *   1. JS-GENERATED SVG. This exact field, but the `<line>`
 *      elements were written into an EMPTY `<g>` by the
 *      `ge/background` Component at mount. The look was right.
 *      The cost was that nothing painted until the whole module
 *      chain had been fetched, parsed and run — 0.5-1s of empty
 *      background on a cold load — and the Component also held
 *      a window `resize` listener AND a `ResizeObserver` on
 *      `document.body`, both of which re-serialised the entire
 *      field through `innerHTML` on every async content change
 *      (drawers, lazy tabs, fetched lists).
 *
 *   2. CSS `repeating-linear-gradient`. Fixed both of those and
 *      broke the design. A gradient cannot dash: the bands came
 *      out SOLID, which is ~4.3x the ink at the same width and
 *      spacing, and the only motion available to a solid band is
 *      translating the whole field, which creeps DOWN the page
 *      instead of travelling along the lines. Lowering the
 *      opacity compensated the ink and slowing the drift did not
 *      fix the direction, because it was the wrong motion rather
 *      than a fast one. See "THE MOTION IS NOT REPOINTABLE".
 *
 *   3. STATIC SVG (current). The geometry of (1), emitted as
 *      literal markup in the partial. It paints with the page's
 *      first paint exactly like (2) did — static markup needs no
 *      JS — and it is the real dashed field, so the ink weight
 *      and the motion are the approved ones. The MODULE was the
 *      thing worth retiring, not the SVG.
 *
 * Do not reintroduce `ge/background`, do not attach script to
 * this markup, and do not "simplify" it back to a gradient.
 *
 * ---- THE MOTION IS NOT REPOINTABLE WITHOUT DASHES ----------
 *
 * Worth recording, because shape (2) looked like a tuning
 * problem and was not. For lines `y = m·x + c`, translating the
 * whole field right by `dx` gives `y = m·x + (c − m·dx)` — i.e.
 * it is EXACTLY a vertical shift of `m·dx`. Horizontal and
 * vertical translation of a parallel-line field are the same
 * visual event, differing only by the `1/m = 5.6x` scale factor.
 * A solid band has no texture of its own, so EVERY possible
 * translation of a solid field reads as lines sliding
 * perpendicular — down or up the page. Only texture can move
 * along the axis, and the dashes are that texture: animating
 * `stroke-dashoffset` slides the dash pattern ALONG each line
 * while the line itself holds position. That is why the field
 * shimmers in place instead of sliding past, and why it reads
 * left-to-right.
 *
 * ---- GEOMETRY ----------------------------------------------
 *
 *   slope          0.18     — the field's diagonal, ~10.2deg.
 *   vertical gap   118px    — spacing between consecutive lines.
 *   stroke width   2.5px
 *   dasharray      420 1400 — a 1820px period with 420px of ink,
 *                             so ~23% of each line is painted.
 *                             THIS is the design's ink weight;
 *                             the solid-band experiment put it at
 *                             ~100% and needed the opacity cut to
 *                             .18/.07 to stay bearable. With the
 *                             dashes back, the opacities below are
 *                             the ORIGINAL .75 / .28 again.
 *   dashoffset     1820 -> 0 — one full period per cycle, so the
 *                             loop is seamless.
 *
 * Per-line `animation-duration` (38-75s) and `animation-delay`
 * (negative, so each line starts mid-cycle) are INLINE in the
 * partial, because they differ per line. That staggering is what
 * stops the dashes marching in lockstep, and it is the reason
 * this cannot be expressed as a single mask over one gradient.
 *
 * ---- SIZING ------------------------------------------------
 *
 * `.bg-lines` is pinned to its viewBox size in CSS PIXELS
 * (3840 x 33000), not `100% x 100%`. With
 * `preserveAspectRatio="none"` and a percentage size the field
 * stretches with the page — a short page squashes all 289 lines
 * into its height and flattens the slope with it, so every page
 * shows a different field. Pinned, one user unit is one CSS
 * pixel and the spacing/slope/stroke are identical everywhere;
 * `.site-background` clips the surplus with `overflow: hidden`.
 *
 * CEILING: a fixed line count means a finite field, and past it
 * a page shows bare `--bg`. 33000px was chosen against the
 * tallest page the site renders — the legal documents, which
 * `content/legal.html` renders in ONE pass (no pagination, no
 * accordion). Terms of Service is ~41,500 source characters,
 * which estimates to ~31,000px at a 360px phone width and
 * ~14,400px at 1280px. This height and `height: 33000px` below
 * must be changed TOGETHER, and together with the line count in
 * the partial. Do not switch to percentage sizing to dodge it.
 *
 * ---- LAYER -------------------------------------------------
 *
 *   .site-background — document-anchored full-height layer.
 *       `position: absolute` against <body> (which is
 *       `position: relative` per base.css), so the field scrolls
 *       WITH the page instead of sticking to the viewport.
 *       `z-index: -1` paints it above the body background colour
 *       but below ALL content, so pages need no z-index of their
 *       own (unlike the retired `.lines` motif, which required
 *       content at z-index 2). `pointer-events: none` — never
 *       intercepts clicks. `min-height` 100vh/100svh guarantees
 *       coverage on pages shorter than the viewport; the svh line
 *       matches the body's sticky-footer min-height on mobile so
 *       the layer cannot poke past the footer.
 *
 *       EVERY rule in this file is scoped under
 *       `.site-background`. A page may override
 *       `{% block background %}` with an empty body, so nothing
 *       here may land on `body` / `html` — a background-less page
 *       must inherit nothing from this file.
 *
 * Stroke colours are brand tokens (`--blue` / `--cyan` /
 * `--purple` / `--orange`), cycled TOP-TO-BOTTOM in that order.
 * They are theme-INVARIANT: declared once in `:root`
 * (tokens.css), not re-declared by `html[data-theme="dark"]`, and
 * `shared/theme.css` declares no custom properties at all. A
 * theme switch changes exactly one value — the layer's opacity.
 *
 * `prefers-reduced-motion`: dashes stop and settle at offset 0.
 * The field remains, fully painted; only the travel is dropped.
 *
 * ---- Center lift — REMOVED (lift-removal follow-up) ---------
 * The mockup's `.bg-lift` (fixed, 190px-blurred white ellipse)
 * produced a visible radiating glow / banding halo around
 * content and images in production and was removed entirely —
 * the partial no longer renders the element and no rules remain.
 * Do not re-add without a design round.
 * ============================================================ */

.site-background {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    min-height: 100vh;
    min-height: 100svh;
    z-index: -1;
    pointer-events: none;
    overflow: hidden;

    /* Dark theme; the light override is below. Back to the
     * design's original value now that the dashes carry the ink
     * weight again — see GEOMETRY in the header. */
    opacity: .75;
}

/* -------- Per-theme opacity --------
 * The ONLY theme-dependent value in the file. The stroke colours
 * are identical in both themes (see the header); vivid strokes
 * read as texture on the dark bg but would overpower a light
 * page, so the light layer is much fainter. */
html[data-theme="light"] .site-background {
    opacity: .28;
}

/* -------- The line field --------
 * Pinned to viewBox pixels, NOT 100% — see SIZING in the header.
 * `display: block` kills the inline-element baseline gap; the
 * layer clips whatever overflows. */
.site-background .bg-lines {
    position: absolute;
    top: 0;
    left: 0;
    width: 3840px;
    height: 33000px;
    display: block;
}

/* -------- The dashes --------
 * `stroke-dasharray: 420 1400` is the design's ~23% ink ratio.
 * Duration and delay are supplied per line by the inline style
 * in the partial; everything shared lives here. Starting offset
 * is the full period so the first painted frame already matches
 * a mid-animation frame. */
.site-background .bg-lines line {
    stroke-width: 2.5;
    stroke-dasharray: 420 1400;
    stroke-dashoffset: 1820;
    animation-name: ge-bg-dash;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
}

/* -------- Dash travel --------
 * One full dash period per cycle, so the end state is pixel-
 * identical to the start state and the loop has no seam.
 * DECREASING the offset moves the pattern from x1 toward x2,
 * i.e. left to right, which is the direction the design travels. */
@keyframes ge-bg-dash {
    from { stroke-dashoffset: 1820; }
    to   { stroke-dashoffset: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .site-background .bg-lines line {
        animation: none !important;
        stroke-dashoffset: 0;
    }
}
