/* ─────────────────────────────────────────────────────────────────────────────
   Grid Field — the scroll-revealed cell grid with data flow.

   Implements the Grid Field spec (§1–§15). Pairs with grid-field.js and sits
   underneath theme.css's Sunset system: theme.css owns colour and type, this
   owns the background texture. Nothing here paints above content.

   ── CALIBRATED AGAINST THE REAL SITE, NOT THE SPEC'S PLACEHOLDERS ────────────
   Phase 0 (§0) was run against https://mistral.ai/ at 1440x900. Every token
   below marked "measured" came out of that capture; §13 says the capture wins,
   so it does. What was measured:

     paper        #fbfbf8   (--color-surface-brand-primary)   spec guessed #FAF7F2
     rule         #e4e3de   (--color-border-primary)          spec guessed rgb(14 14 14/.07)
     rule strong  #c9c9c4   (--color-border-secondary)
     flame ramp   #ffdd73 #ff8204 #ff5229 #fa500f #e51300     spec was close but off
     cell         80px      (the atomic "markitecture-block ... h-20" square)
     easing       cubic-bezier(0.4, 0, 0.2, 1)                spec guessed (0.22,1,0.36,1)
     durations    300ms transform / 500ms opacity             spec guessed 420ms

   ── WHERE THE CAPTURE CONTRADICTS THE SPEC (§13 requires this be recorded) ───
   The spec describes the site as having a diagonal per-cell reveal wave and a
   canvas packet system. It does not. The capture found:

     * No packet layer at all. The only <canvas> is 1006x360 at y=540 — a hero
       element, not a full-viewport field. census.json.
     * No per-cell stagger. The whole page uses exactly three transition delays:
       0.05s, 0.1s, 0.2s. A diagonal wave over a 18x11 grid would need hundreds
       of distinct delays. probe.json.
     * The grid is not a CSS background gradient. Zero elements carry both a
       gradient and a background-size; the rules are ordinary 1px DOM borders on
       square blocks Mistral's own classes call "markitecture-block" / "tech-dot".

   So the wave and the packets are the spec author's extrapolation, not a
   reproduction. They are implemented here as specified because that is what was
   asked for — but the numbers driving them are pulled toward the measured
   values (50ms-ish stagger feel, 300ms cell) rather than the spec's invented
   ones, so the result stays inside the brand's actual motion vocabulary.

   ── STACKING CONTRACT ────────────────────────────────────────────────────────
   .grid-field is fixed at z-index 0. A fixed positioned element paints above
   static content, so page content MUST be raised. .wrap does that below; any
   new top-level container needs the same treatment or the grid will cover text.
   ───────────────────────────────────────────────────────────────────────────── */

:root {
  /* ── Geometry ─────────────────────────────────────────────────────────────
     --grid-cell is a starting value only. grid-field.js overwrites it with a
     width that divides the viewport exactly (§4), because a fractional cell
     leaves a clipped half-column at the right edge that gives the effect away. */
  --grid-cell: 80px;              /* measured: the atomic block is 80px square */
  --grid-rule-w: 1px;
  --grid-bar-h: 0.14;             /* fraction of a cell — a slab, not a block */
  --grid-bar-inset: 0.10;         /* fraction of a cell, each side */

  /* ── Colour — all measured ────────────────────────────────────────────── */
  --grid-paper: #fbfbf8;
  --grid-rule: #e4e3de;
  --grid-rule-strong: #c9c9c4;

  /* The bar sits between the rule and the paper: present as texture, never as
     a table. Expressed as an alpha over paper so it survives a surface change. */
  --grid-bar: rgba(21, 21, 36, 0.055);

  /* Flame ramp — measured from the live custom properties, NOT eyedropped.
     yellow-300 / tangerine-500 / orange-500 / orange-600 / red-600. */
  --flame-1: #ffdd73;
  --flame-2: #ff8204;
  --flame-3: #ff5229;
  --flame-4: #fa500f;
  --flame-5: #e51300;

  /* ── Motion ───────────────────────────────────────────────────────────── */
  --grid-dur-cell: 300ms;                        /* measured */
  --grid-ease: cubic-bezier(0.4, 0, 0.2, 1);     /* measured */
  --grid-stagger: 24ms;
  --grid-wave-max: 600ms;                        /* hard cap, enforced in JS */
}

/* §11 — responsive. Denser grids are noisier and more expensive; a short
   viewport shows the whole field at once, so the wave has to be quicker. */
@media (max-width: 1439px) { :root { --grid-cell: 64px; } }
@media (max-width: 768px)  {
  :root { --grid-cell: 48px; --grid-stagger: 14px; --grid-wave-max: 400ms; }
}

/* ── The field ──────────────────────────────────────────────────────────── */

.grid-field {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;          /* §10 — never intercepts a click */
  overflow: hidden;
  background: var(--grid-paper);
}

/* Content sits above the field. See the stacking contract above.
   .sunset-stripe is in this list because it is static in theme.css — without a
   z-index it renders underneath the fixed field and the signature element
   silently disappears from every page. */
.wrap,
.topnav,
.sunset-stripe,
.grid-above { position: relative; z-index: 1; }

/* ── L0 — rule layer (§5). Static, one paint, never animated. ───────────── */

.grid-field__rules {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(90deg,  var(--grid-rule) var(--grid-rule-w), transparent var(--grid-rule-w)),
    linear-gradient(180deg, var(--grid-rule) var(--grid-rule-w), transparent var(--grid-rule-w));
  background-size: var(--grid-cell) var(--grid-cell);
  /* Fades out rather than terminating hard against header and footer. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 10%, #000 90%, transparent 100%);
          mask-image: linear-gradient(180deg, transparent 0, #000 10%, #000 90%, transparent 100%);
}

/* ── L1 — cell layer. Bars written left-to-right. ───────────────────────── */

.grid-field__cells { position: absolute; inset: 0; }

.grid-cell-bar {
  position: absolute;
  background: var(--grid-bar);
  transform: scaleX(0);
  transform-origin: left center;
  opacity: 0;
  transition:
    transform var(--grid-dur-cell) var(--grid-ease),
    opacity   var(--grid-dur-cell) var(--grid-ease);
  transition-delay: calc(var(--i, 0) * var(--grid-stagger));
}

.grid-field.is-revealed .grid-cell-bar { transform: scaleX(1); opacity: 1; }

/* ── NO COLOURED CELL BARS. This was tried and removed. ────────────────────
   A first pass tinted a few percent of the bars with --flame-4 / --flame-2 so
   the static field read as data rather than as grey texture. It looked good and
   it failed §10: the cell layer is full-bleed and fixed, so a tinted bar lands
   wherever it lands — a render put one directly behind the dashboard's lead
   paragraph. §10 requires body text to clear 4.5:1 against the background
   WITHOUT relying on the grid being absent behind it, and theme.css's --steel
   on paper is already only 4.46:1, so any tint under it fails outright.

   Colour therefore lives exclusively in the packet layer, which is what §2 and
   §7 specify anyway: 2.5px, moving, and gone in under two seconds, so it never
   sits under a paragraph long enough to be read through. */

/* ── L2 — packet layer. Canvas, sized by JS. ────────────────────────────── */

.grid-field__packets { position: absolute; inset: 0; display: block; }

/* ── §10 — reduced motion ───────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .grid-cell-bar {
    transform: scaleX(1);
    opacity: 1;
    transition: none;
    transition-delay: 0s;
  }
  .grid-cell-bar.is-hot  { opacity: 0.32; }
  .grid-cell-bar.is-warm { opacity: 0.30; }
  /* The rAF loop is never started either — see grid-field.js. */
  .grid-field__packets { display: none; }
}
