/* Eve unified components — derived 1:1 from handoff/Design-unified/Eve Shell.dc.html.
   tokens.css is the value authority; nothing here invents styling.
   Class names kept stable across the app so screens share one vocabulary. */

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  /* Behave like an app, not a page: no double-tap zoom, and no Safari
     text inflation on rotate. Pinch is blocked in index.html (the viewport
     tag plus gesture handlers, since iOS ignores user-scalable in a tab). */
  touch-action: manipulation;
  -webkit-text-size-adjust: 100%;
  overscroll-behavior-y: none;   /* no rubber-band past the ends */
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, "SF Pro Text", "Helvetica Neue", sans-serif;
  -webkit-font-smoothing: antialiased;
}

button { font-family: inherit; cursor: pointer; color: inherit; background: none; border: none; }
input { font-family: inherit; }
input::placeholder { color: var(--faint); }

.num, input[type="number"] { font-variant-numeric: tabular-nums; }

/* ---- app frame ---- */
#app {
  max-width: 600px;
  margin: 0 auto;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  position: relative;
}

/* ---- header: monogram / title / right chips ----
   The web app runs standalone with viewport-fit=cover and a translucent
   status bar, so the view extends under the clock and the Dynamic Island —
   the header must inset itself or it renders behind them. Sticky at top:0
   keeps the inset area filled with --bg while content scrolls under it. */
.app-header {
  position: sticky; top: 0; z-index: 10;
  background: var(--bg);
  padding: calc(14px + env(safe-area-inset-top)) 16px 8px;
  display: flex; align-items: center; gap: 10px;
  min-height: 56px;
}
.app-title { font-size: 20px; font-weight: 700; letter-spacing: -0.2px; color: var(--ink); }
/* .app-title .co is GONE with the KRAFT/ful weight break it styled. The
   training module's header is "KRAFT" now — KRAFTful is the product — and the
   product's wordmark ships as an outlined SVG lockup rather than live text, so
   this should not return as a CSS rule. See design/brandfiles/svg/. */
.spacer { flex: 1; }

/* ── The header lockup ─────────────────────────────────────────────────────
   Mark plus wordmark, on every screen. Outlined SVG rather than live text so
   the KRAFT/ful weight break cannot reflow and no webfont is involved; two
   files swapped by theme because both are transparent artwork. Same shape as
   coach.css's .mark — dark is the base, light is the override. */
.app-brand { display: inline-flex; align-items: center; flex-shrink: 0; }
/* The routine name beside the lockup on a live session — the one screen that
   still needs to name itself, because you are looking at it mid-lift. */
.session-name { font-size: 15px; font-weight: 600; color: var(--dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 40vw; }
.app-brand .lockup { height: 20px; width: auto; display: block; }
.app-brand .lockup-light { display: none; }
:root[data-theme="light"] .app-brand .lockup-dark { display: none; }
:root[data-theme="light"] .app-brand .lockup-light { display: block; }

/* ── The mark ──────────────────────────────────────────────────────────────
   ⚠️ THE SUPPLIED ARTWORK, NOT A TYPED LETTER, and not a recolour of it.
   svg/kraftful-mark-circle.svg is the file the spec names for the header and
   tab avatar, used unchanged: an ink disc carrying a bone broken-ring, the
   Archivo ExtraBold K, and two amber dots leaving on an outward trajectory.
   The ring is the cycle, the gap is where the dot left it, and the thesis is
   graduation. It was briefly a text "K" in the UI font, which is the re-trace
   design/brandfiles/README.md tells you not to do.

   ⚠️ THE SAME FILE IN BOTH THEMES, deliberately. Client identity is bone ON
   ink; ink on bone is the COACH inversion, so theming this would hand the
   client the coach's silhouette every time the page went light. On a dark page
   the disc merges into --surface-page and the mark reads as a floating ring
   and K; on a light page the same disc reads as a solid token. That is the
   intended behaviour of one silhouette, not two.

   The disc is IN the artwork, so no background-colour here — anything set
   behind it would show as a rim.

   Applied below by .eve-btn (the dock button) and .login-mark. The header no
   longer carries a bare mark — it shows the full lockup on every screen — so
   the old .monogram rule that lived here is gone with it. invite.html and
   reset.html mirror these same three lines inline, because they are standalone
   pages that never load this stylesheet. */

.hdr-pill {
  font-size: 12.5px; color: var(--ink-3);
  border: 1px solid var(--chip-line-2); border-radius: var(--r-pill);
  padding: 5px 12px; font-variant-numeric: tabular-nums;
}
.hdr-icon {
  width: 30px; height: 30px; border-radius: 9px;
  background: var(--field);
  display: flex; align-items: center; justify-content: center;
  color: var(--dim); font-size: 14px;
}
.hdr-icon.round { border-radius: 13px; width: 26px; height: 26px; background: none; border: 1px solid var(--chip-line-2); font-size: 13px; }

/* ---- content ---- */
.content {
  flex: 1;
  padding: 4px 16px 140px;   /* room for the floating dock */
  display: flex; flex-direction: column; gap: 12px;
}

/* ---- cards ---- */
.card {
  background: var(--card-grad);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  padding: 18px 20px;
}
.card.list { padding: 6px 20px; }
.card.tight { padding: 16px 18px; }
.card-title { font-size: 17px; font-weight: 700; }
.card-head { display: flex; align-items: center; gap: 8px; }
.row { display: flex; align-items: center; gap: 10px; }
/* ⚠️ min-width: 0 — a flex/grid item defaults to min-width:auto, which is its
   content's minimum. An <input> reports ~177px there whatever you set width to,
   so a two-column grid of inputs measured 502px inside a 375px phone and the
   sheet ran off the screen. Several call sites had already patched this inline;
   it belongs here. */
.col { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.half-row { display: flex; gap: 12px; }
.half-row > .card { flex: 1; }

/* module OFF — dashed read-only card */
.card.off {
  background: var(--canvas);
  border: 1px dashed var(--hatch);
}
.card.off .card-title, .card.off .hero { color: var(--ink-3); }
.card.off .hero-unit, .card.off .footnote { color: var(--dock-dim); }

/* ---- source chips + pills ---- */
.src {
  margin-left: auto;
  font-size: 9px; font-weight: 700; letter-spacing: 0.13em;
  color: var(--dim);
  border: 1px solid var(--chip-line); border-radius: var(--r-pill);
  padding: 3px 9px; text-transform: uppercase; white-space: nowrap;
}
/* ⚠️ .src.acc is a NO-OP, kept so call sites need no edit. It used to be the
   periwinkle-on-tinted pill that marked "this came from one of our own
   modules" (KOST, KRAFT) as against an external feed. The spec retires it to a
   neutral pill outright, and it was the last place a source chip carried hue.
   A brighter pill next to a dimmer one was still ranking the sources by
   colour, which is the channel doing chrome's job. */
.src.acc { }

.pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12.5px; font-weight: 600;
  border-radius: var(--r-pill); padding: 3px 10px;
  border: 1px solid var(--chip-line-2);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.pill .dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; }
/* ⚠️ Pills are NEUTRAL, including delta pills, and .good/.warn are deliberately
   no-ops kept so call sites need no edit. "−0.23 kg/wk" states the delta; the
   HERO states the judgement, and the eye lands on the hero first — value and
   verdict in a single fixation. A green ring round the delta and a green hero
   is the same fact coloured twice.
   The one exception is a hard miss, which may take --miss for border and text. */
.pill.good { color: var(--ink-3); border-color: var(--chip-line-2); }
.pill.warn { color: var(--ink-3); border-color: var(--chip-line-2); }
.pill.bad  { color: var(--miss); border-color: var(--miss-border); }
.pill.acc  { color: var(--acc); background: var(--acc-tint); border: none; font-weight: 700; font-size: 11px; letter-spacing: 0.4px; }

/* micro-label — tracked caps, used inside KOST + eyebrows only */
.micro {
  font-size: 9px; font-weight: 700; letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--dim);
}
.micro.faint { color: var(--faint); }

/* ---- hero values ---- */
/* ⚠️ WEIGHT 800, AND THE RATIO TO THE EYEBROW IS THE POINT. The neutral
   palette leans on type for hierarchy that colour used to provide: retiring
   periwinkle took an entire channel out of the visual system, and scale
   contrast is what replaces it. A 42px/800 numeral against a 9px/0.13em
   eyebrow is roughly a 4.7x size ratio doing that work.
   Verified on the cards that hold four numbers (Energy, Program, the weight
   stat tiles) rather than one — the failure mode of a big ratio is a deck of
   competing headlines, and those cards are where it would show first. */
.hero {
  font-size: 42px; font-weight: 800; letter-spacing: -1.5px;
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.hero.k44 { font-size: 44px; }
/* The hero carries the verdict — the one place a large area of saturated colour
   is allowed, because it is where the eye lands first. No class = --neutral,
   which is the correct state for "no target" and for "still in progress". */
.hero.compliant { color: var(--compliant); }
.hero.miss      { color: var(--miss); }
.hero-unit { font-size: 14px; color: var(--dim); font-variant-numeric: tabular-nums; }
.baseline { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.footnote { font-size: 12.5px; color: var(--dim); margin-top: 8px; font-variant-numeric: tabular-nums; }
.footnote b { color: var(--ink); font-weight: 600; }
.footnote .em { color: var(--ink-2); font-weight: 600; }
.meta12 { font-size: 12px; color: var(--faint); line-height: 1.5; }
.meta12 b { color: var(--ink-2); font-weight: 600; }

/* ---- program fold-out (Routines screen) ---- */
.prog { border-top: 1px solid var(--card-line); }
.prog:first-of-type { border-top: 0; }
.prog > summary { list-style: none; display: flex; align-items: center; gap: 8px; }
.prog > summary::-webkit-details-marker { display: none; }
.prog-caret { color: var(--text-quaternary); font-size: 18px; transition: transform .18s var(--ease); }
.prog[open] .prog-caret { transform: rotate(90deg); }
.prog-body { padding-left: 12px; border-left: 2px solid var(--hairline); margin: 2px 0 8px 6px; }
/* The programme's own progression rule, in its own words. For a solo lifter
   there is no coach to explain what the engine cannot automate, so the rule
   text IS the instruction — see the seed's progression_rule column. */
.prog-rule { font-size: 12.5px; line-height: 1.5; color: var(--text-secondary);
  padding: 8px 0 10px; }
@media (prefers-reduced-motion: reduce) { .prog-caret { transition: none; } }

/* ---- progress bars ---- */
.bar {
  height: 8px; border-radius: 4px;
  background: var(--surface-track); overflow: hidden;
}

/* ── Target bars ───────────────────────────────────────────────────────────
   .bar.target marks a bar that tracks a TARGET, and its fill then means
   compliance and nothing else: --noncompliant until the target is met,
   --compliant when it is, --miss when a ceiling has been exceeded.

   ⚠️ DIRECTION IS PER METRIC, WHICH IS WHY THE CLASS SAYS `met` RATHER THAN A
   PERCENTAGE. Steps and protein are FLOORS — met at 100%. Calories are a
   CEILING you are trying to stay under, so under budget IS met and filling the
   bar is not an achievement. One rule, two directions, decided at the call
   site by the same test the hero uses.

   ⚠️ NOT every bar is a target bar. Kraft's up/same/down breakdown is a
   distribution — three bars describing one set of exercises — and colouring it
   by compliance would borrow the channel to say something it does not mean.
   Those keep the plain .bar and their own colours.

   The leading-edge dot rides on .target too, echoing the escaping dot on the
   icon. `overflow: visible` is required because the dot sits half outside the
   track; the fill re-clips its own corners. */
.bar.target > .fill      { background: var(--noncompliant); }
.bar.target > .fill.met  { background: var(--compliant); }
.bar.target > .fill.over { background: var(--miss); }

.bar.target { overflow: visible; }
.bar.target > .fill { position: relative; }
.bar.target > .fill::after {
  content: ''; position: absolute; right: 0; top: 50%;
  width: 6px; height: 6px; border-radius: 50%;
  transform: translate(50%, -50%);
  background: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-halo);
}
.bar.k9 { height: 9px; border-radius: 5px; }
.bar > .fill {
  height: 100%; border-radius: inherit;
  background: var(--good);
  transition: width 0.4s var(--ease);
}
.bar > .fill.warn { background: var(--noncompliant); }
.bar > .fill.bad { background: var(--bad); }
.bar > .fill.acc { background: var(--acc); }
.bar > .fill.same { background: var(--faint); }
@media (prefers-reduced-motion: reduce) {
  .bar > .fill { transition: none; }
  * { animation: none !important; }
}

/* ---- buttons ---- */
.btn-primary {
  display: block; width: 100%;
  background: var(--acc); color: var(--bg);
  border-radius: var(--r-btn);
  padding: 14px; text-align: center;
  font-size: 15.5px; font-weight: 700;
}
.btn-primary.k18 { border-radius: 18px; padding: 15px; font-size: 16px; }
.btn-primary:disabled { opacity: 0.45; cursor: default; }
.btn-outline {
  background: var(--dock-bg);
  border: 1px solid var(--acc-border-strong);
  color: var(--acc);
  border-radius: var(--r-btn);
  padding: 14px; text-align: center;
  font-size: 15.5px; font-weight: 700;
}
.btn-quiet {
  border: 1px solid var(--chip-line-2); color: var(--ink-2);
  border-radius: var(--r-btn);
  padding: 14px 18px; font-size: 15px; font-weight: 600;
}
.btn-chip {   /* Swap, ⋯ — small hairline pill button */
  font-size: 12.5px; font-weight: 600; color: var(--ink-2);
  border: 1px solid var(--chip-line-2); border-radius: var(--r-pill);
  padding: 6px 14px;
}
/* Underlined for the same reason as coach.css's `a` — with periwinkle gone,
   colour alone no longer says "this is a link". */
.link { font-size: 13.5px; font-weight: 600; color: var(--acc); cursor: pointer; background: none; border: none; padding: 0;
  text-decoration: underline; text-underline-offset: 2px; }
.link.bad { color: var(--bad); }
button:active { transform: scale(0.97); }

/* unlock pill (settings) */
.unlock-pill {
  background: var(--acc-tint-2); color: var(--acc);
  font-size: 13px; font-weight: 700;
  border-radius: var(--r-pill); padding: 8px 18px; flex-shrink: 0;
}

/* ---- toggle (mint when on) ---- */
.toggle {
  width: 50px; height: 30px; border-radius: 15px; flex-shrink: 0;
  background: var(--field); border: 1px solid var(--chip-line);
  position: relative; transition: background 0.2s var(--ease); padding: 0;
}
.toggle::after {
  content: ''; position: absolute; top: 2px; left: 2px;
  width: 26px; height: 26px; border-radius: 13px;
  background: var(--dim);
  transition: transform 0.2s var(--ease), background 0.2s var(--ease);
}
.toggle.on { background: var(--good); border-color: var(--good); }
.toggle.on::after { transform: translateX(20px); background: #fff; }

/* ---- list rows ---- */
.list-head { display: flex; align-items: center; padding: 14px 0 8px; }
.list-row {
  display: flex; align-items: center; gap: 14px;
  min-height: 48px; padding: 13px 0;
  border-bottom: 1px solid var(--card-line);
}
.list-row:last-child { border-bottom: none; }
.list-row .name { flex: 1; font-size: 15px; font-weight: 600; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.list-row .sub { font-size: 12px; color: var(--dim); margin-top: 2px; }
.list-row .meta { font-size: 12.5px; color: var(--dim); text-align: right; font-variant-numeric: tabular-nums; }
.list-row .meta b { color: var(--ink); font-weight: 600; }
.chev { flex-shrink: 0; }

/* ---- session grammar: sub-cards, value chips, RIR, beam ---- */
.subrow {
  border: 1px solid var(--sub-line);
  border-radius: var(--r-sub);
  padding: 14px; margin-top: 10px;
}
.subrow.done { border-color: var(--good-border-strong); }
.setnum {
  width: 24px; height: 24px; border-radius: 12px; flex-shrink: 0;
  border: 1.5px solid var(--faint); color: var(--dim);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.setnum.done { border-color: var(--good); color: var(--good); }
.setnum.sm { width: 22px; height: 22px; border-radius: 11px; font-size: 11px; }
.valchip {
  background: var(--val); border-radius: var(--r-val); border: none;
  padding: 9px 14px; font-size: 18px; font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--ink);
  display: inline-flex; align-items: baseline; gap: 5px;
}
.valchip .unit { font-size: 12px; font-weight: 500; color: var(--dim); }
.valchip input {
  /* ⚠️ ch, NOT px — and a different width for each field. 44px fitted "100"
     and clipped "207.5" to "207." plus half a glyph, which does not read as a
     truncated number: it reads as a corrupt one, and the value it hides is the
     load somebody is about to lift.
     The chip is inline-flex, so the input sizes and the chip follows. 5.5ch
     holds "207.5" and "1000"; reps never needs more than three. */
  width: 3.2ch; background: none; border: none; outline: none;
  font: inherit; color: inherit; text-align: center; padding: 0;
}
/* 6.5ch, measured rather than guessed: "207.5" renders 60px at this size, so
   5.5ch (55px) still clipped it. */
.valchip input[data-winput] { width: 6.5ch; }

/* Where the browser can size an input to its own content, let it — then no
   width is ever a guess, and a value nobody predicted still fits. The fixed
   widths above remain the fallback. */
@supports (field-sizing: content) {
  /* ⚠️ BOTH SELECTORS, because @supports adds no specificity: on its own,
     `.valchip input` loses to `.valchip input[data-winput]` above and the
     fixed width silently wins. Measured it doing exactly that. */
  .valchip input,
  .valchip input[data-winput] { field-sizing: content; width: auto; min-width: 2.5ch; }
}
.valchip.sm { border-radius: 10px; padding: 7px 12px; font-size: 14.5px; }
.valchip.sm .unit { font-size: 11px; }
.done-btn {
  width: 40px; height: 40px; border-radius: 12px; flex-shrink: 0;
  background: var(--field); color: var(--faint);
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; margin-left: auto;
}
.done-btn.done { background: var(--good); color: var(--bg); font-weight: 800; }
.beam { height: 3px; border-radius: 2px; background: var(--track-2); margin-top: 8px; position: relative; }
.beam .marker { position: absolute; top: -3px; width: 3px; height: 9px; border-radius: 2px; background: var(--good); }
.beam .marker.warn { background: var(--noncompliant); }
.beam-state { font-size: 12.5px; font-weight: 600; margin-top: 10px; }
.beam-state.good { color: var(--good); }
.beam-state.warn { color: var(--warn); }
.prog-line { display: flex; justify-content: space-between; font-size: 12px; color: var(--dim); margin-top: 12px; font-variant-numeric: tabular-nums; }
.prog-line b { color: var(--ink-2); font-weight: 600; }
.rir-row { display: flex; align-items: center; gap: 6px; margin-top: 10px; }
.rir-label { font-size: 11px; font-weight: 700; color: var(--dim); width: 26px; }
.rir-chip {
  flex: 1; text-align: center; font-size: 13.5px; font-weight: 600;
  color: var(--dim); background: var(--field);
  border-radius: var(--r-chip); padding: 8px 0;
  font-variant-numeric: tabular-nums;
}
.rir-chip.selected { color: var(--bg); background: var(--acc); font-weight: 700; }
.role-pill {
  font-size: 11px; font-weight: 700; border-radius: var(--r-pill); padding: 3px 10px;
  color: var(--acc); background: var(--acc-tint);
}
.role-pill.backoff { color: var(--warn); background: color-mix(in srgb, var(--warn) 10%, transparent); }
.role-pill.warmup { color: var(--dim); background: var(--field); }

/* ---- eyebrow (EXERCISE 1 OF 14 · REST 1:30) ---- */
.eyebrow { font-size: 11px; font-weight: 700; letter-spacing: 0.8px; color: var(--dim); text-transform: uppercase; }

/* ---- search field ---- */
.search-field {
  flex: 1; display: flex; align-items: center; gap: 10px;
  background: var(--field); border-radius: 14px;
  padding: 12px 14px; min-height: 46px; min-width: 0;
}
.search-field input {
  flex: 1; min-width: 0; background: none; border: none; outline: none;
  font-size: 14.5px; color: var(--ink);
}

/* ---- segmented pill (7d / 30d / 90d) ---- */
.seg { display: flex; background: var(--field); border-radius: var(--r-pill); padding: 3px; }
.seg button {
  font-size: 12px; font-weight: 600; color: var(--dim);
  border-radius: var(--r-pill); padding: 5px 14px;
}
.seg button.active { font-weight: 700; color: var(--bg); background: var(--acc); }

/* ---- the Eve dock ---- */
.dock-wrap {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  padding: 14px 14px calc(20px + env(safe-area-inset-bottom));
  background: linear-gradient(to top, var(--bg) 62%, var(--bg-0));
  pointer-events: none;
}
.dock-inner { max-width: 600px; margin: 0 auto; pointer-events: auto; }
.dock-extra { margin-bottom: 8px; }        /* screen-supplied footer content (progress bars, buttons) */
.dock-under { margin-top: 10px; }          /* screen-supplied content BELOW the dock buttons */
/* No playlists, no gap. The host div is always created, so without this its
   margin adds 10px of dead space under the buttons on every workout for
   anybody who has not added a link. */
.dock-under:empty { display: none; }

/* The music launcher under the workout dock. Chips here carry the dock's own
   surface rather than --field: --field is 5% white, and 5% white over a
   background that is itself a fade reads as a translucent smudge floating on
   the screen. --dock-bg is 94% opaque and is what the buttons directly above
   are made of, so the row lands as the bottom edge of the same object.
   Height is capped by the chip's own line, not by --tap-min: this row sits
   below the primary actions and must not push them up the screen. */
.music-strip { gap: 6px; }
.music-strip .chip {
  min-height: 38px; padding: 0 13px;
  background: var(--dock-bg); border: 1px solid var(--chip-line);
  color: var(--ink-2); font-size: 12.5px; font-weight: 600;
  display: inline-flex; align-items: center; gap: 6px;
}
.music-strip .chip:active { color: var(--ink); }

/* variant A — dashboard: one pill, monogram + spaces */
.dock {
  display: flex; align-items: center;
  background: var(--dock-bg);
  border: 1px solid var(--chip-line);
  border-radius: 30px; padding: 7px;
}
.eve-btn {
  width: 46px; height: 46px; border-radius: 23px;
  background-image: url('/mark-circle.svg');
  background-repeat: no-repeat; background-position: center; background-size: 100%;
  display: flex; align-items: center; justify-content: center;
  font-size: 0; color: transparent; flex-shrink: 0;
  position: relative;
}
/* Unread coach message or a card waiting to be reviewed. A dot, not a count:
   the number is on the screen itself, and all the dock has to answer is "is
   there something". Bordered in the dock's own background so it reads as a
   badge sitting ON the monogram rather than a smudge in it.
   ⚠️ Mint (--good) rather than the accent, and the reason has CHANGED without
   the code needing to. It used to be that the monogram was periwinkle-filled
   and a periwinkle dot on a periwinkle disc is invisible. The disc is bone
   now, and mint on bone is 1.57:1 — worse, not better. What rescues it is the
   2.5px --dock-bg ring below: the badge reads against the ring, not against
   the disc, in both themes. Do not remove that border thinking it is
   decoration; it is the only thing making this legible. */
.eve-btn.has-news::after {
  content: ''; position: absolute; top: -1px; right: -1px;
  width: 13px; height: 13px; border-radius: var(--r-pill);
  background: var(--good); border: 2.5px solid var(--dock-bg);
}
.dock-sep { width: 1px; height: 28px; background: var(--chip-line); margin: 0 6px; }
.dock-item {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px;
  color: var(--dock-dim); min-height: var(--tap-min); justify-content: center;
}
.dock-item .lbl { font-size: 10.5px; font-weight: 600; }
.dock-item.active { color: var(--ink); }
.dock-item.active .lbl { font-weight: 700; color: var(--acc); }
.dock-item.off { color: var(--ghost); }
/* A KRAFT session left running. Mint, like the Eve button's news dot, because
   this is a status and not an alert — the workout is fine, it is just still
   open, and the label says "Kraft · live" beside it. */
.dock-item.live { color: var(--good); }
.dock-item.live .lbl { color: var(--good); font-weight: 700; }
.dock-item.live::after {
  content: ''; position: absolute; top: 4px; right: 50%;
  transform: translateX(22px);
  width: 8px; height: 8px; border-radius: var(--r-pill);
  background: var(--good); border: 2px solid var(--dock-bg);
}
.dock-item { position: relative; }

/* variant C — inside modules: floating Eve button + the module's own tabs */
.dock-c { display: flex; align-items: center; gap: 10px; }
.dock-c .eve-btn { width: 52px; height: 52px; border-radius: 26px; box-shadow: 0 8px 24px var(--shadow); }
.mod-tabs {
  flex: 1; display: flex; align-items: center;
  background: var(--dock-bg);
  border: 1px solid var(--chip-line);
  border-radius: 30px; padding: 15px 18px;
}
.mod-tabs button { flex: 1; text-align: center; font-size: 13px; font-weight: 600; color: var(--dock-dim); }
.mod-tabs button.active { font-weight: 700; color: var(--acc); }

/* The module's primary "add" sitting between its tabs. Filled disc, not a
   label, so it reads as an action inside a row that is otherwise navigation —
   and it lives in the dock, which is fixed, so it never scrolls away. The pill
   grows 48px → 56px to hold a full 44px tap target rather than letting the
   disc overhang. */
.mod-tabs.has-add { padding: 6px 10px; }
.mod-tabs.has-add > button { min-height: var(--tap-min); }
.mod-tabs .tab-add {
  flex: 0 0 auto; width: 44px; height: 44px; border-radius: var(--r-pill);
  background: var(--acc); color: var(--bg);
  display: flex; align-items: center; justify-content: center;
}

/* ---- bottom sheets (native <dialog>) ---- */
dialog.sheet-dialog {
  border: none; background: none; padding: 0;
  width: 100%; max-width: 600px;
  margin: auto auto 0; max-height: 92dvh;
  color: var(--ink);
}
dialog.sheet-dialog::backdrop { background: var(--scrim); }

/* ---- the food picker is a SCREEN, not a sheet ----
   It is opened from the dock's +, the same row Today and Recipes are opened
   from, so it has to arrive the same way: screen background, no grip to swipe,
   an opaque backdrop, and the full viewport.
   ⚠️ `height: 100%` here used to lose to `.sheet`'s own `max-height: 92dvh` —
   equal specificity, and max-height wins regardless — which is what left a
   strip of the app showing above it and made it read as a floating dialog. */
dialog.picker-dialog { max-height: 100dvh; height: 100dvh; }
dialog.picker-dialog::backdrop { background: var(--bg); }
dialog.picker-dialog .sheet {
  background: var(--bg);
  border: none; border-radius: 0;
  height: 100%; max-height: none; overflow-y: auto;
  padding-top: 0;
  animation: page-in 0.18s var(--ease);
}
dialog.picker-dialog .grip { display: none; }
@keyframes page-in { from { opacity: 0; } to { opacity: 1; } }

/* Title + search stay put while the results scroll — on a full screen the
   search field is the control you keep coming back to, and it used to scroll
   away with everything else. It carries the safe-area padding itself, so
   nothing lands under the status bar once it is stuck at the top.
   The negative margin cancels the flex gap it would otherwise leave, so rows
   scroll under the head instead of through a 12px slot below it. */
dialog.picker-dialog .pk-head {
  position: sticky; top: 0; z-index: 2;
  display: flex; flex-direction: column; gap: 12px;
  background: var(--bg);
  padding: calc(10px + env(safe-area-inset-top)) 0 12px;
  margin-bottom: -12px;
}
.sheet {
  background: var(--sheet);
  border-top: 1px solid var(--chip-line);
  border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  padding: 10px 20px calc(40px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 12px;
  overflow-y: auto; max-height: 92dvh;
  animation: sheet-up 0.25s var(--ease);
}
/* ⚠️ 38×5 px IS A PICTURE OF A HANDLE, NOT A HANDLE. Now that the grip
   actually dismisses the sheet it needs a thumb-sized target, so ::before
   spreads the hit area to roughly 29×160 px without changing a pixel of what
   is drawn. `touch-action: none` keeps iOS from claiming the drag for the
   sheet's own scroller before the first touchmove lands. */
.sheet .grip {
  width: 38px; height: 5px; border-radius: 3px; background: var(--grip);
  margin: 4px auto 8px; flex-shrink: 0;
  position: relative; touch-action: none; cursor: grab;
}
.sheet .grip::before { content: ''; position: absolute; inset: -12px -60px; }
.sheet .grip:active { cursor: grabbing; }
.sheet-title { font-size: 17px; font-weight: 700; }
@keyframes sheet-up { from { transform: translateY(24px); opacity: 0.6; } to { transform: none; opacity: 1; } }

/* switcher space rows */
.space-row {
  display: flex; align-items: center; gap: 14px;
  background: var(--wash);
  border: 1px solid var(--sub-line);
  border-radius: var(--r-sub);
  padding: 14px 16px; width: 100%; text-align: left;
}
.space-row.active { background: color-mix(in srgb, var(--acc) 10%, transparent); border-color: var(--acc-border); }
.space-row .name { font-size: 15.5px; font-weight: 700; }
.space-row .sub { font-size: 12px; color: var(--dim); margin-top: 2px; }

/* ---- info note (routine editor progression note) ---- */
.note {
  display: flex; gap: 10px;
  background: color-mix(in srgb, var(--acc) 7%, transparent);
  border: 1px solid color-mix(in srgb, var(--acc) 18%, transparent);
  border-radius: 14px; padding: 11px 14px;
  font-size: 12px; color: var(--dim); line-height: 1.5;
}
.note .i { color: var(--acc); font-weight: 700; }

/* ---- inputs (settings, weigh-in) ---- */
.field-input {
  min-height: 44px;
  background: var(--field); border: none; border-radius: 12px;
  color: var(--ink); padding: 0 14px; font-size: 14.5px; outline: none;
}
.field-input:focus { outline: 1px solid var(--acc-border); }

/* ---- charts ---- */
.chart-svg { width: 100%; display: block; }

/* ---- empty states ---- */
.empty { font-size: 14px; color: var(--dim); padding: 16px 0; text-align: center; }
.caption { font-size: 12px; color: var(--faint); text-align: center; padding: 2px 24px; line-height: 1.5; }

/* ---- compat: legacy class names, restyled to the new grammar ----
   (kost-picker and parts of kraft were written against the first vocabulary;
   these render them per the Eve design until each file is fully migrated.) */
.icon-btn {
  width: 30px; height: 30px; border-radius: 9px;
  background: var(--field); border: none;
  display: flex; align-items: center; justify-content: center;
  color: var(--dim); flex-shrink: 0;
}
.icon-btn.lg { width: 46px; height: 46px; border-radius: 14px; color: var(--ink); }
.recessed {
  display: flex; align-items: baseline; gap: 6px;
  background: var(--val); border: none; border-radius: var(--r-val);
  padding: 9px 14px; min-width: 0;
}
.recessed input {
  min-width: 0; background: none; border: none; outline: none;
  font-size: 18px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums; width: 52px;
}
.recessed input::placeholder { color: var(--dim); font-weight: 700; }
.recessed .unit { font-size: 12px; font-weight: 500; color: var(--dim); }
.chip-row { display: flex; gap: 8px; flex: 1; }
.chip {
  flex: 1; min-width: var(--tap-min); min-height: var(--tap-min);
  border-radius: var(--r-chip); border: none;
  background: var(--field); color: var(--dim);
  font-size: 13.5px; font-weight: 600; font-variant-numeric: tabular-nums;
}
.chip.selected { background: var(--acc); color: var(--bg); font-weight: 700; }
/* Chips that carry words rather than numbers — meal labels. Equal columns
   clip "Aftensmad" at any phone width, so these size to their text and wrap
   onto a second line instead. */
.chip-row.wrap { flex-wrap: wrap; }
.chip-row.wrap .chip { flex: 0 1 auto; padding: 0 14px; white-space: nowrap; }
/* Word chips in a FIXED-HEIGHT box — the music launcher in the workout footer.
   `.wrap` is wrong there: a fifth playlist would add a second line and move the
   progress bar, so the row's height would depend on how many playlists you own.
   This one scrolls sideways instead. Without it, `.chip { flex: 1 }` divides
   the width equally and every label wraps inside its own chip — "One album, on
   repeat" came out three lines tall. */
.chip-row.scroll {
  flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.chip-row.scroll::-webkit-scrollbar { display: none; }
.chip-row.scroll .chip { flex: 0 0 auto; padding: 0 13px; white-space: nowrap; }
.quick-log {
  min-height: var(--tap-min); padding: 8px 14px;
  background: var(--acc-tint-2); border: none; border-radius: var(--r-pill);
  color: var(--acc); font-size: 13.5px; font-weight: 700;
  font-variant-numeric: tabular-nums; white-space: nowrap; flex-shrink: 0;
}
.quick-log.logged { background: color-mix(in srgb, var(--good) 14%, transparent); color: var(--good); }
.btn {
  min-height: var(--tap-min); padding: 0 14px;
  background: var(--field); border: 1px solid var(--chip-line);
  border-radius: 14px; color: var(--ink);
  font-size: 13.5px; font-weight: 600;
}
.card.card-list { padding: 6px 16px; }
.card-flat { padding: 14px 16px; }
.list-row.tall { min-height: 60px; padding: 8px 0; }
.subcard {
  border: 1px solid var(--sub-line); background: none;
  border-radius: var(--r-sub); padding: 14px;
  display: flex; flex-direction: column; gap: 12px;
}
.subcard.done { border-color: var(--good-border-strong); }
.hero-sm { font-size: 42px; }
.value-lg { font-size: 24px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.5px; }
.beam-track { position: relative; height: 3px; border-radius: 2px; background: var(--track-2); }
.beam-band { position: absolute; top: 0; bottom: 0; background: var(--baseline-band); border-radius: 2px; opacity: 0.5; }
.beam-marker { position: absolute; top: -3px; width: 3px; height: 9px; border-radius: 2px; background: var(--good); }
.beam-marker.warn { background: var(--noncompliant); }
.setnum.sm2 { width: 26px; height: 26px; }
.module-off-note { font-size: 12px; color: var(--dim); }
.locked-card { align-items: center; text-align: center; gap: 8px; padding: 24px 16px; display: flex; flex-direction: column; }
.setting-row { display: flex; align-items: center; gap: 12px; min-height: 56px; border-top: 1px solid var(--card-line); padding: 8px 0; }
.setting-row:first-of-type { border-top: none; }
.setting-row .title { font-size: 14px; font-weight: 600; color: var(--ink); }
.setting-row .sub { font-size: 12px; color: var(--dim); }

/* ---- landscape on a phone: ask for portrait ----
   Scoped to touch devices short enough to be a handset, so tablets, the
   desktop browser and the dev preview are untouched. Modal dialogs live in
   the top layer and would paint over any normal overlay, so they are hidden
   here rather than covered — reopening on rotate is automatic. */
#rotate-hint { display: none; }
@media (orientation: landscape) and (max-height: 520px) and (pointer: coarse) {
  #app, .dock-wrap, dialog[open] { display: none !important; }
  #rotate-hint {
    display: flex; position: fixed; inset: 0; z-index: 200;
    background: var(--bg); color: var(--dim);
    align-items: center; justify-content: center; text-align: center;
    padding: 24px;
  }
  #rotate-hint .rot-text { font-size: 14px; font-weight: 600; margin-top: 12px; }
}

/* ---- toast ---- */
#toast {
  position: fixed; left: 50%; bottom: 120px; transform: translateX(-50%) translateY(8px);
  /* ⚠️ ABOVE THE SHEETS (200), NOT BELOW THEM (60).
     Every error in this app is reported with a toast, and almost every action
     that can fail happens inside a sheet — logging food, saving targets,
     unlocking a module. At z-index 60 the report was painted UNDERNEATH the
     thing that raised it, so a failed write looked like a dead button.
     That is what "log food does nothing" looked like from the outside, and it
     is why it took a server-side hunt to find a client-side symptom. */
  z-index: 300; max-width: 90vw;
  background: var(--sheet); border: 1px solid var(--chip-line); border-radius: 14px;
  color: var(--ink); font-size: 13px; font-weight: 600;
  padding: 10px 16px; opacity: 0; pointer-events: none;
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease);
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ---- dashboard detail panels ----
   Ported detail bodies from dash/public/dashboard.html, re-clothed in Eve
   tokens. Everything below is additive and scoped (.detail / .compliance-body /
   the expandable-head classes) so no existing rule changes meaning. */

:root {
  /* two legacy vars the ported markup uses that tokens.css does not alias */
  --bad-tint: color-mix(in srgb, var(--bad) 10%, transparent);
  --accent-border: var(--acc-border);
}

/* expandable card head: whole row is the tap target, chevron rotates open */
.card-head.tappable {
  cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent;
  min-height: var(--tap-min); padding: 10px 0; margin: -10px 0;
}
.card-head .exp-chev {
  display: flex; align-items: center; justify-content: center;
  width: 20px; flex-shrink: 0; margin-left: 2px;
  color: var(--faint); transition: transform 0.2s var(--ease);
}
.card-head.tappable[aria-expanded="true"] .exp-chev { transform: rotate(90deg); }

/* method-note "i" button (44px target, small glyph) */
.info-btn {
  width: var(--tap-min); height: var(--tap-min); margin: -12px -10px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center; color: var(--dim);
}
.info-btn span {
  width: 18px; height: 18px; border: 1.5px solid currentColor; border-radius: 50%;
  font-size: 11.5px; font-weight: 700; font-style: italic; line-height: 16px; text-align: center;
}
.sheet .method-p { font-size: 14px; line-height: 1.55; color: var(--ink-2); margin: 0; }
.sheet .method-p b, .sheet .method-p code { color: var(--ink); }
.sheet .method-p code { font-family: ui-monospace, SFMono-Regular, monospace; font-size: 12.5px; }

/* detail body under a summary card */
.detail {
  margin-top: 14px; border-top: 1px solid var(--card-line); padding-top: 14px;
  display: flex; flex-direction: column; gap: 10px;
}
.detail[hidden] { display: none; }
.detail hr { border: 0; border-top: 1px solid var(--card-line); margin: 4px 0; width: 100%; }
.detail .sub { font-size: 12.5px; color: var(--dim); line-height: 1.5; }
.detail .sub b.up { color: var(--good); }
.detail .sub b.down { color: var(--bad); }

/* window segments (ported markup drives state via aria-pressed) */
.detail .seglab { font-size: 12px; color: var(--dim); margin-bottom: -6px; }
.detail .seg { width: 100%; }
.detail .seg button { flex: 1; min-height: var(--tap-min); padding: 5px 6px; }
.seg button[aria-pressed="true"] { font-weight: 700; color: var(--bg); background: var(--acc); }

/* stat tiles */
.detail .stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.detail .stat { background: var(--field); border: 1px solid var(--card-line); border-radius: 12px; padding: 10px 12px; }
.detail .stat .k { font-size: 12px; color: var(--dim); }
.detail .stat .n { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 2px; }

/* metric rows (label / value / bar) */
.detail .metric { display: grid; grid-template-columns: 1fr auto; gap: 4px 10px; align-items: center; padding: 2px 0; }
.detail .metric .lab { font-size: 13px; color: var(--ink); }
.detail .metric .val { font-size: 15px; font-weight: 650; font-variant-numeric: tabular-nums; }
.detail .metric .bar { grid-column: 1 / -1; }
/* neutralise the app-level .note (info box) inside detail bodies — here it is
   the ported quiet caption, not the accent note component */
.detail .note {
  display: block; background: none; border: none; border-radius: 0; padding: 0;
  font-size: 12px; color: var(--faint); line-height: 1.5;
}
.detail .metric .note { grid-column: 1 / -1; }

/* ported bars fill with an <i> child */
/* Detail bars follow the same rule as .bar.target — fill means compliance, so
   NOT MET is the default and .good is the only thing that turns it green. It
   used to default to --good, which made every bar green regardless of where it
   sat against its target.
   ⚠️ .accent is NOT a compliance bar. It is the cardio-modality split: each
   modality's share of the window's minutes, a magnitude with no target at all.
   It takes --data-line, the no-target series colour, so it cannot be read as a
   verdict on anything. */
.detail .bar > i { display: block; height: 100%; border-radius: inherit; background: var(--noncompliant); transition: width 0.4s var(--ease); }
.detail .bar.good > i { background: var(--compliant); }
.detail .bar.warn > i { background: var(--noncompliant); }
.detail .bar.bad > i { background: var(--miss); }
.detail .bar.accent > i { background: var(--data-line); }

/* chart legend chips (ported .chiprow > .chip — override the app-level .chip) */
.detail .chiprow { display: flex; flex-wrap: wrap; gap: 10px; }
.detail .chiprow .chip {
  flex: none; min-width: 0; min-height: 0; background: none; border-radius: 0;
  font-size: 12px; font-weight: 500; color: var(--dim);
  display: inline-flex; align-items: center; gap: 5px;
}
.detail .chiprow .chip i { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }

.detail .warnbox {
  background: var(--warn-tint); border: 1px solid var(--warn-border); border-radius: 12px;
  padding: 10px 12px; font-size: 13px; color: var(--ink); line-height: 1.5;
}

svg.chart { display: block; width: 100%; height: auto; overflow: visible; }

/* week-grouped tables */
.detail table { width: 100%; border-collapse: collapse; }
.detail th {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; color: var(--dim);
  text-align: right; padding: 6px 4px; font-weight: 600;
}
.detail th:first-child { text-align: left; }
.detail td {
  font-size: 13px; text-align: right; padding: 7px 4px;
  border-top: 1px solid var(--card-line); font-variant-numeric: tabular-nums;
}
.detail td:first-child { text-align: left; color: var(--dim); }
.detail tr.wkavg td { background: var(--field); font-weight: 650; color: var(--ink); }
.detail tr.wkavg td:first-child { color: var(--dim); font-weight: 600; }
.detail tr.wkstart td { border-top: 2px solid var(--chip-line); }

/* the "earlier rows" / lazy folds nested inside a detail body */
.detail details.more { border-top: 1px solid var(--card-line); margin-top: 2px; }
.detail details.more > summary {
  list-style: none; cursor: pointer; padding: 12px 0; min-height: var(--tap-min);
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.6px; color: var(--dim);
}
.detail details.more > summary::-webkit-details-marker { display: none; }
.detail details.more > summary::after {
  content: ""; width: 8px; height: 8px; flex-shrink: 0;
  border-right: 2px solid var(--faint); border-bottom: 2px solid var(--faint);
  transform: rotate(45deg) translateY(-2px); transition: transform 0.2s var(--ease);
}
.detail details.more[open] > summary::after { transform: rotate(-135deg) translateY(-2px); }

/* recovery signal rows + normal-range band */
.detail .sig { padding: 6px 0 2px; border-top: 1px solid var(--card-line); }
.detail .sig:first-of-type { border-top: 0; }
.detail .sig .row { align-items: baseline; gap: 8px; }
.detail .sig .row .lab { flex: 1; font-size: 14px; color: var(--ink); }
.detail .sig .row .now { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }
.detail .sig .row .dev { font-size: 13px; font-variant-numeric: tabular-nums; color: var(--dim); }
.detail .band { position: relative; height: 22px; margin: 5px 0 2px; }
.detail .band .track { position: absolute; top: 9px; left: 0; right: 0; height: 4px; border-radius: 2px; background: var(--track); }
.detail .band .norm { position: absolute; top: 9px; height: 4px; border-radius: 2px; background: var(--baseline-band); }
.detail .band .mark { position: absolute; top: 3px; width: 3px; height: 16px; border-radius: 2px; background: var(--ink); transform: translateX(-1.5px); }
.detail .band .mark.good { background: var(--good); }
.detail .band .mark.warn { background: var(--noncompliant); }
.detail .band .mark.bad { background: var(--bad); }
.detail .band .ends {
  position: absolute; top: 0; left: 0; right: 0; display: flex; justify-content: space-between;
  font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums;
}

/* ---- completed workout ---- */
.sum-head { padding: 2px 6px 4px; }
.sum-title { font-size: 26px; font-weight: 800; letter-spacing: -0.3px; }
.sum-when { font-size: 13px; color: var(--dim); margin-top: 3px; }
.sum-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 10px; }
.sum-tile { text-align: left; }
.sum-val { font-size: 30px; font-weight: 800; line-height: 1.1; letter-spacing: -0.5px; }
.sum-lbl {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.7px;
  text-transform: uppercase; color: var(--dim); margin-top: 3px;
}
/* The PR card is the one thing on this screen allowed to shout. */
.pr-card { border-color: var(--good-border-strong, var(--good)); }
.pr-row { font-size: 15px; font-weight: 600; padding: 5px 0; }

/* ⚠️ THE CHECKERBOARD IS THE MESSAGE, NOT DECORATION. The card is a
   TRANSPARENT png meant to land on your own photo; previewed on a flat dark
   panel it looks like white text on a black card, and nobody discovers the
   transparency until they have already posted it. The squares say "there is
   nothing behind this" in the one language every image editor shares. */
/* ⚠️ THE PREVIEW MUST BE THE CARD'S OWN SHAPE, OR IT LIES ABOUT IT. This was
   a landscape box with `max-height` on both the box and the image, and a
   1080×1920 card inside it got clipped rather than fitted — the bottom of
   every card was cut off, so the layouts looked broken when they were not.
   The box now IS 9:16: height is the constraint, width follows from
   aspect-ratio, and the image fills it exactly. */
.share-preview {
  border-radius: 14px; overflow: hidden; margin: 2px auto;
  display: flex; align-items: center; justify-content: center;
  height: 42vh; aspect-ratio: 1080 / 1920; width: auto;
  background-color: #2a2a30;
  background-image:
    linear-gradient(45deg, #3a3a42 25%, transparent 25%, transparent 75%, #3a3a42 75%),
    linear-gradient(45deg, #3a3a42 25%, transparent 25%, transparent 75%, #3a3a42 75%);
  background-size: 22px 22px;
  background-position: 0 0, 11px 11px;
}
.share-preview img { width: 100%; height: 100%; object-fit: contain; display: block; }
/* Dark ink on the standard grey checkerboard is unreadable for the same reason
   it exists — so the preview lightens when dark ink is chosen. Still checked,
   still saying transparent, just at the brightness the ink is FOR. */
.share-preview.on-light { background-color: #d8d8dc; }
.share-preview.on-light {
  background-image:
    linear-gradient(45deg, #eaeaee 25%, transparent 25%, transparent 75%, #eaeaee 75%),
    linear-gradient(45deg, #eaeaee 25%, transparent 25%, transparent 75%, #eaeaee 75%);
}

/* program week stepper + session sheet */
.detail .wknav { display: flex; align-items: center; gap: 10px; }
.detail .wknav button {
  min-width: var(--tap-min); min-height: var(--tap-min);
  border: 1px solid var(--chip-line); background: var(--field); color: var(--ink);
  border-radius: 12px; font-size: 16px; line-height: 1;
}
.detail .wknav button:disabled { opacity: 0.3; cursor: default; }
.detail .wknav .rng { flex: 1; text-align: center; font-size: 14px; font-weight: 650; color: var(--ink); font-variant-numeric: tabular-nums; }
.detail .wknav .rng small { display: block; font-size: 11px; font-weight: 500; color: var(--faint); }
.detail .mtx { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.detail .sessgrid { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(230px, 1fr); gap: 16px; align-items: start; }
.detail .sesshead { padding-bottom: 7px; border-bottom: 1px solid var(--card-line); }
.detail .sesshead .wo { display: block; font-size: 11px; color: var(--acc); letter-spacing: 0.5px; text-transform: uppercase; font-weight: 600; }
.detail .sesshead .dt { display: block; font-size: 14px; color: var(--ink); font-weight: 650; }
.detail .sesshead .mn { display: block; font-size: 11px; color: var(--faint); }
.detail .lift { padding: 10px 0; border-top: 1px solid var(--card-line); }
.detail .lift:first-of-type { border-top: 0; }
.detail .lname { font-size: 13px; color: var(--ink); font-weight: 600; line-height: 1.3; }
.detail .lift .sets { font-size: 14px; color: var(--ink); font-variant-numeric: tabular-nums; margin-top: 3px; }
.detail .lift .meta { font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; }
.detail .lift .warm { color: var(--faint); }
.detail .vs { font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; margin-top: 2px; }
.detail .vs.up { color: var(--good); }
.detail .vs.down { color: var(--bad); }
.detail .vs.same { color: var(--dim); }

/* compliance grid (This week) */
.compliance-body { margin-top: 12px; display: flex; flex-direction: column; gap: 12px; }
.compliance-body .grid { display: grid; grid-template-columns: auto repeat(7, 1fr); gap: 4px; align-items: center; }
.compliance-body .grid .h { font-size: 11px; color: var(--dim); text-align: center; text-transform: uppercase; letter-spacing: 0.4px; }
.compliance-body .grid .rl { font-size: 12px; color: var(--ink); padding-right: 6px; white-space: nowrap; }
.compliance-body .cell { height: 26px; border-radius: 7px; border: 1px solid var(--card-line); background: var(--field); }
.compliance-body .cell.good { background: var(--good-tint); border-color: var(--good-border); }
/* ⚠️ --noncompliant, not a neutral tint. This grid IS the compliance channel —
   a day either hit the target or it did not — so a not-met cell takes the
   same colour the not-met half of a bar takes. Left as --warn-tint it
   rendered as a faint white wash, which read as "lit but unremarkable"
   rather than "missed". .cell.none keeps its hatch, so no-data and
   target-missed stay different things. */
.compliance-body .cell.warn { background: var(--noncompliant); border-color: var(--hairline); }
.compliance-body .cell.bad { background: var(--bad-tint); border-color: var(--bad-border); }
.compliance-body .cell.none { background: repeating-linear-gradient(135deg, transparent, transparent 4px, rgba(91,102,118,0.18) 4px, rgba(91,102,118,0.18) 5px); }
.compliance-body .legend { display: flex; flex-wrap: wrap; gap: 8px; }
.compliance-body .legend span { font-size: 12px; color: var(--dim); display: inline-flex; align-items: center; gap: 5px; }
.compliance-body .legend i { width: 11px; height: 11px; border-radius: 3px; border: 1px solid var(--card-line); display: inline-block; }
.compliance-body .sub { font-size: 12.5px; color: var(--dim); font-variant-numeric: tabular-nums; }

/* ── recording (C9: text, voice, video) ────────────────────────────────── */
.rec-bar[hidden] { display: none; }   /* an element with display:flex ignores the
                                          hidden attribute, which only sets
                                          display:none at the weakest possible
                                          specificity — so say it explicitly */
.rec-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  border: 1px solid var(--bad-border); border-radius: var(--r-sub);
  padding: 10px 12px; margin-bottom: 10px; background: var(--field);
}
.rec-bar .spacer { flex: 1; }
.rec-dot {
  width: 10px; height: 10px; border-radius: var(--r-pill);
  background: var(--bad); animation: rec-pulse 1.2s var(--ease) infinite;
}
@keyframes rec-pulse { 0%,100% { opacity: 1 } 50% { opacity: 0.25 } }
@media (prefers-reduced-motion: reduce) { .rec-dot { animation: none } }
.rec-time { font-variant-numeric: tabular-nums; color: var(--ink-2); }
.rec-preview {
  width: 160px; border-radius: var(--r-val); background: #000;
  transform: scaleX(-1);   /* a selfie preview that is not mirrored feels wrong */
}
.rec-row { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
.rec-btn { white-space: nowrap; }


/* ── the coaching screen ───────────────────────────────────────────────────
   ⚠️ ALL SCOPED UNDER .coach-screen. This screen borrows shapes from the coach
   portal (a hairline tag, a before/after grid, a message thread) that the Eve
   app has no vocabulary for — and one name, .chip, that it DOES have and means
   something entirely different (a 44px flex tap target for RIR selection).
   Scoping keeps both meanings intact. Every value below is an existing token;
   nothing new is invented (§7.3). */
.coach-screen .card + .card { margin-top: var(--gap-card); }
.coach-screen .body { margin-top: 10px; }
.coach-screen .dim { color: var(--dim); }
.coach-screen .err { color: var(--bad); font-size: 13px; margin: 8px 0 0; }
.coach-screen h4 {
  margin: 14px 0 6px; font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--faint); font-weight: 600;
}

/* Hairline pill — the coach portal's .chip, renamed so it cannot collide. */
.coach-screen .tag {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--chip-line); background: var(--field);
  border-radius: var(--r-pill); padding: 3px 9px;
  font-size: 12px; color: var(--dim); white-space: nowrap;
}
.coach-screen .tag.acc { border-color: var(--acc-border); color: var(--acc); }

.coach-screen .access-list { margin: 0; padding-left: 18px; }
.coach-screen .access-list li { margin-bottom: 8px; line-height: 1.45; }

/* Before → after on an accept-card. Two columns so the numbers line up; the
   old value is struck through rather than removed, because "what changes" is
   the question a client actually has. */
.coach-screen .kv {
  display: grid; grid-template-columns: auto 1fr; gap: 4px 14px;
  font-variant-numeric: tabular-nums; margin: 8px 0;
}
.coach-screen .kv > :nth-child(odd) { color: var(--dim); }
.coach-screen .was { color: var(--faint); text-decoration: line-through; }

.coach-screen .row-btns { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.coach-screen .card.accent { border-color: var(--acc-border); background: var(--acc-tint); }
.coach-screen .card.dashed { border-style: dashed; background: transparent; }

/* The thread. Deliberately the same shapes as the coach portal's, so the two
   halves of one conversation look like one conversation. */
.coach-screen .thread { display: grid; gap: 10px; margin: 10px 0; max-height: 52vh; overflow-y: auto; }
.coach-screen .msg {
  max-width: 82%; padding: 10px 13px; border-radius: var(--r-sub);
  background: var(--field); border: 1px solid var(--card-line); font-size: 14.5px;
}
.coach-screen .msg.mine { margin-left: auto; background: var(--acc-tint); border-color: var(--acc-border); }
.coach-screen .msg.system {
  margin: 0 auto; background: none; border-style: dashed;
  color: var(--dim); font-size: 13px; text-align: center;
}
.coach-screen .msg .meta { font-size: 11px; color: var(--faint); margin-top: 5px; }
.coach-screen .msg audio { width: 100%; margin-top: 6px; }
.coach-screen .msg video { max-width: 100%; margin-top: 6px; border-radius: var(--r-val); }


/* Photos in the thread. Capped so a portrait phone shot does not become a
   scroll of its own, and tappable through to the full image. */
.coach-screen .msg img { max-width: 100%; max-height: 320px; border-radius: var(--r-val); margin-top: 6px; display: block; }

.coach-screen .composer { display: flex; gap: 8px; align-items: flex-end; }
.coach-screen .composer textarea {
  flex: 1; background: var(--field); border: 1px solid var(--chip-line); color: var(--ink);
  border-radius: var(--r-val); padding: 11px 12px; font: inherit; resize: vertical;
  min-height: var(--tap-min);
}
.coach-screen .composer textarea:focus { outline: none; border-color: var(--acc-border); }
.coach-screen input[type="text"], .coach-screen #coach-code {
  width: 100%; background: var(--field); border: 1px solid var(--chip-line); color: var(--ink);
  border-radius: var(--r-val); padding: 12px; font: inherit; min-height: var(--tap-min);
  margin-bottom: 10px;
}

/* ── the client's own plan (idea 3) ──────────────────────────────────────
   Scoped under .coach-screen like the rest of the coaching UI, so none of
   these names can collide with the app's own vocabulary — the lesson from
   .chip, which meant a 44px tap target here and a hairline pill there. */
.coach-screen .plan-line { list-style: none; margin: 14px 0 0; padding: 0 0 0 22px; position: relative; }
.coach-screen .plan-line::before {
  content: ''; position: absolute; left: 5px; top: 8px; bottom: 8px;
  width: 2px; background: var(--card-line);
}
.coach-screen .plan-phase { position: relative; padding: 0 0 16px; }
.coach-screen .plan-phase:last-child { padding-bottom: 0; }
.coach-screen .plan-dot {
  position: absolute; left: -22px; top: 4px; width: 12px; height: 12px;
  border-radius: 999px; background: var(--bg); border: 2px solid var(--ghost);
}
.coach-screen .plan-phase.is-active .plan-dot { border-color: var(--acc); background: var(--acc); }
.coach-screen .plan-phase.is-done .plan-dot,
.coach-screen .plan-phase.is-skipped .plan-dot { border-color: var(--good); background: var(--good); }
.coach-screen .plan-phase.is-done, .coach-screen .plan-phase.is-skipped { opacity: 0.68; }
.coach-screen .plan-phase .name { font-size: 14.5px; font-weight: 600; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.coach-screen .plan-phase .sub { font-size: 12.5px; color: var(--dim); line-height: 1.5; margin-top: 2px; }

/* ── sign in ─────────────────────────────────────────────────────────────
   A full-screen overlay, not a route. Every screen in this app needs data, so
   there is nothing to show behind it. */
.login-screen {
  position: fixed; inset: 0; z-index: 200;
  background: var(--bg);
  display: grid; place-items: center;
  padding: 24px calc(18px + env(safe-area-inset-left)) calc(24px + env(safe-area-inset-bottom));
}
.login-card { width: min(400px, 100%); display: grid; gap: 12px; }
.login-mark {
  width: 46px; height: 46px; border-radius: 999px;
  background-image: url('/mark-circle.svg');
  background-repeat: no-repeat; background-position: center; background-size: 100%;
  display: grid; place-items: center;
  font-size: 0; color: transparent; margin-bottom: 4px;
}
.login-screen .field { display: grid; gap: 6px; font-size: 13px; color: var(--dim); }
.login-screen .field input {
  background: var(--field); border: 1px solid var(--chip-line); color: var(--ink);
  border-radius: var(--r-val); padding: 13px; font: inherit;
  /* 16px stops iOS zooming the page when the field takes focus — below that
     Safari treats it as unreadable and scales, which shifts the whole layout. */
  font-size: 16px; min-height: var(--tap-min);
}
.login-screen .field input:focus { outline: none; border-color: var(--acc-border); }
.login-err { color: var(--bad); font-size: 13px; line-height: 1.5; }
