/* ============================================================================
   DESIGN TOKENS — single source of truth for colour
   ============================================================================

   Every document links this file. That includes the standalone pages
   (main, workout, activity, history, custom_food) which do NOT extend
   layout.html and therefore used to hand-copy the surface ladder, with a
   comment noting they were "kept in sync by hand". They no longer are; this
   file is the one definition.

   RULES
   -----
   1. Surfaces, text and accent tokens are THEME-VARYING. A theme overrides
      them and nothing else.
   2. State tokens are FIXED across themes. Meaning must not shift with taste —
      if "danger" turns green in a forest tint, the interface lies.
   3. Themes override via :root[data-theme="name"] (specificity 0,2,0) so they
      beat the :root defaults below (0,1,0) regardless of source order.

   Values here are exactly what the app already renders. Introducing this file
   changes no pixel; it only gives the existing colours names.
   ========================================================================= */

:root {

  /* === SURFACE LADDER ===
     Three tones on one hue axis, shared by every page. The old page base was a
     pure-neutral #161616 sitting under cards of #1b1b1e, which carry a +3 blue
     offset (essentially Apple's #1C1C1E secondary system background) — neutral
     under blue-cast is a hue seam at every card edge. All three rungs carry the
     same +2 blue, so levels change in lightness only, never in temperature.

     16 -> 27 -> 44 per channel. Steps expand going up because perceived
     lightness compresses near black, so equal hex steps would look uneven.
     Near-black rather than #000: pure black leaves nothing to recess into and
     erases every hairline and shadow. */
  --surface-page:   #101012;
  --surface-panel:  #1b1b1e;
  --surface-raise:  #2c2c2e;

  /* Recessed below the page — modal header/footer bars, .bg-black. This one
     sits *under* the panel it belongs to, which is why it is darker than the
     page rather than part of the upward ladder. */
  --surface-sunken: #0c0c0c;

  /* Hairlines. The solid border is the long-standing `1px solid #444`; the soft
     one is the glass treatment used on inputs, where a solid line would read as
     a box instead of an edge. */
  --surface-border:      #444444;
  --surface-border-soft: rgba(255, 255, 255, 0.1);

  /* === TEXT ===
     A five-rung ladder against the surfaces above. --text-body, not
     --text-primary, is the default body colour: pure white is reserved for
     emphasis, because body copy at #fff on near-black vibrates.

     NOTE: workout.html's second :root defines --text-primary as #e0e0e0, i.e.
     what is called --text-body here. That block is untouched for now and still
     wins locally on that page. Reconciling it belongs to the roll-out stage. */
  --text-primary:   #ffffff;
  --text-body:      #e0e0e0;
  --text-secondary: #aaaaaa;
  --text-muted:     #888888;
  --text-disabled:  #555555;

  /* === ACCENT (the tint axis) ===
     One hue across five lightness rungs. This is what a theme actually swaps;
     --accent is also the value in <meta name="theme-color">.

     Five rungs rather than three because the app had ~113 distinct purples
     clustered at four lightness levels, not one. Collapsing them all onto a
     single --accent would have flattened every gradient in the app. The rungs
     below are the four most-used purples verbatim, plus a deep rung for the
     dark ends of gradients. */
  /* The two darkest rungs exist because the app uses accent-tinted colours as
     near-black BACKGROUNDS (hero gradients, card washes), not just as accents.
     Without them, a #171020 background has no nearer home than #6f42c1 and gets
     dragged up ~41% in lightness — from near-black to a vivid mid purple. */
  --accent-shadow: #171020;  /* L  9% - near-black accent-tinted backgrounds */
  --accent-dark:   #3e2c60;  /* L 28% - dark gradient stops, deep washes     */
  --accent-deep:  #6f42c1;   /* L 51% - gradient shadows, pressed states */
  --accent:       #8b34f6;   /* L 58% - primary actions, active states    */
  --accent-mid:   #9359fe;   /* L 67% - gradient midpoints                */
  --accent-soft:  #a688fa;   /* L 76% - hover, focus rings                */
  --accent-pale:  #b989f0;   /* L 74% - text-on-dark accent, icons        */
  --accent-tint:  #ded4ff;   /* L 92% - near-white with an accent cast.
                                Body text and badge labels that should read as
                                white but carry a hint of the theme. Too
                                desaturated to be caught by hue detection, so
                                it must be maintained by hand. */

  /* Same five rungs as bare RGB triples, for rgba() at arbitrary alpha.
     The app uses 61 DISTINCT alpha values on accent colours, so pre-baked
     tokens like --accent-glow can never cover them all. Usage:
         box-shadow: 0 0 12px rgba(var(--accent-rgb), 0.42);
     This is the only way an rgba() can follow the theme. */
  --accent-shadow-rgb: 23, 16, 32;
  --accent-dark-rgb:   62, 44, 96;
  --accent-deep-rgb: 111, 66, 193;
  --accent-rgb:      139, 52, 246;
  --accent-mid-rgb:  147, 89, 254;
  --accent-soft-rgb: 166, 136, 250;
  --accent-pale-rgb: 185, 137, 240;
  --accent-tint-rgb: 222, 212, 255;

  /* The two alpha treatments that were already named. Kept as aliases so
     existing usage keeps working. */
  --accent-glow:  rgba(139, 52, 246, 0.25);
  --accent-wash:  rgba(139, 52, 246, 0.1);

  /* === ABSORBED LITERALS (added for the Daylight light theme) ===

     Everything below existed in the templates as a hardcoded value. None of it
     was themeable, which is fine while every theme is dark and impossible the
     moment one is not: a #151515 card or an rgba(255,255,255,0.05) hairline has
     no light-mode meaning at all.

     THE RULE FOR THIS BLOCK: every :root value here is the exact literal it
     replaced, so all six dark themes render byte-identically to before. Only
     :root[data-theme="daylight"] gives them different values. Where one token
     absorbed several near-identical literals, the drift is stated — none of the
     merges move a colour by more than 5/255, which is below the threshold where
     a difference is visible on a display.

     These are deliberately named by ROLE, not by tone, because the tone flips
     between the dark themes and Daylight while the role does not.
     ------------------------------------------------------------------------ */

  /* --- Extra text rungs --------------------------------------------------
     The five-rung ramp above covers the ramp the app was designed around; the
     templates then drifted a dozen near-duplicates around it. Each rung below
     is one cluster of that drift. */
  --text-bright:  #f1f1f1;  /* absorbs #f4f4f4 -3, #f0f0f0 +1, #eeeeee +3 */
  --text-soft:    #d8d8d8;  /* absorbs #dddddd -5, #d8d8d8  0, #d5d5d5 +3 */
  --text-quiet:   #cfcfcf;
  --text-dim:     #b8b4b4;  /* absorbs #bdbdbd -5, #b3afaf +5 */
  --text-faint:   #9b9b9b;  /* absorbs #9d9d9d -2, #999999 +2 */
  --text-whisper: #777777;

  /* Near-white with an accent cast, one rung above --accent-tint. Defined on
     :root only, exactly as the literal was: the dark themes never overrode the
     literal either, so this reproduces today's behaviour rather than quietly
     re-tinting headings in ocean/ember/platinum/magenta/jade. Worth revisiting
     as its own change. */
  --accent-tint-hi: #f1ecff;

  /* --- Surfaces the ladder never covered ---------------------------------
     Wells, slabs, tiles and fields that predate the three-rung ladder. Left at
     their exact tones rather than snapped onto the ladder, because snapping
     them IS a visible change and this pass is not that change. */
  --surface-well:    #111111;  /* deepest inline wells                       */
  --surface-slab:    #151515;  /* card + modal slabs (absorbs #161616 +1)    */
  --surface-tile:    #202020;  /* list tiles, segmented controls            */
  --surface-field:   #2a2a2a;  /* inputs and selects                        */
  --surface-track:   #2a2a2a;  /* progress-bar tracks                       */
  --surface-ring:    #222222;  /* the calorie ring's unfilled track          */
  --surface-chip:    #333333;  /* chips and chip borders                    */
  --surface-chip-hi: #3a3a3a;  /* chip hover (absorbs #3f3f3f -5)           */
  --surface-panel-2: #141416;  /* the darker stop of the panel gradient     */
  --surface-menu:    #222222;  /* dropdown menus, progress-ring rims        */
  --surface-arrow:   #1b1b1b;  /* tooltip arrows and tooltip body           */
  --surface-black:   #000000;  /* the footer slab and .bg-black             */

  --surface-edge:    #555555;  /* mid-grey rules, heavier than a hairline    */

  /* The custom-macro modal's calorie bar. Its "you still have budget" state is
     plain white — the loudest thing available on a near-black dialog. On a
     light one it is the quietest, and a white bar on a white track means the
     widget has no fill at all. It gets a token so the neutral state can move
     without touching the over-budget red or the on-target green, which are
     state colours and stay put in every theme. Identical to #ffffff here. */
  --macro-bar-neutral: #ffffff;

  /* The panel drop shadow is an OPAQUE near-black rather than an alpha, so it
     cannot ride --shadow-strength. It gets its own token instead. */
  --panel-shadow: rgb(21 21 23);

  /* --- Inverse ramp ------------------------------------------------------
     A whole family of controls is deliberately the REVERSE of the page: a light
     slab carrying dark text, used to make one action louder than everything
     around it (.btn-jere.active, .nav-link-box:hover, .btn-index:hover, the
     grams input in the diary, the nutrition grid's value cells). They have to
     flip with the theme like everything else, or Daylight ends up with a white
     button on a white page.

     Four rungs because the app uses four: pure white, a soft white, a light
     grey and a mid grey. In Daylight all four become near-blacks that keep the
     same ordering, so a control that was the brightest thing on a dark page is
     the darkest thing on a light one. */
  --inverse-solid:   #ffffff;
  --inverse-surface: #f0f0f0;
  --inverse-dim:     #d4d4d4;
  --inverse-mid:     #858585;
  --inverse-text:    #000000;

  /* Label colour for the one control that sits on --accent-soft. Every other
     filled button in the app carries white; this one carries black, because
     --accent-soft is a PALE purple in the dark themes. In Daylight that rung is
     dark, so the label has to invert with it. */
  --on-accent-soft:  #000000;

  /* --- Channel tokens: the actual light-mode seam -------------------------
     Two idioms account for most of the app's chrome, and both are direction-
     dependent rather than colour-dependent:

       a white wash over a dark surface   -> a black wash over a light one
       a black shadow under a dark card   -> a soft grey shadow under a light one

     Rather than tokenise 60-odd distinct alpha values, the CHANNEL is the
     token and the alpha stays written at the call site:

         background: rgba(var(--wash-rgb), 0.05);
         box-shadow: 0 4px 12px rgba(var(--shadow-rgb),
                                     calc(0.3 * var(--shadow-strength)));

     --wash-strength is not needed: black at 5% over white and white at 5% over
     near-black are the same perceptual step. Shadows are not symmetric — a
     0.5-alpha black shadow is nearly invisible on #101012 and a bruise on
     #f2f0f6 — so shadows carry a strength multiplier that Daylight turns down.
     calc() inside rgba() is safe on every browser this PWA supports (Chrome 79,
     Safari 12). */
  --wash-rgb:        255, 255, 255;
  --shadow-rgb:      0, 0, 0;
  --shadow-strength: 1;

  /* The third direction, and the one that is easy to miss. A wash LIGHTENS a
     surface; a sink DARKENS it — form fields, recessed wells, the strip behind
     a toast icon. On a dark page both idioms exist and only the wash is
     obviously theme-dependent, which is why `rgba(0,0,0,0.4)` was written
     literally everywhere. It is just as direction-dependent: on a light page a
     40% black field is a hole, not an input.

     NOT the same token as --shadow-rgb even though both are black today. A
     shadow is cast BEHIND an element and Daylight keeps it dark, only weaker; a
     sink is painted INTO the surface and Daylight flips it to white. Merging
     them would make every light-mode input dark grey. */
  --sink-rgb: 0, 0, 0;

  /* Glass slabs: `rgba(30,30,30,α)` and `rgba(39,39,39,α)` behind
     backdrop-filter. Same reasoning as above — the alpha is compositional and
     stays at the call site, the tone follows the theme. */
  --glass-rgb:    30, 30, 30;
  --glass-hi-rgb: 39, 39, 39;

  /* The diary's sticky selection bar sits over scrolling content behind a blur,
     so it is an alpha gradient rather than a flat surface, and its two stops are
     different tones. Both need to follow the theme or the bar stays a black slab
     under a light page. */
  --bar-rgb:      20, 20, 22;
  --bar-rgb-deep: 15, 15, 17;

  /* activity.html's informational note slab. An off-palette blue-purple — the
     same family as the `rgba(100, 100, 200, ...)` rule noted further up — so no
     surface rung or channel fits it, and it gets a channel of its own rather
     than being snapped onto something 30 units away. */
  --surface-note-rgb: 40, 40, 60;

  /* === STATE (fixed across themes) === */
  --state-success: #7bc8a4;
  --state-warning: #ffd166;
  --state-danger:  #ff4444;
  --state-info:    #6bfeff;
  --state-gold:    #ffd700;

  /* === CATEGORY PALETTES (fixed across themes, deliberately NOT tokenised) ===

     THE RULE: a colour that distinguishes one CATEGORY from another must never
     theme. Category colours only work as a SET — recolouring one member breaks
     its contrast with the others. Protein is purple in every theme, carbs are
     #6dc0d5 and fats are #ff9e6d in every theme; the macro triad is its own
     small palette and does not follow the user's taste. Only chrome (buttons,
     hovers, borders, gradients, focus rings) follows the accent.

     STATUS: every palette below is now LITERAL in the templates. Earlier
     revisions of this file described some members as accent-tokenised — that
     is no longer true and the accent tokens have been removed from them. Do
     not put them back. A themed --accent-pale on a protein bar turns protein
     green in this theme, blue in ocean and orange in ember, which breaks the
     triad's meaning for the sake of a cosmetic.

     HOW TO SPOT A REGRESSION — three mechanical checks. Run them before any
     themed change ships; each one catches a case the others are blind to.

       1. A custom property declared with a literal in one place and an accent
          token in another, in the same file. That is the signature of a
          palette with one member drifting back onto the accent:
             --triad-color: var(--accent-pale);                 <- REGRESSION
             [data-macro="carbs"] { --triad-color: #6dc0d5; }   <- correct
          Applies to --group-color, --macro-color, --cell-color, --card-color,
          --triad-color, --highlight-color.

       2. An accent token inside a selector naming a category —
          pro/proteins/carbs/fats/fiber/legs/chest/dinner/water/volume-pr/
          weight-pr. Catches per-class members that use no custom property.

       3. An accent token whose surrounding ~4 lines contain a macro WORD
          (Proteiini / Hiilihydraatit / Rasva / Kuitu) or a macro ID
          (#custom-pro-range, id="impact-pro-bar", …). Checks 1 and 2 both read
          CSS selectors, so they are blind to inline styles in markup and to
          id-based rules. This one is not:
             <label style="color: var(--accent-pale)">Proteiini</label>
             #custom-pro-range::-webkit-slider-thumb { border: 3px solid … }
          Expect false positives — "Rasvanpoltto" is a plan type, not the fats
          macro, and "progress"/"promo" match a bare `pro` — so read the hits
          rather than bulk-applying them.

     The palettes, all of which use #b989f0 for their purple member:

       - Muscle groups  (workout, history, workout_showcase, program_builder):
         .legs alongside chest #f78fb3 / back #6dc0d5 / shoulders #ffd166 /
         arms #7bc8a4 / core #ff9e6d / muut #6c757d  -- via --group-color
       - Meal groups (main.html): .dinner alongside breakfast #ff9e6d /
         lunch #6dc0d5 / snack #ffd166 / other #7bc8a4  -- via --group-color
       - Macros (main.html, index.html): protein alongside carbs #6dc0d5 and
         fats #ff9e6d. Appears as --macro-color, --cell-color, --triad-color,
         .filter-pill, .opt1-fill-pro, .grid-proteins, .progress-proteins,
         .mobile-tab-btn, and inline protein readouts. Dark partner #835fac.
         Also the custom-macro modal (main.html): the "Proteiini" label,
         #custom-pro-range's track and thumb border (webkit AND moz), and the
         #impact-pro-text / #impact-pro-bar readouts. Fiber is a fourth member
         here at #81c784.
       - Stat cards (workout.html): .card-volume #9359fe alongside
         time #6dc0d5 / fire #ff9800 / pr #ffd700 / reps #4CAF50
       - Mobile macro triad (main.html): --triad-color #b989f0 alongside
         [data-macro="carbs"] #6dc0d5 / [data-macro="fats"] #ff9e6d
       - Pinned micronutrient cells (main.html): --cell-color #b989f0 and its
         .progress-pinned bars, alongside .is-water #6ec6f5 / #4a86a8
       - PR highlights (workout.html): --highlight-color, .volume-pr #a688fa
         and .baseline-highlight #b989f0, alongside base #7bc8a4 and
         .weight-pr #ffd65a
       - PR badges (workout.html): .exercise-completion-pr-badge.is-volume-pr
         is fully literal, matching its all-literal .is-weight-pr sibling
       - Macro reward badges (main.html): .food-log-reward-badge.is-proteins
         #dfc3ff alongside is-carbs #aee8f5 / is-fats #ffc2a4 / is-fiber #a8e4c7
       - History nav tabs (history.html): .exercises-btn #b989f0 alongside
         .nutrition-btn #ff9e6d / .workouts-btn var(--bs-pink)
       - Social platform pills (profile, settings): these are BRAND colours.
         instagram rgba(139,52,246,1) alongside tiktok #FF0050 /
         spotify #1DB954 / strava #FC4C02 / facebook #1877F2. Instagram is
         not blue in anyone's theme.
       - Avatar border cosmetics (profile, settings, index, workout):
         .border-glow #a688fa / #9359fe and .border-rainbow, alongside
         .border-gold #FFD700 / .border-fire / .border-water. These are
         *earned* rewards — a cosmetic must look the same for everyone who
         unlocked it, not shift with the owner's theme.
       - Coaching states (activity.html): recomposition #6A1B9A,
         metabolic_adaptation #7B1FA2. Same rule as --state-*. The generic
         ANALYYSI fallback DOES theme, having no specific meaning to preserve.
       - Chart.js series (history.html): left literal by choice.
       - .bar-segment.protein (history, activity): #b989f0 -> #835fac, the
         macro stack in the nutrition bars.
       - .tdee-line-day (history.html): #9359fe with an #a688fa glow. A data
         reference line drawn over the macro bars, not chrome.
       - .status-maintain (main.html): text/border #e1bee7 on a
         rgba(111, 66, 193, ..) fill, beside .status-gain #ff8a80. A status
         scale, so the fill must stay literal like its sibling.

     Also literal on purpose: <meta name="theme-color"> in markup, because
     var() does not resolve in a meta attribute -- theme.js rewrites it at
     runtime instead.

     === PLACES var() CANNOT REACH ===
     Three contexts silently swallow var(). Each needs a different workaround:

       1. SVG presentation attributes -- stroke="var(--accent)" renders
          unstroked. The lucide() macro in templates/macros/icons.html emits an
          inline style instead when the caller passes a var() colour.

       2. Inside a url() data URI -- the URI is one opaque string, so a colour
          baked into the SVG (stroke='%239359fe') can never follow the theme.
          FIX: use the SVG as a MASK and let background-color supply the colour.
          CSS masks default to alpha mode, so the stroke colour inside the mask
          SVG is irrelevant. See .custom-select-trigger::after in workout.html
          for the working pattern.

       3. <meta> attributes -- rewritten by theme.js at runtime.

     Canvas libraries (Chart.js) are a fourth case: they parse colours with no
     CSS context. TYTheme.color('--accent-pale', 0.4) in theme.js reads the
     computed value for them.
     ========================================================================= */
}


/* ============================================================================
   THEMES
   ============================================================================

   A theme overrides surfaces and the accent axis. It does NOT override state
   colours, and it does NOT override the text ramp — text is tuned for contrast
   against the surface luminances, and every theme keeps those luminances on the
   same rungs, so the ramp stays valid without restating it.

   Each theme reproduces the ladder's two structural rules:
     - the three rungs sit at roughly luminance 17 / 28 / 46
     - all three carry the SAME hue offset, so levels change in lightness only
       and never in temperature (this is what prevents seams at card edges)

   Selector is :root[data-theme="..."] (specificity 0,2,0) so it beats the
   :root defaults (0,1,0) regardless of source order.
   ========================================================================= */

:root[data-theme="ocean"] {
  /* Same rungs as default, rotated to a cool blue. Every rung carries the same
     blue-dominant offset, matching the default ladder's +2 discipline. */
  --surface-page:   #0f1216;
  --surface-panel:  #191d22;
  --surface-raise:  #2a2f36;
  --surface-sunken: #08090b;

  --surface-border:      #3d444d;
  --surface-border-soft: rgba(255, 255, 255, 0.1);

  /* Five rungs mirroring the default ramp's structure. Blues read lighter than
     purples at equal lightness, so these sit a few points darker by measure
     while landing in the same perceptual place. */
  --accent-shadow: #0d1420;
  --accent-dark:   #1c3a5e;
  --accent-deep: #1565c0;
  --accent:      #1e88e5;
  --accent-mid:  #42a5f5;
  --accent-soft: #64b5f6;
  --accent-pale: #90caf9;
  --accent-tint: #d6e8fb;

  --accent-shadow-rgb: 13, 20, 32;
  --accent-dark-rgb:   28, 58, 94;
  --accent-deep-rgb: 21, 101, 192;
  --accent-rgb:      30, 136, 229;
  --accent-mid-rgb:  66, 165, 245;
  --accent-soft-rgb: 100, 181, 246;
  --accent-pale-rgb: 144, 202, 249;
  --accent-tint-rgb: 214, 232, 251;

  --accent-glow: rgba(30, 136, 229, 0.25);
  --accent-wash: rgba(30, 136, 229, 0.1);
}


:root[data-theme="ember"] {
  /* A deliberately darker ladder than the default — roughly half its lightness
     at every rung. The same +2 blue offset is carried on all four, so the
     surfaces stay a consistent cool neutral. That coolness is the point: it is
     what makes the warm accent read as heat against them rather than as a
     general orange cast over the whole page. */
  --surface-page:   #08080a;
  --surface-panel:  #121214;
  --surface-raise:  #202022;
  --surface-sunken: #050507;

  --surface-border:      #2e2e30;
  --surface-border-soft: rgba(255, 255, 255, 0.1);

  /* Orange, deliberately kept saturated and clearly separated from the two
     warm colours that do NOT theme:
        --state-warning  #ffd166  (gold, yellower)
        macro "fats"     #ff9e6d  (soft peach, desaturated)
     --accent #f97316 is more saturated and redder than both, so a themed
     button never reads as a warning badge or as the fats macro. */
  --accent-shadow: #17100a;
  --accent-dark:   #4d2c12;
  --accent-deep:   #b45309;
  --accent:        #f97316;
  --accent-mid:    #fb923c;
  --accent-soft:   #fdba74;
  --accent-pale:   #fed7aa;
  --accent-tint:   #ffe4cc;

  --accent-shadow-rgb: 23, 16, 10;
  --accent-dark-rgb:   77, 44, 18;
  --accent-deep-rgb:   180, 83, 9;
  --accent-rgb:        249, 115, 22;
  --accent-mid-rgb:    251, 146, 60;
  --accent-soft-rgb:   253, 186, 116;
  --accent-pale-rgb:   254, 215, 170;
  --accent-tint-rgb:   255, 228, 204;

  --accent-glow: rgba(249, 115, 22, 0.25);
  --accent-wash: rgba(249, 115, 22, 0.1);
}


:root[data-theme="platinum"] {
  /* Ember's near-black ladder, rotated cool. The surfaces carry a stronger
     blue offset than any other theme (+4 rather than +2) because the accent
     here is itself near-neutral: with a +2 ladder the two would converge and
     the accent would stop reading as a colour at all. The wider offset keeps
     surface and accent on opposite sides of neutral. */
  --surface-page:   #0d1014;
  --surface-panel:  #171b21;
  --surface-raise:  #262b33;
  --surface-sunken: #08090c;

  --surface-border:      #333a44;
  --surface-border-soft: rgba(255, 255, 255, 0.1);

  /* Steel. The deliberate constraint here is that --accent must still carry
     WHITE label text, like every other theme's does — the alternative was an
     --accent-on token and an audit of every filled button in the app. #7d8ca3
     lands at contrast 3.4:1 against white, i.e. the same rung ocean's #1e88e5
     already occupies, so nothing downstream has to change.

     The upper rungs are the risk in this palette, not the lower ones. Left
     fully desaturated they collide with the text ramp: --accent-pale is the
     token used for accent-coloured TEXT, and a neutral #ccd4e0 sitting beside
     --text-body #e0e0e0 reads as "the unstyled one" rather than as a tint.
     Every rung above --accent therefore keeps a visible blue bias, which is
     also what stops the protein bar (which uses --accent-pale) from looking
     uncoloured next to its literal carbs #6dc0d5 and fats #ff9e6d siblings. */
  --accent-shadow: #0e1219;
  --accent-dark:   #2a3242;
  --accent-deep:   #55627a;
  --accent:        #7d8ca3;
  --accent-mid:    #96a4ba;
  --accent-soft:   #aeb9c9;
  --accent-pale:   #b8c5d6;
  --accent-tint:   #e8edf5;

  --accent-shadow-rgb: 14, 18, 25;
  --accent-dark-rgb:   42, 50, 66;
  --accent-deep-rgb:   85, 98, 122;
  --accent-rgb:        125, 140, 163;
  --accent-mid-rgb:    150, 164, 186;
  --accent-soft-rgb:   174, 185, 201;
  --accent-pale-rgb:   184, 197, 214;
  --accent-tint-rgb:   232, 237, 245;

  --accent-glow: rgba(125, 140, 163, 0.25);
  --accent-wash: rgba(125, 140, 163, 0.1);
}


:root[data-theme="magenta"] {
  /* Warm near-black. Same discipline as the others — one hue offset carried on
     all four rungs — but the offset is red-dominant here, so the surfaces sit
     slightly warm and the accent reads as belonging to them rather than as a
     pink stuck onto a neutral grey. */
  --surface-page:   #100a0e;
  --surface-panel:  #1c1219;
  --surface-raise:  #2d1f28;
  --surface-sunken: #0a0508;

  --surface-border:      #46303c;
  --surface-border-soft: rgba(255, 255, 255, 0.1);

  /* Fuchsia — the one saturated hue the fixed palettes leave free. It has to
     hold distance from three things:
        chest (muscle groups)  #f78fb3  lighter and far less saturated
        tiktok (brand pill)    #FF0050  redder, no blue component to speak of
        --state-danger         #ff4444  pure red
     --accent #e91e96 is bluer than the two reds and roughly twice the
     saturation of chest, so a themed button never reads as a delete action or
     as a muscle-group chip.

     Deliberately NOT a rotation of the default purple: dropping the blue is
     the whole point, otherwise donors get a second purple and wonder what
     they paid for. */
  --accent-shadow: #1a0a14;
  --accent-dark:   #5c1240;
  --accent-deep:   #b3157a;
  --accent:        #e91e96;
  --accent-mid:    #f45cb0;
  --accent-soft:   #f98ac8;
  --accent-pale:   #f9a8d4;
  --accent-tint:   #ffd3ec;

  --accent-shadow-rgb: 26, 10, 20;
  --accent-dark-rgb:   92, 18, 64;
  --accent-deep-rgb:   179, 21, 122;
  --accent-rgb:        233, 30, 150;
  --accent-mid-rgb:    244, 92, 176;
  --accent-soft-rgb:   249, 138, 200;
  --accent-pale-rgb:   249, 168, 212;
  --accent-tint-rgb:   255, 211, 236;

  --accent-glow: rgba(233, 30, 150, 0.25);
  --accent-wash: rgba(233, 30, 150, 0.1);
}


:root[data-theme="jade"] {
  /* Deep jade. The one hue a food app is expected to have and did not, and the
     only saturated hue left after the fixed palettes claim gold, red and cyan.

     Green is claimed too — but only at the pale, desaturated end:
        --state-success  #7bc8a4  (soft sage)
        fiber macro      #81c784  (pastel)
     Both sit high in lightness and low in chroma. The accent rung here is dark
     and heavily saturated instead, so a themed button never reads as a success
     badge or as the fiber macro — the same separation-by-saturation that keeps
     ember's #f97316 clear of the peach fats macro.

     Surfaces carry one green-cool offset on all four rungs, so levels change in
     lightness only and card edges show no hue seam. Rungs land on the ladder's
     usual 17 / 28 / 46 luminances. */
  --surface-page:   #0b1110;
  --surface-panel:  #141d1a;
  --surface-raise:  #232f2b;
  --surface-sunken: #070c0b;

  --surface-border:      #35443f;
  --surface-border-soft: rgba(255, 255, 255, 0.1);

  /* --accent #0e8f60 lands at 4.1:1 against white — the same rung magenta's
     #e91e96 already occupies, and a little safer than ocean's #1e88e5 at 3.7.
     That is deliberate: every filled button in the app hardcodes a white
     label, so landing on an existing rung means none of them need an
     --accent-on token or a per-theme override.

     The upper rungs stay mint rather than sage. --accent-pale is the token used
     for accent-coloured TEXT and thin bars; drifted toward #81c784 it would
     start reading as the fiber macro, which is exactly the collision the
     saturated lower rungs were chosen to avoid. */
  --accent-shadow: #0a1712;  /* L  8% */
  --accent-dark:   #10422f;  /* L 24% - status bar / manifest theme_color */
  --accent-deep:   #0b6e4c;  /* L 41% */
  --accent:        #0e8f60;  /* L 54% - primary actions */
  --accent-mid:    #14b981;  /* L 68% */
  --accent-soft:   #4ed7a5;  /* L 79% */
  --accent-pale:   #86e5c0;  /* L 86% */
  --accent-tint:   #d6f7e8;  /* L 95% */

  --accent-shadow-rgb: 10, 23, 18;
  --accent-dark-rgb:   16, 66, 47;
  --accent-deep-rgb:   11, 110, 76;
  --accent-rgb:        14, 143, 96;
  --accent-mid-rgb:    20, 185, 129;
  --accent-soft-rgb:   78, 215, 165;
  --accent-pale-rgb:   134, 229, 192;
  --accent-tint-rgb:   214, 247, 232;

  --accent-glow: rgba(14, 143, 96, 0.25);
  --accent-wash: rgba(14, 143, 96, 0.1);
}


/* ============================================================================
   DAYLIGHT — the first light theme
   ============================================================================

   Every theme before this one is a dark ladder with a different tint. Daylight
   is the first that moves on the LIGHTNESS axis instead of the hue axis, which
   makes it the only theme that breaks the rule the block at the top of this
   file states: "a theme overrides surfaces and the accent axis, and it does NOT
   override the text ramp — text is tuned for contrast against the surface
   luminances, and every theme keeps those luminances on the same rungs."

   Daylight does not keep them, so it overrides the text ramp too, along with
   the hairlines, the wash channel and the shadow strength. That is the whole
   reason those tokens exist.

   THE LADDER IS INVERTED, NOT SHUFFLED. --surface-raise is still the rung that
   reads as "closer to the viewer": in the dark themes that means lighter than
   the panel, here it means whiter than the panel. Anything that was elevated
   stays elevated, so no component has to know which theme it is in.

   THREE DIRECTIONS FLIP, and every one of them is a token in this block:
     1. Text: near-white on near-black becomes near-black on near-white.
     2. Washes: --wash-rgb goes from white to a near-black with an accent cast,
        so `rgba(var(--wash-rgb), 0.05)` stays a subtle step in both.
     3. Shadows: dark UIs cast almost nothing and lean on glow; light UIs cast
        real shadows. --shadow-strength scales every call site at once.

   THE ACCENT AXIS IS RE-WEIGHTED, NOT ROTATED. The hue stays the app's purple —
   this is a light mode, not a sixth colour. But the ramp's rungs are defined by
   what they are USED for, and on a light ground those uses want the opposite
   lightness: --accent-shadow and --accent-dark are backgrounds (near-black in
   the dark themes, so near-white here), while --accent-pale and --accent-tint
   are foreground text (near-white there, so near-black here). Reversing them is
   what keeps `color: var(--accent-pale)` legible in both.

   WHAT STILL DOES NOT THEME: state colours and every category palette. The
   macro triad is purple / #6dc0d5 / #ff9e6d in Daylight exactly as it is in the
   dark themes — those colours only work as a set and a light ground is not a
   licence to break the set. Some of them are tuned for near-black and will look
   thin on white; that is a separate, deliberate pass, not something to fix by
   quietly recolouring one member.
   ========================================================================= */

:root[data-theme="daylight"] {

  /* Inverted ladder. Same three rungs, same one hue offset on all of them —
     a faint violet, so the greys read as chosen rather than as unstyled, and so
     the purple accent belongs to the surfaces instead of sitting on top of a
     neutral. Page is NOT pure white: white leaves nothing for a card to rise
     out of, the same reason the dark ladder does not start at #000. */
  --surface-page:   #f2f0f6;
  --surface-panel:  #fbfafd;
  --surface-raise:  #ffffff;

  /* Below the page, matching the dark theme's relationship rather than its
     direction: modal header/footer bars and .bg-black recede. */
  --surface-sunken: #e6e2ee;

  --surface-border:      #d7d2e0;
  --surface-border-soft: rgba(0, 0, 0, 0.10);

  /* Text ramp, mirrored. Contrast against --surface-page tracks the dark ramp
     closely: body 13.1:1 here vs 14.4:1 there, secondary 6.0 vs 6.2, muted 4.5
     vs 4.9. Kept a touch under the dark values on purpose — pure #000 on white
     glares in daylight the same way pure #fff on black vibrates at night. */
  --text-primary:   #100c18;
  --text-body:      #2b2536;
  --text-secondary: #5f5870;
  --text-muted:     #746c86;
  --text-disabled:  #b0a9bd;

  /* The absorbed rungs, mirrored the same way. */
  --text-bright:  #17131f;
  --text-soft:    #332c40;
  --text-quiet:   #3d3548;
  --text-dim:     #524a60;
  --text-faint:   #6b6379;
  --text-whisper: #7e7689;

  --accent-tint-hi: #241634;

  /* Surfaces the ladder never covered, inverted in place. Each keeps its role:
     a well is still the most recessed thing on the page, a chip still sits
     above the tile it is on. */
  --surface-well:    #e9e5f0;
  --surface-slab:    #ffffff;
  --surface-tile:    #f4f2f8;
  --surface-field:   #ffffff;
  --surface-track:   #e6e2ee;  /* a field goes white on light; a TRACK cannot,
                                  or the unfilled part of every macro bar
                                  disappears into the card behind it */
  --surface-ring:    #ded8e8;  /* same reason, and it matters more here: at 0
                                  kcal the coloured ring is fully hidden, so the
                                  track IS the widget. A white track on a white
                                  card makes the whole ring disappear. */
  --surface-chip:    #e4e0ec;
  --surface-chip-hi: #dbd6e6;
  --surface-panel-2: #f4f2f8;
  --surface-menu:    #ffffff;
  --surface-arrow:   #ffffff;
  --surface-black:   #e6e2ee;

  --surface-edge:    #c4bed2;
  --panel-shadow:    rgba(42, 30, 62, 0.10);

  /* The neutral macro bar takes the accent instead of white: a purple line is
     the only thing that reads on a white track, and it leaves the on-target
     green and the over-budget red as the two states that mean something. */
  --macro-bar-neutral: var(--accent);

  /* The inverse ramp does NOT flip all the way here, and that is deliberate.
     A true mirror would make every grams pill, portion chip and active meal
     button a near-black slab — technically consistent, and far too loud on a
     light page for controls that appear dozens of times in one diary.

     Instead the ramp stays light and separates by TONE: each rung is a step
     darker than the panel it sits on, with dark text inside. The ordering the
     dark themes rely on is preserved (solid is the brightest rung, mid the
     heaviest), so a control that was the loudest thing on a dark page is still
     the most emphatic on a light one — it just says so with contrast against
     the surface rather than by inverting against the whole page. */
  --inverse-solid:   #ffffff;
  --inverse-surface: #eeeaf6;
  --inverse-dim:     #e0daed;
  --inverse-mid:     #cec6df;
  --inverse-text:    #241c2e;
  --on-accent-soft:  #ffffff;

  /* The channel flip. --wash-rgb carries the same violet cast as the surfaces,
     so a wash darkens toward the page tone rather than toward neutral grey. */
  --wash-rgb:        26, 18, 38;
  --shadow-rgb:      42, 30, 62;
  --shadow-strength: 0.28;

  /* Fields and wells become white against the page rather than darker than it,
     which is what a light-mode input looks like everywhere else. They stay
     readable as fields because --surface-border draws their edge. */
  --sink-rgb: 255, 255, 255;

  /* Glass slabs invert too, or every backdrop-filter panel stays a dark card
     floating on a light page. */
  --glass-rgb:    252, 251, 254;
  --glass-hi-rgb: 246, 244, 250;

  --bar-rgb:      251, 250, 253;
  --bar-rgb-deep: 240, 238, 246;

  --surface-note-rgb: 232, 230, 246;

  /* Accent axis, re-weighted for a light ground (see the header note).
     --accent #7c2fe0 is 6.3:1 against white, so filled buttons keep the white
     label every one of them hardcodes — more headroom than any dark theme has,
     which matters because a saturated purple on white is where fringing shows
     first. */
  --accent-shadow: #f5efff;  /* was near-black background -> near-white wash */
  --accent-dark:   #e6dbff;  /* was dark gradient stop    -> pale gradient stop */
  --accent-deep:   #5a2ba0;  /* pressed / gradient shadow  -> darker, as before */
  --accent:        #7c2fe0;  /* primary actions */
  --accent-mid:    #8f3ff0;  /* gradient midpoints, still white-label safe (5.1:1) */
  --accent-soft:   #6a25c4;  /* hover + focus rings: hover DARKENS on light */
  --accent-pale:   #6b2bbf;  /* accent-coloured TEXT and thin bars */
  --accent-tint:   #241634;  /* near-black with an accent cast */

  --accent-shadow-rgb: 245, 239, 255;
  --accent-dark-rgb:   230, 219, 255;
  --accent-deep-rgb:   90, 43, 160;
  --accent-rgb:        124, 47, 224;
  --accent-mid-rgb:    143, 63, 240;
  --accent-soft-rgb:   106, 37, 196;
  --accent-pale-rgb:   107, 43, 191;
  --accent-tint-rgb:   36, 22, 52;

  --accent-glow: rgba(124, 47, 224, 0.25);
  --accent-wash: rgba(124, 47, 224, 0.1);
}


/* ============================================================================
   BOOTSTRAP BRIDGE — Daylight only
   ============================================================================

   Bootstrap 5.3 ships a handful of utilities that hardcode "the page is dark":
   .text-white, .text-light, .text-white-50, .bg-dark, .dropdown-menu-dark. They
   are correct in every theme this app had until now, so these rules are scoped
   to :root[data-theme="daylight"] and the dark themes never see them. This file
   loads after bootstrap.min.css, so matching Bootstrap's !important is enough
   to win — no specificity games needed.

   NOT bridged, on purpose:
     - .text-muted is already repointed at var(--text-muted) in layout.html and
       index.html, so it flips on its own.
     - .text-body / .text-secondary resolve to Bootstrap's own #212529 family,
       which is a LIGHT-mode colour. They are wrong on the dark themes today and
       right under Daylight. Repointing them is a real fix, but it is a change to
       how the dark themes render, so it does not belong in this pass.
   ========================================================================= */

:root[data-theme="daylight"] .text-white,
:root[data-theme="daylight"] .text-light {
  color: var(--text-primary) !important;
}

:root[data-theme="daylight"] .text-white-50 {
  color: var(--text-secondary) !important;
}

:root[data-theme="daylight"] .bg-dark {
  background-color: var(--surface-field) !important;
  color: var(--text-body) !important;
}

/* Bootstrap paints the menu from --bs-dropdown-bg rather than from a plain
   background-color, and .dropdown-menu-dark's whole job is to set that variable
   to #343a40. Overriding the variable AND the declaration covers both paths,
   whichever rule ends up winning in a given stacking context — and !important
   because .dropdown-menu-dark is a utility, so it is competing against other
   utilities rather than against ordinary component CSS. */
:root[data-theme="daylight"] .dropdown-menu,
:root[data-theme="daylight"] .dropdown-menu-dark {
  --bs-dropdown-bg: var(--surface-menu);
  --bs-dropdown-color: var(--text-body);
  --bs-dropdown-link-color: var(--text-body);
  --bs-dropdown-link-hover-color: var(--text-primary);
  --bs-dropdown-link-hover-bg: rgba(var(--wash-rgb), 0.06);
  --bs-dropdown-border-color: var(--surface-border);
  background-color: var(--surface-menu) !important;
  border-color: var(--surface-border);
  color: var(--text-body);
}

:root[data-theme="daylight"] .dropdown-divider {
  border-top-color: var(--surface-border);
}

:root[data-theme="daylight"] .dropdown-menu-dark .dropdown-item {
  color: var(--text-body);
}

:root[data-theme="daylight"] .dropdown-menu-dark .dropdown-item:hover,
:root[data-theme="daylight"] .dropdown-menu-dark .dropdown-item:focus {
  background-color: rgba(var(--wash-rgb), 0.06);
  color: var(--text-primary);
}

/* The navbar brand and links come from .navbar-dark, which sets rgba white. */
:root[data-theme="daylight"] .navbar-dark .navbar-brand,
:root[data-theme="daylight"] .navbar-dark .nav-link {
  color: var(--text-body);
}

:root[data-theme="daylight"] .navbar-dark .nav-link:hover,
:root[data-theme="daylight"] .navbar-dark .nav-link:focus {
  color: var(--text-primary);
}

/* Bootstrap draws the modal close button as a black SVG and inverts it for dark
   pages. Daylight wants the un-inverted one back. */
:root[data-theme="daylight"] .btn-close-white {
  filter: none;
}


/* ============================================================================
   DAYLIGHT COMPONENT CORRECTIONS
   ============================================================================

   Components whose colour is right in every dark theme and wrong on a light
   ground for a reason no token can express — a hue that only had contrast
   against near-black, or an alpha tuned for a dark surface. Every rule here is
   scoped to :root[data-theme="daylight"], so the dark themes never see them and
   the whole block can be deleted to revert Daylight to the raw token output.
   ========================================================================= */

/* Bootstrap's light outline button is white-on-white here. */
:root[data-theme="daylight"] .btn-outline-light {
  color: var(--text-body);
  border-color: var(--surface-border);
}

:root[data-theme="daylight"] .btn-outline-light:hover,
:root[data-theme="daylight"] .btn-outline-light:focus {
  color: var(--text-primary);
  background-color: var(--surface-chip);
  border-color: var(--surface-edge);
}

/* Meal-group icon: pinned to the dark themes' exact treatment.

   The first attempt gave Daylight its own version — a light disc with a dark
   glyph — on the reasoning that a white wash inverts to a dark disc. But the
   icon does not sit on the page; it sits on the group header, which is a
   gradient starting at full --group-color in exactly the spot the icon occupies.
   That ground is the same in both themes, so the treatment tuned against it is
   too, and the light-disc version only removed the contrast the original had.

   These are literals rather than tokens on purpose: the point is that this
   component does NOT follow the surface, because its surface is the meal colour.
   */
:root[data-theme="daylight"] .group-icon {
  background: rgba(255, 255, 255, 0.2);
  /* !important because program_builder.html and workout_showcase.html write
     this component as `.group-icon .text-white`, and the Bootstrap bridge
     repoints .text-white to near-black. Without it the same icon is white in
     the diary and dark on the other two pages. */
  color: #fff !important;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.2) inset,
              0 0 0 1px rgba(0, 0, 0, 0.3);
}

:root[data-theme="daylight"] .group-icon:hover,
:root[data-theme="daylight"] .group-icon:focus-visible {
  background: rgba(255, 255, 255, 0.3);
}

:root[data-theme="daylight"] .group-icon.group-selection-active {
  background: var(--group-color);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.85), 0 0 12px var(--group-color);
  color: #fff;
}

:root[data-theme="daylight"] .group-icon.group-selection-active::after {
  background: #fff;
  border-color: rgba(0, 0, 0, 0.35);
  color: #151515;
}

/* Favourite portion. Gold at 10% alpha over near-black is a warm glow; over
   white it is nearly nothing, and #ffd700 text on white is 1.4:1. The hue stays
   gold — it is the "favourite" signal — but the label drops to a dark amber and
   the fill gains enough alpha to read as a tinted chip. */
:root[data-theme="daylight"] .portion-btn.favourite-btn,
:root[data-theme="daylight"] #food-details .portion-btn.favourite-btn {
  background: linear-gradient(135deg, rgba(255, 193, 7, 0.22), rgba(255, 152, 0, 0.18));
  border-color: rgba(196, 145, 0, 0.55);
  color: #7a5b00;
}

:root[data-theme="daylight"] .portion-btn.favourite-btn:hover {
  background: linear-gradient(135deg, rgba(255, 193, 7, 0.34), rgba(255, 152, 0, 0.28));
  border-color: #c49100;
}

:root[data-theme="daylight"] .portion-btn.favourite-btn.active,
:root[data-theme="daylight"] #food-details .portion-btn.favourite-btn.active {
  background: linear-gradient(135deg, #ffc107, #ff9800);
  border-color: #c49100;
  color: #3d2c00;
}

/* Panel identity. The per-panel tint is a 10% wash of the panel's own accent —
   plenty against #1b1b1e, almost nothing against #fbfafd, which is what makes
   the three tabs look identical in Daylight. Raising the alpha and pulling the
   falloff in restores the orange / cyan / purple separation without touching
   --panel-accent itself, so the hue that identifies each tab is unchanged. */
:root[data-theme="daylight"] .grid-panel {
  background:
    radial-gradient(120% 90% at 12% 0%,
                    rgba(var(--panel-accent, 255, 255, 255), 0.30),
                    transparent 52%),
    linear-gradient(145deg, var(--surface-panel) 0%, var(--surface-panel-2) 100%) !important;
}

/* Same wash on the tab buttons and headings that name the panel. */
:root[data-theme="daylight"] #add-food-panel .card-header,
:root[data-theme="daylight"] #food-log-panel .card-header,
:root[data-theme="daylight"] #nutrition-panel .card-header {
  background: linear-gradient(180deg,
              rgba(var(--panel-accent, 255, 255, 255), 0.16),
              transparent) !important;
}

/* Grams chips in the diary. As a pure inverse they were a white pill on a
   near-white row — technically the brightest rung, visually a smudge. They read
   better as the opposite gesture: a faint darkening of the row with a dark
   label, which is what "editable value" looks like on a light surface. */
:root[data-theme="daylight"] .edit-grams,
:root[data-theme="daylight"] .edit-grams-input,
:root[data-theme="daylight"] #food-log .edit-grams-input {
  background: rgba(var(--wash-rgb), 0.07) !important;
  border-color: rgba(var(--wash-rgb), 0.16) !important;
  color: var(--inverse-text) !important;
}

:root[data-theme="daylight"] .edit-grams-input:focus,
:root[data-theme="daylight"] #food-log .edit-grams-input.is-open,
:root[data-theme="daylight"] #food-log .edit-grams-input:focus {
  background: rgba(var(--wash-rgb), 0.12) !important;
  border-color: rgba(var(--accent-rgb), 0.45) !important;
}

/* Real-time breakdown values. The number is gradient-clipped text running from
   white into the macro's own colour — a treatment that only works when "white"
   is the bright end. On a light page the top 30% of every digit is the page
   itself. Daylight runs the same gradient the other way: from the ink colour
   into a darkened mix of the macro hue, so the macro identity survives while the
   digits stay readable. */
:root[data-theme="daylight"] .rtb-value {
  background: linear-gradient(180deg,
              var(--text-primary) 22%,
              color-mix(in srgb, var(--macro-color) 58%, var(--text-primary)) 100%);
  -webkit-background-clip: text;
  background-clip: text;
}

/* The calories card's macro colour is #e0e0e0 — the neutral "silver" member of
   the macro set. Every other member is chromatic and carries itself onto a light
   ground; this one disappears. It moves to a mid grey so it still reads as the
   neutral member of the set, and the purple/cyan/orange members are untouched. */
:root[data-theme="daylight"] .macro-cals {
  --macro-color: #6b6379;
}

/* Primary modal action. --accent is a dark saturated purple here, and the label
   on it is --text-primary, which is near-black in Daylight: dark on dark. Rather
   than force a white label onto it — which would make one button the only
   inverted thing in a light dialog — the fill moves to the pale end of the same
   accent ramp and the label takes the deep rung. Same hue, same "this is the
   primary action" weight, legible ground. */
:root[data-theme="daylight"] .modal-action-primary {
  background: var(--accent-dark) !important;
  border-color: rgba(var(--accent-rgb), 0.34) !important;
  color: var(--accent-deep) !important;
}

:root[data-theme="daylight"] .modal-action-primary:hover,
:root[data-theme="daylight"] .modal-action-primary:focus-visible {
  background: #d9c9ff !important;
  border-color: rgba(var(--accent-rgb), 0.55) !important;
  color: var(--accent-deep) !important;
}

/* Its success sibling is a mid green that already carries white, so it only
   needs the same pale treatment to stay in the same visual family. */
:root[data-theme="daylight"] .modal-action-success {
  background: #d8f0de !important;
  border-color: rgba(60, 150, 85, 0.34) !important;
  color: #1f6b36 !important;
}

:root[data-theme="daylight"] .modal-action-success:hover,
:root[data-theme="daylight"] .modal-action-success:focus-visible {
  background: #c7e9d1 !important;
  border-color: rgba(60, 150, 85, 0.55) !important;
  color: #1f6b36 !important;
}

/* --- Buttons that live outside the app's own modal system ------------------
   .modal-action-primary got the pale-accent treatment in main.html, but the
   settings and profile dialogs are built on Bootstrap's .btn-primary and on
   .settings-save-action, which are saturated accent fills of their own. Left
   alone they are the only dark, inverted things in an otherwise light dialog.

   Bootstrap 5.3 paints .btn-primary entirely from --bs-btn-* custom properties,
   so setting those covers base, hover, active and disabled in one place —
   no !important, and the states stay consistent with each other. */
:root[data-theme="daylight"] .btn-primary {
  --bs-btn-bg: var(--accent-dark);
  --bs-btn-border-color: rgba(var(--accent-rgb), 0.34);
  --bs-btn-color: var(--accent-deep);
  --bs-btn-hover-bg: #d9c9ff;
  --bs-btn-hover-border-color: rgba(var(--accent-rgb), 0.55);
  --bs-btn-hover-color: var(--accent-deep);
  --bs-btn-active-bg: #cdb8ff;
  --bs-btn-active-border-color: rgba(var(--accent-rgb), 0.6);
  --bs-btn-active-color: var(--accent-deep);
  --bs-btn-disabled-bg: var(--accent-shadow);
  --bs-btn-disabled-border-color: rgba(var(--accent-rgb), 0.2);
  --bs-btn-disabled-color: var(--accent-deep);
}

/* profile.html and settings.html both carry a blanket
       .btn, .btn-accent, .btn-outline-light, a.accent-color {
           background-color: var(--surface-arrow) !important;
           color: var(--text-primary) !important;
       }
   which beats Bootstrap's variable-driven fill outright — .btn-primary has
   never actually been coloured on those two pages, in any theme. The --bs-btn-*
   block above is therefore ignored there, and the primary action has to be
   restated at a weight that can win. Scoped to Daylight, so the dark themes keep
   the neutral chip they have always had. (Worth deciding separately whether that
   blanket rule should be neutralising the primary action at all.) */
/* `background`, not `background-color`: activity.html paints .btn-primary with
   `background: linear-gradient(var(--accent-deep), ...)`, and a background-color
   override leaves that gradient IMAGE in place — the fill would stay dark while
   the label went dark with it. The shorthand clears the image too. */
:root[data-theme="daylight"] .btn.btn-primary,
:root[data-theme="daylight"] .app-modal-surface .btn-primary,
:root[data-theme="daylight"] .avatar-modal .btn-primary,
:root[data-theme="daylight"] .social-modal-content .btn-primary {
  background: var(--accent-dark) !important;
  border: 1px solid rgba(var(--accent-rgb), 0.34) !important;
  color: var(--accent-deep) !important;
}

:root[data-theme="daylight"] .btn.btn-primary:hover,
:root[data-theme="daylight"] .btn.btn-primary:focus-visible,
:root[data-theme="daylight"] .app-modal-surface .btn-primary:hover,
:root[data-theme="daylight"] .avatar-modal .btn-primary:hover,
:root[data-theme="daylight"] .social-modal-content .btn-primary:hover {
  background: #d9c9ff !important;
  border-color: rgba(var(--accent-rgb), 0.55) !important;
  color: var(--accent-deep) !important;
}

/* The settings/contact save action, in both its page and rail forms. */
:root[data-theme="daylight"] .settings-save-action,
:root[data-theme="daylight"] .settings-rail-actions .settings-save-action {
  background: linear-gradient(135deg, var(--accent-dark), #efe7ff) !important;
  border-color: rgba(var(--accent-rgb), 0.34) !important;
  color: var(--accent-deep) !important;
  box-shadow: 0 4px 14px rgba(var(--accent-rgb), 0.14) !important;
}

:root[data-theme="daylight"] .settings-save-action:hover,
:root[data-theme="daylight"] .settings-rail-actions .settings-save-action:hover {
  background: linear-gradient(135deg, #d9c9ff, var(--accent-dark)) !important;
  border-color: rgba(var(--accent-rgb), 0.55) !important;
  color: var(--accent-deep) !important;
}

/* Bootstrap's secondary outline is #6c757d on both counts — readable on white,
   but a full step quieter than every other label in the same dialog footer, so
   "Peruuta" reads as disabled beside a coloured primary. */
:root[data-theme="daylight"] .btn-outline-secondary {
  --bs-btn-color: var(--text-body);
  --bs-btn-border-color: var(--surface-border);
  --bs-btn-hover-bg: var(--surface-chip);
  --bs-btn-hover-border-color: var(--surface-edge);
  --bs-btn-hover-color: var(--text-primary);
  --bs-btn-active-bg: var(--surface-chip);
  --bs-btn-active-border-color: var(--surface-edge);
  --bs-btn-active-color: var(--text-primary);
}

/* X's brand black was inverted to near-white so it would read against a
   near-black page. On a light one that reasoning runs the other way, and the
   original black is the correct mark again. */
:root[data-theme="daylight"] .social-pill[data-platform="twitter"] {
  border-left-color: #16161a;
}

/* Save-food button. Its fill is a wash of --state-success, which is fixed
   across themes, so the hue stays — but the label was #d6f2e5, a pale mint that
   only had contrast because the wash sat on near-black. On the same wash over a
   light panel it is about 1.3:1. The label drops to the dark end of the same
   green (the tone its own shadow already uses) rather than going neutral, so the
   button still reads as the success action rather than as a grey one. */
:root[data-theme="daylight"] .save-food-btn {
  color: #14614a;
}

/* ---------------------------------------------------------------------------
   The chroma 9-45 band.

   The automated sweeps split colours in two: neutral (chroma <= 8) got mapped
   onto the ramps, anything more saturated was treated as a deliberate hue and
   left alone. Between those sits a third kind — a grey with a faint cast, or a
   pale tint of a state colour — which is neither. Those read as "nearly white"
   on a dark page and as "nearly invisible" on a light one, and no ramp mapping
   fits them because the cast is the point.

   A pass over every converted file found nine. Five were fixed at the call site
   (they were plain enough to take a ramp token). The four below keep their hue
   and only move along it, because the hue is carrying meaning: saved-green,
   error-red, master-tier amber.

   Deliberately NOT touched by that pass: .rewards-title #a8b2d1 in three files.
   It sits inside .toast-achievement, whose gradient runs --bs-black ->
   --accent-deep -> --bs-black. Both ends are dark in every theme including this
   one, so the toast never goes light and its pale label is still correct.
   ------------------------------------------------------------------------- */

/* Master-tier achievement title on hover. The card goes to a light amber here,
   so a near-white title vanishes exactly when the user points at it. */
:root[data-theme="daylight"] .achievement-item-new.tier-glow-5:hover .achievement-title {
  color: #5c3300;
  text-shadow: 0 0 12px rgba(255, 128, 0, 0.35);
}

/* Highlight-selection status line: three states carrying three meanings. Each
   moves to the dark end of its own hue rather than to a neutral. */
:root[data-theme="daylight"] .profile-highlight-save-state.is-saved { color: #2f7a5c; }
:root[data-theme="daylight"] .profile-highlight-save-state.is-error { color: #a33232; }

/* The selected-highlight chip is a pale mint label on a 13% mint wash — fine on
   near-black, about 1.2:1 on a light panel. */
:root[data-theme="daylight"] .profile-highlight-selected-chip { color: #1c6e50; }
:root[data-theme="daylight"] .profile-highlight-selected-chip b { color: #16694a; }
:root[data-theme="daylight"] .profile-highlight-selected-chip button { color: #2f7a5c; }

/* The last dark slabs in the app: layout.html's alert bars. Their tinted
   near-blacks were the only backgrounds the sweep left behind that stay dark on
   a light page. Same three hues, inverted — light tint, dark label. .alert-info
   is already a light slab and needs nothing. */
:root[data-theme="daylight"] .alert-success {
  background-color: #e3f4e6;
  color: #1c5c2b;
  border-color: #b9dfc2;
}

:root[data-theme="daylight"] .alert-danger {
  background-color: #fbe6e6;
  color: #8c2626;
  border-color: #efc2c2;
}

:root[data-theme="daylight"] .alert-warning {
  background-color: #f8f2dd;
  color: #6b5a12;
  border-color: #e4d8a8;
}

/* The unallocated-calories bar is a grey-to-light-grey ramp at 0.7 opacity —
   deliberately the quietest thing in the chart. On a light track that reading
   becomes "not there at all", so Daylight keeps the same quiet intent with a
   pair that still has somewhere to be quiet against. */
:root[data-theme="daylight"] .progress-bar-other {
  background: linear-gradient(135deg, #9c96a8 0%, #c4bed2 100%);
}

/* The TDEE save button carries its fill on an ID selector with !important, so
   the generic .btn-primary treatment above cannot reach it. Restated at a
   specificity that can — same pale-accent-with-deep-label pairing, so the two
   primary actions on the page still look like the same button. */
:root[data-theme="daylight"] #save-tdee-btn:not(.btn-flash-success):not([data-locked="true"]) {
  background: var(--accent-dark) !important;
  border-color: rgba(var(--accent-rgb), 0.34) !important;
  color: var(--accent-deep) !important;
}

/* The native date picker. `color-scheme` is what tells the browser which of its
   OWN widgets to draw — the calendar popup, the spinners, the clear button —
   and none of that is reachable from CSS. Forcing it dark is right for six
   themes and wrong for this one. */
:root[data-theme="daylight"] .dark-calendar {
  color-scheme: light;
}

/* The "magic" button. Its fill was an 8-digit #212529bf that no sweep matched;
   it now rides --glass-rgb, so the button itself goes light here. Two things
   then have to follow it or the effect breaks:

     - the shine. It sweeps a translucent WHITE band across a dark button. On a
       white button white-on-white is nothing at all, so Daylight sweeps a soft
       dark band instead — same gesture, opposite channel, and much lower alpha
       because a dark streak reads far more strongly than a light one.
     - the cast glow (magicGlow) fires a white-plus-gold flash, which is
       likewise invisible against a light page. It becomes an accent flash. */
:root[data-theme="daylight"] .btn-magic {
  background-color: var(--surface-raise) !important;
  border: 1px solid rgba(var(--accent-rgb), 0.3) !important;
  color: var(--accent-deep) !important;
}

/* Hover is gated to real pointers. On touch, :hover latches after a tap and
   survives until you touch something else — so casting the spell left the
   button sitting in its lit state afterwards instead of returning to rest.
   (The dark themes have the same latch, but their hover is a near-invisible
   shift from rgba(glass,0.75) to --surface-field, so nobody sees it. Here it is
   a pale accent fill against white, and it is obvious.) */
@media (hover: hover) and (pointer: fine) {
  :root[data-theme="daylight"] .btn-magic:hover {
    background-color: var(--accent-shadow) !important;
    box-shadow: 0 0 10px rgba(var(--accent-rgb), 0.22);
  }
}

/* The cast is GOLD, not purple. The dark themes already say so — magicGlow
   flashes `rgb(250,250,250)` with `rgba(255,215,0,0.5)` under it, i.e. a white
   hot core wrapped in gold. The first Daylight version reached for the accent
   because that is what the rest of this pass does, and it was wrong: this
   animation is the one place in the app that means "something good just
   happened", and that reads gold everywhere else too (the locked day, the
   achievement toast, the tier badges).

   White cannot be the core on a light page, so the gold carries it alone —
   deeper and a touch stronger than the dark version's, since a glow has to
   subtract from a white page rather than add to a black one. */
:root[data-theme="daylight"] .btn-magic::before {
  background: linear-gradient(90deg,
              transparent,
              rgba(212, 160, 20, 0.34),
              transparent);
}

:root[data-theme="daylight"] .magic-flash {
  animation: magicGlowDaylight 0.6s ease-out;
}

@keyframes magicGlowDaylight {
  0%   { box-shadow: 0 0 0 rgba(184, 137, 26, 0); }
  50%  { box-shadow: 0 0 22px rgba(184, 137, 26, 0.62),
                     0 0 9px rgba(212, 160, 20, 0.5); }
  100% { box-shadow: 0 0 0 rgba(184, 137, 26, 0); }
}

/* Bootstrap's pill tabs. Every place the app styles its own pills is already
   handled, but .nav-pills carries a default of white-on-#0d6efd for any pill the
   app has not overridden — and on a light page that is the one control still
   shouting. Setting the two variables covers the lot in one rule, in the same
   pale-fill / deep-label pairing the primary buttons use. */
:root[data-theme="daylight"] .nav-pills {
  --bs-nav-pills-link-active-bg: var(--accent-dark);
  --bs-nav-pills-link-active-color: var(--accent-deep);
}

/* ---------------------------------------------------------------------------
   workout.html: tinted labels whose hue is the message.

   This page leans on pale tints of the state colours to say things — mint for
   "saved", ice blue for a cardio toast, warm white for a personal record. Each
   only had contrast because it sat on near-black; on a light ground every one
   of them lands between 1.2:1 and 1.8:1. They move to the dark end of their own
   hue rather than to a neutral, so the message survives the move.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .generated-feedback-thanks,
:root[data-theme="daylight"] .saved-workout-toolbar,
:root[data-theme="daylight"] .start-new-workout-btn {
  color: #1c6e50;
}

:root[data-theme="daylight"] .saved-workout-toolbar strong {
  color: #14614a;
}

:root[data-theme="daylight"] .generator-history-note,
:root[data-theme="daylight"] .generator-coach-summary {
  color: #3f3350;
}

:root[data-theme="daylight"] .toast-text {
  color: #0f5c66;
  text-shadow: none;
}

:root[data-theme="daylight"] .pr-text {
  color: #5c4600;
  text-shadow: none;
}

/* ---------------------------------------------------------------------------
   workout.html, second pass. Everything here is a control the page paints from
   a rule the token system cannot reach — a Bootstrap default, an inline style
   built in JS, or a filter.
   ------------------------------------------------------------------------- */

/* Bootstrap badges. .badge sets `color: var(--bs-badge-color)`, which is white,
   and the app uses them on pale grounds throughout the template modals — white
   on white with only the grey background hinting there is a chip there. */
:root[data-theme="daylight"] .badge {
  --bs-badge-color: var(--text-body);
}

:root[data-theme="daylight"] .badge.bg-secondary,
:root[data-theme="daylight"] .badge.rounded-pill {
  background-color: var(--surface-chip) !important;
  color: var(--text-body) !important;
}

/* Form fields. --surface-chip is a hairline against near-black and almost
   nothing against white, so the field edge disappears and inputs stop reading
   as inputs. --surface-edge is the heavier rule for exactly this. */
:root[data-theme="daylight"] .form-control,
:root[data-theme="daylight"] .form-select {
  border-color: var(--surface-edge) !important;
}

:root[data-theme="daylight"] .form-control:focus,
:root[data-theme="daylight"] .form-select:focus {
  border-color: var(--accent) !important;
}

/* The bulk-add summary rows are built in JS with a 5% wash. Five percent of
   white on near-black is a visible step; five percent of near-black on white is
   not, so the rows stop separating from the modal. */
:root[data-theme="daylight"] #bulkSummaryList > div {
  background: var(--surface-chip) !important;
  border: 1px solid var(--surface-border);
}

/* Bootstrap draws the close glyph as a black SVG; this modal inverts it to
   white. On a light dialog the un-inverted original is the correct one. */
:root[data-theme="daylight"] #addNewSetModal .btn-close {
  filter: none;
}

/* Completed-exercise check. #7df2ad is a neon mint tuned for near-black — 1.5:1
   on a light row. Same green, dark end. */
:root[data-theme="daylight"] .complete-exercise-icon i.completed {
  color: #17794f;
}

/* The saved-workout strip is an 8% green wash with a 22% border. Both were sized
   against near-black; on white the whole strip reads as nothing at all. */
:root[data-theme="daylight"] .saved-workout-toolbar {
  background: rgba(25, 135, 84, 0.14);
  border-top-color: rgba(25, 135, 84, 0.3);
  border-bottom-color: rgba(25, 135, 84, 0.3);
}

/* Week stepper chevrons: the lucide macro emits currentColor, so they follow the
   button — but only if the button's own colour survives whatever wraps it. Pin
   it, since these are two of the smallest tap targets on the page. */
:root[data-theme="daylight"] .workout-date-button,
:root[data-theme="daylight"] .workout-date-button svg {
  color: var(--text-body);
}

/* The template preview pane carries Bootstrap's .text-light on its body, which
   the bridge already flips — but the pane itself is painted from an inline
   style attribute, so it needs the surface named here. */
:root[data-theme="daylight"] #template-preview-modal-body {
  background-color: var(--surface-panel) !important;
  color: var(--text-body);
}

/* The completion checkbox. Its border is --inverse-solid, i.e. pure white — a
   crisp edge against a dark row and literally invisible against a light one.
   The box is the target you have to find before you can tap it, so it takes a
   real hairline here rather than an inverse one. */
:root[data-theme="daylight"] .complete-exercise-icon {
  border-color: var(--surface-edge) !important;
  background: var(--surface-raise) !important;
  color: var(--text-faint);
}

:root[data-theme="daylight"] .complete-exercise-icon:hover {
  border-color: var(--accent-soft) !important;
}

/* Set weight/rep fields. These are typed into constantly, often mid-set with
   one hand, so their edge wants to be heavier than the app's ordinary field
   rule — closer to a real box than a hairline. */
:root[data-theme="daylight"] .set-input,
:root[data-theme="daylight"] input.set-weight,
:root[data-theme="daylight"] input.set-reps,
:root[data-theme="daylight"] .workout-table input.form-control {
  border-color: #656262 !important;
}

/* Active rest timer. The fill was a 82%-opaque mid grey — near-black's idea of
   "lit up". On a light page it is a dark blob with darker text inside it. */
:root[data-theme="daylight"] .timer-icon-clickable.active {
  color: var(--accent-deep);
  background-color: rgba(var(--accent-rgb), 0.16);
}


/* ===========================================================================
   DAYLIGHT — THE COMPLETION DIALOGS (workout.html + renderworkout.js)
   ===========================================================================

   The workout-complete flow is two stacked dialogs — the results modal and the
   XP card — and both were built as *dark* glass: pale text lifted off a near-
   black slab, thin white washes for structure, and a handful of gradients that
   start at pure white because pure white was the brightest thing available.

   On a light page every one of those idioms fails in the same direction: the
   bright end of the gradient IS the page, the 3%-opacity watermark has nothing
   to sit against, and the 4%-opacity wash that separated a card from the slab
   separates nothing. That is why the screenshots read as "half the text is
   gone" rather than as "the text is a bit light" — the top of each glyph is
   literally the background colour.

   Nothing below changes a hue. Each rule takes the same idiom and points it the
   other way: gradients start at the ink instead of at the page, washes darken
   instead of lighten, and the pale state colours move to the dark end of their
   own hue.
   ======================================================================== */

/* --- Gradient-clipped numbers --------------------------------------------
   .achievement-value and .xp-gained-display paint the digits with a gradient
   and clip it to the glyphs. Both run `--inverse-solid 20% -> <colour> 100%`,
   and --inverse-solid is white in every theme including this one, so the top
   fifth of every digit is painted the same colour as the card behind it.

   Same construction, reversed: start at the ink and fall off into the card's
   own colour. The card identity (purple volume, gold PR, orange fire) is in
   the second stop, so it survives untouched — this only replaces the end that
   used to be "the brightest thing on the page" with "the darkest".
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .achievement-value {
  background: linear-gradient(180deg,
              var(--text-primary) 22%,
              color-mix(in srgb, var(--card-color, var(--accent)) 62%, var(--text-primary)) 100%);
  -webkit-background-clip: text;
  background-clip: text;
}

:root[data-theme="daylight"] .xp-gained-display {
  background: linear-gradient(180deg, var(--accent-deep) 20%, var(--accent) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  filter: drop-shadow(0 3px 10px rgba(var(--accent-rgb), 0.22));
}

/* --- The metric cards -----------------------------------------------------
   `rgba(--surface-note-rgb, 0.4)` was a lightening wash: 40% of a blue-grey
   over near-black lifts the card clear of the slab. Daylight's note colour is
   already near-white, so at 40% over a white dialog the card and the dialog are
   the same tone and the grid reads as one flat sheet with text floating on it.

   The card goes to the page tone with a real edge — the same relationship the
   dark theme has, expressed the way a light UI expresses it: the card is a step
   DOWN from the surface it sits on, and the hairline closes it.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .achievement-card.result-metric-card,
:root[data-theme="daylight"] .streak-display {
  background: var(--surface-page);
  border: 1px solid var(--surface-border);
  box-shadow: 0 1px 2px rgba(var(--shadow-rgb), calc(0.9 * var(--shadow-strength)));
}

:root[data-theme="daylight"] .achievement-card.result-metric-card:hover {
  background: var(--surface-sunken);
  box-shadow: 0 8px 18px rgba(var(--shadow-rgb), calc(1.2 * var(--shadow-strength)));
}

/* The corner glow is a white radial — invisible here, and pointing the wash the
   other way would put a dark smudge in the corner of a light card. It is
   decoration on a card that now has an edge, so it comes off.

   Scoped to .result-metric-card, like the two rules above it: profile.html
   reuses the .achievement-card class for its achievement modal, and its
   ::before is a completely different thing — the shimmer sweep — which this
   rule would switch off. Every .achievement-card in the workout flow carries
   .result-metric-card, so the narrower selector loses nothing here. */
:root[data-theme="daylight"] .achievement-card.result-metric-card::before {
  display: none;
}

/* The oversized icon behind each card. 3% of a mid colour over near-black is a
   visible ghost; over #f2f0f6 it is nothing at all, which is why the watermark
   "disappeared". Dark type on light needs roughly three times the alpha to sit
   at the same perceived weight. */
:root[data-theme="daylight"] .card-watermark {
  opacity: 0.10;
}

/* The icon disc. Its background was a 5% white wash; the flip makes it a 5%
   dark one, which is right, but the glow around it is a solid 15px of the
   card's own saturated colour and that is far too loud once the card behind it
   is pale. It shrinks to a tint. */
:root[data-theme="daylight"] .result-metric-card .achievement-icon {
  background: color-mix(in srgb, var(--card-color, var(--accent)) 13%, transparent);
  border-color: color-mix(in srgb, var(--card-color, var(--accent)) 30%, transparent);
  box-shadow: none;
}

/* Set-count delta chips. Mint on near-black, invisible on white; the hue is the
   message ("you did more sets"), so it moves down its own hue rather than to a
   neutral. The decrease chip already resolves dark via --text-dim. */
:root[data-theme="daylight"] .sets-delta-chip.increase {
  border-color: rgba(31, 107, 54, 0.30);
  background: rgba(31, 107, 54, 0.09);
  color: #1f6b36;
}

/* --- The XP card ----------------------------------------------------------
   Same wash problem one dialog up: source rows lifted with 40% of the note
   colour, and the level-progress well painted with `--sink-rgb` at 30% — which
   in Daylight is WHITE at 30% over a white card, i.e. nothing.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .xp-source-item {
  background: var(--surface-page);
  border-color: var(--surface-border);
}

:root[data-theme="daylight"] .xp-source-item:hover {
  background: var(--surface-sunken);
}

:root[data-theme="daylight"] .level-progress {
  background: var(--surface-page);
  border-color: var(--surface-border);
}

/* --- The new-exercises toast ---------------------------------------------
   One of the app's hardcoded dark slabs: `rgba(10, 40, 60, 0.9)` as the far end
   of the gradient, carrying ice-blue text. Under Daylight the near end flipped
   with --bar-rgb and the far end did not, so the panel ran from white into navy
   with pale cyan text over both halves. It becomes what it always meant — a
   cyan-tinted note — with the text at the dark end of the same cyan. The 4px
   cyan rule on the left, which is the actual identity, is unchanged.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .new-exercises-toast {
  background: linear-gradient(135deg,
              rgba(0, 188, 212, 0.10) 0%,
              rgba(0, 188, 212, 0.04) 100%),
              var(--surface-raise);
  border-color: rgba(0, 150, 170, 0.28);
  box-shadow: 0 6px 16px rgba(var(--shadow-rgb), calc(0.9 * var(--shadow-strength)));
}

:root[data-theme="daylight"] .toast-icon {
  background: rgba(0, 188, 212, 0.13);
  border-color: rgba(0, 150, 170, 0.30);
  box-shadow: none;
  color: #0f5c66;
}

:root[data-theme="daylight"] .toast-subtext {
  color: #256b74;
}

/* The baseline card is the one .workout-highlight variant with its own opaque
   background — a 42% dark purple that stays dark whatever the theme does. */
:root[data-theme="daylight"] .workout-highlight.baseline-highlight {
  background: linear-gradient(135deg,
              rgba(var(--accent-rgb), 0.10),
              rgba(var(--glass-rgb), 0.96));
}

/* --- Primary actions, unified --------------------------------------------
   main.html and profile.html already run their primary modal action as a pale
   accent fill with a deep accent label (see .modal-action-primary above). The
   workout dialogs predate that and each rolls its own saturated-accent-plus-
   white-label button, which makes them the only inverted controls in an
   otherwise light dialog — and puts three different treatments on the same
   gesture across three pages.

   They all move to the one treatment. Same hue, same weight, same button.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .btn-jerex,
:root[data-theme="daylight"] .btn-primary-results,
:root[data-theme="daylight"] .continue-btn,
:root[data-theme="daylight"] .generated-feedback-submit {
  background: var(--accent-dark) !important;
  background-image: none !important;
  border: 1px solid rgba(var(--accent-rgb), 0.34) !important;
  color: var(--accent-deep) !important;
  box-shadow: none;
}

:root[data-theme="daylight"] .btn-jerex:hover,
:root[data-theme="daylight"] .btn-jerex.active,
:root[data-theme="daylight"] .btn-primary-results:hover,
:root[data-theme="daylight"] .continue-btn:hover,
:root[data-theme="daylight"] .generated-feedback-submit:hover {
  background: #d9c9ff !important;
  background-image: none !important;
  border-color: rgba(var(--accent-rgb), 0.55) !important;
  color: var(--accent-deep) !important;
}

/* "Tallenna pohjaksi" is the same gesture in chip form — an inline span inside
   the prompt button. It takes the same fill and label, but no border: it is
   laid out inline, and a border would change the chip's metrics rather than
   just its colours. */
:root[data-theme="daylight"] .results-template-link {
  background: var(--accent-dark);
  color: var(--accent-deep);
}

:root[data-theme="daylight"] .results-template-prompt:hover .results-template-link,
:root[data-theme="daylight"] .results-template-prompt:focus-visible .results-template-link {
  background: #d9c9ff;
  color: var(--accent-deep);
}

/* Its quiet sibling. `rgba(--wash-rgb, 0.1)` flips to a faint darkening, which
   is correct but leaves the button edgeless against the dialog footer. */
:root[data-theme="daylight"] .btn-secondary-results {
  background: var(--surface-raise);
  border: 1px solid var(--surface-edge);
  color: var(--text-body);
}

/* "Piditkö treenistä?" — the label above the save-as-template button. At 52% of
   the wash it lands on 3.1:1, which is under the mark for 0.72rem type. */
:root[data-theme="daylight"] .results-template-prompt {
  color: var(--text-secondary);
}

/* --- The save button ------------------------------------------------------
   Its lift is two shadows, one of which rides --shadow-strength (0.28 here) and
   one of which is a 12%-alpha accent glow tuned to bloom against near-black.
   Together they come to almost nothing on a light page, and this is the button
   the whole screen exists to get you to press. Both get their weight back —
   the accent glow does the work, since a light UI reads a coloured halo more
   readily than a grey one.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .workout-save-btn {
  box-shadow: 0 4px 12px rgba(var(--shadow-rgb), calc(1.6 * var(--shadow-strength))),
              0 0 0 3px rgba(var(--accent-rgb), 0.10),
              0 6px 22px rgba(var(--accent-rgb), 0.30);
}

:root[data-theme="daylight"] .workout-save-btn:hover:not(:disabled) {
  box-shadow: 0 8px 26px rgba(var(--accent-rgb), 0.46),
              0 0 0 4px rgba(var(--accent-rgb), 0.14);
}

:root[data-theme="daylight"] .workout-save-btn svg {
  color: var(--accent);
}


/* ===========================================================================
   DAYLIGHT — DEBUG-LIST FIXES ACROSS THE REMAINING PAGES
   ======================================================================== */

/* --- index.html: the dashboard hero --------------------------------------
   The hero is this page's single light source: a purple ellipse at 14% over the
   card gradient. 14% of a saturated purple over near-black is a clear glow; the
   same 14% over #fbfafd is about two units of tint, so the panel that anchors
   the whole page reads as an ordinary card. Alpha roughly doubles, and the
   falloff is pulled in so the tint has an edge to be seen against rather than
   fading across the full width.

   Both the desktop rule and the full-bleed mobile one are restated, because the
   mobile variant carries its own !important background and would otherwise keep
   the weak ellipse below 992px.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .hero-glass-panel {
  background:
    radial-gradient(ellipse at top center,
                    rgba(var(--accent-rgb), 0.30) 0%,
                    rgba(var(--accent-rgb), 0.07) 46%,
                    transparent 72%),
    linear-gradient(145deg, var(--surface-panel) 0%, var(--surface-panel-2) 100%) !important;
}

@media (max-width: 991px) {
  :root[data-theme="daylight"] .hero-glass-panel {
    background:
      radial-gradient(ellipse at top center,
                      rgba(var(--accent-rgb), 0.30) 0%,
                      rgba(var(--accent-rgb), 0.07) 46%,
                      transparent 72%),
      linear-gradient(180deg, var(--surface-panel) 0%, var(--surface-page) 100%) !important;
  }
}

/* --- main.html: the diary panel ------------------------------------------
   The food-log heading chip is a 13% cyan wash with the cyan itself as the icon
   colour. #6dc0d5 is 1.9:1 on a white chip. It moves down its own hue — the
   panel identity is the hue, not the lightness — and the chip wash comes up to
   match the other two panels. */
:root[data-theme="daylight"] .food-log-heading-icon {
  color: #0f6b7d;
}

/* Diary rows. The group list sits on a 4.5% wash, which was a lightening on
   near-black and is a greying here — so every row in the diary is a shade
   dirtier than the panel around it. On a light page the resting state of a list
   row is the surface itself; the meal's colour gradient still runs over it, and
   hover/selection still darken from there, so nothing loses its cue. */
:root[data-theme="daylight"] #food-log-panel .group-items {
  background:
    linear-gradient(180deg,
                    color-mix(in srgb, var(--group-color) 10%, transparent) 0%,
                    transparent 72%),
    var(--surface-raise);
}

/* --- main.html: public-recipe filter pills -------------------------------
   Every pill is a pale tint of the thing it filters by, drawn as both the
   border and the label, on a transparent ground. All six were picked against
   near-black and all six land between 1.2:1 and 1.9:1 on a light modal — the
   row reads as grey outlines.

   Each pill moves down its own hue rather than to a neutral, so "gold =
   popular, cyan = carbs, orange = fats" survives; only the lightness changes.
   The active-state tints stay as they are: they are already alpha washes of the
   same hue and they darken correctly on their own.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .filter-pill {
  border-color: var(--surface-edge);
}

:root[data-theme="daylight"] .filter-pill.pill-popular { border-color: #8a6510; color: #8a6510; }
:root[data-theme="daylight"] .filter-pill.pill-cals    { border-color: var(--text-secondary); color: var(--text-body); }
:root[data-theme="daylight"] .filter-pill.pill-pro     { border-color: #6b2bbf; color: #6b2bbf; }
:root[data-theme="daylight"] .filter-pill.pill-carbs   { border-color: #0f6b7d; color: #0f6b7d; }
:root[data-theme="daylight"] .filter-pill.pill-fats    { border-color: #a24512; color: #a24512; }
:root[data-theme="daylight"] .filter-pill.pill-fiber   { border-color: #96501f; color: #96501f; }

/* --- profile.html: the avatar ---------------------------------------------
   The default frame is --surface-border, the app's hairline. Against a dark
   page that is a visible ring; against a white card, with a light avatar inside
   it, there is a white-on-white seam and the picture has no edge. Every earned
   border (gold, platinum, glow…) draws its own 4px ring and is unaffected. */
:root[data-theme="daylight"] .avatar-with-border .avatar-img,
:root[data-theme="daylight"] .avatar-container .avatar-img {
  border-color: var(--surface-edge);
}

/* --- The donor role, both places it appears -------------------------------
   "Lahjoittaja" is gold gradient text with a white stop swept through it on a
   6s loop — the glint. On a dark page the white stop is the highlight; on a
   light one it is a hole that travels across the word twice a cycle, taking a
   letter or two with it. The sweep stays (it is the whole point of the
   treatment) but the stops move into a range that stays legible on white: the
   glint becomes a pale amber rather than white, the darks go deeper, and a
   hairline shadow keeps the lightest stop off the background.

   Two class names, one component: profile.html calls it .profile-role-donor and
   index.html calls it .dashboard-user-role-donor. The rules are otherwise
   identical (index writes the 46% stop as #ffffff where profile writes
   var(--inverse-solid), which resolves to the same white), so both take the
   same override. Checked the rest of the app: those are the only two.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .profile-role-donor,
:root[data-theme="daylight"] .dashboard-user-role-donor {
  /* background-IMAGE, not the `background` shorthand. The shorthand would reset
     background-position along with everything else, and both pages carry a
     `@media (prefers-reduced-motion: reduce)` rule that parks the sweep at
     `65% 50%` — a lower-specificity rule this one would then outrank, leaving
     reduced-motion users on Daylight looking at 0% 0% instead of the stop the
     rule picked. Setting only the image leaves position and size to the page,
     where they already are. */
  background-image: linear-gradient(
      105deg,
      #7a5410 0%,
      #a8791d 28%,
      #d9ae43 46%,
      #8a6212 64%,
      #6b4a0c 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: #7a5410;
  text-shadow: 0 1px 1px rgba(74, 50, 6, 0.30);
}

/* --- activity.html: the calculating overlay -------------------------------
   The overlay flips with --surface-black, so it is a pale lavender sheet here.
   "Analysoidaan…" was left on #bebebe, which was a soft grey against black and
   is 1.3:1 against this. Its sibling line already resolves through the text
   ramp; this one just never had a token. */
:root[data-theme="daylight"] .analyzing-text {
  color: var(--text-secondary);
}

/* --- The results header ---------------------------------------------------
   `.results-header` asks for `linear-gradient(106deg, var(--accent-deep),
   var(--bs-black))`. Bootstrap 5.3 ships --bs-black-rgb but NOT --bs-black, so
   that gradient has been invalid — and therefore absent — in every theme since
   it was written. The dark themes get away with it: the header is transparent,
   the modal behind it is near-black, and the hardcoded `color: white` reads
   fine. Daylight does not: white on a white dialog.

   The declaration is left exactly as it is rather than "fixed", because
   supplying --bs-black would give all six dark themes a header gradient they
   have never had. Daylight instead paints the band it was reaching for, in the
   direction a light dialog wants, and takes the ink colour with it.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .results-header {
  background: linear-gradient(106deg,
              rgba(var(--accent-rgb), 0.20),
              rgba(var(--accent-rgb), 0.05));
  border-bottom: 1px solid rgba(var(--accent-rgb), 0.18);
  color: var(--text-primary);
}

:root[data-theme="daylight"] .results-title {
  color: var(--accent-deep);
  text-shadow: none;
}

/* --- The PR highlight banner ---------------------------------------------
   --highlight-color is doing three jobs at once: the kicker text, the big
   value, and the card's own border and tint. The four variants were picked as
   *glow* colours against near-black, so on white the border and tint are right
   and the two pieces of text are 1.6:1.

   Splitting the token would mean touching four rules the dark themes rely on.
   Instead the two text roles take a darkened mix of whatever --highlight-color
   is, computed at use: the hue stays exactly the one the variant chose, only
   the lightness moves, and the border and background tint keep the original.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .workout-highlight-kicker,
:root[data-theme="daylight"] .workout-highlight-value {
  color: color-mix(in srgb, var(--highlight-color) 55%, #1a1024);
}

/* Volume/decline deltas under each metric card. Mint and salmon: both were
   chosen to glow on near-black and both land near 1.7:1 on a light card. Same
   move down their own hues as the delta chips above. */
:root[data-theme="daylight"] .achievement-change.improvement,
:root[data-theme="daylight"] .achievement-change.improvement i {
  color: #1f6b36;
}

:root[data-theme="daylight"] .achievement-change.decline,
:root[data-theme="daylight"] .achievement-change.decline i {
  color: #a32b2b;
}

/* The bare .neutral rule (an amber) is shared with the streak scale and is
   frozen; only the .achievement-change.neutral variant is in play here and it
   already resolves through --text-quiet. */


/* ===========================================================================
   DAYLIGHT — profile.html, the achievement wall and its modal
   ======================================================================== */

/* --- Tier status badges ---------------------------------------------------
   Five tiers, five pill colours, and four of the five carry the comment
   "lightened for readability" — lightened against a near-black card, which is
   what makes them 1.3:1 to 2.0:1 against a white one. Only tier 4 reads today,
   and only because it resolves through --accent-pale, which already flipped
   with the theme.

   Each badge moves to the dark end of its own metal: bronze stays bronze,
   silver stays silver, gold stays gold. The 15%-alpha backgrounds and the
   40%-alpha borders are untouched — those are washes of the true tier colour
   and they behave correctly on a light ground already.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .achievement-item-new.tier-glow-1 .achievement-status-badge {
  color: #7d4a17;
}

:root[data-theme="daylight"] .achievement-item-new.tier-glow-2 .achievement-status-badge {
  color: #55505c;
  border-color: rgba(120, 120, 130, 0.45);
}

:root[data-theme="daylight"] .achievement-item-new.tier-glow-3 .achievement-status-badge {
  color: #7a5c00;
  border-color: rgba(190, 160, 0, 0.5);
}

/* Tier 4 already resolves through --accent-pale and is the one badge that
   reads correctly as-is. Left alone on purpose. */

:root[data-theme="daylight"] .achievement-item-new.tier-glow-5 .achievement-status-badge {
  color: #8a4200;
}

/* --- The achievement modal icon -------------------------------------------
   .achievement-icon is a bare 80px box in profile.html — no fill of its own,
   just a blurred radial behind it. That radial is a white glow, so under
   Daylight it flips to a dark one and puts a grey smudge behind the trophy.
   The icon wants no ground at all here: the card is already white and the
   trophy is the only thing on it. */
:root[data-theme="daylight"] .achievement-card .achievement-icon::before {
  background: none;
}

/* --- The achieved-card shimmer --------------------------------------------
   `.achievement-card.achieved::before` is a lens-flare sweep built from three
   stops of --wash-rgb. Flipping the channel turns it into a dark streak, which
   is the correct direction for a wash and the wrong one for a *shine*: a
   highlight passing over a light card cannot be made of shadow, and it cannot
   be made of white either, because the card already is.

   So it becomes a tint sweep instead — the same gold the achievement iconography
   is built from, at an alpha that reads on white. Geometry, timing and easing
   are untouched; only what the flare is made of changes.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .achievement-card.achieved::before {
  background: linear-gradient(
    115deg,
    transparent 0%,
    transparent 40%,
    rgba(196, 148, 20, 0.06) 45%,
    rgba(196, 148, 20, 0.26) 50%,
    rgba(196, 148, 20, 0.06) 55%,
    transparent 60%,
    transparent 100%
  );
}

/* Its gold inset bloom is a 5%-alpha glow tuned to near-black; on white it is
   nothing. Same colour, enough alpha to be a bloom again. */
:root[data-theme="daylight"] .achievement-card.achieved::after {
  box-shadow: inset 0 0 22px rgba(212, 160, 20, 0.16);
}


/* ===========================================================================
   DAYLIGHT — program_builder.html, workout_showcase.html, leaderboards.html
   ===========================================================================

   The last three templates. All three were built on the same idioms as the
   rest of the app, so the token conversion carries most of the work; what is
   left here is the handful of places where a literal had no honest token, or
   where a colour was picked to glow against near-black and cannot simply flip.
   ======================================================================== */

/* --- Shared: the muscle-group header --------------------------------------
   `linear-gradient(135deg, var(--group-color), rgba(0,0,0,0.8))` — the group's
   own colour fading into the page ground. That tail is a SINK (a darkening
   painted into a surface), not a shadow, so it flips to white with --sink-rgb
   and the header becomes "group colour fading into the light page", which is
   the same gesture. The white icon and white title still sit on the saturated
   end, exactly as in the dark themes.

   Nothing to override — noted here because it is the one place on these pages
   where the sink channel does real work, and because the group colours
   themselves are frozen and must stay so.

   The exercise row beneath it is a different matter: its divider resolves to
   --surface-black, which is a pale lavender here, and its ground resolves to
   --surface-field, which goes white. Both correct; both leave the row with no
   edge at all, since white on white is not a row. It takes the page tone. */
:root[data-theme="daylight"] .exercise-header {
  background: var(--surface-page);
  border-bottom-color: var(--surface-border);
}

:root[data-theme="daylight"] .exercise-header:hover {
  background: var(--surface-sunken);
}

/* Checked and left alone: the set table needs no rule. Its body cells resolve
   through --surface-menu, which is #ffffff in Daylight, and its header cells
   carry --surface-black inline, which is the pale lavender below the page. The
   header/body split the dark themes get from #222 against #000 survives the
   flip on its own. */

/* --- program_builder.html -------------------------------------------------
   The focused program field. It inverts to a light-grey slab with black text —
   the "this field is active" gesture on a dark page. Its three literals have no
   token because the inverse ramp has no rung at that lightness, and on a light
   page the gesture is wrong anyway: a grey slab is what a DISABLED field looks
   like here. Focus becomes what it looks like everywhere else in Daylight — the
   field stays white and the accent draws the ring.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .form-controlz:focus {
  background-color: var(--surface-raise) !important;
  color: var(--text-primary) !important;
  border-color: var(--accent) !important;
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.16) !important;
}

/* The comment tooltip is a floating glass slab. --glass-rgb flips with the
   theme, so the slab is already light; it just loses its edge against the
   card it floats over. */
:root[data-theme="daylight"] .comment-tooltip {
  border-color: var(--surface-edge);
}

/* Checked deliberately and left alone: `.delete-item i` at #bd5457 is a muted
   red that measures 4.5:1 on white, and the bulk-picker's selected state is a
   20%-alpha wash of #7bc8a4 with the same green as its border — a tint of a
   true colour, which behaves on a light ground. Neither needs a Daylight
   variant, and both are load-bearing signals, so neither gets one it does not
   need. */

/* --- workout_showcase.html ------------------------------------------------
   This is the page a stranger sees. Its meta line sits at #929292 — a mid grey
   with no rung on the ramp (the nearest is 9 units away, past the threshold the
   rest of this conversion holds to), so the literal stays and Daylight takes
   the override instead. Zero drift for the six dark themes, correct contrast
   here. */
:root[data-theme="daylight"] .showcase-meta {
  color: var(--text-faint);
}

/* The record marker and the PR badge are the page's two gold accents. Gold is
   the achievement colour throughout the app and stays gold; it only moves down
   its own hue far enough to read on white. The 20% amber wash behind the badge
   is left alone — a tint of the true colour behaves correctly on a light
   ground already. */
:root[data-theme="daylight"] .showcase-record-icon,
:root[data-theme="daylight"] .showcase-weight-pr-trigger {
  color: #8a6512 !important;
}

/* The personal-record badge writes its amber inline, so nothing in a stylesheet
   can reach it without !important — hence the class added to the markup for
   this rule to hold on to. Its 20% amber fill is left as it is. */
:root[data-theme="daylight"] .showcase-pr-badge {
  color: #7d5a10 !important;
  border-color: #b8891a !important;
}

/* The page's own tint. `--showcase-accent` at 13% is a clear wash on near-black
   and about two units of colour on white — the same falloff problem the diary
   panels had, and the same fix: more alpha, tighter falloff. The hue is
   untouched, so a lifting session still reads purple and a cardio one red. */
:root[data-theme="daylight"] .glass-card {
  background:
    radial-gradient(circle at 12% 0%, rgba(var(--showcase-accent), 0.30), transparent 46%),
    linear-gradient(145deg, var(--surface-panel) 0%, var(--surface-panel-2) 100%);
}

/* --- leaderboards.html ----------------------------------------------------
   The three panels separate by ACCENT RUNG rather than by hue (see the note at
   the top of that sheet). That works on a dark page, where soft / base / dark
   are three clearly different purples. In Daylight the ramp is re-weighted for
   a light ground and --accent-dark is a pale tint, so the month panel's wash
   lands at roughly nothing while the other two stay visible — the separation
   the design depends on collapses.

   The rungs are re-picked here, not in the ramp: --accent-dark is pale on
   purpose and is doing correct work everywhere else. Feed / week / month keep
   the same ordering, in tones that all read on white.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .lb-panel {
  background:
    radial-gradient(120% 90% at 12% 0%,
                    rgba(var(--panel-accent), var(--lb-panel-alpha, 0.26)),
                    transparent 58%),
    linear-gradient(145deg, var(--surface-panel) 0%, var(--surface-panel-2) 100%);
}

/* The dark ramp spreads soft / base / dark across 76% / 58% / 28% lightness, so
   three rungs at one alpha are plainly three different panels. Daylight's rungs
   are all mid-to-dark purples — correct for a light ground, and only a few
   units apart once diluted to a wash. So the separation moves to the alpha,
   which is the same idea expressed in the axis a light theme actually has. */
:root[data-theme="daylight"] .lb-panel-feed  { --lb-panel-alpha: 0.13; }
:root[data-theme="daylight"] .lb-panel-week  { --lb-panel-alpha: 0.26; }
:root[data-theme="daylight"] .lb-panel-month {
  --panel-accent: var(--accent-deep-rgb);
  --lb-panel-alpha: 0.40;
}

/* Medal ranks. Gold at 1.6:1 and silver at 1.3:1 against white — the top two
   places on the board are the two that cannot be read. Same treatment as the
   tier badges in profile.html: each metal keeps its hue and moves to a
   lightness that works on a light ground. Bronze already reads and moves
   only slightly, so the three stay distinguishable from each other. */
:root[data-theme="daylight"] .lb-rank-1 { color: #8a6512; }
:root[data-theme="daylight"] .lb-rank-2 { color: #5f5f66; }
:root[data-theme="daylight"] .lb-rank-3 { color: #96501f; }


/* ===========================================================================
   DAYLIGHT — the achievement toast, pinned dark
   ===========================================================================

   The toast is the one component in the app that is deliberately NOT part of
   the page. It flies in over the top of whatever you were doing, holds for five
   seconds and leaves, and its whole design — white label, white title, a white
   shimmer raking across it, a dark rewards footer — was built to read against
   the dark page behind it.

   There is a wrinkle worth knowing about. `.toast-achievement` asks for
   `linear-gradient(135deg, var(--bs-black) 0%, var(--accent-deep) 50%,
   var(--bs-black) 100%)`, and Bootstrap 5.3 ships --bs-black-rgb but not
   --bs-black. That makes the whole declaration invalid at computed-value time,
   so `background` computes to its initial value and the toast has been
   TRANSPARENT in every theme since it was written — the same --bs-black problem
   .results-header has. On a dark page nobody notices: the page is the ground,
   and white text on it looks exactly like a dark toast. On a light page there
   is no ground, and the toast is white text on white.

   That declaration is left exactly as it is, because supplying --bs-black would
   hand all six dark themes a gradient they have never had. Instead Daylight
   gives the toast the ground it was always borrowing — the dark theme's own
   page tone — and re-declares the channel and ramp tokens locally so every
   descendant rule inside it resolves the DARK way. Custom properties inherit,
   so this one block pins the entire subtree without a rule per element: the
   shimmer stays a white rake, the rewards footer stays a dark well, the reward
   chips stay white-on-dark. Nothing about the toast changes between themes,
   which is the point — it is the same object arriving over a different page.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .toast-achievement {
  /* The ground it used to borrow from the page. */
  background-color: #101012 !important;

  /* Channels, flipped back. */
  --wash-rgb:        255, 255, 255;
  --sink-rgb:        0, 0, 0;
  --shadow-rgb:      0, 0, 0;
  --shadow-strength: 1;

  /* Text ramp, flipped back. */
  --text-primary:   #ffffff;
  --text-bright:    #f1f1f1;
  --text-body:      #e0e0e0;
  --text-soft:      #d8d8d8;
  --text-quiet:     #cfcfcf;
  --text-dim:       #b8b4b4;
  --text-secondary: #aaaaaa;
  --text-faint:     #9b9b9b;
  --text-muted:     #888888;
  --text-whisper:   #777777;

  /* The accent rungs the toast paints with: the halo behind it, the disc
     behind each reward icon, and — in layout.html's first, superseded
     .toast-achievement block — the stops of a gradient that never wins. Every
     token the toast subtree consumes across all five pages is listed here, so
     nothing inside it can resolve the light way. Audited by enumerating every
     var() inside the toast rules of layout, main, workout, activity and
     custom_food; the only name not covered is --bs-black, which is undefined
     everywhere (see the note above). */
  --accent:          #8b34f6;
  --accent-rgb:      139, 52, 246;
  --accent-soft:     #a688fa;
  --accent-tint:     #ded4ff;
  --accent-deep:     #6f42c1;
  --accent-deep-rgb: 111, 66, 193;
}

/* The close button is the one piece the token pin cannot reach: the Daylight
   bridge repaints .btn-close-white with a literal and !important, which no
   amount of local token redefinition undoes. Bootstrap's own white-close
   filter is restored inside the toast. */
:root[data-theme="daylight"] .toast-achievement .btn-close-white {
  filter: var(--bs-btn-close-white-filter, invert(1) grayscale(100%) brightness(200%));
}


/* ===========================================================================
   DAYLIGHT — donate.html and the donation result pages
   ===========================================================================

   These three pages are one component repeated: a slab with a hairline, a grid
   of amount buttons, and a couple of notes underneath. The conversion carries
   almost all of it; what is left is the one place the numbers and the roles
   disagree.

   Every hairline on these pages was written as #2a2a2a, which is an exact match
   for --surface-field and 26 units from --surface-border. So the snap put the
   card edges, the header rule and the button outlines on the FIELD token — the
   right number in the dark themes, and the wrong role: fields go white on a
   light page, so on Daylight every one of those edges disappears into the card
   it is supposed to be drawing. The tokens stay (they are exact where it
   matters) and the borders take the border rung here instead.
   ------------------------------------------------------------------------- */
:root[data-theme="daylight"] .glass-card,
:root[data-theme="daylight"] .donation-result-card,
:root[data-theme="daylight"] .perk-block,
:root[data-theme="daylight"] .donation-btn,
:root[data-theme="daylight"] .result-btn {
  border-color: var(--surface-border);
}

:root[data-theme="daylight"] .card-header {
  border-bottom-color: var(--surface-border);
}

/* The amount buttons. Resting is --surface-well, which is a pale lavender here
   and reads correctly as a button. Hover was #111 -> #1a1a1a — a LIGHTENING,
   which is what "lifted" means on a near-black page and lands on pure white
   here, i.e. the button vanishes at exactly the moment you point at it. It
   lifts toward the accent instead, which is the same gesture in the direction a
   light surface has. */
:root[data-theme="daylight"] .donation-btn:hover,
:root[data-theme="daylight"] .donation-btn:focus-visible,
:root[data-theme="daylight"] .result-btn:hover,
:root[data-theme="daylight"] .result-btn:focus-visible {
  background: var(--accent-shadow);
  border-color: var(--accent);
  color: var(--accent-deep);
}

/* The custom-amount field is a field, so it goes white with a drawn edge —
   unlike the amount buttons above it, which are controls. */
:root[data-theme="daylight"] .custom-donation .form-control,
:root[data-theme="daylight"] .custom-donation .input-group-text {
  background: var(--surface-raise);
  border-color: var(--surface-edge);
}

/* Three colours that stayed literal because they carry meaning rather than
   structure, and all three were picked against near-black: the donor heart, the
   error line, and Bootstrap's pale link blue in the legal note. Each moves down
   its own hue far enough to read on white, and no further. */
:root[data-theme="daylight"] .donor-status i {
  color: #c2264a;
}

:root[data-theme="daylight"] .donation-message {
  color: #a32b2b;
}

:root[data-theme="daylight"] .donation-legal-note a {
  color: #14549c;
}


/* ===========================================================================
   DAYLIGHT — final tuning round
   ======================================================================== */

/* --- main.html: the grams flash ------------------------------------------
   Typing a portion flashes the grams field for 500ms. #666 is a LIGHTENING
   against the near-black field — a mid grey is the brightest thing that still
   reads as "same control, momentarily changed". It has no token because it sits
   17 units off --surface-edge, well past what this conversion snaps, and it is
   correct where it is.

   On a white field the same flash is a dark slab with dark text on it, which
   reads as an error rather than an acknowledgement. It flips direction and
   keeps the gesture small: a soft grey pulse, roughly the same perceptual step
   away from the field as #666 is on the dark side. */
:root[data-theme="daylight"] .highlight-temp {
  background-color: #dcd5e8 !important;
}

/* --- main.html: the search dice ------------------------------------------
   #ffd166 is the palette yellow — the same one the level badge and the
   achievement count use — and it is a CATEGORY colour, so it does not theme.
   It is also 1.6:1 on white, which for a 32px control with an 18px glyph inside
   means the dice is there and you cannot see it.

   The hue is kept and the lightness moved: same yellow, dark enough to read.
   The wash behind it comes up to match, since 13% of a pale yellow over white
   is not a chip. Hover darkens rather than brightens, which is the direction a
   light surface reads as "pressed". */
:root[data-theme="daylight"] .search-dice {
  background: rgba(255, 209, 102, 0.30);
  color: #9a7300;
}

:root[data-theme="daylight"] .search-dice:hover,
:root[data-theme="daylight"] .search-dice:focus-visible {
  background: rgba(255, 209, 102, 0.46);
  color: #7d5c00;
}

/* --- activity.html: the calorie slider -----------------------------------
   The track is --surface-chip, which is a clear step up from a near-black
   panel and lands 16 units from the adjustment zone's own tone on Daylight —
   close enough that the unfilled part of the slider stops looking like a
   track. It takes the heavier rule instead, the same one field edges use, so
   the bar has an actual shape to drag along.

   Only the -webkit- track is touched, because only the -webkit- track is
   styled in the first place; Firefox falls through to Bootstrap's .form-range
   in both themes, as it already did. */
:root[data-theme="daylight"] .custom-slider::-webkit-slider-runnable-track {
  background: var(--surface-edge);
}

/* --- activity.html: the planner's two buttons ----------------------------
   "Takaisin" and "Lukitse päivä" are painted #212529bf — Bootstrap's dark at
   75% — with --text-primary as the label. The fill is an 8-digit hex, so the
   conversion left it alone (correctly: an alpha hex carries its own intent),
   and the label follows the ramp. On Daylight that pairs a dark slab with
   near-black text.

   They become what a secondary action looks like on a light page: the raised
   surface, a drawn edge, body-weight text. Scoped away from .active-lock so
   the gold locked state — which is the same gold in every theme, with black
   text and its own border — keeps winning. */
:root[data-theme="daylight"] .btn-ayy:not(.active-lock),
:root[data-theme="daylight"] .btn-ayytwo {
  background-color: var(--surface-raise);
  border-color: var(--surface-edge) !important;
  color: var(--text-body) !important;
}

:root[data-theme="daylight"] .btn-ayy:not(.active-lock):hover,
:root[data-theme="daylight"] .btn-ayytwo:hover {
  background-color: var(--surface-sunken);
  border-color: var(--accent) !important;
  color: var(--accent-deep) !important;
}


/* ===========================================================================
   DAYLIGHT — index.html, "Päivän fokus"
   ===========================================================================

   The focus widget has four states — attention (the default, and what every
   tone that is not explicitly set falls back to), positive, neutral and error —
   and each one says what it is with a hue: amber, green, purple, red. Only
   neutral survives the flip, and only by accident: it draws its border from
   --accent-soft-rgb, which themed along with everything else.

   The other three were picked as GLOW colours against a near-black card, and
   they fail in two ways at once on a light one. The text and glyphs land
   between 1.3:1 and 2.3:1 — the amber action line, which is the thing you are
   meant to read, is the worst of them at 1.35:1. And the borders and washes
   that carry the state are 14–28% of a pale colour over near-white, which is
   nothing at all: every card ends up looking like the same card.

   So each state moves down its own hue for anything that carries text, and up
   in alpha for anything that carries the state as a tint. Nothing changes hue,
   and --state-warning itself is untouched — it is a fixed state colour and is
   used elsewhere; only the rules that paint this widget with it are overridden.
   ======================================================================== */

/* --- attention (default) + the widget-picker rows that share these rules ---
   The base .today-focus-widget border IS the attention state; there is no
   .focus-tone-attention class for it to match. */
:root[data-theme="daylight"] .today-focus-widget {
  border-color: rgba(196, 148, 20, 0.45);
}

:root[data-theme="daylight"] .mobile-widget-option-icon.focus,
:root[data-theme="daylight"] .mobile-trend-icon.focus,
:root[data-theme="daylight"] .mobile-widget-option-icon.leaderboard {
  background: rgba(255, 209, 102, 0.34);
  color: #8a6512;
}

/* The action line — the sentence the widget exists to deliver. #ffe29a is the
   palest thing in the whole component and measures 1.35:1 here, which is why
   it reads as "the yellow is missing" rather than "the yellow is light". */
:root[data-theme="daylight"] .today-focus-action {
  color: #7d5c00;
}

/* --- positive ------------------------------------------------------------- */
:root[data-theme="daylight"] .today-focus-widget.focus-tone-positive {
  border-color: rgba(31, 107, 54, 0.38);
  background: linear-gradient(180deg, rgba(31, 107, 54, 0.09), rgba(var(--wash-rgb), 0.025));
}

:root[data-theme="daylight"] .today-focus-widget.focus-tone-positive .mobile-trend-icon.focus {
  color: #1f6b36;
  background: rgba(31, 107, 54, 0.13);
}

:root[data-theme="daylight"] .today-focus-widget.focus-tone-positive .today-focus-action {
  color: #1a5c2e;
}

/* --- neutral --------------------------------------------------------------
   The one state that already worked: its border rides --accent-soft-rgb, which
   flipped with the theme. It only needs the alpha the other three now carry, so
   the four states stay comparable to each other. */
:root[data-theme="daylight"] .today-focus-widget.focus-tone-neutral {
  border-color: rgba(var(--accent-soft-rgb), 0.34);
}

/* --- error ----------------------------------------------------------------
   The action line stays amber here, as it does in the dark themes — the tone
   colours the frame and the icon, not the suggestion. */
:root[data-theme="daylight"] .today-focus-widget.focus-tone-error {
  border-color: rgba(163, 43, 43, 0.35);
}

:root[data-theme="daylight"] .today-focus-widget.focus-tone-error .mobile-trend-icon.focus {
  color: #a32b2b;
  background: rgba(163, 43, 43, 0.11);
}


/* ===========================================================================
   DAYLIGHT — spinners, the coach composer, the lock shine, micro tracks
   ======================================================================== */

/* --- workout.html: the analysis overlay ----------------------------------
   The overlay ground is `rgba(--sink-rgb, 0.9)` over `rgba(--glass-rgb, 0.9)`,
   so it flips to a near-white sheet correctly on its own. What did not flip is
   what sits on it: the spinning dumbbell is #6dc0d5, a colour picked to glow
   against a black scrim, and it lands at 2.0:1 on the white one. The progress
   bar has the same problem twice — a pale cyan-to-green fill and a cyan halo
   that is pure bloom, which is subtractive nonsense on a light ground.

   Both keep their hues. The dumbbell moves to the dark end of the same cyan,
   the bar's stops go deep enough to read as a filled bar, and the halo comes
   off entirely rather than being re-tuned: a glow is an additive effect and
   there is nothing on a white page for it to add to. */
:root[data-theme="daylight"] .spinning-dumbbell {
  color: #0f6b7d;
}

:root[data-theme="daylight"] .analysis-progress-bar {
  background: linear-gradient(90deg, #12798d, #1f6b36);
  box-shadow: none;
}

/* --- workout.html: the generic spinner ------------------------------------
   `#ffffffb8` — white at 72%. An 8-digit hex, so the conversion left it alone
   (an alpha hex carries its own intent and is usually deliberate), and it is
   deliberate: a soft white spinner on a near-black page. On a light one it is
   a soft white spinner on a white page. */
:root[data-theme="daylight"] .fa-spinner {
  color: rgba(var(--wash-rgb), 0.62);
}

/* --- activity.html: the coach composer ------------------------------------
   `#coach-followup-send` is written as `.btn .btn-jere` with the accent
   hardcoded inline — but .btn-jere is a workout.html class and has no styling
   at all on this page, so the button is a bare Bootstrap .btn: a saturated
   accent fill with `--bs-btn-color` (Bootstrap's near-black body colour) as the
   label. Dark text on saturated purple.

   It takes the same pale-fill / deep-label treatment as every other primary
   action in this pass. !important on the background because the inline style
   would otherwise win. */
:root[data-theme="daylight"] #coach-followup-send {
  background-color: var(--accent-dark) !important;
  border: 1px solid rgba(var(--accent-rgb), 0.34) !important;
  color: var(--accent-deep) !important;
}

@media (hover: hover) and (pointer: fine) {
  :root[data-theme="daylight"] #coach-followup-send:hover {
    background-color: #d9c9ff !important;
    border-color: rgba(var(--accent-rgb), 0.55) !important;
  }
}

/* --- activity.html: the lock button's shine -------------------------------
   `.btn-ayy.active-lock` is a gold gradient with black text, and it is the SAME
   gold in every theme — the locked day is a fixed signal, not a themed surface.
   Its shine sweep, though, is `rgba(--wash-rgb, 0.6)`, which flips with the
   channel and drags a dark bar across the gold instead of a highlight.

   Pinned to white, for the same reason the meal-group icon is pinned: the
   element does not sit on the page, it sits on a colour that never changes, so
   the treatment tuned against that colour should not change either. */
:root[data-theme="daylight"] .btn-ayy.active-lock::after {
  background: rgba(255, 255, 255, 0.65);
}

/* --- food-micronutrients.css: the empty track -----------------------------
   `rgba(--sink-rgb, 0.2)` is a 20% darkening on a near-black card — exactly
   what an empty track should be. The sink channel flips to white, so on a white
   card it is white on white and the unfilled part of every micronutrient bar
   stops existing.

   It becomes the same gesture the other way: a light darkening of the card,
   kept deliberately faint so the track still reads as absence rather than as a
   second filled bar. */
:root[data-theme="daylight"] .micro-progress-track {
  background: rgba(var(--wash-rgb), 0.11);
}

/* --- privacy.html / terms.html --------------------------------------------
   Both extend layout.html and neither is behind @login_required, but index.html
   links them from the footer — so a signed-in Daylight user reaches them and
   gets the themed <html>. Everything else on the two pages now resolves through
   the ramp; the link blue is a literal, and #63b3ed is a colour for a dark page
   (2.4:1 on white). Same hue, dark enough to read, and it stays visibly a link
   rather than becoming body text.

   Their siblings — login, register, reset_request, reset_token — are NOT
   converted, deliberately: theme.js takes data-theme from users.theme rendered
   server-side, so a logged-out page never carries one and always paints dark.
   reset_token redirects authenticated users away outright. */
:root[data-theme="daylight"] .language-toggle a,
:root[data-theme="daylight"] .card a {
  color: #14549c;
}

/* verify_email_request.html IS a logged-in page (it reads current_user.email),
   so it themes. Its one remaining literal is the delete-request link, a soft
   red picked against near-black. */
:root[data-theme="daylight"] .verification-delete {
  color: #a32b2b;
}


/* ===========================================================================
   DAYLIGHT — the completion / reward badges
   ===========================================================================

   Both pages fire the same kind of thing when you finish something: a small
   pill that flies in over the page, holds for a beat and leaves. They are
   built as SELF-CONTAINED chips — they float over arbitrary content, so they
   carry their own ground rather than borrowing the page's, and everything on
   them is tuned against that ground.

   Which is why the weight-PR badge already works on Daylight and the volume-PR
   one does not: the weight badge's gradient is two hardcoded dark ambers, so it
   stays a dark chip in every theme, while the volume badge's SECOND stop is
   `rgba(--glass-rgb, 0.98)` — a token that flips to near-white. The pill runs
   from dark purple into white with pale lilac text over both halves, and the
   white half swallows it.
   ======================================================================== */

/* Pinned dark, matching how .is-weight-pr is already written. The chip is not
   part of the page; it is a thing that appears on top of it, and the treatment
   inside it was tuned against a dark ground in both themes. */
:root[data-theme="daylight"] .exercise-completion-pr-badge.is-volume-pr {
  background: linear-gradient(135deg, rgba(50, 30, 72, 0.98), rgba(32, 19, 48, 0.98));
}

/* The volume delta that sits under the badge, and its calorie twin in the
   diary. These do NOT carry a ground — they are bare text over whatever is
   behind them, which is why they wear three stacked shadows. The text itself
   flips correctly (--wash-rgb at 92% is dark ink here), but the shadows flip
   with it, so dark text ends up wrapped in a dark halo. The halo has to be the
   opposite of the text to do its job: white, and lighter, since it only has to
   hold the glyphs off a light page rather than off arbitrary dark content. */
:root[data-theme="daylight"] .exercise-completion-volume-change,
:root[data-theme="daylight"] .food-log-calorie-feedback {
  text-shadow:
    0 1px 1px rgba(255, 255, 255, 0.95),
    0 0 3px rgba(255, 255, 255, 0.9),
    0 2px 7px rgba(255, 255, 255, 0.75);
}

:root[data-theme="daylight"] .exercise-completion-volume-change i {
  color: #1f6b36;
  filter: none;
}

/* --- workout.html: the PR celebration rows -------------------------------
   The card ground flips on its own (--surface-note-rgb), so what is left is
   everything gold that was drawn to GLOW against near-black: a 40% gold
   hairline, a blurred gold blob behind the trophy, a 15% gold shimmer, and two
   text rungs at 1.6:1 and 1.7:1.

   Gold is the achievement colour throughout the app and stays gold. The text
   rungs move down their own hue, the hairline goes to a real line, and the two
   additive effects — the blur and the shimmer — come off rather than being
   re-tuned: both are light being ADDED, and there is nothing on a white card
   for them to add to. The trophy and the bounce carry the celebration on
   their own. */
:root[data-theme="daylight"] .pr-celebration {
  border-color: rgba(154, 115, 0, 0.55);
}

:root[data-theme="daylight"] .pr-celebration::before,
:root[data-theme="daylight"] .pr-celebration::after {
  display: none;
}

:root[data-theme="daylight"] .pr-details {
  color: #7d5c00;
}

:root[data-theme="daylight"] .pr-details.pr-new-best {
  color: #6b4a00;
  text-shadow: none;
}

/* --- main.html: the diary's reward badges --------------------------------
   Same chip idiom, but this one takes its ground from `rgba(--bar-rgb, 0.94)`,
   which flips to a near-white pill — correct, and it leaves every colour on
   top of it stranded. The base label is a literal #ffffff, and the four macro
   variants are pale tints picked to sit on near-black: lilac, ice blue, peach
   and mint, all between 1.5:1 and 2:1 against the pill they are now on. The
   border is `currentColor`, so it fades with them.

   Each macro moves to the same darkened ink Daylight already uses for that
   macro elsewhere — the recipe filter pills, the diary heading chip, the
   nutrition rows — so protein is the same purple in every place it appears,
   and so on. Hue is untouched; only lightness moves, and the border follows
   for free. */
:root[data-theme="daylight"] .food-log-reward-badge {
  color: var(--text-body);
}

/* border-color goes back to currentColor, which is what the base rule asks for
   — the four macro rules then override it with a pale alpha of the ORIGINAL
   tint, so without this the outline stays washed while the label darkens. */
:root[data-theme="daylight"] .food-log-reward-badge.is-proteins,
:root[data-theme="daylight"] .food-log-reward-badge.is-carbs,
:root[data-theme="daylight"] .food-log-reward-badge.is-fats,
:root[data-theme="daylight"] .food-log-reward-badge.is-fiber {
  border-color: currentColor;
}

:root[data-theme="daylight"] .food-log-reward-badge.is-proteins { color: #6b2bbf; }
:root[data-theme="daylight"] .food-log-reward-badge.is-carbs    { color: #0f6b7d; }
:root[data-theme="daylight"] .food-log-reward-badge.is-fats     { color: #a24512; }
:root[data-theme="daylight"] .food-log-reward-badge.is-fiber    { color: #1f6b36; }
