/* Theme base styles — the one stylesheet (SYSTEM.md §13). Order matters:
 * tokens, then the document, then the band and the layouts a module stands
 * on, then the components. Partials are concatenated by HubL at render. */

/* Generic */
*, *:before, *:after {
  box-sizing: border-box;
}
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */

/* Document
   ========================================================================== */

/**
 * 1. Correct the line height in all browsers.
 * 2. Prevent adjustments of font size after orientation changes in iOS.
 */

html {
  line-height: 1.15; /* 1 */
  -webkit-text-size-adjust: 100%; /* 2 */
}

/* Sections
   ========================================================================== */

/**
 * Remove the margin in all browsers.
 */

body {
  margin: 0;
}

/**
 * Correct the font size and margin on `h1` elements within `section` and
 * `article` contexts in Chrome, Firefox, and Safari.
 */

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

/* Grouping content
   ========================================================================== */

/**
 * Add the correct box sizing in Firefox.
 */

hr {
  box-sizing: content-box;
  height: 0;
}

/**
 * 1. Correct the inheritance and scaling of font size in all browsers.
 * 2. Correct the odd `em` font sizing in all browsers.
 */

pre {
  font-family: monospace, monospace; /* 1 */
  font-size: 1em; /* 2 */
}

/* Text-level semantics
   ========================================================================== */

/**
 * 1. Remove the bottom border in Chrome 57-
 * 2. Add the correct text decoration in Chrome, Edge, Opera, and Safari.
 */

abbr[title] {
  border-bottom: none; /* 1 */
  text-decoration: underline; /* 2 */
  text-decoration: underline dotted; /* 2 */
}

/**
 * Add the correct font weight in Chrome, Edge, and Safari.
 */

b,
strong {
  font-weight: bolder;
}

/**
 * 1. Correct the inheritance and scaling of font size in all browsers.
 * 2. Correct the odd `em` font sizing in all browsers.
 */

code,
kbd,
samp {
  font-family: monospace, monospace; /* 1 */
  font-size: 1em; /* 2 */
}

/**
 * Add the correct font size in all browsers.
 */

small {
  font-size: 80%;
}

/**
 * Prevent `sub` and `sup` elements from affecting the line height in
 * all browsers.
 */

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

/* Forms
   ========================================================================== */

/**
 * 1. Change the font styles in all browsers.
 * 2. Remove the margin in Firefox and Safari.
 */

button,
input,
optgroup,
select,
textarea {
  font-family: inherit; /* 1 */
  font-size: 100%; /* 1 */
  line-height: 1.15; /* 1 */
  margin: 0; /* 2 */
}

/**
 * Remove the inheritance of text transform in Edge and Firefox.
 * 1. Remove the inheritance of text transform in Firefox.
 */

button,
select { /* 1 */
  text-transform: none;
}

/**
 * Correct the inability to style clickable types in iOS and Safari.
 */

button,
[type="button"],
[type="reset"],
[type="submit"] {
  -webkit-appearance: button;
}

/**
 * Remove the inner border and padding in Firefox.
 */

button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
  border-style: none;
  padding: 0;
}

/**
 * Restore the focus styles unset by the previous rule.
 */

button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
  outline: 1px dotted ButtonText;
}

/**
 * Correct the padding in Firefox.
 */

fieldset {
  padding: 0.35em 0.75em 0.625em;
}

/**
 * Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers.
 */

legend {
  padding: 0;
}

/**
 * Add the correct vertical alignment in Chrome, Firefox, and Opera.
 */

progress {
  vertical-align: baseline;
}

/**
 * Correct the cursor style of increment and decrement buttons in Chrome.
 */

[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
  height: auto;
}

/**
 * 1. Correct the odd appearance in Chrome and Safari.
 * 2. Correct the outline style in Safari.
 */

[type="search"] {
  -webkit-appearance: textfield; /* 1 */
  outline-offset: -2px; /* 2 */
}

/**
 * Remove the inner padding in Chrome and Safari on macOS.
 */

[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

/**
 * 1. Correct the inability to style clickable types in iOS and Safari.
 * 2. Change font properties to `inherit` in Safari.
 */

::-webkit-file-upload-button {
  -webkit-appearance: button; /* 1 */
  font: inherit; /* 2 */
}

/* Interactive
   ========================================================================== */

/*
 * Add the correct display in Edge and Firefox.
 */

details {
  display: block;
}

/*
 * Add the correct display in all browsers.
 */

summary {
  display: list-item;
}

/* Design tokens: the app's first (verbatim from Rotario/TMM), then the
 * site-only layer. See css/tokens/ and SYSTEM.md §13. */
/* Hanken Grotesk (variable, OFL): the one typeface, self-hosted from
 * fonts/ (same subsets and unicode ranges as the app). font-display: swap;
 * base.html preloads the upright latin file. */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('https://144736776.fs1.hubspotusercontent-eu1.net/hubfs/144736776/raw_assets/public/TMM_Quiet/fonts/HankenGrotesk-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('https://144736776.fs1.hubspotusercontent-eu1.net/hubfs/144736776/raw_assets/public/TMM_Quiet/fonts/HankenGrotesk-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url('https://144736776.fs1.hubspotusercontent-eu1.net/hubfs/144736776/raw_assets/public/TMM_Quiet/fonts/HankenGrotesk-Italic-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url('https://144736776.fs1.hubspotusercontent-eu1.net/hubfs/144736776/raw_assets/public/TMM_Quiet/fonts/HankenGrotesk-Italic-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* The fallback while the woff2 loads: Arial (Liberation Sans on Linux)
 * scaled to Hanken Grotesk's advance widths and line metrics, so a line
 * wraps where Hanken will wrap it and the swap moves nothing (CLS). Measured
 * 2026-10-14 on a 200-character sample: Hanken / Arial width 1.003 at
 * 400 and 0.948 at 700; Hanken ascent 1.00, descent 0.30. Listed second in
 * --font-sans (tokens/_site.css). */
@font-face {
  font-family: 'Hanken Grotesk Fallback';
  font-style: normal;
  font-weight: 100 500;
  src: local('Arial'), local('ArialMT'), local('Liberation Sans'), local('LiberationSans');
  size-adjust: 100.3%;
  ascent-override: 99.7%;
  descent-override: 29.9%;
  line-gap-override: 0%;
}
@font-face {
  font-family: 'Hanken Grotesk Fallback';
  font-style: normal;
  font-weight: 600 900;
  src: local('Arial Bold'), local('Arial-BoldMT'), local('Liberation Sans Bold'), local('LiberationSans-Bold');
  size-adjust: 94.8%;
  ascent-override: 105.5%;
  descent-override: 31.6%;
  line-gap-override: 0%;
}
/* tokens/_app.css: the TrackMyMachines app's design-token NAMES (Rotario/TMM,
 * app/assets/stylesheets/application.tailwind.css :root), kept so anything
 * written in the app's vocabulary still resolves. Since 2026-10-13 every app
 * colour is an ALIAS onto the site's colour scale (css/tokens/_site.css): no
 * hex here, and an app name that is also a scale name (canvas, surface, rule,
 * rule-strong, danger, success, warning, warning-wash, star, dark-accent,
 * dark-danger) is declared once, in the scale. Site CSS never uses these
 * aliases (the color-scale lint); it writes the band variables or the scale.
 * Non-colour tokens (shadows, radii, motion, type) are the app's values, with
 * color-mix() shadows pre-resolved to rgba. Radii from the app's tailwind.config.js.
 * This file is HubL-parsed: never put template syntax in it, even in comments. */
:root {
  /* Surfaces */
  --color-surface-variant: var(--color-paper);
  --color-settings-ground: var(--color-paper);
  --color-surface-tint: var(--color-rule);
  --color-hover: var(--color-paper);
  --color-hover-strong: var(--color-canvas);
  --color-tint: var(--color-rule-strong);
  --color-tint-hover: var(--color-rule-strong);
  --color-field: var(--color-surface);
  --color-row-hover: var(--color-accent-wash);

  /* Text ladder */
  --color-text-primary: var(--color-ink);
  --color-text-secondary: var(--color-ink-2);
  --color-text-muted: var(--color-ink-muted);
  --color-text-meta: var(--color-ink-muted);
  --color-text-faint: var(--color-ink-faint);
  --color-text-inverse: var(--color-dark-ink);

  /* Nav (dark chrome) */
  --color-nav-ground: var(--color-charcoal);
  --color-nav-hover: var(--color-charcoal);
  --color-nav-rule: var(--color-dark-ink-faint);
  --color-nav-ink: var(--color-dark-ink);
  --color-nav-muted: var(--color-dark-ink-2);

  /* Brand */
  --color-brand: var(--color-accent);
  --color-brand-hover: var(--color-accent-strong);
  --color-brand-deep: var(--color-accent-strong);
  --color-brand-tint: var(--color-accent-wash);
  --color-brand-tint-strong: var(--color-accent-wash);
  --color-info-bg: var(--color-accent-wash);
  --color-info-bg-strong: var(--color-accent-wash);
  --color-approved-bg: var(--color-accent-wash);
  --color-approved: var(--color-accent-strong);

  /* Rules */
  --color-rule-inner: var(--color-rule);

  /* Status */
  --color-danger-hover: var(--color-danger);
  --color-danger-bg: var(--color-warning-wash);
  --color-danger-bg-strong: var(--color-warning-wash);
  --color-danger-strong: var(--color-danger);
  --color-success-bg: var(--color-success-wash);
  --color-success-strong: var(--color-signal);
  --color-success-soft: var(--color-success-wash);
  --color-warning-bg: var(--color-warning-wash);
  --color-warning-bg-strong: var(--color-warning-wash);

  /* Dark family */
  --color-dark-canvas: var(--color-black);
  --color-dark-surface: var(--color-dark);
  --color-dark-surface-raised: var(--color-charcoal);
  --color-dark-surface-elevated: var(--color-charcoal);
  --color-dark-surface-overlay: var(--color-dark-ink-faint);
  --color-dark-rule: var(--color-charcoal);
  --color-dark-rule-strong: var(--color-dark-ink-faint);
  --color-dark-rule-stronger: var(--color-dark-ink-faint);
  --color-dark-text-muted: var(--color-dark-ink-muted);
  --color-dark-text-secondary: var(--color-dark-ink-2);
  --color-dark-text-bright: var(--color-dark-ink);
  --color-dark-accent-strong: var(--color-dark-accent);
  --color-dark-surface-recessed: var(--color-black);
  --color-dark-text-primary: var(--color-dark-ink-body);
  --color-dark-text-faint: var(--color-dark-ink-faint);
  --color-dark-accent-fill: var(--color-accent);
  --color-dark-accent-fill-hover: var(--color-accent-strong);
  --color-dark-on-accent: var(--color-surface);

  /* Card, elevation, motion */
  --pop-duration: 150ms;
  --pop-ease: ease-out;
  --card-ring: var(--color-rule-strong);
  --shadow-card: 0 1px 2px rgba(35,33,30,.05), 0 8px 24px -8px rgba(35,33,30,.09);
  --card-radius: 20px;
  --card-radius-sm: 16px;
  --shadow-pop: 0 0 0 1px rgba(35,33,30,.12), 0 1px 2px rgba(35,33,30,.06), 0 18px 44px -12px rgba(35,33,30,.22);
  --shadow-rail: 2px 0 8px rgba(35, 33, 30, .05);
  --shadow-hairline: inset 0 0 0 1px rgba(35,33,30,0.12);
  --shadow-hairline-strong: inset 0 0 0 1px rgba(35,33,30,0.18);
  --shadow-hairline-soft: inset 0 0 0 1px rgba(35,33,30,0.09);
  --shadow-lift: 0 1px 2px rgba(18,75,147,0.35), 0 3px 8px -2px rgba(18,75,147,0.25);
  --shadow-lift-hover: 0 1px 2px rgba(18,75,147,0.4);
  --shadow-focus: inset 0 0 0 1px var(--color-brand), 0 0 0 3px rgba(26,115,232,0.16);
  --shadow-focus-strong: inset 0 0 0 1px var(--color-brand), 0 0 0 3px rgba(26,115,232,0.35);
  --shadow-focus-soft: 0 0 0 3px rgba(26,115,232,0.16);
  --shadow-tooltip: 0 8px 15px rgba(35,33,30,0.08);
  --color-shadow-ink: var(--color-ink);
  --radius-md: 10px;
  --radius-lg: 12px;
  --radius-pop: 14px;
  --radius-xl: 16px;
  --radius-2xl: 20px;

  /* Type */
  --font-sans: 'Hanken Grotesk', ui-sans-serif, system-ui, sans-serif;
  --type-micro: 12px;
  --type-caption: 13px;
  --type-dense: 14px;
  --type-body: 15px;
  --type-callout: 16px;
  --type-heading: 18px;
  --type-title: 24px;
  --type-stat: 30px;
}
/* tokens/_site.css: marketing-site-only tokens. Everything the app has no
 * equivalent for: page rhythm, container, stack gaps. Radius, elevation and
 * motion come from _app.css and are never redefined here. The type sizes are
 * the one exception: the site's ramp (Rowan, 2026-10-13, type round variant 2)
 * restates the app's size tokens below, so _app.css stays verbatim.
 * HubL-parsed: no template syntax in this file, even in comments. */
:root {
  --container-max: 1280px;

  /* The app's stack with the metric-matched fallback second
   * (generic/_fonts.css), so the swap to Hanken Grotesk shifts no line. */
  --font-sans: 'Hanken Grotesk', 'Hanken Grotesk Fallback', ui-sans-serif, system-ui, sans-serif;

  /* Section vertical padding: one fluid scale, six steps. Drives .section /
   * .section--sm and the editor section-padding preset (render_section). */
  --section-pad-none: 0px;
  --section-pad-xs: clamp(20px, 0.94rem + 1.33vw, 32px);
  --section-pad-sm: clamp(40px, 1.88rem + 2.65vw, 64px);
  --section-pad-md: clamp(56px, 2.46rem + 4.42vw, 96px);
  --section-pad-lg: clamp(72px, 3.05rem + 6.19vw, 128px);
  --section-pad-xl: clamp(104px, 4.22rem + 9.72vw, 192px);

  /* Space below a .section__header; modules never add their own. */
  --section-header-gap: clamp(32px, 1.17rem + 3.54vw, 64px);

  /* Card inner padding: the app card's p-7 (CardComponent padding: :md),
   * fixed; a compact tile is 24 (the chart card's p-6). Grid gap, fluid. */
  --card-padding: 28px;
  --bento-gap: clamp(16px, 0.59rem + 1.77vw, 32px);

  /* Stack rhythm (SYSTEM.md §5): xs paired-tight, sm paired, md group,
   * lg section break, xl biggest break. */
  --text-gap-xs: clamp(8px,  0.4rem  + 0.44vw, 12px);
  --text-gap-sm: clamp(12px, 0.65rem + 0.44vw, 16px);
  --text-gap-md: clamp(20px, 1.15rem + 0.44vw, 24px);
  --text-gap-lg: clamp(28px, 1.44rem + 1.33vw, 40px);
  --text-gap-xl: clamp(44px, 2.23rem + 2.2vw,  64px);

  /* The site's type ramp (SYSTEM.md §4 › Typography; Rowan, 2026-10-13: the
   * type round's variant 2, "Stripe ramp, TMM weight"). Stripe's steps on
   * Hanken: hero 56, section 32, sub-title 26, card 22, lead 20, copy 16 to
   * 18, small 14, the 12 label. Phone first; the steps at 768 and 1024 are
   * the blocks after :root. The app's size tokens (_app.css) are restated
   * here so the whole site reads one ramp; _app.css stays verbatim. */
  --type-display: clamp(34px, 1.6rem + 2.4vw, 56px);
  --type-h1: clamp(34px, 1.6rem + 1.6vw, 48px);
  /* The step between h1 and h2 (Rowan, 2026-10-13: more heading sizes on the
   * same scale): 29 on a phone, 31 at 768, 40 at 1440. */
  --type-h1-5: clamp(29px, 1.3rem + 1.4vw, 40px);
  --type-h2: clamp(24px, 1rem + 1.2vw, 32px);
  --type-h3: 20px;
  --type-lead: 18px;
  /* The lead one step up, for Text size Large (.section--text-large .t-body-xl,
   * _typography.css): the ramp's next size above the lead at each width,
   * 20 on a phone and at 768, 22 from 1024 (the lead is 18 / 18 / 20). */
  --type-lead-lg: 20px;
  --type-title: 18px;
  --type-heading: 16px;
  --type-callout: 16px;
  --type-body: 16px;
  --type-dense: 16px;
  --type-caption: 14px;
  --type-micro: 12px;
  --type-stat: 28px;
  /* Buttons keep the app's control sizes (13 / 15 / 16), whatever the copy
   * ramp does: a button is a control, not copy. */
  --type-button-sm: 13px;
  --type-button-md: 15px;
  --type-button-lg: 16px;
  /* The figure role (.t-figure): one oversized number standing alone beside
   * its proof, the ROI page's +5 (Rowan, 2026-10-12). Fixed, above the display. */
  --type-figure: 120px;
  /* The large stat (render_stat size lg), fluid like the headings; a bead
   * centres it on its cell's rule by half this height (_layouts.css). */
  --type-stat-lg: clamp(36px, 1.6rem + 1.6vw, 52px);

  --radius-full: 9999px;
  /* The chart bar's top corners (the ROI page's cumulative chart), the
   * smallest step on the radius ladder. */
  --radius-xs: 4px;
  /* Every button's corner, all sizes (Rowan, 2026-10-14: the square corner,
   * buttons round v1). Icon buttons stay round. */
  --radius-btn: var(--radius-xs);

  /* Motion: the app's one popup curve, applied to hover states. Never
   * transition: all. Theme fades stay slow so section switches feel calm. */
  --transition-hover: background-color var(--pop-duration) var(--pop-ease),
                      color var(--pop-duration) var(--pop-ease),
                      border-color var(--pop-duration) var(--pop-ease),
                      box-shadow var(--pop-duration) var(--pop-ease);
  --transition-theme: background-color 0.5s ease, color 0.5s ease, border-color 0.5s ease;
  /* The opening curve as one timing, for anything that opens or swaps
   * (a tab panel, a drawer, a toast): transition: opacity var(--transition-pop). */
  --transition-pop: var(--pop-duration) var(--pop-ease);

  /* ── The colour scale (SYSTEM.md §3 › Colour; Rowan, 2026-10-13) ──────────
   * The only colour values in the theme. Roles, not shades: five grounds
   * and black, the ink ladder on light and on dark, two rules, the accent's
   * three steps and the dark accent, the status colours in use. Everything
   * else is derived in _band.css (the dark rules, the on-brand ladder, the
   * washes, the raised and sunken plates) or is a state below. The app's own
   * names (_app.css) are aliases onto these. A new colour token fails the
   * color-scale lint: merge into the nearest step instead.
   * Contrast, measured: ink 14.4, ink-2 7.0, ink-muted 4.8, accent-strong
   * 6.2 on the canvas; dark-ink 15.2, dark-ink-body 12.0, dark-ink-2 6.9,
   * dark-ink-muted 4.7, dark-accent 8.4 on the dark ground. --ink-faint
   * and --dark-ink-faint are never text; the accent is 4.03 on the canvas,
   * so small accent text is --accent-strong. */
  /* Grounds */
  --color-canvas: #F4F2EF;
  --color-paper: #F8F6F1;        /* the paper band (2026-10-12) and the light well */
  --color-surface: #FFFFFF;      /* the white band, cards, plates, ink on a brand fill */
  --color-dark: #1A1815;
  --color-charcoal: #23211E;     /* the chrome's ground (nav, footer) and the card on dark */
  --color-black: #000000;        /* the video letterbox; the dark wells mix it */
  /* Ink, light grounds */
  --color-ink: #23211E;
  --color-ink-2: #55524A;        /* secondary and body */
  --color-ink-muted: #6F6A60;
  --color-ink-faint: #C6C1B6;
  /* Ink, dark grounds */
  --color-dark-ink: #F1EDE6;
  --color-dark-ink-body: #D9D4CB; /* body copy on dark (Rowan, 2026-10-13: kept) */
  --color-dark-ink-2: #A8A196;
  --color-dark-ink-muted: #8A8378;
  --color-dark-ink-faint: #6B655B;
  /* Rules, light grounds (the band's --rule is derived in _band.css: the ink at
   * 18% on light, the dark ink at 36 / 12% on dark, white at 40 / 28% on brand) */
  --color-rule-strong: #DCD7CC;  /* the card edge */
  --color-rule: #EEECE7;         /* --rule-soft: rows inside a component */
  /* Accent */
  --color-accent: #1A73E8;
  --color-accent-strong: #1257B5; /* small accent text, the primary button's hover */
  --color-accent-wash: #E2EBFA;
  --color-dark-accent: #8AB4F8;
  /* Status */
  --color-danger: #96291E;
  --color-dark-danger: #F28B82;
  --color-success: #1B5A2F;
  --color-success-wash: #DFEEDF;
  --color-warning: #75490C;
  --color-warning-wash: #F4E8CE;
  --color-star: #E0A200;         /* the rating star, the amber status dot */
  --color-signal: #2E8B4F;       /* the green status dot (never text: 3.8 on the canvas) */

  /* ── States (SYSTEM.md §3 › Colour › States) ───────────────────────────────
   * Hover, press and select are the ground's own tone laid over whatever is
   * underneath, never a per-ground solid: the band's --ink on light and dark
   * grounds (darkens light, lightens dark), black on the brand ground
   * (--state-tone, _band.css). Transparent at rest: background-color:
   * var(--state-hover). On a fill: color-mix(in oklab, var(--state-tone)
   * var(--state-hover-amount), <the fill>), so the plate or stripe stays
   * underneath and the change still fades. A custom property resolves where
   * it is declared, so these are restated on every scope that sets --ink
   * (the block below). */
  --state-hover-amount: 6%;
  --state-press-amount: 10%;
  --state-select-amount: 14%;

  /* Card elevation (SYSTEM.md §7 › Cards). The card's edge is the light rule
   * (--card-ring, _band.css per ground); the hover retints it to the accent
   * at 55% (--card-ring-hover) and firms the float to a tighter, softer pair.
   * --shadow-pop-depth is --shadow-pop without its leading ring, for a
   * card raised to pop whose ring is already its border (two rings on one
   * edge strike their corners at different radii). */
  --shadow-card-hover: 0 1px 2px rgba(32, 28, 22, 0.05), 0 4px 14px rgba(32, 28, 22, 0.05);
  --shadow-pop-depth: 0 1px 2px rgba(35, 33, 30, 0.06), 0 18px 44px -12px rgba(35, 33, 30, 0.22);
}

/* The ramp's steps (variant 2): from 768 and from 1024. */
@media (min-width: 768px) {
  :root { --type-h3: 22px; --type-title: 20px; --type-callout: 17px; }
}
@media (min-width: 1024px) {
  :root { --type-h3: 26px; --type-title: 22px; --type-heading: 18px; --type-lead: 20px; --type-lead-lg: 22px; --type-callout: 18px; --type-stat: 32px; }
}

/* The states, on every scope that sets --ink or --state-tone (a ground
 * class can sit on a wrapper that is not a .section, the lookbook's
 * grounds and the stage among them). */
:root,
.section,
.section--light,
.section--white,
.section--paper,
.section--dark,
.section--charcoal,
.section--brand,
.card,
.tmm-form-wrap--dark,
.tmm-socials {
  --state-hover: color-mix(in oklab, var(--state-tone) var(--state-hover-amount), transparent);
  --state-press: color-mix(in oklab, var(--state-tone) var(--state-press-amount), transparent);
  --state-select: color-mix(in oklab, var(--state-tone) var(--state-select-amount), transparent);
}

/* Objects */
/* The page's content area clips sideways, so a module that bleeds to the screen
 * edge (feature-split's Full bleed image layout) can reach it without the
 * scrollbar's width turning into a horizontal scroll. The nav and the footer
 * sit outside main, so nothing fixed is affected (Rowan, 2026-10-05). */
main.body-container-wrapper { overflow-x: clip; }



/* CSS variables */

:root {
  --column-gap: 2.13%;
  --column-width-multiplier: 8.333;
}

/* Mobile layout */

.row-fluid {
  display: flex;
  flex-wrap: wrap;
  width: 100%;
}


  .row-fluid .span1,
  .row-fluid .span2,
  .row-fluid .span3,
  .row-fluid .span4,
  .row-fluid .span5,
  .row-fluid .span6,
  .row-fluid .span7,
  .row-fluid .span8,
  .row-fluid .span9,
  .row-fluid .span10,
  .row-fluid .span11,
  .row-fluid .span12{
  min-height: 1px;
  width: 100%;
}

/* Desktop layout */

@media (min-width: 768px) {
  .row-fluid {
    flex-wrap: nowrap;
    justify-content: space-between;
  }

  
    .row-fluid .span1 {
      width: calc(var(--column-width-multiplier) * 1% * 1 - var(--column-gap) * (11 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span2 {
      width: calc(var(--column-width-multiplier) * 1% * 2 - var(--column-gap) * (10 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span3 {
      width: calc(var(--column-width-multiplier) * 1% * 3 - var(--column-gap) * (9 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span4 {
      width: calc(var(--column-width-multiplier) * 1% * 4 - var(--column-gap) * (8 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span5 {
      width: calc(var(--column-width-multiplier) * 1% * 5 - var(--column-gap) * (7 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span6 {
      width: calc(var(--column-width-multiplier) * 1% * 6 - var(--column-gap) * (6 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span7 {
      width: calc(var(--column-width-multiplier) * 1% * 7 - var(--column-gap) * (5 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span8 {
      width: calc(var(--column-width-multiplier) * 1% * 8 - var(--column-gap) * (4 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span9 {
      width: calc(var(--column-width-multiplier) * 1% * 9 - var(--column-gap) * (3 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span10 {
      width: calc(var(--column-width-multiplier) * 1% * 10 - var(--column-gap) * (2 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span11 {
      width: calc(var(--column-width-multiplier) * 1% * 11 - var(--column-gap) * (1 * var(--column-width-multiplier) / 100));
    }
  
}
/* DnD wrappers are zero-padding passthroughs — modules handle their own container/spacing */

.dnd-section {
  padding-top: 0 !important;
  padding-bottom: 0 !important;
  padding-left: 0 !important;
  padding-right: 0 !important;
  max-width: 100% !important;
}

.dnd-section > .row-fluid {
  margin: 0 !important;
  max-width: 100% !important;
  width: 100% !important;
}

.dnd-section .dnd-column {
  padding: 0 !important;
}

/* The band: theme variables, rules, slab, container, section header */
/* The band — every section module's ground (SYSTEM.md §14).
 *
 * Output of templates/macros/section.html -> render_section / render_band:
 *
 *   [div.section__card-bg--wide|normal]        the slab's air (the conveyor alone, card != none)
 *     section.section.section--{mode}[.section--rules-hug][.section--rule-top[.section--hug-top]]
 *            [.section--rule-bottom[.section--hug-bottom]][.section--card]
 *       div.container
 *         div.section__band                     the content row; a row line (hug) sits on it
 *
 * This file owns:
 *   1. the theme variables (one set per ground; modules use them and never
 *      the raw dark tokens);
 *   2. the band rules (frame: on the section's edges; hug: on the content
 *      row; per edge in the spacing model: outside space, line, inside
 *      space), 1px pseudo-elements in the ground's one rule colour (--rule),
 *      never a border;
 *   3. the slab (.section--card) and its air: the conveyor's alone;
 *   4. the container and the measures;
 *   5. the section header anatomy (output of render_section_header).
 *
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

/* ── 1. Theme variables ──────────────────────────────────────────────────
 * The light set is the document default so chrome, the lookbook and anything
 * outside a band resolve to the light ladder. Each ground restates the set,
 * from the colour scale (css/tokens/_site.css) only; what the scale does not
 * hold is derived here with color-mix (the dark rules, the on-brand ladder,
 * the washes, the raised and sunken plates on dark), so a ground is a recipe
 * on the scale, not more colours.
 * --accent-text is the accent as small text (under 24px: the index
 * numerals, an accent title, a chip; the eyebrow's label is --ink and its
 * slash, a mark, --accent, 2026-10-12): the strong blue on the light
 * grounds, where the brand blue measures 4.03 : 1 on the canvas; the accent
 * itself on the dark and brand grounds. --accent stays for glyphs, fills,
 * rules and large figures. --link-hover is a resting colour too (the small
 * link on light, 6.15 : 1): it is the accent text, never a hover wash.
 * --state-tone is what the states (_site.css) mix: the ink, black on brand.
 * Hovers are the states; the only per-ground hover solids left are the
 * primary fill's (--accent-fill-hover, the app's own darker blue: an
 * overlay of its white label would lighten it) and the card ring's retint. */
:root {
  --ink: var(--color-ink);
  --ink-body: var(--color-ink-2);
  --ink-2: var(--color-ink-2);
  --ink-muted: var(--color-ink-muted);
  --ink-faint: var(--color-ink-faint);
  --ground: var(--color-canvas);
  --ground-raised: var(--color-surface);
  --ground-sunken: var(--color-paper);
  --rule: color-mix(in oklab, var(--color-ink) 18%, transparent);
  --rule-soft: var(--color-rule);
  --accent: var(--color-accent);
  --accent-wash: var(--color-accent-wash);
  --accent-ink: var(--color-accent-strong);
  --accent-fill: var(--color-accent);
  --accent-fill-hover: var(--color-accent-strong);
  --on-accent: var(--color-surface);
  --link: var(--color-accent);
  --link-hover: var(--color-accent-strong);
  --accent-text: var(--color-accent-strong);
  --ring: var(--shadow-hairline);
  --card-ground: var(--color-surface);
  --card-ring: var(--color-rule-strong);
  --card-ring-hover: color-mix(in oklab, var(--color-accent) 55%, transparent);
  --card-shadow: var(--shadow-card);
  --card-shadow-hover: var(--shadow-card-hover);
  --card-shadow-pop: var(--shadow-pop-depth);
  --field-ground: var(--color-surface);
  --danger: var(--color-danger);
  --state-tone: var(--ink);
  /* Where a sticky element pins: under the fixed bar on site pages. 0 where
   * there is no bar (the lookbook frame, lookbook.css) or where it scrolls
   * away (the blog templates, _header.css). Every sticky in the theme reads
   * it, never --nav-height directly (OPERATIONS.md §7 › Content offset). */
  --sticky-top: var(--nav-height);
}

.section--light,
.section--white,
.section--paper {
  --ink: var(--color-ink);
  --ink-body: var(--color-ink-2);
  --ink-2: var(--color-ink-2);
  --ink-muted: var(--color-ink-muted);
  --ink-faint: var(--color-ink-faint);
  --ground-raised: var(--color-surface);
  --ground-sunken: var(--color-paper);
  --rule: color-mix(in oklab, var(--color-ink) 18%, transparent);
  --rule-soft: var(--color-rule);
  --accent: var(--color-accent);
  --accent-wash: var(--color-accent-wash);
  --accent-ink: var(--color-accent-strong);
  --accent-fill: var(--color-accent);
  --accent-fill-hover: var(--color-accent-strong);
  --on-accent: var(--color-surface);
  --link: var(--color-accent);
  --link-hover: var(--color-accent-strong);
  --accent-text: var(--color-accent-strong);
  --ring: var(--shadow-hairline);
  --card-ground: var(--color-surface);
  --card-ring: var(--color-rule-strong);
  --card-ring-hover: color-mix(in oklab, var(--color-accent) 55%, transparent);
  --card-shadow: var(--shadow-card);
  --card-shadow-hover: var(--shadow-card-hover);
  --card-shadow-pop: var(--shadow-pop-depth);
  --field-ground: var(--color-surface);
  --danger: var(--color-danger);
  --state-tone: var(--ink);
}
.section--light { --ground: var(--color-canvas); }
.section--white { --ground: var(--color-surface); }
/* Paper (Rowan, 2026-10-12): the fifth ground, a little lighter than the
 * canvas. Its well steps down to the canvas and a plate on it is the white
 * surface, as on the canvas. */
.section--paper {
  --ground: var(--color-paper);
  --ground-sunken: var(--color-canvas);
  --card-ground: var(--color-surface);
}

/* The dark grounds. The rules are the dark ink laid over the ground (36%
 * the band's rule, 12% the soft rule, the card edge and the hairline), so
 * they read the same on dark and on charcoal; a raised plate is the ground
 * lifted by the ink at 6% (#24221F on dark, #2D2B28 on charcoal) and the
 * well is the ground with 20% black.
 * .section--brand.section--dark: a brand band turned into the stage
 * (stage_band_class) outranks the brand set below. */
.section--dark,
.section--charcoal,
.section--brand.section--dark,
.card--dark,
.card--island-dark,
.card--stage,
.tmm-form-wrap--dark {
  --ink: var(--color-dark-ink);
  --ink-body: var(--color-dark-ink-body);
  --ink-2: var(--color-dark-ink-2);
  --ink-muted: var(--color-dark-ink-muted);
  --ink-faint: var(--color-dark-ink-faint);
  --ground: var(--color-dark);
  --ground-raised: color-mix(in oklab, var(--color-dark-ink) 6%, var(--ground));
  --ground-sunken: color-mix(in oklab, var(--color-black) 20%, var(--ground));
  --rule: color-mix(in oklab, var(--color-dark-ink) 36%, transparent);
  --rule-soft: color-mix(in oklab, var(--color-dark-ink) 12%, transparent);
  --accent: var(--color-dark-accent);
  --accent-wash: color-mix(in oklab, var(--color-dark-accent) 16%, transparent);
  --accent-ink: var(--color-dark-accent);
  --accent-fill: var(--color-accent);
  --accent-fill-hover: var(--color-accent-strong);
  --on-accent: var(--color-surface); /* pure white on the accent fill: the dark ladder's off-white on the brand blue measured 3.9:1 (2026-10-09) */
  --link: var(--color-dark-accent);
  --link-hover: var(--color-dark-ink); /* ink-only hover: the link is already the light blue */
  --accent-text: var(--color-dark-accent);
  --ring: inset 0 0 0 1px var(--rule-soft);
  --card-ground: var(--ground-raised);
  --card-ring: var(--rule-soft);
  --card-ring-hover: var(--color-dark-accent);
  --card-shadow: none;
  --card-shadow-hover: none;
  --card-shadow-pop: var(--shadow-pop-depth);
  --field-ground: var(--ground-raised);
  --danger: var(--color-dark-danger);
  --state-tone: var(--ink);
}
/* The brand ground (.section--brand, color_mode: brand) and the brand card
 * (.card--brand): the whole ladder resolved on the brand blue. Ink in white
 * and its translucent steps (90 / 80 / 60%); the rules a white hairline at
 * two strengths (40 / 28%; the focus ring keeps 56%); the accent is the on-brand ink (there is no
 * second blue on blue, so the accent gesture, the eyebrow's slash and the
 * ledger numeral turn white); the accent fill is white with the brand as its
 * ink, so the primary button renders as the inverse (_buttons.css). A plate
 * on the band is the accent with 27% black (#0D4997, 1.9 : 1 on the band,
 * as the app's deep blue was). The states darken here: their tone is black
 * (Rowan, 2026-10-13). */
.section--brand,
.card--brand {
  --ink: var(--color-surface);
  --ink-body: var(--color-surface);
  --ink-2: color-mix(in oklab, var(--color-surface) 90%, transparent);
  --ink-muted: color-mix(in oklab, var(--color-surface) 80%, transparent);
  --ink-faint: color-mix(in oklab, var(--color-surface) 60%, transparent);
  --ground: var(--color-accent);
  --ground-raised: color-mix(in oklab, var(--color-black) 27%, var(--color-accent));
  --ground-sunken: var(--color-accent-strong);
  --rule: color-mix(in oklab, var(--color-surface) 40%, transparent);
  --rule-soft: color-mix(in oklab, var(--color-surface) 28%, transparent);
  --accent: var(--color-surface);
  --accent-wash: color-mix(in oklab, var(--color-surface) 14%, transparent);
  --accent-ink: var(--color-surface);
  --accent-fill: var(--color-surface);
  --accent-fill-hover: var(--color-accent-wash);
  --on-accent: var(--color-accent);
  --link: var(--color-surface);
  --link-hover: var(--color-accent-wash);
  --accent-text: var(--color-surface);
  --ring: inset 0 0 0 1px var(--rule);
  --card-ground: var(--ground-raised);
  --card-ring: var(--rule-soft);
  --card-ring-hover: var(--rule);
  --card-shadow: none;
  --card-shadow-hover: none;
  --card-shadow-pop: var(--shadow-pop-depth);
  --field-ground: var(--ground-raised);
  --danger: var(--color-surface);
  --state-tone: var(--color-black);
  /* The focus ring is the brand's own blue elsewhere: white here. */
  --shadow-focus-strong: inset 0 0 0 1px var(--color-surface), 0 0 0 3px color-mix(in oklab, var(--color-surface) 56%, transparent);
}

/* Charcoal is the chrome's ground (nav, footer): the ink ground. Its band
 * rule is the dark set's (36%), no longer the soft one: one rule colour per
 * ground that reads (Rowan, 2026-10-13). */
.section--charcoal {
  --ground: var(--color-charcoal);
}

/* ── 2. The band and its rules ──────────────────────────────────────────── */

.section {
  position: relative;
  padding-block: var(--section-pad-lg);
  background-color: var(--ground);
  color: var(--ink);
  transition: var(--transition-theme);
  /* clip, not hidden: it clips the same and is not a scroll container, so a
   * sticky inside a band (the comparison table's head) pins to the page.
   * flow-root keeps the block formatting context hidden gave (margins and
   * floats stay inside the band). */
  display: flow-root;
  overflow: clip;
}
.section--sm { padding-block: var(--section-pad-md); }

/* --section-bg: a module's own CSS may read the band's fill (a mat); the
 * override from the editor sets it inline. */
.section--light,
.section--white,
.section--paper,
.section--dark,
.section--charcoal,
.section--brand { --section-bg: var(--ground); }

/* A band that bleeds (feature-split's Full picture) must not clip; main
 * clips sideways instead (css/objects/_layout.css). */
.section--bleed { overflow: visible; }
.section--bleed > .container { max-width: none; padding-inline: 0; }
/* A bleeding band runs its rules to the screen with the picture (the band
 * rules below read these); the same distance as .tmm-bleed. Added
 * 2026-10-08 in the drift sweep, from feature-split's Full layout. */
.section--bleed { --band-rule-left: calc(50% - 50vw); --band-rule-right: calc(50% - 50vw); }

.section > .container { position: relative; z-index: 1; }

/* Band rules. Frame: the rule is the band's edge (the editor's spacing sits
 * inside it). Hug: the rule sits on the content row (the spacing sits
 * outside it, so the rule lands where the content starts and stops). Both
 * are the same 1px line in the ground's --rule; --band-rule-left / -right let
 * a bleeding module run the rule to the screen edge. Modules never style
 * .section::before / ::after themselves.
 * One rule colour per ground, no tone option (Rowan, 2026-10-13: "We've got
 * loads of rule colour options on modules; we just want them to be good
 * contrast with the right colour, not loads of options"): the Rule tone
 * (default / subtle / strong / brand) is retired and a saved value ignored. Each --rule above is tuned to about the same
 * weight on its ground (APCA Lc 20 to 24; WCAG 1.43 to 1.44 on the light
 * grounds, 3.0 on dark and charcoal, 1.9 on brand). */
.section--rule-top::before,
.section--rule-bottom::after,
.section--rule-top:is(.section--rules-hug, .section--hug-top) .section__band::before,
.section--rule-bottom:is(.section--rules-hug, .section--hug-bottom) .section__band::after {
  content: '';
  position: absolute;
  left: var(--band-rule-left, 0);
  right: var(--band-rule-right, 0);
  height: 1px;
  background: var(--rule);
  pointer-events: none;
}
.section--rule-top::before { top: 0; }
.section--rule-bottom::after { bottom: 0; }
/* In hug mode the section's own rules give way to the row's. */
.section--rule-top:is(.section--rules-hug, .section--hug-top)::before,
.section--rule-bottom:is(.section--rules-hug, .section--hug-bottom)::after { display: none; }
.section__band { position: relative; }
.section:is(.section--rules-hug, .section--hug-top) .section__band::before { top: 0; }
.section:is(.section--rules-hug, .section--hug-bottom) .section__band::after { bottom: 0; }

/* The spacing model (the sweep, 2026-10; Rowan, 2026-10-13: Frame / Hug retired, each edge is
 * a line and two spaces; SCHEMA.md 3.2). render_band draws a swept module's edge from the band
 * inward: the outside space (the section's padding), the line, the inside space, the content.
 * A line with no space outside sits on the band's edge (.section--rule-top / -bottom, above),
 * the band's full width, and the inside space is the section's padding. A line with space
 * outside sits on the content row, the content's width, per edge: .section--hug-top /
 * .section--hug-bottom move that edge's line onto .section__band, whose inline padding is the
 * inside space. The old .section--rules-hug is both edges at once with no inside space; an
 * unswept module still writes it. The line is the same 1px --rule either way. */

/* Inner lines (Rowan, 2026-10-15: "two sets of horizontal lines, inner and outer"; the editor's
 * Inner lines switch, section_settings.borders.inner_lines, SCHEMA.md 3.2). The outer lines are
 * the band's edge rules above. The inner lines are every horizontal hairline a layout draws
 * inside the band: the ruled block, a ledger's rows, the cells' row rules, the figure's rule,
 * the accordion's rows, a table's row rules, a module's own seam. Each of those reads its
 * colour as var(--inner-rule, var(--rule)) or var(--inner-rule, var(--rule-soft)): --inner-rule
 * is never declared, so the fallback draws the line as before, and this one rule blanks them
 * all by declaring it. Vertical rules (the index and middle rules, cells' column rules, ticks,
 * dividers) read var(--rule) directly and stay; a card resets the variable (_cards.css), so
 * its own hairlines stay with its edge; a ticked measure (.tmm-timeline, the pagination) is
 * one drawing and stays whole. The 1px the line took stays, so nothing moves. */
.section--inner-lines-none { --inner-rule: transparent; }
/* The edges' Line inside toggles (the panel standard, Rowan 2026-10-15; render_band through
 * inner_edge_off): a layout's own line across the top (bottom) of its content, inside the band's
 * edge line, drawn in module CSS, reads var(--inner-rule-top, var(--inner-rule, var(--rule)))
 * (--inner-rule-bottom for the foot). Never declared, so the line draws as before; Off declares
 * it transparent. A .tmm-ruled block takes --no-top / --no-bottom in its module.html instead. */
.section--in-top-off { --inner-rule-top: transparent; }
.section--in-bottom-off { --inner-rule-bottom: transparent; }

/* ── 3. The slab (.section--card) and its air ───────────────────────────
 * The conveyor's alone (Rowan, 2026-10-13: "we won't be using these rounded
 * corner outer cards anymore … the only one to get a card should be the
 * conveyor belt module"). render_band draws it only with allow_slab=true,
 * which conveyor.module alone passes; every other band is plain.
 * The whole band in a rounded card on the page (section_settings.card:
 * none | wide | normal, labelled "Slab"; SCHEMA.md 3.5): 32px corners, 64px
 * from 1024 up; wide sits 40 / 80 in from the viewport, normal in the 1280
 * column. The editor's spacing is the air around the card, on the wrapper;
 * the band's own rules are off.
 * The slab's surface follows its ground (2026-10-12): on light, white and
 * paper it is the raised plate (--ground-raised, the white surface) edged by
 * the card ring, the card edge's selector list in _cards.css (one border in
 * the theme); on dark and charcoal it is the dark ground itself and on brand
 * the blue, the edge taking the fill. A slab whose band is the stage
 * (stage_band_class: a light slab turned dark) reads as dark. */
:root { --slab-radius: 32px; } /* the slab's corners (the conveyor's) */
@media (min-width: 1024px) { :root { --slab-radius: 64px; } }
.section__card-bg { box-sizing: border-box; padding-inline: 0; }
/* The slab's air is the band's own ground, not the page body behind it
 * (2026-10-12: a light slab band showed the white body round its card). */
.section__card-bg--on-light { background: var(--color-canvas); }
.section__card-bg--on-white { background: var(--color-surface); }
.section__card-bg--on-paper { background: var(--color-paper); }
.section__card-bg--on-dark { background: var(--color-dark); }
.section__card-bg--on-charcoal { background: var(--color-charcoal); }
@media (min-width: 768px) {
  .section__card-bg--wide,
  .section__card-bg--normal { padding-inline: 40px; }
}
@media (min-width: 1024px) {
  .section__card-bg--wide { padding-inline: 80px; }
  .section__card-bg--normal { padding-inline: 0; max-width: var(--container-max); margin-inline: auto; }
}
.section.section--card {
  border-radius: var(--slab-radius);
  overflow: hidden;
  padding-block: var(--section-pad-md);
}
.section--card:is(.section--light, .section--white, .section--paper):not(.section--dark) {
  --ground: var(--ground-raised);
}
.section--card:is(.section--dark, .section--charcoal, .section--brand) { border-color: transparent; }
@media (min-width: 1024px) {
  .section.section--card { padding-block: var(--section-pad-lg); }
}
.section--card.section--rule-top::before,
.section--card.section--rule-bottom::after { display: none; }

/* ── 4. Container and measures ──────────────────────────────────────────
 * One measure: 1280 with 32 / 40 / 80 gutters, mobile first. Inner measures
 * narrow a band's content (a reading column, a centred form) and are the only
 * max-widths a module may use. */
:root { --gutter: 32px; }
@media (min-width: 768px) { :root { --gutter: 40px; } }
@media (min-width: 1024px) { :root { --gutter: 80px; } }
.container {
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.tmm-measure { margin-inline: auto; width: 100%; }
.tmm-measure--reading { max-width: 720px; }
.tmm-measure--narrow { max-width: 880px; }
.tmm-measure--mid { max-width: 1024px; }
/* --keep: a measure that sizes an object, not a text column (security's
 * row of square tiles: four across 880 is 202px a tile, released to the
 * container they grew to 262, Rowan 2026-10-13 "massive card boxes"). The
 * page layer's release (_page.css, 4) passes it by. */

/* ── 5. Section header anatomy (render_section_header) ──────────────────
 * Every gap is a token so the rhythm scales with the type and the padding.
 * --section-header-gap is the space under the whole block. */
.section__header {
  text-align: center;
  margin-bottom: var(--section-header-gap);
}
.section__header--left { text-align: left; }
.section__badge { margin-bottom: var(--text-gap-md); }
.section__subheader { margin: 0 0 var(--text-gap-sm); }
/* The two-class selectors (0,2,0) hold the centring against the type roles'
 * margin: 0, which load after this file. */
.section__header .section__heading { margin: 0 auto var(--text-gap-md); max-width: 800px; }
.section__header .section__description { margin: 0 auto; max-width: 640px; }
/* A richtext description is a div of paragraphs: they take the role's type
 * and the stack rhythm, not the document's paragraph defaults. The second
 * gap selector (0,1,1) outranks the margin reset above it, as in
 * _typography.css's .t-richtext pair. */
.section__description > p,
.section__description > ul,
.section__description > ol { font: inherit; color: inherit; margin: 0; }
.section__description > * + *,
.section__description > * + :is(p, ul, ol) { margin-top: var(--text-gap-sm); }
.section__header--left .section__heading,
.section__header--left .section__description { margin-inline: 0; }
.section__header--left .section__heading { margin-bottom: var(--text-gap-md); }

/* Header measures: a tighter cap for a heading and its description, in ch
 * so it follows the type (the hero, the CTA banner, the blog CTA). Three
 * steps, named after the inner measures and in the same order:
 *   --measure-reading 20ch / 36ch, --narrow 28ch / 42ch, --mid 36ch / 56ch. */
.section__header--measure-reading { --header-heading-max: 20ch; --header-desc-max: 36ch; }
.section__header--measure-narrow { --header-heading-max: 28ch; --header-desc-max: 42ch; }
.section__header--measure-mid { --header-heading-max: 36ch; --header-desc-max: 56ch; }
.section__header[class*="section__header--measure-"] .section__heading { max-width: var(--header-heading-max); }
.section__header[class*="section__header--measure-"] .section__description { max-width: var(--header-desc-max); }

/* Split header: eyebrow + heading left, copy + optional button right, bottom
 * aligned to the heading on desktop; one column, top aligned, under 1024. */
.section__header--split {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--text-gap-md);
  text-align: left;
  margin-bottom: var(--section-header-gap);
}
.section__header--split .section__heading,
.section__header--split .section__description { margin-inline: 0; max-width: none; }
.section__header--split .section__header-left > :last-child { margin-bottom: 0; }
.section__header--split .section__header-left { align-self: start; min-width: 0; }
.section__header--split .section__header-right {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  min-width: 0;
  gap: var(--text-gap-sm);
}
.section__header--split:has(.section__header-right > .section__header-cta:only-child) { gap: var(--text-gap-sm); }
.section__header-right--left .section__description { text-align: left; }
.section__header-right--left .section__header-cta { align-self: flex-start; }
@media (min-width: 1024px) {
  .section__header--split { grid-template-columns: 1fr 1fr; gap: clamp(32px, 1.65rem + 3.1vw, 64px); }
  .section__header--split .section__header-right { justify-content: flex-end; }
  .section__header-right--right .section__description { text-align: right; }
  .section__header-right--right .section__header-cta { align-self: flex-end; }
}

/* Elements */
/* Typography: the document defaults and the type roles (.t-*). Sizing,
 * weight and spacing live here; colour is the theme variables (SYSTEM.md
 * §3) so every role reads correctly on every ground without a restated
 * selector. Modules never redefine a role or its colour.
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

body {
  font-family: var(--font-sans);
  line-height: 1.4;
  overflow-wrap: break-word;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: var(--ink);
}

html[lang^="ja"] body,
html[lang^="zh"] body,
html[lang^="ko"] body {
  line-break: strict;
  overflow-wrap: normal;
  word-break: break-all;
}

/* Paragraphs */
p { font-size: 1rem; margin: 0 0 1.4rem; }

/* Anchors: the link ink per ground. Buttons own their own colour (the
 * .btn--* rules out-rank these at 0,1,0 vs 0,0,1), so no :not() is needed
 * at rest; the hover rule keeps the guard so a button anchor on a dark band
 * never repaints. */
a { cursor: pointer; color: var(--link); text-decoration: none; }
a:not(.btn):not(.cta_button):hover,
a:not(.btn):not(.cta_button):focus { color: var(--link-hover); }

/* Headings: bare defaults in the ink, modest sizes so a role class wins. */
h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6 {
  font-family: var(--font-sans);
  font-weight: 650;
  color: var(--ink);
}
h1, .h1 { font-size: 40px; line-height: 1.1; letter-spacing: -0.03em; }
h2, .h2 { font-size: 32px; line-height: 1.15; letter-spacing: -0.02em; }
h3, .h3 { font-size: 24px; line-height: 1.2; letter-spacing: -0.01em; }
h4, .h4 { font-size: 20px; line-height: 1.3; }
h5, .h5 { font-size: 18px; line-height: 1.3; }
h6, .h6 { font-size: 16px; line-height: 1.3; }
h1, h2, h3, h4, h5, h6 { margin: 0 0 1.4rem; }

/* Lists */
ul, ol { margin: 0 0 1.4rem; }
ul ul, ol ul, ul ol, ol ol { margin: 0; }
ul.no-list { list-style: none; margin: 0; padding-left: 0; }

/* Code */
pre { overflow: auto; }
code { vertical-align: bottom; }

/* Highlight: the accent wash behind the ground's ink, a tint not a fill
 * (the solid accent is the current page and the primary button only).
 * Selection keeps the brand fill with the on-brand ink. */
mark { background-color: var(--accent-wash); color: var(--ink); padding: 0.1em 0.3em; border-radius: 4px; }
::selection { background-color: var(--color-accent); color: var(--color-surface); }
::-moz-selection { background-color: var(--color-accent); color: var(--color-surface); }

/* Blockquote and hr defaults (the prose has its own) */
blockquote { border-left: 2px solid var(--rule); margin: 0 0 1.4rem; padding-left: 0.7rem; }
hr { border: none; height: 1px; background: var(--inner-rule, var(--rule)); }

/* Image alt text */
img { font-size: 0.583rem; word-break: normal; }

/* ── The type roles ──────────────────────────────────────────────────────
 * The sizes are the ramp in tokens/_site.css (Rowan, 2026-10-13: the type
 * round's variant 2, Stripe's steps with TMM's weight): three fluid sizes at
 * the top, whole pixels from .t-h3 down. Headings 640 (card titles 620) with
 * tracking that tightens as the size grows; leading as ratios, never a fixed
 * pixel, so a size step never strands a line height. */
.t-display,
.t-h1,
.t-h1-5,
.t-h2,
.t-h3,
.t-card-title {
  font-family: var(--font-sans);
  font-weight: 640;
  margin: 0;
  color: var(--ink);
}
.t-display { font-size: var(--type-display); line-height: 1.04; letter-spacing: -0.032em; }
.t-h1 { font-size: var(--type-h1); line-height: 1.06; letter-spacing: -0.028em; }
/* The 40 step between h1 and h2: leading and tracking halfway between them. */
.t-h1-5 { font-size: var(--type-h1-5); line-height: 1.08; letter-spacing: -0.026em; }
.t-h2 { font-size: var(--type-h2); line-height: 1.1; letter-spacing: -0.024em; }
.t-h3 { font-size: var(--type-h3); line-height: 1.15; letter-spacing: -0.016em; }
/* Card title: the one card-heading role, so the eye never recalibrates. */
.t-card-title { font-size: var(--type-title); font-weight: 620; line-height: 1.22; letter-spacing: -0.012em; }
/* Figure: one oversized number alone beside its proof (the ROI page's +5),
 * fixed at 120, tabular; the colour is the caller's (the band's accent). */
.t-figure { font-family: var(--font-sans); font-size: var(--type-figure); font-weight: 650; line-height: 0.9; letter-spacing: -0.045em; font-variant-numeric: tabular-nums; }


/* Micro labels stay fixed-size: 12px uppercase overlines do not scale. */
.t-subheader,
.t-label-sm {
  font-family: var(--font-sans);
  font-size: var(--type-micro);
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin: 0;
  color: var(--ink-2);
}
.t-label-sm { line-height: 1.45; }
/* A quieter span inside a heading or a line (a heading's continuation), and its
 * accent twin for a figure that is the point (the price pair's "£0 extra"). Both
 * sit after the size roles so a span that is both (.t-h1.t-muted) takes the ink.
 * Every .t-* role here is span-safe: an editor's rich text wraps a label line in
 * .t-label-sm, a figure in .t-h1 or .t-display, a source in .t-caption. */
.t-muted { color: var(--ink-2); }
.t-accent { color: var(--accent-text); }

/* A richtext field in a body role: its paragraphs and lists take the role's
 * type and the stack rhythm, not the document's defaults. The second
 * selector outranks the reset above (0,1,1), which had zeroed the gap
 * between paragraphs (narrative, 2026-10-13). */
.t-richtext > p,
.t-richtext > ul,
.t-richtext > ol,
.t-richtext li { font: inherit; color: inherit; margin: 0; }
.t-richtext > * + *,
.t-richtext > * + :is(p, ul, ol) { margin-top: var(--text-gap-md); }
.t-richtext > ul,
.t-richtext > ol { padding-left: 24px; }
.t-richtext strong { font-weight: 600; color: inherit; }
.t-subheader--accent { color: var(--accent-text); }
.t-subheader--muted { color: var(--ink-2); }

/* Body roles. .t-body-lg is the site's reading voice; .t-body-xl the lead. */
.t-body-lg { font-family: var(--font-sans); font-size: var(--type-callout); font-weight: 400; line-height: 1.5; margin: 0; color: var(--ink-body); }
.t-body-xl { font-family: var(--font-sans); font-size: var(--type-lead); font-weight: 400; line-height: 1.45; margin: 0; color: var(--ink-body); }
.t-body { font-family: var(--font-sans); font-size: var(--type-body); font-weight: 400; line-height: 1.5; margin: 0; color: var(--ink-body); }
.t-body-md { font-family: var(--font-sans); font-size: var(--type-dense); font-weight: 400; line-height: 1.5; margin: 0; color: var(--ink-body); }
/* Text size Large (Style > Text on every section module, Rowan 2026-10-15; render_band
 * text_size='large'): the band's body roles step up one, the lead to the large lead, the reading
 * voice to the lead and body and dense copy to the reading voice. Headings, labels and captions
 * keep their size; a heading drawn in the lead role (the cta's Thin bar) is a heading. */
.section--text-large .t-body-xl:not(.section__heading) { font-size: var(--type-lead-lg); }
.section--text-large .t-body-lg { font-size: var(--type-lead); line-height: 1.45; }
.section--text-large .t-body,
.section--text-large .t-body-md { font-size: var(--type-callout); }
/* An editor richtext reads at the copy leading of the role it sits in. */
.t-richtext { line-height: 1.5; }
/* The caption, 14 at 1.45 in the caption ink: a privacy line, a note under a form. */
.t-caption { font-family: var(--font-sans); font-size: var(--type-caption); font-weight: 400; line-height: 1.45; margin: 0; color: var(--ink-2); }

/* Stat: the numeric role (28 to 32, 640, tabular). */
.t-stat {
  font-family: var(--font-sans);
  font-size: var(--type-stat);
  font-weight: 640;
  line-height: 1.15;
  letter-spacing: -0.015em;
  font-variant-numeric: tabular-nums;
  margin: 0;
}

/* ── Section eyebrow ─────────────────────────────────────────────────────
 * A leading accent slash and an uppercase micro label; the standard section
 * eyebrow. Markup: p.t-eyebrow > span.t-eyebrow__dot + LABEL (render_eyebrow).
 * The slash is a graphic mark in --accent (the brand blue on light, the strong
 * accent on dark, the on-brand ink on brand: 3 : 1 suffices); the label is
 * text in --ink on every ground (Rowan, 2026-10-12).
 *   .t-eyebrow         accent slash, ink label
 *   .t-eyebrow--live   the slash pulses (monitoring contexts)
 *   .t-eyebrow--muted  accent slash, the caption ink for the label
 *   .t-eyebrow--quiet  no slash, the caption ink: a label over a sub-list or
 *                      a panel inside a section (feature lists, fact panels) */
.t-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-family: var(--font-sans);
  font-size: var(--type-micro);
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink);
  margin: 0;
}
/* The slash pivots at its bottom-left so it leans into the gap, never
 * overhanging the content line. */
.t-eyebrow__dot {
  flex-shrink: 0;
  display: inline-block;
  width: 4px;
  height: 19px;
  background: var(--accent);
  transform: skewX(-22deg);
  transform-origin: left bottom;
}
.t-eyebrow--live .t-eyebrow__dot { animation: t-eyebrow-live-pulse 2s ease-in-out infinite; }
body.is-editor .t-eyebrow--live .t-eyebrow__dot { animation: none; }
.t-eyebrow--muted { color: var(--ink-2); }
.t-eyebrow--quiet { color: var(--ink-2); }
.t-eyebrow--quiet .t-eyebrow__dot { display: none; }

@keyframes t-eyebrow-live-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}
/* Buttons: the app's button system on the site's editor-facing variant names.
 * Editors keep primary / secondary / inverse / ghost / accent / link and
 * sm / md / lg / xl because those names are stored in page content; the look
 * behind each name is the app's:
 *   primary   → app primary   (accent fill, lift shadow)
 *   secondary → app outline   (raised ground, hairline ring)
 *   inverse   → app ink       (ink fill, pop shadow)
 *   ghost     → app ghost     (text, hover wash)
 *   accent    → app link      (accent text + chevron)
 *   link      → app link with the intrinsic underline
 * Colour comes from the theme variables (SYSTEM.md §3), so a button on a
 * dark band takes the dark accent fill without a restated selector. Every
 * :hover is paired with .is-hover and every :focus-visible with .is-focus so
 * the lookbook can hold a state still. No transform on :active (iOS Safari
 * drops the tap hit-test mid-transform, verified May 2026).
 *
 * HubSpot-system buttons (blog list "Read more", form submits, system pages)
 * opt in through NAMED classes only; a bare <button> is left alone.
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.button,
.hs-button,
.hs-blog-post-listing__post-button {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 38px;
  padding: 0 18px;
  font-family: var(--font-sans);
  font-size: var(--type-button-md);
  font-weight: 650;
  line-height: 1.2;
  text-align: center;
  text-transform: none;
  text-decoration: none;
  white-space: normal;
  background-color: var(--accent-fill);
  border: 0;
  border-radius: var(--radius-btn);
  color: var(--on-accent);
  box-shadow: none;
  transition: var(--transition-hover);
}
.button:hover, .button.is-hover, .button:focus-visible, .button.is-focus,
.hs-button:hover, .hs-button.is-hover, .hs-button:focus-visible, .hs-button.is-focus,
.hs-blog-post-listing__post-button:hover, .hs-blog-post-listing__post-button.is-hover,
.hs-blog-post-listing__post-button:focus-visible, .hs-blog-post-listing__post-button.is-focus {
  background-color: var(--accent-fill-hover);
  color: var(--on-accent);
  box-shadow: none;
}
.button:focus-visible, .button.is-focus,
.hs-button:focus-visible, .hs-button.is-focus { outline: none; box-shadow: var(--shadow-focus-strong); }
.button:disabled,
.hs-button:disabled { opacity: .5; cursor: not-allowed; box-shadow: none; }

/* No button */
.no-button,
.no-button:hover,
.no-button:focus,
.no-button:active {
  background: none;
  border: none;
  border-radius: 0;
  color: initial;
  font-family: inherit;
  font-size: inherit;
  font-style: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  line-height: inherit;
  margin-bottom: 0;
  padding: 0;
  text-align: left;
  text-decoration: none;
  box-shadow: none;
  transition: none;
}

/* ── The button ─────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--font-sans);
  font-weight: 650;
  line-height: 1.2;
  border: 0;
  border-radius: var(--radius-btn);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  outline: none;
  transition: var(--transition-hover);
  -webkit-font-smoothing: antialiased;
}
/* Focus: a 2px ring of the band's ink (white on the brand blue), 2px off
 * the button, so it reads on every ground; text buttons sit 6px off. */
.btn:focus-visible, .btn.is-focus { outline: 2px solid var(--ink); outline-offset: 2px; }
.btn--ghost:not(.btn--icon):focus-visible, .btn--ghost:not(.btn--icon).is-focus,
.btn--accent:focus-visible, .btn--accent.is-focus,
.btn--link:focus-visible, .btn--link.is-focus { outline-offset: 6px; border-radius: var(--radius-btn); }
.btn:disabled, .btn[aria-disabled="true"], .btn.is-disabled { opacity: .5; cursor: not-allowed; box-shadow: none; }

/* Pre-label icon: sized to the font so it matches cap-height; currentColor. */
.btn__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1em;
  height: 1em;
}
.btn__icon svg { width: 100%; height: 100%; display: block; }
/* The chevron never moves (SYSTEM.md §9 › No decoration). */
.btn__chevron { flex-shrink: 0; }

/* Ghost / accent with the chevron strip the box: text + chevron is the affordance. */
.btn--ghost.btn--with-arrow,
.btn--accent.btn--with-arrow,
.btn--ghost.btn--with-arrow:hover, .btn--ghost.btn--with-arrow.is-hover, .btn--ghost.btn--with-arrow:focus-visible,
.btn--accent.btn--with-arrow:hover, .btn--accent.btn--with-arrow.is-hover, .btn--accent.btn--with-arrow:focus-visible {
  padding: 0 !important;
  background: transparent !important;
  box-shadow: none !important;
}

/* Sizes: the app's three control heights plus a site-only xl for heroes.
 * One square corner on every size (--radius-btn, 4px; Rowan, 2026-10-14).
 * sm is 34px with 16px sides so it reads as a button, not a chip. The type
 * is the button's own tokens (13 / 15 / 16), never the copy ramp. */
.btn--sm { min-height: 34px; padding: 0 16px; font-size: var(--type-button-sm); border-radius: var(--radius-btn); }
.btn--md { min-height: 38px; padding: 0 18px; font-size: var(--type-button-md); border-radius: var(--radius-btn); }
.btn--lg { min-height: 44px; padding: 0 clamp(18px, 0.9rem + 0.6vw, 22px); font-size: var(--type-button-lg); border-radius: var(--radius-btn); }
.btn--xl { min-height: clamp(44px, 2.4rem + 0.8vw, 52px); padding: 0 clamp(22px, 1.1rem + 1vw, 28px); font-size: clamp(16px, 0.9rem + 0.22vw, 18px); border-radius: var(--radius-btn); }
.btn--full { width: 100%; }

/* Icon-only: a round control with the glyph alone
 * (play, share, pager, close; render_icon_button). Sizes 32 / 38 / 56; the
 * style's colours and hover are the button's, so only colour moves. */
.btn--icon { padding: 0; gap: 0; flex: none; border-radius: var(--radius-full); }
.btn--icon.btn--sm { width: 32px; min-height: 32px; height: 32px; }
.btn--icon.btn--md { width: 38px; min-height: 38px; height: 38px; }
.btn--icon.btn--lg { width: 56px; min-height: 56px; height: 56px; }
.btn--icon .tmm-icon { display: block; flex: none; }
/* xl: the play control on a poster (video, Rowan, 2026-10-09): 72 on phones,
 * 80 from 640, the glyph 36 / 40 (the tmm play triangle fills about half its
 * box, so a smaller glyph reads small). */
.btn--icon.btn--xl { width: 72px; min-height: 72px; height: 72px; }
.btn--icon.btn--xl .tmm-icon { width: 36px; height: 36px; }
@media (min-width: 640px) {
  .btn--icon.btn--xl { width: 80px; min-height: 80px; height: 80px; }
  .btn--icon.btn--xl .tmm-icon { width: 40px; height: 40px; }
}
/* Stretched: the control's target is the box it sits on. The button stays
 * unpositioned, so its ::after resolves against the nearest positioned
 * ancestor (the picture card) and covers it: one control, one name, and the
 * button's own :hover fires anywhere on the card. */
.btn--stretch { position: static; }
.btn--stretch::after { content: ''; position: absolute; inset: 0; }
/* Host: an icon button drawn inside a larger link (the blog pager's chevron
 * beside its title) is a span with the button's classes, aria-hidden; the
 * link carries .btn-host so its hover reaches the button. Added 2026-10-08
 * (drift sweep, templates). */
.btn-host:hover .btn--secondary,
.btn-host.is-hover .btn--secondary {
  background-color: color-mix(in oklab, var(--state-tone) var(--state-hover-amount), var(--btn-outline-ground, var(--ground-raised)));
  box-shadow: var(--btn-outline-ring-hover, var(--shadow-hairline-strong));
}

/* On a picture: a control laid over the duotone poster (the video's play,
 * Rowan, 2026-10-09: "the ring"). The poster is the brand's blue on every
 * band, so the disc takes the picture's colours, not the band's: the hatch's
 * own white (--color-surface, as .tmm-treat__hatch) and the light set's ink
 * for the glyph (the band's --ground, which the round used, is pale on a
 * light band), with the --ring hairline. Hover (its own, or its host's
 * through .btn-host): a 6px ring of the band's --accent outside the disc, a
 * box-shadow spread; the disc stays white and nothing moves.
 * Focus: --shadow-focus-strong is brand on a brand picture and vanishes, so
 * the ring is followed by 3px of the band's ground and 2px of the white,
 * which reads on a light poster and a dark one alike. This is the focus of
 * every icon button on a picture. */
.btn--picture {
  background-color: var(--color-surface);
  color: var(--color-ink);
  box-shadow: var(--ring);
}
.btn--picture:hover,
.btn--picture.is-hover,
.btn-host:hover .btn--picture,
.btn-host.is-hover .btn--picture { box-shadow: var(--ring), 0 0 0 6px var(--accent); }
.btn--picture:focus-visible,
.btn--picture.is-focus { box-shadow: var(--ring), 0 0 0 6px var(--accent), 0 0 0 9px var(--ground), 0 0 0 11px var(--color-surface); }

/* Primary: the accent fill, flat (Rowan, 2026-10-14: no raised edge; beside the
 * outline button it must read as the same shape). */
.btn--primary {
  background-color: var(--accent-fill);
  color: var(--on-accent);
  box-shadow: none;
}
.btn--primary:hover, .btn--primary.is-hover {
  background-color: var(--accent-fill-hover);
  color: var(--on-accent);
  box-shadow: none;
}
/* Pressed: a step past the hover. */
.btn--primary:active, .btn--primary.is-active { background-color: color-mix(in oklab, var(--color-black) 12%, var(--accent-fill-hover)); box-shadow: none; }

/* Secondary: the outline button. Raised ground, hairline ring, no border. */
.btn--secondary {
  background-color: var(--btn-outline-ground, var(--ground-raised));
  color: var(--ink);
  box-shadow: var(--btn-outline-ring, var(--shadow-hairline));
}
.btn--secondary:hover, .btn--secondary.is-hover {
  background-color: color-mix(in oklab, var(--state-tone) var(--state-hover-amount), var(--btn-outline-ground, var(--ground-raised)));
  color: var(--ink);
  box-shadow: var(--btn-outline-ring-hover, var(--shadow-hairline-strong));
}
.btn--secondary:active, .btn--secondary.is-active { background-color: color-mix(in oklab, var(--state-tone) var(--state-press-amount), var(--btn-outline-ground, var(--ground-raised))); }

/* Outline: a clear line and no fill, at the primary's size (Rowan,
 * 2026-10-14: buttons round v9, an option beside Secondary). The line is
 * the band's ink at 42%; hover adds the state wash and a 2px line of ink. */
.btn--outline {
  background-color: transparent;
  color: var(--ink);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--ink) 42%, transparent);
}
.btn--outline:hover, .btn--outline.is-hover {
  background-color: var(--state-hover);
  color: var(--ink);
  box-shadow: inset 0 0 0 2px var(--ink);
}
.btn--outline:active, .btn--outline.is-active { background-color: var(--state-press); box-shadow: inset 0 0 0 2px var(--ink); }

/* Inverse: the ink button. Inverts the band's ground; its hover lays its
 * own label colour (the band's ground) over the fill at the press amount,
 * so it lightens on light grounds and dims on dark ones. */
.btn--inverse {
  background-color: var(--ink);
  color: var(--ground);
  box-shadow: var(--btn-inverse-shadow, var(--shadow-pop));
}
.btn--inverse:hover, .btn--inverse.is-hover {
  background-color: color-mix(in oklab, var(--ground) var(--state-press-amount), var(--ink));
  color: var(--ground);
}

/* Ghost: text with a hover wash. */
.btn--ghost { background-color: transparent; color: var(--ink); }
.btn--ghost:hover, .btn--ghost.is-hover { background-color: var(--state-hover); color: var(--link-hover); }

/* Accent: text + chevron in the accent as small text (--accent-text, AA
 * on every ground), hovering to the ink. */
.btn--accent { background-color: transparent; color: var(--accent-text); }
.btn--accent:hover, .btn--accent.is-hover { background-color: transparent; color: var(--ink); }
.btn--ghost:active, .btn--ghost.is-active,
.btn--accent:active, .btn--accent.is-active,
.btn--link:active, .btn--link.is-active { color: var(--ink); }

/* Link: text + optional chevron + the intrinsic 1px underline. */
.btn--link {
  min-height: 0;
  padding: 0 0 4px 0;
  background-color: transparent;
  border-bottom: 1px solid currentColor;
  border-radius: 0;
  color: var(--ink);
}
.btn--link:hover, .btn--link.is-hover { background-color: transparent; color: var(--link-hover); }

/* Per-ground button resolution: the lift shadow and the outline ring are the
 * light ground's; on dark bands primary carries no shadow, the outline is the
 * charcoal lifted 6% with the dark rule at 22%; hovers are the states. */
.section--dark,
.section--charcoal,
.card--dark,
.card--island-dark {
  --btn-lift: none;
  --btn-lift-hover: none;
  --btn-outline-ground: transparent;
  --btn-outline-ring: inset 0 0 0 1px color-mix(in oklab, var(--color-dark-ink) 42%, transparent);
  --btn-outline-ring-hover: inset 0 0 0 1px var(--color-dark-ink);
  --btn-inverse-shadow: none;
}

/* The brand ground (.section--brand, .card--brand; render_button's theme
 * 'brand'): the accent fill is white there (_band.css), so primary renders
 * as the inverse, white with the brand ink, hovering to the accent wash; the
 * outline button is a white-hairline ghost, no fill; nothing lifts. */
.section--brand,
.card--brand {
  --btn-lift: none;
  --btn-lift-hover: none;
  --btn-outline-ground: transparent;
  --btn-outline-ring: inset 0 0 0 1px var(--rule);
  --btn-outline-ring-hover: inset 0 0 0 1px var(--color-surface);
  --btn-inverse-shadow: none;
}
.section--brand .btn--primary:hover, .section--brand .btn--primary.is-hover,
.card--brand .btn--primary:hover, .card--brand .btn--primary.is-hover { color: var(--color-accent-strong); }
.section--brand .btn--primary:active, .section--brand .btn--primary.is-active,
.card--brand .btn--primary:active, .card--brand .btn--primary.is-active { background-color: color-mix(in oklab, var(--color-accent) 14%, var(--color-accent-wash)); color: var(--color-accent-strong); }

/* CTA group row: wrapper for 1 or 2 buttons (output of render_cta_group).
 * Stacked full width on the phone, a row from 640. */
.tmm-cta-row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  margin-top: 8px;
}
.tmm-cta-row > * { width: 100%; }
.tmm-cta-row .btn { justify-content: center; }
.tmm-cta-row .tmm-cta { text-align: center; }
@media (min-width: 640px) {
  .tmm-cta-row { flex-direction: row; flex-wrap: wrap; align-items: center; }
  .tmm-cta-row > * { width: auto; }
  .tmm-cta-row .tmm-cta { text-align: left; }
}

/* ── HubSpot CTA wrapper ─────────────────────────────────────────────────
 * Overrides HubSpot's default .cta_button so a CTA picked from the CMS
 * renders as the same button. The inner a.cta_button is HubSpot's, hence
 * the !important. Each variant mirrors the .btn--* rule above. */
.tmm-cta { display: inline-block; line-height: 0; }
.tmm-cta .cta_button,
.tmm-cta a.cta_button {
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  gap: 8px !important;
  font-family: var(--font-sans) !important;
  font-weight: 650 !important;
  line-height: 1.2 !important;
  border: 0 !important;
  border-radius: var(--radius-btn) !important;
  text-decoration: none !important;
  white-space: nowrap !important;
  cursor: pointer;
  transition: var(--transition-hover) !important;
  -webkit-font-smoothing: antialiased !important;
}
.tmm-cta .cta_button:focus-visible { outline: 2px solid var(--ink) !important; outline-offset: 2px !important; }

.tmm-cta--sm .cta_button { min-height: 34px !important; padding: 0 16px !important; font-size: var(--type-button-sm) !important; }
.tmm-cta--md .cta_button { min-height: 38px !important; padding: 0 18px !important; font-size: var(--type-button-md) !important; }
.tmm-cta--lg .cta_button { min-height: 44px !important; padding: 0 clamp(18px, 0.9rem + 0.6vw, 22px) !important; font-size: var(--type-button-lg) !important; }
.tmm-cta--xl .cta_button { min-height: clamp(44px, 2.4rem + 0.8vw, 52px) !important; padding: 0 clamp(22px, 1.1rem + 1vw, 28px) !important; font-size: clamp(16px, 0.9rem + 0.22vw, 18px) !important; }

.tmm-cta--primary .cta_button { background-color: var(--accent-fill) !important; color: var(--on-accent) !important; box-shadow: none !important; }
.tmm-cta--primary .cta_button:hover, .tmm-cta--primary.is-hover .cta_button { background-color: var(--accent-fill-hover) !important; box-shadow: none !important; }

.tmm-cta--secondary .cta_button { background-color: var(--btn-outline-ground, var(--ground-raised)) !important; color: var(--ink) !important; box-shadow: var(--btn-outline-ring, var(--shadow-hairline)) !important; }
.tmm-cta--secondary .cta_button:hover, .tmm-cta--secondary.is-hover .cta_button { background-color: color-mix(in oklab, var(--state-tone) var(--state-hover-amount), var(--btn-outline-ground, var(--ground-raised))) !important; box-shadow: var(--btn-outline-ring-hover, var(--shadow-hairline-strong)) !important; }

.tmm-cta--outline .cta_button { background-color: transparent !important; color: var(--ink) !important; box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--ink) 42%, transparent) !important; }
.tmm-cta--outline .cta_button:hover, .tmm-cta--outline.is-hover .cta_button { background-color: var(--state-hover) !important; box-shadow: inset 0 0 0 2px var(--ink) !important; }

.tmm-cta--inverse .cta_button { background-color: var(--ink) !important; color: var(--ground) !important; box-shadow: var(--btn-inverse-shadow, var(--shadow-pop)) !important; }
.tmm-cta--inverse .cta_button:hover, .tmm-cta--inverse.is-hover .cta_button { background-color: color-mix(in oklab, var(--ground) var(--state-press-amount), var(--ink)) !important; }

.tmm-cta--ghost .cta_button { background-color: transparent !important; color: var(--ink) !important; box-shadow: none !important; }
.tmm-cta--ghost .cta_button:hover, .tmm-cta--ghost.is-hover .cta_button { background-color: var(--state-hover) !important; color: var(--link-hover) !important; }

.tmm-cta--accent .cta_button { background-color: transparent !important; color: var(--accent-text) !important; box-shadow: none !important; }
.tmm-cta--accent .cta_button:hover, .tmm-cta--accent.is-hover .cta_button { color: var(--ink) !important; }

.tmm-cta--link .cta_button {
  min-height: 0 !important;
  padding: 0 0 4px 0 !important;
  background-color: transparent !important;
  border-bottom: 1px solid currentColor !important;
  border-radius: 0 !important;
  color: var(--ink) !important;
  box-shadow: none !important;
}
.tmm-cta--link .cta_button:hover, .tmm-cta--link.is-hover .cta_button { color: var(--link-hover) !important; }
/* Badge — the app's badge (Quiet): a flat brand-tint pill, radius 7, micro
 * type. No border, no blur, no inset highlight, no coloured shadow. Depth is
 * zero on purpose: a badge is a label, not a surface. Per SYSTEM.md §7,
 * Elevation and depth, the only shadows on the site are --shadow-card /
 * --shadow-pop / --shadow-hairline, and a badge needs none of them.
 *
 * Markup must include both classes: `class="badge badge--{theme} t-subheader"`.
 * The macro render_badge does this automatically. .t-subheader supplies the
 * uppercase micro role; _badges.css is included after _typography.css, so
 * the tighter 16px leading and .02em tracking below win at equal specificity.
 *
 * Use for section eyebrows that need visual weight. For a quieter alternative,
 * use .t-eyebrow (accent slash + label, no chrome). */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: 7px;
  border: 0;
  font: 600 var(--type-micro) / 16px var(--font-sans);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
  box-shadow: none;
  transition: var(--transition-hover);
}

/* Leading icon (render_badge icon=…) — never shrinks, inherits currentColor. */
.badge--with-icon { gap: 6px; }
.badge__icon { flex-shrink: 0; }

/* Colour comes from the band's variables: the accent wash and the accent ink
 * resolve per ground, so one rule serves every theme. The combined-class
 * specificity (.badge.badge--*) is kept so the render_badge theme class, which
 * saved markup still carries, keeps winning against role rules on the pill. */
.badge,
.badge.badge--light,
.badge.badge--dark {
  background-color: var(--accent-wash);
  color: var(--accent-ink);
}

/* The old .tag (grey badge) is retired: tags and filters are the neutral
 * chip — render_pill(text, color='neutral') in templates/macros/lists.html,
 * .tmm-chip--neutral in css/components/_chip.css. Nothing renders .tag. */
/* TMM Form System: HubSpot's form markup restyled onto the app's field.
 *
 * The source is the app's FormFieldComponent / CheckboxComponent /
 * RadioComponent / ButtonComponent class tables (Rotario/TMM, the Quiet v2
 * field; SYSTEM.md §10 › Forms carries the spec table). The site is the app in a
 * wider column: the same field, at the same numbers.
 *
 * HubSpot's DOM, which every rule below targets:
 *
 *   <form class="hs-form stacked">
 *     <fieldset class="form-columns-{1,2,3}">        a row, N columns
 *       <div class="hs-form-field hs-fieldtype-*">   a field
 *         <label><span>Name</span><span class="hs-form-required">*</span></label>
 *         <legend class="hs-field-desc">help</legend>
 *         <div class="input"><input class="hs-input"></div>
 *         <ul class="hs-error-msgs inputs-list"><li><label class="hs-error-msg">
 *       </div>
 *     </fieldset>
 *     <div class="legal-consent-container">…</div>
 *     <div class="hs_error_rollup">…</div>
 *     <div class="hs_submit"><div class="actions"><input type="submit" class="hs-button primary large">
 *   </form>
 *
 * The field's anatomy is the app's: label · hint · error · control, 6px
 * apart (the error is lifted above the control with `order`, where the
 * autofill menu and the keyboard cannot cover it). Fields are 20px apart,
 * columns 24px, the submit 20px under the last field.
 *
 * The edge: the app draws the text field's edge as a real 1px border
 * (`border border-rule-strong`), so the field does too, in var(--rule)
 * (rule-strong on light). It is the one place a 1px solid border is the
 * spec; it is the app's, not a card edge.
 *
 * Colour comes from the ground's theme variables (css/components/_band.css),
 * never the raw tokens, so every rule resolves per ground: light on a light
 * band and in .card--light, dark on a dark band and in a dark card, white on
 * the brand ground. A form in a light card on a dark band reads light.
 *
 * Every :hover is paired with .is-hover and every :focus with .is-focus so
 * the lookbook can freeze states.
 *
 * This file is HubL-parsed (see the search block at the end): never put
 * template syntax inside a CSS comment. */

:root {
  --form-row-gap:    20px;  /* app: gap-5 between fields */
  --form-col-gap:    24px;  /* app: gap-x-6 in a two-column form row */
  --form-submit-gap: 20px;  /* app: gap-5, the submit is the stack's last item */
  --form-field-gap:  6px;   /* app: gap-1.5, label to hint to error to control */
}

/* ── Rows ─────────────────────────────────────────────────────────────── */

/* Legacy portals emit <fieldset class="form-columns-N">; newer ones a div.
 * `!important` throughout the spacing: HubSpot injects inline styles and its
 * own form stylesheet onto these elements. */
form fieldset,
form .form-columns-1,
form .form-columns-2,
form .form-columns-3 {
  border: 0 !important;
  padding: 0 !important;
  margin: 0 0 var(--form-row-gap) 0 !important;
  min-width: 0;
  max-width: 100% !important;
  display: grid !important;
  grid-template-columns: 1fr;
  gap: var(--form-row-gap) var(--form-col-gap);
}

form fieldset:last-of-type,
form .form-columns-1:last-of-type,
form .form-columns-2:last-of-type,
form .form-columns-3:last-of-type {
  margin-bottom: 0 !important;
}

/* One column on phones; the builder's columns from 640. */
@media (min-width: 640px) {
  form .form-columns-2 { grid-template-columns: 1fr 1fr !important; }
  form .form-columns-3 { grid-template-columns: 1fr 1fr 1fr !important; }
}

/* ── The field: label · hint · error · control ────────────────────────── */

/* HubSpot ships inline float / width / margin-right on the field; the grid
 * places it. The column is the app's anatomy, 6px apart. */
.hs-form-field {
  margin: 0 !important;
  width: 100% !important;
  float: none !important;
  display: flex;
  flex-direction: column;
  gap: var(--form-field-gap);
  min-width: 0;
}
.hs-form-field > label,
.hs-form-field > legend { margin: 0; }
.hs-form-field > .hs-error-msgs { order: 1; }
.hs-form-field > .input { order: 2; margin: 0 !important; }

/* Label: the app's LABEL, dense 14/20 at 600 in the secondary ink. */
form label,
.hs-form-field > label {
  display: block;
  font-family: var(--font-sans);
  font-size: var(--type-dense);
  font-weight: 600;
  line-height: 1.35;
  color: var(--ink-2);
  margin-bottom: var(--form-field-gap);
}
.hs-form-field > label { margin-bottom: 0; }

/* Required mark. The app marks the optional field instead ("(optional)" in
 * the label) and keeps danger for refusals, so HubSpot's asterisk stays but
 * quiet: the label's weight, the caption ink. */
.hs-form-required {
  color: var(--ink-2);
  margin-left: 2px;
  font-weight: inherit;
}

/* Hint: the app's HINT, caption 13/18 in the secondary ink. */
form legend,
.hs-form-field .hs-field-desc {
  font-family: var(--font-sans);
  font-size: var(--type-caption);
  font-weight: 400;
  line-height: 18px;
  color: var(--ink-2);
  margin: 0;
  padding: 0;
}

/* ── Text fields, selects, textareas ──────────────────────────────────── */

/* The app's INPUT: --field-ground, 1px --rule border, r12, 10/16 padding,
 * body 15/22 at 400 in the primary ink: 44px tall (the control ramp's lg,
 * "a control standing beside text inputs in a form"). HubSpot puts .hs-input
 * on checkboxes and radios too; they are excluded here. */
form input[type=text],
form input[type=search],
form input[type=email],
form input[type=password],
form input[type=tel],
form input[type=number],
form input[type=url],
form input[type=date],
form input[type=file],
form select,
form textarea,
.hs-input:not([type=checkbox]):not([type=radio]) {
  display: block;
  /* `!important` defeats HubSpot's inline `style="width: 95%"`. */
  width: 100% !important;
  max-width: 100% !important;
  min-height: 44px;
  margin: 0;
  font: 400 var(--type-body)/22px var(--font-sans);
  color: var(--ink);
  background-color: var(--field-ground);
  /* audit-allow: the app's field edge is a 1px border (FormFieldComponent::INPUT) */
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  padding: 10px 16px;
  box-shadow: none;
  transition: var(--transition-hover);
  -webkit-appearance: none;
  appearance: none;
}

/* Three lines tall at rest (the app's rows="3"), drag to grow. */
form textarea,
textarea.hs-input:not([type=checkbox]):not([type=radio]) {
  resize: vertical;
  min-height: 88px;
}

/* Placeholder: the meta ink (placeholder-text-meta). */
form input::placeholder,
form textarea::placeholder,
.hs-input::placeholder {
  color: var(--ink-muted);
  opacity: 1;
}

/* Focus: the border turns accent and the soft halo sits outside, never a
 * hard ring. The app keys it on :focus, so a select opened by the mouse
 * shows it too. */
form input:focus,
form select:focus,
form textarea:focus,
.hs-input:focus {
  outline: none;
}
form input:not([type=checkbox]):not([type=radio]):not([type=submit]):focus,
form select:focus,
form textarea:focus,
.hs-input:not([type=checkbox]):not([type=radio]):focus,
form input:not([type=checkbox]):not([type=radio]):not([type=submit]).is-focus,
form select.is-focus,
form textarea.is-focus,
.hs-input:not([type=checkbox]):not([type=radio]).is-focus {
  border-color: var(--accent);
  box-shadow: var(--shadow-focus-soft);
}

/* Disabled: the sunken ground, muted ink, no edge. */
form input:disabled,
form select:disabled,
form textarea:disabled,
.hs-input:not([type=checkbox]):not([type=radio]):disabled {
  background-color: var(--ground-sunken);
  color: var(--ink-muted);
  border-color: transparent;
  box-shadow: none;
  cursor: not-allowed;
}

/* Error (the app's VALIDATION): the border turns danger at rest; focused,
 * the halo is the danger ring. The text stays on the normal ink. */
/* Specificity: these out-rank the base (.hs-input:not([type=checkbox]):not([type=radio]),
 * 0,3,0) and the focus rule (0,4,2), so a refused field stays danger. */
form input:not([type=checkbox]):not([type=radio]):not([type=submit]).invalid,
form input:not([type=checkbox]):not([type=radio]):not([type=submit]).error,
form select.invalid,
form select.error,
form textarea.invalid,
form textarea.error,
.hs-input:not([type=checkbox]):not([type=radio]).invalid,
.hs-input:not([type=checkbox]):not([type=radio]).error {
  border-color: var(--danger);
  box-shadow: none;
}
form input:not([type=checkbox]):not([type=radio]):not([type=submit]).invalid:focus,
form input:not([type=checkbox]):not([type=radio]):not([type=submit]).error:focus,
form select.invalid:focus,
form select.error:focus,
form textarea.invalid:focus,
form textarea.error:focus,
form .hs-input:not([type=checkbox]):not([type=radio]).invalid:focus,
form .hs-input:not([type=checkbox]):not([type=radio]).error:focus,
form input:not([type=checkbox]):not([type=radio]):not([type=submit]).invalid.is-focus,
form input:not([type=checkbox]):not([type=radio]):not([type=submit]).error.is-focus,
form select.invalid.is-focus,
form select.error.is-focus,
form textarea.invalid.is-focus,
form textarea.error.is-focus,
form .hs-input:not([type=checkbox]):not([type=radio]).invalid.is-focus,
form .hs-input:not([type=checkbox]):not([type=radio]).error.is-focus {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger);
}

/* File picker: a drop well, not a typed field. */
form input[type=file],
.hs-input[type=file]:not([type=checkbox]):not([type=radio]) {
  background-color: transparent;
  border-style: dashed;
  cursor: pointer;
}

/* ── Select ───────────────────────────────────────────────────────────── */

/* The app's select is the field with the field family's one affordance, the
 * chevron (FormFieldComponent::SELECT_ARROW_URL: the Quiet Line chevron at
 * 20px, 16px in from the right, 1.75 stroke in text-muted). A data URI
 * cannot read a custom property, so the stroke is the resolved
 * --color-ink-muted, one chevron on every ground. Hover lifts the edge to
 * the faint ink (the app's TRIGGER), the one field that hovers. */
form select,
select.hs-input:not([type=checkbox]):not([type=radio]) {
  /* audit-allow: SVG data URI, stroke = --color-ink-muted resolved */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%236F6A60' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5.8 9.2 6.2 6.2 6.2-6.2'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  background-size: 20px 20px;
  padding-right: 40px;
  cursor: pointer;
}
form select:not(:disabled):not(:focus):hover,
form select:not(:disabled):not(:focus).is-hover {
  border-color: var(--ink-faint);
}

/* ── Error lines ──────────────────────────────────────────────────────── */

/* The app's error line: the Quiet Line exclamation circle at 14px beside the
 * message, dense 14 at 500 in danger, tight leading, 6px apart. HubSpot
 * wraps each in ul.hs-error-msgs > li > label.hs-error-msg. */
.hs-error-msgs.inputs-list {
  margin: 0;
  padding: 0;
  list-style: none;
}
.hs-error-msgs.inputs-list > li { margin: 0; }

.hs-error-msg,
.hs-error-msgs label {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  font-family: var(--font-sans);
  font-size: var(--type-dense);
  font-weight: 500;
  line-height: 1.25;
  color: var(--danger);
  margin: 0;
}

/* The glyph takes the message's colour through mask + currentColor, so a
 * refusal is never colour alone. */
.hs-error-msg::before,
.hs-error-msgs label::before {
  content: '';
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  margin-top: 1px;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.6'/%3E%3Cpath d='M12 7.6v5'/%3E%3Cpath d='M12 16.3h.01' stroke-width='2.3'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.6'/%3E%3Cpath d='M12 7.6v5'/%3E%3Cpath d='M12 16.3h.01' stroke-width='2.3'/%3E%3C/svg%3E");
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-size: contain;
          mask-size: contain;
}

/* The form-level rollup ("Please complete all required fields.") sits
 * between the last row and the submit. */
form .hs_error_rollup { margin-top: var(--form-row-gap); }

/* ── Checkboxes and radios ────────────────────────────────────────────── */

/* An option list stacks 10px apart (the app's RadioGroup, stacked). The
 * :not(.hs-error-msgs) matters: HubSpot uses .inputs-list for both option
 * lists and error lists. */
form .inputs-list:not(.hs-error-msgs) {
  list-style: none;
  margin: 0;
  padding: 2px 0 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
form .inputs-list:not(.hs-error-msgs) > li { margin: 0; }

/* An option row: box then text, 10px apart. In a list of options the text
 * is the app's radio-group text, dense 14/20 in the primary ink. */
form .inputs-list:not(.hs-error-msgs) label {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--type-dense);
  font-weight: 400;
  line-height: 1.35;
  color: var(--ink);
  cursor: pointer;
  margin-bottom: 0;
  text-transform: none;
  letter-spacing: 0;
}

/* A single checkbox that IS the field (HubSpot's booleancheckbox, the
 * consent box) is the app's CHECKBOX_LABEL: body 15/22 at 500 in the
 * secondary ink. */
form .hs-fieldtype-booleancheckbox .inputs-list label,
form .hs-form-booleancheckbox-display {
  align-items: flex-start;
  font-size: var(--type-body);
  font-weight: 500;
  line-height: 22px;
  color: var(--ink-2);
}
form .hs-fieldtype-booleancheckbox input[type=checkbox],
form .hs-form-booleancheckbox-display input[type=checkbox] { margin-top: 1px; }

/* The box: the app's CheckboxComponent at md. 20px, r4, 1px faint-ink edge
 * (an empty box has to announce itself), the raised ground; hover takes the
 * edge to the muted ink; checked is the accent fill with the edge gone and
 * @tailwindcss/forms' white tick; the focus halo is the strong one;
 * disabled is the box at .45. A radio is the same object, round, with the
 * white dot. */
form input[type=checkbox],
form input[type=radio] {
  flex-shrink: 0;
  width: 20px !important;
  height: 20px !important;
  margin: 0;
  padding: 0;
  display: inline-block;
  vertical-align: middle;
  background-color: var(--ground-raised);
  /* audit-allow: the app's tickbox edge is a 1px border (CheckboxComponent::TICK_DRESS) */
  border: 1px solid var(--ink-faint);
  border-radius: 4px;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
  cursor: pointer;
  transition: var(--transition-hover);
  -webkit-appearance: none;
  appearance: none;
}
form input[type=radio] { border-radius: var(--radius-full); }

form input[type=checkbox]:hover,
form input[type=radio]:hover,
form input[type=checkbox].is-hover,
form input[type=radio].is-hover {
  border-color: var(--ink-muted);
}

form input[type=checkbox]:focus,
form input[type=radio]:focus,
form input[type=checkbox].is-focus,
form input[type=radio].is-focus {
  outline: none;
  box-shadow: var(--shadow-focus-strong);
}

form input[type=checkbox]:checked,
form input[type=radio]:checked {
  background-color: var(--accent-fill);
  border-color: transparent;
}
form input[type=checkbox]:checked {
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3E%3C/svg%3E");
}
form input[type=radio]:checked {
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E");
}
/* On the brand ground the fill is white, so the mark is the brand blue
 * (the --on-accent of that ground, resolved: a data URI cannot read it). */
.section--brand form input[type=checkbox]:checked,
.card--brand form input[type=checkbox]:checked {
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='%231a73e8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3E%3C/svg%3E");
}
.section--brand form input[type=radio]:checked,
.card--brand form input[type=radio]:checked {
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='%231a73e8' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E");
}

form input[type=checkbox]:disabled,
form input[type=radio]:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ── Rich text and legal consent ──────────────────────────────────────── */

/* A rich-text block in the form reads as body copy. */
form .hs-richtext,
form .hs-richtext p {
  font-family: var(--font-sans);
  font-size: var(--type-body);
  font-weight: 400;
  line-height: 22px;
  color: var(--ink-body);
  margin: 0 0 12px;
}
form .hs-richtext img { max-width: 100%; }

/* GDPR consent sits between the last row and the submit, a row of its own.
 * Its legal paragraphs are the hint role; its checkbox sentence is the
 * checkbox label (above). */
form .legal-consent-container {
  margin: var(--form-row-gap) 0 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
form .legal-consent-container .hs-richtext,
form .legal-consent-container .hs-richtext p {
  font-size: var(--type-caption);
  line-height: 18px;
  color: var(--ink-2);
  margin: 0;
}
form .legal-consent-container .hs-richtext p + p { margin-top: 8px; }
form .legal-consent-container .hs-form-field { gap: var(--form-field-gap); }

/* HubSpot's consent markup is label > input + span > p: the label a flex
 * row, the span takes the rest, so the copy wraps at the field's width. */
.legal-consent-container .hs-form-booleancheckbox-display {
  display: flex;
  width: 100%;
}
.legal-consent-container .hs-form-booleancheckbox-display > span {
  flex: 1 1 auto;
  min-width: 0;
  margin-left: 0;
}
.legal-consent-container .hs-form-booleancheckbox-display > span p {
  margin: 0;
  font: inherit;
  color: inherit;
}

/* ── Submit ───────────────────────────────────────────────────────────── */

/* The app's primary button at lg (ButtonComponent): accent fill, on-accent
 * ink, 44px, 0 22px, callout 16 at 650, r12, the lift shadow; hover the
 * hover fill and the short shadow; focus the strong ring; disabled at .5.
 * Full width in the form, as the app's sign-in card has it.
 * theme-overrides.css repeats the paint with !important against portal
 * button settings; keep the two in step.
 *
 *   <input type="submit">   HubSpot's default; the arrow is a background
 *                           image at the right edge.
 *   <button type="submit">  gated-form/module.js may swap the input for a
 *                           button so text and arrow sit as flex siblings. */
form .hs_submit,
form .hs-submit {
  margin-top: var(--form-submit-gap);
}
form .hs_submit .actions,
form .hs-submit .actions {
  margin: 0;
  padding: 0;
}

form input[type=submit],
form button[type=submit],
form .hs-form-submit input,
form .hs-button {
  width: 100%;
  height: 44px;
  margin: 0;
  padding: 0 22px;
  font-family: var(--font-sans);
  font-size: var(--type-button-lg);
  font-weight: 650;
  line-height: 22px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--on-accent);
  background-color: var(--accent-fill);
  border: 0;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  cursor: pointer;
  transition: var(--transition-hover);
}

form input[type=submit]:hover,
form button[type=submit]:hover,
form .hs-form-submit input:hover,
form .hs-button:hover,
form input[type=submit].is-hover,
form button[type=submit].is-hover,
form .hs-form-submit input.is-hover,
form .hs-button.is-hover {
  background-color: var(--accent-fill-hover);
  box-shadow: var(--shadow-lift-hover);
}

form input[type=submit]:focus-visible,
form button[type=submit]:focus-visible,
form .hs-form-submit input:focus-visible,
form .hs-button:focus-visible,
form input[type=submit].is-focus,
form button[type=submit].is-focus,
form .hs-form-submit input.is-focus,
form .hs-button.is-focus {
  outline: none;
  box-shadow: var(--shadow-focus-strong);
}

form input[type=submit]:disabled,
form button[type=submit]:disabled,
form .hs-button:disabled {
  opacity: 0.5;
  box-shadow: none;
  cursor: not-allowed;
}

/* Input variant: the arrow at the right edge; the right padding keeps the
 * centred label clear of it. A white stroke on the accent fill. */
form input[type=submit],
form .hs-form-submit input {
  display: block;
  padding-right: 48px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3Cpolyline points='12 5 19 12 12 19'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 20px center;
}

/* Button variant: text and arrow centred together, 8px apart (the app's
 * gap-2). */
form button[type=submit],
form button.hs-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.gated-form__btn-arrow { flex-shrink: 0; }

/* Captcha */
.grecaptcha-badge { margin: 0 auto; }

/* ── TMM form wrapper (macro output) ──────────────────────────────────── */

/* One card recipe for every card_style: the card ground, the card edge (the
 * border in --card-ring, drawn by the shared rule in _cards.css) and the
 * card shadow (none on dark grounds; _band.css sets all three). `glass` and `solid` are legacy names kept for stored page
 * content; they now render as the same card (no blur, no translucent white,
 * no 60px shadow). `bare` emits no wrapper at all. The macro's theme class
 * (.tmm-form-wrap--dark) resolves the dark set via _band.css. */
.tmm-form-wrap {
  border-radius: var(--card-radius);
  padding: var(--card-padding);
  transition: var(--transition-hover);
}

.tmm-form-wrap--default,
.tmm-form-wrap--flat,
.tmm-form-wrap--solid,
.tmm-form-wrap--glass {
  background: var(--card-ground);
  box-shadow: var(--card-shadow);
}

/* ── Search field (HubSpot theme module) ──────────────────────────────── */

  .body-container-wrapper .hs-search-field__button {
    padding: 10px 16px;
    border-radius: var(--radius-lg);
  }
  .body-container-wrapper .hs-search-field__bar--button-inline .hs-search-field__button {
    margin-left: 8px;
    margin-bottom: 0;
  }
  .body-container-wrapper .hs-search-field__button svg {
    height: 16px;
    fill: var(--color-surface);
  }
  .body-container-wrapper .hs-search-field__bar > form > .hs-search-field__input {
    padding: 10px 16px;
  }
  .body-container-wrapper .hs-search-field__suggestions li a {
    color: var(--color-ink);
    padding: 8px 16px;
    text-decoration: none;
    transition: var(--transition-hover);
  }
  .body-container-wrapper .hs-search-field__suggestions li a:hover,
  .body-container-wrapper .hs-search-field__suggestions li a:focus-visible,
  .body-container-wrapper .hs-search-field__suggestions li a.is-hover,
  .body-container-wrapper .hs-search-field__suggestions li a.is-focus {
    background-color: var(--state-hover);
  }

/* TMM Table System (Quiet)
 * Horizontal-only rules, no vertical rules.
 * Every colour is a theme variable (css/components/_band.css), so each rule
 * is written once and resolves per ground: light, white, dark, charcoal,
 * brand and the dark/brand cards all restate the same set.
 * <th> uses the micro role (12/16 600 uppercase) in --ink-muted on
 * --ground-sunken with a --rule bottom rule; <td> is the dense role (14/20)
 * in --ink-body with --rule-soft between rows.
 * Designed tables sit in a rounded --card-ground container: the card edge
 * (the border in --card-ring, _cards.css) on --card-shadow, the app's table
 * card. Row hover is --state-hover laid over the row; the wrapper also stripes even rows
 * with --ground-sunken.
 * Lookbook force class: .is-hover on a <tr>.
 */

/* Default table reset — overrides theme-overrides.css (which would otherwise
 * apply border: 1px solid <portal-brand-primary> to all <table>/<th>/<td>).
 * .container scoping bumps specificity above the bare `table {}` rule. */
table,
.container table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 1.4rem;
  overflow-wrap: break-word;
  font-family: var(--font-sans);
  border: none;
  background: transparent;
}

td,
th,
.container td,
.container th {
  vertical-align: top;
  text-align: left;
  border: none;
  border-bottom: 1px solid var(--inner-rule, var(--rule-soft));
  padding: 14px 16px;
  font-size: var(--type-dense);
  line-height: 1.4;
  color: var(--ink-body);
  background: transparent;
}

thead th,
.container thead th {
  vertical-align: bottom;
  background-color: var(--ground-sunken);
  border-bottom-color: var(--inner-rule, var(--rule));
  font-size: var(--type-micro);
  font-weight: 600;
  line-height: 16px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-2);
}

tfoot td {
  background-color: var(--ground-sunken);
  color: var(--ink-2);
}

/* ── TMM table wrapper ───────────────────────────────────────────────── */

.tmm-table-wrap {
  --inner-rule: initial; /* a card-edged table keeps its row rules under the band's Inner lines switch, as a card does (_cards.css) */
  border-radius: var(--radius-xl);
  box-shadow: var(--card-shadow);
  overflow: hidden;
  background: var(--card-ground);
}

.tmm-table-wrap table {
  margin: 0;
  border: none;
}

.tmm-table-wrap th {
  padding: 12px 20px;
  background: var(--ground-sunken);
  border: none;
  border-bottom: 1px solid var(--inner-rule, var(--rule));
  font-family: var(--font-sans);
  font-size: var(--type-micro);
  font-weight: 600;
  line-height: 16px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.tmm-table-wrap td {
  padding: 14px 20px;
  border: none;
  border-bottom: 1px solid var(--inner-rule, var(--rule-soft));
  font-family: var(--font-sans);
  font-size: var(--type-dense);
  font-weight: 400;
  line-height: 1.4;
  color: var(--ink-body);
  transition: var(--transition-hover);
}

.tmm-table-wrap tr:last-child td { border-bottom: none; }

.tmm-table-wrap tbody tr:nth-child(even) td {
  background: var(--ground-sunken);
}

/* Row hover: the state over the row, over the stripe on an even row. */
.tmm-table-wrap tbody tr:hover td,
.tmm-table-wrap tbody tr.is-hover td {
  background: var(--state-hover);
}
.tmm-table-wrap tbody tr:nth-child(even):hover td,
.tmm-table-wrap tbody tr:nth-child(even).is-hover td {
  background: color-mix(in oklab, var(--state-tone) var(--state-hover-amount), var(--ground-sunken));
}

/* ── The ruled table: a kit for any designed table ────────────────────────
 * Rowan, 2026-10-09 (SYSTEM.md §10 › Tables). First adopter: the comparison
 * table. A real <table>; each piece is a class a future table can reuse.
 *
 *   .tmm-ruled-table            the grammar: every cell 14px 16px, a 20px
 *                               line, a 1px var(--rule) under each row and
 *                               none outside (the last row has none)
 *   .tmm-ruled-table--cells     a 1px rule between every pair of cells too
 *   .tmm-ruled-table__stub      the row-head column (the feature names),
 *                               left-aligned, no left padding
 *   .tmm-ruled-table__mark      a 20 x 20 block for a tick or a dash
 *   .tmm-ruled-table__sticky    on <thead>: the sticky head. Every head row
 *                               pins at --sticky-top (_band.css) as one
 *                               opaque block on --table-head-ground, a 1px
 *                               rule on its top and bottom edge, no shadow.
 *                               With two rows the first is --table-head-row
 *                               tall and the second pins under it.
 *   .tmm-ruled-table__hatch     a row holding a 16px .tmm-hatch strip, the
 *                               break before a group (one cell, colspan all)
 *   .tmm-ruled-table__group     a group head row, 24px over 12px
 *   .tmm-ruled-table--rowheads  under 640: each row's stub becomes a full-
 *                               width row head with the row's cells on one
 *                               line beneath, in --plan-n equal columns,
 *                               under the head pinned whole; table layout
 *                               from 640
 *
 * Sticky needs the page as the scroller: no ancestor may be a scroll
 * container (the band clips with overflow: clip, never hidden). */
table.tmm-ruled-table {
  --table-head-ground: var(--ground);
  --table-head-row: 41px;
  width: 100%;
  margin: 0;
  table-layout: fixed;
  border-collapse: separate;
  border-spacing: 0;
}
.tmm-ruled-table th,
.tmm-ruled-table td {
  position: relative;
  padding: 14px 16px;
  vertical-align: middle;
  text-align: center;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--inner-rule, var(--rule));
  font-size: var(--type-dense);
  line-height: 1.4;
  color: var(--ink-body);
}
.tmm-ruled-table .tmm-ruled-table__stub { width: 40%; padding-left: 0; text-align: left; font-weight: 500; color: var(--ink); }
.tmm-ruled-table > tbody:last-child > tr:last-child > * { border-bottom: 0; }
.tmm-ruled-table .t-eyebrow { margin: 0; }
.tmm-ruled-table__mark { display: flex; justify-content: center; }
.tmm-ruled-table__mark svg { display: block; }

.tmm-ruled-table thead > tr > * {
  position: sticky;
  top: var(--sticky-top);
  z-index: 2;
  vertical-align: bottom;
  background: var(--table-head-ground);
  font-size: var(--type-micro);
  font-weight: 600;
  line-height: 16px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.tmm-ruled-table thead > tr:not(:last-child) > * { height: var(--table-head-row); padding-block: 10px; }
.tmm-ruled-table thead > tr:not(:last-child) > .tmm-ruled-table__stub { border-bottom: 0; }
.tmm-ruled-table thead > tr + tr > * { top: calc(var(--sticky-top) + var(--table-head-row)); }
.tmm-ruled-table thead > tr:first-child > *::after { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px; background: var(--inner-rule, var(--rule)); pointer-events: none; }

.tmm-ruled-table .tmm-ruled-table__hatch > td { padding: 0; border-bottom: 0; }
.tmm-ruled-table__hatch .tmm-hatch { height: 16px; }
.tmm-ruled-table .tmm-ruled-table__group > * { padding-top: 24px; padding-bottom: 12px; vertical-align: bottom; }

.tmm-ruled-table--cells tr > * + *::before { content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 1px; background: var(--rule); pointer-events: none; }

/* Row heads, the phone layout; table layout returns at 640. */
.tmm-ruled-table--rowheads,
.tmm-ruled-table--rowheads > thead,
.tmm-ruled-table--rowheads > tbody { display: block; }
.tmm-ruled-table--rowheads > thead { position: sticky; top: var(--sticky-top); z-index: 2; background: var(--table-head-ground); }
.tmm-ruled-table--rowheads tr { display: grid; grid-template-columns: repeat(var(--plan-n, 3), minmax(0, 1fr)); }
.tmm-ruled-table.tmm-ruled-table--rowheads th,
.tmm-ruled-table.tmm-ruled-table--rowheads td { width: auto; padding-inline: 8px; }
.tmm-ruled-table.tmm-ruled-table--rowheads > thead > tr > * { position: relative; top: auto; padding-inline: 4px; text-transform: none; letter-spacing: 0; overflow-wrap: anywhere; }
.tmm-ruled-table--rowheads [colspan="2"] { grid-column: span 2; }
.tmm-ruled-table--rowheads [colspan="3"] { grid-column: span 3; }
.tmm-ruled-table--rowheads thead .tmm-ruled-table__stub,
.tmm-ruled-table--rowheads td.tmm-ruled-table__stub,
.tmm-ruled-table--rowheads th.tmm-ruled-table__stub:empty,
.tmm-ruled-table--rowheads .tmm-ruled-table__group > td { display: none; }
.tmm-ruled-table--rowheads .tmm-ruled-table__stub + *::before { display: none; }
.tmm-ruled-table.tmm-ruled-table--rowheads tbody .tmm-ruled-table__stub { grid-column: 1 / -1; padding: 14px 0 4px; border-bottom: 0; }
.tmm-ruled-table.tmm-ruled-table--rowheads tbody td { padding-top: 4px; }
.tmm-ruled-table.tmm-ruled-table--rowheads .tmm-ruled-table__group > .tmm-ruled-table__stub { padding: 24px 0 12px; border-bottom: 1px solid var(--inner-rule, var(--rule)); }
.tmm-ruled-table.tmm-ruled-table--rowheads .tmm-ruled-table__hatch > td { grid-column: 1 / -1; padding: 0; }
@media (min-width: 640px) {
  .tmm-ruled-table--rowheads { display: table; }
  .tmm-ruled-table--rowheads > thead { display: table-header-group; position: static; }
  .tmm-ruled-table--rowheads > tbody { display: table-row-group; }
  .tmm-ruled-table--rowheads tr { display: table-row; }
  .tmm-ruled-table.tmm-ruled-table--rowheads th,
  .tmm-ruled-table.tmm-ruled-table--rowheads td { padding: 14px 16px; }
  .tmm-ruled-table.tmm-ruled-table--rowheads .tmm-ruled-table__stub { width: 40%; padding-left: 0; }
  .tmm-ruled-table.tmm-ruled-table--rowheads > thead > tr > * { position: sticky; top: var(--sticky-top); padding-inline: 16px; text-transform: uppercase; letter-spacing: 0.05em; overflow-wrap: normal; }
  .tmm-ruled-table.tmm-ruled-table--rowheads > thead > tr:not(:last-child) > * { padding-block: 10px; }
  .tmm-ruled-table.tmm-ruled-table--rowheads > thead > tr + tr > * { top: calc(var(--sticky-top) + var(--table-head-row)); }
  .tmm-ruled-table--rowheads thead .tmm-ruled-table__stub,
  .tmm-ruled-table--rowheads td.tmm-ruled-table__stub,
  .tmm-ruled-table--rowheads th.tmm-ruled-table__stub:empty,
  .tmm-ruled-table--rowheads .tmm-ruled-table__group > td { display: table-cell; }
  .tmm-ruled-table--rowheads .tmm-ruled-table__stub + *::before { display: block; }
  .tmm-ruled-table.tmm-ruled-table--rowheads tbody .tmm-ruled-table__stub { padding: 14px 16px 14px 0; border-bottom: 1px solid var(--inner-rule, var(--rule)); }
  .tmm-ruled-table.tmm-ruled-table--rowheads tbody td { padding-top: 14px; }
  .tmm-ruled-table.tmm-ruled-table--rowheads .tmm-ruled-table__group > * { padding-top: 24px; padding-bottom: 12px; }
  .tmm-ruled-table.tmm-ruled-table--rowheads .tmm-ruled-table__group > .tmm-ruled-table__stub { padding: 24px 16px 12px 0; }
  .tmm-ruled-table.tmm-ruled-table--rowheads .tmm-ruled-table__hatch > td { padding: 0; border-bottom: 0; }
  .tmm-ruled-table.tmm-ruled-table--rowheads > tbody:last-child > tr:last-child > * { border-bottom: 0; }
}

/* ── The comparison table (comparison-table.module) on the ruled table ────
 * .is-base marks the base plan's cells (plan_roles.base_column). The two
 * highlights, Rowan 2026-10-09:
 *   --plate        the base column on --ground-raised through both sticky
 *                  rows; from 640 a card (the card edge and shadow, _cards.css) at the tile radius
 *                  behind it, 16px past the table top and bottom
 *                  (.tmm-compare__frame + .tmm-compare__plate, placed by
 *                  --base-i of --plan-n)
 *   --accent-head  the base column's names cell the one brand fill
 *                  (--accent-fill, --on-accent), the rest of it raised */
.tmm-compare thead .is-base { color: var(--ink); }
.tmm-compare thead .tmm-chip { margin-top: 4px; text-transform: none; letter-spacing: 0; }
.tmm-compare__check { color: var(--accent); }
.tmm-compare__minus { color: var(--ink-faint); }
.tmm-compare__value { font-weight: 500; color: var(--ink-2); }
.tmm-ruled-table .tmm-compare__intro > * { vertical-align: top; font-size: var(--type-caption); }
.tmm-compare__intro .tmm-compare__value { display: block; font-weight: 400; }
.tmm-compare__intro .tmm-chip { margin-top: 8px; }
.tmm-ruled-table .tmm-compare__end td { padding-top: 24px; border-bottom: 0; }
.tmm-compare__end .btn,
.tmm-compare__end .tmm-cta .cta_button { width: 100%; justify-content: center; }
.card .tmm-compare { --table-head-ground: var(--ground-sunken); }
.tmm-compare--rows-hover .tmm-compare__row { transition: var(--transition-hover); }
.tmm-compare--rows-hover .tmm-compare__row:hover,
.tmm-compare--rows-hover .tmm-compare__row.is-hover { background: var(--state-hover); }

.tmm-compare--plate .is-base,
.tmm-compare--accent-head .is-base { background: var(--ground-raised); }
.tmm-compare--accent-head .tmm-compare__names .is-base { background: var(--accent-fill); color: var(--on-accent); }
.tmm-compare--accent-head .tmm-compare__names .is-base .tmm-chip { background: var(--on-accent); color: var(--accent-fill); }
.tmm-compare__frame { position: relative; }
.tmm-compare__plate { display: none; }
@media (min-width: 640px) {
  .tmm-compare__frame { padding-block: 16px; }
  .tmm-compare__plate {
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(40% + var(--base-i, 0) * 60% / var(--plan-n, 3));
    width: calc(60% / var(--plan-n, 3));
    border-radius: var(--card-radius-sm);
    background: var(--ground-raised);
    box-shadow: var(--card-shadow);
    pointer-events: none;
  }
}

/* Layouts and components */
/* Layouts — the grids a module stands on (SYSTEM.md §5). Mobile first; the
 * three widths are 640 / 768 / 1024. A module's own CSS sets the variables
 * and the ordering; it never redraws these.
 *
 *   .tmm-split             copy + media side by side from 1024
 *   .section__grid--N      equal columns (2 / 3 / 4)
 *   .section__tile-grid    count-driven tiles, orphans centred (tile_cols);
 *                          --auto fits columns to --tile-min, --row keeps one
 *                          wrapping row, --stack is one column on the phone
 *   .tmm-stack             a vertical stack on the text-gap rhythm
 *   .tmm-bleed             past the container or the screen edge
 *
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

/* ── The split ──────────────────────────────────────────────────────────
 * Markup:  div.tmm-split[.tmm-split--media-left][.tmm-split--ruled]
 *                       [.tmm-split--bleed-container|--bleed-full][.tmm-split--top]
 *            > div.tmm-split__copy
 *            > div.tmm-split__media
 * Variables: --split-media (the media column's share, default 50%),
 *            --split-gutter (the gap either side of the middle, default 64px).
 * Stacked under 1024: copy first, media under it, the stack gap between.
 * --ruled draws the index rule (full row height on the copy's outer edge)
 * and the middle rule between the columns; --bleed-container lets the media
 * fill its column edge to edge and top to bottom (the middle rule goes, the
 * picture's edge is the division); --bleed-full carries it on to the screen
 * (the band adds .section--bleed so the container lets go). */
.tmm-split {
  --split-media: 50%;
  --split-gutter: 64px;
  --split-index-gutter: 56px;
  display: flex;
  flex-direction: column;
  gap: var(--split-stack-gap, clamp(40px, 5vw, 72px));
}
.tmm-split__copy  { order: 0; width: 100%; min-width: 0; position: relative; }
.tmm-split__media { order: 1; width: 100%; min-width: 0; position: relative; }

.tmm-split--bleed-full .tmm-split__media { width: 100vw; margin-inline: calc(50% - 50vw); }
.section--bleed .tmm-split__copy { padding-inline: 32px; }
@media (min-width: 768px) { .section--bleed .tmm-split__copy { padding-inline: 40px; } }

@media (min-width: 1024px) {
  .tmm-split { flex-direction: row; align-items: center; gap: 0; }
  .tmm-split--top { align-items: flex-start; }
  /* The copy column stays in view beside a long list (Rowan, 2026-10-14: more than two items
   * on the right); the module adds the class when it counts them. */
  .tmm-split--sticky-copy > .tmm-split__copy { position: sticky; top: calc(var(--sticky-top, 72px) + 32px); }
  .tmm-split--stretch { align-items: stretch; }
  .tmm-split__media { width: var(--split-media); padding-left: var(--split-gutter); }
  .tmm-split__copy  { width: calc(100% - var(--split-media)); padding-right: var(--split-gutter); }
  .tmm-split--media-left .tmm-split__copy  { order: 1; padding-right: 0; padding-left: var(--split-gutter); }
  .tmm-split--media-left .tmm-split__media { order: 0; padding-left: 0; padding-right: var(--split-gutter); }

  /* ruled: the index rule and the middle rule, full row height */
  .tmm-split--ruled { align-items: stretch; }
  .tmm-split--ruled .tmm-split__copy {
    padding-left: var(--split-index-gutter);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
  }
  .tmm-split--ruled .tmm-split__copy::before,
  .tmm-split--ruled .tmm-split__media::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 1px;
    background: var(--rule);
    pointer-events: none;
  }
  /* divided: the middle rule only (the hero's split) */
  .tmm-split--divided { align-items: stretch; }
  .tmm-split--divided .tmm-split__media::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 1px;
    background: var(--rule);
    pointer-events: none;
  }
  .tmm-split--divided.tmm-split--media-left .tmm-split__media::before { left: auto; right: 0; }
  /* The inside space (Spacing and lines, Rowan 2026-10-13: "spacing inner is inside them"):
   * a split that opens or closes the content row runs its vertical rules through the row's
   * inside space, so the space sits within the lines and the outside space beyond them.
   * render_band adds .has-in-top / .has-in-bottom and sets --edge-in-top / --edge-in-bottom only
   * when the editor chose an inside space; otherwise nothing here applies. */
  .section__band.has-in-top > .tmm-split:first-child :is(.tmm-split__copy, .tmm-split__media)::before { top: calc(-1 * var(--edge-in-top, 0px)); }
  .section__band.has-in-bottom > .tmm-split:last-child :is(.tmm-split__copy, .tmm-split__media)::before { bottom: calc(-1 * var(--edge-in-bottom, 0px)); }
  .tmm-split--ruled.tmm-split--media-left .tmm-split__copy { padding-left: var(--split-gutter); padding-right: var(--split-index-gutter); }
  .tmm-split--ruled.tmm-split--media-left .tmm-split__copy::before,
  .tmm-split--ruled.tmm-split--media-left .tmm-split__media::before { left: auto; right: 0; }
  .tmm-split--ruled .tmm-split__media { display: flex; align-items: center; }
  .tmm-split--ruled.tmm-split--top .tmm-split__copy { justify-content: flex-start; }
  .tmm-split--ruled.tmm-split--top .tmm-split__media { align-items: flex-start; }

  /* bleeds: the media fills its column; no gutter, no middle rule */
  .tmm-split--bleed-container,
  .tmm-split--bleed-full { align-items: stretch; }
  .tmm-split--bleed-container .tmm-split__media,
  .tmm-split--bleed-full .tmm-split__media { padding: 0; margin: 0; display: flex; align-items: stretch; }
  /* The filling box is the column's height, never the picture's: the box
   * stretches as a flex child and the picture is pinned inside it. */
  .tmm-split--bleed-container .tmm-image-box--fill,
  .tmm-split--bleed-full .tmm-image-box--fill { flex: 1 1 auto; height: auto; min-height: 320px; }
  .tmm-image-box--fill .tmm-img { position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; }
  /* contained, the wrapper still fills the column (not the picture's own
   * aspect) and the picture fits inside it, centred (_image-box.css) */
  .tmm-image-box--fill.tmm-image-box--contain > .tmm-img { width: 100%; height: 100%; max-width: none; max-height: none; aspect-ratio: auto; }
  .tmm-split--bleed-container:not(.tmm-split--divided) .tmm-split__media::before,
  .tmm-split--bleed-full:not(.tmm-split--divided) .tmm-split__media::before { display: none; }
  /* full: the column stops being a fixed share and takes the free space, so
   * the negative margin grows it (a fixed-width item would only shrink).
   * Every wrapper is centred, so 50vw - 50% is the distance to the screen. */
  .tmm-split--bleed-full .tmm-split__media { width: auto; flex: 1 1 auto; margin-right: calc(50% - 50vw); }
  .tmm-split--bleed-full .tmm-split__copy { flex: 0 0 auto; }
  .tmm-split--bleed-full.tmm-split--media-left .tmm-split__media { margin-right: 0; margin-left: calc(50% - 50vw); }
  /* the copy holds the page's gutter itself once the container lets go */
  .section--bleed .tmm-split__copy { padding-left: max(40px, calc((100% - var(--container-max)) / 2 + 80px)); padding-right: var(--split-gutter); }
  .section--bleed .tmm-split--media-left .tmm-split__copy { padding-right: max(40px, calc((100% - var(--container-max)) / 2 + 80px)); padding-left: var(--split-gutter); }
}

/* Asymmetric pairs: the media column's share. */
.tmm-split--5-7 { --split-media: 58.3333%; }
.tmm-split--7-5 { --split-media: 41.6667%; }
.tmm-split--4-8 { --split-media: 66.6667%; }
.tmm-split--8-4 { --split-media: 33.3333%; }

/* ── Equal-column grids ─────────────────────────────────────────────────
 * One column, two from 768, the full count from 1024. --ruled draws cells
 * (1px rules between tiles, none outside); --ledger draws rows. */
.section__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--bento-gap);
}
@media (min-width: 768px) {
  .section__grid--2,
  .section__grid--3,
  .section__grid--4 { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
  .section__grid--3 { grid-template-columns: repeat(3, 1fr); }
  .section__grid--4 { grid-template-columns: repeat(4, 1fr); }
}

/* Cells: the gap becomes the rule. Each tile draws its left and top rule;
 * the grid clips the outer ones so only the inner lines show. The top rule
 * (::after, a row rule) reads var(--inner-rule, ...) so the band's Inner
 * lines switch blanks it; the left rule (::before, a column rule) stays. The clip is
 * inset 1px on the two ruled sides: a grid on a half pixel (a centred
 * container of odd width, 1905 or 1906 wide) snaps its clip and its cells'
 * rules differently, and a clip on the grid's own edge let half of the
 * outer rule through. Inset, the outer rule (at -1px) stays a full pixel
 * outside the clip at any sub-pixel offset; the cells' padding owns the
 * 1px taken from the first row and column, so integer widths look the same. */
.section__grid--ruled {
  gap: 0;
  overflow: hidden;
  clip-path: inset(1px 0 0 1px);
}
/* A ruled grid that is also a .tmm-ruled block (blog-card-group) draws its
 * own hairline on its top edge, which hides the top row's rule anyway. */
.section__grid--ruled.tmm-ruled { clip-path: inset(0 0 0 1px); }
.section__grid--ruled > * {
  position: relative;
  padding: var(--cell-pad, var(--card-padding));
}
.section__grid--ruled > *::before,
.section__grid--ruled > *::after {
  content: '';
  position: absolute;
  background: var(--rule);
  pointer-events: none;
}
.section__grid--ruled > *::before { top: 0; bottom: 0; left: -1px; width: 1px; }
.section__grid--ruled > *::after { left: 0; right: 0; top: -1px; height: 1px; background: var(--inner-rule, var(--rule)); }

/* Ledger: rows on rules, numbered from the accent. */
.section__grid--ledger {
  grid-template-columns: 1fr;
  gap: 0;
  counter-reset: ledger;
}
.section__grid--ledger > * {
  position: relative;
  padding: var(--ledger-pad, 24px) 0;
}
.section__grid--ledger > *::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--inner-rule, var(--rule));
}
.section__grid--ledger > *:first-child { border-top: 0; }
.section__grid--ledger > *:first-child::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 1px;
  background: var(--inner-rule, var(--rule));
}

/* ── Tile grid ──────────────────────────────────────────────────────────
 * Count-driven: the caller sets --desk-cols inline (tile_cols). Two columns
 * on the phone, the count from 768. Orphans centre. */
.section__tile-grid {
  --tile-cols: min(var(--desk-cols, 4), 2);
  --tile-gap: 16px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--tile-gap);
  max-width: 1024px;
  margin-inline: auto;
}
.section__tile-grid > * {
  box-sizing: border-box;
  flex: 0 0 calc((100% - var(--tile-gap) * (var(--tile-cols) - 1)) / var(--tile-cols));
  max-width: calc((100% - var(--tile-gap) * (var(--tile-cols) - 1)) / var(--tile-cols));
}
/* Stack: one column on the phone, two from 640, the count from 768, for
 * tiles whose titles are too long for a 150px column (a card with a heading
 * and a paragraph, not a logo or a stat). */
.section__tile-grid--stack { --tile-cols: 1; }
@media (min-width: 640px) {
  .section__tile-grid--stack { --tile-cols: min(var(--desk-cols, 4), 2); }
}
@media (min-width: 768px) {
  .section__tile-grid,
  .section__tile-grid--stack { --tile-cols: var(--desk-cols, 4); --tile-gap: var(--bento-gap); }
}

/* Auto: as many columns as fit, each at least --tile-min wide (a feature
 * strip, a logo field), for a set whose count the editor does not drive.
 * Grid, so the last row fills; no 1024 cap, the band's measure decides. */
.section__tile-grid--auto {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(var(--tile-min, 220px), 100%), 1fr));
  max-width: none;
}
.section__tile-grid--auto > * { max-width: none; }
/* Row: every tile shares one row and wraps only when a tile would fall
 * under --tile-min (a CTA's points, a logo row, the steps on a wide band). */
.section__tile-grid--row { max-width: none; }
.section__tile-grid--row > * { flex: 1 1 var(--tile-min, 160px); max-width: none; }

/* ── Icon tile grids on lines (Rowan, 2026-10-08: integrations, roles) ──
 * Two compositions of the cells idiom for a count of icon tiles, shared by
 * every module that draws one. The caller sets --desk-cols inline (tile_cols
 * for the cells, its own mat count for the mat).
 *   .section__grid--tiles   count-driven columns on the equal-column grid:
 *                           one, two from 768, --desk-cols from 1024
 *   .section__grid--beads   cells on the rule: the top rule on every row (the
 *                           first included), no outer side rules; the tile's
 *                           plate sits in a .section__bead centred on its
 *                           cell's top rule, the rule cut 8px either side
 *   .section__grid--mat     the cells inside one .card--plate.tmm-mat: every
 *                           row the height of the tallest (1fr rows, content
 *                           at the top); .section__mat-hatch closes the last
 *                           row's empty slots, shown per width through
 *                           --hatch-md / --hatch-lg (block) and --hatch-span
 * A tile that is a link draws the focus ring inside its cell. */
@media (min-width: 768px) {
  .section__grid--tiles { grid-template-columns: repeat(min(var(--desk-cols, 4), 2), 1fr); }
}
@media (min-width: 1024px) {
  .section__grid--tiles { grid-template-columns: repeat(var(--desk-cols, 4), 1fr); }
}
.section__grid--beads { padding-top: 24px; }
.section__grid--beads > * { --cell-pad: 0 0 32px; }
.section__bead {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-self: flex-start;
  margin: -24px 0 0 -8px;
  padding-inline: 8px;
  background: var(--ground);
}
/* The bare glyph and the muted glyph (render_icon_box 'muted', the default)
 * in a bead: the 24px glyph alone centred on the cell's top rule, the rule
 * cut 8px either side by the bead's ground (Rowan, 2026-10-12, the ROI
 * page's finds; until then the muted glyph stood 8px over the rule, uncut). */
.section__bead > .icon-box--bare,
.section__bead > .icon-box--muted { width: 24px; height: 24px; margin-top: 12px; }
/* The bare glyph on a rule ('rule') in a bead: the cell's top rule is the
 * glyph's rule (the rule's own tick is dropped), the glyph stands 8px over
 * it, uncut (the bead lifts 32, has no ground and no side pad). The grid's
 * top pad gains the 8 the glyph stands higher than a plate's half, each
 * cell's foot 8 (16 on phones). */
.section__bead:has(> .icon-box--rule) { margin: -32px 0 0; padding-inline: 0; background: none; }
.section__bead > .icon-box--rule { height: auto; padding-bottom: 8px; }
.section__bead > .icon-box--rule::after { content: none; }
.section__grid.section__grid--beads:has(.icon-box--rule) { padding-top: 32px; }
.section__grid--beads:has(.icon-box--rule) > * { --cell-pad: 0 0 48px; }
/* A large stat in a bead (render_stat size lg): the figure centred on the
 * cell's top rule, the rule cut 8px either side; the grid's top pad is the
 * figure's half (Rowan, 2026-10-12, the ROI page's global figures). */
.section__bead:has(> .tmm-stat--lg) { margin-top: calc(var(--type-stat-lg) / -2); }
.section__grid.section__grid--beads:has(> * > .section__bead > .tmm-stat--lg) { padding-top: calc(var(--type-stat-lg) / 2); }
.section__grid--mat { grid-auto-rows: 1fr; }
/* Beads keep the first row's top rule (it is the rule the plates sit on,
 * inside the grid's 24px top padding) and the plates reach the grid's top
 * edge, so only the side is inset, and only from 768: under it the grid is
 * one column, every side rule is an outer one, and the plate sits on the
 * cell's left edge (no side padding), so the side rules are not drawn and
 * nothing is inset. The mat clips like every ruled grid. */
.section__grid--beads { clip-path: none; }
.section__grid--beads > *::before { content: none; }
.section__grid--mat > * { --cell-pad: 28px 24px; }
.section__mat-hatch { display: none; }
.section__mat-hatch > .tmm-hatch { position: absolute; inset: 0; }
@media (min-width: 768px) {
  .section__grid--beads { clip-path: inset(0 0 0 1px); }
  .section__grid--beads > *::before { content: ''; }
  .section__grid--beads > * { --cell-pad: 0 32px 40px; }
  .section__grid--beads:has(.icon-box--rule) > * { --cell-pad: 0 32px 48px; }
  .section__grid--mat > * { --cell-pad: 32px; }
  .section__mat-hatch { display: var(--hatch-md, none); }
}
@media (min-width: 1024px) {
  .section__mat-hatch { display: var(--hatch-lg, none); grid-column: span var(--hatch-span, 1); }
}
/* Fill (Rowan, 2026-10-09: the resources grid): a ruled grid whose count
 * the server does not know up front (a listing filtered as it renders)
 * closes its last row with one hatched cell, the mat's hatched empty slot.
 * The caller puts one .section__grid-fill after the items; its position
 * says how many slots the last row lacks, so no count is passed: the
 * (n+1)th child of an n-item grid. Hidden when the row is full or the grid
 * is one column; it draws its cell rules like any cell (.section__grid--
 * ruled). Equal columns only (.section__grid--2 / --3). */
.section__grid-fill { display: none; }
.section__grid-fill > .tmm-hatch { position: absolute; inset: 0; }
@media (min-width: 768px) {
  .section__grid--fill > .section__grid-fill:nth-child(even) { display: block; }
}
@media (min-width: 1024px) {
  .section__grid--fill.section__grid--3 > .section__grid-fill { display: none; }
  .section__grid--fill.section__grid--3 > .section__grid-fill:nth-child(3n + 2) { display: block; grid-column: span 2; }
  .section__grid--fill.section__grid--3 > .section__grid-fill:nth-child(3n) { display: block; }
}
.section__grid--ruled > a { color: inherit; text-decoration: none; transition: var(--transition-hover); }
.section__grid--ruled > a:focus-visible,
.section__grid--ruled > a.is-focus { outline: none; box-shadow: var(--shadow-focus-strong); }

/* ── Stack ──────────────────────────────────────────────────────────────── */
.tmm-stack { display: flex; flex-direction: column; align-items: flex-start; gap: var(--text-gap-md); }
.tmm-stack--tight { gap: var(--text-gap-sm); }
.tmm-stack--loose { gap: var(--text-gap-lg); }
.tmm-stack--center { align-items: center; text-align: center; }
.tmm-stack > * { margin: 0; }

/* ── Bleed ───────────────────────────────────────────────────────────────
 * The one way past the container. Every wrapper is centred, so the distance
 * from a block's edge to the screen is 50vw - 50% of its containing block;
 * main clips sideways (css/objects/_layout.css) so nothing scrolls. */
.tmm-bleed { width: 100vw; margin-inline: calc(50% - 50vw); }
.tmm-bleed--right { margin-right: calc(50% - 50vw); }
.tmm-bleed--left { margin-left: calc(50% - 50vw); }
/* Cards — the app card (SYSTEM.md §7), taken from the
 * app's CardComponent and its "Card edge" rule (application.tailwind.css,
 * 17 Aug): a solid surface, a 1px border in --card-ring, --shadow-card for
 * depth only, 20px radius (16 on a tile), 28px padding (24 on a tile).
 *
 * The edge is a BORDER, the app's exact mechanism, and the one place in the
 * theme a box is drawn with one (the `edge` lint allows exactly this rule).
 * The app tried three other ways first and each was beaten: a `0 0 0 1px`
 * shadow layer is not pixel-snapped and washed to 41% down the straight
 * sides; `outline-offset: -1px` was painted over by a sticky descendant;
 * an `inset` shadow sits under any full-bleed child (a picture, a table
 * row). A border is inside the border box (no ancestor clip reaches it) and
 * outside the padding box (no child can paint over it), and it rasterises
 * with the background. --card-ring is the tint hook: anything that wants to
 * highlight a card sets --card-ring and the card redraws its own edge, so
 * there is never a second ring beside it.
 *
 * Hover never moves a card. It is the app's link card (.ui-card-link): the
 * ring retints to --card-ring-hover and the float firms to
 * --card-shadow-hover. Every :hover is paired with .is-hover.
 *
 *   .card                 the card: --card-ground, --card-ring, --card-shadow, --card-padding
 *   .card--light/--dark   the ground restated (dark = a dark island anywhere)
 *   .card--island-dark    a dark island on a light band
 *   .card--brand          brand fill, on-brand ink
 *   .card--compact        24px padding, 16px radius (tiles)
 *   .card--media          a picture on top, the body under it (blog cards)
 *   .card--featured       the ring in the accent (the recommended tier)
 *   .card--static         no hover
 *   .card--plate          the sunken plate: a static well on the card edge
 *   .card--flat           square and flat: a card holding a section's content
 *   .card--stage          the dark stage on a light band (render_stage): the
 *                         dark island, static, --radius-xl, no padding, clipped
 *   .tmm-stage            the stage where the band is already dark (no card)
 *   .card--hover-lift / --hover-border / --hover-none
 *   .card-edge            the edge alone, for a module's own box (a picture,
 *                         a mat, an info box): the app's .ui-card-edge
 *
 * Output of templates/macros/cards.html -> render_card, and module markup. */

/* ── The card edge: one rule for every card-shaped box ───────────────────
 * The card, a picture drawn as a card, the half window, the form card, the
 * table card, the comparison table's raised column and the opt-in
 * .card-edge. Selector list, not a class to remember, as the app does
 * (.shadow-card, .chart-card, .ui-card-edge). box-sizing is border-box
 * (_reset.css), so the border costs no outer size. */
.card,
.tmm-image-box--card,
.tmm-img--card,
.tmm-image-box--card-top,
.tmm-img--card-top,
.tmm-img--window,
.tmm-form-wrap--default,
.tmm-form-wrap--flat,
.tmm-form-wrap--solid,
.tmm-form-wrap--glass,
.tmm-table-wrap,
.tmm-compare__plate,
.section--card,
.card-edge {
  border: 1px solid var(--card-ring);
}

.card {
  background: var(--card-ground);
  border-radius: var(--card-radius);
  box-shadow: var(--card-shadow);
  padding: var(--card-padding);
  color: var(--ink);
  transition: var(--transition-hover);
  /* A card keeps its edge and its own hairlines (a window's title bar, a ledger in a tier,
   * a post card's foot) under the band's Inner lines switch (_band.css 2): the switch is
   * the band's, the card's lines are the card's anatomy. initial is the guaranteed-invalid
   * value, so var(--inner-rule, var(--rule)) inside a card falls back to the rule. */
  --inner-rule: initial;
}
/* .card--light is a light island wherever it sits (white tiles on a dark
 * card): it restates the light variable set so everything inside reads the
 * light ladder. .card--dark and .card--island-dark resolve the dark set
 * (_band.css): the dark card has no shadow, its edge is the soft dark
 * rule (the app's dark card: #131315 on a #27272a border, two steps up). */
.card--light {
  --ink: var(--color-ink);
  --ink-body: var(--color-ink-2);
  --ink-2: var(--color-ink-2);
  --ink-muted: var(--color-ink-muted);
  --ink-faint: var(--color-ink-faint);
  --ground: var(--color-surface);
  --ground-raised: var(--color-surface);
  --ground-sunken: var(--color-paper);
  --rule: color-mix(in oklab, var(--color-ink) 18%, transparent);
  --rule-soft: var(--color-rule);
  --accent: var(--color-accent);
  --accent-wash: var(--color-accent-wash);
  --accent-ink: var(--color-accent-strong);
  --accent-fill: var(--color-accent);
  --accent-fill-hover: var(--color-accent-strong);
  --on-accent: var(--color-surface);
  --link: var(--color-accent);
  --link-hover: var(--color-accent-strong);
  --accent-text: var(--color-accent-strong);
  --ring: var(--shadow-hairline);
  --card-ground: var(--color-surface);
  --card-ring: var(--color-rule-strong);
  --card-ring-hover: color-mix(in oklab, var(--color-accent) 55%, transparent);
  --card-shadow: var(--shadow-card);
  --card-shadow-hover: var(--shadow-card-hover);
  --card-shadow-pop: var(--shadow-pop-depth);
  --field-ground: var(--color-surface);
  --danger: var(--color-danger);
  --state-tone: var(--ink);
}
/* A dark island sits on a light band as a card does: the card's depth under
 * its dark edge. On a dark band it steps up to the charcoal lifted by the
 * ink at 6% (#2D2B28) so it still outranks its peers (the island resolves
 * the dark set itself, so its own --ground is the dark, not the band's).
 * Hover is the state laid over the card's own ground. */
.card--island-dark { box-shadow: var(--shadow-card); }
.section--dark .card--island-dark,
.section--charcoal .card--island-dark { --card-ground: color-mix(in oklab, var(--color-dark-ink) 6%, var(--color-charcoal)); }
.card--dark:hover,
.card--dark.is-hover,
.card--island-dark:hover,
.card--island-dark.is-hover { background: color-mix(in oklab, var(--state-tone) var(--state-hover-amount), var(--card-ground)); }

/* The brand card: the on-brand ladder is _band.css's (shared with
 * .section--brand); this is its fill. Its edge is the on-brand rule, the
 * same hairline a card on the brand band wears. */
.card--brand {
  background: var(--color-accent);
  box-shadow: var(--shadow-card);
  color: var(--color-surface);
}
.card--brand:hover,
.card--brand.is-hover { background: var(--color-accent-strong); }

/* Card colours (card_tone, macros/cards.html; 2026-10-13, the CTA's Card
 * colour): paper on the light island, the dark and charcoal grounds on the
 * dark island, so a card can be any of the band colours whatever band it sits
 * on. Two classes (0,2,0) so they outrank the dark band's island step. */
.card.card--tone-paper { --ground: var(--color-paper); --ground-sunken: var(--color-canvas); --card-ground: var(--color-paper); }
.card.card--tone-dark { --ground: var(--color-dark); --card-ground: var(--color-dark); }
.card.card--tone-charcoal { --ground: var(--color-charcoal); --card-ground: var(--color-charcoal); }

/* Hover variants. Lift = the app's link card (the ring retints, the float
 * firms); border = the ring retints alone; none = the resting card. */
.card--hover-lift:hover,
.card--hover-lift.is-hover { --card-ring: var(--card-ring-hover); box-shadow: var(--card-shadow-hover); }
.card--hover-border:hover,
.card--hover-border.is-hover { --card-ring: var(--card-ring-hover); }
.card--island-dark.card--hover-lift:hover,
.card--island-dark.card--hover-lift.is-hover,
.card--brand.card--hover-lift:hover,
.card--brand.card--hover-lift.is-hover { box-shadow: var(--shadow-card-hover); }
.card--hover-none:hover,
.card--hover-none.is-hover { background: var(--card-ground); box-shadow: var(--card-shadow); }
.card--island-dark.card--hover-none:hover,
.card--island-dark.card--hover-none.is-hover,
.card--brand.card--hover-none:hover,
.card--brand.card--hover-none.is-hover { box-shadow: var(--shadow-card); }
.card--brand.card--hover-none:hover,
.card--brand.card--hover-none.is-hover { background: var(--color-accent); }

/* Static: nothing changes under the cursor. */
.card.card--static,
.card.card--static:hover,
.card.card--static.is-hover { transition: none; }
.card--static:hover,
.card--static.is-hover { background: var(--card-ground); box-shadow: var(--card-shadow); }
.card--island-dark.card--static:hover,
.card--island-dark.card--static.is-hover { box-shadow: var(--shadow-card); }
.card--brand.card--static:hover,
.card--brand.card--static.is-hover { background: var(--color-accent); box-shadow: var(--shadow-card); }

/* Featured: the card's own ring in the accent (the recommended pricing
 * tier). The ring is retinted, never doubled. */
.card--featured,
.card--featured:hover,
.card--featured.is-hover { --card-ring: var(--accent); }

/* Plate: a recessed well, for an info box, a meta panel, a pager or a mat
 * (blog-info-box, case-study-meta, the tile grids' mat). The card's edge
 * and no depth: it sits in the surface, not on it. Static: it is a surface,
 * not a target, so nothing changes on hover. */
.card--plate,
.card--plate:hover,
.card--plate.is-hover {
  --card-ground: var(--ground-sunken);
  --card-padding: 24px;
  background: var(--ground-sunken);
  box-shadow: none;
  border-radius: var(--card-radius-sm);
  transition: none;
}

/* Square: a card held square in a reading column, flat on its ground, its
 * padding on the --text-gap-lg step (28, 40 wide). The blog CTA's brand
 * slab and its plate with the mark (Rowan, 2026-10-09). On .card--brand
 * the edge takes the fill, so the slab is the blue alone. */
.card.card--square,
.card.card--square:hover,
.card.card--square.is-hover { --card-padding: var(--text-gap-lg); padding: var(--card-padding); border-radius: 0; box-shadow: none; }
.card--brand.card--square { --card-ring: var(--ground); }

/* Stage: the one way to draw a dark scene (the conveyor's belts, a product
 * demo) on any ground: "island or slab, never a card in a card" (2026-10-12).
 * render_stage (macros/section.html) picks the box: on a light, white or
 * paper band with no slab, this dark island (the markup carries
 * .card--island-dark and .card--static too, so it takes their ladder, depth
 * and stillness); in a slab (the conveyor's alone, 2026-10-13) the band itself turns dark (stage_band_class)
 * and on a dark band the stage is the bare .tmm-stage. The scene inside
 * owns its padding; the island clips it to the xl radius. */
.card.card--stage {
  --card-padding: 0;
  padding: 0;
  border-radius: var(--radius-xl);
  overflow: hidden;
}
.tmm-stage { position: relative; min-width: 0; }

/* Mat: a plate that holds a ruled grid edge to edge (the icon tile grids'
 * mat, Rowan 2026-10-08): no padding, the grid's cells carry it; the
 * plate's radius clips the cells. Markup: .card.card--plate.tmm-mat >
 * .section__grid--ruled.section__grid--mat (_layouts.css). */
.card.tmm-mat { --card-padding: 0; padding: 0; overflow: hidden; }

/* Flat: a card that holds a section's whole content (feature-showcase's
 * On a card, security's Card, the hardware runway's island) sits square
 * and flat on the band: the ring at square corners, no depth (Rowan,
 * 2026-10-13: "we won't be using these rounded corner outer cards
 * anymore", the conveyor's slab alone keeps them). Small cards in a grid
 * and pictures keep their radius. After the stage and the mat, so it
 * outranks their radius. */
.card.card--flat,
.card.card--flat.card--flat:hover,
.card.card--flat.card--flat.is-hover { border-radius: 0; box-shadow: none; }

/* Compact: tiles. The 16px radius sits inside a 20px parent; 24px padding,
 * the app's chart-card body (p-6). */
.card--compact { --card-padding: 24px; border-radius: var(--card-radius-sm); }

/* Media card: the picture clips to the top corners, the body sits under it
 * with the card's padding. Markup: .card.card--media > .card__media + .card__body-wrap */
.card--media { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.card--media > .card__media { display: block; }
.card--media > .card__media .tmm-img,
.card--media > .card__media .tmm-image-box { border-radius: 0; }
.card--media > .card__body-wrap { padding: var(--card-padding); display: flex; flex-direction: column; gap: var(--text-gap-sm); flex: 1 1 auto; }

/* Inset media (blog-card-group cells, Rowan 2026-10-08): a media card with
 * no card around it, the post drawn in a ruled cell. The picture sits inside
 * the cell's padding and keeps the control radius at its corners.
 * Markup: .card__media.card__media--inset > .tmm-image-box */
.card__media--inset .tmm-image-box { border-radius: var(--radius-md); }

/* Card internals. Title inherits the ink; body is the reading ink. */
.card__title { margin: 0 0 12px; color: var(--ink); }
.card__title--with-icon { margin-top: 16px; }
.card__body { color: var(--ink-body); }

/* ── The post card (render_blog_card, macros/cards.html) ────────────────
 * The decided blog card (Rowan, 2026-10-09), shared by the blog-card-group
 * module and the resources listing: a column whose title is the post's
 * link, a 1px underline in the rule with the chevron held to the last word,
 * its ::after covering the card. Hover (the whole card, picture included)
 * and the link's focus turn the title and its chevron the band's brand blue,
 * --link (Rowan, 2026-10-09: colour, not the underline, which stays in the
 * rule); under .blog-card-group--accent the title rests in --accent-text
 * (small accent text, 2026-10-09) and goes to --accent-ink. In cells (.blog-card-group--cells) the cell carries the
 * padding. The rows ledger is the module's own layout (blog-card-group
 * module.css). */
.blog-card-group__card { --bcg-hover: var(--link); position: relative; display: flex; flex-direction: column; }
.blog-card-group--accent .blog-card-group__card { --bcg-hover: var(--accent-ink); }
.blog-card-group__body { flex: 1 1 auto; display: flex; flex-direction: column; gap: var(--text-gap-xs); }
/* On phones the cells are one column: no side padding, so the picture, the
 * title and the row hairlines hold the heading's left edge (the module and
 * the Resources listing alike); side by side from 768 they take it back. */
.blog-card-group--cells .blog-card-group__card { --cell-pad: 28px 0; gap: var(--text-gap-sm); }
@media (min-width: 768px) {
  .blog-card-group--cells .blog-card-group__card { --cell-pad: 28px 20px; }
}

/* title_link: number. The post's number left, the date right. */
.blog-card-group__top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.blog-card-group__date { color: var(--ink-2); }

/* Unclamped, so a long title never cuts its chevron off. */
.blog-card-group__link { text-decoration-line: underline; text-decoration-thickness: 1px; text-decoration-color: var(--rule); text-underline-offset: 5px; transition: var(--transition-hover); }
/* Chained past the global a:hover / a:focus accent (0,3,1): the title rests
 * in its ink (or --link under accent) and takes --bcg-hover with the card. */
.blog-card-group .blog-card-group__card .blog-card-group__title a.blog-card-group__link { color: inherit; }
.blog-card-group--accent .blog-card-group__card .blog-card-group__title a.blog-card-group__link { color: var(--accent-text); }
.blog-card-group .blog-card-group__card:hover .blog-card-group__title a.blog-card-group__link,
.blog-card-group .blog-card-group__card.is-hover .blog-card-group__title a.blog-card-group__link,
.blog-card-group .blog-card-group__card .blog-card-group__title a.blog-card-group__link:focus-visible,
.blog-card-group .blog-card-group__card .blog-card-group__title a.blog-card-group__link.is-focus { color: var(--bcg-hover); }
.blog-card-group__link::after { content: ''; position: absolute; inset: 0; }
.blog-card-group__last { white-space: nowrap; }
.blog-card-group__chev { display: inline-block; margin-left: 6px; vertical-align: -1px; line-height: 0; color: var(--ink-2); transition: var(--transition-hover); }
.blog-card-group__chev svg { display: block; }
.blog-card-group__card:hover .blog-card-group__chev,
.blog-card-group__card.is-hover .blog-card-group__chev,
.blog-card-group__card:has(.blog-card-group__link:focus-visible) .blog-card-group__chev,
.blog-card-group__card:has(.blog-card-group__link.is-focus) .blog-card-group__chev { color: var(--bcg-hover); }

/* The byline (Rowan, 2026-10-13): render_person at the caption size at the card's foot. margin-top: auto
 * puts it on the cell's bottom, so the bylines of a row sit level; the body's gap sits above it.
 * With the photo off it is the name over the date, flush left.
 * A hairline in the rule ink (as the featured post's meta) over it (Rowan, 2026-10-13: "a little border above the author and date"), 12 under the rule. */
.blog-card-group__foot { margin-top: auto; padding-top: 12px; border-top: 1px solid var(--rule); }
.blog-card-group__excerpt { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
/* Lists — the feature list, the ledger, the slashed words, the accordion
 * (SYSTEM.md §14, §6). Output of templates/macros/lists.html ->
 * render_feature_list / render_slash_list and the faq module's accordion.
 * render_icon_list (.tmm-icon-list), the tile it loops (render_tile,
 * .tmm-tile), the figures ledger (render_ledger, .tmm-ledger) and the index
 * numeral (.tmm-index-num) live here too. Colour comes from the theme variables; nothing here
 * restates a ground.
 *
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

/* ── Feature list (check) ─────────────────────────────────────────────────
 * Rows of a tick and a claim: the plain TMM check at 18 in a 20px box, in
 * the band's accent, no box, no circle, no hover (Rowan, 2026-10-14; the
 * square of 2026-10-09 retired). The box is centred on the row's first line
 * box: (1lh - 20px) / 2 from the row's own line height. The one check on
 * the site: this list, .tmm-check-list (an editor's rich text list) and
 * render_check (one tick alone); the check-mark lint counts any other. */
.tmm-feature-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.tmm-feature-list__item {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  font-family: var(--font-sans);
  font-size: var(--type-dense);
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink-body);
}
.tmm-feature-list__icon,
.tmm-check {
  /* A plain tick, no box (Rowan, 2026-10-14: a boxed tick that reacts to hover
   * read as a checkbox to click): the TMM check, in the band's accent. The
   * same tick alone is .tmm-check (render_check, macros/markers.html). */
  width: 20px;
  height: 20px;
  margin-top: calc((1lh - 20px) / 2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--accent);
}
.tmm-feature-list__icon svg,
.tmm-check svg { width: 18px; height: 18px; }

/* A divider row: a soft rule between groups of claims. */
.tmm-feature-list__item--divider {
  display: block;
  padding: 0;
  margin: 0;
  height: 1px;
  background: var(--inner-rule, var(--rule-soft));
}

/* Large: the body-lg voice (the Body Text = Large toggle). */
.tmm-feature-list--lg .tmm-feature-list__item { font-size: var(--type-callout); line-height: 1.6; }

/* ── Ledger ─────────────────────────────────────────────────────────────
 * Each claim on a rule, numbered 01 02 03 in the accent; the check plate is
 * not rendered. Rules are drawn (not borders): the list's top rule and each
 * row's bottom rule. */
.tmm-feature-list--ledger {
  gap: 0;
  counter-reset: ledger;
  position: relative;
  padding-top: 1px;
}
.tmm-feature-list--ledger::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 1px;
  background: var(--inner-rule, var(--rule));
}
.tmm-feature-list--ledger .tmm-feature-list__item {
  position: relative;
  gap: 0;
  padding: 14px 0;
  max-width: 480px;
}
.tmm-feature-list--ledger .tmm-feature-list__item::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--inner-rule, var(--rule));
}
.tmm-feature-list--ledger .tmm-feature-list__item::before {
  counter-increment: ledger;
  content: '0' counter(ledger);
  flex: none;
  width: 36px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: var(--type-micro);
  line-height: inherit;
  color: var(--accent-text);
}
.tmm-feature-list--ledger .tmm-feature-list__item--divider { display: none; }
.tmm-feature-list--ledger.tmm-feature-list--wide .tmm-feature-list__item { max-width: none; }

/* ── Index numeral ──────────────────────────────────────────────────────
 * The ledger's "01" as a class, for a numbered tile, step or cert written
 * in markup (security, deployment-steps). The caller prints the figure.
 *   .tmm-index-num          the accent figure at the label size, tabular,
 *                           in --accent-text (--lg in --accent)
 *   .tmm-index-num--lg      the same at the stat size (a ledger's margin)
 *   .tmm-index-num--ghost   the large faint numeral behind a card's corner;
 *                           decorative, so aria-hidden; the caller places it */
.tmm-index-num {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: var(--type-micro);
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--accent-text);
}
.tmm-index-num--lg { font-size: var(--type-stat); font-weight: 650; line-height: 1; letter-spacing: -0.02em; color: var(--accent); }
.tmm-index-num--ghost {
  font-size: var(--type-display);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--rule-soft);
  pointer-events: none;
  user-select: none;
}

/* ── Slashed words ──────────────────────────────────────────────────────
 * Points as words in a row, each led by the brand's slash. Works on the
 * macro's ul and on an editor richtext ul inside a .tmm-slash-list wrapper. */
.tmm-slash-list ul,
ul.tmm-slash-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 14px 36px;
}
.tmm-slash-list li {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-dense);
  font-weight: 600;
  line-height: 1.4;
  color: var(--ink);
}
/* Text size Large (.section--text-large, _typography.css): the points step up one with the
 * band's body, the dense size to the reading voice. */
.section--text-large .tmm-slash-list li { font-size: var(--type-callout); }
.tmm-slash-list li::before {
  content: '';
  flex: none;
  width: 2px;
  height: 14px;
  background: var(--accent);
  transform: skewX(-22deg);
  transform-origin: left bottom;
}

/* ── Check list (richtext) ──────────────────────────────────────────────
 * An editor richtext list inside a .tmm-check-list wrapper, each item led by
 * the feature list's plain tick (above): ::before the TMM check as a mask in
 * the band's accent, centred on the first line. */
.tmm-check-list ul,
.tmm-check-list ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.tmm-check-list li {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-dense);
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink-body);
}
.tmm-check-list li::before {
  /* The TMM check (the lookbook's icon set), plain, in the band's accent: no box,
   * no hover, so it never reads as a checkbox (Rowan, 2026-10-14). */
  content: '';
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  margin-top: calc((1lh - 20px) / 2);
  background-color: var(--accent);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4.6 12.6 4.8 4.8L19.4 7.4'/%3E%3C/svg%3E") center / 18px 18px no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4.6 12.6 4.8 4.8L19.4 7.4'/%3E%3C/svg%3E") center / 18px 18px no-repeat;
}

/* ── Accordion ──────────────────────────────────────────────────────────
 * Rows on rules (Rowan, 2026-10-08): soft rules between the items, the
 * band's rule top and bottom when --ruled. The trigger is the row itself,
 * full width and square, with no fill: a question hovers by lifting its ink
 * (ink-2 to ink, and it stays lifted while open) and turning its glyph or
 * numeral accent, on var(--transition-hover). Focus is the same plus the
 * focus ring. Open/close rides the app's one pop curve.
 *   .tmm-accordion__num            the ledger's numeral, accent, body size
 *   .tmm-accordion__num--control   the large numeral as the control: faint at
 *                                  rest, accent on hover / focus / open
 *   .tmm-accordion__glyph--plus    plus closed, minus open (both printed)
 *   .tmm-accordion--edge           the item rules run out to the page gutter
 *   --accordion-indent / -end      the answer's indent under a numeral column */
.tmm-accordion {
  display: flex;
  flex-direction: column;
  gap: 0;
}
.tmm-accordion--ruled .tmm-accordion__item { --accordion-rule: var(--inner-rule, var(--rule)); }
.tmm-accordion__item {
  --accordion-rule: var(--inner-rule, var(--rule-soft));
  position: relative;
}
.tmm-accordion__item::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--accordion-rule);
}
.tmm-accordion__item:last-child::after { display: none; }
.tmm-accordion--ruled .tmm-accordion__item:last-child::after,
.tmm-accordion--edge .tmm-accordion__item:last-child::after { display: block; background: var(--inner-rule-bottom, var(--accordion-rule)); }
/* The first and last rules are the list's own lines across its top and foot: the band's Line inside
 * toggles blank them (--inner-rule-top / --inner-rule-bottom, _band.css). */
.tmm-accordion--ruled .tmm-accordion__item:first-child::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 1px;
  background: var(--inner-rule-top, var(--accordion-rule));
}
.tmm-accordion--edge .tmm-accordion__item::after { left: calc(-1 * var(--gutter)); right: calc(-1 * var(--gutter)); }

.tmm-accordion__trigger {
  position: relative;
  width: 100%;
  margin: 0;
  background: none;
  border: none;
  border-radius: 0;
  padding: 24px 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  cursor: pointer;
  color: var(--ink-2);
  text-align: left;
  font-family: var(--font-sans);
  transition: var(--transition-hover);
}
.tmm-accordion__trigger:focus-visible,
.tmm-accordion__trigger.is-focus { outline: none; box-shadow: var(--shadow-focus-strong); }

/* The question: the reading size at 500, tighter leading for a single line;
 * ink-2 at rest, ink on hover, focus and while open. */
.tmm-accordion__question {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink-2);
  transition: var(--transition-hover);
}
.tmm-accordion__item.is-open > .tmm-accordion__trigger .tmm-accordion__question,
.tmm-accordion__trigger:hover .tmm-accordion__question,
.tmm-accordion__trigger.is-hover .tmm-accordion__question,
.tmm-accordion__trigger:focus-visible .tmm-accordion__question,
.tmm-accordion__trigger.is-focus .tmm-accordion__question { color: var(--ink); }

.tmm-accordion__chevron {
  flex-shrink: 0;
  color: var(--ink-muted);
  transition: var(--transition-hover), transform var(--pop-duration) var(--pop-ease);
}
.tmm-accordion__item.is-open .tmm-accordion__chevron { transform: rotate(180deg); }
.tmm-accordion__trigger:hover .tmm-accordion__chevron,
.tmm-accordion__trigger.is-hover .tmm-accordion__chevron,
.tmm-accordion__trigger:focus-visible .tmm-accordion__chevron,
.tmm-accordion__trigger.is-focus .tmm-accordion__chevron { color: var(--accent); }

/* The ledger's numeral, and the large numeral that is the control. */
.tmm-accordion__num {
  flex-shrink: 0;
  font-size: var(--type-dense);
  line-height: 1.43;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--accent-text);
  transition: var(--transition-hover);
}
.tmm-accordion__num--control { flex-shrink: 0; color: var(--ink-faint); transition: var(--transition-hover); }
.tmm-accordion__item.is-open > .tmm-accordion__trigger .tmm-accordion__num--control,
.tmm-accordion__trigger:hover .tmm-accordion__num--control,
.tmm-accordion__trigger.is-hover .tmm-accordion__num--control,
.tmm-accordion__trigger:focus-visible .tmm-accordion__num--control,
.tmm-accordion__trigger.is-focus .tmm-accordion__num--control { color: var(--accent); }

/* Plus closed, minus open: both glyphs are printed, the state shows one. */
.tmm-accordion__glyph { display: flex; flex-shrink: 0; }
.tmm-accordion__glyph--plus .tmm-accordion__chevron,
.tmm-accordion__item.is-open .tmm-accordion__glyph--plus .tmm-accordion__chevron { transform: none; }
.tmm-accordion__glyph--plus .tmm-accordion__minus,
.tmm-accordion__item.is-open .tmm-accordion__glyph--plus .tmm-accordion__plus { display: none; }
.tmm-accordion__item.is-open .tmm-accordion__glyph--plus .tmm-accordion__minus { display: block; }

.tmm-accordion__panel {
  padding-left: var(--accordion-indent, 0px);
  padding-right: var(--accordion-indent-end, 0px);
  max-height: 0;
  overflow: hidden;
  transition: max-height var(--pop-duration) var(--pop-ease);
}
.tmm-accordion__item.is-open .tmm-accordion__panel { max-height: 600px; }

.tmm-accordion__answer {
  padding-bottom: 24px;
  line-height: 1.5;
  color: var(--ink-body);
}
.tmm-accordion__answer p { margin: 0; }
.tmm-accordion__answer p + p { margin-top: var(--text-gap-sm); }
/* Answer links take the ground's --link-hover (4.5 : 1 on every light ground;
 * --link is 4.03 to 4.17), the same as .blog-prose; hover to the ink. */
.tmm-accordion__answer a { color: var(--link-hover); text-decoration: underline; text-underline-offset: 2px; transition: var(--transition-hover); }
.tmm-accordion__answer a:hover,
.tmm-accordion__answer a.is-hover,
.tmm-accordion__answer a:focus-visible,
.tmm-accordion__answer a.is-focus { color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  .tmm-accordion__trigger,
  .tmm-accordion__question,
  .tmm-accordion__num,
  .tmm-accordion__num--control,
  .tmm-accordion__chevron,
  .tmm-accordion__panel { transition: none; }
}

/* ── The tile (render_tile, macros/cards.html; 2026-10-12) ────────────────
 * One glyph, a title and a description on the stack rhythm: the glyph
 * (render_icon_box, the muted glyph by default) --text-gap-sm over the
 * title, the description --text-gap-xs under it. No card: the tile sits on
 * its grid's cells or beads, or alone. With an href the title is the link
 * and its ::after covers the tile; the tile's hover (and the link's focus)
 * turns the title --link and lifts the muted glyph to the ink, colour only.
 * Markup: div.tmm-tile > [.section__bead >] .icon-box
 *           + div.tmm-tile__copy > hN.tmm-tile__title + div.tmm-tile__body
 * render_icon_list loops it as li.tmm-tile.tmm-icon-list__item, so the
 * icon list's rules below set its layout inside a list. */
.tmm-tile { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: var(--text-gap-sm); min-width: 0; }
.tmm-tile__copy { display: flex; flex-direction: column; gap: var(--text-gap-xs); min-width: 0; }
.tmm-tile__body { margin: 0; }
.tmm-tile__body > p { margin: 0; font: inherit; color: inherit; }
.tmm-tile__link { color: inherit; text-decoration: none; transition: var(--transition-hover); }
.tmm-tile__link::after { content: ''; position: absolute; inset: 0; }
.tmm-tile .tmm-tile__title a.tmm-tile__link { color: inherit; }
.tmm-tile:hover .tmm-tile__title a.tmm-tile__link,
.tmm-tile.is-hover .tmm-tile__title a.tmm-tile__link,
.tmm-tile .tmm-tile__title a.tmm-tile__link:focus-visible,
.tmm-tile .tmm-tile__title a.tmm-tile__link.is-focus { color: var(--link); }
.tmm-tile:hover > .icon-box--muted,
.tmm-tile.is-hover > .icon-box--muted,
.tmm-tile:hover > .section__bead > .icon-box--muted,
.tmm-tile.is-hover > .section__bead > .icon-box--muted { color: var(--ink); }

/* ── Icon list ──────────────────────────────────────────────────────────
 * An icon plate, a title and a description per item (render_icon_list), for
 * the gated form's points, the blog feature list, the security claims. A
 * vertical list (plate beside the copy) or a grid on .section__grid--N
 * (plate above the copy). Markup: ul.tmm-icon-list > li.tmm-icon-list__item
 *   > .icon-box + div.tmm-icon-list__copy > .tmm-icon-list__title + __desc */
.tmm-icon-list { list-style: none; margin: 0; padding: 0; }
.tmm-icon-list--stack { display: flex; flex-direction: column; gap: var(--text-gap-md); }
.tmm-icon-list__item { display: flex; align-items: flex-start; gap: 16px; margin: 0; min-width: 0; }
.tmm-icon-list--stack .tmm-icon-list__item { flex-direction: row; }
.tmm-icon-list--grid .tmm-icon-list__item { flex-direction: column; gap: var(--text-gap-sm); }
/* In beads the grid keeps the 24px the plates rise into over the first rule. */
.tmm-icon-list.section__grid--beads { padding-top: 24px; }
.tmm-icon-list__copy { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
/* The muted glyph (render_icon_box 'muted', the default) and the bare glyph
 * on a rule ('rule') beside the copy, always the md box (muted: the 24px
 * glyph, 24px in all; rule: the tick 8px under it, 33px in all): the
 * glyph's top on the title's cap top (Rowan, 2026-10-09: never higher than
 * the text beside it), so the glyph runs past the baseline and the tick sits
 * under the line. The cap top in a line box is line / 2 - 0.354 x the font
 * size for Hanken Grotesk (ascent 1, descent .29, cap .71, measured in the
 * browser). --icon-list-line / --icon-list-size are the title's line and
 * size: the callout title 24px / --type-callout, .t-card-title (lg, card)
 * 32px / --type-title. */
.tmm-icon-list { --icon-list-line: 24px; --icon-list-size: var(--type-callout); }
.tmm-icon-list--lg,
.tmm-icon-list--card { --icon-list-line: 32px; --icon-list-size: var(--type-title); }
.tmm-icon-list--stack .tmm-icon-list__item > .icon-box--muted,
.tmm-icon-list--stack .tmm-icon-list__item > .icon-box--rule { margin-top: calc(var(--icon-list-line) / 2 - 0.354 * var(--icon-list-size)); }
/* The muted glyph lifts to the ink while its row is hovered, colour only. */
.tmm-icon-list__item:hover > .icon-box--muted,
.tmm-icon-list__item.is-hover > .icon-box--muted,
.tmm-icon-list__item:hover > .section__bead > .icon-box--muted,
.tmm-icon-list__item.is-hover > .section__bead > .icon-box--muted { color: var(--ink); }
.tmm-icon-list__title {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-callout);
  font-weight: 650;
  line-height: 24px;
  color: var(--ink);
}
.tmm-icon-list--lg .tmm-icon-list__copy,
.tmm-icon-list--card .tmm-icon-list__copy { gap: 8px; }
.tmm-icon-list__desc { margin: 0; }
.tmm-icon-list__desc > p { margin: 0; font: inherit; color: inherit; }

/* ── The figures ledger (render_ledger; 2026-10-12) ──────────────────────
 * Rows of a label and its figure on hairlines: the label left in the ink, a
 * note under it in the caption ink, the value right in tabular figures. The
 * first and last rules are the band's --rule, the rules between rows the
 * soft rule (SYSTEM.md §6 › Drawing lines). A breakdown, a quote's lines, the
 * payback's sums. Markup:
 *   dl.tmm-ledger > div.tmm-ledger__row > dt.tmm-ledger__label
 *     + dd.tmm-ledger__value + dd.tmm-ledger__note */
.tmm-ledger { position: relative; margin: 0; padding: 0; }
.tmm-ledger::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--inner-rule, var(--rule)); pointer-events: none; }
.tmm-ledger__row {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: var(--text-gap-md);
  row-gap: 2px;
  align-items: baseline;
  padding-block: 14px;
}
.tmm-ledger__row::before { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: var(--inner-rule, var(--rule-soft)); pointer-events: none; }
.tmm-ledger__row:first-child::before { background: var(--inner-rule, var(--rule)); }
.tmm-ledger .tmm-ledger__label { grid-column: 1; grid-row: 1; margin: 0; color: var(--ink); font-weight: 500; }
.tmm-ledger .tmm-ledger__value { grid-column: 2; grid-row: 1; margin: 0; color: var(--ink); font-weight: 650; font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
.tmm-ledger .tmm-ledger__note { grid-column: 1; grid-row: 2; margin: 0; color: var(--ink-2); }
/* A glyph before the label (row.icon): 16 in the muted ink, 12 to the label.
 * A linked row (row.href): the label is the link, its ::after covers the
 * row; the row's hover (and the link's focus) turns the label --link, colour
 * only. A foot (row.foot) runs the row's width under the note. */
.tmm-ledger__icon { display: inline-block; flex: none; margin-right: 12px; vertical-align: -2px; color: var(--ink-muted); }
.tmm-ledger__link { color: inherit; text-decoration: none; transition: var(--transition-hover); }
.tmm-ledger__link::after { content: ''; position: absolute; inset: 0; }
.tmm-ledger__row:hover .tmm-ledger__link,
.tmm-ledger__row.is-hover .tmm-ledger__link,
.tmm-ledger__link:focus-visible,
.tmm-ledger__link.is-focus { color: var(--link); }
/* A linked row's chevron, given as the value (roles' Ledger, integrations'
 * before it): the muted ink at rest, the accent on the row's hover. */
.tmm-ledger__go { display: flex; color: var(--ink-muted); transition: var(--transition-hover); }
.tmm-ledger__row:hover .tmm-ledger__go,
.tmm-ledger__row.is-hover .tmm-ledger__go { color: var(--accent); }
.tmm-ledger .tmm-ledger__foot { grid-column: 1 / -1; margin: var(--text-gap-sm) 0 0; }
.tmm-ledger__foot > :first-child { margin-top: 0; }
/* The titled ledger (render_ledger title_level): the same rows as a list, each
 * label a real heading; the heading's own tracking gives way to the label's. */
.tmm-ledger--titled { list-style: none; }
.tmm-ledger--titled .tmm-ledger__label { letter-spacing: inherit; }
/* Pictures — the image wrapper and the image box (SYSTEM.md §14).
 * Output of templates/macros/images.html -> render_image / render_image_box.
 *
 *   .tmm-img                the wrapper render_image emits; fills its parent
 *     --cover               the picture fills, cropping to the box (default)
 *     --contain             the wrapper hugs the picture's own aspect
 *     --native              the picture dictates the wrapper's aspect, whole
 *   .tmm-img--window        the half window: a screenshot under a 24px bar of
 *                           three dots (render_image's class, fit native)
 *   .tmm-image-box          a fixed box: the editor's aspect, cover or contain,
 *                           the crop position, the mat behind a contained
 *                           picture, a placeholder when there is no picture;
 *                           --fill takes the parent's height instead (the
 *                           bleeding split layouts)
 *
 * A picture is bare: no radius, no shadow, no frame. The caller adds a
 * radius when the picture is a card's top (.card--media).
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-img {
  display: block;
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
.tmm-img img {
  display: block;
  width: 100%;
  height: 100%;
}
.tmm-img--cover img { object-fit: cover; }

/* Contain: the wrapper adopts the picture's aspect (set inline as --img-aspect)
 * and caps at the parent's height, so there is no letterbox and the result is
 * the same in every engine (width pinned, height derived). The picture itself
 * is contained too: when the stored size is missing or stale (no
 * --img-aspect, so the 16 / 10 fallback) or a caller fixes the wrapper's
 * shape, the whole picture still shows, letterboxed, never cropped (Rowan,
 * 2026-10-13: "contain doesn't work"). */
.tmm-img--contain {
  width: 100%;
  height: auto;
  max-height: 100%;
  aspect-ratio: var(--img-aspect, 16 / 10);
  margin-inline: auto;
}
.tmm-img--contain img { object-fit: contain; }

.tmm-img--native { height: auto; aspect-ratio: var(--img-aspect, 16 / 10); }
.tmm-img--native img { object-fit: contain; }

/* ── The image box ─────────────────────────────────────────────────────── */
.tmm-image-box {
  position: relative;
  width: 100%;
  aspect-ratio: var(--image-box-aspect, 4 / 3);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.tmm-image-box > .tmm-img { width: 100%; height: 100%; }

/* Contain: the whole picture inside the box on the sunken mat. */
.tmm-image-box--contain { background: var(--ground-sunken); }
.tmm-image-box--contain > .tmm-img { width: auto; height: auto; max-width: 100%; max-height: 100%; }

/* Fill: the parent's height is the box (a bleeding split column). */
.tmm-image-box--fill { aspect-ratio: auto; height: 100%; min-height: 100%; }
/* Fill and contain: the whole picture centred in the column, on the band's
 * own ground (no mat: the column is the band, not a box). Stacked, the
 * wrapper already takes the picture's aspect; side by side it is pinned to
 * the column (_layouts.css) and the picture fits inside it. */
.tmm-image-box--fill.tmm-image-box--contain { background: none; }
.tmm-image-box--fill.tmm-image-box--contain .tmm-img img { object-fit: contain; }

/* No picture yet: the mat holds the footprint. */
.tmm-image-box__placeholder { width: 100%; height: 100%; background: var(--ground-sunken); }

/* A picture as a card: the card radius, the card edge (the border in
 * --card-ring, _cards.css) and the pop depth (a hero's picture, a panel's
 * screenshot). --card-top rounds the top corners only, for a picture that
 * runs into the next band, and drops the bottom edge where it is cut. */
.tmm-image-box--card,
.tmm-img--card { border-radius: var(--card-radius); box-shadow: var(--card-shadow-pop); overflow: hidden; }
.tmm-image-box--card-top,
.tmm-img--card-top { border-radius: var(--card-radius) var(--card-radius) 0 0; border-bottom-width: 0; box-shadow: var(--card-shadow-pop); overflow: hidden; }

/* A picture card that is one control (the video's poster, Rowan, 2026-10-09:
 * the ring and the corner label). It rests on the card's own depth and takes
 * the pop depth under the cursor or with its control's focus; the control is
 * render_icon_button 'picture' 'xl' with .btn--stretch (_buttons.css), whose
 * ::after covers the card, and the card carries .btn-host so its hover
 * reaches the disc. The picture itself never changes: no lift, no wash.
 * __caption is a .card--plate in the bottom-left corner, invisible at rest
 * and shown with the hover or the focus (opacity only); __caption-short
 * stands in for it under 640, where the whole line would meet the disc's
 * focus ring on a 155px card. */
.tmm-image-box--target { box-shadow: var(--card-shadow); transition: var(--transition-hover); }
.tmm-image-box--target:hover,
.tmm-image-box--target.is-hover,
.tmm-image-box--target:has(:focus-visible),
.tmm-image-box--target:has(.is-focus) { box-shadow: var(--card-shadow-pop); }
.tmm-image-box__caption {
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: 3;
  padding: 5px 10px;
  color: var(--ink);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--transition-pop);
}
.tmm-image-box__caption-long { display: none; }
@media (min-width: 640px) {
  .tmm-image-box__caption { left: 24px; bottom: 24px; padding: 10px 14px; }
  .tmm-image-box__caption-short { display: none; }
  .tmm-image-box__caption-long { display: inline; }
}
.tmm-image-box--target:hover .tmm-image-box__caption,
.tmm-image-box--target.is-hover .tmm-image-box__caption,
.tmm-image-box--target:has(:focus-visible) .tmm-image-box__caption,
.tmm-image-box--target:has(.is-focus) .tmm-image-box__caption { opacity: 1; }

/* ── The half window ───────────────────────────────────────────────────
 * A flat screenshot drawn as an app window cut in half (platform tabs,
 * Rowan, 2026-10-08): a 24px bar on the raised ground carrying three 6px
 * dots in the faint ink, one rule under it, the top corners at the control
 * radius, the bottom square so it stands on a rule. The edge is the card's
 * border in --card-ring (_cards.css), outside the padding box, so the
 * picture can never paint over it; the bar is 23px inside it (24 outside).
 * On render_image's wrapper with fit native:
 *   render_image(image, 'native', '', 'tmm-img--window')
 * The dots are one pseudo-element and its two shadow copies. Not combined
 * with a .tmm-treat (both draw on ::after); not for a device render that
 * carries its own chrome. */
.tmm-img--window {
  aspect-ratio: auto;
  padding-top: 23px;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  background: var(--ground-raised);
}
.tmm-img--window img { height: auto; box-shadow: 0 -1px 0 var(--rule); }
.tmm-img--window::before {
  content: '';
  position: absolute;
  top: 8px;
  left: 9px;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--ink-faint);
  box-shadow: 12px 0 0 var(--ink-faint), 24px 0 0 var(--ink-faint);
}

/* ── Treatments ─────────────────────────────────────────────────────────
 * A photograph can carry the brand. Duotone maps its whole tonal range into
 * two blues: the picture is greyed and screened onto the deep blue, so the
 * shadows take that colour, then the primary is multiplied over the result,
 * so the highlights land on the primary instead of running to white. That
 * clamp is the point: a bright photograph treated by luminosity alone keeps
 * its own brightness and comes out white with a tint, whatever the hue
 * underneath. The range here is a deep navy through to #1a73e8 on any
 * source. --duotone-hatch adds the brand's diagonals over it at a 5px pitch
 * (lookbook: imagery, flourishes, "White hatch on duotone", refinement 1,
 * chosen 2026-10-05).
 *
 * The carrier must establish a position (every picture wrapper here does);
 * this adds the stacking context so the blending cannot reach the page.
 * The two blend modes are the one piece of the retired decorative layer that
 * came back, for this treatment alone: SYSTEM.md §8 › Image treatments. */
.tmm-treat { isolation: isolate; }
.tmm-treat--duotone,
.tmm-treat--duotone-hatch {
  --treat-ground: color-mix(in oklab, var(--color-black) 27%, var(--color-accent));
  --treat-filter: grayscale(1) contrast(1.15);
  --treat-filter-rest: grayscale(0) contrast(1);
  --treat-hatch: .34;
  background: var(--treat-ground);
}
.tmm-treat--duotone img,
.tmm-treat--duotone-hatch img { filter: var(--treat-filter); mix-blend-mode: screen; }
.tmm-treat--duotone::after,
.tmm-treat--duotone-hatch::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: var(--color-accent);
  mix-blend-mode: multiply;
}

/* Deep (Rowan, 2026-10-13): the same duotone and hatch with near-black shadows and brand-blue
 * highlights, so a light photo reads in strong contrast rather than as a flat blue field; the
 * hatch a little quieter over the darker ground. Drawn by render_hatch_overlay(style='deep'). */
.tmm-treat--deep {
  --treat-ground: color-mix(in oklab, var(--color-black) 82%, var(--color-accent));
  --treat-filter: grayscale(1) brightness(.62) contrast(1.45);
  --treat-filter-rest: grayscale(0) brightness(1) contrast(1);
  --treat-hatch: .22;
}

/* The screen: one pixel per line at any zoom, the ink that goes on the brand. */
.tmm-treat__hatch {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-color: var(--color-surface);
  opacity: var(--treat-hatch, .34);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='5' height='5' viewBox='0 0 5 5'%3E%3Cpath d='M-1.5 1.5 L1.5 -1.5 M0 5 L5 0 M3.5 6.5 L6.5 3.5' stroke='black' stroke-width='1'/%3E%3C/svg%3E") 0 0 / 5px 5px repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='5' height='5' viewBox='0 0 5 5'%3E%3Cpath d='M-1.5 1.5 L1.5 -1.5 M0 5 L5 0 M3.5 6.5 L6.5 3.5' stroke='black' stroke-width='1'/%3E%3C/svg%3E") 0 0 / 5px 5px repeat;
}

/* On hover (Rowan, 2026-10-13: the blog card's picture takes the duotone hatch
 * when the card is hovered): .tmm-treat--on-hover beside --duotone-hatch (and
 * --deep) rests as the plain photograph and fades the treatment in while its
 * host is hovered (.tmm-treat-host, the card), carries .is-hover, or holds the
 * keyboard focus; it fades out on leave. The blend modes stay as above: with
 * no ground under it the screened picture shows as itself, so the ground, the
 * grey, the multiply layer and the hatch are what fade. Markup:
 * render_image_box(…, class='tmm-treat tmm-treat--duotone-hatch tmm-treat--on-hover',
 * hatch=true) inside an element with .tmm-treat-host (SYSTEM.md §14, render_hatch_overlay). */
.tmm-treat--on-hover { background-color: transparent; transition: var(--transition-hover); }
.tmm-treat--on-hover img { filter: var(--treat-filter-rest); transition: filter var(--transition-pop); }
.tmm-treat--on-hover::after,
.tmm-treat--on-hover .tmm-treat__hatch { opacity: 0; transition: opacity var(--transition-pop); }
.tmm-treat--on-hover:hover,
.tmm-treat--on-hover.is-hover,
.tmm-treat-host:hover .tmm-treat--on-hover,
.tmm-treat-host.is-hover .tmm-treat--on-hover,
.tmm-treat-host:has(:focus-visible) .tmm-treat--on-hover,
.tmm-treat-host:has(.is-focus) .tmm-treat--on-hover { background-color: var(--treat-ground); }
.tmm-treat--on-hover:hover img,
.tmm-treat--on-hover.is-hover img,
.tmm-treat-host:hover .tmm-treat--on-hover img,
.tmm-treat-host.is-hover .tmm-treat--on-hover img,
.tmm-treat-host:has(:focus-visible) .tmm-treat--on-hover img,
.tmm-treat-host:has(.is-focus) .tmm-treat--on-hover img { filter: var(--treat-filter); }
.tmm-treat--on-hover:hover::after,
.tmm-treat--on-hover.is-hover::after,
.tmm-treat-host:hover .tmm-treat--on-hover::after,
.tmm-treat-host.is-hover .tmm-treat--on-hover::after,
.tmm-treat-host:has(:focus-visible) .tmm-treat--on-hover::after,
.tmm-treat-host:has(.is-focus) .tmm-treat--on-hover::after { opacity: 1; }
.tmm-treat--on-hover:hover .tmm-treat__hatch,
.tmm-treat--on-hover.is-hover .tmm-treat__hatch,
.tmm-treat-host:hover .tmm-treat--on-hover .tmm-treat__hatch,
.tmm-treat-host.is-hover .tmm-treat--on-hover .tmm-treat__hatch,
.tmm-treat-host:has(:focus-visible) .tmm-treat--on-hover .tmm-treat__hatch,
.tmm-treat-host:has(.is-focus) .tmm-treat--on-hover .tmm-treat__hatch { opacity: var(--treat-hatch, .34); }
@media (prefers-reduced-motion: reduce) {
  .tmm-treat--on-hover,
  .tmm-treat--on-hover img,
  .tmm-treat--on-hover::after,
  .tmm-treat--on-hover .tmm-treat__hatch { transition: none; }
}

/* Scrim: the photograph sunk into the band's own ground at a third of its
 * strength, so the copy on a card laid over it reads first (lookbook: image
 * CTAs, "The Industrial Scrim Banner", v3; cta-image's scrim layout, Rowan,
 * 2026-10-08). The ground is the band variable: a dark band gives the dark
 * scrim, a light band a pale one. No blend mode (the duotone owns the two
 * the decoration lint allows); the carrier is the box render_image_box emits:
 * render_image_box(image, 'cover', position, aspect, class='tmm-treat tmm-treat--scrim'). */
.tmm-treat--scrim { background: var(--ground); }
.tmm-treat--scrim img { opacity: .35; }
/* Icons and icon plates (SYSTEM.md §14).
 *
 *   .tmm-icon     the inline SVG render_icon emits (templates/macros/icons.html):
 *                 sized by the macro, stroke currentColor, never coloured here.
 *   .icon-box     the plate: a glyph in a square, 48px on the 12px radius.
 *                 Sizes follow the radius ladder (sm 32 / md 48 / lg 64),
 *                 circle overrides them. Variants paint the plate; the glyph
 *                 takes the plate's colour.
 *     --muted     the default (Rowan, 2026-10-09, third pick): no plate, no
 *                 ring, no tick; the glyph at stroke 2 in the band's second
 *                 ink, the box the glyph (sm 16 / md 24 / lg 32). Hover (the
 *                 box, a link, a card or .is-hover around it) lifts the glyph
 *                 to the ink, colour only. On a cell's top rule
 *                 (.section__bead) the glyph is centred on the rule, the rule
 *                 cut 8px either side (_layouts.css; 2026-10-12).
 *     --rule      kept (Rowan, 2026-10-09, second pick): no plate, the
 *                 glyph in the ink standing on a 1px tick in the band's rule,
 *                 the glyph's width, at the box's foot (8px under a 24px
 *                 glyph). The box is the glyph wide and the glyph plus 8
 *                 plus the tick tall (sm 16 x 25, md 24 x 33, lg 32 x 41). Hover (a link, a card
 *                 or .is-hover around it): the tick steps to --ink-faint,
 *                 colour only. On a cell's top rule (.section__bead) the tick
 *                 is dropped and the glyph stands on the cell's rule
 *                 (_layouts.css).
 *     --hatch     kept (Rowan, 2026-10-09, first pick): the brand's hatch in the
 *                 ink at .4 clipped to the plate, the glyph in the ink on a
 *                 ground disc the glyph plus 8px wide, so no diagonal crosses
 *                 the glyph. Markup: span.icon-box__hatch > span.tmm-hatch,
 *                 then span.icon-box__disc around the glyph. Inside a link
 *                 the hatch steps up to .56 on hover.
 *     --subtle    accent on the accent wash with a 40% accent ring (kept for
 *                 saved choices; the ring is currentColor at .4)
 *     --solid     on-accent glyph on the accent fill (--solid-crisp = the same)
 *     --neutral   the ink on the sunken ground (plates inside reading copy)
 *     --bare      nothing painted, accent glyph (the plate only reserves the box)
 * Output of templates/macros/cards.html -> render_icon_box.
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-icon,
.lucide-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
  flex-shrink: 0;
  vertical-align: middle;
}
.tmm-icon svg,
.lucide-icon svg { display: block; width: 100%; height: 100%; }
svg.tmm-icon { display: inline-block; vertical-align: middle; flex-shrink: 0; }

.icon-box {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-lg);
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  background: var(--accent-wash);
  color: var(--accent);
}
.icon-box--hatch { --disc: 32px; background: transparent; color: var(--ink); }
.icon-box--subtle { background: var(--accent-wash); color: var(--accent); }
.icon-box--subtle::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px currentColor;
  opacity: .4;
  pointer-events: none;
}
.icon-box--solid-crisp,
.icon-box--solid { background: var(--accent-fill); color: var(--on-accent); }
.icon-box--neutral { background: var(--ground-sunken); color: var(--ink); }
.icon-box--bare { background: transparent; color: var(--accent); }

.icon-box--sm { width: 32px; height: 32px; border-radius: var(--radius-md); }
.icon-box--lg { width: 64px; height: 64px; border-radius: var(--card-radius-sm); }
.icon-box--circle { border-radius: var(--radius-full); }

/* ── The muted glyph ─────────────────────────────────────────────────────
 * No plate, no ring, no shadow, no tick: the glyph alone in the band's
 * second ink, lifting to the ink on hover. The box is the glyph, so its top
 * is the glyph's top. The size rules repeat the variant so they win over
 * --sm / --lg above. */
.icon-box--muted {
  width: 24px;
  height: 24px;
  border-radius: 0;
  background: transparent;
  color: var(--ink-2);
  transition: var(--transition-hover);
}
.icon-box--muted.icon-box--sm { width: 16px; height: 16px; border-radius: 0; }
.icon-box--muted.icon-box--lg { width: 32px; height: 32px; border-radius: 0; }
.icon-box--muted.icon-box--circle { border-radius: 0; }
.icon-box--muted:hover,
.icon-box--muted.is-hover,
a:hover .icon-box--muted,
a.is-hover .icon-box--muted,
.card:hover .icon-box--muted,
.card.is-hover .icon-box--muted,
.is-hover .icon-box--muted { color: var(--ink); }

/* ── The bare glyph on a rule ────────────────────────────────────────────
 * No plate, no ring, no shadow: the glyph in the ink, top of the box, and a
 * 1px tick in the band's rule the glyph's width along the box's foot. The
 * size rules repeat the variant so they win over --sm / --lg above. */
.icon-box--rule {
  width: 24px;
  height: 33px;
  align-items: flex-start;
  border-radius: 0;
  background: transparent;
  color: var(--ink);
}
.icon-box--rule.icon-box--sm { width: 16px; height: 25px; border-radius: 0; }
.icon-box--rule.icon-box--lg { width: 32px; height: 41px; border-radius: 0; }
.icon-box--rule.icon-box--circle { border-radius: 0; }
.icon-box--rule::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: var(--inner-rule, var(--rule));
  transition: var(--transition-hover);
  pointer-events: none;
}
a:hover .icon-box--rule::after,
a.is-hover .icon-box--rule::after,
.card:hover .icon-box--rule::after,
.card.is-hover .icon-box--rule::after,
.is-hover .icon-box--rule::after { background: var(--inner-rule, var(--ink-faint)); }

/* ── The hatch plate ─────────────────────────────────────────────────────
 * .tmm-hatch (_rule.css) supplies the 1px diagonals on the 8px pitch; here
 * it fills the plate at the plate's radius, in the ink at .4. The disc is a
 * round hole the glyph plus 8px wide cut from the hatch, so the glyph sits
 * on whatever ground the plate stands on (the band, a card, a hover tint)
 * and no diagonal crosses it. */
.icon-box--sm.icon-box--hatch { --disc: 24px; }
.icon-box--lg.icon-box--hatch { --disc: 40px; }
.icon-box__hatch {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;
  opacity: .4;
  transition: opacity var(--pop-duration) var(--pop-ease);
  /* audit-allow: a functional mask, the disc's hard-edged hole in the hatch, not a gradient fill. */
  -webkit-mask: radial-gradient(circle, transparent calc(var(--disc) / 2 - .5px), black calc(var(--disc) / 2));
  /* audit-allow: the same functional mask, unprefixed. */
          mask: radial-gradient(circle, transparent calc(var(--disc) / 2 - .5px), black calc(var(--disc) / 2));
}
.icon-box__hatch > .tmm-hatch { width: 100%; height: 100%; background-color: var(--ink); }
.icon-box__disc { position: relative; display: inline-flex; }
a:hover .icon-box__hatch,
a.is-hover .icon-box__hatch,
.is-hover .icon-box__hatch { opacity: .56; }
/* TMM Navigation */

/* Skip link — canonical visually-hidden pattern. Width/height 1px + clip
 * keep it accessible to screen readers and keyboard focus while never
 * occupying any visible space. The old `left: -9999px` approach was
 * vulnerable to CSS-load ordering and stacking-context bugs that could
 * leave the text briefly visible during page load. */
.tmm-nav__skip {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
/* :focus-visible (NOT :focus) — only reveals the skip link when the
 * user is keyboard-navigating. Plain :focus also matched on
 * programmatic focus from scripts, browser autofocus, and (on some
 * browsers) mouse-clicks-elsewhere bouncing focus to the first
 * focusable element. That manifested as the link visually appearing
 * under the nav bar at random times. :focus-visible is the modern
 * canonical pattern for this exact accessibility-vs-visual-noise
 * tradeoff: keyboard users still get the link, mouse users don't. */
.tmm-nav__skip:focus-visible,
.tmm-nav__skip.is-focus {
  width: auto;
  height: auto;
  clip: auto;
  overflow: visible;
  position: fixed;
  top: 0;
  left: 0;
  padding: 8px 16px;
  margin: 0;
  background: var(--color-charcoal);
  color: var(--color-dark-ink);
  text-decoration: none;
  border-radius: 0 0 var(--radius-md) 0;
  z-index: 9999;
}

/* ── Nav surface state machine ─────────────────────────────────────
 * Two orthogonal axes editor-controllable:
 *   theme:        light (dark text)   |  dark (white text)
 *   start state:  solid (bg always)   |  transparent (over hero)
 *
 * Defaults come from navigation.module's STYLE-tab fields. Per-page
 * override comes from the Header Override module placed on a page;
 * main.js → initNavOverride() reads either source and adds body
 * classes (nav-theme-{light|dark}, nav-start-{solid|transparent}) that
 * the rules below switch on. Scrolled state (.nav--scrolled, also from
 * main.js) layers on top regardless of start state.
 *
 * Surface ("Canvas, hairline on stick", decided 2026-10-02 from the
 * lookbook round nav/sticky): at the top of the page the bar is the
 * page's own canvas. Once scrolled it is a sheet on --color-surface with
 * a --color-rule hairline, so it separates from content only when content
 * is passing beneath it. The bar is 72px in both states and an open
 * dropdown changes nothing about it (Rowan, 2026-10-02: nothing may shift
 * or recolour on the transition). The dark theme is the app's chrome:
 * --color-charcoal, --color-nav-rule. No blur, ever.
 */

.tmm-nav {
  /* Fixed — nav stays pinned to the viewport top across every template
   * (homepage, blog post, blog index, about, pricing, etc.). The
   * transparency-over-hero effect is purely a function of the start_state
   * axis (transparent vs solid) — orthogonal to position. Pages that want
   * a hero floating under the nav set start_state=transparent (typically
   * via the Header Override module placed in the page editor); pages
   * without a top hero use the default start_state=solid and pair with
   * the body.nav-start-solid main padding rule below to push content
   * clear of the fixed bar. */
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  height: var(--nav-height);
  background-color: transparent; /* the bar never paints: whatever is under it (a hero, the canvas) shows through; the plate is the only fill */
  transition: var(--transition-hover);
}
/* THE LEDGER BAR (Rowan, 2026-10-02; lookbook › Navigation › Dividers in the
 * bar › 5 without the hatch, Going dark, refined › 6 · Nothing).
 *
 * At rest the bar paints nothing: a full-height hairline between every item
 * (the ledger) and the ruled line under the bar, spanning the container.
 * Once stuck (.nav--scrolled, or start_state=solid from the top of the page)
 * the bar IS the dark bar — ground, lines and ink all switch at once, with no
 * transition of any kind: "Nothing".
 *
 * Two custom properties carry the lines so each state sets them once:
 *   --nav-rule-v  the vertical cell rules
 *   --nav-rule-h  the line under the bar
 * Contrast (measured, sRGB): the dark rule tokens are 1.3–1.5:1 on these dark
 * grounds, under the 3:1 a boundary needs, so the lines come from the text
 * ladder instead — faint #6B655B 3.1:1 at rest on a dark page, muted #8A8378
 * 4.3:1 on the stuck bar. */
.tmm-nav {
  --nav-ground-stuck: var(--color-charcoal);
  --nav-accent: var(--color-accent); /* the current page's mark; the dark accent on a dark bar */
  --nav-accent-text: var(--color-accent-strong); /* the current page's link: the deep blue as small text on the light bar (the brand blue is 4.03 : 1 on the canvas) */
  --nav-rule-v: var(--color-rule-strong);
  --nav-rule-h: var(--color-rule-strong);
  /* The ink ladder (OPERATIONS.md §7 › The state machine), set once per state like the
   * lines: a link at rest, its hover / focus / open / active-branch step, the
   * wordmark and burger, the burger's hover wash. */
  --nav-ink: var(--color-ink-2);
  --nav-ink-hover: var(--color-ink);
  --nav-logo: var(--color-ink);
  --nav-burger-wash: color-mix(in oklab, var(--color-ink) var(--state-hover-amount), transparent);
}
body.nav-theme-dark .tmm-nav {
  --nav-rule-v: var(--color-dark-ink-faint);
  --nav-rule-h: var(--color-dark-ink-faint);
  --nav-accent: var(--color-dark-accent);
  --nav-accent-text: var(--color-dark-accent);
  --nav-ink: var(--color-dark-ink); /* the muted step is too dim on dark (Rowan, 2026-10-02) */
  --nav-ink-hover: var(--color-dark-ink);
  --nav-logo: var(--color-dark-ink);
  --nav-burger-wash: color-mix(in oklab, var(--color-dark-ink) var(--state-hover-amount), transparent);
}
/* Stuck: the dark bar on both themes. The dark-theme chain is spelled out
 * because body.nav-theme-dark .tmm-nav (0,2,1) out-ranks .tmm-nav.nav--scrolled
 * (0,2,0), which left a scrolled dark-theme bar on the at-rest ladder. */
.tmm-nav.nav--scrolled,
body.nav-theme-dark .tmm-nav.nav--scrolled,
body.nav-start-solid .tmm-nav {
  --nav-rule-v: var(--color-dark-ink-muted);
  --nav-rule-h: var(--color-dark-ink-muted);
  --nav-accent: var(--color-dark-accent);
  --nav-accent-text: var(--color-dark-accent);
  --nav-ink: var(--color-dark-ink);
  --nav-ink-hover: var(--color-dark-ink);
  --nav-logo: var(--color-dark-ink);
  --nav-burger-wash: color-mix(in oklab, var(--color-dark-ink) var(--state-hover-amount), transparent);
}

/* The ground. No transition: the bar is dark the moment it sticks. */
.tmm-nav::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--nav-ground-stuck);
  opacity: 0;
  pointer-events: none;
}
.tmm-nav.nav--scrolled::before,
body.nav-start-solid .tmm-nav::before { opacity: 1; }

/* The line under the bar: the container's width at rest, the whole bar stuck. */
.tmm-nav::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 0;
  width: min(var(--container-max), 100%);
  height: 1px;
  background: var(--nav-rule-h);
  transform: translateX(-50%);
  pointer-events: none;
}
.tmm-nav.nav--scrolled::after,
body.nav-start-solid .tmm-nav::after { width: 100%; }

/* The ledger. One full-height hairline closes the wordmark off from the links;
 * the rules between the links are the link's own height (40px), centred, so the
 * bar reads as one ruled row rather than a grid of boxes (Rowan, 2026-10-02). */
.tmm-nav__links { border-left: 1px solid var(--nav-rule-v); }
.tmm-nav__item + .tmm-nav__item::before,
.tmm-nav__item:last-child::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 1px;
  height: 40px;
  background: var(--nav-rule-v);
  transform: translateY(-50%);
  pointer-events: none;
}
.tmm-nav__item + .tmm-nav__item::before { left: 0; }
.tmm-nav__item:last-child::after { right: 0; }

/* The action is the site's primary at md on every state of the bar (Rowan,
 * 2026-10-13: nav CTA round, 1 · The primary). Nothing repaints the button:
 * the bar never lifts it, and once the bar is dark (stuck, solid start, the
 * dark theme) its right side resolves the charcoal band's variables, so
 * render_button resolves its own style there as on a charcoal band. */
.tmm-nav__right {
  --btn-lift: none;
  --btn-lift-hover: none;
}
.tmm-nav.nav--scrolled .tmm-nav__right,
body.nav-start-solid .tmm-nav .tmm-nav__right,
body.nav-theme-dark .tmm-nav .tmm-nav__right {
  --ink: var(--color-dark-ink);
  --ink-2: var(--color-dark-ink-2);
  --ink-muted: var(--color-dark-ink-muted);
  --ground: var(--color-charcoal);
  --ground-raised: color-mix(in oklab, var(--color-dark-ink) 6%, var(--color-charcoal));
  --rule: color-mix(in oklab, var(--color-dark-ink) 36%, transparent);
  --rule-soft: color-mix(in oklab, var(--color-dark-ink) 12%, transparent);
  --accent: var(--color-dark-accent);
  --accent-text: var(--color-dark-accent);
  --accent-fill: var(--color-accent);
  --accent-fill-hover: var(--color-accent-strong);
  --on-accent: var(--color-surface);
  --link: var(--color-dark-accent);
  --link-hover: var(--color-dark-ink);
  --state-tone: var(--color-dark-ink);
  --state-hover: color-mix(in oklab, var(--color-dark-ink) var(--state-hover-amount), transparent);
  --btn-outline-ground: color-mix(in oklab, var(--color-dark-ink) 6%, var(--color-charcoal));
  --btn-outline-ring: inset 0 0 0 1px color-mix(in oklab, var(--color-dark-ink) 22%, transparent);
  --btn-outline-ring-hover: inset 0 0 0 1px color-mix(in oklab, var(--color-dark-ink) 40%, transparent);
  --btn-inverse-shadow: none;
}

/* Content offset — solid-start pages only. A transparent start (the
 * default) lets the hero run under the bar, which the bar never paints over;
 * the hero's own top padding keeps its copy clear. scroll-padding-top on the
 * root makes on-page anchor jumps (#pricing, #demo-form) land below the nav. */
:root { --nav-height: 72px; }
/* scrollbar-gutter: stable permanently reserves the scrollbar's track
 * width. Without it, opening the mobile drawer (which sets
 * body { overflow: hidden } to lock scroll) removes the scrollbar, the
 * viewport widens by its width, and the fixed nav re-centres — visibly
 * shifting the right-aligned burger sideways. Reserving the gutter keeps
 * layout width constant whether the page scrolls or not. */
html {
  scroll-padding-top: var(--nav-height);
  scrollbar-gutter: stable;
}
body.nav-start-solid main { padding-top: var(--nav-height); }

/* Logo SVG fill follows theme via CSS (overrides any inline fill on
   individual paths, e.g. the secondary-glyph greys baked into the source
   SVG). The default colour is set on .tmm-nav__logo-link itself so the
   logo renders dark from the very first paint — without it, the wrapper
   has no explicit colour, currentColor falls through to the browser's
   default <a> blue. --nav-logo resolves on .tmm-nav before any body
   class lands, so the first paint is already the light ladder. */
.tmm-nav__logo-link { color: var(--nav-logo); }
/* The wordmark never takes a hover colour. Chained past the global
 * a:hover / a:focus (0,3,1, elements/_typography.css), which would paint it
 * --link-hover. */
.tmm-nav .tmm-nav__inner > .tmm-nav__logo-link:hover,
.tmm-nav .tmm-nav__inner > .tmm-nav__logo-link.is-hover,
.tmm-nav .tmm-nav__inner > .tmm-nav__logo-link:focus,
.tmm-nav .tmm-nav__inner > .tmm-nav__logo-link.is-focus { color: var(--nav-logo); }
.tmm-nav__logo-link svg path,
.tmm-nav__logo-link svg polygon,
.tmm-nav__logo-link svg rect {
  fill: currentColor;
}

/* Both themes: the bar itself never paints. The theme only sets the ink and
   the plate's fill (surface on light, the raised dark surface on dark). */


.tmm-nav__inner {
  position: relative;
  z-index: 1;
  max-width: 1280px;
  margin-inline: auto;
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  height: 100%;
}
/* Logo + right actions vertically centred via align-self so they don't
   stretch the way nav links do — links need to span full bar height so
   the dropdown can anchor to the bar's bottom edge via top: 100%. */
.tmm-nav__inner > .tmm-nav__logo-link,
.tmm-nav__inner > .tmm-nav__right {
  align-self: center;
}

.tmm-nav__logo-link {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  text-decoration: none;
}
.tmm-nav__logo-link svg { height: 24px; width: auto; }

/* Desktop nav links container — flex row of .tmm-nav__item children.
   align-items: stretch (cascading down from .tmm-nav { height: var(--nav-height) })
   so each item is full bar height; the dropdown then positions cleanly
   at top: 100% of the item = the bar's bottom edge. */
.tmm-nav__links {
  display: flex;
  align-items: stretch;
  gap: 0; /* the ledger's cells meet: the hairline is the gap */
  flex: 1;
  justify-content: flex-start;
}

.tmm-nav__item {
  position: relative;
  display: flex;
  align-items: center;
}

.tmm-nav__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 24px; /* the ledger cell's breathing room */
  white-space: nowrap; /* a label never wraps: the bar's row is measured to fit at 1024 */
  font-size: var(--type-dense);
  font-weight: 600;
  color: var(--nav-ink);
  text-decoration: none;
  background: none;
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  font-family: inherit;
  transition: color var(--pop-duration) var(--pop-ease);
}
/* Hover, focus, an open panel and the active branch lift the link one step;
 * only is-active (the current page) takes the accent (OPERATIONS.md §7 › Active state).
 * Chained to 0,4,0 so the global a:hover / a:focus (0,3,1) never reaches a
 * nav link. */
.tmm-nav .tmm-nav__item > .tmm-nav__link:hover,
.tmm-nav .tmm-nav__item > .tmm-nav__link.is-hover,
.tmm-nav .tmm-nav__item > .tmm-nav__link:focus,
.tmm-nav .tmm-nav__item > .tmm-nav__link.is-focus,
.tmm-nav .tmm-nav__item > .tmm-nav__link[aria-expanded="true"],
.tmm-nav .tmm-nav__item.is-active-branch > .tmm-nav__link {
  color: var(--nav-ink-hover);
}
.tmm-nav .tmm-nav__item > .tmm-nav__link.is-active { color: var(--nav-accent-text); }
.tmm-nav__link--readonly { cursor: default; }

.tmm-nav__chevron {
  display: inline-flex;
  align-items: center;
  transition: transform var(--pop-duration) var(--pop-ease);
}
.tmm-nav__link[aria-expanded="true"] .tmm-nav__chevron {
  transform: rotate(180deg);
}

/* Active-branch underline cue: the accent line on the bar's bottom edge of
 * the current page's top-level cell. The link is unpositioned, so this
 * anchors to .tmm-nav__item, which spans the bar (align-items: stretch):
 * bottom: 1px seats the 2px line inside the bar, just above the 1px line
 * under it, in every state (OPERATIONS.md §7 › The ledger bar, 2026-10-09). Hidden
 * while a dropdown is open, so the eye goes to the open item. */
.tmm-nav__item.is-active-branch > .tmm-nav__link::after {
  content: '';
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 1px;
  height: 2px;
  background: var(--nav-accent);
  border-radius: 1px;
  opacity: 1;
  transition: opacity var(--pop-duration) var(--pop-ease);
}
.tmm-nav.is-dropdown-open .tmm-nav__item.is-active-branch > .tmm-nav__link::after {
  opacity: 0;
}

.tmm-nav__right {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-shrink: 0;
}

/* ── Desktop dropdown panel ─────────────────────────────────────────
 * App popover chrome: solid --color-surface, --radius-pop, --shadow-pop
 * (whose first layer is the 1px ring, so the panel carries no border).
 * Two width variants:
 *   --simple : 280px single column (e.g. Resources)
 *   --mega   : the 1040 ruled grid (e.g. Platform), centred on the bar
 * Both are absolute children of the bar, at top: 100%, centred via
 * left: 50% + translateX(-50%): the simple one under its item, the mega
 * one on the page (below). A panel closes when the page scrolls (main.js).
 * Reveal: opacity + translate on the [hidden] toggle, on the pop curve. */

/* Solid surface — no blur. The dropdown layers content over the page
   beneath, and any transparency makes the leaf links difficult to read
   once they overlap a hero photograph or a busy section. */
.tmm-nav__dropdown {
  position: absolute;
  top: 100%;
  left: 50%;
  z-index: 50;
  margin-top: 8px;
  padding: 16px;
  background-color: var(--color-surface);
  border: 0;
  border-radius: var(--radius-pop);
  box-shadow: var(--shadow-pop);
  /* visibility (delayed on close) keeps a closed panel's links out of the
     tab order and the a11y tree: [hidden] is overridden to display: block
     for the transition, so without it every leaf stayed focusable. */
  visibility: hidden;
  opacity: 0;
  transform: translateX(-50%) translateY(8px);
  pointer-events: none;
  transition: opacity var(--pop-duration) var(--pop-ease), transform var(--pop-duration) var(--pop-ease), visibility 0s linear var(--pop-duration);
}
.tmm-nav__dropdown[hidden] {
  display: block;       /* override [hidden] so we can transition */
}
.tmm-nav__item.is-open > .tmm-nav__dropdown {
  visibility: visible;
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
  transition: opacity var(--pop-duration) var(--pop-ease), transform var(--pop-duration) var(--pop-ease), visibility 0s linear 0s;
}
/* Hover bridge — invisible wedge above the panel so the cursor doesn't
   close the dropdown while moving from trigger to panel content. */
.tmm-nav__dropdown::before {
  content: '';
  position: absolute;
  top: -8px;
  left: 0;
  right: 0;
  height: 8px;
}

.tmm-nav__dropdown--simple {
  width: 280px;
  padding: 8px;
}
/* THE NARROW PANEL (Rowan, 2026-10-02; lookbook › Navigation › The hatched
 * panel › 10 · Narrow). The mega menu hangs off the bar's ruled line rather
 * than floating under it: centred on the page and not on the item it belongs
 * to, 1040 wide, square where it meets the bar and curved at the bottom, on
 * the hairline ring. The columns are ruled, each label is closed by a
 * hairline, and the hatched block ends the row.
 *
 * Anchored to the bar by construction (2026-10-09): an absolute child of
 * .tmm-nav__inner (the centred, full-bar-height measure), at top: 100%, so it
 * moves with the bar in every position mode. It used to be position: fixed at
 * top: var(--nav-height), which held it to the viewport: on the blog (the bar
 * absolute, scrolling away) an open panel stayed on screen while the bar left.
 * Its item is unpositioned so the inner is the containing block; the item's
 * ledger rules and the active-branch underline, which anchored to the item,
 * become a flex rule and an underline on the full-height link. */
.tmm-nav__item.tmm-nav__item--mega { position: static; }
.tmm-nav__item.tmm-nav__item--mega > .tmm-nav__link { position: relative; align-self: stretch; }
.tmm-nav__item.tmm-nav__item--mega::before,
.tmm-nav__item.tmm-nav__item--mega:last-child::after { position: static; flex: 0 0 1px; transform: none; }
.tmm-nav__item.tmm-nav__item--mega::before { margin-right: -1px; }
.tmm-nav__item.tmm-nav__item--mega:last-child::after { margin-left: -1px; }
.tmm-nav__dropdown--mega {
  top: 100%;
  width: min(1040px, calc(100% - 64px));
  max-width: none;
  margin-top: 0;
  padding: 0;
  overflow: hidden;
  border-radius: 0 0 var(--card-radius) var(--card-radius);
  box-shadow: var(--shadow-hairline);
}
.tmm-nav__dropdown--mega .tmm-nav__dropdown-grid { gap: 0; }
.tmm-nav__dropdown--mega .tmm-nav__dropdown-section { padding: 28px 32px 32px; }
.tmm-nav__dropdown--mega .tmm-nav__dropdown-section + .tmm-nav__dropdown-section { border-left: 1px solid var(--color-rule-strong); }
.tmm-nav__dropdown--mega .tmm-nav__dropdown-section + .tmm-nav__dropdown-section::before { content: none; }
.tmm-nav__dropdown--mega .tmm-nav__dropdown-section-title {
  padding: 0 0 12px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--color-rule);
}
.tmm-nav__dropdown--mega .tmm-nav__dropdown-link { padding: 10px 12px; margin-inline: -12px; border-radius: var(--radius-md); }
/* The hatched end: the brochure's corner mark, closing the row. */
.tmm-nav__dropdown-hatch { flex: 0 0 24px; align-self: stretch; }

/* THE CTA COLUMN (Rowan, 2026-10-09; lookbook › The mega menu, second set ›
 * 6 · The brand column left; OPERATIONS.md §7 › Dropdowns). The editor's mega_cta: a
 * 280 column on the brand ground, first or last in the panel, in place of the
 * hatched end. .section--brand on the column resolves the band variables, so
 * every text is white on the brand blue and the primary button renders white
 * with the brand ink. The hatch, in the column's white at .2, fills the top
 * above the copy. In the drawer the same column is a plate closing the menu's
 * accordion. */
.tmm-nav__mega-cta {
  flex: 0 0 280px;
  display: flex;
  flex-direction: column;
  background-color: var(--ground);
  color: var(--ink);
}
.tmm-nav__mega-cta-hatch { flex: 1 1 auto; min-height: 72px; background-color: var(--ink); opacity: .2; }
.tmm-nav__mega-cta-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 20px;
  padding: 24px 28px 28px;
}
.tmm-nav__mega-cta-heading { line-height: 28px; }
.tmm-nav__mega-cta-body .tmm-nav__mega-cta-eyebrow + .tmm-nav__mega-cta-heading { margin-top: -8px; }
.tmm-nav__mega-cta--drawer { flex: none; margin-top: 8px; border-radius: var(--card-radius); overflow: hidden; }
.tmm-nav__mega-cta--drawer .tmm-nav__mega-cta-hatch { flex: 0 0 24px; min-height: 24px; }
.tmm-nav__mega-cta--drawer .tmm-nav__mega-cta-body { gap: 16px; padding: 24px; }

.tmm-nav__dropdown-grid {
  display: flex;
  gap: var(--bento-gap);
}
.tmm-nav__dropdown-section {
  flex: 1;
  min-width: 0;
  position: relative;
}
/* Hairline divider between adjacent columns — sits centred inside the
   bento gap (calc(--bento-gap / -2 + 0.5px) puts it 0.5px left of the
   column's left edge, which equals the gap's centre line). */
.tmm-nav__dropdown-section + .tmm-nav__dropdown-section::before {
  content: '';
  position: absolute;
  left: calc((var(--bento-gap) / -2) - 0.5px);
  top: 4px;
  bottom: 4px;
  width: 1px;
  background: var(--color-rule);
}
/* Section title — uses the eyebrow font (.t-subheader) in the muted text
   tone so it reads as a quiet category label and doesn't compete with
   the leaf-link titles below. Hover (only when wrapped in <a>): steps up
   to the secondary tone for interactive feedback without going brand. */
.tmm-nav__dropdown-section-title {
  display: block;
  margin: 0 0 16px;
  padding: 0 12px;
  color: var(--color-ink-muted);
  text-decoration: none;
  transition: color var(--pop-duration) var(--pop-ease);
}
/* Chained past the global a:hover / a:focus (0,3,1). */
.tmm-nav__dropdown .tmm-nav__dropdown-section > a.tmm-nav__dropdown-section-title:hover,
.tmm-nav__dropdown .tmm-nav__dropdown-section > a.tmm-nav__dropdown-section-title.is-hover,
.tmm-nav__dropdown .tmm-nav__dropdown-section > a.tmm-nav__dropdown-section-title:focus,
.tmm-nav__dropdown .tmm-nav__dropdown-section > a.tmm-nav__dropdown-section-title.is-focus {
  color: var(--color-ink-2);
}

.tmm-nav__dropdown-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.tmm-nav__dropdown-link {
  display: block;
  padding: 8px 12px;
  border-radius: var(--radius-lg);
  text-decoration: none;
  transition: background-color var(--pop-duration) var(--pop-ease);
}
/* Selected vs hover (Rowan, 2026-10-09; SYSTEM.md §3 › Colour): the current
 * page sits on --accent-wash with an ink title, a hovered or focused leaf on
 * the hover state (--state-hover, the ink at 6%), and the selected leaf keeps its wash under hover.
 * The panel is always light, so the light wash is restated on it. */
.tmm-nav__dropdown { --accent-wash: var(--color-accent-wash); --ink-2: var(--color-ink-2); }
.tmm-nav__dropdown-link:hover,
.tmm-nav__dropdown-link.is-hover,
.tmm-nav__dropdown-link:focus-visible,
.tmm-nav__dropdown-link.is-focus {
  background-color: var(--state-hover);
}
.tmm-nav__dropdown-link.is-active,
.tmm-nav__dropdown-link.is-active:hover,
.tmm-nav__dropdown-link.is-active.is-hover,
.tmm-nav__dropdown-link.is-active:focus-visible,
.tmm-nav__dropdown-link.is-active.is-focus {
  background-color: var(--accent-wash);
}
.tmm-nav__dropdown-link-title {
  display: block;
  font-size: var(--type-dense);
  font-weight: 600;
  color: var(--color-ink);
  letter-spacing: -0.01em;
  transition: color var(--pop-duration) var(--pop-ease);
}
/* The title stays ink on hover and when selected: the wash behind the link
 * marks the state, never blue text (Rowan, 2026-10-02 / 2026-10-09). */
/* The tag pill after a leaf title ("<mark>New</mark>" in the menu label,
 * OPERATIONS.md §7 › Tags): the deep brand blue with the surface ink (7 : 1 at the
 * micro size; the brand blue is 4 : 1), so it holds on a selected leaf's wash. */
.tmm-chip.tmm-nav__tag {
  height: 20px;
  margin: 0 0 0 8px;
  padding: 0 8px;
  vertical-align: 2px;
  font-size: var(--type-micro);
  letter-spacing: 0.02em;
  background: var(--color-accent-strong);
  color: var(--color-surface);
}
.tmm-nav__dropdown-link-desc {
  display: block;
  margin-top: 2px;
  font-size: var(--type-caption);
  font-weight: 400;
  /* --ink-2, not the muted grey: the muted grey measured 4.48 : 1 on the
   * current leaf's wash (lookbook › The mega menu, 2026-10-09). */
  color: var(--ink-2);
  line-height: 1.35;
}

@media (prefers-reduced-motion: reduce) {
  .tmm-nav__dropdown,
  .tmm-nav__chevron,
  .tmm-nav__dropdown-link,
  .tmm-nav__dropdown-link-title { transition: none; }
}

/* Desktop bar CTA: render_button(…, 'primary', 'md',
   extra_class='tmm-nav__cta'), the site's primary (accent fill, hover
   --accent-fill-hover, focus --shadow-focus-strong) from
   elements/_buttons.css, its square corner included. */

.tmm-nav__burger {
  display: none;
  position: relative;
  width: 44px;
  height: 44px;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: var(--nav-logo);
  border-radius: var(--radius-md);
  transition: background-color var(--pop-duration) var(--pop-ease);
}
.tmm-nav__burger:hover,
.tmm-nav__burger.is-hover { background-color: var(--nav-burger-wash); }
.tmm-nav__burger-icon {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity var(--pop-duration) var(--pop-ease), transform var(--pop-duration) var(--pop-ease);
}
.tmm-nav__burger-icon--close { opacity: 0; transform: rotate(-90deg); }
.tmm-nav__burger[aria-expanded="true"] .tmm-nav__burger-icon--menu { opacity: 0; transform: rotate(90deg); }
.tmm-nav__burger[aria-expanded="true"] .tmm-nav__burger-icon--close { opacity: 1; transform: rotate(0); }

/* ── Mobile drawer ───────────────────────────────────────────────────
 * Full-viewport overlay below the bar (top: var(--nav-height)). Hidden
 * by default; .is-open on the drawer reveals it via opacity + translate.
 * Accordion sections animate via max-height transition on the panel.
 *
 * Rendered as a SIBLING of .tmm-nav, never a child: a blur / filter /
 * transform ancestor becomes the containing block for position:fixed
 * descendants — which would resolve this drawer's top/bottom against
 * the 72px nav box instead of the viewport, collapsing it to zero
 * usable height. The bar is opaque today; the structure stays so that
 * invariant never has to be rediscovered.
 *
 * Surface follows the nav theme: --color-surface on light,
 * --color-charcoal on dark, with every text/rule token swapped to the
 * nav-* family under body.nav-theme-dark. */

.tmm-nav__drawer {
  position: fixed;
  top: var(--nav-height);
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
  display: flex;
  flex-direction: column;
  background-color: var(--color-surface);
  overflow-y: auto;
  /* contain scroll chaining so flicking a long menu doesn't scroll the
     (locked) page behind it. */
  overscroll-behavior: contain;
  /* visibility (not display) drives hide/show so the fade+slide runs
     both ways; visibility:hidden also drops the drawer from the a11y
     tree + tab order at rest. The delayed visibility transition keeps
     the element rendered through the fade-out, then hides it. */
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transform: translateY(-10px);
  transition: opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.3s cubic-bezier(0.16, 1, 0.3, 1),
              visibility 0s linear 0.3s;
}
.tmm-nav__drawer[hidden] { display: none; }
.tmm-nav__drawer.is-open {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
  transition: opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.3s cubic-bezier(0.16, 1, 0.3, 1),
              visibility 0s linear 0s;
}
body.nav-theme-dark .tmm-nav__drawer { background-color: var(--color-charcoal); }

.tmm-nav__drawer-body {
  flex: 1;
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.tmm-nav__drawer-item {
  display: flex;
  flex-direction: column;
}

.tmm-nav__drawer-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 8px 0;
  background: none;
  border: none;
  text-align: left;
  font-family: inherit;
  font-size: var(--type-title);
  font-weight: 650;
  letter-spacing: -0.025em;
  color: var(--color-ink-2);
  text-decoration: none;
  cursor: pointer;
  transition: color var(--pop-duration) var(--pop-ease);
}
/* Chained to 0,4,0: a top-level item without children renders the trigger as
 * an <a>, which the global a:hover / a:focus (0,3,1) would otherwise paint. */
.tmm-nav__drawer .tmm-nav__drawer-item > .tmm-nav__drawer-trigger:hover,
.tmm-nav__drawer .tmm-nav__drawer-item > .tmm-nav__drawer-trigger.is-hover,
.tmm-nav__drawer .tmm-nav__drawer-item > .tmm-nav__drawer-trigger:focus,
.tmm-nav__drawer .tmm-nav__drawer-item > .tmm-nav__drawer-trigger.is-focus,
.tmm-nav__drawer .tmm-nav__drawer-item > .tmm-nav__drawer-trigger[aria-expanded="true"] {
  color: var(--color-ink);
}
.tmm-nav__drawer-chevron {
  display: inline-flex;
  align-items: center;
  color: var(--color-ink-muted);
  transition: transform var(--pop-duration) var(--pop-ease);
}
.tmm-nav__drawer-trigger[aria-expanded="true"] .tmm-nav__drawer-chevron {
  transform: rotate(180deg);
}

.tmm-nav__drawer-panel {
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-height 0.3s cubic-bezier(0.16, 1, 0.3, 1),
              opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.tmm-nav__drawer-trigger[aria-expanded="true"] + .tmm-nav__drawer-panel {
  max-height: 1200px;
  opacity: 1;
}

.tmm-nav__drawer-panel-inner {
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: 16px 0 8px;
}

.tmm-nav__drawer-section {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.tmm-nav__drawer-section-title {
  display: block;
  margin: 0 0 8px;
  padding: 0 12px;
  color: var(--color-ink-muted);
  text-decoration: none;
  transition: color var(--pop-duration) var(--pop-ease);
}
.tmm-nav__drawer .tmm-nav__drawer-section > a.tmm-nav__drawer-section-title:hover,
.tmm-nav__drawer .tmm-nav__drawer-section > a.tmm-nav__drawer-section-title.is-hover,
.tmm-nav__drawer .tmm-nav__drawer-section > a.tmm-nav__drawer-section-title:focus,
.tmm-nav__drawer .tmm-nav__drawer-section > a.tmm-nav__drawer-section-title.is-focus {
  color: var(--color-ink-2);
}

.tmm-nav__drawer-leaf {
  display: block;
  padding: 12px;
  border-radius: var(--radius-lg);
  text-decoration: none;
  transition: background-color var(--pop-duration) var(--pop-ease);
}
/* Same selected-vs-hover rule as the dropdown leaf: beige on hover / focus,
 * --accent-wash (restated per drawer theme) on the current page, kept under
 * hover. */
.tmm-nav__drawer { --accent-wash: var(--color-accent-wash); }
.tmm-nav__drawer-leaf:hover,
.tmm-nav__drawer-leaf.is-hover,
.tmm-nav__drawer-leaf:active,
.tmm-nav__drawer-leaf:focus-visible,
.tmm-nav__drawer-leaf.is-focus {
  background-color: var(--state-hover);
}
.tmm-nav__drawer-leaf.is-active,
.tmm-nav__drawer-leaf.is-active:hover,
.tmm-nav__drawer-leaf.is-active.is-hover,
.tmm-nav__drawer-leaf.is-active:active,
.tmm-nav__drawer-leaf.is-active:focus-visible,
.tmm-nav__drawer-leaf.is-active.is-focus {
  background-color: var(--accent-wash);
}
.tmm-nav__drawer-leaf-title {
  display: block;
  font-size: var(--type-callout);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--color-ink);
  transition: color var(--pop-duration) var(--pop-ease);
}
.tmm-nav__drawer-leaf-desc {
  display: block;
  margin-top: 2px;
  font-size: var(--type-dense);
  font-weight: 400;
  color: var(--color-ink-muted);
  line-height: 1.35;
}

.tmm-nav__drawer-footer {
  padding: 24px;
  border-top: 1px solid var(--color-rule);
  background-color: var(--color-paper);
}
/* Drawer CTA — rendered by render_button(…, 'primary', 'xl',
   extra_class='tmm-nav__drawer-cta'): the app's primary button (brand
   fill, lift shadow, hover to brand-hover) from elements/_buttons.css.
   The only nav-specific rule is the full-width placement in the footer. */
.btn.tmm-nav__drawer-cta {
  width: 100%;
}

/* Dark-theme drawer — same geometry, nav-* tokens. */
body.nav-theme-dark .tmm-nav__drawer-trigger { color: var(--color-dark-ink-2); }
body.nav-theme-dark .tmm-nav__drawer .tmm-nav__drawer-item > .tmm-nav__drawer-trigger:hover,
body.nav-theme-dark .tmm-nav__drawer .tmm-nav__drawer-item > .tmm-nav__drawer-trigger.is-hover,
body.nav-theme-dark .tmm-nav__drawer .tmm-nav__drawer-item > .tmm-nav__drawer-trigger:focus,
body.nav-theme-dark .tmm-nav__drawer .tmm-nav__drawer-item > .tmm-nav__drawer-trigger.is-focus,
body.nav-theme-dark .tmm-nav__drawer .tmm-nav__drawer-item > .tmm-nav__drawer-trigger[aria-expanded="true"] { color: var(--color-dark-ink); }
body.nav-theme-dark .tmm-nav__drawer-chevron { color: var(--color-dark-ink-2); }
body.nav-theme-dark .tmm-nav__drawer-section-title { color: var(--color-dark-ink-2); }
body.nav-theme-dark .tmm-nav__drawer .tmm-nav__drawer-section > a.tmm-nav__drawer-section-title:hover,
body.nav-theme-dark .tmm-nav__drawer .tmm-nav__drawer-section > a.tmm-nav__drawer-section-title.is-hover,
body.nav-theme-dark .tmm-nav__drawer .tmm-nav__drawer-section > a.tmm-nav__drawer-section-title:focus,
body.nav-theme-dark .tmm-nav__drawer .tmm-nav__drawer-section > a.tmm-nav__drawer-section-title.is-focus { color: var(--color-dark-ink); }
body.nav-theme-dark .tmm-nav__drawer { --accent-wash: color-mix(in oklab, var(--color-dark-accent) 16%, transparent); }
body.nav-theme-dark .tmm-nav__drawer-leaf:hover,
body.nav-theme-dark .tmm-nav__drawer-leaf.is-hover,
body.nav-theme-dark .tmm-nav__drawer-leaf:active,
body.nav-theme-dark .tmm-nav__drawer-leaf:focus-visible,
body.nav-theme-dark .tmm-nav__drawer-leaf.is-focus { background-color: color-mix(in oklab, var(--color-dark-ink) var(--state-hover-amount), transparent); }
body.nav-theme-dark .tmm-nav__drawer-leaf.is-active,
body.nav-theme-dark .tmm-nav__drawer-leaf.is-active:hover,
body.nav-theme-dark .tmm-nav__drawer-leaf.is-active.is-hover,
body.nav-theme-dark .tmm-nav__drawer-leaf.is-active:active,
body.nav-theme-dark .tmm-nav__drawer-leaf.is-active:focus-visible,
body.nav-theme-dark .tmm-nav__drawer-leaf.is-active.is-focus { background-color: var(--accent-wash); }
/* The title stays ink on hover and when selected (OPERATIONS.md §7 › Dropdowns › Leaf). */
body.nav-theme-dark .tmm-nav__drawer-leaf-title { color: var(--color-dark-ink); }
body.nav-theme-dark .tmm-nav__drawer-leaf-desc { color: var(--color-dark-ink-2); }
body.nav-theme-dark .tmm-nav__drawer-footer {
  border-top-color: color-mix(in oklab, var(--color-dark-ink) 12%, transparent);
  background-color: var(--color-charcoal);
}
/* Drawer CTA on the dark theme — the same mapping .section--dark gives
   .btn--primary (dark accent fill, on-accent ink, no lift shadow); the
   drawer is not a .section so it is restated here. Colour only. */
body.nav-theme-dark .btn.tmm-nav__drawer-cta {
  background-color: var(--color-accent);
  color: var(--color-surface);
  box-shadow: none;
}
body.nav-theme-dark .btn.tmm-nav__drawer-cta:hover,
body.nav-theme-dark .btn.tmm-nav__drawer-cta.is-hover {
  background-color: var(--color-accent-strong);
  color: var(--color-surface);
}

/* Body lock while drawer is open — JS adds .is-mobile-nav-open. */
body.is-mobile-nav-open { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .tmm-nav__drawer,
  .tmm-nav__drawer-panel,
  .tmm-nav__drawer-trigger,
  .tmm-nav__drawer-chevron,
  .tmm-nav__drawer-leaf,
  .tmm-nav__burger-icon { transition: none; }
}

/* Under 1024 the bar is the phone bar: burger and drawer, no links, no CTA,
   the gutter's 24px. From 1024 the desktop bar above applies (mobile-first,
   SYSTEM.md §11). */
.tmm-nav__inner { padding-inline: 24px; gap: 0; }
.tmm-nav__burger { display: block; }
/* .btn.tmm-nav__cta (0,2,0) beats .btn's display:inline-flex (0,1,0). */
.btn.tmm-nav__cta { display: none; }
.tmm-nav__links { display: none; }

/* The gutter is 40 at 1024 and widens to 80 by 1184 (OPERATIONS.md §7 › The ledger
   bar). At 1024 the row is logo 172 + 2 x 48 gap + four links (24px padding,
   two chevrons, 477) + the md CTA (133) = 878 of the 944 left by a 40 gutter;
   an 80 gutter leaves 864 and the longest label wrapped. The ramp grows the
   gutter by a quarter of each extra pixel, so the room only grows. */
@media (min-width: 1024px) {
  .tmm-nav__inner { padding-inline: clamp(40px, calc(40px + (100vw - 1024px) / 4), 80px); gap: 48px; }
  .tmm-nav__burger { display: none; }
  .btn.tmm-nav__cta { display: inline-flex; }
  .tmm-nav__links { display: flex; }
}

/* TMM Footer — always on the app's dark nav chrome (--color-nav-*), on
 * every template. The .is-footer-dark wrapper that blog-post.html adds
 * is therefore a no-op now (kept in the template so nothing else moves). */

.tmm-footer {
  background-color: var(--color-charcoal);
  border-top: 1px solid color-mix(in oklab, var(--color-dark-ink) 12%, transparent);
  color: var(--color-dark-ink-2);
  padding-bottom: 40px;
}

/* Two-column outer layout — brand block on the left (logo + tagline),
   menu block on the right. The menu block has its own internal grid for
   the per-section columns so the brand block stays as a single full-
   height unit instead of competing as one of N equal columns. */
.tmm-footer__grid {
  display: grid;
  align-items: start;
}

/* Menu block — internal grid for the per-section columns. auto-fit means
   1–4 menu items render cleanly without manual breakpoint math; minmax
   prevents columns from squashing too narrow on tablets. */
.tmm-footer__nav {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}

/* Logo renders single-tone in nav ink (fill: currentColor overrides the
   source SVG's inline secondary-glyph greys). */
.tmm-footer__logo-link {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  margin-bottom: 20px;
  color: var(--color-dark-ink);
}
/* The wordmark keeps its ink: chained past the global a:hover / a:focus. */
.tmm-footer .tmm-footer__brand > .tmm-footer__logo-link:hover,
.tmm-footer .tmm-footer__brand > .tmm-footer__logo-link.is-hover,
.tmm-footer .tmm-footer__brand > .tmm-footer__logo-link:focus,
.tmm-footer .tmm-footer__brand > .tmm-footer__logo-link.is-focus { color: var(--color-dark-ink); }
.tmm-footer__logo-link svg { height: 28px; width: auto; }
.tmm-footer__logo-link svg path,
.tmm-footer__logo-link svg polygon,
.tmm-footer__logo-link svg rect {
  fill: currentColor;
}

.tmm-footer__tagline {
  font-size: var(--type-dense);
  color: var(--color-dark-ink-2);
  font-weight: 500;
  margin: 0;
  line-height: 1.5;
}

/* Social icons row (.tmm-footer__socials on .tmm-socials): drawn entirely by
 * components/_social.css, footer variant included. Nothing here. */

.tmm-footer__col-heading {
  font-size: var(--type-caption);
  font-weight: 600;
  color: var(--color-dark-ink);
  margin: 0 0 16px;
  font-family: var(--font-sans);
}

.tmm-footer__links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.tmm-footer__links a {
  font-size: var(--type-dense);
  font-weight: 500;
  color: var(--color-dark-ink-2);
  text-decoration: none;
  transition: color var(--pop-duration) var(--pop-ease);
}
/* Chained to 0,3,2 past the global a:hover / a:focus (0,3,1). */
.tmm-footer .tmm-footer__links li > a:hover,
.tmm-footer .tmm-footer__links li > a.is-hover,
.tmm-footer .tmm-footer__links li > a:focus,
.tmm-footer .tmm-footer__links li > a.is-focus { color: var(--color-dark-ink); }

.tmm-footer__bottom {
  border-top: 1px solid color-mix(in oklab, var(--color-dark-ink) 12%, transparent);
  padding-top: 32px;
  text-align: center;
  font-size: var(--type-caption);
  font-weight: 500;
  color: var(--color-dark-ink-2);
}

/* Under 1024 the footer stacks: brand on top, menu block below (its
   internal grid keeps its auto-fit columns); phones tighten the spacing.
   Mobile-first: the phone values are the base, 768 and 1024 open them up. */
.tmm-footer { padding-top: 48px; }
.tmm-footer__grid { grid-template-columns: 1fr; gap: 40px; margin-bottom: 40px; }
.tmm-footer__nav { gap: 32px; }

@media (min-width: 768px) {
  .tmm-footer { padding-top: 80px; }
  .tmm-footer__grid { gap: 48px; margin-bottom: 64px; }
  .tmm-footer__nav { gap: 40px; }
}
@media (min-width: 1024px) {
  .tmm-footer__grid { grid-template-columns: minmax(240px, 1fr) 2fr; gap: 80px; }
}

/* Boilerplate header — only what templates/partials/header-no-navigation
 * (used by landing-page.html) still renders: the .header DND areas, the
 * .header__container row and the .header__logo block. The HubSpot
 * boilerplate search bar, language switcher and mobile toggle menu were
 * deleted: nothing in modules/ or templates/ renders those classes. */

.header .dnd-section {
  padding: 0;
}

.header__container {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 1rem 0 0;
}

@media (min-width: 768px) {
  .header__container {
    flex-direction: row;
    padding: 0;
  }
}

.header__logo {
  align-items: center;
  display: flex;
  height: auto;
  margin: 0 auto;
  width: 100%;
  max-width: 200px;
  overflow: hidden;
}

@media (min-width: 768px) {
  .header__logo {
    margin: 0 auto 0 0;
    width: auto;
  }
}

.header__logo img {
  max-width: 100%;
}

.header__logo .logo-company-name {
  font-size: 1.167rem;
  margin-top: 0.7rem;
}

.header__logo--main {
  padding-top: 1rem;
}

/* Blog nav mode — distinct VISUAL + POSITION treatment for blog post
 * and blog index pages.
 *
 * Position: absolute (overrides the global .tmm-nav { position: fixed }
 * default). The nav sits at the top of the page document, NOT pinned to
 * the viewport — so it scrolls AWAY with the page as the reader moves
 * through the article. On blog posts the bottom pager (rendered by
 * blog-post.html as a sibling of main.blog-article) is the always-
 * visible navigation affordance during long reads; on the blog index
 * the listing itself is the surface, and a top nav that follows the
 * scroll would sit on top of grid cards reading as visual noise.
 *
 * Surface: filled with --color-paper, the same token blog and
 * blog-index use as their page background. The strip blends seamlessly
 * into the page and the hairline border underneath is what delineates
 * it. No shadow — reads as a clean editorial nav strip. The dark nav
 * theme swaps to the nav chrome (--color-charcoal / --color-nav-rule).
 *
 * Content offset: kept (see the note at the end of this block).
 *
 * Scope: HubSpot's auto-applied body classes hs-blog-post (single
 * article + case study) and hs-blog-listing (resources index, tag
 * pages). Both match the body-wrapper class set in base.html via
 * builtin_body_classes — no template change needed; every blog post
 * and listing picks this up automatically.
 *
 * !important: the existing nav rules chain multiple body classes
 * (body.nav-start-solid.nav-theme-light .tmm-nav, specificity 0,3,1)
 * and a single-scope override can't beat that without long selector
 * chains. !important here is contained to one block + one scope,
 * doesn't leak. */

.body-wrapper.hs-blog-post .tmm-nav,
.body-wrapper.hs-blog-post .tmm-nav.nav--scrolled,
.body-wrapper.hs-blog-listing .tmm-nav,
.body-wrapper.hs-blog-listing .tmm-nav.nav--scrolled {
  position: absolute !important;
  background-color: transparent !important;
  box-shadow: none !important;
}

/* No plate, ever: the strip scrolls away, so it stays on the at-rest ladder
 * of its theme even when initScrollNav() marks it scrolled or the page starts
 * solid. Before this, .nav--scrolled (and nav-start-solid) still faded in the
 * dark plate and switched the ink while the strip was leaving the viewport. */
.body-wrapper.hs-blog-post .tmm-nav::before,
.body-wrapper.hs-blog-listing .tmm-nav::before { opacity: 0 !important; }
.body-wrapper.hs-blog-post .tmm-nav::after,
.body-wrapper.hs-blog-listing .tmm-nav::after { width: min(var(--container-max), 100%) !important; }
.body-wrapper.hs-blog-post .tmm-nav,
.body-wrapper.hs-blog-listing .tmm-nav {
  --nav-accent: var(--color-accent) !important;
  --nav-accent-text: var(--color-accent-strong) !important;
  --nav-rule-v: var(--color-rule-strong) !important;
  --nav-rule-h: var(--color-rule-strong) !important;
  --nav-ink: var(--color-ink-2) !important;
  --nav-ink-hover: var(--color-ink) !important;
  --nav-logo: var(--color-ink) !important;
  --nav-burger-wash: color-mix(in oklab, var(--color-ink) var(--state-hover-amount), transparent) !important;
}
body.nav-theme-dark .body-wrapper.hs-blog-post .tmm-nav,
body.nav-theme-dark .body-wrapper.hs-blog-listing .tmm-nav {
  --nav-accent: var(--color-dark-accent) !important;
  --nav-accent-text: var(--color-dark-accent) !important;
  --nav-rule-v: var(--color-dark-ink-faint) !important;
  --nav-rule-h: var(--color-dark-ink-faint) !important;
  --nav-ink: var(--color-dark-ink) !important;
  --nav-ink-hover: var(--color-dark-ink) !important;
  --nav-logo: var(--color-dark-ink) !important;
  --nav-burger-wash: color-mix(in oklab, var(--color-dark-ink) var(--state-hover-amount), transparent) !important;
}
/* The bar CTA is the primary on the strip too: the accent fill reads the
 * same on the page's light ground and the dark set, so nothing restates it. */

/* The global content offset (body.nav-start-solid main) stays on the blog
 * templates: the strip is absolute over the page top, and the page header
 * (blog.css / blog-index.css) clears it from that offset. A rule that
 * cancelled the offset here never matched (it scoped to <body>) and, once
 * corrected, put the page title under the strip; it is gone. */

/* The bar scrolls away on the blog templates, so nothing pins under it:
 * stickies sit at the top of the viewport (--sticky-top, _band.css). */
.body-wrapper.hs-blog-post,
.body-wrapper.hs-blog-listing { --sticky-top: 0px; }
/* Chip — the app's filter chip (Quiet): a 28px pill, caption type at 600.
 * A chip is a surface, so it is stronger than the typographic eyebrow; used
 * at the top of case-study posts and gated-form sections, and as a tag or
 * filter (render_pill with href / active).
 *
 * Colour variants are tinted fills with status ink, never a solid fill with
 * white glyphs. The accent and neutral variants follow the theme variables,
 * so they read on dark bands without a restated selector; success and
 * warning keep their own tints on every ground.
 * Output of templates/macros/lists.html -> render_pill (and render_chip).
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 28px;
  padding: 0 12px;
  border-radius: var(--radius-full);
  font: 600 var(--type-caption) / 1 var(--font-sans);
  white-space: nowrap;
  background: var(--ground-raised);
  color: var(--ink);
  box-shadow: var(--shadow-hairline-soft);
  transition: var(--transition-hover);
  /* The chip owns its bottom rhythm as an eyebrow (24px to the heading); a
   * chip row zeroes it. align-self stops a flex-column parent stretching it. */
  margin-bottom: 24px;
  align-self: flex-start;
}
.tmm-chip__icon { flex-shrink: 0; }

.tmm-chip--accent  { background: var(--accent-wash); color: var(--accent-text); box-shadow: none; }
.tmm-chip--success { background: var(--color-success-wash); color: var(--color-success); box-shadow: none; }
.tmm-chip--warning { background: var(--color-warning-wash); color: var(--color-warning); box-shadow: none; }
.tmm-chip--dark    { background: var(--ink); color: var(--ground); box-shadow: none; }
.tmm-chip--neutral { background: var(--ground-sunken); color: var(--ink-2); box-shadow: none; }

a.tmm-chip,
button.tmm-chip { cursor: pointer; text-decoration: none; }
a.tmm-chip:hover,
a.tmm-chip.is-hover,
button.tmm-chip:hover,
button.tmm-chip.is-hover { background: color-mix(in oklab, var(--state-tone) var(--state-hover-amount), var(--ground-raised)); color: var(--ink); }
a.tmm-chip:focus-visible,
a.tmm-chip.is-focus,
button.tmm-chip:focus-visible,
button.tmm-chip.is-focus { outline: none; box-shadow: var(--shadow-focus-strong); }

.tmm-chip.is-active,
a.tmm-chip.is-active:hover,
a.tmm-chip.is-active.is-hover,
button.tmm-chip.is-active:hover,
button.tmm-chip.is-active.is-hover {
  background: var(--ink);
  color: var(--ground);
  box-shadow: none;
}

/* Large: the 44px control chip (a field's height), the dense size, 24px
 * sides; a choice row of two or three (the ROI calculator's Yes / No). */
.tmm-chip--lg {
  height: 44px;
  padding: 0 24px;
  font-size: var(--type-dense);
}

.tmm-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.tmm-chip-row .tmm-chip { margin-bottom: 0; }
/* Stat — a metric figure with a caption. Output of
 * templates/macros/lists.html -> render_stat.
 *
 * Type is the shared roles (.t-stat for the figure, .t-label-sm for the
 * caption); this file owns the stack, the unit, the accent inks and the hero
 * size. Colour follows the theme variables, so a stat inside a dark island
 * reads the dark ladder without a restated selector.
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-stat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  min-width: 0;
}
.tmm-stat__value { display: block; line-height: 1.15; color: var(--ink); }
.tmm-stat__label { color: var(--ink-2); }

/* Trailing unit: a step down from the figure, the caption ink. */
.tmm-stat__unit {
  font-size: var(--type-callout);
  font-weight: 500;
  letter-spacing: 0;
  margin-left: 0.15em;
  color: var(--ink-2);
}

/* Accent inks. Success and warning read on both grounds; white is the bright
 * dark-ladder tone for dark islands regardless of the band. */
.tmm-stat__value--accent  { color: var(--accent); }
.tmm-stat__value--success { color: var(--color-success); }
.tmm-stat__value--warning { color: var(--color-warning); }
.tmm-stat__value--white   { color: var(--color-dark-ink); }
.section--dark .tmm-stat__value--warning,
.card--island-dark .tmm-stat__value--warning { color: var(--color-warning-wash); }

/* Hero figure: the one step above the 30px stat role, fluid like the headings. */
.tmm-stat--lg .tmm-stat__value {
  font-size: var(--type-stat-lg);
  line-height: 1;
  letter-spacing: -0.03em;
}
.tmm-stat--lg .tmm-stat__unit { font-size: var(--type-title); }

/* Tile: render_stat(tile=true) adds the compact card; the stat keeps its stack. */
.tmm-stat.card { display: flex; }
/* The stat strip: output of templates/macros/stats.html -> render_stat_strip.
 * One row of figures, each a render_stat (_stat.css owns the figure, the unit,
 * the label and the inks); this file owns the row and its lines.
 *
 *   --cells   a column rule between figures and a row rule between rows (the
 *             ruled grid's cells); only the inner lines show and each row's
 *             first figure sits on the copy's edge. The column rule is vertical
 *             (var(--rule)); the row rule a horizontal inner hairline
 *             (var(--inner-rule, ...)).
 *   --ticks   one row from 768, a standing rule between figures (the stories
 *             meta row); stacked with no rules on a phone, where a tick would
 *             dangle at a line's end (the critic, 2026-10-13, at 390).
 *   --tiles   each figure on the compact card (render_stat tile=true), wrapping.
 *   --none    the figures alone on the gaps.
 * Columns: two on a phone, the count (up to four) from 768.
 * Modifiers: --label-first (the label above, the figures level on the foot),
 * --end (held right). With .tmm-ruled the row sits between hairlines.
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-stat-strip {
  --strip-col-gap: 48px;
  --strip-row-gap: 32px;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--strip-row-gap) var(--strip-col-gap);
  margin: 0;
  padding: 0;
}
.tmm-stat-strip--cols-2,
.tmm-stat-strip--cols-3,
.tmm-stat-strip--cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 768px) {
  .tmm-stat-strip--cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .tmm-stat-strip--cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.tmm-stat-strip.tmm-ruled { padding-block: 20px; }

/* Cells: the cells abut (the gap is the rule); each draws its column rule on
 * its left edge and its row rule on its top edge, as .section__grid--ruled.
 * The top row's rule sits outside the strip and is clipped (inset 1px, the
 * ruled grid's half-pixel guard; with .tmm-ruled the strip's own hairline is
 * the top, so the clip is the strip's edge). A row's first cell draws no
 * column rule and has no left padding, so each row starts on the copy's edge. */
.tmm-stat-strip--cells {
  --strip-cell-pad-y: 16px;
  --strip-cell-pad-x: 24px;
  gap: 0;
  clip-path: inset(1px 0 0 0);
}
.tmm-stat-strip--cells.tmm-ruled { padding-block: 0; clip-path: inset(0); }
.tmm-stat-strip--cells > .tmm-stat-strip__item {
  position: relative;
  padding: var(--strip-cell-pad-y) var(--strip-cell-pad-x);
}
.tmm-stat-strip--cells > .tmm-stat-strip__item::before,
.tmm-stat-strip--cells > .tmm-stat-strip__item::after {
  content: '';
  position: absolute;
  pointer-events: none;
}
.tmm-stat-strip--cells > .tmm-stat-strip__item::before { top: 0; bottom: 0; left: -1px; width: 1px; background: var(--rule); }
.tmm-stat-strip--cells > .tmm-stat-strip__item::after { left: 0; right: 0; top: -1px; height: 1px; background: var(--inner-rule, var(--rule)); }
/* A row's first cell: two columns on a phone (one for a single figure). */
.tmm-stat-strip--cells.tmm-stat-strip--cols-1 > .tmm-stat-strip__item,
.tmm-stat-strip--cells > .tmm-stat-strip__item:nth-child(2n+1) { padding-left: 0; }
.tmm-stat-strip--cells.tmm-stat-strip--cols-1 > .tmm-stat-strip__item::before,
.tmm-stat-strip--cells > .tmm-stat-strip__item:nth-child(2n+1)::before { display: none; }
@media (min-width: 768px) {
  .tmm-stat-strip--cells.tmm-stat-strip--cols-3 > .tmm-stat-strip__item:nth-child(2n+1),
  .tmm-stat-strip--cells.tmm-stat-strip--cols-4 > .tmm-stat-strip__item:nth-child(2n+1) { padding-left: var(--strip-cell-pad-x); }
  .tmm-stat-strip--cells.tmm-stat-strip--cols-3 > .tmm-stat-strip__item:nth-child(2n+1)::before,
  .tmm-stat-strip--cells.tmm-stat-strip--cols-4 > .tmm-stat-strip__item:nth-child(2n+1)::before { display: block; }
  .tmm-stat-strip--cells.tmm-stat-strip--cols-3 > .tmm-stat-strip__item:nth-child(3n+1),
  .tmm-stat-strip--cells.tmm-stat-strip--cols-4 > .tmm-stat-strip__item:nth-child(4n+1) { padding-left: 0; }
  .tmm-stat-strip--cells.tmm-stat-strip--cols-3 > .tmm-stat-strip__item:nth-child(3n+1)::before,
  .tmm-stat-strip--cells.tmm-stat-strip--cols-4 > .tmm-stat-strip__item:nth-child(4n+1)::before { display: none; }
}

/* Ticks: stacked on a phone, one row with a standing rule between from 768. */
.tmm-stat-strip--ticks { display: flex; flex-direction: column; align-items: flex-start; gap: var(--text-gap-sm); }
@media (min-width: 768px) {
  .tmm-stat-strip--ticks { --strip-col-gap: 56px; flex-direction: row; flex-wrap: wrap; align-items: stretch; gap: var(--text-gap-sm) var(--strip-col-gap); }
  .tmm-stat-strip--ticks > .tmm-stat-strip__item { position: relative; }
  .tmm-stat-strip--ticks > .tmm-stat-strip__item + .tmm-stat-strip__item::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(var(--strip-col-gap) / -2);
    width: 1px;
    background: var(--rule);
    pointer-events: none;
  }
}

/* Tiles: the compact cards wrap on the rhythm the case-study hero used. */
.tmm-stat-strip--tiles { display: flex; flex-wrap: wrap; gap: 12px; }
.tmm-stat-strip--tiles > .tmm-stat-strip__item { --card-padding: 16px 24px; min-width: 140px; }
@media (min-width: 768px) { .tmm-stat-strip--tiles { gap: 16px; } }
@media (min-width: 1024px) { .tmm-stat-strip--tiles { gap: 24px; } }

/* The label above, the figures level on the row's foot (the ROI payback cells). */
.tmm-stat-strip--label-first > .tmm-stat-strip__item .tmm-stat__label { order: -1; }
.tmm-stat-strip--label-first > .tmm-stat-strip__item .tmm-stat__value { margin-top: auto; }

/* Held right (the stories metric beside its author). */
.tmm-stat-strip--end { justify-content: end; justify-items: end; text-align: right; }
.tmm-stat-strip--end > .tmm-stat-strip__item { align-items: flex-end; }
/* Item markers: output of templates/macros/markers.html -> render_marker.
 *
 * The number and the text are the index numeral (.tmm-index-num, _lists.css:
 * the accent figure at the label size, --lg at the stat size); the icon is the
 * icon plate (.icon-box, _icon-box.css). This file adds only what the three
 * share: the tick (.tmm-marker--rule), the line language's mark under a
 * marker, a 1px rule in the band's rule its own width, 8px under it (the
 * icon plate's 'rule' variant draws the same tick for a glyph), and the
 * text's one line.
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-marker { flex: none; }

/* The text: a few words in the numeral's voice, one line, no paragraph gap. */
.tmm-marker--text { max-width: 100%; }
.tmm-marker--text.t-richtext > * { margin: 0; }

/* The tick: the number or the text standing on a 1px rule (the glyph takes
 * the 'rule' plate instead). A horizontal hairline, so Inner lines: None
 * blanks it and keeps its pixel. */
.tmm-marker--rule:not(.icon-box) {
  position: relative;
  min-width: 24px;
  padding-bottom: 8px;
}
.tmm-marker--rule:not(.icon-box)::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--inner-rule, var(--rule));
  pointer-events: none;
}
/* Figures and index bars (the /mcp round-6 mocks, 2026-10-15).
 *
 *   .tmm-figures   a row of sourced figures: output of
 *                  templates/macros/lists.html -> render_figures. Each figure is
 *                  a hairline above (--lines), an optional small label, the value
 *                  in a heading role (t-h2 / t-h1-5 / t-display, never past 56,
 *                  tabular), one line of meaning, the source in the caption role
 *                  and an optional foot (a button). One column on the phone, two
 *                  from 640, the count (--cols-N, up to four) from 1024.
 *   .tmm-index     indexed bar rows: render_index_bars. Label, a bar scaled to
 *                  the largest value, the value; one row highlighted (--on) in
 *                  the ink with the accent fill, the rest in the caption ink on
 *                  the muted fill; soft hairlines between rows; a source caption
 *                  under a short rule.
 *
 * Type comes from the shared roles; colour from the band variables, so a dark
 * band draws the dark ladder without a restated selector.
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

/* ── Figures ──────────────────────────────────────────────────────────── */
.tmm-figures {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--text-gap-md) 32px;
  margin: 0;
  padding: 0;
}
.tmm-figure {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  min-width: 0;
}
.tmm-figures--lines .tmm-figure { padding-top: 14px; }
.tmm-figures--lines .tmm-figure::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 1px;
  background: var(--inner-rule, var(--rule));
  pointer-events: none;
}
.tmm-figure__label { margin: 0 0 4px; }
.tmm-figure__value {
  display: block;
  margin: 0;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  text-wrap: balance;
}
.tmm-figure--muted .tmm-figure__value { color: var(--ink-2); }
.tmm-figure--accent .tmm-figure__value { color: var(--accent); }
/* The unit beside the value: a step down, the caption ink, on the value's baseline. */
.tmm-figure__unit {
  font-size: var(--type-h3);
  font-weight: 600;
  letter-spacing: -0.016em;
  color: var(--ink-2);
  margin-left: 0.3em;
}
.tmm-figures--md .tmm-figure__unit { font-size: var(--type-callout); }
.tmm-figure__meaning { margin: 0; max-width: 42ch; text-wrap: pretty; }
.tmm-figure--muted .tmm-figure__meaning { color: var(--ink-2); }
.tmm-figure__source { margin: 0; max-width: 42ch; }
.tmm-figure__foot { margin-top: var(--text-gap-sm); }
.tmm-figure__foot .tmm-cta-row { margin-top: 0; }
@media (min-width: 640px) {
  .tmm-figures--cols-2,
  .tmm-figures--cols-3,
  .tmm-figures--cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  .tmm-figures--cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .tmm-figures--cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ── Index bars ───────────────────────────────────────────────────────── */
.tmm-index { display: grid; margin: 0; padding: 0; }
.tmm-index__row {
  position: relative;
  display: grid;
  grid-template-columns: 7.5em minmax(0, 1fr) 2.5em;
  gap: 10px;
  align-items: center;
  padding: 10px 0;
}
.tmm-index__row + .tmm-index__row::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 1px;
  background: var(--inner-rule, var(--rule-soft));
  pointer-events: none;
}
.tmm-index__label,
.tmm-index__value { margin: 0; font-weight: 600; color: var(--ink-2); }
.tmm-index__value { text-align: right; font-variant-numeric: tabular-nums; }
.tmm-index__row--on .tmm-index__label,
.tmm-index__row--on .tmm-index__value { color: var(--ink); }
.tmm-index__bar {
  position: relative;
  display: block;
  height: 12px;
  overflow: hidden;
  border-radius: 2px;
  background: var(--rule-soft);
}
.tmm-index__bar > i {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: var(--w, 0%);
  border-radius: 2px;
  background: var(--ink-muted);
}
.tmm-index__row--on .tmm-index__bar > i { background: var(--accent); }
/* The source caption under a short rule. */
.tmm-index__caption {
  position: relative;
  margin: var(--text-gap-md) 0 0;
  padding-top: 14px;
  max-width: 44ch;
}
.tmm-index__caption::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: 48px;
  height: 1px;
  background: var(--inner-rule, var(--rule));
}
@media (min-width: 640px) {
  .tmm-index__row { grid-template-columns: 8.5em minmax(0, 1fr) 2.5em; gap: 14px; }
}
/* Stars — the 1–5 rating. Output of templates/macros/lists.html ->
 * render_stars. Each star is a span masked to the Lucide star polygon; the
 * background is a linear-gradient cut at --fill (0 / 50 / 100%, set inline
 * per star) so halves need no second image. The star size is --star-size,
 * set inline by the macro; the gap scales with it (2px at 16, 4px at 32).
 * The empty track is the faint ink of the ground the star sits on (--ink-faint). */

.tmm-stars {
  --star-size: 16px;
  --star-empty: var(--ink-faint);
  display: inline-flex;
  align-items: center;
  gap: calc(var(--star-size) / 8);
}

.tmm-stars__star {
  display: inline-block;
  width: var(--star-size);
  height: var(--star-size);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpolygon points='12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpolygon points='12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26'/%3E%3C/svg%3E");
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-size: contain;
          mask-size: contain;
  /* audit-allow: the partial star is a hard-stop gradient under a mask, a fill level not a decoration. */
  background: linear-gradient(
    to right,
    var(--color-star) 0%,
    var(--color-star) var(--fill, 0%),
    var(--star-empty) var(--fill, 0%),
    var(--star-empty) 100%
  );
}
/* Avatar, person, avatar stack. Output of templates/macros/lists.html ->
 * render_avatar / render_person / render_avatar_stack.
 *
 * Plate: the sunken ground with the soft hairline so an empty picture still
 * has a footprint. Circle by default (bylines); square for testimonial
 * authors. Sizes 32 (xs) / 40 / 48 / 56. Colour follows the theme variables.
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-avatar {
  display: inline-block;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-full);
  overflow: hidden;
  background: var(--ground-sunken);
  box-shadow: var(--shadow-hairline-soft);
}
.tmm-avatar--xs { width: 32px; height: 32px; }
.tmm-avatar--sm { width: 40px; height: 40px; }
.tmm-avatar--md { width: 48px; height: 48px; }
.tmm-avatar--lg { width: 56px; height: 56px; }
.tmm-avatar--square { border-radius: var(--radius-md); }
.tmm-avatar img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.section--dark .tmm-avatar,
.card--island-dark .tmm-avatar { box-shadow: none; }

/* Person: avatar beside a two-line stack. Name is the dense size at 600 in
 * the ink; meta is the caption size in the caption ink. */
.tmm-person {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}
.tmm-person__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.tmm-person__name {
  font-family: var(--font-sans);
  font-size: var(--type-dense);
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
}
.tmm-person__meta {
  font-family: var(--font-sans);
  font-size: var(--type-caption);
  font-weight: 500;
  line-height: 1.4;
  color: var(--ink-2);
}
.tmm-person .tmm-avatar--lg + .tmm-person__text .tmm-person__name { font-size: var(--type-callout); }
.tmm-person .tmm-avatar--lg { margin-right: 4px; }

/* The caption person (text_size='caption'; the post card's byline, Rowan 2026-10-13): the name at the
 * caption size, still 600 in the ink, over the meta at the caption size and weight, tabular for a date. */
.tmm-person--caption { gap: 10px; }
.tmm-person--caption .tmm-person__name { font-size: var(--type-caption); line-height: 1.35; }
.tmm-person--caption .tmm-person__meta { font-weight: 400; line-height: 1.35; font-variant-numeric: tabular-nums; }

/* Stack: overlapping circles, each ringed in the raised ground so the
 * overlap reads as a cut-out. */
.tmm-avatar-stack { display: inline-flex; align-items: center; }
.tmm-avatar-stack .tmm-avatar { box-shadow: 0 0 0 2px var(--ground-raised); }
.tmm-avatar-stack .tmm-avatar + .tmm-avatar { margin-left: -10px; }
/* Empty state — the editor-facing placeholder when a module has nothing to
 * render yet ("pick a blog in the content panel"). Output of
 * templates/macros/lists.html -> render_empty_state.
 *
 * A dashed rule on the raised ground, card radius, the muted ink: clearly a
 * placeholder, never mistaken for content. audit-allow: the dashed edge is
 * the placeholder's whole meaning, not a card border.
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 64px 32px;
  text-align: center;
  background: var(--ground-raised);
  /* audit-allow: the dashed edge is the placeholder, not a card border */
  border: 1px dashed var(--rule);
  border-radius: var(--card-radius);
  color: var(--ink-2);
}
.tmm-empty__icon { display: inline-flex; color: var(--ink-faint); }
.tmm-empty .tmm-empty__text {
  color: var(--ink-2);
  font-weight: 500;
  max-width: 48ch;
}
/* Rules, dots, the meta row and the hatch (SYSTEM.md §6).
 *
 *   .tmm-rule            1px horizontal line in the soft rule (an hr or a div);
 *                        --strong draws it in the band's rule colour
 *   .tmm-rule--vertical  1px vertical line; stretches to its flex row
 *   .tmm-dot             the 4px separator dot
 *   .tmm-meta            dot-separated inline meta (render_meta_row)
 *   .tmm-hatch           the brand's hatched block: 1px diagonals on an 8px
 *                        pitch, an SVG mask over the rule colour so every
 *                        stripe is one pixel at every zoom. Size and position
 *                        belong to the caller, except for the two shapes below
 *   .tmm-hatch--end      a hatched end: the full height of its block at the
 *                        left edge (--right: the right), --hatch-size wide,
 *                        shown from 1024
 *   .tmm-hatch--cap      a hatched square, --hatch-size, in a corner of its
 *                        block (bottom right; --top / --left move it), always
 *   .tmm-hatch-room      the block that holds them: positioned, and the
 *                        padding the ends take (--hatch-size + --hatch-gap)
 *   .tmm-ruled           a block between two hairlines run to the gutter;
 *                        --no-top / --no-bottom drop one, --inset holds them
 *                        to the block's own width, --thread draws one rule
 *                        through the block's middle instead
 *   .tmm-timeline        a list hung off one vertical rule, a tick per item;
 *                        --measure turns it into the ticked measure from 768
 *   .tmm-pagination      a listing's page control on the measure: ticks,
 *                        numbers, chevron caps (render_pagination)
 *
 * Colour comes from the theme variables (--rule, --rule-soft, --ink-*), so a
 * rule on a dark band is the dark rule without a restated selector. Margins
 * are zero; the caller owns the rhythm around a rule.
 * A horizontal hairline reads var(--inner-rule, ...): the band's Inner lines
 * switch (_band.css 2) blanks it; a vertical one reads the rule directly.
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-rule {
  display: block;
  border: 0;
  width: 100%;
  height: 1px;
  margin: 0;
  padding: 0;
  background: var(--inner-rule, var(--rule-soft));
}
.tmm-rule--strong { background: var(--inner-rule, var(--rule)); }
.tmm-rule--vertical {
  width: 1px;
  height: auto;
  min-height: 1em;
  align-self: stretch;
  background: var(--rule-soft);
}
.tmm-rule--vertical.tmm-rule--strong { background: var(--rule); }

.tmm-dot {
  display: inline-block;
  flex-shrink: 0;
  width: 4px;
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--ink-faint);
}

/* Meta row: dense body size at 500, the caption ink; items never wrap
 * mid-phrase, the row wraps between items. Each item after the first
 * carries its dot (--after) and reaches 12px back over the 20px gap, so the
 * dot sits 8 from either text; an item that wraps to a new line starts
 * with its dot outside the row, where the clip hides it (2026-10-13: a dot
 * dangled at a line's end when a post card's author wrapped). */
.tmm-meta {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 20px;
  clip-path: inset(0);
  font-family: var(--font-sans);
  font-size: var(--type-dense);
  font-weight: 500;
  line-height: 1.4;
  color: var(--ink-2);
}
.tmm-meta__item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.tmm-meta__item--after { margin-left: -12px; }
.tmm-meta__item--after > .tmm-dot { margin-right: 2px; }
.tmm-meta__icon { color: var(--ink-muted); }

.tmm-hatch {
  display: block;
  background-color: var(--rule);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cpath d='M-2 2 L2 -2 M0 8 L8 0 M6 10 L10 6' stroke='black' stroke-width='1'/%3E%3C/svg%3E") 0 0 / 8px 8px repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cpath d='M-2 2 L2 -2 M0 8 L8 0 M6 10 L10 6' stroke='black' stroke-width='1'/%3E%3C/svg%3E") 0 0 / 8px 8px repeat;
}

/* ── Hatched ends and caps (2026-10-12) ────────────────────────────────
 * The one way to close a block with the hatch (the CTA banner's offer, the
 * thin bar, the hatched cap). Markup:
 *   div.tmm-hatch-room[.tmm-hatch-room--left|--right|--cap]
 *     span.tmm-hatch.tmm-hatch--end                      aria-hidden
 *     …the block's content…
 *     span.tmm-hatch.tmm-hatch--end.tmm-hatch--right     aria-hidden
 * or one span.tmm-hatch.tmm-hatch--cap[.tmm-hatch--top][.tmm-hatch--left].
 * --hatch-size (48px) is the end's width and the cap's side; --hatch-gap
 * (32px) the clear space between the hatch and the content. Set either on
 * the room (the offer's 64px ends: --hatch-size: 64px). The ends and their
 * room appear from 1024 (under it the block is the content alone); a cap
 * and its room are there at every width. The hatch is --rule, so it reads
 * on light, dark and brand alike. The block's rules are the caller's
 * (.tmm-ruled), and an end runs the block's full height between them. */
.tmm-hatch-room { --hatch-size: 48px; --hatch-gap: 32px; position: relative; }
.tmm-hatch.tmm-hatch--end {
  display: none;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--hatch-size, 48px);
  pointer-events: none;
}
.tmm-hatch.tmm-hatch--end.tmm-hatch--right { left: auto; right: 0; }
.tmm-hatch.tmm-hatch--cap {
  position: absolute;
  right: 0;
  bottom: 0;
  width: var(--hatch-size, 48px);
  height: var(--hatch-size, 48px);
  pointer-events: none;
}
.tmm-hatch.tmm-hatch--cap.tmm-hatch--top { bottom: auto; top: 0; }
.tmm-hatch.tmm-hatch--cap.tmm-hatch--left { right: auto; left: 0; }
.tmm-hatch-room--cap { padding-right: calc(var(--hatch-size) + var(--hatch-gap)); }
.tmm-hatch-room--cap:has(> .tmm-hatch--cap.tmm-hatch--left) { padding-right: 0; padding-left: calc(var(--hatch-size) + var(--hatch-gap)); }
@media (min-width: 1024px) {
  .tmm-hatch.tmm-hatch--end { display: block; }
  .tmm-hatch-room:not(.tmm-hatch-room--cap) { padding-inline: calc(var(--hatch-size) + var(--hatch-gap)); }
  .tmm-hatch-room.tmm-hatch-room--left { padding-right: 0; }
  .tmm-hatch-room.tmm-hatch-room--right { padding-left: 0; }
}

/* ── The ruled block ───────────────────────────────────────────────────
 * A block drawn between two hairlines that run to the page's margin (the
 * gutter), not just the content edge: the hero's split row, the platform
 * tabs' crossing, the CTA banner, the image card. The band's own rules stay
 * at the section's edges; this is the block's. --no-bottom for a block
 * that meets a card's edge, --no-top for one that sits under one. */
.tmm-ruled { position: relative; }
.tmm-ruled::before,
.tmm-ruled::after {
  content: '';
  position: absolute;
  left: calc(-1 * var(--gutter, 32px));
  right: calc(-1 * var(--gutter, 32px));
  height: 1px;
  background: var(--inner-rule, var(--rule));
  pointer-events: none;
}
.tmm-ruled::before { top: 0; }
.tmm-ruled::after { bottom: 0; }
.tmm-ruled--no-bottom::after { display: none; }
.tmm-ruled--no-top::before { display: none; }

/* The inset ruled block: the same two hairlines held to the block's own
 * width, for a column, a panel or a copy block inside a grid (gated-form,
 * stories, video). Combine with --no-top / --no-bottom for one edge. */
.tmm-ruled--inset::before,
.tmm-ruled--inset::after { left: 0; right: 0; }

/* The thread: one hairline through the middle of a row of opaque tiles, run
 * to the gutter and drawn behind them, so it shows only in the gaps and
 * either side of the row and the tiles read as one object (security's
 * threaded certifications). Added 2026-10-08 in the drift sweep. */
.tmm-ruled--thread::before { top: 50%; z-index: 0; }
.tmm-ruled--thread::after { display: none; }
.tmm-ruled--thread > * { position: relative; z-index: 1; }


/* ── The timeline and the measure ──────────────────────────────────────
 * Markup:  ol.tmm-timeline[.tmm-timeline--measure] > li
 * The timeline: one vertical hairline the list's full height on its left
 * edge, the items hung --timeline-indent inside it, a tick of --tick-len
 * from the rule to each item at --tick-top (the middle of the item's first
 * line). The caller sets --tick-top to its head row (deployment-steps'
 * index timeline). The measure: the same list under 768; from 768 the rule
 * runs across the list at 52px, each item stands a 25px tick at its left
 * edge from 40px and one more closes the end, the items in --measure-cols
 * equal columns; the caller's head row is 40px tall so its figure stands
 * on the tick (deployment-steps' measure). Added 2026-10-08. */
.tmm-timeline {
  --timeline-indent: 56px;
  --tick-len: 40px;
  --tick-top: 18px;
  position: relative;
  padding-left: var(--timeline-indent);
}
.tmm-timeline::before,
.tmm-timeline > *::before,
.tmm-timeline--measure::after {
  content: '';
  position: absolute;
  background: var(--rule);
  pointer-events: none;
}
.tmm-timeline::before { left: 0; top: 0; bottom: 0; width: 1px; }
.tmm-timeline > * { position: relative; }
.tmm-timeline > * + * { margin-top: var(--timeline-gap, 56px); }
.tmm-timeline > *::before { left: calc(-1 * var(--timeline-indent)); top: var(--tick-top); width: var(--tick-len); height: 1px; }
.tmm-timeline--measure { --timeline-indent: 40px; --tick-len: 16px; --timeline-gap: 48px; }
.tmm-timeline--measure::after { display: none; }
@media (min-width: 768px) {
  .tmm-timeline--measure { display: grid; grid-template-columns: repeat(var(--measure-cols, 3), minmax(0, 1fr)); padding-left: 0; }
  .tmm-timeline--measure::before { top: 52px; bottom: auto; right: 0; width: auto; height: 1px; }
  .tmm-timeline--measure::after { display: block; right: 0; top: 40px; width: 1px; height: 25px; }
  .tmm-timeline--measure > * { padding: 0 40px 0 16px; }
  .tmm-timeline--measure > * + * { margin-top: 0; }
  .tmm-timeline--measure > *::before { left: 0; top: 40px; width: 1px; height: 25px; }
}

/* ── The pagination: ticks on a measure ─────────────────────────────────
 * Markup (render_pagination): nav.tmm-pagination > cap, ol.tmm-pagination__measure
 * .tmm-timeline--measure > li > a|span.tmm-pagination__num, cap. The measure
 * is horizontal at every width: one hairline at 24px across the stations
 * (12px past them each side), a 1 x 12 tick standing on it per page, the
 * current 2 x 24 in the accent, the number under its tick. Stations are
 * 56px and give down to 20px, so nine stations and the caps fit 390. The
 * chevron caps (ghost sm, 32px) are centred on the rule. Rowan, 2026-10-09. */
.tmm-pagination { display: flex; align-items: flex-start; justify-content: center; }
.tmm-pagination__cap { flex: none; margin-top: 8px; }
.tmm-pagination__measure.tmm-timeline--measure { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(20px, 56px); grid-template-columns: none; flex: 0 1 auto; min-width: 0; margin: 0; padding: 0 12px; list-style: none; }
.tmm-pagination__measure.tmm-timeline--measure::before { top: 24px; bottom: auto; left: 0; right: 0; width: auto; height: 1px; }
.tmm-pagination__measure.tmm-timeline--measure::after { display: none; }
.tmm-pagination__measure.tmm-timeline--measure > li { min-width: 0; margin: 0; padding: 0; }
.tmm-pagination__measure.tmm-timeline--measure > li::before { left: 50%; top: 12px; width: 1px; height: 12px; transition: var(--transition-hover); }
.tmm-pagination__measure.tmm-timeline--measure > li.is-current::before { top: 0; width: 2px; height: 24px; margin-left: -1px; background: var(--accent); }
.tmm-pagination__measure.tmm-timeline--measure > li.tmm-pagination__gap::before { background: var(--rule-soft); }
.tmm-pagination__num { display: block; height: 64px; padding-top: 32px; text-align: center; font-weight: 500; font-variant-numeric: tabular-nums; color: var(--ink-2); text-decoration: none; transition: var(--transition-hover); }
.tmm-pagination__num[aria-current] { color: var(--ink); }
.tmm-pagination__gap .tmm-pagination__num { color: var(--ink-faint); }
a.tmm-pagination__num:hover,
a.tmm-pagination__num.is-hover,
a.tmm-pagination__num:focus-visible,
a.tmm-pagination__num.is-focus { color: var(--ink); }
.tmm-pagination__measure.tmm-timeline--measure > li:has(> a:hover)::before,
.tmm-pagination__measure.tmm-timeline--measure > li:has(> a.is-hover)::before,
.tmm-pagination__measure.tmm-timeline--measure > li:has(> a:focus-visible)::before,
.tmm-pagination__measure.tmm-timeline--measure > li:has(> a.is-focus)::before { background: var(--ink); }
/* Quote — testimonial and pull quote. Output of
 * templates/macros/lists.html -> render_quote.
 *
 * md is the callout size at 400 (testimonial cards); lg is the sub-title
 * step (--type-h3, 20 to 26, 500, tight tracking) for a featured testimonial,
 * so it never matches the section heading; xl is the
 * pull quote set at a measure, at the h2 size and 400, because a long
 * sentence at a heading weight shouts. No bar, no italic: the quote marks do
 * the work. The editorial blockquote inside .blog-prose keeps its own rule
 * in _prose.css.
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-quote {
  margin: 0;
  padding: 0;
  border: 0;
  color: var(--ink);
}
.tmm-quote__text {
  font-family: var(--font-sans);
  font-size: var(--type-callout);
  font-weight: 400;
  line-height: 1.55;
  margin: 0;
  color: inherit;
}
.tmm-quote--lg .tmm-quote__text {
  font-size: var(--type-h3);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.02em;
}
.tmm-quote--xl .tmm-quote__text {
  font-size: var(--type-h2);
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: -0.02em;
}
.tmm-quote__cite {
  display: block;
  margin-top: var(--text-gap-sm);
  font-family: var(--font-sans);
  font-size: var(--type-dense);
  font-weight: 500;
  font-style: normal;
  line-height: 1.4;
  color: var(--ink-2);
}
.tmm-quote--lg .tmm-quote__cite,
.tmm-quote--xl .tmm-quote__cite { font-size: var(--type-callout); }
/* Logo tile — a brand mark as an image, or an icon + wordmark fallback.
 * Output of templates/macros/lists.html -> render_logo_tile.
 *
 * Sizes cap the image height and scale the wordmark and the icon plate:
 * sm 40, md 64, lg 100. Image logos render in their own colour by default;
 * modifiers neutralise them:
 *   --mute  greyscale at rest, colour on hover (a row can return the colour
 *           for every tile with .is-hover)
 *   --grey  editorial greyscale, softened, held on hover
 *   --tint  the mark drawn in the text ladder (its alpha masks currentColor):
 *           the muted ink, no hover: a logo is not a control (Rowan, 2026-10-02).
 *           render_logo_tile(…, tint=true) puts the image URL in --logo-src;
 *           the img stays for size and alt but is invisible.
 * On dark bands every image logo goes white: the one allowed filter rule.
 *
 * The lockup spec (Rowan, 2026-10-09; the logo carousel round):
 *   the box     a caller sets --logo-box (one height for the row: 28px in
 *               a marquee, 24px in a list) and --logo-cap (96px): every
 *               mark is that high at its own aspect, a wide one contained
 *               in the cap
 *   --wordmark  a file that spells the name (chosen, or wider than 2.5 : 1):
 *               in a lockup it stands alone, the name in its alt
 *   --padded    a square icon file with the mark in its middle band: the
 *               box takes a 16 : 9 band and the image covers it
 *   --lockup    mark, 12px, the name in a body role at 500 (__label); with
 *               no file the name alone in the same slot
 *   --no-mark   the library mark 404'd: the img hidden, the name alone
 *   --quiet     the row's treatment: on light the mark grey at 70% and the
 *               name in --ink-2; on dark the mark white and the name in
 *               --ink, both at 70%; hover lifts to full opacity and --ink,
 *               nothing else (a caller tunes the dark name's rest with
 *               --logo-label-rest)
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  max-width: 100%;
  color: inherit;
}
.tmm-logo__img {
  display: block;
  max-height: 64px;
  max-width: var(--logo-cap, 100%);
  width: auto;
  height: var(--logo-box, auto);
  object-fit: contain;
  transition: filter var(--pop-duration) var(--pop-ease), opacity var(--pop-duration) var(--pop-ease);
}
.tmm-logo--sm .tmm-logo__img { max-height: 40px; }
.tmm-logo--md .tmm-logo__img { max-height: 64px; }
.tmm-logo--lg .tmm-logo__img { max-height: 100px; }

.tmm-logo--mute .tmm-logo__img { filter: grayscale(100%); }
.tmm-logo--mute:hover .tmm-logo__img,
.tmm-logo--mute.is-hover .tmm-logo__img { filter: grayscale(0%); }
.tmm-logo--grey .tmm-logo__img,
.tmm-logo--grey:hover .tmm-logo__img,
.tmm-logo--grey.is-hover .tmm-logo__img { filter: grayscale(100%); opacity: 0.8; }

/* The padded icon file: a 16 : 9 band of the square, cropped. */
.tmm-logo--padded .tmm-logo__img { aspect-ratio: 16 / 9; object-fit: cover; }

/* The lockup: mark, then the name in a body role. */
.tmm-logo--lockup { gap: 12px; }
.tmm-logo__label { font-weight: 500; white-space: nowrap; color: inherit; }
/* A wordmark carries the name in its alt; a mark that failed to load
 * (--no-mark, set by the img's error handler) leaves the name alone. */
.tmm-logo--lockup.tmm-logo--wordmark .tmm-logo__label { display: none; }
.tmm-logo--no-mark .tmm-logo__img { display: none; }
.tmm-logo--lockup.tmm-logo--no-mark .tmm-logo__label { display: inline; }

/* Quiet: the row's rest and hover (the carousel, the integrations ledger). */
.tmm-logo--quiet { color: var(--ink-2); transition: var(--transition-hover); }
.tmm-logo--quiet > * { transition: opacity var(--transition-pop), filter var(--transition-pop); }
.tmm-logo--quiet .tmm-logo__img { filter: grayscale(100%); opacity: 0.7; }
.tmm-logo--quiet:hover,
.tmm-logo--quiet.is-hover { color: var(--ink); }
.tmm-logo--quiet:hover > *,
.tmm-logo--quiet.is-hover > * { opacity: 1; }
.section--dark .tmm-logo--quiet,
.card--island-dark .tmm-logo--quiet { color: var(--ink); }
.section--dark .tmm-logo--quiet > *,
.card--island-dark .tmm-logo--quiet > * { opacity: 0.7; }
.section--dark .tmm-logo--quiet > .tmm-logo__label,
.card--island-dark .tmm-logo--quiet > .tmm-logo__label { opacity: var(--logo-label-rest, 0.7); }
.section--dark .tmm-logo--quiet:hover > *,
.section--dark .tmm-logo--quiet.is-hover > *,
.card--island-dark .tmm-logo--quiet:hover > *,
.card--island-dark .tmm-logo--quiet.is-hover > * { opacity: 1; }

/* Dark bands: image logos go white. */
.section--dark .tmm-logo__img,
.section--dark .tmm-logo:hover .tmm-logo__img,
.section--dark .tmm-logo.is-hover .tmm-logo__img,
.card--island-dark .tmm-logo__img { filter: brightness(0) invert(1); }

/* Wordmark fallback: logo geometry, not a type role; scales with the tile. */
.tmm-logo__name {
  font-family: var(--font-sans);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1;
  white-space: nowrap;
  color: inherit;
}
.tmm-logo--sm .tmm-logo__name { font-size: var(--type-heading); }
.tmm-logo--md .tmm-logo__name { font-size: var(--type-title); }
.tmm-logo--lg .tmm-logo__name { font-size: clamp(24px, 1.2rem + 0.8vw, 32px); }

/* Tinted marks: the ladder's grey instead of the file's colours; no hover. */
.tmm-logo--tint { position: relative; color: var(--ink-muted); }
.tmm-logo--tint .tmm-logo__img { opacity: 0; filter: none; }
.tmm-logo--tint::after {
  content: '';
  position: absolute;
  inset: 0;
  background: currentColor;
  -webkit-mask: var(--logo-src) center / contain no-repeat;
          mask: var(--logo-src) center / contain no-repeat;
}
.section--dark .tmm-logo--tint .tmm-logo__img { filter: none; }
/* On dark and charcoal the tinted marks take the second ink, a step brighter
 * than the muted one, so a logo row reads clearly (Rowan, 2026-10-13: the
 * dark logo bar's contrast was low). */
.section--dark .tmm-logo--tint { color: var(--ink-2); }
.tmm-logo--tint.tmm-logo--padded::after { -webkit-mask-size: cover; mask-size: cover; }
/* Social links — the row of brand marks. Output of
 * templates/macros/lists.html -> render_social_links; marks from
 * templates/partials/brand-icons.html.
 *
 * Each link is a 36px circle on the sunken ground with the ink, stepping to
 * the hover ground under the cursor. In the footer (and on any dark band)
 * the circle at rest is the raised dark ground with the caption ink. Hover is
 * the same on every ground: the band's hover ground and its ink. Brand is the
 * current page's mark, never a hover (Rowan, 2026-10-08: the blue wash was
 * the wrong highlight). On the dark grounds the hover ground is the rest
 * ground, so the lift is the ink stepping from caption to bright.
 * The footer is not a band, so it states the two dark variables the hover
 * reads (the charcoal set, components/_band.css).
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-socials {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  list-style: none;
  padding: 0;
  margin: 0;
}
.tmm-socials__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-full);
  background: var(--ground-sunken);
  color: var(--ink);
  text-decoration: none;
  transition: var(--transition-hover);
}
.tmm-socials__link:hover,
.tmm-socials__link.is-hover { background: color-mix(in oklab, var(--state-tone) var(--state-hover-amount), var(--ground-sunken)); color: var(--ink); }
.tmm-socials__link:focus-visible,
.tmm-socials__link.is-focus { outline: none; box-shadow: var(--shadow-focus-strong); }
.tmm-socials__link svg { display: block; pointer-events: none; }

/* Footer: 24px under the tagline, on the chrome's dark ground. */
.tmm-footer .tmm-socials {
  margin: 24px 0 0;
  --ink: var(--color-dark-ink);
  --state-tone: var(--ink);
}
.section--dark .tmm-socials__link,
.tmm-footer .tmm-socials__link { background: color-mix(in oklab, var(--color-dark-ink) 6%, var(--color-charcoal)); color: var(--color-dark-ink-2); }
.section--dark .tmm-socials__link:hover,
.section--dark .tmm-socials__link.is-hover,
.tmm-footer .tmm-socials__link:hover,
.tmm-footer .tmm-socials__link.is-hover { background: color-mix(in oklab, var(--color-dark-ink) 12%, var(--color-charcoal)); color: var(--ink); }

/* The global a:hover / a:focus (0,3,1, elements/_typography.css) out-ranks
 * every colour above, so the glyph turned --link-hover under the cursor and
 * stayed that way while focused (a new-tab link keeps focus on return).
 * These restate the colours at 0,3,3 / 0,4,3. */
ul.tmm-socials > li > a.tmm-socials__link:focus { color: var(--ink); }
ul.tmm-socials > li > a.tmm-socials__link:hover,
ul.tmm-socials > li > a.tmm-socials__link.is-hover { color: var(--ink); }
.section--dark ul.tmm-socials > li > a.tmm-socials__link:focus,
.tmm-footer ul.tmm-socials > li > a.tmm-socials__link:focus { color: var(--color-dark-ink-2); }
.section--dark ul.tmm-socials > li > a.tmm-socials__link:hover,
.section--dark ul.tmm-socials > li > a.tmm-socials__link.is-hover,
.tmm-footer ul.tmm-socials > li > a.tmm-socials__link:hover,
.tmm-footer ul.tmm-socials > li > a.tmm-socials__link.is-hover { color: var(--ink); }
/* Page header — eyebrow / title / lead for standalone pages (system pages,
 * search). Output of templates/macros/lists.html -> render_page_header.
 * Type is the shared roles (.t-eyebrow, .t-h2, .t-body-lg); this file owns
 * the stack rhythm, alignment and the lead's muted ink.
 * HubL is parsed in CSS files: no template syntax here, even in comments. */

.tmm-page-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  margin-bottom: 40px;
}
.tmm-page-head--left { align-items: flex-start; text-align: left; }
.tmm-page-head__eyebrow { margin: 0 0 var(--text-gap-sm); }
.tmm-page-head__eyebrow .t-eyebrow { margin: 0; }
.tmm-page-head__title { margin: 0 0 var(--text-gap-sm); }
.tmm-page-head__lead {
  color: var(--ink-body);
  max-width: 48ch;
  margin: 0;
}
/* ============================================================================
   Info tip
   ----------------------------------------------------------------------------
   Reusable "i" affordance with a popover of helper text. Render via
   render_info_tip(text) (templates/macros/icons.html).

   Opens on hover on pointer devices (CSS) AND on tap/click on any device
   (js/main.js -> initInfoTips, which toggles .is-open on .tmm-info, sets
   aria-expanded on .tmm-info__btn, and nudges .tmm-info__pop horizontally
   via an inline transform so it never overflows the viewport). The popover
   is the app's ink tooltip (nav-ground / nav-ink) so it reads on both light
   and dark sections; the trigger takes its colour from the ground's theme
   variables (css/components/_band.css): --ink-muted at rest, --ink on
   hover and focus, written once and resolved per ground.

   The 5px arrow is drawn on .tmm-info::after (the wrapper), not on the
   popover, so it stays centred on the trigger even when the JS shifts the
   popover sideways.

   Lookbook force classes: .is-hover on .tmm-info__btn / .tmm-info,
   .is-focus on .tmm-info__btn.
   ============================================================================ */
.tmm-info {
  position: relative;
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
}

/* Trigger — 16px circle, muted ink, steps up to primary ink on hover. */
.tmm-info__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: none;
  cursor: help;
  color: var(--ink-muted);
  transition: var(--transition-hover);
  -webkit-tap-highlight-color: transparent;
}

.tmm-info__btn.is-hover { color: var(--ink); }

.tmm-info__btn:focus-visible,
.tmm-info__btn.is-focus {
  outline: none;
  box-shadow: var(--shadow-focus-soft);
  color: var(--ink);
}

.tmm-info__btn .lucide,
.tmm-info__btn svg { display: block; width: 16px; height: 16px; }

/* Popover — ink tooltip. Resets tracking/transform/alignment because the
 * tip often sits inside an uppercase label. */
.tmm-info__pop {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  width: min(248px, calc(100vw - 32px));
  padding: 6px 11px;
  border: 0;
  border-radius: 8px;
  background: var(--color-charcoal);
  color: var(--color-dark-ink);
  box-shadow: var(--shadow-tooltip);
  font: 500 var(--type-dense) / 20px var(--font-sans);
  letter-spacing: 0;
  text-transform: none;
  text-align: left;
  white-space: normal;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--pop-duration) var(--pop-ease);
  z-index: 50;
  pointer-events: none;
}

/* Arrow — 5px, sits in the 10px gap between trigger and popover. */
.tmm-info::after {
  content: "";
  position: absolute;
  bottom: calc(100% + 5px);
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-bottom-width: 0;
  border-top-color: var(--color-charcoal);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--pop-duration) var(--pop-ease);
  z-index: 50;
  pointer-events: none;
}

/* Open on tap/click (all devices) — toggled by JS. .is-hover is the lookbook
 * freeze for the hover-open state on any device. */
.tmm-info.is-open .tmm-info__pop,
.tmm-info.is-open::after,
.tmm-info.is-hover .tmm-info__pop,
.tmm-info.is-hover::after { opacity: 1; visibility: visible; }

/* Open on hover — pointer devices only, so it can't stick after a touch. */
@media (hover: hover) {
  .tmm-info__btn:hover { color: var(--ink); }
  .tmm-info:hover .tmm-info__pop,
  .tmm-info:hover::after { opacity: 1; visibility: visible; }
}

/* Toast: the same ink material for a short message a script shows, not a
 * hover tip (the blog's "Link copied" under its share buttons). Markup:
 * span.tmm-info__pop.tmm-info__pop--toast inside a positioned parent; it
 * sits under the parent's bottom-left corner, sized to its text, and the
 * script adds .is-shown. Fades only. Added 2026-10-08 (drift sweep,
 * templates): replaces blog.css's own toast. */
.tmm-info__pop--toast {
  top: calc(100% + 8px);
  bottom: auto;
  left: 0;
  transform: none;
  width: max-content;
}
.tmm-info__pop--toast.is-shown { opacity: 1; visibility: visible; }
/* Editorial body prose: the reading type for any rich-text body (blog
 * posts, case studies, legal pages) and the blog-* modules inside it.
 * Lives in main.css so .blog-prose is available on every template.
 *
 * Decided 2026-10-12 (Rowan: "we need the blog to be really readable",
 * "typography legible and perfectly spaced with correct usage of scale").
 * The ladder, on the 4px grid, every step a role or a --type-* token:
 *
 *   measure    min(680px, 55ch)  64-70 characters a line (the ch is the
 *              "0", 10px at 18: 55ch is 554px; 66ch measured 77-83)
 *   body       18 / 30   400  --ink     24 between paragraphs
 *   h2         32 / 40   650  --ink     48 above, 16 below
 *   h3         24 / 32   650  --ink     40 above, 12 below
 *   h4         20 / 28   650  --ink     32 above,  8 below
 *   lists      24 indent, 8 between items, ol markers --accent-text tabular
 *   strong     600 --ink
 *   links      --link-hover, a 1px underline 3 under; hover --ink
 *   blocks     --prose-gap 40 above and below every block (figure, table,
 *              quote, code, rule, the blog-* block modules)
 *   captions   14 / 20 --ink-2, 12 under the picture
 *
 * Colour is the band's (css/components/_band.css), so prose on a dark
 * island needs no override. Measured on the article's --ground-sunken:
 * body 14.87 : 1, links 6.36 : 1, captions 7.22 : 1. Full spec: OPERATIONS.md §8 >
 * Typography. */

.blog-prose {
  --measure-prose: min(680px, 55ch);
  --prose-gap: 40px;
  max-width: var(--measure-prose);
  color: var(--ink);
  font-size: var(--type-lead);
  font-weight: 400;
  line-height: 30px;
  letter-spacing: -0.011em;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}
/* A .blog-prose nested in another (blog-paragraph, blog-quote) fills it. */
.blog-prose .blog-prose { max-width: none; }

/* Descendant selectors (no `>`): the RTE nests the body in .hs_cos_wrapper
 * spans and the blog-* modules nest their own copy. */
.blog-prose p {
  font-size: var(--type-lead);
  font-weight: 400;
  line-height: 30px;
  letter-spacing: -0.011em;
  color: var(--ink);
  margin: 0 0 24px;
  text-wrap: pretty;          /* no one-word last line */
}

/* ── Links: always underlined, 1px, 3 under the baseline ─────────────── */
.blog-prose a {
  color: var(--link-hover);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: currentColor;
  transition: var(--transition-hover);
}
.blog-prose a:hover,
.blog-prose a.is-hover,
.blog-prose a:focus-visible,
.blog-prose a.is-focus { color: var(--ink); }

.blog-prose strong,
.blog-prose b   { font-weight: 600; color: var(--ink); }
.blog-prose em  { font-style: italic; }

/* ── Lists ───────────────────────────────────────────────────────────── */
.blog-prose ul,
.blog-prose ol {
  margin: 0 0 24px;
  padding-left: 24px;
}
.blog-prose li {
  margin: 0;
  font-size: var(--type-lead);
  font-weight: 400;
  line-height: 30px;
  letter-spacing: -0.011em;
  color: var(--ink);
  text-wrap: pretty;
}
.blog-prose li + li { margin-top: 8px; }
.blog-prose li > p { margin: 0; }
.blog-prose li > ul,
.blog-prose li > ol { margin: 8px 0 0; }
.blog-prose ul > li::marker { color: var(--ink-2); }
.blog-prose ol > li::marker {
  color: var(--accent-text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ── Headings: the same measure, tight to what follows ───────────────── */
.blog-prose .blog-heading,
.blog-prose h2,
.blog-prose h3,
.blog-prose h4 {
  font-family: var(--font-sans);
  font-weight: 650;
  letter-spacing: -0.02em;
  color: var(--ink);
  scroll-margin-top: 128px;
  text-wrap: balance;
  break-after: avoid;
}
.blog-prose .blog-heading--h2,
.blog-prose h2 {
  font-size: var(--type-h3);
  line-height: 40px;
  margin: 48px 0 16px;
}
.blog-prose .blog-heading--h3,
.blog-prose h3 {
  font-size: var(--type-title);
  line-height: 32px;
  letter-spacing: -0.015em;
  margin: 40px 0 12px;
}
.blog-prose h4 {
  /* audit-allow: the one step between the 18 body and the 24 h3 (OPERATIONS.md §8 > Typography) */
  font-size: 20px;
  line-height: 28px;
  letter-spacing: -0.011em;
  margin: 32px 0 8px;
}
/* A heading under a heading keeps the smaller pair gap, not a second break. */
.blog-prose :is(h2, h3, .blog-heading) + :is(h3, h4, .blog-heading--h3) { margin-top: 0; }

/* ── Blocks: one rhythm, --prose-gap above and below ─────────────────── */
.blog-prose img {
  display: block;
  max-width: 100%;
  height: auto;
}
.blog-prose p > img { margin-inline: auto; }
.blog-prose figure { margin: var(--prose-gap) 0; }
.blog-prose figure img { width: 100%; }
.blog-prose figcaption {
  margin-top: 12px;
  font-size: var(--type-dense);
  line-height: 1.4;
  letter-spacing: 0;
  color: var(--ink-2);
}

/* Tables: the app's table rules (css/elements/_tables.css) in a box that
 * scrolls sideways. HubSpot's RTE wraps a table in [data-hs-responsive-table];
 * a bare table scrolls itself. */
.blog-prose [data-hs-responsive-table] {
  margin: var(--prose-gap) 0;
  overflow-x: auto;
  max-width: 100%;
}
.blog-prose table {
  margin: var(--prose-gap) 0;
  font-variant-numeric: tabular-nums;
}
.blog-prose :not([data-hs-responsive-table]) > table {
  display: block;
  overflow-x: auto;
  max-width: 100%;
}
.blog-prose [data-hs-responsive-table] > table { margin: 0; }
.blog-prose td > p,
.blog-prose th > p { margin: 0; font-size: inherit; line-height: inherit; color: inherit; }

/* Blockquote: the editorial pull. A 2px rule down the left. */
.blog-prose blockquote {
  border-left: 2px solid var(--rule);
  padding: 8px 0 8px 32px;
  margin: var(--prose-gap) 0;
  font-style: italic;
  font-weight: 400;
  font-size: var(--type-title);
  line-height: 32px;
  letter-spacing: -0.011em;
  color: var(--ink);
}
.blog-prose blockquote p { font-size: inherit; line-height: inherit; color: inherit; }
.blog-prose blockquote p:last-child { margin-bottom: 0; }
@media (min-width: 768px) {
  .blog-prose blockquote { font-size: var(--type-h3); line-height: 40px; }
}

/* Code: inline and block. Sunken fill with the hairline ring (a shadow,
 * never a border); inline sits a hair below the body size. */
.blog-prose code,
.blog-prose pre {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  background: var(--ground-sunken);
  border: 0;
  box-shadow: var(--ring);
  color: var(--ink);
}
.blog-prose code {
  font-size: 0.85em;
  padding: 0.1em 0.4em;
  border-radius: 6px;
}
.blog-prose pre {
  font-size: var(--type-body);
  line-height: 24px;
  padding: 20px 24px;
  margin: var(--prose-gap) 0;
  border-radius: var(--radius-lg);
  overflow-x: auto;
}
.blog-prose pre code {
  font-size: inherit;
  padding: 0;
  border: 0;
  box-shadow: none;
  border-radius: 0;
  background: none;
}

/* Horizontal rule: a hairline in the soft rule tone. */
.blog-prose hr {
  border: 0;
  border-top: 1px solid var(--rule-soft);
  margin: var(--prose-gap) 0;
}

/* ── The editorial lead ──────────────────────────────────────────────────
 * One step above the prose body: the summary under an article title, the
 * first paragraph of a post or case study, the lead copy on an editorial
 * page (about). Fluid 19 → 22, 400, the reading ink (7.22 : 1 on the
 * article's ground). Two hooks, one look:
 *   .prose-lead          on the paragraph itself (template markup)
 *   .blog-prose--lead    on a .blog-prose container whose first paragraph is
 *                        editor richtext and cannot carry a class */
.prose-lead,
.blog-prose--lead > p:first-child,
.blog-prose--lead .blog-paragraph:first-child p:first-child {
  font-family: var(--font-sans);
  font-size: clamp(19px, 1.05rem + 0.4vw, 22px);
  font-weight: 400;
  line-height: 1.6;
  letter-spacing: -0.011em;
  color: var(--ink-body);
  margin: 0;
}
.blog-prose--lead > p:first-child,
.blog-prose--lead .blog-paragraph:first-child p:first-child { margin-bottom: 32px; }
.prose-lead strong { font-weight: 600; color: var(--ink); }
/* The popup kit (OPERATIONS.md §9): .tmm-popup on a native <dialog>, rendered closed at the end of
 * <body> by templates/partials/popups.html and opened by js/popups.js. The box is the app card
 * (.card .card--light: the surface, the card edge, the 20 radius) raised to pop
 * (--card-shadow-pop), centred over the --ink backdrop at .4. Sizes: sm 480, md 640, lg 880;
 * on phones the viewport less 16 a side. The dialog never exceeds the viewport less 32: the
 * body scrolls inside it and the close button (render_icon_button, the Lucide x, ghost md)
 * stays in the top-right corner. Colour comes from the card's variables; nothing moves: the
 * dialog fades in on opacity alone (the pop timing), and not at all under reduced motion.
 * The page behind does not scroll or jump: the root is held at overflow hidden while a popup
 * is open, and scrollbar-gutter (_header.css) keeps its width.
 * .tmm-popup--static draws the same box in the flow, open, for the lookbook. */

/* The card raised to pop at rest and in every card state (.card--static restates --card-shadow on hover). */
.tmm-popup { --popup-w: 480px; --card-shadow: var(--card-shadow-pop); --card-shadow-hover: var(--card-shadow-pop); }
.tmm-popup--md { --popup-w: 640px; }
.tmm-popup--lg { --popup-w: 880px; }

dialog.tmm-popup {
  position: fixed;
  inset: 0;
  box-sizing: border-box;
  width: min(var(--popup-w), calc(100vw - 32px));
  max-width: none;
  height: fit-content;
  max-height: calc(100dvh - 32px);
  margin: auto;
  padding: 0;
  overflow: hidden;
  color: var(--ink);
  text-align: left;
  box-shadow: var(--card-shadow-pop);
}
dialog.tmm-popup[open] {
  display: flex;
  flex-direction: column;
  opacity: 1;
  transition: opacity var(--transition-pop);
}
@starting-style {
  dialog.tmm-popup[open] { opacity: 0; }
}
dialog.tmm-popup::backdrop { background: color-mix(in srgb, var(--ink, var(--color-ink)) 40%, transparent); }

/* The page behind holds still while a popup is open (js/popups.js sets the class; :has covers a
 * dialog opened by anything else). */
html.has-tmm-popup,
html:has(dialog.tmm-popup[open]) { overflow: hidden; }

.tmm-popup__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--card-padding);
}
.tmm-popup__close { position: absolute; top: 16px; right: 16px; z-index: 1; }

/* The content the partials draw (popups/*.html), on the type roles. */
.tmm-popup :where(p, h2, ol, ul) { margin: 0; }
.tmm-popup :where(ol) { list-style: none; padding: 0; }
.tmm-popup .t-eyebrow { display: flex; margin-bottom: 8px; }
.tmm-popup__title { margin: 0 44px 16px 0; color: var(--ink); }

/* The ledger (tiered pricing): the head on the rule, the bands on soft hairlines, 44 tall on
 * phones and 32 from 768, the band left and the price right at 600, the current band on the
 * wash with ink. */
.tmm-popup__ledger-head { display: flex; justify-content: space-between; gap: 16px; padding: 0 12px 6px; color: var(--ink-2); box-shadow: inset 0 -1px 0 var(--rule); }
.tmm-popup__band { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 44px; padding: 0 12px; color: var(--ink); box-shadow: inset 0 -1px 0 var(--rule-soft); }
.tmm-popup__band.is-current { background: var(--accent-wash); }
.tmm-popup__band-price { font-weight: 600; font-variant-numeric: tabular-nums; }
.tmm-popup__note { margin-top: 8px; padding-inline: 12px; color: var(--ink-2); }

/* The offer (pilot): the square check list under the heading, the price on the baseline
 * lockup 20 under it. */
.tmm-popup__points { margin-bottom: 20px; }
.tmm-popup__price { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 12px; }
.tmm-popup__figure { color: var(--ink); font-variant-numeric: tabular-nums; }
.tmm-popup__price-unit { color: var(--ink-2); }

/* The one button under a soft hairline, its line 12 over it; full width on phones. */
.tmm-popup__cta { display: grid; justify-items: start; row-gap: 12px; margin-top: 16px; padding-top: 16px; box-shadow: inset 0 1px 0 var(--rule-soft); }
.tmm-popup__ask { color: var(--ink-body); }
.tmm-popup__cta .btn { width: 100%; justify-content: center; }

/* The lookbook's open, static copy: the same box in the flow. */
.tmm-popup--static {
  position: relative;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  width: min(var(--popup-w), 100%);
  margin: 0 auto;
  padding: 0;
  color: var(--ink);
  text-align: left;
  box-shadow: var(--card-shadow-pop);
}

@media (min-width: 640px) {
  .tmm-popup__cta .btn { width: auto; }
}
@media (min-width: 768px) {
  .tmm-popup__band { min-height: 32px; }
}
@media (prefers-reduced-motion: reduce) {
  dialog.tmm-popup[open] { transition: none; }
}
/* Menu and simple menu */

.hs-menu-wrapper ul {
  display: flex;
  flex-wrap: wrap;
  list-style: none;
  margin: 0;
  padding-left: 0;
}

/* Horizontal menu */

.hs-menu-wrapper.hs-menu-flow-horizontal .hs-menu-children-wrapper {
  flex-direction: column;
}

@media (max-width: 767px) {
  .hs-menu-wrapper.hs-menu-flow-horizontal ul {
    flex-direction: column;
  }
}

/* Vertical menu */

.hs-menu-wrapper.hs-menu-flow-vertical ul {
  flex-direction: column;
}

/* Flyouts */

.hs-menu-wrapper.hs-menu-flow-vertical.flyouts ul {
  display: inline-flex;
}

@media (max-width: 767px) {
  .hs-menu-wrapper.hs-menu-flow-vertical ul {
    display: flex;
  }
}

.hs-menu-wrapper.flyouts .hs-item-has-children {
  position: relative;
}

.hs-menu-wrapper.flyouts .hs-menu-children-wrapper {
  left: -9999px;
  opacity: 0;
  position: absolute;
}

.hs-menu-wrapper.flyouts .hs-menu-children-wrapper a {
  display: block;
  white-space: nowrap;
}

.hs-menu-wrapper.hs-menu-flow-horizontal.flyouts .hs-item-has-children:hover > .hs-menu-children-wrapper {
  left: 0;
  opacity: 1;
  top: 100%;
}

.hs-menu-wrapper.hs-menu-flow-vertical.flyouts .hs-item-has-children:hover > .hs-menu-children-wrapper {
  left: 100%;
  opacity: 1;
  top: 0;
}

@media (max-width: 767px) {
  .hs-menu-wrapper.flyouts .hs-menu-children-wrapper,
  .hs-menu-wrapper.hs-menu-flow-horizontal.flyouts .hs-item-has-children:hover > .hs-menu-children-wrapper,
  .hs-menu-wrapper.hs-menu-flow-vertical.flyouts .hs-item-has-children:hover > .hs-menu-children-wrapper {
    left: 0;
    opacity: 1;
    position: relative;
    top: auto;
  }
}

/* CTA, logo, and rich text images */

.hs_cos_wrapper_type_cta img,
.hs_cos_wrapper_type_logo img,
.hs_cos_wrapper_type_rich_text img {
  height: auto;
  max-width: 100%;
}

/* The page layer: one header system, width and seam lines for a page built
 * from a pagesheet (.page-composed); after the components so it wins. */
/* ── The page layer (SYSTEM.md §5) ─────────────────────────────────────
 * A page built on a shared page type carries .page-composed on its dnd area.
 * Until 2026-10-14 this layer imposed one header alignment, measure, gap,
 * heading size and content width across the page; that fought the editor's
 * module settings, so it was retired (Rowan, 2026-10-14), except the header
 * alignment (below) and the homepage, which keeps all of it. What remains for
 * every page: the alignment and the seam fixes between bands (the one-pixel
 * overlap, and .section--rules-over for lines that must show over it). */

/* Header alignment stays the page's for now: most modules have no alignment
 * setting and centre a stacked header, so without this a left-aligned page
 * would centre them with no way back in the editor. Retire it when the
 * modules carry their own Header alignment. */
.page--header-left .section:not(.hero):not(.conv):not(.logo-carousel) .section__header:not(.section__header--split):not(.section__header--keep) { text-align: left; }
.page--header-left .section:not(.hero):not(.conv):not(.logo-carousel) .section__header:not(.section__header--split):not(.section__header--keep) .section__heading,
.page--header-left .section:not(.hero):not(.conv):not(.logo-carousel) .section__header:not(.section__header--split):not(.section__header--keep) .section__description { margin-inline: 0; }

.page--header-centre .section:not(.hero) .section__header:not(.section__header--split):not(.section__header--keep) { text-align: center; }
.page--header-centre .section:not(.hero) .section__header:not(.section__header--split):not(.section__header--keep) .section__heading,
.page--header-centre .section:not(.hero) .section__header:not(.section__header--split):not(.section__header--keep) .section__description { margin-inline: auto; }

/* Retired 2026-10-14 (Rowan: "shouldn't the module set them"): the page's
 * header measure, header gap, heading size and inner-measure release. Every
 * module shows its own. The homepage keeps them unchanged (Rowan: the
 * homepage draft is not to change), on its own template's area only. */
.body-container--home.page-composed { --page-heading-max: 22ch; --page-desc-max: 56ch; }
.body-container--home.page-composed .section:not(.hero) .section__header .section__heading { max-width: var(--page-heading-max); }
.body-container--home.page-composed .section:not(.hero) .section__header .section__description { max-width: var(--page-desc-max); }
.body-container--home.page-composed .section:not(.hero) .section__header--split .section__heading,
.body-container--home.page-composed .section:not(.hero) .section__header--split .section__description { max-width: none; }
.body-container--home.page-composed .section:not(.hero) .section__band > .section__header { margin-bottom: var(--section-header-gap); }
.body-container--home.page-composed .section:not(.hero) .section__header:not(.section__header--own-size) .section__heading { font-size: var(--type-h2); letter-spacing: -0.028em; }
.body-container--home.page-composed .section:not(.hero) .tmm-measure--narrow:not(.tmm-measure--keep),
.body-container--home.page-composed .section:not(.hero) .tmm-measure--mid:not(.tmm-measure--keep) { max-width: none; margin-inline: 0; }

/* Bands overlap by one pixel so a fractional offset never shows a hairline of
 * the page body between two bands of the same dark ground (2026-10-12). The
 * lower band paints over the upper's last pixel; the seam rule drawn at the
 * lower band's top still shows. */
.page-composed .dnd-section + .dnd-section { margin-top: -1px; }
/* The overlap hides a band's own edge lines: the lower band paints over the
 * upper's bottom line, and a previous band whose container (z-index 1,
 * _band.css) reaches its foot, such as a bleeding picture, paints over the
 * lower band's top line (Rowan, 2026-10-13: the Top and Bottom border
 * switches did nothing on the homepage's statement band). A band that opts in
 * lifts its lines over both. Opt-in so the other bands on the hand-designed
 * pages keep what they show today (DECISIONS.md). */
.page-composed .section--rules-over.section--rule-top::before,
.page-composed .section--rules-over.section--rule-bottom::after { z-index: 2; }

/* 5. Seam lines: the editor's. The page layer used to hide the band rules and
 * draw its own seam lines; that made the editor's Top and Bottom border
 * toggles do nothing on a composed page (Rowan, 2026-10-13, a top border set
 * on the dark logo bar did not show). Removed: the band rules are always the
 * editor's choice. */

/* Utilities */
/* For content that needs to be visually hidden but stay visible for screenreaders */

.show-for-sr {
  border: 0 !important;
  clip: rect(0, 0, 0, 0) !important;
  height: 1px !important;
  overflow: hidden !important;
  padding: 0 !important;
  position: absolute !important;
  white-space: nowrap !important;
  width: 1px !important;
}

@media (max-width: 767px) {
  .show-for-sr--mobile {
    border: 0 !important;
    clip: rect(0, 0, 0, 0) !important;
    height: 1px !important;
    overflow: hidden !important;
    padding: 0 !important;
    position: absolute !important;
    white-space: nowrap !important;
    width: 1px !important;
  }
}

/* HubSpot system chrome, retokenised (portal colour injections, forms,
 * default menu, blog list, cookie banner). Last so it wins. */


























































.content-wrapper { max-width: 1240px; }

.dnd-section,
.content-wrapper--vertical-spacing { padding: 80px 1rem; }

.dnd-section > .row-fluid { max-width: 1240px; }





/* Base size is fixed; the type ramp (--type-*) and the .t-* classes in
 * _typography.css are all expressed in px, so nothing downstream depends on
 * a portal-chosen root size. */
/* audit-allow: the root rem base, not a type role. */
html { font-size: 16px; }

/* The app face on the document and on form controls (which do not inherit
 * font-family by default). Everything else inherits from body; headings,
 * links and buttons are owned by their own element files. */
body,
input, select, textarea, button {
  font-family: var(--font-sans);
}

/* Heading sizes are owned by .t-h1 / .t-h2 / .t-h3 utility classes (in
 * _typography.css). Bare h1-h6 defaults also live in _typography.css. We do
 * NOT enforce sizes via !important here; that previously locked every <h3>
 * at 36px and broke every card-title module that wanted a smaller size. */





/* Strip HubSpot-injected form wrapper box. !important required because portal
 * settings inject background/border/padding on bare <form>. */

form,
.hs-form {
  background: transparent !important;
  border: none !important;
  padding: 0 !important;
  border-radius: 0 !important;
}

/* HubSpot-rendered submit button: the app's primary button at lg (accent
 * fill, on-accent ink, callout 16 at 650, r12, 44px, --shadow-lift), on the
 * ground's variables so a dark or brand band resolves it. !important defeats portal button
 * settings. Layout (full width, arrow, flex centring) lives in _forms.css;
 * the two files carry the same values. HubSpot renders the submit as
 * <input type="submit" class="hs-button primary large"> inside .hs-submit,
 * and gated-form/module.js may swap it for a <button> with the same class. */

form input[type=submit],
form .hs-submit .hs-button,
form button.hs-button {
  width: 100% !important;
  height: 44px !important;
  background-color: var(--accent-fill) !important;
  color: var(--on-accent) !important;
  border: none !important;
  border-radius: var(--radius-btn) !important;
  padding-top: 0 !important;
  padding-bottom: 0 !important;
  font-family: var(--font-sans) !important;
  font-size: var(--type-button-lg) !important;
  font-weight: 650 !important;
  line-height: 22px !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  cursor: pointer;
  transition: var(--transition-hover);
  box-shadow: var(--shadow-lift) !important;
}

form input[type=submit]:hover,
form .hs-submit .hs-button:hover,
form button.hs-button:hover,
form input[type=submit].is-hover,
form .hs-submit .hs-button.is-hover,
form button.hs-button.is-hover {
  background-color: var(--accent-fill-hover) !important;
  color: var(--on-accent) !important;
  box-shadow: var(--shadow-lift-hover) !important;
}

form input[type=submit]:focus-visible,
form .hs-submit .hs-button:focus-visible,
form button.hs-button:focus-visible,
form input[type=submit].is-focus,
form .hs-submit .hs-button.is-focus,
form button.hs-button.is-focus {
  outline: none;
  box-shadow: var(--shadow-focus-strong) !important;
}

form input[type=submit]:disabled,
form .hs-submit .hs-button:disabled,
form button.hs-button:disabled {
  opacity: 0.5;
  box-shadow: none !important;
  cursor: not-allowed;
}

/* No :active transform — see _buttons.css note. Background change in
 * :hover + focus ring on :focus-visible already provides feedback. */





/* The TMM .tmm-nav (components/_header.css) is the canonical nav surface.
 * The legacy HubSpot-menu styling below stays for backwards compatibility
 * with blog/system templates that may still use the boilerplate menu. */

.header { background-color: var(--color-surface); }

.menu__link,
.header__logo .logo-company-name,
.header__language-switcher-label-current,
.header__language-switcher .lang_list_class li a {
  color: var(--color-ink);
  text-decoration: none;
  transition: var(--transition-hover);
}

.menu__child-toggle-icon { border-top-color: var(--color-ink); }

.menu .menu__link:hover,
.menu .menu__link:focus-visible,
.menu .menu__link.is-hover,
.menu .menu__link.is-focus,
.header__language-switcher-label-current:hover,
.header__language-switcher-label-current:focus-visible,
.header__language-switcher-label-current.is-hover,
.header__language-switcher-label-current.is-focus,
.header__language-switcher .lang_list_class li:hover a,
.header__language-switcher .lang_list_class li.is-hover a,
.header__language-switcher .lang_list_class li a:focus-visible,
.header__language-switcher .lang_list_class li a.is-focus {
  color: var(--color-accent);
}

.menu__child-toggle-icon:hover,
.menu__child-toggle-icon:focus-visible,
.menu__child-toggle-icon.is-hover,
.menu__child-toggle-icon.is-focus { border-top-color: var(--color-accent); }

.menu .menu__item--depth-1 > .menu__link--active-link:after {
  background-color: var(--color-accent);
}

@media (min-width: 767px) {
  .menu .menu__submenu,
  .header__language-switcher .lang_list_class {
    border: 1px solid var(--color-rule);
    border-radius: var(--radius-lg);
    background-color: var(--color-surface);
    box-shadow: var(--shadow-pop);
  }
}

.menu__submenu .menu__link {
  color: var(--color-ink);
  background-color: var(--color-surface);
}

.menu__submenu .menu__link:hover,
.menu__submenu .menu__link:focus-visible,
.menu__submenu .menu__link.is-hover,
.menu__submenu .menu__link.is-focus,
.header__language-switcher .lang_list_class li:hover,
.header__language-switcher .lang_list_class li.is-hover {
  color: var(--color-ink);
  background-color: var(--state-hover);
}

@media (max-width: 767px) {
  .header__navigation { background-color: var(--color-surface); }
  .header__navigation-toggle svg,
  .menu-arrow svg { fill: var(--color-ink); }
}





/* Legacy boilerplate footer sits on the app's dark nav chrome. */
.footer { background-color: var(--color-charcoal); }

.footer p,
.footer h1, .footer h2, .footer h3, .footer h4, .footer h5, .footer h6,
.footer label,
.footer span,
.footer li,
.footer img {
  color: var(--color-dark-ink);
}





.blog-post,
.blog-header__inner,
.blog-related-posts {
  padding: var(--section-pad-md) 0;
}

.blog-post__meta a,
.blog-post__tag-link {
  color: var(--color-ink-2);
  transition: var(--transition-hover);
}

.blog-post__tag-link:hover,
.blog-post__tag-link:focus-visible,
.blog-post__tag-link.is-hover,
.blog-post__tag-link.is-focus {
  color: var(--color-ink);
}

.blog-related-posts__title-link,
.blog-related-posts__title-link:hover,
.blog-related-posts__title-link:focus-visible,
.blog-related-posts__title-link:active,
.blog-related-posts__title-link.is-hover,
.blog-related-posts__title-link.is-focus {
  font-weight: 650;
  color: var(--color-ink);
}

.blog-comments { margin-bottom: var(--section-pad-md); }

#comments-listing .comment-reply-to {
  color: var(--color-accent);
  transition: var(--transition-hover);
}
#comments-listing .comment-reply-to:hover,
#comments-listing .comment-reply-to:focus-visible,
#comments-listing .comment-reply-to.is-hover,
#comments-listing .comment-reply-to.is-focus { color: var(--color-accent-strong); }

.hs-blog-post-listing__post-title-link,
.hs-blog-post-listing__post-title-link:hover,
.hs-blog-post-listing__post-title-link:focus-visible,
.hs-blog-post-listing__post-title-link:active,
.hs-blog-post-listing__post-title-link.is-hover,
.hs-blog-post-listing__post-title-link.is-focus {
  font-weight: 650;
  color: var(--color-ink);
}

.hs-blog-post-listing__post-tag,
.hs-blog-post-listing__post-author-name {
  color: var(--color-ink-2);
}

.hs-pagination__link-text,
.hs-pagination__link--number {
  color: var(--color-ink);
  transition: var(--transition-hover);
}

.hs-pagination__link:hover .hs-pagination__link-text,
.hs-pagination__link:focus-visible .hs-pagination__link-text,
.hs-pagination__link.is-hover .hs-pagination__link-text,
.hs-pagination__link.is-focus .hs-pagination__link-text,
.hs-pagination__link--number:hover,
.hs-pagination__link--number:focus-visible,
.hs-pagination__link--number.is-hover,
.hs-pagination__link--number.is-focus {
  color: var(--color-accent);
}

.hs-pagination__link-icon svg { fill: var(--color-ink); }





.hs-search-results__title {
  font-weight: 650;
  color: var(--color-ink);
}




/* HubSpot v2 cookie banner — TOKEN-ONLY restyle.

   HubSpot emits a stack of CSS custom properties inline on
   #hs-banner-parent (--hs-banner-color, --hs-banner-text-color,
   --hs-banner-accept-color, --hs-banner-modal-*, …) — that's the
   supported customization surface. The HubSpot Settings UI under
   Privacy & Consent → Configure Banner writes the same vars; setting
   them here just expresses the same values in code.

   IMPORTANT — do NOT extend this block with layout, animation, or
   positioning rules on #hs-eu-cookie-confirmation, #hs-banner-parent,
   or any descendant. HubSpot's show animation uses transform with
   animation-fill-mode: forwards, leaving a held transform on the banner
   after entrance. iOS Safari has a hit-test regression on position:fixed
   elements with a non-`none` transform — touches on descendants silently
   drop. A prior restyle that touched animation/position broke
   Accept/Decline taps on iPhone for exactly this reason. Token-only
   leaves HubSpot's lifecycle (show via .hs-banner-optimization-animation
   class, dismiss via class removal) entirely untouched.

   !important is required: the values are inline-styled on
   #hs-banner-parent (a style attribute carrying the --hs-banner-* vars),
   so only !important from an external rule can win the cascade. */
#hs-banner-parent {
  /* Banner surface: app card, r12 corners. */
  --hs-banner-color: var(--color-surface) !important;
  --hs-banner-text-color: var(--color-ink) !important;
  --hs-banner-settings-text-color: var(--color-ink) !important;
  --hs-banner-corners: var(--radius-lg) !important;
  --hs-banner-font-family: var(--font-sans) !important;

  /* Accept button: primary (brand fill, surface-white text). */
  --hs-banner-accept-color: var(--color-accent) !important;
  --hs-banner-accept-text-color: var(--color-surface) !important;
  --hs-banner-accept-border-color: var(--color-accent) !important;

  /* Decline button: outline (surface, strong rule, primary text). */
  --hs-banner-decline-color: var(--color-surface) !important;
  --hs-banner-decline-text-color: var(--color-ink) !important;
  --hs-banner-decline-border-color: var(--color-rule-strong) !important;

  /* Button corners ride with the site's button corner, not a pill. */
  --hs-banner-button-corners: var(--radius-btn) !important;

  /* "Manage cookies" settings modal — mirror the palette so the modal
     experience stays consistent if a visitor opens it. NOTE: HubSpot
     emits `--hs-banner-modal-cateogry-color` (typo: "cateogry"); we
     match their naming so the override actually applies. */
  --hs-banner-modal-color: var(--color-surface) !important;
  --hs-banner-modal-text-color: var(--color-ink) !important;
  --hs-banner-modal-corners: var(--radius-lg) !important;
  --hs-banner-modal-button-corners: var(--radius-btn) !important;
  --hs-banner-modal-category-corners: var(--radius-md) !important;
  --hs-banner-modal-cateogry-color: var(--color-paper) !important;
  --hs-banner-modal-category-text-color: var(--color-ink) !important;

  --hs-banner-modal-accept-color: var(--color-accent) !important;
  --hs-banner-modal-accept-text-color: var(--color-surface) !important;
  --hs-banner-modal-accept-border-color: var(--color-accent) !important;

  --hs-banner-modal-decline-color: var(--color-surface) !important;
  --hs-banner-modal-decline-text-color: var(--color-ink) !important;
  --hs-banner-modal-decline-border-color: var(--color-rule-strong) !important;

  --hs-banner-modal-save-color: var(--color-accent) !important;
  --hs-banner-modal-save-text-color: var(--color-surface) !important;
  --hs-banner-modal-save-border-color: var(--color-accent) !important;

  --hs-banner-modal-toggle-on-color: var(--color-accent) !important;
}