/* Ubersuggest Panel — shared design system (tokens + components) for
   login.html, connect.html, panel.html, settings.html.

   Palette: "Graphite & Signal".

   The system is deliberately desaturated: a near-neutral zinc scale carries
   every surface, border and label, and exactly one saturated hue (indigo)
   marks what is interactive or currently true. Depth comes from stepping
   surface lightness and 1px borders, not from glow or gradient fills — so
   the data stays the loudest thing on screen.

   Semantic color is capped at four meanings (primary / success / danger /
   warning). If something new needs a color it almost certainly wants one of
   those four, or a neutral. */

:root {
  color-scheme: light dark;

  --font-sans: "Plus Jakarta Sans", "Inter", system-ui, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", "SF Mono", Consolas, monospace;

  /* Type scale — one ladder, referenced by name so sizes stay in step. */
  --text-2xs: 10.5px;
  --text-xs: 11px;
  --text-sm: 12px;
  --text-base: 13px;
  --text-md: 14px;
  --text-lg: 16px;
  --text-xl: 20px;
  --text-2xl: 26px;

  /* The one saturated hue. Everything else in the system is a neutral. */
  --color-primary: #4338ca;
  --color-primary-hover: #3730a3;
  --color-primary-active: #312e81;
  --color-primary-contrast: #ffffff;
  /* Tinted fills for icon chips and active states — a wash of the accent over
     a neutral, never the accent at full strength behind text. */
  --color-primary-soft: #eef2ff;
  --color-primary-soft-border: #dcdcfb;

  /* Accent is reserved for positive movement in data (rank up, gains). Teal
     reads as distinct from indigo at a glance, which is the whole point:
     "interactive" and "improving" must never look like the same thing. */
  --color-accent: #0d9488;

  --color-success: #0f766e;
  --color-success-bg: #ccfbf1;
  --color-danger: #b91c1c;
  --color-danger-bg: #fee2e2;
  --color-warning: #a34a08;
  --color-warning-bg: #fef3c7;

  /* Neutral ramp (zinc). Each surface is one step apart from what sits behind
     it — sunken < canvas < muted < surface. */
  --color-bg: #fafafa;
  --color-surface: #ffffff;
  --color-surface-muted: #f4f4f5;
  --color-surface-sunken: #f7f7f8;
  --color-border: #e4e4e7;
  --color-border-strong: #d4d4d8;
  --color-text: #18181b;
  --color-text-muted: #52525b;   /* 7.4:1 on canvas */
  --color-text-faint: #68686f;   /* 5.2:1 on canvas — AA as body text, not just as a hint */

  /* Kept as a token because a few components reference it by name. Now a tight
     tonal ramp inside one hue rather than a three-stop rainbow. */
  --gradient-brand: linear-gradient(140deg, #4f46e5 0%, #4338ca 100%);

  /* Soft depth: two low-opacity neutral layers per step — a tight contact
     shadow plus a wide diffuse one. Never colored, never a glow. */
  --shadow-xs: 0 1px 2px rgba(9, 9, 11, 0.05);
  --shadow-sm: 0 1px 2px rgba(9, 9, 11, 0.04), 0 1px 3px rgba(9, 9, 11, 0.05);
  --shadow-md: 0 2px 4px rgba(9, 9, 11, 0.04), 0 6px 16px -4px rgba(9, 9, 11, 0.09);
  --shadow-lg: 0 4px 8px rgba(9, 9, 11, 0.04), 0 16px 40px -8px rgba(9, 9, 11, 0.14);
  --shadow-xl: 0 8px 16px rgba(9, 9, 11, 0.06), 0 32px 64px -16px rgba(9, 9, 11, 0.2);
  --shadow-glow: var(--shadow-sm);

  --ring: 0 0 0 3px rgba(67, 56, 202, 0.16);

  --radius-xs: 5px;
  --radius-sm: 7px;
  --radius-md: 10px;
  --radius-lg: 14px;
  --radius-xl: 20px;
  --radius-pill: 999px;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;

  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-fast: 0.12s;
  --dur: 0.18s;

  /* Chevron for custom-styled <select>. The stroke color is baked into the
     data URI, so it needs its own definition per theme. */
  --select-chevron: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2352525b' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");

  --skeleton-base: #e4e4e7;
  --skeleton-sheen: rgba(255, 255, 255, 0.75);
  --overlay-scrim: rgba(9, 9, 11, 0.5);
}

/* Dark: a true neutral ground, not a tinted one. The base sits at #09090b
   rather than pure black (pure black causes halation and leaves no room to
   step elevation downward), and each nested surface is a few percent lighter
   than its parent — the same elevation logic as light mode, inverted. */
@media (prefers-color-scheme: dark) {
  :root {
    --color-primary: #a5b4fc;
    --color-primary-hover: #c7d2fe;
    --color-primary-active: #e0e7ff;
    --color-primary-contrast: #1e1b4b;
    --color-primary-soft: #1e1b3a;
    --color-primary-soft-border: #312e63;

    --color-accent: #2dd4bf;

    --color-success: #2dd4bf;
    --color-success-bg: #10312e;
    --color-danger: #f87171;
    --color-danger-bg: #3b1618;
    --color-warning: #fbbf24;
    --color-warning-bg: #3a2c0c;

    --color-bg: #09090b;
    --color-surface: #131316;
    --color-surface-muted: #1b1b1f;
    --color-surface-sunken: #0e0e10;
    --color-border: #27272a;
    --color-border-strong: #3f3f46;
    --color-text: #f4f4f5;
    --color-text-muted: #a1a1aa;
    --color-text-faint: #8b8b95;

    --gradient-brand: linear-gradient(140deg, #6366f1 0%, #4f46e5 100%);

    --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.3);
    --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.4), 0 6px 16px -4px rgba(0, 0, 0, 0.5);
    --shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.4), 0 16px 40px -8px rgba(0, 0, 0, 0.6);
    --shadow-xl: 0 8px 16px rgba(0, 0, 0, 0.5), 0 32px 64px -16px rgba(0, 0, 0, 0.7);

    --ring: 0 0 0 3px rgba(165, 180, 252, 0.22);

    --select-chevron: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23a1a1aa' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");

    --skeleton-base: #27272a;
    --skeleton-sheen: rgba(255, 255, 255, 0.06);
    --overlay-scrim: rgba(0, 0, 0, 0.7);
  }
}

/* ---- Base ------------------------------------------------------------------ */

* { box-sizing: border-box; }
/* A plain [hidden] only wins the cascade over an author rule with an explicit
   display (e.g. .field's display:flex) when it's !important — same-specificity
   author rules beat the browser's default UA [hidden] regardless of source
   order. Restated here as an author rule so `hidden` reliably hides anything,
   not just elements with no display of their own. */
[hidden] { display: none !important; }

/* clip, not hidden. `overflow-x: hidden` makes the root a scroll container, and
   the scroll container is what `position: sticky` sticks within — so the sticky
   header and the settings sidebar had a "scrollport" as tall as the whole
   document and never stuck to anything. `clip` guards against sideways overflow
   the same way without creating that scroll container (it's the one non-visible
   value that leaves the other axis computing to `visible`). A browser too old
   for it ignores the line, which costs the overflow guard, not the header. */
html, body { overflow-x: clip; }
html { scroll-behavior: smooth; }

body {
  font: var(--text-md) / 1.6 var(--font-sans);
  margin: 0;
  min-height: 100vh;
  /* Flat canvas. The previous build painted two large radial color blobs here;
     they tinted every "white" card sitting on top and competed with the data.
     Depth is carried by surfaces and borders instead. */
  background: var(--color-bg);
  color: var(--color-text);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-variant-numeric: tabular-nums;
}

h1, h2, h3 { font-weight: 700; letter-spacing: -0.018em; }

a { color: var(--color-primary); text-underline-offset: 2px; }

/* Checkboxes and range inputs pick up the accent with no custom markup. */
input, select, textarea, button { accent-color: var(--color-primary); }

::selection { background: color-mix(in srgb, var(--color-primary) 22%, transparent); }

::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--color-border-strong);
  border-radius: var(--radius-pill);
  border: 3px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--color-text-faint); background-clip: content-box; }

svg.icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  stroke-width: 2;
  flex: none;
}

/* ---- Focus & accessibility -------------------------------------------------- */
/* WCAG 2.2 SC 2.4.11 wants a focus indicator with at least 3:1 contrast against
   the unfocused state, plus real perimeter area. A 2px ring with a 2px offset
   clears both, and the offset keeps it legible against a same-colored control. */
a:focus-visible,
button:focus-visible,
select:focus-visible,
input:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Honour a system-level request for less motion: no sweeps, no lifts, no spins
   that never end. Durations collapse rather than being removed, so state
   changes still register. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .tile:hover, .btn:hover, .opp-item:hover, .btn.btn-secondary:hover { transform: none !important; }
}

/* ---- Layout: auth pages (login / connect) ---------------------------------- */
.auth-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--space-5);
}

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  padding: var(--space-7) var(--space-6) var(--space-6);
  max-width: 440px;
  width: 100%;
  box-shadow: var(--shadow-lg);
  position: relative;
}
/* The old card wore a 4px rainbow bar across its top edge. A single hairline
   highlight along the top does the same job — says "this surface is lifted" —
   without introducing a second color story on a one-decision screen. */
.card::before {
  content: "";
  position: absolute;
  inset: 0 var(--space-5) auto;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--color-border-strong), transparent);
}

.card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-lg);
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary-soft-border);
  color: var(--color-primary);
  margin-bottom: var(--space-5);
}
.card-icon svg.icon { width: 20px; height: 20px; }

.card h1 { font-size: var(--text-xl); margin: 0 0 var(--space-2); letter-spacing: -0.025em; }
.card p.muted { color: var(--color-text-muted); margin: 0 0 var(--space-5); font-size: var(--text-md); line-height: 1.65; }
.card.text-center { text-align: center; }
.card.text-center .card-icon { margin-left: auto; margin-right: auto; }

/* ---- Buttons ---------------------------------------------------------------- */
.btn {
  font: inherit;
  font-size: var(--text-base);
  font-weight: 600;
  letter-spacing: -0.005em;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 9px 16px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-primary);
  cursor: pointer;
  /* Flat fill, not a shifting gradient. The primary action is already the only
     saturated block on screen; motion and glow on top of that reads as noise. */
  background: var(--color-primary);
  color: var(--color-primary-contrast);
  box-shadow: var(--shadow-xs);
  transition:
    background var(--dur) var(--ease),
    border-color var(--dur) var(--ease),
    transform var(--dur-fast) var(--ease),
    box-shadow var(--dur) var(--ease),
    opacity var(--dur) var(--ease);
}
.btn:hover {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}
.btn:active {
  background: var(--color-primary-active);
  border-color: var(--color-primary-active);
  transform: translateY(0);
  box-shadow: var(--shadow-xs);
}
.btn:disabled {
  background: var(--color-surface-muted);
  border-color: var(--color-border);
  color: var(--color-text-faint);
  cursor: not-allowed;
  box-shadow: none;
  transform: none;
}
.btn .spinner { border-color: currentColor; border-top-color: transparent; opacity: 0.85; }
.btn.btn-lg { font-size: var(--text-md); padding: 12px 22px; border-radius: var(--radius-md); }

.btn.btn-secondary {
  background: var(--color-surface);
  color: var(--color-text);
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-xs);
}
.btn.btn-secondary:hover {
  background: var(--color-surface-muted);
  border-color: var(--color-text-faint);
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}
.btn.btn-secondary:active { background: var(--color-border); transform: translateY(0); }

.btn.btn-danger {
  background: var(--color-danger);
  color: #fff;
  border-color: var(--color-danger);
}
.btn.btn-danger:hover {
  background: color-mix(in srgb, var(--color-danger) 86%, #000);
  border-color: color-mix(in srgb, var(--color-danger) 86%, #000);
}
.btn.btn-danger:active { background: color-mix(in srgb, var(--color-danger) 74%, #000); transform: translateY(0); }
/* Disabled is the button's resting state until the confirm phrase is typed, so
   it must not look broken — just plainly not-yet-armed. */
.btn.btn-danger:disabled { background: var(--color-surface-muted); border-color: var(--color-border); color: var(--color-text-faint); }

.btn.btn-danger-ghost {
  background: transparent;
  color: var(--color-danger);
  border-color: var(--color-border);
  box-shadow: none;
}
.btn.btn-danger-ghost:hover {
  background: var(--color-danger-bg);
  border-color: var(--color-danger);
  box-shadow: none;
  transform: none;
}

.icon-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: var(--text-base);
  font-weight: 600;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.icon-link:hover { background: var(--color-surface-muted); color: var(--color-text); }
.icon-link.danger:hover { color: var(--color-danger); background: var(--color-danger-bg); }

/* ---- Spinner ----------------------------------------------------------------- */
.spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid var(--color-border);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  vertical-align: -2px;
  margin-right: 8px;
}
@keyframes spin { to { transform: rotate(360deg); } }

.status { margin-top: var(--space-4); min-height: 22px; font-size: var(--text-md); color: var(--color-text-muted); }
.status.error { color: var(--color-danger); }
.status.ok { color: var(--color-success); }

/* ---- App shell: header + main (panel.html, settings.html) ------------------- */
.app-header {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 12px var(--space-6);
  border-bottom: 1px solid var(--color-border);
  /* Near-opaque rather than heavily translucent: content scrolling underneath a
     glassy bar makes the header text sit on a moving field, which is exactly
     where legibility goes. The blur is here to soften, not to reveal. */
  background: color-mix(in srgb, var(--color-surface) 92%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  position: sticky;
  top: 0;
  z-index: 10;
}
.brand { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; min-width: 0; }

/* ---- Top ribbon tabs ---------------------------------------------------------- */
/* The wordmark, not a link: /panel/ already has its own tab, and a second way to
   reach it would just make the current tab ambiguous. */
.brand-name {
  font-size: var(--text-md);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--color-text);
  white-space: nowrap;
  flex: 0 0 auto;
}
/* Every page is reachable from every other page, so the sections live in the top
   bar rather than each page linking back to one hub. Same three cues as
   .settings-nav-item's active state (fill, accent text, border) — color alone
   would leave the current section ambiguous for anyone who can't separate the
   two hues. */
.top-nav { display: flex; align-items: center; gap: 2px; flex: 0 1 auto; min-width: 0; }
.top-nav-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: var(--text-base);
  font-weight: 600;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.top-nav-item svg.icon { width: 15px; height: 15px; }
.top-nav-item:hover { background: var(--color-surface-muted); color: var(--color-text); }
.top-nav-item.active {
  background: var(--color-primary-soft);
  color: var(--color-primary);
  border-color: var(--color-primary-soft-border);
}
.top-nav-item.active svg.icon { color: var(--color-primary); }
.brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-md);
  background: var(--gradient-brand);
  color: #fff;
  box-shadow: var(--shadow-xs);
  flex: none;
}
.brand-mark svg.icon { width: 15px; height: 15px; }
.app-header h1 {
  font-size: var(--text-md);
  font-weight: 700;
  margin: 0;
  letter-spacing: -0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.grow { flex: 1 1 auto; }
.header-actions { display: flex; align-items: center; gap: var(--space-1); }

/* Who is signed in with Microsoft. Sits left of the header links and truncates
   rather than pushing them off a narrow window — the full address is in the
   title attribute. */
.user-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 200px;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-muted);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.user-chip svg.icon { width: 12px; height: 12px; }

/* Sign-out line under the Ubersuggest Connect card. */
.signed-in-note { margin-top: var(--space-4); font-size: var(--text-sm); color: var(--color-text-muted); }
.signed-in-note a { color: inherit; text-decoration: underline; }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--text-sm);
  font-weight: 600;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-muted);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.badge svg.icon { width: 12px; height: 12px; }
.badge .spinner, .push-status .spinner, .loading-row .spinner { margin-right: 0; }
/* Status badges keep a border in their own hue rather than going borderless —
   a tinted pill with no edge loses its shape against a tinted surface. */
.badge.stale { background: var(--color-warning-bg); color: var(--color-warning); border-color: color-mix(in srgb, var(--color-warning) 28%, transparent); }
.badge.ok { background: var(--color-success-bg); color: var(--color-success); border-color: color-mix(in srgb, var(--color-success) 28%, transparent); }
.badge.danger { background: var(--color-danger-bg); color: var(--color-danger); border-color: color-mix(in srgb, var(--color-danger) 28%, transparent); }

main { max-width: 1120px; margin: 0 auto; padding: var(--space-6); }

/* ---- Form controls ----------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.field label,
.pm-grid label,
.modal label {
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}
.req { font-weight: 600; text-transform: none; letter-spacing: normal; color: var(--color-warning); }

select,
input[type="date"],
input[type="text"],
input[type="password"],
input[type="number"],
textarea {
  font: inherit;
  font-size: var(--text-base);
  padding: 8px 11px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), background var(--dur) var(--ease);
}
/* A native select renders a different control on every platform, which makes it
   the one element that can single-handedly date a UI. Drawing our own chevron
   keeps it in the system. */
select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  padding-right: 30px;
  background-image: var(--select-chevron);
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 15px 15px;
  cursor: pointer;
}
select:hover, input:hover, textarea:hover { border-color: var(--color-text-faint); }
select:focus, input:focus, textarea:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: var(--ring);
}
select:disabled, input:disabled, textarea:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  background: var(--color-surface-muted);
}

/* ---- Toolbar ------------------------------------------------------------------ */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
  padding: var(--space-4) var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
/* The toolbar's left side is a title, not a form field — From/To used to sit
   here (see panel.html; they're hidden now, they only ever drove Insights
   below, not this row), and the project select alone left the box looking like
   an accident. It borrows the same icon-chip language as .section-head below
   so it reads as a real title, just sized for a one-line toolbar rather than a
   full section divider. */
.toolbar-title { display: flex; align-items: center; gap: 9px; }
.toolbar-title-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-sm);
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary-soft-border);
  color: var(--color-primary);
  flex: 0 0 auto;
}
.toolbar-title-icon svg.icon { width: 13px; height: 13px; }
.toolbar-title label {
  /* One step up from .section-head h2's text-sm — a bit larger, since this
     title has no border-bottom or surrounding heading rhythm to lean on. */
  font-size: var(--text-md);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--color-text-muted);
  cursor: pointer;
}
.toolbar-select { flex: 0 1 300px; }
/* A label+select pair that has to wrap as one unit, not each piece on its own
   — see the Tasks page, which has two of these (Ubersuggest project, Trutimer
   project) in one toolbar. Without this, .toolbar's flex-wrap treats the title
   and its select as separate items: on a mid-width screen the title stays on
   line one while its own select drops to line two, which reads as broken
   rather than as a second filter stacking below the first. */
.toolbar-field { display: flex; align-items: center; gap: var(--space-3); flex: 1 1 320px; min-width: 0; }
.toolbar-field .toolbar-select { flex: 1 1 auto; }

/* ---- Section headers ----------------------------------------------------------- */
.section-head {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: var(--space-6) 0 var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
/* Lucide swaps the <i> placeholder for an <svg> at runtime, so both selectors
   have to describe the same chip or the header jumps on first paint. */
.section-head > svg.icon,
.section-head > i[data-lucide] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-sm);
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary-soft-border);
  color: var(--color-primary);
  flex: 0 0 auto;
}
.section-head svg.icon { width: 13px; height: 13px; }
.section-head h2 {
  font-size: var(--text-sm);
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--color-text-muted);
}

/* ---- Stat tiles ------------------------------------------------------------- */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.tile {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.tile:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
  border-color: var(--color-border-strong);
}
.tile .tile-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-md);
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary-soft-border);
  color: var(--color-primary);
  flex: 0 0 auto;
}
.tile .tile-icon svg.icon { width: 15px; height: 15px; }
/* The number is the reason the tile exists — it gets the size, the weight and
   the tight tracking; the label stays a quiet caption underneath. */
.tile .n {
  font-size: var(--text-2xl);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
}
.tile .l {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-top: 3px;
  font-weight: 500;
  line-height: 1.45;
}
.tile.up .n, .tile.up .tile-icon { color: var(--color-success); }
.tile.up .tile-icon { background: var(--color-success-bg); border-color: color-mix(in srgb, var(--color-success) 25%, transparent); }
.tile.down .n, .tile.down .tile-icon { color: var(--color-danger); }
.tile.down .tile-icon { background: var(--color-danger-bg); border-color: color-mix(in srgb, var(--color-danger) 25%, transparent); }

/* A tile that doubles as a status filter (Tasks page) — same look, plus a
   pressed state so the active status reads as "on" without a separate control. */
.tile-filter { cursor: pointer; text-align: left; font: inherit; color: inherit; }
.tile-filter.active { border-color: var(--color-primary); box-shadow: 0 0 0 1px var(--color-primary) inset; }

/* ---- Loading states ------------------------------------------------------------ */
/* A sweep rather than an opacity pulse: it reads as "content is arriving" instead
   of "this element is blinking", and it never dips the placeholder below the
   3:1 non-text contrast floor the way a fade to 0.5 opacity does. Reduced-motion
   users get the flat base color, which still shows the shape of what's coming. */
@keyframes skeleton-sweep {
  from { background-position: 100% 50%; }
  to { background-position: 0 50%; }
}
.skeleton {
  background-color: var(--skeleton-base);
  background-image: linear-gradient(90deg, transparent 15%, var(--skeleton-sheen) 38%, transparent 62%);
  background-size: 300% 100%;
  background-repeat: no-repeat;
  border-radius: var(--radius-xs);
  animation: skeleton-sweep 1.4s var(--ease) infinite;
}
.tile .tile-icon.skeleton { background-color: var(--skeleton-base); border-color: transparent; }
.tile-skel-lines { flex: 1 1 auto; padding-top: 3px; }
.tile-skel-line { height: 18px; width: 55%; }
.tile-skel-line.short { height: 10px; width: 78%; margin-top: 9px; }
.loading-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: var(--space-4) 0;
  font-size: var(--text-base);
  color: var(--color-text-muted);
}

/* ---- Table --------------------------------------------------------------------- */
.table-wrap {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  /* overflow-x must be auto, not hidden: a table wider than its wrapper used to
     lose its trailing columns outright, with no scrollbar to reveal them.
     overflow-y stays hidden so the corners still clip to the radius. */
  overflow-x: auto;
  overflow-y: hidden;
  box-shadow: var(--shadow-sm);
}
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 11px 16px; border-bottom: 1px solid var(--color-border); font-size: var(--text-base); }
th {
  color: var(--color-text-muted);
  font-weight: 700;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  background: var(--color-surface-sunken);
  border-bottom-color: var(--color-border-strong);
  white-space: nowrap;
}
tbody tr { transition: background var(--dur-fast) var(--ease); }
tbody tr:hover { background: var(--color-surface-muted); }
tr:last-child td { border-bottom: 0; }

.pos { font-variant-numeric: tabular-nums; display: inline-flex; align-items: center; gap: 4px; font-weight: 600; }
.pos svg.icon { width: 12px; height: 12px; }
.pos.up { color: var(--color-success); }
.pos.down { color: var(--color-danger); }
.pos.none { color: var(--color-text-faint); }

/* ---- Opportunity list ------------------------------------------------------------ */
.opp-item {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: flex-start;
  padding: 13px 16px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  /* The accent rail is the one place a solid saturated edge earns its keep: it
     turns a stack of same-shaped rows into a scannable list. */
  border-left: 3px solid var(--color-primary);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-2);
  box-shadow: var(--shadow-xs);
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.opp-item:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
  border-color: var(--color-border-strong);
  border-left-color: var(--color-primary-hover);
}
.opp-item svg.icon { width: 14px; height: 14px; color: var(--color-primary); margin-top: 3px; }
/* A plain block of inline content — the "Sent" marker and the title are both
   inline, so they share a baseline and wrap like ordinary text. .t stays a
   separate element so the strikethrough on a sent row lands on the title only:
   text-decoration inherits into descendants and can't be undone by one. */
.opp-item .t { font-weight: 700; font-size: var(--text-base); letter-spacing: -0.01em; }
.opp-item .d { font-size: var(--text-sm); color: var(--color-text-muted); margin-top: 3px; white-space: pre-line; line-height: 1.6; }
.opp-tags { display: flex; gap: 6px; flex: 0 0 auto; flex-wrap: wrap; justify-content: flex-end; align-items: flex-start; }

/* goOppPage() scrolls the list back to its top on a page change, and the sticky
   header sits over that top — without this the first item of the new page lands
   underneath it. Same offset .settings-nav sticks at, for the same reason. */
#oppList { scroll-margin-top: 84px; }

/* Paging controls under the opportunity list. :empty keeps a single-page list
   from carrying the margin of controls that aren't there. */
.opp-pager { display: flex; align-items: center; justify-content: center; gap: var(--space-3); margin-top: var(--space-3); flex-wrap: wrap; }
.opp-pager:empty { display: none; }
.opp-pager-count { font-size: var(--text-sm); color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.tag {
  font-size: var(--text-xs);
  font-weight: 700;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  border: 1px solid transparent;
}
/* "Sent" is a state, not an attribute of the opportunity, so it deliberately
   doesn't wear the .tag pill shape the impact/effort pair uses — no fill, no
   border, just the success colour and a check.

   Plain inline, not inline-flex: a flex box takes its baseline from its first
   item — here the icon — rather than from its text, so sitting it beside the
   title left the word riding above the title's baseline. Inline text puts both
   on the same baseline for free, and lets a long title wrap around it. */
.tag-sent {
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--color-success);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  white-space: nowrap;
  margin-right: 8px;
}
.tag-sent svg.icon {
  width: 13px;
  height: 13px;
  color: var(--color-success);
  margin-top: 0;
  margin-right: 4px;
  vertical-align: -2px;
}

/* An already-sent row is history, not a to-do: it recedes and its title is
   struck through, so a glance down the list separates the two without reading.
   Same language the push modal already uses for these (.pm-task-sent) — dimmed,
   dashed, no fill — with the accent rail muted so it stops advertising itself. */
.opp-item.sent {
  opacity: 0.65;
  box-shadow: none;
  border-style: dashed;
  border-left-style: solid;
  border-left-color: var(--color-border-strong);
}
.opp-item.sent .t { text-decoration: line-through; }
.opp-item.sent:hover { border-left-color: var(--color-border-strong); }

.tag-good { background: var(--color-success-bg); color: var(--color-success); border-color: color-mix(in srgb, var(--color-success) 22%, transparent); }
.tag-neutral { background: var(--color-surface-muted); color: var(--color-text-muted); border-color: var(--color-border); }
.tag-bad { background: var(--color-warning-bg); color: var(--color-warning); border-color: color-mix(in srgb, var(--color-warning) 22%, transparent); }

/* ---- Empty / error states ---------------------------------------------------------- */
.empty, .err {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: var(--space-5) var(--space-4);
  font-size: var(--text-base);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  border: 1px dashed var(--color-border-strong);
}
.empty { color: var(--color-text-faint); }
.err { color: var(--color-danger); border-style: solid; border-color: color-mix(in srgb, var(--color-danger) 30%, transparent); background: var(--color-danger-bg); }
.empty svg.icon, .err svg.icon { width: 15px; height: 15px; }

/* ---- Toast ------------------------------------------------------------------------ */
/* A small card that drops in under the header on the right and gets out of the
   way — for something worth saying once, not something the page has to keep
   saying. The one use so far is "no sync job scheduled yet" on /panel/.

   Fixed rather than in the flow: a banner across the top of the page pushes the
   real content down and reads as an error state, which this isn't. Above the
   page (z-index 40) but below a modal (50) — a toast must never sit over a
   dialog the user is filling in. */
.toast {
  position: fixed;
  top: calc(var(--space-6) + 44px);
  right: var(--space-6);
  z-index: 40;
  width: min(340px, calc(100vw - 2 * var(--space-4)));
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-warning);
  box-shadow: var(--shadow-lg);
  animation: toast-in var(--dur) var(--ease-out);
}
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: none; }
}
/* Fading out is the only place a toast is allowed to be slow — long enough to
   register as "that closed", short enough not to linger. */
.toast.leaving { opacity: 0; transform: translateY(-6px); transition: opacity 0.25s var(--ease), transform 0.25s var(--ease); }
.toast > svg.icon { width: 16px; height: 16px; flex: none; margin-top: 1px; color: var(--color-warning); }
.toast-body { flex: 1; min-width: 0; }
.toast-title { font-size: var(--text-base); font-weight: 700; color: var(--color-text); }
.toast-text { font-size: var(--text-sm); color: var(--color-text-muted); line-height: 1.5; margin: 3px 0 0; }
.toast-action {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 8px;
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-primary);
  text-decoration: none;
}
.toast-action:hover { text-decoration: underline; }
.toast-action svg.icon { width: 13px; height: 13px; }
.toast-close {
  flex: none;
  border: 0;
  background: transparent;
  color: var(--color-text-faint);
  cursor: pointer;
  padding: 3px;
  line-height: 0;
  border-radius: var(--radius-sm);
}
.toast-close:hover { color: var(--color-text); background: var(--color-surface-muted); }
.toast-close svg.icon { width: 14px; height: 14px; }
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
  .toast.leaving { transition: none; }
}

.row { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-3); flex-wrap: wrap; }
.stack > * + * { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.hint { font-size: var(--text-sm); color: var(--color-text-faint); margin: 4px 0 0; line-height: 1.55; }
.current { font-size: var(--text-sm); color: var(--color-text-muted); margin-top: 5px; }

.push-status { font-size: var(--text-sm); color: var(--color-text-muted); display: inline-flex; align-items: center; gap: 5px; font-weight: 600; }
.push-status svg.icon { width: 13px; height: 13px; }
.push-status.error { color: var(--color-danger); }
.push-status.ok { color: var(--color-success); }

/* ---- Modal ---------------------------------------------------------------------- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--overlay-scrim);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 50;
  padding: var(--space-4);
}
.modal-overlay.open { display: flex; }
.modal {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  padding: var(--space-5) var(--space-6) var(--space-6);
  width: 100%;
  max-width: 440px;
  box-shadow: var(--shadow-xl);
}
.modal.modal-lg { max-width: 660px; max-height: 88vh; display: flex; flex-direction: column; overflow: hidden; }
/* The flex column above only governs .modal's direct children (#pmForm/#pmSuccess) —
   #pmForm itself must also be a flex column, or its content lays out as plain
   block flow with no way to shrink, and anything past max-height (here, the
   action buttons) gets clipped by .modal's overflow:hidden instead of .pm-tasks
   (flex:1 1 auto + its own overflow-y:auto below) absorbing the extra height. */
.modal.modal-lg #pmForm { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
.modal.modal-lg .modal-actions { flex: 0 0 auto; }

/* Fullscreen variant (push modal): same flex-column shape as modal-lg, just
   sized to the viewport instead of capped at a max-width/height. */
.modal-overlay.modal-overlay-fullscreen { padding: 0; }
.modal.modal-fullscreen {
  max-width: none;
  width: 100vw;
  height: 100vh;
  max-height: 100vh;
  border-radius: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: var(--space-5) var(--space-6);
}
.modal.modal-fullscreen #pmForm { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
.modal.modal-fullscreen .modal-actions { flex: 0 0 auto; }
.modal.modal-fullscreen .pm-tasks { max-height: none; }

.modal-title { display: flex; align-items: center; gap: 11px; margin: 0 0 var(--space-1); }
.modal-title svg.icon {
  width: 32px;
  height: 32px;
  padding: 8px;
  box-sizing: border-box;
  border-radius: var(--radius-md);
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary-soft-border);
  color: var(--color-primary);
}
.modal h3 { margin: 0; font-size: var(--text-lg); letter-spacing: -0.02em; }
.modal p.muted { color: var(--color-text-muted); margin: 0 0 var(--space-3); font-size: var(--text-sm); }
.modal label { display: block; margin: var(--space-3) 0 5px; }
.modal select, .modal input { width: 100%; }
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}
.modal-err { color: var(--color-danger); font-size: var(--text-sm); margin-top: var(--space-3); min-height: 16px; }

.pm-success { text-align: center; padding: var(--space-6) 0; margin: auto 0; }
.pm-success-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--color-success-bg);
  border: 1px solid color-mix(in srgb, var(--color-success) 28%, transparent);
  color: var(--color-success);
  margin-bottom: var(--space-4);
}
.pm-success-icon svg.icon { width: 27px; height: 27px; }
.pm-success h3 { margin: 0 0 6px; font-size: var(--text-xl); }

.pm-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.pm-grid label { margin: var(--space-3) 0 5px; }
/* The push modal's one settings row (project/assignee/status/priority/type/hours) —
   kept to a single compact row so the task list below gets most of the fullscreen
   modal's height instead of two stacked full-width rows plus a 4-col grid. */
.pm-grid-top { grid-template-columns: 1.6fr 1.6fr 1fr 1fr 1fr 0.8fr; margin-top: var(--space-4); }
.pm-grid-top label { margin: 0 0 5px; }

.pm-tasks-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-top: var(--space-5); }
.pm-tasks-head label { margin: 0; text-transform: none; letter-spacing: normal; }
.pm-select-all {
  display: flex !important;
  align-items: center;
  gap: 7px;
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--color-text);
  cursor: pointer;
}
.pm-select-all input { width: auto; }
.pm-selected-count { font-size: var(--text-sm); color: var(--color-text-muted); font-weight: 600; }

/* A sunken well so the scrollable task list reads as one bounded region rather
   than as loose cards floating on the modal. */
.pm-tasks {
  max-height: 320px;
  overflow-y: auto;
  overflow-x: hidden;
  margin-top: var(--space-2);
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}
.pm-task {
  padding: 12px 14px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-xs);
  display: flex;
  align-items: flex-start;
  gap: 12px;
  transition: opacity var(--dur) var(--ease), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.modal input.pm-task-select { width: auto; flex: 0 0 auto; margin-top: 3px; }
.pm-task-body { flex: 1 1 auto; min-width: 0; }
.pm-task-head-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 7px; }
.pm-task-meta {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 700;
}
.modal select.pm-task-assignee { width: auto; min-width: 160px; font-size: var(--text-sm); padding: 4px 26px 4px 8px; background-position: right 6px center; background-size: 13px 13px; }
.pm-task-subject { font-weight: 700; margin-bottom: 7px; }
.pm-task-desc {
  resize: none;
  overflow: hidden;
  font: inherit;
  font-size: var(--text-base);
  width: 100%;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: inherit;
  line-height: 1.6;
}
.pm-task-sent { opacity: 0.65; background: transparent; box-shadow: none; border-style: dashed; }
.pm-task-unselected { opacity: 0.5; }
/* Already-sent tasks render as plain read-only text (see openPushModal in panel.js),
   not disabled inputs — a disabled textarea still shows its resize handle and an
   internal scrollbar that clips content, which looked broken for a field nobody can edit. */
div.pm-task-subject.pm-task-static,
div.pm-task-desc.pm-task-static {
  border: 0;
  background: transparent;
  padding: 0;
  white-space: pre-wrap;
  word-break: break-word;
}
div.pm-task-desc.pm-task-static { font-size: var(--text-base); color: var(--color-text-muted); }
.pm-sent-badge { color: var(--color-success); font-weight: 700; text-transform: none; letter-spacing: normal; }
/* "Already in another Trutimer project" — a warning, not a skip: the row will
   still be sent here. Warning-toned so it can't be mistaken for the green
   .pm-sent-badge, which means the opposite (this one won't be sent). */
.pm-elsewhere-badge { color: var(--color-warning); font-weight: 700; text-transform: none; letter-spacing: normal; }
.pm-elsewhere-badge svg.icon { width: 12px; height: 12px; }

/* Which Ubersuggest project this fullscreen push is reading from — sits beside
   the modal's title, since the toolbar that normally names it is covered. */
.pm-source {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text-muted);
  background: var(--color-surface-muted);
  border: 1px solid var(--color-border);
}
.pm-source svg.icon {
  /* .modal-title's svg rule dresses its icons up as the big rounded chip — this
     one is inline type, so it opts out. */
  width: 13px;
  height: 13px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--color-text-faint);
}

/* An explanation the count alone can't give: every row is sendable, but only
   because this destination hasn't had them yet. */
.pm-tasks-note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 9px var(--space-3);
  margin-bottom: var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-warning);
  background: var(--color-warning-bg);
  border: 1px solid color-mix(in srgb, var(--color-warning) 26%, transparent);
}
.pm-tasks-note svg.icon { width: 15px; height: 15px; flex: none; margin-top: 1px; }

/* Marks where the actionable rows stop and the already-sent ones begin (see
   loadPmTasks). Rendered only when there is something on both sides of it.
   .pm-tasks is a flex column with its own gap, so this only needs to add the
   extra breathing room above that makes the two groups read as separate. */
.pm-tasks-divider {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-faint);
}
.pm-tasks-divider svg.icon { width: 13px; height: 13px; flex: 0 0 auto; }
/* The rule runs to the right of the label rather than boxing it in. */
.pm-tasks-divider::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--color-border);
}
/* The first row in the list never needs the top margin the divider adds. */
.pm-tasks > .pm-tasks-divider:first-child { margin-top: 0; }

/* ---- Settings shell: sidebar nav + panels (settings.html) --------------------- */
.settings-shell {
  display: grid;
  grid-template-columns: 208px 1fr;
  gap: var(--space-6);
  align-items: start;
  /* Wider than the panel page's 1120px measure on purpose: this page's two
     tables (the job list and the nine-column sync log) are the widest things in
     the app, and at 1120 they overflowed their wrapper while ~460px of window
     sat empty either side. Sized so a run fits with its columns intact rather
     than crushed — the nav sits further left to pay for it. */
  max-width: 1360px;
  margin: 0 auto;
}

/* Page title inside the content area rather than the top bar, so it scrolls with
   the page and leaves the bar to navigation. In the settings grid it spans both
   columns and the grid's own row gap spaces it; on a plain page it needs its own
   margin, since .toolbar and .section-head below carry no top margin. */
.page-title { grid-column: 1 / -1; display: flex; align-items: center; gap: 10px; margin-bottom: var(--space-4); }
.settings-shell > .page-title { margin-bottom: 0; }
.page-title h1 { margin: 0; font-size: var(--text-xl); font-weight: 700; letter-spacing: -0.022em; }

.settings-nav {
  position: sticky;
  top: 84px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.settings-nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  font: inherit;
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--color-text-muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 9px 12px;
  cursor: pointer;
  text-align: left;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.settings-nav-item svg.icon { width: 15px; height: 15px; }
.settings-nav-item:hover { background: var(--color-surface-muted); color: var(--color-text); }
/* Active state carries three cues, not one: tinted fill, accent text, and a
   border. Color alone would leave the current section ambiguous for anyone who
   can't separate the two hues. */
.settings-nav-item.active {
  background: var(--color-primary-soft);
  color: var(--color-primary);
  border-color: var(--color-primary-soft-border);
  box-shadow: none;
}
.settings-nav-item.active svg.icon { color: var(--color-primary); }

.settings-nav-item.nav-danger {
  color: var(--color-danger);
  margin-top: var(--space-3);
  padding-top: calc(var(--space-3) + 9px);
  border-top: 1px solid var(--color-border);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}
.settings-nav-item.nav-danger:hover { background: var(--color-danger-bg); color: var(--color-danger); }
.settings-nav-item.nav-danger.active {
  background: var(--color-danger-bg);
  color: var(--color-danger);
  border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
}
.settings-nav-item.nav-danger.active svg.icon { color: var(--color-danger); }

/* ---- Reset (destructive) panel -------------------------------------------------- */
/* A framed, tinted block so the reset controls never read as just another row of
   settings — the visual weight is the warning. */
.danger-zone {
  border: 1px solid color-mix(in srgb, var(--color-danger) 35%, transparent);
  background: color-mix(in srgb, var(--color-danger-bg) 55%, transparent);
  border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-5);
  margin-top: var(--space-2);
}
.danger-zone h3 { margin: 0 0 6px; font-size: var(--text-base); color: var(--color-danger); }
.danger-zone p { margin: 0 0 var(--space-3); font-size: var(--text-sm); color: var(--color-text-muted); line-height: 1.6; }

.reset-list { margin: 0 0 var(--space-4); padding-left: 18px; font-size: var(--text-sm); color: var(--color-text-muted); line-height: 1.9; }
.reset-list strong { color: var(--color-text); font-weight: 600; }
.reset-list .count { color: var(--color-text-faint); }
/* Kept visually apart from the delete list above it — what survives a reset is
   as important to state as what doesn't. */
.reset-keeps { font-size: var(--text-sm); color: var(--color-text-faint); margin: 0; line-height: 1.7; }

.modal .reset-confirm-label { margin-top: var(--space-4); font-size: var(--text-sm); }
.modal input.reset-confirm-input { font-family: var(--font-mono); letter-spacing: 0.08em; }
.reset-check {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  line-height: 1.5;
}
.modal input.reset-check-box { width: auto; flex: 0 0 auto; margin: 2px 0 0; }

.settings-content { min-width: 0; }

.settings-panel {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-5) var(--space-6) var(--space-4);
  box-shadow: var(--shadow-sm);
  margin-bottom: var(--space-5);
}
.settings-panel-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-1);
  border-bottom: 1px solid var(--color-border);
}
.settings-panel-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-md);
  flex: 0 0 auto;
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary-soft-border);
  color: var(--color-primary);
}
.settings-panel-icon svg.icon { width: 16px; height: 16px; }
.settings-panel-head h1 { font-size: var(--text-lg); margin: 0 0 3px; letter-spacing: -0.022em; }
.settings-panel-head p.muted { margin: 0; font-size: var(--text-base); color: var(--color-text-muted); line-height: 1.55; }
.settings-panel-head .badge,
.settings-panel-head .btn { margin-left: auto; flex: 0 0 auto; align-self: center; }
/* Only the first badge claims the auto margin. Flexbox splits free space evenly
   between *every* auto margin, so a second badge doesn't sit next to the first —
   it takes half the gap with it and the pair drifts apart. The connection head
   shows two (saved / verified) and they describe one thing, so they read as a
   pair separated by the head's own gap. */
.settings-panel-head .badge + .badge { margin-left: 0; }

.settings-row {
  display: flex;
  gap: var(--space-5);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-border);
}
.settings-row:last-of-type { border-bottom: 0; }
.settings-row-label { flex: 0 0 250px; }
.settings-row-label h3 { margin: 0; font-size: var(--text-base); font-weight: 700; color: var(--color-text); }
.settings-row-label .req { display: block; font-size: var(--text-xs); margin-top: 3px; }
.settings-row-label .hint { margin: 4px 0 0; }
.settings-row-control { flex: 1 1 auto; max-width: 420px; }
.settings-row-control select, .settings-row-control input { width: 100%; }
.settings-row-control-end { display: flex; align-items: center; }

.settings-panel-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-top: var(--space-4);
  margin-top: var(--space-1);
  border-top: 1px solid var(--color-border);
  flex-wrap: wrap;
}

/* ---- Switch --------------------------------------------------------------------- */
.switch { position: relative; display: inline-block; width: 40px; height: 23px; flex: 0 0 auto; }
.switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.switch .slider {
  position: absolute;
  inset: 0;
  background: var(--color-border-strong);
  border-radius: var(--radius-pill);
  transition: background var(--dur) var(--ease);
  pointer-events: none;
}
.switch .slider::before {
  content: "";
  position: absolute;
  width: 17px;
  height: 17px;
  left: 3px;
  top: 3px;
  background: #fff;
  border-radius: 50%;
  transition: transform var(--dur) var(--ease-out);
  box-shadow: var(--shadow-sm);
}
.switch input:checked + .slider { background: var(--color-primary); }
.switch input:checked + .slider::before { transform: translateX(17px); }
.switch input:disabled + .slider { opacity: 0.5; cursor: not-allowed; }
/* The real <input> is transparent and covers the track, so the focus ring has to
   be drawn on the slider the user actually sees. */
.switch input:focus-visible + .slider { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.interval-row { display: flex; align-items: center; gap: 8px; font-size: var(--text-base); color: var(--color-text-muted); }
.interval-row input[type="number"] { width: 90px; }

/* Project picker + its "Re-check" button: the filter input stretches, the
   button keeps its natural width (min-width:0 so a long domain can't push it off). */
.project-verify-row { display: flex; align-items: center; gap: 8px; }
.project-verify-row select, .project-verify-row input { flex: 1 1 auto; min-width: 0; }
.project-verify-row .btn { flex: 0 0 auto; white-space: nowrap; }

.hint.hint-warn { color: var(--color-warning); }
.hint.hint-danger { color: var(--color-danger); }

/* Multi-project chip picker (job editor's Ubersuggest picker): every project is
   a clickable pill in a wrapping cloud instead of a <select> or a checkbox list
   — a job can pick several, a click toggles one, and a project already claimed
   by another job shows dashed/greyed-out and unclickable, naming its owner. */
.chip-picker {
  display: flex; flex-wrap: wrap; align-content: flex-start; gap: 8px;
  max-height: 200px; overflow-y: auto;
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  padding: 10px;
}
/* Outline by default, filled solid once picked — a deliberately strong
   on/off contrast (not just a hue shift) so the state reads clearly regardless
   of exact theme colors. */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  border: 1.5px solid var(--color-border-strong);
  background: transparent;
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.3;
  white-space: nowrap;
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.chip:hover { border-color: var(--color-primary); color: var(--color-text); }
.chip:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.chip .chip-check { flex: 0 0 auto; font-weight: 800; }
.chip .chip-x { flex: 0 0 auto; font-size: 1.05em; line-height: 1; opacity: .8; }
/* Selected: solid fill, like a pressed toggle — matches .btn's primary fill so
   a picked project reads as "on" the same way a primary action does. */
.chip.selected {
  background: var(--color-primary); border-color: var(--color-primary); color: var(--color-primary-contrast);
  box-shadow: var(--shadow-xs);
}
.chip.selected:hover { background: var(--color-primary-hover); border-color: var(--color-primary-hover); }
.chip.disabled { cursor: not-allowed; opacity: .55; border-style: dashed; color: var(--color-text-faint); }
.chip.disabled:hover { border-color: var(--color-border-strong); color: var(--color-text-faint); }
.chip-empty { padding: 6px 4px; color: var(--color-text-faint); font-size: var(--text-sm); }
/* The loading/empty placeholder is the picker's only child at that point —
   let it span the row instead of shrinking to its own content width like a chip. */
.chip-picker > .loading-row, .chip-picker > .chip-empty { flex: 1 1 100%; padding: 0; }

/* --- Scheduled sync: the job list and the job editor ---------------------- */
/* The editor is a tall form in a modal-lg, so the fields scroll while the title
   and the action buttons stay put — same arrangement as the push modal's #pmForm. */
.modal.modal-lg .job-form { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-right: 4px; }
.job-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
.job-grid-3 { grid-template-columns: repeat(3, 1fr); }
/* Without this a long project name in a <select> stretches its grid column
   instead of ellipsing inside it, and the columns stop lining up. */
.job-grid > div { min-width: 0; }
.job-grid .hint { margin: 4px 0 0; }

.jobs-table td { vertical-align: middle; }
/* The route column next door is long enough that auto table layout will crush
   this one to fit it, and a job called "job 1" then breaks across two lines —
   which reads as two jobs at a glance. The name is the row's label, so it stays
   on one line and ellipses instead; the full name is in the cell's title. The
   route is the column that gives, and .job-route already wraps cleanly. */
.jobs-table .job-name {
  font-weight: 700;
  color: var(--color-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.jobs-table td:first-child { max-width: 240px; }
.jobs-table .badge { margin-top: 5px; }
/* source -> destination on one line, wrapping as a unit on a narrow screen. */
.job-route { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; color: var(--color-text-muted); }
.job-route svg.icon { width: 13px; height: 13px; flex: 0 0 auto; color: var(--color-text-faint); }
.job-actions { display: flex; gap: 6px; justify-content: flex-end; }
/* Icon-only, same pattern as #jobUberRecheckBtn above (icon + a title tooltip,
   no visible label). Three labeled buttons per row (Edit / Run now / Delete)
   were the widest thing in the table by far — wide enough that even a roomy
   desktop window forced .table-wrap into its horizontal-scroll fallback for
   every row, not just on a narrow one. Icon-only keeps all three actions and
   their click targets, just without the label text driving the column width;
   aria-label carries the name for screen readers, since title alone isn't
   reliably exposed to them. */
.job-actions .icon-btn { padding: 6px 9px; }

.logs-head-actions { display: flex; align-items: center; gap: 8px; }
.logs-head-actions select { min-width: 190px; }
/* The "(deleted)" marker beside a log row's job name. */
.log-row .count { color: var(--color-text-faint); }

.log-detail {
  white-space: pre-wrap;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-sunken);
  line-height: 1.65;
}
.log-row { cursor: pointer; }
.log-row td:first-child { white-space: nowrap; }

/* The sync log is nine columns wide, four of them counts (Found/Created/Skipped/
   Failed = columns 6-9; column 5 is the route, not a count). Right-aligning the
   numerics and letting them shrink to their content buys back the width the
   table was overflowing by, so the whole run fits without scrolling. */
#logsTableWrap table { min-width: 720px; }
#logsTableWrap th:nth-child(n+6),
#logsTableWrap td:nth-child(n+6) {
  text-align: right;
  width: 1%;
  white-space: nowrap;
  padding-left: var(--space-2);
  padding-right: var(--space-3);
  font-variant-numeric: tabular-nums;
}
/* The colspan detail row is nth-child(1), so the rule above leaves it alone.
   nowrap for the same reason as .jobs-table .job-name: "Job 1" split over two
   lines reads as two things, not one. */
#logsTableWrap th:nth-child(2),
#logsTableWrap td:nth-child(2) { text-transform: capitalize; white-space: nowrap; }
/* Tighter side padding than the default table (11px 16px) — with nine columns
   the default alone was ~140px of pure whitespace, enough by itself to tip a
   perfectly reasonable panel width into the horizontal-scroll fallback. */
#logsTableWrap th, #logsTableWrap td { padding-left: var(--space-3); padding-right: var(--space-3); }
/* The route column (Ubersuggest project -> portal project) is the one column
   with no natural cap — two domain-ish names plus an arrow icon, easily the
   widest thing in the row. .job-route already wraps (flex-wrap: wrap); it just
   needed a width to wrap *against*, and the two names need to be allowed to
   shrink below their own min-content width so a long one doesn't just push
   past the cap instead of wrapping. */
#logsTableWrap th:nth-child(5),
#logsTableWrap td:nth-child(5) { max-width: 230px; }
#logsTableWrap .job-route span { min-width: 0; overflow-wrap: anywhere; }
/* Rows expand on click, so they need to read as pressable — cursor alone is
   only discoverable once you are already hovering the right pixel. */
.log-row:hover td { background: var(--color-surface-muted); }
.log-row:hover td:first-child { box-shadow: inset 2px 0 0 var(--color-primary); }

/* --- Tasks table ------------------------------------------------------------------ */
/* Same fix as #logsTableWrap above: tighter side padding, and a cap on every
   column with no natural one (project/task/Trutimer names), so six columns
   plus a checkbox fit a normal panel width without tripping .table-wrap's
   horizontal-scroll fallback. */
#tasksTableWrap th, #tasksTableWrap td { padding-left: var(--space-3); padding-right: var(--space-3); }
#tasksTableWrap th:first-child, #tasksTableWrap td:first-child {
  width: 1%;
  max-width: none;
  padding-right: 0;
  text-align: center;
}
#tasksTableWrap td:nth-child(2) {
  max-width: 170px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#tasksTableWrap td:nth-child(3) { max-width: 320px; }
#tasksTableWrap td:nth-child(5) {
  max-width: 190px;
  overflow-wrap: anywhere;
}
#tasksTableWrap td:nth-child(6) { white-space: nowrap; }

/* ---- Responsive ------------------------------------------------------------------- */
@media (max-width: 900px) {
  .pm-grid-top { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 760px) {
  .settings-shell { grid-template-columns: 1fr; gap: var(--space-4); }
  .settings-nav {
    position: static;
    flex-direction: row;
    overflow-x: auto;
    gap: 6px;
    padding-bottom: 4px;
    margin-bottom: var(--space-1);
  }
  .settings-nav-item { flex: 0 0 auto; white-space: nowrap; }
  .settings-row { flex-direction: column; gap: 8px; }
  .settings-row-label { flex: none; }
  .settings-row-control { max-width: none; }
  .settings-panel { padding: var(--space-4) var(--space-5); }
}

@media (max-width: 640px) {
  .app-header { padding: 10px var(--space-4); flex-wrap: wrap; row-gap: 8px; }
  .app-header h1 { font-size: var(--text-base); }
  .header-actions { flex: 1 0 100%; justify-content: flex-end; }
  /* Three tabs don't fit a phone: scroll them rather than wrapping each label,
     and give them the width the wordmark was using. */
  .top-nav { flex: 1 1 auto; overflow-x: auto; }
  .top-nav-item { padding: 8px 10px; }
  .brand-name { display: none; }
  main { padding: var(--space-4); }
  .toolbar { flex-direction: column; align-items: stretch; }
  .toolbar .grow { display: none; }
  /* .toolbar-select's 300px flex-basis is a main-axis (width) constraint for
     the row layout above — in this column layout the main axis is vertical, so
     left as-is it would force a 300px-tall select. Reset to a plain full-width
     block instead; the title above it already stacks naturally. */
  .toolbar-select { flex: none; }
  .toolbar-field { flex-direction: column; align-items: stretch; gap: 6px; }
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tile { padding: var(--space-3); }
  .tile .n { font-size: var(--text-xl); }
  .opp-tags { flex-basis: 100%; justify-content: flex-start; margin-top: 2px; }
  .pm-grid, .pm-grid-top { grid-template-columns: repeat(2, 1fr); }
  .modal.modal-fullscreen { padding: var(--space-4); }
  .modal-actions { flex-direction: column-reverse; }
  .modal-actions .btn { width: 100%; }
  /* Data tables are exempt from the reflow requirement — horizontal scroll is
     the correct affordance here rather than stacking cells. (.table-wrap already
     scrolls at every width; this just stops columns crushing on a phone.) */
  table { min-width: 480px; }
}

@media (max-width: 400px) {
  .tiles { grid-template-columns: 1fr; }
  .pm-grid, .pm-grid-top { grid-template-columns: 1fr; }
}
