/* ─ Midnight Violet palette ─ */
:root {
  --bg: #0a0510;          /* near-black with violet tint */
  --bg-card: #1a0f2b;     /* deep purple card */
  --bg-input: #1f1233;    /* form inputs / search bar */
  --border: #2a1a42;
  --fg: #ede9fe;          /* lavender-white text */
  --fg-muted: #b8a5e0;
  --accent: #a855f7;      /* violet-500 — primary actions */
  --accent-hover: #c084fc;
  --overdue: #f43f5e;     /* rose — past-due warning */
  --today: #c4b5fd;       /* violet-300 — due today */
  --upcoming: #8b5cf6;    /* violet-500 — future */
  --success: #86efac;     /* saved/ok states */
  --bg-header: #160a24;   /* header gradient top stop */
  --on-accent: #ffffff;   /* text/icon color for content sitting on --accent */

  --font-body: "Space Grotesk", system-ui, -apple-system, sans-serif;
}

/* ─ Deep Ocean palette ─ */
[data-theme="deep-ocean"] {
  --bg: #04090f;
  --bg-header: #0a1624;
  --bg-card: #0e1a2b;
  --bg-input: #112033;
  --border: #1a2e42;
  --fg: #e9f2fe;
  --fg-muted: #a5c2e0;
  --accent: #38bdf8;
  --accent-hover: #7dd3fc;
  --on-accent: #062033;
  --overdue: #f43f5e;
  --today: #7dd3fc;
  --upcoming: #3b82f6;
  --success: #86efac;
}

/* ─ Ember palette ─ */
[data-theme="ember"] {
  --bg: #0f0904;
  --bg-header: #241407;
  --bg-card: #2b1a0e;
  --bg-input: #332011;
  --border: #42301a;
  --fg: #fef3e9;
  --fg-muted: #e0c8a5;
  --accent: #f59e0b;
  --accent-hover: #fbbf24;
  --on-accent: #331f05;
  --overdue: #f43f5e;
  --today: #fcd34d;
  --upcoming: #fb923c;
  --success: #86efac;
}

/* ─ Eclipse palette (journey prestige unlock) ─ */
[data-theme="eclipse"] {
  --bg: #060608;
  --bg-header: #131316;
  --bg-card: #1e1e22;
  --bg-input: #232327;
  --border: #2e2e33;
  --fg: #ececef;
  --fg-muted: #b8b8c0;
  --accent: #c0c3cc;
  --accent-hover: #d7d9e0;
  --on-accent: #18181b;
  --overdue: #f43f5e;
  --today: #d7d9e0;
  --upcoming: #9c9ca8;
  --success: #86efac;
}

/* ─ Comet palette (referral reward unlock) ─ */
[data-theme="comet"] {
  --bg: #071019;
  --bg-header: #0a1826;
  --bg-card: #0d1b28;
  --bg-input: #0a1622;
  --border: #1d3348;
  --fg: #e8f2fa;
  --fg-muted: #8fa9bd;
  --accent: #4dd0e1;
  --accent-hover: #26c6da;
  --on-accent: #04252b;
  --overdue: #f43f5e;
  --today: #80deea;
  --upcoming: #4a9fd4;
  --success: #86efac;
}

* { box-sizing: border-box; }

/* Inline SVG icons (sprite in tasks/_icons.html) — sized in em, themed via
   currentColor so they inherit each context's palette. */
.icon {
  width: 1.1em;
  height: 1.1em;
  fill: currentColor;
  vertical-align: -0.15em;
  flex-shrink: 0;
}
.task-checkbox .icon {
  width: 1.4rem;
  height: 1.4rem;
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-feature-settings: "ss01", "ss02";
  margin: 0;
  padding: 0;
  -webkit-font-smoothing: antialiased;
}

/* Header — scoped to the site header (a direct child of <body>) so a nested
   <header> inside a card (e.g. .suggestion-card-header) doesn't inherit the
   gradient, the bottom border and `justify-content: space-between`. */
body > header {
  background: linear-gradient(180deg, var(--bg-header) 0%, var(--bg) 100%);
  border-bottom: 1px solid var(--border);
  padding: 1rem 1.25rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
body > header a, body > header strong {
  color: var(--fg);
  text-decoration: none;
  font-weight: 600;
  letter-spacing: 0.02em;
}

main {
  padding: 1.25rem;
  max-width: 720px;
  margin: 0 auto;
}

/* Content links. Deliberately placed BEFORE `form a` below: both are (0,0,2),
   so the later `form a` rule keeps winning inside forms (Cancel next to Save
   must stay low-emphasis) while prose links everywhere else stop rendering in
   the browser's default #0000EE. Every class-based link treatment
   (.task-body, .list-chip, .btn, .link-danger, .list-row-actions a …) also
   outranks this. */
main a { color: var(--accent); }

/* Search bar */
.search {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.search input {
  flex: 1;
  padding: 0.6rem 0.85rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-input);
  color: var(--fg);
  font-family: inherit;
  font-size: 0.95rem;
}
.search input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.2);
}

/* Inline search spinner — htmx toggles .htmx-request on the form (hx-indicator).
   Lives as a normal flex sibling next to the input (not an overlay) so it
   never covers the typed text or the native type="search" clear button. */
.search .search-spinner {
  flex: none;
  width: 1rem;
  height: 1rem;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  opacity: 0;
  transition: opacity 0.15s;
}
.search.htmx-request .search-spinner {
  opacity: 1;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Sections */
.section { margin-bottom: 1.75rem; }
.section:not(:has(.task-card)) { display: none; }
.section h2 {
  margin: 0.5rem 0 0.75rem;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--fg-muted);
}
.section.overdue h2 { color: var(--overdue); }
.section.today h2 { color: var(--today); }
.section.upcoming h2 { color: var(--upcoming); }

/* Task cards */
.task-card {
  position: relative;        /* anchor for ::before stripe */
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  background: var(--bg-card);
  padding: 0.85rem 1rem 0.85rem 1.15rem;  /* extra left padding for stripe */
  margin-bottom: 0.55rem;
  border-radius: 10px;
  border: 1px solid transparent;
  transition: border-color 0.15s, transform 0.1s, opacity 0.15s, box-shadow 0.15s;
  overflow: hidden;          /* clip the stripe to the rounded corner */
}
/* List-color stripe — driven by inline --list-color CSS var on each card. */
.task-card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: var(--list-color, var(--accent));
}
/* Desktop hover: accent-tinted border + soft elevation (Material feel).
   Gated on a real hover-capable pointer so touch devices don't get a sticky
   hover state after a tap. */
@media (hover: hover) {
  .task-card:hover {
    border-color: color-mix(in srgb, var(--accent) 40%, transparent);
    transform: translateY(-1px);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
  }
}
/* Subtle press feedback on the whole card. */
.task-card:active {
  transform: scale(0.997);
}
.task-checkbox {
  background: transparent;
  border: none;
  padding: 0;
  width: 1.6rem;
  height: 1.6rem;
  cursor: pointer;
  color: var(--fg-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  flex-shrink: 0;
  margin-top: 0.05rem;
  transition: color 0.15s, transform 0.1s;
}
.task-checkbox:hover {
  color: var(--accent);
}
.task-checkbox:active {
  transform: scale(0.9);
}
/* Inline-finish exit animation — a transition (not @keyframes) is
   deliberate: two error paths (409-stale-complete, network failure) remove
   this class again to roll the optimistic fade back, and only a transition
   eases that reversal via the base .task-card rule above (which now
   includes `opacity`); an @keyframes-driven animation would just cancel
   and snap instantly on class removal. text-decoration below applies the
   instant the class lands (it isn't transitioned), so the strike-through
   is visible on its own during the transition-delay, before opacity/
   transform start easing out. delay 0.14s + duration 0.21s = 0.35s total,
   matching the JS setTimeout that removes the node. */
.task-card.card-finishing {
  opacity: 0;
  transform: translateX(16px);
  transition: opacity 0.21s ease 0.14s, transform 0.21s ease 0.14s;
}
.task-card.card-finishing .task-title {
  text-decoration: line-through;
  color: var(--fg-muted);
}
.task-body {
  flex: 1;
  display: block;
  color: var(--fg);
  text-decoration: none;
}
.task-title {
  display: block;
  font-weight: 500;
  line-height: 1.35;
}
.task-subtitle {
  display: block;
  font-size: 0.85rem;
  color: var(--fg-muted);
  margin-top: 0.15rem;
  line-height: 1.3;
}
.task-due {
  display: inline-block;
  font-size: 0.8rem;
  color: var(--fg-muted);
  margin-top: 0.3rem;
  letter-spacing: 0.01em;
}
.section.overdue .task-due { color: var(--overdue); font-weight: 500; }
.section.today .task-due { color: var(--today); }
/* Fallback for views that don't bucket cards into sections (Search). The */
/* server-side {% due_state task %} tag emits data-due-state on the card; */
/* this matches the same overdue/today treatment the section selectors    */
/* give on the Today page. Spec §2 (2026-05-13) — web↔Android parity.     */
.task-card[data-due-state="overdue"] .task-due { color: var(--overdue); font-weight: 500; }
.task-card[data-due-state="today"] .task-due { color: var(--today); }

/* Snooze button — sits at the right edge of the card. Form wrapper exists */
/* only to carry the CSRF token; it shouldn't add layout. */
.task-snooze-form {
  display: inline-flex;
  margin: 0;
  padding: 0;
  align-self: center;
  flex-shrink: 0;
}
.task-snooze {
  /* Both <button> (unsnooze form) and <a> (snooze GET → picker) wear this */
  /* class — keep style identical so a11y/affordance stays consistent. The */
  /* anchor needs explicit text-decoration:none + flex centering since */
  /* browsers underline links and don't auto-center their content. */
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: pointer;
  color: var(--fg-muted);
  font-size: 1.1rem;
  line-height: 1;
  padding: 0.35rem 0.5rem;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: center;
  flex-shrink: 0;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
/* accent-tinted via color-mix, not a literal violet rgba(): the hover wash has
   to follow the active theme, and the pair below must be able to reuse the
   exact same value without re-hardcoding a channel triple. */
.task-snooze:hover {
  border-color: var(--border);
  color: var(--fg);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
/* The unsnooze affordance is a <button type="submit"> inside .task-snooze-form,
   so `form button[type="submit"]` (0,1,2) outranks the single-class
   `.task-snooze` (0,1,0) above and repaints it as a solid accent pill — the
   loudest thing on /today/, directly beside a transparent snooze <a> that is
   meant to look identical. These descendant selectors are (0,2,0), which wins
   on class count, and restate exactly the properties the global rule sets.
   Same specificity trap (and same remedy) as the header's Sign out at
   `.nav-links .btn-ghost` below. `transition` has to be restated too: the
   global rule narrows it to `background` alone, which would make the border
   and text colour snap instead of ease on hover.
   `.task-unsnooze`'s slightly smaller font-size is deliberate and left alone —
   it only trims the undo glyph, it doesn't change the treatment. */
.task-snooze-form .task-snooze {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  color: var(--fg-muted);
  font-weight: 400;
  padding: 0.35rem 0.5rem;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.task-snooze-form .task-snooze:hover {
  border-color: var(--border);
  color: var(--fg);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
/* Snoozed card — slightly dimmed so the user reads it as "hibernating". */
/* Title stays full-strength; the rest fades. The wake-up time appears as */
/* an inline pill below the due date. */
.task-card-snoozed {
  opacity: 0.72;
}
.task-card-snoozed .task-title {
  color: var(--fg-muted);
}
.task-snooze-until {
  display: inline-block;
  margin-top: 0.3rem;
  margin-left: 0.5rem;
  font-size: 0.75rem;
  color: var(--fg-muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
}
.task-unsnooze {
  font-size: 1.05rem;
}

/* Pin toggle — sits beside .task-snooze, same button shell so the two icon
   affordances read as one visual family. Unpinned starts muted/outline-ish
   (shares .task-snooze's base treatment exactly); pinned goes full accent,
   which is the "filled" look the design calls for without needing a second
   SVG glyph (see #icon-pin's single symbol, reused for both states). */
.task-pin-form {
  display: inline-flex;
  margin: 0;
  padding: 0;
  align-self: center;
  flex-shrink: 0;
}
.task-pin {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: pointer;
  color: var(--fg-muted);
  font-size: 1.1rem;
  line-height: 1;
  padding: 0.35rem 0.5rem;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: center;
  flex-shrink: 0;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.task-pin:hover {
  border-color: var(--border);
  color: var(--fg);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
/* Same specificity trap as `.task-snooze-form .task-snooze` above: the pin
   is a <button type="submit"> inside .task-pin-form, so the global
   `form button[type="submit"]` (0,1,2) repainted the UNPINNED state as a
   solid accent pill — every open task looked already-pinned (user report
   2026-08-20). Restate the muted base at (0,2,0); the `.task-pin-active`
   rules below share that specificity and come later, so the pinned state
   still wins. */
.task-pin-form .task-pin {
  background: transparent;
  border: 1px solid transparent;
  color: var(--fg-muted);
  font-weight: 400;
  padding: 0.35rem 0.5rem;
  border-radius: 6px;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.task-pin-form .task-pin:hover {
  border-color: var(--border);
  color: var(--fg);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
/* Pinned (active) state — same specificity trap as .task-snooze-form
   .task-snooze above: the <button type="submit"> needs its own descendant
   rule to outrank the plain .task-snooze/.task-pin button reset, and to
   paint the "filled" accent look instead of the muted default. */
.task-pin-form .task-pin-active {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-color: transparent;
  color: var(--accent);
}
/* `border-color` is restated because the muted `.task-pin-form .task-pin:hover`
   above is (0,3,0) and would otherwise outrank this (0,2,0) rule, painting a
   grey outline around the pinned pill on hover. */
.task-pin-form .task-pin-active:hover {
  background: color-mix(in srgb, var(--accent) 24%, transparent);
  border-color: transparent;
  color: var(--accent);
}
/* Pinned card — a subtle accent tint instead of the opacity fade used for
   snoozed cards (that treatment reads as "dimmed / hidden", the opposite of
   what a pin means). Title stays full-strength. The accent border mirrors
   Android's pinned TaskRow (1dp accent @60% vs the default border) so the
   pin signal reads the same on both surfaces; 60% also outranks the 40%
   hover tint, so hovering a pinned card doesn't visibly weaken its border. */
.task-card-pinned,
.task-card-pinned:hover {
  background: color-mix(in srgb, var(--accent) 6%, var(--bg-card));
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
}
.section.pinned h2 {
  color: var(--accent);
}
.recurrence-icon {
  display: inline-block;
  margin-left: 0.4rem;
  color: var(--accent);
  opacity: 0.75;
}
.checklist-badge {
  display: inline-block;
  margin-top: 0.3rem;
  margin-left: 0.5rem;
  font-size: 0.75rem;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
  opacity: 0.85;
}

/* Inline subtasks toggle — replaces the static .checklist-badge with an
   interactive button. Tap toggles .checklist-expanded on the parent
   .task-card; CSS below shows/hides the panel accordingly. */
.checklist-badge-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.3rem;
  margin-left: 0.5rem;
  background: transparent;
  font-size: 0.75rem;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
  cursor: pointer;
  font-family: inherit;
  opacity: 0.85;
}
/* Badge + progress bar column — one flex item in the card row. */
.checklist-badge-col {
  display: inline-flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-top: 0.3rem;
  margin-left: 0.5rem;
  align-self: center;
  flex-shrink: 0;
}
.checklist-badge-col .checklist-badge-toggle {
  margin-top: 0;
  margin-left: 0;
}
/* Subtask progress bar — mirrors the Android card's done/total signal. */
.checklist-progress {
  display: block;
  height: 3px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}
/* Width comes from the inline --progress custom property the card template
   computes server-side (done/total). A custom property — not an inline
   `width:` — so the "no inline styles" lock can allow data and still reject
   layout (see tests/test_web_no_inline_styles.py). */
.checklist-progress-fill {
  display: block;
  height: 100%;
  width: var(--progress, 0%);
  background: var(--list-color, var(--accent));
  transition: width 0.3s ease;
}
.checklist-badge-toggle:hover { opacity: 1; }
.checklist-badge-toggle .chevron {
  display: inline-block;
  transition: transform 200ms ease;
}
.checklist-badge-toggle[aria-expanded="true"] .chevron {
  transform: rotate(90deg);
}

.checklist-panel {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0.4rem 0 0 0;
  border-top: 1px solid var(--border);
  display: none;
}
.task-card.checklist-expanded .checklist-panel {
  display: block;
}
.checklist-panel li {
  padding: 0.15rem 0;
  font-size: 0.9rem;
}
.checklist-panel label {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
}
.checklist-panel .checklist-text.done {
  text-decoration: line-through;
  opacity: 0.6;
}

/* Auto-complete toast (2-action: Desfazer / Concluir). */
.toast.toast-auto-complete {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(20, 20, 30, 0.96);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  padding: 0.75rem 1rem;
  display: flex;
  gap: 0.75rem;
  align-items: center;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  z-index: 1000;
  font-size: 0.9rem;
}
.toast.toast-auto-complete .toast-action {
  background: transparent;
  border: 1px solid var(--accent);
  color: var(--accent);
  padding: 0.25rem 0.75rem;
  border-radius: 0.25rem;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.85rem;
}
.toast.toast-auto-complete .toast-action:hover {
  background: var(--accent);
  color: var(--bg);
}

/* Server-side flash messages (e.g. post-list-delete toast). Reuses the
   bottom-center toast pattern; auto-dismissed by inline JS in base.html. */
.message-stack {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  z-index: 1000;
}
.toast.toast-flash {
  background: rgba(20, 20, 30, 0.96);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  padding: 0.75rem 1rem;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  font-size: 0.9rem;
  max-width: min(90vw, 32rem);
}
.toast.toast-flash.toast-success {
  border-color: var(--accent);
}
.toast.toast-flash.toast-error {
  border-color: var(--overdue);
}
.toast.toast-flash.fade-out {
  opacity: 0;
  transition: opacity 0.4s ease-out;
}
.empty-state {
  color: var(--fg-muted);
  text-align: center;
  margin-top: 3rem;
  font-size: 0.95rem;
}

/* Search-result snippet block: rendered under a task card when the match
   came from one or more checklist items. The card itself stays untouched
   (it is also used by Today/Upcoming where snippets don't apply). */
.search-snippets {
  list-style: none;
  margin: -0.4rem 0 0.9rem 2.4rem; /* tuck under the card, indent past the checkbox */
  padding: 0;
  font-size: 0.85rem;
  color: var(--fg-muted);
  font-style: italic;
}
.search-snippets li { line-height: 1.4; }
.search-snippets strong {
  color: var(--accent);
  font-weight: 600;
  font-style: normal;
}
.search-snippets-overflow { opacity: 0.7; }

/* Modals */
.modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}
.modal-content {
  background: var(--bg-card);
  color: var(--fg);
  padding: 1.75rem;
  border-radius: 12px;
  max-width: 420px;
  border: 1px solid var(--border);
}
.modal-content strong { color: var(--accent); }
.modal-content button {
  margin-left: 0.5rem;
  padding: 0.5rem 1.15rem;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  font-weight: 500;
}
.modal-content a {
  color: var(--fg-muted);
  text-decoration: none;
}
.modal-content a:hover { color: var(--fg); }

/* Snooze duration picker — stacks the three duration buttons vertically */
/* (matches Android's SnoozeOptionsDialog) instead of the default inline */
/* "Cancel | Confirm" pair. The Cancel link sits below as a low-emphasis */
/* escape hatch. */
.snooze-picker {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-top: 1rem;
}
.snooze-picker button {
  margin-left: 0;
  width: 100%;
}
/* Cancel is the escape hatch, not a fourth duration — it must not inherit the
   full-width accent treatment of the three <button type="submit"> durations
   above it, or the user reads four equivalent choices. */
.snooze-picker .btn-ghost {
  width: auto;
  align-self: center;
  margin-top: 0.35rem;
}
.snooze-picker a {
  text-align: center;
  margin-top: 0.35rem;
}

/* Inline-display form wrapper — used where multiple <form> elements need to
   sit side by side (e.g. the repeat-task Yes/No confirm, the lists page
   member actions). Forms are block-level by default; this is the one
   reusable escape hatch instead of a per-page inline style. */
.inline-form { display: inline; }

/* Native <dialog> styling (quit-confirm modal) */
dialog {
  background: var(--bg-card);
  color: var(--fg);
  padding: 1.5rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  max-width: 400px;
}
dialog::backdrop {
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(4px);
}
dialog strong { color: var(--accent); }
dialog button {
  margin-left: 0.5rem;
  padding: 0.45rem 1rem;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
}
dialog button[value="cancel"] {
  background: transparent;
  color: var(--fg-muted);
  border: 1px solid var(--border);
}

/* ── Shared app dialogs (htmx-served into #modal-root) ──────────────── */
.app-dialog {
  min-width: 340px;
  max-width: 480px;
}
.app-dialog-title {
  margin: 0 0 0.35rem;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--fg);
}
.app-dialog-subtitle {
  margin: 0 0 0.75rem;
  font-size: 0.9rem;
  color: var(--fg-muted);
}
.app-dialog-hint {
  margin: 0 0 0.75rem;
  font-size: 0.85rem;
  color: var(--fg-muted);
}
.app-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 1rem;
}
/* This row spaces its children with `gap`. The legacy `margin-left: 0.5rem`
   on `dialog button` / `.modal-content button` would stack on top of it —
   1rem between buttons plus 0.5rem of dead margin before the first one. Zeroed
   here rather than deleted there: the repeat-task dialog's Yes/No pair sits in
   two sibling `.inline-form`s with no flex container, and that margin is the
   only thing keeping those two buttons apart. */
.app-dialog-actions button { margin-left: 0; }

/* Dialog entry animation — covers every <dialog> in the app: .app-dialog
   reaches #composer-dialog, #confirm-action-dialog and #confirm-dialog (all
   three carry class="app-dialog"); #quit-confirm-modal is listed explicitly
   because it doesn't carry that class. */
.app-dialog[open], #quit-confirm-modal[open] {
  animation: dialog-in 0.18s ease-out;
}
@keyframes dialog-in {
  from { opacity: 0; transform: scale(0.96) translateY(8px); }
}

/* Forms */
form p { margin: 0.85rem 0; }
/* `<div class="form-row">` is used for form rows that nest block-level
   children (e.g., a date input plus a row of preset chip buttons) — `<p>`
   is invalid HTML5 around `<div>`. Same vertical rhythm as `<p>`. */
form .form-row { margin: 0.85rem 0; display: block; }
form label {
  display: block;
  font-size: 0.85rem;
  color: var(--fg-muted);
  margin-bottom: 0.3rem;
  letter-spacing: 0.01em;
}
form input[type="text"],
form input[type="search"],
form input[type="datetime-local"],
form input[type="number"],
form select,
form textarea {
  background: var(--bg-input);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.55rem 0.75rem;
  width: 100%;
  font-family: inherit;
  font-size: 0.95rem;
}
form input:focus,
form select:focus,
form textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.2);
}
form input[type="number"] { width: auto; }

/* Date-preset chip row above the datetime-local input. Outlined / subdued
   look so it complements (rather than competes with) the primary "Save"
   button below. Wraps to 2 rows on narrow viewports. */
.date-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.25rem 0 0.5rem;
}
.date-presets .preset-btn {
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--border);
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.9rem;
  transition: border-color 0.15s, background 0.15s;
}
.date-presets .preset-btn:hover {
  border-color: var(--accent);
  background: rgba(168, 85, 247, 0.08);
}

form button[type="submit"] {
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  padding: 0.55rem 1.2rem;
  border-radius: 8px;
  cursor: pointer;
  font-family: inherit;
  font-weight: 600;
  transition: background 0.15s;
}
form button[type="submit"]:hover { background: var(--accent-hover); }
form a {
  color: var(--fg-muted);
  margin-left: 0.6rem;
  text-decoration: none;
}
form a:hover { color: var(--fg); }

.error {
  color: var(--overdue);
  font-size: 0.85rem;
  display: block;
  margin-top: 0.2rem;
}

.optional-field { opacity: 0.6; transition: opacity 0.2s; }
.optional-field:focus-within { opacity: 1; }
.optional-field textarea {
  background: rgba(31, 18, 51, 0.6);
}
.muted {
  font-size: 0.78rem;
  color: var(--fg-muted);
  font-style: italic;
  margin-left: 0.3rem;
}

/* Selection */
::selection {
  background: rgba(168, 85, 247, 0.35);
  color: var(--fg);
}

/* ─ Login page ─────────────────────────────────────────────
   Full-bleed cosmic background asset (mirrors the Android
   pre-login screen — same WebP file, served from `tasks/img/`).
   No card chrome — content sits directly on the image with the
   4-line tagline + Google button centered vertically. The dark
   gradient overlay deepens the lower half so text stays legible
   over any cropping of the underlying image. */
.login-body {
  min-height: 100vh;
  background:
    linear-gradient(180deg, rgba(13, 11, 20, 0.35) 0%, rgba(13, 11, 20, 0.7) 100%),
    url("img/hindsight_prelogin_background.4bddda3092e5.webp") center / cover no-repeat,
    #0d0b14;
  display: flex;
}
.login-shell {
  margin: auto;
  padding: 3rem 1.25rem;
  width: 100%;
  max-width: 460px;
  text-align: center;
}
.login-brand {
  margin: 0 0 1.75rem 0;
  display: inline-flex;
  align-items: flex-start;
  gap: 0.4rem;
  font-size: clamp(2.5rem, 8vw, 3.75rem);
  font-weight: 300;
  letter-spacing: 0.5px;
  color: #f2f2f7;
  line-height: 1.1;
}
.login-brand-sparkle {
  width: 1.1rem;
  height: 1.1rem;
  margin-top: 0.25rem;
  color: var(--fg);
  flex-shrink: 0;
}
.login-tagline {
  margin: 0 0 2.5rem 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.login-tagline p {
  margin: 0;
  font-size: 1.05rem;
}
.login-tagline-lead {
  color: #f2f2f7;
  margin-bottom: 0.85rem !important;
  font-size: 1.15rem !important;
}
.login-tagline-accent {
  color: #a78bfa;
}
.login-shell .g_id_signin {
  display: inline-block;
}

/* ─ Lists feature ─────────────────────────────────────────────
   Chips on the home view, the dedicated /lists/ CRUD page, and
   the 8-swatch color picker in the create/edit form. List-color
   is delivered via the --list-color custom property on each
   element so the same dot/stripe styles work for any palette
   value without per-color CSS classes. */

/* Filter chips on the home view */
.list-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.85rem 0 1rem;
}
.list-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--fg);
  font-size: 0.85rem;
  text-decoration: none;
  transition: border-color 0.15s, background 0.15s;
}
.list-chip:hover {
  border-color: var(--accent);
}
/* Selected highlight: solid lavender-white fill with the bg color flipped to */
/* dark text. This "inverse" treatment is dramatically louder than a tinted   */
/* bg and reads as an unambiguous active state at a glance — and it doesn't  */
/* compete with the violet dot that the Default chip already carries.        */
.list-chip.selected {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
  font-weight: 600;
}
.list-chip.muted-chip {
  color: var(--fg-muted);
  border-style: dashed;
}

/* Color dot — used on chips and on the /lists/ rows */
.list-color-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--list-color, var(--accent));
  flex-shrink: 0;
}

/* /lists/ page */
.lists-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0 0 1rem;
}
.lists-header h2 {
  margin: 0;
  font-size: 1.4rem;
  font-weight: 600;
}
.lists-collection {
  list-style: none;
  padding: 0;
  margin: 0 0 1.5rem;
}
.list-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  border-radius: 8px;
  background: var(--bg-card);
  margin-bottom: 0.5rem;
}
.list-row-name {
  flex: 1;
  font-weight: 500;
}
.badge-default {
  display: inline-block;
  padding: 0.1em 0.5em;
  margin-left: 0.5em;
  font-size: 0.7em;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  background: var(--accent, #8b5cf6);
  color: var(--on-accent);
  border-radius: 999px;
  vertical-align: middle;
}
.list-row-actions {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.85rem;
}
.list-row-actions a,
.list-row-actions .link-button {
  color: var(--fg-muted);
  text-decoration: none;
  background: none;
  border: none;
  cursor: pointer;
  font: inherit;
  padding: 0;
}
.list-row-actions a:hover,
.list-row-actions .link-button:hover {
  color: var(--fg);
}

/* Color picker — 8 swatches in a row */
.color-picker {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.25rem;
}
.color-swatch {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--list-color, var(--accent));
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
  transition: border-color 0.15s, transform 0.1s;
}
.color-swatch:hover {
  transform: scale(1.05);
}
.color-swatch.selected {
  border-color: var(--fg);
  box-shadow: 0 0 0 2px var(--bg);
}

/* ── Aggregation suggestions banner + cards ──────────────────────────── */
/* Accent-tinted via color-mix so all five themes keep the banner in their own
   hue — it sits directly above the onboarding banner on /today/, which does
   the same. A literal violet rgba() made them disagree on four of five. */
.suggestions-banner,
.insights-banner {
  display: block;
  padding: 0.75rem 1.25rem;
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  color: var(--fg);
  text-decoration: none;
  font-weight: 500;
}
.suggestions-banner:hover,
.insights-banner:hover {
  background: color-mix(in srgb, var(--accent) 25%, transparent);
}

/* ── Insights: LIST_FLOW card (created vs completed per list, 7d) ──────── */
/* Same two series colors and legend as the Android Trend/ListFlow cards:
   created = --accent, completed = teal. Value labels wear the text token,
   never the series color (dataviz rule shared with the Android side). */
.insight-list-flow-rows {
  list-style: none;
  padding: 0;
  margin: 0.5rem 0 0.75rem;
  display: grid;
  row-gap: 0.6rem;
}
.insight-list-flow-row {
  display: grid;
  grid-template-columns: 78px minmax(0, 1fr);
  column-gap: 0.6rem;
  align-items: center;
  font-size: 0.8rem;
}
.insight-list-flow-name {
  color: var(--fg-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.insight-list-flow-bars {
  display: grid;
  row-gap: 3px;
}
.insight-list-flow-bar-line {
  display: flex;
  align-items: center;
  gap: 6px;
}
.insight-list-flow-bar-line b {
  color: var(--fg);
  min-width: 1.2em;
}
.insight-list-flow-bar {
  display: inline-block;
  height: 8px;
  max-width: 85%;
  border-radius: 0 4px 4px 0;
}
.insight-list-flow-created,
.insight-dot-created {
  background: var(--accent);
}
.insight-list-flow-completed,
.insight-dot-completed {
  background: #0d9488;
}
.insight-legend {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.75rem;
  color: var(--fg-muted);
}
.insight-legend .insight-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
}
.insight-legend .insight-dot-completed {
  margin-left: 10px;
}

.suggestion-card {
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  padding: 1rem;
  margin-bottom: 1rem;
  background: rgba(255, 255, 255, 0.03);
}
/* The base class carries the pill's geometry; without a background the padding
   and radius are invisible, so the list-level badge types (create_list,
   add_to_list, rename_list, merge_lists, append) rendered as bare floating
   text. A neutral foreground-tinted fill gives them the pill the base class
   already implies, without inventing a per-type palette. The former
   `.suggestion-badge-split` / `.suggestion-badge-merge` typed variants were
   removed in the card redesign (2026-08-22) — SPLIT and MERGE now render
   their own badges inline (preview-item-badge / live-count), not this
   type-chip pill. */
.suggestion-badge {
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 0.75rem;
  font-weight: 600;
  background: color-mix(in srgb, var(--fg) 12%, transparent);
  color: var(--fg-muted);
}
/* ── Task checklist (from accepted aggregation suggestions) ─────────── */
/* Accent-tinted via color-mix: the literal rgba() below used to be a stale
   violet that stayed violet on ember/eclipse/deep-ocean/comet. */
.checklist-fieldset {
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-radius: 8px;
  padding: 0.5rem 1rem 0.75rem;
  margin: 1rem 0;
}
.checklist-fieldset legend {
  padding: 0 0.5rem;
  font-size: 0.85rem;
  opacity: 0.7;
}
.checklist-items {
  list-style: none;
  padding: 0;
  margin: 0.25rem 0 0;
}
.checklist-items li {
  margin-bottom: 0.25rem;
  display: flex;
  align-items: center;
  gap: 0.25rem;
}
/* The growing element of a subtask row, in both shapes this list is built in:
   the edit page renders `li > label > input + span` (the label is the flex
   child), while the composer's JS builds `li > span.checklist-text` with no
   label — there is no checkbox to associate, so wrapping it in one would be
   markup for markup's sake. Without the second selector nothing in the
   composer's row grows, and the trailing "×" tracks the label's text width
   instead of pinning to the row's right edge (measured 496px vs 756px for two
   rows of different lengths). `li > .checklist-text` cannot reach the edit
   page's spans — those are nested inside the <label>, not direct children of
   the <li> — so this stays clear of a template that is out of bounds. */
.checklist-items li > label,
.checklist-items li > .checklist-text {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.checklist-text.done {
  text-decoration: line-through;
  opacity: 0.55;
}

/* Inline "×" beside each subtask on the edit form. Subtle by default,
   warms to a delete-red on hover so a misclick is recoverable (HTMX
   request happens on click — no confirmation, parallel to the toggle). */
.checklist-delete-btn {
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.45);
  font-size: 1.2rem;
  line-height: 1;
  padding: 0 0.4rem;
  cursor: pointer;
  border-radius: 4px;
}
.checklist-delete-btn:hover,
.checklist-delete-btn:focus-visible {
  color: var(--overdue);
  background: rgba(244, 63, 94, 0.1);
}

/* Add-subtask inline form below the list. Compact row — input takes the
   remaining width, the "+" button is square. */
.checklist-add-form {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.5rem;
}
.checklist-add-form input[type="text"] {
  flex: 1;
  padding: 0.35rem 0.5rem;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
  border-radius: 6px;
  color: inherit;
  font: inherit;
}
.checklist-add-form input[type="text"]:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
}
/* The composer's add button is reached by its id, NOT by widening the shared
   selector above it. Two reasons: (1) an id (1,0,0) is the only thing that
   beats the generic `dialog button` rule, which otherwise renders it as an
   oversized solid accent pill inside the composer dialog; (2) the edit page's
   add button is `type="button"` with no class too, so `.checklist-add-form
   button` would silently restyle a page that is out of bounds this cycle.
   One declaration block, two selectors, each keeping its own specificity. */
.checklist-add-form button[type="submit"],
#new-task-subtask-add {
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  margin: 0;
  font-size: 1.1rem;
  font-family: inherit;
  line-height: 1;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: 6px;
  color: inherit;
  cursor: pointer;
}
.checklist-add-form button[type="submit"]:hover,
#new-task-subtask-add:hover {
  background: color-mix(in srgb, var(--accent) 30%, transparent);
}

/* ── Cohesion kit (Web Love cycle 1) ─────────────────────────────────────
   Every declaration here resolves through the theme variables at the top of
   this file — five palettes ride on them, so a literal hex/rgba would break
   four of them silently. `.inline-form` is NOT redefined here; it already
   exists above. */
.btn {
  display: inline-flex;
  align-items: center;
  /* Explicit — the snooze picker stretches its .btn children to width:100%,
     and a flex container defaults to flex-start (label glued to the left). */
  justify-content: center;
  gap: 0.4rem;
  padding: 0.5rem 1.1rem;
  border-radius: 8px;
  border: 1px solid transparent;
  cursor: pointer;
  font-family: inherit;
  font-weight: 600;
  font-size: 0.9rem;
  text-decoration: none;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
/* Keyboard focus ring. Before this the app had no focus styling at all, so
   every focusable control fell back to Chromium's `outline: auto rgb(0,95,204)`
   — a stock blue double ring that clashes with violet and badly with amber. The
   most visible instance was the snooze dialog: showModal() autofocuses the
   first duration button, so the blue ring appeared every single time a user
   snoozed anything.

   `:focus-visible` (not `:focus`) so a mouse click doesn't paint a ring, and
   `outline-offset` so the ring sits clear of the control's own border rather
   than on top of it. This is deliberately NOT `outline: none` anywhere —
   keyboard users need the indicator; it is only being re-themed.

   Text inputs are intentionally not reached by this: `form input:focus` /
   `.search input:focus` (0,2,1) outrank this (0,1,0) and already provide their
   own indicator (accent border + accent box-shadow ring). */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.btn-primary { background: var(--accent); color: var(--on-accent); }
.btn-primary:hover { background: var(--accent-hover); }
/* A solid-accent surface would wear an accent ring in its own hue, separated
   only by the 2px offset gap. Switch to the foreground colour so the ring stays
   legible on the fill — this is the snooze dialog's autofocused button. */
.btn-primary:focus-visible { outline-color: var(--fg); }
.btn-ghost { background: transparent; color: var(--fg-muted); border-color: var(--border); }
.btn-ghost:hover { color: var(--fg); border-color: var(--accent); }

/* The header's Sign out is a <button type="submit"> inside a <form>, so
   `form button[type="submit"]` (0,1,2) outranks the single-class kit rules and
   would repaint it as the loudest accent pill on the page. These two-class
   selectors (0,2,0) win it back and keep the compact header sizing the retired
   `header button` rule used to provide. Retiring `form button[type="submit"]`
   itself is a repo-wide audit, not this task. */
.nav-links .btn {
  border: 1px solid transparent;
  padding: 0.35rem 0.85rem;
  border-radius: 6px;
  font-size: 0.85rem;
  font-weight: 500;
  /* Restated because `form button[type="submit"]` (0,1,2) narrows the kit's
     transition list down to `background` alone — without this the border and
     text colour would snap on hover instead of easing. Same list the retired
     `header button` rule carried. */
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.nav-links .btn-ghost { background: transparent; color: var(--fg-muted); border-color: var(--border); }
/* `background` is restated here on purpose: `form button[type="submit"]:hover`
   is (0,2,2) and would otherwise repaint the resting ghost into a solid accent
   pill the moment the pointer lands on it — the exact loudness this treatment
   set out to remove.

   The hover feedback is a faint accent *wash*, deliberately not a border and
   not brighter text. The earlier version set `border-color: var(--accent)` plus
   `color: var(--fg)`, which handed a secondary control a fully saturated ring
   and text at exactly the wordmark's brightness — measured brighter than the
   wordmark in both themes, so hovering Sign out made it the loudest element in
   the header. Keeping the border at `--border` and the text at `--fg-muted`
   preserves the hierarchy (wordmark > nav links > Sign out) while the
   background tint still gives an unmistakable "this is clickable" response. */
.nav-links .btn-ghost:hover {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--fg-muted);
  border-color: var(--border);
}

/* Page-level CTA rhythm for the "+ New task" button (was .new-task-btn, now
   .btn.btn-primary + this margin). */
.new-task-cta { margin: 1rem 0 1.5rem; }

/* Header nav. `aria-current="page"` is emitted by base.html from
   request.resolver_match.view_name, so the active tab is both announced by
   screen readers and painted in the accent color. */
.nav-links { display: inline-flex; gap: 0.85rem; align-items: center; }
.nav-links a { color: var(--fg); opacity: 0.7; transition: opacity 0.15s, color 0.15s; }
.nav-links a:hover { opacity: 1; }
.nav-links a[aria-current="page"] { opacity: 1; color: var(--accent); }

.site-footer { text-align: center; opacity: 0.55; font-size: 0.8rem; padding: 1.5rem 0; }
.site-footer a { color: var(--fg); }

.section-block { margin-top: 2.5rem; }
.section-block h2 { font-size: 1.05rem; margin-bottom: 0.4rem; }
.about-me-form textarea {
  width: 100%;
  max-width: 40rem;
  background: var(--bg-input);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.6rem 0.75rem;
  font-family: inherit;
  font-size: 0.95rem;
  line-height: 1.5;
  resize: vertical;
}
.about-me-form textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.2);
}
.page-title { margin-bottom: 1rem; }
.text-muted { opacity: 0.75; font-size: 0.9rem; margin-bottom: 0.6rem; }
.text-muted-sm { opacity: 0.55; font-size: 0.85rem; }
.success-note { color: var(--success); }
.link-danger { color: var(--overdue); }
/* `form input[type="number"] { width: auto }` above outranks a bare class
   selector, so the narrow-field override has to match that specificity. */
.field-narrow,
form input[type="number"].field-narrow { width: 4rem; }
/* Off-screen SVG symbol sprite (tasks/_icons.html). */
.icon-sprite { position: absolute; }

/* First-visit onboarding banner on /today/. Accent-tinted via color-mix so
   all five themes keep the banner in their own hue (the same technique
   .task-card:hover uses for its border). */
.onboarding-banner {
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: 8px;
  padding: 0.85rem 1rem;
  margin-bottom: 1rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
}
/* Descendant-scoped on purpose: the [×] is a submit button, and
   `form button[type="submit"]` above would otherwise paint it as a big accent
   pill. Two classes outrank one class + one attribute. */
.onboarding-banner .onboarding-dismiss {
  background: none;
  border: none;
  border-radius: 0;
  color: var(--fg);
  opacity: 0.7;
  font-size: 1.2rem;
  font-weight: 400;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.25rem;
}
.onboarding-banner .onboarding-dismiss:hover { opacity: 1; }

/* Suggestions review screen (/suggestions/) */
.next-batch-list { margin-top: 0.25rem; }
.suggestion-card-header {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.5rem;
}
.suggestion-reasoning { opacity: 0.85; margin: 0.5rem 0; }
.suggestion-due { margin: 0.25rem 0; opacity: 0.85; }
.suggestion-affected { margin-top: 0.5rem; }
.suggestion-affected > summary { cursor: pointer; opacity: 0.7; }
.suggestion-task-list,
.suggestion-checklist {
  list-style: none;
  padding-left: 0;
}
.suggestion-task-list { margin: 0.5rem 0 0.5rem 1rem; }
.suggestion-task-list > li { margin-bottom: 0.35rem; }
.suggestion-checklist { margin: 0.25rem 0 0.5rem 1rem; }
.suggestion-checklist > li { margin-bottom: 0.25rem; }
/* Opt-out checkboxes live in both list shapes (the checklist ul on the append
   card, the task ul on create-list / add-to-list), so the pointer cursor has to
   cover both — the list-level partials used to carry it as an inline style. */
.suggestion-checklist label,
.suggestion-task-list label { cursor: pointer; }
.suggestion-checklist-heading { margin-top: 0.75rem; }
/* Single-line "outcome" rows on the list-level cards (create-list / add-to-list
   / rename-list / merge-lists) — the new/target name plus its badge(s). Not a
   checklist item, just a labeled result line, so it gets its own light rule
   instead of overloading `.suggestion-checklist-heading` or `.suggestion-due`. */
.suggestion-result { margin: 0.5rem 0; }
.suggestion-form { margin: 0; }
.suggestion-actions { margin-top: 0.75rem; display: flex; gap: 0.5rem; }
.suggestion-meta { opacity: 0.7; }
.suggestion-stale-note { opacity: 0.85; font-style: italic; }
/* Source row the server marked as no longer applicable — Apply drops it. */
.is-stale { opacity: 0.5; text-decoration: line-through; }
/* Opt-out cards: preview row whose paired source was unchecked — the source
   stays as-is, so the proposed line is visually withdrawn. Shared by every
   card that pairs source rows 1:1 with preview rows (append-to-checklist
   today; create-list / add-to-list / merge follow the same contract). */
.preview-item.is-excluded { opacity: 0.45; text-decoration: line-through; }
.preview-item-badge { margin-left: 0.35rem; }
/* Long task titles inside a suggestion row: one line, ellipsis, no reflow. */
.truncate-inline {
  display: inline-block;
  max-width: 24rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}
.suggestions-history { border-top: 1px solid var(--border); padding-top: 1rem; }
.suggestions-history-note { opacity: 0.7; font-size: 0.85rem; margin-bottom: 0.75rem; }
.suggestions-history-list { list-style: none; padding: 0; margin: 0; }
.suggestions-history-list > li {
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--border);
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
  flex-wrap: wrap;
}
.suggestions-history-list > li > strong { min-width: 4.5rem; }
.history-type { opacity: 0.7; font-size: 0.85rem; }
.history-when { opacity: 0.55; font-size: 0.85rem; }
.history-label { opacity: 0.85; }

/* Accessibility: collapse all animation for users who ask for it. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0.01ms !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0.01ms !important;
  }
  /* The universal selector above does not reach View Transitions API
     pseudo-elements (they aren't ordinary DOM nodes), so the #task-list
     hx-swap="transition:true" cross-fade would otherwise still run at full
     speed for a user who explicitly asked for reduced motion. Disable it
     explicitly — the swap still happens, just without the animated
     cross-fade. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}
