@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap');

:root {
  /* ─── Sumi (墨) — dark theme ─────────────────────────────────────
     Warm-neutral ramp + one koi accent. Vermillion is reserved for
     "this is asking for you": overdue, active state, brand mark,
     primary action. Everything else lives in the neutral or energy
     ramps — no orange theatrics, no competing accents. */

  /* Surfaces. --bg is the page; --bg-elevated lifts cards / drawers /
     popovers; --bg-input sits a touch deeper than the page so inputs
     read as sunken slots rather than another raised card.
     v2 tuning: deepened the page and brightened the card so the lift
     reads — original 3% delta was too thin to perceive against the
     dark surround. */
  --bg: #070710;
  --bg-elevated: #1a1a22;
  --bg-input: #0e0e13;

  /* Text ramp. Targets on --bg #0a0a0e: text ~14:1 (AAA), secondary
     ~8.1:1 (AAA), muted ~5.2:1 (AA body), dim ~3.0:1 (AA-large only
     — eyebrows, counts, placeholders), strike below that for finished
     state where readability is intentionally fading. */
  --text: #e8e6e0;
  --text-secondary: #b5b2ab;
  --text-muted: #8e8b85;
  --text-dim: #6a6864;
  --strike: #3f3d38;

  --border: #1f1f27;
  --border-light: #15151b;

  /* Koi. Used sparingly — overdue stripe, active tab, focused filter,
     primary button fill, brand mark, edit-form edge flash. If you're
     reaching for accent in a fourth place, ask whether it earns it. */
  --accent: #c8392e;
  --accent-hover: #d94539;

  /* Energy ramp — sits inside the same earthy family. Used by the
     energy-square indicator and the energy chip; can also tint the
     effort badge in future. low = restorative, med = default, high =
     draining. */
  --energy-low: #6b7f49;
  --energy-med: #7d7a72;
  --energy-high: #a85543;

  /* Semantic re-exports so the rest of the file keeps composing
     against meaningful names. success / warning / danger map onto
     the palette: completed work → moss green, waiting/parked → amber,
     true "danger" → koi (we only surface one alert color). */
  --success: #6b7f49;
  --warning: #b58a3e;
  --danger:  #c8392e;

  --google-blue: #6ea8f5;       /* slight desat for the warmer surround */

  /* Delegation — amber family, distinct chrome so "ACTING AS …" never
     conflates with the active koi. */
  --delegate:        #b58a3e;
  --delegate-bg:     #1a1410;
  --delegate-border: #4a3a1c;

  /* Card decoration. Inset top highlight + soft drop = warm sumi card
     resting on the board. Single token so we don't repeat it.
     v2 tuning: the original values were tuned for the mockup's body
     bg (#060609) at a higher zoom — at the app's actual viewport the
     shadow was invisible. Pushed both the inset and the drop a step
     stronger so cards now read as objects on a surface. */
  --card-top:    rgba(255,255,255,0.04);
  --card-shadow: 0 1px 0 rgba(0,0,0,0.55), 0 4px 14px rgba(0,0,0,0.45);

  /* Overlays are a separate register from cards. "Cards cast no shadow at
     rest" is a statement about cards; a menu is above the page by definition.
     Camel setting --card-shadow to none left .ws-menu / .kb-cols-menu /
     .header-overflow-menu as white panels over white with only a 1.36:1
     hairline between them. Paper themes override this below. */
  --overlay-shadow: 0 10px 28px -6px rgba(0,0,0,0.55), 0 2px 6px rgba(0,0,0,0.4);

  /* Japanese serif — column-header glosses (受信 / 進行中 / 予定 / 完了 / いつか)
     and any inline JP characters. Mincho stack for the brushed-ink
     feel; fallbacks if the Japanese fonts aren't installed. */
  --jp: "Hiragino Mincho ProN", "Yu Mincho", "Noto Serif JP", "Hiragino Sans", system-ui, serif;
}

/* Paper themes need a far lighter overlay than the dark default. */
[data-theme="light"],
[data-theme="modernist"],
[data-theme="camel"] {
  --overlay-shadow: 0 10px 28px -6px rgba(17,20,23,0.22), 0 2px 6px rgba(17,20,23,0.08);
}

/* Light theme — warm off-white bg paired with a darker accent so the
   contrast inversion holds. Pattern: many rules use `color: var(--bg)`
   on `background: var(--accent)` (buttons, badges, ::selection). For
   that to stay readable, accent and bg must remain high-contrast
   opposites in BOTH modes. Keep that invariant if you tweak colors. */
[data-theme="light"] {
  /* Editorial Sumi — Warm Theme palette (WARM_THEME.md).
     Warm parchment + clay. Sand page sits flat across header/tabs/filters;
     per-column tonal fills (warm status family) do the band work. CLAY is the
     single brand accent — active tab, restore links, primary buttons; BRICK is
     reserved for danger only. Parchment cards lift via shadow on the column
     tone. The Japanese parts stay: Mincho column glosses (--jp), serif display
     headings, and the sumi column treatment. Energy ramp = sage / ochre / clay. */
  --bg: #ece6d8;                /* sand — page, flat across all chrome */
  --bg-elevated: #fcf9f3;       /* paper — kanban cards, popovers, drawer */
  --bg-header: #ece6d8;
  --bg-tabs: #ece6d8;
  --bg-filters: #ece6d8;
  --bg-input: #fcf9f3;          /* paper — clean field slots on sand */
  --text: #3d382f;              /* ink */
  --text-secondary: #565045;    /* ~7:1 on --bg — between ink and inksoft */
  --text-muted: #7c7466;        /* inksoft */
  --text-dim: #a89f8e;          /* eyebrows / counts / placeholders (AA-large) */
  --strike: #a89f8e;
  --border: #dcd4c3;            /* line */
  --border-light: #e6dfd1;
  --accent: #ad5e48;            /* clay-600 — brand action */
  --accent-hover: #8f4b38;      /* clay-700 — darker on hover */
  --success: #566a45;           /* sage-700 */
  --warning: #8a6a1f;           /* ochre-700 */
  --danger:  #a63a2b;           /* brick-600 — danger only */
  --google-blue: #1a73e8;       /* Google brand — unchanged */
  --delegate: #8a6a1f;          /* ochre-700 */
  --delegate-bg: #eeddb4;       /* ochre-50 */
  --delegate-border: #c2912e;   /* mid ochre */
  --energy-low:  #566a45;       /* sage — restorative */
  --energy-med:  #8a6a1f;       /* ochre — default */
  --energy-high: #8f4b38;       /* clay-700 — draining */
  --card-top:    rgba(255,255,255,0.55);
  --card-shadow: 0 1px 0 rgba(61,56,47,0.04), 0 6px 18px rgba(61,56,47,0.08);
  --jp: "Hiragino Mincho ProN", "Yu Mincho", "Noto Serif JP", "Hiragino Sans", system-ui, serif;
  /* Warm Theme typeface — Figtree across the whole UI (body, headings, meta,
     chips, counts). Only the Japanese glosses keep Mincho via --jp. */
  --font-ui:  'Figtree', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-mono: 'Figtree', ui-sans-serif, system-ui, sans-serif;
  --mono:      'Figtree', ui-sans-serif, system-ui, sans-serif;
  --serif-display: 'Figtree', ui-sans-serif, system-ui, sans-serif;
  --serif-body:    'Figtree', ui-sans-serif, system-ui, sans-serif;
  /* Per-column tonal fills — warm status family (sage / dust / mauve / ochre),
     with a warm stone for the neutral Inbox. Parchment cards sit on top. */
  --col-inbox:     #e4dccb;     /* warm stone */
  --col-in-progress: #ead3c6;   /* clay — warmer than stone, cooler than sage */
  --col-scheduled: #dce6cc;     /* sage-50 */
  --col-done:      #cdd6db;     /* dust-50 */
  --col-someday:   #dbd0dc;     /* mauve-50 */
  --col-trashed:   #eeddb4;     /* ochre-50 */
}

/* Direction 2 — Modernist Stripe. Pale white surface, geometric sans
   typography, columns identified by a 4px top stripe in their accent
   colour. Azure is the action accent. Energy ramp uses the standard
   green/amber/red traffic-light trio. */
[data-theme="modernist"] {
  --bg: #f5f6f8;
  --bg-elevated: #ffffff;
  --bg-header: #ffffff;
  --bg-tabs: #ffffff;
  --bg-filters: #ffffff;
  --bg-input: #ffffff;
  --text: #0d1117;
  --text-secondary: #3d434c;
  --text-muted: #6b7280;
  --text-dim: #aab0bb;
  --strike: #aab0bb;
  --border: #e5e7eb;
  --border-light: #eef0f3;
  --accent: #2563eb;
  --accent-hover: #1d4ed8;
  --success: #16a34a;
  --warning: #f59e0b;
  --danger:  #ef4444;
  --google-blue: #1a73e8;
  --delegate: #92400e;
  --delegate-bg: #fef3c7;
  --delegate-border: #d97706;
  --energy-low:  #16a34a;
  --energy-med:  #f59e0b;
  --energy-high: #ef4444;
  --card-top:    rgba(255,255,255,1);
  --card-shadow: 0 1px 2px rgba(15,23,42,0.04), 0 1px 0 rgba(15,23,42,0.02);
  --jp: 'Noto Sans JP', 'Plus Jakarta Sans', sans-serif;
  --sans-display: 'Plus Jakarta Sans', system-ui, sans-serif;
  --sans-body:    'Plus Jakarta Sans', system-ui, sans-serif;
  /* Per-column accent + ultra-pale body tint (the column reads as white
     unless you look closely). The accent rides as a 4px top border. */
  --col-inbox:     #f8faff;
  --col-in-progress: #f4fdfb;
  --col-scheduled: #fffaf0;
  --col-done:      #f5fbf6;
  --col-someday:   #faf7ff;
  --col-trashed:   #f7f8fa;
  --col-inbox-accent:     #2563eb;
  --col-in-progress-accent: #0d9488;   /* teal — amber scheduled sits too close to orange */
  --col-scheduled-accent: #f59e0b;
  --col-done-accent:      #16a34a;
  --col-someday-accent:   #8b5cf6;
  --col-trashed-accent:   #9aa3b2;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* The chrome bands stretch to 100vw, and vw units include the scrollbar
   gutter — without this the ~15px overshoot grows a horizontal scrollbar.
   clip (not hidden) so html never becomes a scroll container. */
html { overflow-x: clip; }

html, body {
  /* --font-ui lets a theme swap the whole UI typeface; unset → the mono stack,
     so dark / modernist are unchanged. Editorial Sumi sets it to
     Figtree (Warm Theme). */
  font-family: var(--font-ui, 'JetBrains Mono', 'Fira Code', 'SF Mono', monospace);
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--accent); color: var(--bg); }
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: var(--border); }

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

/* Keyboard-focus ring for non-input controls. Inputs already get a
   border-color shift on :focus (see .input below), so we exclude them here
   to avoid the outline doubling up on top of the border change.
   Mouse-click :focus on the same controls is explicitly cleared — without
   this, some browsers render their default outline (often dark / near-
   black) on the just-clicked button, which can read as a stray border on
   the surrounding container. */
button:focus,
[role="button"]:focus,
select:focus,
a:focus,
[tabindex]:focus { outline: none; }
button:focus-visible,
[role="button"]:focus-visible,
select:focus-visible,
a:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ─── LAYOUT ──────────────────────────────────────────────── */
/* One constant frame for the whole app shell so the header + tab strip never
   shift width between tabs — the nav stays put as you move Home → Today →
   Projects → People. Caps at 1800px (the width the kanban board wants for five
   comfortable columns) and shrinks fluidly with the viewport below that.
   Views lay their own content out inside this frame (Today's responsive
   columns, the inbox card grid) rather than narrowing the whole shell.

   The chrome bands (.header, .tabs, .delegate-banner) are the exception: their
   surfaces and hairlines belong to the viewport, not the frame. Each spans the
   full window with margin-inline: calc(50% - 50vw) and hands the overshoot
   back as padding-inline: calc(50vw - 50%), so the band paints edge to edge
   while its content stays on this frame's rail. (Before this, a band's fill
   and border stopped dead at 1800px and read as a rendering bug on any monitor
   wider than the frame.) The math self-adapts to whatever gutter .app carries:
   inside a viewport-wide frame the margins resolve to exactly -16px / -12px,
   which is what the old hard-coded values did. */
.app {
  max-width: 1800px; margin: 0 auto;
  /* Safe-area insets for notch / home-indicator on iOS PWAs. The base
     16/100 paddings stay as a minimum; env() only adds to them when the
     device actually reports an inset. */
  padding:
    max(16px, env(safe-area-inset-top, 16px))
    max(16px, env(safe-area-inset-right, 16px))
    calc(100px + env(safe-area-inset-bottom, 0px))
    max(16px, env(safe-area-inset-left, 16px));
}
.app-narrow { max-width: 680px; margin: 0 auto; }

.header {
  /* Three tracks so the workspace anchor lands on the true optical centre of
     the bar whatever the two flanks weigh. Reverts to the wrapping flex row
     at <=700px — see the mobile block further down. */
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  /* Viewport-wide band, content on the frame's rail — see the LAYOUT note.
     Vertical values unchanged: top margin still counters .app's gutter. */
  padding: 14px calc(50vw - 50%);
  margin: -16px calc(50% - 50vw) 0;

  /* Workspace wash. appHeaderHTML sets --ws-wash inline when a single
     workspace is active; unset (All, or signed out) it falls back to the
     header's own colour and the mix resolves to a no-op. 12% is the most
     tint the strip takes while --text-muted still clears 4.5:1 on it. */
  --ws-wash: var(--bg-header, transparent);
  background: color-mix(in srgb, var(--ws-wash) 12%, var(--bg-header, transparent));
}
.header-lead { display: flex; align-items: center; min-width: 0; }
.header-actions { justify-self: end; }
/* The negative pull-up assumes .app's padding is the plain 16px gutter. On iOS
   that padding also carries the status-bar inset, and pulling through it puts
   the logo behind the clock — where it stops taking taps. Whichever of these
   lands at the top stops pulling once an inset exists; a header sitting under a
   delegate banner keeps its negative margin, so the two stay flush. */
.header:first-child,
.delegate-banner:first-child {
  margin-top: min(0px, calc(-16px + env(safe-area-inset-top, 0px)));
}
.logo { font-size: 1.2em; font-weight: 700; cursor: pointer; }
.logo-accent { color: var(--accent); }
.logo-alpha {
  font-size: 0.58em; font-weight: 500; color: var(--text-muted);
  margin-left: 7px; letter-spacing: 0.5px; vertical-align: 1px;
}
.header-actions { display: flex; gap: 16px; align-items: center; position: relative; }
.header-link {
  color: var(--text-muted); cursor: pointer; font-size: 0.8em;
  letter-spacing: 1px; transition: color 0.2s; border: none; background: none;
}
.header-link:hover { color: var(--text-secondary); }

/* Caret shared by the workspace switcher and the SETTINGS dropdown trigger. */
.theme-caret { color: var(--text-dim); font-size: 0.9em; line-height: 1; }

/* ─── NOTIFICATIONS ───────────────────────────────────────────
   Bell + popover + the /#/notifications page. Every colour is derived from
   --accent via color-mix rather than hard-coded, so Editorial, Modernist and
   Camel inherit the unread tint without a per-theme pass. The one value that
   genuinely needs tuning is the tint percentage: clay on paper carries further
   than vermillion on near-black, so the light themes take it down a notch. */
/* The popover hangs off the bell, the way the Settings menu hangs off its own
   trigger — a panel that opens somewhere else reads as unrelated to the thing
   you clicked. The bell sits beside the avatar at the right edge, so like every
   other header menu it aligns right and opens leftwards; a left-aligned 360px
   panel would run off the right of the viewport.
   Scoped under .header-actions past .header-menu, which sits on the same element
   and later in this file — two single-class selectors, so source order would
   otherwise win. */
.header-actions .notif-wrap { position: relative; display: inline-flex; }
.notif-bell {
  display: inline-flex; align-items: center;
  position: relative;
  padding: 4px;
  min-width: 32px; min-height: 32px;
  justify-content: center;
}
.notif-bell-on { color: var(--text); }
/* Send feedback: same footprint as the bell so the two read as a pair. Stays
   in the account menu too, which is where phones reach it (collapsible). */
.header-feedback {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 7px; padding: 4px; min-width: 32px; min-height: 32px;
}
.header-feedback .icon { margin: 0; }
/* We're leaning on people to report things, so the icon alone wasn't pulling
   its weight — this labels it. A rectangle with the right edge cut to a point
   aimed at the icon; clip-path rather than a border-triangle ::after so the
   fill stays one element and one colour. aria-hidden: the button already
   carries "Send feedback" as its accessible name, so this would only stutter.
   Collapses with its button at ≤700px (.header-link-collapsible). */
.header-feedback-tag {
  display: inline-flex; align-items: center;
  padding: 4px 11px 4px 7px;
  background: var(--accent); color: #fff;
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.8px;
  line-height: 1; text-transform: uppercase; white-space: nowrap;
  clip-path: polygon(0 0, calc(100% - 5px) 0, 100% 50%, calc(100% - 5px) 100%, 0 100%);
}
.notif-count {
  position: absolute; top: -1px; right: -3px;
  min-width: 15px; height: 15px; padding: 0 4px;
  background: var(--accent); color: #fff;
  font-size: 9.5px; font-weight: 700; letter-spacing: 0;
  line-height: 15px; text-align: center;
  border-radius: 8px;
  font-variant-numeric: tabular-nums;
}
.header-actions .notif-menu {
  width: 360px; max-width: calc(100vw - 24px); padding: 0;
  left: auto; right: 0;
}
.notif-menu-head {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  padding: 10px 13px;
  border-bottom: 1px solid var(--border-light);
  font-size: 0.7em; letter-spacing: 2px; text-transform: uppercase;
  color: var(--text-muted);
}
.notif-mark {
  background: none; border: 0; padding: 0;
  font: inherit; font-size: 1em; letter-spacing: 1px;
  color: var(--text-muted); cursor: pointer;
}
.notif-mark:hover { color: var(--text); }
.notif-daygroup {
  padding: 8px 13px 4px;
  font-size: 0.64em; letter-spacing: 2px; text-transform: uppercase;
  color: var(--text-dim);
}
.notif-empty {
  padding: 14px 13px; font-size: 0.82em; color: var(--text-muted); line-height: 1.5;
}
.notif-empty-page { padding: 34px 4px; text-align: center; }

/* Rows are buttons: the whole row is the target, and it has to be reachable by
   keyboard like any other control. */
.notif-row {
  display: grid; grid-template-columns: auto 1fr; gap: 10px;
  width: 100%; text-align: left;
  background: none; border: 0;
  border-bottom: 1px solid var(--border-light);
  padding: 10px 13px;
  font: inherit; color: var(--text);
  cursor: pointer;
  position: relative;
}
.notif-row:last-child { border-bottom: 0; }
.notif-row:hover { background: var(--bg-input); }
.notif-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Unread is carried by TWO signals — the rail and the tint — so it survives a
   colourblind reader and a washed-out screen. */
.notif-row-unread { background: color-mix(in srgb, var(--accent) 13%, transparent); }
.notif-row-unread::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 2px; background: var(--accent);
}
.notif-row-unread:hover { background: color-mix(in srgb, var(--accent) 18%, transparent); }
[data-theme="light"] .notif-row-unread,
[data-theme="camel"] .notif-row-unread,
[data-theme="modernist"] .notif-row-unread { background: color-mix(in srgb, var(--accent) 9%, transparent); }
[data-theme="light"] .notif-row-unread:hover,
[data-theme="camel"] .notif-row-unread:hover,
[data-theme="modernist"] .notif-row-unread:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }

.notif-avatar {
  width: 26px; height: 26px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--bg-input);
  color: var(--text-secondary);
  font-size: 9.5px; font-weight: 600; letter-spacing: 0.5px;
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
}
.notif-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.notif-line { font-size: 0.85em; line-height: 1.45; color: var(--text); }
.notif-subject { color: var(--text-secondary); }
.notif-excerpt {
  font-size: 0.78em; color: var(--text-muted); line-height: 1.5;
  border-left: 2px solid var(--border); padding-left: 8px;
  overflow-wrap: anywhere;
}
.notif-meta {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  font-size: 0.64em; letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--text-dim);
}
.notif-reason { color: var(--text-muted); }
.notif-reason-strong { color: var(--accent); }
.notif-ws { border: 1px solid var(--border); padding: 1px 5px; color: var(--text-muted); }
.notif-foot {
  display: block; width: 100%;
  background: none; border: 0; border-top: 1px solid var(--border-light);
  padding: 10px 13px;
  font: inherit; font-size: 0.72em; letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--text-secondary); cursor: pointer;
}
.notif-foot:hover { background: var(--bg-input); color: var(--text); }

/* Page */
.notif-page { padding: 0 0 40px; }
.notif-page-head {
  display: flex; justify-content: space-between; align-items: flex-end;
  gap: 16px; flex-wrap: wrap;
  padding: 20px 0 14px;
}
.notif-page-title { font-size: 1.35em; font-weight: 700; letter-spacing: -0.01em; }
.notif-page-sub { font-size: 0.78em; color: var(--text-muted); margin-top: 3px; }
.notif-filters { margin-bottom: 4px; }
.notif-chip-n { font-variant-numeric: tabular-nums; opacity: 0.8; }
.notif-daybar {
  font-size: 0.64em; letter-spacing: 2px; text-transform: uppercase;
  color: var(--text-dim);
  padding: 16px 0 8px;
  border-bottom: 1px solid var(--border-light);
}
.notif-row-full { grid-template-columns: auto 1fr auto; padding: 13px 12px 13px 10px; }
.notif-row-full .notif-time {
  font-size: 0.68em; color: var(--text-dim); letter-spacing: 0.6px;
  font-variant-numeric: tabular-nums; white-space: nowrap; padding-top: 2px;
}
/* A notification you have read is still the fastest route back to the thread it
   came from, so read rows stay in place rather than vanishing. */
.notif-row-full:not(.notif-row-unread) { opacity: 0.62; }
.notif-row-full:not(.notif-row-unread):hover { opacity: 1; }

/* Mention chips inside a note body. Wrapped after DOMPurify has run, around a
   name we already hold, so this cannot reopen an injection path. */
.note-mention {
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  color: var(--accent);
  padding: 0 3px; border-radius: 2px;
  font-weight: 500;
}
.note-mention-me {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  box-shadow: inset 0 -1px 0 var(--accent);
}

@media (max-width: 700px) {
  /* The bell is the one header control that must not collapse into ⋯ — a badge
     nobody can see is not a notification. */
  .notif-wrap.header-link-collapsible { display: inline-flex; }
  /* Below this width a near-full-width panel cannot hang off the bell without
     running off the left edge, so it becomes a sheet under the whole header
     instead. Selectors match the desktop rules' specificity — a bare
     .notif-menu here would lose to .header-actions .notif-menu above. */
  .header-actions .notif-wrap { position: static; }
  .header-actions .notif-menu { width: calc(100vw - 24px); }
}

/* ─── @ MENTION PICKER ────────────────────────────────────────
   Anchored above the composer rather than to the caret: the textarea is short,
   the list is short, and caret-coordinate maths in a textarea is a well-known
   source of off-by-a-line bugs for no visible gain here. */
.kb-notes-composer { position: relative; }
.mention-menu {
  position: absolute; left: 0; bottom: calc(100% + 6px);
  width: 290px; max-width: 100%;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  box-shadow: var(--card-shadow);
  z-index: 30;
  max-height: 260px; overflow-y: auto;
}
.mention-row {
  display: grid; grid-template-columns: auto 1fr; gap: 9px; align-items: center;
  width: 100%; text-align: left;
  background: none; border: 0;
  padding: 8px 11px;
  font: inherit; color: var(--text);
  cursor: pointer;
}
.mention-row:hover { background: var(--bg-input); }
.mention-row-active { background: var(--bg-input); box-shadow: inset 2px 0 0 var(--accent); }
.mention-avatar {
  width: 21px; height: 21px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--bg-input);
  color: var(--text-secondary);
  font-size: 8.5px; font-weight: 600;
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
}
.mention-who { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.mention-name { font-size: 0.85em; }
.mention-context {
  font-size: 0.66em; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mention-empty { padding: 10px 11px; font-size: 0.78em; color: var(--text-muted); }
.mention-foot {
  position: sticky; bottom: 0;
  border-top: 1px solid var(--border-light);
  background: var(--bg-elevated);
  padding: 7px 11px;
  font-size: 0.64em; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--text-dim);
  display: flex; gap: 10px; flex-wrap: wrap;
}

/* ─── WATCHERS ROW + PROJECT FOLLOW ───────────────────────────
   The watchers row sits above the notes thread in the task drawer, and the
   follow control sits in the project header. They share a button because they
   are the same promise in two places: "tell me about this". */
.kb-watchers {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  position: relative;
  padding-bottom: 10px;
}
.kb-watch-label {
  background: none; border: 0; padding: 0;
  font: inherit; font-size: 0.7em; letter-spacing: 2px; text-transform: uppercase;
  color: var(--text-muted); cursor: pointer;
}
.kb-watch-label:hover { color: var(--text); }
.kb-avatar-stack { display: flex; }
.notif-avatar-sm { width: 21px; height: 21px; font-size: 8.5px; }
/* The ring is the page ground, so overlapping avatars read as separate discs
   rather than one blob. */
.kb-avatar-stack .notif-avatar { margin-left: -6px; box-shadow: 0 0 0 2px var(--bg-elevated); }
.kb-avatar-stack .notif-avatar:first-child { margin-left: 0; }
.kb-watch-btn {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--border); background: var(--bg-input);
  color: var(--text-secondary);
  padding: 7px 13px; min-height: 34px;
  font: inherit;
  font-size: 0.7em; letter-spacing: 1.6px; text-transform: uppercase;
  cursor: pointer;
  transition: color .15s, border-color .15s;
}
.kb-watch-btn:hover { color: var(--text); border-color: var(--text-muted); }
.kb-watch-btn-on { color: var(--accent); border-color: var(--accent); }
.kb-watch-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.kb-watch-btn .icon { width: 14px; height: 14px; }
.kb-watch-pop {
  position: absolute; right: 0; top: calc(100% + 2px);
  width: 268px; max-width: calc(100vw - 32px);
  background: var(--bg-elevated); border: 1px solid var(--border);
  box-shadow: var(--card-shadow); z-index: 20;
}
.kb-watch-pop[hidden] { display: none; }
.kb-watch-pop-row {
  display: grid; grid-template-columns: auto 1fr auto; gap: 9px; align-items: center;
  padding: 8px 11px;
}
.kb-watch-why {
  font-size: 0.62em; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--text-dim); white-space: nowrap;
}

.pj-follow { position: relative; display: inline-flex; }
.pj-follow .kb-watch-btn { margin-left: 0; }
.pj-follow-menu {
  position: absolute; right: 0; top: calc(100% + 6px);
  width: 250px; max-width: calc(100vw - 32px);
  background: var(--bg-elevated); border: 1px solid var(--border);
  box-shadow: var(--card-shadow); z-index: 20;
}
.pj-follow-menu[hidden] { display: none; }
.pj-follow-opt {
  display: flex; flex-direction: column; gap: 2px;
  width: 100%; text-align: left;
  background: none; border: 0;
  padding: 9px 12px;
  font: inherit; color: var(--text-secondary);
  cursor: pointer;
}
.pj-follow-opt:hover { background: var(--bg-input); }
.pj-follow-opt-active { color: var(--accent); background: var(--bg-input); }
.pj-follow-opt-active .pj-follow-name::before { content: "● "; font-size: 0.7em; }
.pj-follow-name { font-size: 0.82em; }
.pj-follow-desc {
  font-size: 0.68em; color: var(--text-dim); line-height: 1.45;
  letter-spacing: 0.2px; text-transform: none;
}

/* ─── TABS ────────────────────────────────────────────────── */
.tabs {
  display: flex;
  align-items: center;
  /* Same wash recipe as .header so the two bands read as one chrome group —
     appTabsHTML sets --ws-wash inline exactly as appHeaderHTML does; unset
     (All, or signed out) the mix resolves to plain --bg-tabs. */
  --ws-wash: var(--bg-tabs, transparent);
  background: color-mix(in srgb, var(--ws-wash) 12%, var(--bg-tabs, transparent));
  /* Viewport-wide band, content on the frame's rail — see the LAYOUT note. */
  padding: 0 calc(50vw - 50%);
  margin: 0 calc(50% - 50vw) 0;  /* flush against header band above */
}
/* Capture box rides the right end of the strip on every dashboard tab. The
   "+ New task" button is hidden until there's text — Enter alone files the
   line to the Inbox, so the button only earns its space once it has a title
   to hand the modal. */
.tabs-capture {
  margin: 0 0 0 auto; display: flex; align-items: center; gap: 8px;
  padding: 4px 0;
}
/* Same anatomy as .pal-trigger in the header: icon, text, shortcut badge in
   one bordered box. The border and background live on the wrapper so the
   three sit inside a single field rather than the input carrying its own. */
.tabs-capture-field {
  display: flex; align-items: center; gap: 8px;
  min-width: 240px; min-height: 34px; padding: 6px 10px;
  background: var(--bg-input); border: 1px solid var(--border);
  border-radius: 6px;
  transition: border-color 0.2s;
}
.tabs-capture-field:focus-within { border-color: var(--accent); }
.tabs-capture-field .icon { color: var(--text-muted); flex: 0 0 auto; }
.tabs-capture .home-capture-input {
  flex: 1 1 auto; min-width: 0; min-height: 0;
  background: none; border: none; border-radius: 0; padding: 0;
  line-height: 1.25;   /* without this the input's leading makes the field 3px taller than .pal-trigger */
}
.tabs-capture-kbd { flex: 0 0 auto; }
/* Sized to the field it acts on, and it slides in from behind the field's
   right edge the moment there's text — a small "this just became possible"
   rather than a button appearing from nowhere. */
.tabs-capture .tabs-capture-new {
  height: 34px; min-height: 0; padding: 0 12px; font-size: 0.78em;
  animation: tabs-capture-in 0.18s ease-out;
}
.tabs-capture-new[hidden] { display: none; }
@keyframes tabs-capture-in {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .tabs-capture .tabs-capture-new { animation: none; } }
/* Phone: the strip scrolls, so the box drops to its own full-width row under
   the tabs rather than riding inside the scroller. */
@media (max-width: 700px) {
  .tabs { flex-wrap: wrap; }
  .tabs-capture { flex: 1 1 100%; margin: 0; padding: 8px 0; }
  .tabs-capture-field { flex: 1 1 auto; min-width: 0; min-height: 44px; }
}
/* Phone: the strip is wider than the screen, so Projects gets cut mid-word with
   nothing to say it continues. Let it scroll, keep the tabs from being squeezed,
   and fade the right edge so the cut reads as "more" rather than "broken". */
.tabs-strip { display: flex; }
@media (max-width: 700px) {
  .tabs-strip {
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
            mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
  }
  .tabs-strip::-webkit-scrollbar { display: none; }
  .tabs .tab { flex: 0 0 auto; }
}
.tab {
  padding: 12px 18px; cursor: pointer; font-size: 0.85em;
  letter-spacing: 1px; color: var(--text-muted); border: none; background: none;
  border-bottom: 2px solid transparent; transition: all 0.2s;
}
.tab.active { color: var(--accent); border-bottom-color: var(--accent); }
/* Hover fills the tab's own block, not just the label. Mixed off --text so a
   single rule reads correctly on every theme — a faint lift on the dark ones,
   a faint darkening on parchment/white. The active tab gets an even fainter
   accent wash so the whole strip feels alive without competing with the
   accent underline. */
.tab:hover:not(.active) {
  color: var(--text-secondary);
  background: color-mix(in srgb, var(--text) 6%, transparent);
}
.tab.active:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }
/* Inbox count on the Plan tab — dimmed "(47)" beside the label, identical
   on every tab, active included (no .tab.active override — the count keeps
   one look wherever you are). Nothing here may set a height or
   vertical-align, or the tab row drifts. */
.tab-count { margin-left: 5px; color: var(--text-dim); font-size: 0.9em; letter-spacing: 0; }
.tab-count[hidden] { display: none; }

/* ─── SUBNAV (second level) ───────────────────────────────────
   Settings sections and project-page panels. This row sits directly under the
   primary strip, so it must not reuse the primary's device: an accent underline
   on both rows makes two levels shout the same way, and the caps this row used
   to carry made the lower one shout louder than the one above it. Pills instead
   — an underline means "where you are in the app", a filled pill means "where
   you are on this page", and the two can't be read as the same thing. Sentence
   case, one step down in size, and the active state carries a wash as well as
   accent text so it never rests on colour alone.
   Styled by the two existing class names rather than a new one, so the JS that
   toggles them is untouched. */
.settings-tabs, .pj-tabs {
  display: flex; align-items: center; gap: 2px;
  /* The row can outrun the viewport, so it scrolls — but the scrollbar itself
     is chrome we don't want sitting under the nav. It drew a grey band across
     the settings row even on desktop, where all seven sections fit. Same
     treatment as .tabs-strip: scroll, no visible bar. */
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.settings-tabs::-webkit-scrollbar, .pj-tabs::-webkit-scrollbar { display: none; }
.settings-tab, .pj-tab {
  /* 14px inline padding, not 13, so the first pill's text sits on the same
     vertical line as the first primary tab's — the two rows read as one edge. */
  flex: 0 0 auto; padding: 7px 14px; cursor: pointer; white-space: nowrap;
  font: inherit; font-size: 0.78em; letter-spacing: 0.2px; text-transform: none;
  color: var(--text-muted); background: none;
  border: none; border-radius: 6px;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.settings-tab:hover:not(.settings-tab-active), .pj-tab:hover:not(.active) {
  color: var(--text-secondary);
  background: color-mix(in srgb, var(--text) 6%, transparent);
}
/* The wash carries the accent; the label stays --text. Accent AS the label
   measured 3.69:1 on the default light theme at this size — the same trap
   already recorded for --warning/--danger in CLAUDE.md. --text is the one
   token guaranteed to clear contrast on every theme, and weight + wash
   already say "selected" without leaning on colour. */
.settings-tab-active, .pj-tab.active {
  color: var(--text); font-weight: 600;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.settings-tab:focus-visible, .pj-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Context prefix ("Settings │", "Admin │"). The settings row sits under a
   primary nav with nothing selected, so without this the pills float with no
   parent. A static label, not a breadcrumb: there is no Settings landing page
   to link back to, so a chevron would promise a destination that isn't there.
   Same 14px inline padding as the pills so it shares their left edge; the
   hairline rule closes the label off from the segmented control. Sticky so
   it stays put on phones while the pills scroll under it. aria-hidden — the
   tablist's aria-label already names the section for readers. */
.settings-tabs-ctx {
  flex: 0 0 auto; position: sticky; left: 0; z-index: 1;
  padding: 7px 12px 7px 14px; margin-right: 2px;
  font-size: 0.78em; font-weight: 500; letter-spacing: 0.2px; white-space: nowrap;
  color: var(--text-dim); background: var(--bg);
  border-right: 1px solid var(--border);
}
/* Counts stay quiet inside the pill and never take the accent — the wash
   already says which pill is active, so a second accent cue is noise. */
.pj-tab-n { margin-left: 5px; font-size: 0.85em; letter-spacing: 0; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.pj-tab-n:empty { display: none; }
.pj-tab.active .pj-tab-n { color: inherit; opacity: 0.7; }
/* Desktop has the room, so let the pills breathe — 2px reads as a single
   segmented control rather than a row of separate destinations. Phone keeps
   the tight gap so more of the scrolling row stays on screen. */
@media (min-width: 701px) {
  .settings-tabs, .pj-tabs { gap: 7px; }
}
/* Phone: the row really does scroll, so fade the cut edge to say "more" rather
   than "broken", and give every pill a thumb-sized target. */
@media (max-width: 700px) {
  .settings-tabs, .pj-tabs {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
            mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
  }
}

/* ─── SECTION HEADERS ─────────────────────────────────────── */
.section-label {
  color: var(--accent); font-size: 0.75em; letter-spacing: 3px;
  margin-bottom: 10px; font-weight: 500; text-transform: uppercase;
}
.section-label-row {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 10px;
}
/* Settings card headers are the card's title, not an eyebrow: title case at
   heading size so the card reads as the unit. Modal titles and in-page
   eyebrows keep the tracked-caps .section-label above. .settings-card-head
   is the header row variant with a control (e.g. "+ New link") on the right. */
.settings-card > .section-label,
.settings-section > .section-label,
.settings-panel > .section-label,
.settings-card-head .section-label {
  font-size: 19px; font-weight: 600; line-height: 1.25; letter-spacing: 0.02em;
  text-transform: none; color: var(--text-secondary); margin-bottom: 14px;
}
.settings-card-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-bottom: 8px;
}
.settings-card-head .section-label { margin-bottom: 0; }
.settings-card-head .btn { padding: 8px 14px; font-size: 0.85em; letter-spacing: 0; white-space: nowrap; }

/* ─── INPUTS ──────────────────────────────────────────────── */
.input {
  width: 100%; background: var(--bg-input); border: 1px solid var(--border);
  color: var(--text); padding: 10px 14px; font-size: 0.95em; outline: none;
  transition: border-color 0.2s;
}
.input:focus { border-color: var(--accent); }
.input::placeholder { color: var(--text-dim); }
.input-success:focus { border-color: var(--success); }

/* Password input with show/hide eye toggle. The wrap is relative so the eye
   button can absolutely-position over the input's right edge; the input gets
   extra right padding so typed characters don't slide under the icon. */
.password-wrap { position: relative; }
.password-wrap > .input { padding-right: 38px; }
.password-eye {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  background: none; border: none; color: var(--text-muted);
  cursor: pointer; padding: 4px 6px; line-height: 0;
}
.password-eye:hover { color: var(--text); }
.password-eye:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.password-eye svg { width: 18px; height: 18px; display: block; }

.input-row { display: flex; gap: 8px; margin-top: 16px; }
.input-row .input { flex: 1; }

/* ─── BUTTONS ─────────────────────────────────────────────── */
.btn {
  background: var(--accent); color: var(--bg); border: none;
  padding: 12px 24px; font-weight: 700; cursor: pointer;
  letter-spacing: 1px; font-size: 0.95em; transition: all 0.2s;
}
.btn:hover { background: var(--accent-hover); transform: translateY(-1px); }
.btn-full { width: 100%; }
.btn-outline {
  background: transparent; border: 1px solid var(--border);
  color: var(--text-muted); padding: 10px 16px; font-size: 0.85em;
}
.btn-outline:hover { border-color: var(--text-muted); color: var(--text); transform: none; }
.btn-google {
  background: transparent; border: 1px solid var(--google-blue);
  color: var(--google-blue); display: flex; align-items: center;
  justify-content: center; gap: 10px;
}
.btn-google:hover { background: rgba(66, 133, 244, 0.07); transform: none; }
.btn-small {
  padding: 4px 8px; font-size: 0.75em; letter-spacing: 0;
  border: 1px solid var(--border);
}
/* Danger buttons: red text on a transparent fill (the base .btn's accent
   background made red-on-accent unreadable), with a clear red-fill hover. */
.btn-danger {
  background: transparent; color: var(--danger);
  border: 1px solid var(--danger);
}
.btn-danger:hover { background: var(--danger); color: #fff; border-color: var(--danger); transform: none; }
.btn-success { color: var(--success); }

/* ─── TASK LIST ───────────────────────────────────────────── */
.task-item { margin-bottom: 10px; }
.task-row {
  display: flex; align-items: center; gap: 10px;
}
.task-check {
  color: var(--text-dim); cursor: pointer; font-size: 1.1em;
  transition: color 0.2s; background: none; border: none; padding: 0;
}
.task-check:hover { color: var(--success); }
.task-title { color: var(--text); }
.task-meta { color: var(--text-muted); font-size: 0.8em; margin-left: 26px; }
.task-initiative { color: var(--text-muted); font-size: 0.8em; }
.task-schedule-btn {
  color: var(--text-dim); cursor: pointer; font-size: 0.75em;
  margin-left: auto; background: none; border: none; transition: color 0.2s;
}
.task-schedule-btn:hover { color: var(--accent); }

.badge { font-size: 0.85em; opacity: 0.7; }
/* Effort indicator — vertical pipes scale 1–5 with task duration. Mono +
 * letter-spacing keeps each pipe visually distinct at small sizes. */
.badge-size {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: 1.5px;
  font-weight: 600;
  opacity: 0.85;
}
/* Energy indicator — small filled square. Pure CSS so it renders identically
 * on every platform (the prior emoji approach was unreliable). */
.energy-sq {
  display: inline-block;
  width: 9px;
  height: 9px;
  vertical-align: middle;
  border-radius: 1px;
}
/* Energy ramp lives on its own tokens (--energy-low / --energy-med /
 * --energy-high) so the meaning stays clear: moss / warm neutral /
 * dusty red. Pointing these at semantic tokens (success/warning/danger)
 * would conflate "task energy" with "ambient alert level". */
.energy-sq-low    { background: var(--energy-low); }
.energy-sq-medium { background: var(--energy-med); }
.energy-sq-high   { background: var(--energy-high); }
.badge-energy-low    { color: var(--energy-low); }
.badge-energy-medium { color: var(--energy-med); }
.badge-energy-high   { color: var(--energy-high); }

/* ─── CALENDAR TIMELINE ───────────────────────────────────── */
.timeline { border-left: 2px solid var(--border-light); padding-left: 16px; margin-bottom: 24px; }
.timeline-item { margin-bottom: 8px; display: flex; gap: 12px; align-items: baseline; }
.timeline-time { color: var(--text-muted); font-size: 0.85em; min-width: 42px; }
.timeline-title { color: var(--text-secondary); }
.timeline-title-task { color: var(--accent); }
.timeline-duration { color: var(--text-dim); font-size: 0.8em; }
.timeline-free { color: var(--border); }

/* ─── DONE LOG ────────────────────────────────────────────── */
.done-item { display: flex; align-items: baseline; gap: 10px; margin-bottom: 6px; }
.done-check { color: var(--success); }
.done-title { color: var(--text-secondary); }
.done-time { color: var(--text-dim); font-size: 0.85em; min-width: 42px; }

/* ─── STATUS BAR ──────────────────────────────────────────── */
.status-bar {
  border-top: 1px solid var(--border-light); padding-top: 12px;
  display: flex; gap: 16px; color: var(--text-muted); font-size: 0.8em;
  margin-top: 20px;
}

/* ─── INBOX ITEM ──────────────────────────────────────────── */
.inbox-item {
  border: 1px solid var(--border-light); padding: 14px; margin-bottom: 8px;
  display: flex; justify-content: space-between; align-items: center;
}
.inbox-text { color: var(--text); margin-bottom: 4px; }
.inbox-time { color: var(--text-dim); font-size: 0.8em; }
.inbox-actions { display: flex; gap: 8px; }

/* ─── SCHEDULE MODAL ──────────────────────────────────────── */
.schedule-modal {
  margin-left: 26px; margin-top: 8px; border: 1px solid var(--border);
  padding: 12px; background: var(--bg-elevated);
}
.picker-row { display: flex; gap: 4px; margin-bottom: 10px; }
.picker-btn {
  padding: 4px 10px; border: 1px solid var(--border); color: var(--text-muted);
  cursor: pointer; font-size: 0.8em; background: none; transition: all 0.15s;
}
.picker-btn.active { border-color: var(--accent); color: var(--accent); }
.picker-btn-energy-low.active    { border-color: var(--energy-low);  color: var(--energy-low);  }
.picker-btn-energy-medium.active { border-color: var(--energy-med);  color: var(--energy-med);  }
.picker-btn-energy-high.active   { border-color: var(--energy-high); color: var(--energy-high); }
.schedule-actions { display: flex; gap: 8px; }
.schedule-actions .btn { flex: 1; padding: 8px 0; font-size: 0.8em; }

/* ─── CONTEXT GROUP ───────────────────────────────────────── */
.context-group { margin-bottom: 12px; }
.context-label { color: var(--text-muted); font-size: 0.8em; margin-bottom: 6px; }
.context-tasks { margin-left: 12px; }

/* ─── TODAY LAYOUT ────────────────────────────────────────────
   Today fills the shared app frame as a two-column planner: priorities +
   next actions in the wider main column, the time-anchored calendar and the
   day's completed log in a narrower rail. Collapses to a single stacked
   column once the frame gets tight, so it reads the same on a phone. The
   status bar + capture row sit full-width below the grid as the page
   footer. */
/* The date is the page's H1 — a proper major heading, not a muted caption
   jammed under the tabs. Matches the .pj-title scale + per-theme font. */
.today-date {
  margin: 24px 0 28px;
  font-size: 30px; line-height: 1.1; font-weight: 600; letter-spacing: -0.01em;
  color: var(--text-primary);
}
[data-theme="light"] .today-date { font-family: var(--serif-display); }
[data-theme="modernist"] .today-date { font-family: var(--sans-display); font-weight: 700; }
.today-section { margin-bottom: 24px; }
.today-section:last-child { margin-bottom: 0; }

/* ── Today hero band ── */
.today-hero {
  margin: 20px 0 24px; padding-bottom: 18px;
  border-bottom: 1px solid var(--border-light);
}
/* Date on the left, inbox count on the right. The link is styled as a peer of
   the date rather than a call to action — processing the inbox isn't what this
   page is for, it just shouldn't be invisible from here. */
.today-hero-top {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
}
/* Matches .today-hero-loc below exactly — same size, weight, tracking and
   colour — so the date and location read as one line split by the accent pipe,
   not as a label and a value. Change the two together. */
.today-hero-date {
  font-size: 0.72em; letter-spacing: 1px; text-transform: uppercase;
  color: var(--text-secondary);
}
/* Today's work location, read-only. margin-right:auto keeps it beside the date
   instead of being centred by the row's space-between. */
.today-hero-loc {
  margin-right: auto;
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 0.72em; letter-spacing: 1px; text-transform: uppercase;
  color: var(--text-secondary);
  border-left: 2px solid var(--accent); padding-left: 8px;
}
.today-hero-loc-pin { display: flex; }
.today-hero-loc-pin svg { width: 12px; height: 12px; }
.today-hero-loc-tz { color: var(--text-secondary); letter-spacing: 0; text-transform: none; font-variant-numeric: tabular-nums; }

.today-hero-greet {
  font-size: 26px; line-height: 1.15; font-weight: 600; letter-spacing: -0.01em;
  color: var(--text-primary); margin: 6px 0 10px;
}
[data-theme="light"] .today-hero-greet { font-family: var(--serif-display); }
[data-theme="modernist"] .today-hero-greet { font-family: var(--sans-display); font-weight: 700; }
.today-hero-quote { color: var(--text-secondary); font-size: 0.95em; font-style: italic; max-width: 64ch; }
.today-hero-author { color: var(--text-muted); font-style: normal; font-size: 0.85em; }

/* Phone: the hero's top row has no room for one line — the location wraps
   mid-word beside the date and the switcher.
   `display: contents` on .today-hero-top is what makes the regroup possible:
   CSS can't move a node to a different parent, but it can dissolve the parent
   so the date, location and switcher become the hero's own flex children — and
   then the ::after spacer forces the wrap so exactly two of them stay on line
   one. */
@media (max-width: 700px) {
  .today-hero {
    display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
    margin: 14px 0 18px; padding-bottom: 14px;
  }
  .today-hero-top { display: contents; }
  .today-hero::after { content: ''; flex-basis: 100%; height: 0; order: 3; }
  .today-hero-date  { order: 1; }
  .today-hero-loc   { order: 2; margin-right: 0; }
  .today-hero-greet { order: 4; flex: 1 1 auto; min-width: 0; margin: 0; font-size: 20px; }
  .today-hero-top .home-modes { order: 5; }
  /* The quote is a desktop pleasure. On a phone it costs two lines directly
     above the timeline, which is what the page is actually for. */
  .today-hero-quote { display: none; }
}
.today-empty { color: var(--text-dim); font-size: 0.85em; line-height: 1.6; }
.link-btn { background: none; border: none; color: var(--accent); cursor: pointer; font: inherit; padding: 0; }
.link-btn:hover { text-decoration: underline; }

/* ── Today storyline ──────────────────────────────────────────
   One axis for the whole day. Markers sit at their real clock position, so
   the distance between two of them is the actual gap — an empty afternoon
   reads as empty space rather than as a shorter list. Cards alternate above
   and below the axis; the koi NOW line splits spent time from booked time.

   Desktop and mobile share one DOM. Above 860px the markers are absolutely
   positioned off the inline `left:%`; below it, `left` is overridden and the
   <ol> becomes a plain vertical stack — which is why app.js splices the NOW
   element into the list in chronological order instead of appending it. */
.td-line { margin: 0 0 30px; --td-dot-max: 80px; }  /* mirrors TD_DOT_MAX in app.js */
/* Was label-left / meta-right; with the label gone the meta stands alone and
   absorbs the 10px the label's own margin used to contribute. */
.td-line-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; margin-bottom: 16px;
}
.td-line-meta {
  font-size: 0.75em; color: var(--text-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Day stepper. Sized to be reachable rather than prominent — the rail is the
   subject, this just says which day you're looking at. 28px keeps the tap
   target usable without the chevrons reading as primary actions. */
.td-day-nav { display: flex; align-items: center; gap: 2px; }
.td-day-name {
  font-size: 0.72em; letter-spacing: 2px; text-transform: uppercase;
  color: var(--text-secondary); min-width: 8.5ch; text-align: center;
}
.td-day-arrow {
  width: 28px; height: 28px; flex: none;
  display: grid; place-items: center;
  background: none; border: 1px solid transparent; border-radius: 6px;
  color: var(--text-muted); font: inherit; font-size: 1.1em; line-height: 1;
  cursor: pointer;
}
.td-day-arrow:hover:not(:disabled) { color: var(--text); border-color: var(--border); }
.td-day-arrow:disabled { opacity: 0.28; cursor: default; }
/* Above 860px the stepper lives on the axis instead — see .td-rail-arrow. */
.td-day-arrow { display: none; }

/* Chevrons on the line itself, sitting in the gutters the track is inset by
   and centred on the axis, with a hairline running out to meet the rail so
   the timeline reads as continuing into the day either side. Same --td-axis
   the axis and ticks use, so they stay aligned when the geometry is
   remeasured. */
.td-rail-arrow {
  position: absolute; top: var(--td-axis, 160px); transform: translateY(-50%);
  transition: opacity 140ms ease;
  width: 34px; height: 34px; display: grid; place-items: center;
  background: var(--bg); border: 1px solid var(--border); border-radius: 50%;
  color: var(--text-muted); font: inherit; font-size: 1.05em; line-height: 1;
  cursor: pointer; z-index: 1;
}
.td-rail-arrow--prev { left: 0; }
.td-rail-arrow--next { right: 0; }
/* Gutter is 68px, button is 34px — the connector covers the remaining 34px. */
.td-rail-arrow::after {
  content: ''; position: absolute; top: 50%; width: 34px; height: 1px;
  background: var(--border);
}
.td-rail-arrow--prev::after { left: 100%; }
.td-rail-arrow--next::after { right: 100%; }
.td-rail-arrow:hover:not(:disabled) { color: var(--text); border-color: var(--text-muted); }
.td-rail-arrow:disabled { opacity: 0.3; cursor: default; }
.td-rail.is-empty .td-rail-arrow { top: var(--td-axis, 30px); }
.td-line-empty { margin-top: 10px; }

/* Day transition: yesterday, today and tomorrow ride one strip — a ticker —
   so a drag shows the neighbouring day really there under the pointer, and a
   chevron just animates the same pull. Built by todayBuildStrip on gesture or
   step, torn down by the in-place settle.

   The chevrons belong to a resting rail. While the paper is in hand they get
   out of the way — and every pane has its own pair, which would otherwise
   slide through the middle of the gesture. */
.td-slide-viewport.is-dragging .td-rail-arrow { opacity: 0; pointer-events: none; }

/* On the strip, the axis runs edge to edge. At rest the 68px track gutters
   are bridged by the chevrons' connector stubs, but those are pseudo-elements
   of the arrows and vanish with them in drag mode — leaving a bite of missing
   line at every pane seam. These stubs belong to the axis itself, so the line
   is continuous through the gutters whether the arrows are shown or not.
   Pseudo-elements rather than widening the axis element, so the spent-time
   fill's percentage widths keep their meaning. Desktop only — the stacked
   layout's axis is vertical. */
@media (min-width: 861px) {
  .td-slide-strip .td-axis::before,
  .td-slide-strip .td-axis::after {
    content: ''; position: absolute; top: 0; height: 1px; width: 68px;
    background: var(--border);
  }
  .td-slide-strip .td-axis::before { right: 100%; }
  .td-slide-strip .td-axis::after { left: 100%; }
}
/* The viewport takes over the line's bottom margin for the trip: its height
   is pinned to the pane's content box, so a margin left on the pane would be
   clipped and the content below would hop 30px at mount and back at unwrap. */
.td-slide-viewport { overflow: hidden; margin: 0 0 30px; }
.td-slide-strip { display: flex; align-items: flex-start; will-change: transform; }
.td-slide-strip > .td-line { flex: 0 0 100%; min-width: 100%; margin-bottom: 0; }

/* Drag states. touch-action lets the browser keep vertical panning while the
   horizontal axis is ours, so a swipe down the stacked layout still scrolls.
   It isn't inherited, so the markers — which now also accept a pull — need it
   explicitly or a touch starting on a dot hands the horizontal to the
   browser and we get a pointercancel instead of a drag. */
.td-rail { cursor: grab; touch-action: pan-y; }
.td-mk-card, .td-mk-dot { touch-action: pan-y; }
.td-slide-viewport.is-dragging { cursor: grabbing; user-select: none; }
.td-slide-viewport.is-dragging .td-rail { cursor: grabbing; }
/* Reduced motion: the drag itself stays — it's direct manipulation, not
   decoration — and todayAnimateStrip skips its transitions in JS. */

/* Geometry is measured, not fixed: a dot's diameter is its duration on the
   rail's own pixels-per-minute scale, so how much vertical room the rail needs
   depends on the day and the window. todayLineLayout() writes --td-lane (axis
   to card), --td-axis (axis offset) and --td-rail-h; the values here are the
   pre-measure defaults and the mobile fallback. Working outward from the axis:
   half the largest dot plus clearance, then the card, then an 18px NOW-pill
   band above and a 24px ruler band below. */
.td-rail { position: relative; height: var(--td-rail-h, 324px); }
/* An empty day follows the same measured geometry as a populated one — the
   layout keeps a session-wide high-water (see _todayGeoMax), and the axis has
   to sit at that shared level on every day or the line jumps when stepping
   onto a clear day. The compact values are only the pre-measure fallback. */
.td-rail.is-empty { height: var(--td-rail-h, 84px); }
/* The scale lives inside a track inset by half a card, so a marker at 0% or
   100% still has room for its label instead of hanging off the page gutter
   and getting clipped. Everything positioned by percentage — axis, ticks,
   markers, NOW — sits in here, so they all stay on the same scale. */
.td-track { position: absolute; inset: 0 68px; }
.td-axis {
  position: absolute; left: 0; right: 0; top: var(--td-axis, 160px); height: 1px;
  background: var(--border);
}
.td-rail.is-empty .td-axis { top: var(--td-axis, 30px); }
.td-axis-spent {
  position: absolute; left: 0; top: 0; height: 1px;
  background: linear-gradient(90deg, var(--border) 0%, var(--accent) 100%);
  opacity: 0.55;
}
/* One element per hour: a short mark straddling the axis, and its label
   parked on the ruler band so the two never fight for the same pixels. */
.td-tick { position: absolute; top: var(--td-axis, 160px); bottom: 0; width: 1px; }
.td-tick::before {
  content: ''; position: absolute; top: -3px; left: 0;
  width: 1px; height: 7px; background: var(--border);
}
.td-tick::after {
  content: attr(data-h); position: absolute; bottom: 0; left: 50%;
  transform: translateX(-50%);
  font-size: 0.66em; letter-spacing: 0.06em; color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.td-rail.is-empty .td-tick { top: var(--td-axis, 30px); }

.td-marks { list-style: none; margin: 0; padding: 0; position: absolute; inset: 0; }
/* Markers are 132px boxes centred on their dot, so on a busy rail they overlap
   each other heavily. pointer-events:none on the box and auto on the two
   things you can actually press stops a neighbour's empty margin swallowing
   the click that was meant for this dot. */
.td-mk {
  position: absolute; transform: translateX(-50%); pointer-events: none;
  display: flex; flex-direction: column; align-items: center; width: 132px;
  /* Rendered diameter. Desktop draws --dot as given; the mobile block caps it,
     so everything downstream sizes off this rather than --dot directly. */
  --dot-r: var(--dot, 11px);
}
.td-mk-card, .td-mk-dot { pointer-events: auto; }
/* Cards hug the axis and grow outward, so the ragged edge is the outer one
   and the timestamps stay on a common line next to the rail. The half-dot
   pull-back keeps the dot centred on the axis at any diameter. */
.td-mk--up   { bottom: calc(var(--td-rail-h, 324px) - var(--td-axis, 160px)); margin-bottom: calc(var(--dot-r) / -2); }
.td-mk--down { top: var(--td-axis, 160px); margin-top: calc(var(--dot-r) / -2); flex-direction: column-reverse; }

/* Diameter is the booked duration (--dot, set per marker in app.js), fill is
   the colour category (--cat) where the task has one. Ring and tick scale with
   the dot but the ring stays a hairline at every size — scaled to a 180px
   circle it stopped reading as an outline and started reading as a band.
   Fills are translucent and there is deliberately no separating outline: now
   that a circle is its event's interval, two circles crossing means those two
   events really do clash, and the blended lens is how you read the clash. An
   opaque fill would say "in front of" instead. The hairline axis showing
   through is the point too — the circle sits on the timeline, not over it. */
.td-mk-dot {
  width: var(--dot-r); height: var(--dot-r);
  border-radius: 50%; flex: none;
  /* The ink is everything left of the NOW line: a hard-stop gradient whose
     edge (--fill, set from the same geometry as the line) the border-radius
     clips into a circular segment. The line draws the fill as it sweeps. */
  background: linear-gradient(90deg, var(--dot-wash, transparent) var(--fill, 0%), transparent var(--fill, 0%));
  border: 1px solid var(--text-dim);
  display: grid; place-items: center;
  font-size: clamp(7px, calc(var(--dot-r) * 0.4), 15px);
  font-weight: 700; line-height: 1; color: var(--text);
}
/* The stem gives back exactly the half-dot the marker just took, so every
   card sits the same distance from the axis whatever its dot's size — the
   timestamps stay on one line instead of stepping with the durations. */
.td-mk-stem {
  width: 1px; flex: none; background: var(--border);
  height: calc(var(--td-lane, 80px) - var(--dot-r) / 2);
}
/* --shift is set by todayLineLayout(): how far this label has to slide right
   to clear its neighbour. Only the card moves — the dot stays on its hour.
   It's a <button> (it opens the note bubble), hence the UA reset. */
.td-mk-card {
  display: flex; flex-direction: column; gap: 2px; align-items: center;
  text-align: center; max-width: 100%; padding: 0 0 6px;
  transform: translateX(var(--shift, 0px));
  background: none; border: 0; font: inherit; color: inherit; cursor: pointer;
}
.td-mk-card:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px;
}
/* One hover state for the whole marker, whichever half the pointer is over.
   Deliberately colour only, no scale: a dot's diameter is its duration on the
   axis, so growing it on hover would have it claim time it doesn't occupy and
   nudge it into overlapping a neighbour it doesn't clash with. */
.td-mk-dot { cursor: pointer; position: relative; }
/* A 5m dot is 9px across — findable, but not something you want to have to
   aim at. This pads the hit area out to 26px without touching the drawn size
   or the layout. Kept modest so two dots ten minutes apart don't fight over
   the pointer; the card is the generous target. */
.td-mk-dot::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: max(26px, 100%); height: max(26px, 100%);
  transform: translate(-50%, -50%);
}
.td-mk:hover .td-mk-title,
.td-mk:focus-within .td-mk-title { color: var(--text); }
.td-mk:hover .td-mk-dot,
.td-mk:focus-within .td-mk-dot { border-color: var(--text); }
.td-mk--down .td-mk-card { padding-bottom: 0; padding-top: 6px; }
.td-mk-time {
  font-size: 0.68em; letter-spacing: 0.08em; color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
/* The duration in words next to the start time — the dot's diameter carries
   it at a glance, this is what makes it checkable (and readable aloud). */
.td-mk-dur { color: var(--text-dim); letter-spacing: 0; }
.td-mk-title {
  font-size: 0.78em; line-height: 1.3; color: var(--text-secondary);
  max-width: 100%;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Dense rail: on a day with more blocks than the track can label at full
   width, todayLineLayout narrows every card to the widest that still seats the
   busier lane and sets .is-dense. One ellipsised line each keeps the run
   readable — the full title stays in the dot's tooltip and the note bubble.
   Narrowing the marker doesn't move it: it's centred on its hour by
   translateX(-50%) of its own width, whatever that width is. */
.td-line.is-dense .td-mk { width: var(--td-card-w, 132px); }
.td-line.is-dense .td-mk-title { -webkit-line-clamp: 1; }
/* --accent-hover, not --accent: at this size the base koi lands at 3.9:1 on
   the dark page, the hover tone clears 4.5:1 — and it's the darker of the
   pair in the light themes, so the swap holds both ways. */
.td-mk-tag {
  font-size: 0.62em; font-weight: 700; letter-spacing: 0.1em;
  color: var(--accent-hover); font-variant-numeric: tabular-nums;
}

/* Two readings on one dot: ink says how much of it the NOW line has swept
   (a half-done meeting is a half-inked circle), hue says which category it
   belongs to. Every state defines only its wash colour — the gradient above
   decides how much of it shows, so the line can start inking a dot the
   minute tick reaches without waiting for a re-render to flip its class.
   The ring always carries the colour at full strength so a circle stays
   legible where it crosses another. */
.td-mk.is-past .td-mk-dot {
  --dot-wash: color-mix(in srgb, var(--cat, var(--text-dim)) 44%, transparent);
  border-color: var(--cat, var(--text-dim));
}
.td-mk.is-past .td-mk-title { color: var(--text-muted); }
.td-mk.is-ahead .td-mk-dot {
  --dot-wash: color-mix(in srgb, var(--cat, var(--text-muted)) 44%, transparent);
  border-color: var(--cat, var(--text-muted));
}
.td-mk.is-ahead.is-task .td-mk-dot { border-color: var(--cat, var(--accent)); }
.td-mk.is-ahead .td-mk-title { color: var(--text-secondary); }
/* NEXT is marked by being the one upcoming dot that's filled, plus its tag.
   No halo — a second ring around an already-large dot is loud, and the rail
   is meant to be calm. */
.td-mk.is-next .td-mk-dot {
  --dot-wash: color-mix(in srgb, var(--cat, var(--accent)) 55%, transparent);
  border-color: var(--cat, var(--accent));
}
.td-mk.is-next .td-mk-title { color: var(--text); }

/* ── Rail tooltip ──
   Two stages in one floating element, caret pointing at the disc it belongs
   to. Stage 1 is the event restated — the whole tip is one soft button.
   Clicking it becomes stage 2 (.is-note): the tight note box. Kept off the
   .kb-chip-popover shell deliberately: this is a pointer, not a menu. */
.td-tip-backdrop { position: fixed; inset: 0; z-index: 400; }
.td-tip {
  position: fixed; z-index: 401;
  max-width: min(300px, calc(100vw - 16px));
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: 9px; padding: 8px 12px 9px;
  box-shadow: inset 0 1px 0 var(--card-top), 0 6px 20px rgba(0, 0, 0, 0.3);
  cursor: pointer;
  animation: td-tip-in 130ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
@keyframes td-tip-in { from { opacity: 0; transform: translateY(3px); } }
/* The caret: a rotated square borrowing the tip's surface and border, sitting
   on whichever edge faces the disc. --caret-x keeps it on the disc's centre
   even when the box has shoved sideways to stay on the page. */
.td-tip::after {
  content: ''; position: absolute; width: 9px; height: 9px;
  left: var(--caret-x, 50%); bottom: -5.5px;
  background: var(--bg-elevated);
  border-right: 1px solid var(--border); border-bottom: 1px solid var(--border);
  transform: translateX(-50%) rotate(45deg);
}
.td-tip.is-below::after {
  bottom: auto; top: -5.5px;
  border: none; border-left: 1px solid var(--border); border-top: 1px solid var(--border);
}
.td-tip-when {
  font-size: 0.66em; letter-spacing: 0.08em; color: var(--text-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.td-tip-title { font-size: 0.88em; color: var(--text); line-height: 1.3; margin-top: 2px; }
.td-tip-hint { font-size: 0.62em; letter-spacing: 0.06em; color: var(--text-dim); margin-top: 5px; }

/* Stage 2 — the note box. Same element grown up: default cursor back, a
   240px column, everything tight. */
.td-tip.is-note { cursor: default; width: 264px; animation: td-tip-grow 140ms cubic-bezier(0.2, 0.7, 0.3, 1); }
@keyframes td-tip-grow { from { transform: scale(0.97); } }
.td-tip-last {
  display: none;
  font-size: 0.72em; color: var(--text-muted); line-height: 1.4; margin-top: 6px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.td-tip-last.has-notes { display: block; }
.td-tip-last-n { color: var(--text-dim); }
.td-tip-input {
  display: block; width: 100%; margin-top: 7px; padding: 6px 8px;
  background: var(--bg-input); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  font: inherit; font-size: 0.8em; line-height: 1.45; resize: none;
}
.td-tip-input:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
.td-tip-input::placeholder { color: var(--text-dim); }
.td-tip-actions {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: 8px;
}
.td-tip-open { font-size: 0.78em; white-space: nowrap; }
.td-tip-empty { font-size: 0.78em; color: var(--text-muted); line-height: 1.5; margin-top: 5px; max-width: 230px; }
@media (prefers-reduced-motion: reduce) {
  .td-tip, .td-tip.is-note { animation: none; }
}

/* NOW: the one thing on the rail that moves. */
.td-now { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--accent); list-style: none; }
.td-now-pill {
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  font-size: 0.56em; letter-spacing: 0.14em; font-weight: 700;
  color: #fff; background: var(--accent);
  padding: 2px 5px; border-radius: 0 0 3px 3px; white-space: nowrap;
}

/* Cards land in clock order rather than all at once — reads as the day
   being laid down left to right. A page-load flourish only: td-no-stagger
   turns it off for rails that arrive by sliding, because CSS animations
   restart whenever an element is re-inserted — and the slide re-inserts its
   panes twice (onto the strip, then unwrapping off it). Without the guard,
   every dot went blank on landing and cascaded back in. The class rides the
   line for life; the next full render builds a fresh line without it. */
.td-mk { animation: td-mk-in 0.34s cubic-bezier(0.2, 0.7, 0.3, 1) both; animation-delay: calc(var(--i, 0) * 45ms); }
.td-line.td-no-stagger .td-mk { animation: none; }
@keyframes td-mk-in { from { opacity: 0; transform: translateX(-50%) translateY(6px); } }

@media (max-width: 860px) {
  /* Same markup, split around a centre rail. The axis runs down the middle,
     the start time and duration sit right-aligned in the left column and the
     title takes the right, so neither half is dead space. `left` has to beat
     the inline percentage todayLineLayout() writes, hence the !important. */
  .td-line-head { flex-direction: column; align-items: flex-start; gap: 3px; }
  /* Chevrons are the only thing on this page you aim at with a thumb. */
  /* The stepper is the one thing on this page you aim at with a thumb, so it
     gets real targets: 48px chevrons carrying a visible edge rather than one
     that only appears on hover (there is no hover here), spaced 8px so two
     targets don't read as one, and a label big enough to take in at a glance. */
  .td-day-arrow {
    display: grid; width: 48px; height: 48px;
    border-color: var(--border); font-size: 1.3em;
  }
  /* The stepper takes the whole row: chevrons pinned to either end, day name
     centred between them. The ends of a phone are easier to reach than its
     middle, and centring the label stops the row shuffling as the text changes
     length between TODAY and a date. Held to the content gutter rather than
     bled to the screen edge, so the targets clear the edge-swipe zone. */
  .td-day-nav {
    width: 100%; margin-left: 0;
    justify-content: space-between; gap: 8px;
  }
  .td-day-name {
    flex: 1 1 auto; text-align: center;
    font-size: 0.86em; letter-spacing: 2.5px;
    min-width: 10ch; color: var(--text);
  }
  .td-rail-arrow { display: none; }
  /* An 80px dot would take a fifth of the screen width as gutter, so the phone
     gets its own ceiling. Same curve, shorter top end — the spread still
     reads, it just stops sooner. */
  .td-line { --td-dot-max: 40px; }
  .td-mk { --dot-r: min(var(--dot, 11px), 40px); }
  .td-rail, .td-rail.is-empty { height: auto; }
  .td-track { position: static; inset: auto; }
  .td-axis {
    top: 14px; bottom: 14px; left: 50%; right: auto;
    width: 1px; height: auto; transform: translateX(-50%);
  }
  .td-rail.is-empty .td-axis { display: none; }
  .td-axis-spent, .td-tick { display: none; }
  .td-marks { position: static; display: flex; flex-direction: column; }
  .td-mk {
    position: relative !important; left: auto !important;
    /* --up/--down set top/bottom for the two-sided desktop rail. Those were
       inert while this was position:static; relative makes them live again and
       they'd shunt every row by the rail height, so they're zeroed here. */
    top: auto !important; bottom: auto !important;
    /* margin without !important: todayLineLayoutStacked writes margin-top per
       row to draw the real gap between two events, and an !important here
       would outrank that inline value. Ordering still beats --up/--down. */
    transform: none; width: auto; margin: 0;
    display: block; padding: 4px 0;
  }
  /* Lifted out of the flow and pinned to the axis. Padding each dot into a
     fixed column would work too, but centring on the line by transform keeps
     a 9px and a 40px disc on the same rail without reserving 40px of gutter
     for every row. top re-creates the old flex alignment: the row's 4px pad
     plus half a text line, less half the diameter. */
  .td-mk-dot {
    position: absolute; left: 50%; z-index: 1; margin: 0;
    top: calc(4px + 11px - var(--dot-r) / 2);
    transform: translateX(-50%);
  }
  .td-mk-stem { display: none; }
  /* Two equal columns split by a gutter wide enough for the largest disc, so
     the gap's midpoint lands exactly on the axis. min-height, not padding: the
     row keeps its tight look while the button clears the 44px touch target. */
  .td-mk-card {
    /* width, not just display:grid — a <button> sizes to fit-content, so
       without this the 1fr/1fr split is taken on the text width and the gutter
       stops lining up with the axis. */
    display: grid; width: 100%; min-width: 0; transform: none; min-height: 44px;
    grid-template-columns: 1fr 1fr;
    column-gap: calc(var(--td-dot-max, 40px) + 24px);
    row-gap: 2px; align-content: flex-start;
    text-align: left; padding: 0 !important;
  }
  .td-mk-time  { grid-column: 1; grid-row: 1; justify-self: end; text-align: right; }
  .td-mk-title { grid-column: 2; grid-row: 1; -webkit-line-clamp: 3; }
  .td-mk-tag   { grid-column: 2; grid-row: 2; width: auto; order: 0; }
  .td-now {
    position: static; width: auto; height: auto; background: none;
    margin: 4px 0; display: flex; align-items: center; gap: 8px;
  }
  .td-now::after { content: ''; flex: 1; height: 1px; background: var(--accent); opacity: 0.5; }
  .td-now-pill { position: static; transform: none; border-radius: 3px; }
  @keyframes td-mk-in { from { opacity: 0; transform: translateY(6px); } }
}

@media (prefers-reduced-motion: reduce) {
  .td-mk { animation: none; }
}

/* ─── INBOX LAYOUT ────────────────────────────────────────────
   Capture bar stays a focused single field; the unprocessed items flow into
   a responsive card grid that fills the frame and stacks as it narrows. */
.inbox-capture { max-width: 720px; margin-bottom: 16px; }
.inbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: 8px;
  align-items: start;
}
.inbox-grid .inbox-item { margin-bottom: 0; }

/* ─── AUTH PAGES ──────────────────────────────────────────── */
.auth-container {
  min-height: 100vh; display: flex; align-items: center;
  justify-content: center;
  padding:
    calc(20px + env(safe-area-inset-top, 0px))
    max(20px, env(safe-area-inset-right, 20px))
    calc(20px + env(safe-area-inset-bottom, 0px))
    max(20px, env(safe-area-inset-left, 20px));
}
.auth-box { max-width: 400px; width: 100%; }
.auth-step { color: var(--text-muted); margin-bottom: 32px; font-size: 0.85em; }
.form-group { margin-bottom: 16px; }
.form-label {
  display: block; color: var(--text-muted); font-size: 0.75em;
  letter-spacing: 2px; margin-bottom: 6px;
}
/* Was a styled <span onclick>; now a real <button> so it's keyboard-focusable
   and announced as a button. These resets make a button render like plain
   underlined accent text. */
.auth-link {
  background: none; border: none; padding: 0; font: inherit;
  color: var(--accent); cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}
.auth-footer { text-align: center; margin-top: 20px; color: var(--text-muted); font-size: 0.85em; }

/* ─── SETTINGS ────────────────────────────────────────────── */
.settings-section {
  border: 1px solid var(--border-light); padding: 20px; margin-bottom: 12px;
}
.settings-row {
  display: flex; justify-content: space-between; align-items: center;
  margin-bottom: 12px;
}
.settings-row:last-child { margin-bottom: 0; }
.settings-label { color: var(--text-secondary); }
.settings-sublabel { color: var(--text-muted); font-size: 0.8em; }
.settings-value {
  color: var(--text-secondary); font-size: 0.9em; padding: 4px 10px;
  border: 1px solid var(--border-light); cursor: pointer;
}
.settings-value-select {
  color: var(--text-secondary); font-size: 0.9em; padding: 4px 10px;
  border: 1px solid var(--border-light); cursor: pointer;
  background: var(--bg-input); font-family: inherit;
}
.settings-value-select:focus { outline: none; border-color: var(--accent); }

/* Settings page — horizontal tabs + lifted contrast cards */
/* Same gap above and below the pill row — 10px from the primary strip's rule
   to the pills, 10px from the pills to the first card. */
.settings-shell { margin-top: 10px; }
/* Look and behaviour live in the SUBNAV block; only the page-specific
   spacing is set here. No border-bottom: the pills group themselves, and a
   rule this close under the primary strip just stacks two lines. */
.settings-tabs { margin-bottom: 10px; }
.settings-panel { display: none; }
.settings-panel-active { display: block; }
.settings-card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 20px;
  margin-bottom: 14px;
  box-shadow: var(--card-shadow, none);
}
.settings-card:last-child { margin-bottom: 0; }

/* Toggle switch */
.toggle {
  width: 44px; height: 24px; border-radius: 12px; cursor: pointer;
  position: relative; transition: background 0.2s; border: none;
}
.toggle.on { background: var(--accent); }
.toggle.off { background: var(--border); }
.toggle-knob {
  width: 18px; height: 18px; border-radius: 50%; background: var(--text);
  position: absolute; top: 3px; transition: left 0.2s;
}
.toggle.on .toggle-knob { left: 23px; }
.toggle.off .toggle-knob { left: 3px; }

/* ─── LANDING PAGE ────────────────────────────────────────── */
.landing {
  min-height: 100vh; display: flex; flex-direction: column; align-items: center;
  /* iOS standalone on the Dark theme draws the page under the status bar
     (viewport-fit=cover + black-translucent), so the design padding has to
     clear the inset or the top row sits behind the clock and stops taking
     taps. The light themes use the opaque bar and report a zero inset. */
  padding:
    calc(40px + env(safe-area-inset-top, 0px))
    max(20px, env(safe-area-inset-right, 20px))
    calc(40px + env(safe-area-inset-bottom, 0px))
    max(20px, env(safe-area-inset-left, 20px));
}
.landing-content { max-width: 640px; width: 100%; }
.landing-version { opacity: 0.4; font-size: 0.8em; letter-spacing: 4px; margin-bottom: 8px; }
.landing-topbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.landing-topbar .landing-version { margin-bottom: 0; }
.landing-signin { font-size: 0.8em; letter-spacing: 2px; text-decoration: none; }
.landing-signin:hover { text-decoration: underline; text-underline-offset: 3px; }
.landing-hero { font-size: clamp(2rem, 6vw, 3.5rem); font-weight: 700; line-height: 1.1; margin-bottom: 16px; }
.landing-subtitle { font-size: 1.15em; line-height: 1.7; color: var(--text-secondary); }
.landing-subtitle-dim { color: var(--text-muted); }

.terminal-demo {
  background: var(--bg-elevated); border: 1px solid var(--border);
  padding: 24px 28px; margin-bottom: 50px;
}
.terminal-dots { display: flex; gap: 6px; margin-bottom: 16px; }
.terminal-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.terminal-dot-red { background: var(--danger); }
.terminal-dot-yellow { background: var(--warning); }
.terminal-dot-green { background: var(--success); }
.terminal-cmd { margin-bottom: 12px; }
.terminal-prompt { color: var(--accent); }
.terminal-desc { color: var(--text-secondary); margin-left: 18px; margin-top: 2px; font-size: 0.9em; }
.terminal-stats { margin-top: 20px; }

.how-section { margin-bottom: 50px; }
.how-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; }
.how-card { border: 1px solid var(--border-light); padding: 16px; }
.how-num { color: var(--accent); font-size: 0.75em; margin-bottom: 6px; }
.how-title { color: var(--text); font-weight: 600; margin-bottom: 6px; }
.how-desc { color: var(--text-muted); font-size: 0.85em; line-height: 1.5; }

.pricing-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 50px; }
.price-card { border: 1px solid var(--border); padding: 20px; position: relative; }
.price-card-pro { border-color: var(--accent); }
.price-badge {
  position: absolute; top: -1px; right: -1px; background: var(--accent);
  color: var(--bg); font-size: 0.65em; font-weight: 700; padding: 3px 8px;
  letter-spacing: 1px;
}
.price-tier { font-size: 0.75em; letter-spacing: 2px; margin-bottom: 8px; }
.price-amount { font-size: 1.8em; margin-bottom: 4px; }
.price-period { color: var(--text-muted); font-size: 0.85em; margin-bottom: 16px; }
.price-features { font-size: 0.85em; line-height: 2; }

.landing-cta { text-align: center; margin-bottom: 40px; }
.landing-cta-note { color: var(--text-muted); font-size: 0.8em; margin-top: 12px; }
.landing-footer { margin-top: auto; padding-top: 60px; color: var(--border); font-size: 0.75em; letter-spacing: 2px; text-align: center; }
/* Legal links sit above the copyright mark. --text-muted, not the footer's
   own --border colour, so they clear AA against the page. */
.landing-footer-links { display: flex; gap: 24px; justify-content: center; flex-wrap: wrap; margin-bottom: 14px; }
.landing-footer-links a,
.landing-footer-links button { color: var(--text-muted); text-decoration: none; }
.landing-footer-links button {
  background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; letter-spacing: inherit;
}
.landing-footer-links a:hover, .landing-footer-links a:focus-visible,
.landing-footer-links button:hover, .landing-footer-links button:focus-visible { color: var(--text); text-decoration: underline; text-underline-offset: 4px; }
.landing-footer-mark { color: var(--border); }

/* ─── CONSENT BANNER ──────────────────────────────────────── */
/* Non-modal: it never traps focus or covers the page, so it can't act as an
   interstitial in front of content. Anchored bottom-left rather than full
   width so it reads as a panel rather than a bar. */
.consent {
  position: fixed; left: 0; bottom: 0; z-index: 900;
  padding: 16px; max-width: 520px; width: 100%;
  /* Clear the home indicator so the accept button stays tappable. */
  padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  padding-left: max(16px, env(safe-area-inset-left, 16px));
  animation: consent-in 260ms ease-out;
}
.consent-out { animation: consent-out 170ms ease-in forwards; pointer-events: none; }
@keyframes consent-in  { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
@keyframes consent-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(12px); } }
@media (prefers-reduced-motion: reduce) {
  .consent, .consent-out { animation: none; }
}
.consent-box {
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  padding: 18px 20px; box-shadow: var(--card-shadow);
}
.consent-title {
  font-size: 0.72em; letter-spacing: 3px; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 8px;
}
.consent-copy p { margin: 0; font-size: 0.85em; line-height: 1.65; color: var(--text-secondary); }
.consent-copy a { color: var(--accent-hover); text-underline-offset: 3px; }

.consent-opts { margin: 16px 0 4px; display: flex; flex-direction: column; gap: 4px; }
/* 44px min target, and the whole row is the label so the hit area is the row. */
.consent-opt {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 10px 8px; min-height: 44px; cursor: pointer;
  border: 1px solid transparent;
}
.consent-opt:hover { background: var(--bg); border-color: var(--border); }
.consent-opt input { margin-top: 3px; width: 16px; height: 16px; accent-color: var(--accent); flex-shrink: 0; }
.consent-opt input:disabled { opacity: 0.4; cursor: not-allowed; }
.consent-opt input:disabled ~ span { opacity: 0.45; }
.consent-opt span { display: flex; flex-direction: column; font-size: 0.8em; color: var(--text-muted); line-height: 1.5; }
.consent-opt strong { color: var(--text); font-weight: 600; font-size: 1.05em; }

.consent-actions { display: flex; gap: 10px; margin-top: 14px; }
/* Decline is the same size and weight as Save — refusing must not be harder
   than agreeing. */
.consent-btn { flex: 1; padding: 11px 16px; font-size: 0.78em; min-height: 44px; }

@media (max-width: 560px) {
  .consent { padding: 10px; max-width: none; }
  .consent-box { padding: 16px; }
  .consent-actions { flex-direction: column; }
}

/* ─── EMPTY STATE ─────────────────────────────────────────── */
.empty { text-align: center; padding: 40px; color: var(--text-muted); }
.empty-icon { font-size: 2em; margin-bottom: 8px; }

/* ─── TOAST ───────────────────────────────────────────────── */
.toast {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
  background: var(--bg-elevated); border: 1px solid var(--border);
  padding: 12px 20px; z-index: 1000; display: flex; gap: 12px; align-items: center;
  animation: slideUp 0.3s ease;
}
.toast-success { border-color: var(--success); }
.toast-error { border-color: var(--danger); }
.toast-pending { border-color: var(--accent); }
.toast-spin {
  display: inline-block; width: 12px; height: 12px;
  border: 2px solid var(--border); border-top-color: var(--accent);
  border-radius: 50%; animation: toast-spin 0.7s linear infinite;
}
@keyframes toast-spin { to { transform: rotate(360deg); } }

/* ─── ANIMATIONS ──────────────────────────────────────────── */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes slideUp { from { opacity: 0; transform: translate(-50%, 20px); } to { opacity: 1; transform: translate(-50%, 0); } }
@keyframes blink { 50% { opacity: 0; } }
.fade-in { animation: fadeIn 0.3s ease; }

/* ─── KANBAN ──────────────────────────────────────────────── */
/* The kanban breaks out of the 680px app container — it needs the full
   viewport so columns aren't crushed. We override max-width via a wider
   container that the kanban tab renders inside. */

/* Kanban-only card treatment in light mode. Cards take a warm near-white
   fill and a stronger shadow so they read as raised against the page.
   We DON'T darken the page bg here — the chrome→content ramp puts the
   content surface as the lightest band, and cards lift via shadow + tone.
   --kb-card-bg flows through the .kb-card / .kb-capture-closed rules below;
   keeping it as a var (rather than a direct .kb-card override) means
   .kb-card-ghost's translucent koi fill still wins on drop placement. */
[data-theme="light"]:has(.kb-board-kanban) {
  --kb-card-bg: #fcf9f3;     /* paper — Warm Theme */
  --kb-card-border: var(--border);
  --card-shadow: 0 1px 0 rgba(61,56,47,0.04), 0 6px 18px rgba(61,56,47,0.08);
}
/* Per-column tonal fills. Each column gets its own backdrop tone so
   parchment cards lift via the temperature change rather than via
   chrome stepping. Light theme only — dark stays flat on slate. */
[data-theme="light"] .kb-col[data-col="inbox"]     { background: var(--col-inbox);     border-radius: 10px; padding: 18px 12px 24px; }
[data-theme="light"] .kb-col[data-col="in_progress"] { background: var(--col-in-progress); border-radius: 10px; padding: 18px 12px 24px; }
[data-theme="light"] .kb-col[data-col="scheduled"] { background: var(--col-scheduled); border-radius: 10px; padding: 18px 12px 24px; }
[data-theme="light"] .kb-col[data-col="done"]      { background: var(--col-done);      border-radius: 10px; padding: 18px 12px 24px; }
[data-theme="light"] .kb-col[data-col="someday"]   { background: var(--col-someday);   border-radius: 10px; padding: 18px 12px 24px; }
[data-theme="light"] .kb-col[data-col="trashed"]   { background: var(--col-trashed);   border-radius: 10px; padding: 18px 12px 24px; }
[data-theme="light"] .kb-col-count {
  background: rgba(255,255,255,0.4);
  border: 1px solid rgba(255,255,255,0.5);
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.95em;
}
[data-theme="light"] .kb-capture-closed {
  background: rgba(255,255,255,0.35);
  border-style: dashed;
  border-color: rgba(0,0,0,0.18);
}

/* ─── Direction 2: Modernist Stripe — kanban specifics ─────────
   Each column reads as a white panel with a 4px coloured top stripe
   in its accent. Cards are pure white with a hairline border, sans
   typography throughout. */
[data-theme="modernist"]:has(.kb-board-kanban) {
  --kb-card-bg: #ffffff;
  --kb-card-border: var(--border);
}
[data-theme="modernist"] body,
[data-theme="modernist"] input,
[data-theme="modernist"] textarea,
[data-theme="modernist"] select,
[data-theme="modernist"] button {
  font-family: var(--sans-body);
}
[data-theme="modernist"] .kb-col[data-col] {
  border-radius: 10px;
  border: 1px solid var(--border-light);
  border-top: 4px solid var(--col-accent, var(--accent));
  padding: 16px 14px 24px;
}
[data-theme="modernist"] .kb-col[data-col="inbox"]     { background: var(--col-inbox);     --col-accent: var(--col-inbox-accent); }
[data-theme="modernist"] .kb-col[data-col="in_progress"] { background: var(--col-in-progress); --col-accent: var(--col-in-progress-accent); }
[data-theme="modernist"] .kb-col[data-col="scheduled"] { background: var(--col-scheduled); --col-accent: var(--col-scheduled-accent); }
[data-theme="modernist"] .kb-col[data-col="done"]      { background: var(--col-done);      --col-accent: var(--col-done-accent); }
[data-theme="modernist"] .kb-col[data-col="someday"]   { background: var(--col-someday);   --col-accent: var(--col-someday-accent); }
[data-theme="modernist"] .kb-col[data-col="trashed"]   { background: var(--col-trashed);   --col-accent: var(--col-trashed-accent); }
[data-theme="modernist"] .kb-col-header {
  flex-wrap: wrap;
  align-items: center;
  padding: 4px 4px 10px;
  border-bottom: none;
  font-size: 1em;
  letter-spacing: 0;
}
[data-theme="modernist"] .kb-col-header > span:first-child {
  font-family: var(--sans-display);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text);
  display: inline-flex; align-items: center; gap: 8px;
}
/* Leading colour dot before the column name. */
[data-theme="modernist"] .kb-col-header > span:first-child::before {
  content: "";
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--col-accent);
}
[data-theme="modernist"] .kb-col[data-col] .kb-col-header > span:first-child::after {
  font-size: 0.85em;
  color: var(--text-muted);
  margin-left: 4px;
}
[data-theme="modernist"] .kb-col-header::after {
  content: var(--col-sub, "");
  flex-basis: 100%;
  margin-top: 4px;
  font-family: var(--sans-body);
  font-weight: 400;
  font-size: 12.5px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-muted);
}
[data-theme="modernist"] .kb-col[data-col="inbox"]     { --col-sub: "Capture inbound"; }
[data-theme="modernist"] .kb-col[data-col="in_progress"] { --col-sub: "Actively working"; }
[data-theme="modernist"] .kb-col[data-col="scheduled"] { --col-sub: "On the calendar"; }
[data-theme="modernist"] .kb-col[data-col="done"]      { --col-sub: "Shipped"; }
[data-theme="modernist"] .kb-col[data-col="someday"]   { --col-sub: "On the horizon"; }
[data-theme="modernist"] .kb-col[data-col="trashed"]   { --col-sub: "Recently removed"; }
[data-theme="modernist"] .kb-col-count {
  background: rgba(0,0,0,0.04);
  border: none;
  padding: 2px 7px;
  border-radius: 4px;
  font-family: var(--font-ui, 'JetBrains Mono', monospace);
  font-size: 0.85em;
}
[data-theme="modernist"] .kb-card-text {
  font-family: var(--sans-display);
  font-weight: 600;
  font-size: 13.5px;
  letter-spacing: -0.005em;
  line-height: 1.35;
}
[data-theme="modernist"] .kb-card {
  border-radius: 8px;
}
[data-theme="modernist"] .kb-card:hover {
  box-shadow: 0 4px 12px rgba(15,23,42,0.08), 0 1px 2px rgba(15,23,42,0.04);
}
[data-theme="modernist"] .kb-capture-closed {
  border: 1.5px dashed var(--accent);
  background: rgba(37,99,235,0.04);
  color: var(--accent);
  border-radius: 8px;
}

.kb-filters {
  display: flex; flex-wrap: wrap; gap: 6px;
  background: var(--bg-filters, transparent);
  padding: 12px 16px;
  margin: 0 -16px 16px;    /* flush against tabs above, breathe before content */
}
/* Page title inside the controls band. The band carries the same tint as the
   tab strip on three of the four themes, so the two read as one region flowing
   down from the header — lifting the title out would split that in half. The
   auto right margin pushes the controls to the far end; the modifier class
   centres the row so a taller title doesn't stretch the chips beside it (the
   Kanban and Notifications filter bars share .kb-filters and have no title). */
.kb-filters-titled { align-items: center; }
.kb-filters-title {
  margin: 0 auto 0 0;
  font-size: 1.35em; font-weight: 700; letter-spacing: -0.01em;
  line-height: 1.2; color: var(--text);
}
.kb-chip {
  background: transparent; border: 1px solid var(--border);
  color: var(--text-muted); padding: 6px 12px; font-size: 0.75em;
  font-family: inherit; letter-spacing: 1px; cursor: pointer;
  transition: all 0.15s;
}
.kb-chip:hover { color: var(--text); border-color: var(--text-muted); }
.kb-chip-active { color: var(--accent); border-color: var(--accent); }

/* Columns show/hide dropdown — sits at the right end of the filter bar.
   margin-left:auto pushes it past whatever chips are present. The popover
   anchors to the trigger; checkboxes drive kanbanToggleHidden. */
.kb-cols-wrap { position: relative; margin-left: auto; }
.kb-cols-toggle-open { color: var(--accent); border-color: var(--accent); }
.kb-cols-menu {
  position: absolute; right: 0; top: calc(100% + 4px);
  background: var(--bg-elevated); border: 1px solid var(--border);
  min-width: 160px; z-index: 100; padding: 4px 0;
  box-shadow: var(--overlay-shadow);
}
.kb-cols-item {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 12px; cursor: pointer; color: var(--text);
  font-size: 0.85em; user-select: none;
}
.kb-cols-item:hover { background: var(--bg); }
.kb-cols-item input { cursor: pointer; accent-color: var(--accent); margin: 0; }

/* The board is a flex row of columns that share the available width.
   On wide viewports the five columns flow to fit the container; on the
   medium range (900px–1100px) min-width can push columns past the edge
   and the overflow-x: auto fallback lets the row scroll. Below 900px
   the layout switches entirely to the tabbed mode (.kb-tabs above the
   board) — see the @media block at the bottom of this file. */
.kb-board {
  display: flex; gap: 12px;
  align-items: flex-start;   /* columns size to their own content, not the tallest */
  overflow-x: auto; overflow-y: visible;
  padding-bottom: 12px;
  scrollbar-width: thin;
}
.kb-col {
  flex: 1 1 0; min-width: 200px;
  display: flex; flex-direction: column;
}
/* The drop handler lives on .kb-col, so a column is only droppable as far
   down as it is tall. With the board's default flex-start that meant a short
   column sitting beside a tall one had almost no drop target — you could only
   land a card where cards already were. Stretch equalises the columns so the
   whole column accepts a drop, and min-height keeps a usable target when every
   column is empty. Scoped to the kanban board: .kb-board-projects deliberately
   keeps content-height columns. */
.kb-board-kanban {
  align-items: stretch;
  min-height: calc(100vh - 260px);
}

/* ── Column groups ────────────────────────────────────────────────
   A rail above the board naming the broad category over each run of
   columns — the board's own column names don't say that In Progress
   and Scheduled are two grades of the same thing. Each group mirrors
   its columns' flex geometry (--span = how many columns it covers)
   so the spans line up. All groups read alike: the grouping is
   structural, and the koi stays reserved for per-task state (overdue,
   active card) rather than a standing header.
   overflow-x is hidden rather than visible so kanbanSyncGroupRail can
   drive scrollLeft to match the board when six columns don't fit. */
.kb-group-rail {
  display: flex; gap: 12px;
  overflow-x: hidden;
  margin-bottom: 7px;
}
.kb-group {
  display: flex; flex-direction: column; gap: 5px;
  min-width: calc(var(--span) * 200px + (var(--span) - 1) * 12px);
}
.kb-group-line {
  height: 2px; border-radius: 1px;
  background: var(--border);
}
.kb-group-label {
  font-size: 0.6em; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── Week view ──────────────────────────────────────────────────
   The Week tab reuses .kb-board / .kb-col / .kb-card but its
   columns are days, not status buckets. The board layout is
   identical to the home kanban. */
/* One control row above the planner: the overlay-calendar chips take the
   Inbox sidebar's slot on the left, the week toggle + range start where the
   calendar grid does, and calm/refresh sit at the far right. */
.week-nav {
  display: flex; align-items: center; gap: 12px;
  /* Equal air above and below: 8px clears the tab strip's bottom border, 8px
     sits between the controls and the calendar's top edge. Same 16px total
     the old 4/12 split used, just centred. */
  margin: 0 16px 8px;
  padding-top: 8px;
  flex-wrap: wrap;
}
/* Planner/Month All+Mine bar. The generic .kb-filters is a full-bleed band
   sized to sit directly under the tab strip; here it's a second control row
   under .week-nav, so it drops the band padding and the negative gutters and
   aligns to the same 16px inset as the nav above it. */
.week-tab > .kb-filters {
  padding: 0 16px;
  margin: 0 0 8px;
  background: transparent;
}
/* 280px sidebar + 16px .week-layout gap, minus this row's own 12px gap. An
   empty spacer — it exists only to start the toggle at the calendar's edge. */
.week-nav-lead { flex: 0 0 284px; max-width: 284px; }
@media (max-width: 700px) { .week-nav-lead { display: none; } }
/* Week stepper: ← Today →. Borrows the .week-refresh shell from the action
   group at the other end of the row, so the whole nav reads as one family of
   controls. Today keeps its slot when disabled — hiding it on the current week
   would shunt the date range sideways every time you navigate home. */
.week-nav-step { display: inline-flex; align-items: center; gap: 6px; }
.week-nav-step .week-refresh { margin-left: 0; }
.week-nav-arrow {
  justify-content: center;
  min-width: 34px;
  font-size: 0.85em;
}
.week-nav-today { letter-spacing: 0.08em; text-transform: uppercase; font-size: 0.7em; }
.week-nav-today:disabled,
.week-nav-today:disabled:hover {
  color: var(--strike); border-color: var(--border-light);
  cursor: default; opacity: 1;
}
/* Touch: the 30px .week-refresh shell is under the 44px target, and these
   three are the controls a thumb reaches for most. */
@media (pointer: coarse) {
  .week-nav-step .week-refresh { min-height: 44px; padding: 3px 12px; }
  .week-nav-arrow { min-width: 44px; }
}
.week-range {
  font-size: 0.85em;
  color: var(--text-muted);
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
}
/* "Next week" / "3 weeks ahead" — only rendered off the current week. */
.week-offset-cap {
  font-size: 0.78em; color: var(--text-muted); letter-spacing: 0.02em;
}

/* Week | Month switch. Two .week-refresh shells butted together so the pair
   reads as one control rather than two loose buttons. */
.planner-mode { display: inline-flex; }
.planner-mode .week-refresh { margin-left: 0; border-radius: 0; }
.planner-mode .week-refresh:first-child { border-radius: 3px 0 0 3px; }
.planner-mode .week-refresh:last-child  { border-radius: 0 3px 3px 0; margin-left: -1px; }
.planner-mode-btn.is-active {
  color: var(--bg); background: var(--accent); border-color: var(--accent);
}
.planner-mode-btn.is-active:hover { color: var(--bg); border-color: var(--accent); }
@media (pointer: coarse) { .planner-mode .week-refresh { min-height: 44px; } }

/* ─── MONTH VIEW ───────────────────────────────────────────────────────────
   6x7 ledger grid. Fixed row height: cells must not resize as events land, or
   the whole page shifts under the cursor mid-scan. Overflow becomes "+N more"
   rather than a taller cell. */
.mo {
  margin: 0 16px 16px;
  border: 1px solid var(--border); border-radius: 4px;
  background: var(--bg-elevated); overflow: hidden;
}
.mo-heads { display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid var(--border); }
.mo-heads > div {
  padding: 8px 10px; border-right: 1px solid var(--border);
  font-size: 0.68em; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-dim);
}
.mo-heads > div:last-child { border-right: 0; }
.mo-grid { display: grid; grid-template-columns: repeat(7, 1fr); grid-auto-rows: 118px; }
.mo-cell {
  display: flex; flex-direction: column; gap: 3px;
  padding: 6px 7px 4px; overflow: hidden; position: relative;
  border-right: 1px solid var(--border-light); border-bottom: 1px solid var(--border-light);
  cursor: pointer; transition: background 150ms ease;
}
.mo-cell:nth-child(7n) { border-right: 0; }
.mo-grid > .mo-cell:nth-last-child(-n+7) { border-bottom: 0; }
.mo-cell:hover { background: color-mix(in srgb, var(--text) 4%, transparent); }
.mo-cell:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Drop target for a task dragged out of the Inbox. */
.mo-cell-drop {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.mo-cell.is-weekend { background: color-mix(in srgb, var(--text) 1.5%, transparent); }
.mo-cell.is-outside { background: color-mix(in srgb, var(--text) 2%, transparent); }
.mo-cell.is-outside .mo-num, .mo-cell.is-past .mo-num { color: var(--text-dim); }
/* Blocked days carry a hatch AND the word — never colour alone. */
.mo-cell.is-blocked {
  background-image: repeating-linear-gradient(135deg,
    transparent 0 5px, color-mix(in srgb, var(--text) 6%, transparent) 5px 10px);
}
.mo-daytop { display: flex; align-items: center; gap: 5px; min-height: 22px; }
.mo-num { font-size: 0.85em; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.mo-cell.is-today .mo-num {
  color: var(--bg); background: var(--accent); border-radius: 999px;
  min-width: 22px; height: 22px; padding: 0 6px;
  display: inline-flex; align-items: center; justify-content: center;
}
.mo-flags { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; }
.mo-flag {
  font-size: 0.6em; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted);
  display: inline-flex; align-items: center;
}
.mo-flag-loc svg { width: 11px; height: 11px; display: block; }
.mo-mst {
  display: flex; align-items: center; gap: 5px;
  font-size: 0.68em; line-height: 1.3; color: var(--warning);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mo-mst::before { content: '\25C6'; font-size: 0.85em; flex: none; }
/* One ledger line: colour dot, time, title. */
.mo-row {
  display: flex; align-items: baseline; gap: 5px;
  width: 100%; padding: 0; border: 0; background: none; text-align: left;
  font: inherit; font-size: 0.68em; line-height: 1.32;
  white-space: nowrap; overflow: hidden; cursor: pointer;
}
.mo-row:hover .mo-n { color: var(--accent); }
/* Rows you can drag to another day say so under the cursor. */
.mo-row[draggable="true"] { cursor: grab; }
.mo-row[draggable="true"]:active { cursor: grabbing; }
.mo-row.kb-card-dragging { opacity: 0.4; }
.mo-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.mo-dot {
  width: 6px; height: 6px; border-radius: 2px; flex: none;
  background: var(--text-dim); transform: translateY(-1px);
}
.mo-dot-task { border-radius: 999px; background: var(--accent); }
.mo-t { color: var(--text-muted); font-variant-numeric: tabular-nums; flex: none; }
.mo-n { color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; }
.mo-row-task .mo-n { color: var(--text); }
.mo-more { margin-top: auto; font-size: 0.66em; color: var(--text-muted); letter-spacing: 0.02em; }

/* Phones: the ledger can't fit seven columns of text, so cells shrink to the
   date plus a count. Tapping still opens the week, which is where the detail
   is legible anyway. */
@media (max-width: 700px) {
  .mo { margin: 0 12px 12px; }
  .mo-grid { grid-auto-rows: 62px; }
  .mo-heads > div { padding: 6px 4px; font-size: 0.6em; letter-spacing: 0.06em; }
  .mo-cell { padding: 4px 4px 2px; gap: 1px; }
  .mo-row, .mo-mst, .mo-more { display: none; }
  .mo-cell::after {
    content: attr(data-count); margin-top: auto;
    font-size: 0.6em; color: var(--text-muted); font-variant-numeric: tabular-nums;
  }
  .mo-flag:not(.mo-flag-loc) { display: none; }
}
/* Calendar refresh. Sits at the far right of the week nav. The button folds in
   its own status: it spins + reads "Syncing…" while a fetch (manual or
   background) is in flight, and settles back to "↻ Refresh" when done. */
.week-refresh {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 5px;
  background: none; border: 1px solid var(--border); border-radius: 3px;
  padding: 3px 8px;
  font: inherit; font-size: 0.75em; color: var(--text-muted);
  letter-spacing: 0.02em; cursor: pointer;
  transition: color 150ms ease, border-color 150ms ease;
}
.week-refresh-label { line-height: 1; }
.week-refresh:hover:not(:disabled) { color: var(--accent); border-color: var(--text-dim); }
.week-refresh:disabled { opacity: 0.6; cursor: progress; }
.week-refresh:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.week-refresh-icon { display: inline-block; line-height: 1; }
/* Overlay-calendars dropdown. Same .week-refresh shell as Calm and Refresh —
   the only extra is the caret, so the three read as one quiet set. */
.week-overlay-caret { line-height: 1; font-size: 0.9em; color: var(--text-dim); }
.week-overlay-btn:hover:not(:disabled) .week-overlay-caret { color: var(--accent); }
.week-overlay-menu .kb-chip-popover-item {
  display: flex; align-items: center; gap: 8px;
}
.week-overlay-menu .team-person-sw { flex: 0 0 auto; }

/* Right-aligned group holding Overlay calendars + Super-calm + Refresh. */
.week-nav-actions { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; }
.week-nav-actions .week-refresh { margin-left: 0; }
/* Calm + Refresh share .week-refresh, so they're identical in size and state;
   keep their icon/label metrics matched so they line up exactly. The calm button
   has no on/off visual state — only its label word changes. */
.week-calm-icon { display: inline-block; line-height: 1; }
.week-calm-label { line-height: 1; }
/* Super calm mode: strip all text from calendar blocks → pure free/busy. The
   coloured blocks remain; only the labels/time/project/lock are hidden. */
.week-tab.week-calm .week-cal-block-title,
.week-tab.week-calm .week-cal-block-time,
.week-tab.week-calm .week-cal-block-proj,
.week-tab.week-calm .week-cal-event-lock { display: none; }
.week-tab.week-calm .week-cal-allday,
.week-tab.week-calm .week-cal-allday-more { color: transparent; }
/* Calm mode also lowers the volume of the block colours. The hue stays — so
   the event / task / per-category coding is still legible at a glance — but
   saturation and opacity drop so the calendar reads as a quiet free/busy wash
   rather than a vivid agenda. filter (not colour overrides) is what lets this
   desaturate the inline category/Google-palette colours too; a class rule
   can't reach an inline style. */
.week-tab.week-calm .week-cal-event,
.week-tab.week-calm .week-cal-task,
.week-tab.week-calm .week-cal-allday {
  filter: saturate(0.4);
  opacity: 0.62;
}
.week-tab.week-calm .week-cal-event:hover,
.week-tab.week-calm .week-cal-task:hover {
  opacity: 0.78;   /* a touch more present on hover, still muted */
}
/* Super calm mode also strips the inbox cards down to title + Do Now + Trash:
   hide the captured-at "ago" / assignee meta, the chip row (project/due/size/
   energy), the energy/size box stack, the Schedule + Autoschedule row, and the
   Someday button (a plain .kb-action, unlike Do Now/Trash which are
   success/danger). Scoped to .kb-card-week so only the Week sidebar is touched. */
.week-tab.week-calm .kb-card-week .kb-card-meta,
.week-tab.week-calm .kb-card-week .kb-card-assignee,
.week-tab.week-calm .kb-card-week .kb-card-chips,
.week-tab.week-calm .kb-card-week .kb-card-boxes,
.week-tab.week-calm .kb-card-week .kb-card-actions-row:first-child,
.week-tab.week-calm .kb-card-week .kb-card-actions-row .kb-action:not(.kb-action-success):not(.kb-action-danger) {
  display: none;
}
.week-refresh-icon.is-spinning { animation: week-refresh-spin 0.8s linear infinite; }
@keyframes week-refresh-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .week-refresh-icon.is-spinning { animation: none; } }

/* Calendar-week loading is surfaced via the global pending toast (see
   weekFetchCalendar) rather than a scrim — the old overlay blocked clicks on
   native events while fetching. The pulse keyframe lives on below since
   .view-loading-dot still uses it. */
@keyframes week-cal-loading-pulse {
  0%, 100% { opacity: 0.3; transform: scale(0.85); }
  50%      { opacity: 1;   transform: scale(1); }
}

/* ─── VIEW LOADING ────────────────────────────────────────────
   Shared placeholder for content-area / tab loads. Centers a pulsing dot +
   label in the middle of the area that's actually loading, instead of jamming
   a bare "Loading…" against the tab strip. The 0.18s delay + fill-mode keeps
   it invisible on fast loads so it never flashes; on completion the real
   view's own fade-in takes over. */
.view-loading {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 55vh;
  color: var(--text-muted); font-size: 0.9em; letter-spacing: 0.02em;
  animation: viewLoadingFade 0.45s ease 0.18s both;
}
.view-loading-dot {
  width: 8px; height: 8px;
  background: var(--accent);
  animation: week-cal-loading-pulse 1.1s ease-in-out infinite;
}
@keyframes viewLoadingFade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .view-loading     { animation: none; }
  .view-loading-dot { animation: none; opacity: 0.8; }
}

/* ── Week calendar grid ──────────────────────────────────────
   Real calendar layout: hour rail on the left, seven day columns to the
   right, events positioned absolutely by start time, height proportional
   to duration. Overlapping events split horizontally into equal tracks.
   The Inbox column lives in a sidebar to the left of the grid so the
   drag gesture still flows left-to-right.

   Geometry constants (kept in lockstep with WEEK_CAL_* in app.js):
     start hour: 6 AM,  end hour: 24 (midnight),  hour height: 56px. */
.week-layout {
  display: flex;
  gap: 16px;
  align-items: stretch;
  /* Cap the whole region so the Inbox sidebar can't grow taller than the
     calendar. Both children scroll internally. The 200px subtracts an
     estimate of the page chrome above (header + tabs + filters + week nav
     + padding). Overlay chips share the week-nav row, so that's one row less
     than it used to be. */
  max-height: calc(100vh - 165px);
}
.week-inbox-side {
  flex: 0 0 280px;
  max-width: 280px;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Separates the stacked Inbox and In Progress columns. Collapsed children
     are display:none, so this adds nothing to the rail. */
  gap: 10px;
  /* The collapse animates basis + max-width together; the calendar (flex:1)
     reclaims the freed width. */
  transition: flex-basis 280ms cubic-bezier(.2,.8,.2,1),
              max-width 280ms cubic-bezier(.2,.8,.2,1);
}

/* ── Horizontal inbox collapse ────────────────────────────────────────
   .inbox-collapsed on .week-layout folds the sidebar to a thin rail and
   swaps the full .kb-col for the .week-inbox-rail. State persists via
   weekGetInboxCollapsed (localStorage). */
.week-layout.inbox-collapsed .week-inbox-side {
  flex-basis: 52px;
  max-width: 52px;
}
.week-layout.inbox-collapsed .week-inbox-side .kb-col { display: none; }

/* Collapse chevron in the inbox header (week mode only) */
.kb-col-header-right { display: flex; align-items: center; gap: 10px; }
.kb-col-collapse {
  background: transparent; border: 0; cursor: pointer;
  color: var(--text-dim); padding: 2px; display: flex; border-radius: 5px;
  transition: color 160ms ease, background 160ms ease;
}
.kb-col-collapse:hover { color: var(--text); background: var(--bg-input); }
.kb-col-collapse svg { width: 16px; height: 16px; }

/* The rail itself — full height, cross-fades in when collapsed. */
.week-inbox-rail { display: none; }
.week-layout.inbox-collapsed .week-inbox-rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  flex: 1 1 auto;
  min-height: 0;
  width: 52px;
  padding: 14px 0;
  border: 1px solid var(--border-light);
  border-radius: 10px;
  background: var(--bg-elevated);
  cursor: pointer;
  animation: week-rail-in 240ms cubic-bezier(.2,.8,.2,1);
}
@keyframes week-rail-in { from { opacity: 0; } to { opacity: 1; } }
.week-inbox-rail:hover { border-color: var(--text-dim); }
.rail-inbox-icon { width: 22px; height: 22px; color: var(--text-secondary); }
.week-inbox-rail:hover .rail-inbox-icon { color: var(--text); }
.rail-label {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  font-size: 9.5px; letter-spacing: 3px;
  color: var(--text-dim);
}
.rail-label .jp { font-family: var(--jp); letter-spacing: 1px; }
/* Gentle circled count — calm bordered disc, no alert colour. */
.rail-count {
  width: 26px; height: 26px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg-input);
  color: var(--text-secondary);
  font-size: 12px; font-weight: 500;
  display: flex; align-items: center; justify-content: center;
}
.week-inbox-rail:hover .rail-count { border-color: var(--text-dim); color: var(--text); }
.rail-btn {
  background: transparent; border: 0; cursor: pointer;
  color: var(--text-dim); padding: 6px; border-radius: 7px; display: flex;
  transition: color 160ms ease, background 160ms ease;
}
.rail-btn:hover { color: var(--text); background: var(--bg-input); }
.rail-btn svg { width: 17px; height: 17px; }

/* "Autoschedule all" — quiet text link at the top of the Week Inbox.
   Reads as a secondary action; the koi accent only surfaces on hover so
   the bar doesn't pull weight from the cards. Running state stays muted
   but pulses subtly so the user can see the iteration progressing. */
.week-bulk-autoschedule {
  display: block; width: 100%;
  margin: 6px 0 8px;
  padding: 2px 4px;
  background: transparent;
  border: 0;
  color: var(--text-muted);
  font: inherit; font-size: 0.8em; letter-spacing: 0.02em;
  text-align: left;
  cursor: pointer;
  transition: color 200ms cubic-bezier(.2,.8,.2,1);
}
.week-bulk-autoschedule:hover:not(:disabled) { color: var(--accent); }
/* Off the current week autoschedule has nowhere honest to put things — the
   button stays visible so the count still reads, but goes inert. */
.week-bulk-autoschedule:disabled { color: var(--strike); cursor: default; }
.week-bulk-status {
  margin: 6px 0 8px;
  padding: 2px 4px;
  color: var(--text-muted);
  font-size: 0.8em;
  letter-spacing: 0.02em;
  text-align: left;
  font-style: italic;
  animation: kb-pulse-dim 1.4s ease-in-out infinite;
}

/* Accordion. Both panels are always mounted; the closed one folds to its
   header so the open one takes the whole sidebar. The body sits in a
   grid-rows wrapper — 1fr → 0fr transitions cleanly, unlike a max-height
   collapse on a flex item, which snaps shut. */
.week-inbox-side .kb-col {
  flex: 0 0 auto;
  min-height: 0;
  max-height: none;
  display: flex;
  flex-direction: column;
}
.week-inbox-side[data-open="inbox"] .kb-col[data-acc="inbox"],
.week-inbox-side[data-open="in_progress"] .kb-col[data-acc="in_progress"] {
  /* Shrink-but-don't-grow: a short list (or "Inbox zero") stays content-sized
     so the folded panel nests right under it; a long list shrinks to the
     sidebar and scrolls inside .kb-col-body. flex-grow would pin the folded
     header to the bottom of the sidebar with empty space between. */
  flex: 0 1 auto;
}
.week-inbox-side .kb-acc-wrap {
  display: grid;
  grid-template-rows: 0fr;
  min-height: 0;
  transition: grid-template-rows 260ms cubic-bezier(.2,.8,.2,1);
}
.week-inbox-side[data-open="inbox"] .kb-col[data-acc="inbox"] .kb-acc-wrap,
.week-inbox-side[data-open="in_progress"] .kb-col[data-acc="in_progress"] .kb-acc-wrap {
  grid-template-rows: 1fr;
  flex: 1 1 auto;
}
@media (prefers-reduced-motion: reduce) {
  .week-inbox-side .kb-acc-wrap { transition: none; }
}
/* The body is the grid item, so its own padding sets the row's floor — a
   0fr row still measures 16px with .kb-col-body's 8px block padding. Collapse
   the padding alongside the row so a folded panel is exactly its header. */
.week-inbox-side .kb-col-body {
  flex: 1 1 auto;
  overflow: hidden;
  min-height: 0;
  padding-top: 0; padding-bottom: 0;
  transition: padding 260ms cubic-bezier(.2,.8,.2,1);
}
.week-inbox-side[data-open="inbox"] .kb-col[data-acc="inbox"] .kb-col-body,
.week-inbox-side[data-open="in_progress"] .kb-col[data-acc="in_progress"] .kb-col-body {
  overflow-y: auto;
  padding-top: 8px; padding-bottom: 8px;
}
@media (prefers-reduced-motion: reduce) {
  .week-inbox-side .kb-col-body { transition: none; }
}
/* Header doubles as the accordion's control. The caret points down when the
   panel is open, right when it's folded. Bare text on the page background made
   the two controls read as captions, so they get a band of their own: elevated
   fill, hairline border, and an accent rail that marks which panel is open —
   the same job the accent does on an active tab. */
.week-inbox-side .kb-col-header-acc {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-left: 3px solid transparent;
  border-radius: 3px;
  padding: 10px 12px;
  color: var(--text-secondary);
  transition: border-color 160ms ease, color 160ms ease;
  --acc-title: var(--accent);
}
/* Sumi's koi reads 3.6:1 on --bg-elevated: enough for the 3px rail, which is a
   UI component at 3:1, but short of 4.5:1 for an 11px label. On the dark
   default the open title stays in --text and the rail does the marking. */
:root:not([data-theme]) .week-inbox-side .kb-col-header-acc,
[data-theme="dark"] .week-inbox-side .kb-col-header-acc { --acc-title: var(--text); }
.week-inbox-side[data-open="inbox"] .kb-col[data-acc="inbox"] .kb-col-header-acc,
.week-inbox-side[data-open="in_progress"] .kb-col[data-acc="in_progress"] .kb-col-header-acc {
  border-left-color: var(--accent);
  color: var(--acc-title);
}
.week-inbox-side[data-open="inbox"] .kb-col[data-acc="inbox"] .kb-acc-caret,
.week-inbox-side[data-open="in_progress"] .kb-col[data-acc="in_progress"] .kb-acc-caret {
  color: var(--accent);
}
.kb-col-header-acc { cursor: pointer; user-select: none; }
/* Hover lifts a folded header toward full ink; the open one keeps its accent
   title (its rule is more specific), so hover never reads as "this is active". */
.kb-col-header-acc:hover { color: var(--text); }
.week-inbox-side .kb-col-header-acc:hover { border-color: var(--text-dim); }
.kb-col-header-acc:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.kb-acc-caret {
  display: inline-block; margin-right: 8px;
  color: var(--text-dim); letter-spacing: 0;
  transform: rotate(-90deg);
  transition: transform 260ms cubic-bezier(.2,.8,.2,1);
}
.week-inbox-side[data-open="inbox"] .kb-col[data-acc="inbox"] .kb-acc-caret,
.week-inbox-side[data-open="in_progress"] .kb-col[data-acc="in_progress"] .kb-acc-caret {
  transform: rotate(0deg);
}
@media (prefers-reduced-motion: reduce) {
  .kb-acc-caret { transition: none; }
}
.week-cal-wrap {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex; flex-direction: column;
  position: relative;          /* anchor for the loading overlay */
  border: 1px solid var(--border-light);
  border-radius: 10px;
  overflow: hidden;
  background: var(--bg-elevated);
}
.week-cal-headers {
  display: grid;
  grid-template-columns: 56px repeat(7, 1fr);
  border-bottom: 1px solid var(--border-light);
}
.week-cal-corner {
  border-right: 1px solid var(--border-light);
  font-family: var(--font-ui, 'JetBrains Mono', monospace);
  font-size: 0.7em;
  color: var(--text-dim);
  display: flex; align-items: center; justify-content: center;
  letter-spacing: 0.04em;
  padding: 8px 4px;
}
.week-cal-corner-label { color: var(--text-muted); }
.week-cal-head {
  position: relative;
  padding: 10px 12px;
  border-right: 1px solid var(--border-light);
  text-align: left;
}
.week-cal-head:last-child { border-right: 0; }

/* Day-options caret: always visible but muted, brightening on hover/focus.
   Sits top-right of the header; opens the block/unblock popover (weekDayMenu). */
.week-cal-head-menu {
  position: absolute;
  top: 6px; right: 6px;
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  border: 0; border-radius: 5px;
  background: transparent;
  color: var(--text-muted);
  opacity: 0.5;
  cursor: pointer;
  transition: opacity 120ms ease, background 120ms ease, color 120ms ease;
}
.week-cal-head-menu svg { width: 15px; height: 15px; }
.week-cal-head:hover .week-cal-head-menu,
.week-cal-head-menu:focus-visible { opacity: 1; }
.week-cal-head-menu:hover { background: var(--bg-input); color: var(--accent); }
.week-cal-head-menu:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Rose marks a blocked day, bottom-right of the header. */
.week-cal-head-rose {
  position: absolute;
  right: 8px; bottom: 8px;
  display: flex;
  color: var(--accent);
  pointer-events: none;
}
.week-cal-head-rose svg { width: 20px; height: 20px; }

/* Blocked header: mute the date so the rose reads as the state. */
.week-cal-head-blocked .week-cal-head-day,
.week-cal-head-blocked .week-cal-head-num { color: var(--text-muted); }

/* Blocked column: a faint diagonal hatch behind the day's content. Any events
   or tasks already on the day still render on top — blocking only stops
   automation from adding more. Hatch is inert (pointer-events:none). */
.week-cal-col-blocked::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    -45deg,
    color-mix(in srgb, var(--accent) 7%, transparent) 0,
    color-mix(in srgb, var(--accent) 7%, transparent) 6px,
    transparent 6px,
    transparent 12px
  );
  z-index: 0;   /* above the column background, below the event/task blocks */
}
/* Lift the day's real blocks above the hatch so blocking never dims content. */
.week-cal-col-blocked > .week-cal-event,
.week-cal-col-blocked > .week-cal-task { z-index: 1; }
.week-cal-head-day {
  font-family: var(--font-ui, 'JetBrains Mono', monospace);
  font-size: 0.65em;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  margin-bottom: 2px;
}
.week-cal-head-num {
  font-size: 1.6em;
  font-weight: 500;
  color: var(--text);
  line-height: 1;
}
.week-cal-head-today .week-cal-head-num { color: var(--accent); }
.week-cal-head-today {
  box-shadow: inset 0 -2px 0 var(--accent);
}
/* Location row. One fixed-height track under the date headers: a location is
   a per-day attribute (one or none), so it lives here rather than stacking in
   the all-day cell, where one day's band made every column's strip taller.
   Always in the DOM so setDayLocation can repaint into it; .is-empty hides it
   for weeks with no location at all. */
.week-cal-dayloc-row {
  display: grid;
  grid-template-columns: 56px repeat(7, 1fr);
  border-bottom: 1px solid var(--border-light);
}
.week-cal-dayloc-row.is-empty { display: none; }
.week-cal-dayloc-cell {
  border-right: 1px solid var(--border-light);
  padding: 3px 6px;
  display: flex; flex-direction: column;
  min-width: 0;
}
.week-cal-dayloc-cell:last-child { border-right: 0; }
.week-cal-allday-row {
  display: grid;
  grid-template-columns: 56px repeat(7, 1fr);
  border-bottom: 1px solid var(--border-light);
  min-height: 32px;
}
.week-cal-allday-cell {
  border-right: 1px solid var(--border-light);
  padding: 4px 6px;
  display: flex; flex-direction: column; gap: 3px;
  /* Centred so the gutter around a chip is even top and bottom. The row's 32px
     min-height is taller than a single chip, and top-alignment left 4px above
     it against 9px below. A no-op once the content fills the row. */
  justify-content: center;
  /* A grid item's automatic minimum size is its content, so one unwrappable
     child would widen this track and pull the whole row out of alignment with
     .week-cal-headers and .week-cal-body, which use the same template. Pin it
     to 0 so the column always keeps its 1fr share and children truncate. */
  min-width: 0;
}
.week-cal-allday-cell:last-child { border-right: 0; }
.week-cal-allday {
  padding: 3px 8px;
  background: rgba(96,144,224,0.16);
  color: var(--text);
  font-size: 0.72em;
  border-radius: 3px;
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.week-cal-allday-link:hover { background: rgba(96,144,224,0.28); }
/* Overflow chip. Same footprint as an event chip so the capped row stays the
   same height; ink-on-ground rather than event blue because it isn't one. */
.week-cal-allday-more {
  padding: 3px 8px;
  background: color-mix(in srgb, var(--text) 7%, transparent);
  border: 0; border-radius: 3px;
  color: var(--text);
  font-family: inherit; font-size: 0.72em;
  text-align: left; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.week-cal-allday-more:hover { background: color-mix(in srgb, var(--text) 12%, transparent); }
.week-cal-allday-more:focus-visible,
.week-cal-allday-less:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.week-cal-allday-less {
  background: none; border: 0; padding: 0;
  font: inherit; color: inherit; letter-spacing: inherit;
  cursor: pointer;
}
.week-cal-allday-less:hover { color: var(--text); }

/* Day location band. Deliberately NOT styled like .week-cal-allday above — it
   isn't an event and must not read as draggable, so it takes the page ground
   plus a solid left rule instead of the blue event fill. --bg-input is not an
   option: it resolves to the same value as --bg-elevated on the light themes,
   which would make the band vanish. */
/* The slot is the flex item, so it — not the button inside it — is what carries
   the min-content contribution up to the grid track. Without min-width:0 a long
   location name pushes the band into the next day's column.
   The negative margins claw back most of the cell's 6px side padding, leaving a
   3px gutter so the band reads as a chip sitting in the column rather than a
   full-bleed rule. It lands 3px from the divider on BOTH sides: the cell's 1px
   right border is inside its box, so -3px there measures 3px to the visible
   line, not 4px. No max-width: 100% would resolve against the cell's content box
   and cap the band back to the old inset width. */
.week-cal-dayloc-slot { min-width: 0; margin: 0 -3px; }
.week-cal-dayloc-slot:empty { display: none; }
.week-cal-dayloc {
  display: flex; align-items: center; gap: 5px;
  width: 100%;
  /* 3px gutter + 2px rule + 7px = pin at 12px, matching the day name and date. */
  padding: 3px 6px 3px 7px;
  background: var(--bg);
  border: 0; border-left: 2px solid var(--accent);
  /* --text-muted / --text-dim measure 3.7:1 and below on the light and
     modernist themes at this size. The band is small text, so both parts take
     tokens that clear 4.5:1 everywhere and lean on the accent rule for colour. */
  color: var(--text);
  font-family: inherit; font-size: 0.7em;
  letter-spacing: 0.04em; text-transform: uppercase;
  text-align: left; cursor: pointer;
  overflow: hidden;
}
.week-cal-dayloc:hover { background: var(--bg-elevated); }
.week-cal-dayloc:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.week-cal-dayloc-pin { flex: 0 0 auto; display: flex; }
.week-cal-dayloc-pin svg { width: 11px; height: 11px; }
.week-cal-dayloc-name {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.week-cal-dayloc-tz {
  flex: 0 0 auto;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  text-transform: none; letter-spacing: 0;
}

/* Milestone marker in the all-day strip. Not an event and not a location band:
   a dated commitment, so it takes a neutral ink tint plus the Timeline's ◆
   rather than the blue event fill — the two surfaces read as the same object.
   min-width:0 for the same reason the location band has it (a flex item's
   automatic minimum is its content, which would widen the grid track and pull
   the all-day row out of alignment with the headers and the hour grid). The
   -3px side margins claw back the cell padding so the chip lines up with the
   location band stacked above it. */
.week-cal-ms {
  display: flex; align-items: center; gap: 5px;
  min-width: 0; margin: 0 -3px;
  padding: 3px 6px 3px 7px;
  background: color-mix(in srgb, var(--text) 7%, transparent);
  border: 0; border-left: 2px solid var(--text);
  color: var(--text);
  font-family: inherit; font-size: 0.72em;
  text-align: left; cursor: pointer;
  overflow: hidden;
}
.week-cal-ms:hover { background: color-mix(in srgb, var(--text) 13%, transparent); }
.week-cal-ms:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.week-cal-ms-dia { flex: 0 0 auto; line-height: 1; font-size: 0.85em; }
.week-cal-ms-name {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Hit and late are carried by the rule + diamond, never by the label's colour:
   --success and --danger both fail 4.5:1 as small text on the light themes. */
.week-cal-ms.is-hit { border-left-color: var(--success); }
.week-cal-ms.is-hit .week-cal-ms-dia { color: var(--success); }
.week-cal-ms.is-hit .week-cal-ms-name { text-decoration: line-through; }
.week-cal-ms.is-late { border-left-color: var(--danger); }
.week-cal-ms.is-late .week-cal-ms-dia { color: var(--danger); }
.week-cal-ms-tag {
  flex: 0 0 auto;
  font-size: 0.85em; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text);
}

/* Location picker popover. Extends .admin-menu; the rows carry their own hover
   treatment for the same --bg-input reason as the band. */
.week-day-loc-menu { min-width: 240px; max-width: 300px; }
.week-day-loc-head {
  padding: 7px 14px 5px;
  font-size: 0.72em; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-dim);
}
.week-day-menu-label {
  padding: 7px 14px 3px;
  font-size: 0.68em; letter-spacing: 0.09em;
  color: var(--text-dim);
}
.week-day-loc-search { padding: 4px 10px 6px; }
.week-day-loc-search input {
  width: 100%; padding: 5px 8px;
  background: var(--bg); border: 1px solid var(--border);
  color: var(--text); font-family: inherit; font-size: 0.85em;
}
.week-day-loc-search input:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.week-day-loc-list { max-height: 260px; overflow-y: auto; }
.week-day-loc-item {
  display: flex; align-items: baseline; gap: 10px;
  width: 100%;
  border-left: 2px solid transparent;
}
.week-day-loc-item:hover:not([disabled]) { background: var(--bg); border-left-color: var(--accent); }
.week-day-loc-item.is-current { border-left-color: var(--accent); color: var(--accent); }
.week-day-loc-item-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.week-day-loc-item-tz { flex: 0 0 auto; font-size: 0.82em; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.week-day-loc-empty { padding: 10px 14px; font-size: 0.85em; color: var(--text-dim); }
.week-day-loc-sep { height: 1px; margin: 4px 0; background: var(--border-light); }

.week-cal-body {
  display: grid;
  grid-template-columns: 56px repeat(7, 1fr);
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  position: relative;
}
/* All three rows share the 56px + 7×1fr template, but only the body scrolls, so
   its tracks lay out inside a content box narrower by the scrollbar width. That
   drifts ~0.86px per column — 5px by Saturday on a 6px scrollbar — and the day
   dividers stop lining up with the headers. Reserving the same gutter above puts
   them back on the same grid. Measured into --week-scrollbar by
   _weekSyncScrollGutter rather than hardcoded: the width is platform- and
   setting-dependent, and 0 with macOS overlay scrollbars. */
.week-cal-headers,
.week-cal-dayloc-row,
.week-cal-allday-row { padding-right: var(--week-scrollbar, 0px); }
.week-cal-rail {
  position: relative;
  border-right: 1px solid var(--border-light);
}
.week-cal-rail-label {
  position: absolute;
  right: 6px;
  margin-top: -7px;             /* center the label on the hour line */
  font-family: var(--font-ui, 'JetBrains Mono', monospace);
  font-size: 0.65em;
  color: var(--text-dim);
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.week-cal-col {
  position: relative;
  border-right: 1px solid var(--border-light);
  /* The drop guideline appears at --week-cal-hint-y while drag-over. */
  transition: background 120ms ease;
}
.week-cal-col:last-child { border-right: 0; }
.week-cal-col-today { background: rgba(200,57,46,0.025); }

/* ─── Team week (cross-user calendar sharing) ─────────────────────────────── */
.week-nav-title { font-size: 0.95em; color: var(--text); font-weight: 500; letter-spacing: 0.02em; }
.week-nav-toggle { display: inline-flex; gap: 6px; margin-left: auto; }
.week-nav-btn {
  background: transparent; border: 1px solid var(--border);
  color: var(--text-muted); padding: 4px 10px; font: inherit; font-size: 0.75em;
  cursor: pointer; letter-spacing: 0.02em; border-radius: 4px;
}
.week-nav-btn:hover { color: var(--text); border-color: var(--text-dim); }
.week-nav-btn.active { color: var(--accent); border-color: var(--accent); }
.team-people-panel {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 0 16px 12px;
}
.team-person {
  display: inline-flex; align-items: center; gap: 7px;
  background: transparent; border: 1px solid var(--border);
  color: var(--text-muted); padding: 4px 10px; font: inherit; font-size: 0.78em;
  cursor: pointer; border-radius: 14px;
}
.team-person:hover { color: var(--text); border-color: var(--text-dim); }
.team-person-on { color: var(--text); border-color: var(--text-dim); }
.team-person-legend { cursor: default; opacity: 0.9; }
.team-person-sw { width: 11px; height: 11px; border-radius: 50%; border: 2px solid; box-sizing: border-box; flex: 0 0 auto; }
.team-person-name { white-space: nowrap; }
.team-person-warn { font-size: 0.85em; color: var(--danger, #d50000); margin-left: 2px; }
.team-empty-hint { color: var(--text-muted); font-size: 0.8em; line-height: 1.5; }
/* "Busy" overlay blocks read like events but carry no detail. */
.team-cal-block { overflow: hidden; }
/* Shared-calendar overlay on the Week view: translucent busy bands drawn behind
   your own events (inserted before the event blocks in the column, so they paint
   underneath). Non-interactive — clicks/drag pass through to the column. */
.week-cal-overlay-band {
  position: absolute; left: 0; right: 0;
  border-left: 3px solid; border-radius: 2px;
  pointer-events: none;
}
/* Calendar-sharing settings rows */
.cal-share-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.cal-share-meta { min-width: 0; }
.cal-share-meta .settings-label { font-size: 13px; line-height: 1.35; }
.cal-share-email { font-size: 11.5px; line-height: 1.4; color: var(--text-muted); }
.cal-share-actions { display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.cal-share-level { font-size: 0.7em; color: var(--text-dim); letter-spacing: 0.04em; text-transform: uppercase; white-space: nowrap; }
/* Each subhead owns the rows under it, so it has to outrank them: the card
   title (19px) → group heading (15px) → person's name (13px) → email (11.5px).
   A rule closes the heading and the list indents beneath it, so a row reads as
   nested inside its group rather than as another item in a flat stack. */
.cal-share-group + .cal-share-group { margin-top: 22px; }
.cal-share-subhead {
  font-size: 15px; font-weight: 600; color: var(--text); letter-spacing: 0;
  margin: 0 0 10px; padding-bottom: 7px; border-bottom: 1px solid var(--border-light);
}
.cal-share-list { padding-left: 12px; }
/* Drag-preview ghost block. Sized to the source task's size (height comes
   from --week-cal-hint-h, set by weekCalDragOver). Sits at the snap-Y so
   the user previews exactly where + how big the task lands. The column
   itself doesn't get the inherited kanban dragover chrome (dashed
   outline + koi wash) — the ghost block alone is enough. */
.week-cal-col.kb-col-dragover {
  outline: none;
  background: transparent;
}
.week-cal-col-today.kb-col-dragover {
  background: rgba(200,57,46,0.025);   /* keep the faint today tint */
}
.week-cal-col.kb-col-dragover::before {
  content: '';
  position: absolute; left: 2px; right: 2px;
  top: var(--week-cal-hint-y, 0);
  height: var(--week-cal-hint-h, 28px);
  background: rgba(200,57,46,0.14);
  border-radius: 3px;
  pointer-events: none;
  transition: top 80ms ease, height 80ms ease;
}
.week-cal-hour-line {
  position: absolute; left: 0; right: 0;
  border-top: 1px solid var(--border-light);
  pointer-events: none;
}

/* Current-time indicator: a 2px accent line across today's column with a
   dot at its left edge. Theme-aware via --accent (our take, not Google's
   red). Sits above event/task blocks; never intercepts pointer/drag. */
.week-cal-now {
  position: absolute; left: 0; right: 0;
  border-top: 2px solid var(--accent);
  z-index: 5;
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .week-cal-now { transition: top 300ms ease; }
}
.week-cal-now-dot {
  position: absolute; left: -3px; top: -4px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--accent);
}

/* Event + task blocks: absolute-positioned, height = duration × hour-px.
   Inner layout is title + small time chip stacked, clipped at the bottom
   when a block is short. Cursor:grab on tasks signals they're draggable;
   events are pointer for the GCal link. */
.week-cal-event,
.week-cal-task {
  position: absolute;
  box-sizing: border-box;
  padding: 4px 6px;
  border-radius: 3px;
  font-size: 0.75em;
  line-height: 1.2;
  overflow: hidden;
  text-decoration: none;
  display: flex; flex-direction: column; gap: 2px;
  /* Eased so entering / leaving calm mode fades the desaturation in place
     (weekToggleCalm flips the class without a re-render). */
  transition: filter 260ms cubic-bezier(.2,.8,.2,1), opacity 260ms cubic-bezier(.2,.8,.2,1);
}
.week-cal-event {
  background: rgba(96,144,224,0.85);
  color: #fff;
  border-left: 3px solid rgba(96,144,224,1);
  cursor: pointer;
}
.week-cal-event:hover { background: rgba(96,144,224,1); }
/* Editable events become drag sources; the cursor telegraphs that. The
   lock variant keeps the block looking like a calendar item but signals
   it can't be moved (declined invites, shared calendars without write
   access). */
.week-cal-event-editable[draggable="true"] { cursor: grab; }
.week-cal-event-editable[draggable="true"]:active { cursor: grabbing; }
.week-cal-event-locked { opacity: 0.7; cursor: pointer; }
.week-cal-event-declined { opacity: 0.45; }
.week-cal-event-declined .week-cal-block-title { text-decoration: line-through; }
.week-cal-event-lock {
  margin-right: 4px;
  font-size: 0.85em;
  opacity: 0.85;
}
.week-cal-task {
  background: var(--accent);
  border: 1px solid var(--accent);
  border-left: 3px solid var(--accent-hover);
  color: var(--bg);
  cursor: grab;
  box-shadow: 0 1px 0 rgba(40,32,18,0.08);
}
.week-cal-task:hover { box-shadow: 0 2px 8px rgba(40,32,18,0.12); }
.week-cal-task[draggable="true"]:active { cursor: grabbing; }
/* Optimistic block: on the grid the instant you hit Create, before the task +
   calendar event exist. Dashed edge + a slow opacity breath says "landing",
   so nobody re-clicks Create wondering if it stuck. Static under
   prefers-reduced-motion — the dashed edge alone carries the state. */
.week-cal-task-pending {
  border-style: dashed;
  border-left-style: solid;
  cursor: progress;
  animation: week-cal-pending-breath 1.4s ease-in-out infinite;
}
.week-cal-task-pending:hover { box-shadow: 0 1px 0 rgba(40,32,18,0.08); }
@keyframes week-cal-pending-breath {
  0%, 100% { opacity: 0.62; }
  50%      { opacity: 0.92; }
}
@media (prefers-reduced-motion: reduce) {
  .week-cal-task-pending { animation: none; opacity: 0.7; }
}
.week-cal-task.kb-card-overdue {
  background: var(--danger);
  border-color: var(--danger);
  border-left-color: var(--danger);
}
/* Blocked block on the Planner grid: the fill is whatever the category set
   (inline), so the state rides on a dashed edge, a faded fill and a ⛔ in the
   title rather than on colour. Toggled in place by _repaintBlocked. */
.week-cal-task.kb-card-blocked { border-style: dashed; opacity: 0.72; }
.week-cal-task.kb-card-blocked .week-cal-block-title::before { content: '⛔ '; }
.week-cal-block-title {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Only a block with a spare line to give (1h and up) lets the title wrap —
   at 45 min a two-line title pushes the time out of the box and gets sliced
   mid-glyph by the block's own overflow. */
.week-cal-block-roomy .week-cal-block-title {
  white-space: normal;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow-wrap: anywhere;
}
.week-cal-block-time {
  font-family: var(--font-ui, 'JetBrains Mono', monospace);
  font-size: 0.85em;
  opacity: 0.85;
  letter-spacing: 0.02em;
  flex: 0 0 auto;
}
/* ── Short-block density ───────────────────────────────────────────────────
   A 30-min block has room for exactly one line of text, so the stacked layout
   spends it on the clock and clips the title away entirely. Below 34px the two
   share a row; below 26px (15-min blocks) only the title survives and the clock
   falls back to the tooltip. Set by _weekCalDensity() at render time. */
.week-cal-block-tight {
  flex-direction: row;
  align-items: baseline;
  gap: 5px;
  padding-top: 3px;
  padding-bottom: 3px;
}
/* On one row the title yields first — the clock is 6 characters and fixed, the
   title has an ellipsis to fall back on. */
.week-cal-block-tight .week-cal-block-title {
  min-width: 0;
  flex: 0 1 auto;
}
.week-cal-block-tiny {
  padding-top: 3px;
  padding-bottom: 3px;
  justify-content: center;
}
/* Always rendered so an in-place resize back up restores the clock without a
   re-render; hidden here where there is genuinely no room for it. */
.week-cal-block-tiny .week-cal-block-time { display: none; }
/* The resize grip's pill would sit on top of a single line of text at these
   heights; the block is still resizable, the hint just stops competing. */
.week-cal-block-tiny .week-cal-resize::after,
.week-cal-block-tight .week-cal-resize::after { opacity: 0; }
.week-cal-block-tiny:hover .week-cal-resize::after,
.week-cal-block-tight:hover .week-cal-resize::after { opacity: 0.75; }
/* gtd_done project tag on an external event block (only shown when annotated).
   Clipped by the block's overflow on short events. */
.week-cal-block-proj {
  font-size: 0.8em;
  opacity: 0.75;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Bottom-edge resize handle. Spans the block's full width along its lower
   border; the grab zone is a few px tall so it doesn't fight the block's own
   click. A faint pill fades in on hover to telegraph the affordance. */
.week-cal-resize {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 11px;
  cursor: ns-resize;
  z-index: 4;
}
.week-cal-resize::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 1px;
  transform: translateX(-50%);
  width: 28px;
  height: 3px;
  border-radius: 2px;
  background: currentColor;
  opacity: 0.3;                 /* faintly visible by default so it's discoverable */
  transition: opacity 0.12s ease;
}
.week-cal-event:hover .week-cal-resize::after,
.week-cal-task:hover .week-cal-resize::after { opacity: 0.75; }
/* While actively resizing, lock the cursor and kill text selection globally. */
body.week-cal-resizing { cursor: ns-resize; user-select: none; }
/* No edge-resize on touch — the strip would otherwise eat taps meant to open
   the block. Duration there stays driven by the size chip. */
@media (max-width: 700px) { .week-cal-resize { display: none; } }

/* "Syncing to Google" cue — shown on an event block while a move/resize write
   is in flight upstream. A small spinner pinned to the block's top-right corner,
   plus a faint pulse on the whole block so the state reads even on tiny blocks. */
.week-cal-syncing { animation: week-cal-sync-pulse 1.1s ease-in-out infinite; }
.week-cal-sync-badge {
  position: absolute;
  top: 3px; right: 3px;
  width: 12px; height: 12px;
  z-index: 5;
  pointer-events: none;
}
.week-cal-sync-spin {
  display: block;
  width: 100%; height: 100%;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  opacity: 0.85;
  animation: week-cal-sync-rotate 0.7s linear infinite;
}
@keyframes week-cal-sync-rotate { to { transform: rotate(360deg); } }
@keyframes week-cal-sync-pulse { 50% { opacity: 0.6; } }

/* Enrichment chip row in the event modal. */
.cal-evt-enrich .kb-card-chips { margin: 0; }
/* Colour-category swatch shown in chips + popover items. */
.cat-swatch {
  display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  margin-right: 5px; vertical-align: baseline;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.18);
}
/* Parallel read-only native Google colour under the category chip. */
.cal-evt-gcolor {
  margin-top: 6px; font-size: 0.78em; color: var(--text-dim);
  display: flex; align-items: center; gap: 2px;
}
/* Always-visible category dot on a kanban card face, before the title. */
.cat-swatch-card { width: 8px; height: 8px; margin-right: 6px; }
/* Container for the category chip in the task detail rail. */
.kb-detail-chiprow { display: flex; flex-wrap: wrap; gap: 6px; }

/* Theme variants for blocks. The default greens/blues above are tuned for
   dark; light theme softens them so they don't shout on parchment. */
[data-theme="light"] .week-cal-event {
  background: rgba(76,89,99,0.15);     /* dust — muted steel, warm-harmonised */
  color: var(--text);
  border-left-color: #4c5963;          /* dust-700 */
}
[data-theme="light"] .week-cal-event:hover { background: rgba(76,89,99,0.24); }
[data-theme="light"] .week-cal-allday {
  background: rgba(76,89,99,0.12);
  color: var(--text-secondary);
}
[data-theme="light"] .week-cal-task {
  background: var(--accent);
  color: #fcf9f3;                      /* paper */
}
[data-theme="modernist"] .week-cal-event {
  background: rgba(37,99,235,0.15);
  color: var(--text);
  border-left-color: #2563eb;
}
[data-theme="modernist"] .week-cal-allday {
  background: rgba(37,99,235,0.12);
  color: var(--text-secondary);
}
[data-theme="modernist"] .week-cal-task {
  background: #2563eb;
  color: #ffffff;
  border-color: #2563eb;
  border-left-color: #1d4ed8;
}

/* Drag-to-create: the drawn block (ghost) + its inline title popover. The
   ghost mirrors the drag-over preview; the popover anchors at the block top
   and sits above everything so it stays usable even on a short block. */
.week-cal-create-ghost {
  position: absolute;
  left: 2px; right: 2px;
  background: rgba(200,57,46,0.15);
  border: 1px dashed var(--accent);
  border-radius: 4px;
  pointer-events: none;
  z-index: 3;
}
.week-cal-create-anchor {
  position: absolute;
  left: 2px; right: 2px;
  z-index: 20;
}
.week-cal-create-pop {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px;
  box-shadow: 0 4px 14px rgba(0,0,0,0.35);
  cursor: default;
}
/* Live readout of the span being drawn, pinned to the ghost's top edge and
   flipped below it near the top of the grid. Solid chip rather than a tint —
   it crosses hour lines and whatever blocks it passes over. */
.week-cal-create-time {
  position: absolute;
  left: 2px;
  transform: translateY(-100%);
  margin-top: -3px;
  z-index: 21;
  pointer-events: none;
  white-space: nowrap;
  /* Inverted chip, not accent-on-bg: --accent is a blue on Modernist and a
     deep red on the default, so no single ink reads on all of them.
     --text on --bg is by definition the theme's own body-contrast pair. */
  background: var(--text);
  color: var(--bg);
  font-family: var(--font-ui, 'JetBrains Mono', monospace);
  font-size: 0.68rem;
  line-height: 1;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  padding: 4px 7px;
  border-radius: 4px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.28);
}
.week-cal-create-time-below {
  transform: none;
  margin-top: 3px;
}
.week-cal-create-dur { opacity: 0.72; margin-left: 3px; }
/* Same span, held in the naming popover once the pointer readout is gone. */
.week-cal-create-when {
  font-family: var(--font-ui, 'JetBrains Mono', monospace);
  font-size: 0.7rem;
  line-height: 1;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  margin-bottom: 6px;
}
.week-cal-create-when .week-cal-create-dur { color: var(--text-muted); opacity: 1; }
.week-cal-create-input {
  width: 100%;
  margin-bottom: 6px;
  font-size: 12px;
}
.week-cal-create-actions {
  display: flex;
  gap: 6px;
}
.week-cal-create-actions .kb-action { flex: 1; }

/* Mobile column-switcher tabs. Pill bar sits above .kb-board-kanban and
   is hidden on desktop. Horizontally scrollable in case the column-name +
   count pills don't all fit on small screens. */
.kb-tabs {
  display: none; /* shown by the narrow-viewport media query */
  gap: 6px;
  margin-bottom: 12px;
  overflow-x: auto;
  scrollbar-width: thin;
  padding-bottom: 4px;
  -webkit-overflow-scrolling: touch;
}
.kb-tab {
  flex: 0 0 auto;
  background: transparent; border: 1px solid var(--border);
  color: var(--text-muted); padding: 10px 14px;
  font-family: inherit; font-size: 0.75em; letter-spacing: 2px;
  cursor: pointer; white-space: nowrap;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.kb-tab:hover { color: var(--text); border-color: var(--text-muted); }
.kb-tab-active {
  color: var(--accent); border-color: var(--accent);
  background: var(--bg-elevated);
}
.kb-tab-count {
  display: inline-block; margin-left: 8px;
  color: var(--text-muted); font-size: 0.95em; letter-spacing: 0;
}
.kb-tab-active .kb-tab-count { color: var(--accent); }
.kb-col-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
  color: var(--text); font-size: 0.75em; letter-spacing: 3px;
  /* Title sits in --text now, not --accent. The koi was carrying every
     column header at full volume; under the Sumi rule it's reserved
     for active state / overdue / brand. The JP gloss below adds back
     the colour interest without competing for attention. */
}
.kb-col-count {
  color: var(--text-dim); font-size: 1em; letter-spacing: 0;
  /* Count is plain text now — pill chrome removed. */
}

/* Japanese column gloss. The English header inherits the column name
   from app.js (`INBOX`, `SCHEDULED`, etc.); we append the matching JP
   character in the mincho stack via ::after, keyed off the data-col
   attribute that _dropAttrs writes onto each .kb-col wrapper. Gloss
   sits dimmer + larger than the English so it reads as ornament. */
.kb-col[data-col="inbox"]     .kb-col-header > span:first-child::after { content: " 受信";   }
.kb-col[data-col="in_progress"] .kb-col-header > span:first-child::after { content: " 進行中"; }
.kb-col[data-col="scheduled"] .kb-col-header > span:first-child::after { content: " 予定";   }
.kb-col[data-col="done"]      .kb-col-header > span:first-child::after { content: " 完了";   }
.kb-col[data-col="someday"]   .kb-col-header > span:first-child::after { content: " いつか"; }
.kb-col[data-col="trashed"]   .kb-col-header > span:first-child::after { content: " ゴミ箱"; }
.kb-col[data-col] .kb-col-header > span:first-child::after {
  font-family: var(--jp);
  font-size: 1.05em;            /* slightly larger than the tracking-out English */
  letter-spacing: 0.08em;
  color: var(--text-dim);
  margin-left: 6px;
  font-weight: 400;
}

/* Direction 1 — Editorial Sumi column headers.
   Light theme only. The header becomes a wrap-friendly flex row that
   carries title + count on row 1, and a subtitle injected via ::after
   on row 2. Subtitle text comes from --col-sub per data-col. Title gets
   the Fraunces serif treatment; kanji gloss sits inline next to it.
   Dark theme keeps the tracked-caps mono header unchanged. */
[data-theme="light"] .kb-col-header {
  flex-wrap: wrap;
  align-items: baseline;
  padding: 4px 4px 10px;
  border-bottom: none;
  font-size: 1em;
  letter-spacing: 0;
}
[data-theme="light"] .kb-col-header > span:first-child {
  font-family: var(--serif-display);
  font-weight: 500;
  font-size: 22px;
  letter-spacing: -0.01em;
  color: var(--text);
  text-transform: none;
}
[data-theme="light"] .kb-col[data-col] .kb-col-header > span:first-child::after {
  font-size: 0.6em;
  color: var(--text-muted);
}
[data-theme="light"] .kb-col-header::after {
  content: var(--col-sub, "");
  flex-basis: 100%;
  margin-top: 4px;
  font-family: var(--font-ui, 'JetBrains Mono', 'Fira Code', monospace);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
[data-theme="light"] .kb-col[data-col="inbox"]     { --col-sub: "Capture · capture. Decide later."; }
[data-theme="light"] .kb-col[data-col="in_progress"] { --col-sub: "Do · in flight now."; }
[data-theme="light"] .kb-col[data-col="scheduled"] { --col-sub: "Plan · on the calendar."; }
[data-theme="light"] .kb-col[data-col="done"]      { --col-sub: "Ship · finished this week."; }
[data-theme="light"] .kb-col[data-col="someday"]   { --col-sub: "Dream · maybe one day."; }
[data-theme="light"] .kb-col[data-col="trashed"]   { --col-sub: "Archive · restorable for 30 days."; }

/* Card title gets the serif in light theme. The mono stays everywhere
   else so meta, chips, and counts keep their technical precision. */
[data-theme="light"] .kb-card-text {
  font-family: var(--serif-display);
  font-weight: 500;
  font-size: 14px;            /* Figtree's larger x-height reads bigger than the serif this replaced */
  letter-spacing: -0.005em;
  line-height: 1.3;
}
.kb-col-body {
  flex: 1; overflow-y: auto; padding: 8px;
  display: flex; flex-direction: column; gap: 8px;
}
.kb-empty {
  color: var(--text-dim); font-size: 0.85em; text-align: center;
  padding: 24px 8px;
}

/* "More" pager at the foot of the Done column. Quiet by default — a dashed,
   full-width row that reads as a continuation affordance, not a primary
   action; the koi only surfaces on hover. */
.kb-col-more {
  display: block; width: 100%;
  margin-top: 8px; padding: 8px;
  background: transparent;
  border: 1px dashed var(--border); border-radius: 8px;
  color: var(--text-muted);
  font: inherit; font-size: 0.78em; letter-spacing: 0.04em;
  text-align: center; cursor: pointer;
  transition: color 160ms ease, border-color 160ms ease;
}
.kb-col-more:hover:not(:disabled) { color: var(--accent); border-color: var(--text-dim); }
.kb-col-more:disabled { color: var(--text-dim); cursor: progress; }

/* Quiet capture row — sits at the top of the Inbox column. Closed state is a
   barely-there "+" that opens into a real input on click. The row eats far
   less vertical space than the old wide capture bar so cards get more room. */
.kb-capture-row {
  width: 100%; box-sizing: border-box;
  font: inherit; color: inherit;
  background: transparent; border: 0; outline: 0;
  padding: 0;
}
.kb-capture-closed {
  display: block; position: relative;
  background: transparent;          /* empty-slot — page bg shows through */
  border: 1px dashed var(--border); /* slightly more present than dotted at this scale */
  color: var(--text-dim); font-size: 1.1em; line-height: 1;
  padding: 10px 12px;
  text-align: center;
  cursor: pointer;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.kb-capture-closed:hover,
.kb-capture-closed:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--bg-elevated);   /* fills toward card-state so the click feels pre-committed */
}
.kb-capture-input {
  width: 100%; box-sizing: border-box;
  background: var(--bg-input);
  border: 1px solid var(--accent);
  color: var(--text);
  font-family: inherit; font-size: 0.9em;
  padding: 6px 10px;
  outline: none;
}
.kb-capture-input::placeholder { color: var(--text-dim); }

/* Cards. Sumi card chrome: solid 1px border in --border, a faint inset
   top highlight (--card-top) catching imaginary overhead light, and a
   soft drop (--card-shadow) so the card reads as resting on the board.
   Sits inside the same warm palette as everything else — no orange
   theatrics. The dotted-hairline + transparent-bg pattern of the old
   theme was the "lots of lines" complaint; this is the answer.
   v7 layout: two-column flex — .kb-card-body on the left holds title,
   assignee-at-rest, and the .kb-card-hover-content reveal layer; the
   .kb-card-boxes stack lives on the right, anchored top, fixed-width
   so it doesn't shift when the body expands on hover. */
.kb-card {
  border: 1px solid var(--kb-card-border, var(--border));
  background: var(--kb-card-bg, var(--bg-elevated));
  padding: 10px 12px;
  position: relative;
  box-shadow: inset 0 1px 0 var(--card-top), var(--card-shadow);
  transition: border-color 240ms cubic-bezier(.2,.8,.2,1), box-shadow 240ms cubic-bezier(.2,.8,.2,1);
  display: flex;
  gap: 12px;
  align-items: flex-start;
}
.kb-card:hover {
  border-color: var(--text-dim);   /* warm-grey lift; vermillion stays reserved */
}
.kb-card-body {
  flex: 1 1 0;
  min-width: 0;
  /* Hard cap so a long unbreakable title can't push past the body's flex
     share and overlap the energy box stack on the right. */
  max-width: 100%;
  overflow: hidden;
}

/* Vertical 5-box energy/effort stack on the right of each card.
   column-reverse packs filled boxes at the bottom (fuel-gauge feel —
   bigger effort = taller stack). The whole column wears one colour
   via data-energy; empty boxes share that hue as a border so the
   strip reads as one element. align-self: flex-start anchors the
   stack to the card's top so it doesn't move when the body expands. */
.kb-card-boxes {
  display: flex;
  flex-direction: column-reverse;
  gap: 3px;
  flex-shrink: 0;
  align-self: flex-start;
  padding-top: 2px;
  cursor: pointer;              /* clickable surface — clicks bubble to card → opens drawer */
}
.kb-card-boxes span {
  width: 7px; height: 7px;
  background: transparent;
  border: 1px solid var(--energy-med);
  border-radius: 1px;
}
.kb-card-boxes span.on { background: var(--energy-med); }
.kb-card-boxes[data-energy="high"] span    { border-color: var(--energy-high); }
.kb-card-boxes[data-energy="high"] span.on { background: var(--energy-high); }
.kb-card-boxes[data-energy="low"] span     { border-color: var(--energy-low); }
.kb-card-boxes[data-energy="low"] span.on  { background: var(--energy-low); }

/* Box-stack tooltip. Singleton element appended to <body> by the
 * mousemove handler in app.js — lives outside the card / column /
 * flex tree entirely so its layout is never affected by overflow,
 * transforms, or stacking-context tricks elsewhere.
 * Positioning: bubble's bottom-right corner sits a touch above-and-
 * past the cursor X, so the downward arrow on the right side of
 * the bubble lands directly above the cursor. Above-and-left of the
 * cursor as a whole. */
.kb-tip {
  position: fixed;
  left: 0; top: 0;
  transform: translate(calc(-100% + 14px), calc(-100% - 10px));
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  color: var(--text-secondary);
  font-family: var(--mono, inherit);
  font-size: 11px;
  line-height: 1.3;
  letter-spacing: 0.02em;
  padding: 6px 10px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 100ms ease;
  box-shadow: inset 0 1px 0 var(--card-top), 0 4px 14px rgba(0,0,0,0.32);
  z-index: 2147483647;
}
.kb-tip.kb-tip-show { opacity: 1; }
.kb-tip strong { color: var(--text); font-weight: 500; }
.kb-tip-energy[data-energy="low"]    { color: var(--success); }
.kb-tip-energy[data-energy="medium"] { color: var(--warning); }
.kb-tip-energy[data-energy="high"]   { color: var(--danger); }

/* Speech-bubble tail — simple downward triangle on the right side
 * of the bubble, just inside the corner so it visually "hangs" from
 * the bottom edge pointing at the cursor below. Two pseudo elements:
 * the OUTER triangle in --border colour, the INNER offset 1px so
 * the border continues along the tail's slanted edges. */
.kb-tip::before,
.kb-tip::after {
  content: '';
  position: absolute;
  width: 0; height: 0;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
}
.kb-tip::before {
  top: 100%;
  right: 8px;
  border-top: 7px solid var(--border);   /* outer — provides the slanted border */
}
.kb-tip::after {
  top: calc(100% - 1px);                  /* 1px up so the inner fills inside the outer */
  right: 9px;
  border-top: 6px solid var(--bg-elevated);
}

/* Quick-actions drawer. Chips, waiting banner, and the actions row live
   inside .kb-card-hover-content. The card is fully static at rest — it
   never reacts to hover. On drawer cards (.kb-card-has-drawer) the panel
   is a full-width wrapped flex line at the card's base: negative inline
   margins cancel the card's horizontal padding so it bleeds to both edges,
   and the card drops its bottom padding when open so the drawer meets the
   bottom border. Tinted with --bg (the surface *beneath* the card) + a top
   inset shadow, with the toggle lip casting a shadow down onto it, so it
   reads as sliding out from UNDER the card — not opening inside it. The
   base rule is just the shared collapse mechanics; the week-view inbox
   renders the panel inline + always-open and overrides below. */
/* Drawer cards lay out as a wrapping column: title+boxes on the first line,
   the toggle lip and the full-bleed drawer wrap onto their own lines below.
   --kb-px is the card's horizontal padding, reused by the lip + drawer to
   cancel it and bleed edge-to-edge. */
.kb-card-has-drawer {
  flex-wrap: wrap;
  row-gap: 0;
  --kb-px: 12px;
  /* No card bottom padding — the toggle's own 8px bottom padding is the only
     spacing below "Quick actions" (closed) and above the drawer (open), so it
     stays even with the 8px above the toggle text. */
  padding-bottom: 0;
  transition: border-color 240ms cubic-bezier(.2,.8,.2,1),
              box-shadow 240ms cubic-bezier(.2,.8,.2,1);
}
/* Overdue stripe lives in the 2px left border; keep padding symmetric so the
   --kb-px the lip + drawer bleed against stays accurate. */
.kb-card-has-drawer.kb-card-overdue { padding-left: 12px; }

/* The drawer is the full-bleed recessed compartment (bg + inset lip shadow);
   the inner wrapper carries the padding + content. It collapses to a true 0
   when closed (the toggle is the last row), and kbToggleCardTab animates
   max-height to the panel's EXACT height on open so the ease maps 1:1 to the
   real motion — no arbitrary ceiling, no pre-collapse lag. */
.kb-card-has-drawer .kb-card-hover-content {
  flex: 0 0 auto;
  overflow: hidden;
  /* As a flex item this defaults to min-height:auto (= content height), which
     would override max-height:0 and leave the empty panel when closed. Pin it
     to 0 so the collapse actually takes. */
  min-height: 0;
  max-height: 0;
  /* Explicit width (not flex-basis:100%) + the ±1px bleeds the panel past the
     card's 1px side border, so the border frames only the face above. */
  width: calc(100% + 2 * var(--kb-px, 12px) + 2px);
  margin: 0 calc(-1 * var(--kb-px, 12px) - 1px);
  background: color-mix(in srgb, var(--bg-elevated) 55%, var(--bg) 45%);
  box-shadow: inset 0 8px 9px -8px rgba(0,0,0,0.4);
  /* Toggled cards get their slide timing set INLINE by kbToggleCardTab, per
     direction (snappy ease-out on open; gentle ease-in-out on close, with a
     reflow so the close actually animates instead of snapping). This base
     transition only governs the CSS-forced scheduling open below. */
  transition: max-height 340ms cubic-bezier(.22, 1, .36, 1);
}
/* Scheduling is a CSS-forced open (full re-render, no JS toggle) — give it a
   generous ceiling so the inline scheduler always fits. */
.kb-card-has-drawer.kb-card-scheduling .kb-card-hover-content {
  max-height: 500px;
}
.kb-card-drawer-inner {
  overflow: hidden;
  min-height: 0;
  padding: 12px calc(var(--kb-px, 12px) + 1px);
  /* No cross-fade — content stays opaque and is simply revealed/clipped by the
     sliding height, so open and close are clean slides, not slide+fade. */
}
@media (prefers-reduced-motion: reduce) {
  .kb-card-has-drawer,
  .kb-card-has-drawer .kb-card-hover-content,
  .kb-card-drawer-inner,
  .kb-card-tab-chev { transition: none; }
}
/* Week-view inbox cards render the panel inline + always-open (not a drawer
   card, so no grid accordion) — just the inline spacing. */
.kb-card-week .kb-card-hover-content {
  margin-top: 8px;
}
.kb-card-week .kb-card-text { cursor: pointer; }

/* Overdue: 2px koi left edge. Compensate padding so title baseline
   doesn't shift between overdue and on-time cards. */
.kb-card-overdue {
  border-left: 2px solid var(--accent);
  padding-left: 10px;   /* 12 default → 10 with the 2px stripe restoring the 12px effective gap */
}
/* Blocked: same 2px stripe slot as overdue (dashed, danger) so the two never
   fight over padding; blocked wins the colour when both apply. The title dims
   and takes a ⛔ mark so the state reads at rest, not just in the drawer. */
.kb-card.kb-card-blocked {
  border-left: 2px dashed var(--danger);
  padding-left: 10px;
}
.kb-card-blocked .kb-card-text { color: var(--text-muted); }
.kb-card-blocked .kb-card-text::before { content: '⛔'; margin-right: 4px; font-size: 0.8em; }
/* Scheduled-past: a scheduled task whose block has elapsed but isn't done yet.
   Small amber corner triangle, top-left, visible at rest. Decorative only —
   clicks fall through to the card (opens the task). */
.kb-card-past::after {
  content: "";
  position: absolute; top: 0; left: 0;
  border-top: 12px solid #d98a3d;
  border-right: 12px solid transparent;
  pointer-events: none;
  /* Follow the card's own corner. `inherit` reads .kb-card's computed radius,
     so the flag curves with whatever each theme sets — 8px on the camel board,
     6px in the week rail, square where a theme has none — instead of poking
     out past a rounded corner. */
  border-top-left-radius: inherit;
}
/* ::before colored edges retired (kept the rule shells dormant in case any
   per-status hooks are still referenced elsewhere; no visible output). */
.kb-card-needs-horizon { border-color: var(--danger); }

.kb-card-text {
  color: var(--text); font-size: 0.9em; line-height: 1.4;
  margin-bottom: 4px;
  /* `anywhere` + `break-word` together force long tokens (emails, URLs) to
     wrap at character boundaries when no whitespace break is available,
     so the title can't push past the body's flex share and overlap the
     vertical energy box stack on the right. */
  overflow-wrap: anywhere;
  word-break: break-word;
  hyphens: auto;
  min-width: 0;
  max-width: 100%;
}
/* Size+energy badge group, anchored to the card's top-right. Sits above the
 * title; the title's right padding (set per-column where needed) keeps long
 * titles from running underneath. */
.kb-card-badges-tr {
  position: absolute;
  top: 8px;
  right: 10px;
  font-size: 0.85em;
  line-height: 1;
  pointer-events: none;
}
/* v7: the scheduled-card right padding was reserving room for the old
   top-right badge slot (.kb-card-badges-tr). Boxes now live as a sibling
   flex column outside .kb-card-body, so no reserved space is needed. */
/* Done cards keep the card chrome but quiet down — drop the drop-shadow
 * (so they don't "float" like in-flight cards) and dim the text to muted.
 * Strikethrough stays reserved for trashed so the two states keep their
 * meaning distinct (done = completed work / trashed = removed).
 * v7: also hide the box stack, the assignee, and the hover content —
 * archival records don't need energy / ownership / actions. */
.kb-card-done {
  box-shadow: inset 0 1px 0 var(--card-top);  /* keep the inset, drop the float */
}
.kb-card-done .kb-card-text { color: var(--text-muted); }
.kb-card-done .kb-card-boxes,
.kb-card-done .kb-card-assignee,
.kb-card-done .kb-card-hover-content { display: none; }

.kb-card-trashed .kb-card-text { color: var(--text-muted); text-decoration: line-through; }
.kb-card-trashed .kb-card-boxes,
.kb-card-trashed .kb-card-assignee,
.kb-card-trashed .kb-card-hover-content { display: none; }
.kb-card-meta {
  color: var(--text-muted); font-size: 0.75em; line-height: 1.4;
  margin-bottom: 4px;
}

/* Assignee at rest — v7 pattern. Plain small text under the title,
 * not a bordered chip. The bullet (● / ○) lives in the markup and
 * inherits the text colour. Hidden on done / trashed cards (archival
 * records — ownership isn't useful any more). */
.kb-card-assignee {
  display: inline-block;
  margin: 4px 0 0;
  padding: 0;
  font-size: 0.7em; letter-spacing: 0;
  font-family: inherit;
  color: var(--text-muted);
  background: transparent;
  border: 0;
}
.kb-card-assignee-unassigned {
  color: var(--text-dim);
}
.kb-card-assignee-click { cursor: pointer; border-radius: 4px; padding: 1px 4px; margin-left: -4px; }
.kb-card-assignee-click:hover { color: var(--text); background: var(--bg-input); }

/* Inline assignee dropdown on raw inbox cards. Matches the .kb-action
 * button styling exactly so it reads as part of the card's action row
 * rather than a form field. Sits where the captured-at timestamp used
 * to live. */
.kb-inbox-assignee-select {
  display: inline-block;
  margin: 4px 0 6px;
  padding: 3px 7px;
  font-size: 0.7em; letter-spacing: 0;
  font-family: inherit;
  color: var(--text-muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 0;
  cursor: pointer;
  max-width: 100%;
  transition: all 0.15s;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  /* small caret rendered as a background image so the select looks like
     the surrounding buttons but still hints at being a dropdown */
  padding-right: 18px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 9px) 55%, calc(100% - 5px) 55%;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}
.kb-inbox-assignee-select:hover { color: var(--text); border-color: var(--text-muted); }
.kb-inbox-assignee-select:focus { outline: 1px solid var(--accent); outline-offset: 1px; }

/* Actions container is now a column of two rows — paired by intent.
 * Row 1 = in-flight ops (Schedule / Reschedule / Auto-reschedule /
 * Someday / Inbox). Row 2 = terminal ops (Done / Trash). The 10px
 * gap between rows reads as deliberate semantic separation, not
 * accidental stacking. */
/* "⚡ Quick actions" toggle — the static lip at the base of a drawer card.
   Full-bleed (negative inline margins cancel the card padding) with a dotted
   hairline on top, same language as the card's other dividers. It holds its
   position; the drawer slides out from under it. When open it gains a soft
   downward shadow so it reads as the card edge the drawer is tucked beneath.
   Hidden where there's nothing to toggle (week / scheduling / done / trashed). */
.kb-card-has-drawer .kb-card-tab {
  flex: 0 0 auto;
  width: calc(100% + 2 * var(--kb-px, 12px) + 2px);
  display: flex; align-items: center; justify-content: space-between;
  position: relative; z-index: 1;
  margin: 8px calc(-1 * var(--kb-px, 12px) - 1px) 0;
  /* symmetric vertical padding so the label keeps its own air — the drawer
     starts below the lip, never crowding the words. */
  padding: 8px calc(var(--kb-px, 12px) + 1px);
  border: none; border-top: 1px dotted var(--border);
  background: transparent; cursor: pointer;
  color: var(--text-dim); font-family: inherit; font-size: 0.7em;
  letter-spacing: 0.03em; transition: color 0.15s;
}
.kb-card-has-drawer .kb-card-tab:hover { color: var(--text-secondary); }
.kb-card-has-drawer .kb-card-tab:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }
.kb-card-has-drawer.kb-card-open .kb-card-tab {
  color: var(--text-secondary);
  box-shadow: 0 4px 6px -4px rgba(0,0,0,0.45);   /* lip casts onto the open drawer */
}
/* SVG chevron — flex-centred (the old ⌄/⌃ glyphs sat off-centre) and rotated
   180° on open so the flip animates instead of swapping characters. */
.kb-card-tab-chev {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform 240ms cubic-bezier(.2,.8,.2,1);
}
.kb-card-has-drawer.kb-card-open .kb-card-tab-chev { transform: rotate(180deg); }
.kb-card-week .kb-card-tab,
.kb-card-scheduling .kb-card-tab,
.kb-card-done .kb-card-tab,
.kb-card-trashed .kb-card-tab { display: none; }

.kb-card-actions {
  display: flex; flex-direction: column; gap: 10px; margin-top: 8px;
}
.kb-card-actions-row {
  display: flex; flex-wrap: wrap; gap: 14px;
}
/* Action buttons render as text links — no border, no padding. The orange
   on hover is reserved for here and the column wake-up; cards stay grey. */
.kb-action {
  background: transparent; border: none;
  color: var(--text-secondary); padding: 0; font-size: 0.7em;
  font-family: inherit; letter-spacing: 0; cursor: pointer;
  transition: color 0.15s;
}
.kb-action:hover { color: var(--accent); }
.kb-action:disabled, .kb-action[disabled] {
  opacity: 0.55; cursor: progress; pointer-events: none;
}
.kb-event-link {
  display: inline-block; margin-left: 6px; font-size: 0.85em;
  text-decoration: none; opacity: 0.65; transition: opacity 0.15s;
}
.kb-event-link:hover { opacity: 1; }
.kb-action-success:hover { color: var(--success); }
.kb-action-danger { color: var(--text-dim); padding: 0; }
.kb-action-danger:hover { color: var(--danger); }
.kb-action-quiet {
  color: var(--text-dim); padding: 0;
  font-size: 0.65em; opacity: 0.7;
}
.kb-action-quiet:hover {
  color: var(--warning); opacity: 1;
}

/* Hidden on desktop — these duplicate column-move actions that drag covers.
   Re-shown at the same breakpoint the kanban switches to single-column tabs. */
.kb-action-drag-only { display: none; }
@media (max-width: 900px) {
  .kb-action-drag-only { display: inline-block; }
}
.kb-card-actions-secondary { margin-top: 4px; }

/* Locked actions row — task is assigned to someone other than the current
 * user. Children are visually disabled and unclickable. The wrapper retains
 * pointer-events so its `title` tooltip surfaces on hover, explaining why.
 * Card click → edit form is intentionally still allowed (handled at the
 * card level) so the user can reassign as the unblock path. */
.kb-card-actions-locked .kb-action,
.kb-card-actions-locked .kb-action-success,
.kb-card-actions-locked .kb-action-danger,
.kb-card-actions-locked .kb-action-quiet {
  opacity: 0.4;
  pointer-events: none;
  cursor: not-allowed;
}

/* Drag-and-drop states */
.kb-card[draggable="true"] { cursor: grab; }
.kb-card[draggable="true"]:active { cursor: grabbing; }
.kb-card-dragging { opacity: 0.4; }
.kb-col { transition: background 0.15s, opacity 0.18s ease; }
.kb-col-dragover {
  background: rgba(200, 57, 46, 0.06);   /* koi-tinted drop wash */
  outline: 2px dashed var(--accent); outline-offset: -2px;
}

/* Desktop column dulling — at rest each column sits at 0.7 opacity, the column
   under the mouse restores to 1.0. Touch/mobile keeps everything full strength
   since hover is not a usable state. The (hover:hover)+(pointer:fine) guard
   excludes touch devices and pen input. Drag-over forces full opacity so the
   destination column stays clearly readable while you're aiming a drop. */
@media (hover: hover) and (pointer: fine) {
  .kb-col { opacity: 0.7; }
  .kb-col:hover,
  .kb-col-dragover { opacity: 1; }
}
/* Ghost card placeholder — shown in Scheduled while picking a time after drop. */
.kb-card-ghost {
  border-style: dashed !important;
  background: rgba(200, 57, 46, 0.04);   /* koi-tinted ghost — matches drop wash */
  box-shadow: none;                       /* ghost doesn't get the resting card lift */
  animation: ghost-fade-in 0.15s ease-out;
}
.kb-card-meta-ghost {
  color: var(--accent) !important; font-size: 0.7em; letter-spacing: 1.5px;
  margin-top: 2px;
}
@keyframes kb-card-arrive {
  from { opacity: 0; transform: translateY(-10px); }
  to   { opacity: 1; transform: translateY(0); }
}
.kb-card-arriving { animation: kb-card-arrive 0.28s ease-out; }

@keyframes kb-card-leave {
  to { opacity: 0; transform: translateY(-4px); }
}
.kb-card-leaving {
  animation: kb-card-leave 0.16s ease-in forwards;
  pointer-events: none;
}

@keyframes kb-pulse-dim {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}
.kb-action-pulsing { animation: kb-pulse-dim 1.1s ease-in-out infinite; }

@keyframes ghost-fade-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Inline scheduler panel — appears inside a card when scheduling */
.kb-scheduler {
  margin-top: 8px; padding: 10px; background: var(--bg-elevated);
  border: 1px solid var(--border); display: flex; flex-direction: column; gap: 8px;
}
.kb-scheduler-label {
  color: var(--text-muted); font-size: 0.7em; letter-spacing: 2px;
}
.kb-scheduler-input {
  background: var(--bg-input); border: 1px solid var(--border);
  color: var(--text); padding: 6px 8px; font-family: inherit; font-size: 0.85em;
}
.kb-scheduler-input:focus { outline: none; border-color: var(--accent); }
.kb-scheduler-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.kb-chip-mini {
  background: transparent; border: 1px solid var(--border); color: var(--text-muted);
  padding: 3px 8px; font-size: 0.7em; font-family: inherit; cursor: pointer;
  transition: all 0.15s;
}
.kb-chip-mini:hover { color: var(--accent); border-color: var(--accent); }
/* Wraps because Autoschedule sits alongside Schedule and Cancel, and three
   actions don't fit a 200px column on one line. */
.kb-scheduler-actions { display: flex; flex-wrap: wrap; gap: 6px; }

/* Inline edit form — appears inside a card when the user clicks it */
.kb-edit-form {
  display: flex; flex-direction: column; gap: 8px;
  margin-top: 4px; padding: 10px;
  background: var(--bg-elevated); border: 1px solid var(--border);
}

/* Open animation. The card itself is a fresh DOM node after re-render,
 * so we can't smoothly transition height — instead the form fades and
 * slides down, and rows cascade a few ms behind so it reads as the card
 * unfolding rather than blinking into place. */
@keyframes kb-edit-form-open {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.kb-edit-form {
  transform-origin: top center;
  animation: kb-edit-form-open 0.18s ease-out;
}
@keyframes kb-edit-row-in {
  from { opacity: 0; transform: translateY(-3px); }
  to   { opacity: 1; transform: translateY(0); }
}
.kb-edit-form > .kb-edit-row,
.kb-edit-form > .kb-edit-grid,
.kb-edit-form > .kb-edit-actions {
  animation: kb-edit-row-in 0.22s ease-out backwards;
}
.kb-edit-form > *:nth-child(1) { animation-delay: 0.06s; }
.kb-edit-form > *:nth-child(2) { animation-delay: 0.09s; }
.kb-edit-form > *:nth-child(3) { animation-delay: 0.12s; }
.kb-edit-form > *:nth-child(4) { animation-delay: 0.15s; }
.kb-edit-form > *:nth-child(5) { animation-delay: 0.18s; }
.kb-edit-form > *:nth-child(6) { animation-delay: 0.21s; }
.kb-edit-form > *:nth-child(7) { animation-delay: 0.24s; }
.kb-edit-form > *:nth-child(8) { animation-delay: 0.27s; }

/* Close animation. The form fades AND collapses its height/padding/margin
 * to zero so the column reflows smoothly during the close — without that,
 * the column would hold the form's full height until the moment of
 * re-render and then snap down in one frame. JS sets max-height to the
 * form's measured scrollHeight inline before adding this class, giving
 * the keyframe a real from-value to transition down from. `forwards`
 * keeps the form collapsed once the animation lands so there's no
 * flicker before the re-render swaps the DOM. */
@keyframes kb-edit-form-close {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
    max-height: 0;
    padding-top: 0;
    padding-bottom: 0;
    margin-top: 0;
    border-top-width: 0;
    border-bottom-width: 0;
  }
}
.kb-edit-form-closing {
  overflow: hidden;
  animation: kb-edit-form-close 0.22s cubic-bezier(0.4, 0, 0.2, 1) forwards !important;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .kb-edit-form,
  .kb-edit-form > .kb-edit-row,
  .kb-edit-form > .kb-edit-grid,
  .kb-edit-form > .kb-edit-actions,
  .kb-edit-form-closing { animation: none; }
}
.kb-edit-row { display: flex; flex-direction: column; gap: 3px; }
.kb-edit-label {
  color: var(--text-muted); font-size: 0.65em; letter-spacing: 1.5px;
}
.kb-edit-textarea { resize: vertical; min-height: 36px; font-family: inherit; }
.kb-edit-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
}
.kb-edit-form .input {
  background: var(--bg-input); border: 1px solid var(--border);
  color: var(--text); padding: 5px 7px;
  font-family: inherit; font-size: 0.85em; width: 100%;
}
.kb-edit-form .input:focus { outline: none; border-color: var(--accent); }
.kb-edit-actions {
  display: flex; gap: 6px; margin-top: 4px;
  border-top: 1px solid var(--border); padding-top: 8px;
}

.kb-progress {
  color: var(--text-muted); font-size: 0.75em; letter-spacing: 1px;
  margin: 4px 0;
}

/* Status columns on the Projects board (Someday / On hold / Completed /
   Archived) sit after the horizons; a quieter header keeps the eye on the
   time-bounded columns, which are the point of the board. */
.kb-col-status .kb-col-header { color: var(--text-muted); }

/* Editable title on the project detail page — same affordance pattern as the
   seamless task title: faint bg shift on hover so it reads as clickable. */
.pj-title-editable {
  cursor: pointer; padding: 2px 6px; margin-left: -6px;
  transition: background-color 0.15s;
}
.pj-title-editable:hover { background: var(--bg-input); }
.pj-title-editable:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
/* The field it swaps to. Sized to sit in the same space as the text it
   replaces, so committing an edit doesn't shift the header. */
.pj-title-input {
  font-size: 1.6em; line-height: 1.2; font-weight: inherit;
  width: 100%; padding: 2px 6px; margin-left: -6px;
}
/* The goal is always a textarea, styled to read as a paragraph: no box, grows
   with its content (_pjGrowGoal), placeholder is the empty state. */
.pj-goal-input {
  display: block; width: calc(100% + 12px); margin-left: -6px; padding: 2px 6px;
  resize: none; overflow: hidden; min-height: 0;
  font: inherit; font-size: 0.95em; line-height: 1.5; color: var(--text-secondary);
  background: transparent; border: none; border-radius: 3px;
  transition: background-color 0.15s;
}
.pj-goal-input:hover, .pj-goal-input:focus { background: var(--bg-input); }
.pj-goal-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.pj-goal-input::placeholder { color: var(--text-dim); font-style: italic; opacity: 1; }

.kb-orphans {
  border: 1px dashed var(--danger); padding: 12px;
  margin-bottom: 16px;
  display: flex; flex-direction: column; gap: 8px;
}
.kb-orphans-label {
  color: var(--danger); font-size: 0.75em; letter-spacing: 2px;
  margin-bottom: 4px;
}
/* Collapsible "needs a horizon" warning (native <details>). Summary is a single
   clickable row; the flex column gap on .kb-orphans spaces it from the body. */
.kb-orphans-summary {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; cursor: pointer; margin-bottom: 0; list-style: none;
}
.kb-orphans-summary::-webkit-details-marker { display: none; }
.kb-orphans-chev { transition: transform 0.15s ease; }
.kb-orphans-acc[open] .kb-orphans-chev { transform: rotate(180deg); }
.kb-orphans-body { display: flex; flex-direction: column; gap: 8px; }

/* ─── DELEGATION ──────────────────────────────────────────── */
/* Banner shown at the top of the app when the logged-in user is acting as
   someone else's delegate. The accent border on .app-delegating frames the
   whole view in amber so confusion about whose data is on screen is impossible. */

.delegate-banner {
  background: var(--delegate-bg);
  border-bottom: 1px solid var(--delegate-border);
  color: var(--delegate);
  padding: 8px calc(50vw - 50%);
  display: flex; align-items: center; gap: 12px;
  font-size: 0.8em; letter-spacing: 1px;
  margin: -16px calc(50% - 50vw) 16px;  /* viewport-wide band, like .header */
}
.delegate-banner-label {
  color: var(--delegate); font-weight: 600; letter-spacing: 2px;
}
.delegate-banner-name { color: var(--text); font-weight: 500; flex: 1; }
.delegate-banner-exit {
  background: transparent; border: 1px solid var(--delegate-border);
  color: var(--delegate); padding: 4px 12px; font-size: 0.85em;
  font-family: inherit; cursor: pointer; letter-spacing: 1px;
}
.delegate-banner-exit:hover { background: var(--delegate); color: var(--bg); }

.app-delegating { box-shadow: inset 0 0 0 1px var(--delegate-border); }

/* Workspace switcher dropdown in the header */
.ws-switcher { position: relative; display: inline-flex; }

/* Full-viewport scrim shown during a workspace switch. Dim + light blur of
   the current view, with a small status pill centered. Fades in/out via
   the .visible class. Blocks clicks while present so the user can't fire
   another switch / nav mid-transition. */
.ws-switching-overlay {
  position: fixed; inset: 0; z-index: 500;
  background: rgba(0, 0, 0, 0.22);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity 160ms ease;
  pointer-events: all;
}
.ws-switching-overlay.visible { opacity: 1; }
.ws-switching-overlay-label {
  font-family: inherit; font-size: 0.85em; letter-spacing: 2px;
  text-transform: uppercase; color: var(--text);
  background: var(--bg-elevated); border: 1px solid var(--border);
  padding: 12px 18px;
}
/* The workspace anchor. Deliberately NOT a .header-link: every item in that
   utility row means "go somewhere", this one means "where you already are",
   so it carries a border, real weight and a colour swatch instead of blending
   into the row. Sits in the header's centre track. */
.ws-anchor {
  display: inline-flex; align-items: center; gap: 9px;
  max-width: 340px; min-height: 34px;
  padding: 6px 12px 6px 8px;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text); font-family: inherit; font-size: 0.85em; font-weight: 600;
  letter-spacing: 0.03em; text-transform: uppercase;
  cursor: pointer; transition: border-color 0.2s;
}
.ws-anchor:hover { border-color: var(--text-dim); }
.ws-anchor-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-anchor-gloss {
  font-size: 0.8em; font-weight: 400; letter-spacing: 0.1em;
  color: var(--text-muted); white-space: nowrap;
  padding-left: 9px; border-left: 1px solid var(--border);
}
.ws-anchor-sw {
  width: 18px; height: 18px; border-radius: 4px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.72em; font-weight: 700; letter-spacing: 0; color: #fff;
}
/* All workspaces is the widest view, not a lesser one, so its pill is the
   same solid shell as a single space — a dashed edge read as provisional.
   The swatch carries the whole distinction: a star for everything, a
   coloured initial for one space. Ink rather than a hue, so it can't be
   mistaken for a workspace of its own. */
.ws-anchor-sw-all { background: var(--text); color: var(--bg-elevated); font-size: 0.82em; }

/* ─── WORKSPACE COLOUR PICKER (Team tab) ─────────────────────
   Owner-only swatch row. The dot carries the hue and the label names it, so
   the control never depends on colour alone — an owner who can't separate
   sage from moss can still read which one is set. */
.ws-colour-intro {
  color: var(--text-secondary); font-size: 0.85em; line-height: 1.6;
  margin: 0 0 14px; max-width: 62ch;
}
.ws-colour-swatches { display: flex; flex-wrap: wrap; gap: 8px; }
.ws-colour-sw {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 12px 7px 9px; min-height: 36px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 999px;
  color: var(--text-secondary); font-family: inherit; font-size: 0.78em;
  letter-spacing: 0.04em; text-transform: uppercase; cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.ws-colour-sw:hover { border-color: var(--text-dim); color: var(--text); }
.ws-colour-sw-on { border-color: var(--text); color: var(--text); font-weight: 600; }
.ws-colour-dot {
  width: 16px; height: 16px; border-radius: 50%; flex: 0 0 auto;
  background: var(--sw);
}
/* The set swatch gets a ring as well as the heavier border — the border
   alone is a thin cue on a row that is already all about colour. */
.ws-colour-sw-on .ws-colour-dot { box-shadow: 0 0 0 2px var(--bg), 0 0 0 3.5px var(--sw); }
.ws-colour-note { margin: 12px 0 0; font-size: 0.8em; color: var(--text-muted); }
@media (pointer: coarse) { .ws-colour-sw { min-height: 44px; } }

/* Custom colour. Sits below the presets on its own rule so the row reads as
   "or any colour you like" rather than an eighth preset. The native picker is
   the control — it is the one colour UI every platform already ships, it
   handles touch and keyboard on its own, and it costs nothing. The hex field
   beside it is for the case the picker is bad at: pasting a brand colour. */
.ws-colour-custom {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-light);
}
.ws-colour-custom-lab {
  font-family: var(--mono); font-size: 0.72em; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--text-muted);
}
.ws-colour-custom-on .ws-colour-custom-lab { color: var(--text); font-weight: 600; }
/* The chrome around a colour input is unstyleable and different on every
   platform; stripping it back to the swatch keeps it in the same family as
   the preset dots. */
.ws-colour-picker {
  -webkit-appearance: none; appearance: none;
  width: 34px; height: 34px; padding: 0; flex: 0 0 auto;
  background: none; border: none; border-radius: 999px; cursor: pointer;
}
.ws-colour-picker::-webkit-color-swatch-wrapper { padding: 0; }
.ws-colour-picker::-webkit-color-swatch {
  border: 1px solid var(--border); border-radius: 999px;
}
.ws-colour-picker::-moz-color-swatch {
  border: 1px solid var(--border); border-radius: 999px;
}
.ws-colour-picker:hover::-webkit-color-swatch { border-color: var(--text-dim); }
.ws-colour-picker:hover::-moz-color-swatch { border-color: var(--text-dim); }
/* Same ring the chosen preset dot wears, so "custom is the one that's set" is
   the same cue in both rows and doesn't rest on the label weight alone. */
.ws-colour-custom-on .ws-colour-picker::-webkit-color-swatch {
  border-color: var(--text); box-shadow: 0 0 0 2px var(--bg), 0 0 0 3.5px var(--sw);
}
.ws-colour-custom-on .ws-colour-picker::-moz-color-swatch {
  border-color: var(--text); box-shadow: 0 0 0 2px var(--bg), 0 0 0 3.5px var(--sw);
}
.ws-colour-picker:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ws-colour-hex {
  width: 13ch; background: var(--bg-input); border: 1px solid var(--border);
  color: var(--text); padding: 8px 10px; font-family: var(--mono);
  font-size: 0.8em; letter-spacing: 0.04em; outline: none;
  transition: border-color 0.15s;
}
.ws-colour-hex:focus { border-color: var(--accent); }
@media (pointer: coarse) {
  .ws-colour-picker { width: 44px; height: 44px; }
  .ws-colour-hex { min-height: 44px; }
}
/* Centred under the pill, which is itself centred — a menu that opened from
   one edge of a centred trigger would read as belonging to something else. */
.ws-menu {
  position: absolute; left: 50%; transform: translateX(-50%); top: calc(100% + 6px);
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: 6px; min-width: 232px; z-index: 100; padding: 4px 0;
  box-shadow: var(--overlay-shadow);
}
/* Same shell and same type scale as the account menu (see the dropdown type
   system near .header-overflow-item). Rows read in sentence case at one
   size; the workspace you're in takes the ● the theme rows use, in the same
   30px gutter the icons occupy, so every label sits on one left edge. */
.ws-menu-item {
  display: flex; flex-direction: column; justify-content: center;
  position: relative;
  width: 100%; text-align: left; min-height: 44px;
  background: transparent; border: none; color: var(--text);
  padding: 10px 14px 10px 30px; font-family: inherit; cursor: pointer;
}
.ws-menu-item:hover { background: var(--bg-input); }
.ws-menu-item-active { color: var(--accent); }
.ws-menu-item-active::before {
  content: "●";
  position: absolute;
  left: 15px; top: 50%;
  transform: translateY(-50%);
  font-size: 0.62em;
}
.ws-menu-name { font-size: 0.85em; letter-spacing: 0.2px; }
.ws-menu-role { color: var(--text-dim); font-size: 0.72em; margin-top: 2px; }
/* Footer actions sit under one rule, so the divider is drawn once for the
   pair rather than above each row. */
.ws-menu-foot {
  border-top: 1px solid var(--border-light); margin-top: 4px; padding-top: 4px;
}
/* Demoted, not disabled: --text-muted on a clickable row reads as greyed
   out now that the caps are gone, so the create action sits one step down
   from the workspace names rather than two. */
.ws-menu-foot-item { color: var(--text-secondary); }
.ws-menu-foot-item:hover { color: var(--text); }

/* Workspace badge on initiative cards in the unified view (active = personal,
   row belongs to a team / delegated workspace). Subtle inline tag so the
   primary content stays the title; badge whispers "this lives somewhere else". */
.ws-badge {
  display: inline-block; margin-left: 8px;
  font-size: 0.65em; letter-spacing: 1px;
  padding: 1px 6px; border: 1px solid var(--border);
  color: var(--text-muted);
  vertical-align: middle;
}
.ws-badge-team { color: var(--text-secondary); border-color: var(--border); }
.ws-badge-personal { color: var(--delegate); border-color: var(--delegate-border); }

/* Settings → Delegates section */
.invite-row { display: flex; gap: 8px; align-items: stretch; }
.invite-row .input { flex: 1; }
.delegate-row { padding: 10px 0; border-bottom: 1px solid var(--border-light); }
.delegate-row:last-child { border-bottom: none; }

/* Team-member row: name/email on the left, inline role select + remove on the right. */
.team-member-row { display: flex; align-items: center; gap: 10px; }
.team-member-info { flex: 1; min-width: 0; }
.team-role-select { width: 110px; padding: 5px 8px; font-size: 0.78em; text-transform: uppercase; letter-spacing: 1px; }
.team-role-static {
  color: var(--text-muted); font-size: 0.78em; letter-spacing: 1px; text-transform: uppercase;
  padding: 4px 8px; border: 1px solid var(--border-light);
}

/* ── People cards (People tab + Settings → Delegates) ──
   Index cards on the desk: monogram tile, name/email, role tag, quiet
   footer actions. The add slot is a dashed ghost card in the same grid
   that expands in place into the invite form. */
.ppl-intro { color: var(--text-secondary); font-size: 0.85em; line-height: 1.6; margin-bottom: 16px; max-width: 62ch; }
.ppl-empty { color: var(--text-muted); font-size: 0.82em; margin-top: 14px; }
.ppl-grid {
  display: grid; gap: 12px; align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(272px, 1fr));
  container-type: inline-size;
}
/* Secondary text sits on the lifted card surface, where --text-muted and
   --text-secondary both dip under 4.5:1 on the light themes — mix toward
   --text so hierarchy comes from size, not faintness. */
.ppl-grid { --ppl-dim: color-mix(in srgb, var(--text-secondary) 60%, var(--text)); }
.ppl-card {
  height: 176px; display: grid; grid-template-rows: auto auto 1fr; gap: 10px; min-width: 0;
  position: relative;
  padding: 16px 16px 8px; background: var(--bg-elevated);
  border: 1px solid var(--border); border-radius: 4px;
  box-shadow: inset 0 1px 0 var(--card-top), var(--card-shadow);
  animation: ppl-card-in 240ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
  animation-delay: calc(var(--ppl-i, 0) * 35ms);
}
@keyframes ppl-card-in { from { opacity: 0; transform: translateY(6px); } }
.ppl-card-head { display: flex; align-items: center; gap: 12px; min-width: 0; padding: 4px 0 0 4px; }
.ppl-card-id { flex: 1; min-width: 0; }
.ppl-av {
  width: 44px; height: 44px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  font-size: 0.95em; font-weight: 600; letter-spacing: 0.5px; color: var(--text);
  border: 1.5px solid var(--border);
  transition: background-color 200ms ease, color 200ms ease, border-color 200ms ease;
}
/* Role ring: a 2px ring floated off the tile. Owner = koi, admin = amber,
   editor = neutral, viewer = dashed neutral. */
.ppl-av-owner, .ppl-av-admin, .ppl-av-editor, .ppl-av-viewer { box-shadow: 0 0 0 2px var(--bg-elevated), 0 0 0 4px var(--ppl-ring, var(--text-dim)); }
.ppl-av-owner  { --ppl-ring: var(--accent); }
.ppl-av-admin  { --ppl-ring: var(--warning); }
.ppl-av-editor { --ppl-ring: var(--text-dim); }
.ppl-av-viewer { --ppl-ring: transparent; outline: 2px dashed var(--text-dim); outline-offset: 2px; }
.ppl-role-admin { color: var(--warning); border-color: var(--warning); }
.ppl-av-ghost { background: transparent; border-style: dashed; color: var(--text-dim); font-weight: 400; font-size: 1.2em; }
.ppl-name { color: var(--text); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.3; }
.ppl-you { color: var(--ppl-dim); font-size: 0.86em; font-weight: 400; letter-spacing: 1px; text-transform: uppercase; margin-left: 4px; }
.ppl-email { color: var(--ppl-dim); font-size: 0.9em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-height: 1.4em; }
.ppl-card-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; }
.ppl-sub { color: var(--ppl-dim); font-size: 0.86em; font-variant-numeric: tabular-nums; }
.ppl-role {
  color: var(--ppl-dim); font-size: 0.84em; letter-spacing: 1.5px; text-transform: uppercase;
  padding: 3px 8px; border: 1px solid var(--border); border-radius: 2px; flex: none;
}
.ppl-role-owner { color: var(--accent); border-color: var(--accent); }
.ppl-role-pending { color: var(--warning); border-color: var(--warning); }
.ppl-role-select {
  width: auto; padding: 4px 8px; font-size: 0.72em; letter-spacing: 1.5px; text-transform: uppercase;
  min-height: 28px; cursor: pointer;
}
.ppl-card-foot {
  display: flex; gap: 2px; align-items: end; align-self: end; margin: 0 -8px;
  padding-top: 2px; border-top: 1px solid var(--border-light);
}
.ppl-card-foot-quiet { border-top-color: transparent; }
.ppl-card-foot-quiet span { color: var(--text-dim); font-size: 0.86em; padding: 8px 10px; }
.ppl-act {
  background: none; border: 1px solid transparent; border-radius: 3px; cursor: pointer;
  color: var(--ppl-dim); font: inherit; font-size: 0.86em; letter-spacing: 0.5px;
  min-height: 44px; padding: 8px 10px;
  transition: color 150ms ease, border-color 150ms ease;
}
.ppl-act:hover { color: var(--text); }
.ppl-act-danger:hover { color: var(--danger); }
.ppl-act:focus-visible, .ppl-card-add:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ppl-card-pending { border-style: dashed; box-shadow: none; background: transparent; }
.ppl-card-pending .ppl-av { border-style: dashed; }
.ppl-card-self { border-color: var(--text-dim); }
.ppl-card-new { animation: ppl-card-in 240ms cubic-bezier(0.2, 0.7, 0.2, 1) both, ppl-card-flash 1200ms ease-out 240ms; }
@keyframes ppl-card-flash { 0%, 40% { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent); } }

/* Add slot */
.ppl-card-add {
  grid-template-rows: 1fr; place-items: center; text-align: center; gap: 8px;
  cursor: pointer; font: inherit; padding: 16px;
  background: transparent; border-style: dashed; box-shadow: none;
  color: var(--text-muted);
  transition: border-color 150ms ease, color 150ms ease, background-color 150ms ease;
}
.ppl-card-add:hover { border-color: var(--accent); color: var(--text); background: color-mix(in srgb, var(--accent) 5%, transparent); }
.ppl-card-add:hover .ppl-av-ghost { border-color: var(--accent); color: var(--accent); }
.ppl-card-add:active { transform: scale(0.985); }
.ppl-card-add .ppl-name { color: inherit; margin-top: 8px; }
.ppl-card-add > * { display: block; }
.ppl-card-add .ppl-av { margin: 0 auto; }

/* Add form — same slot; full width in a narrow grid, two columns where there's room */
.ppl-card-form { grid-column: 1 / -1; height: auto; min-height: 176px; grid-template-rows: auto auto auto; padding: 16px; border-color: var(--accent); animation: none; }
@container (min-width: 520px) { .ppl-card-form { grid-column: span 2; } }
.ppl-form-field { display: flex; flex-direction: column; gap: 4px; }
.ppl-label {
  color: var(--ppl-dim); font-size: 0.78em; letter-spacing: 2px; text-transform: uppercase;
}
.ppl-card-form .input { padding: 9px 12px; font-size: 0.9em; min-height: 44px; }
.ppl-roles { border: none; padding: 0; margin: 0; display: grid; gap: 6px; grid-template-columns: repeat(3, 1fr); }
@container (max-width: 560px) { .ppl-roles { grid-template-columns: 1fr; } }
.ppl-roles legend { padding: 0; margin-bottom: 6px; }
.ppl-role-opt {
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; column-gap: 10px; align-items: center;
  padding: 8px 10px; min-height: 44px; border: 1px solid var(--border-light); border-radius: 3px; cursor: pointer;
  transition: border-color 150ms ease, background-color 150ms ease;
}
.ppl-role-opt:hover { border-color: var(--text-dim); }
.ppl-role-opt:has(input:checked) { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, transparent); }
.ppl-role-opt:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.ppl-role-opt input { grid-row: 1 / span 2; margin: 0; accent-color: var(--accent); width: 16px; height: 16px; }
.ppl-role-opt-name { color: var(--text); font-size: 0.85em; font-weight: 500; }
.ppl-role-opt-desc { color: var(--ppl-dim); font-size: 0.86em; }
.ppl-form-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.ppl-form-actions .btn { padding: 10px 18px; font-size: 0.82em; min-height: 44px; }
.ppl-form-actions .btn:disabled { opacity: 0.5; cursor: progress; transform: none; }
@media (max-width: 560px) {
  .ppl-grid { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  .ppl-card, .ppl-card-new { animation: none; }
  .ppl-card-add:active { transform: none; }
}


/* Role popover on a member card */
.ppl-role-menu {
  position: absolute; left: 8px; right: 8px; bottom: 52px; z-index: 5;
  display: grid; gap: 4px; padding: 6px;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 4px;
  box-shadow: var(--card-shadow);
}
.ppl-role-menu-item {
  display: grid; text-align: left; gap: 1px; padding: 8px 10px; min-height: 44px;
  background: none; border: 1px solid transparent; border-radius: 3px; cursor: pointer; font: inherit; color: var(--text);
}
.ppl-role-menu-item small { color: var(--ppl-dim); font-size: 0.86em; }
.ppl-role-menu-item:hover { border-color: var(--text-dim); }
.ppl-role-menu-item.is-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, transparent); }
.ppl-role-menu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ── Invite renewal ──────────────────────────────────────────
   The expiry text is the trigger for Extend / New link, so the card footer
   keeps its two buttons instead of growing to four. It reads as the .ppl-sub
   it replaces; the caret and the hover border are the only affordance. The
   --ppl-dim fallback is for the project page, where the var isn't in scope. */
.ppl-inv-exp {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 4px; margin: -2px -4px;
  background: none; border: 1px solid transparent; border-radius: 3px;
  cursor: pointer; font: inherit; font-size: 0.86em; font-variant-numeric: tabular-nums;
  color: var(--ppl-dim, var(--text-muted));
  transition: color 150ms ease, border-color 150ms ease;
}
.ppl-inv-exp i { font-style: normal; font-size: 0.9em; }
.ppl-inv-exp:hover:not(:disabled) { color: var(--text); border-color: var(--border); }
.ppl-inv-exp:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ppl-inv-exp:disabled { cursor: default; opacity: 0.7; }
/* Negative margin absorbs the taller tap target so the fixed-height card's
   meta row doesn't grow and squeeze the footer. */
@media (pointer: coarse) {
  .ppl-inv-exp { min-height: 44px; margin: -8px -4px; }
}
/* A lapsed invite stays on the board so it can be renewed. Dashed grey tag
   rather than --warning/--danger, which drop under 4.5:1 as text on the
   light themes. */
.ppl-role-expired { border-color: var(--text-dim); border-style: dashed; }
.ppl-card-expired { border-color: var(--border-light); }
.ppl-card-expired .ppl-av { opacity: 0.65; }
/* Parented to <body> and fixed, so neither the 176px card nor the project
   card's overflow:hidden can clip it. JS sets top/left off the trigger. */
.ppl-inv-menu {
  position: fixed; left: auto; right: auto; bottom: auto; width: 232px;
  /* 401 puts it with .kb-chip-popover — the app's other body-parented menu —
     above the settings panels and below modals. */
  z-index: 401;
}
.member-role-expired { border-style: dashed; }
/* Doubled class: .member-row-pending sits later in the file and would
   otherwise win the tie. */
.member-row.member-row-expired { border-style: dashed; }
.ppl-sec { margin-bottom: 12px; }
.ppl-sec .section-label { margin-bottom: 4px; }
.ppl-sec .ppl-intro { margin-bottom: 0; }
.ppl-danger { margin-top: 28px; padding-top: 12px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; }
.ppl-danger-link {
  background: none; border: 0; cursor: pointer; font: inherit; font-size: 0.9em; color: var(--text-muted);
  min-height: 44px; padding: 8px 0; text-decoration: underline; text-underline-offset: 3px;
}
.ppl-danger-link:hover { color: var(--danger); }
.ppl-danger-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Team masthead — the workspace is the title */
.team-mast { display: grid; grid-template-columns: 1fr auto; gap: 8px 24px; align-items: start; }
.team-mast-eyebrow {
  display: flex; align-items: center; gap: 8px;
  color: var(--accent); font-size: 0.78em; letter-spacing: 3px; text-transform: uppercase; font-weight: 500;
}
.team-mast-sw { width: 10px; height: 10px; border-radius: 2px; background: var(--accent); }
.team-mast-title { display: flex; align-items: baseline; gap: 10px; margin-top: 2px; min-width: 0; }
.team-mast-name { font-size: 2em; font-weight: 600; letter-spacing: -0.01em; margin: 0; line-height: 1.15; color: var(--text); text-wrap: balance; }
[data-theme="light"] .team-mast-name { font-family: var(--serif-display); }
.team-mast-rename {
  background: none; border: 0; cursor: pointer; font: inherit; font-size: 0.86em; color: var(--text-muted);
  padding: 8px 4px; min-height: 44px; text-decoration: underline; text-underline-offset: 3px;
}
.team-mast-rename:hover { color: var(--text); }
.team-mast-rename:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.team-mast-input { width: auto; flex: 1; max-width: 420px; font-size: 1.2em; padding: 8px 12px; }
.team-mast-save { padding: 8px 16px; font-size: 0.82em; min-height: 44px; }
.team-mast-meta { display: flex; flex-wrap: wrap; gap: 6px 0; margin-top: 6px; color: var(--text-secondary); font-size: 0.92em; font-variant-numeric: tabular-nums; }
.team-mast-meta span + span::before { content: "·"; color: var(--text-dim); margin: 0 9px; }
.team-mast-meta b { color: var(--text); font-weight: 600; }
.team-mast-actions { align-self: center; }
.team-mast-copy { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; font-size: 0.8em; }
.team-mast-copy svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
@media (max-width: 700px) {
  .team-mast { grid-template-columns: 1fr; }
  .team-mast-actions { justify-self: start; }
}

/* Invite link modal */
.invite-modal-wrap {
  position: fixed; inset: 0; background: rgba(0,0,0,0.7);
  display: flex; align-items: center; justify-content: center;
  z-index: 200; padding: 16px;
}
.invite-modal {
  background: var(--bg-elevated); border: 1px solid var(--border);
  padding: 24px; max-width: 480px; width: 100%;
}
.invite-link-row { display: flex; gap: 6px; }
.invite-link-row .input { flex: 1; font-size: 0.8em; }

/* Pre-consent explainer for the bundled Google connect — reuses the
   .invite-modal shell; this is just the permission list inside it. */
.gconsent-modal-wrap .invite-modal { max-height: calc(100vh - 32px); overflow-y: auto; }
.gconsent-list { display: flex; flex-direction: column; gap: 14px; margin: 16px 0 18px; }
.gconsent-item { display: flex; gap: 10px; align-items: flex-start; }
.gconsent-item > svg { flex: 0 0 auto; margin-top: 2px; color: var(--accent); }
.gconsent-item-title { font-size: 0.9em; font-weight: 600; }
/* --text-secondary, not the --text-muted the surrounding hints use: this is
   consent copy, and muted-on-elevated lands at 4.39:1 on the light theme. */
.gconsent-item-body, .gconsent-modal-wrap .settings-empty-hint {
  color: var(--text-secondary); font-size: 0.86em; line-height: 1.55;
}
.gconsent-item-body { margin-top: 3px; max-width: 46ch; }

/* New Task modal — same scrim/panel shell as the invite modal; the fields
   inside reuse the kanban editor's .kb-edit-* row/label/grid styles. */
.nt-modal-wrap {
  position: fixed; inset: 0; background: rgba(0,0,0,0.7);
  display: flex; align-items: center; justify-content: center;
  z-index: 200; padding: 16px;
}
.nt-modal {
  background: var(--bg-elevated); border: 1px solid var(--border);
  padding: 24px; max-width: 520px; width: 100%;
  display: flex; flex-direction: column; gap: 10px;
  max-height: calc(100vh - 32px); overflow-y: auto;
}
.nt-modal-head { display: flex; justify-content: space-between; align-items: flex-start; }
.nt-modal-head .section-label { margin-bottom: 0; }
/* Visually small ✕, but padded out to a ~44px touch target for iOS (no Esc
   key there — this and the scrim tap are the only close paths). Negative
   margins cancel the padding so it still sits flush with the header corner. */
.nt-modal-x {
  font: inherit; font-size: 0.9em; background: none; border: none;
  color: var(--text-muted); cursor: pointer; line-height: 1;
  padding: 14px; margin: -14px -14px 0 0;
}
.nt-modal-x:hover { color: var(--text); }
.nt-modal-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }

/* Send feedback — .invite-modal shell, .nt-modal-head for the ✕. The kind
   picker is three outline buttons behaving as radios; the checked one takes
   the accent border and full text colour rather than colour alone. */
.feedback-modal { max-width: 520px; display: flex; flex-direction: column; gap: 12px; }
.feedback-kinds { display: flex; flex-wrap: wrap; gap: 8px; }
.feedback-kind { letter-spacing: 0; text-transform: none; }
/* Beats the camel theme's .btn padding (10px 18px) so all five moods fit one row. */
.feedback-kinds .feedback-kind { padding: 7px 11px; }
.feedback-kind[aria-checked="true"] { border-color: var(--accent); color: var(--text); font-weight: 600; }
.feedback-msg { resize: vertical; min-height: 110px; font: inherit; line-height: 1.5; }
.feedback-mood-group { margin-bottom: 0; }
.feedback-mood-group .form-label { margin-bottom: 8px; }
/* Mood scale, fuming → super green. The pill text stays --text (see the
   --warning/--danger contrast note on .my-fb-head); the colour rides the
   border, and on the modal buttons only the chosen one takes it. */
.fb-mood-fuming,      .feedback-mood-fuming[aria-checked="true"]      { border-color: var(--danger); }
.fb-mood-average,     .feedback-mood-average[aria-checked="true"]     { border-color: var(--border); }
.fb-mood-good,        .feedback-mood-good[aria-checked="true"]        { border-color: var(--text-muted); }
.fb-mood-amazing,     .feedback-mood-amazing[aria-checked="true"]     { border-color: var(--success); }
.fb-mood-super_green, .feedback-mood-super_green[aria-checked="true"] { border-color: var(--success); box-shadow: inset 0 0 0 1px var(--success); }
.admin-fb-mood { margin-top: 4px; }
.fb-mood-emoji { margin-right: 5px; font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif; }
.feedback-shot-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.feedback-shot-pick { display: inline-flex; align-items: center; gap: 6px; letter-spacing: 0; text-transform: none; }
.feedback-shot-pick .icon { width: 14px; height: 14px; }
.feedback-shot-pick:focus-within { border-color: var(--accent); }
.feedback-shot-name { color: var(--text-secondary); font-size: 0.85em; }
.feedback-shot-clear {
  font: inherit; font-size: 0.9em; background: none; border: none; padding: 4px 6px;
  color: var(--text-muted); text-decoration: underline; cursor: pointer;
}
.feedback-shot-clear:hover { color: var(--text); }
.feedback-meta { color: var(--text-muted); font-size: 0.8em; }
.feedback-meta code { font-size: 0.95em; }
.feedback-actions { display: flex; gap: 8px; justify-content: flex-end; align-items: center; margin-top: 4px; }
/* Pushed to the far left of the actions row: it's an exit from the modal, not
   one of the two things you came here to do, so it sits away from Send. */
/* Two classes deliberately: .btn-link is declared ~1,200 lines further down, so
   a bare .feedback-mine loses the colour on source order. --text-muted, its
   default, measures 4.39:1 on the light theme — under the 4.5:1 a link needs. */
.feedback-actions .feedback-mine { margin-right: auto; }
/* Receipt state — replaces the form once the row is saved. */
.feedback-sent { display: flex; gap: 12px; align-items: flex-start; padding: 4px 0 8px; }
.feedback-sent-tick { color: var(--success); flex: none; line-height: 0; }
.feedback-sent-tick .icon { width: 22px; height: 22px; margin: 0; }
.feedback-sent-head { font-weight: 600; margin-bottom: 6px; }
.feedback-sent-body { color: var(--text-secondary); font-size: 0.88em; line-height: 1.6; }
.feedback-sent-body code { font-size: 0.95em; }
@media (pointer: coarse) {
  .feedback-kind, .feedback-shot-pick { min-height: 44px; }
}
/* Settings › Feedback — your own reports. One block per report; the reply,
   when there is one, takes an accent rule so it reads as the answer rather
   than more of your own text. --bg (not --bg-elevated) because the two are
   the same value on the light themes. */
/* Each thread is its own .settings-card, so the card shell carries the
   padding and the gap between them. The panel label sits above the stack
   rather than inside a card, indented to line up with the card text. */
#spanel-feedback > .section-label { padding: 0 20px; }
.my-fb-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
/* The kind pill keeps its coloured rule as the signal but takes --text for the
   label — --danger/--success drop to ~3.3:1 as text on the modernist and dark
   themes. Scoped here; the admin table is admin-only and left as it was. */
.my-fb-head .admin-pill { color: var(--text); }
.my-fb-when { color: var(--text-muted); font-size: 0.82em; font-variant-numeric: tabular-nums; }
.my-fb-status { color: var(--text-secondary); font-size: 0.82em; }
.my-fb-msg { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.6; }
/* Who filed it — only on reports that aren't yours, which for everyone but
   an app admin is never. Sits above the message so the words are attributed
   before they're read. */
.my-fb-from { margin-bottom: 6px; overflow-wrap: anywhere; }
.my-fb-from strong { color: var(--text-secondary); font-weight: 600; }
.my-fb-page { margin-top: 6px; word-break: break-all; }
.my-fb-reply {
  margin-top: 12px; padding: 10px 12px;
  background: var(--bg); border-left: 2px solid var(--accent);
}
.my-fb-reply-head {
  color: var(--text); font-size: 0.78em; font-weight: 600;
  letter-spacing: 0.5px; text-transform: uppercase; margin-bottom: 6px;
}
.my-fb-reply-body { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.6; color: var(--text-secondary); }
/* Who answered: their name, then a badge marking them as one of us. The
   head is already uppercase + small, so the pill inherits that and only
   adds the accent outline. */
.my-fb-staff {
  color: var(--accent); border-color: var(--accent);
  font-size: 0.9em; padding: 0 6px; margin-left: 2px; vertical-align: 1px;
}
/* Your own turn in the thread: neutral rule, so the accent stays the marker
   for an answer coming back to you. */
.my-fb-reply-mine { border-left-color: var(--border); }
.my-fb-actions { margin-top: 10px; display: flex; gap: 8px; }
.my-fb-replybox { margin-top: 12px; }
.my-fb-reply-input { resize: vertical; min-height: 70px; font: inherit; line-height: 1.5; }
.my-fb-replybox-actions { display: flex; gap: 8px; margin-top: 8px; }
@media (pointer: coarse) {
  .my-fb-head .btn { min-height: 44px; }
}
/* Button + its one-line explainer stacked, so the context reads without
   waiting on a hover tooltip. Four actions → 2×2; single column when narrow. */
.nt-action {
  flex: 1 1 calc(50% - 4px); min-width: 130px;
  display: flex; flex-direction: column; gap: 5px;
}
.nt-action .btn {
  width: 100%; padding: 10px 8px; font-size: 0.78em;
  white-space: nowrap; letter-spacing: 0.5px;
}
.nt-action-sub { color: var(--text-dim); font-size: 0.68em; line-height: 1.45; }

/* ─── TEAM MANAGEMENT PANEL ───────────────────────────────── */
/* Used on the People tab when the active workspace is a team. Compose with
   .settings-section (border + padding) for the outer container. */
.team-mgmt-row {
  display: flex; gap: 10px; align-items: center;
}
.team-mgmt-row .input { flex: 1; }
.team-stats {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
}
.team-stat {
  border: 1px solid var(--border-light); padding: 12px 14px;
}
.team-stat-value {
  font-family: var(--font-mono, monospace);
  font-size: 1.4em; color: var(--text-primary); line-height: 1.1;
}
.team-stat-label {
  color: var(--text-muted); font-size: 0.72em;
  letter-spacing: 1.5px; text-transform: uppercase; margin-top: 4px;
}
@media (max-width: 700px) {
  .team-stats { grid-template-columns: repeat(2, 1fr); }
  .team-mgmt-row { flex-wrap: wrap; }
  .team-mgmt-row .settings-label { min-width: 0 !important; flex-basis: 100%; }
}

/* ─── RESPONSIVE ──────────────────────────────────────────── */
/* Narrow-viewport kanban: switch to tabbed single-column mode. Scoped to
   .kb-board-kanban so the projects board (.kb-board-projects, no qualifier)
   keeps its desktop horizontal-scroll behaviour on mobile. */
@media (max-width: 900px) {
  .kb-tabs { display: flex; }
  /* One column shows at a time here, so a rail spanning columns has nothing
     to span. The tab pills carry the navigation instead. */
  .kb-group-rail { display: none; }
  .kb-board-kanban {
    display: block;
    overflow: visible;
    scroll-snap-type: none;
    margin-left: 0; margin-right: 0;
    padding-left: 0; padding-right: 0;
    /* Single-column mode shows one column at a time and touch can't drag
       anyway, so the desktop drop-target min-height would only add dead
       space below a short column. */
    min-height: 0;
  }
  .kb-board-kanban > .kb-col { display: none; }
  .kb-board-kanban > .kb-col-active {
    display: flex;
    width: 100%;
    max-height: calc(100vh - 250px);
  }
}
@media (max-width: 480px) {
  html { font-size: 13px; }
  .app {
    padding:
      max(12px, env(safe-area-inset-top, 12px))
      max(12px, env(safe-area-inset-right, 12px))
      calc(100px + env(safe-area-inset-bottom, 0px))
      max(12px, env(safe-area-inset-left, 12px));
  }
  .tab { padding: 8px 10px; font-size: 0.8em; }
  .input-row { flex-direction: column; }
  .kb-col { max-height: calc(100vh - 200px); }
  .kb-board { gap: 8px; }
  .header { margin: -12px calc(50% - 50vw) 0; padding: 12px calc(50vw - 50%); }
  .header:first-child,
  .delegate-banner:first-child { margin-top: min(0px, calc(-12px + env(safe-area-inset-top, 0px))); }
  .tabs { margin: 0 calc(50% - 50vw) 0; padding: 0 calc(50vw - 50%); }
  .kb-filters { margin: 0 -12px 12px; padding: 10px 12px; }
  .week-tab > .kb-filters { margin: 0 0 8px; padding: 0 12px; }
  .delegate-banner { margin: -12px calc(50% - 50vw) 12px; flex-wrap: wrap; }
  .delegate-banner-exit { font-size: 0.75em; }
  .invite-row { flex-direction: column; }
}

/* ─── ON-CARD CHIPS (TASK CARDS) ─────────────────────────────
   The chip row sits below the title and above the action buttons.
   Each chip is a click target that opens a popover for that field.
   Empty-state chips (no value yet) use dashed borders so they read as
   "set me" affordances rather than filled state. Pattern mirrors
   .kb-card-assignee + .kb-card-assignee-unassigned which already
   exist for the assignee slot. */
.kb-card-chips {
  display: flex; flex-wrap: wrap; gap: 4px;
  margin: 6px 0 2px;
  align-items: center;
}
.kb-card-chip {
  display: inline-flex; align-items: center; gap: 4px;
  background: transparent; border: 1px solid var(--border);
  color: var(--text-muted);
  padding: 3px 7px;
  font-size: 0.7em;
  font-family: inherit;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
  letter-spacing: 0;
}
.kb-card-chip:hover { color: var(--text); border-color: var(--text-muted); }
.kb-card-chip-empty { color: var(--text-dim); border-style: dashed; }
.kb-card-chip-empty:hover { color: var(--text-muted); border-color: var(--text-muted); }
/* Non-interactive chip (e.g. workspace on a projected task — governed by the
   project, so display-only). No hover affordance, default cursor. */
.kb-card-chip-static { cursor: default; color: var(--text-dim); }
.kb-card-chip-static:hover { color: var(--text-dim); border-color: var(--border); }
/* Priority chip retired from cards in v7. Class names removed from
   taskCardChipRowHTML / rawInboxChipRowHTML; the priority popover
   handlers (kanbanChipPriority etc.) still exist so the drawer can
   surface them. */
.kb-card-chip-due-overdue { color: var(--danger); border-color: var(--danger); }
.kb-card-chip-due-today { color: var(--warning); border-color: var(--warning); }
/* Blocked chip carries the note or the blocker's title, so it clips. Text stays
   --text (danger fails contrast as ink on the light themes); the border says red. */
.kb-card-chip-blocked {
  color: var(--text); border-color: var(--danger);
  display: inline-block; max-width: 170px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ─── CHIP POPOVER (FIELD QUICK-EDIT) ────────────────────────
   A small floating menu anchored to the chip that opened it.
   Used for priority cycle, due-date set, project picker, assignee
   picker, size/energy pickers. JS positions it via inline style;
   we just style the box. The backdrop is invisible but covers the
   viewport so a click anywhere outside dismisses. */
.kb-chip-popover-backdrop {
  /* Above the task detail modal panel (z-index 301) so chips opened inside the
     modal show in front of it, not hidden behind. */
  position: fixed; inset: 0; z-index: 400; background: transparent;
}
.kb-chip-popover {
  position: fixed; z-index: 401;
  max-height: 60vh; overflow-y: auto;
  background: var(--bg-elevated); border: 1px solid var(--border);
  padding: 6px; min-width: 160px; max-width: 280px;
  /* Sumi: lean on the border, not the shadow. Soft drop keeps the menu
     anchored above the card without the old theatrical wash. */
  box-shadow: inset 0 1px 0 var(--card-top), 0 6px 18px rgba(0,0,0,0.28);
  display: flex; flex-direction: column; gap: 2px;
  animation: kb-chip-popover-in 0.12s ease-out;
}
@keyframes kb-chip-popover-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.kb-chip-popover-item {
  background: transparent; border: none;
  color: var(--text); padding: 6px 10px;
  font-family: inherit; font-size: 0.85em;
  text-align: left; cursor: pointer;
  letter-spacing: 0;
}
.kb-chip-popover-item:hover { background: var(--bg); color: var(--accent); }
.kb-chip-popover-item-active { color: var(--accent); }
/* Divides Google's eleven from the gtd-only categories below them. */
.kb-chip-popover-rule {
  color: var(--text-muted); font-size: 0.7em; letter-spacing: 2px;
  padding: 8px 10px 4px; margin-top: 4px;
  border-top: 1px solid var(--border-light);
}
.kb-chip-popover-item-clear {
  color: var(--text-muted); border-top: 1px solid var(--border-light);
  margin-top: 4px; padding-top: 8px;
}
/* "Apply to: This / All in series" scope chooser atop the category popover for
   recurring events. */
.kb-chip-popover-scope {
  display: flex; flex-direction: column; gap: 4px;
  padding: 2px 6px 8px; margin-bottom: 4px;
  border-bottom: 1px solid var(--border-light);
  font-size: 0.82em; color: var(--text-secondary);
}
.kb-chip-popover-scope label { display: flex; align-items: center; gap: 6px; cursor: pointer; }
.kb-chip-popover-scopelabel {
  font-size: 0.66em; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim);
}
.kb-chip-popover-input {
  background: var(--bg-input); border: 1px solid var(--border);
  color: var(--text); font-family: inherit; font-size: 0.85em;
  padding: 6px 8px; outline: none;
}
.kb-chip-popover-input:focus { border-color: var(--accent); }
.kb-chip-popover-actions { display: flex; gap: 4px; margin-top: 4px; }
.kb-chip-popover-actions .kb-action { flex: 1; }
/* Search results inside a popover (blocked-by picker). */
.kb-chip-popover-results {
  display: flex; flex-direction: column; gap: 2px;
  max-height: 180px; overflow-y: auto;
}

/* ─── TASK DETAIL DRAWER ─────────────────────────────────────
   Lives in #task-detail-panel outside the kanban grid so column
   re-renders don't blow it away. On desktop ≥900px it's a right-side
   drawer ~420px wide, board scrolls behind. On mobile <900px it's a
   full-height bottom sheet. URL-synced to #/<tab>?task=<id>. */
.kb-detail-backdrop {
  /* Subtle dim — enough to indicate the drawer is the active surface
     and to register clicks outside as "close", but light enough that
     the kanban behind stays clearly readable.
     v2 tuning: the 0.32 wash was tuned against the old --bg (#0a0a0a);
     on the deeper Sumi page (#070710) the same alpha disappears
     into the bg, so we lift it slightly. */
  position: fixed; inset: 0; z-index: 300;
  background: rgba(0,0,0,0.42);
  animation: kb-detail-backdrop-in 0.18s ease-out;
}
[data-theme="light"] .kb-detail-backdrop { background: rgba(0,0,0,0.18); }
/* Mobile: keep the heavier curtain since the sheet itself fills
   most of the screen and the backdrop's mostly there to mask the
   body scroll lock anyway. */
@media (max-width: 900px) {
  .kb-detail-backdrop { background: rgba(0,0,0,0.5); }
  [data-theme="light"] .kb-detail-backdrop { background: rgba(0,0,0,0.32); }
}
@keyframes kb-detail-backdrop-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.kb-detail-panel {
  /* Vertically + horizontally centered slide-up card: floats in the
     middle of the viewport, constrained width so the kanban stays
     visible on either side. The card slides in from below the
     viewport into its centered resting position. */
  position: fixed;
  top: 50%;
  left: 50%;
  width: min(600px, calc(100vw - 32px));
  height: min(680px, 70vh); max-height: min(680px, 70vh);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
  z-index: 301;
  display: flex; flex-direction: column;
  /* The static transform centers the card on both axes. Keyframes
     compose translate(-50%, -50%) with the slide so centering
     survives the animation. */
  transform: translate(-50%, -50%);
  animation: kb-detail-panel-in-desk 0.28s cubic-bezier(0.2, 0.7, 0.2, 1);
  /* Sumi: the drawer is the card writ large — same inset top + soft
     drop pattern as .kb-card, just scaled up. No 64px hero shadow. */
  box-shadow: inset 0 1px 0 var(--card-top), 0 16px 40px rgba(0,0,0,0.4);
  overflow: hidden;
}
[data-theme="light"] .kb-detail-panel {
  box-shadow: inset 0 1px 0 var(--card-top), 0 16px 40px rgba(0,0,0,0.14);
}
@keyframes kb-detail-panel-in-desk {
  from { transform: translate(-50%, -50%) translateY(calc(50vh + 50%)); }
  to   { transform: translate(-50%, -50%); }
}
@keyframes kb-detail-panel-out-desk {
  from { transform: translate(-50%, -50%); }
  to   { transform: translate(-50%, -50%) translateY(calc(50vh + 50%)); }
}
.kb-detail-panel-closing {
  animation: kb-detail-panel-out-desk 0.26s cubic-bezier(0.5, 0, 0.75, 0) forwards;
}
@keyframes kb-detail-backdrop-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}
.kb-detail-backdrop-closing {
  animation: kb-detail-backdrop-out 0.22s ease-in forwards;
}
/* In-place re-renders (a field autosave rebuilds the panel HTML) recreate the
   .kb-detail-panel element, which would replay its slide-up entry — the
   "double bounce" on first interaction. When the mount carries .kb-detail-no-anim
   (set on every render except the initial open) we cancel the entry animation.
   The :not(...closing) guards keep the slide-out/fade-out playing on close. */
.kb-detail-no-anim .kb-detail-panel:not(.kb-detail-panel-closing),
.kb-detail-no-anim .kb-detail-backdrop:not(.kb-detail-backdrop-closing) {
  animation: none;
}

/* ── Wide two-column task detail ───────────────────────────────
   A large floating "workspace" panel: main reading column (title +
   description + notes thread) on the left, a compact properties rail on
   the right. The base .kb-detail-panel (narrow, single column) is still
   used by the read-only calendar-event modal — only the task drawer opts
   into .kb-detail-panel-wide. */
.kb-detail-panel-wide {
  width: min(1040px, 94vw);
  height: min(860px, 86vh);
  max-height: min(860px, 86vh);
}
/* Single-column task detail (hero + body + "More"). Narrower than the wide
   two-pane shell since everything stacks. */
.kb-detail-panel-task {
  width: min(720px, 94vw);
  height: min(820px, 88vh);
  max-height: min(820px, 88vh);
}
.kb-detail-body-single {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 14px;
  padding: 18px 22px; max-width: 760px; width: 100%; margin: 0 auto;
}
/* Hero: status + schedule line, then the chip rack. */
.kb-detail-hero { display: flex; flex-direction: column; gap: 8px; }
.kb-hero-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.kb-hero-status { display: inline-flex; align-items: center; gap: 6px; position: relative; }
.kb-hero-status::before {
  content: ''; width: 8px; height: 8px; border-radius: 50%;
  background: var(--text-dim);
}
/* Both "being worked on" states share the accent; inbox stays dim and someday
   muted. Only one pill shows at a time, so scheduled/in_progress can match. */
.kb-hero-status-in_progress::before { background: var(--accent); }
.kb-hero-status-scheduled::before { background: var(--accent); }
.kb-hero-status-someday::before { background: var(--text-muted); }
.kb-hero-select {
  background: var(--bg-input); border: 1px solid var(--border);
  color: var(--text); padding: 4px 8px; border-radius: 4px;
  font: inherit; font-size: 0.82em; text-transform: capitalize; cursor: pointer;
}
.kb-hero-when { display: inline-flex; align-items: center; gap: 5px; color: var(--text-secondary); font-size: 0.85em; }
.kb-hero-when .icon { width: 14px; height: 14px; }
/* The scheduled time doubles as the reschedule affordance when the task has a
   real Google event behind it. Dotted underline so it reads as actionable
   without becoming a button-shaped thing in the middle of the hero line. */
button.kb-hero-when-btn {
  background: none; font-family: inherit; cursor: pointer; padding: 2px 0;
  border: none; border-bottom: 1px dotted var(--border);
  min-height: 24px; transition: color 0.15s, border-color 0.15s;
}
button.kb-hero-when-btn:hover { color: var(--accent); border-bottom-color: var(--accent); }
button.kb-hero-when-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.kb-hero-resched { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.kb-hero-resched-input { max-width: 260px; min-height: 44px; }
.kb-hero-resched-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.kb-hero-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
/* Flatten the nested chip groups so all chips wrap as one continuous row. */
.kb-hero-chips .kb-card-chips { display: contents; }
/* Two-up grid for raw-inbox props + the "More" disclosure fields. */
.kb-detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; }
@media (max-width: 560px) { .kb-detail-grid { grid-template-columns: 1fr; } }
.kb-detail-more { border-top: 1px solid var(--border-light); padding-top: 6px; }
.kb-detail-more-summary {
  cursor: pointer; list-style: none; user-select: none;
  color: var(--text-secondary); font-size: 0.72em; letter-spacing: 0.06em;
  text-transform: uppercase; padding: 6px 0;
}
.kb-detail-more-summary::-webkit-details-marker { display: none; }
.kb-detail-more-summary::before { content: '⌄ '; display: inline-block; }
.kb-detail-more[open] .kb-detail-more-summary::before { content: '⌃ '; }
.kb-detail-more-grid { margin-top: 8px; }
/* Footer info (calendar link + created) on the left of the action bar. */
.kb-detail-foot-info { display: flex; align-items: center; gap: 8px; color: var(--text-dim); font-size: 0.8em; min-width: 0; }
.kb-detail-foot-info .kb-detail-cal-link { white-space: nowrap; }
.kb-detail-foot-sep { opacity: 0.5; }
/* Save state. Autosave used to be entirely silent, which left no moment where
   you could tell an edit had landed. --danger reads as a rule, not as body
   text: it fails contrast on the light themes, so the words stay --text. */
.kb-detail-savestate { font-size: 0.8em; color: var(--text-dim); white-space: nowrap; }
.kb-detail-savestate-error {
  display: inline-flex; align-items: center; gap: 8px; white-space: normal;
  color: var(--text); border-left: 2px solid var(--danger); padding-left: 8px;
}
/* The panel takes programmatic focus on open (for keyboard/ESC) — it's not a
   tab stop, so no visible ring; nothing should look "selected" on open. */
.kb-detail-panel:focus { outline: none; }
.kb-detail-body.kb-detail-body-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 304px;
  gap: 0; padding: 0; margin: 0; max-width: none;
  min-height: 0;
  overflow-y: auto;         /* ONE scroll for the whole body, not per-column */
}
.kb-detail-main {
  min-width: 0;
  display: flex; flex-direction: column; gap: 14px;
  padding: 20px 22px 18px;
  border-right: 1px solid var(--border-light);
}
.kb-detail-rail {
  padding: 20px 18px 22px;
  display: flex; flex-direction: column; gap: 13px;
  background: var(--bg);    /* subtle separation from the elevated panel */
}
/* Seamless title + description: read like content, reveal the input chrome
   only on hover/focus so editing in place feels inline. */
.kb-detail-title-input {
  font-size: 1.5em; font-weight: 700; letter-spacing: -0.2px;
  /* Seamless inline editing: no border in any state (width 0 also neutralises
     the .input:focus accent border) — the subtle hover/focus fill below is the
     only edit affordance. */
  border: none;
}
.kb-detail-title-input,
.kb-detail-desc-input {
  background: transparent;
  border-color: transparent;
  padding: 4px 0;   /* no inner padding → text aligns flush-left with labels,
                       substrip, notes and the rest of the main column */
}
.kb-detail-title-input:hover,
.kb-detail-desc-input:hover {
  background: var(--bg-input);
}
.kb-detail-title-input:focus,
.kb-detail-desc-input:focus {
  background: var(--bg-input);   /* border-color → accent via .input:focus */
}
/* Description rendered view (Markdown) — reads as content; click to edit. */
.kb-detail-desc-view {
  color: var(--text); font-size: 0.95em; line-height: 1.5;
  cursor: text; padding: 4px 0; min-height: 1.4em;
  word-wrap: break-word; overflow-wrap: anywhere;
  transition: background-color 0.15s;
}
.kb-detail-desc-view:hover { background: var(--bg-input); }
.kb-detail-desc-view p { margin: 0 0 6px; }
.kb-detail-desc-view p:last-child { margin-bottom: 0; }
.kb-detail-desc-view a { color: var(--accent); }
.kb-detail-desc-view code {
  font-family: inherit; background: var(--bg-input);
  padding: 1px 4px; border: 1px solid var(--border-light);
}
.kb-detail-desc-view pre {
  background: var(--bg-input); border: 1px solid var(--border-light);
  padding: 8px 10px; overflow-x: auto;
}
.kb-detail-desc-view ul, .kb-detail-desc-view ol { margin: 0 0 6px 18px; padding: 0; }
.kb-detail-desc-empty { color: var(--text-dim); }

/* "Scheduled for …" line tucked under the title. */
.kb-detail-substrip {
  display: flex; align-items: center; gap: 6px;
  margin-top: -8px;
  color: var(--text-secondary); font-size: 0.8em;
}
.kb-detail-substrip .icon { width: 14px; height: 14px; flex: 0 0 auto; }
/* Notes flow at natural height; the body owns the single scroll. The taller
   column (usually the notes thread) drives the scrollable height. */
.kb-detail-main .kb-notes {
  border-top: 1px solid var(--border-light);
  padding-top: 12px;
}
/* "Load earlier" pager at the top of the thread (chat convention). */
.kb-notes-earlier {
  align-self: center;
  background: none; border: 1px solid var(--border-light);
  color: var(--text-muted); font-family: inherit; font-size: 0.78em;
  letter-spacing: 0.5px; padding: 4px 10px; margin-bottom: 2px; cursor: pointer;
}
.kb-notes-earlier:hover { color: var(--text); border-color: var(--border); }
.kb-notes-earlier:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Constrain form width on wide screens so labels and selects don't
   sprawl across an enormous monitor. The body still scrolls within
   the panel; the constraint just keeps line-length comfortable. */
.kb-detail-body { max-width: 760px; width: 100%; margin: 0 auto; }
.kb-detail-actions-inner {
  max-width: 760px; width: 100%; margin: 0 auto;
  display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
}
.kb-detail-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 16px 14px;
  border-bottom: 1px solid var(--border-light);
  flex-shrink: 0;
  position: relative;
}
/* Pull-handle affordance at the top, identical look at every size. */
.kb-detail-header::before {
  content: ''; position: absolute; top: 6px; left: 50%;
  transform: translateX(-50%);
  width: 36px; height: 3px; border-radius: 2px;
  background: var(--border);
}
.kb-detail-status-pill {
  font-size: 0.7em; letter-spacing: 2px;
  color: var(--text-muted); text-transform: uppercase;
}
.kb-detail-status-pill-inbox { color: var(--warning); }
.kb-detail-status-pill-in_progress { color: var(--accent); }
.kb-detail-status-pill-scheduled { color: var(--accent); }
.kb-detail-status-pill-someday { color: var(--text-muted); }
/* Recurrence marker — sits beside the status pill; margin-right:auto keeps the
   close button pinned right despite the header's space-between. */
.kb-detail-recur-pill {
  margin-left: 10px; margin-right: auto;
  font-size: 0.7em; letter-spacing: 1px;
  color: var(--text-muted); text-transform: uppercase;
}
.kb-detail-close {
  background: transparent; border: 1px solid var(--border);
  color: var(--text-muted); padding: 4px 10px;
  font-family: inherit; font-size: 0.9em; cursor: pointer;
  transition: all 0.15s;
}
.kb-detail-close:hover { color: var(--text); border-color: var(--text-muted); }

/* Read-only third-party calendar event modal — content inside the reused
   .kb-detail-panel / .kb-detail-body shell. */
.cal-evt-title { font-size: 1.05em; font-weight: 600; color: var(--text); }
.cal-evt-when {
  color: var(--accent); font-size: 0.85em; letter-spacing: 0.5px;
  margin-bottom: 18px;
}
/* Event length, appended to the when-line — quieter than the accent times. */
.cal-evt-when .cal-evt-dur { color: var(--text-muted); }
.cal-evt-section { margin-bottom: 16px; }
.cal-evt-label {
  font-size: 0.7em; letter-spacing: 2px; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 5px;
}
.cal-evt-desc {
  white-space: normal; line-height: 1.55; color: var(--text-secondary);
  /* Join URLs run to 300+ chars — break them anywhere or the modal scrolls sideways. */
  overflow-wrap: anywhere;
}
/* Underlined in --accent rather than coloured with it: --accent as body text is
   only 3.36:1 on the dark theme. Same call as .pal-mark. */
.cal-evt-link {
  color: var(--text); text-decoration: underline;
  text-decoration-color: var(--accent); text-decoration-thickness: 2px;
  text-underline-offset: 2px;
}
.cal-evt-link:hover { text-decoration-color: var(--accent-hover); }
.cal-evt-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.cal-evt-attendee {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 4px 0; border-bottom: 1px solid var(--border-light);
}
.cal-evt-attendee:last-child { border-bottom: none; }
.cal-evt-resp { color: var(--text-muted); font-size: 0.85em; white-space: nowrap; }
.cal-evt-muted { color: var(--text-muted); font-size: 0.85em; }
.cal-evt-actions {
  display: flex; flex-wrap: wrap; gap: 8px; margin-top: 22px;
}
.cal-evt-btn {
  display: inline-block; padding: 8px 14px; font-size: 0.85em;
  border: 1px solid var(--border); color: var(--text-secondary);
  text-decoration: none; transition: all 0.15s;
}
.cal-evt-btn:hover { color: var(--text); border-color: var(--text-muted); }
.cal-evt-btn-primary {
  background: var(--accent); color: var(--bg); border-color: var(--accent);
}
.cal-evt-btn-primary:hover { background: var(--accent-hover); color: var(--bg); border-color: var(--accent-hover); }
.cal-evt-btn-danger {
  background: none; cursor: pointer; font-family: inherit;
  color: var(--danger); border-color: var(--danger);
}
.cal-evt-btn-danger:hover { background: var(--danger); color: var(--bg); border-color: var(--danger); }
/* .cal-evt-btn is written for the <a> actions; the booking actions are real
   buttons, so they need the form-control defaults reset to match. */
button.cal-evt-btn { background: none; cursor: pointer; font-family: inherit; }
button.cal-evt-btn:disabled { opacity: 0.5; cursor: default; }
/* …but that reset is element+class, so it also out-specifies the -primary
   fill above, leaving color:var(--bg) text on no background — invisible.
   Only <a> used -primary until the win modal needed a real button. */
button.cal-evt-btn-primary { background: var(--accent); }
button.cal-evt-btn-primary:hover { background: var(--accent-hover); }
/* Host-side reschedule panel inside the event modal. */
.cal-evt-booking:empty { display: none; }
.cal-evt-booking { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border); }
.cal-evt-booking-note { font-size: 0.85em; color: var(--text-muted); line-height: 1.5; }
.cal-evt-booking-slots { max-height: 260px; overflow-y: auto; margin-top: 8px; }
.cal-evt-booking-confirm {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 12px;
  font-size: 0.85em; color: var(--text-secondary);
}
/* Manual date/time entry. The offered grid is a long flat scroll once you're
   past this week, and a Week-view drag can't cross a week boundary at all. */
.cal-evt-booking-manual { margin-top: 16px; }
.cal-evt-manual-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.cal-evt-manual-input {
  flex: 1 1 130px; min-height: 44px; background: var(--bg-input);
  border: 1px solid var(--border); color: var(--text); font-family: inherit;
  padding: 10px 12px; font-size: 0.85em; outline: none;
  transition: border-color 0.2s;
}
.cal-evt-manual-input:focus { border-color: var(--accent); }
/* --warning reads as a border, not as body text: it fails contrast on the
   light themes. Carry the signal on the rule and keep the words readable. */
.cal-evt-booking-warn {
  margin-top: 12px; padding: 8px 10px; border-left: 2px solid var(--warning);
  background: var(--bg); font-size: 0.8em; color: var(--text-secondary); line-height: 1.5;
}
/* Going / Maybe / No RSVP segmented control. */
.cal-evt-rsvp { display: inline-flex; border: 1px solid var(--border); }
.cal-evt-rsvp-btn {
  background: none; cursor: pointer; font-family: inherit;
  padding: 7px 16px; font-size: 0.85em; color: var(--text-secondary);
  border: none; border-right: 1px solid var(--border); transition: all 0.15s;
}
.cal-evt-rsvp-btn:last-child { border-right: none; }
.cal-evt-rsvp-btn:hover { color: var(--text); }
.cal-evt-rsvp-on { background: var(--accent); color: var(--bg); }
.cal-evt-rsvp-on:hover { color: var(--bg); }
.cal-evt-rsvp-declined.cal-evt-rsvp-on { background: var(--danger); }
.kb-detail-body {
  flex: 1; overflow-y: auto;
  padding: 18px 18px 24px;
  display: flex; flex-direction: column; gap: 14px;
}
.kb-detail-row { display: flex; flex-direction: column; gap: 4px; }
.kb-detail-label {
  color: var(--text-muted); font-size: 0.7em;
  letter-spacing: 2px; text-transform: uppercase;
}
.kb-detail-row .input,
.kb-detail-row textarea.input,
.kb-detail-row select.input {
  width: 100%;
}

/* Meeting block: location + participants + Google Meet toggle. */
.kb-detail-meet-wrap { display: flex; flex-direction: column; gap: 6px; }
.kb-meet-label { margin-bottom: 2px; }
.kb-meet-row { gap: 6px; }
.kb-meet-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.kb-meet-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--bg-input); border: 1px solid var(--border);
  border-radius: 12px; padding: 2px 6px 2px 10px;
  font-size: 0.82em; color: var(--text); max-width: 100%;
}
.kb-meet-chip-x {
  background: none; border: 0; cursor: pointer;
  color: var(--text-muted); font-size: 1.05em; line-height: 1;
  padding: 0 2px; border-radius: 50%;
}
.kb-meet-chip-x:hover { color: var(--danger); }
.kb-meet-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 0.82em; color: var(--text-muted); cursor: pointer; width: fit-content;
}
.kb-meet-toggle input { cursor: pointer; }
.kb-meet-hint { font-size: 0.78em; color: var(--text-dim); }
/* The room itself. Given real weight — it's the thing you click when the
   meeting starts, and a ticked checkbox alone left people with no way in. */
.kb-meet-join {
  display: inline-flex; align-items: center; gap: 7px; width: fit-content;
  min-height: 44px; padding: 8px 14px; margin-top: 2px;
  border: 1px solid var(--accent); color: var(--accent);
  font-size: 0.85em; text-decoration: none; transition: all 0.15s;
}
.kb-meet-join:hover { background: var(--accent); color: var(--bg); }
.kb-meet-join:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.kb-meet-join .icon { width: 15px; height: 15px; }
.kb-meet-pending { font-size: 0.78em; color: var(--text-dim); font-style: italic; }
/* Minting a Meet room is a round trip plus a poll — say so, in place, instead
   of leaving a ticked box and nothing to click. */
.kb-meet-creating {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px;
  font-size: 0.82em; color: var(--text-secondary);
}
.kb-meet-spinner {
  width: 13px; height: 13px; flex: none; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: kb-meet-spin 0.7s linear infinite;
}
@keyframes kb-meet-spin { to { transform: rotate(360deg); } }
/* Reduced motion: hold the ring still and pulse opacity instead — the point is
   "something is happening", which a fade carries without the rotation. */
@media (prefers-reduced-motion: reduce) {
  .kb-meet-spinner { animation: kb-meet-fade 1.2s ease-in-out infinite; }
  @keyframes kb-meet-fade { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
}

/* Location autocomplete + "Open in Maps". Shares the .kb-meet-suggest dropdown
   with the contact picker — same anchor, same rows, same keyboard model — so
   the two fields in this block behave identically. */
.kb-meet-loc-combo { width: 100%; }
/* The slot is always in the DOM so the link can be repainted in place, but an
   empty one must not spend the row's 4px gap. */
.kb-meet-map-slot { display: flex; flex-direction: column; gap: 4px; }
.kb-meet-map-slot:empty { display: none; }
/* The embed renders in Google's own light styling whatever the theme, so it is
   framed rather than blended — a deliberate inset panel reads as an embedded
   map, where a half-hearted tint would just look like a rendering bug. */
.kb-meet-map-frame {
  display: block; width: 100%; height: 160px;
  border: 1px solid var(--border); border-radius: 2px;
  background: var(--bg-input);
}
.kb-meet-map {
  display: inline-flex; align-items: center; gap: 6px; width: fit-content;
  min-height: 44px; padding: 4px 2px;
  font-size: 0.8em; color: var(--text-muted); text-decoration: none;
}
.kb-meet-map:hover { color: var(--accent); }
.kb-meet-map:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.kb-meet-map .icon { width: 14px; height: 14px; }
/* The pin sits in the same 28px well the contact avatars use, so the two lists
   share one left edge. */
.kb-meet-suggest-avatar-fallback .icon { width: 14px; height: 14px; }

/* Contact autocomplete. The list is anchored to the input; the two sources
   (saved contacts vs people you've emailed) carry their own headers because
   the distinction is meaningful, not decorative. */
.kb-meet-combo { position: relative; }
.kb-meet-suggest {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 60;
  max-height: 288px; overflow-y: auto;
  background: var(--bg-elevated); border: 1px solid var(--border);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
}
.kb-meet-suggest[hidden] { display: none; }
.kb-meet-suggest-head {
  font-size: 0.68em; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--text-dim); padding: 8px 10px 4px;
  border-top: 1px solid var(--border);
}
.kb-meet-suggest-head:first-child { border-top: 0; }
.kb-meet-suggest-row {
  display: flex; align-items: center; gap: 9px; width: 100%;
  min-height: 44px; padding: 6px 10px;
  background: none; border: 0; cursor: pointer; text-align: left;
  font-family: inherit; font-size: 0.85em; color: var(--text);
}
/* NOT --bg-input: it resolves to the same colour as --bg-elevated on the sand
   and light themes, so the highlight vanished against the dropdown. --bg
   differs from --bg-elevated on all three themes, and the accent rule makes
   the selected row unambiguous rather than relying on a faint tint alone. */
.kb-meet-suggest-row:hover,
.kb-meet-suggest-row-active {
  background: var(--bg);
  box-shadow: inset 2px 0 0 var(--accent);
}
.kb-meet-suggest-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.kb-meet-suggest-avatar {
  width: 28px; height: 28px; flex: 0 0 28px; border-radius: 50%; object-fit: cover;
}
.kb-meet-suggest-avatar-fallback {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg-input); border: 1px solid var(--border);
  color: var(--text-muted); font-size: 0.8em;
}
.kb-meet-suggest-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.kb-meet-suggest-name,
.kb-meet-suggest-email { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kb-meet-suggest-email { font-size: 0.85em; color: var(--text-muted); }
.kb-meet-suggest-tag {
  flex: 0 0 auto; font-size: 0.68em; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-dim); border: 1px solid var(--border); padding: 1px 5px;
}
.kb-meet-suggest-tag-acct { text-transform: none; letter-spacing: 0; }
/* Grant offer as a footer under real results, so asking for the contacts scope
   never costs the user the suggestions the directory already found. */
.kb-meet-suggest-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 10px; border-top: 1px solid var(--border);
  font-size: 0.78em; color: var(--text-muted);
}
.kb-meet-suggest-empty,
.kb-meet-suggest-prompt { padding: 10px; font-size: 0.8em; color: var(--text-muted); }
.kb-meet-suggest-prompt { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.kb-meet-suggest-prompt-text { color: var(--text); }
.kb-meet-suggest-prompt-note { font-size: 0.92em; color: var(--text-dim); }
.kb-detail-row textarea.input {
  min-height: 80px; resize: vertical;
}
.kb-detail-meta {
  font-size: 0.7em; color: var(--text-muted);
  padding-top: 10px; border-top: 1px solid var(--border-light);
  display: flex; flex-direction: column; gap: 3px;
}
.kb-detail-meta a { color: var(--text-secondary); }
.kb-detail-actions {
  display: flex; gap: 6px; flex-wrap: wrap;
  padding: 12px 16px;
  border-top: 1px solid var(--border-light);
  flex-shrink: 0;
  background: var(--bg-elevated);
}
.kb-detail-actions .kb-action { flex: 0 1 auto; }
.kb-detail-actions-spacer { flex: 1; }

/* Calendar-event link — own drawer row promoted out of the meta
 * block. Read-only; clicking opens the event in Google Calendar. */
.kb-detail-cal-link {
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.9em;
  padding: 6px 0;
  display: inline-block;
}
.kb-detail-cal-link:hover { color: var(--accent); text-decoration: underline; }

/* ── Notes thread (drawer) ─────────────────────────────────────
   A multi-author discussion attached to the task. Lives at the
   bottom of the drawer body, separated from the form fields and
   meta block by its own divider. Visual rhythm follows the same
   uppercase-label / muted-meta pattern as the rest of the drawer. */
/* Work breakdown — parent link + subtask list in the task drawer. Shares the
   notes section's shell so the two read as peers down the drawer. */
.kb-wbs {
  display: flex; flex-direction: column; gap: 8px;
  padding-top: 14px;
  border-top: 1px solid var(--border-light);
}
.kb-wbs-header {
  color: var(--text-muted); font-size: 0.7em;
  letter-spacing: 2px; text-transform: uppercase;
}
.kb-wbs-row { display: flex; align-items: center; gap: 8px; }
.kb-wbs-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.kb-wbs-item {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 8px;
  border: 1px solid var(--border-light);
  background: var(--bg-elevated);
}
/* Titles are buttons (they open that task's drawer), so the button chrome is
   stripped back to text and the row stays readable as a list. */
.kb-wbs-link {
  flex: 1; min-width: 0;
  background: none; border: 0; padding: 4px 0;
  font-family: inherit; font-size: 0.9em; text-align: left;
  color: var(--text); cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kb-wbs-link:hover { color: var(--accent); }
.kb-wbs-size {
  flex: none; color: var(--text-muted); font-size: 0.7em;
  letter-spacing: 1px; font-variant-numeric: tabular-nums;
}
.kb-wbs-x {
  flex: none;
  min-width: 28px; min-height: 28px;
  background: none; border: 0; padding: 0;
  font-family: inherit; font-size: 0.8em;
  color: var(--text-muted); cursor: pointer;
}
.kb-wbs-x:hover { color: var(--accent); }
.kb-wbs-add {
  align-self: flex-start;
  background: none; border: 1px dashed var(--border-light);
  padding: 6px 10px; min-height: 32px;
  font-family: inherit; font-size: 0.8em;
  letter-spacing: 1px; text-transform: uppercase;
  color: var(--text-muted); cursor: pointer;
}
.kb-wbs-add:hover { color: var(--text); border-color: var(--border); }
.kb-wbs-search, .kb-wbs-new { flex: 1; font-size: 0.9em; }
.kb-wbs-results { display: flex; flex-direction: column; gap: 2px; }
.kb-wbs-result {
  display: flex; align-items: baseline; gap: 8px;
  background: none; border: 1px solid transparent;
  padding: 6px 8px; min-height: 32px;
  font-family: inherit; font-size: 0.9em; text-align: left;
  color: var(--text); cursor: pointer;
}
.kb-wbs-result:hover { border-color: var(--border-light); background: var(--bg-elevated); }
.kb-wbs-result-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kb-wbs-result-proj { flex: none; color: var(--text-muted); font-size: 0.75em; letter-spacing: 1px; }
.kb-wbs-none { color: var(--text-dim); font-size: 0.85em; padding: 4px 0; }
.kb-wbs-work {
  color: var(--text-muted); font-size: 0.75em;
  letter-spacing: 1px; font-variant-numeric: tabular-nums;
}
.kb-wbs-link:focus-visible,
.kb-wbs-x:focus-visible,
.kb-wbs-add:focus-visible,
.kb-wbs-result:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Touch needs the full target even though the desktop list stays dense. */
@media (pointer: coarse) {
  .kb-wbs-x { min-width: 44px; min-height: 44px; }
  .kb-wbs-add, .kb-wbs-result { min-height: 44px; }
}

.kb-notes {
  display: flex; flex-direction: column; gap: 10px;
  padding-top: 14px;
  border-top: 1px solid var(--border-light);
}
.kb-notes-header {
  color: var(--text-muted); font-size: 0.7em;
  letter-spacing: 2px; text-transform: uppercase;
}
.kb-notes-thread { display: flex; flex-direction: column; gap: 10px; }
.kb-notes-empty {
  color: var(--text-dim); font-size: 0.85em; padding: 4px 0;
}
.kb-note {
  display: flex; flex-direction: column; gap: 4px;
  padding: 8px 10px;
  border: 1px solid var(--border-light);
  background: var(--bg-elevated);
}
.kb-note-editing { border-color: var(--border); }
.kb-note-meta {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: 0.7em; color: var(--text-muted);
  letter-spacing: 1px; text-transform: uppercase;
}
.kb-note-author { color: var(--text-secondary); }
.kb-note-time { color: var(--text-muted); }
.kb-note-actions-inline { margin-left: auto; display: flex; gap: 8px; }
.kb-note-link {
  background: none; border: 0; padding: 0;
  font-family: inherit; font-size: inherit;
  color: var(--text-muted); cursor: pointer;
  text-transform: uppercase; letter-spacing: 1px;
}
.kb-note-link:hover { color: var(--text); }
.kb-note-link-danger:hover { color: var(--accent); }
.kb-note-body {
  color: var(--text); font-size: 0.95em; line-height: 1.5;
  word-wrap: break-word; overflow-wrap: anywhere;
}
.kb-note-body p { margin: 0 0 6px; }
.kb-note-body p:last-child { margin-bottom: 0; }
.kb-note-body a { color: var(--accent); }
.kb-note-body code {
  font-family: inherit; background: var(--bg-input);
  padding: 1px 4px; border: 1px solid var(--border-light);
}
.kb-note-body pre {
  background: var(--bg-input); border: 1px solid var(--border-light);
  padding: 8px 10px; overflow-x: auto;
}
.kb-note-body ul, .kb-note-body ol { margin: 0 0 6px 18px; padding: 0; }
.kb-note-body blockquote {
  margin: 0 0 6px; padding-left: 10px;
  border-left: 2px solid var(--border);
  color: var(--text-secondary);
}
.kb-note-fallback { white-space: pre-wrap; }
.kb-note-edit-input { min-height: 60px; resize: vertical; }
.kb-note-actions {
  display: flex; gap: 6px; align-items: center;
  margin-top: 4px;
}
.kb-notes-composer {
  display: flex; flex-direction: column; gap: 8px;
  margin-top: 2px;
}
.kb-notes-composer-input { width: 100%; min-height: 60px; resize: vertical; }
/* Boxed, clearly-clickable Post action below the textarea (overrides the
   transparent .kb-action base, which read as plain text). */
.kb-notes-post {
  align-self: flex-end;
  padding: 8px 20px;
  font-size: 0.72em; letter-spacing: 2px; text-transform: uppercase;
  border: 1px solid var(--border); background: var(--bg-input);
  color: var(--text-secondary); transition: all 0.15s;
}
.kb-notes-post:hover {
  border-color: var(--accent); color: var(--accent); background: var(--bg-elevated);
}
.kb-notes-post:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Emailed attachments + the inbound-address affordance. Shares the notes
   block's tokens so it reads as part of the same panel. */
.kb-attachments { display: flex; flex-direction: column; gap: 6px; }
.kb-attachments:empty { display: none; }
.kb-attachments-header {
  color: var(--text-muted); font-size: 0.7em;
  letter-spacing: 2px; text-transform: uppercase;
}
.kb-attachment {
  display: flex; align-items: center; gap: 8px;
  width: 100%; min-height: 40px; padding: 6px 10px;
  border: 1px solid var(--border-light); background: var(--bg-elevated);
  color: var(--text); font-family: inherit; font-size: 0.9em;
  text-align: left; cursor: pointer;
}
.kb-attachment:hover { border-color: var(--border); }
.kb-attachment:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.kb-attachment .icon { width: 16px; height: 16px; flex: 0 0 auto; color: var(--text-muted); }
.kb-attachment-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kb-attachment-meta {
  flex: 0 0 auto; color: var(--text-muted); font-size: 0.85em;
  font-variant-numeric: tabular-nums; text-transform: uppercase; letter-spacing: 1px;
}

.kb-task-email {
  display: flex; flex-direction: column; gap: 4px;
  padding-top: 10px; margin-top: 2px;
  border-top: 1px solid var(--border-light);
}
.kb-task-email-label {
  display: flex; align-items: center; gap: 6px;
  color: var(--text-muted); font-size: 0.7em;
  letter-spacing: 1.5px; text-transform: uppercase;
}
.kb-task-email-label .icon { width: 14px; height: 14px; color: var(--text-muted); }
.kb-task-email-addr {
  display: inline-flex; align-items: center; gap: 8px;
  align-self: flex-start; max-width: 100%;
  padding: 4px 0;                       /* flush-left, seamless (no box) */
  border: 1px solid transparent; background: transparent;
  color: var(--text); font-family: inherit; font-size: 0.9em;
  cursor: pointer; text-align: left;
}
.kb-task-email-addr .kb-task-email-text { word-break: break-all; }
.kb-task-email-addr .icon { width: 14px; height: 14px; flex: 0 0 auto; color: var(--text-muted); }
.kb-task-email-addr:hover:not([disabled]) { color: var(--accent); }
.kb-task-email-addr:hover:not([disabled]) .icon { color: var(--accent); }
.kb-task-email-addr:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.kb-task-email-addr[disabled] { color: var(--text-dim); cursor: default; }

/* Project edit modal: inbound email row. The .kb-task-email-addr button
   reuses the task drawer's styling; this rule just wires the row spacing
   and the short hint line below it. */
.kb-detail-hint {
  margin: 4px 0 0;
  color: var(--text-muted);
  font-size: 0.78em;
  line-height: 1.5;
}
.kb-detail-hint a { color: var(--accent); text-decoration: underline; }
.kb-detail-hint a:hover { color: var(--text); }

/* Mobile: full-width bottom sheet (no width constraint, no centering
   transform). On a phone the contextual-card framing is unnecessary —
   tappable form controls need every pixel of width. Different keyframe
   names so the desktop translateX(-50%) doesn't apply here. */
@keyframes kb-detail-panel-in-mob {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}
@keyframes kb-detail-panel-out-mob {
  from { transform: translateY(0); }
  to   { transform: translateY(100%); }
}
@media (max-width: 900px) {
  .kb-detail-panel {
    /* Reset desktop's vertical-center anchoring; mobile is a
       bottom-anchored sheet meeting the screen edge. */
    top: auto;
    bottom: 0;
    left: 0; right: 0;
    width: 100%; max-width: 100%;
    height: 92vh; max-height: 92vh;
    border-left: none;
    border-right: none;
    border-bottom: none;
    border-radius: 0;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    transform: none;
    animation-name: kb-detail-panel-in-mob;
  }
  .kb-detail-panel-closing {
    animation-name: kb-detail-panel-out-mob;
  }
  /* Stack the two columns: main (title/description/notes) first, then the
     properties rail — and let the whole sheet scroll as one, rather than two
     nested scroll areas. */
  .kb-detail-body.kb-detail-body-split {
    display: block;
    overflow-y: auto;
  }
  .kb-detail-main {
    overflow: visible;
    border-right: none;
    border-bottom: 1px solid var(--border-light);
    padding-bottom: 16px;
  }
  .kb-detail-rail { overflow: visible; background: transparent; }
  .kb-detail-main .kb-notes,
  .kb-detail-main .kb-notes-thread {
    flex: none; min-height: 0; overflow: visible;
  }
}

@media (prefers-reduced-motion: reduce) {
  .kb-detail-panel,
  .kb-detail-backdrop,
  .kb-chip-popover { animation: none; }
}

/* ─── CROSS-WORKSPACE PROJECTS ─────────────────────────────── */
/* Tokens for surfacing tasks/projects you have access to via direct project
   membership (not via the project's home workspace). Distinct steel-blue from
   the orange accent and amber delegate so signals don't conflict. Mirrored
   for the light theme. */
:root {
  --shared: #7aa6c2;
  --shared-bg: #0e1a22;
  --shared-border: #2a4a5e;
}
[data-theme="light"] {
  --shared: #4c5963;            /* dust-700 — warm steel, AA on cream */
  --shared-bg: #cdd6db;         /* dust-50 */
  --shared-border: #7c8a94;     /* mid dust */
}

/* Shared-project filter chip (mock variant of .kb-chip). Reuses .kb-chip
   structurally so layout, hover, and active states stay aligned. */
.kb-chip-shared { color: var(--shared); border-color: var(--shared); }
.kb-chip-shared:hover { color: var(--shared); border-color: var(--shared); }
.kb-chip-shared.kb-chip-active {
  background: var(--shared-bg); color: var(--shared); border-color: var(--shared);
}

/* Card-level badge naming the project that brought a task into the user's
   kanban via cross-workspace project membership. Worn by tasks whose project's
   home workspace is NOT one the user belongs to. */
.ws-badge-shared {
  display: inline-block; vertical-align: middle;
  border: 1px solid var(--shared-border); background: var(--shared-bg);
  color: var(--shared);
  padding: 1px 6px; margin-left: 6px;
  font-size: 0.7em; letter-spacing: 1px; border-radius: 2px;
  white-space: nowrap;
}

/* Project members section inside the project edit modal. */
.members-section {
  margin-top: 18px; padding-top: 16px;
  border-top: 1px solid var(--border-light);
}
.members-section-label {
  display: block; color: var(--text-muted);
  font-size: 0.7em; letter-spacing: 2px; margin-bottom: 8px;
}
.members-list { display: flex; flex-direction: column; gap: 6px; }
.member-row {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  padding: 8px 10px; background: var(--bg-input);
  border: 1px solid var(--border-light);
  font-size: 0.84em; border-radius: 2px;
}
.member-row-pending { opacity: 0.7; }
.member-meta { color: var(--text-muted); font-size: 0.78em; }
.member-role-tag {
  color: var(--text-secondary); border: 1px solid var(--border);
  padding: 2px 8px; font-size: 0.72em; letter-spacing: 1px; border-radius: 2px;
}
.member-role-owner { color: var(--accent); border-color: var(--accent); }
.member-role-pending { color: var(--warning); border-color: var(--warning); }
.member-remove {
  background: none; border: none; color: var(--text-muted);
  font: inherit; font-size: 0.85em; cursor: pointer; padding: 2px 6px;
}
.member-remove:hover { color: var(--danger); }
.member-copy-link {
  background: none; border: none; color: var(--text-muted);
  font: inherit; font-size: 0.78em; letter-spacing: 0.5px;
  cursor: pointer; padding: 2px 6px;
}
.member-copy-link:hover { color: var(--accent); }
/* Stacked so the email field gets the full row even inside narrow project
   cards on the Projects tab. Role + button share a tighter second row. */
.invite-row { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.invite-row > .input { width: 100%; }
.invite-row-actions { display: flex; gap: 6px; }
.invite-row-actions select.input { flex: 0 0 110px; }
.invite-row-actions .btn { white-space: nowrap; }

/* ── Project page (#/w/<ws>/projects/<id>) and access-error page ── */
.project-page { max-width: 760px; margin: 0 auto; padding: 24px 20px 60px; }
.btn-link {
  background: none; border: none; color: var(--text-muted);
  font: inherit; cursor: pointer; padding: 4px 0;
}
.btn-link:hover { color: var(--text-primary); }
.project-page-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; margin-bottom: 12px;
}
.project-page-title { margin: 0; font-size: 1.6em; line-height: 1.2; }
.project-page-desc { color: var(--text-secondary); margin: 0 0 24px; }
.project-section { margin: 28px 0; }
.project-section-h {
  display: flex; align-items: center; gap: 8px;
  font-size: 0.78em; letter-spacing: 1px; color: var(--text-muted);
  border-bottom: 1px solid var(--border); padding-bottom: 6px; margin: 0 0 10px;
}
.project-section-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.project-section-count {
  margin-left: auto; color: var(--text-dim); font-variant-numeric: tabular-nums;
}
.project-section-empty { color: var(--text-muted); font-size: 0.9em; padding: 6px 0; }
.project-task-list { list-style: none; margin: 0; padding: 0; }
.project-task-row {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 8px 0; border-bottom: 1px dashed var(--border);
}
.project-task-row:last-child { border-bottom: none; }
.project-task-title { color: var(--text-primary); flex: 1; min-width: 0; }
.project-task-when { color: var(--text-muted); font-size: 0.85em; white-space: nowrap; }
.project-task-chips { display: inline-flex; gap: 6px; flex-wrap: wrap; flex: none; }
.project-task-row-done .project-task-title {
  color: var(--text-muted); text-decoration: line-through; text-decoration-color: var(--text-dim);
}

/* Project detail: at-a-glance progress hero */
.project-progress { margin: 0 0 28px; }
.project-progress-track {
  height: 6px; border-radius: 3px; background: var(--border); overflow: hidden;
}
.project-progress-fill {
  height: 100%; border-radius: 3px; background: var(--col-done-accent, var(--accent));
}
@media (prefers-reduced-motion: no-preference) {
  .project-progress-fill { transition: width 300ms ease; }
}
.project-progress-meta {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-top: 8px;
}
.project-progress-count {
  color: var(--text-primary); font-size: 0.9em; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.project-counts { display: inline-flex; gap: 14px; flex-wrap: wrap; }
.project-count {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--text-muted); font-size: 0.82em; font-variant-numeric: tabular-nums;
}
.project-count-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }

/* ─── PROJECT PM VIEW ─────────────────────────────────────────
   One token-driven layout that re-skins per active theme. Uses only
   semantic tokens (--success/--warning/--danger/--google-blue/--text-*),
   never --col-*-accent. */
.pj-body { margin-top: 8px; }
.pj-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; border-bottom: 1px solid var(--border); padding-bottom: 18px; }
.pj-chips { display: flex; gap: 7px; flex-wrap: wrap; margin-bottom: 10px; }
.pj-chip { font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--text-muted); border: 1px solid var(--border); border-radius: 3px; padding: 2px 8px; }
.pj-chip-accent { color: var(--accent); border-color: var(--accent); }
/* Status chip is a dropdown. A native select sizes to its widest option, so
   the visible text is a span and the select sits over it invisibly — the chip
   keeps its shape, the caret is the only tell, and the menu still opens native. */
.pj-chip-sel { position: relative; cursor: pointer; }
.pj-chip-sel:hover { color: var(--text); border-color: var(--text-muted); }
.pj-chip-sel select { position: absolute; inset: 0; width: 100%; opacity: 0; cursor: pointer; }
.pj-chip-sel:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Health chip: the rag colour is carried by the dot and border, never the
   text — --warning/--danger don't clear contrast as 10px type on the paper themes. */
.pj-chip-health {
  display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
  background: none; font-family: inherit; border-color: var(--pjs-rag);
}
.pj-chip-health i { width: 7px; height: 7px; border-radius: 50%; background: var(--pjs-rag); flex: none; }
.pj-chip-health:hover { color: var(--text); }
.pj-chip-health:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The set-status popover hangs under the chip row. */
.pj-chips-wrap { position: relative; }
.pj-chips-wrap .pjs-pop { left: 0; right: auto; top: calc(100% + 6px); }
.pj-title { margin: 0; font-size: 30px; line-height: 1.1; font-weight: 600; letter-spacing: -0.01em; }
.pj-goal { display: block; margin: 12px 0 0; max-width: 62ch; }
.pj-goal-lbl { display: block; font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-dim); margin-bottom: 3px; }
.pj-head-right { text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 12px; flex: none; }
.pj-team-lbl { font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-dim); }
.pj-team { display: flex; }
.pj-av { width: 28px; height: 28px; border-radius: 50%; background: var(--bg-input); border: 1px solid var(--border); color: var(--text-secondary); display: grid; place-items: center; font-size: 10px; font-weight: 600; margin-left: -6px; }
.pj-av:first-child { margin-left: 0; }
.pj-av-none { color: var(--text-dim); }

/* See the SUBNAV block. The rule stays here because .pj-tools (search + jump
   links) shares this row, and it's what separates that whole row from the
   panel below; the padding keeps the pills off it. */
.pj-tabs { border-bottom: 1px solid var(--border); margin: 20px 0 22px; padding-bottom: 8px; }
.pj-panel { display: none; }
.pj-panel.active { display: block; }

.pj-kpis { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-bottom: 20px; }
.pj-kpi { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px; box-shadow: var(--card-shadow); padding: 13px 14px; }
.pj-kpi-v { font-size: 26px; font-weight: 600; line-height: 1; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
.pj-kpi-l { margin-top: 7px; font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--text-muted); }
.pj-kpi-s { margin-top: 3px; font-size: 10.5px; color: var(--text-dim); }
.pj-kpi-alert { border-color: var(--danger); }
.pj-kpi-alert .pj-kpi-v { color: var(--danger); }

.pj-progress { margin: 0 0 22px; }
.pj-progress-track { height: 8px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.pj-progress-fill { height: 100%; background: var(--success); }

.pj-grid { display: grid; gap: 20px; }
.pj-grid-2 { grid-template-columns: 1.4fr 1fr; }
.pj-grid-2b { grid-template-columns: 1fr 1fr; }
@media (max-width: 780px) { .pj-grid-2, .pj-grid-2b { grid-template-columns: 1fr; } .pj-kpis { grid-template-columns: repeat(2, 1fr); } }

.pj-card { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px; box-shadow: var(--card-shadow); padding: 15px 17px; margin-bottom: 20px; }
.pj-card-h { font-size: 10.5px; letter-spacing: 1px; text-transform: uppercase; color: var(--text-muted); margin-bottom: 12px; }

/* Notes panel on the project Notes tab. Reuses the .kb-notes layout
   (border-top + flex column) but sits inside the project body, not the
   task drawer — wrap it in the same card visual rhythm as the rest of
   the project page so it feels native. */
.pj-notes {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-top: 1px solid var(--border); /* override .kb-notes' border-light */
  border-radius: 6px;
  box-shadow: var(--card-shadow);
  padding: 14px 17px 15px;
  margin: 0 0 22px;
}
.pj-notes .kb-notes-header { margin-bottom: 4px; }
/* Notes and Files tabs — cap the measure so prose and file rows stay readable. */
.pj-panel-notes .pj-notes, .pj-files-panel, .pj-panel-milestones .pjs-card { max-width: 780px; }
.pj-tasks-cards { margin-bottom: 22px; }
/* Task titles open the board drawer — same affordance as .home-task-open. */
.pj-task-open { background: none; border: 0; padding: 0; font: inherit; color: inherit; text-align: left; cursor: pointer; max-width: 100%; }
.pj-task-open:hover { text-decoration: underline; text-underline-offset: 2px; }
.pj-task-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.pj-task-open[aria-busy="true"] { opacity: 0.6; cursor: progress; }

/* Jumps: links out to the filtered common views, sat in the tab strip but
   drawn as buttons so they read as "leave the page", not as tabs. */
.pj-tools { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.pj-jumps { display: flex; gap: 6px; }
/* Project search: answers from what the page holds; results as a listbox
   under the field. Hover/active rows use --bg plus an inset rule because
   --bg-elevated and --bg-input are the same colour on the light themes. */
.pj-search { position: relative; }
.pj-search-in {
  width: 200px; min-height: 30px; padding: 4px 10px; border: 1px solid var(--border); border-radius: 4px;
  background: var(--bg-input); font: inherit; font-size: 0.8em; color: var(--text); transition: width 0.15s;
}
.pj-search-in::placeholder { color: var(--text-dim); }
.pj-search-in:focus { outline: 2px solid var(--accent); outline-offset: 1px; width: 300px; }
.pj-search-pop {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 30; width: 400px; max-height: 60vh; overflow: auto;
  padding: 4px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px;
  box-shadow: var(--overlay-shadow, 0 10px 28px -6px rgba(0, 0, 0, 0.25));
}
.pj-search-row {
  display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 10px; align-items: start;
  width: 100%; min-height: 44px; padding: 8px 10px; border: 0; border-radius: 4px; background: none;
  text-align: left; font: inherit; color: var(--text); cursor: pointer;
}
.pj-search-row:hover, .pj-search-row.active { background: var(--bg); box-shadow: inset 2px 0 0 var(--accent); }
.pj-search-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pj-search-kind { padding-top: 3px; font-size: 0.68em; letter-spacing: 1px; text-transform: uppercase; color: var(--text-dim); }
.pj-search-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pj-search-title { font-size: 0.88em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pj-search-meta { font-size: 0.74em; color: var(--text-muted); }
.pj-search-empty { padding: 12px; font-size: 0.85em; color: var(--text-dim); }
.pj-flash { box-shadow: 0 0 0 2px var(--accent); transition: box-shadow 0.8s ease; }
@media (max-width: 960px) {
  .pj-tools { flex-wrap: wrap; width: 100%; margin-left: 0; }
  .pj-search { flex: 1 1 100%; }
  .pj-search-in, .pj-search-in:focus { width: 100%; }
  .pj-search-pop { left: 0; right: auto; width: min(400px, 92vw); }
}
.pj-jump { display: inline-flex; align-items: center; gap: 6px; min-height: 30px; padding: 4px 10px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg-elevated); font-size: 0.8em; color: var(--text-secondary); text-decoration: none; }
.pj-jump:hover { border-color: var(--line-strong, var(--text-dim)); color: var(--text); }
.pj-jump-n { font-size: 0.9em; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.pj-jump-arr { color: var(--text-dim); font-size: 0.9em; }

/* Properties + milestones rail cards */
.pjs-prop { display: grid; grid-template-columns: 78px minmax(0, 1fr); gap: 10px; align-items: center; padding: 8px 15px; border-top: 1px solid var(--border-light); font-size: 0.85em; }
.pjs-card > .pjs-prop:first-of-type { border-top: 0; }
.pjs-prop-k { font-size: 0.78em; letter-spacing: 1px; text-transform: uppercase; color: var(--text-dim); }
.pjs-prop-v { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.pjs-prop-sel { max-width: 100%; padding: 4px 6px; border: 1px solid transparent; border-radius: 4px; background: transparent; font: inherit; font-size: 0.95em; color: var(--text); cursor: pointer; }
.pjs-prop-sel:hover, .pjs-prop-sel:focus { border-color: var(--border); background: var(--bg-elevated); }
.pjs-prop-rag { font-weight: 600; color: var(--pjs-rag); }
.pjs-prop-dim { color: var(--text-dim); font-size: 0.9em; }
/* Members card — the list and invite row come from the shared .members-*
   rules; this only gives them the rail card's padding. */
.pjs-members { padding: 12px 15px 14px; }
.pjs-members .members-list { margin-bottom: 2px; }
/* Archive + delete. Set apart from Properties rather than styled as a warning
   block: the accent belongs on work, not on a control you rarely touch. The
   destructive one is marked by its own button, not by colouring the card. */
.pjs-card-danger .pjs-card-h h2 { color: var(--text-secondary); }
.pjs-danger-hint {
  color: var(--text-muted); font-size: 0.82em; line-height: 1.6;
  margin: 10px 0 0;
}
.pjs-danger-acts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
/* The card has no padding of its own (the header and .pjs-prop rows carry
   theirs), so the body here has to match the 15px rail the properties sit on. */
.pjs-card-danger > :not(.pjs-card-h) { padding-left: 15px; padding-right: 15px; }
.pjs-card-danger > :last-child { padding-bottom: 14px; }
@media (pointer: coarse) {
  .pjs-danger-acts .btn { min-height: 44px; }
}
.pjs-prop-act { margin-left: auto; min-height: 28px; padding: 2px 8px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg-elevated); font: inherit; font-size: 0.82em; color: var(--text-muted); cursor: pointer; }
.pjs-prop-act:hover { color: var(--text); border-color: var(--line-strong, var(--text-dim)); }
.pjs-num { font-variant-numeric: tabular-nums; }
.pjs-ms { display: grid; grid-template-columns: 14px minmax(0, 1fr) auto; gap: 11px; align-items: center; padding: 10px 15px; border-top: 1px solid var(--border-light); }
.pjs-card > .pjs-ms:first-of-type { border-top: 0; }
.pjs-ms .pjs-dia { width: 11px; height: 11px; margin-left: 2px; }
.pjs-ms-hit .pjs-dia { background: var(--success); border-color: var(--success); }
.pjs-ms-late .pjs-dia { border-color: var(--danger); }
.pjs-ms-t { font-size: 0.87em; font-weight: 500; }
.pjs-ms-hit .pjs-ms-t { color: var(--text-muted); }
.pjs-ms-m { margin-top: 2px; font-size: 0.76em; color: var(--text-muted); }
.pjs-ms-a { display: flex; gap: 4px; }
.pjs-ms-a .pjs-prop-act { margin-left: 0; }
.pjs-ms-x { min-width: 28px; padding: 2px 0; text-align: center; }
.pjs-ms-add { display: grid; grid-template-columns: minmax(0, 1fr) 128px auto; gap: 6px; padding: 10px 15px 12px; border-top: 1px dashed var(--border); }
.pjs-ms-add .input { min-height: 32px; padding: 4px 8px; font-size: 0.82em; }
.pj-next { display: flex; align-items: center; gap: 9px; font-weight: 500; }
.pj-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex: none; margin-right: 6px; vertical-align: middle; }
.pj-muted { color: var(--text-dim); }

.pj-distrib { display: flex; height: 26px; border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
.pj-distrib span { display: inline-block; height: 100%; }
.pj-legend { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 10px; }
.pj-leg { font-size: 11px; color: var(--text-muted); display: inline-flex; align-items: center; }
.pj-leg i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; margin-right: 5px; }

.pj-feed { list-style: none; margin: 0; padding: 0; }
.pj-feed li { display: grid; grid-template-columns: 22px 1fr; gap: 11px; padding: 11px 0; border-top: 1px solid var(--border-light); }
.pj-feed li:first-child { border-top: 0; }
.pj-ic { width: 22px; height: 22px; border-radius: 50%; background: var(--bg-input); border: 1px solid var(--border); color: var(--accent); display: grid; place-items: center; font-size: 10px; }
.pj-who { font-weight: 600; }
.pj-feed time { display: block; margin-top: 3px; font-size: 10px; letter-spacing: .5px; text-transform: uppercase; color: var(--text-dim); }
.pj-feed .pj-feed-row-hidden { display: none; }
.pj-feed-pager {
  display: flex; flex-wrap: wrap; gap: 4px;
  justify-content: center; align-items: center;
  margin-top: 10px; padding-top: 8px;
  border-top: 1px solid var(--border-light);
}
.pj-feed-pg {
  background: none; border: 1px solid transparent;
  color: var(--text-muted); font: inherit; font-size: 0.78em;
  min-width: 24px; padding: 3px 7px; cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.pj-feed-pg:hover:not(:disabled) { color: var(--text); border-color: var(--border); }
.pj-feed-pg:disabled { color: var(--text-dim); cursor: not-allowed; opacity: 0.55; }
.pj-feed-pg-active {
  color: var(--accent); border-color: var(--accent);
  background: var(--bg-input);
}
.pj-feed-pg-arr { font-size: 1.05em; line-height: 1; }


/* ─── PROJECT SNAPSHOT (pjs-) ─────────────────────────────────
   The Snapshot panel. Deliberately its own class family: the older
   pj- rules style the Tasks/Stats/Activity tabs and the page header,
   and .pj-chip / .pj-card there mean different things. Semantic
   tokens only, so every theme re-skins it for free. */
.pjs-vd-clear { background: none; border: 0; padding: 0; font: inherit; font-size: 1em; color: var(--text-muted); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }
.pjs-vd-clear:hover { color: var(--text); }

.pjs-btn { min-height: 34px; padding: 7px 12px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); font: inherit; font-size: 0.78em; color: var(--text-secondary); cursor: pointer; }
.pjs-btn:hover { color: var(--text); border-color: var(--text-dim); }
.pjs-btn-primary { background: var(--accent); border-color: var(--accent); color: var(--bg); font-weight: 600; }
.pjs-btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--bg); }

/* Set-status popover. --bg-input equals --bg-elevated on the paper themes,
   so the panel is separated by its border and overlay shadow, not a wash. */
.pjs-pop {
  position: absolute; right: 14px; top: 52px; z-index: 60; width: min(370px, calc(100vw - 48px));
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px;
  box-shadow: var(--overlay-shadow); padding: 15px;
}
.pjs-pop[hidden] { display: none; }
.pjs-pop-l { display: block; font-size: 10px; letter-spacing: 1.3px; text-transform: uppercase; color: var(--text-dim); margin-bottom: 8px; }
.pjs-pop-rags { display: grid; grid-template-columns: repeat(3, 1fr); gap: 7px; margin-bottom: 14px; }
.pjs-pop-rag { display: flex; flex-direction: column; align-items: center; gap: 6px; min-height: 44px; padding: 9px 6px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); font: inherit; font-size: 0.78em; color: var(--text-secondary); cursor: pointer; }
.pjs-pop-rag i { width: 9px; height: 9px; border-radius: 50%; display: block; }
.pjs-pop-rag[aria-pressed="true"] { border-width: 2px; padding: 8px 5px; color: var(--text); font-weight: 600; }
.pjs-pop-rag[data-rag="on_track"][aria-pressed="true"]  { border-color: var(--success); }
.pjs-pop-rag[data-rag="at_risk"][aria-pressed="true"]   { border-color: var(--warning); }
.pjs-pop-rag[data-rag="off_track"][aria-pressed="true"] { border-color: var(--danger); }
.pjs-pop textarea { width: 100%; min-height: 80px; resize: vertical; padding: 9px 10px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg-input); color: var(--text); font: inherit; font-size: 0.9em; }
.pjs-pop-count { display: block; text-align: right; margin-top: 5px; font-size: 10.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.pjs-pop-foot { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
.pjs-pop-drop { margin-left: auto; }

.pjs-card { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px; box-shadow: var(--card-shadow); overflow: hidden; }
.pjs-card-h { display: flex; align-items: center; gap: 10px; padding: 11px 15px; border-bottom: 1px solid var(--border-light); }
.pjs-card-h h2 { margin: 0; font-size: 0.85em; font-weight: 600; }
.pjs-hint { font-size: 0.76em; color: var(--text-dim); }
.pjs-n { margin-left: auto; font-size: 0.76em; color: var(--text-dim); font-variant-numeric: tabular-nums; }

/* Runway */
.pjs-runway { margin-bottom: 16px; }
.pjs-spine-wrap { overflow-x: auto; padding: 4px 0 0; }
.pjs-spine { display: block; min-width: 820px; width: 100%; }
.pjs-spine text { font-size: 9px; fill: var(--text-dim); font-variant-numeric: tabular-nums; }
.pjs-spine text.pjs-ms-t { font-size: 11px; font-weight: 600; fill: var(--text); }
.pjs-spine text.pjs-ms-d { font-size: 9px; fill: var(--text-muted); }
.pjs-spine text.pjs-lane { font-size: 8.5px; letter-spacing: 1px; fill: var(--text-dim); }
.pjs-legend { display: flex; flex-wrap: wrap; gap: 14px; padding: 4px 15px 13px; font-size: 0.74em; color: var(--text-muted); }
.pjs-legend span { display: inline-flex; align-items: center; gap: 6px; }
.pjs-legend i { display: block; flex: none; }
.pjs-lg-di { width: 9px; height: 9px; transform: rotate(45deg); }
.pjs-lg-hit { background: var(--success); }
.pjs-lg-miss { border: 2px solid var(--danger); }
.pjs-lg-ahead { border: 2px solid var(--text-dim); }
.pjs-lg-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--success); }
.pjs-lg-bk { width: 8px; height: 12px; background: var(--google-blue); }
.pjs-lg-bkx { width: 8px; height: 12px; background: var(--bg); border: 1px solid var(--warning); }

/* Two-column body */
.pjs-cols { display: grid; grid-template-columns: minmax(0, 1.85fr) minmax(290px, 0.85fr); gap: 16px; align-items: start; }
.pjs-sechead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 11px; }
.pjs-sechead h2 { margin: 0; font-size: 1em; font-weight: 600; }
.pjs-seg { margin-left: auto; display: flex; border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
.pjs-seg button { min-height: 34px; padding: 6px 12px; border: 0; background: var(--bg-elevated); font: inherit; font-size: 0.78em; color: var(--text-muted); cursor: pointer; }
.pjs-seg button + button { border-left: 1px solid var(--border); }
.pjs-seg button[aria-pressed="true"] { background: var(--bg); color: var(--text); font-weight: 600; }

/* Milestone gates */
.pjs-gate { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px; box-shadow: var(--card-shadow); margin-bottom: 12px; overflow: hidden; }
.pjs-gate-h { display: grid; grid-template-columns: auto 1fr auto; gap: 12px; align-items: center; padding: 12px 15px; border-bottom: 1px solid var(--border-light); background: var(--bg); }
.pjs-gate-hit .pjs-gate-h  { box-shadow: inset 3px 0 0 var(--success); }
.pjs-gate-late .pjs-gate-h { box-shadow: inset 3px 0 0 var(--danger); }
.pjs-dia { width: 14px; height: 14px; transform: rotate(45deg); border: 2px solid var(--text-dim); background: var(--bg-elevated); flex: none; }
.pjs-gate-hit .pjs-dia { background: var(--success); border-color: var(--success); }
.pjs-gate-late .pjs-dia { border-color: var(--danger); }
.pjs-dia-none { border-style: dashed; }
.pjs-gate-t { font-weight: 600; font-size: 0.95em; }
.pjs-gate-m { margin-top: 2px; font-size: 0.78em; color: var(--text-muted); }
.pjs-late { color: var(--danger); font-weight: 600; }
.pjs-soon { color: var(--warning); font-weight: 600; }
.pjs-gate-p { display: flex; align-items: center; gap: 9px; }
.pjs-gate-bar { width: 92px; height: 6px; border-radius: 99px; background: var(--bg); border: 1px solid var(--border); overflow: hidden; }
.pjs-gate-bar i { display: block; height: 100%; background: var(--success); }
.pjs-gate-n { font-size: 0.76em; color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.pjs-gate-foot { padding: 9px 15px; border-top: 1px solid var(--border-light); font-size: 0.78em; color: var(--text-dim); }
.pjs-gate-foot b { color: var(--text-muted); font-weight: 500; }
.pjs-gate-empty { padding: 16px 15px; font-size: 0.82em; color: var(--text-dim); }

/* Task rows */
.pjs-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 11px; align-items: center; min-height: 44px; padding: 9px 15px; border-top: 1px solid var(--border-light); }
.pjs-gate .pjs-row:first-of-type { border-top: 0; }
.pjs-st { width: 9px; height: 9px; border-radius: 50%; display: block; flex: none; }
.pjs-st-lg { width: 11px; height: 11px; }
.pjs-row-t { font-size: 0.9em; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pjs-row-done .pjs-row-t { color: var(--strike); text-decoration: line-through; }
.pjs-row-sub-l { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 4px; }
.pjs-row-note { font-size: 0.76em; color: var(--text-muted); }
.pjs-row-r { display: flex; align-items: center; gap: 8px; flex: none; }
.pjs-row-r .pj-av { margin-left: 0; width: 24px; height: 24px; font-size: 9px; }
.pjs-chip { display: inline-flex; align-items: center; padding: 2px 7px; border: 1px solid var(--border); border-radius: 4px; font-size: 0.72em; color: var(--text-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.pjs-chip-r { color: var(--danger); border-color: var(--danger); }
.pjs-chip-a { color: var(--warning); border-color: var(--warning); }
.pjs-chip-g { color: var(--success); border-color: var(--success); }
.pjs-chip-b { color: var(--google-blue); border-color: var(--google-blue); }
.pjs-chip-plain { border-color: transparent; background: var(--bg); }
.pjs-rollup { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border-radius: 99px; background: var(--bg); font-size: 0.72em; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.pjs-pips { display: flex; gap: 2px; }
.pjs-pips i { width: 8px; height: 4px; border-radius: 1px; background: var(--border); display: block; }
.pjs-pips i.on { background: var(--success); }
/* Subtasks: indented under their parent with a tree rail. */
.pjs-subs { background: var(--bg); border-top: 1px solid var(--border-light); }
.pjs-subs .pjs-row { border-top-color: var(--border); }
.pjs-subs .pjs-row { position: relative; padding-left: 44px; }
.pjs-subs .pjs-row:first-child { border-top: 0; }
.pjs-subs .pjs-row::before { content: ""; position: absolute; left: 27px; top: 0; bottom: 0; width: 1px; background: var(--border); }
.pjs-subs .pjs-row:last-child::before { bottom: 50%; }
.pjs-subs .pjs-row::after { content: ""; position: absolute; left: 27px; top: 50%; width: 10px; height: 1px; background: var(--border); }
.pjs-subs .pjs-row-t { font-size: 0.86em; }

/* Rail */
.pjs-rail { display: flex; flex-direction: column; gap: 14px; position: sticky; top: 16px; }
.pjs-srow { padding: 11px 15px; border-top: 1px solid var(--border-light); }
.pjs-card > .pjs-srow:first-of-type { border-top: 0; }
.pjs-srow-t { font-size: 0.87em; font-weight: 500; }
.pjs-srow-m { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 4px; font-size: 0.76em; color: var(--text-muted); }
.pjs-days { color: var(--danger); font-weight: 600; font-variant-numeric: tabular-nums; }
.pjs-pulse { display: flex; align-items: flex-end; gap: 3px; height: 38px; padding: 13px 15px 6px; }
.pjs-pulse span { flex: 1; background: var(--border); border-radius: 1px; min-height: 3px; }
.pjs-pulse span.on { background: var(--accent); }
.pjs-pulse-x { display: flex; justify-content: space-between; padding: 0 15px 10px; font-size: 10px; color: var(--text-dim); }
.pjs-feed-rail li { padding: 9px 15px; }

@media (max-width: 960px) {
  .pjs-cols { grid-template-columns: 1fr; }
  .pjs-rail { position: static; }
}
@media (max-width: 560px) {
  .pjs-btn, .pjs-seg button { min-height: 44px; }
  .pjs-row { grid-template-columns: auto minmax(0, 1fr); row-gap: 7px; }
  .pjs-row-r { grid-column: 2; justify-content: flex-start; flex-wrap: wrap; }
  .pjs-pop { right: 8px; left: 8px; width: auto; }
}

.pj-grp { margin-bottom: 22px; }
.pj-grp-h { display: flex; align-items: center; gap: 9px; padding-bottom: 7px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.pj-grp-ct { margin-left: auto; font-size: 10.5px; letter-spacing: 1px; text-transform: uppercase; color: var(--text-dim); }
.pj-table { width: 100%; border-collapse: collapse; }
.pj-table th { text-align: left; font-size: 10px; letter-spacing: .5px; text-transform: uppercase; color: var(--text-dim); font-weight: 500; padding: 7px 10px; }
.pj-table td { padding: 9px 10px; border-top: 1px solid var(--border-light); vertical-align: middle; }
.pj-table tbody tr:hover { background: var(--bg-input); }
.pj-t-title { font-weight: 500; }
.pj-row-done .pj-t-title { color: var(--text-dim); text-decoration: line-through; text-decoration-color: var(--strike); }

.pj-bars { display: flex; align-items: flex-end; gap: 12px; height: 120px; }
.pj-bcol { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; gap: 6px; height: 100%; }
.pj-b { width: 72%; background: var(--accent); border-radius: 3px 3px 0 0; min-height: 3px; }
.pj-bval { font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; }
.pj-blab { font-size: 9.5px; color: var(--text-dim); }

.pj-wl { display: grid; grid-template-columns: 96px 1fr 64px; align-items: center; gap: 10px; margin: 9px 0; }
.pj-wl-name { font-size: 0.85em; }
.pj-wl-bar { height: 15px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 4px; display: flex; overflow: hidden; }
.pj-wl-bar span { display: inline-block; height: 100%; }
.pj-wl-n { font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums; text-align: right; }
.pj-over { color: var(--danger); font-weight: 600; }

/* Per-theme heading font — serif in Editorial Sumi, geometric sans in
   Modernist, mono (base) in Dark/Terminal. */
[data-theme="light"] .pj-title { font-family: var(--serif-display); }
[data-theme="modernist"] .pj-title { font-family: var(--sans-display); font-weight: 700; }

.kb-card-link {
  color: inherit; text-decoration: none;
}
.kb-card-link:hover { color: var(--accent); }

.access-error-page {
  max-width: 520px; margin: 80px auto; padding: 0 20px; text-align: center;
}
.access-error-code {
  font-size: 3em; color: var(--text-muted); margin-bottom: 16px; letter-spacing: 2px;
}
.access-error-title { margin: 0 0 12px; font-size: 1.3em; }
.access-error-sub { color: var(--text-muted); margin: 0 0 24px; }

/* ── Booking links ───────────────────────────────────────────────────────── */
.btn-xs { padding: 6px 10px; font-size: 0.75em; }
.settings-empty-hint { color: var(--text-muted); font-size: 0.85em; }
.booking-link-row { align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.booking-link-meta { flex: 1; min-width: 180px; }
.booking-link-url-row { display: flex; align-items: center; gap: 4px; margin-top: 2px; min-height: 32px; }
.booking-link-url { color: var(--text-muted); font-size: 0.76em; word-break: break-all; }
/* Icon-only copy control on the URL line. The visual box stays small so it sits
   with the 0.76em URL; ::after widens the tap target sideways (into the URL text
   and the empty space after it) without reaching the rows above or below, which
   on shared links carry their own buttons. */
.booking-copy-url {
  position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; border: 0; border-radius: 4px;
  background: none; color: var(--text-muted); cursor: pointer;
  transition: color 0.12s ease, background 0.12s ease;
}
.booking-copy-url::after { content: ''; position: absolute; top: 50%; left: 50%; width: 44px; height: 32px; transform: translate(-50%, -50%); }
.booking-copy-url svg { width: 14px; height: 14px; }
.booking-copy-url:hover { color: var(--text); background: var(--bg); }
.booking-copy-url:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.booking-link-dur { color: var(--text-muted); font-weight: 400; }
.booking-link-actions { display: flex; gap: 8px; align-items: center; }
.booking-link-create { gap: 8px; flex-wrap: wrap; }
.booking-link-create .input { flex: 1; min-width: 160px; }
.booking-link-create select.input { flex: 0 0 auto; width: auto; }
/* "Google Meet" opt-in on the create row — sized to sit level with the inputs. */
.booking-create-check {
  display: flex; align-items: center; gap: 6px; flex: 0 0 auto;
  font-size: 0.8em; color: var(--text-secondary); cursor: pointer; white-space: nowrap;
  min-height: 44px; padding: 0 2px;
}
.booking-create-check input { width: auto; margin: 0; cursor: pointer; }
/* Badge on a link row when that link mints a Meet on every booking. */
.booking-meet-badge {
  display: inline-flex; align-items: center; gap: 4px; margin-left: 8px;
  font-size: 0.7em; font-weight: 400; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--text-muted); border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px;
  vertical-align: middle;
}
.booking-meet-badge svg { width: 11px; height: 11px; flex: 0 0 auto; }

/* Edit modal — read-only URL preview line (token is fixed, tail follows title) */
.booking-edit-hint { color: var(--text-muted); font-size: 0.72em; line-height: 1.4; margin: 0 0 4px; word-break: break-all; }

/* Connected Google accounts (Settings → Google Calendar) */
.calendars-block { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.account-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 7px 0; border-bottom: 1px solid var(--border); }
.account-row:last-of-type { border-bottom: none; }
.account-email { font-size: 0.88em; color: var(--text); word-break: break-all; }
.account-tags, .account-actions { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.account-tag { font-size: 0.7em; text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 6px; border-radius: 3px; }
.account-tag-home { color: var(--accent); border: 1px solid var(--accent); }
.account-busy { display: inline-flex; align-items: center; gap: 4px; font-size: 0.8em; color: var(--text-secondary); cursor: pointer; }
.account-busy input { width: 14px; height: 14px; cursor: pointer; }

/* Time report (Time tab) */
.time-report { padding-top: 4px; }
.time-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 18px; }
.time-head-title { font-size: 22px; font-weight: 600; letter-spacing: -0.01em; color: var(--text); }
.time-total { font-size: 34px; font-weight: 600; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; line-height: 1; color: var(--text); }
.time-total-sub { font-size: 0.8em; color: var(--text-muted); margin-top: 4px; }
.time-week-nav { display: flex; align-items: center; gap: 8px; }
.time-week-label { font-size: 0.85em; color: var(--text-secondary); min-width: 78px; text-align: center; }
.time-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 18px; }
.time-stat { border: 1px solid var(--border); border-radius: 10px; padding: 12px 10px; background: var(--bg-elevated, var(--bg-input)); text-align: center; }
.time-stat-v { font-size: 20px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.time-stat-l { font-size: 0.7em; color: var(--text-muted); margin-top: 3px; text-transform: uppercase; letter-spacing: 0.04em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.time-chart { display: flex; justify-content: center; margin: 8px 0 22px; }
.time-donut-top { fill: var(--text); font-size: 22px; font-weight: 600; }
.time-donut-sub { fill: var(--text-muted); font-size: 9px; text-transform: uppercase; letter-spacing: 0.08em; }
.time-swatch { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 7px; vertical-align: middle; }
@media (max-width: 480px) { .time-stats { grid-template-columns: repeat(2, 1fr); } }
.time-controls { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 16px; }
/* Category / project / workspace filter dropdowns. */
.time-filters { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.time-filter-sel {
  background: var(--bg-input); border: 1px solid var(--border); color: var(--text);
  font-family: inherit; font-size: 0.8em; padding: 5px 9px; border-radius: 6px; cursor: pointer;
}
.time-filter-sel:focus { border-color: var(--accent); outline: none; }
/* Time-period selector (This week so far / Planned / Last week / 7 / 30). */
.time-period-sel {
  background: var(--bg-input); border: 1px solid var(--border); color: var(--text);
  font-family: inherit; font-size: 0.85em; padding: 6px 10px; border-radius: 6px; cursor: pointer;
}
.time-period-sel:focus { border-color: var(--accent); outline: none; }
.time-meetings { display: inline-flex; align-items: center; gap: 6px; font-size: 0.82em; color: var(--text-secondary); cursor: pointer; }
.time-meetings input { width: 15px; height: 15px; cursor: pointer; }
.time-dim { display: inline-flex; gap: 4px; border: 1px solid var(--border); border-radius: 8px; padding: 2px; }
.time-dim-btn { background: none; border: none; color: var(--text-muted); font-family: inherit; font-size: 0.8em; padding: 5px 14px; border-radius: 6px; cursor: pointer; }
.time-dim-btn:hover { color: var(--text); }
.time-dim-active { background: var(--accent); color: var(--bg); }
.time-dim-active:hover { color: var(--bg); }
.time-list { display: flex; flex-direction: column; gap: 14px; }
.time-row-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 5px; }
.time-row-label { font-size: 0.92em; color: var(--text); }
.time-row-val { font-size: 0.92em; color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap; }
.time-row-pct { color: var(--text-muted); margin-left: 8px; }
.time-bar { height: 8px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.time-bar-fill { height: 100%; background: var(--accent); border-radius: 6px 0 0 6px; transition: width 0.25s ease; }
.time-row-sub { font-size: 0.72em; color: var(--text-dim); margin-top: 4px; }
.time-foot { margin-top: 22px; font-size: 0.74em; color: var(--text-dim); line-height: 1.5; }

/* Reports tab — a flat export view. Borrows the Time log's table styling so
   the two read as the same family; only the controls row is its own. */
.report-dates { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 14px; }
.report-date { display: inline-flex; align-items: center; gap: 8px; font-size: 0.8em; color: var(--text-secondary); }
.report-date .input { width: auto; min-width: 150px; padding: 7px 10px; font-size: 0.9em; }
.report-controls {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-bottom: 18px;
}
.report-types { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.report-search { flex: 1 1 180px; width: auto; min-width: 160px; max-width: 300px; padding: 8px 10px; font-size: 13px; min-height: 38px; }
.report-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-left: auto; }
/* --text-secondary rather than .btn-outline's --text-muted: muted lands at
   3.7:1 on the light theme, under the floor for text this size. */
.report-actions .btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px; font-size: 12px; min-height: 38px; color: var(--text-secondary);
}
.report-actions .btn:disabled { opacity: 0.45; cursor: default; }
/* No global focus-visible rule in this sheet, so the Reports controls carry
   their own — keyboard users need to see where they are. */
.report-actions .btn:focus-visible,
.report-search:focus-visible,
.report-date .input:focus-visible,
.report-types input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.report-meta { font-size: 12px; color: var(--text-secondary); margin-bottom: 10px; font-variant-numeric: tabular-nums; }
/* A note means the export is incomplete, so the text itself takes --text and
   the warning colour rides on the rule — --warning is 4.05:1 on the light
   theme, fine for a border, not for the words. */
.report-note {
  font-size: 12px; color: var(--text); line-height: 1.5;
  border: 1px solid var(--border); border-left: 3px solid var(--warning);
  border-radius: 8px; padding: 9px 12px; margin-bottom: 12px;
}
/* Absolute sizes below: these sit inside .time-log's 0.85em, so an em-relative
   size compounds down to ~8px and stops being readable. */
.report-proj { color: var(--text-secondary); font-size: 11px; margin-left: 8px; }
.report-kind { white-space: nowrap; }
.report-tag {
  display: inline-block; font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em;
  padding: 3px 7px; border-radius: 4px; border: 1px solid var(--border); color: var(--text-secondary);
}
/* Accent-on-background lands at 3.9:1, under the 4.5:1 floor for text this
   small, so the accent stays on the border and the label takes --text. The
   word itself carries the meaning; the colour only reinforces it. */
.report-tag-task { border-color: var(--accent); color: var(--text); }
@media (max-width: 560px) {
  .report-actions { margin-left: 0; }
  .report-search { max-width: none; }
  /* Touch targets on the surface where fingers actually land. */
  .report-actions .btn { min-height: 44px; flex: 1 1 auto; justify-content: center; }
  .report-search { min-height: 44px; }
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── Observation banner + day strip ─────────────────────────
   Descriptive only: it reports the shape of the period and never scores it,
   so there's no success/failure colour anywhere in this block. */
.time-recap {
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--bg-elevated, var(--bg-input));
  padding: 16px 18px 18px; margin-bottom: 18px;
}
.time-recap-label {
  font-size: 0.66em; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-dim);
}
.time-recap-line {
  font-size: 1.22em; line-height: 1.35; font-weight: 500; letter-spacing: -0.01em;
  color: var(--text); margin: 7px 0 0; text-wrap: balance;
}
.time-recap-sub { font-size: 0.8em; color: var(--text-muted); margin-top: 7px; line-height: 1.5; }

.time-days { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 5px; margin-top: 15px; }
.time-day { display: flex; flex-direction: column; align-items: center; gap: 5px; min-width: 0; }
.time-day-bar {
  width: 100%; height: 42px; border-radius: 4px;
  background: var(--bg-input); border: 1px solid var(--border-light);
  display: flex; align-items: flex-end; overflow: hidden;
}
.time-day-fill { width: 100%; background: var(--accent); border-radius: 3px 3px 0 0; }
.time-day-h { font-size: 0.66em; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.time-day-d { font-size: 0.62em; letter-spacing: 0.06em; color: var(--text-muted); text-transform: uppercase; }

/* ── Hero total ─────────────────────────────────────────── */
.time-hero { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.time-hero-n {
  font-size: 2.6em; font-weight: 600; letter-spacing: -0.03em; line-height: 1;
  color: var(--text); font-variant-numeric: tabular-nums;
}
.time-hero-delta { font-size: 0.8em; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.time-hero-sub { font-size: 0.76em; color: var(--text-secondary); margin-top: 7px; }

/* ── Section headers shared by trend + heatmap ──────────── */
.time-sect { margin-top: 26px; }
.time-sect-h { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.time-sect-l, .time-sect-r {
  font-size: 0.66em; letter-spacing: 0.1em; text-transform: uppercase;
}
.time-sect-l { color: var(--text-secondary); }
.time-sect-r { color: var(--text-secondary); font-variant-numeric: tabular-nums; }

/* ── Hours by work location. Bars are relative to the busiest location, not to
      any target — same observational read as the rest of the tab. ── */
.time-loc-row { display: flex; align-items: center; gap: 12px; padding: 5px 0; }
.time-loc-name {
  flex: 0 0 34%; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 0.82em; color: var(--text);
}
.time-loc-bar { flex: 1 1 auto; height: 6px; background: var(--bg-input); }
.time-loc-bar i { display: block; height: 100%; background: var(--accent); }
.time-loc-val {
  flex: 0 0 auto; font-size: 0.82em; color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* ── 12-week trend. The dashed rule is the rolling average — a reference
      point, not a target to clear. ─────────────────────────── */
.time-trend {
  position: relative; display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: 5px; align-items: end; height: 70px;
}
/* One hue throughout — past weeks sit back, the current week comes forward.
   Opacity rather than a second colour keeps this legible on all four themes. */
.time-trend-bar { background: var(--accent); opacity: 0.32; border-radius: 3px 3px 0 0; min-height: 2px; }
.time-trend-now { opacity: 1; }
.time-trend-avg {
  position: absolute; left: 0; right: 0; height: 0;
  border-top: 1px dashed var(--text-dim); opacity: 0.55; pointer-events: none;
}
.time-trend-axis {
  display: flex; justify-content: space-between; margin-top: 7px;
  font-size: 0.66em; color: var(--text-muted);
}

/* ── Five-week heatmap ──────────────────────────────────── */
/* Month-shaped, so it stays a calendar block rather than stretching to the
   column width — 35 cells look wrong blown up to 90px each. */
.time-heat { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; max-width: 268px; }
.time-heat-dow {
  font-size: 0.6em; color: var(--text-muted); text-align: center;
  text-transform: uppercase; letter-spacing: 0.04em; padding-bottom: 2px;
}
.time-heat-cell { aspect-ratio: 1; border-radius: 3px; background: var(--accent); }
.time-heat-l0 { background: var(--bg-input); border: 1px solid var(--border-light); }
.time-heat-l1 { opacity: 0.3; }
.time-heat-l2 { opacity: 0.62; }
.time-heat-l3 { opacity: 1; }
.time-heat-future { background: transparent; border: 1px dashed var(--border-light); }
.time-heat-key {
  display: flex; align-items: center; gap: 8px; margin-top: 9px;
  font-size: 0.66em; color: var(--text-muted); max-width: 268px;
}
.time-heat-scale { display: inline-flex; gap: 3px; }
.time-heat-scale i { width: 10px; height: 10px; border-radius: 2px; background: var(--accent); }
.time-heat-scale i.time-heat-l0 { background: var(--bg-input); border: 1px solid var(--border-light); }

/* ── Drill-through rows ─────────────────────────────────── */
.time-row-open {
  cursor: pointer; border-radius: 8px;
  margin: -6px -8px; padding: 6px 8px;
  transition: background 0.15s ease;
}
.time-row-open:hover { background: var(--bg-elevated, var(--bg-input)); }
.time-row-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.time-row-open .time-row-label::after {
  content: '›'; color: var(--text-dim); margin-left: 7px; font-size: 1.05em; line-height: 1;
}
.time-row-open:hover .time-row-label::after { color: var(--text-secondary); }

/* ── Detail log ─────────────────────────────────────────── */
.time-back {
  background: none; border: none; color: var(--text-muted); font-family: inherit;
  font-size: 0.78em; padding: 4px 0; margin-bottom: 12px; cursor: pointer;
}
.time-back:hover { color: var(--text); }
.time-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.time-log-wrap { overflow-x: auto; }
.time-log { width: 100%; border-collapse: collapse; font-size: 0.85em; }
.time-log th {
  text-align: left; font-weight: 500; font-size: 0.72em; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--text-secondary);
  padding: 0 12px 8px 0; border-bottom: 1px solid var(--border); white-space: nowrap;
}
.time-log td { padding: 9px 12px 9px 0; border-bottom: 1px solid var(--border-light); color: var(--text-secondary); }
.time-log tbody tr:last-child td { border-bottom: 0; }
.time-log-daysep th {
  padding: 16px 12px 7px 0; border-bottom: 1px solid var(--border-light);
  color: var(--text-secondary); font-size: 0.7em;
}
.time-log-daysep td { padding: 16px 0 7px; border-bottom: 1px solid var(--border-light); }
.time-log tbody tr:first-child th, .time-log tbody tr:first-child td { padding-top: 9px; }
.time-log-t { color: var(--text); }
.time-log-size { color: var(--text-secondary); font-size: 0.85em; letter-spacing: 0.06em; white-space: nowrap; }
/* Beats `.time-log th`'s left-align, which otherwise wins on specificity and
   leaves the Time header adrift from the column it labels. */
.time-log th.time-log-num, .time-log td.time-log-num { text-align: right; }
.time-log-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ── Skeleton (first paint, before the window lands) ────── */
.time-skel {
  height: 10px; border-radius: 5px; background: var(--border-light);
  animation: time-skel-pulse 1.4s ease-in-out infinite;
}
.time-skel-recap {
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--bg-elevated, var(--bg-input));
  padding: 18px; margin-bottom: 18px;
  display: flex; flex-direction: column; gap: 11px;
}
.time-skel-rows { display: flex; flex-direction: column; gap: 18px; }
.time-skel-rows > div { display: flex; flex-direction: column; gap: 7px; }
.time-skel-bar { height: 8px; }
@keyframes time-skel-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) {
  .time-skel { animation: none; }
  .time-row-open { transition: none; }
}

@media (max-width: 560px) {
  .time-recap-line { font-size: 1.08em; }
  .time-hero-n { font-size: 2.1em; }
  .time-day-bar { height: 34px; }
  .time-day-h { font-size: 0.6em; }
}

/* Calendar colour-label editor (Settings) */
.color-label-grid { display: flex; flex-direction: column; gap: 8px; }
.color-label-row { display: flex; align-items: center; gap: 10px; }
.color-label-swatch { width: 16px; height: 16px; border-radius: 4px; flex: 0 0 auto; border: 1px solid rgba(0,0,0,0.2); }
.color-label-name { font-size: 0.76em; color: var(--text-muted); width: 82px; flex: 0 0 auto; }
.color-label-row .input { flex: 1; min-width: 0; }

/* gtd-only categories: same grid, plus a colour well and a remove control.
   The heading carries the "doesn't sync" caveat once instead of per row. */
.cat-custom-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin: 20px 0 6px; padding-top: 14px; border-top: 1px solid var(--border);
}
.cat-custom-title { color: var(--accent); font-size: 0.75em; letter-spacing: 3px; font-weight: 500; }
/* --text, not --text-muted: at this size the muted token lands at 3.7:1 on the
   light themes, and "doesn't sync" is the one thing here you can't afford to
   miss. The accent title beside it still carries the hierarchy. */
.cat-custom-note { color: var(--text); font-size: 0.72em; }
/* 44px hit area; the well itself reads as a swatch. Chrome/Safari draw their
   own padding inside the control, so the border stays on the outside. */
.cat-hex {
  width: 44px; height: 44px; flex: 0 0 auto; padding: 3px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  cursor: pointer;
}
.cat-hex::-webkit-color-swatch-wrapper { padding: 0; }
.cat-hex::-webkit-color-swatch { border: none; border-radius: 3px; }
.cat-hex::-moz-color-swatch { border: none; border-radius: 3px; }
.cat-remove {
  width: 44px; height: 44px; flex: 0 0 auto;
  background: transparent; border: 1px solid var(--border); border-radius: 6px;
  color: var(--text-muted); font-family: inherit; font-size: 0.9em; cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.cat-remove:hover { color: var(--danger); border-color: var(--danger); }

/* Public booking page (no-auth) */
.booking-page {
  min-height: 100vh; display: flex; align-items: flex-start; justify-content: center;
  padding:
    calc(48px + env(safe-area-inset-top, 0px))
    max(16px, env(safe-area-inset-right, 16px))
    calc(48px + env(safe-area-inset-bottom, 0px))
    max(16px, env(safe-area-inset-left, 16px));
  background: var(--bg);
}
.booking-card {
  width: 100%; max-width: 520px; background: var(--bg-elevated);
  border: 1px solid var(--border); border-radius: 8px; padding: 28px;
}
.booking-loading, .booking-empty { color: var(--text-muted); font-size: 0.9em; }
.booking-title { font-size: 1.3em; font-weight: 700; color: var(--text); }
.booking-sub { color: var(--text-muted); font-size: 0.85em; margin-top: 4px; }
.booking-tz-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 16px;
}
.booking-tz-label {
  font-size: 0.72em; letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--text-muted); white-space: nowrap;
}
.booking-tz-combo { position: relative; flex: 1 1 220px; min-width: 200px; }
.booking-tz-input {
  width: 100%; min-height: 40px;
  background: var(--bg-input); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 8px 34px 8px 10px; font-family: inherit; font-size: 0.85em;
  transition: border-color 0.15s;
}
.booking-tz-input:focus { border-color: var(--accent); }
.booking-tz-input::placeholder { color: var(--text-dim); }
.booking-tz-arrow {
  position: absolute; top: 0; right: 0; height: 100%; width: 32px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 0; color: var(--text-muted); cursor: pointer;
}
.booking-tz-arrow svg { width: 16px; height: 16px; }
.booking-tz-panel {
  display: none; position: absolute; z-index: 20;
  top: calc(100% + 4px); left: 0; right: 0;
  max-height: 260px; overflow-y: auto;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: 6px; box-shadow: 0 8px 24px rgba(0,0,0,0.35);
}
.booking-tz-panel.open { display: block; }
.booking-tz-opt {
  padding: 8px 10px; font-size: 0.85em; color: var(--text); cursor: pointer;
}
.booking-tz-opt.active { background: var(--bg-input); color: var(--accent); }
.booking-tz-empty { padding: 10px; font-size: 0.82em; color: var(--text-muted); }
.booking-when-tz { color: var(--text-muted); font-weight: 400; }
.booking-slotwrap { margin-top: 20px; max-height: 46vh; overflow-y: auto; }
.booking-day { margin-bottom: 16px; }
.booking-day-label {
  font-size: 0.72em; letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 8px;
}
.booking-slots { display: flex; flex-wrap: wrap; gap: 8px; }
.booking-slot {
  background: transparent; border: 1px solid var(--border); color: var(--text);
  padding: 8px 14px; min-height: 40px; border-radius: 6px; cursor: pointer;
  font-family: inherit; font-size: 0.85em; transition: border-color 0.15s, color 0.15s;
}
.booking-slot:hover { border-color: var(--accent); }
.booking-slot-selected { border-color: var(--accent); color: var(--accent); }
.booking-form-inner {
  margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 10px;
}
.booking-when { color: var(--text-secondary); font-size: 0.9em; }
.booking-form-actions { display: flex; gap: 8px; margin-top: 4px; }
.booking-confirmed { text-align: center; padding: 24px 0; }
.booking-confirmed-check {
  font-size: 2.4em; color: var(--success); line-height: 1; margin-bottom: 10px;
}

/* Shared (multi-user) booking links */
.shared-booking-row .sb-members { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.sb-member {
  font-size: 0.72em; padding: 2px 8px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--text-secondary);
}
.sb-mem-accepted { border-color: var(--success); color: var(--success); }
.sb-mem-pending  { border-color: var(--border); color: var(--text-muted); }
.sb-mem-declined { border-color: var(--danger); color: var(--danger); opacity: 0.8; }
.sb-member-x {
  background: none; border: none; cursor: pointer; padding: 0 0 0 5px;
  margin-left: 1px; font-size: 1.15em; line-height: 1; color: inherit; opacity: 0.55;
  vertical-align: middle;
}
.sb-member-x:hover { opacity: 1; color: var(--danger); }
.sb-addmember { display: flex; gap: 6px; margin-top: 8px; }
.sb-addmember .input-xs { flex: 1; min-width: 140px; padding: 6px 10px; font-size: 0.78em; }
/* Inline message under the add-member input (non-user → invite-to-sign-up). */
.sb-addmember-msg { margin-top: 6px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 0.78em; }
.sb-addmember-msg:empty { display: none; }
.sb-msg-warn { color: var(--warning); }
/* Pending-invite fallback: copy the invite link if the email didn't arrive. */
.sb-pending-help { margin-top: 6px; font-size: 0.76em; color: var(--text-muted); }
.sb-copy-invite {
  background: none; border: none; padding: 0; font: inherit; font-size: 1em;
  color: var(--accent); cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
}
.sb-copy-invite:hover { color: var(--accent-hover); }
.sb-copy-invite:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sb-msg-ok { color: var(--success); }
.sb-msg-muted { color: var(--text-muted); }
.sb-section-sub {
  font-size: 0.72em; letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--text-muted); margin: 4px 0 8px;
}
.sb-invite-row { align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.sb-tabs { display: flex; gap: 4px; margin: 10px 0 14px; border-bottom: 1px solid var(--border); }
.sb-tab {
  background: transparent; border: none; border-bottom: 2px solid transparent;
  color: var(--text-muted); padding: 8px 12px; cursor: pointer;
  font-family: inherit; font-size: 0.82em; letter-spacing: 0.3px;
}
.sb-tab:hover { color: var(--text); }
.sb-tab-active { color: var(--accent); border-bottom-color: var(--accent); }
.sb-invites-banner {
  border: 1px solid var(--accent); border-radius: 6px; padding: 10px 12px; margin-bottom: 12px;
  background: rgba(199,125,74,0.08);
}
.sb-invites-banner .sb-section-sub { margin-top: 0; color: var(--accent); }
.sb-trust-opt {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 6px;
  font-size: 0.78em; color: var(--text-muted); cursor: pointer;
}
.sb-trust-opt input { cursor: pointer; }
.sb-trust-list { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.sb-trust-row { align-items: flex-start; gap: 12px; }

/* Reasonableness heat-map on the public shared page */
.booking-legend {
  display: flex; flex-wrap: wrap; gap: 14px; margin-top: 14px;
  font-size: 0.72em; color: var(--text-muted);
}
.booking-legend span { display: inline-flex; align-items: center; gap: 5px; }
.booking-sw { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.booking-sw--green  { background: var(--success); }
.booking-sw--orange { background: #c77d4a; }
.booking-sw--black  { background: #2a2a2a; border: 1px solid var(--border); }
.booking-slot--green  { border-color: var(--success); }
.booking-slot--green:hover  { border-color: var(--success); }
.booking-slot--orange { border-color: #c77d4a; color: #c77d4a; }
.booking-slot--orange:hover { border-color: #c77d4a; }
.booking-slot--black  { border-color: #555; color: var(--text-muted); background: rgba(0,0,0,0.18); }
.booking-slot--black:hover  { border-color: #888; }
.booking-offhours-warn {
  font-size: 0.8em; color: #c77d4a; background: rgba(199,125,74,0.1);
  border: 1px solid rgba(199,125,74,0.4); border-radius: 6px; padding: 8px 10px;
}
/* "A Google Meet link will be included" hint on the public booking form. */
.booking-meet-hint { font-size: 0.8em; color: var(--text-muted); }

/* /manage/<token> — the booker's own reschedule / cancel page. Reuses the
   booking page's card, slot grid and form chrome; these carry the parts it
   doesn't share: the "booked for" summary and its two action rows. */
.booking-manage-current {
  margin-top: 16px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 6px;
  display: flex; flex-direction: column; gap: 4px;
}
.booking-manage-current-label {
  font-size: 0.7em; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-muted);
}
.booking-manage-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.booking-manage-actions .btn { min-height: 44px; }
.booking-manage-step {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 18px; font-size: 0.8em; letter-spacing: 1.5px;
  text-transform: uppercase; color: var(--text-muted);
}
/* Per-link toggles in the booking-link edit modal (Add Meet / Allow guests). */
.booking-edit-check {
  display: flex; align-items: center; gap: 8px;
  font-size: 0.85em; color: var(--text-secondary); cursor: pointer; margin-top: 6px;
}
.booking-edit-check input { width: auto; margin: 0; cursor: pointer; }

/* Collapsible settings sections (accordion) */
.settings-section > .section-label {
  cursor: pointer; user-select: none;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
.settings-section > .section-label::after {
  content: '▾'; color: var(--text-muted); font-size: 0.85em;
  transition: transform 0.15s ease;
}
.settings-section.collapsed > .section-label::after { transform: rotate(-90deg); }
.settings-section.collapsed > :not(.section-label) { display: none; }
.settings-section > .section-label:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px;
}

/* Inline nav icon set (Lucide-style) — monochrome via currentColor so each
   icon inherits its link's colour + hover state. */
.icon { width: 15px; height: 15px; vertical-align: -3px; flex: none; }
.header-link .icon { margin-right: 6px; }

/* ─── ADMIN PANEL ─────────────────────────────────────────────
   App-admin-only user table. Numeric columns use tabular figures so they
   line up; deactivated rows dim. */
/* Full rail width — the user table has fourteen columns and nothing to gain
   from a cap. 10px top matches .settings-shell. */
.admin-wrap { margin: 10px 0 0; }
.admin-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 14px;
}
.admin-summary { color: var(--text-secondary); font-size: 0.9em; }
.admin-summary strong { color: var(--text); }
.admin-bar-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.admin-search { width: 240px; max-width: 100%; }
.admin-waitlist-note { width: 200px; max-width: 100%; font-size: 12px; padding: 4px 8px; }
/* Admin › Feedback. New rows carry an accent rule so the unread ones stand
   out without relying on the status text alone. */
.admin-fb-new td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
.admin-table td.admin-fb-msg { white-space: pre-wrap; overflow-wrap: anywhere; min-width: 28ch; max-width: 48ch; }
.admin-fb-page { margin-top: 4px; word-break: break-all; }
.admin-fb-from { color: var(--text-secondary); text-decoration: underline; text-underline-offset: 2px; }
.admin-fb-from:hover { color: var(--accent); }
.admin-fb-status { width: auto; font-size: 12px; padding: 4px 8px; }
/* Reply cell. The button carries the state word ("Reply" / "Edit reply") so
   the column reads without colour; the line under it is the receipt. */
.admin-fb-reply-cell { white-space: nowrap; vertical-align: top; }
/* The only destructive control on either feedback list. It sits quiet until
   you reach for it — --danger carries the warning as a border, never as text,
   where it drops to 1.93:1 on the light themes. */
.admin-fb-delete { margin-left: 6px; }
.admin-fb-delete:hover, .my-fb-delete:hover { border-color: var(--danger); }
/* The exchange, under the report it answers. Ours takes the accent rule and
   theirs a neutral one, so who spoke last reads without colour alone — the
   name is on every turn regardless. */
.admin-fb-turn {
  margin-top: 10px; padding: 8px 10px;
  background: var(--bg); border-left: 2px solid var(--border);
  line-height: 1.6;
}
.admin-fb-turn-ours { border-left-color: var(--accent); }
.admin-fb-turn-body { white-space: pre-wrap; overflow-wrap: anywhere; }
.admin-fb-turn-head {
  color: var(--text); font-size: 0.82em; font-weight: 600;
  letter-spacing: 0.4px; text-transform: uppercase; margin-bottom: 4px;
}
/* A row carrying an exchange is much taller than the controls beside it, and
   the table's default middle alignment leaves those floating mid-cell. */
#admin-feedback-body .admin-table td { vertical-align: top; }
/* Reply modal — same .invite-modal shell as Send feedback. The quoted report
   sits above the box so you answer with it in view. */
.admin-reply-modal { max-width: 560px; display: flex; flex-direction: column; gap: 12px; }
/* Scrolls internally and opens at the bottom: the newest turn is the one you
   are answering, and the log can run long. */
.admin-reply-log {
  max-height: 260px; overflow-y: auto;
  display: flex; flex-direction: column; gap: 8px;
  padding: 2px; margin-bottom: 2px;
}
.admin-reply-turn {
  padding: 8px 10px; background: var(--bg);
  border-left: 2px solid var(--border); font-size: 0.88em; line-height: 1.6;
}
.admin-reply-turn-ours { border-left-color: var(--accent); }
.admin-reply-turn-head {
  color: var(--text); font-size: 0.82em; font-weight: 600;
  letter-spacing: 0.4px; text-transform: uppercase; margin-bottom: 4px;
}
.admin-reply-turn-body {
  white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text-secondary);
}
.admin-reply-body { resize: vertical; min-height: 130px; font: inherit; line-height: 1.5; }
.admin-pill-fb-bug   { color: var(--danger);  border-color: var(--danger); }
.admin-pill-fb-idea  { color: var(--success); border-color: var(--success); }
.admin-pill-fb-other { color: var(--text-muted); border-color: var(--border); }
.admin-create {
  margin-bottom: 14px; padding: 14px;
  border: 1px solid var(--border-light); background: var(--bg-elevated);
}
.admin-create-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.admin-create-row .input { flex: 1 1 180px; min-width: 140px; }
.admin-create-row select.input { flex: 0 0 auto; }
.admin-create-hint { color: var(--text-muted); font-size: 0.82em; margin-top: 8px; }
.admin-table-scroll { overflow-x: auto; border: 1px solid var(--border-light); }
.admin-table { width: 100%; border-collapse: collapse; font-size: 0.85em; }
.admin-table th, .admin-table td {
  padding: 9px 12px; text-align: left; white-space: nowrap;
  border-bottom: 1px solid var(--border-light);
  vertical-align: middle;
}
.admin-table thead th {
  color: var(--text-muted); font-size: 0.82em; font-weight: 600;
  letter-spacing: 1px; text-transform: uppercase;
  position: sticky; top: 0; background: var(--bg-elevated);
}
.admin-table tbody tr:hover { background: var(--bg-input); }
.admin-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.admin-user-name { color: var(--text); font-weight: 500; }
.admin-user-email { color: var(--text-muted); font-size: 0.92em; }
.admin-when { color: var(--text-muted); }
.admin-row-off { opacity: 0.5; }
.admin-empty { color: var(--text-dim); text-align: center; padding: 20px; }
.admin-pill {
  display: inline-block; padding: 1px 7px; font-size: 0.85em;
  border: 1px solid var(--border); border-radius: 10px; letter-spacing: 0.3px;
}
.admin-pill-on  { color: var(--success); border-color: var(--success); }
/* Admin → Landing: copy editor. Groups are <details> per landing section. */
.admin-landing-hint { margin: 0 0 12px; }
.admin-landing-group { border: 1px solid var(--border); border-radius: 6px; margin-bottom: 10px; }
.admin-landing-group > summary {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px; cursor: pointer;
  list-style: none;
}
.admin-landing-group > summary::before { content: '▸'; color: var(--text-muted); font-size: 0.8em; }
.admin-landing-group[open] > summary::before { content: '▾'; }
.admin-landing-group > summary::-webkit-details-marker { display: none; }
.admin-landing-group-name { font-weight: 600; font-family: var(--mono); }
.admin-landing-slot { padding: 8px 12px 10px; border-top: 1px solid var(--border-light); }
.admin-landing-slot.is-edited { box-shadow: inset 3px 0 0 var(--success); }
.admin-landing-slot-head {
  display: flex; align-items: center; gap: 8px; margin-bottom: 4px; font-size: 0.8em; color: var(--text-secondary);
}
.admin-landing-slot-head .admin-landing-revert { margin-left: auto; }
.admin-landing-ta { width: 100%; font-family: var(--mono); font-size: 0.85em; line-height: 1.45; resize: vertical; }
/* Visual mode: section nav beside the live landing page in an iframe. */
.admin-landing-mode { margin-left: 0; }
.admin-lv-wrap { display: flex; gap: 12px; height: calc(100vh - 300px); min-height: 480px; }
.admin-lv-nav { width: 170px; flex: none; overflow-y: auto; font-family: var(--mono); font-size: 0.78em; }
.admin-lv-nav button {
  display: block; width: 100%; min-height: 36px; padding: 6px 8px; text-align: left;
  border: 0; border-left: 2px solid transparent; background: none; color: var(--text-secondary);
  font: inherit; cursor: pointer;
}
.admin-lv-nav button:not(.is-section) { padding-left: 18px; color: var(--text-muted); }
.admin-lv-navrow { display: flex; align-items: center; }
.admin-lv-navrow > button:first-child { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.admin-lv-nav .admin-lv-eye { flex: none; width: auto; padding: 0 8px; min-height: 36px; border: 0; background: none; color: var(--text-muted); font: inherit; font-size: 0.9em; cursor: pointer; opacity: 0.6; }
.admin-lv-navrow:hover .admin-lv-eye, .admin-lv-eye:focus-visible, .admin-lv-navrow.is-hidden .admin-lv-eye { opacity: 1; }
.admin-lv-navrow.is-hidden > button:first-child { text-decoration: line-through; color: var(--text-muted); }
.admin-lv-nav button:hover, .admin-lv-nav button:focus-visible { color: var(--text); border-left-color: var(--text); }
.admin-lv-frame { flex: 1; min-width: 0; height: 100%; border: 1px solid var(--border); border-radius: 6px; background: #fff; }
/* Image panel: sits between the bar and the frame while a figure is open. */
.admin-lv-imgpanel { border: 1px solid var(--border); border-radius: 6px; padding: 10px 12px; margin-bottom: 10px; background: var(--bg-elevated); }
.admin-lv-imgpanel-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.admin-lv-imgpanel-head button { margin-left: auto; }
.admin-lv-imglist { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
.admin-lv-imgrow { display: flex; align-items: center; gap: 8px; }
.admin-lv-imgrow img { width: 96px; height: 56px; object-fit: cover; border-radius: 4px; border: 1px solid var(--border); flex: none; background: var(--bg); }
.admin-lv-imgrow .input { flex: 1; min-width: 120px; }
.admin-lv-imgrow .member-meta { flex: none; min-width: 72px; text-align: right; font-variant-numeric: tabular-nums; }
.admin-lv-imgpanel-empty { color: var(--text-muted); font-size: 0.9em; padding: 4px 0; }
.admin-lv-imgpanel-actions { display: flex; gap: 8px; flex-wrap: wrap; }
@media (max-width: 720px) {
  .admin-lv-wrap { flex-direction: column; height: auto; }
  .admin-lv-nav { width: auto; display: flex; flex-wrap: wrap; gap: 4px; }
  .admin-lv-nav button { width: auto; min-height: 44px; }
  .admin-lv-frame { height: 70vh; }
}
.admin-pill-off { color: var(--danger); border-color: var(--danger); }
.admin-pill-admin { color: var(--accent); border-color: var(--accent); margin-left: 6px; font-size: 0.78em; }
.admin-actions { display: flex; align-items: center; gap: 10px; }
.admin-link {
  background: none; border: 0; padding: 0; cursor: pointer;
  font-family: inherit; font-size: 0.92em; color: var(--text-secondary);
}
.admin-link:hover:not([disabled]) { color: var(--accent); }
.admin-link-danger:hover:not([disabled]) { color: var(--danger); }
.admin-link[disabled] { color: var(--text-dim); cursor: default; }
.admin-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Actions ▾ trigger + floating per-row menu. The menu is fixed-positioned
   and appended to <body> so .admin-table-scroll's overflow can't clip it. */
.admin-actions-btn { padding: 2px 6px; }
.admin-menu {
  position: fixed; z-index: 1000;
  min-width: 200px; padding: 4px 0;
  background: var(--bg-elevated); border: 1px solid var(--border);
  box-shadow: 0 10px 24px rgba(0,0,0,0.35);
  display: flex; flex-direction: column;
}
.admin-menu-item {
  background: none; border: 0;
  padding: 8px 14px; text-align: left;
  font-family: inherit; font-size: 0.9em; color: var(--text);
  cursor: pointer;
}
.admin-menu-item:hover:not([disabled])    { background: var(--bg-input); color: var(--accent); }
.admin-menu-danger:hover:not([disabled])  { background: var(--bg-input); color: var(--danger); }
.admin-menu-item[disabled] { color: var(--text-dim); cursor: default; }
.admin-menu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Permissions-matrix help block at the top of the Admin panel. */
.admin-matrix {
  margin-bottom: 14px; padding: 0;
  border: 1px solid var(--border-light); background: var(--bg-elevated);
}
.admin-matrix > summary {
  list-style: none; cursor: pointer; user-select: none;
  padding: 10px 14px; color: var(--text-muted);
  font-size: 0.78em; letter-spacing: 1.5px; text-transform: uppercase;
}
.admin-matrix > summary::-webkit-details-marker { display: none; }
.admin-matrix > summary::before {
  content: '▸'; display: inline-block; width: 10px;
  color: var(--text-dim); transition: transform 120ms ease;
}
.admin-matrix[open] > summary::before { transform: rotate(90deg); }
.admin-matrix-body { padding: 4px 14px 14px; }

/* App-admin workspaces management list actions. */
.admin-ws-actions { white-space: nowrap; text-align: right; }
.admin-ws-actions .btn { margin-left: 6px; }

/* Demo tab — seed / capture / reset the account used for product videos.
   Reuses the admin panel's surfaces and disclosure treatment; the only new
   idea here is the "uncaptured changes" callout. */
#apanel-demo .muted { color: var(--text-muted); font-size: 0.85em; }
#apanel-demo p { margin: 8px 0 14px; max-width: 68ch; }

.admin-demo-source {
  padding: 10px 14px; margin-bottom: 14px;
  border: 1px solid var(--border-light); background: var(--bg-elevated);
  font-size: 0.85em; line-height: 1.7;
}

.admin-demo-toggle {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 12px 14px; margin-bottom: 12px;
  border: 1px solid var(--border-light); background: var(--bg-elevated);
  cursor: pointer; font-size: 0.9em;
}
/* 44px minimum hit target: the checkbox is small, so the padded label carries
   the target and the input keeps a comfortable box of its own. */
.admin-demo-toggle input { width: 18px; height: 18px; margin-top: 2px; flex: none; }

/* Uncaptured work. --warning fails contrast as body text on the light themes,
   so the state is carried by a coloured rule and the text stays --text. */
.admin-demo-dirty {
  padding: 10px 14px; margin-bottom: 12px;
  border: 1px solid var(--border-light); border-left: 3px solid var(--warning);
  background: var(--bg-elevated); color: var(--text); font-size: 0.9em;
}
.admin-demo-dirty .muted { display: block; margin-top: 4px; }

.admin-demo-rule { border: 0; border-top: 1px solid var(--border-light); margin: 18px 0 14px; }

.admin-demo-details {
  margin-top: 14px;
  border: 1px solid var(--border-light); background: var(--bg-elevated);
}
.admin-demo-details > summary {
  list-style: none; cursor: pointer; user-select: none;
  padding: 10px 14px; color: var(--text-muted);
  font-size: 0.78em; letter-spacing: 1.5px; text-transform: uppercase;
}
.admin-demo-details > summary::-webkit-details-marker { display: none; }
.admin-demo-details > summary::before {
  content: '▸'; display: inline-block; width: 10px;
  color: var(--text-dim); transition: transform 120ms ease;
}
.admin-demo-details[open] > summary::before { transform: rotate(90deg); }
.admin-demo-details > *:not(summary) { padding: 0 14px 14px; }
.admin-demo-details > .admin-bar-actions { padding: 4px 14px 10px; }

.admin-demo-explain { margin: 0; font-size: 0.88em; line-height: 1.6; }
.admin-demo-explain dt { color: var(--text); font-weight: 600; margin-top: 12px; }
.admin-demo-explain dt:first-child { margin-top: 0; }
.admin-demo-explain dd { margin: 4px 0 0; color: var(--text-secondary); max-width: 72ch; }

.admin-demo-snap-actions { white-space: nowrap; text-align: right; }
.admin-demo-snap-actions .btn { margin-left: 6px; }

/* Disabled has to read as disabled — "Discard changes" with nothing to discard
   and "To pinned" with no pin are both common states here. */
#apanel-demo .btn:disabled { opacity: 0.45; cursor: default; }

/* Demo sign-in details. The password is a setting you pick before seeding, not
   an outcome of it, so this shows whether or not the account exists. */
.admin-demo-creds {
  padding: 12px 14px; margin-bottom: 14px;
  border: 1px solid var(--border-light); background: var(--bg-elevated);
  font-size: 0.9em;
}
.admin-demo-creds label { display: block; margin-bottom: 6px; }
.admin-demo-creds .input { width: 260px; max-width: 100%; font-family: var(--font-mono, monospace); }
.admin-demo-creds .muted { margin-top: 6px; }

/* In-button progress. A demo run is many seconds of sequential writes, so the
   button that was clicked has to say so — a toast alone reads as a dead click. */
.demo-spin {
  display: inline-block; width: 11px; height: 11px; margin-right: 7px;
  vertical-align: -1px;
  border: 2px solid var(--border); border-top-color: var(--accent);
  border-radius: 50%; animation: toast-spin 0.7s linear infinite;
}
.admin-demo-progress { margin-top: 8px; min-height: 1.2em; }
/* Reserve the line's height even when empty, so starting a run doesn't shift
   the buttons under the pointer. */

@media (prefers-reduced-motion: reduce) {
  /* Keep the mark, drop the rotation — it still reads as "busy" as a static
     ring beside a changed label, and the label is the real signal. */
  .demo-spin { animation: none; border-top-color: var(--accent); }
}

@media (prefers-reduced-motion: reduce) {
  .admin-demo-details > summary::before { transition: none; }
}
.admin-matrix-table { width: 100%; border-collapse: collapse; font-size: 0.85em; }
.admin-matrix-table th, .admin-matrix-table td {
  padding: 6px 10px; text-align: left; border-bottom: 1px solid var(--border-light);
  vertical-align: top;
}
.admin-matrix-table th {
  color: var(--text-muted); font-size: 0.82em; font-weight: 600;
  letter-spacing: 1px; text-transform: uppercase;
}
.admin-matrix-note {
  color: var(--text-muted); font-size: 0.85em; line-height: 1.5; margin: 10px 0 0;
}
/* Side-by-side "APP admin ≠ TEAM admin" callout above the full role table. */
.admin-matrix-callout {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 14px; align-items: stretch;
  margin-bottom: 14px;
}
.admin-matrix-pane {
  padding: 12px 14px;
  border: 1px solid var(--border-light); background: var(--bg);
}
.admin-matrix-pane-tag {
  display: inline-block; padding: 1px 8px; margin-bottom: 6px;
  border: 1px solid var(--accent); color: var(--accent);
  font-size: 0.7em; letter-spacing: 2px;
}
.admin-matrix-pane-tag-team { border-color: var(--text-secondary); color: var(--text-secondary); }
.admin-matrix-pane-title {
  font-size: 0.9em; font-weight: 600; color: var(--text); margin-bottom: 6px;
}
.admin-matrix-pane ul { margin: 0; padding-left: 18px; color: var(--text-secondary); font-size: 0.85em; line-height: 1.55; }
.admin-matrix-pane li { margin: 2px 0; }
.admin-matrix-pane code { font-family: inherit; background: var(--bg-input); padding: 1px 4px; border: 1px solid var(--border-light); }
.admin-matrix-vs {
  align-self: center; color: var(--text-muted); font-size: 1.6em;
}
.admin-matrix-callout-note {
  color: var(--text-secondary); font-size: 0.88em; line-height: 1.5; margin: 0 0 12px;
}
.admin-matrix-callout-note code {
  font-family: inherit; background: var(--bg-input); padding: 1px 4px; border: 1px solid var(--border-light);
}
.admin-matrix-more > summary {
  cursor: pointer; user-select: none; padding: 6px 0;
  color: var(--text-muted); font-size: 0.78em; letter-spacing: 1.5px; text-transform: uppercase;
  list-style: none;
}
.admin-matrix-more > summary::-webkit-details-marker { display: none; }
.admin-matrix-more > summary::before {
  content: '▸'; display: inline-block; width: 10px;
  color: var(--text-dim); transition: transform 120ms ease;
}
.admin-matrix-more[open] > summary::before { transform: rotate(90deg); }
@media (max-width: 720px) {
  .admin-matrix-callout { grid-template-columns: 1fr; }
  .admin-matrix-vs { display: none; }
}

/* Expand button in the User cell + the expansion row below. The caret gets
   its own column beside the name/email stack, centred on both lines, rather
   than sitting as a stray glyph above the name. */
.admin-user-cell { display: flex; align-items: center; gap: 8px; }
.admin-user-text { min-width: 0; }
.admin-expand {
  flex: 0 0 auto; width: 28px; height: 28px; margin-left: -6px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; border-radius: 4px; padding: 0; cursor: pointer;
  color: var(--text-muted);
  transition: transform 120ms ease, background-color 120ms ease;
}
.admin-expand .icon { width: 18px; height: 18px; vertical-align: 0; }
.admin-expand:hover { color: var(--text); background: color-mix(in srgb, var(--text) 8%, transparent); }
.admin-expand:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.admin-expand-open { transform: rotate(90deg); color: var(--accent); }
.admin-expansion-row > td {
  background: var(--bg); padding: 0; border-bottom: 1px solid var(--border-light);
}

/* Memberships panel inside an expanded row. */
.admin-memberships {
  padding: 14px 18px; display: flex; flex-direction: column; gap: 8px;
}
.admin-memberships-header {
  color: var(--text-muted); font-size: 0.75em;
  letter-spacing: 1.5px; text-transform: uppercase;
}
.admin-memberships-loading, .admin-memberships-empty {
  color: var(--text-dim); font-size: 0.9em; padding: 4px 0;
}
.admin-membership-row {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 0; border-bottom: 1px solid var(--border-light);
  font-size: 0.9em;
}
.admin-membership-row:last-of-type { border-bottom: none; }
.admin-membership-inactive { opacity: 0.6; }
.admin-membership-ws { color: var(--text); font-weight: 500; min-width: 160px; }
.admin-membership-type {
  color: var(--text-muted); font-size: 0.85em;
  text-transform: uppercase; letter-spacing: 1px;
}
.admin-membership-role { width: 130px; padding: 4px 8px; font-size: 0.9em; }
.admin-membership-status {
  color: var(--text-muted); font-size: 0.82em;
  text-transform: uppercase; letter-spacing: 1px;
}
.admin-membership-fill { flex: 1; }
.admin-membership-note { color: var(--text-dim); font-size: 0.85em; }
.admin-add-membership {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 8px; padding-top: 10px; border-top: 1px solid var(--border-light);
}
.admin-add-label {
  color: var(--text-muted); font-size: 0.78em;
  letter-spacing: 1px; text-transform: uppercase;
}
.admin-add-membership .input { padding: 5px 8px; font-size: 0.9em; flex: 0 1 220px; }

/* ─── MOBILE / TOUCH PASS 1 ────────────────────────────────────────
   Quick-win pass — no structural changes, no per-component rewrites:
     1) Inputs ≥16px on touch devices to stop iOS Safari auto-zooming on focus.
     2) Touch-target minimums (44pt primary, 32pt chips) on coarse pointers
        regardless of viewport width — reverses the small-screen .tab shrink.
     3) Header allowed to wrap below 700px (a proper compact-icon pattern
        with an overflow menu is Phase 3).
     4) Workspace / theme dropdowns clamped to the viewport on narrow widths.
   Safe-area insets are handled at .app (top of file). */

@media (pointer: coarse) {
  /* Stop iOS Safari auto-zooming on focus when font-size < 16px. */
  input, textarea, select,
  .input, .kb-capture-input, .kb-inbox-assignee-select,
  .settings-value-select {
    font-size: 16px;
  }
  /* Primary tappables — 44pt minimum (Apple HIG / WCAG 2.5.5). */
  .tab, .pj-tab, .settings-tab, .btn, .btn-outline, .btn-link, .kb-action, .header-link, .ws-anchor {
    min-height: 44px;
  }
  /* Override the 480px width-based tab shrink: touch users get a
     comfortable target whatever the viewport. */
  .tab {
    padding: 12px 16px;
    font-size: 0.85em;
  }
  /* Subnav pills take the 44px above and wider padding, but keep their
     smaller type — sizing them to the primary strip here would undo on touch
     the hierarchy the pills exist to create. */
  .pj-tab, .settings-tab {
    padding: 10px 14px;
  }
  /* Inline chips stay visually compact (they're secondary metadata) but
     get breathing room between neighbours so taps don't mis-fire. */
  .kb-card-chip {
    min-height: 32px;
    padding: 6px 10px;
  }
  .kb-card-chips, .project-task-chips { gap: 8px; }
}

@media (max-width: 700px) {
  /* Header packs many actions on small phones — allow it to wrap rather
     than overflow or horizontal-scroll. A compact-icon + overflow-menu
     pattern replaces this in Phase 3. The three-track grid has no room to
     resolve here, so the row goes back to wrapping flex. */
  .header { display: flex; flex-wrap: wrap; row-gap: 10px; }
  /* Row 1 keeps the logo, search and actions; the anchor takes row 2 on its
     own. Squeezing it onto row 1 truncates the name to a few letters and
     strands the bell on a second row anyway — this way the thing the feature
     exists to surface gets a full row and stays readable. */
  .header-lead { order: 1; flex: 1 1 auto; }
  .header-actions { order: 2; flex-wrap: wrap; row-gap: 8px; justify-content: flex-end; }
  .ws-switcher { order: 3; flex: 1 1 100%; }
  .ws-anchor { max-width: 100%; }
  /* Left-aligned trigger on its own row — hang the menu off its left edge. */
  .ws-menu { left: 0; right: auto; transform: none; }
  /* Don't let dropdowns run off the right edge of narrow viewports. */
  .ws-menu, .header-overflow-menu { max-width: calc(100vw - 24px); }
}

/* ─── MOBILE / TOUCH PASS 2 ────────────────────────────────────────
   PM project view → Tasks tab: at ≤780px the 5-column table doesn't fit.
   Reformat each <tr> as a stacked card (title on top, due/size/priority/
   status as a wrapped meta row). The data and DOM stay the same — only
   display values change, so the same render works on desktop and mobile. */
@media (max-width: 780px) {
  .pj-table,
  .pj-table tbody,
  .pj-table tr { display: block; }
  /* Column headers (Task / Due / Size / Priority / Status) are
     redundant when each row is a self-labelling card. */
  .pj-table thead { display: none; }
  /* Each row becomes a card. */
  .pj-table tr {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 6px;
    box-shadow: var(--card-shadow);
    padding: 12px 14px;
    margin: 0 0 8px;
  }
  /* Cells flow inline as a wrapped meta row beneath the title. */
  .pj-table td {
    display: inline-block;
    padding: 0;
    border: 0;
    margin: 0 12px 0 0;
    vertical-align: middle;
    font-size: 0.85em;
  }
  /* Title takes its own line, full-width, slightly bolder. */
  .pj-table .pj-t-title {
    display: block;
    margin: 0 0 6px;
    font-size: 1em;
    font-weight: 600;
    line-height: 1.3;
  }
  /* Suppress noisy '—' placeholders in the meta row on mobile so the
     card only shows the cells that carry information. */
  .pj-table td .pj-muted { display: none; }
  .pj-table td:has(> .pj-muted:only-child) { display: none; }
  /* Done-row strikethrough still applies via .pj-row-done .pj-t-title. */
}

/* ─── HEADER DROPDOWN SHELL ────────────────────────────────────────
   The panel every header dropdown wears — account menu, notifications,
   and (further down) the workspace switcher. One shell, so a menu can't
   quietly grow its own border radius or padding. */
.header-overflow-menu {
  position: absolute; top: calc(100% + 4px); right: 0;
  min-width: 200px; max-width: calc(100vw - 24px);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: var(--overlay-shadow);
  z-index: 100;
  padding: 4px 0;
  /* The ⋯ menu now carries the theme rows too (11 items with an admin link),
     which is taller than a short phone in landscape. Scroll the menu rather
     than letting it run off the bottom of the screen. */
  max-height: calc(100vh - 80px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* ── DROPDOWN TYPE SYSTEM ──────────────────────────────────────────
   One scale for every header dropdown (account menu, notifications, the
   workspace switcher below). Rows are choices, so they read in sentence
   case at one size: uppercase flattens the ascenders and descenders the
   eye scans by, and the tracking it needs to stay legible made rows look
   like headings. Group labels separate themselves by size and colour
   instead — quieter than the items, never louder. */
.header-overflow-item {
  display: flex; align-items: center; gap: 8px;
  width: 100%; min-height: 44px;
  background: none; border: 0; cursor: pointer;
  padding: 10px 14px;
  color: var(--text);
  font: inherit;
  font-size: 0.85em;
  letter-spacing: 0.2px;
  text-align: left;
}
.header-overflow-item:hover { background: var(--bg-input); color: var(--text); }

/* Grouped dropdown — the SETTINGS menu stacks Stats and Theme under labels
   rather than running eleven undifferentiated rows together. The ⋯ menu
   borrows the same group + separator for its theme block. */
.header-menu-wide { min-width: 232px; }
.header-menu-group {
  padding: 10px 14px 4px;
  color: var(--text-dim);
  font-size: 0.72em;
  letter-spacing: 0.3px;
}
.header-menu-group:first-child { padding-top: 6px; }
.header-menu-sep {
  height: 1px;
  background: var(--border-light);
  margin: 5px 0;
}
/* Theme rows carry no icon. The ● marker is absolutely placed in the gutter the
   icons occupy, so every label sits on one left edge whether it's marked or not
   — indenting only the unmarked rows leaves the list visibly ragged. */
[data-theme-items] .header-overflow-item { position: relative; padding-left: 30px; }
.header-overflow-item-active { color: var(--accent); }
.header-overflow-item-active::before {
  content: "●";
  position: absolute;
  left: 15px; top: 50%;
  transform: translateY(-50%);
  font-size: 0.62em;
}

/* Positioning context for every header dropdown trigger. */
.header-menu { position: relative; display: inline-flex; }

/* The account trigger: your initial, not a hamburger — it says whose menu
   this is, and it is the same control at every width, so there is no second
   ⋯ menu to drift out of sync with this one. */
.acct-trigger {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0;
  background: var(--bg-input); color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: 50%;
  cursor: pointer; font: inherit;
  transition: color 0.2s, border-color 0.2s;
}
.acct-trigger:hover { color: var(--text); border-color: var(--text-dim); }
.acct-initial { font-size: 0.78em; font-weight: 500; line-height: 1; }
/* Who you are, above what you can do. Non-interactive, so it sits outside
   the 44px row rhythm. */
.header-menu-id { padding: 10px 14px 8px; }
.header-menu-id-name { font-size: 0.85em; color: var(--text); }
.header-menu-id-mail {
  font-size: 0.72em; color: var(--text-dim); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

@media (max-width: 700px) {
  .header-link-collapsible { display: none; }
}

/* Kanban project-filter dropdown — at ≤700px the chip list (All, Mine,
   Areas, Projects) collapses into a screen-width <select> so it doesn't
   eat 4–5 rows of vertical space on a phone. Columns ▾ stays visible. */
.kb-filter-select { display: none; }
@media (max-width: 700px) {
  /* Hide every direct chip in the filter bar; keep .kb-cols-wrap. */
  .kb-filters > .kb-chip { display: none; }
  .kb-filter-select {
    display: block;
    flex: 1 1 100%;
    width: 100%;
    min-width: 0;
    background: var(--bg-elevated);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 10px 12px;
    font: inherit;
    font-size: 16px; /* prevent iOS zoom on focus */
    appearance: auto;  /* keep the native chevron / picker */
  }
}

/* iOS Add-to-Home-Screen banner — quiet accent-bordered hint at the top
   of the Home tab. Only renders client-side when we detect iOS Safari,
   not-installed, not-dismissed (see iosA2HSBannerHTML in app.js). */
.ios-a2hs {
  display: flex; align-items: center; gap: 12px;
  margin: 0 0 14px;
  padding: 10px 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--accent);
  border-radius: 6px;
  color: var(--text-secondary);
  font-size: 0.85em;
  line-height: 1.45;
  box-shadow: var(--card-shadow);
}
.ios-a2hs-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.ios-a2hs-title { color: var(--accent); font-weight: 600; letter-spacing: 0.5px; }
.ios-a2hs-hint { color: var(--text-secondary); }
.ios-a2hs-hint b { color: var(--text); font-weight: 600; }
.ios-share-glyph { display: inline; vertical-align: -2px; color: var(--accent); margin: 0 2px; }
.ios-a2hs-dismiss {
  background: none; border: 0; cursor: pointer;
  color: var(--text-muted); font-size: 1.05em;
  padding: 6px 10px;
  flex: none;
}
.ios-a2hs-dismiss:hover { color: var(--text); }
@media (pointer: coarse) {
  .ios-a2hs-dismiss { min-height: 44px; min-width: 44px; }
}

/* ─── MOBILE WEEK · Day-at-a-time ──────────────────────────────────
   At ≤700px the desktop 7-column .week-layout hides and .week-mobile
   shows: day picker (chips + dropdown + arrows), single day calendar
   below (reuses the desktop dayCol geometry), floating Inbox FAB with
   a bottom-sheet pick-then-time flow. Horizontal swipe pages days. */
.week-mobile { display: none; }
@media (max-width: 700px) {
  .week-layout { display: none; }
  .week-mobile { display: block; }
}

.week-mobile-picker {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 8px;
}
.week-mobile-arrow {
  flex: 0 0 44px; min-height: 44px;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: 6px; color: var(--text); font-size: 1.1em; cursor: pointer;
}
.week-mobile-arrow[disabled] { opacity: 0.4; cursor: default; }
.week-mobile-day-select {
  flex: 1 1 auto; min-width: 0; min-height: 44px;
  background: var(--bg-elevated); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 8px 12px; font: inherit; font-size: 16px;
}

.week-mobile-chips {
  display: grid; grid-template-columns: repeat(7, 1fr);
  gap: 4px; margin-bottom: 12px;
}
.week-mobile-chip {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  min-height: 52px; padding: 6px 2px;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: 6px; color: var(--text-muted); font: inherit; cursor: pointer;
}
.week-mobile-chip-day { font-size: 9px; letter-spacing: 1px; }
.week-mobile-chip-num { font-size: 1.05em; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.week-mobile-chip.today .week-mobile-chip-num { color: var(--accent); }
.week-mobile-chip.active {
  background: var(--accent); border-color: var(--accent); color: var(--bg);
}
.week-mobile-chip.active .week-mobile-chip-day,
.week-mobile-chip.active .week-mobile-chip-num { color: var(--bg); }

.week-mobile-allday {
  display: flex; align-items: flex-start; gap: 10px;
  margin-bottom: 10px;
  border: 1px solid var(--border-light); border-radius: 6px;
  background: var(--bg-elevated); padding: 8px 10px;
}
.week-mobile-allday-label {
  flex: 0 0 60px;
  font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--text-muted);
  padding-top: 2px;
}
.week-mobile-allday .week-cal-allday-cell { flex: 1; }
/* Mobile shows one day column at a time, so the band can afford a real touch
   target here where it can't in the seven-column desktop header. */
.week-mobile-allday .week-cal-dayloc { min-height: 44px; font-size: 0.8em; padding: 6px 8px 6px 10px; }
.week-mobile-allday .week-cal-dayloc-pin svg { width: 14px; height: 14px; }
/* Same reasoning for the milestone chip — a real touch target where the layout
   can afford one. */
.week-mobile-allday .week-cal-ms { min-height: 44px; font-size: 0.8em; padding: 6px 8px 6px 10px; }
.week-mobile-allday .week-cal-ms-dia { font-size: 1em; }

.week-mobile-grid {
  display: flex; gap: 0;
  border: 1px solid var(--border-light); border-radius: 6px; overflow: hidden;
  background: var(--bg-elevated);
  touch-action: pan-y;
}
.week-mobile-grid .week-cal-rail { flex: 0 0 56px; }
.week-mobile-day-wrap { flex: 1; position: relative; }
.week-mobile-day-wrap .week-cal-col { width: 100%; }

/* Floating Inbox button — fixed bottom-right with safe-area inset. */
.week-mobile-fab {
  position: fixed;
  right: max(16px, env(safe-area-inset-right, 16px));
  bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  display: inline-flex; align-items: center; gap: 8px;
  padding: 12px 18px; min-height: 48px;
  background: var(--accent); color: var(--bg);
  border: 0; border-radius: 999px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.25);
  font: inherit; font-weight: 600; letter-spacing: 0.5px; cursor: pointer;
  z-index: 50;
}
.week-mobile-fab-icon { font-size: 1.1em; line-height: 1; }
.week-mobile-fab-count {
  display: inline-block; min-width: 22px; padding: 1px 7px;
  background: rgba(0,0,0,0.18); border-radius: 999px;
  font-size: 0.85em; font-variant-numeric: tabular-nums;
}

/* Bottom sheet for inbox picking + time choosing. */
.week-mobile-sheet { position: fixed; inset: 0; z-index: 200; }
.week-mobile-sheet-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.5); }
.week-mobile-sheet-panel {
  position: absolute; left: 0; right: 0; bottom: 0;
  max-height: 80vh;
  background: var(--bg-elevated);
  border-top: 1px solid var(--border);
  border-top-left-radius: 14px; border-top-right-radius: 14px;
  box-shadow: 0 -6px 24px rgba(0,0,0,0.35);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  display: flex; flex-direction: column;
  animation: weekMobileSheetIn 180ms ease-out;
}
@keyframes weekMobileSheetIn {
  from { transform: translateY(40%); opacity: 0.4; }
  to   { transform: translateY(0);   opacity: 1; }
}
.week-mobile-sheet-h {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px; border-bottom: 1px solid var(--border-light);
  font-size: 0.95em; font-weight: 600; color: var(--text);
}
.week-mobile-sheet-h > span { flex: 1; min-width: 0; }
.week-mobile-sheet-close, .week-mobile-sheet-back {
  background: none; border: 0; cursor: pointer;
  color: var(--text-muted); font-size: 1.15em;
  min-width: 32px; padding: 4px 8px;
}
.week-mobile-sheet-list {
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 6px 8px 14px;
}
.week-mobile-sheet-empty {
  padding: 22px 14px; text-align: center; color: var(--text-muted);
}
.week-mobile-pick-row {
  display: block; width: 100%; text-align: left;
  background: var(--bg-input); border: 1px solid var(--border);
  border-radius: 6px; padding: 12px 14px; margin: 6px 0;
  color: var(--text); font: inherit; cursor: pointer;
  min-height: 48px;
}
.week-mobile-pick-row:hover { border-color: var(--accent); }
.week-mobile-pick-row-title { display: block; }

.week-mobile-pick {
  padding: 14px 16px 18px;
  display: flex; flex-direction: column; gap: 12px;
}
.week-mobile-pick-title { font-size: 1.05em; font-weight: 600; line-height: 1.35; }
.week-mobile-pick-label { font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: var(--text-muted); }
.week-mobile-pick-time {
  background: var(--bg-input); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 10px 12px; font: inherit; font-size: 16px;
  min-height: 44px;
}
.week-mobile-pick-actions {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
}

/* ─── MOBILE PROJECTS · Stacked horizon accordions ─────────────────
   At ≤700px the horizontal 5-column projects board hides and this
   vertical accordion shows. 1–3 months opens by default; later horizons
   collapse so the screen leads with what's actionable now. Built on
   native <details>/<summary> — no JS for the toggle. */
.projects-mobile { display: none; }
@media (max-width: 700px) {
  .kb-board-projects { display: none; }
  .projects-mobile { display: block; margin-top: 4px; }
}

.projects-mobile-h {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 6px;
  margin: 0 0 10px;
  overflow: hidden;
}
.projects-mobile-h-summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
  padding: 12px 14px;
  display: flex; align-items: center; gap: 10px;
  min-height: 48px;
  color: var(--text);
}
.projects-mobile-h-summary::-webkit-details-marker { display: none; }
.projects-mobile-h-label {
  flex: 1; min-width: 0;
  font-size: 0.82em; font-weight: 600;
  letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--text-muted);
}
.projects-mobile-h[open] .projects-mobile-h-label { color: var(--text); }
.projects-mobile-h-count {
  flex: none;
  font-size: 0.78em; font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  padding: 1px 8px;
  border: 1px solid var(--border);
  border-radius: 10px;
  letter-spacing: 0.3px;
}
.projects-mobile-h[open] .projects-mobile-h-count {
  color: var(--accent); border-color: var(--accent);
}
.projects-mobile-h-chev {
  flex: none;
  color: var(--text-dim);
  font-size: 0.85em;
  transition: transform 160ms ease;
}
.projects-mobile-h[open] .projects-mobile-h-chev { transform: rotate(180deg); }
.projects-mobile-h-body {
  padding: 4px 12px 12px;
  border-top: 1px solid var(--border-light);
}
.projects-mobile-h-body .kb-card { margin: 8px 0; }
.projects-mobile-h-body .kb-empty {
  padding: 12px; color: var(--text-dim); font-size: 0.85em; text-align: center;
}

/* ─── DANGER ZONE · Delete-workspace confirmation ──────────────────
   Footer block in the Team section (owner-only) plus the typed-name
   confirmation modal. The danger styling lifts from --danger; the
   modal is centered, single-purpose, escape-via-backdrop OK. */
.danger-zone {
  margin-top: 28px;
  padding-top: 18px;
  border-top: 1px dashed var(--danger);
}
.danger-row { align-items: center; gap: 12px; }

.delete-ws-modal-backdrop {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 1000;
}
.delete-ws-modal {
  position: fixed; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  z-index: 1001;
  width: min(440px, calc(100vw - 32px));
  background: var(--bg-elevated);
  border: 1px solid var(--danger);
  border-radius: 8px;
  box-shadow: var(--card-shadow);
}
.delete-ws-modal-header {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}
.delete-ws-modal-header h2 {
  flex: 1; margin: 0;
  font-size: 1.05em; color: var(--danger);
  letter-spacing: 0.5px;
}
.delete-ws-modal-close {
  background: none; border: 0; cursor: pointer;
  color: var(--text-muted); font-size: 1.1em;
  padding: 4px 8px; min-width: 32px;
}
.delete-ws-modal-close:hover { color: var(--text); }
.delete-ws-modal-body { padding: 16px 18px; }
.delete-ws-warning {
  margin: 0 0 14px;
  color: var(--text-secondary);
  font-size: 0.92em; line-height: 1.5;
}
.delete-ws-warning strong { color: var(--text); }
.delete-ws-label {
  display: block;
  font-size: 0.78em; letter-spacing: 0.5px;
  color: var(--text-muted);
}
.delete-ws-label code {
  background: var(--bg-input);
  border: 1px solid var(--border);
  padding: 1px 6px; border-radius: 3px;
  color: var(--text); font-size: 0.95em;
  font-family: inherit;
}
.delete-ws-input { margin-top: 8px; width: 100%; }
.delete-ws-modal-actions {
  display: flex; justify-content: flex-end; gap: 10px;
  padding: 12px 18px;
  border-top: 1px solid var(--border);
}

/* ─── Trial notice + wall ─────────────────────────────────────────── */
/* The strip sits above #app, so it pushes the shell down rather than
   floating over it — a countdown that covers the toolbar is worse than
   the countdown is useful. Colour is never the only signal; the text
   states the state on its own. */
.trial-notice {
  display: flex; align-items: center; gap: 12px;
  flex-wrap: wrap;
  padding: 10px 16px;
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
  border-left: 3px solid var(--warning);
  font-size: 0.88em; line-height: 1.45;
  color: var(--text-secondary);
}
.trial-notice-over { border-left-color: var(--danger); }
.trial-notice-text { flex: 1; min-width: 220px; }
.trial-notice-text strong { color: var(--text); }
.trial-notice-btn {
  min-height: 44px; padding: 8px 14px;
  background: none; cursor: pointer;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text); font: inherit; font-size: 0.95em;
}
.trial-notice-btn:hover { border-color: var(--accent); color: var(--accent); }
.trial-notice-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.trial-wall-backdrop {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 1000;
}
.trial-wall {
  position: fixed; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  z-index: 1001;
  width: min(460px, calc(100vw - 32px));
  max-height: calc(100vh - 48px); overflow-y: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--accent);
  border-radius: 8px;
  box-shadow: var(--card-shadow);
}
.trial-wall-header {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}
.trial-wall-header h2 {
  flex: 1; margin: 0;
  font-size: 1.05em; color: var(--accent);
  letter-spacing: 0.5px;
}
.trial-wall-close {
  background: none; border: 0; cursor: pointer;
  color: var(--text-muted); font-size: 1.1em;
  padding: 4px 8px; min-width: 44px; min-height: 44px;
}
.trial-wall-close:hover { color: var(--text); }
.trial-wall-close:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.trial-wall-body { padding: 16px 18px; }
.trial-wall-body p {
  margin: 0 0 12px;
  color: var(--text-secondary);
  font-size: 0.92em; line-height: 1.55;
}
.trial-wall-body p:last-child { margin-bottom: 0; }
.trial-wall-note { font-size: 0.86em !important; color: var(--text-muted) !important; }
.trial-wall-note a { color: var(--accent); }
.trial-wall-actions {
  display: flex; justify-content: flex-end; gap: 10px;
  padding: 12px 18px;
  border-top: 1px solid var(--border);
}
.trial-wall-actions .btn { text-decoration: none; display: inline-flex; align-items: center; }

.settings-inline-link {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--accent); font: inherit;
  text-decoration: underline;
}
.settings-inline-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ─── Admin: plan + trial ─────────────────────────────────────────── */
.admin-pill-comp { color: var(--warning); border-color: var(--warning); }

/* Trial + age columns. Both are scan-columns, so tabular figures — the
   point is comparing rows down the column, not reading any one value. */
.admin-trial, .admin-age {
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}
/* Urgency rides on weight + a coloured rule, not on the text colour. Both
   --warning and --danger drop under 2:1 as text on the lighter themes, and
   the cell already says "3d" / "ended 5d ago" — the words carry the meaning,
   the colour just makes the column scannable. */
.admin-trial-soon {
  color: var(--text); font-weight: 600;
  border-bottom: 2px solid var(--warning);
}
.admin-trial-over {
  color: var(--text); white-space: nowrap;
  border-bottom: 2px solid var(--danger);
}
.admin-null { color: var(--text-muted); }

/* Sortable headers. The caret is the state indicator; aria-sort carries the
   same thing for screen readers, so the arrow is never the only signal. */
.admin-sort-btn {
  background: none; border: 0; padding: 4px 2px;
  font: inherit; color: inherit; letter-spacing: inherit;
  cursor: pointer; white-space: nowrap;
}
.admin-sort-btn:hover { color: var(--text); }
.admin-sort-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.admin-sort-caret { font-size: 0.7em; color: var(--accent); }
th.admin-sorted { color: var(--text); }

/* Filter chips. Active state is border + weight + background, not colour
   alone, and aria-pressed carries it to assistive tech. */
.admin-filters {
  display: flex; flex-wrap: wrap; gap: 8px;
  padding: 0 0 14px;
}
.admin-filter {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 44px; padding: 8px 14px;
  background: none; cursor: pointer;
  border: 1px solid var(--border); border-radius: 4px;
  color: var(--text-secondary); font: inherit; font-size: 0.85em;
}
.admin-filter:hover { border-color: var(--text-muted); color: var(--text); }
.admin-filter:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.admin-filter-on {
  border-color: var(--accent);
  background: var(--bg-input);
  color: var(--text); font-weight: 600;
}
.admin-filter-n {
  font-variant-numeric: tabular-nums;
  color: var(--text-muted); font-size: 0.92em;
}
.admin-filter-on .admin-filter-n { color: var(--accent); }
.admin-plan-modal { width: min(420px, calc(100vw - 32px)); }
.admin-plan-who {
  color: var(--text) !important;
  font-size: 0.95em !important;
  margin-bottom: 14px !important;
}
.admin-plan-opts { display: flex; flex-direction: column; gap: 2px; margin-bottom: 18px; }
.admin-plan-opt {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px; min-height: 44px;
  border: 1px solid transparent; border-radius: 4px;
  cursor: pointer;
}
.admin-plan-opt:hover { background: var(--bg-input); }
.admin-plan-opt:focus-within { border-color: var(--accent); }
.admin-plan-opt input { margin-top: 3px; accent-color: var(--accent); }
.admin-plan-opt span { display: flex; flex-direction: column; gap: 2px; }
.admin-plan-opt strong { color: var(--text); font-size: 0.92em; font-weight: 600; }
.admin-plan-opt em { color: var(--text-muted); font-size: 0.82em; font-style: normal; }
.admin-plan-date-label {
  display: block; margin-bottom: 6px;
  font-size: 0.78em; letter-spacing: 0.5px;
  color: var(--text-muted);
}
.admin-plan-date-row { display: flex; gap: 8px; align-items: center; }
.admin-plan-date-row .input { flex: 1; min-width: 0; }
.admin-plan-hint {
  margin-top: 12px !important;
  font-size: 0.82em !important;
  color: var(--text-muted) !important;
}


/* ═══════════════════════════════════════════════════════════════════
   CAMEL — "Concrete Steel" (Lincolne Ventures shared design language)
   Source of truth: vc-crm/camel_design_system (tokens copied verbatim,
   local reference: .claude/skills/camel-design). Rules that matter:
   cool light-grey concrete ground, near-black ink, ONE accent (clay)
   for action, brick for danger ONLY, muted status family (sage / dust /
   ochre / mauve) for orientation, borders over shadows, 4px controls /
   6px cards / pill chips, sans only (Archivo display · Figtree UI ·
   IBM Plex Mono micro), 8px rhythm. Fonts load from index.html.
   ═══════════════════════════════════════════════════════════════════ */
[data-theme="camel"] {
  /* surfaces — steel-050 page, paper cards, shelf strips */
  --bg: #F2F3F5;
  --bg-elevated: #FFFFFF;
  --bg-header: #FFFFFF;
  --bg-tabs: #FFFFFF;              /* tabs share the header surface — one chrome group */
  --bg-filters: #F2F3F5;
  --bg-input: #FFFFFF;
  --camel-shelf: #E9EBED;          /* steel-100 — recessed strips, table heads */
  --camel-line-strong: #C4C8CE;    /* steel-300 — input borders, dashed empties */

  /* ink ramp — contrast pushed hard */
  --text: #111417;                 /* ink (steel-900) */
  --text-primary: #111417;
  --text-secondary: #2A2F34;       /* steel-800 */
  --text-muted: #5D656E;           /* inksoft (steel-600) */
  --text-dim: #9AA1A9;             /* inkfaint (steel-400) */
  --strike: #C4C8CE;

  --border: #DADDE1;               /* line (steel-200) — the hairline */
  --border-light: #E9EBED;

  /* clay is the ONE action colour; hover darkens, never lightens */
  --accent: #B24A2E;               /* clay-600 */
  --accent-hover: #8E3A22;         /* clay-700 */

  /* status — muted family; brick strictly danger */
  --success: #2F6A47;              /* sage-700 */
  --warning: #856314;              /* ochre-700 */
  --danger:  #B3261E;              /* brick-600 */
  --google-blue: #1a73e8;

  --delegate: #856314;             /* ochre family for "ACTING AS …" chrome */
  --delegate-bg: #F6E8C8;          /* ochre-50 */
  --delegate-border: #B08A2E;

  /* energy gauge = orientation, not alarm: sage / ochre / clay */
  --energy-low:  #2F6A47;
  --energy-med:  #856314;
  --energy-high: #B24A2E;

  /* borders carry structure — cards cast no shadow at rest */
  --card-top: rgba(0,0,0,0);
  --card-shadow: none;

  /* type registers. --font-ui flips the whole app to Figtree; the
     display + mono faces are consumed by the overrides below. */
  --font-ui: 'Figtree', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --camel-display: 'Archivo', 'Figtree', ui-sans-serif, system-ui, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --serif-display: 'Archivo', 'Figtree', sans-serif;   /* shared hooks land on Archivo */
  --serif-body: 'Figtree', ui-sans-serif, system-ui, sans-serif;
  --sans-display: 'Archivo', 'Figtree', sans-serif;
  --sans-body: 'Figtree', ui-sans-serif, system-ui, sans-serif;
  --jp: 'Figtree', sans-serif;     /* kanji glosses are hidden below */

  /* column subtitles — say what you actually know */
  }

/* selection is clay-50, not a clay slab */
[data-theme="camel"] ::selection { background: #F6E7E1; color: #111417; }

/* ── chrome: header on paper over a hairline, quiet links ── */
[data-theme="camel"] .header { border-bottom: 1px solid var(--border); }
/* When the tab strip follows, header + tabs form one chrome group: the group's
   single steel-200 hairline lives on .tabs, and the header keeps only a
   whisper of separation inside the surface. Pages that render a header with
   no tab strip keep the full border above. */
[data-theme="camel"] .header:has(+ .tabs) { border-bottom-color: var(--border-light); }
[data-theme="camel"] .logo {
  font-family: var(--camel-display);
  font-weight: 800; letter-spacing: -0.02em; font-size: 1.25em;
}
[data-theme="camel"] .header-link {
  letter-spacing: 0; font-size: 0.85em; font-weight: 500;
}
[data-theme="camel"] .tabs { border-bottom: 1px solid var(--border); }
/* Active underline overlaps the band's hairline so the steel line visibly
   breaks under the current tab instead of double-ruling beneath the accent. */
[data-theme="camel"] .tabs .tab { margin-bottom: -1px; }
[data-theme="camel"] .tab {
  letter-spacing: 0; font-size: 0.9em; font-weight: 500; padding: 10px 14px;
}
[data-theme="camel"] .tab.active { color: var(--text); }   /* clay stays in the underline */

/* section labels become mono eyebrows (inksoft, not accent) */
[data-theme="camel"] .section-label {
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-muted);
}
/* …except settings card headers, which are titles, not eyebrows: Archivo at
   heading size, the same display register as the page voices below. */
[data-theme="camel"] .settings-card > .section-label,
[data-theme="camel"] .settings-section > .section-label,
[data-theme="camel"] .settings-panel > .section-label,
[data-theme="camel"] .settings-card-head .section-label {
  font-family: var(--camel-display);
  font-size: 19px; letter-spacing: 0; text-transform: none;
  color: var(--text-secondary);
}

/* ── controls: 4px radius, no tracked caps, hover never lifts ── */
[data-theme="camel"] .btn {
  border-radius: 4px; letter-spacing: 0; font-weight: 600;
  color: #fff; padding: 10px 18px;
}
[data-theme="camel"] .btn:hover { transform: none; }
[data-theme="camel"] .btn-small { border-radius: 4px; }
[data-theme="camel"] .btn-outline { border-radius: 4px; color: var(--text-secondary); }
[data-theme="camel"] .btn-outline:hover { transform: none; background: var(--camel-shelf); }
/* re-assert transparent-variant text colours the .btn override above would
   otherwise repaint white (attribute selector outweighs the base classes) */
[data-theme="camel"] .btn-danger { color: var(--danger); background: transparent; }
[data-theme="camel"] .btn-danger:hover { color: #fff; background: var(--danger); }
[data-theme="camel"] .btn-google { color: var(--google-blue); background: transparent; }
[data-theme="camel"] .btn-success { color: var(--success); }
[data-theme="camel"] .input {
  border-radius: 4px; border-color: var(--camel-line-strong);
}
[data-theme="camel"] .input:focus {
  box-shadow: 0 0 0 3px rgba(178, 74, 46, 0.16);
}
[data-theme="camel"] .header-overflow-menu,
[data-theme="camel"] .ws-menu,
[data-theme="camel"] .kb-chip-popover {
  border-radius: 6px;
  box-shadow: 0 8px 24px -6px rgba(17,20,23,0.18), 0 2px 6px rgba(17,20,23,0.06);
}

/* ── filter chips: pills; active = ink fill ── */
[data-theme="camel"] .kb-chip { border-radius: 999px; letter-spacing: 0; }
[data-theme="camel"] .kb-chip-active {
  background: var(--text); color: var(--bg-elevated); border-color: var(--text);
}

/* ── the board: separate paper panels over sand ──
   Was one paper panel with hairline dividers, which read as a single slab
   once columns stretched to equal height. Each column is now its own panel
   and the sand page shows through the gutters, so the separation comes from
   space rather than a drawn rule. Uses the standard 12px board gap, so the
   group rail needs no gap override. */
[data-theme="camel"] .kb-board-kanban {
  gap: 12px;
  background: transparent;
  border: 0;
  border-radius: 0;
}
[data-theme="camel"] .kb-board-kanban .kb-col {
  /* Camel lets columns shrink below the 200px floor so six always fit
     without the board scrolling. */
  min-width: 0;
  /* The lane is the recessed tone and the card is the paper, not the other
     way round. Both used to be --bg-elevated, which left the card/column
     join at 0 L* with a 1.36:1 hairline carrying it alone; now the card
     lifts 7.05 L* off its lane (Trello measures 4.53 for the same join).
     The outline goes with it — a border here would be a third tone in a
     two-tone junction. */
  background: var(--camel-shelf);
  border: 1px solid transparent;
  border-radius: 8px;
}
/* Shelf strip follows the panel's rounded top. */
/* Head sits one step BELOW the lane (3.51 L*), so the lane reads as a tray
   with a tab rather than a flat block — the direction --camel-shelf always
   implied. #DEE1E6 is the floor: one step darker and the count, which is
   --text-muted, drops to 4.43:1 and fails AA.
   Type goes 10px -> 14px (matching the card title, so lane and card speak at
   one volume) with tracking wound back from .14em, which was only ever
   compensating for 10px. Scoped to the board: .kb-col-header is shared with
   the week view's inbox rail. */
[data-theme="camel"] .kb-board-kanban .kb-col-header {
  background: #DEE1E6;
  border-bottom: 1px solid #D3D7DD;
  border-radius: 8px 8px 0 0;
  font-size: 14px; letter-spacing: 0.07em; line-height: 1.2;
  padding: 12px 14px;
}
[data-theme="camel"] .kb-board-kanban .kb-col-count { font-size: 11px; }
/* Subtitle retired — it was the board's worst contrast at 2.18:1, and a
   column labelled INBOX doesn't need "Capture now, decide later." under it.
   content:none rather than deleting the shared rule: light and modernist
   still use --col-sub. */
[data-theme="camel"] .kb-board-kanban .kb-col-header::after { content: none; }
/* Groups shrink with the columns rather than holding the 200px-per-span floor. */
[data-theme="camel"] .kb-group { min-width: 0; }

/* column heads: shelf strip, mono micro-register, tabular count */
[data-theme="camel"] .kb-col-header {
  flex-wrap: wrap;
  background: var(--camel-shelf);
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.14em;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}
[data-theme="camel"] .kb-col-count {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  color: var(--text-muted); font-size: 10px;
}
/* kanji glosses retired under Camel — one language register per surface */
[data-theme="camel"] .kb-col[data-col] .kb-col-header > span:first-child::after { content: none; }
/* subtitle row inside the strip: quiet Figtree, sentence case */
[data-theme="camel"] .kb-col-header::after {
  content: var(--col-sub, "");
  flex-basis: 100%;
  margin-top: 2px;
  font-family: var(--font-ui); font-size: 11px; font-weight: 400;
  letter-spacing: 0; text-transform: none;
  color: var(--text-dim);
}

/* ── cards: hairline + 6px radius; the border does the talking ── */
[data-theme="camel"] .kb-card { border-radius: 6px; }
/* Board cards only. The border comes off and Trello's two-layer shadow takes
   over: the first layer is the drop, the second (0 0 1px) is a hairline ring
   that reads as an edge. A ring made of translucent black darkens whatever is
   behind it, so it can never become an independent third tone the way
   --border does. Overrides the base box-shadow outright; --card-top is
   transparent under camel anyway. */
[data-theme="camel"] .kb-board-kanban .kb-card {
  border-color: transparent;
  border-radius: 8px;
  padding: 12px 14px;
  box-shadow: rgba(30,31,33,0.22) 0 1px 1px 0, rgba(30,31,33,0.28) 0 0 1px 0;
}
[data-theme="camel"] .kb-board-kanban .kb-card:hover {
  border-color: transparent;
  box-shadow: rgba(30,31,33,0.28) 0 2px 4px 0, rgba(30,31,33,0.32) 0 0 1px 0;
}
[data-theme="camel"] .kb-board-kanban .kb-card-text { font-size: 14px; line-height: 1.43; }
/* A little more air now that the lane shows between cards. */
[data-theme="camel"] .kb-board-kanban .kb-col-body { padding: 10px; gap: 10px; }
[data-theme="camel"] .kb-card:hover { border-color: var(--camel-line-strong); }
[data-theme="camel"] .kb-card-text {
  font-size: 13px; font-weight: 500; line-height: 1.35;
}
/* overdue is danger, and danger is brick — never clay */
[data-theme="camel"] .kb-card-overdue { border-left-color: var(--danger); }
[data-theme="camel"] .kb-card-past::after { border-top-color: var(--warning); }

/* meta chips: mono micro-register on shelf */
[data-theme="camel"] .kb-card-chip {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.04em;
  border-radius: 4px; background: var(--camel-shelf);
}
[data-theme="camel"] .kb-action { border-radius: 4px; letter-spacing: 0; }

/* capture + pager: dashed hairlines, 4px */
[data-theme="camel"] .kb-capture-closed {
  border-radius: 4px; border-color: var(--camel-line-strong);
}
/* The capture slot becomes a card, so on the board it takes the card's corner
   rather than the tighter 4px. Both states get it: the + button and the input
   it swaps to are one slot, and a square input popping out of a rounded button
   reads as a glitch. Board-scoped — the week rail's cards are still 6px. */
[data-theme="camel"] .kb-board-kanban .kb-capture-closed,
[data-theme="camel"] .kb-board-kanban .kb-capture-input {
  border-radius: 8px;
}
/* Paper at rest, not just on hover. The base rule leaves the slot transparent
   so "page bg shows through" — written when the column was white and that read
   as an empty card. The lane is the recessed tone now, so a transparent slot
   reads as a hole punched in the tray instead. Filling it makes the slot the
   same paper as the card it becomes; the dashed edge is what still says empty.
   Hover keeps its job — border and glyph go to accent. */
[data-theme="camel"] .kb-board-kanban .kb-capture-closed {
  background: var(--bg-elevated);
}
[data-theme="camel"] .kb-col-more { border-radius: 4px; }

/* ── task drawer: 6px card writ large, modal shadow earns itself ── */
[data-theme="camel"] .kb-detail-panel {
  border-radius: 6px;
  box-shadow: 0 24px 60px -12px rgba(17,20,23,0.28);
}
[data-theme="camel"] .kb-detail-label {
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  letter-spacing: 0.14em;
}

/* ── display register: Archivo 800, tight, for the page voices ── */
[data-theme="camel"] .today-date,
[data-theme="camel"] .today-hero-greet {
  font-family: var(--camel-display);
  font-weight: 800; letter-spacing: -0.03em;
}

/* ── small-text register: Figtree needs a 12px floor ──
   At 400 weight Figtree's tight fit closes up below ~12px, so the
   settings meta tier (URLs, chips, sublabels, sub-tabs) read cramped on
   paper. Lift them to 12px with a touch of tracking, running copy to
   13px, and give labels the nav's 500 so title / meta / eyebrow sit on
   three distinct weights. Eyebrows, buttons and the 14px base are
   untouched. Camel only — dark stays mono, modernist stays Jakarta. */
[data-theme="camel"] .booking-link-url,
[data-theme="camel"] .sb-member,
[data-theme="camel"] .settings-sublabel,
[data-theme="camel"] .cal-share-email,
[data-theme="camel"] .booking-create-check,
[data-theme="camel"] .sb-tab { font-size: 12px; letter-spacing: 0.02em; }
[data-theme="camel"] .booking-meet-badge { font-size: 10.5px; letter-spacing: 0.06em; }
[data-theme="camel"] .settings-empty-hint,
[data-theme="camel"] .settings-card p { font-size: 13px; line-height: 1.6; }
[data-theme="camel"] .settings-label,
[data-theme="camel"] .booking-create-check { font-weight: 500; }
/* group headings sit in the same display register as the card title above */
[data-theme="camel"] .cal-share-subhead { font-family: var(--camel-display); letter-spacing: 0; }

/* ── board footer: the four muted tints, given a job ──
   Camel-only summary strip under the kanban. Each decided-state column
   gets its tint (dust / sage / mauve / ochre) as a swatch beside a mono
   label + tabular count. Hidden in every other theme. */
.kb-board-footer { display: none; }
[data-theme="camel"] .kb-board-footer {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 6px 24px;
  padding: 10px 2px 0;
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-muted);
}
[data-theme="camel"] .kb-bf-item { display: inline-flex; align-items: center; gap: 7px; }
[data-theme="camel"] .kb-bf-count { color: var(--text); font-variant-numeric: tabular-nums; }
[data-theme="camel"] .kb-bf-swatch {
  width: 9px; height: 9px; border-radius: 2px; flex-shrink: 0;
  border: 1px solid transparent;
}
[data-theme="camel"] .kb-bf-swatch[data-col="in_progress"] { background: #F2DFD4; border-color: #8A4B2A; } /* clay */
[data-theme="camel"] .kb-bf-swatch[data-col="scheduled"] { background: #DBE7EE; border-color: #2A5A72; } /* dust */
[data-theme="camel"] .kb-bf-swatch[data-col="done"]      { background: #DEEBE2; border-color: #2F6A47; } /* sage */
[data-theme="camel"] .kb-bf-swatch[data-col="someday"]   { background: #E4DFEE; border-color: #5C4C77; } /* mauve */
[data-theme="camel"] .kb-bf-swatch[data-col="trashed"]   { background: #F6E8C8; border-color: #856314; } /* ochre */

/* ─── TIMELINE (Gantt) ────────────────────────────────────── */
.tl-wrap { --tl-lw: 260px; }
.tl-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; flex-wrap: wrap; margin: 6px 0 14px; }
.tl-eyebrow { font-size: 0.6rem; letter-spacing: 3px; text-transform: uppercase; color: var(--accent); margin: 0 0 5px; }
.tl-title { font-size: 1.3rem; margin: 0; font-weight: 700; letter-spacing: -0.3px; }
.tl-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tl-segmented { display: inline-flex; border: 1px solid var(--border); border-radius: 3px; overflow: hidden; }
.tl-segmented button { font: inherit; font-size: 0.66rem; letter-spacing: 0.5px; text-transform: uppercase; background: var(--bg-elevated); color: var(--text-muted); border: none; padding: 7px 12px; cursor: pointer; border-right: 1px solid var(--border); }
.tl-segmented button:last-child { border-right: none; }
.tl-segmented button.on { background: var(--accent); color: #fff; font-weight: 700; }
.tl-ctl { font: inherit; font-size: 0.66rem; letter-spacing: 0.5px; text-transform: uppercase; background: var(--bg-elevated); color: var(--text-secondary); border: 1px solid var(--border); border-radius: 3px; padding: 7px 12px; cursor: pointer; }
.tl-ctl:hover { border-color: var(--text-muted); color: var(--text); }
/* Sits at the right end of the legend row; wraps under it on narrow screens. */
/* One type treatment for the whole row — the uppercase tracked grey the FILTER
   label already used. Set once on the container so every child inherits it and
   the cluster reads as a single band of chrome rather than four competing
   styles. Children below override only box properties, never type. */
.tl-filter {
  display: inline-flex; align-items: center; gap: 7px; margin-left: auto;
  font-size: 0.6rem; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-muted);
}
.tl-filter-lab { color: inherit; }
.tl-ws-note { color: inherit; }
.tl-filter-sel { font: inherit; letter-spacing: inherit; text-transform: inherit; background: var(--bg-elevated); color: inherit; border: 1px solid var(--border); border-radius: 3px; padding: 6px 8px; max-width: 220px; cursor: pointer; }
.tl-filter-sel:hover { border-color: var(--text-muted); color: var(--text); }
/* Row-filter toggles. Sized to the selects beside them so the filter cluster
   reads as one control group, and each carries the count it would hide — a
   filter whose cost is invisible is one you forget you left on. */
.tl-toggle { display: inline-flex; align-items: center; gap: 6px; color: inherit; cursor: pointer; padding: 5px 2px; min-height: 32px; }
.tl-toggle:hover { color: var(--text); }
/* Needed explicitly: the author `display: inline-flex` above outranks the UA
   stylesheet's [hidden] rule, so the attribute alone would not hide it. */
.tl-toggle[hidden] { display: none; }
.tl-toggle input { accent-color: var(--accent); cursor: pointer; margin: 0; width: 13px; height: 13px; flex: none; }
.tl-toggle span { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.tl-toggle input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* First-run empty state. Larger than .kb-empty's usual muted line because this
   one asks for an action rather than reporting a count — the lead has to carry
   at body-text contrast, not --text-dim. */
.tl-empty { padding: 56px 24px; max-width: 460px; margin: 0 auto; }
.tl-empty-lead { margin: 0 0 6px; font-size: 1.05rem; color: var(--text); }
.tl-empty-sub { margin: 0 0 20px; font-size: 0.85rem; line-height: 1.55; color: var(--text-muted); }
.tl-empty-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.tl-empty-cta {
  font: inherit; font-size: 0.75rem; letter-spacing: 1.5px; text-transform: uppercase;
  background: var(--accent); color: #fff; border: 1px solid var(--accent);
  border-radius: 3px; padding: 10px 18px; min-height: 44px; cursor: pointer;
}
.tl-empty-cta:hover { filter: brightness(1.08); }
.tl-empty-cta:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tl-empty .tl-clear-filters { margin-left: 0; min-height: 44px; padding: 10px 14px; }

.tl-clear-filters { font: inherit; background: none; border: 1px solid var(--border); border-radius: 3px; color: var(--text-secondary); padding: 5px 9px; margin-left: 6px; cursor: pointer; }
.tl-clear-filters:hover { border-color: var(--text-muted); color: var(--text); }
.tl-clear-filters:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (pointer: coarse) { .tl-toggle { min-height: 44px; } .tl-clear-filters { min-height: 44px; } }

.tl-legend { display: flex; gap: 16px; flex-wrap: wrap; align-items: center; margin: 0 0 14px; padding: 9px 13px; border: 1px solid var(--border-light); border-radius: 4px; }
.tl-li { display: inline-flex; align-items: center; gap: 7px; font-size: 0.66rem; color: var(--text-secondary); }
.tl-sw { width: 20px; height: 9px; border-radius: 2px; flex: none; }
.tl-sw.ip { background: var(--accent); }
.tl-sw.sc { background: transparent; border: 1px solid var(--warning); }
.tl-sw.dn { background: transparent; border: 1px solid var(--success); }
.tl-sw.di { width: 10px; height: 10px; transform: rotate(45deg); background: var(--text); border-radius: 1px; }
.tl-sw.di.late { background: var(--danger); }

.tl-gantt { border: 1px solid var(--border); border-radius: 6px; overflow: hidden; background: var(--bg-elevated); }
.tl-axis { display: grid; grid-template-columns: var(--tl-lw) 1fr; border-bottom: 1px solid var(--border); background: var(--bg-elevated); }
.tl-corner { padding: 9px 13px; border-right: 1px solid var(--border); display: flex; align-items: flex-end; }
.tl-corner span { font-size: 0.55rem; letter-spacing: 2px; text-transform: uppercase; color: var(--text-dim); }
.tl-months { display: flex; border-bottom: 1px solid var(--border-light); }
.tl-month { font-size: 0.6rem; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-secondary); padding: 6px 0 5px 9px; border-right: 1px solid var(--border-light); overflow: hidden; white-space: nowrap; }
/* Today's band on the day scale — with only three days up, marking which one
   you're on saves reading the dates. */
.tl-month-today { color: var(--text); font-weight: 700; }
.tl-weeks { display: flex; }
.tl-week { flex: 1 0 0; font-size: 0.58rem; color: var(--text-dim); padding: 4px 0 5px 5px; border-right: 1px solid var(--border-light); font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; }
/* Hour columns on the day scale: narrow, so the label is centred rather than
   hung off the left edge like a date. */
.tl-hour { padding-left: 0; text-align: center; letter-spacing: 0.5px; }
.tl-week:last-child { border-right: none; }
.tl-week-wknd { background: color-mix(in srgb, var(--text-dim) 9%, transparent); }
.tl-week-mon { color: var(--text-dim); }

.tl-body { position: relative; }
.tl-grid { position: absolute; inset: 0; left: var(--tl-lw); pointer-events: none; z-index: 4; }
.tl-gline { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--border-light); }
/* Midnight on the day scale — the day boundary has to outrank the hour ticks
   between it, or three days read as one undifferentiated ruler. */
.tl-gline-day { background: var(--border); }
.tl-today { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--danger); }
.tl-today-tag { position: absolute; top: 0; left: 50%; transform: translateX(-50%); font-size: 0.48rem; letter-spacing: 1px; font-weight: 700; color: #fff; background: var(--danger); padding: 1px 4px; border-radius: 0 0 3px 3px; white-space: nowrap; }

.tl-grouprow { display: grid; grid-template-columns: var(--tl-lw) 1fr; background: var(--bg-elevated); border-top: 1px solid var(--border); }
.tl-grouprow:first-child { border-top: none; }
.tl-group-head { padding: 8px 13px; display: flex; align-items: center; gap: 9px; border-right: 1px solid var(--border); }
.tl-group-name { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.2px; }
.tl-group-meta { font-size: 0.58rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.tl-group-track { position: relative; }

.tl-row { display: grid; grid-template-columns: var(--tl-lw) 1fr; border-top: 1px solid var(--border-light); position: relative; z-index: 2; }
.tl-row:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.tl-ms-lane { background: color-mix(in srgb, var(--text-dim) 5%, transparent); }
.tl-label { padding: 0 13px; height: 34px; display: flex; align-items: center; gap: 8px; border-right: 1px solid var(--border); overflow: hidden; }
.tl-t-name { font-size: 0.7rem; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Task name opens the drawer. Stretched to the full row height so the target
   is the row, not the glyphs — and keeps ellipsis, which a flex child loses. */
button.tl-t-name { font-family: inherit; background: none; border: none; padding: 0; margin: 0; text-align: left; cursor: pointer; flex: 1; min-width: 0; align-self: stretch; line-height: 34px; }
button.tl-t-name:hover { color: var(--accent); }
button.tl-t-name:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 2px; }
.tl-label-sub { font-size: 0.56rem; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-dim); }
/* Owner bubble: solid disc in the assignee's colour (--tl-owner-hue, set per
   assignee inline), darkened so white initials clear 5:1 on every hue in the
   palette. Same fill in all themes — the disc reads against light and dark. */
.tl-owner { margin-left: auto; font-size: 0.6rem; letter-spacing: -0.02em; color: #fff;
  background: color-mix(in srgb, var(--tl-owner-hue, var(--text-dim)) 55%, #000);
  border-radius: 999px; width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; flex: none; font-weight: 700; }
.tl-track { position: relative; height: 34px; }
.tl-nobar { position: absolute; left: 6px; top: 50%; transform: translateY(-50%); color: var(--text-dim); font-size: 0.7rem; }

/* Width is the task's real span as a percentage of the window — no rounding up,
   so a 30-minute block and a 4-hour block differ on the day scale. min-width is
   the only floor: it keeps a bar that projects to under 3px hoverable instead
   of inflating every short one to the same stub. */
.tl-bar { position: absolute; top: 50%; transform: translateY(-50%); height: 16px; min-width: 3px; border-radius: 3px; display: flex; align-items: center; overflow: hidden; font-size: 0.55rem; white-space: nowrap; cursor: default; z-index: 1; }
.tl-bar b { padding: 0 7px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
.tl-bar.ip { background: var(--accent); color: #fff; }
.tl-bar.sc { background: color-mix(in srgb, var(--warning) 16%, transparent); border: 1px solid var(--warning); color: var(--warning); }
.tl-bar.dn { background: color-mix(in srgb, var(--success) 14%, transparent); border: 1px solid var(--success); color: var(--success); }
.tl-bar.dn b::before { content: '✓ '; }

/* ── Work breakdown on the Gantt ──
   A parent's bar is a rolled-up span, not work you sit down and do, so it's
   drawn the way PM tools draw a summary task: thin, square, neutral, with end
   caps. The title lives in the label column, so hiding it inside the bar costs
   nothing and keeps a long span from reading as a task in its own right.
   overflow goes visible because .tl-bar clips, which would eat the caps. */
.tl-bar.sum { height: 9px; border-radius: 0; overflow: visible; background: var(--text-secondary); border: 0; }
.tl-bar.sum b { display: none; }
.tl-bar.sum::before, .tl-bar.sum::after {
  content: ''; position: absolute; top: 100%;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 4px solid var(--text-secondary);
}
.tl-bar.sum::before { left: 0; }
.tl-bar.sum::after { right: 0; }
/* A subtask reaching past its parent's due date. Outlined rather than
   recoloured so the bar keeps saying what status it is. */
.tl-bar.ovr { outline: 1px solid var(--danger); outline-offset: 1px; }
.tl-over-tag {
  flex: none; font-size: 0.5rem; letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--danger); border: 1px solid var(--danger); border-radius: 2px; padding: 0 3px;
}
.tl-row-child { background: color-mix(in srgb, var(--text-dim) 3%, transparent); }
.tl-row-child .tl-label { padding-left: 22px; }
.tl-child-tick { flex: none; color: var(--text-dim); font-size: 0.7rem; line-height: 1; }
.tl-sum-work { flex: none; font-size: 0.55rem; letter-spacing: 1px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.tl-sw.sum { height: 4px; border-radius: 0; background: var(--text-secondary); }

.tl-diamond { position: absolute; top: 50%; width: 12px; height: 12px; transform: translate(-50%, -50%) rotate(45deg); background: var(--text); border-radius: 2px; cursor: pointer; z-index: 3; }
.tl-diamond:hover { box-shadow: 0 0 0 3px color-mix(in srgb, var(--text) 20%, transparent); }
.tl-diamond.hit { background: var(--success); }
.tl-diamond.late { background: var(--danger); }
.tl-ms-label { position: absolute; top: 50%; transform: translate(10px, -50%); font-size: 0.56rem; color: var(--text-muted); white-space: nowrap; pointer-events: none; }

.tl-pop { position: fixed; z-index: 1000; width: 260px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 12px 34px -12px rgba(0,0,0,0.5); padding: 12px; display: flex; flex-direction: column; gap: 9px; }
.tl-pop-title { font-size: 0.62rem; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-muted); }
.tl-pop-input, .tl-pop-date, .tl-pop-sel { width: 100%; }
.tl-pop-lab { font-size: 0.66rem; color: var(--text-secondary); display: flex; flex-direction: column; gap: 4px; }
.tl-pop-actions { display: flex; align-items: center; gap: 6px; margin-top: 2px; }
.tl-pop-btn { font: inherit; font-size: 0.62rem; letter-spacing: 0.4px; background: var(--bg-input); color: var(--text-secondary); border: 1px solid var(--border); border-radius: 3px; padding: 6px 10px; cursor: pointer; }
.tl-pop-btn:hover { border-color: var(--text-muted); color: var(--text); }
.tl-pop-primary { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 700; }
.tl-pop-primary:hover { color: #fff; }
.tl-pop-danger { color: var(--danger); border-color: var(--danger); }
.tl-pop-danger:hover { background: var(--danger); color: #fff; }

@media (max-width: 680px) {
  .tl-wrap { --tl-lw: 140px; }
}

/* Timeline — bar tooltip + task modal */
.tl-bar { cursor: pointer; }
.tl-pill { font-size: 0.56rem; letter-spacing: 0.4px; text-transform: uppercase; font-weight: 700; border: 1px solid; border-radius: 2px; padding: 1px 6px; }

.tl-tip { position: fixed; z-index: 1200; pointer-events: none; max-width: 300px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 5px; box-shadow: 0 10px 30px -10px rgba(0,0,0,0.55); padding: 9px 11px; display: none; }
.tl-tip-title { font-size: 0.74rem; font-weight: 700; color: var(--text); line-height: 1.35; margin-bottom: 6px; }
.tl-tip-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 5px; }
.tl-tip-proj { font-size: 0.62rem; color: var(--text-muted); }
.tl-tip-dates { font-size: 0.62rem; color: var(--text-secondary); font-variant-numeric: tabular-nums; }

.tl-modal { position: fixed; inset: 0; z-index: 1100; display: flex; align-items: center; justify-content: center; padding: 20px; }
.tl-modal-bg { position: absolute; inset: 0; background: rgba(0,0,0,0.5); }
.tl-modal-card { position: relative; width: 100%; max-width: 380px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 24px 60px -20px rgba(0,0,0,0.6); overflow: hidden; }
.tl-modal-head { display: flex; align-items: flex-start; gap: 12px; padding: 15px 16px 12px; border-bottom: 1px solid var(--border-light); }
.tl-modal-title { font-size: 0.92rem; font-weight: 700; line-height: 1.35; flex: 1; }
.tl-modal-x { font: inherit; background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 0.9rem; padding: 0 2px; }
.tl-modal-x:hover { color: var(--text); }
.tl-modal-body { padding: 14px 16px; display: flex; flex-direction: column; gap: 9px; }
.tl-modal-pill { margin-bottom: 3px; }
.tl-m-drow { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; font-size: 0.74rem; }
.tl-m-dlab { color: var(--text-dim); text-transform: uppercase; letter-spacing: 1px; font-size: 0.58rem; }
.tl-m-dval { color: var(--text); font-variant-numeric: tabular-nums; text-align: right; }
.tl-modal-foot { display: flex; gap: 8px; justify-content: flex-end; padding: 12px 16px; border-top: 1px solid var(--border-light); }

/* Timeline modal — richer meta */
.tl-modal-pill { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 3px; }
.tl-due { font-size: 0.58rem; font-weight: 700; letter-spacing: 0.4px; text-transform: uppercase; border: 1px solid var(--text-dim); color: var(--text-muted); border-radius: 2px; padding: 1px 6px; }
.tl-due.soon { color: var(--warning); border-color: var(--warning); }
.tl-due.late { color: var(--danger); border-color: var(--danger); }
.tl-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.tl-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 0.62rem; color: var(--text-secondary); background: var(--bg-input); border: 1px solid var(--border); border-radius: 3px; padding: 2px 7px; }
.tl-m-wait { font-size: 0.68rem; color: var(--warning); background: color-mix(in srgb, var(--warning) 12%, transparent); border: 1px solid color-mix(in srgb, var(--warning) 40%, transparent); border-radius: 3px; padding: 5px 9px; }
.tl-m-desc { font-size: 0.7rem; color: var(--text-secondary); line-height: 1.5; padding-top: 4px; border-top: 1px solid var(--border-light); }

/* ─── HOME LAYOUTS ─────────────────────────────────────────────
   Home is one of three layouts behind a dropdown: Timeline (the rail
   above, untouched), Console and Spaces. Everything here reads the
   theme tokens — tones are the status vars mixed into the surface, text
   stays --text with a coloured rule (the status vars fail contrast as text
   on the light themes). */
.home-modes { position: relative; display: inline-flex; }
/* Timeline hero: the label is noise beside the date; the value alone reads. */
.today-hero-top .home-modes { margin-left: auto; }
.today-hero-top .home-modes-label { display: none; }
.home-modes-trigger {
  display: inline-flex; align-items: center; gap: 7px;
  font: inherit; font-size: 0.8em; color: var(--text);
  background: var(--bg-input); border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 10px; min-height: 32px; cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.home-modes-trigger:hover { border-color: var(--border-strong, var(--text-dim)); }
.home-modes-label {
  font-family: var(--mono); font-size: 0.8em; font-weight: 600;
  letter-spacing: 1.4px; text-transform: uppercase; color: var(--text-dim);
}
.home-modes-cur { font-weight: 600; }
.home-modes-caret { color: var(--text-dim); font-size: 0.9em; line-height: 1; }
.home-modes-menu {
  position: absolute; right: 0; top: calc(100% + 6px);
  min-width: 230px; padding: 4px 0; z-index: 200;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 4px 14px rgba(0,0,0,0.18);
}
.home-modes-menu[hidden] { display: none; }
.home-menu-row {
  display: flex; align-items: center; gap: 8px; padding-right: 10px;
}
/* --bg-input matches --bg-elevated on the light themes, so the hover row has
   to come from --bg plus an accent rule rather than a fill. */
.home-menu-row:hover { background: var(--bg); }
.home-menu-pick {
  flex: 1 1 auto; text-align: left;
  font: inherit; font-size: 0.85em; font-weight: 600; color: var(--text-secondary);
  background: none; border: none; padding: 8px 10px; cursor: pointer;
}
.home-menu-pick::before { content: "●"; font-size: 0.66em; margin-right: 8px; color: transparent; }
.home-menu-pick:hover { color: var(--text); }
.home-menu-pick-active { color: var(--accent); }
.home-menu-pick-active::before { color: var(--accent); }
.home-menu-def, .home-menu-setdef {
  font-family: var(--mono); font-size: 0.62em; font-weight: 600;
  letter-spacing: 0.8px; text-transform: uppercase; white-space: nowrap;
}
.home-menu-def { color: var(--text-dim); }
.home-menu-setdef {
  color: var(--accent); background: none; border: none; padding: 6px 2px;
  cursor: pointer; text-decoration: underline; text-decoration-style: dashed;
  text-underline-offset: 3px;
}
.home-menu-setdef:hover { color: var(--accent-hover, var(--accent)); }
.home-modes-trigger:focus-visible, .home-menu-pick:focus-visible, .home-menu-setdef:focus-visible,
.home-more:focus-visible, .home-kebab:focus-visible, .home-tmenu-item:focus-visible,
.home-cbx:focus-visible, .home-proj:focus-visible, .home-ws-head:focus-visible,
.home-inbox-chip:focus-visible, .home-pname:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.tabs-capture-field:has(:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── header row (Console + Spaces) ── */
/* flex-end so the switcher's bottom edge lands on the subtitle's, not the
   eyebrow's — the actions read as part of the same block. */
.home-topbar {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  margin: 16px 0 18px; padding-bottom: 16px; border-bottom: 1px solid var(--border);
}
.home-eyebrow {
  font-family: var(--mono); font-size: 0.68em; font-weight: 600;
  letter-spacing: 1.4px; text-transform: uppercase; color: var(--text-dim);
}
.home-h1 {
  font-size: 26px; line-height: 1.15; font-weight: 600; letter-spacing: -0.01em;
  color: var(--text-primary, var(--text)); margin: 4px 0 3px; text-wrap: balance;
}
[data-theme="light"] .home-h1 { font-family: var(--serif-display); }
[data-theme="modernist"] .home-h1 { font-family: var(--sans-display); font-weight: 700; }
.home-sub { color: var(--text-secondary); font-size: 0.9em; }
.home-sub b { color: var(--text); font-weight: 600; }
.home-topbar-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.home-capture-input {
  font: inherit; font-size: 0.9em; color: var(--text);
  background: var(--bg-input); border: 1px solid var(--border); border-radius: 6px;
  padding: 9px 12px; min-width: 260px; min-height: 40px;
}
.home-capture-input::placeholder { color: var(--text-dim); }
.home-capture-input:focus { outline: none; }
.home-inbox-chip {
  display: inline-flex; align-items: center; min-height: 40px;
  font-family: var(--mono); font-size: 0.68em; font-weight: 600;
  letter-spacing: 0.8px; text-transform: uppercase; text-decoration: none;
  color: var(--text); background: color-mix(in srgb, var(--warning) 16%, transparent);
  box-shadow: inset 3px 0 0 var(--warning); border-radius: 6px; padding: 8px 12px;
}
.home-inbox-chip:hover { background: color-mix(in srgb, var(--warning) 24%, transparent); }
.home-inbox-chip-clear { background: none; box-shadow: inset 3px 0 0 var(--success); color: var(--text-muted); }
.home-btn-primary { padding: 9px 16px; font-size: 0.85em; letter-spacing: 0; border-radius: 6px; min-height: 40px; }

/* ── pills + chips ── */
/* Same rounded rectangle as .home-chip — same radius, same padding, same mono
   size — so a row's size chip and its state pills read as one set of tags.
   The tone is the only thing that separates them: it tints the fill and draws
   the border, in place of the inset left bar this used to carry. */
/* One tag geometry for every pill and chip on a row: fixed height, no border,
   tint only. min-height plus line-height:1 is what makes them equal — content
   height alone differs, since a pill carrying an 11px glyph is taller than one
   carrying only text, and that was the visible offset between OVERDUE and
   SCHEDULED. No border because the tint alone reads cleanly at this size and a
   1px outline on a 20px tag is most of what the eye sees. */
.home-pill, .home-chip {
  min-height: 20px; line-height: 1; padding: 0 8px;
  border: none; border-radius: 4px; white-space: nowrap;
}
.home-pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--mono); font-size: 0.7em; font-weight: 600;
  letter-spacing: 0.3px; text-transform: uppercase; color: var(--text);
  background: color-mix(in srgb, var(--home-tone) 17%, transparent);
}
/* The glyph inherits the pill's text colour rather than taking --home-tone:
   the tone colours fail 3:1 against their own tinted background on some themes
   (ochre lands at 1.56 on modernist). The pill already carries the tone in its
   tint — the glyph's job is to be a second, non-colour channel, so shape is
   what it contributes. Worst case this way is 8:1. */
.home-pill-i { flex: none; }
/* A status someone stated carries a dot; a derived one doesn't. currentColor,
   not --home-tone, for exactly the reason above — the tone fails 3:1 on its
   own tint. Shape is the channel; colour still comes from the pill's tint. */
.home-pill-set i { width: 5px; height: 5px; border-radius: 50%; background: currentColor; flex: none; }
.home-pill-sage  { --home-tone: var(--success); }
.home-pill-ochre { --home-tone: var(--warning); }
.home-pill-brick { --home-tone: var(--danger); }
.home-pill-dust  { --home-tone: var(--text-muted); }
.home-pill-clay  { --home-tone: var(--accent); }
/* The one cool tone. --google-blue is the palette's only blue and is already
   used as a plain colour elsewhere (.home-av-4), not as a Google marker. It
   exists here because the warm tones run out: clay sits 39-47 RGB from brick
   on the sand themes, so an accent status pill beside an OVERDUE one is
   nearly the same colour. Blue clears both alarm tones by 230+ everywhere. */
.home-pill-azure { --home-tone: var(--google-blue); }
/* Tinted like a pill rather than outlined: --bg-input resolves to the same
   colour as the card on the sand themes, so a borderless chip on that
   background would have disappeared. Text is --text, not --text-secondary:
   on the tint that only reached 3.27:1 on the light theme at this size.
   Weight, not colour, is what keeps a chip quieter than a pill. */
.home-chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 0.76em; font-weight: 500; color: var(--text);
  background: color-mix(in srgb, var(--text-muted) 13%, transparent);
  max-width: 220px; overflow: hidden; text-overflow: ellipsis;
}
.home-chip .cat-swatch { margin-right: 0; width: 7px; height: 7px; }
/* No colour override: --text-muted on the chip's own tint is the worst
   contrast on the card, and these are the smallest text on a row. Mono and
   size carry the distinction instead. */
.home-chip-mono { font-family: var(--mono); font-size: 0.7em; letter-spacing: 0.3px; }

/* ── cards + grid ── */
.home-grid { display: grid; grid-template-columns: 1.5fr 1fr; gap: 12px; align-items: start; }
.home-col { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.home-card {
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px;
  padding: 16px; display: flex; flex-direction: column; gap: 12px; min-width: 0;
  box-shadow: var(--card-shadow, none);
}
.home-card h2 { font-size: 1em; font-weight: 600; letter-spacing: -0.1px; margin: 0; color: var(--text); }
.home-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.home-more {
  font: inherit; font-size: 0.78em; font-weight: 600; color: var(--accent);
  background: none; border: none; padding: 0; cursor: pointer; text-decoration: none; white-space: nowrap;
}
.home-more:hover { text-decoration: underline; }
.home-more[disabled] { color: var(--text-dim); cursor: progress; text-decoration: none; }
.home-more-foot { display: inline-block; margin-top: 2px; }
.home-empty { color: var(--text-dim); font-size: 0.85em; line-height: 1.6; }

/* ── projects runway ── */
.home-runway { padding: 16px 0 6px; }
.home-runway .home-card-head { padding: 0 16px; }
.home-runway .home-empty { padding: 0 16px 10px; }
.home-ptable-scroll { overflow-x: auto; }
.home-ptable { width: 100%; border-collapse: collapse; font-size: 0.88em; }
.home-ptable th {
  font-family: var(--mono); font-size: 0.7em; font-weight: 600;
  letter-spacing: 1.2px; text-transform: uppercase; color: var(--text-dim);
  text-align: left; padding: 10px 16px 8px; border-bottom: 1px solid var(--border);
}
.home-ptable td { padding: 10px 12px; border-bottom: 1px solid var(--border-light); vertical-align: middle; }
.home-ptable td:first-child, .home-ptable th:first-child { padding-left: 16px; }
.home-ptable tr:last-child td { border-bottom: none; }
.home-ptable td:first-child, .home-ptable td:nth-child(2), .home-ptable td:nth-child(4) { white-space: nowrap; }
.home-pname { font-weight: 600; color: var(--text); text-decoration: none; }
.home-pname:hover { color: var(--accent); }
.home-pbar { display: flex; align-items: center; gap: 8px; }
.home-pbar-track {
  width: 72px; height: 6px; background: var(--bg-input); border: 1px solid var(--border);
  border-radius: 3px; overflow: hidden; flex: none;
}
.home-pbar-track i { display: block; height: 100%; background: var(--success); }
.home-pbar-n { font-family: var(--mono); font-size: 0.8em; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.home-pws { color: var(--text-secondary); }

/* ── wins ── */
.home-wins { display: flex; flex-direction: column; }
.home-win { display: flex; gap: 10px; align-items: baseline; padding: 8px 0; border-bottom: 1px solid var(--border-light); }
.home-win:last-child { border-bottom: none; }
.home-win-tick { color: var(--success); font-weight: 800; font-size: 0.9em; flex: none; width: 14px; }
.home-win-plus { color: var(--accent); font-weight: 800; font-size: 0.9em; flex: none; width: 14px; }
.home-win-body { flex: 1; min-width: 0; font-size: 0.9em; font-weight: 500; }
.home-win-when { font-family: var(--mono); font-size: 0.72em; color: var(--text-dim); font-variant-numeric: tabular-nums; }
/* The win title opens the win modal. Same reset the Next-actions title uses:
   a button that has to sit where the div did. */
.home-win-open {
  display: block; width: 100%; text-align: left; padding: 0; border: 0;
  background: none; font: inherit; font-size: 0.9em; font-weight: 500;
  color: inherit; cursor: pointer;
}
.home-win-open:hover { text-decoration: underline; text-underline-offset: 2px; }
.home-win-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
/* A win carrying a note says so without spending a row on it. */
.home-win[data-noted] .home-win-open::after {
  content: "\201C\201D"; margin-left: 6px; font-size: 0.8em; color: var(--text-dim); letter-spacing: -1px;
}
.home-win-meta { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.home-win-note {
  width: 100%; resize: vertical; padding: 9px 10px; border: 1px solid var(--border);
  border-radius: 4px; background: var(--bg-input); color: var(--text); font: inherit; font-size: 0.88em;
}
.home-win-note::placeholder { color: var(--text-dim); }
.home-win-warn { margin-top: 8px; font-size: 0.76em; color: var(--text-dim); flex-basis: 100%; }
.home-win-form { margin: 0; }
.home-win-form[hidden] { display: none; }

/* ── task lists (next actions, blocked) ── */
.home-tasklist { display: flex; flex-direction: column; }
.home-task { display: flex; align-items: flex-start; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--border-light); }
.home-task:last-child { border-bottom: none; padding-bottom: 0; }
.home-task:first-child { padding-top: 0; }
.home-cbx {
  position: relative; width: 16px; height: 16px; margin-top: 2px; flex: none;
  border: 1.5px solid var(--text-muted); border-radius: 4px; background: var(--bg-elevated);
  cursor: pointer; padding: 0;
}
.home-cbx::after { content: ''; position: absolute; inset: -12px; }   /* 40px hit area */
.home-cbx:hover { border-color: var(--success); background: color-mix(in srgb, var(--success) 15%, transparent); }
.home-task-body { flex: 1; min-width: 0; }
.home-task-title { font-size: 0.9em; font-weight: 600; }
/* The title opens the board drawer. A button, so it is tabbable and Enter
   works, but it has to sit in the row exactly where the div did — hence the
   full reset. The underline on hover/focus is the only affordance: a
   background tint would fight the row's own hover. */
.home-task-open {
  display: block; width: 100%; text-align: left; padding: 0; border: 0;
  background: none; font: inherit; font-size: 0.9em; font-weight: 600;
  color: inherit; cursor: pointer;
}
.home-task-open:hover { text-decoration: underline; text-underline-offset: 2px; }
.home-task-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.home-task[aria-busy="true"] .home-task-open { opacity: 0.6; cursor: progress; }
.home-task-note { font-size: 0.8em; color: var(--text-muted); margin-top: 2px; }
.home-task-meta { display: flex; gap: 6px; margin-top: 4px; flex-wrap: wrap; align-items: center; }
.home-task-meta .home-more { font-size: 0.76em; }
.home-task .schedule-modal { margin-top: 8px; }

/* Per-task kebab: the board's column moves, without leaving Home. Always
   visible rather than hover-revealed — it has to be reachable on a phone. */
.home-tmenu { position: relative; flex: none; }
.home-kebab {
  display: inline-flex; align-items: center; justify-content: center;
  position: relative; width: 26px; height: 26px; margin: -3px -5px 0 0; padding: 0;
  background: none; border: none; border-radius: 6px;
  color: var(--text-muted); cursor: pointer;   /* --text-dim is under 3:1 on the light themes */
  transition: color 0.15s, background 0.15s;
}
.home-kebab::after { content: ''; position: absolute; inset: -9px; }   /* 44px hit area */
.home-kebab:hover { color: var(--text); background: var(--bg); }
.home-kebab .icon { width: 16px; height: 16px; }
.home-tmenu-pop {
  position: absolute; right: 0; top: calc(100% + 4px);
  min-width: 172px; padding: 4px 0; z-index: 200;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 4px 14px rgba(0,0,0,0.18);
}
.home-tmenu-pop.home-tmenu-up { top: auto; bottom: calc(100% + 4px); }
.home-tmenu-pop[hidden] { display: none; }
.home-tmenu-head {
  font-family: var(--mono); font-size: 0.62em; font-weight: 600;
  letter-spacing: 1px; text-transform: uppercase; color: var(--text-dim);
  padding: 6px 12px 4px;
}
.home-tmenu-item {
  display: block; width: 100%; text-align: left;
  font: inherit; font-size: 0.85em; font-weight: 600; color: var(--text-secondary);
  background: none; border: none; padding: 8px 12px; cursor: pointer;
}
.home-tmenu-item::before { content: "\25CF"; font-size: 0.66em; margin-right: 8px; color: transparent; }
/* --bg-input matches --bg-elevated on the light themes, so hover comes from
   --bg rather than a fill that would read as no change at all. */
.home-tmenu-item:hover { background: var(--bg); color: var(--text); }
.home-tmenu-item[disabled] { color: var(--text-dim); cursor: progress; background: none; }
.home-tmenu-item-active { color: var(--accent); }
.home-tmenu-item-active::before { color: var(--accent); }
.home-tmenu-sep { height: 1px; margin: 4px 0; background: var(--border-light); }
/* --danger fails contrast as text on the light themes — carry it as a rule. */
.home-tmenu-danger { color: var(--text); box-shadow: inset 2px 0 0 var(--danger); }
.home-tmenu-danger:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--text); }

/* ── spaces ── */
/* One full-width card per workspace, stacked. The header is the accordion
   control; open, the card carries a project rail beside one project's detail. */
.home-at-grid { display: flex; flex-direction: column; gap: 12px; }
.home-ws {
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px;
  min-width: 0; box-shadow: var(--card-shadow, none);
  transition: border-color 0.22s ease;
}
.home-ws.is-open { border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
.home-ws-head {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 14px 16px; border: none; border-radius: 7px;
  background: none; font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.home-ws.is-open .home-ws-head { border-radius: 7px 7px 0 0; border-bottom: 1px solid var(--border); }
.home-ws-head:hover { background: var(--bg-input); }
.home-ws-glyph {
  width: 34px; height: 34px; border-radius: 8px; display: grid; place-items: center; flex: none;
  font-family: var(--mono); font-size: 0.78em; font-weight: 600;
}
.home-ws-t { flex: 1; min-width: 0; }
.home-ws-t b { display: block; font-size: 0.95em; font-weight: 700; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.home-ws-t span { display: block; font-size: 0.76em; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A closed row still has to answer "how is this workspace going?", so the
   roll-up that used to sit in the card footer rides in the header instead. */
.home-ws-stat {
  font-family: var(--mono); font-size: 0.76em; color: var(--text-secondary);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Reorder grip. A sibling of the head button — buttons can't nest — so the
   row wraps both; the head keeps its full-width hit area and the grip gets a
   44px column of its own. Native HTML5 drag, so it does nothing on touch:
   coarse pointers hide it rather than show a handle that can't be dragged. */
.home-ws-row { display: flex; align-items: stretch; }
/* No left padding beside the grip: the dots then sit 22px from the card edge
   and 22px from the glyph, instead of 22 and 38. */
.home-ws-row .home-ws-head { flex: 1; min-width: 0; padding-left: 0; border-radius: 0 7px 7px 0; }
.home-ws.is-open .home-ws-row .home-ws-head { border-radius: 0 7px 0 0; }
.home-ws-grip {
  flex: none; width: 44px; display: grid; place-items: center;
  color: var(--text-dim); cursor: grab; border-radius: 7px 0 0 7px; touch-action: none;
}
.home-ws-grip:hover { color: var(--text); background: var(--bg-input); }
.home-ws-grip:active { cursor: grabbing; }
.home-ws-grip:focus-visible { outline-offset: -2px; }
.home-ws.is-open .home-ws-grip { border-radius: 7px 0 0 0; border-bottom: 1px solid var(--border); }
.home-ws.is-dragging { opacity: 0.35; }
/* Insertion line: a 2px rule centred in the 12px gap above or below the card. */
.home-ws.is-drop-before { box-shadow: 0 -7px 0 -5px var(--accent); }
.home-ws.is-drop-after  { box-shadow: 0  7px 0 -5px var(--accent); }
@media (hover: none) and (pointer: coarse) {
  .home-ws-grip { display: none; }
  .home-ws-row .home-ws-head { padding-left: 16px; border-radius: 7px; }
  .home-ws.is-open .home-ws-row .home-ws-head { border-radius: 7px 7px 0 0; }
}
.home-ws-chev { flex: none; color: var(--text-dim); transition: transform 0.25s ease; }
.home-ws.is-open .home-ws-chev { transform: rotate(180deg); }
.home-ws-head:hover .home-ws-chev { color: var(--text); }

/* Accordion. grid-template-rows 0fr→1fr animates in BOTH directions; a
   max-height transition on a flex child only animates open (see the kanban
   card drawer, which needed the double-rAF recipe to close). */
.home-ws-panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.28s cubic-bezier(0.2, 0.7, 0.3, 1); }
.home-ws.is-open .home-ws-panel { grid-template-rows: 1fr; }
.home-ws-panel-in { min-height: 0; overflow: hidden; }
@media (prefers-reduced-motion: reduce) {
  .home-ws-panel, .home-ws-chev { transition: none; }
}

.home-sp-peek { display: grid; grid-template-columns: 232px minmax(0, 1fr); align-items: stretch; }
.home-sp-none { padding: 16px; }
.home-sp-rail {
  border-right: 1px solid var(--border); background: var(--bg-input);
  padding: 12px 10px; display: flex; flex-direction: column; gap: 3px;
}
.home-sp-rail-lbl {
  font-family: var(--mono); font-size: 0.66em; letter-spacing: 1.6px; text-transform: uppercase;
  color: var(--text-dim); padding: 2px 8px 8px;
}
.home-sp-rail-item {
  display: flex; align-items: center; gap: 8px; padding: 8px 9px; min-height: 38px;
  border: none; border-left: 3px solid transparent; border-radius: 0 5px 5px 0;
  background: none; font: inherit; font-size: 0.85em; text-align: left;
  color: var(--text-secondary); cursor: pointer;
}
.home-sp-rail-item:hover { background: var(--bg-elevated); color: var(--text); }
.home-sp-rail-item[aria-current="true"] { background: var(--bg-elevated); border-left-color: var(--accent); color: var(--text); font-weight: 600; }
.home-sp-rail-nm { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.home-sp-rail-nm.is-loose { font-family: var(--mono); font-size: 0.86em; letter-spacing: 1px; text-transform: uppercase; color: var(--text-dim); }
.home-sp-rail-item[aria-current="true"] .home-sp-rail-nm.is-loose { color: var(--text-secondary); }
.home-sp-rail-n { font-family: var(--mono); font-size: 0.8em; color: var(--text-dim); font-variant-numeric: tabular-nums; }

.home-sp-body { padding: 16px 18px 18px; display: flex; flex-direction: column; gap: 16px; min-width: 0; }
/* .view-loading is a whole-page style (min-height: 55vh) — inside a card
   panel that opens the accordion to half the viewport and then snaps shut to
   the real content. Constrain it to roughly a loaded panel's height so the
   open animation lands near its final size. */
.home-sp-loading { padding: 6px 0; }
.home-sp-loading .view-loading { min-height: 140px; justify-content: flex-start; }
.home-sp-head { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
.home-sp-title { flex: 1; min-width: 0; }
.home-sp-crumb {
  font-family: var(--mono); font-size: 0.68em; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--text-dim); display: flex; align-items: center; gap: 6px;
}
.home-sp-title h3 { font-size: 1.15em; font-weight: 700; letter-spacing: -0.012em; margin: 4px 0 6px; }
.home-sp-goal { font-size: 0.85em; color: var(--text-secondary); margin: 0; max-width: 62ch; }
.home-sp-acts { display: flex; align-items: center; gap: 8px; }
.home-sp-btn {
  font: inherit; font-size: 0.8em; font-weight: 600; padding: 8px 13px; min-height: 36px;
  border-radius: 6px; border: 1px solid var(--border); background: var(--bg-input);
  color: var(--text-secondary); cursor: pointer; white-space: nowrap;
}
.home-sp-btn:hover { border-color: var(--accent); color: var(--text); }

.home-sp-meter { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.home-sp-track { flex: 0 1 300px; height: 6px; border-radius: 3px; background: var(--bg-input); border: 1px solid var(--border-light); overflow: hidden; }
.home-sp-track i { display: block; height: 100%; background: var(--accent); }
.home-sp-track.done i { background: var(--success); }
.home-sp-read { font-family: var(--mono); font-size: 0.76em; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.home-sp-tags { display: flex; gap: 6px; flex-wrap: wrap; }

.home-sp-cols { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr) minmax(0, 0.9fr); gap: 20px; }
.home-sp-col { min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.home-sp-ch {
  display: flex; align-items: baseline; gap: 8px;
  font-family: var(--mono); font-size: 0.68em; font-weight: 600;
  letter-spacing: 1.4px; text-transform: uppercase; color: var(--text-dim);
  padding-bottom: 6px; border-bottom: 1px solid var(--border);
}
.home-sp-ch .n { color: var(--text-secondary); font-variant-numeric: tabular-nums; }

/* Task pager. Sits in the section rule so the list below it never moves. The
   header aligns on baselines, which strands a 44px control at the top of the
   row on touch — where there's a pager, centre the row instead. */
.home-sp-ch:has(.home-sp-pager) { align-items: center; }
.home-sp-pager { margin-left: auto; display: inline-flex; align-items: center; gap: 2px; }
.home-sp-pg {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 26px; min-height: 26px; padding: 0;
  border: 1px solid transparent; border-radius: 5px;
  background: none; color: var(--text-secondary); cursor: pointer;
}
/* --bg, not --bg-input: on the sand themes --bg-input is the same colour as
   the card it sits on, so the hover would be invisible. */
.home-sp-pg:hover:not(:disabled) { background: var(--bg); border-color: var(--border); color: var(--text); }
.home-sp-pg:disabled { color: var(--text-dim); opacity: 0.35; cursor: default; }
.home-sp-pg-n {
  font-family: var(--mono); font-size: 1em; letter-spacing: 0.4px; text-transform: none;
  color: var(--text-secondary); font-variant-numeric: tabular-nums;
  padding: 0 5px; white-space: nowrap;
}
.home-sp-tk-note { margin: 9px 0 0; font-size: 0.78em; color: var(--text-dim); }
@media (pointer: coarse) { .home-sp-pg { min-width: 44px; min-height: 44px; } }

.home-sp-tk, .home-sp-ms, .home-sp-pp { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.home-sp-tk li {
  display: flex; align-items: center; gap: 9px; padding: 8px 2px; min-height: 40px;
  border-bottom: 1px solid var(--border-light); font-size: 0.88em;
}
.home-sp-tk li:last-child { border-bottom: none; }
/* A paged list keeps five rows' worth of height whatever the page holds, so
   stepping onto a short last page doesn't shrink the card under the pointer.
   5 rows = 5 x 41px, less the border the last row doesn't draw. */
.home-sp-tk.is-paged { min-height: 204px; }

/* Checkbox and title are real buttons — a Spaces card is somewhere you work
   from. The visual square stays 13px and stays where it was; the button around
   it is the touch target, pulled back with negative margins so the advance is
   still 13px and the list keeps its rhythm. */
.home-sp-check {
  flex: none; width: 32px; height: 32px; margin: -4px -10px -4px -9px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border: none; background: none; border-radius: 6px; cursor: pointer;
}
.home-sp-box {
  width: 13px; height: 13px; flex: none; border-radius: 3px;
  border: 1.5px solid var(--text-dim);
  display: inline-flex; align-items: center; justify-content: center;
  color: transparent;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.home-sp-check.is-top .home-sp-box { border-color: var(--accent); }
.home-sp-check:hover .home-sp-box { border-color: var(--text); }
/* Hovering a done box previews the undo: the fill lifts toward the card. */
.home-sp-check[aria-checked="true"]:hover .home-sp-box {
  background: color-mix(in srgb, var(--success) 55%, var(--bg-elevated));
  border-color: var(--success);
}
.home-sp-check[aria-checked="true"] .home-sp-box {
  background: var(--success); border-color: var(--success); color: var(--bg);
}
.home-sp-tt {
  flex: 1; min-width: 0; display: block; text-align: left;
  padding: 8px 4px; margin: -8px -4px; border-radius: 4px;
  font: inherit; color: inherit; background: none; border: none; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.home-sp-tt.is-blocked { color: var(--text-muted); }
.home-sp-tt:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
/* display:contents, not a box. As a plain span this wrapper formed a line box
   taller than the pill inside it, so the row centred the leading rather than
   the pill — that was OVERDUE and SCHEDULED sitting at different heights. It
   also means a row with no status pill contributes no flex gap. */
.home-sp-st { display: contents; }
/* Opening a task from another workspace switches the active one and fetches
   its board first — long enough that the click needs an answer. */
.home-sp-tt[aria-busy="true"] { color: var(--text-dim); cursor: progress; animation: sp-tt-busy 1s ease-in-out infinite; }
@keyframes sp-tt-busy { 50% { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) {
  .home-sp-tt[aria-busy="true"] { animation: none; opacity: 0.55; }
}
/* Done, and staying put. The row holds its place until the next load re-sorts
   it below the open work, so the strike-through is the whole signal and every
   state tag goes quiet — a finished task is not overdue, not blocking, and not
   sitting in a column. They're hidden rather than left out of the markup so
   reopening the row brings them straight back. */
.home-sp-tk li.is-done .home-sp-tt { color: var(--text-dim); text-decoration: line-through; }
.home-sp-tk li.is-done .home-pill { display: none; }

@media (prefers-reduced-motion: reduce) {
  .home-sp-box { transition: none; }
}

/* Touch: 44px targets, and the row grows to hold them. */
@media (pointer: coarse) {
  .home-sp-tk li { min-height: 48px; }
  .home-sp-tk.is-paged { min-height: 244px; }
  .home-sp-check { width: 44px; height: 44px; margin: -8px -6px -8px -15px; }
  .home-sp-tt { padding-block: 14px; margin-block: -14px; }
}

.home-sp-ms { position: relative; padding-left: 4px; }
.home-sp-ms::before { content: ""; position: absolute; left: 9px; top: 14px; bottom: 14px; width: 1px; background: var(--border); }
.home-sp-ms li { display: flex; gap: 12px; padding: 8px 0; align-items: flex-start; }
.home-sp-dia {
  width: 11px; height: 11px; flex: none; margin-top: 4px; transform: rotate(45deg);
  border: 1.5px solid var(--text-dim); background: var(--bg-elevated); border-radius: 2px;
  position: relative; z-index: 1; box-shadow: 0 0 0 4px var(--bg-elevated);
}
.home-sp-ms li.hit .home-sp-dia  { background: var(--success); border-color: var(--success); }
.home-sp-ms li.over .home-sp-dia { background: var(--danger); border-color: var(--danger); }
.home-sp-ms li.soon .home-sp-dia { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 30%, var(--bg-elevated)); }
.home-sp-ms-b { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.home-sp-ms-t { font-size: 0.88em; }
.home-sp-ms li.hit .home-sp-ms-t { color: var(--text-muted); }
.home-sp-ms-d { font-family: var(--mono); font-size: 0.72em; color: var(--text-dim); font-variant-numeric: tabular-nums; }

.home-sp-pp li { display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: 10px; align-items: start; padding: 8px 2px; min-height: 44px; }
.home-sp-pp .home-av { margin-top: 1px; }
.home-sp-pp-b { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.home-sp-pp-top { display: flex; align-items: baseline; gap: 8px; }
.home-sp-pp-nm { flex: 1; min-width: 0; font-size: 0.85em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.home-sp-ov { font-family: var(--mono); font-size: 0.7em; font-weight: 600; color: var(--danger); white-space: nowrap; }
.home-sp-bar { display: flex; height: 4px; border-radius: 2px; overflow: hidden; background: var(--bg-input); }
.home-sp-bar i { display: block; height: 100%; }
.home-sp-bar .o { background: var(--accent); }
.home-sp-bar .d { background: var(--success); }
.home-sp-ct { font-family: var(--mono); font-size: 0.72em; color: var(--text-muted); font-variant-numeric: tabular-nums; }

.home-push { margin-left: auto; }
.home-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.home-dot.fresh { background: var(--success); }
.home-dot.warm  { background: var(--warning); }
.home-dot.stale { background: var(--text-dim); }
.home-avs { display: flex; }
.home-av {
  width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center; flex: none;
  font-family: var(--mono); font-size: 0.58em; font-weight: 600;
  border: 1.5px solid var(--bg-elevated); color: var(--text);
}
.home-avs .home-av + .home-av { margin-left: -6px; }
.home-av-1 { background: color-mix(in srgb, var(--accent) 22%, var(--bg-elevated)); }
.home-av-2 { background: color-mix(in srgb, var(--success) 22%, var(--bg-elevated)); }
.home-av-3 { background: color-mix(in srgb, var(--warning) 22%, var(--bg-elevated)); }
.home-av-4 { background: color-mix(in srgb, var(--google-blue) 22%, var(--bg-elevated)); }
.home-av-5 { background: color-mix(in srgb, var(--energy-high) 22%, var(--bg-elevated)); }
.home-av-6 { background: color-mix(in srgb, var(--text-muted) 22%, var(--bg-elevated)); }
.home-av-more { background: var(--bg-input); color: var(--text-muted); }
/* ── responsive ── */
@media (max-width: 900px) {
  .home-grid { grid-template-columns: 1fr; }
  .home-topbar-actions { width: 100%; }
}
@media (max-width: 700px) {
  .home-h1 { font-size: 20px; }
  .home-modes-menu { min-width: 210px; }
}
@media (max-width: 860px) {
  .home-sp-cols { grid-template-columns: minmax(0, 1fr); gap: 18px; }
}
@media (max-width: 760px) {
  .home-ws-stat { display: none; }
  .home-sp-peek { grid-template-columns: minmax(0, 1fr); }
  .home-sp-body { padding: 14px 14px 16px; }
  /* The rail is the only way to change project, so at this width it turns
     into a scrolling strip above the detail rather than disappearing. */
  .home-sp-rail {
    flex-direction: row; gap: 4px; overflow-x: auto; padding: 8px 10px;
    border-right: none; border-bottom: 1px solid var(--border);
  }
  .home-sp-rail-lbl { display: none; }
  .home-sp-rail-item {
    white-space: nowrap; border-left: none; border-bottom: 3px solid transparent;
    border-radius: 5px 5px 0 0;
  }
  .home-sp-rail-item[aria-current="true"] { border-bottom-color: var(--accent); }
}
@media (pointer: coarse) {
  .home-more, .home-modes-trigger, .home-menu-pick, .home-menu-setdef,
  .home-sp-rail-item, .home-sp-btn, .home-ws-head, .home-tmenu-item { min-height: 44px; }
  .home-task-meta .home-more { min-height: 0; padding: 10px 0; }
  .home-kebab { width: 44px; height: 44px; margin: -10px -12px 0 0; }
}

/* ─── COMMAND PALETTE ──────────────────────────────────────────
   Header trigger + ⌘K overlay. Companion to .tabs-capture: that bar
   creates, this finds. The trigger is a button rather than a second text
   field on purpose — two adjacent inputs separated only by placeholder
   text is an error trap, and the header has no room for one at 700px. */
.pal-trigger {
  display: inline-flex; align-items: center; gap: 8px;
  margin: 0 0 0 28px;             /* sits just right of the logo inside .header-lead */
  padding: 6px 10px; min-height: 34px;
  background: var(--bg-input); border: 1px solid var(--border);
  border-radius: 6px;             /* matches .home-capture-input — the two read as a pair */
  color: var(--text-muted); font: inherit; font-size: 0.8em; letter-spacing: 1px;
  cursor: pointer; transition: border-color 0.2s, color 0.2s;
}
.pal-trigger:hover { border-color: var(--text-dim); color: var(--text-secondary); }
.pal-trigger-label { min-width: 130px; text-align: left; }
.pal-kbd, .tabs-capture-kbd {
  font: inherit; font-size: 0.9em; letter-spacing: 0;
  color: var(--text-muted); border: 1px solid var(--border); padding: 1px 5px;
  border-radius: 3px;
}
/* 0.9em resolves against the parent, and .pal-trigger already sets 0.8em. The
   capture field's parent is base size, so the same rule renders a third larger
   there and drives the field 3px taller than the trigger. Must sit after the
   shared rule above — equal specificity, so source order decides. */
.tabs-capture-kbd { font-size: 0.75em; }

.pal-scrim {
  position: fixed; inset: 0; z-index: 400;
  background: rgba(0, 0, 0, 0.5);
  display: flex; justify-content: center; align-items: flex-start;
  padding: 10vh 16px 16px;
}
.pal {
  width: 100%; max-width: 620px;
  background: var(--bg-elevated); border: 1px solid var(--border);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
  display: flex; flex-direction: column; max-height: 70vh;
  animation: pal-in 0.14s ease-out;
}
@keyframes pal-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .pal { animation: none; } }

.pal-field {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; border-bottom: 1px solid var(--border);
}
.pal-field .icon { color: var(--text-dim); flex: 0 0 auto; }
.pal-input {
  flex: 1 1 auto; min-width: 0;
  background: none; border: none; outline: none;
  color: var(--text); font: inherit; font-size: 1em;
}
.pal-input::placeholder { color: var(--text-dim); }
.pal-kbd-esc { flex: 0 0 auto; }
/* The palette goes full-screen at ≤700px, which removes both desktop exits at
   once — there is no scrim left to tap and no keyboard to press esc on. This
   button is the phone's only way out, so it appears exactly where they go. */
.pal-close {
  display: none; flex: 0 0 auto;
  min-width: 44px; min-height: 44px;
  background: none; border: none; color: var(--text-muted);
  font: inherit; font-size: 1.1em; line-height: 1; cursor: pointer;
}
.pal-close:hover { color: var(--text); }

.pal-list { overflow-y: auto; padding: 6px 0; }
.pal-group + .pal-group { border-top: 1px solid var(--border-light); }
.pal-group-head {
  padding: 8px 14px 4px;
  font-size: 0.7em; letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--text-muted);
}
/* --bg-input equals --bg-elevated on the light themes, so a background swap
   alone would leave the active row invisible there. The inset accent rule is
   what actually carries the state. */
.pal-row {
  padding: 8px 14px 8px 11px; cursor: pointer;
  border-left: 3px solid transparent;
  display: flex; align-items: baseline; gap: 10px;
}
.pal-row-active { background: var(--bg); border-left-color: var(--accent); }
.pal-row-title { flex: 1 1 auto; min-width: 0; color: var(--text); font-size: 0.9em;
                 overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pal-row-sub { flex: 0 0 auto; max-width: 40%; color: var(--text-muted); font-size: 0.78em;
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The hit is marked with weight + an accent rule, not accent-coloured text.
   --accent as body text is only 3.36:1 on the dark theme, and these are the
   very characters the eye goes to — same lesson as --warning/--danger, which
   read fine as borders and fail as text. */
.pal-mark {
  background: none; color: var(--text); font-weight: 600;
  text-decoration: underline; text-decoration-color: var(--accent);
  text-decoration-thickness: 2px; text-underline-offset: 2px;
}

.pal-none, .pal-empty { padding: 18px 14px; color: var(--text-muted); font-size: 0.9em; }
.pal-empty { padding: 8px 0; }
/* Result count for screen readers; the list itself carries it visually. */
.pal-live {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* Phone: .header wraps at this width, so the trigger drops its label and kbd
   hint and rides as a bare icon rather than forcing its own row. It stays out
   of the ⋯ overflow menu — search is reached too often to bury. */
@media (max-width: 700px) {
  .pal-trigger { margin: 0 0 0 auto; min-width: 44px; min-height: 44px; justify-content: center; }
  .pal-trigger-label, .pal-kbd, .tabs-capture-kbd { display: none; }
  .pal-scrim { padding: 0; }
  .pal { max-width: none; max-height: 100%; height: 100%; border: none; }
  .pal-list { flex: 1 1 auto; }
  .pal-row { min-height: 44px; align-items: center; }
  .pal-field { padding: 14px; }
  .pal-close { display: inline-flex; align-items: center; justify-content: center; }
}

/* ─── PROJECT FILES ────────────────────────────────────────── */
/* Upload affordances on the Notes & files card, per-note inline files,
   and the Files section. Rows reuse .kb-attachment; the delete control
   sits beside it so the row stays one flex line. */
.pj-notes.pj-drop-over { outline: 2px dashed var(--accent); outline-offset: -6px; }
.pj-composer-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pj-composer-row .kb-notes-post { margin-left: auto; align-self: auto; }
.pj-attach-btn {
  display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 6px 12px;
  border: 1px solid var(--border); border-radius: 4px; background: var(--bg-input);
  color: var(--text-secondary); font: inherit; font-size: 0.8em; cursor: pointer;
}
.pj-attach-btn .icon { width: 14px; height: 14px; }
.pj-attach-btn:hover { border-color: var(--accent); color: var(--accent); }
.pj-attach-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.pj-attach-hint { font-size: 0.76em; color: var(--text-dim); }
.pj-pending { display: flex; flex-wrap: wrap; gap: 6px; }
.pj-pending:empty { display: none; }
.pj-pending-file {
  display: inline-flex; align-items: center; gap: 6px; padding: 2px 2px 2px 10px;
  border: 1px solid var(--border); border-radius: 4px; background: var(--bg-elevated); font-size: 0.8em;
}
.pj-pending-name { max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pj-pending-size { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.pj-pending-file button { min-width: 28px; min-height: 28px; border: 0; background: none; color: var(--text-dim); cursor: pointer; font-size: 1.1em; line-height: 1; }
.pj-pending-file button:hover { color: var(--danger); }
.pj-files { display: flex; flex-direction: column; gap: 6px; }
.pj-files:empty { display: none; }
.kb-attachment-row { display: flex; align-items: stretch; gap: 4px; }
.kb-attachment-row .kb-attachment { flex: 1; min-width: 0; }
.kb-attachment-x {
  flex: 0 0 auto; min-width: 40px; border: 1px solid var(--border-light); background: var(--bg-elevated);
  color: var(--text-dim); cursor: pointer; font-size: 1.1em; line-height: 1;
}
.kb-attachment-x:hover { color: var(--danger); border-color: var(--danger); }
.kb-attachment-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.kb-note-files { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
.kb-attachment-row-inline .kb-attachment { min-height: 34px; padding: 4px 8px; font-size: 0.85em; }
.pj-files-panel { display: flex; flex-direction: column; gap: 14px; }
.pj-files-panel.pj-drop-over { outline: 2px dashed var(--accent); outline-offset: 6px; border-radius: 6px; }
.pj-files-drop {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 14px 17px;
  border: 1px dashed var(--border); border-radius: 6px; background: var(--bg-elevated);
}
.pj-files-drop .pj-pending { flex-basis: 100%; }
.pj-files-drop .kb-notes-post { margin-left: auto; align-self: auto; }
.pj-files-hint { font-size: 0.78em; color: var(--text-dim); }

/* ─── INTRO LOOP STRIP ─────────────────────────────────────────────────────
   The "How gtd_done works" card on Home. Shown once per account and dismissed
   to user_profiles.intro_loop_dismissed.

   Deliberately quiet: a bordered band in the page's own palette rather than a
   coloured callout. It is the first thing a new user reads, and it should look
   like part of the app, not an advert bolted onto it. The accent appears only
   on the step numbers and on hover, so the four steps scan as a sequence
   without the band shouting.

   overflow:hidden matters — introLoopDismiss() animates height to 0. */
.intro-loop {
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  margin-bottom: 20px;
  overflow: hidden;
}
.intro-loop-head {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
}
.intro-loop-h {
  margin: 0; flex: 1;
  font-size: 0.72em; font-weight: 600;
  letter-spacing: 2px; text-transform: uppercase;
  /* --text-muted measures 4.39:1 on the Editorial theme at this size — under
     AA. --text-secondary clears it on all four themes. */
  color: var(--text-secondary);
}
.intro-loop-x {
  background: transparent; border: 1px solid var(--border);
  color: var(--text-secondary);
  font: inherit; font-size: 0.75em; letter-spacing: 1px;
  padding: 6px 12px; min-height: 32px; cursor: pointer;
}
.intro-loop-x:hover { border-color: var(--text-muted); color: var(--text); }
.intro-loop-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.intro-loop-steps {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, 1fr);
}
.intro-loop-step + .intro-loop-step { border-left: 1px solid var(--border); }
.intro-loop-btn {
  display: block; width: 100%; height: 100%;
  text-align: left; background: transparent; border: 0;
  font: inherit; color: inherit; cursor: pointer;
  padding: 14px; min-height: 44px;
  transition: background 120ms ease;
}
.intro-loop-btn:hover { background: var(--bg); }
.intro-loop-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.intro-loop-n {
  display: block;
  /* --accent on --bg-elevated is 3.36:1 (dark) / 4.47:1 (light) — under AA for
     small text. As a display numeral it clears the large-text threshold
     (>=18.66px at >=700), where 3:1 applies and both pass. Explicit px, not em:
     that threshold is absolute, and an em would inherit whatever scale wraps it
     and could silently drop the requirement back to 4.5:1. */
  font-size: 20px; font-weight: 700; line-height: 1.1;
  letter-spacing: 1px;
  color: var(--accent); margin-bottom: 8px;
  font-variant-numeric: tabular-nums;
}
.intro-loop-t {
  display: block; font-size: 0.9em; font-weight: 600;
  color: var(--text); margin-bottom: 4px;
}
.intro-loop-b {
  display: block; font-size: 0.78em; line-height: 1.55;
  color: var(--text-secondary);
}
/* Stack below the point where four columns stop being readable. The left
   border becomes a top border so the sequence still reads as connected. */
@media (max-width: 760px) {
  .intro-loop-steps { grid-template-columns: 1fr; }
  .intro-loop-step + .intro-loop-step {
    border-left: 0; border-top: 1px solid var(--border);
  }
}
@media (prefers-reduced-motion: reduce) {
  .intro-loop-btn { transition: none; }
}

/* ─── FIRST-RUN EMPTY STATES ───────────────────────────────────────────────
   Shown in place of the usual "Inbox zero. ✓" copy when a workspace has never
   held anything (_wsIsVirgin). Wider and left-aligned rather than centred: this
   is a sentence to read, not a status to glance at. */
.kb-empty-first {
  text-align: left;
  color: var(--text-secondary);
  line-height: 1.6;
  padding: 18px 12px;
}
.kb-empty-first .tl-clear-filters { margin-left: 0; margin-top: 10px; }

/* Sample-workspace marker in the workspace switcher. */
.ws-menu-demo {
  margin-left: 8px; padding: 1px 6px;
  border: 1px solid var(--border); border-radius: 2px;
  font-size: 0.72em; letter-spacing: 1px; text-transform: uppercase;
  color: var(--text-secondary); vertical-align: middle;
}

/* The sample-workspace banner reuses .delegate-banner's shell (including the
   negative margin that makes it full-bleed) but not its colour: acting-as is a
   warning, this is a note. Neutral tokens keep it from competing. */
.delegate-banner-demo {
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
  color: var(--text-secondary);
}
.delegate-banner-demo .delegate-banner-label { color: var(--text-secondary); }
.delegate-banner-demo .delegate-banner-name {
  color: var(--text-secondary); font-weight: 400; letter-spacing: 0;
}
.delegate-banner-demo .delegate-banner-exit {
  border-color: var(--border); color: var(--text-secondary);
}
.delegate-banner-demo .delegate-banner-exit:hover {
  background: transparent; border-color: var(--text-muted); color: var(--text);
}

/* Pointer to the sample workspace, in the intro strip's foot. Quiet: it is an
   offer, not the next step — the four steps above are the next step. */
.intro-loop-foot {
  border-top: 1px solid var(--border);
  padding: 10px 14px;
  font-size: 0.78em; color: var(--text-secondary);
}
.intro-loop-link {
  background: none; border: 0; padding: 0 0 0 4px;
  font: inherit; cursor: pointer;
  /* --accent measures 3.36:1 on dark at this size — it is the step numerals'
     colour precisely because they are large enough to carry it, and this text
     isn't. The underline is the affordance, so nothing is lost by taking the
     tint off, and colour stops being the sole carrier of meaning. */
  color: var(--text);
  text-decoration: underline; text-underline-offset: 3px;
}
.intro-loop-link:hover { color: var(--text-secondary); }
.intro-loop-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
