/* VialMath — dark, dense, cockpit-style. Mobile-first, one-handed use. */

/* Self-hosted (no network call — local-first) IBM Plex Mono, matching the
   marketing site's brand mono. Used for every NUMBER in the app via --font-
   mono + tabular-nums, so figures never visually jitter/misalign. */
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("./fonts/IBMPlexMono-500.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("./fonts/IBMPlexMono-600.woff2") format("woff2");
}
/* Space Grotesk (design pass, 2026-08-29) — the OTHER brand typeface per
   brand/vialmath/DESIGN-SPEC.md ("Space Grotesk / IBM Plex Mono only", no
   third font introduced). Self-hosted variable font, same file the video
   engine already renders with (video-engine/public/fonts/SpaceGrotesk-
   variable.woff2, copied here byte-for-byte) — one brand asset, two
   surfaces, and no Google Fonts network call (keeps the Condition 12
   privacy claim true: the only network call anywhere in this app is the
   license check). Weight range covers 400 body / 500 labels / 600
   headings/nav / 700 hero, exactly as the spec's type table lists them. */
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("./fonts/SpaceGrotesk-variable.woff2") format("woff2");
}

:root {
  --bg: #0b0d10;
  /* Graphite surface, ~92% opaque over the textured body background
     (2026-08-27) — matches brand/vialmath/DESIGN-SPEC.md's #15191E
     graphite almost exactly (was solid #14171c). Every card/panel using
     this stays legible over app-bg.jpg because 84% opacity is still
     overwhelmingly the graphite color, not the texture. */
  --bg-panel: rgba(21, 25, 30, 0.84); /* 0.92 -> 0.84 (CEO 2026-08-29: texture was too faint) */
  --bg-panel-raised: #1b1f26;
  --border: #2a2f38;
  --text: #e6e9ee;
  --text-dim: #9aa3b2;
  --text-faint: #666e7c;
  /* Brand palette (2026-08-27): #0B0D10 / #3D7EFF — matches the marketing
     site's --bg/--accent exactly, so the app and the site read as one brand. */
  --accent: #3d7eff;
  --accent-dim: #1d3d80;
  --ok: #34c77b;
  --warn: #e0a531;
  --danger: #e0553f;
  /* Space Grotesk first, same system stack as before as the fallback while
     the self-hosted woff2 loads (font-display: swap covers that gap). */
  --font: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, Menlo, Consolas, monospace;
  --tap-min: 48px;

  /* ---- 8px spacing grid (design pass 2026-08-29, DESIGN-SPEC's "8px base
     grid") — used by NEW components (syringe ruler, the two card layouts,
     view-hero bands) below. Existing spacing values are left as they are;
     this is additive, not a repaint of every rule in the file. ---- */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;

  /* ---- Type scale (design pass 2026-08-29) — one named step per role, so
     "the single most important number on a screen" has one obvious class
     to reach for instead of a bespoke font-size on each new element. ---- */
  --fs-hero: 2.1rem;    /* the syringe ruler's own big value; the biggest number on any screen */
  --fs-xl: 1.375rem;    /* dose/prep draw numbers — the number a row exists to show */
  --fs-lg: 1.05rem;     /* card titles, row names */
  --fs-base: 0.95rem;
  --fs-sm: 0.82rem;
  --fs-xs: 0.72rem;     /* eyebrows, tags, mono labels */
}

* { box-sizing: border-box; }

/* Every NUMBER in the app — draw units, mg, dates in stat rows, counts —
   goes through this class or one of the selectors below it, per the brand
   rule "every number IBM Plex Mono tabular". */
.num, .mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
}
.dose-units, .stat-value, .stat-tile-value, .gate-date, .metric-row span:first-child,
.metric-row span:nth-child(2), .vial-history-row, .prep-units, .csv-preview-row,
.calendar-day-num, .print-row-value, .print-generated {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
}

/* The app toggles visibility everywhere via the plain HTML `hidden`
   attribute (view sections, the first-run modal, the conditional schedule
   sub-fields, the CSV mapping panel, etc). Several component classes below
   (.field, .field-row, .modal-overlay, ...) set their own `display` value,
   which — at equal selector specificity — wins the cascade over the UA
   stylesheet's `[hidden] { display: none }` because author rules apply
   after the UA sheet. This single high-specificity rule restores `hidden`
   as an absolute "not displayed" regardless of what else targets the
   element, which is the only sane semantics for an attribute named
   `hidden`. Found live: without this, the first-run modal and the
   schedule sub-fields rendered visible even while `hidden` was true. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  /* Textured instrument-panel background (2026-08-27). Solid --bg color
     FIRST as the fallback layer (shows instantly, and stays the paint if
     the image fails/is slow), the image on top, fixed so it doesn't
     scroll with content, cover so it never tiles or letterboxes. Cards
     (.card-form, .dash-card, dose/compound/vial/gate cards, stat-tiles)
     all paint their own ~92%-opaque graphite surface on top, so body text
     never sits directly on the texture — see the legibility note on
     .dash-card below. */
  background: var(--bg) url('./assets/app-bg.jpg?v=32') center center / cover no-repeat fixed;
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.4;
  -webkit-tap-highlight-color: transparent;
}

#app-root {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.app-header {
  padding: env(safe-area-inset-top, 0) 16px 10px;
  padding-top: calc(env(safe-area-inset-top, 0) + 14px);
  background: var(--bg-panel);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.app-title {
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--text);
}

/* Small "?" affordance (spec item 2) — opens the Guide from anywhere. */
.header-help-btn {
  width: 32px;
  height: 32px;
  min-height: 0;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg-panel-raised);
  color: var(--text-dim);
  font-family: var(--font);
  font-weight: 700;
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.header-help-btn:active { opacity: 0.75; }

.app-main {
  flex: 1;
  padding: 12px 12px 96px;
  max-width: 640px;
  width: 100%;
  margin: 0 auto;
}

.view-title {
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin: 4px 0 12px;
}

.section-title {
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-faint);
  margin: 18px 0 8px;
}

/* Gate checks due, shown at the very top of Today — the hero feature
   gets a visually distinct heading, not just another section-title. */
.section-title-hero {
  font-size: 0.95rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--warn);
  margin: 0 0 8px;
}

/* ---- tab bar (nav rework 2026-08-26/27: 4 primary thumb-reachable tabs
   — Prep/Calendar/Compounds/Metric — plus a 5th "More" button that opens
   the slide-up drawer holding every other view, grouped. No more
   horizontal scrolling: 5 buttons fit at any phone width. ---- */

.tab-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  background: var(--bg-panel);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom, 0);
  z-index: 5;
}

.tab-btn {
  flex: 1 1 0;
  min-width: 0;
  max-width: 100%;
  background: none;
  border: none;
  color: var(--text-faint);
  font-family: var(--font);
  font-size: 0.66rem;
  letter-spacing: 0.01em;
  padding: 10px 3px;
  min-height: var(--tap-min);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Bug fix 2026-08-27: at 5 tabs (Dashboard/Calendar/Compounds/Metric/More)
     the widest labels (Dashboard, Compounds) at the old 0.72rem/10px-6px
     sizing were wider than their 1/5 share of a 360-390px viewport. flex
     shrinking alone doesn't shrink the TEXT, so it visually bled past the
     button's own box — invisible to scrollWidth checks because .tab-bar is
     position:fixed (fixed elements don't contribute to document scroll
     width at all), but clipped by the viewport itself. Smaller font/padding
     fits all 5 labels in full at both widths; overflow:hidden +
     text-overflow:ellipsis is a safety net for any future label change,
     so it truncates cleanly instead of bleeding into the next tab. */
}

.tab-btn.active {
  color: var(--accent);
  font-weight: 700;
}

/* ---- cards / rows ---- */

.dose-card, .compound-card, .vial-card, .gate-card {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
  margin-bottom: 10px;
}

.dose-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}

.dose-info { flex: 1; min-width: 160px; }

.dose-name { font-weight: 700; font-size: var(--fs-lg); }
.dose-phase { color: var(--text-dim); font-size: 0.8rem; margin-top: 2px; }
/* The draw number IS the reason this row exists — the single most
   important figure on this screen gets the biggest type on it. */
.dose-units { color: var(--accent); font-size: var(--fs-xl); font-weight: 700; margin-top: 6px; }

.dose-actions {
  display: flex;
  gap: 8px;
  align-items: center;
}

.btn {
  font-family: var(--font);
  border: 1px solid var(--border);
  background: var(--bg-panel-raised);
  color: var(--text);
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 0.9rem;
  cursor: pointer;
  min-height: var(--tap-min);
}

.btn:active { opacity: 0.75; }

.btn-tap {
  min-width: 84px;
  min-height: var(--tap-min);
  font-weight: 700;
  font-size: 0.95rem;
}

.btn-done { background: #163d2a; border-color: #24603f; color: var(--ok); }
.btn-skip { background: #3d2416; border-color: #60401f; color: var(--warn); }

.btn-primary { background: var(--accent-dim); border-color: var(--accent); color: #eaf6ff; font-weight: 700; }
.btn-ghost { background: transparent; }
.btn-danger { background: #3d1a16; border-color: #602820; color: var(--danger); }
.btn-small { min-height: 40px; padding: 7px 12px; font-size: 0.82rem; }

.status-pill {
  padding: 6px 10px;
  border-radius: 20px;
  font-size: 0.8rem;
  font-weight: 700;
}
.status-done { background: #163d2a; color: var(--ok); }
.status-skipped { background: #3d2416; color: var(--warn); }

.empty-state { color: var(--text-faint); font-size: 0.9rem; padding: 8px 0; }

.banner {
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 10px;
  font-size: 0.88rem;
  border: 1px solid var(--border);
}
.banner-cycle { background: #142534; border-color: #234a68; color: #bfe3ff; }
.banner-gate { background: #2a2416; border-color: #5c4d1f; color: #f0d98a; }
.banner-warn { background: #3d2416; border-color: #60401f; color: var(--warn); margin-top: 8px; }

/* ---- compound card ---- */

.compound-card-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}
.tag {
  background: var(--bg-panel-raised);
  border: 1px solid var(--border);
  color: var(--text-dim);
  border-radius: 12px;
  padding: 3px 9px;
  font-size: 0.72rem;
}
.compound-card-body { color: var(--text-dim); font-size: 0.85rem; margin-top: 6px; display: flex; flex-direction: column; gap: 2px; }
.compound-card-actions { display: flex; gap: 8px; margin-top: 10px; }
.compound-card-tags { color: var(--accent); font-size: 0.75rem; margin-top: 4px; font-weight: 700; }

/* ---- PRN / on-demand section (v1 final round #4) ---- */

.prn-section {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
  margin-bottom: 16px;
}
.prn-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 0;
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
}
.prn-row:first-of-type { border-top: none; }
.prn-row-info { flex: 1; min-width: 140px; }

/* ---- forms ---- */

.card-form {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.card-form h3 {
  margin: 0 0 2px;
  font-size: 0.95rem;
  color: var(--text);
}

/* The backup action is deliberately the most prominent card on the Data
   view — an accent border sets it apart from the other (equally useful,
   but secondary) data actions below it. */
.backup-card {
  border-color: var(--accent);
  border-width: 2px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
  min-width: 0;
}

.field > span {
  font-size: 0.75rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.field-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

/* Planned start date (2026-08-27) — the field that drives Calendar/Prep
   entry, called out from the pack so it doesn't read as just another date
   field. Emphasis only (accent label + left rule) — same input, same
   behavior, just harder to miss. */
.field-emphasis > span {
  color: var(--accent);
  font-weight: 700;
}
.field-emphasis {
  border-left: 2px solid var(--accent);
  padding-left: 10px;
}

/* No-reconstitution checkbox (v1 final round #3) — a checkbox field reads
   as flex-column via .field, which stacks the box above its label; this
   override keeps it a single inline row like every other checkbox label
   in the app (weekday grid, etc). */
.field-checkbox {
  flex-direction: row;
  align-items: center;
  gap: 8px;
}
.field-checkbox input[type="checkbox"] { width: 20px; height: 20px; flex-shrink: 0; }
.field-checkbox > span { text-transform: none; font-size: 0.9rem; letter-spacing: normal; }

input[type="text"], input[type="number"], input[type="date"], select, textarea {
  background: var(--bg-panel-raised);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  padding: 10px;
  font-family: var(--font);
  font-size: 0.95rem;
  min-height: var(--tap-min);
  width: 100%;
}

textarea { min-height: 60px; resize: vertical; }

.field-note {
  font-size: 0.78rem;
  color: var(--text-faint);
  margin: 0;
}

/* Persistent on-screen privacy claim (focus-group finding #6, 2026-08-27) —
   true by absence since v1, now actually stated where a first-time visitor
   looks: the Dashboard footer and the Data tab header. Exact wording is
   COUNSEL-CONDITIONS.md condition 12; never paraphrase or broaden it. */
.privacy-line {
  font-size: 0.78rem;
  color: var(--text-dim);
  border-top: 1px solid var(--border);
  margin: 14px 0 0;
  padding-top: 10px;
}

.schedule-sub {
  border-top: 1px dashed var(--border);
  padding-top: 10px;
}

.weekday-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(64px, 1fr));
  gap: 6px;
}
.weekday-grid label {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--bg-panel-raised);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px;
  font-size: 0.85rem;
  min-height: var(--tap-min);
}

.chip-list { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.chip {
  background: var(--bg-panel-raised);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 4px 4px 4px 10px;
  font-size: 0.8rem;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.chip-x {
  background: none;
  border: none;
  color: var(--text-faint);
  font-size: 1rem;
  cursor: pointer;
  padding: 4px 8px;
}

.form-actions { display: flex; gap: 10px; flex-wrap: wrap; }

.file-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.csv-header-toggle { flex-direction: row; align-items: center; gap: 8px; }
.csv-header-toggle input { width: auto; min-height: auto; }

/* ---- vial card ---- */

.vial-card-body { display: flex; flex-direction: column; gap: 4px; margin: 8px 0; }
.stat-row { display: flex; justify-content: space-between; font-size: 0.88rem; border-bottom: 1px dashed var(--border); padding: 4px 0; gap: 12px; }
.stat-label { color: var(--text-dim); }
.stat-value { font-weight: 600; text-align: right; }

.vial-card-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }

.vial-history { margin-top: 10px; font-size: 0.82rem; color: var(--text-dim); }
.vial-history summary { cursor: pointer; color: var(--text-dim); }
.vial-history-row { padding: 4px 0; border-bottom: 1px dashed var(--border); }

/* ---- gate checks (Today hero cards, Gates tab, Decisions tab all share
   this card styling via buildGateCard in app.js) ---- */

.gate-card.gate-due { border-color: var(--warn); border-width: 2px; }
.gate-date { color: var(--text-dim); font-size: 0.78rem; }
.gate-question { font-weight: 600; margin: 4px 0 8px; }
.gate-decision { color: var(--text); font-size: 0.88rem; }
.gate-closed-badge { color: var(--ok); font-weight: 700; margin-right: 4px; }
.gate-timestamp { color: var(--text-faint); font-size: 0.75rem; margin-top: 2px; }
.gate-card textarea { margin-bottom: 8px; }

/* ---- CSV import preview ---- */

.csv-preview { margin: 4px 0; }
.csv-preview-row {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 0.78rem;
  color: var(--text-dim);
  padding: 4px 0;
  border-bottom: 1px dashed var(--border);
  white-space: nowrap;
  overflow-x: auto;
}

/* ---- metric ---- */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 12px;
}
.stat-tile {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
  text-align: center;
}
.stat-tile-label { font-size: 0.68rem; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.03em; }
.stat-tile-value { font-size: 1.05rem; font-weight: 700; color: var(--accent); }

.sparkline-host {
  margin-top: 12px;
  color: var(--accent);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px;
}

.metric-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  padding: 8px 4px;
  border-bottom: 1px dashed var(--border);
  font-size: 0.9rem;
}

/* ---- first-run modal ---- */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
  padding: 20px;
}
/* Hiding handled by the global [hidden] rule near the top of this file. */
.modal-box {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 20px;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.first-run-text { font-size: 1rem; font-weight: 600; margin: 0; }
.first-run-subtext { font-size: 0.85rem; color: var(--text-dim); margin: 0; }

/* ---- license gate: activation bar + activation modal (2026-08-27) ----
   LICENSE-GATE-SPEC.md. The bar sits between the header and the tab
   content so it stays visible across every view while in demo mode; the
   modal reuses .modal-overlay/.modal-box exactly like the first-run and
   setup modals above. */
.activation-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  padding: 8px 16px;
  background: var(--accent-dim);
  border-bottom: 1px solid var(--accent);
  color: #eaf6ff;
  font-size: 0.85rem;
  position: sticky;
  top: 0;
  z-index: 4;
}
.activation-bar-text { flex: 1 1 auto; font-weight: 600; }
.activation-bar-buy-link {
  color: #eaf6ff;
  font-size: 0.8rem;
  text-decoration: underline;
  white-space: nowrap;
}

.activation-modal-title { font-size: 1.05rem; font-weight: 700; margin: 0; }
.activation-modal-box { max-width: 380px; }
.activation-error {
  font-size: 0.82rem;
  color: var(--danger);
  margin: 0;
}
.activation-buy-link {
  font-size: 0.82rem;
  color: var(--text-dim);
  text-decoration: underline;
  text-align: center;
}
/* Small inline spinner shown on the Activate button while verifyKey() is
   pending — plain CSS, no image asset. */
.activation-spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  margin-left: 8px;
  border: 2px solid rgba(234, 246, 255, 0.35);
  border-top-color: #eaf6ff;
  border-radius: 50%;
  vertical-align: middle;
  animation: activation-spin 0.7s linear infinite;
}
@keyframes activation-spin {
  to { transform: rotate(360deg); }
}

@media (min-width: 640px) {
  .tab-btn { font-size: 0.8rem; }
}

/* =====================================================================
   DASHBOARD (2026-08-27) — instrument-style card grid, the app's default
   landing view. 2-up on phone, more columns as width allows; the hero
   (Today) card always spans the full row. Every number in a card goes
   through .num (IBM Plex Mono, tabular) per brand/vialmath/DESIGN-SPEC.md.
   ===================================================================== */

.dash-grid {
  display: grid;
  /* Bug fix 2026-08-27: plain `1fr` is shorthand for `minmax(auto, 1fr)` —
     the `auto` minimum means a track refuses to shrink below the
     MIN-CONTENT size of whatever is inside it (e.g. a long unbroken
     "153.0 lbs -> 130.0 lbs" value line), so at 390px the two columns
     could add up to MORE than the viewport and clip the right one, even
     though every card already had min-width:0 on itself — that only
     bounds the card, not the grid track it sits in. minmax(0, 1fr) is
     the actual fix: it lets each track shrink to 0 before growing
     proportionally, so the grid always fits the container exactly. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-top: 8px;
  width: 100%;
  max-width: 100%;
}

@media (min-width: 640px) {
  .dash-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 900px) {
  .dash-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.dash-card {
  background: var(--bg-panel); /* ~92% opaque graphite — legible over the textured body */
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  max-width: 100%;
  overflow-wrap: anywhere;
}

.dash-card-hero {
  grid-column: 1 / -1;
  max-width: 100%;
}

.dash-card-label {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  margin-bottom: 2px;
}

.dash-card-subhead {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  margin-top: 8px;
}

.dash-value {
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--text);
  line-height: 1.2;
  word-break: break-word;
  overflow-wrap: anywhere;
  max-width: 100%;
}
.dash-card-hero .dash-value { font-size: var(--fs-xl); color: var(--accent); }

.dash-line {
  font-size: 0.85rem;
  color: var(--text-dim);
  line-height: 1.35;
  overflow-wrap: anywhere;
  max-width: 100%;
}
.dash-line-muted { color: var(--text-faint); font-size: 0.78rem; }
.dash-flag-text { color: var(--danger); }

.dash-empty {
  color: var(--text-faint);
  font-size: 0.82rem;
  padding: 2px 0;
}

.dash-progress-track {
  height: 6px;
  border-radius: 3px;
  background: var(--bg-panel-raised);
  overflow: hidden;
  margin-top: 4px;
}
.dash-progress-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 3px;
}

.dash-sparkline {
  color: var(--accent);
  margin-top: 4px;
}

.dash-quick-actions {
  /* Bug fix 2026-08-27: flex:1 1 auto items default to min-width:auto
     (content width), so three buttons whose combined label width exceeded
     the viewport did not reliably wrap before overflowing at 390px ("Add
     compound" -> "Add c" cut off at the edge). A 3-col grid with
     minmax(0,1fr) tracks (same fix as .dash-grid above) guarantees all
     three always fit the row width exactly, wrapping their own label text
     onto a second line if a track gets too narrow rather than overflowing. */
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin-top: 12px;
  max-width: 100%;
}
.dash-quick-actions .btn {
  min-width: 0;
  white-space: normal;
  line-height: 1.2;
}

.dash-details-heading {
  margin-top: 20px;
}

/* ---- backup nudge banner (Today view) ---- */

.banner-backup {
  background: #2a2416;
  border-color: #5c4d1f;
  color: #f0d98a;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.banner-backup-actions { display: flex; gap: 8px; flex-shrink: 0; }

/* ---- history view ---- */

.history-date-heading {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-faint);
  margin: 16px 0 6px;
}
.history-date-heading:first-child { margin-top: 0; }

/* ---- blends ---- */

.member-picker { display: flex; flex-direction: column; gap: 8px; margin-top: 6px; }
.member-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px;
}
.member-row label.member-check { display: flex; align-items: center; gap: 6px; font-weight: 600; flex: 1 1 auto; min-width: 120px; }
.member-row .member-dose-fields { display: flex; gap: 6px; align-items: center; }
.member-row .member-dose-fields input { width: 84px; }
.member-row .member-dose-fields select { width: 68px; }
.blend-card .compound-card-body { font-size: 0.85rem; }
.blend-member-list { color: var(--text-dim); font-size: 0.85rem; margin-top: 4px; }

/* ---- prep view (v1 feature 1) ---- */

.prep-toggle { display: flex; gap: 8px; margin-bottom: 4px; }
.prep-toggle-btn.active { background: var(--accent-dim); border-color: var(--accent); color: #eaf6ff; }
.prep-date-heading {
  font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-faint); margin: 12px 0 8px;
}
.prep-row {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
  margin-bottom: 8px;
}
/* 2026-08-29: the checkbox + text moved into their OWN inner label
   (.prep-row-check) so the per-row action buttons (.prep-row-actions) can
   sit as SIBLINGS, outside it — tapping "Mix new vial" or "Out of stock"
   must never also toggle the prep checklist item. */
.prep-row-check { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
.prep-row input[type="checkbox"] { width: 22px; height: 22px; margin-top: 2px; flex-shrink: 0; }
.prep-row-checked { opacity: 0.55; }
.prep-row-checked .prep-row-name { text-decoration: line-through; }
.prep-row-body { flex: 1; }
.prep-row-name { font-weight: 700; font-size: 1rem; display: flex; align-items: center; gap: 8px; }
.prep-kind-tag { text-transform: capitalize; }
.prep-detail { color: var(--text-dim); font-size: 0.85rem; margin-top: 2px; }
.prep-units { color: var(--accent); font-weight: 700; font-size: var(--fs-xl); }
.prep-dose-change { color: #d8c8ff; font-size: 0.78rem; margin-top: 3px; font-weight: 700; }
.prep-row-actions { display: flex; gap: 8px; padding-left: 32px; flex-wrap: wrap; }

/* ---- reconstitution planner card (inside Vials view) ---- */

.recon-card { border-color: var(--accent); }

.recon-recipes-heading { font-weight: 700; font-size: 0.95rem; margin: 16px 0 4px; }

.recon-recipe-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px dashed var(--border);
  flex-wrap: wrap;
}
.recon-recipe-row-meta { font-size: 0.82rem; color: var(--text-dim); }

/* ---- inventory / pack (shares .vial-card styling from above) ---- */

/* ---- calendar month view (v1 feature 5) ---- */

.calendar-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.calendar-month-label { font-weight: 700; font-size: 0.95rem; }
.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}
/* The empty-state hero (no compounds/blends/orals at all) replaces the
   whole grid for that render, so it must span all 7 columns rather than
   being squeezed into a single day-cell's width. */
.calendar-grid > .empty-hero { grid-column: 1 / -1; min-height: 220px; }
.calendar-weekday-label {
  text-align: center;
  font-size: 0.68rem;
  color: var(--text-faint);
  text-transform: uppercase;
  padding-bottom: 4px;
}
.calendar-cell {
  aspect-ratio: 1 / 1;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-family: var(--font);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding: 4px 2px;
  cursor: pointer;
  min-height: 44px;
}
.calendar-cell-empty { background: transparent; border-color: transparent; cursor: default; }
.calendar-cell-today { border-color: var(--accent); border-width: 2px; }
.calendar-cell-cycle-on { background: #142f22; }
.calendar-cell-cycle-off { background: #201418; }
.calendar-day-num { font-size: 0.82rem; }
.calendar-markers { display: flex; gap: 2px; flex-wrap: wrap; justify-content: center; margin-top: 2px; }
.cal-marker {
  font-size: 0.6rem;
  line-height: 1;
  border-radius: 4px;
  padding: 1px 3px;
  font-weight: 700;
}
.cal-marker-dose { background: var(--accent-dim); color: #eaf6ff; }
.cal-marker-gate { background: #5c4d1f; color: #f0d98a; }
.cal-marker-reorder { background: #60401f; color: var(--warn); }
.cal-marker-weighin { background: #24603f; color: var(--ok); }
.cal-marker-dosechange { background: #402f60; color: #d8c8ff; }
.calendar-day-detail {
  margin-top: 14px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
}

/* ---- labs (shares .gate-card styling; kind tag) ---- */

.gate-kind-tag { margin-left: 8px; }

/* ---- printable sheets (v1 feature 9) ----
   #print-root is hidden by the global [hidden] rule outside of printing.
   When body.print-mode is set (only for the duration of window.print()),
   everything else is hidden and only #print-root is shown/printed. */

.print-root {
  background: #fff;
  color: #000;
  padding: 24px;
}
.print-title { font-size: 1.3rem; margin: 0 0 4px; }
.print-generated { color: #444; font-size: 0.8rem; margin-bottom: 16px; }
.print-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid #ccc;
  font-size: 0.92rem;
}
.print-row-label { color: #000; }
.print-row-value { font-weight: 600; }

body.print-mode > #app-root,
body.print-mode > .tab-bar { display: none !important; }
body.print-mode #print-root { display: block !important; }

@media print {
  body.print-mode { background: #fff; }
  #print-root { display: block !important; }
  #app-root, .tab-bar { display: none !important; }
}

/* =====================================================================
   GUIDE — numbered how-to steps (CEO addition 2026-08-26/27). Plain
   readable list styling, no card chrome — it's a document, not a form.
   ===================================================================== */

.guide-list {
  margin: 0;
  padding-left: 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  font-size: 0.92rem;
  line-height: 1.5;
}
.guide-list > li strong { color: var(--text); }
.guide-sublist {
  margin: 8px 0 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 0.88rem;
  color: var(--text-dim);
}
.guide-sublist strong { color: var(--text); }

/* =====================================================================
   EMPTY-STATE HERO — cinematic backdrop shown instead of blank space
   wherever a view has no data yet (CEO addition 2026-08-26/27). Dimmed
   brand scene stills under a scrim so text stays legible in either scene.
   The plain `.empty-state` class above stays as-is for small in-card
   notes ("No vial on record", etc) — this is only for whole-view empties.
   ===================================================================== */

.empty-hero {
  position: relative;
  min-height: 260px;
  border-radius: 12px;
  border: 1px solid var(--border);
  overflow: hidden;
  margin: 8px 0 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-size: cover;
  background-position: center;
  background-color: var(--bg-panel);
}
.empty-hero-desk { background-image: url('./assets/scene-desk.jpeg'); }
.empty-hero-gym { background-image: url('./assets/scene-gym.jpeg'); }

/* Scrim strength is DERIVED from the image's measured brightness, never a
   guessed constant (Priority 3, design pass 2026-08-29) — see app.js's
   scrimStops()/IMAGE_BRIGHTNESS (same principle as video-engine's
   CinematicBg.tsx autoDim: a dark image and a bright image land at the
   same text contrast). --scrim-top/--scrim-bottom are set inline per
   element by JS; the values here are the safe fallback (this file's own
   floor) so nothing looks broken before/without that JS running. */
.empty-hero-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(11,13,16,var(--scrim-top, 0.6)) 0%, rgba(11,13,16,var(--scrim-bottom, 0.88)) 100%);
}

.empty-hero-content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
  padding: 28px 24px;
  max-width: 320px;
}

.empty-hero-text {
  margin: 0;
  color: var(--text);
  font-weight: 600;
  font-size: 0.95rem;
  text-shadow: 0 1px 6px rgba(0,0,0,0.5);
}

/* =====================================================================
   CINEMATIC SPLASH — full-bleed cold-open (CEO addition 2026-08-26/27).
   Shown once per browser session (app.js gates it via sessionStorage),
   fades to the app after ~1.2s or a single tap. Highest z-index in the
   app so it sits above everything, including the modals underneath.
   ===================================================================== */

.splash-screen {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg) url('./assets/splash-bg.png') center / cover no-repeat;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 1;
  transition: opacity 0.45s ease;
}
.splash-screen.splash-fade-out {
  opacity: 0;
  pointer-events: none;
}
/* splash-bg.png measures 12.0 mean luminance (tools/measure_brightness.py);
   run through the same scrimStops() formula as every other scrim in this
   file (app.js), that's top 0.61 / bottom 0.89 — frozen here as static
   values (rather than set via JS) only because this element paints before
   app.js has run at all (see index.html's inline splash-skip script). */
.splash-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(11,13,16,0.61) 0%, rgba(11,13,16,0.89) 100%);
}
.splash-content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}
.splash-mark {
  width: 88px;
  height: 88px;
  border-radius: 50%;
}
.splash-wordmark {
  font-family: var(--font);
  font-weight: 700;
  font-size: 1.6rem;
  letter-spacing: 0.01em;
  color: var(--text);
}
.splash-datum {
  width: 64px;
  height: 3px;
  background: var(--accent);
}
.splash-skip-hint {
  position: absolute;
  z-index: 1;
  bottom: calc(env(safe-area-inset-bottom, 0) + 24px);
  left: 0;
  right: 0;
  text-align: center;
  color: var(--text-faint);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* =====================================================================
   FIRST-RUN SETUP MODAL — reuses .modal-overlay/.modal-box (first-run
   notice) and .field/.field-row/.form-actions (every form in the app).
   ===================================================================== */

.setup-modal-box {
  max-width: 460px;
  max-height: 88vh;
  overflow-y: auto;
}
.setup-modal-title {
  margin: 0;
  font-size: 1.05rem;
}

/* =====================================================================
   NAV DRAWER ("More" sheet) — CEO menu-bar rework 2026-08-26/27. Bottom
   nav keeps 4 primary tabs + More; everything else lives here, grouped,
   slid up from the bottom so it stays thumb-reachable.
   ===================================================================== */

.nav-drawer-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.6);
  z-index: 40;
}

.nav-drawer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 41;
  max-height: 72vh;
  background: var(--bg-panel);
  border-top: 1px solid var(--border);
  border-radius: 16px 16px 0 0;
  padding-bottom: env(safe-area-inset-bottom, 0);
  display: flex;
  flex-direction: column;
  box-shadow: 0 -8px 24px rgba(0,0,0,0.4);
}

.nav-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px 10px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.nav-drawer-title {
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--text);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.nav-drawer-body {
  overflow-y: auto;
  padding: 8px 16px 20px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.nav-drawer-group {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.nav-drawer-group-label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  margin: 6px 0 4px;
}

.menu-link {
  text-align: left;
  background: none;
  border: none;
  border-radius: 8px;
  color: var(--text);
  font-family: var(--font);
  font-size: 0.95rem;
  padding: 12px 10px;
  min-height: var(--tap-min);
  cursor: pointer;
}
.menu-link:active { background: var(--bg-panel-raised); }
.menu-link.active {
  color: var(--accent);
  font-weight: 700;
  background: var(--accent-dim);
}

/* =====================================================================
   SETTINGS VIEW (owner first-use feedback, 2026-08-28) — Simple/Full
   toggle reuses .prep-toggle/.prep-toggle-btn exactly as the Prep view's
   Today/Tomorrow toggle already does. Dashboard-card checklist is a plain
   vertical stack of .field-checkbox rows (same class the compound form's
   "No reconstitution" checkbox already uses).
   ===================================================================== */

.settings-checklist {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 8px;
}

/* ---- shopping list (Fix 4) — reuses .recon-recipe-row's row layout ---- */

.shopping-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px dashed var(--border);
  flex-wrap: wrap;
}
.shopping-row-meta { font-size: 0.82rem; color: var(--text-dim); }
/* Overdue uses the SAME .dash-flag-text color already used for an overdue
   reorder line on the Dashboard — a LOGISTICS DEADLINE (a date in the
   past), never a body metric or dose (COUNSEL-CONDITIONS #13 addendum).
   Compound selector, not just `.dash-flag-text` alone: that rule sits
   EARLIER in this file (near the Dashboard section) than the plain
   `.shopping-row-meta` color above, so on a tied specificity the LATER
   rule (this file's own .shopping-row-meta) would otherwise win the
   cascade and silently swallow the red — caught by actually looking at a
   screenshot, not by any test (this is presentation CSS, nothing a
   PE.*-level unit test touches). The extra class in the selector raises
   specificity above either single-class rule, so this wins regardless of
   source order. */
.shopping-row-meta.dash-flag-text { color: var(--danger); }

/* ---- refill discoverability hint (Fix 6) ---- */

.vial-refill-hint { color: var(--accent); font-weight: 600; }

/* ---- guided first-compound wizard (Fix 1) — reuses .modal-overlay/
   .modal-box exactly like the first-run and setup modals. ---- */

.wizard-modal-box {
  max-width: 460px;
  max-height: 88vh;
  overflow-y: auto;
}
.wizard-progress {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-faint);
  margin-bottom: 4px;
}
.wizard-choice-row { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 8px; }
.wizard-choice-row .btn { flex: 1 1 auto; min-width: 140px; }
.wizard-review-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px dashed var(--border);
  font-size: 0.9rem;
}

/* ---- second metric series (Fix 7) — plain <details> disclosure ---- */

.metric2-details { margin-top: 20px; border-top: 1px solid var(--border); padding-top: 12px; }
.metric2-details summary { cursor: pointer; font-weight: 700; color: var(--text-dim); }

.recon-rate{font-size:1.05rem;color:var(--text,#F2F4F5);margin:0 0 6px;}
.recon-rate strong{font-family:var(--mono,monospace);color:var(--accent,#3D7EFF);}

.cwiz-error{margin:10px 0 0;padding:10px 12px;border-radius:8px;font-size:.92rem;color:#F05D68;background:rgba(240,93,104,.10);border:1px solid rgba(240,93,104,.35);}

/* CEO 2026-08-29 — mobile browser chrome overlapped the tab bar. env(safe-area-inset-bottom)
   is 0 in a browser tab (it only reports the home-indicator inset), so it did nothing about
   Safari/Chrome's own bottom toolbar. Add real clearance and pad the scroll container so the
   last row of content is never trapped underneath it. */
.tab-bar { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 12px); }
.app-main { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 96px); }

.header-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.header-home-btn {
  background: transparent; color: var(--text, #F2F4F5);
  border: 1px solid var(--border, rgba(242,244,245,.2));
  border-radius: 999px; padding: 6px 14px; font-size: .82rem; cursor: pointer;
  font-family: inherit; white-space: nowrap;
}
.header-home-btn:hover, .header-home-btn:focus-visible {
  border-color: var(--accent, #3D7EFF); color: var(--accent, #3D7EFF); outline: none;
}

/* =====================================================================
   VIEW HERO BAND (Priority 3, visual design pass 2026-08-29) — replaces
   the flat uppercase .view-title label on the primary screens with a
   proper header: a small mono eyebrow, the screen name in Space Grotesk,
   a datum-line accent rule (brand/vialmath/DESIGN-SPEC.md's recurring
   motif). Applied via upgradeViewHeroBands() in app.js (wraps each
   .view-title's existing text in child spans) — no index.html markup
   changes needed, and idempotent.

   Background is a pure CSS gradient in brand tokens — NO photo shipped in
   this pass; see the build report for exactly which images + dimensions
   would go here. --view-hero-image defaults to 'none' so the mechanism is
   inert until a real asset exists. When one is wired in, app.js's
   scrimStops() derives --scrim-top/--scrim-bottom from THAT image's own
   measured brightness — never a guessed constant, exactly like the
   empty-state hero above it in this file.
   ===================================================================== */
.view-title.view-hero {
  position: relative;
  overflow: hidden;
  margin: 0 0 var(--sp-4);
  padding: var(--sp-5) var(--sp-4) var(--sp-4);
  border-radius: 14px;
  border: 1px solid var(--border);
  background:
    var(--view-hero-image, none) center / cover no-repeat,
    linear-gradient(135deg, var(--bg-panel-raised) 0%, var(--bg) 130%);
  text-transform: none;
  letter-spacing: normal;
  color: var(--text);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
/* Scrim sits between the background image and the text — fully
   transparent (costs nothing) until JS sets --scrim-top/--scrim-bottom
   for a real photo. */
.view-title.view-hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(11,13,16,var(--scrim-top, 0)) 0%, rgba(11,13,16,var(--scrim-bottom, 0)) 100%);
  pointer-events: none;
}
.view-hero-eyebrow, .view-hero-title, .view-hero-datum { position: relative; z-index: 1; }
.view-hero-eyebrow {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-dim);
}
.view-hero-title {
  display: block;
  font-family: var(--font);
  font-weight: 700;
  font-size: var(--fs-xl);
  line-height: 1.15;
  margin-top: 2px;
}
.view-hero-datum {
  width: 56px;
  height: 3px;
  background: var(--accent);
  margin-top: var(--sp-2);
}

/* =====================================================================
   SYRINGE RULER (Priority 1, visual design pass 2026-08-29) — the
   centerpiece. Translates a computed draw number into the actual mark on
   a barrel instead of leaving the user to do that translation. The
   marker position is DERIVED at render time (app.js: barrelStartX +
   fraction*barrelLength, fraction from the pure/tested
   syringeMarkerFraction) — never a drawn asset, so it is correct at every
   value, not just one.

   Collapsed by default in Prep (that list can have many due items — this
   stays a focal detail, not a decoration on every row); always shown in
   Today, where there are normally 1-3 rows and the user is standing at
   the bench about to draw ("where the user is standing when they draw").
   ===================================================================== */
.syringe-ruler-toggle-row { margin-top: var(--sp-2); }
.syringe-ruler-toggle {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--accent);
  border-radius: 8px;
  padding: 6px 12px;
  font-family: var(--font);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  min-height: 40px;
}
.syringe-ruler {
  margin-top: var(--sp-3);
  padding: var(--sp-4);
  background: var(--bg-panel-raised);
  border: 1px solid var(--border);
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  text-align: center;
}
.syringe-ruler-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--fs-hero);
  font-weight: 600;
  color: var(--text);
  line-height: 1.1;
}
.syringe-ruler-subvalue {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--fs-sm);
  color: var(--text-dim);
}
.syringe-ruler-svg { width: 100%; max-width: 420px; margin-top: var(--sp-2); }
.syringe-ruler-svg svg { width: 100%; height: auto; display: block; }
.syringe-ruler-instruction {
  font-family: var(--font);
  font-size: var(--fs-base);
  color: var(--text-dim);
  margin-top: var(--sp-2);
}
/* Neutral — deliberately NOT --warn/--danger. This flags a MECHANICAL fact
   (the number doesn't fit on this instrument's printed scale), never an
   evaluation of the dose itself (COUNSEL-CONDITIONS #4). */
.syringe-ruler-flag {
  font-family: var(--font);
  font-size: var(--fs-sm);
  color: var(--text-dim);
  background: var(--bg-panel);
  border: 1px dashed var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  margin-top: var(--sp-2);
}

/* =====================================================================
   MULTI-DOSE COMPOUND CARD (Priority 2A, visual design pass 2026-08-29)
   — a compound with doseSlots. One row per slot: a plain accent bar (the
   SAME color on every row — never a per-row hue, which would read as
   ranking one slot over another), the slot's own free-text label +
   schedule on the left, the draw value large on the right.
   ===================================================================== */
.slot-card { margin-top: var(--sp-2); }
.slot-card-summary {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  margin-bottom: var(--sp-2);
}
.slot-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 8px;
  margin-bottom: var(--sp-2);
}
.slot-row-info { flex: 1; min-width: 0; }
.slot-row-label { font-weight: 600; font-size: var(--fs-base); color: var(--text); overflow-wrap: anywhere; }
.slot-row-schedule { font-size: var(--fs-sm); color: var(--text-dim); margin-top: 2px; }
.slot-row-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--accent);
  white-space: nowrap;
  text-align: right;
}

/* =====================================================================
   BLEND-IN-ONE-VIAL CARD (Priority 2B, visual design pass 2026-08-29) —
   a compound with .members (COUNSEL-CONDITIONS Addendum 2026-08-27,
   conditions 8-9: user-authored data, never a shipped combination).
   Named apart from .blend-card on purpose — that class belongs to the
   UNRELATED state.blends feature (a user-named GROUP of separate
   compounds, each its own vial); this is one vial, several peptides.
   Neutral wording only: the dose-target member is marked "dose target",
   every other member "delivered with this draw" — NEVER correct/high/
   low/off (Condition 4). ===================================================================== */
.member-blend-card {
  margin-top: var(--sp-2);
  padding: var(--sp-3);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.member-blend-header {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  margin-bottom: var(--sp-2);
}
.member-blend-total {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--accent);
  margin-bottom: var(--sp-2);
}
.member-blend-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-2);
  padding: 6px 0;
  border-top: 1px dashed var(--border);
  font-size: var(--fs-sm);
}
.member-blend-row:first-of-type { border-top: none; }
.member-blend-name { color: var(--text); font-weight: 600; }
.member-blend-role {
  color: var(--text-faint);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-left: 6px;
}
.member-blend-amount {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--text-dim);
  white-space: nowrap;
}
.member-blend-row.is-target .member-blend-amount { color: var(--text); font-weight: 600; }

/* Soft-card imagery hook (Priority 3, visual design pass 2026-08-29) —
   Streak and Where-You-Are, the two cards named in the brief as safe for
   imagery (encouraging/orienting, no precision number at risk). Inert
   today: --soft-card-image defaults to 'none', so this costs nothing
   until a real photo is wired in (see the build report for exactly which
   images + dimensions). When one is, app.js's applyScrim() derives
   --scrim-top/--scrim-bottom from THAT image's measured brightness —
   never a guessed constant, same mechanism as every scrim in this file. */
.dash-card.dash-card-soft {
  position: relative;
  overflow: hidden;
  background:
    var(--soft-card-image, none) center / cover no-repeat,
    var(--bg-panel);
}
.dash-card.dash-card-soft::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(11,13,16,var(--scrim-top, 0)) 0%, rgba(11,13,16,var(--scrim-bottom, 0)) 100%);
  pointer-events: none;
}
.dash-card.dash-card-soft > * { position: relative; z-index: 1; }

.prep-actions{display:flex;gap:var(--sp-2,8px);margin:0 0 var(--sp-3,12px);}

/* Photographic syringe ruler. The photo is a fixed backdrop; only the mark moves,
   and its position is computed from the value (see buildSyringePhotoRuler). */
.syringe-photo{position:relative;width:100%;background-size:100% 100%;
  background-repeat:no-repeat;border-radius:10px;margin:var(--sp-2,8px) 0;}
.syringe-photo-mark{position:absolute;top:26%;bottom:26%;width:2px;
  background:var(--accent,#3D7EFF);box-shadow:0 0 10px rgba(61,126,255,.9);
  transform:translateX(-1px);}
.syringe-photo-tag{position:absolute;top:10%;transform:translateX(-50%);
  font-size:.72rem;color:var(--accent,#3D7EFF);white-space:nowrap;
  background:rgba(11,13,16,.85);padding:1px 6px;border-radius:4px;}
.syringe-photo-over .syringe-photo-mark{background:var(--text-dim,#8C969F);
  box-shadow:none;}
.syringe-photo-over .syringe-photo-tag{color:var(--text-dim,#8C969F);}

.compound-thumb{width:34px;height:34px;flex:0 0 34px;border-radius:8px;
  background-size:cover;background-position:center;
  border:1px solid var(--border,rgba(242,244,245,.12));margin-right:var(--sp-2,8px);}
.compound-card-head{display:flex;align-items:center;gap:var(--sp-2,8px);}

.vial-card-head{display:flex;align-items:center;gap:var(--sp-2,8px);}

/* Header-band readout (2026-08-29) — fills the dead right side of the hero and
   gives the app a visible date, which it never had. Instrument stamp, not decoration. */
.view-hero{position:relative;}
.view-hero-readout{position:absolute;right:var(--sp-4,18px);top:50%;
  transform:translateY(-50%);text-align:right;pointer-events:none;
  display:flex;flex-direction:column;align-items:flex-end;gap:2px;}
.vhr-day{font-family:var(--mono,monospace);font-size:.68rem;letter-spacing:.22em;
  color:var(--text-dim,#8C969F);}
.vhr-date{font-family:var(--mono,monospace);font-size:.95rem;letter-spacing:.06em;
  color:var(--text,#F2F4F5);}
.vhr-rule{width:34px;height:2px;background:var(--accent,#3D7EFF);
  border-radius:2px;margin:6px 0 4px;opacity:.85;}
.vhr-stat{font-family:var(--mono,monospace);font-size:1.5rem;line-height:1;
  color:var(--accent,#3D7EFF);font-variant-numeric:tabular-nums;}
.vhr-stat-label{font-family:var(--mono,monospace);font-size:.6rem;letter-spacing:.2em;
  color:var(--text-dim,#8C969F);}
@media (max-width:360px){ .view-hero-readout{display:none;} }

/* ---------------------------------------------------------------------
   CALENDAR DAY PANEL — rebuilt 2026-08-29 to the CEO's spec. Replaces a
   stack of undifferentiated .field-note lines ("a small little box, and
   then a bunch of words underneath it") with labelled sections.
   Schedule-change meaning is carried by the GLYPH (+ / - / ->); colour
   reinforces but never stands alone, so it survives colour-blindness and
   reads as a diff rather than a verdict.
   --------------------------------------------------------------------- */
.cal-marker-start { background: #1f5138; color: #86e3b0; }
.cal-marker-stop  { background: #5c2b2b; color: #ffa8a8; }
.calendar-cell-change { box-shadow: inset 0 -3px 0 0 rgba(134, 227, 176, 0.55); }

.cal-detail-open { padding: 18px 18px 14px; }
.cal-detail-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding-bottom: 12px; margin-bottom: 14px;
  border-bottom: 1px solid var(--rule, rgba(140,150,159,0.22));
}
.cal-detail-weekday { font-size: 0.95rem; color: var(--muted); letter-spacing: 0.02em; }
.cal-detail-date { font-size: 1.35rem; font-weight: 700; letter-spacing: 0.04em; }
.cal-detail-today-pill {
  margin-left: auto; font-size: 0.66rem; font-weight: 700; letter-spacing: 0.12em;
  padding: 3px 9px; border-radius: 999px;
  background: var(--accent-dim); color: #eaf6ff;
}

.cal-detail-section { margin-bottom: 14px; }
.cal-detail-section:last-child { margin-bottom: 0; }
.cal-detail-label {
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 6px;
}
.cal-detail-rows { display: flex; flex-direction: column; gap: 5px; }
.cal-detail-row {
  display: flex; align-items: baseline; gap: 9px;
  font-size: 0.95rem; line-height: 1.4;
}
.cal-detail-name { flex: 1 1 auto; min-width: 0; word-break: break-word; }
.cal-detail-meta {
  flex: 0 0 auto; font-size: 0.8rem; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.cal-chg-glyph {
  flex: 0 0 auto; width: 1.35em; height: 1.35em; line-height: 1.35em;
  text-align: center; border-radius: 5px;
  font-weight: 700; font-size: 0.85rem; align-self: center;
}
.cal-chg-start { background: #1f5138; color: #86e3b0; }
.cal-chg-stop  { background: #5c2b2b; color: #ffa8a8; }
.cal-chg-step  { background: #402f60; color: #d8c8ff; }
