/* Generated by scripts/sync-ds.py. Do not edit: change the design system and re-run.
   Source: bwpromo/design@db10f23b50f7c1aaa132fe8b67fd1484382f81ab design-system/components.css
   Sections: Buttons, Inputs (base of the search field), Brand logo (OnboardingHeader, AppFooter), Search field · Topic card · Search result · Prose, Breadcrumbs, Segmented control, Menu (OnboardingHeader language menu), Onboarding header, App footer, Disclosure (accordion), Callout. */

/* ═══ components.css § Buttons · source lines 16–215 ═══ */
/* ─── Buttons ───
   Full update 2026-11-03 · DES-137 · feat/ds-button — token-only.
   Variants (interaction weight, high → low): btn-accent · btn-primary · btn-outline · btn-ghost
   Destructive pair: btn-destructive — fires the irreversible action, from the destructive
                     confirmation modal (and, at .btn-xs, the record Actions-panel trigger)
                   · btn-outline-destructive — low-weight trigger that opens that modal
   Sizes (height): default 56 (Medium / workhorse · --input-height) · .btn-sm 40 · .btn-xs 32.
     Below 640 px sm + xs grow to the 44 px touch floor (§ Touch-density auto-engage).
   Width: .btn--block 100% (action footers, stacked CTAs).
   Anatomy: label (required) · leading icon .btn__icon (optional, what the action does) ·
     trailing icon (optional, direction only) · .cta-sep + price (optional).
     Icons size by button size: --icon-inline 16 · xs --icon-meta 14.
   Loading: .btn--loading + aria-busy — the spinner replaces the leading .btn__icon.
   Retired 2026-11-03 (CSS aliases kept until apps migrate): .btn-white ·
     .btn-ghost-destructive (→ .btn-outline-destructive) · .btn--touch / .btn--mobile.
   Hover steps one shade · active steps two — per CLAUDE.md "Hover, press, focus, disabled". */
:root {
  --btn-height:            var(--input-height);       /* 56 */
  --btn-height-sm:         var(--control-height-sm);  /* 40 */
  --btn-height-xs:         var(--space-8);            /* 32 */
  --btn-pad-x:             var(--space-8);
  --btn-pad-x-sm:          var(--space-5);
  --btn-pad-x-xs:          var(--space-4);
  --btn-gap:               var(--space-2);
  --btn-gap-xs:            var(--space-1-5);
  --btn-icon:              var(--icon-inline);        /* 16 · default + sm */
  --btn-icon-xs:           var(--icon-meta);          /* 14 */
  --btn-border:            2px;                       /* outline variants · heavier than the 0.5px hairline on purpose */
  --btn-disabled-opacity:  0.4;
  /* Accent fill is button-scoped (rev. 2026-11-03 · DES-137): white on the
     db-400 --color-interactive-accent is 3.10:1 and fails 1.4.3, so the CTA
     fills with vivid Daybreak — the same 203° hue at full saturation, the
     brightest step that passes: vivid-500 (4.62:1) · hover vivid-600 (5.65:1)
     · active vivid-700 (7.07:1). The Daybreak glow stays db-400. */
  --btn-accent-bg:         var(--color-db-vivid-500);
  --btn-accent-bg-hover:   var(--color-db-vivid-600);
  --btn-accent-bg-active:  var(--color-db-vivid-700);
  --btn-spinner:           14px;
  --btn-spinner-sm:        12px;
  --btn-spinner-xs:        11px;
  --btn-spinner-stroke:    2px;
  --btn-spinner-stroke-xs: 1.5px;
}
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--btn-gap);
  height: var(--btn-height);
  padding: 0 var(--btn-pad-x);
  border: none;
  border-radius: var(--radius-action);
  font-family: var(--font-inter);
  font-size: var(--text-button-size);
  font-weight: var(--text-button-weight);
  line-height: var(--text-button-leading);
  letter-spacing: var(--text-button-tracking);
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;         /* for a.btn — <button>/<input> ignore */
  box-sizing: border-box;        /* contains padding + outline border within width — required for .btn--block in flex columns */
  /* Selection · hover and press fade colour, background and border at fast
     (rev. 2026-10-26). Never position, size or shadow: the accent glow swaps
     instantly. Under reduced motion the fade caps at fast and stays. */
  transition-property: color, background-color, border-color;
  transition-duration: var(--motion-duration-fast);
  transition-timing-function: var(--motion-ease-standard);
}
/* The same Selection hover / press fade on every other interactive control
   (rev. 2026-10-26). One shared rule so the property list and timing never
   drift between components; the components' own rules (later in this file)
   set colours only. Never position, size or shadow. */
.icon-btn,
.menu__item,
.quick-filter,
.choice-pill,
.crumbs a,
.crumbs .more,
.data-grid__row--body .data-grid__cell {
  transition-property: color, background-color, border-color;
  transition-duration: var(--motion-duration-fast);
  transition-timing-function: var(--motion-ease-standard);
}
/* Icons (Phosphor <i> or an SVG <Icon>) size by the button size. */
.btn > i   { flex-shrink: 0; font-size: var(--btn-icon); }
.btn > svg { flex-shrink: 0; width: var(--btn-icon); height: var(--btn-icon); }

.btn-accent  {
  background: var(--btn-accent-bg);
  color: var(--color-white);
  /* Matched-colour Daybreak glow · added 2026-05-20. Hover lifts, :active
     compresses, disabled removes it. Scoped to .btn-accent only — every
     other variant stays flat. */
  box-shadow: var(--shadow-accent);
}
.btn-accent:hover  { background: var(--btn-accent-bg-hover);  box-shadow: var(--shadow-accent-hover); }
.btn-accent:active { background: var(--btn-accent-bg-active); box-shadow: var(--shadow-accent-pressed); }
.btn-accent:disabled,
.btn-accent[aria-disabled="true"] { box-shadow: none; }

.btn-primary { background: var(--color-interactive-primary); color: var(--color-white); }
.btn-primary:hover  { background: var(--color-bh-800); }
.btn-primary:active { background: var(--color-bh-900); }

/* .btn-outline hover is a fill flip (rev. 2026-11-03 · DES-137 · approved by
   Justin): the bh-50 wash was 1.1:1 against white and barely read, so hover
   fills Blue Hour with white ink (14.01:1) and press steps to bh-800
   (16.76:1). Colour, background and border only, at fast. */
.btn-outline { background: transparent; color: var(--color-text-heading); border: var(--btn-border) solid var(--color-text-heading); }
.btn-outline:hover  { background: var(--color-interactive-primary); color: var(--color-white); }
.btn-outline:active { background: var(--color-bh-800); border-color: var(--color-bh-800); color: var(--color-white); }

/* .btn-ghost — lowest interaction weight. No fill, no border. Hover reveals
   a soft Cloud chip; active steps to bh-100. Use for tertiary actions that
   should fade into the chrome (table-toolbar utilities, popover-footer
   "Cancel", inline secondary that must not draw from the Daybreak budget). */
.btn-ghost   { background: transparent; color: var(--color-text-heading); }
.btn-ghost:hover  { background: var(--color-bh-50); }
.btn-ghost:active { background: var(--color-bh-100); }

/* .btn-destructive — fires the irreversible action from the canonical
   destructive confirmation modal (components-modals.html); at .btn-xs it is
   also the record Actions-panel trigger. Never a primary on the surface that
   initiates the action — see the destructive trigger contract. */
.btn-destructive { background: var(--status-critical-icon); color: var(--color-white); }
.btn-destructive:hover  { background: var(--status-critical-hover); }
.btn-destructive:active { background: var(--status-critical-active); }

/* .btn-outline-destructive — low-weight destructive TRIGGER (renamed from
   .btn-ghost-destructive 2026-11-03 · DES-137: it carries a border, so it is
   an outline). Scarlet ink + outline at rest; hover adds the critical tint and
   steps the ink to critical-hover (5.21:1 — critical-icon on the tint is 4.28
   and fails 1.4.3); active steps to critical-active. Opens the destructive
   modal only. First consumer: MP team member detail · Remove Member.
   .btn-ghost-destructive is a retired alias, kept until apps migrate. */
.btn-outline-destructive,
.btn-ghost-destructive { background: transparent; color: var(--status-critical-icon); border: var(--btn-border) solid var(--status-critical-icon); }
.btn-outline-destructive:hover,
.btn-ghost-destructive:hover  { background: var(--status-critical-bg); border-color: var(--status-critical-hover); color: var(--status-critical-hover); }
.btn-outline-destructive:active,
.btn-ghost-destructive:active { background: var(--status-critical-bg); border-color: var(--status-critical-active); color: var(--status-critical-active); }

/* .btn-white — RETIRED 2026-11-03 (DES-137): the system is light-only and it
   had no consumer. Kept so nothing breaks; do not use. */
.btn-white   { background: var(--color-white); color: var(--color-text-heading); }
.btn-white:hover  { background: var(--color-bh-50); }
.btn-white:active { background: var(--color-bh-100); }

.btn-sm {
  height: var(--btn-height-sm);
  padding: 0 var(--btn-pad-x-sm);
  font-size: var(--text-button-sm-size);
  line-height: var(--text-button-sm-leading);
  letter-spacing: var(--text-button-sm-tracking);
}
/* .btn-xs — compact size for tight surfaces: peek-drawer and popover footers,
   sub-record adds, dense toolbars, row actions. */
.btn-xs {
  height: var(--btn-height-xs);
  padding: 0 var(--btn-pad-x-xs);
  gap: var(--btn-gap-xs);
  font-size: var(--text-button-xs-size);
  line-height: var(--text-button-xs-leading);
  letter-spacing: var(--text-button-xs-tracking);
}
.btn-xs > i   { font-size: var(--btn-icon-xs); }
.btn-xs > svg { width: var(--btn-icon-xs); height: var(--btn-icon-xs); }

.btn--block { width: 100%; display: flex; }
/* Stacked CTA pair: --space-3 gap, secondary uses .btn-outline. Never two filled. */

.btn:disabled,
.btn[aria-disabled="true"] { opacity: var(--btn-disabled-opacity); cursor: not-allowed; pointer-events: none; }
.btn:focus-visible { outline: var(--ring-focus); outline-offset: var(--ring-focus-offset); }

/* .btn--loading — in-flight state for save / submit / sign actions.
   The spinner replaces the leading .btn__icon and uses currentColor, so it
   inherits the variant's ink. Consumers must:
     · set aria-busy="true" (and aria-disabled — <Button loading> sets both)
     · keep the label text in the DOM (screen readers read it alongside aria-busy)
     · disable submission server-side too — pointer-events alone is not a guarantee
   <input type="submit"> cannot render ::before — loading state requires <button>. */
.btn--loading { pointer-events: none; cursor: wait; }
.btn--loading > .btn__icon { display: none; }
.btn--loading::before {
  content: "";
  width: var(--btn-spinner);
  height: var(--btn-spinner);
  border: var(--btn-spinner-stroke) solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  flex-shrink: 0;
  animation: dn-spin var(--motion-duration-spin) var(--motion-ease-linear) infinite;
}
.btn-sm.btn--loading::before { width: var(--btn-spinner-sm); height: var(--btn-spinner-sm); }
.btn-xs.btn--loading::before { width: var(--btn-spinner-xs); height: var(--btn-spinner-xs); border-width: var(--btn-spinner-stroke-xs); }
@media (prefers-reduced-motion: reduce) {
  .btn--loading::before { animation: none; }
}

/* ═══ components.css § Inputs (base of the search field) · source lines 401–453 ═══ */
/* ─── Inputs ─── */
.field { display: flex; flex-direction: column; gap: var(--space-2); }
/* Field text is on the roles (rev. 2026-10-30 · 4px rhythm): the label is
   label (13 / 500 / 20, was 12 / 600 / 1.55); optional, scope and helper are
   meta (12 / 16, were 11 / 1.5). */
.field__label { font-size: var(--text-label-size); font-weight: var(--text-label-weight); line-height: var(--text-label-leading); color: var(--text-label-color); }
.field__optional { font-size: var(--text-meta-size); font-weight: var(--text-meta-weight); line-height: var(--text-meta-leading); color: var(--text-meta-color); margin-left: var(--space-1-5); }
/* Faint audience / visibility annotation beside a label (e.g. "External" ·
   "Internal") — a scope tag, not an optionality tag. Same faint treatment as
   .field__optional so label-adjacent metadata reads as one quiet family. */
.field__scope { font-size: var(--text-meta-size); font-weight: var(--text-meta-weight); line-height: var(--text-meta-leading); color: var(--text-meta-color); margin-left: var(--space-1-5); }
.field__helper { font-size: var(--text-meta-size); line-height: var(--text-meta-leading); color: var(--text-meta-color); }
.field__error-message {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  background: var(--color-validation-bg);
  border: 0.5px solid var(--color-validation-border);
  border-radius: 0 0 6px 6px;
  padding: 7px 12px;
  font-size: 11px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--color-validation-text);  /* #7C2D12 · ≈9:1 (AAA). NOT --color-validation-warning (#EA580C · ~3.4:1 · fails 1.4.3 as text); the orange stays for the .input--error border + icon only. */
  margin-top: -1px;
}
/* Its display: flex would otherwise beat the hidden attribute. */
.field__error-message[hidden] { display: none; }
/* Leading validation icon (ph-warning-circle) · the one place the Validation
   Orange shows as a glyph: it's a non-text symbol (passes 1.4.11), so it may
   carry the orange the message text must not. Promoted from the primitives
   preview so the canonical primitive renders the icon consumers already ship. */
.field__error-message i,
.field__error-message svg { color: var(--color-validation-warning); flex-shrink: 0; }

.input {
  height: var(--input-height);
  border-radius: var(--radius-md);
  border: 1.5px solid var(--color-border-control);
  padding: 0 var(--space-4);
  font-family: var(--font-inter);
  font-size: 13px;
  color: var(--color-text-heading);
  background: var(--color-white);
  width: 100%;
  box-sizing: border-box;
}
.input::placeholder { color: var(--color-placeholder); opacity: 1; }
.input:focus-visible { outline: none; border: 2px solid var(--color-focus-ring); }
.input:disabled { background: var(--color-surface-page); border-color: var(--color-border-subtle); opacity: 0.4; cursor: not-allowed; }
.input--error { border: 2px solid var(--color-validation-warning); }
.input--success { border: 2px solid var(--status-resolved-icon); }

/* ═══ components.css § Brand logo (OnboardingHeader, AppFooter) · source lines 3368–3413 ═══ */
/* ─── Brand logo ───
   The one Docnote mark (JSX components/BrandLogo.tsx · brand-logo rebuild
   2026-10-27 · docs/brand-logo-spec.md · preview/brand-logo.html). Artwork is
   the tight crop (assets/logos/wordmark-* · symbol-*, no built-in whitespace),
   so the height IS the visible glyph height.
   ─ size     --brand-logo-h, from a size class or inherited from the slot
              (.sidebar__brand, .onboarding-header__brand …); md when neither.
   ─ clearspace  half the height on every side, built in; --flush drops it
              where the parent already spaces the mark.
   ─ tone     swap the file (wordmark-blue-hour / -white / -black), never a
              CSS filter. --inverse only adjusts the lockup text and focus ring
              for bh-700 surfaces.
   ─ link     an <a class="brand-logo"> named "Docnote home" takes the focus ring.
   No motion. */
.brand-logo {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  box-sizing: content-box;
  padding: calc(var(--brand-logo-h, var(--brand-logo-md)) / 2);
  line-height: 0;
  text-decoration: none;
  border-radius: var(--radius-control);
}
.brand-logo--sm { --brand-logo-h: var(--brand-logo-sm); }
.brand-logo--md { --brand-logo-h: var(--brand-logo-md); }
.brand-logo--lg { --brand-logo-h: var(--brand-logo-lg); }
.brand-logo--xl { --brand-logo-h: var(--brand-logo-xl); }
.brand-logo--flush { padding: 0; }
.brand-logo__img {
  display: block;
  height: max(var(--brand-logo-h, var(--brand-logo-md)), var(--brand-logo-min));
  width: auto;
  max-width: none;
}
a.brand-logo:focus-visible { outline: var(--ring-focus); outline-offset: var(--ring-focus-offset); }
/* "Powered by" lockup — the text leads, the wordmark follows on one baseline row. */
.brand-logo--lockup { gap: var(--space-2); line-height: var(--text-meta-leading); }
.brand-logo__lead {
  font-size: var(--text-meta-size);
  font-weight: var(--weight-medium);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.brand-logo--inverse .brand-logo__lead { color: var(--color-white); }

/* ═══ components.css § Search field · Topic card · Search result · Prose · source lines 6589–6910 ═══ */
/* ─── Search field ─── .search-field (added 2026-10-19 · pill family rev. 2026-10-23)
   The one search input for every surface — a pill with a leading magnifier,
   the canonical .input as the control, and a trailing clear (×) while there is
   a query. Always wrapped in <form role="search"> with a required accessible
   name. Three sizes, picked by the job the search does:
     --sm     40px (--control-height-sm) · table and list toolbars beside 40px
              controls, menus, the top bar · 44px on touch
     default  56px (--input-height) · page-level filtering beside .quick-filters
     --hero   64px (--control-height-lg) · 16px text · the page's one primary
              search (help centre)
   Shared: --radius-pill (a sanctioned exception to radius intent — see
   CLAUDE.md § Borders, shadows, corners), the 1.5px control border, placeholder
   ink, a bh-500 magnifier, and the dark Blue Hour focus edge. The × glyph sits
   at the same inset from the right as the magnifier from the left at every
   size. Replaces .tbl__search, .summary-list-record__search, --lg and --pill
   (retired 2026-10-23). JSX components/SearchField.tsx.
     <form class="search-field search-field--sm" role="search" aria-label="Patients">
       <svg class="search-field__icon" aria-hidden="true">…</svg>
       <input class="input search-field__input" type="search" aria-label="Search patients">
       <button class="search-field__clear" type="button" aria-label="Clear search" hidden>×</button>
     </form> */
.search-field {
  /* Each size sets these knobs; every measurement below derives from them. */
  --sf-h: var(--input-height);
  --sf-icon: 20px;
  --sf-inset: var(--space-4);
  --sf-clear: 32px;
  --sf-font: var(--font-size-label-md);
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
}
.search-field--sm {
  --sf-h: var(--control-height-sm);
  --sf-icon: 16px;
  --sf-inset: var(--space-3);
  --sf-clear: 28px;
}
.search-field--hero {
  --sf-h: var(--control-height-lg);
  --sf-icon: 24px;
  --sf-inset: var(--space-5);
  --sf-clear: 40px;
  --sf-font: var(--font-size-body-md);
}
.search-field__icon {
  position: absolute;
  left: var(--sf-inset);
  top: 50%;
  transform: translateY(-50%);
  width: var(--sf-icon);
  height: var(--sf-icon);
  font-size: var(--sf-icon);   /* sizes the glyph when the icon is a Phosphor <i> (HTML previews) */
  color: var(--color-bh-500);
  pointer-events: none;
}
/* Scoped under .search-field so it outranks the later touch-density
   `.input { height: var(--input-height) }` rule below --bp-mobile. */
.search-field .search-field__input {
  height: var(--sf-h);
  font-size: var(--sf-font);
  border-radius: var(--radius-pill);
  padding-left: calc(var(--sf-inset) + var(--sf-icon) + var(--space-3));     /* inset + icon + gap */
  padding-right: calc(var(--sf-inset) + (var(--sf-clear) + 16px) / 2 + var(--space-2)); /* clear room */
}
/* Focus — the dark Blue Hour edge (settled 2026-10-23): the 1.5px border turns
   heading ink and an outer 1.5px ring of the same colour thickens it to 3px
   without shifting the text. The magnifier darkens with it. Replaces the
   generic .input 2px Daybreak focus edge for search only. */
.search-field__input:focus,
.search-field__input:focus-visible {
  outline: none;
  border: 1.5px solid var(--color-text-heading);
  box-shadow: 0 0 0 1.5px var(--color-text-heading);
}
.search-field:focus-within .search-field__icon { color: var(--color-text-heading); }
/* Native search-cancel glyph off — the canonical clear button replaces it. */
.search-field__input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.search-field__clear {
  position: absolute;
  right: calc(var(--sf-inset) - (var(--sf-clear) - 16px) / 2);   /* 16px glyph mirrors the magnifier */
  top: 50%;
  transform: translateY(-50%);
  width: var(--sf-clear);
  height: var(--sf-clear);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-bh-500);
  cursor: pointer;
}
.search-field__clear[hidden] { display: none; }
.search-field__clear:hover { background: var(--color-bh-50); color: var(--color-text-heading); }
.search-field__clear:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: 1px; }
/* Toolbar slot — inside a table toolbar (incl. the data grid's) or a filter slot the field
   takes the bounded 320px basis / 240px floor (first set by MP Accounts
   Receivable) instead of the full row. Zero specificity, so a consumer's
   layout class (wider basis, fixed width) always wins. */
:where(.tbl__toolbar, .data-grid__toolbar, .data-grid__filter) :where(.search-field) { flex: 0 1 320px; min-width: 240px; }
:where(.data-grid__filter) > :where(.search-field) { max-width: 320px; }
/* Touch density below --bp-mobile: --sm grows to the 44px floor and every size
   takes 16px text so iOS does not zoom the page on focus. */
@media (max-width: 639px) {
  .search-field { --sf-font: var(--font-size-body-md); }
  .search-field--sm { --sf-h: var(--touch-target-min); --sf-clear: 32px; }
}

/* ─── Topic card ─── .topic-grid / .topic-card (added 2026-10-19)
   Help-centre "Explore all topics" tiles: a soft neutral tile, centred content —
   a white circular icon badge, a title, and a one-line summary. The whole tile
   is the link (<a class="topic-card">). Grid: 3 columns, 2 at ≤768px, 1 at
   ≤480px. First consumer: the MP public help page (/help).
     <div class="topic-grid">
       <a class="topic-card" href="…">
         <span class="topic-card__icon" aria-hidden="true"><svg…/></span>
         <h3 class="topic-card__title">Sending service requests</h3>
         <p class="topic-card__desc">Creating, sending and cancelling requests.</p>
       </a>
     </div>
   No motion — hover adds a border instantly; focus is the canonical ring. */
.topic-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-5);
}
@media (max-width: 768px) { .topic-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px) { .topic-grid { grid-template-columns: minmax(0, 1fr); } }
.topic-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-6) var(--space-8);
  border-radius: var(--radius-lg);
  border: 2px solid transparent;   /* reserved so the hover border causes no shift */
  background: var(--color-surface-neutral);
  color: inherit;
  text-decoration: none;
}
/* Hover — a 2px heading-colour border, the same affordance as the data-grid
   row action (.data-grid__view). Instant, no motion. */
.topic-card:hover { border-color: var(--color-text-heading); }
.topic-card:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: 2px; }
.topic-card__icon {
  width: 48px;
  height: 48px;
  margin-bottom: var(--space-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--color-white);
  color: var(--color-text-heading);
  box-shadow: 0 0 0 0.5px var(--color-bh-200);
}
.topic-card__icon svg { width: 24px; height: 24px; }
.topic-card__title {
  margin: 0;
  font-size: var(--font-size-body-lg);
  font-weight: var(--weight-semibold);
  line-height: 1.3;
  color: var(--color-text-heading);
}
.topic-card__desc {
  margin: 0;
  font-size: var(--font-size-body-sm);
  line-height: 1.55;
  color: var(--color-text-muted-on-white);
}

/* --flip · MOTION EXCEPTION #14 (2026-10-19) — when the grid's content set
   changes (help centre Clinic ↔ Patient audience switch), the new cards
   rotate in on the Y axis from edge-on (90°) to flat, with a light fade:
   --motion-duration-base each with the enter ease, the second card half a
   --motion-stagger behind and the rest one stagger behind, so the whole flip
   ends by 320ms, inside the 360ms budget (rev. 2026-10-26, was 450ms with a
   200ms tail). Transform + opacity only. Consumer contract:
   remount the grid on the switch (React key) and add .topic-grid--flip only
   AFTER the first user switch, so nothing animates on page load. Halts under
   prefers-reduced-motion (the cards simply swap). */
.topic-grid--flip { perspective: 1200px; }
.topic-grid--flip .topic-card {
  animation: topic-card-flip var(--motion-duration-base) var(--motion-ease-enter) both;
  backface-visibility: hidden;
  transform-origin: center;
}
.topic-grid--flip .topic-card:nth-child(2) { animation-delay: calc(var(--motion-stagger) / 2); }
.topic-grid--flip .topic-card:nth-child(n+3) { animation-delay: var(--motion-stagger); }
@keyframes topic-card-flip {
  from { transform: rotateY(-90deg); opacity: 0; }
  60%  { opacity: 1; }
  to   { transform: rotateY(0deg); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .topic-grid--flip .topic-card { animation: none; }
}

/* ─── Search result ─── .search-results / .search-result (added 2026-10-19)
   A ranked list of search hits — each a full-row link: title, a two-line
   excerpt with the matched terms in <mark class="search-result__match">
   (weight, not colour), and a trailing chevron. Hover / focus: a soft neutral
   rounded fill that bleeds --space-4 past the text column (negative inline
   margin) so titles stay aligned with the page heading. No motion.
   First consumer: MP help-centre search (/help/search · /support/search).
     <ol class="search-results">
       <li><a class="search-result" href="…">
         <span class="search-result__body">
           <span class="search-result__title">How do I pay?</span>
           <span class="search-result__excerpt">…how do I <mark class="search-result__match">pay</mark>…</span>
         </span>
         <svg class="search-result__chevron" aria-hidden="true">…</svg>
       </a></li>
     </ol> */
.search-results {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.search-result {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  margin-inline: calc(var(--space-4) * -1);
  padding: var(--space-4);
  border-radius: var(--radius-lg);
  color: inherit;
  text-decoration: none;
}
.search-result:hover { background: var(--color-surface-neutral); }
.search-result:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: 0; }
.search-result__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.search-result__title {
  font-size: var(--font-size-body-md);
  font-weight: var(--weight-semibold);
  line-height: 1.4;
  color: var(--color-text-heading);
}
.search-result__excerpt {
  font-size: var(--font-size-body-sm);
  line-height: 1.6;
  color: var(--color-text-muted-on-white);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.search-result__match {
  background: none;
  color: var(--color-text-heading);
  font-weight: var(--weight-semibold);
}
.search-result__chevron { flex-shrink: 0; width: 20px; height: 20px; color: var(--color-bh-500); }

/* ─── Prose ─── .prose (added 2026-10-19)
   Long-form reading content — help-centre / user-guide articles. One readable
   column (--prose-max-width) of body-md copy at a generous leading, with:
   h2 section headings · ordered/unordered steps · <strong> for UI labels
   ("select **Send**") · underlined secondary links · <hr> section breaks.
   Text-only content; components (callouts, cards) sit between prose blocks,
   not inside them. First consumer: MP /help/[topic]/[article].
     <article class="prose">
       <p class="prose__lead">…</p>
       <hr>
       <h2>How to …</h2>
       <p>…</p>
       <ol><li>From the <strong>Dashboard</strong>, select …</li></ol>
     </article> */
.prose {
  max-width: var(--prose-max-width, var(--measure-text));  /* rev. 2026-10-30, was 680 */
  font-family: var(--font-inter);
  font-size: var(--font-size-body-md);
  line-height: var(--line-28);                              /* 16 / 28, was 1.7 (27.2) */
  color: var(--color-text-body);
}
.prose > * { margin: 0; }
.prose > * + * { margin-top: var(--space-5); }
.prose__lead { font-size: var(--font-size-body-lg); line-height: var(--line-28); }  /* 18 / 28, was 1.65 (29.7) */
.prose h2 {
  font-size: var(--font-size-title-sm);
  font-weight: var(--weight-semibold);
  line-height: 1.3;
  letter-spacing: -0.005em;
  color: var(--color-text-heading);
}
.prose > * + h2 { margin-top: var(--space-12); }
.prose h3 {
  font-size: var(--font-size-title-xs);
  font-weight: var(--weight-semibold);
  line-height: 1.35;
  color: var(--color-text-heading);
}
.prose > * + h3 { margin-top: var(--space-8); }
.prose ol,
.prose ul { padding-left: var(--space-8); }
.prose li + li { margin-top: var(--space-2); }
.prose li::marker { color: var(--color-text-muted-on-white); font-variant-numeric: tabular-nums; }
.prose strong { font-weight: var(--weight-semibold); color: var(--color-text-heading); }
.prose a {
  font-weight: var(--weight-semibold);
  color: var(--color-text-link-secondary);
  text-decoration: underline;
  text-underline-offset: 4px;
}
.prose a:hover { color: var(--color-bh-900); }
.prose a:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }
.prose hr {
  height: 0;
  border: 0;
  border-top: 0.5px solid var(--color-bh-200);
}
.prose > hr,
.prose > * + hr,
.prose > hr + * { margin-top: var(--space-10); }

/* ═══ components.css § Breadcrumbs · source lines 7706–7829 ═══ */
/* ─── Breadcrumbs ─── .crumbs · JSX components/Breadcrumbs.tsx
   Wayfinding chrome (rev. 2026-10-26 · refined on the foundation roles).
   Region 0 of the page-composition contract: the content column,
   directly above .page-header. Anatomy, left → right:
     .crumbs__back  — "Back" (leading arrow) to the parent landing, nested
                      pages only; its accessible name is "Back to {parent}".
                      Then .crumbs__divider: back is an action, not a level,
                      so it never takes a › and never collapses into "…".
     .anchor        — the active sidebar entry (icon + label).
     record links   — drill-down levels, each an <a>.
     .current       — the page itself, aria-current="page", never a link.
                      On an L1 landing it is the only crumb, with its icon.
     .more          — "…" button standing in for collapsed middle levels
                      (≥ 5 levels on desktop, keep first + last 2; ≥ 3 at
                      ≤ 640px, keep first + last 1). Click expands in place.

   Every value is a role: text meta (links 500 · current 600 — the crumb's
   own weights) · colour text-muted / text-heading / placeholder (›) /
   border (divider) / fill-hover · radius nested (chip) and utility (…) ·
   spacing section-lg above (48 → 32 from the role) and stack-tight (8) to
   the page header · the focus outline. Two named sizes: --crumbs-icon-size
   (14px icons, matched to 12px text; the divider takes the same height) and
   --crumbs-target (the hit height: 26px, 44px at ≤ 640px for touch).

   No motion: hover swaps instantly. Canonical reference:
   preview/components-breadcrumbs.html. */
.crumbs {
  --crumbs-icon-size: 14px;
  --crumbs-sep-size: 12px;
  --crumbs-target: 26px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-size: var(--text-meta-size);
  line-height: var(--text-meta-leading);
  color: var(--text-meta-color);
  padding-top: var(--space-section-lg);
  margin-bottom: var(--space-stack-tight);
}
.crumbs a,
.crumbs .current,
.crumbs .crumbs__text {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--crumbs-target);
  padding: 0 var(--space-2);
  box-sizing: border-box;
}
.crumbs a {
  color: var(--color-text-muted);
  font-weight: var(--weight-medium);
  text-decoration: none;
  border-radius: var(--radius-nested);
}
.crumbs a:hover {
  background: var(--color-fill-hover);
  color: var(--color-text-heading);
}
.crumbs a:focus-visible,
.crumbs .more:focus-visible {
  outline: var(--ring-focus);
  outline-offset: var(--ring-focus-offset);
}
.crumbs .current {
  color: var(--color-text-heading);
  font-weight: var(--weight-semibold);
}
.crumbs svg,
.crumbs i {
  flex-shrink: 0;
  width: var(--crumbs-icon-size);
  height: var(--crumbs-icon-size);
  font-size: var(--crumbs-icon-size);
}
.crumbs .sep {
  display: inline-flex;
  align-items: center;
  color: var(--color-placeholder);
}
.crumbs .sep svg,
.crumbs .sep i {
  width: var(--crumbs-sep-size);
  height: var(--crumbs-sep-size);
  font-size: var(--crumbs-sep-size);
}
.crumbs__divider {
  display: inline-block;
  width: 0.5px;
  height: var(--crumbs-icon-size);
  background: var(--color-border);
}
/* "…" — a button with the control border, no resting fill, the hover-on-white
   fill on hover. */
.crumbs .more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--crumbs-target);
  min-width: var(--crumbs-target);
  padding: 0 var(--space-2);
  box-sizing: border-box;
  border: 1.5px solid var(--color-border-control);
  border-radius: var(--radius-utility);
  background: transparent;
  color: var(--color-text-muted);
  font: inherit;
  font-weight: var(--weight-semibold);
  cursor: pointer;
}
.crumbs .more:hover {
  background: var(--color-fill-hover);
  color: var(--color-text-heading);
}
/* Crumbs lead the page header: the header's own top padding gives way. */
.crumbs + .page-header { padding-top: 0; }

/* Touch density at ≤ 640px: the hit height grows to the 44px floor; text
   stays 12px. (The 48 → 32 top step comes from --space-section-lg.) */
@media (max-width: 640px) {
  .crumbs { --crumbs-target: var(--touch-target-min); }
}

/* ═══ components.css § Segmented control · source lines 8096–8223 ═══ */
/* ─── Segmented control (filter chips) ─── */
.segmented {
  display: inline-flex;
  padding: 4px;
  background: var(--color-bh-50);
  border-radius: var(--radius-pill);
  gap: 0;
}
/* Disabled option (added 2026-10-21) · stays visible, can't be chosen. */
.segmented__option:disabled { opacity: 0.4; cursor: not-allowed; }
.segmented__option {
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  font-family: var(--font-inter);
  font-size: 13px;
  font-weight: var(--weight-medium);
  color: var(--color-text-body);   /* bh-600 — was text-muted (bh-300 = 2.6:1 on the bh-50 track, failed); 9.5:1 */
  background: transparent;
  /* Transparent border on every option so widths stay shift-free as the
     selection moves; only the active pill paints its Blue Hour edge below. */
  border: 1px solid transparent;
  cursor: pointer;
  white-space: nowrap;
}
/* Leading icon (added 2026-10-19) — an option may carry a 16px Phosphor icon
   left of its label (.segmented__icon); the option centres icon + label. */
.segmented__option:has(.segmented__icon) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
}
.segmented__icon { display: inline-flex; flex-shrink: 0; }
.segmented__icon svg { width: 16px; height: 16px; }
/* Inactive hover — darkens the label + previews the active chip with a soft
   white wash over the bh-50 track. No motion (instant). */
.segmented__option:not(.segmented__option--active):hover {
  color: var(--color-text-heading);
  background: rgba(255, 255, 255, 0.6);
}
.segmented__option--active {
  background: var(--color-white);
  color: var(--color-text-heading);
  font-weight: var(--weight-semibold);
  /* Blue Hour edge — reuses the system "Blue Hour border" language (the 1px
     bh-700 ring on .card--brand-hover). Additive to the lift shadow: gives the
     selected pill a crisp bounded edge against the bh-50 track. malachor 2026-07-22. */
  border-color: var(--color-bh-700);
  box-shadow: 0 1px 2px rgba(8, 44, 84, 0.06);
}
.segmented__option:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: 2px; }
/* Touch-density auto-engage at the mobile breakpoint — matches the v2.4
   auto-engage pattern shared with .btn / .input / .select / .modal /
   .toast. At < 640 px the segmented option grows to --touch-target-min
   (44 px) so the chip is comfortably tappable on touch surfaces.
   Promoted 2026-05-13 in jakku PR alongside the patient-home filter
   toolbar consumer. */
@media (max-width: 640px) {
  .segmented__option { min-height: var(--touch-target-min); }
  /* Wide segmented (many options · e.g. the Finances period selector) scrolls
     horizontally instead of overflowing its container and forcing page-wide
     horizontal scroll. min-width:0 lets the inline-flex track shrink below its
     content (which also collapses its min-content contribution to ancestors, so
     stretched siblings — like a full-width dropdown trigger — stop inheriting
     the segmented's intrinsic width); overflow-x:auto makes it a scroll
     container; options keep their size via flex-shrink:0 + the existing nowrap.
     Short segmenteds (e.g. EN/FR) fit and never scroll. */
  .segmented {
    max-width: 100%;
    min-width: 0;
    overflow-x: auto;
    /* Scroll without a visible scrollbar (iOS-standard) — the track otherwise
       reads as page chrome under the control. Firefox + WebKit/Blink. */
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  .segmented::-webkit-scrollbar { display: none; }
  .segmented__option { flex-shrink: 0; }
}

/* ── Sliding-pill variant (opt-in) · MOTION EXCEPTION (exegol · 2026-09-14) ──
   The default .segmented repaints the active pill instantly (the no-motion
   non-negotiable). .segmented--sliding instead floats ONE pill (.segmented__thumb)
   that SLIDES between EQUAL-width options on select — transform only, so it stays
   cheap and honours reduced-motion. The plain .segmented is unchanged; only
   consumers that opt into the modifier get motion.
   Consumer contract: render <span class="segmented__thumb"> as the FIRST child of
   the track, and set --seg-count (total options) + --seg-index (0-based active) on
   the track. Options split the track equally; the thumb overlays the active one.
   Geometry: the track has 4px padding and no border, so each option (and the
   thumb) is (100% − 8px) / count wide, and the thumb steps by index × 100%. */
.segmented--sliding {
  display: flex;         /* block-level so the equal (flex:1) options have a width */
  position: relative;    /* positioning context for the absolute thumb */
}
.segmented--sliding .segmented__option {
  flex: 1;
  text-align: center;
  position: relative;    /* label paints above the thumb */
  z-index: 1;
}
/* The active option no longer paints its own pill — the thumb is the pill. The
   active label keeps its colour + weight from the base .segmented__option--active. */
.segmented--sliding .segmented__option--active {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}
.segmented__thumb {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 4px;
  z-index: 0;
  width: calc((100% - 8px) / var(--seg-count, 2));
  transform: translateX(calc(var(--seg-index, 0) * 100%));
  border-radius: var(--radius-pill);
  background: var(--color-white);
  border: 1px solid var(--color-bh-700);   /* Blue Hour edge — matches the active chip */
  box-shadow: 0 1px 2px rgba(8, 44, 84, 0.06);
  transition: transform var(--motion-duration-base) var(--motion-ease-standard);
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .segmented__thumb { transition: none; }
}

/* ═══ components.css § Menu (OnboardingHeader language menu) · source lines 8442–8529 ═══ */
/* ──────────────────────────────────────────────────────────────────
   MENU · v3 · rev. 2026-10-26 (menu audit · preview/components-menu.html)
   The one dropdown primitive: single-select listboxes, multi-select
   toggles and grouped action menus anchored to a trigger (pill, icon
   button, profile link). Behaviour lives in ONE hook, lib/useMenu.ts —
   open / close, arrows, Home / End, type-ahead, Esc, Tab, outside click,
   focus return, flip / shift placement and the exit. Components never
   re-implement it.

   Composition:
     <div class="menu-wrap">                 — anchors the menu
       <button ...{...triggerProps}>Trigger</button>
       <ul class="menu" role="listbox" ...{...menuProps}>
         <li role="presentation">
           <button class="menu__item" role="option" aria-selected="true">
             <span>Label</span>
             <i class="ph ph-check menu__item__check"></i>
           </button>
         </li>
       </ul>
     </div>

   Surface: white · 0.5px border · --radius-md · --shadow-elevated, always
   (the flat modifier is retired). Width follows the longest label — rows
   never wrap — capped at --ws-switch-max and the viewport.

   Placement: under the trigger by default (right edges matching;
   --align-left matches left edges). useMenu measures on open: with no
   room below it adds .menu--above; it shifts the menu sideways to stay
   8px inside the viewport (--menu-shift-x). .menu--fixed is the path for
   a menu portalled out of a clipping scroll surface (the data grid).

   Motion (Surfaces theme): opens with a fast fade + 4px rise from the
   trigger (--motion-distance-xs, inverted when above); closes with a fast
   fade (.menu--closing, held mounted by useMenu). Reduced motion: the
   distance token collapses, so only the fade remains.

   Rows: 36px; 44px under --bp-mobile. Hover and keyboard focus take the
   fill; the selected row rests unfilled (check + medium weight). Keyboard
   focus adds a 2px inset dark Blue Hour ring (the db-400 system ring is
   2.78:1 on the fill). No submenus.
   ────────────────────────────────────────────────────────────────── */
.menu-wrap {
  position: relative;
  display: inline-flex;
}

.menu {
  position: absolute;
  top: calc(100% + var(--space-1));
  right: 0;
  width: max-content;
  min-width: 184px;
  max-width: min(var(--ws-switch-max), calc(100vw - 2 * var(--space-2)));
  box-sizing: border-box;
  padding: var(--space-1);
  margin: 0;
  list-style: none;
  background: var(--color-white);
  border: 0.5px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-elevated);
  z-index: var(--z-dropdown);
  translate: var(--menu-shift-x, 0px) 0;
  animation: menu-enter var(--motion-duration-fast) var(--motion-ease-enter);
}
.menu[hidden] { display: none; }

/* Alignment modifier — by default the menu anchors to the trigger's
   right edge (right-slot utilities). Use --align-left when the trigger
   sits on the left edge of its container. */
.menu--align-left { right: auto; left: 0; }

/* Placement modifiers — set by useMenu, never by hand. */
.menu--above { top: auto; bottom: calc(100% + var(--space-1)); animation-name: menu-enter-above; }
.menu--fixed { position: fixed; top: var(--menu-top); bottom: auto; left: var(--menu-left); right: auto; z-index: var(--z-popover); }   /* portalled: above an open drawer or modal */
.menu--closing {
  opacity: 0;
  animation: none;
  transition: opacity var(--motion-duration-fast) var(--motion-ease-exit);
}
@keyframes menu-enter {
  from { opacity: 0; transform: translateY(calc(var(--motion-distance-xs) * -1)); }
}
@keyframes menu-enter-above {
  from { opacity: 0; transform: translateY(var(--motion-distance-xs)); }
}

/* ═══ components.css § Onboarding header · source lines 8850–9010 ═══ */
/* ──────────────────────────────────────────────────────────────────
   ONBOARDING HEADER
   v2.4 · 2026-05-12 · Full-bleed three-slot chrome shared by every
   onboarding stage across B2C (patient invite) and B2B (clinician
   workspace) flows. Layout is identical for both audiences; only
   the right-slot affordance changes (Help vs. Sign out).

   Anatomy:  back-or-empty (left)  ·  Docnote logo (centred)  ·  help-or-sign-out (right)
   The two side slots each reserve a fixed width so the centred logo
   stays geometrically anchored across stages, even when the back
   button is absent on the entry stage.
   ────────────────────────────────────────────────────────────────── */
.onboarding-header {
  display: grid;
  grid-template-columns: 120px 1fr 120px;
  align-items: center;
  height: var(--bar-height-lg);
  padding: 0 var(--space-6);
  background: var(--color-white);
  border-bottom: 0.5px solid var(--color-border-subtle);
}
.onboarding-header--on-cloud { background: var(--color-bh-50); }
.onboarding-header--sticky {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
}
.onboarding-header__slot {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--touch-target-min);
}
.onboarding-header__slot--left  { justify-content: flex-start; }
.onboarding-header__slot--right { justify-content: flex-end; }

/* Centred mark — <BrandLogo flush className="onboarding-header__brand">
   (a home link when the header has logoHref). --brand-logo-lg (32px) at
   brand-anchored chrome (≥641px), --brand-logo-sm (20px) at touch density
   (≤640px) — brand-logo rebuild 2026-10-27, was the padded file at 56 / 40. */
.onboarding-header__brand {
  --brand-logo-h: var(--brand-logo-lg);
  justify-self: center;
}

/* Back / Help / Sign-out controls — share one geometry.
   The geometry is the canonical .btn-back rule (promoted 2026-05-11). The
   __back selector here is a deprecation alias for consumer markup that
   hasn't yet migrated to plain class="btn-back". __help and __signout share
   the same geometry but are NOT back affordances — they keep their own
   selectors and will be reviewed for canonical promotion separately
   (e.g. .btn-utility-nav) when a non-onboarding-header consumer ships them. */
.onboarding-header__back,
.onboarding-header__help,
.onboarding-header__signout {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--touch-target-min);
  padding: 0 var(--space-3);
  font-family: var(--font-inter);
  font-size: var(--size-13);
  font-weight: var(--weight-medium);
  color: var(--color-text-body);
  background: transparent;
  border: none;
  border-radius: var(--radius-md);
  text-decoration: none;
  cursor: pointer;
}
.onboarding-header__back:hover,
.onboarding-header__help:hover,
.onboarding-header__signout:hover { color: var(--color-text-heading); background: var(--color-bh-100); }
.onboarding-header__back:focus-visible,
.onboarding-header__help:focus-visible,
.onboarding-header__signout:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: 2px; }
.onboarding-header__signout { color: var(--color-text-muted); }

/* Touch density (≤640px): collapse labels to icons-only, narrow side slots,
   and scale the bar height + logo down to match the login-entry header at
   the same breakpoint (--bar-height-md / --brand-logo-sm). Hit area stays 44×44. */
@media (max-width: 640px) {
  .onboarding-header {
    grid-template-columns: 44px 1fr 44px;
    height: var(--bar-height-md);
    padding: 0 var(--space-4);
  }
  .onboarding-header__brand { --brand-logo-h: var(--brand-logo-sm); }
  .onboarding-header__back span,
  .onboarding-header__help span,
  .onboarding-header__signout span { display: none; }
  .onboarding-header__back,
  .onboarding-header__help,
  .onboarding-header__signout { width: 44px; padding: 0; justify-content: center; }
}

/* ─── Logged-in variant ───
   When the user is authenticated and their identity is available, the
   right-slot exit point switches from .onboarding-header__signout to the
   canonical .profile-link from components-avatar.html. Click navigates to
   the profile settings page (where sign-out itself lives). The grid shifts
   to 1fr auto 1fr so the centred logo geometry is preserved while the
   right slot accommodates the wider profile-link composition.
   Pairs with — and never replaces — the sign-out variant; pick whichever
   matches the user's authentication / onboarding state. */
.onboarding-header--with-profile {
  grid-template-columns: 1fr auto 1fr;
}

@media (max-width: 640px) {
  /* Touch density: keep slots square; profile-link collapses to avatar-only
     (name and caret hidden) so the logo stays centred. */
  .onboarding-header--with-profile {
    grid-template-columns: 44px 1fr 44px;
  }
  .onboarding-header--with-profile .profile-link__name,
  .onboarding-header--with-profile .profile-link__caret { display: none; }
  .onboarding-header--with-profile .profile-link {
    width: 44px;
    padding: 0;
    justify-content: center;
  }
}

/* ─── Language selector · Phase 01 · 2026-05-12 ───
   Sits in the right slot, LEFT of __help / __signout / .profile-link.
   Trigger mirrors __help / __signout geometry (13px medium, --touch-
   target-min hit area). Menu composes the canonical .menu primitive,
   elevated like every menu (the Phase 01 flat opt-out was retired
   2026-10-26).

   Default state — auto-detected on load from navigator.language:
     · FR if browser language starts with 'fr'
     · EN otherwise
   Selection dispatches a docnote:lang-change CustomEvent for the
   Phase 02 translation store. */
.onboarding-header__lang {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--touch-target-min);
  padding: 0 var(--space-3);
  font-family: var(--font-inter);
  font-size: var(--size-13);
  font-weight: var(--weight-medium);
  color: var(--color-text-body);
  background: transparent;
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
}
.onboarding-header__lang:hover { color: var(--color-text-heading); background: var(--color-bh-100); }
.onboarding-header__lang:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: 2px; }
.onboarding-header__lang[aria-expanded="true"] { color: var(--color-text-heading); background: var(--color-bh-100); }

@media (max-width: 640px) {
  .onboarding-header__lang span { display: none; }
  .onboarding-header__lang { width: 44px; padding: 0; justify-content: center; }
}

/* ═══ components.css § App footer · source lines 10344–10485 ═══ */
/* ─── App footer (compliance / legal chrome) ─── .app-footer
   Canonical bottom chrome that carries the decorative brand-anchor mark
   + utility nav (Contact us · Security · Legal · Privacy · Accessibility ·
   Cookie settings) + copyright lock-up across two horizontal tiers
   separated by 0.5 px bh-100 dividers. Distinct from .action-footer (sticky
   CTA region) — .app-footer is page-level legal/compliance chrome that
   sits at the natural bottom of the surface, never sticky.

   Promoted 2026-05-12 from preview-local .auth__footer (renamed to proper
   BEM in the promotion pass — see preview/layouts-login-entry.html and
   preview/layouts-app-shell.html decision logs for the rollout).

   Chrome rule — where .app-footer renders:
     · Every logged-out entry / auth surface — pinned to the bottom of the
       .surface flex column by .auth__content { flex: 1 } above (see
       layouts-login-entry.html for the canonical embed pattern).
     · Every B2C patient-portal screen — last child of <body> on each
       surface; <body> flex column + <main> { flex: 1 } pattern.
     · Every authenticated SaaS desktop page (≥ 640 px) — last child of
       <main class="content"> inside the .app-shell content column.
       Sidebar runs full-height alongside; footer is constrained to the
       content-column width with negative horizontal margins of --space-12
       bleeding the dividers to the column edges, and row padding restored
       to --space-12 so brand-mark + nav align with the .page-header h1
       above. Canonical pattern in layouts-app-shell.html.

   .app-footer does NOT render on:
     · The SaaS mobile shell (< 640 px) — the canonical .tab-bar (64 px)
       already owns the bottom of the mobile viewport. Compliance / legal
       links route through the sidebar "Workspace Manager" Settings entry
       on mobile (reached via the .app-bar__hamburger .drawer--top) per
       the amended Settings rule in CLAUDE.md (dagobah · 2026-05-28).
       (Entry surfaces and patient-portal screens at < 640 px keep the
       footer — they have no .tab-bar.)

   Brand mark is decorative on every consumer — <span> with aria-hidden,
   empty alt on the <img>, no anchor wrapping, no link affordance. */
.app-footer {
  background: var(--color-white);
  /* Canonical breathing zone ABOVE the top divider · tatooine · 2026-06-02.
     The top divider lives on .app-footer__row--upper (border-top), NOT on
     .app-footer — so this padding-top opens space BETWEEN the content above
     and the divider line, rather than below it. (kashyyyk · 2026-06-02
     originally put the divider on .app-footer and the padding below it, so
     the breathing room landed under the line — corrected here.) Internal
     padding, so it never fights the SaaS-shell consumer's `margin-top: auto`
     bottom-pinning (a plain margin-top would collapse to 0 on long-content
     pages where the auto-margin evaluates to ~0). Tightens to --space-4 at
     mobile widths along with the row-padding tightening below. */
  padding-top: var(--space-6);
}
.app-footer__row {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-4) var(--space-6);
}
.app-footer__row--upper {
  justify-content: space-between;
  /* Top divider · sits below the .app-footer breathing zone so the hairline
     reads with space above it, not crowding the content. */
  border-top: 0.5px solid var(--color-bh-100);
}
.app-footer__row--lower {
  justify-content: flex-end;
  border-top: 0.5px solid var(--color-bh-600);
  background: var(--color-bh-700);
}
/* The decorative mark is <BrandLogo kind="symbol" size="md" flush decorative>
   (24px visible — the old 44px padded favicon showed ~22). Brand-logo rebuild
   2026-10-27. */
.app-footer__nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-5);
  justify-content: flex-end;
}
.app-footer__link {
  font-family: var(--font-inter);
  font-size: var(--size-13);
  font-weight: var(--weight-medium);
  color: var(--color-text-muted-on-white);
  text-decoration: none;
}
.app-footer__link:hover {
  color: var(--color-text-heading);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.app-footer__link:focus-visible {
  outline: 3px solid var(--color-focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
/* Inactive footer links — opt-in via <AppFooter inactive> (rendered as a
   non-interactive <span aria-disabled>). Used where the footer is present as
   chrome but its destinations are not wired (e.g. the Dynacare demo). Muted a
   step below the active link colour (bh-400, not opacity, to keep the label
   legible per the inert-entry precedent) and pointer-events:none so the
   hover / underline affordance never fires. */
.app-footer__link--inactive {
  color: var(--color-bh-400);
  cursor: default;
  pointer-events: none;
}
.app-footer__copy {
  margin: 0;
  font-family: var(--font-inter);
  font-size: var(--size-12);
  color: var(--color-white);
  font-feature-settings: "tnum";
}

/* Mobile (≤ 640 px) — upper tier stacks (brand mark above wrapped utility
   nav, both left-aligned); lower copyright row centres. Padding tightens
   on the row. Hide-on-SaaS-shell is enforced by the consumer (the
   chrome rule documents which surfaces render the footer at this
   breakpoint — see CLAUDE.md § Non-negotiables). */
@media (max-width: 640px) {
  .app-footer {
    /* Tighter breathing zone at mobile widths · matches the row-padding
       tightening below. */
    padding-top: var(--space-4);
  }
  .app-footer__row {
    padding: var(--space-3) var(--space-5);
  }
  .app-footer__row--upper {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
  }
  .app-footer__nav {
    justify-content: flex-start;
    gap: var(--space-2) var(--space-4);
  }
  .app-footer__row--lower {
    justify-content: center;
  }
}

/* ═══ components.css § Disclosure (accordion) · source lines 11946–12128 ═══ */
/* ═══════════════════════════════════════════════════════════════════════
   DISCLOSURE · progressive-disclosure info panels
   ─────────────────────────────────────────────────────────────────────
   Borderless group of expandable panels for grouped, read-only information.
   Promoted from preview/components-disclosure.html (crait · 2026-07-10).

   Anatomy: .disclosure (group) > .disclosure-panel (each section). Each panel
   is a <button> header (static text title + rotating chevron) inside a heading
   element, plus a [hidden]-toggled region holding label/value rows. Row layout
   is a named display axis — .disclosure--inline (DEFAULT · value right on one
   row) / .disclosure--stacked (value below the label) — mirroring the
   .summary-list--inline / --stacked pair (see § Display variants below).
   Separation is whitespace + hairline dividers ONLY — no enclosing box, no
   card, no shadow, no radius on the surface. Titles are static (shown
   verbatim); the chevron rotation + aria-expanded are the only state signals.
   Values are read-only display text — no links, no interactivity.

   Motion: expand/collapse is an INSTANT show/hide and the chevron rotation
   swaps instantly (option b · 2026-07-10) — honours the "No motion"
   non-negotiable, matches .summary-list + the sidebar progressive-disclosure
   precedent, and is reduced-motion-safe by construction (zero transitions).

   Token namespace: apps restyle via --disclosure-* overrides only — never by
   editing these rules. This is the first per-component token namespace in
   components.css, added deliberately per the component handover; every value
   maps to a canonical design-system token (zero raw hex; no border/radius/
   card tokens — borderless by design).
   ═══════════════════════════════════════════════════════════════════════ */
:root {
  /* colour */
  --disclosure-bg:                 transparent;                       /* borderless · sits on white/transparent */
  --disclosure-title-color:        var(--color-text-heading);         /* bh-700 dark navy */
  --disclosure-chevron-color:      var(--color-bh-500);               /* muted · matches .summary-list chevron */
  --disclosure-row-divider-color:  var(--color-bh-100);               /* hairline between rows (component-internal) */
  --disclosure-panel-divider-color:var(--color-bh-200);               /* hairline between panels (component-external) */
  --disclosure-label-color:        var(--color-text-muted-on-white);  /* bh-500 steel-blue · 7.13:1 on white */
  --disclosure-value-color:        var(--color-text-heading);         /* bh-700 dark navy */
  --disclosure-header-hover-bg:    var(--color-bh-50);                /* Cloud tint on header hover */
  --disclosure-focus-ring:         var(--color-focus-ring);           /* db-400 */

  /* typography · family = Inter, from the design system */
  --disclosure-font-family:        var(--font-inter);
  --disclosure-title-font-size:    var(--font-size-body-md);          /* 16 */
  --disclosure-title-font-weight:  var(--weight-bold);                /* 700 */
  --disclosure-label-font-size:    var(--font-size-caption);          /* 12 */
  --disclosure-label-font-weight:  var(--weight-medium);              /* 500 */
  --disclosure-value-font-size:    var(--font-size-body-sm);          /* 14 */
  --disclosure-value-font-weight:  var(--weight-bold);                /* 700 */

  /* spacing */
  --disclosure-header-padding-y:   var(--space-4);                    /* 16 */
  --disclosure-header-padding-x:   var(--space-2);                    /* 8 · gentle inset so hover fill isn't edge-harsh */
  --disclosure-body-padding-x:     var(--space-2);                    /* 8 · rows align to title left edge */
  --disclosure-row-padding-y:      var(--space-3);                    /* 12 */
  --disclosure-header-gap:         var(--space-4);                    /* 16 · min gap · title → chevron */
  --disclosure-row-gap-inline:     var(--space-4);                    /* 16 · label ↔ value min gap on ONE row (--inline) */
  --disclosure-row-gap-stacked:    var(--space-1);                    /* 4  · label → value when the value drops below (--stacked) */

  /* chevron */
  --disclosure-chevron-size:       18px;
}

/* No border/radius/card tokens — the layout is borderless by design.
   Separation is carried by whitespace + hairline dividers only. */
.disclosure { background: var(--disclosure-bg); font-family: var(--disclosure-font-family); }

/* Panel-to-panel separation: a hairline between adjacent panels (not an
   enclosing border). */
.disclosure-panel + .disclosure-panel { border-top: 0.5px solid var(--disclosure-panel-divider-color); }

/* Heading wrapper keeps the title in the screen-reader heading outline; the
   inner <button> carries the expand/collapse control. Zero <h*> margin so the
   heading level can be tuned to the doc outline without altering visuals. */
.disclosure-panel__heading { margin: 0; }

.disclosure-panel__header {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--disclosure-header-gap);
  width: 100%;
  padding: var(--disclosure-header-padding-y) var(--disclosure-header-padding-x);
  background: transparent; border: none; text-align: left; cursor: pointer;
  font-family: inherit;
  -webkit-appearance: none; appearance: none;
  border-radius: var(--radius-sm);
}
.disclosure-panel__header:hover { background: var(--disclosure-header-hover-bg); }
.disclosure-panel__header:focus-visible {
  outline: 3px solid var(--disclosure-focus-ring);
  outline-offset: -2px;
}

.disclosure-panel__title {
  font-size: var(--disclosure-title-font-size);
  font-weight: var(--disclosure-title-font-weight);
  color: var(--disclosure-title-color);
  line-height: 1.4;
  min-width: 0;
}
.disclosure-panel__chevron {
  font-size: var(--disclosure-chevron-size);
  color: var(--disclosure-chevron-color);
  line-height: 1; flex-shrink: 0;
  transform: rotate(0deg);                     /* collapsed → caret down */
}
/* Chevron ROTATES 180° on expand — never a glyph swap. The sole visual state
   affordance; the title text never changes with state. Instant per the
   "No motion" contract (no transition), matching the sidebar precedent. */
.disclosure-panel.is-open .disclosure-panel__chevron { transform: rotate(180deg); }

/* Expand / collapse is an INSTANT show/hide — collapsed panels carry [hidden]
   so the content leaves the tab order + a11y tree. */
.disclosure-panel__region[hidden] { display: none; }
.disclosure-panel__body { padding: 0 var(--disclosure-body-padding-x); }
.disclosure-panel__rows { margin: 0; }   /* zero default <dl> margin */

/* ─── Row · the label→value pair ───────────────────────────────────────
   The base row ships the INLINE layout (label left, value right on ONE row,
   baseline-aligned) so .disclosure--inline re-declares the default — the same
   pattern .summary-list uses for its display-variant pair. Hairline divider
   between consecutive rows. Restyle the layout with the display variants
   below (never by editing this rule). */
.disclosure-row {
  display: grid;
  grid-template-columns: 1fr auto;              /* label absorbs slack · value hugs its content */
  align-items: baseline;
  gap: var(--disclosure-row-gap-inline);
  padding: var(--disclosure-row-padding-y) 0;
}
.disclosure-row + .disclosure-row { border-top: 0.5px solid var(--disclosure-row-divider-color); }
.disclosure-row:last-child { padding-bottom: var(--space-4); }
.disclosure-row__label {
  margin: 0;
  font-size: var(--disclosure-label-font-size);
  font-weight: var(--disclosure-label-font-weight);
  color: var(--disclosure-label-color);
  line-height: 1.4;
}
.disclosure-row__value {
  margin: 0;                                    /* zero default <dd> margin-inline-start */
  font-size: var(--disclosure-value-font-size);
  font-weight: var(--disclosure-value-font-weight);
  color: var(--disclosure-value-color);
  line-height: 1.5;
  text-align: right;                            /* inline default · right-aligned like .summary-list */
  font-variant-numeric: tabular-nums;           /* IDs / phone / dates stay column-aligned */
  word-break: break-word;                       /* long emails / addresses wrap, never overflow */
}

/* ─── Display variants · the row-layout axis ───────────────────────────
   ONE axis, two named display styles — swap a single modifier on the
   .disclosure group to flip how EVERY row lays out. Mirrors the
   .summary-list--inline / --stacked matched pair so the two read-only
   label/value components share one vocabulary and one default (inline).

     .disclosure--inline   (DEFAULT) · label + value share ONE row · value
                             sits in an `auto` track, RIGHT-aligned,
                             baseline-aligned with the label. Best for
                             fee / currency / date / ID panels where
                             right-aligned tabular-nums carry scanning.
     .disclosure--stacked  · value drops BELOW the label, LEFT-aligned
                             directly under it (single column). Best for
                             identity / contact panels where the value is a
                             long string (email, clinic address, provider
                             name) that wraps awkwardly against a right edge.

   The base .disclosure-row already ships the inline layout, so
   .disclosure--inline re-declares the default — it exists so the two
   variants read as a NAMED, MATCHED PAIR: a consumer writes `--inline` or
   `--stacked` and never has to know which one is "the unnamed default".
   Both are safe to author explicitly; neither fights the base rule. */
.disclosure--inline .disclosure-row {
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: var(--disclosure-row-gap-inline);
}
.disclosure--inline .disclosure-row__value { text-align: right; }
.disclosure--stacked .disclosure-row {
  grid-template-columns: 1fr;                   /* was: 1fr auto */
  align-items: start;                           /* was: baseline */
  gap: var(--disclosure-row-gap-stacked);       /* was: --disclosure-row-gap-inline */
}
.disclosure--stacked .disclosure-row__value { text-align: left; }  /* was: right */

/* ═══ components.css § Callout · source lines 12642–12776 ═══ */
/* ─── Callout ─── (section marker for check:tokens; the Callout banner follows) */

/* ═══════════════════════════════════════════════════════════════════════
   § Callout · a supporting information / cross-sell panel
   ─────────────────────────────────────────────────────────────────────────
   A quiet Cloud (bh-50) zone that carries a supporting message — education, a
   cross-sell, a secondary offer — set apart from the primary content without
   competing with it. Anatomy: a leading icon + a content column (title → body →
   optional actions). The body is free composition (paragraphs via .callout__text,
   lists, an .callout__actions button row) so one primitive serves several
   messages. Left-aligned; flat (no shadow — border/adjacency-free, the tint
   carries the separation).

   NEUTRAL ONLY. The tint is Cloud, never Daybreak or a status colour — colour
   stays reserved for interaction / consequence. A status/Daybreak variant would
   be a deliberate future decision, not an ad-hoc override.

   Token namespace: restyle via --callout-* overrides only. This is the THIRD
   per-component token namespace in components.css (after --disclosure-* and
   --file-card-*); every value maps to a canonical token (zero raw hex).
   ═══════════════════════════════════════════════════════════════════════ */
:root {
  /* colour */
  --callout-bg:                 var(--color-bh-50);               /* Cloud tint */
  --callout-icon-color:         var(--color-text-heading);        /* bh-700 · icon matches the title */
  --callout-title-color:        var(--color-text-heading);        /* bh-700 */
  --callout-text-color:         var(--color-text-body);           /* bh-600 */

  /* typography · family = Inter, from the design system */
  --callout-font-family:        var(--font-inter);
  --callout-title-font-size:    var(--font-size-title-xs);        /* 20 · matches .t-title-xs */
  --callout-title-font-weight:  var(--weight-semibold);           /* 600 */
  --callout-text-font-size:     var(--font-size-body-sm);         /* 14 */
  --callout-icon-size:          24px;                             /* Phosphor web-font in previews; React passes size={24} */

  /* spacing / geometry */
  --callout-padding:            var(--space-6);                   /* 24 */
  --callout-radius:             var(--radius-md);                 /* 8 */
  --callout-gap:                var(--space-4);                   /* 16 · icon → body */
  --callout-body-gap:           var(--space-3);                   /* 12 · title → body → actions */
  --callout-actions-gap:        var(--space-3);                   /* 12 · between action buttons */
}

.callout {
  display: flex;
  gap: var(--callout-gap);
  padding: var(--callout-padding);
  background: var(--callout-bg);
  border-radius: var(--callout-radius);
  font-family: var(--callout-font-family);
  text-align: left;
}

/* Leading icon · flex-none, matches the title colour (icon-colour rule), nudged
   down to optically align with the title cap-height. */
.callout__icon {
  flex: none;
  margin-top: 2px;
  font-size: var(--callout-icon-size);
  color: var(--callout-icon-color);
}

.callout__body {
  display: flex;
  flex-direction: column;
  gap: var(--callout-body-gap);
  min-width: 0;
}

.callout__title {
  margin: 0;
  font-size: var(--callout-title-font-size);
  font-weight: var(--callout-title-font-weight);
  line-height: 1.4;
  color: var(--callout-title-color);
}

.callout__text {
  margin: 0;
  font-size: var(--callout-text-font-size);
  line-height: 1.5;
  color: var(--callout-text-color);
}

.callout__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--callout-actions-gap);
  margin-top: var(--space-1);
}

/* ─── Callout · compact ─── .callout--sm · field info under a form control (2026-10-29)
   The quiet size for supporting information that opens under a field (inside
   a .reveal while the field has focus). Three forms from one markup: headline
   + text (item-title over body-sm) · text only (drop .callout__title) · no
   icon (drop .callout__icon). The 16px icon centres on the first line, the
   title's when there is one, and is Phosphor Bold (sanctioned exception,
   2026-10-29: matches the semibold headline; Bold with or without one). Token-only (--callout-sm-*). JSX: <Callout size="sm">. */
:root {
  --callout-sm-padding:       var(--space-inset-compact);    /* 16 · 12 under 640px */
  --callout-sm-gap:           var(--space-2);                /* 8 · icon → body */
  --callout-sm-body-gap:      var(--space-1);                /* 4 · headline → text */
  --callout-sm-icon-size:     var(--icon-inline);            /* 16 */
  --callout-sm-title-line:    calc(var(--text-item-title-size) * var(--text-item-title-leading));
  --callout-sm-text-line:     calc(var(--text-body-sm-size) * var(--text-body-sm-leading));
}
.callout--sm {
  gap: var(--callout-sm-gap);
  padding: var(--callout-sm-padding);
}
.callout--sm .callout__body { gap: var(--callout-sm-body-gap); }
.callout--sm .callout__icon {
  width: var(--callout-sm-icon-size);
  height: var(--callout-sm-icon-size);
  font-size: var(--callout-sm-icon-size);
  margin-top: calc((var(--callout-sm-text-line) - var(--callout-sm-icon-size)) / 2);
}
.callout--sm:has(.callout__title) .callout__icon {
  margin-top: calc((var(--callout-sm-title-line) - var(--callout-sm-icon-size)) / 2);
}
.callout--sm .callout__title {
  font-size: var(--text-item-title-size);
  font-weight: var(--text-item-title-weight);
  line-height: var(--text-item-title-leading);
  letter-spacing: var(--text-item-title-tracking);
  color: var(--text-item-title-color);
}
.callout--sm .callout__text {
  font-size: var(--text-body-sm-size);
  font-weight: var(--text-body-sm-weight);
  line-height: var(--text-body-sm-leading);
  letter-spacing: var(--text-body-sm-tracking);
  color: var(--text-body-sm-color);
}
