/* ---- Design system ----
   Editorial, flat, near-black. Hierarchy is carried by type size and weight, sections are
   separated by whitespace and hairline rules, never by boxed cards, drop-shadows or rounded
   chrome. Color is reserved strictly for meaning, never decoration: chrome (buttons, toggles,
   focus rings, inputs) is neutral ink on charcoal. Fire (urgent) and Ice (not urgent) carry
   urgency/quadrant state — the pile indicators, the heat-map, the Calendar's pace fills, the
   escalating tags, Focus mode's glow. Other meaning-bearing systems keep their own hues,
   tuned to this base: habit cadence (aqua / violet / magenta), Capacity (plum), importance and
   big wins (gold). The one non-semantic color is each view's quiet accent (--view-accent),
   used only on that view's framing rules and active-tab marks.
   Dark is the default theme; light is the same system on paper. */
:root {
  --font: "Bricolage Grotesque", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Type scale: section labels (window titles, quadrant names, folder/category names) are
     set well above UI text; task titles sit at body size in medium weight; metadata drops
     smaller and quieter. */
  --fs-title: 2.6rem;     /* the app name in the header: always the largest type */
  --fs-display: 2.25rem;  /* Daily Plate / Fridge / Focus */
  --fs-section: 1.6rem;   /* Do / Plan / Clear / Backlog, panel headings, modal titles */
  --fs-group: 1.08rem;    /* folder / category / bucket names */
  --fs-body: 0.95rem;     /* task titles */
  --fs-meta: 0.76rem;     /* dates, context, scores */
  --fs-micro: 0.66rem;    /* tags, uppercase labels */
  --tracking-tight: -0.025em;

  --radius: 2px;           /* the only corner radius: small controls and tags, never panels */
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);

  --bg: #161616;
  --surface: #1e1e1e;      /* raised: modals, inputs, focus panel — never a card background */
  --border: #2b2b2b;       /* hairline */
  --border-strong: #4a4a48; /* section rules, control outlines */
  --text: #f1f0ec;
  --text-muted: #a3a29d;
  --text-faint: #6f6e6a;
  --accent: var(--text);   /* neutral: "accent" is ink, not a hue */
  --accent-hover: #ffffff;
  --on-accent: #161616;    /* text on an ink-filled control */
  /* Tab-style controls (view tabs, mode toggles, category tabs): three distinct levels, so
     state reads from the label's color as well as the underline. */
  --tab-idle: var(--text-faint);
  --tab-hover: var(--text-muted);
  --tab-active: #ffffff;
  /* Color tabs (.color-tab: the view tabs and category tabs): an active tab fills with its own
     color, darkened this much toward black on the dark theme so its near-white label stays
     legible over the lighter dark-theme tints. */
  --tab-fill-strength: 64%;
  --tab-on: #f7f6f2;
  --danger: var(--text);   /* destructive actions stay neutral; the confirm dialog carries the weight */
  --danger-hover: #ffffff;
  --done: #6f6e6a;
  --row-hover: #1d1d1d;
  --panel: #1b1b1b;        /* a window's ground: one shade off the page, for separation only */

  /* Fire / Ice: the only two saturated colors. Fire's red end marks Do and overdue; its
     amber end marks Clear and "due today". Ice marks Plan and the calm, not-urgent side. */
  --fire: #ff5a2c;
  --fire-amber: #ffab35;
  --ice: #5bc4fb;
  --fire-soft: rgba(255, 90, 44, 0.13);
  --fire-amber-soft: rgba(255, 171, 53, 0.13);
  /* The Daily Plate flame's shades, deepest (rear, tallest) to lightest (front, shortest);
     depth is carried by these distinct shades, never opacity. */
  --flame-0: #a3241a;
  --flame-1: #cf3a1d;
  --flame-2: #ff5a2c;
  --flame-3: #ff8030;
  --flame-4: #ffa63a;
  --flame-5: #ffcb70;
  --flame-6: #ffe2a8;
  --flame-glow: rgba(255, 120, 40, 0.75);
  /* The Fridge ice cubes' shades, deepest first — same rules as the flame. */
  --ice-0: #1b4a86;
  --ice-1: #2767ad;
  --ice-2: #3a8cd4;
  --ice-3: #5bc4fb;
  --ice-4: #8ad6fc;
  --ice-5: #bde8fd;
  --ice-6: #e4f6ff;
  --ice-glow: rgba(120, 210, 255, 0.8);

  /* Escalating tag (Do Today < Due Today < Overdue), one slot, most severe wins: neutral
     outline -> amber -> solid fire, so severity reads from saturation as well as wording. */
  --tag-do-today-bg: transparent;
  --tag-do-today-text: var(--text);
  --tag-due-today-bg: var(--fire-amber-soft);
  --tag-due-today-text: var(--fire-amber);
  --tag-overdue-bg: var(--fire);
  --tag-overdue-text: #161616;
  /* Gold = importance: the High/Critical badges and the Calendar's "big win" (a High/Critical
     task completed) share it. A pale, yellower gold than Fire's amber (46° vs 36°, lighter),
     so it never reads as Clear / Due Today. */
  --gold: #f0cd5e;
  --gold-soft: rgba(240, 205, 94, 0.14);
  --gold-glow: rgba(240, 205, 94, 0.5);
  --badge-high-bg: transparent;
  --badge-high-text: var(--gold);
  --badge-critical-bg: var(--gold);
  --badge-critical-text: #161616;
  --badge-recurring-bg: transparent;
  --badge-recurring-text: var(--text-muted);

  /* Calendar pace fills, in Fire/Ice terms: red (something overdue, any importance) is fire;
     deep blue (everything planned got done) is full ice; pale blue (only some of it) is pale
     ice; gray ("nothing was planned", not a low score) stays neutral. Dedicated tokens, tuned
     to read at full-cell size. */
  --day-red-bg: #5b2415;
  --day-red-text: #ffb8a1;
  --day-deep-blue-bg: #12506f;
  --day-deep-blue-text: #c9ecff;
  --day-pale-blue-bg: #1b2e39;
  --day-pale-blue-text: #93cdea;
  --day-gray-bg: #212120;
  --day-gray-text: #8f8e8a;
  /* The big-win overlay, on any base fill: gold (importance), a glow plus a star. */
  --day-win: var(--gold);
  --day-win-glow: var(--gold-glow);

  /* Capacity view: its own plum hue (300°), deliberately none of Pace's fire/ice or the gold —
     volume, not judgment. A continuous dusk -> vivid ramp; the overload marker is the same hue
     pushed brighter, so "busy" can never be mistaken for "missed something important". */
  --capacity-h: 300;
  --capacity-s0: 10%;  --capacity-s1: 42%;
  --capacity-l0: 13%;  --capacity-l1: 34%;
  --capacity-overload: #e98ad9;

  /* Habit cadence hues: a separate system from the quadrants (habits sit outside the
     Eisenhower matrix), so the hue says "daily / weekly / monthly", never a priority. Daily is
     aqua rather than the old sky, since sky is now Ice (Plan). Reused wherever something is
     specifically day- or week-scoped (the Calendar's day repository, Weekly panel, markers). */
  --daily-h: 172;
  --weekly-h: 262;
  --monthly-h: 322;
  --habit-acc-s: 62%;  --habit-acc-l: 70%;   /* rules, titles, fractions, row bars */
  --habit-wash-s: 28%; --habit-wash-l: 12%;  /* faint row ground, like a pale heat-map band */

  /* Per-view accent: quiet, desaturated, used only on the active view's framing (section
     rules, active-tab underline, mode-toggle underline) — never fills, never state. Hues
     picked away from what each view already uses for meaning. */
  --accent-overview: hsl(262 34% 74%);   /* lilac: Overview shows no habits (violet) */
  --accent-list: var(--fire);            /* Daily Plate red: Checklist's main view is Plate */
  --accent-calendar: hsl(34 34% 66%);    /* sand */
  --sage: hsl(148 24% 63%);              /* List mode's own color (the view toggle, app.js) */

  /* Category colors (assigned per category, CATEGORY_COLORS in app.js): a muted set for small
     dots only — identity, not state — kept low-chroma so none reads as Fire, Ice, gold or a
     habit hue even where they sit side by side. */
  --cat-slate: hsl(214 24% 64%);
  --cat-moss: hsl(96 28% 56%);
  --cat-clay: hsl(27 42% 55%);
  --cat-rose: hsl(344 38% 70%);
  --cat-lagoon: hsl(188 30% 56%);
  --cat-lavender: hsl(248 32% 74%);
  --cat-olive: hsl(60 26% 55%);
  --cat-stone: hsl(30 9% 63%);

  /* Staleness check-in tiers: ice (Plan's hue — an undated task quietly aging is exactly
     the "important, not urgent" case), deepening with severity. Wording never escalates. */
  --staleness-low-text: #7fa9bf;
  --staleness-medium-text: #5bc4fb;
  --staleness-high-text: #a9e2ff;

  --overlay: rgba(0, 0, 0, 0.62);
  /* Focus mode dims much harder than a plain modal backdrop — real isolation — and its panel
     carries a soft fire glow: an urgency signal ("this is the urgent stuff"), not decoration. */
  --focus-overlay: rgba(0, 0, 0, 0.84);
  --focus-glow: rgba(255, 90, 44, 0.26);

  /* Heat-map ramp: each quadrant has a fixed hue (see .quadrant-* below); a row's --p
     (0 = pale, 1 = vivid, from priority_score) slides saturation and lightness between
     these endpoints. Backlog uses the same ramp with chroma 0, so lightness alone. On
     charcoal the pale end sits just above the page, so low-priority rows barely tint, and
     the vivid end stays deep (dark fire/amber turns muddy brown if pushed lighter) — the
     accent bar and the hue-colored numerals carry the loud end of the signal. */
  --tint-s0: 14%;  --tint-s1: 52%;   /* row background saturation */
  --tint-l0: 11.5%; --tint-l1: 19%;  /* row background lightness */
  --acc-s0: 28%;   --acc-s1: 100%;   /* accent bar / label saturation */
  --acc-l0: 34%;   --acc-l1: 60%;    /* accent bar / label lightness */
  --gray-l1: 24%;  --gray-acc-l1: 64%;

  color-scheme: dark;
}

:root[data-theme="light"] {
  --bg: #f2f1ed;
  --surface: #fbfaf7;
  --border: #dddbd4;
  --border-strong: #a6a49d;
  --text: #151515;
  --text-muted: #5d5c58;
  --text-faint: #8e8c86;
  --accent-hover: #000000;
  --on-accent: #f2f1ed;
  --tab-active: #000000;
  --tab-fill-strength: 100%;
  --tab-on: #ffffff;
  --danger-hover: #000000;
  --done: #9a9892;
  --row-hover: #eae9e4;
  --panel: #f8f7f3;

  --fire: #d6400e;
  --fire-amber: #b36a00;
  --ice: #0c84c6;
  --fire-soft: rgba(214, 64, 14, 0.1);
  --fire-amber-soft: rgba(179, 106, 0, 0.11);
  --flame-0: #8e1f12;
  --flame-1: #b8311a;
  --flame-2: #d6400e;
  --flame-3: #e8641a;
  --flame-4: #ef8420;
  --flame-5: #f2a338;
  --flame-6: #f5bd55;
  --flame-glow: rgba(230, 90, 20, 0.55);
  --ice-0: #0f3a6e;
  --ice-1: #1a5594;
  --ice-2: #1f6fb5;
  --ice-3: #0c84c6;
  --ice-4: #3fa2da;
  --ice-5: #72bde6;
  --ice-6: #a3d5f0;
  --ice-glow: rgba(12, 132, 198, 0.5);

  --tag-overdue-text: #ffffff;

  --day-red-bg: #f5c6b5;
  --day-red-text: #862808;
  --day-deep-blue-bg: #9dd5f4;
  --day-deep-blue-text: #094a70;
  --day-pale-blue-bg: #dbebf5;
  --day-pale-blue-text: #2b6585;
  --day-gray-bg: #e4e2db;
  --day-gray-text: #6b6a64;

  --gold: #8c6d00;
  --gold-soft: rgba(176, 138, 0, 0.12);
  --gold-glow: rgba(214, 166, 0, 0.55);
  --badge-critical-bg: #e9c24a;
  --badge-critical-text: #1a1400;

  --capacity-s0: 14%;  --capacity-s1: 58%;
  --capacity-l0: 96%;  --capacity-l1: 80%;
  --capacity-overload: #7a1f63;

  --habit-acc-s: 55%;  --habit-acc-l: 38%;
  --habit-wash-s: 45%; --habit-wash-l: 95.5%;

  --accent-overview: hsl(262 26% 48%);
  --accent-list: var(--fire);
  --accent-calendar: hsl(30 28% 38%);
  --sage: hsl(145 20% 34%);

  --cat-slate: hsl(214 26% 45%);
  --cat-moss: hsl(96 30% 38%);
  --cat-clay: hsl(27 46% 38%);
  --cat-rose: hsl(344 38% 50%);
  --cat-lagoon: hsl(188 36% 36%);
  --cat-lavender: hsl(248 28% 56%);
  --cat-olive: hsl(60 32% 34%);
  --cat-stone: hsl(30 8% 48%);

  --staleness-low-text: #5e879d;
  --staleness-medium-text: #0c84c6;
  --staleness-high-text: #07527d;

  --overlay: rgba(20, 20, 18, 0.4);
  --focus-overlay: rgba(15, 15, 13, 0.7);
  --focus-glow: rgba(214, 64, 14, 0.35);

  --tint-s0: 30%;  --tint-s1: 72%;
  --tint-l0: 95.5%; --tint-l1: 84%;
  --acc-s0: 35%;   --acc-s1: 90%;
  --acc-l0: 74%;   --acc-l1: 45%;
  --gray-l1: 80%;  --gray-acc-l1: 42%;

  color-scheme: light;
}

/* The active view's accent (data-view is set on <html> by renderViewSwitch in app.js). */
:root[data-view="overview"] { --view-accent: var(--accent-overview); }
:root[data-view="list"] { --view-accent: var(--accent-list); }
:root[data-view="calendar"] { --view-accent: var(--accent-calendar); }

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font);
  font-optical-sizing: auto;
  font-size: 15px;
  line-height: 1.4;
  background: var(--bg);
  color: var(--text);
  padding-bottom: 4rem;
  -webkit-font-smoothing: antialiased;
}

button, input, select, textarea {
  font-family: inherit;
}

:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

::selection {
  background: var(--text);
  color: var(--on-accent);
}

/* ---- Completion checkbox ----
   One treatment for every completion checkbox in the app (Plate/Fridge cards, list rows,
   subtasks, habits, the Overdue strip, Completed Today). Bigger than the native box, square,
   ink-outlined. Checking one plays the app's single deliberate flourish (.check-anim, added
   by wireCheckAnimation in app.js): the box fills from the center with a slight overshoot,
   then the tick draws left to right; unchecking plays it back in reverse (.uncheck-anim).
   Nothing else in the UI animates this much. */
input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  margin: 0;
  border: 1.5px solid var(--text-muted);
  border-radius: var(--radius);
  background: transparent;
  cursor: pointer;
  display: inline-grid;
  place-content: center;
  vertical-align: middle;
}

input[type="checkbox"]:hover {
  border-color: var(--text);
}

/* Fill: a square that grows from the center (scale 0 -> 1) rather than a color swap. */
input[type="checkbox"]::before {
  content: "";
  position: absolute;
  inset: -1.5px;
  border-radius: var(--radius);
  background: var(--text);
  transform: scale(0);
}

/* Tick: an SVG mask, revealed left to right by clip-path. */
input[type="checkbox"]::after {
  content: "";
  position: relative;
  width: 14px;
  height: 14px;
  background: var(--on-accent);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2.5 7.4 5.6 10.4 11.6 3.8' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='square'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2.5 7.4 5.6 10.4 11.6 3.8' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='square'/%3E%3C/svg%3E") center / contain no-repeat;
  clip-path: inset(0 100% 0 0);
}

input[type="checkbox"]:checked {
  border-color: var(--text);
}

input[type="checkbox"]:checked::before {
  transform: scale(1);
}

input[type="checkbox"]:checked::after {
  clip-path: inset(0 0 0 0);
}

input[type="checkbox"]:disabled {
  opacity: 0.35;
  cursor: default;
}

input[type="checkbox"].check-anim {
  animation: check-pop 380ms var(--ease);
}

input[type="checkbox"].check-anim::before {
  animation: check-fill 220ms var(--ease);
}

input[type="checkbox"].check-anim::after {
  animation: check-draw 240ms 120ms var(--ease) backwards;
}

@keyframes check-pop {
  0% { transform: scale(0.82); }
  45% { transform: scale(1.14); }
  100% { transform: scale(1); }
}

@keyframes check-fill {
  from { transform: scale(0); }
  to { transform: scale(1); }
}

@keyframes check-draw {
  from { clip-path: inset(0 100% 0 0); }
  to { clip-path: inset(0 0 0 0); }
}

/* Unchecking plays the flourish in reverse (.uncheck-anim): the tick erases right to left, then
   the fill shrinks back into the center with a slight give. */
input[type="checkbox"].uncheck-anim {
  animation: uncheck-pop 380ms var(--ease);
}

input[type="checkbox"].uncheck-anim::after {
  animation: uncheck-erase 200ms var(--ease);
}

input[type="checkbox"].uncheck-anim::before {
  animation: uncheck-empty 240ms 120ms var(--ease) backwards;
}

@keyframes uncheck-pop {
  0% { transform: scale(1); }
  55% { transform: scale(0.88); }
  100% { transform: scale(1); }
}

@keyframes uncheck-erase {
  from { clip-path: inset(0 0 0 0); }
  to { clip-path: inset(0 100% 0 0); }
}

@keyframes uncheck-empty {
  from { transform: scale(1); }
  to { transform: scale(0); }
}

@media (prefers-reduced-motion: reduce) {
  input[type="checkbox"].check-anim,
  input[type="checkbox"].check-anim::before,
  input[type="checkbox"].check-anim::after,
  input[type="checkbox"].uncheck-anim,
  input[type="checkbox"].uncheck-anim::before,
  input[type="checkbox"].uncheck-anim::after {
    animation: none;
  }
}

/* ---- Header ---- */
.app-header {
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  padding: 1.1rem 2rem 0;
  position: sticky;
  top: 0;
  z-index: 10;
}

.header-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  margin-bottom: 0.6rem;
}

/* The app's name is the biggest type on the page — a step above the Daily Plate / Fridge
   display headings — paired with the place-setting logo at cap height. */
.app-header h1 {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  margin: 0;
  font-size: var(--fs-title);
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1;
  white-space: nowrap;
}

.app-logo {
  flex: none;
  width: 1.3em;
  height: auto;
  overflow: visible; /* the pieces hop above the drawing's top edge */
}

/* Hovering the logo: fork, plate, then knife each hop up and wiggle (turn one way, swing past
   center, settle) in one motion, about 70ms apart. Each piece turns around its own center, so
   it wiggles in place rather than swinging on an arm. Plays once per hover. */
.app-logo > g {
  transform-box: fill-box;
  transform-origin: center;
}

.app-logo.tap-hover > g {
  animation: logo-hop 0.56s var(--ease);
}

.app-logo.tap-hover > .app-logo-plate { animation-delay: 70ms; }
.app-logo.tap-hover > .app-logo-knife { animation-delay: 140ms; }

@media (hover: hover) {
  .app-logo:hover > g {
    animation: logo-hop 0.56s var(--ease);
  }

  .app-logo:hover > .app-logo-plate { animation-delay: 70ms; }
  .app-logo:hover > .app-logo-knife { animation-delay: 140ms; }
}

@keyframes logo-hop {
  35% { transform: translateY(-3px) rotate(-12deg); }
  65% { transform: translateY(-1px) rotate(7deg); }
}

@media (prefers-reduced-motion: reduce) {
  .app-logo:hover > g,
  .app-logo.tap-hover > g {
    animation: none;
  }
}

.header-actions {
  display: flex;
  gap: 0.15rem;
}

/* Header icon buttons (bulk import, shortcuts, settings, theme): bare icons with a square hit area. */
.theme-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--text-muted);
  border-radius: var(--radius);
  width: 2.2rem;
  height: 2.2rem;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  flex-shrink: 0;
}

.theme-toggle:hover {
  color: var(--text);
  background: var(--row-hover);
}

/* Keyboard shortcuts cheat-sheet */
.shortcuts-list {
  margin: 0 0 1.25rem;
}

.shortcuts-row {
  display: flex;
  align-items: baseline;
  gap: 0.9rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--border);
}

.shortcuts-row:last-child {
  border-bottom: none;
}

.shortcuts-row dt {
  flex-shrink: 0;
  min-width: 5rem;
  display: flex;
  gap: 0.25rem;
}

.shortcuts-row dd {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.88rem;
}

kbd {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0.05rem 0.4rem;
  background: transparent;
  color: var(--text);
}

.storage-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.6rem;
  font-size: var(--fs-meta);
  color: var(--text-faint);
  margin-bottom: 0.6rem;
  min-height: 1.2rem;
}

.storage-bar .link-btn {
  font-size: var(--fs-meta);
}

.storage-status-error {
  color: var(--text);
  font-weight: 700;
}

.gate-text {
  font-size: 0.92rem;
  color: var(--text-muted);
  margin: 0 0 1.25rem;
}

.gate-actions {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* Overdue callout: sticky in the header (via .app-header), in the slot the old Top banner
   held. Independent of the category tabs and of the List display mode. Membership only —
   deadline < today — so it doesn't render at all until something is actually overdue. It
   is an urgency signal, so it's one of the few places Fire appears outside the heat-map. */
.overdue-callout {
  border-top: 1px solid var(--border);
  padding: 0.55rem 0 0.6rem;
}

.overdue-callout:empty {
  display: none;
}

.overdue-callout-header {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: var(--fs-meta);
  color: var(--text-muted);
}

.overdue-callout-label {
  font-weight: 800;
  font-size: 0.85rem;
  letter-spacing: -0.01em;
  color: var(--fire);
}

.overdue-callout-count {
  font-weight: 800;
  font-size: 0.85rem;
  color: var(--fire);
  font-variant-numeric: tabular-nums;
}

.overdue-callout-hint {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.overdue-strip {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  margin-top: 0.45rem;
  padding-bottom: 0.2rem;
}

/* One card per overdue task: a soft fire wash (this strip means one thing), the quadrant
   accent on the left edge so it still ties back to the heat-map. */
.overdue-card {
  --p: 1;
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  flex: 1 1 0;
  min-width: 190px;
  max-width: 280px;
  border-left: 3px solid var(--row-accent, var(--fire));
  padding: 0.5rem 0.7rem 0.5rem 0.6rem;
  background: var(--fire-soft);
  color: var(--text);
}

.overdue-card.done-pending {
  opacity: 0.6;
}

.overdue-card.done-pending .overdue-card-title {
  text-decoration: line-through;
  color: var(--done);
}

.overdue-card-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
  min-width: 0;
  flex: 1;
  text-align: left;
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

.overdue-card-body:hover .overdue-card-title {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.overdue-card-title {
  font-size: 0.88rem;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.overdue-card-meta {
  font-size: 0.72rem;
  color: var(--fire);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* Category tabs: section labels, so bold and larger than UI text; active = ink + underline. */
.folder-tabs {
  display: flex;
  flex-wrap: wrap; /* wraps rather than scrolling sideways, so a color picker isn't clipped */
  align-items: center;
  gap: 0.5rem 0.6rem;
}

.folder-tabs:empty {
  display: none;
}

/* The category "+ Category" add button borrows .window-bucket-add's bucket-list styling, whose
   align-self: flex-start pins it to the top there — wrong here, where it should sit level with
   the tabs beside it. */
.folder-tabs .window-bucket-add {
  align-self: center;
}

/* A category tab's label; its colors and states come from the .color-tab wrap around it. */
.folder-tab {
  border: none;
  background: transparent;
  font-size: var(--fs-group);
  font-weight: 700;
  letter-spacing: -0.01em;
  cursor: pointer;
  white-space: nowrap;
}

/* ---- Category color dots and picker ----
   A category tab is its color dot (a button: click to pick a color) followed by the tab
   itself. The same small dot leads folder names and folder labels elsewhere
   (categoryDotForFolder in app.js), so a folder's category reads at a glance. */
.folder-tab-wrap {
  position: relative;
  gap: 0.2rem;
}

.cat-dot {
  display: inline-block;
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--cat-color);
  margin-right: 0.4em;
  vertical-align: 0.08em;
}

/* The tab's dot is a button: a bigger hit area around the same small dot, ringed on hover. */
.cat-dot-btn {
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  padding: 0;
  border: none;
  background: radial-gradient(circle, var(--cat-color) 0 0.27rem, transparent 0.28rem);
  cursor: pointer;
}

.cat-dot-btn:hover,
.cat-dot-btn[aria-expanded="true"] {
  box-shadow: inset 0 0 0 1px var(--border-strong);
}

.cat-palette {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 30;
  display: grid;
  grid-template-columns: repeat(4, 1.5rem);
  gap: 0.4rem;
  padding: 0.6rem;
  background: var(--surface);
  border: 1px solid var(--border-strong);
}

/* Pops open from its dot (top left) and closes the same way in reverse (openCategoryPaletteFor /
   closeCategoryPalette in app.js). */
.cat-palette-opening {
  transform-origin: top left;
  animation: palette-in 0.2s var(--ease);
}

.cat-palette-closing {
  transform-origin: top left;
  animation: palette-out 0.2s var(--ease) forwards;
  pointer-events: none;
}

@keyframes palette-in {
  from { opacity: 0; transform: translateY(-4px) scale(0.96); }
}

@keyframes palette-out {
  to { opacity: 0; transform: translateY(-4px) scale(0.96); }
}

@media (prefers-reduced-motion: reduce) {
  .cat-palette-opening,
  .cat-palette-closing {
    animation: none;
  }
}

.cat-swatch {
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--cat-color);
  cursor: pointer;
}

.cat-swatch:hover {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 3px var(--text-muted);
}

.cat-swatch[aria-pressed="true"] {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 3.5px var(--text);
}

.app-main {
  max-width: 1180px;
  margin: 0 auto;
  padding: 2rem 2rem;
}

.habits-rail {
  display: none;
}

/* ---- Ultra-wide (>1800px) ----
   Rather than centring the desktop-width column in a sea of empty margin, the content runs
   much wider (still short of edge to edge), and the width that frees up goes to the habit
   boxes: they move out of List mode into a rail of their own (renderHabitsRail in app.js) that
   stays beside Overview and Calendar too. The rail grows open from zero width and shuts the
   same way, e.g. switching into Plate mode, whose Now window already shows today's habits;
   the views column gives up and takes back the room in step, on the same curve. */
@media (min-width: 1800.02px) {
  .app-main {
    max-width: min(88vw, 2200px);
    display: grid;
    grid-template-columns: minmax(0, 1fr) 0px;
    align-items: start;
    transition: grid-template-columns 0.45s var(--ease);
  }

  .app-main.rail-open {
    grid-template-columns: minmax(0, 1fr) calc(280px + 3rem);
  }

  .app-main.rail-instant {
    transition: none;
  }

  .app-views {
    grid-area: 1 / 1;
    min-width: 0;
  }

  .habits-rail {
    display: flex;
    justify-content: flex-end; /* anchored right, so opening reads as sliding in from the edge */
    grid-area: 1 / 2;
    min-width: 0;
    overflow: clip;
    visibility: hidden;
    transition: visibility 0s linear 0.45s; /* stays visible until the rail has shut */
  }

  .rail-open .habits-rail {
    visibility: visible;
    transition-delay: 0s;
  }

  .habits-rail > .recurring-column {
    width: 280px;
    margin-left: 3rem;
  }
}

/* ---- Theme switch (the theme toggle in app.js) ----
   Light and dark crossfade over the whole page instead of cutting, on the signature curve. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.4s;
  animation-timing-function: var(--ease);
}

/* ---- Opening and closing sections (toggleSection / setShownAnimated in app.js) ----
   A section grows open from zero height and fades in; closing is the same in reverse. Its
   caret turns along with it. interpolate-size lets the height run to and from "auto". */
.section-opening,
.section-closing {
  interpolate-size: allow-keywords;
  overflow: clip;
}

.section-opening {
  animation: section-open 0.28s var(--ease);
}

.section-closing {
  animation: section-close 0.28s var(--ease) forwards;
  pointer-events: none;
}

@keyframes section-open {
  from { height: 0; opacity: 0; padding-block: 0; margin-block: 0; }
}

@keyframes section-close {
  to { height: 0; opacity: 0; padding-block: 0; margin-block: 0; }
}

.caret-closing {
  transform: rotate(-90deg) !important; /* momentary, and must beat each caret's own open-state rule */
}

.caret-opening {
  animation: caret-open 0.28s var(--ease);
}

@keyframes caret-open {
  from { transform: rotate(-90deg); }
}

@media (prefers-reduced-motion: reduce) {
  .section-opening,
  .section-closing,
  .caret-opening {
    animation: none;
  }
}

/* ---- Rows arriving, leaving and shifting (leaveThen / playRowMotion in app.js) ----
   A leaving row folds shut (the same close as a fold) before it's removed; rows it or a new
   arrival pushes around glide from their old spot (--flip-x / --flip-y) instead of jumping.
   Arrivals rise in with the row-rise below. */
.row-leaving {
  interpolate-size: allow-keywords;
  overflow: clip;
  pointer-events: none;
  animation: section-close 0.28s var(--ease) forwards;
}

.row-flip {
  animation: row-flip 0.28s var(--ease);
}

@keyframes row-flip {
  from { transform: translate(var(--flip-x, 0), var(--flip-y, 0)); }
}

@media (prefers-reduced-motion: reduce) {
  .row-leaving,
  .row-flip {
    animation: none;
  }
}

/* ---- Row rise (changeWithRowRise in app.js) ----
   A filter or mode change inside a view: the current rows drop a little and fade out
   together, then the new rows fade in and rise into place, one row after another (80ms +
   row × 60ms, set through --rise-i). The fall is the rise in reverse. */
.row-rise {
  animation: row-rise 280ms var(--ease) backwards;
  animation-delay: calc(80ms + var(--rise-i, 0) * 60ms);
}

.row-fall {
  animation: row-fall 160ms var(--ease) forwards;
  pointer-events: none;
}

@keyframes row-rise {
  from { opacity: 0; transform: translateY(12px); }
}

@keyframes row-fall {
  to { opacity: 0; transform: translateY(12px); }
}

@media (prefers-reduced-motion: reduce) {
  .row-rise,
  .row-fall {
    animation: none;
  }
}

/* ---- View switch slide (slideViews in app.js) ----
   For the length of the slide the outgoing and incoming views share one grid cell, the
   outgoing one travels a full width off to one side while the incoming one arrives from the
   other (--slide-dir: 1 = moving right in tab order, -1 = left). A bit longer than the small
   UI motions given how much is moving, same signature curve. The page clips sideways overflow
   meanwhile so no horizontal scrollbar flashes.
   The views column itself clips sideways too, at its own edges: a view only travels its own
   width, so without this its trailing edge would come to rest in the page gutter (and, wider
   up, the empty margin beside the column) and linger there as a ghost strip of the previous
   tab until the slide ended. Clipped, each view passes cleanly out of (and into) the column,
   and never across the ultra-wide habits rail beside it. Sideways only (clip, unlike hidden,
   allows that), so nothing hanging below the views is cut off. */
:root.view-sliding body {
  overflow-x: clip;
}

.app-views.view-sliding {
  display: grid;
  overflow-x: clip;
}

.app-views.view-sliding > * {
  grid-area: 1 / 1;
  min-width: 0;
}

.view-out {
  animation: view-out 0.45s var(--ease) forwards;
  pointer-events: none;
}

.view-in {
  animation: view-in 0.45s var(--ease);
}

@keyframes view-out {
  to { transform: translateX(calc(var(--slide-dir) * -100%)); }
}

@keyframes view-in {
  from { transform: translateX(calc(var(--slide-dir) * 100%)); }
}

/* Below ~900px the large wordmark can't share a row with the tabs and icon buttons: give it
   its own row and put the tabs (left) and icons (right) together on the row beneath, rather
   than letting the icons wrap onto a row of their own. */
@media (max-width: 900px) {
  .app-header h1 {
    flex-basis: 100%;
  }

  #view-switch {
    margin-left: 0;
  }

  .header-actions {
    margin-left: auto;
  }
}

/* Phones: 1rem gutters, tighter tabs and icon buttons, and the title/display sizes stepped
   down a notch. */
@media (max-width: 600px) {
  :root {
    --fs-title: 2.1rem;
    --fs-display: 1.9rem;
    --fs-section: 1.4rem;
  }

  .app-header {
    padding: 0.9rem 1rem 0;
  }

  .app-main {
    padding: 1.5rem 1rem;
  }

  .header-top {
    gap: 0.4rem 1rem;
  }

  #view-switch .view-switch-btn {
    font-size: 0.88rem;
  }

  .theme-toggle {
    width: 1.9rem;
  }
}

/* View toolbars (List here, .overview-toolbar and .calendar-toolbar below): two parts that never
   wrap past each other — everything else in a group that wraps within its own width, and the
   view's display toggle, fixed in the top-right corner, the same spot in every view and at
   every breakpoint. */
.toolbar {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem 1rem;
  margin-bottom: 1.5rem;
}

.toolbar-main {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
}

/* Heat-map key: a swatch per quadrant drawn as its pale -> vivid ramp, label in plain text. */
.heatmap-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 1rem;
  margin-right: auto;
  font-size: var(--fs-meta);
  color: var(--text-muted);
}

.legend-chip {
  --p: 1;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--text);
  font-weight: 600;
}

.legend-chip::before {
  content: "";
  width: 1.6rem;
  height: 0.6rem;
  border-left: 3px solid var(--row-accent);
  background: linear-gradient(90deg, var(--row-bg-pale), var(--row-bg-vivid));
}

.legend-note {
  color: var(--text-faint);
}

/* Flat/Folder (#list-group-mode) and the Plate/List toggle (#list-mode). heatmap-legend's
   margin-right:auto (above) pushes the rest of the toolbar group — Flat/Folder, the sort and
   +Folder — to the right, up against the Plate/List toggle, which sits outside the group in
   the top-right corner (item 41: one fixed screen position, the legend top-left to match). */
.list-mode {
  margin-right: 0;
}

#list-mode {
  flex: none;
}

/* ---- Heat-map colour maths ----
   Hue per quadrant is fixed; --chroma 0 turns Backlog into a pure lightness ramp. The two
   urgent quadrants take Fire's two ends (Do the red, Clear the amber), Plan takes Ice, so
   hue alone answers "is this urgent?" and intensity answers "how much does it matter?". */
.quadrant-q1 { --h: 13;  --chroma: 1; }
.quadrant-q2 { --h: 201; --chroma: 1; }
.quadrant-q3 { --h: 36;  --chroma: 1; }
.quadrant-q4 { --h: 40;  --chroma: 0; }

.quadrant-q1, .quadrant-q2, .quadrant-q3, .quadrant-q4 {
  --tint-l1-eff: var(--tint-l1);
  --acc-l1-eff: var(--acc-l1);
  --row-bg: hsl(
    var(--h)
    calc(var(--chroma) * (var(--tint-s0) + (var(--tint-s1) - var(--tint-s0)) * var(--p)))
    calc(var(--tint-l0) + (var(--tint-l1-eff) - var(--tint-l0)) * var(--p))
  );
  --row-accent: hsl(
    var(--h)
    calc(var(--chroma) * (var(--acc-s0) + (var(--acc-s1) - var(--acc-s0)) * var(--p)))
    calc(var(--acc-l0) + (var(--acc-l1-eff) - var(--acc-l0)) * var(--p))
  );
  --row-bg-pale: hsl(var(--h) calc(var(--chroma) * var(--tint-s0)) var(--tint-l0));
  --row-bg-vivid: hsl(var(--h) calc(var(--chroma) * var(--tint-s1)) var(--tint-l1-eff));
  /* Full-strength hue for labels (quadrant names, scores) regardless of the row's --p. */
  --row-hue: hsl(var(--h) calc(var(--chroma) * var(--acc-s1)) var(--acc-l1-eff));
}

/* Gray has no saturation to lean on, so give its lightness ramp its own endpoints. */
.quadrant-q4 {
  --tint-l1-eff: var(--gray-l1);
  --acc-l1-eff: var(--gray-acc-l1);
}

.main-layout {
  display: flex;
  gap: 3rem;
  align-items: flex-start;
}

.main-column {
  flex: 1;
  min-width: 0;
}

.recurring-column {
  width: 280px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 2.25rem;
}

/* Below the desktop range (tablets and phones) the two columns never share a row: the task
   list comes first at full width, the Habits panel below it at full width too. Task content
   gets space priority at every width, so the stack must stretch both columns, not just the
   habits (align-items: flex-start above would otherwise shrink the task list to its content).
   On a tablet the three cadence boxes sit side by side under the list rather than one long
   column; on a phone they stack. */
@media (max-width: 1023.98px) {
  .main-layout {
    flex-direction: column;
    align-items: stretch;
  }

  .recurring-column {
    width: 100%;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    column-gap: 2rem;
    align-items: start;
  }
}

@media (max-width: 639.98px) {
  .recurring-column {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Habit cadence hue (see --daily-h / --weekly-h / --monthly-h): set per box and per row, so
   everything habit-scoped reads "daily", "weekly" or "monthly" at a glance — a separate
   system from the quadrant hues, never a priority level. */
#recurring-daily, .habit-daily { --habit-h: var(--daily-h); }
#recurring-weekly, .habit-weekly { --habit-h: var(--weekly-h); }
#recurring-monthly, .habit-monthly { --habit-h: var(--monthly-h); }

.recurring-box, .window-habit {
  --habit-color: hsl(var(--habit-h) var(--habit-acc-s) var(--habit-acc-l));
  --habit-wash: hsl(var(--habit-h) var(--habit-wash-s) var(--habit-wash-l));
}

/* Recurring habit boxes (Daily / Weekly / Monthly): the same open section treatment as
   everything else — a rule, a bold label, the fraction as a numeral — with the rule, label
   and fraction in the box's cadence hue. No wash across the box. */
.recurring-box {
  border-top: 2px solid var(--habit-color);
}

.recurring-box-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.85rem 0 0.5rem;
}

.recurring-box-title {
  font-size: 1.3rem;
  font-weight: 800;
  letter-spacing: var(--tracking-tight);
  color: var(--habit-color);
}

.recurring-box-fraction {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--habit-color);
  font-variant-numeric: tabular-nums;
}

.recurring-box-body {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* Folders nested inside a habit box sit a level below the box heading: hairline, not the
   section rule the top-level folder list uses. */
.recurring-box .recurring-folder-section {
  border-top-color: var(--border);
}

.recurring-folder-section .folder-header {
  padding: 0.35rem 0;
}

.recurring-folder-section .folder-name {
  font-size: 0.9rem;
}

.recurring-folder-section .folder-body {
  padding: 0.1rem 0 0.2rem;
  border-top: none;
}

/* ---- Buttons: rectangular, flat. Primary is an ink block; secondary a hairline outline. ---- */
.btn {
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 0.6rem 1.1rem;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
}

.btn-primary {
  background: var(--accent);
  color: var(--on-accent);
}

.btn-primary:hover {
  background: var(--accent-hover);
}

.btn-secondary {
  background: transparent;
  border-color: var(--border-strong);
  color: var(--text);
}

.btn-secondary:hover {
  border-color: var(--text);
}

.btn-danger {
  background: transparent;
  color: var(--danger);
}

.btn-danger:hover {
  color: var(--danger-hover);
}

.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
  color: var(--text-muted);
  padding: 0.25rem 0.4rem;
}

.btn-icon:hover {
  color: var(--text);
}

/* ---- Roll: the hover swap for icons (rollIcon() in app.js) ----
   Two states stacked in a clipped box the size of one: the first sits in view, the second
   waits just below. Hovering (or keyboard-focusing) the button the box sits in slides both up
   one step, so the second state rolls into view. Same signature curve as the rest of the
   app's motion. */
.roll {
  position: relative;
  display: inline-flex;
  overflow: hidden;
  vertical-align: middle;
  line-height: 1;
}

.roll > * {
  display: block;
  flex: none;
  transition: transform 0.32s var(--ease);
}

.roll > :nth-child(2) {
  position: absolute;
  top: 100%;
  left: 0;
}

.tap-hover > .roll > *,
:focus-visible > .roll > * {
  transform: translateY(-100%);
}

@media (hover: hover) {
  :hover > .roll > * {
    transform: translateY(-100%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .roll > * {
    transition: none;
  }
}

/* ---- Smooth scroll (Lenis, see app.js) ----
   The one rule Lenis needs from its stylesheet: the page keeps its natural height while it
   drives the scrolling. */
html.lenis,
html.lenis body {
  height: auto;
}

/* ---- Text roll: the hover swap for clickable text and tab labels (applyTextRoll() in app.js) ----
   The label is a clipped track: the text, with a copy of it (::after, from data-text) stacked
   a small gap below. Hovering or keyboard-focusing the control slides the text up out of the
   track and the copy into its place. The track reaches a little past the line box top and
   bottom (padding, cancelled by a matching negative margin) so ascenders, descenders and
   underlines aren't cut off, and the gap keeps the copy out of sight until it moves. */
.text-roll {
  --roll-gap: 0.35em;
  display: inline-flex; /* unlike inline-block, keeps the text's own baseline despite the clip */
  overflow: hidden;
  padding-block: 0.15em;
  margin-block: -0.15em;
  text-decoration: inherit;
}

.text-roll-face {
  position: relative;
  display: block;
  white-space: nowrap;
  text-align: center;
  text-decoration: inherit;
  transition: transform 0.32s var(--ease);
}

/* An invisible, zero-height copy of the rolled-in text, so the track is as wide as the wider of
   the two labels — a mode toggle rolls to a different word (data-roll-to), which mustn't be cut
   off; for a plain copy it changes nothing. */
.text-roll-face::before {
  content: attr(data-text) / "";
  display: block;
  height: 0;
  visibility: hidden; /* not clipped: a clipped box would drag the label's baseline to its top */
}

.text-roll-face::after {
  content: attr(data-text) / ""; /* decorative copy: screen readers skip it */
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + var(--roll-gap));
  text-decoration: inherit; /* positioned, so it doesn't pick up the label's underline by itself */
}

:is(button, a).tap-hover .text-roll-face,
:is(button, a):focus-visible .text-roll-face {
  transform: translateY(calc(-100% - var(--roll-gap)));
}

@media (hover: hover) {
  :is(button, a):hover .text-roll-face {
    transform: translateY(calc(-100% - var(--roll-gap)));
  }
}

@media (prefers-reduced-motion: reduce) {
  .text-roll-face {
    transition: none;
  }
}

/* Habit status badge (Missed/Skipped, playHabitStatusRoll in app.js): a temporary two-face stack
   standing in for the real badge while it rolls, the whole pill moving as one piece. The track
   (the clipping window) is sized inline to exactly the final badge's box, so the row around it
   is laid out as it will end up; it clips top and bottom only (clip, unlike hidden, allows
   that), letting a wider outgoing badge overhang sideways as it rolls away. Being a clip, not a
   scroll container, it also keeps the badge's own text baseline, so it lines up in a
   baseline-aligned title line exactly where the real badge does. The inner stack is what rolls;
   it keeps its full width even though the track is narrower, right-aligned so the faces share
   the badge's right edge and a wider one overhangs to the left. Rolling out to nothing
   (.habit-status-roll-out) the track has no width and is held at the old badge's left edge, so
   the stack hangs from there instead. */
.habit-status-roll-track {
  display: inline-flex;
  justify-content: flex-end;
  overflow-x: visible;
  overflow-y: clip;
}

.habit-status-roll-out {
  justify-content: flex-start;
}

.habit-status-roll-inner {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  align-self: flex-start; /* both faces tall, not stretched to the track: the roll is -50% */
  flex-shrink: 0;
  transform: translateY(0);
}

.habit-status-rolling {
  overflow-x: clip;
}

/* ---- Shape gestures: icons whose shape suggests its own hover motion ----
   Roll is the fallback; where an icon has an obvious, minimal gesture of its own, it gets
   that instead, on hover or keyboard focus of its button, with the same curve and timing.
   "+" (every add button): a quarter turn. */
.icon-plus {
  transition: transform 0.32s var(--ease);
}

.tap-hover > .icon-plus,
:focus-visible > .icon-plus {
  transform: rotate(90deg);
}

/* Arrows nudge the way they point: previous left, next right, and the bulk-import arrow dips
   into its tray. */
.icon-prev,
.icon-next,
.icon-import-arrow {
  transition: transform 0.32s var(--ease);
}

.tap-hover > .icon-prev,
:focus-visible > .icon-prev {
  transform: translateX(-3px);
}

.tap-hover > .icon-next,
:focus-visible > .icon-next {
  transform: translateX(3px);
}

.tap-hover > .icon-import .icon-import-arrow,
:focus-visible > .icon-import .icon-import-arrow {
  transform: translateY(3px);
}

/* The settings gear turns one tooth. */
.icon-gear {
  transition: transform 0.32s var(--ease);
}

.tap-hover > .icon-gear,
:focus-visible > .icon-gear {
  transform: rotate(45deg);
}

/* Focus mode's reticle "scopes in": the sight pushes in a little and the target at its center
   swells, like zooming through a scope. */
.icon-focus,
.icon-focus-dot {
  transition: transform 0.28s var(--ease);
}

.icon-focus-dot {
  transform-box: fill-box;
  transform-origin: center;
}

.tap-hover > .icon-focus,
:focus-visible > .icon-focus {
  transform: scale(1.18);
}

.tap-hover > .icon-focus .icon-focus-dot,
:focus-visible > .icon-focus .icon-focus-dot {
  transform: scale(1.6);
}

/* Flip (flipIcon() in app.js): an on/off toggle turns over like a card to show its other
   side. Both faces share one grid cell; the back one starts turned away. */
.flip {
  display: inline-grid;
  vertical-align: middle;
  line-height: 1;
  transform: perspective(60px) rotateY(0deg);
  transform-style: preserve-3d;
  transition: transform 0.45s var(--ease);
}

.flip > * {
  grid-area: 1 / 1;
  display: block;
  backface-visibility: hidden;
}

.flip > :nth-child(2) {
  transform: rotateY(180deg);
}

.tap-hover > .flip,
:focus-visible > .flip {
  transform: perspective(60px) rotateY(180deg);
}

/* The mouse versions of the gestures above. Touch-only devices skip these: a tapped control
   would otherwise keep its emulated hover (stuck mid-gesture until the next tap elsewhere); the
   tap plays the gesture through .tap-hover instead (TAP_HOVER_MS in app.js). */
@media (hover: hover) {
  :hover > .icon-plus { transform: rotate(90deg); }
  :hover > .icon-prev { transform: translateX(-3px); }
  :hover > .icon-next { transform: translateX(3px); }
  :hover > .icon-import .icon-import-arrow { transform: translateY(3px); }
  :hover > .icon-gear { transform: rotate(45deg); }
  :hover > .icon-focus { transform: scale(1.18); }
  :hover > .icon-focus .icon-focus-dot { transform: scale(1.6); }
  :hover > .flip { transform: perspective(60px) rotateY(180deg); }
}

@media (prefers-reduced-motion: reduce) {
  .icon-plus,
  .icon-prev,
  .icon-next,
  .icon-import-arrow,
  .icon-gear,
  .icon-focus,
  .icon-focus-dot,
  .flip {
    transition: none;
  }
}

/* Skip action: always visible next to the checkbox (unlike the hover-only edit/delete pair in
   .task-actions), so it reads as an equally-primary alternative to checking off — a round
   outline where the checkbox is a square, so the two can't be confused. Pressed
   (aria-pressed) fills it with ink: the skip is currently in effect. Skipping (or undoing it)
   grows/shrinks that fill from the center — a genuine scale on a ::before circle behind the
   icon, the same mechanism the completion checkbox's own fill uses (its ::before, style.css
   above), not a color fade — paired with the icon pushing once in its own arrow direction.
   Distinct from both the checkbox's tick-draw flourish and a fold. None of .skip-anim /
   .unskip-anim's animated properties carry a plain `transition` anywhere else on these
   elements, so removing the class after it finishes (playSkipAnimation, app.js) can't also
   trigger a reverse animation back — it just snaps to whatever the static aria-pressed rule
   already says, which is the same value the animation already ended on. */
.btn-skip {
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  flex-shrink: 0;
  background: transparent;
  position: relative;
  isolation: isolate;
}

.btn-skip svg {
  width: 11px;
  height: 11px;
  position: relative;
}

.btn-skip::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  background: var(--text);
  transform: scale(0);
  z-index: -1;
}

.btn-skip[aria-pressed="true"] {
  color: var(--on-accent);
  border-color: var(--text);
}

.btn-skip[aria-pressed="true"]::before {
  transform: scale(1);
}

.btn-skip:disabled {
  opacity: 0.35;
  cursor: default;
}

.window-habit-skip {
  flex-shrink: 0;
}

.btn-skip.skip-anim {
  animation: skip-fill-color 220ms var(--ease) forwards;
}

.btn-skip.skip-anim::before {
  animation: skip-fill-scale 220ms var(--ease) forwards;
}

.btn-skip.skip-anim svg {
  animation: skip-push 260ms var(--ease);
}

.btn-skip.unskip-anim {
  animation: skip-unfill-color 220ms var(--ease) forwards;
}

.btn-skip.unskip-anim::before {
  animation: skip-unfill-scale 220ms var(--ease) forwards;
}

.btn-skip.unskip-anim svg {
  animation: skip-push-back 260ms var(--ease);
}

/* Both ends spelled out explicitly (matching check-fill/check-draw above) rather than leaving
   "to" implicit — an implicit end keyframe resolves against the base (unpressed) rule here,
   not the [aria-pressed="true"] override, which left the whole animation sitting at scale(0)
   the entire time and only snapping to scale(1) once the class came back off. */
@keyframes skip-fill-scale {
  from { transform: scale(0); }
  to { transform: scale(1); }
}

@keyframes skip-unfill-scale {
  from { transform: scale(1); }
  to { transform: scale(0); }
}

@keyframes skip-fill-color {
  from { color: var(--text-muted); }
  to { color: var(--on-accent); }
}

@keyframes skip-unfill-color {
  from { color: var(--on-accent); }
  to { color: var(--text-muted); }
}

@keyframes skip-push {
  45% { transform: translateX(3px); }
}

@keyframes skip-push-back {
  45% { transform: translateX(-3px); }
}

@media (prefers-reduced-motion: reduce) {
  .btn-skip.skip-anim,
  .btn-skip.unskip-anim,
  .btn-skip.skip-anim::before,
  .btn-skip.unskip-anim::before,
  .btn-skip.skip-anim svg,
  .btn-skip.unskip-anim svg {
    animation: none;
  }
}

/* ---- Folder list (List mode) ----
   Open sections, not boxes: a rule, the folder name set as a section label, the rows. */
.folder-list {
  display: flex;
  flex-direction: column;
  gap: 2.25rem;
}

.folder-section {
  border-top: 1px solid var(--border-strong);
}

/* Folders directly in the list (a single category tab) or nested one level under a category
   section ("All" tab, item 38): the divider carries the folder's own category color
   (--folder-accent, set inline per section, app.js) rather than a hardcoded green or the view
   accent — a folder with no category yet (or no color assigned) falls back to List mode's own
   color (--mode-accent, renderViewSwitch). */
#folder-list .folder-section {
  border-top-color: var(--folder-accent, var(--mode-accent, var(--border-strong)));
}

.folder-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 0 0.6rem;
  cursor: pointer;
  user-select: none;
}

.folder-caret {
  transition: transform 0.2s var(--ease);
  color: var(--text-faint);
  font-size: 0.7rem;
  width: 0.9rem;
  flex-shrink: 0;
}

.folder-section.collapsed .folder-caret {
  transform: rotate(-90deg);
}

.folder-name {
  font-size: 1.3rem;
  font-weight: 800;
  letter-spacing: var(--tracking-tight);
  flex: 1;
  color: var(--folder-accent, var(--text));
}

.folder-header:hover .folder-name {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

.folder-count {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: var(--fs-meta);
  font-weight: 600;
  font-family: inherit;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  padding: 0.1rem 0.3rem;
  border-radius: var(--radius);
}

.folder-count:hover {
  color: var(--text);
}

.folder-body {
  padding: 0.1rem 0 0.4rem;
}

.folder-section.collapsed .folder-body {
  display: none;
}

/* Category sections ("All" tab, item 38): one level up from a folder, so its name reads a
   touch larger/looser than the folders nested inside it, which get their own indent to read
   as a level down rather than sitting flush with the category name above them. */
.category-name {
  font-size: 1.5rem;
  letter-spacing: var(--tracking-tight);
}

.category-body {
  padding-left: 1.1rem;
}

.category-body > .folder-section:first-child {
  border-top: none;
}

.category-body > .folder-section:first-child .folder-header {
  padding-top: 0.3rem;
}

.empty-hint {
  color: var(--text-faint);
  font-size: 0.85rem;
  padding: 0.5rem 0;
}

.task-list, .subtask-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.subtask-list {
  margin-left: 1.9rem;
  border-left: 1px solid var(--border);
  padding-left: 0.75rem;
}

.task-row {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.6rem 0.5rem 0.6rem 0.55rem;
  border-left: 3px solid transparent;
  margin-bottom: 2px;
}

/* Double-tapping a task row completes it (app.js), so a double-tap here must never be taken
   as the browser's zoom gesture. Scrolling and pinch-zoom still work as normal. */
.task-row,
.window-card,
.window-subtask {
  touch-action: manipulation;
}

/* Swipe right to skip a habit (app.js). Sideways moves on a habit row belong to the swipe;
   the page still scrolls vertically and pinch-zooms from it as normal. While dragging the
   row follows the pointer, its right side vanishing into its resting right edge (an inline
   clip-path, never pulled out past its window), and the Skip button fills with --swipe-p
   (0 -> 1); once full it
   takes the pressed look, and letting go there skips. Letting go glides the row (and any
   partial fill) back on the signature curve. */
.habit-row,
.window-habit {
  touch-action: pan-y pinch-zoom;
}

.habit-swiping {
  position: relative;
  z-index: 1;
  cursor: grabbing;
  user-select: none;
}

.habit-swiping .btn-skip[aria-pressed="false"]::before {
  transform: scale(var(--swipe-p, 0));
}

.habit-swiping .btn-skip {
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease);
}

.swipe-armed .btn-skip {
  color: var(--on-accent);
  border-color: var(--text);
}

.habit-swipe-release {
  transition: transform 0.3s var(--ease), clip-path 0.3s var(--ease);
}

.habit-swipe-release .btn-skip::before {
  transition: transform 0.3s var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  .habit-swipe-release,
  .habit-swipe-release .btn-skip::before {
    transition: none;
  }
}

.task-row:hover {
  background: var(--row-hover);
}

/* Heat-map: each active row is tinted by its live quadrant hue at its priority intensity. */
.task-row[class*="quadrant-"] {
  background: var(--row-bg);
  border-left-color: var(--row-accent);
}

.task-row[class*="quadrant-"]:hover {
  box-shadow: inset 0 0 0 100vmax rgba(255, 255, 255, 0.035);
}

:root[data-theme="light"] .task-row[class*="quadrant-"]:hover {
  box-shadow: inset 0 0 0 100vmax rgba(0, 0, 0, 0.035);
}

.task-row input[type="checkbox"] {
  margin-top: 0.05rem;
}

.task-row .btn-skip {
  margin-top: 0;
}

.task-caret {
  background: none;
  border: none;
  padding: 0;
  margin-top: 0.3rem;
  width: 0.85rem;
  flex-shrink: 0;
  color: var(--text-faint);
  font-size: 0.66rem;
  cursor: pointer;
  transition: transform 0.2s var(--ease);
}

.task-caret:hover {
  color: var(--text);
}

.task-caret.collapsed {
  transform: rotate(-90deg);
}

.task-caret-spacer {
  width: 0.85rem;
  flex-shrink: 0;
}

.subtask-container.collapsed {
  display: none;
}

.task-main {
  flex: 1;
  min-width: 0;
}

.task-title-line {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  flex-wrap: wrap;
}

.task-title {
  font-size: var(--fs-body);
  font-weight: 500;
}

.task-row.done .task-title {
  text-decoration: line-through;
  color: var(--done);
}

/* Tags and badges: small uppercase labels on a 2px corner, never pills. */
.task-badge {
  font-size: var(--fs-micro);
  line-height: 1.5;
  padding: 0 0.4rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
}

/* Escalating tag: the same badge wherever a task appears (list rows, Now/Later cards, the
   priority panel, the quadrant list). One slot: Overdue beats Due Today beats Do Today. */
.tag-badge {
  white-space: nowrap;
  flex-shrink: 0;
}

.tag-do-today {
  background: var(--tag-do-today-bg);
  color: var(--tag-do-today-text);
  border-color: var(--border-strong);
}

.tag-due-today {
  background: var(--tag-due-today-bg);
  color: var(--tag-due-today-text);
}

.tag-overdue {
  background: var(--tag-overdue-bg);
  color: var(--tag-overdue-text);
}

/* Stacked with the priority score, not inside the ellipsised title/meta lines (both the
   priority panel and the quadrant list truncate long titles with white-space: nowrap), so a
   long task name can never clip the tag off. See .priority-card-score-col / .matrix-item-score-col. */

/* Importance badges: gold, the importance hue (shared with the Calendar's big-win glow). High
   is a gold outline, Critical solid gold — importance, not urgency, so never fire. */
.badge-high {
  background: var(--badge-high-bg);
  color: var(--badge-high-text);
  border-color: var(--gold);
}

.badge-critical {
  background: var(--badge-critical-bg);
  color: var(--badge-critical-text);
}

.badge-recurring {
  background: var(--badge-recurring-bg);
  color: var(--badge-recurring-text);
  border-color: var(--border);
}

.task-urgency-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.3rem;
}

/* Label takes the quadrant's full-strength hue — it names the state, so it can carry it. */
.quadrant-label {
  font-weight: 700;
  color: var(--row-hue, var(--text-muted));
}

.subtask-progress {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.35rem;
}

.subtask-progress-bar {
  width: 72px;
  height: 3px;
  background: var(--border-strong);
  overflow: hidden;
  flex-shrink: 0;
}

.subtask-progress-fill {
  height: 100%;
  background: var(--text);
  transition: width 0.3s var(--ease);
}

.task-meta {
  font-size: var(--fs-meta);
  color: var(--text-muted);
  margin-top: 0.15rem;
}

/* Flat grouping only (renderTaskRow's showFolderContext): the folder name a task's own header
   would otherwise have named, same dot the folder header itself uses. */
.task-folder-context {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: var(--fs-meta);
  color: var(--text-muted);
  margin-bottom: 0.15rem;
}

.task-notes {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin-top: 0.2rem;
  white-space: pre-wrap;
}

/* Hover-only actions appear instantly — no fade on every hover. */
.task-actions {
  display: flex;
  gap: 0.15rem;
  opacity: 0;
}

/* Child combinator: subtask rows nest inside their parent's row, so a descendant selector
   would reveal every nested row's actions at once. */
.task-row:hover > .task-actions,
.task-row:focus-within > .task-actions {
  opacity: 1;
}

/* Bare "+" add-subtask, same affordance the Daily Plate/Fridge cards use (.window-card-add-subtask):
   the first icon in the hover-revealed actions cluster, so a compact task doesn't carry a
   text link — or an empty line reserved for one — by default. */
.add-subtask-row {
  font-size: 1.05rem;
  font-weight: 600;
  line-height: 1;
  padding: 0.1rem 0.35rem;
  text-decoration: none;
  color: var(--text-muted);
}

.add-subtask-row:hover {
  color: var(--text);
}

.link-btn {
  background: none;
  border: none;
  color: var(--text);
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  padding: 0.25rem 0;
  text-decoration: underline;
  text-decoration-color: var(--border-strong);
  text-underline-offset: 3px;
}

.link-btn:hover {
  text-decoration-color: var(--text);
}

/* ---- Daily Plate / Fridge windows ----
   Two open columns side by side above the folder list, equal width by default; "Expand" on
   either one gives it two thirds (the column change animates — it answers the click). Cards
   inside carry the same quadrant-hue heat-map as list rows (a Plate task is Do or Clear
   thanks to the do_date floor; a Fridge task is Plan or Backlog), so the windows don't
   invent any colour of their own. */
.windows-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 2.75rem;
  align-items: start;
  margin-bottom: 2.5rem;
  transition: grid-template-columns 0.28s var(--ease);
}

.windows-row.focus-now { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
.windows-row.focus-later { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }

@media (max-width: 700px) {
  .windows-row,
  .windows-row.focus-now,
  .windows-row.focus-later {
    grid-template-columns: minmax(0, 1fr);
  }
}

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

/* Non-interactive space anywhere in the panel doubles as a big expand/shrink hit target (see
   wireWindowExpandClick in windows.js) — cursor:pointer signals that everywhere, and cards,
   buttons, inputs and selects all set their own cursor anyway so this never fights them.
   Separated from the page by a ground one shade lighter (--panel) and a hairline edge, with
   the heavy rule on top — separation, not a card: square, flat, no shadow. */
.window-panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-top: 2px solid var(--view-accent, var(--text));
  padding: 1rem 1.1rem 1.1rem;
  min-width: 0;
  cursor: pointer;
}

/* Drop-zone highlight while a task is being dragged over it (also used on .folder-list). */
.drop-target {
  outline: 1.5px dashed var(--text-muted);
  outline-offset: 4px;
}

.dragging {
  opacity: 0.4;
}

.window-header {
  display: flex;
  align-items: center;
  gap: 0.35rem 0.6rem;
  flex-wrap: wrap;
  margin-bottom: 0.9rem;
}

.window-title {
  margin: 0;
  font-size: var(--fs-display);
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1;
}

/* A plain count numeral (focus mode and anywhere without a pile indicator). */
.window-count {
  font-size: 1rem;
  font-weight: 800;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.window-hint {
  font-size: var(--fs-meta);
  color: var(--text-faint);
  flex: 1 1 8rem;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.window-focus-btn {
  padding: 0.1rem;
  margin-left: -0.2rem;
}

/* Plate / Fridge icon doubling as the expand/shrink control: a neutral line icon, its own
   content (food on the plate, the door open) shows the state; once expanded it inverts to
   an ink block — the same on/off-through-appearance language as the Bite-size toggle. */
.window-focus-icon-now,
.window-focus-icon-later {
  color: var(--window-color, var(--text));
}

.window-focus-icon-now.active,
.window-focus-icon-later.active {
  color: var(--on-accent);
  background: var(--window-color, var(--text));
}

/* Each window's element color — fire for Daily Plate, ice for Fridge — on its three identity
   spots: the frame (heavy top rule, hairline tinted toward the hue), the plate/fridge icon,
   and the title. The cards inside still carry the real quadrant heat-map. */
.window-now { --window-color: var(--fire); }
.window-later { --window-color: var(--ice); }

.window-panel.window-now,
.window-panel.window-later {
  border-color: color-mix(in srgb, var(--window-color) 30%, var(--border));
  border-top-color: var(--window-color);
}

.window-now .window-title,
.window-later .window-title {
  color: var(--window-color);
}

/* ---- Pile-size indicator: fire (Daily Plate) / ice (Fridge) ----
   The count numeral in the window's element color with a staged pile icon beside it
   (renderPileIcon in windows.js): a flame for Daily Plate, a stack of ice cubes for Fridge.
   Static at rest; hovering swells it up from its base, and adding a task "stokes" it — the
   same swell plus a glow, for about a second. */
.pile-indicator {
  display: inline-flex;
  align-items: flex-end;
  gap: 0.4rem;
  align-self: center;
}

.pile-indicator .window-count {
  font-size: 1.35rem;
  line-height: 0.9;
}

.pile-fire .window-count { color: var(--fire); }
.pile-ice .window-count { color: var(--ice); }

.pile-indicator.pile-empty .window-count {
  color: var(--text-faint);
}

/* The icon sits on the count's baseline: its box has a little headroom around the shapes (see
   PILE_BOX), pulled back with negative margins so the shapes' base lines up. */
.pile-icon-holder {
  display: inline-flex;
  margin: -6px 0 -2px -4px;
}

.pile-icon {
  display: block;
  /* Scale from the shapes' base line (y 34 of the 36-unit box), so the pile grows upward and
     outward from where it sits rather than from its middle. */
  transform-origin: 50% 94.4%;
  transition: transform 0.32s var(--ease), filter 0.32s var(--ease);
}

/* Hover the indicator: the pile swells a little. Adding a task (.pile-stoked, about a second)
   does the same plus a glow — the flame flares, the ice frosts. */
.pile-indicator.tap-hover .pile-icon,
.pile-stoked .pile-icon {
  transform: scale(1.14);
}

@media (hover: hover) {
  .pile-indicator:hover .pile-icon {
    transform: scale(1.14);
  }
}

.pile-fire .pile-stoked .pile-icon { filter: drop-shadow(0 0 4px var(--flame-glow)); }
.pile-ice .pile-stoked .pile-icon { filter: drop-shadow(0 0 4px var(--ice-glow)); }

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

.window-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.55rem;
}

/* Sort dropdown: five options is too many for a segmented switch. Underlined text select,
   level with the grouping switch beside it. */
.window-sort-select {
  font: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text);
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border-strong);
  border-radius: 0;
  padding: 0.25rem 0.1rem;
  cursor: pointer;
}

.window-sort-select:hover {
  border-bottom-color: var(--text);
}

.window-sort-select option {
  background: var(--surface);
  color: var(--text);
}

/* Bucket / folder group label inside a window: a section label in its own right (a folder
   name, or Do/Plan/Clear/Backlog in the quadrant sort), set bold at group size with the
   count as a quiet numeral beside it, generous space above. */
.window-bucket-header {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: var(--fs-group);
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text);
  border-top: 1px solid var(--border);
  padding-top: 0.8rem;
  margin-top: 1.1rem;
  margin-bottom: 0.15rem;
}

.window-bucket-header-first {
  border-top: none;
  padding-top: 0;
  margin-top: 0.1rem;
}

.window-bucket-label {
  min-width: 0;
}

.window-bucket-count {
  font-size: var(--fs-meta);
  font-weight: 700;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

/* Bottom-left "+", below the last card (or the empty hint) in the bucket. Same square
   hairline treatment as the global add icon up in the controls row, so the two read as the
   same kind of control. */
.window-bucket-add {
  align-self: flex-start;
  padding: 0.2rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: transparent;
}

.window-bucket-add:hover {
  border-color: var(--text);
}

/* Category/folder add: same "+" icon, but with a word after it naming what it adds, so it
   doesn't get lost among the several other plain "+" icons on the same page. */
.window-bucket-add-labeled {
  gap: 0.3rem;
  padding: 0.3rem 0.7rem 0.3rem 0.4rem;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--text);
}

/* Buckets: each a drop target that makes a dropped card belong there, kept droppable even
   when empty. */
.window-bucket {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-height: 2.4rem;
}

/* Folder subgroup within a bucket ("Folder" grouping, item 42): collapsible the same way the
   folder list's own sections are, same shared state — so it needs its own gap between cards
   (a plain wrapper div, not itself a flex child sharing .window-bucket's gap) and its own
   caret-rotate/hide-body rule, matching .folder-section's pattern in the folder list. */
.window-folder-body {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.window-folder-header {
  cursor: pointer;
  user-select: none;
}

.window-folder-header .folder-caret {
  font-size: 0.65rem;
}

.window-folder-group.collapsed .window-folder-body {
  display: none;
}

.window-folder-group.collapsed .folder-caret {
  transform: rotate(-90deg);
}

.window-zone-empty {
  padding: 0.35rem 0;
  font-size: 0.8rem;
}

.window-body {
  display: flex;
  flex-direction: column;
  gap: 3px;
  max-height: max(440px, 64vh); /* the windows are the whole page in "windows" mode */
  overflow-y: auto;
  overflow-x: hidden;
  padding-right: 4px;
}

/* Buckets, cards and folder headers are flex items of the scrolling column above; without
   this they shrink below their content height once it exceeds max-height, and the squeezed
   boxes let later items overlap the still full-size content above them instead of the column
   just scrolling. */
.window-body > * {
  flex-shrink: 0;
}

.window-empty {
  padding: 0.6rem 0;
}

/* Task card: a flat heat-map row — accent stripe, tinted ground, square corners — draggable.
   One line of flex items (caret, checkbox, body); the body takes all remaining width, since
   the hover actions overlay the card rather than sitting beside the title. */
.window-card {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.65rem 0.7rem 0.65rem 0.6rem;
  border-left: 3px solid var(--row-accent);
  background: var(--row-bg);
  cursor: grab;
}

.window-card:active {
  cursor: grabbing;
}

.window-card-body {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.window-card-title-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.25rem 0.45rem;
}

.window-card-title {
  font-size: var(--fs-body);
  font-weight: 500;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

/* Inline do_date / deadline inputs, expanded cards only. */
.window-card-dates {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.9rem;
  margin-top: 0.25rem;
}

.window-card-date {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

.window-card-date input[type="date"] {
  font: inherit;
  font-size: 0.74rem;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border-strong);
  border-radius: 0;
  padding: 0.05rem 0.1rem;
  cursor: text;
}

.window-card-meta {
  font-size: var(--fs-meta);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Hover-only actions ("+", edit, delete): an overlay pinned to the card's top-right on the
   card's own ground color, shown on hover/focus, so they never reserve width beside the title
   or a row of their own. (The Bite-size chip, always visible, lives with the tags instead.) */
.window-card {
  position: relative;
}

.window-card-actions,
.window-subtask-actions {
  position: absolute;
  display: flex;
  align-items: center;
  gap: 0.1rem;
  opacity: 0;
  pointer-events: none;
}

.window-card-actions {
  top: 0.45rem;
  right: 0.4rem;
  padding-left: 0.4rem;
  background: var(--row-bg, var(--bg));
  box-shadow: -10px 0 8px -4px var(--row-bg, var(--bg));
}

.window-card:hover > .window-card-actions,
.window-card:focus-within > .window-card-actions,
.window-subtask:hover > .window-subtask-head > .window-subtask-actions,
.window-subtask-head:focus-within > .window-subtask-actions {
  opacity: 1;
  pointer-events: auto;
}

.window-subtask-head {
  position: relative;
}

.window-subtask-actions {
  top: -0.1rem;
  right: 0;
  padding-left: 0.3rem;
  background: var(--row-bg, var(--bg));
}

/* Sizing chip: always reads "Bite-size" — on (ink-filled) vs. off (outlined) shows through
   .active, never through the text. A toggle, not a state signal, so it stays neutral. */
.window-chip {
  font: inherit;
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  line-height: 1.5;
  padding: 0 0.45rem;
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
}

.window-chip:hover {
  border-color: var(--text);
  color: var(--text);
}

.window-chip.active {
  background: var(--text);
  border-color: var(--text);
  color: var(--on-accent);
}

/* Quick wins are the "knock it out" pile: a slimmer card, no meta line, lighter title. */
.window-card-quick {
  padding: 0.45rem 0.7rem 0.45rem 0.6rem;
  gap: 0.6rem;
}

.window-card-quick .window-card-title {
  font-size: 0.88rem;
  font-weight: 400;
}

/* Bite-size cards never show the meta line, expanded or not — small by definition. */
.window-card-quick .window-card-meta {
  display: none;
}

/* ---- Nested subtasks on cards ----
   Same caret and collapsed set as the folder list. Rows sit inside the card body, each with
   its own quadrant accent (active) or struck through (done), further children indented. */
.window-card-caret {
  margin-top: 0.3rem;
  margin-right: -0.2rem;
}

.window-card-subtasks {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 0.45rem;
  padding-left: 0.1rem;
}

.window-subtask {
  border-left: 2px solid var(--border-strong);
  padding: 0.3rem 0.3rem 0.3rem 0.5rem;
}

.window-subtask[class*="quadrant-"] {
  border-left-color: var(--row-accent);
  background: color-mix(in srgb, var(--row-bg) 60%, transparent);
}

.window-subtask.done {
  cursor: default;
}

.window-subtask-head {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
}

.window-subtask-head > .task-caret {
  margin-top: 0.3rem;
}

.window-subtask-title-line {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.15rem 0.4rem;
  min-height: 20px;
}

.window-subtask-title-line .subtask-progress {
  margin-top: 0;
}

.window-subtask-title {
  font-size: 0.86rem;
  font-weight: 500;
  overflow-wrap: anywhere;
}

.window-subtask.done .window-subtask-title {
  text-decoration: line-through;
  color: var(--done);
}

.window-subtask-edit {
  padding: 0.1rem;
}

.window-subtask .window-card-subtasks {
  margin-left: 1.3rem;
}

/* "+" add-subtask: same affordance the folder list has always had, now on cards too — a bare
   symbol in the hover-revealed actions overlay beside the pencil (the card's, or the subtask
   row's own), so it never reserves a line of its own. The overlays' child-combinator hover
   rules above mean hovering a card reveals its own "+", not every nested subtask's too. */
.window-card-add-subtask {
  font-size: 1.05rem;
  font-weight: 600;
  line-height: 1;
  padding: 0.1rem 0.3rem;
  text-decoration: none;
  color: var(--text-muted);
}

.window-card-add-subtask:hover {
  color: var(--text);
}

/* ---- Habits in Daily Plate ----
   Schedule-filtered view of the Daily/Weekly/Monthly boxes, under the task cards. Outside the
   Eisenhower system, so no heat-map: each row takes its cadence hue instead (bar, faint
   ground, meta), the same flat band shape as a task card so the two read as siblings. */
.window-habits {
  margin-top: 1.4rem;
  border-top: 1px solid var(--border);
  padding-top: 0.8rem;
}

.window-habits-header {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}

.window-habits-title {
  font-size: var(--fs-group);
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text);
}

.window-habits-fraction {
  font-size: var(--fs-meta);
  font-weight: 700;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.window-habits-list {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* The "+" sits after the list as a plain sibling, not one of its flex items, so it doesn't
   inherit the list's own 3px row gap — added back here to match every other add icon's
   distance from the last card above it. */
.window-habits > .window-bucket-add {
  margin-top: 3px;
}

.window-habit {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem 0.6rem;
  padding: 0.5rem 0.7rem 0.5rem 0.6rem;
  border-left: 3px solid var(--habit-color);
  background: var(--habit-wash);
  cursor: pointer;
}

.window-habit-title {
  font-size: 0.9rem;
  font-weight: 500;
  flex: 1 1 7rem;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.window-habit.done .window-habit-title {
  text-decoration: line-through;
  color: var(--done);
}

.window-habit-meta {
  font-size: 0.72rem;
  color: var(--habit-color, var(--text-faint));
  font-weight: 600;
  white-space: nowrap;
  margin-left: auto; /* drops under the title, right-aligned, when the column is narrow */
}

/* Empty-Plate suggestion: a live-checked highlight (never a stored/dismissible flag) around
   the top 3 Fridge tasks by priority, shown whenever the Plate has nothing planned.
   Disappears the instant anything lands in the Plate. */
.window-suggestion {
  border: 1px dashed var(--border-strong);
  padding: 0.75rem 0.75rem 0.7rem;
  margin-bottom: 1rem;
}

.window-suggestion-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.55rem;
}

.window-suggestion-label {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--text);
}

.window-suggestion-add-all {
  padding: 0.3rem 0.7rem;
  font-size: 0.78rem;
  flex-shrink: 0;
  white-space: nowrap;
}

.window-suggestion-cards {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* "Became Do Today" sub-list inside the digest. */
.digest-subheading {
  font-size: var(--fs-group);
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 1.4rem 0 0.4rem;
}

/* ---- Focus mode ----
   On-demand overlay over the page: only the Plate's do_date == today subset, expanded cards.
   The heavily dimmed backdrop (the overlay itself) is the deprioritize drop target; the panel
   isn't. The panel carries a fire rule and a soft fire glow — the one glow in the app, and an
   urgency signal ("this is the urgent stuff"), the same category as the pile indicators. */
.focus-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--focus-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2vh 1rem;
  overflow-y: auto;
}

.focus-overlay.hidden {
  display: none;
}

.focus-overlay.drop-target {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--text-muted);
}

.focus-panel {
  width: 100%;
  max-width: 600px;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  border-top: 2px solid var(--fire);
  padding: 1.25rem 1.5rem 1.5rem;
  box-shadow: 0 0 40px 2px var(--focus-glow);
}

/* Opening Focus mode (openFocusMode in windows.js): the panel zooms in from 85% as if through a
   scope while the backdrop dims in behind it. Closing (.scope-out) plays the same motion in
   reverse — the panel pulls back out and fades, the dim lifts — then the overlay hides.
   Transition-only: nothing lingers afterwards. */
.focus-overlay.scope-in {
  animation: scope-dim 0.52s var(--ease);
}

.focus-overlay.scope-in .focus-panel {
  animation: scope-zoom 0.52s var(--ease);
}

.focus-overlay.scope-out {
  animation: scope-undim 0.52s var(--ease) forwards;
  pointer-events: none; /* already on its way out */
}

.focus-overlay.scope-out .focus-panel {
  animation: scope-zoom-out 0.52s var(--ease) forwards;
}

@keyframes scope-dim {
  from { background-color: transparent; }
}

@keyframes scope-zoom {
  from { transform: scale(0.85); opacity: 0; }
  40% { opacity: 1; }
}

/* The exit keyframes mirror the entry ones rather than playing them backwards, so the exit
   keeps the same fast-start curve and answers the click at once. */
@keyframes scope-undim {
  to { background-color: transparent; }
}

@keyframes scope-zoom-out {
  to { transform: scale(0.85); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .focus-overlay.scope-in,
  .focus-overlay.scope-in .focus-panel,
  .focus-overlay.scope-out,
  .focus-overlay.scope-out .focus-panel {
    animation: none;
  }
}

.focus-header {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.focus-body {
  max-height: none;
  flex: 1;
  overflow-y: auto;
}

/* Deadline backfill modal rows */
.backfill-list {
  display: flex;
  flex-direction: column;
  margin-bottom: 1rem;
  border-top: 1px solid var(--border);
}

.modal-content form .backfill-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin: 0;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--border);
  color: var(--text);
}

.backfill-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.backfill-title {
  font-size: var(--fs-body);
  font-weight: 500;
}

.backfill-meta {
  font-size: var(--fs-meta);
  color: var(--text-muted);
}

.modal-content .backfill-row input[type="date"] {
  width: auto;
  margin-top: 0;
}

/* ---- Modal ----
   A flat raised sheet: square corners, hairline edge, a heavy ink rule on top like every
   other section, the title set large. */
.modal {
  position: fixed;
  inset: 0;
  background: var(--overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 1rem;
}

.modal.hidden {
  display: none;
}

/* Opening and closing (showModal/hideModal in app.js): the backdrop dims in as the sheet rises
   a little and settles; closing is the same motion in reverse. A closing pop-up is already
   "hidden" as far as the app is concerned, so it stays on screen only for its exit, and takes
   no clicks meanwhile. */
.modal.modal-closing {
  display: flex;
  pointer-events: none;
  animation: modal-undim 0.28s var(--ease) forwards;
}

.modal.modal-opening {
  animation: modal-dim 0.28s var(--ease);
}

.modal.modal-opening .modal-content {
  animation: modal-sheet-in 0.28s var(--ease);
}

.modal.modal-closing .modal-content {
  animation: modal-sheet-out 0.28s var(--ease) forwards;
}

@keyframes modal-dim {
  from { background-color: transparent; }
}

@keyframes modal-undim {
  to { background-color: transparent; }
}

@keyframes modal-sheet-in {
  from { opacity: 0; transform: translateY(14px) scale(0.98); }
}

@keyframes modal-sheet-out {
  to { opacity: 0; transform: translateY(14px) scale(0.98); }
}

@media (prefers-reduced-motion: reduce) {
  .modal.modal-opening,
  .modal.modal-opening .modal-content,
  .modal.modal-closing,
  .modal.modal-closing .modal-content {
    animation: none;
  }

  .modal.modal-closing {
    display: none;
  }
}

.modal-content {
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 2px solid var(--text);
  padding: 1.6rem 1.9rem 1.9rem;
  width: 100%;
  max-width: 480px;
  max-height: 90vh;
  overflow-y: auto;
}

.modal-content h2 {
  margin: 0 0 1.25rem;
  font-size: var(--fs-section);
  font-weight: 800;
  letter-spacing: var(--tracking-tight);
  line-height: 1.1;
}

.modal-content form label {
  display: block;
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 1.1rem;
}

.modal-content form label[hidden] {
  display: none;
}

.modal-content-wide {
  max-width: 600px;
}

.settings-help {
  font-size: 0.9rem;
  color: var(--text-muted);
  margin: 0 0 1.5rem;
}

.settings-group {
  border: none;
  border-top: 1px solid var(--border);
  padding: 1rem 0 0.2rem;
  margin: 0 0 0.6rem;
}

.settings-group legend {
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text);
  padding: 0 0.6rem 0 0;
}

.settings-hint {
  font-size: 0.78rem;
  color: var(--text-faint);
  margin: -0.4rem 0 0.9rem;
}

/* Validation errors: neutral ink, bold, a rule on the left — not an urgency signal, so no
   fire. */
.settings-error {
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 700;
  min-height: 1.2rem;
  margin: 0 0 0.25rem;
}

.settings-error:not(:empty) {
  border-left: 2px solid var(--text);
  padding-left: 0.6rem;
}

.settings-reset {
  margin-right: auto;
}

.settings-hint code,
.bulk-import-rule code {
  font-family: var(--font-mono);
  color: var(--text);
  background: var(--row-hover);
  border-radius: var(--radius);
  padding: 0.05rem 0.3rem;
  font-size: 0.85em;
}

/* Bulk Import tag rules: genuinely an ordered set (#folder must come first), so they get
   the indexed treatment — a numbered list with the index as a quiet numeral. */
.bulk-import-rules {
  list-style: none;
  counter-reset: rule;
  margin: 0 0 1.25rem;
  padding: 0;
  border-top: 1px solid var(--border);
}

.bulk-import-rule {
  counter-increment: rule;
  display: grid;
  grid-template-columns: 2.2rem minmax(0, 1fr);
  align-items: baseline;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.86rem;
  color: var(--text-muted);
}

.bulk-import-rule::before {
  content: counter(rule, decimal-leading-zero);
  font-size: var(--fs-meta);
  font-weight: 800;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.bulk-import-note {
  font-size: 0.8rem;
  color: var(--text-faint);
  margin: -0.5rem 0 1.1rem;
}

.bulk-import-text {
  font-family: var(--font-mono);
  resize: vertical;
}

.bulk-import-summary {
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  margin: 0 0 0.75rem;
}

.bulk-import-list {
  list-style: none;
  margin: 0 0 1.25rem;
  padding: 0;
  max-height: 280px;
  overflow-y: auto;
  border-top: 1px solid var(--border);
}

/* Each imported row slides in once, staggered by its index (--i, set in JS, capped) — the
   confirmation answers the Import click. */
.bulk-import-list li {
  padding: 0.5rem 0;
  font-size: 0.88rem;
  border-bottom: 1px solid var(--border);
  animation: import-row-in 0.32s var(--ease) backwards;
  animation-delay: calc(min(var(--i, 0), 12) * 35ms);
}

.bulk-import-list li:last-child {
  border-bottom: none;
}

/* Slides in from the left, not from below: a downward offset on the last row overflows the
   scrolling list and flashes a scrollbar mid-animation; negative overflow never does. */
@keyframes import-row-in {
  from { opacity: 0; transform: translateX(-8px); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .bulk-import-list li {
    animation: none;
  }
}

/* Inputs: hairline box, square, ink border on focus. Labels above them are the small
   uppercase field names; the value itself reads at body size in normal case. */
.modal-content input[type="text"],
.modal-content input[type="date"],
.modal-content input[type="number"],
.modal-content textarea,
.modal-content select {
  display: block;
  width: 100%;
  margin-top: 0.4rem;
  padding: 0.55rem 0.65rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  font-size: 0.95rem;
  font-weight: 400;
  font-family: inherit;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  background: var(--bg);
}

.modal-content input:focus,
.modal-content textarea:focus,
.modal-content select:focus {
  outline: none;
  border-color: var(--text);
}

.modal-content textarea.bulk-import-text {
  font-family: var(--font-mono);
  font-size: 0.86rem;
}

.form-row {
  display: flex;
  gap: 1rem;
}

.form-row > label {
  flex: 1;
}

.form-field-stack {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.checkbox-label {
  display: flex !important;
  align-items: center;
  gap: 0.5rem;
  margin-top: 1.4rem;
}

.checkbox-label input {
  width: auto;
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 1rem;
}

/* ---- View switch (Overview / Checklist / Calendar, and every in-page mode toggle) ----
   Text tabs, not a pill track: inactive labels are quiet, the active one is ink with a
   rule underneath. */
[hidden] {
  display: none !important;
}

.view-switch {
  display: inline-flex;
  gap: 1.25rem;
  margin-left: auto;
  margin-right: 0.5rem;
}

.view-switch-btn {
  border: none;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--tab-idle);
  transition: color 0.15s var(--ease);
  font: inherit;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: -0.005em;
  padding: 0.3rem 0 0.25rem;
  cursor: pointer;
  white-space: nowrap;
}

.view-switch-btn:hover {
  color: var(--tab-hover);
}

.view-switch-btn.active,
.view-switch-btn.active:hover {
  color: var(--tab-active);
  border-bottom-color: var(--view-accent, var(--text));
}

/* Mode toggles (makeModeToggle in app.js: Plate/List, Scatter/Quadrants, Pace/Capacity,
   Flat/Folder): one hairline-boxed button naming the current mode, a size down from the view
   tabs. Hover rolls its label to the mode a click switches to, and firms up the box. */
.mode-toggle {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: -0.005em;
  padding: 0.3rem 0.7rem;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.2s var(--ease);
}

.mode-toggle:hover,
.mode-toggle:focus-visible {
  border-color: var(--text);
}

/* View toggles (a view's display mode: Plate/List, Scatter/Quadrants, Pace/Capacity): set
   apart from every other button, including the plain Flat/Folder toggle, as the one control
   that changes what the whole view is. Each mode has its own color (the mode lists' `color`,
   passed in as --mode-now / --mode-next): a heavier box in that color, the label in it over a
   faint wash of it, and a solid swatch leading the label. Hovering (or tapping) shifts the
   color to the mode a click switches to, in step with the label rolling to its name — the
   whole button previews the destination. --mode-c is registered so it can transition. */
@property --mode-c {
  syntax: "<color>";
  inherits: true;
  initial-value: transparent;
}

.mode-toggle.view-toggle {
  --mode-c: var(--mode-now);
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  border: 1.5px solid var(--mode-c);
  color: var(--mode-c);
  background: color-mix(in srgb, var(--mode-c) 14%, transparent);
  padding: 0.3rem 0.75rem 0.3rem 0.6rem;
  transition: --mode-c 0.32s var(--ease);
}

.mode-toggle.view-toggle::before {
  content: "";
  flex: none;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 1px;
  background: var(--mode-c);
}

.mode-toggle.view-toggle.tap-hover,
.mode-toggle.view-toggle:focus-visible {
  --mode-c: var(--mode-next);
  border-color: var(--mode-c);
}

@media (hover: hover) {
  .mode-toggle.view-toggle:hover {
    --mode-c: var(--mode-next);
    border-color: var(--mode-c);
  }
}

@media (prefers-reduced-motion: reduce) {
  .mode-toggle.view-toggle {
    transition: none;
  }
}

/* ---- Color tabs: the view tabs (Overview / Checklist / Calendar) and the category tabs ----
   One mechanism for both, in the app's box language (hairline border, small radius, never a
   pill), each tab in its own color (--tab-color: the view's accent, the category's color, ink
   for "All"):
   - idle: a thin underline in that color; the label (and any dot) stays neutral;
   - hover: the underline closes into a box around the whole tab, and the label and dot take the
     color too, all together;
   - active: the box fills solid with the color, label and dot near-white on top.
   A category tab's box is its wrap (dot + label); a view tab's box is the button itself. */
.color-tab {
  --tab-color: var(--text);
  display: inline-flex;
  align-items: center;
  border: 1px solid transparent;
  border-bottom-color: var(--tab-color);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  transition: color 0.2s var(--ease), background-color 0.2s var(--ease), border-color 0.2s var(--ease);
}

.color-tab:hover,
.color-tab:focus-within {
  border-color: var(--tab-color);
  color: var(--tab-color);
}

.color-tab.active,
.color-tab.active:hover {
  border-color: transparent;
  background: color-mix(in oklab, var(--tab-color) var(--tab-fill-strength), #000);
  color: var(--tab-on);
}

/* "All" is ink, not a hue: it fills with ink at full strength and takes the page color on top. */
.color-tab-neutral.active,
.color-tab-neutral.active:hover {
  background: var(--text);
  color: var(--bg);
}

.color-tab[data-view="overview"] { --tab-color: var(--accent-overview); }
.color-tab[data-view="list"] { --tab-color: var(--accent-list); }
.color-tab[data-view="calendar"] { --tab-color: var(--accent-calendar); }

/* View tabs: the button is the box; boxes sit closer than bare labels did. */
#view-switch {
  gap: 0.4rem;
}

.view-switch-btn.color-tab {
  padding: 0.3rem 0.6rem;
}

/* Category tabs: the wrap is the box; the label button and dot just carry its color. */
.color-tab .folder-tab,
.color-tab .folder-tab:hover {
  color: inherit;
  border: none;
  padding: 0.3rem 0.6rem;
}

.color-tab .cat-dot-btn + .folder-tab {
  padding-left: 0.15rem;
}

.color-tab .cat-dot-btn {
  margin-left: 0.4rem;
}

.color-tab.active .cat-dot-btn {
  background: radial-gradient(circle, var(--tab-on) 0 0.27rem, transparent 0.28rem);
}

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

/* ---- Overview (scatter / quadrant list, priority panel, digest) ---- */
.overview-toolbar {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem 1.5rem;
  margin-bottom: 1.75rem;
}

.overview-mode {
  flex: none;
  margin-left: auto;
}

.overview-layout {
  display: flex;
  gap: 3rem;
  align-items: flex-start;
}

/* The task map's main divider sits on the container both display modes share, so it stays put
   through a Scatter/Quadrants switch (persistent chrome) while only the map under it falls and
   rises, and eases from one mode's color to the other's (--mode-accent, renderViewSwitch). The
   scatter box sits flush under it; the quadrant list gets the usual gap below a rule. */
.overview-main {
  flex: 1;
  min-width: 0;
  border-top: 2px solid var(--mode-accent, var(--view-accent, var(--text)));
  transition: border-color 0.3s var(--ease);
}

.overview-main > .matrix-grid {
  padding-top: 0.9rem;
}

.overview-sidebar {
  width: 320px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}

.overview-panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-top: 2px solid var(--view-accent, var(--text));
  padding: 0.9rem 1rem 1rem;
}

@media (max-width: 900px) {
  .overview-layout {
    flex-direction: column;
    align-items: stretch; /* so the plot box takes the full width and scrolls inside itself */
  }

  .overview-sidebar {
    width: 100%;
  }
}

.overview-panel-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.overview-panel-title {
  margin: 0;
  font-size: var(--fs-section);
  font-weight: 800;
  letter-spacing: var(--tracking-tight);
  line-height: 1.05;
}

.overview-panel-rule {
  font-size: var(--fs-meta);
  color: var(--text-faint);
  cursor: help;
}

.overview-panel-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* Same heat-map as everything else: hue from the quadrant, intensity from priority. The
   rank is genuinely sequential (1st, 2nd, 3rd priority today), so it gets the indexed
   treatment — a large numeral in the quadrant's hue, matching the numbered dot in the
   scatter plot. The day's picks also carry a ring + glow in that same hue (--pick-glow,
   shared with .matrix-item-priority): for the overdue / Do tasks that usually top this list
   it's a fire glow — an urgency signal, echoing the halo around their scatter dots. */
.priority-card,
.matrix-item-priority {
  --pick-glow:
    0 0 0 1px color-mix(in srgb, var(--row-accent) 45%, transparent),
    0 0 18px -3px color-mix(in srgb, var(--row-accent) 60%, transparent);
}

.priority-card {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0.7rem 0.6rem 0.6rem;
  border-left: 3px solid var(--row-accent);
  background: var(--row-bg);
  box-shadow: var(--pick-glow);
}

.priority-card-rank {
  flex-shrink: 0;
  min-width: 1.6rem;
  font-size: 1.35rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--row-hue);
}

.priority-card-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.priority-card-title {
  font-size: 0.92rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.priority-card-meta {
  font-size: 0.72rem;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.priority-card-score-col {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.25rem;
}

.priority-card-score {
  font-size: 0.9rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--row-hue);
}

.overview-panel-rest {
  margin: 0.8rem 0 0;
  font-size: var(--fs-meta);
  color: var(--text-faint);
}

/* Expanded panel: every active task, scrollable, with an inline edit row per card. The
   day's summary picks keep their full tint; the rest sit quieter (.priority-card-plain). */
.overview-panel-toggle {
  font-size: var(--fs-meta);
  padding: 0;
  margin-left: auto;
}

.overview-panel-list-full {
  max-height: min(70vh, 640px);
  overflow-y: auto;
  padding-right: 4px;
}

.priority-card-plain {
  background: transparent;
  border-bottom: 1px solid var(--border);
  box-shadow: none;
}

.priority-card-edit-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.8rem;
  margin-top: 0.35rem;
}

.priority-card-field {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

.priority-card-field input[type="date"],
.priority-card-field select {
  font: inherit;
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border-strong);
  border-radius: 0;
  padding: 0.05rem 0.1rem;
}

.priority-card-field select option {
  background: var(--surface);
}

.priority-card-edit {
  padding: 0.1rem;
  opacity: 0;
}

.priority-card:hover .priority-card-edit {
  opacity: 1;
}

/* Scatter plot: no box, just the plot on the page, hairline axes. */
.scatter {
  background: var(--panel);
  border: 1px solid var(--border);
  border-top: none; /* the main divider above it (.overview-main) is its top edge */
  padding: 0.75rem 0.75rem 0.5rem;
  overflow-x: auto; /* on a phone the plot keeps a readable size and scrolls inside its box */
  overscroll-behavior-x: contain; /* a sideways swipe at the plot's edge never becomes "back" */
}

/* Phones only (where the plot is wider than its box): a one-line cue that it scrolls sideways,
   pinned to the box's left edge so it stays put while the plot moves under it. */
.scatter-scroll-hint {
  display: none;
  position: sticky;
  left: 0;
  width: max-content;
  margin: 0 0 0.5rem;
  font-size: 0.78rem;
  color: var(--text-muted);
}

@media (max-width: 639.98px) {
  .scatter-scroll-hint {
    display: block;
  }
}

.scatter-svg {
  display: block;
  width: 100%;
  min-width: 560px; /* keeps 11px labels at roughly 10px or larger */
  height: auto;
  font-family: inherit;
}

.scatter-region-fill {
  fill: var(--row-bg-pale);
}

.scatter-region-label {
  fill: var(--row-hue);
  font-size: 15px;
  font-weight: 800;
  letter-spacing: -0.01em;
  pointer-events: none;
  /* halo in the panel colour keeps the word legible if a dot drifts close */
  paint-order: stroke;
  stroke: var(--panel);
  stroke-width: 3px;
  stroke-linejoin: round;
}

.scatter-split {
  stroke: var(--text-faint);
  stroke-width: 1;
  stroke-dasharray: 3 4;
}

.scatter-split-label {
  fill: var(--text-faint);
  font-size: 10px;
}

.scatter-axis {
  stroke: var(--border-strong);
  stroke-width: 1;
}

.scatter-tick {
  fill: var(--text-faint);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}

.scatter-axis-title {
  fill: var(--text-muted);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.scatter-dot-group {
  cursor: default;
  outline: none;
}

.scatter-dot {
  fill: var(--row-accent);
  stroke: var(--panel);
  stroke-width: 1.5;
  transition: r 0.12s var(--ease);
}

/* Thin ring around priority-panel dots, so they read as distinct from a plain size bump
   alone, even inside a dense cluster. */
.scatter-dot-halo {
  fill: none;
  stroke: var(--row-accent);
  stroke-width: 1.5;
  opacity: 0.6;
  pointer-events: none;
}

.scatter-dot-group:hover .scatter-dot,
.scatter-dot-group:focus .scatter-dot {
  r: 9;
  stroke: var(--text);
}

/* Escalating tag on a dot: a distinct stroke in the tag's colour, and the same colour on the
   tag suffix of its label. Do Today is neutral, so its stroke is ink. */
.scatter-dot-group.tag-do-today .scatter-dot { stroke: var(--text); stroke-width: 2; }
.scatter-dot-group.tag-due-today .scatter-dot { stroke: var(--fire-amber); stroke-width: 2.5; }
.scatter-dot-group.tag-overdue .scatter-dot { stroke: var(--fire); stroke-width: 2.5; }
.scatter-label-tagged.tag-do-today { fill: var(--text); }
.scatter-label-tagged.tag-due-today { fill: var(--fire-amber); }
.scatter-label-tagged.tag-overdue { fill: var(--fire); }

.scatter-dot-rank {
  fill: var(--on-accent);
  font-size: 10px;
  font-weight: 800;
  text-anchor: middle;
  dominant-baseline: central;
  pointer-events: none;
}

/* Always-on title labels: text colour with a panel-coloured halo so they read over any
   quadrant tint; panel tasks are bold to match their bigger, numbered dots. */
.scatter-label {
  fill: var(--text);
  font-size: 11px;
  pointer-events: none;
  paint-order: stroke;
  stroke: var(--panel);
  stroke-width: 3px;
  stroke-linejoin: round;
}

.scatter-label-summary {
  font-size: 12px;
  font-weight: 700;
}

.scatter .empty-hint {
  text-align: center;
}

/* auto column for the row-axis labels, then the two quadrant columns */
.matrix-grid {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) minmax(0, 1fr);
  /* auto, not 1fr: with an indefinite container height, equal-fr row tracks all resolve to
     the same size (the tallest row's content) rather than sizing independently — Do/Plan
     would end up as tall as Clear/Backlog even with far fewer cards. auto rows size to their
     own content; align-items: stretch below still matches heights WITHIN each row. */
  grid-template-rows: auto auto auto;
  gap: 2.25rem 2rem;
  align-items: stretch;
}

.matrix-axis {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-faint);
}

.matrix-axis-col {
  justify-content: flex-start;
  padding-bottom: 0;
  margin-bottom: -1.25rem;
}

.matrix-axis-row {
  width: 1.4rem;
  grid-column: 1;
}

/* Explicit placement (not auto-flow), same corners as the scatter plot: Do top-left,
   Plan top-right, Clear bottom-left, Backlog bottom-right. */
.matrix-corner { grid-column: 1; grid-row: 1; }
.matrix-axis-col-high { grid-column: 2; grid-row: 1; }
.matrix-axis-col-low { grid-column: 3; grid-row: 1; }
.matrix-axis-row-high { grid-row: 2; }
.matrix-axis-row-low { grid-row: 3; }
.matrix-cell-q1 { grid-column: 2; grid-row: 2; }
.matrix-cell-q2 { grid-column: 3; grid-row: 2; }
.matrix-cell-q3 { grid-column: 2; grid-row: 3; }
.matrix-cell-q4 { grid-column: 3; grid-row: 3; }

.matrix-axis-row span {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
}

/* A quadrant is an open section: a rule in its hue, its name set large in ink, the count
   as a numeral in the hue. */
.matrix-cell {
  --p: 1;
  display: flex;
  flex-direction: column;
  min-height: 180px;
  border-top: 3px solid var(--row-hue);
}

.matrix-cell-header {
  display: flex;
  align-items: baseline;
  gap: 0.65rem;
  padding: 0.75rem 0 0.8rem;
}

.matrix-cell-label {
  margin: 0;
  font-size: var(--fs-section);
  font-weight: 800;
  letter-spacing: var(--tracking-tight);
  line-height: 1;
  color: var(--text);
}

.matrix-cell-hint {
  font-size: var(--fs-meta);
  color: var(--text-faint);
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.matrix-cell-count {
  font-size: 1.1rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--row-hue);
}

.matrix-cell .empty-hint {
  padding: 0.25rem 0;
}

.matrix-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* Same heat-map as List rows: hue from the quadrant, intensity from priority_score. */
.matrix-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "title score" "meta score";
  column-gap: 0.6rem;
  align-items: center;
  padding: 0.5rem 0.65rem 0.5rem 0.6rem;
  border-left: 3px solid var(--row-accent);
  background: var(--row-bg);
  cursor: default;
}

/* Priority-panel tasks get a rank numeral column and the same ring + glow as their
   priority-card and numbered scatter dot, so all three read as the same highlighted task. */
.matrix-item-priority {
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "rank title score" "rank meta score";
  box-shadow: var(--pick-glow);
}

.matrix-item-rank {
  grid-area: rank;
  align-self: center;
  min-width: 1.3rem;
  font-size: 1.1rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--row-hue);
}

.matrix-item-title {
  grid-area: title;
  font-size: 0.9rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.matrix-item-meta {
  grid-area: meta;
  font-size: 0.72rem;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.matrix-item-meta:empty {
  display: none;
}

.matrix-item-score-col {
  grid-area: score;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  gap: 0.25rem;
}

.matrix-item-score {
  font-size: 0.76rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--row-hue);
}

@media (max-width: 640px) {
  .matrix-grid {
    display: flex;
    flex-direction: column;
  }

  .matrix-corner, .matrix-axis {
    display: none;
  }

  .matrix-cell {
    min-height: 0;
  }

  .matrix-cell-q1 { order: 0; }
  .matrix-cell-q2 { order: 1; }
  .matrix-cell-q3 { order: 2; }
  .matrix-cell-q4 { order: 3; }
}

/* What changed since yesterday */
.digest {
  margin-top: 3rem;
  border-top: 2px solid var(--view-accent, var(--text));
  padding-top: 0.9rem;
}

.digest-heading {
  margin: 0 0 0.3rem;
  font-size: var(--fs-section);
  font-weight: 800;
  letter-spacing: var(--tracking-tight);
  line-height: 1.05;
}

.digest-sub {
  margin: 0;
  font-size: 0.82rem;
  color: var(--text-muted);
}

.digest-list {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--border);
}

.digest-line {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.9rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--border);
}

/* Quadrant chip: the quadrant's name in its hue, a small tinted block. */
.digest-chip {
  --p: 1;
  flex-shrink: 0;
  font-size: var(--fs-micro);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  line-height: 1.6;
  padding: 0 0.5rem;
  border-radius: var(--radius);
  min-width: 4.8rem;
  text-align: center;
  background: var(--row-bg-vivid);
  color: var(--row-hue);
}

:root[data-theme="light"] .digest-chip {
  color: var(--text);
}

.digest-chip-neutral {
  background: transparent;
  color: var(--text-muted);
  box-shadow: inset 0 0 0 1px var(--border-strong);
}

.digest-arrow {
  color: var(--text-faint);
  flex-shrink: 0;
}

.digest-text {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem;
  min-width: 0;
}

.digest-title {
  font-weight: 500;
}

.digest-context {
  font-size: var(--fs-meta);
  color: var(--text-muted);
}

.digest-context::before {
  content: "· ";
}

.digest-reason {
  font-size: 0.8rem;
  color: var(--text-muted);
}

.digest-reason::before {
  content: "— ";
}

.digest-note {
  margin: 1rem 0 0;
  font-size: 0.84rem;
  color: var(--text-faint);
}

/* Secondary tier (a manual edit caused or contributed to the quadrant shift): collapsed by
   default and, once opened, visibly quieter than the primary list above — smaller text and
   reduced opacity — since you were already aware of the edit when you made it. */
.digest-secondary {
  margin-top: 1rem;
}

.digest-secondary summary {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
}

.digest-secondary summary:hover {
  color: var(--text);
}

/* Opening and closing grow and fade the section, the same motion as every other fold. */
.digest-secondary::details-content {
  interpolate-size: allow-keywords;
  height: 0;
  opacity: 0;
  overflow: clip;
  transition: height 0.28s var(--ease), opacity 0.28s var(--ease), content-visibility 0.28s allow-discrete;
}

.digest-secondary[open]::details-content {
  height: auto;
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .digest-secondary::details-content {
    transition: none;
  }
}

.digest-list-secondary {
  margin-top: 0.6rem;
  opacity: 0.8;
}

.digest-list-secondary .digest-line {
  font-size: 0.82rem;
}

.digest-list-secondary .digest-reason {
  font-size: 0.74rem;
}

@media (max-width: 640px) {
  .digest-line {
    flex-wrap: wrap;
  }

  .digest-text {
    flex-basis: 100%;
  }
}

/* Staleness check-ins: its own section under the Priority Today panel, not part of the "what
   changed" digest above — a different kind of signal (undated tasks aging in place, not a
   quadrant shift). Ice throughout, Plan's hue, since staleness on an undated task is exactly
   the "important but not urgent, don't forget it" case Plan exists for. Severity is carried
   by color alone (the message tint), the wording never escalates. */
.digest-staleness {
  border-top: 2px solid var(--view-accent, var(--text));
  padding-top: 0.9rem;
}

.digest-staleness-heading {
  margin: 0 0 0.8rem;
  font-size: var(--fs-section);
  font-weight: 800;
  letter-spacing: var(--tracking-tight);
  line-height: 1.05;
}

.digest-staleness-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* Left edge is the task's own quadrant color (same --row-accent formula as the digest chips
   and priority cards), so a stale task still reads as Do/Plan/Clear/Backlog at a glance.
   The ice tier ramp below is reserved for the message text alone — the two colors answer
   different questions (why this task is prioritized vs. how overdue this reminder is) and
   can disagree, e.g. a Backlog task deep into a "strong" staleness tier. */
.digest-staleness-item {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.55rem 0 0.55rem 0.7rem;
  border-left: 3px solid var(--row-accent);
  border-bottom: 1px solid var(--border);
}

.digest-staleness-title {
  font-size: 0.9rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.digest-staleness-context {
  font-size: 0.72rem;
  color: var(--text-muted);
}

.digest-staleness-message {
  font-size: 0.8rem;
  color: var(--staleness-low-text);
}

.staleness-tier-medium .digest-staleness-message { color: var(--staleness-medium-text); }
.staleness-tier-high .digest-staleness-message { color: var(--staleness-high-text); font-weight: 700; }

/* ---- Calendar (monthly grid, day repository, Weekly Accomplishments panel) ---- */
.calendar-toolbar {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem 1.25rem;
  margin-bottom: 1.75rem;
}

.calendar-toolbar-main {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem 1.25rem;
}

/* Pace/Capacity toggle: outside the wrapping group, in the same top-right corner the display
   toggle holds in every view (see .toolbar), level with the month/year row. The legend, which
   changes size between the two modes, gets a row of its own underneath, so it can never move
   the toggle. */
.calendar-mode {
  flex: none;
  margin-left: auto;
  margin-right: 0;
}

.calendar-toolbar .calendar-legend {
  flex-basis: 100%;
}

.calendar-nav {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

/* Month / week arrows: line chevrons, sized up to sit beside the large month label. */
.calendar-nav .btn-icon svg {
  width: 20px;
  height: 20px;
}

.calendar-weekly-header .btn-icon svg {
  width: 17px;
  height: 17px;
}

.calendar-month-label {
  margin: 0;
  font-size: var(--fs-section);
  font-weight: 800;
  letter-spacing: var(--tracking-tight);
  min-width: 12rem;
  text-align: center;
}

.calendar-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.4rem;
  font-size: 0.72rem;
  color: var(--text-muted);
}

.calendar-legend-chip {
  position: relative; /* the ring/win-mark overlay pseudo-elements below anchor to this */
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.15rem 0.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-weight: 600;
  cursor: help;
}

.calendar-layout {
  display: flex;
  gap: 3rem;
  align-items: flex-start;
}

/* The grid's own framing rule, level with the weekly panel's violet rule beside it, takes the
   display mode's color (--mode-accent, renderViewSwitch: Pace sand, Capacity magenta) and eases
   between them on a Pace/Capacity switch. */
.calendar-main {
  flex: 1;
  min-width: 0;
  border-top: 2px solid var(--mode-accent, var(--view-accent, var(--text)));
  padding-top: 0.9rem;
  transition: border-color 0.3s var(--ease);
}

.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 3px;
}

.calendar-weekday-head {
  text-align: left;
  font-size: var(--fs-micro);
  font-weight: 700;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0 0 0.45rem 0.1rem;
}

/* Day cells: flat squares, the fill is the whole signal. */
.calendar-cell {
  position: relative;
  min-height: 64px;
  border: none;
  border-radius: 0;
  background: var(--row-hover);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  padding: 0.35rem 0.45rem;
}

.calendar-cell-blank {
  background: transparent;
  cursor: default;
}

.calendar-cell:hover:not(.calendar-cell-blank) {
  box-shadow: inset 0 0 0 100vmax rgba(255, 255, 255, 0.05);
}

:root[data-theme="light"] .calendar-cell:hover:not(.calendar-cell-blank) {
  box-shadow: inset 0 0 0 100vmax rgba(0, 0, 0, 0.05);
}

/* Day-color buckets: solid fills, not the continuous heat-map ramp the quadrant hues use —
   this is a discrete pace signal (strict priority order, see computeDayStats), not a
   priority intensity. Fire and Ice: red = fire (something was overdue), deep blue = full ice
   (everything planned got done), pale blue = pale ice (only some of it), gray stays
   deliberately muted — "nothing was planned", not a low score. */
.calendar-day-red, .calendar-legend-chip.calendar-day-red {
  background: var(--day-red-bg);
  color: var(--day-red-text);
}

.calendar-day-deep-blue, .calendar-legend-chip.calendar-day-deep-blue {
  background: var(--day-deep-blue-bg);
  color: var(--day-deep-blue-text);
}

.calendar-day-pale-blue, .calendar-legend-chip.calendar-day-pale-blue {
  background: var(--day-pale-blue-bg);
  color: var(--day-pale-blue-text);
}

.calendar-day-gray, .calendar-legend-chip.calendar-day-gray {
  background: var(--day-gray-bg);
  color: var(--day-gray-text);
}

/* A day strictly after today has no pace to show — it keeps the bare page with a hairline
   edge and a muted number, so it can't be confused with any of the four scored fills, and
   only its due-date markers (below) carry information. */
.calendar-day-upcoming, .calendar-legend-chip.calendar-day-upcoming {
  background: transparent;
  color: var(--text-muted);
  box-shadow: inset 0 0 0 1px var(--border);
}

/* ---- Capacity view: continuous effort/capacity gradient (--p, 0-1, set per cell in JS),
   one plum hue shared by past AND future days alike — deliberately none of Pace's fire/ice
   fills, this is volume, not judgment. ---- */
.calendar-cell-capacity, .calendar-legend-chip.calendar-cell-capacity {
  background: hsl(
    var(--capacity-h)
    calc(var(--capacity-s0) + (var(--capacity-s1) - var(--capacity-s0)) * var(--p, 0))
    calc(var(--capacity-l0) + (var(--capacity-l1) - var(--capacity-l0)) * var(--p, 0))
  );
  color: var(--text);
}

/* Overload marker: fires once effort crosses 100% of daily capacity. A ring in Capacity's own
   plum, pushed brighter (not fire — see --capacity-overload), so it can never be mistaken for
   Pace's overdue trigger; they're different claims ("busy" vs. "missed something important"). */
.calendar-cell-capacity-overload::after {
  content: "";
  position: absolute;
  inset: 3px;
  border: 2px solid var(--capacity-overload);
  pointer-events: none;
}

.calendar-legend-chip.calendar-cell-capacity-overload::after {
  inset: 1px;
  border-width: 1.5px;
}

/* ---- Big win overlay: independent of the base fill, drawn as a pseudo-element so it never
   competes with the cell's own box-shadow (today / selected week / open / hover rings). ----
   A task completed this day that was ever in Plan, or a High/Critical task finished after its
   deadline (isBigWin, calendar.js). Gold, the importance hue: an inset glow at the cell's edge
   (inward, so it never bleeds under neighbours) plus a small gold star in the top-right corner,
   so the win still reads on a fire or ice fill. Layers on any base color — a fire day can still
   carry a win if something important also got cleared. */
.calendar-cell-big-win::before {
  content: "✦";
  position: absolute;
  inset: 0;
  display: flex;
  justify-content: flex-end;
  align-items: flex-start;
  padding: 0.3rem 0.4rem;
  font-size: 0.85rem;
  line-height: 1;
  color: var(--day-win);
  box-shadow: inset 0 0 0 1.5px var(--day-win), inset 0 0 12px 1px var(--day-win-glow);
  pointer-events: none;
}

.calendar-legend-chip.calendar-cell-big-win {
  padding-left: 1.35rem;
}

.calendar-legend-chip.calendar-cell-big-win::before {
  justify-content: flex-start;
  align-items: center;
  padding: 0 0.4rem;
  font-size: 0.75rem;
  box-shadow: inset 0 0 0 1px var(--day-win), inset 0 0 6px 0 var(--day-win-glow);
}

/* ---- Future due-date markers (days after today only) ---- */
.calendar-cell-markers {
  position: absolute;
  bottom: 0.4rem;
  left: 0.45rem;
  right: 0.45rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  pointer-events: none;
}

/* Plain dot: a regular task's deadline lands on this day. */
.calendar-cell-marker {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-muted);
  flex: none;
  pointer-events: auto;
}

/* Accented marker: the task would be listed in the Overview's priority summary panel
   (top N or score ≥ flag threshold). Larger, ink, and a diamond rather than a dot so it
   stays distinct by shape alone. */
.calendar-cell-marker-top {
  width: 9px;
  height: 9px;
  border-radius: 0;
  background: var(--text);
  transform: rotate(45deg);
}

.calendar-legend-chip .calendar-cell-marker-top {
  width: 8px;
  height: 8px;
  margin: 0 1px;
}

/* Weekly/monthly RecurringTask marker: a small square in the weekly violet used everywhere
   else for "weekly" (the habit box, the Weekly Accomplishments panel) — distinct in shape
   from both the plain deadline dot and the top-priority diamond. Daily habits deliberately
   get no marker at all. */
.calendar-cell-marker-recurring {
  width: 7px;
  height: 7px;
  border-radius: 0;
  background: hsl(var(--weekly-h) var(--habit-acc-s) var(--habit-acc-l));
}

.calendar-legend-chip .calendar-cell-marker-recurring {
  margin: 0 1px;
}

/* Today: the date numeral inverts to an ink block, a typographic mark rather than a ring,
   so it never competes with the selection rings below. */
.calendar-cell-today .calendar-cell-num {
  background: var(--text);
  color: var(--on-accent);
  padding: 0 0.3rem;
  margin: -0.05rem 0 0 -0.2rem;
}

/* Selected week: a plain hairline ring, no hue. Thinner than the "open day" ring below so
   the two stay visually distinct when a cell is both — the day you just clicked is always
   inside its own selected week. */
.calendar-cell-selected-week {
  box-shadow: inset 0 0 0 1px var(--text-muted);
}

.calendar-cell-open {
  box-shadow: inset 0 0 0 2px var(--text);
}

.calendar-cell-num {
  font-size: 0.85rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.35;
}

.calendar-cell-badge {
  position: absolute;
  bottom: 0.35rem;
  right: 0.45rem;
  font-size: 0.72rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: inherit;
  opacity: 0.85;
}

/* A single day's completions: an open section under the grid, its rule and heading in the
   daily habit hue (aqua) — it's day-scoped, like the Daily box. The Weekly Accomplishments
   panel beside it takes the weekly violet the same way, so the two summaries read as clearly
   different things. */
.calendar-day-repo {
  margin-top: 2rem;
  border-top: 2px solid hsl(var(--daily-h) var(--habit-acc-s) var(--habit-acc-l));
  padding: 0.9rem 0 0;
  position: relative;
}

.calendar-day-repo-heading {
  margin: 0 0 0.75rem;
  font-size: 1.3rem;
  font-weight: 800;
  letter-spacing: var(--tracking-tight);
  padding-right: 2rem;
  color: hsl(var(--daily-h) var(--habit-acc-s) var(--habit-acc-l));
}

.calendar-day-repo-close {
  position: absolute;
  top: 0.8rem;
  right: 0;
  font-size: 1rem;
}

.calendar-day-repo-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--border);
}

.calendar-day-repo-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  font-size: 0.9rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
}

.calendar-day-repo-meta {
  font-size: var(--fs-meta);
  color: var(--text-muted);
  white-space: nowrap;
}

.calendar-weekly-panel {
  width: 320px;
  flex-shrink: 0;
  border-top: 2px solid hsl(var(--weekly-h) var(--habit-acc-s) var(--habit-acc-l));
  padding: 0.9rem 0 0;
}

@media (max-width: 900px) {
  .calendar-layout {
    flex-direction: column;
    align-items: stretch;
  }

  .calendar-weekly-panel {
    width: 100%;
  }
}

.calendar-weekly-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 1rem;
}

.calendar-weekly-title {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 800;
  letter-spacing: -0.015em;
  min-width: 8rem;
  text-align: center;
  color: hsl(var(--weekly-h) var(--habit-acc-s) var(--habit-acc-l));
}

.calendar-weekly-thisweek {
  margin-left: auto;
}

.calendar-weekly-subhead {
  margin: 1.4rem 0 0.4rem;
  font-size: var(--fs-group);
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text);
}

.calendar-weekly-subhead:first-of-type {
  margin-top: 0;
}

/* Categorical tally ("6 Work, 3 Errands, 2 Recruiting") rather than a title-by-title list —
   the day-repository already covers that level of detail. */
.calendar-weekly-summary {
  font-size: 0.9rem;
  color: var(--text-muted);
}

.calendar-biggest-win {
  --p: 1;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.6rem 0.7rem;
  border-left: 3px solid var(--row-accent);
  background: var(--row-bg);
}

.calendar-biggest-win-title {
  font-weight: 500;
  font-size: 0.92rem;
}

.calendar-biggest-win-score {
  font-size: var(--fs-meta);
  color: var(--row-hue);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 480px) {
  .calendar-cell {
    min-height: 44px;
    padding: 0.25rem 0.3rem;
  }

  .calendar-cell-num {
    font-size: 0.72rem;
  }
}

/* ---- Windows: shared toolbar, global "+" icons ---- */
/* The shared sort dropdown and flat/folder switch for both windows: merged into the main
   list-view toolbar row (top-left, item 28) rather than a second row of their own. */
.windows-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
}

/* The plain global "+" at the right end of each window's controls row (where the per-window
   grouping switch used to sit). */
.window-add-icon {
  margin-left: auto;
  padding: 0.2rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: transparent;
}

.window-add-icon:hover {
  border-color: var(--text);
}

/* ---- Cards: countdown, rollover / missed badges, completion grace period ---- */

/* "Due in N": shown while a deadline approaches; at 0 the Due Today / Overdue tag replaces it.
   The quietest tag: no fill, no rule, just the words. */
.tag-countdown {
  background: transparent;
  color: var(--text-muted);
  border-color: var(--border);
}

/* "Rolled 3x": plain, no tiers. */
.badge-rolled {
  background: transparent;
  color: var(--text-muted);
  border-color: var(--border);
  text-transform: none;
  letter-spacing: 0;
  font-weight: 600;
}

/* "Missed yesterday / last week / last month": the habit nudge — an urgency cue, so the
   same amber as Due Today. */
.badge-missed {
  background: var(--tag-due-today-bg);
  color: var(--tag-due-today-text);
  text-transform: none;
  letter-spacing: 0;
  font-weight: 600;
}

/* "Skipped today / this week / this month": the deliberate-rest-day marker, neutral, so it
   reads as a state, not an alert. */
.badge-skipped {
  background: var(--badge-recurring-bg);
  color: var(--badge-recurring-text);
  border-color: var(--border);
  text-transform: none;
  letter-spacing: 0;
  font-weight: 600;
}

/* Checked, waiting out the grace period before the toggle actually commits (Plate/Fridge cards
   and habit rows move to Completed Today once it does; the main list row and subtasks complete
   in place; an Overdue strip card leaves the strip). Same pattern everywhere a completion
   checkbox appears. */
.window-card.done-pending .window-card-title,
.window-subtask.done-pending .window-subtask-title,
.window-habit.done-pending .window-habit-title,
.task-row.done-pending .task-title {
  text-decoration: line-through;
  color: var(--done);
}

.window-card.done-pending,
.window-habit.done-pending,
.task-row.done-pending {
  opacity: 0.6;
  transition: opacity 0.3s var(--ease);
}

/* ---- Completed Today folder (Daily Plate only) ---- */
.window-completed {
  margin-top: 1.4rem;
  border-top: 1px solid var(--border);
  padding-top: 0.6rem;
}

.window-completed-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.2rem 0;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: var(--fs-group);
  font-weight: 800;
  letter-spacing: -0.01em;
  cursor: pointer;
  text-align: left;
}

.window-completed-header:hover {
  color: var(--text);
}

.window-completed-header .folder-caret {
  transform: rotate(-90deg);
}

.window-completed.open .window-completed-header .folder-caret {
  transform: none;
}

.window-completed-list {
  display: flex;
  flex-direction: column;
  margin-top: 0.4rem;
}

.window-completed-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.1rem;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}

.window-completed-row:last-child {
  border-bottom: none;
}

.window-completed-title {
  flex: 1;
  min-width: 0;
  font-size: 0.88rem;
  text-decoration: line-through;
  color: var(--done);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- Task form: week-strip do_date picker and the sizing toggle ---- */
.form-row-picker {
  align-items: flex-start;
  margin-bottom: 1.1rem; /* the same gap a form label leaves, which the hint below expects */
}

.week-picker-field {
  flex: 1 1 auto;
  min-width: 0;
}

.size-toggle-field {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
}

.week-picker-title {
  display: block;
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 0.4rem;
}

.week-picker {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 3px;
}

.week-day {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.05rem;
  padding: 0.4rem 0.1rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  cursor: pointer;
}

.week-day:hover {
  border-color: var(--text);
}

.week-day-today {
  border-style: dashed;
}

.week-day.active {
  background: var(--text);
  border-color: var(--text);
  color: var(--on-accent);
}

.week-day-letter {
  font-size: 0.72rem;
  font-weight: 800;
  text-transform: uppercase;
}

.week-day-num {
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
}

.week-picker-extra {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.45rem;
}

.week-picker-extra input[type="date"] {
  width: auto;
  margin: 0;
}

.week-picker-clear {
  border: 1px solid var(--border-strong);
}

/* Sizing toggle: text always reads "Bite-size" — on/off is ink-filled vs. outline, via
   [aria-pressed], never a text swap. The off state has no label of its own. */
.size-toggle {
  font: inherit;
  font-size: 0.88rem;
  font-weight: 700;
  padding: 0.55rem 1rem;
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
}

.size-toggle:hover {
  border-color: var(--text);
}

.size-toggle[aria-pressed="true"] {
  background: var(--text);
  border-color: var(--text);
  color: var(--on-accent);
}
