/*
 * ericville26 — global base
 * =========================
 * Page ground, paper grain and the type ladder. Values copied exactly from the
 * handoff.
 *
 * Why this loads LAST, after every ericville25 component stylesheet
 * ----------------------------------------------------------------
 * These are element selectors — h1, h2, body — at specificity (0,0,1), and
 * basetags.css sets the same ones. Loading the base early (as Stage 2 did)
 * meant basetags won every tie: the hero headline rendered at basetags'
 * 2.4rem instead of the specced clamp(48px, 6.6vw, 88px), and the type ladder
 * silently did not apply anywhere. Only the fonts came through, because those
 * arrive via tokens rather than these rules.
 *
 * Tokens still load first (css/v26.css) so every component resolves the new
 * palette. This file only needs to win the element-level ties, and any legacy
 * rule with a class behind it still overrides it — which is what keeps the
 * components that have not been rebuilt yet intact.
 */

*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:var(--bg);color:var(--text);font-family:var(--sans);font-size:var(--t-body);line-height:1.6;-webkit-font-smoothing:antialiased;transition:background var(--med) var(--ease),color var(--med) var(--ease)}
/* Paper grain over the whole page.
 *
 * #1421/#1430 — DOCUMENT-anchored, not viewport-anchored.
 *
 * The handoff puts this on body::before. It cannot live there in this theme:
 * page-home.css already claims body.home::before for the home wash, and being
 * both more specific and later in the cascade it wins — so the home page would
 * silently lose its grain. It lived on html::before instead, as a FIXED
 * full-viewport layer.
 *
 * Fixed was wrong, for the same reason the hero wash and the footer band were
 * wrong: a layer that belongs to the WINDOW drawn across a page that belongs to
 * the DOCUMENT. It covered exactly one viewport and stopped, leaving textured
 * paper above 900px and flat paper below it on EVERY page — the last hard
 * horizontal edge in the theme. (And fixed noise swims: it stayed still while
 * content scrolled, reading as a filter over the screen rather than as the
 * tooth of the paper the page is printed on.) Proved, not assumed: a throwaway
 * capture with --grain-op:0 removed the line and nothing else.
 *
 * TWO THINGS THAT DO NOT WORK, recorded so nobody retries them:
 *   1. html::before{position:absolute;inset:0}. The containing block for an
 *      absolutely positioned child of the ROOT element is the initial
 *      containing block, which is viewport-sized — the layer stays 100vh and
 *      the edge does not move.
 *   2. body::before. Taken by page-home.css, per above. (#1458 now takes it
 *      deliberately for the accent wash, below — the grain stays on ::after.
 *      Grain over wash is the right order anyway: the tooth of the paper sits
 *      on top of the ink, not under it.)
 *
 * So: body::after, absolute, inside a relatively-positioned body. body is an
 * ordinary in-flow block whose height IS the document's content height, so the
 * layer covers the page by construction. Positioning body is inert for layout
 * (page-home.css already did it on home and nothing regressed); fixed
 * descendants still resolve against the viewport, and body gains no stacking
 * context, so the z-index:9 layer sits over content exactly as before. */
body{position:relative}
body::after{content:"";position:absolute;inset:0;background-image:var(--grain);background-size:380px;background-repeat:repeat;opacity:var(--grain-op);mix-blend-mode:multiply;pointer-events:none;z-index:9}
[data-theme="dark"] body::after{mix-blend-mode:overlay}

/* ── the top accent wash — ONE rule, five sections (#1458) ──────────────────
 *
 * "Make sure each of the pages (UX, Lab, Shop, Art) has the same top
 * background accent as the Home page for its given accent color."
 *
 * This is the layer #1421/#1430 rebuilt for home, moved down into the shared
 * base and de-scoped from `html body.home::before` to `html body::before`. It
 * is written ONCE. The hue comes from --accent and the strength from
 * --wash-a, both of which [data-section] already sets on <html> (header.php),
 * so home is amber, ux blue, art vermilion, shop teal and lab jade with no
 * per-section rule and no per-page stylesheet. Copying this block five times
 * would reintroduce exactly the drift #1421 was filed to remove: a surface
 * defined per page cannot stay consistent. If you find yourself wanting a
 * per-page version of this, the answer is a token, not a rule.
 *
 * ONE CONTINUOUS FIELD, NOT A STACK OF SLABS. Everything below is inherited
 * from #1430 and is load-bearing, not decoration:
 *
 *   DOCUMENT-ANCHORED, not viewport-anchored. The original was
 *   position:fixed;height:100vh, so it belonged to the WINDOW and terminated
 *   at full opacity exactly one viewport down — wherever the fold happened to
 *   land. That was the band Eric saw. It is absolute now, inside a
 *   position:relative body, so it is a property of the top of the DOCUMENT.
 *
 *   NO BLEND MODE. mix-blend-mode:hard-light made this a second full-viewport
 *   blend layer stacked on the grain, and blend layers cannot be composited as
 *   a plain texture. An infinite animation on this same layer had already been
 *   removed for measured jank; the blend mode was the other half of that bill.
 *   Plain alpha, no filter, nothing animated. Keep it that way.
 *
 *   EVERY STOP EASED TO A ZERO SLOPE, not merely to a zero value. The first
 *   pass reached transparency with a single linear ramp, so alpha hit 0 with
 *   the slope still on — a kink in the brightness curve, which the eye reads
 *   as an edge (Mach band); a faint line survived at ~880px. Each interval now
 *   roughly halves the remaining alpha, so the last few hundred pixels are
 *   indistinguishable from flat ground and there is nothing to see the end of.
 *   DO NOT "simplify" these back to two stops.
 *
 * WHAT #1458 CHANGED, beyond the selector:
 *
 *   The stop alphas are now calc()'d off --wash-a instead of being literal
 *   percentages, so one dial per section moves the whole ramp in proportion
 *   and the SHAPE of the fade is guaranteed identical everywhere. The ratios
 *   are home's original numbers divided by its 26%, so home renders unchanged.
 *
 *   height gained a min(...,100%). The box was clamp(900px,150vh,1500px),
 *   tuned against home's 2000px-plus front page. Art is now a ~1800px page and
 *   a short page (404, search, an empty archive) can be shorter than the box —
 *   an absolutely positioned child taller than its container extends the
 *   scrollable area, which would hand a stubby page a phantom scroll. 100% of
 *   a position:relative body IS the document height, so the layer can never
 *   outrun the page it is washing. On every real page min() picks the clamp
 *   and nothing moves.
 *
 *   Nothing was needed for Art's full-bleed carousel or Lab's very long page,
 *   and that is the point of a zero-slope fade rather than a tuned height: the
 *   wash is already under 1.5% alpha by ~750px at every viewport, so it has
 *   finished before Art's carousel rather than dying behind it, and on Lab it
 *   is simply a top-of-document event on a 5600px page. The extent is set by
 *   where the gradient reaches zero, not by where the box ends.
 *
 * z-index:-1 puts it above the canvas (body's background propagates to the
 * canvas because html paints none) and below all content. It must stay under
 * body::after so the grain lies over it.
 *
 * SPECIFICITY — and a trap that cost a capture cycle. css/page-home.css is
 * ericville25 legacy and still owns `body.home::before`. That is (0,1,2): one
 * CLASS, and TWO elements, because a pseudo-ELEMENT counts in the element
 * column. #1430 beat it with `html body.home::before` (0,1,3). De-scoping to
 * `html body::before` drops the class and lands at (0,0,3) — which LOSES to
 * (0,1,2) no matter how late this file loads. It looked like it worked, too:
 * every section page rendered correctly (page-home's rule needs .home) while
 * home alone silently fell back to the legacy fixed 100vh hard-light wash,
 * i.e. quietly re-opened #1430 on the one page anybody would check first.
 * Caught by sampling the column, not by looking.
 *
 * `html[data-section]` restores the class-column point with an attribute, so
 * this is (0,1,3) again — and it is honest rather than a hack, because that
 * attribute IS the precondition: the wash only means anything on a document
 * that has declared which section it is. Dies with page-home.css. */
html[data-section] body::before{
  content:"";
  position:absolute;
  top:0;left:0;right:0;width:auto;
  height:min(clamp(900px,150vh,1500px),100%);
  z-index:-1;
  pointer-events:none;
  mix-blend-mode:normal;
  background-repeat:no-repeat;
  background-size:auto;
  background-position:0 0;
  background-image:
    /* the warm centre, up behind the headline */
    radial-gradient(112% 62% at 14% -8%,
      color-mix(in oklab,var(--accent) var(--wash-a),transparent) 0%,
      color-mix(in oklab,var(--accent) calc(var(--wash-a) * .5),transparent) 30%,
      color-mix(in oklab,var(--accent) calc(var(--wash-a) * .192),transparent) 52%,
      color-mix(in oklab,var(--accent) calc(var(--wash-a) * .062),transparent) 74%,
      transparent 100%),
    /* a second, quieter source top-right so the field is not one flat sweep */
    radial-gradient(88% 54% at 94% 4%,
      color-mix(in oklab,var(--accent) calc(var(--wash-a) * .654),transparent) 0%,
      color-mix(in oklab,var(--accent) calc(var(--wash-a) * .269),transparent) 34%,
      color-mix(in oklab,var(--accent) calc(var(--wash-a) * .077),transparent) 66%,
      transparent 100%),
    /* and the long vertical decay into the flat ground */
    linear-gradient(180deg,
      color-mix(in oklab,var(--accent) calc(var(--wash-a) * .462),transparent) 0%,
      color-mix(in oklab,var(--accent) calc(var(--wash-a) * .250),transparent) 26%,
      color-mix(in oklab,var(--accent) calc(var(--wash-a) * .115),transparent) 48%,
      color-mix(in oklab,var(--accent) calc(var(--wash-a) * .046),transparent) 68%,
      color-mix(in oklab,var(--accent) calc(var(--wash-a) * .015),transparent) 84%,
      transparent 100%);
}

img{display:block}

/* ── type ladder: weight does the work ─────────────── */
h1,h2,h3,h4{font-family:var(--serif);color:var(--ink);margin:0;text-wrap:pretty;font-optical-sizing:auto}
/* The handoff specifies TWO h1 sizes: clamp(48px,6.6vw,88px) for the home hero
   and clamp(40px,4.6vw,60px) for an article head. 88px is a hero-only display
   moment, so the article size is the default and the hero opts up
   (.home-hero h1 in v26-components.css). Both values exact. */
h1{font-weight:var(--disp-weight);font-size:var(--t-d-head);line-height:1.02;letter-spacing:var(--disp-track)}
h1 em{font-style:normal;font-weight:400;color:var(--accent)}
h2{font-weight:600;font-size:var(--t-sub);line-height:1.08;letter-spacing:var(--tr-head)}
h3{font-weight:500;font-size:var(--t-title);line-height:1.15;letter-spacing:var(--tr-title)}
h4{font-weight:500;font-size:var(--t-lead);line-height:1.2;letter-spacing:var(--tr-lead)}

/* Type utilities. Global because they carry no layout and collide with
   nothing in the existing markup. */
.deck{font-size:var(--t-lead);line-height:1.55;color:var(--muted);max-width:56ch;margin:0}
.micro{font-family:var(--sans);font-size:var(--t-label);font-weight:700;letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--faint)}
.num{font-family:var(--sans);font-size:var(--t-label);font-weight:700;letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--accent)}
.stat{font-family:var(--sans);font-size:var(--t-max);font-weight:700;letter-spacing:var(--tr-stat);color:var(--ink);line-height:1}

@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
