/* TherapyTracked — brand stylesheet
   "Ledger" identity per the Brand Guidelines (Notion → 02 Brand). Two brand
   layers: INDIGO = identity (logo, headings, wordmark — never interactive) and
   CORAL = the single interactive accent (links, buttons, focus). A separate
   traffic-light triad — good / warn / crit — carries the 90/60/30-day status
   cadence, kept visually distinct from the brand hues. Every pairing is WCAG-AA
   verified: --coral is stroke/border/focus only (sub-AA as text); --coral-deep
   is the AA-safe text/link/button colour. Calm, confident, trustworthy. */

:root {
  /* Indigo — identity (logo, headings, wordmark; never interactive) */
  --indigo-deep: #2c2a5a;
  --indigo: #5951a6;
  --indigo-wash: #eceaf4;
  --indigo-ink: #211f3d;

  /* Coral — the single interactive accent. -deep is AA-safe for text/links/
     button fills; -mid (4.21:1 as text) is stroke/border/focus only. */
  --coral-deep: #8e3c29;
  --coral: #c1553d;
  --coral-pale: #e7bdae;
  --coral-wash: #f5e4dd;

  /* Functional — traffic-light cadence (90/60/30 alerts, status chips) */
  --good: #27794d;
  --good-wash: #e1f0e7;
  --warn: #8e581a;
  --warn-wash: #f5e9d6;
  --crit: #b23b3b;
  --crit-wash: #f7e2df;

  /* Neutral — canvas */
  --ground: #f7f6f9;
  --surface: #ffffff;
  --surface-2: #eceaf4;
  --ink: #211f3d;
  --ink-soft: #4b4870;
  --ink-faint: #6b6890;
  --on-indigo: #ffffff;

  /* Always-dark identity band (sidebar, entrance hero, marketing bands). Fixed
     dark in both themes (unlike --indigo-deep, which flips) so white text always
     reads. --band-soft / --band-accent are the light-on-band secondary + accent. */
  --band: #2c2a5a;
  --band-soft: #c9c6e8;
  --band-accent: #e8967d;

  /* Semantic aliases — the names the component styles below consume. */
  --bg: var(--ground);
  --surface-sunken: var(--surface-2);

  /* Content-panel elevation ladder — page < panel < header. Light mode maps
     these onto the existing surfaces so nothing here changes; the dark block
     lifts them (see below) so panels/tables read against the darker page.
     Consumed by the shared panel/table primitives: .panel, .section-card,
     .table-card, .dash-section, .form-card, .entity-header, thead th,
     .collapse-card (+ page-level tiles). */
  --panel: var(--surface);
  --panel-header: var(--surface-sunken);
  --panel-nested: var(--surface);

  --fg: var(--ink);
  --fg-muted: var(--ink-soft);
  --border: #dcd9ea;
  --primary: var(--coral-deep);
  --primary-hover: #74301f;
  --primary-contrast: #ffffff;
  --secondary: var(--indigo);
  --secondary-strong: var(--indigo-deep);
  --secondary-wash: var(--indigo-wash);
  --accent: var(--coral);
  --link: var(--coral-deep);
  --link-hover: #74301f;
  --focus: var(--coral);
  --danger: var(--crit);
  --danger-wash: var(--crit-wash);

  /* The sticky app topbar's height, as a token because it is not only a topbar
     size: every in-page #anchor has to clear it, so the scroll offset below is
     derived from this rather than guessed. Same value at every width — the phone
     media query trims the topbar's padding, never its height. */
  --topbar-height: 56px;

  --radius-sm: 6px;
  --radius: 10px;
  --radius-lg: 16px;
  --shadow-sm: 0 1px 2px rgba(44, 42, 90, 0.07);
  --shadow: 0 4px 16px rgba(44, 42, 90, 0.10);

  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-serif: Georgia, "Times New Roman", Times, serif;
}

/* Dark theme — the Ledger palette's dark column. Driven by a
   [data-theme="dark"] attribute on <html> (static/js/theme.js resolves the
   "system" choice and reacts to OS changes). --band-soft / --band-accent are
   inherited from :root (the identity band is dark in both themes). */
:root[data-theme="dark"] {
  --indigo-deep: #edecfa;
  --indigo: #b6b2e0;
  --indigo-wash: #302c5c;
  --indigo-ink: #edecfa;

  --coral-deep: #e8967d;
  --coral: #dd8267;
  --coral-pale: #8e3c29;
  --coral-wash: #3a2620;

  --good: #4db37d;
  --good-wash: #1e3a2c;
  --warn: #e0a94e;
  --warn-wash: #3a2a13;
  --crit: #e0776a;
  --crit-wash: #3a201d;

  --ground: #1c1a38;
  --surface: #242145;
  --surface-2: #2c2950;
  --ink: #edecfa;
  --ink-soft: #b6b2e0;
  --ink-faint: #9994c4;
  --on-indigo: #edecfa;

  --band: #1c1a38;

  --bg: var(--ground);
  --surface-sunken: var(--surface-2);

  /* Lifted panel ladder (see the light-mode definitions). #53 proved these
     three steps on the dashboard; generalized here so EVERY table/panel surface
     gets the same page(--ground #1c1a38) < panel < header hierarchy instead of
     sitting flat on the near-identical --surface (#242145). */
  --panel: #2b2856;
  --panel-header: #343063;
  --panel-nested: #332f61;

  --fg: var(--ink);
  --fg-muted: var(--ink-soft);
  --border: #3a3663;
  --primary: var(--coral-deep);
  --primary-hover: #f0a88f;
  --primary-contrast: #211f3d;
  --secondary: var(--indigo);
  --secondary-strong: var(--indigo-deep);
  --secondary-wash: var(--indigo-wash);
  --accent: var(--coral);
  --link: var(--coral-deep);
  --link-hover: #f0a88f;
  --focus: var(--coral);
  --danger: var(--crit);
  --danger-wash: var(--crit-wash);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

/* Every in-page #anchor has to clear the sticky topbar, not just the ones inside
   rendered prose (TT-310d). prose.css offsets .prose__heading, but the app is
   full of plain id targets that nothing else covers — #credentials on the member
   page and the account-settings tab, #billing on both subscription pages,
   #renewal-outcome on a run, #email-delivery — and several of them are the
   destination of an attention-feed link, i.e. the app's own "go look at this"
   affordance landing the thing to look at underneath the bar.

   scroll-padding-top belongs on the SCROLL CONTAINER, which in standards mode is
   <html>; on <body> it does nothing. Scoped via :has(body.app-body) so it applies
   to the app shell only — the auth and onboarding layouts have no topbar, and an
   offset there would just push their targets down for no reason. (:has is already
   load-bearing in this sheet, e.g. .form-section:has(+ .form-actions).) */
html:has(body.app-body) {
  scroll-padding-top: calc(var(--topbar-height) + 1rem);
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  font-family: var(--font-sans);
  color: var(--fg);
  background: var(--bg);
  line-height: 1.6;
}

h1,
h2,
h3 {
  line-height: 1.25;
  color: var(--fg);
}

h1 {
  font-size: 1.7rem;
  letter-spacing: -0.01em;
}

h2 {
  font-size: 1.25rem;
}

a {
  color: var(--link);
  text-decoration: none;
}

a:hover {
  color: var(--link-hover);
  text-decoration: underline;
}

small {
  color: var(--fg-muted);
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ---- Header / brand ---------------------------------------------------- */

.site-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 0.85rem 1.5rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--fg);
}

.brand:hover {
  text-decoration: none;
}

.brand__mark {
  flex: none;
  display: block;
}

/* Countdown-ring logo — theme-aware inline strokes (header + error pages).
   Dark identity bands hardcode brightened arcs + a white checkmark instead;
   the .svg asset files carry their own fixed colours. */
.tt-arc-good { stroke: var(--good); }
.tt-arc-warn { stroke: var(--warn); }
.tt-arc-crit { stroke: var(--crit); }
.tt-check { stroke: var(--indigo-deep); }

.brand__word {
  font-family: var(--font-sans);
  font-size: 1.3rem;
  line-height: 1;
}

.brand__therapy {
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--indigo-deep);
}

.brand__tracked {
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--indigo);
}

:root[data-theme="dark"] {
  .brand__therapy {
    color: var(--indigo-deep);
  }
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.site-nav form {
  display: inline;
  margin: 0;
}

.site-nav__user {
  color: var(--fg-muted);
  font-size: 0.92rem;
}

/* ---- Layout ------------------------------------------------------------ */

main {
  width: 100%;
  max-width: 820px;
  margin: 2.25rem auto;
  padding: 0 1.5rem;
  flex: 1 0 auto;
}

.site-footer {
  flex: none;
  padding: 1.5rem;
  text-align: center;
  border-top: 1px solid var(--border);
  background: var(--surface);
}

.site-footer__tagline {
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--fg-muted);
  font-size: 0.95rem;
}

/* ---- Cards / panels ---------------------------------------------------- */

.demo,
.card,
.panel {
  margin-top: 1.5rem;
  padding: 1.25rem 1.4rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
}

.demo h2:first-child,
.card h2:first-child {
  margin-top: 0;
}

/* ---- Buttons ----------------------------------------------------------- */

/* :where() zeroes the submit/input element selectors' specificity so size/variant
   modifiers (.btn--sm, .btn--ghost, .btn--danger-ghost, …) reliably win on a bare
   <button type="submit"> instead of being out-specified by this base rule. */
button,
.btn,
:where(input[type="submit"], button[type="submit"]) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  cursor: pointer;
  padding: 0.55rem 1rem;
  border: 1px solid var(--primary);
  background: var(--primary);
  color: var(--primary-contrast);
  border-radius: var(--radius-sm);
  font: inherit;
  font-weight: 600;
  line-height: 1.2;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

button:hover,
.btn:hover,
input[type="submit"]:hover,
button[type="submit"]:hover {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
  color: var(--primary-contrast);
  text-decoration: none;
}

/* Two-class selector so a ghost <button type="submit"> (e.g. Duplicate, the
   runs filter's Apply) still beats button[type="submit"]'s solid-primary base,
   which has higher specificity than a lone .btn--ghost would. */
.btn.btn--ghost {
  background: transparent;
  color: var(--primary);
  border-color: var(--border);
}

.btn.btn--ghost:hover {
  background: var(--surface-sunken);
  color: var(--primary-hover);
  border-color: var(--primary);
}

button:disabled,
.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* ---- Forms ------------------------------------------------------------- */

label {
  display: inline-block;
  margin-bottom: 0.25rem;
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--fg);
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="search"],
input[type="tel"],
input[type="url"],
select,
textarea {
  width: 100%;
  max-width: 28rem;
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--fg);
  font: inherit;
}

input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--coral) 22%, transparent);
}

input[type="checkbox"],
input[type="radio"] {
  width: auto;
  max-width: none;
  margin-right: 0.4rem;
  accent-color: var(--primary);
}

textarea {
  resize: vertical;
}

/* Modern number stepper — replaces the native up/down spinner arrows on
   "due in N days" style fields with a cleaner +/- control. */
.number-stepper {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.number-stepper__input.number-stepper__input {
  width: 4.5rem;
  max-width: 4.5rem;
  text-align: center;
  -moz-appearance: textfield;
  appearance: textfield;
}

.number-stepper__input.number-stepper__input::-webkit-inner-spin-button,
.number-stepper__input.number-stepper__input::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.number-stepper__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  flex: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--fg);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
}

.number-stepper__btn:hover {
  background: var(--surface-sunken);
  border-color: var(--primary);
  color: var(--primary-hover);
}

.number-stepper__suffix {
  font-size: 0.82rem;
  color: var(--fg-muted);
}

/* Small ghost icon button for removing a step from a sequence template —
   subtle until hovered, then signals the destructive action with --danger. */
.step-remove-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  flex: none;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
}

.step-remove-btn:hover,
.step-remove-btn:focus-visible {
  background: var(--danger-wash);
  border-color: var(--danger);
  color: var(--danger);
  outline: none;
}

/* ── Step authoring row ───────────────────────────────────────────────────── */

.step-form-row {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 1rem 1.1rem;
  background: var(--surface);
}

.step-drag-handle {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  padding-top: 0.35rem;
  color: var(--fg-muted);
  cursor: grab;
  user-select: none;
}

.step-order-badge {
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--fg-muted);
}

/* The body owns the vertical rhythm via a single gap, so every field is evenly
   spaced and visually distinct — no more fields globbing together. */
.step-form-row__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

/* The step body splits into "what to do" + "settings". One column when narrow,
   two when there's room (the wide authoring layout) — using the extra real
   estate instead of stretching every field across the full width. */
.step-fields {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.9rem;
}

.step-fields__main,
.step-fields__side {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  min-width: 0;
}

/* Narrow: settings sit under "what to do", split by a horizontal rule.
   Wide: two columns split by a vertical rule. */
.step-fields__side {
  border-top: 1px solid var(--border);
  padding-top: 0.9rem;
}

@media (min-width: 1024px) {
  .step-fields {
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
    column-gap: 2rem;
  }

  .step-fields__side {
    border-top: none;
    padding-top: 0;
    border-left: 1px solid var(--border);
    padding-left: 2rem;
  }
}

.step-form-row__footer {
  display: flex;
  justify-content: flex-end;
}

/* A checkbox that is itself the field's label (the due-date / reminder toggles) */
.step-toggle-label {
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

.step-subfields {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  max-width: 26rem;
}

.step-subfield-label {
  display: block;
  font-size: 0.82rem;
  color: var(--fg-muted);
  margin-bottom: 0.2rem;
}

/* Small helper text tied to the field directly above it */
.field-hint {
  font-size: 0.78rem;
  color: var(--fg-muted);
  line-height: 1.4;
  margin: 0;
}

/* allauth + simple forms wrap each field in a <p> */
form p {
  margin: 0 0 1rem;
}

.errorlist,
form ul.errorlist {
  margin: 0.25rem 0 0;
  padding: 0;
  list-style: none;
  color: var(--danger);
  font-size: 0.88rem;
}

.helptext,
form p span {
  display: inline-block;
  margin-top: 0.25rem;
  color: var(--fg-muted);
  font-size: 0.86rem;
}

/* SSO provider list rendered by allauth as <ul><li><a> */
.providers,
ul.providers {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.providers li a {
  display: block;
  padding: 0.55rem 1rem;
  text-align: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  color: var(--primary-hover);
  font-weight: 600;
}

.providers li a:hover {
  border-color: var(--primary);
  text-decoration: none;
}

/* ---- Tables ------------------------------------------------------------ */

table {
  width: 100%;
  border-collapse: collapse;
}

th,
td {
  text-align: left;
  padding: 0.6rem 0.65rem;
  border-bottom: 1px solid var(--border);
}

thead th {
  background: var(--panel-header);
  color: var(--indigo);
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

:root[data-theme="dark"] {
  thead th {
    color: var(--indigo);
  }
}

tbody tr:hover {
  background: color-mix(in srgb, var(--indigo-wash) 40%, transparent);
}

/* Collapsible card list — the mobile counterpart to a wide table. A table shows
   on desktop; on phones it's hidden and this list takes over: each row is a
   collapsed summary that expands (Alpine collapseCard) to reveal the rest. The
   list is display:none by default and only turns on inside the phone media query
   at the end of this file, so it never renders on desktop. */
.collapse-cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: none;
}
.collapse-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel-nested);
  margin-bottom: 0.6rem;
  overflow: hidden;
}
.collapse-card__head {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.6rem;
  width: 100%;
  padding: 0.7rem 0.85rem;
  background: none;
  border: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
/* Beat the global button:hover (0,1,1) which would otherwise fill the head with
   solid primary. A subtle surface tint reads as a tap affordance instead. */
.collapse-card__head:hover {
  background: var(--surface-sunken);
  border-color: transparent;
  color: inherit;
}
.collapse-card__chev {
  flex-shrink: 0;
  color: var(--fg-muted);
  transition: transform 0.15s ease;
}
.collapse-card__head[aria-expanded="true"] .collapse-card__chev {
  transform: rotate(180deg);
}
.collapse-card__body {
  padding: 0 0.85rem 0.55rem;
}
.collapse-card__row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 0.45rem 0;
  border-top: 1px solid var(--border);
  text-align: right;
}
/* A row's top border is its separator from the row above it. Inside a
   .collapse-card that includes separating the FIRST row from the head, which
   draws no border of its own — so the border must stay there. Inside a
   .section-card the head DOES draw a bottom border, and the first row's top
   border lands a second rule a body-padding below it: the doubled divider on
   record detail cards (TT-310). Scoped to that context for exactly that reason;
   .panel-row solves the same problem the same way (see :first-child below it). */
.section-card__body > .collapse-card__row:first-child {
  border-top: 0;
  padding-top: 0;
}
.collapse-card__label {
  flex-shrink: 0;
  color: var(--fg-muted);
  font-weight: 600;
  text-align: left;
  white-space: nowrap;
}
.collapse-card__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem;
  padding-top: 0.6rem;
  margin-top: 0.15rem;
  border-top: 1px solid var(--border);
}
/* Expense summary line (date · vendor · amount) inside the card head. */
.expense-sum__date {
  flex-shrink: 0;
  font-size: 0.85rem;
  color: var(--fg-muted);
}
.expense-sum__vendor {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}
.expense-sum__amount {
  flex-shrink: 0;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* Collapse-card summary bits — shared by the dashboard and every list table. */
.collapse-card__title,
.dash-card__title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}
.collapse-card__badge,
.dash-card__badge {
  flex-shrink: 0;
}
.collapse-card__meta,
.dash-card__meta {
  flex-shrink: 0;
  color: var(--fg-muted);
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
}
.collapse-card__avatar {
  flex-shrink: 0;
}
.dash-card__progress {
  margin: 0.1rem 0 0.35rem;
}
.dash-cards__empty {
  padding: 1.5rem 1rem;
  text-align: center;
  color: var(--fg-muted);
  font-size: 0.9rem;
}

/* Show any collapse-card list, and hide its paired desktop table, on phones. */
@media (max-width: 600px) {
  .collapse-cards { display: block; }
  .dash-table--desktop,
  .table-card--desktop { display: none; }
}

/* ---- Lists ------------------------------------------------------------- */

main ul:not(.messages):not(.providers) {
  padding-left: 1.2rem;
}

main li {
  margin: 0.25rem 0;
}

/* ---- Alerts / messages ------------------------------------------------- */

.messages {
  list-style: none;
  margin: 0 0 1.5rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.alert {
  padding: 0.7rem 1rem;
  border: 1px solid var(--good);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  background: var(--good-wash);
  color: var(--good);
  font-size: 0.95rem;
}

.alert--error,
.alert--danger {
  border-color: var(--danger);
  background: var(--danger-wash);
  color: var(--danger);
}

.alert--warning {
  border-color: var(--warn);
  background: var(--warn-wash);
  color: var(--warn);
}

:root[data-theme="dark"] {
  .alert {
    color: var(--good);
  }
}

/* Post-save confirmation pane — same success tone as .alert, but a titled card
   with a bulleted list of what changed. */
.save-summary {
  border: 1px solid var(--good);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  background: var(--good-wash);
  color: var(--good);
  padding: 0.85rem 1rem;
  margin-bottom: 1.5rem;
}

.save-summary__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.95rem;
}

.save-summary__icon {
  flex-shrink: 0;
}

.save-summary__close {
  margin-left: auto;
  display: inline-flex;
  background: none;
  border: none;
  padding: 0.15rem;
  color: inherit;
  cursor: pointer;
  opacity: 0.65;
}

.save-summary__close:hover,
.save-summary__close:focus-visible {
  opacity: 1;
  outline: none;
}

.save-summary__list {
  margin: 0.55rem 0 0;
  padding-left: 1.7rem;
  font-size: 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

:root[data-theme="dark"] {
  .save-summary {
    color: var(--good);
  }
}

/* ---- Auth / entrance layout -------------------------------------------- */

.auth-body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

.auth-split {
  display: grid;
  grid-template-columns: 5fr 7fr;
  min-height: 100vh;
}

/* Hero panel */

.auth-hero {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 3.5rem 3rem 5rem;
  background: var(--band);
}

.auth-hero__brand {
  display: inline-flex;
  align-items: center;
  gap: 0.85rem;
  text-decoration: none;
  margin-bottom: 1.5rem;
}

.auth-hero__wordmark {
  font-family: var(--font-sans);
  font-size: clamp(2rem, 3.8vw, 3rem);
  line-height: 1;
  display: block;
}

.auth-hero__therapy {
  font-weight: 700;
  letter-spacing: -0.01em;
  color: #ffffff;
}

.auth-hero__tracked {
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--band-soft);
}

.auth-hero__tagline {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 1.05rem;
  color: var(--band-soft);
  opacity: 0.75;
  margin: 0;
}

.auth-hero__curve {
  position: absolute;
  bottom: 2.5rem;
  left: 0;
  right: 0;
  width: 100%;
  height: 60px;
  pointer-events: none;
}

/* Form panel */

.auth-panel {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3rem 2rem;
  background: var(--surface);
}

.auth-form-wrap {
  width: 100%;
  max-width: 22rem;
}

.auth-form__heading {
  font-size: 1.6rem;
  margin: 0 0 1.5rem;
  color: var(--fg);
}

.auth-form__sub {
  margin: -0.85rem 0 1.4rem;
  color: var(--fg-muted);
  font-size: 0.92rem;
  line-height: 1.5;
}

.auth-form__back {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin: 0 0 1rem;
  padding: 0;
  background: none;
  border: 0;
  color: var(--link);
  font-size: 0.88rem;
  cursor: pointer;
}
.auth-form__back:hover { text-decoration: underline; }

/* Passkey button */

.btn--passkey {
  width: 100%;
  justify-content: center;
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-contrast);
  padding: 0.7rem 1rem;
  font-size: 0.97rem;
}

.btn--passkey:hover {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
  color: var(--primary-contrast);
}

/* "or" divider */

.auth-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.25rem 0;
  color: var(--fg-muted);
  font-size: 0.85rem;
}

.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* allauth entrance pages routed through the hero (templates/allauth/layouts/
   entrance.html). Their bodies are built from {% element %} tags, which emit
   .manage-page__title / .btn / bare hr+h2, so scope a few rules to the entrance
   wrapper to make them read like the hand-built login form — without touching the
   login / sign-in-code pages that share .auth-panel. */
.auth-form-wrap--entrance .manage-page__title {
  font-size: 1.6rem;
  margin: 0 0 1rem;
  color: var(--fg);
}
.auth-form-wrap--entrance .manage-page__text {
  margin: 0 0 1.25rem;
  color: var(--fg-muted);
  font-size: 0.92rem;
  line-height: 1.5;
}
/* Tighten only the lede paragraph directly under the heading; a trailing helper
   paragraph (e.g. "contact us…" after the form) keeps its own top spacing. */
.auth-form-wrap--entrance .manage-page__title + .manage-page__text {
  margin-top: -0.35rem;
}
/* Stacked, full-width CTAs (element button.html doesn't add .btn--full). */
.auth-form-wrap--entrance .btn,
.auth-form-wrap--entrance button:not(.auth-form__back) {
  width: 100%;
  justify-content: center;
}
/* Breathing room between stacked alternative buttons (e.g. the re-auth
   "Other ways to confirm" group: security key / authenticator). */
.auth-form-wrap--entrance .btn + .btn { margin-top: 0.5rem; }
/* Forms carry bottom space so a trailing helper paragraph clears the CTA. */
.auth-form-wrap--entrance form { margin: 0 0 1.25rem; }
.auth-form-wrap--entrance form + form { margin-top: 0.6rem; }
/* "Alternative options" divider + label (bare hr/h2 from un-overridden elements). */
.auth-form-wrap--entrance hr {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 1.5rem 0 1.25rem;
}
.auth-form-wrap--entrance h2 {
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--fg-muted);
  margin: 0 0 0.75rem;
}

/* Form fields */

.field {
  margin-bottom: 1.1rem;
}

.field__label-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.25rem;
}

.field__label-row label {
  margin-bottom: 0;
}

.auth-form__forgot {
  font-size: 0.82rem;
  color: var(--fg-muted);
}

.auth-form__forgot:hover {
  color: var(--link);
}

/* Full-width submit */

.btn--full {
  width: 100%;
  justify-content: center;
  margin-top: 0.5rem;
  padding: 0.7rem 1rem;
  font-size: 0.97rem;
}

.auth-form__footer {
  margin: 1.5rem 0 0;
  text-align: center;
  font-size: 0.9rem;
  color: var(--fg-muted);
}
.auth-form__footer--tight { margin-top: 0.4rem; }

/* Inline, link-styled button (e.g. "Request a new code" — a form submit that must
   read as a link). */
.auth-form__linkbtn {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--link);
  cursor: pointer;
}
.auth-form__linkbtn:hover { text-decoration: underline; }

/* Mobile: stack hero above form */

@media (max-width: 700px) {
  .auth-split {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
    min-height: 100vh;
  }

  .auth-hero {
    padding: 2.25rem 1.75rem 3rem;
    justify-content: flex-start;
  }

  .auth-hero__wordmark {
    font-size: 1.9rem;
  }

  .auth-hero__curve {
    bottom: 1rem;
  }

  .auth-panel {
    align-items: flex-start;
    padding: 2.25rem 1.75rem;
  }
}

/* ── Onboarding wizard ───────────────────────────────────────────────────── */

.ob-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

.ob-header {
  background: var(--band);
  padding: 1rem 2rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.ob-wordmark {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
  color: #fff;
  font-family: var(--font-sans);
  font-size: 1.15rem;
  letter-spacing: 0.01em;
}

.ob-monogram {
  height: 28px;
  width: 28px;
}

.ob-main {
  flex: 1;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 3rem 1.5rem 4rem;
}

.ob-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  width: 100%;
  max-width: 640px;
  padding: 2.5rem;
}

/* Step indicator */
.ob-steps ol {
  list-style: none;
  margin: 0 0 2rem;
  padding: 0;
  display: flex;
  align-items: center;
}

.ob-step {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.ob-step__num {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8rem;
  font-weight: 700;
  background: var(--border);
  color: var(--fg-muted);
  flex-shrink: 0;
}

.ob-step--active .ob-step__num {
  background: var(--primary);
  color: var(--primary-contrast);
}

.ob-step--done .ob-step__num {
  background: var(--good-wash);
  color: var(--good);
}

.ob-step__label {
  font-size: 0.85rem;
  color: var(--fg-muted);
  white-space: nowrap;
}

.ob-step--active .ob-step__label {
  color: var(--fg);
  font-weight: 600;
}

.ob-step-connector {
  flex: 1;
  height: 1px;
  background: var(--border);
  margin: 0 0.75rem;
}

/* Content area */
.ob-content {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.ob-content--center {
  align-items: center;
  text-align: center;
  padding: 1rem 0;
}

.ob-heading {
  font-family: var(--font-serif);
  font-size: 1.6rem;
  color: var(--fg);
  margin: 0 0 0.4rem;
}

.ob-subheading {
  color: var(--fg-muted);
  font-size: 0.95rem;
  margin: 0 0 1.75rem;
  line-height: 1.55;
}

.ob-complete-icon {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--good-wash);
  color: var(--good);
  font-size: 1.6rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1.25rem;
}

.ob-complete-icon--warn {
  background: var(--warn-wash);
  color: var(--warn);
}

/* Form fields */
.ob-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin-bottom: 1rem;
}

.ob-field label {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--fg);
}

.ob-field input,
.ob-field select {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.75rem;
  font-size: 0.95rem;
  color: var(--fg);
  background: var(--surface);
  width: 100%;
  box-sizing: border-box;
  transition: border-color 0.15s;
}

.ob-field input:focus,
.ob-field select:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--coral) 20%, transparent);
}

.ob-field__error {
  font-size: 0.8rem;
  color: var(--danger);
  margin: 0;
}

.ob-field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

.ob-field-row--3 {
  grid-template-columns: 1fr 80px 100px;
}

/* Invite rows */
.ob-invite-header {
  display: grid;
  grid-template-columns: 1fr 180px;
  gap: 1rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 0.5rem;
}

.ob-invite-row {
  display: grid;
  grid-template-columns: 1fr 180px;
  gap: 1rem;
  align-items: start;
  margin-bottom: 0.5rem;
}

.ob-invite-row .ob-field {
  margin-bottom: 0;
}

/* Actions */
.ob-actions {
  margin-top: 1.75rem;
  display: flex;
  justify-content: flex-end;
}

.ob-actions--split {
  justify-content: space-between;
}

/* Invite-accept: passkey vs password chooser */
.signin-choice {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.signin-choice__opt {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  cursor: pointer;
  transition: border-color 0.12s;
}
.signin-choice__opt:has(input:checked) {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
.signin-choice__opt input {
  margin-top: 0.2rem;
  flex: none;
}
.signin-choice__body {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.signin-choice__title {
  font-weight: 600;
  font-size: 0.95rem;
  display: flex;
  align-items: center;
  gap: 0.45rem;
}
.signin-choice__tag {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--coral-deep);
  background: var(--coral-wash);
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
}
.signin-choice__hint {
  font-size: 0.82rem;
  color: var(--fg-muted);
}

.ob-actions--center {
  justify-content: center;
}

.ob-btn {
  display: inline-flex;
  align-items: center;
  padding: 0.6rem 1.4rem;
  border-radius: var(--radius-sm);
  font-size: 0.95rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  border: none;
  transition: background 0.15s, color 0.15s;
}

.ob-btn--primary {
  background: var(--primary);
  color: var(--primary-contrast);
}

.ob-btn--primary:hover {
  background: var(--primary-hover);
}

.ob-btn--ghost {
  background: transparent;
  color: var(--fg-muted);
  border: 1px solid var(--border);
}

.ob-btn--ghost:hover {
  background: var(--surface-sunken);
  color: var(--fg);
}

/* Self-serve checkout (TT-258, payment-then-setup) */
.ob-card--wide { max-width: 800px; }
.ob-notice {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.8rem 1rem;
  margin: 0 0 1.25rem;
  font-size: 0.9rem;
  color: var(--fg);
  background: var(--surface-sunken);
}
.ob-notice p { margin: 0 0 0.35rem; }
.ob-notice p:last-child { margin-bottom: 0; }
.ob-notice--warn {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 8%, transparent);
}
.ob-choice {
  border: 0;
  padding: 0;
  margin: 0 0 1.5rem;
  min-width: 0;
}
.ob-choice__legend {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--fg);
  padding: 0;
  margin-bottom: 0.5rem;
}
.ob-choice__options {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.6rem;
}
.ob-choice__opt {
  font-weight: 400;
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  column-gap: 0.7rem;
  align-items: start;
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  cursor: pointer;
  transition: border-color 0.12s;
}
.ob-choice__opt:has(input:checked) {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
.ob-choice__opt input {
  grid-row: 1 / span 2;
  margin-top: 0.2rem;
  width: auto;
}
.ob-choice__title {
  font-weight: 600;
  font-size: 0.95rem;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex-wrap: wrap;
}
.ob-choice__tag {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--coral-deep);
  background: var(--coral-wash);
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
}
.ob-choice__hint {
  font-size: 0.82rem;
  font-weight: 400;
  color: var(--fg-muted);
}
.ob-stepper {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.ob-stepper__btn {
  width: 2.25rem;
  height: 2.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--fg);
  font-size: 1.1rem;
  cursor: pointer;
}
.ob-stepper__btn:hover { background: var(--surface-sunken); color: var(--fg); border-color: var(--border); }
/* Two classes: the global input[type="number"] rule (0,1,1) caps every input at
   28rem, and a lone .ob-stepper__input (0,1,0) loses to it. */
.ob-stepper .ob-stepper__input {
  width: 4.5rem;
  max-width: 4.5rem;
  text-align: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.4rem;
  font-size: 0.95rem;
  color: var(--fg);
  background: var(--surface);
}
.ob-stepper__label {
  font-size: 0.85rem;
  color: var(--fg-muted);
}
.ob-check {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  font-weight: 400; /* a <label>; the global label rule bolds it, and the hint must not inherit that */
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  cursor: pointer;
}
.ob-check:has(input:checked) {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
.ob-check--error { border-color: var(--danger); }
.ob-check input { margin-top: 0.25rem; flex: none; width: auto; }
.ob-check__body { display: flex; flex-direction: column; gap: 0.15rem; }
.ob-check__title { font-weight: 600; font-size: 0.95rem; }
.ob-check__hint { font-size: 0.82rem; color: var(--fg-muted); }
.ob-quote {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  padding: 0.9rem 1rem;
  margin: 0 0 1.5rem;
}
.ob-quote__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
}
.ob-quote__label { font-size: 0.9rem; color: var(--fg-muted); }
.ob-quote__total { font-size: 1.05rem; }
.ob-quote__total strong { font-size: 1.4rem; }
.ob-quote__trial { margin: 0.5rem 0 0.25rem; font-size: 0.9rem; }
.ob-agree__done { font-size: 0.9rem; margin: 0; }
/* TT-334: the per-order plan term, read as part of the order it governs. */
.ob-plan-terms { font-size: 0.92rem; line-height: 1.5; margin: 0 0 0.35rem; }
.ob-checkout__stripe { margin-top: 0.75rem; }
.ob-header__signout { font-size: 0.85rem; color: var(--fg-muted); text-decoration: none; }
.ob-header__signout:hover { color: var(--fg); text-decoration: underline; }
.ob-switch { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--border); }
.ob-switch__form { display: inline-block; margin: 0.35rem 0.5rem 0 0; }
.ob-agreements__toc {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  margin: 0 0 1.5rem;
  font-size: 0.9rem;
}
.ob-agreement { margin: 0 0 2rem; }
.ob-agreement__title { font-size: 1.15rem; margin: 0 0 0.25rem; }
.ob-agreement__meta { font-size: 0.82rem; color: var(--fg-muted); margin: 0 0 0.75rem; }
.ob-agreement__body { font-size: 0.92rem; line-height: 1.55; }

@media (max-width: 600px) {
  .ob-choice__options { grid-template-columns: 1fr; }
  .ob-card {
    padding: 1.5rem 1.25rem;
  }

  .ob-field-row,
  .ob-field-row--3,
  .ob-invite-row {
    grid-template-columns: 1fr;
  }

  .ob-invite-header {
    display: none;
  }

  .ob-step__label {
    display: none;
  }
}

/* ── Authenticated app shell ─────────────────────────────────────────────── */

.app-body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
}

.app-shell {
  display: flex;
  min-height: 100vh;
}

/* Sidebar */
.app-sidebar {
  width: 240px;
  flex-shrink: 0;
  background: var(--band);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.app-sidebar__brand {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 1.25rem 1rem 1rem;
  text-decoration: none;
  margin-bottom: 0.5rem;
}

.app-sidebar__monogram {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
}

.app-sidebar__wordmark {
  font-family: var(--font-sans);
  font-size: 1rem;
  color: #fff;
  letter-spacing: 0.01em;
}

/* Nav */
.app-nav {
  flex: 1 1 auto;
  min-height: 0;        /* allow the nav to shrink + scroll inside the flex column */
  overflow-y: auto;
  padding: 0 0.5rem;
}

.app-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.app-nav__item {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.55rem 0.75rem;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  color: rgba(255, 255, 255, 0.72);
  text-decoration: none;
  transition: background 0.12s, color 0.12s;
  cursor: pointer;
  border: none;
  background: transparent;
  width: 100%;
  text-align: left;
}

.app-nav__item:hover {
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
}

.app-nav__item--active {
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  font-weight: 600;
}

.app-nav__icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  opacity: 0.85;
}

.app-nav__item--active .app-nav__icon,
.app-nav__item:hover .app-nav__icon {
  opacity: 1;
}

.app-nav__divider {
  height: 1px;
  background: rgba(255, 255, 255, 0.12);
  margin: 0.5rem 0.75rem;
  list-style: none;
}

/* Section heading inside the nav — groups items (Work / Records / Administration) */
.app-nav__label {
  list-style: none;
  padding: 0.7rem 0.75rem 0.2rem;
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: rgba(255, 255, 255, 0.38);
  pointer-events: none;
  user-select: none;
}

/* Expandable nav sections (Admin center / Organization) */
.app-nav__section-head {
  display: flex;
  align-items: center;
  gap: 2px;
}
.app-nav__section-head .app-nav__item {
  flex: 1;
  min-width: 0;
}
.app-nav__toggle {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: none;
  background: transparent;
  color: rgba(255, 255, 255, 0.55);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.app-nav__toggle:hover {
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
}
.app-nav__chev {
  transition: transform 0.18s ease;
}
.app-nav__toggle[aria-expanded="true"] .app-nav__chev {
  transform: rotate(90deg);
}
.app-nav__sub {
  list-style: none;
  margin: 2px 0 4px 1.45rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  border-left: 1px solid rgba(255, 255, 255, 0.14);
}
.app-nav__subitem {
  display: block;
  padding: 0.4rem 0.7rem;
  border-radius: var(--radius-sm);
  font-size: 0.83rem;
  color: rgba(255, 255, 255, 0.6);
  text-decoration: none;
  transition: background 0.12s, color 0.12s;
}
.app-nav__subitem:hover {
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
}
.app-nav__subitem--active {
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-weight: 600;
}
/* A group label INSIDE the accordion — the Organization children folded into the
   Admin center section (TT-302, decision 4.1b). Same voice as .app-nav__label,
   one size down, so a reader sees a sub-group rather than a second section. */
/* The org group's own list inside the accordion: same rail, no second border. */
.app-nav__sub--group { border-left-color: transparent; margin-top: 0; }
.app-nav__sublabel {
  list-style: none;
  padding: 0.55rem 0.7rem 0.15rem;
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: rgba(255, 255, 255, 0.38);
  pointer-events: none;
  user-select: none;
}

/* Sidebar footer */
.app-sidebar__footer {
  flex-shrink: 0;        /* stays pinned while .app-nav scrolls */
  padding: 0.75rem 0.5rem 1rem;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: relative;
}

.app-sidebar__user-card {
  /* inherits app-nav__item layout; reset button chrome + room for the chevron */
  gap: 0.6rem;
  width: 100%;
  background: none;
  border: none;
  cursor: pointer;
  font: inherit;
  text-align: left;
}

.app-sidebar__user-chevron {
  margin-left: auto;
  flex-shrink: 0;
  color: rgba(255, 255, 255, 0.55);
  transition: transform 0.15s ease;
}
/* Chevron flips when the account menu is open — off aria-expanded, no :style ternary. */
.app-sidebar__user-card[aria-expanded="true"] .app-sidebar__user-chevron { transform: rotate(180deg); }

/* Account quick menu — pops up from the tile */
.app-sidebar__menu {
  position: absolute;
  left: 0.5rem;
  right: 0.5rem;
  bottom: calc(100% - 0.25rem);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 0.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  z-index: 30;
}

.app-sidebar__menu-item {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.5rem 0.55rem;
  border-radius: var(--radius-sm);
  font-size: 0.88rem;
  color: var(--fg);
}

/* Color pinned HERE at (0,2,0) so both flavors of menu item hover identically:
   the <a> otherwise takes the global a:hover link tint (0,1,1) while the
   <button> takes button:hover's --primary-contrast (0,1,1) — near-invisible on
   the popup surface in both themes. One rule beats both globals; --link-hover
   matches what the <a> always rendered, so this is Log out falling in line
   with Account settings. */
.app-sidebar__menu-item:hover {
  background: var(--surface-sunken);
  color: var(--link-hover);
  text-decoration: none;
}

/* The Log out menu item is a <button>; reset it to match the <a> menu items. */
button.app-sidebar__menu-item {
  width: 100%;
  border: none;
  background: transparent;
  cursor: pointer;
  font: inherit;
  text-align: left;
  justify-content: flex-start;
}

.app-sidebar__menu-logout {
  margin: 0;
}

.app-sidebar__menu-confirm {
  padding: 0.4rem 0.55rem;
}

.app-sidebar__menu-section {
  padding: 0.5rem 0.55rem 0.2rem;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.app-sidebar__menu-label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--fg-muted);
}

/* Light/dark theme toggle — a single ghost icon button in the top bar (mirrors
   the marketing site). Both icons render; CSS keyed off [data-theme] shows the one
   for the mode you'd switch TO (moon in light, sun in dark) — no Alpine expression
   on the icons, so it stays CSP-safe. */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  flex: none;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
  transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.theme-toggle:hover {
  background: var(--surface-sunken);
  color: var(--accent);
}
.theme-toggle:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
.theme-toggle .ic {
  display: block;
}
.theme-toggle .ic--sun {
  display: none;
}
:root[data-theme="dark"] .theme-toggle .ic--moon {
  display: none;
}
:root[data-theme="dark"] .theme-toggle .ic--sun {
  display: block;
}

.app-sidebar__avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.app-sidebar__user-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.app-sidebar__user-name {
  font-size: 0.8rem;
  color: #fff;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-sidebar__user-role {
  font-size: 0.72rem;
  color: rgba(255, 255, 255, 0.55);
}

/* Messages banner inside the app shell */

.app-messages {
  padding: 1rem 2rem 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

@media (max-width: 600px) {
  .app-messages {
    padding: 0.75rem 1.25rem 0;
  }
}

/* Main content area */
.app-content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.app-topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 0 2rem;
  height: var(--topbar-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: sticky;
  top: 0;
  z-index: 10;
}

.app-topbar__title {
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--fg);
  margin: 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.app-topbar__lead {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  min-width: 0;
  flex: 1 1 auto;
}
/* Back affordance in the topbar (e.g. allauth management pages → Security tab). */
.app-topbar__back {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  flex-shrink: 0;
  font-size: 0.85rem;
  color: var(--fg-muted);
  padding-right: 0.85rem;
  border-right: 1px solid var(--border);
}
.app-topbar__back:hover { color: var(--link); }

.app-topbar__right {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  flex-shrink: 0;
}

.app-topbar__practice {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--fg-muted);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.3rem 0.75rem;
  white-space: nowrap;
}

.app-topbar__practice-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  opacity: 0.7;
}

/* Practice switcher — the topbar chip becomes a dropdown when the user belongs
   to more than one practice. */
.app-topbar__switch { position: relative; }
.app-topbar__practice--btn { cursor: pointer; font: inherit; }
.app-topbar__practice--btn:hover { border-color: var(--primary); color: var(--fg); }
.app-topbar__practice-name { max-width: 14rem; overflow: hidden; text-overflow: ellipsis; }
.app-topbar__practice-caret { flex-shrink: 0; opacity: 0.7; transition: transform 0.15s ease; }
/* Caret flips when the menu is open — driven off aria-expanded so no Alpine
   :style ternary is needed (CSP-build friendly). */
.app-topbar__practice--btn[aria-expanded="true"] .app-topbar__practice-caret { transform: rotate(180deg); }
.app-topbar__switch-menu {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  min-width: 14rem;
  max-width: 20rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 0.4rem;
  z-index: 50;
}
.app-topbar__switch-label {
  display: block;
  padding: 0.3rem 0.6rem 0.4rem;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--fg-muted);
}
.app-topbar__switch-menu form { margin: 0; }
.app-topbar__switch-option {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.6rem;
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  font: inherit;
  color: var(--fg);
  text-align: left;
  cursor: pointer;
}
.app-topbar__switch-option:hover { background: var(--surface-sunken); }
.app-topbar__switch-option.is-active { font-weight: 600; }
.app-topbar__switch-option-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-topbar__switch-check { color: var(--primary); flex-shrink: 0; }

/* Inline expand-to-confirm control — shared by the account-menu Log out button
   and every destructive delete (see templates/partials/_confirm_action.html), so
   confirmations read identically wherever they appear. */
.confirm-action {
  display: inline-flex;
  align-items: center;
}

.confirm-inline {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.confirm-inline__label {
  font-size: 0.85rem;
  color: var(--fg-muted);
}

.app-main {
  padding: 2rem;
  max-width: 1100px;
  width: 100%;
}

/* Content-heavy pages (e.g. step authoring) opt into a wider, fluid column so
   they use the available width instead of leaving dead space on large screens.
   Still capped so line lengths stay sane on ultrawide monitors. */
.app-main--wide {
  max-width: 1360px;
}

/* ── Dashboard ────────────────────────────────────────────────────────────── */

.dash-sections {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

.dash-section {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

/* Dark-mode panel/table contrast is now handled globally by the
   --panel / --panel-header / --panel-nested ladder (see the :root tokens):
   .dash-section → --panel (#2b2856), .dash-table thead th → --panel-header
   (#343063, via the base `thead th` rule), and .collapse-card → --panel-nested
   (#332f61). #53's original dashboard-only override folded into that generalized
   hierarchy in TT-103, so no .dash-sections-specific rule is needed. */

.dash-actions-section {
  margin-bottom: 2.5rem;
}

.dash-section__head {
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.dash-section__title {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--fg);
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* Quick-actions header doubles as a disclosure toggle on phones (collapsibleSection).
   On wider screens the chevron is hidden and the button is inert — a plain title. */
.dash-section__toggle {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.5rem;
  width: 100%;
  padding: 0;
  background: none;
  border: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.dash-section__chev {
  display: none;
  margin-left: auto;
  flex-shrink: 0;
  color: var(--fg-muted);
  transition: transform 0.15s ease;
}
.dash-section__toggle[aria-expanded="true"] .dash-section__chev { transform: rotate(180deg); }
@media (min-width: 601px) {
  .dash-actions-section .dash-section__toggle { pointer-events: none; }
}

/* Quick actions — ONE compact toolbar row of icon+label buttons that wrap (TT-297).
   These were five full-width tiles, each carrying a one-line description ("Create a
   patient record" under "Add patient"). That copy is first-run training wheels, and
   it was paying permanent rent directly above the attention feed — inverting the
   dashboard's priority for every returning user. Descriptions deleted, tile chrome
   slimmed to a pill; the space goes back to the feed.

   flex-wrap, not grid: the buttons are label-width, so a grid track would re-introduce
   the fixed 210px tile this row exists to remove. The phone disclosure is untouched —
   it hangs off .dash-actions-section / collapsibleSection, not off this rule. */
.dash-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.9rem 1.5rem;
}

.dash-action {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.85rem;
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: border-color 0.12s, background 0.12s, box-shadow 0.12s;
}

.dash-action:hover {
  background: var(--surface);
  border-color: var(--primary);
  box-shadow: var(--shadow-sm);
}

/* The tinted 40px square went with the tile: at toolbar scale it outweighed the
   label it was meant to mark. A bare glyph in the accent colour reads faster. */
.dash-action__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--primary);
}

.dash-action__icon svg {
  width: 18px;
  height: 18px;
}

.dash-action__label {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--fg);
  white-space: nowrap;
}

/* Mobile-drawer chrome — hidden on desktop; the sidebar is a static rail there. */
.app-topbar__hamburger,
.app-sidebar__close {
  display: none;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  cursor: pointer;
  padding: 0;
  border-radius: var(--radius-sm);
}
.app-topbar__hamburger {
  width: 2.25rem;
  height: 2.25rem;
  margin-left: -0.5rem;
  color: var(--fg);
  flex-shrink: 0;
}
.app-topbar__hamburger:hover { background: var(--surface-sunken); }
.app-sidebar__close {
  position: absolute;
  top: 0.9rem;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  color: rgba(255, 255, 255, 0.7);
}
.app-sidebar__close:hover { background: rgba(255, 255, 255, 0.12); color: #fff; }
.app-backdrop { display: none; }

/* ── Desktop sidebar: auto-collapse to an icon rail, pin to keep open ──────────
   Driven by [data-sidebar] on <html> — seeded pre-paint by theme.js (no flash)
   and toggled by the pin button (sidebarPin). "rail" collapses to a 64px icon rail
   that floats the full 240px panel over the content on hover (peek, no reflow);
   "expanded" keeps it open. All >=900px, so the <900px drawer block below is inert. */
.app-sidebar__pin {
  display: none;  /* desktop-only; enabled by the media query below */
  position: absolute;
  top: 1.15rem;
  right: 0.5rem;
  z-index: 1;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: rgba(255, 255, 255, 0.7);
  cursor: pointer;
  transition: color 0.12s, background 0.12s;
}
.app-sidebar__pin:hover { background: rgba(255, 255, 255, 0.12); color: #fff; }
:root[data-sidebar="expanded"] .app-sidebar__pin { color: var(--band-accent); }
/* Inline SVG flex-items have a 0 min-content width and would otherwise be
   shrunk to nothing by the button's default flex-shrink — pin the size. */
.app-sidebar__pin svg { flex: none; width: 18px; height: 18px; }

@media (min-width: 900px) {
  .app-sidebar__pin { display: inline-flex; }

  :root[data-sidebar="rail"] .app-sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: 64px;
    z-index: 40;
    transition: width 0.16s ease;
  }
  /* Reserve the rail's footprint so content isn't hidden under the fixed rail. */
  :root[data-sidebar="rail"] .app-content { margin-left: 64px; }
  /* Peek: hovering the rail floats the full sidebar over the content (no reflow). */
  :root[data-sidebar="rail"] .app-sidebar:hover {
    width: 240px;
    box-shadow: var(--shadow);
    z-index: 60;
  }
  /* Collapsed (rail, not peeking): icons only, centred. */
  :root[data-sidebar="rail"] .app-sidebar:not(:hover) .app-nav__item:not(.app-sidebar__user-card) {
    justify-content: center;
    gap: 0;
    font-size: 0;
  }
  :root[data-sidebar="rail"] .app-sidebar:not(:hover) .app-sidebar__user-card {
    justify-content: center;
    gap: 0;
  }
  :root[data-sidebar="rail"] .app-sidebar:not(:hover) .app-sidebar__wordmark,
  :root[data-sidebar="rail"] .app-sidebar:not(:hover) .app-sidebar__pin,
  :root[data-sidebar="rail"] .app-sidebar:not(:hover) .app-nav__label,
  :root[data-sidebar="rail"] .app-sidebar:not(:hover) .app-nav__toggle,
  :root[data-sidebar="rail"] .app-sidebar:not(:hover) .app-nav__sub,
  :root[data-sidebar="rail"] .app-sidebar:not(:hover) .app-sidebar__user-info,
  :root[data-sidebar="rail"] .app-sidebar:not(:hover) .app-sidebar__user-chevron {
    display: none;
  }
}

@media (max-width: 900px) {
  /* Sidebar becomes an off-canvas drawer that slides in from the left. */
  .app-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    height: 100dvh;
    z-index: 60;
    transform: translateX(-100%);
    transition: transform 0.25s ease;
    box-shadow: 0 0 40px rgba(0, 0, 0, 0.5);
  }
  .app-shell.is-drawer-open .app-sidebar { transform: translateX(0); }

  .app-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 55;
    background: rgba(0, 0, 0, 0.5);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease;
  }
  .app-shell.is-drawer-open .app-backdrop { opacity: 1; pointer-events: auto; }

  .app-topbar__hamburger,
  .app-sidebar__close { display: inline-flex; }
}

@media (prefers-reduced-motion: reduce) {
  .app-sidebar,
  .app-backdrop { transition: none; }
}

@media (max-width: 600px) {
  .app-main {
    padding: 1.25rem;
  }
  /* Tight phone topbar: reclaim the generous desktop side padding, and collapse
     the practice chip to its icon so the hamburger + page title get the room.
     The practice name/switcher is still reachable (tap the chip, or open the drawer). */
  .app-topbar {
    padding: 0 1rem;
    gap: 0.5rem;
  }
  .app-topbar__practice {
    padding: 0.3rem 0.5rem;
  }
  .app-topbar__practice-name {
    display: none;
  }
  /* Breadcrumb collapses to just its arrow (drawn via ::before so the real link
     text stays in the DOM for screen readers) — the page title gets the room. */
  .app-topbar__back {
    font-size: 0;
    gap: 0;
    padding-right: 0;
    border-right: none;
  }
  .app-topbar__back::before {
    content: "\2190";
    font-size: 1.15rem;
  }

  /* Dashboard: Quick actions collapse to a tap-to-expand disclosure. */
  .dash-actions-section .dash-section__chev { display: block; }
  /* Toolbar pills are the entire expanded section on a phone — pad them back up
     to a comfortable tap target (the desktop row is mouse-sized). */
  .dash-action { padding: 0.65rem 0.9rem; }
}

/* "Needs attention" queue */
.attn-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.attn-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--border);
}

.attn-item:last-child {
  border-bottom: none;
}

.attn-item__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 12rem;
}

.attn-item__summary {
  font-weight: 600;
  color: var(--fg);
}

.attn-item__detail {
  font-size: 0.85rem;
  color: var(--fg-muted);
}

/* Renewal progress — pairs an expiring credential with its in-progress run */
.renewal-progress {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-top: 0.3rem;
}

.renewal-progress__bar {
  width: 5.5rem;
  height: 6px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
  flex-shrink: 0;
}

.renewal-progress__fill {
  height: 100%;
  background: var(--primary);
  border-radius: inherit;
}

.renewal-progress__label {
  font-size: 0.85rem;
  color: var(--fg-muted);
}

.renewal-progress__label--none {
  display: inline-block;
  margin-top: 0.3rem;
  font-style: italic;
}

/* ---- Sequences landing: heads-up metric tiles ------------------------- */

.seq-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.25rem;
}

.seq-metrics--focus {
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  max-width: 30rem;
}

/* Exactly-three-tile variant: auto-fit drops the third tile onto its own row on
   a phone, which reads as a stray rather than a set. Recompacting to three
   across (the .finance-stats phone treatment) keeps the band legible as one
   status line. Base grid is unchanged — desktop still auto-fits. */
@media (max-width: 600px) {
  .seq-metrics--trio {
    grid-template-columns: repeat(3, 1fr);
    gap: 0.5rem;
    margin-bottom: 1rem;
  }
  .seq-metrics--trio .seq-metric-tile {
    padding: 0.6rem 0.55rem;
    box-shadow: none;
  }
  .seq-metrics--trio .seq-metric-tile__value { font-size: 1.15rem; }
  .seq-metrics--trio .seq-metric-tile__label { font-size: 0.6rem; }
}

.seq-metric-tile {
  display: block;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.seq-metric-tile:hover {
  text-decoration: none;
  border-color: var(--fg-muted);
  box-shadow: var(--shadow-sm);
}

.seq-metric-tile--active {
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary);
}

.seq-metric-tile__value {
  display: block;
  font-size: 1.7rem;
  font-weight: 700;
  line-height: 1.1;
}

.seq-metric-tile__label {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.78rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.seq-metric-tile--danger .seq-metric-tile__value { color: var(--danger); }
.seq-metric-tile--warn .seq-metric-tile__value { color: var(--warn); }
.seq-metric-tile--info .seq-metric-tile__value { color: var(--secondary); }
.seq-metric-tile--muted .seq-metric-tile__value { color: var(--fg-muted); }

/* Inline step resolution from a worklist row */
.run-steps-cell {
  background: var(--surface-sunken);
  padding: 0.5rem 1rem 0.85rem;
}

.run-steps-inline .run-step {
  padding: 0.7rem 0;
  border-top: 1px solid var(--border);
}

.run-steps-inline .run-step:first-child {
  border-top: none;
}

/* In-progress renewals table — aligns cell padding with the section head and
   adds a muted second line for credential expiry / overdue notes. */
.dash-table th,
.dash-table td {
  padding-left: 1.5rem;
  padding-right: 1.5rem;
  vertical-align: middle;
}

.dash-table tbody tr:last-child td {
  border-bottom: none;
}

.dash-table .badge.dash-badge--trailing {
  margin-left: 0.5rem;
}

.dash-table__sub {
  font-size: 0.8rem;
  color: var(--fg-muted);
  margin-top: 0.15rem;
}

.dash-table__sub--danger {
  color: var(--danger);
}

/* Knowledge-base article picker on a sequence-template step — a filterable,
   category-grouped, collapsible checkbox list that stays usable as the KB grows. */
.kb-picker__filter {
  width: 100%;
  margin: 0.3rem 0 0.5rem;
}

.kb-picker__groups {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  max-height: 16rem;
  overflow-y: auto;
  background: var(--surface);
}

.kb-picker__group {
  border-bottom: 1px solid var(--border);
}

.kb-picker__group:last-of-type {
  border-bottom: none;
}

.kb-picker__group-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  background: var(--surface-sunken);
  border: none;
  padding: 0.45rem 0.7rem;
  cursor: pointer;
  font: inherit;
  text-align: left;
}

.kb-picker__chevron {
  flex-shrink: 0;
  color: var(--fg-muted);
  transition: transform 0.12s ease;
}

.kb-picker__group-label {
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--fg-muted);
}

.kb-picker__group-count {
  margin-left: auto;
  font-size: 0.72rem;
  color: var(--fg-muted);
  background: var(--border);
  border-radius: 999px;
  padding: 0.05rem 0.5rem;
}

.kb-picker__list {
  list-style: none;
  margin: 0;
  padding: 0.4rem 0.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.kb-picker__option {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-weight: 400;
  font-size: 0.85rem;
  margin: 0;
  cursor: pointer;
}

.kb-picker__empty {
  padding: 0.6rem 0.7rem;
  margin: 0;
  font-size: 0.82rem;
  color: var(--fg-muted);
}

/* Combined dashboard table — sliding tab toggle, type chips, filter box,
   sortable headers, and client-side pagination over the full dataset. */
.dash-table-toolbar {
  flex-wrap: wrap;
  gap: 0.75rem;
}

.dash-table-tools {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.seg {
  display: inline-flex;
  gap: 2px;
  background: var(--surface-sunken);
  border-radius: 999px;
  padding: 3px;
}

.seg__btn {
  flex: 0 1 auto;
  border: 1px solid transparent;
  background: transparent;
  cursor: pointer;
  padding: 0.4rem 1rem;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--fg-muted);
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease;
}

.seg__btn--active {
  background: var(--surface);
  border-color: var(--border);
  color: var(--fg);
  font-weight: 700;
  box-shadow: var(--shadow-sm);
}

.seg__count {
  opacity: 0.6;
  font-variant-numeric: tabular-nums;
}

.type-chips {
  display: flex;
  gap: 0.35rem;
}

.chip {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg-muted);
  border-radius: 999px;
  padding: 0.25rem 0.7rem;
  font-size: 0.78rem;
  cursor: pointer;
}

.chip--on {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-contrast);
  font-weight: 500;
}

.dash-table-filter {
  height: 34px;
  width: 11rem;
  max-width: 40vw;
}

.dash-th--sort {
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}

.dash-th--sort:hover {
  color: var(--fg);
}

.dash-th__arrow {
  display: inline-block;
  width: 0.7em;
  color: var(--primary);
}

.dash-table__empty {
  text-align: center;
  color: var(--fg-muted);
  padding: 2rem 1.5rem;
}

.dash-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.7rem 1.5rem;
  border-top: 1px solid var(--border);
}

.dash-pager__range {
  font-size: 0.8rem;
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}

.dash-pager__nav {
  display: flex;
  gap: 0.4rem;
}

.dash-pager__nav button:disabled {
  opacity: 0.45;
  cursor: default;
}

/* The templateless exit (TT-306 item 2) — "No template fits? Create one."
   sits directly under the template picker, and `.form-field` carries no bottom
   margin of its own, so without this the link runs straight into the next
   field's "Title" label and the two read as one block.

   Scoped to this hint rather than fixed on `.form-field`, deliberately.
   `.form-field` is shared by every form in the application; giving it a bottom
   margin would re-space dozens of pages at once, which is a real visual change
   and belongs in its own review rather than riding along in a sequences PR. */
.field-hint--exit {
  margin-top: 0.1rem;
  margin-bottom: 0.9rem;
}

/* Run header progress (TT-306 item 5) — the shared connected-dot tracker,
   sized for a header meta row rather than the IEP spine.

   The base component is `display: block` with the note stacked under the dots,
   which is right in a table cell and wrong here: every sibling in
   .entity-header__meta is a one-line fact, and a block child inside the inline
   meta item would also split it across anonymous boxes. So this puts the dots
   and their note on one baseline beside the "Progress" label, and drops the
   base rule's stacking margin. Nothing about the dots' MEANING changes — the
   colours and the aria-hidden/note split stay exactly as the component sets
   them. */
.run-progress {
  display: flex;
  align-items: baseline;
  gap: 0.25rem;
  flex-wrap: wrap;
}

.run-progress .dot-track {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
}

.run-progress .dot-track__dots {
  margin-bottom: 0;
  /* The dots are a fixed 9px tall; baseline-aligning a flex row of them against
     the note's text baseline lifts them a hair high, so nudge them back onto
     the note's optical line. */
  align-self: center;
}

/* Run step rows — the divider lives on the class (not a forloop check) so a
   single row re-rendered standalone by an htmx swap matches its neighbours. */
.run-step {
  padding: 1rem 0;
}

.run-step + .run-step {
  border-top: 1px solid var(--border);
}

/* Step actions — "Mark complete" and "Mark N/A" sit side by side, and each
   expands IN PLACE into its own panel. They are two independent <form>s (two
   different actions), not one button group, which is what makes the alignment
   load-bearing rather than cosmetic (TT-310b):

   - The panels carry a real width. They used to carry only `max-width`, which is
     a ceiling and never a floor, so each panel shrink-wrapped to its widest
     child — its own button row — and the notes textarea came out roughly as wide
     as "Save & mark complete" + "Cancel". (Same trap as the global 28rem control
     cap: width:100% resolves against a parent that was itself sized by the
     buttons.)
   - `align-items: flex-end` puts the collapsed sibling level with the expanded
     panel's action row, instead of stranding "Mark N/A" up beside the textarea.
     With both collapsed the two buttons are the same height, so it is a no-op. */
.run-step__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.5rem;
  margin-top: 0.6rem;
}

.run-step__panel {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  width: 28rem;
  max-width: 100%;
}

.run-step__panel--narrow {
  width: 24rem;
  gap: 0.5rem;
}

.run-step__panel-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Run-list filter bar */
.run-filters {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: center;
}

.run-filters__check {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.85rem;
  color: var(--fg-muted);
  white-space: nowrap;
}

/* Collapsible filter disclosure (Sequences landing) */
.run-filters-panel {
  margin: 0 0 1.25rem;
  padding: 0.85rem 1rem;
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.filters-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.4rem;
  margin-left: 0.4rem;
  border-radius: 999px;
  background: var(--secondary-wash);
  color: var(--secondary-strong);
  font-size: 0.72rem;
  font-weight: 700;
}

/* ── Shared feature-page utilities ──────────────────────────────────────────── */

.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
  flex-wrap: wrap;
}

.page-header__filters {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  flex: 1;
}

/* Lead paragraph + primary action for a feature page that opens with prose
   rather than a filter bar. Fills a real gap: list pages have been reaching for
   an undefined `.page-intro` (or raw inline styles) for this, which is why the
   lead's width and spacing drifted per page. The measure cap keeps the sentence
   readable; the action stays top-right and wraps under the prose on narrow. */
.page-lead-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}

.page-lead {
  margin: 0;
  max-width: 62ch;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--fg-muted);
}

/* --- List-page meta row (TT-308) ---------------------------------------------
   Four list pages opened with a paragraph of explainer prose that rendered for
   everyone, every visit, forever: the facility-license lecture above the practice
   credentials, the credentialing primer above paneling, the status glossary above
   an import preview. It is genuinely useful copy — the first time. After that it
   is training wheels charging permanent rent at the top of the page, pushing the
   records the visit is actually for below the fold.

   The rule this row implements: explainer prose renders in the EMPTY state (where
   there is nothing else to show and the reader is by definition new to the
   feature) and behind an on-demand affordance (for everyone else). What stays
   always-on is at most a one-line subtitle.

   .page-meta is the compact strip that carries the on-demand pieces — the help
   disclosure and the alert-window statement — directly above the content. */
.page-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  margin-bottom: 1rem;
  /* The containing block for the help panel below. Anchoring the panel to the
     ROW rather than to its own <summary> is what keeps it on-screen: pinned to
     the trigger, a chip sitting far along a wide row opened a 30rem panel from
     wherever that chip happened to land, which ran off the right of a phone
     viewport (~134px past it on the paneling list, where the short alert text
     leaves the chip on the first line). Anchored to the row, `left: 0` is the
     content's left edge and `width: min(30rem, 100%)` can never exceed the
     content column — at any width, on every page that uses it. */
  position: relative;
}

/* The on-demand explainer itself. <details> rather than an Alpine disclosure on
   purpose: it needs no JS at all, so it is CSP-safe by construction and adds no
   component to register — and tests/test_static_lints.py's two-way Alpine census
   means every registered component must also be mounted somewhere. The same
   reasoning (and very nearly the same CSS) as .ge-help, the guide editor's
   formatting bubble, which is the app's only other disclosure of this shape. */
/* Deliberately NOT position: relative — that would make the summary chip the
   panel's containing block again, which is the bug above. */
.page-help {
  display: inline-block;
}

.page-help > summary {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--fg-muted);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}

.page-help > summary::-webkit-details-marker {
  display: none;
}

.page-help > summary:hover {
  border-color: var(--primary);
  color: var(--primary);
}

.page-help > summary:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.page-help__panel {
  position: absolute;
  left: 0;
  top: calc(100% + 0.4rem);
  z-index: 30;
  /* Relative to .page-meta, so 100% is the content column, not the viewport and
     not the chip. */
  width: min(30rem, 100%);
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow);
  color: var(--fg-muted);
  font-size: 0.85rem;
  line-height: 1.5;
}

.page-help__panel p {
  margin: 0 0 0.6rem;
}

.page-help__panel p:last-child {
  margin-bottom: 0;
}

/* The effective alert window, stated where the expiry badges are actually read.
   "Why is this not flagged yet?" is otherwise only answerable from a settings
   page two clicks away. See partials/_alert_window.html for why the link is
   optional rather than always present. */
.alert-window {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.8rem;
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}

.search-input {
  padding: 0.45rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  font-size: 0.92rem;
  width: 240px;
  max-width: 100%;
}

.search-input:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--coral) 22%, transparent);
}

/* Badges */

.badge {
  display: inline-flex;
  align-items: center;
  padding: 0.2rem 0.6rem;
  border-radius: 99px;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1.4;
  vertical-align: middle;
}

.badge--active {
  background: color-mix(in srgb, var(--good) 14%, transparent);
  color: var(--good);
}

.badge--inactive {
  background: var(--surface-sunken);
  color: var(--fg-muted);
}

/* In progress — the amber/gold secondary */
.badge--progress {
  background: var(--secondary-wash);
  color: var(--secondary-strong);
}

/* Due soon / expiring / unverified — orange warning (one step hotter than gold) */
.badge--warn {
  background: var(--warn-wash);
  color: var(--warn);
}

.badge--danger {
  background: var(--danger-wash);
  color: var(--danger);
}

/* Alert tiers (overdue → today → soon), reusing the danger/warn/neutral washes. */
.badge--overdue { background: var(--danger-wash); color: var(--danger); }
.badge--today { background: var(--warn-wash); color: var(--warn); }
.badge--soon { background: var(--surface-sunken); color: var(--fg-muted); }

:root[data-theme="dark"] {
  .badge--active {
    background: color-mix(in srgb, var(--good) 22%, transparent);
    color: var(--good);
  }
}

/* Category badge hues — variety where it carries meaning. Built-ins get a fixed
   semantic hue; custom categories get a stable hue from their slug (see the
   category_hue template filter in apps/core/templatetags). */
.badge--hue-teal { background: var(--good-wash); color: var(--good); }
.badge--hue-amber { background: var(--warn-wash); color: var(--warn); }
.badge--hue-violet { background: #f1ebfd; color: #6b21a8; }
.badge--hue-blue { background: #e4e6fb; color: #3538a0; }
.badge--hue-rose { background: #fde7ef; color: #9d2449; }
.badge--hue-slate { background: #e8eef5; color: #3b5066; }
.badge--hue-grey { background: var(--surface-sunken); color: var(--fg-muted); }

:root[data-theme="dark"] {
  .badge--hue-teal { background: color-mix(in srgb, var(--good) 22%, transparent); color: var(--good); }
  .badge--hue-amber { background: color-mix(in srgb, var(--warn) 22%, transparent); color: var(--warn); }
  .badge--hue-violet { background: rgba(147, 51, 234, 0.22); color: #d8b4fe; }
  .badge--hue-blue { background: rgba(101, 104, 214, 0.22); color: #b6b8ee; }
  .badge--hue-rose { background: rgba(214, 51, 108, 0.20); color: #f4a8c4; }
  .badge--hue-slate { background: rgba(100, 116, 139, 0.24); color: #b7c3d3; }
  .badge--hue-grey { background: var(--surface-sunken); color: var(--fg-muted); }
}

/* Table as card */

.table-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  /* Scroll wide tables (e.g. the owner template list) instead of clipping their
     right-hand columns; tables that fit show no scrollbar. */
  overflow-x: auto;
}

.table-card table {
  margin: 0;
}

/* A table dropped straight into a .section-card IS that card's body, not a card
   of its own: the card already draws the border, the shadow and the radius, and
   the head above it already draws the divider. Twelve templates were re-stating
   that inline, and ten of them ALSO re-added a `border-top` — a second 1px rule
   immediately beneath the head's `border-bottom`, which is the doubled divider on
   every record detail page (TT-310a). Said once here so the next one cannot get
   it wrong.

   Scoped with `>` deliberately: a .table-card nested inside .section-card__body
   (guide_form's attachments, organization_members' admin table) is a real boxed
   table in its own right and keeps its border. */
.section-card > .table-card {
  border: 0;
  box-shadow: none;
  border-radius: 0;
}

/* ...and once it IS the body, it has to be inset like one (TT-305). The head's
   title sits at 1.25rem, `.section-card__body` pads to 1.25rem, but `th,td` pad
   to 0.65rem — so on the fourteen head-over-table cards the section title stood
   0.6rem to the LEFT of the column it titles, and the founder's note was that
   the header "sits too tight to its section". Vertically it is the same story:
   0.6rem of cell padding under a 1px divider, against 1.25rem everywhere else,
   which put two uppercase micro-headings (the section title and the column
   heads, near-identical treatments on adjacent filled bands) ~1.45rem apart.

   Fixed by widening the CELLS, never the head: `.section-card__head`'s padding is
   shared with `.section-card__toggle` and with 30+ head-over-BODY cards that are
   already correct. Same shape as the two rules that already solve this locally,
   `.dash-table` and `.iep-register`. */
.section-card > .table-card th,
.section-card > .table-card td {
  padding-left: 1.25rem;
  padding-right: 1.25rem;
}

.section-card > .table-card thead th {
  padding-top: 0.85rem;
  padding-bottom: 0.85rem;
}

.table-action {
  width: 1%;
  white-space: nowrap;
  padding-right: 1rem;
}

/* Empty state */

.empty-state {
  text-align: center;
  padding: 4rem 1.5rem;
  color: var(--fg-muted);
}

.empty-state p {
  margin: 0 0 1.25rem;
  font-size: 0.95rem;
}

/* The second paragraph of an empty state: the explainer under the headline fact.
   Nine empty states were each re-declaring the same two properties inline. It is
   also where TT-308's moved intro prose lands, so it is now a shared rule rather
   than a shape that drifts a little on every page that copies it. */
.empty-state__detail {
  font-size: 0.85rem;
  color: var(--fg-muted);
}

/* Breadcrumb */

.breadcrumb {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--fg-muted);
  margin-bottom: 1.25rem;
  flex-wrap: wrap;
}

.breadcrumb a {
  color: var(--fg-muted);
}

.breadcrumb a:hover {
  color: var(--link);
}

/* Entity detail header */

.entity-header {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.5rem;
  margin-bottom: 1.5rem;
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
}

.entity-header__main {
  flex: 1;
  min-width: 0;
}

.entity-header__name {
  font-size: 1.4rem;
  font-weight: 700;
  color: var(--fg);
  margin: 0 0 0.6rem;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.entity-header__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 2rem;
}

.entity-header__meta-item {
  font-size: 0.88rem;
  color: var(--fg-muted);
}

.entity-header__meta-item strong {
  color: var(--fg);
  font-weight: 600;
  margin-right: 0.25rem;
}

.entity-header__actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-shrink: 0;
}

/* Section cards (used on detail pages) */

.section-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  margin-bottom: 1.5rem;
  overflow: hidden;
}

.section-card__head {
  padding: 0.85rem 1.25rem;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.section-card__title {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: 0;
}

.section-card__body {
  padding: 1.25rem;
}

/* Compact list of links — partner reverse-link panels (insured patients, etc.) */
.link-list {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.link-list li {
  padding: 0.1rem 0;
}

.section-placeholder {
  padding: 2.5rem 1.25rem;
  text-align: center;
  color: var(--fg-muted);
  font-size: 0.9rem;
}

.section-placeholder p {
  margin: 0;
}

/* Form page */

.form-page {
  max-width: 680px;
}

/* Fills the wider main column (see .app-main--wide) rather than the reading-width
   default — the step authoring form needs the room. */
.form-page--wide {
  max-width: 100%;
}

/* A destructive action below the form card. Not .form-actions: that class carries
   a border-top for the divider inside a card, which outside one draws an orphan
   rule under a card that had already closed (TT-310e). */
.form-page__danger {
  margin-top: 1.25rem;
}

.form-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 1.75rem 2rem;
}

.form-section {
  margin-bottom: 1.75rem;
  padding-bottom: 1.75rem;
  border-bottom: 1px solid var(--border);
}

.form-section:has(+ .form-actions) {
  border-bottom: none;
  margin-bottom: 0;
  padding-bottom: 0;
}

.form-section__title {
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--fg-muted);
  margin: 0 0 1rem;
}

/* A lighter heading for a group within a form-section (e.g. Notifications
   under Preferences). */
.form-subsection__title {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--fg);
  margin: 1.25rem 0 0.35rem;
}

/* Checkbox + inline label row (settings forms). */
.form-check {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 0.6rem;
}
.form-check label { margin: 0; font-weight: 500; }

/* Alerts page — channel rows, day-of-week chips, snooze. */
.alerts-channel {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  padding: 0.85rem 1rem;
  margin-bottom: 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.alerts-channel:has(input:checked) {
  border-color: color-mix(in srgb, var(--primary) 45%, var(--border));
  background: color-mix(in srgb, var(--primary) 6%, transparent);
}
.alerts-channel input[type="checkbox"] { margin-top: 0.15rem; flex: 0 0 auto; }
.alerts-channel__text { display: flex; flex-direction: column; gap: 0.15rem; }
.alerts-channel__title { font-weight: 600; }
.alerts-channel__hint { font-size: 0.85rem; color: var(--fg-muted); }

/* Email sub-options, indented under the email toggle. */
.alerts-email-opts {
  margin: 0.1rem 0 0.5rem;
  padding: 0.9rem 0 0.25rem 1.1rem;
  border-left: 2px solid var(--border);
  display: grid;
  gap: 1rem;
}

/* Day-of-week picker as toggle chips (Django renders nested option <div>s). */
.day-picker > div { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.day-picker label {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.75rem;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 500;
  cursor: pointer;
  user-select: none;
}
.day-picker label:has(input:checked) {
  background: color-mix(in srgb, var(--primary) 16%, transparent);
  border-color: var(--primary);
  color: var(--primary);
}
.day-picker input { position: absolute; width: 1px; height: 1px; opacity: 0; }

.alerts-snooze {
  margin-top: 1.5rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--border);
}
.alerts-snooze__form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9rem;
  color: var(--fg-muted);
}
.alerts-snooze__days { width: 4.5rem; }

.form-row {
  display: grid;
  gap: 1rem;
  margin-bottom: 1rem;
}

.form-row:last-child {
  margin-bottom: 0;
}

.form-row--2 {
  grid-template-columns: 1fr 1fr;
}

.form-field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.form-field label {
  margin-bottom: 0;
}

.form-field input,
.form-field select,
.form-field textarea {
  max-width: none;
  width: 100%;
}

/* Equal specificity to the rule above would otherwise let it win on source
   order and stretch checkboxes/radios to full width, blowing out the gap to
   their label text. */
.form-field input[type="checkbox"],
.form-field input[type="radio"] {
  width: auto;
}

.form-field--narrow {
  max-width: 200px;
}

.field-error {
  font-size: 0.82rem;
  color: var(--danger);
  margin: 0;
}

.field-optional {
  font-weight: 400;
  color: var(--fg-muted);
  font-size: 0.85em;
}

.field-required {
  color: var(--danger);
  font-weight: 700;
}

/* Muted per-field helper text under an input (replaces ad-hoc inline styles). */
.field-help {
  margin: 0.3rem 0 0;
  font-size: 0.8rem;
  color: var(--fg-muted);
}

/* Settings — constrain the stacked cards to a comfortable reading width so form
   rows don't stretch across the full main column, with a short lead intro. */
.settings-page {
  max-width: 820px;
}

.settings-page__intro {
  margin: -0.25rem 0 1.5rem;
  color: var(--fg-muted);
  font-size: 0.92rem;
  max-width: 62ch;
}

/* Plan & Billing (settings section) */
.billing-status {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.billing-status__plan { font-weight: 600; color: var(--fg); }
.billing-status__renews { color: var(--fg-muted); font-size: 0.85rem; }
.billing-badge {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
}
.billing-badge--active { background: color-mix(in srgb, var(--primary) 14%, transparent); color: var(--primary); }
.billing-badge--warn { background: var(--warn-wash); color: var(--warn); }
.billing-badge--none { background: var(--surface-sunken); color: var(--fg-muted); }
.billing-details {
  margin: 1rem 0 0;
  display: grid;
  gap: 0.4rem;
  border-top: 1px solid var(--border);
  padding-top: 1rem;
}
.billing-details__row {
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
  font-size: 0.9rem;
}
.billing-details__row dt { color: var(--fg-muted); min-width: 9rem; flex-shrink: 0; }
.billing-details__row dd { margin: 0; color: var(--fg); font-weight: 500; }
.billing-details__mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.82rem;
  font-weight: 400 !important;
  color: var(--fg-muted) !important;
  word-break: break-all;
}
.billing-banner {
  padding: 0.7rem 1rem;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  margin-bottom: 1rem;
}
.billing-banner--warn {
  background: var(--warn-wash);
  color: var(--warn);
  border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
}

/* Alpine.js — hide x-cloak elements before Alpine initialises */
[x-cloak] { display: none !important; }

/* Screen-reader-only content (the standard clip pattern). Replaces the
   hand-rolled position:absolute;left:-9999px inline hack. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.form-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.75rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--border);
}

/* Avatar */

.avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--indigo) 16%, transparent);
  color: var(--indigo-deep);
  font-size: 0.78rem;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  line-height: 1;
}

.avatar--lg {
  width: 40px;
  height: 40px;
  font-size: 1rem;
}

:root[data-theme="dark"] {
  .avatar {
    background: color-mix(in srgb, var(--indigo) 30%, transparent);
    color: var(--indigo-deep);
  }
}

/* Role badges */

.badge--role-practice_owner {
  background: color-mix(in srgb, var(--indigo) 14%, transparent);
  color: var(--indigo-deep);
}

.badge--role-therapist {
  background: color-mix(in srgb, var(--indigo) 14%, transparent);
  color: var(--indigo-deep);
}

.badge--role-therapist_assistant {
  background: var(--coral-wash);
  color: var(--coral-deep);
}

.badge--role-admin {
  background: var(--surface-sunken);
  color: var(--fg-muted);
}

/* TT-159 PR 4: the "Admin" chip beside a persona badge — the ADMINISTER
   capability, shown separately from what the practice calls the member. Outlined
   rather than filled so it reads as an attribute of the badge before it, not a
   second persona. */
.badge--admin-chip {
  background: transparent;
  color: var(--indigo-deep);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--indigo) 45%, transparent);
}

:root[data-theme="dark"] {
  .badge--role-practice_owner,
  .badge--role-therapist {
    background: color-mix(in srgb, var(--indigo) 26%, transparent);
    color: var(--indigo-deep);
  }

  .badge--role-therapist_assistant {
    background: var(--coral-wash);
    color: var(--coral-deep);
  }
}

/* Danger-toned ghost button */

/* Two-class to match .btn.btn--ghost's bumped specificity (and defined after it,
   so Delete — which carries both modifiers — keeps its danger color/border). */
.btn.btn--danger-ghost {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 40%, transparent);
}

.btn.btn--danger-ghost:hover {
  background: var(--danger-wash);
  border-color: var(--danger);
  color: var(--danger);
}

/* Small button variant */

.btn--sm {
  padding: 0.35rem 0.7rem;
  font-size: 0.82rem;
}

/* Uniform-width actions (Edit / Duplicate / Delete) — a min-width comfortably
   past the widest label so all three render at one size regardless of text.
   center justification (from .btn) keeps the labels centered in the extra room. */
.btn--equal {
  min-width: 7rem;
}

.btn--equal.btn--sm {
  min-width: 6rem;
}

/* ── Practice category management ─────────────────────────────────────────── */

.cat-list {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.cat-list__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.4rem 0.7rem 0.4rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: 0.92rem;
}

.cat-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  max-width: 30rem;
}

.cat-form__field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.cat-form__label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--fg-muted);
}

.cat-form__sections {
  border: 0;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.cat-form__sections .cat-form__label {
  padding: 0;
}

.cat-form__check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.92rem;
  cursor: pointer;
}

.cat-form .btn {
  align-self: flex-start;
}

@media (max-width: 600px) {
  .form-card {
    padding: 1.25rem;
  }

  .form-row--2 {
    grid-template-columns: 1fr;
  }

  .entity-header {
    flex-direction: column;
    gap: 1rem;
  }

  .entity-header__actions {
    width: 100%;
    justify-content: flex-start;
  }
}

/* Notifications -------------------------------------------------------------*/

/* Anchor the dropdown to the right cluster (not the off-centre bell) so a wide
   panel can't overflow the left edge of the viewport. */
.app-topbar__right {
  position: relative;
}

.app-topbar__notify-btn {
  position: relative;
  /* The button holds the icon + an (often empty) OOB badge slot. The base .btn
     gap would space those two flex children apart and shift the icon left of
     centre; the badge is absolutely positioned, so zero the gap to centre the
     icon. */
  gap: 0;
}

.app-topbar__notify-icon {
  width: 1.15rem;
  height: 1.15rem;
}

.app-topbar__notify-badge {
  position: absolute;
  top: -3px;
  right: -3px;
  min-width: 1.1rem;
  height: 1.1rem;
  padding: 0 0.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1;
  color: #fff;
  background: var(--danger);
  border-radius: 999px;
}

.app-topbar__notify-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 0.5rem);
  z-index: 50;
  width: 22rem;
  max-height: 28rem;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.notify-panel__header,
.notify-panel__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
}

.notify-panel__header {
  border-bottom: 1px solid var(--border);
}

.notify-panel__footer {
  border-top: 1px solid var(--border);
}

.notify-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.notify-item {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.65rem 1rem;
  border-bottom: 1px solid var(--border);
}

.notify-item--unread {
  background: var(--surface-sunken);
}

.notify-item__form {
  flex: 1;
  display: flex;
  min-width: 0;
}

.notify-item__link {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  flex: 1;
  text-decoration: none;
  color: inherit;
  /* Button reset: the row is a <button> in a POST form (CSRF-protected, since it
     switches the active practice like the practice switcher), styled to match the
     old <a> row exactly. */
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.notify-item__title {
  font-weight: 600;
}

.notify-item__body {
  font-size: 0.85rem;
  color: var(--fg-muted);
}

.notify-item__action {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--primary);
}

.notify-item__read {
  flex: 0 0 auto;
}

.notify-empty {
  padding: 1.25rem 1rem;
  color: var(--fg-muted);
  text-align: center;
}

.notify-prefs__intro {
  color: var(--fg-muted);
  margin-bottom: 1.25rem;
}

.notify-prefs .form__row {
  margin-bottom: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.notify-prefs .form__row--check {
  flex-direction: row;
  align-items: center;
  gap: 0.5rem;
}

.notify-prefs .form__hint {
  color: var(--fg-muted);
  font-size: 0.85rem;
}

/* ── Partner duplicate review — "merge cluster" cards ──────────────────────
   Each card is one group of likely-duplicate partners: a highlighted keeper
   (everything folds into it) above the candidates that merge in. A left accent
   encodes match confidence (teal = same name, amber = similar). */
.dupe-intro {
  color: var(--fg-muted);
  max-width: 46rem;
  margin: 0 0 1.5rem;
  line-height: 1.55;
}

.dupe-group {
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  margin-bottom: 1.25rem;
  overflow: hidden;
}
.dupe-group--exact { border-left-color: var(--good); }
.dupe-group--similar { border-left-color: var(--warn); }

.dupe-group__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 1.1rem;
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--border);
}
.dupe-group__count {
  margin-left: auto;
  font-size: 0.78rem;
  color: var(--fg-muted);
}

.dupe-group__body { padding: 1.1rem; }

.dupe-hint {
  margin: 0 0 0.6rem;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--fg-muted);
}

/* One selectable row per member. The chosen keeper is highlighted; every other
   row shows a "merge into the keeper" action — both toggled client-side as the
   keeper radio changes. */
.dupe-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  padding: 0.65rem 0.85rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
}
.dupe-row + .dupe-row { margin-top: 0.4rem; }
.dupe-row--keeper {
  background: color-mix(in srgb, var(--good) 8%, var(--surface));
  border-color: color-mix(in srgb, var(--good) 26%, var(--border));
}
.dupe-keep {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 5.5rem;
  cursor: pointer;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--fg-muted);
  user-select: none;
}
.dupe-keep input { accent-color: var(--coral); cursor: pointer; margin: 0; }
.dupe-keep__on { color: var(--primary); }
.dupe-row__name { font-weight: 600; }
.dupe-row__name a { color: var(--fg); }
.dupe-row__name a:hover { color: var(--link); }

/* Per-row "merge this one in" toggle (hidden on the keeper row). */
.dupe-merge-check {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--fg-muted);
  user-select: none;
}
.dupe-merge-check input { accent-color: var(--coral); cursor: pointer; margin: 0; }

.dupe-group__foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--border);
}
.dupe-group__foot .btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ── Entity 360 detail (partner header stats + section panels) ─────────────── */
/* At-a-glance counts across the bottom of an entity-header card. */
.entity-header__stats {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem 2rem;
  margin-top: 1.1rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--border);
}
.entity-stat__num {
  display: block;
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--fg);
  line-height: 1.15;
}
.entity-stat__label {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--fg-muted);
}

/* A row inside a section-card__body: a primary name + sub-line on the left, a
   status badge / date on the right. Divider lines sit between rows only. */
.panel-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  padding: 0.65rem 0;
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
}
.panel-row:first-child { border-top: 0; padding-top: 0; }
.panel-row:last-child { padding-bottom: 0; }
.panel-row__main { min-width: 0; }
.panel-row__name { font-weight: 600; color: var(--fg); }
a.panel-row__name { color: var(--link); }
a.panel-row__name:hover { color: var(--link-hover); }
.panel-row__sub { font-size: 0.82rem; color: var(--fg-muted); margin-top: 0.1rem; }
.panel-row__aside { margin-left: auto; display: inline-flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }
.panel-empty { color: var(--fg-muted); margin: 0; }

/* Collapsible section panels — the head is a full-width toggle button. The count
   badge stays in the header so a collapsed card still reads at a glance; the
   chevron + the head's bottom border track the open state via aria-expanded. */
.section-card__toggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem 1.25rem;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--border);
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.section-card__toggle:hover { background: var(--surface-sunken); }
.section-card__toggle[aria-expanded="false"] { border-bottom-color: transparent; }
.section-card__head-aside {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  flex-shrink: 0;
}
.section-card__chevron {
  width: 14px;
  height: 14px;
  color: var(--fg-muted);
  transition: transform 0.15s ease;
}
.section-card__toggle[aria-expanded="true"] .section-card__chevron { transform: rotate(180deg); }

/* ── Record tables (partner 360 panels): search + sort + status filter ─────── */
.rt-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.85rem;
}
.rt-search { flex: 1; min-width: 12rem; }
.rt-filter { width: auto; flex-shrink: 0; }
.rt-count {
  margin-left: auto;
  font-size: 0.8rem;
  color: var(--fg-muted);
  white-space: nowrap;
}

.rt-scroll { overflow-x: auto; }
.rt-table {
  width: 100%;
  border-collapse: collapse;
}
.rt-th {
  text-align: left;
  padding: 0.4rem 0.65rem;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--fg-muted);
  white-space: nowrap;
  border-bottom: 1px solid var(--border);
}
.rt-th--sortable { cursor: pointer; user-select: none; }
.rt-th--sortable:hover { color: var(--fg); }
.rt-arrow { margin-left: 0.25rem; color: var(--primary); font-weight: 700; }
.rt-table td {
  padding: 0.55rem 0.65rem;
  font-size: 0.9rem;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.rt-table tbody tr:hover td { background: var(--surface-sunken); }
.rt-link { color: var(--link); font-weight: 600; }
.rt-link:hover { color: var(--link-hover); }
.rt-muted { color: var(--fg-muted); }
.rt-empty { color: var(--fg-muted); margin: 0.85rem 0 0; }
.rt-truncated { color: var(--fg-muted); font-size: 0.8rem; margin: 0.75rem 0 0; }
.rt-viewall { font-size: 0.8rem; font-weight: 600; color: var(--link); white-space: nowrap; }
.rt-viewall:hover { color: var(--link-hover); }

/* Admin center — card index of the practice's config surfaces. Whole card is a
   link; mirrors the section-card surface but laid out as a responsive grid. */
.admin-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 1rem;
}
.admin-card {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 1.15rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.12s ease, box-shadow 0.12s ease, transform 0.12s ease;
}
.admin-card:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow);
  transform: translateY(-1px);
}
.admin-card__icon {
  display: inline-flex;
  width: 2.25rem;
  height: 2.25rem;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  color: var(--accent);
  margin-bottom: 0.35rem;
}
.admin-card__icon svg { width: 1.25rem; height: 1.25rem; }
.admin-card__title { font-weight: 600; color: var(--fg); }
.admin-card__desc { font-size: 0.85rem; color: var(--fg-muted); line-height: 1.4; }

/* The de-piled Admin center (TT-302, 13.1a): the same cards, grouped under a
   small heading per kind of thing. A group renders only when a card in it does,
   so the heading never sits over nothing. */
.admin-group + .admin-group { margin-top: 1.75rem; }
.admin-group__title {
  margin: 0 0 0.6rem;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--fg-muted);
}

/* --- The Credential Center (TT-302) -------------------------------------------
   Four whole-card links (the admin-card idiom, one size up), each leading with
   the one number that matters. The state is a 3px left rule and the lead's
   colour — the paneling banner's tone grammar — so no state can end up looking
   more designed than another, and "Nothing on file" is deliberately neither
   green nor red: nothing is wrong and nothing is watched. */
.cc-grid {
  display: grid;
  /* Four panes: 2x2 on a laptop-width main column, one row on a wide screen,
     stacked on a phone — never three-and-an-orphan. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: 1rem;
}
.cc-pane {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 1.15rem 1.25rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--fg-muted);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.12s ease, box-shadow 0.12s ease, transform 0.12s ease;
}
.cc-pane:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow);
  transform: translateY(-1px);
}
.cc-pane--urgent { border-left-color: var(--crit); }
.cc-pane--gaps { border-left-color: var(--warn); }
.cc-pane--calm { border-left-color: var(--good); }
.cc-pane--unwatched { border-left-color: var(--fg-muted); }
/* Keep the tone rule on hover: the accent goes on the other three edges only. */
.cc-pane--urgent:hover { border-left-color: var(--crit); }
.cc-pane--gaps:hover { border-left-color: var(--warn); }
.cc-pane--calm:hover { border-left-color: var(--good); }
.cc-pane--unwatched:hover { border-left-color: var(--fg-muted); }
.cc-pane__head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.35rem;
}
.cc-pane__icon {
  display: inline-flex;
  width: 2rem;
  height: 2rem;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  color: var(--accent);
}
.cc-pane__icon svg { width: 1.15rem; height: 1.15rem; }
.cc-pane__title { font-weight: 600; color: var(--fg); }
.cc-pane__lead {
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
}
.cc-pane__lead--crit { color: var(--crit); }
.cc-pane__lead--warn { color: var(--warn); }
.cc-pane__lead--good { color: var(--good); }
.cc-pane__lead--muted { color: var(--fg-muted); font-weight: 600; }
.cc-pane__detail {
  font-size: 0.86rem;
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
  min-height: 1.3em;
}
.cc-pane__desc {
  margin-top: 0.35rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--border);
  font-size: 0.82rem;
  color: var(--fg-muted);
  line-height: 1.4;
}

/* Organization rollup — per-practice tiles. */
.org-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: 0.85rem;
}
.org-tile {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.85rem 1rem;
  background: var(--surface);
}
.org-tile__name { font-weight: 600; color: var(--fg); }
.org-tile__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  margin-top: 0.45rem;
  font-size: 0.85rem;
  color: var(--fg-muted);
}
.org-tile__stats strong { color: var(--fg); }
/* TT-184: a practice with the PATIENTS entitlement off says so, rather than
   just omitting the count — on the org rollup, silence used to be
   indistinguishable from "we couldn't see it". Muted, not alarming: this is a
   normal Phase-1 posture, not a warning. */
.org-tile__badge {
  display: inline-flex;
  align-items: center;
  padding: 0.05rem 0.4rem;
  border: 1px solid var(--border);
  border-radius: 0.6rem;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--fg-muted);
  white-space: nowrap;
}

/* Small stat chips: linked-record count, external ID. Translucent so they read
   on both the white card and the teal-tinted keeper panel, in either theme. */
.dupe-meta {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}
.dupe-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.15rem 0.55rem;
  border-radius: 99px;
  background: color-mix(in srgb, var(--fg) 7%, transparent);
  color: var(--fg-muted);
  font-size: 0.74rem;
  font-weight: 500;
  white-space: nowrap;
}
.dupe-pill--records { color: var(--fg); font-weight: 600; }

/* ── Trust / Security page ─────────────────────────────────────────── */
.trust-prose {
  max-width: 60rem;
  margin: 0 auto;
  color: var(--fg-muted);
  line-height: 1.65;
}
.trust-prose ul {
  list-style: none;
  padding: 0;
  margin: 1.5rem 0 0;
  display: grid;
  gap: 1rem;
  text-align: left;
}
.trust-prose li {
  background: var(--surface, #fff);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1rem 1.25rem;
}
.trust-prose strong { color: var(--fg); }
.trust-subprocessors { text-align: center; }
.trust-table {
  width: 100%;
  max-width: 60rem;
  margin: 1.5rem auto 0;
  border-collapse: collapse;
  text-align: left;
  font-size: 0.92rem;
}
.trust-table th,
.trust-table td {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.trust-table thead th {
  color: var(--fg-muted);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.trust-table tbody td:first-child { color: var(--fg); font-weight: 600; }

/* ── Account settings — compact tabbed panes ───────────────────────── */
.set-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.set-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.95rem 0;
  border-bottom: 1px solid var(--border);
}
.set-row:first-child { padding-top: 0.25rem; }
.set-row:last-child { border-bottom: 0; padding-bottom: 0.25rem; }
.set-row__label {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-weight: 600;
  color: var(--fg);
}
.set-row__hint {
  margin: 0.2rem 0 0;
  font-size: 0.82rem;
  color: var(--fg-muted);
  max-width: 46ch;
}
.set-row__action {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

/* allauth account-management / 2FA / re-auth pages, rendered inside the app
   shell. The element overrides (templates/allauth/elements/*) emit app-native
   buttons + fields; these style the heading / intro text + spacing so each reads
   as a focused Settings sub-page, not a bare allauth page. */
.app-main.account-manage { max-width: 620px; }
.account-manage .manage-page__title {
  font-size: 1.2rem;
  font-weight: 650;
  margin: 0 0 0.5rem;
}
.account-manage .manage-page__text {
  color: var(--fg-muted);
  margin: 0 0 1.1rem;
  max-width: 56ch;
}
.account-manage form { margin-top: 0.25rem; }
.account-manage .form-field { margin-bottom: 0.95rem; }
.account-manage .form-field img { /* TOTP QR */ max-width: 200px; height: auto; }
.account-manage .check-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  font-weight: 400;
}

/* Passkeys listed inline on the Security tab (no jump to a separate page). */
.key-inline {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.key-inline__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
}
.key-inline__id {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}
.key-inline__name { font-weight: 500; }
.key-inline__meta { font-size: 0.78rem; color: var(--fg-muted); }
.key-inline__actions { display: flex; gap: 0.4rem; flex-shrink: 0; }
/* button row sits below the fields with a little air */
.account-manage form > .btn,
.account-manage form > a.btn,
.account-manage form > button { margin-top: 0.5rem; margin-right: 0.5rem; }

/* ── Error pages (404 / 403 / 429) ───────────────────────────────────────────
   Public variant = .errorpage (branded standalone on layouts/auth.html).
   In-shell variant = .error-state (inside layouts/app.html's main).
   All colours are semantic tokens, so both adapt to light/dark automatically. */

.errorpage {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2.5rem 1.5rem;
}
.errorpage__card {
  width: 100%;
  max-width: 31rem;
  text-align: center;
}
.errorpage__brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 2.5rem;
  text-decoration: none;
}
.errorpage__logo { display: block; flex: none; }
.errorpage__logo-bg { fill: var(--indigo-wash); }
.errorpage__logo-t  { fill: var(--primary); }
.errorpage__logo-b  { fill: var(--indigo); }
.errorpage__wordmark { font-size: 1.15rem; font-weight: 700; letter-spacing: -0.01em; }
.errorpage__wordmark-therapy { color: var(--fg); }
.errorpage__wordmark-tracked { color: var(--primary); }

.errorpage__code {
  margin: 0 0 0.4rem;
  font-size: 3.4rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--primary);
}
.errorpage__heading {
  margin: 0 0 0.7rem;
  font-size: 1.6rem;
  line-height: 1.2;
  color: var(--fg);
}
.errorpage__message {
  margin: 0 auto 1.85rem;
  max-width: 27rem;
  color: var(--fg-muted);
  line-height: 1.6;
}
.errorpage__actions {
  display: flex;
  gap: 0.65rem;
  justify-content: center;
  flex-wrap: wrap;
}
.errorpage__tagline {
  margin: 2.4rem 0 0;
  font-size: 0.8rem;
  font-style: italic;
  color: var(--fg-muted);
  opacity: 0.85;
}

/* In-shell variant — centred empty-state within the app main. */
.error-state {
  max-width: 34rem;
  margin: 0 auto;
  padding: 4.5rem 1.5rem;
  text-align: center;
}
.error-state__code {
  margin: 0 0 0.45rem;
  font-size: 2.4rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--primary);
}
.error-state__heading { margin: 0 0 0.5rem; font-size: 1.35rem; color: var(--fg); }
.error-state__message {
  margin: 0 auto 1.6rem;
  max-width: 30rem;
  color: var(--fg-muted);
  line-height: 1.6;
}

/* Finances — Expenses totals strip + category breakdown */
.finance-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1rem;
  margin-bottom: 1.25rem;
}
.finance-stat {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 1.1rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
}
.finance-stat__label {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--fg-muted);
}
.finance-stat__value {
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}
/* Category breakdown panel — a ranked list, not a chart. Replaces a 3.5rem
   conic-gradient donut whose legend already carried every number the ring was
   trying to convey. Bars are plain element widths, so there is no chart library
   and nothing to load. */
.finance-breakdown {
  margin: 0 0 1.5rem;
  padding: 1.1rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
}
.finance-breakdown__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.35rem 1rem;
  margin-bottom: 0.9rem;
}
.finance-breakdown__title {
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--fg-muted);
  margin: 0;
}
.finance-breakdown__meta {
  margin: 0;
  font-size: 0.85rem;
  color: var(--fg-muted);
}
.finance-breakdown__meta strong {
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}
.finance-breakdown__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}
/* label | meter | % | amount. The meter takes the slack so the three text
   columns stay aligned down the list however long a category name is. */
.finance-breakdown__row {
  display: grid;
  grid-template-columns: minmax(6rem, 11rem) 1fr 2.75rem auto;
  align-items: center;
  gap: 0.75rem;
}
.finance-breakdown__label {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-width: 0;
  font-size: 0.88rem;
}
.finance-breakdown__swatch {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 2px;
  flex-shrink: 0;
}
.finance-breakdown__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.finance-breakdown__meter {
  height: 0.5rem;
  border-radius: 99px;
  /* Derived from the text colour rather than a surface token: --surface-sunken
     sits almost on top of --panel on the dark theme, so the track vanished and
     every bar read as a floating dash with nothing to be a proportion OF. */
  background: color-mix(in srgb, var(--fg) 13%, transparent);
  overflow: hidden;
}
.finance-breakdown__fill {
  display: block;
  height: 100%;
  border-radius: 99px;
  /* A one-category filter still needs a visible sliver rather than nothing. */
  min-width: 2px;
}
.finance-breakdown__pct {
  font-size: 0.82rem;
  color: var(--fg-muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.finance-breakdown__amount {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  line-height: 1.25;
  font-variant-numeric: tabular-nums;
}
.finance-breakdown__amount strong {
  font-size: 0.92rem;
}
.finance-breakdown__count {
  font-size: 0.72rem;
  color: var(--fg-muted);
}
/* One category: the bar would be 100% wide on every row (i.e. on the one row),
   which restates the amount and nothing else. Drop the meter and the percentage
   rather than drawing a full bar as decoration. */
.finance-breakdown__list--solo .finance-breakdown__row {
  grid-template-columns: minmax(6rem, 11rem) 1fr auto;
}
.finance-breakdown__list--solo .finance-breakdown__meter,
.finance-breakdown__list--solo .finance-breakdown__pct {
  display: none;
}

/* Finances — expense actions, filter bar, filtered summary + category donut */
.expense-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.6rem;
  margin: 0 0 1.25rem;
}
.expense-filters { margin: 0 0 1rem; }
/* Filter bar: inline on desktop; a "Filters" popout button on phones. The toggle
   is hidden above the phone breakpoint, where the form is always shown inline. */
.expense-filterbar__toggle { display: none; }
.expense-filterbar__chev { margin-left: 0.15rem; transition: transform 0.15s ease; }
.expense-filterbar__toggle[aria-expanded="true"] .expense-filterbar__chev { transform: rotate(180deg); }
.expense-filterbar__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--primary);
  display: inline-block;
}
/* One shared height so the category button, selects, date inputs and Apply line
   up (they're a mix of <button>/<select>/<input type=date>, which otherwise
   render at slightly different intrinsic heights). box-sizing is border-box app-wide. */
.expense-filters .search-input,
.expense-filters .btn {
  height: 2.5rem;
}

/* Category multi-select — a checkbox dropdown (trigger + absolute menu). */
.cat-filter { position: relative; }
.cat-filter__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 11rem;
  cursor: pointer;
  text-align: left;
}
.cat-filter__caret { margin-left: auto; flex-shrink: 0; color: var(--fg-muted); }
.cat-filter__menu {
  position: absolute;
  z-index: 30;
  top: calc(100% + 0.25rem);
  left: 0;
  min-width: 100%;
  max-height: 17rem;
  overflow-y: auto;
  padding: 0.35rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
}
.cat-filter__opt {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.55rem;
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  white-space: nowrap;
  cursor: pointer;
}
.cat-filter__opt:hover { background: var(--surface-sunken); }
.cat-filter__opt input { cursor: pointer; }
.cat-filter__opt--all {
  font-weight: 600;
  margin-bottom: 0.2rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
}
.finance-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
  margin: 0 0 1rem;
  font-size: 0.9rem;
}
.finance-summary__count { color: var(--fg-muted); }
.finance-summary__count strong { color: var(--fg); font-variant-numeric: tabular-nums; }

/* Date-range presets. Submit buttons, not .btn: they sit as a connected segmented
   group so the five read as one control ("which period?") rather than five
   independent actions competing with Apply. */
.expense-ranges {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface);
}
.expense-range {
  border: 0;
  border-right: 1px solid var(--border);
  background: transparent;
  color: var(--fg-muted);
  font: inherit;
  font-size: 0.82rem;
  padding: 0 0.7rem;
  height: 2.5rem;
  cursor: pointer;
  white-space: nowrap;
}
.expense-ranges .expense-range:last-child { border-right: 0; }
/* The global button:hover rule is more specific than a bare class, so pin
   background AND colour together or the label washes out on hover. */
.expense-range:hover,
.expense-range:focus-visible {
  background: var(--surface-sunken);
  color: var(--fg);
}
.expense-range--on,
.expense-range--on:hover {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-contrast);
  font-weight: 600;
}
/* The sixth option: the custom date pair, boxed so it reads as a peer of the
   preset group rather than as two loose inputs that happen to be nearby. */
.expense-custom {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0 0.55rem;
  height: 2.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.expense-custom--on {
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary);
}
.expense-custom__label {
  font-size: 0.78rem;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* The date inputs sit inside the box, so they shed their own frame. Two classes
   to beat the global control rule. */
.expense-custom .search-input {
  height: 2rem;
  border: 0;
  background: transparent;
  padding: 0 0.2rem;
}

/* Phone layout for the expenses page. Placed AFTER the finance base rules so it
   wins on source order (media queries add no specificity). */
@media (max-width: 600px) {
  /* Hide the wide table; the shared .collapse-cards rule shows the card list. */
  .expenses-table-card { display: none; }

  /* Compact stat tiles — three across, tighter, smaller value.
     minmax(0, 1fr), not 1fr: a grid item's default min-width is auto, i.e. the
     width of its widest unbreakable content, so the track floor is set by the
     longest value rather than by the third of the row it was asked for. Adding
     thousands separators lengthened those values by a character and pushed the
     row past the viewport — the tiles have always been one digit from doing it. */
  .finance-stats {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.5rem;
    margin-bottom: 1rem;
  }
  .finance-stat {
    padding: 0.6rem 0.55rem;
    gap: 0.2rem;
    box-shadow: none;
    min-width: 0;
  }
  .finance-stat__label { font-size: 0.6rem; }
  /* Scales with the tile so a six-figure total still fits three across on one
     line. NOT overflow-wrap: a wrapped money value breaks mid-number ("$10,301.7
     / 4"), which is worse than small — it is briefly a different number. */
  .finance-stat__value {
    font-size: clamp(0.7rem, 3.5vw, 1.05rem);
    white-space: nowrap;
  }

  /* The breakdown row's four columns do not fit a phone: the meter is the
     flexible one, so it is what collapses to nothing, leaving the panel with the
     numbers but not the comparison the bars exist to draw. Reflow to two lines —
     name + amount, then the bar with its percentage under them. */
  .finance-breakdown {
    padding: 0.9rem 1rem;
  }
  .finance-breakdown__row {
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "label  amount"
      "meter  pct";
    gap: 0.3rem 0.6rem;
  }
  .finance-breakdown__label { grid-area: label; }
  .finance-breakdown__amount { grid-area: amount; }
  .finance-breakdown__meter { grid-area: meter; align-self: center; }
  .finance-breakdown__pct { grid-area: pct; }
  .finance-breakdown__list { gap: 0.85rem; }
  /* One category: no meter row to lay out, so it stays a single line. */
  .finance-breakdown__list--solo .finance-breakdown__row {
    grid-template-columns: 1fr auto;
    grid-template-areas: "label amount";
  }

  /* Filters move behind a "Filters" popout button; the revealed form stacks its
     controls full-width in a panel. !important beats the inline width:auto on the
     vendor/date inputs. */
  .expense-filterbar__toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0 0 1rem;
  }
  .expense-filterbar .expense-filters {
    flex-direction: column;
    align-items: stretch;
    margin: 0 0 1.25rem;
    padding: 1rem;
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
  }
  .expense-filterbar .expense-filters .search-input,
  .expense-filterbar .expense-filters .cat-filter,
  .expense-filterbar .expense-filters .cat-filter__btn,
  .expense-filterbar .expense-filters .btn { width: 100% !important; }
  .expense-filterbar .expense-filters .cat-filter__btn { min-width: 0; }
  .expense-filters__arrow { display: none; }

  /* Presets wrap to a two-per-row grid rather than scrolling sideways, and the
     custom pair stacks below them. Both go full width like the other controls;
     the date inputs inside the custom box must NOT (they share one row). */
  .expense-filterbar .expense-filters .expense-ranges {
    display: grid;
    grid-template-columns: 1fr 1fr;
    width: 100%;
  }
  .expense-range { border-bottom: 1px solid var(--border); }
  .expense-ranges .expense-range:nth-child(odd) { border-right: 1px solid var(--border); }
  .expense-ranges .expense-range:nth-last-child(-n+2) { border-bottom: 0; }
  .expense-filterbar .expense-filters .expense-custom { width: 100%; }
  .expense-filterbar .expense-filters .expense-custom .search-input {
    width: auto !important;
    flex: 1 1 0;
    min-width: 0;
  }
}

/* Subscription & Features — add-on (licensed module) list */
.addon-list {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.addon-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.addon-item__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.3rem;
}
.addon-item__name { font-weight: 600; color: var(--fg); }
.addon-item__summary { margin: 0; color: var(--fg-muted); font-size: 0.9rem; line-height: 1.5; }
.addon-item__note { margin: 0.3rem 0 0; color: var(--fg-muted); font-size: 0.8rem; font-style: italic; }
/* The card's link to real documentation (TT-302): one short line, never a
   paragraph, whatever state the module is in. */
.addon-item__docs { margin: 0.35rem 0 0; font-size: 0.82rem; }
/* A module no subscription can grant (TT-302 review): a statement, not a button,
   because there is nothing to click through to. */
.addon-item__unsellable {
  flex-shrink: 0;
  align-self: center;
  font-size: 0.8rem;
  color: var(--fg-muted);
  font-style: italic;
}

/* Finances — receipt upload (styled buttons over hidden native file inputs) */
/* --- Expense form ------------------------------------------------------- */

/* .form-field carries no vertical margin of its own and .form-section sets no
   gap, so consecutive fields sit flush. Scoped to this form rather than fixed
   globally: the same flush stacking exists on four other forms and unpicking it
   app-wide is its own change, not a rider on this one. */
.expense-fields {
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

/* Amount. The affix is a real element rather than a background image so it
   inherits colour in both themes; the input is padded clear of it. */
.money-input {
  position: relative;
  display: block;
  max-width: 12rem;
}
.money-input__symbol {
  position: absolute;
  left: 0.65rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--fg-muted);
  pointer-events: none;
  font-variant-numeric: tabular-nums;
}
/* Two classes: `.form-field input` (0,1,1) releases the global 28rem cap to
   width:100%, and a lone `.money-input__field` (0,1,0) would lose to it. */
.money-input .money-input__field {
  width: 100%;
  max-width: none;
  padding-left: 1.55rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Recent-category quick-picks. Not .chip — each carries the category's own
   breakdown colour and reads as a shortcut into the select below it. */
.quick-cats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}
.quick-cat {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}
.quick-cat:hover,
.quick-cat:focus-visible {
  background: var(--surface-sunken);
  color: var(--fg);
  border-color: var(--fg-muted);
}
.quick-cat--on,
.quick-cat--on:hover {
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary);
  background: var(--surface);
  color: var(--fg);
  font-weight: 600;
}
.quick-cat__dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  flex-shrink: 0;
}

/* The select and its escape hatch on one line — "Manage categories" has to be
   reachable at the moment you discover the category you want is missing. */
.cat-picker {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.35rem 0.75rem;
}
.cat-picker select {
  flex: 1 1 14rem;
  min-width: 0;
}
.cat-picker__manage {
  font-size: 0.8rem;
  white-space: nowrap;
}

.receipt-field__native {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}
.receipt-field__controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}
.receipt-field__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}
.receipt-field__selected {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.75rem;
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2, var(--surface));
  max-width: 100%;
}
.receipt-field__thumb {
  width: 2.5rem;
  height: 2.5rem;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  flex-shrink: 0;
}
.receipt-field__name {
  font-size: 0.9rem;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.receipt-field__clear {
  margin-left: auto;
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  line-height: 1;
  font-size: 1.1rem;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
}
.receipt-field__clear:hover {
  background: var(--border);
  color: var(--fg);
}

/* ============================================================================
   Feedback contract (TT-134) — toasts, busy states, lazy-load retry.
   Companion to static/js/feedback.js; every mutating action must show a busy
   signal, every failure a visible error, every success a flash.
   ========================================================================= */

/* --- Toast region — fixed, above the drawer/topbar (z 60), never focusable
       en masse. Individual toasts carry role=alert / role=status. */
.toasts {
  position: fixed;
  bottom: 1rem;
  right: 1rem;
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: min(26rem, calc(100vw - 2rem));
}

.toast {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--good);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--fg);
  font-size: 0.92rem;
  box-shadow: var(--shadow);
  animation: toast-in 0.18s ease-out;
}

.toast--success { border-color: var(--good); }
.toast--info    { border-color: var(--indigo); }
.toast--warn    { border-color: var(--warn); }
.toast--error   { border-color: var(--danger); }

.toast__msg { flex: 1; min-width: 0; }
.toast__link { white-space: nowrap; font-weight: 600; }
.toast__action { flex-shrink: 0; }

.toast__dismiss {
  flex-shrink: 0;
  border: none;
  background: transparent;
  color: var(--fg-muted);
  font-size: 1.05rem;
  line-height: 1;
  padding: 0 0.15rem;
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.toast__dismiss:hover { color: var(--fg); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(0.4rem); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}

/* --- Busy state — htmx forms get .htmx-request (from htmx itself) plus a
       disabled submit via hx-disabled-elt; plain POST forms get .is-busy from
       feedback.js. Both render the same spinner-after-label affordance. */
form.htmx-request,
form.is-busy { cursor: progress; }

form.htmx-request [type="submit"],
form.is-busy [type="submit"],
form.is-busy button:not([type]),
.btn.htmx-request {
  opacity: 0.65;
  pointer-events: none;
}

form.htmx-request [type="submit"]::after,
form.is-busy [type="submit"]::after,
form.is-busy button:not([type])::after,
.btn.htmx-request::after {
  content: "";
  display: inline-block;
  width: 0.8em;
  height: 0.8em;
  margin-left: 0.45em;
  vertical-align: -0.08em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: tt-spin 0.6s linear infinite;
}

@keyframes tt-spin {
  to { transform: rotate(360deg); }
}

/* An in-flight note revealed only while its form is busy (e.g. the import run:
   "this can take a moment"). */
.busy-note { display: none; }
form.is-busy .busy-note,
form.htmx-request .busy-note {
  display: block;
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  color: var(--fg-muted);
}

/* --- Lazy-load slots ([data-load-slot]) — pulse while refreshing (hx-indicator
       points here), and the inline failed-load retry block. */
[data-load-slot].htmx-request { opacity: 0.6; }

.load-slot__loading {
  color: var(--fg-muted);
  font-size: 0.85rem;
}

.load-error {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.88rem;
  color: var(--danger);
}

/* ── Guide editor: Ribbon & Rail (TT-199) ─────────────────────────────────────
   The authoring surface is a document with a toolbar, not a boxed form: a
   borderless title, a formatting ribbon, a page-shaped writing surface, and a
   right rail for metadata. A CONTAINER query (not viewport) drives the
   collapse, because the pinned/unpinned nav rail changes available width at a
   fixed viewport. Nothing here is position:sticky on purpose: the corpus runs
   a few hundred characters and does not scroll. */

.guide-editor { container-type: inline-size; }

.guide-editor__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 260px;
  gap: 2rem;
  align-items: start;
  /* Space UNDER the action row (TT-310g). There was none: the row had 1.75rem of
     padding above it and nothing below, so the strongest proximity cue on the page
     bound Save to the Attachments card beneath rather than to the document above
     it. Harmless on the add page, which renders no Attachments card. */
  margin-bottom: 2rem;
}

.guide-editor__grid > .form-actions {
  /* Column one, not the full grid. Spanning 1/-1 put Save flush with the RIGHT
     edge of the Attachments card directly below and Cancel flush with its left —
     the pair straddled that card's width exactly, which is the geometry of a card
     toolbar, immediately above a panel whose own copy says its files "save on
     their own, separately from the writing". Confined to the document column, the
     row closes the writing surface it actually commits, ~292px left of where it
     used to sit. Below 900px the grid is one column and this is a no-op. */
  grid-column: 1;
  margin-top: 0;
}

/* The implicit-submission default button: first submit in tree order so Enter
   in the title saves. Never rendered — the attribute selector outweighs the
   global button styling that would otherwise beat [hidden]. A display:none
   button still receives the browser's synthetic default-button click. */
[data-ge-default-submit] { display: none !important; }

.guide-editor .form-actions .ge-save { margin-left: auto; }

.ge-errors { margin-bottom: 1rem; }

/* The document column. */
.ge-doc { min-width: 0; }

/* Borderless document title. 1.5rem is a deliberate split: close to the
   detail page's 1.4rem entity heading so create-save-view has no jarring
   jump, large enough to read as a title. The global :focus-visible ring is
   what makes the missing resting border accessible.

   The one dash of chrome it does keep is a dashed rule under the text. Fully
   borderless is what makes a document title look right and also what makes it
   read as static heading text rather than as a field you can type in (founder
   review, 2026-08-06). A dashed rule is the fill-in-the-blank signal, it is
   quieter than a box, and it lines up with the ribbon's top edge below, so it
   reads as part of the document rather than as an input pasted on top of it.
   It goes solid and coral on hover and on focus: a resting hint that
   strengthens, which is the affordance without the chrome. */
input.ge-title {
  font-size: 1.5rem;
  font-weight: 600;
  border: 0;
  /* Not --border: at #dcd9ea on a white page that is a 1.4:1 hairline, and an
     affordance nobody can see is not an affordance. Muted foreground at 60%
     lands on ~3:1, the floor for a non-text UI element, and dashing it takes
     half the pixels back out so it still reads as a hint rather than chrome. */
  border-bottom: 1px dashed color-mix(in srgb, var(--fg-muted) 60%, transparent);
  background: transparent;
  width: 100%;
  /* The global form-control rule caps every input at 28rem, and width:100%
     does not override a max-width. The title is a document heading, not a
     field in a form column, so it spans the document. This was invisible
     while the title was fully borderless; the resting rule below makes a
     448px underline under a 744px document obvious. */
  max-width: none;
  padding: 0.2rem 0 0.3rem;
  margin-bottom: 0.6rem;
  color: var(--fg);
  box-shadow: none;
  /* On the base rule, not the hover one, so the corners do not snap when the
     tint appears. Invisible at rest against a transparent background. */
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  transition: border-color 0.12s ease-in-out, background 0.12s ease-in-out;
}

input.ge-title:hover,
input.ge-title:focus {
  border-bottom-style: solid;
  border-bottom-color: var(--coral);
  background: var(--surface-sunken);
}

input.ge-title::placeholder { color: var(--fg-muted); opacity: 0.7; }

.ge-ribbon {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  padding: 0.35rem 0.6rem;
  background: var(--panel-header);
  border: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
}

.ge-ribbon__tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.15rem;
}

.ge-ribbon__group { display: inline-flex; gap: 0.15rem; }

.ge-ribbon__sep {
  width: 1px;
  height: 1.3rem;
  background: var(--border);
  margin: 0 0.35rem;
}

/* The Preview toggle and the help disclosure wrap as a unit and stay
   right-aligned. */
.ge-ribbon__view {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-left: auto;
}

.ge-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.32rem;
  border: 1px solid transparent;
  background: transparent;
  padding: 0.35rem 0.55rem;
  border-radius: var(--radius-sm);
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--fg);
  cursor: pointer;
}

/* EVERY interactive state below is scoped with .ge-ribbon, and that is
   load-bearing rather than tidiness. The global control rule is

     button:hover, .btn:hover, input[type="submit"]:hover,
     button[type="submit"]:hover { background: var(--primary-hover);
       border-color: var(--primary-hover); color: var(--primary-contrast); }

   A bare `.ge-btn:hover` is (0,2,0). It used to set ONLY `background`, so even
   on the type="button" formatting controls the global `button:hover` (0,1,1)
   still landed `color` and `border-color`: the label rendered
   --primary-contrast on --surface, which is #211f3d on #242145 in dark and
   #ffffff on #ffffff in light. The text was still there and completely
   invisible, with the border flipped to bright coral, so a hovered button read
   as an empty outlined box. Founder review, 2026-08-06.

   The view toggle is worse, because it is a real submit (the no-JS path), and
   `button[type="submit"]:hover` is (0,2,1) — it outranks a bare (0,2,0)
   outright and took the background and the pressed state with it. Scoping to
   .ge-ribbon makes these (0,3,0), which clears (0,2,1). Every .ge-btn in the
   app lives on this strip, so the scope costs nothing.

   Pin all three properties, never just background: whatever the global sets is
   what leaks. */
.ge-ribbon .ge-btn:hover {
  background: var(--surface);
  border-color: var(--border);
  color: var(--fg);
}

/* Pressed state must be more than colour: fill plus border. After the hover
   rule on purpose, so a pressed button keeps reading as pressed while the
   pointer is over it (equal specificity, later wins). */
.ge-ribbon .ge-btn[aria-pressed="true"] {
  background: var(--surface);
  border-color: var(--border);
  color: var(--fg);
  font-weight: 700;
}

/* The view toggle is outside the cloaked toolbar and is the ribbon's only
   control when JavaScript is off, so unlike the formatting buttons it carries
   a resting border: alone on an otherwise empty strip, a transparent-bordered
   button reads as a label rather than as something to press. */
.ge-btn--view {
  border-color: var(--border);
  background: var(--panel);
}

/* …which is exactly why it also needs its OWN pressed rule. The generic
   pressed state fills with --surface, but this is the one ribbon button that
   already rests on a fill, and in the light theme --panel IS --surface — so
   "previewing" and "writing" rendered with an identical background AND an
   identical border, separated only by the weight bump. Founder review,
   2026-08-06: "not very noticable". Coral wash plus a coral border is the
   app's own active language, and the weight bump stays so the state is never
   carried by colour alone (redesign note §7). --coral for the stroke and
   --coral-deep for the text is the brand rule: mid coral is sub-AA as text. */
.ge-ribbon .ge-btn--view[aria-pressed="true"] {
  background: var(--coral-wash);
  border-color: var(--coral);
  color: var(--coral-deep);
  font-weight: 700;
}

.ge-help summary {
  list-style: none;
  cursor: pointer;
  width: 1.6rem;
  height: 1.6rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--border);
  color: var(--fg-muted);
  font-size: 0.8rem;
  font-weight: 700;
  background: var(--surface);
}

.ge-help summary::-webkit-details-marker { display: none; }

.ge-help { position: relative; }

.ge-help[open] .ge-help__panel {
  position: absolute;
  right: 0;
  top: calc(100% + 0.4rem);
  z-index: 30;
  width: 16rem;
  padding: 0.75rem 0.9rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  font-size: 0.82rem;
  color: var(--fg-muted);
}

.ge-help__panel p { margin: 0 0 0.5rem; }
.ge-help__panel p:last-child { margin-bottom: 0; }

/* The page. Write and Read are the SAME rectangle rendered two ways: equal
   measure, font-size, line-height and padding, so the flip reads as the marks
   dissolving in place. line-height 1.65 matches prose.css exactly. */
.ge-page {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 0 0 var(--radius) var(--radius);
  padding: 1.75rem 2rem;
}

.ge-page:focus-within { border-color: var(--focus); }

.ge-write,
.ge-read {
  font-size: 1rem;
  line-height: 1.65;
  min-height: 24rem;
}

/* Proportional, never monospace: monospace tells a therapist she is editing
   code. min-height owns the box (no rows attr); resize stays available. */
textarea.ge-write {
  width: 100%;
  /* Same 28rem global cap as the title, same reason it has to go: the author
     was writing in a 448px column inside a 678px page while the Read pane
     rendered the full width, so the flip changed the measure — the one thing
     the comment above says it must not do. */
  max-width: none;
  border: 0;
  background: transparent;
  resize: vertical;
  font-family: inherit;
  color: var(--fg);
  padding: 0;
  box-shadow: none;
  display: block;
}

textarea.ge-write:focus-visible { outline: none; }

/* The Write/Read flip drives visibility with the hidden attribute; author
   display rules would otherwise beat the UA's [hidden] style. */
textarea.ge-write[hidden],
.ge-read[hidden] { display: none !important; }

.ge-read { cursor: text; }

.ge-read__pending {
  color: var(--fg-muted);
  font-size: 0.9rem;
}

.ge-hint {
  font-size: 0.82rem;
  color: var(--fg-muted);
  margin: 0.5rem 0 0;
}

/* The rail. */
.ge-rail { min-width: 0; }

.ge-rail__section { margin-bottom: 1.5rem; }
.ge-rail__section:last-child { margin-bottom: 0; }

.ge-rail__section .form-field { margin-bottom: 0.9rem; }
.ge-rail__section .form-field:last-child { margin-bottom: 0; }

.ge-status {
  font-size: 0.85rem;
  color: var(--fg-muted);
  margin: 0;
}

/* .ge-templates (the rail's "Start from a template" list) is gone with the
   markup it styled — see templates/knowledge/guide_form.html. Left as a note
   rather than dead selectors, so re-adding the list is a decision rather than
   something that quietly picks up styling that is still lying around. */

.ge-rail__help { font-size: 0.85rem; margin: 0; }

/* The rail's tap-to-expand head exists only when the layout is one column. */
.ge-rail__toggle {
  display: none;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel-header);
  padding: 0.55rem 0.8rem;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--fg);
  cursor: pointer;
  margin-bottom: 0.75rem;
}

.ge-rail__toggle[aria-expanded="true"] .ge-rail__chev { transform: rotate(180deg); }

/* The empty-state gallery: the bare new-guide GET lands on template cards. */
.ge-gallery__lead {
  color: var(--fg-muted);
  font-size: 0.92rem;
  margin: 0 0 1rem;
}

.ge-gallery__cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 0.9rem;
}

.ge-gallery__card {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.12s ease-in-out, background 0.12s ease-in-out;
}

/* text-decoration pinned HERE at (0,2,0), the .app-sidebar__menu-item:hover
   trick: the card is an <a>, so the resting text-decoration:none on
   .ge-gallery__card (0,1,0) loses to the global a:hover (0,1,1) and every line
   in the card underlines at once. The border and the background shift ARE the
   hover feedback. Colour is pinned for the same reason: without it the card's
   name inherits the link tint on hover while the hint and excerpt, which set
   their own colour, do not — so the card changes colour in halves. */
.ge-gallery__card:hover,
.ge-gallery__card:focus-visible {
  border-color: var(--coral);
  background: var(--surface-sunken);
  color: inherit;
  text-decoration: none;
}

.ge-gallery__name { font-weight: 600; font-size: 0.95rem; }

.ge-gallery__hint {
  color: var(--fg-muted);
  font-size: 0.82rem;
  line-height: 1.4;
}

.ge-gallery__excerpt {
  white-space: pre-line;
  color: var(--fg-muted);
  font-size: 0.8rem;
  line-height: 1.5;
  border-top: 1px dashed var(--border);
  padding-top: 0.5rem;
  margin-top: 0.25rem;
}

.ge-gallery__blank {
  margin: 1rem 0 0;
  font-size: 0.9rem;
}

/* One column when the container is narrow; the rail moves ABOVE the page as
   a collapsed strip so it is not a wall between the author and the textarea. */
@container (max-width: 780px) {
  .guide-editor__grid { grid-template-columns: 1fr; }
  .ge-rail { order: -1; }
  .ge-rail__toggle { display: flex; }
}

@container (max-width: 600px) {
  .ge-page { padding: 1.25rem 1rem; }
  input.ge-title { font-size: 1.3rem; }
  .ge-btn { min-height: 40px; }
}

/* ── IEP module — instrument band + register ────────────────────────────────
   The licensed IEP worklist. One band (a data-generated verdict, the practice's
   alert window, three count tiles) over a fixed-column register.

   The page stays monochrome until something is actually late: only --crit and
   --warn ever appear, and there is deliberately no "all clear" green — this app
   does not congratulate a register for being fine (see WatchdogSummary in
   apps/core/attention.py for the same posture on the dashboard).

   Every colour is an existing token, so dark mode needs no block of its own:
   theming is driven by [data-theme] and each token already flips there. */

.iep-band { margin-bottom: 1.5rem; }

/* --panel-header, NOT --surface-sunken: in dark the former is a real step UP
   the elevation ladder over --panel, where the latter sits flat on the card. */
.iep-band__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  flex-wrap: wrap;
  padding: 1.05rem 1.25rem 0.9rem;
  border-bottom: 1px solid var(--border);
  background: var(--panel-header);
}

.iep-band__verdict {
  flex: 1 1 22rem;
  min-width: 0;
  margin: 0;
  font-size: 1rem;
  line-height: 1.45;
  color: var(--fg);
}

/* Tone the NUMBER, never the sentence and never the panel — the house rule set
   by .seq-metric-tile--danger .seq-metric-tile__value. */
/* 1.25rem, not the sentence's 1rem: at 16px/700 the dark-theme --crit on
   --panel-header measures 4.01:1, under the 4.5:1 AA bar for normal text.
   >=18.66px bold is "large text" (3:1), which 4.01:1 clears — and the headline
   figure reading larger than its sentence is the better design anyway. */
.iep-band__count {
  font-weight: 700;
  font-size: 1.25rem;
  font-variant-numeric: tabular-nums;
}
.iep-band__verdict--crit .iep-band__count { color: var(--crit); }
.iep-band__verdict--warn .iep-band__count { color: var(--warn); }

/* The tile row is the Sequences primitive verbatim — same hover, same active
   ring, same tone-on-value, same phone recompaction. Only the trailing margin
   goes, because .section-card__body already pads below. */
.iep-band .seq-metrics { margin-bottom: 0; }

/* ── Register ───────────────────────────────────────────────────────────────
   Fixed layout + declared widths + tabular figures, so dates line up
   character-for-character down the page.

   Two independent defences, because fixed layout does not grow for overflowing
   content and these cells are nowrap dates:

   1. THE GATE. The desktop table shows from 601px up, but a percentage-width
      Issued column is narrower than the date inside it until the table is wide
      enough — the date then paints over the Status column beside it. Below the
      gate the table falls back to auto layout, which sizes to content and lets
      .table-card's own overflow-x scroll it: the app's normal wide-table
      behaviour, and never an overlap.

      1200px, not 1100px: the sidebar is 240px expanded but only 64px as a rail
      (:root[data-sidebar="rail"]), and a media query cannot see which. 1100px
      was derived against the rail and left a ~10px collision for anyone running
      the sidebar expanded. This clears the EXPANDED case, the narrower of the
      two, so both are safe.

   2. THE NET. The date cells clip like the name/plan cells already do, so no
      combination of sidebar state, zoom, font fallback or locale date format
      can produce an overlap even if the arithmetic above is ever wrong again.
      Clipping is the graceful failure; painting over the next column is not. */
@media (min-width: 1200px) {
  .iep-register table { table-layout: fixed; }

  /* Six columns since TT-160. Phase and next-action joined; the issue date and
     the plan TITLE left for the plan's own page and the phone card — every IEP
     is titled "IEP — <school year>", so at this width the column rendered as
     "IEP — 202…" on every row and identified nothing. The patient, the school
     and the expiry date already tell the rows apart. These MUST re-sum to 100
     whenever a column is added or dropped — see the note above on why the
     arithmetic is load-bearing here. */
  .iep-col--who     { width: 21%; }
  .iep-col--phase   { width: 14%; }
  .iep-col--term    { width: 17%; }
  .iep-col--next    { width: 28%; }
  .iep-col--expires { width: 12%; }
  .iep-col--act     { width:  8%; }
}

.iep-date,
.iep-status__date,
.iep-status__word,
.iep-phase,
.iep-termtype,
.iep-next__label,
.iep-next__detail {
  overflow: hidden;
  text-overflow: ellipsis;
}

.iep-register th,
.iep-register td { padding: 0.8rem 0.9rem; vertical-align: top; }
.iep-register thead th { padding-top: 0.6rem; padding-bottom: 0.6rem; white-space: nowrap; }
.iep-register tbody tr:last-child td { border-bottom: 0; }
.iep-register .table-action { text-align: right; }

/* Status rail as an INSET shadow, never border-left: a border occupies layout
   width, so a flagged row's first cell would shift 3px right and break the
   alignment the register is built on. */
.iep-row--crit > td:first-child { box-shadow: inset 3px 0 0 var(--crit); }
.iep-row--warn > td:first-child { box-shadow: inset 3px 0 0 var(--warn); }

/* Fixed layout will not grow for a long value, so these truncate rather than
   spill; title= carries the full string. */
.iep-name {
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

/* .dash-table__sub carries no truncation of its own; scope it here so no other
   page changes. */
.iep-register .dash-table__sub {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.iep-date {
  font-size: 0.88rem;
  color: var(--fg-muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* Status: a word over a date. The word is the signal — no dot, no badge, no
   green. "Current" and "No expiry" stay muted. */
.iep-status { display: block; white-space: nowrap; }
.iep-status__word { display: block; font-size: 0.88rem; font-weight: 600; color: var(--fg-muted); }
.iep-status__date {
  display: block;
  margin-top: 0.1rem;
  font-size: 0.88rem;
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}
.iep-status--crit .iep-status__word { color: var(--crit); }
.iep-status--warn .iep-status__word { color: var(--warn); }

/* Connected-dot tracker — a SHARED component (templates/components/_dot_track.html),
   not an IEP one. The IEP register draws a plan's term with it: one dot per
   annual re-test for a 3-year plan, or one per quarter of the term for a 1-year
   plan, derived from issued_on/expires_on. TT-306 adopts it for sequence runs,
   so nothing in this block may name IEPs or terms.

   Built from spans rather than a list so the global `main li` margin never
   spaces the marks apart. The dots are aria-hidden decoration; .dot-track__note
   carries the same state as text, so nothing here is meaning-by-colour — and
   the two-tone rule holds: only --warn and --crit ever appear, never a green
   "all clear" competing with a real deadline. */
.dot-track { display: block; }
.dot-track__dots {
  display: flex;
  align-items: center;
  margin-bottom: 0.25rem;
}
.dot-track__dot { display: flex; align-items: center; }
.dot-track__dot + .dot-track__dot::before {
  content: "";
  flex: none;
  width: 0.9rem;
  height: 1px;
  background: var(--border);
}
.dot-track__mark {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--border);          /* future */
}
.dot-track__dot--past .dot-track__mark { background: var(--fg-muted); box-shadow: inset 0 0 0 1px var(--fg-muted); }
.dot-track__dot--now  .dot-track__mark { box-shadow: inset 0 0 0 2px var(--fg-muted); }
.dot-track__dot--due  .dot-track__mark { box-shadow: inset 0 0 0 2px var(--warn); }
.dot-track__dot--over .dot-track__mark { background: var(--crit); box-shadow: inset 0 0 0 1px var(--crit); }
.dot-track__note {
  display: block;
  font-size: 0.82rem;
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}

/* ---- Arrived-from highlight (TT-306 item 3) -----------------------------
   Applied by static/js/feedback.js to whatever element a deep link's #hash
   names, and only inside a container that opted in with
   `data-arrival-highlight`. Generic on purpose: a run's steps are the first
   caller, not the only possible one.

   The tint fades itself out. The highlight has done its work the moment the
   reader's eye lands, and one that stayed would become a second, permanent
   status colour on a row that already carries real ones (Done / N/A / Overdue).

   Under prefers-reduced-motion the animation is REPLACED, not removed. Deleting
   it would leave the reduced-motion reader with no answer at all to the
   question they arrived with — which row? — so they get a static edge marker
   instead. That is information, not movement, which is what the media query is
   actually about. */
.is-arrival {
  border-radius: var(--radius-sm);
  animation: tt-arrival 2.4s ease-out 1;
}

@keyframes tt-arrival {
  from { background: color-mix(in srgb, var(--primary) 14%, transparent); }
  to   { background: transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .is-arrival {
    animation: none;
    box-shadow: inset 3px 0 0 0 var(--primary);
  }
}

/* --- Payer paneling: the hero surface (TT-298 PR-1) -------------------------
   The coverage matrix, its banner, and the list tab's next-deadline column.

   Every colour here is a semantic custom property (--good / --warn / --crit /
   --fg-muted / --panel / --border), all of which flip in the dark block near the
   top of this file — so there is no theme-specific rule below and none can drift.

   The matrix's tone modifiers are shared by BOTH renderings (the desktop table
   cell and the phone stack) rather than duplicated per layout, which is what
   keeps the small screen from quietly disagreeing with the wide one.  */

/* The banner above the matrix: calm, urgent, or unarmed. One block, three
   left-edge tones — a state change moves a 3px rule and a heading colour, not a
   whole component, so no state can end up looking more designed than another. */
.paneling-banner {
  border: 1px solid var(--border);
  border-left: 3px solid var(--fg-muted);
  border-radius: var(--radius);
  background: var(--panel);
  padding: 0.85rem 1rem;
  margin: 0 0 1rem;
}
.paneling-banner__lead {
  margin: 0;
  font-size: 0.95rem;
}
.paneling-banner__detail {
  margin: 0.3rem 0 0;
  font-size: 0.86rem;
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}
.paneling-banner--calm { border-left-color: var(--good); }
.paneling-banner--calm .paneling-banner__lead strong { color: var(--good); }
.paneling-banner--urgent { border-left-color: var(--crit); }
.paneling-banner--urgent .paneling-banner__lead strong { color: var(--crit); }
/* Unarmed is deliberately NOT green and NOT red: nothing is wrong, and nothing
   is being watched either. Muted is the honest third colour. */
.paneling-banner--unwatched { border-left-color: var(--fg-muted); }

/* The count-only receipt (TT-298 PR-3), tucked directly under the banner.

   Deliberately the QUIETEST thing on the surface: muted, small, no rule, no
   tone. It is a record of work already done, and the banner above it is work
   still to do — a receipt that competed with a red deadline for attention would
   be the surface congratulating itself over an overdue date.

   The negative top margin collapses against .paneling-banner's 1rem bottom
   (max of the positives plus the min of the negatives = 0.4rem), so the line
   reads as attached to the banner rather than floating between it and the tabs.
   Tabular numerals for the same reason every other count on this page has them:
   the number is the point. */
.paneling-receipt {
  margin: -0.6rem 0 1rem;
  font-size: 0.82rem;
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}

/* The tab strip and the add action share one row. .seg is the app's existing
   segmented control (see sequences/run_list.html); the tabs are <a> elements
   carrying ?view=, so this needs no JS. */
.paneling-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 0 0 1rem;
}
/* Two owner/admin actions now share the right-hand slot (TT-298: "Deadline
   cycles" beside "Add paneling"), so they wrap together rather than the settings
   link drifting under the tab strip on a narrow window. */
.paneling-toolbar__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* --- The matrix ---------------------------------------------------------- */
/* .matrix-card sits on .table-card, which already scrolls its own overflow-x —
   so a practice with more payers than fit scrolls the GRID, and the page body
   never scrolls sideways at any width. */
.matrix {
  font-variant-numeric: tabular-nums;
}
.matrix th,
.matrix td {
  vertical-align: top;
}
.matrix__corner,
.matrix__who {
  text-align: left;
  white-space: nowrap;
  position: sticky;
  left: 0;
  /* The clinician column stays put while the payers scroll under it: the row
     label is the only thing that makes a scrolled-away cell readable. */
  background: var(--panel);
  z-index: 1;
}
.matrix thead .matrix__corner { background: var(--panel-header); }
/* TT-298 decision 5, the half that is NOT the data. The payer headers rendered
   as "SOONERCARE (OKLAHOMA HEALTH CARE AUTHORITY)" over three lines, and the
   demo tenant stores that payer as "SoonerCare (Oklahoma Health Care Authority)"
   — properly cased. The shouting came from the global `thead th`
   text-transform (line ~708), which is right for a column LABEL ("CLINICIAN",
   "STATUS") and wrong for a proper noun: a payer's name is data that happens to
   sit in a header, and uppercasing data both costs width and misspells the
   brand. The corner cell keeps the house treatment because it IS a label.
   The width half is Partner.display_short_name, which drops the parenthetical. */
.matrix thead th:not(.matrix__corner) {
  text-transform: none;
  letter-spacing: normal;
}
.matrix__cell { min-width: 9.5rem; }
.matrix__empty { color: var(--fg-muted); }

.matrix__link {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  text-decoration: none;
  color: inherit;
}
/* The global `a:hover` (0,1,1) outranks a bare `.matrix__link` (0,1,0) and sets
   BOTH color and text-decoration, so without this the whole cell turned coral and
   underlined on hover and the rule below was dead. Pinned at (0,2,0). This is the
   specificity trap this stylesheet has been bitten by before; the same applies to
   `.seg__btn` used as an <a>, which .seg__btn--active already pins. */
.matrix__link:hover,
.matrix-cards__link:hover {
  color: inherit;
  text-decoration: none;
}
.matrix__link:hover .matrix__status,
.matrix-cards__link:hover .matrix-cards__payer { text-decoration: underline; }
.matrix__status {
  font-weight: 600;
  font-size: 0.86rem;
}
.matrix__due,
.matrix-cards__due {
  font-size: 0.8rem;
  color: var(--fg-muted);
}
/* The relative chip ("16d over", "today", "12d") — only rendered for a deadline
   inside its window, which is the only place "how long have I got" is the
   question being asked. */
.matrix__tone {
  /* inline-block, not a flex child: since TT-298 the chip is a SIBLING of
     .matrix__link rather than nested inside it (no anchors inside anchors), and
     the link's `display: flex` makes it a block-level box, so the chip starts on
     its own line without the cell having to become a flex container. */
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 600;
  border-radius: 99px;
  border: 1px solid transparent;
  padding: 0.05rem 0.45rem;
  margin-top: 0.1rem;
}
.matrix__more,
.matrix-cards__more {
  font-size: 0.72rem;
  color: var(--fg-muted);
}

/* Tone, shared by the table cell and the phone card. Colour is never the only
   signal: the date and its relative chip carry the same fact as text.

   TT-298 DECISION 1 — WHERE THE TONE GOES. Until PR-2 the hot tone landed on the
   STATUS WORD: `.matrix__cell--overdue .matrix__status { color: var(--crit) }`
   painted "In network" red. Caleb's ruling, and the reason: a red status word
   reads as "out of network", which is a false alarm on the one fact that has not
   changed. The urgency belongs to the DEADLINE.

   So a hot cell now tints its own SURFACE and carries a left edge in the tier
   colour — the same three-pixel rule .paneling-banner uses one component up, so
   the two states of this page are spoken in one visual language — and the
   deadline line and its chip carry the colour. The status word inherits plain
   ink from .matrix__link's `color: inherit`, which is why there is no rule for
   it here: the fix is the ABSENCE of the two selectors this block used to
   carry. */
.matrix__cell--overdue,
.matrix-cards__item.matrix__cell--overdue {
  background: var(--crit-wash);
  box-shadow: inset 3px 0 0 var(--crit);
}
.matrix__cell--soon,
.matrix-cards__item.matrix__cell--soon {
  background: var(--warn-wash);
  box-shadow: inset 3px 0 0 var(--warn);
}
.matrix__cell--overdue .matrix__due,
.matrix__cell--overdue .matrix-cards__due { color: var(--crit); }
.matrix__cell--soon .matrix__due,
.matrix__cell--soon .matrix-cards__due { color: var(--warn); }
/* The relative chip. On a tinted surface a wash-on-wash background would vanish,
   so the chip carries a 1px outline in the tier colour instead of a fill. */
.matrix__cell--overdue .matrix__tone { border-color: var(--crit); color: var(--crit); }
.matrix__cell--soon .matrix__tone { border-color: var(--warn); color: var(--warn); }
/* The chip is an <a> when the viewer may act on it (TT-298: the roll-forward
   verb is reachable from a hot cell's deadline chip). The global `a:hover`
   (0,1,1) outranks a bare class, so BOTH its colour and its underline are pinned
   here at (0,2,0) — the same specificity trap .matrix__link records above. */
a.matrix__tone { text-decoration: none; }
a.matrix__tone:hover,
a.matrix__tone:focus-visible {
  text-decoration: underline;
  color: inherit;
}
.matrix__cell--overdue a.matrix__tone:hover { color: var(--crit); }
.matrix__cell--soon a.matrix__tone:hover { color: var(--warn); }
/* A dead relationship (terminated) is dim by design: the attention engine is not
   chasing it, so the coverage map must not shout about it. A DENIED cell is no
   longer in this class as of TT-298 decision 3 — it carries a re-apply date and
   tones like any other. */
.matrix__cell--dim .matrix__status,
.matrix__cell--dim .matrix-cards__status,
.matrix__cell--dim .matrix__due { color: var(--fg-muted); font-weight: 500; }

/* One clinician is still the matrix, not a lonely grid: the single row reads as
   a payer strip rather than a table that lost its data. */
.matrix-card--solo .matrix__cell { min-width: 8rem; }

/* Phones: the stacked reading of the same cells. Hidden above the phone
   breakpoint, exactly like .collapse-cards. */
/* `padding-left` is deliberately NOT declared on either list below. The global
   `main ul:not(.messages):not(.providers)` rule is (0,2,2) and outranks any plain
   class selector, so a `padding: 0` here would be dead CSS masquerading as intent
   — the same silent loss `.collapse-cards` already takes on every phone list in
   the app (measured: both compute to 19.2px). The 1.2rem it supplies indents each
   clinician's cards beneath their name, which is the hierarchy this layout wants;
   overriding it would need a selector shaped like the global one, and making this
   one component diverge from `.collapse-cards` is not this PR's call. */
.matrix-cards {
  list-style: none;
  margin: 0;
  display: none;
}
.matrix-cards__group + .matrix-cards__group { margin-top: 1rem; }
.matrix-cards__who {
  font-size: 0.95rem;
  margin: 0 0 0.4rem;
}
.matrix-cards__list {
  list-style: none;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.matrix-cards__item {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel);
  padding: 0.55rem 0.7rem;
}
.matrix-cards__link {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  text-decoration: none;
  color: inherit;
}
.matrix-cards__payer { font-weight: 600; font-size: 0.9rem; }
/* The phone card's roll-forward verb, a sibling of the card link for the same
   no-nested-anchors reason as the grid chip. */
.matrix-cards__verb { margin-top: 0.4rem; }
.matrix-cards__status { font-size: 0.82rem; }
.matrix-cards__due { font-variant-numeric: tabular-nums; }

/* --- The list tab's single next-deadline column --------------------------- */
.paneling-next { white-space: nowrap; }
.paneling-next__kind { font-size: 0.82rem; color: var(--fg-muted); }
.paneling-next__date { font-variant-numeric: tabular-nums; }
.paneling-next__none { color: var(--fg-muted); }

@media (max-width: 600px) {
  .matrix-cards { display: block; }
}

/* --- The status-shaped detail page (TT-298 Move 5) ------------------------ */
/* The lede: who, which payer, the status word, and the record's history spine.
   Deliberately NOT a .section-card. The page below it is a stack of cards, and
   giving the lede a card of its own put a bordered box inside a bordered box's
   worth of rules at the top of the page — the doubled-rule reading the polish
   pass called out. One bottom rule separates the lede from the story. */
.paneling-lede {
  padding: 0 0 1rem;
  margin: 0 0 1.25rem;
  border-bottom: 1px solid var(--border);
}
.paneling-lede__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.6rem;
}
.paneling-lede__name { margin: 0; font-size: 1.15rem; }
.paneling-lede__payer { margin: 0.15rem 0 0; color: var(--fg-muted); }
.paneling-lede__track { margin-top: 0.75rem; }
/* The spine wants the page-sized tracker, exactly like the IEP detail page —
   scoped from the outside so the shared partial stays parameter-free. */
.paneling-lede__track .dot-track__dot + .dot-track__dot::before { width: 2.4rem; }
.paneling-lede__track .dot-track__mark { width: 12px; height: 12px; border-radius: 3px; }
.paneling-lede__track .dot-track__dots { margin-bottom: 0.4rem; }
.paneling-lede__track .dot-track__note { font-size: 0.9rem; }

/* The story each shape leads with. Plain paragraphs, no ornament: the page's
   job here is to be read, and a callout box around a sentence is decoration
   asking to be mistaken for a state. */
.paneling-story { margin: 0; }
.paneling-story__notes {
  margin: 0.75rem 0 0;
  white-space: pre-wrap;
  color: var(--fg-muted);
}
.paneling-story__gap { margin: 0.75rem 0 0; font-size: 0.9rem; color: var(--fg-muted); }

/* One deadline: what it is, when it is due, how long that leaves, and the verb
   that completes it. Tone placement is decision 1's rule, the same one the
   matrix cell follows — the DATE and the relative phrase carry the tier, and
   nothing here paints a status word. */
.paneling-deadline {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.4rem 0.9rem;
  padding: 0.55rem 0;
  border-top: 1px solid var(--border);
}
/* A row's top border is its separator from the row above; the first one sits
   directly under .section-card__head, which already draws a bottom rule. Same
   doubled-rule fix .section-card__body > .collapse-card__row:first-child makes,
   and for the same reason. */
.section-card__body > .paneling-deadline:first-child {
  border-top: 0;
  padding-top: 0;
}
.paneling-deadline__fact {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.15rem 0.6rem;
}
.paneling-deadline__label { font-weight: 600; }
.paneling-deadline__date { font-variant-numeric: tabular-nums; }
.paneling-deadline__rel { font-size: 0.85rem; color: var(--fg-muted); }
.paneling-deadline__none { font-size: 0.85rem; color: var(--fg-muted); }
.paneling-deadline--overdue .paneling-deadline__date,
.paneling-deadline--overdue .paneling-deadline__rel { color: var(--crit); }
.paneling-deadline--soon .paneling-deadline__date,
.paneling-deadline--soon .paneling-deadline__rel { color: var(--warn); }
.paneling-deadline__aside {
  margin: 0.9rem 0 0;
  padding-top: 0.9rem;
  border-top: 1px solid var(--border);
  font-size: 0.86rem;
  color: var(--fg-muted);
}

/* The completions log — the same facts the tracker draws as dots, in words. */
.paneling-events { list-style: none; margin: 0; }
.paneling-event {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.7rem;
  padding: 0.45rem 0;
  border-top: 1px solid var(--border);
  font-size: 0.9rem;
}
.section-card__body > .paneling-events > .paneling-event:first-child {
  border-top: 0;
  padding-top: 0;
}
.paneling-event__what { font-weight: 600; }
.paneling-event__when { font-variant-numeric: tabular-nums; }
.paneling-event__timing,
.paneling-event__who { color: var(--fg-muted); }

/* The verb's confirm step. */
.paneling-confirm__gloss { margin: 0.75rem 0 0; font-size: 0.9rem; color: var(--fg-muted); }
.paneling-confirm__from {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin: 1rem 0 0;
  padding: 0.6rem 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.paneling-confirm__label { font-size: 0.85rem; color: var(--fg-muted); }
.paneling-confirm__value { font-weight: 600; font-variant-numeric: tabular-nums; }
.paneling-confirm__value--none { font-weight: 400; color: var(--fg-muted); }
.paneling-confirm__discharge { margin: 1rem 0 0; }
.paneling-confirm__records { margin: 1rem 0 0; font-size: 0.86rem; color: var(--fg-muted); }
/* Where the tracker is a PAGE's spine rather than a table cell it wants more
   room. Scoped from the outside (a wrapper class the page owns) rather than a
   modifier on the component, so the partial stays parameter-free and TT-306 can
   size it its own way without touching this. */
.iep-spine__meter .dot-track__dot + .dot-track__dot::before { width: 2.4rem; }
.iep-spine__meter .dot-track__mark { width: 12px; height: 12px; border-radius: 3px; }
.iep-spine__meter .dot-track__dots { margin-bottom: 0.4rem; }
.iep-spine__meter .dot-track__note { font-size: 0.9rem; }

/* Phase — the word that says where a plan stands. Same two-tone vocabulary as
   .iep-status: untoned is the calm case, and there is no green. */
.iep-phase {
  display: inline-block;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--fg-muted);
  white-space: nowrap;
}
.iep-phase--warn { color: var(--warn); }
.iep-phase--crit { color: var(--crit); }

/* Term type — 1-year vs 3-year, and whether we were told or worked it out. */
.iep-termtype { display: block; font-size: 0.82rem; color: var(--fg-muted); white-space: nowrap; }
/* Inside a phone collapse-card the tracker sits under its term label, both
   right-aligned with the rest of the card's values. */
.collapse-card__row .dot-track__dots { justify-content: flex-end; }
.collapse-card__row .dot-track__note { text-align: right; }
.iep-termtype__inferred { opacity: 0.75; font-style: italic; }

/* Next action — one per plan, on the row and again as the detail page's lead. */
.iep-next { display: block; }
.iep-next__label { display: block; font-size: 0.88rem; font-weight: 600; color: var(--fg); }
.iep-next__detail { display: block; font-size: 0.82rem; color: var(--fg-muted); }
.iep-next--warn .iep-next__label { color: var(--warn); }
.iep-next--crit .iep-next__label { color: var(--crit); }

/* The detail page's lead panel: phase + term + tracker + the next action. */
.iep-spine__phase-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 0.9rem;
  margin-bottom: 0.75rem;
}
.iep-spine__phase { font-size: 1.05rem; font-weight: 700; }
.iep-spine__next {
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--border);
}
.iep-milestones { margin: 0.75rem 0 0; padding: 0; list-style: none; }
.iep-milestones li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin: 0;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.88rem;
}
.iep-milestones li:last-child { border-bottom: 0; }
.iep-milestones__when { color: var(--fg-muted); font-variant-numeric: tabular-nums; }
.iep-milestones__when--due { color: var(--warn); font-weight: 600; }
.iep-milestones__when--over { color: var(--crit); font-weight: 600; }

/* --- CSV import wizard (TT-305) ---------------------------------------------
   Three surfaces, one theme: say which way the mapping runs, keep a wide
   preview readable while it scrolls, and make one payer decision at a time. */

/* Column mapping: SOURCE -> TARGET, stated. The header row and each row share a
   grid so the arrow column lines up down the page; below 600px the arrow is the
   only thing that survives the stack, so it turns and the columns become rows. */
.map-direction,
.map-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 1.5rem minmax(0, 1fr);
  align-items: center;
  gap: 0.75rem;
}

.map-direction {
  margin: 0 0 0.35rem;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.map-direction__arrow { text-align: center; }

.map-rows {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.map-row {
  padding: 0.6rem 0;
  border-top: 1px solid var(--border);
}

.map-row__source label {
  display: block;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.map-row__arrow {
  text-align: center;
  color: var(--fg-muted);
}

/* The select carries the app-wide 28rem control cap; the cell must not force it
   wider or narrower, only give it the room. */
.map-row__target select { width: 100%; }

@media (max-width: 600px) {
  .map-direction { display: none; }

  .map-row {
    grid-template-columns: 1fr;
    gap: 0.35rem;
  }

  .map-row__arrow { text-align: left; }
}

/* Every column matched: the whole table folds to one confirming line. A native
   <details>, so the selects stay in the DOM and still POST while it is closed,
   and no JS (and so no CSP surface) is involved — the same reasoning as
   .page-help. */
.map-confirm > summary {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.75rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-sunken);
  cursor: pointer;
  list-style: none;
}

.map-confirm > summary::-webkit-details-marker { display: none; }
.map-confirm > summary:hover { border-color: var(--primary); }

.map-confirm > summary:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.map-confirm__say {
  flex: 1 1 14rem;
  font-size: 0.88rem;
}

.map-confirm__more {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--primary);
}

.map-confirm[open] > summary { margin-bottom: 0.9rem; }

/* Preview table: pin the identifying columns, scroll the rest.
   .table-card is already the horizontal scroll container, exactly as the
   paneling matrix arranges it — .preview-table is inside one at PAGE level, not
   inside a .section-card, so nothing clips the sticky cells. The pinned cells
   paint their own opaque background because `tbody tr:hover` fills the row
   behind them, and `border-collapse: collapse` does not carry borders onto a
   sticky cell. */
.preview-table {
  /* The pinned columns' widths, declared ONCE: the left offsets below are
     arithmetic on these, and a literal in both places is a misalignment waiting
     for someone to tune one of them. */
  --pin-row: 4rem;
  --pin-status: 10.5rem;
  --pin-name: 11rem;
}

.preview-table th,
.preview-table td { vertical-align: top; }

.preview-table__pin { background: var(--panel); }
.preview-table thead .preview-table__pin { background: var(--panel-header); }

/* Each pinned cell needs its width from BOTH ends, and neither alone is enough
   under `table-layout: auto`:
     * `min-width` stops the column shrinking below the offset the next one is
       pinned at — `width` alone is only a suggestion the layout may ignore, and
       a short column silently detaches the pin to its right;
     * `overflow-wrap: anywhere` stops it GROWING, by letting any content wrap to
       one character so the column's min-content width can never exceed the
       declared size. On its own it is what let the name column collapse.
   Together the column is exactly its declared width, whatever is in it. */
.preview-table__pin--row {
  width: var(--pin-row);
  min-width: var(--pin-row);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.preview-table__pin--status {
  width: var(--pin-status);
  min-width: var(--pin-status);
  overflow-wrap: anywhere;
}

.preview-table__pin--name {
  width: var(--pin-name);
  min-width: var(--pin-name);
  overflow-wrap: anywhere;
}

@media (min-width: 601px) {
  /* Three frozen columns is 25.5rem — more than a phone has to spare, so the
     pinning only starts where there is something left to scroll. */
  .preview-table__pin {
    position: sticky;
    z-index: 1;
  }

  .preview-table__pin--row { left: 0; }
  .preview-table__pin--status { left: var(--pin-row); }
  .preview-table__pin--name { left: calc(var(--pin-row) + var(--pin-status)); }
}

/* The row hover fill is painted on the <tr>, which the pinned cells' own opaque
   background then covers — so a hovered row would light up only in its scrolling
   half. Repainted here as the same colour composited over --panel rather than
   over transparent, because a sticky cell cannot be see-through. */
.preview-table tbody tr:hover .preview-table__pin {
  background: color-mix(in srgb, var(--indigo-wash) 40%, var(--panel));
}

/* Per-row issue detail. It used to print under every badge at once, so a
   200-row preview opened as a wall of red prose; behind a disclosure, only the
   row being investigated is tall. In flow rather than an overlay on purpose:
   .table-card scrolls its own overflow, and an absolutely positioned panel
   would be clipped or would push the card into a scrollbar. */
.row-issue {
  margin-top: 0.35rem;
  font-size: 0.78rem;
}

.row-issue > summary {
  color: var(--fg-muted);
  cursor: pointer;
  font-weight: 600;
}

.row-issue > summary:hover { color: var(--primary); }

.row-issue > summary:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.row-issue__list {
  margin: 0.3rem 0 0;
  padding-left: 1rem;
  color: var(--fg-muted);
  line-height: 1.45;
}

.row-issue__list li { margin-bottom: 0.2rem; }

/* A warning is not an error. Both ride the same disclosure, so without this the
   only difference between "1 problem" (the row is dropped) and "1 warning" (the
   row imports, with a note) is the word itself — two opposite outcomes in
   identical grey. The warn tone is the badge vocabulary's, not a new colour. */
.row-issue--warning > summary { color: var(--warn); }

.row-issue--warning > summary:hover {
  color: var(--warn);
  text-decoration: underline;
}

/* One unmatched payer name, three answers. The picker and its Link button sit
   on one line; the alternatives fall below so the destructive-sounding one is
   never the first thing under the cursor. */
.payer-choice__link {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.payer-choice__alts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.9rem;
}

/* ---- Member page (TT-304) ------------------------------------------------ */
/* The Membership card's fact rows: .billing-details__row's dt/dd layout without
   the top border and margin — the card's own head is the separator. */
.member-facts {
  margin: 0;
  display: grid;
  gap: 0.6rem;
}

.member-facts__row {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  font-size: 0.9rem;
}

.member-facts__row dt {
  color: var(--fg-muted);
  min-width: 9rem;
  flex-shrink: 0;
  padding-top: 0.2rem;
}

.member-facts__row dd {
  margin: 0;
  color: var(--fg);
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
  min-width: 0;
}

/* A control's explainer sits under the row's controls, on its own line. */
.member-facts__row dd > .field-help {
  flex-basis: 100%;
  margin: 0;
  max-width: 34rem;
}

.member-facts__note {
  font-size: 0.82rem;
  color: var(--fg-muted);
  max-width: 26rem;
}

/* The role select and its Update button on one line inside the Role row. The
   global control rule (select { width: 100%; max-width: 28rem }) would have
   the select fill the form and push the button underneath it. */
.member-facts__form {
  display: inline-flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
}

.member-facts__form select {
  width: auto;
  min-width: 12rem;
}

/* TT-159 PR 4: the card's "Administer this practice" checkbox, inline beside the
   persona select. */
.member-facts__check {
  display: inline-flex;
  gap: 0.4rem;
  align-items: center;
  cursor: pointer;
}

/* Phones: the label sits above its value instead of beside it — a 9rem label
   column beside a 375px viewport left the value half the screen and wrapped
   every badge and button. Same breakpoint the rest of the stylesheet uses. */
@media (max-width: 600px) {
  .member-facts__row {
    flex-direction: column;
    gap: 0.25rem;
  }

  .member-facts__row dt {
    min-width: 0;
    padding-top: 0;
  }
}

.member-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}

.member-actions__item {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  align-items: flex-start;
}

.member-actions__state {
  margin: 0 0 0.35rem;
  color: var(--fg-muted);
  max-width: 30rem;
}

/* The tab strip row — .paneling-toolbar's layout (tabs left, an action right). */
.detail-tabs {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 0 0 1rem;
}

/* Phones: FOUR tabs do not fit one row. `.seg` is an inline-flex pill sized by
   its content, so at 375px the strip measured ~531px against a ~335px column and
   the PAGE BODY panned sideways — the one thing no layout here may do. The strip
   wraps within itself instead: every tab stays visible and reachable (a
   scroll-inside-the-pill would hide two of them behind a gesture with no
   affordance), and a two-row pill squares off because a 999px radius on a
   two-line block reads as a blob. Rule pinned by
   apps/staff/tests/test_member_detail_layout.py; the fit itself was checked in a
   browser at 375px. */
@media (max-width: 600px) {
  .detail-tabs .seg {
    display: flex;
    flex-wrap: wrap;
    border-radius: var(--radius);
  }

  .detail-tabs .seg__btn {
    flex: 1 1 auto;
    text-align: center;
  }
}

/* The Disable confirm reveal is a review, so it stacks instead of running on. */
.confirm-inline--stack {
  flex-direction: column;
  align-items: flex-start;
}

.disable-review__title {
  font-weight: 600;
  color: var(--fg);
}

/* Indent via padding, not a left margin: the markers then sit INSIDE the box, so
   they cannot be clipped by an ancestor's overflow, and the browser's ~40px
   default padding is replaced rather than fought with a `padding: 0` that read
   as dead beside the margin. */
.disable-review {
  margin: 0.35rem 0 0.75rem;
  padding-left: 1.1rem;
  font-size: 0.85rem;
  color: var(--fg-muted);
  max-width: 34rem;
}

/* `.seg__btn` as an <a> (paneling, the sequences worklist, the member page): the
   global a:hover (0,1,1) outranks BOTH .seg__btn and .seg__btn--active (0,1,0),
   so a tab link went link-coloured and underlined on hover — the note beside
   .matrix__link:hover assumed the active modifier pinned it, but nothing did.
   Pinned at (0,2,1): hover darkens the label and never underlines it; the
   active tab keeps its own colour. */
a.seg__btn:hover {
  color: var(--fg);
  text-decoration: none;
}
