/* ============================================================
   BAC · surface.css — the warmth layer: one texture recipe
   Single definition.
   Status: PROPOSED. Zero-build: linked with a plain <link>, no
   pipeline. This file is the ONLY place the recipe lives; both
   00-primitives.html (grounds § 05 C) and 01-surfaces.html link
   it. Do not re-declare it inline anywhere.

   Rules: docs/design-rules.md § 5.

   Usage:  <div class="surface s-ink">…</div>
           or set --c directly: style="--c:#042839"

   RESTRICTED to the four deep tones — ink · slate · muted · mist.
   Card, Paper and Sky are NOT textured grounds: below Mist the
   ramp's shade end greys a near-white instead of deepening it,
   and Sky stays fenced to the story layer (§ 1).

   How it works: every stop is the block's OWN colour moved by a
   fixed OKLCH delta — lightness shifts, chroma is held, hue drifts
   cool→warm as it descends. No white, no black, no overlay alphas.
   Fixed *perceptual* deltas are what let one recipe treat a navy
   and a near-white identically; percentage mixes cannot.

   Degradation: without relative-colour-syntax support the whole
   background-image stack is dropped and the flat base colour
   remains — which is already the intended one-ink fallback.
   ============================================================ */

/* grain: static paper noise — fine and low-contrast, so it reads as paper
   tooth and never as visible static on large dark grounds.
   Mirrors assets/textures/grain.svg exactly.

   --grain and --grain-alpha live in tokens/tokens.css and are inherited from
   :root, so both .surface and .expanse draw from the same tile. This sheet
   used to carry a SECOND :root copy of both, byte-identical to the spine's.
   Removed 7 Aug 2026 — a hand-kept duplicate is the exact drift class
   tools/check.py C1 exists to catch, and it is how 01-surfaces went two
   rounds stale. One declaration, in the spine. */

.surface {
  /* the ramp — fixed OKLCH deltas off --c */
  --pale:   oklch(from var(--c) calc(l + 0.105) calc(c * 0.90) calc(h - 7));
  --light:  oklch(from var(--c) calc(l + 0.052) calc(c * 0.95) calc(h - 4));
  --mid:    var(--c);
  --warmed: oklch(from var(--c) calc(l - 0.022) calc(c * 1.10) calc(h + 7));
  --dark:   oklch(from var(--c) calc(l - 0.072) calc(c * 1.05) calc(h + 4));
  --deep:   oklch(from var(--c) calc(l - 0.130) calc(c * 1.02) calc(h + 2));

  /* fade partners — same colour at zero alpha, so nothing greys out mid-blend */
  --pale-0:   oklch(from var(--pale)   l c h / 0);
  --light-0:  oklch(from var(--light)  l c h / 0);
  --warmed-0: oklch(from var(--warmed) l c h / 0);
  --dark-0:   oklch(from var(--dark)   l c h / 0);
  --deep-0:   oklch(from var(--deep)   l c h / 0);

  /* grain + grain-alpha inherit from :root above — the single tile. */

  position: relative;
  overflow: hidden;
  isolation: isolate;
  background-color: var(--mid);
  background-image:
    /* vignette — carries real weight, still in-hue */
    radial-gradient(112% 104% at 44% 38%, var(--deep-0) 30%, var(--deep) 100%),
    /* corner accents — small, low-contrast; they modulate the diagonal */
    radial-gradient(42% 40% at 8% 2%,     var(--pale),   var(--pale-0)   72%),
    radial-gradient(50% 46% at 76% 6%,    var(--light),  var(--light-0)  84%),
    radial-gradient(54% 50% at 10% 78%,   var(--warmed), var(--warmed-0) 78%),
    radial-gradient(70% 64% at 100% 100%, var(--dark),   var(--dark-0)   70%),
    /* the diagonal ramp — the organic flow, TL→BR */
    linear-gradient(133deg,
      var(--pale)   0%,
      var(--light)  17%,
      var(--mid)    38%,
      var(--warmed) 56%,
      var(--dark)   78%,
      var(--mid)    100%);
}

.surface::after {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background-image: var(--grain);
  background-size: 240px 240px;
  mix-blend-mode: soft-light;
  opacity: var(--grain-alpha);
}

.surface > * { position: relative; z-index: 1; }

/* the four permitted tones — the ONLY thing that changes per surface */
.surface.s-ink   { --c: #042839; }
.surface.s-slate { --c: #364349; }
.surface.s-muted { --c: #7C868C; }
.surface.s-mist  { --c: #E0ECE8; }

/* ============================================================
   THE EXPANDED SURFACE — .expanse
   Status: PROPOSED (4 Aug 2026). The showcase ground: an elevated,
   atmospheric backdrop for UI proofs — warmth and depth behind a
   floating card, table or panel. NOT a replacement for .surface:
   .surface is a palette colour made material and can carry type;
   .expanse is a *scene*, and only ever carries a UI element.

   Three layers, in order:
     1. the expanse    — assets/textures/expanse.png, a blurred
                         light-field (no subject, no photography).
     2. the lattice    — a grid or dot field, masked so it enters
                         from one side and dissolves across.
     3. the grain      — the same 240px tile as .surface, soft-light.

   Degradation / one-ink: if the image is missing or the page is
   printed, the flat base colour remains — the intended fallback.
   Everything here is static; nothing loops, nothing idles.

   Usage:  <div class="expanse e-grid-right">…UI…</div>
           <div class="expanse e-grid-left">…UI…</div>
           <div class="expanse e-dots">…UI…</div>
   ============================================================ */

.expanse {
  --expanse-img: url("/assets/textures/expanse.png");
  --expanse-base: #0D7DA6;      /* mean of the field — the one-ink fallback */
  --lattice: rgba(249, 248, 245, 0.17);
  --lattice-cell: 44px;
  --lattice-dot: 22px;
  --grain-alpha: 0.34;          /* a touch above .surface: the field is softer */

  position: relative;
  overflow: hidden;
  isolation: isolate;
  background-color: var(--expanse-base);
  background-image: var(--expanse-img);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* 2 · the lattice — drawn full-bleed, then masked by the variant */
.expanse::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    repeating-linear-gradient(to right,  var(--lattice) 0 1px, transparent 1px var(--lattice-cell)),
    repeating-linear-gradient(to bottom, var(--lattice) 0 1px, transparent 1px var(--lattice-cell));
}

/* 3 · the grain — identical tile and blend to .surface */
.expanse::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image: var(--grain);
  background-size: 240px 240px;
  mix-blend-mode: soft-light;
  opacity: var(--grain-alpha);
}

.expanse > * { position: relative; z-index: 1; }

/* ---- the three variants ---------------------------------- */

/* grid entering from the LEFT, dissolving up and to the right */
.expanse.e-grid-left::before {
  -webkit-mask-image: linear-gradient(107deg, #000 0%, rgba(0,0,0,.82) 26%, transparent 64%);
          mask-image: linear-gradient(107deg, #000 0%, rgba(0,0,0,.82) 26%, transparent 64%);
}

/* grid entering from the RIGHT, dissolving down and to the left — mirrored */
.expanse.e-grid-right::before {
  -webkit-mask-image: linear-gradient(253deg, #000 0%, rgba(0,0,0,.82) 26%, transparent 64%);
          mask-image: linear-gradient(253deg, #000 0%, rgba(0,0,0,.82) 26%, transparent 64%);
}

/* F · the field ALONE — no lattice at all. Every other instance rules the
   field with a grid or a dot mask; this one suppresses ::before entirely, for
   the places that want the light and the grain but no drawn geometry over it
   (the story plate). Composes with e-ink like the rest: `.expanse.e-ink.e-bare`. */
.expanse.e-bare::before { content: none; }

/* the field re-graded onto the primary — the SAME light-expanse, its luminance
   remapped to Navy's own hue and saturation in the asset itself. No veil, no
   blend mode, no correction at runtime: the PNG is navy on disk, so the flat
   fallback is the primary exactly (mean #042E42 against #042839).
   Combine with a lattice variant: <div class="expanse e-ink e-dots">. */
.expanse.e-ink {
  --expanse-img: url("/assets/textures/expanse-ink.png");
  --expanse-base: #042839;
  --lattice: rgba(249, 248, 245, 0.13);   /* the lattice reads harder on ink */
}

/* dot field across the whole ground, weighted low.
   The dot geometry is declared ONCE, as --dot-field, so the hero instance
   below can re-stencil it without restating it. Two copies of this gradient
   is how the two instances drift apart. */
.expanse { --dot-field:
    radial-gradient(circle at center, var(--lattice) 0 1.1px, transparent 1.2px); }

.expanse.e-dots::before {
  background-image: var(--dot-field);
  background-size: var(--lattice-dot) var(--lattice-dot);
  -webkit-mask-image: radial-gradient(128% 108% at 50% 104%, #000 18%, rgba(0,0,0,.55) 58%, transparent 92%);
          mask-image: radial-gradient(128% 108% at 50% 104%, #000 18%, rgba(0,0,0,.55) 58%, transparent 92%);
}

/* E · THE HERO SURFACE — the same field and the same dots, re-stencilled.
   Behind a small piece of UI (instances A–D) the lattice at 0.17 reads as
   texture; behind a headline, a standfirst, two buttons and a logo row it
   reads as noise and competes with the type. So the hero pulls the ink back
   and changes the STENCIL rather than the geometry.

     <section class="expanse e-ink e-hero">…hero…</section>

   The stencil: one circular field rising from below the bottom edge, with its
   greys running soft → strong → soft → strong on the way up, so the dots swell,
   recede and swell again before clearing. An ebb, not a fade. The field is
   densest low and behind the CTA row, and gone by the headline — the type
   never competes with texture for the same space.

   It is a NAMED INSTANCE, not a per-page override, so the value lives in the
   surface system where a non-designer can find it. It is also the ONE instance
   permitted to sit behind body copy (§04's fence names it as the exception).

   ⚠ Specificity: this rule carries three classes so it outranks
   .expanse.e-dots::before. A hero override written with fewer classes loses
   silently — you get the A–D stencil and no error. Measured, not assumed. */
.expanse.e-hero { --lattice: rgba(249, 248, 245, 0.065); --lattice-dot: 26px; }

.expanse.e-hero::before,
.expanse.e-dots.e-hero::before {
  background-image: var(--dot-field);
  background-size: var(--lattice-dot) var(--lattice-dot);
  -webkit-mask-image: radial-gradient(132% 124% at 50% 116%,
      #000 4%, rgba(0,0,0,.42) 20%, rgba(0,0,0,.88) 38%,
      rgba(0,0,0,.18) 54%, rgba(0,0,0,.62) 68%,
      rgba(0,0,0,.12) 82%, transparent 96%);
          mask-image: radial-gradient(132% 124% at 50% 116%,
      #000 4%, rgba(0,0,0,.42) 20%, rgba(0,0,0,.88) 38%,
      rgba(0,0,0,.18) 54%, rgba(0,0,0,.62) 68%,
      rgba(0,0,0,.12) 82%, transparent 96%);
}
