/* sps-theme.css — POLISH LAYER ONLY.
 *
 * This file may 404 (wrong relative depth, opened from file://, an S3 miss).
 * When it does, every page must STILL be correctly laid out, contrast-compliant
 * and keyboard-navigable — because all of that lives inline in each page's own
 * <style> "floor" block (tokens, focus ring, reduced-motion guard, layout, CTA
 * colours). Nothing here is load-bearing.
 *
 * Additive properties only: background, box-shadow, border-color, filter, mask,
 * animation, and ::before/::after decoration. Never display / position /
 * grid-* / flex-* / width / max-width, and never a `color` on body text.
 *
 * Linked as:
 *   SinglePageSandbox/index.html          -> href="sps-theme.css"
 *   SinglePageSandbox/<Tool>/index.html   -> href="../sps-theme.css"
 *   SinglePageSandbox/FormulaWorksheetMaker/<x>-worksheet/index.html
 *                                         -> href="../../sps-theme.css"
 *
 * The top-level index.html / terms.html / about.html / 404.html deliberately
 * inline the handful of devices they use instead of linking this file, because
 * the repo's CLAUDE.md discourages cross-project utilities at the top level.
 */

/* Load beacon — sps-data.js warns in the console when this is absent. */
:root { --tng-theme: 1; }

/* ───────────────────────────────────────────────────────────────────────────
   D1 — Blueprint field.
   A measured hairline grid behind the page, masked so it only exists near the
   top. Reads as graph paper / schematic rather than as texture.
   ─────────────────────────────────────────────────────────────────────────── */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    repeating-linear-gradient(0deg,  var(--hairline) 0 1px, transparent 1px 72px),
    repeating-linear-gradient(90deg, var(--hairline) 0 1px, transparent 1px 72px);
  -webkit-mask-image: radial-gradient(120% 90% at 50% 0%, #000, rgba(0, 0, 0, .5) 45%, transparent 78%);
          mask-image: radial-gradient(120% 90% at 50% 0%, #000, rgba(0, 0, 0, .5) 45%, transparent 78%);
}
/* Lift real content above the field. Deliberately NOT `body > *` — the fixed
   toolbar, the three modals and #tng-gdpr are also body children and must keep
   their own stacking contexts. */
header, main, footer { position: relative; z-index: 1; }

/* ───────────────────────────────────────────────────────────────────────────
   D2 — Anisotropic conic aurora.
   Light with a direction and a hue sweep, like a spectrum off an anodised
   edge — instead of the symmetric radial blob every dark template uses.
   ─────────────────────────────────────────────────────────────────────────── */
.hero::before,
header.hero::before,
.brand::before,
.sps-hero::before {
  content: "";
  position: absolute;
  inset: -30% -10% auto -10%;
  height: 180%;
  pointer-events: none;
  filter: blur(38px);
  background: conic-gradient(from 210deg at 50% 0%,
    rgba(110, 168, 255, 0)   0deg,
    rgba(110, 168, 255, .26) 38deg,
    rgba(159, 123, 255, .22) 92deg,
    rgba(255, 204,  74, .10) 140deg,
    rgba(110, 168, 255, 0)   200deg);
  -webkit-mask-image: radial-gradient(70% 60% at 50% 8%, #000, transparent 72%);
          mask-image: radial-gradient(70% 60% at 50% 8%, #000, transparent 72%);
}

/* ───────────────────────────────────────────────────────────────────────────
   D3 — Edge-lit card border.
   A gradient hairline that catches light on the top-left corner. The two-value
   `<image> padding-box` syntax sets background-origin AND background-clip in
   one token, which is why this needs no wrapper element.
   ─────────────────────────────────────────────────────────────────────────── */
.u-edge,
.tile,
.pillar,
.card,
.faq details,
.demo-frame,
.spec-panel {
  border: 1px solid transparent;
  background:
    linear-gradient(180deg, var(--panel), var(--panel-2)) padding-box,
    var(--edge-lit) border-box;
  transition:
    background var(--dur-2) var(--ease-out),
    transform  var(--dur-2) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .u-edge:hover,
  .tile:hover,
  .card:hover {
    background:
      linear-gradient(180deg, var(--panel-2), var(--panel-3)) padding-box,
      linear-gradient(150deg, rgba(124, 196, 255, .75), rgba(159, 123, 255, .40) 40%, rgba(255, 255, 255, .05)) border-box;
    box-shadow: var(--elev-3), var(--glow-accent);
  }
}

/* ───────────────────────────────────────────────────────────────────────────
   D4 — Lit rule. One hairline of directional light across the top of a hero
   surface. Already invented on .brand::after; promoted here to a shared voice.
   ─────────────────────────────────────────────────────────────────────────── */
.u-lit-rule { position: relative; overflow: hidden; }
.u-lit-rule::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent) 22%, var(--accent-2) 62%, transparent);
  opacity: .75;
}

/* ───────────────────────────────────────────────────────────────────────────
   D5 — Corner ticks. Registration marks from a technical drawing. Used on
   .brand and .card only — on every tile they would become noise.
   ─────────────────────────────────────────────────────────────────────────── */
.u-ticks { position: relative; }
.u-ticks::before,
.u-ticks::after {
  content: "";
  position: absolute;
  width: 10px; height: 10px;
  pointer-events: none;
  border-style: solid;
  border-color: var(--border-lit);
}
.u-ticks::before { top: 9px;    left: 9px;   border-width: 1px 0 0 1px; }
.u-ticks::after  { bottom: 9px; right: 9px;  border-width: 0 1px 1px 0; }

/* ───────────────────────────────────────────────────────────────────────────
   D6 — Monospace eyebrow. The typographic voice; system mono only, no
   download. Colour is set inline in each page's floor so a 404 here cannot
   change a measured contrast pair.
   ─────────────────────────────────────────────────────────────────────────── */
.eyebrow::before {
  content: "";
  display: inline-block;
  width: 18px; height: 1px;
  vertical-align: middle;
  margin-right: 8px;
  background: linear-gradient(90deg, var(--accent), transparent);
}

/* ───────────────────────────────────────────────────────────────────────────
   D7 — Grain, image-free. Two out-of-phase checkers at coprime scales beat
   into something that reads as film grain rather than as a pattern.
   Kept off scrolling lists (mix-blend-mode forces a compositing layer).
   ─────────────────────────────────────────────────────────────────────────── */
.u-grain { position: relative; }
.u-grain::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  mix-blend-mode: overlay;
  opacity: .55;
  background:
    repeating-conic-gradient(rgba(255, 255, 255, .030) 0% 25%, rgba(0, 0, 0, .030) 0% 50%) 0 0 / 3px 3px,
    repeating-conic-gradient(rgba(255, 255, 255, .022) 0% 25%, rgba(0, 0, 0, .022) 0% 50%) 1px 2px / 5px 5px;
}

/* ───────────────────────────────────────────────────────────────────────────
   D8 — Travelling gradient stroke. The stroke moves on hover; the fill never
   does. Border colour carries the affordance, so --border-strong (3.57:1) is
   deliberate — see WCAG 1.4.11.
   ─────────────────────────────────────────────────────────────────────────── */
.btn.secondary,
.btn-ghost {
  border: 1px solid transparent;
  background:
    linear-gradient(var(--bg), var(--bg)) padding-box,
    linear-gradient(120deg, var(--border-strong), rgba(124, 196, 255, .65), var(--border-strong)) border-box;
  background-size: 100% 100%, 220% 100%;
  background-position: 0 0, 0 0;
  transition:
    background-position var(--dur-3) var(--ease-out),
    transform var(--dur-1) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .btn.secondary:hover,
  .btn-ghost:hover { background-position: 0 0, 100% 0; transform: translateY(-1px); }
}

/* ───────────────────────────────────────────────────────────────────────────
   .specline — monospace telemetry strip. The 1px-gap trick means a wrapped
   row never shows a hanging divider.
   ─────────────────────────────────────────────────────────────────────────── */
.specline { background: var(--border); border-color: var(--border); box-shadow: var(--elev-1); }
.specline > span { background: linear-gradient(180deg, var(--panel), var(--panel-2)); }
.specline i {
  background: var(--accent);
  box-shadow: 0 0 0 3px rgba(110, 168, 255, .18);
}

/* ───────────────────────────────────────────────────────────────────────────
   Entrance motion. The whole rule lives inside no-preference so a
   reduced-motion user never receives an animation-fill-mode that could leave
   content at opacity:0.
   ─────────────────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
  .u-rise { animation: tngRise var(--dur-4) var(--ease-spring) both; }
  .u-rise:nth-child(2) { animation-delay:  60ms; }
  .u-rise:nth-child(3) { animation-delay: 120ms; }
  .u-rise:nth-child(4) { animation-delay: 180ms; }
  @keyframes tngRise {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: none; }
  }
}
