/* ==========================================================================
   Apple-HIG token system (spec: docs/superpowers/specs/2026-07-11-apple-hig-
   reskin-design.md §1). Canvas/ink/hairlines/shadows adopt Apple neutrals;
   --brand stays THIS app's green (not Apple system blue) per design intent.

   Legacy custom-property NAMES are all kept (grepped before this rewrite) so
   every existing var(--x) consumer elsewhere in this file keeps working
   untouched -- including surfaces this task doesn't touch directly (header,
   dock, buttons, chips, grouped lists, helper/login), which pick up the new
   values automatically. That's intentional: later reskin tasks restyle those
   surfaces' structure, not their tokens.

   Several OLD values below were subtly green-tinted where they should have
   been neutral (a "brand leak"): --bg #f4f8f6, --text #12261d, --muted
   #5f7269, --border #dfe9e4, dark --bg/--card/--text/--muted/--border, and
   --shadow-card's rgba(14,122,85,...) (literally the brand RGB). All are
   replaced with true Apple neutrals below -- see task-1-report.md for the
   full before/after list. */
:root {
  --bg: #F5F5F7;            /* Apple canvas */
  --card: #FFFFFF;
  --text: #1D1D1F;          /* ink */
  --muted: #86868B;
  --tertiary: #AEAEB2; /* reserved, no consumers yet */
  --separator: #D2D2D7;
  --border: #E5E5EA;        /* hairline-light */

  --brand: #0e7a55;
  --brand-2: #12996b;
  --brand-grad: linear-gradient(135deg, var(--brand), var(--brand-2));
  --brand-ink: var(--brand); /* brand when used AS TEXT/icon (vs. fill/gradient); dark mode overrides below */

  /* Apple control fill (translucent gray; composites over whatever's behind
     it). Consumed starting with the buttons/segmented-control tasks -- both
     light and dark values are defined here now per this task's interface
     contract ("produces the spec §1 custom properties ... consumed by every
     later task"). */
  --fill-gray: rgba(118, 118, 128, 0.12);
  --fill-gray-2: rgba(118, 118, 128, 0.16); /* reserved, no consumers yet */

  /* Apple semantic status hues -- raw/system-accurate, NOT pre-adjusted for
     text-on-tint contrast. See .status-pill below for the AA-tuned literals
     actually used as pill background/text (this raw form is for borders,
     icons, and future button fills). */
  --danger: #FF3B30;
  --success: #34C759;
  --warning: #FF9F0A;

  --radius-l: 18px;
  --radius-m: 12px;
  --radius-s: 8px;
  --radius: var(--radius-m); /* legacy alias: every existing var(--radius) consumer now gets 12px */

  --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.05);    /* feather-light; was rgba(14,122,85,.10) -- brand-green shadow leak removed */
  --shadow-raised: 0 4px 16px rgba(0, 0, 0, 0.12); /* dock +, modals, panels */

  /* --- Legacy tint aliases -----------------------------------------------
     Kept as reusable tokens for out-of-scope-this-task consumers (banner,
     low-stock box, alert box, login-error, btn.danger, preview-card, dock
     panel). --amber/--red below are text-safe (same hexes documented with
     computed ratios in the .status-pill comment further down) so nothing
     that currently renders var(--amber)/var(--red) AS TEXT regresses below
     4.5:1 when this token swap lands -- e.g. .low-stock-text was actually
     only 4.14:1 under the OLD --amber, so this is a net accessibility
     improvement, not just a reskin. */
  --amber: #9D5F00;      /* warning family, text-safe: 4.63:1 on --amber-bg */
  --amber-bg: #FFF1DA;   /* warning(#FF9F0A) @ 15% over white */
  --red: #CF0B00;        /* danger family, text-safe: 4.65:1 on --red-bg */
  --red-bg: #FFE2E0;     /* danger(#FF3B30) @ 15% over white */
  /* --green-bg is a BRAND tint (not Apple success-green) -- it's consumed
     only by .preview-card, which is showing brand-green schedule-preview
     identity, not a status. Alpha tuned to 12% (lower than the usual 15%)
     specifically so var(--brand) text on top still clears 4.5:1 (4.52:1). */
  --green-bg: #E2EFEB;
  --neutral-bg: #EEEEEF; /* gray(#8E8E93) @ 15% over white -- same value as .status-pill.upcoming/missed/skipped below */

  /* Secondary/muted text against non-canvas surfaces (header glass, grouped-
     list captions/labels -- spec §2b/§3) hits the same var(--muted)-is-too-
     light problem: #86868B only measures ~3.3-3.6:1 against white/--bg/glass
     composites here (computed), failing 4.5:1 at these sizes (14px 600
     uppercase caption, 16px 400 label/.header-sub -- neither qualifies for
     the large-text AA exception). #6E6E73 clears it everywhere this token is
     consumed: 5.07:1 over the --card/white composite, 4.99:1 over --bg,
     4.96:1 over the header's amber-tint composite, 4.658:1 over the
     grouped-list surface -- all >=4.5:1. Dark mode aliases to var(--muted)
     (5.39-6.35:1+ over every composite this token hits, computed) -- no
     separate dark literal needed.
     (Final review, task-6: this used to be two tokens, --muted-on-card here
     and a header-scoped --muted-on-glass with an identical light value and
     identical dark aliasing -- merged into this single global token; the
     header's own consumers now read this directly.) */
  --muted-on-card: #6E6E73;

  --dock-h: 64px; /* thumb dock content height, excluding safe-area padding */
  --shadow-btn: 0 4px 12px rgba(14, 122, 85, 0.35);  /* unchanged: intentional brand-tinted glow, kept for the hero CTA (.btn.primary). Task 2 moved .dock-plus off this token onto --shadow-raised (spec §2: "raised + keeps ... --shadow-raised") -- this comment used to list "dock +" as a consumer too; that's stale now. */
  --shadow-dock: 0 -4px 16px rgba(0, 0, 0, 0.10);
  --shadow-panel: 0 12px 32px rgba(0, 0, 0, 0.18);
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #000000;
    --card: #1C1C1E;
    --text: #F5F5F7;
    --muted: #98989D;
    --tertiary: #636366;
    --separator: #38383A;
    --border: #2C2C2E;

    --fill-gray: rgba(118, 118, 128, 0.24);
    --fill-gray-2: rgba(118, 118, 128, 0.32); /* spec gives dark --fill-gray only; scaled by the same ~1.33x ratio as light fill-gray -> fill-gray-2 */

    --danger: #FF453A;
    --success: #32D74B;
    --warning: #FFD60A;
    /* --brand is intentionally NOT overridden here -- spec: "brand
       brightened for AA where used as text (reuse the existing #4fd38a
       precedent)". var(--brand) itself stays #0e7a55 in dark mode. The
       handful of dark-mode TEXT/icon usages that need a brighter green
       (e.g. .dock-item.active) read --brand-ink instead -- consolidated
       here (final review, task-6) from what used to be #4fd38a hardcoded
       at ~9 separate call sites. */
    /* brand when used AS TEXT/icon on dark surfaces -- AA-safe */
    --brand-ink: #4fd38a;

    /* var(--muted) itself (#98989D) already clears 4.5:1 on dark --bg/--card
       (5.93:1+, computed) -- no separate tuned literal needed in dark mode. */
    --muted-on-card: var(--muted);

    --amber: #FFD60A;      /* text-safe on dark --amber-bg: 7.69:1 */
    --amber-bg: #453D1A;   /* warning(#FFD60A) @ 18% over #1C1C1E */
    --red: #FF5E55;        /* text-safe on dark --red-bg: 4.60:1 */
    --red-bg: #452323;     /* danger(#FF453A) @ 18% over #1C1C1E */
    /* var(--brand) text on this only reaches ~2.7:1 -- brand green can't
       hit 4.5:1 against any near-black background at its fixed hue/light-
       ness. Pre-existing gap (the OLD --green-bg #16362a measured 2.47:1
       for the same pairing) that this task doesn't newly introduce and isn't
       in scope to fix; only .preview-card consumes it. Flagged in
       task-1-report.md for a future task. */
    --green-bg: #192D28;   /* brand @ 18% over #1C1C1E */
    --neutral-bg: #323235; /* gray @ 18% over #1C1C1E -- same value as .status-pill.upcoming/missed/skipped below. Replaces OLD #232d27, which was accidentally green-tinted for a "neutral" surface. */

    /* Spec defines --shadow-card/--shadow-raised once (Light section) and
       doesn't re-tune them for dark -- flat, minimal elevation (rather than
       a heavier dark shadow) is the Apple dark-mode convention, so both
       stay identical to the light values. */
    --shadow-btn: 0 4px 12px rgba(0, 0, 0, 0.5);
    --shadow-dock: 0 -4px 16px rgba(0, 0, 0, 0.4);
    --shadow-panel: 0 12px 32px rgba(0, 0, 0, 0.5);
  }
}

* { box-sizing: border-box; }

/* ==========================================================================
   Mobile app-feel polish (spec §8, task-12): kill residual "web page" touch
   behaviors on touch devices, as a class, not piecemeal. HARD CONSTRAINT:
   pinch-zoom must keep working -- none of this touches the viewport meta,
   and no `user-scalable`/`maximum-scale` is added anywhere (verified in
   index.html/helper.html/login.html, all three still plain
   `width=device-width, initial-scale=1.0[, viewport-fit=cover]`).

   - touch-action: manipulation on html (inherited everywhere, like `color`):
     removes the double-tap-to-zoom gesture and its ~300ms tap-response delay
     while explicitly preserving pinch-zoom and normal scroll/pan ("manipulation"
     = pan + pinch-zoom, minus double-tap-zoom -- it is NOT "disable zoom").
     Audited (grepped) for any element that needs a DIFFERENT touch-action:
     none -- this app has no custom drag/swipe/pointer-gesture code
     (touchstart/touchmove/pointermove all absent except suggest.js's
     mousedown-steals-focus comment, which isn't a gesture), so one
     html-level rule safely covers the whole app.
   - -webkit-tap-highlight-color: transparent on html: removes the gray tap
     flash Safari/Chrome-Android paint on every tap. Safe specifically
     because every interactive element in the app already carries (or was
     just given, see the :active audit further down this file) its own
     visible `:active` press state -- removing the highlight without one
     would make taps feel inert/broken. */
html {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
/* overscroll-behavior-y: contain on body (not html -- this is a scroll-
   container property): stops scroll chaining at the page boundary so an
   overscroll pull at the very top/bottom doesn't propagate past this
   document and trigger Android Chrome's pull-to-refresh or iOS's
   rubber-band-into-chrome gesture. Vertical only (`-y`) -- nothing in this
   app scrolls horizontally. Normal in-page scrolling is unaffected. */
body {
  overscroll-behavior-y: contain;
}

/* --- Long-press callout + text selection: interactive CHROME only --------
   -webkit-touch-callout: none (kills the iOS long-press "Copy/Share/Look Up"
   popover) and user-select: none (kills drag-to-select) on chrome elements
   ONLY -- never on content.

   Selector choice: a blanket `button` selector plus the two chrome elements
   that AREN'T real <button> tags -- the greeting shortcut (`<h1 role=
   "button">`) and the segmented-control container (`<div role="group">`,
   spec-named explicitly; its own segment buttons are already covered by the
   blanket `button` rule below via inheritance, this additionally silences
   long-press in the small gaps between them). Verified (grepped app.js/
   fab.js/builder.js/segmented.js) that EVERY <button> in this app is chrome
   -- dock items/+/panel rows, nav#tabs, chips, day-picker circles, wizard
   cards, segments, and every card action button (Info/Refill/Edit/Pause/
   Delete/Copy link/Share/Edit/Delete/Take now/I took it/Skip/Sign out/modal
   Close) -- never a wrapper around copyable content data (med name, phone
   number, history line, etc.), so the blanket selector cannot catch
   content-embedded text. Naming all ~8 button classes individually
   (.dock-item, .dock-plus, .dock-panel-row, nav#tabs button, .chip,
   .day-picker button, .wizard-card, .segment) would be equivalent but
   longer -- this is the simpler of the two spec-sanctioned options.

   Content stays selectable BY OMISSION, not by an override: med/helper card
   text, history entries, hero greeting/date text, .hint paragraphs, and
   modal-body drug info are never matched by this rule. Inputs/textareas/
   selects are ALSO never matched -- user-select: none on a form field
   breaks native text selection inside it on some browsers, so they are
   deliberately excluded even though several sit inside a `<label class=
   "row">` wrapper (this rule doesn't target labels either, only the two
   listed non-button chrome classes). */
button,
.greeting,
.segmented,
.suggest-option {
  /* .suggest-option: the autocomplete rows are <li role="option"> — tappable
     chrome like .dock-panel-row, not copyable content (task-12 review). */
  -webkit-touch-callout: none;
  user-select: none;
}

/* Links (Miguel's on-device report: the helper share link rendered default
   browser blue + underline on the dark Helpers tab). App-wide brand tint via
   --brand-ink, the token built for exactly "brand when used AS TEXT":
   var(--brand) #0e7a55 in light (5.34:1 on --card white, 5.1:1 on --bg --
   same white<->brand pair already computed at .btn.primary), #4fd38a in dark
   (the AA-safe brightened-green precedent, 7.3:1+ on --card #1C1C1E per the
   .dock-item.active/.progress-fill writeups). Underline is deliberately
   KEPT (browser default, not overridden) so links inside running text stay
   identifiable by more than color alone (WCAG 1.4.1). */
a { color: var(--brand-ink); }

/* Checkboxes (same on-device report): the native checked fill rendered
   iOS/browser default blue. --brand-ink again rather than raw --brand: in
   light it computes to var(--brand) (the requested green); in dark, raw
   --brand's checked fill only measures ~3.2:1 against the #1C1C1E card --
   technically at the 3:1 non-text floor but visibly murky (same "washed
   out" failure .progress-fill's dark override documents), so the
   brightened dark literal applies here too. The browser auto-picks a
   contrasting checkmark color for the fill. */
input[type="checkbox"] { accent-color: var(--brand-ink); }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, system-ui, sans-serif;
  font-size: 20px; /* Fernando floor: content text stays >=20px -- untouched by this reskin */
  line-height: 1.4;
  background: var(--bg);
  color: var(--text);
}

/* Glass chrome (spec §2 / task-2 brief): header goes from the old green
   gradient to a light/dark frosted bar, sticky above scrolled content.
   Recipe is shared with .dock below -- background/blur/hairline only, kept
   in sync deliberately (see .dock comment for the @supports fallback, which
   covers both bars in one rule). This base rule is still shared by all three
   pages' plain `<header><h1>...</h1></header>` (helper.html, login.html --
   untouched by spec §2c). index.html's compact bar layers `.header-bar`
   below to override padding/alignment for its own constant height, without
   touching those two pages. */
header {
  /* Secondary header text (.header-sub, .progress-label) is 14-16px normal
     weight -- below WCAG's large-text threshold, so the full 4.5:1 floor
     applies; var(--muted) #86868B only measures ~3.5-3.6:1 over the light
     glass composites. Reads the global var(--muted-on-card) token (see its
     :root definition above for the full contrast writeup and the task-6
     final-review merge that folded this header's former scoped
     --muted-on-glass into it -- same value, same dark aliasing, one name). */
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--separator);
  color: var(--text);
  padding: 18px 20px;
  padding-top: calc(env(safe-area-inset-top) + 18px);
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
}
@media (prefers-color-scheme: dark) {
  header {
    background: rgba(28, 28, 30, 0.75);
  }
}
header h1 { margin: 0; font-size: 26px; }

/* --- Zero-CLS compact bar (spec §2c, supersedes §2b's animated collapse) --
   index.html's <header> is `.header-bar`: the ONLY sticky element, and its
   height is CONSTANT at every scroll position -- no JS-driven height/padding
   transition, so it cannot contribute to layout shift. Overrides the shared
   `header` rule's padding/alignment above (more specific selector) with a
   permanently-compact bar (measured ~36px tall excl. safe-area, constant at
   every scroll position -- verified via getBoundingClientRect at scrollY
   0/100/500/bottom); there is no separate "expanded" header
   state to toggle into anymore -- the hero block below carries the
   date/greeting/full progress bar as normal page content instead (see
   .hero further down). */
.header-bar {
  padding: 5px 20px;
  padding-top: calc(env(safe-area-inset-top) + 5px);
  align-items: center;
}

/* Scroll anchors (dock ＋ → "Add a medicine", etc.) must land BELOW the sticky
   bar, not under it: clearance = bar (~36px + safe-area) + breathing room. */
h2, .group-title {
  scroll-margin-top: calc(env(safe-area-inset-top) + 52px);
}
/* Mini progress track + "X of Y" label -- hidden (opacity 0) at rest, faded
   in by the IntersectionObserver in app.js once the hero's own progress bar
   scrolls out from under this bar (via #hero-sentinel). Opacity ONLY (spec
   §2c: "opacity/transform never cause layout shift") -- no max-height/
   margin/visibility here, unlike the old §2b collapse machinery this
   replaces. prefers-reduced-motion zeroes this transition's duration via the
   global rule at the bottom of this file, so "no fade" needs no extra rule. */
.bar-info {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1 1 auto;
  min-width: 0;
  opacity: 0;
  transition: opacity 0.2s ease;
}
.header-bar.scrolled .bar-info { opacity: 1; }
/* Fixed-width mini track (not flex-shrink): a decorative progress glance,
   not information, so 100% of any width squeeze falls on the label instead
   (which gets an ellipsis fallback) -- ported from the old header.compact
   fix (task-6 gate sweep): a flexible track + rigid label measured a real
   ~10px text-on-text overlap with #clock at 375px in Spanish. */
.bar-progress-wrap { flex: 0 0 40px; width: 40px; }
.bar-info .progress-label {
  /* flex-basis 0 (not auto): with auto, this item's content-based
     hypothetical size wins out over its flex-grow and renders mid-truncated
     despite genuinely free space next to it -- `0` is the standard "grow to
     fill, ignore my own content width as a basis" idiom (ported fix). */
  margin: 0; white-space: nowrap; flex: 1 1 0; min-width: 40px;
  overflow: hidden; text-overflow: ellipsis;
  /* Load-time CLS (spec §2d, found during gate verification): both #clock
     and this label start empty (renderHeader() hasn't run yet -- and
     #clock's own first paint is ALSO empty regardless, since its setInterval
     has no immediate call, only a 1s-delayed one) and are the tallest
     children deciding .header-bar's own height (align-items: center, no
     fixed height on the bar itself). Without a reservation, the bar silently
     grows ~14px as content lands -- a real violation of §2c's "constant
     height ... CLS = 0" contract, just manifesting on load instead of
     scroll. min-height = font-size * 1.4 (the global line-height), same
     technique as the hero reservations below. */
  min-height: 22.4px; /* 16px * 1.4 */
}
/* nowrap: the clock rendered as two lines ("07:14" / "PM") when squeezed --
   spec §2b requires single-line always; still true in the §2c bar. 18px
   (down from the old expanded state's 22px) is what gets the scrolled
   chrome total under the ~15%-of-700px budget (measured 16.4% at 22px,
   14.9% at 18px) -- constant now, no separate expand/compact size. */
#clock {
  font-size: 18px; font-variant-numeric: tabular-nums; white-space: nowrap;
  min-height: 25.2px; /* 18px * 1.4 -- see .bar-info .progress-label comment above: reserved for the same load-time CLS reason */
}

/* --- Hero block (spec §2c) ------------------------------------------------
   Normal in-flow content directly below the sticky bar: date eyebrow,
   greeting, full progress bar + label. No glass, no sticky, no safe-area
   padding (that lives on .header-bar now) -- it's page content now, so it
   scrolls away naturally like any other block instead of animating its own
   height. #hero-sentinel (1px tall, end of this block) is what the
   IntersectionObserver in app.js watches to know when this has scrolled
   under the bar. */
.hero {
  padding: 18px 20px 20px;
}
#hero-sentinel { height: 1px; }
/* 16px floor (spec §2b / mobile-ux-review item 5): these two were 14px.
   Global classes (not scoped to .hero) -- unused elsewhere. */
.header-sub { font-size: 16px; color: var(--muted-on-card); text-transform: capitalize; }
.greeting { margin: 2px 0 10px; font-size: 26px; font-weight: 700; cursor: pointer; }
/* :active press state (spec §8 audit): the greeting is a real tap target
   (role="button", jumps to Today) but had ZERO pressed feedback -- opacity
   nudge rather than a background fill/scale, since it's inline hero text
   sitting directly on the plain --bg canvas (no card surface to fill, and
   scaling a 26px heading down would look like a glitch, not a press). */
.greeting:active { opacity: 0.6; }

.progress-wrap {
  height: 8px;
  width: 100%;
  max-width: 320px;
  border-radius: 99px;
  background: var(--fill-gray);
  overflow: hidden;
}
.progress-fill {
  height: 8px;
  border-radius: 99px;
  background: var(--brand);
  transition: width .4s;
}
@media (prefers-color-scheme: dark) {
  /* var(--brand) (#0e7a55, a dark forest green) only measures ~2.4-2.6:1
     against the dark --fill-gray track composited on the glass header --
     dark-on-dark, fails even the 3:1 non-text-contrast floor (WCAG 1.4.11)
     and reads washed out/invisible. Reuse the same brightened-green
     precedent already established for dark-mode brand-as-foreground use
     (.dock-item.active below, .status-pill.taken per task-1-report.md) --
     measures 6.74-7.27:1 against the track, comfortably clears both 3:1 and
     4.5:1. */
  .progress-fill { background: var(--brand-ink); }
}
.progress-label { font-size: 16px; color: var(--muted-on-card); margin-top: 4px; } /* was 14px -- 16px floor per spec §2b */

/* --- Load-time CLS reservations (spec §2d) --------------------------------
   Async /api/state population used to grow these from empty (or, on a
   repeat visit, from a stale one-frame boot-cache value to the corrected
   one -- see the boot-cache IIFE in app.js) to their real height with no
   space held for them, shifting the hero/list/banner below. Each min-height
   = its own font-size * the global line-height (1.4, set on body above --
   none of these three override it), so the box already occupies its
   eventual size before any text lands in it. */
#header-date { min-height: 22.4px; }    /* 16px * 1.4 (.header-sub) */
#progress-label { min-height: 22.4px; } /* 16px * 1.4 (.progress-label, same rule as above) */
#greeting { min-height: 36.4px; }       /* 26px * 1.4 (.greeting) */

nav#tabs {
  display: flex;
  background: var(--card);
  border-bottom: 2px solid var(--border);
  overflow-x: auto;
}
nav#tabs button {
  flex: 1;
  padding: 14px 8px;
  border: none;
  background: none;
  font-size: 18px;
  /* AA (task 6 sweep): var(--muted) on --card measures 3.62:1 light -- fails.
     Reuses the existing --muted-on-card tuned literal (the same global token
     the header's glass text also reads; see .detail below for the full
     writeup). */
  color: var(--muted-on-card);
  cursor: pointer;
  white-space: nowrap;
}
/* :active press state (spec §8 audit): nav#tabs had no pressed feedback at
   all before this -- only the persistent `.active` (selected-tab) state
   below, which is a different thing (worn all the time, not just while
   held down). Needed now that -webkit-tap-highlight-color is suppressed
   globally; token-consistent fill matches the same idiom used everywhere
   else in this file (.row-action:active, .dock-panel-row hover, etc). */
nav#tabs button:active {
  background: var(--fill-gray);
}
nav#tabs button.active {
  color: var(--brand);
  font-weight: 700;
  border-bottom: 4px solid var(--brand);
}
nav#tabs button.active::before { content: '● '; font-size: 10px; }
@media (prefers-color-scheme: dark) {
  /* var(--brand) on dark --card measures 3.19:1 -- 18px/700 sits just under
     the 18.66px bold large-text threshold, so the full 4.5:1 floor applies
     and this fails it. Same brightened-green precedent reused everywhere
     else in this file (.btn.ghost, .dock-item.active, .row-action, etc). */
  nav#tabs button.active { color: var(--brand-ink); border-bottom-color: var(--brand-ink); }
}

main { max-width: 640px; margin: 0 auto; padding: 16px; }
.tab { display: none; }
.tab.active { display: block; }

/* Load-time CLS reservation (spec §2d): #today-list has no children until
   renderToday()'s first run -- reserve ~one dose card's height so the
   nav/dock below it don't jump up then back down as content lands. Only
   applies while genuinely empty: once renderToday() writes anything
   (including the "no medicines today" empty state), real content's own
   height takes over and :empty no longer matches. */
#today-list:empty { min-height: 140px; }

.today-section { margin-bottom: 24px; }
.today-heading {
  margin: 0 4px 10px;
  font-size: 15px;
  line-height: 1.3;
  font-weight: 750;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted-on-card);
}
.header-inner {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.today-now .today-heading { color: var(--amber-ink, #8A5200); }
.today-completed {
  margin: 4px 0 18px;
  border-top: 1px solid var(--border);
}
.today-completed summary {
  min-height: 52px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px;
  color: var(--muted-on-card);
  font-size: 17px;
  font-weight: 650;
  cursor: pointer;
  list-style: none;
}
.today-completed summary::-webkit-details-marker { display: none; }
.today-completed summary::before {
  content: '›';
  font-size: 25px;
  line-height: 1;
  transform: rotate(0deg);
  transition: transform .18s ease;
}
.today-completed[open] summary::before { transform: rotate(90deg); }
.completed-list { padding-top: 4px; }

.card {
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-card);
  border-radius: var(--radius-m);
  padding: 18px;
  margin-bottom: 16px;
}

.banner {
  background: var(--amber-bg);
  border-bottom: 2px solid var(--amber);
  padding: 16px 20px;
  text-align: center;
}
.banner p { margin: 0 0 12px; }
.hidden { display: none !important; }
.login-brand-icon { display: block; margin: 0 auto 14px; border-radius: 18px; }

.btn {
  border: none;
  border-radius: var(--radius-s);
  padding: 12px 20px;
  font-size: 19px;
  font-weight: 600;
  cursor: pointer;
  background: var(--fill-gray);
  color: var(--text);
  min-height: 48px;
  transition: all .15s ease;
}
.btn:active { transform: scale(0.98); }
/* Flat brand fill (spec §4) -- the gradient is reserved for .btn.big (the
   "I took it"/Take-now/Turn-on-reminders/Scan hero CTAs) and the dock ＋
   (task 2), NOT the general .primary case (Save Medicine/Helper/Settings,
   wizard Next). White text on solid --brand: 5.34:1, both themes (--brand
   doesn't change in dark mode) -- clears 4.5:1 with margin at every size
   this class is used at. */
.btn.primary { background: var(--brand); color: #fff; box-shadow: var(--shadow-btn); }
/* Hover/active fill: spec §4 named var(--brand-2), but white text on
   #12996b only measures 3.62:1 -- fails the 4.5:1 floor at this class's
   19px/600 base size, and WCAG 1.4.3 applies to ALL states (hover is a
   real path: the app is verified/used at 1024px). Reviewer-ruled fix:
   darker tuned literal instead (same tuned-literal precedent as the
   .status-pill hexes). #0c6b48 measures 6.54:1 white-on-fill -- a literal,
   so identical in both themes (--brand family doesn't flip in dark).
   var(--brand-2) itself is untouched; it remains the gradient's end stop. */
.btn.primary:hover,
.btn.primary:active { background: #0c6b48; }
/* Danger: soft tint + text, not the old solid-fill white-on-red -- this is
   the deferred Task-1 fix (task-1-report.md flagged dark solid-fill
   white-on-red at 3.01:1, expected to "self-resolve" when this class
   changed shape). Alpha is 0.1 light / 0.2 dark per spec (lighter than
   .status-pill's 15%/18% tint). var(--red) (#CF0B00, the existing
   text-safe danger literal from Task 1) still clears AA at this lighter
   10% alpha -- 4.96:1 -- so it's reused as-is rather than hardcoding a new
   light-mode literal. See the dark override below for why dark needs a new
   literal instead of reusing --red's dark value. */
.btn.danger { background: rgba(255, 59, 48, 0.1); color: var(--red); }
.btn.big { font-size: 24px; padding: 18px 28px; width: 100%; }
/* .btn.big is always also .primary in this app's markup (6 instances,
   grepped; invariant: .big is always also .primary) -- higher-specificity
   override puts the gradient back only here. */
.btn.primary.big { background: var(--brand-grad); }
/* Explicit, not a source-order tie-break: the heroes keep the gradient
   through hover/press (their press cue is the brightness dim + scale, not
   a fill swap), so a future rule reorder can't hand them the flat
   .btn.primary hover fill above. */
.btn.primary.big:hover,
.btn.primary.big:active { background: var(--brand-grad); }
.btn.primary.big:active { filter: brightness(0.92); }
.btn.small { font-size: 15px; padding: 6px 12px; margin-top: 6px; min-height: 40px; }
.btn:disabled { opacity: .55; cursor: default; box-shadow: none; }

/* New: transparent/text-only button (spec §4). Brand text on transparent --
   every .btn in this app renders on a --card surface (dose/med/helper
   cards, modal-body, settings form), so "on transparent" means "on --card"
   for contrast purposes in practice. Hover/press gets a faint brand tint
   (non-text, no AA requirement) matching the .fill-gray idiom used
   elsewhere for control states. */
.btn.ghost { background: transparent; color: var(--brand); box-shadow: none; }
.btn.ghost:hover,
.btn.ghost:active { background: rgba(14, 122, 85, 0.08); }
@media (prefers-color-scheme: dark) {
  /* var(--brand) text on dark --card measures 3.19:1 -- the same
     brand-can't-reach-4.5:1-on-near-black constraint as .preview-card
     .label further down. Reuses the SAME brightened-green precedent
     already established for dark-mode brand-as-foreground text
     (.dock-item.active, dark .progress-fill, .status-pill.taken) --
     measures 8.93:1 here. */
  .btn.ghost { color: var(--brand-ink); }
  .btn.ghost:hover,
  .btn.ghost:active { background: rgba(14, 122, 85, 0.16); }
  /* The dark danger tint is more saturated than the light one (0.2 alpha
     vs 0.1), so the existing --red dark literal (#FF5E55, tuned for
     .status-pill's 18%-alpha bg) narrowly MISSES AA against this specific
     0.2-alpha bg -- measured 4.46:1. A brighter literal is needed just for
     this pairing, hence hardcoded here instead of reusing var(--red). */
  .btn.danger { background: rgba(255, 69, 58, 0.2); color: #FF6961; } /* 4.75:1 */
}

/* Dose cards on the Today tab */
.dose {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.dose-main { display: flex; align-items: flex-start; gap: 18px; }
.dose-copy { flex: 1; min-width: 0; padding-top: 3px; }
.dose .when { font-size: 30px; font-weight: 700; }
.dose .med { font-size: 24px; font-weight: 600; }
.dose-priority {
  padding: 20px;
  border-width: 1px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, .10);
}
.dose-priority .when { font-size: 34px; }
.dose-priority .med { font-size: 25px; }
.dose-compact { padding: 14px 16px; gap: 7px; box-shadow: none; }
.dose-compact .dose-main { gap: 14px; }
.dose-compact .when { font-size: 22px; min-width: 82px; }
.dose-compact .med { font-size: 18px; }
.dose-compact .detail { font-size: 15px; }
.confirmed-line { color: #207C38; font-weight: 600; }
@media (prefers-color-scheme: dark) {
  .today-now .today-heading { color: #FFD60A; }
  .confirmed-line,
  .empty-icon { color: var(--brand-ink); }
}

@media (min-width: 768px) {
  :root { --desktop-content-width: min(920px, calc(100vw - 48px)); }
  .header-inner,
  .hero {
    width: var(--desktop-content-width);
    margin-left: auto;
    margin-right: auto;
  }
  nav#tabs {
    width: var(--desktop-content-width);
    margin: 8px auto 16px;
    padding: 6px;
    gap: 4px;
    overflow: visible;
    border: 1px solid var(--border);
    border-radius: 18px;
    background: color-mix(in srgb, var(--card) 88%, transparent);
    box-shadow: var(--shadow-raised);
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
  }
  nav#tabs button {
    min-height: 48px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 14px;
    border: 0;
    border-radius: 13px;
    font-size: 16px;
    transition: color .15s ease, background .15s ease, transform .15s ease;
  }
  nav#tabs button .app-icon { width: 20px; height: 20px; }
  nav#tabs button.active {
    color: var(--brand-ink);
    border: 0;
    background: var(--green-bg);
  }
  nav#tabs button.active::before { content: none; }
  nav#tabs button:active { transform: scale(.98); }
}
/* Secondary-info text (task 6 ledger sweep): `.detail` is used everywhere a
   card needs a muted second line -- dose instructions/confirmedAt, med
   schedule/stock lines, helper phone/email, history rows, the account-email
   row. It previously had NO general rule (only `.dose .detail` was styled,
   inconsistently muting just the Today tab and leaving every other `.detail`
   full-ink); this generalizes it and fixes the AA gap at the same time:
   var(--muted) #86868B on --card/--bg measures 3.33-3.62:1 light -- fails
   4.5:1 at these sizes (15-20px, none of which clear the large-text
   exception). var(--muted-on-card) (#6E6E73) is the already-established
   tuned literal for exactly this problem (spec §3's group-list secondary
   text) -- reused here rather than re-tuning: 5.07:1 on white/--card,
   4.66:1 on --bg, both themes computed. */
.detail { color: var(--muted-on-card); }
/* Drug-information modal: warnings/dosage/overdose copy stays full-ink for the
   patient persona — never de-emphasize safety content (Task 6 review decision). */
.modal-body .detail { color: var(--text); }
/* Two tinted-card exceptions where muted-on-card itself fails: dark
   low-stock-box (--amber-bg dark composite measures 3.78:1) and light
   alert-box (--red-bg light composite measures 4.16:1) -- computed, not
   assumed. Both alerts are important safety copy anyway (low supply /
   unconfirmed dose), so full-ink var(--text) is the right call regardless of
   the failing tint math: 9.96-15.1:1 on every composite, both themes. */
.low-stock-box .detail,
.alert-box .detail { color: var(--text); }

/* Deleted-medication fallback (spec §2b Task-6 scope): calm/muted, not an
   alarming red -- this is "the record still exists, its medicine doesn't"
   bookkeeping copy, not a warning. Renders inside bold contexts (.med,
   .when, <strong>) on Today/History/the helper page, so both color AND
   weight/style need their own declaration to actually de-emphasize it
   there -- inherited bold would otherwise win. var(--muted-on-card) is the
   same AA-safe literal as .detail (5.07:1 light / 5.93:1 dark on --card,
   computed above); this element only ever sits on plain --card dose/history
   cards, never the tinted low-stock/alert boxes, so no separate override is
   needed the way .detail got. */
.med-deleted { color: var(--muted-on-card); font-weight: 400; font-style: italic; }
/* Glanceability left-edge (Fernando's requirement) stays, narrowed 8px->6px per reskin spec. */
.dose.status-due { border-left: 6px solid var(--amber); }
.dose.status-escalated { border-left: 6px solid var(--red); }
.dose.status-taken { border-left: 6px solid var(--brand); }
.dose.status-upcoming { border-left: 6px solid var(--border); }
.dose.status-missed, .dose.status-skipped { border-left: 6px solid var(--muted); }
.dose.status-taken .dose-copy,
.dose.status-skipped .dose-copy { color: var(--muted-on-card); }

.empty-celebration { padding: 36px 24px; text-align: center; }
.empty-icon {
  width: 58px;
  height: 58px;
  display: grid;
  place-items: center;
  margin: 0 auto 14px;
  border-radius: 50%;
  background: var(--green-bg);
  color: var(--brand);
  font-size: 30px;
  font-weight: 800;
}
.empty-title { margin-bottom: 5px; font-size: 22px; font-weight: 750; }

.toast {
  position: fixed;
  z-index: 1000;
  left: 50%;
  bottom: calc(var(--dock-h) + env(safe-area-inset-bottom) + 20px);
  max-width: calc(100vw - 32px);
  padding: 12px 18px;
  border-radius: 999px;
  background: rgba(28, 28, 30, .94);
  color: #fff;
  font-size: 17px;
  font-weight: 650;
  box-shadow: var(--shadow-raised);
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, 12px);
  transition: opacity .2s ease, transform .2s ease;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
.btn.is-loading { position: relative; }

@media (max-width: 420px) {
  .dose-main { gap: 13px; }
  .dose .when { font-size: 26px; min-width: 86px; }
  .dose .med { font-size: 21px; }
  .dose-priority .when { font-size: 30px; }
  .dose-compact .when { font-size: 20px; min-width: 76px; }
  .dose-compact .med { font-size: 18px; }
}

@media (prefers-reduced-motion: reduce) {
  .today-completed summary::before,
  .toast { transition: none; }
}

/* Final responsive hierarchy overrides live after the legacy component
   declarations so they reliably win the cascade. */
@media (max-width: 767.98px) {
  .modal { align-items: flex-end; padding: 0; }
  .modal-body {
    width: 100%; max-width: none; max-height: 88vh; margin: 0;
    border-radius: 22px 22px 0 0;
    padding-bottom: calc(22px + env(safe-area-inset-bottom));
  }
  #modal-close { position: sticky; top: 0; z-index: 2; float: none; margin-left: auto; }
}

@media (min-width: 900px) {
  #tab-meds #meds-list,
  #history-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; align-items: start; }
  #tab-meds #meds-list .card { margin-bottom: 0; }
  #history-list .history-day { margin-bottom: 0; }
  #tab-meds,
  #tab-history { width: var(--desktop-content-width); margin-left: 50%; transform: translateX(-50%); }
  #tab-settings { max-width: 700px; margin: 0 auto; }
}

/* Keep this after the shared row-control rules: routine times intentionally
   use a wider, identical column than generic compact time inputs. */
.settings-section .routine-group input[type="time"] {
  width: 156px;
  min-width: 156px;
  max-width: 156px;
  margin: 0;
  padding-left: 8px;
  padding-right: 4px;
  text-align: left;
  text-align-last: left;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 390px) {
  .settings-section .routine-group input[type="time"] { width: 148px; min-width: 148px; max-width: 148px; }
}

/* --- Information-hierarchy refinement ---------------------------------- */
.caught-up,
.helper-intro {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 20px;
}
.caught-up {
  padding: 16px;
  border-radius: var(--radius-m);
  background: var(--green-bg);
}
.caught-up-icon,
.helper-intro-icon,
.empty-state-icon {
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--green-bg);
  color: var(--brand-ink);
}
.caught-up-icon .app-icon,
.helper-intro-icon .app-icon,
.empty-state-icon .app-icon { width: 24px; height: 24px; }

.empty-state { display: grid; justify-items: center; gap: 7px; }
.empty-state > strong { color: var(--text); font-size: 21px; }

.card-primary-actions { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
.page-heading {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin: 4px 0 20px;
}
.page-heading h2 { margin: 0; font-size: 30px; }
.page-heading .hint { margin: 3px 0 0; }
.page-heading .btn { flex: 0 0 auto; }

.med-editor {
  display: none;
  margin-top: 28px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
  scroll-margin-top: calc(env(safe-area-inset-top) + 58px);
}
.med-editor.open { display: block; }
.med-editor-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 14px; }
.med-editor-heading h2 { margin: 0; }
.med-editor-heading .icon-button { flex: 0 0 44px; width: 44px; height: 44px; }

#meds-list .card { position: relative; }
#meds-list .card-name { line-height: 1.25; }
#meds-list .detail-row { padding-top: 3px; }
#meds-list .card-primary-actions { padding-top: 6px; border-top: 1px solid var(--border); }
@media (max-width: 480px) {
  .page-heading { align-items: center; }
  .page-heading h2 { font-size: 27px; }
  .page-heading .hint { display: none; }
  .page-heading .btn { padding-left: 14px; padding-right: 14px; }
}
.action-menu { position: relative; margin-left: auto; }
.action-menu > summary {
  width: 44px; height: 44px; display: grid; place-items: center;
  border-radius: var(--radius-s); background: var(--fill-gray); color: var(--text);
  cursor: pointer; list-style: none;
}
.action-menu > summary::-webkit-details-marker { display: none; }
.action-menu > summary .app-icon { width: 22px; height: 22px; }
.action-menu-popover {
  position: absolute; z-index: 20; right: 0; bottom: 50px; width: 210px;
  padding: 6px; border: 1px solid var(--border); border-radius: var(--radius-m);
  background: var(--card); box-shadow: var(--shadow-raised);
}
.action-menu-popover .dock-panel-row .app-icon { width: 20px; height: 20px; color: var(--brand-ink); }
.action-menu-popover .danger-row { color: var(--red); }
.action-menu-popover .danger-row .app-icon { color: currentColor; }

.history-day { margin-bottom: 24px; }
.history-day > h2 { margin: 0 4px 8px; font-size: 16px; text-transform: capitalize; color: var(--muted-on-card); }
.history-group { overflow: hidden; border: 1px solid var(--border); border-radius: var(--radius-m); background: var(--card); }
.history-row {
  display: grid; grid-template-columns: 28px 82px 1fr; align-items: start; gap: 10px;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.history-row:last-child { border-bottom: none; }
.history-row time { font-weight: 700; font-variant-numeric: tabular-nums; }
.history-status-icon { width: 24px; height: 24px; display: grid; place-items: center; border-radius: 50%; color: var(--muted-on-card); }
.history-status-icon .app-icon { width: 18px; height: 18px; }
.history-row.status-taken .history-status-icon { background: var(--green-bg); color: var(--brand-ink); }
.history-row.status-missed .history-status-icon { background: var(--red-bg); color: var(--red); }

.settings-section { margin-bottom: 12px; }
.settings-section > summary {
  min-height: 68px; display: grid; grid-template-columns: 42px 1fr auto; align-items: center; gap: 12px;
  padding: 10px 14px; border: 1px solid var(--border); border-radius: var(--radius-m);
  background: var(--card); cursor: pointer; list-style: none;
}
.settings-section > summary::-webkit-details-marker { display: none; }
.settings-section > summary::after { content: '›'; color: var(--muted-on-card); font-size: 27px; transition: transform .18s ease; }
.settings-section[open] > summary::after { transform: rotate(90deg); }
.settings-section > summary > span:nth-child(2) { display: grid; gap: 2px; }
.settings-section > summary small { color: var(--muted-on-card); font-size: 15px; }
.settings-summary-icon { width: 38px; height: 38px; display: grid; place-items: center; border-radius: 10px; background: var(--green-bg); color: var(--brand-ink); }
.settings-summary-icon .app-icon { width: 21px; height: 21px; }
.settings-section[open] > .group-title { margin-top: 16px; }
.settings-section > .group,
.settings-section > .hint { margin-left: 0; margin-right: 0; }
#settings-form > .btn.primary { margin-top: 8px; }
#smtp-form > .settings-section { margin-top: 26px; }
#smtp-form > .settings-section > .btn { margin-bottom: 10px; }

/* Daily routine is a small data table, not a collection of content-sized
   flex rows. A stable label column makes every HH:MM, AM/PM suffix, and
   native clock affordance share the same vertical guides. */
.routine-group .row {
  display: grid;
  grid-template-columns: minmax(92px, 1fr) 156px;
  align-items: center;
  column-gap: 16px;
}
.routine-group .row-label { min-width: 0; }
.routine-group input[type="time"] {
  width: 156px;
  min-width: 156px;
  justify-self: end;
  margin: 0;
  padding-left: 8px;
  padding-right: 4px;
  font-variant-numeric: tabular-nums;
  text-align: left;
}
@media (max-width: 390px) {
  .routine-group .row { grid-template-columns: minmax(82px, 1fr) 148px; column-gap: 10px; }
  .routine-group input[type="time"] { width: 148px; min-width: 148px; }
}

.helper-brand { display: flex; align-items: center; justify-content: center; gap: 12px; }
.helper-brand img { border-radius: 11px; }
.helper-brand h1 { margin: 0; }
.helper-intro h2 { margin: 0; }
.helper-urgent { position: relative; padding-top: 22px; }
.urgent-icon { width: 40px; height: 40px; display: grid; place-items: center; margin-bottom: 6px; border-radius: 50%; background: var(--red-bg); color: var(--red); }
.urgent-icon .app-icon { width: 22px; height: 22px; }
.helper-urgent .btn.big { margin-top: 4px; text-decoration: none; justify-content: center; }
.helper-all-good { text-align: center; display: grid; justify-items: center; gap: 8px; }
.login-language { margin-top: 4px; }

/* Mobile drug information behaves like a bottom sheet. */
@media (max-width: 767.98px) {
  .modal { align-items: flex-end; padding: 0; }
  .modal-body { width: 100%; max-width: none; max-height: 88vh; margin: 0; border-radius: 22px 22px 0 0; padding-bottom: calc(22px + env(safe-area-inset-bottom)); }
  #modal-close { position: sticky; top: 0; z-index: 2; float: none; margin-left: auto; }
}

@media (min-width: 900px) {
  #tab-meds #meds-list,
  #history-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; align-items: start; }
  #tab-meds #meds-list .card { margin-bottom: 0; }
  #history-list .history-day { margin-bottom: 0; }
  #tab-meds,
  #tab-history { width: var(--desktop-content-width); margin-left: 50%; transform: translateX(-50%); }
  #tab-settings { max-width: 700px; margin: 0 auto; }
}

.status-pill {
  display: inline-block;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 16px;
  font-weight: 700;
  width: fit-content;
}
/* Tint table (spec §1): due=warning, escalated=danger, taken=success,
   upcoming/missed/skipped=gray. Every pill renders inside a .card, so each
   background below is that semantic Apple color alpha-composited over
   --card (white light / #1C1C1E dark); each text color is the same hue
   pushed in lightness (darkened for light mode, lightened for dark) until
   the computed contrast against THAT composited background clears 4.5:1.
   Computed with a small WCAG relative-luminance script (not eyeballed) --
   see contrast-t1.js referenced in task-1-report.md. Bold 16px is below the
   "large text" AA threshold (18.66px bold / 24px regular), so it needs the
   full 4.5:1 floor, same as body text; every pairing below carries >=0.1
   margin above that floor. */
.status-pill.due { background: #FFF1DA; color: #9D5F00; }        /* warning #FF9F0A @ 15% over #fff -> 4.63:1 */
.status-pill.escalated { background: #FFE2E0; color: #CF0B00; }  /* danger  #FF3B30 @ 15% over #fff -> 4.65:1 */
.status-pill.taken { background: #E1F7E6; color: #207C38; }      /* success #34C759 @ 15% over #fff -> 4.66:1 */
.status-pill.upcoming,
.status-pill.missed,
.status-pill.skipped { background: #EEEEEF; color: #6A6A6F; }    /* gray    #8E8E93 @ 15% over #fff -> 4.64:1 */
@media (prefers-color-scheme: dark) {
  .status-pill.due { background: #453D1A; color: #FFD60A; }        /* warning #FFD60A @ 18% over #1C1C1E -> 7.69:1 */
  .status-pill.escalated { background: #452323; color: #FF5E55; }  /* danger  #FF453A @ 18% over #1C1C1E -> 4.60:1 */
  .status-pill.taken { background: #203E26; color: #32D74B; }      /* success #32D74B @ 18% over #1C1C1E -> 6.16:1 */
  .status-pill.upcoming,
  .status-pill.missed,
  .status-pill.skipped { background: #323235; color: #9B9B9F; }    /* gray    #98989D @ 18% over #1C1C1E -> 4.61:1 */
}

/* --- Card content redesign (spec §7) --------------------------------------
   Shared anatomy for Helper/Medicine card headers (renderHelpers/renderMeds
   in app.js): an optional 40px avatar circle or plain title, name/dosage at
   18px/600, an optional trailing soft-tint badge. Detail rows below are one
   per line, 16px, var(--muted-on-card) -- the SAME token already AA-verified
   for .detail above across the 15-20px range that comment documents (16px
   falls inside it, so no new contrast measurement needed for the text
   color itself; link color is covered separately below). */
.card-header-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 6px;
}
.card-name {
  font-size: 18px;
  font-weight: 600;
  flex: 1 1 auto;
  min-width: 0;
}
.avatar {
  flex: 0 0 40px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  font-weight: 700;
  /* Same brand-tint pair as .badge.brand below -- avatar initial and the
     "Email alerts" badge are the same visual language on purpose. */
  background: var(--green-bg);
  color: var(--brand-ink);
}
.detail-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 16px;
  color: var(--muted-on-card);
  margin-top: 4px;
}
/* tel:/mailto: links (helper card): reuses the app-wide `a { color:
   var(--brand-ink) }` rule already declared above -- no override needed,
   listed here only so the fixed-width icon column below doesn't push it out
   of a flex row. */
.app-icon { width: 1em; height: 1em; display: block; flex: 0 0 auto; }
.detail-row .icon-col {
  flex: 0 0 20px;
  display: grid;
  place-items: center;
  color: var(--brand-ink);
}
.detail-row .icon-col .app-icon { width: 19px; height: 19px; }
.btn-with-icon,
.title-with-icon { display: inline-flex; align-items: center; gap: 7px; }
.btn-with-icon .app-icon { width: 19px; height: 19px; }
.title-with-icon .app-icon { width: 25px; height: 25px; color: var(--brand-ink); }
.icon-button { display: inline-grid; place-items: center; padding: 8px; }
.icon-button .app-icon { width: 21px; height: 21px; }

/* Soft-tint badges: "Paused" (medicine, inactive), low/out-of-stock
   (medicine), "Email alerts" (helper). Same tinted-pill idiom as
   .status-pill above; each variant below REUSES an already-computed literal
   pair from elsewhere in this file rather than inventing a new one, so no
   fresh AA risk: .badge.warning is byte-for-byte .status-pill.due's
   background/text; .badge.neutral is byte-for-byte .status-pill.upcoming's.
   .badge.brand is new (var(--green-bg)/var(--brand-ink) weren't previously
   paired as a text-on-tint badge) -- computed here: brand #0e7a55 on
   --green-bg #E2EFEB (light) = 4.52:1; --brand-ink #4fd38a on dark
   --green-bg #192D28 = 7.61:1. Both >=4.5:1 at this badge's 15px/600 size
   (below the large-text threshold, so the full floor applies). */
.badge {
  display: inline-block;
  width: fit-content;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 15px;
  font-weight: 600;
}
.badge.brand { background: var(--green-bg); color: var(--brand-ink); }
.badge.warning { background: var(--amber-bg); color: var(--amber); }
.badge.neutral { background: #EEEEEF; color: #6A6A6F; }    /* same literal as .status-pill.upcoming: 4.64:1 */
@media (prefers-color-scheme: dark) {
  .badge.neutral { background: #323235; color: #9B9B9F; }  /* same literal as dark .status-pill.upcoming: 4.61:1 */
}

/* History line anatomy (spec §7, loadHistory()): date becomes a muted
   eyebrow above the time+med line. Both sit at the 16px secondary-text
   floor (mobile-ux-review item 5); .hist-date reuses the same
   var(--muted-on-card) pair as .detail (5.07:1 light / 5.93:1 dark on
   --card, already computed above), .hist-line is full-ink var(--text)
   (16.83:1 light / 15.63:1 dark on --card -- no gap possible). */
.hist-date { font-size: 16px; color: var(--muted-on-card); margin-top: 2px; }
.hist-line { font-size: 16px; color: var(--text); margin-top: 2px; }

form label { display: block; margin-bottom: 14px; font-weight: 600; }
form input:not([type="checkbox"]),
form select {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 12px;
  font-size: 19px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card);
  color: var(--text);
}
form label.check { display: flex; align-items: center; gap: 10px; font-weight: 400; }
form input[type="checkbox"] { width: 24px; height: 24px; }

/* --- Grouped lists (iOS Settings style) -- spec §3 ------------------------
   Settings tab, med form, and helper form all use this pattern: a
   `.group-title` caption above a `.group` card made of `.row` items. The
   `form label`/`form input` rules above stay in effect for markup this task
   leaves untouched (the schedule builder's own bespoke chips/wizard/time
   inputs inside the "Schedule" group -- restyled in a later task per §5). */
.group-title {
  font-size: 14px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted-on-card);
  margin: 24px 0 8px 16px;
}
.group {
  background: var(--card);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  margin-bottom: 8px;
}
/* The Schedule group hosts the builder's own bespoke markup (chips, wizard,
   time rows) instead of `.row` items -- unlike `.row`, which pads itself
   `0 16px`, `.group` has no padding of its own, so this needs its own. */
.group.schedule-group {
  padding: 18px 16px 16px;
}
.row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 52px;
  padding: 0 16px;
  margin: 0;
  position: relative;
  font-weight: 400;
}
/* Inset hairline divider between rows (iOS convention: none above the
   first, none below the last), anchored at the same 16px inset as the row
   padding so it starts under the label, not the card edge. */
.row::after {
  content: '';
  position: absolute;
  left: 16px;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--separator);
}
.row:last-child::after {
  display: none;
}
.row-label {
  font-size: 16px;
  font-weight: 400;
  color: var(--muted-on-card);
  /* flex-shrink: 0 -- rows only use the default (non-stacked) pattern when
     their label is short by design (see the .row.stacked comment below for
     the long-label cases), so the label should never lose the shrink
     tug-of-war against the value's flex-grow and get squeezed into an
     unwanted multi-line wrap; max-width stays as a safety cap in case a
     future translation runs long anyway. */
  flex: 0 0 auto;
  max-width: 62%;
}
/* Seamless right-aligned inputs: no border/box, transparent, 18px text --
   the row itself is the visual container. flex-shrink must stay 1 (not the
   more tempting flex:0 0 auto pinned to natural size) for every one of
   these -- a <select>'s auto flex-basis sizes to its WIDEST <option> (e.g.
   "Español (México)"), and a plain <input type=number/time> defaults to a
   ~170-190px UA content width; with flex-shrink:0 either one refuses to
   compress and blows the row (and the whole page) out past the viewport.
   Capping time/number at max-width keeps them visually compact without
   reintroducing that overflow, since they can still shrink below it. */
.row input:not([type="checkbox"]),
.row select {
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  background: transparent;
  padding: 0;
  margin: 0;
  font-size: 18px;
  color: var(--text);
  text-align: right;
  text-align-last: right;
}
.row input[type="time"],
.row input[type="number"] {
  max-width: 140px;
}
.row input[type="checkbox"] {
  margin-left: auto;
}
/* Caret breathing room: right-aligned text puts the end-of-text insertion
   point flush against the content-box edge, so mobile browsers draw the
   blinking caret ON TOP of the last typed glyph. 4px right padding gives
   the caret room to render beside the glyph; the matching negative margin
   cancels the layout shift so values stay on the row's 16px inset, exactly
   aligned with rows that hold a select instead. */
.row input:not([type="checkbox"]) {
  padding-right: 4px;
  margin-right: -4px;
}
/* Focus affordance for text-ish inputs: the spec's original brand-caret-only
   treatment (spec §3) was invisible in practice -- a 2px blinking line is
   not a discoverable "ready for input" signal, especially with a mouse on
   desktop. Reuse the selects' soft pill (same tokens, so adjacent controls
   in one group light up identically); the spread shadow extends the pill
   6px past the text without shifting layout. Plain :focus, not
   :focus-visible: while typing on touch (no keyboard-vs-pointer split that
   matters for a text field) the pill must stay lit. */
.row input:focus {
  outline: none;
  caret-color: var(--brand);
  background: var(--fill-gray);
  border-radius: var(--radius-s);
  box-shadow: 0 0 0 6px var(--fill-gray);
}
.row select:focus-visible {
  outline: none;
  background: var(--fill-gray);
  border-radius: var(--radius-s);
}
/* Stacked variant: for the handful of rows whose label is long-form
   instructional copy (settings help text, inventory-tracking explanation)
   rather than a short noun -- forcing these onto one right-aligned line
   would either crush the input to an unusable width or wrap the label into
   a ragged multi-line mess beside a vertically-centered input. Label gets
   its own full-width line; the input follows, left-aligned, below it (the
   row's min-height simply grows). Chosen per-row by content length in both
   languages -- most rows in this app (short nouns: "Name", "Dose", "Wake
   up"...) keep the default horizontal pattern. */
.row.stacked {
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  padding: 10px 16px;
}
.row.stacked .row-label {
  max-width: 100%;
  white-space: normal;
}
.row.stacked input,
.row.stacked select {
  flex: none;
  width: 100%;
  text-align: left;
  text-align-last: left;
  margin-left: 0;
}
/* Full-width tappable action row (Settings > Account > Sign out): centered
   brand-colored text instead of a label+value pair. Not `.btn` -- that
   class's own padding/background/radius would fight `.row`'s. */
.row-action {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 52px;
  padding: 0 16px;
  border: none;
  background: none;
  color: var(--brand);
  font-size: 17px;
  font-weight: 600;
  cursor: pointer;
}
.row-action:active { background: var(--fill-gray); }
@media (prefers-color-scheme: dark) {
  /* Same brightened-green precedent as .btn.ghost/.dock-item.active/dark
     .progress-fill/.status-pill.taken -- var(--brand) text under-contrasts
     on dark --card. */
  .row-action { color: var(--brand-ink); }
}
/* Submit/cancel buttons sit below their groups, not inside rows (spec §3) --
   `.group`'s own 8px margin-bottom plus this gives ~16px total breathing
   room before the button, matching the app's existing .card rhythm. */
#settings-form > .btn,
#med-form > .btn,
#helper-form > .btn,
#smtp-form > .btn {
  margin-top: 8px;
}
/* Second .group (the test-email row) inside #smtp-form needs the same
   breathing room above it that a .btn gets above -- otherwise it sits flush
   under the Save button with no visual separation between the two actions. */
#smtp-form > .group + .group {
  margin-top: 16px;
}

/* --- Email alerts (SMTP) settings (2026-07-14 spec) ---------------------
   Inline test-email result text: success/danger tokens, computed AA (not
   alert(), per spec -- the result needs to stay legible on its own).
   Light literals reuse already-verified pairs: #207C38 is the exact
   .status-pill.taken text color (5.25:1 on white/--bg, computed above);
   var(--red) is the same literal .login-error uses (5.67:1 on white).
   Dark mode: var(--success) (#32D74B) measures 8.88:1 on dark --bg/--card;
   var(--red)'s dark override (#FF5E55) is already 5.66:1 there -- no
   separate danger override needed in dark mode. */
.smtp-result.success { color: #207C38; }
.smtp-result.danger { color: var(--red); }
@media (prefers-color-scheme: dark) {
  .smtp-result.success { color: var(--success); }
}

/* --- Segmented controls -- spec §5 -----------------------------------------
   Progressive enhancement over the language / ease-of-use <select>s (see
   public/segmented.js): the select stays in the DOM as the source of truth,
   visually-hidden once JS successfully builds its .segmented replacement.
   Both settings rows that host one are `.row.stacked` (spec: "a segmented
   control is wider than a right-aligned select -- stack those two rows") --
   `.row.stacked`'s `align-items: stretch` is what makes the appended
   .segmented div span the row's full width for free, no extra CSS needed. */
.segmented {
  display: flex;
  gap: 2px;
  background: var(--fill-gray);
  padding: 3px;
  border-radius: calc(var(--radius-s) + 1px); /* 9px */
}
.segment {
  flex: 1 1 0;
  min-height: 44px;
  padding: 0 8px;
  border: none;
  border-radius: calc(var(--radius-s) - 2px);
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color .15s ease, box-shadow .15s ease;
}
/* :active press state (spec §8 audit): unpressed/inactive segments had no
   pressed feedback (only `.segment.active`'s PERSISTENT selected-state
   background, a different thing -- and `:focus-visible`, keyboard-only).
   Placed before `.segment.active` in source order so the persistent active
   background still wins on ties (same specificity, later rule wins) when
   pressing an already-selected segment -- it already has its own shadow
   affordance and doesn't need this fill on top. */
.segment:active {
  background: var(--fill-gray-2);
}
/* Text contrast (computed): var(--text) on --fill-gray composited over
   --card -- light ~13.7:1 (#1D1D1F on ~#EFEFF0), dark ~12:1 (#F5F5F7 on
   ~#323236) -- both far clear of AA 4.5:1; same pairing already used by
   .chip elsewhere in this file. */
.segment.active {
  background: var(--card);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  font-weight: 700;
}
.segment:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* Visually-hidden utility (standard clip technique): stays in the DOM/
   scriptable, removed from visual layout. segmented.js additionally sets
   tabindex="-1" + aria-hidden="true" on the selects it hides so they don't
   leave a stray, invisible tab stop -- the segment buttons are the only
   reachable UI once JS enhances them. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Gap widened 6px->8px (task 6 ledger sweep) to match the rest of the
   reskin's spacing scale -- checked at 375px: the row already wraps to 5+2
   at 6px (7*48px buttons don't fit one row inside the schedule group's
   padded width at any phone size), and 8px produces the identical wrap
   point, so this is a pure breathing-room change, not a new overflow. */
.day-picker { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
.day-picker button {
  width: 48px; height: 48px;
  border-radius: 50%;
  border: none;
  background: var(--fill-gray);
  color: var(--text);
  font-size: 16px;
  cursor: pointer;
  transition: all .15s ease;
}
.day-picker button:active { transform: scale(0.98); }
/* Active white-on-solid-brand: 5.34:1, both themes (same pairing as
   .btn.primary above -- reused, not re-measured). */
.day-picker button.on { background: var(--brand); color: #fff; }

/* Schedule builder (public/builder.js) */
.label {
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: .04em;
  /* AA (task 6 sweep): same var(--muted)-on-card gap as .detail/.hint above. */
  color: var(--muted-on-card);
  font-weight: 700;
  margin-bottom: 6px;
}
.builder-section { margin-bottom: 18px; }
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 16px; }
.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 10px 18px;
  border-radius: 999px;
  border: none;
  background: var(--fill-gray);
  color: var(--text);
  font-size: 17px;
  cursor: pointer;
  transition: all .15s ease;
}
.chip:active { transform: scale(0.98); }
/* Active white-on-solid-brand: 5.34:1, both themes (same pairing as
   .btn.primary above -- reused, not re-measured). */
.chip.on { background: var(--brand); color: #fff; }

.times-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 8px; }
.time-row { display: flex; align-items: center; gap: 8px; }
.time-row input[type="time"] { flex: 1; margin-top: 0; }
/* Remove-time button must meet the 48px touch floor (global constraint);
   scoped to the builder so other .btn.small uses keep their compact size. */
.time-row .btn.small { margin-top: 0; flex-shrink: 0; min-height: 48px; min-width: 48px; }

.interval-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 14px;
  font-weight: 600;
}
.interval-row select,
.interval-row input[type="time"] { width: auto; margin-top: 0; }

/* Standalone box model (used to be inherited from the also-applied `.card`
   class -- that class is dropped now that #schedule-preview sits inside the
   already-card-styled "Schedule" .group instead of a `.card` form). */
.preview-card { background: var(--green-bg); border-radius: var(--radius-s); padding: 14px 16px; margin-top: 4px; }
.preview-card .label { color: var(--brand); }
@media (prefers-color-scheme: dark) {
  /* var(--brand) on dark --green-bg measures 2.72:1 -- fails AA. This is
     the OTHER deferred Task-1 fix (task-1-report.md: "brand-on-brand-tint
     ... Recommend Task 3 or 6 apply the same brightened-text trick").
     Reuses the exact #4fd38a precedent used for .btn.ghost above,
     .dock-item.active, dark .progress-fill and .status-pill.taken --
     measures 7.61:1 here. */
  .preview-card .label { color: var(--brand-ink); }
}

/* Simple mode (public/app.js uiMode()): hide secondary dose metadata on Today
   so cards stay big and scannable. Scoped to #tab-today only — History reuses
   the same .dose/.detail classes and must keep showing date/time/med name. */
body.simple #tab-today .dose .detail { display: none; }

/* Simple-mode wizard (public/builder.js renderSimple()) */
.wizard-progress { display: flex; flex-direction: column; align-items: center; gap: 6px; margin-bottom: 16px; }
.wizard-dots { display: flex; gap: 8px; }
.wizard-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--border); }
.wizard-dot.on { background: var(--brand); }
/* AA (task 6 sweep): same var(--muted)-on-card gap as .detail/.hint. */
.wizard-step-label { font-size: 14px; color: var(--muted-on-card); font-weight: 600; }

.wizard-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 8px 0 16px; }
.wizard-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 88px;
  padding: 12px;
  border-radius: var(--radius);
  border: none;
  background: var(--fill-gray);
  color: var(--text);
  font-size: 17px;
  font-weight: 600;
  text-align: center;
  cursor: pointer;
  transition: all .15s ease;
}
.wizard-card:active { transform: scale(0.98); }
/* Active white-on-solid-brand: 5.34:1, both themes (same pairing as
   .btn.primary above -- reused, not re-measured). */
.wizard-card.on { background: var(--brand); color: #fff; }
.wizard-card-icon {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  color: var(--brand-ink);
}
.wizard-card-icon .app-icon { width: 30px; height: 30px; }
.wizard-card.on .wizard-card-icon { color: #fff; }

/* "Large" time/number inputs inside the wizard steps (bigger than the
   default form control size used elsewhere). */
.wizard-body input[type="time"],
.wizard-body input[type="number"] { font-size: 22px; padding: 14px; min-height: 52px; }

.wizard-nav { display: flex; gap: 12px; margin-top: 4px; }
.wizard-nav .btn { min-height: 48px; flex: 1; }
.wizard-nav .wizard-back { flex: 0 0 auto; min-width: 96px; }

.wizard-closed { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.wizard-closed-summary { flex: 1; }
.wizard-closed .btn { min-height: 48px; flex: 0 0 auto; }

/* AA (task 6 sweep): .hint/.empty had the same var(--muted) 3.33-3.62:1-on-
   card/--bg gap as .detail above -- same fix, same reused literal.
   (.helper-link, formerly styled here too, was retired by spec §7's card
   redesign -- the raw share link no longer renders as visible text; see
   .hint's reuse for the new helperLinkHint copy in renderHelpers().) */
.hint { color: var(--muted-on-card); font-size: 17px; }
.row-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 8px; }

.empty { text-align: center; color: var(--muted-on-card); padding: 40px 0; }

/* Label scan */
.scan-card { text-align: center; }
.scan-card .hint { margin-bottom: 0; }

/* Inventory: low-stock warning -- spec §6 "warning-tint group" treatment.
   Was a heavier 2px border (old pre-reskin style); softened to the same 1px
   hairline weight every other reskinned surface uses, keeping the amber tint
   + `.card`'s inherited radius/shadow doing the "this is a group" signaling
   instead of a thick outline. */
.low-stock-box { background: var(--amber-bg); border: 1px solid var(--amber); }
/* .low-stock-text (bold inline amber text) retired by spec §7: renderMeds()'s
   low-stock line is now a .badge.warning instead of inline colored text. */

/* Modal (drug information) */
.modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  z-index: 100;
}
.modal-body {
  background: var(--card);
  border-radius: var(--radius-l);
  padding: 20px;
  max-width: 560px;
  width: 100%;
  max-height: 85vh;
  overflow-y: auto;
  box-shadow: var(--shadow-raised);
}
.modal-body h2 { margin-top: 0; }
.modal-body h3 { margin-bottom: 4px; color: var(--brand); }
#modal-close { float: right; }
@media (prefers-color-scheme: dark) {
  /* var(--brand) on dark --card measures 3.19:1 -- same brand-can't-clear-
     4.5:1-on-near-black constraint as every other dark-mode brand-as-text
     instance in this file; reuses the same brightened literal. */
  .modal-body h3 { color: var(--brand-ink); }
}

/* Helper page */
.alert-box { background: var(--red-bg); border: 2px solid var(--red); }
.big-phone {
  display: block;
  text-align: center;
  font-size: 28px;
  font-weight: 800;
  color: var(--brand);
  text-decoration: none;
  padding: 14px;
  border: 2px solid var(--brand);
  border-radius: var(--radius);
  margin: 10px 0;
}
@media (prefers-color-scheme: dark) {
  /* Computed (task 6 sweep): var(--brand) on --red-bg dark (#452323) measures
     only 2.59:1 -- fails even the 3:1 large-text floor (28px always
     qualifies as large text regardless of weight). This is the helper's
     "call the patient" CTA, a genuinely important action -- reuses the same
     brightened literal (7.26:1 on this exact composite, computed). */
  .big-phone { color: var(--brand-ink); border-color: var(--brand-ink); }
}

/* Login page */
.login-card { max-width: 420px; margin: 40px auto; text-align: center; }
.login-card #google-btn { display: flex; justify-content: center; margin: 20px 0; }
.login-error { color: var(--red); font-weight: 600; }

/* --- Mobile bottom thumb dock (fab.js) --- */
#fab-root { display: none; }

.dock-backdrop {
  position: fixed; inset: 0; background: rgba(0, 0, 0, 0.45);
  opacity: 0; pointer-events: none; transition: opacity 0.15s ease-in; z-index: 55;
}
.dock-backdrop.open { opacity: 1; pointer-events: auto; transition: opacity 0.2s ease-out; }

/* Fixed full-width bar, 5 equal columns: today, meds, +, history, more. Equal-fr
   columns (not eyeballed offsets) are what keep the + mathematically centered on
   the viewport regardless of screen width. */
.dock {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  display: grid; grid-template-columns: repeat(5, 1fr);
  min-height: var(--dock-h);
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-top: 1px solid var(--separator);
  box-shadow: var(--shadow-dock);
  padding-bottom: max(10px, env(safe-area-inset-bottom));
  margin: 0;
}
@media (prefers-color-scheme: dark) {
  .dock { background: rgba(28, 28, 30, 0.75); }
}
/* Solid fallback for browsers that support neither the unprefixed nor
   -webkit-prefixed backdrop-filter (older Safari/iOS shipped only the
   -webkit- form for years, so testing the unprefixed property alone in
   @supports would wrongly fall back on those -- check both). Both bars
   share the same recipe, so one rule covers them. */
@supports not ((backdrop-filter: blur(20px)) or (-webkit-backdrop-filter: blur(20px))) {
  header, .dock { background: var(--card); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

.dock-item {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; width: 100%; min-height: 48px; padding: 8px 4px 6px;
  border: none; background: none; cursor: pointer; font: inherit;
  /* AA (task 6 sweep): var(--muted) on the glass dock composite measures the
     same ~3.3-3.6:1 the header already documented and fixed for its own
     glass text. Same literal value, reused directly via the global
     --muted-on-card token instead of re-declaring a scoped one. */
  color: var(--muted-on-card);
}
/* :active press state (spec §8 audit): dock items had NO pressed feedback
   at all before this -- unlike .dock-plus/.chip/.day-picker/.wizard-card,
   which already had a scale or brightness cue from earlier reskin tasks.
   Now load-bearing (not just nice-to-have) since -webkit-tap-highlight-color
   is suppressed globally above -- without this, tapping a dock item would
   give zero visual feedback. Background fill (not scale, unlike the other
   dock control .dock-plus) since dock items are a fixed grid column --
   scaling one down would visibly wobble the row; a soft fill reads as
   "pressed" without disturbing layout, matching the row-action idiom. */
.dock-item:active {
  background: var(--fill-gray);
  border-radius: var(--radius-s);
}
.dock-item.active { color: var(--brand); }
.dock-icon {
  width: 25px; height: 25px; display: grid; place-items: center; line-height: 1;
}
.dock-icon .app-icon { width: 24px; height: 24px; }
.dock-item.active .app-icon { stroke-width: 2.35; }
.dock-plus .app-icon { width: 28px; height: 28px; }
.dock-label { font-size: 13px; font-weight: 600; }
.dock-item.active .dock-label { font-weight: 800; }
@media (prefers-color-scheme: dark) {
  /* var(--brand) doesn't change in dark mode, but --card does, which drops the
     active dock label to ~3:1 against the dark dock bar (same issue already
     fixed above for .status-pill.taken). Reuse the same brightened green. */
  .dock-item.active { color: var(--brand-ink); }
}
/* Narrowest supported phones (320px, e.g. iPhone SE 1st-gen / small Android): 13px
   labels wrap on the longer Spanish strings ("Medicinas", "Historial") inside a
   64px-ish grid column, which would grow the dock past its fixed --dock-h and
   overlap page content. Shrink + stop wrapping instead. */
@media (max-width: 359.98px) {
  .dock-label { font-size: 12px; white-space: nowrap; letter-spacing: -0.1px; }
}

/* Center + : 56px circle, popped up above the bar, ringed in the page
   background so it reads punched-through rather than embedded in the dock. */
.dock-plus-slot { display: flex; align-items: flex-start; justify-content: center; }
.dock-plus {
  width: 56px; height: 56px; margin-top: -24px; border-radius: 50%;
  border: 4px solid var(--bg); background: var(--brand-grad); color: #fff;
  font-size: 28px; line-height: 1; display: flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-raised); cursor: pointer;
}
.dock-plus:active { filter: brightness(0.94); }

/* More panel (right-anchored) and + panel (center-anchored): opaque card,
   scale+fade from its anchor. Centering and the scale+translateY enter/leave
   animation both live on the SAME element (.dock-panel-anchor), composed into one
   `transform` value (e.g. `translateX(-50%) scale(1) translateY(0)` for the + case)
   — .dock-panel itself carries no transform at all. */
.dock-panel-anchor {
  position: fixed; bottom: calc(var(--dock-h) + max(10px, env(safe-area-inset-bottom)) + 8px);
  z-index: 65; opacity: 0; pointer-events: none;
  transition: opacity 0.15s ease-in, transform 0.15s ease-in;
}
.dock-panel-anchor.open { opacity: 1; pointer-events: auto; transition: opacity 0.2s ease-out, transform 0.2s ease-out; }
.dock-panel-anchor.more {
  right: 12px; transform: scale(0.92) translateY(8px); transform-origin: bottom right;
}
.dock-panel-anchor.more.open { transform: scale(1) translateY(0); }
.dock-panel-anchor.plus {
  left: 50%; transform: translateX(-50%) scale(0.92) translateY(8px); transform-origin: bottom center;
}
.dock-panel-anchor.plus.open { transform: translateX(-50%) scale(1) translateY(0); }

.dock-panel {
  background: var(--card); border-radius: 16px; min-width: 220px; padding: 8px;
  box-shadow: var(--shadow-card), var(--shadow-panel);
}
.dock-panel-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 48px; padding: 6px 10px; border: none; border-radius: 10px;
  background: none; color: var(--text); font: inherit; text-align: left; cursor: pointer;
}
/* :active added (spec §8 audit) alongside the pre-existing :hover/
   :focus-visible -- hover doesn't fire on touch, so panel rows had no
   pressed feedback on a phone before this. */
.dock-panel-row:hover, .dock-panel-row:focus-visible, .dock-panel-row:active { background: var(--neutral-bg); }
.dock-panel-icon {
  width: 40px; height: 40px; flex-shrink: 0; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  background: var(--neutral-bg); color: var(--brand-ink);
}
.dock-panel-icon .app-icon { width: 23px; height: 23px; }
.dock-panel-label { font-size: 17px; font-weight: 600; }

/* Breakpoint swap: dock below 768px, tab bar above. */
@media (max-width: 767.98px) {
  nav#tabs { display: none; }
  #fab-root { display: block; }
  /* keep the dock clear of the last card: dock height + safe area + 16px breathing room */
  body:has(#fab-root) main { padding-bottom: calc(var(--dock-h) + max(10px, env(safe-area-inset-bottom)) + 16px); }
}

/* Today-tab "earlier today" divider between actionable and passed doses --
   spec §6: reuses .group-title's exact type treatment (14px/600/uppercase/
   var(--muted-on-card), same AA-safe literal) instead of its own bespoke
   14px/700 var(--muted) style (which also failed AA -- 3.33-3.62:1 light,
   same gap as .detail/.hint above). Kept centered (its own rule, not the
   `.group-title` class itself) since this divider sits between two stacks of
   dose cards rather than above a `.group` box, so group-title's left-aligned
   16px inset margin doesn't apply here. */
.section-divider {
  text-align: center; color: var(--muted-on-card);
  font-size: 14px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  margin: 24px 0 8px;
}

/* --- Medicine-name suggestions dropdown (public/suggest.js, spec:
   docs/superpowers/specs/2026-07-12-medicine-name-suggestions-design.md) ---
   Absolutely positioned INSIDE the Name `.row` (`.row` is already
   `position: relative`, set above) so it overlays whatever is below it
   instead of pushing content down -- opening/closing it causes zero layout
   shift. Left/right 0 land on the row's padding-box edges (the containing
   block for an absolutely-positioned descendant of a `position: relative`
   box), i.e. the same 16px inset the row's own label/input already sit at.

   `.group` clips its children to its rounded corners via `overflow: hidden`
   (see `.group` above) -- which would also clip this dropdown, since the
   row it's positioned against is a descendant of `.group`. Rather than
   portal the dropdown to <body> (its own position-math + scroll/resize
   sync, on a page with a sticky header -- more moving parts for the same
   result), the `:has()` rule below switches the one `.group` that can ever
   contain `.suggest-list` (the Medicine group) to `overflow: visible`.
   NOTE this is NOT conditional on the dropdown being open: `suggest.js`'s
   `hide()` only empties/hides the `<ul class="suggest-list">` (toggles the
   `hidden` class + clears `innerHTML`), it never removes the node from the
   DOM. The node is created once on script load and stays inserted for the
   page's entire lifetime, so `:has(.suggest-list)` matches unconditionally
   from first paint onward -- functionally equivalent to just leaving this
   one `.group` as `overflow: visible` permanently. Every other `.group`
   (which can never contain `.suggest-list`) keeps the normal corner-clip.
   `:has()` is already an established pattern in this file
   (`body:has(#fab-root)` above), so no new browser-support surface. */
.group:has(.suggest-list) {
  overflow: visible;
}
.suggest-list {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 20;
  margin: 6px 0 0;
  padding: 8px 0;
  list-style: none;
  background: var(--card);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-raised);
  max-height: 60vh;
  overflow-y: auto;
}
/* Same 14px/600/uppercase/var(--muted-on-card) group-title treatment used
   elsewhere in this file (`.group-title`, `.section-divider`) -- reused
   literally per spec ("14px group-title style"). */
.suggest-header {
  padding: 4px 16px 8px;
  font-size: 14px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted-on-card);
}
.suggest-option {
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: 0 16px;
  font-size: 18px;
  color: var(--text);
  cursor: pointer;
}
.suggest-option.suggest-highlighted,
.suggest-option:active {
  background: var(--fill-gray);
}

/* --- Global prefers-reduced-motion audit (spec §2b Task-6 scope) ----------
   Every `transition`/`animation` declaration the reskin added was grepped
   (header padding/fade, progress-fill width, .btn/.chip/.day-picker/
   .wizard-card press-scale, .segment active-state, the dock backdrop fade
   and dock-panel open/close scale+fade+slide) -- previously only two of
   those spots (header, .segment) had their own scoped reduced-motion
   override, and the rest (dock backdrop/panels, all the `:active { scale
   (0.98) }` press cues, progress-fill) had none. Consolidated into ONE rule
   here instead of N scoped ones, per spec ("add once, globally"): near-zero
   duration rather than `transition: none` so elements still resolve to
   their end state (and any future transitionend-dependent code keeps
   firing) -- same technique as the widely-used a11y-css-reset snippet. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.001ms !important;
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* Responsive page-level overrides intentionally last in the cascade. */
@media (max-width: 767.98px) {
  .modal { align-items: flex-end; padding: 0; }
  .modal-body {
    width: 100%; max-width: none; max-height: 88vh; margin: 0;
    border-radius: 22px 22px 0 0;
    padding-bottom: calc(22px + env(safe-area-inset-bottom));
  }
  #modal-close { position: sticky; top: 0; z-index: 2; float: none; margin-left: auto; }
}
@media (min-width: 900px) {
  #tab-meds #meds-list,
  #history-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; align-items: start; }
  #tab-meds #meds-list .card { margin-bottom: 0; }
  #history-list .history-day { margin-bottom: 0; }
  #tab-meds,
  #tab-history { width: var(--desktop-content-width); margin-left: 50%; transform: translateX(-50%); }
  #tab-settings { max-width: 700px; margin: 0 auto; }
}

.settings-section .routine-group input[type="time"] {
  width: 156px;
  min-width: 156px;
  max-width: 156px;
  margin: 0;
  padding-left: 8px;
  padding-right: 4px;
  text-align: left;
  text-align-last: left;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 390px) {
  .settings-section .routine-group input[type="time"] { width: 148px; min-width: 148px; max-width: 148px; }
}
