/* Author display beats the UA [hidden] rule, so JS-toggled controls that also
   carry a display declaration (.chart-controls, .chart-toggle) would render
   while "hidden" without this guard. */
[hidden] { display: none !important; }

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

:root {
  color-scheme: dark;
  /* surfaces — deep blue-black */
  --bg: #0a0e16;            /* page */
  --surface: #111726;       /* cards, header */
  --surface-2: #1a2233;     /* nested/raised elements, input fills */
  --border: #27324a;
  /* text */
  --text: #e7edf9;
  --text-muted: #94a2c0;
  /* interactive accent — electric cyan */
  --accent: #56d9f2;
  --accent-strong: #86e6f8; /* hover/active */
  --accent-contrast: #051019; /* text on filled accent surfaces */
  /* semantic status — verdict/notices, plus the alert hue on destructive confirm
     buttons; never on ordinary buttons/links */
  --ok: #48d78b;
  --watch: #e9c962;
  --alert: #f26a5c;
  --alert-strong: #f48579;   /* hover/active */
  --alert-contrast: #051019; /* text on filled alert surfaces */
  /* charts */
  --chart-grid: rgba(231, 237, 249, 0.08);
  --chart-tick: #94a2c0;
  --chart-weight: #5cabff;     /* EWMA line */
  --chart-band: #48d78b;       /* target band */
  --chart-predicted: #a98bfa;
  --chart-raw: #6d7c98;
  --chart-excluded: #8698b6;   /* hollow out-of-model weigh-ins */
  --chart-intake: #5cabff;     /* logged-intake line */
  --chart-maintenance: #94a2c0;/* equation maintenance-estimate line */
  --chart-surplus: #f26a5c;    /* intake-over-maintenance fill */
  --chart-deficit: #48d78b;    /* intake-under-maintenance fill */
  --chart-overlay: #e9c962;    /* weight-trend overlay on the intake chart */
  --chart-plan: #e9c962;       /* declared-plan line on the weight chart */
  --chart-steps-pad: #48d78b;  /* activity-mode steps, aggregate fallback */
  --chart-steps-off: #5cabff;  /* other steps */
  /* per-mode step series, assigned in config order and cycled past six; mode-1
     matches --chart-steps-pad so a one-mode config draws as it always has */
  --chart-steps-mode-1: #48d78b;
  --chart-steps-mode-2: #a98bfa;
  --chart-steps-mode-3: #e9c962;
  --chart-steps-mode-4: #f59445;
  --chart-steps-mode-5: #56d9f2;
  --chart-steps-mode-6: #f291c8;
  --chart-steps-unattributed: #7f8ca6; /* activity steps no mode accounts for */
  /* typography — Space Grotesk for display, system-ui for body/numerals */
  --font-display: "Space Grotesk", system-ui, sans-serif;
  --h1: 1.9rem;
  --h2: 1.3rem;
  --h3: 1.1rem;
  /* the three text sizes below body copy, smallest first: a label step, a caption step,
     and a compact-UI step */
  --text-micro: 0.8rem;
  --text-caption: 0.85rem;
  --text-small: 0.9rem;
  --tracking-tight: -0.015em;
  /* shape & rhythm */
  --radius-sm: 8px;
  --radius-md: 14px;
  --touch-target: 2.75rem; /* ≥44px minimum interactive target */
  --space-1: 4px; --space-2: 8px; --space-3: 12px;
  --space-4: 16px; --space-5: 24px; --space-6: 32px;
  /* An upper bound on the fixed tab bar's height, built from the bar's own
     parts — a link's touch target, the link's vertical padding, and the bar's
     top border — so resizing the bar carries the body clearance with it. It
     stays an upper bound for as long as a link's icon, gap, and label stack
     fits inside the touch target. */
  --tab-bar-clearance: calc(var(--touch-target) + 2 * var(--space-2) + 1px);
  /* The same measurement for the day page's fixed save bar, built from its own parts —
     the button's touch target, the bar's vertical padding, and its top border. */
  --day-save-clearance: calc(var(--touch-target) + 2 * var(--space-2) + 1px);
  /* How far a linked anchor has to sit below the viewport top to clear the sticky
     header that would otherwise cover it. */
  --header-clearance: calc(4rem + env(safe-area-inset-top, 0px));
}

/* Vendored variable font (latin subset); provenance in vendor/PROVENANCE.md. */
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 300 700;
  /* optional over swap: no mid-view swap flash; the head preload lets the font win the block window. */
  font-display: optional;
  src: url("vendor/fonts/SpaceGrotesk-Variable-latin.woff2") format("woff2");
}

/* ============================================================
   Element base
   ============================================================ */

body {
  font-family: system-ui, sans-serif;
  margin: 0;
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
  /* Inherited by everything: a word wider than the column it sits in breaks rather than
     painting past it and putting the whole page on a sideways scroll. Inert wherever the
     word fits, which is every ordinary text size. */
  overflow-wrap: break-word;
  /* Clearance for the fixed bottom tab bar; drops to the bare inset ≥40rem
     where the bar hides. */
  padding-bottom: calc(var(--tab-bar-clearance) + env(safe-area-inset-bottom, 0px));
}

h1,
h2,
h3,
.brand {
  font-family: var(--font-display);
  letter-spacing: var(--tracking-tight);
}

/* At large text on a narrow screen a heading is often one word wider than the line it sits
   on, and a hyphen is the readable way to split that word; the break inherited from the body
   is the fallback where English offers no hyphen. This is not a last-resort rule, though:
   hyphens:auto is greedy, so any heading that wraps at all takes a hyphen — an ordinary
   two-line one reads "Intake vs main-/tenance". Headings only for that reason, the wordmark
   left out of it rather than split into "Health track-/er". Not exercised by the visual
   sweep either: headless Chrome ships no hyphenation dictionary and draws none of this. */
h1,
h2,
h3 {
  -webkit-hyphens: auto;
  hyphens: auto;
}

h1 {
  font-size: var(--h1);
  font-weight: 600;
  line-height: 1.15;
  margin: 0 0 var(--space-4);
}

h2 {
  font-size: var(--h2);
  font-weight: 600;
  margin: var(--space-5) 0 var(--space-3);
}

h3 {
  font-size: var(--h3);
  font-weight: 600;
  margin: var(--space-4) 0 var(--space-2);
}

a {
  color: var(--accent);
}

a:hover {
  color: var(--accent-strong);
}

ul {
  padding-left: 1.2rem;
}

dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-1) var(--space-4);
}

dt {
  color: var(--text-muted);
}

dd {
  margin: 0;
}

/* Text-like form controls: warm dark fill, comfortable touch height, amber
   focus ring. Width/stacking layout is applied per-form, not here, so the
   inline date-jump forms keep their auto-width controls. */
input[type="text"],
input[type="date"],
input[type="time"],
input[type="number"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="tel"],
input[type="url"],
select,
textarea {
  font: inherit;
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

/* Every native date control, so none is sized by its own chrome: iOS Safari sizes one by its
   intrinsic width -- scaled up by the system text size -- and lets that beat the width or
   max-width its form gives it, which pushes the field past the card or bar it sits in.
   Without the native appearance it obeys the same box rules as its siblings, and the rule
   above already draws its fill, border, and touch height. iOS then centres the value and
   lets the slot collapse when empty, so both are set back to match the text fields. */
input[type="date"] {
  appearance: none;
  -webkit-appearance: none;
}

input[type="date"]::-webkit-date-and-time-value {
  min-height: 1.2em;
  text-align: left;
}

input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--accent);
}

button {
  font: inherit;
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-4);
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

/* A submitted button, held until the next document replaces the page: dimmed and inert so
   a slow POST reads as underway rather than as a tap that missed. */
button[aria-busy="true"] {
  opacity: 0.55;
  cursor: default;
}

a:focus-visible,
button:focus-visible,
summary:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
/* Not a control but a destination: the review recommendation sends a reader here, and the
   ring is how they see where they landed. Keyed to :focus rather than :focus-visible, which
   suppresses the ring on exactly the tap that jumped here. The ring takes the corner from
   the target's own border-radius, which is what makes it read as a highlighted group rather
   than a box drawn around one. */
#review-window:focus {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.muted {
  color: var(--text-muted);
}

/* ============================================================
   App shell
   ============================================================ */

.app-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  border-bottom: 1px solid var(--border);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

/* Every anchorable element, not only the ones a link happens to point at today: the header
   sticks over the top of the page, so a fragment jump lands under it without this. */
[id] {
  scroll-margin-top: var(--header-clearance);
}

/* The header is the app's top edge under viewport-fit=cover, so it absorbs the
   top inset for the whole page; the side insets keep the brand and nav out of
   the rounded corners in landscape. */
.app-header-inner {
  max-width: 44rem;
  margin: 0 auto;
  padding:
    max(var(--space-2), env(safe-area-inset-top, 0px))
    max(var(--space-4), env(safe-area-inset-right, 0px))
    var(--space-2)
    max(var(--space-4), env(safe-area-inset-left, 0px));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

.brand {
  font-weight: 600;
  font-size: 1.05rem;
  color: var(--text);
  text-decoration: none;
}

.brand:hover {
  color: var(--text);
}

/* Wide-screen link row; hidden below 40rem where the bottom tab bar takes over. */
.app-nav {
  display: none;
}

.app-nav a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: 0 var(--space-2);
  font-family: var(--font-display);
  font-size: 0.95rem;
  letter-spacing: var(--tracking-tight);
  color: var(--text-muted);
  text-decoration: none;
}

.app-nav a:hover {
  color: var(--text);
}

.app-nav a[aria-current="page"] {
  color: var(--accent);
}

/* Primary "Today" action: one treatment at both widths — a raised neutral chip, around the
   label here and around the whole tab in the tab bar. Deliberately not the accent:
   accent is the current-page marker in both bars, so an accent Today would claim
   "you are here" on every page that is not the day page. Raising and weight say "start
   here"; hue is left to say "you are here", and the two compose on today's day page. */
.app-nav .nav-today {
  padding: 0 var(--space-4);
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  font-weight: 600;
}

.app-nav .nav-today:hover {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
}

/* The settings gear: the header's third child, outside .app-nav, so it stays put at both
   breakpoints while the link row itself hides below 40rem. The icon is drawn at 24px —
   under the height the brand's line box already gives the header — and the touch target is
   grown to the full 44px by a transparent ::after, which cannot push the header taller. */
.header-settings {
  position: relative;
  flex: none;
  display: inline-flex;
  align-items: center;
  color: var(--text-muted);
}

.header-settings svg {
  width: 1.5rem;
  height: 1.5rem;
}

/* Invisible hit area around the gear, in px rather than rem on purpose: a finger is the same
   size at every text setting, and a target that grew with the text would reach past the
   header's own padding and push the whole document sideways. 44px is the physical baseline
   and the icon has already outgrown it by the time the text is scaled that far. */
.header-settings::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

.header-settings:hover {
  color: var(--text);
}

/* Its own current-page rule: the nav's is scoped to .app-nav a, which this sits outside. */
.header-settings[aria-current="page"] {
  color: var(--accent);
}

/* Fixed bottom tab bar: the primary nav below 40rem; hidden ≥40rem. */
.tab-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: flex;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  border-top: 1px solid var(--border);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  transform: translateZ(0); /* own compositing layer so the fixed blurred bar paints on first frame (iOS Safari late-composite quirk) */
  padding-bottom: env(safe-area-inset-bottom, 0px);
  /* Queried below to decide whether the labels fit. The bar is the container rather than a
     link because its width comes from the viewport, not from what it holds — a link sized by
     its own label cannot be asked how much room its label has. */
  container: tab-bar / inline-size;
}

/* Columns start at their label's width and share what is left over, so every label the bar
   shows is whole. min-width:0 is what keeps the label rule's last resort from becoming an
   overflow instead: a column that refuses to go below its own label pushes the last
   destination off the screen rather than clamping the label inside itself. */
.tab-bar a {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-1);
  font-family: var(--font-display);
  font-size: 0.75rem;
  letter-spacing: var(--tracking-tight);
  color: var(--text-muted);
  text-decoration: none;
}

/* One line always: a wrapped label would push the bar past --tab-bar-clearance and cover the
   end of the page. position:relative is what lifts the label onto the Today chip below; the
   ellipsis is a clamp of last resort, so a label longer than the five measured below stays
   inside its column rather than over its neighbour. */
.tab-label {
  position: relative;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Five labels side by side need their combined text (11.8rem covers both the display font
   and its fallback, with margin so the switch never sits on the exact fitting width) plus
   each link's horizontal padding (5 × 2 × --space-1, which is px and so does not scale).
   Narrower than that they cannot all be whole, and rather than cut them
   the bar drops to its icons: the text stays in the accessibility tree, so every destination
   is still read by the name printed beside it at every size that has the room. The threshold
   is in rem, so it tracks the text setting — a viewport measure would not, which is the whole
   reason the labels overflowed at 200% text. */
@container tab-bar (max-width: calc(11.8rem + 40px)) {
  .tab-label {
    position: absolute;
    width: 1px;
    height: 1px;
    clip-path: inset(50%);
  }
}

.tab-bar svg {
  width: 1.4rem;
  height: 1.4rem;
}

/* The link row's Today chip, here around the whole tab, labelled or not. It is painted
   rather than laid out, and its box is inset from the link's own rather than sized in its
   own units, so at any text size it can neither make the bar taller than its clearance nor
   reach into a neighbouring tab. */
.tab-bar .nav-today {
  position: relative;
  color: var(--text);
  font-weight: 600;
}

.tab-bar .nav-today::before {
  content: "";
  position: absolute;
  inset: var(--space-1);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
}

/* A positioned pseudo-element paints over its parent's text and inline content, so the icon
   is lifted onto the same layer to sit on the chip rather than under it. The label carries
   its own position for the same reason, up where the rule that may take it out of the flow
   can override it. */
.tab-bar .nav-today svg {
  position: relative;
}

.tab-bar a[aria-current="page"] {
  color: var(--accent);
}

@media (min-width: 40rem) {
  /* margin-left:auto rather than the container's space-between: with a third child in the
     header, space-between alone would strand the link row in the middle. */
  .app-nav {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-left: auto;
  }

  .tab-bar {
    display: none;
  }

  body {
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
}

/* Side insets only: the header above absorbs the top inset, and the bottom
   padding on <body> absorbs the bottom one — tab-bar clearance below 40rem,
   the bare inset above it. */
.container {
  max-width: 44rem;
  margin: 0 auto;
  padding:
    var(--space-5)
    max(var(--space-4), env(safe-area-inset-right, 0px))
    var(--space-6)
    max(var(--space-4), env(safe-area-inset-left, 0px));
}

/* ============================================================
   Component primitives (defined here, applied per page)
   ============================================================ */

.btn {
  font: inherit;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch-target);
  /* A label carrying a filename has no space to break at; without these the button sizes
     itself to that one token and takes the page sideways with it. */
  max-width: 100%;
  overflow-wrap: anywhere;
  padding: var(--space-2) var(--space-4);
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  text-decoration: none;
  cursor: pointer;
}

.btn:hover {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
}

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--accent) 20%, transparent);
}

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

/* The apply button on a destructive confirm: same weight and shape as .btn-primary —
   it is still the emphasized action — but in the alert hue, so the muscle memory for
   "the accent button is the safe one" never fires on an irreversible click. */
.btn-danger {
  background: var(--alert);
  border-color: var(--alert);
  color: var(--alert-contrast);
  font-weight: 600;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--alert) 20%, transparent);
}

.btn-danger:hover {
  background: var(--alert-strong);
  border-color: var(--alert-strong);
  color: var(--alert-contrast);
}

.btn-sm {
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-caption);
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
}

/* ---- "On this page" chip nav ----------------------------------------------
   The way in to each section of a page too long to take in at once: one link chip
   per heading, wrapped into as many rows as the width needs. Links rather than a
   disclosure -- there is nothing here worth hiding behind a control. Worn by the
   data overview, the settings index, and the guide's table of contents, which are
   the same row over the same kind of list; the chips take the same grammar as the
   dashboard's action links. Only the block around a row is set per page. */
.page-contents ul,
.settings-index ul,
.guide-toc ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A chip label that outgrows a narrow screen at large text shrinks and wraps rather
   than pushing the page sideways. */
.page-contents a,
.settings-index a,
.guide-toc a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  max-width: 100%;
  padding: var(--space-1) var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  text-decoration: none;
  font-size: var(--text-small);
}

.page-contents a:hover,
.settings-index a:hover,
.guide-toc a:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* ---- Notice family --------------------------------------------------------
   Banners, callouts, flags, and the review/offer strips share one grammar: a
   tinted panel over the surface with a colored left rule. Variants differ only
   in hue — success (ok), warning (watch), alert (alert), offer (accent) — so
   every notice reads as one crafted component rather than an ad-hoc tinted box.
   Persistent notices carry a trailing `.learn-more` link into the guide. */

.banner {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border-left: 3px solid var(--border);
  background: var(--surface-2);
  margin: var(--space-4) 0;
}

.banner.error {
  border-left-color: var(--alert);
  background: color-mix(in srgb, var(--alert) 14%, var(--surface));
  color: var(--text);
}

.banner.saved {
  border-left-color: var(--ok);
  background: color-mix(in srgb, var(--ok) 14%, var(--surface));
  color: var(--text);
}

.callout-warning,
.flag,
.review-reason {
  margin: var(--space-4) 0;
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--watch);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--watch) 12%, var(--surface));
}

/* Alert variant of the callout: same grammar as .callout-warning in the alert hue,
   worn by the calibration drift/stale notice. */
.callout-alert {
  margin: var(--space-4) 0;
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--alert);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--alert) 12%, var(--surface));
}

/* A callout carrying a quoted multi-sentence note: a title naming the question it answers,
   then one paragraph per sentence. Several can stack on one page, so the title is what makes
   them tellable apart at a glance — the display face at the label step in the callout's own
   hue, a register neither the prose below nor the page's headings use. */
.plan-note-title {
  margin: 0 0 var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--watch);
}

/* The alert-hued member of the same family: one note in a stack of them says the plan has
   stopped doing what it was declared to do, and its title takes the callout's hue like every
   other title here so the tint and the label cannot disagree. */
.plan-note-alert .plan-note-title {
  color: var(--alert);
}

.plan-note p {
  margin: 0 0 var(--space-3);
}

.plan-note p:last-child {
  margin-bottom: 0;
}

/* Trailing "Learn more" affordance shared by the notice family; the arrow is
   supplied here rather than in the link text. */
.learn-more {
  display: inline-block;
  margin-top: var(--space-2);
  font-size: var(--text-caption);
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
}

.learn-more::after {
  content: " \2192";
}

.learn-more:hover {
  color: var(--accent-strong);
}

/* The engine's normal "ok" flag is not a data-integrity warning. */
.flag.flag-ok {
  margin: var(--space-3) 0;
  padding: 0;
  border-left: none;
  background: none;
  color: var(--text-muted);
}

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  gap: var(--space-3);
  margin: var(--space-4) 0;
}

.stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.stat-label {
  font-size: var(--text-micro);
  color: var(--text-muted);
}

/* Tiles stretch to the tallest in their row, so pushing the value down puts every
   value on one bottom edge — a label that wraps to three lines no longer staggers the
   number it is compared against.

   A tile can hold a single unbreakable word ("Unreadable" on /data), which on the
   narrowest screens outruns its tile: let it break rather than spill. */
.stat-value {
  margin-top: auto;
  font-size: 1.25rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

@media (min-width: 40rem) {
  .stat-value {
    font-size: 1.4rem;
  }
}

/* A tile whose value is a sentence rather than a figure (a rebuilding trend states why it
   has no number yet): it drops the figure typography for the muted prose the grid's own
   context row wears, so it never reads as a number to compare with the tiles beside it. */
.stat-value--muted {
  font-size: var(--text-small);
  font-weight: 400;
  color: var(--text-muted);
}

/* These tiles hold formatted numbers with a unit suffix, and a break inside a suffix
   reads as a different unit ("kcal/da" over "y"), so they wrap at their spaces only; the
   narrow-screen size above is what keeps the longest suffix inside its tile instead. */
.verdict-stats .stat-value,
.calibration-card .stat-value,
.calibration-offer-card .stat-value {
  overflow-wrap: normal;
}

/* The rule above forbids the only wrap that would fit "kcal/day" into a narrow tile, so
   these grids may not put two tiles in a row narrower than the suffix: the shared 7rem
   track lets a second column in while a tile still holds ~5.5rem of text, which spills the
   suffix out of its tile on a common phone width. A track wide enough for the longest
   suffix is what decides the column count instead, so the pair stacks exactly when it
   cannot fit side by side, and it stays true at larger text sizes because both the track
   and the suffix are measured in the same growing unit. The `min()` keeps the track from
   outrunning a container narrower than itself. */
.calibration-card .stat-grid,
.calibration-offer-card .stat-grid {
  grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr));
}

/* A sentence about the tiles it sits with rather than a figure of its own: it spans
   the whole grid as a quiet context row instead of pretending to be a tile. */
.stat-row {
  grid-column: 1 / -1;
  padding: var(--space-1) 0;
  background: none;
  font-size: var(--text-small);
  color: var(--text-muted);
}

/* Label/value summary of what a step just did or is about to do: the import and
   proposal result pages report their counts here, and the confirm-before-destroy pages
   list what they are about to remove. One framed row per pair — the boxed row the
   guide's term list already wears — so every such summary is read the same way and a
   long value wraps inside its own row. */
.summary-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: var(--space-4) 0;
}

.summary-list > div {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

/* Wide enough to line the terms up, never wider than the row that holds them: at large text
   a bare 9rem is more than the whole column and pushes the page sideways. */
.summary-list dt {
  min-width: min(9rem, 100%);
}

.summary-list dd {
  overflow-wrap: anywhere;
}

.verdict-ok {
  border-color: var(--ok);
  background: color-mix(in srgb, var(--ok) 8%, var(--surface));
}

.verdict-watch {
  border-color: var(--watch);
  background: color-mix(in srgb, var(--watch) 8%, var(--surface));
}

.verdict-alert {
  border-color: var(--alert);
  background: color-mix(in srgb, var(--alert) 8%, var(--surface));
}

/* A card whose reading is genuinely old. The zone hue is left exactly as it is — an old
   below-floor reading is still a below-floor reading, and dimming it would trade one
   dishonesty for a worse one — so the disclaimer is added on top of it rather than taken out
   of it: a hatch across the card's own fill and a dashed edge on the card and its zone chip.
   Both read without colour, which the tinted fills alone do not, and the sentences that date
   the reading come out of the muted tone below (see .verdict-tense). Declared after the three
   zone rules because their `background` shorthand resets the image this one paints. */
.verdict-not-current {
  border-style: dashed;
  background-image: repeating-linear-gradient(
    135deg,
    transparent 0 6px,
    color-mix(in srgb, var(--text-muted) 9%, transparent) 6px 12px
  );
}

.verdict-not-current .zone-chip {
  border-style: dashed;
}

/* On a card claiming currency these two sentences are an aside on a figure that speaks for
   itself; on one disclaiming it they are the disclaimer, and a glance that reads colour
   before text has to arrive at words that are not whispering. */
.verdict-not-current .verdict-tense .muted {
  color: var(--text);
}

/* Four tiles (EWMA, intake, rolling mean, slope) laid two across rather than at the
   grid's auto-fit density: these labels are dated sentences, not one-word captions, and
   a third column would leave the longest of them wrapping in the narrowest tile. On a
   phone even two columns are too narrow for a value carrying a unit and a direction
   word, so they stack — a figure broken across three lines is not a figure. */
.verdict-stats {
  grid-template-columns: 1fr;
}

@media (min-width: 40rem) {
  .verdict-stats {
    grid-template-columns: 1fr 1fr;
  }
}

/* Banner strip: the notice family's alert variant, with a bolder 4px rule and stronger fill
   than the callouts. Worn straight only by the two banners that stand above the verdict card
   because they block trust in it — a model config that will not load, and a failure the app
   has already recorded. Everything else that wears this shape reads below the card and takes
   a variant below. */
.watcher-banner {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-left: 4px solid var(--alert);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--alert) 14%, var(--surface));
}

.watcher-banner-heading {
  margin: 0 0 var(--space-2);
  font-weight: 600;
}

.watcher-banner ul {
  margin: 0;
}

/* The advisory variant: something worth attention that does not put the reading above it in
   doubt — a review the app recommends, days left open. It keeps the banner's shape in the
   watch hue, which leaves the alert hue meaning one thing among this page's banners: trust in
   the card is blocked. The card itself wears that hue for a below-floor or above-ceiling zone,
   which is a claim about the reading rather than about a notice. */
.watcher-banner--advisory {
  border-left-color: var(--watch);
  background: color-mix(in srgb, var(--watch) 12%, var(--surface));
}

/* The notice family's footnote variant: a note qualifying the review answer rather than
   asking for anything, so it keeps the banner's shape and drops the alert tint that would
   read as a second thing to act on. */
.watcher-banner--note {
  border-left-color: var(--border);
  background: var(--surface);
  color: var(--text-muted);
}

/* Calibration banner: the notice family's offer variant, last in the suggestions group under
   the verdict card. It reads as an opportunity rather than something to put right, so it
   wears the accent hue against the watch hue its neighbours in that group take. */
.calibration-banner {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
}

.calibration-banner-value {
  margin: var(--space-1) 0 var(--space-2);
  font-size: 1.6rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.calibration-banner p:last-child {
  margin-bottom: 0;
}

/* ============================================================
   Shared status/verdict text
   ============================================================ */

/* Hero figures: the display number a page is built around — the dashboard weight, the
   day cap and the instruction priced from it, the prune count, the calibration value and
   its from -> to offer — share one size, weight, and numeral treatment so the primary
   figure reads the same everywhere. */
.weight,
.cap-value,
[data-day-instruction-value],
.prune-count strong,
.calibration-value,
.calibration-offer {
  font-size: 2.2rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Hero labels: the small muted line that names the figure below it. The day/plan cap
   card keeps this label rather than the section heading its sibling cards wear — it
   names a number, not a section — and shares the micro size and muted tone with the
   calibration surfaces that name theirs the same way; being an h2, it alone keeps the
   display heading face. */
.cap-card > h2,
.calibration-banner-heading,
.calibration-label {
  margin: 0;
  font-size: var(--text-micro);
  color: var(--text-muted);
}

/* Trailing qualifier on a figure or status line: a muted, normal-weight aside at body
   size, so the caveat never competes with the value it annotates. Wherever the value is
   rendered — a zone line, a stat tile — the qualifier beside it reads the same. */
.weight .excluded-marker,
.zone .reconverging,
.stat-value .reconverging,
.pad-rate-measured {
  font-size: 1rem;
  font-weight: 400;
  color: var(--text-muted);
}

/* The line that carries the day page's acted-on number; the figure itself wears the
   shared hero treatment above. */
.cap {
  margin: var(--space-2) 0;
}

/* Cap hero: the day page's display figure, framed like the dashboard hero. The
   heading rides above the number as a small label, and the range/disclosure lines
   read as its caption. An accent-tinted frame and extra room mark it as the page's
   primary number. */
.cap-card {
  padding: var(--space-5);
  border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
}

.cap-card > h2 {
  margin-bottom: var(--space-1);
}

.cap-range,
.cap-disclosure {
  margin: var(--space-1) 0 0;
  font-size: var(--text-caption);
  line-height: 1.4;
}

/* Stale cap: a live recompute failed, so the shown value is dimmed to the muted
   tone and a watch-hued note (the notice family's .callout-warning grammar) sits
   below it. The note is compact so appearing and clearing shifts only its own row. */
.cap-stale .cap-value {
  color: var(--text-muted);
}

.cap-stale-note {
  margin: var(--space-3) 0 0;
  font-size: var(--text-caption);
}

/* Instruction card: the number the day is acted on, framed in the cap card's grammar and
   wearing the same display treatment, because it is the cap's answer and not a caption to
   it — as body text under an accent-framed hero it lost the glance to the figure the
   command moves away from. The sentence states the whole instruction and follows immediately
   below; a bound's ceiling word rides inside the figure line at the body size that line
   keeps, so the display number is never read bare. */
.day-instruction {
  padding: var(--space-5);
  border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
}

/* The figure sits close over the sentence it heads, in the spacing the cap hero keeps over
   its caption. Both top margins are cleared here rather than left to the card's first-child
   reset, which reaches only the figure: that slot is withheld wherever the instruction names
   no number, and the sentence has to meet the top of the card the same way when it is. */
[data-day-instruction-figure] {
  margin: 0 0 var(--space-2);
}

[data-day-instruction-line] {
  margin-top: 0;
}

/* The same failure over the day page's instruction, worn the same way: the figure and the
   sentence dimmed to the muted tone and the note in the grammar above, so one live-update
   failure reads alike wherever it lands. */
.day-instruction-stale [data-day-instruction-figure],
.day-instruction-stale [data-day-instruction-line] {
  color: var(--text-muted);
}

.day-instruction-stale-note {
  margin: var(--space-3) 0 0;
  font-size: var(--text-caption);
}

/* Inverse answer: the plan page's reply to "cover this target", worn in the notice
   family's grammar so the three states are told apart before the sentence is read.
   The accent hue marks the activity to add — the figure the question was asked for,
   so it carries the same accent the page frames its cap with; the ok hue reports a
   target the plan already reaches; and a refusal takes the softer watch hue in the
   muted tone, so an answer the model cannot price never reads as one to act on. */
.cap-inverse {
  margin: var(--space-3) 0 0;
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  line-height: 1.4;
}

.inverse-answer {
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  font-size: 1.05rem;
}

.inverse-covered {
  border-left-color: var(--ok);
  background: color-mix(in srgb, var(--ok) 12%, var(--surface));
}

.inverse-refused {
  border-left-color: var(--watch);
  background: color-mix(in srgb, var(--watch) 12%, var(--surface));
  color: var(--text-muted);
  font-size: var(--text-small);
}

.zone {
  font-size: 1.2rem;
  font-weight: 600;
  margin-top: 1.25rem;
}

/* Dashboard hero: the shared .zone line worn as a status chip, tinted to match the
   verdict card's semantic modifier. Only the hero carries .zone-chip, so the day and
   result pages keep the plain .zone treatment. */
.zone-chip {
  display: inline-block;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-2);
}

.verdict-ok .zone-chip {
  border-color: color-mix(in srgb, var(--ok) 60%, var(--border));
  background: color-mix(in srgb, var(--ok) 16%, var(--surface));
}

.verdict-watch .zone-chip {
  border-color: color-mix(in srgb, var(--watch) 60%, var(--border));
  background: color-mix(in srgb, var(--watch) 16%, var(--surface));
}

.verdict-alert .zone-chip {
  border-color: color-mix(in srgb, var(--alert) 60%, var(--border));
  background: color-mix(in srgb, var(--alert) 16%, var(--surface));
}

/* The verdict's action sentence: the hero's primary instruction, one step up from body. */
.verdict-action {
  margin: var(--space-3) 0;
  font-size: 1.05rem;
}

/* ============================================================
   Day page
   ============================================================ */

.pad-rate {
  margin: var(--space-1) 0;
}

.pad-rate-measured {
  margin-left: var(--space-1);
}

/* The main day form stacks full-width controls; scoped so the inline
   date-jump forms (action="/day") keep their auto-width layout. */
form[action^="/day/"] label {
  display: block;
  margin: var(--space-2) 0;
}

form[action^="/day/"] input,
form[action^="/day/"] select,
form[action^="/day/"] textarea {
  display: block;
  width: 100%;
  margin-top: var(--space-1);
}

form[action^="/day/"] label.checkbox {
  display: flex;
  align-items: center;
  min-height: var(--touch-target);
  gap: var(--space-2);
}

form[action^="/day/"] label.checkbox input {
  display: inline-block;
  width: auto;
  margin: 0;
}

/* The day form runs several screens long, and the page it sits on runs longer still — the
   cap it prices, the trend, the flags, the clear action. So the save follows the whole page
   rather than the length of the form: fixed at the foot of the screen above the tab bar,
   which is the reach a thumb has while the keyboard is open. It stays in the form's own
   markup after the last field, so a keyboard reaches it there rather than past the
   destructive action at the page foot. Two rules keep it from covering anything: the page
   reserves its height at the end, so nothing comes to rest underneath it, and scroll-padding
   keeps a field the browser scrolls to — a focus, a refusal — clear of the bar. Same
   behaviour at both widths: the cards below the form are as long on a desktop screen, and a
   save that has scrolled out of reach there is the same defect. */
.day-save {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(var(--tab-bar-clearance) + env(safe-area-inset-bottom, 0px));
  z-index: 10;
  padding: var(--space-2) max(var(--space-4), env(safe-area-inset-right, 0px))
    var(--space-2) max(var(--space-4), env(safe-area-inset-left, 0px));
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-top: 1px solid var(--border);
}

/* The bar spans the screen; its control lines up with the content column above it. */
.day-save-inner {
  max-width: 44rem;
  margin: 0 auto;
}

.day-save .btn-primary {
  width: 100%;
}

/* The room the bar takes out of the viewport, on the pages that carry one: reserved at the
   page foot so the last card can be scrolled clear of it, and given to the scroll anchoring
   so a focused field never lands underneath it. */
body:has(.day-save) {
  padding-bottom: calc(
    var(--tab-bar-clearance) + var(--day-save-clearance) +
      env(safe-area-inset-bottom, 0px)
  );
}

html:has(.day-save) {
  scroll-padding-bottom: calc(
    var(--tab-bar-clearance) + var(--day-save-clearance) +
      env(safe-area-inset-bottom, 0px)
  );
}

@media (min-width: 40rem) {
  /* No tab bar to clear at this width; the bar and the room it takes both drop that term. */
  .day-save {
    bottom: env(safe-area-inset-bottom, 0px);
  }

  .day-save .btn-primary {
    width: auto;
  }

  body:has(.day-save) {
    padding-bottom: calc(
      var(--day-save-clearance) + env(safe-area-inset-bottom, 0px)
    );
  }

  html:has(.day-save) {
    scroll-padding-bottom: calc(
      var(--day-save-clearance) + env(safe-area-inset-bottom, 0px)
    );
  }
}

/* Fieldset groups break the long entry forms — and the import-apply row list — into
   labeled section cards. min-width:0 overrides the fieldset default (min-content) that
   would otherwise let a wide control force horizontal overflow. */
form[action^="/day/"] fieldset,
.plan-form fieldset,
.goals-form fieldset,
.sweep-members,
.import-apply fieldset {
  min-width: 0;
  margin: 0 0 var(--space-4);
  padding: var(--space-2) var(--space-4) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

form[action^="/day/"] legend,
.plan-form legend,
.goals-form legend,
.sweep-members > legend,
.import-apply legend {
  padding: 0 var(--space-2);
  font-size: 1.15rem;
  font-weight: 600;
}

/* The day form's lower-frequency groups ride behind a disclosure, in the same card the
   fieldsets above wear; the toggle itself shares the guide's summary grammar (see the Guide
   page section). A section holding anything at all is served open by the route, so a shut
   one is an empty one and no state is ever behind a closed door. */
.day-section {
  min-width: 0;
  margin: 0 0 var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

/* The section's name is a heading, so it is announced as the group the fields below belong
   to; it is sized to read as the legend of the fieldsets it sits among rather than as the
   page's own h2s, and carries no heading margin inside the summary's own padding. */
.day-section-name {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 600;
}

/* A field that takes no entry — the empty step readout of a mode logging no steps. It keeps
   its place in the form, because the mode's pair is what the reader reads it against, but it
   stops looking like somewhere to type. One already holding a count is left open instead, so
   the count can be cleared. */
form[action^="/day/"] input[readonly] {
  background: var(--surface-2);
  color: var(--text-muted);
  cursor: default;
}

/* The day-form stacking above is scoped to form[action^="/day/"], so the plan
   calculator's form carries its own equivalent: label above a full-width control,
   grouped in the same fieldset card. The shared .field-pair grid handles its
   two-up rows. */
.plan-form label {
  display: block;
  margin: var(--space-2) 0;
}

.plan-form input,
.plan-form select {
  display: block;
  width: 100%;
  margin-top: var(--space-1);
}

/* The two Plan surfaces' chooser, worn as a segmented bar in the range selector's grammar:
   the current surface is the filled segment, marked by aria-current so the style and the
   assistive announcement cannot part. It is the only link between the two pages. */
.plan-chooser {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-4) 0;
}

.plan-chooser a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-caption);
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  text-decoration: none;
}

.plan-chooser a:hover {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
}

.plan-chooser a[aria-current="page"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
}

/* A refused declaration's named reason, printed under the field whose value caused it: the
   alert hue in caption size, so it reads as the answer to that field rather than as a page
   banner. */
.field-refusal {
  margin: 0 0 var(--space-3);
  font-size: var(--text-caption);
  line-height: 1.4;
  color: var(--alert);
}

/* Two states a control itself has to carry, in the hues the notice family already uses for
   them: an entry the save refused, and an entry put back from a visit that never saved. Both
   are worn as an inner rule rather than a thicker border, so a field lighting up moves nothing
   around it. Neither state is left to color alone — the refusal prints its sentence under the
   field, and the restored figures are named in the notice above the form. */
input[aria-invalid="true"],
select[aria-invalid="true"],
textarea[aria-invalid="true"] {
  border-color: var(--alert);
  box-shadow: inset 0 0 0 1px var(--alert);
}

[data-day-unsaved] {
  border-color: var(--watch);
  box-shadow: inset 0 0 0 1px var(--watch);
}

/* Related controls sit two-up on wider screens and stack below ~40rem. Each cell
   is a bare <label> or a .field-col wrapping a label plus its estimate note. */
.field-pair {
  display: grid;
  gap: var(--space-2) var(--space-4);
}

.field-pair,
.field-col {
  min-width: 0;
}

.field-pair > label,
.field-pair > .field-col,
.field-col > label {
  margin: 0;
}

@media (min-width: 40rem) {
  .field-pair {
    grid-template-columns: 1fr 1fr;
    align-items: start;
  }
}

.estimate {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-1) 0 0;
  font-size: var(--text-caption);
  color: var(--text-muted);
}

.use-estimate {
  flex-shrink: 0;
}

/* A free-text field's recognized values, offered as one button each below it, so the
   value is pickable by tap where a type-ahead suggestion list is not dependable. The
   pressed button is the value currently in the field, filled like the primary action
   because it reads as a chosen state rather than an available one. */
.quick-fill {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-2) 0;
}

.quick-fill > button[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.split-summary,
.excluded-note,
.ate-out-note,
.estimated-note {
  margin: var(--space-2) 0;
  font-size: var(--text-caption);
}

/* The parenthesized raw method key is one long underscore run with no break
   opportunity, so it needs to break mid-token to stay inside the card. */
.split-method {
  margin: var(--space-2) 0;
  overflow-wrap: anywhere;
}

/* The Flags card leads each line with the same kind of raw token, and breaks it for the
   same reason: at large text sizes on a narrow screen one of them is otherwise wider than
   the page and the whole page scrolls sideways. Spacing is the notice family's above. */
.flag {
  overflow-wrap: anywhere;
}

/* Direct-child cards rendered as stacked blocks: shared vertical rhythm, plus the
   first child (section heading or display figure) pulled flush to the card's top
   padding. Covers the dashboard's verdict card and the day page's section cards so
   both pages share one card rhythm. */
.container > .card,
#day-form > .card {
  margin: var(--space-4) 0;
}

.container > .card > :first-child,
#day-form > .card > :first-child {
  margin-top: 0;
}

.field-help {
  margin: 0 0 var(--space-3);
  font-size: var(--text-caption);
}

/* Shared up-link bar: the one link a child page, a confirm page, or a transfer tool carries
   to the page it belongs to. A destination the navs already carry has none, and the day
   page's own up-link rides in .day-header below instead. */
.day-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
}

.day-nav > a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
}

/* Page header: the day named in words as the title with its ISO date under it, over a
   compact bar that pairs the back link with the date-jump form. The bar wraps and stays
   reachable one-handed at 390px. */
.day-header {
  margin: 0 0 var(--space-5);
}

.day-header-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* The in-page back link fills the bar's height (already ~44px from the date-jump
   controls beside it) so its touch target matches the app-shell link. */
.day-back {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
}

/* The native date control carries its own intrinsic width, which at large text sizes is
   wider than a 320px screen; the row wraps and the control gives up its minimum so the
   picker stays inside the page rather than pushing it sideways. It also takes the bar's
   leftover width and centres its own controls in it, so the date+Go pair sits between the
   back link and the bar's far edge — and centres on the line it claims once the bar wraps. */
.date-jump {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  flex-grow: 1;
  justify-content: center;
  min-width: 0;
  max-width: 100%;
}

.date-jump input,
.date-jump button {
  display: inline-block;
  width: auto;
  min-width: 0;
  max-width: 100%;
  margin: 0;
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-small);
}

.day-header > h1 {
  margin: var(--space-3) 0 0;
}

/* The ISO date under the heading: the key the store, the change log, and this page's own
   address speak, kept in figures the eye can compare down a list. */
.day-date-line {
  margin: var(--space-1) 0 0;
  font-size: var(--text-caption);
  font-variant-numeric: tabular-nums;
}

/* Danger zone: the shared destructive-action panel — the day page's clear-day form, the
   data page's critical actions, and the confirm pages' apply buttons — held at the page
   foot inside an alert-tinted panel so its destructive weight is unmistakable. Only the
   confirm pages' apply button, the one that actually writes, wears .btn-danger; the entry
   points that merely open a confirm stay neutral. */
.danger-zone {
  margin: var(--space-6) 0 0;
  padding: var(--space-4);
  border: 1px solid color-mix(in srgb, var(--alert) 35%, var(--border));
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--alert) 6%, var(--surface));
}

.danger-zone > h2 {
  margin-top: 0;
}

.danger-zone .day-clear {
  margin: var(--space-3) 0 0;
}

/* Delete your data sits last in the data page's critical actions, under the sweep form, and
   takes the same separation from it that the form takes from the row of copies above. */
.danger-zone .range-form + .store-actions {
  margin: var(--space-4) 0 0;
}

/* ============================================================
   Status page
   ============================================================ */

/* The notices the verdict does not depend on, read as one group under it. The banners keep
   their own spacing; the group's job is to close with a single guide link that answers for
   whichever of them fired, sitting tight under the last one rather than floating between
   them. */
.dashboard-notices > .learn-more {
  margin-top: 0;
}

/* The date-jump form and the recent-days chips share one compact row: side by
   side on wider screens, stacked once the row can no longer hold both. */
.quick-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-5);
  align-items: flex-start;
  margin: var(--space-4) 0;
}

/* min-width:0 overrides the flex-item default (min-content): the date control inside carries
   its own intrinsic width, and without it this column refuses to go below that width and
   pushes the picker past the page column. */
.quick-row .day-entry {
  flex: 1 1 13rem;
  min-width: 0;
}

.quick-row .recent-days {
  flex: 3 1 16rem;
  margin-top: 0;
}

/* Label on its own row above the input+button pair, so the label never wraps
   mid-phrase against a right-aligned control. */
.day-entry form {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

.day-entry label {
  flex: 1 1 100%;
  color: var(--text-muted);
  font-size: var(--text-small);
}

.day-entry input {
  flex: 1 1 auto;
  min-width: 0;
}

.day-entry input,
.day-entry button {
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-small);
}

/* Data freshness: a quiet line under the day-entry row, naming the last phone post whose
   numbers reached the store — or, on an instance that has never logged anything, pointing
   at the logging guide. The stale variant borrows the notice family's .callout-warning
   grammar (the softer watch hue and 3px rule), so it stays quieter than the alert-hued
   banners that stand above the verdict card and never competes with a real alert. */
.phone-freshness {
  margin: 0 0 var(--space-4);
  font-size: var(--text-small);
}

/* The day chips ride behind a disclosure: they are a shortcut to reach for, not
   something to read, so the row leads with the date-jump form and the chips unfold
   only when asked for. */
.recent-days {
  margin-top: var(--space-4);
  font-size: var(--text-small);
}

/* The toggle: a ≥44px tap target wearing the guide's chevron in place of the UA
   disclosure marker, sized down for a one-line label. Shared with the range confirm's
   list of exact dates and the archive manifest, which are the same kind of detail asked
   for only when wanted. */
.recent-days > summary,
.range-dates > summary,
.archive-manifest > summary {
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  color: var(--text-muted);
  cursor: pointer;
}

.recent-days > summary::-webkit-details-marker,
.range-dates > summary::-webkit-details-marker,
.archive-manifest > summary::-webkit-details-marker {
  display: none;
}

.recent-days > summary::after,
.range-dates > summary::after,
.archive-manifest > summary::after {
  content: "";
  width: 0.45rem;
  height: 0.45rem;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: rotate(45deg);
  transition: transform 0.15s ease;
}

.recent-days[open] > summary::after,
.range-dates[open] > summary::after,
.archive-manifest[open] > summary::after {
  transform: rotate(-135deg);
}

.recent-days > summary:hover,
.range-dates > summary:hover,
.archive-manifest > summary:hover {
  color: var(--accent);
}

.recent-days > summary:hover::after,
.range-dates > summary:hover::after,
.archive-manifest > summary:hover::after {
  border-color: var(--accent);
}

.recent-days ul {
  list-style: none;
  padding: 0;
  margin: var(--space-2) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.recent-days a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: var(--space-1) var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  text-decoration: none;
  font-variant-numeric: tabular-nums;
}

.recent-days a:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* Secondary actions grouped at the foot of the dashboard: labeled link groups for
   export, import, data management, and model edits, kept below the charts so flags
   and the verdict keep the top of the page. */
.actions-card .action-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.action-group-label {
  flex: 1 1 100%;
  font-size: var(--text-micro);
  color: var(--text-muted);
}

/* A chip's label can be a filename, which is one unbreakable word: at 200% text it is wider
   than the card, so it is allowed to break inside rather than carry the page sideways. */
.actions-card a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  max-width: 100%;
  padding: var(--space-1) var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  text-decoration: none;
  font-size: var(--text-small);
  overflow-wrap: anywhere;
}

.actions-card a:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* The page's closing report: what the notification settings say, in a bordered panel of its
   own rather than as a stray line under the card. Deliberately no chip and no hue — it is a
   statement of configuration, not an action to take or a state to worry about. */
.digest-state {
  margin: var(--space-3) 0 0;
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-size: var(--text-small);
}

/* The review-window control claims its own wrapping row inside the Export group: the label,
   From/To pickers, and reset chip flow inline; the hint takes a full-width line beneath them. */
.review-window {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  /* The landing ring this group takes as a fragment target is drawn around its box, so the
     box has to be one worth ringing: the radius gives the outline the card's own corner, and
     the padding, with the ring's own 2px offset, keeps the date inputs off it — flush against
     the ring they read as clipped. Constant rather than focus-only, so nothing shifts under
     the tap that lands here. The negative inline margin pays the inset back to the card, so
     this row's content still starts on the same left edge as the chips above it rather than a
     step in from them. One step and no more: this inset already makes that clearance, and the
     payback is inline only, so a wider one would spend itself on the row's height for no
     legibility. */
  padding: var(--space-1);
  margin-inline: calc(-1 * var(--space-1));
  border-radius: var(--radius-md);
  /* The review recommendation lands a reader here, and the download this control aims sits
     one chip-row above it: clearing that row as well as the header is what puts the whole
     workflow on screen at once on the shortest phone. */
  scroll-margin-top: calc(
    var(--header-clearance) + var(--touch-target) + var(--space-3)
  );
}

/* In this card the pair is a row of its own rather than a tail on a bar of buttons: each
   picker claims a column, its label may sit above it where the column is too narrow to hold
   both, and the input fills whatever the column has. Together with the shrink guard the shared
   `.range-date` rule carries, that is what keeps a 200%-text phone from scrolling sideways
   here. The two grow back onto one row as soon as two 9rem columns fit. */
.review-window .range-date {
  flex: 1 1 9rem;
  flex-wrap: wrap;
}

.review-window .range-date input {
  flex: 1 1 auto;
}

/* The base button rule already supplies the surface-2 fill, border, radius, and 44px height;
   only the chip's tighter padding and type size need matching here. */
#review-window-reset {
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-small);
}

#review-window-reset:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.review-window-hint {
  flex: 1 1 100%;
  margin: 0;
  font-size: var(--text-micro);
}

.review-window-hint--error {
  color: var(--alert);
}

/* `.actions-card a` renders every link in this card as a chip; the hint's guide link is a text
   affordance, so restore the inline `.learn-more` look and hue here. */
.review-window-hint .learn-more {
  display: inline;
  min-height: 0;
  padding: 0;
  background: none;
  border: none;
  color: var(--accent);
  font-size: var(--text-caption);
}

.review-window-hint .learn-more:hover {
  color: var(--accent-strong);
}

/* The snap dates take their own full-width block under the hint: a lead line, a note, then one
   group per stretch. Each chip stacks the input it fills and its date over the boundary that
   induced it. */
.review-snap {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Each of these claims its own line: the stretch groups stack rather than sharing a row, and
   inside one the span heading sits above its chips rather than beside them. */
.review-snap > .controls-label,
.review-snap-note,
.review-snap-stretch,
.review-snap-stretch > .controls-label {
  flex: 1 1 100%;
}

.review-snap-note {
  margin: 0;
  font-size: var(--text-micro);
}

/* The rule down the left binds a stretch's heading to its own chips, so a From chip and a To
   chip read as the two ends of one stretch rather than as neighbours in a list. */
.review-snap-stretch {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--space-2);
  padding-left: var(--space-3);
  border-left: 3px solid var(--border);
}

/* The base button rule supplies the fill, border, radius, and 44px height; the chip only
   stacks its two lines and tightens the padding. The shared basis keeps a stretch's two chips
   the same width side by side, so they read as its two ends until the row is too narrow for
   both — then each takes a line of its own and the reason text wraps inside it. */
.review-snap-btn {
  display: inline-flex;
  flex: 1 1 16rem;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-3);
  max-width: 100%;
  text-align: left;
}

.review-snap-btn:hover {
  border-color: var(--accent);
}

.review-snap-date {
  font-size: var(--text-caption);
}

.review-snap-reason {
  font-size: var(--text-micro);
  color: var(--text-muted);
}

/* ============================================================
   Import page
   ============================================================ */

/* Upload page: a centered card holding the three-file explainer above the upload form. */
.import-panel {
  max-width: 30rem;
  margin-inline: auto;
}

/* The bundle's three members as a compact contents list. */
.import-contents {
  margin: var(--space-3) 0 0;
  padding-left: 1.1rem;
  font-size: var(--text-small);
}

.import-contents li {
  margin: var(--space-1) 0;
}

/* Upload form inside the panel. The native file input's selector button is restyled to
   match a secondary button, so the whole control reads as one designed piece with no
   drag-drop scripting. */
.import-upload {
  margin-top: var(--space-4);
}

.import-file {
  display: block;
  margin: 0 0 var(--space-4);
}

.import-file-label {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--text-small);
  color: var(--text-muted);
}

.import-upload input[type="file"] {
  display: block;
  width: 100%;
  font: inherit;
  padding: var(--space-2);
  color: var(--text-muted);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.import-upload input[type="file"]::file-selector-button {
  font: inherit;
  margin-right: var(--space-3);
  padding: var(--space-2) var(--space-4);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.import-upload input[type="file"]::file-selector-button:hover {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
}

.import-upload .btn-primary {
  width: 100%;
}

/* Preview summary reuses .stat-grid tiles; the untouched-rows line rides below
   them as a full-width .stat-row context line. */

/* Each conflict/new row is one of the shared fieldset cards, carrying its date as the
   legend — hence the tabular numerals a dated legend needs. */
.import-apply legend {
  font-variant-numeric: tabular-nums;
}

.import-apply label.checkbox {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  gap: var(--space-2);
}

.import-submit {
  margin-top: var(--space-2);
}

/* Bulk conflict-choice toolbar: a surface action bar above the conflict cards. It
   ships [hidden] and is revealed by JS, so the display declaration needs the
   [hidden] guard at the top of this file to stay hidden until then. */
.import-bulk {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  padding: var(--space-2) var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

/* Stored-vs-uploaded choice as two segmented option chips filling the row; the
   chosen chip picks up the accent tint (native radio marker as JS-off fallback). */
.import-choice {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.import-choice .checkbox {
  flex: 1 1 0;
  justify-content: center;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.import-choice .checkbox:has(input:checked) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, var(--surface));
}

.import-new dl {
  margin: var(--space-2) 0 0;
}

/* The diff table scrolls inside its own box on narrow screens so the page body itself
   never gains a horizontal scrollbar. Scrolling sideways is the deliberate choice for
   this table rather than restacking each row into a card: it is read column against
   column, and stacking would break the comparison it exists to make. */
.import-diff-scroll {
  /* The cover has to be painted in whatever the box actually sits on, or it reads as a
     band of its own; a host on another backdrop overrides this. */
  --diff-cover: var(--bg);
  overflow-x: auto;
  margin-top: var(--space-3);
  /* Touch draws no scrollbar to say the columns continue, so the edges say it: a band
     fixed to the box shades a side with more table past it, and a backdrop-colored band
     riding the content covers that shade once the content's own edge arrives. The shade
     darkens whatever it lands on rather than naming a color, so it survives being reused
     over another backdrop; it stays light enough that the outermost column is not read
     through a heavy wash. */
  background:
    linear-gradient(to right, var(--diff-cover), transparent) 0 0 / var(--space-5) 100%
      no-repeat local,
    linear-gradient(to left, var(--diff-cover), transparent) 100% 0 / var(--space-5) 100%
      no-repeat local,
    linear-gradient(to right, color-mix(in srgb, #000 45%, transparent), transparent) 0 0 /
      var(--space-5) 100% no-repeat scroll,
    linear-gradient(to left, color-mix(in srgb, #000 45%, transparent), transparent) 100%
      0 / var(--space-5) 100% no-repeat scroll;
}

.import-apply fieldset .import-diff-scroll {
  --diff-cover: var(--surface);
}

.import-diff {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

/* Tighter rows than the standard vertical rhythm: many rows are scanned against each
   other in one look here, so the extra density is deliberate. */
.import-diff th,
.import-diff td {
  padding: var(--space-1) var(--space-3);
  text-align: left;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.import-diff th {
  color: var(--text-muted);
  font-weight: 600;
}

/* Finding reading panel: the proposal's rationale in a bordered surface, and the same
   surface for a loader's own words about a file it refused. The <pre> keeps the line breaks
   the text was written with -- for a YAML fault, the line and column it ends on -- but wraps
   long lines so the page body never scrolls. */
.finding-panel,
.error-detail {
  margin-top: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow-x: auto;
}

.finding-panel pre,
.error-detail pre {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: var(--text-small);
  line-height: 1.5;
}

/* Wrapping action row: the apply/cancel pair on the proposal preview and calibration
   pages, and the confirm/cancel pair inside the destructive-action panel (prune-confirm,
   day-clear, findings-delete, delete-all) — so the primary and cancel controls line up
   rather than stacking awkwardly. */
.proposal-actions,
.prune-actions,
.confirm-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* Invalid rows: an alert-tinted card so a skipped row reads as an error, not an option. */
.import-invalid {
  margin: var(--space-3) 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid color-mix(in srgb, var(--alert) 35%, var(--border));
  border-left: 3px solid var(--alert);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--alert) 8%, var(--surface));
}

.import-invalid-head {
  margin: 0 0 var(--space-1);
  font-weight: 600;
}

.import-invalid ul {
  margin: 0;
}

/* ============================================================
   Data pages
   ============================================================ */

/* The overview's own contents list, so every task on the page is one tap away from the top
   of it. The row and its chips are the shared "on this page" nav (see Component
   primitives); only the block around them is the data page's own. */
.page-contents {
  margin: var(--space-4) 0;
}

/* One lifecycle group of stores: its heading, its one-line lead, and the rows under it. */
.store-group {
  margin: var(--space-6) 0 0;
}

.store-group > h2 {
  margin-bottom: var(--space-2);
}

.store-rows {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* One store as a compact row: its name and file, what it is in a line, then its figures.
   Deliberately not a card -- seventeen cards is the page this replaces, and the row exists so
   the whole inventory can be read at once and any one of them opened from there. */
.store-row {
  padding: var(--space-2) var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.store-row-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
}

/* The link fills the row's first line and keeps a full touch target of its own: it is the
   only control on the row, and the row is scanned on a phone. */
.store-row-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.store-row-file {
  font-size: var(--text-micro);
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

.store-row-role {
  margin: 0;
  font-size: var(--text-small);
}

/* The figures and the two policy words, one wrapping row: each stays whole as it wraps, so
   a narrow screen breaks between facts rather than inside one. */
.store-row-facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  margin: var(--space-2) 0 0;
  font-size: var(--text-small);
}

.store-fact {
  color: var(--text-muted);
  white-space: nowrap;
}

/* The exact byte count beside the rounded size, on both data surfaces -- what the hover title
   it replaces could only offer a mouse. It sits a step below the figure it qualifies so it
   stays detail rather than reading as a second size, in the tabular figures a column of store
   sizes is compared in. */
.size-exact {
  font-size: var(--text-micro);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* In the store's own tile it takes the line under the figure rather than running on from it:
   side by side inside one tile the two would read as two separate facts. The tile's weight is
   the headline figure's, not this one's. */
.stat-value .size-exact {
  display: block;
  font-weight: 400;
}

.store-badge {
  padding: 2px var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text-muted);
  font-size: var(--text-micro);
  white-space: nowrap;
}

/* The copy actions, as their own labelled block above the destructive section: taking one
   is not a critical action, and grouping it with them made it read like one. */
.store-transfer {
  margin-top: var(--space-6);
}

.store-transfer > h2 {
  margin-top: 0;
}

/* The two ways in, as sentences rather than a row of buttons: which one a reader wants
   turns on the difference between them, which is the text and not the link. */
.transfer-links {
  margin: 0;
  padding-left: var(--space-4);
  font-size: var(--text-small);
  color: var(--text-muted);
}

.transfer-links li + li {
  margin-top: var(--space-2);
}

/* What the zip holds and leaves out, folded away wherever it is offered: the sentence a
   reader needs stands outside it, and these lists are what they check before deciding.
   The toggle wears the same chevron grammar as the page's other compact disclosures. */
.archive-manifest {
  margin: var(--space-3) 0 0;
  font-size: var(--text-small);
}

.archive-manifest-body ul {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-4);
  color: var(--text-muted);
}

.archive-manifest-body > p {
  margin: 0 0 var(--space-1);
}

/* The files the instance holds that no store owns: a footnote to the page, set apart from
   the destructive section above it and never styled as another group of stores. */
.page-note {
  margin-top: var(--space-6);
  font-size: var(--text-small);
  color: var(--text-muted);
}

.page-note ul {
  margin: 0;
  padding-left: var(--space-4);
}

.page-note li + li {
  margin-top: var(--space-2);
}

/* A store's own page: the policy summary, the surface that owns its contents, and the
   inspection panel that sits directly above the controls that remove from it. */
.store-policy {
  margin-bottom: var(--space-3);
}

.store-owner {
  margin: 0 0 var(--space-4);
  font-size: var(--text-small);
}

.store-inspection > h2 {
  margin-top: 0;
}

.store-facts {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--text-small);
  overflow-wrap: anywhere;
}

/* Retention note sits as a muted caption between a store's stats and its actions. */
.store-retention {
  margin: var(--space-3) 0 var(--space-4);
  font-size: var(--text-small);
}

/* The download button(s) for a store, as one action row. */
.store-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
}

/* The age-prune control: its dated cutoff picker above the preview button, wrapping
   to stack on the narrowest screens. */
.prune-form {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

.prune-form label,
.range-form .field-pair label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--text-small);
  color: var(--text-muted);
}

/* The date-range control, stacked: its lead line, the two endpoints side by side, the
   open-start choice, then the preview button. */
.range-form {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.range-lead {
  margin: 0;
  font-size: var(--text-small);
  color: var(--text-muted);
}

/* From and To are the two ends of one answer, so they hold one row at phone width too
   rather than taking the shared grid's full row each: a date control needs about half a
   phone to read, and reading them apart is what makes a range easy to get backwards. The
   auto-fit floor is the fallback -- a viewport too narrow for two legible controls drops
   them back into a column instead of squeezing both. */
.range-form .field-pair {
  width: 100%;
  grid-template-columns: repeat(auto-fit, minmax(min(8.5rem, 100%), 1fr));
  column-gap: var(--space-2);
  align-items: start;
}

/* Full-width in its column, like the other controls on this form; what keeps the native
   control from beating that width is the app-wide date-input rule. */
.range-form input[type="date"] {
  width: 100%;
}

/* From while the tick beside it stands in for a start date: it is filled in with the day
   that store begins on, and that day is there to be read rather than typed over, so it
   wears the muted text of a value the reader does not set. */
.range-form input[type="date"]:disabled {
  color: var(--text-muted);
  cursor: not-allowed;
}

/* The exact dates a range removes: as many short columns as the width allows, and its own
   scroll past a few screens' worth, so opening a long range never buries the button that
   applies it. */
.range-dates {
  margin-top: var(--space-3);
  font-size: var(--text-small);
}

.range-date-list {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
  gap: var(--space-1) var(--space-3);
  max-height: 40vh;
  overflow-y: auto;
  margin: var(--space-2) 0 0;
  padding: 0;
}

/* Findings directory: one bordered row per document, its name/size on the left and
   its download/delete actions on the right. */
.findings-list {
  list-style: none;
  padding: 0;
  margin: var(--space-4) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.findings-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.findings-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  font-size: var(--text-small);
}

.findings-name {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.findings-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* Confirm-prune page: the removal count as the page's headline figure. */
.prune-headline {
  margin: var(--space-4) 0;
}

.prune-count {
  margin: 0 0 var(--space-2);
  font-size: 1.1rem;
}

.prune-consequence {
  margin: 0;
}

/* A checkbox that carries a sentence rather than a word -- the delete-all confirm's
   full-reset choice, the range form's open-start choice, the sweep's store choices --
   aligns to the first line of that sentence rather than to the middle of it. The 44px
   minimum every other checkbox in the app clears is set here too: a sentence long enough
   to wrap already exceeds it, but the shortest of these is one line and would not. */
.delete-all-form label.checkbox,
.range-form label.checkbox {
  display: flex;
  align-items: flex-start;
  min-height: var(--touch-target);
  gap: var(--space-2);
}

/* Delete-all confirm: the full-reset choice and the typed gate stack as separate blocks
   above the apply button, so each is read and answered on its own. */
.delete-all-form label.checkbox {
  margin: 0 0 var(--space-4);
}

.confirm-gate {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-width: 16rem;
  margin: 0 0 var(--space-4);
}

/* The sweep's store picker: one full-width choice per line inside the shared fieldset card,
   each its own tap target rather than a row of boxes to aim between. Each choice names a
   store, its axis, and the day it begins on -- a sentence, which runs onto a second line at
   phone width -- so it takes the first-line alignment above and only sets its own size. */
.sweep-members {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.sweep-members label.checkbox {
  font-size: var(--text-small);
}

/* The date the oldest day names reads wrong when broken, and it is short enough to keep
   whole at any text size. The parenthesised axis beside it is not: held whole it is wider
   than the row at large text and takes the page sideways, so it wraps like the rest of the
   label. */
.sweep-members label.checkbox .sweep-day {
  white-space: nowrap;
}

/* ============================================================
   Goals editor
   ============================================================ */

/* The day-form full-width stacking is scoped to form[action^="/day/"], so the goals
   form carries its own equivalent: label above a full-width control, no page overflow.
   The plan declaration form is the same shape of form and wears the same rules. */
.goals-form label,
.goal-plan-form label {
  display: block;
  margin: var(--space-3) 0 var(--space-1);
}

.goals-form input,
.goal-plan-form input,
.goal-plan-form select {
  display: block;
  width: 100%;
  margin-top: var(--space-1);
}

.goals-form button,
.goal-plan-form button {
  margin-top: var(--space-4);
}

/* The declaration's two range fields sit two-up on the shared grid; the cells inside it
   keep the block stacking above rather than the grid's own inline default. */
.goal-plan-form .field-pair > label {
  margin-top: 0;
}

/* ============================================================
   Dashboard charts
   ============================================================ */

.dashboard {
  margin-top: 1.75rem;
}

/* The section margin sets the gap above the heading; drop its own top margin so the
   two don't stack. */
.dashboard > h2 {
  margin-top: 0;
}

/* Each canvas sits in a bordered surface card; the fixed height and relative
   positioning are what Chart.js needs to size the responsive canvas, and overflow
   clipping keeps the chart inside the rounded corners. */
.chart-card {
  position: relative;
  height: 300px;
  margin: 0 0 var(--space-5);
  /* Inset the canvas past the corner radius so chart-drawn text is never
     clipped by the overflow-hidden rounding. */
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.chart-card canvas {
  max-width: 100%;
}

/* A range with nothing to draw keeps its card and shows the card's note in place of
   the chart: the card sheds the height it reserves for a canvas and takes the roomier
   padding a text card wants. Compounded so it also outranks the wide-screen height
   below. */
.chart-card.chart-card-empty {
  height: auto;
  padding: var(--space-4);
}

.chart-caption {
  margin: calc(-1 * var(--space-3)) 0 var(--space-5);
  font-size: var(--text-caption);
  line-height: 1.35;
}

/* The caption rule's negative top margin tucks a caption under its card; inside the
   card there is nothing to tuck under. */
.chart-card > .chart-caption {
  margin: 0;
}

/* Each chart's own name over its card. The section heading names the group; without these
   the section is four unlabelled canvases, and the heading list a screen reader navigates
   by skips straight from Charts to whatever follows them. */
.chart-heading {
  margin: var(--space-5) 0 var(--space-2);
}

/* The chart's reading in words, attached under the card it reads: the one surface where a
   dated value, a hold reason, or an exclusion is reachable with no pointer to hover. Not
   another bordered card — a lighter fill and the snug tuck the toggles use, so it reads as
   this chart's readout rather than as a second surface competing with it. */
.chart-reading {
  margin: calc(-1 * var(--space-3)) 0 var(--space-5);
  padding: var(--space-3);
  background: var(--surface-2);
  border-radius: var(--radius-sm);
}

/* The day leads the reading and wears the emphasis: every figure under it belongs to that
   morning rather than to today. */
.chart-reading-date {
  margin: 0;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.chart-reading-values {
  margin: var(--space-2) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-caption);
  line-height: 1.4;
  color: var(--text-muted);
}

.chart-reading-values li + li {
  margin-top: var(--space-1);
}

.chart-reading-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.chart-reading-step {
  font: inherit;
  font-size: var(--text-caption);
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}

/* At the window's edge the button is marked rather than `disabled`, so it keeps the focus the
   reader walked it to and no `:disabled` styling can reach it: without this the pixels say
   available while the accessibility tree says otherwise. The pointer is dropped rather than
   turned to `not-allowed`, which would read as broken on a control that is still focusable. */
.chart-reading-step[aria-disabled="true"] {
  color: var(--text-muted);
  cursor: default;
}

/* The one control here that leaves the page stands apart from the two that only move the
   reading, so a deliberate navigation is never taken for another step. */
.chart-reading-open {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  margin-left: auto;
  font-size: var(--text-caption);
}

#dashboard-empty {
  margin-top: var(--space-2);
}

/* A load/fetch failure is not the same as "no data yet": tint it like an alert
   so a broken pipeline reads differently from an empty one. */
#dashboard-empty.note-error {
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--alert);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--alert) 12%, var(--surface));
  color: var(--text);
}

/* Shared range selector above the charts, framed as one segmented control bar: the
   preset buttons and the From/To pickers wrap within the bar at narrow widths. */
.chart-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-5);
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

.controls-label {
  font-size: var(--text-micro);
  color: var(--text-muted);
  margin-right: var(--space-1);
}

/* min-width earns its place on the shortest label: "All" is under the touch baseline on its
   padding alone, where the day presets clear it. The bar wraps, so the wider button costs a
   narrow screen a row at worst, never an overflow. */
.range-btn {
  font: inherit;
  font-size: var(--text-caption);
  min-height: var(--touch-target);
  min-width: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
}

.range-btn[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
}

/* From/To custom-range pickers share the range bar with the preset buttons. The base
   input[type="date"] rule already supplies the 44px height, border, fill, and focus ring;
   these only set the inline label and keep each label+input pair together as it wraps.
   Both pairs may shrink below the width of the date they render: a native date control is as
   wide as its own text, which at 200% text is wider than a 320px screen, and a flex item that
   will not shrink under its content pushes the whole document sideways instead. */
.range-date {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
  font-size: var(--text-micro);
  color: var(--text-muted);
}

.range-date input {
  min-width: 0;
  max-width: 100%;
  font-size: var(--text-caption);
}

/* Per-chart toggle rendered as a switch, pulled up snug under its (fixed-height)
   chart card. The native checkbox is restyled into the switch and its box spans the
   full 44px row height, so the tap target clears the minimum rather than shrinking to
   the ~18px of a default control. */
.chart-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  margin: calc(-1 * var(--space-3)) 0 var(--space-5);
  font-size: var(--text-caption);
  color: var(--text);
  cursor: pointer;
}

.chart-toggle input {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex-shrink: 0;
  width: 2.9rem;
  height: var(--touch-target);
  margin: 0;
  background: none;
  border: none;
  cursor: pointer;
}

.chart-toggle input::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  transform: translateY(-50%);
  width: 2.9rem;
  height: 1.5rem;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  transition: background 0.15s ease, border-color 0.15s ease;
}

.chart-toggle input::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 4px;
  transform: translateY(-50%);
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 50%;
  background: var(--text-muted);
  transition: left 0.15s ease, background 0.15s ease;
}

.chart-toggle input:checked::before {
  background: var(--accent);
  border-color: var(--accent);
}

.chart-toggle input:checked::after {
  left: calc(2.9rem - 1.1rem - 4px);
  background: var(--accent-contrast);
}

/* The input's box is stretched to a full touch target, so the shared focus ring would
   frame empty space above and below the switch. Ring the drawn pill instead. */
.chart-toggle input:focus-visible {
  outline: none;
}

.chart-toggle input:focus-visible::before {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (min-width: 40rem) {
  .chart-card {
    height: 360px;
  }
}

/* Prediction-accuracy card: a textual summary (no canvas) below the charts, framed with
   the roomier padding the other hero-figure cards get. It carries its own margin because
   it is not the direct .container child the shared card rule reaches. */
.calibration-card {
  margin: 0 0 var(--space-5);
  padding: var(--space-5);
}

.calibration-headline {
  margin: 0;
}

.calibration-label {
  display: block;
}

.calibration-sublabel {
  margin: var(--space-2) 0 var(--space-4);
  font-size: var(--text-caption);
  line-height: 1.35;
}

/* The card answers three questions, and each gets its own band so a reader can find the one
   they came for instead of reading a column of equally-weighted muted lines. The rule between
   bands does the separating; the heading above each names it. */
.calibration-section + .calibration-section {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

/* A register of its own, distinct from both the hero figure and the prose under it: the
   display face at the smallest step, tracked and upper-cased, in the accent hue. It names a
   band rather than opening a section of the page, so it wears none of the h3 size. */
.calibration-section-heading {
  margin: 0 0 var(--space-3);
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

/* The span the estimate was read over, as its own object rather than another muted line: a
   raised inset sized to its own text, so the dates read as the thing the counts below
   describe. */
.calibration-window {
  display: inline-block;
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: var(--text-small);
  font-variant-numeric: tabular-nums;
}

.calibration-window-counts {
  margin: var(--space-2) 0 0;
  font-size: var(--text-caption);
  font-variant-numeric: tabular-nums;
}

/* The preview states the span and the row count on one line, so it stays a plain line: an
   inset sized to its own text would box a sentence rather than an object. */
.calibration-window-line {
  margin: var(--space-3) 0 0;
  font-size: var(--text-caption);
  font-variant-numeric: tabular-nums;
}

.calibration-insufficient {
  margin: 0;
  font-size: 0.95rem;
}

/* Two destinations, not a stack: a wrapping row so they read as sibling choices, each still
   wearing the notice family's trailing-link look. */
.calibration-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  margin: var(--space-4) 0 0;
}

.calibration-links .learn-more {
  margin-top: 0;
}

/* ============================================================
   Calibration page
   ============================================================ */

/* The eligible offer wears an accent-tinted frame so it reads as an offer to accept,
   sibling to the dashboard's accent calibration banner. */
.calibration-offer-card {
  padding: var(--space-5);
  border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
}

/* The offer's from -> to base as the page's hero figures: the current base (muted, being
   left behind) resolving to the calibrated one at the shared hero size. */
.calibration-offer {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  margin: var(--space-1) 0 0;
  line-height: 1.1;
}

.calibration-from,
.calibration-arrow {
  color: var(--text-muted);
}

.calibration-arrow {
  font-weight: 400;
}

/* Blocked reasons: a plain list with a little breathing room between conditions. */
.calibration-blocked {
  margin: var(--space-2) 0 0;
}

.calibration-blocked li {
  margin: var(--space-1) 0;
}

/* ============================================================
   Settings page
   ============================================================ */

/* The way in to each section, in the order the page holds them: the shared "on this page"
   chip nav (see Component primitives). The index stands on its own above the first card,
   so it pays for the gap under itself; the guide's row sits inside a labelled block that
   pays that gap for it. */
.settings-index ul {
  margin-bottom: var(--space-4);
}

/* A section's own long account of itself, kept out of the way of the switch it explains:
   shut, a section reads heading then control, which is what a reader came here to change.
   Only explanation lives behind it — a group's warning and every banner stay on the page.
   A quieter toggle than the cards the day page and the guide open, sharing their chevron
   (see the Guide page section) inside this card rather than opening one. */
.settings-note {
  margin: var(--space-2) 0 var(--space-3);
}

.settings-note > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  list-style: none;
  color: var(--text-muted);
  font-size: var(--text-small);
  cursor: pointer;
}

.settings-note > summary:hover {
  color: var(--accent);
}

/* The account opens clear of the line that opened it. */
.settings-note > summary + p {
  margin-top: var(--space-2);
}

/* Same stacking the goals form carries, for the same reason: the day form's full-width
   rule is scoped to its own action prefix. */
.settings-form label {
  display: block;
  margin: var(--space-3) 0 var(--space-1);
}

.settings-form input,
.settings-form select {
  display: block;
  width: 100%;
  margin-top: var(--space-1);
}

/* A time or a picked option holds a few characters: sized to that content the
   control reads as a set value rather than an empty bar. Free-text entry (name,
   URL) keeps the full-width stretch above. */
.settings-form input[type="time"],
.settings-form select {
  width: auto;
  /* Never wider than the field it sits in: at large text a bare 9rem outgrows the card. */
  min-width: min(9rem, 100%);
}

.settings-form label.checkbox {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  margin-bottom: 0;
}

.settings-form label.checkbox input {
  display: inline-block;
  width: auto;
  margin: 0;
}

.settings-form button {
  margin-top: var(--space-4);
}

/* Each digest kind is one ruled group — the toggle, then the fields it governs —
   the same paired rhythm the content groups below carry. */
.schedule-kind + .schedule-kind {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}

/* Day and time answer one question, so they share a line where they fit;
   vertical rhythm stays with the labels' own margins when the row wraps. */
.schedule-when {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-4);
}

/* One toggle plus everything said about it: the description, and the warning when the
   group is currently degraded. Ruled off so a long list stays scannable as pairs. */
.content-group + .content-group {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}

.content-group .field-help,
.content-group .callout-warning {
  margin: var(--space-1) 0 0;
}

.target-list {
  list-style: none;
  margin: var(--space-3) 0;
  padding: 0;
}

.target-row {
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border);
}

.target-identity {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
}

.target-name {
  font-weight: 600;
}

/* Whether this target is being sent to, as a word and a dot in the same hue: the dot is what
   carries down a list of targets at a glance, the word is what carries without colour. */
.target-state {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-caption);
}

.target-state::before {
  content: "";
  flex: none;
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: currentColor;
}

.target-state.is-enabled {
  color: var(--ok);
}

/* The alert hue rather than the muted one: a target that has been switched off is a digest
   with one fewer place to go, which is a state to notice and not a detail to fade out. */
.target-state.is-disabled {
  color: var(--alert);
}

.target-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

/* ============================================================
   Error page
   ============================================================ */

.error-page {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
  text-align: center;
}

.error-page .card {
  max-width: 28rem;
}

/* A card carrying steps rather than a sentence: the heading stays centred, the body goes
   flush left, because a centred command wraps into something nobody can scan or read back. */
.error-page .card.error-page-steps p {
  text-align: left;
}

/* The ways out of a failure, centered under the card's centered text rather than flush
   left as an action row sits everywhere else. */
.error-page .store-actions {
  justify-content: center;
  margin-bottom: 0;
}

/* ============================================================
   Sign in
   ============================================================ */

/* The page nobody is signed in for: one card held in the middle of the viewport the way
   the error page above holds its own, and left-aligned, because what is in it is a form
   rather than a sentence. */
.login-page {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
}

.login-page .card {
  width: 100%;
  max-width: 24rem;
}

.login-form label {
  display: block;
  margin-bottom: var(--space-1);
}

.login-form input,
.login-form button {
  display: block;
  width: 100%;
}

.login-form button {
  margin-top: var(--space-4);
}

/* The line under the form is an aside, not a caption on the button above it, so it keeps
   its distance -- and nothing follows it inside the card. */
.login-page .field-help {
  margin: var(--space-3) 0 0;
}

/* ============================================================
   Guide
   ============================================================ */

/* Lead paragraph under the title: the "what this app is" intro, one step up from body. */
.guide-hero {
  margin: 0 0 var(--space-5);
}

.guide-hero p {
  margin: 0;
  font-size: 1.05rem;
}

/* Anything after the lead is a standing scope note rather than more of the intro, so it is
   set apart and set smaller — which also keeps the section list below it on the first
   screen of a phone. It keeps the normal text colour: it is the page's one limits-of-use
   statement, and muting it would read as small print. */
.guide-hero p + p {
  margin-top: var(--space-3);
  font-size: var(--text-small);
}

/* Table of contents: a wrapping row of link chips, one per section — the same chip shape
   as the dashboard's recent-day and action links; the pill shape stays reserved for state
   chips and the primary nav action. The row and its chips are the shared "on this page"
   nav (see Component primitives); only the block around them lives here. */
.guide-toc {
  margin: 0 0 var(--space-5);
}

.guide-toc-label {
  margin: 0 0 var(--space-2);
  font-size: var(--text-micro);
  color: var(--text-muted);
}

/* Each section is a disclosure card built on <details>: the heading rides in the
   summary and the body follows. Ships open; a narrow-screen first load collapses all
   but the first (progressive JS), and JS-off leaves them all open. */
.guide-section {
  margin: 0 0 var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

/* The summary is the full-width toggle: a ≥44px tap target with our own chevron in
   place of the UA disclosure marker. Shared with the day form's own sections, which are
   the same kind of card opened the same way. */
.guide-section > summary,
.day-section > summary {
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
}

.guide-section > summary::-webkit-details-marker,
.day-section > summary::-webkit-details-marker,
.settings-note > summary::-webkit-details-marker {
  display: none;
}

/* min-width:0 overrides the flex-item default (min-content): at large text a one-word
   section name is wider than the summary row, and without it the row grows to fit rather
   than letting the name wrap. */
.guide-section > summary h2 {
  margin: 0;
  font-size: var(--h2);
  min-width: 0;
}

.guide-section > summary:hover h2,
.day-section > summary:hover .day-section-name {
  color: var(--accent);
}

/* Chevron that points down when closed and up when open. */
.guide-section > summary::after,
.day-section > summary::after,
.settings-note > summary::after {
  content: "";
  flex-shrink: 0;
  margin-left: auto;
  width: 0.55rem;
  height: 0.55rem;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: rotate(45deg);
  transition: transform 0.15s ease;
}

.guide-section[open] > summary::after,
.day-section[open] > summary::after,
.settings-note[open] > summary::after {
  transform: rotate(-135deg);
}

.guide-section-body,
.day-section-body {
  padding: 0 var(--space-4) var(--space-4);
}

/* Inline code stays inside its column on the narrowest screens. ``anywhere`` rather than
   ``break-word``: only the former counts a broken token as narrow when the grid measures
   the entry it sits in, and a dotted config key is longer than the column that holds it. */
.guide-hero code,
.guide-section code {
  padding: 0 0.25em;
  background: var(--surface-2);
  border-radius: 4px;
  font-size: 0.9em;
  overflow-wrap: anywhere;
}

/* Key terms and the customizable tiers: each dt/dd pair is a bordered entry stacked
   top-to-bottom, overriding the global two-column dl grid. */
.guide-terms {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}

.guide-terms > div {
  /* Overrides the grid-item default (min-content): an entry defining a long config key is
     otherwise as wide as that key and reaches past the card it sits in. */
  min-width: 0;
  padding: var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.guide-terms dt {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--text);
  letter-spacing: var(--tracking-tight);
}

.guide-terms dd {
  margin: var(--space-1) 0 0;
  color: var(--text-muted);
}

/* A term whose definition needs more than a sentence brings a list and follow-on paragraphs
   with it; the UA's own indent and paragraph rhythm are too loose inside an entry this size. */
.guide-terms dd ul {
  margin: var(--space-2) 0;
  padding-left: var(--space-5);
}

.guide-terms dd li + li,
.guide-terms dd p {
  margin: var(--space-2) 0 0;
}

/* Calibration gate table. Mobile-first: each gate restacks into a labeled card so it
   never forces horizontal scroll; at ≥40rem it returns to a real table. */
.gate-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-small);
}

.gate-table thead {
  /* The per-cell data-label carries the column name in the stacked layout, so the
     real header row is hidden until the table form returns at ≥40rem. */
  display: none;
}

.gate-table tr {
  display: block;
  margin: 0 0 var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.gate-table td {
  display: block;
  padding: var(--space-1) 0;
}

.gate-table td::before {
  content: attr(data-label);
  display: block;
  font-size: var(--text-micro);
  font-weight: 600;
  color: var(--text-muted);
}

@media (min-width: 40rem) {
  .gate-table thead {
    display: table-header-group;
  }

  .gate-table th {
    padding: var(--space-2) var(--space-3);
    text-align: left;
    border-bottom: 1px solid var(--border);
    color: var(--text-muted);
    font-weight: 600;
    vertical-align: top;
  }

  .gate-table tr {
    display: table-row;
    margin: 0;
    padding: 0;
    background: none;
    border: none;
    border-radius: 0;
  }

  .gate-table td {
    display: table-cell;
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--border);
    vertical-align: top;
  }

  .gate-table td::before {
    display: none;
  }
}

/* ============================================================
   Motion
   ============================================================ */

/* The switch slide and the disclosure chevron are decoration around a state change the
   layout already shows, so they simply stop for anyone who asks for less motion. */
@media (prefers-reduced-motion: reduce) {
  .chart-toggle input::before,
  .chart-toggle input::after,
  .guide-section > summary::after,
  .day-section > summary::after,
  .settings-note > summary::after,
  .recent-days > summary::after,
  .range-dates > summary::after {
    transition: none;
  }
}
