/* ══════════════════════════════════════════════════════════════════════════
   chrome-refresh.css — VANTA's chrome, owner brief 2026-08-24 (client demo).

   Four changes, all of them chrome:
     1. the nav bar's GEOMETRY was broken — fixed here, at the ID
     2. the nav is INVISIBLE over the hero and opaque once you scroll
     3. the hero runs to the TOP of the page, behind the nav
     4. a RESEARCH-USE strip is pinned above the nav on every chrome page

   ── TWO HOUSE RULES THIS FILE KEEPS ───────────────────────────────────────

   NO HEX IN THIS FILE, AND NO FONT FAMILY. Three sessions are on this client
   at once: the palette lives in vanta-mono.css (declared on `html:root`, so it
   out-specifies every `:root` block on the site) and the type lives in
   vanta-type.css. Every colour and every face below is a var() read from those,
   so the palette can move under this file without it going stale, and so the
   strip and the nav can never become the one surface set in the odd face.

   SPECIFICITY, NOT SOURCE ORDER. The nav's own rules ship inside each page's
   `<style id="sitewide-nav-css">`, which sits IN THE BODY — later in document
   order than this <link> in <head>. An ID rule there therefore beats an ID rule
   here at equal weight. So every rule below carries one extra element
   (`body #site-nav …`, `html body …`) and wins on specificity no matter where
   the file is loaded from. Do not "simplify" those selectors.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  /* The strip's height is a token because THREE things have to agree on it:
     the strip itself, the nav's `top`, and the offset on <body>. */
  --sf-ruo-h: 34px;
}


/* ── 1. NAV GEOMETRY ───────────────────────────────────────────────────────
   MEASURED BREAKAGE, live at 1440px, before this file existed:
       logo         76,22  65x50   — overflowed the 66px bar, bottom at y=72
       .snav-links 507,14  426x66  — hung 14px BELOW the bar, which is the
                                     active-link underline dangling under the
                                     pill in the owner's screenshot
   CAUSE: `--sf-nav-h` is `calc(38px + 2 * 14px)` = 66px, and the
   `nav-surface: floating` axis puts 14px of padding-block on `.sf-nav-inner`
   with box-sizing:border-box — so the inner's CONTENT box is 38px. But
   `.snav-link` is `height: var(--sf-nav-h)` = 66px and the logo caps at
   `calc(var(--sf-nav-h) - 16px)` = 50px. Both children size themselves to the
   OUTER height and neither one fits the inner box.

   THE FIX IS THE INNER, NOT THE CHILDREN. Zeroing the vertical padding gives
   the children back the full 66px they already ask for, so nothing else has to
   change and no child needs a second height written down somewhere. The pill's
   own skin (ground, radius, shadow, side margins) goes with it: the BAR carries
   the surface now, because a bar that has to fade to transparent over the hero
   cannot be a floating island with its own opaque ground. */
body #site-nav .sf-nav-inner {
  padding-block: 0;
  padding-inline: 0;
  margin-inline: auto;
  max-width: 1280px;          /* the same container the product grid uses, so the
                                 logo lines up with the page content under it */
  background: transparent;
  border-radius: 0;
  box-shadow: none;
}

/* Belt and braces on the two children the measurement caught. Neither is a
   change of value — both restate the height they already had — but they pin it
   at a specificity the layout axes cannot lose, which is what went wrong. */
body #site-nav .snav-link {
  height: var(--sf-nav-h, 64px);
}
body #site-nav .snav-logo img {
  height: min(calc(var(--sf-logo-nav-h, 38px) * var(--sf-logo-scale, 1)),
              calc(var(--sf-nav-h, 64px) - 18px));
}


/* ── 2. TRANSPARENT OVER THE HERO, OPAQUE ONCE SCROLLED ────────────────────
   The overlay state is opt-in per page: `body.sf-nav-overlay` is set only where
   a full-bleed hero actually runs behind the bar (index.html). Everywhere else
   the bar is opaque from the first pixel, because there is nothing behind it to
   show through and a see-through bar over a white page is just a broken bar.

   OPAQUE ONCE SCROLLED IS NOT A TASTE CALL. A translucent fixed bar composites
   whatever scrolls under it, so the ratio a gate measures against the solid
   ground is not the ratio a reader gets — that is the standing chrome rule in
   CLAUDE.md, and it is why the scrolled state below is a solid token and never
   an alpha. `transparent` is safe only while nothing has scrolled under it yet. */
body #site-nav {
  top: var(--sf-ruo-h);
  transition: background-color .3s ease, box-shadow .3s ease, border-color .3s ease;
}
@media (prefers-reduced-motion: reduce) {
  body #site-nav { transition: none; }
}

body.sf-nav-overlay #site-nav {
  background: transparent;
  box-shadow: none;
  border-bottom: 1px solid transparent;
}

/* Scrolled — and every non-overlay page, from the first pixel.

   NOT `--sf-nav-bg`, AND THIS IS THE WHOLE REASON THE RULE READS THIS WAY.
   The `nav-surface: floating` axis sets `--sf-nav-bg: transparent` on <body>,
   because in that composition the PILL carried the ground and the bar behind it
   was meant to be invisible. `#site-nav` inherits from <body>, so that
   declaration beats the palette's own `html:root` value for everything inside
   the bar — the solid state painted nothing at all when it was written against
   `--sf-nav-bg`, measured rgba(0,0,0,0) with the class applied. It also means
   every INNER page was shipping a fully transparent bar over white content.
   `--sf-chrome-nav-bg` is the palette's nav ground and no axis rewrites it.

   Ink needs no second answer: `--sf-nav-ink` resolves to the chrome triple's
   light ink, which clears AA against the dark hero at rest (9.3:1) and against
   this bar once scrolled (10.0:1), so nothing flips mid-scroll. */
body.sf-nav-overlay #site-nav.sf-nav-solid,
body:not(.sf-nav-overlay) #site-nav {
  background: var(--sf-chrome-nav-bg, var(--sf-chrome-bg));
  /* One separator, not two. A SOLVED second colour, never an alpha fade: an
     edge faded toward `transparent` walks back toward its own ground and
     composites whatever scrolls under it, so no gate can measure it. A
     color-mix against the bar's own ground is opaque and measurable. */
  box-shadow: none;
  border-bottom: 1px solid color-mix(in srgb,
    var(--sf-chrome-accent, var(--sf-chrome-nav-ink)) 22%,
    var(--sf-chrome-nav-bg, var(--sf-chrome-bg)));
}


/* ── 3. THE HERO RUNS TO THE TOP OF THE PAGE ───────────────────────────────
   The nav's clearance moves OFF the hero and ONTO the hero's own content, so
   the painted area starts under the strip and runs up behind the bar while the
   headline stays exactly where it was. Deliberately additive: the hero's own
   padding-top is left alone, so whoever owns that number keeps owning it. */
body.sf-nav-overlay #hero {
  margin-top: 0;
}
body.sf-nav-overlay #hero > #hero-content,
body.sf-nav-overlay #hero > .sf-hero-inner {
  padding-top: var(--sf-nav-h, 64px);
}

/* THE HERO LINE-UP. The master's hero media slot ships a token ground and no
   image, filled from `client.json brand.hero`; with none set, this client was
   shipping an empty 880x385 grey slab under the headline. apply-chrome.mjs puts
   the client's own three vials there instead, and the slab goes with them — the
   vials stand on the hero itself, over the particle field, not inside a panel. */
body #hero .sf-hero-media {
  background: transparent;
  box-shadow: none;
}
/* AND THE TINT COMES OFF WITH IT. `media-tint: wash` paints a full-bleed
   ::after over .sf-hero-media at 0.28 — a treatment that reads as a tint only
   when the slot holds a PHOTO that fills it. The line-up is a cut-out with real
   alpha, so the same rule stops tinting an image and starts drawing a pale
   rectangle behind it: measured rgb(228,239,242) at 0.28 across the full
   880x385 box, which is the grey slab under the vials. Scoped to the hero, so
   the axis keeps working wherever a photo actually fills the frame. */
body #hero .sf-hero-media::after {
  content: none;
}
body #hero .sf-hero-lineup {
  display: block;
  width: 100%;
  max-width: 880px;
  height: auto;
  margin: 0 auto;
}

/* The particle field. It layers between #hero-bg-overlay (z 1) and the client's
   own helix motif (z 2), so it reads as BACKGROUND texture and never competes
   with the figure — the owner asked for a particle background, not a second
   figure. Pointer-events off: it sits over the whole hero and nothing in it is
   interactive. */
body #hero-particles {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}


/* ── 4. THE RESEARCH-USE STRIP ─────────────────────────────────────────────
   The engine ships this element (`#site-disclaimer`, resolveRuoStrip() /
   verifyRuoStrip()) and it is OFF by default; the owner asked for it ON and at
   the very TOP, above the nav, matching the client's own reference site. So it
   is the engine's element and id, repositioned — not a second disclaimer.

   NEVER display:none THIS. Off means DELETED, because a hidden disclaimer is
   one nobody reads that still passes every gate (BUILD-DEFAULTS §2e).

   GROUND IS CHROME-LEVEL, NOT PAGE-LEVEL. It sits above a near-black bar, so it
   is painted from --sf-chrome-bg / --sf-chrome-ink. Mixing a page-level token
   (--sf-bg, which is white) into a chrome-level fill while taking chrome-level
   ink is the alpha-panel trap that already put white text on a white panel in
   the footer on this very build. */
html body #site-disclaimer {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--sf-ruo-h);
  z-index: 9990;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 1rem;
  background: var(--sf-chrome-bg, var(--sf-text));
  color: var(--sf-chrome-ink, var(--sf-bg));
  border-bottom: 1px solid color-mix(in srgb,
    var(--sf-chrome-accent, var(--sf-chrome-ink)) 26%,
    var(--sf-chrome-bg, var(--sf-text)));
}
html body #site-disclaimer p {
  margin: 0;
  font-family: var(--sf-font-body, inherit);
  font-size: 0.66rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: inherit;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The separators are the one quiet register in the strip. Silver is the brand's
   own accent here, and it clears AA against the chrome ground on its own — it is
   a solved second colour, not this ink at reduced opacity. */
html body #site-disclaimer .sf-ruo-sep {
  color: var(--sf-chrome-accent, var(--sf-chrome-ink));
  margin: 0 0.7em;
  font-weight: 400;
}

/* THE OFFSET LIVES WITH THE STRIP. Every page clears the NAV itself with
   var(--sf-nav-h); the strip is a second band of fixed chrome that no page
   knows about, so its height is added once, here, on <body>. `html body` beats
   each page's own `body {}` rule at any source order — which matters, because
   an orphaned offset is a band of empty page under a fixed bar that no contrast
   probe, link check or copy gate can see. */
html body {
  padding-top: var(--sf-ruo-h);
}

/* Narrow screens: the three-part sentence will not fit at 0.66rem, so the type
   comes down rather than the sentence being cut. Below 560px the strip drops the
   two trailing clauses to a second line's worth of height instead of truncating
   — nothing is hidden, because a disclaimer that is partly readable is worse
   than none. */
@media (max-width: 720px) {
  :root { --sf-ruo-h: 30px; }
  html body #site-disclaimer p { font-size: 0.56rem; letter-spacing: 0.11em; }
  html body #site-disclaimer .sf-ruo-sep { margin: 0 0.45em; }
}
@media (max-width: 480px) {
  html body #site-disclaimer p { font-size: 0.5rem; letter-spacing: 0.07em; }
}


/* ── 5. THE HOME PAGE CLOSES ITS OWN HOLES ─────────────────────────────────
   Owner, 2026-08-24, from a client demo: "there is a gap in it under the hero
   section" and "remove the random gray box in the hero section". Two separate
   causes, both the same shape — a container the master ships EMPTY for this
   client, still drawing its own box.

   Both rules are CONDITIONAL on the emptiness, never a flat display:none. That
   is the whole point: the moment either region gets real content the rule stops
   matching by itself and the layout comes back, so nothing here has to be
   remembered and undone later.

   Scoped to what these two containers actually are, so no other page is touched.
   ------------------------------------------------------------------------- */

/* (a) THE GRAY BOX. `.sf-hero-media` is the contract's hero-image hook, and
   under layout-hero-centered theme.css gives it max-width 880px, a --sf-surface
   ground and three shadows — a designed frame for a picture. Vanta has no
   reviewed hero asset bound, so the region still holds its DEFAULT child,
   `.sf-hero-ground`: a token gradient panel with aspect-ratio 16/7. A framed,
   shadowed, 880x385 panel of surface-grey with nothing in it is what the owner
   saw hanging out of the bottom of the hero.
   The wrapper STAYS in the DOM — engine/FRONTEND-CONTRACT.md requires it on
   every layout and the themes/layouts/*.css files style it — so this hides,
   never deletes. `:has(> .sf-hero-ground)` is the emptiness test: the ground IS
   the placeholder, and binding a real brand.hero replaces it with an <img> or
   <video>, at which point this selector stops matching and the frame returns. */
body.layout-hero-centered .sf-hero-media:has(> .sf-hero-ground) {
  display: none;
}

/* The 40px that separated the copy from that panel was the panel's OWN
   margin-top, so hiding it took the hero's bottom breathing room with it and
   left the stat cards flush against the section edge. layout-hero-centered sets
   the hero's padding-bottom to 0 precisely BECAUSE the media is meant to run
   into the next section. With no media, the hero needs a floor of its own —
   set here on the same conditional, so it too reverts on its own. */
body.layout-hero-centered #hero:has(.sf-hero-media > .sf-hero-ground) {
  padding-bottom: clamp(48px, 7vh, 88px);
}

/* (b) THE GAP. `#categories` is the category rail, and build.mjs fills
   SF:HOME_CATEGORIES from the client's own catalogue facets — legitimately
   EMPTY when the catalogue supports only one facet, which is Vanta's case. The
   section element still ships, and `.sf-section` under this layout carries
   62.4px of padding top and bottom: 125px of page-ground white, sitting between
   a dark hero and a dark assurance band, reading as a rendering fault rather
   than as space.
   The test is "does the rail hold any ELEMENT" — comments and whitespace are all
   that is in there now, which is exactly why `:empty` cannot be used for this.
   Fill the rail and the section reappears with no edit here. */
#categories.sf-cat-section:not(:has(.sf-catalog > *)) {
  display: none;
}

/* ── page header under the fixed nav, on a phone ───────────────────────────
   Measured on a 375px phone 2026-09-03 (research page, then every page that
   shares the band): the fixed nav sits at y=30 (under the research-use strip)
   and ends at 96, while the page band's phone padding-top left the h1 at 80 —
   the headline's first line was under the bar. The band has to clear the nav
   by construction, so its top padding is the nav's own height plus breathing
   room, not a fixed rem. Two classes so it outranks the engine's `.pg-band`
   media rule without an id. */
@media (max-width: 40rem) {
  body .page-header-bg.pg-band { padding-top: calc(var(--sf-nav-h, 66px) + 1.6rem); }
}
