/**
 * shared/styles.css
 *
 * One design system for the whole app — every screen links this file
 * so the client app, trainer dashboard, and every sub-screen look and
 * feel like one product, not a pile of separately-styled prototypes.
 * Same CSS variable approach as pose-trainer/index.html, extended
 * with the additional components this larger app needs (nav, tabs,
 * cards, badges, forms).
 */

@import url("theme.css");  /* palette lives in one place — see theme.css */

:root[data-theme="premium"] .top-bar .title,
:root[data-theme="premium"] .section-label,
:root[data-theme="premium"] #greeting { font-family: ui-serif, Georgia, "Times New Roman", serif; }

/* Segmented control — theme + units pickers */
.theme-switch { display: flex; gap: 6px; flex-wrap: wrap; }
.theme-switch button {
  flex: 1 1 auto; min-width: 62px; padding: 9px 8px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--card); color: var(--text); font-size: 13px; font-weight: 600; cursor: pointer;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.theme-switch button[aria-pressed="true"] { background: var(--fill-accent); color: var(--on-accent); border-color: var(--fill-accent); }

/* Toggle switch (replaces the default checkbox in settings rows) */
.list-row input[type="checkbox"] {
  appearance: none; -webkit-appearance: none; margin: 0; cursor: pointer; flex: none;
  width: 42px; height: 25px; border-radius: 999px; background: var(--border);
  position: relative; transition: background-color .18s ease;
}
.list-row input[type="checkbox"]::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 21px; height: 21px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.25);
  transition: transform .18s ease;
}
.list-row input[type="checkbox"]:checked { background: var(--fill-accent); }
.list-row input[type="checkbox"]:checked::after { transform: translateX(17px); }
.list-row input[type="checkbox"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

body { transition: background-color .25s ease, color .25s ease; }

/* Staggered card entrance (used on the Settings tab) */
@keyframes cardIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.card.anim-in { animation: cardIn .34s cubic-bezier(.2,.7,.3,1) both; }
@media (prefers-reduced-motion: reduce) { .card.anim-in { animation: none; } }

* { box-sizing: border-box; }

/* Form controls do not inherit the page font by default — without this, every
   button and tab label falls back to the UA font (Arial) while the rest of the
   app renders in the system stack. Icon fonts (.ti) set their own family and
   are unaffected. */
button, input, select, textarea { font-family: inherit; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  background: var(--bg);
  color: var(--text);
}

.app-wrap { max-width: 480px; margin: 0 auto; padding: calc(20px + env(safe-area-inset-top)) 16px calc(84px + env(safe-area-inset-bottom)); min-height: 100vh; }

/* Top bar */
.top-bar { display: flex; justify-content: space-between; align-items: center; padding: 4px 0 16px; }
.top-bar .title { font-size: 16px; font-weight: 600; }
.top-bar .avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--accent-bg); color: var(--accent); display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 600; }

/* Cross-app nav — bottom bar, matching the client's tab bar position/style */
.bottom-nav {
  position: fixed; bottom: 0; left: 0; right: 0;
  max-width: 480px; margin: 0 auto; z-index: 50;
  background: var(--card); border-top: 1px solid var(--border);
  display: flex; justify-content: space-around; padding: 8px 0 calc(8px + env(safe-area-inset-bottom));
}
.bottom-nav a {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  font-size: 10px; color: var(--text-secondary); text-decoration: none; padding: 4px 10px;
}
.bottom-nav a i { font-size: 20px; }
.bottom-nav a.active { color: var(--accent); }

/* ---- Shared app chrome: fixed slim top header + bottom tab bar (client + sub-pages) ---- */
.client-topbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  max-width: 480px; margin: 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  padding: calc(10px + env(safe-area-inset-top)) 16px 10px;
  background: var(--bg); border-bottom: 1px solid var(--border);
}
.client-topbar .title { font-size: 16px; font-weight: 700; }
/* The two header controls sit side by side on EVERY screen, so a size difference between
   them is the most-seen mismatch in the app — measured 34px against 19px. One size, set
   once, applied to both: whatever is inside them (an icon, initials, a "?") no longer
   decides how big the button is. */
.client-topbar .avatar {
  flex: 0 0 auto; box-sizing: border-box; width: 34px; height: 34px; border-radius: 50%;
  border: none; padding: 0; text-decoration: none;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  font-size: 13px; font-weight: 600; line-height: 1;
}
.client-topbar .avatar { background: var(--accent-bg); color: var(--accent); }
/* QUICK ADD IS A THUMB-ARC FAB, NOT A HEADER BUTTON. Brad, 2026-08-28: "if a client is
   holding their phone it should only take a thumb to reach it." The top-right corner is
   the single hardest reach one-handed; bottom-right just above the tab bar is the easiest.
   Same id, same handler - only where it lives changed. right is clamped so the button
   stays inside the 480px app column on wide screens. */
.fab-add {
  position: fixed; z-index: 49;
  right: max(16px, calc((100vw - 480px) / 2 + 16px));
  bottom: calc(68px + env(safe-area-inset-bottom));
  width: 54px; height: 54px; border-radius: 50%; border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--on-accent);
  box-shadow: 0 4px 14px rgba(0,0,0,.35), 0 1px 3px rgba(0,0,0,.3);
  transition: transform .12s ease, box-shadow .12s ease;
}
.fab-add:active { transform: scale(.92); box-shadow: 0 2px 6px rgba(0,0,0,.35); }
.fab-add i { font-size: 26px; }
@media (prefers-reduced-motion: reduce) { .fab-add { transition: none; } }
/* ⚠️ THE TAB BAR IS FIXED, SO EVERY SCREEN NEEDS ROOM UNDERNEATH IT.
   Found by looking at the gate rather than by any assertion: "OR, IF YOU CANNOT POINT AT IT" was
   sliced in half along its baseline by the nav. #tabContent had ZERO bottom padding, so on every
   scrollable screen in the app the last ~57px sat behind the bar — the DOM checks all passed
   because the element was present, the right size, and in the right place. It was simply
   underneath something.
   The reserve is the bar's own height plus the safe-area inset plus a little air, so the last row
   of a list clears it instead of touching it. */
#tabContent {
  padding-bottom: calc(72px + env(safe-area-inset-bottom));
}

.tabbar {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 50;
  max-width: 480px; margin: 0 auto;
  background: var(--card); border-top: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-around;
  padding: 6px 6px calc(6px + env(safe-area-inset-bottom));
}
.tabbar .tab {
  flex: 1; background: none; border: none; cursor: pointer; text-decoration: none;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  color: var(--text-secondary); font-size: 10px; padding: 4px 0;
}
.tabbar .tab i { font-size: 21px; }
.tabbar .tab.active { color: var(--accent); }
.tab-add {
  flex: 0 0 auto; width: 52px; height: 52px; border-radius: 50%;
  border: none; background: var(--accent); color: var(--on-accent); text-decoration: none;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; margin: 0 6px; box-shadow: 0 4px 14px rgba(0,0,0,.22);
  transform: translateY(-10px);
}
.tab-add i { font-size: 26px; }
/* Sub-pages opt in with body.appchrome: clear the fixed chrome, hide the legacy icon nav. */
body.appchrome .app-wrap { padding: calc(60px + env(safe-area-inset-top)) 16px calc(84px + env(safe-area-inset-bottom)); }
body.appchrome .bottom-nav { display: none; }

/* Cards */
/* ---- CARD SHELL ------------------------------------------------------------------------
 * Brad, 2026-08-17: "make a rule all cards including hero should be the same size for
 * uniformity."
 *
 * WHAT IS UNIFORM IS THE SHELL, NOT THE HEIGHT. Every card gets identical padding, radius,
 * border, shadow and gap — that is the consistency people actually read as polish. Height
 * stays content-driven on purpose: the Food day-totals card is 492px because it holds five
 * macros, a water row and a log button, and the Progress chart is 372px because it is a
 * chart. Forcing those to one number buys tidy measurements at the cost of either dead
 * space in short cards or scrolling inside tall ones, and dead space is exactly what makes
 * an interface feel templated. The HERO is the exception below — it says the same kind of
 * thing on every tab, so it gets one fixed size. */
/* THE CARD AND THE GROUP ARE NOW THE SAME OBJECT.
   Rolling the .fn-* grammar across five tabs one component at a time would have meant rewriting
   every shared card in the app — the re-screen prompt, the coach review, the MBP hub, the gear
   list — each of which is used from several places. Converging the primitive instead means the
   parts not yet converted still look like they belong: same 14px radius, same border, same
   shadow, same rhythm. A .fn-group is simply a .card whose rows carry their own padding.
   Radius was 18px against the group's 14px, which is exactly the kind of two-pixel mismatch
   Brad's uniformity rule exists to catch. */
/* ---- NO BOXES -------------------------------------------------------------------------------
 * Brad, 2026-08-21, looking at Direction 1: *"I like how clean the design looks without the
 * cards. make every tab like that."*
 *
 * So content sits directly on the page and hairline rules do the separating. That is the
 * Instrument direction's structure, and it is also what Apple's own Health detail screens and
 * MacroFactor's light mode do: no container, generous space, one rule where a boundary matters.
 *
 * TWO THINGS HAVE TO MOVE WITH THE BOX, or a card-less layout reads as unfinished rather than
 * calm:
 *   1. THE GROUND GOES WHITE. A grey ground only earns its keep by making white cards float on
 *      it. With no cards to float there is nothing to separate, and grey behind grey-less
 *      content just looks like a page that failed to load its styles.
 *   2. SPACE REPLACES THE EDGE. The 14px of padding and the border used to say "this group ends
 *      here". That job now belongs to the margin above the next section label, which is why
 *      .fn-label carries far more top margin than a heading normally would.
 */
.card { background: none; border: none; border-radius: 13px; padding: 0; margin-bottom: 22px; box-shadow: none; }

/* ---- EMPHASIS IS A RULE, NOT A BOX -----------------------------------------------------------
 * Brad, 2026-08-21: *"mobility has a couple cards still. it looks way better without them."*
 *
 * He is right, and the count says why. Sixty-three of the app's 235 cards set an inline accent
 * border or an accent tint to mark themselves as the thing worth looking at — better than one in
 * four. That was survivable when EVERYTHING was a box and these were slightly louder boxes. With
 * the boxes gone they are the only containers left on the page, so they stop reading as emphasis
 * and start reading as leftovers.
 *
 * So emphasis becomes a 3px rule down the left edge, which is what the plateau card already used
 * and what reads well on a card-less page: present, directional, no container. The inline colour
 * is preserved — collapsing the border WIDTH rather than restyling it means an accent card stays
 * accent, a warn card stays warn, and a `${tone}` card stays whatever it computed.
 *
 * WARN AND DANGER TINTS SURVIVE (two of them). A safety message should feel different from a
 * promotion, and a soft tint is how it does that; those set --warn-bg, which is matched below and
 * deliberately not stripped.
 *
 * !important is required and is not laziness: these are INLINE styles, and an attribute selector
 * cannot outrank one without it.
 */
/* NO RULE EITHER. Brad, 2026-08-21: *"weird 1 box side on this needs to be cleared."*
   Converting the emphasis boxes to a left rule was the right first step and the wrong last one.
   On a page where every other thing is flat, a 3px bar down two cards is the ONLY vertical line
   on the screen — it stops reading as emphasis and starts reading as something that failed to be
   removed. And it is redundant: these blocks already sit under their own section label, which is
   what marks them out now. So the borders and the accent tints come off entirely and the label
   carries it. */
.card[style*="border:1px solid"], .card[style*="border: 1px solid"],
.card[style*="border-left"], .card[style*="var(--accent-bg)"] {
  border: none !important; background: none !important;
  border-radius: 0; padding: 0;
}
/* …except the safety tints, which keep their fill and their soft corners. */
.card[style*="var(--warn-bg)"], .card[style*="var(--danger-bg)"] {
  background: var(--warn-bg) !important;
  border: none !important; border-radius: 12px; padding: 13px 14px;
}
.card[style*="var(--danger-bg)"] { background: var(--danger-bg) !important; }


/* ---- HERO ------------------------------------------------------------------------------
 * One height on all five tabs, so switching tabs does not make the page jump. The hero
 * carries a title and one line about what the tab is FOR — never a sales pitch, which is
 * what pushed Food's hero to 166px against everyone else's 92-124px.
 * min-height (not height) so a longer title wraps rather than clipping; the content is
 * centred vertically so a short hero and a wrapped one still look like the same object. */
/* THE HERO IS A PAGE TITLE NOW, NOT A BOX.
 *
 * Brad, 2026-08-21: *"the rules of 4 cards per page does not need to apply anymore if you can
 * make every tab look clean and nice like that."* Releasing the card cap is what makes this
 * possible, because the boxed hero mostly existed to be one of the four.
 *
 * Not one app in the research boxes its page title — Foodnoms, MacroFactor, Ladder, Strong and
 * Hevy all open with large type sitting directly on the ground ("Food Log / Today, April 1").
 * It is the single biggest remaining difference between our tabs and their screenshots, and
 * changing the primitive converts all seventeen `.card.hero` instances without touching a call
 * site.
 *
 * THIS PROMOTES THE HERO, IT DOES NOT REMOVE IT. 17px inside a tinted box becomes 26px on the
 * page: bigger, and about 126px cheaper on every tab.
 *
 * The min-height goes with it. It existed so five boxes would land on the same number and the
 * page beneath would not jump between tabs; with no box there is nothing to match, and a title
 * that runs one line on Home and two on Progress simply reads as a title.
 */
.card.hero {
  background: none; border: none; box-shadow: none;
  padding: 2px 2px 0; margin-bottom: 15px;
}
.card.hero .hero-title { font-weight: 800; font-size: 26px; letter-spacing: -.03em; margin: 0 0 3px; line-height: 1.12; }
/* Clamped to three lines so a long status line cannot push one tab's hero past another's.
   Without this the heroes landed at 120/123/124px — imperceptible individually, but the
   whole point of a fixed hero is that switching tabs does not move the content beneath it. */
/* The three-line clamp is gone with the box. It existed to stop a long status line pushing one
   fixed-height hero past another's, and there is no fixed height left to protect. */
.card.hero .hero-sub {
  color: var(--text-secondary); font-size: 13px; line-height: 1.45; margin: 0;
}
.card.hero > p:not(.hero-title):not(.hero-sub) { margin: 3px 0 0; }
.card-flat { background: var(--surface); border: none; border-radius: 12px; padding: 12px; }

/* Tabs */
.tabs { display: flex; gap: 6px; background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 4px; margin-bottom: 16px; overflow-x: auto; }
.tab { flex: 1; text-align: center; padding: 8px 10px; border-radius: 9px; font-size: 12px; font-weight: 500; color: var(--text-secondary); cursor: pointer; white-space: nowrap; }
.tab.active { background: var(--accent-bg); color: var(--accent); }

/* Buttons */
.btn { width: 100%; padding: 13px; border-radius: 12px; border: none; background: var(--fill-accent); color: var(--on-accent); font-size: 15px; font-weight: 600; cursor: pointer; }
.btn:disabled { opacity: 0.5; cursor: default; }
.btn-secondary { background: transparent; color: var(--text-secondary); border: 1px solid var(--border); font-weight: 500; }
/* Small is a look, not a hit area: the box stays visually compact and the target reaches 40px.
   Measured before changing it -- "Start ›" rows were 34px tall. */
.btn-small { width: auto; min-height: 40px; padding: 8px 14px; font-size: 13px; }
.btn-outline { background: transparent; color: var(--accent); border: 1px dashed var(--border); font-weight: 500; font-size: 13px; padding: 10px; }

/* Forms */
label { display: block; font-size: 12px; color: var(--text-secondary); margin: 14px 0 5px; font-weight: 500; }
label:first-of-type { margin-top: 0; }
input, select, textarea {
  /* box-sizing so padding never grows the outer box, and ONE control height everywhere:
     two inputs side by side must be the same size whatever their label or value says. */
  box-sizing: border-box;
  width: 100%; min-height: 44px; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--card); font-size: 14px; color: var(--text); font-family: inherit;
}
/* Checkboxes and radios are switches, not text fields — exempt from the 44px control box. */
input[type="checkbox"], input[type="radio"] { width: auto; min-height: 0; }
/* COMPACT CONTROL ROW. The set-logging table is a dense list, not a form, so 44px controls
   would make every row tower. It gets its own single height instead — applied to the
   inputs AND the tick button together, because the rule that matters is that everything in
   one row matches, not that every control in the app is the same size. */
.ctl-sm { box-sizing: border-box; height: 34px; min-height: 34px; padding: 4px 6px; font-size: 13px; text-align: center; }
button.ctl-sm { width: 34px; padding: 0; border-radius: 8px; border: 1px solid var(--border); cursor: pointer; line-height: 1; }
textarea { resize: vertical; min-height: 60px; }
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Two (or more) fields side by side. `stretch` + equal columns is what keeps a pair of
   fields on the same vector — a flex row with intrinsic heights lets the taller label push
   its own column down, which is exactly the mismatch Brad screenshotted. */
.row { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 10px; align-items: stretch; }
.row > div { min-width: 0; display: flex; flex-direction: column; }
/* Inside a .row the caption sits directly above its control with no stray top margin —
   the global label margin (14px) applied to the second column only when its text wrapped,
   which offset the two columns from each other. */
.row > div > label { margin: 0 0 5px; }
.row > div > input, .row > div > select { margin-top: auto; }

/* Pills / chips */
.pill-group { display: flex; gap: 6px; flex-wrap: wrap; }
.pill { font-size: 12px; padding: 7px 13px; border-radius: 999px; background: var(--bg); color: var(--text-secondary); border: 1px solid var(--border); cursor: pointer; user-select: none; }
.pill.selected { background: var(--fill-accent); color: var(--on-accent); border-color: var(--fill-accent); }

/* Badges */
.badge { font-size: 11px; padding: 2px 9px; border-radius: 999px; background: var(--accent-bg); color: var(--accent); }
.badge-muted { background: var(--bg); color: var(--text-secondary); }
.badge-danger { background: var(--danger-bg); color: var(--danger); }

/* Progress bar */
.progress-track { height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; }
.progress-fill { height: 100%; background: var(--fill-accent); border-radius: 3px; }

/* Misc text */
.text-secondary { color: var(--text-secondary); }
.text-muted { color: var(--text-muted); }
.text-danger { color: var(--danger); }
.text-success { color: var(--success); }
.section-label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.09em; font-weight: 700; margin: 0 0 10px; }
.num { font-variant-numeric: tabular-nums; }

.list-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--border); }
.list-row:last-child { border-bottom: none; }

/* ---- UNIFORM SIBLINGS ------------------------------------------------------------------
 * Brad, 2026-08-17: "a lot of the UI is mismatched… we are creating a AAA app not some
 * generic slop. some boxes and text boxes and images next to each other are different
 * heights." Measured across all five tabs: stat tiles landed at 104 vs 120px, the Progress
 * row at 59/45/28, and buttons sat at whatever their label made them.
 *
 * Three shared primitives so a card never has to invent its own geometry:
 *   .tile-row  — equal-height tiles in a row (stretch, not center)
 *   .tile      — the tile itself: one padding, one radius, one border, content pinned top
 *   .row-act   — the trailing control in a list-row, given ONE height everywhere
 * Anything using these is uniform by construction rather than by luck. */
.tile-row { display: flex; align-items: stretch; gap: 8px; }
.tile-row > * { flex: 1 1 0; min-width: 0; }
.tile {
  display: flex; flex-direction: column; justify-content: flex-start;
  padding: 10px 12px; border-radius: 12px; background: var(--card);
  border: 1px solid var(--border); min-height: 64px;
}
.tile .tile-label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .07em; font-weight: 700; }
.tile .tile-value { font-size: 18px; font-weight: 800; letter-spacing: -.02em; font-variant-numeric: tabular-nums; margin-top: 2px; }
.tile .tile-sub { font-size: 11px; color: var(--text-secondary); margin-top: auto; padding-top: 4px; }
/* Controls that sit at the end of a list-row: identical height, never label-dependent. */
.list-row > .btn-small, .list-row > .row-act { min-height: 40px; align-self: center; flex: 0 0 auto; }
/* Media inside a row keeps its box even when the image is still loading. */
.row-media { flex: 0 0 auto; border-radius: 10px; overflow: hidden; background: var(--card); }
.row-media img, .row-media video { display: block; width: 100%; height: 100%; object-fit: cover; }

/* ---- FIELDS SIDE BY SIDE ---------------------------------------------------------------
 * Brad, 2026-08-17, with a screenshot of the set-logging card: "look at this UI, completely
 * mismatched." The weight and reps fields sat at different heights and different widths.
 *
 * CAUSE: each field was a bare <label> wrapping its caption and its <input>. A <label> is
 * display:inline, so the caption text and the input share one INLINE line box — the input's
 * width:100% resolves against an inline container, and the two cells baseline-align against
 * text of different lengths ("LB" vs "REPS"). Two cells that look like a grid, laid out as
 * running text.
 *
 * FIX: .field is a flex COLUMN — caption on its own row, control filling the width beneath —
 * and .field-row stretches its children so both columns are the same height whatever is in
 * them. Any two controls placed side by side use this and cannot drift. */
.field-row { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 8px; align-items: stretch; }
.field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.field > .field-label,
.field > label { font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); line-height: 1.2; }
/* One control height across the app. box-sizing so padding never changes the outer box. */
.field input, .field select, .field textarea {
  box-sizing: border-box; width: 100%; min-height: 44px; margin: 0;
  padding: 9px 10px; font-size: 15px; text-align: center;
}

.empty-state { text-align: center; padding: 30px 20px; color: var(--text-secondary); }
.empty-state i { font-size: 32px; color: var(--text-muted); }

/* Collapsible upsell/detail cards: hide the native disclosure triangle and use
   our own chevron so the summary row matches the app's list-row styling. */
summary::-webkit-details-marker { display: none; }
.gear-chev { transition: transform .18s ease; }
details[open] > summary .gear-chev { transform: rotate(180deg); }

/* ---- FOODNOMS GRAMMAR ------------------------------------------------------------------
 * Brad, 2026-08-21: "Copy foodnoms to the T on format because they have it looking very
 * nice, and add whatever is extra at the bottom." His clients already use it and like it,
 * which is a stronger signal than any opinion of mine about layout.
 *
 * What Foodnoms actually does, and what these classes reproduce:
 *   · INSET GROUPED LISTS. The section label sits OUTSIDE the group in small caps, and the
 *     group itself is one rounded surface with hairline separators inset from the left edge
 *     so they start under the text rather than cutting the card in half.
 *   · ONE ROW SHAPE EVERYWHERE. Title (and a quiet second line) on the left, the number
 *     right-aligned in tabular figures. Every list in the tab is that row, which is most of
 *     why theirs reads calm and ours read busy.
 *   · THE DAY IS THE PAGE. Summary at the top, meals in time order below it, detail last.
 *
 * These are additive: nothing else in the app uses .fn-*, so the rest of the tabs are
 * untouched while Food adopts the format.
 */
/* The label IS the section boundary now, so it carries the space the card edge used to. */
.fn-label {
  font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .085em;
  font-weight: 700; margin: 26px 0 8px; padding: 0 2px; display: flex; align-items: baseline; gap: 8px;
}
.fn-label:first-child { margin-top: 4px; }
.fn-label .fn-label-value { margin-left: auto; text-transform: none; letter-spacing: 0; font-weight: 700; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.fn-group {
  background: none; border: none; border-radius: 0;
  margin-bottom: 22px; box-shadow: none;
  /* The rule under the last row closes the group where its box used to. */
  border-bottom: 1px solid var(--border);
}
/* A group that is a single block of prose rather than a list of rows needs no closing rule. */
.fn-group.plain { border-bottom: none; }

/* ⚠️ A SIGNAL IS A CARD, and it is the one place the card-less rule is deliberately broken.
   Brad, 2026-08-22: *"card borders here."*

   Everything else on these tabs is CONTENT — a list of meals, a set of goals — and content reads
   better flowing, which is why .fn-group has no box. A cross-tab signal is not content; it is the
   app interrupting to say something it noticed, and an interruption has to look separable from
   the page it is sitting in or the client reads it as another paragraph of the food log.

   Inheriting .fn-group gave it the worst of both: a left accent rule and the group's closing
   hairline underneath, which is half a box and looks like a rendering fault rather than a
   decision. So it gets a whole one — border, radius, and a lifted ground — and drops the
   inherited rule. The left edge keeps carrying the tone. */
.fn-signal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: 12px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 8px;
  padding: 12px 14px;
}
.fn-signal > summary { list-style: none; cursor: pointer; }
.fn-signal > summary::-webkit-details-marker { display: none; }
/* The separator is inset so it starts under the label, which is the detail that makes an
   iOS grouped list look like one object instead of stacked strips. */
.fn-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 13px 2px; background: none; border: none; border-radius: 0; margin: 0;
  color: var(--text); font-size: 14px; position: relative;
}
/* Full-width now, not inset. The inset existed to start the rule under the text INSIDE a card;
   with no card the rule is the only structure on the page, so it runs edge to edge. */
.fn-row + .fn-row::before {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: var(--border);
}
button.fn-row { cursor: pointer; }
button.fn-row:active { background: var(--accent-bg); border-radius: 8px; }
.fn-row-main { flex: 1; min-width: 0; }
.fn-row-title { display: block; font-size: 14px; font-weight: 550; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* TWO LINES, NOT ONE. Seen on screen: the gate row read "Where it hurts, what is doing it, and
   what to do about…" - the sentence stops exactly where it starts being useful. One line is
   right for a name and wrong for a description, and every row in the app that carries a real
   sentence was losing its second half. Clamped at two so a long one still cannot push a list
   out of shape. */
.fn-row-sub { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  font-size: 11.5px; color: var(--text-muted); line-height: 1.35; margin-top: 2px; overflow: hidden; }
/* ⚠️ A RECIPE STEP MUST WRAP. Brad, 2026-08-22: *"how do i read the rest of this? …. does not
   help."* The method steps were borrowing .fn-row-sub, which is built for a one-line row subtitle
   and therefore carries `white-space: nowrap` + ellipsis. On a phone that clipped every step at
   the screen edge — "Dice the red potato small (1 cm) and boil in salted water 6–8 minut…" — so
   the one part of the app a client reads WHILE COOKING was the one part they could not read.
   Prose in a row needs the opposite of what a label needs. */
.fn-row-prose {
  display: block; font-size: 13.5px; color: var(--text); line-height: 1.6;
  white-space: normal; overflow: visible; text-overflow: clip; text-wrap: pretty;
}
.fn-row-value { font-variant-numeric: tabular-nums; font-size: 14px; font-weight: 650; white-space: nowrap; }
.fn-row-unit { font-size: 11px; color: var(--text-muted); font-weight: 600; margin-left: 3px; }
.fn-row-chev { color: var(--text-muted); font-size: 15px; flex: none; }
/* The last row of a meal group — Foodnoms' quiet in-group add. */
.fn-row-add { color: var(--accent); font-weight: 650; font-size: 13.5px; }

/* The summary block: one big number, then the macros as equal thirds. */
.fn-summary { display: flex; align-items: center; gap: 18px; padding: 16px 14px 14px; }
.fn-ring { flex: none; }
.fn-big { font-size: 34px; font-weight: 800; letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1; }
.fn-big-sub { font-size: 11.5px; color: var(--text-muted); margin-top: 4px; }
.fn-macros { display: flex; gap: 0; border-top: 1px solid var(--border); }
.fn-macro { flex: 1 1 0; min-width: 0; padding: 11px 12px 12px; text-align: left; }
.fn-macro + .fn-macro { border-left: 1px solid var(--border); }
.fn-macro-name { font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em; font-weight: 700; color: var(--text-muted); }
.fn-macro-val { font-size: 16px; font-weight: 750; font-variant-numeric: tabular-nums; margin: 3px 0 6px; }
.fn-macro-val span { font-size: 11px; font-weight: 600; color: var(--text-muted); }
.fn-bar { height: 4px; border-radius: 2px; background: var(--border); overflow: hidden; }
.fn-bar > i { display: block; height: 100%; border-radius: 2px; }

/* Nutrient detail rows carry their own thin bar under the value. */
.fn-nut { display: block; padding: 11px 2px; position: relative; }
.fn-nut + .fn-nut::before { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: var(--border); }
.fn-nut-top { display: flex; align-items: baseline; gap: 10px; margin-bottom: 6px; }
.fn-nut-top span:first-child { flex: 1; font-size: 13px; }

/* ---- FOODNOMS, PART TWO: the parts Brad sent screenshots of ----------------------------------
 * "needs to look like this." The first pass got the grouped-list grammar right and missed four
 * things that are most of what the screenshots actually look like: the week strip, the goals as
 * a grid of ring cards, the macro chips under every entry, and the floating add button.
 *
 * THE MACRO CHIP is the detail worth naming. Foodnoms puts 🔥223 C1 F15 P19 under every food, so
 * the macro split of a meal is readable without opening anything — and because the letters are
 * always the same colour (carbs blue, fat green, protein orange) the eye learns them in a day.
 * We had the numbers and showed only calories, which is the one macro that says least about
 * whether a day went well.
 */
.fn-week { display: flex; gap: 4px; margin: 0 0 14px; }
.fn-day {
  flex: 1 1 0; min-width: 0; text-align: center; padding: 6px 0 7px; border-radius: 11px;
  background: none; border: none; cursor: pointer; color: var(--text); margin: 0;
}
.fn-day-name { font-size: 10.5px; color: var(--text-muted); font-weight: 650; letter-spacing: .02em; }
.fn-day-num { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 3px; }
.fn-day.on { background: var(--fill-accent); }
.fn-day.on .fn-day-name, .fn-day.on .fn-day-num { color: var(--on-accent, #fff); }
.fn-day.ahead { opacity: .38; cursor: default; }
.fn-day.logged .fn-day-num::after {
  content: ''; display: block; width: 4px; height: 4px; border-radius: 50%;
  background: var(--fill-accent); margin: 2px auto 0;
}
.fn-day.on.logged .fn-day-num::after { background: var(--on-accent, #fff); }

/* FOUR ACTIVITY DOTS under a day, in a fixed order: workout, cardio, corrective, food.
   The order never varies, so after a week the POSITION is the label and the strip reads without
   a legend — the same principle the macro colours run on. Rendered as real elements rather than
   the single ::after above because four of them need four colours, and a pseudo-element cannot
   carry a set. The one-dot `.logged` rule stays for anywhere still using it. */
.fn-day-dots {
  display: flex; gap: 2px; justify-content: center; align-items: center;
  height: 5px; margin-top: 3px;
}
.fn-day-dots i { width: 4px; height: 4px; border-radius: 50%; display: block; }
.fn-day.on .fn-day-dots i { box-shadow: 0 0 0 1px var(--on-accent, #fff); }

/* Goals: four cards, two up. Name and "X under" on the left, ring on the right. */
.fn-goals { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; margin-bottom: 16px; }
/* THE TILES LOSE THEIR FILL TOO.
 *
 * Brad, 2026-08-21: *"mobility still has card borders."* He was looking at these — by then the
 * only filled, rounded things left on the page, which made three measurement tiles read as the
 * cards everything else had shed.
 *
 * I had argued they needed "SOME boundary or they read as eight loose lines". That was wrong,
 * and the reason is the ring: each tile already carries a bold name, a grey sub-line and a
 * coloured dial, which is plenty of internal structure to group by. The grid gap separates them.
 * A fill was solving a problem the content had already solved.
 */
.fn-goal {
  background: none; border: none; border-radius: 0;
  padding: 2px 0; display: flex; align-items: center; gap: 10px; box-shadow: none;
}
.fn-goals { gap: 14px 16px; }
.fn-goal-main { flex: 1; min-width: 0; }
.fn-goal-name { font-size: 13.5px; font-weight: 700; line-height: 1.2; }
/* The fact: what they have eaten over what they are aiming at. Tabular figures so the four
   cards line up their digits, and the goal is dimmed so the eaten number reads first. */
.fn-goal-now { font-size: 12.5px; margin-top: 3px; color: var(--text-secondary); font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
/* THE LIVE NUMBER IS THE INSTRUMENT (AAA audit, 2026-08-28). Every million-download tracker
   makes the consumed number the biggest thing in its cell - MFP's dial, WHOOP's strain digit.
   Ours sat at 12.5px next to its own target. The number leads at display size; the target and
   unit stay small around it. Same data, same layout - only the hierarchy changed. */
.fn-goal-now strong { color: var(--text); font-weight: 800; font-size: 21px; letter-spacing: -.02em; line-height: 1.05; }
.fn-goal-sub { font-size: 11.5px; color: var(--text-muted); margin-top: 2px; font-variant-numeric: tabular-nums; }
.fn-goal-ring { width: 46px; height: 46px; border-radius: 50%; flex: none; display: grid; place-items: center; }
.fn-goal-ring > i { width: 33px; height: 33px; border-radius: 50%; background: var(--bg); display: block; }

/* Per-entry macro chips. */
.fn-chips { display: flex; gap: 9px; margin-top: 4px; flex-wrap: wrap; }
.fn-chip { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; font-weight: 650; font-variant-numeric: tabular-nums; color: var(--text-secondary); }
.fn-chip i {
  width: 13px; height: 13px; border-radius: 50%; display: inline-grid; place-items: center;
  font-size: 8.5px; font-weight: 800; font-style: normal; color: #fff; flex: none;
}

/* NO FLOATING ADD BUTTON. Foodnoms has one, but the app already carries a round + in the header
   (#quickAdd) and every meal group now ends in its own "Add Food" row — a third add affordance on
   one screen is not the pattern, it is three of them. */

/* The section switch: Log / Insights / Library. iOS segmented control, which is what Foodnoms
   uses for Week/Month/Year on every chart — so the client has already met this shape. */
.fn-seg {
  display: flex; gap: 2px; padding: 3px; margin: 0 0 18px;
  background: var(--surface); border-radius: 11px;
}
/* MIN-HEIGHT 40 IS THE THUMB RULE, and it is not decoration. Apple's own target is 44px and 40 is
   the floor below which a control starts getting missed on a phone -- measured at 32px here, which
   is why the section switch took two taps often enough to be annoying. Applied as min-height so the
   padding still controls the look. */
.fn-seg-btn {
  flex: 1 1 0; min-width: 0; min-height: 40px; padding: 7px 4px; border: none; border-radius: 9px; margin: 0;
  background: none; color: var(--text-secondary); font-size: 13px; font-weight: 650; cursor: pointer;
}
.fn-seg-btn.on { background: var(--card); color: var(--text); box-shadow: var(--shadow-sm); }

/* The month grid behind the date header. Seven equal columns, a dot for a day with food in it. */
.cal-head { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; margin-bottom: 4px; }
.cal-head span { text-align: center; font-size: 10.5px; font-weight: 700; color: var(--text-muted); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-d {
  position: relative; aspect-ratio: 1; border: none; background: none; margin: 0; padding: 0;
  border-radius: 10px; cursor: pointer; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px; color: var(--text);
}
.cal-n { font-size: 13.5px; font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1; }
.cal-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--fill-accent); display: block; }
.cal-d.on { background: var(--fill-accent); }
.cal-d.on .cal-n { color: var(--on-accent); font-weight: 800; }
.cal-d.on .cal-dot { background: var(--on-accent); }
.cal-d.ahead { opacity: .28; cursor: default; }
.cal-d:not(.on):not(.ahead):hover { background: var(--accent-bg); }

/* ---- THE INSTRUMENT PRIMITIVES -------------------------------------------------------------
 * Brad, 2026-08-21, choosing between four directions: *"4 for the furniture, 1 for the numbers."*
 *
 * The .fn-* classes above are the FURNITURE — grouped lists, one row shape, a fixed colour per
 * measurement. These are the NUMBERS: how a measured value gets drawn anywhere in the app.
 *
 * The pattern came out of the competitor research and it is the same in both of the apps that do
 * it best. MacroFactor: "Average 2940 kcal · Difference +268 kcal" over a chart with a shaded
 * flux band. Ladder: "165 lbs / Estimated 1RM ⓘ" over a bar chart and a table. Big figure, small
 * unit, a label that names what it is, and — the part nobody else does — the band underneath
 * saying what counts as noise.
 *
 * THE RULE THAT MAKES IT OURS: any number drawn with .fn-stat should be able to say how sure it
 * is. If a measurement has a known error, .fn-noise draws it. If it does not, that is a signal
 * the number may not deserve this treatment.
 */
.fn-stat { display: flex; align-items: baseline; gap: 5px; }
.fn-stat-fig {
  font-size: 34px; font-weight: 800; letter-spacing: -.035em; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.fn-stat-fig.sm { font-size: 25px; letter-spacing: -.028em; }
.fn-stat-fig.lg { font-size: 44px; letter-spacing: -.04em; }
.fn-stat-unit { font-size: 13px; font-weight: 650; color: var(--text-muted); letter-spacing: 0; }
.fn-stat-lab {
  font-size: 11px; font-weight: 650; color: var(--text-muted); letter-spacing: .01em;
  margin-bottom: 3px; display: flex; align-items: center; gap: 4px;
}
/* Two measurements side by side — the value and its change, MacroFactor's header. */
.fn-stat-row { display: flex; gap: 26px; flex-wrap: wrap; }
.fn-stat-row > div { min-width: 0; }

/* THE NOISE BAND. The shaded zone is the range we cannot resolve; the tick is where the client
   actually is. A change that ends inside the shading is not reported as a change anywhere in
   this app, and this is the picture of that rule. */
.fn-noise { position: relative; height: 7px; border-radius: 4px; background: var(--border); margin-top: 9px; }
.fn-noise > i { position: absolute; top: 0; height: 100%; border-radius: 4px; background: var(--accent-bg); display: block; }
.fn-noise > b { position: absolute; top: -3px; width: 2px; height: 13px; border-radius: 1px; background: var(--text); display: block; }
.fn-noise-note {
  font-size: 10.5px; color: var(--text-muted); margin-top: 6px; line-height: 1.45;
  font-variant-numeric: tabular-nums;
}

/* Ladder's stat tiles: a row of small labelled totals under a chart. */
.fn-tiles { display: flex; gap: 7px; margin-top: 12px; }
/* Same for the stat tiles — a label above a value needs no container either. A hairline between
   them says "these are three of a kind" without drawing three boxes. */
.fn-tile { flex: 1 1 0; min-width: 0; background: none; border: none; border-radius: 0; padding: 2px 0; }
.fn-tile + .fn-tile { border-left: 1px solid var(--border); padding-left: 14px; }
.fn-tiles { gap: 14px; }
.fn-tile-v { font-size: 16px; font-weight: 750; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.fn-tile-l { font-size: 10.5px; color: var(--text-muted); margin-top: 2px; line-height: 1.3; }

/* The range selector every chart in the category uses. */
.fn-range { display: flex; gap: 2px; background: var(--border); border-radius: 9px; padding: 3px; margin: 11px 0; }
.fn-range button {
  flex: 1 1 0; min-width: 0; padding: 5px 2px; border: none; border-radius: 7px; margin: 0;
  background: none; color: var(--text-secondary); font-size: 11px; font-weight: 650; cursor: pointer;
}
.fn-range button.on { background: var(--card); color: var(--text); box-shadow: var(--shadow-sm); }

/* ---- IN-APP DIALOGS (replacing alert / confirm / prompt) --------------------------------------
 * Own layer at z-index 2100, ABOVE the 2000 that #app-sheet uses, so a confirm can be raised
 * from inside a sheet without destroying it. See the header on toast() in client/app.js.
 */
.app-toast {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(76px + env(safe-area-inset-bottom)); z-index: 2200;
  max-width: min(420px, calc(100vw - 32px));
  background: var(--text); color: var(--bg);
  padding: 11px 16px; border-radius: 11px; font-size: 13.5px; font-weight: 600; line-height: 1.4;
  box-shadow: 0 6px 24px rgba(0,0,0,.3); text-align: center;
  animation: toastIn .22s cubic-bezier(.2,.7,.3,1) both;
}
.app-toast.warn { background: var(--warn); color: #fff; }
.app-toast.leaving { animation: toastOut .2s ease both; }
@keyframes toastIn { from { opacity: 0; transform: translate(-50%, 14px); } to { opacity: 1; transform: translate(-50%, 0); } }
@keyframes toastOut { to { opacity: 0; transform: translate(-50%, 10px); } }

.app-dialog {
  position: fixed; inset: 0; z-index: 2100; background: rgba(0,0,0,.5);
  backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px);
  display: flex; align-items: center; justify-content: center; padding: 22px;
  animation: dlgFade .16s ease both;
}
.app-dialog-panel {
  background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
  padding: 20px; width: 100%; max-width: 340px;
  box-shadow: 0 18px 46px rgba(0,0,0,.34);
  animation: dlgPop .2s cubic-bezier(.2,.7,.3,1) both;
}
.app-dialog-title { font-size: 16.5px; font-weight: 750; letter-spacing: -.015em; margin: 0 0 7px; line-height: 1.3; }
.app-dialog-body { font-size: 13px; color: var(--text-secondary); line-height: 1.55; margin: 0 0 15px; }
.btn.btn-danger { background: var(--danger); color: #fff; }
@keyframes dlgFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes dlgPop { from { opacity: 0; transform: scale(.94); } to { opacity: 1; transform: scale(1); } }
@media (prefers-reduced-motion: reduce) {
  .app-toast, .app-dialog, .app-dialog-panel { animation: none; }
}

/* ---- PRINTING THE CLINICIAN REPORT ------------------------------------------------------------
 * The report used to live in its own popup window, so printing it was just window.print(). It is
 * an in-app view now, and printing the app would put the tab bar, the header and the app's own
 * padding on a document that is going to a physiotherapist.
 *
 * `body.printing-report` is set for the duration of the print and removed on afterprint. It
 * strips the chrome and lets the report use the whole sheet — .report already carries its own
 * serif type, margins and max-width for exactly this.
 */
@media print {
  body.printing-report .tabbar,
  body.printing-report .client-topbar,
  body.printing-report .top-bar,
  body.printing-report .bottom-nav,
  body.printing-report .no-print,
  body.printing-report #app-toast,
  body.printing-report #app-dialog,
  body.printing-report #app-sheet { display: none !important; }
  body.printing-report { background: #fff !important; }
  body.printing-report .app-wrap { max-width: none; padding: 0 !important; }
  body.printing-report .report { padding: 0; max-width: none; }
}

/* ---- THE WEEK DOTS ---------------------------------------------------------------------------
 * From the Coach mock Brad picked out: seven circles across the top, filled with a tick for a day
 * that happened, outlined for one that has not. Ladder uses exactly this and it is the clearest
 * adherence display in the category — a bar chart of sessions makes you read it, this you just
 * see.
 *
 * The letter stays visible in the empty state so the row is still a WEEK and not seven blanks.
 */
.wk-dots { display: flex; gap: 6px; margin: 0 0 18px; }
.wk-dot {
  flex: 1 1 0; min-width: 0; aspect-ratio: 1; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
  border: 1.5px solid var(--border); color: var(--text-muted); background: none;
}
.wk-dot.done { background: var(--fill-accent); border-color: var(--fill-accent); color: var(--on-accent); }
.wk-dot.today { border-color: var(--accent); color: var(--accent); }
.wk-dot.today.done { color: var(--on-accent); }
.wk-dot.ahead { opacity: .4; }

/* ---- THE SET TABLE, HEVY/STRONG STYLE ---------------------------------------------------------
 * Brad, 2026-08-21: *"look at hevy and strong i want my apperance to look clean and functional
 * like those apps. Just like how you copied foodnoms."*
 *
 * Ours was already the same table — SET / PREVIOUS / kg / REPS / ✓, with Strong's tap-to-tag set
 * types. Two things were missing, and they are the two doing the work in their screenshots:
 *
 *   1. THE WHOLE ROW TINTS WHEN THE SET IS DONE, not just the tick. Under a barbell, between
 *      sets, half-focused, you are looking for "where am I up to" — and a block of colour answers
 *      that from arm's length where a small green check does not.
 *   2. A LABELLED STAT HEADER (Time / Volume / Sets). Three numbers with their names above them,
 *      no box, no tiles.
 *
 * Both apps arrived at this independently with three million users between them. On the workout
 * screen specifically, that is worth more than any opinion of mine.
 */
.ws-set-row { border-radius: 8px; padding: 4px 6px; margin: 3px -6px 0; transition: background .14s ease; }
.ws-set-row.done { background: var(--done-bg); }
.ws-set-row.done .text-muted { color: var(--text-secondary); }

/* Session stats: label above value, no container — Hevy's header exactly. */
.ws-stats { display: flex; gap: 26px; padding: 2px 0 13px; margin-bottom: 13px; border-bottom: 1px solid var(--border); }
.ws-stat-l { font-size: 10.5px; color: var(--text-muted); font-weight: 650; letter-spacing: .02em; }
.ws-stat-v { font-size: 16px; font-weight: 750; font-variant-numeric: tabular-nums; margin-top: 2px; letter-spacing: -.01em; }
.ws-stat-v.live { color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .ws-set-row { transition: none; } }

/* The add-food method row: Search / Scan / Snap / Describe / Manual. Foodnoms and MacroFactor
   both use a tab row here rather than a stack of buttons, and the reason is that a stack asks
   you to read five options every time while a row shows the one you are in. */
.afs-methods { display: flex; gap: 2px; margin: 11px 0 2px; border-bottom: 1px solid var(--border); }
.afs-m {
  flex: 1 1 0; min-width: 0; padding: 9px 2px 8px; margin: 0; border: none; background: none;
  color: var(--text-muted); font-size: 12px; font-weight: 650; cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.afs-m.on { color: var(--accent); border-bottom-color: var(--accent); }

/* A findings tile is a button now — tap it for that measurement's history. */
button.fn-goal { cursor: pointer; text-align: left; margin: 0; color: inherit; }
button.fn-goal.on .fn-goal-name { color: var(--accent); }
.fg-detail {
  margin: -8px 0 0; padding: 14px 2px 2px; border-top: 1px solid var(--border);
  animation: fgIn .2s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes fgIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .fg-detail { animation: none; } }

/* ---- BULLET LISTS, AND THE LONG-LIST PATTERN --------------------------------------------------
 * Brad, 2026-08-21, with a screenshot of the nutrition rules: *"make this collapsable. anything
 * with a long list needs to be cleaner and collapsable."*
 *
 * THE ALIGNMENT BUG IN THAT SCREENSHOT WAS REAL AND WORTH NAMING. Those bullets were built out
 * of `.list-row`, which is `justify-content: space-between` — a LABEL-AND-VALUE row. Give it two
 * children where the second is a short sentence and the sentence floats to the right edge, which
 * is why "Drink about 109 oz of water" sat indented while its neighbours did not. It was never a
 * list-row; it is a bullet list, and it now says so.
 */
.fn-bullets { display: flex; flex-direction: column; gap: 11px; }
.fn-bullet { display: flex; align-items: flex-start; gap: 9px; font-size: 12.5px; line-height: 1.5; }
.fn-bullet::before {
  content: ''; flex: none; width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent); margin-top: 7px;
}
.fn-bullet > span { flex: 1; min-width: 0; }

/* A long list, folded. The summary carries the count so the client knows what is behind it
   before deciding to open it — a disclosure that hides an unknown quantity gets opened once. */
.fn-more { border-top: 1px solid var(--border); margin-top: 12px; padding-top: 11px; }
.fn-more > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; font-weight: 650; color: var(--accent);
}
.fn-more > summary::-webkit-details-marker { display: none; }
.fn-more > summary .fn-more-count { margin-left: auto; color: var(--text-muted); font-weight: 600; font-size: 11.5px; }
.fn-more > div { margin-top: 12px; }

/* ---- FIRST RUN CHECKLIST ----------------------------------------------------------------------
 * One step open at a time — the first unfinished one. Six expanded steps is a wall of homework
 * and reads as "this app is a lot of work"; one step with a reason and a button reads as one
 * thing to do. Finished steps collapse to a tick, which is the only progress bar worth having.
 */
.fr-step { padding: 12px 2px; position: relative; }
.fr-step + .fr-step::before {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: var(--border);
}
.fr-head { display: flex; align-items: center; gap: 10px; }
.fr-tick {
  flex: none; width: 19px; height: 19px; border-radius: 50%;
  border: 1.5px solid var(--border); display: grid; place-items: center;
  font-size: 11px; font-weight: 800; color: transparent;
}
.fr-step.done .fr-tick { background: var(--success); border-color: var(--success); color: #fff; }
.fr-title { flex: 1; font-size: 14px; font-weight: 600; }
.fr-step.done .fr-title { color: var(--text-muted); text-decoration: line-through; }
.fr-step.open .fr-title { font-weight: 750; }
.fr-wait { font-size: 11px; color: var(--text-muted); font-weight: 600; }
.fr-why { font-size: 12.5px; color: var(--text-secondary); line-height: 1.5; margin: 7px 0 0; padding-left: 29px; }

/* ============================================================================================
 * THUMB TARGETS — the floor, applied once, everywhere.
 * ============================================================================================
 * Brad's rule, 2026-08-26: what we build has to be usable by a client, a trainer and a clinician.
 * A control smaller than about 40px gets missed on a phone, and an audit of every tab found the
 * same handful of shared classes failing on all of them: the week-strip arrows at 17x21, the
 * date button at 276x20, the little "?Why" links at 32px, and the inline ⓘ at 19x15.
 *
 * Measured before changing, in every tab, rather than applied on principle — these numbers are
 * what the browser reported at 375x812.
 *
 * The pattern throughout: the TARGET grows, the ink does not. Each rule sets min-height and
 * centres its contents, so nothing looks bigger and everything is easier to hit. Where a control
 * sits inline in a sentence, negative margin keeps the line height from opening up.
 */
.tr-strip-prev, .tr-strip-next, .mb-strip-prev, .mb-strip-next,
.fd-strip-prev, .fd-strip-next, .cd-strip-prev, .cd-strip-next {
  min-width: 40px; min-height: 40px; display: inline-flex; align-items: center; justify-content: center;
}
.tr-strip-cal, .mb-strip-cal, .fd-strip-cal, .cd-strip-cal {
  min-height: 40px; display: flex; align-items: center; justify-content: center;
}
/* The why-links and the inline info dot are read AND tapped, so they get a real box without
   changing the type size that makes them feel like a footnote. */
.why-link { min-height: 40px; display: flex; align-items: center; }
.readiness-how, .fn-edit-goals, .row-info {
  min-width: 40px; min-height: 40px; display: inline-flex; align-items: center; justify-content: center;
  margin: -12px -10px; /* the target grows outward; the line it sits on does not */
}
/* A <summary> is a control even when it looks like a heading. */
#tabContent summary { min-height: 40px; display: flex; align-items: center; }

/* The last three shared classes the whole-app sweep found under 40px: a findings shortcut that
   looks like a caption, the fasting switch, and the Movement signal rows. Same treatment - the
   target grows, the ink does not. */
.pf-view { min-height: 40px; display: inline-flex; align-items: center; }
.oc-signal.list-row { min-height: 44px; }
/* A checkbox-style switch is drawn by its ::before/::after, so the input itself can be tiny while
   looking right. Growing the input is invisible and makes it hittable. */
#tabContent input[type="checkbox"] { min-width: 40px; min-height: 40px; }


/* A group with nothing in it is a bordered gap that reads as a failed load. */
.fn-group:empty { display: none; }

/* The pill used by the 3D screen's chips, promoted to shared because the body gate's hero now
   uses the same shape to name its three ways in. One definition, one look. */
.a3-chip { padding: 6px 12px; border-radius: 100px; border: 1px solid var(--border);
  background: transparent; color: var(--text-muted); font-size: 12px; cursor: pointer;
  width: auto; margin: 0; font-weight: 500; line-height: 1.3; display: inline-block; }
.a3-chip.on { background: var(--text); color: var(--bg, #14121a); border-color: var(--text); font-weight: 650; }
.a3-chip.find.on { background: var(--accent); color: #fff; border-color: var(--accent); }
/* A chip you can TAP needs a thumb target; a chip that is only a label does not. The element
   tells them apart - the symptom door's examples and the zone door's regions are buttons, the
   three on the body-gate hero are spans that name the ways in. */
button.a3-chip { min-height: 40px; display: inline-flex; align-items: center; justify-content: center; }


/* ---- AAA MOTION LAYER (Brad, 2026-08-28: "we need the UI to look like a AAA app") --------------
 * Three rules, applied app-wide rather than per-feature:
 *   1. Anything that appears, arrives - sheets slide up, tab content fades in.
 *   2. Anything pressable compresses under the thumb - buttons, rows, tabs.
 *   3. All of it is skipped under prefers-reduced-motion.
 * The flat .fn-* furniture stays flat on purpose (that IS the design system); motion carries
 * the premium feel instead of shadows. */
#app-sheet { animation: sheetDim .2s ease both; }
#app-sheet .sheet-panel {
  animation: sheetUp .28s cubic-bezier(.2,.75,.25,1) both;
  box-shadow: 0 -8px 32px rgba(0,0,0,.28);
}
@keyframes sheetDim { from { background: rgba(0,0,0,0); } }
@keyframes sheetUp { from { transform: translateY(46px); opacity: .4; } to { transform: none; opacity: 1; } }

#tabContent.tab-anim { animation: tabIn .2s ease both; }
@keyframes tabIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }

.btn, .fn-row-main, .fn-more > summary, .tabbar .tab, .fab-add, .chip {
  transition: transform .1s ease, opacity .1s ease;
}
.btn:active, .fn-row-main:active, .fn-more > summary:active, .chip:active { transform: scale(.975); opacity: .82; }
.tabbar .tab i { transition: transform .16s cubic-bezier(.2,.75,.25,1); }
.tabbar .tab.active i { transform: translateY(-1.5px) scale(1.1); }
.tabbar .tab:active i { transform: scale(.88); }
.tabbar { box-shadow: 0 -1px 12px rgba(0,0,0,.14); }

@media (prefers-reduced-motion: reduce) {
  #app-sheet, #app-sheet .sheet-panel, #tabContent.tab-anim { animation: none; }
  .btn, .fn-row-main, .fn-more > summary, .tabbar .tab, .tabbar .tab i, .fab-add, .chip { transition: none; }
  .btn:active, .fn-row-main:active, .fn-more > summary:active, .chip:active { transform: none; }
  .tabbar .tab.active i { transform: none; }
}
