/* Timeline — Now · Soon · Later (Blazor) */
/* Palette — warm monochrome "stone" system (user-spec). A single cream page
   base and one earthy warm-stone ramp carry the whole app. Amber is the
   only secondary hue and is reserved for live/time-passing signals.
   Everything else (Soon desaturation, Later recession, Earlier "past")
   is achieved via lightness + blur + a single off-family surface, not via
   additional hues. */
:root {
  /* Neutral surfaces — cream page, white for elevated modals, warm
     variants for zone backgrounds. */
  --surface-base:  #F5F2E8;   /* cream — page background (spec) */
  --surface-elev:  #FFFFFF;   /* dialogs, menus */
  --surface-muted: #EDE5D6;   /* Soon zone bg — same as stone-100 */
  --surface-soft:  #E8E3D2;   /* Earlier / Later zone bg — "slightly different
                                 warm tone than the stone ramp, so 'past / later'
                                 stays distinguishable from active content" */

  /* Stone ramp — primary warm-beige palette (user-spec). Named by how the
     values are used, not by formal lightness steps. */
  --stone-50:  #F5F0E8;   /* lightest wash — quick borders, subtle fills */
  --stone-100: #EDE5D6;   /* short-task fill, Spotlight card surface */
  --stone-200: #DDD0B8;   /* solid-task fill, quick-task border */
  --stone-300: #C4B296;   /* medium warm, hover states */
  --stone-400: #A89478;   /* deep-task fill ("warm stone" itself) */
  --stone-500: #8A7659;   /* top bar, Add Task button */
  --stone-600: #6B5A42;   /* dark text on light stone surfaces */
  --stone-700: #4A3E2E;   /* accent bar on deep tasks, darkest UI element */
  --stone-800: #241F1B;   /* full-panel dark ground — the paywall's commitment
                             surface. Deeper than stone-700 because that value
                             is tuned for small accents on cream and reads as
                             muddy across a whole panel. */

  /* Ink family — semantic text colors. Kept separate from the stone ramp
     because text needs its own perceptual lightness scale, independent of
     surface fills. */
  --ink-primary: #2C2C2A;   /* body text, task names */
  --ink-muted:   #6B6449;   /* secondary text, metadata, countdowns */
  --ink-faint:   #8A8578;   /* zone headers, "earlier" label, placeholders,
                               fixed-time dot markers (doesn't compete with
                               the amber "running" signal) */

  /* Warm amber — the one accent hue. Used only where "live / time passing"
     or "awareness, not alarm" matters (spotlight progress, overload dot). */
  --amber-300: #D9B06A;   /* light — progress lighter stripe */
  --amber-400: #C89848;   /* primary — progress bar, "running" (user-spec) */
  --amber-500: #B8873A;   /* darker — "running long", final third (user-spec) */
  --amber-600: #926A22;   /* darkest — amber text on cream */

  /* Public timeline tokens (referenced elsewhere in the file) */
  --timeline-now-bg:   var(--surface-base);
  --timeline-soon-bg:  var(--surface-muted);
  --timeline-later-bg: var(--surface-soft);
  --timeline-line:     var(--stone-200);
  --timeline-line-now: var(--stone-500);
}

/* Visually hidden but still announced. Defined locally rather than imported across
   stylesheets, matching the same pattern in account.css / integrations.css /
   paywall.css so each sheet stays independently loadable. `clip-path` over the
   legacy `clip` so the node keeps a real box for assistive tech. */
.hf-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Wrapper keeps timeline inside layout content and below sticky header */
.timeline-page {
  position: relative;
  z-index: 0;
}

.timeline {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  position: relative;
  z-index: 0;
  /* Never let hover lifts / shadows / fractional transforms bleed out into a
     page-level horizontal scrollbar. */
  overflow-x: clip;
  /* Reserve space at the bottom so the fixed-position "Done today" pill
     never overlaps the final task / footer content of the active sections.
     Tracks the launcher's `bottom: 1.25rem` plus its own height + a little
     breathing room. */
  padding-bottom: 4.5rem;
}

.timeline-content {
  position: relative;
  z-index: 0;
  flex: 0 1 auto;
}

.timeline .timeline-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 12rem;
}

/* Line is absolute and only as tall as the content, so it does not extend into empty space below */
.timeline-content::before {
  content: '';
  position: absolute;
  left: 2rem;
  top: 0;
  bottom: 0;
  width: 3px;
  background: linear-gradient(
    to bottom,
    var(--timeline-line-now) 0%,
    var(--timeline-line-now) 50%,
    var(--timeline-line) 50%,
    var(--timeline-line) 80%,
    var(--timeline-line) 80%,
    var(--timeline-line) 100%
  );
  z-index: 0;
  border-radius: 2px;
}

/* Hide the line while the timeline data is loading */
.timeline.loading .timeline-content::before {
  display: none;
}

/* Free-before-next (§24.3): composition is not the sectioned timeline — the rail
   reads as a stray vertical stroke beside the free-time panel. */
.timeline-content.timeline-content--no-rail::before {
  display: none;
}

/* Greeting banner */
.timeline-greeting {
  position: relative;
  z-index: 1;
  padding: 1.75rem 2rem 0 4rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 0.75rem;
}

.timeline-greeting-left {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 0.75rem;
}

.timeline-add-task-btn {
  flex-shrink: 0;
}

/* Find (Pick from today, §2.1) sits beside Add Task on desktop. */
.timeline-greeting-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

.timeline-find-btn.mud-button-root {
  text-transform: none;
  border-color: var(--stone-300);
  color: var(--stone-700);
}

/* Mobile FAB — fixed bottom-right */
.timeline-fab {
  position: fixed !important;
  bottom: 1.5rem;
  right: 1.5rem;
  z-index: 1100;
}

/* When the host shell renders the bottom tab bar (mobile MAUI, narrow web
   if it ever opts in), lift the FAB above the bar so it doesn't get hidden
   behind the Today/Someday/Plan strip. The bar is ~3.95rem tall plus the
   iOS safe-area inset; we add ~0.8rem of breathing room above it. */
.hf-has-bottom-tabs .timeline-fab {
  bottom: calc(4.75rem + env(safe-area-inset-bottom, 0px));
}

/* The Done-today pill needs the same lift. Without it the pill sits entirely
   underneath the opaque bottom tab bar on MAUI — the whole completion-reward
   surface (§14 / §16.5) is invisible and untappable on mobile.

   It also has to move to the bottom-LEFT wherever the tab bar renders, not just
   below 640px: the phone-width rule further down does that on its own, but a
   tab-bar shell at 641px and up would leave the pill lifted into the same
   bottom-right corner as the Add-Task FAB, overlapping it. */
.hf-has-bottom-tabs .done-today-launcher {
  bottom: calc(4.75rem + env(safe-area-inset-bottom, 0px));
  right: auto;
  left: 1rem;
  align-items: flex-start;
}

.timeline-greeting-text {
  font-size: 1.35rem;
  font-weight: 600;
  color: var(--ink-primary);
  line-height: 1.3;
}

.timeline-greeting-tz-link {
  font-size: 0.8rem;
  font-weight: 400;
  color: var(--ink-muted);
  text-decoration: none;
  white-space: nowrap;
}

.timeline-greeting-tz-link:hover {
  text-decoration: underline;
}

/* Energy selector row + day capacity meter (spec §11) on the right */
.timeline-energy {
  position: relative;
  z-index: 1;
  padding: 0.75rem 2rem 0.25rem 4rem;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.65rem 1rem;
}

.timeline-energy-left {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  flex: 1 1 auto;
  min-width: 0;
}

/* Names the chip row. Same micro-label register as the other system labels on
   this page (zone headers, the check-in eyebrow) so it reads as chrome telling
   you what the control is, not as a fourth option in the set. `nowrap` keeps
   the two words together when .timeline-energy-left wraps on a narrow phone —
   the label should push the chips onto the next line whole, not split itself. */
.timeline-energy-label {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
}

.timeline-energy-chips {
  display: flex;
  gap: 0.35rem;
}

.timeline-energy-chip {
  font-size: 0.78rem !important;
  min-height: 28px !important;
  padding: 0 0.7rem !important;
  box-sizing: border-box !important;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

/* MudBlazor's Outlined variant draws a 1px border while Filled has none, which
   makes a chip change width by 2px when it flips between selected/unselected and
   pushes its neighbors. Give the Filled variant a 1px transparent border so the
   box metrics stay identical across both states. */
.timeline-energy-chip.mud-chip-filled {
  border: 1px solid transparent !important;
}

.timeline-energy-chip:hover {
  transform: translateY(-1px);
}

.timeline-energy-hint {
  font-size: 0.78rem;
  color: var(--ink-faint);
  font-style: italic;
}

.timeline .section {
  position: relative;
  z-index: 1;
  padding: 2rem 2rem 2rem 4rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.timeline .section ul {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
  min-height: 3rem;
}

/* Insertion spacers between task rows.
   These are not interactive: the gesture layer in timeline-dnd.js hit-tests against
   measured geometry, and the gap a card will drop into is opened with transforms on
   the rows themselves rather than by growing a slot (growing one relayouts the whole
   list, and every zone below it, on every pointer move).
   They stay in the DOM because the Now and Soon zones express their proximity /
   density gradients as :nth-child(2/4/6/8), which only lands on task rows because a
   spacer precedes each one — and because they carry the inter-card rhythm. */
.timeline .section ul .drop-slot {
  list-style: none;
  min-height: 2px;
  margin: 0;
  padding: 0;
  border-radius: 2px;
}

.timeline .section-header {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #71717a;
  margin-bottom: 0.25rem;
}

.timeline .section-header-meta {
  font-size: 0.78rem;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  color: #71717a;
  margin: -0.1rem 0 0.5rem 0;
  line-height: 1.35;
}

/* Content-sized, like Soon and Later: §25 says zones "collapse to the height of
   their content" and never have fixed heights. Now used to be pinned to 40vh with
   `overflow: hidden`, which on most screens cut the second or third card off, and
   the Earlier tray then started right on the cut, so it looked as if it had been
   drawn over the card above it. Keeping Now short is the overload hint's job
   (§2), not a clip's. */
.timeline .section-now {
  flex: 0 1 auto;
  min-height: min-content;
  background: var(--timeline-now-bg);
  display: flex;
  flex-direction: column;
}

.timeline .section-now .section-header {
  flex-shrink: 0;
  color: var(--ink-faint);
}

/* Overload hint — ambient metadata, not a task. Mirrors .next-fixed-countdown
   (inline text, tiny leading dot, no fill/border) so it reads as "an
   observation about the list" rather than a card to parse. The dot is a
   muted amber so the type of metadata is distinguishable at a glance from
   the slate-dot "next fixed" line above it. Amber here carries the spec's
   "awareness, not alarm" warm-neutral idiom; the dot is small enough that
   it never competes with the Spotlight progress bar's amber signal. */
.timeline .section-now .now-overload-hint {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.25rem 0 0.2rem 0;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 0;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--ink-muted);
  letter-spacing: 0.01em;
}

.timeline .section-now .now-overload-hint::before {
  content: '';
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 50%;
  background: var(--amber-400);
  flex-shrink: 0;
}

.timeline .section-now ul {
  /* No 3rem empty floor here (`.timeline .section ul` gives Soon and Later one):
     an empty Now collapses to its header, per §25's collapse rule, and the
     SprintEntryCard sits just beneath it. The drag engine targets the whole
     section box, so this costs nothing as a drop target. */
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.timeline .section-now .task {
  flex: 0 0 auto;      /* natural height from min-height below, not flex-grow */
  display: flex;
  align-items: center;
}

/* Earlier — a slightly different warm tone (--surface-soft = #E8E3D2) so
   "past / missed" reads as distinguishable from the active stone surfaces
   in the rest of the app. Same family, different enough to feel aside.
   No size encoding here; all missed tasks look the same.

   Alignment note: horizontal margins match the Now-section task-card
   inset (left = .section padding-left = 4rem, right = .section
   padding-right = 2rem) so the framed "earlier" card's border lines up
   with the task cards above rather than drifting out to the timeline
   rail / page edge. Internal padding (not the section's default
   2rem / 4rem) is compact because everything inside is metadata, not
   a full task row. Gap is tightened for the same reason — the default
   1rem flex gap inherited from .section makes the header, hint, and
   task row feel disconnected inside a small card. */
.timeline .section-earlier {
  min-height: 0;
  margin-left: 4rem;
  margin-right: 2rem;
  /* Breathing room between the earlier card and the Soon zone that starts
     immediately below — without this, the framed card sits flush against
     Soon's different-coloured surface and the seam reads as a glitch. */
  margin-bottom: 1.5rem;
  padding: 0.85rem 1rem 1rem 1rem;
  gap: 0.35rem;
  border: 1px solid var(--stone-200);
  border-radius: 8px;
  background: var(--surface-soft);
}

/* Mask the vertical timeline rail (.timeline-content::before at x=2rem)
   where it runs down the earlier card's left gutter. The earlier card is
   a framed container of "past / missed" metadata, not a node ON the
   timeline, so the rail peeking through next to it reads as a stray
   line instead of structure. We paint the page background over the rail
   for exactly the earlier section's vertical span; the section's
   stacking context (z-index: 1) sits above the rail (z-index: 0), so a
   plain opaque fill is enough.

   Vertical extents deliberately overshoot the section box:
   • `top: -2px` covers the 1px `border-top` band (abs-positioned
     children anchor to the padding edge, so `top: 0` would leave the
     border band uncovered and the rail bleeds through as a tiny dot at
     the top-left corner) plus any sub-pixel seam against Now above.
   • `bottom: calc(-1.5rem - 2px)` reaches through the `margin-bottom`
     gap to Soon below, so the rail doesn't re-emerge as a short line
     between this card and the Soon surface. */
.timeline .section-earlier::before {
  content: '';
  position: absolute;
  top: -2px;
  bottom: calc(-1.5rem - 2px);
  left: -2.5rem;   /* section outer-left is at container-x = 4rem; rail is at
                      container-x = 2rem — cover 1.5rem..2.75rem around it */
  width: 1.25rem;
  background: var(--timeline-now-bg);
  pointer-events: none;
}

.timeline .section-earlier .section-header-earlier {
  font-size: 0.72rem;
  font-weight: 500;
  font-style: italic;
  letter-spacing: 0.12em;
  text-transform: lowercase;
  color: var(--ink-faint);
  margin: 0 0 0.15rem 0;
}

.timeline .section-earlier-hint {
  font-size: 0.72rem;
  color: var(--ink-faint);
  margin: 0 0 0.65rem 0;
  line-height: 1.35;
}

.timeline .section-earlier-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* Row layout — title on the left, actions on the right, single line.
   Near-white surface (stone-50) over the earlier card's --surface-soft
   background gives the task a clear "item on a tray" read without
   needing a visible border. Title color is ink-primary (not muted) so
   the missed task still reads as something that needs attention; the
   muted tone lives in the surrounding header + hint, not the task. */
.timeline .section-earlier .task-earlier {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 0.75rem;
  padding: 0.6rem 0.85rem;
  font-size: 0.85rem;
  border-radius: 6px;
  border: none;
  background: var(--stone-50);
  color: var(--ink-primary);
  box-shadow: none;
  filter: none;
  opacity: 1;
}

.timeline .task-earlier-title {
  flex: 1 1 auto;
  min-width: 0;
  line-height: 1.35;
  font-weight: 600;
  color: var(--ink-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.timeline .task-earlier-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.15rem 0.5rem;
  justify-content: flex-end;
  flex-shrink: 0;
}

/* Earlier action buttons — plain text, non-bold. Reschedule is the
   "give it another shot" default (neutral muted ink); Done is the
   terminal "close it out" action, colored with the amber accent so
   it reads as a small affordance without a full filled-button feel.
   Amber-600 is the darkest stop on the amber ramp — enough contrast
   on the near-white task surface to pass as a text link. */
.timeline .task-earlier-btn {
  text-transform: none;
  letter-spacing: normal;
  min-width: 0;
  padding: 0.15rem 0.4rem;
  font-size: 0.82rem;
  font-weight: 500;
}

.timeline .task-earlier-btn-reschedule {
  color: var(--ink-muted);
}

.timeline .task-earlier-btn-done {
  color: var(--amber-600);
}

.timeline .task-earlier-btn:hover {
  background: transparent;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.timeline .section-earlier.is-empty {
  display: none;
}

.timeline .section-earlier:not(.is-empty) {
  flex: 0 0 auto;
}

/* Now task — neutral base. Size (below) earns the density via fill
   saturation; proximity (nth-child heights below) earns the scale. The base
   rule is only a fallback — every task gets a size class from the razor. */
.timeline .section-now .task {
  opacity: 1;
  filter: none;
  color: var(--ink-primary);
  background: var(--surface-elev);
  border: 1px solid var(--stone-200);
  border-left: 3px solid var(--stone-300);
  border-radius: 10px;
  box-shadow: 0 1px 2px rgba(60, 40, 30, 0.04);
  transition: transform 0.25s ease, box-shadow 0.25s ease, background 0.2s ease, border-color 0.2s ease, opacity 0.2s ease, filter 0.2s ease;
}

/* Hover lift — translateY + a gentle center-origin scale. No translateX:
   that's what was shifting the card right and clipping the thick left
   accent bar on "deep" tasks. The scale is small enough (1.015) that the
   card just "breathes" rather than jumping, growing into the section's side
   padding, and `.timeline`'s `overflow-x: clip` means it can never produce a
   horizontal scrollbar. */
.timeline .section-now .task:hover {
  transform: translateY(-2px) scale(1.015);
  box-shadow: 0 8px 20px rgba(70, 45, 30, 0.10);
}

.timeline .section-now .task.size-deep:hover {
  box-shadow: 0 10px 24px rgba(74, 62, 46, 0.25);
}

/* -------------------------------------------------------------------------
   Proximity gradient (spec §5): the top task is perceptibly tallest and
   boldest; each one below steps down in height, padding, and weight.
   Heights roughly follow 96 → 72 → 60 → 52 → 48 px.
   4th+ tasks enter the overload zone — dimmed and slightly blurred so the
   eye knows "too many, consider moving some to Soon."
   ------------------------------------------------------------------------- */
.timeline .section-now ul > li.task:nth-child(2)  { min-height: 6rem;    padding: 1.35rem 1.5rem; font-size: 1.22rem; font-weight: 700; letter-spacing: -0.01em; }
.timeline .section-now ul > li.task:nth-child(4)  { min-height: 4.5rem;  padding: 1rem    1.3rem; font-size: 1.03rem; font-weight: 600; }
.timeline .section-now ul > li.task:nth-child(6)  { min-height: 3.75rem; padding: 0.8rem  1.15rem; font-size: 0.95rem; font-weight: 550; }
.timeline .section-now ul > li.task:nth-child(8)  { min-height: 3.25rem; padding: 0.65rem 1.05rem; font-size: 0.88rem; font-weight: 500; opacity: 0.78; filter: blur(0.3px); }
.timeline .section-now ul > li.task:nth-child(2n):not(:nth-child(2)):not(:nth-child(4)):not(:nth-child(6)):not(:nth-child(8)) {
  min-height: 3rem; padding: 0.55rem 1rem; font-size: 0.82rem; font-weight: 500;
  opacity: 0.62; filter: blur(0.6px);
}

/* On hover, overload tasks resolve to their true rendering so they can still
   be acted on. */
.timeline .section-now ul > li.task:nth-child(8):hover,
.timeline .section-now ul > li.task:nth-child(8):focus-visible,
.timeline .section-now ul > li.task:nth-child(2n):not(:nth-child(2)):not(:nth-child(4)):not(:nth-child(6)):not(:nth-child(8)):hover,
.timeline .section-now ul > li.task:nth-child(2n):not(:nth-child(2)):not(:nth-child(4)):not(:nth-child(6)):not(:nth-child(8)):focus-visible {
  opacity: 1;
  filter: none;
}

/* -------------------------------------------------------------------------
   Size density (Now only, spec §5). Monochrome stone-family fill gradient:
      Quick  → transparent fill, stone-200 1px border only, muted text.
      Short  → stone-100 fill, almost blending with the page cream.
      Solid  → stone-200 fill, dark text — dramatically lighter than deep.
      Deep   → stone-400 ("warm stone") fill, stone-700 left accent bar,
               cream text. Feels substantial without going black-on-white.
   ------------------------------------------------------------------------- */
.timeline .section-now .task.size-quick {
  background: transparent;
  border: 1px solid var(--stone-200);
  border-left: 1px solid var(--stone-200);
  color: var(--ink-muted);
  box-shadow: none;
}

.timeline .section-now .task.size-short {
  background: var(--stone-100);
  border-color: var(--stone-200);
  border-left: 3px solid var(--stone-300);
  color: var(--ink-primary);
}

.timeline .section-now .task.size-solid {
  background: var(--stone-200);
  border-color: var(--stone-300);
  border-left: 4px solid var(--stone-500);
  color: var(--ink-primary);
  box-shadow: 0 2px 6px rgba(110, 70, 50, 0.06);
}

.timeline .section-now .task.size-deep {
  background: var(--stone-400);              /* "warm stone" — deep-task fill */
  border: 1px solid var(--stone-500);
  border-left: 6px solid var(--stone-700);   /* darkest UI element as accent */
  color: var(--surface-base);                /* cream text on warm stone */
  box-shadow: 0 4px 14px rgba(74, 62, 46, 0.22);
  font-weight: 700;
}

/* On a "deep" (dark) task, the time badge / pills must flip to light-on-dark
   so contrast holds. Inside-out the light-mode rules. */
.timeline .section-now .task.size-deep .task-title,
.timeline .section-now .task.size-deep .task-complete-btn { color: var(--surface-base); }
.timeline .section-now .task.size-deep .task-time-badge {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.28);
  color: rgba(255, 255, 255, 0.92);
}
.timeline .section-now .task.size-deep .task-time-dot {
  color: rgba(255, 255, 255, 0.75);
}
.timeline .section-now .task.size-deep .size-pill {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(255, 255, 255, 0.35);
  color: #ffffff;
}

.timeline .section-soon {
  flex: 0 1 auto;
  min-height: min-content;
  /* Unified cream background across Now / Soon / Later — zone separation
     now comes entirely from per-task opacity/blur/size treatment (see
     `.section-soon .task` below) rather than a background shift. */
  background: var(--timeline-now-bg);
  padding-bottom: 2.5rem;
}

.timeline .section-soon .section-header {
  color: var(--ink-faint);
}

/* Soon — same warm-stone family but lighter + blurred. No separate hue; the
   zone recedes via lightness (stone-50/100) and blur. Pronounced enough
   to read as "deferred": text is legible but not demanding. Size doesn't get
   full visual weight here, but "deep" is allowed to read slightly denser so
   the capacity picture survives the blur. */
.timeline .section-soon .task {
  opacity: 0.72;
  filter: blur(1.6px);
  padding: 0.65rem 0.9rem;
  font-size: 0.8rem;
  border-radius: 8px;
  border: 1px solid var(--stone-100);
  border-left: 2px solid var(--stone-200);
  color: var(--ink-muted);
  background: var(--stone-50);
  box-shadow: 0 1px 4px rgba(70, 50, 40, 0.04);
  transition: transform 0.25s ease, filter 0.25s ease, opacity 0.25s ease, box-shadow 0.25s ease, background 0.25s ease;
}

/* `:focus-visible` only — task rows are tabbable now (space lifts them for a
   keyboard drag), and matching plain `:focus` would leave a card un-blurred after
   an ordinary click. The drag engine also suppresses mouse focus on pointerdown so
   the ring stays a keyboard affordance. */
.timeline .section-soon .task:hover,
.timeline .section-soon .task:focus-visible {
  opacity: 1 !important;
  filter: none !important;
  transform: translateY(-2px) scale(1.02);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
}
.timeline .section-soon .task:focus {
  outline: none;
}
.timeline .section-soon .task:focus-visible {
  outline: 2px solid var(--timeline-line-now);
  outline-offset: 2px;
}

/* Subtle per-size lift in Soon. Only "deep" is noticeably denser; the rest
   stay in the same light-stone range so the zone still reads as deferred. */
.timeline .section-soon .task.size-solid {
  background: var(--stone-100);
}
.timeline .section-soon .task.size-deep {
  background: var(--stone-200);
  border-left-color: var(--ink-muted);
  color: var(--ink-primary);
}

.timeline .section-later {
  flex: 0 1 auto;
  min-height: min-content;
  /* Unified cream background — see .section-soon note. Later still recedes
     via deeper per-task blur + opacity, not via a darker zone fill. */
  background: var(--timeline-now-bg);
  padding-bottom: 2.5rem;
}

.timeline .section-later .section-header {
  color: var(--ink-faint);
}

/* Later — near-monochrome. No size encoding on individual tasks; all rows
   look the same so the eye skips ahead to Now. Blur is deeper than Soon so
   the zone visually recedes. The capacity meter still picks up sizes. */
.timeline .section-later .task {
  opacity: 0.42;
  filter: blur(2.6px);
  padding: 0.5rem 0.75rem;
  font-size: 0.7rem;
  border-radius: 6px;
  border: 1px solid #ebeae8;
  border-left: 2px solid var(--stone-300);
  color: var(--ink-muted);
  background: var(--stone-100);
  box-shadow: none;
  transition: transform 0.25s ease, filter 0.25s ease, opacity 0.25s ease, box-shadow 0.25s ease;
}

.timeline .section-later .task:hover,
.timeline .section-later .task:focus-visible {
  opacity: 1 !important;
  filter: none !important;
  transform: translateY(-2px) scale(1.02);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
.timeline .section-later .task:focus {
  outline: none;
}
.timeline .section-later .task:focus-visible {
  outline: 2px solid var(--timeline-line-now);
  outline-offset: 2px;
}

.timeline .task {
  list-style: none;
  font-weight: 500;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  /* Horizontal travel belongs to swipe-to-complete and to the drag engine; vertical
     panning stays the browser's, so the page scrolls natively right up until a long
     press claims the gesture. Without this the browser can also steal a horizontal
     swipe for overscroll / back-navigation. */
  touch-action: pan-y pinch-zoom;
}

.timeline .task:active {
  cursor: grabbing;
}

/* Earlier rows share the .task class but are a read-only "already happened" tray —
   never a drag source, never a drop target. They should not advertise a grab. */
.timeline .task-earlier,
.timeline .task-earlier:active {
  cursor: default;
}

/* =========================================================================
   Drag and drop — the visual half of timeline-dnd.js.

   Everything here keys off `data-hf-*` attributes and a class on <body> rather
   than off classes on the rows themselves. Blazor rewrites the `class` attribute
   of anything it renders, so a JS-applied class on a task would be wiped by the
   next unrelated re-render; attributes it never rendered survive.
   ========================================================================= */

/* While a card is in the air the page belongs to the gesture. */
body.hf-dnd-active {
  cursor: grabbing;
  user-select: none;
  -webkit-user-select: none;
}

/* Rows must not react to the pointer sweeping across them. The hover lift and the
   Soon/Later un-blur would fire on every card the dragged one passes, which reads
   as noise around the one thing the user is concentrating on. */
body.hf-dnd-active .timeline .task {
  pointer-events: none;
}

body.hf-dnd-active .timeline .task-menu {
  opacity: 0 !important;
}

/* The row the card was lifted from keeps its box — that is what holds the list's
   layout completely still for the whole gesture — but goes invisible, because the
   rows below slide up over it to close the gap it left.

   Deliberately over-specified: a keyboard-lifted row is also the focused row, and
   the Soon/Later un-blur rules match `:focus-visible` with `!important` at four
   classes. This has to outrank them or the source stays fully visible under the
   card that was just lifted off it. */
body.hf-dnd-active .timeline .section .task[data-hf-drag="source"] {
  opacity: 0 !important;
  transform: none !important;
}

/* Rows, section boxes and in-section ornaments are moved with transforms only, so
   nothing relayouts mid-drag: the geometry measured at lift stays true, and the
   work stays on the compositor. `static` is the settling state applied without
   animation (at lift, and after a re-measure); `anim` is a target change. */
body.hf-dnd-active .timeline [data-hf-shift] {
  will-change: transform;
}

body.hf-dnd-active .timeline [data-hf-shift="anim"] {
  transition: transform 200ms cubic-bezier(0.2, 0, 0, 1),
              box-shadow 200ms cubic-bezier(0.2, 0, 0, 1) !important;
}

body.hf-dnd-active .timeline [data-hf-shift="static"] {
  transition: none !important;
}

/* A zone that grows to receive a card leaves a gap above the zone below it — the
   preview of the space the card is about to take. `.timeline-content::before`
   paints the timeline rail behind the zones, so that bare gap shows a short stroke
   of it: the same artifact `.section-earlier::before` already masks where the
   Earlier card's margins expose the rail. Bridge it with the zone's own surface.
   Scoped to the attribute rather than applied blanket, so the one element that
   carries a real box-shadow here (the float-suggestion card) is never touched. */
body.hf-dnd-active .timeline-content > [data-hf-bridge] {
  box-shadow: 0 var(--hf-dnd-bridge, 0px) 0 0 var(--timeline-now-bg) !important;
}

/* The card in flight. It is a clone in a fixed layer at <body> level, so it can
   travel over the whole page: .timeline clips horizontally. */
.hf-dnd-layer {
  position: fixed;
  inset: 0;
  /* Above the app's own fixed chrome (the collapsed web header sits at 1300) and
     below MudBlazor's dialog layer at 1400, so a card can travel over the page
     but never over a dialog. */
  z-index: 1350;
  pointer-events: none;
}

/* The layer replicates .timeline > .timeline-content > section > ul purely so the
   clone still matches every selector that styled it in place — per-zone fill,
   blur, size and the Now proximity gradient. `display: contents` keeps those
   shells out of layout entirely; they exist only for the cascade. */
.hf-dnd-layer .hf-dnd-shell {
  display: contents !important;
}

/* `display: contents` removes the box but keeps the element's pseudo-elements, and
   .timeline-content::before is a full-height 3px timeline rail. Without this the
   replica would paint that rail down the middle of the viewport, on top of
   everything, for the duration of every drag. */
.hf-dnd-layer .hf-dnd-shell::before,
.hf-dnd-layer .hf-dnd-shell::after {
  content: none !important;
  display: none !important;
}

/* Filler rows so `:nth-child()` still resolves to the same step of the gradient
   the card was rendered at. */
.hf-dnd-layer .hf-dnd-pad {
  display: none !important;
}

/* Position rides the `translate` property and the lift rides `scale`, which is
   what lets them carry different transitions: the card tracks the pointer 1:1
   while the lift still eases in, and only the settle animates position.
   `!important` because the clone deliberately still matches
   `.timeline .section-now .task { transition: transform … }`, which is both more
   specific and lists neither `translate` nor `scale` — inheriting it would drop
   the lift and the settle animation entirely. */
.hf-dnd-layer [data-hf-drag="clone"] {
  z-index: 1;
  scale: 1;
  transform: none !important;
  animation: none !important;
  transition: scale 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
              box-shadow 180ms ease,
              opacity 160ms ease !important;
}

.hf-dnd-layer [data-hf-drag="clone"][data-hf-lifted] {
  scale: 1.025;
  box-shadow: 0 18px 38px rgba(74, 62, 46, 0.26), 0 3px 8px rgba(40, 25, 18, 0.12) !important;
}

.hf-dnd-layer [data-hf-drag="clone"][data-hf-glide] {
  transition: translate var(--hf-dnd-settle, 220ms) cubic-bezier(0.2, 0.8, 0.2, 1),
              scale var(--hf-dnd-settle, 220ms) cubic-bezier(0.2, 0.8, 0.2, 1),
              box-shadow var(--hf-dnd-settle, 220ms) ease,
              opacity var(--hf-dnd-settle, 220ms) ease !important;
}

/* Settle from the drag preview into the layout .NET actually rendered. The
   engine measures the difference, applies it as an inverse transform, then
   releases it on the next frame — so zone-to-zone size changes and the temporal
   anchoring clamp resolve as a movement instead of a jump. */
.timeline [data-hf-flip="hold"] {
  transition: none !important;
}

.timeline [data-hf-flip="play"] {
  transition: transform 240ms cubic-bezier(0.2, 0, 0, 1) !important;
}

/* Screen-reader narration for the keyboard drag (space to lift, arrows to move,
   space to drop, escape to cancel). */
.hf-dnd-live {
  position: fixed;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  body.hf-dnd-active .timeline [data-hf-shift] {
    transition: none !important;
  }

  .hf-dnd-layer [data-hf-drag="clone"],
  .hf-dnd-layer [data-hf-drag="clone"][data-hf-glide] {
    transition: none !important;
  }
}

/* Brief animation when a task lands in its new position */
.timeline .task.task-just-dropped {
  animation: task-dropped 0.35s ease-out;
}

@keyframes task-dropped {
  0% {
    transform: scale(1.02);
    box-shadow: 0 8px 24px rgba(138, 118, 89, 0.22);
  }
  100% {
    transform: scale(1);
    box-shadow: 0 1px 2px rgba(60, 40, 30, 0.04);
  }
}

.timeline .section-soon ul > li.task:nth-child(2) { padding: 0.85rem 1rem; font-size: 0.88rem; min-height: 2.75rem; }
.timeline .section-soon ul > li.task:nth-child(4) { padding: 0.65rem 0.9rem; font-size: 0.8rem; min-height: 2.5rem; }
.timeline .section-soon ul > li.task:nth-child(6) { padding: 0.55rem 0.85rem; font-size: 0.75rem; min-height: 2.25rem; }
.timeline .section-soon ul > li.task:nth-child(2n):not(:nth-child(2)):not(:nth-child(4)):not(:nth-child(6)) { padding: 0.5rem 0.8rem; font-size: 0.72rem; min-height: 2rem; }

.timeline .section-later ul > li.task:nth-child(2) { padding: 0.65rem 0.85rem; font-size: 0.78rem; min-height: 2.25rem; font-weight: 600; }
.timeline .section-later ul > li.task:nth-child(4) { padding: 0.5rem 0.75rem; font-size: 0.7rem; min-height: 2rem; }
.timeline .section-later ul > li.task:nth-child(6) { padding: 0.45rem 0.7rem; font-size: 0.65rem; min-height: 1.75rem; }
.timeline .section-later ul > li.task:nth-child(2n):not(:nth-child(2)):not(:nth-child(4)):not(:nth-child(6)) { padding: 0.4rem 0.65rem; font-size: 0.62rem; min-height: 1.6rem; }

/* `:focus-visible` only, for the same reason as the zone rules above: task rows
   are tabbable now, and plain `:focus` would leave a card sharp and opaque after
   an ordinary click or tap. */
.timeline .section-soon ul > li.task:hover,
.timeline .section-soon ul > li.task:focus-visible,
.timeline .section-later ul > li.task:hover,
.timeline .section-later ul > li.task:focus-visible {
  opacity: 1 !important;
  filter: none !important;
}

/* =========================================================================
   Later, "Look through" (spec §2): the zone comes into focus on request.

   The resting look above is untouched. Only while `.is-looking` is on the
   section does every card drop the blur and the shrinking proximity sizes,
   so the whole zone reads at one size instead of one card per hover. It is
   switched off by "Done looking", by taking any task out of Later, and by
   leaving Today. `!important` because the `:nth-child()` size rules are up
   to seven classes deep and would otherwise win.

   timeline-dnd.js copies the section's classes onto the drag shell, so a
   card lifted while looking stays sharp in flight.
   ========================================================================= */
.timeline .section-later.is-looking ul > li.task {
  opacity: 1 !important;
  filter: none !important;
  padding: 0.6rem 0.85rem !important;
  font-size: 0.8rem !important;
  min-height: 2.4rem !important;
  font-weight: 500 !important;
  color: var(--ink-primary);
  background: var(--stone-50);
  border-color: var(--stone-200);
}

.timeline .section-later.is-looking .size-text {
  color: var(--ink-muted);
}

/* The link beside Later's count. The separator dot and the gaps are margins,
   not text, because Razor strips the whitespace beside an @if. */
.timeline .later-look-sep {
  margin: 0 0.15rem 0 0.4rem;
}

.timeline .later-look-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-left: 0.2rem;
  padding: 0.15rem 0.1rem;
  min-height: 1.75rem;
  border: 0;
  background: none;
  cursor: pointer;
  font: inherit;
  font-weight: 600;
  color: var(--stone-700);
  text-decoration: underline;
  text-decoration-color: var(--stone-300);
  text-underline-offset: 3px;
}

.timeline .later-look-toggle:hover {
  text-decoration-color: currentColor;
}

.timeline .later-look-toggle:focus-visible {
  outline: 2px solid var(--timeline-line-now);
  outline-offset: 2px;
  border-radius: 4px;
}

.timeline .later-look-toggle .later-look-icon {
  font-size: 1rem;
  width: 1rem;
  height: 1rem;
}

.timeline .task strong {
  font-weight: 600;
}

/* =========================================================================
   Size pills (spec §5): visual density hint per zone.
   ========================================================================= */

.size-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: auto;
  padding: 0.15rem 0.55rem;
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: lowercase;
  border-radius: 999px;
  background: var(--stone-50);
  color: var(--ink-muted);
  border: 1px solid var(--stone-200);
  line-height: 1;
  white-space: nowrap;
  flex-shrink: 0;
}

/* In Now, the pill doubles the density cue. We desaturate so it never fights
   the card fill behind it — it's a label, not another accent. */
.timeline .section-now .task .size-pill.size-quick {
  background: transparent;
  border-color: var(--stone-200);
  color: var(--ink-muted);
}
.timeline .section-now .task .size-pill.size-short {
  background: var(--surface-elev);
  border-color: var(--stone-200);
  color: var(--ink-muted);
}
.timeline .section-now .task .size-pill.size-solid {
  background: var(--stone-50);
  border-color: var(--stone-300);
  color: var(--ink-primary);
}
.timeline .section-now .task .size-pill.size-deep {
  background: var(--surface-elev);
  border-color: var(--stone-400);
  color: var(--ink-primary);
  font-weight: 700;
}

/* Soon: small inline text instead of a pill — low-visual-weight */
.size-text {
  margin-left: auto;
  font-size: 0.6rem;
  color: #71717a;
  letter-spacing: 0.06em;
  flex-shrink: 0;
}

/* Every task row is one flex line: title, then whatever badges the task carries,
   then the kebab.

   Later used to be left out of this, which left its <li> a block of inline
   spans — and `overflow` / `text-overflow` do not apply to a non-replaced inline
   box, so the ellipsis below was silently inert there: a long Later title ran
   straight out past the card's right edge, and because the title itself was
   `nowrap` the completion circle and the kebab were each pushed onto a line of
   their own. Later is a flex row like the other two zones now, so the title is
   the one thing that gives and the row keeps its shape. */
.timeline .section-soon .task,
.timeline .section-now .task,
.timeline .section-later .task {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* A long title WRAPS, in every zone. The zones are content-sized
   (`flex: 0 1 auto` + `min-height: min-content`) inside a page that scrolls, so a
   second line costs nothing, and a title the user can read beats a tidy row they
   have to open a menu to identify — which is the standard §10.13 sets for the
   task name anywhere it is shown: readable, not truncated. An ellipsis also fails
   the same section's 200%-text-zoom requirement in a way a wrap does not.

   Now was the last zone to cut its titles to one line (the lead card two, on a
   phone), so that no card could stand taller than the one above it (§5). The
   user chose readable titles over that strict height order (12 Oct 2026); the
   gradient still steps down in type size and weight.

   Then an ellipsis, as a backstop: three lines in Soon and Later, four in Now,
   whose larger type fits fewer characters to a line. Measured at 375px, the
   first card of each zone keeps about 110 characters in Now, 100 in Soon and 110
   in Later before the clamp bites, and the smaller cards below keep more — all
   well beyond the titles people write. Task titles are not length-capped
   anywhere in the stack, so the clamp is what stops one pasted paragraph turning
   a card into the tallest thing on the page.

   `overflow-wrap: anywhere` is load-bearing, not polish: a clamp only draws its
   ellipsis when the text runs to MORE lines than it allows, so a title that is one
   unbreakable token — a pasted URL, a long compound word — never triggers it and
   gets sliced mid-letter at the padding edge instead. Letting the token break puts
   it back over the line limit, which is what makes the ellipsis appear; it also
   keeps this working on iOS, where the element stays a real -webkit-box and
   `text-overflow` is not dependable.

   `flex-basis: 0`, not `auto`: from `auto` the title's base size is its full
   max-content width, so flexbox resolves the overflow by shrinking every badge on
   the row (only `.task-time-badge` sets `flex-shrink: 0`) before the title yields
   any of its own. From zero the title simply grows into whatever the row has left
   and the badges keep their natural widths. On a phone, where the rows wrap, it
   matters twice over: flexbox wraps before it shrinks, so an `auto` basis would
   shove the kebab onto a line of its own the moment a title got long. */
.timeline .task .task-title {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: normal;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
}

/* Now's type is the largest on the page, and at the inherited 1.43 leading a
   wrapped Now title falls apart into loose separate lines. */
.timeline .section-now .task > .task-title {
  -webkit-line-clamp: 4;
  line-clamp: 4;
  line-height: 1.25;
}

/* Fixed-time badge (spec §4 — gentle indicator for clock-time tasks) */
.task-time-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.1rem 0.5rem;
  font-size: 0.65rem;
  font-weight: 600;
  border-radius: 4px;
  background: var(--stone-50);
  color: var(--ink-muted);
  border: 1px solid var(--stone-200);
  letter-spacing: 0.02em;
  white-space: nowrap;
  flex-shrink: 0;
}

/* Fixed-time dot is a TYPE MARKER, not a time-warning cue — desaturated
   slate so it distinguishes fixed-time tasks from float tasks without
   borrowing the "live/time passing" signal that amber carries in Spotlight. */
.task-time-dot {
  font-size: 0.5rem;
  line-height: 1;
  color: var(--ink-faint);
  opacity: 0.9;
}

.task-time-badge-soon {
  background: var(--stone-100);
  color: var(--ink-muted);
  border-color: var(--stone-200);
}

/* The dot has to survive Soon's 1.6px blur, where a 0.5rem glyph dissolves into
   the badge. Nudged up and fully opaque so the fixed/float distinction §12 calls
   non-negotiable still reads at a glance — the same reasoning as letting "deep"
   stay denser than the other sizes through the same blur (§5). */
.task-time-badge-soon .task-time-dot {
  font-size: 0.58rem;
  opacity: 1;
}

/*
 * Calendar-sourced task indicator. Tucked next to the time badge so users can tell
 * at a glance which cards are mirroring an external event (read-only schedule + title).
 */
.task-calendar-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 999px;
  background: var(--stone-100);
  color: var(--ink-muted);
  border: 1px solid var(--stone-200);
}
.task-calendar-badge .mud-icon-root {
  width: 0.85rem;
  height: 0.85rem;
}
.timeline .section-now .task.size-deep .task-calendar-badge {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.28);
  color: rgba(255, 255, 255, 0.92);
}

/*
 * Day Transition spec — quiet "still here" and "needs a new plan" chips. Both
 * speak from the task's perspective (patience, not accusation), carry no count
 * and no time reference, and share one visual register so they read as a
 * family of system observations rather than two competing indicators. The
 * still-here badge is gated by a 3+ visibility cap at the page level; the
 * needs-a-new-plan chip auto-decays after one local day. The two are mutually
 * exclusive on a single card by construction (a reframed task is anchored to
 * today and therefore not still-here).
 */
.task-still-here-badge,
.task-needs-new-plan-badge,
.task-hard-to-start-badge {
  display: inline-flex;
  align-items: center;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  font-weight: 500;
  padding: 0.125rem 0.5rem;
  border-radius: 999px;
  background: var(--stone-100);
  color: var(--ink-muted);
  border: 1px solid var(--stone-200);
  text-transform: lowercase;
  white-space: nowrap;
}
.timeline .section-now .task.size-deep .task-still-here-badge,
.timeline .section-now .task.size-deep .task-needs-new-plan-badge,
.timeline .section-now .task.size-deep .task-hard-to-start-badge {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.28);
  color: rgba(255, 255, 255, 0.92);
}

/* =========================================================================
   Complete button (spec §9 — one-tap, undo via snackbar).
   ========================================================================= */

.task-complete-btn {
  appearance: none;
  background: transparent;
  border: none;
  padding: 0;
  margin: 0 0.25rem 0 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  color: inherit;
  transition: transform 0.15s ease;
}

.task-complete-btn:hover {
  transform: scale(1.1);
}

.task-complete-btn:focus {
  outline: none;
}

.task-complete-btn:focus-visible {
  outline: 2px solid var(--stone-400);
  outline-offset: 2px;
  border-radius: 50%;
}

.task-complete-circle {
  display: block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid currentColor;
  background: transparent;
  opacity: 0.7;
  transition: background 0.15s ease, border-color 0.15s ease, opacity 0.15s ease, transform 0.15s ease;
}

/* Completion hover reward — the circle fills with the primary stone accent
   and scales up a touch, so tapping feels like a small win rather than a
   chore. */
.task-complete-btn:hover .task-complete-circle {
  background: var(--stone-500);
  border-color: var(--ink-muted);
  opacity: 1;
  transform: scale(1.06);
}

.task-complete-btn:active .task-complete-circle {
  background: var(--stone-600);
  border-color: var(--ink-muted);
  transform: scale(0.92);
}

/* Subtle variant for blurred Soon/Later zones (spec §16.5) */
.task-complete-btn--subtle {
  opacity: 0.55;
}

.task-complete-btn--subtle:hover {
  opacity: 0.85;
}

.task-complete-btn--subtle .task-complete-circle {
  opacity: 0.55;
}

.task-complete-btn--subtle:hover .task-complete-circle {
  opacity: 0.9;
}

/* Swipe-to-complete visual feedback (spec §16.5) */
.timeline .task.task-swiping {
  transition: transform 0.08s ease-out;
  will-change: transform;
}

.timeline .task.task-swipe-complete {
  animation: task-complete-dissolve 0.35s ease forwards;
}

/* §14 / §16.5 — the same dissolve for every completion surface, not just the swipe. Tap paths
   used to remove the card optimistically the instant it was tapped, so the "brief, satisfying
   animation" the reward loop is built on never played anywhere except a swipe. Driven from .NET
   so the card is held in the DOM for the duration of the animation before it is removed. */
.timeline .task.task-completing,
.spotlight.spotlight-completing {
  animation: task-complete-dissolve 0.35s ease forwards;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .timeline .task.task-swipe-complete,
  .timeline .task.task-completing,
  .spotlight.spotlight-completing {
    animation: none;
    opacity: 0;
  }
}

@keyframes task-complete-dissolve {
  0% {
    opacity: 1;
    transform: translateX(0) scale(1);
  }
  100% {
    opacity: 0;
    transform: translateX(24px) scale(0.92);
  }
}

/* =========================================================================
   Day capacity meter (lives inline in .timeline-energy).
   ========================================================================= */

.day-capacity {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-width: 0;
}

/* Inline meter beside energy chips — right-aligned label under bar */
.timeline-energy .day-capacity.timeline-energy-meter {
  flex: 0 1 260px;
  max-width: min(280px, 42vw);
  min-width: 140px;
}

.day-capacity-bar {
  display: flex;
  height: 8px;
  width: 100%;
  border-radius: 999px;
  overflow: hidden;
  background: var(--stone-100);
  box-shadow: inset 0 0 0 1px rgba(60, 40, 30, 0.04);
}

.day-capacity-empty {
  flex: 1;
  background: transparent;
}

.day-capacity-seg {
  height: 100%;
  transition: flex-basis 0.3s ease;
}

/* Energy-meter ramp (spec §11), adapted to the warm-stone palette: deep
   stone / mid stone / golden amber / light stone — muted. Deep + Solid
   share the warm-beige hue but differ in lightness (same-family gradient
   for "real work"); Short shifts to golden amber (distinct hue from
   stone's brown so they read as neighbors, not duplicates); Quick stays
   light warm. Order goes heaviest → lightest so Deep reads as "thoughtful
   work" and Quick as "low-effort". */
.day-capacity-deep  { background: var(--stone-700);  }
.day-capacity-solid { background: var(--stone-500);  }
.day-capacity-short { background: var(--amber-400); }
.day-capacity-quick { background: var(--stone-400); }
.day-capacity-free  { background: transparent; }

.day-capacity-label {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.4rem;
  font-size: 0.75rem;
  color: #52525b;
  letter-spacing: 0.02em;
}

.day-capacity-load {
  font-weight: 600;
  color: #3f3f46;
}

/* =========================================================================
   Done today (spec §14 + user-spec): floating bottom-anchored launcher
   that exposes a richer "Your day so far" insights surface. The launcher
   is a fixed-position pill; the insights panel is rendered either inline
   as a hover popover (desktop) or inside a MudDialog (mobile, see
   DoneTodayInsightsDialog). The two paths share the DoneTodayInsights
   component so the visual language stays consistent.
   ========================================================================= */

.done-today-launcher {
  position: fixed;
  z-index: 1100;
  bottom: 1.25rem;
  right: 1.5rem;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  /* Leave breathing room around the popover so the drop shadow doesn't get
     clipped against the launcher's bounding box. */
  pointer-events: none;
}

.done-today-launcher > * {
  pointer-events: auto;
}

.done-today-button {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  /* Slightly squat pill matches the mock's bottom-right CTA — generous
     horizontal padding + tighter vertical so the count number does the
     visual work. */
  padding: 0.5rem 1.1rem;
  border: 1px solid var(--stone-200);
  background: var(--surface-base);
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--ink-primary);
  cursor: pointer;
  font-family: inherit;
  box-shadow: 0 4px 14px rgba(74, 62, 46, 0.10), 0 1px 2px rgba(40, 25, 18, 0.04);
  transition: background 0.15s ease, border-color 0.15s ease,
              transform 0.15s ease, box-shadow 0.2s ease;
}

.done-today-button:hover,
.done-today-launcher:not(.is-dismissed):focus-within .done-today-button {
  background: var(--stone-50);
  border-color: var(--stone-300);
  transform: translateY(-1px);
  box-shadow: 0 10px 24px rgba(74, 62, 46, 0.14), 0 1px 2px rgba(40, 25, 18, 0.04);
}

.done-today-button:focus-visible {
  outline: 2px solid var(--stone-400);
  outline-offset: 3px;
}

.done-today-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 50%;
  background: var(--stone-200);
  color: var(--stone-700);
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1;
}

.done-today-count {
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--ink-primary);
  min-width: 1ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.done-today-label {
  color: var(--ink-muted);
  letter-spacing: 0.01em;
  font-size: 0.82rem;
}

/* -----------------------------------------------------------------------
   Inline hover popover (mouse pointers only). The launcher renders the
   panel absolutely positioned above the pill; visibility is gated on
   either a pointer hovering the launcher or a keyboard caret landing
   inside it.

   The popover is `display: none` by DEFAULT and only opted back in for
   pointers that affirmatively report hover support. Gating it the other
   way round — hiding it under `(hover: none)` — left a ghost panel on
   phones whose browser doesn't report `hover: none`: tapping the pill
   opens the dialog, but the same tap also leaves a sticky `:hover` on the
   launcher (and, once the dialog closes and focus returns to the pill,
   `:focus-within`), so the popover faded in and stayed there after the
   dialog was dismissed — an orphaned card shadow hanging off the edge of
   the screen until the user tapped somewhere else. Opting in only on real
   hovering pointers means touch always falls through to the dialog path,
   whatever the browser claims about hover.

   Desktop had the same ghost from the other half of that: the click that
   opens the dialog focuses the pill in Chromium (the Windows app's
   WebView2, Chrome, Edge), MudDialog hands focus back to it on close, and
   :focus-within revealed the panel until the user clicked elsewhere.
   DoneTodayPanel marks the launcher `.is-dismissed` from that click until
   focus next leaves it, and focus is not a reason to reveal while it is.
   Hover still is.
   ----------------------------------------------------------------------- */

.done-today-popover {
  display: none;
  position: absolute;
  right: 0;
  /* The popover wrapper extends right down to the button so the cursor
     never crosses an empty gap when traveling from the button into the
     panel — losing :hover on the launcher would close the popover before
     the cursor reaches it. The visual offset between the panel and the
     pill comes from `padding-bottom` (still inside the launcher's hit
     box), not from a positioning gap. */
  bottom: 100%;
  padding-bottom: 0.75rem;
  /* Slightly wider than before so the heatmap + stats grid breathe and the
     momentum-curve labels land cleanly on their dots. */
  width: min(440px, calc(100vw - 3rem));
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition: opacity 160ms ease, transform 160ms ease;
}

/* Wherever the launcher is anchored bottom-LEFT — phone widths, or any
   shell that renders the bottom tab bar — the panel has to grow rightward
   from the pill's left edge. Leaving `right: 0` in place there pinned a
   440px-wide card to the right edge of a ~150px pill, so it hung off the
   left of the viewport. */
.hf-has-bottom-tabs .done-today-popover {
  right: auto;
  left: 0;
}

/* Three independent guards, because the phone that produced the ghost
   panel evidently answered `hover: hover`: a hovering pointer, a *fine*
   pointer (touch reports `coarse` even where hover is mis-reported), and a
   viewport above the phone breakpoint, which is where the layout hands the
   insights over to the dialog anyway. */
@media (hover: hover) and (pointer: fine) and (min-width: 641px) {
  .done-today-popover {
    display: block;
  }

  .done-today-launcher:hover .done-today-popover,
  .done-today-launcher:not(.is-dismissed):focus-within .done-today-popover {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .done-today-popover {
    transition: opacity 0s;
  }
}

/* -----------------------------------------------------------------------
   Insights surface — shared visual chrome for both the inline popover
   and the dialog body. `.is-popover` adds the floating elevation and
   border-radius; `.is-dialog` keeps the chrome flat so MudDialog's own
   surface carries the elevation.
   ----------------------------------------------------------------------- */

/* Insights surface — the warm-stone card itself. Used identically for the
   inline desktop popover and the mobile dialog: same cream background,
   same stone border, same padding, same elevation. The popover wrapper
   and the MudDialog around it are both made transparent so the only
   surface anyone sees is this card. */
.done-today-insights {
  display: flex;
  flex-direction: column;
  /* Inter-section gap separates header / heatmap / stats / curve / summary
     into five clear beats, matching the rhythm in the design mock. */
  gap: 1.1rem;
  font-family: inherit;
  color: var(--ink-primary);
  background: var(--surface-base);
  border: 1px solid var(--stone-200);
  border-radius: 14px;
  padding: 1.15rem 1.35rem 1.25rem;
  box-shadow: 0 24px 48px rgba(74, 62, 46, 0.18),
              0 2px 8px rgba(40, 25, 18, 0.06);
}

.done-today-insights-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.1rem;
}

/* Title carries the visual weight of the panel: warm dark stone with a
   slightly tighter tracking, sized so it reads as a panel heading, not a
   subheading. */
.done-today-insights-title {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--stone-700);
  line-height: 1.2;
}

.done-today-insights-empty {
  padding: 0.5rem 0 0.25rem;
  font-size: 0.88rem;
  font-style: italic;
  color: var(--ink-muted);
  line-height: 1.55;
}

/* Summary line — italic in dark stone (not faded), kept compact so it
   reads as a closing caption rather than a paragraph. The dashed divider
   separates it from the curve without adding chrome. */
.done-today-insights-summary {
  margin: 0;
  padding-top: 0.7rem;
  border-top: 1px dashed var(--stone-200);
  font-size: 0.72rem;
  font-style: italic;
  color: var(--stone-700);
  line-height: 1.45;
  letter-spacing: 0.005em;
}

/* -----------------------------------------------------------------------
   Energy heatmap — 8 hourly cells with a low-info "idle → deep" legend.
   Densities map to the warm-stone ramp so the heatmap reads as part of
   the timeline palette rather than a foreign chart.
   ----------------------------------------------------------------------- */

.done-today-heatmap {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.done-today-heatmap-eyebrow,
.done-today-momentum-eyebrow {
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* Two-row 8-column grid: row 1 = tick labels (8am · 10am · …), row 2 =
   cells. Both rows share the column track so the labels naturally sit
   centred over the cell they describe. */
.done-today-heatmap-track {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  grid-auto-rows: auto;
  column-gap: 0.4rem;
  row-gap: 0.25rem;
  align-items: end;
}

/* Heatmap cells: square swatches with the warm-stone density ramp. The
   ramp progresses through the 100→200→400→600→700 stone steps so the
   visual jump between idle / light / mid / busy / deep stays clearly
   readable on the cream surface, matching the mock. */
.done-today-heatmap-cell {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: 4px;
  background: var(--stone-100);
  transition: transform 120ms ease, box-shadow 120ms ease;
}

.done-today-heatmap-cell[data-density="0"] { background: var(--stone-100); }
.done-today-heatmap-cell[data-density="1"] { background: var(--stone-200); }
.done-today-heatmap-cell[data-density="2"] { background: var(--stone-400); }
.done-today-heatmap-cell[data-density="3"] { background: var(--stone-600); }
.done-today-heatmap-cell[data-density="4"] { background: var(--stone-700); }

.done-today-heatmap-cell:hover {
  transform: translateY(-1px) scale(1.05);
  box-shadow: 0 4px 10px rgba(74, 62, 46, 0.22);
}

/* Tick row — a slim line of "8am 10am 12pm 2pm now" labels above the
   cells. Empty slots still take their column so the labelled ticks line
   up over the right cells. */
.done-today-heatmap-tick {
  font-size: 0.62rem;
  font-weight: 500;
  color: var(--ink-faint);
  letter-spacing: 0.01em;
  white-space: nowrap;
  text-align: center;
  line-height: 1;
}

/* Legend lives below the cell row: a row of mini-swatches that visually
   re-states the ramp so the cell colors are decodable on first glance. */
.done-today-heatmap-legend {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.6rem;
  color: var(--ink-faint);
  letter-spacing: 0.04em;
  margin-top: 0.15rem;
}

.done-today-heatmap-legend-label {
  text-transform: lowercase;
  font-style: italic;
}

.done-today-heatmap-legend-swatches {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.done-today-heatmap-legend-swatch {
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 2px;
}

.done-today-heatmap-legend-swatch[data-density="0"] { background: var(--stone-100); }
.done-today-heatmap-legend-swatch[data-density="1"] { background: var(--stone-200); }
.done-today-heatmap-legend-swatch[data-density="2"] { background: var(--stone-400); }
.done-today-heatmap-legend-swatch[data-density="3"] { background: var(--stone-600); }
.done-today-heatmap-legend-swatch[data-density="4"] { background: var(--stone-700); }

/* -----------------------------------------------------------------------
   Stats grid — four soft tiles. Numbers feel "trophy-case" big without
   competing with the timeline's own typography (kept under 1.2rem).
   ----------------------------------------------------------------------- */

/* Stats grid: four "trophy-case" tiles with big numbers and small lower-
   case labels. The tiles have only a hairline stone-100 frame on a
   barely-visible stone-50 fill so the numbers do all the visual work,
   matching the mock's airy feel. */
.done-today-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.55rem;
}

.done-today-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.2rem;
  padding: 0.85rem 0.5rem 0.7rem;
  background: var(--stone-50);
  border: 1px solid var(--stone-100);
  border-radius: 10px;
  text-align: center;
}

.done-today-stat-value {
  font-size: 1.45rem;
  font-weight: 600;
  color: var(--stone-700);
  letter-spacing: -0.015em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.done-today-stat-label {
  font-size: 0.65rem;
  color: var(--ink-faint);
  letter-spacing: 0.02em;
  white-space: nowrap;
  font-weight: 500;
  text-transform: lowercase;
}

/* -----------------------------------------------------------------------
   Momentum curve — SVG line with discrete completion dots and up to
   three soft labels. Stroke uses the warm-stone ramp so it stays inside
   the palette; labels are intentionally small so they read as captions
   on the curve, not chart data points.
   ----------------------------------------------------------------------- */

/* Momentum curve: a hand-rolled SVG (warm amber gradient under a smooth
   stone-stroke line) plus an absolutely-positioned HTML tooltip overlay
   that rides whichever data dot the cursor is on. Keeping the chart in
   raw SVG instead of a chart library lets us match the design's
   coffee-on-cream palette exactly and gives full control over how the
   labels render — they're real DOM nodes, so they inherit the page font
   and never clip the SVG. */
.done-today-momentum {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.done-today-momentum-chart {
  /* Positioning anchor for .done-today-momentum-tooltip (which is placed
     by percent coords against this container). Height drives the trace's
     vertical room — generous enough that even a single Deep + a string
     of Quicks paints a recognisable shape. */
  position: relative;
  width: 100%;
  height: 180px;
}

.done-today-momentum-svg {
  display: block;
  width: 100%;
  height: 100%;
  /* Allow the dot rings to spill slightly outside the viewBox without
     getting clipped — the hit-circles are 12px in viewBox space which
     can stretch larger than the painted dots once the SVG is scaled
     into a wide popover. */
  overflow: visible;
}

.done-today-momentum-point {
  cursor: pointer;
  outline: none;
}

.done-today-momentum-point:focus-visible .done-today-momentum-dot {
  /* Soft cream ring on keyboard focus — visible against the warm
     surface without yelling like the default browser focus outline. */
  stroke: var(--amber-400);
  stroke-width: 2;
}

.done-today-momentum-dot {
  /* Smooth size transition between resting and hovered states; the new
     radius is set on the SVG attribute, this just animates the change. */
  transition: r 120ms ease;
}

/* Tooltip overlay: HTML so the title reads in the page font at full
   contrast, not as a tiny SVG <text> element. The element itself sits
   above the dot via translate; the inline style supplies the dot's
   percentage coordinates inside .done-today-momentum-chart. */
.done-today-momentum-tooltip {
  position: absolute;
  /* Centered horizontally on the dot, parked just above it so the
     tooltip body never overlaps the data marker the user is reading. */
  transform: translate(-50%, calc(-100% - 14px));
  background: var(--surface-base);
  color: var(--ink-primary);
  border: 1px solid var(--stone-300);
  border-radius: 8px;
  padding: 0.5rem 0.7rem;
  box-shadow: 0 6px 18px rgba(40, 25, 18, 0.18);
  font-size: 0.78rem;
  line-height: 1.35;
  white-space: nowrap;
  pointer-events: none;
  z-index: 2;
  /* Soft fade so the tooltip slides in instead of popping when the
     cursor lands on a dot. */
  animation: done-today-momentum-tooltip-in 120ms ease-out;
}

.done-today-momentum-tooltip-title {
  font-weight: 600;
  color: var(--ink-primary);
  letter-spacing: 0.005em;
}

.done-today-momentum-tooltip-meta {
  margin-top: 0.15rem;
  font-size: 0.7rem;
  color: var(--stone-600);
  letter-spacing: 0.01em;
}

@keyframes done-today-momentum-tooltip-in {
  from {
    opacity: 0;
    transform: translate(-50%, calc(-100% - 10px));
  }
  to {
    opacity: 1;
    transform: translate(-50%, calc(-100% - 14px));
  }
}

@media (prefers-reduced-motion: reduce) {
  .done-today-momentum-dot {
    transition: none;
  }
  .done-today-momentum-tooltip {
    animation: none;
  }
}

/* -----------------------------------------------------------------------
   Mobile dialog wrapper — strip MudDialog's own chrome (white surface,
   border, shadow, padding) so the only thing the user sees is the
   .done-today-insights card itself. With this approach the dialog and
   the desktop hover popover render the exact same card; the only
   difference is how the user invoked it. The dialog's MudDialog header
   is suppressed via `NoHeader = true` in DialogOptions so the title isn't
   duplicated above the card's own "Your day so far" header.
   ----------------------------------------------------------------------- */

.done-today-insights-dialog.mud-dialog,
.done-today-insights-dialog .mud-dialog {
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
}

.done-today-insights-dialog .mud-dialog-content {
  padding: 0;
  background: transparent;
  overflow: visible;
}

/* Action row sits flush under the card with a small clearance — the card's
   own bottom padding gives the space, this just keeps the Close button
   from clipping into the card's drop shadow. */
.done-today-insights-dialog .mud-dialog-actions {
  padding: 0.6rem 0 0;
  background: transparent;
  justify-content: flex-end;
}

.done-today-insights-close-btn {
  text-transform: none !important;
  letter-spacing: 0 !important;
  font-weight: 600 !important;
  color: var(--stone-700) !important;
  background: var(--surface-base) !important;
  border: 1px solid var(--stone-200) !important;
  border-radius: 999px !important;
  padding: 0.4rem 1.1rem !important;
}

.done-today-insights-close-btn:hover {
  background: var(--stone-50) !important;
  border-color: var(--stone-300) !important;
}

/* Standalone pill variants (Done Today panel, spotlight size-pill fallback).
   Same warm-stone family at different lightnesses, with amber as the only
   non-stone hue for the Short segment. */
.size-pill.size-quick:not(.timeline .section-now .task .size-pill) {
  background: var(--stone-100);
  color: var(--ink-primary);
  border-color: var(--stone-200);
}
.size-pill.size-short:not(.timeline .section-now .task .size-pill) {
  background: var(--stone-50);
  color: var(--ink-muted);
  border-color: var(--stone-200);
}
.size-pill.size-solid:not(.timeline .section-now .task .size-pill) {
  background: var(--stone-100);
  color: var(--ink-primary);
  border-color: var(--stone-300);
}
.size-pill.size-deep:not(.timeline .section-now .task .size-pill) {
  background: var(--stone-200);
  color: var(--ink-primary);
  border-color: var(--stone-400);
}

@media (max-width: 640px) {
  /* On mobile the launcher sits at the bottom-LEFT so it doesn't compete
     with the Add-Task FAB in the bottom-right. The dialog (not the inline
     popover) is the rendered insights surface at this width — the popover's
     own `min-width: 641px` guard keeps it out of the way — so we only need
     to adjust the pill itself. */
  .done-today-launcher {
    bottom: 1rem;
    right: auto;
    left: 1rem;
    align-items: flex-start;
  }

  .done-today-button {
    padding: 0.55rem 0.85rem;
    font-size: 0.78rem;
  }

  /* Dialog layout: tighten the heatmap labels + stat tiles so the panel
     reads well at ~360px viewport widths typical on phones. Stats wrap
     into a 2-column grid because four squat tiles in one row feels
     cramped at this width. */
  .done-today-insights-dialog .done-today-stats {
    grid-template-columns: repeat(2, 1fr);
  }

  .done-today-insights-dialog .done-today-heatmap-track {
    gap: 0.25rem;
  }

  .done-today-insights-dialog .done-today-heatmap-tick {
    font-size: 0.55rem;
  }
}

/* ------------------------------------------------------------------ */
/* Task row inline menu (kebab) for "Not today" and friends            */
/* ------------------------------------------------------------------ */
.task .task-menu {
  margin-left: auto;
  opacity: 0;
  transition: opacity 120ms ease;
}

.task:hover .task-menu,
.task:focus-within .task-menu {
  opacity: 1;
}

@media (hover: none) {
  .task .task-menu {
    opacity: 0.65;
  }
}

/* ------------------------------------------------------------------ */
/* AddTaskDialog: size picker + Today/Someday routing                 */
/* ------------------------------------------------------------------ */
/* Add-task dialog (Timeline + sidebar Quick Add) — shares the warm-stone
   look of the Plan add dialog so the whole app reads as one surface. */
/* Midday energy check-in (§6 / §21). Quiet register on purpose: it is a question, not an
   alert, so it borrows the stone palette rather than amber — §8 reserves amber for "live,
   time passing" and this card is neither. Sits inline under the energy row. */
.energy-checkin {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  position: relative;
  margin: 0 2rem 1rem 4rem;
  padding: 0.7rem 2.25rem 0.7rem 0.95rem;
  border: 1px solid var(--stone-200);
  border-radius: 8px;
  background: var(--stone-50);
}

.energy-checkin-body {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
  flex: 1 1 12rem;
}

.energy-checkin-eyebrow {
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.energy-checkin-prompt {
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--ink-primary);
}

.energy-checkin-actions {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}

/* The dismiss control is deliberately the quietest thing in the card — §21 makes the
   check-in optional, but a prominent ✕ would read as the expected answer. */
.energy-checkin-dismiss {
  position: absolute;
  top: 0.35rem;
  right: 0.4rem;
  border: 0;
  background: none;
  padding: 0.2rem 0.35rem;
  font-size: 0.75rem;
  line-height: 1;
  color: var(--ink-faint);
  cursor: pointer;
  border-radius: 4px;
}

.energy-checkin-dismiss:hover:not(:disabled) { color: var(--ink-muted); }
.energy-checkin-dismiss:focus-visible {
  outline: 2px solid var(--timeline-line-now);
  outline-offset: 1px;
}

@media (max-width: 640px) {
  .energy-checkin { margin-left: 0.75rem; margin-right: 0.75rem; }
}

/* Someday one-in-one-out exchange (§18 adding pressure, 20+ tier). Lives inside the add
   dialog, which swaps to it after the capture is saved. Deliberately quiet: the list being
   full is an observation, not a warning, so no amber and no alert colouring. */
.someday-trade {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.someday-trade-heading {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink-primary);
}

.someday-trade-prompt {
  margin: 0;
  font-size: 0.85rem;
  color: var(--ink-muted);
}

/* The card being offered for trade. Echoes the Someday stack's own card so the thing the
   user is deciding about looks like what it is. */
.someday-trade-card {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: 0.75rem 0.85rem;
  margin-top: 0.25rem;
  border: 1px solid var(--stone-200);
  border-radius: 8px;
  background: var(--stone-50);
}

.someday-trade-card-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.someday-trade-card-age {
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  font-style: italic;
}

.someday-trade-card-title {
  margin: 0;
  font-size: 1rem;
  font-weight: 500;
  color: var(--ink-primary);
  line-height: 1.3;
}

.someday-trade-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.35rem;
}

/* Per-task heads-up picker (§3 / §12 / §17). Appears inside the task dialogs once a clock
   time exists. Chips rather than a select or a slider because §17 asks for "one tap" — a
   dropdown is three (open, choose, close) and a slider is a drag. */
.heads-up-picker {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin: 0.75rem 0 0.25rem;
}

.heads-up-picker-head {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.heads-up-picker-label {
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* The concrete arrival time, which is the thing the user is actually choosing between. */
.heads-up-picker-hint {
  font-size: 0.78rem;
  color: var(--ink-muted);
  line-height: 1.35;
}

.heads-up-picker-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.heads-up-chip {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: 0.3rem;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--stone-200);
  border-radius: 999px;
  background: var(--surface-base);
  color: var(--ink-muted);
  font-size: 0.75rem;
  line-height: 1.2;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.heads-up-chip:hover:not(:disabled):not(.is-selected) {
  border-color: var(--stone-300);
  background: var(--stone-50);
}

.heads-up-chip.is-selected {
  background: var(--stone-200);
  border-color: var(--stone-400);
  color: var(--ink-primary);
  font-weight: 600;
}

/* Hover on the chip that is already chosen deepens its own fill rather than
   falling back to the unselected hover wash: that wash outranks .is-selected on
   specificity and would repaint the background light while leaving the selected
   text colour in place. Touch devices hold :hover on the element last tapped, so
   this state was every selection on mobile, not just a mouse-over. */
.heads-up-chip.is-selected:hover:not(:disabled) {
  background: var(--stone-300);
  border-color: var(--stone-400);
}

.heads-up-chip:disabled { opacity: 0.55; cursor: default; }

.heads-up-chip:focus-visible {
  outline: 2px solid var(--timeline-line-now);
  outline-offset: 2px;
}

/* Marks which chip is the user's profile-wide setting, so "change it for this one task" is
   legible as a departure from it rather than a free-floating choice. A dot rather than the
   words: the caption it replaces was the only two-line chip in the row, which set the height
   of all six and read as clutter. The wording lives on in the title and the aria-label. */
.heads-up-chip-dot {
  width: 0.3rem;
  height: 0.3rem;
  border-radius: 50%;
  background: var(--stone-400);
  flex: none;
}

.heads-up-chip.is-selected .heads-up-chip-dot { background: var(--ink-muted); }

/* Split-task dialog (§9 / §10.6). One row per step: the field takes the width and the
   remove control sits beside it rather than under, so a three-step list stays three lines.
   The remove button is only rendered above the two-step minimum, so no disabled state
   needs designing for it. */
.split-step-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.split-step-field {
  flex: 1 1 auto;
  min-width: 0;
}

.split-add-step {
  align-self: flex-start;
}

/* ==================================================================
   Task dialogs — Add task / Edit task (§4 size, §17 routing, §27 flag)
   ==================================================================
   Both dialogs carry `.hf-task-dialog` on the MudDialog itself, so the
   shell rules below can reach MudBlazor's own title / content / actions
   without leaking into any other dialog in the app.

   One rule carries the whole surface: LIGHTER THAN THE PAGE MEANS YOU
   CAN ACT ON IT — the fields, the flag toggle, the heads-up chips, the
   card being offered in the exchange step. Nothing else goes lighter.
   In particular a CHOSEN option does not, because "you can act here"
   and "this is the one you picked" are different questions and the old
   pass answered both with the same brightness.

   Two things fall out of that, and both were wrong before:

   • The lit surface is #FDFBF6, not #FFFFFF. Measuring warmth as R−B,
     every surface here runs warm: the page 13, stone-100 23, stone-200
     37. Pure white runs 0 — it is the only neutral thing in the dialog,
     which is why it read cool and slightly unthemed against the cream.
     #FDFBF6 keeps 7 of those 13 points and still lifts 8/9/14 above the
     page. It is declared locally rather than as a brand token because
     only these dialogs use it; promote it to tokens.css if that changes.
     Note it is NOT --surface-elev, which is still #FFFFFF and is still
     load-bearing for plan.css and the timeline's own cards.

   • The segmented pickers no longer mark the chosen option by making it
     the brightest cell. They used to do the opposite of even that —
     every option rendered as a white card and only the SELECTED one got
     tinted, so the chosen option looked like the recessed one.
   ------------------------------------------------------------------ */

.hf-task-dialog {
  /* The one "you can act here" surface. See the note above before reaching
     for --surface-elev or a bare #FFF anywhere inside this dialog. */
  --hf-dialog-lit: #FDFBF6;
}

.hf-task-dialog.mud-dialog,
.hf-task-dialog .mud-dialog {
  border-radius: 18px;
  border: 1px solid var(--stone-200);
  background: var(--surface-base);
  box-shadow:
    0 18px 44px -12px rgba(40, 25, 18, 0.28),
    0 2px 6px rgba(40, 25, 18, 0.08);
  overflow: hidden;
}

/* Title and actions sit on a slightly lighter band so the scrolling middle
   reads as the part that moves. --stone-50 against --surface-base is a
   deliberately tiny step: the hairline does the separating, the tint only
   stops the bands from floating. */
.hf-task-dialog .mud-dialog-title {
  padding: 1.15rem 1.5rem 0.9rem;
  border-bottom: 1px solid var(--stone-200);
  background: var(--stone-50);
}

.hf-task-dialog-title.mud-typography {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ink-primary);
}

.hf-task-dialog .mud-dialog-content {
  padding: 1.25rem 1.5rem 1.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.95rem;
}

/* The content column scrolls when the dialog outgrows the viewport, and a
   flex child would otherwise compress instead of scrolling. */
.hf-task-dialog .mud-dialog-content > * { flex-shrink: 0; }

/* The dialogs still hand MudBlazor its own mb-3 / mb-1 spacing utilities;
   the flex gap above now owns the rhythm, so those would double it. */
.hf-task-dialog .mud-dialog-content > .mb-3,
.hf-task-dialog .mud-dialog-content > .mb-1 { margin-bottom: 0 !important; }

.hf-task-dialog .mud-dialog-actions {
  padding: 0.85rem 1.5rem;
  border-top: 1px solid var(--stone-200);
  background: var(--stone-50);
  gap: 0.5rem;
}

/* ---- section labels ---------------------------------------------- */
/* The size and routing pickers had no visible label at all — eight boxes
   and no statement of what question they answer. Same eyebrow treatment
   as .heads-up-picker-label, which renders in these dialogs too, so the
   whole surface reads as one set of sections. --ink-muted rather than
   --ink-faint: at this size faint measures 3.3:1 on cream. */
.add-task-field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.add-task-label {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* ---- segmented pickers (size + routing) --------------------------- */
/* An inset warm track holding one raised cell. The track groups the options
   into a single control rather than four adjacent buttons; the chosen cell
   rises back to page cream and takes a stone rim, so it reads as lifted out
   of the track rather than as the brightest thing on the surface.

   The track sits a step darker than --stone-100 so page cream has something
   to lift off. It is a one-off mix rather than --stone-200, which is dark
   enough that the unselected labels start to lose contrast against it. */
.add-task-size-row,
.add-task-route-row {
  display: grid;
  gap: 0.25rem;
  margin: 0;
  padding: 0.25rem;
  border: 1px solid var(--stone-200);
  border-radius: 14px;
  background: #E9E1D1;
}

.add-task-size-row  { grid-template-columns: repeat(4, 1fr); }
.add-task-route-row { grid-template-columns: 1fr 1fr; }

.add-task-size-btn,
.add-task-route-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.1rem;
  border: 1px solid transparent;
  border-radius: 11px;
  background: transparent;
  color: var(--ink-muted);
  font-family: inherit;
  text-align: center;
  line-height: 1.2;
  cursor: pointer;
  transition: background 140ms ease, border-color 140ms ease,
              color 140ms ease, box-shadow 140ms ease;
}

.add-task-size-btn  { padding: 0.6rem 0.3rem 0.62rem; }
.add-task-route-btn { padding: 0.62rem 0.6rem 0.68rem; }

/* Hover previews the selected state rather than flashing a lighter tint, so
   the row does not gain a second brightness level of its own. */
.add-task-size-btn:hover,
.add-task-route-btn:hover {
  background: rgba(245, 242, 232, 0.6);
  color: var(--ink-primary);
}

.add-task-size-btn.is-selected,
.add-task-route-btn.is-selected {
  background: var(--surface-base);
  border-color: var(--stone-500);
  color: var(--ink-primary);
  box-shadow:
    0 1px 2px rgba(74, 62, 46, 0.12),
    0 3px 8px rgba(74, 62, 46, 0.07);
}

/* The label joins the rim in stone rather than staying ink, so selection
   reads as one signal instead of "brown border plus black text". */
.add-task-size-btn.is-selected .add-task-size-name,
.add-task-route-btn.is-selected .add-task-route-title { color: var(--stone-700); }

/* Inset so the ring stays inside the track rather than straddling it. */
.add-task-size-btn:focus-visible,
.add-task-route-btn:focus-visible {
  outline: 2px solid var(--stone-500);
  outline-offset: -1px;
}

.add-task-size-name {
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 0.01em;
  color: inherit;
}

.add-task-size-hint {
  font-size: 0.67rem;
  color: var(--ink-muted);
}

.add-task-route-title {
  font-weight: 700;
  font-size: 0.9rem;
  color: inherit;
}

.add-task-route-sub {
  font-size: 0.73rem;
  color: var(--ink-muted);
}

/* ---- locked day (Plan's add dialog, §19.4.5) ----------------------- */
/* Takes the routing picker's place: the same track holding one cell drawn
   as already chosen. It is a fact, not a control, so it never goes lighter
   than the page (see the rule at the top of this section) and has no hover.
   The lock says why there is nothing to pick. */
.add-task-locked-day {
  margin: 0;
  padding: 0.25rem;
  border: 1px solid var(--stone-200);
  border-radius: 14px;
  background: #E9E1D1;
}

.add-task-locked-day-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  padding: 0.62rem 0.6rem 0.68rem;
  border: 1px solid var(--stone-500);
  border-radius: 11px;
  background: var(--surface-base);
  text-align: center;
  line-height: 1.2;
  box-shadow:
    0 1px 2px rgba(74, 62, 46, 0.12),
    0 3px 8px rgba(74, 62, 46, 0.07);
}

.add-task-locked-day-cell .add-task-route-title {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--stone-700);
}

.add-task-locked-day-cell .mud-icon-root {
  font-size: 0.95rem;
  width: 0.95rem;
  height: 0.95rem;
  color: var(--stone-500);
}

/* "This day is looking full" — informational, so amber marks it through the
   icon only and the words stay at readable ink. */
.add-task-day-note {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0.1rem 0 0;
  font-size: 0.8rem;
  line-height: 1.35;
  color: var(--ink-muted);
}

.add-task-day-note .mud-icon-root {
  flex: 0 0 auto;
  font-size: 1rem;
  width: 1rem;
  height: 1rem;
  color: var(--amber-500);
}

/* ---- hard-to-start toggle (§27) ----------------------------------- */
/* Off is neutral and on is amber. It used to be amber in BOTH states —
   a MudChip with Color.Warning, outlined when off and filled when on —
   which spent the product's one accent hue on a flag that is off by
   default, and left "is this set?" resting on fill weight alone. */
.add-task-flag-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.45rem 0.7rem;
  margin: 0;
}

.add-task-flag-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  flex: 0 0 auto;
  padding: 0.4rem 0.85rem 0.4rem 0.65rem;
  border: 1px solid var(--stone-300);
  border-radius: 999px;
  background: var(--hf-dialog-lit, #FDFBF6);
  color: var(--ink-muted);
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}

.add-task-flag-toggle .mud-icon-root {
  font-size: 1rem;
  width: 1rem;
  height: 1rem;
  color: var(--stone-400);
  transition: color 140ms ease;
}

.add-task-flag-toggle:hover {
  border-color: var(--stone-400);
  background: var(--stone-50);
  color: var(--ink-primary);
}

/* Amber carries the state through the wash, the rim and the bolt; the label
   stays stone. --amber-600 is the darkest amber there is and still only
   measures 4.13:1 on this wash at 13px, which is under AA — and the token's
   own comment scopes it to "amber text on cream", where it is already only
   marginal. Same move as the segmented cells above: colour signals, the
   label stays readable. */
.add-task-flag-toggle.is-on {
  background: rgba(200, 152, 72, 0.16);
  border-color: var(--amber-400);
  color: var(--stone-700);
}

.add-task-flag-toggle.is-on .mud-icon-root { color: var(--amber-500); }

.add-task-flag-toggle:focus-visible {
  outline: 2px solid var(--stone-500);
  outline-offset: 2px;
}

.add-task-flag-hint {
  font-size: 0.78rem;
  color: var(--ink-muted);
  line-height: 1.35;
  flex: 1 1 11rem;
  min-width: 0;
}

/* ---- MudBlazor fields inside the dialog --------------------------- */
.hf-task-dialog .mud-input.mud-input-outlined {
  background: var(--hf-dialog-lit, #FDFBF6);
  border-radius: 12px;
}

/* MudBlazor draws the underline on the outlined variant as well, so an
   outlined field renders a box PLUS a heavier line along its bottom edge.
   Cream on cream hid it; on the white fill it reads as a mistake. */
.hf-task-dialog .mud-input.mud-input-outlined.mud-input-underline::before,
.hf-task-dialog .mud-input.mud-input-outlined.mud-input-underline::after {
  border-bottom: none;
}

.hf-task-dialog .mud-input.mud-input-outlined > .mud-input-outlined-border {
  border-radius: 12px;
  border-color: var(--stone-200);
  transition: border-color 140ms ease, box-shadow 140ms ease;
}

.hf-task-dialog .mud-input.mud-input-outlined:not(.mud-disabled):not(:focus-within):hover > .mud-input-outlined-border {
  border-color: var(--stone-300);
}

/* A soft stone ring instead of MudBlazor's 2px primary border, which
   makes the field jump by a pixel on focus. */
.hf-task-dialog .mud-input.mud-input-outlined:focus-within > .mud-input-outlined-border,
.hf-task-dialog .mud-input.mud-input-outlined:focus-within .mud-shrink > .mud-input-outlined-border {
  border-width: 1px;
  border-color: var(--stone-500);
  box-shadow: 0 0 0 3px rgba(138, 118, 89, 0.16);
}

.hf-task-dialog .mud-input.mud-focused ~ .mud-input-label-outlined,
.hf-task-dialog .mud-input:focus-within ~ .mud-input-label-outlined {
  color: var(--stone-600);
}

.hf-task-dialog .mud-input-helper-text {
  margin-top: 0.4rem;
  font-size: 0.75rem;
  line-height: 1.4;
  color: var(--ink-muted);
}

/* ---- heads-up picker, as it renders in these dialogs -------------- */
.hf-task-dialog .heads-up-picker { gap: 0.45rem; margin: 0; }

.hf-task-dialog .heads-up-picker-label {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--ink-muted);
}

.hf-task-dialog .heads-up-picker-hint { color: var(--ink-muted); }

.hf-task-dialog .heads-up-chip {
  border-color: var(--stone-200);
  background: var(--hf-dialog-lit, #FDFBF6);
}

.hf-task-dialog .heads-up-chip:hover:not(:disabled):not(.is-selected) {
  border-color: var(--stone-400);
  background: var(--stone-50);
}

.hf-task-dialog .heads-up-chip.is-selected {
  background: var(--stone-500);
  border-color: var(--stone-500);
  color: var(--surface-base);
}

.hf-task-dialog .heads-up-chip.is-selected:hover:not(:disabled) {
  background: var(--stone-600);
  border-color: var(--stone-600);
}

.hf-task-dialog .heads-up-chip.is-selected .heads-up-chip-dot { background: var(--stone-200); }

/* ---- error alert -------------------------------------------------- */
/* MudAlert's Text variant paints a 6%-opacity rust wash, which all but
   disappears on cream. It only ever appears when a save actually failed,
   so it has to be readable — without becoming a red slab in a dialog
   whose whole job is to stay calm. */
.hf-task-dialog .mud-alert {
  border-radius: 12px;
  padding: 0.6rem 0.85rem;
  border: 1px solid rgba(160, 84, 74, 0.32);
  background-color: rgba(160, 84, 74, 0.10);
  color: var(--rust-500, #A0544A);
}

.hf-task-dialog .mud-alert .mud-alert-icon { color: var(--rust-500, #A0544A); }

.hf-task-dialog .mud-alert .mud-alert-message {
  font-size: 0.85rem;
  line-height: 1.4;
  color: var(--rust-500, #A0544A);
}

/* ---- Someday exchange step (§18) ---------------------------------- */
/* Base rules live with the rest of the Someday styles further up; these
   only align the step with the dialog it renders inside. */
.hf-task-dialog .someday-trade { gap: 0.35rem; }

.hf-task-dialog .someday-trade-heading { font-size: 1rem; line-height: 1.35; }

.hf-task-dialog .someday-trade-prompt { font-size: 0.86rem; line-height: 1.45; }

.hf-task-dialog .someday-trade-card {
  margin-top: 0.5rem;
  padding: 0.85rem 0.95rem;
  border-radius: 12px;
  border-color: var(--stone-200);
  background: var(--hf-dialog-lit, #FDFBF6);
}

.hf-task-dialog .someday-trade-actions { gap: 0.5rem; margin-top: 0.75rem; }

.hf-task-dialog .someday-trade-actions .mud-button-outlined {
  border-color: var(--stone-300);
  background: var(--hf-dialog-lit, #FDFBF6);
  color: var(--ink-muted);
}

.hf-task-dialog .someday-trade-actions .mud-button-outlined:hover {
  border-color: var(--stone-400);
  background: var(--stone-50);
  color: var(--ink-primary);
}

/* ---- buttons ------------------------------------------------------ */
/* Material's uppercase button label was the loudest type on the surface
   and the only place the product shouts. Sentence case throughout. */
.hf-task-dialog .mud-button-root {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 600;
  border-radius: 10px;
}

.hf-task-dialog .mud-button-filled-primary {
  background: var(--stone-500);
  color: var(--surface-base);
}

.hf-task-dialog .mud-button-filled-primary:hover { background: var(--stone-600); }

.hf-task-dialog .mud-dialog-actions .mud-button-root {
  font-size: 0.875rem;
  padding: 0.42rem 1.1rem;
}

.hf-task-dialog .mud-dialog-actions .mud-button-text { color: var(--ink-muted); }
.hf-task-dialog .mud-dialog-actions .mud-button-text:hover { background: var(--stone-100); }

.hf-task-dialog .mud-dialog-actions .mud-button-filled-primary { padding: 0.42rem 1.35rem; }

/* ---- narrow screens ----------------------------------------------- */
/* The four size cells stay four across down to 320px — at that width the
   widest hint ("under 10 min") measures 57px in a 65px cell. */
@media (max-width: 480px) {
  .hf-task-dialog .mud-dialog-title   { padding: 1rem 1.1rem 0.8rem; }
  .hf-task-dialog .mud-dialog-content { padding: 1.05rem 1.1rem 1.2rem; gap: 0.9rem; }
  .hf-task-dialog .mud-dialog-actions { padding: 0.75rem 1.1rem; }
  .add-task-size-btn  { padding: 0.5rem 0.15rem 0.55rem; }
  .add-task-size-name { font-size: 0.72rem; }
  .add-task-size-hint { font-size: 0.6rem; }
  .add-task-flag-hint { flex-basis: 100%; }
}

/* ------------------------------------------------------------------ */
/* "Next fixed commitment" countdown header in the Now zone           */
/* ------------------------------------------------------------------ */
/* "Next fixed commitment" — a scheduled countdown, NOT a live progress
   indicator. Desaturated slate keeps it out of the amber "live" channel. */
.next-fixed-countdown {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.35rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--ink-muted);
  letter-spacing: 0.01em;
}

.next-fixed-countdown-dot {
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 50%;
  background: var(--ink-faint);
  flex-shrink: 0;
}

/* Imminent still uses a slate ramp (just darker) — we don't escalate to
   amber because amber is reserved for the running Spotlight progress bar.
   Urgency comes from the pulse animation, not the hue. */
.next-fixed-countdown.is-imminent {
  color: var(--ink-primary);
}

.next-fixed-countdown.is-imminent .next-fixed-countdown-dot {
  background: var(--stone-500);
  animation: nextFixedPulse 1.4s ease-in-out infinite;
}

.next-fixed-countdown-time {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.next-fixed-countdown-title {
  opacity: 0.85;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 18ch;
}

@keyframes nextFixedPulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.55; transform: scale(0.85); }
}

/* =========================================================================
   Someday page (design spec §18).

   A distinct surface from the Now/Soon/Later timeline — same warm-stone
   palette, but arranged as a processing space for the "things to come back
   to" list. The core interaction is a single card at a time with two ghost
   cards peeking behind; there is NEVER a scrollable list view (that's the
   anti-pattern the spec explicitly forbids).
   ========================================================================= */
.someday-page {
  max-width: 720px;
  margin: 0 auto;
  padding: 1.25rem 1.25rem 4.5rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  outline: none;
  font-family: 'DM Sans', system-ui, sans-serif;
  color: var(--ink-primary);
}

/* Focus ring only shows for keyboard-focus so the shortcuts work without a
   persistent outline while the user is clicking around with a pointer. */
.someday-page:focus-visible {
  outline: 2px solid var(--stone-300);
  outline-offset: 4px;
  border-radius: 12px;
}

/* -----------------------------------------------------------------------
   Header
   ----------------------------------------------------------------------- */
.someday-header {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0 0.1rem;
  margin-bottom: 0.25rem;
}

.someday-title {
  margin: 0;
  font-size: 1.75rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink-primary);
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

/* Day Transition spec — quiet ambient "something just arrived" dot on the
   Someday header. Intentionally subtle, no count, no time reference. */
.someday-arrival-dot {
  display: inline-block;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 999px;
  background: var(--stone-500);
  opacity: 0.6;
  vertical-align: middle;
  flex: 0 0 auto;
}

.someday-subtitle {
  margin: 0;
  font-size: 0.9rem;
  font-style: italic;
  color: var(--ink-muted);
}

/* -----------------------------------------------------------------------
   Search bar — retrieval tool (§18 "Search is for retrieval; cards are for
   processing"). Low-profile input on cream so it reads as "type to filter"
   not "form field".
   ----------------------------------------------------------------------- */
.someday-search {
  position: relative;
  display: flex;
  align-items: center;
  background: var(--surface-base);
  border: 1px solid var(--stone-200);
  border-radius: 999px;
  padding: 0.55rem 0.9rem 0.55rem 2.4rem;
  transition: border-color 150ms ease, box-shadow 150ms ease;
}

.someday-search:focus-within {
  border-color: var(--stone-400);
  box-shadow: 0 0 0 3px rgba(138, 118, 89, 0.15);
}

.someday-search-icon {
  position: absolute;
  left: 0.95rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ink-faint);
  display: flex;
  line-height: 0;
}

.someday-search-input {
  flex: 1 1 auto;
  appearance: none;
  border: none;
  background: transparent;
  color: var(--ink-primary);
  font-family: inherit;
  font-size: 0.92rem;
  padding: 0;
  outline: none;
  min-width: 0;
}

.someday-search-input::placeholder {
  color: var(--ink-faint);
}

.someday-search-input::-webkit-search-cancel-button {
  appearance: none;
}

/* -----------------------------------------------------------------------
   Browse themes — filter pills for the card stack (not a separate view).
   §18: Everything / Quick wins / Big ideas / Been here a while.
   ----------------------------------------------------------------------- */
.someday-browse {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.6rem;
  padding: 0.25rem 0.1rem;
}

.someday-browse-label {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-right: 0.25rem;
}

.someday-theme-pill {
  appearance: none;
  border: 1px solid var(--stone-200);
  background: transparent;
  color: var(--ink-muted);
  padding: 0.35rem 0.9rem;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 500;
  border-radius: 999px;
  cursor: pointer;
  transition: background 150ms ease, border-color 150ms ease, color 150ms ease;
}

.someday-theme-pill:hover {
  background: var(--stone-50);
  border-color: var(--stone-300);
  color: var(--ink-primary);
}

.someday-theme-pill.is-selected {
  background: var(--stone-500);
  border-color: var(--stone-500);
  color: var(--surface-base);
  font-weight: 600;
  box-shadow: 0 2px 6px rgba(74, 62, 46, 0.18);
}

.someday-theme-pill:focus-visible {
  outline: 2px solid var(--stone-400);
  outline-offset: 2px;
}

/* -----------------------------------------------------------------------
   Fill indicator — non-numeric density signal (§18). Tiny jar SVG + label
   + hint. Never shows a count.
   ----------------------------------------------------------------------- */
.someday-fill-panel {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.85rem 1rem;
  background: var(--stone-100);
  border: 1px solid var(--stone-200);
  border-radius: 10px;
}

.someday-fill-jar {
  flex-shrink: 0;
  display: flex;
  line-height: 0;
}

.someday-fill-copy {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}

.someday-fill-label {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink-primary);
}

.someday-fill-hint {
  font-size: 0.8rem;
  color: var(--ink-muted);
}

/* -----------------------------------------------------------------------
   Card stack — one card visible, two ghosts peeking behind at slight
   angles. Random order on every load (handled server-side).
   ----------------------------------------------------------------------- */
.someday-stack {
  position: relative;
  min-height: 280px;
  margin: 0.5rem 0;
  display: flex;
  align-items: stretch;
  justify-content: center;
  /* Bottom padding is the deck's clearance and MUST stay equal to the `bottom`
     the ghosts anchor to, so a ghost box lines up exactly with the live card
     before its own transform runs. 3.25rem (52px) clears the deepest corner of
     the back ghost — 34px of `translateY` plus the ~10px its rotation drops
     one end — with room to spare. */
  padding: 0.75rem 0 3.25rem;
}

/* The seasonal review reuses the stack for layout but never renders ghosts —
   review is one card at a time by definition. Without this it would inherit
   the deck's bottom clearance as dead space under the review card. */
.someday-stack.seasonal-review-stack {
  padding-bottom: 1.25rem;
}

/* Ghost cards peek behind the live one to communicate depth. Absolute
   positioning + transforms let the live card hold layout space.

   Every rule below is written as `.someday-card.someday-card-ghost` (two
   classes) on purpose. The ghosts carry `someday-card` too, and that rule is
   declared *after* this block — at equal specificity it won every property
   the ghosts tried to set that it also sets (background, max-width,
   box-shadow, z-index), so the ghosts were painted identically to the live
   card and sat entirely inside its footprint: invisible at any card count.
   Raising the specificity fixes that for good and lets the `!important` on
   `position` go away. */
.someday-card.someday-card-ghost {
  position: absolute;
  /* Retiring and un-retiring a layer animates; see the `is-gone` rule below. */
  transition: opacity 240ms ease, transform 240ms ease;
  /* Anchored top and bottom to the stack's own padding rather than given a
     height, so a ghost is always exactly as tall as the live card in front of
     it — the aged card grows by a "still want it?" line and the deck grows
     with it. `translateY` below then slides the whole box down to peek. */
  top: 0.75rem;
  bottom: 3.25rem;
  left: 50%;
  min-height: 0;
  /* Ghosts are empty boxes — none of the live card's content layout, gesture
     handling or entrance animation applies to them. */
  padding: 0;
  display: block;
  gap: 0;
  touch-action: auto;
  will-change: auto;
  animation: none;
  border: 1px solid var(--stone-200);
  border-radius: 14px;
  /* The live card is cream-on-cream (`--surface-base` over `--surface-base`)
     and reads as a card only through its border and shadow. A ghost in the
     same cream would stay invisible however far it peeked, so the deck is
     tinted one step warmer and then faded back toward the page — nearer ghost
     darker, further ghost lighter, the way depth actually reads. */
  background: var(--stone-100);
  box-shadow: 0 2px 5px rgba(74, 62, 46, 0.05);
  pointer-events: none;
  z-index: 0;
}

/* Each layer is stepped in by an explicit width rather than a `scale()`, so
   its 1px border and 14px corner radius stay the same weight as the live
   card's — under `scale()` both shrink and the deck reads as a soft smear
   instead of paper. Each peek is therefore its `translateY` (17px / 34px)
   plus what the rotation drops the lower corner.

   The two cards tilt opposite ways, and each is nudged a few px off centre,
   so the deck reads as shuffled rather than mechanically fanned. The angles
   are small for a reason: the edges converge where the deeper card's raised
   end meets the shallower card's dropped end, and past roughly ±2° they meet
   and the two cards read as one muddy line. At these values the tightest
   crossing still leaves ~6px between them. */
.someday-card.someday-card-ghost-front {
  width: calc(100% - 18px);
  max-width: 482px;
  transform: translateX(-50%) translate(4px, 17px) rotate(-1.1deg);
}

.someday-card.someday-card-ghost-back {
  width: calc(100% - 40px);
  max-width: 460px;
  transform: translateX(-50%) translate(-5px, 34px) rotate(1.7deg);
  opacity: 0.7;
}

/* A ghost is a layer of the pile, so the pile has to thin out one card at a
   time as the user decides. Both ghosts are always in the DOM and this class
   retires them (see `Someday.razor`) — conditional markup would pop them out
   between renders, and the last three cards would go from a visible stack to
   nothing with no step in between. Retiring slides the layer back under the
   live card as it fades, which reads as one sheet being taken off the top. */
.someday-card.someday-card-ghost.is-gone {
  opacity: 0;
  transform: translateX(-50%) translate(0, 0) rotate(0deg);
}

.someday-card {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 500px;
  background: var(--surface-base);
  border: 1px solid var(--stone-200);
  border-radius: 14px;
  padding: 1.4rem 1.5rem 1.35rem;
  box-shadow: 0 8px 24px rgba(74, 62, 46, 0.08),
    0 1px 2px rgba(40, 25, 18, 0.04);
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  /* Spec §18 — swipe gestures on mobile (left / down / right). We hand the
     card over to `someday.js` for touch handling so the browser must not
     hijack vertical pans for native scroll. The page above/below the card
     remains scrollable because `touch-action` only applies inside the card
     box. `user-select: none` keeps a long-press during a swipe from
     popping the system text-selection menu mid-gesture. */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  /* `will-change` keeps the per-frame transform from causing repaint thrash
     on lower-end mobile hardware while a finger is actively dragging. */
  will-change: transform, opacity;
}

/* When one card flies off and the next replaces it (Blazor mints a fresh
   <article> because of `@key="card.TaskId"`) we want a small, calm "here I
   am" entrance — otherwise the swap reads as a hard pop, especially on
   mobile where the user just made a gesture and is now waiting for feedback.
   The keyframe is short so it doesn't visibly fight a swipe started right
   after the previous one; `someday.js` also clears `style.animation` on
   touchstart so an in-progress entrance never overrides the live drag
   transform. Ghosts are decorative and stay still.

   The card must stay opaque and full size throughout. It used to arrive from
   `opacity: 0` at `scale(0.97)`, which was invisible when nothing was behind
   it — but there is a deck behind it now, and a card that is both smaller
   than its ghosts and see-through lets them show around and through it. That
   read as the card flashing pale for a moment on every decision. Sliding it
   into place says the same thing and never uncovers the pile. */
.someday-card:not(.someday-card-ghost) {
  animation: someday-card-enter 280ms cubic-bezier(0.18, 0.89, 0.32, 1.28);
}

@keyframes someday-card-enter {
  from {
    transform: translateY(10px);
  }
  to {
    transform: translateY(0);
  }
}

/* The pile thinning out and the next card settling into place are both motion
   for its own sake — every state they describe still lands without them. */
@media (prefers-reduced-motion: reduce) {
  .someday-card.someday-card-ghost {
    transition: none;
  }

  .someday-card:not(.someday-card-ghost) {
    animation: none;
  }
}

.someday-card.is-aged {
  border-color: var(--amber-400);
  box-shadow: 0 8px 24px rgba(200, 152, 72, 0.18),
    0 1px 2px rgba(40, 25, 18, 0.04);
}

.someday-card-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.someday-card-eyebrow {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.someday-card-meta-right {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: nowrap;
}

.someday-card-age {
  font-size: 0.74rem;
  color: var(--ink-muted);
  font-style: italic;
}

.someday-card-age.is-aged {
  color: var(--amber-600);
  font-style: normal;
  font-weight: 600;
}

.someday-card-title {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--ink-primary);
  line-height: 1.35;
  letter-spacing: -0.005em;
}

.someday-card-renewal {
  margin: 0;
  font-size: 0.85rem;
  font-style: italic;
  color: var(--amber-600);
}

.someday-card-actions {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.55rem;
  margin-top: 0.35rem;
}

/* Three strict actions (§18). Today is primary / filled; Keep and Let go are
   outlined, with Let go slightly more muted so it reads last. No fourth
   escape — passive skipping is the exact anti-pattern the card stack prevents. */
.someday-action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 0.7rem 0.75rem;
  border-radius: 10px;
  border: 1px solid var(--stone-200);
  background: var(--surface-base);
  color: var(--ink-muted);
  cursor: pointer;
  font-family: inherit;
  font-size: 0.88rem;
  font-weight: 500;
  letter-spacing: 0.005em;
  transition: transform 120ms ease, border-color 150ms ease,
    background-color 150ms ease, color 150ms ease, box-shadow 150ms ease;
}

.someday-action-btn:hover:not(:disabled) {
  transform: translateY(-1px);
  border-color: var(--stone-300);
  color: var(--ink-primary);
}

.someday-action-btn:focus-visible {
  outline: 2px solid var(--stone-400);
  outline-offset: 2px;
}

.someday-action-btn:disabled {
  cursor: not-allowed;
  opacity: 0.55;
  transform: none;
}

.someday-action-btn.action-today {
  background: var(--stone-500);
  border-color: var(--stone-500);
  color: var(--surface-base);
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(74, 62, 46, 0.12);
}

.someday-action-btn.action-today:hover:not(:disabled) {
  background: var(--stone-600);
  border-color: var(--stone-600);
  color: var(--surface-base);
  box-shadow: 0 4px 12px rgba(74, 62, 46, 0.2);
}

.someday-action-btn.action-today .someday-action-icon {
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1;
}

.someday-action-btn.action-keep:hover:not(:disabled) {
  background: var(--stone-50);
}

.someday-action-btn.action-letgo {
  color: var(--ink-faint);
}

.someday-action-btn.action-letgo:hover:not(:disabled) {
  background: var(--stone-50);
  color: var(--ink-muted);
}

.someday-action-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

/* -----------------------------------------------------------------------
   Keyboard shortcuts footer — small legend at the bottom so discoverability
   doesn't rely on trial-and-error (§18 shortcuts map to swipe gestures on
   mobile).
   ----------------------------------------------------------------------- */
.someday-shortcuts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 0.25rem 0.75rem;
  padding: 0.5rem 0;
  font-size: 0.78rem;
  color: var(--ink-faint);
  letter-spacing: 0.02em;
}

/* The page renders both a desktop (keyboard) and a mobile (swipe) variant of
   the guide — Razor markup is the same on web and mobile builds, so we use
   the viewport breakpoint to pick which one is visible. The mobile copy is
   intentionally hidden on wider screens; trackpad/desktop users don't have
   a swipe gesture and shouldn't see swipe instructions. */
.someday-shortcuts-mobile {
  display: none;
}

.someday-shortcut {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

/* Leading caption that frames the shortcut row as both swipe + keyboard.
   On touch devices the kbd hints are still useful as iconography ("the
   left arrow means Today") even though there's no physical key. */
.someday-shortcut-mode {
  font-weight: 500;
  color: var(--ink-muted);
  margin-right: 0.25rem;
}

.someday-shortcut-sep {
  color: var(--stone-300);
}

.someday-kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.4rem;
  height: 1.4rem;
  padding: 0 0.3rem;
  background: var(--surface-base);
  border: 1px solid var(--stone-200);
  border-bottom-width: 2px;
  border-radius: 4px;
  font-family: inherit;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--ink-muted);
  line-height: 1;
}

/* -----------------------------------------------------------------------
   Loading / empty / filtered-empty states
   ----------------------------------------------------------------------- */
.someday-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3rem 1rem;
}

.someday-empty-state {
  margin: 1rem auto;
  max-width: 420px;
  padding: 2.5rem 1.5rem;
  text-align: center;
  background: var(--stone-50);
  border: 1px dashed var(--stone-300);
  border-radius: 12px;
  color: var(--ink-muted);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  align-items: center;
}

.someday-empty-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink-primary);
}

.someday-empty-hint {
  margin: 0;
  font-size: 0.85rem;
  color: var(--ink-muted);
  line-height: 1.5;
}

.someday-empty-reset {
  margin-top: 0.5rem;
  appearance: none;
  border: 1px solid var(--stone-300);
  background: var(--surface-base);
  color: var(--ink-primary);
  padding: 0.4rem 0.9rem;
  border-radius: 8px;
  font-size: 0.82rem;
  cursor: pointer;
  font-family: inherit;
}

.someday-empty-reset:hover {
  background: var(--stone-50);
  border-color: var(--stone-400);
}

/* Narrow viewports — buttons stack for easier thumb reach and text remains
   legible. */
@media (max-width: 520px) {
  .someday-page {
    padding: 1rem 0.85rem 4rem;
  }

  .someday-card {
    padding: 1.15rem 1.15rem 1.2rem;
  }

  .someday-card-title {
    font-size: 1.1rem;
  }

  .someday-card-actions {
    grid-template-columns: 1fr;
  }

  /* Swap which guide is visible: hide the keyboard one, show the swipe one.
     Touch users have no keyboard so the desktop hint would be misleading. */
  .someday-shortcuts-desktop {
    display: none;
  }

  .someday-shortcuts-mobile {
    display: flex;
  }

  /* Render the visible guide as a quiet hint, not a toolbar — the kbd-style
     arrows double as direction icons (← swipe left, ↓ swipe down,
     → swipe right) so the visual mapping still works without a keyboard. */
  .someday-shortcuts {
    font-size: 0.65rem;
    gap: 0.15rem 0.5rem;
    padding: 0.35rem 0 0.85rem;
    line-height: 1.3;
    letter-spacing: 0.015em;
  }

  .someday-shortcut-mode {
    font-size: 0.65rem;
    margin-right: 0.15rem;
  }

  .someday-kbd {
    min-width: 1.1rem;
    height: 1.1rem;
    font-size: 0.6rem;
    padding: 0 0.2rem;
    border-bottom-width: 1.5px;
  }

  .someday-shortcut-sep {
    font-size: 0.6rem;
  }
}

/* =========================================================================
   Spotlight — active task at the top of the timeline (design spec §8).
   Visually separated from Now; ambient progress bar represents elapsed time.
   ========================================================================= */

/* Spotlight — the one place color earns its keep. Stone-100 surface
   sets the "live" frame; the progress bar below is the single warm accent
   (muted amber, not fluorescent) that signals time passing. */
.spotlight {
  position: relative;
  margin: 0.5rem 2rem 1rem 4rem;
  padding: 1.25rem 1.25rem 1.35rem 1.65rem;
  min-height: 9.5rem;
  background: var(--stone-100);              /* spec: Stone 100 */
  border: 1px solid var(--stone-200);        /* spec: thin Stone 200 */
  border-radius: 14px;
  box-shadow: 0 6px 20px rgba(74, 62, 46, 0.08), 0 1px 2px rgba(40, 25, 18, 0.04);
  overflow: hidden;
  transition: box-shadow 200ms ease, transform 200ms ease, border-color 200ms ease, background 200ms ease;
}

/* Drag-over highlight. Set as an attribute by timeline-dnd.js rather than as a
   Blazor-rendered class: routing it through the service would re-render the whole
   timeline on every pointer move across this card. */
.spotlight[data-hf-drop-active] {
  border-color: var(--stone-400);
  box-shadow: 0 0 0 3px rgba(138, 118, 89, 0.28), 0 10px 24px rgba(74, 62, 46, 0.15);
  transform: translateY(-1px);
  background: var(--stone-200);
}

.spotlight-empty {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.35rem 0.1rem 0.35rem 1rem;
  color: var(--ink-muted);
}

.spotlight-empty-icon {
  font-size: 1.7rem;
  color: var(--ink-muted);
  flex-shrink: 0;
}

.spotlight-empty-copy {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.spotlight-empty-title {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink-primary);
}

.spotlight-empty-hint {
  font-size: 0.8rem;
  color: var(--ink-faint);
}

/* Pick from today's way in (§2.1). A quiet pill, not a primary button: the
   empty spotlight is an invitation, and the drag stays the main path. */
.spotlight-pick-btn {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.5rem;
  padding: 0.4rem 0.9rem;
  min-height: 2.25rem;
  border: 1px solid var(--stone-300);
  border-radius: 999px;
  background: var(--surface-elev);
  color: var(--stone-700);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}

.spotlight-pick-btn:hover {
  background: var(--stone-50);
  border-color: var(--stone-400);
}

.spotlight-pick-btn:focus-visible {
  outline: 2px solid var(--timeline-line-now);
  outline-offset: 2px;
}

.spotlight-pick-btn .mud-icon-root {
  font-size: 1rem;
  width: 1rem;
  height: 1rem;
}

.spotlight-accent {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: linear-gradient(180deg, var(--stone-500) 0%, var(--stone-400) 100%);
}

.spotlight-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.6rem;
}

.spotlight-eyebrow {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.spotlight-clear-btn {
  background: transparent;
  border: none;
  color: var(--ink-muted);
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 150ms ease, color 150ms ease;
}

.spotlight-clear-btn:hover {
  background: rgba(115, 106, 99, 0.12);
  color: var(--ink-primary);
}

.spotlight-body {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.85rem;
}

.spotlight-complete-btn {
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  flex-shrink: 0;
  width: 1.75rem;
  height: 1.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.spotlight-complete-circle {
  width: 1.3rem;
  height: 1.3rem;
  border: 2px solid var(--ink-faint);
  border-radius: 50%;
  transition: border-color 150ms ease, background 150ms ease;
}

.spotlight-complete-btn:hover .spotlight-complete-circle {
  border-color: var(--ink-muted);
  background: rgba(138, 118, 89, 0.14);
}

.spotlight-title-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-width: 0;
  flex: 1;
}

.spotlight-title {
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--ink-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
}

.spotlight-size-pill {
  margin-left: 0;
  background: rgba(138, 118, 89, 0.16);   /* stone-500 tint on stone-100 bg */
  color: var(--ink-primary);
  border-color: var(--stone-300);
}

/* Progress bar — the single warm accent on the whole page.
   The bar itself carries time-passing; we desaturate so it reads as warm
   amber, not fluorescent orange. Overflow doubles down with hatch stripes. */
.spotlight-progress {
  margin-bottom: 0.5rem;
}

.spotlight-progress-track {
  position: relative;
  height: 6px;
  border-radius: 999px;
  background: rgba(115, 106, 99, 0.18);
  overflow: hidden;
}

.spotlight-progress-fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  background: linear-gradient(90deg, var(--amber-300) 0%, var(--amber-400) 100%);
  border-radius: 999px;
  transition: width 600ms ease, background 400ms ease;
}

.spotlight-warning .spotlight-progress-fill {
  background: linear-gradient(90deg, var(--amber-300) 0%, var(--amber-500) 100%);
}

.spotlight-overflow .spotlight-progress-fill {
  background: linear-gradient(90deg, var(--amber-400) 0%, var(--amber-500) 60%, var(--amber-600) 100%);
  width: 100% !important;
}

/* Secondary "overflow" bar that fills once elapsed surpasses the target. */
.spotlight-progress-overflow {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  background: repeating-linear-gradient(
    45deg,
    rgba(184, 135, 58, 0.72) 0,       /* amber-500: running long */
    rgba(184, 135, 58, 0.72) 6px,
    rgba(200, 152, 72, 0.72) 6px,     /* amber-400: primary running */
    rgba(200, 152, 72, 0.72) 12px
  );
  border-radius: 999px;
  transition: width 600ms ease;
}

.spotlight-meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.78rem;
  color: var(--ink-muted);
}

.spotlight-meta-dot {
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: var(--amber-400);
  animation: spotlight-pulse 2.4s ease-in-out infinite;
}

.spotlight-overflow .spotlight-meta-dot {
  background: var(--amber-500);
}

.spotlight-overflow .spotlight-meta-text {
  color: var(--amber-600);
  font-weight: 600;
}

@keyframes spotlight-pulse {
  0%, 100% { opacity: 0.5; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.3); }
}

@media (prefers-reduced-motion: reduce) {
  .spotlight-meta-dot { animation: none; }
  .spotlight-progress-fill,
  .spotlight-progress-overflow { transition: none; }
}

/* =========================================================================
   Empty-day states (design spec §24).

   Three compositions share the .empty-day wrapper:
     .empty-day-fresh          — fresh morning, no timeline yet
     .empty-day-cleared        — all cleared (mid-day)
     .empty-day-free-before-next — Now empty with a later fixed commitment

   Each composition is its own layout — not the normal UI with tasks removed.
   Fresh morning and All-cleared are each one bounded column, optically
   centered in whatever height the timeline gives them.
   ========================================================================= */

.empty-day {
  display: flex;
  flex-direction: column;
  padding: 2rem 2rem 2.5rem 4rem;
  min-height: 60vh;
  gap: 0.85rem;
  position: relative;
  z-index: 1;
}

/* Fresh morning and All-cleared are centered compositions. Free-before-next
   stays left-aligned so it reads as "work mode" rather than a welcome screen. */
.empty-day-fresh,
.empty-day-cleared {
  align-items: center;
  text-align: center;
  padding-top: 2.25rem;
}

.empty-day-greeting {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin-bottom: 0.5rem;
}

.empty-day-greeting-main {
  font-size: 1.35rem;
  font-weight: 500;
  color: var(--ink-primary);
  letter-spacing: -0.005em;
  line-height: 1.3;
}

.empty-day-greeting-sub {
  font-size: 0.82rem;
  font-style: italic;
  color: var(--ink-faint);
}

/* On All-cleared the sub-line is an actual sentence (not an italic flourish)
   so lose the italics + punch up a touch for hierarchy. */
.empty-day-greeting-sub-strong {
  font-style: normal;
  color: var(--ink-muted);
  font-size: 0.88rem;
}

/* -----------------------------------------------------------------------
   Fresh morning (§26.4) — the day's opening screen, and the first screen a
   new account sees.

   Built as All-cleared's pair: one bounded column, optically centered in
   the height the timeline gives it, over the same kind of ambient wash. The
   previous version was a top-packed stack with flex spacers, which on a
   tall screen left a greeting, three chips and a button floating in a field
   of cream.

   Plain elements throughout (no MudChip / MudButton), for the reason the
   All-cleared tiles give: multi-part compositions, and real :disabled and
   [aria-pressed] states without fighting MudBlazor's rules. Text that
   carries information uses --ink-muted, never --ink-faint (AA contrast).
   ----------------------------------------------------------------------- */

.empty-day-fresh {
  flex: 1 1 auto;
  justify-content: center;
  /* No 4rem rail inset — this composition never renders the rail. */
  padding: 2rem 1.5rem 2.5rem;
  gap: 0;
}

/* Ambient light, centered on the sky rather than the headline. z-index: -1
   for the reason given on .empty-day-cleared::before. */
.empty-day-fresh::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    110% 44% at 50% 16%,
    rgba(221, 208, 184, 0.42) 0%,
    rgba(237, 229, 214, 0.18) 46%,
    rgba(245, 242, 232, 0) 74%
  );
}

/* Night: the same wash, a step dimmer — dusk rather than daylight. */
.empty-day-fresh--night::before {
  background: radial-gradient(
    110% 44% at 50% 16%,
    rgba(196, 178, 150, 0.34) 0%,
    rgba(221, 208, 184, 0.14) 46%,
    rgba(245, 242, 232, 0) 74%
  );
}

.empty-day-fresh-inner {
  width: 100%;
  max-width: 26rem;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  animation: empty-day-fresh-in 260ms ease-out both;
}

@keyframes empty-day-fresh-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Sky — drawn from the clock (see SunPosition in EmptyDayState.razor).
   Every colour is a stone token: amber is reserved for live signals (§8),
   and a decorative sun is exactly the dilution that rule exists to stop. */
.empty-day-sky {
  width: 15rem;
  max-width: 100%;
  margin: 0 auto 1.15rem;
}

.empty-day-sky svg {
  display: block;
  width: 100%;
  height: auto;
}

.empty-day-sky-path {
  stroke: var(--stone-400);
  stroke-opacity: 0.6;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-dasharray: 0.1 5.2;
}

.empty-day-sky-halo-core { stop-color: var(--stone-300); stop-opacity: 0.6; }
.empty-day-sky-halo-edge { stop-color: var(--stone-300); stop-opacity: 0; }
.empty-day-sky-horizon-mid  { stop-color: var(--stone-400); stop-opacity: 1; }
.empty-day-sky-horizon-edge { stop-color: var(--stone-300); stop-opacity: 0; }

.empty-day-sky-sun-disc { fill: var(--stone-400); }

.empty-day-sky-ripple {
  stroke: var(--stone-200);
  stroke-width: 1.6;
  stroke-linecap: round;
}

.empty-day-sky-moon { fill: var(--stone-400); }
.empty-day-sky-stars circle { fill: var(--stone-300); }
.empty-day-fresh--night .empty-day-sky-path { stroke-opacity: 0.35; }

/* One slow rise as the screen opens — the sun settling into place, not a
   loop. No bounce (§26 transition anti-patterns). */
.empty-day-sky-sun {
  animation: empty-day-sun-in 900ms 120ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

@keyframes empty-day-sun-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Date + greeting. The greeting is the page's h1 and takes the serif, the
   same face as the All-cleared headline that closes the day. */
.empty-day-fresh-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 1.6rem;
  text-align: center;
}

.empty-day-fresh-date {
  margin: 0;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.empty-day-fresh-greeting {
  margin: 0;
  font-family: 'Lora', Georgia, serif;
  font-size: 1.85rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.015em;
  color: var(--ink-primary);
  overflow-wrap: break-word;
  text-wrap: balance;
}

.empty-day-fresh-sub {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.45;
  color: var(--ink-muted);
}

/* Energy card — the §6 morning question. Same lifted card as the
   All-cleared done panel, so the day opens and closes on one surface. */
.empty-day-energy-card {
  background: linear-gradient(180deg, #FCFAF5 0%, var(--stone-50) 100%);
  border: 1px solid var(--stone-200);
  border-radius: 16px;
  padding: 1rem 1rem 0.9rem;
  box-shadow:
    0 1px 2px rgba(74, 62, 46, 0.05),
    0 14px 30px -20px rgba(74, 62, 46, 0.5);
}

.empty-day-energy-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.empty-day-energy-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink-primary);
}

.empty-day-energy-optional {
  font-size: 0.74rem;
  font-style: italic;
  color: var(--ink-muted);
}

.empty-day-energy-options {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
}

.empty-day-energy-option {
  appearance: none;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  padding: 0.75rem 0.35rem 0.7rem;
  font-family: inherit;
  text-align: center;
  color: var(--ink-primary);
  background: var(--surface-base);
  border: 1px solid var(--stone-200);
  border-radius: 12px;
  cursor: pointer;
  transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}

.empty-day-energy-option:hover {
  border-color: var(--stone-300);
  background: var(--stone-50);
}

.empty-day-energy-option:focus-visible {
  outline: 2px solid var(--stone-500);
  outline-offset: 2px;
}

/* Selected: stone-500 ring (the first stone step to clear 3:1 against the
   card) drawn as border + inset shadow, so the tile reads 2px thick without
   its box growing and shoving its neighbours. */
.empty-day-energy-option.is-selected {
  background: var(--stone-100);
  border-color: var(--stone-500);
  box-shadow: inset 0 0 0 1px var(--stone-500);
}

.empty-day-energy-bars {
  width: 1.2rem;
  height: 1.05rem;
  margin-bottom: 0.2rem;
}

.empty-day-energy-bars svg {
  display: block;
  width: 100%;
  height: 100%;
}

.empty-day-energy-bars rect {
  fill: var(--stone-200);
  transition: fill 140ms ease;
}

.empty-day-energy-bars rect.is-on {
  fill: var(--stone-400);
}

.empty-day-energy-option.is-selected .empty-day-energy-bars rect {
  fill: var(--stone-300);
}

.empty-day-energy-option.is-selected .empty-day-energy-bars rect.is-on {
  fill: var(--stone-700);
}

.empty-day-energy-option-label {
  font-size: 0.9rem;
  font-weight: 500;
  line-height: 1.2;
}

.empty-day-energy-option.is-selected .empty-day-energy-option-label {
  font-weight: 600;
}

.empty-day-energy-option-sub {
  font-size: 0.7rem;
  line-height: 1.25;
  color: var(--ink-muted);
}

.empty-day-energy-note {
  margin: 0.75rem 0 0;
  font-size: 0.74rem;
  line-height: 1.35;
  text-align: center;
  color: var(--ink-muted);
}

/* The first step. The prompt is the §22 "after absence" line minus its
   "today", so it holds at any hour. */
.empty-day-start {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.7rem;
  margin-top: 1.75rem;
}

.empty-day-start-prompt {
  margin: 0;
  font-family: 'Lora', Georgia, serif;
  font-size: 1.02rem;
  font-style: italic;
  line-height: 1.35;
  text-align: center;
  color: var(--ink-muted);
}

/* The add-task CTA keeps the elevation every add-task affordance gets (see
   the allow-list in app.css), declared here because it is a native button.
   stone-600, not the theme's stone-500: cream on stone-500 is ~3.9:1, under
   AA for 16px text; on stone-600 it is ~5.9:1. */
.empty-day-cta-btn {
  appearance: none;
  width: 100%;
  min-height: 3.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0.8rem 1.25rem;
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--surface-base);
  background: var(--stone-600);
  border: 0;
  border-radius: 14px;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    0 1px 2px rgba(74, 62, 46, 0.2),
    0 12px 24px -14px rgba(74, 62, 46, 0.75);
  transition: background 140ms ease, box-shadow 140ms ease, transform 140ms ease;
}

.empty-day-cta-btn:hover:not(:disabled) {
  background: var(--stone-700);
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.1),
    0 2px 4px rgba(74, 62, 46, 0.2),
    0 16px 28px -14px rgba(74, 62, 46, 0.8);
}

.empty-day-cta-btn:active:not(:disabled) {
  transform: translateY(0);
}

.empty-day-cta-btn:focus-visible {
  outline: 2px solid var(--stone-700);
  outline-offset: 3px;
}

.empty-day-cta-btn:disabled {
  opacity: 0.5;
  cursor: default;
  box-shadow: none;
}

.empty-day-cta-icon {
  width: 1.5rem;
  height: 1.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.14);
  flex-shrink: 0;
}

.empty-day-cta-icon svg {
  width: 0.95rem;
  height: 0.95rem;
  display: block;
}

/* Someday peek — an offer, not a section. Dashed like every other
   suggestion surface; omitted entirely when Someday is empty (§26.4). */
.empty-day-someday {
  margin-top: 1.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  text-align: left;
}

.empty-day-someday-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.empty-day-someday-label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.empty-day-someday-label svg {
  width: 0.9rem;
  height: 0.9rem;
  color: var(--stone-500);
  flex-shrink: 0;
}

.empty-day-someday-all {
  padding: 0.2rem 0.4rem;
  margin-right: -0.4rem;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--stone-600);
  text-decoration: none;
  border-radius: 6px;
  transition: background 120ms ease;
}

.empty-day-someday-all:hover {
  background: rgba(138, 118, 89, 0.12);
}

.empty-day-someday-all:focus-visible {
  outline: 2px solid var(--stone-500);
  outline-offset: 1px;
}

.empty-day-someday-card {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.85rem 0.85rem 0.85rem 1rem;
  background: rgba(237, 229, 214, 0.35);
  border: 1px dashed var(--stone-300);
  border-radius: 14px;
}

.empty-day-someday-card-text {
  flex: 1 1 auto;
  min-width: 0;
}

/* Wraps, never truncates — a Someday title cut to "Call the den…" is not
   something anyone can decide to bring into today. */
.empty-day-someday-card-title {
  font-size: 0.92rem;
  line-height: 1.35;
  color: var(--ink-primary);
  overflow-wrap: break-word;
}

.empty-day-someday-card-meta {
  margin-top: 0.2rem;
  font-size: 0.72rem;
  color: var(--ink-muted);
}

.empty-day-someday-add {
  appearance: none;
  flex-shrink: 0;
  padding: 0.5rem 0.8rem;
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
  color: var(--stone-700);
  background: var(--surface-base);
  border: 1px solid var(--stone-300);
  border-radius: 10px;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease;
}

.empty-day-someday-add:hover:not(:disabled) {
  background: var(--stone-100);
  border-color: var(--stone-400);
}

.empty-day-someday-add:focus-visible {
  outline: 2px solid var(--stone-500);
  outline-offset: 2px;
}

.empty-day-someday-add:disabled {
  opacity: 0.5;
  cursor: default;
}

@media (prefers-reduced-motion: reduce) {
  .empty-day-fresh-inner,
  .empty-day-sky-sun { animation: none; }
  .empty-day-cta-btn,
  .empty-day-cta-btn:hover:not(:disabled) { transform: none; }
}

/* -----------------------------------------------------------------------
   All-cleared (§26.5) — the day's closing screen.

   Layout note: this composition is a BOUNDED, optically centered group, not
   a top-packed stack. The previous version inherited .empty-day's flex
   spacers while its container never grew, so on a phone the whole thing sat
   in the top 60% and the permission divider was a full-width rule stranded
   across the middle of an empty screen. Here .empty-day-cleared grows to
   fill the timeline and centers one max-width column inside it, so the
   composition reads the same at 640px and at 1200px.

   Everything is a plain element — no MudButton — because the action tiles
   need a three-line composition and a real :disabled treatment, both of
   which would otherwise be a fight with MudBlazor's own rules.
   ----------------------------------------------------------------------- */

.empty-day-cleared {
  flex: 1 1 auto;
  justify-content: center;
  /* .empty-day's 4rem left inset exists for the timeline rail, which this
     composition never renders. Left as-is it pushed a "centered" column 1rem
     off center on desktop. */
  padding: 2rem 1.5rem 2.5rem;
  gap: 0;
}

/* Ambient warmth behind the headline so the screen has light rather than a
   flat cream field. z-index: -1 — .empty-day is a stacking context (position:
   relative + z-index: 1), and a positioned ::before at z-index 0 would paint
   ABOVE the in-flow text inside it. */
.empty-day-cleared::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    115% 48% at 50% 22%,
    rgba(221, 208, 184, 0.38) 0%,
    rgba(237, 229, 214, 0.16) 45%,
    rgba(245, 242, 232, 0) 72%
  );
}

.empty-day-cleared-inner {
  width: 100%;
  max-width: 27rem;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Quiet crossfade only (§26.5 anti-patterns: no bouncing, no celebration).
     Same 4px / ~220ms vocabulary as the Sprint beat. */
  animation: empty-day-cleared-in 260ms ease-out both;
}

@keyframes empty-day-cleared-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .empty-day-cleared-inner { animation: none; }
}

/* Stone seal. Warm, never green — green is the productivity-app success
   register the rest of the app deliberately avoids (cf. the Sprint beat
   check, which is amber for the same reason). */
.empty-day-seal {
  width: 3.5rem;
  height: 3.5rem;
  margin-bottom: 1.1rem;
  flex-shrink: 0;
}

.empty-day-seal svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* Headline — serif, matching the Sprint completion screens. This is the one
   reflective moment in the timeline, so it gets the expressive face rather
   than the UI sans. */
.empty-day-cleared .empty-day-greeting {
  gap: 0.45rem;
  margin-bottom: 1.5rem;
}

.empty-day-cleared .empty-day-greeting-main {
  font-family: 'Lora', Georgia, serif;
  font-size: 1.6rem;
  font-weight: 600;
  line-height: 1.24;
  letter-spacing: -0.012em;
  color: var(--ink-primary);
}

.empty-day-cleared .empty-day-greeting-sub-strong {
  font-size: 0.92rem;
  color: var(--ink-muted);
  line-height: 1.4;
}

/* Done-today panel — the day's record.

   Lifted, not sunk: the old --stone-100 fill was DARKER than the cream page,
   which read as a placeholder well rather than a card. A near-white top edge
   over stone-50 plus a warm cast shadow puts it in front of the page. */
.empty-day-done-panel {
  width: 100%;
  max-width: none;
  margin: 0;
  background: linear-gradient(180deg, #FCFAF5 0%, var(--stone-50) 100%);
  border: 1px solid var(--stone-200);
  border-radius: 14px;
  padding: 1.15rem 1.25rem 1.2rem;
  box-shadow:
    0 1px 2px rgba(74, 62, 46, 0.05),
    0 14px 30px -20px rgba(74, 62, 46, 0.5);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
}

.empty-day-done-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.28rem;
}

/* The count is set inline with its words on purpose. A standalone numeral
   reads as a score, and §26.5 bans progress framing outright. */
.empty-day-done-count {
  display: inline-flex;
  align-items: baseline;
  gap: 0.38rem;
  color: var(--stone-700);
}

.empty-day-done-n {
  font-family: 'Lora', Georgia, serif;
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
}

.empty-day-done-word {
  font-size: 0.92rem;
  font-weight: 500;
}

.empty-day-done-breakdown {
  font-size: 0.72rem;
  color: var(--ink-faint);
  letter-spacing: 0.04em;
}

.empty-day-done-list {
  list-style: none;
  margin: 0.95rem 0 0;
  padding: 0.9rem 0 0;
  border-top: 1px solid var(--stone-200);
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  text-align: left;
}

.empty-day-done-item {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  font-size: 0.86rem;
  line-height: 1.38;
  color: var(--ink-muted);
}

.empty-day-done-item-dot {
  width: 5px;
  height: 5px;
  margin-top: 0.42rem;
  border-radius: 50%;
  background: var(--stone-300);
  flex: 0 0 auto;
}

/* Titles wrap; they are never truncated. A finished task the user cannot
   read back is not a record of anything. */
.empty-day-done-item-title {
  min-width: 0;
  overflow-wrap: anywhere;
}

.empty-day-done-overflow {
  /* Indented to the title column (dot 5px + 0.6rem gap), not the dot column. */
  padding-left: 0.92rem;
  margin-top: 0.65rem;
  font-size: 0.74rem;
  font-style: italic;
  color: var(--ink-faint);
  text-align: left;
}

/* "If you're up for more" — an offer, never a nudge. */
.empty-day-more {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  margin-top: 1.75rem;
  margin-bottom: 0;
}

.empty-day-more-hint {
  font-size: 0.8rem;
  font-style: italic;
  color: var(--ink-faint);
}

.empty-day-more-actions {
  display: flex;
  align-items: stretch;
  gap: 0.6rem;
  width: 100%;
}

.empty-day-tile {
  appearance: none;
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  padding: 0.85rem 0.7rem 0.9rem;
  font-family: inherit;
  text-align: center;
  color: var(--ink-primary);
  background: var(--surface-base);
  border: 1px solid var(--stone-200);
  border-radius: 12px;
  cursor: pointer;
  transition: background 140ms ease, border-color 140ms ease,
              box-shadow 140ms ease, transform 140ms ease, opacity 180ms ease;
}

.empty-day-tile:hover:not(:disabled) {
  background: var(--stone-50);
  border-color: var(--stone-300);
  transform: translateY(-1px);
  box-shadow: 0 8px 18px -12px rgba(74, 62, 46, 0.55);
}

.empty-day-tile:active:not(:disabled) {
  transform: translateY(0);
  box-shadow: none;
}

.empty-day-tile:focus-visible {
  outline: 2px solid var(--stone-500);
  outline-offset: 2px;
}

/* Disabled is a real state here: "Pull from someday" with an empty Someday
   list has nowhere to send the user, so it dims rather than delivering them
   to a blank page. Dimmed, never hidden — the affordance still teaches what
   the list is for. */
.empty-day-tile:disabled {
  opacity: 0.42;
  cursor: default;
  transform: none;
  box-shadow: none;
}

.empty-day-tile-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  color: var(--stone-500);
}

.empty-day-tile-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

.empty-day-tile-label {
  font-size: 0.88rem;
  font-weight: 500;
  line-height: 1.25;
}

.empty-day-tile-sub {
  font-size: 0.7rem;
  line-height: 1.3;
  color: var(--ink-faint);
}

/* Permission footer — the non-negotiable line (§26.5). A short centered
   hairline replaces the old full-width rule: edge to edge at this point in
   the composition it cut the screen in half instead of closing it. */
.empty-day-permission {
  position: relative;
  width: 100%;
  margin-top: 1.9rem;
  padding-top: 0;
  border-top: none;
  text-align: center;
  font-size: 0.8rem;
  font-style: italic;
  color: var(--ink-faint);
}

.empty-day-permission::before {
  content: '';
  position: absolute;
  top: -1rem;
  left: 50%;
  transform: translateX(-50%);
  width: 2.5rem;
  height: 1px;
  background: var(--stone-200);
}

/* -----------------------------------------------------------------------
   Free before next — free-time panel + suggestion + blurred Later preview
   ----------------------------------------------------------------------- */

.empty-day-free-before-next {
  padding: 1.5rem 2rem 2rem 4rem;
  align-items: stretch;
  gap: 1.25rem;
}

.empty-day-free-time-panel {
  background: var(--stone-100);
  border: 1px solid var(--stone-200);
  border-radius: 10px;
  padding: 1.1rem 1.25rem;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.empty-day-free-time-label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.empty-day-free-time-duration {
  font-size: 1.4rem;
  font-weight: 500;
  color: var(--stone-700);
  letter-spacing: -0.005em;
  font-variant-numeric: tabular-nums;
}

.empty-day-free-time-before {
  font-size: 0.8rem;
  color: var(--ink-muted);
}

.empty-day-free-time-task {
  font-weight: 500;
  color: var(--ink-primary);
}

/* Suggestion card — cream fill over stone-100, the amber dot is the single
   live-signal accent (same idiom as spotlight §8). */
.empty-day-suggestion-card {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.8rem 0.95rem;
  background: var(--surface-base);
  border: 1px solid var(--stone-200);
  border-radius: 8px;
}

.empty-day-suggestion-dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--amber-400);
  flex-shrink: 0;
}

.empty-day-suggestion-body {
  flex: 1 1 auto;
  min-width: 0;
}

.empty-day-suggestion-title {
  font-size: 0.9rem;
  color: var(--ink-primary);
  margin-bottom: 0.15rem;
  line-height: 1.35;
}

.empty-day-suggestion-title strong {
  font-weight: 600;
}

.empty-day-suggestion-meta {
  font-size: 0.68rem;
  color: var(--ink-faint);
  letter-spacing: 0.01em;
}

.empty-day-suggestion-actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.25rem;
}

.empty-day-suggestion-start,
.empty-day-suggestion-cycle {
  flex: 1 1 0;
  padding: 0.5rem !important;
  font-size: 0.82rem !important;
  text-transform: none !important;
  letter-spacing: normal !important;
  border-radius: 6px !important;
}

/* Fallback line when no float task fits the gap — calm, no pressure. */
.empty-day-suggestion-fallback {
  padding: 1rem;
  text-align: center;
  font-size: 0.85rem;
  font-style: italic;
  color: var(--ink-muted);
}

/* Later preview — fixed-time commitments stay legible; float titles use §5-style
   blur + opacity. Each float row has a sharp "Start" control (spotlight) without
   cycling the suggestion box. */
.empty-day-later-preview {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin-top: 0.5rem;
}

.empty-day-later-header {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0;
}

.empty-day-later-count {
  font-size: 0.72rem;
  color: var(--ink-faint);
  margin: 0 0 0.25rem 0;
}

.empty-day-later-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.empty-day-later-card {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--stone-100);
  border-radius: 4px;
  padding: 0.45rem 0.75rem;
  font-size: 0.72rem;
  color: var(--ink-muted);
}

/* Blur only the title row so the Start control stays sharp and tappable. */
.empty-day-later-card--float .empty-day-later-card-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  filter: blur(2px);
  opacity: 0.55;
  transition: filter 200ms ease, opacity 200ms ease;
}

.empty-day-later-card--float:hover .empty-day-later-card-main {
  filter: blur(0);
  opacity: 1;
}

.empty-day-later-start-btn {
  flex-shrink: 0;
  align-self: center;
  margin: -0.2rem -0.35rem -0.2rem 0 !important;
  min-width: auto !important;
  padding: 0.1rem 0.4rem !important;
  font-size: 0.65rem !important;
  font-weight: 600 !important;
  line-height: 1.2 !important;
  text-transform: none !important;
  letter-spacing: normal !important;
  border-radius: 4px !important;
}

.empty-day-later-card--fixed {
  filter: none;
  opacity: 1;
}

.empty-day-later-card--fixed .empty-day-later-time {
  color: var(--ink-primary);
}

.empty-day-later-card--fixed .empty-day-later-title {
  color: var(--ink-primary);
  font-weight: 500;
}

.empty-day-later-dot {
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  border: 1px solid var(--stone-300);
  flex-shrink: 0;
}

.empty-day-later-time {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}

.empty-day-later-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* §2 "Look through" on Free-before-next's Later list: the titles sharpen and
   wrap in full, at a readable size. The fixed-time rows are already sharp. */
.empty-day-later-preview.is-looking .empty-day-later-card {
  font-size: 0.8rem;
  color: var(--ink-primary);
}

.empty-day-later-preview.is-looking .empty-day-later-card--float .empty-day-later-card-main {
  filter: none;
  opacity: 1;
}

.empty-day-later-preview.is-looking .empty-day-later-title {
  white-space: normal;
  overflow-wrap: anywhere;
}

/* The pick button keeps the spotlight's pill; here it sits under the suggestion. */
.empty-day-pick-btn {
  margin-top: 0;
}

/* Mobile/narrow: match Someday horizontal padding (~1.25rem) and keep empty-day
   compositions usable. Desktop's 4rem section inset + 2rem rail is too wide
   on phones. */
@media (max-width: 640px) {
  .timeline-content::before {
    left: 0.5rem;
  }

  .timeline-greeting {
    padding: 1.25rem 1.25rem 0 1.25rem;
  }

  .timeline-energy {
    padding: 0.65rem 1.25rem 0.2rem 1.25rem;
    flex-direction: column;
    align-items: stretch;
  }

  .timeline-energy .day-capacity.timeline-energy-meter {
    flex: 1 1 auto;
    max-width: none;
    min-width: 0;
    align-self: stretch;
  }

  .timeline-energy .day-capacity.timeline-energy-meter .day-capacity-label {
    justify-content: flex-start;
  }

  .timeline .section {
    padding: 1.5rem 1.25rem 1.5rem 1.25rem;
  }

  /* Sprint entry invitation: on narrow viewports the single-line desktop copy
     would wrap wherever the width runs out. Set it instead as two short lines,
     one per phrase:
       3 quick wins in 10 minutes. / Try a Someday sprint.
     Each is its own span, so the break holds at every phone width, and the copy
     is written to fit: at 320px the longer line takes 174px of the 183px the
     card leaves it (narrower than that, it wraps to three). The lines
     are centered, and so is the composition as a whole (icon + block), so the
     prompt reads as one balanced block. The centering targets the inner
     __start button (was the wrapper before the Dismiss × was added) — applying
     it to the wrapper instead would push the start area off-center because the
     dismiss button on the trailing edge would consume layout. */
  .hf-sprint-entry__start {
    justify-content: center;
    text-align: center;
  }

  .hf-sprint-entry__copy {
    text-align: center;
  }

  .hf-sprint-entry__line {
    display: block;
  }

  .timeline .section-earlier {
    margin-left: 1.25rem;
    margin-right: 1.25rem;
  }

  /* Re-aimed with the card, to the same -1rem / 0.75rem as
     .float-promotion-card::before further down (the arithmetic is noted there):
     it covers the 3px rail at 0.5rem and stops short of the card's own border.
     The wider strip this replaces reached 0.3rem past that border, and since the
     mask paints above its parent it erased the tray's left edge, which left the
     tray standing out of line with the Now cards above it. */
  .timeline .section-earlier::before {
    left: -1rem;
    width: 0.75rem;
  }

  .spotlight {
    margin: 0.5rem 1.25rem 1rem 1.25rem;
  }

  /* .float-promotion-card's phone overrides are NOT here — see the matching
     @media block at the end of the Float task promotion section below. Its base
     rules are declared after this one, so an override placed here loses on source
     order at equal specificity and silently does nothing. */

  .empty-day {
    padding: 1.25rem 1.25rem 2rem 1.25rem;
  }

  .empty-day-free-before-next {
    padding: 1.25rem 1.25rem 1.75rem 1.25rem;
  }

  .empty-day-greeting-main {
    font-size: 1.2rem;
  }

  .empty-day-free-time-duration {
    font-size: 1.25rem;
  }

  /* Fresh morning: like All-cleared below, a deeper bottom inset lifts the
     centered column clear of the bottom tab bar; the rest is type scale and
     tighter rhythm so the whole morning fits one phone screen. */
  .empty-day-fresh {
    padding: 1.25rem 1.25rem 3rem;
  }

  .empty-day-sky {
    width: 13rem;
    margin-bottom: 0.9rem;
  }

  .empty-day-fresh-head {
    margin-bottom: 1.3rem;
  }

  .empty-day-fresh-greeting {
    font-size: 1.6rem;
  }

  .empty-day-energy-card {
    padding: 0.9rem 0.75rem 0.8rem;
  }

  .empty-day-energy-head {
    padding: 0 0.15rem;
  }

  .empty-day-energy-options {
    gap: 0.4rem;
  }

  .empty-day-energy-option {
    padding: 0.7rem 0.2rem 0.65rem;
  }

  .empty-day-start,
  .empty-day-someday {
    margin-top: 1.4rem;
  }

  /* All-cleared: the whole composition is optically centered, so the phone
     override is type scale plus a deeper bottom inset that lifts the group
     clear of the bottom tab bar. */
  .empty-day-cleared {
    padding: 1.5rem 1.25rem 3rem;
  }

  .empty-day-cleared .empty-day-greeting-main {
    font-size: 1.42rem;
  }

  .empty-day-cleared .empty-day-greeting-sub-strong {
    font-size: 0.88rem;
  }

  .empty-day-done-panel {
    max-width: 100%;
  }

  /* Two tiles side by side at 375px leaves each ~160px, so the label wraps to
     two lines. That's fine — `align-items: stretch` keeps both boxes equal —
     but the padding has to come in or the wrap gets cramped. */
  .empty-day-tile {
    padding: 0.8rem 0.55rem 0.85rem;
  }

  .empty-day-tile-label {
    font-size: 0.85rem;
  }

  .empty-day-tile-sub {
    font-size: 0.68rem;
  }

  /* -----------------------------------------------------------------------
     Now cards on a phone: the title first, its metadata on a line of its own.

     The desktop card is a single flex row in which `.task-title` is the only
     child that can give — every badge holds its natural width. At 375px that
     leaves the title 83px, and nothing at all once a card also carries a
     calendar mark or a state chip, so the one thing the zone exists to answer
     ("what am I doing?") is the first thing to disappear. §2 wants Now tasks
     "crisp, fully visible"; §10.13 wants the task name readable without
     truncation. A card that shows "Catch …" honours neither.

     So the row breaks in two on phones: the title keeps the card's full width
     beside the completion circle, and the time badge, calendar mark, state
     chips and size pill drop to a quiet strip beneath it. That is the shape
     the spotlight card already uses for its own status line, so Now reads as
     the same surface rather than a second idea.

     The break is a zero-height, full-width `::after` rather than a wrapper
     element, which keeps this a stylesheet change: the razor is untouched,
     `:nth-child` still lands on the task rows, and timeline-dnd.js goes on
     hit-testing an <li> whose shape it already knows.
     ----------------------------------------------------------------------- */
  .timeline .section-now ul > li.task {
    flex-wrap: wrap;
    align-content: center;
    row-gap: 0.25rem;
    /* Tighter than the desktop 0.6rem in both directions: on the title row every
       pixel here is a pixel off the title, and on the strip it is what keeps a
       heavily-flagged task's badges to a single line. */
    column-gap: 0.4rem;
  }

  /* The kebab stays up on the title row. It is the card's action, and a seat
     that holds still is easier to find than one that drifts down whenever a
     task happens to pick up another chip. */
  .timeline .section-now ul > li.task > .task-menu {
    order: 1;
  }

  .timeline .section-now ul > li.task::after {
    content: "";
    order: 2;
    flex: 0 0 100%;
    height: 0;
  }

  /* The metadata strip. `margin-left: auto` on the size pill is inert on the
     desktop row — the title's flex-grow claims the slack long before the auto
     margin is offered any — but down here nothing grows, so it wakes up and
     strands the pill out in the middle of the strip. Zeroed, the strip simply
     reads in order: when, where it came from, how it feels, how big.

     The calendar badge is reached through MudTooltip's `.mud-tooltip-root`
     wrapper because that wrapper, not `.task-calendar-badge`, is the flex
     child; ordering the span itself would quietly do nothing. */
  .timeline .section-now ul > li.task > .task-time-badge,
  .timeline .section-now ul > li.task > .mud-tooltip-root,
  .timeline .section-now ul > li.task > .task-still-here-badge,
  .timeline .section-now ul > li.task > .task-needs-new-plan-badge,
  .timeline .section-now ul > li.task > .task-hard-to-start-badge,
  .timeline .section-now ul > li.task > .size-pill {
    order: 3;
    margin-left: 0;
    /* One height for the whole strip. The pill, the time badge and the state
       chips are drawn at three different type scales (16.4 / 19.2 / 21px), and
       left ragged they make the strip's height depend on which badges a task
       happens to carry — which lets a card lower down out-grow the one above
       it and quietly inverts the §5 gradient. */
    min-height: 1.35rem;
  }

  /* MudTooltip's wrapper is an inline-block, so it carries a line box and sits
     a couple of pixels proud of the rest of the strip. Flex removes it. */
  .timeline .section-now ul > li.task > .mud-tooltip-root {
    display: inline-flex;
    align-items: center;
  }

  /* Tighten the stack to help pay for the second row. */
  .timeline .section-now ul {
    gap: 0.45rem;
  }

  /* The proximity gradient, re-cut for the phone. Padding and min-height come
     down to pay for the second row, while the ordering §5 protects still reads:
     type steps 17 → 16 → 15 → 14.4px and the weights are left exactly as they
     are, so "boldest first" holds on every card and "tallest first" on every
     card whose title fits a line. A longer title wraps rather than being cut
     (see `.timeline .task .task-title`), even where that stands a card taller
     than the one above it. */
  .timeline .section-now ul > li.task:nth-child(2) { min-height: 4.75rem; padding: 0.85rem 1rem;   font-size: 1.0625rem; }
  .timeline .section-now ul > li.task:nth-child(4) { min-height: 4rem;    padding: 0.7rem  0.9rem;  font-size: 1rem; }
  .timeline .section-now ul > li.task:nth-child(6) { min-height: 3.5rem;  padding: 0.6rem  0.85rem; font-size: 0.9375rem; }
  .timeline .section-now ul > li.task:nth-child(8) { min-height: 3.25rem; padding: 0.55rem 0.8rem;  font-size: 0.9rem; }

  /* -----------------------------------------------------------------------
     Soon and Later on a phone: the same two-row shape, for the same reason.

     Both zones let a long title wrap rather than cutting it (see
     `.timeline .task .task-title` up the file), but a wrap only helps if
     the title has width to wrap into. On one row at 375px every badge holds its
     natural width and the title gets the leftovers — measured: 133px in Soon on
     a card carrying a time badge and a size word, 148px in Later on one
     carrying a calendar mark and a state chip. Three lines of twenty characters
     is not a readable title; it is an ellipsis with extra steps.

     So the row breaks in two exactly as the Now card does above: title (with
     the completion circle and the kebab) on top, metadata on a quiet strip
     beneath, via the same zero-height full-width `::after`. No razor change, so
     `:nth-child` still lands on task rows and timeline-dnd.js goes on
     hit-testing an <li> whose shape it already knows.

     Unlike Now, neither zone trims its padding to pay for the second row: the
     strip is simply added. Both are content-sized (§25 — zones "collapse to the
     height of their content", cards "flex to content") inside a page that
     scrolls.

     `row-gap: 0`, with the spacing moved onto the strip itself — unlike the Now
     card above, which can use a row-gap because a Now card always carries a
     size pill and its strip is therefore never empty. Later rows carry no size
     word and usually no badge either, and a row-gap would charge every one of
     them for the blank break line — 4px on a 39px card, on the most common row
     in the zone, to buy nothing. With the gap on the strip instead the rule
     holds in both zones: a card with something to put on the strip grows by the
     strip, and a card with nothing measures exactly what it measures today.
     ----------------------------------------------------------------------- */
  .timeline .section-soon ul > li.task,
  .timeline .section-later ul > li.task {
    flex-wrap: wrap;
    align-content: center;
    row-gap: 0;
  }

  /* The kebab keeps its seat on the title row. It is the card's action, and a
     seat that holds still is easier to find than one that drifts down whenever
     a task happens to pick up another chip. */
  .timeline .section-soon ul > li.task > .task-menu,
  .timeline .section-later ul > li.task > .task-menu {
    order: 1;
  }

  .timeline .section-soon ul > li.task::after,
  .timeline .section-later ul > li.task::after {
    content: "";
    order: 2;
    flex: 0 0 100%;
    height: 0;
  }

  /* The metadata strip. Soon's size word goes down here with the badges rather
     than staying up beside the title: it is only one quiet 0.6rem word, but it
     is 35px of title width on every card in the zone, and it is the difference
     between two lines and three on a 62-character title (231px against 198px,
     measured at 375px). It does mean every Soon card carries a strip and so
     stands ~16px taller on a phone, since every Soon card has a size word —
     paid deliberately, and only in the zone that scrolls.

     `margin-left: auto` on `.size-text` is inert on the desktop row — the
     title's flex-grow claims the slack long before the auto margin is offered
     any — but down here nothing grows, so it wakes up and strands the size word
     out at the far end of the strip. Zeroed, the strip simply reads in order:
     when, where it came from, how it feels, how big.

     The calendar badge is reached through MudTooltip's `.mud-tooltip-root`
     wrapper because that wrapper, not `.task-calendar-badge`, is the flex child
     (MudTooltip `Inline` defaults true); ordering the span itself would quietly
     do nothing. */
  .timeline .section-soon ul > li.task > .task-time-badge,
  .timeline .section-soon ul > li.task > .mud-tooltip-root,
  .timeline .section-soon ul > li.task > .task-still-here-badge,
  .timeline .section-soon ul > li.task > .task-needs-new-plan-badge,
  .timeline .section-soon ul > li.task > .task-hard-to-start-badge,
  .timeline .section-soon ul > li.task > .size-text,
  .timeline .section-later ul > li.task > .mud-tooltip-root,
  .timeline .section-later ul > li.task > .task-still-here-badge,
  .timeline .section-later ul > li.task > .task-needs-new-plan-badge,
  .timeline .section-later ul > li.task > .task-hard-to-start-badge,
  .timeline .section-later ul > li.task > .task-time-badge,
  .timeline .section-later ul > li.task > .size-text {
    order: 3;
    margin-left: 0;
    margin-top: 0.25rem;
  }

  /* MudTooltip's wrapper is an inline-block, so it carries a line box and sits
     a couple of pixels proud of the rest of the strip. Flex removes it. */
  .timeline .section-soon ul > li.task > .mud-tooltip-root,
  .timeline .section-later ul > li.task > .mud-tooltip-root {
    display: inline-flex;
    align-items: center;
  }
}

/* —— Float task promotion (spec §10) ——
   The card is reused on top of `.empty-day-suggestion-card` (it shares the dot
   and body skeleton), but it lives mid-timeline and needs different bones:
   stack vertically, cap the width so wide screens don't blow the buttons up,
   and override a couple of inherited rules that don't fit here (notably the
   global `.size-pill { margin-left: auto }`, which would otherwise float the
   size pill to the far right of its row, away from the deadline pill).

   Horizontal placement: the card lives directly inside `.timeline-content`,
   not inside a `.section`. We mirror the section inset so the card aligns
   with task titles instead of butting up against the page edge, and so the
   timeline rail (at left: 2rem on desktop, 0.5rem on mobile) sits to the
   left of the card rather than visibly running through it. */
.float-promotion-card {
  flex-direction: column;
  align-items: stretch;
  gap: 0.4rem;
  max-width: 32rem;
  margin: 0.5rem 2rem 1rem 4rem;
  padding: 0.85rem 1rem;
  position: relative;
  z-index: 1;
  border-color: var(--stone-300);
  border-width: 1px;
  border-radius: 10px;
  background: var(--surface-base);
  box-shadow: 0 1px 2px rgba(24, 24, 27, 0.04);
}

/* Mask the vertical timeline rail (.timeline-content::before at x=2rem) in
   the empty stretch above, beside, and below the card. Without this, the
   rail re-emerges as small vertical nubs just above the card top border
   and just below its bottom border (the card's own background only covers
   its own box, not the margin gaps to Now above and Soon below).

   Same pattern as .section-earlier::before: paint the page background over
   the rail in a narrow strip. The strip is generously extended (top/bottom
   = -2rem) past the card's vertical span so the rail can't peek through at
   the corners or in the margin gaps. The painted column lives at
   container-x ≈ 1.5rem..2.75rem, which is inside every section's left
   padding gutter, so the overshoot harmlessly paints over a rail-width
   strip that already matches the page background — no visible seam.

   The card's own stacking context (z-index: 1) sits above the rail
   (z-index: 0), so a plain opaque fill is enough. */
.float-promotion-card::before {
  content: '';
  position: absolute;
  top: -2rem;
  bottom: -2rem;
  left: -2.5rem;
  width: 1.25rem;
  background: var(--surface-base);
  pointer-events: none;
}

/* Phone: follow the section inset down to 1.25rem. The card mirrors that inset by
   hand — it lives directly in .timeline-content, not inside a .section — so without
   this it keeps the desktop 4rem and stands 2.75rem to the right of every zone header
   and task title. `max-width: none` because the desktop cap exists to stop the buttons
   stretching across a wide column; at phone widths it only leaves a ragged right edge
   while the sections and .spotlight run full-bleed.

   The mask has to be re-aimed to match: it is positioned off the card's own left edge,
   so moving the card moves the mask with it, while the rail moves independently
   (.timeline-content::before goes 2rem → 0.5rem on phones). Left unadjusted the two
   stop overlapping and the rail shows as a bare stroke beside the card. The card's
   padding box starts at 1.25rem plus its 1px border, so -1rem/0.75rem paints roughly
   0.3rem..1.05rem — over the 3px rail at 0.5rem, and still clear of the card's own
   border, which a wider strip would erase (the mask is a positioned child, so it
   paints above the parent's border).

   Deliberately down HERE rather than in the phone block up with .section and
   .spotlight: these two selectors are declared above, so an override placed there ties
   on specificity and loses on source order. */
@media (max-width: 640px) {
  .float-promotion-card {
    margin: 0.5rem 1.25rem 1rem 1.25rem;
    max-width: none;
  }

  .float-promotion-card::before {
    left: -1rem;
    width: 0.75rem;
  }
}

.float-promotion-card .empty-day-suggestion-dot {
  align-self: flex-start;
  margin-top: 0.3rem;
}

/* Fade the card *contents* rather than the card itself. Putting `opacity` on
   `.float-promotion-card` would also fade the `::before` mask above, letting
   the vertical timeline rail bleed through as a faint line on the card's
   left edge. Targeting `> *` keeps the card box (background, border) and its
   mask fully opaque while still softening the dot, copy, pills, and actions. */
.float-promotion-card.is-fading > * {
  opacity: 0.35;
  transition: opacity 5s linear;
}

/* §10.13 — the host already withholds `is-fading` (and the 60s auto-dismiss) when
   the user prefers reduced motion, so this is belt-and-braces: if the class ever
   arrives by another route, the card must not animate away underneath someone who
   asked for less movement. */
@media (prefers-reduced-motion: reduce) {
  .float-promotion-card.is-fading > * {
    opacity: 1;
    transition: none;
  }
}

.float-promotion-body {
  width: 100%;
}

.float-promotion-context {
  font-size: 0.85rem;
  color: var(--ink-muted);
  margin-bottom: 0.25rem;
  line-height: 1.35;
}

.float-promotion-task-name {
  font-weight: 600;
  font-size: 1rem;
  margin-bottom: 0.4rem;
  color: var(--ink-primary);
  line-height: 1.3;
}

.float-promotion-pills {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.5rem;
}

.float-promotion-pills .size-pill {
  margin-left: 0;
}

.float-promotion-deadline-pill {
  font-size: 0.65rem;
  padding: 0.15rem 0.45rem;
  border-radius: 999px;
  background: var(--stone-100);
  color: var(--ink-muted);
  letter-spacing: 0.04em;
}

/* §18 supplement — provenance marker for a Someday pick. Same geometry as the schedule pill
   it replaces, but outlined rather than filled: it is describing where the task came from,
   not asserting anything about when it is due. */
.float-promotion-someday-pill {
  font-size: 0.65rem;
  padding: 0.15rem 0.45rem;
  border-radius: 999px;
  background: transparent;
  /* Inset shadow rather than a border: this pill occupies the slot the schedule pill would
     otherwise fill, and the two are mutually exclusive, so their boxes have to be identical.
     A real 1px border adds 2px of height and the pill row visibly steps up. `border: 0` is
     explicit because a foreign reset in the host page otherwise supplies one. */
  border: 0;
  box-shadow: inset 0 0 0 1px var(--stone-200);
  color: var(--ink-faint);
  letter-spacing: 0.04em;
  font-style: italic;
}

.float-promotion-deadline-pill.is-today {
  background: #fef3c7;
  color: #92400e;
}

.float-promotion-initiation {
  display: block;
  width: 100%;
  margin: 0 0 0.5rem;
  padding: 0.4rem 0.55rem;
  border: 1px dashed var(--stone-300);
  border-radius: 6px;
  background: var(--stone-50);
  color: var(--ink-primary);
  font-size: 0.78rem;
  text-align: left;
  cursor: pointer;
}

.float-promotion-initiation:hover {
  background: #ebe5d7;
}

/* Don't let the empty-day stretch rules turn three buttons into three
   equally weighted blocks — Start now is the offer, the others are escapes.
   Keep this row compact so the card reads as a gentle inline banner, not a
   modal-strength prompt. */
.float-promotion-card .empty-day-suggestion-actions {
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.15rem;
}

.float-promotion-card .empty-day-suggestion-start {
  flex: 0 1 auto;
  min-width: 8rem;
  padding: 0.4rem 1rem !important;
}

.float-promotion-card .empty-day-suggestion-cycle {
  flex: 0 0 auto;
  padding: 0.4rem 0.75rem !important;
}

.spotlight-suggestion-pending {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.35rem 0;
}

.task-suggested-float {
  outline: 1px dashed var(--amber-400);
  outline-offset: 2px;
}

.float-promotion-diagnostic {
  border: 1px solid var(--stone-200);
  border-radius: 8px;
  padding: 0.9rem 1rem;
  margin: 0 0 0.75rem;
  background: var(--surface-base);
}

.float-promotion-diagnostic-title {
  font-size: 1rem;
  font-weight: 600;
  margin: 0 0 0.35rem;
  color: var(--ink-primary);
}

.float-promotion-diagnostic-task {
  font-size: 0.9rem;
  margin: 0 0 0.75rem;
  color: var(--ink-muted);
}

.float-promotion-diagnostic-actions {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.float-promotion-diag-btn {
  text-transform: none !important;
  letter-spacing: normal !important;
}

/* =========================================================================
   Dev time panel — only visible in Development with the toolbar toggled on.
   Sits at the top of the timeline page; visually echoes the Spotlight card
   (stone-100 surface, stone-200 border, soft warm shadow, same horizontal
   margins) so it reads as part of the timeline rhythm rather than a stray
   admin widget. Carries a red accent bar + small "DEV" pill so it can never
   be mistaken for a production tool.
   ========================================================================= */
.hf-devpanel {
  position: relative;
  margin: 0.5rem 2rem 1rem 4rem;
  padding: 1.1rem 1.25rem 1.15rem 1.65rem;
  background: var(--stone-100);
  border: 1px solid var(--stone-200);
  border-radius: 14px;
  box-shadow:
    0 6px 20px rgba(74, 62, 46, 0.08),
    0 1px 2px rgba(40, 25, 18, 0.04);
  overflow: hidden;
  color: var(--ink-primary);
  font-family: 'DM Sans', system-ui, sans-serif;
}

.hf-devpanel__accent {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: linear-gradient(180deg, #c0392b 0%, #8b2c20 100%);
}

.hf-devpanel__header {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 0.55rem;
}

/* When the header is rendered as a <button> (collapsible panels), strip the
   browser default chrome so it matches the original <div> presentation while
   staying keyboard- and screen-reader-friendly. */
button.hf-devpanel__header {
  background: transparent;
  border: 0;
  padding: 0;
  width: 100%;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
}

button.hf-devpanel__header:focus-visible {
  outline: 2px solid var(--stone-500, #8a7659);
  outline-offset: 2px;
  border-radius: 6px;
}

.hf-devpanel__chevron {
  margin-left: 0.4rem;
  color: var(--ink-muted, #6c6457);
  transition: transform 160ms ease;
  flex-shrink: 0;
}

/* Collapsed state: keep the chrome but remove the body's surrounding rhythm
   so the panel reads as a single compact strip. */
.hf-devpanel--collapsed {
  padding-top: 0.7rem;
  padding-bottom: 0.7rem;
}

.hf-devpanel--collapsed .hf-devpanel__header {
  margin-bottom: 0;
}

.hf-devpanel__eyebrow {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #ffffff;
  background: #b03422;
  padding: 0.18rem 0.5rem;
  border-radius: 999px;
  line-height: 1;
}

.hf-devpanel__title {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--ink-primary);
}

.hf-devpanel__hint {
  font-size: 0.72rem;
  color: var(--ink-faint);
  margin-left: auto;
}

.hf-devpanel__clock {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 0.85rem;
  row-gap: 0.2rem;
  align-items: baseline;
  padding: 0.55rem 0.75rem;
  margin-bottom: 0.75rem;
  background: var(--stone-50);
  border: 1px solid var(--stone-200);
  border-radius: 10px;
}

.hf-devpanel__clock-label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.hf-devpanel__clock-value {
  font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular,
    Menlo, Consolas, monospace;
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--ink-primary);
}

.hf-devpanel__clock-value--sim {
  color: #8b2c20;
}

.hf-devpanel__sim-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.6rem;
  margin-bottom: 0.7rem;
  background: #f8e7d4;
  border: 1px solid #e3b48a;
  border-radius: 8px;
  font-size: 0.78rem;
  color: #6b3a14;
}

.hf-devpanel__sim-dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--amber-500);
  box-shadow: 0 0 0 3px rgba(184, 135, 58, 0.18);
  animation: hf-devpanel-pulse 2.4s ease-in-out infinite;
}

@keyframes hf-devpanel-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

@media (prefers-reduced-motion: reduce) {
  .hf-devpanel__sim-dot { animation: none; }
}

.hf-devpanel__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}

.hf-devpanel__offset {
  min-width: 9rem;
}

.hf-devpanel__offset .mud-input-control {
  background: var(--surface-elev);
  border-radius: 8px;
}

.hf-devpanel__btn {
  text-transform: none !important;
  letter-spacing: 0 !important;
  font-weight: 600 !important;
  border-radius: 8px !important;
}

.hf-devmock__action-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-top: 0.85rem;
}

.hf-devmock__summary {
  font-size: 0.75rem;
  color: var(--ink-faint);
}

.hf-devmock__action-btn {
  text-transform: none !important;
  letter-spacing: 0 !important;
  font-weight: 600 !important;
  border-radius: 8px !important;
}

/* Data-mock panel — sibling of the time-controls panel. Uses the same
   .hf-devpanel chrome but lays its config row out in a responsive grid so
   the numeric counters + size selector stay aligned across breakpoints. */
.hf-devmock__hint {
  font-size: 0.78rem;
  color: var(--ink-muted);
  margin: 0 0 0.65rem;
  line-height: 1.45;
}

.hf-devmock__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: 0.6rem;
  margin-bottom: 0.55rem;
}

.hf-devmock__field .mud-input-control {
  background: var(--surface-elev);
  border-radius: 8px;
}

.hf-devmock__field--size {
  min-width: 9rem;
}

.hf-devmock__options {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.1rem 0 0.4rem;
}

/* Previous-day rollover panel — sibling of the time-controls and data-mock panels.
   Reuses the .hf-devpanel chrome but lays out scenario buttons in a responsive
   grid. Each button shows the scenario name, what gets seeded, and what the
   timeline is expected to render after the next refresh — so the dev can pick
   the shape they want to inspect without consulting the spec. */
.hf-devrollover__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: 0.55rem;
  margin-top: 0.4rem;
  align-items: stretch;
}

.hf-devrollover__btn {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  text-align: left;
  gap: 0.35rem;
  padding: 0.65rem 0.8rem 0.7rem;
  background: #f5f0e8;
  border: 1px solid rgba(140, 60, 50, 0.22);
  border-left: 3px solid #b14b3c;
  border-radius: 8px;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease, transform 120ms ease;
  font-family: inherit;
  color: inherit;
  height: 100%;
}

.hf-devrollover__btn:hover:not([disabled]) {
  background: rgba(177, 75, 60, 0.08);
  border-color: rgba(140, 60, 50, 0.45);
}

.hf-devrollover__btn:active:not([disabled]) {
  transform: translateY(1px);
}

.hf-devrollover__btn[disabled] {
  opacity: 0.55;
  cursor: progress;
}

.hf-devrollover__btn-title {
  font-size: 0.85rem;
  font-weight: 600;
  color: #6a2418;
  margin-bottom: 0.05rem;
}

.hf-devrollover__btn-line {
  display: grid;
  grid-template-columns: 4.25rem 1fr;
  align-items: baseline;
  gap: 0.45rem;
}

.hf-devrollover__btn-label {
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: rgba(106, 36, 24, 0.78);
  background: rgba(177, 75, 60, 0.1);
  padding: 0.1rem 0.35rem;
  border-radius: 4px;
  text-align: center;
  justify-self: start;
  align-self: center;
  white-space: nowrap;
}

.hf-devrollover__btn-label--expected {
  color: rgba(40, 84, 50, 0.85);
  background: rgba(60, 130, 80, 0.12);
}

.hf-devrollover__btn-text {
  font-size: 0.72rem;
  color: var(--ink-muted, #4a4a4a);
  line-height: 1.35;
}

/* Containment dev panel — adds grouped scenario buckets (Hard / Soft / Windows / Reset)
   on top of the shared .hf-devrollover button grid. The group label is a small
   eyebrow above each grid so the visual hierarchy mirrors the spec sections. */
.hf-devcontain__group-label {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(106, 36, 24, 0.78);
  margin: 0.65rem 0 0.2rem;
}

.hf-devcontain__group-label:first-of-type {
  margin-top: 0.35rem;
}

@media (max-width: 720px) {
  .hf-devpanel {
    margin: 0.5rem 0.75rem 1rem;
    padding: 1rem 1rem 1rem 1.25rem;
  }

  .hf-devpanel__clock {
    grid-template-columns: 1fr;
    row-gap: 0.05rem;
  }

  .hf-devpanel__clock-label {
    margin-top: 0.25rem;
  }

  .hf-devmock__grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .hf-devrollover__grid {
    grid-template-columns: 1fr;
  }
}

/* =====================================================================
   Containment Mode overlay (design spec §16)
   Full-viewport calm screen that replaces the timeline while a session is
   active. Palette: warm cream surface, walnut ink, single solid action
   button on the One Thing card. Layout matches the published mockup —
   brand top-left, dismiss top-right, centered serif reframe, optional
   anchor card, prominent One Thing card, two text-button exits at the
   bottom. Never branded as "containment" to the user.
   ===================================================================== */
.hf-contain {
  position: fixed;
  inset: 0;
  z-index: 1200;
  background: var(--surface-base, #f5f2e8);
  color: var(--ink-primary, #2c2c2a);
  font-family: 'DM Sans', system-ui, sans-serif;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  /* Soft fade-in so the screen change doesn't feel abrupt. */
  animation: hf-contain-fade-in 240ms ease-out both;
}

@keyframes hf-contain-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.hf-contain__bar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 1.25rem 1.5rem 0.5rem;
}

.hf-contain__close {
  width: 2rem;
  height: 2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  color: var(--stone-700, #4a3e2e);
  cursor: pointer;
  border-radius: 999px;
  padding: 0;
  transition: background-color 120ms ease, opacity 120ms ease;
}

.hf-contain__close:hover:not([disabled]) {
  background: rgba(74, 62, 46, 0.08);
}

.hf-contain__close[disabled] {
  opacity: 0.4;
  cursor: progress;
}

.hf-contain__close svg {
  width: 1.15rem;
  height: 1.15rem;
}

.hf-contain__body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 1.75rem;
  padding: 2rem 1.5rem 3rem;
  width: 100%;
  max-width: 36rem;
  margin: 0 auto;
}

.hf-contain__reframe {
  font-family: 'Cormorant Garamond', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  font-weight: 500;
  font-size: clamp(1.75rem, 3.5vw + 0.5rem, 2.4rem);
  line-height: 1.18;
  text-align: center;
  color: var(--stone-700, #4a3e2e);
  margin: 1rem 0 0.25rem;
  letter-spacing: 0.005em;
}

/* Anchor card — pure layout, no surface fill (matches the mockup which
   places the missed-task card flush on the cream background, separated
   only by spacing and the small clock-icon meta line). */
.hf-contain__anchor {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  align-items: flex-start;
  padding: 0.5rem 0;
}

.hf-contain__anchor-meta {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--stone-600, #6b5a42);
  font-size: 0.85rem;
}

.hf-contain__anchor-icon {
  width: 1rem;
  height: 1rem;
}

.hf-contain__anchor-title {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--ink-primary, #2c2c2a);
  line-height: 1.35;
}

.hf-contain__anchor-actions {
  display: flex;
  gap: 0.65rem;
  margin-top: 0.4rem;
  width: 100%;
}

/* One Thing card — visually heaviest element. Soft cream surface with a
   subtle border to lift it off the page, larger serif title, single solid
   action button. */
.hf-contain__onething {
  width: 100%;
  background: var(--stone-50, #f5f0e8);
  border: 1px solid rgba(74, 62, 46, 0.12);
  border-radius: 14px;
  padding: 1.6rem 1.25rem 1.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.85rem;
  text-align: center;
  margin: 0.25rem 0 0.5rem;
}

.hf-contain__onething-eyebrow {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--stone-600, #6b5a42);
}

.hf-contain__onething-title {
  font-family: 'Cormorant Garamond', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  font-weight: 500;
  font-size: clamp(1.45rem, 2.5vw + 0.6rem, 1.85rem);
  line-height: 1.2;
  color: var(--stone-700, #4a3e2e);
  margin: 0.1rem 0 0.4rem;
}

/* Buttons — two variants: ghost (outlined, used by anchor) and solid
   (filled walnut, used by One Thing). Both share the same height + radius
   so the visual rhythm stays consistent across the screen. */
.hf-contain__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.5rem;
  padding: 0.55rem 1.25rem;
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 500;
  border-radius: 8px;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease,
              color 120ms ease, opacity 120ms ease, transform 120ms ease;
  letter-spacing: 0.005em;
}

.hf-contain__btn[disabled] {
  opacity: 0.5;
  cursor: progress;
}

.hf-contain__btn:active:not([disabled]) {
  transform: translateY(1px);
}

.hf-contain__btn--ghost {
  flex: 1 1 0;
  background: transparent;
  border: 1px solid rgba(74, 62, 46, 0.32);
  color: var(--stone-700, #4a3e2e);
}

.hf-contain__btn--ghost:hover:not([disabled]) {
  background: rgba(74, 62, 46, 0.06);
  border-color: rgba(74, 62, 46, 0.55);
}

.hf-contain__btn--solid {
  background: var(--stone-500, #8a7659);
  color: #fff;
  border: 1px solid var(--stone-500, #8a7659);
  padding-left: 1.6rem;
  padding-right: 1.6rem;
}

.hf-contain__btn--solid:hover:not([disabled]) {
  background: var(--stone-600, #6b5a42);
  border-color: var(--stone-600, #6b5a42);
}

/* Footer text-buttons — visually quiet, single hairline separator dot
   between them, both routed through the same exit-window logic. */
.hf-contain__footer {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  margin: 0.75rem 0 0;
  flex-wrap: wrap;
  justify-content: center;
}

.hf-contain__link {
  background: transparent;
  border: 0;
  padding: 0.35rem 0.5rem;
  cursor: pointer;
  color: var(--stone-600, #6b5a42);
  font-size: 0.9rem;
  font-family: inherit;
  border-radius: 6px;
  transition: color 120ms ease, background-color 120ms ease, opacity 120ms ease;
}

.hf-contain__link:hover:not([disabled]) {
  color: var(--stone-700, #4a3e2e);
  background: rgba(74, 62, 46, 0.05);
}

.hf-contain__link[disabled] {
  opacity: 0.5;
  cursor: progress;
}

.hf-contain__footer-sep {
  color: var(--stone-300, #c4b296);
  font-size: 0.9rem;
  user-select: none;
}

@media (max-width: 540px) {
  .hf-contain__body {
    padding: 1.5rem 1.25rem 2.25rem;
    gap: 1.5rem;
  }

  .hf-contain__onething {
    padding: 1.4rem 1rem 1.3rem;
  }
}

/* ============================================================
   Someday Sprint (spec §Sprint)
   - Entry card (inline on the timeline, between Spotlight and Now)
   - Sprint overlay (full-viewport mode shift, NOT a modal)
   - Between-task beat ("That's one" / "That's two")
   - Completion screen ("Three down. Nice burst.")

   Design rules drawn from the spec:
   - Cream page background; brown filled primary buttons; amber only
     where "live / time passing / acknowledgment" applies (the bolt
     glyph and the beat check).
   - No pulsing, no animation pressure, no countdown framing.
   - Timer counts up, tabular nums.
   - Title is Lora serif, stripped of metadata.
   ============================================================ */

/* ----- Entry invitation ----------------------------------------- */

/* Dashed-border invitation that lives inside .section-now after the task
   list (rendered by Timeline.razor). It surfaces when the server's surfacing
   conditions hold AND the Now zone has room (≤3 tasks) — when either gate
   closes, the element disappears silently. Visually it reads as a contextual
   prompt sitting on the Now surface, not as a card competing with the tasks
   above it: same cream surface as Now, dashed warm-stone border (matching the
   dashed-border idiom established by .empty-day suggestion cards and the
   drop-slot active state), amber bolt + a single line of copy.
   `flex: 0 0 auto` holds it at its natural height directly under the last
   Now task. */
.hf-sprint-entry {
  flex: 0 0 auto;
  display: flex;
  align-items: stretch;
  gap: 0;
  /* No top margin: the section's `gap: 1rem` already supplies the spacing
     between the task <ul> and this invitation. Doubling them up pushed the
     dashed border noticeably away from the last task. */
  margin: 0;
  /* Padding is owned by the inner __start button so its hit area covers the
     full visual surface (apart from the small Dismiss × on the trailing edge). */
  padding: 0;
  background: transparent;
  border: 1px dashed var(--stone-300);
  border-radius: 10px;
  transition: border-color 120ms ease;
}

/* The wrapper became a <div> when we added the sibling Dismiss button (one
   <button> can't legally nest another). The previous border-on-hover hint —
   which used to live on the wrapper button — moves here so the dashed outline
   still nudges to its hover tone whenever the cursor is anywhere over the
   card. We can't use `:not([disabled])` on the wrapper anymore (divs don't
   carry disabled state); the busy state is reflected on the inner buttons via
   their own `[disabled]` rule, and a slightly darker dashed border under the
   cursor while busy is harmless. */
.hf-sprint-entry:hover {
  border-color: var(--stone-400);
}

.hf-sprint-entry__start {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.7rem 1rem;
  background: transparent;
  border: 0;
  /* 1px less than the wrapper so the focus / hover background nests cleanly
     inside the dashed outline without bleeding over it. */
  border-radius: 9px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  font-family: 'DM Sans', system-ui, sans-serif;
  color: var(--ink-muted);
  transition: background-color 120ms ease, color 120ms ease, opacity 120ms ease;
}

.hf-sprint-entry__start:hover:not([disabled]) {
  background: rgba(138, 118, 89, 0.05);
  color: var(--ink-primary);
}

.hf-sprint-entry__start:focus-visible {
  outline: 2px solid var(--amber-500);
  outline-offset: 2px;
}

.hf-sprint-entry__start[disabled] {
  opacity: 0.55;
  cursor: progress;
}

/* Quiet × on the trailing edge. Sized small and tinted muted so it reads as a
   secondary control next to the primary invite copy — the user who came to
   start a sprint shouldn't read it as part of the primary affordance, and the
   user who wants to clear the card for the day can find it without the card
   feeling like a banner ad. Spec §Sprint: "Tapping Dismiss hides it for the
   rest of the day with no re-surfacing." */
.hf-sprint-entry__dismiss {
  flex: 0 0 auto;
  align-self: center;
  margin-right: 0.4rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-muted);
  cursor: pointer;
  font-size: 1.05rem;
  line-height: 1;
  font-family: inherit;
  transition: background-color 120ms ease, color 120ms ease, opacity 120ms ease;
}

.hf-sprint-entry__dismiss:hover:not([disabled]) {
  background: rgba(138, 118, 89, 0.08);
  color: var(--ink-primary);
}

.hf-sprint-entry__dismiss:focus-visible {
  outline: 2px solid var(--amber-500);
  outline-offset: 2px;
}

.hf-sprint-entry__dismiss[disabled] {
  opacity: 0.55;
  cursor: progress;
}

.hf-sprint-entry__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  flex: 0 0 auto;
  color: var(--amber-500);
}

.hf-sprint-entry__icon svg {
  width: 1.05rem;
  height: 1.05rem;
}

.hf-sprint-entry__copy {
  font-size: 0.88rem;
  font-weight: 500;
  letter-spacing: 0.005em;
  line-height: 1.35;
  min-width: 0;
}

/* ----- Full-viewport Sprint mode -------------------------------- */

.hf-sprint {
  position: fixed;
  inset: 0;
  /* Sit above the docked MudDrawer (z-index ~1100) and the collapsed-chrome
     hamburger (1300). The full-viewport cream layer hides the timeline
     behind it; the centered .hf-sprint__card inside is the actual Sprint
     surface the user reads. We deliberately stay below MudDialog (1400)
     and MudSnackbar (1600); the Sprint flow doesn't open either, so
     nothing in the app paints on top of the Sprint surface. */
  z-index: 1390;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: var(--surface-base);
  color: var(--ink-primary);
  font-family: 'DM Sans', system-ui, sans-serif;
  /* Spec: deliberate mode shift, not a modal. We avoid any modal-ish
     fade/scrim — the cream surface fully replaces the timeline. */
  animation: hf-sprint-fade-in 200ms ease-out;
}

/* The visible Sprint card. Cream-on-cream per the brand-surfaces rule —
   distinguished from the page by a 1px stone-200 outline + a soft warm
   shadow. Same visual language as MudPaper cards elsewhere in the app, so
   Sprint reads as a self-contained focus surface rather than a floating
   overlay. The card defines its own bounded interior, which is what makes
   the top-left Exit button feel like it belongs to the Sprint, not the
   page chrome. */
.hf-sprint__card {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 56rem;
  /* Reserve the same vertical footprint across all three Sprint states
     (active task, between-task beat, completion). The active-task screen
     is the tallest — it stacks eyebrow, title, timer and the Done/Skip
     row — so the beat ("X done") and completion ("Three down…") screens
     would otherwise render as a noticeably shorter card and the
     transition between states reads as a visual jump. The body below is
     already a flex container with `align-items: center`, so the shorter
     content for the beat / completion states stays vertically centred
     inside this reserved space. Clamped against the available height so
     short viewports don't force the card to overflow. */
  min-height: min(30rem, 100%);
  max-height: 100%;
  background: var(--surface-base);
  border: 1px solid var(--stone-200);
  border-radius: 1.5rem;
  box-shadow: 0 14px 38px rgba(74, 62, 46, 0.10),
              0 2px 8px rgba(74, 62, 46, 0.05);
  overflow: hidden;
}

@keyframes hf-sprint-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.hf-sprint__bar {
  /* 3-column grid keeps Exit anchored at the top-left and the progress
     dots centered regardless of dot count. The right column is left empty
     so the dots stay visually centered against the viewport, not biased
     toward the Exit button. */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 1.4rem 1.75rem 0.5rem;
  gap: 1rem;
}

/* Exit button: rendered as plain text, not a pill. The bounded sprint card
   already supplies the surface affordance; a chrome-laden button competed
   with the primary Done CTA. We keep a generous hit area + a hover color
   shift so it still reads as interactive without painting a chip. */
.hf-sprint__exit {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  background: transparent;
  border: 0;
  padding: 0.55rem 0.25rem;
  cursor: pointer;
  font: inherit;
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--ink-muted);
  /* Generous hit area so a misclick on the icon side still registers. */
  min-height: 2.4rem;
  transition: color 120ms ease, transform 80ms ease;
}

.hf-sprint__exit:hover {
  color: var(--ink-primary);
}

.hf-sprint__exit:active {
  transform: scale(0.97);
}

.hf-sprint__exit:focus-visible {
  outline: 2px solid var(--amber-500);
  outline-offset: 2px;
  border-radius: 4px;
}

.hf-sprint__exit-icon {
  font-size: 1.05rem;
  line-height: 1;
}

.hf-sprint__dots {
  justify-self: center;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}

.hf-sprint__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--stone-200);
  display: inline-block;
}

.hf-sprint__dot.is-filled {
  background: var(--stone-500);
}

.hf-sprint__body {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem 1.5rem 3rem;
}

/* ----- Active task screen -------------------------------------- */

.hf-sprint__task {
  width: 100%;
  max-width: 38rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  text-align: center;
}

.hf-sprint__eyebrow {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--amber-600);
  margin-bottom: 0.25rem;
}

.hf-sprint__title {
  font-family: 'Lora', Georgia, serif;
  font-weight: 600;
  font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2rem);
  line-height: 1.22;
  color: var(--ink-primary);
  margin: 0;
  /* Spec: title only, no metadata. We give it room to breathe so the
     stripped-down phrasing reads as the task itself, not a label. */
  max-width: 30ch;
}

.hf-sprint__timer {
  margin-top: 1.4rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
}

.hf-sprint__timer-clock {
  font-size: clamp(2.4rem, 2rem + 2vw, 3rem);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  /* Tabular nums (spec) keep the colon from jittering as digits change. */
  color: var(--stone-700);
  letter-spacing: 0.005em;
  line-height: 1;
}

.hf-sprint__timer-label {
  font-size: 0.92rem;
  color: var(--ink-muted);
  /* "no rush" — informational, not evaluative (spec). */
}

.hf-sprint__actions {
  margin-top: 1.6rem;
  display: flex;
  align-items: center;
  gap: 1.25rem;
}

.hf-sprint__primary {
  background: var(--stone-700);
  color: #fff;
  border: 0;
  border-radius: 999px;
  padding: 0.85rem 2.4rem;
  font: inherit;
  font-size: 1rem;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 120ms ease, transform 120ms ease, opacity 120ms ease;
  min-width: 9.5rem;
  text-align: center;
}

.hf-sprint__primary:hover:not([disabled]) {
  background: #3a3022;
}

.hf-sprint__primary:active:not([disabled]) {
  transform: translateY(1px);
}

.hf-sprint__primary:focus-visible {
  outline: 2px solid var(--amber-500);
  outline-offset: 3px;
}

.hf-sprint__primary[disabled] {
  opacity: 0.55;
  cursor: progress;
}

.hf-sprint__secondary {
  background: transparent;
  border: 0;
  padding: 0.5rem 0.75rem;
  cursor: pointer;
  font: inherit;
  font-size: 0.95rem;
  color: var(--ink-muted);
  border-radius: 6px;
  transition: color 120ms ease, background-color 120ms ease, opacity 120ms ease;
}

.hf-sprint__secondary:hover:not([disabled]) {
  color: var(--ink-primary);
  background: rgba(74, 62, 46, 0.05);
}

.hf-sprint__secondary:focus-visible {
  outline: 2px solid var(--amber-500);
  outline-offset: 2px;
}

.hf-sprint__secondary[disabled] {
  opacity: 0.5;
  cursor: progress;
}

/* ----- Between-task beat --------------------------------------- */

.hf-sprint__beat {
  width: 100%;
  max-width: 28rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.4rem;
  text-align: center;
  /* Brief gentle fade — spec calls for ~700–900ms takeover; the timer
     in code drives the dismissal, this is just visual entry. */
  animation: hf-sprint-beat-in 220ms ease-out;
}

@keyframes hf-sprint-beat-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.hf-sprint__beat-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 4.6rem;
  height: 4.6rem;
  border-radius: 50%;
  /* Soft amber — spec: "not green; green reads as productivity-app
     success state, wrong register." */
  background: rgba(200, 152, 72, 0.18);
  color: var(--amber-500);
}

.hf-sprint__beat-check svg {
  width: 2.1rem;
  height: 2.1rem;
}

.hf-sprint__beat-phrase {
  margin: 0;
  font-family: 'Lora', Georgia, serif;
  font-weight: 600;
  font-size: clamp(1.7rem, 1.3rem + 1.4vw, 2.1rem);
  line-height: 1.2;
  color: var(--ink-primary);
}

.hf-sprint__beat-dots {
  margin-top: 0.25rem;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

/* ----- Completion screen --------------------------------------- */

.hf-sprint__completion {
  width: 100%;
  max-width: 32rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  text-align: center;
}

.hf-sprint__completion-dots {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 0.4rem;
}

.hf-sprint__completion-dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--stone-500);
  display: inline-block;
}

.hf-sprint__completion-headline {
  font-family: 'Lora', Georgia, serif;
  font-weight: 600;
  font-size: clamp(1.85rem, 1.4rem + 1.8vw, 2.4rem);
  line-height: 1.18;
  color: var(--ink-primary);
  margin: 0.2rem 0 0;
  /* Spec: no confetti, no streak, no comparison. The headline carries
     the whole acknowledgment. */
}

.hf-sprint__completion-sub {
  margin: 0 0 0.6rem;
  font-size: 0.98rem;
  color: var(--ink-muted);
  /* Borderline copy per spec — kept because completion is the one
     moment small contextual reinforcement feels earned. */
}

.hf-sprint__completion .hf-sprint__primary {
  margin-top: 0.4rem;
  min-width: 11rem;
}

/* ----- Mobile tightening --------------------------------------- */

@media (max-width: 540px) {
  .hf-sprint {
    /* Less outer breathing room on phones — every pixel is wanted by the
       title and primary action. */
    padding: 0.75rem;
  }

  .hf-sprint__card {
    border-radius: 1.1rem;
    /* Mobile bumps up the reserved height because the active-task screen
       stacks Done above Skip (see `.hf-sprint__actions` override below),
       which is taller than the desktop inline row. Beat / completion
       screens reuse the same footprint so swapping between states still
       feels like a screen swap rather than a card resize. */
    min-height: min(32rem, 100%);
  }

  .hf-sprint__bar {
    padding: 1.1rem 1rem 0.4rem;
  }

  .hf-sprint__body {
    padding: 0.5rem 1.1rem 2rem;
  }

  .hf-sprint__title {
    font-size: 1.55rem;
  }

  .hf-sprint__timer-clock {
    font-size: 2.4rem;
  }

  .hf-sprint__actions {
    flex-direction: column;
    gap: 0.75rem;
    width: 100%;
  }

  .hf-sprint__primary {
    width: 100%;
    min-width: 0;
  }
}

/* ============================================================
   Seasonal Review (design spec §18 Seasonal Review)
   ------------------------------------------------------------
   Entry banner, focused review queue, end-of-session summary.
   Visual vocabulary mirrors the Someday card stack (same parchment
   surface, stone ink palette, identical card chrome) so the user
   never feels like they're being pulled into a different app — it's
   the same place, just framed as a moment to prune.
   ============================================================ */

/* Contrast rules for this section (see the ink-faint note in the tokens): every
   piece of text a reader acts on uses --ink-muted or darker, never --ink-faint.
   The amber CTA sits on --amber-600 (#926A22, ~4.9:1 under white text); the
   old #BE8E47 fill measured ~2.9:1 and failed AA for its 15px label. */

/* Banner: parchment card with an amber leading stripe (spec §18). The stripe
   is a clipped ::before rather than a border-left, so it stays a straight
   edge instead of following the corner radius. A faint leaf watermark gives
   the ritual its own identity without adding a second element to read. */
.seasonal-banner {
  position: relative;
  display: block;
  margin: 0 0 1.25rem;
  padding: 1rem 1.35rem 1.3rem 1.5rem;
  border-radius: 16px;
  overflow: hidden;
  background: linear-gradient(135deg, #F4EAD6 0%, #F8F1E3 55%, #F2E6CE 100%);
  border: 1px solid rgba(166, 138, 95, 0.28);
  box-shadow: 0 1px 2px rgba(74, 62, 46, 0.05),
    0 10px 28px -16px rgba(146, 106, 34, 0.35);
}

.seasonal-banner::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  background: linear-gradient(180deg, var(--amber-400) 0%, var(--amber-600) 100%);
}

.seasonal-banner::after {
  content: "";
  position: absolute;
  right: -20px;
  bottom: -30px;
  width: 136px;
  height: 136px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23926A22' stroke-width='1.1' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z'/%3E%3Cpath d='M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12'/%3E%3C/svg%3E") center / contain no-repeat;
  opacity: 0.1;
  transform: rotate(-14deg);
  pointer-events: none;
}

.seasonal-banner > * {
  position: relative;
  z-index: 1;
}

.seasonal-banner.is-resume {
  background: linear-gradient(135deg, #EEE2CA 0%, #F4EBDA 55%, #ECDDC2 100%);
}

.seasonal-banner.is-resume::before {
  background: linear-gradient(180deg, var(--amber-500) 0%, var(--stone-600) 100%);
}

/* Eyebrow row: the ritual's name on the left, "Not now" on the right. */
.seasonal-banner-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin: 0 0 0.6rem;
}

.seasonal-banner-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--amber-600);
}

.seasonal-banner-glyph {
  flex-shrink: 0;
}

.seasonal-banner-body {
  display: block;
}

.seasonal-banner-title {
  margin: 0 0 0.3rem;
  font-size: 1.35rem;
  font-weight: 600;
  letter-spacing: -0.012em;
  line-height: 1.25;
  color: var(--ink-primary);
  text-wrap: balance;
}

.seasonal-banner-copy {
  margin: 0 0 1.05rem;
  max-width: 42ch;
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--ink-muted);
  text-wrap: pretty;
}

.seasonal-banner-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

/* Shared by the banner CTA and the summary's Finish. */
.seasonal-banner-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 44px;
  padding: 0.65rem 1.25rem;
  border-radius: 12px;
  border: 1px solid #7E5B1C;
  background: var(--amber-600);
  color: #fff;
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.005em;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16),
    0 1px 2px rgba(110, 77, 30, 0.2),
    0 6px 16px -8px rgba(146, 106, 34, 0.55);
  transition: background-color 150ms ease, box-shadow 150ms ease,
    transform 120ms ease;
}

.seasonal-banner-primary-arrow {
  display: inline-block;
  transition: transform 180ms ease;
}

.seasonal-banner-primary:hover:not(:disabled) {
  background: #7E5B1C;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14),
    0 2px 4px rgba(110, 77, 30, 0.22),
    0 10px 22px -10px rgba(146, 106, 34, 0.6);
  transform: translateY(-1px);
}

.seasonal-banner-primary:hover:not(:disabled) .seasonal-banner-primary-arrow {
  transform: translateX(3px);
}

.seasonal-banner-primary:active:not(:disabled) {
  transform: translateY(0) scale(0.98);
}

.seasonal-banner-primary:focus-visible {
  outline: 2px solid var(--amber-600);
  outline-offset: 3px;
}

.seasonal-banner-primary:disabled {
  cursor: not-allowed;
  opacity: 0.55;
  transform: none;
}

/* "Not now": text-button weight (spec), but a full-size hit area. The negative
   margins give it a ~40px target without pushing the eyebrow row taller. */
.seasonal-banner-dismiss {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  margin: -0.5rem -0.65rem -0.5rem 0;
  padding: 0.55rem 0.7rem;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-muted);
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease;
}

.seasonal-banner-dismiss:hover:not(:disabled) {
  background: rgba(74, 62, 46, 0.06);
  color: var(--ink-primary);
}

.seasonal-banner-dismiss:focus-visible {
  outline: 2px solid var(--stone-500);
  outline-offset: 1px;
}

.seasonal-banner-dismiss:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

/* Review-mode toolbar: Pause on the left, counters on the right, and a
   hairline progress track spanning the row beneath. Counters stay a quiet
   record of work done, not a target to hit. */
.seasonal-review-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem 0.75rem;
  margin: 0 0 0.75rem;
}

.seasonal-review-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: 40px;
  margin-left: -0.6rem;
  padding: 0.45rem 0.85rem 0.45rem 0.6rem;
  border-radius: 999px;
  border: 0;
  background: transparent;
  color: var(--ink-muted);
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease;
}

.seasonal-review-back:hover:not(:disabled) {
  background: rgba(74, 62, 46, 0.06);
  color: var(--ink-primary);
}

.seasonal-review-back:focus-visible {
  outline: 2px solid var(--stone-500);
  outline-offset: 2px;
}

.seasonal-review-back:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

.seasonal-review-counters {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.seasonal-counter {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 0.25rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--stone-200);
  background: var(--surface-base);
  color: var(--ink-muted);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
}

.seasonal-counter-kept {
  border-color: rgba(94, 122, 80, 0.38);
  background: rgba(149, 175, 124, 0.18);
  color: rgb(66, 90, 56);
}

.seasonal-counter-letgo {
  border-color: rgba(166, 138, 95, 0.4);
  background: rgba(217, 200, 168, 0.3);
  color: rgb(106, 84, 50);
}

.seasonal-review-progress {
  flex-basis: 100%;
  height: 4px;
  border-radius: 999px;
  background: rgba(196, 178, 150, 0.4);
  overflow: hidden;
}

.seasonal-review-progress-fill {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--amber-400) 0%, var(--amber-500) 100%);
  transition: width 360ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* The review card: the one thing on the page, so it gets more presence than
   a browsing card: larger title, deeper shadow, and actions pinned to the
   bottom of a steady minimum height so Keep and Let go stay put from one card
   to the next (no hunting for a button that moved with a shorter title). */
.someday-card.seasonal-review-card {
  min-height: 14.5rem;
  gap: 0.7rem;
  padding: 1.6rem 1.6rem 1.45rem;
  border-radius: 20px;
  box-shadow: 0 1px 2px rgba(40, 25, 18, 0.05),
    0 22px 44px -22px rgba(74, 62, 46, 0.32);
}

.seasonal-review-card .someday-card-eyebrow {
  color: var(--ink-muted);
  letter-spacing: 0.16em;
}

.seasonal-review-card .someday-card-title {
  margin-top: 0.4rem;
  font-size: 1.55rem;
  line-height: 1.28;
  letter-spacing: -0.014em;
  overflow-wrap: anywhere;
  text-wrap: balance;
}

.seasonal-review-prompt {
  font-size: 0.95rem;
  font-style: italic;
  color: var(--ink-muted);
}

.someday-card-actions.seasonal-review-actions {
  grid-template-columns: repeat(2, 1fr);
  gap: 0.65rem;
  margin-top: auto;
  padding-top: 0.75rem;
}

.seasonal-review-actions .someday-action-btn {
  min-height: 50px;
  gap: 0.5rem;
  border-radius: 14px;
  font-size: 0.97rem;
  font-weight: 600;
}

.seasonal-review-actions .someday-action-btn:active:not(:disabled) {
  transform: scale(0.97);
}

.seasonal-action-icon {
  flex-shrink: 0;
}

/* Keep: neutral but solid; white lift, primary ink, a sage check that ties it
   to the Kept counter. */
.seasonal-review-actions .someday-action-btn.action-keep {
  background: var(--surface-elev);
  border-color: var(--stone-200);
  color: var(--ink-primary);
  box-shadow: 0 1px 2px rgba(40, 25, 18, 0.06);
}

.seasonal-review-actions .someday-action-btn.action-keep .seasonal-action-icon {
  color: rgb(84, 116, 68);
}

.seasonal-review-actions .someday-action-btn.action-keep:hover:not(:disabled) {
  background: var(--surface-elev);
  border-color: rgba(94, 122, 80, 0.5);
  color: var(--ink-primary);
  box-shadow: 0 6px 16px -8px rgba(74, 62, 46, 0.3);
}

/* Let go: muted (spec), yet still AA. It reads second through its quieter
   surface, not through faint ink. */
.seasonal-review-actions .someday-action-btn.action-letgo {
  background: transparent;
  border-color: var(--stone-200);
  color: var(--ink-muted);
}

.seasonal-review-actions .someday-action-btn.action-letgo:hover:not(:disabled) {
  background: rgba(217, 200, 168, 0.28);
  border-color: rgba(166, 138, 95, 0.45);
  color: rgb(106, 84, 50);
}

/* The stack wrapper only provides centring here; the review never renders
   ghosts, so it needs neither the deck's min-height nor its clearance. */
.someday-stack.seasonal-review-stack {
  min-height: 0;
  padding: 0.25rem 0 0.9rem;
}

/* Fixed-height slot so swapping the hint for the Undo chip never shifts the
   shortcuts legend underneath. */
.seasonal-review-undo {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  margin: 0 0 0.25rem;
}

/* Inline flow, not flex: a long title wraps as one centred sentence with the
   ↶ leading its first line, instead of the glyph floating in a side column. */
.seasonal-review-undo-btn {
  display: inline-block;
  max-width: 100%;
  min-height: 38px;
  padding: 0.5rem 1.05rem;
  border-radius: 1.25rem;
  line-height: 1.4;
  text-wrap: balance;
  border: 1px solid var(--stone-200);
  background: var(--surface-elev);
  color: var(--ink-muted);
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  text-align: center;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(40, 25, 18, 0.05);
  animation: seasonal-undo-in 200ms ease-out;
  transition: background-color 150ms ease, color 150ms ease,
    border-color 150ms ease;
}

.seasonal-review-undo-text {
  margin-left: 0.4rem;
  overflow-wrap: anywhere;
}

.seasonal-review-undo-btn:hover:not(:disabled) {
  border-color: var(--stone-400);
  color: var(--ink-primary);
}

.seasonal-review-undo-btn:focus-visible {
  outline: 2px solid var(--stone-500);
  outline-offset: 2px;
}

.seasonal-review-undo-btn:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

@keyframes seasonal-undo-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.seasonal-review-undo-hint {
  max-width: 48ch;
  font-size: 0.8rem;
  font-style: italic;
  line-height: 1.45;
  color: var(--ink-muted);
  text-align: center;
  text-wrap: balance;
}

/* End-of-session summary: centred, roomier, with a sage glyph so the wrap
   reads as an ending, not another decision. */
.seasonal-review-summary {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.55rem;
  /* width: 100% because .someday-page is a flex column: with auto margins the
     card would otherwise shrink to its content instead of filling to 460px. */
  width: 100%;
  margin: 1.25rem auto 0;
  padding: 2.25rem 2rem 2rem;
  max-width: 460px;
  border-radius: 24px;
  background: radial-gradient(120% 70% at 50% 0%, #F7EEDB 0%, var(--surface-base) 62%);
  border: 1px solid var(--stone-200);
  box-shadow: 0 1px 2px rgba(40, 25, 18, 0.05),
    0 24px 48px -24px rgba(74, 62, 46, 0.32);
  animation: seasonal-summary-in 420ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.seasonal-summary-glyph {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  margin-bottom: 0.45rem;
  border-radius: 50%;
  background: rgba(149, 175, 124, 0.22);
  color: rgb(66, 90, 56);
  box-shadow: 0 0 0 8px rgba(149, 175, 124, 0.09);
}

.seasonal-summary-eyebrow {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--ink-muted);
}

.seasonal-summary-heading {
  margin: 0;
  font-size: 1.6rem;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.015em;
  color: var(--ink-primary);
  text-wrap: balance;
}

.seasonal-summary-body {
  margin: 0;
  max-width: 34ch;
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink-muted);
  text-wrap: pretty;
}

.seasonal-summary-stats {
  display: inline-flex;
  gap: 0.5rem;
  margin: 0.45rem 0 0.25rem;
}

.seasonal-summary-actions {
  display: flex;
  justify-content: center;
  width: 100%;
  margin-top: 0.75rem;
}

.seasonal-summary-actions .seasonal-banner-primary {
  min-width: 12rem;
}

@keyframes seasonal-summary-in {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .seasonal-review-summary,
  .seasonal-review-undo-btn {
    animation: none;
  }

  .seasonal-review-progress-fill,
  .seasonal-banner-primary-arrow {
    transition: none;
  }
}

@media (max-width: 540px) {
  .seasonal-banner {
    padding: 0.95rem 1.05rem 1.1rem 1.25rem;
    border-radius: 14px;
  }

  /* The CTA spans the banner on phones, so the corner watermark would sit
     behind the button label. Drop it there. */
  .seasonal-banner::after {
    display: none;
  }

  .seasonal-banner-title {
    font-size: 1.2rem;
  }

  .seasonal-banner-copy {
    font-size: 0.92rem;
  }

  .seasonal-banner-primary {
    width: 100%;
  }

  .someday-card.seasonal-review-card {
    min-height: 13rem;
    padding: 1.3rem 1.25rem 1.25rem;
    border-radius: 18px;
  }

  .seasonal-review-card .someday-card-title {
    font-size: 1.3rem;
  }

  .seasonal-review-summary {
    padding: 1.9rem 1.25rem 1.5rem;
    border-radius: 20px;
  }

  .seasonal-summary-heading {
    font-size: 1.4rem;
  }

  .seasonal-summary-actions .seasonal-banner-primary {
    min-width: 0;
  }
}

/* "Want a heads-up when it's time to get ready?" (docs/notifications.md, rule 1). Wears the
   energy check's quiet card (.energy-checkin) so the two read as the same kind of question. The
   eyebrow and note use ink-muted, not the energy card's ink-faint, which falls short of WCAG AA
   for text this informative. */
.notify-ask-eyebrow {
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.notify-ask-note {
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--ink-muted);
}

.notify-ask-afterword {
  margin: 0 2rem 1rem 4rem;
  font-size: 0.82rem;
  color: var(--ink-muted);
}

@media (max-width: 640px) {
  .notify-ask-afterword { margin-left: 0.75rem; margin-right: 0.75rem; }
}

/* =========================================================================
   Pick from today (spec §2.1). Opt-in from Profile.

   A searchable list of today's tasks without a time, from Soon and Later,
   with one-tap "To Now" and "Start". It wears the task dialogs' look
   (`.hf-task-dialog`), centred on desktop and as a bottom sheet on phones.
   The content column scrolls; the search band stays pinned above it.
   ========================================================================= */
.hf-pick-dialog .mud-dialog-content {
  padding-top: 0;
  gap: 0.7rem;
}

.hf-pick-search-band {
  position: sticky;
  top: 0;
  z-index: 1;
  margin: 0 -1.5rem;
  padding: 0.9rem 1.5rem 0.5rem;
  background: var(--surface-base);
}

.hf-pick-search {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.75rem;
  padding: 0 0.8rem;
  border: 1px solid var(--stone-300);
  border-radius: 10px;
  background: var(--hf-dialog-lit, #FDFBF6);
  cursor: text;
}

.hf-pick-search:focus-within {
  border-color: var(--stone-500);
  box-shadow: 0 0 0 3px rgba(138, 118, 89, 0.18);
}

.hf-pick-search .hf-pick-search-icon {
  color: var(--ink-muted);
  flex-shrink: 0;
}

.hf-pick-search-input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  padding: 0.5rem 0;
  font: inherit;
  font-size: 0.95rem;
  color: var(--ink-primary);
}

.hf-pick-search-input::placeholder {
  color: var(--ink-muted);
  opacity: 1;
}

.hf-pick-groups {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.hf-pick-group-label {
  margin: 0 0 0.2rem;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.hf-pick-list {
  list-style: none;
  margin: 0 -0.6rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.hf-pick-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.6rem;
  border-radius: 10px;
}

.hf-pick-row:hover {
  background: var(--stone-100);
}

.hf-pick-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

/* Wraps, never ellipsis: the full title is the whole point of the list. */
.hf-pick-title {
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink-primary);
  overflow-wrap: anywhere;
}

/* Neutral on purpose: amber means "live, time passing" (§8). */
.hf-pick-title mark {
  padding: 0 1px;
  border-radius: 3px;
  background: var(--stone-200);
  box-shadow: inset 0 -2px 0 var(--stone-400);
  color: inherit;
}

.hf-pick-meta {
  font-size: 0.74rem;
  color: var(--ink-muted);
}

.hf-pick-actions {
  display: flex;
  gap: 0.3rem;
  flex-shrink: 0;
}

.hf-pick-btn {
  min-height: 2.25rem;
  padding: 0.3rem 0.8rem;
  border: 1px solid var(--stone-300);
  border-radius: 999px;
  background: transparent;
  color: var(--stone-700);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

.hf-pick-btn:hover {
  background: var(--stone-50);
  border-color: var(--stone-400);
}

.hf-pick-btn.is-primary {
  border-color: var(--mud-palette-primary);
  background: var(--mud-palette-primary);
  color: var(--mud-palette-primary-text);
}

.hf-pick-btn.is-primary:hover {
  border-color: var(--mud-palette-primary-darken);
  background: var(--mud-palette-primary-darken);
}

.hf-pick-btn:focus-visible {
  outline: 2px solid var(--timeline-line-now);
  outline-offset: 2px;
}

.hf-pick-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.5rem 0 0.25rem;
  font-size: 0.88rem;
  color: var(--ink-muted);
}

.hf-pick-empty p {
  margin: 0;
}

.hf-pick-keys {
  margin: 0 auto 0 0;
  font-size: 0.74rem;
  color: var(--ink-muted);
}

.hf-pick-keys kbd {
  padding: 0 0.3rem;
  border: 1px solid var(--stone-300);
  border-bottom-width: 2px;
  border-radius: 4px;
  font-family: 'DM Mono', ui-monospace, monospace;
  font-size: 0.7rem;
}

/* The arrow-key selection and the key hint belong to a keyboard and mouse. */
@media (hover: hover) and (pointer: fine) {
  .hf-pick-row.is-selected {
    background: var(--stone-100);
  }
}

@media (hover: none), (pointer: coarse) {
  .hf-pick-keys {
    display: none;
  }

  .hf-pick-btn {
    min-height: 2.75rem;
  }
}

/* Phones: a bottom sheet, the list within thumb reach. */
@media (max-width: 640px) {
  .mud-dialog-container:has(.hf-pick-dialog) {
    align-items: flex-end;
  }

  .mud-dialog.hf-pick-dialog {
    width: 100%;
    max-width: none;
    max-height: 85dvh;
    margin: 0;
    border-bottom: 0;
    border-radius: 18px 18px 0 0;
  }

  .hf-pick-dialog .mud-dialog-content {
    padding-left: 1.1rem;
    padding-right: 1.1rem;
  }

  .hf-pick-dialog .mud-dialog-actions {
    padding-bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
  }

  .hf-pick-search-band {
    margin: 0 -1.1rem;
    padding-left: 1.1rem;
    padding-right: 1.1rem;
  }
}

@media (max-width: 360px) {
  .hf-pick-row {
    flex-wrap: wrap;
    row-gap: 0.4rem;
  }

  .hf-pick-actions {
    width: 100%;
    justify-content: flex-end;
  }
}
