/*!
 * Marrow Dashboard Theme Layer
 * ------------------------------------------------------------------
 * One stylesheet that gives every dashboard shell (coach, athlete, gym)
 * the marrowfitness.com look: a black surface with red accents by default,
 * a light bone day theme on request, and vibrant color coded metrics.
 * Pair it with /assets/js/theme-toggle.js which stamps
 * data-mz-theme="dark" or "light" on the html element and mounts the
 * user facing day and night switch.
 *
 * Include lines for any dashboard page (after the surface stylesheet):
 *   <link rel="stylesheet" href="/assets/css/dashboard-theme.css">
 *   <script src="/assets/js/theme-toggle.js"></script>
 *
 * Rules honored:
 *   - Brand tokens come from /css/styles.css. The dark theme REMAPS their
 *     values under :root[data-mz-theme="dark"] so the whole existing
 *     dashboard CSS flips without rewrites. No new brand identity tokens.
 *   - The metric colors below are FUNCTIONAL data viz tokens, not brand
 *     colors, following the --marrow-ember-text precedent in styles.css.
 *     Each has a light value and a dark value tuned for WCAG AA on its
 *     surface. Meaning is fixed: training reads green, mind reads cyan,
 *     spirit reads violet, money reads gold, roster reads blue.
 *   - prefers-reduced-motion stills every transition this file adds.
 *   - No em or en dashes anywhere in this file.
 */

/* =====================================================================
   1. FUNCTIONAL METRIC TOKENS (light theme defaults, AA on bone)
   ===================================================================== */
:root {
  --mzv-train:  #0F7B37;  /* training and sessions, green */
  --mzv-mind:   #0E7490;  /* mind and recovery, cyan */
  --mzv-spirit: #6D28D9;  /* spirit and streaks, violet */
  --mzv-money:  #995F0B;  /* payments and revenue, gold */
  --mzv-roster: #1D4ED8;  /* roster and people, blue */
  --mzv-onfill: #FBF8F2;  /* constant light text for red or dark fills */
  /* Surface the accent as type on dark token for theme use even if
     styles.css is older. 2026-09-04: the brand accent is teal, so this is the
     accent hue lifted to 7.74:1 on the dark page and 7.14:1 on the dark card,
     which is the comfort the retired ember red #E08894 was chosen for. The
     name is historical. THE RETIRED RED IS NOT GONE FROM THE FILE: it is now
     --mz-status-risk in section 18.2, because risk is meaning and meaning
     stays red. */
  --marrow-ember-text: #5FAEB0;
  /* Vitality kit fallbacks so this layer stands alone on pages that do
     not load /assets/css/vitality.css. Same values, no conflict. */
  --mz-accent: var(--brand-fill, var(--marrow-red, #356A6B));
  --mz-accent-text: var(--brand-accent, var(--marrow-red, #356A6B));
  --mz-surface: var(--bone-white, #FBF8F2);
  --mz-line: var(--line, rgba(10, 9, 8, 0.08));

  /* AMBIENT ORB TOKENS. The six orb gradients in section 3 read the colour,
     the two strengths and the blur from tokens, and assets/js/theme-engine.js
     rewrites them from the coach accent at runtime through the brand kit.
     The values here ARE the Marrow default, so with no coach theme stored the
     rendering is the unbranded Marrow surface.

     53, 106, 107 is #356A6B in channels, the shipped --marrow-red. It is NOT
     Edwin's raw #376F70, and the five units of difference are deliberate: this
     default has to be byte identical to what assets/brand-kit.js derives for
     an unbranded coach, or a coach who opens the picker, presses the Marrow
     preset and saves gets a dashboard subtly different from the one they were
     just looking at, with nothing erroring to tell them. A guard in
     tests/dashboard-orbs-follow-the-coach.test.mjs compares the two sources
     and it caught exactly that drift when this line said 55, 111, 112.
     A COACH'S orbs still paint their RAW pick, because resolveAction keeps it
     and the alpha solver proves the composite; it is only the unbranded
     default that has to match the fallback the engine falls back to.
     rgba() cannot take a hex, so the triple is the token and the alpha stays
     in the stylesheet where each of the six orbs can carry its own.

     LIGHT NO LONGER RUNS AT A THIRD OF DARK, and that is Edwin's call:
     "the white theme is way too plain, the white one doesnt have as much
     color, i need them BOTH to have the SAME TYPE of color, the SAME AMOUNT
     of color." A third of the alpha was the old answer and it is what made
     light read washed out.

     A MATCHING ALPHA IS THE WRONG ANSWER, and so is matching ink headroom.
     Both were tried here. The same 0.30 that reads as a whisper on black reads
     as a stain on a pale ground, so a matching alpha is out. The answer that
     shipped on 2026-09-04 was headroom parity: solve each theme so the wash
     spends the same FRACTION of that theme's bare ink contrast. It gave 0.20
     and 0.133, and it is genuinely well solved, dark keeps 60.7 percent of its
     headroom and light keeps 60.4 percent.

     IT STILL LEFT LIGHT SHORT OF COLOUR, and Edwin said so again on 2026-09-05
     looking at the same live dashboard. Headroom is a LEGIBILITY budget, not a
     measure of colourfulness. What it counts is luminance the wash spends. What
     Edwin is looking at is chroma the wash delivers, and the two do not track,
     because the light ground already carries the accent hue in itself since the
     2b rederivation while the dark ground is close to neutral. Charging both
     themes the same contrast and calling the result the same colour is the
     error, and it was worth about a sixth of the light card.

     SO THE TARGET IS THE COLOUR AND THE HEADROOM IS THE GUARD. Parity is now
     measured as the absolute chroma of the composited surface in OKLab, which
     is the space built so equal chroma reads as equal colourfulness at
     different lightness. Measured, not eyeballed, against #356A6B on each
     theme's real ground and real card:

       page ground   dark 0.30  -> #172626  chroma 20.60
                     light 0.20 -> #C3D4D5  chroma 19.21   93.3 percent
                     light 0.24 -> #BCCFCF  chroma 21.16  102.7 percent
       raised card   dark 0.20  -> #1D2523  chroma 12.38
                     light 0.133-> #DCE6E6  chroma 10.35   83.6 percent
                     light 0.17 -> #D5E1E1  chroma 12.37   99.9 percent

     The card was the real gap. It ran a sixth under dark and it is the surface
     a coach looks at all day.

     THE HEADROOM IS STILL PROVEN, it is just no longer the thing being
     equalised. Two washes overlap on a narrow viewport, so the composite is
     1 - (1 - a)^2. Light at 0.24 composites to #9BB7B7 and body ink reads
     9.33:1 on it, against 10.36:1 for dark at 0.30. Light at 0.17 on the card
     composites to #BACDCD at 11.99:1. The floor is 4.5:1, so the worst pair
     this change produces sits at more than twice the floor, and brand-kit.js
     maxTintAlpha re-solves both the moment a coach picks a brighter accent, so
     these are defaults and never ceilings.

     Note the light values are NOT in the same ratio as the dark pair, 0.708
     against 0.667, and that is a consequence rather than a slip. Light's ground
     carries chroma and light's card is close to neutral, so the card needs
     proportionally more tint to arrive in the same place. brand-kit derives
     soft from strong using each theme's OWN ratio, so the two stay locked
     together when a coach accent moves them.

     The retired value was 0.11, roughly a third of dark, which kept 79 percent
     of the light headroom and is exactly the washed out theme Edwin rejected.

     The blur stays wider in light, 66px against 54px. That is a rendering
     nuance about six discs resolving on a pale ground, not an amount of
     colour, and dropping it would trade the wash for six visible circles. */
  --mz-orb-rgb: 53, 106, 107;
  /* The page ground as channels, so the translucent nav glass can be mixed
     from it. --bone-cream is a hex and rgba() cannot take one, which is why
     the two nav bars carried literal near black and near cream and stayed
     that colour under a coach ground. This is the light value, and it tracks
     the accent tinted light ground in section 2b rather than bone cream. */
  --mz-ground-rgb: 230, 239, 239;
  --mz-orb-strong: 0.24;
  --mz-orb-soft: 0.17;
}

/* =====================================================================
   2. DARK THEME TOKEN REMAP
   The black background, red accent look Edwin locked for the dashboards.
   :root[data-mz-theme="dark"] outranks the :root block in styles.css, so
   every token driven rule in the existing dashboard CSS flips with it.
   ===================================================================== */
:root[data-mz-theme="dark"] {
  color-scheme: dark;
  --bone-cream: #0A0908;                   /* page background, true Marrow black */
  --bone-white: #171411;                   /* cards and raised surfaces */
  --bone-black: #F2EDE3;                   /* primary text, bone on black */
  --steel: #A8B4C4;                        /* secondary text, 8.7:1 on card */
  --steel-mid: #8393A6;
  --steel-light: #C3CEDC;
  /* 2026-08-04. --ash is the softer body ink on roughly 78 rules across the
     themed surfaces: program card descriptions, review text, exercise cues,
     the range adherence philosophy note, importer copy, retention copy and
     more. The token never flipped, so that ink measured 1.28:1 on the dark
     card, unreadable, which is the "too gray, too dark, brighten them up"
     defect Edwin flagged. Remapped to #E8DFCE, an existing repo value
     (styles.css, --r2-stamp-color dark), 13.87:1 on the dark card and
     15.04:1 on the dark page. Ash FILLS were audited before this flip: the
     retention anniversary rails and the two dark button hover fills keyed
     to ash now read correctly light on dark, and the one dark fallback in
     this file that named ash names the card token instead (section 18.5
     fallback). The print block below restores the light value. */
  --ash: #E8DFCE;
  --line: rgba(242, 237, 227, 0.14);
  /* Metric colors go neon in the dark, all 7:1 or better on the card. */
  --mzv-train:  #3DDC84;
  --mzv-mind:   #33CFEE;
  --mzv-spirit: #B79CFF;
  --mzv-money:  #F5B83D;
  --mzv-roster: #5EA8FF;
  /* Vitality kit accent text flips to the AA accent on dark. */
  --mz-accent-text: var(--marrow-ember-text);
  /* Orb strengths for the dark ground. These two numbers are the 0.30 and 0.20
     the six gradients carried literally before they were tokenised, so the
     default dark backdrop is unchanged. */
  --mz-orb-strong: 0.30;
  --mz-orb-soft: 0.20;
  /* The dark page ground as channels. 10, 9, 8 is #0A0908. */
  --mz-ground-rgb: 10, 9, 8;
}

/* =====================================================================
   2b. LIGHT THEME TOKEN REMAP, COLOUR PARITY WITH DARK  (2026-09-04)

   Edwin, verbatim: "the white theme is way too plain, not as nice as the dark
   theme, the white one doesnt have as much color. i need them BOTH to have the
   SAME TYPE of color, the SAME AMOUNT of color."

   He is right, and the reason is structural rather than a matter of taste.
   Dark got THREE sources of colour: a ground that is a warm near black rather
   than a neutral one, a card that is lifted warm off it, and an orb wash at
   0.30. Light got a bone cream ground with no accent in it at all and an orb
   wash at 0.11. So dark read as a room and light read as a form.

   THE FIX IS THE GROUND ITSELF, NOT MORE ACCENT ON TOP OF IT. Painting more
   teal chrome onto a neutral cream would have made light louder without making
   it warmer, and loud is not the same as coloured. Instead the light ground and
   the light card are re-derived FROM THE ACCENT HUE, exactly as the dark ground
   is a warm near black rather than a neutral one. Same move, opposite register.

   THE DERIVATION, and it is a rule rather than two picked hexes:
     ground = hsl(accent.h, accent.s * 0.7, 92%)   -> #E6EFEF
     card   = hsl(accent.h, accent.s * 0.5, 97%)   -> #F6F9F9
   Hue is the accent's, held exactly. Saturation is scaled down because a
   ground carries hue at a fraction of an accent's chroma or it stops being a
   ground. Lightness is fixed high, and that is the load bearing half: the
   obvious move is to tint the cream by compositing the accent OVER it, which
   drags the ground DARKER and takes accent inked chips from 4.57:1 to 4.27:1.
   Measured, not assumed. Holding lightness and moving hue adds colour and
   ADDS headroom at the same time.

   WHAT IT MEASURES, against the values this block replaces:
     body ink #0A0908 on ground   17.01:1  (was 16.45:1 on bone cream)
     body ink #0A0908 on card     18.79:1  (was 18.06:1 on bone white)
     accent  #356A6B on ground     5.24:1  (was 5.07:1)
     accent  #356A6B washed 0.08   4.71:1  (was 4.57:1)
     muted   #475569 on ground     6.48:1  (was 6.26:1)
   Every pair is better than the ground it replaces. Nothing was traded.

   THE SAME AMOUNT OF COLOUR IS THE ORB ALPHA, and it is in section 1 above:
   light runs 0.24 and 0.17 against dark's 0.30 and 0.20, solved so the
   composited surface carries the same OKLab chroma in both themes, 102.7 and
   99.9 percent of dark.

   THESE FOUR NUMBERS WERE WRONG HERE FOR A DAY AND THE DEFECT IS WORTH NAMING.
   This paragraph read "0.24 and 0.16" from 2026-09-04, while section 1 shipped
   0.20 and 0.133 in the very same commit. Prose and declaration disagreed, and
   the declaration is what renders, so the light theme ran under its own written
   target and nothing failed. A number restated in a comment is a copy, and a
   copy drifts. Section 1 is the only place these live now; this line points at
   it rather than repeating it, and the two values above are quoted only because
   the paragraph would say nothing without them.

   SCOPE. This block is stamped [data-mz-theme="light"], so it reaches the
   dashboards and nothing else. The public marketing site never stamps a theme
   and keeps bone cream. THE COACH STILL OUTRANKS IT: theme-engine.js writes a
   chosen ground as an INLINE property on the html element, which beats an
   attribute selector, so a coach who paints their own light ground gets theirs
   and this is only the default they started from.
   ===================================================================== */
:root[data-mz-theme="light"] {
  color-scheme: light;
  /* The page. hsl(181, 24%, 92%). */
  --bone-cream: #E6EFEF;
  /* The raised card. hsl(181, 17%, 97%). Same lift off the page that bone
     cream to bone white carries, so every surface ladder rule is unmoved. */
  --bone-white: #F6F9F9;
  /* The hairline takes the accent instead of neutral ink, for the same reason
     the ground does. It is a rule and never type, so it is held to nothing
     but being visible. */
  --line: rgba(53, 106, 107, 0.16);
  --mz-line: rgba(53, 106, 107, 0.16);
  --mz-surface: #F6F9F9;
  /* The nav glass mixes from the ground, so it has to move with it. */
  --mz-ground-rgb: 230, 239, 239;
}

/* =====================================================================
   3. DARK THEME COMPONENT OVERRIDES
   Everything the token flip cannot reach: hardcoded rgba surfaces, red
   text that needs the brighter ember value on black, and fills whose
   text must stay light in both themes.
   ===================================================================== */

/* THE AMBIENT ORB WASH IS GONE.  (2026-09-04, Edwin: "take out the orbs.")

   Six low opacity radial gradients used to sit behind every dashboard on
   body::before, drifting on a forty second loop. They are deleted, rule,
   animation and keyframes.

   THE REASON IS THE SAME ONE THAT MADE THE REST OF THIS PASS NECESSARY. A
   coloured wash painted behind everything fights an arbitrary coach chosen
   ground: the coach picks navy, and a teal cloud sits over it at thirty
   percent, and the page they chose is not the page they get. It also made the
   two themes structurally different, because the same alpha reads as a whisper
   on black and a stain on cream, so light was permanently the quieter theme
   for a reason no amount of tuning could fix. Removing it makes the two themes
   symmetric by construction rather than by a solved constant.

   THE COLOUR DID NOT LEAVE WITH IT. It moved into the ground and the card,
   which is where a ground colour belongs: section 2b derives the light page
   and card from the accent hue, and the accent decorations below now apply in
   BOTH themes at the same strengths instead of dark only.

   --mz-orb-rgb SURVIVES and is still written by the brand engine, because five
   other things read it: the nav wash and hairline, the brandmark glow, the
   card breath, the mind body spirit panel and the premium card glow. The name
   is historical now. --mz-orb-strong and --mz-orb-soft survive as the two
   PROVEN alpha ceilings those decorations are drawn at, which is what keeps a
   coach's own colour from washing out their own text. --mz-orb-blur is gone
   with the thing that blurred. */

/* Nav and mobile bars: black glass instead of bone glass. */
[data-mz-theme="dark"] .dash-nav { background: rgba(10, 9, 8, 0.88); }
[data-mz-theme="dark"] .dash-bottom-nav { background: rgba(13, 11, 10, 0.94); }

/* The Marrow orb in the nav carries hardcoded bone black strokes. */
[data-mz-theme="dark"] .logo-svg circle,
[data-mz-theme="dark"] .logo-svg g { stroke: #EFE9DC; }

/* Solid marrow red TEXT fails AA on black. Remap known red text to the
   ember accessibility token. Fills, borders, dots, and rails stay solid red. */
[data-mz-theme="dark"] .card-eyebrow,
[data-mz-theme="dark"] .vs-time-period,
[data-mz-theme="dark"] .bnav-btn[aria-current="page"],
[data-mz-theme="dark"] .lib-tab[aria-selected="true"],
[data-mz-theme="dark"] .set-tab[aria-selected="true"],
[data-mz-theme="dark"] .ex-tag.shared,
[data-mz-theme="dark"] .logo-upload-status.is-error,
/* .status-pill.onboarding was removed from this group on 2026-07-31. The
   group's premise is that every selector in it is already brand red in LIGHT
   and only needs the ember remap to clear AA on black. That premise never held
   for onboarding: coach/athletes/roster.css painted it navy in light, so the
   mismatch was invisible. With the roster override repointed to
   --mz-status-info, onboarding reads correctly in light as in flight, and the
   ember remap here would have made the same chip read as risk in dark. An
   athlete part way through onboarding is in flight, not at risk. Removed so
   the chip inherits the info treatment in both themes. .silent and
   .uncollectible stay: those are genuinely risk states. */
[data-mz-theme="dark"] .status-pill.silent,
[data-mz-theme="dark"] .status-pill.uncollectible,
[data-mz-theme="dark"] .recovery-score-num.red,
[data-mz-theme="dark"] .week-row[data-today="true"] .week-day,
[data-mz-theme="dark"] .booking-date-mon,
[data-mz-theme="dark"] .coach-attribution strong,
[data-mz-theme="dark"] .next-up .d,
[data-mz-theme="dark"] .error-block,
[data-mz-theme="dark"] .preview-badge,
/* .lamella-chip-kicker ADDED 2026-09-04, found by rendering the real page and
   measuring every ink against the ground it lands on. It is `color:
   var(--marrow-red)` in dashboard.css and it sits on the near black nav, which
   measures 1.49:1. A comment beside that rule asserts the opposite, that "the
   engine lightens it, and the resolved value measures 7.09:1". The engine does
   not lighten anything: assets/js/theme-engine.js only ever DEEPENS a pale
   pick, and section 2 of this file states plainly that --marrow-red is not
   remapped by the dark theme and must not be. So the token resolved to the
   literal accent, which was #6B0F1A at 1.49:1 then and is #356A6B at 2.99:1
   now, and the word "MARROW" in the Lamella chip has been unreadable on every
   dark dashboard either way. A comment describing a gate is not the
   gate. It joins the ember group, which is the gate. */
[data-mz-theme="dark"] .lamella-chip-kicker,
[data-mz-theme="dark"] .card-eyebrow.mz-brand-eyebrow {
  color: var(--marrow-ember-text);
}
[data-mz-theme="dark"] .bnav-btn[aria-current="page"] svg { stroke: var(--marrow-ember-text); }

/* Red tinted chips read against black with a slightly stronger tint.
   .onboarding left this group on 2026-07-31 for the reason recorded above: it
   is not a risk state, so it must not carry a red tinted fill either.

   THE LITERAL RED IS DELIBERATE AND IT SURVIVED THE 2026-09-04 ACCENT CHANGE.
   139, 24, 40 was --marrow-red-glow in channels. It is not that token any more
   and it is not written as one, because RISK IS NOT BRAND. A coach whose
   colour is teal must not read a teal "uncollectible" chip, and Edwin's
   "remove all red" is about branding, not about meaning. Section 20.4 inks
   these same two chips with --mz-status-risk for the same reason. */
[data-mz-theme="dark"] .status-pill.silent,
[data-mz-theme="dark"] .status-pill.uncollectible {
  background: rgba(139, 24, 40, 0.3);
}

/* Status pills, theme scoped, repointed at the SOURCE 2026-07-31.
   These rules used to paint literal green (#57D9A3) and amber (#F0B429 dark,
   #8A6118 light). An attribute selector plus two classes outranks a plain
   two class rule, so they beat the neutral --mz-status-* palette on every
   coach sub-surface that renders .status-pill, not only the one surface that
   was patched. The palette is set here now, once, in both themes, so roster,
   invoices, forms, scheduling, classes, packages, store, credentials, form
   progression and the gym surfaces all read the same.

   Steady states read neutral. A state that is simply true does not earn a
   colour, and that restraint is what keeps the risk colour meaning
   something. Amber does not exist in this palette, so .paused, .open and
   .departing resolve to neutral rather than to a warning hue. .paid is the
   one exception: an invoice being paid is a genuinely good outcome, not a
   resting state, so it keeps the good reading. */
[data-mz-theme="dark"] .status-pill.active,
[data-mz-theme="light"] .status-pill.active,
[data-mz-theme="dark"] .status-pill.paused,
[data-mz-theme="light"] .status-pill.paused,
[data-mz-theme="dark"] .status-pill.open,
[data-mz-theme="light"] .status-pill.open,
[data-mz-theme="dark"] .status-pill.departing,
[data-mz-theme="light"] .status-pill.departing {
  color: var(--mz-status-neutral);
  background: var(--mz-status-neutral-soft);
}
[data-mz-theme="dark"] .status-pill.paid,
[data-mz-theme="light"] .status-pill.paid {
  color: var(--mz-status-good);
  background: var(--mz-status-good-soft);
}

/* The recovery numeral carries hue only, never a chip fill. Green keeps the
   good reading. The class still named amber resolves to neutral, because the
   value it marks is a steady one and this palette has no amber. */
[data-mz-theme="dark"] .recovery-score-num.green,
[data-mz-theme="light"] .recovery-score-num.green { color: var(--mz-status-good); }
[data-mz-theme="dark"] .recovery-score-num.amber,
[data-mz-theme="light"] .recovery-score-num.amber { color: var(--mz-status-neutral); }

/* Red and dark fills keep constant light text in both themes. Under the
   dark remap --bone-white becomes the card charcoal, which would put dark
   text on a red button. These stay bone. */
[data-mz-theme="dark"] .badge-unread,
[data-mz-theme="dark"] .review-action.primary,
[data-mz-theme="dark"] .vs-link.primary,
[data-mz-theme="dark"] .msg-thread-unread,
[data-mz-theme="dark"] .composer-send,
[data-mz-theme="dark"] .logo-upload-btn:hover,
/* .plan-badge is a RED fill, so constant bone text is right. Its .plan-badge-custom
   variant is not: that one fills with var(--bone-black), and --bone-black remaps
   to #F2EDE3 in this theme, so the chip became bone text on a bone fill, 1.02:1,
   the unreadable CUSTOM chip. The variant is excluded here and keeps its own
   color: var(--bone-white), which remaps to the card charcoal #171411 for
   15.72:1. Token names are ROLES in this theme, not colors. */
[data-mz-theme="dark"] .plan-badge:not(.plan-badge-custom),
[data-mz-theme="dark"] .btn-primary-dash,
[data-mz-theme="dark"] .retry-banner,
[data-mz-theme="dark"] .retry-btn,
[data-mz-theme="dark"] .week-tab[data-active="true"][data-deload="true"],
[data-mz-theme="dark"] .att-action.primary,
[data-mz-theme="dark"] .tier-pill.founder_cohort,
[data-mz-theme="dark"] .role-pill.owner {
  color: var(--mzv-onfill);
}

/* Focus ring and selection need the bright red on black. */
[data-mz-theme="dark"] a:focus-visible,
[data-mz-theme="dark"] button:focus-visible,
[data-mz-theme="dark"] input:focus-visible,
[data-mz-theme="dark"] [tabindex]:focus-visible {
  outline-color: var(--marrow-ember-text);
}
[data-mz-theme="dark"] ::selection { background: var(--marrow-red-glow); color: #FBF8F2; }

/* Overlays and shadows deepen on black. */
[data-mz-theme="dark"] .more-sheet { background: rgba(0, 0, 0, 0.62); }
[data-mz-theme="dark"] .dash-settings-menu,
[data-mz-theme="dark"] .card,
[data-mz-theme="dark"] .mz-statcard { box-shadow: none; }
[data-mz-theme="dark"] .dash-settings-menu { box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55); }
[data-mz-theme="dark"] .skeleton {
  background: linear-gradient(90deg, #1E1A16 0%, rgba(242, 237, 227, 0.08) 50%, #1E1A16 100%);
  background-size: 200% 100%;
}

/* A faint accent breath across the top right of every card, the
   marrowfitness.com section feel.

   BOTH THEMES, 2026-09-04. This was [data-mz-theme="dark"] only, and it is one
   of the three reasons Edwin read the light theme as "way too plain": the dark
   card carried a coloured breath and the light card carried nothing at all.
   The alpha is the same 0.10 in both, and it is the same accent, so the two
   themes now differ in their register and not in how much colour they carry.
   Measured on the light card #F6F9F9: the composite is #E3EBEB, body ink lands
   at 16.44:1 and secondary ink at 6.26:1, so nothing was traded for it. */
[data-mz-theme] .card {
  background:
    radial-gradient(120% 90% at 100% 0%, rgba(var(--mz-orb-rgb), 0.10), transparent 55%),
    var(--bone-white);
  border-color: var(--line);
}

/* Print always renders the light palette, ink on paper. */
@media print {
  :root[data-mz-theme="dark"] {
    --bone-cream: #EFE9DC;
    --bone-white: #FBF8F2;
    --bone-black: #0A0908;
    --steel: #475569;
    --ash: #2A2A28;
    --line: rgba(10, 9, 8, 0.08);
  }
}

/* =====================================================================
   4. VIBRANT COLOR CODED METRICS (both themes)
   Stat tiles opt in with data-viz="training|mind|spirit|money|roster".
   The number takes the meaning color, the tile gets a soft wash of the
   same hue, and in the dark theme a quiet neon glow.
   ===================================================================== */
.mz-statcard[data-viz]         { --viz: var(--mz-accent); }
.mz-statcard[data-viz="training"] { --viz: var(--mzv-train); }
.mz-statcard[data-viz="mind"]     { --viz: var(--mzv-mind); }
.mz-statcard[data-viz="spirit"]   { --viz: var(--mzv-spirit); }
.mz-statcard[data-viz="money"]    { --viz: var(--mzv-money); }
.mz-statcard[data-viz="roster"]   { --viz: var(--mzv-roster); }

.mz-statcard[data-viz] {
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--viz) 9%, var(--mz-surface)), var(--mz-surface));
  border-color: color-mix(in srgb, var(--viz) 22%, var(--mz-line));
}
.mz-statcard[data-viz] .v { color: var(--viz); }
.mz-statcard[data-viz] .d.up { color: var(--viz); }
.mz-statcard[data-viz]::after {
  content: "";
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 0;
  height: 3px;
  border-radius: 3px 3px 0 0;
  background: linear-gradient(90deg, var(--viz), color-mix(in srgb, var(--viz) 30%, transparent));
}
[data-mz-theme="dark"] .mz-statcard[data-viz] {
  box-shadow: 0 0 34px -18px color-mix(in srgb, var(--viz) 80%, transparent);
}
@supports not (background: color-mix(in srgb, red, blue)) {
  .mz-statcard[data-viz] { background: var(--mz-surface); }
}

/* Momentum sparkline takes the training green. */
.wk-momentum .mz-spark i { background: color-mix(in srgb, var(--mzv-train) 45%, var(--mz-surface)); }
.wk-momentum .mz-spark i:last-child { background: var(--mzv-train); }

/* =====================================================================
   5. SPACING POLISH (both themes)
   Fixes the cramped Today and By the numbers headers and the clipped
   MOMENTUM label Edwin flagged on 2026-07-02.

   THESE FOUR RULES ARE THE SINGLE OWNER OF THIS SPACING (noted 2026-07-31)
   ---------------------------------------------------------------------
   coach/dashboard/dashboard.css used to restate the same four properties
   on the same four classes with its own competing values. That was
   invisible only because the coach dashboard and the gym console loaded
   their surface stylesheet BEFORE this file, inverting the load order
   documented in assets/css/mz-primitives.css, so these declarations won
   on source position alone. Both pages were corrected on 2026-07-31 to
   load the surface stylesheet LAST, and the competing declarations were
   deleted from the surface rather than compensated for, so this block is
   now the only place the values live and the cascade cannot flip them
   back. Nothing here was raised in specificity on purpose: 86 other pages
   load this file without a surface sheet that contests these classes, and
   the athlete dashboard already follows the contract and deliberately
   carries its own tighter spacing, which a specificity raise here would
   silently overwrite.
   ===================================================================== */
.section-head { margin-bottom: 30px; row-gap: 12px; }
.section-sub { margin-top: 8px; }
.card-head { margin-bottom: 22px; row-gap: 6px; }
.card-title { margin-top: 7px; }
.kpi-grid { gap: 16px 18px; }
.card.mz-rail { padding-left: 30px; }
.wk-momentum {
  padding: 4px 2px 0;
  gap: 16px;
  flex-wrap: wrap;
}
.wk-momentum .card-eyebrow { white-space: nowrap; overflow: visible; }
@media (min-width: 720px) {
  .card.mz-rail { padding-left: 34px; }
}

/* =====================================================================
   6. THEME TOGGLE BUTTON (mounted by theme-toggle.js)
   ===================================================================== */
.mz-theme-btn {
  width: 38px;
  height: 38px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--bone-black);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
  margin-right: 8px;
  flex-shrink: 0;
}
.mz-theme-btn:hover { border-color: var(--bone-black); background: var(--bone-white); transform: rotate(12deg); }
.mz-theme-btn svg { width: 17px; height: 17px; }
.mz-theme-btn-floating {
  position: fixed;
  right: 16px;
  bottom: calc(76px + env(safe-area-inset-bottom));
  z-index: 120;
  background: var(--bone-white);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.28);
  margin-right: 0;
}
@media (min-width: 900px) {
  .mz-theme-btn-floating { bottom: 20px; }
}

/* ---------------------------------------------------------------------
   6b. ONE CONTROL, BOTH JOBS  (2026-09-05, Edwin)

   Edwin, on the live coach dashboard: "where it says to change the dark and
   light theme, it needs to be changed and have the ability to change colors."

   The colour capability was already built and already shipping, in
   /assets/js/theme-color-picker.js. It had no way in on the dashboard: the
   host element it mounts into was taken out of coach/dashboard/index.html on
   2026-09-04 to save its bytes, so the module was never even fetched there.
   theme-toggle.js now lends the button a coach already presses to that same
   picker, on first reach rather than on page load, so nothing here is a second
   picker and the byte objection that removed the control still holds.

   The wrapper is what the popover positions against, which is why it carries
   .mz-colorpick as well. It opens to the LEFT edge of the panel because this
   control sits at the right end of the nav on every dashboard, and left: 0
   would push 300px of panel off the side of the phone.
   --------------------------------------------------------------------- */
.mz-theme-control { position: relative; display: inline-flex; }
.mz-theme-control > .mz-colorpick-panel { left: auto; right: 0; }

/* The glyph sits in its own span so the colour dot beside it survives a theme
   flip. theme-toggle.js used to write the icon straight into the button. */
.mz-theme-btn-glyph { display: inline-flex; align-items: center; justify-content: center; }

/* The upgraded button shows the colour it currently holds as a dot on its rim,
   so the control announces that it is about colour before anyone presses it.
   That was the whole complaint: the capability was invisible. */
.mz-theme-btn-color { position: relative; overflow: visible; }
.mz-theme-btn-color .mz-colorpick-trigger-dot {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--bone-cream);
  background: var(--mz-brand-primary);
  flex: none;
  pointer-events: none;
}
/* The plain button rotates on hover. A swatch that rotates reads as broken, so
   the upgraded one lifts its border instead and keeps the dot still. */
.mz-theme-btn-color:hover {
  transform: none;
  border-color: var(--bone-black);
  background: var(--bone-white);
}

/* The floating fallback places the BUTTON with position:fixed. The popover
   anchors to the wrapper, so when there is no nav to sit in the wrapper takes
   the fixed placement and the button goes back to flowing inside it. */
.mz-theme-control-floating {
  position: fixed;
  right: 16px;
  bottom: calc(76px + env(safe-area-inset-bottom));
  z-index: 120;
}
.mz-theme-control-floating .mz-theme-btn {
  background: var(--bone-white);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.28);
  margin-right: 0;
}
/* Anchored to the bottom of the screen, the panel has to open upward. */
.mz-theme-control-floating > .mz-colorpick-panel {
  top: auto;
  bottom: calc(100% + 8px);
}
@media (min-width: 900px) {
  .mz-theme-control-floating { bottom: 20px; }
}

/* =====================================================================
   7. CALENDAR (coach dashboard schedule card)
   Month and week views, sessions from the live scheduling data path.
   ===================================================================== */
.mz-cal-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.mz-cal-label {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--steel);
  min-width: 110px;
  text-align: center;
}
.mz-cal-nav-btn,
.mz-cal-view-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--line);
  color: var(--bone-black);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.mz-cal-nav-btn { width: 30px; height: 30px; border-radius: 50%; }
.mz-cal-nav-btn:hover { border-color: var(--bone-black); }
.mz-cal-nav-btn svg { width: 14px; height: 14px; }
.mz-cal-viewtoggle { display: inline-flex; border-radius: 999px; overflow: hidden; border: 1px solid var(--line); }
.mz-cal-view-btn {
  border: none;
  padding: 6px 14px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--steel);
}
.mz-cal-view-btn[aria-pressed="true"] {
  background: var(--bone-black);
  color: var(--bone-cream);
}
.mz-cal-weekdays,
.mz-cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 6px;
}
/* Type floor, applied 2026-07-31: the tracked uppercase mono micro label sits
   at 10px, sentence text at 12px, and nothing on a dashboard renders in
   single digit pixels. These three calendar labels were the last 9px rules
   in this file. */
.mz-cal-weekdays {
  margin-bottom: 6px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--steel);
  text-align: center;
}
.mz-cal-day {
  position: relative;
  min-height: 56px;
  padding: 6px 4px 4px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: transparent;
  color: var(--bone-black);
  font-family: var(--mono);
  font-size: 12px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.mz-cal-day:hover { border-color: color-mix(in srgb, var(--mz-accent) 40%, var(--line)); }
.mz-cal-day.is-out { opacity: 0.34; }
.mz-cal-day.is-today {
  border-color: var(--marrow-red);
  box-shadow: inset 0 0 0 1px var(--marrow-red);
}
.mz-cal-day[aria-pressed="true"] {
  background: color-mix(in srgb, var(--mz-accent) 12%, var(--mz-surface));
  border-color: var(--marrow-red);
}
.mz-cal-day .n { line-height: 1; }
.mz-cal-dots { display: flex; gap: 3px; flex-wrap: wrap; justify-content: center; }
.mz-cal-dots i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--mzv-train);
  display: block;
}
.mz-cal-dots i.virtual { background: var(--mzv-mind); }
.mz-cal-count {
  font-size: 10px;
  color: var(--steel);
  letter-spacing: 0.08em;
}
.mz-cal-agenda { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 14px; }
.mz-cal-agenda-title {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--steel);
  margin: 0 0 10px;
}
.mz-cal-week { display: grid; gap: 8px; }
@media (min-width: 900px) { .mz-cal-week { grid-template-columns: repeat(7, 1fr); } }
.mz-cal-weekcol {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px;
  min-height: 84px;
}
.mz-cal-weekcol.is-today { border-color: var(--marrow-red); }
.mz-cal-weekcol h4 {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--steel);
  margin: 0 0 8px;
  font-weight: 500;
}
.mz-cal-event {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 8px;
  margin-bottom: 4px;
  background: color-mix(in srgb, var(--mzv-train) 10%, var(--mz-surface));
  border-left: 3px solid var(--mzv-train);
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--bone-black);
}
.mz-cal-event.virtual {
  background: color-mix(in srgb, var(--mzv-mind) 10%, var(--mz-surface));
  border-left-color: var(--mzv-mind);
}
.mz-cal-event .t {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--steel);
  flex-shrink: 0;
}
.mz-cal-empty-note {
  margin: 12px 0 0;
  font-size: 13px;
  color: var(--steel);
}
.mz-cal-empty-note a { color: var(--mz-accent-text); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }

/* =====================================================================
   8. MARROW BRANDMARK (Programming section and anywhere a section
   needs to carry the official app mark)
   ===================================================================== */
.mz-brand-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.mz-brand-eyebrow .mz-brand-mark { width: 18px; height: 18px; flex-shrink: 0; }
/* The official mark keeps its ink stroke: bone black in the light theme,
   bone text in the dark theme. The center hex stays marrow red. */
.mz-brand-mark circle,
.mz-brand-mark g { stroke: var(--bone-black); }

/* =====================================================================
   9. ACHIEVEMENT ENGINE, dark theme depth
   The card itself flips with the token remap. This adds the black
   theater dim and a soft accent glow so the moment lands at night.
   ===================================================================== */
[data-mz-theme="dark"] .marrow-achv-wrap { background: rgba(0, 0, 0, 0.62); }
[data-mz-theme="dark"] .marrow-achv-card {
  box-shadow:
    0 0 0 1px rgba(242, 237, 227, 0.07),
    0 30px 80px rgba(0, 0, 0, 0.65),
    0 0 70px -24px color-mix(in srgb, var(--achv-accent, var(--marrow-red)) 55%, transparent);
}
[data-mz-theme="dark"] .marrow-achv-eyebrow { color: var(--marrow-ember-text); }

/* Mind body spirit panel: its accent wash breathes in BOTH themes now, for
   the same reason the card breath above does. Composite on the light card is
   #DBE5E5, body ink 15.49:1, secondary ink 5.90:1. */
[data-mz-theme] .mbs {
  background:
    radial-gradient(130% 120% at 100% 0%, rgba(var(--mz-orb-rgb), 0.14), transparent 55%),
    var(--bone-white);
  box-shadow: none;
}

/* =====================================================================
   10. REDUCED MOTION
   ===================================================================== */
@media (prefers-reduced-motion: reduce) {
  .mz-theme-btn,
  .mz-cal-day,
  .mz-cal-nav-btn,
  .mz-cal-view-btn { transition: none; }
  .mz-theme-btn:hover { transform: none; }
}

/* ------------------------------------------------------------------
 * QA walkthrough 2026-07-03: dark-scope repairs for surfaces that
 * hardcode the cream sticky nav or invert fills through raw tokens.
 * Scoped to dark so the light theme keeps the original look untouched.
 * ------------------------------------------------------------------ */
:root[data-mz-theme="dark"] .programs-nav,
:root[data-mz-theme="dark"] .rev-nav {
  background: rgba(10, 9, 8, 0.92);
  border-bottom-color: var(--mz-line);
}
/* Adversarial review 2026-07-03: the onboarding wizards hardcode the same
 * cream sticky bar. This commit put the dark default on those pages, which
 * left bone text (#F2EDE3 after the token flip) on the cream glass. Same
 * repair as programs-nav above. */
:root[data-mz-theme="dark"] .wiz-nav,
:root[data-mz-theme="dark"] .wiz-header {
  background: rgba(10, 9, 8, 0.92);
  border-bottom-color: var(--mz-line);
}
/* Inverted stat tile (light fill in the light theme). Under dark the
 * remapped tokens flipped it cream on black; pin it to the card surface
 * with an accent edge so it still reads as the highlighted tile. */
:root[data-mz-theme="dark"] .metric-tile.accent {
  background: var(--bone-white);
  color: var(--bone-black);
  border-color: var(--mz-accent);
}
:root[data-mz-theme="dark"] .metric-tile.accent .k {
  color: var(--steel);
  opacity: 1;
}
/* The accent tile's value and meta pin to the inverted fill colors, which
 * both resolve dark-on-dark once the tile sits on a card surface. Pin them
 * back to readable ink and secondary text. */
:root[data-mz-theme="dark"] .metric-tile.accent .v { color: var(--bone-black); }
:root[data-mz-theme="dark"] .metric-tile.accent .meta { color: var(--steel); opacity: 1; }

/* =====================================================================
   11. CONTRAST PAIRING LAYER  (V3 item 13)
   ---------------------------------------------------------------------
   A semantic text-on-background system. Every text ROLE maps to a
   foreground token that is guaranteed to clear WCAG contrast against the
   background it is meant to sit on: 4.5:1 for normal text, 3:1 for large
   text. Nothing here restyles an existing selector. It only defines
   custom properties and opt-in utility classes, so with no class adopted
   and no theme selected the rendered look is byte for byte the current
   Marrow default. Surfaces adopt the roles page by page in a later wave.

   Every value maps to an EXISTING token from styles.css or from the
   sections above. No new brand color values are introduced here.

   Verified pairings (sRGB relative luminance, WCAG 2.x):
     Re-measured 2026-09-04 against the teal accent and the accent tinted
     light ground introduced in section 2b.
     LIGHT (page --bone-cream #E6EFEF, card --bone-white #F6F9F9, accent fill)
       heading / body   --bone-black #0A0908 on page ............. 17.01:1
       muted            --steel #475569 on page ................... 6.48:1
       link             --marrow-red #356A6B on page .............. 5.24:1
       on-accent        --mzv-onfill #FBF8F2 on accent ............ 5.78:1
       placeholder      --steel #475569 on page ................... 6.48:1
       badge            --bone-black on --steel-light chip ....... 11.36:1
     DARK (page #0A0908, card #171411, accent fill)
       heading / body   #F2EDE3 on page ......................... 17.05:1
       heading / body   #F2EDE3 on card ......................... 15.72:1
       muted            #A8B4C4 on card .......................... 8.73:1
       link             --marrow-ember-text #5FAEB0 on page ...... 7.74:1
       link             --marrow-ember-text #5FAEB0 on card ...... 7.14:1
       on-accent        --mzv-onfill #FBF8F2 on accent ............ 5.78:1
       placeholder      #A8B4C4 on card .......................... 8.73:1
       badge            #F2EDE3 on the remapped chip ............ 12.59:1
   The role tokens ride the existing token flips, so --mz-fg-heading and
   its siblings auto-correct in the dark theme. TWO roles need an explicit
   dark value, one on each side of the pairing. The link INK, because solid
   marrow red text scores 1.62:1 on black. And the badge GROUND, because
   --steel-light is pale in both tables and never flips, so the flipped ink
   measured 1.365:1 on it. The dark badge row above was MISSING from this
   table until 2026-09-05, and the missing row was the defect. Both remaps
   live in the dark stamp below.

   Design note (item 51 research fold). A premium dashboard earns its calm
   from restraint, not decoration. The disciplines applied here:
     - One ink for text, one accent for meaning. Color carries data
       (the metric tokens above), never mood. Text stays near-monochrome.
     - Contrast is a floor, not a target. Body copy sits far above 4.5:1
       so the eye never strains, while muted and placeholder text stay a
       clear step down without dropping under the line.
     - A muted role is a deliberate tier, not faded body text. It reads as
       hierarchy, so secondary information recedes without becoming noise.
     - The type scale climbs in gentle steps (roughly a 1.2 ratio) and the
       spacing rhythm holds to an 8px lattice (section 5 above), so the
       page feels engineered rather than assembled.
     - Links declare themselves with the brand red, underlined on adoption,
       never by color alone, so meaning survives a grayscale or colorblind
       read.
   ===================================================================== */
:root {
  /* Text role foregrounds (light theme values, all existing tokens). */
  --mz-fg-heading:     var(--bone-black);
  --mz-fg-body:        var(--bone-black);
  --mz-fg-muted:       var(--steel);
  --mz-fg-on-card:     var(--bone-black);
  --mz-fg-on-accent:   var(--mzv-onfill);   /* text on a marrow-red or brand-primary fill */
  --mz-fg-badge:       var(--bone-black);    /* text on a neutral chip surface */
  --mz-fg-placeholder: var(--steel);         /* WCAG pass, not the sub-3:1 hint gray */
  --mz-fg-link:        var(--marrow-red);

  /* Background reference aliases, so a surface can name the pairing it is
     adopting without guessing which token the role was tuned against. */
  --mz-bg-page:    var(--bone-cream);
  /* 2026-07-30 (Lane 1) pinned this to the literal #FBF8F2 because
     var(--bone-white) resolved to #EFE9DC in styles.css, identical to
     --mz-bg-page, so light mode cards had zero surface separation.
     2026-07-31: the root token is now the documented canon #FBF8F2, so the
     workaround is retired and the card names the token again. One source of
     truth. Section 18.5 still states the dark card explicitly. */
  --mz-bg-card:    var(--bone-white);
  --mz-bg-accent:  var(--marrow-red);
  --mz-bg-badge:   var(--steel-light);
}

/* TWO roles cannot ride the token flip, and they fail for opposite reasons.

   THE LINK. Solid marrow red text fails on the dark page, so it is remapped
   to the accessibility ember, matching the red text handling already used
   across section 3.

   THE BADGE, added 2026-09-05, and it is fixed on the GROUND side rather than
   the ink side. Nothing about this one is written in a single wrong line.
   --mz-bg-badge names --steel-light, and --steel-light is a LIGHT value in
   BOTH tables: #B8C5D6 in the light one, #C3CEDC under the dark remap. It
   does not flip. --mz-fg-badge names --bone-black, which DOES flip, from
   #0A0908 to #F2EDE3. So the chip ground stayed pale while its ink went pale
   with the theme, and the badge role measured 1.365:1 under the dark stamp
   against 11.365:1 in the light one. That is a chip whose label is not there.

   THE OBVIOUS REPAIR IS THE WRONG ONE, and this is worth writing down because
   it looks correct in isolation. Pinning --mz-fg-badge to --bone-cream here
   fixes the pair, 12.487:1, and breaks every element that actually renders.
   The .mz-fg-badge utility sets a COLOUR and no background, so consumers
   bring their own chip: .hb-meta-pill in coach/habits paints --bone-cream and
   .cf-chip in coach/forms paints --bone-white, both of which DO flip. Dark ink
   on those takes them from 17.05:1 and 15.72:1 to 1.00:1 and 1.13:1. The one
   token in this pairing that refuses to flip is the ground, so the ground is
   what moves.

   --mz-bg-modal is the most lifted step of the surface ladder in section 18.5
   and the only existing dark neutral that separates from BOTH the page
   (#0A0908) and the card (#171411). The dark chip becomes #2A2826 and its
   flipped ink reads 12.587:1 on it. No new token, and the light table is
   untouched, so the badge still measures the 11.365:1 section 11 documents.

   Nothing consumes --mz-bg-badge today, which is exactly why this went
   unseen: the pair is a documented contract that no element realises, so
   every guard that discovers its subjects by rendering a page is structurally
   blind to it. The per surface patches that solved the same non flipping
   --steel-light fill by hand, section 19.2 below and the disabled send chip
   in athlete/messages/messages.css, are untouched and still correct, because
   those paint --steel-light directly rather than through this token.

   Guarded by tests/theme-role-token-contrast-matrix.test.mjs, which resolves
   every fg role token against every ground role token it is paired with, in
   every theme stamp this file defines, and which is where the defect above
   was found and where the wrong repair was caught. */
:root[data-mz-theme="dark"] {
  --mz-fg-link: var(--marrow-ember-text);
  --mz-bg-badge: var(--mz-bg-modal);
}

/* Opt-in utility classes. A surface adds the class to adopt the role.
   Absent the class, nothing changes. */
.mz-fg-heading { color: var(--mz-fg-heading); }
.mz-fg-body    { color: var(--mz-fg-body); }
.mz-fg-muted   { color: var(--mz-fg-muted); }
.mz-fg-on-card { color: var(--mz-fg-on-card); }
.mz-fg-badge   { color: var(--mz-fg-badge); }
.mz-on-accent  { color: var(--mz-fg-on-accent); }
.mz-fg-link    { color: var(--mz-fg-link); }
.mz-fg-link:hover { color: var(--marrow-red-glow); }
.mz-fg-placeholder::placeholder { color: var(--mz-fg-placeholder); opacity: 1; }

/* =====================================================================
   12. PER-COACH THEMING ENGINE HOOKS  (V3 items 18 to 21, engine only)
   ---------------------------------------------------------------------
   A coach brand layer that sits OVER the locked Marrow tokens, never
   replacing them. The three properties below default to the locked
   brand values, so a surface that reads var(--mz-brand-primary) renders
   exactly the Marrow red until a coach theme is stored. No existing
   selector reads these yet, so defining them changes nothing today.

   /assets/js/theme-engine.js fills these at runtime only when a coach
   theme exists, and recomputes --mz-brand-on-primary and the on-accent
   pairing from the chosen color's luminance so text on the brand fill
   always clears 4.5:1. When nothing is stored the engine writes nothing
   and these defaults hold, keeping the default look unchanged.
   ===================================================================== */
:root {
  --mz-brand-primary:     var(--marrow-red);        /* coach primary, falls back to locked red */
  --mz-brand-offset:      var(--marrow-red-glow);   /* coach offset / hover, falls back to glow */
  --mz-brand-on-primary:  var(--mzv-onfill);        /* readable text on the primary fill */
}

/* Opt-in brand fill utilities. Text color is the engine-computed readable
   pairing, so it stays legible whatever primary the coach chose. */
.mz-brand-fill {
  background: var(--mz-brand-primary);
  color: var(--mz-brand-on-primary);
}
.mz-brand-fill--offset { background: var(--mz-brand-offset); color: var(--mz-brand-on-primary); }
.mz-brand-edge { border-color: var(--mz-brand-primary); }

/* =====================================================================
   13. DASHBOARD BUTTON AFFORDANCE  (V3.1 item 9, appended 2026-07-07)
   ---------------------------------------------------------------------
   This theme layer enriches the per-surface .btn-primary-dash base
   (defined in coach/athlete/gym dashboard.css) without editing it. Adds
   depth, a clear pressed state, and a focus-visible ring so the dashboard
   primary button reads as obviously clickable. Hover background stays
   owned by the surface rule (a different property), so nothing is
   overwritten. The reworked .btn-secondary base in styles.css keys off
   --bone-black, which the dark remap in section 2 flips, so the secondary
   button self-corrects to light ink on the dark dashboard theme with no
   extra rule here.

   THE TRANSITION HAS ONE OWNER, THIS BLOCK (noted 2026-07-31)
   ---------------------------------------------------------------------
   The original header said this file loads after every surface stylesheet.
   That was true of the coach dashboard and the gym console only because
   both inverted the load order documented in mz-primitives.css. Both were
   corrected on 2026-07-31 so the surface stylesheet loads LAST, which
   would have let coach/dashboard/dashboard.css win with its own bare
   transition: background 0.15s ease and strip transform and box-shadow
   from the list on 10 buttons, so the hover lift would snap instead of
   ease. That competing declaration was deleted from the surface, leaving
   this block as the only owner. The athlete dashboard keeps its own
   0.15s transition on purpose, which is why nothing here was raised in
   specificity.
   ===================================================================== */
.btn-primary-dash {
  box-shadow: 0 1px 2px rgba(10, 9, 8, 0.18);
  transition: background 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
}
.btn-primary-dash:hover { transform: translateY(-1px); box-shadow: 0 8px 20px var(--mz-brand-halo, rgba(53, 106, 107, 0.30)); }
.btn-primary-dash:focus-visible { outline: 2px solid var(--marrow-red); outline-offset: 3px; }
.btn-primary-dash:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(10, 9, 8, 0.24); }
:root[data-mz-theme="dark"] .btn-primary-dash:focus-visible { outline-color: var(--marrow-ember-text); }
:root[data-mz-theme="dark"] .btn-primary-dash:hover { box-shadow: 0 8px 22px rgba(var(--mz-orb-rgb), 0.5); }
@media (prefers-reduced-motion: reduce) {
  .btn-primary-dash { transition: background 0.15s ease; }
  .btn-primary-dash:hover, .btn-primary-dash:active { transform: none; }
}

/* =====================================================================
   14. COLOR PICKER POPOVER  (V3.1 item 1, appended 2026-07-07)
   ---------------------------------------------------------------------
   Styles only. The open/close, swatch selection, and native input wiring
   are built separately in JS against these class names. Premium, compact,
   floats above content, keyboard focusable, dark-mode aware through the
   token flip. Used where a coach or gym picks their brand primary color.
   ===================================================================== */
.mz-colorpick { position: relative; display: inline-flex; }

.mz-colorpick-trigger {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px 8px 10px;
  background: var(--bone-white);
  color: var(--bone-black);
  border: 1px solid var(--line);
  border-radius: 10px;
  font: 600 13px var(--sans);
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.mz-colorpick-trigger:hover { border-color: var(--bone-black); box-shadow: 0 6px 16px rgba(10, 9, 8, 0.10); }
.mz-colorpick-trigger:focus-visible { outline: 2px solid var(--marrow-red); outline-offset: 2px; }
/* The swatch inside the trigger. This rule used to name .mz-colorpick-chip,
   a class theme-color-picker.js has never emitted, so it matched nothing and
   the swatch was an empty inline span with no width, no ring and no radius.
   The module builds .mz-colorpick-trigger-dot, so that is what is styled here.
   Without the ring and radius the dot reads as a stray colored block rather
   than a color swatch, which is the whole point of the control. The background
   is the pre-script fallback; the module sets the live color inline. */
.mz-colorpick-trigger .mz-colorpick-chip,
.mz-colorpick-trigger .mz-colorpick-trigger-dot {
  width: 20px;
  height: 20px;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--mz-brand-primary);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25);
  flex: none;
}
.mz-colorpick-trigger .mz-colorpick-caret {
  width: 10px;
  height: 10px;
  color: var(--steel);
  flex: none;
  transition: transform 0.15s ease;
}
.mz-colorpick.is-open .mz-colorpick-trigger .mz-colorpick-caret,
.mz-colorpick-trigger[aria-expanded="true"] .mz-colorpick-caret { transform: rotate(180deg); }

.mz-colorpick-panel {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 140;
  min-width: 232px;
  padding: 14px;
  background: var(--bone-white);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 18px 44px rgba(10, 9, 8, 0.18);
  display: none;
}
.mz-colorpick-panel[data-mz-open="true"],
.mz-colorpick.is-open .mz-colorpick-panel {
  display: block;
  animation: mz-colorpick-in 0.16s ease both;
}
.mz-colorpick-panel--right { left: auto; right: 0; }
[data-mz-theme="dark"] .mz-colorpick-panel { box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55); }
@keyframes mz-colorpick-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.mz-colorpick-swatches {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 8px;
}
.mz-colorpick-swatch {
  width: 100%;
  aspect-ratio: 1;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.mz-colorpick-swatch:hover { transform: scale(1.08); }
.mz-colorpick-swatch:focus-visible { outline: 2px solid var(--marrow-red); outline-offset: 2px; }
.mz-colorpick-swatch[aria-pressed="true"],
.mz-colorpick-swatch.is-selected {
  box-shadow: 0 0 0 2px var(--bone-white), 0 0 0 4px var(--bone-black);
}
[data-mz-theme="dark"] .mz-colorpick-swatch:focus-visible { outline-color: var(--marrow-ember-text); }

.mz-colorpick-native {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  font: 500 12px var(--sans);
  color: var(--steel);
}
.mz-colorpick-native label { color: var(--steel); }
.mz-colorpick-native input[type="color"] {
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: transparent;
  cursor: pointer;
  flex: none;
}
.mz-colorpick-native input[type="color"]:focus-visible { outline: 2px solid var(--marrow-red); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .mz-colorpick-trigger,
  .mz-colorpick-trigger .mz-colorpick-caret,
  .mz-colorpick-swatch { transition: none; }
  .mz-colorpick-panel[data-mz-open="true"],
  .mz-colorpick.is-open .mz-colorpick-panel { animation: none; }
  .mz-colorpick-swatch:hover { transform: none; }
}

/* =====================================================================
   15. COMMUNITY + EVENTS TABS AND CARDS  (V3.1 item 12, 2026-07-07)
   ---------------------------------------------------------------------
   Two new dashboard tabs, Community and Events. The tabs and panels reuse
   the existing .dash-section-btn and .dash-section look verbatim, so they
   are visually identical to Today and Training. The two additions below
   are only the per-section accent hooks and the premium surface cards the
   Events and Community feeds render into.

   Every text-on-background pair uses the readable-ink pairing tokens
   (--mz-fg-*, section 11) or the engine-computed --mz-brand-on-primary, so
   copy stays legible on the light theme, the dark theme, and any per-coach
   primary color. No hardcoded near-white ink on light.
   ===================================================================== */

/* Tab accent parity for the two new sections (matches the existing
   underline treatment in the surface dashboard.css). */
.dash-section-btn[data-section="community"][aria-selected="true"]::after,
.dash-section-btn[data-section="events"][aria-selected="true"]::after {
  background: var(--marrow-red);
}
[data-mz-theme="dark"] .dash-section-btn[data-section="community"][aria-selected="true"]::after,
[data-mz-theme="dark"] .dash-section-btn[data-section="events"][aria-selected="true"]::after {
  background: var(--marrow-ember-text);
}

/* ----- Events surface, built to beat Eventbrite ----- */
.ev-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 20px;
}
.ev-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--mz-bg-card, var(--bone-white));
  border: 1px solid var(--line);
  border-radius: 16px;
  overflow: hidden;
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.ev-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 20px 44px rgba(10, 9, 8, 0.10);
  border-color: color-mix(in srgb, var(--mz-brand-primary) 40%, var(--line));
}
[data-mz-theme="dark"] .ev-card:hover { box-shadow: 0 20px 44px rgba(0, 0, 0, 0.5); }

.ev-card-media {
  position: relative;
  aspect-ratio: 16 / 9;
  background:
    linear-gradient(135deg,
      color-mix(in srgb, var(--mz-brand-primary) 24%, var(--bone-white)),
      color-mix(in srgb, var(--mz-brand-primary) 6%, var(--bone-white)));
  display: flex;
  align-items: flex-end;
  padding: 12px;
  overflow: hidden;
}
.ev-card-media img,
.ev-card-media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ev-card-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 18px 20px 20px;
}
.ev-card-date {
  font: 600 11px var(--mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mz-fg-muted, var(--steel));
}
.ev-card-title {
  font-family: var(--display);
  font-weight: 800;
  font-size: 19px;
  line-height: 1.18;
  letter-spacing: -0.01em;
  color: var(--mz-fg-heading, var(--bone-black));
}
.ev-card-meta {
  font: 400 13.5px var(--sans);
  line-height: 1.5;
  color: var(--mz-fg-muted, var(--steel));
}
.ev-card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 4px;
}

/* Badge sits on the brand fill, ink is the engine-computed readable pairing. */
.ev-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border-radius: 999px;
  background: var(--mz-brand-primary);
  color: var(--mz-brand-on-primary);
  font: 600 10.5px var(--mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
}
.ev-badge--soft {
  background: color-mix(in srgb, var(--mz-brand-primary) 14%, var(--bone-white));
  color: var(--mz-fg-heading, var(--bone-black));
}
.ev-badge--free {
  background: color-mix(in srgb, var(--mzv-train) 16%, var(--bone-white));
  color: var(--mzv-train);
}

/* RSVP action. Compact filled button on the brand color, full microstates. */
.ev-rsvp {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border: none;
  border-radius: 10px;
  background: var(--mz-brand-primary);
  color: var(--mz-brand-on-primary);
  font: 600 12.5px var(--sans);
  letter-spacing: 0.02em;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(10, 9, 8, 0.18);
  transition: background 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
}
.ev-rsvp:hover { background: var(--mz-brand-offset); transform: translateY(-1px); box-shadow: 0 8px 18px var(--mz-brand-halo, rgba(53, 106, 107, 0.28)); }
.ev-rsvp:focus-visible { outline: 2px solid var(--marrow-red); outline-offset: 2px; }
.ev-rsvp:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(10, 9, 8, 0.24); }
.ev-rsvp[aria-pressed="true"] {
  background: color-mix(in srgb, var(--mz-brand-primary) 14%, var(--bone-white));
  color: var(--mz-fg-heading, var(--bone-black));
  box-shadow: none;
}
.ev-rsvp[disabled] { background: var(--steel); color: var(--mzv-onfill); cursor: not-allowed; box-shadow: none; }
/* --steel FLIPS to the light #A8B4C4 under the dark stamp while --mzv-onfill
   is constant light, so the disabled label read 1.92 to 1 there. Same pairing
   and same repair as section 19.2 below: name the page black for the dark
   table. Found by PART 12 of tests/theme-ink-ground-guard.test.mjs. */
:root[data-mz-theme="dark"] .ev-rsvp[disabled] { color: var(--bone-cream); }
[data-mz-theme="dark"] .ev-rsvp:focus-visible { outline-color: var(--marrow-ember-text); }

/* Co-host stack. Small overlapping avatars plus a label. */
.ev-cohost { display: inline-flex; align-items: center; gap: 8px; }
.ev-cohost-avatars { display: inline-flex; }
.ev-cohost-avatars > * {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 2px solid var(--bone-white);
  margin-left: -8px;
  object-fit: cover;
  background: color-mix(in srgb, var(--mz-brand-primary) 20%, var(--bone-white));
}
.ev-cohost-avatars > *:first-child { margin-left: 0; }
.ev-cohost-label {
  font: 500 12px var(--sans);
  color: var(--mz-fg-muted, var(--steel));
}

/* ----- Community feed ----- */
.cm-feed { display: flex; flex-direction: column; gap: 16px; }
.cm-post {
  background: var(--mz-bg-card, var(--bone-white));
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 18px 20px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.cm-post:hover { border-color: color-mix(in srgb, var(--mz-brand-primary) 30%, var(--line)); box-shadow: 0 12px 30px rgba(10, 9, 8, 0.07); }
[data-mz-theme="dark"] .cm-post:hover { box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45); }
.cm-post-head { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.cm-post-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  object-fit: cover;
  background: color-mix(in srgb, var(--mz-brand-primary) 20%, var(--bone-white));
  flex: none;
}
.cm-post-author {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 14px;
  color: var(--mz-fg-heading, var(--bone-black));
  line-height: 1.2;
}
.cm-post-time {
  font: 500 11.5px var(--mono);
  letter-spacing: 0.06em;
  color: var(--mz-fg-muted, var(--steel));
}
.cm-post-body {
  font: 400 14.5px var(--sans);
  line-height: 1.6;
  color: var(--mz-fg-body, var(--bone-black));
}
.cm-post-actions { display: flex; gap: 18px; margin-top: 14px; }
.cm-post-action {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  background: transparent;
  border: none;
  cursor: pointer;
  font: 500 12.5px var(--sans);
  color: var(--mz-fg-muted, var(--steel));
  transition: color 0.15s ease;
}
.cm-post-action:hover { color: var(--mz-fg-link, var(--marrow-red)); }
.cm-post-action:focus-visible { outline: 2px solid var(--marrow-red); outline-offset: 3px; border-radius: 3px; }
.cm-post-action[aria-pressed="true"] { color: var(--mz-fg-link, var(--marrow-red)); font-weight: 600; }
[data-mz-theme="dark"] .cm-post-action:focus-visible { outline-color: var(--marrow-ember-text); }

/* Community area tiles (topics, spaces, groups a member can enter). */
.cm-area-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 14px;
}
.cm-area-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 18px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--mz-brand-primary) 7%, var(--bone-white)), var(--bone-white));
  cursor: pointer;
  text-align: left;
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.cm-area-card:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--mz-brand-primary) 45%, var(--line));
  box-shadow: 0 14px 32px rgba(10, 9, 8, 0.08);
}
.cm-area-card:focus-visible { outline: 2px solid var(--marrow-red); outline-offset: 2px; }
[data-mz-theme="dark"] .cm-area-card:focus-visible { outline-color: var(--marrow-ember-text); }
.cm-area-title {
  font-family: var(--display);
  font-weight: 800;
  font-size: 16px;
  letter-spacing: -0.01em;
  color: var(--mz-fg-heading, var(--bone-black));
}
.cm-area-count {
  font: 500 12px var(--mono);
  letter-spacing: 0.08em;
  color: var(--mz-fg-muted, var(--steel));
}

@supports not (background: color-mix(in srgb, red, blue)) {
  .ev-card-media { background: var(--bone-cream); }
  .cm-area-card { background: var(--bone-white); }
}
@media (prefers-reduced-motion: reduce) {
  .ev-card, .ev-rsvp, .cm-post, .cm-post-action, .cm-area-card { transition: none; }
  .ev-card:hover, .ev-rsvp:hover, .cm-area-card:hover { transform: none; }
}

/* =====================================================================
   16. CHECK-IN SECTION  (V3.1 item 10, appended 2026-07-07)
   ---------------------------------------------------------------------
   A shared check-in block reused by both the Training and Nutrition
   surfaces: the section shell, a cadence row, and the delivery-method
   pills (text, virtual, phone, email). Readable-ink tokens throughout so
   it holds on light, dark, and any per-coach primary.
   ===================================================================== */
.checkin-section {
  background: var(--mz-bg-card, var(--bone-white));
  border: 1px solid var(--line);
  border-radius: 14px;
  /* 2026-08-04: widened from 20px 22px to the shared box inset so the copy
     never crowds the card edge. Falls back to the same numbers when the
     foundation tokens are not loaded. */
  padding: var(--mz-pad-y, 24px) var(--mz-pad-x, 28px);
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.checkin-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.checkin-section-title {
  font-family: var(--display);
  font-weight: 800;
  /* 2026-08-04: 17px to 18px so the check-in block titles carry the card
     the way Edwin asked the check-ins area to pop. */
  font-size: 18px;
  letter-spacing: -0.01em;
  color: var(--mz-fg-heading, var(--bone-black));
}
.checkin-section-note {
  font: 400 13px var(--sans);
  color: var(--mz-fg-muted, var(--steel));
}

.checkin-cadence {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.checkin-cadence-option {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: color-mix(in srgb, var(--bone-black) 3%, var(--bone-white));
  color: var(--mz-fg-body, var(--bone-black));
  font: 600 12.5px var(--sans);
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}
.checkin-cadence-option:hover { border-color: var(--bone-black); transform: translateY(-1px); }
.checkin-cadence-option:focus-visible { outline: 2px solid var(--marrow-red); outline-offset: 2px; }
.checkin-cadence-option[aria-pressed="true"],
.checkin-cadence-option.is-selected {
  background: var(--mz-brand-primary);
  border-color: var(--mz-brand-primary);
  color: var(--mz-brand-on-primary);
}
[data-mz-theme="dark"] .checkin-cadence-option:focus-visible { outline-color: var(--marrow-ember-text); }

/* Delivery-method pills. Neutral by default, brand fill when selected, each
   method keeps a small color cue drawn from the functional metric tokens
   so the four read apart at a glance. */
.checkin-methods {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.checkin-method {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bone-white);
  color: var(--mz-fg-body, var(--bone-black));
  font: 600 12.5px var(--sans);
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}
.checkin-method::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--steel);
  flex: none;
}
.checkin-method--text::before    { background: var(--mzv-train); }
.checkin-method--virtual::before { background: var(--mzv-mind); }
.checkin-method--phone::before   { background: var(--mzv-money); }
.checkin-method--email::before   { background: var(--mzv-roster); }
.checkin-method:hover { border-color: var(--bone-black); transform: translateY(-1px); }
.checkin-method:focus-visible { outline: 2px solid var(--marrow-red); outline-offset: 2px; }
.checkin-method[aria-pressed="true"],
.checkin-method.is-selected {
  background: var(--mz-brand-primary);
  border-color: var(--mz-brand-primary);
  color: var(--mz-brand-on-primary);
}
.checkin-method[aria-pressed="true"]::before,
.checkin-method.is-selected::before { background: var(--mz-brand-on-primary); }
[data-mz-theme="dark"] .checkin-method:focus-visible { outline-color: var(--marrow-ember-text); }

@supports not (background: color-mix(in srgb, red, blue)) {
  .checkin-cadence-option { background: var(--bone-white); }
}
@media (prefers-reduced-motion: reduce) {
  .checkin-cadence-option, .checkin-method { transition: border-color 0.15s ease, background 0.15s ease; }
  .checkin-cadence-option:hover, .checkin-method:hover { transform: none; }
}

/* =====================================================================
   17. COACH SURFACE DARK REPAIRS (2026-07-24)
   The marketing footer rule in styles.css keys its background off
   --bone-black, which the dark remap flips to bone. Re-pin dashboard
   footers to the page surface. Same class of repair as the QA nav
   fixes above. No new color values, tokens only.
   ===================================================================== */
:root[data-mz-theme="dark"] footer {
  background: var(--bone-cream);
  color: var(--steel);
}
/* RE-PINNING A GROUND WITHOUT ITS INK IS HALF A REPAIR.
   The rule directly above moved the footer ground to --bone-cream, which is
   #0A0908 under this stamp. .footer-wordmark in css/styles.css keeps
   color: var(--bone-white), and --bone-white is the CARD charcoal #171411 in
   dark, so the brand block rendered its own brand at 1.08 to 1: a footer with
   no wordmark in it on every dark stamped page. --bone-black is the dark
   stamp's primary ink token, #F2EDE3, and measures 17.06 to 1 on this ground.
   Scoped to the dark stamp only, so the light footer keeps bone white on
   #0A0908 at 18.77 to 1 and is not touched. Both directions are measured in
   tests/lamella-connect-steps.test.mjs Part 10, which resolves the cascade
   across every stylesheet a page loads and then holds the CLASS: for the four
   dark stamped Lamella pages, every ink that lands on a ground this file
   re-pins has to still clear its floor there. That guard is scoped to the
   pages it can walk, so it is a floor under this family and not a proof about
   the other dark surfaces. */
:root[data-mz-theme="dark"] footer .footer-wordmark {
  color: var(--bone-black);
}

/* The program builder save bar hardcodes the cream glass the nav repairs
   above already handle. Same black glass value. */
:root[data-mz-theme="dark"] .builder-savebar {
  background: rgba(10, 9, 8, 0.92);
  border-top-color: var(--line);
}

/* Solid marrow red text fails 1.62:1 on black. Ember convention,
   extending the section 3 list to the newer surface classes. */
[data-mz-theme="dark"] .growth-card-label,
[data-mz-theme="dark"] .mg-moves li,
[data-mz-theme="dark"] .mg-card-mode.is-self,
[data-mz-theme="dark"] .mr-comm-eye,
[data-mz-theme="dark"] .mr-chal-err {
  color: var(--marrow-ember-text);
}

/* Red fills keep constant light text in both themes. */
[data-mz-theme="dark"] .mr-comm-btn {
  color: var(--mzv-onfill);
}
[data-mz-theme="dark"] .mr-comm-btn.ghost { color: var(--bone-black); }

/* Steel-light borders flip to a glowing #C3CEDC line on black. Pin the
   migrate surface chrome to the standard dark hairline. */
[data-mz-theme="dark"] .mg-moves li,
[data-mz-theme="dark"] .mg-card,
[data-mz-theme="dark"] .mg-panel,
[data-mz-theme="dark"] .mg-drop,
[data-mz-theme="dark"] .mg-recent-item,
[data-mz-theme="dark"] .mg-all-result {
  border-color: var(--line);
}

/* ---------------------------------------------------------------------
   Grep sweep, checklist step 10. Every remaining red TEXT class that
   renders on a themed coach surface and was not already covered by the
   section 3 list. Each one was checked to sit on a transparent, bone, or
   low alpha red background, never on a solid red fill, so the ember
   convention is the correct repair. Fills, borders, dots, and rails keep
   the solid red. Reused classes live here, one edit for every page.
   --------------------------------------------------------------------- */
[data-mz-theme="dark"] .dash-identity-eyebrow,
[data-mz-theme="dark"] .dash-navmenu-kicker,
[data-mz-theme="dark"] .review-kind,
[data-mz-theme="dark"] .program-card-type,
[data-mz-theme="dark"] .program-card.cta:hover,
[data-mz-theme="dark"] .ex-thumb,
[data-mz-theme="dark"] .tr-flag,
[data-mz-theme="dark"] .qc-method-tag,
[data-mz-theme="dark"] .ach-seeall,
[data-mz-theme="dark"] .mz-tour-eyebrow,
[data-mz-theme="dark"] .week-deload,
[data-mz-theme="dark"] .day-name,
[data-mz-theme="dark"] .drawer-delete,
[data-mz-theme="dark"] .ghost-link,
[data-mz-theme="dark"] .sc-remove:hover:not(:disabled),
[data-mz-theme="dark"] .programs-related-link,
[data-mz-theme="dark"] .btn-danger,
[data-mz-theme="dark"] .split-pane .pane-eyebrow,
[data-mz-theme="dark"] .field .err,
[data-mz-theme="dark"] .hub-card .hub-eyebrow,
[data-mz-theme="dark"] .mg-error,
[data-mz-theme="dark"] .agenda-day-add,
/* Second pass. These live in single line rule bodies, which the first
   grep shape missed and the rendered browser check caught. */
[data-mz-theme="dark"] .tag-chip button:hover,
[data-mz-theme="dark"] details[open] .builder-desc-hint,
[data-mz-theme="dark"] .phase-remove:hover,
[data-mz-theme="dark"] .week-item[aria-current="true"] .week-label,
[data-mz-theme="dark"] .ex-card-icon:hover,
[data-mz-theme="dark"] .icon-btn:hover,
[data-mz-theme="dark"] .canvas-exercise .remove-x:hover,
[data-mz-theme="dark"] .canvas-day-add:hover,
[data-mz-theme="dark"] .canvas-week-add:hover,
[data-mz-theme="dark"] .status-pill[data-state="rejected"],
[data-mz-theme="dark"] .mg-file-status.is-ok,
[data-mz-theme="dark"] .mg-file-remove:focus-visible,
[data-mz-theme="dark"] .mg-help a,
[data-mz-theme="dark"] .dash-checkins-grid .nut-checkin-icon,
[data-mz-theme="dark"] .dash-checkins-grid .nut-checkin-status[data-tone="warn"],
[data-mz-theme="dark"] .dash-checkins-grid .nut-checkin-status[data-tone="error"],
[data-mz-theme="dark"] .sched-banner.attention,
[data-mz-theme="dark"] .agenda-item[data-kind="booking"] .agenda-kind-pill,
[data-mz-theme="dark"] .agenda-item[data-kind="one_on_one_session"] .agenda-kind-pill {
  color: var(--marrow-ember-text);
}

/* One member of that list needs its own rule (added 2026-07-31).
   coach/dashboard/dashboard.css carries .program-card-type.template with a
   two class selector, which ties with [data-mz-theme="dark"]
   .program-card-type above. Until 2026-07-31 the coach dashboard and the
   gym console loaded that surface stylesheet BEFORE this file, so the dark
   remap above won on source order alone. Both pages now load the surface
   stylesheet LAST, per the contract in assets/css/mz-primitives.css, which
   would have handed the template chip back to the flat steel value in
   dark. Restated here at four class-level components so the deliberate
   dark remap holds on specificity instead of on load order. Same value,
   dark only, light theme untouched. */
:root[data-mz-theme="dark"] .program-card-type.template {
  color: var(--marrow-ember-text);
}

/* Grep sweep, checklist step 11. Text sitting on a SOLID marrow red fill.
   Marrow red does not flip with the theme, so bone-white ink flips to the
   card charcoal and lands near black on red. Pin these to the constant
   light ink. Fills keyed to --bone-black are skipped on purpose: that
   token flips, so those buttons self correct. */
[data-mz-theme="dark"] .btn-primary,
[data-mz-theme="dark"] .btn-danger:hover,
[data-mz-theme="dark"] .sc-ordinal,
[data-mz-theme="dark"] .program-status[data-state="published"],
[data-mz-theme="dark"] .status-pill[data-state="live"],
[data-mz-theme="dark"] .mr-comm-tab[aria-selected="true"],
[data-mz-theme="dark"] .mr-comm-chip[aria-pressed="true"] {
  color: var(--mzv-onfill);
}

/* The paid pill hardcodes a deep green that scores 3.64:1 on the dark card.
   Repointed 2026-07-31 onto the shared good token, so the quick check pill
   and the invoice pill resolve to one value instead of two greens. */
[data-mz-theme="dark"] .qc-status-pill[data-state="paid"] { color: var(--mz-status-good); }

/* ---------------------------------------------------------------------
   Form control baseline. Fills the UA-default gap (grey fills in dark)
   on every themed dashboard. Element level, so any surface rule wins.
   --------------------------------------------------------------------- */
/* :where() holds this at specificity zero. The spec's stated rule is that
   the baseline "loses every specificity contest, so already styled controls
   keep their look" and only replaces UA defaults. Written bare, the seven
   :not() clauses score [0,14,1] and do the opposite: they outrank surface
   rules like .mg-field input[type="text"] [0,2,1] and flatten their
   max-width, padding, and fill in BOTH themes. :where() is the literal
   expression of the intent. */
:where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="submit"]):not([type="button"])),
:where(select),
:where(textarea) {
  font: inherit;
  font-size: 14px;
  color: var(--bone-black);
  background: var(--bone-cream);
  border: 1px solid var(--steel-light);
  border-radius: 8px;
  padding: 10px 12px;
  max-width: 100%;
}
input::placeholder,
textarea::placeholder {
  color: var(--mz-fg-placeholder);
  opacity: 1;
}
input:not([type="checkbox"]):not([type="radio"]):focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--marrow-red);
  outline-offset: 1px;
}
:root[data-mz-theme="dark"] input:focus-visible,
:root[data-mz-theme="dark"] select:focus-visible,
:root[data-mz-theme="dark"] textarea:focus-visible {
  outline-color: var(--marrow-ember-text);
}
:root[data-mz-theme="dark"] input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="submit"]):not([type="button"]),
:root[data-mz-theme="dark"] select,
:root[data-mz-theme="dark"] textarea {
  border-color: var(--line);
}


/* ---------------------------------------------------------------------
   Wave 2, review finding C-1. Inverted fill text still live on the high
   traffic surfaces. Marrow red does not flip with the theme, so ink keyed
   to --bone-white lands as the card charcoal on a red fill, measured
   1.49:1. Same repair as the step 11 block above: pin the ink to the
   constant onfill token. Fills, borders, dots, and rails keep solid red.
   --------------------------------------------------------------------- */
[data-mz-theme="dark"] .inbox-unread-total,
[data-mz-theme="dark"] .inbox-row-avatar,
[data-mz-theme="dark"] .inbox-thread-avatar,
[data-mz-theme="dark"] .inbox-row-unread,
[data-mz-theme="dark"] .inbox-bubble-coach,
[data-mz-theme="dark"] .inbox-send-btn,
[data-mz-theme="dark"] .hb-seg input:checked + span,
[data-mz-theme="dark"] .hb-tag-opt input:checked + .hb-tag-body,
[data-mz-theme="dark"] .hb-tag-opt input:checked + .hb-tag-mind,
[data-mz-theme="dark"] .hb-tag-opt input:checked + .hb-tag-spirit,
[data-mz-theme="dark"] .hb-avatar,
[data-mz-theme="dark"] .hb-tab.is-active,
[data-mz-theme="dark"] .cl-filter-tab.is-active,
[data-mz-theme="dark"] .cl-preset-use:hover,
[data-mz-theme="dark"] .cl-avatar,
[data-mz-theme="dark"] .mc-viewtoggle button[aria-pressed="true"],
[data-mz-theme="dark"] .mc-cell.is-today .mc-daynum,
[data-mz-theme="dark"] .mc-copy,
[data-mz-theme="dark"] .roster-cta-primary,
[data-mz-theme="dark"] .ath-avatar,
[data-mz-theme="dark"] .add-ath-submit,
[data-mz-theme="dark"] .invite-cta-primary,
[data-mz-theme="dark"] .mv-pr-flag,
[data-mz-theme="dark"] .mv-iconbtn-play:hover:not(:disabled),
[data-mz-theme="dark"] .mv-voice-btn[data-recording="true"],
[data-mz-theme="dark"] .drawer-stage-btn[aria-checked="true"],
[data-mz-theme="dark"] .cx-btn-primary,
[data-mz-theme="dark"] .mb-btn-primary,
[data-mz-theme="dark"] .mops-pill--needs_attention,
[data-mz-theme="dark"] .wiz-step-dot[data-state="done"],
[data-mz-theme="dark"] .wiz-readiness-pill.is-selected,
[data-mz-theme="dark"] .fp-card[data-latest="true"] .band-pill,
[data-mz-theme="dark"] .severity-pill[data-severity="critical"],
[data-mz-theme="dark"] .imp-stepper-item[data-state="active"] .imp-stepper-dot,
[data-mz-theme="dark"] .imp-warning-list-num,
[data-mz-theme="dark"] .imp-map-required-flag,
[data-mz-theme="dark"] .imp-halt-mark,
[data-mz-theme="dark"] .imp-halt-cta {
  color: var(--mzv-onfill);
}

/* ---------------------------------------------------------------------
   Wave 2, review finding M-2. The section switcher renders as a <button>
   on checkins, crm, analytics, nutrition and gym dashboard, and as an <a>
   on athletes, but no base rule exists for the button form anywhere. Under
   color-scheme: dark the browser paints its own grey button face with
   white ink, which is the grey fill complaint and a templated tell. Reset
   it to the anchor form. Tokens only, both themes, no new values. The
   aria-selected decorations in dashboard.css keep their own specificity.
   --------------------------------------------------------------------- */
button.dash-section-btn {
  appearance: none;
  background: transparent;
  border: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

/* ---------------------------------------------------------------------
   Wave 2, review finding M-1. Third sweep pass. The wave 1 grep shape
   keyed on single property lines and on the seven verified pages, so
   compound selectors, multi line rule bodies, and the themed surfaces
   outside those seven never entered the net. This list is the machine
   checked remainder: every rule across the themed coach surfaces whose
   foreground color resolves to the solid marrow red family, minus every
   selector already remapped anywhere in this file. Solid red scores
   1.62:1 on the dark page. Text and glyph foregrounds move to the ember
   token; fills, borders, dots, and rails are untouched and stay solid red.
   --------------------------------------------------------------------- */
[data-mz-theme="dark"] .mnote-readall,
[data-mz-theme="dark"] .mnote-action,
[data-mz-theme="dark"] .mnote-empty-mark,
[data-mz-theme="dark"] .mnote-toast-action,
[data-mz-theme="dark"] .marrow-welcome-eyebrow,
[data-mz-theme="dark"] .marrow-welcome-steps li a,
[data-mz-theme="dark"] .adash-card-tag[data-state="warn"],
[data-mz-theme="dark"] .adash-card-tag[data-state="down"],
[data-mz-theme="dark"] .adash-denied a,
[data-mz-theme="dark"] .mops-denied a,
[data-mz-theme="dark"] .mops-action-msg--ok,
[data-mz-theme="dark"] .mops-action-msg--err,
[data-mz-theme="dark"] .amod-denied a,
[data-mz-theme="dark"] .amod-eyebrow,
[data-mz-theme="dark"] .astf-denied a,
[data-mz-theme="dark"] .astf-eyebrow,
[data-mz-theme="dark"] .astf-btn.danger,
[data-mz-theme="dark"] .asup-denied a,
[data-mz-theme="dark"] .asup-eyebrow,
[data-mz-theme="dark"] .an-kpi-risk .kpi-value,
[data-mz-theme="dark"] .an-inline-link,
[data-mz-theme="dark"] .an-eyebrow-risk,
[data-mz-theme="dark"] .an-risk-meta,
[data-mz-theme="dark"] .status-pill.at-risk,
[data-mz-theme="dark"] .an-cell-risk,
[data-mz-theme="dark"] .detail-avatar,
[data-mz-theme="dark"] .detail-status-badge[data-status="onboarding"],
[data-mz-theme="dark"] .overview-prs li strong,
[data-mz-theme="dark"] .sess-status[data-status="missed"],
[data-mz-theme="dark"] .review-flag strong,
[data-mz-theme="dark"] .session-exercises .ex-actual.is-pr,
[data-mz-theme="dark"] .session-side .unreviewed,
[data-mz-theme="dark"] .program-block[data-block-status="current"],
[data-mz-theme="dark"] .program-week .status[data-status="missed"],
[data-mz-theme="dark"] .msg-bubble.kind-form_video .form-attach,
[data-mz-theme="dark"] .form-card-pr,
[data-mz-theme="dark"] .nutrition-plan-caution,
[data-mz-theme="dark"] .checkin-chip[data-mood="drained"],
[data-mz-theme="dark"] .attendance-badge[data-tone="missed"],
[data-mz-theme="dark"] .achievement-icon,
[data-mz-theme="dark"] .detail-risk-chip-label,
[data-mz-theme="dark"] .sg-swap,
[data-mz-theme="dark"] .mv-iconbtn-danger:hover,
[data-mz-theme="dark"] .mv-fallback .mono,
[data-mz-theme="dark"] .mv-item-stamp,
[data-mz-theme="dark"] .mv-item-del:hover,
[data-mz-theme="dark"] .mv-warning,
[data-mz-theme="dark"] .status-pill.churned,
[data-mz-theme="dark"] .add-ath-error,
[data-mz-theme="dark"] .af-note-band > svg,
[data-mz-theme="dark"] .af-held-flag svg,
[data-mz-theme="dark"] .af-held-pill,
[data-mz-theme="dark"] .af-icon-btn:hover,
[data-mz-theme="dark"] .af-outcome-pill[data-outcome="queued_held"],
[data-mz-theme="dark"] .af-outcome-pill[data-outcome="test"],
[data-mz-theme="dark"] .ck-status-chip[data-status="submitted"],
[data-mz-theme="dark"] .ck-reply-status,
[data-mz-theme="dark"] .kb-col-title,
[data-mz-theme="dark"] .drawer-section-h,
[data-mz-theme="dark"] .drawer-files li .file-kind,
[data-mz-theme="dark"] .bc-count[data-warn="true"],
[data-mz-theme="dark"] .bc-recent-kind.email,
[data-mz-theme="dark"] .bc-recent-status[data-status="scheduled"],
[data-mz-theme="dark"] .pg-row-url:hover,
[data-mz-theme="dark"] .pg-template-best,
[data-mz-theme="dark"] .tk-meta[data-overdue="true"],
[data-mz-theme="dark"] .tk-delete:hover,
[data-mz-theme="dark"] .rt-group[data-tier="lapsing"] .rt-group-title,
[data-mz-theme="dark"] .rt-client-days[data-tier="lapsing"],
[data-mz-theme="dark"] .cx-nav-help:hover,
[data-mz-theme="dark"] .cx-btn-text:hover,
[data-mz-theme="dark"] .imp-tier-price,
[data-mz-theme="dark"] .imp-tier-status-hold,
[data-mz-theme="dark"] .imp-warning-eyebrow,
[data-mz-theme="dark"] .imp-file-mark[data-kind="clients"],
[data-mz-theme="dark"] .imp-file-mark[data-kind="unknown"],
[data-mz-theme="dark"] .imp-file-sub[data-flag="warn"],
[data-mz-theme="dark"] .imp-file-remove:hover,
[data-mz-theme="dark"] .imp-file-remove:focus-visible,
[data-mz-theme="dark"] .imp-conf-pill[data-conf="low"],
[data-mz-theme="dark"] .imp-row-issue,
[data-mz-theme="dark"] .imp-error-list strong,
[data-mz-theme="dark"] .imp-summary-stat-num,
[data-mz-theme="dark"] .imp-rebill-msg,
[data-mz-theme="dark"] .imp-draft-status[data-state="saving"],
[data-mz-theme="dark"] .ghost-link:hover,
[data-mz-theme="dark"] .ghost-link:focus-visible,
[data-mz-theme="dark"] .mig-card-status[data-status="ready"],
[data-mz-theme="dark"] .mig-help-eyebrow,
[data-mz-theme="dark"] .imp-file-mark[data-kind="csv"],
[data-mz-theme="dark"] .field-hint[data-state="error"],
[data-mz-theme="dark"] .form-banner,
[data-mz-theme="dark"] .save-status[data-state="saving"],
[data-mz-theme="dark"] .save-status[data-state="error"],
[data-mz-theme="dark"] .profile-preview-link a,
[data-mz-theme="dark"] .profile-preview-link a:hover,
[data-mz-theme="dark"] .fp-card[data-latest="true"]::before,
[data-mz-theme="dark"] .severity-flag,
[data-mz-theme="dark"] .fp-compare-label,
[data-mz-theme="dark"] .fp-card-athlete,
[data-mz-theme="dark"] .cf-legal-note > svg,
[data-mz-theme="dark"] .cf-review-pill[data-state="needs"],
[data-mz-theme="dark"] .marrow-founder-badge__eyebrow,
[data-mz-theme="dark"] .gr-chip[data-tone="red"],
[data-mz-theme="dark"] .gr-form-status,
[data-mz-theme="dark"] .gr-remove-btn:hover,
[data-mz-theme="dark"] .gr-remove-btn:focus-visible,
[data-mz-theme="dark"] .gr-session-when,
[data-mz-theme="dark"] .gr-board-row[data-top="true"] .gr-board-rank,
[data-mz-theme="dark"] .hb-preview-chip,
[data-mz-theme="dark"] .hb-icon-btn:hover,
[data-mz-theme="dark"] .cl-preset-use,
[data-mz-theme="dark"] .iv-hold-note > svg,
[data-mz-theme="dark"] .iv-line-remove:hover:not([disabled]),
[data-mz-theme="dark"] .iv-row-open:hover,
[data-mz-theme="dark"] .mb-eyebrow,
[data-mz-theme="dark"] .mb-signin a,
[data-mz-theme="dark"] .mb-help a,
[data-mz-theme="dark"] .mb-error,
[data-mz-theme="dark"] .mb-card-price,
[data-mz-theme="dark"] .mg-file-remove:hover,
[data-mz-theme="dark"] .mig-job-eyebrow,
[data-mz-theme="dark"] .mig-item-kind,
[data-mz-theme="dark"] .adh-data > summary,
[data-mz-theme="dark"] .plan-card-new:hover,
[data-mz-theme="dark"] .nut-checksum[data-tone="warn"] .nut-checksum-body,
[data-mz-theme="dark"] .nut-generator > summary::before,
[data-mz-theme="dark"] .nut-checkin-ref-link:hover,
[data-mz-theme="dark"] .nut-checkin-status[data-tone="error"],
[data-mz-theme="dark"] .mp-targets-eyebrow,
[data-mz-theme="dark"] .mp-targets-warn-link,
[data-mz-theme="dark"] .pe-eyebrow,
[data-mz-theme="dark"] .pe-status.err,
[data-mz-theme="dark"] .wiz-eyebrow,
[data-mz-theme="dark"] .wiz-secondary-card.is-selected,
[data-mz-theme="dark"] .wiz-program-tag,
[data-mz-theme="dark"] .wiz-form-error,
[data-mz-theme="dark"] .wiz-cohort-action,
[data-mz-theme="dark"] .wiz-roadmap h2,
[data-mz-theme="dark"] .od-badge[data-tone="attention"],
[data-mz-theme="dark"] .od-quality-mark[data-state="fail"],
[data-mz-theme="dark"] .od-banner.attention,
[data-mz-theme="dark"] .od-panel .panel-eyebrow,
[data-mz-theme="dark"] .cp-chip-paid,
[data-mz-theme="dark"] .cp-cancel-btn:hover,
[data-mz-theme="dark"] .cp-service-remove:hover:not([disabled]),
[data-mz-theme="dark"] .perm-eyebrow,
[data-mz-theme="dark"] .perm-status.is-ok,
[data-mz-theme="dark"] .perm-status.is-err,
[data-mz-theme="dark"] .perm-toggle-default.is-overridden,
[data-mz-theme="dark"] .cp-frame-tag,
[data-mz-theme="dark"] .mx-table-toggle,
[data-mz-theme="dark"] .ret-pill[data-tone="red"],
[data-mz-theme="dark"] .ret-onb-first.missing,
[data-mz-theme="dark"] .ret-ttfv[data-hit="false"],
[data-mz-theme="dark"] .ret-onb-status[data-status="stalled"],
[data-mz-theme="dark"] .ret2-kpi-foot.down,
[data-mz-theme="dark"] .ret2-kpi-link:hover,
[data-mz-theme="dark"] .ret2-reason-rank,
[data-mz-theme="dark"] .ret2-atrisk-signal,
[data-mz-theme="dark"] .mc-more,
[data-mz-theme="dark"] .st-status-row a,
[data-mz-theme="dark"] .st-pill-attention,
[data-mz-theme="dark"] .st-empty a,
[data-mz-theme="dark"] .st-preview-chip,
[data-mz-theme="dark"] .st-hex-err,
[data-mz-theme="dark"] .st-pct-sign,
[data-mz-theme="dark"] .st-plan-custom .st-plan-num,
[data-mz-theme="dark"] .tl-status a,
[data-mz-theme="dark"] .tl-card-style,
[data-mz-theme="dark"] .tl-block-name,
[data-mz-theme="dark"] .tr-kicker,
[data-mz-theme="dark"] .tr-summary-num,
[data-mz-theme="dark"] .type-badge[data-type="follow_along"],
[data-mz-theme="dark"] .cw-preview-chip,
[data-mz-theme="dark"] .cw-pill.ok {
  color: var(--marrow-ember-text);
}

/* Same C-1 class, two hits the review inventory listed but did not carry
   into its fix list. Both sit on a fill that does not flip with the theme:
   the staff primary button on solid marrow red, and the video duration
   badge on a fixed dark overlay. */
[data-mz-theme="dark"] .astf-btn.primary,
[data-mz-theme="dark"] .vid-thumb-duration,
/* Browser pass additions. The ink on these comes from a base rule keyed to a
   bone token, while a later rule swaps the fill to a value that does not flip.
   The importer howto marker lands near black on solid red at 1.49:1.
   .plan-card-action.primary:hover left this list on 2026-08-04: its hover
   fill is var(--ash), which the dark remap now turns light, so the base
   rule's flipped dark ink is the correct pairing and the onfill pin here
   would have put light ink on a light fill. */
[data-mz-theme="dark"] .imp-howto-card-meta .imp-howto-num {
  color: var(--mzv-onfill);
}

/* ---------------------------------------------------------------------
   Wave 3, sign-off findings F-1, F-3, F-4 and F-6. The last defects live
   in chrome the themed coach pages pull from css/styles.css and
   community/community.css, two files no earlier sweep entered because
   both sweeps scoped their net to the coach/ directory. A sweep's net is
   defined by what the PAGES load, never by directory.

   Every rule below is dark scoped, so the public marketing pages that
   share css/styles.css and never carry data-mz-theme stay exactly as
   they are. No new color values: tokens and the established dark
   conventions only.
   --------------------------------------------------------------------- */

/* F-1. The marketing nav band is a literal cream glass that does not flip
   with the theme, so its ink lands light on light at 1.44:1 on the first
   page a new coach sees. Same black glass family as the .dash-nav and
   builder save bar repairs above. */
:root[data-mz-theme="dark"] .nav {
  background: rgba(10, 9, 8, 0.85);
}

/* F-1. Solid marrow red does not flip, so ink keyed to a bone token lands
   as the card charcoal on the red fill. Constant light ink instead. */
[data-mz-theme="dark"] .nav-cta {
  color: var(--mzv-onfill);
}

/* F-1. The orb hero is a fixed dark band. Its ink is keyed to bone tokens
   that flip, which takes the headline to 1.08:1 and the lede to 1.00:1,
   both effectively invisible. Constant light ink on a fixed dark fill,
   the same rule the rest of this section applies. */
[data-mz-theme="dark"] .onb-orb-hero .onb-eyebrow,
[data-mz-theme="dark"] .onb-orb-hero .onb-title,
[data-mz-theme="dark"] .onb-orb-hero .onb-lede,
[data-mz-theme="dark"] .onb-orb-hero .onb-lede strong {
  color: var(--mzv-onfill);
}

/* F-1 and F-3. Solid marrow red as text on a dark card, 1.49:1 on the
   onboarding form and 1.62:1 on the progress hero. Ember convention,
   extending the section 3 list to the shared marketing classes. */
[data-mz-theme="dark"] .apply-form-eyebrow,
[data-mz-theme="dark"] .consent-label a,
[data-mz-theme="dark"] .page-hero-eyebrow {
  color: var(--marrow-ember-text);
}

/* F-4. The community nav band is a second literal cream glass, this one
   on the themed event pages, measured 1.07:1. Same repair as the
   marketing band above. */
:root[data-mz-theme="dark"] .com-nav {
  background: rgba(10, 9, 8, 0.92);
  border-bottom-color: var(--line);
}
[data-mz-theme="dark"] .com-nav-brand-text small {
  color: var(--marrow-ember-text);
}

/* F-4. Both button states swap to a solid red fill that does not flip,
   while the ink stays keyed to a bone token. */
[data-mz-theme="dark"] .com-btn.primary:hover,
[data-mz-theme="dark"] .com-btn.red {
  color: var(--mzv-onfill);
}

/* F-6. Brand preview eyebrow: the brand primary rendered as TEXT on the
   dark settings card, 1.49:1. The token itself stays untouched because it
   drives fills, the color-mix tint recipes above, achievement accents and
   two JS readers, and the theme engine deepens saved picks so they read
   on LIGHT surfaces. Mixing toward the constant light ink keeps whatever
   hue a coach saved and always clears AA on the dark card. color-mix on
   the brand token is this file's established idiom, see section 13. */
[data-mz-theme="dark"] .st-preview-eyebrow {
  color: color-mix(in srgb, var(--mz-brand-primary) 40%, var(--mzv-onfill));
}

/* ---------------------------------------------------------------------
   Wave 4, sign-off finding. The post payment welcome hero on
   /coach/onboarded/ is the last rule on a coach surface that keys its
   own FILL off --bone-black, exactly the defect family the marketing
   footer repair at the head of this section fixed. The dark remap turns
   --bone-black into bone, so the hero card flips to cream while every
   ink inside it stays keyed to dark theme values: the ember eyebrow and
   welcome name land at 2.22:1 and the two subtitle lines at 1.37:1.

   Repin the card to the dark card surface token so it stays a raised
   dark block, and give it the constant light ink every fixed dark fill
   in this file already uses. The two strong rules inside the card key
   off --bone-white, which the remap turns into that same card charcoal,
   so they move to the constant light ink with it. Dark scoped, tokens
   only, no new values, light theme untouched.
   --------------------------------------------------------------------- */
[data-mz-theme="dark"] .ob-status-card {
  background: var(--bone-white);
  color: var(--mzv-onfill);
}
[data-mz-theme="dark"] .ob-subtitle strong,
[data-mz-theme="dark"] .ob-pending-banner strong {
  color: var(--mzv-onfill);
}

/* =====================================================================
   18. DASHBOARD FOUNDATION APPENDIX  (Lane 1, appended 2026-07-30)
   ---------------------------------------------------------------------
   Four token families the dashboards were missing: a productive type
   scale, an accent independent status layer, spacing and radius half
   steps, and dashboard scoped motion. Plus the dark raised surface
   ladder and the dark side of the light --mz-bg-card fix above.

   Nothing here renames, reorders, or redefines an existing --mz-* token
   except the single documented --mz-bg-card light value in section 11.
   No new brand color or font token is introduced. Every hex below
   already exists in this repo, cited per line. Consumed by
   /assets/css/mz-primitives.css, which loads after this file.
   ===================================================================== */

/* ---------------------------------------------------------------------
   18.1  TYPE SCALE
   Productive scale, tight steps in the 12px to 20px working band, wider
   above. Switzer (--display) sets the page title and large metrics,
   Inter (--sans) is the UI font and never renders above 24px here,
   JetBrains Mono (--mono) sets labels, eyebrows and timestamps.
   Nothing renders below 12px. Sizes only, no color identity.
   --------------------------------------------------------------------- */
:root {
  --fs-page:      1.75rem;  /* 28px, page title, Switzer 700, lh 1.15, tracking -0.02em */
  --fs-section:   1.25rem;  /* 20px, section header, Inter 600, lh 1.30, tracking -0.01em */
  --fs-card:      1rem;     /* 16px, card title, Inter 600, lh 1.375, tracking 0 */
  --fs-metric-xl: 2.5rem;   /* 40px, hero metric, Switzer 600, lh 1.05, tracking -0.025em */
  --fs-metric:    2rem;     /* 32px, card metric, Switzer 600, lh 1.10, tracking -0.02em */
  --fs-metric-sm: 1.25rem;  /* 20px, inline or table metric, Inter 600, lh 1.20, tracking -0.01em */
  --fs-body:      0.875rem; /* 14px, body and prose in panels, Inter 400, lh 1.50 */
  --fs-td:        0.875rem; /* 14px, table cell, Inter 400, lh 1.43 */
  --fs-th:        0.75rem;  /* 12px, table header, Inter 600, uppercase, tracking 0.04em */
  --fs-label:     0.75rem;  /* 12px, metric label and eyebrow, mono 500, uppercase, tracking 0.08em */
  --fs-help:      0.75rem;  /* 12px, helper and caption, Inter 400, sentence case */
}

/* ---------------------------------------------------------------------
   18.2  SEMANTIC STATUS LAYER, ACCENT INDEPENDENT
   Two non overlapping color jobs. Identity and interaction belong to the
   brand layer alone (--mz-accent, --mz-brand-*). State belongs here and
   NEVER references the brand layer, so a gym whose theme-engine brand is
   green cannot make brand read as success. theme-engine.js never writes
   these names.

   Functional tokens, not new brand colors, the same precedent as
   --marrow-ember-text in styles.css. Every hex is reused:
     #0C632C  = --r2-cat-nutrition light   (styles.css:4990)
     #9E2B18  = --r2-cat-training light    (styles.css:4988)
     #6D28D9  = --mzv-spirit light         (this file, section 1)
     #4E4942  = --r2-muted-color light     (styles.css:4970)
     #3ECF74  = --r2-cat-nutrition dark    (styles.css:5009)
     #E08894  = the retired ember red, KEPT here because risk is meaning
     #B79CFF  = --mzv-spirit dark          (this file, section 2)
     #C9C2B4  = --r2-muted-color dark      (styles.css:5003)
   Info moved off #475569 / #B8C5D6 on 2026-07-31. Those were --steel and
   --steel-light, which are also --mz-fg-muted, so info and muted were the
   same ink. See the note on the token itself.

   Rules: there is no warning amber, a caution renders neutral plus glyph
   plus words. Never color alone, every status ships color plus glyph plus
   word. --marrow-red is never a status color and never text on dark.
   Trend direction is not a status.
   --------------------------------------------------------------------- */
:root {
  --mz-status-good:         #0C632C;
  --mz-status-good-soft:    rgba(12, 99, 44, 0.10);
  --mz-status-risk:         #9E2B18;
  --mz-status-risk-soft:    rgba(158, 43, 24, 0.10);
  /* 2026-07-31. Was #475569, byte identical to --steel and therefore to
     --mz-fg-muted, so an info chip and a muted label were the same hue in
     light and only their words told them apart. Now --mzv-spirit
     (dashboard-theme.css:34), an existing value, no new hue family.
     #6D28D9 on the light card 6.70:1, on the light page 5.87:1, both AA.
     CIEDE2000 from muted #475569 is 18.38, from good 50.78, risk 42.10,
     neutral 36.63. */
  --mz-status-info:         #6D28D9;
  --mz-status-info-soft:    rgba(109, 40, 217, 0.10);
  --mz-status-neutral:      #4E4942;
  --mz-status-neutral-soft: rgba(78, 73, 66, 0.10);
}
:root[data-mz-theme="dark"] {
  --mz-status-good:         #3ECF74;
  --mz-status-good-soft:    rgba(62, 207, 116, 0.15);
  --mz-status-risk:         #E08894;
  --mz-status-risk-soft:    rgba(224, 136, 148, 0.15);
  /* Dark follows light onto the spirit pair, --mzv-spirit dark
     (dashboard-theme.css:66). The old #B8C5D6 scored CIEDE2000 4.47 against
     dark muted #A8B4C4, a just noticeable difference, so dark carried a
     quieter version of the same collision. #B79CFF is 8.05:1 on the dark
     card, 8.73:1 on the dark page, and 12.92 from dark muted. */
  --mz-status-info:         #B79CFF;
  --mz-status-info-soft:    rgba(183, 156, 255, 0.15);
  --mz-status-neutral:      #C9C2B4;
  --mz-status-neutral-soft: rgba(201, 194, 180, 0.15);
}

/* ---------------------------------------------------------------------
   18.3  SPACING AND RADIUS
   --space-1 through --space-8 in styles.css are untouched. Two half
   steps are added for optical corrections only. Everything vertical
   resolves to a multiple of 4, the only 2px values are optical.
   Exactly two radius values exist on these surfaces. Avatars and the
   brand mark are the only circles.
   --------------------------------------------------------------------- */
:root {
  --space-025: 2px;
  --space-05:  4px;
  --radius-sm: 4px;   /* inputs, pills, chips, chart elements */
  --radius-md: 10px;  /* cards, panels, modals */
}

/* ---------------------------------------------------------------------
   18.4  MOTION, DASHBOARD SCOPED
   The THEME-CONTRACT motion tokens live in css/marrow-theme.css, which no
   dashboard loads. These are the dashboard scoped equivalents, taking the
   exact values that contract documents so web and app stay one language.
   Only the fast three durations and three easings exist. The slow pair
   and the spring are deliberately NOT defined here so they cannot be
   misused. Nothing above 240ms, no overshoot.

   Reduced motion is already handled site wide in this file, in the
   surface CSS, and in styles.css. The block below collapses these new
   tokens in step with those, it does not duplicate them.
   --------------------------------------------------------------------- */
:root {
  --mz-dur-instant: 80ms;
  --mz-dur-fast:    160ms;
  --mz-dur-base:    240ms;
  --mz-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --mz-ease-out:      cubic-bezier(0.16, 1, 0.3, 1);
  --mz-ease-in:       cubic-bezier(0.7, 0, 0.84, 0);
}
@media (prefers-reduced-motion: reduce) {
  :root {
    --mz-dur-instant: 0.01ms;
    --mz-dur-fast:    0.01ms;
    --mz-dur-base:    0.01ms;
  }
}

/* ---------------------------------------------------------------------
   18.5  SURFACE LADDER
   Light mode resting card is --mz-bg-card with a 1px --line border and no
   shadow, so every raised level resolves to the same card surface. Dark
   mode bans shadows outright, depth comes from this ladder plus the
   hairline. Both dark levels are computed with color-mix from the two
   canonical bone values already in this file, so no new hex is created.

   Dark --mz-bg-card is restored here to the card charcoal it has always
   had. Section 11 above now pins the LIGHT value to a literal, which
   would otherwise have leaked the light card into the dark theme.
   --------------------------------------------------------------------- */
:root {
  --mz-bg-raised:  var(--mz-bg-card);
  --mz-bg-overlay: var(--mz-bg-card);
  --mz-bg-modal:   var(--mz-bg-card);
}
:root[data-mz-theme="dark"] {
  --mz-bg-card:    var(--bone-white);                          /* #171411 under the dark remap */
  --mz-bg-raised:  color-mix(in srgb, #EFE9DC 7%,  #0A0908);   /* hover, sticky header */
  --mz-bg-overlay: color-mix(in srgb, #EFE9DC 10%, #0A0908);   /* dropdown, popover */
  --mz-bg-modal:   color-mix(in srgb, #EFE9DC 14%, #0A0908);   /* modal, drawer */
}
@supports not (background: color-mix(in srgb, red, blue)) {
  :root[data-mz-theme="dark"] {
    --mz-bg-raised:  var(--bone-white);
    --mz-bg-overlay: var(--bone-white);
    /* Was var(--ash). The 2026-08-04 dark remap turns ash into a light ink,
       so a modal keyed to it would flash light in dark on the no color-mix
       browsers. The card token is the correct dark surface here. */
    --mz-bg-modal:   var(--bone-white);
  }
}

/* =====================================================================
   19. PREMIUM POLISH WAVE (2026-08-04)
   ---------------------------------------------------------------------
   Edwin's voice note remediation, fixed at the source so every surface
   corrects at once. Four repairs:
     19.1 corner tags get a chip inset so small mono type never sits
          flush in a card corner
     19.2 the disabled dashboard primary button (the Save cadence
          "white and blue" complaint) becomes a full opacity, AA passing
          state in both themes
     19.3 the nutrition legal footer's hardcoded 60 percent black ink,
          invisible on the dark card, moves to the muted role token
     19.4 nothing else: the "too gray" body ink family is fixed by the
          --ash dark remap in section 2, not here
   Tokens only, no new color values.
   ===================================================================== */

/* 19.1 CORNER TAGS. .card-meta inside a .card-head is the small
   uppercase count and quiet state tag ("None today", "Quiet week",
   "Empty"). Two class specificity beats the surface's one class rule
   regardless of load order, so this corrects every dashboard at once.
   --line as a soft chip fill flips with the theme. Contrast held: the
   tag ink is --steel, 5.9:1 or better on the tinted chip in light,
   8.4:1 or better in dark. */
.card-head .card-meta {
  padding: 5px 12px;
  background: var(--line);
  border-radius: 999px;
  line-height: 1.4;
  white-space: nowrap;
}

/* 19.2 DISABLED PRIMARY BUTTON. The base rule paints a disabled
   .btn-primary-dash steel blue with bone ink, and the nutrition surface
   fades the whole button to 0.55 opacity, which lands the Save cadence
   label at 2.55:1 in light and 1.69:1 in dark. Element plus class plus
   pseudo class outranks the surface's .nut-checkin-save:disabled, so the
   fade is retired everywhere. After: 7.14:1 in light (bone ink on
   steel), 9.47:1 in dark (page ink on the flipped light steel). */
button.btn-primary-dash[disabled],
button.btn-primary-dash:disabled,
.btn-primary-dash[disabled] {
  background: var(--steel);
  color: var(--mzv-onfill);
  opacity: 1;
  box-shadow: none;
  cursor: not-allowed;
}
:root[data-mz-theme="dark"] button.btn-primary-dash[disabled],
:root[data-mz-theme="dark"] button.btn-primary-dash:disabled,
/* The bare class was missing from this group while it WAS present in the base
   group three rules above, so a disabled .btn-primary-dash on anything that is
   not a <button> kept the constant light ink on the dark flipped --steel fill
   and measured 1.98:1. Nothing renders it today, every disabled use in the
   repo is a real button, so it was a latent gap rather than a live break.
   Found by tests/theme-ink-ground-guard.test.mjs once brand-kit.js started
   loading on the dark stamped auth pages. */
:root[data-mz-theme="dark"] .btn-primary-dash[disabled] {
  /* Dark flips --steel to #A8B4C4, a light fill, so the constant light
     ink would vanish. --bone-cream is the page black under the remap. */
  color: var(--bone-cream);
}

/* 19.3 LEGAL FOOTER INK. rgba(10, 9, 8, 0.6) is a literal that cannot
   flip, measured 1.02:1 on the dark card. The muted role token reads
   8.73:1 on the dark card per the section 11 table. Dark scoped, the
   light theme keeps its current look. */
[data-mz-theme="dark"] .nut-legal-footer {
  color: var(--mz-fg-muted);
  border-top-color: var(--line);
}

/* =====================================================================
   MZ SURFACE HEADER + PREMIUM CARD SCALE, DARK THEME (2026-08-05)
   Companion to the two blocks of the same names at the tail of
   /css/styles.css. The header itself needs no dark rules: every ink it
   uses is a remapped token (bone-black, steel, steel-light, bone-white,
   line) and the orb strokes already flip via the .logo-svg rules in
   section 3 of this file. What does need remapping are the premium
   card's elevation, sheen, and glow layers, which ride custom
   properties precisely so this block can restyle them without touching
   the !important declarations in styles.css. Values are low-alpha
   overlays of existing token values only, per the rgba convention used
   throughout this file.
   ===================================================================== */
:root[data-mz-theme="dark"] {
  /* Dark elevation: an inset top hairline of bone light catches the
     card edge like a machined bevel, and the ambient goes deeper. */
  --mz-card-shadow: inset 0 1px 0 rgba(242, 237, 227, 0.05), 0 18px 44px rgba(0, 0, 0, 0.55);
  /* Faint top-down bone sheen so the card reads as a lit surface, not
     a flat cutout, on the black page. 4 percent at the top edge. */
  --mz-card-sheen: linear-gradient(180deg, rgba(242, 237, 227, 0.04), rgba(242, 237, 227, 0) 55%);
  /* The brand glow wash on the premium card. It reads --mz-orb-soft rather
     than a literal, so it is drawn at the alpha brand-kit.js PROVED for this
     theme and this coach's accent rather than at a number chosen for the
     Marrow default. Dark solves to 0.20, which is a touch stronger than the
     0.16 literal it replaces, and the composite #1D2523 carries body ink at
     13.42:1. The light half of this pair is in css/styles.css and it moved
     from a literal 0.05 to the same token: that lift is most of what made the
     light card look empty. The light value moved again on 2026-09-05, from
     0.133 to 0.17, because the first pass equalised ink headroom rather than
     colour and left the light card at 83.6 percent of dark's chroma. It reads
     99.9 percent now, composite #D5E1E1, body ink 14.83:1. */
  --mz-card-glow-fill: radial-gradient(120% 60% at 50% 0%, rgba(var(--mz-orb-rgb), var(--mz-orb-soft)), transparent 60%);
}
/* Accent micro label follows the ember convention: solid marrow red
   text fails on the dark card, ember clears 7.09:1 there. !important
   matches the base declaration in styles.css; this selector's higher
   specificity settles the cascade between the two importants. */
[data-mz-theme="dark"] .mz-card-micro-accent { color: var(--marrow-ember-text) !important; }


/* =========================================================================
   RED AS TYPE, ON A DARK GROUND.

   .section-eyebrow in css/styles.css is coloured with solid --marrow-red.
   That is 5.07:1 on bone cream and correct on every light marketing page.
   On the dark ground it is 3.25:1, under the 4.5 floor, which is the
   contrast defect that has already bitten this build three times in other
   forms.

   THE TWO NUMBERS ABOVE MOVED ON 2026-09-04 AND THE RULE DID NOT. They read
   10.16 and 1.62 when --marrow-red was #6B0F1A. It carries #356A6B now, so
   the light reading is 5.07 and the dark reading is 3.25. Both are the same
   verdict: correct on light, under the floor on dark. A brighter accent did
   not make accent-as-type-on-dark safe, it only made it less obviously
   broken, which is exactly the shape this override exists to stop.

   --marrow-red is NOT remapped by the theme, and must not be: it is a fill,
   a rule, a dot and a glow, and all of those are correct on both grounds.
   Only its use AS TYPE is wrong on dark, so only that is overridden here,
   and only under the dark stamp. Light pages are untouched.

   --marrow-ember-text is the shipped accessibility token for exactly this.
   It followed the accent off red on the same day, #E08894 to #5FAEB0, and
   css/styles.css states the current readings at the token: 7.74:1 on
   #0A0908 and 7.14:1 on #171411.

   Found by browser QA, not by a stylesheet sweep. The sweep only searched
   the Lamella stylesheets and this class lives in the shared one.
   ========================================================================= */
:root[data-mz-theme="dark"] .section-eyebrow {
  color: var(--marrow-ember-text);
}



/* =====================================================================
   20. THE COACH SURFACE  (2026-09-04)

   Edwin's ask, verbatim: "we need the ability to change the color of the
   entire background allowing the orbs to match the colors they put. we need
   the buttons in the dashboard to look more vibrant where they can pick the
   colors to be whatever they want. everything can match there branding if
   they want. this can be done at the top part as well."

   Three things live in this section. The orbs are up in section 3 where they
   already were. Here are the VIBRANT ACTIONS and THE TOP.

   COLOUR PSYCHOLOGY, and it is a working constraint rather than a note.
   The brand colour is for ACTION and IDENTITY only, never for information. A
   dashboard where six things are brand coloured has told the coach nothing
   about which one to touch, which is exactly the failure this pass is fixing:
   a real coach opened this screen and could not work out what to do. So the
   loud colour is rationed to the thing you are meant to press, the calm
   ground carries the work, and the functional metric tokens keep their fixed
   meanings (green reads progress, blue reads people, gold reads money). A
   state that is merely true still earns no colour at all, which is the
   discipline section 3 already set for the status pills and which is what
   keeps a red pill meaning something.

   WHAT IS DELIBERATELY NOT BRANDED. The red tinted risk chips at
   .status-pill.silent and .status-pill.uncollectible keep a literal red. Risk
   is not brand. A coach whose colour is green would otherwise get a green
   "uncollectible" chip, and the colour would be actively lying.

   EVERY COLOUR BELOW IS A TOKEN WITH A DEFAULT EQUAL TO TODAY'S VALUE, so an
   unbranded dashboard renders as it did before this section existed.
   ===================================================================== */

/* ---- 20.1 VIBRANT PRIMARY ACTION -------------------------------------
   The primary button was a flat slab of one colour. It is a two stop gradient
   now, from the coach fill to a brighter stop of the same hue, which is what
   gives it dimension without giving it decoration.

   BOTH STOPS ARE PROVEN, not just the fill. The label sits across the whole
   gradient, so brand-kit.js holds --mz-brand-vivid to AA text (4.5:1) against
   the same label colour the fill is held to, and walks the bright stop back
   toward the fill until it passes. A gradient checked at one end is a gradient
   that fails at the other.

   AA TEXT, NOT AA LARGE. This label renders at 12px uppercase. However loud
   it looks, WCAG counts it as normal text, so 3:1 would be the wrong floor.

   SPECIFICITY. coach/dashboard/dashboard.css loads after this file and sets
   .btn-primary-dash background at one class. The selector below carries five
   components so it settles on specificity rather than on load order, which is
   the same correction section 19 of this file already made for four other
   declarations. */
:root[data-mz-theme] body.dash .dash-main .btn-primary-dash,
:root[data-mz-theme] body.dash .dash-main a.btn-primary-dash {
  background-color: var(--marrow-red);
  background-image: linear-gradient(
    135deg,
    var(--marrow-red) 0%,
    var(--mz-brand-vivid, var(--marrow-red-glow, #376F70)) 100%
  );
  color: var(--mz-fg-on-accent, var(--mzv-onfill, #FBF8F2));
  border-radius: 6px;
}
:root[data-mz-theme] body.dash .dash-main .btn-primary-dash:hover,
:root[data-mz-theme] body.dash .dash-main a.btn-primary-dash:hover {
  background-image: linear-gradient(
    135deg,
    var(--mz-brand-vivid, var(--marrow-red-glow, #376F70)) 0%,
    var(--marrow-red) 100%
  );
}
/* A disabled action is not an action, so it loses the gradient entirely and
   goes back to the flat quiet slab section 19.2 defined for it. Leaving the
   gradient on a disabled control is how a dead button keeps looking pressable. */
:root[data-mz-theme] body.dash .dash-main .btn-primary-dash[disabled],
:root[data-mz-theme] body.dash .dash-main .btn-primary-dash[aria-disabled="true"] {
  background-image: none;
}

/* The halo. LIGHT THEME ONLY, on purpose. Section 19 of this file bans
   box-shadow on .dash-main .btn-primary-dash under the dark stamp, and that
   ban is a deliberate discipline for the black ground rather than an
   oversight, so the vibrancy in dark comes from the gradient alone and the
   halo is not smuggled back in underneath it. */
:root[data-mz-theme="light"] body.dash .dash-main .btn-primary-dash:hover {
  box-shadow: 0 10px 24px var(--mz-brand-halo, rgba(53, 106, 107, 0.30));
}

/* ---- 20.2 THE TOP ----------------------------------------------------
   The nav is the first thing on the screen and it was the one part of the
   chrome that never took the coach's colour. It does now, as a wash and a
   hairline rather than as a fill: a brand coloured bar would put the loudest
   colour on the screen behind something nobody is meant to press, which is
   the opposite of what section 20 is for.

   The glass ground stays per theme. Only the wash and the hairline are brand,
   and both sit low enough in alpha that the nav ink is untouched: the wash
   maxes at 6 percent of the accent over the existing glass, which cannot move
   any pairing in this bar across the AA line in either theme. */
:root[data-mz-theme] body.dash .dash-nav {
  background-image: linear-gradient(
    180deg,
    rgba(var(--mz-orb-rgb), 0.06) 0%,
    rgba(var(--mz-orb-rgb), 0.015) 100%
  );
  /* The hairline is the loudest brand tint in the bar, so it is drawn at the
     PROVEN ceiling rather than at a literal 0.30 that happened to match the
     old dark orb. A coach whose accent is bright enough to threaten the ink
     gets a softer hairline automatically, which is the same discipline every
     other accent wash on this page now follows. */
  box-shadow: inset 0 -1px 0 rgba(var(--mz-orb-rgb), var(--mz-orb-strong));
}
/* THE GLASS ITSELF FOLLOWS THE GROUND. Section 3 pins the dark nav to
   rgba(10, 9, 8, 0.88) and dashboard.css pins the light nav to
   rgba(239, 233, 220, 0.92), both literal, so a coach with a navy page got a
   black bar sitting on top of it. That seam is the most visible thing on the
   surface, and it is visible on every scroll. The bars are mixed from
   --mz-ground-rgb now, which defaults per theme to the exact two colours they
   carried, so an unbranded dashboard is unchanged.

   Alpha is kept per bar and per theme rather than tokenised: the mobile bar
   sits over content that scrolls under it and needs to be more opaque than
   the top bar, and those are two different judgements, not one number. */
:root[data-mz-theme="dark"] body.dash .dash-nav {
  background-color: rgba(var(--mz-ground-rgb), 0.88);
}
:root[data-mz-theme="light"] body.dash .dash-nav {
  background-color: rgba(var(--mz-ground-rgb), 0.92);
}
:root[data-mz-theme="dark"] body.dash .dash-bottom-nav {
  background-color: rgba(var(--mz-ground-rgb), 0.94);
}
:root[data-mz-theme="light"] body.dash .dash-bottom-nav {
  background-color: rgba(var(--mz-ground-rgb), 0.96);
}

/* The brandmark glow behind the hex follows the accent too, so the mark and
   the bar read as one lockup instead of two colours meeting. */
:root[data-mz-theme] body.dash .dash-brand-mark-glow {
  background: radial-gradient(circle, rgba(var(--mz-orb-rgb), 0.42), transparent 70%);
}

/* ---- 20.3 A COACH CHOSEN PAGE GROUND ---------------------------------
   When a coach sets their own background, theme-engine.js writes the whole
   derived ramp (--bone-cream, --bone-white, --bone-black, --steel,
   --steel-light, --ash, --line) as INLINE properties on the html element, so
   it outranks both the :root block in css/styles.css and the dark remap in
   section 2 of this file, and every token driven rule in roughly 5,600 lines
   of surface CSS follows without a single one of them being rewritten.

   Nothing in this stylesheet has to know that happened. The one thing that
   does is the stamp below, which exists so a surface can tell a coach ground
   from the Marrow ground when it genuinely matters, and so the state is
   readable in devtools instead of being invisible.

   The ramp is never invented. brand-kit.js proves the body ink, the secondary
   ink, the soft ink and the card against the ground and against each other at
   4.5:1 before any of it is written, and when a coach picks something that
   cannot carry text it moves the ground, keeps their hue, and returns a
   correction so the coach is told what changed. A pick is never silently
   dropped and never silently shipped unreadable. */
:root[data-mz-ground="custom"] body.dash {
  background-color: var(--bone-cream);
}


/* ---- 20.4 RISK IS NOT BRAND, AND THE EMBER TOKEN CARRIED BOTH ---------
   --marrow-ember-text is the "red as type on a dark ground" ink, and section 3
   of this file remaps a named list of selectors onto it. Once that token is
   DERIVED FROM THE COACH ACCENT, which is what makes a branded dashboard stop
   showing another brand's pink, the list stops being one list. It is two:

     BRAND as type   .card-eyebrow, .lamella-chip-kicker, .program-card-type,
                     .review-kind, .vs-time-period, the selected tabs, and the
                     rest of the chrome. These SHOULD wear the coach colour.
     RISK as type    .tr-flag, .status-pill.silent, .status-pill.uncollectible,
                     .recovery-score-num.red, .error-block. These must NOT.

   A coach who brands their dashboard green would otherwise read a GREEN churn
   flag sitting on a red tint, and a green "uncollectible" chip. The colour
   would be actively lying about what it means, which is the same reason
   section 3 keeps the risk chip tint as a literal red rather than tokenising
   it, and the same reason .tr-flag's own fill stays literal in dashboard.css.

   So the risk members are lifted off the brand ink and onto --mz-status-risk,
   which is the meaning palette: never brand, and re-proven against a coach
   chosen ground by brand-kit.js so it stays legible without becoming the
   accent. Raised by hand from Lane A, who found .tr-flag's ink while repointing
   the decorative literals around it and could not fix it from that file.

   Specificity: coach/dashboard/dashboard.css loads after this sheet and its
   rules are two components, so these carry four and settle without touching
   the other lane's file. */
:root[data-mz-theme="dark"] body.dash .tr-flag,
:root[data-mz-theme="dark"] body.dash .status-pill.silent,
:root[data-mz-theme="dark"] body.dash .status-pill.uncollectible,
:root[data-mz-theme="dark"] body.dash .recovery-score-num.red,
:root[data-mz-theme="dark"] body.dash .error-block,
:root[data-mz-theme="dark"] body.dash .logo-upload-status.is-error,
:root[data-mz-theme="light"] body.dash .tr-flag,
:root[data-mz-theme="light"] body.dash .recovery-score-num.red,
:root[data-mz-theme="light"] body.dash .error-block,
:root[data-mz-theme="light"] body.dash .logo-upload-status.is-error {
  color: var(--mz-status-risk);
}
