/* ============================================================================
   LASER Holographic — design layer for the Payment Portal
   ============================================================================

   Ports laser-platform/packages/frontend/src/theme/{tokens,animations,
   glassComponents}.ts to CSS custom properties, so the Django portal wears the
   SAME identity as the LASER app rather than an approximation of it.

   ⚠️ WHY THIS FILE EXISTS AT ALL — the portal was already "matched to LASER"
   once, and matched to the WRONG LASER. laser-platform holds two themes:

     packages/frontend/src/theme/theme.ts   dark  #0A0A0A / cyan #00E5FF  ← the app
     packages/payment-portal/src/theme.ts   light #f5f5f5 / blue #1565c0  ← a skeleton

   styles.css was tokenised against the SECOND one (read its own comments:
   "LASER is flat", "No lift — LASER's MuiButton carries no elevation"). Both
   statements are true of the 46-line skeleton and false of the app the owner
   actually looks at, which has glass-morphism, cyan glow and holographic
   gradient headings. That mismatch — not a missing palette — is why the UI was
   described as looking like "the 80s and 90s": measured against styles.css,
   5,712 lines carried 1 gradient, 1 keyframe and 1 backdrop-filter.

   ⛔ LOAD ORDER MATTERS. This file must come AFTER styles.css and BEFORE the
   per-org `<style id="dynamic-theme">` block in base.html. It deliberately
   re-points styles.css's existing tokens (--elevation-*, --border-radius-*)
   instead of restyling components one by one, so ~5,700 lines of existing rules
   inherit the new identity without being touched. An organization palette still
   wins, because it is emitted later at :root — multi-tenant branding is a
   product requirement (ColorScheme.to_css_variables), not decoration.

   Source of truth for every value below: the laser-platform files named above.
   ============================================================================ */

/* ─── Brand tokens (tokens.ts → COLORS) ──────────────────────────────────── */

:root {
    /* Electric cyan — "laser blue". The single hue the whole identity hangs on. */
    --laser-primary: #00E5FF;
    --laser-primary-dark: #00B8D4;
    --laser-primary-light: #B2EBF2;

    /* Secondary / accent, and the pink that terminates the holographic sweep. */
    --laser-secondary: #7C4DFF;
    --laser-accent-pink: #FF4081;

    /* ⛔ #2085: `--laser-secondary` is a FILL and a GRADIENT STOP, never body text on a dark
       ground. Measured against the composited `.status-settled` chip fill (#7C4DFF at 14%
       over --laser-bg-card = #28213A) it reads **3.19:1** — below the 4.5 AA bar, and the
       only non-exempt failure in the palette. This lighter tint of the SAME hue reads
       **5.98:1** on that fill.
       The base token is deliberately unchanged: it must keep equalling laser-platform's
       `COLORS.SECONDARY` (packages/frontend/src/theme/tokens.ts), which is the whole point
       of the layer. Text takes this token instead. */
    --laser-secondary-text: #A991FF;

    /* Grounds. Three steps, not two: page < card < appbar-as-recess. */
    --laser-bg-page: #0A0A0A;
    --laser-bg-card: #1A1A1A;
    --laser-bg-appbar: #0D0D0D;

    /* Text. Alpha-based, so it composites over any ground including glass. */
    --laser-text-primary: #FFFFFF;
    --laser-text-secondary: rgba(255, 255, 255, 0.7);
    --laser-text-disabled: rgba(255, 255, 255, 0.4);

    /* Lines. The cyan tint is what makes a 1px border read as "LASER" rather
       than as a generic dark theme — it is the cheapest brand signal here. */
    --laser-border-cyan: rgba(0, 229, 255, 0.2);
    --laser-border-divider: rgba(0, 229, 255, 0.15);

    /* ⛔ #2085: the two tints above are DECORATIVE, and that is measured, not assumed.
       Composited over the card ground they read 1.60:1 (0.2) and 1.38:1 (0.15) — far
       below WCAG SC 1.4.11's 3.0 bar for a non-text boundary.
       That is fine for a DIVIDER (it separates content that is itself legible) and for a
       card edge (the glass fill and the page's own structure carry the grouping).
       It is NOT fine for a FORM CONTROL: an input's border is what tells a user where the
       field IS, so 1.4.11 applies to it directly. Measured on the live page, an input had
       fill 1.08:1 and border 1.60:1 against the ground — its extent was conveyed by
       nothing perceivable until it took focus. A real failure, found by measuring every
       pair rather than sampling.
       0.5 alpha = 3.72:1 over the card: clears 3.0 with margin, still reads as a tinted
       hairline rather than a hard outline. Used by every input/select/textarea. */
    --laser-border-control: rgba(0, 229, 255, 0.5);

    /* Status. Kept at LASER's values so a chip means the same thing in both apps. */
    --laser-success: #00E676;
    --laser-warning: #FFD740;
    --laser-error: #FF5252;
    --laser-info: #448AFF;

    /* Interaction washes — the hover/selected tints used across MUI's overrides. */
    --laser-wash-4: rgba(0, 229, 255, 0.04);
    --laser-wash-8: rgba(0, 229, 255, 0.08);
    --laser-wash-12: rgba(0, 229, 255, 0.12);

    /* ─── Gradients (tokens.ts → GRADIENTS) ─────────────────────────────── */
    /* CD-rainbow refraction. Starts and ends on cyan so a 200% background-size
       sweep loops seamlessly (see --laser-shimmer). */
    --laser-holographic: linear-gradient(135deg, #00E5FF 0%, #7C4DFF 33%, #FF4081 66%, #00E5FF 100%);
    --laser-surface-gradient: linear-gradient(180deg, rgba(0, 229, 255, 0.05) 0%, transparent 100%);

    /* ─── Glow (tokens.ts → GLOW) ───────────────────────────────────────── */
    --laser-glow-subtle: 0 0 10px rgba(0, 229, 255, 0.15);
    --laser-glow: 0 0 20px rgba(0, 229, 255, 0.3);
    --laser-glow-intense: 0 0 30px rgba(0, 229, 255, 0.5);
    --laser-glow-purple: 0 0 20px rgba(124, 77, 255, 0.3);

    /* ─── Glass (tokens.ts → GLASS) ─────────────────────────────────────── */
    /* ⛔ 0.60, NOT 0.70, AND TINTED — this is the value that decides whether the glass
       is VISIBLE, and the old one made every other glass property inert. At
       `rgba(26,26,26,0.7)` the fill sat within ~16 units of the page ground on a 255
       scale, so a correctly-applied `backdrop-filter` produced a flat dark box: the
       effect was present in the cascade and invisible on screen, reported three times
       before it was measured.
       ★ Owner ruling 2026-09-09 (sample "G"): 0.55 — lighter still, so more of the
       ambient wash reads through. G is C+E merged: Apple's top-lit sheen and specular
       lip over the cyan/violet brand tint, so the glass is unmistakably glass AND
       unmistakably this product's, rather than generic frosted grey.
       ⚠️ The sheen below stacks TWO gradients over this fill — a white top-light and
       the brand tint — in that order. Reversing them puts the tint over the highlight
       and the pane stops reading as lit from above. */
    /* ⚠️ 0.07 -> 0.045 (owner, 2026-09-09: "make the cards a bit more transparent").
       The ground behind it is the 44px grid + aurora, so a lower fill lets more of that
       read through the pane — which is the point of the glass, and what the earlier
       near-black page could not show at any alpha.
       ⛔ Do not push this to 0 chasing "more glass": the fill is what separates a card
       from the page, and body text sits on it. At 0.045 over #0A0A0A the card is still a
       distinct surface; below ~0.03 the border becomes the only thing defining the box. */
    --laser-glass-bg: rgba(255, 255, 255, 0.045);
    --laser-glass-blur: blur(20px);

    /* ⛔ THE GLASS VOCABULARY, AS THREE TOKENS — because it was duplicated NINE times.
       Before this, `.laser-glass` carried the 2026 treatment (top-lit gradient, saturate,
       specular edge) while nine other surfaces — panels, cards, the app bar, modals, both
       KPI tiles, the nav menu and three notification surfaces — hand-rolled the older
       `background + blur + border` recipe inline. They were not "missing glass"; they were
       running a SECOND, older definition of it, which is why only badges looked current.

       ★ Re-deriving a shared rule at nine call sites is the repo's most-repeated defect
       (AGENTS.md §5). Tokens make the treatment single-sourced: a surface opts in by
       consuming these three, and the two accessibility opt-outs below re-point the tokens
       ONCE instead of needing a per-surface override for every consumer.

       ⚠️ These are consumed as whole property values (`background: var(--laser-glass-sheen)`),
       not composed into one another, so a consumer can take the sheen without the shadow —
       the app bar does exactly that. */

    /* ⛔ FLAT FILL, NO GRADIENT — owner ruling 2026-09-09, sample B ("lighter tint").
       This token previously stacked a 160deg top-lit gradient over the fill (sample C).
       The two were shown side by side and the gradient was rejected: it reads as a
       sheen painted ON the panel rather than as a tinted pane, and on a tall card the
       fade to transparent at 72% leaves the lower half visibly darker than the upper.
       A single even tint is the chosen look, so the sheen IS the fill.

       ⚠️ Kept as its own token rather than collapsed into `--laser-glass-bg`: the two
       accessibility opt-outs below re-point `--laser-glass-sheen`, and nine surfaces
       consume it as a whole `background` value. */
    --laser-glass-sheen: var(--laser-glass-bg);

    /* `saturate` is the second half of the frosted look: blur alone greys the backdrop,
       and pulling saturation back up keeps the cyan/violet ambient wash reading as colour
       rather than as mud. */
    --laser-glass-filter: var(--laser-glass-blur) saturate(160%);

    /* ★ THE SPECULAR EDGE — the single detail separating "glass" from "dark box". A 1px
       inset highlight on the TOP edge only, reading as light catching the lip of the pane,
       plus a soft outer shadow so the panel sits above the backdrop rather than being
       painted onto it. */
    /* ⛔ A SEPARATE BORDER TOKEN FOR GLASS, not a bump to `--laser-border-cyan`.
       That token is read by 31 surfaces — dividers, rails, table hairlines — and
       raising it to E's 0.35 would brighten every line in the product to make one
       surface look right. Glass panes carry a stronger rim than a divider does,
       which is a real distinction, so it gets its own value.
       ⚠️ Still decorative, not a control boundary: #2085 measured these tints at
       1.38-1.60:1 and ruled that acceptable for a card EDGE (the fill and the page
       structure carry the grouping) but NOT for a form control, which keeps
       `--laser-border-control` at 0.5. That ruling is unchanged. */
    --laser-glass-border: rgba(255, 255, 255, 0.16);

    /* ⛔ ONE soft inset highlight, not C's specular lip. The previous value was
       `0.85` white with a dark bottom inset — a bright chrome edge that belongs to the
       gradient treatment rejected above; against a flat tint it reads as a rim rather
       than as glass. B's 22% top inset is the whole edge now, over a soft cast shadow
       so the pane still sits above the backdrop instead of being painted onto it. */
    --laser-glass-edge:
        inset 0 1px 0 rgba(255, 255, 255, 0.22),
        0 12px 40px rgba(0, 0, 0, 0.40);

    /* ─── Motion (tokens.ts → TRANSITIONS) ──────────────────────────────── */
    --laser-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
    --laser-transition: 250ms cubic-bezier(0.4, 0, 0.2, 1);
    --laser-transition-slow: 400ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* ─── Adoption: re-point styles.css's own tokens ─────────────────────────────

   Scoped to .laser-theme on <body> rather than bare :root, so the layer is a
   deliberate opt-in and every surface can be compared against the old look by
   toggling one class. This is also what keeps the old light palette reachable
   for any org that has not moved.

   ⚠️ These are the SEAMS the existing stylesheet already reads. Setting them
   here restyles rules that will never be edited — which is the only reason a
   full re-skin is affordable at 36,630 template lines.
   -------------------------------------------------------------------------- */

body.laser-theme {
    /* Palette → the tokens every existing rule consumes. */
    --primary-color: var(--laser-primary);
    --primary-hover-color: var(--laser-primary-dark);
    --primary-active-color: #0093AB;      /* one step darker again, for :active */
    --on-primary: var(--laser-bg-page);   /* cyan is LIGHT: text on a cyan fill must be near-black */
    --primary-text: var(--laser-primary); /* brand-as-text, on a dark ground */

    --text-primary-color: var(--laser-text-primary);
    --text-secondary-color: var(--laser-text-secondary);
    --text-light-color: var(--laser-text-disabled);
    --link-color: var(--laser-primary);

    --background-color: var(--laser-bg-page);
    --surface-color: var(--laser-bg-card);
    --surface-alt-color: #222222;         /* between card and page: the "raised" step */
    --border-color: var(--laser-border-cyan);

    --success-color: var(--laser-success);
    --warning-color: var(--laser-warning);
    --error-color: var(--laser-error);
    --info-color: var(--laser-info);

    /* Status fills/text/borders. Derived as a low-alpha wash of each hue plus the
       hue itself as text — on a dark ground a *tinted* fill keeps the hue legible,
       whereas the light theme's pale pastels turn to mud. */
    --success-bg-color: rgba(0, 230, 118, 0.12);
    --success-text-color: var(--laser-success);
    --success-border-color: rgba(0, 230, 118, 0.4);
    --error-bg-color: rgba(255, 82, 82, 0.12);
    --error-text-color: var(--laser-error);
    --error-border-color: rgba(255, 82, 82, 0.4);
    --warning-bg-color: rgba(255, 215, 64, 0.12);
    --warning-text-color: var(--laser-warning);
    --warning-border-color: rgba(255, 215, 64, 0.4);
    --info-bg-color: rgba(68, 138, 255, 0.12);
    --info-text-color: var(--laser-info);
    --info-border-color: rgba(68, 138, 255, 0.4);

    /* Text ON a filled control. Every LASER status hue is light, so near-black
       reads and white does not — this is the pair that fails silently if guessed. */
    --on-success: var(--laser-bg-page);
    --on-error: var(--laser-bg-page);
    --on-warning: var(--laser-bg-page);
    --on-info: var(--laser-bg-page);

    --header-background-color: var(--laser-bg-appbar);
    --footer-text-color: var(--laser-text-secondary);

    /* Shape (theme.ts → RADIUS). 8 is the BASE step, not the small one. */
    /* #2280 — HARD radius scale. Mirrors _01-base-and-tokens.css; this block
       WINS on specificity AND on load order, so the two must never disagree.

       ⛔ THEY DID DISAGREE, AND THIS FILE SILENTLY WON. `_01` was re-pointed to the
       approved 6/8/12 scale and this block was left at 3/6/8 -- so all 117 call
       sites kept rendering the legacy corners while the "fix" sat in a file that
       loses. A `.summary-card` drew a 3px corner where the approved sample draws
       12px, and that is exactly what read as edgy.

       ★ The lesson: when a token is declared twice, editing the losing copy
       changes nothing and looks like the change didn't apply. Verify with
       getComputedStyle, not grep. */
    --border-radius-sm: 6px;
    --border-radius-md: 8px;
    --border-radius-lg: 12px;

    /* Depth. styles.css pins --elevation-flat: none because the SKELETON theme is
       flat. The app is not: it separates with a cyan hairline and lifts on hover
       with glow instead of a grey drop shadow. Re-pointing the seam gives every
       `box-shadow: var(--elevation-flat)` rule the brand's depth at once. */
    --elevation-flat: 0 0 0 1px var(--laser-border-divider);

    background-color: var(--laser-bg-page);
    color: var(--laser-text-primary);
}

/* ─── The tenant escape hatch ────────────────────────────────────────────────

   Owner decision (2026-09-02): LASER is the default for EVERY tenant today, and
   the per-tenant palette must still work "in future if anyone wants to modify".

   Those requirements conflict on one mechanic. An organization's ColorScheme is
   emitted as custom properties at `:root` (specificity 0,1,0) while the block
   above sets the same properties on `body.laser-theme` (0,1,1) — so LASER wins
   automatically, which is the wanted DEFAULT but would make tenant branding
   permanently unreachable.

   `.laser-defer-palette` is the release valve. The context processor adds it only
   when `organization.applied_color_scheme_id` is set, i.e. when an operator has
   deliberately applied a palette. Each declaration then reads
   `var(--<org-token>, <laser value>)`, so:

     · no palette applied → the org token is not defined → LASER's value is used
     · palette applied    → the org token IS defined at :root → the tenant wins

   ⚠️ Why re-declare instead of just deleting the LASER block for these orgs: the
   fallback form keeps LASER as the answer for any token a partial palette omits.
   A ColorScheme is fourteen independent hex fields, and an org that sets only a
   primary must not lose its grounds to an undefined value.

   ⛔ Only COLOUR defers. Radii, depth, glass, glow and motion stay LASER for
   everyone — that is the "one product" half of the decision, and it is why a
   tenant palette produces a branded LASER surface rather than a second design.
   -------------------------------------------------------------------------- */

body.laser-theme.laser-defer-palette {
    --primary-color: var(--org-primary-color, var(--laser-primary));
    --primary-hover-color: var(--org-primary-hover-color, var(--laser-primary-dark));
    --primary-active-color: var(--org-primary-active-color, #0093AB);

    --text-primary-color: var(--org-text-primary-color, var(--laser-text-primary));
    --text-secondary-color: var(--org-text-secondary-color, var(--laser-text-secondary));
    --text-light-color: var(--org-text-light-color, var(--laser-text-disabled));

    --background-color: var(--org-background-color, var(--laser-bg-page));
    --surface-color: var(--org-surface-color, var(--laser-bg-card));
    --surface-alt-color: var(--org-surface-alt-color, #222222);
    --border-color: var(--org-border-color, var(--laser-border-cyan));

    --success-color: var(--org-success-color, var(--laser-success));
    --warning-color: var(--org-warning-color, var(--laser-warning));
    --error-color: var(--org-error-color, var(--laser-error));
    --info-color: var(--org-info-color, var(--laser-info));

    --header-background-color: var(--org-header-background-color, var(--laser-bg-appbar));
    --footer-text-color: var(--org-footer-text-color, var(--laser-text-secondary));

    background-color: var(--background-color);
    color: var(--text-primary-color);
}

/* ─── Motion (animations.ts) ─────────────────────────────────────────────── */

@keyframes laser-fade-in-up {
    from { opacity: 0; transform: translateY(20px); }
    to   { opacity: 1; transform: translateY(0); }
}

@keyframes laser-fade-in-scale {
    from { opacity: 0; transform: scale(0.95); }
    to   { opacity: 1; transform: scale(1); }
}

@keyframes laser-pulse {
    0%, 100% { box-shadow: 0 0 10px rgba(0, 229, 255, 0.2); }
    50%      { box-shadow: 0 0 25px rgba(0, 229, 255, 0.5); }
}

@keyframes laser-shimmer {
    0%   { background-position: -200% center; }
    100% { background-position: 200% center; }
}

/* ⛔ Every animation and transition in this file is switched off here, in ONE
   place, rather than per-rule. A holographic identity is decorative by
   definition, so `prefers-reduced-motion` must strip it completely — LASER's
   own presets do the same (ANIMATION_PRESETS spreads a reduced-motion reset into
   each one, and REDUCED_MOTION_MEDIA_QUERY cancels the hover transform). */
@media (prefers-reduced-motion: reduce) {
    body.laser-theme *,
    body.laser-theme *::before,
    body.laser-theme *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ─── Holographic text (glassComponents.ts → HolographicText) ────────────── */

/* ⛔ THE GRADIENT WAS REMOVED FROM PAGE TITLES 2026-09-08. `/DESIGN.md` bans it in one
   line -- "Don't use a shadow, glow, glass, gradient text, emoji-as-icon, or a display
   face" -- and it was the single most visible reason the product did not read as one
   product: it applied to `.page-header h1` on 15+ pages while `Recurring Payment Plans`,
   `Process Payment`, `Branding & Colors` and `Integration Status` rendered plain white,
   so the SAME element had two identities depending on which template drew it.

   ★ `.laser-holo` keeps the effect as an OPT-IN class for the rare hero or empty state
   that wants it. What is withdrawn is the automatic application to every page title.
   A page title is `--font-size-page-title` at `--text-primary-color`; the hierarchy comes
   from size and weight, which survive greyscale and forced-colors without a fallback. */
body.laser-theme .laser-holo {
    background: var(--laser-holographic);
    background-size: 200% auto;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    display: inline-block;
    font-weight: 700;
    letter-spacing: -0.02em;
}

/* The page title, plainly. One treatment, every page. */
body.laser-theme .page-header h1,
body.laser-theme h1.laser-title {
    color: var(--text-primary-color);
    font-weight: 700;
    letter-spacing: -0.02em;
}

/* Animated variant — opt-in, for a hero or an empty state, never for a heading a
   CSR reads fifty times a day. */
body.laser-theme .laser-holo-animate {
    animation: laser-shimmer 3s linear infinite;
}

/* ⛔ Forced-colors / high-contrast: transparent text is INVISIBLE there, and a
   gradient is not rendered at all. Restore a real colour or the page loses its
   headings entirely — this is a genuine a11y failure, not a cosmetic downgrade. */
@media (forced-colors: active) {
    body.laser-theme .laser-holo,
    body.laser-theme .page-header h1,
    body.laser-theme h1.laser-title {
        background: none;
        -webkit-text-fill-color: currentColor;
        color: CanvasText;
    }
}

/* ─── Glass surfaces (glassComponents.ts → GlassCard) ────────────────────── */

/* ⛔ `.section-card` IS THE CARD NOW (owner ruling, 2026-09-09). The project ran
   two card designs side by side: `.section-card` (the partial's box) and
   `.section-card` (hand-rolled in csr_account_detail / loa_history). MEASURED on
   /csr/account/<id>/: Overview rendered `rgba(255,255,255,0.06)` + `blur(24px)`,
   while the Payments/Activity/Notes/Documents tabs rendered an opaque
   `rgb(26,26,26)` with a cyan border — the same page, two different cards.

   The owner picked `.section-card`'s DESIGN (the head band, the 12px radius, the
   body padding as an explicit opt-in) and this glass FILL. So the two are joined
   here rather than one being converted into the other: `.section-card` keeps its
   markup contract for the 11 templates that emit it, and both boxes now resolve
   to one surface. Adding them to this selector — not copying the declarations —
   is what stops the pair drifting apart again.

   ⚠️ This must beat `.card, .section-card` in _10-components.css, which sets
   `background: var(--surface-color)` and `border-color: var(--border-color)`.
   Under the laser theme those tokens resolve to `--laser-bg-card` (opaque) and
   `--laser-border-cyan` — which is precisely the mismatch above. `body.laser-theme`
   is (0,2,0) against that file's (0,1,0), so specificity carries it without a
   priority flag; the radius is restated because `--border-radius-md` (6px) would
   otherwise overwrite `.section-card`'s approved `--r-card` (12px). */
body.laser-theme .laser-glass,
body.laser-theme .section-card,
body.laser-theme .card {
    background: var(--laser-glass-sheen);
    -webkit-backdrop-filter: var(--laser-glass-filter);
    backdrop-filter: var(--laser-glass-filter);
    border: 1px solid var(--laser-glass-border);
    border-radius: var(--r-card);
    box-shadow: var(--laser-glass-edge);
    transition: box-shadow var(--laser-transition), transform var(--laser-transition);
}

body.laser-theme .laser-glass:hover,
body.laser-theme .section-card:hover,
body.laser-theme .card:hover {
    box-shadow: var(--laser-glow);
    transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
    body.laser-theme .laser-glass:hover,
    body.laser-theme .section-card:hover,
    body.laser-theme .card:hover { transform: none; }
}

/* ─── The two glass opt-outs, applied at the TOKEN, not per selector ─────────

   ⛔ THIS IS THE WHOLE REASON THE VOCABULARY WAS TOKENISED. Both blocks below
   re-point the three glass tokens on `body.laser-theme`, so EVERY surface that
   consumes them opts out together — `.laser-glass`, panels and cards, the app bar,
   modals, both KPI tiles, the nav menu and the notification surfaces.

   Written as per-selector overrides instead, each block would need a ten-selector
   list that a future glass surface would silently omit — which is exactly what had
   already happened: nine hand-rolled glass surfaces existed and NONE of them
   honoured `prefers-reduced-transparency`, because the opt-out named only the one
   class it was written beside.

   ⚠️ A surface that hard-codes its own blur instead of consuming the token is NOT
   covered. Two deliberately keep their own: the modal scrim and the sidebar overlay,
   which are 4px/2px dimming veils behind a dialog rather than glass panes. They are
   handled at their own call sites.
   -------------------------------------------------------------------------- */

/* `backdrop-filter` is the one property here with a real support gap. Without this
   fallback a glass surface is 70%-transparent over the page ground and its text
   contrast collapses — so opt OUT of translucency rather than in. With no blur there
   is no pane for a specular edge to sit on, so the sheen and the edge go too. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    body.laser-theme {
        --laser-glass-sheen: var(--laser-bg-card);
        --laser-glass-edge: none;
    }
}

/* ⛔ `prefers-reduced-transparency` — a 2026 glassmorphism requirement, and the reason
   it matters is not aesthetic. Translucency over a busy ground is exactly what some
   users with low vision, vestibular sensitivity or migraine disable at the OS level
   (Windows "Transparency effects", macOS "Reduce transparency"). A user who has asked
   the system for opaque surfaces must not still get a 70%-transparent card with the
   page showing through its text.
   ★ Paired with the `@supports` fallback above, which handles the "cannot blur" case;
   this one handles "can, but was asked not to". Both land on the same opaque token, so
   there is one answer to "what does a non-glass surface look like".
   ⚠️ Blur is dropped as well as opacity — a blur behind an opaque background paints
   nothing and still costs GPU time, which is the property's documented cost on low-end
   hardware. The GRADIENT goes too: a highlight sweep exists to make a translucent pane
   read as glass, and on an opaque card it is only noise. */
@media (prefers-reduced-transparency: reduce) {
    body.laser-theme {
        --laser-glass-sheen: var(--laser-bg-card);
        --laser-glass-filter: none;
        --laser-glass-edge: none;
        /* The rim drops back to the ordinary card line too. E's brighter border reads
           as light on a translucent pane; on an opaque card it is just a loud outline. */
        --laser-glass-border: var(--laser-border-cyan);
    }
}

/* ⛔ PRINT — the third opt-out, and it MUST live in this file, not beside the print block
   in `_11-theme-chrome.css` where it was first written.
   `_11` arrives via `styles.css`'s `@import` tree, which `base.html` loads at line 324;
   this file is loaded at line 336. The unconditional `body.laser-theme` token block near
   the top of this file and a `body.laser-theme` block inside `_11`'s `@media print` have
   IDENTICAL specificity (0,2,1), so source order decides — and this file is later, so a
   print-token override written in `_11` is overwritten by the normal tokens here and
   silently does nothing. Declared here, it wins the same way.
   ★ This is the trap `laser.css`'s own radius comment already records: when a token is
   declared twice, editing the losing copy changes nothing and looks like the change did
   not apply.

   `_11`'s print block still forces `color` / `border-color` on four named selectors —
   those are not part of the glass vocabulary. What this adds is that all twelve glass
   surfaces (and any added later) lose their sheen, blur and specular edge on paper,
   instead of only the four that block happens to name. A dark ground wastes toner and a
   frosted panel prints as grey mud. */
@media print {
    body.laser-theme {
        --laser-glass-sheen: #fff;
        --laser-glass-filter: none;
        --laser-glass-edge: none;
    }
}

/* Entrance. Applied to cards and panels, capped at a few staggered steps: a long
   ladder of delays makes a data-dense page feel SLOW rather than polished. */
body.laser-theme .laser-enter {
    animation: laser-fade-in-up var(--laser-transition-slow) both;
}

body.laser-theme .laser-enter-1 { animation-delay: 40ms; }
body.laser-theme .laser-enter-2 { animation-delay: 80ms; }
body.laser-theme .laser-enter-3 { animation-delay: 120ms; }
body.laser-theme .laser-enter-4 { animation-delay: 160ms; }

/* Active/processing accent. Infinite, so reserve it for something that is
   genuinely in-flight — a pending charge, a running job — never for decoration. */
body.laser-theme .laser-active {
    animation: laser-pulse 2s ease-in-out infinite;
}


/* ─── The ambient backdrop — WHAT THE GLASS IS GLASS OVER ─────────────────── */

/* ⛔ WITHOUT THIS, EVERY `backdrop-filter` IN THIS FILE RENDERS NOTHING, and that
   is measured, not assumed. Before this rule the page ground was a flat
   `rgb(10, 10, 10)` with `background-image: none` (read from the live page via
   DevTools). `.laser-glass` was correctly applying `rgba(26, 26, 26, 0.7)` and
   `blur(16px)` — but blurring a flat colour returns that same flat colour, and
   tinting near-black with near-black moves it about 16 units on a 255 scale.
   The effect was present in the cascade and invisible on screen.

   ★ Frosted glass needs something BEHIND it. These three fixed radial washes are
   that something: brand cyan and violet at 4-7% over the page ground, so a card
   frosts over real colour instead of over nothing. One rule fixes every glass
   surface in the product at once — panels, the app bar, modals, the nav — which
   is why the fix belongs here and not on each component.

   ⚠️ `fixed` attachment, not `scroll`: a backdrop that scrolls with the content
   makes the blur shimmer as the page moves, which reads as jank on exactly the
   low-end hardware the blur already taxes.

   ⚠️ Opacities are deliberately LOW (0.04-0.07). This is a payment surface: the
   wash exists to give the glass something to refract, never to compete with text.
   Raising these is how a decorative backdrop starts failing contrast on the
   content above it. */
/* ─── The page ground: blueprint grid + top light (owner ruling, 2026-09-09) ───

   ⛔ THIS IS WHAT MAKES THE GLASS READ AS GLASS, and that is why it is not
   decoration. `backdrop-filter` blurs WHAT IS BEHIND an element — so over a flat
   ground it composites nothing and every glass surface in the product renders as a
   plain translucent box. The panels were tuned three times against a page that had
   nothing for them to refract.

   ⚠️ The previous value was not missing, it was INVISIBLE: three radial gradients at
   0.04-0.07 alpha over `#0A0A0A` (near-pure black), which is under the threshold
   where an 8-bit channel can even show a step. It read as flat black, and the owner
   reported it as such.

   The layers, in paint order (first = on top):
     1+2. the 44px grid, ON TOP so it is the layer the blur actually bends — a regular
        rule distorting behind a panel is what the eye reads as a refracting surface,
        where a smooth gradient just looks like a lighter box. It sits above the
        gradients so the aurora tints it rather than washing it out;
     3. cyan, top-left — the dominant light;
     4. violet, top-right — the second hue, which is what stops the page reading as
        one flat colour cast;
     5. cyan again, bottom-centre, so the lower half is not dead ground.

   ⚠️ The three radials are at 0.10-0.18 alpha. They were 0.04-0.07, which is under
   the step an 8-bit channel can show over #0A0A0A — present in the file, invisible on
   screen, and reported by the owner as "complete black background".

   ⚠️ `background-attachment: fixed` on ALL layers, so the grid does not scroll with
   the content — a moving grid behind a static panel reads as a texture sliding under
   glass, which is the opposite of the intended stillness.
   ⚠️ `background-repeat` is per-layer and POSITIONAL: the two grid layers repeat, the
   three gradients do not. Getting this list out of step with the image list tiles a
   gradient across the viewport. */
body.laser-theme {
    background-color: var(--laser-bg-page);
    background-image:
        linear-gradient(rgba(255, 255, 255, 0.028) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255, 255, 255, 0.028) 1px, transparent 1px),
        radial-gradient(52rem 34rem at 8% -6%, rgba(0, 229, 255, 0.18), transparent 58%),
        radial-gradient(46rem 32rem at 96% 6%, rgba(124, 77, 255, 0.16), transparent 60%),
        radial-gradient(44rem 30rem at 46% 104%, rgba(0, 229, 255, 0.10), transparent 62%);
    background-size: 44px 44px, 44px 44px, auto, auto, auto;
    background-attachment: fixed;
    background-repeat: repeat, repeat, no-repeat, no-repeat, no-repeat;
}

/* The two accessibility opt-outs the glass itself honours apply here too — a wash
   that exists to be refracted has no purpose once the refraction is switched off,
   and print reverts to ink on white (see the @media print block above). */
@media (prefers-reduced-transparency: reduce) {
    body.laser-theme {
        background-image: none;
    }
}
