/* ============================================================================
   kiosk-ds.css — Ganger Kiosk Design System (Fable 5, Wave 2)
   ----------------------------------------------------------------------------
   Hand-authored, committed, STATIC CSS. NOT Tailwind. Loaded AFTER
   shared/css/app.css in views/layout.php. Because these classes ship as a
   plain file (no build step), they are immune to the "did this utility
   survive the Tailwind build?" bug class that bit this repo three times (#111).

   Source of truth: design-review/FABLE5-DESIGN-REVIEW.md §2 (token sheet),
   §2.1 color roles, §2.2 type scale, §2.3 space/radius, §2.4 elevation,
   §2.5 the eight core components, §2.6 + §4 motion.

   Conventions:
     - Semantic .ds-* classes. Views ADOPT these onto existing markup.
     - Dark theme ONLY (gray-950 app bg). One radius pair: 16 cards / 12
       buttons+inputs / 999 pills.
     - Elderly-first: nothing below 15px on patient-consumed text; touch
       targets >= 48px.
     - Every animation is disabled under prefers-reduced-motion: reduce.
     - The global :focus-visible ring (violet-400/60) is defined in
       layout.php's inline <style>. It is NOT redefined here — see .ds-focus
       at the bottom for the rare bespoke case only.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   §2.1 / §2.2 / §2.3 — Tokens
   One place for every color role, type step, space unit, and radius.
   ------------------------------------------------------------------------- */
:root {
  /* Color roles — one signal per color (§2.1) */
  --ds-bg:            #030712;              /* gray-950  — body only            */
  --ds-surface:      #111827;              /* gray-900  — all cards            */
  --ds-well:         #1F2937;              /* gray-800  — rows, fills, wells   */
  --ds-hairline:     rgb(255 255 255 / 0.08); /* card + row borders            */
  --ds-text:         #F9FAFB;              /* headings, values                 */
  --ds-text-2:       #9CA3AF;              /* gray-400 — labels/captions FLOOR */

  --ds-go:           #059669;              /* emerald-600 — THE go/confirm      */
  --ds-go-press:     #047857;             /* emerald-700 — primary :active      */
  --ds-brand:        #8B5CF6;             /* violet-500  — brand/assist/select  */
  --ds-brand-strong: #7C3AED;             /* violet-600                         */
  --ds-caution:      #F59E0B;             /* amber-500   — exception paths      */
  --ds-critical:     #F43F5E;             /* rose-500    — safety/destructive   */
  --ds-focus:        rgb(167 139 250 / 0.6); /* violet-400/60 — app-wide ring   */

  /* Type scale (§2.2) — size/line, elderly floor at 15px */
  --ds-fs-display: 34px; --ds-lh-display: 40px;
  --ds-fs-title:   28px; --ds-lh-title:   34px;
  --ds-fs-heading: 22px; --ds-lh-heading: 28px;
  --ds-fs-body-l:  20px; --ds-lh-body-l:  28px;
  --ds-fs-body:    17px; --ds-lh-body:    24px;
  --ds-fs-label:   15px; --ds-lh-label:   20px;

  /* Space (§2.3) — 4pt grid */
  --ds-gutter:   32px;  /* screen gutter        */
  --ds-pad-card: 24px;  /* card padding         */
  --ds-gap-card: 16px;  /* card-to-card         */
  --ds-gap-lf:    8px;  /* label-to-field       */
  --ds-pad-foot: 16px;  /* footer block pad-y   */

  /* Radius (§2.3) — one pair everywhere */
  --ds-r-card: 16px;
  --ds-r-ctl:  12px;   /* buttons + inputs      */
  --ds-r-pill: 999px;
}

/* ---------------------------------------------------------------------------
   LOAD-ORDER SAFETY (structural — makes a whole bug class impossible)
   app.css defines `.hidden { display: none }` with NO !important, and this
   sheet loads AFTER it. Any `.ds-*` class that sets `display` (choice-card,
   masked, icon-tile, stat-tile, show-btn, btn-*) would otherwise WIN the
   cascade over `.hidden` on the same element and render a JS-toggled node
   un-hideable (found during Wave 2 B2 adoption). Kiosk patient views never use
   a responsive `hidden <bp>:block` pattern (verified: 0 matches), so forcing
   `.hidden` to always win is safe here and removes the footgun for every view
   that adopts a display-setting `.ds-*` class on (or near) a toggled element.
   ------------------------------------------------------------------------- */
.hidden { display: none !important; }

/* ---------------------------------------------------------------------------
   §2.5-4 support / §2.4 — Surfaces: card + nested well
   Flat surface + hairline. NO drop shadow (shadows on near-black smudge).
   ------------------------------------------------------------------------- */
.ds-card {
  background: var(--ds-surface);
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-r-card);
  padding: var(--ds-pad-card);
  color: var(--ds-text);
}
.ds-well {                       /* nested interactive fill inside a card */
  background: var(--ds-well);
  border-radius: var(--ds-r-ctl);
  padding: var(--ds-pad-card);
}

/* ---------------------------------------------------------------------------
   §2.5-1/2/3 + §2.4 (F3) — Buttons
   Primary emerald / secondary hairline / tertiary text. The color-agnostic
   raised look (.btn-primary shadow) already lives in layout.php; .ds-btn-primary
   restates a self-contained neutral lift so the class works standalone too.
   The global `button:active { transform: scale(.97) }` in layout.php supplies
   the press-compress; here we only add the fill shift.
   ------------------------------------------------------------------------- */
.ds-btn-primary {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; max-width: 36rem;              /* full within column, centered */
  min-height: 60px; padding: 0 24px;
  font-size: var(--ds-fs-body-l); line-height: 1; font-weight: 700; /* Title-weight 20px */
  color: #fff; background: var(--ds-go);
  border: 1px solid rgb(255 255 255 / 0.10);
  border-radius: var(--ds-r-ctl);
  box-shadow: 0 6px 16px -6px rgb(0 0 0 / 0.5), inset 0 1px 0 rgb(255 255 255 / 0.12);
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  cursor: pointer;
}
.ds-btn-primary:active { background: var(--ds-go-press); } /* emerald-700, no dim */
.ds-btn-primary:disabled,
.ds-btn-primary[aria-disabled="true"] {
  background: var(--ds-well); color: var(--ds-text-2);
  box-shadow: none; cursor: default;          /* neutral "locked", not "broken green" */
}

.ds-btn-secondary {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; max-width: 36rem;
  min-height: 52px; padding: 0 24px;
  font-size: var(--ds-fs-body); font-weight: 600;
  color: var(--ds-text); background: transparent;
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-r-ctl);
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  cursor: pointer;
}

.ds-btn-tertiary {                             /* escape / skip — 48px hit area */
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; padding: 0 16px;
  font-size: var(--ds-fs-body); font-weight: 500;
  color: var(--ds-text-2); background: none; border: 0;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  cursor: pointer;
}

/* ---------------------------------------------------------------------------
   §2.5-4 — Choice card (the confirm pattern)
   "Here's what we have; is it right?" One object: neutral icon tile + value
   stack + the question line as the card's own footer. The icon tile is
   NEUTRAL gray-800 — never an accent color per step.
   ------------------------------------------------------------------------- */
.ds-choice-card {
  background: var(--ds-surface);
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-r-card);
  padding: var(--ds-pad-card);
  display: flex; flex-direction: column; gap: 16px;
}
.ds-icon-tile {                                /* 56px neutral tile for a 28px glyph */
  display: inline-flex; align-items: center; justify-content: center;
  width: 56px; height: 56px; flex: none;
  background: var(--ds-well); border-radius: var(--ds-r-ctl);
  color: var(--ds-text); font-size: 28px; line-height: 1;
}
.ds-choice-value {                             /* value stack (name, address, ...) */
  display: flex; flex-direction: column; gap: 4px;
}
.ds-choice-value .ds-choice-name {
  font-size: var(--ds-fs-heading); line-height: var(--ds-lh-heading); font-weight: 600;
  color: var(--ds-text);
}
.ds-choice-value .ds-choice-meta {
  font-size: var(--ds-fs-label); line-height: var(--ds-lh-label); color: var(--ds-text-2);
}
.ds-choice-question {                          /* footer question, inside the card */
  padding-top: 16px; border-top: 1px solid var(--ds-hairline);
  font-size: var(--ds-fs-body-l); line-height: var(--ds-lh-body-l); color: var(--ds-text);
}

/* ---------------------------------------------------------------------------
   §2.5-8 — List row
   64px min rows, hairline separators BETWEEN rows inside ONE card. This is the
   fix for boxes-in-boxes (no per-item bordered wells). Put .ds-list-row
   siblings directly inside a .ds-card (or a .ds-list wrapper).
   ------------------------------------------------------------------------- */
.ds-list-row {
  display: flex; align-items: center; gap: 16px;
  min-height: 64px; padding: 12px 0;
  border-top: 1px solid var(--ds-hairline);
}
.ds-list-row:first-child { border-top: 0; }    /* separators BETWEEN, not around */
.ds-list-row .ds-row-name {
  font-size: var(--ds-fs-body-l); line-height: var(--ds-lh-body-l); color: var(--ds-text);
}
.ds-list-row .ds-row-meta {
  margin-left: auto;
  font-size: var(--ds-fs-label); line-height: var(--ds-lh-label); color: var(--ds-text-2);
}

/* ---------------------------------------------------------------------------
   §2.5-5 — Masked field (one treatment for ALL masked values)
   Value area + trailing 44px eye/Show ghost button. One copy line per screen:
   "Hidden for privacy — tap Show." lives in the view, not here.
   ------------------------------------------------------------------------- */
.ds-masked {
  display: flex; align-items: center; gap: 12px;
  min-height: 48px;
  background: var(--ds-well); border-radius: var(--ds-r-ctl);
  padding: 8px 8px 8px 16px;
}
.ds-masked .ds-masked-value {
  flex: 1 1 auto; min-width: 0;
  font-size: var(--ds-fs-body); line-height: var(--ds-lh-body); color: var(--ds-text);
  letter-spacing: 0.02em;                      /* dot masks read cleaner */
}
.ds-show-btn {                                 /* trailing ghost eye/Show */
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  width: 44px; height: 44px; flex: none;       /* icon-only default is 44px */
  padding: 0 10px; border: 0; background: transparent;
  color: var(--ds-brand); font-size: var(--ds-fs-label); font-weight: 600;
  border-radius: var(--ds-r-ctl);
  -webkit-tap-highlight-color: transparent; touch-action: manipulation; cursor: pointer;
}
.ds-show-btn.ds-show-btn--labeled { width: auto; min-width: 44px; } /* "Show" text variant */

/* ---------------------------------------------------------------------------
   §2.5 / §3.3 — Stat tile (copay / deductible)
   Label caption + 34px value, NO inner box. Lay two in a row; the
   --separator variant draws a hairline column rule between peers.
   ------------------------------------------------------------------------- */
.ds-stat-tile { display: flex; flex-direction: column; gap: 4px; padding: 4px 0; }
.ds-stat-tile .ds-stat-label {
  font-size: var(--ds-fs-label); line-height: var(--ds-lh-label); font-weight: 500;
  color: var(--ds-text-2); text-transform: uppercase; letter-spacing: 0.04em;
}
.ds-stat-tile .ds-stat-value {
  font-size: var(--ds-fs-display); line-height: var(--ds-lh-display); font-weight: 700;
  color: var(--ds-text);                       /* the fact patients care about, biggest */
}
.ds-stat-tile--separator { padding-left: 24px; border-left: 1px solid var(--ds-hairline); }

/* ---------------------------------------------------------------------------
   §2.5-6 / §3.0 — Progress rail
   "Step N of M · Label" (15px Label) over a 3px full-width rail. The violet
   fill width is driven by --ds-rail-pct (0–100) and animates 300ms ease-out
   on step load (§4-2). Replaces the 8-dot parade.
   ------------------------------------------------------------------------- */
/* Self-contained: layout.php emits a BARE `<div class="ds-progress-rail"
   style="--ds-rail-pct:N%">` (the "Step N of M · Label" caption is its own <p>
   above it in the nav). So the rail element IS the 3px track and its ::after is
   the violet fill — no required child nodes. */
.ds-progress-rail {
  position: relative; height: 3px; width: 100%;
  background: var(--ds-well); border-radius: var(--ds-r-pill); overflow: hidden;
}
.ds-progress-rail::after {
  content: ""; position: absolute; inset: 0 auto 0 0; height: 100%;
  width: var(--ds-rail-pct, 0%);
  background: var(--ds-brand); border-radius: var(--ds-r-pill);
  transition: width 0.3s ease-out;
}
.ds-progress-label {           /* optional caption class, if a consumer wants it inline */
  font-size: var(--ds-fs-label); line-height: var(--ds-lh-label); font-weight: 500;
  color: var(--ds-text-2);
}
@media (prefers-reduced-motion: reduce) {
  .ds-progress-rail::after { transition: none; }
}

/* ---------------------------------------------------------------------------
   §2.5-7 — Footer bar
   Fixed-height 3-zone grid [Back][——][Cancel] + a CTA stack below. The middle
   zone is a reserved cell: toggling a middle element must NOT change footer
   height (the old help-pill reveal shifted Back/Cancel between two layouts).
   ------------------------------------------------------------------------- */
.ds-footer { display: flex; flex-direction: column; gap: 16px; padding: var(--ds-pad-foot) 0; }
.ds-footer-nav {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  min-height: 48px;                            /* fixed — immune to middle toggles */
}
.ds-footer-nav > .ds-footer-back   { justify-self: start; }
.ds-footer-nav > .ds-footer-mid    { justify-self: center; }  /* reserved cell */
.ds-footer-nav > .ds-footer-cancel { justify-self: end; }
.ds-footer-cta { display: flex; flex-direction: column; align-items: center; gap: 12px; }

/* ---------------------------------------------------------------------------
   §2.5-7 / §3.14 — Persistent help corner pill
   Always in the same place; quiet by default (hairline border, violet text),
   filled emerald ONLY in the active "Help is on the way" state. Keeps the
   violet channel discipline and gives the primary CTA its stage back.
   ------------------------------------------------------------------------- */
/* POSITIONING-ONLY wrapper. Deliberately no border/bg/padding of its own:
   layout.php renders `<div class="ds-help-corner"><div id="assist-help-fab"
   class="hidden …"><button class="assist-fab-btn">…</button></div></div>`, and
   js/kiosk.js toggles `.hidden` on the INNER #assist-help-fab. If this wrapper
   drew its own pill, an EMPTY styled frame would float on every screen while the
   fab is hidden. So the wrapper only positions; the visible pill is the inner
   .assist-fab-btn / .assist-fab-ack (styled in layout.php's inline <style>,
   the #111-safe path), which appears only when JS reveals the fab.
   Top-right, tucked below the ~40px rail — NOT bottom-right, where the footer's
   Cancel AND the primary CTA live and footer height varies per step. */
/* §3.12-4 nudge: pulled to right:16px and up to the rail row itself so it sits
   in the nav band's dead right space (content is max-w-4xl centered) — at the
   old top:(gutter+2.75rem) it could overlap a card's Edit button on demographics
   at 1194px landscape. Truly chrome, never content overlap. */
.ds-help-corner {
  position: fixed; right: 16px; top: 12px; z-index: 40;
}

/* ---------------------------------------------------------------------------
   §3.0-2 / §4-7 — Timeout re-temper
   A thin circular DRAINING stroke (NO pulse) with a 34px numeral. The ring
   circle in markup must carry pathLength="100"; drive --ds-ring-pct (0–100,
   remaining) from JS and the stroke drains smoothly.
   ------------------------------------------------------------------------- */
/* Self-contained calm ring: layout.php emits `<div class="ds-timeout-ring">
   <div id="timeout-countdown" class="timeout-countdown-num">N</div></div>`. The
   ring is a 120px amber circular frame; the numeral (34px via .timeout-countdown-num
   in layout.php's inline <style>) centers via flex. This retires the old 96px
   PULSING glyph — a calm concierge frame, not a klaxon (§3.0-2). A true DRAINING
   stroke needs JS to set --ds-ring-pct each countdown tick; deferred as a small
   follow-up (js/kiosk.js only sets #timeout-countdown.textContent today, a frozen
   contract we are not expanding in the foundation). */
.ds-timeout-ring {
  width: 120px; height: 120px; border-radius: 50%;
  border: 4px solid var(--ds-caution);
  display: flex; align-items: center; justify-content: center;
}

/* ---------------------------------------------------------------------------
   §2.6 + §4 — Motion set
   Directional step entrance, checkmark stroke-draw, list stagger, confirm
   micro-moment. Each animation class is paired with a reduced-motion kill
   switch at the bottom of this block.
   ------------------------------------------------------------------------- */

/* §4-1 page turn — forward rises from +16px, back falls from -16px */
@keyframes ds-enter-forward { from { opacity: 0; transform: translateY(16px);  } to { opacity: 1; transform: translateY(0); } }
@keyframes ds-enter-back    { from { opacity: 0; transform: translateY(-16px); } to { opacity: 1; transform: translateY(0); } }
.ds-enter-forward { animation: ds-enter-forward 0.22s ease-out; }
.ds-enter-back    { animation: ds-enter-back    0.22s ease-out; }

/* §4-5 completion checkmark — SVG stroke draws on. The path/circle in markup
   sets stroke-dasharray + starting stroke-dashoffset; here we animate to 0. */
@keyframes ds-check-draw { to { stroke-dashoffset: 0; } }
.ds-check-draw {
  stroke-dasharray: 180; stroke-dashoffset: 180;
  animation: ds-check-draw 0.5s ease-out forwards;
}

/* §4-4 list stagger — rows cascade in at 30ms offsets (opacity + 4px rise).
   Apply .ds-stagger to the list container; children animate in order. */
@keyframes ds-stagger-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
.ds-stagger > * { animation: ds-stagger-in 0.25s ease-out both; }
.ds-stagger > *:nth-child(1) { animation-delay: 0ms; }
.ds-stagger > *:nth-child(2) { animation-delay: 30ms; }
.ds-stagger > *:nth-child(3) { animation-delay: 60ms; }
.ds-stagger > *:nth-child(4) { animation-delay: 90ms; }
.ds-stagger > *:nth-child(5) { animation-delay: 120ms; }
.ds-stagger > *:nth-child(6) { animation-delay: 150ms; }
.ds-stagger > *:nth-child(7) { animation-delay: 180ms; }
.ds-stagger > *:nth-child(8) { animation-delay: 210ms; }

/* §4-3 confirm micro-moment — the "Confirm & Continue" tap compresses the
   button (0.97) so each commit feels received. JS adds .ds-confirming. */
@keyframes ds-confirm-compress { 0% { transform: scale(1); } 45% { transform: scale(0.97); } 100% { transform: scale(1); } }
.ds-confirming { animation: ds-confirm-compress 0.25s ease-out; }

/* Reduced-motion: honor the layout.php discipline — every DS animation off. */
@media (prefers-reduced-motion: reduce) {
  .ds-enter-forward,
  .ds-enter-back,
  .ds-stagger > *,
  .ds-confirming { animation: none; }
  .ds-check-draw { animation: none; stroke-dashoffset: 0; } /* show drawn, not animate */
}

/* ---------------------------------------------------------------------------
   Focus ring — the global :focus-visible (violet-400/60) lives in layout.php
   and must WIN app-wide. Do NOT redefine it globally here. .ds-focus is only
   for a component that needs a bespoke ring on a non-focusable wrapper.
   ------------------------------------------------------------------------- */
.ds-focus { outline: 2px solid var(--ds-focus); outline-offset: 2px; }

/* ===========================================================================
   WAVE 4 — Fable-5 Pass-2 polish (design-only additions)
   Source: design-review/FABLE5-REVIEW-2.md §5 + §1.1. Foundation classes the
   per-view adoption waves apply; every rule here is paint/typography/layout
   only and preserves all JS-toggled id/onclick contracts. Loaded AFTER
   app.css so the §1.1 cascade rules win same-specificity ties.
   ========================================================================= */

/* §A — one spinner (retires the per-view blue-spinner archipelago).
   Pure class swap on non-JS-toggled nodes; sets no `display`, so no cascade
   footgun with app.css `.hidden`. */
.ds-spinner {
  width: 2rem; height: 2rem; border-radius: 999px;
  border: 3px solid var(--ds-brand); border-top-color: transparent;
  animation: ds-spin 0.8s linear infinite; margin-inline: auto;
}
.ds-spinner--sm { width: 1rem; height: 1rem; border-width: 2px; }
@keyframes ds-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ds-spinner { animation-duration: 1.6s; } }

/* §B — eyebrow label: the 15px uppercase caption used ad hoc via inline styles
   on complete/fast-lane/insurance, promoted to a class. */
.ds-eyebrow {
  font-size: var(--ds-fs-label); line-height: var(--ds-lh-label);
  font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--ds-text-2);
}

/* §C — flat rows: opt-in container that flattens boxed children into hairline
   rows (generalizes the phi-auth override; pair with per-view scoped selectors
   for JS-emitted class names). */
.ds-rows > * {
  background: transparent !important; border-radius: 0 !important;
  padding: 10px 0 !important;
}
.ds-rows > * + * { border-top: 1px solid var(--ds-hairline); margin-top: 0 !important; }

/* §D — segmented sub-step control (demographics screens, consents dots).
   `.ds-seg-item` sets no `display`, so it never fights app.css `.hidden`. The
   active look keys off `aria-current` OR the JS-applied `.bg-emerald-500`
   utility (demographics dots) — cascade-safe, no js/kiosk.js edit required. */
.ds-seg {
  display: inline-flex; gap: 4px; padding: 4px;
  border-radius: var(--ds-r-pill); background: var(--ds-well);
}
.ds-seg-item {
  min-height: 44px; padding: 0 20px; border-radius: var(--ds-r-pill);
  font-size: var(--ds-fs-label); font-weight: 600; color: var(--ds-text-2);
  background: transparent; border: 0; cursor: pointer;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
.ds-seg-item[aria-current="true"], .ds-seg-item.bg-emerald-500 {
  background: var(--ds-surface); color: var(--ds-text);
  box-shadow: inset 0 0 0 1px var(--ds-hairline);
}

/* §E — quiet exception row under a primary CTA (insurance footer). */
.ds-cta-row { display: flex; align-items: center; justify-content: center; gap: 12px; }
.ds-cta-row > .ds-cta-dot { color: var(--ds-text-2); }

/* §F — empty-state note ("Not on file"). Italic gray so "on file" vs "absent"
   is scannable (fast-lane grammar). */
.ds-empty-note { font-style: italic; color: #6B7280; font-size: var(--ds-fs-body); }

/* §G / §3.12-2 — timeout drain ring. CSS-only conic DRAIN with NO JS: the
   overlay reveals by display toggle (none → flex) which (re)starts the
   animation from full. Drive the duration inline: style="--drain-s: <warn>s".
   The conic+mask is on a ::before so the numeral child is NOT clipped by the
   ring mask (CSS masking applies to an element's whole subtree). `@property`
   is Safari 16.4+; on older iPads --ds-ring-live can't interpolate, so the
   ring simply stays full (graceful — the numeral still counts down via the
   frozen #timeout-countdown textContent contract). */
@property --ds-ring-live { syntax: "<number>"; inherits: false; initial-value: 1; }
.ds-ring-drain { border-color: transparent; position: relative; }
.ds-ring-drain::before {
  content: ""; position: absolute; inset: -4px; border-radius: 50%;
  background: conic-gradient(
    var(--ds-caution) calc(var(--ds-ring-live, 1) * 360deg),
    var(--ds-well) 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
          mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
  animation: ds-ring-drain var(--drain-s, 20s) linear forwards;
}
@keyframes ds-ring-drain { from { --ds-ring-live: 1; } to { --ds-ring-live: 0; } }

/* §H — primary-bar modifier: footer-spanning primary (overrides the 36rem cap
   from .ds-btn-primary so a footer CTA can span the column). */
.ds-btn-primary--bar { max-width: none; }

/* §I / §1.1 — input + checkbox convergence (blue retirement, cascade-level).
   Same (0,2,0) specificity as Tailwind's `focus:border-blue-500` utilities;
   this sheet loads later, so these win the tie. `!important` guarantees the
   win regardless of build order. Retires every blue focus ring app-wide with
   ZERO per-view edits. None of these selectors set `display`. */
input[type=checkbox] { accent-color: var(--ds-brand); }
.kiosk-input:focus, #pcp-first-name:focus, #pcp-last-name:focus,
input[type=text]:focus, input[type=tel]:focus, input[type=email]:focus,
select:focus, textarea:focus {
  border-color: var(--ds-brand) !important;
  box-shadow: 0 0 0 2px rgb(139 92 246 / 0.45) !important;
  outline: none;
}
