/* ======================================================================
   sf-refresh.css - Core Chain Labs colour expansion (2026-08-18)
   Loads AFTER theme.css + sf-base.css. Additive only: it re-tunes tokens
   and repaints surfaces; it never restructures layout or money paths.
   Palette taken from the brand mark itself: navy wordmark, cyan hex.
   ====================================================================== */

:root {
  /* -- the expanded brand ramp -- */
  --cc-navy-900: #071A33;
  --cc-navy-800: #0C2440;
  --cc-navy-700: #10294A;
  --cc-navy-600: #17406B;
  --cc-blue-600: #1C5E96;
  --cc-blue-500: #2C7AB8;
  --cc-blue-400: #4A9BD4;
  /* THE BRIGHT AZURE PAIR (owner, 2026-08-20: the hex pattern on the home
     page wants "a brighter colour that pops"). A step OFF the blue ramp, not a
     replacement for it: --cc-blue-* carries INK uses elsewhere on the site
     (standards.html's fills and links), each solved against the dark ground,
     and none of those may move. These two are decoration only - the hero mesh's
     wires - so they are free to be loud. Kept in the BLUE family on purpose:
     the nodes are cyan, and the owner's 2026-08-19 rule is that the wires and
     the stars read as different colours. Ceilings over the navy band's ground-2
     #040E20, from solve-contrast.mjs: azure-400 0.717, azure-300 0.572 - the
     mesh ships under both. */
  --cc-azure-400: #3E9DEB;
  --cc-azure-300: #6FC3FF;
  --cc-teal-700: #135C6E;
  --cc-teal-600: #1B7C8C;
  --cc-teal-500: #279CAD;
  --cc-cyan-400: #16BFD1;
  --cc-cyan-300: #57D6E4;
  --cc-cyan-100: #C9F0F6;
  --cc-silver-500: #7E93A6;
  --cc-silver-400: #9FB3C4;
  --cc-silver-200: #CBD9E4;
  --cc-ice-100:   #E4F1F9;
  --cc-ice-50:    #F1F8FC;

  /* -- the METALLIC SILVER register (owner, 2026-08-19) --
     The client asked for "blue, white, gray, silver, metallic silver". Silver
     is not one hex, it is a RAMP plus a specular sweep: a flat #C0C0C0 reads as
     grey paint, not metal. These three are the ink/hairline/sheen triple every
     metal surface below is built from, and they are opaque values, never alpha
     fades - see the chrome note. */
  --cc-metal-100: #F0F6FA;   /* polished highlight - also the page ink */
  --cc-metal-300: #A6BACB;   /* brushed silver - the de-emphasised ink */
  --cc-metal-line: rgb(166 186 203 / 0.20);  /* hairline; a BORDER, not text */
  --cc-metal-sheen: linear-gradient(90deg,
    transparent 0%, rgb(166 186 203 / 0.10) 22%,
    rgb(240 246 250 / 0.42) 50%,
    rgb(166 186 203 / 0.10) 78%, transparent 100%);

  /* -- GROUNDS: DEEP NAVY, not the solver's near-neutral black --
     client.json theme.mode is now "dark", so themes/tokens.mjs already emits a
     dark base (#03090D bg / #071015 surface / #E9F3F8 ink). That base is
     correct in KIND and wrong in TEMPERATURE for this brand: solved off a
     desaturated teal it lands near-neutral black, and the client asked for
     deep navy. These four re-pin the same four rungs one hue-step bluer at
     nearly the same lightness, so nothing the solver proved about the base
     changes rank - only its cast.
     Contrast, computed not eyeballed (WCAG relative luminance):
       L(bg #060E1A)=0.00427  L(surface #0C1726)=0.00831
       L(elevated #142235)=0.01549  L(text #F0F6FA)=0.91311
       L(muted #A6BACB)=0.47544
       text/bg   17.75:1   muted/bg        9.68:1
       text/surf 14.60:1   muted/surface   9.01:1
                           muted/elevated  8.02:1
     Every one clears 4.5:1 with room for the section washes layered below. */
  --sf-bg:       #060E1A;
  --sf-surface:  #0C1726;
  --sf-elevated: #142235;
  /* Dark mode aliases band to surface by doctrine (BUILD-DEFAULTS 2a): a
     second ground one step BELOW an already near-black page is a step nobody
     can see. Aliased rather than re-pinned so the two can never drift. */
  --sf-band:     var(--sf-surface);
  --sf-text:     var(--cc-metal-100);
  --sf-muted:    var(--cc-metal-300);

  /* -- ACCENT: the colour that survives a silver site --
     "still include some colours somewhere". The solver's dark accent (#5F94AD)
     is a dusty slate that reads as one more grey next to a silver ink, which is
     exactly the monochrome failure to avoid. #3FB6C9 is a metallic cyan: bright
     enough to be a colour, desaturated enough not to read neon on near-black.
     It has to pass TWICE, because the token is used both ways -
       as TEXT   (.sf-hero-stats b, sf-base:193) on --sf-bg: 8.06:1
       as a FILL (.pg-cta on standards.html) under --sf-accent-text: 7.81:1
     and its 82%-alpha eyebrow on that fill composites to 5.96:1.
     The 1..5 ramp is restored to the contract's order (1 deepest, 5 lightest);
     it had been left non-monotonic with silver at position 1. */
  --sf-accent:      #3FB6C9;
  --sf-accent-text: #04141A;
  --sf-accent-1: var(--cc-navy-600);
  --sf-accent-2: var(--cc-blue-600);
  --sf-accent-3: var(--cc-teal-600);
  --sf-accent-4: var(--cc-cyan-300);
  --sf-accent-5: var(--cc-silver-200);
  --sf-accent-hover: #63CBDA;
  /* --sf-brand / --sf-brand-text are DELETED here, not re-pinned. They were
     overriding the client's own solved hex (#20576E + #EDF9FF) with a hand
     value, and BUILD-DEFAULTS keeps --sf-brand unclamped and client-owned. The
     solved pair measures 7.40:1 on the closing CTA band and 5.42:1 for its dim
     register, so there is nothing here to fix. */

  /* -- CHROME: nav + footer, OPAQUE, de-emphasis SOLVED --
     Lifted ~2 lightness steps ABOVE the page ground so the bar is a bar on a
     near-black page instead of an invisible seam. Both inks are SOLID hexes:
     an ink faded toward transparent walks back to its own ground and
     composites whatever scrolls under it, which no gate can measure.
       L(chrome-bg #0B1A2C)=0.00992, L(wash-to #050D18)=0.00386
       ink #F0F6FA  / chrome-bg 16.07:1   / wash-to 16.83:1
       ink-dim #A6BACB / chrome-bg 8.77:1 / wash-to 9.76:1
       accent #57D6E4  / chrome-bg 10.13:1 / wash-to 11.28:1
     The footer's coloured register goes to cyan-300 rather than the solver's
     #50869E, which measured 5.00:1 on this ground - passing, and grey. */
  --sf-chrome-bg:       #0B1A2C;
  --sf-chrome-ink:      var(--cc-metal-100);
  --sf-chrome-ink-dim:  var(--cc-metal-300);
  --sf-chrome-accent:   var(--cc-cyan-300);
  --sf-chrome-wash-from: #0B1A2C;
  --sf-chrome-wash-to:   #050D18;
  --sf-chrome-wash: linear-gradient(104deg, #0B1A2C 0%, #081525 46%, #050D18 100%);
  --sf-logo-filter-chrome: brightness(0) invert(1);
  --sf-nav-border: 1px solid rgb(87 214 228 / 0.26);

  /* Hairlines flip polarity on a dark ground: the old value was a NAVY alpha,
     i.e. dark ink on dark, which is no border at all. Silver at 20% is the
     metal edge. Borders are not text, so no ratio is owed. */
  --sf-border-color: var(--cc-metal-line);
  --sf-divider-bg:   var(--cc-metal-line);
  --sf-hero-wash: rgb(63 182 201 / 0.16);
  /* The one ground under all five product-photo surfaces (owner, 2026-08-19:
     "make all backgrounds for peptides white"). The vial master is a true
     cut-out — vial.mjs un-premultiplies against the render's own white studio
     floor — so a white plate is the ground it was made for and composites
     without a halo. This REVERSES the earlier dark-slate choice on this client
     deliberately: the site stays dark, the product photography does not.
     A white plate flips the ink polarity on the two surfaces that print OVER
     it — the tile RUO overlay and the PDP gallery overlay — and both are
     re-solved below rather than left to inherit the dark page's light ink. */
  --sf-photo-bg: #FFFFFF;
}

/* -- NAV -- */
/* OPAQUE, and the light end of the sweep is the number that has to hold: the
   dim ink reads wherever it lands along the gradient, so the ratio is owed at
   the BRIGHTEST stop, not the average. #102A46 is that stop (L=0.02208) and
   --sf-chrome-ink-dim measures 7.29:1 on it. The old sweep ran out to
   --cc-navy-600 (#17406B), where the same ink is 5.30:1 - passing, but with no
   margin left for the 1px cyan seam sitting on top of it. */
body #site-nav {
  /* THE NAV-SCOPED INK TOKENS COME WITH THE REPAINT (2026-08-20). The build
     solved the chrome triple against --sf-chrome-nav-bg #FFFFFF, so
     --sf-chrome-nav-ink-dim is #587581 - correct on white, 3.56:1 on the bar
     this rule actually paints, which qa.mjs read off real pixels. Repainting a
     surface without re-solving its own ink is the failure BUILD-DEFAULTS warns
     about; the general chrome inks were re-solved here and the NAV-scoped pair
     was missed. --sf-chrome-ink-dim (#ABB5BB) measures 7.29:1 at the light end
     of the sweep, and de-emphasis stays a SECOND COLOUR, never an alpha fade. */
  --sf-nav-ink: var(--sf-text);
  --sf-nav-ink-dim: var(--sf-chrome-ink-dim);
  background: linear-gradient(96deg, #081525 0%, #0B1A2C 48%, #102A46 100%);
  box-shadow: 0 1px 0 rgb(87 214 228 / 0.26), 0 12px 34px -24px rgb(0 0 0 / 0.95);
}
body #site-nav a:hover { color: var(--cc-cyan-300); }
body.opt-nav-link-style-pill #site-nav .snav-link.is-active,
body.opt-nav-link-style-pill #site-nav .snav-link[aria-current] {
  background: rgb(22 191 209 / 0.16);
  color: var(--cc-cyan-300);
}
/* The badge classes are `snav-cart-badge` + `sf-cart-count` — this rule shipped
   with both names TRANSPOSED (`snav-cart-count` / `sf-cart-badge`), so it matched
   nothing on any page and the template's own rule stood:
     #site-nav .snav-cart-badge { background: var(--sf-nav-ink); color: var(--sf-nav-bg) }
   On this client #site-nav re-declares --sf-nav-ink to --sf-text (#F0F6FA) while
   --sf-nav-bg stays the :root chrome default (#FFFFFF) — a white number on a
   near-white disc, 1.09:1. The count was ALWAYS being written by cart.js's
   updateCartBadge(); it was simply invisible. Cyan on navy is 8.9:1.
   `body #site-nav` (1,2,0) is deliberate: the template's rule lives in a <style>
   inside _nav.part, i.e. in the BODY, so it comes after every linked sheet and
   an equal-specificity selector here would lose on source order. */
body #site-nav .snav-cart-badge,
body #site-nav .sf-cart-count { background: var(--cc-cyan-400); color: var(--cc-navy-900); }
/* 15px/8px was solved against the old 38px icon button. Section G grew that
   button to 46px and explicitly left the badge alone; at 8px the digit was
   unreadable even once it had a colour to be read in. */
body #site-nav .snav-cart-badge { width: 18px; height: 18px; font-size: 10px; top: -5px; right: -5px; }

/* The DRAWER lockup is the third copy of the same mark and it was the last one
   still flattened: .snav-mob-logo img reads --sf-logo-filter (theme.css:70,
   brightness(0) invert(1)) rather than the -nav/-foot pair, so on a phone the
   bar showed the coloured mark and the open drawer showed a white silhouette
   of it. Same artwork, same navy ground, same answer as the bar. */
body .snav-mob-logo img { filter: none; }

/* -- FOOTER -- */
body .sf-footer { background: var(--sf-chrome-wash); }
/* MATCH THE NAV, sitewide (owner, 2026-08-21). The nav draws LOGO-ONDARK.webp
   with --sf-logo-filter-nav: none, so it keeps the cyan mark and the drawn
   wordmark; the footer flattened the SAME artwork to a white silhouette on the
   SAME dark chrome ground, so one page carried two different brand marks. Both
   grounds are the navy chrome wash, so `none` is right here for exactly the
   reason it is right there. The height follows the nav lockup as well
   (--sf-logo-nav-h 45px x --sf-logo-scale 1.15 = 51.75px) — the footer is the
   one brand block on the page with the vertical room for it. */
body .sf-footer .sf-logo-img {
  filter: none;
  height: calc(var(--sf-logo-nav-h, 45px) * var(--sf-logo-scale, 1.15));
}
body .sf-footer a:hover, body .sf-ftr-link:hover { color: var(--cc-cyan-300); }
body .sf-footer-social a { border-color: rgb(22 191 209 / 0.3); }
body .sf-footer-social a:hover { color: var(--cc-cyan-300); border-color: var(--cc-cyan-400); }

/* -- RUO strip: was a grey rule, now a brand band -- */
/* Deepened for the dark ground. The old teal end (#1B7C8C, L=0.16538) put its
   near-white ink at 4.2:1 - under the line - and it only ever escaped the gate
   because qa.mjs probes #site-disclaimer, which is a separate fixed #111111
   bar. Fixing it here rather than leaving an unmeasured surface failing. */
body .sf-ruo-strip, body #ruo-strip {
  background: linear-gradient(90deg, #0E3B47 0%, #123C5E 52%, #102A46 100%);
  color: #EAF8FB;
}
body .sf-ruo-strip a, body #ruo-strip a { color: var(--cc-cyan-100); }

/* -- BUTTONS: three registers, all inside the brand -- */
/* qa.mjs samples the AVERAGED pixels of the button, so a gradient is graded on
   its midpoint - but the far end is what a reader's eye actually lands on, and
   the old teal-600 end put #F4FCFF at 4.69:1, four hundredths of margin. Pulled
   to teal-700: the averaged ground (rgb 24,93,130) reads 6.91:1 and the worst
   single stop 6.35:1. Deeper also suits a dark page - a light button on a
   near-black ground is a flashbulb. */
body .sf-btn-primary, body .btn-teal,
body.opt-btn-style-soft .sf-btn-primary {
  background: linear-gradient(135deg, var(--cc-blue-600) 0%, var(--cc-teal-700) 100%);
  color: #F4FCFF;
  border: 1px solid rgb(87 214 228 / 0.38);
  box-shadow: 0 10px 26px -14px rgb(0 0 0 / 0.85), inset 0 1px 0 rgb(240 246 250 / 0.14);
}
body .sf-btn-primary:hover, body .btn-teal:hover,
body.opt-btn-hover-shade.opt-btn-style-soft .sf-btn-primary:hover {
  background: linear-gradient(135deg, var(--cc-blue-500) 0%, var(--cc-teal-500) 100%);
  box-shadow: 0 12px 30px -12px rgb(22 191 209 / 0.6);
}
/* Ink INVERTED for the dark ground - --cc-teal-700 on near-black was 2.5:1 and
   is the single clearest contradiction the flip creates. The 10% cyan wash
   composites over --sf-bg to rgb(9,33,45); #C9F0F6 on that measures 13.64:1. */
body .sf-btn-secondary, body .btn-ghost-white,
body.opt-secondary-style-outline .sf-btn-secondary {
  background: rgb(63 182 201 / 0.10);
  color: var(--cc-cyan-100);
  border: 1.5px solid rgb(87 214 228 / 0.40);
}
body .sf-btn-secondary:hover, body .btn-ghost-white:hover,
body.opt-secondary-style-outline .sf-btn-secondary:hover {
  background: rgb(63 182 201 / 0.18);
  border-color: var(--cc-cyan-300);
  color: #F0FCFF;
}
/* on a dark ground the secondary flips to a light outline */
body #cta-banner .sf-btn-secondary, body #cta-banner .btn-ghost-white,
body .sf-footer .sf-btn-secondary {
  background: rgb(255 255 255 / 0.06);
  color: var(--cc-cyan-100);
  border-color: rgb(87 214 228 / 0.55);
}
body #cta-banner .sf-btn-secondary:hover, body #cta-banner .btn-ghost-white:hover {
  background: rgb(22 191 209 / 0.2); color: #fff; border-color: var(--cc-cyan-300);
}

/* -- LINKS / KICKERS / CHIPS: colour where there was grey -- */
/* Both inks lifted off the teal end of the ramp onto the cyan end: --cc-teal-600
   measured 3.88:1 on the new ground (fails 4.5), --cc-cyan-300 measures 11.19:1
   and is where the "some colours somewhere" actually lives on a silver site. */
body .sf-kicker, body .section-label { color: var(--cc-cyan-300); }
body .sf-badge, body .sf-pill, body .sf-verified-pill {
  background: rgb(63 182 201 / 0.12);
  color: var(--cc-cyan-100);
  border-color: rgb(87 214 228 / 0.34);
}

/* -- SECTION RHYTHM: alternating tinted grounds, not grey -- */
/* INVERTED, not tinted differently: every stop here was an ice/white plate and
   a white plate on a dark site is a hole, not a band. The rhythm survives - the
   sections still alternate - but now between #060E1A and #0C1726, a 1.08:1
   step, which is the same visible distance a light site gets from its band.
   The brightest COMPOSITED stop below is #research-intro's blue wash over the
   band - rgb(15,37,60), L=0.01749. --sf-muted measures 7.79:1 there and
   --sf-text 14.27:1, so the washes cost nothing legible at their worst point. */
body .sf-section.sf-band { background: linear-gradient(180deg, #0C1726 0%, #0A1421 100%); }
body #categories {
  background: linear-gradient(180deg, #0A1421 0%, var(--sf-bg) 100%);
  border-bottom: 1px solid rgb(87 214 228 / 0.16);
}
body #research-intro {
  background:
    radial-gradient(90% 120% at 8% 0%, rgb(63 182 201 / 0.14) 0%, transparent 58%),
    radial-gradient(80% 110% at 100% 100%, rgb(28 94 150 / 0.20) 0%, transparent 60%),
    linear-gradient(180deg, #0C1726 0%, #081221 100%);
}
body #featured-strip {
  background:
    radial-gradient(70% 90% at 92% 6%, rgb(74 155 212 / 0.12) 0%, transparent 62%),
    var(--sf-bg);
}
body #home-faq {
  background: linear-gradient(180deg, var(--sf-bg) 0%, #0C1726 100%);
}
/* The separator becomes the metal sheen - a specular line is what makes silver
   read as silver rather than as grey, and it costs nothing measurable. */
body.opt-section-separator-fade .sf-section + .sf-section::before {
  background: var(--cc-metal-sheen);
}

/* -- ASSURANCE BAND: it reads off the chrome triple, so it is navy now -- */
body .sf-assure-band {
  background: linear-gradient(102deg, var(--cc-navy-800) 0%, var(--cc-navy-700) 55%, var(--cc-navy-600) 100%);
  position: relative;
}
body .sf-assure-band::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(60% 140% at 82% 0%, rgb(22 191 209 / 0.20) 0%, transparent 62%);
}
body .sf-assure-band .assure-wrap { position: relative; z-index: 1; }
body .sf-assure-band .assure-ico { color: var(--cc-cyan-400); opacity: 1; }
body .sf-assure-band .assure-card { padding-left: 0.9rem; border-left: 2px solid rgb(22 191 209 / 0.35); }

/* -- CARDS: colour on the edge, glow on hover -- */
/* Card edges become brushed metal: a navy alpha on a navy card is no edge. The
   inset highlight is the top bevel - one pixel of silver is the whole
   difference between a flat panel and a machined one. */
body .sf-card, body .sf-panel {
  border-color: var(--cc-metal-line);
  box-shadow: inset 0 1px 0 rgb(240 246 250 / 0.07);
}
body.opt-card-accent-edge-left .sf-card::before {
  background: linear-gradient(180deg, var(--cc-cyan-300) 0%, var(--cc-blue-600) 100%);
}
body .sf-card:hover {
  border-color: rgb(87 214 228 / 0.45);
  box-shadow: 0 16px 38px -20px rgb(0 0 0 / 0.85), 0 0 0 1px rgb(63 182 201 / 0.22),
              inset 0 1px 0 rgb(240 246 250 / 0.12);
}
/* --sf-photo-bg is the one token all five product-photo surfaces read; this
   restates it on .sf-card-media because the master's page-level rules paint
   that element too. */
body .sf-card-media { background: var(--sf-photo-bg, #FFFFFF); }

/* -- INK OVER THE WHITE PLATE --
   Two surfaces print text ON the product photo, and both take their colour
   from --sf-text / --sf-surface, which on this client are the DARK page's
   light ink and dark ground. Over a white plate that is light-on-white, i.e.
   invisible, and NOTHING GATES IT: qa.mjs has no probe on .sf-card-media, and
   the tile disclaimer is a ::after pseudo-element no probe could sample.
   The fix is scoped CUSTOM PROPERTIES rather than colour overrides, so it
   needs no !important and wins regardless of stylesheet order: the master's
   rules keep reading var(--sf-text)/var(--sf-surface), they just resolve to
   the plate's own polarity inside these two elements.
   Ratio: --cc-navy-900 #071A33 at 78% over white composites to rgb(62,76,96),
   8.7:1 — the 4.5:1 floor holds with headroom on the worst pixel, not an
   average, because the scrim's bottom stop is 96% white. */
/* -- THE AXIS OUTRANKS THE TOKEN (measured in the browser, 2026-08-19) --
   Setting --sf-photo-bg was NOT enough and nothing said so. This client wears
   the design axis `opt-card-media-bg-tinted`, and theme.css ships it as

     body.opt-card-media-bg-tinted .sf-card-media,
     body.opt-card-media-bg-tinted .card-img-wrap { background: var(--sf-soft-bg); }

   at specificity (0,2,1) — above sf-base's `body .sf-card-media` (0,1,1), the
   rule BUILD-DEFAULTS 2a calls "one token for the same artifact on all five
   surfaces". So two of the five (the grid tiles and .card-img-wrap) were
   painted --sf-soft-bg #13222A no matter what --sf-photo-bg said, while the
   related rail DID take the token — one artifact, several grounds. (The
   gallery is a THIRD ground again; see the second axis below.)
   Restated here at the SAME specificity, after theme.css, so the token wins.
   This is a MASTER defect and it is reported upward, not fixed in the axis
   from a client stylesheet: redefining opt-card-media-bg-tinted would repaint
   every future client's tiles, and that is a BUILD-DEFAULTS row, not a CSS
   edit. Nothing gates it either way — no qa.mjs probe samples a product plate. */
body.opt-card-media-bg-tinted .sf-card-media,
body.opt-card-media-bg-tinted .card-img-wrap { background: var(--sf-photo-bg, #FFFFFF); }

/* SECOND AXIS, SAME SHAPE: `opt-gallery-frame-none` ships
   `body.opt-gallery-frame-none .sf-gallery { background: transparent; border: none;
   box-shadow: none }` — also (0,2,1) — so the PDP gallery was showing the DARK
   PAGE through it, not --sf-photo-bg and not even --sf-surface. Between the two
   axes, --sf-photo-bg reached exactly ONE of its five surfaces on this client
   (.sf-related .rel-media). Only `background` is restated below, so the axis
   keeps doing the thing it is named for: no border, no shadow, no frame. A
   painted plate is not a frame. */
body.opt-gallery-frame-none .sf-gallery { background: var(--sf-photo-bg, #FFFFFF); }

body #product-grid .sf-card-media { --sf-text: var(--cc-navy-900); --sf-surface: #FFFFFF; }
body #ruo-overlay { --sf-text: var(--cc-navy-900); }
/* Colour only - no pricing logic is touched here. --cc-navy-700 ink was
   1.9:1 on a dark card; the page ink is 14.60:1 on --sf-surface. */
body .sf-price, body .sf-product-price { color: var(--sf-text); }

/* -- CTA BANNER: the one deep-navy close on the page -- */
body #cta-banner { background: linear-gradient(180deg, #0A1421 0%, var(--sf-bg) 100%); }
body #cta-banner .cta-card {
  --sf-text: #F0F8FD;
  background: linear-gradient(135deg, var(--cc-navy-800) 0%, var(--cc-navy-700) 48%, var(--cc-teal-700) 100%);
  border: 1px solid rgb(22 191 209 / 0.34);
  box-shadow: 0 30px 70px -40px rgb(7 26 51 / 0.9);
  color: #F0F8FD;
}
body #cta-banner .cta-card .sf-kicker,
body #cta-banner .cta-card .section-label { color: var(--cc-cyan-300); }
body #cta-banner .cta-card-glow {
  background: radial-gradient(60% 80% at 20% 0%, rgb(22 191 209 / 0.28) 0%, transparent 62%);
}
body #cta-banner .cta-badge {
  background: rgb(255 255 255 / 0.07);
  border: 1px solid rgb(22 191 209 / 0.3);
}
body #cta-banner .cta-badge-k { color: var(--cc-cyan-300); }
/* SOLVED, not faded. This was rgb(240 248 253 / 0.78) - the same alpha-toward-
   transparent mistake the chrome rule bans, on a card that carries a radial
   glow, so the real ratio moved with the pixel. #B9CEDD is the opaque silver
   that alpha was approximating: 8.99:1 on the card's mid stop (#10294A). */
body #cta-banner .cta-badge-v { color: #B9CEDD; }

/* -- HERO: a coloured ground instead of a flat wash -- */
/* The hero is the deepest ground on the site and climbs one step toward navy
   at the top-right, which is where the motif sits - the figure is lit, the
   copy column is not. The washes were capped at 0.22 alpha (brightest pixel
   rgb(21,55,72), L=0.03359, --sf-text 11.52:1) until 2026-08-20 — see the
   darkening note below for the figures that now ship.
   Note qa.mjs grades the HERO HEADING at 4.5:1 flat - it does not take the
   3:1 large-text relaxation - so the headline is held to body-text standard. */
/* DARKENED 2026-08-20 (owner: "darkening the colour for the hero animation so
   it stands out more"). This rule OWNS the hero ground — specificity 0,2,1 on
   its first selector, which is why the day's photo experiment had to be written
   at 0,2,1 too, and why anything written as a plain `body .sf-hero` further
   down the file is dead CSS. Darken HERE or do not darken.
   The three washes come down with the ramp (0.20/0.22/0.16 -> 0.13/0.14/0.10)
   for the reason the whole change exists: the network's mesh is the only thing
   in this band that is meant to glow, and a corner wash competing with it is
   what reads as "opaque". Brightest composited pixel is now the cyan corner at
   rgb(13,32,41), L=0.01456, where --sf-text measures 15.24:1 — up from 11.52:1,
   i.e. the darkening only ever moves the headline in the safe direction. */
body.opt-hero-bg-gradient .sf-hero, body .sf-hero {
  background:
    radial-gradient(120% 95% at 88% -12%, rgb(63 182 201 / 0.13) 0%, transparent 58%),
    radial-gradient(105% 90% at -12% 108%, rgb(28 94 150 / 0.14) 0%, transparent 56%),
    radial-gradient(80% 70% at 50% 120%, rgb(19 92 110 / 0.10) 0%, transparent 60%),
    linear-gradient(168deg, #050D1C 0%, #030A16 46%, #02070F 100%);
  border-bottom: 1px solid rgb(87 214 228 / 0.20);
}
body .sf-hero-kicker { color: var(--cc-cyan-300); }
/* Was --cc-navy-800 - navy ink on a navy hero. The page ink is the only
   headline colour that survives the flip. */
body .sf-hero-title { color: var(--sf-text); }
body .sf-hero-stats b { color: var(--cc-cyan-300); }
body .sf-trust-row span { border-color: rgb(87 214 228 / 0.28); background: rgb(63 182 201 / 0.07); }

/* THE HERO SUBLINE MOVES UNDER THE CTA ROW (owner, 2026-08-18) */
body.layout-hero-product-forward .sf-hero-sub {
  grid-column: 1 / -1;
  grid-row: auto;
  align-self: start;
  margin-top: 0.95rem;
  max-width: 62ch;
  color: var(--sf-muted);
}

/* ======================================================================
/* ======================================================================
   THE HERO LATTICE IS GONE (owner, 2026-08-22: "the hero on the home page
   — I want the entire animation gone and just the navy background").

   WHAT WAS HERE, and why deleting it is not a visual change: ~105 lines of
   .sf-hero-motif styling and FIVE keyframe families — cc-aura, cc-lattice-
   drift, cc-bond-current, cc-node-pulse and cc-sweep. Every one of them was
   DEAD PAINT already: .sf-hero-motif carries a top-level display:none at the
   "THE HERO FIGURE IS THE HEX MESH" block below, and has since 2026-08-19.

   IT WAS ALSO A GATE BLIND SPOT, WHICH IS THE REAL REASON IT COULD NOT STAY.
   verifyHeroMotif() (build.mjs step 10) greps the ONE generated <style> block
   inside index.html; this is an external sheet, so five keyframe families
   aimed squarely at .sf-hero-motif sat outside every gate that exists. The day
   someone deletes the display:none below — a one-line change that looks
   harmless — a hero pinned to the STILL lane starts animating and nothing
   anywhere fails. Dead CSS that can resurrect itself is worse than dead CSS.

   The hero figure this client actually wears is now the enlarged brand mark:
   assets/logo-mark.svg, placed and coloured by final/12-hero.mjs +
   final/css/hero.css. The display:none below STAYS — see the note there.
   ====================================================================== */

/* ======================================================================
   COA PROOF - the sample certificate is gone; the standards rail moves
   up into the column it left behind (owner, 2026-08-18).
   ====================================================================== */
body #coa-proof {
  background:
    radial-gradient(70% 80% at 6% 0%, rgb(63 182 201 / 0.13) 0%, transparent 60%),
    radial-gradient(70% 80% at 96% 100%, rgb(28 94 150 / 0.16) 0%, transparent 62%),
    linear-gradient(180deg, #0C1726 0%, var(--sf-bg) 48%, #0C1726 100%);
}
body #coa-proof .coa2-glow-a { background: radial-gradient(ellipse at top left, rgb(22 191 209 / 0.26) 0%, transparent 64%); }
body #coa-proof .coa2-glow-b { background: radial-gradient(ellipse at bottom right, rgb(28 94 150 / 0.20) 0%, transparent 66%); }
body #coa-proof .coa2-grid { grid-template-columns: 1fr 1fr; align-items: start; }
body #coa-proof .coa2-accent { color: var(--cc-cyan-300); }
body #coa-proof .section-label { color: var(--cc-cyan-300); }
body #coa-proof .coa2-topline { background: linear-gradient(90deg, transparent, var(--cc-cyan-300), transparent); }
body #coa-proof .coa2-lede { max-width: 46ch; }

/* the standards rail - replaces the three-card pillar row */
/* The rail was a WHITE card - the single largest light surface on the page and
   the one thing that would still read as a light site after the flip. It
   becomes a machined slate panel: brushed-metal edge, one specular bevel at the
   top, and a cyan-to-silver cap rail. Ground #101E2E, L=0.01236, is the ink
   reference every ratio in this block is computed against:
     --sf-text 15.44:1 · --cc-cyan-300 9.74:1 · --cc-silver-400 7.79:1 */
body .std-rail {
  position: relative;
  background: linear-gradient(160deg, #101E2E 0%, #0A1523 100%);
  border: 1px solid var(--cc-metal-line);
  border-radius: var(--sf-radius, 16px);
  padding: 1.9rem 1.7rem 1.6rem;
  box-shadow: 0 22px 52px -34px rgb(0 0 0 / 0.9), inset 0 1px 0 rgb(240 246 250 / 0.09);
  overflow: hidden;
}
body .std-rail::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: linear-gradient(90deg, var(--cc-cyan-300) 0%, var(--cc-blue-500) 50%, var(--cc-silver-500) 100%);
}
body .std-rail .std-eyebrow {
  display: inline-block; font-size: 0.78rem; font-weight: 800; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--cc-cyan-300); margin-bottom: 0.5rem;
}
body .std-rail .std-h {
  font-family: var(--sf-font-display, inherit);
  font-size: clamp(1.15rem, 1.8vw, 1.45rem); font-weight: 800; letter-spacing: -0.028em;
  color: var(--sf-text); line-height: 1.2; margin: 0 0 1.5rem;
}
body .std-rail .std-list { list-style: none; margin: 0; padding: 0; position: relative; }
body .std-rail .std-list::before {
  content: ""; position: absolute; left: 21px; top: 14px; bottom: 26px; width: 2px;
  background: linear-gradient(180deg, var(--cc-cyan-300) 0%, var(--cc-blue-500) 45%, rgb(166 186 203 / 0.14) 100%);
  border-radius: 2px;
}
body .std-rail .std-item {
  position: relative; display: grid; grid-template-columns: 44px 1fr; gap: 0.95rem;
  padding: 0 0 1.35rem;
}
body .std-rail .std-item:last-child { padding-bottom: 0; }
body .std-rail .std-num {
  position: relative; z-index: 1;
  width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.78rem; font-weight: 800; letter-spacing: 0.02em;
  color: #F4FCFF;
  background: linear-gradient(140deg, var(--cc-blue-600) 0%, var(--cc-teal-700) 100%);
  /* The 4px halo is what punches the disc through the connector line, so it has
     to be the RAIL's ground - it was --cc-ice-50, a white ring that would have
     been the brightest thing on the page. */
  box-shadow: 0 0 0 4px #101E2E, 0 6px 16px -8px rgb(0 0 0 / 0.8);
}
body .std-rail .std-item:nth-child(1) .std-num { background: linear-gradient(140deg, var(--cc-teal-600) 0%, var(--cc-teal-700) 100%); }
body .std-rail .std-item:nth-child(3) .std-num { background: linear-gradient(140deg, var(--cc-blue-600) 0%, var(--cc-navy-600) 100%); }
body .std-rail .std-t {
  font-size: 0.98rem; font-weight: 750; letter-spacing: -0.02em;
  color: var(--sf-text); margin: 0.55rem 0 0.35rem;
  display: flex; align-items: center; gap: 0.5rem;
}
body .std-rail .std-t svg { width: 17px; height: 17px; flex: none; color: var(--cc-cyan-300); }
/* 0.83rem -> 0.92rem and 0.76rem -> 0.86rem: this rail is dense fine print and
   it was the smallest running copy on the page. See the TYPE SCALE block. */
body .std-rail .std-d { font-size: 0.92rem; line-height: 1.68; color: var(--sf-muted); margin: 0; }
body .std-rail .std-foot {
  margin-top: 1.4rem; padding-top: 1.1rem; border-top: 1px solid var(--cc-metal-line);
  font-size: 0.86rem; line-height: 1.6;
  /* --cc-silver-500 measures 5.13:1 on the rail - passing, but this is the
     smallest type in the block and the one place a passing-but-thin ink is a
     bad trade. --cc-silver-400 is 7.79:1 at no design cost. */
  color: var(--cc-silver-400);
}
body .std-rail .std-foot b { color: var(--cc-cyan-300); font-weight: 750; }

@media (max-width: 820px) {
  body #coa-proof .coa2-grid { grid-template-columns: 1fr; }
  body .std-rail { padding: 1.6rem 1.25rem 1.35rem; }
}

/* ======================================================================
   TYPE SCALE - sitewide, fluid (owner relaying the client, 2026-08-19:
   "increase font sitewide - needs to be big enough to read on mobile").

   WHY THE ROOT AND NOT JUST THE BODY TOKEN. --sf-body-fs is the obvious
   lever and on its own it fixes almost nothing: it lands on `body` only
   (sf-base.css:27), while the template sizes practically everything else
   in REM - 0.85rem trust rows, 0.78rem footer titles, 0.72rem contact
   notes, 0.65rem footer warnings - and rem resolves against the ROOT, not
   against body. Raising body alone would have left every one of those at
   its old pixel size and made the mismatch worse. So the root moves, the
   body token is expressed as 1rem, and one number carries the whole site.

   THE NUMBERS. The generated theme ships --sf-body-fs: 16px and the built
   client theme narrows it to 15px (the "compact" body-scale axis value) -
   which is the client's complaint, precisely. At 375px the clamp below
   resolves the root to 17.01px and at >=657px to 18px, so:

     surface            before        after (mobile / desktop)
     body copy          15px          17.01px / 18px
     footer link .92rem 13.8px        15.65px / 16.6px
     footer copy .78rem 11.7px        13.3px  / 14.0px
     hero h1 (product-forward layout, clamp(1.7rem,3.2vw,2.5rem))
                        25.5-42.5px   28.9-42.5px / 30.6-45px

   Body clears the 17px floor at every viewport a phone reports, which is
   the line mobile Safari and Chrome use before they offer to zoom.

   FLUID, NOT TWO BREAKPOINTS: one clamp() means no step where a word
   reflows, and it keeps working at the tablet widths nobody tests.

   `html:root` (0,1,1) rather than bare `html` (0,0,1) is deliberate:
   tailwind.min.css is linked AFTER this file on every page, and a
   preflight `html { font-size }` at equal specificity would win on
   source order. This cannot lose that race.

   HEADINGS SCALE WITH IT rather than being restated. Every display size
   in the template is calc(clamp(<rem>, <vw>, <rem>) * --sf-display-scale),
   so the rem ends of those clamps ride the root and the vw end is
   untouched - headings grow on phones, where the rem end governs, and
   hold their tuned maximum on desktop, where the vw end does. Nothing
   here restates a heading selector.
   ====================================================================== */
html:root {
  /* 320px -> 17px (floor) · 375px -> 17.01px · >=657px -> 18px (ceiling) */
  font-size: clamp(17px, 0.35vw + 15.7px, 18px);
}
:root {
  /* Was 15px. In rem so it tracks the root instead of being a third number
     to keep in step. */
  --sf-body-fs: 1rem;
  --sf-body-lh: 1.72;

  /* The four sizing tokens the template exposes. Each is a rem multiple, so
     these are proportions of the fluid root, not new fixed sizes. */
  --sf-card-title-fs: 1.12rem;   /* was 1.05rem */
  --sf-btn-fs:        1rem;      /* was 0.95rem - buttons are tap targets */
  --sf-input-fs:      1rem;      /* was 0.98rem - <16px computed makes iOS
                                    Safari zoom the page on focus, which is a
                                    layout jump on every form on the site */
  --sf-badge-fs:      0.82rem;   /* was 0.72rem */
}

/* The template's hardcoded sub-body sizes. No size token reaches these - they
   are literals in sf-base.css - so the smallest set of selectors that does the
   job is listed rather than restating the type system. Floor is 0.8rem
   (13.6px mobile / 14.4px desktop) for chrome fine print, 0.9rem for anything
   that carries a sentence. Sizes only: not one of these rules touches colour,
   so no measured contrast pair moves. */
body .sf-hero-stats span,
body .sf-trust-row,
body .sf-card-meta-extra { font-size: 0.9rem; }
body .sf-card-meta { font-size: 0.8rem; }

/* Retightened 2026-08-26 (owner: "reduce font size where necessary") and the
   dead selectors dropped — final/19-footer.mjs deletes the RUO pill and the
   notice panel from the markup, so their rules would style nothing. */
body .sf-ftr-tagline,
body .sf-ftr-link,
body .sf-ftr-ct-strong,
body .sf-ftr-fine { font-size: 0.9rem; }
body .sf-ftr-ct-body,
body .sf-ftr-copy,
body .sf-ftr-cta,
body .sf-ftr-bottom-links .sf-ftr-link { font-size: 0.85rem; }
/* Uppercase tracked labels read a size smaller than they measure. */
body .sf-ftr-title { font-size: 0.78rem; }
body .sf-ftr-warn { font-size: 0.76rem; }

body .sf-ctaband-kicker { font-size: 0.8rem; }
body .sf-ctaband-sub { font-size: 1rem; }
body .sf-ctaband-btn { font-size: 0.95rem; }

/* A phone gets the extra size where it is actually read: running copy and the
   two label registers, held at the floor the client asked for rather than
   scaled down again by a narrow viewport. */
@media (max-width: 30rem) {
  body .sf-ctaband-sub,
  body .sf-ftr-tagline,
  body .std-rail .std-d { font-size: 1rem; }
  body .sf-card-meta { font-size: 0.85rem; }
}

/* ======================================================================
   THE HERO FIGURE IS THE HEX MESH (client instruction, 2026-08-19)
   "instead of the lattice pattern it needs to be the hex pattern seen on
   the other nav heros. it will be used sitewide for the heroes."

   The built-in figure still renders on the home page because
   factory/tools/lib/hero-motif.mjs has TEN figures and none of them is a
   hexagon — it resolved to `lattice` as the nearest neighbour, which is
   the exact shape the client asked us to stop using. So the home hero was
   drawing a lattice behind the hex band it was supposed to be replaced by.

   Hidden rather than deleted, deliberately. verifyHeroMotif() runs at
   build step 10 and fails an EMPTY motif region; the region is still
   emitted, still gated, still carries its reduced-motion guard. It simply
   is not the figure this client wears. Removing the markup would trade a
   visual defect for a gate failure.

   The real fix is a `hex` figure in the resolver — this client's
   brand.logoMotif already reads "three hexagonal rings connected by short
   branching line segments", so the evidence was there and there was
   nothing to resolve TO. Recorded in BUILD-LOG.md as the refit task this
   pass throws off.

   STILL TRUE AND STILL DELIBERATE, 2026-08-22. The owner asked for the hero
   animation gone; the mesh above went with it, and the figure the hero now
   wears is the client's own brand mark (assets/logo-mark.svg, placed by
   final/12-hero.mjs). This rule is what keeps the resolver's `lattice` — the
   very shape the client asked us to stop drawing — from appearing behind it.
   DO NOT DELETE THIS LINE without also removing the motif region, which is an
   unconditional build failure at build.mjs verifyHeroMotif(). One line, two
   jobs: it hides the wrong figure and it keeps the gate satisfied.
   ====================================================================== */
.sf-hero-motif { display: none; }

/* ======================================================================
   THE HERO AS A FULL-BLEED CENTRED STAGE (owner reference, 2026-08-19)

   The owner handed over a React/Tailwind hero as the styling reference:
   a full-screen section, a 12-column rule overlay, a cover image under a
   dark scrim, centred display type, and a split pill CTA whose arrow
   slides out to the upper right while a second slides in behind it.

   WHAT IS TAKEN AND WHAT IS NOT (BUILD-DEFAULTS.md hierarchy: a reference
   site is authority for STRUCTURE, never for COLOUR):
     TAKEN     the full-viewport stage, the centred single-column stack,
               the vertical column rules, the scrim, the display-scale
               headline, the pill + circular-arrow CTA and its slide.
     NOT TAKEN the reference's lime #e1fcad / #122023 pair and its stock
               photo. The pill wears THIS client's accent (--cc-cyan-300
               on --cc-navy-900, 11.30:1) and the "cover image" is the
               client's own hex network, which already paints the ground.

   ALL OF IT IS CSS. No markup moved, so nothing here can collide with the
   hex-network session (.claude/coordination/2026-08-19-...-parallel-sessions.md)
   and re-running apply-refresh.mjs cannot undo it.

   LAYERS, and why the numbers are what they are: the network SVG mounts
   into .sf-hero at z-index 0 (cc-network.part.html:588), #hero-content is
   at z-index 10. The scrim takes 1, the column rules 2 - between the two,
   exactly where the reference puts its overlay and its grid.
   ====================================================================== */

body .sf-hero {
  /* Full stage, minus the fixed nav the section is already offset by
     (index.html: body:not(.layout-default) #hero { margin-top: --sf-nav-h }).
     THE LADDER (owner, 2026-08-26): vh for browsers that know no viewport
     variants, svh next, dvh LAST AND DELIBERATE. svh fills only the
     URL-bar-visible state — with the bar retracted the visible viewport
     grew past 100svh and #categories' white plate peeked into the first
     screen, which is the exact defect the owner filmed. dvh re-solves
     against the live viewport in both bar states. Do not "simplify" this
     back to svh: the white peek is the regression. */
  min-height: calc(100vh - var(--sf-nav-h, 64px));
  min-height: calc(100svh - var(--sf-nav-h, 64px));
  min-height: calc(100dvh - var(--sf-nav-h, 64px));
  display: flex;
  align-items: center;
  justify-content: center;
  padding-block: clamp(3.5rem, 8vh, 6.5rem);

  /* ONE DIAL FOR THE SCRIM. The reference uses bg-black/40 flat because a
     photo's luminance is unknowable; ours is not - the network paints an
     opaque, measured ground, so a flat 40% would only throw away the
     twinkle that session was tuned for. This is centred instead: full
     strength under the copy, nothing at the edges. Set it to 0 to remove
     the scrim entirely without touching a gradient.

     0.36 -> 0.12, 2026-08-20 (owner: "it looks too opaque right now"). That
     value was set while a PHOTOGRAPH was under the band and its luminance
     genuinely was unknowable; the photo came out the same day and the ground
     beneath the mesh is once more a measured hex, which the network's own
     ground has already darkened by 26%. A 36% black wash on top of that is
     no longer protection, it is the muddiness — it dims the crest of every
     twinkle and every wire equally, which is exactly the "details of the
     design and lighting" the owner cannot see. Kept non-zero only to settle
     the copy's own centre. THE CONTRAST FLOOR IS NOT AT RISK EITHER WAY: the
     scrim can only DARKEN, and solve-contrast.mjs models it at ZERO, so the
     mesh's budgets hold at any value from here to 1. */
  --cc-hero-scrim: 0.12;
  --cc-hero-rule: var(--cc-metal-line);
}

/* THE SCRIM - the reference's dark overlay, concentrated where the words
   are. It can only DARKEN a ground that is already dark, so every contrast
   number the network session measured moves in the safe direction. */
body .sf-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(78% 62% at 50% 50%,
      rgb(4 10 20 / var(--cc-hero-scrim)) 0%,
      rgb(4 10 20 / calc(var(--cc-hero-scrim) * 0.52)) 46%,
      transparent 76%),
    linear-gradient(to bottom,
      rgb(4 10 20 / calc(var(--cc-hero-scrim) * 0.75)) 0%,
      transparent 20%,
      transparent 72%,
      rgb(4 10 20 / calc(var(--cc-hero-scrim) * 0.9)) 100%);
}

/* THE COLUMN RULES - the reference's `grid-cols-12 divide-x divide-white/20`
   with spans 1-3-4-3-1, so the dividers land on the 1st, 4th, 8th and 11th
   twelfths. Drawn as four 1px gradients rather than five empty divs: same
   picture, no markup. The ink is the brand's own hairline token, not white
   at 20% - a white rule on deep navy reads colder than this brand is. */
body .sf-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image:
    linear-gradient(var(--cc-hero-rule), var(--cc-hero-rule)),
    linear-gradient(var(--cc-hero-rule), var(--cc-hero-rule)),
    linear-gradient(var(--cc-hero-rule), var(--cc-hero-rule)),
    linear-gradient(var(--cc-hero-rule), var(--cc-hero-rule));
  background-size: 1px 100%;
  background-repeat: no-repeat;
  background-position: 8.3333% 0, 33.3333% 0, 66.6667% 0, 91.6667% 0;
}

/* THE CENTRED STACK. theme.css gives this layout a two-column grid with
   .sf-hero-copy set to `display: contents` so the title and the sub land in
   separate columns; a centred hero wants one column, so the copy becomes a
   real flex box again and every child centres inside it. The grid-column
   rules on the children go inert on their own once the parent is not a grid.
   ORDER IS DELIBERATELY UNCHANGED: the subline stays UNDER the CTA row,
   which is the owner's call of 2026-08-18, not the reference's order. */
/* The ID is load-bearing, not decoration: theme.css scopes this element as
   `body.layout-hero-product-forward .sf-hero-inner` (0,2,1) and a plain
   `body .sf-hero-inner` (0,1,1) loses to it no matter which file loads last.
   MEASURED: the first pass of this rule was inert - the inner stayed a
   two-column grid and the centred stack never happened. */
body #hero-content,
body .sf-hero .sf-hero-inner {
  position: relative;
  z-index: 10;
  display: block;
  width: 100%;
  max-width: 64rem;
  margin-inline: auto;
}
body .sf-hero .sf-hero-copy {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0;
}
body .sf-hero .sf-hero-kicker {
  margin-bottom: 0.9rem;
  font-size: 0.78rem;
  letter-spacing: 0.2em;
}
/* Display scale, tracked tight - the reference's text-5xl to lg:text-8xl. */
body .sf-hero .sf-hero-title {
  /* No cap of its own: the 64rem inner already holds the measure, and a
     tighter one wrapped a 38-character headline onto four lines where the
     reference wraps onto two. */
  margin: 0;
  font-size: calc(clamp(2.5rem, 6.4vw, 5.25rem) * var(--sf-display-scale, 1));
  line-height: 1.04;
  letter-spacing: -0.03em;
  text-wrap: balance;
}
body .sf-hero .sf-hero-cta {
  /* Declared on the ROW, not on the pill: the secondary button has to read
     the same height or the pair stops matching the moment a media query
     moves one of them. MEASURED at 375px before this: 51px vs 57px. */
  --cc-cta-h: 3.35rem;
  --cc-cta-gap: 0.4rem;
  justify-content: center;
  margin-top: clamp(1.75rem, 3.4vh, 2.6rem);
}
body .sf-hero .sf-hero-sub {
  max-width: 52ch;
  margin: clamp(1.5rem, 3vh, 2.2rem) auto 0;
  font-size: 1.02rem;
  line-height: 1.7;
  font-weight: 400;
  /* The reference's text-white/90. De-emphasis is a SOLVED second ink here,
     never an alpha fade - --sf-muted is #A6BACB, 9.68:1 on the page ground. */
  color: var(--sf-muted);
}
body .sf-hero .sf-trust-row,
body .sf-hero .sf-hero-stats {
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 1.6rem;
}
body .sf-hero .sf-hero-stats { gap: clamp(1.4rem, 4vw, 3rem); }

/* -- THE SPLIT PILL CTA ------------------------------------------------
   The reference builds this from two elements plus two lucide icons. Our
   markup already ships both halves - the <a> and its .sf-ctaband-arrow
   span - so the anchor becomes the pill and the span becomes the circular
   badge, positioned just OUTSIDE the anchor's box with the space reserved
   by an equal margin. The badge is still a DOM child of the anchor, so it
   hovers, focuses and clicks as one control.
   Scoped to the hero on purpose: the site-wide primary register stays the
   deep teal gradient decided on 2026-08-18 ("a light button on a near-black
   ground is a flashbulb"). One light pill, in the one place the reference
   puts one. --cc-cyan-300 on --cc-navy-900 measures 11.30:1. */
body .sf-hero .sf-hero-cta .sf-btn-primary,
body .sf-hero .sf-hero-cta .btn-teal {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: var(--cc-cta-h);
  padding: 0 1.85rem;
  margin-right: calc(var(--cc-cta-h) + var(--cc-cta-gap));
  border: 0;
  border-radius: 999px;
  background: var(--cc-cyan-300);
  color: var(--cc-navy-900);
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  box-shadow: none;
  transition: background-color 0.5s ease, color 0.5s ease;
}
body .sf-hero .sf-hero-cta .sf-btn-primary:hover,
body .sf-hero .sf-hero-cta .btn-teal:hover,
body .sf-hero .sf-hero-cta .sf-btn-primary:focus-visible {
  background: var(--cc-navy-900);
  color: var(--cc-cyan-300);
  box-shadow: none;
}
body .sf-hero .sf-hero-cta .sf-btn-primary:focus-visible {
  outline: 2px solid var(--cc-cyan-100);
  outline-offset: 3px;
}

body .sf-hero .sf-hero-cta .sf-ctaband-arrow {
  position: absolute;
  top: 50%;
  left: calc(100% + var(--cc-cta-gap));
  width: var(--cc-cta-h);
  height: var(--cc-cta-h);
  transform: translateY(-50%);
  border-radius: 50%;
  background: var(--cc-cyan-300);
  color: var(--cc-navy-900);
  overflow: hidden;
  /* the markup's &rarr; glyph collapses; the two arrows below are drawn */
  font-size: 0;
  transition: background-color 0.5s ease, color 0.5s ease;
}
body .sf-hero .sf-hero-cta .sf-btn-primary:hover .sf-ctaband-arrow,
body .sf-hero .sf-hero-cta .sf-btn-primary:focus-visible .sf-ctaband-arrow {
  background: var(--cc-navy-900);
  color: var(--cc-cyan-300);
  transform: translateY(-50%);
}
/* Two arrows, one mask each, tinted by currentColor so they flip with the
   badge. ::before is the one at rest and leaves to the upper right; ::after
   waits below-left and takes its place - the reference's pair of
   ArrowUpRight icons, drawn instead of imported. */
body .sf-hero .sf-hero-cta .sf-ctaband-arrow::before,
body .sf-hero .sf-hero-cta .sf-ctaband-arrow::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 1.2rem;
  height: 1.2rem;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 17 17 7'/%3E%3Cpath d='M7 7h10v10'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 17 17 7'/%3E%3Cpath d='M7 7h10v10'/%3E%3C/svg%3E") center / contain no-repeat;
  transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
body .sf-hero .sf-hero-cta .sf-ctaband-arrow::after { transform: translate(-190%, 190%); }
body .sf-hero .sf-hero-cta .sf-btn-primary:hover .sf-ctaband-arrow::before,
body .sf-hero .sf-hero-cta .sf-btn-primary:focus-visible .sf-ctaband-arrow::before {
  transform: translate(190%, -190%);
}
body .sf-hero .sf-hero-cta .sf-btn-primary:hover .sf-ctaband-arrow::after,
body .sf-hero .sf-hero-cta .sf-btn-primary:focus-visible .sf-ctaband-arrow::after {
  transform: translate(0, 0);
}

/* The second button keeps its quiet register and only takes the pill's
   shape and height, so the pair reads as one control group. */
body .sf-hero .sf-hero-cta .sf-btn-secondary,
body .sf-hero .sf-hero-cta .btn-ghost-white {
  display: inline-flex;
  align-items: center;
  min-height: var(--cc-cta-h);
  padding: 0 1.7rem;
  border-radius: 999px;
  font-size: 0.95rem;
}

@media (max-width: 640px) {
  /* Four rules across a phone is a fence. Keep the outer pair. */
  body .sf-hero::after {
    background-image:
      linear-gradient(var(--cc-hero-rule), var(--cc-hero-rule)),
      linear-gradient(var(--cc-hero-rule), var(--cc-hero-rule));
    background-position: 8.3333% 0, 91.6667% 0;
  }
  body .sf-hero .sf-hero-cta { --cc-cta-h: 3rem; }
}

@media (prefers-reduced-motion: reduce) {
  body .sf-hero .sf-hero-cta .sf-ctaband-arrow::before,
  body .sf-hero .sf-hero-cta .sf-ctaband-arrow::after { transition: none; }
  /* No slide at all: the resting arrow simply stays, and the understudy
     never enters. A swap with the motion removed is just a flicker. */
  body .sf-hero .sf-hero-cta .sf-ctaband-arrow::after { display: none; }
  body .sf-hero .sf-hero-cta .sf-btn-primary:hover .sf-ctaband-arrow::before,
  body .sf-hero .sf-hero-cta .sf-btn-primary:focus-visible .sf-ctaband-arrow::before {
    transform: none;
  }
}

@media (forced-colors: active) {
  body .sf-hero::after { display: none; }
}

/* ======================================================================
   OWNER CHANGE #2 — 2026-08-20 — THE HOME HERO
   Three things, all scoped so nothing outside the home hero moves:
     A. the laboratory photograph goes UNDER the hex network, the way the
        reference site carries a photograph under its overlay
     B. the three trust tags come out (markup: apply-refresh.mjs step 14)
        and a USA badge takes their place
     C. the detached circular arrow beside "Browse Peptides" comes out
   ====================================================================== */

/* ── A. NO PHOTOGRAPH UNDER THE HERO ───────────────────────────────────
   REVERSED 2026-08-20, same day it went in (owner: "removing the lab photo
   on the home page and the hero"). The photograph had been layered under
   the hex network with the network's own ground dropped to a 0.62 VEIL over
   it — which is what made the band read muddy: every node and wire was
   painted through a two-thirds-strength ground onto an unknowable pixel.

   With the photo gone the veil goes with it. The network's ground rect is
   OPAQUE again (its default), which is the state cc-network.part.html's
   whole contrast budget is solved in — the pixel under the headline is a
   known hex, not a photo, so the mesh's alphas are arithmetic again rather
   than a guess. The section keeps only the flat navy ramp beneath the band,
   which is what shows in the gutters either side of the network's own rect.

   The body class `body.cc-hero-photo` is NO LONGER STAMPED (apply-refresh step
   13 now strips it), and NO REPLACEMENT RULE IS WRITTEN HERE ON PURPOSE. The
   hero ground already has an owner further up this file
   (`body.opt-hero-bg-gradient .sf-hero`, specificity 0,2,1) and that is where
   the owner's darkening went. A rule written at this point of the file as a
   plain `body .sf-hero` LOSES to it and paints nothing — worth knowing, because
   it looks like it works right up until somebody measures the pixel. */

/* ── B. THE USA BADGE ──────────────────────────────────────────────────
   Sits where the three tags were: under the subline, on the hero's centre
   line. A hairline pill, not a sticker — the flag is the only saturated
   thing in it, so it reads as a mark rather than as a fourth button.
   --cc-silver-200 on the hero ground measures well past 4.5:1. */
body .sf-hero .cc-usa-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin-top: 1.6rem;
  padding: 0.45rem 1rem 0.45rem 0.7rem;
  border: 1px solid var(--cc-metal-line);
  border-radius: 999px;
  background: rgb(7 26 51 / 0.55);
  color: var(--cc-silver-200);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
body .sf-hero .cc-usa-badge svg {
  display: block;
  width: 1.45rem;
  height: auto;
  border-radius: 2px;
  box-shadow: 0 0 0 1px rgb(203 217 228 / 0.28);
}

/* ── C. THE DETACHED ARROW COMES OUT ───────────────────────────────────
   The split-pill CTA keeps its pill; the circular ArrowUpRight badge that
   floated beside it is gone, and the margin that reserved its slot with
   it, so the two buttons sit at the gap the row was designed for. */
body .sf-hero .sf-hero-cta .sf-btn-primary,
body .sf-hero .sf-hero-cta .btn-teal { margin-right: 0; }
body .sf-hero .sf-hero-cta .sf-ctaband-arrow { display: none; }

/* ── D. THE PRODUCT TILE'S ONE BUTTON (owner change #3, 2026-08-20) ─────
   The tile shipped a labelled "View Details" link plus an icon-only cart
   button. The owner asked for one button, reading Add to cart, and
   apply-refresh.mjs step 17 removes the icon and gives the remaining
   control the cart action. So the remaining control has to LOOK like the
   primary it now is: the same navy the icon button carried, full width of
   the action row, and the same emerald "added" flash — the engine's
   emerald rule is scoped to .tile-btn-atc, which no longer exists here. */
#product-grid .tile-actions .tile-btn-details {
  flex: 1 1 100%;
  background: var(--sf-bg);
  color: var(--sf-text);
  border: 1px solid var(--sf-bg);
}
#product-grid .tile-actions .tile-btn-details:hover {
  background: #1B3468;
  border-color: #1B3468;
  box-shadow: 0 6px 18px rgb(var(--sf-shadow) / 0.28);
}
#product-grid .tile-actions .tile-btn-details.is-added,
#product-grid .tile-actions .tile-btn-details.is-added:hover {
  background: #059669;
  border-color: #059669;
}
#product-grid .tile-actions .tile-btn-details.is-limit,
#product-grid .tile-actions .tile-btn-details.is-limit:hover {
  background: var(--sf-muted);
  border-color: var(--sf-muted);
}

/* ══════════════════════════════════════════════════════════════════════
   OWNER CHANGES, 2026-08-20 (second pass)
   ══════════════════════════════════════════════════════════════════════ */

/* ── E. THE HERO COLUMN RULES COME OUT ─────────────────────────────────
   The four vertical hairlines the reference's `divide-x` inspired (drawn
   above as body .sf-hero::after, on the 1st/4th/8th/11th twelfths). The
   owner does not want the hero divided. The pseudo-element is not
   generated at all, so its background stack and the phone-width override
   are both inert rather than merely invisible. */
body .sf-hero::after { content: none; }

/* ── F. ONE ARROW PER CTA ──────────────────────────────────────────────
   Two arrows were reaching the same button from two places: the markup
   ships <span class="sf-ctaband-arrow">&rarr;</span>, and the design axis
   opt-btn-arrow-arrow adds a second one at .sf-btn-cta::after. Where both
   land on one control the MARKUP one comes out and the ::after glyph — the
   site's own arrow register, with the same hover nudge — is the survivor.
   Sitewide by rule, not per-button, so a future CTA cannot regrow the pair.
   (The hero's copy of this span is already hidden at section C above.) */
body.opt-btn-arrow-arrow .sf-btn-cta .sf-ctaband-arrow { display: none; }

/* ── G. CORNERS — ONE SLIGHT RADIUS, SITEWIDE ──────────────────────────
   OWNER REVERSAL, 2026-08-26: "add that same slight rounding to all boxes
   sitewide - i dont want any rigid boxes anywhere." This supersedes the
   2026-08-20 square-buttons rule that lived in this section — do not
   restore the zeros citing the old comment; the reversal is the owner's
   own words and is journaled in BUILD-LOG.md.
   ONE TOKEN drives every surface family: --cc-radius, 8px — already the
   site's most common non-zero literal (the nav drawer, the checkout page
   styles, the theme's own --sf-input-radius, the legal/money inline
   panels), so one value converges the most boxes. The selector list below
   is KEPT VERBATIM from the square pass, minus the circle-designed modal
   close (restored to its circle, note below): its specificity was chosen
   to beat the page styles and the runtime-injected nav/cart styles
   (BUILD-LOG 2026-08-20), and the same win now carries the radius.
   Deliberately NOT re-pinned: --sf-badge-radius (999px chips are already
   round — not rigid) and --sf-check-radius (4px ticks). The app pages'
   own late-loading sheet (final/css/app-surfaces.css) was moved to the
   same token the same day — a 0/2px literal there loads after this file
   and would silently re-square all 14 cc-app pages. */
:root {
  --cc-radius: 8px;
  --sf-radius: var(--cc-radius);
  --sf-btn-radius: var(--cc-radius);
  --sf-card-radius: var(--cc-radius);
  --sf-input-radius: var(--cc-radius);
  --sf-modal-radius: var(--cc-radius);
  --sf-menu-radius: var(--cc-radius);
  --sf-thumb-radius: var(--cc-radius);
  --sf-img-radius: var(--cc-radius);
}

html body .sf-btn,
html body .sf-btn-primary,
html body .sf-btn-secondary,
html body .sf-btn-cta,
html body .sf-ctaband-btn,
html body .sf-form-btn,
html body .btn,
html body .btn-primary,
html body .btn-teal,
html body .btn-navy,
html body .btn-dark,
html body .btn-red,
html body .btn-glass,
html body .btn-submit,
html body .btn-ghost,
html body .btn-ghost-white,
html body .btn-coa,
html body .co-btn,
html body .pg-btn,
html body .qty-btn,
html body .gate-btn,
html body .tab-btn,
html body .mini-btn,
html body .copy-btn,
html body .mo-reorder-btn,
html body .tile-btn,
html body .snav-icon-btn,
html body .snav-mob-btn,
html body #product-grid .tile-btn,
html body .sf-coa-link,
html body .sf-qty,
html body button.sf-pressable {
  border-radius: var(--cc-radius, 8px);
}

/* body.opt-close-style-circle .sf-modal-close came OUT of the list above:
   this client's body wears opt-close-style-circle, so that control was
   DESIGNED as a circle — the square pass flattened it, and a blanket 8px
   would leave it a rounded square instead of restoring the engine's own
   circle. No override = the circle is back. */

/* The hero's split-pill CTA and its quiet partner were shaped at section
   "THE SPLIT PILL CTA" above; same file, so these simply come later.
   Same slight radius as everything else (owner, 2026-08-26). */
body .sf-hero .sf-hero-cta .sf-btn-primary,
body .sf-hero .sf-hero-cta .btn-teal,
body .sf-hero .sf-hero-cta .sf-btn-secondary,
body .sf-hero .sf-hero-cta .btn-ghost-white {
  border-radius: var(--cc-radius, 8px);
}

/* ══════════════════════════════════════════════════════════════════════════
   OWNER CHANGE #4, 2026-08-20 — THE RESEARCH PAGE
   The nav's "Research" is standards.html. Three defects the owner named, all
   born the same way: that page was authored for the LIGHT ground the site
   shipped on, and the dark flip never reached it.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── A. THE STRAY LINE, AND THE SWOLLEN "01" ────────────────────────────────
   Fixed above, at the rules themselves, not here — the home page's numbered
   rail is built from .std-list / .std-item / .std-num and every one of those
   rules was written UNSCOPED (`body .std-list`). standards.html uses .std-list
   for the five-standard ACCORDION and .std-num for the little "01 · Purity"
   category line, so the rail's 2px connector was drawn down the left of the
   accordion — starting under "Standards researchers can review." and running
   through all five rows — and each "01"…"05" was inflated into a 44px blue
   disc. Every rule in that block is now scoped `body .std-rail .std-*`.
   The rail on index.html is unchanged: its .std-list IS inside .std-rail. */

/* ── B. THE BROWN ───────────────────────────────────────────────────────────
   standards.html carries two page-local tokens, --pg-warm #A85420 and
   --pg-warm-ink #96481A. They were solved as a warm counterweight to a light
   page; on the navy site they read as plain brown, and brown is not in this
   brand — the client asked for blue, white, gray, silver, metallic silver.
   They cannot be re-pinned from :root here: that page's own <style> block
   loads AFTER this file, so its :root wins on source order. `body` wins
   instead, and not by specificity — it is a NEARER ANCESTOR of every .pg-*
   element, so its value is the one they inherit whatever :root says.
   The split is re-cut rather than re-coloured, because on a dark ground the
   token's two jobs pull apart. Computed against the grounds in this file
   (L(bg)=0.00427, L(surface)=0.00831, L(elevated)=0.01549):
     --pg-warm      FILL role — #1C5E96, L=0.10454; white on it 6.79:1
                    (.pg-btn-warm, the open chevron, the FAQ sign, .pg-seal)
     --pg-warm-ink  INK role  — #4A9BD4, L=0.29648; 5.29:1 on elevated,
                    5.94:1 on surface, 6.38:1 on bg
   #1C5E96 as ink would have measured 2.85:1 on the page ground and #4A9BD4 as
   a fill only 3.03:1 under white, so neither value can do both jobs. Every INK
   use is re-pointed below; the four fills keep --pg-warm. */
body {
  --pg-warm: var(--cc-blue-600);
  --pg-warm-ink: var(--cc-blue-400);
}
body .pg-accent-word,
body .pg-eyebrow-warm,
body .pg-chip-badge,
body .std-cat .std-num,
body .std-link { color: var(--pg-warm-ink); }
body .pg-icon-warm {
  color: var(--pg-warm-ink);
  background: color-mix(in srgb, var(--pg-warm-ink) 14%, transparent);
}
/* the 6px dot on every eyebrow. The closing CTA band overrides this with its
   own (0,2,1) rule and keeps its on-brand dot. */
body .pg-eyebrow::before { background: var(--pg-warm-ink); }
body .pg-mission-quote { border-left-color: var(--pg-warm-ink); }
/* fill="var(--pg-warm)" is a presentation attribute on the mission ring's dot
   group; CSS outranks it. The sibling <g> carries stroke, not fill. */
body .pg-mission-ring svg g[fill] { fill: var(--pg-warm-ink); }

/* ── C. THE THREE WHITE BOXES ───────────────────────────────────────────────
   .pg-coa, .pg-coa-frame img and .pg-panel-figure img each carried
   `background:#FFFFFF` so a transparent product cut-out would sit on paper.
   On a near-black page they are three white rectangles, and two of them were
   holding a blank mock certificate. The artwork that replaces them (step 18)
   draws its own panel, so the paper comes out. */
body .pg-coa,
body .pg-coa-frame img,
body .pg-panel-figure img { background: transparent; }
/* the readout is a 2:3 plate, not a photo — it needs the frame's full height */
body .pg-coa-frame img { height: 460px; }
body .pg-panel-figure img { height: 190px; object-fit: cover; }
/* The two readout slots are `object-fit: contain` on a box wider than the 2:3
   plate, so a BOX shadow would draw a rectangle wider than the artwork. A
   drop-shadow filter follows the alpha instead. */
body .pg-coa,
body .pg-coa-frame img {
  box-shadow: none;
  filter: drop-shadow(0 16px 30px rgb(0 0 0 / 0.45));
}

/* ======================================================================
   OWNER CHANGE, 2026-08-20 — THE PEPTIDES STAND IN THE HERO

   "add peptides in the hero background as statics using the mockups we
   made."

   The mockups are the client's own vial composites (assets/products/,
   drawn by factory/tools/vial.mjs from this client's real labels) — so
   this costs 0 credits and no provider, and the vial in the hero is the
   same object the PDP sells. Four of them are cropped to their own alpha
   bounding box once (assets/hero-vials/*.webp, 560x878) and copied into
   the built site by apply-refresh step 22; cropping is what makes the
   positions below exact, because the 1200px composites carry ~30% empty
   frame on the right and the vial does not sit in their centre.

   STATIC, and that word is load-bearing: no animation, no transition, no
   transform beyond the fixed rotation. The one moving thing in this hero
   is the mesh, and a second moving layer would compete with the headline.

   WHERE THEY SIT AND WHY. The hero is a full-viewport CENTRED stage, so
   the copy owns the middle and the corners are empty. Two vials stand in
   the bottom corners, cropped by the frame edge — a vial half out of
   frame reads as depth, a whole one floating reads as a sticker. Two more
   sit high and small at ~0.42 opacity with a hair of blur: the same
   near-far pair the codex uses for a product field. Nothing crosses the
   inner 64rem the headline is measured to.

   LAYERS: the network SVG is z-index 0, the scrim 1, the column rules 2,
   #hero-content 10. The vials take 3 — in FRONT of the scrim, so they
   keep their own contrast, and behind every word on the page.

   ── OWNER CHANGE #5, 2026-08-20 ────────────────────────────────────────
   "make the vials in the backround and somewhat shaded similar to this
   reference."

   THE SHADING IS DONE BY THE VIALS, NOT BY MOVING THEM UNDER THE SCRIM,
   and that is measured rather than preferred: --cc-hero-scrim is 0.12 and
   its stops fall to 0.052 across the middle band, so a plate demoted
   behind it would come back roughly 6% darker — invisible. Dropping them
   to z-index 0 would also put them in a DOM-order fight with the network
   SVG, which cc-network.part.html injects at runtime. So the layer order
   is left exactly where it was and the recession is a filter: darker,
   slightly desaturated, softly out of focus, and larger. Large + dim +
   unfocused is what reads as "behind"; small + sharp + bright is what
   read as ornament, which is the note the owner is giving.

   THIS MOVES CONTRAST IN THE SAFE DIRECTION. The hero ground is dark and
   the type on it is light, so the binding pixel is the BRIGHTEST thing
   the decoration paints. brightness(0.58) on the near pair and 0.46 on
   the far pair lowers that peak by 42%/54% while the plates grow, so the
   headline sits on a quieter field than it did at the smaller size.

   SIZE IS BOUNDED BY THE COPY, NOT BY THE REFERENCE. The plate aspect is
   560x878, so height = 1.568 x width: the near pair's 330px ceiling is a
   517px vial, and at bottom:-12% of a 720px hero about 430px of it shows.
   That fills the corner without reaching the centred column — the note
   below about the headline running x 104 -> 1148 at 1280 still binds, and
   nothing here crosses the inner 64rem the copy is measured to.
   ====================================================================== */
body .cc-hero-vials {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  overflow: hidden;
  /* Decorative by definition: nothing in here can affect the page's
     layout or paint. */
  contain: layout paint style;
}
body .cc-hero-vials img {
  position: absolute;
  display: block;
  /* ONE SIZE EXPRESSION FOR EVERY WIDTH. --cc-hv-w is a vw clamp, so the four
     plates keep their proportion to the frame and to each other at 1280 and at
     360 alike; --cc-hv-zoom is the ONLY thing a narrow viewport is allowed to
     change, and it scales all four by the same factor, so a zoom can move the
     layer's SIZE and never its composition. */
  width: calc(var(--cc-hv-w) * var(--cc-hv-zoom, 1));
  height: auto;
  /* OWNER CHANGE, 2026-08-20: "peptides are too opaque". Was 0.72 - a plate
     that solid reads as a cut-out pasted over the mesh rather than as an object
     standing in the same room as it. Down to 0.44, so the honeycomb and its
     nodes read THROUGH the glass. The filter chain below is untouched: opacity
     is the one dial that lets the figure behind show, and it moves the
     headline binding pixel DOWN, never up. */
  /* REVERSED 2026-08-21 (owner: "i want the vials on the home page to be solid
     - not opaque"). 0.44 did not make the vials recede, it made them
     TRANSPARENT: the honeycomb ran straight through the glass, the label and
     the cap alike, so they read as ghosts printed on the mesh rather than as
     objects standing in front of it. Solid at 1.

     THE RECESSION IS UNCHANGED AND IT WAS NEVER THE OPACITY DOING IT. Depth
     here is carried by the filter chain below - brightness 0.58 near / 0.46
     far, a touch of desaturation, a blur that grows with distance - and by
     size and crop. Those are what "behind" is made of; see-through is not one
     of them, because a real object in a dark room is DARK, not translucent.

     WHERE THIS MOVES CONTRAST. It raises the brightest pixel this layer paints
     by 1/0.44, back to what it was before 2026-08-20 and still under
     brightness(0.58) - the vial label's white lands near #8C8C8C, and no plate
     crosses the inner 64rem the headline is measured to (the placement block
     below), so no glyph on this page sits on one. The mesh behind them loses
     nothing it was contributing: it was contributing a honeycomb drawn across
     a bottle, which is the defect. */
  opacity: var(--cc-hv-o, 1);
  /* THE CROP IS A PERCENTAGE OF THE VIAL, NOT OF THE HERO — this is the fix
     itself; the block comment above .cc-hv-a has the arithmetic. translateY(%)
     resolves against the element's OWN height, so "about three quarters of it
     shows" stays true at every width with one set of numbers. */
  transform: translateY(var(--cc-hv-ty, 0%)) rotate(var(--cc-hv-r, 0deg));
  /* One chain, in this order: shade, desaturate a touch toward the ground,
     defocus, then cast. drop-shadow LAST so the shadow is thrown by the
     already-blurred silhouette rather than being blurred itself. */
  filter:
    brightness(var(--cc-hv-b, 0.58))
    saturate(var(--cc-hv-s, 0.84))
    blur(var(--cc-hv-blur, 1.3px))
    drop-shadow(0 26px 40px rgb(2 7 21 / 0.62));
}
/* ── OWNER CHANGE, 2026-08-20 — ONE COMPOSITION, EVERY WIDTH ────────────
   "for core chains lab why does this happen fix it. first picture isnt mobile
   as evident by top nav. second picture is mobile - vials move to different
   positions and arent consistent with the web view."

   HE IS RIGHT, AND THE TWO SHOTS DIFFER BY MORE THAN A BREAKPOINT. Measured on
   the built page before this change:
     desktop 1280x833   near pair 19/21vw at the frame edges, far pair 10.5/11vw
                        standing INBOARD at 14%/15% — four vials on one floor;
     phone   375x752    near pair 53/55vw — 2.8x their share of the frame — and
                        the far pair TELEPORTED to top:30.6%, up at the sides
                        beside the CTAs, at a nineteenth of the near pair's area.
   Two different pictures, not one picture at two sizes. That is the defect.

   WHY IT DRIFTED, AND IT IS THE SAME ROOT BOTH TIMES: the composition was
   written in units that do not travel. A bottom of -12% is a percentage of the
   HERO, and the hero barely shrinks from desktop to phone (833 -> 752) while a
   vw-sized vial shrinks 3.4x — so the identical percentage that crops a quarter
   off a desktop vial swallows a phone one whole. Every previous fix answered
   that by writing a SECOND composition inside a media query, which is how the
   phone ended up with its own arrangement, its own sizes and its own author.

   THE FIX IS A UNIT, NOT A MEDIA QUERY. A bottom of 0 stands every vial on the
   hero floor and translateY(n%) pushes it back down by a percentage of ITS OWN
   height — so "26% of this vial is below the fold" is one number that is true
   at 1280 and at 360. The four numbers below are solved from the desktop
   picture the owner is holding up as correct, and reproduce it to the pixel:
     a  -12% of 833 = 100.0px below the floor / 381.3px tall = 26.2%  -> 26%
     b  -14% of 833 = 116.6px                 / 421.5px tall = 27.7%  -> 28%
     c   -5% of 833 =  41.7px                 / 210.7px tall = 19.8%  -> 20%
     d   -4% of 833 =  33.3px                 / 220.8px tall = 15.1%  -> 15%
   The px floors in the clamps come down for the same reason: clamp(150px, 19vw,
   330px) stops being a 19vw plate below 789px wide, so at 375 it was the FLOOR,
   not the vw, that made the near pair enormous.

   The media query that follows now sets ONE property, and the rule for the next
   session is in it: a placement only a phone can see is how this drifted twice.
   ────────────────────────────────────────────────────────────────────── */
/* the near pair — bottom corners, cropped by the frame */
body .cc-hero-vials .cc-hv-a {
  left: -3%; bottom: 0;
  --cc-hv-ty: 26%;
  --cc-hv-w: clamp(64px, 19vw, 330px);
  --cc-hv-r: -7deg;
}
body .cc-hero-vials .cc-hv-b {
  right: -4%; bottom: 0;
  --cc-hv-ty: 28%;
  --cc-hv-w: clamp(68px, 21vw, 360px);
  --cc-hv-r: 8deg;
}
/* the far pair — smaller, dimmer, softened; depth, not a second row */
/* The far pair recedes further on all three dials at once — dimmer, darker
   and more defocused — because depth on a flat field is a gradient of those
   three together, not one of them turned up. It overrides the whole filter
   chain rather than a variable so the shadow can be the smaller near one. */
body .cc-hero-vials .cc-hv-c,
body .cc-hero-vials .cc-hv-d {
  /* SOLID TOO, 2026-08-21 - same owner note, same reasoning. The far pair keeps
     its distance on the three dials that actually read as distance and are all
     still here: it is smaller, darker (0.46 vs 0.58), less saturated and more
     defocused than the near pair. It was 0.20, i.e. barely present at all. */
  --cc-hv-o: 1;      /* was 0.20, and 0.34 before that */
  filter:
    brightness(0.46)
    saturate(0.72)
    blur(2.4px)
    drop-shadow(0 14px 22px rgb(2 7 21 / 0.5));
}
/* ALL FOUR STAND ON THE BOTTOM EDGE, and that is a measured decision, not a
   composition preference. The far pair was first placed high in the top
   corners; measured on the real page at 1280x720 the headline's second line
   runs x 104 -> 1148, so both of them sat UNDER the type. A full-bleed centred
   headline leaves no room at the top corners on any viewport, so the depth
   comes from size and haze instead of from height: the far pair is inboard of
   the near pair, smaller, dimmer, and standing on the same floor. */
body .cc-hero-vials .cc-hv-c {
  left: 14%; bottom: 0;
  --cc-hv-ty: 20%;
  --cc-hv-w: clamp(38px, 10.5vw, 184px);
  --cc-hv-r: 13deg;
}
body .cc-hero-vials .cc-hv-d {
  right: 15%; bottom: 0;
  --cc-hv-ty: 15%;
  --cc-hv-w: clamp(40px, 11vw, 192px);
  --cc-hv-r: -12deg;
}
/* ── THE PHONE, AND THERE IS NOTHING HERE ────────────────────────────────
   No media query. That is the point of the change above and it is the line to
   keep: below 860px this layer has no rules of its own, so the phone cannot
   have a composition of its own to drift away from the desktop one. The four
   plates are vw-sized and floor-anchored, so a phone gets the same picture at
   the same share of the frame — near pair cropped by the frame edges, far pair
   inboard of it, all four standing on the hero floor.

   WHAT THE PHONE COSTS, MEASURED, and why it is not bought back with a media
   query. At 375x812 the near-right plate is 78.8px wide and shows 93.8px of
   itself; the clear band under the last line of hero copy is 102px, so it
   stands in it with 8px to spare (bands: 62px at 360x740, 102 at 375x812, 116
   at 390x844, 156 at 414x896 — hero height minus the bottom glyph). Scaling
   the layer up on a phone eats that margin: the binding arithmetic is
   0.25 x vw x zoom <= band, which caps the zoom at 1.09 at 375 and at 0.69 on
   a 360x740 short phone. A 9% zoom is not worth a second author for this
   layer, so --cc-hv-zoom stays unset and stays the ONLY knob a narrow viewport
   may ever touch: it scales all four plates together, so it can move the
   layer's size and never its composition. Re-measure the bands before using
   it — hero copy is what fixes them, so a copy edit is what invalidates them.
   ────────────────────────────────────────────────────────────────────── */
/* Same three arms the hero motif declares: a reader asking for contrast is
   asking for the headline to win, forced-colors would repaint decoration as
   system ink, and nobody wants this on paper. */
@media (prefers-contrast: more) {
  body .cc-hero-vials img { opacity: 0.22; --cc-hv-b: 0.4; }
}
@media (forced-colors: active) { body .cc-hero-vials { display: none; } }
@media print { body .cc-hero-vials { display: none; } }

/* == OWNER CHANGE #5, 2026-08-20 — TYPEFACE + NAV SCALE ============= */
/* ══════════════════════════════════════════════════════════════════════════
   Two asks, both sitewide:
     1. the type pair becomes the one catharos ships — Familjen Grotesk
        (display) over Inter (body), replacing Sora / Manrope;
     2. the nav bar and the brand lockup inside it get bigger.

   NOTHING HERE TOUCHES A COLOUR, AN OPACITY OR A CONTRAST RATIO. The site is
   dark-ground and every ink on the bar is a solved chrome token; this block
   moves sizes and families only, so no measured pair moves with it.

   BOTH FACES ARE ALREADY SELF-HOSTED. brand_assets/fonts/fonts.css — linked
   first on all 25 storefront pages — already declares 'Familjen Grotesk',
   'Familjen Grotesk Fallback', 'Inter' and 'Inter Fallback' from local woff2
   at weight 400..700 (they were vendored for catharos, which uses the same
   pair). There is no network font here and no @import: a remote font
   reference is stripped and then FAILED by the build, so this file must
   never grow one.

   SPECIFICITY, and it is the whole reason the selectors below look odd.
   sf-refresh.css is linked at position 4 in <head>, but SIX sheets load after
   it — sf-final-imagery / -legal / -typography / -admin / -app-surfaces /
   -mobile — and TWO of them redeclare the font pair on a bare `:root`:
       sf-final-typography.css:51   :root { --sf-font-display: 'Sora' ... }
       sf-final-admin.css:63        :root { --sf-font-display: 'Sora' ... }
   A plain `:root` here would lose to both on source order. `:root:root` is
   (0,2,0) and beats a bare `:root` (0,1,0) no matter who loads last. The same
   arithmetic governs the nav rules: the shared nav CSS lives in a page-local
   <style id="sitewide-nav-css"> that loads AFTER every stylesheet, so each
   override below is written one step above the rule it replaces —
   `#site-nav .snav-logo img` (1,0,1) is beaten by `body #site-nav ...`
   (1,0,2).
   ══════════════════════════════════════════════════════════════════════════ */

/* -- 1. THE PAIR ---------------------------------------------------------
   Declared on :root for inheritance and again on body, because a page-local
   <style> that scopes something to `body` starts from the body's own value.
   --sf-font-mono is deliberately NOT touched: JetBrains Mono on code/pre/kbd/
   .filename-chip/.studio-mono and the ui-monospace spec-table column are a
   chosen third register, not drift. */
:root,
:root:root,
html:root {
  --sf-font-display: 'Familjen Grotesk', 'Familjen Grotesk Fallback', 'Segoe UI', system-ui, sans-serif;
  --sf-font-body: 'Inter', 'Inter Fallback', 'Segoe UI', system-ui, sans-serif;
}
body {
  --sf-font-display: 'Familjen Grotesk', 'Familjen Grotesk Fallback', 'Segoe UI', system-ui, sans-serif;
  --sf-font-body: 'Inter', 'Inter Fallback', 'Segoe UI', system-ui, sans-serif;
}

/* -- 2. THE PAIR REACHES EVERY ELEMENT -----------------------------------
   Audited first: `grep -oE "font-family: *[^;]+"` over the 26 built pages
   returns 178 hits and every one of them is `inherit` or `var(--sf-font-*)`.
   Nothing in the served storefront hard-codes a family, so moving the two
   tokens above IS the sitewide change and these rules are the belt to its
   braces — they re-assert the inheritance root and the display/body split at
   a specificity no later sheet can undercut. */
html, body { font-family: var(--sf-font-body); }
h1, h2, h3, h4, h5, h6 { font-family: var(--sf-font-display); }

/* Form controls are the one family that does NOT inherit by UA default. The
   template relies on tailwind preflight plus `font-family:inherit`, which the
   four pages that link no tailwind never got — pin them. */
body button,
body input,
body select,
body textarea,
body optgroup,
body option {
  font-family: var(--sf-font-body);
}

/* -- 3. THE BAR ----------------------------------------------------------
   --sf-nav-h is the one knob: #site-nav's height, .snav-link's height, and
   EVERY page's top offset all read it (index #hero margin-top, checkout /
   contact / my-account / my-orders / order-success / product / products
   main padding-top). Raising the variable moves the bar and the content that
   clears it together, on all 25 pages, from one declaration.
     sf-base.css:22   --sf-nav-h: calc(38px + 2 * var(--sf-nav-pad-y))
     theme.css:888    --sf-nav-pad-y: 8px          ->  54px
   The pages that clear the bar with a flat pad (login 96px, privacy/track/
   coa-library 100px, standards 152px) still have 30px+ of headroom at 67px. */
:root,
:root:root,
html:root {
  --sf-nav-h: 67px;        /* was 54px  (+24.1%) */
  --sf-logo-nav-h: 45px;   /* was 38px; x --sf-logo-scale 1.15 = 51.75px */
}

/* The template clamps the lockup to `--sf-nav-h - 16px` so a tall logo can
   never overflow a short bar. The bar grew, so the clamp is re-solved with
   the breathing room the bigger lockup wants: 67 - 14 = 53px, which leaves
   51.75px of artwork and 7.6px of air above and below it. The min() stays —
   it is the guard that keeps the logo inside the bar if either value moves
   again. Vertical centring is untouched (#site-nav, .sf-nav-inner and
   .snav-logo are all flex/align-items:center). */
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) - 14px)
  );
}

/* -- 4. THE LINKS --------------------------------------------------------
   0.88rem -> 1.06rem is +20.5%, but the four labels are UPPERCASE with 0.07em
   tracking (--sf-nav-case / --sf-nav-track) and .snav-link has white-space:
   nowrap, so at the narrow end of the desktop range the row would run under
   .snav-right instead of wrapping — and body{overflow-x:clip} would hide it
   rather than show it. Hence clamp(): full size from ~1180px up, easing back
   to 0.95rem at the 861px edge where the bridge rule (@media 769-860px) hands
   over to the drawer. Measured worst case at 861px: logo 183 + margin 32 +
   links ~408 + two icon buttons 98 + bar padding 64 = 785px in 861px.
   The links' HEIGHT needs no rule — .snav-link already reads --sf-nav-h. */
#site-nav .snav-link {
  font-size: clamp(0.95rem, 0.72rem + 0.45vw, 1.06rem);
  padding: 0 clamp(0.72rem, 1.05vw, 1.1rem);
}
/* The current-page underline is inset to the link's own padding; it was a
   flat 1rem, which no longer matches. Colour is the untouched --sf-nav-ink. */
body #site-nav .snav-link[aria-current="page"]::after {
  left: clamp(0.72rem, 1.05vw, 1.1rem);
  right: clamp(0.72rem, 1.05vw, 1.1rem);
}

/* -- 5. THE ACTIONS ------------------------------------------------------
   Cart and account, the only two buttons in .snav-right. 38 -> 46px (+21%)
   keeps them proportional to the taller bar. Below 860px sf-final-mobile.css
   pins them to 44px !important for the touch floor and that still wins, so
   this is a desktop-only growth by construction. The cart badge is left
   alone: it is positioned off the button's own corner, so it travels. */
#site-nav .snav-right .snav-icon-btn { width: 46px; height: 46px; }
#site-nav .snav-right .snav-icon-btn svg { width: 25px; height: 25px; }

/* -- 6. THE PHONE BAR STAYS A PHONE BAR ----------------------------------
   At 768px and under the four links are gone, the logo is absolutely centred
   and the drawer button takes over; a 67px bar would eat the top of an 812px
   screen for no gain. The bar and the lockup still grow — just gently, and
   the min() above re-solves against the smaller pair automatically
   (min(40 x 1.15, 60 - 14) = 46px). The drawer's own lockup follows too:
   .snav-mob-logo img is calc(--sf-logo-nav-h * 0.82), so 31.2px -> 32.8px. */
@media (max-width: 768px) {
  :root,
  :root:root,
  html:root {
    --sf-nav-h: 60px;        /* was 54px  (+11.1%) */
    --sf-logo-nav-h: 40px;   /* x 1.15, clamped by the bar -> 46px (+21%) */
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   SECTION H — WHITE PLATES, 2026-08-20 (owner: "sitewide on core labs build i
   want more white to help break up the blue. disperse sitewide accordingly to
   help improve flow and looks.")

   CLIENT-ONLY by owner instruction — nothing here is a scaffolder default and
   no BUILD-DEFAULTS row is opened. Appended at the END of the file, which is
   the protocol on this file while three sessions are live: everyone appends,
   nobody rewrites.

   THE RULE THE WHOLE BLOCK FOLLOWS, and it is worth stating once because it is
   what makes a light plate safe on a site solved for a dark ground:
   A PLATE OVERRIDES TOKENS, NEVER COLOURS. Every rule that paints inside these
   sections — the master's, theme.css's, this file's, and each page's own
   <style> block — reads var(--sf-text) / var(--sf-muted) / var(--sf-accent) and
   friends. Re-pinning those custom properties ON THE SECTION means every one of
   those rules keeps working and simply resolves to the plate's polarity. Same
   technique the --sf-photo-bg block above uses on .sf-card-media, and it has
   three properties nothing else here gives:
     · no !important anywhere, including on the four sections whose background
       is an INLINE style attribute — those inline rules are written
       `background: var(--sf-band)` / `var(--sf-surface)`, and an inline
       declaration only outranks a rule for the SAME property, not for the
       custom property it happens to read. Re-pin the token on the element and
       the inline background paints what we say.
     · a rule written later by another session inside a plate keeps working,
       provided it reads a token rather than a hard-coded light hex.
     · nothing outside a plate moves. The nav, the footer, the hero, the
       assurance band, both closing CTA bands and #coa-proof are untouched, so
       every ratio measured elsewhere in this file still stands.

   THE RHYTHM. Navy page header → white content, on every page; the home page
   alternates N·W·N·W·W'·N·W·N. #coa-proof deliberately STAYS navy: it hosts
   .std-rail, whose ground (#101E2E) and disc halo are hard-coded darks, so a
   plate there would put navy ink on a navy panel.

   THE INK IS #04101F, NOT --cc-navy-900, and that is arithmetic rather than
   taste. The pages fade their secondary copy with
   `color-mix(in srgb, var(--sf-text) 60%, transparent)` — #071A33 at 60% over
   white composites to #6A7685 (4.62:1) and over the ice plate to #647283
   (4.47:1, FAILING). #04101F holds both: 5.02:1 on white, 4.86:1 on ice, and
   19.0:1 at full strength. The whole plate palette, measured (WCAG, computed
   not eyeballed):
     ink     #04101F  on #FFFFFF 19.03:1 · on #F2F7FB 17.75:1
     muted   #4A6076  on #FFFFFF  6.51:1 · on #F2F7FB  6.06:1
     accent  #0F7387  on #FFFFFF  5.49:1 — and white ON it 5.49:1, so the one
             value serves both the ink role and the fill role, which the dark
             ground's #3FB6C9 cannot: it measures 2.24:1 on white.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  /* Named once, so a plate tone is changed in one place. Not used outside
     section H — the dark grounds above own the rest of the site. */
  --cc-plate:        #FFFFFF;
  --cc-plate-soft:   #F2F7FB;
  --cc-plate-ink:    #04101F;
  --cc-plate-muted:  #4A6076;
  /* DEEPER THAN THE 5.49:1 THE HEADER COMMENT SOLVES FOR, and the reason is a
     ground the arithmetic did not predict: the chips, badges and pills on these
     pages build their own ground out of the accent itself
     (`color-mix(in srgb, var(--sf-accent) N%, var(--sf-bg))`), then print the
     accent ON it. #0F7387 measured 3.39:1 that way on .cat-chip-count and
     4.24:1 on .coa-pending-badge. Deepening fixes it twice over, because the
     tint is white-dominated and moves a third as far as the ink does:
       #0A5464 on #FFFFFF 8.52:1 · on the chip tint 5.26:1 · white ON it 8.52:1,
     so the one value still serves the ink role and the fill role both. */
  --cc-plate-accent: #0A5464;
  --cc-plate-line:   rgb(4 16 31 / 0.14);
}

/* ── THE TOKEN RE-PIN ───────────────────────────────────────────────────────
   One declaration block, every plate on the site. Order in the list is
   home → catalogue → research → contact → COA library → legal. */
body #categories,
body #research-intro,
body #featured-strip,
body #home-faq,
body #products-grid,
body .pg-band.pg-wash-soft,
body section[style*="var(--sf-band"]:not(.page-header-bg),
body main[style*="var(--sf-band"],
body main.cc-legal {
  --sf-bg:        var(--cc-plate);
  --sf-surface:   var(--cc-plate-soft);
  --sf-elevated:  var(--cc-plate);
  --sf-band:      var(--cc-plate);
  --sf-soft-bg:   var(--cc-plate-soft);
  --sf-text:      var(--cc-plate-ink);
  --sf-muted:     var(--cc-plate-muted);
  --sf-border-color: var(--cc-plate-line);
  --sf-divider-bg:   var(--cc-plate-line);
  --sf-accent:      var(--cc-plate-accent);
  --sf-accent-text: #FFFFFF;
  --sf-accent-4:    var(--cc-plate-accent);
  --sf-accent-5:    var(--cc-plate-muted);
  --sf-accent-hover: #0B5C6C;
  --sf-hero-wash:   rgb(15 115 135 / 0.10);

  /* THE DESIGN-OPTION GROUNDS (theme.css's second :root, the one themes/
     options.mjs appends). These are SOLVED LITERALS for a dark page, not
     derivatives, so nothing above reaches them — and the one that mattered was
     found by measurement, not by reading: --sf-tint-bg #192227 is what
     `body.opt-input-style-filled .sf-input` fills every form field with, so the
     contact form shipped near-black boxes carrying the plate's navy ink at
     1.1:1. The axes this client wears are `input-style-filled`, `card-style-
     glass` and `texture-grain`; each one's ground is re-cut for the plate here.
     --sf-contrast-* is deliberately left alone: it is already a LIGHT ground
     with dark ink, so it reads correctly on a plate. */
  --sf-tint-bg:  var(--cc-plate-soft);
  --sf-soft-ink: var(--cc-plate-accent);
  --sf-glass-fill:        rgb(255 255 255 / 0.72);
  --sf-glass-fill-strong: rgb(255 255 255 / 0.92);
  --sf-grad-from: var(--cc-plate-accent);
  --sf-grad-to:   #17557A;

  /* The cc-* ramp carries INK roles on the dark ground — the page ink, the
     de-emphasised ink, links, kickers, chips, the rail's fine print. Every one
     of them is a light value and every one is invisible on a plate, so the ink
     end of the ramp is re-pinned too. The FILL end (--cc-navy-*, --cc-blue-600,
     --cc-teal-700) is deliberately NOT touched: those are button and badge
     grounds that carry their own light ink and read correctly on white. */
  --cc-metal-100:  var(--cc-plate-ink);
  --cc-metal-300:  var(--cc-plate-muted);
  --cc-metal-line: var(--cc-plate-line);
  --cc-cyan-100:   #0C5F70;
  --cc-cyan-300:   var(--cc-plate-accent);
  --cc-cyan-400:   #0E6C7E;
  --cc-silver-200: var(--cc-plate-muted);
  --cc-silver-400: var(--cc-plate-muted);
  --cc-silver-500: #55697E;
  --cc-metal-sheen: linear-gradient(90deg,
    transparent 0%, rgb(4 16 31 / 0.06) 22%,
    rgb(4 16 31 / 0.22) 50%,
    rgb(4 16 31 / 0.06) 78%, transparent 100%);
  /* standards.html's page-local tokens, re-cut for the plate the same way the
     dark ground re-cut them above: --pg-warm stays a FILL (white ink on it
     6.79:1), --pg-warm-ink becomes an INK that survives white.

     THE FOUR BELOW ARE THE ONE TRAP IN THIS WHOLE TECHNIQUE, and it cost a
     measured failure before it was found: standards.html declares them ON
     :root AS DERIVATIVES —

       :root { --pg-card: var(--sf-elevated);
               --pg-line:    color-mix(in srgb, var(--sf-text) 12%, transparent);
               --pg-ink-60:  color-mix(in srgb, var(--sf-text) 80%, transparent);
               --pg-ink-45:  color-mix(in srgb, var(--sf-text) 66%, transparent); }

     A custom property is substituted at COMPUTED-VALUE TIME ON THE ELEMENT IT
     IS DECLARED ON. --pg-card computes on :root, against :root's --sf-elevated,
     and what descendants inherit is the already-resolved literal #142235 — so
     re-pinning --sf-elevated on the section moves nothing, and the plate shipped
     five dark cards carrying navy ink: .pg-check-row, .pg-q, .pg-card,
     .std-row and .pg-practice article, measured at 1.19:1 by the audit.
     A DERIVED TOKEN MUST BE RE-DECLARED WHEREVER ITS SOURCE IS RE-PINNED.
     Checked against every other :root derivative in the built site — theme.css's
     --sf-border-color (re-pinned above), --sf-nav-ink and --sfhm-ink-2 (chrome
     and hero motif, both outside every plate), and index.html's --navy/--teal/
     --bg, which are declared and never used. This is the complete list. */
  --pg-warm-ink: var(--cc-plate-accent);
  --pg-card:   var(--cc-plate);
  --pg-line:   color-mix(in srgb, var(--cc-plate-ink) 12%, transparent);
  --pg-ink-60: color-mix(in srgb, var(--cc-plate-ink) 80%, transparent);
  --pg-ink-45: color-mix(in srgb, var(--cc-plate-ink) 66%, transparent);
  /* the legal callout box: a navy card on a white sheet would take the plate's
     navy ink and go dark-on-dark. It becomes a tinted callout instead. */
  --cc-legal-note-bg:   #EDF4F9;
  --cc-legal-note-edge: var(--cc-plate-accent);
  --cc-legal-rule:      var(--cc-plate-line);

  color: var(--sf-text);
}

/* ── THE HOME PAGE ──────────────────────────────────────────────────────────
   These four each had an explicit background in the SECTION RHYTHM block near
   the top of this file. Same specificity, later in the same file, so these win
   on source order — the earlier rules are left in place on purpose, because
   they are the record of what the dark rhythm was. */
body #categories {
  background: linear-gradient(180deg, var(--cc-plate) 0%, var(--cc-plate-soft) 100%);
  border-bottom: 1px solid var(--cc-plate-line);
}
/* The research band keeps its two washes so the plate is not flat paper — the
   same two radial stops as the dark version, re-aimed at the light end. */
body #research-intro {
  background:
    radial-gradient(90% 120% at 8% 0%, rgb(15 115 135 / 0.07) 0%, transparent 58%),
    radial-gradient(80% 110% at 100% 100%, rgb(28 94 150 / 0.08) 0%, transparent 60%),
    linear-gradient(180deg, var(--cc-plate) 0%, var(--cc-plate-soft) 100%);
}
/* The featured strip is the SECOND white in a row, so it takes the soft tone —
   a 1.06:1 step off the research band, which is the same visible distance the
   dark rhythm used between #060E1A and #0C1726. Two plates, not one long one. */
body #featured-strip {
  background:
    radial-gradient(70% 90% at 92% 6%, rgb(28 94 150 / 0.07) 0%, transparent 62%),
    var(--cc-plate-soft);
  border-top: 1px solid var(--cc-plate-line);
}
body #home-faq {
  background: linear-gradient(180deg, var(--cc-plate-soft) 0%, var(--cc-plate) 100%);
}

/* ── THE CATALOGUE ─────────────────────────────────────────────────────────
   products.html paints the grid with an INLINE `background:var(--sf-surface)`.
   No background override is written here at all: the token re-pin above is on
   this very element, so the inline declaration resolves to the plate. */
body #products-grid { border-top: 1px solid var(--cc-plate-line); }

/* ── RESEARCH (standards.html) ─────────────────────────────────────────────
   That page's <style> block loads AFTER this file, so source order cannot win
   and specificity has to: `.pg-wash-soft` is (0,1,0), this is (0,2,1). The
   background itself needs no rule — the page writes it as
   `var(--sf-band, var(--sf-surface))`, both re-pinned above. */
body .pg-band.pg-wash-soft {
  /* The band takes the SOFT tone rather than the plate's white, and that is
     what gives this page its depth: --pg-card is white, so every card, row and
     accordion on the band stands one step above it. Same specificity as the
     group rule above, later in the file, so this wins on source order. */
  --sf-band: var(--cc-plate-soft);
  border-top: 1px solid var(--cc-plate-line);
  border-bottom: 1px solid var(--cc-plate-line);
}
/* The panels that had `background:#FFFFFF` stripped for the dark ground (owner
   change #4C) are on a white plate now, so their drop shadow drops to a
   light-ground value — a 0.45 black shadow under artwork on white reads as
   dirt rather than as lift. */
body .pg-wash-soft .pg-coa,
body .pg-wash-soft .pg-coa-frame img,
body .pg-wash-soft .pg-panel-figure img {
  filter: drop-shadow(0 14px 26px rgb(4 16 31 / 0.18));
}

/* ── CONTACT · COA LIBRARY ─────────────────────────────────────────────────
   Both write their ground as an inline `background: var(--sf-band, ...)`, so
   again the token re-pin is the whole change and no rule is needed here. Each
   also carries an inline
   `border-top: 1px solid color-mix(in srgb, var(--sf-text) 7%, transparent)`,
   which re-solves to a navy hairline by itself, and floats a decorative radial
   built from --sf-accent, which follows the plate down.

   THEIR TWO SELECTORS KEY ON THE TOKEN, NOT ON THE PADDING, and that is a
   deliberate second pass. The first version matched
   `[style*="padding:3rem 1.5rem 5rem"]` — three numbers in an inline attribute
   written by the engine, which nobody owns and nothing tells us about. Any
   spacing edit upstream would have silently un-plated a whole page, and a
   silent un-plate is worse than a loud break: the page just quietly goes back
   to navy and no gate on this client looks at it. `[style*="var(--sf-band"]`
   keys on the thing that actually MAKES it a band, and the `:not(.page-header-bg)`
   is what keeps the page headers navy, since their gradients read the same
   token. Checked against the built site: those two selectors match exactly one
   element each across all 26 pages — contact's form panel and coa-library's
   main — and `main.cc-legal`, `#product-main` and `.cc-app-main` match neither. */

/* ── THE LEGAL SHEET ───────────────────────────────────────────────────────
   privacy · terms · shipping-policy · refund-policy · research-use. The page
   header (.legal-hero) STAYS navy — every page on this site opens navy — and
   the document itself becomes a paper sheet standing on it. main.cc-legal has
   no background of its own (sf-final-legal.css sets width and padding only),
   so this is the one plate that has to paint rather than re-point.
   `body main.cc-legal` is (0,1,2) against that file's (0,1,1), which is what
   lets a rule loaded EARLIER win. */
body main.cc-legal {
  background: var(--cc-plate);
  border: 1px solid var(--cc-plate-line);
  border-radius: var(--sf-radius, 16px);
  box-shadow: 0 30px 70px -40px rgb(0 0 0 / 0.6);
  /* the sheet needs its own margin: sf-final-legal.css gives it padding-inline
     only, so without this the paper touches the nav and the footer. */
  padding-block: 2.75rem;
  padding-inline: calc(var(--cc-legal-pad, 1.5rem) + 1.25rem);
  max-width: calc(var(--cc-legal-w, 36rem) + var(--cc-legal-pad, 1.5rem) * 2 + 2.5rem);
  margin-block: 2.5rem 4rem;
}
@media (max-width: 640px) {
  /* on a phone the sheet loses its inset and runs to the edges — a 16px white
     margin either side of a document is wasted width, not composition. */
  body main.cc-legal {
    border-left: 0; border-right: 0; border-radius: 0;
    margin-block: 1.25rem 2.5rem;
    padding-inline: var(--cc-legal-pad, 1.5rem);
  }
}

/* ── SURFACES THAT NEED A LIGHT-GROUND VALUE ───────────────────────────────
   Two rules in the CARDS block above are written as hard-coded alphas rather
   than tokens, so the re-pin cannot reach them. Restated for the plates only.
   The inset white bevel is left alone deliberately — on white it is a no-op,
   and deleting it would cost a rule for nothing. */
body #categories .sf-card, body #research-intro .sf-card,
body #featured-strip .sf-card, body #home-faq .sf-card,
body #products-grid .sf-card, body .pg-wash-soft .sf-card,
body #categories .sf-panel, body #research-intro .sf-panel,
body #featured-strip .sf-panel, body #home-faq .sf-panel,
body #products-grid .sf-panel, body .pg-wash-soft .sf-panel {
  background: var(--cc-plate);
  box-shadow: 0 1px 2px rgb(4 16 31 / 0.05);
}
body #categories .sf-card:hover, body #research-intro .sf-card:hover,
body #featured-strip .sf-card:hover, body #home-faq .sf-card:hover,
body #products-grid .sf-card:hover, body .pg-wash-soft .sf-card:hover {
  border-color: rgb(15 115 135 / 0.55);
  box-shadow: 0 18px 34px -22px rgb(4 16 31 / 0.45),
              0 0 0 1px rgb(15 115 135 / 0.18);
}
/* The product photo plate is white and so is its card now, so the media box
   loses its edge. One hairline puts it back — the vial has to sit ON something,
   which is the whole reason --sf-photo-bg was pinned white in the first place. */
body #featured-strip .sf-card-media,
body #products-grid .sf-card-media,
body #featured-strip .hero-tile-img,
body #products-grid .card-img-wrap {
  border-bottom: 1px solid var(--cc-plate-line);
}

/* ── THE ALPHA FADES THAT NO INK CAN RESCUE ───────────────────────────────
   A LIGHT GROUND HAS LESS RANGE TO SPEND THAN A DARK ONE, and this is the one
   place that costs something. The pages fade quiet copy with
   `color-mix(in srgb, var(--sf-text) N%, transparent)`. On the dark ground a
   50% fade of #F0F6FA still composites to 5.9:1, because it is walking DOWN
   from a near-white ink across a very dark field. On white it is walking down
   from the ground itself, and the arithmetic is brutal: at 50% alpha, PURE
   BLACK over white composites to #808080 — 3.95:1. There is no ink value that
   passes; only the alpha can move. So these are re-stated as SOLVED OPAQUE
   inks, which is the same call the CTA-band block above made ("SOLVED, not
   faded") for the same reason.

   THE LIST IS THE AUDIT'S, NOT A READING OF THE CSS — every rendered text node
   inside every plate was measured in the browser against its real composited
   ground (465 elements across the six plated pages) and exactly these three
   came back under 4.5:1, all of them small fine print at 50–55%. A state that
   does not render on a first load — a modal, an error line, a tab behind a
   click — was not measured and could carry the same idiom; if one turns up, it
   belongs in this list.
   .form-ruo is the one worth naming out loud: it is the RESEARCH-USE-ONLY line
   under the contact form, i.e. the sentence on that page whose legibility is
   not a matter of taste. It measured 4.24:1. */
body main[style*="var(--sf-band"] .coa-empty-sub,
body main[style*="var(--sf-band"] .coa-clear,
body section[style*="var(--sf-band"]:not(.page-header-bg) .form-ruo {
  color: var(--cc-plate-muted);   /* 6.51:1 on the sheet */
}

/* The section separator is the metal sheen, and a silver specular line is
   invisible where two plates meet. Re-pinned as a token above; this restates
   the rule at plate specificity because the separator is drawn on the ::before
   of the FOLLOWING section, which may itself be navy. */
body.opt-section-separator-fade #categories + .sf-section::before,
body.opt-section-separator-fade #research-intro + .sf-section::before,
body.opt-section-separator-fade #featured-strip + .sf-section::before,
body.opt-section-separator-fade #home-faq + .sf-section::before {
  background: linear-gradient(90deg, transparent, rgb(4 16 31 / 0.16), transparent);
}

/* ── THE COA CERTIFICATE CARDS ARE A FIXED-DARK ISLAND ON A PLATE ──────────
   Owner, 2026-08-22: "coas ... have black text and arent visible".

   coa-library.html's <main> carries `background:var(--sf-band…)`, so the plate
   re-pin above catches it and hands the whole page the plate's INK (#04101F)
   — correct for the sheet, and wrong inside `.coa-cert`, which is deliberately
   a FIXED DARK card in both modes (engine/template/coa-library.html: "the
   fixed light inks inside stay correct because the base stays dark"). Its
   values are hard light literals (#eaf1fb / #dce5f2) so they survived; every
   ink that routes through --sf-text — the compound name, the PURITY / NET
   CONTENT labels, the Batch/Date/Lab/Lot keys — went navy-on-navy.

   So the dark-ground ramp is re-pinned ON THE CARD. A custom property set on
   the element itself beats the plate's inherited one regardless of selector
   weight, which is why this needs no !important and no specificity race.
   Measured against the card's own composited ground (#122A39 at the top of its
   gradient, L=0.0213):
     name / values  #F0F6FA  13.5:1
     labels + keys  #A6BACB   7.3:1  (was a 42% alpha fade of the ink → 3.6:1
                                      even on the intended dark ground)
     dose badge     #57D6E4   6.4:1  on its own accent-16% tint
     Download PDF   #04141A on #3FB6C9  7.8:1
   The alpha fades go opaque here for the same reason section H states: a fade
   walks back toward whatever it sits on, and this card sits on two grounds.

   SCOPE: this client only, by the owner's instruction. The same collision is
   waiting for any light-ground client whose COA page renders certificates —
   the engine's fixed-dark card assumes a light --sf-text — but that is an
   engine row, not this file's job. */
.coa-cert {
  /* LITERALS, NOT var(--cc-metal-*): the plate block re-pins the cc-* INK end
     of the ramp too, so pointing at those names here fetches the plate navy
     straight back — measured in the browser, first attempt. These are the dark
     ground's own values, from the :root at the top of this file. */
  --sf-text:         #F0F6FA;
  --sf-muted:        #A6BACB;
  --sf-border-color: rgb(166 186 203 / 0.20);
  --sf-accent:       #3FB6C9;
  --sf-accent-text:  #04141A;
  --sf-accent-4:     #57D6E4;
  --sf-accent-hover: #63CBDA;
}

/* The quiet register inside the card, as SOLVED OPAQUE inks rather than alpha
   fades of the ink above. */
.coa-cert .coa-cert-sub,
.coa-cert .coa-result-label,
.coa-cert .coa-th,
.coa-cert .coa-sn,
.coa-cert .coa-meta-k,
.coa-cert .coa-screen-k,
.coa-cert .coa-screen-m {
  color: #A6BACB;   /* 7.3:1 on the card */
}

/* ══════════════════════════════════════════════════════════════════════════
   OWNER CHANGE #6, 2026-08-26 — THE MOBILE PASS
   Eight fixes in one relay, most of them phone-only. Measured on the LIVE
   store at 375x812 before authoring: the age gate ran 891px tall with
   "Not right now" starting at y=857 (off-screen); the nav lockup (x 106-269)
   and the actions cluster (x 244-343) overlapped by 25px; the hero solved to
   100svh so the white #categories plate peeked in whenever the URL bar
   retracted; the CTA pair (221px + 173px) wrapped inside its 343px row.
   The radius reversal lives in section G (rewritten in place, same day);
   the footer type block near the FOOTER TYPE section was retightened the
   same day; the markup half of the footer simplification is
   final/19-footer.mjs and the age-gate logo swap is final/18-age-gate.mjs.
   ══════════════════════════════════════════════════════════════════════ */

/* ── 6a. THE AGE GATE FITS THE PHONE ─────────────────────────────────────
   The gate's own CSS is a <style id="lag-style"> the runtime appends AFTER
   every linked sheet (age-gate.js GATE_CSS), so nothing here can win on
   order — every selector carries #lag-root (1,1,0+) to beat the gate's own
   class rules (0,2,0 at most) on specificity instead. The attestation
   labels and the fine print are compliance copy: these rules resize, never
   reword, never hide. */

/* The white plate comes off the logo: the gate wears LOGO-ONDARK now (the
   nav's knockout, swapped in by final/18-age-gate.mjs), which is drawn FOR
   dark grounds — a white plate behind it is the defect being fixed.
   All-viewport, not media-scoped: the plate was wrong on desktop too. */
#lag-root .lag-split .lag-logo {
  background: transparent;
  padding: 0;
  border-radius: 0;
  filter: var(--lag-logo-filter, none);
  height: calc(40px * var(--sf-logo-lockup-scale, 1));
}

@media (max-width: 520px) {
  #lag-root { padding: 12px; }
  /* Safety valve only — the metrics below fit 375x812 without engaging it;
     shorter phones scroll INSIDE the modal so both buttons stay reachable.
     Never overflow:hidden here — qa probes the gate's controls on real
     geometry at phone width. */
  #lag-root .lag-modal { max-height: calc(100vh - 24px); max-height: calc(100dvh - 24px); overflow-y: auto; }
  #lag-root .lag-split .lag-logo { height: 32px; }
  #lag-root .lag-aside { padding: 14px 18px; gap: 10px; }
  #lag-root .lag-aside-headline { font-size: 1.02rem; line-height: 1.2; }
  #lag-root .lag-proof { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 12px; padding-top: 10px; }
  #lag-root .lag-proof-sub { font-size: 0.66rem; }
  #lag-root .lag-split .lag-head { padding: 14px 18px 0; }
  #lag-root .lag-split .lag-headline { font-size: 1.12rem; }
  #lag-root .lag-eyebrow { margin-bottom: 0.3rem; }
  #lag-root .lag-split .lag-body { padding: 10px 18px 14px; }
  #lag-root .lag-intro { margin: 0 0 12px; font-size: 0.8rem; line-height: 1.5; }
  #lag-root .lag-check { margin-bottom: 10px; gap: 10px; }
  #lag-root .lag-txt { font-size: 0.78rem; }
  #lag-root .lag-divider { margin: 12px 0; }
  #lag-root .lag-btn-primary { padding: 11px; }
  #lag-root .lag-fine { margin: 10px 0 0; font-size: 0.7rem; }
  #lag-root .lag-btn-secondary { margin-top: 8px; padding: 10px; }
}

/* ── 6b. NOTHING SCROLLS THROUGH THE STRIP ABOVE THE BAR ─────────────────
   #site-nav is fixed at top:0 and OPAQUE, and no offset exists anywhere in
   the CSS — the see-through strip the owner saw is the phone's own viewport
   chrome (URL-bar collapse / overscroll) exposing space ABOVE y=0, where
   nothing painted. The bar now bleeds its own ground 120px upward,
   off-screen in any settled viewport: whatever the browser exposes up there
   reads as bar, not as the page scrolling behind glass. background:inherit
   re-rasterizes the bar's own gradient — do NOT replace it with a literal,
   the chrome ground is gate-measured where it is declared. */
body #site-nav::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: 120px;
  background: inherit;
  pointer-events: none;
}

/* ── 6c. THE MOBILE NAV ROW — NOTHING CROWDS THE LOCKUP ──────────────────
   At ≤768px the page's own inline block (<style id="sitewide-nav-css">, in
   the BODY, wins on order) centred the lockup on the FULL VIEWPORT with
   position:absolute — measured overlap with the cart button at 375px: 25px,
   with the cart glyph sitting on the lockup's last 13px. These rules
   out-SPECIFY it (body #site-nav …, 1,1,1 vs its 1,1,0) instead of trying
   to out-order it — the same trap the cart-badge fix documented earlier in
   this file. Flex centring places the lockup in the space that EXISTS
   (between burger and actions) and lets it shrink, so overlap is
   geometrically impossible down to 320px. The theme's second 16px inset
   collapses so the actions sit one rhythm from the glass like the burger
   does. The !important on gap is required: sf-final-mobile.css pins it
   0.6rem !important at lower specificity. Button SIZES are not touched —
   the 44px floor is lane 02's measured accessibility contract. */
@media (max-width: 768px) {
  body #site-nav .sf-nav-inner { padding-inline: 0; }
  body #site-nav .snav-logo {
    position: static;
    transform: none;
    margin-inline: auto;
    min-width: 0;
    flex-shrink: 1;
    padding-inline: 0.5rem;
  }
  body #site-nav .snav-logo img { max-width: 100%; }
  /* -7px is OPTICAL, and it is measured, not guessed. The burger carries
     margin-left:-10px (lane 02) because a 20px glyph inside a 44px box
     reads as inset by more than the box is; the action glyphs are 25px in
     the same 44px box, so the same box inset leaves them further from the
     glass. MEASURED at 375px: without this the burger glyph sits 19px from
     the left and the account glyph 26px from the right; with it, both read
     19px. The owner asked for "equal and consistent spacing" — that is a
     statement about what the eye sees, not about the boxes. Touch targets
     are untouched (44px, box still 10px clear of the edge). */
  body #site-nav .snav-right { gap: 0.75rem !important; margin-right: -7px; }
}

/* ── 6d. THE HERO CTAs SIT SIDE BY SIDE ON PHONES ────────────────────────
   The row was never a column: flex-wrap:wrap lives in the STYLE ATTRIBUTE
   the scaffold emits on .hero-btns, and 221px + 173px of button overflowed
   the 343px row, so the pair wrapped into a stack. !important is the only
   thing that beats an inline attribute — keep it. Fit at 375px: ~156px +
   ~137px + 8.8px gap ≈ 302px; at the 320px floor the clamp bottoms out and
   the pair still fits. Height stays --cc-cta-h 3rem = 48px, over the 44px
   touch floor. Grounds and inks untouched — final/css/hero.css owns the
   quiet CTA's ground at higher order. */
@media (max-width: 620px) {
  body .sf-hero .sf-hero-cta {
    flex-wrap: nowrap !important;   /* the inline style attribute carries wrap */
    gap: 0.55rem !important;        /* and 0.85rem */
  }
  body .sf-hero .sf-hero-cta .sf-btn-primary,
  body .sf-hero .sf-hero-cta .btn-teal { padding: 0 1.1rem; }
  body .sf-hero .sf-hero-cta .sf-btn-secondary,
  body .sf-hero .sf-hero-cta .btn-ghost-white { padding: 0 1rem; }
  body .sf-hero .sf-hero-cta .sf-btn-primary,
  body .sf-hero .sf-hero-cta .btn-teal,
  body .sf-hero .sf-hero-cta .sf-btn-secondary,
  body .sf-hero .sf-hero-cta .btn-ghost-white {
    font-size: clamp(0.85rem, 3.9vw, 0.95rem);
    white-space: nowrap;
  }
}

/* ── 6e. THE USA BADGE IS BARE — FLAG AND WORDS, NO PILL ─────────────────
   Owner: "remove the tag backround around the shipped from usa and just
   have the text and flag by itself." The plate was never load-bearing:
   hero-background.css's contrast budget pins the worst clip pixel at
   L 0.0867 and the #CBD8E2 span ink at 5.29:1 BARE. The flag svg keeps its
   own 1px ring — that is what stops the white stripes bleeding into the
   navy; it is part of the flag, not the pill. */
body .sf-hero .cc-usa-badge {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

/* ── 6f. THE FOOTER, COMPACTED ───────────────────────────────────────────
   The markup half (the RUO pill and the notice panel come OUT, the terms
   link moves into the bottom legal row) is final/19-footer.mjs. This is
   the layout half: the 2166px mobile footer tightens without losing a word
   of the disclaimers. Same body-prefixed specificity as sf-base's own
   rules; later file wins. Lane 02's !important tap-target rules still beat
   these on phones, which is correct — 44px is an accessibility contract. */
body .sf-footer { padding: 3.25rem 2rem 1.25rem; }
body .sf-footer-inner { gap: 2.5rem; padding-bottom: 2.25rem; }
body .sf-ftr-stack { gap: 0.9rem; }
body .sf-ftr-contact { gap: 1rem; }
body .sf-ftr-title { margin-bottom: 1rem; }
body .sf-ftr-fine { margin-bottom: 1rem; }
@media (max-width: 700px) {
  body .sf-footer-inner { grid-template-columns: 1fr; }
}

/* ── 6g. TWO CORNERS SECTION G CANNOT REACH ──────────────────────────────
   The tile's sale flag ships border-radius:0 from the page's own emitted
   style and sits flush in a corner the tile's overflow:hidden already
   clips — only its INNER corner takes the radius. The CTA band card is a
   hard-coded 20px in sf-base.css, the one box the token sweep missed. */
body .sale-badge { border-radius: 0 0 var(--cc-radius, 8px) 0; }
body .sf-ctaband-card { border-radius: var(--cc-radius, 8px); }
