/* Site Kit — the ONE stylesheet (the loader appends the generated piece
 * rules — tonalities, themes, entrances — to this sheet; see
 * src/site-kit/lib/pieces.ts).
 *
 * SELF-CONTAINMENT (SITE-KIT.md): this kit depends on NOTHING
 * outside src/site-kit/ — the design system is the --kit-* tokens at the
 * top of the root block, and nowhere else. A workspace brand.css injected
 * into a served page is deliberately INERT here: nothing in this sheet reads
 * its variables, and the typography is scoped to outrank its bare
 * [data-text-style] rules.
 *
 * THE INK INDIRECTION (the tone axis): kit CSS never reads a --kit-* token
 * at a use site — it reads the --sk-* ink vars defined on [data-sk]. The
 * `data-tone` axis (default | surface | inverted | accent) REMAPS that whole
 * set on the carrying element, and the readable pairings are designed into
 * the token set, so every tone stays legible.
 *
 * Every kit class is sk-prefixed and every selector is anchored to one (or a
 * kit data attribute), so this sheet can never restyle hand-authored markup
 * around it. */

/* ── Kit roots ─────────────────────────────────────────────────────────── */

[data-sk] {
  font-family: var(--kit-font-sans);
  box-sizing: border-box;
  /* The kit's glyph rendering posture — antialiased, like every editorial
   * canvas this kit targets; without it macOS subpixel rendering reads a
   * full weight step heavier than the design. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  /* THE CASA TOKENS — the kit's own design system. Palette: six roles —
   * background, surface, foreground, muted, accent, divider (DERIVED from
   * muted by default, so a theme overrides it only to tune hairlines).
   * Type: three faces (sans, display, mono — every theme re-voices BOTH
   * sans and display). Scale: six spacing steps plus shape (radius,
   * card-radius, media-radius) and the border axis's width. ONLY this block and the tone
   * remaps below read these;
   * everything else reads the --sk-* indirections, and a THEME
   * (pieces/themes/, stamped data-theme) re-declares exactly these tokens. */
  --kit-background: #fdfdfc;
  --kit-surface: #f4f2ee;
  --kit-foreground: #1c1a17;
  --kit-muted: #6e6a62;
  --kit-accent: #9a5b34;
  /* The DANGER role — the one "something is wrong" color (a field's error
   * state, base.css's form vocabulary). A palette role like accent, so a
   * theme re-voices it; never used for emphasis or decoration. */
  --kit-danger: #c4453c;
  --kit-divider: color-mix(in srgb, var(--kit-muted) 30%, transparent);
  --kit-font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --kit-font-display: var(--kit-font-sans);
  --kit-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* THE OVERLAY WHITES — the fixed near-white ink set for copy on a dark
   * photographic base (the on-media contract's remap, the header's float
   * ink). CONSTANTS, deliberately not theme roles: they sit on the fixed
   * dark veil, never the board's palette — tokens only so the two
   * consumers can't drift (they already had, before this home). Sections
   * with a DOCUMENTED deviation (the gallery's spine, the quote's softer
   * muted) keep their own literals beside a note. */
  --sk-overlay-ink: rgba(255, 255, 255, 0.97);
  --sk-overlay-ink-muted: rgba(255, 255, 255, 0.85);
  --sk-overlay-stroke: rgba(255, 255, 255, 0.32);
  --kit-space-1: 0.35rem;
  --kit-space-2: 0.65rem;
  --kit-space-3: 1rem;
  --kit-space-4: 1.5rem;
  --kit-space-5: 3rem;
  --kit-space-6: 4.5rem;
  /* ── THE BREAKPOINT SCALE — the kit's ONLY sanctioned widths ────────────
   * Five rungs, and every responsive rule in the kit picks one. Which rung a
   * section chooses is its own decision (a two-column band and a six-up grid
   * have no business collapsing at the same width); WHICH WIDTHS EXIST is not,
   * because per-section magic numbers are how a design system stops being one.
   * The census that prompted this found 900 beside 960, and 640 beside 719 and
   * 559 — three pairs meaning the same thing, none of them agreeing, and no way
   * to reason about the set.
   *
   * `md` is load-bearing beyond its number: Forma's preview pane is ~850px
   * on a laptop with the rail and chat open, so anything that collapses ABOVE
   * this rung shows every author a mobile layout while they work.
   *
   * These are DECLARED here and written as literals in `@media` conditions,
   * because a media query can't read a custom property — the var is the record
   * and the documentation, and `site-kit.test.ts` rejects any breakpoint in kit
   * CSS that isn't one of these. That test is what makes the scale real. */
  --kit-bp-xl: 1440px;
  --kit-bp-lg: 1200px;
  --kit-bp-md: 900px;
  --kit-bp-sm: 640px;
  --kit-bp-xs: 560px;
  --kit-radius: 6px;
  --kit-card-radius: 0px;
  /* Media corner (photos, video, tiles): shape-posture role; unthemed fallback. */
  --kit-media-radius: 0px;
  --kit-border-width: 1px;
  /* THE SIZING SYSTEM — BODY IS 1; every element is a ×-body multiplier of
   * --kit-type-base (authoritative: SITE-KIT-PRINCIPLES.md §3). The
   * heading family derives from the ONE title dial on FIBONACCI powers
   * (r = title^⅕; h4 = ×r, h3 = ×r², h2 = ×r³, h1 = ×r⁵, display = ×r⁸
   * — 1, 2, 3, 5, 8). GAPS phrase the lockup's beat — one dial per
   * ADJACENCY (badge→eyebrow, eyebrow→title, title→body, body→actions;
   * 1 = the plain beat). VOICE dials: per-element WEIGHT and TRACKING (the heading
   * family rides weight-title/track-title through structural offsets:
   * weight display +50 / h2 −10 / h3 −20 / h4 −30; track × 1.5/1/0.5 for
   * display/h1/h2 — every offset × the RAMP-CONTRAST dial below, so a theme
   * can flatten the family to ONE voice) plus caps-button, a text-transform
   * keyword for the button/link register. Overlay FRAME dials: air-frame is the inset
   * between an overlay lockup's copy and the
   * frame's edges, ×-body like every spacing dial (SITE-KIT-PRINCIPLES.md:
   * ratios, not px) — the resting end of the --sk-lockup-pad axis family,
   * so per-side section overrides still win — and size-stage is the
   * overlay STAGE's height (raw px — band GEOMETRY, a listed exception;
   * SITE-KIT.md "Flagged"): a MIN-HEIGHT guarantee, not padding, so the
   * anchors stay exactly air-frame from their edges (a pad floor would
   * push an anchored box toward center). Mobile compression, the beat
   * magnitude, and the band breathing are structure, derived below, never
   * knobs. */
  --kit-type-base: 1.05rem;
  --kit-size-badge: 0.75;
  --kit-size-eyebrow: 0.75;
  /* The DETAIL level — the kit's quiet second label voice (row metas, the
   * read link, catalog numbers, captions, field labels): one step under
   * the eyebrow, with its own weight and tracking so a theme can soften
   * row furniture without touching the section eyebrows. Defaults MATCH
   * the eyebrow dials (one voice until a theme splits them) — as literal
   * numbers, not var() refs: a dial IS a bare number (the rack's discovery
   * contract; a reference would hide the knob). */
  --kit-size-detail: 0.75;
  --kit-weight-detail: 500;
  --kit-track-detail: 0.14;
  --kit-size-title: 3.44;
  --kit-size-button: 0.9;
  --kit-gap-badge: 1;
  --kit-gap-eyebrow: 0.5;
  --kit-gap-title: 1;
  --kit-gap-actions: 1.5;
  --kit-air-frame: 2.86;
  /* The EDITORIAL ROW AIR — the block breathing of a ruled list row (a
   * journal entry, a catalog row), ×-body like every air dial. The demo
   * bar: field-standard's journal breathes 2.5 body-widths a side. */
  --kit-air-row: 1.5;
  /* The DATA ROW AIR — a label/value row (a facts list, a spec table, a
   * details block) is a DIFFERENT density from an editorial row: the ruled
   * journal row wants to breathe, a table of numbers wants to be scannable, and
   * one dial for both meant a look that opened its journal also blew its facts
   * list to twice the height it needs. Defaults to the editorial dial, so a
   * theme setting only --kit-air-row behaves exactly as before. */
  --kit-air-data-row: var(--kit-air-row);
  /* The BAND AIR — how much a section breathes above and below its content
   * (resolved as --sk-band-pad below; the section-head margin rides it at
   * ×0.66). ×-body like every air dial. */
  --kit-air-band: 4.3;
  /* The ACTIONS AIR — the HORIZONTAL gap between the buttons in an actions
   * row (×-body, resolved as --sk-actions-gap). The kit's air dials were all
   * vertical, so a look's breathing opened its bands and rows while its CTA
   * pair stayed pinned at one fixed space-scale step however airy or dense
   * the rest was — density had no horizontal dimension at all. The default
   * reproduces the --kit-space-2 it replaced (0.62 × 1.05rem ≈ 0.65rem).
   * A naked-LINK row derives ×2.3 from this one dial rather than carrying a
   * second: links need more air than padded buttons (button chrome is what
   * makes the tighter gap read as separation), and that RELATIONSHIP is
   * structure — one knob moves both. */
  --kit-air-actions: 0.62;
  /* The SITEMAP AIR — the footer link columns' breathing (×-body), one
   * dial per AXIS: -x is the column-to-column gap, -y the gap between
   * STACKED GROUPS in one column ("Research" over "Safety") and between
   * wrap rows. Defaults keep the space-4/space-6 proportion they replaced
   * (1.43/4.29 × the 1.05rem basis = 1.5rem/4.5rem). Within-group pitch
   * (heading→links, link→link) stays on the space scale — type rhythm,
   * not group breathing. */
  --kit-air-sitemap-x: 4.29;
  --kit-air-sitemap-y: 1.43;
  --kit-size-stage: 560;
  /* The PLATE RATIO — the aspect a STACKED lockup's media band keeps (the
   * split-t/split-b letterbox plate), a bare width÷height number. In a stack
   * the media has no band height to fill, so this ratio IS its designed
   * size — but only the DEFAULT: a photo that declares its own data-aspect
   * wins through --sk-image-aspect (the image atom's context pattern). */
  --kit-aspect-plate: 2.4;
  --kit-weight-title: 650;
  --kit-weight-body: 400;
  --kit-weight-eyebrow: 620;
  --kit-weight-badge: 600;
  --kit-weight-button: 600;
  --kit-track-title: -0.02;
  --kit-track-body: 0;
  --kit-track-eyebrow: 0.14;
  --kit-track-badge: 0.02;
  --kit-track-button: 0;
  --kit-caps-button: none;
  /* THE RAMP CONTRAST — how much the heading FAMILY diverges per step. The
   * structural offsets (weight display +50 / h2 −10 / h3 −20 / h4 −30;
   * lead display −0.16 / h2 +0.07 / h3 +0.14 / h4 +0.22; track × 1.5/0.5
   * at the ends) all multiply by this dial: 1 = the kit's full
   * differentiation, 0 = a UNIFORM family — every step at weight-title /
   * lead-title / track-title exactly (the flat editorial voice
   * field-standard's source demo uses). A ratio dial like the gaps: sizes
   * still ladder; only the VOICE spread flattens. */
  --kit-ramp-contrast: 1;
  /* THE FLUID SLOPE — a unitless multiplier on every heading step's vw term
   * (resolved as --sk-type-slope in the scale below): 1 = the kit's own slope,
   * >1 reaches the size-title ceiling at a narrower viewport, <1 holds type
   * smaller for longer. The one knob for "how big are the headlines actually,
   * at the width people read this on" — the ceiling alone doesn't answer that,
   * because the slope is what gets there. Floor and ceiling stay the ratio
   * system's, so a theme can never outgrow its own scale. */
  --kit-type-slope: 1;
  /* THE EFFECT INTENSITY DIALS — signed unitless multipliers (1 = the
   * catalogs' authored strength; the parsers enforce the composition):
   * anim durations divide by the magnitude, travel multiplies by the
   * signed value (negative reverses the entrance direction); tone filter
   * amounts interpolate from identity, wash alphas multiply; SHAPE corners
   * and DENSITY air both multiply. Ordinary kit tokens, so a THEME dials
   * them like any other. Bridged to the kit-neutral --sk-* seam below. */
  --kit-anim-intensity: 1;
  --kit-tone-intensity: 1;
  /* The SHAPE dial — every corner in a shape piece multiplies by it, so one
   * token softens or hardens a look's whole corner posture without leaving
   * the picked shape (0 squares everything off). */
  --kit-shape-intensity: 1;
  /* The BORDER dial — the border piece's width multiplies by it, so one
   * token thins or thickens a look's whole stroke without leaving the
   * picked border (functional edges still floor at 1px downstream). */
  --kit-border-intensity: 1;
  /* The DENSITY dial — every air/gap ratio in a density piece multiplies by
   * it: one token opens or tightens the whole page's breathing while the
   * picked density keeps the PROPORTIONS between its dials. */
  --kit-density-intensity: 1;
  /* THE TWO HALVES OF A TONALITY, separately dialable. A tonality does two
   * different jobs with one look: the FILTER treats the photograph (how far
   * imagery recedes from the page), and the WASH is the legibility floor under
   * overlay copy (§4 — legibility is carried by the kit, never the photo).
   * Those are not the same decision. A look that wants BRIGHT, untreated
   * photography still needs a real scrim wherever type sits on an image, and
   * one dial forced a choice between washed-out photos and illegible overlays.
   * Each half defaults to the shared dial, so a theme that sets only
   * --kit-tone-intensity behaves exactly as before. */
  --kit-tone-filter-intensity: var(--kit-tone-intensity);
  --kit-tone-wash-intensity: var(--kit-tone-intensity);
  /* The CASCADE BEAT — the gap between one entrance and the next inside a
   * reveal phrase, in ms as a bare number so it dials. Every stagger the
   * engine assigns is `slot × this beat`, and the beat divides by the
   * intensity dial's magnitude — faster entrances tighten the layering
   * with them. */
  --kit-cascade-beat: 90;
  /* THE CHROME VOICE — the BRAND wordmark and the NAV LINK register (one
   * chrome vocabulary shared by header and footer links). Same grammar as
   * every element: ×-body size, CSS weight, bare-em tracking, and a
   * keyword caps dial. */
  --kit-size-brand: 1;
  --kit-weight-brand: 600;
  --kit-track-brand: 0.01;
  /* The wordmark's FACE and CASE. The brand voice had size, weight, and
   * tracking but no face ref and no caps dial, so every theme's wordmark was
   * locked to the sans workhorse in sentence case — on a display-face look
   * that makes the single most brand-carrying piece of type on the site the
   * one piece that can't wear the brand's own face. A face REF like every
   * other element voice, defaulting to the sans face so no existing theme
   * moves. */
  --kit-font-brand: var(--kit-font-sans);
  --kit-caps-brand: none;
  /* THE CONTROL BOUNDARY — how much of the muted ink a boxed control's resting
   * border carries, as a PERCENT mixed toward the band it sits on. 100 is the
   * kit's own strength; lower softens the box toward a hairline.
   *
   * WHY A DIAL AND NOT A COLOUR: the shared rule's comment calls this border a
   * "hairline" but it was written as the full --sk-ink-muted, a mid-tone — the
   * boxes read heavier than every other line in the kit. The reason NOT to just
   * point it at --sk-stroke is ACCESSIBILITY: a control boundary needs 3:1
   * non-text contrast (WCAG 1.4.11), and a true hairline on a light page is
   * ~1.3:1. So the softening is bounded and deliberate — mixing toward the
   * band keeps it correct on every tone (both terms are tone-resolved at the
   * use site), and a theme going too low is making a legibility choice it can
   * see named. Around 85 holds ~3.5:1 on a light page. */
  --kit-control-stroke-mix: 100;
  --kit-size-nav: 0.75;
  --kit-weight-nav: 500;
  --kit-track-nav: 0.08;
  --kit-caps-nav: uppercase;
  /* THE ELEMENT VOICE GRID — one token per (family × element) for the five
   * elements the Dressing Room edits (badge / eyebrow / title / body /
   * button). The NAMING CONVENTION is the contract:
   * --kit-<family>-<element> — per-element editors write EXACTLY these
   * tokens, which is what lets an element edit save into a theme's vars (a
   * theme may only re-declare tokens). Families: font (a face REF to one
   * of the three faces above, so theme face overrides keep flowing), slant,
   * lead (lead-title is the h1 leading; the heading family follows through
   * offsets display −0.16 / h2 +0.07 / h3 +0.14 / h4 +0.22), caps, edge +
   * edge-style
   * (per-side-capable border-width shorthand and its style; border COLOR
   * stays the design system's — stroke on the badge, the button pair on
   * buttons, currentColor elsewhere), round, pad. size / weight / track
   * live with the dials above. Lengths default "0px", never bare 0 — a
   * unitless numeric would read as a ratio dial. */
  --kit-font-badge: var(--kit-font-sans);
  --kit-font-eyebrow: var(--kit-font-sans);
  --kit-font-title: var(--kit-font-display);
  --kit-font-body: var(--kit-font-sans);
  --kit-font-button: var(--kit-font-sans);
  --kit-slant-badge: normal;
  --kit-slant-eyebrow: normal;
  --kit-slant-title: normal;
  --kit-slant-body: normal;
  --kit-slant-button: normal;
  --kit-lead-badge: normal;
  --kit-lead-eyebrow: normal;
  --kit-lead-title: 1.08;
  --kit-lead-body: 1.65;
  --kit-lead-button: 1.2;
  --kit-caps-badge: none;
  --kit-caps-eyebrow: uppercase;
  --kit-caps-title: none;
  --kit-caps-body: none;
  --kit-edge-badge: var(--sk-border-width-min);
  --kit-edge-eyebrow: 0px;
  --kit-edge-title: 0px;
  --kit-edge-body: 0px;
  --kit-edge-button: var(--sk-border-width-min);
  --kit-edge-style-badge: solid;
  --kit-edge-style-eyebrow: solid;
  --kit-edge-style-title: solid;
  --kit-edge-style-body: solid;
  --kit-edge-style-button: solid;
  /* Badge tracks the CONTROL corner, tightened a touch (× 0.65): a badge sits
   * inside cards/media, so a neat tuck wants a corner slightly under the
   * surface around it. Control not card, on purpose: `full` still clamps to a
   * pill (0.65 × 999 is still a pill on a small box), `tailored` keeps a
   * rounded badge on square cards, `sharp` stays square, and it stays
   * single-valued so leaf's multi-value card corner can't break the math. */
  --kit-round-badge: calc(var(--kit-radius) * 0.65);
  --kit-round-eyebrow: 0px;
  --kit-round-title: 0px;
  --kit-round-body: 0px;
  /* The ACTION's corner rides the kit radius RAW — no cap. The old
   * `min(…, 1.2em)` existed to keep a big CARD radius from ballooning
   * buttons, back when one token served both; `--kit-card-radius` is its own
   * axis now (see the shape catalog, SITE-KIT.md rule 10), so the guard
   * protected against a conflation that no longer exists — and it made the
   * `full` posture IMPOSSIBLE: 1.2em of the button's own type resolves to
   * ~18px while a full lozenge on this padding needs ~22px, so "fully round"
   * rendered 4px off `rounded` and read as neither. Raw is also self-limiting
   * — CSS clamps border-radius to half the box, so 999px IS a perfect pill at
   * any size and a moderate value stays exactly itself. BOXED controls keep
   * the cap (--sk-control-radius): a text field must never become a capsule. */
  --kit-round-button: var(--kit-radius);
  --kit-pad-badge: 0.4em 0.95em;
  --kit-pad-eyebrow: 0px;
  --kit-pad-title: 0px;
  --kit-pad-body: 0px;
  --kit-pad-button: 0.8rem 1.6rem;
  /* The icon glyph box (× body) — icon rows, the cart glyph. */
  --kit-size-icon: 1.05;
  /* THE CHROME GEOMETRY DIALS — the bar's own shape (raw px for designed
   * lengths). bar-height is a MIN-HEIGHT (content can never be cramped);
   * bar-pad-x the side gutters; bar-gap the gap BETWEEN zones; zone-gap
   * the gap between elements WITHIN a zone; bar-divider the bottom
   * hairline's width (0 = none). */
  --kit-bar-height: 64;
  --kit-bar-pad-x: 24;
  /* The bar's VERTICAL padding — its own dial, like the horizontal one. It was
   * hardcoded to the space scale (--kit-space-2, 0.65rem = 10.4px) while the
   * side gutters were dialable, so a look could widen the bar but never make it
   * BREATHE: min-height is a floor, not air, and the moment the bar's content
   * grew past it (a display-face wordmark) the strip went tight with no way to
   * open it. The default IS that former hardcoded value, so no existing theme
   * shifts by a pixel. */
  --kit-bar-pad-y: 10.4;
  --kit-bar-gap: 24;
  --kit-zone-gap: 16;
  --kit-bar-divider: 0;
  /* THE DROPDOWN PANEL DIALS — the nav flyout surface: radius + padding
   * (raw px) and elevation (a shadow alpha, 0..1 — 0 = flat). The panel
   * PAINTS from the ink set, so a theme darkens it by tone (data-tone via
   * --kit-panel-tone; see the nav element rules), not authored colors. */
  --kit-panel-radius: 10;
  --kit-panel-pad: 20;
  --kit-panel-shadow: 0.14;
  /* THE PAGE GUTTER — the page-level side inset (raw px: page GEOMETRY,
   * same designed-length category as the bar dials; SITE-KIT.md
   * "Flagged"): the ONE distance a content band's internal side gutter
   * and the data-inset axis pull in from the viewport edges, resolved to
   * --sk-page-pad-x. Defaults MATCH bar-pad-x — one shared page edge for
   * chrome and bands until a theme splits them — as a literal number, not
   * a var() ref: a dial IS a bare number (the rack's discovery contract;
   * a reference would hide the knob). The PAGE CAP is its sibling fact but
   * an AXIS, not a dial: a theme picks a page-width piece
   * (pieces/page-widths/ — SITE-KIT.md rule 10) and the generated theme
   * sheet emits the LENGTH onto body (--sk-page-max — pieces.ts
   * themeSheetCss, the canvas rule's posture; body sits outside this token
   * scope), which the RAIL sites consume per band (the band-gutter home's
   * doctrine — a content-rail cap, paint keeps the viewport).
   * --kit-page-max is the pick's hand-tune var (same raw-px geometry
   * category, no declaration here). */
  --kit-page-gutter: 24;
  /* The INK set — every color a kit element paints, indirected so the tone
   * axis can remap them wholesale. */
  --sk-ink: var(--kit-foreground);
  --sk-ink-muted: var(--kit-muted);
  /* THE DERIVED SIZES — the dials resolved into what use sites read; all
   * structure, no knobs (SITE-KIT.md §3). The heading family
   * interpolates from the title dial (r = title^⅕); mobile compression is
   * the same family at ~55% of the step's excess, and the vw slopes here
   * are the ONE place fluid type is written — shared by the ramp's
   * font-sizes and the lockup's beat calcs so type and rhythm compress
   * together. The ×-body element dials sit below the fluid floor and stay
   * steady. */
  --sk-type-r: pow(var(--kit-size-title), 0.2);
  --sk-type-r-min: calc(1 + (var(--sk-type-r) - 1) * 0.55);
  --sk-text-badge-size: calc(var(--kit-type-base) * var(--kit-size-badge));
  --sk-text-eyebrow-size: calc(var(--kit-type-base) * var(--kit-size-eyebrow));
  /* Caption, tags, field labels, row metas — the detail level resolved
   * (its own dial; defaults ride the eyebrow's). */
  --sk-text-detail-size: calc(var(--kit-type-base) * var(--kit-size-detail));
  --sk-text-btn-size: calc(var(--kit-type-base) * var(--kit-size-button));
  /* The chrome registers resolved — the brand wordmark, the nav-link
   * second voice (header + footer), and the icon glyph box. */
  --sk-text-brand-size: calc(var(--kit-type-base) * var(--kit-size-brand));
  --sk-text-nav-size: calc(var(--kit-type-base) * var(--kit-size-nav));
  --sk-icon-size: calc(var(--kit-type-base) * var(--kit-size-icon));
  /* The chrome geometry resolved. */
  --sk-bar-height: calc(var(--kit-bar-height) * 1px);
  --sk-bar-pad-x: calc(var(--kit-bar-pad-x) * 1px);
  --sk-bar-pad-y: calc(var(--kit-bar-pad-y) * 1px);
  --sk-bar-gap: calc(var(--kit-bar-gap) * 1px);
  --sk-zone-gap: calc(var(--kit-zone-gap) * 1px);
  /* THE CHROME SCRIM — §4 applied to the BAR: a floating bar carries its own
   * contrast floor instead of borrowing the hero's. FIXED values, like the
   * overlay inks they hold up (a theme may retune the tonality wash — that
   * dial must never be able to make the chrome unreadable), in the quiet
   * default tonality's own wash ink (ash, pieces/image-tonalities/ash.json).
   * It fades out over --sk-chrome-scrim-fade past the bar's bottom edge so
   * there is no visible band edge on the photo. */
  --sk-chrome-scrim: linear-gradient(rgba(16, 15, 14, 0.55), rgba(16, 15, 14, 0));
  --sk-chrome-scrim-fade: 2rem;
  /* The page gutter resolved — content-band side gutters and the
   * data-inset axis read this, never the bar's own dial. */
  --sk-page-pad-x: calc(var(--kit-page-gutter) * 1px);
  --sk-panel-radius: calc(var(--kit-panel-radius) * 1px);
  --sk-panel-pad: calc(var(--kit-panel-pad) * 1px);
  /* The panel ELEVATION resolved — the ONE popover/sheet shadow every
   * panel-posture surface wears (nav flyout, date calendar, menu sheet, the
   * library's cookie and newsletter cards); the alpha dial is the knob. */
  --sk-panel-shadow: 0 18px 40px -12px rgba(0, 0, 0, var(--kit-panel-shadow));
  /* The heading ladder rides FIBONACCI powers of r (1, 2, 3, 5, 8): h4 =
   * ×r (the labeling step — below the fluid floor, steady), h3 = ×r² (the
   * editorial-row step: a journal title, a tile heading), h2 = ×r³, h1 =
   * ×r⁵, display = ×r⁸. */
  /* THE FLUID SLOPE (--kit-type-slope) — a multiplier on every heading step's
   * vw term, so a theme can decide how HOT its headlines run between the floor
   * and the ceiling without touching the ratio system. It matters more than it
   * looks: the ceiling (--kit-size-title) is only reached once the slope gets
   * there, so two themes with the same size-title can render very differently
   * sized headlines at the same viewport. 1 = the kit's own slope; >1 reaches
   * the ceiling sooner (the display-face editorial posture), <1 holds type
   * smaller for longer. Only the SLOPES scale — the floor and ceiling stay the
   * ratio system's, so no theme can outgrow its own scale. */
  --sk-type-slope: var(--kit-type-slope, 1);
  --sk-text-h4-size: calc(var(--kit-type-base) * var(--sk-type-r));
  --sk-text-h3-size: clamp(
    calc(var(--kit-type-base) * pow(var(--sk-type-r-min), 2)),
    calc(2.25vw * var(--sk-type-slope)),
    calc(var(--kit-type-base) * pow(var(--sk-type-r), 2))
  );
  --sk-text-h2-size: clamp(
    calc(var(--kit-type-base) * pow(var(--sk-type-r-min), 3)),
    calc(3vw * var(--sk-type-slope)),
    calc(var(--kit-type-base) * pow(var(--sk-type-r), 3))
  );
  --sk-text-h1-size: clamp(
    calc(var(--kit-type-base) * pow(var(--sk-type-r-min), 5)),
    calc(5vw * var(--sk-type-slope)),
    calc(var(--kit-type-base) * var(--kit-size-title))
  );
  --sk-text-display-size: clamp(
    calc(var(--kit-type-base) * pow(var(--sk-type-r-min), 8)),
    calc(7.75vw * var(--sk-type-slope)),
    calc(var(--kit-type-base) * pow(var(--sk-type-r), 8))
  );
  /* The LEDE volume — the emphasized intro paragraph under a hero
   * headline: one scale step above body (never a heading step), so the
   * headline keeps its hierarchy on every theme. Sections read this ONE
   * knob (font-size: var(--sk-lede-size)) instead of hand-rolling a clamp. */
  --sk-lede-size: clamp(var(--kit-type-base), 1.5vw, var(--sk-text-h4-size));
  /* The MEASURE — the one readable line-length cap for lockup running copy
   * (descs and ledes), a ratio dial in ch (--kit-measure) so a theme sets
   * its own prose column. Prose must never ride the 90%-viewport container
   * width (100ch+ is unreadable). Sections never re-cap (or un-cap) copy
   * with their own max-width; they inherit this knob. */
  --kit-measure: 46;
  --sk-measure: calc(var(--kit-measure) * 1ch);
  /* THE HEADLINE MEASURE — the same idea, one register up, in the SAME
   * currency: characters. A measure is a count of CHARACTERS per line, and
   * `ch` resolves against the element's own font-size, so one number holds
   * at every register and under any theme title dial. A headline takes far
   * fewer than running copy — the line is a phrase to be taken in at a
   * glance, not a paragraph to be read along.
   *
   * 20 is chosen so a DISPLAY headline spans a normal desktop band (~1445px
   * at the display step on a 1440 viewport — full width, as a poster should
   * be) while still catching an ultra-wide screen, where an uncapped
   * headline would set twenty words to the line and read as a mistake. */
  --kit-measure-title: 20;
  --sk-measure-title: calc(var(--kit-measure-title) * 1ch);
  /* The RHYTHM factors — the per-adjacency gap dials as multiples of the
   * lockup's beat. The lockup's calc()s read these, so the phrasing keeps
   * one owner. */
  --sk-rhythm-badge: var(--kit-gap-badge);
  --sk-rhythm-label: var(--kit-gap-eyebrow);
  --sk-rhythm-title: var(--kit-gap-title);
  --sk-rhythm-departure: var(--kit-gap-actions);
  /* The FRAME INSET — the air-frame dial resolved to a length (×-body:
   * the overlay inset breathes with the type basis, like the band air).
   * The innermost fallback of the --sk-lockup-pad axis family, so any
   * per-side var a section sets still beats it. */
  --sk-frame-pad: calc(var(--kit-air-frame) * var(--kit-type-base));
  /* The ROW AIR resolved — the ruled container's default block padding
   * (×-body). Sections stop hand-picking --sk-ruled-pad from the space
   * scale; the dial owns list breathing kit-wide. */
  --sk-ruled-pad: calc(var(--kit-air-row) * var(--kit-type-base));
  --sk-data-row-pad: calc(var(--kit-air-data-row) * var(--kit-type-base));
  /* The ACTIONS AIR resolved — the one horizontal element gap density moves
   * (button ↔ button in an actions row; the naked-link row derives ×2.3). */
  --sk-actions-gap: calc(var(--kit-air-actions) * var(--kit-type-base));
  /* The SITEMAP AIR resolved — the column gap (x) and the
   * group-stack/wrap-row gap (y). */
  --sk-sitemap-gap-x: calc(var(--kit-air-sitemap-x) * var(--kit-type-base));
  --sk-sitemap-gap-y: calc(var(--kit-air-sitemap-y) * var(--kit-type-base));
  /* The STAGE HEIGHT — the size-stage dial resolved: the overlay lockup's
   * resting min-height. A plain px length, deliberately NOT vh-capped: a
   * cap silently eats the dial, and a fixed length keeps a self-sizing
   * preview frame convergent. Sections override through --sk-lockup-min-h
   * (a full-bleed hero sets 100svh via the height axis). */
  --sk-stage-h: calc(var(--kit-size-stage) * 1px);
  /* The BAND AIR — the one section-breathing value scaffolding reads
   * (.sk-section padding-block; the section-head margin at ×0.66). ×-body,
   * riding the type-base dial, and now riding a THEME DIAL too
   * (--kit-air-band): how generously a look breathes between bands is a
   * per-look decision, not a constant. It was hardcoded at 4.3, which made
   * every theme's vertical rhythm identical however airy or dense its type
   * was — the one structural value a designer most wants to move. */
  --sk-band-pad: calc(var(--kit-type-base) * var(--kit-air-band));
  --sk-accent: var(--kit-accent);
  --sk-bg: var(--kit-background);
  --sk-surface: var(--kit-surface);
  /* Card fill — surface on the default tone so cards tint against the page;
   * tones remap it so cards keep reading as cards on any band. */
  --sk-card: var(--kit-surface);
  /* Form-control fill (inputs, textareas). */
  --sk-field: var(--kit-background);
  /* The DANGER pair — the error banner's fill and its ink. The ink is a
   * FIXED near-white (§4's overlay-ink rule applied to the danger fill:
   * the banner must stay readable on every theme's danger red), never a
   * theme token. */
  --sk-danger: var(--kit-danger);
  --sk-danger-ink: #fffdfc;
  /* Filled buttons pair the foreground fill with background-color text —
   * the one readable pairing the token set guarantees. Tones remap the
   * pair. */
  --sk-btn-bg: var(--kit-foreground);
  --sk-btn-ink: var(--kit-background);
  color: var(--sk-ink);
  /* The SHAPE tokens. The stroke reads the divider role (muted ink at low
   * alpha by default), so hairlines are always quieter than text. */
  --sk-stroke: var(--kit-divider);
  --sk-radius: var(--kit-radius);
  /* Form-control rounding — the kit radius CAPPED, resolved ONCE for every
   * boxed control (input, textarea, select, file, the picker button, the
   * error banner). The em resolves at each use site, so the cap follows the
   * control's own type size. Unlike the ACTION (which is now raw, so `full`
   * can be a real lozenge), a boxed field keeps the cap on purpose: a
   * capsule-shaped text input reads as a pill-shaped BUTTON and stops
   * announcing that you can type in it. */
  --sk-control-radius: min(var(--kit-radius), 1.2em);
  /* Media corner (shape-posture role). Full-bleed bands zero it at their own
   * seam (boundary rule: a corner belongs to the bounded surface). */
  --sk-media-radius: var(--kit-media-radius);
  /* The raised card's corners — reads the card-radius posture role. */
  --sk-card-radius: var(--kit-card-radius);
  /* Decorative borders (card edges, hairlines, image frames) ride this raw —
   * 0 means a genuinely borderless site. FUNCTIONAL borders (buttons, inputs)
   * floor at 1px via max(): an affordance may thin, never vanish. The width
   * itself is the BORDER axis (pieces/borders/ — its own theme facet, split
   * out of shape), consumed through exactly these two tiers. */
  --sk-border-width: var(--kit-border-width);
  --sk-border-width-min: max(var(--kit-border-width), 1px);
  /* ICON stroke — glyph geometry (the faq +/- marker, the pricing check),
   * deliberately OFF the border dial: a heavier rule posture must thicken
   * edges, never fatten drawn glyphs. */
  --sk-icon-stroke: 1px;
}
[data-sk] *,
[data-sk] *::before,
[data-sk] *::after {
  box-sizing: inherit;
}

/* `hidden` WINS, unconditionally. The UA's [hidden] { display: none } is
 * UA-origin, so ANY author `display` on the same element silently defeats
 * it — the newsletter overlay's root (display: grid to center its card)
 * shipped as a full-viewport click shield while "hidden". In this kit the
 * attribute is a LIFECYCLE statement, never a style: overlay roots rest
 * hidden until their script mounts them (scripts-off they never exist) and
 * every entrance rides data-open on an un-hidden root, so nothing here may
 * out-rank it. !important is the point — no section sheet, at any
 * specificity, can reintroduce the class of bug. */
[data-sk][hidden],
[data-sk] [hidden] {
  display: none !important;
}

/* ── The tone axis (data-tone) ────────────────────────────────────
 * Stamped by a section's `tone` prop on its root (or by a template on an
 * inner panel). Each tone remaps the ink set and paints itself, so every
 * descendant use site follows for free. `default` has no rule. These
 * selectors sit AFTER the root block on purpose: same specificity, later in
 * the sheet, so the remap wins on an element carrying both [data-sk] and
 * [data-tone]. */

[data-tone="surface"] {
  --sk-bg: var(--kit-surface);
  --sk-card: var(--kit-background);
  --sk-field: var(--kit-background);
  background-color: var(--sk-bg);
}

/* Inverted: the foreground role as the band, background-role ink on it. */
[data-tone="inverted"] {
  --sk-bg: var(--kit-foreground);
  --sk-ink: var(--kit-background);
  --sk-ink-muted: color-mix(in srgb, var(--kit-background) 62%, transparent);
  /* Accent lightened toward the background to stay legible on the dark
   * band. */
  --sk-accent: color-mix(in srgb, var(--kit-accent) 55%, var(--kit-background));
  --sk-surface: color-mix(in srgb, var(--kit-background) 8%, transparent);
  --sk-card: color-mix(in srgb, var(--kit-background) 7%, transparent);
  --sk-field: color-mix(in srgb, var(--kit-background) 8%, transparent);
  --sk-stroke: color-mix(in srgb, var(--kit-background) 22%, transparent);
  --sk-btn-bg: var(--kit-background);
  --sk-btn-ink: var(--kit-foreground);
  background-color: var(--sk-bg);
  color: var(--sk-ink);
}

/* Accent: the loud band (the CTA look). Background-role ink everywhere. */
[data-tone="accent"] {
  --sk-bg: var(--kit-accent);
  --sk-ink: var(--kit-background);
  --sk-ink-muted: color-mix(in srgb, var(--kit-background) 78%, transparent);
  --sk-accent: var(--kit-background);
  --sk-surface: color-mix(in srgb, var(--kit-background) 12%, transparent);
  --sk-card: color-mix(in srgb, var(--kit-background) 10%, transparent);
  --sk-field: color-mix(in srgb, var(--kit-background) 12%, transparent);
  --sk-stroke: color-mix(in srgb, var(--kit-background) 30%, transparent);
  --sk-btn-bg: var(--kit-background);
  --sk-btn-ink: var(--kit-accent);
  background-color: var(--sk-bg);
  color: var(--sk-ink);
}

/* ── The ANIMATION FRAMEWORK (data-anim) ──────────────────────
 * The site-wide entrance system — none | rise | fade | zoom — declared by
 * EVERY section's `anim` prop (chrome included — all kit components
 * understand the animation system; SITE-KIT-PRINCIPLES.md) and stamped as
 * data-anim on its root (a stance-global axis: pick ONE framework
 * site-wide). The SECTION is the reveal unit; chrome DEFAULTS to `none` —
 * an overridable posture, not a structural inability.
 *
 * The ENGINE is the kit's base.js — an IntersectionObserver stamps `sk-in`
 * on each unit as it enters, as a RE-RUNNABLE idempotent pass (a
 * MutationObserver re-decorates late fetch-repeater rows; SPACES.md
 * "Loading rows from a fetched source"). The frameworks are CATALOG
 * material: each is a piece (pieces/animation-frameworks/<id>.json), and
 * the machinery generates the ARMED rules into this kit's base.css
 * (animationFrameworkCss in lib/pieces.ts, appended by the loader) — a new
 * entrance is a new JSON, never hand-authored CSS here. Every entry composes pace and travel
 * from --sk-anim-intensity (durations divide by its magnitude, offsets
 * multiply by the signed value — the parser enforces it). This sheet keeps
 * only the shared halves: the ease token and the REVEALED rule below.
 * The tagging vocabulary, all optional beyond the root stamp:
 *
 *   data-anim="<framework>"  the section root — the default reveal unit
 *   data-sk-reveal           an INNER element that reveals on its own; when
 *                            a section carries any, the ROOT DELEGATES its
 *                            entrance to them, so entrances never stack
 *   data-sk-reveal-group     a container whose CHILDREN become units
 *                            (base.js stamps data-sk-reveal on them); their
 *                            --sk-reveal-delay is assigned at REVEAL time,
 *                            batch-relative — see base.js
 *   data-sk-reveal-scope     a container whose inner units are ARMED but
 *                            never observed: the section plays them itself
 *                            (window.__skReveal, base.js) when its own state
 *                            changes. The container stays a normal unit; an
 *                            UNCLAIMED scope is played by the engine rather
 *                            than left armed.
 *   data-sk-cascade          the LAYERED entrance: the unit delegates its
 *                            travel to its CHILDREN — the armed rules hide
 *                            them, not it; base.js stamps a per-child
 *                            --sk-reveal-delay (its batch base +
 *                            slot × beat) so one observed unit plays
 *                            its parts a beat apart
 *   data-sk-layer            "background" | "foreground" — the LAYER a THEME
 *                            dresses; a stamped theme's generated rules
 *                            (themeCss) re-dress each layer at (0,3,1),
 *                            between the framework's inner armed rule and
 *                            the revealed rule below
 *
 * DEGRADATION IS STRUCTURAL: the armed (hidden) state exists only under
 * html[data-sk-js] — set by the one inline script in chrome/head.html, so
 * scripts-off/print/headless render static — AND inside
 * @media (prefers-reduced-motion: no-preference), so reduced motion never
 * even arms (late-hydrated rows included). `none` has no rules: it IS the
 * static rendering, and every framework degrades to it. */

[data-sk] {
  --sk-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  /* The intensity bridge — generated catalog rules read the kit-neutral
   * --sk-* vars; only this block reads the kit dials. */
  --sk-anim-intensity: var(--kit-anim-intensity);
  --sk-tone-intensity: var(--kit-tone-intensity);
  --sk-shape-intensity: var(--kit-shape-intensity);
  --sk-border-intensity: var(--kit-border-intensity);
  --sk-density-intensity: var(--kit-density-intensity);
  /* The per-half tone dials (see the kit block): a tonality's `filter` step
   * composes from the first, its `wash` step from the second, so a look can
   * hold a bright photograph AND a legible overlay. */
  --sk-tone-filter-intensity: var(--kit-tone-filter-intensity);
  --sk-tone-wash-intensity: var(--kit-tone-wash-intensity);
  /* The cascade beat resolved to a length the engine's stamped delays read
   * (`calc(<slot> * var(--sk-cascade-beat))`): the dial × 1ms ÷ the
   * intensity's magnitude, so one dial re-paces pace AND phrasing. */
  --sk-cascade-beat: calc(
    var(--kit-cascade-beat) * 1ms / max(abs(var(--sk-anim-intensity, 1)), 0.1)
  );
}

@media (prefers-reduced-motion: no-preference) {
  /* Revealed. The generated ARMED rules are appended AFTER this sheet, so
   * this must win by SPECIFICITY, not order: the generator wraps its root
   * delegation and inner target in :where(), holding every armed rule below
   * this one. (A group container is authored statically, so a root with one
   * delegates from FIRST PAINT — no arm-then-unarm flicker.) A CASCADE
   * unit's `.sk-in` reveals its CHILDREN, each riding its own stamped
   * --sk-reveal-delay. */
  html[data-sk-js] [data-anim].sk-in,
  html[data-sk-js] [data-anim] [data-sk-reveal].sk-in,
  html[data-sk-js] [data-anim] [data-sk-cascade].sk-in > * {
    opacity: 1;
    transform: none;
  }
}

/* ── PAGE TRANSITIONS — the site moves as one document ──────────────────
 * Cross-document view transitions give every page change a soft lift, and
 * the CHROME HOLDS STILL — header and footer carry view-transition-names,
 * so they stay pinned while the content swaps beneath them. Pure CSS: no
 * script, so scripts-off pages keep the lift, and reduced motion collapses
 * the swap to an instant (also neutralizing the UA's default crossfade).
 * The ease literals match --sk-ease-out — a ::view-transition
 * pseudo-element sits on the document root, outside the [data-sk] token
 * scope, so the token cannot flow here. */
@view-transition {
  navigation: auto;
}
.sk-header {
  view-transition-name: sk-header;
}
.sk-footer {
  view-transition-name: sk-footer;
}
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) {
    animation: sk-vt-out 0.26s ease both;
  }
  ::view-transition-new(root) {
    animation: sk-vt-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
  }
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root),
  ::view-transition-group(*) {
    animation: none;
  }
}
@keyframes sk-vt-out {
  to {
    opacity: 0;
    transform: translateY(-10px);
  }
}
@keyframes sk-vt-in {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
}

/* ── Section scaffolding — FLOW SPACING ───────────────────
 * Flow gets margins, objects get padding, regions get authored — and
 * PAGE AIR BELONGS TO CONTENT ALONE. A band is one of two kinds, read
 * off its face stamp (data-sk-face — declared by every band template,
 * pinned stamp↔markup by the catalog test) and its tone:
 *
 * - CONTENT (flat + default tone): copy in the page flow. Paints nothing
 *   — the page wears the theme's canvas (the generated theme sheets
 *   carry a body[data-theme] background rule) — pads nothing, and owes
 *   ONE band-pad of margin ABOVE itself: the page's air, keeping copy
 *   off whatever came before.
 * - A THING (everything else stamped): a toned or inset flat band —
 *   background/gutter + padding INSIDE the paint, local facts it owns —
 *   a backdrop (quote: pad is band air INSIDE its photo), a figure or
 *   construct (photo, map, gallery / lockup: flush, the media edge IS
 *   the boundary). THINGS CARRY NO PAGE AIR OF THEIR OWN: their padding
 *   inside their paint is their air, so things DOCK — against each
 *   other, against the header, against the footer (surface band meets
 *   quote meets photo meets footer edge-to-edge; page-colored strips
 *   between painted/flush edges read as cracks in the composition, the
 *   class both live audits caught). A thing takes margin in exactly one
 *   case: AFTER CONTENT (or after a frame row, whose trailing pane may
 *   be content) — copy must never butt an edge.
 * - UNSTAMPED bands (markup the kit never authored): the padded-object
 *   scaffold below and NO margins — the old posture, standalone-safe.
 *
 * Air is SINGLE-SIDED (an element owes air ABOVE itself; the page owes
 * the closing air before the footer, content endings only) so no seam
 * needs simultaneous facts from both sides beyond the one lookup, and
 * no wrapper can hide one — the flex-proof restatement of margin
 * collapsing (the kit's document skeleton is a flex column, where
 * margins never collapse). This scaffold RETIRED the pairwise seam
 * machinery — the four-tone collapse matrix, the frame-row pane
 * restore, the figure rule, the diptych exception, and both row-top
 * crossing matrices (SITE-KIT.md §1, the 2026-08-10 working notes).
 * Every remaining conditional is a STAMP LOOKUP; a rule here reading
 * data-tone beside a sibling combinator means the matrix is growing
 * back.
 *
 * Ranks: content rides :where() to tie the object scaffold at (0,1,0)
 * and win by source order, below every section sheet and the inset axis
 * (0,3,0); the after-content rule sits at (0,2,0), where the frame
 * block's pane-flush rule (also (0,2,0), later) can silence it inside
 * wrappers. */

.sk-section {
  padding-block: var(--sk-band-pad);
  background-color: var(--sk-bg);
}
.sk-section:where(
  [data-sk-face="flat"]:not(
    [data-tone="surface"],
    [data-tone="inverted"],
    [data-tone="accent"],
    [data-inset="page"]
  )
) {
  margin-top: var(--sk-band-pad);
  background-color: transparent;
  padding-block: 0;
}
/* The one lookup: a thing takes page air only after content. A frame ROW
 * is NOT a content predecessor — followers dock a row exactly as they'd
 * dock the thing pane ending it (the casa row-bottom audit: the lockup
 * must touch the quote pane's paint); the copy in a row's content pane
 * protects ITSELF with its own bottom air (the frame block's pane
 * padding), because one follower margin cannot serve two panes' different
 * natures. The after-content margin restates the same value on a content
 * follower — harmless — so the predecessor arm needs no thing/content
 * split. */
:where(
    .sk-section[data-sk-face="flat"]:not(
        [data-tone="surface"],
        [data-tone="inverted"],
        [data-tone="accent"],
        [data-inset="page"]
      )
  )
  + .sk-section[data-sk-face] {
  margin-top: var(--sk-band-pad);
}
/* The page's closing air — content endings only; a thing (and a row,
 * whose content panes carry their own bottom air) docks the footer. */
body
  > .sk-section:where(
    [data-sk-face="flat"]:not(
      [data-tone="surface"],
      [data-tone="inverted"],
      [data-tone="accent"],
      [data-inset="page"]
    )
  ):where(:has(+ .sk-footer)) {
  margin-bottom: var(--sk-band-pad);
}

.sk-container {
  /* Token-driven measure: 100% minus a gutter each side, capped at 90% of
   * the viewport once there's room (never binding below the 40rem floor,
   * so phones keep full-width-minus-gutter). Both knobs are custom props,
   * so a section can restate ONE value (the header widens to hug the
   * edges) without re-deriving the formula. */
  width: 100%;
  max-width: min(
    100% - var(--sk-container-gutter, var(--kit-space-4)) * 2,
    var(--sk-container-max, max(90%, 40rem))
  );
  margin-inline: auto;
}

/* ── The PAGE FRAME — page-level arrangement vocabulary ──────────────────
 * A PAGE arranges its sections; no section owns the arrangement. The
 * resting arrangement is the plain stack (bare include lines in body flow
 * — no wrapper, no cost). When a page wants bands SIDE BY SIDE it wraps
 * ordinary {.include} lines in a frame ROW; a pane that stacks several
 * bands wraps them in a frame COLUMN. Each pane hosts a WHOLE section —
 * its own tone, gutters, posture, AND its own air (flow spacing: the
 * wrapper is transparent — every pane behaves as if the other weren't
 * there, deriving its air from the row's predecessor) — and the DATA
 * STAYS FLAT:
 * every pane section keeps its own top-level sidecar slice; the wrappers
 * are pure geometry, carrying no scope, no surface, no copy. A row holds
 * ONE or TWO panes (the model's deliberate cap — a third squeezes bands
 * past the width their design survives): equal panes need no attribute
 * (auto-flow columns split evenly); a weighted pair names its split via
 * data-ratio. Below the md rung a row rejoins the plain stack in source
 * order. (This vocabulary REPLACED the splitter composite — a section
 * hosting sections under nested slot keys; SITE-KIT.md, the 2026-07-31
 * page-frame note.)
 *
 * ON A CAPPED PAGE (the page-width axis — a theme's pick or a site's own
 * override) the frame is UNTOUCHED BY CONSTRUCTION: the cap is a
 * CONTENT-RAIL width (the band-gutter home), so rows and panes keep the
 * viewport like every painted edge, and each pane band's own rail formula
 * self-corrects to the gutter (its 100% is the pane's width — the margin
 * term goes negative and the floor wins). Pane rails stay one gutter, the
 * seam stays two, the pane rungs keep measuring the pane's real width.
 * The spec is rendered geometry: tests/e2e/page-cap.e2e.test.ts. */

.sk-frame-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
}
.sk-frame-row[data-ratio="60/40"] {
  grid-template-columns: 3fr 2fr;
}
.sk-frame-row[data-ratio="40/60"] {
  grid-template-columns: 2fr 3fr;
}
/* Panes are grid cells: min-width 0 so a pane section's own media can't
 * blow its column out; the default cell stretch is what makes a shorter
 * band's paint fill the row beside a taller neighbour (the HEIGHT MATCH
 * block below passes that stretch into inner-painted bands and column
 * panes, where the cell stretch alone stops at the box). */
.sk-frame-row > * {
  min-width: 0;
  /* Every pane is a NAMED inline-size container — the cell grid's PANE
   * RUNGS (the fit ladder's @container twin) key on the band's own width,
   * so a squeezed band drops columns instead of keeping its wide posture
   * on a wide screen. Named so the query skips the kit's other containers
   * (.sk-media's size box, the coordinated grid's measuring entries). */
  container: sk-pane / inline-size;
}
/* FLOW SPACING at the frame: THE WRAPPER IS TRANSPARENT TO AIR, both
 * edges. A row carries no margin of its own — each pane derives its OWN
 * air exactly as if it stood alone in the stack, so side-by-side bands
 * behave as if the other weren't there. At the row's TOP: a content pane
 * breathes its unconditional 1× INSIDE its cell (the margin pushes its
 * box down; cell stretch keeps a painted neighbour filling to the row's
 * edges) while a thing pane docks whatever precedes the row — dark quote
 * touching the lockup above, beside a text pane floating 1× below the
 * same edge (the casa row-top audit). At the row's BOTTOM, the mirrored
 * fact with one twist: a follower is ONE element and cannot serve two
 * panes' different natures, so followers DOCK the row (lockup touching
 * the quote pane's paint — the casa row-bottom audit) and a content
 * pane protects its OWN copy with padding-bottom — page-colored air, an
 * unpainted box, visually identical to margin — granted only while the
 * row's follower isn't content (a content follower's unconditional
 * margin already serves both columns; both airs would be 2×).
 *
 * The width regimes:
 * - UNSCOPED first-pane arms — the pane that touches the row's
 *   predecessor at EVERY rung takes the stack's after-content lookup.
 * - ROW MODE (width > 900px) — the sibling-lookup SUPPRESSOR (a pane
 *   beside is not a pane above; without it a thing pane takes 1× from
 *   the content pane NEXT to it), the every-pane re-grants (all panes
 *   sit on the predecessor's edge), and the content panes' guarded
 *   bottom air.
 * - STACK MODE (≤ 900px, the rejoin below) — panes meet as plain
 *   siblings under the scaffold's own seams; the wrapper-crossing
 *   boundaries (a row's neighbours can't see its inner panes) take the
 *   CONSERVATIVE 1× — protecting copy wins when the fact is unknowable
 *   from outside.
 * Inside a COLUMN members are plain siblings — the scaffold rules apply
 * verbatim; the column authors no air. */
:where(
    .sk-section[data-sk-face="flat"]:not(
        [data-tone="surface"],
        [data-tone="inverted"],
        [data-tone="accent"],
        [data-inset="page"]
      )
  )
  + .sk-frame-row
  > .sk-section[data-sk-face]:where(:first-child),
:where(
    .sk-section[data-sk-face="flat"]:not(
        [data-tone="surface"],
        [data-tone="inverted"],
        [data-tone="accent"],
        [data-inset="page"]
      )
  )
  + .sk-frame-row
  > :where(.sk-frame-col:first-child)
  > .sk-section[data-sk-face]:where(:first-child) {
  margin-top: var(--sk-band-pad);
}
@media (width > 900px) {
  /* The sibling-lookup suppressor — (0,3,0), re-granted just below by
   * the predecessor arms (same rank, later in source). Content panes are
   * untouched: their 1× is unconditional whatever precedes. */
  .sk-frame-row
    > .sk-section[data-sk-face]:where(
      :not([data-sk-face="flat"]),
      [data-tone="surface"],
      [data-tone="inverted"],
      [data-tone="accent"],
      [data-inset="page"]
    ) {
    margin-top: 0;
  }
  :where(
      .sk-section[data-sk-face="flat"]:not(
          [data-tone="surface"],
          [data-tone="inverted"],
          [data-tone="accent"],
          [data-inset="page"]
        )
    )
    + .sk-frame-row
    > .sk-section[data-sk-face],
  :where(
      .sk-section[data-sk-face="flat"]:not(
          [data-tone="surface"],
          [data-tone="inverted"],
          [data-tone="accent"],
          [data-inset="page"]
        )
    )
    + .sk-frame-row
    > :where(.sk-frame-col)
    > .sk-section[data-sk-face]:where(:first-child) {
    margin-top: var(--sk-band-pad);
  }
  /* A content pane's own bottom air — self-protection at the row's
   * bottom edge (followers dock the row), granted only while the row's
   * follower isn't content. Unpainted box: the padding reads as page
   * air. (0,2,0) beats the scaffold's content padding-block zero at
   * (0,1,0) by rank. */
  .sk-frame-row:where(
      :not(
        :has(
          + .sk-section[data-sk-face="flat"]:not(
              [data-tone="surface"],
              [data-tone="inverted"],
              [data-tone="accent"],
              [data-inset="page"]
            )
        )
      )
    )
    > .sk-section:where(
      [data-sk-face="flat"]:not(
        [data-tone="surface"],
        [data-tone="inverted"],
        [data-tone="accent"],
        [data-inset="page"]
      )
    ),
  .sk-frame-row:where(
      :not(
        :has(
          + .sk-section[data-sk-face="flat"]:not(
              [data-tone="surface"],
              [data-tone="inverted"],
              [data-tone="accent"],
              [data-inset="page"]
            )
        )
      )
    )
    > :where(.sk-frame-col)
    > .sk-section:where(
      [data-sk-face="flat"]:not(
          [data-tone="surface"],
          [data-tone="inverted"],
          [data-tone="accent"],
          [data-inset="page"]
        ):last-child
    ) {
    padding-bottom: var(--sk-band-pad);
  }
}
/* A column pane — several whole sections sharing one pane, stacked in
 * plain flex order at natural heights; their seams are ordinary sibling
 * seams (the scaffold's own rules), so the column authors no air. */
.sk-frame-col {
  display: flex;
  flex-direction: column;
}
.sk-frame-col > * {
  min-width: 0;
}
/* HEIGHT MATCH — side-by-side panes always meet the row's full height.
 * The cell stretch already sizes every pane BOX to the tallest
 * neighbour; these rules pass the stretch into the two places it
 * stopped at the box (a shorter pane left a page-colored strip under
 * its band beside the taller one):
 * - a band whose PAINT lives on an inner box — a construct's stage
 *   (lockup) or a figure's plate (photo, map, gallery) — kept its
 *   natural height inside the stretched section, so those pane sections
 *   become flex columns and the payload grows (grow-only: the row is
 *   never shorter than the band's own height). Root-painted faces
 *   (toned flats, the quote backdrop) need nothing — the cell stretch
 *   IS their fill.
 * - a COLUMN pane's members stack at natural heights, opening spare
 *   pane height after the last band — the LAST member grows, and when
 *   it's inner-painted the payload rule passes the stretch on.
 * Row mode only: in the stack rejoin every band rests at its natural
 * height. */
@media (width > 900px) {
  .sk-frame-row :where(.sk-section[data-sk-face="construct"], .sk-section[data-sk-face="figure"]) {
    display: flex;
    flex-direction: column;
  }
  .sk-frame-row
    :where(.sk-section[data-sk-face="construct"], .sk-section[data-sk-face="figure"])
    > * {
    flex: 1 0 auto;
  }
  .sk-frame-col > :last-child {
    flex-grow: 1;
  }
}
@media (max-width: 900px) {
  /* md — the row rejoins the plain stack; panes meet as siblings under
   * the scaffold's own seams. The wrapper-crossing BOUNDARIES — a row's
   * neighbours can't see its inner panes from outside — take the
   * conservative 1×: a thing after a row (its last pane may be content),
   * a row's first thing pane after another row, a second-pane column's
   * first band after the content pane above it, and the row before the
   * footer. */
  .sk-frame-row {
    grid-auto-flow: row;
    grid-template-columns: 1fr;
  }
  :where(.sk-frame-row) + .sk-section[data-sk-face],
  :where(.sk-frame-row) + .sk-frame-row > .sk-section[data-sk-face]:where(:first-child),
  :where(.sk-frame-row)
    + .sk-frame-row
    > :where(.sk-frame-col:first-child)
    > .sk-section[data-sk-face]:where(:first-child),
  .sk-section:where(
      [data-sk-face="flat"]:not(
        [data-tone="surface"],
        [data-tone="inverted"],
        [data-tone="accent"],
        [data-inset="page"]
      )
    )
    + :where(.sk-frame-col)
    > .sk-section[data-sk-face]:where(:first-child) {
    margin-top: var(--sk-band-pad);
  }
  body > :where(.sk-frame-row:has(+ .sk-footer)) {
    margin-bottom: var(--sk-band-pad);
  }
}
/* ── Typography ────────────────────────────────────────────────────────────────
 * The kit OWNS its type ramp. Selectors are deliberately scoped under
 * [data-sk] (not bare) so an injected workspace brand.css — which emits BARE
 * [data-text-style] rules at head-end — can never outrank them
 * (SITE-KIT.md, self-containment).
 *
 * SIZES and heading WEIGHTS are never authored here — every step reads the
 * derived type scale and the weight-* / track-* dials (spaced: a bare
 * asterisk-slash would END this comment early and the garbage would eat the
 * next rule), so the whole ramp re-derives from the scale knobs. Only each
 * register's VOICE (face, leading, tracking) is authored per step. */

/* The DISPLAY register — the ramp's statement step, above heading-1: short
 * authored statements (2–6 words) at architectural scale. The crushed
 * leading and tight tracking ARE the register. */
/* The heading family's voice rides the TITLE element tokens: face REF,
 * slant, and caps apply family-wide; LEADING interpolates from the one
 * lead-title dial through structural offsets — display −0.16, h2 +0.07,
 * h3 +0.14, h4 +0.22. Every offset (weight, lead, and the track spread
 * around ×1) multiplies by the RAMP-CONTRAST dial (--kit-ramp-contrast):
 * 1 = the kit's full per-step differentiation, 0 = one uniform family voice
 * (weight-title / lead-title / track-title at every step — the flat
 * editorial ramp). */
[data-sk] [data-text-style="display"] {
  font-family: var(--kit-font-title);
  font-style: var(--kit-slant-title);
  text-transform: var(--kit-caps-title);
  font-size: var(--sk-text-display-size);
  line-height: calc(var(--kit-lead-title) - 0.16 * var(--kit-ramp-contrast));
  font-weight: calc(var(--kit-weight-title) + 50 * var(--kit-ramp-contrast));
  letter-spacing: calc(var(--kit-track-title) * (1 + 0.5 * var(--kit-ramp-contrast)) * 1em);
  margin: 0;
}
[data-sk] [data-text-style="heading-1"] {
  font-family: var(--kit-font-title);
  font-style: var(--kit-slant-title);
  text-transform: var(--kit-caps-title);
  font-size: var(--sk-text-h1-size);
  line-height: var(--kit-lead-title);
  font-weight: var(--kit-weight-title);
  letter-spacing: calc(var(--kit-track-title) * 1em);
  margin: 0;
}
[data-sk] [data-text-style="heading-2"] {
  font-family: var(--kit-font-title);
  font-style: var(--kit-slant-title);
  text-transform: var(--kit-caps-title);
  font-size: var(--sk-text-h2-size);
  line-height: calc(var(--kit-lead-title) + 0.07 * var(--kit-ramp-contrast));
  font-weight: calc(var(--kit-weight-title) - 10 * var(--kit-ramp-contrast));
  letter-spacing: calc(var(--kit-track-title) * (1 - 0.5 * var(--kit-ramp-contrast)) * 1em);
  margin: 0;
}
[data-sk] [data-text-style="heading-3"] {
  font-family: var(--kit-font-title);
  font-style: var(--kit-slant-title);
  text-transform: var(--kit-caps-title);
  font-size: var(--sk-text-h3-size);
  line-height: calc(var(--kit-lead-title) + 0.14 * var(--kit-ramp-contrast));
  font-weight: calc(var(--kit-weight-title) - 20 * var(--kit-ramp-contrast));
  letter-spacing: calc(var(--kit-track-title) * (1 - 0.5 * var(--kit-ramp-contrast)) * 1em);
  margin: 0;
}
[data-sk] [data-text-style="heading-4"] {
  font-family: var(--kit-font-title);
  font-style: var(--kit-slant-title);
  text-transform: var(--kit-caps-title);
  font-size: var(--sk-text-h4-size);
  line-height: calc(var(--kit-lead-title) + 0.22 * var(--kit-ramp-contrast));
  font-weight: calc(var(--kit-weight-title) - 30 * var(--kit-ramp-contrast));
  letter-spacing: calc(var(--kit-track-title) * (1 - 0.5 * var(--kit-ramp-contrast)) * 1em);
  margin: 0;
}
[data-sk] [data-text-style="body"] {
  font-family: var(--kit-font-body);
  font-style: var(--kit-slant-body);
  text-transform: var(--kit-caps-body);
  font-size: var(--kit-type-base);
  line-height: var(--kit-lead-body);
  font-weight: var(--kit-weight-body);
  letter-spacing: calc(var(--kit-track-body) * 1em);
}
/* Inline emphasis is a VOICE SWAP, not just a slant: emphasized words step
 * into the title face, a touch enlarged so the two faces' x-heights meet.
 * A theme's face pair carries through automatically. */
[data-sk] [data-text-style="body"] em {
  font-family: var(--kit-font-title);
  font-size: 1.08em;
}
[data-sk] [data-text-style="caption"] {
  font-size: var(--sk-text-detail-size);
  line-height: 1.5;
  font-weight: var(--kit-weight-detail);
}

/* RICH TEXT — the trope a `richtext` prop's fragment renders inside (the
 * pristine rich-text grammar: p/h1–h3/lists/blockquote/pre/hr + inline
 * marks, substituted `{…|raw}`). The fragment's tags carry no
 * data-text-style stamps, so the ramp re-states itself here PER TAG,
 * token-for-token with the [data-text-style] rules above (heading voice,
 * ramp-contrast modulation and all) — keep the two in lockstep. Rhythm is
 * set on the CHILDREN (entry-detail's own prose rule, hoisted): authored
 * markup carries no classes we control. */
[data-sk] .sk-richtext > * {
  margin: 0;
}
[data-sk] .sk-richtext > * + * {
  margin-top: var(--kit-space-3);
}
/* A heading opens a new movement — one extra step of air above it. */
[data-sk] .sk-richtext > * + :is(h1, h2, h3) {
  margin-top: var(--kit-space-4);
}
[data-sk] .sk-richtext :is(h1, h2, h3) {
  font-family: var(--kit-font-title);
  font-style: var(--kit-slant-title);
  text-transform: var(--kit-caps-title);
}
[data-sk] .sk-richtext h1 {
  font-size: var(--sk-text-h1-size);
  line-height: var(--kit-lead-title);
  font-weight: var(--kit-weight-title);
  letter-spacing: calc(var(--kit-track-title) * 1em);
}
[data-sk] .sk-richtext h2 {
  font-size: var(--sk-text-h2-size);
  line-height: calc(var(--kit-lead-title) + 0.07 * var(--kit-ramp-contrast));
  font-weight: calc(var(--kit-weight-title) - 10 * var(--kit-ramp-contrast));
  letter-spacing: calc(var(--kit-track-title) * (1 - 0.5 * var(--kit-ramp-contrast)) * 1em);
}
[data-sk] .sk-richtext h3 {
  font-size: var(--sk-text-h3-size);
  line-height: calc(var(--kit-lead-title) + 0.14 * var(--kit-ramp-contrast));
  font-weight: calc(var(--kit-weight-title) - 20 * var(--kit-ramp-contrast));
  letter-spacing: calc(var(--kit-track-title) * (1 - 0.5 * var(--kit-ramp-contrast)) * 1em);
}
[data-sk] .sk-richtext :is(ul, ol) {
  padding-inline-start: var(--kit-space-4);
}
[data-sk] .sk-richtext blockquote {
  padding-inline-start: var(--kit-space-4);
  border-inline-start: var(--sk-border-width) solid var(--sk-accent);
  color: var(--sk-ink-muted);
}
[data-sk] .sk-richtext hr {
  border: 0;
  border-top: var(--sk-border-width) solid var(--sk-stroke);
}
[data-sk] .sk-richtext :is(pre, code) {
  font-family: var(--kit-font-mono);
  font-size: calc(var(--sk-text-detail-size) * 0.88);
}
[data-sk] .sk-richtext pre {
  overflow-x: auto;
}
[data-sk] .sk-richtext img {
  max-width: 100%;
  height: auto;
  border-radius: var(--sk-media-radius);
}
[data-sk] .sk-richtext a {
  color: inherit;
  text-underline-offset: 0.2em;
}

/* ── Shared vocabulary ──────────────────────────────────────── */

/* Scoped under [data-sk] deliberately: a composed eyebrow also carries
 * data-text-style="caption", and the caption ramp step matches at (0,2,0) —
 * the eyebrow's OWN voice must win that tie, which this rule does by source
 * order. */
[data-sk] .sk-eyebrow {
  margin: 0 0 var(--kit-space-3);
  font-family: var(--kit-font-eyebrow);
  font-style: var(--kit-slant-eyebrow);
  line-height: var(--kit-lead-eyebrow);
  text-transform: var(--kit-caps-eyebrow);
  letter-spacing: calc(var(--kit-track-eyebrow) * 1em);
  font-size: var(--sk-text-eyebrow-size);
  font-weight: var(--kit-weight-eyebrow);
  border-width: var(--kit-edge-eyebrow);
  border-style: var(--kit-edge-style-eyebrow);
  border-color: currentcolor;
  border-radius: var(--kit-round-eyebrow);
  padding: var(--kit-pad-eyebrow);
  color: var(--sk-accent);
}

/* The DETAIL label trope — the kit's quiet SANS label voice: row metas
 * (a journal dateline), catalog numbers, list furniture. The detail level
 * in tracked caps and MUTED ink — quieter than the accent eyebrow (which
 * kicks a section), warmer than the mono micro (which labels technically).
 * Voice from the detail dials, so a theme softens row furniture without
 * touching its eyebrows. */
.sk-detail {
  margin: 0;
  font-family: var(--kit-font-sans);
  font-size: var(--sk-text-detail-size);
  font-weight: var(--kit-weight-detail);
  letter-spacing: calc(var(--kit-track-detail) * 1em);
  text-transform: var(--kit-caps-eyebrow);
  line-height: 1.5;
  color: var(--sk-ink-muted);
}

/* Hairline rules as STRUCTURE. Both ride --sk-border-width × --sk-stroke,
 * so "ruled vs borderless" stays a BOARD decision (border-width 0 silences
 * every decorative rule site-wide). Decorative by definition: functional
 * borders (inputs, buttons) stay on --sk-border-width-min and never ride
 * these.
 *
 *   .sk-ruled           the ruled CONTAINER: every direct child opens with a
 *                       top hairline + block padding (tune with
 *                       --sk-ruled-pad; 0 when rows pad themselves, e.g. the
 *                       FAQ's <details>). data-cap closes the last row with
 *                       a terminal rule. Narrow-viewport behavior belongs
 *                       to the section. */
.sk-ruled > * {
  border-top: var(--sk-border-width) solid var(--sk-stroke);
  /* Row breathing is the air-row DIAL (resolved in the token block) — a
   * container tunes it only by re-declaring --sk-ruled-pad (e.g. 0 when
   * rows pad themselves). */
  padding-block: var(--sk-ruled-pad);
  margin: 0;
}
.sk-ruled[data-cap] > :last-child {
  border-bottom: var(--sk-border-width) solid var(--sk-stroke);
}

/* ── The CELL GRID — the shared count-aware FIT LADDER ─────────────────
 * ONE grid for every "cells in a band" section (features-grid, product-grid,
 * team, pricing, stats, process, article-list, menu — and the multicol twin
 * for testimonials' wall). It used to be hand-copied per sheet, which is how
 * ladders drift; now the vocabulary lives here and a section contributes
 * only its stamps and its tuning.
 *
 * THE DIAL IS A CEILING, NOT A PROMISE. Four caps meet, the smallest wins:
 *   • the CELL COUNT — `--sk-cell-count`, stamped inline at compose time
 *     from the list length ({rows|count}), so fewer cells than the dial
 *     still span the full row;
 *   • the DIAL — `--sk-columns`, stamped from the section's `columns` prop
 *     (a section without a dial stamps its design cap);
 *   • what actually FITS — `--sk-columns-fit`, the ladder below. Each rung
 *     is a ceiling on the dial, never a floor: a section set to 2 stays 2
 *     all the way down, and one set to 6 steps 4 → 3 → 2 → 1 as the band
 *     narrows. Rungs are measure-driven — the width where a column gets too
 *     narrow for the type ramp to read — not device names.
 *   • what the PANE allows — `--sk-pane-fit`, the same rungs re-spoken as
 *     @container queries on the page frame's sk-pane container (the pane
 *     rungs, after the media ladder below): inside a frame pane a band
 *     renders at a FRACTION of the viewport, so the media rungs read the
 *     wrong measure. Outside a pane no sk-pane container exists, the
 *     queries never match, and the cap rests on its neutral fallback
 *     (the dial).
 *
 * A section TIGHTENS the ladder by re-declaring `--sk-columns-fit` at one
 * of the sanctioned rungs in its own sheet (a menu's name-to-price row
 * wants a wider measure than a stat figure); it NEVER hard-sets the track
 * count inside a media query — that overrides the dial instead of capping
 * it, so a 2-up
 * and a 6-up collapse identically (the old shape this replaced; a kit test
 * rejects it in every sheet).
 *
 *   .sk-cell-grid   tracks from the four caps + the kit cell gap.
 *   .sk-cell-flow   the MULTICOL twin (masonry walls): same dial, same
 *                   ladder, column-count instead of tracks — no count cap,
 *                   because multicol balances short rosters itself.
 */
.sk-cell-grid {
  display: grid;
  --sk-columns-fit: var(--sk-columns, 3);
  --sk-active-columns: min(
    var(--sk-cell-count, var(--sk-columns, 3)),
    var(--sk-columns, 3),
    var(--sk-columns-fit),
    var(--sk-pane-fit, var(--sk-columns, 3))
  );
  grid-template-columns: repeat(var(--sk-active-columns), minmax(0, 1fr));
  gap: var(--kit-space-5) var(--kit-space-4);
}
.sk-cell-flow {
  --sk-columns-fit: var(--sk-columns, 3);
  column-count: min(
    var(--sk-columns, 3),
    var(--sk-columns-fit),
    var(--sk-pane-fit, var(--sk-columns, 3))
  );
  column-gap: var(--kit-space-4);
}
@media (max-width: 1440px) {
  .sk-cell-grid,
  .sk-cell-flow {
    --sk-columns-fit: 4;
  }
}
@media (max-width: 1200px) {
  .sk-cell-grid,
  .sk-cell-flow {
    --sk-columns-fit: 3;
  }
}
@media (max-width: 900px) {
  .sk-cell-grid,
  .sk-cell-flow {
    --sk-columns-fit: 2;
  }
  .sk-cell-grid {
    gap: var(--kit-space-4);
  }
}
@media (max-width: 560px) {
  .sk-cell-grid,
  .sk-cell-flow {
    --sk-columns-fit: 1;
  }
}
/* THE PANE RUNGS — the same ladder keyed on the band's OWN width. Inside a
 * page-frame pane (the sk-pane container, page frame above) the band is a
 * fraction of the viewport and the media rungs read the wrong measure — a
 * squeezed stats strip kept three display-scale figures and the numerals
 * overlapped. The pane cap moves as a FOURTH value in the min() rather
 * than re-declaring --sk-columns-fit, so min() settles it: a section's
 * own (0,2,0) fit tighten can't out-cascade the pane's ceiling. The rung
 * widths are the media ladder's own — for a full-width single-pane row
 * both ladders resolve identically. */
@container sk-pane (max-width: 1440px) {
  .sk-cell-grid,
  .sk-cell-flow {
    --sk-pane-fit: 4;
  }
}
@container sk-pane (max-width: 1200px) {
  .sk-cell-grid,
  .sk-cell-flow {
    --sk-pane-fit: 3;
  }
}
@container sk-pane (max-width: 900px) {
  .sk-cell-grid,
  .sk-cell-flow {
    --sk-pane-fit: 2;
  }
  .sk-cell-grid {
    gap: var(--kit-space-4);
  }
}
@container sk-pane (max-width: 560px) {
  .sk-cell-grid,
  .sk-cell-flow {
    --sk-pane-fit: 1;
  }
}

/* ── THE BAND GUTTER — one home ──────────────────────────────
 * Every content band's inner column stands on the page's ONE horizontal
 * rail (--sk-page-pad-x — the same edge the chrome bars use). Seventeen
 * sheets hand-copied this single declaration (five with the same "band
 * owns its inset" comment); now a new section adds its arm HERE. Every
 * arm is (0,1,0), the sheets' old rank, so a section's own override (the
 * coverflow marquee's flush lanes at (0,2,0)) still wins from its sheet.
 *
 * THE PAGE CAP lives in this padding — the page-width axis is a
 * CONTENT-RAIL width, never a composition box: a capped page's body
 * carries only the LENGTH (--sk-page-max, the theme sheet's page rule /
 * the dress override's), the band's PAINT keeps the viewport, and this
 * rail grows from the gutter to the centering margin the moment the band
 * is wider than the cap. The % resolves PER BAND (the max()'s use site),
 * which is what keeps frame panes safe with no pane machinery: a pane
 * band's 100% is its own width, the margin term goes negative, and the
 * gutter floor wins. The 200vw fallback is the beyond-any-viewport
 * sentinel — an uncapped page rests exactly on the gutter (and full's
 * override EMITS 200vw to cancel a capped theme). The same formula, on
 * their own gutter floors: the chrome bars (site-header/site-footer
 * sheets), the inset axis, and the lockup's no-photo rail below. */
.sk-article-list-inner,
.sk-blog-list-inner,
.sk-coverflow-inner,
.sk-entry-detail-inner,
.sk-faq-inner,
.sk-features-grid-inner,
.sk-form-inner,
.sk-gallery-wall-inner,
.sk-hours-location-inner,
.sk-logo-wall-inner,
.sk-menu-inner,
.sk-pricing-inner,
.sk-product-grid-inner,
.sk-quote-inner,
.sk-stats-inner,
.sk-testimonials-inner,
.sk-text-inner {
  padding-inline: max(var(--sk-page-pad-x), calc((100% - var(--sk-page-max, 200vw)) / 2));
}

/* ── THE FLUSH-CONSTRUCT RAIL — the page cap on bleed media ──────────
 * The constructs whose MEDIA respects the page width (the human's roster:
 * the accordion gallery, the slideshow — gallery-wall already rides the
 * band-gutter home above through its inner). Same per-band % as the rail
 * home, but with a ZERO floor instead of the gutter: at rest these bleed —
 * below the cap and on uncapped pages the term is negative and the flush
 * posture stays byte-identical — and on a capped page the whole stage
 * pulls in to the content rail, the canvas showing in the margins. An
 * authored data-inset="page" still wins (the axis's (0,3,0) over this
 * (0,2,0)) — an inset construct reads the full inset rail, gutter floor
 * included. Photo, map, coverflow, and image-trail stay full-bleed on
 * purpose: a photo band IS the bleed. */
.sk-section[data-sk="gallery"],
.sk-section[data-sk="slideshow"] {
  padding-inline: max(0px, calc((100% - var(--sk-page-max, 200vw)) / 2));
}

/* ── THE CELL LINK — one home ─────────────────────────────────────
 * A media tile that IS an anchor: block posture, no underline, ambient
 * ink, and the kit's focus ring. Arms comma-listed verbatim (each keeps
 * its (0,1,0)/(0,1,1) rank). The ring offset is a seam: coverflow pulls
 * it INSIDE (-2px — its cells sit flush in a clipped lane, an outside
 * ring would be cropped); the gallery's own over-ink ring (gallery.css)
 * is deliberately different and stays local. */
.sk-gallery-wall-cell,
.sk-collection-tile,
.sk-coverflow-cell {
  display: block;
  position: relative;
  text-decoration: none;
  color: inherit;
}
.sk-gallery-wall-cell:focus-visible,
.sk-collection-tile:focus-visible,
.sk-coverflow-cell:focus-visible,
.sk-newsletter-signup-close:focus-visible {
  outline: 2px solid var(--sk-accent);
  outline-offset: var(--sk-cell-ring-offset, 2px);
}

/* ── THE CARD SURFACE — one home ─────────────────────────────────
 * The `data-card="true"` panel skeleton — wash fill, hairline, card radius,
 * inset — spelled ONCE (it was hand-copied in eight sheets and had already
 * drifted: three copies lost their ink). Consumers are comma-listed with
 * their own sheets' selectors (each arm keeps its own rank — the on-media
 * contract's pattern); every sheet keeps only its own geometry extras
 * beside a pointer comment. The inset is the --sk-card-pad seam (default
 * space-5; testimonials/menu take space-4, the faq zeroes it and pads its
 * q/a rows instead). Two deliberate non-verbatim arms:
 *   — features-grid's card opt-in carries its :has() content scope (the
 *     chrome is for text+image lockup entries only).
 *   — stats rides `… .sk-stats-list .sk-stats-figure` — one rank UP from
 *     its old spelling, because its sheet's 560px stacked-rung rules tie
 *     the old (0,3,0) and now load AFTER this home; the bump keeps the
 *     card's fill/border/padding beating the rung without leaning on
 *     source order. */
.sk-features-grid[data-card="true"]
  .sk-feature-entry:has(.sk-lockup-title, .sk-lockup-desc):has(
    .sk-feature-media .sk-image:not([src=""])
  ),
.sk-pricing[data-card="true"] .sk-pricing-tier,
.sk-stats[data-card="true"] .sk-stats-list .sk-stats-figure,
.sk-testimonials[data-card="true"] .sk-testimonial,
.sk-menu[data-card="true"] .sk-menu-item,
.sk-faq[data-card="true"] .sk-faq-item,
.sk-hours-location[data-card="true"] .sk-hours-location-panes,
.sk-quote[data-card="true"] .sk-quote-inner {
  background-color: var(--sk-card);
  color: var(--sk-ink);
  border: var(--sk-border-width) solid var(--sk-stroke);
  border-radius: var(--sk-card-radius);
  padding: var(--sk-card-pad, var(--kit-space-5));
}

/* The announcement pill — a quiet hairline capsule with an accent dot, for
 * kickers above a headline. Sits where an eyebrow would (first in a lockup
 * box). */
.sk-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  margin: 0;
  padding: var(--kit-pad-badge);
  border-width: var(--kit-edge-badge);
  border-style: var(--kit-edge-style-badge);
  border-color: var(--sk-stroke);
  border-radius: var(--kit-round-badge);
  font-family: var(--kit-font-badge);
  font-style: var(--kit-slant-badge);
  line-height: var(--kit-lead-badge);
  text-transform: var(--kit-caps-badge);
  font-size: var(--sk-text-badge-size);
  font-weight: var(--kit-weight-badge);
  letter-spacing: calc(var(--kit-track-badge) * 1em);
  color: var(--sk-ink-muted);
}
.sk-badge::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--sk-accent);
}

/* ── The ACTION part ──────────────────────────────────────────────────────────────────
 * The button registers, a modeled sub-component (SITE-KIT.md
 * "Sub-components", lint.ts part grammar) — EVERY action affordance is one
 * of these three, wherever it renders:
 *
 * .sk-btn          the filled PRIMARY — rides the --sk-btn pair (tones
 *                  remap it)
 * .sk-btn-outline  the outline SECONDARY — inherits ink; never stacked with
 *                  .sk-btn (one register class per action, lint holds it)
 * .sk-link         the quiet link register (defined with the data-action
 *                  remap below — one look, one place)
 *
 * data-size="s" is the COMPACT register: structure, not a knob — the inset
 * derives from the button's own type size (em), so every voice dial keeps
 * flowing through. Voice and shape read the element grid dials
 * (--kit-*-button); no section restyles a button in its own CSS. */
.sk-btn,
.sk-btn-outline {
  display: inline-block;
  padding: var(--kit-pad-button);
  border-radius: var(--kit-round-button);
  border-width: var(--kit-edge-button);
  border-style: var(--kit-edge-style-button);
  font-family: var(--kit-font-button);
  font-style: var(--kit-slant-button);
  font-size: var(--sk-text-btn-size);
  font-weight: var(--kit-weight-button);
  letter-spacing: calc(var(--kit-track-button) * 1em);
  text-transform: var(--kit-caps-button);
  line-height: var(--kit-lead-button);
  text-decoration: none;
  cursor: pointer;
  transition:
    opacity 140ms ease,
    background-color 140ms ease,
    color 140ms ease,
    border-color 140ms ease;
}
/* Hover INVERTS the register: the filled primary empties to its outline
 * posture, the outline secondary fills to the primary pair. Both swaps ride
 * the guaranteed token pairs, so every tone and theme keeps a readable
 * hover for free. */
.sk-btn {
  background: var(--sk-btn-bg);
  color: var(--sk-btn-ink);
  border-color: var(--sk-btn-bg);
}
.sk-btn:hover {
  background: transparent;
  color: var(--sk-ink);
  border-color: currentColor;
}
.sk-btn-outline {
  background: transparent;
  color: var(--sk-ink);
  border-color: var(--sk-ink);
}
.sk-btn-outline:hover {
  background: var(--sk-btn-bg);
  color: var(--sk-btn-ink);
  border-color: var(--sk-btn-bg);
}
/* THE FOCUS REGISTER — the kit's ONE outline for every borderless
 * affordance (buttons, links, icons, nav, subscribe, sitemap). Boxed
 * controls opt out (their border IS the focus paint — see the form
 * baseline); the native check keeps its own tighter offset. */
.sk-btn:focus-visible,
.sk-btn-outline:focus-visible,
.sk-link:focus-visible,
.sk-icon:focus-visible,
.sk-el-nav-link:focus-visible,
.sk-el-subscribe-btn:focus-visible,
.sk-el-sitemap-link:focus-visible {
  outline: 2px solid var(--sk-accent);
  outline-offset: 2px;
}
.sk-btn[data-size="s"],
.sk-btn-outline[data-size="s"] {
  padding: 0.5em 1.2em;
}
/* A CTA's small trailing glyph ("Contact Sales →") — spaced off the label,
 * inheriting the register's ink. */
.sk-el-btn-glyph {
  display: inline-block;
  margin-left: 0.35em;
  line-height: 1;
}
.sk-el-btn-glyph > svg {
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
}

/* Form baseline (contact-form and friends build on these) — and the FORM
 * PART vocabulary (form-parts/<kind>/, the kit's second element family):
 * every part composes from these atoms, so a hand-authored form and a
 * zone-staged one read identically.
 *
 * .sk-form-part      the family marker every part's root carries
 * .sk-field          one labeled control (label + control + hint + error)
 * .sk-field-label    the label — the quiet DETAIL voice, like row metas
 * .sk-field-req      the required mark beside a label
 * .sk-field-hint     the helper line under a control (muted)
 * .sk-field-error    the ERROR BANNER's animated housing — a grid row
 *                    (0fr ↔ 1fr) + opacity that tweens the banner open and
 *                    closed as the part's data-invalid state flips (the
 *                    pristine DialogField gesture, authored kit-natively)
 * .sk-field-error-inner   the overflow-hidden middle the grid row squeezes
 * .sk-field-error-banner  the banner itself — a danger-filled strip with an
 *                    × glyph and the message, ATTACHED under a boxed control
 *                    (the control squares its bottom corners, the banner
 *                    its top — one shape); free-standing (a choice group)
 *                    it keeps its full radius. Painted by the danger pair.
 * .sk-select(-wrap)  the dropdown — input chrome + the kit's own caret
 * .sk-date-wrap      the date control — input chrome + the kit's own
 *                    calendar button (.sk-date-btn) and popover panel
 *                    (.sk-date-cal, built by base.js job 2c); the UA's
 *                    picker indicator hides under html[data-sk-js]
 * .sk-file           the upload control — input chrome, quiet button
 * .sk-choice-list    a fieldset of checkbox/radio rows (.sk-check each)
 * .sk-check          one checkbox/radio row — control + label
 * .sk-form-note      an intro/legend copy block between fields
 * .sk-form-success   the post-submit confirmation — an accent-washed
 *                    message row (optional catalog glyph beside or above)
 * .sk-form-error     the submission-failure notice — success's danger twin,
 *                    for WHOLE-FORM failures (an unwired action, a backend
 *                    error naming no field); field-level rejections keep
 *                    the attached .sk-field-error banner
 * .sk-form-outcome   the engine-outcome housing (+ -inner) — base.js seats
 *                    engine-built success/error parts here so insertion
 *                    tweens open with the field-error gesture
 * .sk-form-actions   the submit row — a filled button + a quiet note */
.sk-field {
  display: block;
  margin-bottom: var(--kit-space-3);
}

/* A form's field container (the `form` band's fields)
 * lays out on a TWO-COLUMN grid: a field spans the whole row by default, and
 * ADJACENT stackable fields (data-sk-stack — a name's first/last, a city/state
 * pair) pack side by side. Adjacency is the sibling selectors' business — a
 * LONE stackable has no stackable neighbor, so it keeps the full row and never
 * strands a half-empty one. The grid gap owns the rhythm, so parts drop their
 * own margin. */
[data-sk-form-fields] {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--kit-space-4);
  align-items: end;
}
[data-sk-form-fields] > .sk-form-part {
  grid-column: 1 / -1;
  margin-bottom: 0;
}
[data-sk-form-fields] > .sk-form-part[data-sk-stack]:has(+ .sk-form-part[data-sk-stack]),
[data-sk-form-fields] > .sk-form-part[data-sk-stack] + .sk-form-part[data-sk-stack] {
  grid-column: auto / span 1;
}
@media (max-width: 560px) {
  [data-sk-form-fields] {
    grid-template-columns: 1fr;
  }
}
.sk-field-label {
  display: block;
  margin-bottom: var(--kit-space-1);
  /* The DETAIL register — the same tracked, cased, muted label voice every
   * other small label in the kit rides (a visit-detail dt, a row meta, a
   * caption). It was the one label set in bold sentence case, which read as a
   * heavier, blunter thing than the labels beside it. */
  font-size: var(--sk-text-detail-size);
  font-weight: var(--kit-weight-eyebrow);
  letter-spacing: calc(var(--kit-track-eyebrow) * 1em);
  text-transform: var(--kit-caps-eyebrow);
  color: var(--sk-ink-muted);
}
.sk-field-req {
  color: var(--sk-accent);
  margin-left: 0.25em;
}
/* The shared BOXED-CONTROL chrome — one rule for the field paint (ink,
 * fill, hairline, the control radius); each control keeps only its own
 * padding/appearance below. The transition is the error-state tween: the
 * danger recolor and corner squaring ride the kit's hover clock so the
 * banner's grid reveal and the control's docking read as ONE gesture
 * (border-WIDTH stays instant — focus must never smear). */
.sk-input,
.sk-textarea,
.sk-select,
.sk-file {
  /* Block, not the default inline: an inline control baseline-aligns in its
   * line box, which reserves descender space BELOW it — a visible sliver of
   * page between the control and its attached error banner (worst on
   * textarea). Block-level kills the line box entirely. */
  display: block;
  width: 100%;
  font: inherit;
  color: var(--sk-ink);
  background: var(--sk-field);
  border: var(--sk-border-width-min) solid
    color-mix(in srgb, var(--sk-ink-muted) calc(var(--kit-control-stroke-mix) * 1%), var(--sk-bg));
  border-radius: var(--sk-control-radius);
  transition:
    border-color 140ms ease,
    border-radius 150ms ease;
}
.sk-input,
.sk-textarea {
  padding: 0.7rem 0.9rem;
}
.sk-input::placeholder,
.sk-textarea::placeholder {
  color: var(--sk-ink-muted);
}
/* FOCUS on a BOXED control recolors ITS OWN BORDER — never an outline. A
 * boxed control already wears a border; adding an outline on top draws a
 * double ring. The border steps to the accent, thickened to 2px INWARD
 * (the growth comes out of the padding via --sk-focus-grow, so the box
 * and its text hold perfectly still); the outline is suppressed for
 * exactly these controls — outline focus remains the rule for everything
 * borderless (buttons, links, checks). */
:is(.sk-input, .sk-textarea, .sk-select, .sk-file):focus-visible {
  --sk-focus-grow: calc(max(var(--kit-border-width), 2px) - var(--sk-border-width-min));
  outline: none;
  border-color: var(--sk-accent);
  border-width: max(var(--kit-border-width), 2px);
}
:is(.sk-input, .sk-textarea):focus-visible {
  padding: calc(0.7rem - var(--sk-focus-grow)) calc(0.9rem - var(--sk-focus-grow));
}
.sk-select:focus-visible {
  padding: calc(0.7rem - var(--sk-focus-grow)) calc(2.4rem - var(--sk-focus-grow))
    calc(0.7rem - var(--sk-focus-grow)) calc(0.9rem - var(--sk-focus-grow));
}
.sk-file:focus-visible {
  padding: calc(0.55rem - var(--sk-focus-grow)) calc(0.9rem - var(--sk-focus-grow));
}
.sk-field-hint {
  margin: var(--kit-space-1) 0 0;
  font-size: var(--sk-text-detail-size);
  color: var(--sk-ink-muted);
}

/* The SELECT — the input chrome with the kit's own caret (appearance:none
 * so the UA's arrow never fights the theme). */
.sk-select-wrap {
  position: relative;
  display: block;
}
.sk-select {
  appearance: none;
  padding: 0.7rem 2.4rem 0.7rem 0.9rem;
}
.sk-select-caret {
  position: absolute;
  right: 0.9rem;
  top: 50%;
  translate: 0 -50%;
  pointer-events: none;
  color: var(--sk-ink-muted);
}

/* The FILE control — input chrome; the picker button rides the detail
 * voice so it stays quieter than the form's real submit. */
.sk-file {
  padding: 0.55rem 0.9rem;
  font-size: var(--sk-text-detail-size);
}
.sk-file::file-selector-button {
  margin-right: 0.9em;
  padding: 0.25em 0.9em;
  font: inherit;
  font-size: var(--sk-text-detail-size);
  color: var(--sk-ink);
  background: none;
  border: var(--sk-border-width-min) solid var(--sk-ink-muted);
  border-radius: var(--sk-control-radius);
  cursor: pointer;
}

/* The DATE control — input chrome plus the kit's OWN calendar. The UA's
 * dropdown calendar is never kit design (the validation-bubble doctrine,
 * job 2b's sibling): under html[data-sk-js] the native picker indicator
 * hides (the -webkit- pseudo is the seam WebKit/Blink give; elsewhere the
 * native icon simply remains) and .sk-date-btn takes its place, opening
 * the engine-built .sk-date-cal (base.js job 2c) — the nav panel's
 * popover posture (page fill, hairline, panel radius + shadow) over a
 * seven-column month grid in the detail voice. Scripts-off the button
 * never shows and the native picker stays — structural degradation, like
 * every enhancement. */
.sk-date-wrap {
  position: relative;
  display: block;
}
.sk-date-btn {
  display: none;
}
html[data-sk-js] .sk-date-input {
  padding-right: 2.6rem;
}
html[data-sk-js] .sk-date-input:focus-visible {
  padding: calc(0.7rem - var(--sk-focus-grow)) calc(2.6rem - var(--sk-focus-grow))
    calc(0.7rem - var(--sk-focus-grow)) calc(0.9rem - var(--sk-focus-grow));
}
html[data-sk-js] .sk-date-input::-webkit-calendar-picker-indicator {
  display: none;
}
html[data-sk-js] .sk-date-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  right: 0.45rem;
  top: 50%;
  translate: 0 -50%;
  width: 2em;
  height: 2em;
  padding: 0;
  border: 0;
  /* The FIELD fill, not transparent: engines with no indicator seam
   * (Firefox) keep their native icon in this corner — the button sits
   * over it and must cover, never blend. */
  background: var(--sk-field);
  color: var(--sk-ink-muted);
  border-radius: var(--sk-control-radius);
  cursor: pointer;
}
html[data-sk-js] .sk-date-btn:hover {
  color: var(--sk-ink);
  background: var(--sk-surface);
}
.sk-date-cal {
  position: absolute;
  z-index: 40;
  top: calc(100% + var(--kit-space-1));
  right: 0;
  min-width: 17rem;
  padding: calc(var(--sk-panel-pad) * 0.75);
  background: var(--sk-bg);
  color: var(--sk-ink);
  border: var(--sk-border-width-min) solid var(--sk-stroke);
  border-radius: var(--sk-panel-radius);
  box-shadow: var(--sk-panel-shadow);
}
.sk-date-cal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--kit-space-2);
  margin-bottom: var(--kit-space-2);
}
.sk-date-cal-label {
  font-size: var(--sk-text-detail-size);
  font-weight: 600;
}
.sk-date-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2em;
  height: 2em;
  padding: 0;
  border: 0;
  background: none;
  color: var(--sk-ink-muted);
  border-radius: var(--sk-control-radius);
  cursor: pointer;
}
.sk-date-nav:hover {
  color: var(--sk-ink);
  background: var(--sk-surface);
}
.sk-date-btn:focus-visible,
.sk-date-nav:focus-visible,
.sk-date-day:focus-visible {
  outline: 2px solid var(--sk-accent);
  outline-offset: 1px;
}
.sk-date-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.sk-date-dow {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 2em;
  font-size: var(--sk-text-detail-size);
  font-weight: var(--kit-weight-detail);
  color: var(--sk-ink-muted);
}
.sk-date-day {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 2.2em;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--sk-text-detail-size);
  color: var(--sk-ink);
  border-radius: var(--sk-control-radius);
  cursor: pointer;
}
.sk-date-day:hover {
  background: var(--sk-surface);
}
.sk-date-day[data-today="true"] {
  box-shadow: inset 0 0 0 var(--sk-border-width-min) var(--sk-stroke);
}
/* Selected-day ink is --sk-btn-ink, not --sk-bg — same doctrine as the
 * badge chips: the §4 ink remaps move the accent fill but not --sk-bg. */
.sk-date-day[data-selected="true"] {
  background: var(--sk-accent);
  color: var(--sk-btn-ink);
  font-weight: 600;
}
.sk-date-day:disabled {
  color: var(--sk-ink-muted);
  opacity: 0.45;
  cursor: default;
  background: none;
}

/* CHECKBOX / RADIO rows — the native control on the accent, a baseline-
 * aligned label. A group is a chromeless fieldset (.sk-choice-list) whose
 * legend rides the field-label voice. The reset zeroes only what the UA
 * adds (inline margins, fieldset padding, the border) — the group KEEPS
 * the field rhythm below it (.sk-field's margin-bottom; this rule is
 * later in source, so it must restate it), and the legend sheds its UA
 * padding-inline so the group label aligns with every other label. */
.sk-choice-list {
  margin: 0 0 var(--kit-space-3);
  padding: 0;
  border: 0;
  min-width: 0;
}
.sk-choice-list > legend {
  padding: 0;
}
.sk-choice-list .sk-check + .sk-check {
  margin-top: var(--kit-space-2);
}
.sk-check {
  display: flex;
  align-items: flex-start;
  gap: 0.6em;
  cursor: pointer;
}
.sk-check-input {
  flex: none;
  width: 1.05em;
  height: 1.05em;
  margin: 0.2em 0 0;
  accent-color: var(--sk-accent);
}
.sk-check-input:focus-visible {
  outline: 2px solid var(--sk-accent);
  outline-offset: 1px;
}

/* The ERROR STATE — stamped data-invalid="true" on the part root when its
 * row carries an `error` message (the data-active idiom: the {.exists}
 * gate leaves the attribute EMPTY when clean, so only the ="true" form
 * matches). The control's border turns danger; the banner ATTACHES under
 * a boxed control (shared corner, one shape — the control squares its
 * bottom corners, the banner its top); a group's banner stands alone with
 * its full radius. The message rides the danger pair — fixed near-white
 * ink on the danger fill, readable on every theme. */
/* The boxed controls' danger recolor — the tween lives with the shared
 * chrome above. */
.sk-field[data-invalid="true"] :is(.sk-input, .sk-textarea, .sk-select, .sk-file) {
  border-color: var(--sk-danger);
}
.sk-field[data-invalid="true"] :is(.sk-input, .sk-textarea, .sk-file):has(+ .sk-field-error),
.sk-field[data-invalid="true"] .sk-select-wrap:has(+ .sk-field-error) .sk-select,
.sk-field[data-invalid="true"] .sk-date-wrap:has(+ .sk-field-error) .sk-input {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}
/* The HOUSING animates: a grid row (0fr ↔ 1fr) + opacity tween the banner
 * open and closed without measuring — the pristine DialogField gesture.
 * The open state rides data-invalid on the part root, so a server-rendered
 * error paints open from the first frame (transitions never fire on
 * initial style) while the ENGINE's flips (base.js job 2b) tween both
 * ways. A cleared banner STAYS MOUNTED, its last message intact, so the
 * exit never empties mid-collapse. */
.sk-field-error {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  margin: 0;
  transition:
    grid-template-rows 150ms ease,
    opacity 150ms ease,
    margin-top 150ms ease;
}
.sk-field[data-invalid="true"] .sk-field-error {
  grid-template-rows: 1fr;
  opacity: 1;
  transition:
    grid-template-rows 180ms var(--sk-ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    opacity 180ms var(--sk-ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    margin-top 180ms var(--sk-ease-out, cubic-bezier(0.22, 1, 0.36, 1));
}
.sk-field-error-inner {
  display: block;
  overflow: hidden;
  min-height: 0;
}
.sk-field-error-banner {
  display: flex;
  align-items: center;
  gap: 0.65em;
  padding: 0.6rem 0.9rem;
  background: var(--sk-danger);
  color: var(--sk-danger-ink);
  border-radius: var(--sk-control-radius);
  /* The DETAIL voice — an error message is field furniture (the label's
   * register), never body copy: it must read at a glance without
   * outshouting the control it annotates. */
  font-size: var(--sk-text-detail-size);
  font-weight: 600;
}
.sk-field
  :is(.sk-input, .sk-textarea, .sk-file, .sk-select-wrap, .sk-date-wrap)
  + .sk-field-error
  .sk-field-error-banner {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
/* A free-standing banner (after a choice row — nothing boxed to attach
 * to) breathes off the row instead; the margin is part of the open state
 * (and the transition) so a collapsed banner leaves no phantom gap. */
.sk-field[data-invalid="true"] .sk-check + .sk-field-error {
  margin-top: var(--kit-space-2);
}
.sk-field-error-x {
  flex: none;
  width: 0.8em;
  height: 0.8em;
}

/* The form NOTE — an intro/legend copy block staged between fields. */
.sk-form-note {
  margin: var(--kit-space-2) 0 var(--kit-space-3);
}
.sk-form-note-title {
  margin: 0 0 var(--kit-space-1);
}
.sk-form-note-body {
  margin: 0;
  color: var(--sk-ink-muted);
  max-width: var(--sk-measure);
}

/* The SUCCESS message — the styled post-submit confirmation. Borderless by
 * design (the pristine InlineMessage wash, spoken in kit tokens): the site
 * accent mixed toward the page fills the whole row — no card stroke. The
 * glyph rides the icon catalog (data-icon) tinted with an accent-toward-ink
 * mix, so it reads as a state marker yet stays legible on the wash whatever
 * hue the theme's accent lands on. data-layout: `beside` seats the glyph
 * against the TOP of the message — centered on the first text line, never
 * on the wrapped block (the inline-notice posture); `above` stacks it over
 * the copy at a larger size, everything left-aligned (the macOS dialog
 * posture, flush to the kit's reading edge). */
.sk-form-success {
  --sk-form-success-ink: color-mix(in srgb, var(--sk-accent) 60%, var(--sk-ink));
  display: flex;
  align-items: flex-start;
  gap: var(--kit-space-2);
  padding: 0.8rem 1rem;
  border-radius: var(--sk-control-radius);
  background: color-mix(in srgb, var(--sk-accent) 12%, var(--sk-bg));
}
.sk-form-success-icon {
  flex: none;
  color: var(--sk-form-success-ink);
  /* Centre the glyph on the FIRST text line (half the leading's slack over
   * the icon box), so a wrapping message keeps it pinned to the top. The
   * calc speaks EXPLICIT body metrics — the same pair pinned on the text
   * below — never `1em`: the icon's em is its INHERITED size, while the
   * message once inherited ambient leading, and the two drifted apart on
   * any theme whose context wasn't the body voice. */
  margin-top: calc((var(--kit-type-base) * var(--kit-lead-body) - var(--sk-icon-size)) / 2);
}
.sk-form-success-text {
  margin: 0;
  max-width: var(--sk-measure);
  /* The body voice's GEOMETRY, pinned — the icon calc above assumes this
   * first-line height, so it must be set here, not inherited. */
  font-size: var(--kit-type-base);
  line-height: var(--kit-lead-body);
}
.sk-form-success[data-layout="above"] {
  flex-direction: column;
  gap: var(--kit-space-1);
  padding: var(--kit-space-3) 1rem;
}
.sk-form-success[data-layout="above"] .sk-form-success-icon {
  width: calc(var(--sk-icon-size) * 1.8);
  height: calc(var(--sk-icon-size) * 1.8);
  margin-top: 0;
}

/* The ERROR message — the success row's danger twin: the same borderless
 * inline-notice wash, spoken in the danger channel (the danger fill mixed
 * toward the page; the glyph mixed toward ink so it reads as a state
 * marker on the wash whatever hue the theme's danger lands on). Carries
 * WHOLE-FORM failures — the engine's submit pass (base.js job 2) builds
 * one for an unwired action or a backend error that names no field —
 * while field-level rejections keep the attached .sk-field-error banner. */
.sk-form-error {
  --sk-form-error-ink: color-mix(in srgb, var(--sk-danger) 60%, var(--sk-ink));
  display: flex;
  align-items: flex-start;
  gap: var(--kit-space-2);
  padding: 0.8rem 1rem;
  border-radius: var(--sk-control-radius);
  background: color-mix(in srgb, var(--sk-danger) 12%, var(--sk-bg));
}
.sk-form-error-icon {
  flex: none;
  color: var(--sk-form-error-ink);
  /* Centre the glyph on the FIRST text line — explicit body metrics, never
   * `1em`; see .sk-form-success-icon. */
  margin-top: calc((var(--kit-type-base) * var(--kit-lead-body) - var(--sk-icon-size)) / 2);
}
.sk-form-error-text {
  margin: 0;
  max-width: var(--sk-measure);
  /* Pinned body geometry — the icon calc's assumption; see success twin. */
  font-size: var(--kit-type-base);
  line-height: var(--kit-lead-body);
}
.sk-form-error[data-layout="above"] {
  flex-direction: column;
  gap: var(--kit-space-1);
  padding: var(--kit-space-3) 1rem;
}
.sk-form-error[data-layout="above"] .sk-form-error-icon {
  width: calc(var(--sk-icon-size) * 1.8);
  height: calc(var(--sk-icon-size) * 1.8);
  margin-top: 0;
}

/* The engine-outcome HOUSING — base.js (job 2) seats each engine-built
 * success/error part here so its ARRIVAL animates with the field-error
 * banner's own gesture: the 0fr↔1fr grid row + opacity + margin tween.
 * Transitions never fire on initial style, so an inserted row needs
 * @starting-style to tween open (the nav panel's idiom); an engine
 * without it SNAPS — exactly the prior posture. The housing also carries
 * the outcome's air: an engine part lands after the actions row, OUTSIDE
 * the fields grid, so no grid gap spaces it — the margin opens with the
 * row rather than jumping in ahead of it. Zone-staged parts ride the
 * grid's own gap and never wear this housing. */
.sk-form-outcome {
  display: grid;
  grid-template-rows: 1fr;
  opacity: 1;
  margin-top: var(--kit-space-3);
  transition:
    grid-template-rows 180ms var(--sk-ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    opacity 180ms var(--sk-ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    margin-top 180ms var(--sk-ease-out, cubic-bezier(0.22, 1, 0.36, 1));
}
@starting-style {
  .sk-form-outcome {
    grid-template-rows: 0fr;
    opacity: 0;
    margin-top: 0;
  }
}
.sk-form-outcome-inner {
  overflow: hidden;
  min-height: 0;
}

/* The engine's ANNOUNCE-ONLY posture for a form's [role="status"] line:
 * once an engine-built success part carries the VISIBLE message (base.js
 * job 2), the live region still needs the text to SPEAK it — but hiding it
 * (hidden / display:none) silences a live region, so it clips off-screen
 * instead. */
.sk-announce-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The SUBMIT row — the filled action plus a quiet trailing note. */
.sk-form-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--kit-space-3);
  margin-top: var(--kit-space-1);
}
/* After a fields GRID the actions row rides the grid's own gap: the grid
 * zeroes its parts' margin-bottom, so the space-1 above (sized for flow
 * where the last .sk-field still contributes space-3) would crowd the
 * submit row against the last field while the fields breathe space-4. */
[data-sk-form-fields] + .sk-form-actions {
  margin-top: var(--kit-space-4);
}
.sk-form-actions-note {
  font-size: var(--sk-text-detail-size);
  color: var(--sk-ink-muted);
}

/* ── The PENDING vocabulary — what "not yet" looks like, kit-carried ─────
 * A fetch repeater's skeleton rows ({.exists skeleton} — SPACES.md) paint
 * .sk-skeleton bars — surface tint, kit radius, a quiet pulse — so a
 * loading band is designed on every theme. Sections size their own bars;
 * this atom owns the look and the pulse. The reduced-motion floor at the
 * bottom of this sheet stills it. */
.sk-skeleton {
  display: block;
  background: var(--sk-surface);
  border-radius: var(--sk-radius);
  animation: sk-skeleton-pulse 1.4s ease-in-out infinite;
}
@keyframes sk-skeleton-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.45;
  }
}

/* ── ELEMENT vocabulary (the kit's sub-components) ──────────────────────
 * The chrome element ATOMS (SITE-HEADER-PLAN.md "sub-components"). Each is
 * a closed part: ONE markup source at the kit root
 * (header-footer/<kind>/<kind>.html — lib/load.ts), stable classes here (this
 * block is the styling contract), voice from the dials, paint from the ink
 * vars only — so tones and themes restyle every part for free. Sections
 * render them through typed repeats (`elements` refs); hand-authored
 * copies are a lint error — one source per element.
 *
 * .sk-el-logo   the brand wordmark — the brand voice dials
 * .sk-el-nav    a row of quiet links; .sk-el-nav-link is one link (also the
 *               standalone `link` element), the nav-register voice
 * .sk-icons     a row of small icon affordances (.sk-icon children)
 * .sk-icon      one icon button/anchor — inline SVG glyph riding
 *               currentColor; COLOR MUST INHERIT (a UA <button>/<a> doesn't
 *               inherit color — without this every ink re-point silently
 *               misses the glyph)
 * .sk-icon-badge  the small count bubble on an icon's corner (cart count)
 * .sk-cart      the cart/bag affordance — a MODIFIER on an icon row of ONE
 *               (always rides .sk-icons; lint holds the pairing)
 * .sk-language  the locale/currency picker — the nav-link register's voice
 * .sk-separator a quiet vertical hairline between adjacent elements
 * .sk-textrun   a plain text run (tagline, note) — the detail register
 * .sk-el-subscribe  the inline newsletter capture — an email field and its
 *               quiet submit sharing one hairline (the underline register) */

.sk-el-logo {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  font-family: var(--kit-font-brand);
  font-size: var(--sk-text-brand-size);
  font-weight: var(--kit-weight-brand);
  letter-spacing: calc(var(--kit-track-brand) * 1em);
  text-transform: var(--kit-caps-brand);
  text-decoration: none;
  color: var(--sk-ink);
  white-space: nowrap;
}
/* An inline-svg brand MARK (alone, or beside the wordmark) — a square box
 * on the brand size, inheriting the brand ink via currentColor. */
.sk-el-logo-mark {
  display: inline-flex;
  line-height: 0;
}
.sk-el-logo-mark > svg {
  width: calc(var(--sk-text-brand-size) * 1.15);
  height: calc(var(--sk-text-brand-size) * 1.15);
}

.sk-el-nav {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kit-space-2) var(--kit-space-3);
}
/* THE NAV-REGISTER VOICE — every affordance that speaks the chrome's second
 * voice reads the nav dials from this ONE rule (header/footer links, a drop
 * link's label, the language picker, sitemap links, the menu sheet's rows);
 * each part keeps only its own geometry and ink below. */
:is(
  .sk-el-nav-link,
  .sk-el-nav-drop-label,
  .sk-language,
  .sk-el-sitemap-link,
  .sk-header-sheet-link
) {
  font-size: var(--sk-text-nav-size);
  font-weight: var(--kit-weight-nav);
  letter-spacing: calc(var(--kit-track-nav) * 1em);
  text-transform: var(--kit-caps-nav);
}
.sk-el-nav-link {
  position: relative;
  text-decoration: none;
  color: var(--sk-ink-muted);
  /* SYMMETRIC underline reservation: the hover underline (::after,
   * bottom: 0) needs room below the text, but bottom-only padding made
   * flexbox center the BOX while the text inside rode ~1px high against
   * the logo and icons. The matching top pad keeps the text true-center;
   * the underline's distance from the text is unchanged. */
  padding-block: 2px;
  transition: color 140ms ease;
}
.sk-el-nav-link:hover {
  color: var(--sk-ink);
}
/* The underline DRAWS IN from the left on hover and exits to the right on
 * leave (the transform-origin flip), and holds full-width on the current
 * page. */
.sk-el-nav-link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.35s var(--sk-ease-out);
}
/* A dropdown TRIGGER is not a link — its hover feedback is the caret turn
 * and the opening panel; the link register's draw-in underline would read
 * as a navigable target. Applies to every trigger (nav items' and the
 * standalone dropdown's — one trigger grammar). */
.sk-el-nav-trigger::after {
  content: none;
}
.sk-el-nav-link:hover::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* Current: data-active is the AUTHORED flag; aria-current is the ENGINE's
 * mark (base.js matches each link's clean target against the page's URL —
 * shared-partial chrome can't know which page it's on). Same look either
 * way. */
.sk-el-nav-link[data-active="true"],
.sk-el-nav-link[aria-current="page"] {
  color: var(--sk-ink);
}
.sk-el-nav-link[data-active="true"]::after,
.sk-el-nav-link[aria-current="page"]::after {
  transform: scaleX(1);
}
/* A small LEADING/TRAILING GLYPH beside a nav label — a text/emoji char or
 * a trusted inline svg (✨ Guide me; Contact sales →). Sized to the label. */
.sk-el-nav-glyph {
  display: inline-block;
  margin-right: 0.35em;
  line-height: 1;
}
.sk-el-nav-glyph[data-at="trail"] {
  margin-right: 0;
  margin-left: 0.35em;
}
.sk-el-nav-glyph > svg {
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
}
/* The LEAVES-THE-SITE arrow — stamped after any newWindow link's label
 * (nav links, drop links, sitemap links). Inherits ink, scales with type. */
.sk-extlink {
  width: 0.7em;
  height: 0.7em;
  margin-left: 0.25em;
  vertical-align: baseline;
  opacity: 0.65;
}
/* An in-row DIVIDER — the thin pipe splitting two clusters that live inside
 * a SINGLE nav element (Stripe's "Pricing | ✨ Guide me"). For separators
 * between top-level elements, use the separator element. */
.sk-el-nav-div {
  align-self: stretch;
  width: var(--sk-border-width-min);
  margin-block: 0.25em;
  background: var(--sk-stroke);
}
/* The item badge — a small count/NEW pill riding any nav label. The ink is
 * --sk-btn-ink, not --sk-bg: the pairs match on every tone, but the §4 ink
 * remaps re-point the accent fill to near-white while leaving --sk-bg alone
 * — only the button ink follows the fill everywhere. */
.sk-el-nav-badge {
  display: inline-block;
  margin-left: 0.45em;
  padding: 0.1em 0.5em;
  border-radius: 999px;
  font-size: 0.8em;
  letter-spacing: 0;
  background: var(--sk-accent);
  color: var(--sk-btn-ink);
}

/* ── The nav DROPDOWN — hover/focus-opened panels, zero script ───────────
 * An item with a `dropdown` renders as .sk-el-nav-item: a <button> trigger
 * plus an absolutely-positioned .sk-el-nav-panel that opens on :hover /
 * :focus-within — CSS-only, so scripts-off still works by keyboard and
 * print renders the bar clean. The panel holds COLUMNS (dropdown.columns —
 * one list concept: a flat menu is a single HEADLESS column), an optional
 * FEATURED card, and an optional full-width FOOTER CTA row. Geometry reads
 * the panel dials; paint reads the ink set, so a theme (or a data-tone
 * stamp on the panel) restyles the surface with zero CSS. */
.sk-el-nav-item {
  position: relative;
  display: inline-flex;
}
.sk-el-nav-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  font-family: inherit;
}
.sk-el-nav-caret {
  opacity: 0.6;
  transition: transform 140ms ease;
}
.sk-el-nav-item:hover .sk-el-nav-caret,
.sk-el-nav-item:focus-within .sk-el-nav-caret {
  transform: rotate(180deg);
}
.sk-el-nav-panel {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 40;
  min-width: 240px;
  padding: var(--sk-panel-pad);
  border-radius: var(--sk-panel-radius);
  background: var(--sk-bg);
  color: var(--sk-ink);
  border: var(--sk-border-width-min) solid var(--sk-stroke);
  box-shadow: var(--sk-panel-shadow);
  /* VIEWPORT AWARENESS — the panel anchors to its trigger's edge, which
   * CSS alone can't place against the viewport, so the ENGINE (base.js
   * job 2f) measures each panel's natural box and writes the correcting
   * shift here (the sanctioned measured-geometry inline: a value on a
   * custom property), holding a 15px gutter on both viewport edges.
   * Scripts-off the floors below stand: the mega cap (88vw) and the
   * ≤640px static reflow. The shift rides the
   * independent `translate` property, NEVER `transform`: the motion block
   * below transitions `transform` (the entrance pose), and a shift written
   * mid-interaction must land instantly — a transitioned shift would GLIDE
   * the surface sideways under the pointer. */
  translate: var(--sk-panel-shift, 0px);
  /* Closed at rest; :hover/:focus-within on the ITEM opens it. A small
   * padding-top bridge keeps the hover corridor from trigger to panel. */
  display: none;
  margin-top: var(--kit-space-2);
}
/* The engine's MEASURING posture — stamped for one synchronous read while
 * the panel rests closed: laid out, painted to nothing. The mega sidecar
 * rule below joins on this stamp so a featured panel measures at its true
 * hovered width. The transform neutralizes the motion block's closed
 * pose — a zoom pose's scale shrinks the rect and would mis-compute the
 * viewport shift. */
.sk-el-nav-panel[data-sk-measure] {
  display: block;
  visibility: hidden;
  transform: none;
}
.sk-el-nav-panel::before {
  content: "";
  position: absolute;
  top: calc(-1 * var(--kit-space-2) - 2px);
  left: 0;
  right: 0;
  height: calc(var(--kit-space-2) + 2px);
}
.sk-el-nav-item:hover > .sk-el-nav-panel,
.sk-el-nav-item:focus-within > .sk-el-nav-panel {
  display: block;
}
/* ── The panel's OPEN/CLOSE motion — pure CSS, scripts-off included ──────
 * The display flip transitions through `allow-discrete` + @starting-style;
 * an engine without them drops the whole transition shorthand and SNAPS —
 * exactly the prior posture, structural degradation. The TRAVEL is the
 * theme's FOREGROUND entrance spoken at PANEL SCALE: each theme sheet
 * writes --sk-panel-anim-from from its framework's `panel-transform` step
 * (themeSheetCss in lib/pieces.ts — generated, never hand-authored here;
 * the band-scale `transform` step is too dramatic for a small hover
 * surface), so a fade site dissolves, a rise site lifts, a zoom site
 * settles — one motion grammar. `transform` here is the POSE ALONE — the measured
 * viewport shift rides the untransitioned `translate` property above, so
 * it always lands instantly. Pace composes from the intensity dial, and
 * the exit runs quicker than the entry: a closing panel is dismissal, not
 * an entrance. */
@media (prefers-reduced-motion: no-preference) {
  .sk-el-nav-panel {
    --sk-panel-anim-pace: calc(1s / max(abs(var(--sk-anim-intensity, 1)), 0.1));
    opacity: 0;
    transform: var(--sk-panel-anim-from, none);
    transform-origin: top;
    /* A closing panel ignores the pointer, so its fading box can't hold
     * the :hover corridor open after the cursor has left. */
    pointer-events: none;
    transition:
      display calc(0.18 * var(--sk-panel-anim-pace)) allow-discrete,
      opacity calc(0.18 * var(--sk-panel-anim-pace)) ease,
      transform calc(0.18 * var(--sk-panel-anim-pace)) ease;
  }
  .sk-el-nav-item:hover > .sk-el-nav-panel,
  .sk-el-nav-item:focus-within > .sk-el-nav-panel {
    opacity: 1;
    transform: none;
    pointer-events: auto;
    transition:
      display calc(0.3 * var(--sk-panel-anim-pace)) allow-discrete,
      opacity calc(0.3 * var(--sk-panel-anim-pace)) var(--sk-ease-out),
      transform calc(0.3 * var(--sk-panel-anim-pace)) var(--sk-ease-out);
  }
  @starting-style {
    .sk-el-nav-item:hover > .sk-el-nav-panel,
    .sk-el-nav-item:focus-within > .sk-el-nav-panel {
      opacity: 0;
      transform: var(--sk-panel-anim-from, none);
    }
  }
}
/* Columns: as many as the data brings (ONE is the flat menu — headless),
 * never pinching below a readable measure. A WRAPPING flex row, deliberately not a column-flow
 * grid: at max-content the columns stand in one row exactly like the old
 * grid, but when the panel's viewport cap bites (below), columns WRAP into
 * further rows instead of overflowing the box — grid-auto-flow: column can
 * never wrap, and its minmax minimum used to defeat the cap outright. */
.sk-el-nav-cols {
  display: flex;
  flex-wrap: wrap;
  gap: var(--kit-space-4);
}
.sk-el-nav-colgroup {
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* The old grid's minmax(200px, 1fr), spoken in flex: never below a
   * readable measure, growing to share the row. */
  flex: 1 1 200px;
  min-width: 200px;
}
.sk-el-nav-colhead {
  font-size: var(--sk-text-detail-size);
  font-weight: var(--kit-weight-eyebrow);
  letter-spacing: calc(var(--kit-track-eyebrow) * 1em);
  text-transform: uppercase;
  color: var(--sk-ink-muted);
  margin-bottom: var(--kit-space-1);
  text-decoration: none;
}
/* A heading that IS a link (headingHref — the section's overview page)
 * keeps the small-caps voice and inks up on hover. */
a.sk-el-nav-colhead:hover {
  color: var(--sk-ink);
}
.sk-el-nav-drop-link {
  display: flex;
  align-items: flex-start;
  gap: var(--kit-space-2);
  padding: var(--kit-space-1) var(--kit-space-2);
  margin-inline: calc(-1 * var(--kit-space-2));
  border-radius: calc(var(--sk-panel-radius) * 0.6);
  text-decoration: none;
  color: var(--sk-ink);
}
.sk-el-nav-drop-link:hover {
  background: var(--sk-surface);
}
/* The optional PRODUCT-ICON tile to the left of a drop link's copy — a
 * built-in glyph (data-icon) or a trusted inline svg, in a small rounded
 * tile on the surface tint (the Vercel / Linear mega-menu pattern). */
.sk-el-nav-drop-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: calc(var(--sk-icon-size) * 1.6);
  height: calc(var(--sk-icon-size) * 1.6);
  margin-top: 1px;
  border-radius: calc(var(--sk-panel-radius) * 0.5);
  background: var(--sk-surface);
  border: var(--sk-border-width-min) solid var(--sk-stroke);
}
.sk-el-nav-drop-icon .sk-icon-glyph,
.sk-el-nav-drop-icon > svg {
  width: calc(var(--sk-icon-size) * 0.9);
  height: calc(var(--sk-icon-size) * 0.9);
}
.sk-el-nav-drop-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.sk-el-nav-drop-desc {
  font-size: var(--sk-text-detail-size);
  color: var(--sk-ink-muted);
}
/* The featured card — the mega-menu's promo sidecar, on the surface tint. */
.sk-el-nav-featured {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: var(--kit-space-3);
  padding: var(--kit-space-3);
  border-radius: calc(var(--sk-panel-radius) * 0.8);
  background: var(--sk-surface);
  text-decoration: none;
  color: var(--sk-ink);
}
.sk-el-nav-featured-title {
  font-weight: var(--kit-weight-title);
}
.sk-el-nav-featured-desc {
  font-size: var(--sk-text-detail-size);
  color: var(--sk-ink-muted);
}
/* The featured card's optional imagery + CTA line. */
.sk-el-nav-featured-img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: calc(var(--sk-panel-radius) * 0.6);
  margin-bottom: var(--kit-space-1);
}
.sk-el-nav-featured-cta {
  margin-top: 2px;
  font-size: var(--sk-text-detail-size);
  color: var(--sk-ink);
}
.sk-el-nav-featured:hover .sk-el-nav-featured-cta {
  text-decoration: underline;
}
/* The full-width footer CTA row across the panel's bottom. */
.sk-el-nav-panel-footer {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--kit-space-3);
  margin: var(--kit-space-3) calc(-1 * var(--sk-panel-pad)) calc(-1 * var(--sk-panel-pad));
  padding: var(--kit-space-2) var(--sk-panel-pad);
  border-top: var(--sk-border-width-min) solid var(--sk-stroke);
  border-radius: 0 0 var(--sk-panel-radius) var(--sk-panel-radius);
  background: var(--sk-surface);
  text-decoration: none;
  color: var(--sk-ink-muted);
  font-size: var(--sk-text-detail-size);
}
.sk-el-nav-panel-cta {
  color: var(--sk-ink);
  white-space: nowrap;
}
/* A panel with COLUMNS (a mega-menu) needs room — shrink-wrap the content,
 * capped short of the viewport. `width` (not `min-width`) on purpose:
 * min-width beats max-width in CSS, so the old min-width: max-content
 * silently defeated the cap and a wide mega could NEVER fit a narrow
 * viewport. Capped, the flex columns above wrap into more rows. */
.sk-el-nav-item:has(.sk-el-nav-cols) > .sk-el-nav-panel {
  width: max-content;
  max-width: min(88vw, 1200px);
}
/* Columns + a FEATURED card — the card becomes a SIDECAR beside the
 * columns (the full mega-menu form) instead of stacking below; the footer
 * CTA row still spans the panel's whole width. */
.sk-el-nav-item:hover > .sk-el-nav-panel:has(.sk-el-nav-cols):has(.sk-el-nav-featured),
.sk-el-nav-item:focus-within > .sk-el-nav-panel:has(.sk-el-nav-cols):has(.sk-el-nav-featured),
.sk-el-nav-panel[data-sk-measure]:has(.sk-el-nav-cols):has(.sk-el-nav-featured) {
  display: grid;
  grid-template-columns: auto minmax(200px, 240px);
  column-gap: var(--kit-space-4);
  align-items: start;
}
.sk-el-nav-panel:has(.sk-el-nav-cols) > .sk-el-nav-featured {
  margin-top: 0;
}
.sk-el-nav-panel:has(.sk-el-nav-cols) > .sk-el-nav-panel-footer {
  grid-column: 1 / -1;
}

/* ── FLUSH POSTURE (the header's `dropdowns` prop → data-dropdowns on the
 * header root) — every panel in the bar sheds the floating card and opens
 * as ONE full-width shelf seated against the bar's bottom edge. Same
 * markup, same CSS-only opening; the ITEM merely stops being the
 * positioning anchor — the BAR takes over, so left/right pin the shelf to
 * the header's whole span. The viewport clamp (base.js job 2f) skips flush
 * panels — a bar-spanning shelf has no overhang to correct. Above the
 * 640 rung only: the small-screen scripts-off floor below keeps its
 * in-flow reflow. */
@media (width > 640px) {
  .sk-header[data-dropdowns="flush"] .sk-header-bar {
    position: relative;
  }
  .sk-header[data-dropdowns="flush"] .sk-el-nav-item {
    position: static;
  }
  .sk-header[data-dropdowns="flush"] .sk-el-nav-item > .sk-el-nav-panel {
    left: 0;
    right: 0;
    width: auto;
    max-width: none;
    margin-top: 0;
    border-radius: 0;
    border-inline: 0;
    /* Content aligns with the bar's own gutters, not the card pad. */
    padding-inline: var(--sk-bar-pad-x);
    /* The columns + featured GRID form: the cluster CENTERS in the shelf
     * (no effect on the block form). space-between here pinned the card to
     * the far viewport edge and smeared the menu across the whole shelf. */
    justify-content: center;
  }
  /* The shelf spans the bar, but its CONTENT keeps a reading measure: the
   * base colgroup's flex-grow shared the WHOLE shelf between columns (two
   * columns → half a viewport each, hover washes a foot wide). Columns
   * stay content-sized under a cap and the cluster centers, with shelf-
   * scale air between columns instead of shelf-wide columns. */
  .sk-header[data-dropdowns="flush"] .sk-el-nav-cols {
    justify-content: center;
    gap: var(--kit-space-5);
  }
  .sk-header[data-dropdowns="flush"] .sk-el-nav-colgroup {
    flex: 0 1 auto;
    max-width: 280px;
  }
  /* The hover corridor spans the trigger→shelf gap — the shelf seats at
   * the BAR's bottom, a bar-pad below the trigger itself. */
  .sk-header[data-dropdowns="flush"] .sk-el-nav-panel::before {
    top: calc(-1 * (var(--sk-bar-pad-y) + var(--kit-space-3)));
    height: calc(var(--sk-bar-pad-y) + var(--kit-space-3));
  }
  .sk-header[data-dropdowns="flush"] .sk-el-nav-panel-footer {
    margin-inline: calc(-1 * var(--sk-bar-pad-x));
    padding-inline: var(--sk-bar-pad-x);
    border-radius: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sk-el-nav-caret {
    transition: none;
  }
}
/* SMALL SCREENS, the SCRIPTS-OFF floor — the wrapping bar (scripts on,
 * the compact chrome below owns small widths and the nav hides into the
 * menu sheet instead). Hover is absent on touch, but the trigger is a
 * real button, so TAP focuses it and :focus-within opens the panel. An
 * absolutely-positioned panel would clip a narrow viewport, so panels
 * reflow INLINE — full width, columns stacking. */
@media (max-width: 640px) {
  .sk-el-nav-item {
    flex-wrap: wrap;
  }
  .sk-el-nav-panel {
    position: static;
    min-width: 100%;
    max-width: 100%;
    box-shadow: none;
    margin-top: var(--kit-space-1);
    /* In-flow reflow — a wide-viewport shift must not ride down here. */
    translate: none;
    transform: none;
  }
  .sk-el-nav-item:has(.sk-el-nav-cols) > .sk-el-nav-panel {
    min-width: 100%;
    max-width: 100%;
  }
  .sk-el-nav-cols {
    flex-direction: column;
  }
  /* The colgroup's 200px flex-basis was a WIDTH share for the row form;
   * on the stacked column axis it becomes a HEIGHT and pads short groups
   * with dead air — let content size the stack. */
  .sk-el-nav-colgroup {
    flex-basis: auto;
  }
  /* The featured SIDECAR stacks back under the columns — a narrow viewport
   * has no room beside them. */
  .sk-el-nav-item:hover > .sk-el-nav-panel:has(.sk-el-nav-cols):has(.sk-el-nav-featured),
  .sk-el-nav-item:focus-within > .sk-el-nav-panel:has(.sk-el-nav-cols):has(.sk-el-nav-featured) {
    display: block;
  }
  .sk-el-nav-panel:has(.sk-el-nav-cols) > .sk-el-nav-featured {
    margin-top: var(--kit-space-3);
  }
}

/* ── COMPACT CHROME (scripts on) — the header's toggle + menu sheet ──────
 * SITE-HEADER-PLAN step 7. NOT breakpoint-keyed: site-header.js MEASURES
 * the bar's natural content width and stamps data-compact on the header
 * root whenever the bar is narrower — a crowded bar collapses right where
 * it stops fitting, a sparse one holds its full grid far narrower. In
 * compact the sheet-bound elements below hide, .sk-header-toggle shows,
 * and the engine builds .sk-header-sheet FROM the bar's own DOM
 * (engine-built UI, the date-picker doctrine — no duplicated markup to
 * drift). Scripts-off none of this exists and the wrap posture stands:
 * never a hidden nav. The sheet is appended to the document body (a fixed
 * panel inside the sticky bar would be trapped by its backdrop-filter
 * containing block), stamped [data-sk] so the kit's box/glyph/motion
 * floors reach it, wearing the header's own data-tone. */
.sk-header-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  /* The WCAG pointer-target floor — tap geometry, not type rhythm. */
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--sk-ink);
}
html[data-sk-js] .sk-header[data-compact] .sk-header-toggle {
  display: inline-flex;
}
/* A header with nothing to reveal (logo + cart only) keeps no toggle —
 * the engine stamps data-menu="none" when the distilled sheet is empty. */
html[data-sk-js] .sk-header[data-menu="none"] .sk-header-toggle {
  display: none;
}
/* The sheet-bound roster: everything the compact bar hands to the menu
 * sheet. The logo and the cart/icon affordances stay on the bar. Scoped
 * to the header bar so footer zones (and the sheet's own clones) keep
 * rendering theirs. */
html[data-sk-js]
  .sk-header[data-compact]
  .sk-header-bar
  :is(
    .sk-el-nav,
    .sk-el-dropdown,
    .sk-el-button,
    .sk-language,
    .sk-separator,
    .sk-textrun,
    .sk-el-spacer
  ) {
  display: none;
}
html[data-sk-js] .sk-header[data-compact] .sk-header-zone > .sk-el-nav-link {
  display: none;
}
/* The affordances that STAY on the compact bar get the same pointer-target
 * floor as the toggle; the row gap yields — the boxes carry the spacing. */
html[data-sk-js] .sk-header[data-compact] .sk-header-bar .sk-icon {
  min-width: 44px;
  min-height: 44px;
}
html[data-sk-js] .sk-header[data-compact] .sk-header-bar .sk-icons {
  gap: 0;
}
/* Scroll-lock while a menu sheet is open (the engine stamps the root). */
html[data-sk-menu] {
  overflow: hidden;
}
.sk-header-scrim {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(0, 0, 0, 0.4);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s var(--sk-ease-out);
}
.sk-header-scrim[data-open] {
  opacity: 1;
  pointer-events: auto;
}
/* The sheet: a right-edge panel in the kit's panel posture. Closed it sits
 * off-canvas AND visibility-hidden (out of tab order and the accessibility
 * tree); the visibility flip is delayed on the way out so the slide plays. */
.sk-header-sheet {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 61;
  width: min(22rem, 86vw);
  display: flex;
  flex-direction: column;
  gap: var(--kit-space-3);
  overflow-y: auto;
  padding: var(--sk-panel-pad);
  /* Notch/home-bar clearance (inert 0 until a host opts into
   * viewport-fit=cover; the right edge and both block ends can sit on
   * device insets). */
  padding-top: calc(var(--sk-panel-pad) + env(safe-area-inset-top, 0px));
  padding-right: calc(var(--sk-panel-pad) + env(safe-area-inset-right, 0px));
  padding-bottom: calc(var(--sk-panel-pad) + env(safe-area-inset-bottom, 0px));
  background: var(--sk-bg);
  color: var(--sk-ink);
  border-left: var(--sk-border-width-min) solid var(--sk-stroke);
  box-shadow: var(--sk-panel-shadow);
  transform: translateX(100%);
  visibility: hidden;
  transition:
    transform 0.35s var(--sk-ease-out),
    visibility 0s 0.35s;
}
/* TAKEOVER (the section's menuStyle prop → data-menu-style on the sheet):
 * the menu fills the viewport from the top — the editorial gesture (the
 * apple pattern) — instead of the right-edge panel. Same contents, same
 * behavior; only the geometry and the entrance axis change, plus roomier
 * rows with the top-level links stepping up the ramp (h4 register). */
.sk-header-sheet[data-menu-style="takeover"] {
  left: 0;
  width: auto;
  border-left: 0;
  padding-left: calc(var(--sk-panel-pad) + env(safe-area-inset-left, 0px));
  transform: translateY(-100%);
}
.sk-header-sheet[data-menu-style="takeover"][data-open] {
  transform: translateY(0);
}
.sk-header-sheet[data-menu-style="takeover"] .sk-header-sheet-link:not([data-sub]) {
  font-size: var(--sk-text-h4-size);
  min-height: 52px;
}

/* MENU POSITION (the section's menuAlign/menuPosition props → data
 * attributes on the sheet). The horizontal axis re-aims the link rows
 * (they are full-width flex rows, so justify-content carries the row
 * content and text-align covers the group heads); the vertical axis
 * centers the nav block between the close bar and the bottom-pinned
 * actions. Defaults (left / top) are the rules above — only the
 * non-default values key here. */
.sk-header-sheet[data-menu-align="center"] {
  text-align: center;
}
.sk-header-sheet[data-menu-align="center"] .sk-header-sheet-link {
  justify-content: center;
}
.sk-header-sheet[data-menu-align="right"] {
  text-align: right;
}
.sk-header-sheet[data-menu-align="right"] .sk-header-sheet-link {
  justify-content: flex-end;
}
/* Vertical centering: the nav's auto block margins split the sheet's free
 * space (auto margins collapse to 0 once content overflows, so a tall menu
 * degrades to the plain top flow). The actions row drops its own bottom
 * pin's auto margin so the split is symmetric around the nav alone. */
.sk-header-sheet[data-menu-position="center"] .sk-header-sheet-nav {
  margin-block: auto;
}
.sk-header-sheet[data-menu-position="center"] .sk-header-sheet-actions {
  margin-top: 0;
}

/* The sheet's head row: the in-sheet close affordance (the toggle sits
 * behind the scrim while open, so the sheet carries its own). */
.sk-header-sheet-bar {
  display: flex;
  justify-content: flex-end;
  /* The close box's tap floor carries its own air; pull it back toward the
   * sheet edge so the glyph sits on the panel pad optically. */
  margin: calc(-1 * var(--kit-space-2)) calc(-1 * var(--kit-space-2)) calc(-1 * var(--kit-space-3))
    0;
}
.sk-header-sheet-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--sk-ink);
}
.sk-header-sheet[data-open] {
  transform: translateX(0);
  visibility: visible;
  transition: transform 0.35s var(--sk-ease-out);
}
/* The stacked nav: top-level links as hairlined rows in the nav voice;
 * a dropdown becomes a DISCLOSURE group — the bar's trigger cloned as a
 * top-level row (same voice, same caret) expanding the panel's links as
 * quieter sub-rows under their .sk-detail column heads. */
.sk-header-sheet-nav {
  display: flex;
  flex-direction: column;
}
.sk-header-sheet-link {
  display: flex;
  align-items: center;
  gap: 0.45em;
  min-height: 44px;
  padding-block: var(--kit-space-1);
  border-bottom: var(--sk-border-width-min) solid var(--sk-stroke);
  text-decoration: none;
  color: var(--sk-ink);
}
.sk-header-sheet-link[data-sub] {
  min-height: 36px;
  border-bottom: 0;
  color: var(--sk-ink-muted);
}
.sk-header-sheet-link[data-active="true"],
.sk-header-sheet-link[aria-current="page"] {
  color: var(--sk-ink);
}
.sk-header-sheet-group {
  display: flex;
  flex-direction: column;
  border-bottom: var(--sk-border-width-min) solid var(--sk-stroke);
}
/* The disclosure row: a cloned bar trigger re-voiced by the shared link
 * class — strip the button chrome (the GROUP carries the row's hairline,
 * so a collapsed group reads exactly like a plain link row) and let the
 * cloned caret flag the expand, flipping on the aria state. */
button.sk-header-sheet-link {
  width: 100%;
  padding-inline: 0;
  background: none;
  border: 0;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  color: var(--sk-ink);
}
.sk-header-sheet-link[aria-expanded="true"] .sk-el-nav-caret {
  transform: rotate(180deg);
}
/* The group's body — the animatable height:auto: a 0fr→1fr grid track
 * whose inner wrapper (min-height 0, overflow hidden) squashes with it,
 * so the expand/collapse GLIDES instead of popping. OPEN is the default
 * posture (a headless flat-menu group has no trigger and simply stays
 * so); a preceding trigger's aria state collapses it, with visibility
 * delayed on the way out so the slide plays — the sheet's own pattern.
 * The inner's block padding transitions to 0 in step: padding on the
 * squashed box would hold the collapsed height above zero. */
.sk-header-sheet-group-body {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 0.3s var(--sk-ease-out);
}
.sk-header-sheet-group-body > div {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  padding-block: var(--kit-space-2) var(--kit-space-3);
  transition: padding 0.3s var(--sk-ease-out);
}
.sk-header-sheet-link[aria-expanded="false"] + .sk-header-sheet-group-body {
  grid-template-rows: 0fr;
  visibility: hidden;
  transition:
    grid-template-rows 0.3s var(--sk-ease-out),
    visibility 0s 0.3s;
}
.sk-header-sheet-link[aria-expanded="false"] + .sk-header-sheet-group-body > div {
  padding-block: 0;
}
/* A head that FOLLOWS a previous column's links opens its own air —
 * without this the folded dropdown's headings sit flush against the rows
 * above them. */
.sk-header-sheet-group .sk-detail:not(:first-child) {
  margin-top: var(--kit-space-3);
}
/* CTAs + the language picker, pinned to the sheet's foot; cloned buttons
 * keep their own .sk-btn paint and stretch full width. */
.sk-header-sheet-actions {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: var(--kit-space-2);
  padding-top: var(--kit-space-3);
}
.sk-header-sheet-actions .sk-btn {
  display: flex;
  justify-content: center;
}
.sk-header-sheet-actions .sk-language {
  align-self: center;
}
/* The scrim/sheet roots mount on <body>, outside any [data-sk] ancestor,
 * so the kit's blanket motion floor can't reach their own transitions —
 * guard them explicitly (the nav-caret precedent). */
@media (prefers-reduced-motion: reduce) {
  .sk-header-scrim,
  .sk-header-sheet {
    transition: none;
  }
}

/* ── OVERLAP CHROME (data-overlap="hero") — NOT HERE, deliberately ────────
 * §4's overlay ink over the floating bar used to live here, apart from the
 * positioning it has to agree with — two rules, two copies of one condition,
 * and a bar that could float while withholding the near-white ink (page-dark
 * type on a raw photograph). The whole posture — position, transparent fill,
 * overlay INK, the sibling's --sk-chrome-clear — is now ONE gated block in
 * library/site-header/site-header.css, so it cannot half-apply. Do not
 * re-introduce a `[data-overlap="hero"]` ink or float rule here; the lockup's
 * own overlay-ink condition (§4, below) is what that gate mirrors. */

/* Pinned chrome overlays anchor targets — give every band below a sticky
 * (or fixed-overlap) bar its scroll clearance. */
.sk-header[data-stick="stick"] ~ .sk-section {
  scroll-margin-top: var(--sk-bar-height);
}

.sk-icons {
  display: inline-flex;
  align-items: center;
  gap: var(--kit-space-2);
}
.sk-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  text-decoration: none;
  color: inherit;
}
.sk-icon > svg {
  width: var(--sk-icon-size);
  height: var(--sk-icon-size);
}
/* The GLYPH SET — mask-drawn icons keyed by data-icon: a data-URI mask
 * riding currentColor via background-color, never a per-use inline SVG.
 * The glyphs are CATALOG material: each is one SVG file under
 * pieces/icons/<id>.svg, and the machinery generates the per-glyph
 * mask-image rules into this kit's base.css (iconGlyphCss in
 * lib/pieces.ts, appended by the loader) — adding a glyph = one SVG file,
 * never hand-authored CSS here. The `icons` element's rows pick by name.
 * This atom keeps only the shared mask channel. */
.sk-icon-glyph {
  display: inline-block;
  width: var(--sk-icon-size);
  height: var(--sk-icon-size);
  background-color: currentColor;
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
}
.sk-icon:hover {
  color: var(--sk-ink-muted);
}

/* The count bubble anchors to the GLYPH's top-right corner, never the
 * .sk-icon box's: the box inflates to the 44px tap floor in compact chrome
 * while the glyph stays centered at --sk-icon-size, so a box-corner anchor
 * drifts off the glyph exactly there. Centering on 50% and offsetting by a
 * glyph fraction holds the same corner at every box size. The bubble sizes
 * from its own type (with a legibility floor — a count must READ, so it
 * never shrinks into a dot) and paints the BUTTON pair: the one fill+ink
 * pairing every tone AND every §4 ink remap re-points coherently (tones
 * deliberately soften --sk-accent toward the band, which washed the chip
 * out; and the overlap-chrome remap deliberately leaves --sk-bg alone, so
 * --sk-bg-as-ink went invisible on the near-white fill over a transparent
 * bar). */
.sk-icon-badge {
  position: absolute;
  left: calc(50% + var(--sk-icon-size) * 0.44);
  top: calc(50% - var(--sk-icon-size) * 0.44);
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.5em;
  height: 1.5em;
  padding: 0 0.35em;
  border-radius: 999px;
  font-size: max(9px, calc(var(--sk-icon-size) * 0.46));
  font-weight: var(--kit-weight-badge);
  line-height: 1;
  background: var(--sk-btn-bg);
  color: var(--sk-btn-ink);
}

.sk-language {
  display: inline-flex;
  align-items: center;
  gap: var(--kit-space-1);
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  text-decoration: none;
  color: var(--sk-ink-muted);
}
.sk-language:hover {
  color: var(--sk-ink);
}
.sk-language > svg {
  width: var(--sk-text-nav-size);
  height: var(--sk-text-nav-size);
}
/* The two canned label forms — "EN" (short) / "English" (long); the row's
 * `displayType` picks one via the data-display stamp. Deliberately dumb:
 * real locale data plumbs in later. */
.sk-language[data-display="short"] .sk-language-long,
.sk-language[data-display="long"] .sk-language-short {
  display: none;
}

.sk-separator {
  align-self: stretch;
  width: var(--sk-border-width-min);
  margin-block: 0.35em;
  background: var(--sk-stroke);
}
/* The DOT look — a small centered dot instead of the hairline. */
.sk-separator[data-look="dot"] {
  align-self: center;
  width: 3px;
  height: 3px;
  margin-block: 0;
  border-radius: 999px;
  background: var(--sk-ink-muted);
}

/* The SPACER — a flex spring pushing its zone neighbors apart. Only
 * meaningful inside a zone that owns the space (a 1fr grid cell already
 * distributes it). Invisible, never announced. */
.sk-el-spacer {
  flex: 1 1 0;
  align-self: stretch;
}

.sk-textrun {
  font-size: var(--sk-text-detail-size);
  color: var(--sk-ink-muted);
}

/* The SUBSCRIBE element — the inline newsletter capture. One hairline
 * carries BOTH the email field and its quiet submit (the classic footer
 * underline register): the field is chromeless (no fill, no border — the
 * shared row underline IS the affordance), the action rides the eyebrow
 * detail voice like a sitemap heading, and focus raises the hairline to
 * full ink. The status line rides the detail register. */
.sk-el-subscribe {
  display: flex;
  flex-direction: column;
  gap: var(--kit-space-2);
  /* Wide enough that a placeholder + SUBSCRIBE pair never crushes; the
   * zone's own wrap handles narrow bands. */
  min-width: min(26ch, 100%);
}
.sk-el-subscribe-row {
  display: flex;
  align-items: baseline;
  gap: var(--kit-space-3);
  padding-bottom: 0.55em;
  border-bottom: var(--sk-border-width-min) solid var(--sk-stroke);
  transition: border-color 140ms ease;
}
.sk-el-subscribe-row:focus-within {
  border-color: var(--sk-ink);
}
.sk-el-subscribe-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  font-size: var(--sk-text-nav-size);
  color: var(--sk-ink);
}
.sk-el-subscribe-input::placeholder {
  color: var(--sk-ink-muted);
}
/* The row underline is the field's focus paint — no double ring. */
.sk-el-subscribe-input:focus-visible {
  outline: none;
}
.sk-el-subscribe-btn {
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  /* The eyebrow detail voice — the register sitemap headings speak. */
  font-family: inherit;
  font-size: var(--sk-text-detail-size);
  font-weight: var(--kit-weight-eyebrow);
  letter-spacing: calc(var(--kit-track-eyebrow) * 1em);
  text-transform: uppercase;
  color: var(--sk-ink-muted);
  transition: color 140ms ease;
}
.sk-el-subscribe-btn:hover {
  color: var(--sk-ink);
}

.sk-el-subscribe-status {
  margin: 0;
  /* The status must never SET the form's width — footer lanes size to
   * content, so a long server error on one line would push the whole band
   * wider. width:0 zeroes its intrinsic contribution (the input+button row
   * decides the width); min-width:100% then fills that decided width, so
   * the message wraps under the row. */
  width: 0;
  min-width: 100%;
  font-size: var(--sk-text-detail-size);
  color: var(--sk-ink-muted);
}

/* The footer SITEMAP — link columns under small-caps headings (the closing
 * band's canonical structure). Columns flow as a wrapping row; each is a
 * COLUMN of one or more headed GROUPS (a `groups` column stacks several —
 * "Research" over "Safety" — and the stack wraps as one unit), each group a
 * heading over a line-height-pitched link list. The heading rides the
 * eyebrow detail voice; links ride the nav register. */
.sk-el-sitemap {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  /* Breathing is the air-sitemap DIAL PAIR (resolved in the token
   * block): wrap rows at -y, columns at -x. */
  gap: var(--sk-sitemap-gap-y) var(--sk-sitemap-gap-x);
}
.sk-el-sitemap-col {
  display: flex;
  flex-direction: column;
  gap: var(--sk-sitemap-gap-y);
  min-width: 10ch;
}
.sk-el-sitemap-group {
  display: flex;
  flex-direction: column;
  gap: var(--kit-space-2);
}
.sk-el-sitemap-head {
  font-size: var(--sk-text-detail-size);
  font-weight: var(--kit-weight-eyebrow);
  letter-spacing: calc(var(--kit-track-eyebrow) * 1em);
  text-transform: uppercase;
  color: var(--sk-ink-muted);
  text-decoration: none;
}
a.sk-el-sitemap-head:hover {
  color: var(--sk-ink);
}
.sk-el-sitemap-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--kit-space-1);
}
.sk-el-sitemap-link {
  text-decoration: none;
  color: var(--sk-ink-muted);
  transition: color 140ms ease;
}
.sk-el-sitemap-link:hover {
  color: var(--sk-ink);
}

/* The cart's TEXT mode — a quiet "Cart (n)" link in the nav register (the
 * editorial-bar pattern); the class is a marker for lint/audit, the look is
 * entirely the nav-link register it stacks with. */

/* ── The lockup ───────────────────────────────────────────────────────────
 * ONE content structure — eyebrow + title + desc + actions + tags + media —
 * styled through two geometry axes (SITE-KIT.md "The lockup"):
 *
 *   data-box   where the text box floats ON the media:
 *              tl | tr | cl | cr | bl | br | center — every position an
 *              overlay carrying its own inset, so the lockup renders FLUSH
 *              (SITE-KIT.md). Omit for a media-less lockup —
 *              the box just stacks.
 *   data-size  the register. Two vocabularies share the axis: the lockup
 *              SECTION's authored band — smaller | normal | larger, a
 *              narrow multiplier band (0.8 / 1 / 1.2) on the hero's h1
 *              step — and the structural ladder other sections stamp on
 *              their entry shells: l (entry header) | m (section head) |
 *              s (editorial row) | xs (card).
 *
 * Size carries RHYTHM and REGISTER only; type always rides the kit's ramp —
 * templates stamp the matching data-text-style per size (xl: display,
 * l: heading-1, m: heading-2, s: heading-3 — the journal-row / tile step —
 * xs: heading-4; desc is body, clamped at xs).
 * The gap — the register's BEAT — is never hand-picked: it derives from the
 * register's own title size (see the beat rule below), so type and rhythm
 * stay in harmony under any theme's dials, fluid compression included.
 * Templates choose the size — authored geometry, a template fact. Parts inside the box: .sk-eyebrow, .sk-lockup-title,
 * .sk-lockup-desc, .sk-lockup-actions; media is
 * .sk-lockup-media on the root. */

.sk-lockup {
  position: relative;
  /* THE REGISTER'S STEP — the ONE ramp step a register is built on, named
   * once so everything the register governs derives from the same number:
   * the beat below, the lockup section's headline size, and its measure.
   * Default is m; each register rule below swaps this and nothing else. */
  --sk-lockup-step: var(--sk-text-h2-size);
  /* THE BEAT — affine in the register's step (0.4 × body + 0.23 × step):
   * the constant floor keeps a small register breathing, the sub-1 rate
   * keeps a billboard from going cavernous. The fluid step carries mobile
   * compression into the rhythm for free. */
  --sk-lockup-gap: calc(var(--kit-type-base) * 0.4 + var(--sk-lockup-step) * 0.23);
  /* THE FRAME INSET RESOLVED PER EDGE — the --sk-lockup-pad axis family
   * (per-side -t/-b beat -y beats the uniform pad beats the air-frame
   * dial) folded ONCE into three private edge vars. The overlay padding
   * reads exactly these. Sections keep tuning the
   * --sk-lockup-pad-* seam (vars inherit, so a section-root override
   * resolves here). */
  /* --sk-chrome-clear folds the floating chrome's height into the TOP edge
   * (0 unless an overlapping header precedes the band — see the overlap
   * chrome block), so overlay copy clears the bar. */
  --sk-frame-t: calc(
    var(--sk-chrome-clear, 0px) +
      var(--sk-lockup-pad-t, var(--sk-lockup-pad-y, var(--sk-lockup-pad, var(--sk-frame-pad))))
  );
  --sk-frame-b: var(
    --sk-lockup-pad-b,
    var(--sk-lockup-pad-y, var(--sk-lockup-pad, var(--sk-frame-pad)))
  );
  --sk-frame-x: var(--sk-lockup-pad-x, var(--sk-lockup-pad, var(--sk-frame-pad)));
}
/* Each register names its STEP; the beat, the headline and the measure all
 * derive from it, so a register is one decision rather than three.
 *
 * THE AUTHORED BAND — the lockup section's own size axis: smaller 0.8×,
 * normal 1×, larger 1.2× the theme's hero step (h1). Deliberately
 * multipliers on ONE ramp step rather than rungs of the ladder: the old
 * axis (xs…xxl) spanned the whole ramp, and a hero two rungs from its
 * neighbour was a different design, not a size choice. The band also stays
 * inside the hero's register by construction — smaller (0.8×h1) still
 * clears the section heading (h2 = h1/r²), so a front door can never
 * announce the site more quietly than its own sections — the invariant the
 * retired load-time register floor existed to police, now structural. */
.sk-lockup[data-size="smaller"] {
  --sk-lockup-step: calc(var(--sk-text-h1-size) * 0.8);
}
.sk-lockup[data-size="normal"] {
  --sk-lockup-step: var(--sk-text-h1-size);
}
.sk-lockup[data-size="larger"] {
  --sk-lockup-step: calc(var(--sk-text-h1-size) * 1.2);
}
/* THE STRUCTURAL LADDER — the registers other sections stamp on their
 * entry shells (entry-detail's header at l, journal/article rows and
 * collection tiles at s, feature cards at xs; m is the resting default
 * above). Template facts, never the authored axis. */
.sk-lockup[data-size="l"] {
  --sk-lockup-step: var(--sk-text-h1-size);
}
.sk-lockup[data-size="s"] {
  --sk-lockup-step: var(--sk-text-h3-size);
}
.sk-lockup[data-size="xs"] {
  --sk-lockup-step: var(--sk-text-h4-size);
}

/* ── THE REGISTER'S HEADLINE (the lockup SECTION's own) ──────────────────
 * The beat was always computed from the register's step while the headline
 * stayed pinned at heading-1 by the template — the rhythm sized for one
 * step, the type rendered at another. Harmless while the template
 * hardcoded `l`; wrong the moment the axis opened. The headline now reads
 * the same step the beat does, so a register means ONE thing.
 *
 * The authored band's steps are MULTIPLIERS on the ramp's h1 step, so a
 * theme's title dial still governs all three and there is no second scale
 * to drift.
 *
 * Scoped to .sk-primitive-lockup — the lockup SECTION. Other sections
 * mount .sk-lockup as their entry Forma (feature entries, collection
 * tiles, journal rows) and stamp their own heading style per part; those
 * are their templates' decision, not this axis's. */
.sk-primitive-lockup .sk-lockup-title {
  font-size: var(--sk-lockup-step);
}

/* The height axis — data-height on a SECTION root sets its lockup's band
 * REGISTER: the ONE shared rule-set every section's `height` enum rides.
 * Three rungs: `auto` (the resting stage — the size-stage dial via
 * --sk-stage-h, the var's fallback, so it needs no rule here), `min` (no
 * stage floor: the band hugs its content — the HEADER posture for a lockup
 * heading another band; the vertical half of the box anchor degenerates
 * with no spare stage, the horizontal half keeps meaning), and `full` (the
 * viewport). svh, not vh — stable under the mobile URL bar. Overlay boxes
 * read the var through their own min-height rule and keep their anchor
 * layout; a STACKED lockup (no data-box) centers its copy in the stretched
 * band. */
[data-height="min"] .sk-lockup {
  --sk-lockup-min-h: 0px;
}
/* FULL height is the VIEWPORT, not the viewport plus the chrome above it.
 * An in-flow header spends real height before the band begins, so the band
 * subtracts it (--sk-chrome-push, set on this section by the header's own
 * sheet + JS). A FLOATING header spends none — it sets --sk-chrome-clear
 * instead, which folds into the top frame inset so the copy clears the bar
 * without shortening the band. Exactly one is ever set. */
[data-height="full"] .sk-lockup {
  --sk-lockup-min-h: calc(100svh - var(--sk-chrome-push, 0px));
  min-height: var(--sk-lockup-min-h);
}
/* Full height INSIDE the page frame — an inset full band is "the viewport
 * wearing the gutter", so the frame padding comes OFF the stage height
 * (band + frame = one viewport), not on top of it. */
[data-height="full"][data-inset="page"] .sk-lockup {
  --sk-lockup-min-h: calc(100svh - var(--sk-chrome-push, 0px) - 2 * var(--sk-page-pad-x));
}
[data-height="full"] .sk-lockup:not([data-box]) {
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Band air keeps the centered copy off the band's edges even when the
   * viewport is shorter than the content. */
  padding-block: var(--sk-band-pad);
}

/* The inset axis — data-inset="page" on a SECTION root pulls the band in
 * from the viewport edges by the theme's page gutter on ALL FOUR sides:
 * the ONE shared rule every section's `inset` enum rides (flush, the kit
 * default, stamps data-inset="flush" and matches nothing). The inset is
 * section PADDING, so the band's own background keeps running edge to
 * edge and the media sits in from the page edge — the same distance a
 * content band's internal side gutter reads (--sk-page-pad-x), so inset
 * media and inset type share one page edge. The selector carries
 * .sk-section for RANK (0,3,0): a flush section's own sheet zeroes
 * padding-block AFTER this sheet by source order (lockup at (0,2,0),
 * photo at (0,1,0) — the flush posture), and the axis must repaint
 * that zero with the gutter — the same deliberate rank engineering the
 * theme token rules use. The INLINE sides ride the page cap's rail
 * formula (the band-gutter home's doctrine): inset media and inset type
 * keep sharing one page edge when that edge is the capped rail. Block
 * stays the plain gutter — the cap is a WIDTH. */
.sk-section[data-sk][data-inset="page"] {
  padding-block: var(--sk-page-pad-x);
  padding-inline: max(var(--sk-page-pad-x), calc((100% - var(--sk-page-max, 200vw)) / 2));
}

/* The META PLACEMENT axis — data-meta-placement on a SECTION root re-seats
 * each row lockup's LABEL SLOT (.sk-eyebrow / .sk-detail) BESIDE the copy
 * as a leading meta column (a dated news index, a publications list): the
 * ONE shared rule-set every list section's `metaPlacement` enum rides
 * (`above`, the resting stance, stamps data-meta-placement="above" and
 * matches nothing — the label keeps its slot atop the stack). The box
 * becomes a two-track grid: the label holds track 1's first row, every
 * other part flows down track 2 — same children, same rhythm margins
 * phrasing the same row gap, so no part learns a new name and a row
 * WITHOUT a label keeps the alignment (its copy still opens track 2).
 * TOP alignment, not baseline: the meta is a small detail line and the
 * title can be a display serif — matching baselines would drop the meta
 * by the title's whole ascent (a phantom top margin); aligned starts let
 * the two line boxes open together, the reference posture. The meta
 * column reads --sk-meta-col (×-body per the ratio rule; a section tunes
 * only the var). Below the sm rung the posture stands down — the base
 * flex stack simply resumes, meta back on top. */
@media (width > 560px) {
  [data-meta-placement="side"] .sk-lockup .sk-lockup-box {
    display: grid;
    grid-template-columns: var(--sk-meta-col, calc(var(--kit-type-base) * 6)) minmax(0, 1fr);
    gap: var(--sk-lockup-gap) var(--kit-space-4);
    align-items: start;
  }
  [data-meta-placement="side"] .sk-lockup .sk-lockup-box > :is(.sk-eyebrow, .sk-detail) {
    grid-column: 1;
    grid-row: 1;
    margin-bottom: 0;
  }
  [data-meta-placement="side"] .sk-lockup .sk-lockup-box > :not(.sk-eyebrow, .sk-detail) {
    grid-column: 2;
  }
}

/* The text box: a column with lockup-gap rhythm; part margins are zeroed so
 * the gap is the ONE spacing authority inside a lockup. */
.sk-lockup-box {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sk-lockup-gap);
  min-width: 0;
}
.sk-lockup-box > * {
  margin: 0;
}
/* PROGRESSIVE RHYTHM — the gap is the beat; adjacency modifiers phrase it.
 * Margins modulate the flex gap (they ADD to it, so each calc is
 * factor − 1), keeping one --sk-lockup-gap tuning the whole lockup while
 * the rhythm factors phrase it — every adjacency has its own dial, and a
 * factor of 1 is the plain beat. */
.sk-lockup-box > .sk-badge {
  margin-bottom: calc(var(--sk-lockup-gap) * (var(--sk-rhythm-badge, 1) - 1));
}
/* Both label voices — the accent eyebrow and the quiet detail (a row's
 * meta line) — sit in the label SLOT and phrase the same adjacency. */
.sk-lockup-box > :is(.sk-eyebrow, .sk-detail) {
  margin-bottom: calc(var(--sk-lockup-gap) * (var(--sk-rhythm-label, 0.5) - 1));
}
.sk-lockup-box > .sk-lockup-title {
  margin-bottom: calc(var(--sk-lockup-gap) * (var(--sk-rhythm-title, 1) - 1));
}
.sk-lockup-box > .sk-lockup-actions {
  margin-top: calc(var(--sk-lockup-gap) * (var(--sk-rhythm-departure, 1.5) - 1));
}
/* The EDITORIAL ROW keeps its action ON the beat (departure factor 1,
 * structural): a repeating row's read-on link is row furniture — a
 * departure-scaled gap would read each row as its own section ending.
 * The xs card's quiet links already sit in tighter columns. */
.sk-lockup[data-size="s"] .sk-lockup-box > .sk-lockup-actions {
  margin-top: 0;
}

/* Wrap quality is baked into the atoms (progressive enhancement): titles
 * BALANCE, descriptions wrap PRETTY. Balance is for short runs only
 * (~6 lines), so it never goes on body copy. */
.sk-lockup-title {
  text-wrap: balance;
  border-width: var(--kit-edge-title);
  border-style: var(--kit-edge-style-title);
  border-color: currentcolor;
  border-radius: var(--kit-round-title);
  padding: var(--kit-pad-title);
}
.sk-lockup-desc {
  color: var(--sk-ink-muted);
  text-wrap: pretty;
  border-width: var(--kit-edge-body);
  border-style: var(--kit-edge-style-body);
  border-color: currentcolor;
  border-radius: var(--kit-round-body);
  padding: var(--kit-pad-body);
}
/* The measure covers registers whose desc runs as a paragraph (the hero
 * band, l/m, and the s editorial row — which runs full excerpts); the xs
 * register's desc is line-clamped below and lives in card columns already
 * narrower than the measure. */
/* THE LEDE VOLUME, finally consumed. --sk-lede-size exists for exactly this
 * paragraph — "the emphasized intro under a hero headline, one scale step above
 * body" — but nothing read it, so a hero's supporting line rendered at plain
 * body size and a billboard's lead sat visually flush with its own body copy.
 * Only the HERO-BAND registers take it (the authored band plus the
 * structural l): at s/m the desc IS body copy (a journal row, a section
 * head), and lifting those would flatten the ramp instead of sharpening it. */
.sk-lockup:is([data-size="l"], [data-size="smaller"], [data-size="normal"], [data-size="larger"])
  .sk-lockup-desc {
  font-size: var(--sk-lede-size);
}
.sk-lockup:is(
    [data-size="s"],
    [data-size="m"],
    [data-size="l"],
    [data-size="smaller"],
    [data-size="normal"],
    [data-size="larger"]
  )
  .sk-lockup-desc {
  max-width: var(--sk-measure, 46ch);
}
/* The xs register clamps the description — cards trail off, never balloon. */
.sk-lockup[data-size="xs"] .sk-lockup-desc {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.sk-lockup-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sk-actions-gap);
}
/* Naked links need more air than padded buttons — button chrome is what
 * makes the tighter gap read as separation. Covers any row holding a
 * link-register anchor and rows that author .sk-link directly. DERIVED from
 * the one actions dial (×2.3, the space-4/space-2 proportion it replaced):
 * the link row's extra air is a RELATIONSHIP, so density moves both from one
 * knob instead of the two drifting apart. */
.sk-lockup-actions:has(> [data-action="link"]),
.sk-lockup-actions:has(> .sk-link) {
  gap: calc(var(--sk-actions-gap) * 2.3);
}

/* The OUTLINE remap — data-action="outline" flips a .sk-btn anchor to the
 * secondary posture (same mechanism as the link remap below): transparent
 * fill, ink border, filling on hover. The chrome button element's `style`
 * enum stamps it, so one bar carries a filled + outline CTA pair off one
 * markup source. */
[data-sk] .sk-btn[data-action="outline"] {
  background: transparent;
  color: var(--sk-ink);
  border-color: var(--sk-ink);
}
[data-sk] .sk-btn[data-action="outline"]:hover {
  background: var(--sk-btn-bg);
  color: var(--sk-btn-ink);
  border-color: var(--sk-btn-bg);
}

/* The quiet link register. The per-CTA style enums (button | link, stamped
 * as data-action on EACH anchor) flip an anchor between button chrome and
 * this look INDEPENDENTLY — a data attribute + remap, same mechanism as the
 * tone axis, so the look is defined ONCE and the anchors keep their button
 * classes. */
.sk-link,
[data-sk] :is(.sk-btn, .sk-btn-outline)[data-action="link"] {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  font-weight: var(--kit-weight-button);
  letter-spacing: calc(var(--kit-track-button) * 1em);
  text-transform: var(--kit-caps-button);
  font-size: var(--sk-text-btn-size);
  text-decoration: none;
  color: var(--sk-ink);
  /* The DRAWN underline, background-image form since the remap owns
   * `background`: grows from the left on hover. Declared AFTER the
   * `background: none` shorthand so the image survives the reset. */
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: left bottom;
  background-size: 0% 1px;
  padding-bottom: 2px;
  transition: background-size 0.35s var(--sk-ease-out);
}
/* Hover only grows the drawn underline: the register rule above outranks
 * the button rules' :hover shorthands in every state ((0,3,0)/(0,4,0) over
 * (0,2,0)), so the image/repeat/position it declares survive untouched. */
.sk-link:hover,
[data-sk] :is(.sk-btn, .sk-btn-outline)[data-action="link"]:hover {
  background-size: 100% 1px;
}

/* Media: an sk-image (the content-imagery atom — see "The image" below)
 * carrying the sk-lockup-media geometry hook; the [data-box] rules shape it
 * per position. The atom owns the base posture, so nothing is defined
 * here. */

/* Overlay boxes — the box floats ON the media. Every position carries its
 * own dark base, tonality veil, and frame inset, so the lockup renders
 * FLUSH — edge to edge, no outer margins (SITE-KIT.md).
 * The copy inks are deliberately NOT brand tokens: they sit on the fixed
 * dark veil, so near-white is the one legible choice for every board.
 * Sections tune the GEOMETRY through the --sk-lockup-* custom props — vars,
 * not declarations, so a section's one-class rule never loses the
 * specificity fight against these [data-box] selectors.
 * The frame inset is the --sk-lockup-pad AXIS FAMILY — never a multi-value
 * shorthand (lint rejects one): --sk-lockup-pad is the uniform inset;
 * --sk-lockup-pad-x overrides the sides, --sk-lockup-pad-y the top+bottom,
 * and --sk-lockup-pad-t / --sk-lockup-pad-b beat -y per edge. Per-side vars
 * (not a shorthand) so every edge use site can read the SAME insets
 * per edge — a shorthand in `top:` is an invalid declaration there. The
 * family resolves ONCE, on .sk-lockup above, into
 * the private --sk-frame-t/-b/-x edge vars every use site here reads.
 * The solid dark base is the legibility FLOOR: the near-white copy must stay
 * readable while the media is loading, if it never arrives, and over a
 * bright photo — the base and wash carry the contrast, never the image.
 * Base and ink ride seam vars (--sk-lockup-base / --sk-lockup-ink) so a
 * viewer that DROPS the media can re-base the frame from a theme's own tone
 * tokens; with media present the fixed dark floor stands.
 * The one exception is an ABSENT media slot — deliberately no photo at all,
 * so the template emits no media element — which reflows to the no-media
 * posture below instead.
 * `isolation` keeps the media/wash/box z-order local to the lockup.
 * SELECTOR SHAPE: "any anchored box" — the data-box enum is closed in the
 * manifests (tl|tr|cl|cr|bl|br|center; absent = stacked, empty = unset), so
 * [data-box] with the :where()-ranked empty guard is the whole overlay
 * family at the same (0,2,0) the layout overrides below tie and beat by
 * source order. */
.sk-lockup[data-box]:where(:not([data-box=""])) {
  display: flex;
  border-radius: var(--sk-lockup-radius, var(--sk-radius));
  /* The stage: a MIN-HEIGHT guarantee (the size-stage dial via
   * --sk-stage-h), never a padding floor — the box still sits exactly
   * air-frame from its edges. */
  min-height: var(--sk-lockup-min-h, var(--sk-stage-h));
  /* The frame inset — the resolved edge vars, all four edges, ONE
   * authority; no floor ever wraps these. */
  padding: var(--sk-frame-t) var(--sk-frame-x) var(--sk-frame-b) var(--sk-frame-x);
}

/* ── THE ON-MEDIA CONTRACT — one home ───────────────────────────────
 * Copy floated ON a photograph is one contract, declared once: an isolated,
 * clipped frame over the fixed dark base (the legibility floor while the
 * media loads, fails, or shines bright — base and ink ride the
 * --sk-lockup-base/-ink seam vars, see the overlay note above), the
 * tonality-owned veil between media and copy, and the near-white INK REMAP
 * (each block below carries its own note). TWO KINDS OF CONSUMER, one rule
 * body, comma-listed so every arm keeps its own rank:
 *   — the lockup's anchored overlay box (its arms, with their media/layout
 *     gates, are unchanged);
 *   — any band FRAME wearing the .sk-on-media marker inside a bleed layout
 *     (the slideshow's frames; `[data-layout="bleed"]` is the lockup's own
 *     layout vocabulary, so a split layout opts out by grammar, and a
 *     section with no layout axis stamps the attribute statically).
 * The NEXT gallery type wears the marker — it never copies these blocks
 * into its own sheet (the drift class this home exists to kill). */
.sk-lockup[data-box]:where(:not([data-box=""])),
[data-layout="bleed"] .sk-on-media {
  overflow: hidden;
  isolation: isolate;
  background-color: var(--sk-lockup-base, rgb(14, 12, 10));
  color: var(--sk-lockup-ink, var(--sk-overlay-ink));
}
.sk-lockup[data-box]:where(:not([data-box=""])) .sk-lockup-media {
  position: absolute;
  inset: 0;
  height: 100%;
  border-radius: 0;
  /* No loading placeholder here: the light --sk-surface fallback would
   * paint over the dark base and put the near-white copy on a light
   * field. */
  background: none;
}
/* The veil sits between media and box (z-index 1, under the box's 2; the
 * media's auto stays below both). Its paint is TONALITY-owned: a section
 * stamps data-image-tone="<id>" on its root, and the kit's tonality catalog
 * (pieces/image-tonalities/ — the loader appends the generated rules to THIS
 * sheet) sets --sk-image-wash here and --sk-image-filter on the image atom.
 * Every tonality composes its strength from --sk-tone-intensity. There is
 * no per-section scrim axis: every treatment is a named kit tonality. The
 * fallback below is the legibility floor for a lockup that renders without
 * a tonality. */
.sk-lockup[data-box]:where(:not([data-box=""]))::before,
[data-layout="bleed"] .sk-on-media::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(
    --sk-image-wash,
    linear-gradient(to bottom, rgba(14, 12, 10, 0.35), rgba(14, 12, 10, 0.55))
  );
}

.sk-lockup[data-box]:where(:not([data-box=""])) .sk-lockup-box {
  position: relative;
  z-index: 2;
  max-width: 34rem;
}
/* …but a BARE box on a bleed band is not a card, and 34rem is a CARD's
 * dimension — a physical object, correctly fixed — that every anchored box
 * was wearing whether a card was there or not. On an imageless statement or
 * a poster it put a display headline in a 544px column whatever its type
 * size, so the louder the register the more stacked the words.
 *
 * EVERY PART CARRIES ITS OWN MEASURE, so the box carries none. It shrinks
 * to its content — which is also what keeps the anchor margins meaningful,
 * since a full-width box has nothing to push against — and the title and
 * desc each cap themselves in their own `ch`. At the display step that
 * lets a headline span the whole band, which is what a poster IS.
 *
 * A real CARD is excluded: there the width belongs to the object, and the
 * data-card block's 34rem stands. */
.sk-primitive-lockup[data-layout="bleed"][data-box]:where(:not([data-box=""], [data-card="true"]))
  .sk-lockup-box {
  width: fit-content;
  max-width: 100%;
}
.sk-primitive-lockup .sk-lockup-title {
  max-width: var(--sk-measure-title);
}
.sk-lockup[data-box="tl"] .sk-lockup-box {
  margin: 0 auto auto 0;
}
.sk-lockup[data-box="tr"] .sk-lockup-box {
  margin: 0 0 auto auto;
}
.sk-lockup[data-box="bl"] .sk-lockup-box {
  margin: auto auto 0 0;
}
.sk-lockup[data-box="br"] .sk-lockup-box {
  margin: auto 0 0 auto;
}
/* The centered edge anchors: vertically centered, pinned to one side. */
.sk-lockup[data-box="cl"] .sk-lockup-box {
  margin: auto auto auto 0;
}
.sk-lockup[data-box="cr"] .sk-lockup-box {
  margin: auto 0 auto auto;
}
/* Right-anchored boxes align text to the anchored edge — a right-anchored
 * ragged-LEFT block leaves its visual edge floating mid-frame. Left anchors
 * already sit on their edge. */
.sk-lockup[data-box="tr"] .sk-lockup-box,
.sk-lockup[data-box="cr"] .sk-lockup-box,
.sk-lockup[data-box="br"] .sk-lockup-box {
  align-items: flex-end;
  text-align: right;
}
.sk-lockup[data-box="center"]:not([data-layout^="split-"], [data-layout^="overlap-"]) {
  /* Bleed (and every non-column layout): pin the flex stage's cross axes so
   * margin:auto on the box cannot drift when only the text column renders
   * (imageless) or the band is taller than the copy block. */
  justify-content: center;
  align-items: center;
}
.sk-lockup[data-box="center"] .sk-lockup-box {
  margin: auto;
  align-items: center;
  text-align: center;
  max-width: 760px;
}
/* CENTER COPY — one treatment for section-head align=center (data-align on
 * the section root) and hero box=center (data-box on the lockup). Flex
 * columns center ITEMS via align-items, but desc lines and action pairs
 * need explicit text-align / justify-content — text-align on the box does
 * not reach flex-item innards. */
.sk-lockup[data-box="center"] .sk-lockup-desc {
  text-align: center;
}
.sk-lockup[data-box="center"] .sk-lockup-actions {
  justify-content: center;
  align-self: center;
}
/* Overlay copy inks: the dark chrome remaps the INK SET wholesale — the
 * same mechanism as the tone axis, so EVERY part that reads the tokens goes
 * near-white for free, instead of a hand-picked list that leaves the rest
 * page-dark and invisible over the wash. Values are FIXED near-whites,
 * never brand tokens: they sit on the fixed dark base, not the board's
 * palette. Scoped to lockups with a real photo — an ABSENT media slot (no
 * media element at all) reflows to the no-media surface posture below, where
 * the page's own inks must keep reading. The COLUMN and STACKED layouts opt
 * out (split and overlap text sits on brand surface beside the media, not
 * over it, so it keeps the page inks — see the layout axis at the end of
 * this section). */
.sk-lockup[data-box]:where(:not([data-box=""])):not(
    [data-layout^="split-"],
    [data-layout^="overlap-"]
  ):has(> .sk-lockup-media),
[data-layout="bleed"] .sk-on-media {
  --sk-ink: var(--sk-overlay-ink);
  --sk-ink-muted: var(--sk-overlay-ink-muted);
  --sk-accent: var(--sk-overlay-ink-muted);
  --sk-stroke: var(--sk-overlay-stroke);
  --sk-surface: rgba(255, 255, 255, 0.14);
  --sk-card: rgba(255, 255, 255, 0.12);
  --sk-field: rgba(255, 255, 255, 0.12);
  --sk-btn-bg: var(--sk-overlay-ink);
  --sk-btn-ink: rgb(14, 12, 10);
}

/* The NO-MEDIA posture — NO media element at all (the deliberately imageless
 * composition: the template omits the slot rather than emitting an empty
 * `<img src="">`) reflows the lockup instead of leaving a hole: the overlay
 * drops the dark base + wash and returns to the band's own inks (with NO photo
 * intended, an imageless billboard is type on a surface, never a black band).
 * The part inks need no restoring: the near-white remap above is scoped
 * POSITIVELY to lockups that have a media element, so it never applies here.
 *
 * It paints --sk-bg — the BAND's background, which the tone axis resolves —
 * not --sk-surface. Hardcoding the surface TINT meant an imageless hero always
 * came out a shade darker than the page it sat on, reading as a stray block
 * behind the headline with no way to author it flush. Now the default tone is
 * the page (flush, as a hero usually wants), and `tone: "surface"` is how you
 * ASK for the block — the same one axis every other band uses. */
.sk-lockup[data-box]:where(:not([data-box=""])):not(:has(> .sk-lockup-media)) {
  background-color: var(--sk-bg);
  color: inherit;
  /* THE PAGE RAIL. With no photo, the band's surface IS the page surface —
   * so the copy must stand on the page's one left edge (--sk-page-pad-x,
   * shared by every content band and the chrome bars), not the
   * density-scaled frame pad. The frame pad measures air inside a
   * photograph's frame; with no photograph it just put the headline on a
   * rail nothing else on the page uses (67px vs 24px at editorial density —
   * the hero read as misaligned against its own page). Horizontal only,
   * through the pad-x seam the edge vars already resolve: the vertical
   * frame air is the band's breathing and keeps the density scale. Rides
   * the page cap's rail formula (the band-gutter home) for the same
   * reason — an imageless hero's headline stands on the capped rail with
   * the rest of the page's content. */
  --sk-lockup-pad-x: max(var(--sk-page-pad-x), calc((100% - var(--sk-page-max, 200vw)) / 2));
}
.sk-lockup[data-box]:where(:not([data-box=""])):not(:has(> .sk-lockup-media))::before {
  content: none;
}

/* ── The lockup LAYOUT axis (data-layout) ─────────────────────────────────
 * bleed is the default and OWNS everything above (media fills the band, the
 * text box floats at its data-box anchor over a dark veil). These rules are
 * ADDITIVE overrides layered after that block — bleed is untouched. They tie
 * the bleed rules on specificity and win by SOURCE ORDER, so they must stay
 * after it. The lockup owns GEOMETRY only; every colour/face/ink still comes
 * from the theme tokens (§3). The layout is PURE geometry — the raised card
 * is the orthogonal data-card treatment at the end of this block.
 *
 * THE COLUMN LAYOUTS — split and overlap share one skeleton: media and text
 * side by side on brand surface (no veil, no ink remap: the remap opts them
 * out above, so --sk-* stay the page inks). The band becomes a column grid;
 * the media goes from absolute-fill to a relative column. Overlap
 * re-templates the grid to THREE tracks (side / lap / side) and the media
 * and text box SHARE the named lap track — a real designed overlap region,
 * never a negative-margin graze (the box's z:2 paints it over the media).
 * The STACKED SPLITS (split-t/split-b) are the same skeleton rotated 90° —
 * one column, text and media as rows, the media a letterbox PLATE (its own
 * block after the shared rules below) — the mobile posture every column
 * layout already collapses to, promoted to a desktop-choosable value.
 *
 * THE SIDE IS THE VALUE'S JOB, NEVER THE BOX'S: split-l/overlap-l put the
 * text column on the LEFT, split-r/overlap-r on the RIGHT, split-t/split-b
 * put the text row ABOVE or BELOW the plate. The box aligns
 * the text WITHIN the space it owns — on bleed the whole band, here its own
 * column or row: the vertical third (t/c/b) sets the self-align below,
 * and the horizontal third (l/c/r) keeps the bleed alignment rules above
 * (left anchors read from the gutter, right anchors align to the trailing
 * edge, center centers; a stacked box also rides justify-self in its own
 * block — a full-width row needs the block PLACED, not just its rag set).
 * The box never flips the lockup. */
.sk-lockup:is([data-layout^="split-"], [data-layout^="overlap-"]) {
  display: grid;
  grid-template-columns: 1fr 1fr;
  overflow: hidden;
  isolation: isolate;
  border-radius: var(--sk-lockup-radius, var(--sk-radius));
  min-height: var(--sk-lockup-min-h, var(--sk-stage-h));
  padding: 0;
  background-color: var(--sk-surface);
  color: var(--sk-ink);
}
/* AN OVERLAP STANDS FULL HEIGHT — the manifest pins it (layout's `forces`,
 * beside the `card` pin it already carried) and this is the render backstop,
 * so the coupling can't be lost through any door.
 *
 * It is a rule about the PHOTO's shape, not about drama. The overlap's media
 * has no declared ratio: it fills a wide column whose height is the band's,
 * so at the resting stage (a fixed 560px) the crop tracks viewport WIDTH
 * alone and slides from 1.16 at 1024 to 1.63 at 1440 to 2.78 at 2560 — the
 * same photograph served as a normal landscape and then as a letterbox
 * sliver, purely because the window got wider. Full height makes both
 * dimensions scale together and the crop settles near 1.0 on the 16:10
 * majority. It also gives the lapping card a stage deep enough to lap OVER,
 * which is the whole point of the layout.
 *
 * The neighbours already solved this and are deliberately untouched: a
 * STACKED split declares --kit-aspect-plate, and every column layout at
 * phone width declares 4/3. Those are constants; this is a proportion of the
 * window — the closest an overlap can get while the band height is what the
 * media reads.
 *
 * The phone stack keeps hugging its content: the ≤640px block below zeroes
 * min-height and gives the media its own ratio, and it wins on source order.
 * This sets only the VAR — the column block's own min-height rule consumes
 * it — so nothing here outranks that. */
.sk-lockup[data-layout^="overlap-"] {
  --sk-lockup-min-h: calc(100svh - var(--sk-chrome-push, 0px));
}
[data-inset="page"] .sk-lockup[data-layout^="overlap-"] {
  --sk-lockup-min-h: calc(100svh - var(--sk-chrome-push, 0px) - 2 * var(--sk-page-pad-x));
}
.sk-lockup:is([data-layout^="split-"], [data-layout^="overlap-"]) .sk-lockup-media {
  position: relative;
  inset: auto;
  width: 100%;
  height: 100%;
  border-radius: 0;
  background: none;
}
/* The veil belongs to bleed only. */
.sk-lockup:is([data-layout^="split-"], [data-layout^="overlap-"])::before {
  content: none;
}
/* The text column: its own frame inset, vertically centred by default,
 * never the bleed max-width clamp or the box's anchor margins. */
.sk-lockup:is([data-layout^="split-"], [data-layout^="overlap-"]) .sk-lockup-box {
  position: relative;
  z-index: 2;
  margin: 0;
  max-width: none;
  padding: var(--sk-lockup-pad, var(--sk-frame-pad));
  align-self: center;
}
/* split-l / split-t lead with the text. Media is the source-order-first
 * child, so the text needs order:-1 to lead; split-r / split-b keep source
 * order. (The overlap values place both items EXPLICITLY below — order is
 * moot.) */
.sk-lockup:is([data-layout="split-l"], [data-layout="split-t"]) .sk-lockup-box {
  order: -1;
}
/* The box's vertical third → the text's self-alignment within the space it
 * owns — the column on a side layout, the (possibly stretched) row on a
 * stacked one (its horizontal third rides the bleed alignment rules above,
 * untouched). */
.sk-lockup:is([data-layout^="split-"], [data-layout^="overlap-"]):is(
    [data-box="tl"],
    [data-box="tr"]
  )
  .sk-lockup-box {
  align-self: start;
}
.sk-lockup:is([data-layout^="split-"], [data-layout^="overlap-"]):is(
    [data-box="bl"],
    [data-box="br"]
  )
  .sk-lockup-box {
  align-self: end;
}
/* box=center on column layouts — the split block above zeros the bleed
 * anchor margins, so re-pin the text block to the CENTER of its column.
 * When the media slot is deliberately ABSENT the second column is gone:
 * collapse to one track and center on the full stage — keyed on the absent
 * media ELEMENT (the templates omit the slot; `src=""` never renders), the
 * same fact the bleed no-media posture reads.
 * The MEASURE is the deliberate exception to the split block's
 * `max-width: none`: left-aligned copy in a column is already measured by
 * the column itself, but CENTERED copy has no ragged edge to read against,
 * so a wide column (or the collapsed one-track case) would sprawl. It rides
 * min(760px, 100%) — the bleed clamp, floored so it can never exceed the
 * track it sits in. */
.sk-lockup:is([data-layout^="split-"], [data-layout^="overlap-"])[data-box="center"]
  .sk-lockup-box {
  justify-self: center;
  align-items: center;
  text-align: center;
  max-width: min(760px, 100%);
}
.sk-lockup:is([data-layout="split-l"], [data-layout="split-r"], [data-layout^="overlap-"]):not(
    :has(> .sk-lockup-media)
  ) {
  grid-template-columns: 1fr;
}
/* Overlap's explicit side/lap spans would strand the lone box in IMPLICIT
 * tracks of the collapsed grid — reset it to the single track. (With one
 * grid item, split-l's order:-1 is moot, so only overlap needs a reset.) */
.sk-lockup:is([data-layout^="overlap-"]):not(:has(> .sk-lockup-media)) .sk-lockup-box {
  grid-column: auto;
  grid-row: auto;
  justify-self: center;
}
/* THE STACKED SPLITS — the column skeleton rotated: one column, text and
 * media as ROWS (split-t text above via the order rule, split-b text below
 * by source order — the value still names the text's side). In a stack the
 * media has no band height to fill ("height: 100%" of an auto row is
 * circular), so it renders as a letterbox PLATE: the --kit-aspect-plate
 * ratio, read through --sk-image-aspect so a photo that declares its own
 * data-aspect wins (the image atom's context pattern). The band keeps the
 * skeleton's stage min-height; when the band is taller than its content
 * (height=full) align-content stretches both auto rows — the plate deepens
 * (cover crops around the focal) and the text row opens, its box still
 * riding the vertical-third align-self rules above.
 *
 * THE MEASURE: a side split's copy is measured by its own column, but a
 * stacked text row runs the full band — without a clamp the lines run wall
 * to wall. Every stacked box rides the bleed clamp (min(760px, 100%)); the
 * horizontal third then PLACES the clamped block — right anchors pin to the
 * trailing edge below, center rides the box=center rule above, and left is
 * the stretch-clamp default. */
.sk-lockup:is([data-layout="split-t"], [data-layout="split-b"]) {
  grid-template-columns: 1fr;
}
.sk-lockup:is([data-layout="split-t"], [data-layout="split-b"]) .sk-lockup-media {
  aspect-ratio: var(--sk-image-aspect, var(--kit-aspect-plate));
  height: auto;
}
.sk-lockup:is([data-layout="split-t"], [data-layout="split-b"]) .sk-lockup-box {
  max-width: min(760px, 100%);
}
.sk-lockup:is([data-layout="split-t"], [data-layout="split-b"]):is(
    [data-box="tr"],
    [data-box="cr"],
    [data-box="br"]
  )
  .sk-lockup-box {
  justify-self: end;
}

/* OVERLAP's lap — a NAMED grid track both items span, not an overflow
 * trick: the grid becomes side / lap / side, the media spans its side + the
 * lap, the text box spans the lap + its side, and the shared middle track
 * IS the overlap — a designed width the composition reads at a glance. The
 * media's side track runs wider (1.15fr vs 0.85fr): the photo dominates and
 * the text visibly sits ON it, the classic editorial proportion. The box
 * always crosses as the CARD (the manifest pins card=card on overlap and
 * the data-card block below backstops it — bare page ink over the photo
 * seam is illegible). Both items pin to grid-row 1 — with
 * definite columns and auto rows, placement would dodge the overlap by
 * wrapping the box to a second row. */
.sk-lockup[data-layout^="overlap-"] {
  --sk-lockup-lap: clamp(4rem, 16vw, 14rem);
}
.sk-lockup[data-layout="overlap-l"] {
  grid-template-columns: 0.85fr var(--sk-lockup-lap) 1.15fr;
}
.sk-lockup[data-layout="overlap-r"] {
  grid-template-columns: 1.15fr var(--sk-lockup-lap) 0.85fr;
}
.sk-lockup[data-layout="overlap-l"] .sk-lockup-media {
  grid-column: 2 / 4;
  grid-row: 1;
}
/* The box: justify-self pins it to the SEAM side of its area, so a
 * width-clamped card (data-card's 34rem) always covers the whole lap track
 * on a wide viewport instead of stranding by the outer edge. */
.sk-lockup[data-layout="overlap-l"] .sk-lockup-box {
  grid-column: 1 / 3;
  grid-row: 1;
  justify-self: end;
}
.sk-lockup[data-layout="overlap-r"] .sk-lockup-media {
  grid-column: 1 / 3;
  grid-row: 1;
}
.sk-lockup[data-layout="overlap-r"] .sk-lockup-box {
  grid-column: 2 / 4;
  grid-row: 1;
  justify-self: start;
}
/* THE CARD IS ALWAYS INSET FROM THE BAND. An overlap card reads as a card
 * only if the band shows ABOVE and BELOW it — flush to both edges it stops
 * being a card breaking a seam and becomes a full-height panel beside a
 * photo, which is the "looks broken" state. The card's height is its
 * CONTENT's (align-self is the box anchor, never stretch), so a tall
 * specimen in a resting-stage band left ~30px of band top and bottom.
 *
 * The inset is a real MARGIN, not band padding: padding would inset the
 * media too, and the photo must keep reaching the band's edges. The band's
 * height is a `min-height`, so a card taller than the resting stage GROWS
 * the band instead of crowding it — the air is guaranteed at every content
 * length. One frame-pad, matching the card's own interior padding, so the
 * card sits as far from the band edge as its words sit from its own. */
.sk-lockup[data-layout^="overlap-"] .sk-lockup-box {
  margin-block: var(--sk-band-pad);
}

/* SMALL SCREENS — two half-width columns can't hold a phone (the text
 * column squeezes to a word a line), so every column layout STACKS: the
 * media becomes a cropped band on top (the atom's object-fit/focal keep the
 * subject), the text follows on brand surface, and the stage min-height
 * yields to content. The stacked splits are ALREADY the stack — they join
 * only the shared crop (a desktop letterbox plate is a sliver at phone
 * width; 4/3 is the kit's one mobile media band) and the min-height yield.
 * Overlap's lap ROTATES with the stack — the text box
 * pulls UP over the media's bottom edge, inset by the frame pad so the
 * photo reads around it. */
@media (max-width: 640px) {
  .sk-lockup:is([data-layout^="split-"], [data-layout^="overlap-"]) {
    grid-template-columns: 1fr;
    min-height: 0;
  }
  .sk-lockup:is([data-layout^="split-"], [data-layout^="overlap-"]) .sk-lockup-media {
    aspect-ratio: 4 / 3;
    height: auto;
  }
  /* Media leads the stack — undo split-l's text-first order. split-t KEEPS
   * its text-first order: above/below is the author's chosen stack order,
   * not a desktop artifact the phone must undo. */
  .sk-lockup[data-layout="split-l"] .sk-lockup-box {
    order: 0;
  }
  /* Overlap's desktop placement is explicit (side/lap/side spans) — reset
   * both items to auto flow in the one-column stack (media first by source
   * order), then rotate the lap: the text box pulls UP over the media's
   * bottom edge, inset by the frame pad so the photo reads around it. */
  .sk-lockup[data-layout^="overlap-"] .sk-lockup-media,
  .sk-lockup[data-layout^="overlap-"] .sk-lockup-box {
    grid-column: auto;
    grid-row: auto;
  }
  .sk-lockup[data-layout^="overlap-"] .sk-lockup-box {
    margin: calc(-1 * clamp(2.5rem, 12vw, 4rem)) var(--sk-frame-pad) 0;
    justify-self: center;
  }
}

/* THE STRIP — the shallow mid-page conversion band (the cta-banner draft
 * folded in: its content was a strict lockup subset, and its ONE new idea
 * was the INLINE AXIS — the copy stack and the action pair side by side at
 * a sub-hero register). Same posture discipline as the other values: these
 * rules tie the earlier lockup rules on specificity and win by SOURCE
 * ORDER. The strip keeps bleed's whole backdrop machinery untouched — an
 * optional photo still rides the absolute media fill, the tonality veil,
 * and the near-white ink remap above (none of those rules exclude strip) —
 * so there is NO new image plumbing here, only band + box geometry. */
.sk-lockup[data-layout="strip"] {
  /* The band HUGS its content — a strip is punctuation between bands,
   * never a stage: no stage min-height, whatever the height axis says
   * (the min posture, pinned — this rule outranks [data-height] by source
   * order). The frame inset above is the strip's only air. */
  --sk-lockup-min-h: 0px;
  min-height: 0;
  /* The SUB-HERO beat: the gap derives from the stepped-down title below
   * (the data-size="s" formula), not the l-register's h1. */
  --sk-lockup-gap: calc(var(--kit-type-base) * 0.4 + var(--sk-text-h3-size) * 0.23);
}
/* The inline axis — the box runs the band's full width as a two-track
 * grid: the copy stack (badge/eyebrow/title/desc) flows down track 1, the
 * action pair holds track 2, the band's width between them. Grid, not a
 * wrapper row: the box's children are flat siblings (the one lockup markup,
 * §2), so the copy "column" is track placement. ROW-GAP stays 0 — the
 * actions span phantom rows below, and a grid gap would open them — so the
 * copy rhythm rides strip-scoped margins carrying the FULL beat
 * (gap × factor, not the flex-gap modifiers' factor − 1). Anchor margins,
 * the center clamp, and center text-align all stand down: the strip owns
 * its own geometry (start-aligned copy, trailing actions). */
.sk-lockup[data-layout="strip"] .sk-lockup-box {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: var(--kit-space-5);
  row-gap: 0;
  width: 100%;
  max-width: none;
  margin: 0;
  text-align: start;
}
.sk-lockup[data-layout="strip"] .sk-lockup-box > * {
  grid-column: 1;
}
.sk-lockup[data-layout="strip"] .sk-lockup-box > .sk-badge {
  margin-bottom: calc(var(--sk-lockup-gap) * var(--sk-rhythm-badge, 1));
}
.sk-lockup[data-layout="strip"] .sk-lockup-box > :is(.sk-eyebrow, .sk-detail) {
  margin-bottom: calc(var(--sk-lockup-gap) * var(--sk-rhythm-label, 0.5));
}
.sk-lockup[data-layout="strip"] .sk-lockup-box > .sk-lockup-title {
  margin-bottom: 0;
}
.sk-lockup[data-layout="strip"] .sk-lockup-box > .sk-lockup-desc {
  margin-top: calc(var(--sk-lockup-gap) * var(--sk-rhythm-title, 1));
}
/* The SUB-HERO register — the draft ran its line at the heading-3 step, so
 * the strip's title reads that token (a ramp INDIRECTION, §3 — the markup
 * stays the one lockup shape at data-text-style="heading-1"); the desc
 * steps back from the l-register's lede to plain body. Both keep the
 * measure. */
.sk-lockup[data-layout="strip"] .sk-lockup-title {
  font-size: var(--sk-text-h3-size);
  max-width: var(--sk-measure);
}
.sk-lockup[data-layout="strip"] .sk-lockup-desc {
  font-size: var(--kit-type-base);
  text-align: start;
}
/* The action pair: track 2, centered against the WHOLE copy stack — the
 * span covers every copy row the one lockup markup can emit (badge,
 * eyebrow, title, desc: four); absent rows are empty auto rows at 0 height
 * (row-gap is 0 above), so the alignment area is exactly the copy block.
 * The departure margin stands down — beside the copy there is no adjacency
 * to phrase. */
.sk-lockup[data-layout="strip"] .sk-lockup-box > .sk-lockup-actions {
  grid-column: 2;
  grid-row: 1 / span 4;
  align-self: center;
  justify-content: flex-start;
  margin: 0;
}
/* Below the sm rung the axis folds — copy above actions, start-aligned,
 * the departure beat restored above the pair (full factor: row-gap is 0). */
@media (max-width: 640px) {
  .sk-lockup[data-layout="strip"] .sk-lockup-box {
    grid-template-columns: minmax(0, 1fr);
  }
  .sk-lockup[data-layout="strip"] .sk-lockup-box > .sk-lockup-actions {
    grid-column: 1;
    grid-row: auto;
    justify-self: start;
    margin-top: calc(var(--sk-lockup-gap) * var(--sk-rhythm-departure, 1.5));
  }
}

/* ── The lockup CARD treatment (data-card) ────────────────────────────────
 * Orthogonal to the layout: the text box raised as a brand-surface CARD —
 * own inks, shadow, square corners (--sk-card-radius, its own axis like
 * imagery's). On bleed it floats a card ON the photo (the
 * inks re-derive brand values on the card ITSELF, since the root remap left
 * the box near-white); on split it's a card in its column; on overlap it's
 * the editorial card breaking the seam. Sits AFTER the layout blocks so its
 * clamp wins the shared column rule's max-width:none by source order.
 * data-card="" (the default) matches nothing.
 *
 * ONLY BLEED LEAVES THE CHOICE FREE: the manifest couples layout→card
 * (`forces` in lockup/manifest.json — the four splits pin card=none, a card
 * in its own column or row is a pointless chip; the strip pins card=none
 * too, the band IS its own surface; overlap-l/-r pin card=card,
 * bare page ink crossing the seam onto the photo is illegible; the tray
 * holds the knob and the writers pin the value). This selector is the
 * RENDER backstop — a coupled state written through any other door still
 * paints the coupled truth: the splits and the strip refuse the card,
 * overlap always wears it. */
.sk-lockup:is(
    [data-card="true"]:not([data-layout^="split-"], [data-layout="strip"]),
    [data-layout^="overlap-"]
  )
  .sk-lockup-box {
  --sk-ink: var(--kit-foreground);
  --sk-ink-muted: var(--kit-muted);
  --sk-accent: var(--kit-accent);
  --sk-surface: var(--kit-surface);
  --sk-card: var(--kit-surface);
  background-color: var(--sk-card);
  color: var(--sk-ink);
  padding: var(--sk-lockup-pad, var(--sk-frame-pad));
  border-radius: var(--sk-card-radius);
  max-width: 34rem;
}
/* THE LIFT IS THE BLEED CARD'S ALONE. A shadow says "this floats above the
 * page", which is true of exactly one card: the bleed card, hovering on a
 * full-bleed photograph. An OVERLAP card sits IN the layout — it shares the
 * lap track with the media as a designed seam — so it rests flat, and a
 * shadow there reads as a widget dropped on the composition.
 *
 * Set here rather than set-then-cancelled above: the shared block's
 * `:is(… :not(…) …)` scores (0,4,0), so an `overlap` override could only
 * win by inflating its own selector to fight it. One rule, one truth. */
.sk-lockup[data-card="true"]:not(
    [data-layout^="split-"],
    [data-layout="strip"],
    [data-layout^="overlap-"]
  )
  .sk-lockup-box {
  box-shadow: 0 var(--kit-space-3) var(--kit-space-6) rgba(0, 0, 0, 0.28);
}

/* ── The image ──────────────────────────────────────────────────────────────────────────
 * The content-MEDIA atom: every photo or clip a section renders is an
 * `.sk-image` — an `<img>`, or the `<video>` twin the sitescript VIDEO-MEDIA
 * pass swaps in when an image slot's value names a video (Image/Video: a
 * video renders anywhere an image does, so every selector here is
 * `:is(img, video)` and the posture below — cover, focal seam, aspect seam,
 * tonality filter — dresses both elements identically; context classes
 * layer geometry on top). One class, one base posture, one handle for
 * editing UI. Marks are exempt: logos and avatars keep their own classes
 * (contain / circle posture, not content photography).
 * The atom reads two VAR SEAMS rather than owning axes of its own (its
 * old keyword tables — data-aspect/data-focal/data-frame on the bare img —
 * were retired machinery from before the .sk-media box; nothing stamped
 * them): --sk-image-aspect is the context's ratio ("layout owns the ratio,
 * the image may override" — sections set it off their OWN grammars), and
 * --sk-focal is the crop pair. NOTE the bare atom's pair semantics are
 * object-position's (image P% pinned to box P%) — TRUE focal centering
 * needs the .sk-media BOX below; a focal-bearing template always wraps its
 * media in one. */

/* ── THE ASPECT TOKEN TABLE — one home ────────────────────────────
 * The "W:H" → W / H map, spelled ONCE (it was hand-copied in six sheets —
 * the drift class the on-media contract killed for paint). Attribute-only
 * arms set a NEUTRAL token (--sk-aspect) and no paint; each section keeps
 * ONE consumer rule in its own sheet at its exact current rank, reading the
 * token into its own seam (aspect-ratio, --sk-image-aspect, --sk-wall-
 * aspect, --sk-map-aspect). Both attribute spellings ride the same arms.
 * INHERITANCE GUARD (the gallery-wall lesson): the token inherits, so a
 * PER-ITEM consumer must stay presence-keyed — `[data-aspect]` on the item
 * itself — and exclude "auto" where the section's base var must win; an
 * item without the attribute must never read its section's inherited
 * token. Decimal twins (e.g. coverflow's ratio) stay local — calc()
 * needs a number, not a ratio. */
[data-aspect="1:1"],
[data-image-crop="1:1"] {
  --sk-aspect: 1 / 1;
}
[data-aspect="3:2"],
[data-image-crop="3:2"] {
  --sk-aspect: 3 / 2;
}
[data-aspect="2:3"],
[data-image-crop="2:3"] {
  --sk-aspect: 2 / 3;
}
[data-aspect="4:3"],
[data-image-crop="4:3"] {
  --sk-aspect: 4 / 3;
}
[data-aspect="3:4"],
[data-image-crop="3:4"] {
  --sk-aspect: 3 / 4;
}
[data-aspect="4:5"],
[data-image-crop="4:5"] {
  --sk-aspect: 4 / 5;
}
[data-aspect="16:9"],
[data-image-crop="16:9"] {
  --sk-aspect: 16 / 9;
}
[data-aspect="21:9"],
[data-image-crop="21:9"] {
  --sk-aspect: 21 / 9;
}
[data-aspect="2.4:1"],
[data-image-crop="2.4:1"] {
  --sk-aspect: 2.4 / 1;
}
/* auto/none resolve the token too, so a presence-keyed consumer under an
 * aspect-stamped SECTION can never read the ancestor's inherited ratio. */
[data-aspect="auto"],
[data-aspect="none"] {
  --sk-aspect: auto;
}

:is(img, video).sk-image {
  display: block;
  width: 100%;
  aspect-ratio: var(--sk-image-aspect, auto);
  object-fit: cover;
  object-position: var(--sk-focal, center);
  /* Media corner; full-bleed bands zero it at their seam. */
  border-radius: var(--sk-media-radius);
  background: var(--sk-surface);
  /* The IMAGE TONALITY's screen — a section stamping data-image-tone="<id>"
   * sets this var (rules generated from pieces/image-tonalities/, appended
   * to this sheet by the loader), so every photo in the band screens
   * through the same curated filter. */
  filter: var(--sk-image-filter, none);
}
/* An EMPTY src ("") disappears entirely rather than paint a dead-image glyph.
 * A DELIBERATELY imageless composition omits the element instead (the
 * templates gate every optional media slot on {.exists}), so this is now a
 * SAFETY NET for a hand-edited sidecar that blanks a REQUIRED row image —
 * never the mechanism the imageless posture rides. Distinct from a non-empty
 * src that is loading or failed, which keeps the surface underlay above. */
:is(img, video).sk-image[src=""] {
  display: none;
}

/* ── The media BOX (.sk-media) — TRUE focal cropping ─────────────────────
 * The crop WINDOW every focal-bearing photo/clip renders through. A focal
 * point means: pull that point of the image to the CENTER of the window,
 * sliding only as far as coverage allows — the crop clamps at the image's
 * edges, so the box is always covered and the subject sits as close to
 * center as the photo permits. (Raw `object-position: X% Y%` does something
 * else entirely — it pins image-P% to box-P%, leaving the subject off-center
 * by design — which is why the atom above is NOT the focal mechanism.)
 *
 * Mechanics — CSS owns the layout, base.js reports two facts CSS cannot
 * read: (1) the media's intrinsic ratio (unknowable server-side for
 * arbitrary sources), measured once at load into --sk-media-ratio + the
 * data-sk-ratio flag; (2) the data-focal percentage pair split into
 * --sk-focal-x / --sk-focal-y (CSS cannot parse an attribute). Everything
 * responsive — covered size, centering, clamping, every resize — is pure
 * CSS below: the box is a size container, the child is laid out at its
 * exact covered size (max() of the two cover candidates), and `translate`
 * slides it so the focal lands at box center, clamped to the edges. The
 * percentages inside the translate clamp resolve against the CHILD's own
 * size (that is what makes the formula expressible); cq units are the box.
 * Until the ratio lands (and without JS) the child rides a plain centered
 * cover — invisible in practice, since nothing paints before load anyway.
 *
 * REQUIREMENTS: the box must be extrinsically sized by its context
 * (inset 0, a height, an aspect-ratio — size containment gives a
 * content-sized box ZERO height, loudly), and the media element is its
 * direct child. Radius and the surface underlay live on the box (it is the
 * visible unit — reveal/layer stamps belong here too); the tonality filter
 * stays on the media element. */
.sk-media {
  position: relative;
  display: block;
  overflow: hidden;
  container-type: size;
  border-radius: var(--sk-media-radius);
  background: var(--sk-surface);
}
.sk-media > :is(img, video).sk-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* The covered size below is DELIBERATELY larger than the box on the
   * overflowing axis — that overflow IS the crop's slide room. A host reset
   * capping replaced elements (`img { max-width: 100% }` — cssremedy,
   * Tailwind preflight, Forma's own reset.css) silently shrinks the
   * child back to the box, so nothing can slide and the focal is ignored:
   * a centered cover that LOOKS plausible while quietly dropping the
   * subject. Declared here so no host stylesheet can defeat the mechanism.
   * (This is the bug that shipped the axe half out of frame.) */
  max-width: none;
  max-height: none;
  object-fit: cover;
  object-position: var(--sk-focal-x, 50%) var(--sk-focal-y, 50%);
  border-radius: 0;
  background: none;
}
.sk-media[data-sk-ratio] > :is(img, video).sk-image {
  inset: auto;
  left: 0;
  top: 0;
  width: max(100cqw, 100cqh * var(--sk-media-ratio));
  height: max(100cqh, 100cqw / var(--sk-media-ratio));
  translate: clamp(100cqw - 100%, 50cqw - var(--sk-focal-x, 50%), 0px)
    clamp(100cqh - 100%, 50cqh - var(--sk-focal-y, 50%), 0px);
}
/* The keyword grammar, on the box: an edge keyword is a 0%/100% focal on
 * that axis (the other axis stays centered). A percentage PAIR in
 * data-focal is split into the vars by base.js. */
.sk-media[data-focal="top"] {
  --sk-focal-y: 0%;
}
.sk-media[data-focal="bottom"] {
  --sk-focal-y: 100%;
}
.sk-media[data-focal="left"] {
  --sk-focal-x: 0%;
}
.sk-media[data-focal="right"] {
  --sk-focal-x: 100%;
}
/* The bare atom's empty-src safety net, box form: the whole window
 * disappears with its blanked media. */
.sk-media:has(> [src=""]) {
  display: none;
}

/* LINKED imagery settles (SITE-KIT.md §8): ONE slow zoom for every photo
 * inside an anchor — the kit-wide gesture, defined once here so sections
 * never re-author it (collection-tiles and product-grid used to carry
 * copies). The linking CONTEXT owns the clipping (an overflow:hidden
 * frame — the lockup's data-box stage, the product thumb), the atom owns
 * the settle. The reduced-motion floor at the end of this sheet collapses
 * it. */
[data-sk] a .sk-image {
  transition: transform 0.9s var(--sk-ease-out);
}
[data-sk] a:hover .sk-image {
  transform: scale(1.05);
}

/* ── The map — the location atom ──────────────────────────────────────
 * One element (.sk-map), two registers:
 *   EMBED — a keyless Google Maps iframe (.sk-map-embed,
 *   maps.google.com/maps?q=…&output=embed) filling the frame, with the
 *   TONE VEIL (.sk-map-veil) authored beside it. Google's palette is
 *   tamed in two steps: the CSS-filter TINT neutralizes the tiles
 *   (grayscale by default, a dark invert under the inverted tone), then
 *   the veil lays the band's OWN paint over them — a --sk-bg wash at
 *   --sk-map-tint strength — so the embed wears the THEME under every
 *   tone for free: paper warms it, a surface band takes the surface's
 *   cast, an accent band reads as a wash of the accent (deliberately
 *   stronger there), and the inverted band's near-black keeps the dark
 *   flip neutral. data-look="dark" forces a fully MONOCHROME night flip
 *   on ANY tone (grayscale before the invert strips every hue; the veil
 *   stands down to 0% — any band-paint wash would reintroduce a cast);
 *   data-look="natural" opts out of both. (True custom map styling needs
 *   the Maps JS API + a billed key; the filter + veil seam is where it
 *   would slot.)
 *   PLACEHOLDER — with no embed child, the element paints a deterministic
 *   board-tinted abstract street grid with an accent pin: the
 *   offline/dev/specimen stand-in, doubling as the loading underlay the
 *   iframe fades in over.
 * Axes:
 *   data-aspect  square | landscape | wide — the frame's ratio; unset (or
 *                "landscape") is 4/3. Sets the --sk-map-aspect VAR (same
 *                contract as the image atom).
 *   data-pin     center | upper-left | upper-right | lower-left |
 *                lower-right — where the PLACEHOLDER's pin sits; the embed
 *                brings Google's own marker, so the pin (and glow) only
 *                renders with no embed child.
 *   data-look    tinted (default) | dark | natural — the embed's treatment.
 *   data-place-card  "true" shows Google's place summary card; any other
 *                value (the default) CLIPS it — the iframe bleeds above
 *                the frame by --sk-map-crop and the card, a fixed overlay
 *                in the embed's upper-left, falls outside the clip. The
 *                classic iwloc=near URL lever is DEAD in the current
 *                embed renderer, so the clip is the mechanism we own; the
 *                bottom attribution is never touched. The crop is clamped
 *                so a short frame keeps its map, and the marker rides
 *                crop/2 above the visible center — designed drift.
 *   data-interactive  "true" lets visitors pan/zoom in place; otherwise
 *                the embed rests as a STILL — pointer-events off (pair
 *                with tabindex="-1" on the iframe), so Google's floating
 *                controls (the D-pad / zoom cluster) can never bloom and
 *                the band never captures the page's scroll. */
.sk-map {
  --sk-map-street: color-mix(in srgb, var(--sk-ink) 8%, transparent);
  --sk-map-avenue: color-mix(in srgb, var(--sk-ink) 14%, transparent);
  /* The tinted register's filter pre-boosts SATURATION before the partial
   * grayscale: Google's marker is the only mass of saturated pixels in the
   * frame (the tiles are pastel, near-neutral), so the boost singles it
   * out — the 22% color the grayscale leaves is a bold, lively red instead
   * of a muddy gray blob — while the tiles stay tamed and the veil still
   * lays the theme's paint over everything. The marker is not reachable as
   * an element (cross-origin iframe; re-skinning it keylessly is the Maps
   * JS API seam), so pixel-wise filters are the lever we own. */
  --sk-map-filter: saturate(2.2) grayscale(0.78);
  --sk-map-tint: 30%;
  --sk-map-veil: color-mix(in srgb, var(--sk-bg) var(--sk-map-tint), transparent);
  --sk-map-crop: min(180px, 45%);
  --sk-map-pin-x: 50%;
  --sk-map-pin-y: 44%;
  display: block;
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--sk-map-aspect, 4 / 3);
  /* A bounded media surface, so it takes the media corner (was the control
   * radius, which `full` blew up to a lozenge). */
  border-radius: var(--sk-media-radius);
  border: var(--sk-border-width) solid var(--sk-stroke);
  background:
    linear-gradient(
      64deg,
      transparent calc(50% - 3px),
      var(--sk-map-avenue) calc(50% - 3px) calc(50% + 3px),
      transparent calc(50% + 3px)
    ),
    repeating-linear-gradient(0deg, transparent 0 64px, var(--sk-map-avenue) 64px 67px),
    repeating-linear-gradient(90deg, transparent 0 88px, var(--sk-map-avenue) 88px 91px),
    repeating-linear-gradient(0deg, transparent 0 21px, var(--sk-map-street) 21px 22px),
    repeating-linear-gradient(90deg, transparent 0 29px, var(--sk-map-street) 29px 30px),
    var(--sk-card);
}
/* The glow under the pin — a quiet accent halo so the spot reads at a
 * glance. */
.sk-map::before {
  content: "";
  position: absolute;
  left: var(--sk-map-pin-x);
  top: var(--sk-map-pin-y);
  width: 72px;
  height: 72px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    color-mix(in srgb, var(--sk-accent) 22%, transparent),
    transparent 72%
  );
}
/* The pin — an accent dot ringed in the tone background, lifted off the map. */
.sk-map::after {
  content: "";
  position: absolute;
  left: var(--sk-map-pin-x);
  top: var(--sk-map-pin-y);
  width: 14px;
  height: 14px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: var(--sk-accent);
  border: 3px solid var(--sk-bg);
  box-shadow: 0 2px 10px color-mix(in srgb, var(--sk-ink) 32%, transparent);
}
.sk-map[data-pin="upper-left"] {
  --sk-map-pin-x: 30%;
  --sk-map-pin-y: 28%;
}
.sk-map[data-pin="upper-right"] {
  --sk-map-pin-x: 70%;
  --sk-map-pin-y: 28%;
}
.sk-map[data-pin="lower-left"] {
  --sk-map-pin-x: 30%;
  --sk-map-pin-y: 68%;
}
.sk-map[data-pin="lower-right"] {
  --sk-map-pin-x: 70%;
  --sk-map-pin-y: 68%;
}

/* The embed register — the iframe fills the frame over the placeholder art,
 * wearing the tint. */
.sk-map-embed {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  border: 0;
  filter: var(--sk-map-filter);
}
/* The card CLIP — without an explicit data-place-card="true" the iframe
 * bleeds upward by the crop and Google's upper-left place card falls
 * outside the frame (top + height over-constrain inset's bottom, which
 * yields). See the axis notes above. */
.sk-map:not([data-place-card="true"]) .sk-map-embed {
  top: calc(-1 * var(--sk-map-crop));
  height: calc(100% + var(--sk-map-crop));
}
/* The STILL posture — without data-interactive="true" no pointer ever
 * reaches the embed, so its floating controls never appear and wheel/drag
 * stay with the page. */
.sk-map:not([data-interactive="true"]) .sk-map-embed {
  pointer-events: none;
}
/* The tone veil — the band's own paint over the neutralized tiles (the
 * register notes above). A real element beside the iframe, never a pseudo
 * (the pin pseudos belong to the placeholder register) and never a blend
 * mode (a plain translucent wash composites reliably over cross-origin
 * iframe content); pointer-events stay off so the map keeps panning and
 * zooming under it. */
.sk-map-veil {
  position: absolute;
  inset: 0;
  background: var(--sk-map-veil);
  pointer-events: none;
}
/* On an inverted band a light Google map is a glowing hole in the page —
 * invert + hue-rotate keeps land/water hues while flipping it dark. */
[data-tone="inverted"] .sk-map {
  --sk-map-filter: invert(0.9) hue-rotate(180deg) grayscale(0.5) contrast(0.9);
}
/* On the accent band the veil doubles down: the tiles read as a wash of
 * the band's own paint, joining the accent field instead of sitting on it
 * as a gray hole. */
[data-tone="accent"] .sk-map {
  --sk-map-tint: 50%;
}
/* The DARK look — a fully MONOCHROME night flip, forced on any tone (an
 * explicit look outranks the tone remaps: same specificity, later in the
 * sheet). grayscale(1) BEFORE the invert strips every hue, the stronger
 * invert + brightness drop push the tiles properly dark, and the veil
 * stands down entirely: ANY band-paint wash over neutral tiles would
 * reintroduce a color cast (and a light band's paint would fog them
 * toward gray) — monochrome means the veil has nothing to add.
 * The MARKER stays legible through a luminance trick: saturate + a
 * hue-rotate swing the marker's red toward BLUE — the lowest grayscale
 * luma coefficient — so it grayscales DARK and the invert lands it
 * distinctly LIGHTER than the tiles and labels around it. Only the
 * marker moves: the tiles are near-neutral, so rotation and saturation
 * barely touch them, and the grayscale(1) after keeps the whole frame
 * monochrome. */
.sk-map[data-look="dark"] {
  --sk-map-filter: saturate(1.5) hue-rotate(240deg) grayscale(1) invert(0.92) brightness(0.85)
    contrast(0.95);
  --sk-map-tint: 0%;
}
.sk-map[data-look="natural"] {
  --sk-map-filter: none;
}
.sk-map[data-look="natural"] .sk-map-veil {
  display: none;
}
/* The pin belongs to the placeholder register only. */
.sk-map:has(.sk-map-embed)::before,
.sk-map:has(.sk-map-embed)::after {
  content: none;
}

/* ── Motion guard ──────────────────────────────────────────────────────────────────── */
/* The blanket floor for everything OUTSIDE the animation framework (the
 * logo wall's marquee, hover transitions): reduced motion collapses every
 * remaining animation/transition to an instant. The framework needs no help
 * here — its armed states exist only under no-preference. */

@media (prefers-reduced-motion: reduce) {
  [data-sk] *,
  [data-sk] *::before,
  [data-sk] *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ── Image tonalities — GENERATED from pieces/image-tonalities/ ── */
[data-image-tone="amber"] {
  --sk-image-filter: sepia(clamp(0, calc(0.3 * var(--sk-tone-filter-intensity, 1)), 1)) saturate(max(0, calc(1 - 0.25 * var(--sk-tone-filter-intensity, 1)))) brightness(max(0, calc(1 - 0.12 * var(--sk-tone-filter-intensity, 1))));
  --sk-image-wash: linear-gradient(rgba(51, 34, 13, clamp(0, calc(0.46 * var(--sk-tone-wash-intensity, 1)), 1)), rgba(26, 17, 8, clamp(0, calc(0.58 * var(--sk-tone-wash-intensity, 1)), 1)));
}
[data-image-tone="ash"] {
  --sk-image-filter: saturate(max(0, calc(1 - 0.45 * var(--sk-tone-filter-intensity, 1)))) brightness(max(0, calc(1 - 0.1 * var(--sk-tone-filter-intensity, 1)))) contrast(max(0, calc(1 - 0.03 * var(--sk-tone-filter-intensity, 1))));
  --sk-image-wash: linear-gradient(rgba(16, 15, 14, clamp(0, calc(0.44 * var(--sk-tone-wash-intensity, 1)), 1)), rgba(16, 15, 14, clamp(0, calc(0.6 * var(--sk-tone-wash-intensity, 1)), 1)));
}
[data-image-tone="onyx"] {
  --sk-image-filter: grayscale(clamp(0, var(--sk-tone-filter-intensity, 1), 1)) brightness(max(0, calc(1 - 0.18 * var(--sk-tone-filter-intensity, 1)))) contrast(max(0, calc(1 + 0.05 * var(--sk-tone-filter-intensity, 1))));
  --sk-image-wash: rgba(12, 12, 12, clamp(0, calc(0.55 * var(--sk-tone-wash-intensity, 1)), 1));
}

/* ── Animation frameworks — GENERATED from pieces/animation-frameworks/ ── */
@media (prefers-reduced-motion: no-preference) {
  html[data-sk-js] [data-anim="fade"]:where(:not(:has([data-sk-reveal], [data-sk-reveal-group], [data-sk-cascade]))),
  html[data-sk-js] [data-anim="fade"] :where([data-sk-reveal]:not([data-sk-cascade])),
  html[data-sk-js] [data-anim="fade"] :where([data-sk-cascade]) > * {
    opacity: 0;
    transform: none;
    transition: opacity calc(0.9s / max(abs(var(--sk-anim-intensity, 1)), 0.1)) ease var(--sk-reveal-delay, 0s);
  }
  html[data-sk-js] [data-anim="rise"]:where(:not(:has([data-sk-reveal], [data-sk-reveal-group], [data-sk-cascade]))),
  html[data-sk-js] [data-anim="rise"] :where([data-sk-reveal]:not([data-sk-cascade])),
  html[data-sk-js] [data-anim="rise"] :where([data-sk-cascade]) > * {
    opacity: 0;
    transform: translateY(calc(26px * var(--sk-anim-intensity, 1)));
    transition: opacity calc(0.7s / max(abs(var(--sk-anim-intensity, 1)), 0.1)) ease var(--sk-reveal-delay, 0s), transform calc(0.7s / max(abs(var(--sk-anim-intensity, 1)), 0.1)) var(--sk-ease-out) var(--sk-reveal-delay, 0s);
  }
  html[data-sk-js] [data-anim="zoom"]:where(:not(:has([data-sk-reveal], [data-sk-reveal-group], [data-sk-cascade]))),
  html[data-sk-js] [data-anim="zoom"] :where([data-sk-reveal]:not([data-sk-cascade])),
  html[data-sk-js] [data-anim="zoom"] :where([data-sk-cascade]) > * {
    opacity: 0;
    transform: scale(max(0, calc(1 - 0.035 * var(--sk-anim-intensity, 1))));
    transition: opacity calc(0.7s / max(abs(var(--sk-anim-intensity, 1)), 0.1)) ease var(--sk-reveal-delay, 0s), transform calc(0.7s / max(abs(var(--sk-anim-intensity, 1)), 0.1)) var(--sk-ease-out) var(--sk-reveal-delay, 0s);
  }
}

/* ── Icon glyphs — GENERATED from pieces/icons/ ── */
.sk-icon-glyph[data-icon="bag"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 8h14l-1.2 11.4a2 2 0 0 1-2 1.6H8.2a2 2 0 0 1-2-1.6L5 8z"/><path d="M9 8V6a3 3 0 0 1 6 0v2"/></svg>');
}
.sk-icon-glyph[data-icon="bookmark"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4h12v17l-6-4-6 4z"/></svg>');
}
.sk-icon-glyph[data-icon="calendar"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round"><rect x="4" y="5" width="16" height="16" rx="2"/><path d="M4 9.5h16M8 3v4M16 3v4"/></svg>');
}
.sk-icon-glyph[data-icon="cart"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 4h2l2.5 11.5a2 2 0 0 0 2 1.5h7.5a2 2 0 0 0 2-1.5L21 8H6"/><circle cx="10" cy="20" r="1.2" fill="black"/><circle cx="17" cy="20" r="1.2" fill="black"/></svg>');
}
.sk-icon-glyph[data-icon="chat"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5h14a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H9.5L5 19v-3.5a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z"/></svg>');
}
.sk-icon-glyph[data-icon="check"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>');
}
.sk-icon-glyph[data-icon="chevron-down"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>');
}
.sk-icon-glyph[data-icon="chevron-left"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M15 6l-6 6 6 6"/></svg>');
}
.sk-icon-glyph[data-icon="chevron-right"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6l6 6-6 6"/></svg>');
}
.sk-icon-glyph[data-icon="close"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg>');
}
.sk-icon-glyph[data-icon="discord"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black"><path d="M20.317 4.37a19.79 19.79 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.865-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.058a.082.082 0 0 0 .031.056 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128c.126-.094.252-.192.372-.291a.074.074 0 0 1 .078-.01c3.928 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .079.009c.12.099.246.198.373.292a.077.077 0 0 1-.006.128 12.3 12.3 0 0 1-1.873.891.077.077 0 0 0-.041.107c.36.698.772 1.363 1.225 1.993a.076.076 0 0 0 .084.029 19.84 19.84 0 0 0 6.002-3.03.077.077 0 0 0 .032-.055c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.029zM8.02 15.331c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>');
}
.sk-icon-glyph[data-icon="github"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-4 1.3-4-2.2-5.5-2.7M15 21v-3.3a3 3 0 0 0-.8-2.2c2.7-.3 5.5-1.3 5.5-6a4.6 4.6 0 0 0-1.3-3.2 4.3 4.3 0 0 0-.1-3.2s-1-.3-3.4 1.3a11.6 11.6 0 0 0-6 0C6.9 2.6 5.9 2.9 5.9 2.9a4.3 4.3 0 0 0-.1 3.2A4.6 4.6 0 0 0 4.5 9.3c0 4.6 2.8 5.6 5.5 6a3 3 0 0 0-.8 2.2V21"/></svg>');
}
.sk-icon-glyph[data-icon="globe"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a15 15 0 0 1 0 18M12 3a15 15 0 0 0 0 18"/></svg>');
}
.sk-icon-glyph[data-icon="heart"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20s-7-4.5-9-9c-1.2-2.8.4-6 3.5-6 2 0 3.5 1 4.5 3 1-2 2.5-3 4.5-3 3.1 0 4.7 3.2 3.5 6-2 4.5-9 9-9 9Z"/></svg>');
}
.sk-icon-glyph[data-icon="info"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round"><circle cx="12" cy="12" r="8.5"/><path d="M12 11v5M12 8h.01"/></svg>');
}
.sk-icon-glyph[data-icon="instagram"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round"><rect x="4" y="4" width="16" height="16" rx="4.5"/><circle cx="12" cy="12" r="3.6"/><path d="M16.6 7.4h.01"/></svg>');
}
.sk-icon-glyph[data-icon="linkedin"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0z"/></svg>');
}
.sk-icon-glyph[data-icon="location"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21s7-5.6 7-11a7 7 0 0 0-14 0c0 5.4 7 11 7 11z"/><circle cx="12" cy="10" r="2.5"/></svg>');
}
.sk-icon-glyph[data-icon="mail"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3.5 7l8.5 6 8.5-6"/></svg>');
}
.sk-icon-glyph[data-icon="menu"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round"><path d="M4 7h16M4 12h16M4 17h16"/></svg>');
}
.sk-icon-glyph[data-icon="pause"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M9.5 5.5v13M14.5 5.5v13"/></svg>');
}
.sk-icon-glyph[data-icon="phone"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3h3l1.5 4-2 1.5a11 11 0 0 0 5 5l1.5-2 4 1.5v3a2 2 0 0 1-2.2 2A15 15 0 0 1 4 5.2 2 2 0 0 1 6 3z"/></svg>');
}
.sk-icon-glyph[data-icon="play"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M8 5.5l10.5 6.5L8 18.5z"/></svg>');
}
.sk-icon-glyph[data-icon="search"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="m20 20-4-4"/></svg>');
}
.sk-icon-glyph[data-icon="settings"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round"><circle cx="12" cy="12" r="3"/><path d="M12 2.5v3M12 18.5v3M5.1 5.1l2.1 2.1M16.8 16.8l2.1 2.1M2.5 12h3M18.5 12h3M5.1 18.9l2.1-2.1M16.8 7.2l2.1-2.1"/></svg>');
}
.sk-icon-glyph[data-icon="star"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.5l2.6 5.3 5.9.85-4.25 4.15 1 5.85L12 16.9l-5.25 2.75 1-5.85L3.5 9.65l5.9-.85z"/></svg>');
}
.sk-icon-glyph[data-icon="tiktok"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black"><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg>');
}
.sk-icon-glyph[data-icon="user"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 21c1.5-3.5 4.5-5 8-5s6.5 1.5 8 5"/></svg>');
}
.sk-icon-glyph[data-icon="x"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>');
}
.sk-icon-glyph[data-icon="youtube"] {
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>');
}
