/* ============================================================
   BIKG SEMANTIC SURFACE MODES (r463)
   ============================================================
   THE PUBLIC API. Light / Dark / Signature.

   data-surface-mode  = the PUBLIC contract (what users pick)
   data-surface       = the IMPLEMENTATION (paper/tone/deep/ink/primary)

   Modes compile DOWN to surfaces. One direction only. The public UI
   never touches a raw surface. Internal surfaces stay dev-only.

   Load order: AFTER tokens-v2.css and components-v2.css. Every rule
   here is an additive override on the canonical surface contract, so
   removing this file restores the prior system with zero residue.

   Governed by: BIKG-Surface-Constitution-v2.md (LOCKED 2026-05-28)

   THE LAW: Brand color is either the BACKGROUND or the BUTTON. Rarely both.
     Light     → neutral bg, BRAND button
     Dark      → dark bg,    BRAND button (contrast-floored)
     Signature → BRAND bg,   INVERTED button
   ============================================================ */

:root {
  --bikg-semantic-version: "r463-semantic-modes-v1-fix1";
}

/* ============================================================
   SURFACE PAINT GUARDRAIL (2026-06-04)
   ============================================================
   THE LAW says a surface mode defines bg + text. Previously the contract
   only SET the tokens (--surface-bg / --surface-text) and left PAINTING to
   the consumer's own CSS. That let a consumer attach data-surface-mode to an
   element (e.g. a <footer>) without a matching background rule, so the element
   kept its inherited (wrong) background while its text flipped to the surface's
   — invisible text on the wrong surface.

   This makes that impossible: ANY element carrying data-surface-mode paints its
   own surface. background-COLOR (not the shorthand) so image/gradient
   backgrounds set by the consumer survive underneath. Consumers may still
   override with their own rules. One line of contract closes a whole category
   of "shouldn't be possible" surface bugs for every downstream consumer.
   ============================================================ */
[data-surface-mode] {
  background-color: var(--surface-bg);
  color: var(--surface-text);
}

/* ============================================================
   MODE 1 — LIGHT
   Clean default. Neutral surface, ink text, brand-color button.
   Brand is NOT the background, so the button carries the brand.
   ============================================================ */
[data-surface-mode="light"] {
  --surface-bg: var(--paper);
  --surface-text: var(--ink);
  --surface-card-bg: var(--tone);
  --surface-border: color-mix(in srgb, var(--surface-text), var(--surface-bg) 78%);

  /* Brand foreground (outline button, links) clamps DOWN to the dark side
     so it reads on a light surface. Mirrors the light-surface group. */
  --color-primary-text: oklch(from var(--brand-primary) clamp(0.35, l, 0.5) calc(c * 1.2) h);

  /* Primary button = brand fill (action contract). */
  --action-primary-bg: var(--color-primary-fill, var(--color-primary));
  --action-primary-text: var(--text-on-primary-auto, var(--text-on-primary, #fff));
  --action-primary-hover-bg: oklch(from var(--action-primary-bg) calc(l - 0.04) c h);
  /* beta-8: solid primaries are BORDERLESS (Andrew). The old text-mix ring
     was invisible on dark primaries but read as a gray outline on the light
     pastel primaries the starter images produce. Outline fill keeps its
     border via the fill-style override below. */
  --action-primary-border: transparent;

  /* Secondary / ghost derives from the LOCAL surface text. */
  --action-secondary-bg: transparent;
  --action-secondary-text: var(--surface-text);
  --action-secondary-border: color-mix(in srgb, var(--surface-text) 28%, transparent);
  --action-secondary-hover-bg: color-mix(in srgb, var(--surface-text) 8%, transparent);
}

/* ============================================================
   MODE 2 — DARK
   Premium contrast. Dark surface, light text, brand-color button
   floored so dark/low-chroma primaries stay visible on ink.
   ============================================================ */
[data-surface-mode="dark"] {
  --surface-bg: var(--ink);
  --surface-text: var(--surface-ink-text);
  /* Card lifts above the ink page with a brand-tinted step. Direct oklch
     formula so it does not depend on the dark-scheme gray scale being active. */
  --surface-card-bg: oklch(from var(--ink) calc(l + 0.05) calc(c * 1.1) h);
  --surface-border: color-mix(in srgb, var(--surface-text) 22%, transparent);

  /* Brand foreground clamps UP to the light side for legibility on ink. */
  --color-primary-text: oklch(from var(--brand-primary) clamp(0.7, l, 0.88) min(c, 0.18) h);

  /* Primary button = brand fill, lightness-floored to 0.5 so near-black /
     low-chroma primaries (Calvary, Minimal Mono) still read against ink. */
  --action-primary-bg: oklch(from var(--color-primary-fill, var(--color-primary)) clamp(0.5, l, 0.85) c h);
  --action-primary-text: var(--text-on-primary-auto, var(--text-on-primary, #fff));
  --action-primary-hover-bg: oklch(from var(--action-primary-bg) calc(l + 0.04) c h);
  --action-primary-border: transparent; /* beta-8: borderless solid primaries */

  --action-secondary-bg: transparent;
  --action-secondary-text: var(--surface-text);
  --action-secondary-border: color-mix(in srgb, var(--surface-text) 32%, transparent);
  --action-secondary-hover-bg: color-mix(in srgb, var(--surface-text) 10%, transparent);
}

/* ============================================================
   MODE 3 — SIGNATURE
   The brand moment. Brand color IS the surface, so the button INVERTS.
   ============================================================ */
[data-surface-mode="signature"] {
  --sig-bg: oklch(from var(--color-primary) clamp(0.42, l, 0.85) c h);
  --surface-bg: var(--sig-bg);
  --surface-text: var(--text-on-primary-auto, oklch(from var(--sig-bg) clamp(0.02, calc((0.58 - l) * 1000), 0.98) 0 0));
  --surface-card-bg: oklch(from var(--sig-bg) calc(l - 0.09) c h);
  --surface-border: color-mix(in srgb, var(--surface-text) 26%, transparent);
  --color-primary-text: var(--surface-text);
  --action-primary-bg: var(--surface-text);
  --action-primary-text: var(--surface-bg);
  --action-primary-hover-bg: color-mix(in srgb, var(--surface-text) 88%, var(--surface-bg));
  --action-primary-border: transparent; /* beta-8: borderless solid primaries */
  --action-secondary-bg: transparent;
  --action-secondary-text: var(--surface-text);
  --action-secondary-border: color-mix(in srgb, var(--surface-text) 40%, transparent);
  --action-secondary-hover-bg: color-mix(in srgb, var(--surface-text) 12%, transparent);
}

/* ============================================================
   PAGE-LEVEL DARK MODE (2026-06-04)
   ============================================================
   When the page is in dark scheme (data-scheme="dark"), the NEUTRAL/light
   sections invert to the dark surface treatment, so a whole page (landing,
   blog, admin, etc.) can flip dark. Signature sections stay the brand color;
   dark sections stay dark. The brand pack pins paper/ink as inline custom
   props, so we can't swap those globally — instead we redefine what a LIGHT
   surface RESOLVES TO under dark scheme (this mirrors the dark-surface block
   above). Specificity (0,2,0) beats the base light block (0,1,0); scoped to
   dark scheme so the light scheme is completely untouched. The editor
   broadcasts data-scheme to linked preview pages, so its dark toggle drives
   this everywhere.
   ============================================================ */
[data-scheme="dark"] [data-surface-mode="light"] {
  --surface-bg: var(--ink);
  --surface-text: var(--surface-ink-text, #ededed);
  --surface-card-bg: oklch(from var(--ink) calc(l + 0.05) calc(c * 1.1) h);
  --surface-border: color-mix(in srgb, var(--surface-text) 22%, transparent);
  --color-primary-text: oklch(from var(--brand-primary) clamp(0.7, l, 0.88) min(c, 0.18) h);
  --action-primary-bg: oklch(from var(--color-primary-fill, var(--color-primary)) clamp(0.5, l, 0.85) c h);
  --action-primary-text: var(--text-on-primary-auto, var(--text-on-primary, #fff));
  --action-primary-hover-bg: oklch(from var(--action-primary-bg) calc(l + 0.04) c h);
  --action-primary-border: transparent; /* beta-8: borderless solid primaries */
  --action-secondary-bg: transparent;
  --action-secondary-text: var(--surface-text);
  --action-secondary-border: color-mix(in srgb, var(--surface-text) 32%, transparent);
  --action-secondary-hover-bg: color-mix(in srgb, var(--surface-text) 10%, transparent);
}

/* ============================================================
   BUTTON FILL BY DIAL — promoted to shared /system/ (2026-06-04)
   ============================================================
   solid / outline / ghost / gradient. outline/ghost route text+border through
   the surface-aware --color-primary-text; gradient scoped to light only.
   Descendant selector (fill on <html>, surface on the section) → 0,2,0 wins.
   ============================================================ */
[data-button-fill-style="outline"] [data-surface-mode] {
  --action-primary-bg: transparent;
  --action-primary-text: var(--color-primary-text);
  --action-primary-border: var(--color-primary-text);
}
[data-button-fill-style="ghost"] [data-surface-mode] {
  --action-primary-bg: transparent;
  --action-primary-text: var(--color-primary-text);
  --action-primary-border: transparent;
}
/* W4c (2026-06-06): gradient fill applies on DARK surfaces too. Under
   page-level dark scheme, declared-light sections resolve dark and were
   already wearing the gradient, while declared-dark bands downgraded to
   solid... same-looking grounds, different buttons (Andrew's catch).
   One behavior now: gradient panel + graded --grad-text on light AND
   dark. SIGNATURE keeps the solid inversion (brand-on-brand ban). */
[data-button-fill-style="gradient"] :is([data-surface-mode="light"], [data-surface-mode="dark"]) {
  /* 2026-06-05: the image is USER-driven. --grad-primary is the pack's
     primary gradient (preset + angle + the SAME stop recipe the editor
     swatches render), written onto <html> by loader.js. Fallback = the
     original fixed gradient, so packs without a gradient are pixel-
     identical to before. */
  --action-primary-bg: var(--grad-primary, linear-gradient(135deg, var(--color-primary), var(--color-deep, var(--color-primary))));
  /* W1: text answers to the ACTUAL gradient (loader-graded), with the
     solid auto-flip chain as fallback for loaderless pages. */
  --action-primary-text: var(--grad-text, var(--text-on-primary-auto, var(--text-on-primary, #fff)));
  /* Gradient fills get NO visible border — the old 1px light border read
     as a hairline halo around the gradient. Color-only (consumers keep
     their border-width), so button geometry never shifts. */
  --action-primary-border: transparent;
}
/* W1 ASSIST VEIL for gradient-fill buttons: same loader-computed veil,
   composited over the button gradient (user preset or bold fallback). */
[data-button-fill-style="gradient"][data-grad-safety="assist"] :is([data-surface-mode="light"], [data-surface-mode="dark"]) {
  --action-primary-bg: linear-gradient(var(--grad-veil, transparent), var(--grad-veil, transparent)), var(--grad-primary, linear-gradient(135deg, var(--color-primary), var(--color-deep, var(--color-primary))));
}

/* ============================================================
   TIER B DOWNGRADES — silent magic on SIGNATURE + DARK
   ============================================================ */
[data-surface-mode="signature"] .btn-gradient,
[data-surface-mode="signature"] .prev-btn--gradient {
  background: var(--action-primary-bg);
  color: var(--action-primary-text);
  border: 1px solid var(--action-primary-border);
}
[data-surface-mode="dark"] .btn-gradient,
[data-surface-mode="dark"] .prev-btn--gradient {
  background: var(--action-primary-bg);
  color: var(--action-primary-text);
  border: 1px solid var(--action-primary-border);
}
[data-surface-mode="signature"] .lab-stat__num,
[data-surface-mode="signature"] .mode-stat__num,
[data-surface-mode="signature"] [data-sig-accent] {
  color: var(--surface-text);
}

/* RETIRED (2026-06-05, W0): the --sig-gradient lever (3 srgb recipes,
   2026-06-03) is gone — it duplicated the editor recipe under the same
   data-grad values. The ONE gradient source is now loader.js GC.resolve
   writing --grad-primary. Last consumer (admin promo) ported. */

/* USER PRIMARY GRADIENT on signature surfaces (2026-06-05; W1 GRADIENT
   CONTRACT 2026-06-05 PM). loader.js resolves known presets into FIXED
   HEX stops (L-clamped around primary) and writes:
     data-grad="<preset>" + --grad-primary   the resolved gradient
     --grad-text    text APCA-graded against EVERY stop + midpoint
     --grad-veil + data-grad-safety="assist" when the rescue ladder fired
   Signature surfaces wear the gradient AND re-anchor --surface-text to
   --grad-text, so borders + the solid action inversion stay coherent
   with the graded text. The paint guardrail above still lays background-
   COLOR (var(--surface-bg)) underneath for tooling. data-grad="none"
   (no pack gradient, or no loader) = no paint, flat solid contract,
   pixel-identical to pre-W1. Brand-on-brand stays forbidden via the
   solid button inversion. */
[data-grad]:not([data-grad="none"]) [data-surface-mode="signature"] {
  background-image: var(--grad-primary, none);
  --surface-text: var(--grad-text, var(--text-on-primary-auto, oklch(from var(--sig-bg) clamp(0.02, calc((0.58 - l) * 1000), 0.98) 0 0)));
}

/* ASSIST VEIL (W1): brand-deep tint composited OVER the whole gradient
   band, alpha picked by loader so the worst text zone clears APCA 60.
   First background-image layer paints on top. Transparent when unset.
   Copy-zone-scoped shielding: .copy-shield, W2 TEXTURE CONTRACT below. */
[data-grad]:not([data-grad="none"])[data-grad-safety="assist"] [data-surface-mode="signature"] {
  background-image: linear-gradient(var(--grad-veil, transparent), var(--grad-veil, transparent)), var(--grad-primary, none);
}

/* GRADIENT EDGE RULE (W1.2, 2026-06-05): a flat hairline painted over a
   varying gradient reads as a halo at the light end and dirt at the dark
   end. ANY signature element wearing the active brand gradient drops its
   OWN border color — radius + elevation still define the edge. Longhand
   border-color on the element only; inner elements keep their tokens.
   Pack with no gradient (data-grad="none") = solid surfaces keep their
   hairlines untouched. Same rule already holds for gradient-fill buttons
   (--action-primary-border: transparent). */
html[data-grad]:not([data-grad="none"]) [data-surface-mode="signature"],
html[data-grad]:not([data-grad="none"]) [data-surface-mode="signature"]:hover {
  border-color: transparent;
  /* W6b: the rim Andrew kept seeing was the gradient's clamped endpoint
     colors smearing through THICK transparent borders (border-width dial
     + background-origin: padding-box). Gradient wearers paint the ramp
     across the full border-box: the see-through border zone shows the
     CONTINUOUS gradient. Rim impossible by construction. */
  background-origin: border-box;
}

/* BRAND MARK CONTRACT (W1.2, 2026-06-05): every logo / avatar / badge
   mark consumes ONE token pair instead of hand-rolling its own fill
   (pages had 3 different hardcoded primary->deep recipes that ignored
   the user's gradient). Solid primary by default; wears the ACTIVE user
   gradient when one is set; ink is always the loader-graded text color.
   Pages consume: background: var(--brand-mark-fill); color: var(--brand-mark-ink). */
:root {
  --brand-mark-fill: var(--color-primary);
  --brand-mark-ink: var(--text-on-primary-auto, var(--text-on-primary, #fff));
}
[data-grad]:not([data-grad="none"]) {
  --brand-mark-fill: var(--grad-primary, var(--color-primary));
  --brand-mark-ink: var(--grad-text, var(--text-on-primary-auto, #fff));
}

/* Callout ring */
[data-surface-mode] .lab-block--callout,
[data-surface-mode] .mode-block--callout,
[data-surface-mode] .prev-callout {
  border-color: var(--color-primary-text, var(--color-primary));
}

[data-surface-mode][data-surface-effect="aurora"]:not([data-dev-aurora]) {
  --surface-effect-opacity: 0;
}

/* ============================================================
   GHOST-TEXT CONTRACT FIX (folded into r463)
   ============================================================ */
:root[data-scheme="dark"] [data-surface="paper"],
:root[data-scheme="dark"] [data-surface="tone"],
:root[data-scheme="dark"] [data-surface="tint"],
:root[data-scheme="dark"] [data-surface="soft"] {
  --action-secondary-text: var(--surface-text);
  --action-secondary-border: color-mix(in srgb, var(--surface-text) 28%, transparent);
  --action-secondary-hover-bg: color-mix(in srgb, var(--surface-text) 8%, transparent);
}

/* ============================================================
   SHAPE BY STYLE — promoted from preview-v3.js (2026-06-04)
   editorial 16/10 · minimal 6/6 · playful 24/999 · sharp 0/0
   ============================================================ */
[data-style="editorial"] { --card-radius: 16px; --btn-radius: 10px;  }
[data-style="minimal"]   { --card-radius: 6px;  --btn-radius: 6px;   }
[data-style="playful"]   { --card-radius: 24px; --btn-radius: 999px; }
[data-style="sharp"]     { --card-radius: 0px;  --btn-radius: 0px;   }

/* ============================================================
   ELEVATION BY DIAL — promoted to shared /system/ (2026-06-04)
   none/low/medium/high/glow → --shadow-card / --shadow-card-hover.
   glow adds a brand-tinted ring. Components must READ these tokens.
   ============================================================ */
:root { --shadow-card: none; --shadow-card-hover: 0 18px 40px rgba(10, 8, 16, 0.14); }
[data-elevation="none"]   { --shadow-card: none;                                                                                  --shadow-card-hover: 0 10px 24px rgba(10, 8, 16, 0.10); }
[data-elevation="low"]    { --shadow-card: 0 1px 2px rgba(10, 8, 16, 0.05), 0 3px 8px rgba(10, 8, 16, 0.06);                       --shadow-card-hover: 0 8px 20px rgba(10, 8, 16, 0.10); }
[data-elevation="medium"] { --shadow-card: 0 2px 6px rgba(10, 8, 16, 0.07), 0 12px 28px rgba(10, 8, 16, 0.10);                     --shadow-card-hover: 0 18px 44px rgba(10, 8, 16, 0.16); }
[data-elevation="high"]   { --shadow-card: 0 4px 12px rgba(10, 8, 16, 0.10), 0 26px 56px rgba(10, 8, 16, 0.18);                    --shadow-card-hover: 0 36px 80px rgba(10, 8, 16, 0.24); }
[data-elevation="glow"]   { --shadow-card: 0 10px 28px rgba(10, 8, 16, 0.10), 0 0 46px color-mix(in srgb, var(--color-primary) 30%, transparent); --shadow-card-hover: 0 16px 40px rgba(10, 8, 16, 0.12), 0 0 64px color-mix(in srgb, var(--color-primary) 42%, transparent); }

/* ============================================================
   W2 · BOUNDARY CONTRACT (2026-06-06)
   ============================================================
   Dividers go system-wide. Three rules, locked 6/5/26:
   1. SAME-MODE neighbors only. A mode shift already IS a divider,
      so [data-surface-mode=X] + [data-surface-mode=X] is the whole
      placement intelligence, pure CSS, zero JS. Under page-level
      dark scheme, declared light and declared dark RESOLVE to the
      same ink surface, so those cross-pairs join the dark set (the
      "contrast change is the separator" rule, applied to what the
      eye actually sees).
   2. Dividers render on a ::before strip, never the section's own
      background. Textures and dividers stack cleanly forever.
   3. Colors come from boundary tokens (--divider-ink 22% text-mix,
      --divider-accent per mode), never raw brand color. Signature
      accent routes through --surface-text so primary-on-primary
      invisibility is impossible by construction.
   Defaults-safe: no attr / "none" matches nothing, untouched render.
   Tokens resolve AT the consuming band, so each pair wears its own
   mode's ink. Pages: DELETE local divider CSS, this is the source.
   ============================================================ */
[data-surface-mode] {
  --divider-ink: color-mix(in srgb, var(--surface-text) 22%, transparent);
}
[data-surface-mode="light"]     { --divider-accent: var(--color-primary); }
[data-surface-mode="dark"]      { --divider-accent: color-mix(in srgb, var(--color-primary) 65%, white); }
[data-surface-mode="signature"] { --divider-accent: color-mix(in srgb, var(--surface-text) 45%, transparent); }
[data-scheme="dark"] [data-surface-mode="light"] { --divider-accent: color-mix(in srgb, var(--color-primary) 65%, white); }

/* Value recipes: geometry tokens on <html>, inherited down. */
[data-section-divider="hairline"] {
  --divider-h: 1px;
  --divider-img: linear-gradient(var(--divider-ink), var(--divider-ink));
}
[data-section-divider="dotted"] {
  --divider-h: 2px;
  --divider-img: radial-gradient(circle at 1px 1px, var(--divider-ink) 1px, transparent 1.4px);
  --divider-repeat: repeat-x;
  --divider-size: 24px 2px;
}
[data-section-divider="gradient"] {
  --divider-h: 2px;
  --divider-img: linear-gradient(90deg, transparent 8%, var(--divider-accent) 50%, transparent 92%);
}
[data-section-divider="chunky"] {
  --divider-h: 4px;
  --divider-img: linear-gradient(var(--divider-accent), var(--divider-accent));
}
[data-section-divider="wave"] {
  /* W4g: the filled 16px scallop read as a chunky decorative band
     (Andrew). The wave is a flowing stroked LINE now: 2px ink stroke,
     long wavelength, an elegant wavy rule instead of bumps. */
  --divider-h: 12px;
  --divider-paint: var(--divider-ink);
  --divider-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 72 12' preserveAspectRatio='none'%3E%3Cpath d='M0 6 Q18 1 36 6 T72 6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
  --divider-mask-size: calc(72px * var(--texture-scale, 1)) 12px;
}

/* Structural rule: one strip, all values, all qualifying pairs. */
/* W6c: dividers belong to BANDS. The pair selectors bind to band-level
   elements only (sections, footers, .band), so same-mode sibling CARDS
   in a grid (social bento, stat rows) are structurally immune... the
   wave strip Andrew found on a social card can't happen again. */
[data-section-divider]:not([data-section-divider="none"]) :is(section, footer, header, main, .band)[data-surface-mode="light"] + :is(section, footer, header, main, .band)[data-surface-mode="light"],
[data-section-divider]:not([data-section-divider="none"]) :is(section, footer, header, main, .band)[data-surface-mode="dark"] + :is(section, footer, header, main, .band)[data-surface-mode="dark"],
[data-section-divider]:not([data-section-divider="none"]) :is(section, footer, header, main, .band)[data-surface-mode="signature"] + :is(section, footer, header, main, .band)[data-surface-mode="signature"],
[data-scheme="dark"][data-section-divider]:not([data-section-divider="none"]) :is(section, footer, header, main, .band)[data-surface-mode="light"] + :is(section, footer, header, main, .band)[data-surface-mode="dark"],
[data-scheme="dark"][data-section-divider]:not([data-section-divider="none"]) :is(section, footer, header, main, .band)[data-surface-mode="dark"] + :is(section, footer, header, main, .band)[data-surface-mode="light"] {
  position: relative;
}
[data-section-divider]:not([data-section-divider="none"]) :is(section, footer, header, main, .band)[data-surface-mode="light"] + :is(section, footer, header, main, .band)[data-surface-mode="light"]::before,
[data-section-divider]:not([data-section-divider="none"]) :is(section, footer, header, main, .band)[data-surface-mode="dark"] + :is(section, footer, header, main, .band)[data-surface-mode="dark"]::before,
[data-section-divider]:not([data-section-divider="none"]) :is(section, footer, header, main, .band)[data-surface-mode="signature"] + :is(section, footer, header, main, .band)[data-surface-mode="signature"]::before,
[data-scheme="dark"][data-section-divider]:not([data-section-divider="none"]) :is(section, footer, header, main, .band)[data-surface-mode="light"] + :is(section, footer, header, main, .band)[data-surface-mode="dark"]::before,
[data-scheme="dark"][data-section-divider]:not([data-section-divider="none"]) :is(section, footer, header, main, .band)[data-surface-mode="dark"] + :is(section, footer, header, main, .band)[data-surface-mode="light"]::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--divider-h, 1px);
  pointer-events: none;
  background-color: var(--divider-paint, transparent);
  background-image: var(--divider-img, none);
  background-repeat: var(--divider-repeat, no-repeat);
  background-size: var(--divider-size, 100% 100%);
  -webkit-mask-image: var(--divider-mask, none);
  mask-image: var(--divider-mask, none);
  -webkit-mask-repeat: repeat-x;
  mask-repeat: repeat-x;
  -webkit-mask-size: var(--divider-mask-size, auto);
  mask-size: var(--divider-mask-size, auto);
}

/* ============================================================
   W2 · TEXTURE CONTRACT (2026-06-06)
   ============================================================
   Patterns go system-wide, opt-in (locked 6/5/26): pages mark
   texture-eligible bands with .band--texture, the dial picks WHICH
   texture, copy-dense bands never get marked. Texture paints on a
   ::after overlay at z-index:-1 inside the band's own isolated
   stacking context: above the band background (including a live
   signature gradient), below ALL content, and it never touches the
   band's background-image, so gradients/dividers/textures compose.
   --texture-ink derives from the band's own --surface-text, so every
   texture is scheme- and mode-aware for free; alpha steps up on dark
   (5% light / 8% dark / 7% signature) so perceived weight matches.
   --texture-scale rides the density dial. Defaults-safe: no attr /
   "none" leaves --texture-layer unset, ::after paints nothing.
   ============================================================ */
:root { --texture-scale: calc(var(--space-multiplier, 1) * var(--texture-zoom, 1)); }
/* texture-1 — Texture Size dial (multiplies the density-derived scale) */
html[data-texture-size="xsmall"]  { --texture-zoom: 0.5; }
html[data-texture-size="smaller"] { --texture-zoom: 0.67; }
html[data-texture-size="small"]   { --texture-zoom: 0.82; }
html[data-texture-size="large"]   { --texture-zoom: 1.3; }
html[data-texture-size="larger"]  { --texture-zoom: 1.7; }
html[data-texture-size="xlarge"]  { --texture-zoom: 2.2; }
[data-surface-mode] {
  --texture-ink: color-mix(in srgb, var(--surface-text) var(--texture-alpha, 5%), transparent);
}
[data-surface-mode="light"]     { --texture-alpha: calc(5% * var(--texture-alpha-mult, 1)); }
[data-surface-mode="dark"]      { --texture-alpha: calc(8% * var(--texture-alpha-mult, 1)); }
[data-surface-mode="signature"] { --texture-alpha: calc(7% * var(--texture-alpha-mult, 1)); }
[data-scheme="dark"] [data-surface-mode="light"] { --texture-alpha: calc(8% * var(--texture-alpha-mult, 1)); }
/* texture-1 — Texture Opacity dial (multiplies the mode-aware base alpha,
   so dialing strength keeps the light/dark/signature weighting intact) */
html[data-texture-opacity="xfaint"]   { --texture-alpha-mult: 0.45; }
html[data-texture-opacity="fainter"]  { --texture-alpha-mult: 0.65; }
html[data-texture-opacity="faint"]    { --texture-alpha-mult: 0.82; }
html[data-texture-opacity="strong"]   { --texture-alpha-mult: 1.6; }
html[data-texture-opacity="stronger"] { --texture-alpha-mult: 2.4; }
html[data-texture-opacity="xstrong"]  { --texture-alpha-mult: 3.4; }

/* Value recipes: geometry on <html>, ink resolves per band. */
[data-pattern-overlay="grid"] {
  --texture-layer: linear-gradient(var(--texture-ink) 1px, transparent 1px),
                   linear-gradient(90deg, var(--texture-ink) 1px, transparent 1px);
  --texture-size: calc(32px * var(--texture-scale, 1)) calc(32px * var(--texture-scale, 1));
}
/* texture-3 (6/7/26): scales / bricks / checker RETIRED per Andrew
   (textures were getting overkill). schema.js migrates saved packs:
   scales->wave, bricks->grid, checker->dot. */
[data-pattern-overlay="dot"] {
  --texture-layer: radial-gradient(circle at 1.5px 1.5px, var(--texture-ink) 1.4px, transparent 1.8px);
  --texture-size: calc(20px * var(--texture-scale, 1)) calc(20px * var(--texture-scale, 1));
}
[data-pattern-overlay="line"] {
  --texture-layer: repeating-linear-gradient(45deg, var(--texture-ink) 0 1px, transparent 1px calc(12px * var(--texture-scale, 1)));
  --texture-size: auto;
}
[data-pattern-overlay="hatch"] {
  --texture-layer: repeating-linear-gradient(45deg, var(--texture-ink) 0 1px, transparent 1px calc(14px * var(--texture-scale, 1))),
                   repeating-linear-gradient(-45deg, var(--texture-ink) 0 1px, transparent 1px calc(14px * var(--texture-scale, 1)));
  --texture-size: auto;
}

.band--texture {
  position: relative;
  isolation: isolate;
}
.band--texture::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  border-radius: inherit;
  background-image: var(--texture-layer, none);
  background-size: var(--texture-size, auto);
}
/* Wave is the one value CSS gradients can't draw: a token-colored
   fill masked through an SVG wave. Replaces BOTH broken legacy waves
   (the scallop hack and the hardcoded-black stroke that died on dark). */
[data-pattern-overlay="wave"] .band--texture::after {
  background-image: none;
  background-color: var(--texture-ink);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 24'%3E%3Cpath d='M0 12 Q16 4 32 12 T64 12' fill='none' stroke='black' stroke-width='1.5'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 24'%3E%3Cpath d='M0 12 Q16 4 32 12 T64 12' fill='none' stroke='black' stroke-width='1.5'/%3E%3C/svg%3E");
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
  -webkit-mask-size: calc(64px * var(--texture-scale, 1)) calc(24px * var(--texture-scale, 1));
  mask-size: calc(64px * var(--texture-scale, 1)) calc(24px * var(--texture-scale, 1));
}
/* texture-1 — cross (original SVG mask, token-colored like wave) */
[data-pattern-overlay="cross"] .band--texture::after {
  background-image: none;
  background-color: var(--texture-ink);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28'%3E%3Crect x='12.9' y='7' width='2.2' height='14' fill='black'/%3E%3Crect x='7' y='12.9' width='14' height='2.2' fill='black'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28'%3E%3Crect x='12.9' y='7' width='2.2' height='14' fill='black'/%3E%3Crect x='7' y='12.9' width='14' height='2.2' fill='black'/%3E%3C/svg%3E");
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
  -webkit-mask-size: calc(28px * var(--texture-scale, 1)) calc(28px * var(--texture-scale, 1));
  mask-size: calc(28px * var(--texture-scale, 1)) calc(28px * var(--texture-scale, 1));
}
/* texture-1 — zigzag (original SVG mask, token-colored like wave) */
[data-pattern-overlay="zigzag"] .band--texture::after {
  background-image: none;
  background-color: var(--texture-ink);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 14'%3E%3Cpath d='M0 10.5 L10 3.5 L20 10.5 L30 3.5 L40 10.5' fill='none' stroke='black' stroke-width='1.6'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 14'%3E%3Cpath d='M0 10.5 L10 3.5 L20 10.5 L30 3.5 L40 10.5' fill='none' stroke='black' stroke-width='1.6'/%3E%3C/svg%3E");
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
  -webkit-mask-size: calc(40px * var(--texture-scale, 1)) calc(14px * var(--texture-scale, 1));
  mask-size: calc(40px * var(--texture-scale, 1)) calc(14px * var(--texture-scale, 1));
}
/* texture-2 — sparkle (original SVG mask, token-colored like wave) */
[data-pattern-overlay="sparkle"] .band--texture::after {
  background-image: none;
  background-color: var(--texture-ink);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cg fill='black'%3E%3Cpath d='M16 4 C17.2 10.5 17.5 10.8 24 12 C17.5 13.2 17.2 13.5 16 20 C14.8 13.5 14.5 13.2 8 12 C14.5 10.8 14.8 10.5 16 4 Z'/%3E%3Cpath d='M37 26 C37.8 30.3 38 30.5 42.3 31.3 C38 32.1 37.8 32.3 37 36.6 C36.2 32.3 36 32.1 31.7 31.3 C36 30.5 36.2 30.3 37 26 Z'/%3E%3Cpath d='M11 36 C11.5 38.7 11.6 38.8 14.3 39.3 C11.6 39.8 11.5 39.9 11 42.6 C10.5 39.9 10.4 39.8 7.7 39.3 C10.4 38.8 10.5 38.7 11 36 Z'/%3E%3C/g%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cg fill='black'%3E%3Cpath d='M16 4 C17.2 10.5 17.5 10.8 24 12 C17.5 13.2 17.2 13.5 16 20 C14.8 13.5 14.5 13.2 8 12 C14.5 10.8 14.8 10.5 16 4 Z'/%3E%3Cpath d='M37 26 C37.8 30.3 38 30.5 42.3 31.3 C38 32.1 37.8 32.3 37 36.6 C36.2 32.3 36 32.1 31.7 31.3 C36 30.5 36.2 30.3 37 26 Z'/%3E%3Cpath d='M11 36 C11.5 38.7 11.6 38.8 14.3 39.3 C11.6 39.8 11.5 39.9 11 42.6 C10.5 39.9 10.4 39.8 7.7 39.3 C10.4 38.8 10.5 38.7 11 36 Z'/%3E%3C/g%3E%3C/svg%3E");
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
  -webkit-mask-size: calc(44px * var(--texture-scale, 1)) calc(44px * var(--texture-scale, 1));
  mask-size: calc(44px * var(--texture-scale, 1)) calc(44px * var(--texture-scale, 1));
}

/* COPY-SHIELD primitive: a soft pool of the band's own surface color
   behind a copy cluster, so text never sits on raw texture. Same
   isolation + negative-z trick: shield sits above the band's texture,
   below the cluster's text. On a live signature gradient the pool
   switches to the brand-deep veil family (never gray), which is the
   copy-zone-scoped shielding the W1 contract reserved for W2. */
.copy-shield {
  position: relative;
  isolation: isolate;
}
.copy-shield::before {
  content: "";
  position: absolute;
  inset: -16px -20px;
  z-index: -1;
  pointer-events: none;
  border-radius: 24px;
  background: radial-gradient(closest-side, var(--surface-bg) 52%, color-mix(in srgb, var(--surface-bg) 55%, transparent) 78%, transparent 100%);
}
/* W7g (2026-06-06): over a LIVE gradient the shield NEVER pools. The
   full-band assist veil already guarantees the APCA floor (gated at
   420 combos / 0 fails); a copy-zone pool on top of it was double
   shielding and read as a dark smudge behind hero copy (Andrew, twice).
   Shield pools remain only for TEXTURE on solid bands, where they are
   surface-colored and invisible by construction. */
[data-grad]:not([data-grad="none"]) [data-surface-mode="signature"] .copy-shield::before {
  background: none;
}

/* ============================================================
   W3 · MEDIA CONTRACT (2026-06-06)
   ============================================================
   Text over photos. The last hand-rolled scrims die here: pages had
   three different hardcoded-black recipes (print card, social tiles,
   hub photo cards). Black is off-brand everywhere and doubly wrong
   under dark scheme. One token family replaces them all:
     --media-scrim-ink  deep brand dark (from --color-deep, chroma-
                        capped, lightness-clamped). NEVER gray/black.
     --media-text       near-white with a whisper of the brand hue,
                        safe by construction over the scrim.
     --media-shadow     token text-shadow ink for type on photos.
   .media wraps a photo ground (auto text color), .media-scrim paints
   the protection layer. Geometry modifiers cover the shipped shapes;
   strength stays in the contract, pages never mix their own. Composes
   with the imagery-filter dial (filters hit the img, scrim sits above).
   ============================================================ */
:root {
  --media-scrim-ink: oklch(from var(--color-deep, var(--color-primary)) clamp(0.12, calc(l - 0.06), 0.3) min(c, 0.09) h);
  --media-text: oklch(from var(--color-deep, var(--color-primary)) 0.97 min(calc(c * 0.18), 0.02) h);
  --media-shadow: color-mix(in srgb, var(--media-scrim-ink) 55%, transparent);
}
.media { position: relative; isolation: isolate; color: var(--media-text); }
.media-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(0deg, color-mix(in srgb, var(--media-scrim-ink) 62%, transparent), transparent 58%);
}
.media-scrim--side {
  background: linear-gradient(120deg, color-mix(in srgb, var(--media-scrim-ink) 78%, transparent), transparent 70%),
              linear-gradient(0deg, color-mix(in srgb, var(--media-scrim-ink) 45%, transparent), transparent 60%);
}
.media-scrim--band {
  background: linear-gradient(90deg, color-mix(in srgb, var(--media-scrim-ink) 60%, transparent), transparent);
}
.media-scrim--frame {
  background: linear-gradient(180deg, color-mix(in srgb, var(--media-scrim-ink) 30%, transparent) 0%, transparent 35%, transparent 75%, color-mix(in srgb, var(--media-scrim-ink) 50%, transparent) 100%);
}
.media-scrim--deep {
  background: linear-gradient(180deg, transparent 50%, color-mix(in srgb, var(--media-scrim-ink) 85%, transparent) 100%);
}
/* Band-level photo ground (the W2 widgets CTA, generalized): brand wash
   over a cover image via --media-img, no inline background-image. */
.band--media,
.band.band--media {
  position: relative;
  color: var(--media-text);
  background-image: linear-gradient(color-mix(in srgb, var(--color-primary) 84%, transparent), color-mix(in srgb, var(--media-scrim-ink) 88%, transparent)), var(--media-img, none);
  background-size: cover;
  background-position: center;
}

/* ============================================================
   W3 · BORDER-STYLE + FOCUS-STYLE PROMOTION (2026-06-06)
   ============================================================
   Both dials lived as page-local copies on 5 of 7 surfaces and were
   DEAD on Widgets + Blog (the W2 coverage matrix caught it). Promoted
   here; page locals deleted. Border-style is a token (cards opt in
   with border-style: var(--dial-border-style, solid)); focus rules
   are global element selectors, so every surface inherits for free.
   ============================================================ */
[data-border-style="none"]   { --dial-border-style: none; }
[data-border-style="dashed"] { --dial-border-style: dashed; }
[data-border-style="dotted"] { --dial-border-style: dotted; }

[data-focus-style="ring"] :is(a, button, input, select, textarea):focus-visible {
  outline: 3px solid color-mix(in srgb, var(--color-primary) 55%, transparent);
  outline-offset: 2px;
}
[data-focus-style="glow"] :is(a, button, input, select, textarea):focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 45%, transparent), 0 0 18px color-mix(in srgb, var(--color-primary) 38%, transparent);
}
[data-focus-style="underline"] a:focus-visible {
  outline: none;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 2px;
}
[data-focus-style="sharp"] :is(a, button, input, select, textarea):focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 0;
}

/* W3 · same-tab page switches crossfade instead of flashing (same-origin
   cross-document view transitions; browsers without support ignore). */
@view-transition { navigation: auto; }


/* ============================================================
   W3.5 · COLOR MODE CONTRACT + BOUNDARY RESTRAINT (2026-06-06)
   ============================================================
   1) COLOR MODE remap. The dial is a promise: mono brands paint ONE
      color family, duo brands paint two. The editor swatches and the
      loader's gradient resolver already honor it (gcPalette); these
      re-declarations make every CSS consumer of secondary/accent honor
      it too. Declared on the surface-mode element so the *-text chains
      substitute against the band's own mode-scoped values.
   2) BOUNDARY RESTRAINT. When exactly ONE side of a same-mode seam is
      textured, the texture already IS the boundary; a divider there is
      double separation. Both sides matching (both textured or both
      plain) keeps the strip. Pure CSS, higher specificity than the
      structural rule, content:none wins.
   ============================================================ */
[data-brand-mode="monochromatic"] [data-surface-mode] {
  --color-secondary: var(--color-primary);
  --color-accent: var(--color-primary);
  --color-secondary-fill: var(--color-primary-fill, var(--color-primary));
  --color-accent-fill: var(--color-primary-fill, var(--color-primary));
  --color-secondary-text: var(--color-primary-text);
  --color-accent-text: var(--color-primary-text);
}
[data-brand-mode="duotone"] [data-surface-mode] {
  --color-accent: var(--color-secondary, var(--color-primary));
  --color-accent-fill: var(--color-secondary-fill, var(--color-primary-fill, var(--color-primary)));
  --color-accent-text: var(--color-secondary-text, var(--color-primary-text));
}

[data-pattern-overlay]:not([data-pattern-overlay="none"]) :is(section, footer, header, main, .band).band--texture + :is(section, footer, header, main, .band)[data-surface-mode]:not(.band--texture)::before,
[data-pattern-overlay]:not([data-pattern-overlay="none"]) :is(section, footer, header, main, .band)[data-surface-mode]:not(.band--texture) + :is(section, footer, header, main, .band).band--texture::before {
  content: none;
}

/* EDUCATIONAL ESCAPE (W4b): style-guide / spec-sheet displays teach the
   brand's TRUE palette; the Color Mode remap is about what the system
   PAINTS with, never about what the brand IS. Displays consume the raw
   --brand-* tokens (loader-written, never remapped), or wrap in
   .brand-truth, which restores the --color-* chains by proximity for
   everything inside the display. */
.brand-truth {
  --color-secondary: var(--brand-secondary, var(--color-primary));
  --color-accent: var(--brand-accent, var(--color-primary));
}


/* ============================================================
   W4 · LAYOUT SAFETY CONTRACT (2026-06-06, Hallmark-derived)
   ============================================================
   System-wide structural guarantees, all surfaces inherit:
   1) overflow-x: clip on html/body (clip, not hidden: hidden kills
      position:sticky). No surface can leak a horizontal scrollbar.
   2) A fixed z-ladder for sticky chrome: page nav wears
      --z-sticky-nav and always out-paints in-page stickies, which
      dock under it via --banner-height + --z-sticky.
   3) Display titles never blow out narrow containers:
      overflow-wrap + min-width:0 as a BASE rule on title primitives.
   4) Image-bearing grid tracks use minmax(0,1fr), never bare 1fr
      (bare 1fr = minmax(auto,1fr); a 1024px-native image sets the
      auto floor and pushes layout past phone viewports). Enforced by
      gate DH-31 + swept across the 7 surfaces this round.
   Gate IDs DH-30/31/32/33 in scripts/slop-test.sh guard all of it.
   ============================================================ */
html, body { overflow-x: clip; }
:root {
  --banner-height: 56px;
  --z-sticky: 200;
  --z-sticky-nav: 300;
}
[data-surface-mode] :is(h1, h2, h3, .b-title, .b-stat) {
  overflow-wrap: anywhere;
  min-width: 0;
}

/* ============================================================
   W4 · COMPOSITION PRIMITIVES (2026-06-06)
   ============================================================
   The system stops only PROTECTING scenarios and starts PRE-DESIGNING
   them. Primitives here, page DOMs consume.
   .b-stat   number callout, scales with type + Heading Drama dials
             (promoted from the widgets rebuild; admin's .stat cards
             are a different, page-local thing and keep their name).
   .overlap  element pulls UP over the previous band's seam: fixed
             z slot under sticky chrome, density-aware pull distance,
             elevated shadow. The classic premium "card straddles two
             bands" move, now one class.
   ============================================================ */
.b-stat {
  font-family: var(--font-display), 'Poppins', sans-serif;
  font-weight: 800;
  font-size: calc(42px * var(--type-scale, 1) * var(--type-contrast, 1));
  line-height: 1;
  color: var(--surface-text);
  font-variant-numeric: tabular-nums;
}
.b-stat--lg { font-size: calc(54px * var(--type-scale, 1) * var(--type-contrast, 1)); }
.b-stat__label {
  font-size: calc(14px * var(--type-scale, 1));
  color: color-mix(in srgb, var(--surface-text) 72%, transparent);
  margin-top: 9px;
}
.overlap {
  position: relative;
  z-index: calc(var(--z-sticky, 200) - 1);
  margin-top: calc(-1 * var(--overlap-pull, 44px) * var(--space-multiplier, 1));
  box-shadow: var(--shadow-card-hover, 0 22px 46px rgba(20, 8, 16, 0.16));
}
.overlap--deep { --overlap-pull: 72px; }

/* ============================================================
   W4 · ICON STROKE + AMBIENT MOTION DIALS GO LIVE (2026-06-06)
   ============================================================
   Both dials had ZERO consumers anywhere (the W3 coverage matrix).
   Icon stroke: one token, every stroke-icon set consumes
   (stroke-width on the shared .i icon primitive per page).
   Ambient motion (liveliness): decorative elements a page marks with
   .alive breathe. off/absent = still (defaults-safe: pages never
   animate until the dial is explicitly set), subtle/lively/vibrant
   step amplitude + tempo. Copy never moves, marks and icon chips do.
   Reduced-motion kills it unconditionally.
   ============================================================ */
[data-icon-stroke="hairline"] { --icon-stroke-w: 1.3; }
[data-icon-stroke="normal"]   { --icon-stroke-w: 1.85; }
[data-icon-stroke="bold"]     { --icon-stroke-w: 2.6; }

@keyframes dh-breathe {
  0%, 100% { transform: translateY(0) scale(1); }
  50% { transform: translateY(calc(-1px * var(--alive-amp, 2))) scale(calc(1 + 0.006 * var(--alive-amp, 2))); }
}
[data-liveliness="subtle"]  .alive { --alive-amp: 1; animation: dh-breathe 7.5s ease-in-out infinite; }
[data-liveliness="lively"]  .alive { --alive-amp: 2; animation: dh-breathe 5.5s ease-in-out infinite; }
[data-liveliness="vibrant"] .alive { --alive-amp: 3.2; animation: dh-breathe 3.8s ease-in-out infinite; }
[data-liveliness="subtle"] .alive:nth-child(2n),
[data-liveliness="lively"] .alive:nth-child(2n),
[data-liveliness="vibrant"] .alive:nth-child(2n) { animation-delay: -2.2s; }
[data-liveliness="off"] .alive { animation: none; }
@media (prefers-reduced-motion: reduce) { .alive { animation: none !important; } }


/* ============================================================
   W4c · NESTED RADIUS CONTRACT (2026-06-06)
   ============================================================
   Rounded-inside-rounded reads sloppy when an inner element's radius
   meets or beats its container's. Rule of concentric corners: flush
   media inside a clipping card draws NO radius of its own... the card's
   overflow does the outer corners, the inner edges stay square. Shape
   utilities (team circles, avatars) and elements that ARE the card
   (gallery tiles = .imgbox.b-card on one element) keep their shape.
   Imagery dials still shape standalone imgboxes everywhere else.
   ============================================================ */
:is(.b-card, .card) > .imgbox:not(:only-child):not(.imgbox--team):not(.imgbox--av) {
  border-radius: 0;
}

/* OVERLAP ROOM: the band ABOVE an .overlap child's seam reserves seat
   so its own copy never gets ridden over (CSS cannot reach a previous
   sibling, so the upper band declares the room). Scales with the same
   pull the .overlap uses. */
.band--overlap-room,
.band.band--overlap-room {
  padding-bottom: calc((var(--overlap-pull, 44px) + 30px) * var(--space-multiplier, 1));
}


/* ============================================================
   W4d · SOLID UI TOKENS (2026-06-06)
   ============================================================
   Chips, step medallions, toggles, and solid pills were borrowing the
   BUTTON action tokens, so the Button Fill dial hollowed them out
   (outline/ghost = transparent category chips, bare step numbers...
   Andrew's screenshots). The fill dial is a promise about BUTTONS.
   Small solid UI gets its own pair with the same mode intelligence
   (dark lightness floor, signature inversion) and total immunity to
   the fill dial. Pages consume: background var(--chip-solid-bg),
   color var(--chip-solid-text).
   ============================================================ */
[data-surface-mode="light"] {
  --chip-solid-bg: var(--color-primary-fill, var(--color-primary));
  --chip-solid-text: var(--text-on-primary-auto, var(--text-on-primary, #fff));
}
[data-surface-mode="dark"] {
  --chip-solid-bg: oklch(from var(--color-primary-fill, var(--color-primary)) clamp(0.5, l, 0.85) c h);
  --chip-solid-text: var(--text-on-primary-auto, var(--text-on-primary, #fff));
}
[data-surface-mode="signature"] {
  --chip-solid-bg: var(--surface-text);
  --chip-solid-text: var(--surface-bg);
}
[data-scheme="dark"] [data-surface-mode="light"] {
  --chip-solid-bg: oklch(from var(--color-primary-fill, var(--color-primary)) clamp(0.5, l, 0.85) c h);
  --chip-solid-text: var(--text-on-primary-auto, var(--text-on-primary, #fff));
}


/* ============================================================
   W4f · BUTTON HOVER + EFFECT RECIPES (2026-06-06)
   ============================================================
   Promoted from 7 page-local copies, and made genuinely DISTINCT:
   shine was lift + a whisker of brightness, ripple was lift + a
   click-shrink (Andrew: "ripple and shine just use lift"). Now:
     none   truly inert
     lift   the base recipe pages ship (translateY + soft shadow)
     pop    springy scale jump
     shine  shimmer sweep + lift + fill-tinted glow + press snap
            (Andrew's reference recipe, shine-1: glint = action text
            color, glow = action FILL color, token-pure)
     ripple a radial pulse from center on hover + compress on press
   html-prefixed selectors out-rank page base :hover rules without
   !important. Reduced motion kills sweep + pulse.
   ============================================================ */
html[data-button-hover-effect="none"] :is(.btn, .b-btn, .ed-topbar__cta, .bwa-bar__btn):hover {
  transform: none;
  filter: none;
  box-shadow: none;
}
html[data-button-hover-effect="pop"] :is(.btn, .b-btn, .ed-topbar__cta, .bwa-bar__btn):hover {
  transform: translateY(-2px) scale(1.04);
}
html[data-button-hover-effect="shine"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary),
html[data-button-hover-effect="ripple"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary) {
  position: relative;
  overflow: hidden;
}
/* shine-1 (2026-06-07): Andrew's reference recipe, token-pure port.
   Sweep transition lives on the BASE ::after so it eases back out on
   un-hover (old version snapped). Hover = -2px lift + glow in the
   button's OWN fill color. Press = snap back down. No hardcoded hexes:
   glint rides action text, glow rides action fill, so every pack and
   every surface mode stays correct. */
html[data-button-hover-effect="shine"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary) {
  transition: transform 0.2s ease, box-shadow 0.3s ease;
}
html[data-button-hover-effect="shine"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary)::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(135deg, transparent 0%, color-mix(in srgb, var(--action-primary-text, #fff) 14%, transparent) 50%, transparent 100%);
  transform: translateX(-100%);
  transition: transform 0.5s ease;
  pointer-events: none;
}
html[data-button-hover-effect="shine"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary):hover {
  transform: translateY(-2px);
  filter: none;
  box-shadow: 0 16px 48px color-mix(in srgb, var(--action-primary-bg, var(--color-primary)) 35%, transparent);
}
html[data-button-hover-effect="shine"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary):hover::after {
  transform: translateX(100%);
}
html[data-button-hover-effect="shine"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary):active {
  transform: translateY(0);
  box-shadow: 0 4px 16px color-mix(in srgb, var(--action-primary-bg, var(--color-primary)) 25%, transparent);
}
html[data-button-hover-effect="ripple"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary)::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 18px;
  height: 18px;
  margin: -9px 0 0 -9px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--action-primary-text, #fff) 32%, transparent);
  opacity: 0;
  transform: scale(0);
  pointer-events: none;
}
html[data-button-hover-effect="ripple"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary):hover::after {
  animation: dh-btn-ripple 0.7s ease-out;
}
html[data-button-hover-effect="ripple"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary):active {
  transform: scale(0.97);
}
@keyframes dh-btn-ripple {
  0% { opacity: 0.85; transform: scale(0); }
  100% { opacity: 0; transform: scale(14); }
}
html[data-button-effect="glow"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary) {
  box-shadow: 0 0 22px color-mix(in srgb, var(--color-primary) 50%, transparent);
}
html[data-button-effect="pulse"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary) {
  animation: dh-btn-pulse-r 2.4s ease-in-out infinite;
}
@keyframes dh-btn-pulse-r {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-primary) 42%, transparent); }
  50% { box-shadow: 0 0 0 7px transparent; }
}
@media (prefers-reduced-motion: reduce) {
  html[data-button-hover-effect="shine"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary),
  html[data-button-hover-effect="shine"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary)::after,
  html[data-button-hover-effect="ripple"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary):hover::after,
  html[data-button-effect="pulse"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary) {
    animation: none;
    transition: none;
  }
  html[data-button-hover-effect="shine"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary):hover {
    transform: none;
  }
  html[data-button-hover-effect="shine"] :is(.btn--p, .b-btn--p, .ed-topbar__cta, .bwa-bar__btn--primary):hover::after {
    transform: translateX(-100%);
  }
}


/* ============================================================
   W6 · GRADIENT WEARS NO BORDER + DIAL WIRING CLOSE-OUT (2026-06-06)
   ============================================================
   Andrew's law, stated once for everything: an element wearing the
   brand gradient never wears a border. The W1.2 edge rule covered
   signature surfaces but page stylesheets load later and some page
   rules out-ranked it (admin revenue card); gradient-fill BUTTONS
   leaked borders through page-level declarations. html-prefixed
   selectors end the argument without !important.
   ============================================================ */
html[data-button-fill-style="gradient"] :is(.btn--p, .b-btn--p, .ed-topbar__cta),
html[data-button-fill-style="gradient"] :is(.btn--p, .b-btn--p, .ed-topbar__cta):hover {
  border-color: transparent;
  background-origin: border-box;
}
/* photos never wear hairline card borders: gallery tiles that ARE the
   card keep radius + shadow + hover, lose the border (sibling rule to
   the nested-radius contract). */
.imgbox:is(.b-card, .card) {
  border: 0;
}

/* BUTTON OUTLINE dial goes live system-wide (it had ONE consumer, the
   editor CTA): one token, consumed by outline-FILL primaries and by
   every secondary/ghost button on every surface. */
[data-button-outline-width="thin"]   { --btn-outline-w: 1px; }
[data-button-outline-width="medium"] { --btn-outline-w: 1.5px; }
[data-button-outline-width="thick"]  { --btn-outline-w: 2.5px; }
[data-button-outline-width="heavy"]  { --btn-outline-w: 3.5px; }
html[data-button-fill-style="outline"] :is(.btn--p, .b-btn--p) {
  border-width: var(--btn-outline-w, 1.5px);
}
html[data-button-outline-width] :is(.btn--g, .b-btn--g) {
  border-width: var(--btn-outline-w, 1.5px);
}

/* BUTTON CASE + WEIGHT promoted from 6 page-local copies (same move
   as the W4f hover promotion; print carries no buttons, so the page
   set is complete by construction). */
html[data-button-text-case="lower"] :is(.btn, .b-btn) { text-transform: lowercase; }
html[data-button-text-case="upper"] :is(.btn, .b-btn) { text-transform: uppercase; letter-spacing: .05em; }
html[data-button-font-weight="normal"] :is(.btn, .b-btn) { font-weight: 500; }
html[data-button-font-weight="bold"]   :is(.btn, .b-btn) { font-weight: 800; }

/* BRAND MARK CONTRACT, admin port (the W1.2 sweep missed it): the
   sidebar mark consumes the contract like every other surface. The
   inset ring dies with the no-border law. */
html .side .mk {
  background: var(--brand-mark-fill, var(--color-primary));
  color: var(--brand-mark-ink, var(--text-on-primary-auto, #fff));
  box-shadow: 0 6px 16px color-mix(in srgb, var(--color-primary) 30%, transparent);
}

/* ── DISPLAY METRIC NORMALIZATION (expand-2, 6/7/26) ──────────────────
   Display faces carry wildly different cap heights — Archivo Black,
   Unbounded, Anton run ~8-12% bulkier than Inter or Fraunces at the same
   font-size, so big headings went clunky on wide faces at Roomy ×
   Dramatic. cap-height normalization renders EVERY family at the same
   optical size: dynamic font handling with no per-font tables, current
   and future faces alike. Unsupported browsers ignore it gracefully. */
h1, h2, h3, .b-title {
  font-size-adjust: cap-height 0.72;
}

/* Extremes compose politely (expand-2b): Roomy × Dramatic stack
   multiplicatively and went clunky on wide display faces even after
   cap normalization. When BOTH are dialed, the scale yields a step —
   the drama stays in the heading/body ratio, not in raw bulk.
   (html-prefixed so it out-ranks the pages' own attr rules.) */
html[data-typescale="spacious"][data-type-contrast="dramatic"] {
  --type-scale: 1.06;
}

/* ── LETTER-SPACING DIALS (tracking-1, 6/7/26) ────────────────────────
   Two 7-step dials: headings + body. DEFAULTS-SAFE: 'normal' emits NO
   attribute (setOrClear), so every page's hand-tuned tracking stays
   exactly as designed. A dialed value overrides via these html-prefixed
   rules; the heading pair carries !important to beat page-local
   hardcodes (one gated override instead of sweeping per-page consumers
   — documented tradeoff). Body rides inheritance: titles/eyebrows with
   their own tracking keep it unless the heading dial says otherwise. */
html[data-heading-tracking="xtight"] { --tracking-display: -0.045em; }
html[data-heading-tracking="tight"]  { --tracking-display: -0.03em; }
html[data-heading-tracking="snug"]   { --tracking-display: -0.015em; }
/* tracking-3 (6/7/26): 'normal' is now an EXPLICIT stop = the font's own
   default spacing. The old contract ('normal' emits no attribute, page
   hand-tunes stay) made the slider non-monotonic: brand-page h1 hand-
   tunes -.025em, so the middle stop rendered TIGHTER than Snug (-.015em).
   Now the editor backfills 'normal' into every pack (schema.blank) and
   the dial enforces true font-default at center.
   tracking-5 (6/7/26): h4 joins the governed set, and every page-local
   display-type element (lockups, wordmarks, quotes, stats, card names)
   consumes var(--tracking-display, <designed fallback>) directly — so
   display type follows the HEADING dial everywhere and body-tracking
   inheritance can never wiggle it. Packs that never carry
   the key still clear the attribute in loader.js, so non-editor
   consumers keep their designed tracking. */
html[data-heading-tracking="normal"] { --tracking-display: normal; }
html[data-heading-tracking="open"]   { --tracking-display: 0.02em; }
html[data-heading-tracking="loose"]  { --tracking-display: 0.04em; }
html[data-heading-tracking="xloose"] { --tracking-display: 0.07em; }
html[data-heading-tracking] h1,
html[data-heading-tracking] h2,
html[data-heading-tracking] h3,
html[data-heading-tracking] h4,
html[data-heading-tracking] .b-title {
  letter-spacing: var(--tracking-display) !important;
}
html[data-body-tracking="xtight"] { --tracking-body: -0.025em; }
html[data-body-tracking="tight"]  { --tracking-body: -0.015em; }
html[data-body-tracking="snug"]   { --tracking-body: -0.008em; }
html[data-body-tracking="normal"] { --tracking-body: normal; }
html[data-body-tracking="open"]   { --tracking-body: 0.012em; }
html[data-body-tracking="loose"]  { --tracking-body: 0.025em; }
html[data-body-tracking="xloose"] { --tracking-body: 0.045em; }
html[data-body-tracking] body { letter-spacing: var(--tracking-body); }

/* ── FONT WEIGHT DIALS (weights-1, 6/11/26, schema v1.4) ──────────────
   Two dials: Heading Weight + Body Weight. DEFAULTS-SAFE: 'default'
   emits NO attribute (loader offVals), so preset-designed weights stay
   untouched until dialed. Canonical numerics live here; the BROWSER
   snaps static families to the nearest loaded face (CSS font-matching)
   while variable families (wght ranges) hit every stop exactly -- incl
   body 'book' 450. Headings carry !important to beat page-local
   hardcodes (same documented tradeoff as tracking). Buttons are
   EXCLUDED on purpose: Button Weight (buttonFontWeight) owns every CTA
   through its own cascade, so the two dials never fight. */
html[data-heading-weight="light"]    { --font-weight-heading: 300; }
html[data-heading-weight="regular"]  { --font-weight-heading: 400; }
html[data-heading-weight="medium"]   { --font-weight-heading: 500; }
html[data-heading-weight="semibold"] { --font-weight-heading: 600; }
html[data-heading-weight="bold"]     { --font-weight-heading: 700; }
html[data-heading-weight="black"]    { --font-weight-heading: 900; }
html[data-heading-weight] h1,
html[data-heading-weight] h2,
html[data-heading-weight] h3,
html[data-heading-weight] h4,
html[data-heading-weight] .b-title {
  font-weight: var(--font-weight-heading) !important;
}
html[data-body-weight="xlight"]   { --font-weight-body: 200; }
html[data-body-weight="light"]    { --font-weight-body: 300; }
html[data-body-weight="regular"]  { --font-weight-body: 400; }
html[data-body-weight="book"]     { --font-weight-body: 450; }
html[data-body-weight="medium"]   { --font-weight-body: 500; }
html[data-body-weight="semibold"] { --font-weight-body: 600; }
html[data-body-weight] body { font-weight: var(--font-weight-body); }
