/* =====================================================================
   lumos-native.css - the no-JS, native-first interaction layer for the
   Lumos starter. Loaded AFTER lumos.css (canonical/verbatim), BEFORE the
   tenant styles.css. Brand-agnostic.

   It ONLY adds what the native mechanism needs on top of the faithful Lumos
   markup + classes (which lumos.css already styles): show/hide bound to the
   native <details> [open] state, the native <dialog> shell reset, CSS
   scroll-snap for the slider, and CSS reveal/marquee. It must NOT re-style the
   Lumos components - lumos.css owns their look. scripts.js adds optional polish
   (dialog wiring, nav/dropdown close affordances, slider controls, ARIA tabs).

   Convention: html.js = JS present (inline <head> flag, before first paint).
   All motion respects prefers-reduced-motion.
   ===================================================================== */

/* ---- Scroll reveal: CSS-only, self-completing on load (content ends visible) ---- */
@keyframes u-reveal-in { from { opacity: 0; transform: translateY(1.25rem); } to { opacity: 1; transform: none; } }
.u-reveal { animation: u-reveal-in 0.6s both; }
.u-reveal-2 { animation-delay: 0.1s; }
.u-reveal-3 { animation-delay: 0.2s; }
.u-reveal-4 { animation-delay: 0.3s; }
@media (prefers-reduced-motion: reduce) { .u-reveal, .u-reveal-2, .u-reveal-3, .u-reveal-4 { animation: none; } }

/* ---- Marquee: CSS @keyframes, configurable via data-* on .u-marquee:
   - data-marquee-speed   seconds for one full cycle (read by scripts.js wireMarquee
                          into --marquee-duration; CSS can't read a numeric attr into
                          a time). Default 30s.
   - data-marquee-loop    "false" = static (no scroll); default loops forever.
   - data-marquee-pause-on-hover  "false" = keep scrolling on hover; default pauses.
   Loop + pause-on-hover are pure-CSS attribute toggles, so they work with NO JS; only
   speed needs the JS hook. The track must hold TWO identical sets of items so the
   -50% shift lands exactly one set over (seamless). Respects reduced-motion. ---- */
.u-marquee { overflow: hidden; }
.u-marquee_track { display: flex; width: max-content; gap: var(--_spacing---space--5, 2rem); animation: u-marquee-x var(--marquee-duration, 30s) linear infinite; }
@keyframes u-marquee-x { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.u-marquee:not([data-marquee-pause-on-hover="false"]):hover .u-marquee_track { animation-play-state: paused; }
.u-marquee[data-marquee-loop="false"] .u-marquee_track { animation: none; }
@media (prefers-reduced-motion: reduce) { .u-marquee_track { animation: none; } }

/* ---- <summary> marker removal for the Lumos toggles we turn into summaries ---- */
.accordion_toggle_button::-webkit-details-marker,
.dropdown_toggle_wrap::-webkit-details-marker,
.nav_menu_summary::-webkit-details-marker { display: none; }
summary.accordion_toggle_button, summary.dropdown_toggle_wrap, summary.nav_menu_summary { list-style: none; cursor: pointer; }

/* ---- Accordion: native <details name> (single-open, no JS). The <details> is
   the .accordion_component (the element whose direct children are the summary +
   the content_wrap); its summary is the .accordion_toggle_button. lumos.css
   styles .accordion_toggle_button / _content_wrap (the latter display:none, was
   JS-driven); we only bind show + icon rotation to the native [open] state. ---- */
details[open].accordion_component .accordion_content_wrap {
  display: block;
  overflow: visible;
}
/* Icon state indicator: Lumos rotated the chevron via its [data-state] var
   (transform: rotate(-180deg * --_state---false)); that JS is stripped and the
   var is inert, so we rebind both rotations to the native [open] state, pinned
   deterministically (not relying on the dead var). Closed = the base downward
   chevron; open = 180deg (upward) - the conventional affordance, same swing
   magnitude Lumos used. */
.accordion_toggle_icon { transition: transform 0.3s ease; }
.accordion_component:not([open]) .accordion_toggle_icon { transform: rotate(0deg); }
details[open].accordion_component .accordion_toggle_icon { transform: rotate(180deg); }
/* The <h3> heading is kept INSIDE the <summary> (for screen-reader heading nav),
   but the flex layout (space-between: text | icon) lives on .accordion_toggle_button
   = the summary. Make the heading wrapper layout-transparent so the text + icon
   spans are the summary's flex items again, exactly as they were direct children
   of the original <button>. lumos.css only zeroes the heading's margins, so this
   drops nothing visible. */
summary.accordion_toggle_button > .accordion_toggle_heading { display: contents; }
@media (prefers-reduced-motion: reduce) { .accordion_toggle_icon { transition: none; } }

/* ---- Dropdown: native <details> (summary = .dropdown_toggle_wrap). lumos.css
   styles dropdown_wrap/_toggle_wrap/_content; we only bind content show + arrow
   to [open]. ---- */
details[open] .dropdown_content { display: block; }
details:not([open]) > .dropdown_content { display: none; }
/* arrow: same dead [data-state] var as the accordion icon (rotate(-180deg *
   --_state---false)); rebind both states to native [open] - closed = base
   (down), open = 180deg (up). */
.dropdown_toggle_arrow { transition: transform 0.3s ease; }
details:not([open]) > .dropdown_toggle_wrap .dropdown_toggle_arrow { transform: rotate(0deg); }
details[open] > .dropdown_toggle_wrap .dropdown_toggle_arrow { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .dropdown_toggle_arrow { transition: none; } }

/* ---- Slider: CSS scroll-snap (Swiper removed). The SCROLL CONTAINER is .slider_element (the
   Swiper viewport), NOT .slider_list (the flex track). Putting overflow-x:auto on the flex track
   left it a dead box - content overflowed but no scroll method (scrollLeft / scrollBy / wheel /
   scrollIntoView) ever engaged; the element is the correct scroll container. The list stays a plain
   flex track (overflow visible) whose slides each get width:calc(100%/--slide-count) (--slide-count
   from data-slider-* via the global table). scripts.js wires the data-slider="next"/"previous" arrows
   + an optional drag + a grid-width scrollbar (no per-slide dots); the track is swipe/wheel-scrollable no-JS. ---- */
.slider_element.swiper {
  overflow-x: auto; overflow-y: hidden; scroll-snap-type: x proximity; scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; scrollbar-width: none;
}
.slider_element.swiper::-webkit-scrollbar { display: none; }
.slider_element.swiper .slider_list { overflow: visible; }   /* the flex track; scroll lives on the element */
/* Full-width crop-left slider (Swiper-equivalent, matches the live G&A .cond_track): while scrolling,
   off-screen cards peek into the RIGHT gutter (the track bleeds to the viewport edge); at scroll-END the
   LAST card rests flush with the content-grid right edge (a "normalised" stop, inline with the section
   content), NOT the viewport edge. How:
   (1) margin-right:-gutter extends the scroll viewport into the gutter so the overflow CLIP happens at the
       viewport edge — that's what makes the right-side peek visible (overflow:auto clips at the box, so the
       box must reach the viewport; clip-path alone can't un-clip it).
   (2) The end-space that lands the last card at the grid edge is a REAL flex item: a ::after pseudo on the
       flex track, gutter-wide. Measured the hard way — neither the scroll container's padding-right NOR a
       flex-item margin-right is counted in scrollWidth here (slides overflow .slider_list via
       overflow:visible, so Chrome ignores the wrapper's end padding/margin); only a real box with a
       definite width is. The ::after adds exactly `gutter` of scrollable content after the last card, so at
       max-scroll the last card's right = element-right(viewport) - gutter = grid edge. Verified -0.2px.
   (3) proximity (not mandatory) scroll-snap + scroll-snap-align:start on the slides: cards snap their
       LEFT edge to the container on rest (left-aligned), while proximity still lets the very END rest
       free (flush) instead of snapping back. This is the G&A native pattern - NO per-slide dots (they
       fight snap on a fractional peek slider). A grid-width custom scroll bar (scripts.js) + arrows +
       native swipe drive it; the track still scrolls without JS.
   gutter is a LENGTH (not %, which would resolve against the bled track). Opt-in via .u-bleed-right on
   .slider_offset; pair with v-slider-crop-left + body/.page_main overflow-x: clip. */
.slider_offset.u-bleed-right {
  --slider-gutter: max(var(--site--margin), calc((100vw - var(--max-width--main)) / 2));
  margin-right: calc(-1 * var(--slider-gutter));
}
.slider_offset.u-bleed-right .slider_list::after { content: ""; flex: 0 0 var(--slider-gutter); align-self: stretch; }

/* Grid-width custom scroll bar. The NATIVE scrollbar can't be used here: the track bleeds to the
   viewport for the right-gutter peek, so its scrollbar bleeds with it. scripts.js injects
   .slider_scrollbar as a grid-width sibling AFTER the bled track and syncs the thumb to scrollLeft;
   the native bar stays hidden (base rule). JS-injected, so no-JS simply has no bar (the track still
   scrolls by swipe/trackpad). */
.slider_scrollbar { position: relative; height: 0.3rem; margin-top: 1.25rem; border-radius: 999px;
  background: color-mix(in srgb, var(--_theme---text) 14%, transparent); touch-action: none; }
.slider_scrollbar_thumb { position: absolute; inset-block: 0; left: 0; min-width: 2rem; border-radius: 999px;
  background: var(--_theme---button-primary--background); cursor: grab; }
.slider_scrollbar_thumb.is-dragging { cursor: grabbing; }
html .slider_list > :not(.u-display-contents, .w-dyn-list),
.slider_list > .u-display-contents > *,
.slider_list > .u-display-contents > .u-display-contents > * { scroll-snap-align: start; }

/* ---- Range slider: native knob baseline (no JS dependency). lumos.css owns the
   track (.form_range_element) and declares `--thumb-color: currentColor`; the export's
   inline <style> overrides it to `transparent` for the JS-present path, which overlays
   Lumos's custom thumb/fill (.form_range_thumb_wrap / .form_range_fill - display:none
   in lumos.css, awaiting Lumos's runtime to un-hide + position via --progress). We
   don't ship that runtime, so under html.js re-assert the visible native knob -
   mirroring the export's own <noscript> (JS-absent) fallback. The thumb's size/shape
   (::-webkit-slider-thumb / ::-moz-range-thumb) is already styled by the export's
   inline <style>; we only restore its colour so it's visible + grabbable.
   scripts.js wireRanges() optionally upgrades to Lumos's custom thumb + progress fill
   (it sets --thumb-color:transparent inline to re-hide this native knob, so the two
   never both show). ---- */
html.js .form_range_wrap { --thumb-color: currentColor; }
/* Give the native thumb an explicit track box (the runnable-track is otherwise
   unstyled - lumos.css styles the .form_range_element wrapper, not the input's own
   track) so the 2rem knob's vertical centring is deterministic while dragging.
   Transparent + zero border so the Lumos track (.form_range_element bg) stays the
   only visible bar. */
.form_range_input::-webkit-slider-runnable-track { height: 0.7rem; background: transparent; border: 0; }
.form_range_input::-moz-range-track { height: 0.7rem; background: transparent; border: 0; }
/* No-JS swaps the slider for a real number field (0-100) - the honest no-JS control,
   since a range can't show its live value without JS (above). The number input
   carries name="Label" and is the canonical SUBMITTED field in BOTH modes: JS-on it's
   hidden but scripts.js wireRanges() writes the dragged value into it; JS-off it's the
   visible control the user reads + types. So exactly one "Label" submits either way.
   (The range loses its name in swap-range-number.py, becoming a JS-only visual driver.) */
.form_range_number_wrap { display: flex; align-items: baseline; gap: 0.6rem; }
.form_range_number { max-width: 7rem; }
.form_range_number_suffix { opacity: 0.7; }
html.js .form_range_number_wrap { display: none; }      /* JS present: show the slider, hide the number field */
html:not(.js) .form_range_element { display: none; }     /* no JS: show the number field, hide the slider */
html:not(.js) .form_range_output_wrap { display: none; } /* no JS: the "%" readout can't track - hide it */

/* ---- Modal: native <dialog> on Lumos's real structure (modal_dialog > modal_inner
   > modal_backdrop + modal_content > modal_scroll), which lumos.css fully styles.
   We ONLY reset the native <dialog> to a full-viewport transparent shell (Lumos's
   modal_dialog was a Webflow div, not a UA-centered native dialog) so modal_inner
   centers the card; keep the native ::backdrop invisible (the .modal_backdrop div
   is the dim); add the no-JS :target reveal. The light panel is Lumos-intentional. ---- */
dialog.modal_dialog {
  position: fixed; inset: 0; width: 100%; max-width: 100%; height: 100dvh; max-height: 100dvh;
  margin: 0; padding: 0; border: 0; background: transparent;
}
.modal_close { cursor: pointer; }
html:not(.js) dialog.modal_dialog:target { display: block; }

/* ---- Tabs: progressive enhancement (no markup swap - Lumos already ships
   .tab_button_list[role=tablist] + .tab_button_item buttons + .tab_content_list
   panels; scripts.js wires the ARIA tablist + switching onto it).
   No-JS baseline (§7): data-preview="true" keeps every panel stacked + reachable.
   JS present: collapse to the active (first) panel BEFORE enhanceTabs runs so
   there's no flash of the stacked baseline; enhanceTabs then sets inline display
   to switch. Lumos renders the active panel as display:block (its :first-child
   rule), which enhanceTabs mirrors. ---- */
html.js .tab_content_list > :not(:first-child):not(.u-display-contents):not(.w-dyn-list) { display: none; }
/* Auto-rotate "Hover to pause" hint - our addition (not a Lumos element). Created
   by scripts.js next to the prev/next arrows only when autoplay + pause-on-hover
   are on, shown only while playing. Inherits the theme text colour, kept muted. */
.tab_autoplay_hint { align-self: center; font-size: 0.8rem; line-height: 1; opacity: 0.55; white-space: nowrap; -webkit-user-select: none; user-select: none; }
.tab_autoplay_hint[hidden] { display: none; }

/* ---- CO-54: the form status line. The PLATFORM renders it (src/formrender.js) as
   `<div class="form_status u-text-style-small …" hidden>` and FORM_ENHANCE_JS toggles
   `hidden` as the submit resolves. Every voice class computes `display: flow-root`, and at
   equal specificity a later class rule beats the UA's `[hidden]{display:none}` — so the
   status line renders as an empty box before it has anything to say. That is a property of
   the platform's own markup, not of any tenant, which is why it belongs here: Kyoom hit it
   and patched locally, G&A escaped only because its contact page hand-authors a bare <p>
   with no voice on it. Same shape as the hint above. Attribute selector, so it also beats a
   voice a tenant adds later. ---- */
.form_status[hidden] { display: none; }

/* ---- P5/D7: `u-text` + a voice = BOX and voice, and the box wins on `display`.
   §8d of PRD-typography-contract (Kris, 26 Jul): "<div class="u-text u-text-style-main"> = box + voice.
   A flex text wrapper that also states a voice." The two keys are orthogonal and compose.
   They did not, on `display`. `.u-text` declares `display:flex` + `flex-flow:column` at lumos.css
   :3028; the `.u-text-style-*` utilities declare `display:flow-root` (D6) from :4138 onward. Equal
   specificity, later source position, so the VOICE was beating the BOX and every wrapper that gained
   a voice stopped being a flex column. Measured on the D7 markup retirement: 3,258 `display` deltas
   across the three trees, and 3,003 `min-width` deltas riding along behind them — `min-width:auto` is
   the flex-ITEM default, so a child of a wrapper that stopped being flex recomputes to 0.
   `flow-root` is the right default for a voiced LEAF, which is what D6 is about. It was never meant
   to dismantle the wrapper it lands beside. Two classes, so this wins on specificity rather than on
   source order — a position-dependent fix would break the next time the sheet is regenerated. ---- */
.u-text[class*="u-text-style-"] { display: flex; }

/* ---- Nav dropdowns: native <details name> (click to open, exclusive per
   breakpoint; scripts.js adds optional hover for data-hover="true"). Lumos's open
   animation lives in the per-page nav embed, keyed to the Webflow
   [aria-expanded]/.w--open states; we rebind it to the native [open]. The
   collapsed state + grid-rows transition in the embed are REUSED - we only add the
   open trigger. The .nav_component prefix wins specificity over the embed rule. ---- */
summary.w-dropdown-toggle { list-style: none; cursor: pointer; }
summary.w-dropdown-toggle::-webkit-details-marker { display: none; }
.nav_component details.nav_dropdown_component[open] > .w-dropdown-list { grid-template-rows: 1fr; visibility: visible; opacity: 1; }
.nav_component details.nav_dropdown_component[open] > summary.w-dropdown-toggle { --_state---true: 0; --_state---false: 1; }  /* expanded toggle (arrow rotation) */
.nav_component:has(details.nav_dropdown_component[open]) .nav_dropdown_backdrop { opacity: 1; }

/* ---- Mobile drawer: hamburger -> <summary>, menu -> content, in
   <details data-nav-drawer>. Lumos keyed the open state to .w-nav-button.w--open;
   rebind the menu clip-path animation + backdrop + scroll-lock + hamburger X to the
   native [open]. The menu is shown/hidden by the native <details>; the clip-path
   reveal animates it open. ---- */
summary.nav_button_wrap { list-style: none; cursor: pointer; }
summary.nav_button_wrap::-webkit-details-marker { display: none; }
/* the drawer toggle is a full-height flex item in the bar (align-items:normal);
   centre the hamburger/X lines vertically within it. */
details[data-nav-drawer] { display: flex; align-items: center; }
/* override Webflow's data-collapse="all" display:none AND supply the overlay
   positioning webflow.js used to add (.w-nav-overlay): position:absolute so the
   menu's own inset/width/margin:auto form the drop-down drawer card from the top. */
.nav_component:has(details[data-nav-drawer][open]) .w-nav-menu {
  display: block; position: absolute; float: none; z-index: 1;
  width: auto; margin: 0;  /* full width of the nav bar - drop Lumos's calc(100% - margins) + margin:auto; the menu's inset:0 0 auto stretches it edge to edge */
  animation: menuOpen var(--nav--menu-open-duration) ease-in-out forwards;
}
.nav_component:has(details[data-nav-drawer]:not([open])) .w-nav-menu { animation: menuClose var(--nav--menu-close-duration) ease-in-out forwards; }
.nav_component:has(details[data-nav-drawer][open]) .nav_menu_backdrop { opacity: 1; }  /* backdrop stays pointer-events:none (clicks pass through); scripts.js wireNav closes on any pointerdown outside the drawer */
body:has(.nav_component details[data-nav-drawer][open]) { overflow: hidden; }
details[data-nav-drawer][open] > summary { --_state---true: 0; --_state---false: 1; }  /* hamburger lines -> X */

/* ---- Slider slides-per-breakpoint: GLOBAL value table (starter) ---------------
   Mirrors Lumos's own [data-number] pattern: one global attribute->var table so a
   slider sets its count purely by attribute, with NO inline <style>. Lives here in
   lumos-native.css (loaded AFTER lumos.css) so it beats the baked-in
   .slider_offset{--lg:3} default (lumos.css:8322) at equal specificity. Covers
   integer counts 0-12 (realistic) + the fractional peek values the starter uses;
   mirrors data-number's 0-99 idea - add a line for any value beyond this set. ---- */
[data-slider-large="0"]{--lg:0}[data-slider-large="1"]{--lg:1}[data-slider-large="2"]{--lg:2}[data-slider-large="3"]{--lg:3}[data-slider-large="4"]{--lg:4}[data-slider-large="5"]{--lg:5}[data-slider-large="6"]{--lg:6}[data-slider-large="7"]{--lg:7}[data-slider-large="8"]{--lg:8}[data-slider-large="9"]{--lg:9}[data-slider-large="10"]{--lg:10}[data-slider-large="11"]{--lg:11}[data-slider-large="12"]{--lg:12}[data-slider-large="1.1"]{--lg:1.1}[data-slider-large="1.5"]{--lg:1.5}[data-slider-large="1.7"]{--lg:1.7}[data-slider-large="2.4"]{--lg:2.4}[data-slider-large="3.27"]{--lg:3.27}[data-slider-large="1.2"]{--lg:1.2}[data-slider-large="1.3"]{--lg:1.3}[data-slider-large="1.8"]{--lg:1.8}[data-slider-large="2.2"]{--lg:2.2}[data-slider-large="2.5"]{--lg:2.5}[data-slider-large="2.7"]{--lg:2.7}[data-slider-large="3.5"]{--lg:3.5}[data-slider-large="4.5"]{--lg:4.5}
[data-slider-medium="0"]{--md:0}[data-slider-medium="1"]{--md:1}[data-slider-medium="2"]{--md:2}[data-slider-medium="3"]{--md:3}[data-slider-medium="4"]{--md:4}[data-slider-medium="5"]{--md:5}[data-slider-medium="6"]{--md:6}[data-slider-medium="7"]{--md:7}[data-slider-medium="8"]{--md:8}[data-slider-medium="9"]{--md:9}[data-slider-medium="10"]{--md:10}[data-slider-medium="11"]{--md:11}[data-slider-medium="12"]{--md:12}[data-slider-medium="1.1"]{--md:1.1}[data-slider-medium="1.5"]{--md:1.5}[data-slider-medium="1.7"]{--md:1.7}[data-slider-medium="2.4"]{--md:2.4}[data-slider-medium="3.27"]{--md:3.27}[data-slider-medium="1.2"]{--md:1.2}[data-slider-medium="1.3"]{--md:1.3}[data-slider-medium="1.8"]{--md:1.8}[data-slider-medium="2.2"]{--md:2.2}[data-slider-medium="2.5"]{--md:2.5}[data-slider-medium="2.7"]{--md:2.7}[data-slider-medium="3.5"]{--md:3.5}[data-slider-medium="4.5"]{--md:4.5}
[data-slider-small="0"]{--sm:0}[data-slider-small="1"]{--sm:1}[data-slider-small="2"]{--sm:2}[data-slider-small="3"]{--sm:3}[data-slider-small="4"]{--sm:4}[data-slider-small="5"]{--sm:5}[data-slider-small="6"]{--sm:6}[data-slider-small="7"]{--sm:7}[data-slider-small="8"]{--sm:8}[data-slider-small="9"]{--sm:9}[data-slider-small="10"]{--sm:10}[data-slider-small="11"]{--sm:11}[data-slider-small="12"]{--sm:12}[data-slider-small="1.1"]{--sm:1.1}[data-slider-small="1.5"]{--sm:1.5}[data-slider-small="1.7"]{--sm:1.7}[data-slider-small="2.4"]{--sm:2.4}[data-slider-small="3.27"]{--sm:3.27}[data-slider-small="1.2"]{--sm:1.2}[data-slider-small="1.3"]{--sm:1.3}[data-slider-small="1.8"]{--sm:1.8}[data-slider-small="2.2"]{--sm:2.2}[data-slider-small="2.5"]{--sm:2.5}[data-slider-small="2.7"]{--sm:2.7}[data-slider-small="3.5"]{--sm:3.5}[data-slider-small="4.5"]{--sm:4.5}
[data-slider-xsmall="0"]{--xs:0}[data-slider-xsmall="1"]{--xs:1}[data-slider-xsmall="2"]{--xs:2}[data-slider-xsmall="3"]{--xs:3}[data-slider-xsmall="4"]{--xs:4}[data-slider-xsmall="5"]{--xs:5}[data-slider-xsmall="6"]{--xs:6}[data-slider-xsmall="7"]{--xs:7}[data-slider-xsmall="8"]{--xs:8}[data-slider-xsmall="9"]{--xs:9}[data-slider-xsmall="10"]{--xs:10}[data-slider-xsmall="11"]{--xs:11}[data-slider-xsmall="12"]{--xs:12}[data-slider-xsmall="1.1"]{--xs:1.1}[data-slider-xsmall="1.5"]{--xs:1.5}[data-slider-xsmall="1.7"]{--xs:1.7}[data-slider-xsmall="2.4"]{--xs:2.4}[data-slider-xsmall="3.27"]{--xs:3.27}[data-slider-xsmall="1.2"]{--xs:1.2}[data-slider-xsmall="1.3"]{--xs:1.3}[data-slider-xsmall="1.8"]{--xs:1.8}[data-slider-xsmall="2.2"]{--xs:2.2}[data-slider-xsmall="2.5"]{--xs:2.5}[data-slider-xsmall="2.7"]{--xs:2.7}[data-slider-xsmall="3.5"]{--xs:3.5}[data-slider-xsmall="4.5"]{--xs:4.5}

/* ===== CUSTOM OVERRIDE [CO-1]: theme-driven media-overlay (folded from Kyoom, 2026-06-25) =====
   Stock Lumos 2.2.3 (lumos.css) paints overlays as a HARDCODED-black scrim and ships only the
   base + linear `gradient` variant. Kyoom (on an older Lumos) custom-added theme-driven overlays
   + a `radial` variant. We fold that in as the starter STANDARD: overlays now derive from a theme
   role token `--_theme---overlay--background`, so they respond to the active theme/brand instead of
   being locked to black. base = scrim, `gradient` = linear (transparent->scrim), `radial` = radial
   vignette; opacity via data-number throughout. A tenant can re-point --_theme---overlay--background
   in styles.css for a brand-tinted scrim.
   Tracked in `_inventory/custom-overrides.md` (CO-1). lumos.css stays byte-identical for re-sync;
   these rules load after it and win. RE-SYNC CHECK: if a future Lumos ships theme-driven overlays
   natively, reconcile/retire this block (see the registry). ===== */
.u-theme-dark, .u-theme-light { --_theme---overlay--background: black; }
.u-theme-brand                { --_theme---overlay--background: var(--swatch--brand-800); }
.u-overlay {
  --_theme---overlay--gradient-linear: linear-gradient(0deg, color-mix(in srgb, var(--_theme---overlay--background, black) calc(var(--number, 50) / 100 * 80%), transparent) 0%, transparent 100%);
  --_theme---overlay--gradient-radial: radial-gradient(transparent 0%, color-mix(in srgb, var(--_theme---overlay--background, black) calc(var(--number, 50) / 100 * 80%), transparent) 100%);
  background-color: color-mix(in srgb, var(--_theme---overlay--background, black) calc(var(--number) / 100 * 80%), transparent);
}
/* `gradient` variant -> linear scrim (overrides lumos.css's hardcoded #0000009e gradient) */
.u-overlay:where(.w-variant-9653c0cc-21fc-fb82-1373-c98b1644e5a8) {
  background-color: transparent;
  background-image: var(--_theme---overlay--gradient-linear);
}
/* `radial` variant -> BRAND radial vignette. The Kyoom export's radial variant re-themes the overlay
   to the brand (brand-800), so the radial scrim is a warm brand tint on ANY theme (not the section's
   theme colour like the base/linear). A tenant re-points --swatch--brand-800 (or this rule) to change it. */
.u-overlay:where(.v-media-overlay-radial) {
  --_theme---overlay--background: var(--swatch--brand-800);
  background-color: transparent;
  background-image: var(--_theme---overlay--gradient-radial);
}

/* ===== CUSTOM OVERRIDE [CO-6]: themed nav logo (folded from Kyoom, 2026-06-25) =====
   The 4th token in Kyoom's overlay-era theme set. Stock Lumos 2.2.3 leaves the nav logo on the
   inherited text colour; Kyoom defines a dedicated --_theme---nav--logo role (but never wired it).
   We define it per theme context AND wire it: the logo SVG is `currentColor`, so setting `color`
   on the logo element drives it. So the logo now follows the theme/brand (brand-500 on dark/light,
   light-100 on brand) instead of the text colour; a tenant re-points it in styles.css.
   Tracked in `_inventory/custom-overrides.md` (CO-6). RE-SYNC: if new Lumos wires nav--logo itself,
   reconcile. ===== */
.u-theme-dark, .u-theme-light { --_theme---nav--logo: var(--swatch--brand-500); }
.u-theme-brand                { --_theme---nav--logo: var(--swatch--light-100); }
.nav_desktop_logo, .nav_mobile_logo { color: var(--_theme---nav--logo, currentColor); }

/* ===== CUSTOM OVERRIDE [CO-8]: Secondary typography MODE (divergence from stock Lumos) =====
   Stock Lumos ships ONE font: the `primary` family + its line-box trim, consumed by every
   text-style via --_typography---font--primary-*. Kris's Lumos DIVERGES from stock: the
   Typography variable COLLECTION gains a second MODE ("Secondary"), and the font/trim token names
   drop the `primary` qualifier so the ACTIVE MODE supplies them (base mode = body font, secondary
   mode = usually the heading font). A Webflow variable-collection mode compiles to a selector that
   re-emits the whole collection; we fold the same capability into the starter so dual-font tenants
   are first-class:
     - define a SECONDARY set (family + trim) that DEFAULTS to the primary values => a no-op for
       single-font tenants (the starter still renders in one font);
     - applying the mode (the .u-typography-secondary class, or a tenant mapping it onto headings
       in styles.css) re-points the primary-* tokens the text-styles read => that scope renders in
       the secondary font WITH its matching trim. Font + trim MUST travel together: each font's
       cap-height/baseline sit differently in the line box, so a mismatched trim clips the glyphs.
   A tenant sets the secondary font in styles.css and maps it where wanted. Tracked in
   `_inventory/custom-overrides.md` (CO-8) + the divergence map. RE-SYNC: stock Lumos has no
   secondary mode, so this stays a DIVERGENCE — unless the re-sync SOURCE is Kris's own Lumos
   (which has the mode + the primary-stripped names natively) => then retire + reconcile (map
   --_typography---font--primary-* <-> the mode-driven --_typography---font--*). ===== */
:root {
  --_typography---font--secondary-family:      var(--_typography---font--primary-family);
  --_typography---font--secondary-trim-top:    var(--_typography---font--primary-trim-top);
  --_typography---font--secondary-trim-bottom: var(--_typography---font--primary-trim-bottom);
}
.u-typography-secondary {
  --_typography---font--primary-family:      var(--_typography---font--secondary-family);
  --_typography---font--primary-trim-top:    var(--_typography---font--secondary-trim-top);
  --_typography---font--primary-trim-bottom: var(--_typography---font--secondary-trim-bottom);
}

/* ===== CUSTOM OVERRIDE [CO-42]: text-wrap/text-transform palette + per-variant channel layer =====
   PRD-starter-token-contract C3 + C4. GENERATED — run `node scripts/gen-variant-channels.mjs`
   and paste; test/css-files.mjs re-derives this block from lumos.css and fails on drift.

   C3 (text-wrap). lumos.css hardcoded `text-wrap` as a LITERAL on 80 slot declarations
   (58 balance on the heading tiers, 22 pretty on the body tiers) and `text-transform:
   uppercase` on v-typography-label. Every other value in the type system already resolves
   through a root token, so these were the only visual properties a tenant could not reach
   from the Design page. They now read a :root palette named exactly the way lumos.css
   already names its own keyword palette (--_typography---text-transform--uppercase:
   uppercase), so the vocabulary is one convention, not two.

   C4 (per-variant channels). lumos.css already ships this shape on the P5 voices
   (--_typography---label--weight, --_typography---text-large--line-height) and CO-41(b)
   added the trim pair to it. The TIER variants had none, so a tenant whose design splits a
   shared tier had nowhere to say it but an element-level override — which is invisible to
   the Design page AND intercepts its overlay, so the panel would show a token that does not
   do what it says. G&A needs three splits today (h1 1.08 / h2 1.22 both on line-height--small;
   h3 / h6 both on --medium; text-main 1.65 / text-large 1.6 both on --huge). Every tier
   variant now carries the full channel set, so the answer is always a ROOT token.
   Inert until seeded: each channel falls back to lumos.css's exact current expression.

   FONT + TRIM TRAVEL TOGETHER (CO-8, CO-41). --<v>--family, --<v>--trim-top and
   --<v>--trim-bottom are ONE triple: a tenant that re-points a variant's family must set
   its trim pair in the same breath, or that variant renders one face under another face's
   cap-trim. CSS cannot enforce the pairing, so it is stated here and in the CO registry.

   ORDERING — this block sits BEFORE CO-41 deliberately. CO-41(b) re-points
   --_text-style---trim-* on six per-SURFACE chrome selectors at (0,1,0), and
   .footer_group_title is h3.u-text-style-h6, so it matches a CO-42 tier rule at the same
   specificity. Today lumos.css's tier rule loses to CO-41(b) on source order; putting CO-42
   after CO-41 would flip that and strip the label trim off all six surfaces. Measured, not
   reasoned: with CO-42 last, .footer_group_title's trim-top goes .37em -> .43em on Kyoom.
   Guarded in test/css-files.mjs.

   NOT covered, each for a reason rather than a carve-out:
   - `:root`'s own slot defaults get the C3 palette but NO per-variant channel: :root is the
     document base every element inherits, not a variant, and naming it `text-main` would
     make a text-main edit move every unvariated element on the page.
   - `.button_main_text…:where(.v-button-main-primary-small)` declares no literal and is a
     component SIZE override, whose axis is --button-size--*, not the typography variants.
   - The P5 voices (statement / pullquote / label / lead / text-large) already carry their
     channels, so only their two hardcoded literals are touched here.
   - font-size IS channelled even though its tier token is already 1:1 per variant, because
     lumos.css's own text-large rule does exactly that. Consistency beats de-duplication in
     a vocabulary the Design page has to teach.

   RE-SYNC: keep. The channel vocabulary is ours (P5 + CO-41 established it); the palette
   retires only if a future Lumos ships text-wrap tokens under these names. ===== */
:root {
  /* C3 palette. Consumed by every declaration below; both entries ARE consumed, so there is
     no row on the Design page that does nothing. */
  --_typography---text-wrap--balance: balance;
  --_typography---text-wrap--pretty: pretty;
  /* the inherited base every unvariated element takes (lumos.css :root shipped the literal) */
  --_text-style---text-wrap: var(--_typography---text-wrap--pretty);
}

/* display */
.u-text:where(.w-variant-eee56f15-d7ce-d101-2f8c-83b3bbd55d4a),
.u-text:where(.w-variant-8ee37a78-065b-6879-973d-319c65e28313),
.u-text:where(.v-typography-display),
.u-text-style-display {
  --_text-style---font-family: var(--_typography---display--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---display--font-size, var(--_typography---font-size--display));
  --_text-style---line-height: var(--_typography---display--line-height, var(--_typography---line-height--small));
  --_text-style---font-weight: var(--_typography---display--weight, var(--_typography---font--primary-medium));
  --_text-style---letter-spacing: var(--_typography---display--letter-spacing, var(--_typography---letter-spacing--tight));
  --_text-style---text-wrap: var(--_typography---display--text-wrap, var(--_typography---text-wrap--balance));
  --_text-style---text-transform: var(--_typography---display--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---display--margin-top, var(--_spacing---space--7));
  --_text-style---margin-bottom: var(--_typography---display--margin-bottom, var(--_spacing---space--6));
  --_text-style---trim-top: var(--_typography---display--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---display--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* h1 */
h1,
.u-text:where(.w-variant-3ddfa43e-abc1-422e-6e6d-23dfb7da71f3),
.u-text:where(.v-typography-h1),
.u-text-style-h1 {
  --_text-style---font-family: var(--_typography---h1--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---h1--font-size, var(--_typography---font-size--h1));
  --_text-style---line-height: var(--_typography---h1--line-height, var(--_typography---line-height--small));
  --_text-style---font-weight: var(--_typography---h1--weight, var(--_typography---font--primary-medium));
  --_text-style---letter-spacing: var(--_typography---h1--letter-spacing, var(--_typography---letter-spacing--tight));
  --_text-style---text-wrap: var(--_typography---h1--text-wrap, var(--_typography---text-wrap--balance));
  --_text-style---text-transform: var(--_typography---h1--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---h1--margin-top, var(--_spacing---space--7));
  --_text-style---margin-bottom: var(--_typography---h1--margin-bottom, var(--_spacing---space--5));
  --_text-style---trim-top: var(--_typography---h1--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---h1--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* h2 */
h2,
.u-text:where(.w-variant-efb733fe-da83-69a3-ea4a-b3f2f89d0389),
.u-text:where(.v-typography-h2),
.u-text-style-h2 {
  --_text-style---font-family: var(--_typography---h2--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---h2--font-size, var(--_typography---font-size--h2));
  --_text-style---line-height: var(--_typography---h2--line-height, var(--_typography---line-height--small));
  --_text-style---font-weight: var(--_typography---h2--weight, var(--_typography---font--primary-medium));
  --_text-style---letter-spacing: var(--_typography---h2--letter-spacing, var(--_typography---letter-spacing--tight));
  --_text-style---text-wrap: var(--_typography---h2--text-wrap, var(--_typography---text-wrap--balance));
  --_text-style---text-transform: var(--_typography---h2--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---h2--margin-top, var(--_spacing---space--7));
  --_text-style---margin-bottom: var(--_typography---h2--margin-bottom, var(--_spacing---space--5));
  --_text-style---trim-top: var(--_typography---h2--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---h2--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* h3 */
h3,
.u-text:where(.w-variant-ad482112-9d0e-852c-0f6e-ba1e5a3aee59),
.u-text:where(.v-typography-h3),
.u-text-style-h3 {
  --_text-style---font-family: var(--_typography---h3--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---h3--font-size, var(--_typography---font-size--h3));
  --_text-style---line-height: var(--_typography---h3--line-height, var(--_typography---line-height--medium));
  --_text-style---font-weight: var(--_typography---h3--weight, var(--_typography---font--primary-medium));
  --_text-style---letter-spacing: var(--_typography---h3--letter-spacing, var(--_typography---letter-spacing--tight));
  --_text-style---text-wrap: var(--_typography---h3--text-wrap, var(--_typography---text-wrap--balance));
  --_text-style---text-transform: var(--_typography---h3--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---h3--margin-top, var(--_spacing---space--7));
  --_text-style---margin-bottom: var(--_typography---h3--margin-bottom, var(--_spacing---space--5));
  --_text-style---trim-top: var(--_typography---h3--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---h3--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* h4 */
h4,
.u-text:where(.w-variant-93e8af18-8413-e3ac-0442-72629401a3db),
.u-text:where(.v-typography-h4),
.u-text-style-h4 {
  --_text-style---font-family: var(--_typography---h4--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---h4--font-size, var(--_typography---font-size--h4));
  --_text-style---line-height: var(--_typography---h4--line-height, var(--_typography---line-height--medium));
  --_text-style---font-weight: var(--_typography---h4--weight, var(--_typography---font--primary-medium));
  --_text-style---letter-spacing: var(--_typography---h4--letter-spacing, var(--_typography---letter-spacing--normal));
  --_text-style---text-wrap: var(--_typography---h4--text-wrap, var(--_typography---text-wrap--balance));
  --_text-style---text-transform: var(--_typography---h4--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---h4--margin-top, var(--_spacing---space--7));
  --_text-style---margin-bottom: var(--_typography---h4--margin-bottom, var(--_spacing---space--4));
  --_text-style---trim-top: var(--_typography---h4--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---h4--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* h5 */
h5,
.u-text:where(.w-variant-5c484503-e2a0-ac99-680d-56013d859efa),
.u-text:where(.w-variant-12e93187-f8ac-b1a1-2e50-59d85a6bf516),
.u-text:where(.v-typography-h5),
.u-text-style-h5 {
  --_text-style---font-family: var(--_typography---h5--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---h5--font-size, var(--_typography---font-size--h5));
  --_text-style---line-height: var(--_typography---h5--line-height, var(--_typography---line-height--medium));
  --_text-style---font-weight: var(--_typography---h5--weight, var(--_typography---font--primary-medium));
  --_text-style---letter-spacing: var(--_typography---h5--letter-spacing, var(--_typography---letter-spacing--normal));
  --_text-style---text-wrap: var(--_typography---h5--text-wrap, var(--_typography---text-wrap--balance));
  --_text-style---text-transform: var(--_typography---h5--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---h5--margin-top, var(--_spacing---space--7));
  --_text-style---margin-bottom: var(--_typography---h5--margin-bottom, var(--_spacing---space--4));
  --_text-style---trim-top: var(--_typography---h5--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---h5--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* h6 */
h6,
.u-text:where(.w-variant-b8555f10-fa7d-e9ba-e262-f9aa44c27c1c),
.u-text:where(.w-variant-47e40b0d-02af-2673-f429-546cac42dc5a),
.u-text:where(.v-typography-h6),
.u-text-style-h6 {
  --_text-style---font-family: var(--_typography---h6--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---h6--font-size, var(--_typography---font-size--h6));
  --_text-style---line-height: var(--_typography---h6--line-height, var(--_typography---line-height--medium));
  --_text-style---font-weight: var(--_typography---h6--weight, var(--_typography---font--primary-medium));
  --_text-style---letter-spacing: var(--_typography---h6--letter-spacing, var(--_typography---letter-spacing--normal));
  --_text-style---text-wrap: var(--_typography---h6--text-wrap, var(--_typography---text-wrap--balance));
  --_text-style---text-transform: var(--_typography---h6--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---h6--margin-top, var(--_spacing---space--7));
  --_text-style---margin-bottom: var(--_typography---h6--margin-bottom, var(--_spacing---space--4));
  --_text-style---trim-top: var(--_typography---h6--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---h6--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* text-large */
.u-text:where(.w-variant-fdb8e663-01e0-aae6-13eb-e6dfca16b689),
.u-text:where(.w-variant-c8b8ac88-1ac7-e9f6-41cc-82c548bf142d),
.u-text:where(.w-variant-74a3180a-e505-b452-032f-599c8e557249),
.u-text-style-large {
  --_text-style---font-family: var(--_typography---text-large--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---text-large--font-size, var(--_typography---font-size--text-large));
  --_text-style---line-height: var(--_typography---text-large--line-height, var(--_typography---line-height--huge));
  --_text-style---font-weight: var(--_typography---text-large--weight, var(--_typography---font--primary-regular));
  --_text-style---letter-spacing: var(--_typography---text-large--letter-spacing, var(--_typography---letter-spacing--normal));
  --_text-style---text-wrap: var(--_typography---text-large--text-wrap, var(--_typography---text-wrap--pretty));
  --_text-style---text-transform: var(--_typography---text-large--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---text-large--margin-top, var(--_spacing---space--4));
  --_text-style---margin-bottom: var(--_typography---text-large--margin-bottom, var(--_spacing---space--4));
  --_text-style---trim-top: var(--_typography---text-large--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---text-large--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* text-main */
.u-text:where(.v-typography-text-main),
.u-text:where(.w-variant-26cc9f23-e71c-deee-cd61-36dc24f35fae),
.u-text:where(.w-variant-9027c8ae-1ba5-f702-ea6e-4a1c3fca9f64),
.u-text-style-main {
  --_text-style---font-family: var(--_typography---text-main--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---text-main--font-size, var(--_typography---font-size--text-main));
  --_text-style---line-height: var(--_typography---text-main--line-height, var(--_typography---line-height--huge));
  --_text-style---font-weight: var(--_typography---text-main--weight, var(--_typography---font--primary-regular));
  --_text-style---letter-spacing: var(--_typography---text-main--letter-spacing, var(--_typography---letter-spacing--normal));
  --_text-style---text-wrap: var(--_typography---text-main--text-wrap, var(--_typography---text-wrap--pretty));
  --_text-style---text-transform: var(--_typography---text-main--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---text-main--margin-top, var(--_spacing---space--4));
  --_text-style---margin-bottom: var(--_typography---text-main--margin-bottom, var(--_spacing---space--4));
  --_text-style---trim-top: var(--_typography---text-main--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---text-main--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* text-small */
.u-text:where(.v-typography-text-small),
.u-text:where(.w-variant-fe6f474d-1651-566b-eeff-3c86e8cbf153),
.u-text:where(.w-variant-3fbd0d32-5675-1947-73b3-abb16c1fb986),
.u-text-style-small {
  --_text-style---font-family: var(--_typography---text-small--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---text-small--font-size, var(--_typography---font-size--text-small));
  --_text-style---line-height: var(--_typography---text-small--line-height, var(--_typography---line-height--huge));
  --_text-style---font-weight: var(--_typography---text-small--weight, var(--_typography---font--primary-regular));
  --_text-style---letter-spacing: var(--_typography---text-small--letter-spacing, var(--_typography---letter-spacing--normal));
  --_text-style---text-wrap: var(--_typography---text-small--text-wrap, var(--_typography---text-wrap--pretty));
  --_text-style---text-transform: var(--_typography---text-small--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---text-small--margin-top, var(--_spacing---space--4));
  --_text-style---margin-bottom: var(--_typography---text-small--margin-bottom, var(--_spacing---space--4));
  --_text-style---trim-top: var(--_typography---text-small--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---text-small--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* text-large — keeps its own late position in lumos.css (line 4325) */
.u-rich-text blockquote {
  --_text-style---font-family: var(--_typography---text-large--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---text-large--font-size, var(--_typography---font-size--text-large));
  --_text-style---line-height: var(--_typography---text-large--line-height, var(--_typography---line-height--huge));
  --_text-style---font-weight: var(--_typography---text-large--weight, var(--_typography---font--primary-regular));
  --_text-style---letter-spacing: var(--_typography---text-large--letter-spacing, var(--_typography---letter-spacing--normal));
  --_text-style---text-wrap: var(--_typography---text-large--text-wrap, var(--_typography---text-wrap--pretty));
  --_text-style---text-transform: var(--_typography---text-large--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---text-large--margin-top, var(--_spacing---space--4));
  --_text-style---margin-bottom: var(--_typography---text-large--margin-bottom, var(--_spacing---space--4));
  --_text-style---trim-top: var(--_typography---text-large--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---text-large--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* text-large — keeps its own late position in lumos.css (line 5765) */
.form_field {
  --_text-style---font-family: var(--_typography---text-large--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---text-large--font-size, var(--_typography---font-size--text-large));
  --_text-style---line-height: var(--_typography---text-large--line-height, var(--_typography---line-height--huge));
  --_text-style---font-weight: var(--_typography---text-large--weight, var(--_typography---font--primary-regular));
  --_text-style---letter-spacing: var(--_typography---text-large--letter-spacing, var(--_typography---letter-spacing--normal));
  --_text-style---text-wrap: var(--_typography---text-large--text-wrap, var(--_typography---text-wrap--pretty));
  --_text-style---text-transform: var(--_typography---text-large--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---text-large--margin-top, var(--_spacing---space--4));
  --_text-style---margin-bottom: var(--_typography---text-large--margin-bottom, var(--_spacing---space--4));
  --_text-style---trim-top: var(--_typography---text-large--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---text-large--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* h5 — keeps its own late position in lumos.css (line 7776) */
.card_primary_title.u-text-style-h4:where(.v-card-primary-stacked) {
  --_text-style---font-family: var(--_typography---h5--family, var(--_typography---font--primary-family));
  --_text-style---font-size: var(--_typography---h5--font-size, var(--_typography---font-size--h5));
  --_text-style---line-height: var(--_typography---h5--line-height, var(--_typography---line-height--medium));
  --_text-style---font-weight: var(--_typography---h5--weight, var(--_typography---font--primary-medium));
  --_text-style---letter-spacing: var(--_typography---h5--letter-spacing, var(--_typography---letter-spacing--normal));
  --_text-style---text-wrap: var(--_typography---h5--text-wrap, var(--_typography---text-wrap--balance));
  --_text-style---text-transform: var(--_typography---h5--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---h5--margin-top, var(--_spacing---space--7));
  --_text-style---margin-bottom: var(--_typography---h5--margin-bottom, var(--_spacing---space--4));
  --_text-style---trim-top: var(--_typography---h5--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---h5--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* statement — keeps its own late position in lumos.css (line 8936) */
.u-text:where(.v-typography-statement) {
  --_text-style---text-wrap: var(--_typography---statement--text-wrap, var(--_typography---text-wrap--balance));
}

/* pullquote — keeps its own late position in lumos.css (line 9100) */
.u-text:where(.v-typography-pullquote) {
  --_text-style---text-wrap: var(--_typography---pullquote--text-wrap, var(--_typography---text-wrap--balance));
}

/* label — keeps its own late position in lumos.css (line 9121) */
.u-text:where(.v-typography-label) {
  --_text-style---text-transform: var(--_typography---label--text-transform, var(--_typography---text-transform--uppercase));
}

/* statement — keeps its own late position in lumos.css (line 9209) */
.u-text-style-statement {
  --_text-style---text-wrap: var(--_typography---statement--text-wrap, var(--_typography---text-wrap--balance));
  --_text-style---text-transform: var(--_typography---statement--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---statement--margin-top, var(--_spacing---space--0));
  --_text-style---margin-bottom: var(--_typography---statement--margin-bottom, var(--_spacing---space--4));
  --_text-style---trim-top: var(--_typography---statement--trim-top, var(--_typography---font--secondary-trim-top));
  --_text-style---trim-bottom: var(--_typography---statement--trim-bottom, var(--_typography---font--secondary-trim-bottom));
}

/* pullquote — keeps its own late position in lumos.css (line 9234) */
.u-text-style-pullquote {
  --_text-style---text-wrap: var(--_typography---pullquote--text-wrap, var(--_typography---text-wrap--balance));
  --_text-style---text-transform: var(--_typography---pullquote--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---pullquote--margin-top, var(--_spacing---space--0));
  --_text-style---margin-bottom: var(--_typography---pullquote--margin-bottom, var(--_spacing---space--4));
  --_text-style---trim-top: var(--_typography---pullquote--trim-top, var(--_typography---font--secondary-trim-top));
  --_text-style---trim-bottom: var(--_typography---pullquote--trim-bottom, var(--_typography---font--secondary-trim-bottom));
}

/* label — keeps its own late position in lumos.css (line 9262) */
.u-text-style-label {
  --_text-style---text-wrap: var(--_typography---label--text-wrap, var(--_typography---text-wrap--pretty));
  --_text-style---text-transform: var(--_typography---label--text-transform, var(--_typography---text-transform--uppercase));
  --_text-style---margin-top: var(--_typography---label--margin-top, var(--_spacing---space--0));
  --_text-style---margin-bottom: var(--_typography---label--margin-bottom, var(--_spacing---space--4));
}

/* lead — keeps its own late position in lumos.css (line 9288) */
.u-text-style-lead {
  --_text-style---text-wrap: var(--_typography---lead--text-wrap, var(--_typography---text-wrap--pretty));
  --_text-style---text-transform: var(--_typography---lead--text-transform, var(--_typography---text-transform--none));
  --_text-style---margin-top: var(--_typography---lead--margin-top, var(--_spacing---space--0));
  --_text-style---margin-bottom: var(--_typography---lead--margin-bottom, var(--_spacing---space--4));
  --_text-style---trim-top: var(--_typography---lead--trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---lead--trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* eyebrow — keeps its own late position in lumos.css (line 9355) */
.u-text-style-eyebrow {
  --_text-style---text-wrap: var(--_typography---eyebrow--text-wrap, var(--_typography---text-wrap--pretty));
  --_text-style---text-transform: var(--_typography---eyebrow--text-transform, var(--_typography---text-transform--uppercase));
  --_text-style---margin-top: var(--_typography---eyebrow--margin-top, var(--_spacing---space--0));
  --_text-style---margin-bottom: var(--_typography---eyebrow--margin-bottom, var(--_spacing---space--4));
}

/* eyebrow-quiet — keeps its own late position in lumos.css (line 9382) */
.u-text-style-eyebrow-quiet {
  --_text-style---text-wrap: var(--_typography---eyebrow-quiet--text-wrap, var(--_typography---text-wrap--pretty));
  --_text-style---text-transform: var(--_typography---eyebrow-quiet--text-transform, var(--_typography---text-transform--uppercase));
  --_text-style---margin-top: var(--_typography---eyebrow-quiet--margin-top, var(--_spacing---space--0));
  --_text-style---margin-bottom: var(--_typography---eyebrow-quiet--margin-bottom, var(--_spacing---space--4));
}

/* eyebrow-stage — keeps its own late position in lumos.css (line 9409) */
.u-text-style-eyebrow-stage {
  --_text-style---text-wrap: var(--_typography---eyebrow-stage--text-wrap, var(--_typography---text-wrap--pretty));
  --_text-style---text-transform: var(--_typography---eyebrow-stage--text-transform, var(--_typography---text-transform--uppercase));
  --_text-style---margin-top: var(--_typography---eyebrow-stage--margin-top, var(--_spacing---space--0));
  --_text-style---margin-bottom: var(--_typography---eyebrow-stage--margin-bottom, var(--_spacing---space--4));
}

/* ===== CUSTOM OVERRIDE [CO-41]: primary-stripped typography token names + label trim pair =====
   Completes the naming half of what CO-8 above already anticipated: Kris's own Lumos drops the
   `primary` qualifier so the ACTIVE MODE supplies the font + trim. Kris's model (25 Jul 2026):
   "there should be one token var(--_typography---font--trim-top) and var(--_typography---font--
   trim-bottom) then there are 2 possible values depending on what mode or family is selected for
   the var(--_typography---font--family)."

   (a) CANONICAL NAMES. The vendored lumos.css consumes --_typography---font--primary-* at 81
   sites; lumos.css stays BYTE-IDENTICAL for re-sync, so we alias rather than rewrite it. The
   direction matters: point the vendored name AT the canonical one, so seeding the canonical trio
   reaches all 81 consumers. The tenant styles.css loads AFTER this file, so a legacy tenant that
   seeds --…--primary-family directly still wins => genuine dual-key, no flag day. Tenants seed the
   canonical trio; the Design page surfaces exactly that (tokens.js parses :root of styles.css), so
   the panel shows ONE font + ONE trim pair and the modes stay internal plumbing.

   (b) LABEL TRIM PAIR. label-family is an established third family slot but carries NO trim, so a
   tri-font tenant renders label text under a foreign trim (measured: IBM Plex Sans under Inter's
   trim = .041em cap slack; under Newsreader's = .158em). Font + trim MUST travel together (CO-8).
   The pair defaults to the PRIMARY trim => value-inert for single-font tenants.

   CONSUMPTION covers BOTH the v-typography-label variant and the two CHROME rule groups
   (lumos.css .nav_links_text/.nav_dropdown_text and .footer_group_title/.footer_link_text/
   .footer_tagline/.footer_bottom_text).

   The chrome half looked impossible at first: those rules set `font-family` as a PROPERTY via a
   var(--label-family, var(--_text-style---font-family, …)) chain, so the inert default for their
   trim seemed to be the INHERITED trim — and `--_text-style---trim-top: var(--…label-trim-top,
   var(--_text-style---trim-top))` is a same-element CYCLE that computes to invalid. The fallback
   just needed to be the right token: BOTH routing blocks (tenant styles.css) re-point
   --_typography---font--primary-trim-top/-bottom ON THE ELEMENT, not only at :root. So
   element-local primary-trim is ALREADY paired with whatever family that element resolves to, and
   falling back to it is inert by construction with no cycle.
   Verified against measured values before writing: .footer_group_title (h3.u-text-style-h6, so the
   routing block matches it) carries primary-trim .37/.37 = the secondary pair it renders in;
   .footer_link_text/.footer_bottom_text/.nav_links_text/.nav_dropdown_text and .footer_tagline
   (p.u-text-style-small, NOT routed) carry primary-trim .43/.3 = their primary pair. In every case
   var(--label-trim-*, var(--primary-trim-*)) reproduces the current value exactly.
   Kyoom is what made this checkable: it seeds label-family ZERO times and its cap-trim pseudos are
   LIVE (content:"", real negative margins), so it genuinely exercises the chrome trim.

   Tracked in `_inventory/custom-overrides.md` (CO-41) + the divergence map. RE-SYNC: retire (a)
   outright when the source is Kris's own Lumos — it already ships these names natively, so this is
   CONVERGENCE toward the sync target, not a divergence away from it. Keep (b) unless that Lumos
   also carries a label mode. ===== */
:root {
  /* (a) the vendored names become pointers at the canonical trio; stock values are the fallbacks,
     so an unseeded tenant renders exactly as before (lumos.css :root ships .33em/.38em). */
  /* NO literal fallback here, deliberately: lumos.css ships no :root value for
     primary-family (build-lumos.sh transform A lifts it to styles.css), so this
     declaration shadows nothing and omitting the fallback keeps CO-41 a STRICT
     no-op for every tenant config — including one that seeds neither name, which
     keeps inheriting exactly as before. A literal here would be the arc's only
     behaviour change, spent guarding a config the framework header already calls
     a contract violation ("the tenant's styles.css MUST define" it). It would also
     put a font stack in a file that must stay unbranded. Guarded by
     test/css-files.mjs, which asserts both the absence of a fallback here AND that
     lumos.css still ships no :root primary-family — the premise this rests on. */
  --_typography---font--primary-family:      var(--_typography---font--family);
  --_typography---font--primary-trim-top:    var(--_typography---font--trim-top, .33em);
  --_typography---font--primary-trim-bottom: var(--_typography---font--trim-bottom, .38em);
  /* (b) label trim pair is DELIBERATELY NOT given a :root default here. CO-8's secondary pair can
     safely mirror primary at :root because the secondary mode RE-DECLARES it per scope. The label
     pair is consumed through a var() FALLBACK instead, and a :root mirror breaks that outright:
     custom properties INHERIT, so a mirrored label-trim is defined on every element, the fallback
     can never fire, and the element receives :ROOT's trim rather than the element-local one the
     routing block paired with its family.
     Measured, not reasoned: with the mirror in place, Kyoom's .footer_group_title resolved
     .43em (:root Aleo) where it currently renders .37em (routed secondary) — the exact unpairing
     this CO exists to prevent. With no mirror it resolves .37em, identical to today, and a tenant
     that seeds .35em gets .35em. So label-trim stays genuinely UNSET until a tenant opts in, which
     also makes CO-41 a literal zero-diff change (the earlier gate's only observed difference was
     these two definitions appearing). Guarded in test/css-files.mjs. */
}
/* (b) consumption — same three-branch chain shape the variant uses for its family, so an unset
   label pair resolves to the primary trim exactly as the family resolves to the primary family. */
.u-text:where(.v-typography-label) {
  --_text-style---trim-top:    var(--_typography---label--trim-top, var(--_typography---font--label-trim-top, var(--_typography---font--primary-trim-top)));
  --_text-style---trim-bottom: var(--_typography---label--trim-bottom, var(--_typography---font--label-trim-bottom, var(--_typography---font--primary-trim-bottom)));
}
/* (b) chrome half — same shape, fallback to the ELEMENT-LOCAL primary-trim the routing block has
   already paired with this element's family. Selector lists mirror lumos.css:8827 and :8832
   exactly; if those lists ever change, these must follow (guarded in test/css-files.mjs). */
.nav_links_text, .nav_dropdown_text,
.footer_group_title, .footer_link_text, .footer_tagline, .footer_bottom_text {
  --_text-style---trim-top:    var(--_typography---font--label-trim-top, var(--_typography---font--primary-trim-top));
  --_text-style---trim-bottom: var(--_typography---font--label-trim-bottom, var(--_typography---font--primary-trim-bottom));
}

/* ===== CUSTOM OVERRIDE [CO-45]: a text BLOCK applies its own top margin
   (PRD-typography-contract — Kris, 27 Jul 2026) =====
   `.u-text` applies `margin-bottom: var(--_text-style---margin-bottom)` and has NO margin-top
   applier at all. Consequence, found by P3's sweep: upstream Lumos authors a `margin-top` on
   EVERY text style (space--7 on the headings, and on the body tiers too in current Lumos) and
   our framework never reads it — the value is inert. The moment markup adopts the contract's
   block form, the top margin vanishes outright: the semantic tag is now a child, so
   `.u-text > *{margin-top:0!important}` zeroes it, and nothing hands it to the block. Measured
   on the G&A sweep: 453 elements went 24px/48px -> 0.

   This completes the pair. The block reads the same slot for both edges, so a converted block
   takes exactly the rhythm its voice authored — which is what makes the markup conversion
   rhythm-neutral instead of silently flattening it.

   Nested blocks do not double up: `.u-text > *` still forces a child's margin-top to 0, so only
   the OUTERMOST block in a stack contributes. That is the same containment the bottom edge has
   always had.

   NOT INERT, and deliberately so — this is the rhythm decision itself, gated on before/after
   page heights rather than 0 deltas (D15's rule). Every `.u-text` block gains the top margin its
   voice already declares. RE-SYNC: keep unless a future Lumos ships the applier itself; if it
   does, retire this and reconcile. Tracked in `_inventory/custom-overrides.md` (CO-45). ===== */
.u-text {
  margin-top: var(--_text-style---margin-top);
}

/* ===== CUSTOM OVERRIDE [CO-45b]: a block's top margin only ever sits BETWEEN blocks =====
   CO-45's companion, and it is not optional — Kris caught both symptoms on the first render:
   a steps numeral no longer top-aligning with its heading, and a two-column heading dropping
   below the rule beside it.

   lumos.css ships a margin trim that already does this (first meaningful child -> margin-top 0,
   last -> margin-bottom 0), but ONLY inside `.u-margin-trim`, `.w-richtext`, `.u-content-wrapper`,
   `[class*="u-container"]` and `[class*="u-layout-column"]`. Before CO-45 nothing applied a top
   margin so the gap never showed. Measured after it: 29 first-children in containers outside that
   list — 13 in unclassed `div`s (so extending the container list cannot fix it), 8 in
   `.card_primary_content`, 6 in `v-layout-columns-3-1`, 2 in `v-layout-auto-width`.

   So the test moves off the CONTAINER and onto the block: a top margin exists to separate a block
   from what PRECEDES it, and with no meaningful preceding sibling it has no job. Same "meaningful"
   definition as the lumos trim (ignoring `.w-condition-invisible`/`.u-cover-absolute`/
   `.u-ignore-trim`), so the two edges stay one convention rather than two.
   (0,2,0) beats CO-45's (0,1,0). RE-SYNC: retires with CO-45. ===== */
.u-text:not(:not(.w-condition-invisible,.u-cover-absolute,.u-ignore-trim) ~ :not(.w-condition-invisible,.u-cover-absolute,.u-ignore-trim)) {
  margin-top: 0;
}

/* ===== CUSTOM OVERRIDE [CO-44]: a VOICED heading keeps its own voice inside a u-text wrapper
   (PRD-typography-contract, option 3 — Kris, 27 Jul 2026) =====
   lumos.css:3451 ships `.u-text h1..h6 { font-family/size/weight/line-height/letter-spacing/
   text-transform/text-wrap: inherit; margin: 0 }`. That rule is what makes the WRAPPER form work
   — a bare <h1> inside a `u-text u-text-style-h1` takes the wrapper's type — and it is also why
   the contract's own line, "VOICE ... on the element", could not be honoured: at (0,1,1) it beats
   `.u-text-style-h1` at (0,1,0), so a heading carrying its own voice inside a wrapper was
   overruled and rendered the wrapper's size.

   There is NO equivalent rule for `p`, so the asymmetry was silent and tag-dependent: putting the
   voice on the wrapper is correct for headings and wrong for paragraphs (measured on G&A P3 batch 2
   — `p.u-text.v-typography-label.is-kicker` 11.2px -> 16.32px, the bare inner <p> falling back to
   the tenant's base paragraph size), while putting it on the element is correct for paragraphs and
   wrong for headings. Neither placement worked for both tags.

   This re-asserts the seven inherited properties from the element's OWN slots when a heading inside
   a `.u-text` carries a `u-text-style-*` voice, at (0,2,1) so it wins. The slots it reads are the
   ones that voice class already sets, so the values are the voice's by construction — no literal
   here, nothing to drift. MARGINS ARE DELIBERATELY NOT RESTORED: `margin-top/bottom: 0` from the
   lumos rule stands, because under voice-only D1 rhythm belongs to the wrapper, not the voice.

   Inert on both tenants at mint time, and measured rather than assumed: headings inside a `.u-text`
   = 2 on G&A / 589 on Kyoom, of which **0** carry their own voice. The zero is "unconsumed", not a
   blind probe — an injected voiced <h2> inside a `u-text u-text-style-small` was verified to inherit
   the wrapper (13px G&A / 14.78px Kyoom), i.e. the lumos rule is live and this exemption does bite
   the moment the population exists. It exists as soon as P3 converts a page.

   RE-SYNC: keep while `.u-text h1..h6` ships as an unconditional inherit. Retires if a future Lumos
   scopes that rule itself. Tracked in `_inventory/custom-overrides.md` (CO-44). ===== */
.u-text :is(h1,h2,h3,h4,h5,h6)[class*="u-text-style-"] {
  font-family: var(--_text-style---font-family);
  font-size: var(--_text-style---font-size);
  font-weight: var(--_text-style---font-weight);
  line-height: var(--_text-style---line-height);
  letter-spacing: var(--_text-style---letter-spacing);
  text-transform: var(--_text-style---text-transform, none);
  text-wrap: var(--_text-style---text-wrap);
}

/* ===== CUSTOM OVERRIDE [CO-47]: border-side utilities (Kris, 27 Jul 2026) =====
   Lumos ships spacing, gap, colour and typography utilities but NO border utility at all, so
   "this surface needs a hairline" has only ever had two answers on this platform: a bespoke rule,
   or a bespoke `is-` class. Both are the thing the framework exists to avoid.

   WHY NOW. Kris, reviewing G&A's `is-seclabel`: *"I really don't like these adhoc is- classes.
   The whole point of the lumos framework is to provide utility classes to stack if needed."*
   `is-seclabel` decomposes into 12 declarations — ten are the label voice (now `u-text-style-label`),
   one is a margin `.u-text` supplies, and the only genuinely distinct pair is `border-top` +
   `padding-top`. Lumos already ships `u-padding-top-*`; the border half had nowhere to go. And the
   population makes the point: **125 `is-seclabel` in G&A markup against 112 `is-bare`**, whose
   entire job is `border-top:none; padding-top:0` — 90% of the class's users immediately cancel the
   one property that distinguishes it.

   A PROMOTION, NOT AN INVENTION. G&A already ships `.u-border-top{border-top:var(--border-width--main)
   solid var(--theme--border)}` (styles.css:448) and carries it on **95 elements**. This is that,
   generalised to four sides and moved into the framework — the same shape as D8, where a tenant-local
   fix became the framework's.

   INERT ON G&A BY CONSTRUCTION: the starter is imported into `@layer yd-starter` and G&A's own rule
   is unlayered, so the tenant keeps winning on those 95 elements and nothing moves. The value is for
   Kyoom and every future tenant, and for retiring G&A's copy at P6.

   TOKENS, NOT LITERALS. Width and colour route through a per-family channel and then set it — the
   shape `.u-gap-*` already uses (`--_gap---size`), so a scope can re-point the channel without a
   bespoke rule. Values are Lumos's own: `--border-width--main` is the only width step the framework
   ships, and `--_theme---border` is what lumos.css itself writes in every border it draws.
   Tracked in `_inventory/custom-overrides.md` (CO-47). RE-SYNC: keep until Lumos ships border
   utilities of its own; if it does, reconcile names and retire. ===== */
.u-border-top,
.u-border-right,
.u-border-bottom,
.u-border-left {
  --_border---width: var(--border-width--main);
  --_border---color: var(--_theme---border);
}
.u-border-top    { border-top:    var(--_border---width) solid var(--_border---color); }
.u-border-right  { border-right:  var(--_border---width) solid var(--_border---color); }
.u-border-bottom { border-bottom: var(--_border---width) solid var(--_border---color); }
.u-border-left   { border-left:   var(--_border---width) solid var(--_border---color); }

/* ===== CUSTOM OVERRIDE [CO-48(c)]: rich-text headings resolve their own channel set =====
   PRD-typography-contract D17 (Kris, 27 Jul 2026): *"we do need new tokens as we often want
   specific styling for headings in rich text (often used for blog posts) as we often want
   headings to be smaller on blog posts."* GENERATED — run
   `node scripts/gen-richtext-channels.mjs`; test/css-files.mjs re-derives and fails on drift.

   A heading inside a rich-text block and a heading on a page are different typographic
   JOBS, and a tenant must be able to make blog h2s smaller without touching section
   headings. That is "same vocabulary, different values", which is exactly what the channel
   layer is for — so this is a design requirement, not a workaround for the cascade defect
   CO-48(a) fixes.

   EIGHT channels, not CO-42's eleven: size, line-height, weight, margin-top, margin-bottom,
   family and the trim pair. letter-spacing / text-wrap / text-transform are absent because an
   article heading differs from a page heading in size, rhythm and face, not in tracking or
   wrapping; they still resolve, through the heading's own tag rule, i.e. the page tier.

   FAMILY AND TRIM ARE IN THE SET on measured evidence, against the instinct to inherit them:
   a bare `h2` chains to --_typography---font--primary-family, while `.u-text-style-h2` reaches
   G&A's display face through a CLASS-keyed tenant rule — so tokenising rich-text headings
   through the tag chain alone gives body-face headings inside every article. Trim travels
   with family (CO-8, CO-41), so the pair rides along rather than inheriting.

   INERT AT MINT BY CONSTRUCTION: every fallback is CO-42's channelled expression for the
   matching page tier, verbatim and derived, so an unseeded tenant resolves exactly what it
   resolves today — and a tenant that re-points --_typography---h3--font-size still reaches
   rich text, because the fallback is the tier CHANNEL and not the raw tier token.

   VOICED HEADINGS ARE EXCLUDED. A heading carrying `u-text-style-*` said what it wanted;
   CO-44 already makes that win inside a wrapper, and this rule at (0,2,1) would overrule it.
   RE-SYNC: keep. Stock Lumos gives rich-text headings margins only and no per-tag typography.
   Tracked in `_inventory/custom-overrides.md` (CO-48). ===== */
.u-rich-text h1:not([class*="u-text-style-"]) {
  --_text-style---font-family: var(--_typography---richtext-h1--family, var(--_typography---h1--family, var(--_typography---font--primary-family)));
  --_text-style---font-size: var(--_typography---richtext-h1--font-size, var(--_typography---h1--font-size, var(--_typography---font-size--h1)));
  --_text-style---line-height: var(--_typography---richtext-h1--line-height, var(--_typography---h1--line-height, var(--_typography---line-height--small)));
  --_text-style---font-weight: var(--_typography---richtext-h1--weight, var(--_typography---h1--weight, var(--_typography---font--primary-medium)));
  --_text-style---margin-top: var(--_typography---richtext-h1--margin-top, var(--_typography---h1--margin-top, var(--_spacing---space--7)));
  --_text-style---margin-bottom: var(--_typography---richtext-h1--margin-bottom, var(--_typography---h1--margin-bottom, var(--_spacing---space--5)));
  --_text-style---trim-top: var(--_typography---richtext-h1--trim-top, var(--_typography---h1--trim-top, var(--_typography---font--primary-trim-top)));
  --_text-style---trim-bottom: var(--_typography---richtext-h1--trim-bottom, var(--_typography---h1--trim-bottom, var(--_typography---font--primary-trim-bottom)));
}
.u-rich-text h2:not([class*="u-text-style-"]) {
  --_text-style---font-family: var(--_typography---richtext-h2--family, var(--_typography---h2--family, var(--_typography---font--primary-family)));
  --_text-style---font-size: var(--_typography---richtext-h2--font-size, var(--_typography---h2--font-size, var(--_typography---font-size--h2)));
  --_text-style---line-height: var(--_typography---richtext-h2--line-height, var(--_typography---h2--line-height, var(--_typography---line-height--small)));
  --_text-style---font-weight: var(--_typography---richtext-h2--weight, var(--_typography---h2--weight, var(--_typography---font--primary-medium)));
  --_text-style---margin-top: var(--_typography---richtext-h2--margin-top, var(--_typography---h2--margin-top, var(--_spacing---space--7)));
  --_text-style---margin-bottom: var(--_typography---richtext-h2--margin-bottom, var(--_typography---h2--margin-bottom, var(--_spacing---space--5)));
  --_text-style---trim-top: var(--_typography---richtext-h2--trim-top, var(--_typography---h2--trim-top, var(--_typography---font--primary-trim-top)));
  --_text-style---trim-bottom: var(--_typography---richtext-h2--trim-bottom, var(--_typography---h2--trim-bottom, var(--_typography---font--primary-trim-bottom)));
}
.u-rich-text h3:not([class*="u-text-style-"]) {
  --_text-style---font-family: var(--_typography---richtext-h3--family, var(--_typography---h3--family, var(--_typography---font--primary-family)));
  --_text-style---font-size: var(--_typography---richtext-h3--font-size, var(--_typography---h3--font-size, var(--_typography---font-size--h3)));
  --_text-style---line-height: var(--_typography---richtext-h3--line-height, var(--_typography---h3--line-height, var(--_typography---line-height--medium)));
  --_text-style---font-weight: var(--_typography---richtext-h3--weight, var(--_typography---h3--weight, var(--_typography---font--primary-medium)));
  --_text-style---margin-top: var(--_typography---richtext-h3--margin-top, var(--_typography---h3--margin-top, var(--_spacing---space--7)));
  --_text-style---margin-bottom: var(--_typography---richtext-h3--margin-bottom, var(--_typography---h3--margin-bottom, var(--_spacing---space--5)));
  --_text-style---trim-top: var(--_typography---richtext-h3--trim-top, var(--_typography---h3--trim-top, var(--_typography---font--primary-trim-top)));
  --_text-style---trim-bottom: var(--_typography---richtext-h3--trim-bottom, var(--_typography---h3--trim-bottom, var(--_typography---font--primary-trim-bottom)));
}
.u-rich-text h4:not([class*="u-text-style-"]) {
  --_text-style---font-family: var(--_typography---richtext-h4--family, var(--_typography---h4--family, var(--_typography---font--primary-family)));
  --_text-style---font-size: var(--_typography---richtext-h4--font-size, var(--_typography---h4--font-size, var(--_typography---font-size--h4)));
  --_text-style---line-height: var(--_typography---richtext-h4--line-height, var(--_typography---h4--line-height, var(--_typography---line-height--medium)));
  --_text-style---font-weight: var(--_typography---richtext-h4--weight, var(--_typography---h4--weight, var(--_typography---font--primary-medium)));
  --_text-style---margin-top: var(--_typography---richtext-h4--margin-top, var(--_typography---h4--margin-top, var(--_spacing---space--7)));
  --_text-style---margin-bottom: var(--_typography---richtext-h4--margin-bottom, var(--_typography---h4--margin-bottom, var(--_spacing---space--4)));
  --_text-style---trim-top: var(--_typography---richtext-h4--trim-top, var(--_typography---h4--trim-top, var(--_typography---font--primary-trim-top)));
  --_text-style---trim-bottom: var(--_typography---richtext-h4--trim-bottom, var(--_typography---h4--trim-bottom, var(--_typography---font--primary-trim-bottom)));
}
.u-rich-text h5:not([class*="u-text-style-"]) {
  --_text-style---font-family: var(--_typography---richtext-h5--family, var(--_typography---h5--family, var(--_typography---font--primary-family)));
  --_text-style---font-size: var(--_typography---richtext-h5--font-size, var(--_typography---h5--font-size, var(--_typography---font-size--h5)));
  --_text-style---line-height: var(--_typography---richtext-h5--line-height, var(--_typography---h5--line-height, var(--_typography---line-height--medium)));
  --_text-style---font-weight: var(--_typography---richtext-h5--weight, var(--_typography---h5--weight, var(--_typography---font--primary-medium)));
  --_text-style---margin-top: var(--_typography---richtext-h5--margin-top, var(--_typography---h5--margin-top, var(--_spacing---space--7)));
  --_text-style---margin-bottom: var(--_typography---richtext-h5--margin-bottom, var(--_typography---h5--margin-bottom, var(--_spacing---space--4)));
  --_text-style---trim-top: var(--_typography---richtext-h5--trim-top, var(--_typography---h5--trim-top, var(--_typography---font--primary-trim-top)));
  --_text-style---trim-bottom: var(--_typography---richtext-h5--trim-bottom, var(--_typography---h5--trim-bottom, var(--_typography---font--primary-trim-bottom)));
}
.u-rich-text h6:not([class*="u-text-style-"]) {
  --_text-style---font-family: var(--_typography---richtext-h6--family, var(--_typography---h6--family, var(--_typography---font--primary-family)));
  --_text-style---font-size: var(--_typography---richtext-h6--font-size, var(--_typography---h6--font-size, var(--_typography---font-size--h6)));
  --_text-style---line-height: var(--_typography---richtext-h6--line-height, var(--_typography---h6--line-height, var(--_typography---line-height--medium)));
  --_text-style---font-weight: var(--_typography---richtext-h6--weight, var(--_typography---h6--weight, var(--_typography---font--primary-medium)));
  --_text-style---margin-top: var(--_typography---richtext-h6--margin-top, var(--_typography---h6--margin-top, var(--_spacing---space--7)));
  --_text-style---margin-bottom: var(--_typography---richtext-h6--margin-bottom, var(--_typography---h6--margin-bottom, var(--_spacing---space--4)));
  --_text-style---trim-top: var(--_typography---richtext-h6--trim-top, var(--_typography---h6--trim-top, var(--_typography---font--primary-trim-top)));
  --_text-style---trim-bottom: var(--_typography---richtext-h6--trim-bottom, var(--_typography---h6--trim-bottom, var(--_typography---font--primary-trim-bottom)));
}

/* ===== CUSTOM OVERRIDE [CO-48(a)+(b)]: a rich-text block's children keep their own type
   and their own rhythm (PRD-typography-contract D17/P9 — Kris, 27 Jul 2026) =====
   GENERATED with CO-48(c) — run `node scripts/gen-richtext-channels.mjs --part release`.

   (a) THE TYPOGRAPHY EXEMPTION, CO-44's mechanism keyed on `.u-rich-text`. Measured on
   `gallery`: a heading inside `u-text w-richtext` renders Inter 17px w400 and stays there
   when `u-rich-text` is added, because `.u-text h1..h6{…: inherit}` at (0,1,1) beats the bare
   `h2{}` tag rule at (0,0,1). Nothing in the framework gave a rich-text heading its own tier —
   `.u-rich-text h1..h6,p` sets MARGINS only. Re-asserting the seven inherited properties from
   the heading's own slots at (0,2,1) recovers it outright: 17px -> 38.19px, w400 -> w300,
   lh 28 -> 46.6. Same shape as CO-44 and the same reason: the wrapper form's inherit rule is
   unconditional, so the exemption has to be.

   (b) THE MARGIN RELEASE. After (a) the heading is still `mt 0`, because
   `.u-text > *{margin-top: 0 !important}` beats `.u-rich-text h1..h6,p{margin-top: var(…)}`.
   That !important is D1-era BLOCK CONTAINMENT — right for a wrapper holding one text block,
   wrong inside rich text, where the children are supposed to carry their own rhythm. D16 is
   what makes the scope exact: a `u-text` holding many children is ONLY ever a `.u-rich-text`,
   so `.u-text.u-rich-text` is precisely the case the containment rule should not govern.

   It carries !important because the declaration it must beat does, and it is derived rather
   than transcribed because two of the values it re-asserts are literal spacing steps.
   FIRST CHILDREN ARE EXCLUDED via CO-45b's "meaningful preceding sibling" test, verbatim:
   lumos.css's margin trim already zeroes a block's first child and carries NO !important, so
   without the guard this rule would beat it and open a gap at the top of every rich-text block.

   AND THE RELEASE IS NOT ENOUGH ON ITS OWN — measured, and it is the finding of this step.
   `.u-text` is `display: flex; flex-flow: column`, so adjacent margins DO NOT COLLAPSE inside
   it. The `margin-top: 0 !important` was doing a second job nobody had named: it is what made
   a FLEX wrapper produce block-flow rhythm. Release it alone and every gap becomes the SUM of
   two edges instead of the larger of them — measured on the repro, a `p` -> `h2` gap goes
   24px -> 72px (24 margin-bottom + 48 margin-top) where correct rich text renders 48px.
   CO-43(d) had already measured this class of divergence (16.3px) and named `.u-text > *` as
   its cause; this is the same finding from the other end.

   So the block returns to NORMAL FLOW, which is what rich text is: prose whose margins collapse.
   The flex column is the SINGLE-text-block wrapper's layout, and under D16 a `.u-text` holding
   many children is only ever a `.u-rich-text` — so `.u-text.u-rich-text` is exactly the case
   that wants flow. The framework already agrees with itself here: a `.u-rich-text` WITHOUT
   `.u-text` is block flow today and renders the rhythm correctly (Kyoom's blog-post h2:
   64px/lh 64/w500/Aleo/mt 48 mb 32), which is the control D17 said must be preserved. With
   the flow restored the WRAPPED block reproduces that control exactly — 48px and 48px, the
   same tier, the same margin — so the two shapes stop disagreeing, which is the split D17
   exists to close. `flow-root` rather than `block` so the wrapper keeps containing its
   children's floats and margins at its own edges, as `flex` did.

   NOT INERT, deliberately — this is the fix, gated on before/after heights with the measured
   population rather than on 0 deltas (D15's rule). `li` is not released: it is never a direct
   child of the block, so `.u-text > *` never reaches it, and its margin-top is negative
   cap-trim rather than rhythm. RE-SYNC: retires if a future Lumos scopes `.u-text > *` or
   `.u-text h1..h6` itself. Tracked in `_inventory/custom-overrides.md` (CO-48). ===== */
.u-rich-text :is(h1,h2,h3,h4,h5,h6):not([class*="u-text-style-"]) {
  font-family: var(--_text-style---font-family);
  font-size: var(--_text-style---font-size);
  font-weight: var(--_text-style---font-weight);
  line-height: var(--_text-style---line-height);
  letter-spacing: var(--_text-style---letter-spacing);
  text-transform: var(--_text-style---text-transform, none);
  text-wrap: var(--_text-style---text-wrap);
}

.u-text.u-rich-text {
  display: flow-root;
}

.u-text.u-rich-text > :not(.w-condition-invisible,.u-cover-absolute,.u-ignore-trim) ~ :is(h1,h2,h3,h4,h5,h6,p,ol,ul) {
  margin-top: var(--_text-style---margin-top) !important;
}

.u-text.u-rich-text > :not(.w-condition-invisible,.u-cover-absolute,.u-ignore-trim) ~ blockquote {
  margin-top: var(--_spacing---space--6) !important;
}

.u-text.u-rich-text > :not(.w-condition-invisible,.u-cover-absolute,.u-ignore-trim) ~ figure {
  margin-top: var(--_spacing---space--7) !important;
}

/* ===== CUSTOM OVERRIDE [CO-50(a)]: the focus ring gets a COLOUR token =====
   (Kris, 27 Jul 2026: "we should surface the focus offset px and border color …
   default value of --focus--color to var(--_theme---text) but this would be
   changeable in the Design tab of the platform")

   lumos.css already tokenises two thirds of the focus ring — `--focus--width` and
   `--focus--offset-outer` (plus `--focus--offset-inner` for controls whose ring must sit
   inside the box). The COLOUR alone is hardcoded, written straight into the rule as
   `var(--_theme---text)`. So a tenant that wants a brand-coloured ring has no token to
   set and must replace the whole rule.

   THAT IS NOT HYPOTHETICAL — IT IS WHY G&A HAS A BUG. G&A wanted a brand ring, so it
   authored its own `:focus-visible` block (`styles.css:254`), and that block also carries
   `border-radius: 2px`, which sets the ELEMENT's radius while focused rather than the
   ring's. Measured on a pill button: `1240px -> 2px` on focus, so the background repaints
   as a rectangle inside a ring the button no longer matches. The framework needs no radius
   token — an outline already follows the element's own border-radius, verified by capture:
   pill button, pill ring, at the 3px offset. The fix is for nobody to have to write that
   rule at all.

   NAMING IS `--focus--color`, NOT a new `--_focus---*` namespace, and that is deliberate.
   The framework already ships `--focus--width` / `--focus--offset-outer` / `--focus--offset-inner`
   at `:root`; joining that family converges on Lumos's own vocabulary rather than minting a
   parallel private one (CO-41's lesson, and [[feedback_no_private_tokens_per_selector]]).
   It also means the Design tab needs NO change: `src/tokens.js`'s Components group already
   allowlists the `--focus--` prefix, so the colour appears beside the width and offset the
   moment it exists.

   NO `:root` DEFAULT, DELIBERATELY — and this is CO-41(b)'s trap, which the first cut of
   this CO walked straight into. Custom properties inherit their COMPUTED value, so
   `:root{--focus--color: var(--_theme---text)}` resolves the theme colour AT THE ROOT and
   every element then inherits that one value. `--_theme---text` is contextual: it is what
   makes the ring white on a dark section and near-black on a light one. Measured on Kyoom
   with the pseudo-state forced, the `:root` version moved a focused button and input from
   `rgb(255,255,255)` to `rgb(31,29,30)` — the ring stopped being theme-aware, which is a
   regression dressed as a token.

   So the default lives in the CONSUMING `var()` fallback instead, where `--_theme---text`
   resolves ON THE ELEMENT. Inert by construction: an unseeded tenant resolves exactly the
   expression the rule used to carry. A tenant seeds `--focus--color` at `:root` (or on any
   scope) and it wins from there down.

   CONSEQUENCE FOR THE DESIGN TAB, stated rather than discovered later: `src/tokens.js`
   lists tokens it finds in a `:root` block, so an unseeded `--focus--color` does not appear
   in the panel — exactly as CO-41(b)'s trim pair does not. It appears the moment a tenant
   sets it, which is the path the panel itself writes. Seeding it in a tenant's `styles.css`
   is how it becomes editable, and that seed is a tenant decision, not a framework default.

   Gated on both tenants — and the zero is not blind, because a `:focus-visible` ring does
   not exist in a static capture at all; it is verified with the pseudo-state forced via CDP.
   RE-SYNC: retire if a future Lumos tokenises the colour itself; reconcile the name.
   Tracked in `_inventory/custom-overrides.md` (CO-50). ===== */
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
details:focus-visible {
  outline-color: var(--focus--color, var(--_theme---text));
}

/* ===== CUSTOM OVERRIDE [CO-49]: the button component may BE the control =====
   (Kris, 27 Jul 2026, on the button-anatomy specimen: "option C")

   WHY. Stock Lumos ships the button as an OVERLAY composition — a `div.button_main_wrap`
   containing an empty, absolutely-positioned `a.clickable_link` that covers the box, with
   the visible label marked `aria-hidden` and the accessible name supplied by the anchor's
   `aria-label`. That shape exists to satisfy a Webflow Designer constraint (a component
   root cannot be made a link there) and to dodge the illegal `<a>`-inside-`<a>` case.

   It is the wrong default for us on ACCESSIBILITY grounds, which is the reason for this CO
   rather than taste. The label is written TWICE — once visibly, once in `aria-label` — with
   nothing keeping them in step, which is a WCAG 2.5.3 (Label in Name) failure waiting on the
   first copy edit, and an unlabelled link (2.4.4 / 4.1.2) if the label is ever dropped, with
   no visible symptom. The semantic form has one label, by construction.

   So the component's WRAP may be the control itself: `<a class="button_main_wrap" href>` when
   it navigates, `<button class="button_main_wrap">` when it acts. Nothing in `.button_main_wrap`
   keys on `div`, so the class already works on any tag — the only thing missing is the reset a
   real `<button>` needs, because the UA styles buttons and `.button_main_wrap` never had to
   neutralise that. That reset is (a).

   THE OVERLAY IS NOT RETIRED. It stays for the case it was actually for: a whole CARD made
   clickable, where the box is the target, it may contain its own links, and there is no visible
   button label to duplicate — there the `aria-label` names a region rather than restating a
   label.

   (b) `summary`/`details` JOIN THE FOCUS CONTRACT. lumos.css:8219 gives `a, button, [tabindex],
   input, select, textarea` a 3px outline-offset and a 2px `:focus-visible` ring — `summary` and
   `details` are in NEITHER list. That became load-bearing the moment the kit's accordions moved
   to native disclosure markup: measured on the kit pages, a focused `<summary>` renders G&A's own
   tenant ring (2px solid, offset 2px — its rule lists `summary`) but on Kyoom falls all the way
   back to the BROWSER DEFAULT (`1px auto rgb(0,95,204)`, offset -2px), inconsistent with every
   other control on the site. This restates the framework's own two rules for the two tags it
   missed; no token changes, `--focus--offset-outer` is already .1875rem.

   INERT AT MINT for (a): no markup carries `button.button_main_wrap` yet — it is minted ahead of
   the kit conversion so that markup lands on a reset that already exists, the CO-46/CO-48 order.
   (b) is NOT visible to a static gate at all, because a `:focus-visible` ring only exists while
   focused; it is verified with the pseudo-state forced via CDP instead.
   RE-SYNC: keep until Lumos ships a tag-agnostic button and a `summary` focus state.
   Tracked in `_inventory/custom-overrides.md` (CO-49). ===== */
button.button_main_wrap,
button.button_arrow_wrap,
button.button_toggle_wrap,
button.button_close_wrap {
  appearance: none;
  background: none;
  border: 0;
  margin: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: inherit;
  cursor: pointer;
}

summary,
details {
  outline-offset: var(--focus--offset-outer);
}
summary:focus-visible,
details:focus-visible {
  outline-color: var(--_theme---text);
  outline-width: var(--focus--width);
  outline-style: solid;
}

/* ===== CUSTOM OVERRIDE [CO-50(b)]: the button's box and its label get a token contract =====
   PRD-button-token-contract §4 (Kris, 27 Jul 2026): *"we should have centralised tokens to
   style buttons (this is very important for composition layer and for eventually starting a
   new site directly from the admin platform)."* GENERATED — run
   `node scripts/gen-button-channels.mjs`; test/css-files.mjs re-derives it and fails on drift.

   WHY. A tenant could not style a button by setting VALUES. Colour and hover were fully
   tokenised (`--_theme---button-{primary,secondary}--*`); everything else — radius, padding,
   gap, line-height, border width, transition, and the label's entire typography — was a hard
   literal. G&A proved the cost: wanting square corners and its own label face it wrote
   `main a.button_main_wrap{…}`, a bespoke selector keyed on the element being a LINK, which
   no shared rule can ever reach. One rule, and a day of symptoms.

   TWO LAYERS, THE SAME ONES THE TYPE SYSTEM ALREADY HAS — and this is the whole correction
   to CO-50(b)'s reverted first cut, which APPLIED properties (`font-size: var(--token,
   inherit)`) as if that were inert. It is not: `inherit` still WINS the cascade, and it
   erased lumos.css:8686's small-variant sizing (measured, Kyoom: 16px -> 18px, box
   147.45x37.7 -> 160.63x39.16). So:

       --button--<member>--<slot>   the TENANT channel   (cf. --_typography---h2--font-size)
       --_button-style---<slot>     the resolved SLOT    (cf. --_text-style---font-size)

   The appliers read the slot, ONCE. Every variant and every size RE-POINTS the slot instead
   of re-applying the property, so no rule here can overrule a more specific framework rule
   by accident. [[feedback_set_slots_not_properties]].

   TWO ORTHOGONAL AXES (Kris, 27 Jul), not twelve fused classes: `v-button-main-primary` +
   `v-button-size-small` is 4 + 3 classes where the fused form is 12, and a fourth variant or
   a fourth size is then additive rather than combinatorial. The slot sets are DISJOINT —
   variant owns radius / border-width / transition, size owns font-size / line-height /
   padding / gap — which is what lets both live in one flat `--button--<name>--<slot>`
   namespace with no collision. `alt` is the spare fourth identity, named for a slot rather
   than for a meaning it may not carry.

   NAMING JOINS `--button-size--*` / `--button--hover-lift`, the family lumos.css already
   ships, rather than minting a parallel `--_button---*` — CO-50(a)'s call for `--focus--color`
   and [[feedback_no_private_tokens_per_selector]]. (`--button-size--medium|large` are the
   TOGGLE button's square dimensions and are a different family from `--button--large--*`; the
   prefixes differ, so nothing collides.) `--_button-style---*` is the resolved slot layer and
   is framework-internal, exactly as `--_text-style---*` is.

   THE LABEL IS A CARRIER TOO, and that is faithfulness, not belt-and-braces. lumos.css keys
   the small padding on the ELEMENT (8685) and the small type on the TEXT (8686), so the two
   halves of the fused class read different carriers. Listing all three reproduces that on
   every configuration: class on both (today's markup, 67 G&A / 60 Kyoom / 1 kit, always both)
   → small box and small type; class on the element only → small box, label unchanged, exactly
   as 8686 not matching leaves it. Drop `.button_main_text` from the list and that last case
   silently gains small type. THE MIGRATION MUST THEREFORE PUT `v-button-size-small` ON THE
   SAME CARRIERS the fused class sits on — both — which is what a dual-key sweep does anyway.

   THE LABEL'S VOICE IS `u-text-style-button`, minted in lumos.css beside the other voices
   (D18: a button label is text, and text gets a voice — the same call CO-46 made for the
   eyebrow). Its font-size and line-height read the SIZE slot, so one voice serves all three
   sizes. Until the markup adopts it, `.button_main_text.u-text-style-main` below routes the
   same slot into the same place, so BOTH spellings resolve one channel set.

   ORDER IS BASE -> VARIANT -> SIZE, and that is FORCED, not chosen: lumos.css writes the
   small padding at line 8685, AFTER the link padding at 5872, so size already wins a
   link+small collision today. Reproducing the framework's own tie-break is what makes this
   inert. Whether that tie-break is DESIRABLE (a small link button taking a small button's
   horizontal padding) is a question for the size-axis migration, not for this mint.

   THE FAMILY CHANNEL SITS IN THE CHAIN ONLY WHERE THE VALUE IS SHARED — border-width,
   transition, line-height, gap, and radius on the three variants that share it. Where a
   member already differs (link's 0 radius and `.5rem 0` padding; each size's own type tier
   and padding) the fallback is that member's OWN value and there is no family channel above
   it, because one there is a trap: `--button--radius` would round off the variant lumos
   squares on purpose, and `--button--padding-block` would flatten the size axis into one
   size. A tenant that wants every button more padded therefore sets three tokens, one per
   size — which is right, because "more padded" is a different number at each size, and that
   is what having a size axis means.

   COLOUR AND HOVER ARE NOT RE-MINTED. They are already tokenised through
   `--_theme---button-<variant>--*` blended by `--_trigger---on/off`, and re-expressing them
   would be churn. The consequence, stated rather than discovered: `v-button-main-alt` renders
   PRIMARY's colourway until the theme family gains a `button-alt--*` row. Its box is its own
   from today.

   INERT AT MINT BY CONSTRUCTION: every fallback is lumos.css's own expression, derived from
   the rule that carries it. RE-SYNC: retire the slots a future Lumos tokenises itself;
   reconcile the names. Tracked in `_inventory/custom-overrides.md` (CO-50). ===== */
:is(.button_main_wrap, .button_main_element, .button_main_text) {
  --_button-style---radius: var(--button--primary--radius, var(--button--radius, var(--radius--round)));
  --_button-style---border-width: var(--button--primary--border-width, var(--button--border-width, var(--border-width--main)));
  --_button-style---transition-duration: var(--button--primary--transition-duration, var(--button--transition-duration, .2s));
  --_button-style---transition-timing: var(--button--primary--transition-timing, var(--button--transition-timing, ease));
  --_button-style---font-size: var(--button--regular--font-size, var(--_typography---text-main--font-size, var(--_typography---font-size--text-main)));
  --_button-style---line-height: var(--button--regular--line-height, var(--button--line-height, 1));
  --_button-style---padding-block: var(--button--regular--padding-block, .9rem);
  --_button-style---padding-inline: var(--button--regular--padding-inline, 1.5rem);
  --_button-style---gap: var(--button--regular--gap, var(--button--gap, .5rem));
}

:is(.button_main_wrap, .button_main_element, .button_main_text):where(.v-button-main-secondary) {
  --_button-style---radius: var(--button--secondary--radius, var(--button--radius, var(--radius--round)));
  --_button-style---border-width: var(--button--secondary--border-width, var(--button--border-width, var(--border-width--main)));
  --_button-style---transition-duration: var(--button--secondary--transition-duration, var(--button--transition-duration, .2s));
  --_button-style---transition-timing: var(--button--secondary--transition-timing, var(--button--transition-timing, ease));
}

:is(.button_main_wrap, .button_main_element, .button_main_text):where(.v-button-main-link) {
  --_button-style---radius: var(--button--link--radius, 0);
  --_button-style---border-width: var(--button--link--border-width, var(--button--border-width, var(--border-width--main)));
  --_button-style---transition-duration: var(--button--link--transition-duration, var(--button--transition-duration, .2s));
  --_button-style---transition-timing: var(--button--link--transition-timing, var(--button--transition-timing, ease));
  --_button-style---padding-block: var(--button--link--padding-block, .5rem);
  --_button-style---padding-inline: var(--button--link--padding-inline, 0);
}

:is(.button_main_wrap, .button_main_element, .button_main_text):where(.v-button-main-alt) {
  --_button-style---radius: var(--button--alt--radius, var(--button--radius, var(--radius--round)));
  --_button-style---border-width: var(--button--alt--border-width, var(--button--border-width, var(--border-width--main)));
  --_button-style---transition-duration: var(--button--alt--transition-duration, var(--button--transition-duration, .2s));
  --_button-style---transition-timing: var(--button--alt--transition-timing, var(--button--transition-timing, ease));
}

:is(.button_main_wrap, .button_main_element, .button_main_text):where(.v-button-size-small),
:is(.button_main_wrap, .button_main_element, .button_main_text):where(.v-button-main-primary-small) {
  --_button-style---font-size: var(--button--small--font-size, var(--_typography---text-small--font-size, var(--_typography---font-size--text-small)));
  --_button-style---line-height: var(--button--small--line-height, var(--button--line-height, 1));
  --_button-style---padding-block: var(--button--small--padding-block, 0.75rem);
  --_button-style---padding-inline: var(--button--small--padding-inline, 1.25rem);
  --_button-style---gap: var(--button--small--gap, var(--button--gap, .5rem));
}

:is(.button_main_wrap, .button_main_element, .button_main_text):where(.v-button-size-large) {
  --_button-style---font-size: var(--button--large--font-size, var(--_typography---text-large--font-size, var(--_typography---font-size--text-large)));
  --_button-style---line-height: var(--button--large--line-height, var(--button--line-height, 1));
  --_button-style---padding-block: var(--button--large--padding-block, 1.08rem);
  --_button-style---padding-inline: var(--button--large--padding-inline, 1.8rem);
  --_button-style---gap: var(--button--large--gap, var(--button--gap, .5rem));
}

/* The appliers. Each property is read ONCE, from the slot, so the whole cascade above is
   slot re-points and this is the only place a button's box is actually written. These beat
   lumos.css on source order at equal specificity, which is why they must restate the
   per-variant and per-size rules too — a base-only applier would flatten link and small. */
.button_main_wrap {
  border-radius: var(--_button-style---radius);
}

.button_main_element {
  padding: var(--_button-style---padding-block) var(--_button-style---padding-inline);
  gap: var(--_button-style---gap);
  line-height: var(--_button-style---line-height);
  border-width: var(--_button-style---border-width);
  transition-duration: var(--_button-style---transition-duration);
  transition-timing-function: var(--_button-style---transition-timing);
}

/* THE MIGRATION EDGE. Today's labels carry `u-text-style-main` and lumos.css:8686 re-points
   their font-size for the fused small class at (0,2,0) — so a (0,1,0) channel rule would
   lose to it, which is exactly how the reverted cut broke Kyoom's small buttons. This
   matches that specificity and routes the SAME slot, so the old spelling, the new size
   class and (after adoption) `u-text-style-button` all read one channel set. Weight is not
   restated: 8686's is already `--_typography---font--primary-regular`, identical to the
   voice's own default, so it stays where it is. */
.button_main_text.u-text-style-main {
  --_text-style---font-size: var(--_button-style---font-size);
}

/* ===== CUSTOM OVERRIDE [CO-51]: the caption is one element with one channel set =====
   (Kris, 28 Jul 2026: "we've used a set class for Fig Captions outside of rich-text but we don't
   actually show the captions in rich-text element. We need a normalised caption element with
   minted tokens for the gallery and starter library.")

   THE SAME JOB WAS SOLVED THREE TIMES AND NONE OF IT WAS IN THE FRAMEWORK. `.figure_caption` (the
   authored figure label, 41 carriers) lived in the starter's AND G&A's styles.css as BYTE-IDENTICAL
   copies. `.u-rich-text figcaption` lived in the starter's and Kyoom's, written as literals
   (`.85em`, a color-mix of currentColor, space--2). G&A wrote a third, keyed
   `.u-rich figcaption, .article_body figcaption`. lumos itself contributes only Webflow's
   normalize (`figcaption{margin-top:5px; text-align:center}`), which G&A then reverts. So a
   CMS-authored caption had no contract at all, and every tenant invented one.

   ONE CHANNEL SET, `--_figure---caption--*`, which is the vocabulary the authored component
   ALREADY used — exhaust the library before minting a namespace ([[feedback_starter_library_doctrine]],
   [[feedback_no_private_tokens_per_selector]]). Both shapes resolve it, so a tenant seeds once.

   TEXT-TRANSFORM IS NOW A TOKEN (Kris): it was the ONE hardcoded literal in a component that
   channelled everything else, so no tenant could turn the uppercase off. Default `none` — the
   starter stays UNBRANDED and a caption is prose until a brand says otherwise; G&A seeds
   `uppercase` and keeps its label look exactly.

   THE LEAD-IN COVERS `b` AND `strong`, and that is the point of normalising rather than a
   nicety: authored markup writes `<b>` (41 of 41 uses) and rich text writes `<strong>` (39 in
   the trees), so a rule targeting `b` alone could never fire on CMS output. Covering both is
   what makes the same editorial device available to an editor.

   THE LEGACY INGEST WRAPPERS ARE DUAL-KEYED (Kris, 29 Jul 2026), same doctrine as CO-52's
   `w-richtext-align-*`. G&A's LIVE article bodies are `.article_body` / `.u-rich`, never
   `.u-rich-text` — the `.u-rich` -> `.u-rich-text` migration is a separate arc (its own
   `styles.css:1305` says so) — so retiring G&A's `.u-rich figcaption, .article_body figcaption`
   rule in favour of a component keyed on `.u-rich-text` left the contract unable to reach a
   single published article. Nothing was visibly broken because all 7 live articles carry ZERO
   figcaptions today, which is also why the suite and the fidelity gate both stayed green: G&A's
   articles live in D1, so no repo page and no probe ever sees them. The first caption an editor
   wrote would have rendered unstyled, as a client-visible bug rather than a test failure.
   Dual-keyed here rather than duplicated in the tenant sheet, so there is still ONE component;
   the legacy halves retire with the migration, exactly as the `w-` halves do.

   THE FLEX BASELINE ROW STAYS ON `.figure_caption` ONLY. It is the authored label's layout — a
   lead-in beside a caption on a shared baseline — and a rich-text figcaption is a single block of
   prose with nothing to sit beside.
   RE-SYNC: keep. Stock Lumos ships no caption component, only the normalize.
   Tracked in `_inventory/custom-overrides.md` (CO-51). ===== */
:is(.figure_caption, .u-rich-text figcaption, .u-rich figcaption, .article_body figcaption) {
  font-family: var(--_figure---caption--family, var(--_typography---font--primary-family));
  font-size: var(--_figure---caption--size, 0.75rem);
  line-height: var(--_figure---caption--line-height, var(--_typography---line-height--large));
  letter-spacing: var(--_figure---caption--letter-spacing, 0.14em);
  text-transform: var(--_figure---caption--text-transform, none);
  color: var(--_figure---caption--color, color-mix(in srgb, var(--_theme---text) 65%, transparent));
  margin-top: var(--_figure---caption--margin-top, var(--_spacing---space--2));
  text-align: var(--_figure---caption--text-align, left);
}

.figure_caption {
  display: flex;
  align-items: baseline;
  gap: var(--_figure---caption--gap, var(--_spacing---space--2));
}

:is(.figure_caption, .u-rich-text figcaption, .u-rich figcaption, .article_body figcaption) :is(b, strong) {
  color: var(--_figure---caption--lead-color, var(--_theme---heading-accent));
  font-weight: var(--_figure---caption--lead-weight, 600);
  letter-spacing: var(--_figure---caption--lead-letter-spacing, 0.12em);
}

/* ===== CUSTOM OVERRIDE [CO-52]: rich-text figures get OUR contract, transposed off Webflow =====
   (Kris, 28 Jul 2026: "these are webflow ingestions - we need to transpose it on to u-rich-text,
   eventually we need to retire/remove w- webflow classes.")

   THE BEHAVIOURS ALREADY EXISTED AND REACHED 38% OF THE MARKUP. lumos.css carries Webflow rich-text
   CSS: `figure img{width:100%}`, `figure{max-width:60%}`, the four alignment modes
   (floatleft / floatright / center / fullwidth) and the `:after{clear:both}` float containment. All
   of it is keyed on `.w-richtext` -- and only 418 of 1,104 `u-rich-text` blocks carry that class.
   On the other 686 NONE of it applies, which is why editor alignment does not work: the CSS is
   there and two thirds of the blocks cannot see it. Measured, not assumed.

   So the behaviours are transposed onto `.u-rich-text`, the class every block already has, and the
   Webflow spellings are DUAL-KEYED so the 418 keep working through the migration window
   ([[feedback_dual_key_not_rename_on_sweeps]]). Retiring the `w-` classes is then a markup sweep
   with nothing depending on it, which is the de-Webflow doctrine ([[feedback_lumos_de_webflow_authoring]]).

   `width: fit-content` on the figure replaces Webflow's `display:table` + `figcaption{display:
   table-caption}` trick. Both make the caption match the IMAGE width rather than the column;
   fit-content says it directly, and it leaves the figcaption a normal block so CO-51 governs it.

   THE FLOAT CONTAINMENT SPLITS BY SHAPE. `.u-text.u-rich-text` is already `display: flow-root`
   (CO-48(b)), which contains floats by definition, so it needs no clearfix. A BARE `.u-rich-text`
   is normal block flow and does, so it gets one -- scoped with `:not(.u-text)` so the two shapes
   are not both paying for it.

   THE INLINE MARGINS CARRY `!important` BECAUSE THE DECLARATION THEY MUST BEAT DOES. lumos ships
   `.u-text > * {margin-inline: 0 !important}` -- D1-era block containment, right for a wrapper
   holding one text block, wrong for a rich-text figure that has to centre or hold a float gap.
   Measured before adding it: centre resolved to margin 0 and both float gaps to 0, i.e. the
   alignment silently did nothing. Exactly the shape CO-48(b) hit with `margin-top`, one axis over.

   AND SO DOES THE MAX-WIDTH -- the same lumos declaration block, one property over, missed on the
   first cut and caught by the gallery specimens. `.u-text > *` also writes
   `max-width: inherit !important`, so inside a `u-text u-rich-text` wrapper the figure inherited
   the wrapper's MEASURE cap (`calc(var(--number) * 1ch)`, measured 1131.87px on the starter) and
   the 60% cap never applied. The visible consequence is that `center` and `fullwidth` rendered
   IDENTICALLY -- both full column width, so centring had nothing to centre. Note this was never a
   CO-52 regression: `.w-richtext figure{max-width:60%}` lost the same way, so Webflow's own cap has
   been dead in that wrapper all along; transposing it faithfully transposed the hole too. Scoped
   to `.u-rich-text figure` only, so the wrapper's measure still reaches its text.
   RE-SYNC: keep. Retire the `w-richtext-align-` half of each selector once the markup sweep lands.
   Tracked in `_inventory/custom-overrides.md` (CO-52). ===== */
.u-rich-text figure {
  position: relative;
  width: fit-content;
  max-width: var(--_richtext---figure--max-width, 60%) !important;
  /* The block rhythm, tokenised at its existing value (lumos.css:4382 wrote these as literals),
     so nothing moves here and a tenant gains the channel. NOT `!important`, deliberately: inside
     a `u-text` wrapper `.u-text > *{margin-top: 0 !important}` still zeroes it and CO-48(b)'s
     `~ figure` release still owns the rhythm, which is the arrangement that already gates clean. */
  margin-top: var(--_richtext---figure--margin-top, var(--_spacing---space--7));
  margin-bottom: var(--_richtext---figure--margin-bottom, var(--_spacing---space--7));
}

/* THE RATIO IS A CHANNEL, DEFAULTING TO THE ASSET'S OWN (Kris, 28 Jul 2026). `height: auto` with
   no `aspect-ratio` means a rich-text image is always its intrinsic shape, which is right as a
   DEFAULT and wrong as the only option: a CMS library is not ratio-consistent, so a tenant that
   wants every article image to crop to one shape has no value to set. `auto` keeps today's
   render exactly (the specimen's letterbox is the placeholder's own 1920x630 = 3.05:1, not a
   squash), and `object-fit` rides with it because a ratio without a fit mode DISTORTS rather
   than crops — the two are one decision and must be seedable as one.
   NOTE the framework-wide ratio default (`.u-image-wrapper{aspect-ratio: 2}`, lumos.css:2925) is
   NOT tokenised here: its six `v-visual-image-*` variants and seven `u-ratio-*` utilities are all
   `:where()`-wrapped, i.e. specificity (0,1,0), the SAME as the base, so a channel added from
   lumos-native would beat all thirteen on source order and flatten the ratio axis. That is
   CO-50's whole-writer-set lesson; it needs the same treatment and its own gate. */
.u-rich-text figure img {
  width: var(--_richtext---image--width, 100%);
  height: auto;
  aspect-ratio: var(--_richtext---image--ratio, auto);
  object-fit: var(--_richtext---image--object-fit, cover);
  display: block;
  border-radius: var(--_richtext---image--radius, 0);
}

.u-rich-text figure:is(.u-richtext-align-normal, .w-richtext-align-normal) {
  clear: both;
}

/* CENTRE AND FULLWIDTH ARE CHANNELLED TOO (Kris, 29 Jul 2026). They were the two modes with
   nothing to tune: every value they wrote was a literal, so the tuner resolved a centred figure
   to `base` alone and there was no way to express "centred means something different here"
   without a bespoke selector — the exact thing the contract exists to retire
   ([[feedback_no_private_tokens_per_selector]]). Each mode now channels EXACTLY the properties
   its rule already writes, at its existing value, so both are inert until seeded. `clear` is
   left alone deliberately: it is structural (whether the mode sits beside a float), not a design
   lever, and the float rules do not channel their `clear: none` either. */
.u-rich-text figure:is(.u-richtext-align-center, .w-richtext-align-center) {
  margin-inline: var(--_richtext---figure--center-margin-inline, auto) !important;
  clear: both;
}

.u-rich-text figure:is(.u-richtext-align-fullwidth, .w-richtext-align-fullwidth) {
  width: var(--_richtext---figure--fullwidth-width, 100%);
  /* !important to beat `.u-text > *{max-width: inherit !important}` like the base rule; it
     still beats the base's `!important` 60% because (0,2,1) > (0,1,1). */
  max-width: var(--_richtext---figure--fullwidth-max-width, 100%) !important;
  margin-inline: var(--_richtext---figure--fullwidth-margin-inline, auto) !important;
  clear: both;
}

/* ===== THE ADMIN'S IMAGE VOCABULARY, BRIDGED TO THE FIGURE (Kris, 13 Aug 2026).
   v2's rich-text editor writes its sizing classes on the IMG — one vocabulary
   location, by design; content-admin's own note says "the CSS makes the figure
   follow via :has()". A CAPTIONED image is wrapped in a <figure>, and the base
   rule above caps `.u-rich-text figure` at 60% !important, so the constrained
   element carries no marker the img classes can reach: full width on a
   captioned image was impossible from the editor whatever the button said
   (measured on G&A: figure 403px in a 672px column).

   WHY THIS LIVES IN THE FRAMEWORK AND NOT IN A BRAND SHEET. It is ADMIN
   vocabulary, so it is needed by every tenant edited through v2, not by one —
   and `sync-framework` copies this file byte-identically to all of them, while
   styles.css is a brand file it never touches. It also has to be HERE for the
   cascade to work at all: the brand sheets are unlayered, the framework is
   imported into `layer(yd-starter)`, and for !important declarations layer
   order INVERTS — a layered important beats an unlayered one at ANY
   specificity. The first attempt sat in the tenant's styles.css with
   !important and (0,2,2) against this rule's (0,1,1) and still computed 60%.
   In-layer, specificity decides again.

   CAUSE, not instance: every sizing class has this shape, not just full — a
   w75 image inside a 60% figure renders at 45% of the column. The pre-existing
   `figure:has(> img.rt-img--*)` bridges in the brand sheets handle floats and
   margins, which are NORMAL declarations an unlayered rule can win; only the
   max-width release needs to be here. ===== */
.u-rich-text figure:has(> img.rt-img--full) {
  width: var(--_richtext---figure--fullwidth-width, 100%);
  max-width: var(--_richtext---figure--fullwidth-max-width, 100%) !important;
  margin-inline: var(--_richtext---figure--fullwidth-margin-inline, auto) !important;
}
/* The width presets need the FIGURE released to the column, so the image's own
   percentage resolves against the column and not against 60% of it. */
.u-rich-text figure:has(> img:is(.rt-img--w25, .rt-img--w33, .rt-img--w50, .rt-img--w75, .rt-img--wc)) {
  width: 100%;
  max-width: var(--_richtext---figure--fullwidth-max-width, 100%) !important;
}

/* A FLOAT DOES NOT CARRY THE BLOCK RHYTHM (Kris, 28 Jul 2026). The 48px above is the in-flow
   figure's contract — the gap between it and the paragraph before it. A float has no paragraph
   before it: it is out of flow, prose is supposed to wrap beside it from the FIRST line, and the
   rhythm margin instead pushed the image down the column on its own. Measured on the starter at
   1440: figure `margin-top: 48px` against a sibling `<p>` whose own 24px collapsed out through
   the bare block's top edge, so the image sat **52.55px below the prose's first line box**.
   Webflow has the same hole — its float rules set only `float`, one inline margin and `clear` —
   so this too came across with the transposition rather than being introduced by it.
   Own channels, defaulting to `--_spacing---space--0`, so the float starts level and a tenant
   that wants the offset back sets a value rather than fighting a literal. `!important` because
   `.u-text > *{margin-top: 0 !important}` and CO-48(b)'s `~ figure` release both write this
   property — without it a seed would work in a bare block and silently do nothing in a `u-text`
   one, which is the exact failure this CO already hit on `margin-inline` and `max-width`.

   AND THE INLINE GAP IS THE SITEWIDE GUTTER (Kris). It is the space on the side that FACES THE
   TEXT — the same measure as every other column gap on the page — so its default is
   `--site--gutter`, not a spacing step chosen for this component alone. Renamed from the
   one-session-old `--_richtext---figure--float-gap`; nothing seeds it yet (0 across all three
   tenants), so this is a rename with no dual-key owed. */
.u-rich-text figure:is(.u-richtext-align-floatleft, .w-richtext-align-floatleft) {
  float: left;
  margin-top: var(--_richtext---figure--float-margin-top, var(--_spacing---space--0)) !important;
  margin-bottom: var(--_richtext---figure--float-margin-bottom, var(--_spacing---space--0)) !important;
  margin-right: var(--_richtext---figure--float-gutter, var(--site--gutter)) !important;
  clear: none;
}

.u-rich-text figure:is(.u-richtext-align-floatright, .w-richtext-align-floatright) {
  float: right;
  margin-top: var(--_richtext---figure--float-margin-top, var(--_spacing---space--0)) !important;
  margin-bottom: var(--_richtext---figure--float-margin-bottom, var(--_spacing---space--0)) !important;
  margin-left: var(--_richtext---figure--float-gutter, var(--site--gutter)) !important;
  clear: none;
}

.u-rich-text:not(.u-text)::after {
  content: "";
  display: table;
  clear: both;
}

/* ===== CUSTOM OVERRIDE [CO-18]: emphasis-in-heading span (H5; wires off CO-8) =====
   A heading runs in one weight; a single inline span carries a CONTRASTING treatment - the
   "emphasis" word(s). This is the explicit hook for it (D4): mixed-weight / mixed-font headings
   WITHOUT overloading <em> (reserved for true italics). It mirrors CO-8's secondary re-point - so
   a dual-font tenant can give the accent the secondary FAMILY - AND flips the weight to a dedicated
   --emphasis-weight token. The common case is a heavy heading + a light accent (Kyoom: Inter-700
   heading / Inter-200 upright accent). Either split order works: apply the class to whichever part
   is the accent (#7 accent = the lighter tail; #8 accent = the lighter head).
   Set font-family/weight DIRECTLY (not only via the token re-point) because an inline <span> inherits
   its parent's COMPUTED font + weight, so the span needs its own declarations to change. The token
   re-points are kept too, for the case where the class is applied to a whole heading block.
   Single-font starter: --secondary-family defaults to primary (no font change), so only the weight
   contrast shows - which is exactly the gallery demo. RE-SYNC: stock Lumos has no emphasis hook; keep.
   Tracked CO-18 + the divergence map. ===== */
:root { --_typography---font--emphasis-weight: 300; }   /* the accent weight; a tenant sets it (Kyoom = 200) */
.u-heading-emphasis {
  font-family: var(--_typography---font--secondary-family);
  font-weight: var(--_typography---font--emphasis-weight);
  --_typography---font--primary-family:      var(--_typography---font--secondary-family);
  --_typography---font--primary-trim-top:    var(--_typography---font--secondary-trim-top);
  --_typography---font--primary-trim-bottom: var(--_typography---font--secondary-trim-bottom);
}

/* Eyebrow heading reset — the eyebrow analog of the `.u-text h1..h6` reset in lumos.css. An eyebrow
   may carry a SEMANTIC heading tag (e.g. the page <h1> styled as a small eyebrow label — the About
   hero: h1 "About Kyoom" reads as an eyebrow, the big statement below is a <p>). A raw <h1> keeps its
   display font-size and blows the eyebrow up, so inherit the eyebrow's own text style instead.
   RE-SYNC: stock Lumos eyebrows never hold a heading; keep. (Registry candidate — see custom-overrides.md.) */
.u-eyebrow-text :is(h1,h2,h3,h4,h5,h6) {
  font-family: inherit; font-size: inherit; font-weight: inherit; line-height: inherit;
  letter-spacing: inherit; text-transform: inherit; text-wrap: inherit; margin: 0;
}

/* NOTE: the former subset-gap overrides CO-9..CO-16 (background-slot z-index, u-hide-if-empty,
   default-collection tokens, u-shadow-primary, nav overlap/sizing/radius, primary-small variant)
   are now folded into the canonical lumos.css via build-lumos.sh block 7. See _inventory/custom-overrides.md. */

/* ===== CUSTOM OVERRIDE [CO-17]: Nav layout variants (ADDITION) =====
   The nav bar (.nav_desktop_contain) is a floating, rounded, theme-glassy strip. Block 7 (lumos.css)
   ships ONE layout - a content-width pill, centred (width:max-content + margin:auto). This lifts that
   into an opt-in choice via a `data-nav-layout` attribute on .nav_component (mirrors the data-card-*
   variant pattern), so a tenant PICKS a layout instead of forking the nav. CENTERED is the default
   (no attribute => block 7 stays in force), so existing markup + computed-style parity are unchanged.
   Five layouts:
     centered - content-width pill, centred + floating         (default)
     full     - floating rounded bar, spans the content column
     content  - content-width pill, left-aligned to the column
     bar      - flush full-bleed 100vw top bar (classic header): no float gap, no radius, edge-to-edge
                background; inner content stays column-constrained via .nav_desktop_layout (already
                max-width-inner + margin:auto). Pairs well with a solid (non-glassy) --_theme---nav--background.
     vertical - full-height sidebar down ONE side. SECOND attribute data-nav-align="left" (default) |
                "right" picks the side. The horizontal row (.nav_desktop_layout) flips to a column, the
                link list stacks, and the sibling .page_main is padded by --nav--vertical-width so content
                clears the fixed sidebar. (Dropdowns still open as the horizontal mega-menu - a vertical
                fly-out treatment is a follow-up.)
   Variant rules out-specify block 7's bare `.nav_desktop_contain` (attr+class = 0,2,0 > 0,1,0).
   Set the attribute on the nav root:  <div class="nav_component" data-nav-layout="vertical" data-nav-align="right">  ===== */
:root { --nav--vertical-width: 16rem; --nav--vertical-collapsed: 4.25rem; }   /* sidebar footprint (expanded / collapsed rail) for data-nav-layout="vertical" */
[data-nav-layout="centered"] .nav_desktop_contain {   /* = default (block 7); listed for round-tripping */
  width: max-content; margin-left: auto; margin-right: auto;
}
[data-nav-layout="full"] .nav_desktop_contain {        /* floating bar: spans the content column */
  width: calc(100% - var(--nav--spacing-outer-horizontal) * 2); margin-left: auto; margin-right: auto;
}
[data-nav-layout="content"] .nav_desktop_contain {     /* content-width pill, left-aligned to the content column edge */
  width: max-content;
  margin-left: max(var(--nav--spacing-outer-horizontal), calc((100% - var(--nav--max-width-outer)) / 2));
  margin-right: auto;
}
[data-nav-layout="bar"] .nav_desktop_wrap { margin-top: 0; }   /* flush to the very top - no float gap */
[data-nav-layout="bar"] .nav_desktop_contain {                 /* full-bleed 100vw, square; content column-constrained by .nav_desktop_layout */
  width: 100%; max-width: none; border-radius: 0; margin-left: 0; margin-right: 0;
}

/* --- vertical sidebar (data-nav-layout="vertical"; data-nav-align left|right, default left) ---
   Everything stacks + LEFT-aligns and is clamped to the rail width (no horizontal overflow), both
   sides. A chevron toggle (.nav_collapse_btn, injected into the nav markup) flips data-nav-collapsed
   on .nav_component to shrink the rail to --nav--vertical-collapsed and hide labels. */
[data-nav-layout="vertical"] .nav_desktop_wrap { margin-top: 0; height: 100%; }
[data-nav-layout="vertical"] .nav_component { inset: 0 auto 0 0; width: var(--nav--vertical-width); transition: width .25s ease; }   /* LEFT (default) */
[data-nav-layout="vertical"][data-nav-align="right"] .nav_component { inset: 0 0 0 auto; }                                          /* RIGHT */
[data-nav-layout="vertical"] .nav_desktop_contain {
  position: relative;                                  /* anchors the collapse toggle */
  width: var(--nav--vertical-width); max-width: none; height: 100vh; min-height: 100%;
  margin: 0; border-radius: 0; overflow-x: hidden; overflow-y: auto;
}
/* The LAYOUT stays flex (column) so .nav_actions_wrap pins to the bottom (margin-top:auto) and
   align-items:stretch fills its direct children to the rail. Everything INSIDE resets to BLOCK so
   width:100% fills predictably and items stack in normal flow (the Webflow nav's nested horizontal
   flex bases - flex:1 1 0% / 0 0 auto + auto margins - were collapsing/shifting widths). The dropdown
   panel is kept as a GRID (its open/close animation) but made static + full-width so it expands
   IN-FLOW, pushing the items below it down. */
[data-nav-layout="vertical"] .nav_desktop_layout {
  display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start;
  height: 100%; max-width: none; row-gap: var(--_spacing---space--4);
}
[data-nav-layout="vertical"] .nav_links_component,
[data-nav-layout="vertical"] .nav_links_wrap,
[data-nav-layout="vertical"] .nav_links_item,
[data-nav-layout="vertical"] .nav_dropdown_component,
[data-nav-layout="vertical"] .nav_actions_wrap,
[data-nav-layout="vertical"] .nav_actions_wrap > * {
  display: block; width: 100%; max-width: 100%; margin-left: 0; margin-right: 0; float: none;
}
/* bottom-pin: only the links block grows (the layout's other direct children - logo + actions - are
   fixed), so the actions are pushed to the bottom of the rail. */
[data-nav-layout="vertical"] .nav_desktop_layout > * { flex: 0 0 auto; }
[data-nav-layout="vertical"] .nav_desktop_layout > .nav_links_wrap { flex: 1 1 auto; }
[data-nav-layout="vertical"] .nav_links_wrap > * + * { margin-top: var(--_spacing---space--1); }    /* link spacing (block, no flex gap) */
[data-nav-layout="vertical"] .nav_actions_wrap > * + * { margin-top: var(--_spacing---space--3); }  /* actions spacing */
/* the link row keeps flex: label LEFT, dropdown chevron RIGHT; flush to the rail's left edge */
[data-nav-layout="vertical"] .nav_links_link { display: flex; justify-content: space-between; align-items: center; width: 100%; padding-left: 0; }
[data-nav-layout="vertical"] .button_main_wrap,
[data-nav-layout="vertical"] .button_main_element { width: 100%; max-width: 100%; }
/* logo + links share the rail's left edge */
[data-nav-layout="vertical"] .nav_desktop_logo,
[data-nav-layout="vertical"] .nav_links_component { padding-left: 0; margin-left: 0; }
/* dropdown panel: keep the grid (open/close animation) but fill the rail + flow in-column; labels wrap */
[data-nav-layout="vertical"] .nav_dropdown_main_wrap,
[data-nav-layout="vertical"] .nav_dropdown_mega_wrap { position: static; width: 100%; max-width: none; }
[data-nav-layout="vertical"] .nav_dropdown_text { white-space: normal; }

/* collapse toggle (chevron). The button is injected into .nav_desktop_contain; scaleX flips the
   glyph by side + state so it always points the way it will move. */
[data-nav-layout="vertical"] .nav_collapse_btn {
  position: absolute; top: var(--_spacing---space--3); right: var(--_spacing---space--3);
  width: 1.9rem; height: 1.9rem; padding: 0; display: grid; place-items: center; cursor: pointer;
  border-radius: 99px; background: var(--_theme---background-2); color: var(--_theme---text);
  border: 1px solid var(--_theme---border); z-index: 5; pointer-events: auto;   /* .nav_component is pointer-events:none; re-enable on the toggle */
}
[data-nav-layout="vertical"][data-nav-align="right"] .nav_collapse_btn { right: auto; left: var(--_spacing---space--3); }
[data-nav-layout="vertical"] .nav_collapse_btn svg { width: 1rem; height: 1rem; transition: transform .2s ease; }
[data-nav-layout="vertical"][data-nav-align="right"] .nav_collapse_btn svg,
[data-nav-layout="vertical"][data-nav-collapsed="true"] .nav_collapse_btn svg { transform: scaleX(-1); }
[data-nav-layout="vertical"][data-nav-align="right"][data-nav-collapsed="true"] .nav_collapse_btn svg { transform: none; }

/* collapsed rail: shrink + hide labels (toggle + dropdown chevrons stay) */
[data-nav-layout="vertical"][data-nav-collapsed="true"] { --nav--vertical-width: var(--nav--vertical-collapsed); }
[data-nav-layout="vertical"][data-nav-collapsed="true"] .nav_links_text,
[data-nav-layout="vertical"][data-nav-collapsed="true"] .nav_links_svg,
[data-nav-layout="vertical"][data-nav-collapsed="true"] .nav_actions_wrap,
[data-nav-layout="vertical"][data-nav-collapsed="true"] .nav_desktop_logo { display: none; }
/* NB: the starter nav links are text-only, so the collapsed rail has no per-link glyphs yet - it
   shrinks + hides labels but shows no icon rail. A USEFUL collapsed rail needs an icon slot added to
   each nav link (+ a logo MARK that fits the rail); that icon set is the follow-up to this mechanism. */

/* page content clears the fixed rail (expanded + collapsed, left + right) */
.nav_component[data-nav-layout="vertical"] ~ .page_main { padding-left: var(--nav--vertical-width); transition: padding .25s ease; }
.nav_component[data-nav-layout="vertical"][data-nav-align="right"] ~ .page_main { padding-left: 0; padding-right: var(--nav--vertical-width); }
.nav_component[data-nav-layout="vertical"][data-nav-collapsed="true"] ~ .page_main { padding-left: var(--nav--vertical-collapsed); }
.nav_component[data-nav-layout="vertical"][data-nav-align="right"][data-nav-collapsed="true"] ~ .page_main { padding-left: 0; padding-right: var(--nav--vertical-collapsed); }
.nav_component[data-nav-layout="vertical"] ~ .page_main { padding-left: var(--nav--vertical-width); }       /* clear the fixed sidebar */
.nav_component[data-nav-layout="vertical"][data-nav-align="right"] ~ .page_main { padding-left: 0; padding-right: var(--nav--vertical-width); }

/* ===== CUSTOM OVERRIDE [CO-19]: nav blur-on-scroll (H2; opt-in) =====
   The desktop nav bar (.nav_desktop_contain) is an opaque light-100 pill at the top of the page
   (the nav theme's --_theme---nav--background). This adds the one missing state: once the page
   scrolls, the bar goes TRANSLUCENT + backdrop-blurred so content reads through it - the floating
   "frosted pill" treatment. Opt-in per tenant via data-nav-blur="true" on .nav_component (mirrors
   CO-17's data-nav-layout attribute pattern), so navs that don't want it are byte-for-byte unchanged.
   scripts.js (wireNavScroll) owns ONLY the .is-scrolled class toggle; the look is entirely here.
   No-JS: the class never lands, so the bar stays opaque - a perfectly good baseline. RE-SYNC: pure
   addition; keep. Tracked CO-19 + the divergence map. ===== */
[data-nav-blur="true"] .nav_desktop_contain {
  transition: background-color .25s ease, -webkit-backdrop-filter .25s ease, backdrop-filter .25s ease;
}
[data-nav-blur="true"].nav_component.is-scrolled .nav_desktop_contain {
  background-color: color-mix(in srgb, var(--_theme---nav--background) 72%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
}

/* ===== CUSTOM OVERRIDE [CO-21]: layout columns must NOT impose an aspect-ratio =====
   Stock Webflow-Lumos sizes a column's image by the COLUMN, not the image: (a) .u-layout-column-2
   carries `aspect-ratio:16/9` + `height:100%`, and (b) when a column's only child is an image/video/
   overlay, Lumos forces that wrapper to `position:absolute; height:100%` so it FILLS the column's
   aspect box. So a v-visual-image-square-1-1 wrapper (its own aspect-ratio:1) renders 16/9 — the
   column wins. Kris's rule: the column never forces a ratio; its CONTENT defines its own. So clear the
   column aspect + height, and put the sole image wrapper back IN FLOW (position:relative; height:auto)
   so its own aspect-ratio drives the box (in-flow + a definite column width → the wrapper sizes itself,
   so the grid row no longer collapses to the text column). RE-SYNC: re-apply after a Lumos re-sync.
   Tracked CO-21. ===== */
.u-layout-column-1, .u-layout-column-2 { aspect-ratio: auto; height: auto; }
.u-layout-column-1:not(.v-layout-card):not(.v-layout-contain-4-8-reversed):not(:has(> :not(.u-image-wrapper,.u-video,.u-overlay))) :is(.u-image-wrapper,.u-video),
.u-layout-column-2:not(.v-layout-card):not(.v-layout-contain-4-8-reversed):not(:has(> :not(.u-image-wrapper,.u-video,.u-overlay))) :is(.u-image-wrapper,.u-video) {
  position: relative; height: auto;   /* in-flow → the wrapper's own aspect-ratio sizes it. EXCLUDES v-layout-card columns (:not(.v-layout-card), see CO-23) + v-layout-contain-4-8-reversed (the Meet Grant square image FILLS its taller text-column box — no grey gap below it, matching stock Lumos/the Webflow export) + keeps .u-overlay out of the target so it stays position:absolute and covers the media. */
}

/* ===== CUSTOM OVERRIDE [CO-23]: v-layout-card media FILLS the card (folded from the Kyoom CTA) =====
   In a v-layout-card the column IS the box: column-2 is position:absolute inset:0 over the card, and the
   media should COVER it (object-fit:cover, centred). CO-21 (above) re-flows a column's sole image to its
   OWN aspect-ratio — right for 2-col text+image rows, WRONG for a card (a 16:9 wrapper overflows a shorter
   card and TOP-CROPS — clock instead of the people). So CO-21 now EXCLUDES v-layout-card columns
   (:not(.v-layout-card)); the card's image/video/overlay fall back to STOCK Lumos
   (.u-layout-column-2:not(:has(...)) :is(...) { position:absolute; height:100% } + width:100% base = FILL).
   Result: media covers the card (object-position centres the crop) and the overlay genuinely covers it.
   The image's faded look (stock opacity .5) is a per-tenant BRAND choice — tune in styles.css, not here.
   No standalone rule needed: the fix is the :not(.v-layout-card) exclusion above. Tracked CO-23 + the
   divergence map. RE-SYNC: re-apply after a Lumos re-sync. ===== */

/* ===== CUSTOM OVERRIDE [CO-24]: grid row-spans collapse below the large breakpoint =====
   A grid's column count is responsive (data-large/medium/small-columns), but the masonry row-span
   utilities (u-row-span-N) are GLOBAL — so a 2-row span that reads as masonry on the wide (large)
   grid leaves big empty GAPS once the grid drops to 2 or 1 columns (the spanned card stays tall while
   its neighbour is short — the Kyoom stats grid on tablet). Below the large breakpoint, collapse
   row-spans so the cards flow as clean rows. Column-spans (full-width cards, e.g. a wide stat) are
   KEPT — a full-width card reads fine at any width. Tracked CO-24. RE-SYNC: re-apply. ===== */
@media screen and (max-width: 991px) {
  .u-grid [class*="u-row-span-"] { grid-row-end: auto; }
}

/* ===== CUSTOM OVERRIDE [CO-22]: slider click-and-drag affordance (opt-in) =====
   Swiper's grabCursor/followFinger replaced by a native pointer-drag in scripts.js wireSliders
   (mouse drags scrollLeft on .slider_element). This is purely the cursor + selection affordance;
   opt-in via data-slider-drag="true" on the .slider_offset (the same element that carries the
   data-slider-* counts). Trackpad/touch/wheel scrolling is unaffected (native). RE-SYNC: re-apply
   after a Lumos re-sync. Tracked CO-22. ===== */
.slider_offset[data-slider-drag="true"] .slider_element.swiper { cursor: grab; }
.slider_offset[data-slider-drag="true"] .slider_element.swiper.is-dragging { cursor: grabbing; user-select: none; }
.slider_offset[data-slider-drag="true"] .slider_element.swiper :is(img, picture, a) { -webkit-user-drag: none; user-drag: none; }

/* ===== CUSTOM OVERRIDE [CO-25]: mobile nav drawer layout =====
   The centred-pill nav reuses the desktop nav markup (v-menu-desktop on the links,
   dropdown lists + dropdown links) inside the mobile drawer, so on mobile the dropdown
   list inherits position:absolute; width:10rem — it floats over and OVERLAPS the links
   below it (the Blog/About/CTA overlap in the drawer) — and the links are max-content with
   small centred text. Restyle the DRAWER only (.nav_mobile_wrap, <=991px): full-width
   stacked rows, text LEFT + chevron RIGHT, larger link/dropdown text, and IN-FLOW dropdown
   lists (position:static, full width) so an open dropdown PUSHES the siblings below it down.
   The grid-rows 0fr->1fr collapse (lumos.css:8436 closed / this file's line ~206 open) makes
   the height in-flow. Scoped to the SAME container query the nav uses for its desktop/
   mobile swap (@container (min-width: 65em) shows desktop; below it the drawer is live) —
   .nav_component is container-type:inline-size, and .nav_mobile_wrap is inside it — so a
   plain @media would miss the ~992-1040px band where the drawer is active. RE-SYNC:
   re-apply after a Lumos re-sync. Should propagate to the starter. Tracked CO-25. ===== */
@container (max-width: 65em) {
  .nav_mobile_wrap .nav_links_component { gap: var(--_spacing---space--3); }
  .nav_mobile_wrap .nav_links_wrap { display: flex; flex-direction: column; align-items: stretch; gap: var(--_spacing---space--2); }
  .nav_mobile_wrap .nav_links_item { width: 100%; }
  .nav_mobile_wrap .nav_links_link {
    width: 100%; max-width: none; height: auto;
    display: flex; justify-content: space-between; align-items: center;
    padding: var(--_spacing---space--2) 0;
  }
  .nav_mobile_wrap .nav_links_text { font-size: 1.25rem; }
  .nav_mobile_wrap .nav_links_svg { width: 1rem; margin-left: auto; }
  /* dropdown list IN-FLOW + full width so it pushes siblings; drop the floating-card chrome */
  .nav_mobile_wrap .nav_dropdown_main_wrap { position: static; width: 100%; max-width: none; padding-top: 0; }
  .nav_mobile_wrap .nav_dropdown_main_scroll { background-color: transparent; max-height: none; border-radius: 0; }
  .nav_mobile_wrap .nav_dropdown_link { padding-left: 0; padding-right: 0; border-bottom: 0; }
  .nav_mobile_wrap .nav_dropdown_text { font-size: 1rem; white-space: normal; }
  .nav_mobile_wrap .nav_actions_wrap { width: 100%; }
}

/* ===== CUSTOM OVERRIDE [CO-55]: full-height MENU drawer nav (data-nav-menu="drawer") =====
   The drawer becomes the nav's ONE menu at EVERY width: the desktop links row is retired
   and the existing <details data-nav-drawer> shell (native PE - operable with no JS; Esc/
   outside/item-click close already wired in scripts.js wireNav; scroll-lock + backdrop +
   burger->X already keyed on [open]) opens a fixed full-height sheet on the right, with
   large-type link rows. Same shell, one new attribute - no new JS. Two new PARTS ride
   along: .nav_link_index (the small index kicker inside a drawer link, label-family chain)
   and .nav_drawer_foot (the pinned CTA foot; the scroll region turns flex-column so
   margin-top:auto seats it). Values via --_nav-drawer---* inheriting slots, neutral
   fallbacks - brand (width, rules, type scale, snap easing, hover invert) is seed-only.
   Composes with data-nav-layout/-flow/-blur. Dropdown lists take the CO-25 in-flow form at
   ALL widths here (the attribute-scoped copies below outrank CO-25's container-query set).
   Origin: yogdog one-pager (PRD-yogdog-onepager 4a-result mint #2). Tracked CO-55. ===== */
/* the drawer form at every width: retire the desktop row, keep the mobile wrap + its
   backdrop visible past the 65em container swap (the :is() arms outrank the hide rule) */
.nav_component[data-nav-menu="drawer"] .nav_desktop_wrap,
.nav_component[data-nav-menu="drawer"] .nav_dropdown_backdrop { display: none; }
.nav_component[data-nav-menu="drawer"] :is(.nav_mobile_wrap, .nav_desktop_wrap:not(.w-condition-invisible) ~ .nav_mobile_wrap) { display: block; }
.nav_component[data-nav-menu="drawer"] :is(.nav_menu_backdrop, .nav_desktop_wrap:not(.w-condition-invisible) ~ .nav_menu_backdrop) { display: block; }
/* the open menu = a fixed full-height right sheet (outranks the CO-25-era absolute form) */
.nav_component[data-nav-menu="drawer"]:has(details[data-nav-drawer][open]) .w-nav-menu {
  position: fixed;
  inset: 0 0 0 auto;
  z-index: 1;
  width: var(--_nav-drawer---width, min(32.5rem, 100vw));
  max-width: none;
  margin: 0;
  border-radius: var(--_nav-drawer---radius, 0);
  border-left: var(--_nav-drawer---border-width, var(--border-width--main)) solid var(--_nav-drawer---border-color, var(--_theme---border));
  background-color: var(--_nav-drawer---background, var(--_theme---nav--background));
  display: flex;
  flex-direction: column;
  animation: ydNavDrawerIn var(--_nav-drawer---duration, var(--nav--menu-open-duration, 400ms)) var(--_nav-drawer---ease, ease-in-out) both;
}
@keyframes ydNavDrawerIn { from { transform: translateX(102%); } to { transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .nav_component[data-nav-menu="drawer"]:has(details[data-nav-drawer][open]) .w-nav-menu { animation: none; }
}
/* the toggle (burger/X) stays clickable above the sheet - it is the close control */
.nav_component[data-nav-menu="drawer"] details[data-nav-drawer] > summary { position: relative; z-index: 2; }
/* ...and while OPEN it pins to the SHEET's top-right - the bar layout would
   otherwise seat it at the content column's edge, mid-sheet (parity r3) */
.nav_component[data-nav-menu="drawer"] details[data-nav-drawer][open] > summary {
  position: fixed;
  top: var(--_nav-drawer---close-inset-y, var(--_spacing---space--3));
  right: var(--_nav-drawer---close-inset-x, var(--_spacing---space--3));
  width: max-content;   /* the bar sizes the toggle as a 2rem burger box - a word toggle clips (r3) */
  height: auto;
  white-space: nowrap;
}
/* scroll region fills the sheet; flex column seats the foot at the bottom */
.nav_component[data-nav-menu="drawer"] .nav_mobile_menu_scroll {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  max-height: none;
  overflow: hidden auto;
  padding-bottom: var(--_nav-drawer---pad-bottom, var(--_spacing---space--4));
}
/* large-type stacked link rows, hairline-ruled - at EVERY width (CO-25 shape, unscoped) */
.nav_component[data-nav-menu="drawer"] .nav_mobile_wrap .nav_links_component { gap: 0; }
.nav_component[data-nav-menu="drawer"] .nav_mobile_wrap .nav_links_wrap { display: flex; flex-direction: column; align-items: stretch; gap: 0; }
.nav_component[data-nav-menu="drawer"] .nav_mobile_wrap .nav_links_item { width: 100%; }
.nav_component[data-nav-menu="drawer"] .nav_mobile_wrap .nav_links_link {
  width: 100%;
  max-width: none;
  height: auto;
  display: flex;
  justify-content: var(--_nav-drawer---link--justify, flex-start);
  align-items: baseline;
  gap: var(--_nav-drawer---link--gap, var(--_spacing---space--3));
  padding: var(--_nav-drawer---link--pad, var(--_spacing---space--3)) 0;
  padding-left: var(--_nav-drawer---link--indent, 0);
  border-bottom: var(--_nav-drawer---rule-width, var(--border-width--main)) solid var(--_nav-drawer---rule-color, var(--_theme---border));
  transition: padding-left var(--_nav-drawer---hover-transition, .08s ease), background-color var(--_nav-drawer---hover-transition, .08s ease), color var(--_nav-drawer---hover-transition, .08s ease);
}
.nav_component[data-nav-menu="drawer"] .nav_mobile_wrap .nav_links_link:hover {
  background-color: var(--_nav-drawer---link-hover--background, transparent);
  color: var(--_nav-drawer---link-hover--color, inherit);
  padding-left: var(--_nav-drawer---link-hover--indent, var(--_nav-drawer---link--indent, 0));
}
/* the index kicker inverts WITH the row (its default already tracks currentColor;
   this slot is for a tenant whose seeded rest colour must not survive the invert) */
.nav_component[data-nav-menu="drawer"] .nav_mobile_wrap .nav_links_link:hover .nav_link_index {
  color: var(--_nav-drawer---link-hover--index-color, var(--_nav-drawer---index--color, color-mix(in srgb, currentColor 55%, transparent)));
}
.nav_component[data-nav-menu="drawer"] .nav_mobile_wrap .nav_links_text {
  font-size: var(--_nav-drawer---link--size, clamp(1.75rem, 4vh, 2.75rem));
  line-height: var(--_nav-drawer---link--leading, 1.05);
  font-weight: var(--_nav-drawer---link--weight, var(--_typography---font--primary-bold, 700));
  /* unseeded fallbacks reproduce today's computed exactly: family = the chrome label
     chain (mirrors lumos.css:8867 - a bare inherit would BYPASS that element rule),
     transform/tracking = inherit (nothing element-level today, both properties inherit) */
  font-family: var(--_nav-drawer---link--family, var(--_typography---font--label-family, var(--_text-style---font-family, var(--_typography---font--primary-family))));
  text-transform: var(--_nav-drawer---link--transform, inherit);
  letter-spacing: var(--_nav-drawer---link--tracking, inherit);
}
/* dropdown lists in-flow at all widths (the CO-25 set, attribute-scoped so it wins) */
.nav_component[data-nav-menu="drawer"] .nav_mobile_wrap .nav_dropdown_main_wrap { position: static; width: 100%; max-width: none; padding-top: 0; }
.nav_component[data-nav-menu="drawer"] .nav_mobile_wrap .nav_dropdown_main_scroll { background-color: transparent; max-height: none; border-radius: 0; }
.nav_component[data-nav-menu="drawer"] .nav_mobile_wrap .nav_dropdown_link { padding-left: 0; padding-right: 0; border-bottom: 0; }
.nav_component[data-nav-menu="drawer"] .nav_mobile_wrap .nav_dropdown_text { white-space: normal; }
.nav_component[data-nav-menu="drawer"] .nav_mobile_wrap .nav_actions_wrap { width: 100%; }
/* PART: index kicker inside a drawer link (01/02/... - the label-family chain) */
.nav_link_index {
  font-family: var(--_nav-drawer---index--family, var(--_typography---font--label-family, var(--_typography---font--primary-family)));
  font-size: var(--_nav-drawer---index--size, 0.6875rem);
  font-weight: var(--_nav-drawer---index--weight, var(--_typography---font--primary-bold, 700));
  letter-spacing: var(--_nav-drawer---index--tracking, .1em);
  color: var(--_nav-drawer---index--color, color-mix(in srgb, currentColor 55%, transparent));
}
/* PART: pinned drawer foot (CTA row) - last child of the scroll region */
.nav_drawer_foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--_nav-drawer---foot--gap, var(--_spacing---space--3));
  padding-top: var(--_nav-drawer---foot--pad, var(--_spacing---space--4));
  border-top: var(--_nav-drawer---rule-width, var(--border-width--main)) solid var(--_nav-drawer---rule-color, var(--_theme---border));
}
.nav_drawer_foot > * { margin-block: 0; }  /* voice margins skew the centring (parity r3) */

/* ===== CUSTOM OVERRIDE [CO-56]: ruled grid (data-grid-ruled on the .u-grid) =====
   The shared-edge ruled frame: the grid's gap collapses to a rule width, the grid element
   paints the rule colour as its own background, and every cell repaints the theme ground -
   so neighbouring cells SHARE one rule (no doubled borders), and the rules re-flow with
   every data-*-columns breakpoint for free. A matching outer border closes the frame.
   Cell background defaults to --_theme---background - the body's own ground token - so
   cells match the page in every theme context (a u-theme-dark band re-resolves it dark).
   Values via --_grid-ruled---* inheriting slots, neutral fallbacks. The cell rule sets NO
   padding - a (0,2,0) cell-pad slot proved non-inert against u-padding-* utilities on the
   cells (measured Y3'a): composition owns padding, the mint owns rules and grounds only.
   Origin: yogdog one-pager (PRD-yogdog-onepager 4a-result mint #7 - services lanes,
   process cells, platform feature grid). Tracked CO-56. ===== */
.u-grid[data-grid-ruled] {
  gap: var(--_grid-ruled---rule-width, var(--border-width--main));
  background-color: var(--_grid-ruled---rule-color, var(--_theme---border));
  border: var(--_grid-ruled---frame-width, var(--_grid-ruled---rule-width, var(--border-width--main))) solid var(--_grid-ruled---frame-color, var(--_grid-ruled---rule-color, var(--_theme---border)));
}
.u-grid[data-grid-ruled] > * {
  background-color: var(--_grid-ruled---cell-background, var(--_theme---background));
  min-width: 0;
}

/* ----- v-form-label-float: float-label form field (registered custom variant,
   custom-variants.json - PRD-yogdog-onepager 4a-result mint #11). Pure CSS: the
   .form_label_text seats INSIDE the field at rest and raises above it on :focus-within
   or a filled control (:has + :not(:placeholder-shown) - the input/textarea must carry
   placeholder=" " for the filled test to exist; a field with no placeholder, or a
   <select>, renders label-above ALWAYS, which is the safe default). No JS, no markup
   change beyond the class. Values via --_form-label---float--* inheriting slots. ----- */
.form_label_wrap:where(.v-form-label-float) { position: relative; }
.form_label_wrap:where(.v-form-label-float) .form_label_text {
  position: absolute;
  z-index: 1;
  top: var(--_form-label---float--inset-top, 0.875rem);
  left: var(--_form-label---float--inset-left, 0.9375rem);
  margin-bottom: 0;
  pointer-events: none;
  color: var(--_form-label---float--color, inherit);
  transition: top var(--_form-label---float--transition, .12s ease), left var(--_form-label---float--transition, .12s ease), color var(--_form-label---float--transition, .12s ease);
}
.form_label_wrap:where(.v-form-label-float):focus-within .form_label_text,
.form_label_wrap:where(.v-form-label-float):has(.form_field:not(:placeholder-shown)) .form_label_text {
  top: var(--_form-label---float--raised-top, -1.6em);
  left: var(--_form-label---float--raised-left, 0);
  color: var(--_form-label---float--raised-color, var(--_form-label---float--color, inherit));
}
/* WRAPPER FORM (the u-text-dropcap precedent): the class on any ANCESTOR floats
   every label inside it - the C20 SSR form renderer emits .form_label_wrap with
   no per-field class hook, so a page opts a whole rendered form in by wrapping
   its <div data-yd-form> placeholder (the def supplies placeholder=" " per
   field). Same declarations as the element form, ancestor-scoped. */
:where(.v-form-label-float) .form_label_wrap { position: relative; }
:where(.v-form-label-float) .form_label_wrap .form_label_text {
  position: absolute;
  z-index: 1;
  top: var(--_form-label---float--inset-top, 0.875rem);
  left: var(--_form-label---float--inset-left, 0.9375rem);
  margin-bottom: 0;
  pointer-events: none;
  color: var(--_form-label---float--color, inherit);
  transition: top var(--_form-label---float--transition, .12s ease), left var(--_form-label---float--transition, .12s ease), color var(--_form-label---float--transition, .12s ease);
}
:where(.v-form-label-float) .form_label_wrap:focus-within .form_label_text,
:where(.v-form-label-float) .form_label_wrap:has(.form_field:not(:placeholder-shown)) .form_label_text {
  top: var(--_form-label---float--raised-top, -1.6em);
  left: var(--_form-label---float--raised-left, 0);
  color: var(--_form-label---float--raised-color, var(--_form-label---float--color, inherit));
}
