/* Three selectable themes (light default, dark, warm) sharing the same
   oklch tokens as TM5A for visual consistency across the app family.
   Status/priority accent colors (--waiting..--urgent) deliberately stay
   constant across themes -- they carry semantic meaning (red=urgent,
   green=done) that shouldn't shift with the surface palette. */
:root[data-theme='light'] {
  --primary: oklch(0.55 0.16 260);
  --primary-dark: oklch(0.42 0.16 260);
  --bg: oklch(0.985 0.003 90);
  --card: #ffffff;
  --border: oklch(0.91 0.005 90);
  --text: oklch(0.22 0.01 90);
  --muted: oklch(0.55 0.005 90);
  --waiting: #94a3b8;
  --notStarted: #64748b;
  --inProgress: #2563eb;
  --done: #16a34a;
  --low: #64748b;
  --medium: #4f46e5;
  --high: #ea580c;
  --urgent: #dc2626;
  --card-shadow: 0 1px 1px rgba(0, 0, 0, 0.05), 0 3px 6px -2px rgba(0, 0, 0, 0.08), 0 10px 20px -8px rgba(0, 0, 0, 0.14);
  --card-shadow-hover: 0 2px 4px rgba(0, 0, 0, 0.08), 0 8px 16px -4px rgba(0, 0, 0, 0.14), 0 24px 36px -12px rgba(0, 0, 0, 0.22);
  --card-highlight: rgba(255, 255, 255, 0.75);
}

:root[data-theme='dark'] {
  --primary: oklch(0.55 0.17 200);
  --primary-dark: oklch(0.42 0.17 200);
  --bg: oklch(0.19 0.015 260);
  --card: oklch(0.24 0.015 260);
  --border: oklch(1 0 0 / 0.08);
  --text: oklch(0.95 0.005 260);
  --muted: oklch(0.62 0.01 260);
  --waiting: #94a3b8;
  --notStarted: #64748b;
  --inProgress: #2563eb;
  --done: #16a34a;
  --low: #64748b;
  --medium: #4f46e5;
  --high: #ea580c;
  --urgent: #dc2626;
  --card-shadow: 0 1px 1px rgba(0, 0, 0, 0.35), 0 4px 8px -3px rgba(0, 0, 0, 0.4), 0 12px 22px -10px rgba(0, 0, 0, 0.55);
  --card-shadow-hover: 0 3px 6px rgba(0, 0, 0, 0.4), 0 10px 20px -4px rgba(0, 0, 0, 0.5), 0 28px 40px -12px rgba(0, 0, 0, 0.65);
  --card-highlight: rgba(255, 255, 255, 0.06);
}

:root[data-theme='warm'] {
  --primary: oklch(0.6 0.14 40);
  --primary-dark: oklch(0.46 0.14 40);
  --bg: oklch(0.96 0.015 75);
  --card: oklch(0.99 0.008 75);
  --border: oklch(0.87 0.02 75);
  --text: oklch(0.3 0.02 60);
  --muted: oklch(0.55 0.02 60);
  --waiting: #94a3b8;
  --notStarted: #64748b;
  --inProgress: #2563eb;
  --done: #16a34a;
  --low: #64748b;
  --medium: #4f46e5;
  --high: #ea580c;
  --urgent: #dc2626;
  --card-shadow: 0 1px 1px rgba(90, 60, 20, 0.07), 0 3px 6px -2px rgba(90, 60, 20, 0.1), 0 10px 20px -8px rgba(90, 60, 20, 0.18);
  --card-shadow-hover: 0 2px 4px rgba(90, 60, 20, 0.1), 0 8px 16px -4px rgba(90, 60, 20, 0.16), 0 24px 36px -12px rgba(90, 60, 20, 0.26);
  --card-highlight: rgba(255, 255, 255, 0.8);
}

:root[data-theme='ocean'] {
  --primary: oklch(0.58 0.13 195);
  --primary-dark: oklch(0.44 0.13 195);
  --bg: oklch(0.97 0.015 205);
  --card: oklch(0.995 0.006 205);
  --border: oklch(0.88 0.025 200);
  --text: oklch(0.26 0.03 220);
  --muted: oklch(0.55 0.02 210);
  --waiting: #94a3b8;
  --notStarted: #64748b;
  --inProgress: #2563eb;
  --done: #16a34a;
  --low: #64748b;
  --medium: #4f46e5;
  --high: #ea580c;
  --urgent: #dc2626;
  --card-shadow: 0 1px 1px rgba(20, 70, 90, 0.07), 0 3px 6px -2px rgba(20, 70, 90, 0.1), 0 10px 20px -8px rgba(20, 70, 90, 0.18);
  --card-shadow-hover: 0 2px 4px rgba(20, 70, 90, 0.1), 0 8px 16px -4px rgba(20, 70, 90, 0.16), 0 24px 36px -12px rgba(20, 70, 90, 0.26);
  --card-highlight: rgba(255, 255, 255, 0.85);
}

/* Self-hosted so the installed PWA keeps its font offline and no page view
   leaks to fonts.googleapis.com (spec 001 RF-06). Both are variable fonts:
   one file per subset covers 400-700, which is why `font-weight` is a range
   and there is no separate bold file to load. */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/fraunces-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/nunito-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/nunito-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* One token for the whole app's typeface (spec 001 RF-01). Every other
   `font-family` in this file is `inherit`, so changing this one custom
   property on :root restyles the entire UI -- that is what the Font picker
   in the appearance popover writes to. Kept out of the per-theme blocks
   above on purpose: font and palette are independent choices, the same way
   the custom background is.
   Cooper BT is Ana's pick (WhatsApp, 2026-08-20). It is a commercial
   Bitstream face we cannot ship, so the stack uses it where it happens to
   be installed and falls back to Fraunces, the closest free soft serif,
   everywhere else -- notably on phones (RF-05). Cooper Black is
   deliberately NOT in the stack: it is a display weight, and a machine
   that has it but not Cooper BT would render the whole UI in fat black. */
:root {
  --font-cooper: "Cooper BT", "Cooper Lt BT", "CooperLtBT", "Fraunces", Georgia, serif;
  --font-system: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-serif: Georgia, "Times New Roman", Times, serif;
  --font-rounded: "Nunito", "Segoe UI Variable Display", -apple-system, BlinkMacSystemFont, sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", Consolas, "SFMono-Regular", Menlo, monospace;
  --app-font: var(--font-cooper);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--app-font);
  background: var(--bg);
  color: var(--text);
  transition: background 0.15s ease, color 0.15s ease;
}

.hidden { display: none !important; }

/* ── App shell: collapsible sidebar + main content ─────────────────────── */
.app-shell {
  display: flex;
  min-height: 100vh;
  overflow-x: hidden;
}

.sidebar {
  width: 260px;
  flex-shrink: 0;
  background: var(--card);
  border-right: 1px solid var(--border);
  padding: 20px 18px;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  transition: margin-left 0.25s ease;
}
.app-shell.sidebar-collapsed .sidebar { margin-left: -260px; }

.sidebar-backdrop {
  display: none;
}

.sidebar-header {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 900;
  font-size: 18px;
  margin-bottom: 18px;
}
.sidebar-badge {
  width: 32px; height: 32px;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
  color: white;
  display: flex; align-items: center; justify-content: center;
  font-weight: 900;
  flex-shrink: 0;
}

.sidebar-section {
  border-top: 1px solid var(--border);
  padding-top: 14px;
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.sidebar-section:first-of-type { border-top: none; margin-top: 0; padding-top: 0; }

.sidebar-label {
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: 6px 0 -2px;
}

.sidebar select { width: 100%; }

.sidebar-link {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  padding: 8px 6px;
  border-radius: 8px;
  transition: background 0.15s;
}
.sidebar-link:hover { background: var(--bg); color: var(--text); }
.sidebar-link-btn {
  text-align: left;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
}

/* Mobile: sidebar becomes an overlay drawer instead of pushing content */
@media (max-width: 768px) {
  .sidebar {
    position: fixed;
    top: 0; left: 0; bottom: 0;
    z-index: 210;
    width: 270px;
    margin-left: -270px;
    box-shadow: 8px 0 30px -6px rgba(15, 23, 42, 0.25);
  }
  .app-shell:not(.sidebar-collapsed) .sidebar { margin-left: 0; }
  .app-shell:not(.sidebar-collapsed) .sidebar-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
    z-index: 200;
  }
}

.main-content {
  flex: 1;
  min-width: 0;
  width: 100%;
}

.topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 20px;
  flex-wrap: wrap;
}
/* 5 icon buttons (filter/sort/theme/AI/triage) plus the title don't fit on
   one line under ~600px without crushing the title down to a couple of
   characters (confirmed via screenshot: "Task Manager Pro" became "T...").
   Drop the controls to their own row rather than let the title keep
   losing the fight for space. */
@media (max-width: 600px) {
  .topbar-actions {
    flex-basis: 100%;
    justify-content: flex-end;
    margin-left: 0;
  }
}
.topbar-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 900;
  font-size: 18px;
  flex: 0 1 auto;
  min-width: 0;
}

/* Sits between the title and the action icons and absorbs the topbar's
   remaining flex space so the search box centers itself regardless of how
   long the title text is, instead of just trailing the title. */
.topbar-search-wrap {
  flex: 1 1 160px;
  display: flex;
  justify-content: center;
  min-width: 0;
}
.topbar-search {
  width: 100%;
  max-width: 320px;
  padding: 8px 12px;
  border: 1.5px solid var(--border);
  border-radius: 10px;
  font-size: 13px;
  font-family: inherit;
  color: var(--text);
  background: var(--card);
}
/* Matches the existing rule that drops .topbar-actions to its own row below
   600px — the search box gets its own row too rather than fighting the
   title for the remaining sliver of space. */
@media (max-width: 600px) {
  .topbar-search-wrap { flex-basis: 100%; order: 2; }
  .topbar-actions { order: 3; }
}
.topbar-title-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.topbar-badge {
  width: 26px; height: 26px;
  border-radius: 8px;
  background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
  color: white;
  display: flex; align-items: center; justify-content: center;
  font-weight: 900;
  font-size: 13px;
}

.version-badge {
  font-size: 10px;
  font-weight: 700;
  color: var(--muted);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px 8px;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
/* Narrow phones: full title + badge + hamburger + 2 icon buttons won't all
   fit on one line — drop the badge there rather than let it wrap/overflow. */
@media (max-width: 480px) {
  .topbar-title .version-badge { display: none; }
}

.icon-btn {
  border: 1.5px solid var(--border);
  background: var(--card);
  border-radius: 10px;
  width: 40px; height: 40px;
  font-size: 17px;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  transition: border-color 0.15s;
}
.icon-btn:hover { border-color: var(--primary); }

.topbar-actions {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.topbar-menu {
  position: relative;
}
.topbar-popover {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--card-shadow-hover);
  padding: 12px;
  width: 200px;
  max-width: calc(100vw - 16px);
  z-index: 220;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.topbar-popover select {
  width: 100%;
  padding: 8px 10px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  font-size: 13px;
  font-family: inherit;
  color: var(--text);
  background: var(--bg);
  margin-bottom: 8px;
}
.topbar-popover select:last-child { margin-bottom: 0; }
.theme-option {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 10px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  font-size: 13px;
  font-family: inherit;
  color: var(--text);
  background: var(--bg);
  cursor: pointer;
  margin-bottom: 4px;
}
.theme-option:last-child { margin-bottom: 0; }
.theme-option.active {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 12%, var(--bg));
  font-weight: 700;
}

/* Same chip as .theme-option, minus the `font-family: inherit` -- each row
   is written in the font it selects, so you can compare them without
   applying one, closing the menu and coming back (Ana's complaint about
   FlipTimer's picker, WhatsApp 2026-08-21). The font name therefore must
   NOT inherit; it is set per button from its data-font. */
.font-option {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 10px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  color: var(--text);
  background: var(--bg);
  cursor: pointer;
  margin-bottom: 4px;
}
.font-option:last-child { margin-bottom: 0; }
.font-option.active {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 12%, var(--bg));
  font-weight: 700;
}
.font-option[data-font='cooper'] { font-family: var(--font-cooper); }
.font-option[data-font='system'] { font-family: var(--font-system); }
.font-option[data-font='serif'] { font-family: var(--font-serif); }
.font-option[data-font='rounded'] { font-family: var(--font-rounded); }
.font-option[data-font='mono'] { font-family: var(--font-mono); }

.checkbox-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 8px;
}
.checkbox-group label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text);
  cursor: pointer;
}
.checkbox-group input[type="checkbox"] { flex-shrink: 0; }

.popover-divider {
  border-top: 1px solid var(--border);
  margin: 8px 0;
}
.bg-custom-row {
  display: flex;
  align-items: center;
  gap: 6px;
}
.bg-custom-row input[type="color"] {
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  flex-shrink: 0;
  background: none;
}
.bg-custom-row .btn-small {
  flex: 1;
  padding: 6px 8px;
  font-size: 12px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
}
.bg-custom-row #bgClearBtn { flex: 0 0 auto; padding: 6px 10px; }

.project-manage-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 8px;
  max-height: 220px;
  overflow-y: auto;
}
.project-manage-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  font-size: 13px;
}
.project-manage-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.project-manage-hint { font-size: 11px; color: var(--muted); }
.project-manage-delete-btn {
  border: none;
  background: none;
  cursor: pointer;
  font-size: 13px;
  flex-shrink: 0;
  color: var(--muted);
}
.project-manage-delete-btn:disabled { opacity: 0.3; cursor: default; }
.project-manage-empty { font-size: 12px; color: var(--muted); padding: 4px 0; }
.project-add-row {
  display: flex;
  gap: 6px;
}
.project-add-row input {
  flex: 1;
  padding: 8px 10px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  font-size: 13px;
  font-family: inherit;
  color: var(--text);
  background: var(--bg);
}
.project-add-row .btn-small { flex: 0 0 auto; }

.similar-task-warning {
  background: color-mix(in srgb, #d97706 12%, var(--card));
  border: 1.5px solid #d97706;
  border-radius: 10px;
  padding: 8px 10px;
  font-size: 12px;
  color: var(--text);
}
.similar-task-warning-title { font-weight: 700; margin-bottom: 4px; }
.similar-task-warning-item { margin: 2px 0; }

.topbar-ai-btn {
  color: var(--muted);
  transition: all 0.15s;
}
.topbar-ai-btn.active {
  background: var(--primary);
  border-color: var(--primary);
  color: white;
}
.topbar-ai-btn:disabled { cursor: wait; opacity: 0.7; }

/* ── Add-task actions (camera/mic/+) ─────────────────────────────────────
   Live inside .topbar-actions, the same collapsible icon row as
   filter/sort/theme/settings/AI/triage/pulse, so they're part of the
   topbar's own layout box on every screen size. Anything living outside
   that box and only *positioned* to line up with it (an earlier attempt
   used position:fixed with an offset copied from the topbar's measured
   height) drifts out of sync the moment the page scrolls or the topbar's
   own height changes — sticky-topbar-plus-fixed-offset stacked two
   separate coordinate systems that had to stay in agreement, and any
   scroll position where they didn't left the buttons floating over
   whatever card happened to be underneath. Being an actual child of the
   topbar sidesteps the whole problem: there's only one coordinate system. */
.topbar-sep {
  width: 1px; height: 24px; background: var(--border); flex-shrink: 0; margin: 0 2px;
}
.fab {
  width: 40px; height: 40px;
  border-radius: 50%;
  border: none;
  color: white;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  transition: transform 0.1s;
}
/* Flex centers each glyph's character box perfectly, but individual emoji
   carry different amounts of asymmetric internal padding (more empty space
   below/right, varying per glyph) so the visible ink reads as shifted
   up-left by a different amount for each icon - a single shared nudge
   either under- or over-corrects depending which icon it's applied to
   (confirmed visually: the same offset that helped 📷 threw off the plain
   "+" character, which was already centered). Tuned per icon instead. */
#imageFab { padding: 1px 0 0 1px; }
#dictateFab { padding: 3px 0 0 3px; }
.fab:active { transform: scale(0.94); }
.fab-primary { background: var(--primary); }
.fab-secondary { background: #16a34a; font-size: 16px; }
.fab-tertiary { background: #7c3aed; font-size: 16px; }

/* Groups the 3 add-task buttons at the very start of the topbar (leftmost
   on every width) instead of trailing after the other icon buttons. On
   desktop the buttons below switch to position:fixed, which takes them out
   of flow entirely, so this wrapper collapses to zero size there — its
   only job is ordering them first in the mobile inline layout. */
/* The 3 buttons inside are position:fixed (see .fab below), so this
   wrapper never actually renders content in place — display:none would
   also hide its fixed-position children, so it stays a normal empty
   inline element instead. */
.topbar-fabs {
  display: contents;
}

/* Fixed bottom-left floating stack on every screen size — earlier this
   lived inline in the topbar on mobile and top-left-fixed on desktop, but
   both put it in a spot that could collide with real page text (topbar
   title/board content). Fixed positioning at the bottom-left corner keeps
   it permanently out of the document flow so it can never overlap anything
   else, on any width. */
.fab {
  position: fixed;
  left: 16px;
  z-index: 90;
}
.fab-primary { bottom: 16px; }
.fab-secondary { bottom: 64px; }
.fab-tertiary { bottom: 112px; }

/* Desktop/tablet only: bigger touch targets and more spacing, since
   there's no cramped-viewport problem to solve at that width. */
@media (min-width: 701px) {
  .fab {
    left: 24px;
    width: 56px; height: 56px;
    font-size: 26px;
    box-shadow: 0 10px 26px -8px rgba(15, 23, 42, 0.4);
  }
  #imageFab { padding: 2px 0 0 2px; }
  #dictateFab { padding: 5px 0 0 5px; }
  .fab-primary { bottom: 24px; }
  .fab-secondary { bottom: 92px; font-size: 22px; }
  .fab-tertiary { bottom: 160px; font-size: 22px; }
  /* Sidebar is a normal-flow flex sibling (not fixed) at this width, so
     a fixed left:24px stack would render on top of its links once it's
     open — push the stack past the sidebar's 260px width instead. */
  .app-shell:not(.sidebar-collapsed) .fab { left: 284px; }
  .app-shell:not(.sidebar-collapsed) .pulse-mini { left: 284px; }
}

/* Mobile: keep the topbar pinned in place while the board scrolls
   underneath, instead of
   scrolling away with the rest of the page. position:sticky is the
   theoretically-correct tool for this and should have worked, but it kept
   visibly failing to hold in place on the actual device this was tested
   on despite nothing in the CSS explaining why — rather than keep
   guessing at which sticky precondition is silently unmet, position:fixed
   is used instead, which cannot depend on a scrolling-ancestor/stacking-
   context computation being right, only on top/left/width being set. The
   real tradeoff: fixed removes the topbar from layout entirely, so
   .wrap needs a compensating top padding (below) or the board's first
   row renders underneath it. */
@media (max-width: 700px) {
  .topbar {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 150;
    background: var(--bg);
    border-bottom: 1px solid var(--border);
  }
}

/* Glanceable Pulse status without opening the full modal - fixed to the
   viewport (not placed after the Done column in the board's own DOM flow)
   so it stays visible regardless of the board's horizontal scroll
   position or which columns are collapsed/expanded. */
.pulse-mini {
  position: fixed;
  left: 16px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  background: var(--card);
  border: 1.5px solid var(--border);
  border-radius: 14px;
  padding: 10px 10px;
  box-shadow: 0 10px 26px -8px rgba(15, 23, 42, 0.3);
  cursor: pointer;
  z-index: 80;
  width: 226px;
}
.pulse-mini:hover { border-color: var(--primary); }
.pulse-mini-level {
  font-size: 12px;
  font-weight: 800;
  text-align: center;
  color: var(--primary);
  line-height: 1.25;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.pulse-mini-table { width: 100%; border-collapse: collapse; }
.pulse-mini-table th {
  font-size: 8px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
  text-align: right;
  padding: 4px 0 4px;
}
.pulse-mini-table td { padding: 3px 0; }
/* Each period (D/W/M) is two rows — Tasks then Pts — sharing one rowspanned
   label cell; the border only goes under the SECOND row of the pair so the
   two rows visually read as one group, not four separate lines. */
.pulse-mini-table tr.pulse-mini-subrow td { border-bottom: 1px solid var(--border); }
.pulse-mini-table tr:last-child td { border-bottom: none; }
.pulse-mini-label {
  font-size: 10px;
  color: var(--txt);
  font-weight: 800;
  white-space: nowrap;
  padding-right: 6px;
  vertical-align: middle;
  border-right: 1px solid var(--border);
}
.pulse-mini-row-label {
  font-size: 8.5px;
  color: var(--muted);
  font-weight: 700;
  white-space: nowrap;
  padding: 0 6px;
}
.pulse-mini-bar-cell { width: 100%; padding-left: 2px; padding-right: 6px; }
.pulse-mini-bar-track {
  width: 100%;
  height: 5px;
  background: var(--border);
  border-radius: 999px;
  overflow: hidden;
}
.pulse-mini-bar-fill { height: 100%; background: var(--primary); border-radius: 999px; }
.pulse-mini-stat {
  font-size: 9.5px;
  color: var(--muted);
  font-weight: 700;
  white-space: nowrap;
  text-align: right;
}
/* Would collide with the collapsed Done column strip and the FABs at
   narrow widths - not worth the squeeze on a phone screen where the full
   modal (📊 icon) is one tap away anyway. */
@media (max-width: 760px) {
  .pulse-mini { display: none; }
}

/* ── Modals (add task / dictate) ────────────────────────────────────────── */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 300;
}
.modal-card {
  background: var(--card);
  border-radius: 20px;
  padding: 24px;
  max-width: 420px;
  width: 100%;
  box-shadow: 0 24px 60px -12px rgba(15, 23, 42, 0.35);
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}
.modal-header h3 { margin: 0; font-size: 18px; font-weight: 800; }

.add-form-modal {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.add-form-modal input, .add-form-modal select {
  padding: 12px;
  border: 1.5px solid var(--border);
  border-radius: 12px;
  font-size: 14px;
  font-family: inherit;
  width: 100%;
}

.modal-actions {
  display: flex;
  gap: 10px;
  margin-top: 16px;
}
.modal-actions .btn { width: auto; flex: 1; }

.dictate-live {
  min-height: 20px;
  font-size: 13px;
  color: var(--muted);
  font-style: italic;
  margin: 4px 0 0;
}

.dictate-lang-toggle {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin-bottom: 12px;
}
.dictate-lang-btn {
  border: 1.5px solid var(--border);
  background: var(--card);
  color: var(--muted);
  border-radius: 999px;
  padding: 5px 14px;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}
.dictate-lang-btn.active {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 12%, var(--card));
  color: var(--primary);
}

.dictate-review-row {
  margin-bottom: 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.dictate-review-row:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }
.dictate-review-title-line {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
.dictate-review-title-line input {
  flex: 1;
  padding: 10px 12px;
  border: 1.5px solid var(--border);
  border-radius: 10px;
  font-size: 14px;
  font-family: inherit;
}
.dictate-review-fields-line {
  display: flex;
  gap: 8px;
}
.dictate-review-fields-line input,
.dictate-review-fields-line select {
  flex: 1;
  padding: 8px 10px;
  border: 1.5px solid var(--border);
  border-radius: 10px;
  font-size: 13px;
  font-family: inherit;
  color: var(--muted);
}
.dictate-review-remove {
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 16px;
  padding: 4px 8px;
  border-radius: 8px;
  flex-shrink: 0;
}
.dictate-review-remove:hover { background: #fef2f2; color: #dc2626; }

.dictate-review-description {
  width: 100%;
  padding: 8px 10px;
  border: 1.5px solid var(--border);
  border-radius: 10px;
  font-size: 13px;
  font-family: inherit;
  color: var(--text);
  resize: vertical;
  min-height: 36px;
  margin-bottom: 6px;
}

.image-review-preview {
  max-width: 100%;
  max-height: 160px;
  border-radius: 12px;
  border: 1px solid var(--border);
  margin-bottom: 12px;
  display: block;
}

.wrap {
  max-width: 720px;
  margin: 0 auto;
  padding: 0 20px 140px;
}

/* Placed after the base .wrap rule above (not inside its own earlier media
   block), because that rule's `padding` shorthand resets padding-top back
   to 0 — same specificity, later same-property declaration wins regardless
   of a media condition on the earlier one. This exact ordering mistake
   has bitten this file more than once now: a later unconditional rule
   silently beats an earlier conditional one on any overlapping property. */
@media (max-width: 700px) {
  .wrap {
    padding-top: var(--topbar-h, 120px);
  }
}

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 32px;
  box-shadow: 0 10px 30px -10px rgba(15, 23, 42, 0.12);
}

.center { text-align: center; max-width: 400px; margin: 0 auto; }

.card h2 { margin: 0 0 6px; font-size: 22px; font-weight: 800; }
.muted { color: var(--muted); font-size: 14px; margin: 0 0 20px; }

.btn {
  width: 100%;
  padding: 14px;
  border-radius: 14px;
  font-weight: 700;
  font-size: 14px;
  border: 1.5px solid var(--border);
  background: var(--card);
  color: var(--text);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  transition: transform 0.1s, box-shadow 0.15s;
}
.btn:hover { box-shadow: 0 4px 14px -4px rgba(15,23,42,0.15); }
.btn:active { transform: scale(0.97); }

.btn-google { margin-bottom: 10px; }
.btn-dev {
  border-style: dashed;
  border-color: #f59e0b;
  color: #b45309;
  background: #fffbeb;
  font-size: 12px;
}
.btn-primary {
  background: var(--primary);
  color: white;
  border: none;
  width: auto;
  padding: 12px 20px;
  /* Auto-width buttons inside a flex-column form (.add-form-modal etc.)
     otherwise sit stuck to the left edge instead of centered - .modal-actions
     buttons are unaffected since flex:1 there overrides sizing anyway. */
  align-self: center;
}
.btn-ghost {
  width: auto;
  padding: 8px 14px;
  font-size: 13px;
  border-color: var(--border);
  color: var(--muted);
  align-self: center;
}

.error {
  background: #fef2f2;
  border: 1px solid #fecaca;
  color: #b91c1c;
  border-radius: 12px;
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 14px;
}

.whoami { font-size: 13px; color: var(--muted); padding: 0 6px; margin-bottom: 4px; }

.ai-hint-row {
  display: flex;
  align-items: center;
  gap: 10px;
  background: #eef2ff;
  border-radius: 10px;
  padding: 8px 8px 8px 12px;
  margin: 0 0 16px;
}
.ai-hint {
  font-size: 12px;
  color: var(--primary);
  flex: 1;
  margin: 0;
}
.ai-hint-exit {
  flex-shrink: 0;
  white-space: nowrap;
  border: 1.5px solid var(--primary);
  background: var(--card);
  color: var(--primary);
  border-radius: 8px;
  padding: 6px 12px;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}
.ai-hint-exit:hover { background: var(--primary); color: white; }

.ai-hint-loading-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.ai-hint-spinner {
  width: 16px; height: 16px;
  border: 2px solid color-mix(in srgb, var(--primary) 25%, transparent);
  border-top-color: var(--primary);
  border-radius: 50%;
  flex-shrink: 0;
  animation: ai-hint-spin 0.8s linear infinite;
}
@keyframes ai-hint-spin {
  to { transform: rotate(360deg); }
}
.ai-hint-progress-track {
  width: 60px;
  height: 4px;
  background: color-mix(in srgb, var(--primary) 15%, transparent);
  border-radius: 999px;
  overflow: hidden;
  position: relative;
}
/* No real percentage exists for a single opaque classification call, so
   this sweeps back and forth instead of filling to a number - the
   standard "indeterminate" pattern for exactly this situation (same
   reason a browser's own page-load progress bar does this when it can't
   compute a real byte count). */
.ai-hint-progress-fill {
  position: absolute;
  top: 0; bottom: 0;
  width: 40%;
  background: var(--primary);
  border-radius: 999px;
  animation: ai-hint-progress-sweep 1.4s ease-in-out infinite;
}
@keyframes ai-hint-progress-sweep {
  0% { left: -40%; }
  100% { left: 100%; }
}

.quadrant-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 11px;
}
.quadrant-1 { background: #fee2e2; color: #b91c1c; }
.quadrant-2 { background: #e0e7ff; color: var(--primary-dark); }
.quadrant-3 { background: #fef3c7; color: #b45309; }
.quadrant-4 { background: #f1f5f9; color: var(--muted); }

.task-row.is-mit {
  border: 1.5px solid var(--primary);
  box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
}

.board {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 12px;
  overflow-x: auto;
  padding-bottom: 10px;
  -webkit-overflow-scrolling: touch;
}
/* Flat mode (a specific status filter, or AI Priority sort) — plain
   single-column list, not Kanban columns. */
.board.board-flat {
  flex-direction: column;
  overflow-x: visible;
}
/* The board needs real screen width to be worth calling a Kanban board;
   .wrap's normal 720px cap (a comfortable single-column reading width) only
   applies outside Kanban mode. */
.wrap.wrap-wide { max-width: 1800px; }

/* Kanban columns, side by side. Expanded columns grow to fill the available
   screen width (so desktop/tablet isn't stuck at a fixed narrow width with
   dead space) but never shrink below a usable minimum — once there's no
   longer room at that minimum, the board scrolls horizontally instead of
   squeezing columns unreadably thin. Collapsing a column shrinks it to a
   fixed thin strip (label rotated vertical) and the flex layout naturally
   pulls everything after it left to fill the space, and also redistributes
   the freed width across the remaining expanded columns. Columns grow to
   their natural content height and the whole page scrolls as one (a single
   scrollbar) rather than each column scrolling independently — columns of
   different lengths simply end at different heights. */
.status-column {
  flex: 1 1 240px;
  min-width: 240px;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: flex-basis 0.15s ease;
}
.status-column.collapsed { flex: 0 0 44px; min-width: 44px; max-width: 44px; }

.status-column-header {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: none;
  border: none;
  padding: 8px 6px;
  font: inherit;
  font-weight: 700;
  font-size: 14px;
  color: var(--text);
  cursor: pointer;
  text-align: left;
  border-radius: 10px;
  flex-shrink: 0;
}
.status-column-header .chevron { color: var(--muted); width: 14px; flex-shrink: 0; }
.status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.status-column-title { flex: 1; }
.status-column-count {
  background: var(--border);
  color: var(--muted);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  padding: 2px 9px;
  flex-shrink: 0;
}
.status-column.collapsed .status-column-header {
  flex-direction: column;
  height: 220px;
  padding: 10px 4px;
}
.status-column.collapsed .status-column-title {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  white-space: nowrap;
  flex: 1;
}

/* On phones, side-by-side Kanban columns force horizontal scrolling and
   clip every card mid-text — there isn't enough width for even two 240px
   columns plus the boxed priority-group borders inside them. Stack columns
   full-width instead, same data/grouping, just one column tall rather than
   many columns wide. Placed after the base .collapsed rules above (not
   earlier in the file) so it actually wins the cascade — same selector
   specificity means source order decides, and an earlier media rule loses
   to a later unconditional one regardless of viewport width. */
@media (max-width: 700px) {
  .wrap.wrap-wide { max-width: 720px; }
  .board {
    flex-direction: column;
    overflow-x: visible;
  }
  .status-column {
    min-width: 100%;
    max-width: 100%;
  }
  .status-column.collapsed {
    flex: 0 0 auto;
    min-width: 100%;
    max-width: 100%;
  }
  .status-column.collapsed .status-column-header {
    flex-direction: row;
    height: auto;
    padding: 8px 6px;
  }
  .status-column.collapsed .status-column-title {
    writing-mode: horizontal-tb;
    text-orientation: mixed;
    white-space: normal;
  }
}

.status-column-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 40px;
  transition: outline 0.1s;
  border-radius: 12px;
  flex: 1;
  padding: 2px;
}
.status-column-body.drag-over { outline: 2px dashed var(--primary); outline-offset: 4px; }
.status-column.collapsed .status-column-body { display: none; }
.status-column-empty {
  border: 1.5px dashed var(--border);
  border-radius: 14px;
  padding: 14px 12px;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}
.status-column-add {
  text-align: center;
  padding: 10px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  border: 1.5px dashed var(--border);
  border-radius: 12px;
  cursor: pointer;
  background: none;
  width: 100%;
  flex-shrink: 0;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.status-column-add:hover {
  color: var(--primary);
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 6%, transparent);
}

/* Boxed group containers - when the board is sorted by a categorical key
   (priority, double-checked), tasks sharing that key get wrapped in their
   own tinted, bordered box with a colored header bar, so the groups read as
   distinct sections rather than one undifferentiated stack of cards. */
.priority-group-container {
  border: 1.5px solid var(--group-color, var(--border));
  border-radius: 14px;
  background: color-mix(in srgb, var(--group-color, var(--border)) 6%, var(--bg));
  overflow: hidden;
}
.priority-group-header {
  background: color-mix(in srgb, var(--group-color, var(--border)) 22%, var(--bg));
  color: var(--group-color, var(--text));
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0.03em;
  padding: 7px 12px;
  border: none;
  border-bottom: 1.5px solid var(--group-color, var(--border));
  /* Now a <button> (collapsible) - reset the native button chrome the
     rest of this rule doesn't already override. */
  width: 100%;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
  display: block;
}
.priority-group-header .chevron { opacity: 0.7; margin-right: 2px; }
.priority-group-container.collapsed .priority-group-header { border-bottom: none; }
.priority-group-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px;
}

.task-row.dragging-source { opacity: 0.35; }
.drag-ghost {
  position: fixed;
  pointer-events: none;
  z-index: 500;
  opacity: 0.9;
  box-shadow: 0 14px 34px -8px rgba(15, 23, 42, 0.4);
}

.task-row {
  position: relative;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  /* Layered shadow (contact + lift + ambient) plus a hairline inset top
     highlight fakes a light source from above, giving the card real depth
     rather than a flat drop-shadow silhouette. */
  box-shadow: var(--card-shadow), inset 0 1px 0 var(--card-highlight);
  transition: transform 0.2s cubic-bezier(0.22, 0.9, 0.32, 1.2), box-shadow 0.2s ease, background 0.15s ease, border-color 0.15s ease;
}

.task-card-actions {
  display: flex;
  gap: 6px;
  flex: 0 0 auto;
}
.task-row:hover:not(.task-row-editing) {
  transform: translateY(-4px) scale(1.012);
  box-shadow: var(--card-shadow-hover), inset 0 1px 0 var(--card-highlight);
}

.task-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas:
    "drag  .     ."
    "pts  .    due"
    "title title del"
    "sub   sub   sub";
  gap: 2px 6px;
  align-items: center;
  padding: 10px 12px;
  background: var(--card-bg);
  border-radius: 10px;
  cursor: pointer;
}
.drag-handle {
  grid-area: drag;
  touch-action: none;
  user-select: none;
  cursor: grab;
  color: var(--muted);
  font-size: 16px;
  line-height: 1;
  padding: 2px 4px;
  justify-self: start;
}
.drag-handle:active { cursor: grabbing; }
.task-row.done .task-title { opacity: 0.6; }
.task-row.done { opacity: 0.7; }
.task-due { grid-area: due; justify-self: end; white-space: nowrap; font-size: 12px; color: var(--muted); }
.points-preview-badge { grid-area: pts; display: inline-flex; align-items: center; font-size: 11px; font-weight: 700; white-space: nowrap; color: #b45309; background: color-mix(in srgb, #f59e0b 15%, transparent); padding: 2px 8px; border-radius: 999px; align-self: center; }
.task-title { grid-area: title; text-align: center; overflow-wrap: break-word; font-weight: 700; font-size: 15px; }
.task-title.strike { text-decoration: line-through; opacity: 0.5; }
.delete-btn:hover { opacity: 1; background: rgba(239,68,68,0.12); }
.subtask-mini { grid-area: sub; justify-self: start; font-size: 11px; color: var(--muted); }
.task-row.dragging-source { opacity: 0.35; }
.status-waiting { background: var(--waiting); }
.status-notStarted { background: var(--notStarted); }
.status-inProgress { background: var(--inProgress); }
.status-done { background: var(--done); }

/* Title is a plain block, own full-width line, text-align:center - no
   float, no flex, nothing sharing the line with it. Every other approach
   tried (shared flex row, float-wrap) broke symmetry in a different way:
   flex either collapsed the title to a sliver on narrow cards or pushed
   due to a whole separate line anyway; float centered the title only
   within the space left of the badge (visibly off-center for short
   titles) and produced choppy single-word first lines for longer ones at
   medium widths. A plain centered block has none of those failure modes
   at any title length or card width - it's the simplest option and also
   the only one that's genuinely symmetric every time. */
.task-title {
  text-align: center;
  overflow-wrap: break-word;
  font-weight: 700;
  font-size: 15px;
}
.task-title.strike { text-decoration: line-through; }



.points-preview-pushright { margin-left: auto; }


.task-meta { font-size: 12px; color: var(--muted); display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

.project-tag {
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 3px 9px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--primary) 12%, transparent);
  color: var(--primary);
}

.priority-dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  margin-right: 4px;
}
.priority-low .priority-dot { background: var(--low); }
.priority-medium .priority-dot { background: var(--medium); }
.priority-high .priority-dot { background: var(--high); }
.priority-urgent .priority-dot { background: var(--urgent); }

.delete-btn, .edit-btn, .dc-btn {
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--muted);
  cursor: pointer;
  font-size: 14px;
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.delete-btn:hover { background: #fef2f2; color: #dc2626; }
.edit-btn:hover { background: #eef2ff; color: var(--primary); }
.dc-btn:hover { background: #ecfdf5; color: var(--done); }
.dc-btn.active {
  background: var(--done);
  border-color: var(--done);
  color: white;
}
.dc-btn.active:hover { background: var(--done); color: white; }

.task-row-editing { align-items: stretch; }
.edit-form {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  width: 100%;
  align-items: center;
}
.edit-form input, .edit-form select {
  padding: 8px 10px;
  border: 1.5px solid var(--border);
  border-radius: 10px;
  font-size: 13px;
  font-family: inherit;
}
.edit-form input[type="text"]:first-of-type { flex: 1 1 160px; }
.btn-small { width: auto; padding: 8px 14px; font-size: 12px; }

/* Owner console tabs (owner-9f3k.html) */
.oc-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1.5px solid var(--border);
  margin-bottom: 18px;
}
.oc-tab {
  border: none;
  background: none;
  padding: 10px 16px;
  font-size: 13px;
  font-weight: 700;
  color: var(--muted);
  cursor: pointer;
  border-bottom: 2.5px solid transparent;
  margin-bottom: -1.5px;
}
.oc-tab:hover { color: var(--text); }
.oc-tab.active { color: var(--primary); border-bottom-color: var(--primary); }

.edit-subtasks {
  flex: 1 1 100%;
  background: var(--bg, #f8fafc);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.edit-subtasks-header {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.edit-subtask-row { display: flex; align-items: center; gap: 8px; }
.edit-subtask-row input[type="checkbox"] { flex-shrink: 0; width: 16px; height: 16px; accent-color: var(--primary); }
.edit-subtask-row input[type="text"] {
  flex: 1;
  padding: 6px 8px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  font-size: 13px;
  font-family: inherit;
}
.edit-subtask-row input[type="text"].subtask-done { text-decoration: line-through; opacity: 0.5; }
.edit-subtask-remove {
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 14px;
  padding: 2px 6px;
  border-radius: 6px;
  flex-shrink: 0;
}
.edit-subtask-remove:hover { background: #fef2f2; color: #dc2626; }
.edit-subtasks-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.edit-subtask-add {
  flex: 1;
  min-width: 120px;
  border: 1.5px dashed var(--border);
  background: none;
  color: var(--primary);
  font-size: 12px;
  font-weight: 700;
  padding: 8px;
  border-radius: 8px;
  cursor: pointer;
}
.edit-subtask-add:hover { background: color-mix(in srgb, var(--primary) 6%, transparent); }
.edit-subtask-suggest {
  flex: 1;
  min-width: 150px;
  border: 1.5px solid transparent;
  background: color-mix(in srgb, var(--primary) 12%, transparent);
  color: var(--primary);
  font-size: 12px;
  font-weight: 700;
  padding: 8px;
  border-radius: 8px;
  cursor: pointer;
}
.edit-subtask-suggest:hover:not(:disabled) { background: color-mix(in srgb, var(--primary) 20%, transparent); }
.edit-subtask-suggest:disabled { opacity: 0.6; cursor: default; }

.subtask-count-badge {
  background: var(--border);
  color: var(--muted);
  border-radius: 999px;
  padding: 1px 8px;
  font-weight: 700;
}

.empty-state {
  text-align: center;
  color: var(--muted);
  padding: 40px 20px;
  font-size: 14px;
}

/* ── Toasts (mutation failures + delete-undo) ── */
.toast-container {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  width: min(92vw, 420px);
}
.toast {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border);
  box-shadow: var(--card-shadow-hover);
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 13px;
  animation: toast-in 0.15s ease-out;
}
.toast-error { border-left: 4px solid var(--urgent); }
.toast-info { border-left: 4px solid var(--done); }
.toast span { flex: 1; }
.toast-action, .toast-close {
  border: none;
  background: transparent;
  color: var(--primary);
  font-weight: 700;
  cursor: pointer;
  padding: 2px 6px;
  flex-shrink: 0;
}
.toast-close { color: var(--muted); font-weight: 400; }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ── Stale-task triage modal ── */
.triage-card {
  max-width: 640px;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
}

.pulse-card {
  max-width: 640px;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
}
.pulse-scroll-body { overflow-y: auto; }

.pulse-level-block {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
  margin-bottom: 14px;
  background: var(--bg);
}
.pulse-level-name { font-size: 20px; font-weight: 800; }
.pulse-level-progress-wrap { flex: 1; max-width: 180px; }
.pulse-level-progress-bar {
  height: 8px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
  margin-bottom: 4px;
}
.pulse-level-progress-fill {
  height: 100%;
  background: var(--primary);
  border-radius: 999px;
  transition: width 0.3s ease;
}

.pulse-goals-table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 16px;
  font-size: 13px;
}
.pulse-goals-table th, .pulse-goals-table td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  text-align: left;
}
.pulse-goals-table th {
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.pulse-goal-bar-cell { width: 35%; min-width: 100px; }
.pulse-goal-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}
.pulse-goal-fill {
  height: 100%;
  background: var(--done);
  border-radius: 999px;
  transition: width 0.3s ease;
}
.pulse-streak { font-size: 11px; color: var(--muted); white-space: nowrap; }

.pulse-section {
  margin-bottom: 16px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
/* .sidebar-label's shared base style uses a small NEGATIVE bottom margin
   (tuned for compact filter/sort popovers elsewhere) which crowds the title
   right into whatever follows it - the Pulse modal's sections are denser
   (graphs, grids, forms) and read better with the title clearly set off as
   its own header rather than blending into the content below it. */
.pulse-section .sidebar-label {
  display: block;
  margin: 0 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.pulse-trend-svg {
  width: 100%;
  height: 100px;
  margin-top: 8px;
  display: block;
}
.pulse-settings-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  margin: 8px 0;
  cursor: pointer;
}
.pulse-goal-inputs {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.pulse-goal-inputs label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  color: var(--muted);
  flex: 1;
  min-width: 90px;
}
.pulse-goal-inputs input {
  padding: 8px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  font-size: 13px;
  color: var(--text);
  background: var(--bg);
}
.pulse-days-off {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 6px;
}
.pulse-days-off label {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px 8px;
  cursor: pointer;
}
.pulse-distribution-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  font-size: 12px;
}
.pulse-distribution-swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  flex-shrink: 0;
}
.pulse-distribution-bar-track {
  flex: 1;
  height: 8px;
  background: var(--border);
  border-radius: 999px;
  overflow: hidden;
}
.pulse-distribution-bar-fill {
  height: 100%;
  border-radius: 999px;
}

.pulse-milestones-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
  gap: 8px;
}
.pulse-badge {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 2px;
  padding: 8px 4px;
  border-radius: 10px;
  border: 1.5px solid var(--border);
  background: var(--bg);
}
.pulse-badge.unlocked {
  border-color: #f59e0b;
  background: color-mix(in srgb, #f59e0b 10%, var(--bg));
}
.pulse-badge.locked { opacity: 0.45; filter: grayscale(0.6); }
.pulse-badge-icon { font-size: 22px; line-height: 1; }
.pulse-badge-name { font-size: 9.5px; font-weight: 700; color: var(--text); line-height: 1.2; }
.pulse-badge-progress { font-size: 9px; color: var(--muted); font-weight: 600; }

/* ── Pulse celebrations (level up / goal hit / badge unlock) ─────────────
   Pure CSS confetti + a bouncing-in banner, no external library, matching
   the app's existing no-CDN-dependency approach for the trend SVG etc.
   Fires only on a genuine state CHANGE (detected by diffing successive
   /pulse/summary + /pulse/milestones fetches in app.js) - never on a plain
   page load, so re-opening the app doesn't re-celebrate old progress. */
.confetti-container {
  position: fixed;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 900;
}
.confetti-piece {
  position: absolute;
  top: -12px;
  width: 8px;
  height: 14px;
  opacity: 0.9;
  animation-name: confetti-fall;
  animation-timing-function: ease-in;
  animation-fill-mode: forwards;
}
@keyframes confetti-fall {
  0% { transform: translateY(-20px) rotate(var(--rot, 0deg)); opacity: 1; }
  100% { transform: translateY(110vh) rotate(calc(var(--rot, 0deg) + 360deg)); opacity: 0.3; }
}
.pulse-celebration-banner {
  position: fixed;
  top: 20%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.6);
  background: var(--card);
  border: 2px solid #f59e0b;
  border-radius: 16px;
  padding: 18px 30px;
  box-shadow: 0 20px 40px -10px rgba(15, 23, 42, 0.4);
  z-index: 901;
  text-align: center;
  opacity: 0;
  max-width: min(320px, calc(100vw - 32px));
  animation: pulse-celebration-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
.pulse-celebration-banner.leaving { animation: pulse-celebration-out 0.5s ease forwards; }
@keyframes pulse-celebration-in { to { opacity: 1; transform: translate(-50%, -50%) scale(1); } }
@keyframes pulse-celebration-out { to { opacity: 0; transform: translate(-50%, -50%) scale(0.85) translateY(-16px); } }
.pulse-celebration-emoji { font-size: 40px; line-height: 1; margin-bottom: 6px; }
.pulse-celebration-text { font-weight: 800; font-size: 15px; color: var(--text); }
@media (prefers-reduced-motion: reduce) {
  .confetti-piece { display: none; }
  .pulse-celebration-banner, .pulse-celebration-banner.leaving { animation: none; opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.triage-list {
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-right: 4px;
}
.triage-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg);
  flex-wrap: wrap;
}
.triage-item-done { opacity: 0.5; }
.triage-item-info { flex: 1; min-width: 160px; }
.triage-item-title { font-weight: 700; font-size: 14px; }
.triage-item-rationale { font-size: 12px; color: var(--muted); margin-top: 2px; }
.triage-item-controls {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.triage-item-controls select {
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 12px;
  background: var(--card);
  color: var(--text);
}
.triage-defer-days {
  width: 56px;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 12px;
}

/* Priority sub-sections inside status columns */
.priority-section { margin-bottom: 10px; }
.priority-section-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  padding: 4px 8px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border, rgba(128,128,128,0.15));
}
.priority-section-tasks { display: flex; flex-direction: column; gap: 6px; }

/* Edit form additions */
.edit-dc-row { display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 13px; margin: 6px 0; }
.edit-ai-badge {
  font-size: 12px;
  color: var(--muted);
  padding: 4px 0;
  font-style: italic;
}

.delete-btn { grid-area: del; justify-self: end; background: none; border: none; cursor: pointer; font-size: 16px; opacity: 0.5; padding: 2px 4px; border-radius: 4px; }
.delete-btn:hover { opacity: 1; background: rgba(239,68,68,0.12); }
