/* GENERATED by scripts/sync-design.mjs from design/tokens.css + design/fonts/fonts.css + design/interactive/components.css. Never edit by hand:
   edit the design/ source and re-run `node scripts/sync-design.mjs`. */
/* GENERATED by design/render.mjs from design/tokens.json. Never edit by hand.
   Semantic tokens only in app code; primitives exist here for the semantics to
   reference (audit.mjs enforces). Theme: system preference by default; an explicit
   choice stamps data-theme="light|dark" on <html> (shared key: designSpecTheme).
   Every state declares color-scheme so the browser's own furniture matches the
   page, and print is always light. The blocks come from lib/tokens.mjs, which the
   style guide inlines from the same function. */
:root {
  color-scheme: light;
  --color-primitive-parchment-0: #FFFFFF;
  --color-primitive-parchment-1: #FDFAF2;
  --color-primitive-parchment-2: #F8F3E9;
  --color-primitive-parchment-3: #F1EADB;
  --color-primitive-parchment-4: #E8DECA;
  --color-primitive-parchment-5: #E3D9C6;
  --color-primitive-parchment-6: #A99E86;
  --color-primitive-parchment-7: #8B8066;
  --color-primitive-parchment-8: #6E6353;
  --color-primitive-parchment-9: #52493C;
  --color-primitive-parchment-10: #3A342A;
  --color-primitive-parchment-11: #241E16;
  --color-primitive-parchment-12: #191510;
  --color-primitive-oxblood-1: #F4E8E0;
  --color-primitive-oxblood-2: #E5CCC2;
  --color-primitive-oxblood-3: #C99384;
  --color-primitive-oxblood-7: #7E332B;
  --color-primitive-oxblood-8: #632119;
  --color-primitive-ember-4: #CE8560;
  --color-primitive-ember-6: #9E4E2E;
  --color-primitive-ember-7: #833C20;
  --color-primitive-night-page: #211B14;
  --color-primitive-night-card: #292217;
  --color-primitive-night-sunken: #15110B;
  --color-primitive-night-ink: #EFE6D6;
  --color-primitive-night-soft: #CDC1AB;
  --color-primitive-night-muted: #A79B84;
  --color-primitive-green-1: #E9F0E9;
  --color-primitive-green-6: #3E7D54;
  --color-primitive-green-7: #2C5F3E;
  --color-primitive-gold-1: #F5ECD2;
  --color-primitive-gold-3: #E9C15B;
  --color-primitive-gold-6: #8A6A14;
  --color-primitive-gold-7: #6B5310;
  --color-primitive-red-1: #FBEAE6;
  --color-primitive-red-6: #B3372F;
  --color-primitive-red-7: #93261F;
  --color-surface-page: #F8F3E9;
  --color-surface-card: #FDFAF2;
  --color-surface-subtle: #F1EADB;
  --color-surface-sunken: #E8DECA;
  --color-surface-scrim: rgba(36,30,22,.48);
  --color-text-primary: #241E16;
  --color-text-secondary: #52493C;
  --color-text-subtle: #6E6353;
  --color-text-disabled: #A99E86;
  --color-text-inverse: #FFFFFF;
  --color-text-brand: #7E332B;
  --color-text-label: #6E6353;
  --color-text-scripture: #7E332B;
  --color-text-heading: #241E16;
  --color-text-action: #7E332B;
  --color-border-subtle: #E3D9C6;
  --color-border-link: #E3D9C6;
  --color-border-control: #8B8066;
  --color-border-strong: #3A342A;
  --color-border-focus: #7E332B;
  --color-action-primary: #7E332B;
  --color-action-primary-hover: #632119;
  --color-action-primary-text: #FFFFFF;
  --color-action-secondary: #FDFAF2;
  --color-action-secondary-hover: #241E16;
  --color-action-secondary-text: #241E16;
  --color-action-hover-wash: rgba(110,99,83,.10);
  --color-action-pressed-wash: rgba(110,99,83,.18);
  --color-action-destructive: #FDFAF2;
  --color-action-destructive-text: #B3372F;
  --color-state-selected-bg: #F4E8E0;
  --color-state-selected-border: #7E332B;
  --color-state-selected-text: #632119;
  --color-state-modified: #8A6A14;
  --color-state-modified-bg: #F5ECD2;
  --color-state-modified-text: #6B5310;
  --color-feedback-error: #B3372F;
  --color-feedback-error-bg: #FBEAE6;
  --color-feedback-warning: #6B5310;
  --color-feedback-warning-bg: #F5ECD2;
  --color-feedback-success: #2C5F3E;
  --color-feedback-success-bg: #E9F0E9;
  --color-data-step-1: #E8DECA;
  --color-data-step-1-text: #241E16;
  --color-data-step-2: #A99E86;
  --color-data-step-2-text: #241E16;
  --color-data-step-3: #52493C;
  --color-data-step-3-text: #FDFAF2;
  --color-data-step-4: #241E16;
  --color-data-step-4-text: #FDFAF2;
  --font-display: "Fraunces", "Iowan Old Style", Georgia, serif;
  --font-body: "Fira Sans", "Avenir Next", "Segoe UI", system-ui, sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --font-size-caption: 13px;
  --font-size-small: 14px;
  --font-size-body: 17px;
  --font-size-reading: 18px;
  --font-size-reading-wide: 20px;
  --font-size-title: 19px;
  --font-size-heading: 22px;
  --font-size-display: 30px;
  --font-size-masthead: 44px;
  --line-height-tight: 1.15;
  --line-height-snug: 1.35;
  --line-height-body: 1.6;
  --line-height-reading: 1.72;
  --font-weight-regular: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --letter-spacing-normal: 0;
  --letter-spacing-caps: 0.04em;
  --letter-spacing-wide: 0.1em;
  --space-3xs: 2px;
  --space-2xs: 4px;
  --space-xs: 8px;
  --space-sm: 12px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-2xl: 48px;
  --space-3xl: 64px;
  --radius-chip: 4px;
  --radius-control: 4px;
  --radius-surface: 4px;
  --radius-pill: 999px;
  --border-width-hairline: 1px;
  --border-width-marker: 2px;
  --border-width-focus: 2px;
  --size-target-min: 24px;
  --size-control-sm: 36px;
  --size-control-md: 48px;
  --size-control-lg: 52px;
  --size-row-min: 48px;
  --size-icon-sm: 16px;
  --size-icon-md: 20px;
  --size-icon-lg: 24px;
  --size-measure-read: 66ch;
  --size-wrap: 1120px;
  --size-focus-offset: 2px;
  --shadow-raised: 0 1px 2px 0 rgba(36,30,22,.05);
  --shadow-panel: 0 2px 8px 0 rgba(36,30,22,.07);
  --shadow-overlay: 0 12px 32px 0 rgba(36,30,22,.16);
  --duration-instant: 80ms;
  --duration-fast: 120ms;
  --duration-base: 180ms;
  --duration-slow: 240ms;
  --easing-standard: 0.2, 0, 0, 1;
  --easing-enter: 0.2, 0.7, 0.3, 1;
  --easing-exit: 0.4, 0, 1, 1;
  --breakpoint-compact: 640px;
  --breakpoint-regular: 1024px;
  --breakpoint-wide: 1440px;
  --z-stage: 0;
  --z-rail: 10;
  --z-header: 20;
  --z-popover: 30;
  --z-modal: 40;
  --z-toast: 50;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  color-scheme: dark;
  --color-surface-page: #211B14;
  --color-surface-card: #292217;
  --color-surface-subtle: rgba(255,255,255,.05);
  --color-surface-sunken: #15110B;
  --color-surface-scrim: rgba(0,0,0,.55);
  --color-text-primary: #EFE6D6;
  --color-text-secondary: #CDC1AB;
  --color-text-subtle: #A79B84;
  --color-text-disabled: #7A6E5D;
  --color-text-brand: #CE8560;
  --color-text-label: #A79B84;
  --color-text-scripture: #CE8560;
  --color-text-heading: #EFE6D6;
  --color-text-action: #CE8560;
  --color-border-subtle: rgba(255,255,255,.09);
  --color-border-link: rgba(255,255,255,.09);
  --color-border-control: rgba(255,255,255,.40);
  --color-border-strong: rgba(255,255,255,.55);
  --color-border-focus: #CE8560;
  --color-action-primary: #9E4E2E;
  --color-action-primary-hover: #833C20;
  --color-action-primary-text: #FFF6EC;
  --color-action-secondary: #292217;
  --color-action-secondary-hover: #CDC1AB;
  --color-action-secondary-text: #EFE6D6;
  --color-action-hover-wash: rgba(255,255,255,.055);
  --color-action-pressed-wash: rgba(255,255,255,.09);
  --color-action-destructive: #292217;
  --color-action-destructive-text: #FF6B5E;
  --color-state-selected-bg: rgba(206,133,96,.16);
  --color-state-selected-border: #CE8560;
  --color-state-selected-text: #EFE6D6;
  --color-state-modified: #E9C15B;
  --color-state-modified-bg: rgba(233,193,91,.15);
  --color-state-modified-text: #E9C15B;
  --color-feedback-error: #FF6B5E;
  --color-feedback-error-bg: rgba(255,107,94,.14);
  --color-feedback-warning: #E9C15B;
  --color-feedback-warning-bg: rgba(233,193,91,.15);
  --color-feedback-success: #7FC796;
  --color-feedback-success-bg: rgba(127,199,150,.15);
  --color-data-step-1: rgba(255,255,255,.07);
  --color-data-step-1-text: #EFE6D6;
  --color-data-step-2: #A79B84;
  --color-data-step-2-text: #292217;
  --color-data-step-3: #CDC1AB;
  --color-data-step-3-text: #292217;
  --color-data-step-4: #EFE6D6;
  --color-data-step-4-text: #292217;
  }
}
[data-theme="dark"] {
  color-scheme: dark;
  --color-surface-page: #211B14;
  --color-surface-card: #292217;
  --color-surface-subtle: rgba(255,255,255,.05);
  --color-surface-sunken: #15110B;
  --color-surface-scrim: rgba(0,0,0,.55);
  --color-text-primary: #EFE6D6;
  --color-text-secondary: #CDC1AB;
  --color-text-subtle: #A79B84;
  --color-text-disabled: #7A6E5D;
  --color-text-brand: #CE8560;
  --color-text-label: #A79B84;
  --color-text-scripture: #CE8560;
  --color-text-heading: #EFE6D6;
  --color-text-action: #CE8560;
  --color-border-subtle: rgba(255,255,255,.09);
  --color-border-link: rgba(255,255,255,.09);
  --color-border-control: rgba(255,255,255,.40);
  --color-border-strong: rgba(255,255,255,.55);
  --color-border-focus: #CE8560;
  --color-action-primary: #9E4E2E;
  --color-action-primary-hover: #833C20;
  --color-action-primary-text: #FFF6EC;
  --color-action-secondary: #292217;
  --color-action-secondary-hover: #CDC1AB;
  --color-action-secondary-text: #EFE6D6;
  --color-action-hover-wash: rgba(255,255,255,.055);
  --color-action-pressed-wash: rgba(255,255,255,.09);
  --color-action-destructive: #292217;
  --color-action-destructive-text: #FF6B5E;
  --color-state-selected-bg: rgba(206,133,96,.16);
  --color-state-selected-border: #CE8560;
  --color-state-selected-text: #EFE6D6;
  --color-state-modified: #E9C15B;
  --color-state-modified-bg: rgba(233,193,91,.15);
  --color-state-modified-text: #E9C15B;
  --color-feedback-error: #FF6B5E;
  --color-feedback-error-bg: rgba(255,107,94,.14);
  --color-feedback-warning: #E9C15B;
  --color-feedback-warning-bg: rgba(233,193,91,.15);
  --color-feedback-success: #7FC796;
  --color-feedback-success-bg: rgba(127,199,150,.15);
  --color-data-step-1: rgba(255,255,255,.07);
  --color-data-step-1-text: #EFE6D6;
  --color-data-step-2: #A79B84;
  --color-data-step-2-text: #292217;
  --color-data-step-3: #CDC1AB;
  --color-data-step-3-text: #292217;
  --color-data-step-4: #EFE6D6;
  --color-data-step-4-text: #292217;
}
@media print {
  :root, :root[data-theme="light"], :root[data-theme="dark"] {
  color-scheme: light;
  --color-primitive-parchment-0: #FFFFFF;
  --color-primitive-parchment-1: #FDFAF2;
  --color-primitive-parchment-2: #F8F3E9;
  --color-primitive-parchment-3: #F1EADB;
  --color-primitive-parchment-4: #E8DECA;
  --color-primitive-parchment-5: #E3D9C6;
  --color-primitive-parchment-6: #A99E86;
  --color-primitive-parchment-7: #8B8066;
  --color-primitive-parchment-8: #6E6353;
  --color-primitive-parchment-9: #52493C;
  --color-primitive-parchment-10: #3A342A;
  --color-primitive-parchment-11: #241E16;
  --color-primitive-parchment-12: #191510;
  --color-primitive-oxblood-1: #F4E8E0;
  --color-primitive-oxblood-2: #E5CCC2;
  --color-primitive-oxblood-3: #C99384;
  --color-primitive-oxblood-7: #7E332B;
  --color-primitive-oxblood-8: #632119;
  --color-primitive-ember-4: #CE8560;
  --color-primitive-ember-6: #9E4E2E;
  --color-primitive-ember-7: #833C20;
  --color-primitive-night-page: #211B14;
  --color-primitive-night-card: #292217;
  --color-primitive-night-sunken: #15110B;
  --color-primitive-night-ink: #EFE6D6;
  --color-primitive-night-soft: #CDC1AB;
  --color-primitive-night-muted: #A79B84;
  --color-primitive-green-1: #E9F0E9;
  --color-primitive-green-6: #3E7D54;
  --color-primitive-green-7: #2C5F3E;
  --color-primitive-gold-1: #F5ECD2;
  --color-primitive-gold-3: #E9C15B;
  --color-primitive-gold-6: #8A6A14;
  --color-primitive-gold-7: #6B5310;
  --color-primitive-red-1: #FBEAE6;
  --color-primitive-red-6: #B3372F;
  --color-primitive-red-7: #93261F;
  --color-surface-page: #F8F3E9;
  --color-surface-card: #FDFAF2;
  --color-surface-subtle: #F1EADB;
  --color-surface-sunken: #E8DECA;
  --color-surface-scrim: rgba(36,30,22,.48);
  --color-text-primary: #241E16;
  --color-text-secondary: #52493C;
  --color-text-subtle: #6E6353;
  --color-text-disabled: #A99E86;
  --color-text-inverse: #FFFFFF;
  --color-text-brand: #7E332B;
  --color-text-label: #6E6353;
  --color-text-scripture: #7E332B;
  --color-text-heading: #241E16;
  --color-text-action: #7E332B;
  --color-border-subtle: #E3D9C6;
  --color-border-link: #E3D9C6;
  --color-border-control: #8B8066;
  --color-border-strong: #3A342A;
  --color-border-focus: #7E332B;
  --color-action-primary: #7E332B;
  --color-action-primary-hover: #632119;
  --color-action-primary-text: #FFFFFF;
  --color-action-secondary: #FDFAF2;
  --color-action-secondary-hover: #241E16;
  --color-action-secondary-text: #241E16;
  --color-action-hover-wash: rgba(110,99,83,.10);
  --color-action-pressed-wash: rgba(110,99,83,.18);
  --color-action-destructive: #FDFAF2;
  --color-action-destructive-text: #B3372F;
  --color-state-selected-bg: #F4E8E0;
  --color-state-selected-border: #7E332B;
  --color-state-selected-text: #632119;
  --color-state-modified: #8A6A14;
  --color-state-modified-bg: #F5ECD2;
  --color-state-modified-text: #6B5310;
  --color-feedback-error: #B3372F;
  --color-feedback-error-bg: #FBEAE6;
  --color-feedback-warning: #6B5310;
  --color-feedback-warning-bg: #F5ECD2;
  --color-feedback-success: #2C5F3E;
  --color-feedback-success-bg: #E9F0E9;
  --color-data-step-1: #E8DECA;
  --color-data-step-1-text: #241E16;
  --color-data-step-2: #A99E86;
  --color-data-step-2-text: #241E16;
  --color-data-step-3: #52493C;
  --color-data-step-3-text: #FDFAF2;
  --color-data-step-4: #241E16;
  --color-data-step-4-text: #FDFAF2;
  --font-display: "Fraunces", "Iowan Old Style", Georgia, serif;
  --font-body: "Fira Sans", "Avenir Next", "Segoe UI", system-ui, sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --font-size-caption: 13px;
  --font-size-small: 14px;
  --font-size-body: 17px;
  --font-size-reading: 18px;
  --font-size-reading-wide: 20px;
  --font-size-title: 19px;
  --font-size-heading: 22px;
  --font-size-display: 30px;
  --font-size-masthead: 44px;
  --line-height-tight: 1.15;
  --line-height-snug: 1.35;
  --line-height-body: 1.6;
  --line-height-reading: 1.72;
  --font-weight-regular: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --letter-spacing-normal: 0;
  --letter-spacing-caps: 0.04em;
  --letter-spacing-wide: 0.1em;
  --space-3xs: 2px;
  --space-2xs: 4px;
  --space-xs: 8px;
  --space-sm: 12px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-2xl: 48px;
  --space-3xl: 64px;
  --radius-chip: 4px;
  --radius-control: 4px;
  --radius-surface: 4px;
  --radius-pill: 999px;
  --border-width-hairline: 1px;
  --border-width-marker: 2px;
  --border-width-focus: 2px;
  --size-target-min: 24px;
  --size-control-sm: 36px;
  --size-control-md: 48px;
  --size-control-lg: 52px;
  --size-row-min: 48px;
  --size-icon-sm: 16px;
  --size-icon-md: 20px;
  --size-icon-lg: 24px;
  --size-measure-read: 66ch;
  --size-wrap: 1120px;
  --size-focus-offset: 2px;
  --shadow-raised: 0 1px 2px 0 rgba(36,30,22,.05);
  --shadow-panel: 0 2px 8px 0 rgba(36,30,22,.07);
  --shadow-overlay: 0 12px 32px 0 rgba(36,30,22,.16);
  --duration-instant: 80ms;
  --duration-fast: 120ms;
  --duration-base: 180ms;
  --duration-slow: 240ms;
  --easing-standard: 0.2, 0, 0, 1;
  --easing-enter: 0.2, 0.7, 0.3, 1;
  --easing-exit: 0.4, 0, 1, 1;
  --breakpoint-compact: 640px;
  --breakpoint-regular: 1024px;
  --breakpoint-wide: 1440px;
  --z-stage: 0;
  --z-rail: 10;
  --z-header: 20;
  --z-popover: 30;
  --z-modal: 40;
  --z-toast: 50;
  }
}

/* Self-hosted faces, subsetted to latin with ALL layout features kept.
   CRITICAL: Fira Sans's true small caps live in GSUB smcp/c2sc. Any re-subsetting
   MUST pass --layout-features='*' (or +=smcp,c2sc) or the SC glyphs are silently
   dropped and font-variant-caps falls back to synthesized fakes. */

@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(Fraunces-var.woff2) format('woff2');
}
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url(Fraunces-Italic-var.woff2) format('woff2');
}
/* The social graphics' cut of Fraunces (#1135): two instanced files with opsz, SOFT
   and WONK pinned and wght left variable, because a canvas cannot set
   font-variation-settings and would draw the leaning WONK forms at display size.
   An @font-face font-variation-settings descriptor does not do it: WebKit ignores
   it on canvas, and most cards are drawn on iPhones. Upright is the display cut
   (opsz 144), the italic the byline's (opsz 24). Nothing but the cards names this
   family, and a face no page uses is never fetched. Rebuild with fonttools
   (pip install fonttools brotli), from design/fonts/:
     fonttools varLib.instancer Fraunces-var.woff2 opsz=144 SOFT=0 WONK=0 -o Fraunces-Card.woff2
     fonttools varLib.instancer Fraunces-Italic-var.woff2 opsz=24 SOFT=0 WONK=0 -o Fraunces-Card-Italic.woff2
   Same glyphs, cmap and layout features as the sources (checked against the binaries).
   The two Fraunces-Card*.ttf beside them are the same cuts as static files at the
   weights the cards set (upright 430, italic 400), for the server-drawn link preview
   (#1129), whose font library cannot read woff2. They are never served to a browser
   and no @font-face names them. The fonttools CLI keeps a woff2 input's flavor, so
   decompress to a variable TTF first, then instance it:
     fonttools ttLib.woff2 decompress -o Fraunces-var.ttf Fraunces-var.woff2
     fonttools varLib.instancer Fraunces-var.ttf opsz=144 SOFT=0 WONK=0 wght=430 -o Fraunces-Card-430.ttf
     fonttools ttLib.woff2 decompress -o Fraunces-Italic-var.ttf Fraunces-Italic-var.woff2
     fonttools varLib.instancer Fraunces-Italic-var.ttf opsz=24 SOFT=0 WONK=0 wght=400 -o Fraunces-Card-Italic-400.ttf
   (Delete the intermediate Fraunces*-var.ttf files afterward.) */
@font-face {
  font-family: 'Fraunces Card';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(Fraunces-Card.woff2) format('woff2');
}
@font-face {
  font-family: 'Fraunces Card';
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url(Fraunces-Card-Italic.woff2) format('woff2');
}
@font-face {
  font-family: 'Fira Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(FiraSans-Regular.woff2) format('woff2');
}
@font-face {
  font-family: 'Fira Sans';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(FiraSans-Italic.woff2) format('woff2');
}
@font-face {
  font-family: 'Fira Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(FiraSans-Medium.woff2) format('woff2');
}
@font-face {
  font-family: 'Fira Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(FiraSans-SemiBold.woff2) format('woff2');
}

/* A church's typefaces (#1402, the brand kit, #1395): the five pairings beside
   ours, each SIL Open Font License, served from here like our own so no
   congregant's browser asks a third party for a font. Each is Google Fonts'
   latin cut as served (2026-11-02), not re-subset here. Those cuts carry no
   smcp or c2sc (checked against the binaries), so `.sm-sc` on these faces is
   the browser's synthesized small capitals; Fraunces and Fira Sans keep their
   true ones. A face that is never named on a page is never fetched.

   `size-adjust` matches each face's x-height to the one it stands in for
   (Fraunces 0.44, Fira Sans 0.53, measured 2026-11-01), part of the way where
   matching it fully would shrink a wide face's capitals: so swapping pairings
   never reflows a page. Cardo (0.44), Libre Franklin (0.53) and Open Sans
   (0.54) need none. */
@font-face {
  font-family: 'Cardo';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(Cardo-400.woff2) format('woff2');
}
@font-face {
  font-family: 'Cardo';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(Cardo-700.woff2) format('woff2');
}
@font-face {
  font-family: 'Cardo';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(Cardo-Italic-400.woff2) format('woff2');
}
@font-face {
  font-family: 'Libre Franklin';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(LibreFranklin-var.woff2) format('woff2');
}
@font-face {
  font-family: 'Libre Franklin';
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url(LibreFranklin-Italic-var.woff2) format('woff2');
}
@font-face {
  font-family: 'Libre Baskerville';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(LibreBaskerville-400.woff2) format('woff2');
  size-adjust: 89%;
}
@font-face {
  font-family: 'Libre Baskerville';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(LibreBaskerville-700.woff2) format('woff2');
  size-adjust: 89%;
}
@font-face {
  font-family: 'Libre Baskerville';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(LibreBaskerville-Italic-400.woff2) format('woff2');
  size-adjust: 89%;
}
@font-face {
  font-family: 'Source Sans 3';
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url(SourceSans3-var.woff2) format('woff2');
  size-adjust: 106%;
}
@font-face {
  font-family: 'Source Sans 3';
  font-style: italic;
  font-weight: 200 900;
  font-display: swap;
  src: url(SourceSans3-Italic-var.woff2) format('woff2');
  size-adjust: 106%;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url(PlayfairDisplay-var.woff2) format('woff2');
  size-adjust: 90%;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400 900;
  font-display: swap;
  src: url(PlayfairDisplay-Italic-var.woff2) format('woff2');
  size-adjust: 90%;
}
@font-face {
  font-family: 'Lora';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(Lora-var.woff2) format('woff2');
  size-adjust: 94%;
}
@font-face {
  font-family: 'Lora';
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url(Lora-Italic-var.woff2) format('woff2');
  size-adjust: 94%;
}
@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 200 1000;
  font-display: swap;
  src: url(NunitoSans-var.woff2) format('woff2');
  size-adjust: 106%;
}
@font-face {
  font-family: 'Nunito Sans';
  font-style: italic;
  font-weight: 200 1000;
  font-display: swap;
  src: url(NunitoSans-Italic-var.woff2) format('woff2');
  size-adjust: 106%;
}
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(Montserrat-var.woff2) format('woff2');
  size-adjust: 89%;
}
@font-face {
  font-family: 'Montserrat';
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url(Montserrat-Italic-var.woff2) format('woff2');
  size-adjust: 89%;
}
@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url(OpenSans-var.woff2) format('woff2');
}
@font-face {
  font-family: 'Open Sans';
  font-style: italic;
  font-weight: 300 800;
  font-display: swap;
  src: url(OpenSans-Italic-var.woff2) format('woff2');
}

/* design/interactive/components.css: the REFERENCE IMPLEMENTATION of every component
   in the spec. Inlined into styleguide.html so the examples are real, working controls;
   written so the app (Hono JSX, CSS-in-TS strings) can adopt it wholesale. Semantic
   tokens only; audit.mjs rules. Class prefix sm- (Shepherd's Message).
   Native elements first: details/summary, radio, checkbox, range; components.js only
   where HTML alone cannot.

   Cursor doctrine (audit.mjs enforces per family): clicked things take `pointer`
   (buttons, selects, checks, radios, chips, summaries, range); a text field keeps its
   caret; disabled goes back to `default`. */

/* ---------- box model ---------- */
.sm-demo, .sm-demo *, [class*="sm-"], [class*="sm-"] * { box-sizing: border-box; }

/* ---------- focus: one ring everywhere ----------
   Ring sits OUTSIDE a focus-offset gap so it survives on fills of its own color. */
[class*="sm-"]:focus-visible, .sm-demo :focus-visible {
  outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset);
}

/* ---------- type utilities ---------- */
/* True small caps: Fira Sans smcp/c2sc. NEVER apply to the display face. */
.sm-sc { font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps); }
/* Letterspaced-uppercase micro-label (TRANSCRIPT, ABOUT THIS RECORDING). */
.sm-microlabel { font-family: var(--font-body); font-size: var(--font-size-caption);
  font-weight: var(--font-weight-semibold); letter-spacing: var(--letter-spacing-wide);
  text-transform: uppercase; color: var(--color-text-label); }
/* The same label as a heading (h2, h3) keeps the paragraph's look and margin. */
h2.sm-microlabel, h3.sm-microlabel, h4.sm-microlabel { margin-block: 1em; line-height: inherit; }
/* Words for a screen reader and not for the page. The system's one such utility,
   and it lives HERE rather than in a surface's own sheet so the spec's examples
   can use it too: a control whose visible label is a state ("Dark theme") but
   whose spoken name should be the act ("Switch to Dark theme") has nowhere else
   to put the difference. Unprefixed, because it is a utility and not a component. */
.vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ---------- buttons ---------- */
.sm-btn {
  font-family: var(--font-body); font-size: var(--font-size-body); font-weight: var(--font-weight-semibold);
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-xs);
  min-height: var(--size-control-md); padding: 0 var(--space-md);
  border-radius: var(--radius-control); cursor: pointer;
  border: var(--border-width-hairline) solid transparent;
  background: none; color: var(--color-text-primary);
  /* A label holds its line: a button that grows to two lines stops being the
     fixed-size target the rest of the system is measured against (button.md
     Behavior, SSM-166). No `text-overflow: ellipsis` with it, deliberately. An
     audit of all 44 button labels in the migrated surfaces found exactly one
     that would clip at 320px, "Sign out and try another account", on the auth
     error path where a confused person most needs the whole sentence. A label
     too long for its box now overflows visibly, which someone can see and
     report, rather than losing its verb to an ellipsis nobody notices. That
     one was reported, and the auth card now lets a label wrap under compact
     (#808, title page section below); everywhere else the line holds. */
  white-space: nowrap;
  transition: background var(--duration-fast), border-color var(--duration-fast), color var(--duration-fast), transform var(--duration-fast);
}
.sm-btn:active { transform: translateY(1px); } /* the press, Phase 1 motion */
.sm-btn:disabled { cursor: default; pointer-events: none; color: var(--color-text-disabled);
  border-color: var(--color-border-subtle); background: var(--color-surface-subtle); }
/* In flight: NO spinner exists in this system (motion.md: nothing loops). A busy
   button keeps its fill and width, refuses input, and its LABEL says what is
   happening ("Publish" → "Publishing…"). Reserve width with min-width if the
   progressive verb runs longer. */
.sm-btn[aria-busy="true"] { cursor: default; pointer-events: none; }
/* Unavailable, and still in the tab order (#858): a button that cannot act yet
   and says why (aria-describedby) keeps its focus stop, so a keyboard reader
   reaches the reason, and its act refuses the press. Drawn as disabled; a busy
   button, which is aria-disabled too, keeps its fill. */
.sm-btn[aria-disabled="true"]:not([aria-busy="true"]) { cursor: default;
  color: var(--color-text-disabled); border-color: var(--color-border-subtle);
  background: var(--color-surface-subtle); }
.sm-btn svg { flex: none; }

.sm-btn--primary { background: var(--color-action-primary); color: var(--color-action-primary-text);
  border-color: var(--color-action-primary-hover); }
.sm-btn--primary:hover { background: var(--color-action-primary-hover); }
.sm-btn--lg { min-height: var(--size-control-lg); font-size: var(--font-size-title); padding: 0 var(--space-lg); }
.sm-btn--sm { min-height: var(--size-control-sm); font-size: var(--font-size-small); padding: 0 var(--space-sm); }

/* Quiet: cream fill, ink label, control edge at rest (the edge IS the affordance:
   Principle 3, buttons look like buttons); hover washes and strengthens the edge. */
.sm-btn--quiet { background: var(--color-action-secondary); color: var(--color-action-secondary-text);
  border-color: var(--color-border-control); font-weight: var(--font-weight-medium); }
.sm-btn--quiet:hover { border-color: var(--color-action-secondary-hover); background: var(--color-action-hover-wash); }

/* Link-button: for inline acts inside prose/metadata only. */
.sm-btn--link { color: var(--color-text-action); min-height: var(--size-target-min); padding: 0 var(--space-2xs);
  font-weight: var(--font-weight-medium); text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--color-border-link); }
/* The one variant that DOES wrap: it is an act inside a sentence, so it breaks
   with the sentence rather than forcing its paragraph wide. */
.sm-btn--link { white-space: normal; }
.sm-btn--link:hover { text-decoration-color: currentColor; }

/* Destructive: outlined; never a red fill (red reports, it does not do identity). */
.sm-btn--destructive { background: var(--color-action-destructive); color: var(--color-action-destructive-text);
  border-color: var(--color-action-destructive-text); }
.sm-btn--destructive:hover { background: var(--color-feedback-error-bg); }

/* ---------- fields ---------- */
.sm-field { display: flex; flex-direction: column; align-items: stretch; gap: var(--space-2xs);
  font-family: var(--font-body); max-width: 480px; }
.sm-field > label { font-size: var(--font-size-body); font-weight: var(--font-weight-medium);
  color: var(--color-text-primary); }
.sm-field > .hint { font-size: var(--font-size-small); color: var(--color-text-subtle); }
/* A hint belongs to the control above it, so the only space between them is the
   field's own 4px gap. Written out because the app renders hints and messages as
   <p> and the UA's 1em margins pushed each description a full line clear of its
   input, reading as a separate remark. The spec's examples use <span>, which is
   why the styleguide never showed the gap. Form-level prose gets the same
   treatment: .sm-form is a flex column and its gap is the whole rhythm. */
.sm-field > .hint, .sm-field > .sm-msg, .sm-form > p { margin: 0; }

/* Text wells keep the caret: writing `pointer` on one is the opposite mistake. */
.sm-well {
  min-height: var(--size-control-md); padding: 0 var(--space-sm); width: 100%;
  background: var(--color-surface-card); color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-control);
  font-family: var(--font-body); font-size: var(--font-size-body);
  transition: border-color var(--duration-fast), background var(--duration-fast);
}
textarea.sm-well { min-height: calc(var(--size-control-md) * 2); padding: var(--space-xs) var(--space-sm);
  line-height: var(--line-height-body); resize: vertical; }
.sm-well::placeholder { color: var(--color-text-subtle); }
.sm-well:hover { border-color: var(--color-border-strong); }
.sm-well:focus { border-color: var(--color-border-focus); outline: none; }
.sm-well[aria-invalid="true"] { border-color: var(--color-feedback-error);
  box-shadow: inset 0 0 0 1px var(--color-feedback-error); } /* 2px edge without geometry change */
.sm-well:disabled { color: var(--color-text-disabled); border-color: var(--color-border-subtle);
  background: var(--color-surface-subtle); }

/* Validation message: rendered below in flow; it may NEVER move the control it
   describes; rows align to control tops. */
.sm-msg { display: flex; gap: var(--space-2xs); align-items: baseline;
  font-family: var(--font-body); font-size: var(--font-size-small); color: var(--color-feedback-error); }

/* ---------- select ---------- */
/* A select is CLICKED, not typed into: it carries the button pointer.
   The popup is OURS, not the OS's: `appearance: base-select` + ::picker(select)
   restyles the real <select>'s option list with the system's selection grammar,
   keeping native keyboard/AT semantics and zero JS. Browsers without base-select
   fall back to the custom-styled closed control + their native popup. */
.sm-selectw { position: relative; display: inline-flex; width: 100%; }
.sm-select {
  appearance: none; -webkit-appearance: none;
  min-height: var(--size-control-md); width: 100%; padding: 0 44px 0 var(--space-sm);
  background: var(--color-surface-card); color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-control); cursor: pointer;
  font-family: var(--font-body); font-size: var(--font-size-body);
  transition: border-color var(--duration-fast);
}
.sm-select:hover { border-color: var(--color-border-strong); }
.sm-select:focus { border-color: var(--color-border-focus); outline: none; }
.sm-select:disabled { cursor: default; color: var(--color-text-disabled);
  border-color: var(--color-border-subtle); background: var(--color-surface-subtle); }
/* Chevron: stroke SVG (iconography.md: 1.5px, currentColor), never a text glyph. */
.sm-selectw .car { position: absolute; right: var(--space-xs); top: 50%; margin-top: -12px;
  width: 24px; height: 24px; display: grid; place-items: center;
  color: var(--color-text-subtle); pointer-events: none;
  transition: transform var(--duration-base); }
.sm-selectw .sm-select:disabled ~ .car { color: var(--color-text-disabled); }
@supports selector(::picker(select)) {
  .sm-select, ::picker(select) { appearance: base-select; }
  .sm-select { display: inline-flex; align-items: center; }
  .sm-select::picker-icon { display: none; } /* our chevron, not the UA's */
  .sm-selectw .sm-select:open ~ .car { transform: rotate(180deg); }
  ::picker(select) { background: var(--color-surface-card);
    border: var(--border-width-hairline) solid var(--color-border-subtle);
    border-radius: var(--radius-control); box-shadow: var(--shadow-overlay);
    padding: var(--space-2xs); margin-top: var(--space-3xs);
    /* Leaving: a fade, quicker than the arrival and without the settle (motion.md:
       exits just fade). A curve token is a curve only inside cubic-bezier() (#824). */
    opacity: 0;
    transition: opacity var(--duration-fast) cubic-bezier(var(--easing-exit)),
      display var(--duration-fast) allow-discrete, overlay var(--duration-fast) allow-discrete; }
  /* Arriving: fade + settle, rising 6px into place (select.md, motion.md). */
  .sm-select:open::picker(select) { opacity: 1; transform: none;
    transition: opacity var(--duration-base) cubic-bezier(var(--easing-enter)),
      transform var(--duration-base) cubic-bezier(var(--easing-enter)),
      display var(--duration-base) allow-discrete, overlay var(--duration-base) allow-discrete; }
  @starting-style { .sm-select:open::picker(select) { opacity: 0; transform: translateY(6px); } }
  /* The system's reduced-motion rule reaches elements, and a picker is a pseudo-element:
     both of its rules are named, the opening one being the more specific. */
  @media (prefers-reduced-motion: reduce) {
    ::picker(select), .sm-select:open::picker(select) { transition: none; } }
  .sm-selectw option { display: flex; align-items: center; gap: var(--space-xs);
    cursor: pointer; min-height: var(--size-control-md);
    padding: var(--space-2xs) var(--space-sm); line-height: var(--line-height-snug);
    border-radius: var(--radius-chip); font-family: var(--font-body);
    font-size: var(--font-size-body); color: var(--color-text-primary); }
  .sm-selectw option:hover, .sm-selectw option:focus-visible { background: var(--color-action-hover-wash); }
  .sm-selectw option:checked { background: var(--color-state-selected-bg); color: var(--color-state-selected-text); }
  .sm-selectw option::checkmark { color: var(--color-state-selected-border); }
  /* An <optgroup>'s label is the only thing inside the popup the UA still paints,
     and it paints it BLACK and bold in every theme: measured at rgb(0,0,0) on a
     night ground, between rows of white type, with no spacing of any kind. It
     takes the microlabel instead, the same quiet caps every other section
     heading in the system wears.

     Two things about the box are deliberate and not obvious:
     · Padding on the <optgroup> indents its option CHILDREN, which steps grouped
       rows in from ungrouped ones. The inline padding is pulled straight back off
       the options, so the heading is indented and the rows still line up.
     · padding-bottom would land under the group's LAST option, not under the
       label, so the air below the heading is the first option's margin. */
  .sm-selectw optgroup { color: var(--color-text-subtle);
    padding: var(--space-sm) var(--space-sm) 0;
    font-family: var(--font-body); font-size: var(--font-size-caption);
    font-weight: var(--font-weight-semibold); line-height: var(--line-height-snug);
    letter-spacing: var(--letter-spacing-wide); text-transform: uppercase; }
  /* text-transform and letter-spacing inherit, and the option rule above sets
     neither, so without this every grouped option renders in caps. */
  .sm-selectw optgroup > option { margin-inline: calc(-1 * var(--space-sm));
    font-weight: var(--font-weight-regular); letter-spacing: normal; text-transform: none; }
  .sm-selectw optgroup > option:first-of-type { margin-block-start: var(--space-2xs); }
}

/* ---------- checkbox & radio rows ---------- */
/* A radio group is a <fieldset> with a <legend>, and the UA's box for both is
   never the design. Stated here so a group needs no inline style; the reference
   example below used to carry one, which is how the app ended up without any. */
.sm-fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
.sm-fieldset > legend { padding: 0 0 var(--space-2xs);
  font-family: var(--font-body); font-size: var(--font-size-body);
  font-weight: var(--font-weight-medium); color: var(--color-text-primary); }
.sm-checkrow, .sm-radiorow { display: flex; align-items: flex-start; gap: var(--space-sm);
  min-height: var(--size-row-min); padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-control); cursor: pointer;
  font-family: var(--font-body); font-size: var(--font-size-body); color: var(--color-text-primary);
  transition: background var(--duration-fast); }
.sm-checkrow:hover, .sm-radiorow:hover { background: var(--color-action-hover-wash); }
.sm-checkrow input, .sm-radiorow input { appearance: none; -webkit-appearance: none; margin: 2px 0 0; flex: none;
  width: 22px; height: 22px;
  border: var(--border-width-hairline) solid var(--color-border-control);
  background: var(--color-surface-card); display: grid; place-items: center; cursor: pointer;
  transition: background var(--duration-fast), border-color var(--duration-fast); }
.sm-checkrow input { border-radius: var(--radius-chip); }
.sm-radiorow input { border-radius: var(--radius-pill); }
.sm-checkrow input:checked, .sm-checkrow input:indeterminate,
.sm-radiorow input:checked { background: var(--color-action-primary); border-color: var(--color-action-primary); }
.sm-checkrow input:checked::before { content: ""; width: 14px; height: 14px;
  background: var(--color-action-primary-text);
  clip-path: polygon(18% 46%, 8% 58%, 40% 86%, 92% 24%, 81% 15%, 39% 65%); }
.sm-checkrow input:indeterminate::before { content: ""; width: 12px; height: 2px;
  background: var(--color-action-primary-text); }
.sm-radiorow input:checked::before { content: ""; width: 8px; height: 8px; border-radius: 50%;
  background: var(--color-action-primary-text); }
.sm-checkrow input:disabled, .sm-radiorow input:disabled { cursor: default;
  border-color: var(--color-border-subtle); background: var(--color-surface-subtle); }
.sm-checkrow .lbl, .sm-radiorow .lbl { display: block; }
.sm-checkrow .hint, .sm-radiorow .hint { display: block; color: var(--color-text-subtle);
  font-size: var(--font-size-small); font-weight: var(--font-weight-regular); }
/* A group of rows is a `fieldset.sm-field` whose rows are direct children, so
   `.sm-field > label` reaches every row and its hint inherited the label's
   medium weight. The hint states its own weight (#1168); the `.lbl` keeps the medium. */
/* In a form a row lines up with the fields around it (#840). Its padding is the
   hover wash's room, and in a form's column it started the box `space.sm` right
   of every field label, with nothing on a field to match. So in a form the row
   gives that room back as a negative inline margin: the box sits on the labels'
   left edge and the wash hangs into the gutter, both sides alike, so the text
   wraps at the column's edge as a hint does. Capped like a field (480px) plus
   the hang, so the wash never runs the column's whole 640px. Not in a table
   cell, where a select cell (patterns/operator.md) draws no padding to give. */
.sm-form :is(.sm-checkrow, .sm-radiorow):not(td > *) {
  margin-inline: calc(-1 * var(--space-sm)); max-width: calc(480px + 2 * var(--space-sm)); }

/* ---------- a field one radio row opens (components/choice.md, #221) ---------- */
/* A row whose choice needs a value of its own (a monthly limit) puts the field
   directly under that row, indented to the row's label edge. In a form the row
   sits on the column's edge (its padding is given back as a negative margin,
   above), so its label starts at the 22px mark and the gap in: the field takes
   that much, and reads as the row's, never the legend's. It shows while its row
   is checked. Where :has() is unsupported the field is simply always there,
   which is still correct. */
.sm-rowfield { margin-inline-start: calc(22px + var(--space-sm));
  padding-block: var(--space-2xs) var(--space-sm); }
.sm-rowfield .sm-field { margin: 0; }
@supports selector(:has(*)) {
  .sm-radiorow:has(input:not(:checked)) + .sm-rowfield { display: none; }
}

/* ---------- inline radios (components/choice.md, Inline radios) ---------- */
/* Two or three short options side by side, no hints, for a list whose every row
   carries its own choice (Invite people's role, #974). Each option is a 48px
   target with space.sm of inline padding, its mark space.xs from its word, so
   one option's word is space.lg from the next one's mark, ink to ink. The mark
   is the radio row's own. The group wraps rather than overflow a narrow phone. */
.sm-radioinline { display: flex; flex-wrap: wrap; margin: 0; padding: 0; border: 0; min-width: 0; }
.sm-radioinline label { display: inline-flex; align-items: center; gap: var(--space-xs);
  min-height: var(--size-control-md); padding: 0 var(--space-sm);
  border-radius: var(--radius-control); cursor: pointer; white-space: nowrap;
  font-family: var(--font-body); font-size: var(--font-size-body); color: var(--color-text-primary);
  transition: background var(--duration-fast); }
.sm-radioinline label:hover { background: var(--color-action-hover-wash); }
.sm-radioinline input { appearance: none; -webkit-appearance: none; margin: 0; flex: none;
  width: 22px; height: 22px; border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-pill); background: var(--color-surface-card);
  display: grid; place-items: center; cursor: pointer;
  transition: background var(--duration-fast), border-color var(--duration-fast); }
.sm-radioinline input:checked { background: var(--color-action-primary); border-color: var(--color-action-primary); }
.sm-radioinline input:checked::before { content: ""; width: 8px; height: 8px; border-radius: 50%;
  background: var(--color-action-primary-text); }
.sm-radioinline input:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-radioinline label:has(input:disabled) { cursor: default; color: var(--color-text-disabled); }
.sm-radioinline label:has(input:disabled):hover { background: none; }
.sm-radioinline input:disabled { cursor: default;
  border-color: var(--color-border-subtle); background: var(--color-surface-subtle); }

/* ---------- segmented choice ---------- */
.sm-seg { display: inline-flex; background: var(--color-surface-sunken);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-control); padding: var(--space-3xs); }
.sm-seg label { position: relative; display: inline-flex; }
/* A choice that is a place of its own (a window in the URL) is a link: the same
   face, current marked by aria-current, no script to move it (#859). */
.sm-seg a { text-decoration: none; cursor: pointer; }
.sm-seg input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.sm-seg input:disabled { cursor: default; }
.sm-seg span, .sm-seg a { display: inline-flex; align-items: center; min-height: calc(var(--size-control-md) - 2 * var(--space-3xs) - 2px);
  padding: 0 var(--space-md); border-radius: var(--radius-chip);
  font-family: var(--font-body); font-size: var(--font-size-body); color: var(--color-text-secondary);
  font-weight: var(--font-weight-medium); /* constant: selection may never change geometry */
  border: var(--border-width-hairline) solid transparent;
  box-shadow: none;
  transition: background var(--duration-fast), color var(--duration-fast),
    border-color var(--duration-fast), box-shadow var(--duration-fast); }
.sm-seg label:hover input:not(:checked) + span,
.sm-seg a:hover:not([aria-current]) { background: var(--color-action-hover-wash);
  box-shadow: var(--shadow-raised); }
.sm-seg input:checked + span, .sm-seg a[aria-current] { background: var(--color-surface-card);
  color: var(--color-text-primary); border-color: var(--color-state-selected-border);
  box-shadow: var(--shadow-raised); }
.sm-seg input:focus-visible + span, .sm-seg a:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
/* The glide (#1028): when the choice moves, the chosen face slides from the
   option it leaves to the one it takes, under words that never move. Script
   draws it, as the track's ::before, only while it travels (`data-glide`): set
   at the new option's box, then translated and scaled back onto the old one,
   it runs home on transform alone. On landing the option's own face, the same
   face, takes over in the same frame. Nothing is measured at rest, so a resize
   or a late font can never leave it behind; without script the face swaps. */
.sm-seg { position: relative; isolation: isolate; }
.sm-seg[data-glide]::before { content: ""; position: absolute; z-index: -1; box-sizing: border-box;
  left: var(--sm-seg-x); top: var(--sm-seg-y); width: var(--sm-seg-w); height: var(--sm-seg-h);
  transform: translateX(var(--sm-seg-dx)) scaleX(var(--sm-seg-s)); transform-origin: 0 0;
  background: var(--color-surface-card); border: var(--border-width-hairline) solid var(--color-state-selected-border);
  border-radius: var(--radius-chip); box-shadow: var(--shadow-raised); }
.sm-seg[data-glide="to"]::before { transition: transform var(--duration-slow) cubic-bezier(var(--easing-standard)); }
.sm-seg[data-glide="land"]::before { content: none; }
/* While it travels the options carry only their words, and the words change
   color over the glide's own time: the one it leaves fades, the one it takes
   brightens as it arrives. A link it is bound for is not current yet, so the
   words follow the glide, not aria-current. */
.sm-seg[data-glide] span, .sm-seg[data-glide] a {
  transition: color var(--duration-slow) cubic-bezier(var(--easing-standard)); }
.sm-seg[data-glide="land"] span, .sm-seg[data-glide="land"] a { transition: none; }
.sm-seg:not([data-glide="land"])[data-glide] input:checked + span,
.sm-seg:not([data-glide="land"])[data-glide] a[aria-current],
.sm-seg:not([data-glide="land"])[data-glide] a[data-glide-to] { background: transparent;
  border-color: transparent; box-shadow: none; }
.sm-seg[data-glide] a[aria-current]:not([data-glide-to]) { color: var(--color-text-secondary); }
.sm-seg[data-glide] a[data-glide-to] { color: var(--color-text-primary); }
@media (prefers-reduced-motion: reduce) { .sm-seg span, .sm-seg a, .sm-seg[data-glide]::before { transition: none; } }

/* ---------- password reveal (components/field.md, Password) ---------- */
/* An icon-only button inside the well's right edge, shown by script only (it is
   `hidden` in the markup). Both marks are in the button; aria-pressed picks one. */
.sm-reveal { position: relative; }
.sm-reveal .sm-well { padding-right: var(--size-control-md); }
/* Edge draws its own reveal in a password well; ours replaces it. */
.sm-reveal .sm-well::-ms-reveal { display: none; }
.sm-reveal .tog { position: absolute; top: var(--border-width-hairline); bottom: var(--border-width-hairline);
  right: var(--border-width-hairline); width: calc(var(--size-control-md) - var(--border-width-hairline) * 2);
  min-width: var(--size-target-min); display: grid; place-items: center; padding: 0; border: 0;
  background: none; cursor: pointer; color: var(--color-text-subtle); border-radius: var(--radius-control);
  transition: background var(--duration-fast), color var(--duration-fast); }
.sm-reveal .tog[hidden] { display: none; }
.sm-reveal .tog:hover { background: var(--color-action-hover-wash); color: var(--color-text-primary); }
.sm-reveal .tog:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: calc(var(--border-width-focus) * -1); }
.sm-reveal .tog svg { display: block; }
.sm-reveal .tog .off, .sm-reveal .tog[aria-pressed="true"] .on { display: none; }
.sm-reveal .tog[aria-pressed="true"] .off { display: block; }

/* ---------- combobox (components/combobox.md) ---------- */
/* Select's graduation for a list that grows: type to narrow the church's own list, or add
   what was typed. The popup wears the select's picker, the field keeps its text cursor. */
.sm-combo { width: 100%; }
.sm-combo .box { position: relative; }
.sm-combo .sm-well { padding-right: 44px; }
.sm-combo .car { position: absolute; right: var(--space-xs); top: 50%; margin-top: -12px;
  width: 24px; height: 24px; display: grid; place-items: center;
  color: var(--color-text-subtle); pointer-events: none;
  transition: transform var(--duration-base); }
.sm-combo [aria-expanded="true"] ~ .car { transform: rotate(180deg); }
.sm-combo .pop { position: absolute; z-index: 20; left: 0; right: 0; top: 100%;
  margin-top: var(--space-3xs); max-height: calc(var(--size-control-md) * 6); overflow-y: auto;
  background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-control); box-shadow: var(--shadow-overlay);
  padding: var(--space-2xs); }
.sm-combo [role="option"] { display: flex; align-items: center; gap: var(--space-xs);
  cursor: pointer; min-height: var(--size-control-md);
  padding: var(--space-2xs) var(--space-sm); line-height: var(--line-height-snug);
  border-radius: var(--radius-chip); font-family: var(--font-body);
  font-size: var(--font-size-body); color: var(--color-text-primary); }
.sm-combo [role="option"]:hover { background: var(--color-action-hover-wash); }
.sm-combo [role="option"][aria-selected="true"] { background: var(--color-state-selected-bg);
  color: var(--color-state-selected-text); }
.sm-combo [role="option"][hidden], .sm-combo [role="group"][hidden] { display: none; }
.sm-combo .grp { padding: var(--space-sm) var(--space-sm) var(--space-2xs);
  font-family: var(--font-body); font-size: var(--font-size-caption);
  font-weight: var(--font-weight-semibold); line-height: var(--line-height-snug);
  letter-spacing: var(--letter-spacing-wide); text-transform: uppercase;
  color: var(--color-text-subtle); }
.sm-combo .add svg { flex: none; color: var(--color-text-subtle); }
/* The people variant (#1257): a row that is a person is their name over their address, so the
   row stacks its two lines and keeps the option's own height and padding. The address is the
   small, secondary line, and wraps rather than run past the popup's edge. */
.sm-combo .pop [role="option"]:has(.sub) { flex-direction: column; align-items: flex-start;
  justify-content: center; gap: 0; }
.sm-combo .pop [role="option"] .sub { display: block; min-width: 0; overflow-wrap: anywhere;
  font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-combo .fresh { margin: var(--space-2xs) 0 0; font-family: var(--font-body);
  font-size: var(--font-size-small); color: var(--color-text-secondary); }
@media (prefers-reduced-motion: reduce) { .sm-combo .car { transition: none; } }
/* The passage field (#945): no chevron, because its list suggests a book to type on from,
   never the field's value; and under it the echo, what the text reads as so far, in the
   note's voice. */
.sm-combo--passage .sm-well { padding-right: var(--space-sm); }
.sm-combo .echo { margin: var(--space-2xs) 0 0; font-family: var(--font-body);
  font-size: var(--font-size-small); color: var(--color-text-secondary); }

/* ---------- scripture cite ---------- */
/* A navigation chip, not a filter: real <a>, small-caps label, bordered like a control
   because it is one. */
.sm-cite { display: inline-flex; align-items: center; min-height: var(--size-control-sm);
  padding: 0 var(--space-sm); margin: 0 var(--space-2xs) var(--space-2xs) 0;
  font-family: var(--font-body); font-size: var(--font-size-small); font-weight: var(--font-weight-semibold);
  font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps);
  color: var(--color-text-primary); background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-chip); text-decoration: none; cursor: pointer;
  transition: color var(--duration-fast), border-color var(--duration-fast), background var(--duration-fast); }
.sm-cite:hover { color: var(--color-text-action); border-color: var(--color-state-selected-border);
  background: var(--color-state-selected-bg); }
/* In a form, an accepted passage is a value being edited, not a way into the index: the
   chip itself does nothing, so it keeps its rest state under the pointer, and its one act
   is the remove button at its end. */
.sm-cite--input { cursor: default; gap: var(--space-2xs); padding-inline-end: var(--space-2xs); }
.sm-cite--input:hover { color: var(--color-text-primary); border-color: var(--color-border-control);
  background: var(--color-surface-card); }
.sm-cite--input .x { flex: none; width: var(--size-target-min); height: var(--size-target-min);
  display: grid; place-items: center; padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--color-text-subtle); border-radius: var(--radius-chip);
  transition: background var(--duration-fast), color var(--duration-fast); }
.sm-cite--input .x:hover { background: var(--color-action-hover-wash); color: var(--color-text-primary); }
.sm-cite--input .x:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }

/* ---------- filter chip (selectable) ---------- */
.sm-chips { display: flex; gap: var(--space-xs); align-items: center; flex-wrap: wrap; }
/* A list with no chip takes no room, so the gap above it is not left empty (#1072). */
.sm-chips:empty { display: none; }
.sm-chip { position: relative; display: inline-flex; }
.sm-chip input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.sm-chip input:disabled { cursor: default; }
.sm-chip span { min-height: var(--size-control-sm); display: inline-flex; align-items: center;
  padding: 0 var(--space-md); border-radius: var(--radius-pill);
  border: var(--border-width-hairline) solid var(--color-border-control);
  color: var(--color-text-secondary); background: var(--color-surface-card);
  font-family: var(--font-body); font-size: var(--font-size-small);
  font-weight: var(--font-weight-medium); /* constant: see .sm-seg */
  transition: background var(--duration-fast), color var(--duration-fast), border-color var(--duration-fast); }
.sm-chip:hover input:not(:checked) + span { background: var(--color-action-hover-wash); }
.sm-chip input:checked + span { background: var(--color-state-selected-bg);
  border-color: var(--color-state-selected-border); color: var(--color-state-selected-text); }
.sm-chip input:focus-visible + span { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }

/* ---------- card ---------- */
.sm-card { background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); box-shadow: var(--shadow-raised);
  padding: var(--space-lg); font-family: var(--font-body); }
.sm-card > h3 { font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-title); color: var(--color-text-heading); margin: 0 0 var(--space-2xs); }

/* ---------- record block (colophon) ---------- */
.sm-record { background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); max-width: 480px; font-family: var(--font-body); }
/* The record's apparatus is TRUE small caps (the reason Fira Sans is here):
   title row and labels both; only the values run in normal case. */
.sm-record > .ct { font-size: var(--font-size-body); font-weight: var(--font-weight-semibold);
  font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps); color: var(--color-text-brand);
  padding: var(--space-xs) var(--space-sm);
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle); }
/* minmax(0, 1fr): a 1fr track never shrinks below its longest word, so one long
   unbreakable value (an address, an ID) pushed the block past a phone's edge. */
.sm-record dl { display: grid; grid-template-columns: 130px minmax(0, 1fr); gap: var(--space-2xs) var(--space-sm);
  margin: 0; padding: var(--space-sm); font-size: var(--font-size-small); }
.sm-record dt { color: var(--color-text-subtle); font-weight: var(--font-weight-regular);
  font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps); font-size: var(--font-size-body); }
.sm-record dd { margin: 0; color: var(--color-text-primary); overflow-wrap: anywhere; }
.sm-record dd a { color: var(--color-text-action); text-underline-offset: 3px;
  text-decoration-color: var(--color-border-link); }
.sm-record dd a:hover { text-decoration-color: currentColor; }
/* A prose body instead of the grid, for the one companion the record takes: a
   summary set beside it. It is the same shell and the same title row, so the
   pair aligns at the top edge, at the hairline, and at the first line of
   content, with no per-page padding math. The measure is the record's own
   width by default; a page that gives it the reading column says so there. */
.sm-record > .bd { padding: var(--space-sm); font-size: var(--font-size-body);
  color: var(--color-text-secondary); }
.sm-record > .bd > p { margin: 0; }
.sm-record > .bd > p + p { margin-top: var(--space-sm); }

/* ---------- table (B1 · ruled catalog) ---------- */
/* Wide tables scroll inside their own container; the page never scrolls sideways. */
.sm-tablewrap { overflow-x: auto; }
.sm-table { width: 100%; border-collapse: collapse; font-family: var(--font-body);
  font-size: var(--font-size-body); color: var(--color-text-primary); }
.sm-table th { text-align: left; font-size: var(--font-size-body); font-weight: var(--font-weight-medium);
  font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps); color: var(--color-text-subtle);
  padding: var(--space-2xs) var(--space-sm) var(--space-xs);
  border-bottom: var(--border-width-marker) solid var(--color-border-strong); }
.sm-table td { padding: var(--space-sm); vertical-align: middle;
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle); }
/* A ROW header is a cell that happens to name its row, so it rules like one.
   Without this it takes the `th` rule above, which lays the 2px column-header
   border under the first column of EVERY row: a heavy line (white in dark mode)
   marking nothing, repeated down the table.
   It sets like one too. The small-caps label apparatus is right for a column
   header and wrong for what a row header holds (a name, a key, a sentence):
   the Applications queue set every church's name as a gray small-caps label,
   which read as a heading repeated down the table (#518). */
.sm-table th[scope="row"] {
  font-variant-caps: normal; letter-spacing: var(--letter-spacing-normal);
  color: var(--color-text-primary); font-size: inherit;
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle); }
/* A note under a table belongs to the table, so it is the caption, set below it
   with air between. Pinned against the last rule it reads as one more row, and
   the reader's eye stops at the table's edge before it gets there. */
.sm-table > caption.note { caption-side: bottom; text-align: left;
  padding-top: var(--space-md); font-family: var(--font-body);
  font-size: var(--font-size-small); line-height: var(--line-height-body);
  color: var(--color-text-subtle); }
.sm-table tbody tr { transition: background var(--duration-fast); }
.sm-table tbody tr:hover { background: var(--color-action-hover-wash); }
.sm-table .title { font-family: var(--font-display); font-size: var(--font-size-title);
  font-weight: var(--font-weight-medium); }
/* Numerics are right-aligned, and the HEADER moves with the data (SSM-150).
   Tabular figures alone buy almost nothing: `tnum` makes digits of the same
   count stack into a grid, but left-aligned strings of different lengths still
   do not line up by place value, which is the comparison the reader is making.
   A right-aligned column has a hard right edge and a ragged left one, so a
   left-aligned header sits where nothing lines up and stops marking the column.
   Every design system that publishes a rule on this (GOV.UK, Polaris, Primer,
   Carbon, Material) aligns both, and GOV.UK ships alignment and tabular figures
   as a single class because they are one decision.

   There is no CSS decimal alignment (`text-align` with a string value has
   effectively no support). Right alignment, plus tabular figures, plus a fixed
   decimal count per column, is how you get it; the third is the caller's job. */
.sm-table .num, .sm-table th.num { text-align: right; }
/* A date is a CATEGORICAL numeric, not a quantity: nobody compares magnitudes
   down a column of dates, so it aligns left with the text it sits among while
   still taking tabular figures so the columns stack. Cloudscape states the
   split: "left-align text and categorical numerics (dates, postal codes, IDs);
   right-align quantitative numerics". */
.sm-table .date, .sm-table th.date { text-align: left; }
.sm-table .date { font-variant-numeric: lining-nums tabular-nums;
  color: var(--color-text-secondary); white-space: nowrap; }
.sm-table .num { font-variant-numeric: lining-nums tabular-nums;
  color: var(--color-text-secondary); white-space: nowrap; }
/* Rowlink: the whole row is the hit area, not just the title.
   A stretched link, not a click handler: the title's own anchor grows an overlay
   over the row, so the accessibility tree still holds exactly ONE link, the
   keyboard still lands on the title, and the row works with no JavaScript.
   Opt-in per table, because the overlay covers anything beneath it: a table whose
   rows carry their own buttons (restore, delete) must never wear this. */
.sm-table--rowlink tbody tr { position: relative; cursor: pointer; }
.sm-table--rowlink tbody tr a.title::after { content: ""; position: absolute; inset: 0; }
/* The ring belongs to the row the link now covers, not to the words: a keyboard
   user should see the shape they are about to activate. Drawn INSIDE the row,
   because the system's outward offset would collide with the rows above and below. */
.sm-table--rowlink tbody tr:has(a.title:focus-visible) {
  background: var(--color-action-hover-wash);
  outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: calc(-1 * var(--border-width-focus)); }
.sm-table--rowlink tbody tr a.title:focus-visible { outline: none; }
/* Operator-console density; never tenant-facing (spacing.md). */
.sm-table--dense { font-size: var(--font-size-small); }
/* Dense is tighter than the tenant table, not airless: at 4px the row's own
   contents (a control, a figure, the line of small type under it) sat against
   the rules above and below with nothing between them, and a rule the eye
   cannot separate from the content it bounds reads as clutter. */
.sm-table--dense td, .sm-table--dense th[scope="row"] {
  padding: var(--space-xs) var(--space-sm); }
.sm-table--dense .title { font-family: var(--font-body); font-size: var(--font-size-body); font-weight: var(--font-weight-medium); }
/* Fold (components/table.md, #557): on a phone a catalog table folds its date
   column under the title, so every row keeps every fact and the table fits the
   screen instead of scrolling sideways. The date is in the markup twice, once
   per layout, and only one is ever displayed, so a screen reader hears it once. */
.sm-table .fold-sub { display: none; }
/* A folded row's service (#1046) moves to the next line whole, its dot closing the line before. */
.sm-table .fold-sub .svc { display: inline-block; }
@media (max-width: 640px) {
  .sm-table--fold th.date, .sm-table--fold td.date { display: none; }
  .sm-table--fold .fold-sub { display: block; margin-top: var(--space-3xs);
    font-family: var(--font-body); font-size: var(--font-size-small);
    font-weight: var(--font-weight-regular); color: var(--color-text-secondary);
    font-variant-numeric: lining-nums tabular-nums; }
  .sm-table--fold td, .sm-table--fold th { padding-left: var(--space-xs); padding-right: var(--space-xs); }
  /* Any column whose facts the first cell repeats folds the same way (#858),
     not only a date: an address, a role, who sent it. A row's own acts fold
     too, as a .fold-sub row of buttons under the title, so every act on a
     phone is in reach without scrolling the table sideways. */
  .sm-table--fold th.fold-col, .sm-table--fold td.fold-col { display: none; }
  .sm-table--fold .fold-sub.sm-acts { display: flex; margin-top: var(--space-xs); }
}
/* An address has no spaces to wrap at, so it breaks anywhere rather than
   pushing its column past the page's edge (#858). */
.sm-table .email { overflow-wrap: anywhere; }
/* A cell's second line (what a key may do, why an endpoint stopped), and an
   address that breaks where it must rather than run past a phone (#964). */
.sm-table .sub { display: block; margin-top: var(--space-3xs); font-size: var(--font-size-small);
  color: var(--color-text-secondary); font-weight: var(--font-weight-regular); }
.sm-table .url { overflow-wrap: anywhere; }

/* ---------- bulk selection (#1308) ---------- */
/* Select rows, then act on the selection: a bar over a table whose first column
   is a checkbox. The bar's height is stated once, from the control and the
   padding it is built of, so the min-height that stops it changing size as its
   contents change and the scroll-margin that keeps focus clear of it are one
   number. Stacked on a phone it is two controls tall instead of one. */
.sm-bulk { --sm-bulkbar-h: calc(var(--size-control-md) + 2 * var(--space-xs) + 2 * var(--border-width-hairline));
  display: flex; flex-direction: column; gap: var(--space-sm); }
/* Sticky under the header band (--sm-band-height, 0 off the dashboard) so the
   acts stay in reach down a long list, and gone when the list is (patterns/
   layout.md, What may stick). z.rail is the rung the page's other stickies use:
   over the table's own rows, under the band (z.header) and any popover a row
   opens. Positioned, never animated. */
.sm-bulkbar { position: sticky; top: var(--sm-band-height, 0px); z-index: var(--z-rail);
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--space-xs) var(--space-sm);
  min-height: var(--sm-bulkbar-h); padding: var(--space-xs) var(--space-sm);
  background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); font-family: var(--font-body); }
/* With rows picked the bar takes the selected state, the same tint a picked row
   wears. The tint is laid over the card surface rather than set as the fill:
   the dark theme's is translucent, and a stuck bar must hide the rows
   scrolling under it. */
.sm-bulkbar[data-some] { background-color: var(--color-surface-card);
  background-image: linear-gradient(var(--color-state-selected-bg), var(--color-state-selected-bg));
  border-color: var(--color-state-selected-border); }
/* The group grows into the room the acts leave and gives it back before the row
   wraps, so the count wraps its words before the bar wraps its groups. */
.sm-bulkbar .sel { display: flex; align-items: center; gap: var(--space-sm);
  flex: 1 1 12rem; min-width: 0; }
.sm-bulkbar .sel [hidden] { display: none; }
.sm-bulkbar .count { margin: 0; font-size: var(--font-size-body); font-weight: var(--font-weight-regular);
  color: var(--color-text-secondary); font-variant-numeric: lining-nums tabular-nums; }
.sm-bulkbar[data-some] .count { font-weight: var(--font-weight-medium); color: var(--color-text-primary); }
.sm-bulkbar .sm-acts { flex: none; margin-top: 0; }
/* The select cell shrinks to its checkbox. The row is already a 48px target
   with its own inline padding, so the cell gives none: the box lands on the
   column edge the cell text used to start at (space.sm), and the next cell
   closes up to the row's hover wash instead of leaving 24px of air. */
.sm-table th.pick, .sm-table td.pick { width: 1%; padding-inline: 0; }
.sm-table :is(th, td).pick + :is(th, td) { padding-left: var(--space-xs); }
/* Header: the row's 48px target alone sets the header's height. */
.sm-table th.pick { padding-block: 0; }
/* Body: top-aligned, because a row with a second line is taller than the
   target. The title's first line is centered 12px (the cell's padding) plus
   half a 19px line at body leading below the row's top; the box is centered
   21px below the top of its row (8px of padding, 2px of margin, half of 22px),
   so 6px of cell padding puts the two centers within a pixel. The target stays
   48px. */
.sm-table td.pick { vertical-align: top;
  padding-block: calc(var(--space-2xs) + var(--space-3xs)) 0; }
/* A picked row reads as picked without moving: the fill changes, no geometry
   does. Specific enough to hold against the row's hover wash. */
.sm-table tbody tr:has(> td.pick input:checked) { background: var(--color-state-selected-bg); }
/* Focus never lands under the stuck bar (WCAG 2.4.11). Scrolling a focused
   control into view honors the control's own scroll-margin, not its row's, so
   the controls carry it as well as the row: the band, the bar, then space.sm. */
.sm-bulk table :is(tr, input, button, a, summary) {
  scroll-margin-top: calc(var(--sm-band-height, 0px) + var(--sm-bulkbar-h) + var(--space-sm)); }
@media (max-width: 640px) {
  /* Stacked: the count (and Clear, pushed to the right edge) on one line, the
     acts on the next sharing the row (at 320px the two labels do not fit side
     by side and the second takes a line of its own, as a label never shrinks).
     Each line is a full control tall so the bar keeps its height whether or
     not Clear is showing. */
  .sm-bulk { --sm-bulkbar-h: calc(2 * var(--size-control-md) + 3 * var(--space-xs) + 2 * var(--border-width-hairline)); }
  .sm-bulkbar { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }
  .sm-bulkbar .sel { flex: none; min-height: var(--size-control-md); }
  .sm-bulkbar .count { flex: 1 1 auto; }
  .sm-bulkbar .sm-acts > .sm-btn { flex: 1; }
}

/* ---------- status chip ---------- */
.sm-status { display: inline-flex; align-items: center; gap: var(--space-2xs); min-height: 28px;
  padding: 0 var(--space-xs); border-radius: var(--radius-chip);
  border: var(--border-width-hairline) solid; white-space: nowrap;
  font-family: var(--font-body); font-size: var(--font-size-small); font-weight: var(--font-weight-semibold);
  font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps); }
.sm-status--draft { color: var(--color-text-subtle); border-color: var(--color-border-subtle); background: var(--color-surface-page); }
.sm-status--processing { color: var(--color-feedback-warning); border-color: var(--color-state-modified); background: var(--color-feedback-warning-bg); }
.sm-status--review { color: var(--color-state-selected-text); border-color: var(--color-state-selected-border); background: var(--color-state-selected-bg); }
.sm-status--published { color: var(--color-feedback-success); border-color: var(--color-feedback-success); background: var(--color-feedback-success-bg); }
.sm-status--error { color: var(--color-feedback-error); border-color: var(--color-feedback-error); background: var(--color-feedback-error-bg); }

/* ---------- tabs (T1 · underline) ---------- */
/* The baseline rule is an INSET SHADOW on the strip, not a border the links overlap
   with negative margins: the -1px overlap made the strip 1px taller than itself and
   summoned a scrollbar. overflow-y is pinned shut; only x may ever scroll. */
.sm-tabs { display: flex; gap: var(--space-lg); font-family: var(--font-body); font-size: var(--font-size-body);
  box-shadow: inset 0 calc(-1 * var(--border-width-hairline)) 0 var(--color-border-subtle);
  overflow-x: auto; overflow-y: hidden; }
.sm-tabs a { display: inline-flex; align-items: center; min-height: var(--size-control-md);
  color: var(--color-text-secondary); text-decoration: none; cursor: pointer; white-space: nowrap;
  font-weight: var(--font-weight-medium); /* constant: selection never changes geometry */
  border-bottom: var(--border-width-marker) solid transparent;
  transition: color var(--duration-fast); }
.sm-tabs a:hover { color: var(--color-text-primary); }
.sm-tabs a[aria-current] { color: var(--color-text-brand); border-bottom-color: var(--color-state-selected-border); }
/* The strip owns the space beneath it, as the back link does: a page head that
   follows it sits space.lg under the rule instead of on it. The head also
   reserves the act row's height, so a pane with no primary act starts its
   content on the same line as one with a button, and changing tabs moves
   nothing below the strip (#546). */
.sm-tabs + .sm-pagehead { margin-top: var(--space-lg); min-height: var(--size-control-md); }

/* ---------- site header ---------- */
/* One row, never an accidental wrap: the brand yields (min-width 0, two-line clamp),
   the nav and Menu button never shrink. Under breakpoint.compact the nav row hides
   and the Menu button takes over (see .sm-menu below). */
.sm-siteheader { position: relative; display: flex; justify-content: space-between;
  align-items: baseline; gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  background: var(--color-surface-card);
  border-bottom: var(--border-width-hairline) solid var(--color-border-strong);
  font-family: var(--font-body); }
/* Long church names (the deliberate rule, Phase 3): title size, at most TWO lines,
   ellipsis past that. The wordmark's size never varies between tenants; the full
   name always appears whole on the landing page and footer. */
.sm-siteheader .brand { font-family: var(--font-display); font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-title); line-height: var(--line-height-snug);
  color: var(--color-text-heading); text-decoration: none; cursor: pointer;
  flex: 1 1 auto; min-width: 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.sm-siteheader > nav { display: flex; gap: var(--space-lg); flex: none; }
.sm-siteheader > nav a { display: inline-flex; align-items: center; min-height: var(--size-target-min);
  color: var(--color-text-primary); text-decoration: none; cursor: pointer;
  font-weight: var(--font-weight-medium); font-size: var(--font-size-body);
  border-bottom: var(--border-width-marker) solid transparent; padding-bottom: var(--space-3xs);
  transition: color var(--duration-fast); }
.sm-siteheader > nav a:hover { color: var(--color-text-action); }
.sm-siteheader > nav a[aria-current] { color: var(--color-text-brand); border-bottom-color: var(--color-state-selected-border); }
/* The brand kit's header (#1399). In small capitals: the menu set the way a
   printed masthead sets it, a church's choice; the current marker is
   unchanged. The mark: the church's own logo beside its name, at the
   name's line height plus its air, never wider than a square and a half so a
   wide wordmark cannot crowd the name off the line. */
.sm-siteheader--caps > nav a { text-transform: uppercase; letter-spacing: var(--letter-spacing-wide);
  font-size: var(--font-size-caption); font-weight: var(--font-weight-semibold); }
.sm-siteheader .sm-brandmark { flex: none; align-self: center; display: block;
  height: var(--size-control-sm); width: auto; max-width: calc(1.5 * var(--size-control-sm));
  object-fit: contain; margin-right: calc(-1 * var(--space-2xs)); }
.sm-siteheader .sm-brandmark[hidden] { display: none; }
.sm-siteheader:has(> .sm-brandmark:not([hidden])) { align-items: center; }
/* On a band of the church's color the rules where the band meets the page are
   hairlines of the band's own ink, not the paper's strong rule: the change of
   ground already draws the edge. */
.sm-siteheader.sm-scheme-band { border-bottom-color: var(--color-border-subtle); }
footer.sm-sitefooter.sm-scheme-band-deep { border-top-color: var(--color-border-subtle); color: var(--color-text-secondary); }
.sm-minibar.sm-scheme-band { border-top-color: var(--color-border-subtle); }
/* The front page's opening on a band of the church's color (#1400): the
   search, the newest sermon and the latest series, meeting the header. The
   ground reaches the window's edges as a border image set out sideways, which
   is ink overflow: the layout keeps the page's width and nothing scrolls
   sideways (a 100vw box would, by a scrollbar's width), and nothing is
   clipped, so no anti-aliased seam shows where the band meets the header (a
   clipped spread shadow drew one). Its parts keep the page's rhythm. */
.sm-frontband { display: flex; flex-direction: column; gap: var(--space-2xl);
  background: var(--color-surface-page); color: var(--color-text-primary);
  border-image: conic-gradient(var(--color-surface-page) 0 0) fill 0 / / 0 100vmax;
  margin-top: calc(-1 * var(--space-2xl)); padding-block: var(--space-2xl) var(--space-3xl); }

/* Compact navigation (M2, chosen Phase 3): a bordered "Menu" button (word + icon,
   never a bare glyph) opening a full-width panel of 48px nav rows. Native details;
   no JS. Hidden above breakpoint.compact, where the nav row shows instead. */
.sm-menu { display: none; flex: none; }
.sm-menu > summary { list-style: none; }
.sm-menu > summary::-webkit-details-marker { display: none; }
.sm-menu > summary .sm-menubtn { display: inline-flex; align-items: center; gap: var(--space-2xs);
  min-height: 44px; padding: 0 var(--space-sm); cursor: pointer;
  background: var(--color-surface-card); color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-control);
  font-family: var(--font-body); font-size: var(--font-size-body); font-weight: var(--font-weight-medium);
  transition: background var(--duration-fast), border-color var(--duration-fast); }
.sm-menu > summary:hover .sm-menubtn { background: var(--color-action-hover-wash);
  border-color: var(--color-border-strong); }
.sm-menu[open] > summary .sm-menubtn { background: var(--color-surface-subtle);
  border-color: var(--color-border-strong); }
/* The panel opens over the page, so it takes the popover layer. The library's
   header makes no stacking context of its own (it neither sticks nor stacks), so
   the panel meets the search band (.sm-find, on the rail) directly, and a raw 5
   slid it under the band and hid its second row (#1017). */
.sm-menu .panel { position: absolute; left: 0; right: 0; top: 100%; z-index: var(--z-popover);
  background: var(--color-surface-card);
  border-bottom: var(--border-width-hairline) solid var(--color-border-strong);
  box-shadow: var(--shadow-panel); padding: var(--space-2xs) 0 var(--space-xs); }
.sm-menu[open] .panel { opacity: 1; transform: none;
  transition: opacity var(--duration-base) cubic-bezier(var(--easing-enter)),
    transform var(--duration-base) cubic-bezier(var(--easing-enter)); }
@starting-style { .sm-menu[open] .panel { opacity: 0; transform: translateY(6px); } }
.sm-menu .panel a { display: flex; align-items: center; min-height: var(--size-control-md);
  padding: 0 var(--space-md); text-decoration: none; cursor: pointer;
  color: var(--color-text-primary); font-size: var(--font-size-body);
  font-weight: var(--font-weight-medium);
  border-left: var(--border-width-marker) solid transparent;
  transition: background var(--duration-fast); }
.sm-menu .panel a:hover { background: var(--color-action-hover-wash); }
.sm-menu .panel a[aria-current] { color: var(--color-text-brand);
  border-left-color: var(--color-state-selected-border);
  background: var(--color-state-selected-bg); }

/* Church switch (#1322): the brand slot's face for a reader with two or more churches.
   A native details, so it opens without script. It replaces the brand link and keeps
   the band's one-row contract: the details yields like the brand does (min-width 0,
   margin-left auto on its far side so the nav still sits at the row's end), and inside
   the summary it is the NAME that clamps to two lines, never the chevron, which keeps
   its size. The name is the same span.brand, so every .sm-siteheader .brand rule above
   (display face, size, clamp) applies to it unchanged. The panel is a popover under the
   summary, not the compact menu's full-width sheet, and it borrows that panel's surface,
   edge, shadow and layer; its rows borrow the menu's row (height, padding, hover wash,
   current row: ink, left bar, wash). */
.sm-churchswitch { position: relative; flex: 0 1 auto; min-width: 0; margin-right: auto; }
.sm-churchswitch > summary { display: flex; align-items: center; gap: var(--space-xs);
  list-style: none; cursor: pointer; border-radius: var(--radius-control); }
.sm-churchswitch > summary::-webkit-details-marker { display: none; }
.sm-churchswitch > summary:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-churchswitch .chev { flex: none; width: var(--size-icon-sm); height: var(--size-icon-sm);
  color: var(--color-text-secondary); }
.sm-churchswitch > summary:hover .chev { color: var(--color-text-action); }
.sm-churchswitch[open] > summary .chev { transform: rotate(180deg); }
.sm-churchswitch .panel { position: absolute; left: 0; top: calc(100% + var(--space-2xs));
  z-index: var(--z-popover); min-width: 16rem; max-width: calc(100vw - 2 * var(--space-lg));
  max-height: 60vh; overflow-y: auto;
  background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-strong);
  border-radius: var(--radius-surface); box-shadow: var(--shadow-panel);
  padding: var(--space-2xs) 0 var(--space-xs); }
/* The micro-label's own face (.sm-microlabel, above), as the panel's heading. */
.sm-churchswitch .lbl { margin: 0; padding: var(--space-xs) var(--space-md) var(--space-2xs);
  font-family: var(--font-body); font-size: var(--font-size-caption);
  font-weight: var(--font-weight-semibold); letter-spacing: var(--letter-spacing-wide);
  text-transform: uppercase; color: var(--color-text-subtle); }
.sm-churchswitch form { margin: 0; }
.sm-churchswitch ul { list-style: none; margin: 0; padding: 0; }
.sm-churchswitch li { margin: 0; padding: 0; }
.sm-churchswitch :is(button, .current) { display: flex; align-items: center; gap: var(--space-xs);
  width: 100%; min-height: var(--size-control-md); margin: 0;
  padding: var(--space-2xs) var(--space-md); text-align: left; overflow-wrap: anywhere;
  background: transparent; color: var(--color-text-primary);
  border: 0; border-left: var(--border-width-marker) solid transparent; border-radius: 0;
  font-family: var(--font-body); font-size: var(--font-size-body);
  font-weight: var(--font-weight-medium); line-height: var(--line-height-snug); }
.sm-churchswitch button { cursor: pointer; transition: background var(--duration-fast); }
.sm-churchswitch button:hover { background: var(--color-action-hover-wash); }
.sm-churchswitch button:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: calc(-1 * var(--border-width-focus)); background: var(--color-action-hover-wash); }
/* The church you are in is a place you already are, so it is not a button: the menu's
   current row, with a check beside the name. */
.sm-churchswitch .current { cursor: default; color: var(--color-text-brand);
  border-left-color: var(--color-state-selected-border);
  background: var(--color-state-selected-bg); }
.sm-churchswitch .check { flex: none; margin-left: auto;
  width: var(--size-icon-sm); height: var(--size-icon-sm); }
@media (prefers-reduced-motion: no-preference) {
  .sm-churchswitch .chev { transition: transform var(--duration-base); }
  .sm-churchswitch[open] .panel { opacity: 1; transform: none;
    transition: opacity var(--duration-base) cubic-bezier(var(--easing-enter)),
      transform var(--duration-base) cubic-bezier(var(--easing-enter)); }
  @starting-style { .sm-churchswitch[open] .panel { opacity: 0; transform: translateY(var(--space-xs)); } }
}
/* Nothing to switch to on paper. */
@media print { .sm-churchswitch .chev, .sm-churchswitch .panel { display: none; } }

/* Row menu (#1286): one labeled Manage button at the end of a table row, its acts in
   a popover under it. Native details, so it opens in place without script; the island
   (data-fixed) then places .pop against the screen with inline top/right so a table's
   scroll container cannot clip it, which is why no rule here sets left or bottom. It is
   inline-block, so the row grows by the summary's own height and no more. */
.sm-rowmenu { position: relative; display: inline-block; }
.sm-rowmenu > summary { list-style: none; cursor: pointer; gap: var(--space-2xs); }
.sm-rowmenu > summary::-webkit-details-marker { display: none; }
.sm-rowmenu > summary svg { flex: none; transition: transform var(--duration-fast); }
.sm-rowmenu[open] > summary svg { transform: rotate(180deg); }
.sm-rowmenu > .pop { position: absolute; right: 0; top: calc(100% + var(--space-3xs));
  z-index: var(--z-popover); min-width: 12rem; display: flex; flex-direction: column;
  padding: var(--space-2xs); background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-strong);
  border-radius: var(--radius-surface); box-shadow: var(--shadow-overlay); }
.sm-rowmenu .item { display: block; width: 100%; min-height: var(--size-target-min);
  padding: var(--space-sm) var(--space-md); text-align: left; cursor: pointer;
  border: 0; border-radius: var(--radius-control); background: transparent;
  color: var(--color-text-primary); font-family: var(--font-body);
  font-size: var(--font-size-body); }
.sm-rowmenu .item:hover, .sm-rowmenu .item:focus-visible { background: var(--color-action-hover-wash); }
.sm-rowmenu .item--destructive { color: var(--color-action-destructive-text); }
/* On a touch screen the small buttons a row is worked by reach the 44px target
   (Principle 3), as a reference row's do: a Manage, and a correction's Hear it
   and Edit (#1285, #1286). */
@media (pointer: coarse) {
  .sm-rowmenu > summary, .sm-crow > .acts .sm-btn--sm { min-height: var(--size-target-min); } }
/* Under a folded row's name (a phone, components/table.md Fold) the button
   sits at the row's start, so its menu opens from its start edge, inside the
   screen, instead of its end (#1286). */
.fold-sub .sm-rowmenu > .pop { left: 0; right: auto; }
.sm-rowmenu .sep { border: 0; border-top: var(--border-width-hairline) solid var(--color-border-subtle);
  height: 0; margin: var(--space-2xs) 0; }
@media (prefers-reduced-motion: reduce) { .sm-rowmenu > summary svg { transition: none; } }

/* The bordered chrome control: a 44px bordered box carrying a stroke icon and
   one word. The account slot's sign-out on the dashboard and the console is its
   only member. The theme control was its other one, as .sm-themebtn, until no
   band carried it: the product's moved to the footer in #1012 and the marketing
   site's followed in #1018, both as the quiet .sm-themeswitch. A church staffer
   reads a missing border as broken (Principle 3), so chrome controls carry one
   even though the band behind them is already card. */
.sm-chromebtn { display: inline-flex; align-items: center; gap: var(--space-2xs);
  min-height: 44px; padding: 0 var(--space-sm); cursor: pointer; flex: none;
  background: var(--color-surface-card); color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-control); text-decoration: none;
  font-family: var(--font-body); font-size: var(--font-size-body); font-weight: var(--font-weight-medium);
  transition: background var(--duration-fast), border-color var(--duration-fast); }
.sm-chromebtn:hover { background: var(--color-action-hover-wash);
  border-color: var(--color-border-strong); }
/* Sign-out is a POST, so the control is wrapped in a form. The wrapper carries
   no look of its own: it exists to be the flex item in the header row and the
   full-width row in the panel, so the button inside it can be the same
   .sm-chromebtn in both places. */
.sm-signout { display: inline-flex; flex: none; margin: 0; }
/* In the compact menu a chrome control is a panel row, not a bordered button: it
   sits below the nav rows behind a hairline, because it is chrome and they are
   places. */
.sm-menu .panel .sm-chromebtn { display: flex; width: 100%;
  justify-content: flex-start;
  min-height: var(--size-control-md); padding: 0 var(--space-md);
  background: none; border: 0; border-radius: 0; }
/* The hairline opens the chrome group once, under the last nav row, on whichever
   control comes first (Sign out, on the dashboard and the console). It belongs
   to the group, not to each control, so a second chrome row does not draw a
   second rule through the middle of it. The library's panel and the marketing
   site's hold no chrome group, so they draw no rule (#1012, #1018). */
.sm-menu .panel > nav + .sm-signout, .sm-menu .panel > nav + .sm-chromebtn {
  margin-top: var(--space-2xs);
  border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-menu .panel .sm-chromebtn:hover { background: var(--color-action-hover-wash); }
.sm-menu .panel .sm-signout { display: block; }
@media (max-width: 640px) {
  .sm-siteheader > nav { display: none; }
  .sm-menu { display: block; }
  /* The header's contract is one row at every width, and a long church name
     keeping ~40 characters whole at 390px. A button beside Menu costs about a
     dozen of them, so a chrome control (the account slot, on the signed-in
     surfaces) moves into the panel instead. */
  .sm-siteheader > .sm-chromebtn, .sm-siteheader > .sm-signout { display: none; }
  /* The exception is a band with no nav, which has no Menu either (the dashboard's
     setup band, #1224): there is no panel for Sign out to move into, so it stays on
     the line beside the brand, which keeps clamping to two lines. */
  .sm-siteheader:not(:has(> nav)) > .sm-signout { display: inline-flex; }
}
/* A band with five places (the dashboard's, since Analytics, #1030) folds
   sooner. The fifth costs about 94px, so at 641px it left a long church name
   29px; under 768px it takes the Menu, which keeps the name at least the 123px
   a four-place band gives it at 641px. */
@media (max-width: 767px) {
  .sm-siteheader:has(> nav > a:nth-child(5)) > nav,
  .sm-siteheader:has(> nav > a:nth-child(5)) > .sm-chromebtn,
  .sm-siteheader:has(> nav > a:nth-child(5)) > .sm-signout { display: none; }
  .sm-siteheader:has(> nav > a:nth-child(5)) .sm-menu { display: block; }
}
/* Five places AND a chrome link (the marketing site's Sign in, #1038) fold sooner
   still: the chrome box costs about 110px, and measured at 768px the wordmark was
   left 158px and broke onto two lines. Under 800px the Menu takes both, which keeps
   the wordmark whole on one line from 800px up. The operator console matches too
   (eight places, and Access's logout is a link), which suits it; the dashboard's
   Sign out is a form (.sm-signout), so the dashboard keeps its 768px fold. */
@media (max-width: 799px) {
  .sm-siteheader:has(> nav > a:nth-child(5)):has(> .sm-chromebtn) > nav,
  .sm-siteheader:has(> nav > a:nth-child(5)) > .sm-chromebtn { display: none; }
  .sm-siteheader:has(> nav > a:nth-child(5)):has(> .sm-chromebtn) .sm-menu { display: block; }
}
/* Six places (the dashboard's, since Trash, #1308) fold sooner still. The sixth
   costs 69px, and measured at 768px it left a long church name 73px; under 840px
   the Menu takes the row, which keeps the name the 141px the five-place band
   gives it at 768px. Scoped to the band whose Sign out is a form, so the operator
   console, whose eight places sit beside a link, keeps its 800px fold. */
@media (max-width: 839px) {
  .sm-siteheader:has(> nav > a:nth-child(6)):has(> .sm-signout) > nav,
  .sm-siteheader:has(> nav > a:nth-child(6)) > .sm-signout { display: none; }
  .sm-siteheader:has(> nav > a:nth-child(6)):has(> .sm-signout) .sm-menu { display: block; }
}

/* ---------- pagination ---------- */
/* The strip owns the space above it. Pagination only ever follows a list, and every
   caller put it flush against the last row's hairline, where the count line read as
   another table row rather than as the shelf's label. */
.sm-pagination { display: flex; align-items: center; gap: var(--space-xs); flex-wrap: wrap;
  margin-top: var(--space-md);
  font-family: var(--font-body); font-size: var(--font-size-small); }
.sm-pagination .count { color: var(--color-text-subtle); margin-right: auto; }
.sm-page { display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--size-control-sm); min-height: var(--size-control-sm); padding: 0 var(--space-xs);
  background: var(--color-surface-card); color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-control); cursor: pointer;
  font-family: var(--font-body); font-size: var(--font-size-small); font-weight: var(--font-weight-medium);
  font-variant-numeric: tabular-nums; text-decoration: none;
  transition: background var(--duration-fast), border-color var(--duration-fast); }
.sm-page:hover { background: var(--color-action-hover-wash); border-color: var(--color-border-strong); }
.sm-page[aria-current] { background: var(--color-state-selected-bg);
  border-color: var(--color-state-selected-border); color: var(--color-state-selected-text); }
.sm-page:disabled, .sm-page[aria-disabled="true"] { cursor: default; pointer-events: none;
  color: var(--color-text-disabled); border-color: var(--color-border-subtle); }

/* ---------- disclosure (native details) ---------- */
/* Full width ALWAYS: without this a details sizes to its widest content, so opening
   a body wider than the title reflowed the whole row (geometry rule violation). */
details.sm-x { width: 100%; }
details.sm-x > summary { list-style: none; cursor: pointer; }
details.sm-x > summary::-webkit-details-marker { display: none; }
/* A hairline at rest, because a control that is invisible until hovered is not a
   control: closed, the summary was a line of bold text with a chevron a thousand
   pixels away, and nobody read it as something to click. */
.sm-xrow { display: flex; align-items: center; gap: var(--space-xs);
  min-height: var(--size-row-min); padding: var(--space-2xs) var(--space-sm);
  background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-control); font-family: var(--font-body); font-size: var(--font-size-body);
  color: var(--color-text-primary); font-weight: var(--font-weight-medium);
  transition: background var(--duration-fast), border-color var(--duration-fast); }
.sm-xrow:hover { background: var(--color-action-hover-wash);
  border-color: var(--color-border-strong); }
/* Open, the summary and its body are one panel: the row squares off its bottom and
   the body picks up the same edge, so the content reads as belonging to the label
   rather than as the next thing on the page. */
details.sm-x[open] > summary .sm-xrow { background: var(--color-surface-subtle);
  border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.sm-xrow .car { margin-left: auto; width: 24px; height: 24px; display: grid; place-items: center;
  color: var(--color-text-subtle); transition: transform var(--duration-base); }
details.sm-x[open] > summary .sm-xrow .car { transform: rotate(180deg); }
.sm-xbody { padding: var(--space-md) var(--space-sm) var(--space-md); font-family: var(--font-body);
  font-size: var(--font-size-body); color: var(--color-text-secondary);
  border: var(--border-width-hairline) solid var(--color-border-subtle); border-top: 0;
  border-radius: 0 0 var(--radius-control) var(--radius-control); }
/* A form's fields inside a disclosure (#1100): the form's own rhythm, one gap between them, and
   the lead sentence set like a form's own paragraphs. */
.sm-xbody > .fields { display: flex; flex-direction: column; gap: var(--space-md); min-width: 0; }
.sm-xbody > .fields > p { margin: 0; }
/* The run once it is done, folded to one line (#1079; patterns/dashboard.md, Review): "Processed in
   9 min" on the left, "Show steps" before the caret, and the timeline's rows, all done, under it
   when it is open. A disclosure like any other: the words say what pressing it does, in both
   states, and the geometry does not move between them. */
.sm-donefold { max-width: 640px; }
.sm-donefold .more { margin-left: auto; font-size: var(--font-size-small);
  font-weight: var(--font-weight-regular); color: var(--color-text-subtle); }
.sm-donefold .more .when-open, .sm-donefold[open] .more .when-closed { display: none; }
.sm-donefold[open] .more .when-open { display: inline; }
.sm-donefold .sm-xrow .car { margin-left: var(--space-xs); }
.sm-donefold .sm-xbody .sm-stages { margin-top: 0; }

/* ---------- dialog ---------- */
/* Real behavior only: opened via showModal() (components.js wires [data-open-dialog]),
   closed by its own form[method=dialog] buttons or Esc. Never rendered pre-opened:
   a non-modal open dialog is absolutely positioned by the UA and squats on the page. */
dialog.sm-dialog { background: var(--color-surface-card); color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); box-shadow: var(--shadow-overlay);
  padding: var(--space-lg); max-width: 480px; font-family: var(--font-body); }
dialog.sm-dialog::backdrop { background: var(--color-surface-scrim); }
/* A combobox in a dialog (the merge pick, #1293): the browser makes a modal
   scroll its own overflow, which would put the open list inside the dialog's
   scroll instead of over it. While the list is open the dialog lets it out. */
dialog.sm-dialog:has(.sm-combo .pop:not([hidden])) { overflow: visible; }
.sm-dialog .title { font-family: var(--font-display); font-size: var(--font-size-title);
  font-weight: var(--font-weight-medium); color: var(--color-text-heading); margin: 0 0 var(--space-2xs); }
/* The act row keeps space.md above it whatever sits there (#819): a typed-
   confirmation field ended flush on the buttons, because only the body carried
   the gap. The body keeps its own space.md below it, for whatever follows it,
   and where the body is last the two margins collapse into one space.md rather
   than adding up. That holds because a dialog's content is in block flow; do
   not make the dialog or its form a flex or grid container. */
.sm-dialog .body { font-size: var(--font-size-body); color: var(--color-text-secondary); margin: 0 0 var(--space-md); }
.sm-dialog .acts { display: flex; gap: var(--space-xs); justify-content: flex-end; margin-top: var(--space-md); }
/* A choice in a dialog (a role, #977): radio rows between the body and the act
   row, then the cost of the option chosen and the server's refusal, each a
   paragraph of the same rhythm. The radio rows are the form's own component
   (components/choice.md). The group is as wide as the dialog, not a field's 480px. */
.sm-dialog .sm-fieldset { max-width: none; margin: 0 0 var(--space-md); }
.sm-dialog .sm-msg { margin: 0 0 var(--space-md); }
/* Under breakpoint.compact the dialog is the screen's width less the page's own
   gutter (#828), so it lines up with the content behind it instead of taking
   whatever width its words give it. The UA's max-width would hold it to the
   viewport less 2em and some pixels, a margin of its own, so it goes. */
/* Its act row shares the width out (#823): two acts that fit split the row,
   and two that do not, "Keep processing" beside "Cancel processing", each take
   a row of their own, full width, in the order they read. A button's label
   never wraps, so a row that could not wrap ran its first act off the edge. */
@media (max-width: 640px) {
  dialog.sm-dialog { width: calc(100% - 2 * var(--space-md)); max-width: none; }
  .sm-dialog .acts { flex-wrap: wrap; }
  .sm-dialog .acts > .sm-btn { flex: 1 1 auto; } }

/* ---------- banner (inline alert) ---------- */
/* Color is never the only channel: every banner carries an icon and words. */
.sm-banner { display: flex; gap: var(--space-sm); align-items: flex-start;
  padding: var(--space-sm) var(--space-md); border-radius: var(--radius-control);
  border: var(--border-width-hairline) solid; font-family: var(--font-body); font-size: var(--font-size-body); }
.sm-banner .ico { flex: none; display: grid; place-items: center; margin-top: 2px; }
.sm-banner .msg { flex: 1; min-width: 0; }
.sm-banner .msg b { font-weight: var(--font-weight-semibold); }
/* Every inline link in the system has a rest→hover step. Banner links run in the
   banner's own ink, so the step is decoration THICKNESS (1px → 2px), visible on any
   wash, zero geometry change (text-decoration never affects layout). */
.sm-banner .msg a { color: inherit; font-weight: var(--font-weight-medium);
  text-underline-offset: 3px; text-decoration-thickness: 1px;
  transition: text-decoration-thickness var(--duration-fast); }
.sm-banner .msg a:hover { text-decoration-thickness: 2px; }
.sm-banner--error { color: var(--color-feedback-error); border-color: var(--color-feedback-error);
  background: var(--color-feedback-error-bg); }
.sm-banner--warning { color: var(--color-feedback-warning); border-color: var(--color-state-modified);
  background: var(--color-feedback-warning-bg); }
.sm-banner--success { color: var(--color-feedback-success); border-color: var(--color-feedback-success);
  background: var(--color-feedback-success-bg); }
.sm-banner--info { color: var(--color-text-secondary); border-color: var(--color-border-subtle);
  background: var(--color-surface-subtle); }
/* A banner act that has to POST (a support session's End session, #880): the
   message is a block holding the sentence and the act's form as siblings (a
   form may not sit in phrasing content), both set inline so the act reads as
   the sentence's last words. Its link-button runs in the banner's ink with the
   banner link's 1px to 2px underline step, so it reads as the sentence's act,
   not an accent link dropped onto the wash. */
.sm-banner .msg > p { display: inline; margin: 0; }
.sm-banner .msg form { display: inline; }
/* A shown-once secret (#964): the warning's sentence, then the value on a
   line of its own in a well, whole and selected with one press, then its
   Copy. A copy the browser refused says so under it. */
.sm-reveal .msg { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-xs); }
.sm-reveal .secret { display: block; width: 100%; box-sizing: border-box;
  padding: var(--space-xs) var(--space-sm); font-family: var(--font-mono); font-size: var(--font-size-small);
  background: var(--color-surface-card); color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-control); border-radius: var(--radius-control);
  overflow-wrap: anywhere; user-select: all; }
.sm-reveal .said { font-size: var(--font-size-small); }
/* A banner act that needs no POST (a stale tab's Reload this page, #1322) sits in
   the message itself, beside the sentence, in the same ink as one in a form. */
.sm-banner .msg form .sm-btn--link, .sm-banner .msg > .sm-btn--link { color: inherit; padding: 0;
  text-decoration-color: currentColor; text-decoration-thickness: 1px;
  transition: text-decoration-thickness var(--duration-fast); }
.sm-banner .msg form .sm-btn--link:hover, .sm-banner .msg > .sm-btn--link:hover { text-decoration-thickness: 2px; }

/* ---------- toast ---------- */
/* Transient confirmation only: errors are never toasts (they persist as banners). */
.sm-toast { display: flex; align-items: center; gap: var(--space-sm); max-width: 420px;
  background: var(--color-surface-card); color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-control); box-shadow: var(--shadow-overlay);
  padding: var(--space-sm) var(--space-md); font-family: var(--font-body); font-size: var(--font-size-body); }
.sm-toast .ico { flex: none; display: grid; place-items: center; color: var(--color-feedback-success); }
.sm-toast .msg { flex: 1; min-width: 0; }
.sm-toast .x { flex: none; width: var(--size-target-min); height: var(--size-target-min);
  display: grid; place-items: center; border: 0; background: none; cursor: pointer; padding: 0;
  color: var(--color-text-subtle); border-radius: var(--radius-chip);
  transition: background var(--duration-fast); }
.sm-toast .x:hover { background: var(--color-action-hover-wash); color: var(--color-text-primary); }
/* Where toasts appear (#494, the first in the product): bottom-right, above a
   pinned mini-bar where the page has one, full width inside the gutters on a
   phone. The region is in the page from the start (role="status"), so a toast
   landing in it is announced. Enters with fade-and-settle, leaves by fading. */
.sm-toasts { position: fixed; right: var(--space-lg); bottom: var(--space-lg); z-index: var(--z-toast);
  display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-xs);
  pointer-events: none; }
.sm-minibar ~ .sm-toasts { bottom: calc(var(--size-control-sm) + 2 * var(--space-xs) + var(--space-md)); }
.sm-toasts .sm-toast { pointer-events: auto;
  animation: sm-toast-in var(--duration-base) cubic-bezier(var(--easing-enter)); }
.sm-toasts .sm-toast.is-leaving { opacity: 0;
  transition: opacity var(--duration-fast) cubic-bezier(var(--easing-exit)); }
@keyframes sm-toast-in { from { opacity: 0; transform: translateY(var(--space-2xs)); } }
@media (prefers-reduced-motion: reduce) {
  .sm-toasts .sm-toast { animation: none; }
  .sm-toasts .sm-toast.is-leaving { transition: none; } }
@media (max-width: 640px) { .sm-toasts { left: var(--space-md); right: var(--space-md); align-items: stretch; } }
/* While a toast shows, the page keeps what the focus moves to clear of the
   corner it holds (#869; WCAG 2.4.11): scrolling to the focused control stops
   short of the toast, one line of it on a wide screen, two on a phone. */
html:has(.sm-toasts .sm-toast) { scroll-padding-bottom: calc(var(--space-lg) + var(--size-control-md) + var(--space-md)); }
@media (max-width: 640px) {
  html:has(.sm-toasts .sm-toast) { scroll-padding-bottom: calc(var(--space-lg) + var(--size-control-md) + var(--space-xl) + var(--space-md)); } }

/* ---------- progress ---------- */
.sm-progress { font-family: var(--font-body); font-size: var(--font-size-small); max-width: 480px; }
.sm-progress .row { display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--space-sm); margin-bottom: var(--space-2xs); }
.sm-progress .lbl { color: var(--color-text-secondary); }
.sm-progress .pct { color: var(--color-text-primary); font-variant-numeric: tabular-nums; }
.sm-progress .track { height: 6px; border-radius: var(--radius-chip);
  background: var(--color-surface-sunken);
  border: var(--border-width-hairline) solid var(--color-border-subtle); overflow: hidden; }
/* The fill is the track's full width and slides in from the left, the track
   clipping what has not arrived. The markup gives the share as --sm-progress
   (style="--sm-progress:62%"); unset, nothing shows. It moves by transform,
   never width (motion.md), and translates rather than scales, because scaleX
   would squash its rounded leading end (#837). */
.sm-progress .fill { display: block; height: 100%; background: var(--color-action-primary);
  border-radius: inherit; transform: translateX(calc(var(--sm-progress, 0%) - 100%));
  transition: transform var(--duration-base) cubic-bezier(var(--easing-standard)); }

/* ---------- upload rows (progress.md, Upload rows; #864) ----------
   A batch of files on their way: the batch's bar above, then one row per file,
   its name and one word ("Waiting", "Uploaded", "Didn't upload"), and its own
   bar only while it moves, so a long batch reads as a list and not a wall of
   tracks. The name wraps rather than pushing the word off the row. */
.sm-uploads { list-style: none; margin: 0; padding: 0; max-width: 640px;
  display: flex; flex-direction: column; gap: var(--space-sm); }
.sm-uploads .sm-progress { max-width: none; }
.sm-uploads .lbl { min-width: 0; overflow-wrap: anywhere; }
.sm-uploads .pct { flex: none; }
.sm-uploads .hint { margin: var(--space-2xs) 0 0; font-family: var(--font-body);
  font-size: var(--font-size-small); color: var(--color-text-subtle); }

/* ---------- timeline (progress.md, Timeline) ---------- */
/* The bar, then the stages under it. The fill follows the clock against each
   stage's usual time and holds at a stage's end until the pipeline reports the
   next; the bar's figure is time words, never a percent nobody measured. Rows
   change state by swapping colors on constant geometry: no growing marks, no
   weight jumps (the wizard's rule). */
.sm-timeline { max-width: 640px; }
.sm-timeline .sm-progress { max-width: none; }
/* The one line under the list ("You can leave this page. The work goes on."): small and quiet (#1120). */
.sm-timeline > p { margin: var(--space-sm) 0 0; font-family: var(--font-body); font-size: var(--font-size-small);
  color: var(--color-text-secondary); }
.sm-stages { list-style: none; margin: var(--space-sm) 0 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-2xs);
  font-family: var(--font-body); font-size: var(--font-size-small); }
.sm-stage { display: grid; grid-template-columns: 16px minmax(0, 1fr) auto; align-items: baseline;
  column-gap: var(--space-xs); color: var(--color-text-subtle); }
.sm-stage .mark { width: 14px; height: 14px; border-radius: var(--radius-pill); align-self: center;
  display: inline-grid; place-items: center; background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-control);
  color: var(--color-feedback-success); }
.sm-stage .mark svg { display: none; }
.sm-stage .when { color: var(--color-text-subtle); font-variant-numeric: tabular-nums; text-align: right; }
.sm-stage--done { color: var(--color-text-secondary); }
.sm-stage--done .mark { background: var(--color-feedback-success-bg); border-color: var(--color-feedback-success); }
.sm-stage--done .mark svg { display: block; }
.sm-stage--current { color: var(--color-text-primary); }
.sm-stage--current .mark { background: var(--color-action-primary); border-color: var(--color-action-primary-hover); }
.sm-stage--current .when { color: var(--color-text-secondary); }
/* A clock (#1120) is one short figure at the right of its row's one line, at every width. A cell
   that holds words ("Sent", the first run's and the publish rows' words) still wraps under its
   label under breakpoint.compact. */
@media (max-width: 640px) {
  .sm-stage:not(:has(.clk)) { grid-template-columns: 16px minmax(0, 1fr); }
  .sm-stage:not(:has(.clk)) .when { grid-column: 2; text-align: left; }
}

/* First run (#807, patterns/dashboard.md): a card with one sentence and two stages. The stages are
   the timeline's list as it is; only the card's own rhythm is set here. */
.sm-firstrun { max-width: 640px; }
.sm-firstrun > h2 { font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-title); color: var(--color-text-heading); margin: 0 0 var(--space-2xs); }
.sm-firstrun > p { margin: 0 0 var(--space-sm); color: var(--color-text-secondary);
  font-size: var(--font-size-body); }
.sm-firstrun .sm-stages { margin: 0; gap: var(--space-xs); font-size: var(--font-size-body); }
.sm-firstrun .acts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs) var(--space-md);
  margin-top: var(--space-md); font-family: var(--font-body); font-size: var(--font-size-body); }

/* A stage that stopped (#929): the error wash on the mark, and "Didn't finish" in its time's
   place, so the state is words as well as color. Same geometry as every other state. */
.sm-stage--failed { color: var(--color-text-primary); }
.sm-stage--failed .mark { background: var(--color-feedback-error-bg); border-color: var(--color-feedback-error); }
.sm-stage--failed .when { color: var(--color-feedback-error); }

/* Publish's stages (#929; patterns/states.md, Act in flight): the timeline's marks under the
   sermon page's lede, without its bar or times, shown only while a publish that has run past half
   a second works, each ticked as the route reports the next. Nothing moves on a clock. */
.sm-publishing { max-width: 640px; }
.sm-publishing[hidden], .sm-publishing .slow[hidden] { display: none; }
.sm-publishing .sm-stages { margin: 0; }
/* No times to align in a column: what a stage says of itself follows its words. */
.sm-publishing .sm-stage { grid-template-columns: 16px auto minmax(0, 1fr); }
.sm-publishing .sm-stage .when { text-align: left; }
@media (max-width: 640px) {
  .sm-publishing .sm-stage { grid-template-columns: 16px minmax(0, 1fr); }
}
.sm-publishing .slow { margin: var(--space-xs) 0 0; font-family: var(--font-body);
  font-size: var(--font-size-small); color: var(--color-text-secondary); }

/* The import's batch publish, counted as it goes (#929): the labeled bar under the page head,
   "3 of 12 published" with its real share. Hidden until a batch has run half a second. */
.sm-batchpub { max-width: 640px; }
.sm-batchpub[hidden] { display: none; }

/* ---------- skeleton (indeterminate loading) ---------- */
/* STATIC: nothing shimmers or pulses (motion.md: nothing loops). A visible "Loading…"
   line carries the state; the blocks only hold the layout steady. */
.sm-skeleton { display: flex; flex-direction: column; gap: var(--space-xs); }
.sm-skeleton i { display: block; height: var(--space-md); border-radius: var(--radius-chip);
  background: var(--color-surface-sunken); }
.sm-skeleton i.w60 { width: 60%; } .sm-skeleton i.w80 { width: 80%; } .sm-skeleton i.w40 { width: 40%; }
.sm-loadline { font-family: var(--font-body); font-size: var(--font-size-small);
  color: var(--color-text-subtle); margin-bottom: var(--space-xs); }

/* ---------- empty state ---------- */
/* Modesty flatters (Principle 4): a small archive must look dignified, not failed. */
.sm-empty { text-align: center; padding: var(--space-2xl) var(--space-lg);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); background: var(--color-surface-card);
  font-family: var(--font-body); }
.sm-empty .ico { color: var(--color-text-subtle); margin-bottom: var(--space-sm); }
.sm-empty .head { font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-title); color: var(--color-text-heading); margin: 0 0 var(--space-2xs); }
.sm-empty .sub { font-size: var(--font-size-body); color: var(--color-text-secondary);
  max-width: 44ch; margin: 0 auto var(--space-md); }

/* ---------- transcript (P1 · margin strips, chosen Phase 2; SSM-144/145) ----------
   Flowing paragraphs, one row per paragraph as the pipeline publishes them,
   with each sentence a plain span inside the <p>. The seek target is the WHOLE
   ROW: the margin strip beside each paragraph (a real button, full paragraph
   height, not the small time chip it carries) seeks to the paragraph, and the
   prose, which the island wires for mouse and touch, seeks to the sentence
   under the pointer, while the button stays the only tab stop.
   The prose keeps the text caret: this is the surface people select and copy
   from, and `pointer` here would deny it (components/transcript.md).
   GEOMETRY IS CONSTANT: every paragraph carries its wash padding and transparent
   bar at rest, so becoming current swaps colors only, zero text movement. */
/* 18px on a phone, 20px above compact (#959). WONK is pinned off: Fraunces's
   default h, m, n, s and & are its leaning forms, swapped for plain ones only
   below an optical size of about 21.7, so a transcript a reader enlarges past
   that would start leaning. WONK reshapes no outlines, so the pin changes
   nothing at 18 or 20px. */
.sm-transcript { font-family: var(--font-display); font-size: var(--font-size-reading);
  font-variation-settings: "WONK" 0;
  line-height: var(--line-height-reading); color: var(--color-text-primary); }
@media (min-width: 641px) { .sm-transcript { font-size: var(--font-size-reading-wide); } }
.sm-tp { display: grid; grid-template-columns: 76px minmax(0, var(--size-measure-read));
  gap: 0 var(--space-md); }
/* The strip is a full-height hit area but paints NOTHING itself, and at rest
   not even its time: a page to read, not a cue list. The chip appears where the
   time means something: on the current paragraph, on a deep-linked one, under
   a hovering pointer, and on a focused strip (SSM-145). Feedback is keyed to
   the ROW, not the strip: hovering anywhere that seeks (strip or prose) reveals
   the chip and washes the paragraph, so the surface never promises more than it
   answers. */
.sm-tp .seek { align-self: stretch; justify-self: stretch; margin: 0;
  display: flex; justify-content: flex-end; align-items: flex-start;
  padding: calc(var(--space-sm) + 3px) var(--space-2xs) var(--space-sm);
  background: none; border: 0; cursor: pointer; }
.sm-tp .seek .t { display: inline-flex; align-items: center; min-height: var(--size-target-min);
  padding: 0 var(--space-xs);
  font-family: var(--font-body); font-size: var(--font-size-caption);
  font-weight: var(--font-weight-semibold); font-variant-numeric: tabular-nums;
  color: var(--color-text-brand);
  border: var(--border-width-hairline) solid transparent; border-radius: var(--radius-chip);
  opacity: 0;
  transition: opacity var(--duration-fast), border-color var(--duration-fast), background var(--duration-fast); }
.sm-tp--current .seek .t, .sm-tp:target .seek .t, .sm-tp:has(:target) .seek .t,
.sm-tp .seek:focus-visible .t { opacity: 1; }
/* Hover reveals the time only where a pointer can hover: on touch, :hover
   would stick to the last tap and leave a stray chip behind. */
@media (hover: hover) {
  .sm-tp:hover .seek .t { opacity: 1; border-color: var(--color-state-selected-border); }
}
.sm-tp:hover:not(.sm-tp--current) p { background: var(--color-action-hover-wash); }
/* The focus ring belongs on the chip, not around the whole tall strip. */
.sm-tp .seek:focus-visible { outline: none; }
.sm-tp .seek:focus-visible .t { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-tp p { margin: 0 0 var(--space-2xs);
  padding: var(--space-sm) var(--space-md);
  border-left: var(--border-width-marker) solid transparent;
  border-radius: 0 var(--radius-control) var(--radius-control) 0;
  transition: background var(--duration-fast), border-color var(--duration-fast); }
/* Current: player-driven (.sm-tp--current), or deep-linked to the paragraph
   (:target) or to a sentence inside it (:has(:target)). */
.sm-tp--current p, .sm-tp:target p, .sm-tp:has(:target) p { background: var(--color-state-selected-bg);
  border-left-color: var(--color-state-selected-border); }
.sm-tp--current .seek .t, .sm-tp:target .seek .t, .sm-tp:has(:target) .seek .t {
  background: var(--color-state-selected-bg); border-color: var(--color-state-selected-border); }
/* The sentence being spoken: a band inside the paragraph's wash, the read-along
   register of Kindle and Immersive Reader. An inline background with no
   padding, so the text never moves; the system's stronger alpha wash, so it
   reads on the wash in both themes (foundations/color.md). Sentence, never
   word: word-by-word marking shows no comprehension gain for fluent adult
   readers, and some find it agitating (SSM-145). */
.sm-tp .s { border-radius: var(--radius-control);
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  transition: background var(--duration-fast); }
.sm-tp .s.is-current, .sm-tp .s:target { background: var(--color-action-pressed-wash); }
/* Compact: there is no margin column at all (a reader's edition, not a study
   edition; SSM-78). The strips stay in the document for keyboard and
   screen-reader users, visually hidden and shown again on focus; the current
   paragraph (or a deep-linked one) shows its time chip as a tab on the top edge
   of its wash. The paragraph is the tap target (the island seeks from it).
   The chip stays OUT of the flow: in it, becoming current pushed the paragraph
   down 32px under the finger that had just tapped it (#526). Centered on the
   edge, it sits in the padding between two paragraphs (the wash's 12px above
   the text, and 20px of margin and padding under the paragraph before) and
   touches no line of text. */
@media (max-width: 640px) {
  .sm-tp { display: block; position: relative; }
  .sm-tp .seek { position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; padding: 0; }
  .sm-tp--current .seek, .sm-tp:target .seek, .sm-tp:has(:target) .seek, .sm-tp .seek:focus-visible {
    top: 0; left: calc(var(--space-md) + var(--border-width-marker)); transform: translateY(-50%);
    z-index: 1; width: auto; height: auto; overflow: visible; clip-path: none; display: inline-flex; }
  .sm-tp .seek .t { border-color: var(--color-border-control); }
  /* Opaque, so the wash's top edge doesn't run through the chip: the wash laid
     over the page, which is what the wash itself looks like. The dark wash is
     translucent, and a translucent tab would show the edge through it. */
  .sm-tp--current .seek .t, .sm-tp:target .seek .t, .sm-tp:has(:target) .seek .t {
    background: linear-gradient(var(--color-state-selected-bg), var(--color-state-selected-bg)), var(--color-surface-page); }
}
/* Hanging margin (sm-transcript--hang: the library sermon page, #532). The
   prose takes the reading column's full width and the margin strip hangs
   outside it, in the page's own margin, so the transcript shares its left edge
   with the title above it and a centered column centers its text. The offsets
   are the row's own geometry: the strip, the gap, the bar and the wash's
   padding on the left; the wash's padding on the right. So the wash, bar and
   all, bleeds past the text into the margin on both sides, and the text
   measure is exactly the column's. Screen only: paper has no strip to hang. */
@media screen and (min-width: 1024px) {
  .sm-transcript--hang {
    margin-left: calc(-1 * (76px + var(--space-md) + var(--border-width-marker) + var(--space-md)));
    margin-right: calc(-1 * var(--space-md)); }
}
/* Below breakpoint.regular a centered 66ch column leaves no margin to hang
   into, and a strip INSIDE the column is exactly what pushed the prose off
   center. So a hanging transcript takes the compact form there: a rule for rule
   copy of the block above, which must stay in step with it. */
@media screen and (min-width: 641px) and (max-width: 1023px) {
  .sm-transcript--hang .sm-tp { display: block; position: relative; }
  .sm-transcript--hang .sm-tp .seek { position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; padding: 0; }
  .sm-transcript--hang .sm-tp--current .seek, .sm-transcript--hang .sm-tp:target .seek,
  .sm-transcript--hang .sm-tp:has(:target) .seek, .sm-transcript--hang .sm-tp .seek:focus-visible {
    top: 0; left: calc(var(--space-md) + var(--border-width-marker)); transform: translateY(-50%);
    z-index: 1; width: auto; height: auto; overflow: visible; clip-path: none; display: inline-flex; }
  .sm-transcript--hang .sm-tp .seek .t { border-color: var(--color-border-control); }
  .sm-transcript--hang .sm-tp--current .seek .t, .sm-transcript--hang .sm-tp:target .seek .t,
  .sm-transcript--hang .sm-tp:has(:target) .seek .t {
    background: linear-gradient(var(--color-state-selected-bg), var(--color-state-selected-bg)), var(--color-surface-page); }
}

/* Dense (sm-transcript--dense: the dashboard's sermon review page, #1262). A
   transcript read to check, not to keep: the body face at body size and leading
   instead of the library's book-size Fraunces, no margin column at any width,
   and no time chip on the current or a deep-linked paragraph (the wash and bar
   already say which it is). The strips stay in the document, visually hidden,
   and show again ONLY on keyboard focus, as the compact tab on the wash's top
   edge, so a keyboard user can always see what has focus. The paragraph keeps
   its wash padding, so that tab sits in the gap between paragraphs as compact's
   does. The type rule doubles the class so it beats the min-width reading-wide
   rule whatever the source order. */
.sm-transcript.sm-transcript--dense { font-family: var(--font-body);
  font-size: var(--font-size-body); font-variation-settings: normal;
  line-height: var(--line-height-body); }
.sm-transcript--dense .sm-tp { display: block; position: relative; }
.sm-transcript--dense .sm-tp .seek { position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; padding: 0; }
.sm-transcript--dense .sm-tp .seek:focus-visible {
  top: 0; left: calc(var(--space-md) + var(--border-width-hairline)); transform: translateY(-50%);
  z-index: 1; width: auto; height: auto; overflow: visible; clip-path: none; display: inline-flex; }
.sm-transcript--dense .sm-tp .seek .t { border-color: var(--color-border-control); }
.sm-transcript--dense .sm-tp .seek:focus-visible .t {
  background: linear-gradient(var(--color-state-selected-bg), var(--color-state-selected-bg)), var(--color-surface-page); }
/* The wash is a box inside a box here, and the library's shape (a left marker
   bar, only the right corners rounded) reads wrong in one (#1268). So the dense
   paragraph is rounded on every corner with no bar: a transparent hairline all
   round at rest, which the current paragraph turns to the selected border, so
   its wash is outlined and the geometry stays constant (zero text movement).
   Hover is the hover wash only. The chip's offset above is the hairline, not the
   bar, so it still lines up with the text. Nothing here caps the width at the
   reading measure: the grid is already a block, so the paragraph takes the full
   width of its container. */
.sm-transcript--dense .sm-tp p { border: var(--border-width-hairline) solid transparent;
  border-radius: var(--radius-control); }
.sm-transcript--dense .sm-tp--current p, .sm-transcript--dense .sm-tp:target p,
.sm-transcript--dense .sm-tp:has(:target) p { border-color: var(--color-state-selected-border); }
/* No margin column, so the correction strip takes the column's full width, with
   the paragraph's shape: every corner rounded, no bar, a selected-border
   hairline all round (#1268). Doubled class: it must beat
   `.sm-transcript > .sm-cstrip` and `.sm-cstrip` below it. */
.sm-transcript.sm-transcript--dense > .sm-cstrip { margin-left: 0; max-width: none;
  border: var(--border-width-hairline) solid var(--color-state-selected-border);
  border-radius: var(--radius-control); }

/* ---------- correction marks (components/transcript.md, Correction marks; #1235) ----------
   On the dashboard's review page a word the engine corrected is a real control
   inside the prose: a button that reads as the text around it, told apart by a
   dotted underline and nothing else. No background and no color: the band and
   the wash stay the playback's own marks, and a correction is a recorded
   decision, not a confidence cue. The underline is text.secondary, which meets
   3:1 on the page and on every wash the paragraph can take (design/contrast.mjs,
   "Correction mark underline"); an open mark goes solid, so the state is not
   carried by a shade. Applied, kept-as-heard and changed words all look the
   same: the strip and the count line say which, in words. */
.sm-cmark { display: inline; margin: 0; padding: 0; border: 0; background: none;
  font: inherit; font-variation-settings: inherit; letter-spacing: inherit; text-align: inherit;
  color: inherit; line-height: inherit; cursor: pointer; border-radius: var(--radius-control);
  text-decoration-line: underline; text-decoration-style: dotted;
  text-decoration-color: var(--color-text-secondary);
  text-decoration-thickness: var(--border-width-marker); text-underline-offset: .2em;
  -webkit-box-decoration-break: clone; box-decoration-break: clone; }
.sm-cmark:hover, .sm-cmark[aria-expanded="true"] { text-decoration-style: solid; }
.sm-cmark:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
/* The strip a mark opens, in place under its paragraph: one at a time, on
   surface.card with the marker bar the current paragraph wears, so it reads as
   an attachment to the words above it and not as a second page. It sits in the
   prose's column (the strip column's width and the gap to its left), at the
   prose's measure, and takes the whole width where the transcript has no margin
   column (compact, and the hanging transcript below breakpoint.regular). */
.sm-cstrip { max-width: var(--size-measure-read); margin: 0 0 var(--space-sm);
  padding: var(--space-md);
  background: var(--color-surface-card); color: var(--color-text-primary);
  border-left: var(--border-width-marker) solid var(--color-state-selected-border);
  border-radius: 0 var(--radius-control) var(--radius-control) 0;
  font-family: var(--font-body); font-size: var(--font-size-body); line-height: var(--line-height-body); }
.sm-transcript > .sm-cstrip { margin-left: calc(76px + var(--space-md)); }
.sm-cstrip .pos { margin: 0 0 var(--space-xs); font-size: var(--font-size-caption);
  font-weight: var(--font-weight-semibold); font-variant-caps: small-caps;
  letter-spacing: var(--letter-spacing-caps); color: var(--color-text-subtle); }
/* The strip is two lines: the position and the way through the corrections
   (previous, next, close) above, then the correction itself, the same readings
   the review page's list carries, with its acts. The readings, the acts and
   the edit field are the correction component's (below), so the list and the
   strip can never look like two ways of reading one choice. */
.sm-cstrip .top { display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--space-sm); margin: 0 0 var(--space-sm); }
.sm-cstrip .top .pos { margin: 0; }
.sm-cstrip .nav { display: flex; flex-wrap: wrap; gap: var(--space-xs); }
.sm-cstrip .row { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-sm) var(--space-md); }
.sm-cstrip [data-cstrip-last] { margin: var(--space-sm) 0 0; font-size: var(--font-size-small);
  color: var(--color-text-secondary); }
@media (max-width: 640px) { .sm-transcript > .sm-cstrip { margin-left: 0; } }
@media screen and (min-width: 641px) and (max-width: 1023px) {
  .sm-transcript--hang > .sm-cstrip { margin-left: 0; }
}

/* ---------- correction (components/correction.md; #1266) ---------- */
/* The review page's list of corrections, each one drawn as the choice it is:
   every reading it could take, side by side, as the words themselves. One layout
   at every width, two lines a correction (#1285): the context, the sermon's own
   words, on the first line across the row; the readings under it at the start and
   the acts at the end. A grid of three columns squeezed the context to a third of
   the work column and wrapped it between two boxed controls. One hairline between
   rows, none above the first or after the last. Never a table (#1266). */
.sm-clist { list-style: none; margin: 0; padding: 0; }
.sm-crow { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--space-md);
  row-gap: var(--space-sm); align-items: center; padding-block: var(--space-md); }
.sm-crow + .sm-crow { border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
/* The first sentence sits close under the list's head, which already sets it
   apart; padding there only opened a gap (#1285). */
.sm-crow:first-child { padding-top: 0; }
.sm-crow > .ctx { grid-column: 1 / -1; grid-row: 1; }
.sm-crow > .sm-readings, .sm-crow > .sm-cedit { grid-column: 1; grid-row: 2; justify-self: start; max-width: 100%; }
.sm-crow > .acts { grid-column: 2; grid-row: 2; justify-self: end; }
/* The context: the transcript a few words either side, as it reads now. The
   current reading is semibold and in the word color; color and wash stay the
   playback's. With a sentence on file it is a link to it: plain at rest, the
   word color and a control-edge underline on hover. */
.sm-crow .ctx { color: var(--color-text-primary); text-decoration: none;
  font-family: var(--font-display);
  font-size: var(--font-size-body); line-height: var(--line-height-body); }
/* The reading in play is only heavier, never painted: color and wash belong to
   playback (correction.md, #1285). */
.sm-crow .ctx b { color: var(--color-text-primary); font-weight: var(--font-weight-semibold); }
a.ctx:hover { color: var(--color-text-primary); text-decoration: underline;
  text-decoration-color: var(--color-border-control); text-underline-offset: .2em; }
/* Hear it and Edit (quiet small buttons), and Save and Cancel inside the edit
   field. The strip's acts are the same row of the same buttons. */
.sm-crow > .acts, .sm-cstrip .acts, .sm-cedit .acts { display: flex; align-items: center;
  gap: var(--space-xs); }

/* The readings are the segmented choice's face (.sm-seg, above) on buttons that
   act at once. Each option holds two lines: the reading's words (.w) over a
   caption (.cap) saying where the reading came from. The static variant is the
   same track with plain spans, for a reader who cannot change a correction. */
.sm-readings { display: inline-flex; gap: var(--space-3xs); padding: var(--space-3xs);
  background: var(--color-surface-sunken);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-control); }
.sm-readings > button, .sm-readings > .opt { display: flex; flex-direction: column;
  align-items: flex-start; justify-content: center; margin: 0;
  min-height: calc(var(--size-control-md) - 2 * var(--space-3xs) - 2px);
  padding: var(--space-3xs) var(--space-sm); border-radius: var(--radius-chip);
  border: var(--border-width-hairline) solid transparent; background: none;
  font: inherit; text-align: left; color: var(--color-text-secondary);
  transition: background var(--duration-fast), color var(--duration-fast),
    border-color var(--duration-fast), box-shadow var(--duration-fast); }
.sm-readings > button { cursor: pointer; }
.sm-readings .w { font-family: var(--font-body); font-size: var(--font-size-body);
  font-weight: var(--font-weight-medium); /* constant: a choice may never change geometry */
  line-height: 1.25; white-space: nowrap; }
.sm-readings .cap { font-size: var(--font-size-caption); font-weight: var(--font-weight-semibold);
  font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps);
  color: var(--color-text-subtle); line-height: 1.2; white-space: nowrap; }
.sm-readings > button:hover:not([aria-current="true"]) { background: var(--color-action-hover-wash);
  box-shadow: var(--shadow-raised); }
.sm-readings > [aria-current="true"] { background: var(--color-surface-card);
  color: var(--color-text-primary); border-color: var(--color-state-selected-border);
  box-shadow: var(--shadow-raised); }
/* The current option, and every static one, is not pressable. */
.sm-readings > button[aria-current="true"], .sm-readings--static > .opt { cursor: default; }
.sm-readings > button:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }

/* Edit puts this field in the readings' place: a small well about 14 characters
   wide, then Save and Cancel. */
.sm-cedit { display: flex; align-items: center; gap: var(--space-xs); margin: 0; }
.sm-cedit .sm-well { width: 14ch; min-height: var(--size-control-sm); padding-block: 0; }
/* A hidden part of a row stays hidden whatever display its own rule sets: the
   edit field while it is shut, Edit while the field is open. */
.sm-crow [hidden], .sm-cstrip [hidden] { display: none; }

/* Under breakpoint.compact the row is one column: the context, the readings,
   then the acts under them at the start (#1285). A reading's words may wrap
   inside its option; the track never runs past the screen's edge. */
@media (max-width: 640px) {
  .sm-crow { grid-template-columns: minmax(0, 1fr); }
  .sm-crow > .acts { grid-column: 1; grid-row: 3; justify-self: start; }
  .sm-readings { max-width: 100%; }
  .sm-readings .w { white-space: normal; }
}
@media (prefers-reduced-motion: reduce) {
  .sm-readings > button, .sm-readings > .opt { transition: none; }
}

/* ---------- audio player ---------- */
.sm-player { display: flex; align-items: center; gap: var(--space-sm);
  font-family: var(--font-body); }
.sm-player .play { flex: none; width: var(--size-control-md); height: var(--size-control-md);
  border-radius: var(--radius-pill); cursor: pointer;
  border: var(--border-width-hairline) solid var(--color-action-primary-hover);
  background: var(--color-action-primary); color: var(--color-action-primary-text);
  display: grid; place-items: center;
  transition: background var(--duration-fast), transform var(--duration-fast); }
.sm-player .play:hover { background: var(--color-action-primary-hover); }
.sm-player .play:active { transform: translateY(1px); }
.sm-player .time { flex: none; font-size: var(--font-size-small); color: var(--color-text-secondary);
  font-variant-numeric: tabular-nums; }
/* ---------- video (click-to-load facade) ---------- */
/* The frame holds 16:9 from first paint, so swapping the facade for the
   provider's iframe reflows nothing. Nothing is requested from the provider
   until the visitor asks, and no poster is pulled from one either: the point
   is that reading a transcript costs no third-party request. */
.sm-video { aspect-ratio: 16 / 9; width: 100%; overflow: hidden;
  background: var(--color-surface-sunken);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); }
.sm-video > iframe { display: block; width: 100%; height: 100%; border: 0; }
/* A real <a href>, so the page still reaches the video with no JS; the island
   upgrades it in place. A link carries `pointer` natively; stated anyway,
   because this one fills a frame and reads as a player control. */
.sm-video .facade { display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: var(--space-sm); width: 100%; height: 100%;
  cursor: pointer; text-decoration: none; color: var(--color-text-secondary); }
.sm-video .glyph { display: grid; place-items: center;
  width: var(--size-control-lg); height: var(--size-control-lg);
  border-radius: var(--radius-pill);
  border: var(--border-width-hairline) solid var(--color-action-primary-hover);
  background: var(--color-action-primary); color: var(--color-action-primary-text);
  transition: background var(--duration-fast); }
.sm-video .facade:hover .glyph { background: var(--color-action-primary-hover); }
.sm-video .label { font-family: var(--font-body); font-size: var(--font-size-body);
  font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps); }
.sm-video .facade:hover .label { color: var(--color-text-primary); }

/* The scrub is a REAL range input; components.js paints the played portion into
   --sm-scrub-fill. A range inherits `cursor: default`; it is dragged, so: pointer. */
input.sm-scrub { flex: 1; min-width: 0; appearance: none; -webkit-appearance: none;
  height: var(--size-target-min); background: transparent; cursor: pointer; margin: 0; }
input.sm-scrub:disabled { cursor: default; }
input.sm-scrub::-webkit-slider-runnable-track { height: 6px; border-radius: var(--radius-chip);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  background: linear-gradient(to right,
    var(--color-action-primary) 0 var(--sm-scrub-fill, 0%),
    var(--color-surface-sunken) var(--sm-scrub-fill, 0%) 100%); }
input.sm-scrub::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px;
  border-radius: var(--radius-pill); background: var(--color-action-primary);
  border: 2px solid var(--color-surface-card); margin-top: -6px; box-shadow: var(--shadow-raised); }
input.sm-scrub::-moz-range-track { height: 6px; border-radius: var(--radius-chip);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  background: var(--color-surface-sunken); }
input.sm-scrub::-moz-range-progress { height: 6px; border-radius: var(--radius-chip);
  background: var(--color-action-primary); }
input.sm-scrub::-moz-range-thumb { width: 16px; height: 16px; border-radius: var(--radius-pill);
  background: var(--color-action-primary); border: 2px solid var(--color-surface-card); }

/* ---------- wizard steps ---------- */
.sm-steps { display: flex; align-items: center; gap: var(--space-sm);
  font-family: var(--font-body); font-size: var(--font-size-small);
  list-style: none; margin: 0; padding: 0; /* an <ol> (wizard.md, Accessibility) */ }
.sm-steps .bar { flex: 1; height: 1px; background: var(--color-border-subtle); min-width: var(--space-md); }
.sm-step { display: inline-flex; align-items: center; gap: var(--space-xs);
  color: var(--color-text-subtle); white-space: nowrap;
  font-weight: var(--font-weight-medium); /* constant: state never changes geometry */ }
.sm-step .n { width: 28px; height: 28px; border-radius: var(--radius-pill); flex: none;
  display: grid; place-items: center; font-weight: var(--font-weight-semibold);
  background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-control);
  color: var(--color-text-secondary); }
.sm-step--done { color: var(--color-text-secondary); }
.sm-step--done .n { background: var(--color-feedback-success-bg);
  border-color: var(--color-feedback-success); color: var(--color-feedback-success); }
.sm-step--current { color: var(--color-text-primary); }
.sm-step--current .n { background: var(--color-action-primary);
  border-color: var(--color-action-primary-hover); color: var(--color-action-primary-text); }
/* Under compact the strip fits the screen (wizard.md, Behavior; #969): the
   other steps keep their circles and rules, their labels leave the screen
   (the .vh treatment, so a screen reader still hears every step), and the
   current label may wrap. Unwrapped, "Choose an address · Add one record ·
   Live" was 446px wide and pushed the Domain page sideways on a 375px phone. */
@media (max-width: 640px) {
  .sm-step { white-space: normal; }
  .sm-step:not(.sm-step--current) > .lbl { position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%);
    white-space: nowrap; border: 0; }
}

/* ---------- stat tile ---------- */
/* dataviz contract: label · value (sans semibold, PROPORTIONAL figures: tabular
   digits look loose at display sizes) · optional delta (direction × goodness,
   arrow + words, never color alone). No serif hero numbers. */
.sm-stat { background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); padding: var(--space-md) var(--space-lg);
  font-family: var(--font-body); min-width: 180px; }
.sm-stat .lbl { font-size: var(--font-size-small); color: var(--color-text-secondary);
  margin-bottom: var(--space-3xs); }
.sm-stat .val { font-size: var(--font-size-display); font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary); line-height: var(--line-height-tight); }
.sm-stat .delta { display: inline-flex; align-items: center; gap: var(--space-3xs);
  margin-top: var(--space-2xs); font-size: var(--font-size-small);
  font-weight: var(--font-weight-medium); }
.sm-stat .delta.up-good { color: var(--color-feedback-success); }
.sm-stat .delta.down-bad { color: var(--color-feedback-error); }
.sm-stat .delta.flat { color: var(--color-text-subtle); }

/* ---------- allowance strip (components/allowance.md, #1031) ---------- */
/* How many sermons are left this month: a sentence, one mark per sermon in the
   allowance, and a quiet line. The sentence carries the figure; the marks are
   its picture. A used mark takes the church's ink (the tenant slot), a mark
   still to use is an empty well, and a sermon past the allowance follows in
   secondary ink after a gap. No percentage, no warning color. */
.sm-allowance { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-xs) var(--space-md); font-family: var(--font-body); }
.sm-allowance p { margin: 0; }
.sm-allowance .t { color: var(--color-text-secondary); }
.sm-allowance .t b { color: var(--color-text-primary); font-weight: var(--font-weight-semibold); }
.sm-allowance .s { font-size: var(--font-size-small); color: var(--color-text-subtle); }
.sm-allowance .marks { display: flex; gap: 3px; }
.sm-allowance .marks i { flex: none; width: 18px; height: 12px; border-radius: var(--space-3xs);
  background: var(--color-surface-sunken);
  box-shadow: inset 0 0 0 var(--border-width-hairline) var(--color-border-control); }
.sm-allowance .marks i.used { background: var(--color-text-brand); box-shadow: none; }
.sm-allowance .marks i.over { background: var(--color-text-secondary); box-shadow: none; }
.sm-allowance .marks i.used + i.over { margin-left: var(--space-xs); }
/* On a phone the marks take a line of their own and share it: up to 18px
   each, narrower as an allowance grows, so 40 still fit 358px. */
@media (max-width: 640px) {
  .sm-allowance .marks { flex: 1 1 100%; }
  .sm-allowance .marks i { flex: 1 1 0; width: auto; max-width: 18px; min-width: var(--space-3xs); }
}
/* Month by month (#1032): the strip's marks, a row per allowance period, newest
   first, the dates before and the count after. The marks are smaller here, a
   record and not the headline. On a phone the dates and the count share a
   line and the marks take the one under it. */
.sm-allowance-months { list-style: none; margin: 0; padding: 0; display: grid;
  gap: var(--space-xs); font-family: var(--font-body); font-size: var(--font-size-small);
  color: var(--color-text-secondary); }
.sm-allowance-months li { display: grid; grid-template-columns: 9.5rem auto 1fr;
  align-items: center; gap: var(--space-md); }
.sm-allowance-months .n { font-variant-numeric: tabular-nums; }
.sm-allowance-months .marks { display: flex; gap: 3px; }
.sm-allowance-months .marks i { flex: none; width: 14px; height: 10px; border-radius: var(--space-3xs);
  background: var(--color-surface-sunken);
  box-shadow: inset 0 0 0 var(--border-width-hairline) var(--color-border-control); }
.sm-allowance-months .marks i.used { background: var(--color-text-brand); box-shadow: none; }
.sm-allowance-months .marks i.over { background: var(--color-text-secondary); box-shadow: none; }
.sm-allowance-months .marks i.used + i.over { margin-left: var(--space-xs); }
@media (max-width: 640px) {
  .sm-allowance-months li { grid-template-columns: 1fr auto; gap: var(--space-2xs) var(--space-md); }
  .sm-allowance-months .marks { grid-column: 1 / -1; grid-row: 2; }
  .sm-allowance-months .marks i { flex: 1 1 0; width: auto; max-width: 14px; min-width: var(--space-3xs); }
}

/* ---------- charts (components/daychart.md, #557; patterns/operator.md, #820) ---------- */
/* Two charts share one grammar: a column of round figures up the left, the
   plot, and a row of dates under it. The day chart is the tenant's: bars a day,
   calm, its own card. The line chart is the operator console's: a line over a
   full axis, denser, inside the console's card. Both are complete without
   script; the chart island adds the readout (`sm-readout`) on hover and focus.
   Text is always ink, never the series color. Nothing moves: no transition. */
.sm-daychart, .sm-linechart { margin: 0; min-width: 0; display: flex; flex-direction: column;
  font-family: var(--font-body); }
.sm-daychart { background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); padding: var(--space-md) var(--space-md) var(--space-sm); }
.sm-daychart figcaption { display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: var(--space-2xs) var(--space-md); }
.sm-daychart figcaption .sm-microlabel { margin: 0; }
.sm-daychart .key { display: inline-flex; align-items: center; gap: var(--space-2xs);
  font-size: var(--font-size-caption); color: var(--color-text-subtle); }
.sm-daychart .key .sw { width: 10px; height: 10px; border-radius: 2px;
  background: var(--color-text-brand); }
/* The week chart (components/weekchart.md, #1033): the day chart's card and
   bar ink, turned on its side for seven named days. Each row is the day, its
   bar from a shared left edge, and its share, printed, so nothing needs a
   readout. The busiest day is not singled out: the sentence names it. */
.sm-weekchart { margin: 0; background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); padding: var(--space-md);
  font-family: var(--font-body); }
.sm-weekchart figcaption .sm-microlabel { margin: 0; }
.sm-weekchart .lead { margin: var(--space-xs) 0 var(--space-sm); color: var(--color-text-primary); }
.sm-weekchart .days { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2xs);
  font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-weekchart .days li { position: relative; display: grid; align-items: center;
  grid-template-columns: 7rem minmax(0, 24rem) 4.5rem; gap: var(--space-md); }
.sm-weekchart .bar { height: 12px; width: max(var(--v, 0%), 2px);
  border-radius: 0 var(--radius-chip) var(--radius-chip) 0;
  background: var(--color-border-control); }
/* A day with none keeps the shared edge as a hairline, never a gap. */
.sm-weekchart .bar.is-zero { width: var(--border-width-hairline); border-radius: 0; }
.sm-weekchart .p { font-variant-numeric: tabular-nums; }
@media (max-width: 640px) {
  .sm-weekchart { padding: var(--space-md) var(--space-sm); }
  .sm-weekchart .days li { grid-template-columns: 5.5rem minmax(0, 1fr) 4rem; gap: var(--space-xs); }
}
.sm-daychart .plot, .sm-linechart .plot { display: grid; grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: minmax(140px, 1fr) auto; column-gap: var(--space-xs);
  flex: 1 1 auto; padding-top: var(--space-lg); }
.sm-daychart .y, .sm-linechart .y { position: relative; grid-row: 1; min-width: calc(var(--w, 2) * 1ch);
  font-size: var(--font-size-caption); color: var(--color-text-subtle);
  font-variant-numeric: lining-nums tabular-nums; }
.sm-daychart .y span, .sm-linechart .y span { position: absolute; right: 0; top: var(--at);
  translate: 0 -50%; line-height: 1; white-space: nowrap; }
/* The plot: the box the marks, the readout and the keyboard's one stop share.
   The island makes it focusable, so it wears the system's focus ring. */
.sm-plot { position: relative; grid-row: 1; grid-column: 2; min-width: 0; display: grid; }

/* The day chart's bars. A bar is its whole column, so the column is what the
   pointer reaches and what the hover wash lights, even on a day of nothing;
   the ink rises from the baseline in ::before, never wider than 24px, so a
   wide chart gets air between its bars rather than slabs. A day with any count
   keeps 2px of ink, so one view under a busy Sunday never reads as none. */
.sm-daychart .bars { position: relative; list-style: none;
  margin: 0; padding: 0; display: grid; grid-template-columns: repeat(var(--n, 28), minmax(0, 1fr));
  gap: 3px;
  border-bottom: var(--border-width-hairline) solid var(--color-border-control);
  background:
    linear-gradient(var(--color-border-subtle), var(--color-border-subtle)) 0 0 / 100% 1px no-repeat,
    linear-gradient(var(--color-border-subtle), var(--color-border-subtle)) 0 50% / 100% 1px no-repeat; }
.sm-daychart .bar { position: relative; border-radius: 2px 2px 0 0; }
.sm-daychart .bar::before { content: ""; position: absolute; left: 50%; bottom: 0;
  width: min(100%, 24px); translate: -50% 0;
  height: var(--v, 0%); min-height: 2px; border-radius: 2px 2px 0 0;
  background: var(--color-border-control); }
.sm-daychart .bar.is-zero::before { min-height: 0; }
/* With no script the column under the pointer lights; with the chart island
   (`is-wired`) the day the readout shows does, which a touch or a pointer
   between two columns could otherwise light alongside the hovered one. */
.sm-daychart:where(:not(.is-wired)) .bar:hover, .sm-daychart .bar.is-active {
  background: var(--color-action-hover-wash); }
.sm-daychart:where(:not(.is-wired)) .bar:hover::before, .sm-daychart .bar.is-active::before {
  background: var(--color-text-secondary); }
.sm-daychart .bar.is-mark::before { background: var(--color-text-brand); }
.sm-daychart .bar.is-mark::after { content: ""; position: absolute; top: 100%; left: 50%;
  width: 4px; height: 4px; margin: var(--space-2xs) 0 0 -2px; border-radius: 50%;
  background: var(--color-text-brand); }
.sm-daychart .bar .v { position: absolute; bottom: max(var(--v, 0%), 2px);
  right: max(0px, calc((100% - 24px) / 2));
  padding-bottom: var(--space-3xs);
  font-size: var(--font-size-caption); color: var(--color-text-secondary);
  font-variant-numeric: lining-nums tabular-nums; white-space: nowrap; }
.sm-daychart .x { grid-row: 2; grid-column: 2; display: grid;
  grid-template-columns: repeat(var(--n, 28), minmax(0, 1fr)); gap: 3px;
  margin-top: var(--space-md); font-size: var(--font-size-caption); color: var(--color-text-subtle);
  font-variant-numeric: lining-nums tabular-nums; }
/* A label names the day under its right edge, so the last one ends at the
   chart's edge instead of hanging past it on a narrow screen. */
.sm-daychart .x span { grid-column: var(--c); grid-row: 1; justify-self: end; white-space: nowrap; }
@media (max-width: 640px) {
  .sm-daychart { padding: var(--space-sm); }
  .sm-daychart .bars, .sm-daychart .x { gap: 2px; }
  /* A phone's bars are narrower than the figure above the last one, which
     then sits on its neighbor. The axis still reads, and every bar keeps its
     figure in words. */
  .sm-daychart .bar .v { display: none; }
}

/* The console's line chart (patterns/operator.md, Analytics charts). The line
   is SVG stretched to the plot with strokes that keep their width; every word
   on it is HTML, so the axis reads at caption size at any width. A hairline at
   each round figure, the baseline a step stronger, the series in the accent
   with its peak dotted and labeled. Each day is a full-height band, the
   pointer's target and the no-script tooltip. */
.sm-linechart .line { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.sm-linechart .gl { stroke: var(--color-border-subtle); stroke-width: 1;
  vector-effect: non-scaling-stroke; shape-rendering: crispEdges; }
.sm-linechart .gl.base { stroke: var(--color-border-control); }
.sm-linechart .series { fill: none; stroke: var(--color-text-brand); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.sm-linechart .pt { position: absolute; left: var(--x); top: var(--y); width: 8px; height: 8px;
  margin: -4px 0 0 -4px; border-radius: 50%; background: var(--color-text-brand);
  box-shadow: 0 0 0 2px var(--color-surface-card); pointer-events: none; }
.sm-linechart .pt[hidden], .sm-linechart .cross[hidden] { display: none; }
.sm-linechart .peak { position: absolute; left: var(--x); top: var(--y); line-height: 1;
  translate: -50% calc(-100% - var(--space-xs)); pointer-events: none; white-space: nowrap;
  font-size: var(--font-size-caption); font-weight: var(--font-weight-medium);
  color: var(--color-text-secondary); font-variant-numeric: lining-nums tabular-nums; }
.sm-linechart .peak.is-start { translate: calc(-1 * var(--space-2xs)) calc(-100% - var(--space-xs)); }
.sm-linechart .peak.is-end { translate: calc(-100% + var(--space-2xs)) calc(-100% - var(--space-xs)); }
.sm-linechart .hit { position: absolute; top: 0; bottom: 0; left: var(--l); width: var(--w); }
.sm-linechart .cross { position: absolute; top: 0; bottom: 0; width: 0; pointer-events: none;
  border-left: var(--border-width-hairline) solid var(--color-border-control); }
.sm-linechart .x { grid-row: 2; grid-column: 2; position: relative; margin-top: var(--space-xs);
  min-height: calc(var(--font-size-caption) * var(--line-height-snug));
  font-size: var(--font-size-caption); line-height: var(--line-height-snug);
  color: var(--color-text-subtle); font-variant-numeric: lining-nums tabular-nums; }
.sm-linechart .x span { position: absolute; top: 0; left: var(--at); translate: -50% 0; white-space: nowrap; }
.sm-linechart .x span.is-start { translate: 0 0; }
.sm-linechart .x span.is-end { translate: -100% 0; }

/* The readout: one figure and its day, where the reader is looking. The value
   leads in ink and the day follows in secondary ink, on the card with the
   overlay shadow, the way a combobox's list floats. The island places it
   over the point and keeps it inside the plot; it never takes the pointer. */
.sm-readout { position: absolute; z-index: 2; left: 0; top: 0; pointer-events: none;
  display: flex; flex-direction: column; padding: var(--space-3xs) var(--space-xs);
  background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-control); box-shadow: var(--shadow-overlay);
  font-family: var(--font-body); font-size: var(--font-size-caption);
  line-height: var(--line-height-snug); white-space: nowrap; color: var(--color-text-secondary); }
.sm-readout[hidden] { display: none; }
.sm-readout b { font-size: var(--font-size-small); font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary); font-variant-numeric: lining-nums tabular-nums; }

/* Forced colors (Windows High Contrast): every background is repainted as the
   page's Canvas, and the marks are backgrounds, so the bars, the dots and the
   key would vanish. They opt out and take system colors instead: a bar in
   CanvasText, a marked day, its dot, the key and the console's series in
   Highlight (the dot still says it without color), the gridlines in GrayText,
   and the column a reader is on outlined. The selectors outrank the tenant's
   ink slot (ui/tenant-theme-css.ts), which would otherwise paint over them. */
@media (forced-colors: active) {
  .sm-daychart .bars .bar::before, .sm-daychart .bars .bar::after,
  .sm-daychart figcaption .key .sw, .sm-linechart .line, .sm-linechart .pt {
    forced-color-adjust: none; }
  .sm-daychart .bars .bar::before,
  .sm-daychart:where(:not(.is-wired)) .bars .bar:hover::before, .sm-daychart .bars .bar.is-active::before {
    background: CanvasText; }
  .sm-daychart .bars .bar.is-mark::before, .sm-daychart .bars .bar.is-mark::after,
  .sm-daychart figcaption .key .sw, .sm-linechart .pt { background: Highlight; }
  .sm-daychart:where(:not(.is-wired)) .bars .bar:hover, .sm-daychart .bars .bar.is-active {
    outline: var(--border-width-hairline) solid CanvasText; outline-offset: -1px; }
  .sm-linechart .gl { stroke: GrayText; }
  .sm-linechart .gl.base { stroke: CanvasText; }
  .sm-linechart .series { stroke: Highlight; }
}

/* ---------- page grammar (patterns/layout.md) ---------- */
/* Page head: title left, ONE thing right: the screen's primary act (dashboard) or a
   plain count (library). Baseline-aligned so the right slot's text sits on the title's
   line; wraps under compact. */
/* A page's opening (patterns/dashboard.md, Upload, #1040): the head, the
   sentence under it, and the allowance strip, as one group the page's own gap
   follows. Block flow, so a back link above the head keeps its own space. */
.sm-opening > p { margin: var(--space-md) 0 0; }
.sm-opening > .sm-allowance { margin-top: var(--space-lg); }
.sm-pagehead { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-sm) var(--space-md); flex-wrap: wrap; font-family: var(--font-body); }
.sm-pagehead h1, .sm-pagehead .h1 { font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-display); line-height: var(--line-height-tight);
  color: var(--color-text-heading); margin: 0; }
.sm-pagehead .count { color: var(--color-text-subtle); font-size: var(--font-size-small);
  font-variant-numeric: tabular-nums; }
/* Back link: names its destination ("← All sermons"), sits above the page title.
   Rest→hover step: hairline underline → currentColor (the system's link grammar).
   The gap under it belongs to the component, not to each page: it is a 24px row
   above a display-size title, and with nothing between them the two read as one
   squashed block. Every surface had been faking it with an inline margin on the
   heading, or not at all. */
a.sm-back { display: inline-flex; align-items: center; gap: var(--space-2xs);
  margin-bottom: var(--space-lg);
  min-height: var(--size-target-min); cursor: pointer;
  color: var(--color-text-action); font-family: var(--font-body);
  font-size: var(--font-size-small); font-weight: var(--font-weight-medium);
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--color-border-link);
  transition: text-decoration-color var(--duration-fast); }
a.sm-back:hover { text-decoration-color: currentColor; }
/* Colophon line: the page's quiet closing counts: a hairline, then plain figures. */
.sm-colophon { display: flex; gap: var(--space-lg); flex-wrap: wrap;
  border-top: var(--border-width-hairline) solid var(--color-border-subtle);
  padding-top: var(--space-md); font-family: var(--font-body);
  font-size: var(--font-size-small); color: var(--color-text-subtle);
  font-variant-numeric: tabular-nums; }

/* ---------- title page (patterns/auth.md) ---------- */
/* The front door, and the only screen in the product that is a title page:
   there is no content yet, so the page has to say what this is and be worth
   trusting with a password. It does that with type, air and apparatus, never a
   photograph or a gradient (Principle 2, and the stated anti-goals).

   Centered in the page rather than stacked at the top of it. There is no
   header band here (components/header.md: the door carries no chrome), so the
   only height above and below the composition is the main wrap's own padding,
   and that is the subtrahend. `100dvh` so a phone's collapsing address bar does
   not leave the composition sitting under the fold on first paint. */
.sm-titlepage { display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: var(--space-3xl) var(--space-2xl);
  min-height: calc(100dvh - var(--space-xl) - var(--space-3xl));
  padding: var(--space-2xl) var(--space-lg);
  font-family: var(--font-body); }
/* Neither block reaches for its edge of the page: the pair sits near the middle
   of a desk-width wrap, which is what makes it read as a composition rather
   than as two things pushed apart. */
.sm-plate { flex: 1 1 22rem; max-width: 30rem; }
.sm-authcard { flex: 0 1 27rem; width: 100%; }
/* Fraunces at the masthead step, opsz floating as the scale intends at display
   sizes (foundations/typography.md). It is a paragraph, not a heading: the
   page's h1 is inside the card, on the act the reader came for. */
.sm-wordmark { font-family: var(--font-display); font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-masthead); line-height: var(--line-height-tight);
  color: var(--color-text-primary); margin: 0; }
.sm-plate .deck { font-size: var(--font-size-reading); line-height: var(--line-height-snug);
  color: var(--color-text-secondary); max-width: 34ch; margin: var(--space-lg) 0 0; }
.sm-plate .sm-colophon { margin-top: var(--space-2xl); }
/* The card's page head sits on its own line above the form: the right-hand slot
   of a pagehead is a page's primary act, and this page's primary act is inside
   the form under it. */
.sm-authcard .sm-pagehead { display: block; margin-bottom: var(--space-md); }
.sm-authcard .sm-form { max-width: none; }
/* The card is block flow, not a flex column, so every space in it is written
   here (patterns/auth.md, The card's rhythm; #990). The step strip is apparatus
   over a display-size title and takes the space between blocks: flush, its
   circles sat on the title's ascenders and the two read as one crowded line. */
.sm-authcard > .sm-steps { margin-bottom: var(--space-lg); }
/* An error banner in the card's flow (the island's page error, or a server
   form's "There are N things to fix") keeps the page head's space below it, so
   it sits centered between what is above and below instead of resting on the
   next line. Inside a form, the form's gap spaces it instead. */
.sm-authcard .sm-banner:not(.sm-form > .sm-banner) { margin-bottom: var(--space-md); }
/* The card's own prose, and an invitation state's, sets no browser margin: the
   UA's 1em put 33px under the page head on an invitation and a tail under the
   sign-out page's link. One plain paragraph after another sits a field's gap
   below it. Classed paragraphs (the waiting line, the "or" rule, the closing
   lines) keep their own. */
.sm-authcard > p:not([class]), .sm-authcard section > p:not([class]) { margin: 0; }
.sm-authcard > p + p:not([class]), .sm-authcard section > p + p:not([class]) { margin-top: var(--space-md); }
/* A card whose content is a sentence and an act, rather than a form: the act
   gets room to be the card's answer instead of sitting against the last line of
   prose. The card's own acts row, or a state's section's, never a form's, so a
   form's acts row keeps the tighter rhythm forms.md gives it. */
.sm-authcard > .sm-acts, .sm-authcard section > .sm-acts { margin-top: var(--space-lg); }
.sm-authcard > .sm-colophon { margin: var(--space-lg) 0 0; }
/* The rule between the two ways in. A divider carrying a word, not a heading,
   so it is a paragraph and the word sits in the gap. */
.signin-or { display: flex; align-items: center; gap: var(--space-sm);
  margin: var(--space-lg) 0; color: var(--color-text-subtle);
  font-size: var(--font-size-small); }
.signin-or::before, .signin-or::after { content: ""; flex: 1;
  border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
/* The federated button is the peer of the whole block above the rule, not of
   one button inside it, so it spans the card exactly as the rule does. */
.sm-authcard .signin-or + .sm-btn { width: 100%; }
/* A federated button that leads the card (the invitation page's first way in,
   #1059) spans it the same way, with the space a button keeps from the
   sentence above it. */
.sm-authcard .sm-authway { width: 100%; margin-top: var(--space-md); }
/* The bot check's line (#1059). Clerk draws its check into a slot the island
   moves to sit after the pressed action; while the check shows, this one line
   says what it is for, so a Cloudflare box that appeared on its own is
   explained where the person is looking. It takes the focus when it appears
   (tabindex -1), so a screen reader reads it, and it is hidden when the check
   goes. Plain text at body size, the card's ink, a field's space below. */
.sm-captcha-note { margin: 0 0 var(--space-sm); color: var(--color-text-primary);
  font-weight: var(--font-weight-medium); }
/* The card's closing line (#808): the other door ("Already have an account?
   Sign in", and sign-in's line to sign-up), or the account's own page on the
   not-connected wall. Small and secondary, after everything the card asks for,
   so the reader who needs it finds it and the one primary act loses nothing.
   A line with a link in it, never a button. */
.sm-authcard .sm-authalt { margin: var(--space-lg) 0 0; font-size: var(--font-size-small);
  color: var(--color-text-secondary); }
/* The waiting state: the same card, a quieter interior. Header, plate, card
   frame and page head are identical either way and never move, so the swap is
   a fade and not a jump (patterns/auth.md).

   `.swap` is how it stays a fade: the waiting line and the form it becomes are
   one grid cell, so the card is sized by the FORM from the first paint. The
   surface hiding the form withholds it with `visibility`, never `display`, or
   the card would grow by a form's height the moment the answer arrived, which
   is the jump this is here to prevent. */
.sm-authcard .waiting { color: var(--color-text-secondary); margin: 0; }
.sm-authcard .swap { display: grid; }
.sm-authcard .swap > * { grid-area: 1 / 1; }
.sm-authcard [data-ready] { animation: sm-authfade var(--duration-base) cubic-bezier(var(--easing-enter)); }
@keyframes sm-authfade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .sm-authcard [data-ready] { animation: none; }
}
@media (max-width: 640px) {
  /* The plate leads, and pays for the form being reachable in one screen: the
     wordmark steps back to display and the colophon goes. Apparatus is the
     first thing worth spending on a phone. */
  .sm-titlepage { gap: var(--space-xl); min-height: 0; align-items: stretch;
    padding-top: var(--space-xl); }
  .sm-wordmark { font-size: var(--font-size-display); }
  .sm-plate .deck { margin-top: var(--space-md); }
  .sm-plate .sm-colophon { display: none; }
  /* The card's own acts row (the not-connected wall's, a dead invitation's)
     stacks and fills the card, as the sign-in form's row does in the same
     composition. At its intrinsic width the wall's "Sign out and try another
     account" ran past the card's edge on a phone (#808). */
  .sm-authcard > .sm-acts, .sm-authcard section > .sm-acts { flex-direction: column; align-items: stretch; }
  .sm-authcard > .sm-acts > .sm-btn, .sm-authcard section > .sm-acts > .sm-btn { width: 100%; }
  /* The one place a button's label may break (button.md): this card is the
     narrowest column in the product, and at 320px its buttons hold about 140px
     of text, which not even "Start an application" fits on one line. The label
     wraps inside the padding, centered, and the button grows a line rather than
     running past the card's edge (#808). */
  .sm-authcard .sm-btn:not(.sm-btn--link) { white-space: normal; text-align: center;
    padding-block: var(--space-xs); }
}

/* ---------- form grammar (patterns/forms.md) ---------- */
.sm-form { display: flex; flex-direction: column; align-items: stretch; gap: var(--space-md);
  max-width: 640px; font-family: var(--font-body); }
/* Two RELATED short fields may share a row at regular width; never three. They
   split the one 480px field line between them rather than taking the 640px
   column, so every field in a form ends on the same edge: a pair that ran past
   the single fields above and below it read as a third width on the page (#932). */
.sm-form .pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-md);
  max-width: 480px; }
@media (max-width: 640px) { .sm-form .pair { grid-template-columns: 1fr; } }
/* Actions row: primary act first (reading order), quiet alternatives after. */
.sm-acts { display: flex; gap: var(--space-xs); align-items: center; flex-wrap: wrap;
  margin-top: var(--space-xs); }
/* The row that CLOSES the block above it: a hairline, then the act. The same
   grammar the colophon line uses to close a page, borrowed so a form's save
   reads as that form's last word rather than as the next button in a column of
   them. Only on a row that genuinely ends its block; never on a row mid-flow. */
.sm-acts--footer { margin-top: var(--space-lg); padding-top: var(--space-lg);
  border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
/* Under compact a form's own acts row stacks and fills the column, which is what
   button.md's Behavior line has always promised and nothing implemented (SSM-132).
   What it buys is a thumb-sized target instead of a 150px box to aim at near the
   screen edge, on the surface whose design target is Principle 3's 68-year-old
   volunteer.
   DIRECT CHILD on purpose. A form's closing acts row is a child of the form; an
   acts row nested inside a block within the form is a different thing, and the
   one that exists (the video finder's "Find it on your channel" beside its own
   status line, dashboard/pages.tsx) is a deliberate pairing that stacking would
   break. Acts rows outside a form keep their intrinsic width: button.md scopes
   this to form footers, and widening it further is a taste call nobody has made. */
@media (max-width: 640px) {
  .sm-form > .sm-acts { flex-direction: column; align-items: stretch; }
  .sm-form > .sm-acts > .sm-btn { width: 100%; }
  /* A form inside a closing band (Series' merge, #968) ends on the band's
     destructive act, which keeps its own width like every band's act
     (patterns/dashboard.md, Destructive acts): it is not a save to reach for. */
  .closing .sm-form > .sm-acts { flex-direction: row; align-items: center; }
  .closing .sm-form > .sm-acts > .sm-btn { width: auto; }
}
/* The Save of a form that edits what is saved (sm-acts--save, #965, the
   owner's ask): the form's closing row, which names any unsaved changes in its
   note and offers Discard beside Save while there are some. While the form
   holds changes and its end is below the screen, the row is held at the foot
   of the screen, so on a long form Save is one reach from the field being
   changed; scrolled to the form's end it sits in its own place, looking the
   same. Held, it is the pinned mini-player's band: card surface under a strong
   hairline, across the whole width (a border-image outset, which is ink and
   never widens the page), its acts still on the fields' line. It arrives with
   the panel entrance and is positioned, never animated, after; the page keeps
   a focused control and a toast clear of it (WCAG 2.4.11). */
.sm-acts--save > .state { margin: 0; font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-acts--save > .state:empty { display: none; }
.sm-form[data-dirty] > .sm-acts--save { position: sticky; bottom: 0; z-index: var(--z-rail);
  padding: var(--space-sm) 0;
  border-image: linear-gradient(var(--color-border-strong) 0 var(--border-width-hairline), var(--color-surface-card) 0) fill 0 / 0 / 0 100vmax;
  animation: sm-dock-in var(--duration-base) cubic-bezier(var(--easing-enter)); }
@keyframes sm-dock-in { from { opacity: 0; transform: translateY(var(--space-2xs)); } }
/* A form brought back in anew still holding its changes (another act's
   swap) is held where it was, without the entrance a second time. */
.sm-form[data-dirty][data-held] > .sm-acts--save { animation: none; }
@media (prefers-reduced-motion: reduce) { .sm-form[data-dirty] > .sm-acts--save { animation: none; } }
/* The page keeps a focused control and a toast clear of the held row. Its
   height is measured as it changes (--sm-dock-h, set by the island runtime),
   since its note can wrap; the estimate stands in until it is. */
html:has(.sm-form[data-dirty] > .sm-acts--save) {
  scroll-padding-bottom: calc(var(--sm-dock-h, calc(var(--size-control-md) + 2 * var(--space-sm))) + var(--space-md)); }
html:has(.sm-form[data-dirty] > .sm-acts--save) .sm-toasts {
  bottom: calc(var(--sm-dock-h, calc(var(--size-control-md) + 2 * var(--space-sm))) + var(--space-md)); }
html:has(.sm-form[data-dirty] > .sm-acts--save):has(.sm-toasts .sm-toast) {
  scroll-padding-bottom: calc(var(--sm-dock-h, calc(var(--size-control-md) + 2 * var(--space-sm))) + var(--size-control-md) + 2 * var(--space-md)); }
/* On a phone the held row keeps to two lines: Save and Discard sharing one,
   the note under them, rather than three full-width rows. A toast there is
   full width and can take two lines. */
@media (max-width: 640px) {
  .sm-form[data-dirty] > .sm-acts--save { display: grid; grid-template-columns: 1fr 1fr;
    padding: var(--space-xs) 0; }
  .sm-form[data-dirty] > .sm-acts--save > .state { grid-column: 1 / -1; }
  html:has(.sm-form[data-dirty] > .sm-acts--save):has(.sm-toasts .sm-toast) {
    scroll-padding-bottom: calc(var(--sm-dock-h, calc(var(--size-control-md) + 2 * var(--space-sm))) + var(--size-control-md) + var(--space-xl) + 2 * var(--space-md)); }
}
/* Sections in the margin (patterns/forms.md, #550): one labeled group of a form.
   Below breakpoint.regular it is the single column it always was, the label
   above its fields at the form's own rhythm. From regular up the group is a row:
   the label, and at most one sentence about the group, in a 240px margin column,
   the fields beside it in the 640px column, and a hairline between rows. The
   form then spans the content wrap the way a table does instead of filling its
   left half, and no field grows: the 480px cap is what stops essays in name
   boxes. It is the catalog row's move (the date in the margin) and the
   transcript's (the seek strip beside the prose): apparatus beside the content. */
/* A <fieldset>, so the UA's border, padding and min-content minimum go: the
   group is named by aria-labelledby, never drawn as a box (forms.md: no
   fieldset boxes). */
.sm-formsec { display: flex; flex-direction: column; gap: var(--space-md);
  border: 0; margin: 0; padding: 0; min-width: 0; }
/* A group's label may be a heading (h2, h3) where the page is made of its
   sections (Your account, #979): it sits in the margin as the paragraph does. */
.sm-formsec :is(p, h2, h3) { margin: 0; }
/* A group's sentences may carry one long unbroken thing (an address): it
   breaks at the column's edge rather than running past it on a phone. */
.sm-formsec > .fields > p { overflow-wrap: anywhere; }
.sm-formsec > .lbl { display: flex; flex-direction: column; gap: var(--space-2xs); }
.sm-formsec > .lbl > .sub { font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-formsec > .fields { display: flex; flex-direction: column; gap: var(--space-md); min-width: 0; }
@media (min-width: 1024px) {
  .sm-form:has(> .sm-formsec) { max-width: calc(240px + var(--space-2xl) + 640px); gap: var(--space-xl); }
  .sm-formsec { display: grid; grid-template-columns: 240px minmax(0, 640px);
    gap: var(--space-md) var(--space-2xl); align-items: start; }
  /* The label's small caps sit on the line of the first field label beside them. */
  .sm-formsec > .lbl { padding-top: var(--space-2xs); }
  /* A group that opens on a checkbox or radio row: the row's first line sits
     where a field's label would, at the top of the column, level with the label
     in the margin beside it (#840). The row's top padding hangs above instead,
     so its wash is still the whole row. One level of wrapper is allowed for a
     list of rows. Only here, where the margin label sits beside the fields; on a
     narrow screen the label is above them and the row keeps its gap. */
  .sm-formsec > .fields > :is(.sm-checkrow, .sm-radiorow):first-child,
  .sm-formsec > .fields > :first-child > :is(.sm-checkrow, .sm-radiorow):first-child {
    margin-top: calc(-1 * var(--space-xs)); }
  .sm-formsec + .sm-formsec { padding-top: var(--space-xl);
    border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
  /* What belongs to the whole form (its closing acts row, a banner, a progress
     bar) stays a direct child, so the compact acts rule above still reaches it,
     and lines up with the fields column here. */
  .sm-form:has(> .sm-formsec) > :not(.sm-formsec) { margin-left: calc(240px + var(--space-2xl));
    max-width: 640px; }
}

/* ---------- the contents rail (components/contents.md, #1191) ---------- */
/* The Settings rail: every page of Settings, the overview included, has the
   same grouped rail beside it from 1024px, on its own recessed surface so it
   reads as the index it is, never as more of the page. Below 1024px it is
   hidden and the page's bar ("← Settings", All settings) is the way about;
   the same rail opens in the sheet there. Kept after the formsec block above,
   never spliced into it: scripts/dashboard-layout.spec.ts reads that block
   literally. */
.sm-contents { display: none; }
.sm-settings .sm-panebar { display: flex; }
@media (min-width: 1024px) {
  /* 240 + 48 + 832 = 1120, the wrap: the rail, the gap, then the pane. */
  .sm-settings { display: grid; grid-template-columns: 240px minmax(0, 1fr);
    gap: var(--space-2xl); align-items: start; }
  .sm-settings .sm-panebar { display: none; }
  /* Sticky so it holds while a long page scrolls. The header band is sticky
     too, so the rail's top clears it (--sm-band-height, set by the dashboard
     shell and measured by the runtime), and the rail scrolls on its own in a
     window too short for every entry. It sticks exactly where it rests, under
     the band and the page's space.xl, so scrolling to and from the top never
     moves it (#1291: at space.lg it slid a few pixels and stopped). */
  .sm-settings > .sm-contents { display: flex; position: sticky;
    top: calc(var(--sm-band-height, 0px) + var(--space-xl));
    max-height: calc(100vh - var(--sm-band-height, 0px) - var(--space-2xl));
    overflow-y: auto; overscroll-behavior: contain; }
  /* The pane's tables sit in panels narrower than the wrap, so they take
     tighter cells and keep an address whole rather than breaking it mid-word. */
  .sm-settings .sm-table th, .sm-settings .sm-table td {
    padding-left: var(--space-xs); padding-right: var(--space-xs); }
  .sm-settings .sm-table .email { overflow-wrap: normal; }
}
.sm-settings > .sm-contents { flex-direction: column; gap: var(--space-sm);
  padding: var(--space-sm) var(--space-xs);
  background: var(--color-surface-subtle);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); }
/* In the phone's sheet the rail is the sheet's whole body, unframed. */
.sm-sheet .sm-contents { display: flex; flex-direction: column; gap: var(--space-sm); }
/* Every row is a 44px target (Principle 3) on a constant weight, so choosing one
   never changes geometry; the hairline is always there, transparent until the
   row is current. */
.sm-contents .home, .sm-contents ul a { position: relative; display: flex; align-items: center;
  gap: var(--space-xs); min-height: calc(var(--size-control-md) - var(--space-2xs));
  padding: 0 var(--space-sm); border: var(--border-width-hairline) solid transparent;
  border-radius: var(--radius-control); color: var(--color-text-primary);
  text-decoration: none; cursor: pointer;
  transition: background-color var(--duration-fast) cubic-bezier(var(--easing-standard)); }
.sm-contents .home { font-family: var(--font-display); font-size: var(--font-size-heading);
  font-weight: var(--font-weight-medium); line-height: var(--line-height-tight); }
.sm-contents .home:hover, .sm-contents ul a:hover { background: var(--color-action-hover-wash); }
.sm-contents .grp .sm-microlabel { margin: 0 0 var(--space-2xs); padding: 0 var(--space-sm);
  line-height: var(--line-height-snug); color: var(--color-text-secondary); }
.sm-contents ul { list-style: none; margin: 0; padding: 0; }
/* A long entry wraps and hangs under its first line; none is truncated. */
.sm-contents ul a { font-family: var(--font-body); font-size: var(--font-size-body);
  line-height: var(--line-height-snug); font-weight: var(--font-weight-regular);
  text-wrap: pretty; }
/* The one marker, in two cues: the row lifts onto the card surface with its
   hairline, in text.brand ink, and a bar hangs in its gutter. */
.sm-contents .home[aria-current] { background: var(--color-surface-card);
  border-color: var(--color-border-subtle); color: var(--color-text-brand); }
.sm-contents ul a[aria-current] { color: var(--color-text-brand);
  background: var(--color-surface-card); border-color: var(--color-border-subtle); }
.sm-contents ul a[aria-current]::before { content: ""; position: absolute;
  left: calc(-1 * var(--border-width-hairline)); top: var(--space-xs); bottom: var(--space-xs);
  width: var(--border-width-marker); border-radius: var(--radius-pill);
  background: var(--color-state-selected-border); }
/* While the page holds unsaved changes, its entry says so with a dot: leaving
   by the rail asks first, and the dot is why. */
html:has(form[data-unsaved][data-dirty]) .sm-contents ul a[aria-current]::after { content: "";
  flex: none; margin-left: auto; width: var(--space-xs); height: var(--space-xs);
  border-radius: var(--radius-pill); background: var(--color-state-modified); }
/* A document of the API reference lists its own groups under its entry (#1197):
   indented on a hairline, a size smaller and in secondary ink, each a link to
   its heading. The row geometry, hover wash and focus ring are the rail's own;
   only a finger needs the full 44px, so a pointer gets a tighter row. */
.sm-contents ul.sub { margin: var(--space-3xs) 0 var(--space-xs) var(--space-sm);
  border-left: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-contents ul.sub a { min-height: calc(var(--size-control-md) - var(--space-md));
  font-size: var(--font-size-small); color: var(--color-text-secondary); cursor: pointer; }
@media (pointer: coarse) {
  .sm-contents ul.sub a { min-height: calc(var(--size-control-md) - var(--space-2xs)); } }
/* The group in view (#1254): as the page scrolls, components.js marks the group
   whose heading last passed the reading line with aria-current="true". It takes
   the current entry's ink and bar but not its lift onto the card, so the page's
   own entry stays the one row that stands up, and the group reads as where you
   are on it. Never the unsaved dot: that belongs to the page's entry. */
.sm-contents ul.sub a[aria-current="true"] { color: var(--color-text-brand);
  background: transparent; border-color: transparent; }
.sm-contents ul.sub a[aria-current="true"]::after { content: none; }
.sm-contents a:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
/* The rail scrolls inside itself, so its column would shrink a row to its
   minimum to fit the window; the door and the way back keep their height. */
/* The Developers area's door (#1196): one card on the rail's recessed ground,
   its name and what is through it, so it reads as a way into a place of its
   own rather than one more entry. */
.sm-contents .door { --sm-chevron: 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.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");
  position: relative; flex: none; display: flex; flex-direction: column; gap: var(--space-3xs);
  min-height: var(--size-target-min); padding: var(--space-xs) var(--space-sm);
  padding-right: calc(var(--space-sm) + var(--size-icon-md) + var(--space-xs));
  background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-control); color: var(--color-text-primary);
  text-decoration: none; cursor: pointer;
  transition: border-color var(--duration-fast) cubic-bezier(var(--easing-standard)); }
.sm-contents .door:hover { border-color: var(--color-border-strong); }
.sm-contents .door .lbl { font-weight: var(--font-weight-medium); line-height: var(--line-height-snug); }
.sm-contents .door .sub { font-size: var(--font-size-small); line-height: var(--line-height-snug);
  color: var(--color-text-secondary); text-wrap: pretty; }
.sm-contents .door::after { content: ""; position: absolute; right: var(--space-sm); top: 50%;
  width: var(--size-icon-md); height: var(--size-icon-md); margin-top: calc(var(--size-icon-md) / -2);
  background: var(--color-text-subtle);
  -webkit-mask: var(--sm-chevron) center / contain no-repeat;
  mask: var(--sm-chevron) center / contain no-repeat; pointer-events: none; }
.sm-contents .door:hover::after { background: var(--color-text-action); }
/* Inside the area, the rail's first line is the way back out. */
.sm-contents .back { display: inline-flex; flex: none; align-items: center; align-self: flex-start;
  min-height: var(--size-target-min); padding: 0 var(--space-sm);
  font-size: var(--font-size-small); color: var(--color-text-action);
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--color-border-link); cursor: pointer; }
.sm-contents .back:hover { text-decoration-color: currentColor; }
@media (prefers-reduced-motion: reduce) {
  .sm-contents .home, .sm-contents ul a, .sm-contents .door { transition: none; } }

/* ---------- the pane (patterns/dashboard.md, Settings; #1191) ---------- */
/* What each Settings page is: its bar (below 1024px), its head, its panels, then
   its colophon and the way to the next page. */
.sm-pane { display: flex; flex-direction: column; gap: var(--space-xl); min-width: 0; }
.sm-panebar { align-items: center; justify-content: space-between; gap: var(--space-md); }
.sm-panebar .sm-back { margin: 0; }
/* The head: the group in small caps, the title, one sentence on what the page
   is for, closed by the band's own ink rule. */
.sm-panehead { display: flex; flex-direction: column; gap: var(--space-xs);
  padding-bottom: var(--space-lg); border-bottom: var(--border-width-hairline) solid var(--color-border-strong); }
.sm-panehead .sm-microlabel { margin: 0; }
/* A page of one thing (a key, an endpoint) goes back from its head where the
   bar is hidden, in its kicker's place. */
.sm-panehead .up { display: none; align-self: flex-start; margin: 0; }
@media (min-width: 1024px) {
  .sm-settings .sm-panehead .up { display: inline-flex; }
  .sm-settings .sm-panehead .up + .sm-microlabel { display: none; }
}
.sm-panehead h1 { margin: 0; font-family: var(--font-display); font-size: var(--font-size-display);
  font-weight: var(--font-weight-medium); line-height: var(--line-height-tight);
  color: var(--color-text-heading); text-wrap: balance; overflow-wrap: anywhere; }
.sm-panehead .lede { margin: 0; max-width: 62ch; color: var(--color-text-secondary); text-wrap: pretty; }
.sm-panehead .addr { margin: 0; }
.sm-panehead .addr a { display: inline-flex; align-items: center; min-height: var(--size-target-min);
  color: var(--color-text-action); text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--color-border-link); overflow-wrap: anywhere; cursor: pointer; }
.sm-panehead .addr a:hover { text-decoration-color: currentColor; }
/* The pane's colophon closes its panels; the pager follows it. */
.sm-pane > .sm-colophon { margin: 0; }

/* ---------- panel (components/panel.md, #1191) ---------- */
/* One decision: a card on the page ground with a hairline frame, a head (its
   title, one sentence, where it shows), a body, and, when it is a form, its own
   closing acts row as its foot. One card deep: nothing inside is boxed but
   controls, chips and code. The panel's edge is the edge of what its act saves. */
.sm-panel { display: flex; flex-direction: column; min-width: 0; margin: 0; padding: 0;
  background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); }
form.sm-panel { max-width: none; gap: 0; align-items: stretch; }
.sm-panel > .head { display: flex; flex-wrap: wrap; justify-content: space-between;
  align-items: flex-start; gap: var(--space-md) var(--space-lg);
  padding: var(--space-lg) var(--space-lg) 0; }
.sm-panel > .head > .t { display: flex; flex-direction: column; gap: var(--space-2xs); min-width: 0;
  flex: 1 1 20rem; }
.sm-panel > .head h2 { margin: 0; font-family: var(--font-display); font-size: var(--font-size-title);
  font-weight: var(--font-weight-semibold); line-height: var(--line-height-snug);
  color: var(--color-text-heading); text-wrap: balance; }
.sm-panel > .head .sub { margin: 0; max-width: 60ch; font-size: var(--font-size-small);
  color: var(--color-text-secondary); text-wrap: pretty; }
.sm-panel > .head .shows { margin: 0; font-size: var(--font-size-caption); color: var(--color-text-subtle); }
.sm-panel > .head > .aside { display: flex; flex-wrap: wrap; gap: var(--space-xs); align-items: center; }
/* The body; in a form it is the panel's one labeled group (a fieldset named by
   the head), which the unsaved note and the leave dialog name it by. */
.sm-panel > .body { display: flex; flex-direction: column; gap: var(--space-md); min-width: 0;
  margin: 0; padding: var(--space-lg); border: 0; }
.sm-panel > .body > :is(p, h3) { margin: 0; }
.sm-panel > .body > p { overflow-wrap: anywhere; }
/* A table runs to the panel's edges, its first and last cells on the panel's
   padding, so its rules span the panel the way the head's does not. */
.sm-panel > .body > .sm-tablewrap { position: relative; margin-inline: calc(-1 * var(--space-lg)); }
.sm-panel > .body > .sm-tablewrap :is(th, td):first-child { padding-left: var(--space-lg); }
.sm-panel > .body > .sm-tablewrap :is(th, td):last-child { padding-right: var(--space-lg); }
/* ...and to its foot when it ends the panel (#1292): the panel's frame is the
   table's last rule, so the body keeps no padding under it, the last row no
   hairline of its own, and the wrap the panel's corners, so a last row's hover
   wash stays inside them. Only where the body is the panel's last part: a form
   panel's acts row below keeps the body's padding. */
.sm-panel > .body:last-child:has(> .sm-tablewrap:last-child) { padding-bottom: 0; }
.sm-panel > .body:last-child > .sm-tablewrap:last-child {
  border-end-start-radius: calc(var(--radius-surface) - var(--border-width-hairline));
  border-end-end-radius: calc(var(--radius-surface) - var(--border-width-hairline)); }
.sm-panel > .body:last-child > .sm-tablewrap:last-child tbody > tr:last-child > :is(th, td) { border-bottom: 0; }
/* Facts (#1196): what one thing is, label over value on a phone and the
   labels in a column of their own from 641px, the way a record sets them but
   in the panel's own face rather than small caps. A value may carry a quieter
   line under it. */
.sm-panel dl.facts { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 var(--space-lg); margin: 0; }
.sm-panel dl.facts > dt { margin: 0; font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-panel dl.facts > dd { margin: 0 0 var(--space-sm); min-width: 0; overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums; }
.sm-panel dl.facts > dd:last-child { margin-bottom: 0; }
.sm-panel dl.facts .sub { display: block; font-size: var(--font-size-small); color: var(--color-text-subtle); }
.sm-panel dl.facts code { font-size: var(--font-size-small); }
@media (min-width: 641px) {
  .sm-panel dl.facts { grid-template-columns: 11rem minmax(0, 1fr); row-gap: var(--space-sm); }
  .sm-panel dl.facts > dt { padding-top: var(--space-3xs); }
  .sm-panel dl.facts > dd { margin-bottom: 0; }
}
/* The foot: the form's own closing acts row, on the subtle surface. A section
   panel whose acts are on the whole panel (Send a test event) closes on the
   same row. */
.sm-panel > .sm-acts--footer { margin: 0; padding: var(--space-sm) var(--space-lg);
  background: var(--color-surface-subtle);
  border-top: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: 0 0 var(--radius-surface) var(--radius-surface); }
/* Unsaved: the frame and the foot take the modified state, the Save turns
   primary (#1121), and the foot holds the foot of the screen while the panel is
   in view, inside the panel and never across the rail. */
.sm-panel[data-dirty] { border-color: var(--color-state-modified); }
form.sm-panel[data-dirty] > .sm-acts--save { border-image: none; padding: var(--space-sm) var(--space-lg);
  background: var(--color-state-modified-bg); border-top-color: var(--color-state-modified); }
form.sm-panel[data-dirty] > .sm-acts--save > .state { color: var(--color-state-modified-text);
  font-weight: var(--font-weight-medium); }
/* Destructive acts get the last panel, set apart from the rest. */
.sm-panel--closing { margin-top: var(--space-lg); }
@media (max-width: 640px) {
  .sm-panel > .head { padding: var(--space-md) var(--space-md) 0; }
  .sm-panel > .body { padding: var(--space-md); }
  .sm-panel > .body > .sm-tablewrap { margin-inline: calc(-1 * var(--space-md)); }
  .sm-panel > .body > .sm-tablewrap :is(th, td):first-child { padding-left: var(--space-md); }
  .sm-panel > .body > .sm-tablewrap :is(th, td):last-child { padding-right: var(--space-md); }
  .sm-panel > .sm-acts--footer, form.sm-panel[data-dirty] > .sm-acts--save {
    padding: var(--space-xs) var(--space-md); }
  /* A closing panel ends on its destructive act, which keeps its own width:
     it is not a save to reach for. */
  .sm-panel--closing > .sm-acts { flex-direction: row; align-items: center; }
  .sm-panel--closing > .sm-acts > .sm-btn { width: auto; }
}

/* ---------- sermon card and state strip (#1280) ---------- */
/* The dashboard sermon page's side card: what the sermon is as a thing to
   play, reach, check and act on, in groups on one card (one card deep, no
   shadow, as the panel). overflow hidden lets the listen band's fill meet the
   corners; the hairline between groups is the only divider. */
.sm-sidecard { display: flex; flex-direction: column; min-width: 0; margin: 0; padding: 0;
  overflow: hidden; background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); }
.sm-sidecard > * { padding: var(--space-md) var(--space-lg); }
.sm-sidecard > * + * { border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-sidecard > .listen { display: flex; align-items: center; gap: var(--space-md);
  background: var(--color-surface-subtle); }
.sm-sidecard .listen .what { display: flex; flex-direction: column; min-width: 0; }
.sm-sidecard .listen .t { font-size: var(--font-size-small); font-weight: var(--font-weight-semibold); }
.sm-sidecard .listen .len { font-size: var(--font-size-small); color: var(--color-text-secondary);
  font-variant-numeric: tabular-nums; }
/* The play button's icon swap: the dashboard's sheet does not carry the
   library's, and without script the button is hidden (the sermon's native
   player stays on the page). */
.sm-sidecard [data-play-src] .pause { display: none; }
.sm-sidecard [data-play-src].is-playing .play-icon { display: none; }
.sm-sidecard [data-play-src].is-playing .pause { display: block; }
html:not(.js) .sm-sidecard > .listen { display: none; }
/* Copy link needs its script; without one, View it in your library is the way to the address. */
html:not(.js) .sm-sidecard [data-copy-link] { display: none; }
/* The round play, outlined at rest so the card's one filled control stays its
   primary act, and filled while it plays (the library's round play is filled
   because nothing else on its row is). */
.sm-sidecard .listen .pb { flex: none; display: inline-grid; place-items: center;
  width: var(--size-control-md); height: var(--size-control-md); padding: 0; cursor: pointer;
  border: var(--border-width-hairline) solid var(--color-border-control); border-radius: var(--radius-pill);
  background: var(--color-surface-card); color: var(--color-text-action); }
.sm-sidecard .listen .pb:hover { border-color: var(--color-border-strong); background: var(--color-action-hover-wash); }
.sm-sidecard .listen .pb.is-playing { border-color: var(--color-action-primary-hover);
  background: var(--color-action-primary); color: var(--color-action-primary-text); }
.sm-sidecard .listen .pb svg { color: inherit; }
.sm-sidecard > .reach { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2xs) var(--space-md); }
.sm-sidecard .reach p { margin: 0; }
.sm-sidecard .reach .n { display: block; font-family: var(--font-display);
  font-size: var(--font-size-heading); font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight); font-variant-numeric: tabular-nums;
  color: var(--color-text-primary); }
.sm-sidecard .reach .l { font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-sidecard .reach p.span { grid-column: 1 / -1; font-size: var(--font-size-caption);
  color: var(--color-text-subtle); }
.sm-sidecard :is(.check, .about) > .sm-microlabel { margin: 0 0 var(--space-xs); }
.sm-sidecard .check ul { margin: 0; padding: 0; list-style: none; }
.sm-sidecard .check li { display: flex; justify-content: space-between; gap: var(--space-sm);
  padding-block: var(--space-xs); font-size: var(--font-size-small); }
.sm-sidecard .check li + li { border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-sidecard .check .count { color: var(--color-text-secondary); font-variant-numeric: tabular-nums;
  white-space: nowrap; }
.sm-sidecard > .acts { display: flex; flex-direction: column; gap: var(--space-sm); }
.sm-sidecard > .acts > .sm-btn { width: 100%; justify-content: center; }
/* Publish is its own form's button (a plain post without script), so the
   form takes the row and its button the full width, as the acts beside it. */
.sm-sidecard > .acts > form { display: flex; flex-direction: column; margin: 0; }
.sm-sidecard > .acts > form > .sm-btn { width: 100%; justify-content: center; }
.sm-sidecard dl.facts { display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2xs) var(--space-md); margin: 0; font-size: var(--font-size-small); }
.sm-sidecard dl.facts > dt { color: var(--color-text-secondary); }
.sm-sidecard dl.facts > dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums;
  color: var(--color-text-primary); }
.sm-sidecard > .foot { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-xs); }
.sm-sidecard .foot p { margin: 0; font-size: var(--font-size-small); color: var(--color-text-secondary); }
@media (max-width: 640px) {
  .sm-sidecard > * { padding: var(--space-md); }
  /* The facts as a two-up grid of small blocks, each term over its value, so
     they do not take a phone's first screen one row apiece (#1285). The terms
     and values are siblings, so each goes in its column by parity and dense
     packing stacks a value under its own term. */
  .sm-sidecard dl.facts { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-flow: dense;
    gap: var(--space-xs) var(--space-md); }
  .sm-sidecard dl.facts > dt:nth-of-type(odd), .sm-sidecard dl.facts > dd:nth-of-type(odd) { grid-column: 1; }
  .sm-sidecard dl.facts > dt:nth-of-type(even), .sm-sidecard dl.facts > dd:nth-of-type(even) { grid-column: 2; }
  .sm-sidecard dl.facts > dd { text-align: left; margin-bottom: var(--space-xs); }
}
/* The state strip: the lede's chip and sentence as one band, tinted by where
   the sermon stands (published, in review, being worked, failed, a draft). The
   `.lede p.deck.sm-statebar` form outranks the dashboard's `.lede p.deck`
   margin and layout rules without editing them. */
.sm-statebar, .lede p.deck.sm-statebar { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-sm); margin: 0; padding: var(--space-sm) var(--space-md);
  background: var(--color-surface-subtle); color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); }
/* In the lede it stands a step below the byline and its cites, and its
   sentence takes the room beside the chip, wrapping there before it drops
   under it. */
.lede p.deck.sm-statebar { margin-top: var(--space-md); }
.lede p.deck.sm-statebar > span + span { flex: 1 1 16rem; min-width: 0; max-width: none; }
.sm-statebar--published, .lede p.deck.sm-statebar--published { background: var(--color-feedback-success-bg); }
.sm-statebar--review, .lede p.deck.sm-statebar--review { background: var(--color-feedback-warning-bg); }
.sm-statebar--error, .lede p.deck.sm-statebar--error { background: var(--color-feedback-error-bg); }
.sm-statebar--work, .lede p.deck.sm-statebar--work,
.sm-statebar--draft, .lede p.deck.sm-statebar--draft { background: var(--color-surface-sunken); }

/* ---------- the sermon page's workspace (#1280) ---------- */
/* The page's swap root is the grid: the work in one column of titled panels,
   the sermon card sticky beside it. The children stay flat (the in-place swap
   keeps elements only as direct children of the root), so the two columns are
   the root's own grid, never wrapper divs. Below 1024px it stays the flex
   column .stack already is, at the tighter xl gap the panels want. */
.stack.sm-workspace { gap: var(--space-xl); }
/* The review notes render an empty section for a published sermon with nothing
   to check; an empty panel would be a bare hairline box. */
.sm-workspace > section:empty { display: none; }
@media (min-width: 1024px) {
  /* row-gap 0 because the card spans many implicit rows and gaps between empty
     rows would add space; each child carries its own margin instead. */
  .stack.sm-workspace { display: grid; grid-template-columns: minmax(0, 1fr) 21rem;
    column-gap: var(--space-xl); row-gap: 0; align-items: start; }
  /* Never the dialog: a modal is centered by its auto margins, and this
     margin-block-end would leave the top one to take all the room, setting the
     confirm dialog at the foot of the screen (#1290). */
  .stack.sm-workspace > :not(dialog) { grid-column: 1; min-width: 0; margin-block-end: var(--space-xl); }
  /* Sticks under the header band the way the Settings rail does. */
  .stack.sm-workspace > .sm-sidecard { grid-column: 2; grid-row: 1 / span 40; position: sticky;
    top: calc(var(--sm-band-height, 0px) + var(--space-lg));
    max-height: calc(100vh - var(--sm-band-height, 0px) - var(--space-2xl));
    overflow-y: auto; margin-block-end: 0; }
  /* The docked save bar's band runs to the screen's left edge, as everywhere,
     but stops at its own column's right edge: carried across the gap it
     painted over the foot of the side card (#1290). */
  .stack.sm-workspace .sm-form[data-dirty] > .sm-acts--save { border-image-outset: 0 0 0 100vmax; }
}
/* The details groups as panels. The base .sm-form and .sm-formsec rules (the
   640px field line, the 240px margin grid, the hairline between groups) are
   left as they are for every other form; these overrides sit at a higher
   specificity (.stack.sm-workspace adds two classes) so they win at every width
   without editing them. The form spans the column and what belongs to the
   whole form (a note, the error banner, the Save row) starts at its left edge. */
.stack.sm-workspace .sm-form:has(> .sm-formsec) { max-width: none; gap: var(--space-xl); }
.stack.sm-workspace .sm-form:has(> .sm-formsec) > :not(.sm-formsec) { margin-left: 0; max-width: none; }
/* Corrections, Transcript and the review notes wear the same panel surface.
   No overflow is set on any of them: the transcript's sticky player must still
   reach the viewport's foot. */
.stack.sm-workspace .sm-form > fieldset.sm-formsec,
.stack.sm-workspace > :is(section.corrections, section.transcript, section.check:not(:empty), section.publish) {
  display: flex; flex-direction: column; gap: var(--space-md); min-width: 0;
  padding: var(--space-lg); background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); }
/* The group's head sits above its fields, so no margin-column padding and no
   hairline from the group before it (the panels' own edges divide them). */
.stack.sm-workspace .sm-form > fieldset.sm-formsec + fieldset.sm-formsec { padding-top: var(--space-lg); }
.stack.sm-workspace .sm-form > fieldset.sm-formsec > .lbl { padding-top: 0; gap: var(--space-2xs); }
.stack.sm-workspace .sm-form > fieldset.sm-formsec > .fields { width: 100%; }
/* A panel's title, as Settings' .sm-panel > .head h2 reads: the display face at
   title size, not the microlabel's small letterspaced caps. The shell sheet's
   `.stack > section > .sm-microlabel:first-child` margin is (0,3,1); this list
   is (0,5,1) so it wins without editing that sheet. */
.stack.sm-workspace :is(.sm-form > fieldset.sm-formsec > .lbl > .sm-microlabel,
  section.corrections .sm-chead > .sm-microlabel, section.transcript > h2.sm-microlabel,
  section.check > .sm-microlabel, section.publish > .sm-microlabel) {
  margin: 0; font-family: var(--font-display); font-size: var(--font-size-title);
  font-weight: var(--font-weight-semibold); line-height: var(--line-height-snug);
  letter-spacing: normal; text-transform: none; font-variant-caps: normal;
  color: var(--color-text-primary); }
/* The head's count stays small and secondary beside its title. */
.stack.sm-workspace section.corrections .sm-chead > .sm-ccount { font-size: var(--font-size-small);
  color: var(--color-text-secondary); }
/* Two related fields fill the panel: two equal columns from 641px, one below
   (the base caps the pair at 480px and stacks at 640px). */
.stack.sm-workspace .sm-formsec .pair { max-width: none; grid-template-columns: 1fr; }
@media (min-width: 641px) {
  .stack.sm-workspace .sm-formsec .pair { grid-template-columns: 1fr 1fr; }
}
/* And so does every single field, so a pair and the fields above and below it
   still end on one edge (#932), the panel's. */
.stack.sm-workspace .sm-formsec .sm-field { max-width: none; }
@media (max-width: 640px) {
  .stack.sm-workspace .sm-form > fieldset.sm-formsec,
  .stack.sm-workspace > :is(section.corrections, section.transcript, section.check:not(:empty), section.publish) {
    padding: var(--space-md); }
  .stack.sm-workspace .sm-form > fieldset.sm-formsec + fieldset.sm-formsec { padding-top: var(--space-md); }
}

/* ---------- pager (patterns/navigation.md, #1191) ---------- */
/* The end of every Settings page: the page before and the page after, in the
   rail's order, so a phone never has to go back to the list to move on. */
.sm-pager { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-md); }
.sm-pager a { display: flex; flex-direction: column; gap: var(--space-3xs); min-width: 0;
  min-height: var(--size-row-min); padding: var(--space-sm) var(--space-md);
  background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); color: var(--color-text-primary); text-decoration: none;
  cursor: pointer; transition: border-color var(--duration-fast) cubic-bezier(var(--easing-standard)); }
.sm-pager a:hover { border-color: var(--color-border-control); }
.sm-pager a:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-pager .dir { font-size: var(--font-size-caption); font-weight: var(--font-weight-semibold);
  letter-spacing: var(--letter-spacing-wide); text-transform: uppercase; color: var(--color-text-subtle); }
.sm-pager .next { grid-column: 2; text-align: right; }
@media (max-width: 640px) {
  .sm-pager { grid-template-columns: minmax(0, 1fr); }
  .sm-pager .next { grid-column: 1; }
}
@media (prefers-reduced-motion: reduce) { .sm-pager a { transition: none; } }

/* ---------- the proof (patterns/dashboard.md, Settings: Church profile, #1150) ---------- */
/* The church's own library front page and the top of its printed transcript,
   drawn beside the form and following it as it is typed. Two frames, each a
   srcdoc document under the real cascade (dashboard/proof.tsx), so what is
   styled HERE is only what surrounds them. From 1280px it is a column beside
   the panels, in the wide frame, that sticks under the header band; below that it is behind a quiet "See the proof" in a sheet, and
   without script the column copy simply stays in the page, under the form. */
.sm-profile { display: block; }
.sm-profile-peek { display: none; margin: 0 0 var(--space-md); }
html[data-proof-sheet] .sm-profile-peek { display: block; }
.sm-proof { display: flex; flex-direction: column; gap: var(--space-xs); min-width: 0;
  font-family: var(--font-body); }
.sm-proof--column { margin-top: var(--space-2xl); }
html[data-proof-sheet] .sm-proof--column { display: none; }
.sm-proof .hd { display: flex; align-items: baseline; gap: var(--space-sm);
  min-height: var(--size-control-sm); }
.sm-proof .hd .sm-microlabel { margin: 0; }
/* The form's own word for it, in the form's own color (state.modified-text). */
.sm-proof .state { margin: 0; font-size: var(--font-size-small);
  font-weight: var(--font-weight-medium); color: var(--color-state-modified-text); }
.sm-proof .frame { display: block; flex: none; box-sizing: border-box; width: 100%; height: 22rem;
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); background: var(--color-surface-page);
  color-scheme: normal; }
.sm-proof .frame--paper { height: 14rem; }
.sm-proof .cap, .sm-proof .say { margin: 0; font-size: var(--font-size-small);
  color: var(--color-text-secondary); text-wrap: pretty; }
.sm-proof .paper { display: flex; flex-direction: column; gap: var(--space-xs);
  margin-top: var(--space-md); }
.sm-proof .paper .sm-microlabel { margin: 0; }
/* In the sheet the sheet's own head says "Proof". */
.sm-proof--sheet .hd .sm-microlabel { display: none; }
.sm-proof--sheet .hd { min-height: 0; }
.sm-proof-sheet .body { padding-top: var(--space-md); }
@media (min-width: 641px) and (max-width: 1279px) {
  dialog.sm-sheet.sm-proof-sheet[open] { display: flex; width: min(100%, 560px); margin: auto auto 0; }
}
/* Church profile is the one Settings page in the wide frame (#1191): the wrap
   plus the rail's width, so the panels keep their fields' 480px with the proof
   beside them. From 1280px the proof is that column; below, it is the sheet. */
main.wrap:has(> .sm-settings--wide) { max-width: calc(var(--size-wrap) + 240px); }
.sm-profile > .panels { display: flex; flex-direction: column; gap: var(--space-xl); min-width: 0; }
@media (min-width: 1280px) {
  .sm-profile { display: grid; grid-template-columns: minmax(0, 1fr) 400px;
    column-gap: var(--space-xl); align-items: start; }
  .sm-profile-peek, html[data-proof-sheet] .sm-profile-peek { display: none; }
  .sm-proof--column, html[data-proof-sheet] .sm-proof--column { display: flex; margin-top: 0;
    position: sticky; top: calc(var(--sm-band-height, 0px) + var(--space-lg));
    max-height: calc(100vh - var(--sm-band-height, 0px) - var(--space-lg) - var(--space-md));
    overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
  /* While a panel holds changes its foot is held at the foot of the window;
     the proof stops above it rather than running under it. */
  html:has(.sm-form[data-dirty] > .sm-acts--save) .sm-proof--column {
    max-height: calc(100vh - var(--sm-band-height, 0px) - var(--space-lg)
      - var(--sm-dock-h, calc(var(--size-control-md) + 2 * var(--space-sm))) - var(--space-md)); }
}

/* ---------- a Settings page with a side card (#1281) ---------- */
/* An API key's page and an endpoint's page keep what they are as a thing to act
   on in the sermon page's card, a direct child of the pane so the in-place swap
   keeps it. Below 1280px the pane is too narrow for a column (688px at 1024px),
   so the card stays in the flow after the head. From 1280px it is the second
   column, sticky under the band as the sermon card is. row-gap 0 because the
   card spans many implicit rows; each child carries the pane's own space-xl
   between children as a margin instead. [hidden] children and a closed dialog
   are display: none, and a fixed toasts region is out of flow, so none takes a
   grid cell. */
@media (min-width: 1280px) {
  .sm-pane:has(> .sm-sidecard) { display: grid; grid-template-columns: minmax(0, 1fr) 21rem;
    column-gap: var(--space-xl); row-gap: 0; align-items: start; }
  /* Not the dialog, whose auto margins center it (#1290). */
  .sm-pane:has(> .sm-sidecard) > :not(dialog) { grid-column: 1; min-width: 0; margin-block-end: var(--space-xl); }
  .sm-pane:has(> .sm-sidecard) > .sm-sidecard { grid-column: 2; grid-row: 1 / span 40;
    position: sticky; top: calc(var(--sm-band-height, 0px) + var(--space-lg));
    max-height: calc(100vh - var(--sm-band-height, 0px) - var(--space-2xl));
    overflow-y: auto; margin-block-end: 0; }
}

/* ---------- a side card that adapts to its own width (#1281) ---------- */
/* The card sits in a 21rem column on a desktop and the full width of the page on
   a tablet, where its acts as full-width bars are a stack of slabs. Inline-size
   containment only, so its height and overflow-y are untouched. Past 32rem the
   acts lie side by side at their own widths; in the column they stay stacked. */
.sm-sidecard { container: sidecard / inline-size; }
@container sidecard (min-width: 32rem) {
  .sm-sidecard > .acts { flex-direction: row; flex-wrap: wrap; align-items: center; }
  .sm-sidecard > .acts > .sm-btn, .sm-sidecard > .acts > form > .sm-btn { width: auto; }
}

/* ---------- a panel's own destructive foot (#1281) ---------- */
/* A panel about one thing (a key, an endpoint) may end on the act that ends it.
   A row after the body, a hairline above, the act at its own width and a line
   saying what it does. Not the save foot's subtle fill: this is not a save row. */
.sm-panel > .sm-acts--closing { margin: 0; padding: var(--space-md) var(--space-lg);
  border-top: var(--border-width-hairline) solid var(--color-border-subtle);
  flex-direction: column; align-items: flex-start; gap: var(--space-xs); }
.sm-panel > .sm-acts--closing > .sm-btn { width: auto; }
.sm-panel > .sm-acts--closing p { margin: 0; font-size: var(--font-size-small);
  color: var(--color-text-secondary); }
@media (max-width: 640px) {
  .sm-panel > .sm-acts--closing { padding: var(--space-md); }
}

/* ---------- the overview (/settings, patterns/dashboard.md; #1149, #1191) ---------- */
/* Settings' front page: the church's name and address as the head, then each
   group a panel of rows, each row the pane's name and what is in it now in
   words, the whole row a link. The rail stands beside it as beside every page.
   Any panel may hold the same rows, as a list of places to go (#1196,
   components/panel.md, Rows); only the overview sets name and value side by
   side, so its values line up like page numbers. */
.sm-panel .reg-rows { --sm-register-chevron: 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.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");
  list-style: none; margin: 0; padding: 0; }
.sm-panel .reg-rows > li { border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-panel .reg-row { display: grid; grid-template-columns: minmax(0, 1fr) var(--size-icon-md);
  column-gap: var(--space-md); row-gap: var(--space-3xs); align-items: baseline;
  min-height: var(--size-row-min); padding: var(--space-sm) var(--space-lg);
  color: inherit; text-decoration: none; }
.sm-panel .reg-rows > li:last-child .reg-row { border-radius: 0 0 var(--radius-surface) var(--radius-surface); }
.sm-panel a.reg-row { position: relative; cursor: pointer;
  transition: background var(--duration-fast); }
.sm-panel a.reg-row:hover { background: var(--color-action-hover-wash); }
.sm-panel .reg-name { grid-column: 1; min-width: 0; font-family: var(--font-display);
  font-weight: var(--font-weight-medium); font-size: var(--font-size-title);
  line-height: var(--line-height-snug); color: var(--color-text-primary);
  transition: color var(--duration-fast); }
.sm-panel a.reg-row:hover .reg-name { color: var(--color-text-action); }
.sm-panel .reg-value { grid-column: 1; grid-row: 2; min-width: 0; text-wrap: pretty;
  font-size: var(--font-size-small); color: var(--color-text-secondary);
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
/* A row may say more under its value: an address, or a status beside a line. */
.sm-panel .reg-value .sm-status { margin-right: var(--space-2xs); }
.sm-panel .reg-value code { font-size: var(--font-size-small); }
.sm-panel a.reg-row::after { content: ""; grid-column: 2; grid-row: 1; align-self: start;
  width: var(--size-icon-md); height: var(--size-icon-md); margin-top: var(--space-3xs);
  background: var(--color-text-subtle);
  -webkit-mask: var(--sm-register-chevron) center / contain no-repeat;
  mask: var(--sm-register-chevron) center / contain no-repeat; pointer-events: none; }
.sm-panel a.reg-row:hover::after { background: var(--color-text-action); }
.sm-panel .reg-row--plain .reg-name { color: var(--color-text-secondary); }
.sm-panel .reg-note { grid-column: 1; margin: 0; font-size: var(--font-size-small);
  color: var(--color-text-subtle); }
.sm-panel a.reg-row:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: calc(-1 * var(--border-width-focus)); }
/* The panel's own body holds the rows edge to edge. */
.sm-panel:has(> .reg-rows) > .head { padding-bottom: var(--space-md); }
/* Rows under a small-caps label, where one panel's rows fall in two kinds. */
.sm-panel > .reg-kind { margin: 0; padding: var(--space-md) var(--space-lg) var(--space-2xs);
  border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-panel > .reg-kind + .reg-rows > li:first-child { border-top: 0; }
@media (min-width: 760px) {
  /* The name and its value share one set of columns down the whole group, so
     the values line up like page numbers. */
  .sm-register .reg-rows { display: grid;
    grid-template-columns: 13rem minmax(0, 1fr) var(--size-icon-md); column-gap: var(--space-md); }
  .sm-register .reg-rows > li { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; }
  .sm-register .reg-row { grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; }
  .sm-register .reg-value { grid-column: 2; grid-row: 1; }
  .sm-register .reg-note { grid-column: 2; grid-row: 2; }
  .sm-register a.reg-row::after { grid-column: 3; align-self: center; margin-top: 0; }
}
@media (max-width: 640px) {
  .sm-panel .reg-row { padding-inline: var(--space-md); }
  .sm-panel > .reg-kind { padding-inline: var(--space-md); }
  .sm-panel:has(> .reg-rows) > .head { padding-bottom: var(--space-sm); }
}
@media (prefers-reduced-motion: reduce) {
  .sm-panel a.reg-row, .sm-panel .reg-name { transition: none; }
}

/* ---------- the Developers area (patterns/dashboard.md, Developers; #1196) ---------- */
/* What a key can do keeps a readable width beside its dates (#1196): its
   label and the line under it each hold to one line. */
.sm-settings .sm-table td.can { white-space: nowrap; }
/* A quieter line in a panel's body: what a value means, said after it. */
.sm-panel > .body > p.sub { font-size: var(--font-size-small); color: var(--color-text-secondary);
  max-width: 62ch; text-wrap: pretty; }
/* The foot's acts on the whole panel keep their own widths: a select beside
   the act it chooses for. */
.sm-panel > .sm-acts--footer { flex-wrap: wrap; align-items: center; }
.sm-panel > .sm-acts--footer > .sm-selectw { width: auto; max-width: 100%; }
/* A secret on a line of its own (#1196): the value in a well, whole and
   selected with one press, then its acts. A signing secret is masked until it
   is asked for; a new key is shown at once, in its dialog. */
.sm-secretwell { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs); }
.sm-secretwell code { flex: 1 1 18rem; min-width: 0; box-sizing: border-box;
  padding: var(--space-xs) var(--space-sm); font-family: var(--font-mono); font-size: var(--font-size-small);
  line-height: var(--line-height-snug); background: var(--color-surface-sunken); color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-control); border-radius: var(--radius-control);
  overflow-wrap: anywhere; user-select: all; }
.sm-secretwell code[data-masked] { user-select: none; color: var(--color-text-secondary); }
.sm-panel p.said, .sm-dialog p.said { margin: 0; font-size: var(--font-size-small); color: var(--color-text-secondary); }
/* A dialog that is a short form (Create a key, Add an endpoint): its steps
   take the dialog's block flow, so the title, body and act-row rhythm holds
   (components/dialog.md, A short form). The permissions a key may be given
   hang under the purpose that asks for them. */
dialog.sm-dialog--form { width: min(560px, calc(100% - 2 * var(--space-md))); max-width: none;
  max-height: calc(100vh - 2 * var(--space-lg)); overflow-y: auto; overscroll-behavior: contain; }
.sm-dialog .step[hidden] { display: none; }
.sm-dialog .step > .sm-field { margin: 0 0 var(--space-md); max-width: none; }
.sm-dialog .step .sm-secretwell { margin: 0 0 var(--space-sm); }
.sm-dialog .step .sm-checkrow { margin-top: var(--space-sm); }
.sm-scopes { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 var(--space-md);
  margin: var(--space-2xs) 0 0; padding-left: calc(22px + var(--space-sm)); }
.sm-scopes[hidden] { display: none; }
@media (min-width: 560px) { .sm-scopes { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* Deliveries (#1196): one row per event sent, in three named columns (what,
   what came of it, when), which opens in place to each of its tries, what was
   sent and what came back. Edge to edge in the panel, as its rows are
   (components/panel.md, Rows), each row 56px at least with room around its
   two lines, so a log of failures reads as a list and not a block. */
.sm-panel:has(> .sm-deliveries) > .head { padding-bottom: var(--space-lg); }
.sm-deliveries { --sm-caret: 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.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  --sm-dlv-cols: minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 12rem) var(--size-icon-md);
  list-style: none; margin: 0; padding: 0; }
.sm-deliveries > li { border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-deliveries > li.cols { display: none; }
.sm-deliveries summary { display: grid; grid-template-columns: minmax(0, 1fr) var(--size-icon-md);
  column-gap: var(--space-lg); row-gap: var(--space-xs); align-items: center;
  min-height: var(--size-row-min); padding: var(--space-md) var(--space-lg);
  list-style: none; cursor: pointer; transition: background var(--duration-fast); }
.sm-deliveries summary::-webkit-details-marker { display: none; }
.sm-deliveries summary:hover { background: var(--color-action-hover-wash); }
.sm-deliveries summary:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: calc(-1 * var(--border-width-focus)); }
.sm-deliveries summary::after { content: ""; grid-column: -2; grid-row: 1; align-self: center;
  width: var(--size-icon-md); height: var(--size-icon-md); background: var(--color-text-subtle);
  -webkit-mask: var(--sm-caret) center / contain no-repeat; mask: var(--sm-caret) center / contain no-repeat;
  transition: transform var(--duration-fast); }
.sm-deliveries summary:hover::after { background: var(--color-text-action); }
.sm-deliveries details[open] > summary::after { transform: rotate(180deg); }
.sm-deliveries details[open] > summary { background: var(--color-surface-subtle); }
.sm-deliveries > li:last-child details:not([open]) > summary { border-radius: 0 0 var(--radius-surface) var(--radius-surface); }
.sm-deliveries .ev, .sm-deliveries .res, .sm-deliveries .when { display: flex; flex-direction: column;
  align-items: flex-start; gap: var(--space-2xs); min-width: 0; }
.sm-deliveries summary > .ev, .sm-deliveries summary > .res, .sm-deliveries summary > .when { grid-column: 1; }
.sm-deliveries .ev .t { font-weight: var(--font-weight-medium); line-height: var(--line-height-snug); }
.sm-deliveries .ev code { font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-deliveries .when { font-size: var(--font-size-small); color: var(--color-text-primary);
  font-variant-numeric: tabular-nums; }
.sm-deliveries .sub { font-size: var(--font-size-small); color: var(--color-text-secondary);
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
/* The opened delivery: its tries, then what was sent beside what came back. */
.sm-deliveries .bd { display: flex; flex-direction: column; gap: var(--space-lg);
  padding: var(--space-lg); background: var(--color-surface-subtle);
  border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-deliveries > li:last-child .bd { border-radius: 0 0 var(--radius-surface) var(--radius-surface); }
.sm-deliveries .bd > p { margin: 0; }
.sm-deliveries .bd .sm-microlabel { margin: 0 0 var(--space-xs); }
.sm-deliveries .bd figure.sm-code { margin: 0; }
.sm-deliveries .bd .sm-acts { margin: 0; }
.sm-deliveries .io { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-md); }
/* Each try: when, what came of it, and which try it was, a row apiece. */
.sm-deliveries ol.tries { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: max-content minmax(0, 1fr) max-content; column-gap: var(--space-lg);
  font-size: var(--font-size-small); font-variant-numeric: tabular-nums; }
.sm-deliveries ol.tries > li { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid;
  align-items: baseline; padding: var(--space-xs) 0; }
.sm-deliveries ol.tries > li + li { border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-deliveries ol.tries .when, .sm-deliveries ol.tries .n { color: var(--color-text-secondary); white-space: nowrap; }
.sm-deliveries ol.tries .failed .what { color: var(--color-feedback-error); }
.sm-deliveries ol.tries .next .what { color: var(--color-text-secondary); font-style: italic; }
@media (min-width: 760px) {
  /* The three columns, named once over the rows. */
  .sm-deliveries > li.cols { display: grid; grid-template-columns: var(--sm-dlv-cols); column-gap: var(--space-lg);
    padding: var(--space-xs) var(--space-lg); background: var(--color-surface-subtle);
    font-size: var(--font-size-caption); font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wide); text-transform: uppercase; color: var(--color-text-subtle); }
  .sm-deliveries summary { grid-template-columns: var(--sm-dlv-cols); }
  .sm-deliveries summary > .ev { grid-column: 1; grid-row: 1; }
  .sm-deliveries summary > .res { grid-column: 2; grid-row: 1; }
  .sm-deliveries summary > .when { grid-column: 3; grid-row: 1; }
}
@media (min-width: 1024px) {
  .sm-deliveries .io { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .sm-deliveries summary { padding-inline: var(--space-md); }
  .sm-deliveries .res { flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--space-xs); }
  .sm-deliveries .bd { padding: var(--space-md); }
  .sm-deliveries ol.tries { grid-template-columns: minmax(0, 1fr) max-content; }
  .sm-deliveries ol.tries .what { grid-column: 1 / -1; grid-row: 2; }
  .sm-panel:has(> .sm-deliveries) > .head { padding-bottom: var(--space-md); }
}
@media (prefers-reduced-motion: reduce) {
  .sm-deliveries summary, .sm-deliveries summary::after { transition: none; }
}

/* ---------- the API reference (patterns/dashboard.md, API reference; #1152) ---------- */
/* Three components live here: the code block (components/code.md), the endpoint
   list (components/endpoint.md) and the attribute list (components/attributes.md).
   The page's groups keep the geometry they once shared with the register. Every
   color is a semantic token and no hue does identity work: a method or a status
   is words, never a color. */
.sm-ref { font-family: var(--font-body); color: var(--color-text-primary); min-width: 0; }
/* The page's groups: the label (small caps, one sentence) over its rows at every
   width, each group under the ink rule. The Settings rail lists a document's groups,
   so the page has no margin column of its own and its operations get the pane's width
   for prose and code side by side (#1197). */
.sm-ref-group { display: grid; grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md); padding-block: var(--space-md) var(--space-sm);
  border-top: var(--border-width-hairline) solid var(--color-border-strong); }
.sm-ref-label { display: flex; flex-direction: column; gap: var(--space-3xs); padding-top: var(--space-xs); }
.sm-ref-label .sm-microlabel { margin: 0; }
.sm-ref-sentence { margin: 0; max-width: var(--size-measure-read); text-wrap: pretty;
  font-size: var(--font-size-small); color: var(--color-text-subtle); }
.sm-ref-rows { list-style: none; margin: 0; padding: 0; }
.sm-ref [hidden] { display: none; }
.sm-ref-lead { margin: 0 0 var(--space-xl); max-width: var(--size-measure-read);
  font-size: var(--font-size-body); line-height: var(--line-height-body);
  color: var(--color-text-secondary); text-wrap: pretty; }
/* The search over a document's operations and fields (#1197): at the reading measure, its
   results quiet rows under the field, each a link to the operation. Nothing sticks, and it
   stays hidden until the island shows it. */
.sm-ref-search { max-width: var(--size-measure-read); margin-bottom: var(--space-xl); }
.sm-ref-results { list-style: none; margin: 0; padding: 0; }
.sm-ref-results:empty { display: none; }
.sm-ref-results a { display: flex; flex-direction: column; gap: var(--space-3xs);
  padding: var(--space-xs) var(--space-sm); border-radius: var(--radius-control);
  color: var(--color-text-primary); text-decoration: none; cursor: pointer; }
.sm-ref-results a:hover { background: var(--color-action-hover-wash); }
.sm-ref-results a:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-ref-hit-name { font-weight: var(--font-weight-medium); }
.sm-ref-hit-meta { display: flex; flex-wrap: wrap; column-gap: var(--space-sm);
  font-size: var(--font-size-small); color: var(--color-text-subtle); overflow-wrap: anywhere; }
.sm-ref-hit-path { font-family: var(--font-mono); }
.sm-ref-rows { min-width: 0; }
.sm-ref-rows > details.sm-x + details.sm-x { margin-top: var(--space-xs); }

/* Inline code: a sunken chip. Inside a block, a head or a row of its own it is bare. */
.sm-ref code { font-family: var(--font-mono); font-size: 0.88em; color: var(--color-text-primary);
  background: var(--color-surface-sunken); border-radius: var(--radius-chip);
  padding: 0 var(--space-2xs); overflow-wrap: anywhere; }
.sm-ref pre code, .sm-ref .sm-attr-head code, .sm-try label code, .sm-ref .sm-endpoint, .sm-ref .sm-op-errors dt code { background: none; padding: 0; font-size: inherit; }

/* Facts: a definition list of what every request needs. */
.sm-ref-facts { margin: 0; padding: 0; }
.sm-ref-facts > div { padding: var(--space-sm) 0; }
.sm-ref-facts > div + div { border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-ref-facts dt { font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps);
  font-size: var(--font-size-caption); font-weight: var(--font-weight-semibold);
  color: var(--color-text-subtle); margin: 0 0 var(--space-3xs); }
.sm-ref-facts dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
/* A value with its Copy beside it: one wrapping row, so on a phone the button starts
   under the value instead of hanging off its own indent. */
.sm-ref-facts dd:has(> .sm-btn) { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs); }
.sm-op-desc p, .sm-attr-desc p, .sm-ref-facts dd p, p.sm-ref-prose { margin: 0 0 var(--space-xs);
  max-width: var(--size-measure-read); font-size: var(--font-size-body);
  line-height: var(--line-height-body); color: var(--color-text-primary); text-wrap: pretty; }
.sm-attr-desc p:last-child, .sm-op-desc p:last-child, .sm-ref-facts dd p:last-child { margin-bottom: 0; }
/* Layout glue (#1152): prose between a group's blocks keeps the block's rhythm, and a
   facts list whose terms are code (events, headers) sets them in the mono face at their
   own width instead of in small caps. */
p.sm-ref-prose + figure.sm-code, figure.sm-code + p.sm-ref-prose, .sm-ref-rows > * + .sm-ref-facts { margin-top: var(--space-md); }
.sm-ref-facts--wide dt code { font-variant-caps: normal; letter-spacing: normal; font-size: var(--font-size-small); }
@media (min-width: 640px) {
  .sm-ref-facts > div { display: grid; grid-template-columns: 120px minmax(0, 1fr); column-gap: var(--space-md); }
  .sm-ref-facts dt { margin: 0; padding-top: var(--space-3xs); }
  .sm-ref-facts--wide > div { grid-template-columns: 220px minmax(0, 1fr); }
}

/* Endpoint list: one section per operation, always open, ruled apart. Its head
   spans the section; below 1024px the prose and then the code follow in one column,
   from 1024px they stand side by side and the code column sticks (#1186, #1197). */
.sm-ops > li + li { border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-op { padding-block: var(--space-xl); min-width: 0; }
.sm-ops > li:first-child > .sm-op { padding-top: 0; }
.sm-op-head { display: grid; grid-template-columns: minmax(0, 1fr) auto;
  column-gap: var(--space-md); row-gap: var(--space-3xs); align-items: baseline;
  margin-bottom: var(--space-md); }
.sm-op-name { grid-column: 1; grid-row: 1; min-width: 0; margin: 0; font-family: var(--font-display);
  font-weight: var(--font-weight-medium); font-size: var(--font-size-title);
  line-height: var(--line-height-snug); color: var(--color-text-primary); }
.sm-endpoint { grid-column: 1; grid-row: 2; min-width: 0; font-family: var(--font-mono);
  font-size: var(--font-size-small); color: var(--color-text-secondary); overflow-wrap: anywhere; }
.sm-endpoint .m { color: var(--color-text-primary); font-weight: var(--font-weight-semibold);
  margin-right: var(--space-xs); }
.sm-endpoint .p { color: var(--color-text-subtle); }
/* The self link stays visible, not revealed on hover: a phone has no hover and a
   keyboard reaches it either way. */
.sm-op-link { grid-column: 2; grid-row: 1; padding: 0 var(--space-2xs); border-radius: var(--radius-chip);
  font-size: var(--font-size-small); color: var(--color-text-subtle); text-decoration: none; cursor: pointer; }
.sm-op-link:hover { color: var(--color-text-action); text-decoration: underline; text-underline-offset: 3px; }
.sm-op-link:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
/* A deep link, a focus move or a scroll into view lands clear of the sticky header band. */
.sm-op, .sm-ref :is(summary, pre, input, button, a) { scroll-margin-top: calc(var(--sm-band-height, 0px) + var(--space-md)); }
/* A group's heading, reached from the rail's list of the groups, lands clear of the band with its rule in view. */
.sm-ref-label .sm-microlabel[id] { scroll-margin-top: calc(var(--sm-band-height, 0px) + var(--space-xl)); }
.sm-op-main, .sm-op-code { min-width: 0; }
.sm-op-code { margin-top: var(--space-xl); }
.sm-op-main > * + *, .sm-op-code > * + * { margin-top: var(--space-lg); }
.sm-op-main > h4.sm-microlabel, .sm-op-code > h4.sm-microlabel { margin: var(--space-lg) 0 var(--space-xs); }
.sm-op-main > h4.sm-microlabel + *, .sm-op-code > h4.sm-microlabel + * { margin-top: 0; }
.sm-op-main > .sm-op-desc { margin-top: 0; }
.sm-op-scope, .sm-op-status { margin: 0; font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-op-main > .sm-op-scope { margin-top: var(--space-xs); }
.sm-op-main > .sm-op-status { margin-bottom: var(--space-xs); }
/* Each column opens flush with the head's margin, whatever its first block is. */
.sm-op-main > :first-child, .sm-op-code > :first-child,
.sm-op-main > h4.sm-microlabel:first-child, .sm-op-code > h4.sm-microlabel:first-child { margin-top: 0; }
@media (min-width: 1024px) {
  .sm-op { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 26rem);
    column-gap: var(--space-xl); align-items: start; }
  .sm-op-head { grid-column: 1 / -1; }
  /* The code column follows its operation down the page and never outgrows the window:
     it scrolls on its own in a short one. The padding and the negative margin leave a
     focus ring room inside the scroll container's clip without moving the column. */
  .sm-op-code { position: sticky; top: calc(var(--sm-band-height, 0px) + var(--space-lg));
    align-self: start; margin: calc(-1 * var(--space-2xs)); padding: var(--space-2xs);
    max-height: calc(100vh - var(--sm-band-height, 0px) - var(--space-2xl));
    overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
}
.sm-op-errors { margin: 0; padding: 0; }
.sm-op-errors > div { display: grid; grid-template-columns: 64px minmax(0, 1fr); column-gap: var(--space-sm);
  padding: var(--space-xs) 0; }
.sm-op-errors > div + div { border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-op-errors dt { font-family: var(--font-mono); font-size: var(--font-size-small);
  font-weight: var(--font-weight-semibold); color: var(--color-text-primary); margin: 0; }
.sm-op-errors dd { margin: 0; min-width: 0; }
.sm-op-errors dd p { margin: 0; font-size: var(--font-size-small); line-height: var(--line-height-body);
  color: var(--color-text-secondary); max-width: var(--size-measure-read); }

/* Attribute list: the fields of a request or a response. */
.sm-attrs { list-style: none; margin: 0; padding: 0; min-width: 0; }
.sm-attrs > li { padding: var(--space-sm) 0; min-width: 0; }
.sm-attrs > li + li { border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-attr-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-3xs) var(--space-xs); margin: 0; }
.sm-attr-name { font-family: var(--font-mono); font-size: var(--font-size-body);
  font-weight: var(--font-weight-semibold); color: var(--color-text-primary); overflow-wrap: anywhere; }
.sm-attr-type { font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-attr-req, .sm-attr-opt, .sm-attr-dep { font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps);
  font-size: var(--font-size-caption); }
.sm-attr-req { font-weight: var(--font-weight-semibold); color: var(--color-text-primary); }
.sm-attr-opt, .sm-attr-dep { color: var(--color-text-subtle); }
.sm-attr-desc { margin-top: var(--space-3xs); }
.sm-attr-enum { margin: var(--space-2xs) 0 0; font-size: var(--font-size-small); color: var(--color-text-secondary); }
details.sm-attr-more { margin-top: var(--space-2xs); }
details.sm-attr-more > summary { list-style: none; display: inline-flex; align-items: center;
  gap: var(--space-2xs); min-height: 44px; cursor: pointer; font-size: var(--font-size-small);
  font-weight: var(--font-weight-medium); color: var(--color-text-action); }
details.sm-attr-more > summary::-webkit-details-marker { display: none; }
details.sm-attr-more > summary:hover { text-decoration: underline; text-underline-offset: 3px; }
details.sm-attr-more > summary:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); border-radius: var(--radius-chip); }
details.sm-attr-more .car { display: grid; place-items: center; width: var(--size-icon-md); height: var(--size-icon-md);
  transition: transform var(--duration-base); }
details.sm-attr-more[open] > summary .car { transform: rotate(180deg); }
details.sm-attr-more .when-open, details.sm-attr-more[open] .when-closed { display: none; }
details.sm-attr-more[open] .when-open { display: inline; }
details.sm-attr-more > .sm-attrs { margin-left: var(--space-2xs); padding-left: var(--space-md);
  border-left: var(--border-width-hairline) solid var(--color-border-subtle); }

/* Code block: a figure with a head (language choice or a label, and Copy) over a
   scrolling pre. A JSON token is told apart by ink, never by hue: values in text.primary, keys and
   punctuation in text.secondary (text.subtle on the sunken ground is 4.40:1 in the light
   theme, under 4.5). */
figure.sm-code { margin: var(--space-xs) 0 0; padding: 0; min-width: 0; max-width: 100%;
  background: var(--color-surface-sunken); border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-control); overflow: hidden; }
.sm-code-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--space-xs); padding: var(--space-2xs) var(--space-xs);
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-code-head .sm-seg span { font-size: var(--font-size-small); padding: 0 var(--space-sm); }
.sm-code-head .sm-seg { max-width: 100%; flex-wrap: wrap; }
.sm-code-label { font-size: var(--font-size-small); font-weight: var(--font-weight-medium);
  color: var(--color-text-secondary); }
pre.sm-code-body { margin: 0; padding: var(--space-sm) var(--space-md); overflow-x: auto; overflow-y: auto;
  max-height: 32rem; font-family: var(--font-mono); font-size: var(--font-size-small);
  line-height: var(--line-height-snug); tab-size: 2; white-space: pre; color: var(--color-text-primary); }
pre.sm-code-body[hidden] { display: none; }
pre.sm-code-body code { font: inherit; }
pre.sm-code-body:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: calc(-1 * var(--border-width-focus)); }
.tk-key, .tk-punct { color: var(--color-text-secondary); }
.tk-string, .tk-number, .tk-literal { color: var(--color-text-primary); }

/* Try it: the real request, sent from the page. */
.sm-try { margin-top: var(--space-lg); }
.sm-try label code { font-weight: var(--font-weight-semibold); color: var(--color-text-primary); }
.sm-try > h4.sm-microlabel { margin: 0 0 var(--space-xs); }
.sm-try-fields { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-md); }
.sm-try-fields > .sm-field { max-width: none; }
.sm-try > .sm-acts { margin-top: var(--space-md); }
.sm-try-result { margin-top: var(--space-md); }
.sm-try-status { margin: var(--space-md) 0 0; font-size: var(--font-size-small);
  color: var(--color-text-secondary); font-variant-numeric: tabular-nums; }
.sm-try-status b { color: var(--color-text-primary); font-weight: var(--font-weight-semibold); }
@media (min-width: 640px) {
  .sm-try-fields:has(> .sm-field:nth-child(3)) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* The small Copy and Forget buttons are 36px; a finger needs 44. */
@media (pointer: coarse) {
  .sm-ref .sm-btn--sm { min-height: 44px; }
}
@media (prefers-reduced-motion: reduce) {
  details.sm-attr-more .car { transition: none; }
}

/* ---------- library patterns (patterns/library.md) ---------- */
/* Catalog row: one sermon, one ruled row, the WHOLE row is one link (A/C, chosen
   Phase 3). Date in the margin, Fraunces title, speaker · series beneath, passage
   in true small caps on the right. Hover washes the row and inks the title. */
a.sm-catrow { --sm-chevron: 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.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E"); position: relative; display: grid; grid-template-columns: 110px minmax(0, 1fr) auto;
  gap: var(--space-2xs) var(--space-md); align-items: baseline;
  padding: var(--space-md) calc(var(--space-sm) + var(--size-icon-md) + var(--space-xs)) var(--space-md) var(--space-sm);
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle);
  text-decoration: none; color: var(--color-text-primary); cursor: pointer;
  font-family: var(--font-body); transition: background var(--duration-fast); }
a.sm-catrow:hover { background: var(--color-action-hover-wash); }
/* The chevron (#1064): a whole row that opens a page says so at its end, the same mark on a
   catalog row and a play row. Drawn as a mask so it takes `text.subtle` and needs no markup; it
   is decoration, never a second link, and never catches a press. */
a.sm-catrow::after, .sm-playrow::after { content: ""; position: absolute; right: var(--space-sm);
  width: var(--size-icon-md); height: var(--size-icon-md); background: var(--color-text-subtle);
  -webkit-mask: var(--sm-chevron) center / contain no-repeat; mask: var(--sm-chevron) center / contain no-repeat;
  pointer-events: none; }
a.sm-catrow::after { top: 50%; margin-top: calc(var(--size-icon-md) / -2); }
a.sm-catrow .date { font-size: var(--font-size-small); color: var(--color-text-subtle);
  font-variant-numeric: tabular-nums; white-space: nowrap; }
/* The service (#1046) rides inside the date, so it goes where the date goes:
   under it in the margin, after a middle dot on its line once the row stacks.
   A long name wraps rather than pushing the margin wider. */
a.sm-catrow .svc { display: block; white-space: normal; }
a.sm-catrow .t { font-family: var(--font-display); font-size: var(--font-size-title);
  font-weight: var(--font-weight-medium); transition: color var(--duration-fast); color: var(--color-text-heading); }
a.sm-catrow:hover .t { color: var(--color-text-action); }
/* A long word or an unbroken title wraps inside its track instead of pushing the
   row wider than the screen (#1066). */
a.sm-catrow .t, a.sm-catrow .meta { min-width: 0; overflow-wrap: anywhere; }
a.sm-catrow .meta { grid-column: 2; font-size: var(--font-size-small); color: var(--color-text-secondary); }
a.sm-catrow .pass { grid-row: 1 / span 2; grid-column: 3; align-self: center;
  font-size: var(--font-size-small); font-weight: var(--font-weight-semibold);
  font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps);
  color: var(--color-text-scripture); white-space: nowrap; }
/* Flush: the same row with no margin column, for a shelf where NO row has
   anything to put there (books always; speakers at a church that writes the role
   into the name). The 110px margin is a promise of a data point, and a column of
   empty margins reads as a rendering fault rather than as an absence. Decided
   per LIST, never per row: rows in one shelf must align, so a shelf where even
   one row carries a margin keeps the column for all of them. */
a.sm-catrow--flush { grid-template-columns: minmax(0, 1fr) auto; }
a.sm-catrow--flush .meta { grid-column: 1; }
a.sm-catrow--flush .pass { grid-column: 2; }
/* The series index's year groups (#1066): a year head over its own list of flush
   rows. The head is the year in the display face with a control rule under it,
   the same weight as the sermon list's month head one step up. */
.sm-yeargroup + .sm-yeargroup { margin-top: var(--space-lg); }
.sm-yeargroup > h2 { margin: 0; padding: 0 var(--space-sm) var(--space-2xs);
  font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-heading); font-variant-numeric: oldstyle-nums;
  border-bottom: var(--border-width-hairline) solid var(--color-border-control); color: var(--color-text-heading); }
.sm-yeargroup > ul { list-style: none; margin: 0; padding: 0; }
/* Compact: the row stacks; date, title, meta, passage, all one column. */
@media (max-width: 640px) {
  a.sm-catrow { grid-template-columns: minmax(0, 1fr); gap: var(--space-3xs); }
  a.sm-catrow .svc { display: inline-block; }
  a.sm-catrow .svc::before { content: "·"; margin: 0 var(--space-xs); }
  a.sm-catrow .meta { grid-column: 1; }
  a.sm-catrow .pass { grid-row: auto; grid-column: 1; align-self: start; }
  a.sm-catrow--flush { grid-template-columns: minmax(0, 1fr); }
  a.sm-catrow--flush .pass { grid-column: 1; }
  a.sm-catrow .pass { white-space: normal; }
}

/* Featured block: this Sunday's sermon on the landing page (C, chosen Phase 3). */
.sm-featured { background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); box-shadow: var(--shadow-raised);
  padding: var(--space-xl) var(--space-xl) var(--space-lg); font-family: var(--font-body); }
.sm-featured .ft { font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-display); line-height: var(--line-height-tight);
  color: var(--color-text-heading); margin: 0 0 var(--space-2xs); }
.sm-featured .deck { font-size: var(--font-size-body); color: var(--color-text-secondary);
  margin: 0 0 var(--space-md); }
/* The passage closes the deck in brand ink, wherever the card sits: the library's
   landing, the dashboard's latest sermon, the marketing site's specimen. */
.sm-featured .deck .pass { color: var(--color-text-scripture); font-weight: var(--font-weight-semibold); }
.sm-featured .sm-player { margin-bottom: var(--space-sm); }
@media (max-width: 640px) { .sm-featured { padding: var(--space-lg) var(--space-md); } }

/* The library's lead (patterns/library.md, The sermon list; #894): the newest
   sermon set like a title page on the page's own ground, no card. One act,
   Listen, carries the length; the pinned mini-player below carries the scrub
   and the time. The summary is for wide screens: a phone leads with the title
   and the act. */
.sm-lead { display: flex; flex-direction: column; gap: var(--space-xs); font-family: var(--font-body); }
.sm-lead > .sm-microlabel { margin: 0; }
.sm-lead .kick { margin: 0; font-size: var(--font-size-small); color: var(--color-text-subtle); }
.sm-lead .kick a { color: var(--color-text-secondary); }
.sm-lead .ft { margin: 0; font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-masthead); line-height: var(--line-height-tight); text-wrap: balance; color: var(--color-text-heading); }
@media (max-width: 640px) { .sm-lead .ft { font-size: var(--font-size-display); } }
.sm-lead .ft a { color: inherit; text-decoration: none; cursor: pointer; }
.sm-lead .ft a:hover { color: var(--color-text-action); }
.sm-lead .deck { margin: 0; color: var(--color-text-secondary); }
.sm-lead .deck .pass { color: var(--color-text-scripture); font-weight: var(--font-weight-semibold); }
.sm-lead .acts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-sm) var(--space-lg);
  margin-top: var(--space-sm); }
.sm-lead .also { margin-top: var(--space-md); border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-lead .also > .sm-microlabel { margin: var(--space-sm) 0 0; }

/* The series run (patterns/library.md; #894): the latest series' parts in
   order, each with its own part number, the newest marked. A column beside the
   lead on a wide screen; on a phone the same list is a strip that scrolls
   sideways and opens on the newest part. */
.sm-run { font-family: var(--font-body); min-width: 0; }
.sm-run > .sm-microlabel { margin: 0 0 var(--space-2xs); }
.sm-run .head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-md);
  margin-bottom: var(--space-sm); }
.sm-run .head h2 { margin: 0; font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-heading); line-height: var(--line-height-snug); color: var(--color-text-heading); }
.sm-run .head h2 a { color: inherit; text-decoration: none; cursor: pointer; }
.sm-run .head h2 a:hover { color: var(--color-text-action); }
.sm-run .head .all { font-size: var(--font-size-small); white-space: nowrap; color: var(--color-text-action); }
.sm-run ol { list-style: none; margin: 0; padding: 0;
  border-top: var(--border-width-hairline) solid var(--color-border-strong); }
.sm-run li a { display: grid; grid-template-columns: 48px minmax(0, 1fr) auto; gap: 0 var(--space-sm);
  align-items: baseline; min-height: var(--size-row-min); padding: var(--space-sm) var(--space-xs);
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle);
  text-decoration: none; color: var(--color-text-primary); cursor: pointer; }
.sm-run li a:hover { background: var(--color-action-hover-wash); }
.sm-run li a:hover .t { color: var(--color-text-action); }
.sm-run .no { font-family: var(--font-display); font-size: var(--font-size-display); line-height: 1;
  font-variant-numeric: oldstyle-nums; color: var(--color-text-subtle); }
.sm-run .t { font-family: var(--font-display); font-size: var(--font-size-title); line-height: var(--line-height-snug); color: var(--color-text-heading); }
.sm-run .pass { display: block; font-size: var(--font-size-small); font-weight: var(--font-weight-semibold);
  font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps); color: var(--color-text-scripture); }
.sm-run .d { font-size: var(--font-size-small); color: var(--color-text-subtle); white-space: nowrap; }
.sm-run li[data-newest] a { background: var(--color-state-selected-bg);
  box-shadow: inset var(--border-width-marker) 0 0 var(--color-state-selected-border); }
.sm-run li[data-newest] .no { color: var(--color-text-brand); }
.sm-run li.end { display: none; }
@media (max-width: 640px) {
  .sm-run ol { display: flex; gap: var(--space-sm); overflow-x: auto; scroll-snap-type: x mandatory;
    scrollbar-width: none; border-top: 0;
    margin: 0 calc(-1 * var(--space-md)); padding: var(--space-2xs) var(--space-md) var(--space-sm);
    scroll-padding: 0 var(--space-md); }
  .sm-run ol::-webkit-scrollbar { display: none; }
  .sm-run li { flex: 0 0 72%; scroll-snap-align: start; }
  .sm-run li a { display: flex; flex-direction: column; gap: var(--space-3xs); height: 100%;
    padding: var(--space-md); border: var(--border-width-hairline) solid var(--color-border-subtle);
    border-radius: var(--radius-surface); background: var(--color-surface-card); }
  .sm-run .no { margin-bottom: var(--space-xs); }
  .sm-run .d { margin-top: auto; padding-top: var(--space-xs); }
  .sm-run li[data-newest] a { background: var(--color-surface-card); border-color: var(--color-state-selected-border);
    box-shadow: inset 0 var(--border-width-marker) 0 var(--color-state-selected-border); }
  .sm-run li.end { display: block; flex-basis: 44%; }
  .sm-run li.end a { justify-content: center; border-style: dashed; background: transparent;
    font-size: var(--font-size-small); font-weight: var(--font-weight-semibold); color: var(--color-text-action); }
}

/* A sermon's place in its series (patterns/library.md; #909). The kick names the
   part above the title, "In this series" is a chip per part with the current one
   marked, and the transcript's end offers the next part as one large link. Nothing
   here plays anything. */
.read .kick { margin: 0; font-size: var(--font-size-small); color: var(--color-text-subtle); }
.read .kick a { color: var(--color-text-secondary); }
.sm-parts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs);
  margin: var(--space-md) 0 0; font-family: var(--font-body); }
.sm-parts .lbl { font-size: var(--font-size-small); color: var(--color-text-subtle);
  margin-right: var(--space-2xs); }
.sm-parts .sm-cite { margin: 0; min-width: var(--size-control-sm); justify-content: center;
  font-variant-numeric: tabular-nums; text-decoration: none; cursor: pointer; }
.sm-parts .gap { color: var(--color-text-subtle); }
.sm-parts a[aria-current] { background: var(--color-state-selected-bg);
  border-color: var(--color-text-brand); color: var(--color-text-brand); cursor: default; }
.sm-upnext { display: flex; flex-direction: column; gap: var(--space-lg);
  margin-top: var(--space-xl); padding-top: var(--space-lg);
  border-top: var(--border-width-hairline) solid var(--color-border-strong);
  font-family: var(--font-body); }
.sm-upnext .next { display: flex; flex-direction: column; gap: var(--space-3xs);
  padding: var(--space-lg); background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-left: var(--border-width-marker) solid var(--color-text-brand);
  border-radius: var(--radius-surface); text-decoration: none;
  color: var(--color-text-primary); cursor: pointer; }
.sm-upnext .next:hover { background: var(--color-action-hover-wash); }
.sm-upnext .next:hover .t { color: var(--color-text-action); }
.sm-upnext .lbl { font-size: var(--font-size-caption); font-weight: var(--font-weight-semibold);
  letter-spacing: var(--letter-spacing-wide); text-transform: uppercase;
  color: var(--color-text-subtle); }
.sm-upnext .t { font-family: var(--font-display); font-size: var(--font-size-display);
  line-height: var(--line-height-tight); color: var(--color-text-heading); }
.sm-upnext .m { font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-upnext [data-like]:empty { display: none; }
/* Sermons like this one (#910): the nearest few by meaning, as play rows, under
   the series offer at the transcript's end (or alone there, for a sermon with no
   next part) and in now playing. The reading column is narrow, so the rows stay
   stacked at every width rather than taking the list's columns. */
.sm-like { display: flex; flex-direction: column; gap: var(--space-2xs); }
.sm-like > h2 { margin: 0; font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-heading); line-height: var(--line-height-snug); color: var(--color-text-heading); }
.sm-like > .sm-microlabel { margin: 0; }
@media (min-width: 1024px) {
  .sm-like .sm-playrow { display: flex; padding-block: var(--space-md); }
  .sm-like .sm-playrow::after { top: var(--space-md); }
  .sm-like .sm-playrow .line { display: flex; }
  .sm-like .sm-playrow .date { padding-top: 0; }
  .sm-like .sm-playrow .pass { text-align: left; }
  .sm-like .sm-playrow .pb { align-self: flex-start; margin-top: var(--space-xs); }
  .sm-like .sm-playrow .svc { display: inline-block; }
  .sm-like .sm-playrow .svc::before { content: "·"; }
}

/* Browse the library (patterns/library.md; #895, #1122): the ways into the
   archive as columns of examples. One column on a phone, three equal columns
   from breakpoint.regular. Each column opens on a hairline and names its way
   (a link to the way's own page), a line of what the examples are, up to four
   rows, and the link to all of them. A way with nothing to choose among is not
   drawn, so a column may be missing and the rest share the width. */
.sm-ways { font-family: var(--font-body); }
.sm-ways > h2 { margin: 0 0 var(--space-sm); font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-heading); line-height: var(--line-height-snug); color: var(--color-text-heading); }
.sm-ways .cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-xl); }
@media (min-width: 1024px) {
  .sm-ways .cols { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2xl); }
  .sm-ways .cols:has(.way:nth-child(2):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sm-ways .cols:has(.way:only-child) { grid-template-columns: minmax(0, 1fr); }
}
.sm-ways .way { min-width: 0; padding-top: var(--space-sm);
  border-top: var(--border-width-hairline) solid var(--color-border-strong); }
.sm-ways .way > h3 { margin: 0; font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-heading); line-height: var(--line-height-snug); color: var(--color-text-heading); }
.sm-ways .way > h3 a { color: var(--color-text-primary); text-decoration: none; cursor: pointer; }
.sm-ways .way > h3 a:hover { color: var(--color-text-action); text-decoration: underline; }
.sm-ways .sub { margin: var(--space-3xs) 0 var(--space-xs); font-size: var(--font-size-small);
  color: var(--color-text-secondary); }
.sm-ways ul { list-style: none; margin: 0; padding: 0; }
.sm-ways li + li { border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-ways li a { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-sm);
  min-height: var(--size-target-min); padding: var(--space-xs) 0; text-decoration: none;
  font-family: var(--font-display); font-size: var(--font-size-body); color: var(--color-text-heading);
  cursor: pointer; }
.sm-ways li a:hover .t { color: var(--color-text-action); text-decoration: underline; }
.sm-ways li .t { min-width: 0; overflow-wrap: anywhere; }
.sm-ways li .n { flex: none; font-family: var(--font-body); font-size: var(--font-size-small);
  color: var(--color-text-secondary); font-variant-numeric: tabular-nums; }
.sm-ways .all { margin: var(--space-xs) 0 0; }
.sm-ways .all .sm-btn--link { margin-left: calc(-1 * var(--space-2xs)); }


/* The Scripture map (#895): every book of the canon in order, by division,
   shaded by the sermons preached from it. A book never cited at all is a faint
   outline and no link: the map is the whole Bible, not only what was preached. */
.sm-canon { display: grid; grid-template-columns: 39fr 27fr; gap: var(--space-xl); font-family: var(--font-body); }
.sm-canon .tm { margin: 0 0 var(--space-sm); font-size: var(--font-size-small); font-weight: var(--font-weight-semibold);
  font-variant-caps: all-small-caps; letter-spacing: var(--letter-spacing-caps); color: var(--color-text-secondary); }
.sm-canon .div + .div { margin-top: var(--space-sm); }
.sm-canon .dl { margin: 0 0 var(--space-3xs); font-size: var(--font-size-caption); color: var(--color-text-subtle); }
.sm-canon .books { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: var(--space-3xs); }
.sm-canon .bk { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-2xs);
  min-height: var(--size-control-sm); padding: var(--space-xs); border-radius: var(--radius-chip);
  font-size: var(--font-size-small); line-height: 1.2; text-decoration: none; color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-subtle); background: var(--color-surface-card); }
.sm-canon a.bk { cursor: pointer; }
.sm-canon a.bk:hover { outline: var(--border-width-marker) solid var(--color-state-selected-border); outline-offset: 1px; }
.sm-canon .bk .n { font-size: var(--font-size-caption); font-variant-numeric: tabular-nums; }
.sm-canon .bk.is-none { color: var(--color-text-disabled); background: transparent; border-style: dashed; }
.sm-canon-key { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs) var(--space-md);
  margin: var(--space-md) 0 0; font-family: var(--font-body); font-size: var(--font-size-caption); color: var(--color-text-subtle); }
.sm-canon-key .k { color: var(--color-text-secondary); font-weight: var(--font-weight-medium); }
.sm-canon-key .kk { display: inline-flex; align-items: center; gap: var(--space-2xs); }
.sm-canon-key .sw { width: 14px; height: 14px; border-radius: 2px;
  border: var(--border-width-hairline) solid var(--color-border-subtle); }
@media (max-width: 1023px) { .sm-canon { grid-template-columns: minmax(0, 1fr); gap: var(--space-lg); } }
@media (max-width: 640px) {
  .sm-canon .books { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .sm-canon .bk { padding: var(--space-xs) var(--space-2xs) var(--space-xs) var(--space-xs); font-size: var(--font-size-caption); }
}

/* A book's chapters, shaded the same way (#895), on the book's own page. */
.sm-chapters { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); gap: var(--space-2xs); font-family: var(--font-body); }
.sm-chapters .ch { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-3xs);
  min-height: 56px; border-radius: var(--radius-chip); border: var(--border-width-hairline) solid var(--color-border-subtle);
  background: var(--color-surface-card); text-decoration: none; color: var(--color-text-primary); }
.sm-chapters a.ch { cursor: pointer; }
.sm-chapters a.ch:hover { outline: var(--border-width-marker) solid var(--color-state-selected-border); outline-offset: 1px; }
.sm-chapters .c { font-family: var(--font-display); font-size: var(--font-size-title); line-height: 1; }
.sm-chapters .n { font-size: var(--font-size-caption); font-variant-numeric: tabular-nums; }
/* The chapter the page is open on (#901): the marker rings it, as the hover ring does. */
.sm-chapters .ch[aria-current] { outline: var(--border-width-marker) solid var(--color-state-selected-border); outline-offset: 1px; }

/* A passage's own page (#935): a book, a chapter or a range, opened from the map, the index or a
   sermon's record. Under its title, a line that counts its two lists; then its chapters; then the
   same two lists as a typed passage, under the same ruled heads: preached from it first, cited
   along the way after. */
.sm-scripture-intro { margin: var(--space-xs) 0 0; color: var(--color-text-secondary); font-size: var(--font-size-small); }
.sm-scripture-all { margin: var(--space-md) 0 0; }
.sm-scripture > section + section { margin-top: var(--space-xl); }
.sm-scripture > section:first-child .grp { margin-top: 0; }

/* A typed passage's results (#901): the sermons on the left, the book's chapters in a rail on the
   right from breakpoint.regular up. Below that the rail is gone and the same chapters sit behind the
   passage's title, a disclosure (components/disclosure.md) that is closed until pressed. */
.sm-passage { display: flex; flex-direction: column; gap: var(--space-xl); }
.sm-passage .here { display: flex; flex-direction: column; gap: var(--space-xs); }
.sm-passage .here .sub { margin: 0; color: var(--color-text-secondary); font-size: var(--font-size-small); }
.sm-passage .ttl-wide { display: none; }
.sm-passage .pick .ttl, .sm-passage .ttl-wide { font-family: var(--font-display); font-size: var(--font-size-display);
  font-weight: var(--font-weight-medium); margin: 0; color: var(--color-text-heading); }
.sm-passage .grp, .sm-scripture .grp { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-sm); margin: var(--space-lg) 0 0; padding-bottom: var(--space-xs);
  border-bottom: var(--border-width-hairline) solid var(--color-border-strong); }
.sm-passage .grp h2, .sm-scripture .grp h2 { margin: 0; font-family: var(--font-display);
  font-weight: var(--font-weight-medium); font-size: var(--font-size-heading); color: var(--color-text-heading); }
.sm-passage .grp .aside, .sm-scripture .grp .aside { font-size: var(--font-size-small); color: var(--color-text-subtle); }
.sm-passage .more { margin: var(--space-md) 0 0; }
.sm-passage .rail { display: none; }
.sm-passage .rail h2 { margin: 0; font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-heading); color: var(--color-text-heading); }
.sm-passage .rail .lede { margin: 0; font-size: var(--font-size-small); color: var(--color-text-subtle); }
@media (min-width: 1024px) {
  .sm-passage { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: var(--space-3xl); align-items: start; }
  .sm-passage .rail { display: flex; flex-direction: column; gap: var(--space-md); position: sticky;
    top: var(--space-lg); padding-left: var(--space-xl);
    border-left: var(--border-width-hairline) solid var(--color-border-subtle); }
  .sm-passage .pick { display: none; }
  .sm-passage .ttl-wide { display: block; }
}

/* How often, shaded: one step per band of sermons preached, shared by the
   Scripture map's books, a book's chapters and the map's key (color.data).
   Scoped to the parts it shades, so it outranks their resting fill. */
.sm-canon .bk.is-ink-1, .sm-chapters .ch.is-ink-1, .sm-canon-key .sw.is-ink-1 {
  background: var(--color-data-step-1); color: var(--color-data-step-1-text); border-color: transparent; }
.sm-canon .bk.is-ink-2, .sm-chapters .ch.is-ink-2, .sm-canon-key .sw.is-ink-2 {
  background: var(--color-data-step-2); color: var(--color-data-step-2-text); border-color: transparent; }
.sm-canon .bk.is-ink-3, .sm-chapters .ch.is-ink-3, .sm-canon-key .sw.is-ink-3 {
  background: var(--color-data-step-3); color: var(--color-data-step-3-text); border-color: transparent; }
.sm-canon .bk.is-ink-4, .sm-chapters .ch.is-ink-4, .sm-canon-key .sw.is-ink-4 {
  background: var(--color-data-step-4); color: var(--color-data-step-4-text); border-color: transparent; }

/* Contents rows (#895): a name, a dotted leader, its plain count. */
.sm-toc { list-style: none; margin: 0; padding: 0; columns: 2 320px; column-gap: var(--space-2xl); font-family: var(--font-body); }
.sm-toc li { break-inside: avoid; }
.sm-toc a { display: flex; align-items: baseline; gap: var(--space-xs); min-height: var(--size-control-md);
  padding: var(--space-xs) 0; text-decoration: none; color: var(--color-text-primary); cursor: pointer; }
.sm-toc .t { font-family: var(--font-display); font-size: var(--font-size-body); }
.sm-toc .r { font-family: var(--font-body); font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-toc a:hover .t { color: var(--color-text-action); }
.sm-toc .lead { flex: 1; min-width: var(--space-lg); transform: translateY(-4px);
  border-bottom: var(--border-width-hairline) dotted var(--color-border-control); }
.sm-toc .n { font-size: var(--font-size-small); font-variant-numeric: tabular-nums; }

/* The Topics page's way in (patterns/library.md, The Topics page; #1272): the
   six most preached topics as tiles, and a bar of letters above the index. One
   column on a phone, two from breakpoint.compact, three from breakpoint.regular.
   A tile is one link and a card (components/card.md), so the whole of it is the
   target; the newest sermon's title inside it is text, never a second link, and
   it wraps rather than clips. The church's color does not reach this block.
   The links carry no sm- class of their own, so the system's one ring is stated
   on them here, the way .sm-contents does, and nothing in a tile clips it. A
   letter with no topic is a span: plain text, not focusable. */
.sm-topictiles { font-family: var(--font-body); }
.sm-topictiles > h2 { margin: 0 0 var(--space-sm); font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-heading); line-height: var(--line-height-snug); color: var(--color-text-heading); }
.sm-topictiles ul { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: minmax(0, 1fr); gap: var(--space-md); }
@media (min-width: 640px) {
  .sm-topictiles ul { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  .sm-topictiles ul { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.sm-topictiles li { min-width: 0; }
.sm-topictiles a { display: flex; flex-direction: column; gap: var(--space-3xs); height: 100%;
  min-height: var(--size-target-min); padding: var(--space-md); text-decoration: none;
  background: var(--color-surface-card); color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); box-shadow: var(--shadow-raised); cursor: pointer; }
.sm-topictiles a:hover { border-color: var(--color-border-strong); }
.sm-topictiles a:hover .t { color: var(--color-text-action); text-decoration: underline; }
.sm-topictiles a:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-topictiles .t { font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-title); line-height: var(--line-height-snug); overflow-wrap: anywhere; color: var(--color-text-heading); }
.sm-topictiles .n { font-family: var(--font-body); font-size: var(--font-size-small);
  color: var(--color-text-secondary); font-variant-numeric: tabular-nums; }
.sm-topictiles .latest { margin-top: var(--space-2xs); font-family: var(--font-body);
  font-size: var(--font-size-small); line-height: var(--line-height-body);
  color: var(--color-text-secondary); overflow-wrap: anywhere; }
.sm-topictiles .latest .st { font-family: var(--font-display); font-size: inherit; color: var(--color-text-secondary); }
.sm-letters { display: flex; flex-wrap: wrap; gap: var(--space-2xs); margin: 0; font-family: var(--font-body); }
.sm-letters > a, .sm-letters > span { display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--size-target-min); min-height: var(--size-target-min);
  font-size: var(--font-size-body); font-variant-numeric: tabular-nums; }
.sm-letters > a { color: var(--color-text-action); text-decoration: none; cursor: pointer; }
.sm-letters > a:hover { text-decoration: underline; }
.sm-letters > a:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-letters > span { color: var(--color-text-disabled); }
.sm-yeargroup[id^="topics-"] { scroll-margin-top: var(--space-md); }

/* A sermon that plays where it is listed (patterns/library.md, Every sermon;
   #896). The play control carries the length and sits beside the title's link,
   never inside it, so the row is two acts a reader can tell apart: read it,
   or listen to it here. One markup: stacked on a phone, columns on a wide
   screen. Rows group under their month when the list is in date order. */
.sm-month + .sm-month { margin-top: var(--space-lg); }
.sm-month > h3 { margin: 0; padding: var(--space-xs) 0; font-family: var(--font-display);
  font-weight: var(--font-weight-medium); font-size: var(--font-size-title);
  border-bottom: var(--border-width-hairline) solid var(--color-border-strong); color: var(--color-text-heading); }
.sm-playrows { list-style: none; margin: 0; padding: 0; }
.sm-playrow { --sm-chevron: 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.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");
  position: relative; display: flex; flex-direction: column; gap: var(--space-3xs); padding: var(--space-md) 0;
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle); font-family: var(--font-body); }
.sm-playrow .t { font-family: var(--font-display); font-size: var(--font-size-title); line-height: var(--line-height-snug);
  color: var(--color-text-heading); text-decoration: none; cursor: pointer; }
/* The whole row opens the sermon (#1064). The title stays the one real link, for keyboards and
   screen readers; its ::after stretches over the row, and everything else a row holds (the play,
   a series or passage link, a moment's button, "see every one", Resume and Remove) sits above it.
   The row reaches a gutter past the column so the hover wash has room, as a catalog row's does. */
.sm-playrow { margin-inline: calc(var(--space-sm) * -1);
  padding-inline: var(--space-sm) calc(var(--space-sm) + var(--size-icon-md) + var(--space-xs));
  transition: background var(--duration-fast); }
.sm-playrow:hover { background: var(--color-action-hover-wash); }
.sm-playrow::after { top: var(--space-md); }
.sm-playrow .t::after { content: ""; position: absolute; inset: 0; }
.sm-playrow:hover .t { color: var(--color-text-action); }
.sm-playrow .t:focus-visible { outline: none; }
.sm-playrow .t:focus-visible::after { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: calc(var(--border-width-focus) * -1); }
.sm-playrow :is(a, button):not(.t) { position: relative; z-index: 1; }
.sm-playrow .meta { font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-playrow .line { display: flex; flex-wrap: wrap; gap: 0 var(--space-sm); font-size: var(--font-size-small); }
.sm-playrow .date { color: var(--color-text-subtle); }
/* The service (#1046) rides inside the date: after a middle dot on the date's
   line while the row stacks, under the date in its column once it lines up. A name
   that does not fit beside the date moves to the next line whole, never split. */
.sm-playrow .svc { display: inline-block; }
.sm-playrow .svc::before { content: "·"; margin: 0 var(--space-xs); }
.sm-playrow .pass { font-weight: var(--font-weight-semibold); font-variant-caps: small-caps;
  letter-spacing: var(--letter-spacing-caps); color: var(--color-text-scripture); }
.sm-playrow .pb { align-self: flex-start; display: inline-flex; align-items: center; gap: var(--space-xs);
  min-height: var(--size-control-sm); margin-top: var(--space-xs); padding: 0 var(--space-sm) 0 var(--space-xs);
  border: var(--border-width-hairline) solid var(--color-border-control); border-radius: var(--radius-control);
  background: var(--color-surface-card); color: var(--color-text-primary); cursor: pointer;
  font-family: var(--font-body); font-size: var(--font-size-small); font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums; }
.sm-playrow .pb svg { color: var(--color-text-brand); }
.sm-playrow .pb:hover { border-color: var(--color-border-strong); background: var(--color-action-hover-wash); }
.sm-playrow .pb.is-playing { background: var(--color-action-primary); border-color: var(--color-action-primary-hover);
  color: var(--color-action-primary-text); }
.sm-playrow .pb.is-playing svg { color: inherit; }
/* The round play (#1063): the mini player's own button, icon only, on every row that
   plays a whole sermon (a play row, a search row with no moment, Up next). It is the
   page's one filled control, so it reads first; its label ("Play Title, 42 min") names
   the length, and the length itself reads in the row's meta line. While it plays the
   glyph swaps to pause (shell.tsx), the same button pressed. */
.sm-playrow .pb.pb--round, .sm-np .next .pb.pb--round { flex: none; display: inline-grid; place-items: center;
  width: var(--size-control-md); height: var(--size-control-md); min-height: 0; margin-top: 0; padding: 0;
  gap: 0; border-color: var(--color-action-primary-hover); border-radius: var(--radius-pill);
  background: var(--color-action-primary); color: var(--color-action-primary-text); }
.sm-playrow .pb.pb--round:hover, .sm-np .next .pb.pb--round:hover { background: var(--color-action-primary-hover);
  border-color: var(--color-action-primary-hover); }
.sm-playrow .pb.pb--round svg, .sm-np .next .pb.pb--round svg { color: inherit; }
/* A row with a round play puts it in a column of its own at the start, the text beside
   it (a phone; the wide grid below keeps it in its first column). */
.sm-playrow--play { display: grid; grid-template-columns: var(--size-control-md) minmax(0, 1fr);
  column-gap: var(--space-md); row-gap: var(--space-3xs); align-items: start; }
.sm-playrow--play > .pb { grid-column: 1; grid-row: 1 / span 3; }
.sm-playrow--play > :not(.pb) { grid-column: 2; }
@media (min-width: 1024px) {
  .sm-playrow { display: grid; grid-template-columns: 112px 92px minmax(0, 1fr) auto;
    grid-template-areas: "pb date t pass" "pb date meta pass"; column-gap: var(--space-md); row-gap: var(--space-3xs);
    align-items: start; padding-block: var(--space-sm); }
  .sm-playrow::after { top: var(--space-sm); }
  .sm-playrow .t { grid-area: t; }
  .sm-playrow .meta { grid-area: meta; }
  .sm-playrow .line { display: contents; }
  .sm-playrow .date { grid-area: date; padding-top: var(--space-2xs); }
  /* Every column starts on the title's first line (#935): the play, the date
     and the passage read across with it, never centered on a taller row. */
  .sm-playrow .pass { grid-area: pass; padding-top: var(--space-2xs); text-align: right; }
  .sm-playrow .pb { grid-area: pb; margin-top: 0; }
  /* A list whose rows name their service widens the date column to hold the
     name under the date, decided per list so its rows still align; a church
     with one service keeps the narrow column. Without :has() the name wraps. */
  .sm-playrows:has(.svc) > .sm-playrow { grid-template-columns: 112px 128px minmax(0, 1fr) auto; }
  /* The round play's column is the button's own width, the row's usual gap after it (#1063). */
  .sm-playrow--play { grid-template-columns: var(--size-control-md) 92px minmax(0, 1fr) auto; }
  .sm-playrows:has(.svc) > .sm-playrow--play { grid-template-columns: var(--size-control-md) 128px minmax(0, 1fr) auto; }
  .sm-playrow .svc { display: block; }
  .sm-playrow .svc::before { content: none; }
}

/* Continue listening (#907): the unfinished sermons this device remembers, on the
   front page above the lead. The island draws it into an empty slot; the rows are
   play rows (sm-playrow) with a Resume button where the length would be, so they
   take the church's color the same way. */
.sm-continue .head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-md);
  border-bottom: var(--border-width-hairline) solid var(--color-border-strong); padding-bottom: var(--space-2xs); }
.sm-continue .head .sm-microlabel { margin: 0; }
.sm-continue .note { margin: 0; padding: var(--space-xs) 0; display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-2xs) var(--space-sm); font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-continue .note:empty { display: none; }
/* Resume and Remove sit together after the meta line: the remove is the quiet
   edged square the Resume button's height, an x and nothing else. */
.sm-playrow--resume .acts { display: flex; align-items: center; gap: var(--space-xs); }
.sm-playrow--resume .acts .pb { margin-top: 0; }
.sm-playrow--resume .rm { flex: none; display: grid; place-items: center; width: var(--size-control-sm);
  height: var(--size-control-sm); padding: 0; cursor: pointer; background: var(--color-surface-card);
  color: var(--color-text-subtle); border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-control); }
/* The box stays Resume's height; the hit area is the 48px target, reaching into
   the gap and the row's padding without growing the row or touching Resume. */
.sm-playrow--resume .rm { position: relative; }
/* Continue listening keeps the stretched link (the row opens the sermon) but draws no chevron
   (#1064): its end holds Remove, and a mark there would sit under the button. */
.sm-playrow--resume { padding-inline-end: var(--space-sm); }
.sm-playrow--resume::after { content: none; }
.sm-playrow--resume .rm::after { content: ""; position: absolute;
  inset: calc((var(--size-control-sm) - var(--size-control-md)) / 2 - var(--border-width-hairline)); }
.sm-playrow--resume .rm:hover { border-color: var(--color-border-strong); background: var(--color-action-hover-wash);
  color: var(--color-text-primary); }
@media (min-width: 1024px) {
  .sm-playrow--resume { grid-template-columns: 112px minmax(0, 1fr) auto; grid-template-areas: none; }
  .sm-playrow--resume .acts { display: contents; }
  .sm-playrow--resume .pb { grid-area: 1 / 1 / span 2 / 2; }
  .sm-playrow--resume .rm { grid-area: 1 / 3 / span 2 / 4; align-self: center; }
  .sm-playrow--resume .t { grid-area: 1 / 2; }
  .sm-playrow--resume .meta { grid-area: 2 / 2; }
}
/* The sermon page's resume line: one quiet sentence and a button, in the flow
   above the transcript. Shipped hidden and empty; the island fills it. */
.sm-resume { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2xs) var(--space-sm);
  margin: 0; font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-resume[hidden] { display: none; }

/* Where a search found it (#900). A result carries the words around the match,
   the matched words in the warm wash over a brand rule, two lines at most. The
   same mark is what the sermon page lays over its transcript, and the current
   one fills. A title-only match carries no snippet: the row stays as it was. */
.sm-playrow .snip { margin: var(--space-2xs) 0 0; font-family: var(--font-display);
  font-size: var(--font-size-body); line-height: var(--line-height-snug); color: var(--color-text-secondary);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
.sm-playrow .snip mark, .sm-tp mark { background: var(--color-state-selected-bg); color: inherit;
  padding: 0 1px; border-radius: var(--radius-chip);
  box-shadow: inset 0 calc(-1 * var(--border-width-marker)) 0 var(--color-text-brand); }
.sm-tp mark.is-here { background: var(--color-text-brand); color: var(--color-surface-card); box-shadow: none; }
/* A search's rows (#935): one column at every width, read top to bottom. The title; one line of
   facts (the date, the series and speaker, the passage) apart by middle dots; the words around the
   match; then a line of acts holding the row's one play button. With a moment (#906), the best
   matching paragraph once the index is built, the button plays from where the sermon says it
   ("▶ 22:46"), and beside it the line says how many places say it, with a link to the page that
   marks every one. Without one it plays from the start ("▶ 44 min"). One button a row: a moment's
   time beside the words and the length at the row's edge read as two ways to do one thing. */
.sm-playrow--found { gap: var(--space-2xs); }
.sm-playrow--found .line { align-items: baseline; column-gap: 0; color: var(--color-text-secondary); }
/* The dot trails each fact, so a line that wraps ends on one rather than opening with it. */
.sm-playrow--found .line > :not(:last-child)::after { content: "·"; margin: 0 var(--space-xs);
  color: var(--color-text-subtle); font-weight: var(--font-weight-regular); font-variant-caps: normal;
  letter-spacing: normal; }
.sm-playrow--found .meta { font-size: inherit; }
.sm-playrow--found .snip { margin: var(--space-3xs) 0 0; }
.sm-playrow .moment { display: flex; flex-direction: column; gap: 0; }
.sm-playrow--found .acts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2xs) var(--space-sm);
  margin-top: var(--space-xs); }
.sm-playrow--found .acts .pb { margin-top: 0; }
.sm-playrow .mb { display: inline-flex; align-items: center; gap: var(--space-xs);
  min-height: var(--size-control-sm); padding: 0 var(--space-sm) 0 var(--space-xs);
  border: var(--border-width-hairline) solid var(--color-border-control); border-radius: var(--radius-control);
  background: var(--color-surface-card); color: var(--color-text-primary); cursor: pointer;
  font-family: var(--font-body); font-size: var(--font-size-small); font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums; }
.sm-playrow .mb svg { color: var(--color-text-brand); }
.sm-playrow .mb:hover { border-color: var(--color-border-strong); background: var(--color-action-hover-wash); }
.sm-playrow .mb.is-playing { background: var(--color-action-primary); border-color: var(--color-action-primary-hover);
  color: var(--color-action-primary-text); }
.sm-playrow .mb.is-playing svg { color: inherit; }
.sm-playrow .count { font-size: var(--font-size-small); color: var(--color-text-subtle); }
.sm-playrow .count a { color: var(--color-text-action); text-decoration: underline; cursor: pointer; }
@media (min-width: 1024px) {
  .sm-playrow--found { display: flex; padding-block: var(--space-md); }
  .sm-playrow--found::after { top: var(--space-md); }
  .sm-playrow--found .line { display: flex; }
  .sm-playrow--found .date { padding-top: 0; }
  .sm-playrow--found .pass { text-align: left; }
  .sm-playrow--found .svc { display: inline-block; }
  .sm-playrow--found .svc::before { content: "·"; }
}

/* ---------- library search (components/combobox.md, "Search suggestions") ---------- */
/* The library's search field, pinned under the header as the page scrolls (patterns/layout.md,
   "What may stick"), with suggestions grouped by kind in a popup under it. The header scrolls
   away, so the field holds the top edge and the pinned mini-player holds the bottom one; they
   never meet. Without script it is the plain GET form it was, still pinned. */
.sm-find { position: sticky; top: 0; z-index: var(--z-rail);
  background: var(--color-surface-page); padding: var(--space-xs) 0; }
/* The field and, outside it on its right, the Search button at the field's height (#925). The
   suggestions are placed in the field's own column, so they open under the field, not the button. */
.sm-find form { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto;
  column-gap: var(--space-xs); align-items: stretch; }
/* The field: a magnifier and the words in one frame. The frame (not the input) carries the
   border, so focus rings the whole. */
.sm-find .box { grid-column: 1; grid-row: 1; display: flex; align-items: center; gap: var(--space-xs);
  min-height: var(--size-control-md); padding: 0 var(--space-sm);
  background: var(--color-surface-card); border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-control); transition: border-color var(--duration-fast); }
.sm-find .box:hover { border-color: var(--color-border-strong); }
.sm-find .box:focus-within { border-color: var(--color-border-focus); }
.sm-find .ic { display: flex; flex: none; color: var(--color-text-subtle); }
.sm-find .go { grid-column: 2; grid-row: 1; }
.sm-find .box .sm-well { flex: 1; min-width: 0; width: auto; min-height: 0; height: var(--size-control-sm);
  padding: 0; border: 0; border-radius: 0; background: transparent; }
.sm-find .top { display: none; }
/* Both lines named: on a positioned grid child an `auto` end line is the form's edge. */
.sm-find .pop { grid-column: 1 / 2; grid-row: 1 / 2; position: absolute; left: 0; right: 0; top: calc(100% + var(--space-3xs));
  z-index: var(--z-popover); max-height: 70vh; overflow-y: auto;
  background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-control); box-shadow: var(--shadow-overlay);
  padding: var(--space-xs) 0; }
.sm-find .pop[hidden] { display: none; }
.sm-find .g { margin: var(--space-sm) var(--space-lg) var(--space-3xs); }
.sm-find .opt { display: flex; align-items: center; gap: var(--space-sm); cursor: pointer;
  min-height: var(--size-control-md); padding: var(--space-2xs) var(--space-lg);
  text-decoration: none; color: var(--color-text-primary); }
.sm-find .opt:hover { background: var(--color-action-hover-wash); }
.sm-find .opt[aria-selected="true"] { background: var(--color-state-selected-bg); }
.sm-find .nm { font-family: var(--font-display); font-size: var(--font-size-title); }
.sm-find .nm b { font-weight: inherit; color: var(--color-text-primary); }
.sm-find .nm .rest { color: var(--color-text-subtle); }
.sm-find .type { display: block; font-family: var(--font-body); font-size: var(--font-size-caption);
  color: var(--color-text-subtle); }
.sm-find .words .nm { font-family: var(--font-body); font-size: var(--font-size-body); }
.sm-find .none { margin: var(--space-sm) var(--space-lg); font-size: var(--font-size-small);
  color: var(--color-text-secondary); }
.sm-find .foot { padding: var(--space-xs) var(--space-lg) var(--space-2xs); }
.sm-find .foot:empty { display: none; }
/* On a phone, focusing the field opens the search full screen (a pattern of its own: the
   page behind has nothing to say while someone is searching). The page's scroll is held, and the
   page is inert, only while the overlay is: it exists on a phone only, so the lock does too. */
@media (max-width: 640px) {
  html[data-suggest-lock] { overflow: hidden; }
  .sm-find[data-suggest-open] { position: fixed; inset: 0; z-index: var(--z-modal); overflow-y: auto;
    padding: var(--space-xs) var(--space-md) var(--space-xl); }
  .sm-find[data-suggest-open] .top { display: flex; align-items: center; justify-content: space-between;
    min-height: var(--size-control-md); }
  .sm-find[data-suggest-open] .pop { grid-column: 1 / -1; grid-row: 2; position: static; max-height: none; box-shadow: none;
    border: 0; border-radius: 0; background: none; margin-top: var(--space-xs); padding: 0; }
  .sm-find[data-suggest-open] .g { margin-inline: 0; }
  .sm-find[data-suggest-open] .opt, .sm-find[data-suggest-open] .none, .sm-find[data-suggest-open] .foot {
    padding-inline: 0; margin-inline: 0; }
}

/* ---------- results by kind and Narrow (components/tabs.md, components/sheet.md) ---------- */
/* A count beside a tab's label: quiet, and the same weight as the label so nothing reflows. */
.sm-tabs a .c { margin-left: var(--space-2xs); color: var(--color-text-subtle);
  font-size: var(--font-size-small); font-variant-numeric: tabular-nums; }
.sm-tabs a[aria-current] .c { color: inherit; }
/* The results and, beside them on a wide screen, Narrow. */
.sm-results { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: var(--space-3xl);
  align-items: start; }
.sm-results > .stack { min-width: 0; }
/* It scrolls with the page, whole: a panel that sticks and scrolls inside itself hides its
   own lower sections behind a clip with no cue that more follows. */
.sm-side { display: flex; flex-direction: column; gap: var(--space-lg);
  padding-left: var(--space-xl);
  border-left: var(--border-width-hairline) solid var(--color-border-subtle);
  font-family: var(--font-body); }
.sm-side h2, .sm-sheet .title { margin: 0; font-family: var(--font-display);
  font-weight: var(--font-weight-medium); font-size: var(--font-size-heading); color: var(--color-text-heading); }
.sm-narrowbtn { display: none; }
/* What one filter says: its name, and what it is set to. */
.sm-side .fh, .sm-sheet .fh { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-sm); margin-bottom: var(--space-2xs); font-size: var(--font-size-small); }
.sm-side .fh b, .sm-sheet .fh b { font-weight: var(--font-weight-semibold); color: var(--color-text-primary); }
.sm-side .fh span, .sm-sheet .fh span { color: var(--color-text-subtle); }
.sm-side .fh .set, .sm-sheet .fh .set { color: var(--color-text-brand); }
/* Years, a bar each as tall as its count: every bar a link that sets the year. */
.sm-hist { display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr)); gap: 3px;
  align-items: end; height: 72px; }
.sm-hist a, .sm-hist span { display: flex; flex-direction: column; justify-content: flex-end;
  height: 100%; cursor: pointer; }
.sm-hist span { cursor: default; }
.sm-hist i { display: block; height: var(--h); min-height: 3px; border-radius: 2px 2px 0 0;
  background: var(--color-border-strong); }
.sm-hist a:hover i { background: var(--color-text-action); }
.sm-hist a[aria-current] i { background: var(--color-state-selected-border); }
.sm-hist a:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-hist-x { display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr)); gap: 3px;
  margin-top: var(--space-3xs); font-size: var(--font-size-caption); color: var(--color-text-subtle);
  text-align: center; font-variant-numeric: tabular-nums; }
/* A filter's options: a ruled list, each a link, with the count it would show. */
.sm-narrowlist { list-style: none; margin: 0; padding: 0; }
.sm-narrowlist a { display: flex; align-items: center; gap: var(--space-sm);
  min-height: var(--size-control-sm); font-size: var(--font-size-small);
  color: var(--color-text-primary); text-decoration: none; cursor: pointer;
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-narrowlist li:last-child a { border-bottom: 0; }
.sm-narrowlist a:hover { color: var(--color-text-action); }
.sm-narrowlist a:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: calc(-1 * var(--size-focus-offset)); }
.sm-narrowlist .mk { flex: none; width: var(--size-icon-sm); height: var(--size-icon-sm);
  box-sizing: border-box; border-radius: var(--radius-pill);
  border: var(--border-width-hairline) solid var(--color-border-control); }
.sm-narrowlist a[aria-current] .mk { border-width: 5px; border-color: var(--color-state-selected-border); }
.sm-narrowlist .c { margin-left: auto; color: var(--color-text-subtle);
  font-variant-numeric: tabular-nums; }
.sm-side .more summary, .sm-sheet .more summary { min-height: var(--size-control-sm);
  display: flex; align-items: center; cursor: pointer; font-size: var(--font-size-small);
  color: var(--color-text-secondary); }
.sm-side .clear, .sm-sheet .clear { font-size: var(--font-size-small); }
/* On a phone Narrow is a button above the results, and the sheet carries its content.
   Until the sheet island has run (no script) the panel stays in the page instead. */
@media (max-width: 640px) {
  .sm-results { grid-template-columns: minmax(0, 1fr); gap: var(--space-lg); }
  .sm-side { position: static; max-height: none; overflow: visible; padding-left: 0;
    border-left: 0; border-top: var(--border-width-hairline) solid var(--color-border-subtle);
    padding-top: var(--space-lg); }
  html[data-sheet] .sm-side { display: none; }
  html[data-sheet] .sm-narrowbtn { display: inline-flex; align-self: flex-start; }
}

/* ---------- sheet (components/sheet.md) ---------- */
/* A native <dialog> opened with showModal(), laid out as a sheet rising from the bottom edge.
   Never rendered pre-opened (a non-modal open dialog is positioned by the UA). Its head and
   foot hold still while the body scrolls. */
dialog.sm-sheet { margin: auto 0 0; padding: 0; width: 100%; max-width: none;
  max-height: 85vh; box-sizing: border-box; overflow: hidden;
  background: var(--color-surface-card); color: var(--color-text-primary);
  border: 0; border-top: var(--border-width-hairline) solid var(--color-border-strong);
  border-radius: var(--radius-surface) var(--radius-surface) 0 0;
  box-shadow: var(--shadow-overlay); font-family: var(--font-body); }
dialog.sm-sheet[open] { display: flex; flex-direction: column; }
dialog.sm-sheet::backdrop { background: var(--color-surface-scrim); }
.sm-sheet .head { flex: none; display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-sm); padding: var(--space-sm) var(--space-md);
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-sheet .body { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: var(--space-lg); padding: var(--space-md); }
.sm-sheet .foot { flex: none; display: flex; gap: var(--space-xs); justify-content: flex-end;
  padding: var(--space-sm) var(--space-md);
  border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-sheet .foot .sm-btn--primary { flex: 1 1 auto; }
html:has(dialog.sm-sheet[open]) { overflow: hidden; }
@media (prefers-reduced-motion: no-preference) {
  dialog.sm-sheet[open] { animation: sm-sheet-in var(--duration-base) ease-out; }
  @keyframes sm-sheet-in { from { transform: translateY(8%); opacity: 0; } }
}
@media (min-width: 641px) { dialog.sm-sheet, dialog.sm-sheet[open] { display: none; } }

/* Pinned mini-player: holds the sermon page's bottom edge while reading (chosen
   Phase 3). Positioned, never animated; the sm-page-level exception in
   patterns/layout.md "What may stick". */
.sm-minibar { position: sticky; bottom: 0; z-index: 4;
  display: flex; align-items: center; gap: var(--space-sm);
  background: var(--color-surface-card);
  border-top: var(--border-width-hairline) solid var(--color-border-strong);
  padding: var(--space-xs) var(--space-lg); font-family: var(--font-body); }
.sm-minibar .sm-player { flex: 1 1 auto; min-width: 0; }
.sm-minibar .sm-player .play { width: var(--size-control-sm); height: var(--size-control-sm); }
/* Skip (#933): a soft pill whose label is its seconds beside the arrow, the name
   saying which way ("Back 15 seconds"). No drawn edge (the owner's choice,
   components/player.md): the player's transport sits on surface.sunken so the
   filled play stays its one weighted act. The bar carries back 15 alone, at the
   play control's height, on every width. */
.sm-skip { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2xs);
  min-height: var(--size-control-md); padding: 0 var(--space-md); border: 0;
  border-radius: var(--radius-pill); cursor: pointer;
  background: var(--color-surface-sunken); color: var(--color-text-primary);
  font-family: var(--font-body); font-size: var(--font-size-body); font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  transition: background var(--duration-fast), transform var(--duration-fast); }
.sm-skip:hover { background: linear-gradient(var(--color-action-hover-wash), var(--color-action-hover-wash)),
  var(--color-surface-sunken); }
.sm-skip:active { transform: translateY(1px); }
.sm-skip svg { flex: none; }
.sm-skip--sm { min-height: var(--size-control-sm); padding: 0 var(--space-sm); font-size: var(--font-size-small); }
.sm-minibar .sm-skip { flex: none; }
/* The way back (components/transcript.md, Following): shown only while the sermon
   plays with the current paragraph off screen. A real bordered button, not a link:
   it is an act, and church staff read a borderless act as broken (button.md). */
.sm-minibar .follow { flex: none; }
/* Stepping through a search's matches on the sermon page (#900): previous, "2 of 69",
   next, announced politely. Shipped hidden; shown when the transcript has a match. */
.sm-minibar .sm-matches { display: inline-flex; align-items: center; gap: var(--space-2xs); flex: none;
  font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-minibar .sm-matches .sm-btn { min-width: var(--size-control-sm); padding: 0 var(--space-xs); }
.sm-minibar .sm-matches .n { min-width: 5em; text-align: center; font-variant-numeric: tabular-nums; }
.sm-minibar .who { font-size: var(--font-size-caption); color: var(--color-text-subtle);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 220px; }
/* The loaded sermon's links in the bar (components/player.md, "Place in a series
   and what is up next"; #909): its title as a link with the part beneath it,
   Transcript, and Up next. All ship hidden and are filled when a sermon loads.
   The title link stands in for the plain title while it shows. When the sermon
   ends the bar carries data-ended and Up next takes the emphasis. */
.sm-minibar [data-bar-title] { display: flex; flex-direction: column; flex: 0 1 auto; min-width: 0;
  max-width: 220px; font-size: var(--font-size-caption); line-height: var(--line-height-snug);
  color: var(--color-text-secondary); text-decoration: none; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sm-minibar [data-bar-title]:hover { color: var(--color-text-action); text-decoration: underline; }
.sm-minibar [data-bar-title] [data-bar-part] { color: var(--color-text-subtle); }
.sm-minibar [data-bar-title]:not([hidden]) ~ .who,
.sm-minibar:has([data-bar-title]:not([hidden])) .who { display: none; }
.sm-minibar [data-bar-transcript],
.sm-minibar [data-bar-next] { flex: none; display: inline-flex; align-items: center;
  min-height: var(--size-control-sm); padding: 0 var(--space-xs); border-radius: var(--radius-control);
  font-size: var(--font-size-small); font-weight: var(--font-weight-semibold);
  color: var(--color-text-action); text-decoration: none; cursor: pointer; }
.sm-minibar [data-bar-transcript]:hover,
.sm-minibar [data-bar-next]:hover { background: var(--color-action-hover-wash); text-decoration: underline; }
.sm-minibar [data-bar-next] { max-width: 260px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sm-minibar[data-ended] [data-bar-next] { background: var(--color-state-selected-bg);
  box-shadow: inset 0 0 0 var(--border-width-marker) var(--color-state-selected-border); }
.sm-minibar [hidden] { display: none; }
/* Transcript is the way back where the bar shows it (#919): it turns to the paragraph playing
   now, so the page's Follow along is not offered beside it. Under compact the bar carries Now
   playing instead of Transcript, and Follow along stays. */
@media (min-width: 641px) {
  .sm-minibar:has([data-bar-transcript]:not([hidden])) .follow { display: none; } }
/* On a phone the bar is a podcast app's mini player (#939): play, and the Now
   playing block naming the sermon, which opens the view that holds the scrub.
   A 360px row cannot hold a scrub worth dragging beside anything else (33px at
   best, measured, and nothing at 320px), so the scrub, the time and Up next,
   all of which now playing carries, leave the row, and how far the sermon
   has played is a line along the bar's top edge (--sm-played, from the bar
   island). */
@media (max-width: 640px) { .sm-minibar { padding: var(--space-xs) var(--space-md); }
  .sm-minibar .who { display: none; }
  .sm-minibar [data-bar-title], .sm-minibar [data-bar-transcript], .sm-minibar [data-bar-next],
  .sm-minibar .sm-scrub, .sm-minibar .time { display: none; }
  .sm-minibar .sm-player { flex: none; }
  .sm-minibar[data-loaded]::before { content: ""; position: absolute; left: 0;
    top: calc(-1 * var(--border-width-hairline)); height: var(--border-width-marker);
    width: var(--sm-played, 0%); background: var(--color-action-primary); pointer-events: none; }
  /* The block takes the row. A sermon's title is content, not a verb, so unlike a
     button's label it gives way to an ellipsis: the whole title is one tap away. */
  .sm-minibar [data-now-open] { flex: 1 1 auto; min-width: 0; justify-content: space-between;
    padding: 0 var(--space-xs) 0 var(--space-sm); text-align: start;
    background: var(--color-surface-sunken); }
  .sm-minibar [data-now-open]:hover { background: linear-gradient(var(--color-action-hover-wash),
    var(--color-action-hover-wash)), var(--color-surface-sunken); }
  .sm-minibar [data-now-open] .words { display: flex; flex-direction: column; min-width: 0;
    line-height: var(--line-height-tight); }
  .sm-minibar [data-now-open] .t,
  .sm-minibar [data-now-open] .m { overflow: hidden; text-overflow: ellipsis; }
  .sm-minibar [data-now-open] .m { font-size: var(--font-size-caption); font-weight: var(--font-weight-regular);
    color: var(--color-text-subtle); font-variant-numeric: tabular-nums; }
  /* The page's own act (Follow along, the search stepper) takes the room while it
     is offered; the block keeps its chevron and its name. */
  .sm-minibar:has(.follow:not([hidden])) [data-now-open],
  .sm-minibar:has(.sm-matches:not([hidden])) [data-now-open] { flex: none; justify-content: center;
    width: var(--size-control-sm); padding: 0; }
  .sm-minibar:has(.follow:not([hidden])) [data-now-open] .words,
  .sm-minibar:has(.sm-matches:not([hidden])) [data-now-open] .words { display: none; }
  /* A sermon in a series that has ended: Up next takes the block's place, and the
     emphasis (components/player.md). */
  .sm-minibar[data-ended] [data-bar-next]:not([hidden]) { display: inline-flex; flex: 1 1 auto;
    min-width: 0; max-width: none; }
  .sm-minibar[data-ended]:has([data-bar-next]:not([hidden])) [data-now-open] { display: none; } }

/* Now playing, on a phone (components/player.md; #908): the bar's Now playing
   button opens this whole-screen dialog. Its body is the sermon's title with its
   place in the series, the whole transcript, and Up next; its controls are one more
   view of the one engine. It never shows on a wide screen, where the sermon page is the transcript. */
.sm-minibar [data-now-open] { display: none; }
.sm-np { position: fixed; inset: 0; width: 100%; max-width: none; height: 100dvh;
  max-height: none; margin: 0; padding: 0; border: 0; box-sizing: border-box;
  background: var(--color-surface-page); color: var(--color-text-primary);
  font-family: var(--font-body); }
.sm-np[open] { display: flex; flex-direction: column; }
.sm-np .bar { display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md); background: var(--color-surface-card);
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-np .bar .sm-microlabel { margin: 0; }
.sm-np .body { flex: 1 1 auto; overflow-y: auto; display: flex; flex-direction: column;
  gap: var(--space-md); padding: var(--space-lg) var(--space-md) var(--space-md); }
.sm-np .kick { margin: 0; font-size: var(--font-size-small); color: var(--color-text-subtle); }
.sm-np .kick a { color: var(--color-text-secondary); }
.sm-np h2 { margin: 0; font-family: var(--font-display); font-weight: var(--font-weight-medium);
  font-size: var(--font-size-display); line-height: var(--line-height-tight); color: var(--color-text-heading); }
.sm-np .deck { margin: 0; font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-np .read { display: flex; flex-direction: column; gap: var(--space-sm); padding: var(--space-md) 0;
  border-top: var(--border-width-hairline) solid var(--color-border-subtle);
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-np .read .hd { display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm); }
.sm-np .read .hd .sm-microlabel { margin: 0; }
/* The whole transcript, scrolling inside the view's own body (#1062); the page's
   own rules turn it (components/transcript.md, "Following"). The way back holds
   at the bottom of that scroll and is offered only while the playing paragraph is
   off screen. */
.sm-np .read .follow { position: sticky; bottom: var(--space-sm); align-self: center;
  background: var(--color-surface-card); border-color: var(--color-border-strong);
  box-shadow: var(--shadow-overlay); }
.sm-np .next { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  gap: var(--space-3xs) var(--space-sm); padding: var(--space-sm) var(--space-md);
  background: var(--color-surface-card); border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); }
.sm-np .next .m { grid-column: 1; font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-np .next .pb--round { grid-column: 2; grid-row: 2 / span 2; align-self: center; }
.sm-np .next .lbl { grid-column: 1 / -1; font-size: var(--font-size-caption); color: var(--color-text-subtle); }
.sm-np .next .t { font-family: var(--font-display); font-size: var(--font-size-title);
  line-height: var(--line-height-snug); color: var(--color-text-heading); cursor: pointer; }
.sm-np [data-like]:empty { display: none; }
.sm-np .ctl { flex: none; display: flex; flex-direction: column; align-items: stretch; gap: var(--space-xs);
  padding: var(--space-md) var(--space-md) calc(var(--space-md) + env(safe-area-inset-bottom));
  background: var(--color-surface-card); border-top: var(--border-width-hairline) solid var(--color-border-strong); }
.sm-np .ctl .sm-scrub { width: 100%; }
/* Elapsed, the speed, remaining: the speed centered under the scrub whatever the
   two times' widths. */
.sm-np .ctl .times { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: var(--space-sm); font-size: var(--font-size-caption);
  color: var(--color-text-subtle); font-variant-numeric: tabular-nums; }
.sm-np .ctl .times [data-now-left] { justify-self: end; }
/* The speed (#933): a select on the transport's soft pill, no drawn edge, showing
   only its value; keyboard focus keeps the system's ring. */
.sm-np .ctl .times .sm-selectw { width: auto; }
.sm-np .ctl .times .sm-select { width: auto; min-height: var(--size-control-sm);
  padding: 0 var(--space-xl) 0 var(--space-sm); border-color: transparent;
  border-radius: var(--radius-pill); background: var(--color-surface-sunken);
  font-size: var(--font-size-small); font-weight: var(--font-weight-semibold); }
.sm-np .ctl .times .sm-select:hover { border-color: transparent;
  background: linear-gradient(var(--color-action-hover-wash), var(--color-action-hover-wash)),
  var(--color-surface-sunken); }
.sm-np .ctl .times .car { right: var(--space-2xs); color: var(--color-text-secondary); }
.sm-np .ctl .play { align-self: center; width: 64px; height: 64px; margin-top: var(--space-2xs); }
/* Back, play, forward (#933): play stays centered whatever the two skips' widths. */
.sm-np .ctl .keys { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: var(--space-lg); }
.sm-np .ctl .keys .sm-skip:first-child { justify-self: end; }
.sm-np .ctl .keys .sm-skip:last-child { justify-self: start; }
@media (max-width: 640px) { .sm-minibar[data-loaded] [data-now-open] { display: inline-flex; } }
/* The dialog's own toast region (the page's is behind the modal): under its bar. */
.sm-np .sm-toasts { top: calc(var(--size-control-sm) + 2 * var(--space-md)); bottom: auto; }
@media (min-width: 641px) { .sm-np { display: none; } }

/* ---------- dashboard patterns (patterns/dashboard.md) ---------- */
/* Waiting-on-you row (D1, chosen Phase 3): title, one plain-language sentence, a
   status chip, ONE quiet act; never a button cluster (a pipeline flag surfaces
   here only when the tenant must decide). */
.sm-qrow { display: flex; align-items: center; gap: var(--space-md); flex-wrap: wrap;
  background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); padding: var(--space-sm) var(--space-md);
  font-family: var(--font-body); }
.sm-qrow .grow { flex: 1; min-width: 240px; }
.sm-qrow .t { font-family: var(--font-display); font-size: var(--font-size-title);
  font-weight: var(--font-weight-medium); color: var(--color-text-heading); }
.sm-qrow .m { font-size: var(--font-size-small); color: var(--color-text-secondary); }
/* A row's title opens the sermon's page, as the featured title does (#1116). */
.sm-qrow .t a { color: inherit; text-decoration: none; transition: color var(--duration-fast); }
.sm-qrow .t a:hover { color: var(--color-text-action); }
/* A sermon's service over its title (#557, #1046): the latest day's rows and Waiting on you. */
.sm-qrow .sm-microlabel { margin: 0 0 var(--space-3xs); }
/* A row its drag handle moves (components/grip.md, Motion). It glides to its place on the
   motion tokens: the island sets only where. Its lift is the overlay shadow on a layer of
   its own, which fades in when the row is taken and out once it has landed, so only
   opacity and transform ever animate. While held it follows the pointer exactly, so its
   own transform does not ease. */
.sm-qrow:has(> .sm-gripslot) { position: relative;
  transition: transform var(--duration-base) cubic-bezier(var(--easing-standard)); }
.sm-qrow:has(> .sm-gripslot)::after { content: ""; position: absolute;
  inset: calc(-1 * var(--border-width-hairline)); border-radius: inherit;
  box-shadow: var(--shadow-overlay); opacity: 0; pointer-events: none;
  transition: opacity var(--duration-fast) cubic-bezier(var(--easing-standard)); }
.sm-qrow.is-dragging, .sm-qrow.is-settling { position: relative; z-index: 1;
  border-color: var(--color-border-control); }
.sm-qrow.is-dragging { transition: none; cursor: grabbing; }
.sm-qrow.is-dragging::after, .sm-qrow.is-settling::after { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .sm-qrow:has(> .sm-gripslot)::after { transition: none; } }

/* The studio (patterns/social.md, The studio; patterns/dashboard.md, Studio; #1136): where a
   published sermon's graphics are made and shared. Three columns from breakpoint.regular
   (the set, the stage, the choices), one stack below it (the stage, the choices, the set).
   The island sets data-studio-state: without script, or where the browser cannot draw, the
   grid is not shown and the captions, which are the page's content, are; and data-studio-size
   on the studio, so the stage's box and the thumbnails take the ratio of the picture on
   screen, and nothing reflows when a new one arrives. */
.sm-studio { margin-top: var(--space-lg); }
.sm-studio > .hint { margin: 0 0 var(--space-md); font-size: var(--font-size-small);
  color: var(--color-text-subtle); }
.sm-studio-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-lg); }
.sm-studio:not([data-studio-state="on"]) .sm-studio-grid { display: none; }
.sm-studio[data-studio-state="on"] .sm-studio-captions { display: none; }
.sm-studio-captions > ul { list-style: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: var(--space-lg); max-width: var(--size-measure-read); }
.sm-studio-captions li { display: flex; flex-direction: column; gap: var(--space-xs); align-items: flex-start; }
.sm-studio-captions .kind, .sm-studio-row .kind { margin: 0; display: block; font-size: var(--font-size-caption);
  color: var(--color-text-subtle); font-variant-caps: all-small-caps; letter-spacing: .06em; }
.sm-studio-captions .sm-studio-caption { align-self: stretch; }

/* The stage: the selected card at its real ratio, a hairline edge in border.subtle because a
   Light card is the page's own paper and would otherwise have none. */
.sm-studio-stage { display: flex; flex-direction: column; gap: var(--space-sm); align-items: center; min-width: 0; }
.sm-studio-stagehead { width: 100%; display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-sm); }
.sm-studio-stagehead .what { font-size: var(--font-size-small); color: var(--color-text-secondary);
  font-variant-numeric: tabular-nums; }
.sm-studio-stagehead .what b { color: var(--color-text-primary); font-weight: var(--font-weight-semibold); }
.sm-studio-stagehead .nav { display: flex; gap: var(--space-3xs); }
.sm-studio-iconbtn { width: var(--size-control-md); height: var(--size-control-md);
  display: inline-flex; align-items: center; justify-content: center; padding: 0; }
.sm-studio-frame { position: relative; width: 100%; max-width: 360px; aspect-ratio: 4 / 5;
  overflow: hidden; touch-action: pan-y; background: var(--color-surface-sunken);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); }
[data-studio-size="square"] .sm-studio-frame { aspect-ratio: 1 / 1; }
[data-studio-size="story"] .sm-studio-frame { aspect-ratio: 9 / 16; max-width: 315px; }
.sm-studio-frame > img, .sm-studio-frame > video { display: block; width: 100%; height: 100%; object-fit: cover; }
/* A clip being made (patterns/social.md, Clips): the words, a determinate bar and Cancel, under
   the stage's picture, which stays as it was. */
.sm-studio-making { width: 100%; max-width: 360px; display: flex; flex-direction: column;
  align-items: flex-start; gap: var(--space-xs); }
.sm-studio-making .sm-progress { align-self: stretch; max-width: none; }
.sm-studio-making .sm-progress .row { font-variant-numeric: tabular-nums; }
[data-studio-size="story"] .sm-studio-making { max-width: 315px; }
/* "Drawing…" sits over the last picture, which can be any look, so it has a ground of its own:
   a chip on surface.card in text.primary, never secondary ink straight on the picture. */
.sm-studio-drawing { position: absolute; left: 50%; bottom: var(--space-md); transform: translateX(-50%);
  width: max-content; max-width: calc(100% - 2 * var(--space-md)); margin: 0;
  padding: var(--space-3xs) var(--space-sm); text-align: center; font-size: var(--font-size-small);
  background: var(--color-surface-card); color: var(--color-text-primary);
  border: var(--border-width-hairline) solid var(--color-border-subtle); border-radius: var(--radius-surface); }
/* Where Instagram covers a story (top 250 and bottom 340 of 1920, the right 140 of 1080 from the
   half down to the bottom zone), over the stage only: never in the file. */
.sm-studio-cover { position: absolute; inset: 0; pointer-events: none; }
.sm-studio-cover > span { position: absolute; display: flex; align-items: center; justify-content: center;
  text-align: center; padding: var(--space-3xs); background: var(--color-surface-scrim);
  color: var(--color-text-inverse); font-size: var(--font-size-caption); line-height: 1.25; }
.sm-studio-cover .top { left: 0; right: 0; top: 0; height: 13.02%;
  border-bottom: var(--border-width-hairline) dashed var(--color-text-inverse); }
.sm-studio-cover .bot { left: 0; right: 0; bottom: 0; height: 17.71%;
  border-top: var(--border-width-hairline) dashed var(--color-text-inverse); }
.sm-studio-cover .side { right: 0; width: 12.96%; top: 50%; bottom: 17.71%; writing-mode: vertical-rl;
  border-left: var(--border-width-hairline) dashed var(--color-text-inverse); }

/* The choices: Size and Look as full-width Segmented controls, the caption in a read-only box,
   then the acts. */
.sm-studio-side { display: flex; flex-direction: column; gap: var(--space-lg); min-width: 0; }
.sm-studio-picks { display: flex; flex-direction: column; gap: var(--space-md); }
.sm-studio-picks > .hint { margin: 0; font-size: var(--font-size-small); color: var(--color-text-subtle); }
.sm-studio-pick .sm-seg { display: flex; width: 100%; }
.sm-studio-pick .sm-seg label { flex: 1; }
.sm-studio-pick .sm-seg span { width: 100%; justify-content: center; padding: 0 var(--space-xs); }
.sm-studio-capblock { display: flex; flex-direction: column; gap: var(--space-xs); align-items: flex-start; }
.sm-studio-caphead { align-self: stretch; display: flex; align-items: center; justify-content: space-between; }
.sm-studio-caphead .lg { font-size: var(--font-size-body); font-weight: var(--font-weight-medium);
  color: var(--color-text-primary); }
.sm-studio-caption { align-self: stretch; margin: 0; padding: var(--space-sm) var(--space-md);
  background: var(--color-surface-card); border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); font-size: var(--font-size-small);
  line-height: var(--line-height-body); color: var(--color-text-secondary);
  white-space: pre-wrap; overflow-wrap: anywhere; }
.sm-studio-hear .clock { margin-left: var(--space-xs); color: var(--color-text-subtle);
  font-variant-numeric: tabular-nums; }
.sm-studio-acts { display: flex; flex-direction: column; gap: var(--space-xs); }
.sm-studio-primary { display: flex; flex-direction: column; gap: var(--space-2xs); }
.sm-studio-primary .sm-btn--primary { width: 100%; }
.sm-studio-more { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-xs); }
/* Send to your phone opens under the acts, in the page: a disclosure, not a dialog. */
.sm-studio-phone { display: flex; flex-direction: column; align-items: center; gap: var(--space-sm);
  padding: var(--space-md); text-align: center; background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle); border-radius: var(--radius-surface); }
.sm-studio-phone p { margin: 0; font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-studio-phone .qr { width: 100%; max-width: 200px; }
.sm-studio-phone .qr > svg { display: block; width: 100%; height: auto;
  border: var(--border-width-hairline) solid var(--color-border-subtle); border-radius: var(--radius-control); }
.sm-studio-phone .addr { max-width: 100%; overflow-wrap: anywhere; user-select: all;
  font-family: var(--font-mono); font-size: var(--font-size-caption); color: var(--color-text-primary); }

/* The set: a row per card, in the order they post. The selected row is the marked line (the
   transcript's wash and bar); a card left out is at half strength. The handle, and the glide
   a row takes to its place, are the grip's (components/grip.md), as the pull quotes' are. */
.sm-studio-set { display: flex; flex-direction: column; gap: var(--space-xs); min-width: 0; }
.sm-studio-set .sethead { display: flex; align-items: baseline; justify-content: space-between; }
.sm-studio-set .sethead .n { font-size: var(--font-size-small); color: var(--color-text-subtle);
  font-variant-numeric: tabular-nums; }
.sm-studio-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.sm-studio-row { position: relative; display: grid; grid-template-columns: var(--size-target-min) minmax(0, 1fr) var(--size-control-md);
  align-items: center; gap: var(--space-2xs); padding: var(--space-2xs) 0 var(--space-2xs) var(--space-2xs);
  border-left: 2px solid transparent; border-bottom: var(--border-width-hairline) solid var(--color-border-subtle);
  transition: transform var(--duration-base) cubic-bezier(var(--easing-standard)); }
.sm-studio-row::after { content: ""; position: absolute; inset: calc(-1 * var(--border-width-hairline));
  border-radius: var(--radius-surface); box-shadow: var(--shadow-overlay); opacity: 0; pointer-events: none;
  transition: opacity var(--duration-fast) cubic-bezier(var(--easing-standard)); }
.sm-studio-row.is-dragging, .sm-studio-row.is-settling { z-index: 1; background: var(--color-surface-card);
  border-color: var(--color-border-control); }
.sm-studio-row.is-dragging { transition: none; }
.sm-studio-row.is-dragging::after, .sm-studio-row.is-settling::after { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .sm-studio-row::after { transition: none; } }
.sm-studio-row[aria-current="true"] { background: var(--color-state-selected-bg);
  border-left-color: var(--color-state-selected-border); }
.sm-studio-row .sm-gripslot { display: flex; }
.sm-studio-row .pick { all: unset; box-sizing: border-box; cursor: pointer; display: grid;
  grid-template-columns: 44px minmax(0, 1fr); gap: var(--space-sm); align-items: center;
  min-height: var(--size-row-min); padding: var(--space-3xs) 0; border-radius: var(--radius-control); }
.sm-studio-row .pick:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-studio-row .thumb { display: block; width: 44px; height: 55px; object-fit: cover;
  background: var(--color-surface-sunken); border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-chip); }
[data-studio-size="square"] .sm-studio-row .thumb { height: 44px; }
.sm-studio-row .thumb:not([src]) { visibility: hidden; }
.sm-studio-row .txt { min-width: 0; }
.sm-studio-row .words { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; font-family: var(--font-display); font-size: var(--font-size-small); line-height: 1.35;
  color: var(--color-text-primary); }
.sm-studio-row .sm-checkrow { justify-self: center; }
.sm-studio-row--off .thumb, .sm-studio-row--off .words { opacity: .5; }

/* A picked line's row has a fourth part, the × that takes it out. */
.sm-studio-row:has(> .x:not([hidden])) { grid-template-columns: var(--size-target-min) minmax(0, 1fr) var(--size-control-md) var(--size-control-md); }
.sm-studio-row .x { width: var(--size-control-md); height: var(--size-control-md); display: grid;
  place-items: center; padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--color-text-subtle); border-radius: var(--radius-chip);
  transition: background var(--duration-fast), color var(--duration-fast); }
.sm-studio-row .x:hover { background: var(--color-action-hover-wash); color: var(--color-text-primary); }
.sm-studio-row .x:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-studio-add { align-self: flex-start; margin-top: var(--space-xs); }

/* The picker (patterns/social.md, A line from the transcript; #1137): a sheet over the page, a
   centered panel from compact width up, where the transcript is a list of sentences, each with
   its time in the margin (a tool for choosing, so the times show at rest: the one scoped
   exception to components/transcript.md), and a line is a run of them. The foot holds the
   count and its bar and the one act. */
.sm-studio-sheet .head { flex-wrap: wrap; }
/* The search rides in the head, on a row of its own, so it stays in reach while the body scrolls. */
.sm-studio-sheet .head .sm-studio-search { flex: 1 0 100%; }
.sm-studio-search .hint { display: block; margin-top: var(--space-3xs); }
.sm-studio-search > .sm-btn { margin-top: var(--space-xs); }
.sm-studio-sentences { list-style: none; margin: 0; padding: 0; }
.sm-studio-sentences li { display: grid; grid-template-columns: var(--size-control-lg) minmax(0, 1fr);
  gap: var(--space-xs); align-items: start; }
.sm-studio-sentences .t { padding-top: var(--space-sm); text-align: right;
  font-family: var(--font-body); font-variant-numeric: tabular-nums;
  font-size: var(--font-size-caption); color: var(--color-text-subtle); }
.sm-studio-sentences li.on .t { color: var(--color-text-brand); }
.sm-studio-sentences button { all: unset; box-sizing: border-box; cursor: pointer; display: block; width: 100%;
  padding: var(--space-xs) var(--space-sm); border-left: 2px solid transparent;
  font-family: var(--font-display); font-size: var(--font-size-reading); line-height: 1.55;
  color: var(--color-text-primary); border-radius: var(--radius-chip); }
.sm-studio-sentences button:hover { background: var(--color-action-hover-wash); }
.sm-studio-sentences button:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-studio-sentences button[aria-pressed="true"] { background: var(--color-state-selected-bg);
  border-left-color: var(--color-state-selected-border); }
.sm-studio-sentences mark { background: var(--color-action-pressed-wash); color: inherit; border-radius: var(--radius-chip); }
.sm-studio-sheet .foot { align-items: center; }
.sm-studio-sheet .foot .sm-btn--primary { flex: 0 0 auto; }
.sm-studio-countwrap { flex: 1 1 auto; min-width: 0; margin-right: auto; }
.sm-studio-countwrap .sm-progress { max-width: none; }
.sm-studio-countwrap .sm-progress .row { font-variant-numeric: tabular-nums; }
.sm-studio-sheet .foot[data-over] .lbl { color: var(--color-feedback-error); }
.sm-studio-sheet .foot[data-over] .fill { background: var(--color-feedback-error); }
@media (min-width: 641px) {
  dialog.sm-sheet.sm-studio-sheet, dialog.sm-sheet.sm-studio-sheet[open] { display: flex; margin: auto;
    width: 640px; max-width: calc(100vw - 2 * var(--space-lg)); max-height: 80vh;
    border: var(--border-width-hairline) solid var(--color-border-strong); border-radius: var(--radius-surface); }
  dialog.sm-sheet.sm-studio-sheet:not([open]) { display: none; }
}

/* A computer: the set, the stage and the choices side by side. */
@media (min-width: 1024px) {
  .sm-studio-grid { grid-template-columns: minmax(0, 280px) minmax(0, 1fr) minmax(0, 320px);
    align-items: start; gap: var(--space-xl); }
  .sm-studio-set { order: 1; position: sticky; top: var(--space-md); }
  .sm-studio-stage { order: 2; }
  .sm-studio-side { order: 3; }
  .sm-studio-frame, .sm-studio-making { max-width: 452px; }
  [data-studio-size="story"] .sm-studio-frame, [data-studio-size="story"] .sm-studio-making { max-width: 315px; }
}

/* A phone: the stage, the choices, then the set, and the one primary act held at the foot of
   the screen (patterns/layout.md, What may stick). The page keeps a toast clear of it, and the
   foot of the page, the footer's links, clear above it. */
@media (max-width: 640px) {
  .sm-studio-frame, .sm-studio-making { max-width: 300px; }
  [data-studio-size="story"] .sm-studio-frame, [data-studio-size="story"] .sm-studio-making { max-width: 300px; }
  html:has(.sm-studio[data-studio-state="on"]) {
    --sm-studio-foot-h: calc(2 * var(--space-sm) + var(--size-control-lg)
      + env(safe-area-inset-bottom, 0px)); }
  .sm-studio-primary { position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--z-rail);
    padding: var(--space-sm) var(--space-md) calc(var(--space-sm) + env(safe-area-inset-bottom, 0px));
    background: var(--color-surface-page); border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
  html:has(.sm-studio[data-studio-state="on"] .sm-studio-primary:not([hidden])) body { padding-bottom: var(--sm-studio-foot-h); }
  /* A focused control scrolls into view above the bar, not under it. */
  html:has(.sm-studio[data-studio-state="on"] .sm-studio-primary:not([hidden])) {
    scroll-padding-bottom: calc(var(--sm-studio-foot-h) + var(--space-md)); }
  html:has(.sm-studio-primary:not([hidden])) .sm-toasts { bottom: calc(var(--sm-studio-foot-h, 0px) + var(--space-md)); }
}

/* The Share section on a sermon's page (patterns/social.md, The Share section; #1271): a card
   (the .sm-card shell) holding the words and the spread, three of the sermon's own graphics
   each over the next by a third of its width. One column below breakpoint.regular with the
   spread first (the markup stays words, then spread, so a reader and a screen reader get the
   words first); from it, two columns, the words on the left and the wider spread on the right.
   The card keeps space.lg of padding at both widths, as every .sm-card does.
   --peek is the width of one card: set once on .spread, so the card's width and its overlap (a
   quarter of it) cannot drift apart. It is 128px below breakpoint.regular and 208px from it, and
   never more than 40% of the spread's own width (the three cards run 5/2 of one), so a narrow
   phone shrinks the cards instead of pushing them past the panel. Each card lies over the one
   before it, the last in front: a card sets its words from the left, so every card keeps its
   opening words in view. The cards are not controls:
   no hover, no cursor, no motion of their own; the act is the button.
   Before and without a drawn card each is a stand-in in tokens, surface.page under a
   border.strong rule across its top with its words in the display face. A script then replaces
   a stand-in's children with one canvas and sets data-drawn on the card. */
.sm-sharepeek { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-xl);
  align-items: center; background: var(--color-surface-card);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); box-shadow: var(--shadow-raised);
  padding: var(--space-lg); font-family: var(--font-body); }
.sm-sharepeek .words { min-width: 0; }
.sm-sharepeek .sm-microlabel { margin: 0; }
.sm-sharepeek .hd { margin: var(--space-3xs) 0 var(--space-xs); font-family: var(--font-display);
  font-weight: var(--font-weight-medium); font-size: var(--font-size-title);
  line-height: var(--line-height-snug); color: var(--color-text-heading); }
.sm-sharepeek .line { margin: 0; font-size: var(--font-size-body); color: var(--color-text-secondary); }
.sm-sharepeek .line + .line { margin-top: var(--space-2xs); }
.sm-sharepeek .sm-acts { margin-top: var(--space-md); }
.sm-sharepeek .spread { --peek: min(128px, 40cqw); order: -1; container-type: inline-size;
  min-width: 0; display: flex; flex-wrap: nowrap; justify-content: center; }
.sm-sharepeek .card { position: relative; flex: none; box-sizing: border-box; width: var(--peek);
  aspect-ratio: 1; overflow: hidden; background: var(--color-surface-page);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  box-shadow: var(--shadow-raised); }
.sm-sharepeek .card + .card { margin-left: calc(-1 * var(--peek) / 4); }
.sm-sharepeek .card:nth-child(1) { z-index: 1; }
.sm-sharepeek .card:nth-child(2) { z-index: 2; }
.sm-sharepeek .card:nth-child(3) { z-index: 3; }
.sm-sharepeek .card .rule { display: block;
  border-top: var(--border-width-marker) solid var(--color-border-strong); }
.sm-sharepeek .card .txt { display: -webkit-box; -webkit-line-clamp: 5; line-clamp: 5;
  -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; padding: var(--space-sm);
  font-family: var(--font-display); font-size: var(--font-size-small);
  line-height: var(--line-height-snug); color: var(--color-text-primary); }
.sm-sharepeek .card[data-drawn] > canvas { display: block; width: 100%; height: 100%; }
@keyframes sm-sharepeek-in { from { opacity: 0; } }
@media (prefers-reduced-motion: no-preference) {
  .sm-sharepeek .card[data-drawn] > canvas {
    animation: sm-sharepeek-in var(--duration-base) cubic-bezier(var(--easing-enter)); }
}
@media (min-width: 1024px) {
  .sm-sharepeek { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); }
  .sm-sharepeek .spread { --peek: min(208px, 40cqw); order: 0; }
}

/* A service in Settings (patterns/dashboard.md, Services): one line where the fields column
   holds it. The handle, the day, the time and the remove button keep fixed widths, so every
   row's controls line up under one another, and the name takes the rest. On a phone the day
   and the time fold under the name, one above the other. Both are selects, the time's wrapper
   the second of the two, and each column fits "No set day", "Wednesday" and "No set time" on
   one line: side by side beside the handle's column a phone has room for neither. */
.sm-slotrow { display: grid; gap: var(--space-xs); align-items: center;
  grid-template-columns: var(--size-target-min) minmax(0, 1fr) 9.5rem 9.5rem var(--size-target-min); }
.sm-slotrow .grow { min-width: 0; }
.sm-slotrow .x, .sm-quoterow .x { width: var(--size-target-min); height: var(--size-target-min); display: grid;
  place-items: center; padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--color-text-subtle); border-radius: var(--radius-chip);
  transition: background var(--duration-fast), color var(--duration-fast); }
.sm-slotrow .x:hover, .sm-quoterow .x:hover { background: var(--color-action-hover-wash); color: var(--color-text-primary); }
.sm-slotrow .x:focus-visible, .sm-quoterow .x:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
/* A service on the signup page (patterns/dashboard.md, Setting up, #1365): a card of its own,
   where the one-line row above left the name a few letters wide in that page's narrow column.
   The name takes the card's first line across both columns, the day and the time sit side by
   side under it, and each control carries a visible label above it. The quiet remove sits at
   the card's top-right corner, inset by the card's own padding, and the name's label is as
   tall as that small button with its text centered, so the two share one line and the button
   never covers the box. It is out of the grid's flow, so it takes no column. A list of cards
   opens its gap to space.sm, where Settings' rows keep the list's tighter one: bordered cards
   that close read as one crowded block. Under 500px, where two selects stop fitting side by
   side (#1158), the time stacks under the day (the media block below). */
.rows:has(> .sm-slotcard) { gap: var(--space-sm); }
.sm-qrow.sm-slotcard { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-sm) var(--space-md); align-items: start; position: relative; }
.sm-slotcard > label { display: grid; gap: var(--space-2xs); min-width: 0; }
.sm-slotcard > .name { grid-column: 1 / -1; }
.sm-slotcard .lbl { font-size: var(--font-size-body); font-weight: var(--font-weight-medium);
  color: var(--color-text-primary); }
.sm-slotcard > .name > .lbl { display: flex; align-items: center; min-height: var(--size-control-sm); }
.sm-slotcard .sm-well, .sm-slotcard .sm-selectw { width: 100%; min-width: 0; }
.sm-slotcard > .rm { position: absolute; inset-block-start: var(--space-sm);
  inset-inline-end: var(--space-md); }
.sm-slotcard > .rm[hidden] { display: none; }
/* A pull quote in a sermon's details form (patterns/dashboard.md, #928): a service row's
   card and its columns, the handle's slot, the quote's box and the quiet remove. The handle
   and the remove sit centered on the box's first line however long the quote runs (the
   box's edge and padding, then half of what a line is taller or shorter than each), and the
   slot keeps its width while the handle is hidden, so the boxes line up. The box grows to
   its words where the browser can size a field to its content, so a long quote shows whole
   on a phone; elsewhere it keeps its three rows and scrolls, as any box does. */
.sm-quoterow { display: grid; gap: var(--space-xs); align-items: start;
  grid-template-columns: var(--size-target-min) minmax(0, 1fr) var(--size-target-min); }
.sm-quoterow textarea.sm-well { field-sizing: content; }
.sm-quoterow .sm-gripslot { margin-top: calc(var(--border-width-hairline) + var(--space-xs)
  + (var(--font-size-body) * var(--line-height-body) - var(--size-control-md)) / 2); }
.sm-quoterow .x { margin-top: calc(var(--border-width-hairline) + var(--space-xs)
  + (var(--font-size-body) * var(--line-height-body) - var(--size-target-min)) / 2); }
/* A person to invite (patterns/dashboard.md, People; #974, the owner's choice from three
   rounds of rendered options, its spacing approved as measured): between hairlines, the
   address, the role as inline radios, and the remove. On a wide screen they share one line,
   centered on it, and every gap along it is space.lg ink to ink: the radios' space.sm of
   margin and their own space.sm of padding before the first mark, and the remove 40px wide
   so its 16px icon sits space.sm in from its edges. On a phone the remove stays beside the
   address (space.sm of margin, so space.lg to its icon) and the radios take the line under
   it, the first mark on the address's left edge and its wash hanging into the gutter, as a
   form's radio rows do (#840). The field error for an address, and the note for a row that
   is skipped or was not sent, run under the row. */
.sm-invite { gap: var(--space-xs); }
/* Three roles (where Viewer is offered) take the fields column's whole 640px, so the
   address keeps room beside them on the line: in the field's 480px it had about 120. */
.sm-invite:has(.sm-radioinline > label:nth-of-type(3)) { max-width: 640px; }
.sm-invite > .lbl { font-weight: var(--font-weight-medium); color: var(--color-text-primary); }
.sm-invite > .key { font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-invite > .key b { font-weight: var(--font-weight-semibold); color: var(--color-text-primary); }
.sm-invite > .rows { list-style: none; margin: 0; padding: 0;
  border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-invite > .sm-acts { margin-top: var(--space-2xs); }
/* The addresses a paste brought past the most one Invite takes, named under the rows. */
.sm-invite > .over { font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-inviterow { display: grid; grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "addr x" "msg msg" "role role" "note note"; align-items: center;
  padding: var(--space-sm) 0; border-bottom: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-inviterow > .sm-well { grid-area: addr; }
/* An address that is a combobox (Who we email, #1257) takes the address's place. */
.sm-inviterow > .sm-combo { grid-area: addr; }
.sm-inviterow > .sm-radioinline { grid-area: role; justify-self: start;
  margin-inline-start: calc(-1 * var(--space-sm)); }
.sm-inviterow > .x { grid-area: x; width: calc(var(--size-control-sm) + var(--space-2xs));
  height: var(--size-control-md); margin-inline-start: var(--space-sm);
  display: grid; place-items: center; padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--color-text-subtle); border-radius: var(--radius-control);
  transition: background var(--duration-fast), color var(--duration-fast); }
.sm-inviterow > .x:hover { background: var(--color-action-hover-wash); color: var(--color-text-primary); }
.sm-inviterow > .x:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-inviterow > .x:disabled { cursor: default; color: var(--color-text-disabled); background: none; }
.sm-inviterow > .sm-msg { grid-area: msg; margin: var(--space-2xs) 0 0; }
.sm-inviterow > .note { grid-area: note; margin: var(--space-2xs) 0 0;
  font-size: var(--font-size-small); color: var(--color-text-secondary); }
@media (min-width: 641px) {
  .sm-inviterow { grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas: "addr role x" "msg msg msg" "note note note"; }
  .sm-inviterow > .sm-radioinline { margin-inline-start: var(--space-sm); }
  .sm-inviterow > .x { margin-inline-start: 0; }
}
/* A row with no role radios (a list of addresses, Who we email) is the address then the remove
   on one line at every width, the message and the note under it. It outranks the wide
   screen's three columns, and keeps the remove's own space.sm from the address, which on the
   wide screen the role's margin supplies. */
.sm-inviterow:not(:has(.sm-radioinline)) { grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "addr x" "msg msg" "note note"; }
.sm-inviterow:not(:has(.sm-radioinline)) > .x { margin-inline-start: var(--space-sm); }
/* A short sentence beside a form's act about what pressing it will leave (Invite
   people's seats, #974): on the act's line, and centered under it once the acts
   stack on a phone. */
.sm-acts > .beside { margin: 0; align-self: center;
  font-size: var(--font-size-small); color: var(--color-text-secondary); }

@media (max-width: 640px) {
  .sm-slotrow { grid-template-columns: var(--size-target-min) minmax(0, 1fr) auto var(--size-target-min); }
  .sm-slotrow .sm-gripslot { grid-row: 1 / span 2; }
  .sm-slotrow .grow { grid-column: 2 / span 2; }
  .sm-slotrow .sm-selectw { grid-column: 2 / span 3; grid-row: 2; }
  .sm-slotrow .sm-selectw + .sm-selectw { grid-row: 3; }
}
/* A signup service card is one column where two selects stop fitting side by side (500px, the
   measurement #1158 recorded): the time stacks under the day. */
@media (max-width: 500px) {
  .sm-qrow.sm-slotcard { grid-template-columns: minmax(0, 1fr); }
}
/* ---------- drag handle (components/grip.md) ---------- */
/* A quiet button at a row's start that moves the row, by pointer or by the arrow keys once
   focused. Its slot keeps the width when the row carries no handle, so fields align. */
.sm-gripslot { flex: none; display: inline-flex; width: var(--size-target-min); }
.sm-grip { width: var(--size-target-min); height: var(--size-control-md); display: grid;
  place-items: center; padding: 0; border: 0; background: none; color: var(--color-text-subtle);
  border-radius: var(--radius-chip); cursor: grab; touch-action: none;
  transition: background var(--duration-fast), color var(--duration-fast); }
.sm-grip:hover { background: var(--color-action-hover-wash); color: var(--color-text-primary); }
.sm-grip:active, .is-dragging .sm-grip { cursor: grabbing; }
.sm-grip:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
/* On a touch screen the handle's target is the tenant size (accessibility.md: 48px). Its
   hit area reaches into the row's padding and the gap after it, so it looks no wider and
   covers nothing else: 16px + 24px + 8px. */
@media (pointer: coarse) {
  .sm-grip { position: relative; }
  .sm-grip::before { content: ""; position: absolute; top: 0; bottom: 0;
    left: calc(-1 * var(--space-md)); right: calc(-1 * var(--space-xs)); } }
/* The handle's menu: pressed without a drag (a click, a tap, Enter or Space), the handle
   opens Move up and Move down under itself, for anyone who can press but not drag
   (WCAG 2.5.7). The select's popup surface, rows at its options' height; an item that
   cannot act stays, in disabled ink. It enters with the fade and settle. */
.sm-gripslot { position: relative; }
.sm-gripmenu { position: absolute; top: calc(100% + var(--space-3xs)); left: 0; z-index: var(--z-popover);
  display: flex; flex-direction: column; min-width: max-content; padding: var(--space-2xs);
  background: var(--color-surface-card); border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-control); box-shadow: var(--shadow-overlay);
  transition: opacity var(--duration-base) cubic-bezier(var(--easing-enter)),
    transform var(--duration-base) cubic-bezier(var(--easing-enter)); }
.sm-gripmenu[hidden] { display: none; }
@starting-style { .sm-gripmenu { opacity: 0; transform: translateY(6px); } }
.sm-gripmenu button { display: flex; align-items: center; min-height: var(--size-control-md);
  padding: var(--space-2xs) var(--space-sm); border: 0; border-radius: var(--radius-chip);
  background: none; color: var(--color-text-primary); text-align: left; cursor: pointer;
  font-family: var(--font-body); font-size: var(--font-size-body);
  transition: background var(--duration-fast); }
.sm-gripmenu button:hover, .sm-gripmenu button:focus { background: var(--color-action-hover-wash); outline: none; }
.sm-gripmenu button:focus-visible { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: calc(-1 * var(--border-width-focus)); }
.sm-gripmenu button[aria-disabled="true"] { color: var(--color-text-disabled); cursor: default; }
.sm-gripmenu button[aria-disabled="true"]:hover { background: none; }

/* The latest day (patterns/dashboard.md, Home; #557): the newest preached date
   leads the desk. Its first sermon is the library's featured card beside its
   record, the rest follow as queue rows. The pair wraps to one column the
   moment both no longer fit, and nothing in it has a width a phone lacks. */
.sm-latest .pair, .sm-reach .pair { display: flex; flex-wrap: wrap; gap: var(--space-lg);
  align-items: stretch; }
.sm-latest .sm-featured { flex: 1 1 520px; min-width: 0; display: flex; flex-direction: column;
  box-shadow: none; }
.sm-latest .sm-featured > .sm-microlabel { margin: 0 0 var(--space-sm); }
.sm-latest .sm-featured .ft a { color: inherit; text-decoration: none;
  transition: color var(--duration-fast); }
.sm-latest .sm-featured .ft a:hover { color: var(--color-text-action); }
/* The public library is one step away, in the sentence (#1116). */
.sm-latest .state a, .sm-latest .sm-qrow .m a { color: var(--color-text-action);
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--color-border-link); }
.sm-latest .state a:hover, .sm-latest .sm-qrow .m a:hover { text-decoration-color: currentColor; }
.sm-latest .state { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-xs) var(--space-sm);
  margin: 0 0 var(--space-md); font-size: var(--font-size-small); color: var(--color-text-secondary); }
/* The player sits at the card's foot, level with the record's last row. */
.sm-latest .listen { margin-top: auto; }
/* The card is a column, and a button in a column stretches: its act keeps its own width. */
.sm-latest .sm-featured > .sm-btn { align-self: flex-start; }
.sm-latest .sm-record { flex: 0 1 400px; min-width: 0; }
.sm-latest .sm-record .nw { white-space: nowrap; }
.sm-latest .rows { display: flex; flex-direction: column; gap: var(--space-xs); }
.sm-latest .sm-qrow .grow { min-width: min(240px, 100%); }
@media (max-width: 640px) {
  .sm-latest .sm-featured { padding: var(--space-md); }
  .sm-latest .sm-featured .ft { font-size: var(--font-size-heading); }
}

/* Your library (patterns/dashboard.md, Home; #557, #600): the week's figures in
   a row, views by day beside the year's record, then the week's most viewed
   sermons. Figures lead this section; the sermon above leads the page. The row
   sits on the page ground under a hairline, not in tiles (D1): each figure a
   number in the stat face (components/stat.md: sans semibold, proportional
   figures), its words under it, and the change from the week before in words. */
.sm-reach .figures { list-style: none; margin: var(--space-xs) 0 var(--space-lg);
  padding: 0 0 var(--space-md); display: flex; flex-wrap: wrap;
  gap: var(--space-md) var(--space-2xl); font-family: var(--font-body);
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-reach .figures li { min-width: 9rem; }
.sm-reach .figures .n { display: block; font-size: var(--font-size-display);
  font-weight: var(--font-weight-semibold); line-height: var(--line-height-tight);
  color: var(--color-text-primary); }
.sm-reach .figures .l { display: block; font-size: var(--font-size-small);
  color: var(--color-text-secondary); }
.sm-reach .figures .d { display: block; font-size: var(--font-size-small);
  color: var(--color-text-subtle); }
.sm-reach .sm-daychart { flex: 1 1 520px; }
.sm-reach .sm-record { flex: 0 1 360px; min-width: 0; }
.sm-reach .most { margin-top: var(--space-xl); }
.sm-reach .most > .sm-microlabel { margin: 0 0 var(--space-sm); }
/* Analytics (#859, #1030): the chart takes the row, and series and speakers
   sit side by side while there is room for two tables. */
.sm-reach > .sm-daychart:not(:last-child) { margin-bottom: var(--space-xl); }
.sm-reach .groups { display: grid; gap: var(--space-xl) var(--space-lg); margin-top: var(--space-xl);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.sm-reach .groups > div > .sm-microlabel { margin: 0 0 var(--space-sm); }
.sm-reach .more { margin: var(--space-xs) 0 0; font-size: var(--font-size-small);
  color: var(--color-text-subtle); }
.sm-usage-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs) var(--space-md); }
.sm-usage-bar p { margin: 0; color: var(--color-text-secondary); }

/* File drop-well: button-first (the volunteer clicks; dragging is a bonus). A label
   wrapping a hidden file input: the whole well is the target. Drag-over swaps to
   the selection colors; geometry never moves. */
.sm-dropwell { display: flex; flex-direction: column; align-items: center; gap: var(--space-sm);
  padding: var(--space-xl) var(--space-lg); cursor: pointer; text-align: center;
  background: var(--color-surface-card); color: var(--color-text-secondary);
  border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-surface); font-family: var(--font-body);
  font-size: var(--font-size-body);
  transition: background var(--duration-fast), border-color var(--duration-fast); }
.sm-dropwell:hover { background: var(--color-action-hover-wash); border-color: var(--color-border-strong); }
.sm-dropwell.is-dragover { background: var(--color-state-selected-bg);
  border-color: var(--color-state-selected-border); }
.sm-dropwell .ico { color: var(--color-text-subtle); }
.sm-dropwell .kbtn { pointer-events: none; } /* the label opens the picker; the styled "button" is a span */
.sm-dropwell input[type="file"] { position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.sm-dropwell:has(input:focus-visible) { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: var(--size-focus-offset); }
.sm-dropwell .hint { font-size: var(--font-size-small); color: var(--color-text-subtle); }
.sm-dropwell .picked { font-size: var(--font-size-small); color: var(--color-text-primary);
  font-weight: var(--font-weight-medium); min-height: 1.4em; }
/* Chosen (#1004): once the recording is read, the well folds into the row that
   names it (the glyph, the name over what it is, and "Choose another"), so the
   trim under it starts near the top of a phone. Still the label: a click
   anywhere on it chooses again, and a file dragged onto it replaces this one. */
.sm-dropwell[data-chosen] { display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "ico name act" "ico meta act"; align-items: center;
  gap: 0 var(--space-sm); padding: var(--space-sm) var(--space-md); text-align: left;
  border-radius: var(--radius-control); }
.sm-dropwell[data-chosen] .ico { grid-area: ico; display: flex; }
.sm-dropwell[data-chosen] .picked { grid-area: name; min-height: 0; font-size: var(--font-size-body);
  overflow-wrap: anywhere; }
.sm-dropwell[data-chosen] .hint { grid-area: meta; color: var(--color-text-secondary); }
.sm-dropwell[data-chosen] .kbtn { grid-area: act; }

/* ---------- trim (components/trim.md, #1004, #1034) ----------
   Marking where the sermon starts and ends in a whole service's recording.
   On top, finding it: the picture (a video), the strip (the whole recording's
   sound and its seek bar, the kept span framed between two grips) and the
   transport. Below, marking it: one card that edits the chosen mark, with that
   mark's minute of sound, its time and Hear it. The strip is placed by three
   custom properties the island sets on the root: --sm-trim-a and --sm-trim-b
   (the marks) and --sm-trim-at (playing), each a percentage of the recording. */
/* A <fieldset>, so a disabled one holds every control still while the upload
   runs; the UA's border, padding and min-content minimum go, as .sm-formsec's do. */
.sm-trim { display: flex; flex-direction: column; gap: var(--space-sm); min-width: 0;
  border: 0; margin: 0; padding: 0;
  --sm-trim-a: 0%; --sm-trim-b: 100%; --sm-trim-at: 0%;
  --sm-trim-edge: calc(var(--border-width-marker) + var(--border-width-hairline)); }
.sm-trim[hidden] { display: none; }
.sm-trim p { margin: 0; }
.sm-trim > .lede { color: var(--color-text-secondary); }
/* The picture, letterboxed in a frame that holds 16:9 from the first paint. */
.sm-trim > .frame { position: relative; aspect-ratio: 16 / 9; overflow: hidden;
  background: var(--color-surface-sunken); border: var(--border-width-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-surface); }
.sm-trim > .frame[hidden] { display: none; }
.sm-trim > .frame video { display: block; width: 100%; height: 100%; object-fit: contain; }
.sm-trim > .aside { font-size: var(--font-size-small); color: var(--color-text-secondary); }
/* The strip: the recording's sound across its whole length, and its seek bar.
   A control, so it carries the control edge. The kept span takes the
   selection wash inside a frame of the mark's color, and what lies outside it
   is hatched, so the two never differ by color alone. Room above it holds the
   playhead's head. */
.sm-trim .strip { position: relative; height: 56px; margin-top: var(--space-xs); touch-action: pan-y;
  background: var(--color-surface-card); border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-control); }
.sm-trim .strip:has(.seek:focus-visible) { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: calc(var(--size-focus-offset) + var(--sm-trim-edge)); }
.sm-trim .strip .span { position: absolute; inset: 0 calc(100% - var(--sm-trim-b)) 0 var(--sm-trim-a);
  background: var(--color-state-selected-bg); }
.sm-trim .strip .dim { position: absolute; top: 0; bottom: 0;
  background: repeating-linear-gradient(135deg, transparent 0 5px, var(--color-border-subtle) 5px 6px); }
.sm-trim .strip .dim--start { left: 0; width: var(--sm-trim-a); border-radius: var(--radius-control) 0 0 var(--radius-control); }
.sm-trim .strip .dim--end { left: var(--sm-trim-b); right: 0; border-radius: 0 var(--radius-control) var(--radius-control) 0; }
/* With nothing to draw (a browser that cannot decode the sound), a hairline
   along the middle, so the strip still reads as a track. */
.sm-trim .strip::before { content: ""; position: absolute; left: 0; right: 0; top: 50%;
  border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-trim .wave { position: absolute; inset: var(--space-xs) 0; width: 100%; height: calc(100% - 2 * var(--space-xs)); }
.sm-trim .wave .out { fill: var(--color-text-disabled); }
.sm-trim .wave .in { fill: var(--color-text-primary); }
.sm-trim .strip .wave .in { clip-path: inset(0 calc(100% - var(--sm-trim-b)) 0 var(--sm-trim-a)); }
/* The seek bar is a real range input laid over the strip, invisible: the strip
   is what it looks like, and the input is what it is. */
.sm-trim .seek { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0;
  cursor: pointer; }
.sm-trim .seek:disabled { cursor: default; }
/* The kept span's frame: the mark's color along its top and bottom, a grip at
   each end, outside the span so no sound inside it hides under a grip. A grip
   drags its mark at the strip's one scale, staying under the hand (#1034); its
   grab area reaches 44px wide, looking no wider. Pointer only: the card's time
   field does all it does. */
.sm-trim .bracket { position: absolute; top: -2px; bottom: -2px; z-index: 1; pointer-events: none;
  left: var(--sm-trim-a); right: calc(100% - var(--sm-trim-b));
  border-top: var(--sm-trim-edge) solid var(--color-action-primary); border-bottom: var(--sm-trim-edge) solid var(--color-action-primary); }
.sm-trim .grip { position: absolute; top: -2px; bottom: -2px; width: 14px; z-index: 2; cursor: grab; touch-action: none;
  background: var(--color-action-primary); transition: background var(--duration-fast); }
.sm-trim .grip::before { content: ""; position: absolute; top: calc(-1 * var(--space-xs)); bottom: calc(-1 * var(--space-xs));
  left: 50%; width: 44px; transform: translateX(-50%); }
.sm-trim .grip::after { content: ""; position: absolute; top: 50%; left: 50%; width: 2px; height: 40%;
  transform: translate(-50%, -50%); border-radius: var(--radius-pill); background: var(--color-action-primary-text); }
.sm-trim .grip--start { left: calc(var(--sm-trim-a) - 14px); border-radius: var(--radius-control) 0 0 var(--radius-control); }
.sm-trim .grip--end { left: var(--sm-trim-b); border-radius: 0 var(--radius-control) var(--radius-control) 0; }
.sm-trim .grip:hover, .sm-trim .grip.is-held { background: var(--color-action-primary-hover); }
.sm-trim .grip.is-held { cursor: grabbing; }
.sm-trim[data-locked] .grip { cursor: default; }
/* Where it is playing: an ink needle with its head above the strip, ringed in
   the ground's color so it reads where it crosses a grip. A mark and the
   playhead never share a shape. */
.sm-trim .head { position: absolute; top: -13px; bottom: 0; left: var(--sm-trim-at); width: 2px; z-index: 3;
  transform: translateX(-50%); pointer-events: none;
  background: var(--color-text-primary); box-shadow: 0 0 0 1px var(--color-surface-card); }
.sm-trim .head::before { content: ""; position: absolute; top: -2px; left: 50%; width: 12px; height: 12px;
  transform: translateX(-50%); border-radius: var(--radius-pill);
  background: var(--color-text-primary); box-shadow: 0 0 0 2px var(--color-surface-page); }
/* The head is a handle too (#1040): a grab area over it, above the strip, that
   drags where it is playing as the strip does. Pointer only, like the grips:
   the range input is the control. */
.sm-trim .knob { position: absolute; left: var(--sm-trim-at); top: -20px; width: 32px; height: 20px; z-index: 4;
  transform: translateX(-50%); cursor: grab; touch-action: none; }
.sm-trim .knob.is-held { cursor: grabbing; }
.sm-trim[data-locked] .knob { cursor: default; }
/* The transport: back 15, play, forward 30 (the player's, components/player.md),
   and the time playing. */
.sm-trim .transport { display: flex; align-items: center; gap: var(--space-xs); }
.sm-trim .transport .play { flex: none; width: var(--size-control-md); height: var(--size-control-md);
  display: grid; place-items: center; border: 0; border-radius: var(--radius-pill); cursor: pointer;
  background: var(--color-action-primary); color: var(--color-action-primary-text);
  transition: background var(--duration-fast), transform var(--duration-fast); }
.sm-trim .transport .play:hover { background: var(--color-action-primary-hover); }
.sm-trim .transport .play:active { transform: translateY(1px); }
.sm-trim .transport .play:disabled { cursor: default; }
.sm-trim .transport .now { margin-left: auto; font-size: var(--font-size-small); font-variant-numeric: tabular-nums;
  color: var(--color-text-secondary); white-space: nowrap; }
.sm-trim .transport .now b { font-size: var(--font-size-body); font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary); }
/* The card that marks the sermon (`.marking`). */
.sm-trim .marking { display: flex; flex-direction: column; margin-top: var(--space-xs); overflow: hidden;
  background: var(--color-surface-card); border: var(--border-width-hairline) solid var(--color-border-control);
  border-radius: var(--radius-surface); }
/* Which mark the card edits: two radios (Choice, segmented, as the library's
   browse switch), dressed as the card's top edge. The chosen one opens into
   the card under a bar of the mark's color; the other sits on the subtle
   ground. Only colors change, so nothing reflows. */
.sm-trim .marks { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  border: 0; margin: 0; padding: 0; min-width: 0; }
.sm-trim .choice { position: relative; display: flex; cursor: pointer; }
.sm-trim .choice input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.sm-trim .choice > span { flex: 1; display: flex; flex-direction: column; justify-content: center; min-height: 64px;
  padding: var(--space-sm) var(--space-md); background: var(--color-surface-subtle); color: var(--color-text-secondary);
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle); transition: background var(--duration-fast); }
.sm-trim .choice + .choice > span { border-left: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-trim .choice .name { font-size: var(--font-size-small); }
.sm-trim .choice b { font-size: var(--font-size-title); font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums; color: var(--color-text-primary); }
.sm-trim .choice:hover input:not(:checked) + span { background: linear-gradient(var(--color-action-hover-wash), var(--color-action-hover-wash)),
  var(--color-surface-subtle); }
.sm-trim .choice input:checked + span { background: var(--color-surface-card); color: var(--color-text-primary);
  border-bottom-color: transparent; box-shadow: inset 0 var(--sm-trim-edge) 0 var(--color-action-primary); }
.sm-trim .choice input:focus-visible + span { outline: var(--border-width-focus) solid var(--color-border-focus);
  outline-offset: calc(-1 * var(--border-width-focus) - var(--space-2xs)); }
.sm-trim .choice input:disabled { cursor: default; }
/* The bar glides (#1028): where the browser can say which mark is chosen, the
   bar of the mark's color is one bar, the switch's ::after, and it rides the
   top edge to the chosen half on transform alone. The halves are equal, so no
   script has to measure where it goes; the chosen half's own bar gives way. */
@supports selector(:has(*)) {
  .sm-trim .marks { position: relative; }
  .sm-trim .marks::after { content: ""; position: absolute; top: 0; left: 0; width: 50%; height: var(--sm-trim-edge);
    background: var(--color-action-primary); pointer-events: none;
    transition: transform var(--duration-slow) cubic-bezier(var(--easing-standard)); }
  .sm-trim .marks:has(input[value="end"]:checked)::after { transform: translateX(100%); }
  .sm-trim .choice input:checked + span { box-shadow: none; }
}
@media (prefers-reduced-motion: reduce) { .sm-trim .marks::after { transition: none; } }
.sm-trim .marking > .body { display: flex; flex-direction: column; gap: var(--space-sm); padding: var(--space-md); }
/* The lane: the chosen mark's minute of sound, the mark fixed on the line in
   the middle and its time in a bubble above it. Dragging the sound moves the
   mark, a minute across the lane's width, always. Pointer only, and hidden
   from a screen reader: the time field does all it does. */
.sm-trim .lane { position: relative; isolation: isolate; height: 100px; overflow: hidden; cursor: grab; touch-action: none;
  user-select: none; border: var(--border-width-hairline) solid var(--color-border-control); border-radius: var(--radius-control);
  background: repeating-linear-gradient(135deg, transparent 0 5px, var(--color-border-subtle) 5px 6px), var(--color-surface-card); }
/* Which end this is, at a glance (#1040): the sermon's side of the line takes
   the strip's wash, the cut side keeps the strip's hatching; the line is a
   bracket opening toward the sermon, [ for the start and ] for the end; and
   the bubble names the mark on the sermon's side. */
.sm-trim .lane .kept { position: absolute; top: 0; bottom: 0; z-index: -1; background: var(--color-state-selected-bg); }
.sm-trim .lane[hidden] { display: none; }
.sm-trim .lane.is-held { cursor: grabbing; }
.sm-trim[data-locked] .lane { cursor: default; }
.sm-trim .lane .wave { inset: 28px 0 20px; height: calc(100% - 48px); }
.sm-trim .lane::before { content: ""; position: absolute; left: 0; right: 0; top: 54px;
  border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-trim .lane .line { position: absolute; top: 0; bottom: 0; left: 50%; width: var(--border-width-marker); z-index: 2;
  transform: translateX(-50%); pointer-events: none; background: var(--color-action-primary); }
.sm-trim .lane .line::before, .sm-trim .lane .line::after { content: ""; position: absolute; left: 0; width: 12px;
  height: var(--sm-trim-edge); background: var(--color-action-primary); }
.sm-trim .lane .line::before { top: 0; }
.sm-trim .lane .line::after { bottom: 0; }
.sm-trim .lane--end .line::before, .sm-trim .lane--end .line::after { left: auto; right: 0; }
.sm-trim .lane .bubble { position: absolute; top: calc(var(--sm-trim-edge) + var(--space-2xs)); left: calc(50% + var(--space-2xs)); z-index: 3;
  padding: 0 var(--space-xs); border-radius: var(--radius-chip); white-space: nowrap; pointer-events: none; line-height: 20px;
  font-size: var(--font-size-small); font-weight: var(--font-weight-semibold); font-variant-numeric: tabular-nums;
  background: var(--color-action-primary); color: var(--color-action-primary-text); }
.sm-trim .lane--end .bubble { left: auto; right: calc(50% + var(--space-2xs)); }
.sm-trim .lane .ticks { position: absolute; left: var(--space-xs); right: var(--space-xs); bottom: 2px; display: flex;
  justify-content: space-between; pointer-events: none; font-size: var(--font-size-caption);
  color: var(--color-text-subtle); font-variant-numeric: tabular-nums; }
/* The other mark, when it falls inside this minute, and where it is playing. */
.sm-trim .lane .other { position: absolute; top: 0; bottom: 18px; width: var(--border-width-marker); z-index: 1;
  transform: translateX(-50%); pointer-events: none; background: var(--color-action-primary); }
.sm-trim .lane .other[hidden], .sm-trim .lane .playing[hidden] { display: none; }
.sm-trim .lane .other i { position: absolute; top: 0; left: 0; padding: 0 var(--space-2xs); line-height: 18px; white-space: nowrap;
  font-style: normal; font-size: var(--font-size-caption); font-weight: var(--font-weight-semibold);
  background: var(--color-action-primary); color: var(--color-action-primary-text); border-radius: 0 0 var(--radius-chip) 0; }
.sm-trim .lane .other--end i { left: auto; right: 0; border-radius: 0 0 0 var(--radius-chip); }
.sm-trim .lane .playing { position: absolute; top: 0; bottom: 18px; width: 2px; z-index: 1;
  transform: translateX(-50%); pointer-events: none; background: var(--color-text-primary); }
.sm-trim .marking .hint { margin-top: calc(-1 * var(--space-2xs)); font-size: var(--font-size-caption); color: var(--color-text-subtle); }
.sm-trim .marking .hint[hidden] { display: none; }
/* The chosen mark's time: a field (components/field.md), and Hear it beside it. */
.sm-trim .time { display: grid; grid-template-columns: minmax(0, 160px) auto; justify-content: start; align-items: center;
  gap: var(--space-2xs) var(--space-md); }
.sm-trim .time > label { grid-column: 1 / -1; font-weight: var(--font-weight-medium); color: var(--color-text-primary); }
.sm-trim .time > .sm-well { text-align: center; font-variant-numeric: tabular-nums; }
.sm-trim .time > .sm-msg { grid-column: 1 / -1; }
.sm-trim .marking .sum { padding-top: var(--space-sm); border-top: var(--border-width-hairline) solid var(--color-border-subtle);
  font-size: var(--font-size-small); color: var(--color-text-secondary); }
.sm-trim .marking .sum b { font-weight: var(--font-weight-semibold); color: var(--color-text-primary); }

/* ---------- assignment matrix, attention list, pending changes (SSM-148) ----------
   The operator console's model page, and the three pieces of it worth writing
   down, because each answers a question that recurs wherever a system has a
   default, a set of exceptions, and a save button.

   The matrix: rows are workloads, columns are a default and the tiers that may
   override it. The layout turns on one rule, an INHERITED CELL CARRIES NO INK.
   Nine of sixteen cells normally run the default; repeating the value and its
   price in each of them buries the two that decide something under forty lines
   of identical gray text. The control's own first option says "same as the
   default" and nothing else in the cell repeats it.

   An inherited cell is NOT dimmed. Gray reads as disabled, which is a different
   and wrong claim; mark the exception rather than muting the norm. */
.sm-matrix { table-layout: fixed; }
.sm-matrix td { vertical-align: top; }
.sm-matrix th:first-child, .sm-matrix td:first-child { width: 17rem; }
.sm-matrix th:not(:first-child) { width: 20.75%; }
.sm-matrix .sm-selectw, .sm-matrix .sm-well { width: 100%; }
/* The default column is RULED OFF, not washed, because editing it moves every
   tier that follows it: the one risky control on the page is set apart at rest,
   rather than being explained in a dialog at save time.

   It used to carry `surface.subtle` as a fill. A fill on a column is a slab of
   color four rows tall that starts below the header and stops above the footer,
   so it reads as a highlight nobody applied rather than as a boundary, and it
   collides with the three cell states (set, dirty, broken) that DO use fill to
   mean something. A single rule running the full height of the table, header
   through footer, says the same thing and says only it. */
.sm-matrix th.def, .sm-matrix td.def {
  border-right: var(--border-width-hairline) solid var(--color-border-control); }
.sm-matrix .wl .nm { font-weight: var(--font-weight-semibold); }
.sm-matrix .wl .hint { font-size: var(--font-size-caption); color: var(--color-text-subtle);
  margin: var(--space-3xs) 0 0; }
.sm-matrix .cost { font-variant-numeric: tabular-nums; font-size: var(--font-size-small);
  color: var(--color-text-secondary); margin: var(--space-2xs) 0 0; }
/* A cell's third line is apparatus (the id behind a name, where a value came
   from, what a red cell will cost), so it sets like the row header's hint
   rather than like the cell's own words. */
.sm-matrix td .hint { font-size: var(--font-size-caption); color: var(--color-text-secondary);
  margin: var(--space-2xs) 0 0; }
/* Set rather than inherited: the modified marker, which the token system already
   defines for exactly this ("a value that differs from its default"). Never the
   only signal, per its own contract: the cell also shows the model and what it
   costs, and an inherited cell shows neither. */
.sm-matrix td.ovr { box-shadow: inset var(--border-width-marker) 0 0 var(--color-state-modified); }
.sm-matrix td.ovr .cost { color: var(--color-state-modified-text); }
/* Edited and not yet saved: the same marker escalated on two more channels, a
   fill and the weight, because "differs from the default" and "differs from what
   is saved" are different facts and the second one is temporary. The hover rule
   is restated so a row hover cannot wash the fill away. */
.sm-matrix td.dirty, .sm-matrix tbody tr:hover td.dirty {
  background: var(--color-state-modified-bg);
  box-shadow: inset var(--border-width-marker) 0 0 var(--color-state-modified); }
.sm-matrix td.dirty .cost { color: var(--color-state-modified-text);
  font-weight: var(--font-weight-semibold); }
/* Broken: an assigned value the provider no longer offers. Red, because the next
   job on this cell fails, which is a different fact from "differs". */
.sm-matrix td.bad, .sm-matrix tbody tr:hover td.bad { background: var(--color-feedback-error-bg);
  box-shadow: inset var(--border-width-marker) 0 0 var(--color-feedback-error); }
.sm-matrix td.bad .cost { color: var(--color-feedback-error); }
/* The totals row is the answer the page was opened for, so it gets the room to
   be one: the body's density is for scanning sixteen cells, and this is one
   line that is read. Room is what separates it, not weight. A 2px
   `border.strong` above it drew a black bar in light mode and a white one in
   dark, heavy enough to read as a warning, for a boundary the type size and the
   label ("A sermon costs") already state. */
.sm-matrix tfoot td, .sm-matrix tfoot th {
  border-top: var(--border-width-hairline) solid var(--color-border-control);
  vertical-align: baseline; padding-top: var(--space-md); padding-bottom: var(--space-md); }
.sm-matrix tfoot .tot { font-size: var(--font-size-title); font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums; display: block; }
.sm-matrix tfoot .sub { font-size: var(--font-size-small); color: var(--color-text-secondary);
  font-variant-numeric: tabular-nums; margin: var(--space-3xs) 0 0; }
/* A workload this page SHOWS but does not set. Rendering an empty text box for
   a value that is a code constant invites an edit the page will not honor and
   makes the row read as unconfigured; the value reads as text instead, sitting
   where the control would, so the row still scans as a row. */
.sm-matrix .ro { display: flex; align-items: center; min-height: var(--size-control-sm);
  font-size: var(--font-size-small); color: var(--color-text-primary); }
.sm-matrix .ro.q { color: var(--color-text-subtle); }

/* A family heading inside the catalog: a run of rows that belong together,
   announced by a row rather than by a column, so the grouping is structural and
   cannot be sorted away by accident. `scope="colgroup"` because it heads the
   rows beneath it across every column. */
.sm-table tr.grp > th { background: var(--color-surface-subtle);
  font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps);
  color: var(--color-text-brand); font-weight: var(--font-weight-semibold);
  border-bottom: var(--border-width-hairline) solid var(--color-border-subtle);
  /* Hairline, not the 2px `border.strong` this used to carry. The row already
     has a fill, small caps, the brand color and a count: the heavy rule was a
     fifth signal for one fact, and at 2px in dark mode it is a white bar. */
  border-top: var(--border-width-hairline) solid var(--color-border-control); }
.sm-table tr.grp > th .n { float: right; font-variant-caps: normal;
  letter-spacing: var(--letter-spacing-normal); color: var(--color-text-subtle);
  font-weight: var(--font-weight-regular); font-variant-numeric: tabular-nums; }
.sm-table tbody:first-of-type tr.grp > th { border-top: none; }
.sm-table tr.grp:hover > th { background: var(--color-surface-subtle); }

/* Standing conditions are rows, not banners. A banner is for an EVENT; a state
   that is simply true belongs beside the thing it describes. Three of them
   stacked as banners is about ninety words before any content, which is how a
   page teaches its reader to scroll past banners. Renders only when it has
   something in it: there is no empty "all clear" row. */
.sm-attn { list-style: none; margin: 0; padding: 0; border-radius: var(--radius-surface);
  border: var(--border-width-hairline) solid var(--color-border-subtle);
  background: var(--color-surface-card); overflow: hidden; }
.sm-attn > li { display: flex; gap: var(--space-sm); align-items: baseline; flex-wrap: wrap;
  padding: var(--space-sm) var(--space-md); font-size: var(--font-size-small);
  border-top: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-attn > li:first-child { border-top: none; }
.sm-attn .what { flex: 1 1 20rem; color: var(--color-text-secondary); }
.sm-attn .what b { color: var(--color-text-primary); font-weight: var(--font-weight-semibold); }
.sm-attn > li.bad { box-shadow: inset 3px 0 0 var(--color-feedback-error); }
.sm-attn > li.warn { box-shadow: inset 3px 0 0 var(--color-state-modified); }
.sm-attn > li.info { box-shadow: inset 3px 0 0 var(--color-border-control); }

/* The editor and the preview are different shapes: the grid holds every cell,
   this holds only the ones that changed. It appears once something has changed
   and not before, and it never appears without script, where there is nothing
   to diff against. */
.sm-pending { border: var(--border-width-hairline) solid var(--color-state-modified);
  border-radius: var(--radius-surface); background: var(--color-state-modified-bg);
  padding: var(--space-sm) var(--space-md); }
.sm-pending ul { list-style: none; margin: var(--space-xs) 0 0; padding: 0;
  font-size: var(--font-size-small); font-variant-numeric: tabular-nums; }
.sm-pending li { padding: var(--space-3xs) 0; }
.sm-pending .was { color: var(--color-text-secondary); }

/* ---------- marketing surface (patterns/marketing.md) ----------
   The fourth surface, and the only one whose reader has not chosen us yet.
   Same tokens, same type, same hairlines as the three inside the product: a
   church that reads the pricing page and then signs in should not feel the
   floor change (the argument patterns/auth.md makes about its own four pages,
   applied one door further out).

   What is NOT here, deliberately, because Principle 2 and the stated
   anti-goals rule them out on every surface and a marketing page is where they
   would arrive first: no hero photograph, no gradient, no illustration, no
   logo wall, no testimonial card, no badge, no counter that ticks. The page
   sells with type, air, apparatus and plain figures, or it does not sell. */

/* Price cards. Three of them, equal width, never four; under `regular` they
   stack, because a price read at a glance beside two others is the whole job
   and three 240px columns on a tablet is not that.

   The figure is Fira Sans semibold with PROPORTIONAL digits, exactly as
   components/stat.md rules for a value: Fraunces here would be a serif hero
   number, which that spec calls decoration, and tabular figures look loose at
   display sizes. */
.sm-tiers { display: grid; gap: var(--space-md); align-items: start;
  grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1023px) { .sm-tiers { grid-template-columns: 1fr; } }
.sm-tier { display: flex; flex-direction: column; gap: var(--space-sm); height: 100%; }
/* Name and marker share ONE row, and that is not a layout preference. A marker
   on a line of its own pushes the price of the card that carries it below the
   price of the two beside it, and three prices that do not line up is the one
   thing a price card must never do: comparing them is the whole job. */
.sm-tier .nmrow { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-sm); }
.sm-tier .nm { font-size: var(--font-size-body); font-weight: var(--font-weight-semibold);
  font-variant-caps: small-caps; letter-spacing: var(--letter-spacing-caps);
  color: var(--color-text-brand); }
.sm-tier .price { display: flex; align-items: baseline; gap: var(--space-2xs); }
.sm-tier .price .n { font-family: var(--font-body); font-size: var(--font-size-masthead);
  font-weight: var(--font-weight-semibold); line-height: var(--line-height-tight);
  color: var(--color-text-primary); }
@media (max-width: 640px) { .sm-tier .price .n { font-size: var(--font-size-display); } }
.sm-tier .price .per { font-size: var(--font-size-small); color: var(--color-text-subtle); }
.sm-tier .who { color: var(--color-text-secondary); margin: 0; }
.sm-tier dl { display: grid; grid-template-columns: 1fr auto; gap: var(--space-2xs) var(--space-sm);
  margin: 0; padding-top: var(--space-sm);
  border-top: var(--border-width-hairline) solid var(--color-border-subtle);
  font-size: var(--font-size-small); }
.sm-tier dt { color: var(--color-text-subtle); }
.sm-tier dd { margin: 0; text-align: right; color: var(--color-text-primary);
  font-variant-numeric: lining-nums tabular-nums; }
/* The one we recommend. An edge and a word, not a "most popular" badge: we have
   no popularity to report and Principle 4 rules out inventing one. */
.sm-tier--pick { border-color: var(--color-state-selected-border); }
.sm-tier .pick { font-size: var(--font-size-caption); font-weight: var(--font-weight-semibold);
  letter-spacing: var(--letter-spacing-wide); text-transform: uppercase;
  color: var(--color-text-brand); }
/* The act sits at the bottom edge of every card whatever the card above it
   says, so the three buttons line up across the row. */
.sm-tier > .sm-acts { margin-top: auto; padding-top: var(--space-sm); }
.sm-tier > .sm-acts > .sm-btn { width: 100%; }

/* Dated claims about somebody else. A comparison page states the month its
   facts were checked, in the page's own apparatus rather than in small print
   at the bottom, because the date is what makes the claim honest and a claim
   whose date is hidden is a claim making a different promise. */
.sm-asof { display: flex; align-items: baseline; gap: var(--space-xs); flex-wrap: wrap;
  font-family: var(--font-body); font-size: var(--font-size-small);
  color: var(--color-text-subtle); margin: var(--space-sm) 0 0; }
.sm-asof .sm-sc { color: var(--color-text-brand); }
/* A source, cited where the claim is, the way a scripture reference is cited
   where it is quoted. Not a footnote: a footnote is read by nobody. */
.sm-src { display: block; font-size: var(--font-size-caption);
  color: var(--color-text-subtle); margin: var(--space-2xs) 0 0; overflow-wrap: anywhere; }
.sm-src a { color: var(--color-text-subtle); text-underline-offset: 3px;
  text-decoration-color: var(--color-border-link);
  transition: color var(--duration-fast), text-decoration-color var(--duration-fast); }
.sm-src a:hover { color: var(--color-text-action); text-decoration-color: currentColor; }

/* ---------- site footer (patterns/marketing.md, "The footer") ----------
   The first footer the system has specified, though the hosted library has
   shipped an unspecified one since Phase 3. It exists because two things have
   to be reachable from the bottom of a page and neither may go in the colophon
   line, which patterns/layout.md defines as "figures, no links, no acts": the
   legal pages a cautious board reads before signing, and the status page
   (#238), whose whole value is that somebody can find it while something is
   broken.

   Columns on the marketing site; a single row everywhere inside the product,
   where a page has at most two or three places to send anybody. */
footer.sm-sitefooter { margin-top: var(--space-3xl);
  background: var(--color-surface-card);
  border-top: var(--border-width-hairline) solid var(--color-border-strong);
  font-family: var(--font-body); font-size: var(--font-size-small); }
.sm-sitefooter .cols { display: grid; gap: var(--space-lg) var(--space-2xl);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  padding: var(--space-2xl) 0 var(--space-lg); }
.sm-sitefooter .col > .sm-microlabel { margin: 0 0 var(--space-2xs); }
.sm-sitefooter ul { list-style: none; margin: 0; padding: 0; }
.sm-sitefooter li a { display: inline-flex; align-items: center;
  min-height: var(--size-target-min); padding: var(--space-2xs) 0;
  color: var(--color-text-secondary); cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--color-border-link);
  transition: color var(--duration-fast), text-decoration-color var(--duration-fast); }
.sm-sitefooter li a:hover { color: var(--color-text-action);
  text-decoration-color: currentColor; }
/* The closing line keeps the colophon's contract exactly: figures and facts,
   no acts. The links live in the columns above it. */
.sm-sitefooter .sm-colophon { padding-bottom: var(--space-2xl); }
/* Inside the product there are no columns: one row of two or three links under
   a hairline, which is all a dashboard or a library page owes its reader, and
   the theme control at the row's far end (space-between). A line's only item
   sits at its start, so when the row wraps on a phone the control drops under
   the links at the left edge rather than hanging off the right. Items share a
   text baseline. */
.sm-sitefooter--row .cols { display: flex; flex-wrap: wrap; gap: var(--space-2xs) var(--space-lg);
  justify-content: space-between; padding: var(--space-lg) 0; align-items: baseline; }
.sm-sitefooter--row .col > .sm-microlabel { display: none; }
.sm-sitefooter--row ul { display: flex; flex-wrap: wrap; gap: var(--space-lg); }
/* A footer that opens on a name (the library: the church's whole name) sets it
   on a line of its own with the theme control at the line's end, the same
   space-between grammar as the row, so a name that needs the width pushes the
   control under it at the left edge. The control's words sit on the name's
   baseline, not on the middle of its box. */
.sm-sitefooter .line { display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: var(--space-2xs) var(--space-lg); }
.sm-sitefooter .line > :first-child { min-width: 0; }
/* In the columns variant (the marketing site, #1018) the theme control is the
   grid's last item: a fourth track at the end of the first row where the page
   holds four, and the next row's first cell where it does not, so on a phone it
   sits under the last column at the left edge. It keeps its own width rather
   than stretching across the cell, and its words share the baseline of the
   columns' microlabels, so it reads as the row's last word. Its 44px target is
   taller than its line, so it overhangs the line by half the difference, upward
   into the band's padding, rather than pushing the columns down to meet it. */
.sm-sitefooter:not(.sm-sitefooter--row) .cols { align-items: baseline; }
.sm-sitefooter:not(.sm-sitefooter--row) .cols > .sm-themeswitch { justify-self: start;
  margin-top: calc((var(--font-size-small) * var(--line-height-snug) - 44px) / 2); }

/* Theme control (#1012, the owner's choice from rendered options, 2026-10-14):
   light and dark, two positions, never three, as quiet text at the end of the
   footer's first line. It was a bordered button in the header, where it was the
   loudest thing in the masthead after the church's name; the device's
   preference already decides for anyone who never presses it, so it belongs
   where a reader looks for a setting, not where they look for the page. No
   border and no box: footer ink at footer size, the ink stepping up and an
   underline on hover, and a 44px target, the chrome controls' own.

   The device's preference IS the default, so a "System" position would be a
   control for the state a reader who has never pressed anything is already in.
   The button names the theme it switches TO. Both faces are in the markup and
   CSS shows the one that matches the theme in force, so the right label paints
   on the first frame. Nothing here waits for a script: the only thing
   JavaScript does is stamp data-theme on <html> and remember the choice, which
   is why a themed page never flashes the wrong label. The three-state selector
   grammar is tokens.css's own, repeated: the media query is guarded with
   :not([data-theme="light"]) so an explicit light choice wins on a dark device.
   It is the only shape the control takes: the marketing site's header button
   (.sm-themebtn) retired when that site moved its control to the footer too
   (#1018). */
.sm-themeswitch { display: inline-flex; align-items: center; flex: none;
  min-height: 44px; padding: 0; margin: 0; cursor: pointer;
  background: none; border: 0; border-radius: var(--radius-control);
  color: var(--color-text-subtle); text-decoration: none; text-underline-offset: 3px;
  font-family: var(--font-body); font-size: var(--font-size-small);
  font-weight: var(--font-weight-regular); line-height: var(--line-height-snug);
  transition: color var(--duration-fast); }
/* The words carry the face's baseline, and so the button's, which is what lets
   the line above align the control by its text; the icon centers on them. */
.sm-themeswitch > span { display: inline-flex; align-items: baseline; gap: var(--space-2xs); }
.sm-themeswitch svg { width: var(--size-icon-sm); height: var(--size-icon-sm); flex: none;
  align-self: center; }
.sm-themeswitch:hover { color: var(--color-text-primary); text-decoration: underline; }
.sm-themeswitch .to-light { display: none; }
[data-theme="dark"] .sm-themeswitch .to-dark { display: none; }
[data-theme="dark"] .sm-themeswitch .to-light { display: inline-flex; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .sm-themeswitch .to-dark { display: none; }
  :root:not([data-theme="light"]) .sm-themeswitch .to-light { display: inline-flex; }
}

/* ---------- demo scaffolding (styleguide only) ---------- */
.sm-demo { font-family: var(--font-body); font-size: var(--font-size-body); background: var(--color-surface-page);
  padding: var(--space-md); border-radius: var(--radius-surface);
  border: var(--border-width-hairline) solid var(--color-border-subtle); }
.sm-demo--card { background: var(--color-surface-card); }
.sm-demo--sunken { background: var(--color-surface-sunken); }
.sm-demo .row { display: flex; gap: var(--space-sm); align-items: flex-start; flex-wrap: wrap; }

@media (prefers-reduced-motion: reduce) {
  [class*="sm-"], [class*="sm-"] * { transition: none !important; }
  .sm-btn:active { transform: none; }
}

/* ---------- print (SSM-158) ---------- */
/* What a printed page IS, decided by the owner 2026-09-20: the web page tidied,
   not a document. The document already exists and is better: the pipeline renders
   a tenant-letterhead transcript PDF (packages/pdf, `transcriptPdfKey`). So this
   block does one job, which is to stop spending the first inch of a sheet on
   navigation nobody can click.
   The tokens sheet (lib/tokens.mjs) already forces the light theme for print, so
   what prints is the page rather than the reader's preference. */
@media print {
  /* Anything you act on. A control on paper is ink spent on a lie: it looks
     pressable and is not. The theme control is the oldest of these (SSM-157): a
     sheet with "Light theme" at its foot is a sheet with a button on it. */
  .sm-themeswitch,
  .sm-menu,
  .sm-acts,
  .sm-siteheader nav,
  .sm-player,
  .sm-fallback-player,
  .sm-minibar,
  .sm-video { display: none; }
  /* Element-qualified, to match the base rules. `a.sm-back` and `input.sm-scrub`
     each carry an element in their own selector, so a bare class here loses on
     specificity and the control prints anyway. Found by checking the computed
     display in a browser under print emulation rather than by reading the file. */
  a.sm-back,
  input.sm-scrub { display: none; }

  /* The header keeps the one thing that is not navigation: whose sermon this is.
     What was a band with a brand at one end and links at the other becomes a
     masthead line over a rule, which is what it already looks like with the
     links removed. */
  .sm-siteheader { display: block; background: none; padding: 0 0 var(--space-xs);
    border-bottom: var(--border-width-hairline) solid var(--color-border-strong); }
  .sm-siteheader .brand { text-decoration: none; }

  /* The transcript loses its margin strip. That column holds the seek button,
     whose timestamp chip is invisible at rest and only appears where the time
     means something on screen (transcript.md, SSM-145), so on paper it is 76px
     of nothing down the whole left edge. The prose keeps its reading measure. */
  .sm-tp { grid-template-columns: minmax(0, 1fr); }
  .sm-tp .seek { display: none; }
  /* A paragraph broken across two sheets mid-sentence is the one thing paper
     does worse than a screen, and the one thing paper can fix. */
  .sm-tp { break-inside: avoid; }

  /* The colophon already reads as a closing line under a hairline. On paper that
     is a footer, so let it be one. */
  .sm-colophon { margin-top: var(--space-lg); }

  @page { margin: 2cm; }
}

/* ---------- Settings apparatus: shows-on notes, recent changes (#1151) ---------- */
/* Appended after everything above, never spliced into the margin block:
   scripts/dashboard-layout.spec.ts reads that block literally. */
/* "Shows on:" under a group's margin sentence: where the group's values appear,
   one quiet line, written only where the code makes it true. */
.sm-formsec > .lbl > .shows { margin: var(--space-2xs) 0 0; font-size: var(--font-size-caption);
  line-height: var(--line-height-snug); color: var(--color-text-subtle); text-wrap: pretty; }
.sm-formsec > .lbl > .sub + .shows { margin-top: var(--space-xs); }
/* Recent changes, on Review and publishing (#1151): a panel of dated plain
   lines, each under a hairline but the first. */
.sm-revisions { list-style: none; margin: 0; padding: 0; min-width: 0; }
.sm-revisions li { display: grid; grid-template-columns: 1fr; gap: var(--space-3xs);
  padding: var(--space-sm) 0; border-top: var(--border-width-hairline) solid var(--color-border-subtle);
  font-size: var(--font-size-small); }
.sm-revisions li:first-child { padding-top: 0; border-top: 0; }
.sm-revisions time { color: var(--color-text-secondary); font-weight: var(--font-weight-medium);
  font-variant-numeric: tabular-nums; }
.sm-revisions li > span { text-wrap: pretty; overflow-wrap: anywhere; }
@media (min-width: 760px) {
  .sm-revisions li { grid-template-columns: 96px minmax(0, 1fr); gap: var(--space-md); align-items: baseline; }
}
/* The pane's closing line sits in the pane's own rhythm, not the page's. */
.sm-settings .sm-colophon { margin: 0; }

