:root {
  color-scheme: light dark;
  --fg: #1b1b1f;
  --bg: #fafafa;
  --card: #ffffff;
  --border: #e2e2e6;
  --accent: #d1495b;
  --muted: #6b6b75;
  --highlight: #4062bb;
  --highlight-hover: #34519a;
  /* A lighter tint of --highlight, for a secondary/narrower-scope action
     that sits next to a --highlight one and needs to visibly read as the
     lesser of the two (e.g. each source's own Sync Now button, next to
     Sync All) without falling back to a completely different, unrelated
     color. */
  --highlight-light: #6c85c4;
  --highlight-light-hover: #5972b3;
  /* The workout-type background tints -- the day-cell boxes (.row-bg-*) and the
     week TSS bar's fill (.week-tss-seg-*) both use these, so they stay identical. */
  --tint-swim: rgba(64, 98, 187, 0.12);
  --tint-bike: rgba(155, 89, 182, 0.12);
  --tint-run: rgba(46, 158, 91, 0.12);
  --tint-other: rgba(224, 164, 88, 0.14);
  --tint-missed: rgba(209, 73, 91, 0.14);
}

@media (prefers-color-scheme: dark) {
  :root {
    --fg: #e9e9ee;
    --bg: #17171b;
    --card: #1f1f24;
    --border: #303038;
    --muted: #9a9aa5;
  }
}

* { box-sizing: border-box; }

/* Mobile Safari/Chrome can silently inflate the font-size of short text
   sitting in a narrow column (their own "readability" heuristic, not
   anything this stylesheet asks for) -- most visible on isolated numbers
   like the nutrition macro bars' logged-value figures, which could render
   noticeably bigger than their neighbors and overflow their own box on a
   real phone despite looking correct in every desktop/emulated check.
   Disabling it keeps every font-size exactly what this file says. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--fg);
}

.topbar {
  padding: 1.25rem 1.5rem;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.topbar h1 { margin: 0; font-size: 1.25rem; }

.home-link {
  display: flex;
  align-items: center;
  gap: .55rem;
  color: inherit;
  text-decoration: none;
}
.home-link:hover .brand-caps { color: var(--highlight); }

.brand-logo { display: block; flex-shrink: 0; }

.brand-name { line-height: 1; }
.brand-caps { font-weight: 800; letter-spacing: .03em; }
.brand-lower { font-weight: 400; color: var(--muted); }

.topbar-controls {
  display: flex;
  align-items: center;
  gap: 1.25rem;
}

.settings-link {
  font-size: .85rem;
  color: var(--muted);
  text-decoration: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: .3rem .7rem;
}

.settings-link:hover { color: var(--fg); border-color: var(--fg); }

.settings-link-icon { display: flex; align-items: center; gap: .4rem; }

@media (max-width: 700px) {
  .settings-link-text { display: none; }
}

button.settings-link {
  background: transparent;
  font-family: inherit;
  cursor: pointer;
}

.settings-menu { position: relative; }

.settings-dropdown {
  position: absolute;
  top: calc(100% + .4rem);
  right: 0;
  min-width: 160px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, .12);
  padding: .35rem;
  display: flex;
  flex-direction: column;
  gap: .1rem;
  z-index: 20;
}

/* The [hidden] attribute's UA-stylesheet display:none has lower specificity
   than the class rule above, which would otherwise always win. */
.settings-dropdown[hidden] { display: none; }

.settings-dropdown-form { margin: 0; }

.settings-dropdown-item {
  display: block;
  width: 100%;
  text-align: left;
  font-size: .85rem;
  font-family: inherit;
  color: var(--fg);
  text-decoration: none;
  background: transparent;
  border: none;
  border-radius: 5px;
  padding: .5rem .6rem;
  cursor: pointer;
}

.settings-dropdown-item:hover { background: var(--bg); }

.unit-toggle {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: .85rem;
  color: var(--muted);
}

.unit-toggle select {
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: .3rem .5rem;
  font-size: .85rem;
}

.nutrition-day {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: .6rem .8rem;
  margin-top: .75rem;
}

/* Immediately below a week divider (see .week-divider further down), a day
   row needs the tighter spacing the divider itself already provides --
   .nutrition-day's own default margin-top is meant for day-to-day spacing
   with no divider in between. */
.week-divider + .nutrition-day { margin-top: .5rem; }

.nutrition-day-today { box-shadow: inset 0 0 0 2px var(--highlight); }

/* The whole header (date + combined bar) is one button now -- clicking
   anywhere on it, including the bar, expands/collapses the details below
   (see the nutritionWeekPanel click handler in index.html). Reset the
   button defaults so it still reads as plain text/a bar, not a button. */
.nutrition-day-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .2rem .6rem;
  width: 100%;
  background: none;
  border: none;
  margin: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.nutrition-day-chevron {
  flex-shrink: 0;
  color: var(--muted);
  transition: transform .15s ease;
}

/* Points right collapsed, down expanded -- aria-expanded is the single
   source of truth the JS toggles, so this follows it rather than a
   separate class. */
.nutrition-day-header[aria-expanded="true"] .nutrition-day-chevron { transform: rotate(90deg); }

.nutrition-day-date { font-weight: 600; font-size: .88rem; flex-shrink: 0; white-space: nowrap; }

/* flex-basis: 100% forces this onto its own line below the chevron+date
   row -- flex-wrap above is only there for this one child to wrap onto,
   since the chevron+date pair never fills a full row on its own. */
.nutrition-day-header .nutrition-combined-bar-holder { flex: 1 1 100%; min-width: 0; }

.nutrition-day-details { margin-top: .3rem; }

.nutrition-day-summary {
  margin: .4rem 0 0;
  font-size: .78rem;
  color: var(--muted);
}

/* How far the calibration moved a day's calorie target off the plan's own
   figure -- muted and parenthetical, since the plan is what was actually
   prescribed and this is the estimate layered on it. */
.nutrition-day-adjusted { color: var(--muted); font-style: italic; cursor: help; }

/* The Cronometer completeness write, doubling as the day's completeness
   badge (see nutritionWeekPanel's click handler and POST
   /nutrition/<date>/complete). A sibling of .nutrition-day-header, not a
   descendant -- a button can't validly nest another interactive control --
   so it's positioned absolutely to land in the same top-right corner the
   old static checkmark badge sat in via the header's own flex layout,
   which needs .nutrition-day itself to be the positioning context. */
.nutrition-day { position: relative; }

.nutrition-day-complete-toggle {
  position: absolute;
  top: .6rem;
  right: .8rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  background: none;
  border: none;
  border-radius: 50%;
  padding: 0;
  color: var(--muted);
  cursor: pointer;
}
.nutrition-day-complete-toggle:hover { background: var(--border); color: var(--fg); }
.nutrition-day-complete-toggle:disabled { opacity: .5; cursor: default; }
/* Same [hidden]-vs-authored-rule specificity problem as .settings-dropdown
   above: an SVG's default UA display isn't guaranteed to lose to [hidden]
   here since these are flex items of the toggle button, so state it
   explicitly rather than relying on the UA stylesheet. */
.nutrition-day-complete-toggle svg[hidden] { display: none; }
/* Green because it's a confirmation, not a caveat -- same color the old
   static badge used. */
.nutrition-day-complete-toggle-done { color: #2e9e5b; }
.nutrition-day-complete-toggle-done:hover { color: #257c49; }

/* Red X instead of the green check when Cronometer's diary is marked
   complete but nothing was actually logged that day (see empty_complete
   in _nutrition_day_macro.html) -- same bright red used for text
   elsewhere (.nutrition-day-summary-over) since this reads as a caveat,
   not a confirmation. */
.nutrition-day-complete-toggle-empty { color: #d1495b; }
.nutrition-day-complete-toggle-empty:hover { color: #a73a49; }

/* Same red/green used for text elsewhere (--accent and legend-green,
   e.g. .tss-compare-hot/-cold) -- the brighter shades meant for text on
   either theme, not the darker ones used for solid bar fills. */
.nutrition-day-summary-over { font-weight: 700; color: #d1495b; }
.nutrition-day-summary-under { font-weight: 700; color: #2e9e5b; }

/* The combined logged/planned/burned bar -- unlike .calendar-bar (which
   grows from a 50% center point), this one is a plain 0-to-max_scale track
   with up to 3 absolutely-positioned fill segments (see
   _combined_calorie_bar in dashboard_data.py) plus a tick mark at each of
   the three values' exact positions. The holder reserves space above and
   below the track itself for that value's number (above) and its icon
   (below), both positioned at the same left% as their tick so all three
   line up in a single vertical stack per value. */
.nutrition-combined-bar-holder {
  position: relative;
  flex: 1;
  /* Extra top padding reserves room for up to 2 higher rows of value
     labels (see .nutrition-combined-bar-value-row2/-row3 below) -- reserved
     unconditionally so every day card lines up the same whether or not
     that day's values actually needed staggering. The icons below the bar
     stay on one row regardless (see .nutrition-combined-bar-icon's own
     --icon-nudge custom property), so they don't need the same reserved
     room underneath. */
  padding: 35px 0 12px;
}

.nutrition-combined-bar {
  position: relative;
  height: 8px;
  border-radius: 4px;
  background: var(--border);
  overflow: hidden;
}

.nutrition-combined-bar-fill { position: absolute; top: 0; bottom: 0; }

/* Green = good (logged came in under both plan and burned, or under
   whichever was smaller), red = bad -- brightening left to right within a
   same-colored run, per segment position (0/1/2), not per which metric the
   segment happens to represent. Reuses the same two shades already used
   elsewhere on the dashboard for this convention (.calendar-bar-fill's
   deficit/surplus and their over-max shade) for segments 1/2, plus one
   darker shade of each for segment 0. */
.nutrition-combined-bar-fill-green-0 { background: #185c34; }
.nutrition-combined-bar-fill-green-1 { background: #206f40; }
.nutrition-combined-bar-fill-green-2 { background: #2e9e5b; }
.nutrition-combined-bar-fill-red-0 { background: #7a2b35; }
.nutrition-combined-bar-fill-red-1 { background: #9d3744; }
.nutrition-combined-bar-fill-red-2 { background: #d1495b; }

/* A future day with nothing logged/burned yet, built from just the plan's
   own calorie target and its own estimated expenditure (see
   _forecast_calorie_bar) -- grey, not red/green, since there's no real
   "logged" figure yet to score either number against. Same 2 shades
   already used for a forecast elsewhere (the Charts tab calories chart's
   own "forecast logged"/"forecast gap" bars). */
.nutrition-combined-bar-fill-grey-0 { background: #5a5a63; }
.nutrition-combined-bar-fill-grey-1 { background: #b8b8c0; }

/* A past day with nothing logged in Cronometer, built the same way as the
   grey forecast bar above but flagged instead of just forecast -- a day
   that's already over should have a diary entry, so it borrows the
   Charts tab calories chart's own "burned only" gold (#e0a458, a day
   Garmin has a burn figure for but nothing was logged against) rather
   than the muted grey a still-pending future/in-progress day gets. Same
   darker-shade-0/lighter-shade-1 convention as green/red/grey above. */
.nutrition-combined-bar-fill-yellow-0 { background: #70522c; }
.nutrition-combined-bar-fill-yellow-1 { background: #e0a458; }

/* Today only: a translucent grey overlay stretching from the burned tick
   out to the projected tick (see _combined_calorie_bar's "ghost" field) --
   layered on top of whatever segment color(s) already occupy that range,
   rather than trying to recolor them, since the projection isn't part of
   the red/green logged-vs-targets comparison. */
.nutrition-combined-bar-ghost {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--muted);
  opacity: .4;
}

/* Each tick's line style (not just its position) also identifies which of
   the three values it marks -- solid for logged, dashed for planned,
   dotted for burned -- so that reads independently of the segment color
   underneath it. */
.nutrition-combined-bar-tick {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 0;
  margin-left: -1px;
  border-left: 2px solid rgba(255, 255, 255, .7);
}

.nutrition-combined-bar-tick-planned { border-left-style: dashed; }
.nutrition-combined-bar-tick-burned { border-left-style: dotted; }

/* The projected tick sits on top of the ghost overlay -- dashed and dimmed
   grey rather than the bright white/solid look of the 3 real ticks, so it
   reads as "estimate" rather than "known value" even before you notice
   its clock icon or "~"-prefixed number. */
.nutrition-combined-bar-tick-projected {
  border-left-color: var(--muted);
  border-left-style: dashed;
  opacity: .8;
}

/* The accuracy-shift tick is a suggestion layered on top of the plan/goal
   target (see _nutrition_day_entry's shift_target in dashboard_data.py),
   not one of the 3 real logged/planned/burned figures -- given its own
   purple accent (the same purple used for the calibration chart's own
   "+ running correction" line and .row-bg-bike) rather than the neutral
   white the other ticks share, so it reads as "the correction" at a
   glance instead of a 4th equally-weighted value. */
.nutrition-combined-bar-tick-shift {
  border-left-color: #9b59b6;
  border-left-style: double;
}

.nutrition-combined-bar-value {
  position: absolute;
  top: 22px;
  transform: translateX(-50%);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
  font-size: .6rem;
  color: var(--muted);
  white-space: nowrap;
  line-height: 1;
}

/* Bumped to a higher row when it would otherwise collide with a nearby
   label (see _label_rows in dashboard_data.py, which picks the lowest row
   that's actually clear rather than just alternating with the previous
   point) -- 3 rows total handles up to 3 points bunched close together;
   a 4th (very rare) shares row 3 with whichever of the others it's
   closest to rather than growing a 4th row. */
.nutrition-combined-bar-value-row2 { top: 11px; }
.nutrition-combined-bar-value-row3 { top: 0; }

/* The projected total reads as an estimate, not a known figure -- its own
   "~" prefix (from dashboard_data.py) already says so, italicized here too
   for a second, quieter signal. */
.nutrition-combined-bar-value-projected { font-style: italic; }

/* Below the bar: a fork (logged), a target (planned), or a flame (burned)
   in place of a letter -- same idea (identify which tick is which without
   relying on the segment color), just an icon instead of text. Drawn with
   mask-image so a single fixed-color SVG can still render in --muted for
   either theme -- mask-image's alpha channel is what's used, so the SVG's
   own stroke color doesn't matter. --icon-nudge (set inline per icon, see
   the template) shifts a colliding icon sideways off its exact tick
   position -- see _icon_nudges in dashboard_data.py -- so two overlapping
   icons spread apart left/right while staying on the same row, rather
   than one dropping beneath the other; it's a no-op (0px) on every icon
   that isn't colliding with a neighbor. */
.nutrition-combined-bar-icon {
  position: absolute;
  bottom: 0;
  transform: translateX(calc(-50% + var(--icon-nudge, 0px)));
  width: 11px;
  height: 11px;
  background: var(--muted);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.nutrition-combined-bar-icon-logged {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 2v6a2 2 0 0 0 2 2h0a2 2 0 0 0 2-2V2'/%3E%3Cpath d='M9 10v12'/%3E%3Cpath d='M17 2c-1.5 2-1.5 5-1.5 7s0 3.5 1.5 3.5 1.5-1.5 1.5-3.5-0-5-1.5-7Z'/%3E%3Cpath d='M17 12.5V22'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 2v6a2 2 0 0 0 2 2h0a2 2 0 0 0 2-2V2'/%3E%3Cpath d='M9 10v12'/%3E%3Cpath d='M17 2c-1.5 2-1.5 5-1.5 7s0 3.5 1.5 3.5 1.5-1.5 1.5-3.5-0-5-1.5-7Z'/%3E%3Cpath d='M17 12.5V22'/%3E%3C/svg%3E");
}


.nutrition-combined-bar-icon-planned {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Ccircle cx='12' cy='12' r='5'/%3E%3Ccircle cx='12' cy='12' r='1' fill='%23000'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Ccircle cx='12' cy='12' r='5'/%3E%3Ccircle cx='12' cy='12' r='1' fill='%23000'/%3E%3C/svg%3E");
}

/* The flame glyph only fills the middle of a 24x24 canvas (unlike the
   fork/target paths above, which reach close to the edges of theirs), so
   mask-size: contain -- which scales the whole canvas, not just the glyph
   -- was rendering it visibly smaller than the other two even after
   bumping its box size up. Cropping the viewBox tightly around the glyph
   itself (instead of the full 24x24 it was drawn on) is what actually
   fixes that: now "contain" scales the glyph itself to fill the box, so
   it can use the same box size as every other icon here. */
.nutrition-combined-bar-icon-burned {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='4 6 13 13' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8.5 14.5A2.5 2.5 0 0 0 11 17a2.5 2.5 0 0 0 2.5-2.5c0-1.5-1.5-2-1.5-3.5 0-1 1-2 1-2s2 2 2 5A5 5 0 0 1 6 16c0-3 2-4.5 2-7.5 0 0 3 1 3 4a3 3 0 0 1-2.5 2.9Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='4 6 13 13' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8.5 14.5A2.5 2.5 0 0 0 11 17a2.5 2.5 0 0 0 2.5-2.5c0-1.5-1.5-2-1.5-3.5 0-1 1-2 1-2s2 2 2 5A5 5 0 0 1 6 16c0-3 2-4.5 2-7.5 0 0 3 1 3 4a3 3 0 0 1-2.5 2.9Z'/%3E%3C/svg%3E");
}

/* A clock, not one of the 3 colored icons -- the projected point isn't
   logged/planned/burned itself, just an estimate of how much of today is
   still left to burn (see predicted_remaining_burn_today). Dimmed to
   match its dashed tick/italic label, same "this isn't a known value yet"
   signal used elsewhere on the dashboard for a forecast (e.g.
   .calendar-bar-fill.forecast's own reduced opacity). */
.nutrition-combined-bar-icon-projected {
  opacity: .8;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpolyline points='12 6 12 12 16 14'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpolyline points='12 6 12 12 16 14'/%3E%3C/svg%3E");
}

/* A trending-down arrow -- the accuracy-shift figure, purple to match its
   tick line (see .nutrition-combined-bar-tick-shift above) rather than the
   --muted every other icon here uses. */
.nutrition-combined-bar-icon-shift {
  background: #9b59b6;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='22 17 13.5 8.5 8.5 13.5 2 7'/%3E%3Cpolyline points='16 17 22 17 22 11'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='22 17 13.5 8.5 8.5 13.5 2 7'/%3E%3Cpolyline points='16 17 22 17 22 11'/%3E%3C/svg%3E");
}

.nutrition-day-body {
  display: flex;
  align-items: flex-start;
  gap: 1.5rem;
}

.nutrition-day-body-main { flex: 1; min-width: 0; container-type: inline-size; }

/* The planned-vs-actual bars (one per nutrient), pushed to the right edge
   of the day card -- same bar visuals as the calendar's/vs-burned bar,
   just stacked instead of one full-width bar. margin-left: auto keeps
   this flush right even as .nutrition-day-body-main grows. 250px is the
   preferred width, but flex-shrink: 1 (plus min-width: 0, overriding the
   default auto floor that would otherwise block shrinking below this
   row's own content size) lets it compress below that on a narrow phone
   instead of demanding 250px unconditionally -- a day card's actual
   available width varies by viewport (roughly viewport - 118px at the
   breakpoints this was checked against), and a fixed, unshrinkable 250px
   only fit down to about 410px wide; below that it silently overflowed
   the card's own edge without ever showing up as page-level scroll,
   because the min-width: 0 grid fix elsewhere in this file (see `main >
   *`) keeps the card itself from growing to accommodate an oversized
   child, so the overflow just painted past the card border instead. Each
   bar's own logged/planned figures (.nutrition-plan-bar-planned/-actual)
   live right here instead of the day card's old separate Logged/Plan
   table, the only place those numbers show up at all. */
.nutrition-plan-bars {
  flex: 0 1 250px;
  min-width: 0;
  margin-left: auto;
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding-top: .1rem;
}

/* On a phone/narrow card the row layout above leaves the left column (the planned
   meals, the logged meals) with whatever the 250px bars don't take -- close to nothing,
   so its text wrapped a word per line. Stack instead: the bars first (they're the day's
   at-a-glance summary), then the meals at the card's full width. */
@media (max-width: 700px) {
  .nutrition-day-body { flex-direction: column; align-items: stretch; gap: .5rem; }
  .nutrition-plan-bars { order: -1; flex: none; width: 100%; margin-left: 0; }
  .nutrition-day-body-main { flex: none; width: 100%; }
}

.nutrition-plan-bar-row { display: flex; align-items: center; gap: .15rem; }

/* align-self/height/line-height matching the bar's own thickness centers
   this on the bar itself rather than on the taller row as a whole (which
   also includes the planned label's line above the bar) -- same technique
   as .nutrition-plan-bar-actual below, see its own comment for why. */
.nutrition-plan-bar-label {
  align-self: flex-end;
  height: 8px;
  line-height: 8px;
  font-size: .64rem;
  font-weight: 600;
  color: var(--muted);
  width: 14px;
  flex-shrink: 0;
}
.nutrition-plan-bar-label-protein { color: #d4a72c; }
.nutrition-plan-bar-label-carbs { color: var(--highlight); }
.nutrition-plan-bar-label-fat { color: #8657c9; }

/* Wraps just the bar itself so the planned figure can be positioned above
   its center/target line (calendar-bar-mid). Top padding reserves room
   for that label's own line plus a bit of clearance below it -- line-
   height: 1 (below) crops right at the font's baseline, so without this
   a descender like the "g" in "150g" hangs down far enough to touch the
   bar. */
.nutrition-plan-bar-track { position: relative; flex: 1; min-width: 0; padding-top: 14px; }
.nutrition-plan-bar { width: 100%; }

.nutrition-plan-bar-planned {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  font-size: .6rem;
  color: var(--muted);
  white-space: nowrap;
  line-height: 1;
}

/* The logged figure: shown on whichever side of the bar the fill actually
   grew toward (deficit = left, surplus = right -- see the template's
   direction check), so it reads as "the number at the filled edge" rather
   than a generic label. Both slots are always in the DOM (see the
   template) but only one is ever non-empty on a given row -- min-width
   (rather than a wider fixed width) keeps the empty side from sitting on
   a big reserved blank gap, while still reserving *some* consistent space
   on both sides so the bar/track between them starts and ends at the same
   x position on every row regardless of which side happens to be
   populated that day. Letting it fully collapse to 0 there (display: none
   on :empty) looked like more room gained, but actually meant the track's
   own start/end position shifted row to row depending on which side had
   text that day, throwing the 3 bars out of alignment with each other --
   this min-width is what a longer value like "289g" actually renders at,
   so ordinary values fit inside it without growing past it; flex-shrink:
   0 (unchanged) means an unusually long value still isn't clipped, just
   grows past this floor on that one row. align-self: flex-end plus a
   height/line-height matching the bar's own thickness (see
   ".nutrition-plan-bars .calendar-bar" below) is what actually centers
   this on the bar itself rather than on the taller row as a whole (which
   also includes the planned label's line above the bar) -- flex-end pins
   this box's bottom edge to the track's bottom edge, which is the bar's
   own bottom edge since the bar is the track's last content, and the
   equal line-height then centers the text within that same span. */
.nutrition-plan-bar-actual {
  flex-shrink: 0;
  align-self: flex-end;
  min-width: 26px;
  height: 8px;
  line-height: 8px;
  font-size: .64rem;
  color: var(--muted);
  white-space: nowrap;
}
.nutrition-plan-bar-actual-left { text-align: right; }
.nutrition-plan-bar-actual-right { text-align: left; }

/* These three bars' fill is a fixed color per nutrient, matching the label
   above (protein yellow, carbs blue, fat purple) -- direction (deficit/
   surplus) still drives which way the bar grows (.calendar-bar-fill.deficit's
   transform), just not its color here. !important is deliberate: it has to
   beat .calendar-bar-fill.deficit/.surplus/.over-max's own background,
   which otherwise wins on higher specificity or later source order. */
.nutrition-plan-bar-fill-protein { background: #d4a72c !important; }
.nutrition-plan-bar-fill-carbs { background: var(--highlight) !important; }
.nutrition-plan-bar-fill-fat { background: #8657c9 !important; }
/* Same thickness as the combined calorie bar above -- higher specificity
   than .calendar-bar's own height, needed since that rule comes later in
   this file and would otherwise win the cascade. */
.nutrition-plan-bars .calendar-bar { height: 8px; }

/* .calendar-bar-mid's shared default (further down this file) is colored
   to match the page background -- meant to read as a subtle notch on the
   calendar's own small bars, not a real target marker. These bars need
   their target line to actually be visible at a glance, against both the
   grey unfilled track and each bar's own colored fill, so this scoped
   override gives them a real, high-contrast line instead -- var(--fg)
   (near-black on light, near-white on dark) rather than --muted, and
   close to fully opaque, so it reads clearly rather than as a faint
   suggestion of a line. */
.nutrition-plan-bars .calendar-bar-mid {
  width: 2px;
  margin-left: -1px;
  background: var(--fg);
  opacity: .85;
}

.nutrition-meals {
  margin-top: .5rem;
  padding-top: .5rem;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: .35rem;
}

.nutrition-meal {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  font-size: .8rem;
}

.nutrition-meal-name { font-weight: 500; }
.nutrition-meal-macros { color: var(--muted); text-align: right; }

/* The plan's meals in a day card (see _nutrition_day_macro.html): one
   collapsible row per meal, ingredient table inside. */
.planned-meals {
  margin-top: .5rem;
  padding-top: .5rem;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: .25rem;
}
.planned-meal { font-size: .8rem; }
.planned-meal-summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  cursor: pointer;
  padding: .15rem 0;
  list-style: none;
}
.planned-meal-summary::-webkit-details-marker { display: none; }
.planned-meal-summary::before {
  content: "\25B8";
  display: inline-block;
  width: .9em;
  color: var(--muted);
}
.planned-meal[open] > .planned-meal-summary::before { content: "\25BE"; }
.planned-meal-name { font-weight: 500; margin-right: auto; }
.planned-meal-macros { color: var(--muted); text-align: right; }
.planned-meal-note { margin: .15rem 0 .25rem 1.1rem; color: var(--muted); font-style: italic; }
.planned-meal-items { width: calc(100% - 1.1rem); margin: 0 0 .4rem 1.1rem; border-collapse: collapse; }
.planned-meal-items td { padding: .15rem .4rem .15rem 0; border-top: 1px solid var(--border); }
.planned-meal-qty, .planned-meal-kcal { text-align: right; white-space: nowrap; color: var(--muted); }

/* When the column is narrow (a phone, or the bars taking their share beside it) every
   meal's macros drop to their own line under its name, instead of only the ones that
   happen not to fit beside it -- the same layout for each row. */
@container (max-width: 440px) {
  .planned-meal-macros, .nutrition-meal-macros { flex-basis: 100%; text-align: left; }
  .planned-meal-summary { row-gap: 0; }
  .planned-meal-macros { padding-left: calc(.9em + 1rem); } /* under the name, past the arrow */
  /* Ingredients: the name gets the row's full width (it was squeezed to one word per
     line between the two nowrap columns), with the quantity and calories on a line under it. */
  .planned-meal-items, .planned-meal-items tbody { display: block; font-size: .8rem; }
  .planned-meal-items tr { display: grid; grid-template-columns: 1fr auto; column-gap: .5rem; padding: .15rem 0; border-top: 1px solid var(--border); }
  .planned-meal-items td { display: block; padding: 0; border: 0; font-size: inherit; }
  .planned-meal-items td:first-child { grid-column: 1 / -1; }
  .planned-meal-qty, .planned-meal-kcal { white-space: normal; }
  .planned-meal-qty { text-align: left; }
}

/* The week row's plan buttons (Shopping list / Batch cooking) and the dialog they
   open -- see _nutrition_days_fragment.html and _nutrition_week_panel.html. */
.week-divider-plan { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
.plan-buttons { display: inline-flex; gap: .4rem; text-transform: none; letter-spacing: 0; }
.plan-open-btn {
  font: inherit;
  font-size: .72rem;
  font-weight: 600;
  padding: .15rem .6rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg);
  color: var(--fg);
  cursor: pointer;
}
.plan-open-btn:hover { border-color: var(--highlight); color: var(--highlight); }

.plan-dialog {
  width: min(42rem, calc(100vw - 1.5rem));
  max-height: calc(100vh - 2rem);
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card);
  color: var(--fg);
}
.plan-dialog::backdrop { background: rgba(0, 0, 0, .55); }
.plan-dialog[open] { display: flex; flex-direction: column; }
.plan-dialog-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: .6rem 1rem;
  border-bottom: 1px solid var(--border);
}
.plan-dialog-head h2 { margin: 0; font-size: 1.05rem; }
.plan-dialog-close { font-size: 1.5rem; line-height: 1; border: 0; background: none; color: var(--muted); cursor: pointer; }
.plan-dialog-body { padding: .75rem 1rem 1rem; overflow-y: auto; }

.plan-progress { margin: 0 0 .5rem; color: var(--muted); font-size: .85rem; }
.plan-shop, .plan-batch { margin-bottom: 1.25rem; }
.plan-shop h3, .plan-batch h3 { margin: .25rem 0 .1rem; font-size: 1rem; }
.plan-shop h4 { margin: .75rem 0 .2rem; font-size: .75rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.plan-shop-date { font-size: .75rem; font-weight: 400; color: var(--muted); }
.plan-note { margin: .1rem 0 .4rem; font-size: .82rem; color: var(--muted); font-style: italic; }
.plan-lines { list-style: none; margin: 0; padding: 0; }
.plan-line { border-top: 1px solid var(--border); }
.plan-line-row { display: flex; align-items: baseline; justify-content: space-between; gap: .5rem; }
.plan-line label { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; padding: .3rem 0; cursor: pointer; font-size: .88rem; flex: 1; min-width: 0; }
.plan-line input { flex: none; align-self: center; }
.plan-line-name { font-weight: 500; }
.plan-line-buy { color: var(--fg); }
.plan-line-note { color: var(--muted); font-size: .8rem; }
.plan-line-also { color: var(--muted); font-size: .72rem; border: 1px solid var(--border); border-radius: 999px; padding: 0 .45rem; }
.plan-line-parts { margin: -.15rem 0 .35rem 1.55rem; color: var(--muted); font-size: .75rem; }
.plan-combine { display: flex; align-items: center; justify-content: space-between; gap: .5rem; flex-wrap: wrap; margin: 0 0 .6rem; font-size: .85rem; color: var(--muted); }
.plan-combine-actions { display: flex; gap: .4rem; }
.plan-line-stub .plan-line-row { align-items: center; padding: .3rem 0; font-size: .88rem; }
.plan-line-stub .plan-line-name { font-weight: 400; color: var(--muted); flex: none; }
.plan-line-stub .plan-line-note { flex: 1; }
.plan-line input:checked ~ span { text-decoration: line-through; opacity: .55; }
.plan-line-pantry .plan-line-name { font-weight: 400; color: var(--muted); }
.plan-covers { margin: .1rem 0 .5rem; font-size: .85rem; }
.plan-steps { margin: .25rem 0 0; padding-left: 1.3rem; font-size: .9rem; }
.plan-steps li { margin-bottom: .45rem; line-height: 1.4; }
.plan-step label { display: flex; align-items: baseline; gap: .5rem; cursor: pointer; }
.plan-step input { flex: none; align-self: flex-start; margin-top: .25rem; }
.plan-step input:checked + span { text-decoration: line-through; opacity: .55; }
.plan-batch-progress { margin-left: .4rem; font-size: .75rem; font-weight: 400; color: var(--muted); }
.planned-meal-batch { margin-left: .5rem; padding: 0 .4rem; border-radius: 999px; font-size: .65rem; font-weight: 600; background: var(--border); color: var(--muted); }

.chart-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
}

.chart-card-header h2 { margin: 0; }

/* Hover (or focus, for keyboard/touch) target for a chart's explanatory
   text that used to sit permanently under the title as a .chart-legend-
   note -- the native title attribute handles the actual popup, same
   convention as .calendar-bar's own title tooltip elsewhere on this page. */
.chart-info-icon {
  display: inline-flex;
  color: var(--muted);
  cursor: help;
}

.chart-info-icon:hover,
.chart-info-icon:focus-visible {
  color: var(--fg);
  outline: none;
}

/* Raw numbers behind the calibration chart's blue/orange lines (see
   buildCalibrationTable in index.html) -- the same two colors the chart
   itself uses (DOT_BLUE/DOT_ORANGE), so a column and its line read as the
   same series without a separate legend. */
.calibration-table-toggle { margin-top: .75rem; }

.calibration-table-holder { margin-top: .75rem; }

.calibration-table-scroll {
  max-width: 100%;
  max-height: 420px;
  overflow-y: auto;
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
}

.calibration-table {
  border-collapse: collapse;
  width: 100%;
  font-size: .8rem;
  white-space: nowrap;
}

.calibration-table th,
.calibration-table td {
  padding: .35rem .6rem;
  text-align: right;
  border-bottom: 1px solid var(--border);
}

.calibration-table th:first-child,
.calibration-table td:first-child { text-align: left; }

.calibration-table thead th {
  position: sticky;
  top: 0;
  background: var(--card);
  font-weight: 600;
}

.calibration-table-blue { color: hsl(223, 49%, 64%); }
.calibration-table-orange { color: hsl(33, 69%, 76%); }
.calibration-table-filled { font-style: italic; opacity: .65; }
.calibration-table-note { margin-top: .5rem; font-size: .8rem; opacity: .7; }

.chart-legend-note {
  font-size: .75rem;
  color: var(--muted);
  margin: .5rem 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  flex-wrap: wrap;
  text-align: center;
}

/* A small 3-way segmented control (e.g. the Efficiency Factor chart's
   swim/bike/run switch) -- same rounded-pill grouping idea as the folder
   tabs above, just compact enough to sit inside one chart card. */
.chart-toggle {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  margin: .5rem 0;
}

.chart-toggle-btn {
  font-size: .78rem;
  font-weight: 600;
  color: var(--muted);
  background: var(--card);
  border: none;
  padding: .3rem .85rem;
  cursor: pointer;
  font-family: inherit;
}

.chart-toggle-btn + .chart-toggle-btn { border-left: 1px solid var(--border); }
.chart-toggle-btn:hover { color: var(--fg); }
.chart-toggle-btn.active { background: var(--highlight); color: #fff; }

/* The EF chart's sport switch (left) and date-range switch (right) share
   one row -- Indoor only sits on its own line underneath instead (see
   .chart-checkbox-toggle below), since it's a filter on the sport switch,
   not a peer choice alongside the range switch. */
.ef-controls-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: .5rem;
}

/* A plain checkbox toggle alongside a .chart-toggle segmented control (e.g.
   the Efficiency Factor chart's "Indoor only" filter) -- deliberately not
   styled as another segmented button, since it's a filter on top of the
   sport switch above it, not a peer choice of its own. */
.chart-checkbox-toggle {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  font-size: .78rem;
  color: var(--muted);
  cursor: pointer;
  margin: 0 0 .5rem;
}

.chart-checkbox-toggle input { cursor: pointer; }

.legend-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-left: .4rem;
}

.legend-green { background: #2e9e5b; }
.legend-yellow { background: #d4a72c; }
.legend-red { background: #d1495b; }
.legend-orange { background: #e0a458; }
.legend-strava { background: #fc4c02; } /* Strava's own brand orange */
.legend-garmin { background: #007cc3; } /* Garmin's own brand blue */
.legend-trainingpeaks { background: #204d74; } /* TrainingPeaks' own brand blue */
.legend-blue { background: #4062bb; }
.legend-darkblue { background: #1a2f66; }
.legend-grey { background: #9a9aa5; }
.legend-darkgrey { background: #5a5a63; }
.legend-lightgrey { background: #b8b8c0; }

.plan-link {
  font-size: .75rem;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
  margin-left: .5rem;
  color: var(--muted);
}

.row-upcoming { font-style: italic; color: var(--muted); }

.row-bg-planned { background: rgba(150, 150, 160, 0.14); }
.row-bg-swim { background: var(--tint-swim); }
.row-bg-bike { background: var(--tint-bike); }
.row-bg-run { background: var(--tint-run); }
.row-bg-other { background: var(--tint-other); }
.row-bg-missed { background: var(--tint-missed); } /* same red as --accent/legend-red */

.legend-swatch {
  display: inline-block;
  width: 14px;
  height: 10px;
  border-radius: 3px;
  margin-left: .4rem;
  vertical-align: middle;
}

main {
  max-width: 1100px;
  margin: 0 auto;
  padding: 1.5rem;
  display: grid;
  gap: 0.5rem;
}

/* Site-wide utility links. Shares main's width and gutter so the link sits
   under the left edge of the content above it rather than the viewport's. */
.sitefooter {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 1.5rem 1.5rem;
  font-size: .8rem;
  display: flex;
  gap: 1rem;
}

.sitefooter a { color: var(--muted); text-decoration: none; }
.sitefooter a:hover { color: var(--fg); text-decoration: underline; }
.sitefooter-version { margin-left: auto; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Grid items default to min-width: auto, not 0 -- meaning by default they
   refuse to shrink below their own content's natural (min-content) width,
   even when that's wider than the viewport. That's the actual root cause
   behind more than one "tab X is too wide on mobile" report: a single
   unshrinkable element anywhere inside the currently-visible tab (a table
   with nowrap cells, a long header) was able to force this single-column
   grid's whole track wider, blowing out the entire page's width -- not
   just that one element's own box. .folder-tab-panel is a *second*,
   nested grid for the same reason (stacking cards within one tab, see its
   own rule below), so needs the identical override for its own children.
   Once actually permitted to shrink, the flex-wrap rules already used
   throughout this stylesheet (.calendar-header, .nutrition-meal, etc.)
   reflow correctly on their own -- this single change, not per-tab
   patches, is what actually keeps any tab's content inside the viewport. */
main > *,
.folder-tab-panel > * {
  min-width: 0;
}

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1.25rem;
}

.card h2 { margin-top: 0; font-size: 1rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }

/* Long-form prose (the privacy policy). Everything else on this site is
   dashboard chrome -- dense cards of numbers -- so this is the one place
   that needs a readable measure and real paragraph rhythm. Capped at 70ch
   rather than the card's full 1100px, since a policy read edge-to-edge on
   a desktop monitor is a wall of text. */
.policy-card { padding: 1.5rem; }

.policy-body {
  max-width: 70ch;
  font-size: .92rem;
  line-height: 1.65;
}

.policy-body h3 {
  margin: 1.75rem 0 .5rem;
  font-size: .95rem;
  letter-spacing: .01em;
}

.policy-body h4 {
  margin: 1.25rem 0 .4rem;
  font-size: .88rem;
  color: var(--muted);
}

.policy-body p { margin: 0 0 .75rem; }
.policy-body ul { margin: 0 0 .75rem; padding-left: 1.25rem; }
.policy-body li { margin-bottom: .35rem; }
/* Nested list (the infrastructure providers under Section 5) -- tighter, so
   it reads as a continuation of its parent bullet rather than a new block. */
.policy-body ul ul { margin: .35rem 0 0; }
.policy-body a { color: var(--highlight); overflow-wrap: anywhere; }
.policy-body a:hover { color: var(--highlight-hover); }

.policy-updated { color: var(--muted); }

/* Unfilled [FILL IN ...] placeholders from the draft. Styled to be
   impossible to miss and to survive a skim -- these are the parts that are
   not yet true, and shipping one unnoticed is worse than an ugly page.
   Uses --accent rather than a <mark>'s default yellow, which is illegible
   on this stylesheet's dark background. */
.policy-todo {
  background: transparent;
  color: var(--accent);
  border: 1px dashed var(--accent);
  border-radius: 4px;
  padding: 0 .3rem;
  font-size: .85em;
  font-weight: 600;
  /* These placeholders run to full sentences, so they have to be able to
     break across lines like the prose around them. */
  overflow-wrap: anywhere;
}

.sources-header {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: .9rem;
  margin-bottom: 1rem;
  font-size: .85rem;
}

.sources-header form { margin: 0; }

.tab-status-dots { display: inline-flex; align-items: center; gap: .3rem; margin-left: .5rem; }

.sources {
  display: flex;
  flex-direction: column;
  gap: .6rem;
}

.source-row {
  display: flex;
  align-items: center;
  gap: .75rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: .6rem .9rem;
  font-size: .85rem;
}

.source-row strong { min-width: 100px; }
.source-row .empty { flex: 1; }

.status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
.status-ok { background: #2e9e5b; }
.status-partial { background: #e0a458; }
.status-error { background: var(--accent); }
.status-auth-expired { background: var(--accent); animation: pulse-dot 1.6s ease-in-out infinite; }
/* "syncing" is the client-side dot state set by the in-page "sync now"
   button while its request is in flight; "running" is the SyncLog.status
   value a page load/refresh sees when a sync (triggered here or by cron)
   is still actually in progress server-side -- same situation, so same
   look, rather than falling through to .status-dot's default grey. */
.status-syncing, .status-running { background: #d4a72c; animation: pulse-dot 0.9s ease-in-out infinite; }

@keyframes pulse-dot {
  0%, 100% { opacity: 1; }
  50% { opacity: .35; }
}

.auth-expired-banner {
  background: rgba(209, 73, 91, 0.12);
  border: 1px solid var(--accent);
  border-radius: 6px;
  padding: .6rem .9rem;
  font-size: .85rem;
  margin: .5rem 0 0;
}

.auth-expired-banner a { color: inherit; font-weight: 600; }

.source-row .sync-form { margin-left: auto; }

/* Sync All (in .sources-header) and each row's own Sync Now share this same
   base shape -- previously Sync All was styled as a plain text link
   (.settings-link), which read as a lesser/secondary action next to the
   per-source buttons rather than the same kind of action at a wider scope.
   Sync All keeps the app's regular --highlight blue (same as every other
   primary button elsewhere), while Sync Now uses a lighter tint of it --
   same shape and same family of color, but visibly the narrower-scope
   action of the two sitting right next to it. */
.source-row button, .sync-all-btn {
  flex-shrink: 0;
  white-space: nowrap;
  border: none;
  color: #fff;
  cursor: pointer;
  border-radius: 6px;
  padding: .4rem .9rem;
  font-size: .8rem;
  font-weight: 600;
  font-family: inherit;
}

.sync-all-btn { background: var(--highlight); }
.sync-all-btn:hover { background: var(--highlight-hover); }
.source-row button { background: var(--highlight-light); }
.source-row button:hover { background: var(--highlight-light-hover); }

/* TEMPORARY -- see the matching comment on .backfill-tools in index.html.
   One .backfill-tool per source currently backfilling. Styled as an
   outlined button (.settings-link's own look, reused rather than
   duplicated) instead of solid-blue like Sync All/Sync Now, so these read
   as separate, one-off tools rather than peers of those two regular
   actions. margin-right: auto on the group pushes it to the left of
   .sources-header (which is otherwise right-aligned), away from Sync
   All, so they aren't easily mistaken for a pair. */
.backfill-tools { margin-right: auto; display: flex; align-items: center; gap: 1rem; }
.backfill-tool { display: flex; align-items: center; gap: .6rem; }
.backfill-tool button {
  flex-shrink: 0;
  white-space: nowrap;
  font-size: .8rem;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: .4rem .9rem;
  font-family: inherit;
  cursor: pointer;
}
.backfill-tool button:hover { color: var(--fg); border-color: var(--fg); }
.backfill-tool button:disabled { cursor: default; opacity: .6; }
.backfill-status { font-size: .78rem; color: var(--muted); }

/* view sync history -- bottom-right corner of the sources card itself
   (see .sources-card in the template), not a separate element below it. */
.sources-footer { margin-top: 1rem; text-align: right; }
.sources-footer .plan-link { margin-left: 0; }

.grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
}

/* Takes up a full row on its own instead of sharing one with the next card
   -- a no-op once .grid-2 collapses to a single column below, so this only
   has any effect on non-mobile widths. */
/* min-width: 0 overrides the grid item's default min-width: auto, which
   otherwise lets wide content inside it (the calibration data table's
   nowrap columns) force the whole grid track wider than the viewport
   instead of scrolling inside its own .calibration-table-scroll. */
.grid-2 .chart-card-wide { grid-column: 1 / -1; min-width: 0; }

@media (max-width: 800px) {
  .grid-2 { grid-template-columns: 1fr; }
}

table { width: 100%; border-collapse: collapse; font-size: .9rem; }
th, td { text-align: left; padding: .5rem .4rem; border-bottom: 1px solid var(--border); }
th { color: var(--muted); font-weight: 500; }

.user-search-form {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-bottom: 1rem;
}
.user-search-form input {
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: .5rem .6rem;
  font-size: .9rem;
  flex: 1;
  max-width: 320px;
}

.empty { color: var(--muted); font-size: .9rem; }

.user-row-action-form { margin: 0; }

.user-row-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .35rem;
}

/* The per-row "Edit" email control -- a <details> disclosure rather than a
   separate page/modal, since the only field involved is one text input.
   Collapsed by default so the table stays scannable; opening one row's
   form doesn't require any JS. */
.user-email-edit { display: inline-block; margin-left: .5rem; }
.user-email-edit summary {
  display: inline;
  font-size: .8rem;
  color: var(--highlight);
  cursor: pointer;
  list-style: none;
}
.user-email-edit summary::-webkit-details-marker { display: none; }
.user-email-edit summary:hover { color: var(--highlight-hover); }
.user-email-edit-form {
  display: flex;
  align-items: center;
  gap: .4rem;
  margin: .5rem 0 0;
}
.user-email-edit-form input[type="email"] {
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: .35rem .55rem;
  font-size: .85rem;
  min-width: 220px;
}

/* "Folder" tabs -- attach to the top of whichever panel's first card is
   showing, so the active tab reads as part of that card rather than a
   separate control floating above it. */
.folder-tabs {
  display: flex;
  gap: .3rem;
  position: relative;
  z-index: 1;
}

.folder-tab-btn {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-size: .82rem;
  font-weight: 600;
  color: var(--muted);
  background: var(--bg);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  padding: .55rem 1rem .5rem;
  cursor: pointer;
  font-family: inherit;
  /* Only needed for the tabs that render as <a> (a real page link, e.g.
     Users -- see settings.html/settings_users.html) rather than <button>
     (the rest, JS-toggled in place) -- an anchor is underlined by the
     browser's own default styling, a button never is, and this class is
     meant to look identical either way. */
  text-decoration: none;
}

.folder-tab-icon { flex-shrink: 0; }

/* Same reasoning as text-decoration above -- a visited <a>-rendered tab
   would otherwise pick up the browser's own default visited-link color
   instead of this class's usual muted/active grey. */
.folder-tab-btn:visited { color: var(--muted); }

.folder-tab-btn:hover { color: var(--fg); }

.folder-tab-btn.active {
  color: var(--fg);
  background: var(--card);
}

/* The card right below the tabs -- flush against them (no top-left radius,
   pulled up 1px to cover the shared border) so the active tab visually
   merges into it. */
.folder-tab-first-card {
  border-radius: 0 10px 10px 10px;
  margin-top: -1px;
}

/* Each tab's own content stack -- a plain div, not a grid-item child of
   <main> like the tabs/panels themselves, so it needs its own gap between
   whatever cards/sections live inside it. */
.folder-tab-panel {
  display: grid;
  gap: 0.5rem;
}

.settings-form h3 {
  font-size: .85rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .04em;
  margin: 1.5rem 0 .75rem;
}

.settings-form h3:first-child { margin-top: 0; }

.settings-group {
  display: grid;
  gap: .9rem;
  grid-template-columns: 1fr;
}

/* A short note sitting between two fields in a .settings-group (e.g. the
   Withings "Connect" link) -- same muted/small treatment as a field's own
   label, but not part of the grid's field-input pairing. */
.settings-field-note {
  grid-column: 1 / -1;
  font-size: .8rem;
  color: var(--muted);
  margin: -.4rem 0 0;
}

.settings-inline-btn {
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: .35rem .7rem;
  font-size: .8rem;
  cursor: pointer;
}
.settings-inline-btn:hover { border-color: var(--highlight); }
.settings-inline-btn:disabled { opacity: .6; cursor: default; }

.settings-field {
  display: flex;
  flex-direction: column;
  gap: .3rem;
  font-size: .85rem;
  color: var(--muted);
}

.settings-field input,
.settings-field select {
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: .5rem .6rem;
  font-size: .9rem;
}

.settings-password-wrap {
  display: flex;
  gap: .5rem;
}

.settings-password-wrap input {
  flex: 1;
  min-width: 0;
}

.settings-password-toggle {
  flex-shrink: 0;
}

/* A toggle rather than a fill-in-the-blank -- checkbox and its label sit on
   one row instead of the column stack every other field type uses. */
.settings-field-checkbox {
  flex-direction: row;
  align-items: center;
  gap: .5rem;
}

.settings-field-checkbox input {
  width: auto;
  accent-color: var(--highlight);
}

.settings-field-checkbox span {
  color: var(--fg);
  font-size: .9rem;
}

/* A value the "Weight & diet targets" card computes rather than asks for
   (current weight, the derived fat %) -- same slot as a normal field's
   input, but plain text instead of something editable. */
.targets-readonly-value {
  color: var(--fg);
  font-size: .9rem;
  padding: .5rem 0;
}

.targets-slider {
  accent-color: var(--highlight);
  width: 100%;
}

/* The slider's live-computed results -- target date, the deficit/surplus
   it implies, and the calorie target that comes from it -- read together
   as one row rather than three separate fields, since none of them is
   itself an input. */
.targets-readout {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin: .75rem 0 1.25rem;
  padding: .75rem 1rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.targets-readout-item {
  display: flex;
  flex-direction: column;
  gap: .2rem;
}

.targets-readout-label {
  font-size: .75rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .04em;
}

.targets-readout-item span:last-child {
  font-size: .95rem;
  font-weight: 600;
}

.settings-textarea {
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: .6rem .7rem;
  font-size: .8rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  resize: vertical;
  width: 100%;
}

.settings-actions-top,
.settings-actions-bottom {
  display: flex;
  justify-content: flex-end;
}

.settings-actions-top { margin-bottom: 1rem; }
.settings-actions-bottom { margin-top: 2.5rem; gap: .6rem; align-items: center; }

/* Wraps an action that posts to its own route rather than submitting the
   shared settings form, so it can sit in the same row as Save without
   nesting a form inside one. */
.settings-inline-form { margin: 0; }

/* Secondary to the Save button beside it -- see --highlight-light's own
   comment for the convention. */
.settings-actions-bottom .settings-secondary-btn { background: var(--highlight-light); }
.settings-actions-bottom .settings-secondary-btn:hover { background: var(--highlight-light-hover); }

.settings-actions-top button,
.settings-actions-bottom button {
  background: var(--highlight);
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: .6rem 1.2rem;
  font-size: .9rem;
  font-weight: 600;
  cursor: pointer;
}

.settings-actions-top button:hover,
.settings-actions-bottom button:hover { background: var(--highlight-hover); }

.settings-saved {
  color: #2e9e5b;
  font-size: .85rem;
  font-weight: 600;
}

.dashboard-flash {
  margin: 0 0 1rem;
}

/* Same shape as .settings-saved, for an action that reported back without
   having worked -- same red already used for text elsewhere (--accent). */
.settings-flash-error {
  color: #d1495b;
  font-size: .85rem;
  font-weight: 600;
}

.login-card {
  max-width: 360px;
  margin: 3rem auto 0;
}

/* The login page's own card -- see login.html's own comment for why this
   echoes app/emails.py's _email_html header treatment (logo + wordmark, a
   warm one-liner, a solid brand-colored button) rather than the plainer
   .login-card look. */
.auth-card {
  max-width: 400px;
  margin: 3rem auto 0;
  padding: 2.4rem 2.2rem 2.2rem;
  text-align: center;
}

.auth-card-header { margin-bottom: 1.6rem; }

.auth-logo { display: block; margin: 0 auto .9rem; }

.auth-wordmark {
  margin: 0 0 1.5rem;
  font-size: .78rem;
  font-weight: 400;
  color: var(--muted);
}

.auth-wordmark-caps {
  font-weight: 800;
  letter-spacing: .03em;
  color: var(--fg);
}

/* Overrides .card h2's small-caps section-label treatment (uppercase,
   muted, letter-spaced) -- needs .auth-card-header's own specificity to
   win over it, not just a same-weight class on the same element. */
.auth-card-header .auth-heading {
  margin: 0 0 .5rem;
  font-size: 1.3rem;
  color: var(--fg);
  text-transform: none;
  letter-spacing: normal;
}

.auth-lede {
  margin: 0;
  color: var(--muted);
  font-size: .9rem;
  line-height: 1.5;
}

/* Fields stay left-aligned (labels above inputs, as everywhere else in the
   app) even though the surrounding card centers its header text. */
.auth-form { text-align: left; margin-top: 1.4rem; }

.auth-divider {
  border: none;
  border-top: 1px solid var(--border);
  margin: 1.6rem 0 0;
}

.btn-primary {
  display: block;
  width: 100%;
  background: var(--highlight);
  color: #fff;
  font-family: inherit;
  font-weight: 700;
  font-size: .95rem;
  border: none;
  border-radius: 9px;
  padding: .75rem 1.2rem;
  margin-top: .3rem;
  cursor: pointer;
}

.btn-primary:hover { background: var(--highlight-hover); }

.login-error {
  color: var(--accent);
  font-size: .85rem;
  font-weight: 600;
}

.login-notice {
  color: var(--text-secondary, inherit);
  font-size: .85rem;
  font-weight: 600;
}

.login-resend-form {
  margin: .5rem 0 1rem;
}

/* Wider than the plain .login-card -- the consent disclosure below needs
   room to actually read as paragraphs, not a cramped column. */
.setup-card {
  max-width: 520px;
}

.setup-disclosure {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: .9rem 1rem;
  margin: 1rem 0;
}

.setup-disclosure p {
  font-size: .85rem;
  margin: 0;
}

.setup-disclosure p + p {
  margin-top: .6rem;
}

.login-links {
  margin-top: 1rem;
  font-size: .85rem;
  text-align: center;
}

.token-flash {
  background: rgba(46, 158, 91, 0.12);
  border: 1px solid #2e9e5b;
  border-radius: 6px;
  padding: .75rem 1rem;
  font-size: .85rem;
  margin: 1rem 0;
}

.token-flash code {
  display: block;
  margin-top: .4rem;
  font-size: .95rem;
  word-break: break-all;
}

.token-form {
  display: flex;
  gap: .6rem;
  align-items: flex-end;
  margin-top: 1rem;
}

.token-form .settings-field { flex: 1; margin: 0; }

.token-form button {
  margin: 0;
  background: var(--highlight);
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: .55rem 1rem;
  font-size: .85rem;
  cursor: pointer;
}

.token-form button:hover { background: var(--highlight-hover); }

.revoke-form { margin: 0; }

.revoke-form button {
  background: transparent;
  border: none;
  color: var(--accent);
  text-decoration: underline;
  cursor: pointer;
  font-size: .8rem;
  padding: 0;
}

.mfa-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  z-index: 1000;
}

.mfa-modal {
  max-width: 420px;
  width: 100%;
}

.mfa-modal-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 2.5rem;
}

/* Same footprint as the Settings page's Save button for both, so Submit and
   Cancel line up -- Cancel just gets an outlined/secondary look instead of
   the filled highlight color. */
.mfa-modal-actions button {
  margin: 0;
  padding: .6rem 1.2rem;
  font-size: .9rem;
  font-weight: 600;
  border-radius: 6px;
  cursor: pointer;
}

.mfa-modal-actions button[type="submit"] {
  background: var(--highlight);
  color: #fff;
  border: none;
}

.mfa-modal-actions button[type="submit"]:hover {
  background: var(--highlight-hover);
}

.mfa-modal-cancel {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border);
}

.mfa-modal-cancel:hover {
  color: var(--fg);
  border-color: var(--fg);
}

/* Wider than the base .mfa-modal (sized for a short verification-code
   form) -- this one's holding a diagnostic text report meant to be read
   and copied, not a couple of form fields. */
.garmin-ef-modal {
  max-width: 720px;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
}

.garmin-ef-output {
  flex: 1;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: .75rem;
  line-height: 1.4;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: .75rem;
  margin-top: .5rem;
}

/* Hidden -- the year itself wasn't judged worth the space next to
   active days/workouts/hours, especially since the x-axis below already
   shows the year implicitly through its month labels. The heading stays
   in the DOM (rather than being removed from the template) since the
   month/week-nav JS still keeps its text updated as you cross a year
   boundary, for the weekly-volume-year comparison that decides whether
   to refetch the chart -- only the display is turned off here. */
.weekly-volume-year { display: none; }

.weekly-volume-stats {
  display: flex;
  justify-content: center;
  align-items: center;
  /* Wraps rather than crushing: the calibration card puts four stats here, each
     with a long unit label, which on a phone would otherwise squeeze every
     column into a five-line ribbon. Cards with fewer stats are unaffected,
     since wrapping only takes effect once they would overflow anyway. */
  flex-wrap: wrap;
  gap: 2.5rem;
  margin-bottom: .75rem;
}

.weekly-volume-stats .stat {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.weekly-volume-stats .stat .value { font-size: 1.5rem; font-weight: 600; }
.weekly-volume-stats .stat .label { font-size: .8rem; color: var(--muted); }
.plan-stat-short { display: none; } /* phone only -- see the max-width: 700px block */

/* The error bar rides along with the coefficient at a size that keeps the
   number the thing being read, without letting it be read on its own. */
.weekly-volume-stats .stat .value small {
  font-size: .75rem;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
}

/* A figure inside its own error bar is greyed to the muted colour the
   labels use: still legible, but visibly not a result. */
.weekly-volume-stats .stat .value.stat-weak { color: var(--muted); }

.metric-verdict {
  margin: 0.5rem 0 0;
  font-size: .85rem;
  color: var(--muted);
  line-height: 1.45;
}

.weekly-volume-wrap {
  position: relative;
  width: 100%;
  height: 90px;
}

.calendar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: .75rem;
}

.calendar-header-controls {
  display: flex;
  align-items: center;
  gap: .6rem;
}

/* Hover/focus popover for the calendar's own legend -- same trigger
   (.chart-info-icon) as the efficiency chart's plain-text tooltip, but this
   legend has colored dots/swatches/icons a native title attribute can't
   render, so it's a positioned element revealed on hover/focus instead. */
.calendar-legend-icon { position: relative; }

.calendar-legend-popover {
  display: none;
  position: absolute;
  top: calc(100% + .5rem);
  right: 0;
  z-index: 20;
  width: max-content;
  max-width: min(420px, 90vw);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, .12);
  padding: .6rem .8rem;
  cursor: auto;
  text-align: left;
}

.calendar-legend-popover .chart-legend-note {
  justify-content: flex-start;
  text-align: left;
  margin: .4rem 0 0;
}
.calendar-legend-popover .chart-legend-note:first-child { margin-top: 0; }

.calendar-legend-icon:hover .calendar-legend-popover,
.calendar-legend-icon:focus .calendar-legend-popover,
.calendar-legend-icon:focus-within .calendar-legend-popover {
  display: block;
}

/* Swim/Bike/Run calendar filter (see applyCalendarSportFilter in index.html)
   -- multi-select, so more than one .chart-toggle-btn can be .active at
   once, unlike the efficiency chart's exclusive version of this control. */
.calendar-workout-hidden { display: none; }

/* Up-arrow "load next week" control, above the calendar grid/week list and
   the Nutrition log (see loadFutureWeek in index.html) -- one click loads
   one future week, prepended above whatever was previously at the top. */
.calendar-load-future-row {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: .4rem .8rem;
  margin-bottom: .6rem;
}

/* Beside the Nutrition tab's button: the plan buttons for the week it would load next. */
#planNextHolder:empty { display: none; }
.plan-next-buttons { display: inline-flex; align-items: center; gap: .4rem; flex-wrap: wrap; justify-content: center; }
.plan-next-label { font-size: .72rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }

.calendar-load-future-btn {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  font-size: .75rem;
  font-weight: 600;
  font-family: inherit;
  color: var(--muted);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: .3rem .8rem;
  cursor: pointer;
}

.calendar-load-future-btn:hover { color: var(--fg); border-color: var(--fg); }
.calendar-load-future-btn:disabled { opacity: .5; cursor: default; }

/* Desktop grid: one persistent header row of weekday labels, then every
   loaded/appended day-cell as a flat child below it -- grid-template-
   columns: repeat(7, ...) auto-wraps every 7 cells into a new row, so a
   week never needs its own wrapper element for the row structure to hold. */
.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}

/* Marks a week's start in the infinite-scroll calendar/nutrition lists --
   see _week_divider in dashboard_data.py for how the label text (and
   which weeks get the bolder month treatment) is computed. One plain,
   left-aligned label band, same look everywhere: the nutrition list and
   the calendar's mobile list (both plain block/flex columns) just get a
   bit of margin around it since they don't already have a bordered
   container providing separation; .week-divider-grid below only adds
   what the desktop grid specifically needs -- spanning a full grid row
   instead of sitting in a list. */
.week-divider {
  display: block;
  margin: .5rem 0 .3rem;
  padding: .3rem .6rem;
  background: var(--card);
  border-radius: 6px;
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}

.week-divider:first-child { margin-top: 0; }

.week-divider-month {
  color: var(--fg);
  font-weight: 700;
  background: var(--border);
}

.week-divider-grid,
.week-divider-grid:first-child {
  grid-column: 1 / -1;
  margin: 0;
  border-radius: 0;
}

/* A week's TSS-target bar, inside its week-range divider row (see
   _calendar_week_bar_macro.html): label on the left, bar filling the rest.
   Fill segments are the day-cell boxes' hues (--tint-*, top of file) at a
   stronger tint, so the bar reads brighter than the boxes themselves. */
.week-divider-bar { display: flex; align-items: center; gap: .75rem; }
.week-tss { display: contents; }
.week-tss-track {
  position: relative;
  flex: 1;
  min-width: 0;
  height: 8px;
  border-radius: 4px;
  background: var(--border);
}
@media (prefers-color-scheme: dark) {
  /* Darker than the row, so the dark tinted fill reads against the empty track. */
  .week-tss-track { background: rgba(0, 0, 0, 0.3); box-shadow: inset 0 0 0 1px var(--border); }
}
.week-tss-fill { display: flex; height: 100%; overflow: hidden; border-radius: 4px; }
.week-tss-seg {
  flex: none;
  height: 100%;
  border-right: 1px solid var(--card);
  box-sizing: border-box;
  /* Tint-over-card, as a workout box in the day cells gets (.row-bg-*). */
  background: linear-gradient(var(--tint), var(--tint)), var(--card);
}
.week-tss-seg-swim { --tint: rgba(64, 98, 187, 0.45); }
.week-tss-seg-bike { --tint: rgba(155, 89, 182, 0.45); }
.week-tss-seg-run { --tint: rgba(46, 158, 91, 0.45); }
.week-tss-seg-other { --tint: rgba(224, 164, 88, 0.45); }

/* The week's target tick: a 6px-wide hit area for the hover title, a thin
   line drawn inside it, standing a little proud of the track. */
.week-tss-target,
.week-tss-actual {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 6px;
  transform: translateX(-3px);
}
.week-tss-target::after,
.week-tss-actual::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 2px;
  width: 2px;
  border-radius: 1px;
  background: var(--fg);
  opacity: .75;
}
/* The actual-TSS tick takes the per-workout dots' colors (.legend-green/-yellow/-red). */
.week-tss-actual::after { opacity: 1; }
.week-tss-actual-green::after { background: #2e9e5b; }
.week-tss-actual-yellow::after { background: #d4a72c; }
.week-tss-actual-red::after { background: #d1495b; }
.week-tss-actual-grey::after { background: #74747c; }
.week-tss-text { flex: none; min-width: 8.5em; text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.week-tss-text-over { color: var(--fg); }
/* Phone week list: the figure alone, pushed to the divider's right edge. */
.week-tss-text-only { margin-left: auto; min-width: 0; }

/* Desktop grid: the divider takes the day columns' own layout, so the bar
   spans exactly Tue-Sat (the middle five) and Mon / Sun keep the week label
   and the TSS figures. Padding moves from the divider onto its items so the
   columns line up with the day cells below (same 1px gap as .calendar-grid). */
.week-divider-grid.week-divider-bar {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  column-gap: 1px;
  padding: .3rem 0;
}
.week-divider-grid.week-divider-bar > span:first-child {
  grid-column: 1;
  padding-left: .6rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.week-divider-grid .week-tss-track { grid-column: 2 / 7; margin: 0 .6rem; }
.week-divider-grid .week-tss-text { grid-column: 7; min-width: 0; padding-right: .6rem; }

.calendar-head {
  background: var(--card);
  padding: .4rem;
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
}

.calendar-cell {
  background: var(--card);
  min-height: 108px;
  padding: .35rem;
}

.calendar-today { box-shadow: inset 0 0 0 2px var(--highlight); }

.calendar-day-num {
  font-size: .72rem;
  font-weight: 500;
  color: var(--muted);
  min-width: 20px;
  height: 20px;
  box-sizing: border-box;
  /* "Oct 1" on a month's first day is wider than the 20px circle -- let the
     pill grow (no wrapping) instead of pushing the number onto a second line. */
  padding: 0 .25rem;
  white-space: nowrap;
  width: fit-content;
  margin-left: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
}

.calendar-day-num-today { font-weight: 700; color: #fff; background: var(--highlight); }

/* Reserves the same sliver of space below the day number whether or not that
   day actually has calorie data -- so the workouts below always start from
   the same spot, instead of shifting up on data-less days. */
.calendar-bar-row { height: 5px; margin-top: 2px; margin-bottom: 4px; }

.calendar-bar {
  position: relative;
  height: 5px;
  border-radius: 3px;
  background: var(--border);
  overflow: hidden;
}

.calendar-bar-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
}

/* Same green-deficit/red-surplus convention as the Calories chart above --
   the darker shade of each until the gap passes _CALORIE_BAR_MAX_DELTA,
   see .over-max below. */
.calendar-bar-fill.deficit { background: #206f40; transform: translateX(-100%); }
.calendar-bar-fill.surplus { background: #9d3744; }

/* Forecasted (from the 7-day weight-change estimate, not actual synced/
   logged data) -- same grey already used for an upcoming planned workout's
   dot. Direction/transform still come from .deficit/.surplus above; this
   only overrides the color. */
.calendar-bar-fill.forecast { background: #74747c; opacity: .8; }

/* Past _CALORIE_BAR_MAX_DELTA, the bar is already maxed out at its full
   half-width and can't grow any further to show *how much* bigger the gap
   really is -- the brighter shade of each color is the second signal for that. */
.calendar-bar-fill.deficit.over-max { background: #2e9e5b; }
.calendar-bar-fill.surplus.over-max { background: #d1495b; }
.calendar-bar-fill.forecast.over-max { background: #9a9aa5; }

/* A past day with a burn figure but nothing logged: one flat bar across the
   whole track (no direction, so no centre tick either -- see the calendar
   templates), in the Charts tab calories chart's own "burned only" gold. */
.calendar-bar-fill.unlogged { left: 0; background: #e0a458; }

.calendar-bar-mid {
  position: absolute;
  top: -1px;
  bottom: -1px;
  left: 50%;
  width: 1px;
  background: var(--card);
}

.calendar-workout {
  font-size: .68rem;
  line-height: 1.35;
  border-radius: 4px;
  padding: 3px 4px;
  margin-top: 3px;
}

.calendar-workout-line1 {
  display: flex;
  align-items: center;
  gap: 4px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.calendar-workout-line2 {
  padding-left: 10px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.calendar-workout-line3 {
  padding-left: 10px;
  font-size: .6rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.calendar-workout-dot { margin-left: 0; }

/* The one thing to focus on next time (see _workout_focus) -- an
   actionable callout, not just informational like line3's EF/Strain, so
   it gets the accent/warning tone rather than --muted. */
.calendar-workout-focus {
  padding-left: 10px;
  font-size: .6rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.calendar-workout-focus-warn { color: #e0a458; } /* same as .legend-orange */
.calendar-workout-focus-ok { color: #4caf7a; } /* nothing to fix -- green like .legend-green */

/* A planned workout's structure -- one bar per step, zone-colored (see
   app/workout_structure.py). The SVG stretches to the cell's width. */
.calendar-workout-structure {
  display: block;
  width: 100%;
  height: 18px;
  margin-top: 3px;
  border-radius: 2px;
}
.calendar-week-row .calendar-workout-structure { height: 26px; }

.tss-compare-icon {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}

.tss-compare-hot { color: #d1495b; } /* same red as --accent/legend-red -- over plan, ran "hot" */
.tss-compare-cold { color: #3d7ea6; } /* a cool blue distinct from the Garmin/TP brand blues -- under plan, ran "cold" */

.missed-icon {
  display: inline-flex;
  align-items: center;
  color: #d1495b; /* same red as --accent/legend-red */
  flex-shrink: 0;
}

.indoor-icon {
  display: inline-flex;
  align-items: center;
  color: var(--muted);
  flex-shrink: 0;
}

/* The mobile day list is a full sibling of the desktop week grid, not a
   CSS reflow of it -- both are always rendered server-side (see
   _calendar_grid_panel.html/_calendar_week_panel.html); this breakpoint
   just picks which one is visible, matching the existing mobile breakpoint
   below. */
.calendar-mobile-view { display: none; }

.calendar-week-list {
  display: flex;
  flex-direction: column;
  gap: .6rem;
}

.calendar-week-row {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: .6rem .75rem;
  background: var(--card);
}

.calendar-week-today { box-shadow: inset 0 0 0 2px var(--highlight); }

.calendar-week-day-header {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  margin-bottom: .4rem;
}

.calendar-week-weekday { font-weight: 600; font-size: .9rem; color: var(--fg); }
.calendar-week-date { font-size: .8rem; color: var(--muted); }

.calendar-week-empty {
  font-size: .8rem;
  color: var(--muted);
  font-style: italic;
  padding-left: 2px;
}

/* Full-width rows have room to spare -- no need for a compressed size or
   ellipsis-truncated lines. */
.calendar-week-row .calendar-workout { font-size: .78rem; }
.calendar-week-row .calendar-workout-line1,
.calendar-week-row .calendar-workout-line2 { white-space: normal; }

@media (max-width: 700px) {
  .calendar-desktop-view .calendar-workout { font-size: .62rem; }
  .calendar-desktop-view .calendar-cell { min-height: 90px; }
  .calendar-desktop-view { display: none; }
  .calendar-mobile-view { display: block; }

  /* A 2.5rem gutter between four stats is most of a phone's width. Once
     they wrap (see .weekly-volume-stats), a tighter gutter is what lets two
     of them actually share a line instead of stacking one per row. */
  .weekly-volume-stats { gap: 1rem 1.25rem; }
  .weekly-volume-stats .stat { flex: 1 1 40%; max-width: 50%; }
  /* Phone shows only the headline stats (e.g. the Nutrition tab: days logged and
     weight change); the rest stay on the desktop layout. */
  .weekly-volume-stats .stat-desktop-only { display: none; }

  /* Roadmap totals: just planned and actual for the chart's Hours/TSS toggle. */
  #planTotals .plan-stat { display: none; }
  #planTotals[data-metric="hours"] .plan-stat[data-metric="hours"],
  #planTotals[data-metric="tss"] .plan-stat[data-metric="tss"] { display: flex; }
  .plan-stat-full { display: none; }
  .plan-stat-short { display: inline; }

  /* The dot/title/status-line/button row was a single non-wrapping flex
     line -- fine on desktop, but the status text ("error · 0 rec ·
     Aug 31, 09:36") alone is wider than a phone screen, pushing the whole
     page out horizontally. Wrap the status line onto its own line under
     the title instead of shrinking (or truncating) it -- order: 10 keeps
     it visually last (under the button) regardless of its actual position
     in the row's markup, and flex-basis: 100% is what actually forces the
     wrap onto a new line rather than just reflowing in place. */
  .source-row {
    flex-wrap: wrap;
  }
  .source-row .empty {
    flex-basis: 100%;
    order: 10;
    margin-top: .3rem;
  }

  /* The 4 folder tab labels (Calendar/Charts/Nutrition/Sources, the last
     with its own row of status dots) don't fit a phone-width viewport at
     full size -- rather than shrinking or scroll-hiding them (a scrolling
     tab bar has no visual affordance here to show there's more off to the
     side, so it just reads as broken), drop to icon-only, same pattern the
     header's own Settings link already uses on mobile
     (.settings-link-text). overflow-x: auto stays as a safety net -- it
     shouldn't be needed at icon-only size, but costs nothing if it ever is
     (a future 5th tab, longer source list widening the status dots, etc.)
     and is strictly better than actual page-level overflow either way. */
  .folder-tabs {
    overflow-x: auto;
    flex-wrap: nowrap;
  }
  .folder-tab-btn {
    flex-shrink: 0;
  }
  .folder-tab-text {
    display: none;
  }
}

/* Roadmap tab -- see static/training_plan.js. */
.plan-header { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: .5rem 1rem; margin-bottom: .5rem; }
.plan-zoom { display: inline-flex; align-items: center; gap: .35rem; }
.plan-zoom .settings-inline-btn { display: inline-flex; align-items: center; padding: .3rem .45rem; }
.plan-zoom-label { min-width: 2.6rem; text-align: center; font-size: .8rem; font-weight: 600; color: var(--muted); }
.plan-year { margin: 0; font-size: 1.1rem; color: var(--fg); text-transform: none; letter-spacing: 0; }
.plan-chart-wrap { position: relative; width: 100%; height: 260px; }
.plan-legend { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem 1rem; margin-top: .75rem; font-size: .8rem; color: var(--muted); }
.plan-legend-item { display: inline-flex; align-items: center; gap: .35rem; }
/* The legend: a button beside the Hours/TSS toggle; the popover spans the legend row and opens upward,
   over the bottom of the chart, so it never covers the Training periods / Target races buttons below. */
.plan-legend { position: relative; }
.plan-key-btn { display: inline-flex; align-items: center; gap: .3rem; font: inherit; font-size: .8rem; color: var(--muted); background: none; border: 0; padding: .2rem .3rem; cursor: pointer; }
.plan-key-btn:hover, .plan-key-btn:focus-visible, .plan-key.open .plan-key-btn { color: var(--fg); outline: none; }
.plan-key-pop {
  display: none; position: absolute; left: 0; right: 0; bottom: 100%; z-index: 20;
  flex-wrap: wrap; gap: .5rem 1rem; padding: .6rem .75rem;
  background: var(--card); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 4px 14px rgba(0, 0, 0, .25);
}
.plan-key-pop::after { content: ""; position: absolute; left: 0; right: 0; top: 100%; height: .6rem; } /* keeps hover across the gap to the button */
.plan-key.open .plan-key-pop { display: flex; }
@media (hover: hover) { .plan-key:hover .plan-key-pop, .plan-key:focus-within .plan-key-pop { display: flex; } }
.plan-swatch { display: inline-block; width: .7rem; height: .7rem; border-radius: 2px; flex: none; }
.plan-error { color: #c62828; font-size: .85rem; margin: .5rem 0 0; }
.plan-hint { font-size: .8rem; color: var(--muted); line-height: 1.45; margin: .5rem 0 0; }
.plan-period-row, .plan-race-row { display: flex; align-items: center; gap: .6rem; padding: .35rem 0; border-bottom: 1px solid var(--border); font-size: .9rem; }
.plan-period-dates, .plan-race-dates { color: var(--muted); font-size: .8rem; margin-right: auto; }
.plan-period-form, .plan-race-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .6rem; margin-top: .75rem; }
.plan-period-form label, .plan-race-form label { display: flex; flex-direction: column; gap: .2rem; font-size: .75rem; color: var(--muted); }
/* The checkered-flag glyph (see FLAG_SVG/raceIcon in training_plan.js) --
   literal black/white image, not a currentColor mask, so its own fixed
   width/height (set per-instance by priority) is all this needs to add. */
.plan-race-flag { vertical-align: text-bottom; flex: none; }
.settings-hr-scroll { overflow-x: auto; margin: .25rem 0 .75rem; }
.settings-hr-table { border-collapse: collapse; font-size: .85rem; }
.settings-hr-table th { text-align: left; font-weight: 600; color: var(--muted); padding: .25rem .5rem .25rem 0; white-space: nowrap; }
.settings-hr-table thead th { text-align: center; padding-bottom: .35rem; }
.settings-hr-table td { padding: .2rem .35rem; }
.settings-hr-table input { width: 4.75rem; text-align: center; }
@media (max-width: 640px) {
  .settings-hr-table th[scope="row"] { white-space: normal; max-width: 5.5rem; font-size: .8rem; }
  .settings-hr-table th, .settings-hr-table td { padding-left: .15rem; padding-right: .15rem; }
  .settings-hr-table input { width: 3.3rem; padding: .3rem .1rem; }
}
/* The Training periods and Target races dialogs (native <dialog>s, each opened from its own button under the Roadmap chart). */
.plan-manage-row { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: .5rem; margin-top: .75rem; }
.plan-manage-row .settings-inline-btn { display: inline-flex; align-items: center; gap: .4rem; }
.plan-manage-count { color: var(--muted); }
.plan-dialog {
  width: min(780px, calc(100vw - 2rem));
  max-height: calc(100vh - 3rem);
  overflow-y: auto;
  color: var(--fg);
  box-shadow: 0 12px 40px rgba(0, 0, 0, .35);
}
.plan-dialog::backdrop { background: rgba(0, 0, 0, .5); }
body:has(dialog[open]) { overflow: hidden; } /* the page behind doesn't scroll */
.plan-dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: .5rem; }
.plan-dialog-head h2 { margin: 0; margin-right: auto; }
.plan-dialog-head .plan-dialog-info { display: inline-flex; }
.plan-dialog-section { padding: .85rem 0; border-top: 1px solid var(--border); }
.plan-dialog-section .chart-card-header h2 { font-size: .9rem; }
.plan-dialog-foot { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding-top: .85rem; border-top: 1px solid var(--border); }
.plan-dialog-foot .plan-hint { margin: 0; }
@media (max-width: 640px) { .plan-dialog { padding: 1rem; max-height: calc(100vh - 1.5rem); } }

.plan-earlier-status { min-height: 1.4rem; margin-top: .5rem; text-align: center; font-size: .8rem; color: var(--muted); }
#planPeriods, #planRaces { margin-top: .25rem; }
.plan-period-edit, .plan-race-edit { flex-wrap: wrap; }
.plan-period-edit input, .plan-period-edit select,
.plan-race-edit input, .plan-race-edit select { font: inherit; font-size: .85rem; padding: .25rem .4rem; background: var(--card); color: var(--fg); border: 1px solid var(--border); border-radius: 6px; }
.plan-period-edit input[type="text"], .plan-race-edit input[type="text"] { width: 9rem; max-width: 100%; }
.plan-period-form input, .plan-period-form select,
.plan-race-form input, .plan-race-form select {
  font: inherit; font-size: .85rem; padding: .3rem .4rem; width: 8.5rem;
  background: var(--card); color: var(--fg); border: 1px solid var(--border); border-radius: 6px;
}
.plan-period-form input[type="number"] { width: 6.5rem; }
.plan-table-scroll { overflow-x: auto; } /* the page scrolls, not the table: older weeks load as you go */
.plan-table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.plan-table th { background: var(--card); text-align: left; font-weight: 600; color: var(--muted); padding: .35rem .5rem; border-bottom: 1px solid var(--border); }
.plan-table td { padding: .2rem .5rem; border-bottom: 1px solid var(--border); white-space: nowrap; }
.plan-table input { width: 4.5rem; font: inherit; padding: .2rem .3rem; background: var(--card); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; }
.plan-current-week td { background: rgba(64, 98, 187, .1); }
@media (max-width: 640px) { .plan-chart-wrap { height: 200px; } }
/* Phone: the range with its back/forward arrows on one line, Today and zoom on the next. */
@media (max-width: 700px) {
  .plan-header { flex-wrap: wrap; gap: .5rem; }
  #planPrev { order: 1; }
  #planRange { order: 2; flex: 1; min-width: 0; text-align: center; font-size: 1rem; white-space: nowrap; }
  #planNext { order: 3; }
  .plan-header::before { content: ""; order: 4; flex-basis: 100%; height: 0; }
  #planToday { order: 5; }
  .plan-zoom { order: 6; }
}

/* Admin Metrics page (settings_metrics.html). */
.metric-tiles { display: flex; flex-wrap: wrap; gap: .75rem; margin: .5rem 0 1rem; }
.metric-tile { flex: 1 1 8rem; display: flex; flex-direction: column; padding: .6rem .8rem; border: 1px solid var(--border); border-radius: 8px; }
.metric-num { font-size: 1.6rem; font-weight: 600; line-height: 1.1; color: var(--fg); }
.metric-label { font-size: .75rem; color: var(--muted); margin-top: .15rem; }
.metric-subhead { margin: 1rem 0 .4rem; font-size: .9rem; color: var(--fg); text-transform: none; letter-spacing: 0; }
.metric-table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.metric-table td { padding: .2rem .4rem; border: 0; }
.metric-key { white-space: nowrap; color: var(--muted); width: 1%; }
.metric-bar-cell { width: 100%; }
.metric-bar { height: .7rem; min-width: 2px; background: #4062bb; border-radius: 3px; }
.metric-val { text-align: right; width: 1%; }

/* Roadmap tab: down weeks. */
.plan-swatch-none { background: transparent; border: 1px solid var(--muted); }
.plan-swatch-current { background: rgba(64, 98, 187, .25); border: 1px solid rgba(64, 98, 187, .8); }
.plan-swatch-hatch { background: repeating-linear-gradient(135deg, var(--muted) 0 2px, transparent 2px 4px); border: 1px solid var(--muted); }
.plan-down-week td { background-image: repeating-linear-gradient(135deg, transparent 0 6px, rgba(128, 128, 128, .13) 6px 12px); }
.plan-down-week.plan-current-week td { background-color: rgba(64, 98, 187, .1); }
.plan-table select { font: inherit; padding: .2rem .3rem; background: var(--card); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; }
.plan-type-select { width: 4.75rem; }

/* Roadmap weekly targets on a phone: each week becomes a card instead of an
   eight-column row -- week and type on top, then period and race (only when
   the week has them), then planned/actual hours and TSS as a 2x2 grid, each
   cell titled by a ::before label (the header row is hidden). The tint for the
   current week and the hatching for a down week move from the cells to the card. */
@media (max-width: 700px) {
  .plan-table, .plan-table tbody { display: block; }
  .plan-table thead { display: none; }
  .plan-table tr {
    display: grid; grid-template-columns: 1fr 1fr; gap: .15rem .75rem; align-items: center;
    padding: .55rem .6rem; margin-bottom: .5rem; border: 1px solid var(--border); border-radius: 8px;
  }
  .plan-table td { padding: 0; border: 0; white-space: normal; background: none !important; }
  .plan-table td:empty { display: none; }
  .plan-table td:nth-child(1) { font-weight: 600; }
  .plan-table td:nth-child(4) { justify-self: end; }
  .plan-table td:nth-child(2), .plan-table td:nth-child(3) { grid-column: 1 / -1; font-size: .8rem; color: var(--muted); }
  .plan-table td:nth-child(n+5) { display: flex; flex-direction: column; margin-top: .35rem; }
  .plan-table td:nth-child(n+5)::before { font-size: .72rem; color: var(--muted); margin-bottom: .1rem; }
  .plan-table td:nth-child(5)::before { content: "planned hrs"; }
  .plan-table td:nth-child(6)::before { content: "actual hrs"; }
  .plan-table td:nth-child(7)::before { content: "planned TSS"; }
  .plan-table td:nth-child(8)::before { content: "actual TSS"; }
  /* order, not fixed rows, so a week with no period or race has no gap: week + type,
     period, race, then planned hours + planned TSS, then actual hours + actual TSS */
  .plan-table td:nth-child(1) { order: -2; }
  .plan-table td:nth-child(4) { order: -1; }
  .plan-table td:nth-child(5) { order: 1; }
  .plan-table td:nth-child(7) { order: 2; }
  .plan-table td:nth-child(6) { order: 3; }
  .plan-table td:nth-child(8) { order: 4; }
  .plan-table td:nth-child(6), .plan-table td:nth-child(8) { font-size: 1rem; }
  .plan-table input { width: 100%; max-width: 8rem; box-sizing: border-box; }
  .plan-table .plan-type-select { width: auto; }
  .plan-table tr.plan-current-week { background: rgba(64, 98, 187, .1); border-color: rgba(64, 98, 187, .6); }
  .plan-table tr.plan-down-week { background-image: repeating-linear-gradient(135deg, transparent 0 6px, rgba(128, 128, 128, .13) 6px 12px); }
}

/* EF vs. heart rate scatter: its legend (each dot group with its typical-EF line). */
.ef-hr-legend { display: flex; flex-wrap: wrap; gap: .4rem 1.1rem; justify-content: center; margin-top: .5rem; font-size: .8rem; color: var(--muted); }
.ef-hr-legend-item { display: inline-flex; align-items: center; gap: .4rem; }
.ef-hr-swatch { display: inline-block; width: 22px; height: 3px; border-radius: 2px; }
.ef-hr-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; }
.ef-hr-dot-recent { background: #e8833a; }
.ef-hr-dot-older { background: rgba(64, 98, 187, .5); }
@media (prefers-color-scheme: dark) { .ef-hr-dot-older { background: rgba(140, 168, 240, .6); } }
.ef-hr-swatch-range { background: #4062bb; }
.ef-hr-size { display: inline-block; border-radius: 50%; background: var(--muted); opacity: .7; }
.ef-hr-size-1 { width: 4px; height: 4px; }
.ef-hr-size-2 { width: 7px; height: 7px; }
.ef-hr-size-3 { width: 11px; height: 11px; }
.ef-hr-swatch-4wk { background: #e8833a; height: 4px; }
#efHrZoneSummary { text-align: center; font-weight: 600; }

/* Swapping an ingredient from the shopping list (see _plan_shopping_panel.html and
   the swap form built in index.html). */
.plan-swap-actions { flex: none; display: inline-flex; gap: .3rem; }
.plan-swap-btn, .plan-swap-undo-btn, .plan-combine-btn, .plan-combine-all, .plan-swap-fields button {
  font: inherit; font-size: .72rem; padding: .1rem .5rem; border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg); color: var(--fg); cursor: pointer;
}
.plan-swap-btn:hover, .plan-swap-undo-btn:hover, .plan-combine-btn:hover, .plan-combine-all:hover, .plan-swap-fields button:hover { border-color: var(--highlight); color: var(--highlight); }
.plan-swap-fields button[type="submit"] { background: var(--highlight); border-color: var(--highlight); color: #fff; }
.plan-line-swapped .plan-line-name { color: var(--highlight); }
.plan-swap-form { margin: 0 0 .5rem 1.4rem; padding: .6rem .7rem; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); font-size: .82rem; }
.plan-swap-form[hidden] { display: none; }
.plan-swap-form p { margin: 0 0 .4rem; }
.plan-swap-option, .plan-swap-mode { display: flex; align-items: baseline; gap: .4rem; margin: .15rem 0; cursor: pointer; flex-wrap: wrap; }
.plan-swap-option small, .plan-swap-mode small { color: var(--muted); }
.plan-swap-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr)); gap: .4rem; margin: .5rem 0; }
.plan-swap-grid label { display: flex; flex-direction: column; gap: .15rem; font-size: .7rem; color: var(--muted); padding: 0; }
.plan-swap-grid label.plan-swap-wide { grid-column: 1 / -1; }
.plan-swap-form input[type="text"], .plan-swap-form input[type="number"], .plan-swap-form select {
  font: inherit; font-size: .82rem; padding: .2rem .35rem; border: 1px solid var(--border); border-radius: 5px; background: var(--card); color: var(--fg); width: 100%;
}
.plan-swap-form input.plan-swap-amount { width: 5.5rem; }
.plan-swap-preview { color: var(--muted); }
.plan-swap-preview strong { color: var(--fg); }
.plan-swap-error { color: var(--accent); }
.plan-swap-done { margin: .4rem 0; padding: .4rem .6rem; border-radius: 6px; background: var(--bg); border: 1px solid var(--border); font-size: .82rem; }
.plan-step-swap, .planned-meal-was { color: var(--highlight); font-size: .75rem; white-space: nowrap; }
