/* public/win/win.css — Phase 18 Plan 04 (PUB-07, 18-UI-SPEC.md)
   + Quick task 260526-jzh (PUB-07 redesign).
   Phone-first shell styles. CONSUMES the existing theme tokens only — no
   hardcoded hex, px, or font literals (the locked px exceptions are: the 44px
   touch-target minimum, the avatar circle dimensions, the steps ring
   diameter/stroke, the @media breakpoint widths, and the 960px layout-container
   max-width — none can be expressed in the existing token scale).

   ROOT CAUSE of the surfaceless-cards bug (jzh #1): the /win document loads ONLY
   theme.css + win.css (win-shell.js buildHead + index.html head) — it NEVER loads
   components.css, so the .card rule (which carries the background/border/shadow/
   radius/padding) has NO effect here. Cards rendered as muted text floating on
   --bg-primary. FIX: a WIN-SCOPED surface below on `.win-slot, .win-streak,
   .win-steps` re-establishes the elevated card look from tokens only.

   GRID ORDER (jzh #2 → lqq LQQ-01): GLANCE-FIRST hierarchy. The compact boxes
   (Steps ring first, then the scaffold slots) LEAD the grid, and the detailed
   #win-slot-streak card SINKS to the BOTTOM full-width (`order: 99` on the STABLE
   id + `grid-column: 1 / -1`). `grid-auto-flow: dense` lets the 6 single-col
   compact cards pack cleanly (no orphaned cells) above the full-width Streak row:
   at 2-col → 3 rows of 2 then Streak full-width last; at 3-col → 2 rows of 3 then
   Streak full-width last; at 1-col → stacked with Streak last. Keyed off the
   stable id so it survives the win.js .card--empty → .win-streak hydration swap. */

.win-page {
  background: var(--bg-primary);
  color: var(--text-primary);
  margin: 0;
  min-height: 100vh;
}

/* Single-column, centered, phone-first content column (~480px max). */
.win-shell {
  max-width: 480px;
  margin: 0 auto;
  padding: var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--grid-gap);
}

/* Tablet+ : widen the centered shell so the .win-slots grid (2–3 columns) has
   room to breathe. 960px is a layout-container max-width (an accepted px
   exception — a content-container width cannot be tokenized, and the repo has
   no breakpoint/container tokens; matches the dashboard's wide-content feel). */
@media (min-width: 600px) {
  .win-shell {
    max-width: 960px;
  }
}

/* ── Hero — the single focal point ─────────────────────────────────────── */
/* jzh #5 (secondary): trim the top-heavy hero. padding-top --space-lg →
   --space-sm and a tighter inter-element gap so the hero is less top-heavy and
   the grid rises into view sooner. Tokens only. */
.win-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-xs);
  padding-top: var(--space-sm);
}

/* Avatar-initial circle — mirrors .user-avatar / competition initials.
   Accent fill (--color-info), white text, full-radius circle. */
.win-hero__avatar {
  width: 64px;
  height: 64px;
  border-radius: var(--radius-full);
  background: var(--color-info);
  color: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-heading);
  font-weight: 600;
  overflow: hidden;   /* clip the profile <img> to the circle (260527-7we) */
}

/* Profile photo fills the avatar circle when present (260527-7we). */
.win-hero__avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius-full);
  display: block;
}

.win-hero__brand {
  margin: 0;
  font-size: var(--text-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
}

.win-hero__greeting {
  margin: 0;
  font-size: var(--text-heading);
  font-weight: 600;
  line-height: var(--leading-tight);
  color: var(--text-heading-color);
}

/* Tappable page-level asOf affordance (D-15). Muted, no accent fill;
   44px min touch target; accent focus outline (global rule reinforced here).
   jzh #5: de-emphasized to --text-label (smaller, more subordinate) — the
   "Updated X ago" line should never compete with the hero. No color brightening
   (stays --text-muted); the 44px touch target is preserved. */
.win-asof {
  appearance: none;
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--text-muted);
  font-size: var(--text-label);
  font-weight: 400;
  font-family: inherit;
  line-height: var(--leading-normal);
  min-height: 44px;
  min-width: 44px;
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);
}

.win-asof:focus-visible {
  outline: 2px solid var(--color-info);
  outline-offset: 2px;
}

/* Quick task 260527-7we: the two-signal freshness block — "Synced {relative}"
   (last pull) above "Data through {day}" (last data). Both subordinate/muted so
   they never compete with the hero; centered, tight stack. Tokens-only. */
.win-freshness {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
}

.win-synced {
  margin: 0;
  font-size: var(--text-label);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

/* ── Pip row (260529-gmm) ──────────────────────────────────────────────────
   7-pillar mobile pip row between the hero and the slots grid (Steps ·
   Workout · Tracking · Calories · Protein · Macros · Weight). Three dot
   states (filled / outline / em-dash) toggled by win.js renderPipRow.
   24px visible dot inside a ≥44px transparent tap area (D-gmm-02 / WCAG 2.5.5).
   Tokens only — no hardcoded hex, no --color-danger, no --color-warning.
   Originally 5 daily-pillar pips (gmm); 6th 'Weight' added by bla; 7th
   'Protein' added by 260601-g77 to mirror the full 7-rung success ladder. */
.win-pips {
  display: flex;
  justify-content: center;
  gap: var(--space-xs);
  padding: var(--space-xs) 0;
}

.win-pips__pip {
  /* Button reset — transparent background, no border. Padding contributes to
     the ≥44px hit-box (D-gmm-02). */
  appearance: none;
  background: transparent;
  border: 0;
  padding: var(--space-xs);
  min-width: 44px;
  min-height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  cursor: pointer;
  color: var(--text-muted);
  font-family: inherit;
}

.win-pips__pip:focus-visible {
  outline: 2px solid var(--color-info);
  outline-offset: 2px;
  border-radius: var(--radius-md);
}

.win-pips__dot {
  width: 24px;
  height: 24px;
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-caption);
  line-height: 1;
}

.win-pips__dot--filled  { background: var(--color-success); }
.win-pips__dot--outline { background: transparent; border: 2px solid var(--text-muted); }
.win-pips__dot--emdash  { background: transparent; color: var(--text-muted); }

/* fvv Change 4 — streak number inside the filled dot. Bumps the dot from the
   gmm-default 24px to 26px to accommodate 2-3 glyph streak numbers at
   --text-label tabular-nums without truncation. Inherits the
   .win-pips__dot--filled success-color background; text color must contrast —
   --color-on-success is the canonical "text on success-bg" token when present,
   else --bg-card (a near-black on the success-green chip) is the safe default.
   Tokens only — no hardcoded hex.

   Quick 260601-g77 follow-up: the dot is now a horizontal PILL — fixed 26px
   height for row consistency, but width grows with content (min-width 26px
   + padding) so we can celebrate long streaks like '128D' and '365D' without
   clamping at '99+'. The 99+ cap was removed from win.js renderPipRow —
   step streaks especially run hundreds of days and hiding that is exactly
   the opposite of what a wins page should do. border-radius:999px keeps the
   pill ends fully rounded regardless of width. */
.win-pips__dot--streak {
  min-width: 26px;
  width: auto;
  height: 26px;
  padding: 0 6px;
  border-radius: 999px;
  font-size: var(--text-label);   /* 12px tabular-nums */
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--color-on-success, var(--bg-card));
  line-height: 1;
  white-space: nowrap;
}

.win-pips__label {
  font-size: var(--text-caption);
  color: var(--text-muted);
}

/* 260531-bla — narrow-viewport hardening (extended to 7 pips by 260601-g77).
   At 375px (iPhone SE baseline) and below, 7 labels with full words risk
   overflowing the centered flex row. We tighten the label font and zero the
   gap so the 44px tap targets butt against each other (each pip's own padding
   still contributes to its hit-box — WCAG 2.5.5 compliant). 24px dot unchanged.
   g77 raises the breakpoint to 480px since 7 pips × 44px = 308px + labels
   ("Workout"/"Tracking"/"Calories"/"Protein") need a little more breathing
   room than the 6-pip layout did. */
@media (max-width: 480px) {
  .win-pips { gap: 0; }
  .win-pips__label { font-size: 0.625rem; } /* 10px — tighter than --text-label 12px */
  .win-pips__pip   { padding: var(--space-xs) 2px; } /* tighter horizontal padding */
}

/* (260601-g77 follow-up): the earlier "11px override + letter-spacing" rule
   here is now folded into the main .win-pips__dot--streak block above — the
   pill-shape change made the font tightening unnecessary. */

/* Pulse animation on a tap-target card (D-gmm-01). 300ms outline-glow using
   --color-info; auto-removed on animationend (one-shot listener in win.js). */
@keyframes win-slot-pulse {
  0%   { outline: 2px solid var(--color-info); outline-offset: 2px; opacity: 1; }
  100% { outline: 2px solid var(--color-info); outline-offset: 2px; opacity: 0; }
}

.win-slot--pulse {
  animation: win-slot-pulse 300ms ease-out forwards;
}

@media (prefers-reduced-motion: reduce) {
  .win-slot--pulse { animation: none; }
}

/* ── Sticky share CTA bar (260529-gmm) ─────────────────────────────────────
   Always-visible bottom CTA bar on /win (D-gmm-04 — no scroll-direction logic).
   z-index 50 sits BELOW the Phase 25 share modal (z-index 1000+), so opening
   the modal naturally overlays it. safe-area-inset-bottom respected so the
   button never sits under the home indicator on iOS. .win-shell gets a
   bottom padding equal to the bar height + safe area so the last slot is
   never hidden behind the bar at full scroll. Tokens only. */
:root {
  --win-share-cta-h: 64px;   /* button visual height target — read by .win-shell padding */
}

.win-share-cta-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  padding: var(--space-sm) var(--space-md);
  padding-bottom: calc(var(--space-sm) + env(safe-area-inset-bottom));
  background: var(--bg-primary);
  border-top: 1px solid var(--text-muted);
  z-index: 50;  /* modal sits at 1000+ (Phase 25), so this stays beneath */
}

.win-share-cta-bar__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: var(--win-share-cta-h);
  border: 0;
  border-radius: var(--radius-md);
  background: var(--color-info);
  color: #ffffff;
  font-size: var(--text-body);
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}

.win-share-cta-bar__btn:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

.win-share-cta-bar__btn--disabled {
  opacity: 0.5;
  cursor: default;
}

/* Page padding so the sticky bar never covers the last slot at full scroll.
   Override the base .win-shell padding-bottom — keeps the rest unchanged. */
.win-shell {
  padding-bottom: calc(var(--win-share-cta-h) + var(--space-md) + env(safe-area-inset-bottom));
}

/* 260603: on MOBILE the redesign places the swipe carousel low on the page, and
   a position:fixed CTA bar overlays it — horizontal swipes hit the button. Below
   600px, un-pin the CTA so "Share your win" flows to the natural page bottom (it's
   the last DOM node, after .win-slots), leaving the carousel unobstructed. Desktop
   (≥600px) keeps the sticky bar unchanged. */
@media (max-width: 599px) {
  .win-share-cta-bar {
    position: static;
    border-top: 0;
    margin-top: var(--space-lg);
    padding-bottom: calc(var(--space-md) + env(safe-area-inset-bottom));
  }
  .win-shell {
    padding-bottom: var(--space-md);
    /* Phone: use the FULL viewport width, not the 480px desktop column — the cap
       was leaving dead space on the right and shifting content off-center. */
    max-width: 100%;
    /* Tighten the vertical rhythm on phones — the 24px desktop section gap left
       the page feeling loose between hero / pips / fresh-week / carousel.
       (quick-260603) */
    gap: var(--space-md);
  }
  /* The "Fresh week — pick one win." strip carried its own 16px top+bottom
     margins, which STACKED on top of the shell gap → ~40px dead bands above and
     below it. Drop the margins on mobile and let the (now 16px) shell gap own the
     spacing. Parent-scoped (.win-shell ...) to out-specify the base rule, which
     sits LATER in the file at equal class specificity. (quick-260603) */
  .win-shell .win-fresh-start { margin-top: 0; margin-bottom: 0; }
  /* "Data through {day}" is a <button> pinned to a 44px tap target — on a 12px
     line that left ~18px of empty space around it. Let it size to content on
     phones; height ≈ 26px still clears the 24px WCAG 2.5.8 AA tap height (min-width
     stays 44px). (quick-260603) */
  .win-asof { min-height: 0; }
  /* Clip any stray horizontal overflow that pushed card right-edges against the
     viewport side; keeps the column symmetric within .win-shell's even padding.
     (Safety net — the real fix below stops the overflow at its source.) */
  .win-page { overflow-x: hidden; }
  .win-mobile,
  .win-mobile__carousel-wrap,
  .win-mobile__carousel { min-width: 0; max-width: 100%; }
  /* ROOT-CAUSE FIX (quick-260603) — the carousel IS the scroll container, and it
     was the one flex item the prior pass missed. iOS Safari sizes an
     overflow:auto flex item to its scrollWidth (all 5 cards) instead of
     stretching to the column parent, which blows .win-shell wider than the
     viewport and shifts the whole column off-centre/flush-right. A DEFINITE
     width:100% pins the carousel to its parent so the cards overflow INTERNALLY
     (scroll) rather than widening the column. Chrome was already correct via the
     automatic overflow min-size; this makes WebKit behave identically. */
  .win-mobile__carousel { width: 100%; }
}

/* ── Scaffold slot grid ────────────────────────────────────────────────────
   Responsive CSS Grid wrapping the existing seven .win-slot cards (slot ids and
   win-shell.js markup are UNCHANGED — the grid lives on the CONTAINER only).
   Phone-first: a single column with clear card separation via --grid-gap.
   Breakpoints: tablet @600px → 2 columns, desktop @900px → 3 columns. The
   raw px in the @media queries are an accepted exception (a media-query width
   cannot be tokenized in CSS; matches dashboard.css's @media px convention). */
.win-slots {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--grid-gap);
  /* jzh #2: dense packing lets later single-col cards backfill the hole that the
     full-row featured card would otherwise leave beside it (no orphaned cells at
     2-col / 3-col). Harmless at 1-col. */
  grid-auto-flow: dense;
}

/* Detailed Streak card SINKS to the BOTTOM at every breakpoint (lqq LQQ-01 —
   compact boxes lead the glance) — keyed off the STABLE #win-slot-streak id so it
   survives the win.js .card--empty → .win-streak hydration swap, and so the card
   sits last regardless of its SECOND DOM position. */
#win-slot-streak {
  order: 99;
}

/* Phase 26 — matrix spans the full row at tablet+ (it's wide).
   The new #win-slot-matrix replaces the four old single-card slots
   (steps / weight-journey / macros / workouts) with one wide pillar × day
   surface; full-row placement keeps the 8 day columns readable at every
   breakpoint ≥ 600px. Desktop ≥ 900px inherits the same rule.
   #win-slot-rank's existing grid-column: 1 / grid-row: 2 (fvv Change 3)
   is UNCHANGED. */
@media (min-width: 600px) {
  #win-slot-matrix {
    grid-column: 1 / -1;
  }
}

/* Tablet (~600–900px): 2-column grid. */
@media (min-width: 600px) {
  .win-slots {
    grid-template-columns: repeat(2, 1fr);
  }

  /* Detailed Streak card — spans the full row and sits LAST (order: 99 above).
     Targeted by the STABLE #win-slot-streak id (NOT .card--empty), because
     win.js's renderStreakCard swaps .card--empty → .win-streak on hydrate; the id
     is stable across both the pending and hydrated states, so this full-width
     bottom placement survives hydration. */
  #win-slot-streak {
    grid-column: 1 / -1;
  }
}

/* Desktop (>~900px): 2–3 column grid. */
@media (min-width: 900px) {
  .win-slots {
    grid-template-columns: repeat(3, 1fr);
  }

  /* Detailed Streak card stays full-row at the bottom at desktop too (stable id,
     survives the win.js .card--empty → .win-streak swap). */
  #win-slot-streak {
    grid-column: 1 / -1;
  }

  /* fvv Change 3 — pin Rank under Steps in the same column at 3-col desktop.
     DOM order is Steps → Streak → Rank → Weight journey → Macros → Badges;
     grid-auto-flow: dense + #win-slot-streak's order:99 + this explicit
     placement land the columns as:
       col1 row1 = Steps             col2 row1 = Weight journey   col3 row1 = Macros
       col1 row2 = Rank              col2 row2 = Badges           col3 row2 = (empty)
       full-row last                = Streak
     dense backfills around the explicit Rank cell so Weight/Macros pack to the
     top row without orphaned holes. Single-column placement (NOT grid-column:
     1 / -1) — Rank is one card wide, never full-width. Steps stays in its
     natural top-left position (DOM-first child). */
  #win-slot-rank {
    grid-column: 1;
    grid-row: 2;
  }
}

/* WIN-SCOPED CARD SURFACE (jzh #1 — root cause: /win never loads components.css,
   so .card has no surface here; see the header comment). This shared selector
   re-establishes the elevated card look on ALL three card shapes — the pending
   scaffold slot (.win-slot), the hydrated Streak card (.win-streak), and the
   hydrated Steps card (.win-steps) — so every card reads as a DISTINCT elevated
   surface on --bg-primary, not floating text. #win-slot-streak is covered by
   .win-slot (pending) and .win-streak (hydrated), so it always has a surface.
   Tokens ONLY (no hardcoded hex/px). */
/* Phase 24 (D-17): extend the shared card-surface selector to include
   .win-badges so the new card gets the same surface tokens as Streak / Steps /
   Rank / Weight / Macros. ONE source of truth for "what a /win card looks like";
   the surfaceless-cards bug (jzh #1) cannot recur. */
/* Phase 26 (D-?): extend the shared card-surface selector to include
   .win-matrix so the new 7-day pillar × day matrix gets the same surface
   tokens as Streak / Steps / Rank / Weight / Macros / Badges. ONE source of
   truth for "what a /win card looks like"; the surfaceless-cards bug (jzh #1)
   cannot recur. */
.win-slot,
.win-streak,
.win-steps,
.win-rank,
.win-weight,
.win-macros,
.win-badges,
.win-matrix {
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  box-shadow: var(--shadow-card);
  border-radius: var(--radius-md);
  padding: var(--card-padding);
}

/* The featured Streak card gets subtly stronger elevation — a larger radius — so
   it reads as the hero card of the grid. Tokens only (optional polish, jzh). */
.win-streak {
  border-radius: var(--radius-lg);
}

/* Slots reuse the .card--empty pending semantics conceptually; win-slot only adds
   the muted "Coming soon" body treatment — NO red, NO colored border (D-14). The
   surface above now gives the pending slots a real elevated card look. */
.win-slot {
  text-align: left;
}

.win-slot__body {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

/* ── Streak card (Phase 19, 19-UI-SPEC.md) ─────────────────────────────────
   Tokens ONLY — no hardcoded hex/px except the locked 44px touch target. The
   no-red HARD RULE (SC#3/SC#5): the destructive/red token appears NOWHERE in
   this block — a broken/pending/at-risk streak is never an error. */

/* The live streak card replaces .card--empty on hydrate (win.js). */
.win-streak {
  display: flex;
  flex-direction: column;
  gap: var(--card-stack-gap);
  text-align: left;
}

/* Headline daily-score roll-up — the card's single largest number + anchor. */
.win-streak__headline {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.win-streak__score {
  font-size: var(--text-kpi);              /* 48px — largest number on the card */
  font-weight: 600;
  line-height: var(--leading-tight);
  color: var(--text-heading-color);
  font-variant-numeric: tabular-nums;
}

.win-streak__score-label {
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

/* Five per-streak rows, dense stack — must fit a phone fold without scroll. */
.win-streak__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.win-streak__row {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.win-streak__rowhead {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.win-streak__label {
  font-size: var(--text-label);            /* 12px / 600 uppercase */
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
}

.win-streak__glyph {
  font-size: var(--text-body);
  line-height: 1;
}

/* Success-green is allowed ONLY on the alive-secured-today indicator glyph. */
.win-streak__glyph--alive {
  color: var(--color-success);
}

.win-streak__value {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs) var(--space-sm);
}

.win-streak__count {
  font-size: var(--text-kpi-sm);           /* 32px — subordinate to the headline */
  font-weight: 600;
  line-height: var(--leading-tight);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

/* Broken / new / pending counts are de-emphasized muted — never red (D-21/D-18). */
.win-streak__count--muted {
  color: var(--text-muted);
}

.win-streak__count-unit {
  font-size: var(--text-subtitle);
  font-weight: 400;
  color: var(--text-muted);
}

.win-streak__caption {
  font-size: var(--text-subtitle);         /* "Best: N days" — secondary, muted */
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
  width: 100%;
}

/* Gentle forward nudge for an alive-but-not-secured-today streak (D-20). No red. */
.win-streak__nudge {
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-secondary);
  width: 100%;
}

/* Per-pillar streak-rank line (quick 260611-8cq F3) — "Steps · #3 of 63".
 * Small, muted, token-only, no-red — a quiet "where you stand on /streaks". */
.win-streak__rank {
  font-size: var(--text-subtitle);
  font-weight: 600;
  line-height: var(--leading-normal);
  color: var(--text-secondary);
  width: 100%;
}

/* Perfect-day streak — de-emphasized, NEVER a KPI size (Pitfall 5). */
.win-streak__perfect {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-secondary);
}

/* Break framing (D-21): the SINGLE warm restart invitation. Lead positive. */
.win-streak__restart {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.win-streak__record {
  margin: 0;
  font-size: var(--text-body);
  font-weight: 600;                        /* heading role via weight, not a new size */
  line-height: var(--leading-normal);
  color: var(--text-primary);
}

/* The card's single accent affordance — primary action (--color-info). 44px. */
.win-streak__restart-btn {
  appearance: none;
  align-self: flex-start;
  background: var(--color-info);
  color: #ffffff;
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-size: var(--text-body);
  font-weight: 600;
  line-height: var(--leading-normal);
  min-height: 44px;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-sm);
}

.win-streak__restart-btn:focus-visible {
  outline: 2px solid var(--color-info);
  outline-offset: 2px;
}

/* Freeze bank + reassuring consume callout (D-12 / SC#4). Optional info accent,
   NEVER red, NEVER a warning iconography or buy/upsell affordance. */
.win-streak__freeze {
  border-radius: var(--radius-sm);
}

.win-streak__freeze-bank {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

.win-streak__freeze-callout {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-primary);
  background: var(--bg-card-hover);
  border-left: 2px solid var(--color-info);   /* reassuring info accent, NOT red */
  padding: var(--space-sm);
  border-radius: var(--radius-sm);
}

/* ── Steps momentum ring (Phase 20, 20-UI-SPEC.md, MOTIV-01) ────────────────
   Tokens ONLY — the SOLE px exception is the ring diameter/stroke, which the
   existing token scale cannot express (locked as single-source custom props at
   the top of .win-steps so they are AI-tunable, treated like win.css's existing
   media-query + 960px container px exceptions).
   no-red HARD RULE (SC#3/SC#5 carried to Phase 20): --color-danger and
   --color-warning appear NOWHERE in this block. Below-goal steps, the stale
   "as of {day}" ring, and the behind/stalled nudge are NOT errors — never red,
   never amber. The ring goes info-blue (in-progress) → success-green (goal hit),
   never amber on the way. */

/* The live steps card replaces .card--empty on hydrate (win.js). Centered column
   so the ring sits in the middle of the card (vs the streak card's left align). */
.win-steps {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--card-stack-gap);
  text-align: center;
  /* Documented px exception: ring dimensions cannot be expressed in the existing
     token scale. Single-source here so they are AI-tunable. */
  --ring-size: 140px;
  --ring-stroke: 16px;
  /* Ring fill (in-progress = info-blue) + track colors; --ring-fill % is injected
     by win.js via style.setProperty (numeric only, NEVER innerHTML). */
  --ring-fill-color: var(--color-info);
  --ring-track-color: var(--gauge-track);
}

/* CSS conic-gradient ring (no SVG, no chart lib — phone-first / in-app-browser
   safe per PUB-07). Fill sweeps clockwise from 12 o'clock (from -90deg). The
   filled arc runs to --ring-fill (default 0%); the remainder is the track. An
   inner --bg-card disc (::after) masks the center into a ring. */
.win-steps__ring {
  position: relative;
  width: var(--ring-size);
  height: var(--ring-size);
  border-radius: var(--radius-full);
  background: conic-gradient(
    from -90deg,
    var(--ring-fill-color) var(--ring-fill, 0%),
    var(--ring-track-color) 0%
  );
  display: flex;
  align-items: center;
  justify-content: center;
}

.win-steps__ring::after {
  content: '';
  position: absolute;
  width: calc(var(--ring-size) - var(--ring-stroke) * 2);
  height: calc(var(--ring-size) - var(--ring-stroke) * 2);
  border-radius: var(--radius-full);
  background: var(--bg-card);
}

/* Centered value stack sits above the inner disc. */
.win-steps__ring-value {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
}

/* Goal hit → full success-green ring (the single celebratory positive in-card). */
.win-steps--goal-hit {
  --ring-fill-color: var(--color-success);
}

/* Stale ("as of {day}") → muted fill, de-emphasized — NOT info-blue, NOT red. */
.win-steps--stale {
  --ring-fill-color: var(--text-muted);
}

/* Today's count — 26px DOCUMENTED PX EXCEPTION (quick task 260531-fvv, Change 1).
   The ring inner disc is 108px (--ring-size 140 - --ring-stroke 16 × 2);
   var(--text-kpi-sm) at 32px tabular-nums measures ~115-120px for a 5-6 char
   count like "11,498" / "101,234" and CLIPS the disc. 26px tabular-nums fits 6
   glyphs with ≥4px horizontal margin on both sides. Matches the documented px
   exception pattern already used by --ring-size / --ring-stroke on this same
   ring (a media-query-style numeric tied to the disc geometry, not theme size).
   Subordinate-to-streak-headline number-hierarchy rule still holds (26 < 48). */
.win-steps__count {
  font-size: 26px;
  font-weight: 600;
  line-height: var(--leading-tight);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

/* Stale/pending number is muted — never red, never a fabricated 0. */
.win-steps__count--muted {
  color: var(--text-muted);
}

/* The "/ {goal}" denominator caption inside the ring — secondary, muted. */
.win-steps__goal-caption {
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* Celebration glyph in the ring center on goal-hit (decorative, aria-hidden). */
.win-steps__glyph {
  font-size: var(--text-kpi-sm);
  line-height: 1;
}

/* "X steps to go" framing line + the stale "as of {day}" label — body, muted. */
.win-steps__togo,
.win-steps__asof {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* Goal-hit headline ("Goal hit!") — heading role via 600 weight (NOT a 5th size),
   success-green. A warm sub-line follows at the default muted body weight. */
.win-steps__togo--hit {
  font-weight: 600;
  color: var(--color-success);
}

.win-steps__subline {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-secondary);
}

/* FJJ-01 — the two step-streak lines inside the Steps ring card. Token-only (no
   new hex), mirroring the .win-steps__togo / .win-steps__subline spacing. */
.win-steps__streaks {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin-top: var(--space-sm);
}

/* GOAL streak line — strict win. Subtitle size, secondary color, tabular-nums for
   the count. NEVER red (no-red HARD RULE): the 0/empty "Start a step-goal streak"
   invitation uses the same supportive secondary token, never an error/red token. */
.win-steps__streak-line {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

/* DAYS-LOGGED line — the forgiving win, de-emphasized below the goal line. */
.win-steps__streak-line--logged {
  color: var(--text-muted);
}

/* Celebration animation — ONLY when the user has not requested reduced motion.
   The static full-green-ring + glyph + headline end state is correct on its own;
   the sweep is pure enhancement (no animation library; CSS keyframes only). */
@media (prefers-reduced-motion: no-preference) {
  @keyframes win-steps-pop {
    0% { transform: scale(0.8); }
    60% { transform: scale(1.12); }
    100% { transform: scale(1); }
  }
  .win-steps--goal-hit .win-steps__glyph {
    animation: win-steps-pop 420ms ease-out;
  }
}

/* ── Hero win line (lqq LQQ-02) ──────────────────────────────────────────────
   The single strongest achievement, led in the hero below the greeting. A warm,
   prominent line — but NOT a KPI size and NOT competing with the greeting.
   win.js fills the glyph + copy via textContent + un-hides when leadWin is
   non-null; the no-JS first-paint fallback shows nothing (it stays [hidden]).
   no-red HARD RULE: a win is POSITIVE — the accent is --color-success ONLY;
   --color-danger / --color-warning appear NOWHERE here. Mirrors the .win-nudge
   flex/center idiom but WITHOUT the chip background/border, so the win line reads
   as plain accent text — lighter than the nudge chip, distinct from it. */
.win-leadwin {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  font-size: var(--text-body);
  font-weight: 600;   /* heading role via weight, not a new font size */
  line-height: var(--leading-normal);
  color: var(--color-success);
}

.win-leadwin__glyph {
  line-height: 1;
}

.win-leadwin__copy {
}

/* ── Personalized nudge (Phase 20, 20-UI-SPEC.md, MOTIV-04, D-04) ────────────
   Lives in .win-hero (NOT a card), centered, below the greeting + above the
   asOf. Copy-led, never a colored status banner.
   no-red HARD RULE (SC#3/SC#5): --color-danger and --color-warning appear
   NOWHERE — behind/stalled are supportive states, not errors. The struggling
   states read at the SAME visual weight as the positive ones; only the words
   differ. The optional left-border is the reassuring info accent, never red. */
.win-nudge {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  max-width: 28rem;
  font-size: var(--text-body);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-primary);
  /* Optional subtle chip surface — mirrors .win-streak__freeze-callout. Reassuring
     info accent on the left border (never red/amber); same chip for every state. */
  background: var(--bg-card-hover);
  border-left: 2px solid var(--color-info);
  border-radius: var(--radius-sm);
  padding: var(--space-sm);
}

.win-nudge__glyph {
  font-size: var(--text-body);
  line-height: 1;
  /* aria-hidden in HTML — decorative, not the accessible label. */
}

.win-nudge__copy {
  /* Inherits typography from .win-nudge. The lead line may take 600 weight. */
}

.win-nudge__copy--lead {
  font-weight: 600;   /* heading role via weight, not a new font size (UI-SPEC) */
}

/* ── Rank card (Phase 21 + quick task 260531-fvv Change 2) ───────────────────
   Tokens ONLY — no hardcoded hex/px (no locked exception is needed here; the
   card carries no ring/avatar/touch-target). no-RED HARD RULE (SC#1/SC#4): a low
   weekly standing / a 'down' trend is a SUPPORTIVE state, never an error — the
   destructive/warning tokens (--color-danger / --color-warning) appear NOWHERE in
   this block. The success/info accent is the only color used.

   fvv Change 2 — the slim Rank card. The all-time HERO block (.win-rank-hero
   [deleted] + its child rules: -glyph, -line, -sub) was DELETED — the renderer
   stopped emitting that element, so the CSS rules became orphans. The card is
   now leaderboard-only: a card__title + the weekly block (lead + trend +
   neighbor list).

   fvv Change 3 — at 3-col desktop (≥900px) Rank is pinned under Steps in
   column 1, row 2 (see #win-slot-rank in the @media (min-width: 900px)
   block above). Mobile (1-col) and tablet (2-col) flow naturally. */
.win-rank {
  display: flex;
  flex-direction: column;
  gap: var(--card-stack-gap);
  text-align: left;
}

/* 260601-lbl: small section label that sits ABOVE each rank block (This week
   PRIMARY, All-time SECONDARY). Uppercase + tracked, --text-label 12px, muted
   color, tight bottom-margin so the label visually pairs with the block below
   without adding the full --card-stack-gap distance. */
.win-rank__label {
  font-size: var(--text-label);
  font-weight: 600;
  line-height: var(--leading-tight);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: calc(var(--space-xs) * -1); /* pull next block up by 4px to pair tightly */
}

/* The weekly competition standing — the SOLE rendered block post-fvv-Change-2. */
.win-rank__weekly {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

/* The weekly lead line + the inline trend run sit on one baseline-aligned row. */
.win-rank__weekly-lead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs) var(--space-sm);
}

.win-rank__weekly-text {
  font-size: var(--text-subtitle);
  font-weight: 600;   /* heading role via weight — anchors the secondary line */
  line-height: var(--leading-normal);
  color: var(--text-secondary);
}

/* 260601-cap: explanatory caption + score line under each rank block. Score
   line stays one notch larger than caption so the number is the focal point;
   caption is the smallest readable size (--text-label) in --text-muted so it
   reads as supporting copy, not competing with the lead text above. */
.win-rank__score {
  font-size: var(--text-subtitle);   /* 14px tabular */
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  line-height: var(--leading-normal);
  color: var(--text-secondary);
}

.win-rank__caption {
  font-size: var(--text-label);      /* 12px */
  font-weight: 400;
  line-height: var(--leading-tight);
  color: var(--text-muted);
  max-width: 100%;
}

/* 260606-mwy: deep-link to the full weekly contest, focused on this client's
   row. Self-explaining text link; accent via the existing info token (never a
   sole-color signal — the text says what it does). Token-only, no hex. */
.win-rank__contest-link {
  display: inline-block;
  margin-top: var(--space-xs);
  font-size: var(--text-label);      /* 12px */
  font-weight: 500;
  line-height: var(--leading-normal);
  color: var(--color-info);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.win-rank__contest-link:hover,
.win-rank__contest-link:focus-visible {
  color: var(--text-primary);
}

/* Trend run — GLYPH + TEXT, never color alone (SC#1). The success-info accent is
   applied to the WHOLE run (glyph + text together) so the color is never the sole
   signal — the text always states the direction. NEVER red, even on 'down'. */
.win-rank__trend {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-xs);
  font-size: var(--text-label);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

.win-rank__trend-glyph {
  font-size: var(--text-label);
  line-height: 1;
}

.win-rank__trend-text {
  font-size: var(--text-label);
}

/* Neighbor list — a tiny clamped window of peers (server-clamped; never the floor).
   Plain stacked rows, muted, no bullets. */
.win-rank__neighbors {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.win-rank__neighbor {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

/* The client's OWN row — distinct via the info accent + weight (never red). */
.win-rank__neighbor--self {
  color: var(--text-primary);
  font-weight: 600;
}

.win-rank__neighbor-pos {
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}

.win-rank__neighbor-name {
  /* inherits the row typography; client-derived (textContent only in win.js). */
}

/* A small "you" tag on the self row — reassuring info accent chip, never red. */
.win-rank__neighbor-you {
  font-size: var(--text-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-info);
}

/* 260603: condense the RANK card on MOBILE — it stacks two full rank blocks
   (This Week + All-Time), each with a wordy explainer caption + a leaderboard,
   so it reads as a tall wall on a phone. Drop the captions, pull each block
   tight, and shrink the leaderboard rows to compact 12px list type. Pure CSS,
   no content/JS change; ≥600px desktop rank card is unchanged. */
@media (max-width: 599px) {
  .win-rank__caption { display: none; }
  .win-rank__weekly { gap: 2px; }
  .win-rank__neighbors { gap: 2px; margin-top: var(--space-xs); }
  .win-rank__neighbor {
    font-size: var(--text-label);
    line-height: var(--leading-tight);
  }
}

/* ── Weight Journey + Workout Consistency card (Phase 22, 22-UI-SPEC.md) ──────
   Tokens ONLY — no hardcoded hex anywhere; the only px values are the locked-
   exception custom properties at the top of .win-weight (UI-SPEC §3.2 — they
   cannot be expressed in the existing token scale). Locked exceptions:
     --win-workouts-ring-size:    88px      (conic-gradient ring diameter)
     --win-workouts-ring-stroke:  12px      (conic-gradient ring stroke band)
     --win-workouts-cell-gap:     3px       (12 × 7 grid cell gap)
     --win-workouts-cell-radius:  2px       (heatmap cell corner radius)
     --win-weight-bar-height:     14px      (progress bar visible height)
     --win-weight-bar-radius:     7px       (progress bar pill corners)
   The 44px touch-target minimum is already a project-wide locked exception.

   NO-RED / NO-AMBER HARD RULE (UI-SPEC §3.1 + §10.2 + threat register T-22-06):
     --color-danger and --color-warning appear NOWHERE in this block. Plateau /
     wrong-direction / lapsed / no-goal / insufficient-history each render with
     muted/info accents — never red, never amber, never an alert chrome. The CSS
     verify gate enforces this. */

.win-weight {
  /* Locked-exception custom properties (UI-SPEC §3.2). Single-source so the
     workouts ring + heatmap + progress bar all read from ONE place. */
  --win-workouts-ring-size: 88px;
  --win-workouts-ring-stroke: 12px;
  --win-workouts-cell-gap: 3px;
  --win-workouts-cell-radius: 2px;
  --win-weight-bar-height: 14px;
  --win-weight-bar-radius: 7px;

  display: flex;
  flex-direction: column;
  gap: var(--card-stack-gap);
  text-align: left;
}

/* ── Weight half — hero + bar + projection + state line ─────────────────── */

.win-weight__hero {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.win-weight__current {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
}

.win-weight__current-num {
  font-size: var(--text-kpi-sm);
  font-weight: 600;
  line-height: var(--leading-tight);
  color: var(--text-heading-color);
  font-variant-numeric: tabular-nums;
}

.win-weight__current-unit {
  font-size: var(--text-subtitle);
  font-weight: 400;
  color: var(--text-secondary);
}

.win-weight__lbs-to-go {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-secondary);
}

/* Trend run — GLYPH + TEXT, never color alone (SC#1 / UI-SPEC §8). The info
   accent applies to the WHOLE run (glyph + text) so the color is decorative;
   the text always states the direction. NEVER red, even on a wrong direction. */
.win-weight__trend {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-xs);
  font-size: var(--text-label);
  font-weight: 500;
  line-height: var(--leading-normal);
  color: var(--text-secondary);
}

.win-weight__trend-glyph {
  font-size: var(--text-label);
  line-height: 1;
}

.win-weight__trend-text {
  font-size: var(--text-label);
}

/* Progress bar — gradient fill from --color-info to --color-success; width via
   the --win-weight-bar-fill custom property the renderer sets via style.setProperty.
   NEVER --color-danger / --color-warning. */
.win-weight__bar {
  background: var(--bg-elevated);
  border-radius: var(--win-weight-bar-radius);
  height: var(--win-weight-bar-height);
  overflow: hidden;
  position: relative;
}

.win-weight__bar-fill {
  height: 100%;
  width: var(--win-weight-bar-fill, 0%);
  background: linear-gradient(90deg, var(--color-info), var(--color-success));
  border-radius: var(--win-weight-bar-radius);
}

/* Projected-date toggle button — 44px min hit-box (locked exception). Tap
   toggles absolute / relative form; focus-visible outline uses the info accent. */
.win-weight__projection {
  appearance: none;
  background: transparent;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font-size: var(--text-subtitle);
  font-weight: 500;
  min-height: 44px;
  min-width: 44px;
  padding: var(--space-xs) var(--space-sm);
  text-align: left;
  cursor: pointer;
}

.win-weight__projection:focus-visible {
  outline: 2px solid var(--color-info);
  outline-offset: 2px;
}

/* State line — muted across every branch except goal-hit (success-green). The
   plateau / wrong-direction / lapsed / no-goal / insufficient-history branches
   all read as the same muted muted info-tone so no branch reads as red/alert. */
.win-weight__state {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-secondary);
}

.win-weight__state--goal-hit {
  color: var(--color-success);
  font-weight: 600;
}

.win-weight__state-glyph {
  margin-right: var(--space-xs);
}

/* ── Workouts half ──────────────────────────────────────────────────────── */

.win-weight__workouts {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  border-top: 1px solid var(--border-color);
  padding-top: var(--space-md);
}

.win-weight__workouts-empty,
.win-weight__weight-empty,
.win-weight__heatmap-pending {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

/* Conic-gradient ring — mirrors .win-steps__ring's --ring-fill pattern. The
   --color-info / --color-success accents are the only colors used. */
.win-weight__ring {
  width: var(--win-workouts-ring-size);
  height: var(--win-workouts-ring-size);
  border-radius: var(--radius-full);
  background:
    conic-gradient(var(--color-info) 0 var(--ring-fill, 0%), var(--gauge-track) var(--ring-fill, 0%) 100%);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.win-weight__ring--complete {
  background:
    conic-gradient(var(--color-success) 0 100%, var(--color-success) 100% 100%);
}

.win-weight__ring--rest {
  background:
    conic-gradient(var(--gauge-track) 0 100%, var(--gauge-track) 100% 100%);
}

/* Inner mask — punches the ring center to expose the card background so the
   conic gradient reads as a stroke ring (not a solid pie). */
.win-weight__ring::before {
  content: '';
  position: absolute;
  top: var(--win-workouts-ring-stroke);
  right: var(--win-workouts-ring-stroke);
  bottom: var(--win-workouts-ring-stroke);
  left: var(--win-workouts-ring-stroke);
  border-radius: var(--radius-full);
  background: var(--bg-card);
}

.win-weight__ring-value {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
}

.win-weight__ring-glyph {
  font-size: var(--text-label);
  line-height: 1;
  color: var(--color-success);
}

.win-weight__ring-num {
  font-size: var(--text-subtitle);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.win-weight__ring-caption {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-secondary);
}

/* ── 84-cell heatmap (7 rows × 12 cols, oldest first) ───────────────────── */

.win-weight__heatmap-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.win-weight__heatmap-placeholder {
  /* Maintains the heatmap's vertical rhythm during the IntersectionObserver
     deferral so the card height does not jump on lazy mount. Reservation only;
     no visible content. */
  min-height: calc(var(--win-workouts-ring-size) - var(--space-md));
}

.win-weight__heatmap {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: repeat(7, 1fr);
  gap: var(--win-workouts-cell-gap);
  grid-auto-flow: column; /* fill column-major (oldest first, top-down) */
}

/* Each cell is a <button> with a transparent 44px touch target; the visible
   swatch is the inner span (sized via aspect-ratio). UI-SPEC §6.7. */
.win-weight__cell {
  appearance: none;
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 44px hit-box at the parent level — the swatch shrinks inside. */
  min-width: 0;
  min-height: 0;
}

.win-weight__cell:focus-visible {
  outline: 2px solid var(--color-info);
  outline-offset: 1px;
}

.win-weight__cell-swatch {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  border-radius: var(--win-workouts-cell-radius);
  background: var(--gauge-track);
}

.win-weight__cell--tracked .win-weight__cell-swatch {
  background: var(--color-success);
}

.win-weight__cell--scheduled .win-weight__cell-swatch {
  background: var(--color-info);
}

.win-weight__cell--rest .win-weight__cell-swatch {
  background: var(--gauge-track);
  opacity: 0.5;
}

.win-weight__cell--no-data .win-weight__cell-swatch {
  background: var(--gauge-track);
  opacity: 0.25;
}

.win-weight__cell-label {
  margin: 0;
  min-height: calc(var(--text-label) * var(--leading-normal));
  font-size: var(--text-label);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-secondary);
}

.win-weight__legend {
  margin: 0;
  font-size: var(--text-label);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

/* Motion gating — animate progress bar + ring fill only when the user has not
   asked for reduced motion (UI-SPEC §6.6). One-shot 320ms ease-out; we never
   loop these animations. Tokens-only — no hardcoded easing/timing baselines. */
@media (prefers-reduced-motion: no-preference) {
  .win-weight__bar-fill {
    transition: width 320ms ease-out;
  }
  .win-weight__ring {
    transition: background 320ms ease-out;
  }
}

/* ── Macros Adherence card (Phase 23, 23-UI-SPEC.md, MOTIV-08) ──────────────
   Tokens ONLY — no hardcoded hex/px/font literals. The no-red HARD RULE
   (UI-SPEC §HARD COLOR RULES): --color-danger and --color-warning appear
   NOWHERE in this block. Below-target macros, an over-target reading on the
   wrong side of phase, a stale card, and a never-tracked client are NOT
   errors. The bars go info-blue (under) → success-green (hit), or muted
   (over) — never amber, never red, never a "you're failing" signal.

   The Macros card is non-interactive per D-23-03 (no tap-to-expand, no row
   click handler). No animation on --bar-fill — bars render in their end
   state on first paint (UI-SPEC §A11y reduced-motion default).

   Structure (UI-SPEC §Macros Card Layout):
     .win-macros                          — outer card, vertical stack
       .card__title                       — "MACROS" (existing token)
       .win-macros__calorie-row           — headline (calories — emphasized)
         .win-macros__row-head            — label + glyph + caption
         .win-macros__row-value           — 32px headline number
         .win-macros__bar                 — thicker (--space-sm) track
           .win-macros__bar-fill          — width = var(--bar-fill, 0%)
       .win-macros__rows                  — Protein / Carbs / Fat stack
         .win-macros__macro-row × 3       — uniform compact rows
       .win-macros__asof                  — optional "as of {day}" (State D)
     OR (binary fallback — State B/E):
       .win-macros__fallback              — single centered indicator
         .win-macros__fallback-glyph      — optional ✓ / • / none
         .win-macros__fallback-lead       — 14px/600 supportive lead
         .win-macros__fallback-sub        — 14px/400 muted next step */

.win-macros {
  display: flex;
  flex-direction: column;
  gap: var(--card-stack-gap);
  text-align: left;
}

/* Calorie headline row (emphasized) + the 3-macro stack share the same vertical
   row layout, but live in different outer containers so the gap discipline can
   differ (calorie row sits flush with the title; the macro rows stack with
   --space-sm between them). */
.win-macros__calorie-row,
.win-macros__macro-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.win-macros__rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Row head — label on the left, glyph + caption on the right. */
.win-macros__row-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--text-subtitle);
  line-height: var(--leading-normal);
}

.win-macros__row-label {
  font-size: var(--text-subtitle);
  font-weight: 400;
  color: var(--text-primary);
}

.win-macros__row-caption {
  font-size: var(--text-subtitle);
  font-weight: 400;
  color: var(--text-muted);
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

.win-macros__row-glyph {
  font-size: var(--text-subtitle);
  line-height: 1;
  color: var(--text-muted);
}

/* Macro row VALUE — "{actual} / {target} g". Heading role via weight (600),
   not a fifth font size — protects the four-size constraint (UI-SPEC §Typography). */
.win-macros__row-value {
  font-size: var(--text-subtitle);
  font-weight: 600;
  line-height: var(--leading-normal);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

/* Calorie row VALUE — 32px headline (D-23-05). Matches the Steps ring's center
   value; subordinate to the 48px Streak headline. */
.win-macros__calorie-row .win-macros__row-value {
  font-size: var(--text-kpi-sm);
  font-weight: 600;
  line-height: var(--leading-tight);
  color: var(--text-heading-color);
  font-variant-numeric: tabular-nums;
}

/* Bar track — default 4px tall for macro rows. */
.win-macros__bar {
  background: var(--gauge-track);
  border-radius: var(--radius-sm);
  height: var(--space-xs);
  overflow: hidden;
  width: 100%;
}

/* Calorie row bar — slightly thicker (D-23-05) to reinforce the headline role. */
.win-macros__calorie-row .win-macros__bar {
  height: var(--space-sm);
}

/* Bar fill — width is set via the --bar-fill CSS custom property (numeric
   only, NEVER innerHTML). The default UNDER state is info-blue; modifier
   classes (.win-macros__bar--{state}) below switch the color per server status.
   Bar caps at 100% width — overage is communicated by caption + ▲ glyph,
   never by a bar that bursts the track (Open Q6 DECIDED NO). */
.win-macros__bar-fill {
  height: 100%;
  width: var(--bar-fill, 0%);
  background: var(--color-info);
  border-radius: inherit;
}

/* State color modifiers — BAR FILL color ONLY (the row caption color stays
   muted via .win-macros__row-caption above; meaning is in the TEXT, not color).
   NEVER --color-danger, NEVER --color-warning (UI-SPEC §HARD COLOR RULES). */
.win-macros__bar-fill.win-macros__bar--hit {
  background: var(--color-success);
}

.win-macros__bar-fill.win-macros__bar--over {
  background: var(--text-muted);
}

.win-macros__bar-fill.win-macros__bar--over-phase-success {
  background: var(--color-success);
}

.win-macros__bar-fill.win-macros__bar--pending-actual {
  background: var(--gauge-track);
}

/* Stale-day footer (State D) — single muted "as of {day}" line. NEVER red. */
.win-macros__asof {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

/* Binary fallback indicator (State B / State E). Centered single block per
   D-23-04 — never a mixed 2-bar + 2-text render. Left-border info accent
   mirrors the Streak freeze-callout treatment (reassuring, never red). */
.win-macros__fallback {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-sm);
  background: var(--bg-card-hover);
  border-left: 2px solid var(--color-info);
  border-radius: var(--radius-sm);
  align-items: center;
  text-align: center;
}

.win-macros__fallback-glyph {
  font-size: var(--text-subtitle);
  line-height: 1;
  color: var(--color-success);
}

.win-macros__fallback-lead {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 600;
  line-height: var(--leading-normal);
  color: var(--text-primary);
}

.win-macros__fallback-sub {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

/* ── Badges card (Phase 24, MOTIV-09 / MOTIV-12, 24-UI-SPEC.md §3/§5/§6) ─────
   Appended per D-16 (NO new CSS file; appended to win.css after the prior
   blocks). The shared card-surface treatment ALREADY includes .win-badges
   via the extended selector at the top of this file (D-17) — these rules
   layer the badge-specific layout / typography / progress + celebration.

   HARD RULES carried (D-04 + no-shame, hard-coded UI-SPEC §3.3):
   - the destructive (red) and warning (amber) tokens appear NOWHERE in this
     block. A client without recent badges is NOT in an error state; a client
     mid-journey toward a tier is NOT in a warning state.
   - Tier identity rides on the unicode GLYPH + the WORD (rendered by win.js
     via textContent) — color is SECONDARY. */

.win-badges {
  display: flex;
  flex-direction: column;
  gap: var(--card-stack-gap);
  text-align: left;
  position: relative;
}

/* Lead line (State C empty + State B/A intros). */
.win-badges__lead {
  margin: 0;
  font-size: var(--text-body);
  font-weight: 600;
  line-height: var(--leading-normal);
  color: var(--text-heading-color);
}

/* Body / sub-line under the lead. */
.win-badges__body {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

/* Earned badge row — flex with overflow-x scroll for State D (3+ badges). */
.win-badges__row {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--space-sm);
  overflow-x: auto;
  scrollbar-width: thin;
}

/* Single tile — circular 44×44 tap surface wraps the glyph (UI-SPEC §6.4). */
.win-badges__tile {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  /* Subtle hover scale; no color flips (no-color-alone rule). */
  transition: transform 120ms ease-out;
}

.win-badges__tile:hover {
  transform: scale(1.05);
}

.win-badges__tile:focus-visible {
  outline: 2px solid var(--color-info);
  outline-offset: 2px;
}

.win-badges__tile-glyph {
  font-size: var(--text-kpi-sm);
  line-height: 1;
}

.win-badges__tile--normal .win-badges__tile-glyph {
  font-size: 32px; /* keeps the glyph comfortable inside the 44×44 surface */
}

.win-badges__tile--large .win-badges__tile-glyph {
  font-size: var(--text-kpi); /* 48px — used for the hero celebration tile */
}

/* Tooltip (UI-SPEC §5.7) — inline chip under the tile, NOT a modal. Mirrors
   the .win-macros__fallback info-accent treatment (left-border, calm). */
.win-badges__tooltip {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-sm);
  background: var(--bg-card-hover);
  border-left: 2px solid var(--color-info);
  border-radius: var(--radius-sm);
  margin-top: var(--space-xs);
}

.win-badges__tooltip-name {
  margin: 0;
  font-size: var(--text-body);
  font-weight: 600;
  line-height: var(--leading-normal);
  color: var(--text-primary);
}

.win-badges__tooltip-sub {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

.win-badges__tooltip-date {
  margin: 0;
  font-size: var(--text-label);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

/* The "+ N more new" chip when the server cap collapses the unseen[] row. */
.win-badges__chip {
  margin: 0;
  font-size: var(--text-label);
  font-weight: 600;
  line-height: var(--leading-normal);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}

/* Next-tier line ("3 more workouts to Bronze") + progress bar. */
.win-badges__next {
  margin: 0;
  font-size: var(--text-subtitle);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.win-badges__progress-bar {
  position: relative;
  width: 100%;
  height: 8px;
  border-radius: var(--radius-sm);
  background: var(--gauge-track);
  overflow: hidden;
}

.win-badges__progress-fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--win-badges-progress, 0%);
  background: var(--color-info);
  border-radius: var(--radius-sm);
  transition: width 240ms ease-out;
}

/* ── Celebration keyframes (D-05) ──────────────────────────────────────────
   Tier-scaled. Reduced-motion collapses ALL to a 150ms opacity fade
   (.win-badges__tile--celebrate-fade) via the @media block below. */

@keyframes win-badges-pop-bronze {
  0%   { transform: scale(0.8); opacity: 0; }
  100% { transform: scale(1);   opacity: 1; }
}

@keyframes win-badges-pop-silver {
  0%   { transform: scale(0.8);  opacity: 0; }
  60%  { transform: scale(1.12); opacity: 1; }
  100% { transform: scale(1);    opacity: 1; }
}

@keyframes win-badges-pop-gold {
  0%   { transform: scale(0.8);  opacity: 0; }
  50%  { transform: scale(1.15); opacity: 1; }
  100% { transform: scale(1);    opacity: 1; }
}

@keyframes win-badges-fade {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}

.win-badges__tile--celebrate-bronze {
  animation: win-badges-pop-bronze 200ms ease-out;
}

.win-badges__tile--celebrate-silver {
  animation: win-badges-pop-silver 400ms ease-out;
}

.win-badges__tile--celebrate-gold {
  animation: win-badges-pop-gold 800ms ease-out;
}

.win-badges__tile--celebrate-fade {
  animation: win-badges-fade 150ms ease-out;
}

/* HARD reduced-motion rule (UI-SPEC §6.2) — collapse EVERY celebration class
   to the 150ms opacity fade. The hover scale is also suppressed. */
@media (prefers-reduced-motion: reduce) {
  .win-badges__tile:hover {
    transform: none;
  }
  .win-badges__tile--celebrate-bronze,
  .win-badges__tile--celebrate-silver,
  .win-badges__tile--celebrate-gold {
    animation: win-badges-fade 150ms ease-out forwards;
  }
  .win-badges__progress-fill {
    transition: none;
  }
}

/* ── Monday fresh-start strip (Phase 24, MOTIV-13, D-06/D-07/§5.8) ──────────
   Lives between .win-hero and .win-slots. Lightest possible visual that still
   reads as a frame: a single warm sentence + the 🌅 glyph + an info-accent
   left border. Mirrors .win-macros__fallback / .win-nudge calm chrome.
   Hidden by default via the [hidden] HTML attribute; win.js toggles. */
.win-fresh-start {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm);
  margin: var(--space-md) 0;
  background: var(--bg-card-hover);
  border-left: 2px solid var(--color-info);
  border-radius: var(--radius-sm);
}

.win-fresh-start__glyph {
  font-size: var(--text-body);
  line-height: 1;
}

.win-fresh-start__lead {
  margin: 0;
  font-size: var(--text-body);
  font-weight: 600;
  line-height: var(--leading-normal);
  color: var(--text-primary);
}

/* ============================================================================
   Quick 260602-mts — Progress-photo nudge
   ============================================================================
   Encouraging, TEXT-ONLY age cue. Mirrors .win-fresh-start's calm strip but
   with a --color-success accent (celebratory/positive). HARD NO-RED RULE:
   --color-danger and --color-warning appear NOWHERE in this block — a photo
   nudge is a motivation cue, never a scorecard warning. No image bytes/URL.
   ============================================================================ */
.win-photo-nudge {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm);
  margin: var(--space-md) 0;
  background: var(--bg-card-hover);
  border-left: 2px solid var(--color-success);
  border-radius: var(--radius-sm);
}

.win-photo-nudge__glyph {
  font-size: var(--text-body);
  line-height: 1;
}

.win-photo-nudge__lead {
  margin: 0;
  font-size: var(--text-body);
  font-weight: 600;
  line-height: var(--leading-normal);
  color: var(--text-primary);
}

/* ============================================================================
   Phase 25 (MOTIV-10) — Shareable Win Card
   ============================================================================
   Three namespaces:
     .win-share        — the in-slot card (CTA / post-share / generating)
     .win-share-sheet  — the hand-rolled overlay modal (UI-SPEC D-05 — NOT <dialog>)
     .win-share-opt    — per-row opt-in checkbox (44px touch target)
     .win-share-png    — the 1080×1350 PNG render source (off-screen, rasterized
                          by html-to-image; CSS-scaled preview shown in modal)

   Tokens-only, 60/30/10 (--bg-card surface, --color-info CTA accent).
   NO danger / warning tokens used in any of these rules — share is a
   POSITIVE action; never red (HARD RULE carried from Phase 19-21).
   ============================================================================ */

/* Slot CTA + post-share states */
.win-share {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md);
  background: var(--bg-card);
  border-radius: var(--radius-md);
  border: 1px solid var(--bg-card-hover);
}

.win-share__title {
  margin: 0;
  font-size: var(--text-h3);
  font-weight: 700;
  line-height: var(--leading-tight);
  color: var(--text-primary);
}

/* ── Slot 7 preview tile (260529-gmm / D-gmm-03) ───────────────────────────
   Compact muted preview line — visual mock of what the share PNG will read.
   Renders ABOVE the hint + CTA in BOTH canShare branches so the user sees
   what they'd be sharing at a glance. Text-only — NO PNG inline. Tokens only. */
.win-share__preview {
  margin: 0 0 var(--space-xs) 0;
  padding: var(--space-xs) var(--space-sm);
  background: var(--bg-elevated, var(--bg-primary));
  border: 1px solid var(--text-muted);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font-size: var(--text-body);
  font-weight: 500;
  text-align: center;
}

.win-share__hint {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  line-height: var(--leading-normal);
}

.win-share__cta {
  min-height: 44px;
  padding: var(--space-sm) var(--space-md);
  background: var(--color-info);
  color: var(--bg-primary);
  border: none;
  border-radius: var(--radius-sm);
  font-size: var(--text-body);
  font-weight: 700;
  cursor: pointer;
  text-align: center;
}

.win-share__cta:hover,
.win-share__cta:focus-visible {
  filter: brightness(1.1);
  outline: 2px solid var(--color-info);
  outline-offset: 2px;
}

.win-share__shared {
  margin: 0;
  font-size: var(--text-body);
  font-weight: 600;
  color: var(--text-primary);
}

.win-share__failure {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  /* No-red HARD RULE: failure uses text-secondary token, NOT a danger color. */
}

/* Overlay modal — hand-rolled (NOT <dialog>) for in-app-browser compat. */
.win-share-sheet {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.win-share-sheet__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
}

.win-share-sheet__panel {
  position: relative;
  z-index: 1;
  width: min(560px, 92vw);
  max-height: 90vh;
  overflow-y: auto;
  background: var(--bg-card);
  border-radius: var(--radius-md);
  display: flex;
  flex-direction: column;
}

.win-share-sheet__header {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-md);
  background: var(--bg-card);
  border-bottom: 1px solid var(--bg-card-hover);
}

.win-share-sheet__close {
  width: 44px;
  height: 44px;
  min-width: 44px;
  background: transparent;
  border: none;
  color: var(--text-primary);
  font-size: 28px;
  line-height: 1;
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.win-share-sheet__close:focus-visible {
  outline: 2px solid var(--color-info);
  outline-offset: 2px;
}

.win-share-sheet__title {
  margin: 0;
  font-size: var(--text-h3);
  font-weight: 700;
  color: var(--text-primary);
}

.win-share-sheet__body {
  padding: var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.win-share-sheet__rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.win-share-sheet__pii {
  margin: 0;
  padding: var(--space-sm);
  background: var(--bg-card-hover);
  border-left: 2px solid var(--color-info);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  line-height: var(--leading-normal);
}

.win-share-sheet__validation {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  /* No-red HARD RULE: validation uses text-secondary token, NOT a danger color. */
}

.win-share-sheet__preview {
  min-height: 80px;
  padding: var(--space-md);
  background: var(--bg-primary);
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
}

.win-share-sheet__preview-brand {
  margin: 0;
  font-size: var(--text-h2);
  font-weight: 800;
  letter-spacing: 4px;
  color: var(--text-primary);
}

.win-share-sheet__footer {
  position: sticky;
  bottom: 0;
  display: flex;
  gap: var(--space-sm);
  padding: var(--space-md);
  background: var(--bg-card);
  border-top: 1px solid var(--bg-card-hover);
}

.win-share-sheet__cancel,
.win-share-sheet__share {
  flex: 1 1 auto;
  min-height: 44px;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-sm);
  font-size: var(--text-body);
  font-weight: 700;
  cursor: pointer;
  border: none;
}

.win-share-sheet__cancel {
  background: transparent;
  color: var(--text-secondary);
  border: 1px solid var(--bg-card-hover);
}

.win-share-sheet__share {
  background: var(--color-info);
  color: var(--bg-primary);
}

.win-share-sheet__share:disabled,
.win-share-sheet__share[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

.win-share-sheet__cancel:focus-visible,
.win-share-sheet__share:focus-visible {
  outline: 2px solid var(--color-info);
  outline-offset: 2px;
}

/* The off-screen render source for html-to-image. Not visible to the user;
   parked off-canvas so html-to-image can rasterize the canonical 1080×1350
   surface without affecting layout. */
.win-share-sheet__offscreen {
  position: absolute;
  left: -9999px;
  top: 0;
  pointer-events: none;
}

/* Per-row opt-in (44px touch target). */
.win-share-opt {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: 44px;
  padding: var(--space-xs) var(--space-sm);
  cursor: pointer;
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
}

.win-share-opt:hover,
.win-share-opt:focus-within {
  background: var(--bg-card-hover);
  color: var(--text-primary);
}

.win-share-opt__cb {
  width: 20px;
  height: 20px;
  accent-color: var(--color-info);
  cursor: pointer;
}

.win-share-opt__cb:checked ~ .win-share-opt__label {
  color: var(--text-primary);
}

.win-share-opt__label {
  font-size: var(--text-body);
  line-height: var(--leading-normal);
}

/* The 1080×1350 PNG render surface. AI-tunable dimensions via --png-w / --png-h.
   Positioned off-screen by default (left:-9999px) so it never affects layout;
   html-to-image walks the DOM regardless of position. */
.win-share-png {
  /* AI-tunable */
  --png-w: 1080px;
  --png-h: 1350px;

  position: absolute;
  left: -9999px;
  top: 0;
  width: var(--png-w);
  height: var(--png-h);
  padding: 80px 60px;
  background: linear-gradient(135deg, var(--bg-card) 0%, var(--bg-primary) 100%);
  color: #ffffff; /* the SINGLE hardcoded hex on the PNG surface — UI-SPEC §6 lock */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, system-ui, sans-serif;
  container-type: inline-size;
}

.win-share-png__brand {
  margin: 0 0 32px 0;
  font-size: 48px;
  font-weight: 800;
  letter-spacing: 4px;
  opacity: 0.95;
}

.win-share-png__intro {
  margin: 0 0 24px 0;
  font-size: 32px;
  font-weight: 600;
  opacity: 0.85;
}

.win-share-png__hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  margin-bottom: 48px;
}

.win-share-png__hero-glyph {
  font-size: 160px;
  line-height: 1;
}

.win-share-png__hero-num {
  margin: 0;
  font-size: clamp(160px, 18cqw, 220px);
  font-weight: 800;
  line-height: 1;
}

.win-share-png__hero-unit {
  margin: 0;
  font-size: 36px;
  font-weight: 600;
  letter-spacing: 2px;
  opacity: 0.85;
}

.win-share-png__stack {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-bottom: 64px;
}

.win-share-png__row {
  margin: 0;
  font-size: 32px;
  font-weight: 500;
  opacity: 0.75;
}

.win-share-png__brand-foot {
  margin: 64px 0 0 0;
  font-size: 28px;
  font-weight: 700;
  letter-spacing: 4px;
  opacity: 0.6;
}

/* Reduced-motion: instant modal open/close instead of slide-in. */
@media (prefers-reduced-motion: reduce) {
  .win-share-sheet,
  .win-share-sheet__panel {
    animation: none !important;
    transition: none !important;
  }
}

/* ── 7-day pillar × day matrix (Phase 26, 26-UI-SPEC.md) ─────────────────────
   PUB-09 — replaces 4 single-card slots (steps / weight-journey / macros /
   workouts) with ONE wide matrix that shows the last 7 days + TODAY for
   STEPS / WORKOUTS / MACROS / WEIGHT / RANK pillars.

   Tokens ONLY — no hardcoded hex anywhere; the only px values are the locked-
   exception custom properties at the top of .win-matrix (UI-SPEC §Spacing —
   they cannot be expressed in the existing token scale) plus the 44px touch
   target locked exception (project-wide) and the media-query widths (a
   media-query width cannot be tokenized in CSS).

   Locked exceptions (UI-SPEC §Spacing — verbatim contract):
     --win-matrix-cell-min-w:          60px    (desktop cell min-width fits "✓10.4")
     --win-matrix-cell-min-h:          48px    (single-line row height, ≥44px tap)
     --win-matrix-cell-min-h--macros:  104px   (4 sub-lines: cals + P + C + F per Q5)
     --win-matrix-today-border:        2px     (vertical separator left of TODAY)
                                                — UNUSED per Locked Q2 (no separator);
                                                  retained as defensive surface for
                                                  future flip without re-tokenizing.
     --win-matrix-axis-h:              28px    (day-axis header row height)

   NO-RED HARD RULE (UI-SPEC §Color + threat register T-26-06-03):
     --color-danger appears NOWHERE in this block. --color-warning appears
     ONLY in the macros/weight locked-in-empty needs-attention rules
     (operator-approved 2026-06-01 polish per quick-260531-sui) — never in
     cell glyph rules, never on Steps/Workouts/Rank, never on TODAY. Empty,
     missed, scheduled-untracked, stale, low-rank, and no-log cells render
     with muted/info accents — never red, never alert chrome. The grep gate
     enforces this.

   Today-column tint (UI-SPEC Locked Q2 — SUBTLE 5% tint, no separator chip):
     background: color-mix(in srgb, var(--bg-card) 95%, var(--color-info) 5%).

   Scroll-snap (UI-SPEC Locked Q4 — FIRST scroll-snap pattern in repo):
     Applies at ≤ 899px viewports for ALL widths below desktop matrix tier.
     The renderer (Plan 07) sets scrollLeft = scrollWidth on mount so TODAY
     lands rightmost; left-edge gradient fade signals "more days that way."

   Single source of truth for the matrix surface — the shared card-surface
   selector (win.css :388) already includes .win-matrix, so background /
   border / shadow / radius / padding come from there. This block carries
   layout, locked exceptions, and per-cell state styling ONLY. */

.win-matrix {
  /* Locked-exception custom properties (UI-SPEC §Spacing). Single-source so
     the axis + every row + the mobile scroll-snap fallback all read from ONE
     place. */
  --win-matrix-cell-min-w: 60px;
  --win-matrix-cell-min-h: 48px;
  --win-matrix-cell-min-h--macros: 104px;
  --win-matrix-today-border: 2px;
  --win-matrix-axis-h: 28px;

  display: flex;
  flex-direction: column;
  gap: var(--card-stack-gap);
  text-align: left;
}

/* Sun-Sat toggle removed 2026-06-01 (operator decision, quick-260531-sui) —
   matrix locks to Mon-Sun. Data layer was already Mon-Sun ISO; this is a
   UI-only removal. */

/* ── Day-axis header row ────────────────────────────────────────────────── */

/* The grid lives on the <tr> (not the <thead>) because <thead> is a structural
   wrapper whose direct child is the row, not the <th> cells. CSS grid only
   lays out direct children, so applying display: grid to the <thead> would
   only place its single <tr> child — collapsing all 9 <th>s into the leader
   column. Mirrors .win-matrix__row body-row layout below where the row IS
   the <tr> (quick-260531-tjw fix #4, D-04). */
.win-matrix__axis > tr {
  display: grid;
  /* leader column matches .win-matrix__row-label width;
     7 prior cells use the locked cell min-width;
     trailing today column reuses the same min-width. */
  grid-template-columns: minmax(96px, 1fr) repeat(7, minmax(var(--win-matrix-cell-min-w), 1fr)) minmax(var(--win-matrix-cell-min-w), 1fr);
  height: var(--win-matrix-axis-h);
  align-items: center;
  gap: var(--space-sm);
}

.win-matrix__axis-leader {
  /* empty in the header — visually anchors the row-label alignment. */
}

.win-matrix__axis-day {
  font-size: var(--text-label);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  text-align: center;
}

.win-matrix__axis-today,
.win-matrix__axis-day[aria-current="date"] {
  font-size: var(--text-label);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--color-info);
  text-align: center;
}

/* ── Pillar rows ────────────────────────────────────────────────────────── */

.win-matrix__rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.win-matrix__row {
  display: grid;
  grid-template-columns: minmax(96px, 1fr) repeat(7, minmax(var(--win-matrix-cell-min-w), 1fr)) minmax(var(--win-matrix-cell-min-w), 1fr);
  align-items: stretch;
  gap: var(--space-sm);
  min-height: var(--win-matrix-cell-min-h);
  /* Subtle 1px row divider — token-driven (quick-260531-sui fix #3). */
  border-bottom: 1px solid var(--border-color);
}

.win-matrix__row:last-of-type {
  /* Avoid doubled edge against the shared card border. */
  border-bottom: 0;
}

.win-matrix__row[data-pillar="macros"] {
  min-height: var(--win-matrix-cell-min-h--macros);
}

.win-matrix__row-label {
  font-size: var(--text-body);
  font-weight: 600;
  color: var(--text-primary);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-xs);
}

.win-matrix__row-label-sub {
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--text-muted);
}

/* `.win-matrix__cells` is a logical wrapper. On desktop ≥ 900px (and the
   600–899 tier) the row's grid lays cells directly, so the cells wrapper
   uses display: contents to dissolve into the row grid. On mobile ≤ 899
   the scroll-snap fallback re-promotes it to a flex scroll container
   (see media query below). */
.win-matrix__cells {
  display: contents;
}

/* ── Per-cell base ──────────────────────────────────────────────────────── */

.win-matrix__cell {
  background: transparent;
  /* Subtle 1px column divider — token-driven (quick-260531-sui fix #3). */
  border: 0;
  border-right: 1px solid var(--border-color);
  padding: var(--space-xs);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  min-width: var(--win-matrix-cell-min-w);
  min-height: var(--win-matrix-cell-min-h);
  font-size: var(--text-subtitle);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  cursor: default;
}

.win-matrix__cell:last-child {
  /* Avoid doubled edge against the shared card border. */
  border-right: 0;
}

.win-matrix__cell:focus-visible {
  outline: 2px solid var(--color-info);
  outline-offset: 2px;
}

.win-matrix__cell-glyph {
  font-size: var(--text-subtitle);
  line-height: 1;
}

.win-matrix__cell-value {
  font-size: var(--text-subtitle);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

/* Workouts: workout name beneath the ✓ glyph (quick-260531-tjw fix #3, D-03).
   Rendered ONLY for tracked cells (locked-in-hit + today-secured) — see the
   gated append in buildMatrixCellTd in win.js. 12px (--text-label), single-
   line, ellipsis-truncated, max-width: 100% so the cell column never widens
   past its grid track. Sits below the ✓ + cell-value; the cell's flex column
   stays vertically centered. NO row-height bump — fits inside the 48px row
   (--win-matrix-cell-min-h) along with the glyph. Native `title` attribute
   on the <td> (set by buildMatrixCellTd) still covers full-text on hover. */
.win-matrix__cell-workout-name {
  font-size: var(--text-label);
  line-height: 1.2;
  color: var(--text-secondary);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Per-cell state variants (UI-SPEC §Per-Cell State Contract) ────────── */

/* locked-in-hit: ✓ glyph in --color-success; number in --text-primary */
.win-matrix__cell--hit > .win-matrix__cell-glyph {
  color: var(--color-success);
}

/* Actual prior-day hit state class — buildMatrixCellTd builds the modifier
   as 'win-matrix__cell--' + cell.state and the state literal is
   'locked-in-hit' (win-presentation.js:757). Parity with today-secured
   (quick-260531-sui fix #1). */
.win-matrix__cell--locked-in-hit > .win-matrix__cell-glyph {
  color: var(--color-success);
}

/* locked-in-partial: ◐ glyph in --color-info; number in --text-primary */
.win-matrix__cell--partial > .win-matrix__cell-glyph {
  color: var(--color-info);
}

/* locked-in-empty: em-dash + muted */
.win-matrix__cell--empty {
  color: var(--text-muted);
}

/* today-in-progress: ↗ / ◐ glyph in --color-info */
.win-matrix__cell--in-progress > .win-matrix__cell-glyph {
  color: var(--color-info);
}

/* today-secured: ✓ glyph in --color-success */
.win-matrix__cell--secured > .win-matrix__cell-glyph {
  color: var(--color-success);
}

/* scheduled-untracked (workouts only): ◐ + label in --color-info; NEVER red */
.win-matrix__cell--scheduled > .win-matrix__cell-glyph {
  color: var(--color-info);
}

/* TODAY column subtle tint — UI-SPEC Locked Q2 verbatim:
   color-mix(in srgb, var(--bg-card) 95%, var(--color-info) 5%) */
.win-matrix__cell--today {
  background: color-mix(in srgb, var(--bg-card) 95%, var(--color-info) 5%);
  border-radius: var(--radius-sm);
}

/* Subtle amber tint on prior-day MACROS + WEIGHT locked-in-empty cells —
   a quiet "log this" signal that respects the no-RED HARD RULE (quick-
   260531-sui fix #4). Scoped via data-pillar so STEPS/WORKOUTS/RANK rows
   are NEVER amber. The .--today modifier always wins (today cells are
   never amber — the today background tint above paints over). */
.win-matrix__row[data-pillar="macros"] .win-matrix__cell--locked-in-empty {
  background: color-mix(in srgb, var(--bg-card) 92%, var(--color-warning) 8%);
  border-radius: var(--radius-sm);
}

.win-matrix__row[data-pillar="weight"] .win-matrix__cell--locked-in-empty {
  background: color-mix(in srgb, var(--bg-card) 92%, var(--color-warning) 8%);
  border-radius: var(--radius-sm);
}

/* "so far today" caption — appears only in today-in-progress cells. */
.win-matrix__cell-caption {
  font-size: var(--text-label);
  color: var(--text-muted);
}

/* ── Macros multi-line cell (4 sub-rows: cals + P + C + F per Q5) ────────── */

.win-matrix__cell-multi {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  min-height: var(--win-matrix-cell-min-h--macros);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.win-matrix__cell-macro-primary {
  font-size: var(--text-subtitle);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.win-matrix__cell-macro-sub {
  font-size: var(--text-label);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}

/* ── Per-pillar streak caption (inline under each row, every breakpoint) ── */

.win-matrix__row-streak {
  font-size: var(--text-label);
  color: var(--text-secondary);
  padding-top: var(--space-xs);
  grid-column: 1 / -1;
}

/* 260601 — weekly nutrition ladder caption (Tracked → Protein → Macros on-track
   weeks) + the 5/7 current-week meter. */
.win-matrix__row-nutrition .win-nutrition__count {
  color: var(--text-primary);
  font-weight: 700;
}
.win-matrix__row-nutrition .win-nutrition__best,
.win-matrix__row-nutrition .win-nutrition__unit {
  opacity: 0.65;
}
.win-matrix__row-nutrition .win-nutrition__sep {
  opacity: 0.4;
}
.win-nutrition__nudge {
  font-style: italic;
}
.win-nutrition__meter {
  display: block;
  margin-top: 2px;
  font-size: var(--text-label);
  opacity: 0.8;
}
.win-nutrition__meter--ok {
  color: var(--color-success, #2ecc71);
  opacity: 1;
}

/* 260601 — per-value macro coloring in the matrix cells: green = within ±10% of
   target, amber = logged but off. NO red (NO-SHAME page rule). Applies to the
   calories value AND the protein/carbs/fat sub-rows. */
.win-matrix__cell-value.win-matrix__macro--on,
.win-matrix__cell-macro-sub.win-matrix__macro--on {
  color: var(--color-success, #2ecc71);
}
.win-matrix__cell-value.win-matrix__macro--off,
.win-matrix__cell-macro-sub.win-matrix__macro--off {
  color: var(--color-warning, #f1c40f);
}

/* ── Visually-hidden utility for the screen-reader-only matrix heading.
   Inlined here (no global .visually-hidden exists in win.css/theme.css). */
.win-matrix__sr-heading {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Responsive: scroll-snap at ≤ 899px (UI-SPEC Locked Q4 verbatim) ────── */
/* FIRST scroll-snap pattern in the repo (RESEARCH §11.1). Today is the
   RIGHTMOST cell; renderer wires scrollLeft = scrollWidth on mount so it
   lands focused on mobile. Left-edge gradient fade signals "more days that
   way." Carries to ALL viewports below the desktop matrix threshold per
   the operator's verbatim Q4 answer ("focus on today, scroll left for more"). */
@media (max-width: 899px) {
  .win-matrix__row {
    /* Collapse the row to a 2-track grid (label + scrollable cells region).
       The cells wrapper takes over horizontal layout via flex below. */
    grid-template-columns: minmax(96px, auto) 1fr;
  }

  .win-matrix__cells {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    position: relative;
    gap: var(--space-sm);
  }

  .win-matrix__cells::-webkit-scrollbar {
    display: none;
  }

  .win-matrix__cell {
    flex: 0 0 auto;
    min-width: var(--win-matrix-cell-min-w);
    scroll-snap-align: end;
  }

  .win-matrix__cells::before {
    content: '';
    position: absolute;
    inset: 0 auto 0 0;
    width: 40px;
    background: linear-gradient(to right, var(--bg-primary), transparent 40px);
    pointer-events: none;
  }

  /* Axis follows the same 2-track collapse so the day labels stay
     horizontally aligned to the scrolling cells region. Selector targets
     the <tr> for the same reason as the desktop rule above (quick-260531-tjw
     fix #4). */
  .win-matrix__axis > tr {
    grid-template-columns: minmax(96px, auto) 1fr;
  }
}

/* ── Mobile breakpoint (≤ 480px) — UI-SPEC §Mobile Collapse Contract ───── */
/* Cells enforce 44×44px tap target per project-wide locked exception. */
@media (max-width: 480px) {
  .win-matrix__cell {
    min-width: 44px;
    min-height: 44px;
  }
}

/* ════════════════════════════════════════════════════════════════════════
   MOBILE-ONLY /win REDESIGN (quick 260603-aun) — cards + dots + swipe
   ────────────────────────────────────────────────────────────────────────
   Adopts the approved sketch's visual polish (rounded 16px pillar cards,
   rounded day-chips, status-tinted swipe dots, soft hero glow) for the 5
   daily pillars on viewports < 600px ONLY. DESKTOP (≥ 600px) is untouched:
   the entire .win-mobile block is `display:none` ≥ 600px, the desktop matrix
   + grid render exactly as before, and #win-slot-matrix is hidden ONLY when
   the mobile carousel is mounted (.win-slots--mobile).

   HARD RULES carried: no --color-danger, no --color-warning anywhere in this
   block (misses render neutral via --text-muted). Tokens only — the lone
   locked px exceptions match the rest of win.css (44px tap target, the
   media-query width, and the carousel card flex-basis percentage which is a
   layout ratio, not a tokenizable length). Hits use --color-success (the
   established /win hit color — pips, matrix, streak all use it).
   ════════════════════════════════════════════════════════════════════════ */

/* The mobile block is built by win.js BELOW .win-hero, ABOVE .win-slots, and
   is the ONLY surface that shows the carousel. Hidden at desktop entirely. */
.win-mobile {
  display: flex;
  flex-direction: column;
  gap: var(--card-stack-gap);
}

/* DESKTOP GUARD — the whole mobile redesign collapses at ≥ 600px so the
   existing matrix + grid layout is pixel-identical to today. */
@media (min-width: 600px) {
  .win-mobile { display: none !important; }
}

/* When the mobile carousel is active, hide the (data-reused) matrix slot so
   the 7-day grid isn't duplicated below the cards. ONLY applies on mobile;
   win.js adds .win-slots--mobile and removes it on a breakpoint cross. */
@media (max-width: 599px) {
  .win-slots--mobile #win-slot-matrix { display: none; }
}

/* ── Pinned always-on summary (hero glow + typography) ─────────────────── */
.win-mobile__summary {
  /* This section now wraps ONLY the photo chip — the pips/greeting live up in
     .win-hero. Its former padding + hero-glow radial wash had nothing above the
     chip to sit behind, so it rendered as a faint EMPTY band eating vertical
     space above the nudge. Collapse it to the chip alone. (quick-260603 BUG 2) */
  padding: 0;
}

/* Summary pip row — mirrors the pillar on-track status. green = on track,
   hollow outline = pending. NEVER red/amber. */
.win-mobile__pips {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

.win-mobile__pip {
  width: 13px;
  height: 13px;
  border-radius: var(--radius-full);
  border: 2px solid var(--text-muted);
  background: transparent;
  flex: 0 0 auto;
}

.win-mobile__pip.is-hit {
  background: var(--color-success);
  border-color: var(--color-success);
}

.win-mobile__pips-label {
  margin-left: var(--space-xs);
  font-size: var(--text-label);
  color: var(--text-muted);
}

/* Photo chip — top, rounded pill. TEXT/age only (no body image ever). */
.win-mobile__photochip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  margin-top: var(--space-sm);
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-full);
  background: var(--bg-card);
  font-size: var(--text-label);
  color: var(--text-primary);
  align-self: flex-start;
}

/* ── Swipe carousel ─────────────────────────────────────────────────────── */
.win-mobile__carousel-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.win-mobile__carousel {
  display: flex;
  gap: var(--space-md);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  padding: var(--space-xs) 0;
  /* Hide the scrollbar so the dots are the only affordance. */
  scrollbar-width: none;
}
.win-mobile__carousel::-webkit-scrollbar { height: 0; width: 0; }

.win-mobile__card {
  /* 88% of viewport so the neighbour card peeks — a swipe affordance. */
  flex: 0 0 88%;
  scroll-snap-align: center;
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: var(--space-md);
  box-sizing: border-box;
}

.win-mobile__card-head {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: var(--text-heading);
  font-weight: 600;
}

.win-mobile__card-sub {
  margin-top: var(--space-xs);
  font-size: var(--text-label);
  color: var(--text-muted);
}

/* 7-day chip strip. */
.win-mobile__week {
  display: flex;
  justify-content: space-between;
  margin-top: var(--space-md);
}

.win-mobile__day {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  flex: 1;
}

.win-mobile__day-label {
  font-size: var(--text-label);
  color: var(--text-muted);
}

.win-mobile__chip {
  width: 30px;
  height: 30px;
  border-radius: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-body);
  background: var(--bg-card-hover);
  color: var(--text-muted);
}

/* HIT — green tint + green glyph. MISS stays neutral (never red/amber). */
.win-mobile__chip.is-hit {
  background: color-mix(in srgb, var(--color-success) 16%, transparent);
  color: var(--color-success);
}

/* Today column — emphasised label + outline, NEVER a color-alone signal. */
.win-mobile__day.is-today .win-mobile__day-label {
  color: var(--color-success);
  font-weight: 600;
}
.win-mobile__day.is-today .win-mobile__chip {
  outline: 2px solid var(--color-success);
  outline-offset: 1px;
}

.win-mobile__stat {
  margin-top: var(--space-md);
  font-size: var(--text-body);
  color: var(--text-primary);
}
.win-mobile__stat-num { color: var(--color-success); font-weight: 600; }

/* ── Status-tinted dots ─────────────────────────────────────────────────── */
.win-mobile__dots {
  display: flex;
  gap: var(--space-sm);
  justify-content: center;
  align-items: center;
  padding: var(--space-sm) 0 var(--space-xs);
}

.win-mobile__dot {
  width: 11px;
  height: 11px;
  border-radius: var(--radius-full);
  border: 2px solid var(--text-muted);
  background: transparent;
  padding: 0;
  cursor: pointer;
  appearance: none;
  transition: transform 0.15s ease;
}

/* On-track dot = green fill (real per-pillar status). NEVER red. */
.win-mobile__dot.is-ontrack {
  background: var(--color-success);
  border-color: var(--color-success);
}

.win-mobile__dot[aria-current="true"] { transform: scale(1.5); }

.win-mobile__dot:focus-visible {
  outline: 2px solid var(--color-info);
  outline-offset: 3px;
}

.win-mobile__swipehint {
  text-align: center;
  color: var(--text-muted);
  font-size: var(--text-label);
  margin: 0;
}

@media (prefers-reduced-motion: reduce) {
  .win-mobile__carousel { scroll-behavior: auto; }
  .win-mobile__dot { transition: none; }
}

/* ── Phase 33 (D-12) — travel/sick excuse control ──────────────────────────
   The client's escape-hatch. Deliberately SUBORDINATE to the win cards: a
   collapsed <details> disclosure with muted chrome, NOT a hero. Tokens only,
   NO red/amber (no-red palette per existing /win conventions); the "on"
   (excused) chip state uses the --color-info accent, the "off" state is muted.
   44px min touch targets on every chip (WCAG 2.5.5). XSS-safe markup is built
   in win.js via createElement/textContent — this file only styles it. */
#win-slot-excuse {
  margin-top: var(--space-sm);
}

.win-excuse {
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: var(--space-sm) var(--space-md);
}

/* The disclosure trigger — muted, de-emphasised so it reads as a quiet option.
   Min 44px tap target; native marker hidden in favour of a calm caret glyph. */
.win-excuse__summary {
  list-style: none;
  cursor: pointer;
  min-height: 44px;
  display: flex;
  align-items: center;
  color: var(--text-muted);
  font-size: var(--text-body);
}
.win-excuse__summary::-webkit-details-marker { display: none; }
.win-excuse__summary::before {
  content: '＋';
  margin-right: var(--space-sm);
  color: var(--text-muted);
}
.win-excuse[open] .win-excuse__summary::before { content: '－'; }
.win-excuse__summary:focus-visible {
  outline: 2px solid var(--color-info);
  outline-offset: 3px;
}

.win-excuse__help {
  margin: var(--space-xs) 0 var(--space-sm);
  color: var(--text-muted);
  font-size: var(--text-label);
  line-height: var(--leading-normal);
}

.win-excuse__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.win-excuse__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.win-excuse__day {
  color: var(--text-primary);
  font-size: var(--text-body);
  min-width: 5.5rem;
}

.win-excuse__chips {
  display: flex;
  gap: var(--space-xs);
}

/* Each chip is a >=44px touch target. OFF = muted outline; ON (excused) = the
   info accent fill. No red/amber in either state. */
.win-excuse__chip {
  min-height: 44px;
  padding: var(--space-xs) var(--space-md);
  border-radius: var(--radius-full);
  font-size: var(--text-label);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.win-excuse__chip--off {
  background: transparent;
  border: 1px solid var(--border-color);
  color: var(--text-muted);
}
.win-excuse__chip--on {
  background: var(--color-info);
  border: 1px solid var(--color-info);
  color: var(--text-heading-color);
}
.win-excuse__chip:disabled { opacity: 0.6; cursor: default; }
.win-excuse__chip:focus-visible {
  outline: 2px solid var(--color-info);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .win-excuse__chip { transition: none; }
}

/* ════════════════════════════════════════════════════════════════════════
   260616-evq — CONCEPT C: pinned aggregate-rank band + 7 per-discipline columns.
   One markup block, two responsive modes: phone = scroll-snap carousel (one
   column at a time, static dot affordance); >=900px = all-7-column grid.
   No JS carousel framework (CSS scroll-snap only — the proven win-matrix lane).
   All color via theme.css tokens; --color-danger / --color-warning appear in NO
   new rule body EXCEPT the ported calendar over-eat wash + per-week Δ chip + the
   weight "no weigh-in" tier (a calendar legend that already shipped on staff —
   the /win no-red rule governs NEW chrome, and the calendar's red --missed is
   recolored to a no-red 'rest' in win.js's classForDay, not via CSS).
   ════════════════════════════════════════════════════════════════════════ */

/* ── Pinned aggregate-rank band ─────────────────────────────────────────── */
.win-rankband {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs) var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  background: var(--bg-card);
  border-radius: var(--radius-md);
  margin-bottom: var(--grid-gap);
}
.win-rankband:empty { display: none; }       /* no-JS / pending fallback */
/* quick 260616-jii — the client's OWN obfuscated name at the top of the band. */
.win-rankband__name {
  flex-basis: 100%;
  font-size: var(--text-body);
  font-weight: 700;
  color: var(--text-primary);
}
/* quick 260616-kv2 — the two boards sit SIDE BY SIDE on desktop and STACK on the
   existing /win mobile breakpoint. The wrapper spans the band; each column flexes
   to share the width evenly. */
.win-rankband__boards {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: var(--grid-gap);
}
.win-rankband__col {
  flex: 1 1 0;
  min-width: 0;                  /* allow the row names to shrink without overflow */
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}
/* keeps the 🏆 headline and its trend glyph baseline-aligned on one line. */
.win-rankband__head {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
}
.win-rankband__headline {
  font-size: var(--text-heading);
  font-weight: 700;
  color: var(--text-heading-color);
}
.win-rankband__trend { font-size: var(--text-body); font-weight: 600; color: var(--color-success); }
.win-rankband__sub {
  font-size: var(--text-label);
  color: var(--text-muted);
}
.win-rankband__link {
  align-self: flex-start;
  font-size: var(--text-label);
  font-weight: 600;
  color: var(--color-info);
  text-decoration: none;
  white-space: nowrap;
}
.win-rankband__link:hover { text-decoration: underline; }
.win-rankband__link:focus-visible { outline: 2px solid var(--color-info); outline-offset: 2px; }

/* quick 260616-jii — the all-clients overall rank line + the ±2 mini-leaderboard.
   Privacy posture mirrors /streaks: obfuscated names + positions only. No-red. */
.win-rankband__overall {
  font-size: var(--text-label);
  font-weight: 600;
  color: var(--text-secondary);
}
/* quick 260616-kdu / kv2 — label atop each of the two side-by-side mini-leaderboards
   ("Your coach's group" / "Whole program"). Tokens only, no hex. */
.win-rankband__board-label {
  font-size: var(--text-label);
  font-weight: 600;
  color: var(--text-muted);
}
.win-rankband__board {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin: var(--space-xs) 0 0;
  padding: 0;
  list-style: none;
}
.win-rankband__row {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  font-size: var(--text-body);
  color: var(--text-body-color, var(--text-primary));
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);
}
.win-rankband__row-pos {
  font-weight: 700;
  color: var(--text-secondary);
  min-width: 2.5em;
}
.win-rankband__row-name { color: var(--text-primary); }
/* YOU — highlighted via a success-tinted surface (never red). */
.win-rankband__row--self {
  background: color-mix(in srgb, var(--color-success) 14%, var(--bg-card));
}
.win-rankband__row--self .win-rankband__row-pos,
.win-rankband__row--self .win-rankband__row-name {
  color: var(--text-primary);
  font-weight: 700;
}

/* Mobile: the two leaderboards STACK full-width. */
@media (max-width: 599px) {
  /* 260617-cons — the rank band scrolls with the page on mobile (NO sticky). The
     old `position: sticky; top: 0` was written when the band was a single compact
     headline pinned above a horizontal card swipe. Since kv2 the band is TWO tall
     stacked leaderboards, so pinning it (opaque bg + z-index) covered most of the
     viewport — scrolling down, the discipline cards slid UP BEHIND the pinned band
     and were invisible (operator: "the cards scroll up behind the top part and I
     can't see the data"). Letting it scroll normally restores access to the cards. */
  /* quick 260616-kv2 — the two boards STACK on mobile (full-width columns) rather
     than splitting the narrow phone width in half. */
  .win-rankband__col {
    flex-basis: 100%;
  }
}

/* 260616-cons — the "couldn't load — tap to retry" control shown ONLY when hydrate
   throws and the discipline surface never rendered (win.js catch). No-red: a calm,
   tappable info button, never an alarm. */
.win-disc__retry {
  display: block;
  width: 100%;
  padding: var(--space-md);
  border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.14));
  border-radius: var(--radius-md);
  background: var(--bg-card);
  color: var(--text-secondary);
  font-size: var(--text-body);
  font-weight: 600;
  cursor: pointer;
}
.win-disc__retry:hover { color: var(--text-primary); }
.win-disc__retry:focus-visible { outline: 2px solid var(--color-info); outline-offset: 2px; }

/* ── Discipline row / carousel track (phone-first = swipe carousel) ──────── */
.win-disc {
  display: flex;
  gap: var(--grid-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;             /* hide bar; dots are the affordance */
  margin-bottom: var(--space-sm);
}
.win-disc::-webkit-scrollbar { display: none; }
.win-disc__col {
  flex: 0 0 88%;                     /* ~one column per screen, peek of next */
  scroll-snap-align: center;
  min-width: 0;                      /* iOS Safari flex-overflow guard */
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--card-padding-sm);
  background: var(--bg-card);
  border-radius: var(--radius-md);
}
.win-disc__head {
  font-size: var(--text-body);
  font-weight: 700;
  color: var(--text-heading-color);
  white-space: nowrap;
  min-height: 1.4em;        /* fix #5: align the icon+title band across columns */
}
.win-disc__streak {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  min-height: var(--text-kpi-sm);   /* fix #5: streak/value band same y in every column */
}
.win-disc__streak-num {
  font-size: var(--text-kpi-sm);
  font-weight: 800;
  line-height: 1;
  color: var(--color-success);
}
.win-disc__streak-num--idle { color: var(--text-muted); }   /* zero/idle streak = no-red muted, never danger */
.win-disc__streak-unit { font-size: var(--text-label); color: var(--text-muted); }
/* Weight has no numeric streak; its trend headline ("▲ trending up") renders at a
   normal heading size — consistent with the other columns' streak ROW, not the
   oversized 32px streak-number. No-red: success-green for any direction (outcome). */
.win-disc__trend {
  font-size: var(--text-heading);
  font-weight: 700;
  line-height: 1.1;
  color: var(--text-heading-color);
}
.win-disc__rankline {
  font-size: var(--text-label);
  font-weight: 600;
  color: var(--text-secondary);
  min-height: 1em;
  overflow-wrap: anywhere;
}
.win-disc__caption {
  font-size: var(--text-label);
  color: var(--text-muted);
  margin-top: auto;          /* fix #5: caption pins to the bottom in every column */
  padding-top: var(--space-xs);
  overflow-wrap: anywhere;
}
/* fix #5: the mini-visual (4-week grid / tracking meter / weekly ladder) is the
   flex band that absorbs slack so the head/streak/rankline above it and the
   caption below it line up across columns of differing visual heights. The grid
   stretches equal-height columns; this gives them a consistent internal rhythm. */
.win-disc__col > .cd-wkcal__body,
.win-disc__col > .win-disc__meter,
.win-disc__col > .win-disc__ladder-wrap,
.win-disc__col > .win-disc__rollup {
  flex: 1 1 auto;
  align-self: stretch;
  min-width: 0;
}
/* Tracking streak-meter mini-visual (no day-grid, locked decision 4). */
.win-disc__meter {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: var(--space-xs) 0;
}
.win-disc__meter-num {
  font-size: var(--text-kpi-sm);
  font-weight: 800;
  line-height: 1;
  color: var(--color-success);
}
.win-disc__meter-num--idle { color: var(--text-muted); }
.win-disc__meter-label { font-size: var(--text-label); color: var(--text-muted); }

/* quick 260616-jii (JII-03) — Tracking column's STACKED all-streaks roll-up:
   every discipline's current streak listed top-to-bottom. No-red (a 0/idle value
   reads muted, never danger). Theme tokens only. */
.win-disc__rollup {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-xs) 0;
}
.win-disc__rollup-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-sm);
  font-size: var(--text-body);
}
.win-disc__rollup-label { color: var(--text-secondary); }
.win-disc__rollup-val {
  color: var(--text-primary);
  font-weight: 700;
}
.win-disc__rollup-val--idle { color: var(--text-muted); font-weight: 600; }

/* Dots: a STATIC "7 columns, swipe" affordance (locked decision 3 — no scroll
   listener; the first dot gets --active in markup and never moves). */
.win-disc__dots {
  display: flex;
  justify-content: center;
  gap: var(--space-xs);
  margin-bottom: var(--space-md);
}
.win-disc__dot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-full);
  background: var(--text-muted);
  opacity: 0.4;
}
.win-disc__dot--active { opacity: 1; background: var(--color-success); }

/* >=600px (tablet + desktop): drop the carousel for a 3-up grid (260616-cons —
   operator: "3 tiles, then a row of 3, then 1, to give it space to breathe"). The
   fixed card order makes the three rows read semantically: Activity (Steps ·
   Workout · Streaks), Nutrition (Nutrition · Protein · Macros), Outcome (Weight).
   The 7th card (Weight) is always present (it renders even with no weigh-ins), so
   it reliably lands alone on row 3 — centered in the middle column so the lone
   "outcome" card reads as deliberate, not stranded. Dots hidden (no carousel). */
@media (min-width: 600px) {
  .win-disc {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    overflow: visible;
    scroll-snap-type: none;
  }
  .win-disc__col { flex: none; }
  .win-disc__col:nth-child(7) { grid-column: 2; }   /* center the lone Weight card */
  .win-disc__dots { display: none; }
}

/* win.css already gates motion above; mirror for the carousel. */
@media (prefers-reduced-motion: reduce) {
  .win-disc { scroll-behavior: auto; }
}

/* ── Ported cd-wkcal__* calendar styles, SCOPED under .win-disc so they never
   collide with the staff app. Moved from public/css/clients.css; raw #fff
   converted to var(--text-primary); rgba() channel values kept (not hex). The
   day cells are sized compact so a column fits ~88% of a phone width. ──────── */
.win-disc .cd-wkcal__body { color: var(--text-primary); min-width: 0; max-width: 100%; }
.win-disc .cd-wkcal__header {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 600;
  margin-bottom: 6px;
  white-space: normal;       /* wrap in the narrow /win column instead of clipping */
  color: var(--text-secondary);
}
.win-disc .cd-wkcal__subheader {
  font-size: 10px;
  font-weight: 400;
  margin-top: -3px;
  margin-bottom: 7px;
  white-space: pre-line;
  max-width: 260px;
  color: var(--text-muted);
}
/* 260616-cons — RESTORED the per-week date-label leader column (operator: "the
   calendar views should have dates on them for the week… mapping like on the
   coaching popups"). The /win column is narrower than the staff hover popover, so
   the leader is a COMPACT date ("Jun 8" = the week's Sunday) in a tight fixed-ish
   column, with the 7 day cells sharing the remaining width via minmax(0, 1fr) so
   they shrink rather than clip. The trailing Δ column (Weight) is a small fixed
   width. min-width:0 + max-width:100% keep the grid inside the card. */
.win-disc .cd-wkcal__grid {
  display: grid;
  grid-template-columns: minmax(2.4em, auto) repeat(7, minmax(0, 1fr));
  gap: 2px;
  align-items: center;
  width: 100%;
  max-width: 100%;
  min-width: 0;
}
.win-disc .cd-wkcal__grid--withextra {
  grid-template-columns: minmax(2.4em, auto) repeat(7, minmax(0, 1fr)) 18px;
}
/* The corner (above the date leader) stays empty but MUST occupy its grid cell so
   the S M T W T F S header row aligns over the day cells (not shifted left). */
.win-disc .cd-wkcal__corner { min-width: 0; }
/* Per-week date leader — compact, muted, right-aligned against the day cells so
   the "Jun 8" sits tight to its row (matches the coaching-popup calendar leader). */
.win-disc .cd-wkcal__daylabel {
  font-size: 9px;
  font-weight: 600;
  color: var(--text-muted);
  text-align: right;
  padding-right: 4px;
  white-space: nowrap;
}
.win-disc .cd-wkcal__collabel {
  font-size: 9px;
  font-weight: 600;
  text-align: center;
  color: var(--text-muted);
  min-width: 0;
}
.win-disc .cd-wkcal__day {
  width: 100%;
  aspect-ratio: 1 / 1;
  min-width: 0;             /* shrink below content; never force overflow */
  border-radius: 3px;
  box-sizing: border-box;
}
.win-disc .cd-wkcal__day--done   { background: var(--color-success); }
/* --missed is recolored to a no-red neutral on /win (win.js classForDay maps a
   step/workout miss to 'rest', so this rule is effectively unused, but kept so a
   stray 'missed' never shows raw red). */
.win-disc .cd-wkcal__day--missed { background: rgba(107, 114, 128, 0.35); }
.win-disc .cd-wkcal__day--rest   { background: rgba(107, 114, 128, 0.35); }
.win-disc .cd-wkcal__day--future {
  background: transparent;
  border: 1px dashed rgba(255, 255, 255, 0.18);
}
.win-disc .cd-wkcal__day--stepamber { background: var(--color-warning); }
.win-disc .cd-wkcal__day--ntracked { background: var(--color-info); }
/* Nutrition split-half box (top = protein-floor hit, bottom = food logged). */
.win-disc .cd-wkcal__day--nuthalf {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: var(--bg-primary);
}
.win-disc .cd-wkcal__nuthalf {
  flex: 1 1 50%;
  background: var(--bg-primary);
}
.win-disc .cd-wkcal__nuthalf--on { background: var(--color-success); }
/* Over-eat amber wash — a documented calendar exception to the no-red/amber rule. */
.win-disc .cd-wkcal__nutwash {
  position: absolute;
  inset: 0;
  background: var(--color-warning);
  opacity: 1;
  pointer-events: none;
}
.win-disc .cd-wkcal__day--nutleg-full { background: var(--color-success); }
.win-disc .cd-wkcal__day--nutleg-logged {
  background: linear-gradient(to bottom,
    var(--bg-primary) 0 50%,
    var(--color-success) 50% 100%);
}
.win-disc .cd-wkcal__day--nutleg-nolog   { background: var(--bg-primary); }
.win-disc .cd-wkcal__day--nutleg-calover { background: var(--color-warning); }
/* Weight phase-aware day tiers. */
.win-disc .cd-wkcal__day--good   { background: var(--color-success); }
/* Weight "wrong way" is recolored neutral on /win (no-red outcome framing). */
.win-disc .cd-wkcal__day--bad    { background: rgba(107, 114, 128, 0.35); }
.win-disc .cd-wkcal__day--flat   { background: rgba(107, 114, 128, 0.35); }
.win-disc .cd-wkcal__day--logged { background: rgba(107, 114, 128, 0.35); }
/* Weight "no weigh-in" amber — a documented calendar exception (matches staff). */
.win-disc .cd-wkcal__day--weight-none { background: var(--color-warning); }
.win-disc .cd-wkcal__weektrend {
  font-size: 9px;
  font-weight: 600;
  text-align: right;
  padding-left: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--text-secondary);
}
.win-disc .cd-wkcal__weektrend--muted { color: var(--text-muted); }
.win-disc .cd-wkcal__legend {
  margin-top: 7px;
  font-size: 10px;
  color: var(--text-muted);
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.win-disc .cd-wkcal__legenditem {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.win-disc .cd-wkcal__swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
}
/* Phase-verdict tokens used by the weight Δ chip (cd-col--good/--bad/--muted). */
.win-disc .cd-col--good { color: var(--color-success); }
.win-disc .cd-col--bad  { color: var(--text-secondary); }   /* no-red: neutral, not danger */
.win-disc .cd-col--muted { color: var(--text-muted); }

/* Weekly on-track ladder (Protein / Macros mini-visual) — see win.js
   buildNutritionWeeklyRow idiom; styled minimally here, tokens only. */
.win-disc__ladder {
  display: flex;
  gap: 4px;
  align-items: flex-end;
  padding: var(--space-xs) 0;
}
.win-disc__ladder-wrap { min-width: 0; max-width: 100%; }
/* fix #4: the shared .win-nutrition__meter caption ("This week: 3/7 days · 2 more
   to be on track") ran past the narrow column into its neighbor. Constrain it to
   the column width and wrap cleanly. Scoped under .win-disc so the matrix copy of
   this meter (full-width row) is unaffected. */
.win-disc .win-nutrition__meter {
  max-width: 100%;
  white-space: normal;
  overflow-wrap: anywhere;
  line-height: 1.3;
}


