/* ───── Buttons ───────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 8px 14px;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
  white-space: nowrap;
}
.btn:hover    { background: var(--surface-2); }
.btn:disabled { opacity: 0.5; cursor: default; }

.btn--primary {
  background: var(--accent);
  color: var(--ink-on-strong);
  border-color: var(--accent);
}
.btn--primary:hover {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
  color: var(--ink-on-strong);
}

/* "Start task" — the ONE universal, tool-wide action, set apart from the flat
   --primary green (report/save/new) by a PILL shape + play glyph, styled to
   match the nav widget's flat tinted treatment (the navbar itself is untouched):
   a muted accent-tint at rest that fills to solid accent on hover. Flat, theme-
   token only — `color-mix(--accent, transparent)` composites on every theme,
   exactly like `.nav-top__time`. The `.btn` base supplies the colour transition. */
.btn--start {
  padding: 8px 18px;
  border-radius: 999px;
  font-weight: var(--fw-semi);
  color: var(--accent);
  /* Clean 1px accent outline, no fill at rest — crisp and deliberate. Fills to a
     light accent tint on hover (no solid, no gloss). Label + play glyph stay
     accent via currentColor. */
  background: transparent;
  border: 1px solid var(--accent);
}
.btn--start:hover {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-color: var(--accent-strong);
}
.btn--start:active {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  border-color: var(--accent-strong);
}
.btn--start .icon { width: 13px; height: 13px; }

.btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--ink-2);
}
.btn--ghost:hover { background: var(--hover); border-color: transparent; color: var(--ink); }

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

.btn--sm { padding: 5px 10px; font-size: var(--fs-xs); }

/* Plain text-link affordance — no button chrome. Use for low-emphasis actions
   ("delete" on a comment, "undo" inline) where a full button would over-promote
   the action. Works on `<button>` (preferred for actions that mutate state) or
   `<a>` (for navigation). Sized via the size modifier; tinted via --danger etc. */
.link {
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  cursor: pointer;
  color: var(--ink-2);
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-underline-offset: 2px;
}
.link:hover { color: var(--ink); }
.link--xs { font-size: var(--fs-xs); }
.link--danger { color: var(--danger); opacity: 0.75; }
.link--danger:hover { color: var(--danger); opacity: 1; }

/* ───── Notes archive section headings (/notes/archive) ───────────── */
.archive-heading {
  font-size: var(--fs-md); font-weight: var(--fw-semi);
  color: var(--ink);
  margin: var(--sp-5) 0 var(--sp-2);
  display: flex; align-items: baseline; gap: var(--sp-2);
}
.archive-heading:first-of-type { margin-top: var(--sp-3); }
.archive-count { font-size: var(--fs-xs); font-weight: var(--fw-reg); color: var(--ink-3); }

/* ───── Form inputs ───────────────────────────────────────────────── */

.input,
.select,
.textarea {
  display: block;
  width: 100%;
  padding: 8px 12px;
  font-size: var(--fs-sm);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
  color-scheme: var(--color-scheme);
}

/* Native date picker indicator — invert proportional to theme so the
   default-dark glyph remains readable on dark and steps out of the way on light */
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  filter: invert(var(--picker-invert));
  cursor: pointer;
  opacity: 0.7;
}
input[type="date"]:hover::-webkit-calendar-picker-indicator { opacity: 1; }

/* Search input clear-button visibility on dark */
input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  height: 14px;
  width: 14px;
  background: var(--ink-3);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M4 4l8 8M12 4l-8 8' stroke='currentColor' stroke-width='1.6' stroke-linecap='round'/></svg>") center/contain no-repeat;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M4 4l8 8M12 4l-8 8' stroke='currentColor' stroke-width='1.6' stroke-linecap='round'/></svg>") center/contain no-repeat;
  cursor: pointer;
}
input[type="search"]:hover::-webkit-search-cancel-button { background: var(--ink); }
.input::placeholder,
.textarea::placeholder { color: var(--ink-3); }

.input:hover,
.select:hover,
.textarea:hover { border-color: var(--ink-3); }

.input:focus,
.select:focus,
.textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus);
  outline: none;
}

.select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none' stroke='%237c8f99' stroke-width='1.5'><path d='M3 5l3 3 3-3'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 30px;
}

.textarea {
  resize: vertical;
  min-height: 80px;
  line-height: var(--lh);
}

.label {
  display: block;
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  color: var(--ink-2);
  margin-bottom: 6px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.field { display: flex; flex-direction: column; }
.field + .field { margin-top: var(--sp-3); }
/* Inside .form-grid the parent's `gap` already handles spacing in both axes,
   so the adjacent-sibling top-margin would offset every grid cell after the
   first downward and break horizontal alignment. Same for .dash-widget,
   which is a flex-column with its own `gap: var(--sp-3)`. */
.form-grid > .field + .field,
.dash-widget > .field + .field { margin-top: 0; }


/* ───── Filter bar ────────────────────────────────────────────────── */

.filterbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
  padding: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
}
.filterbar__group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  flex: 1;
  min-width: 0;
}
.filterbar .input,
.filterbar .select {
  width: auto;
  min-width: 140px;
}
.filterbar .input--search { min-width: 200px; flex: 1; max-width: 320px; }
.filterbar__reset {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  padding: 4px 8px;
  border-radius: var(--r-sm);
}
.filterbar__reset:hover { color: var(--ink); background: var(--hover); }

/* ───── Project rows (dashboard) ──────────────────────────────────── */

.projects {
  display: flex;
  flex-direction: column;
  gap: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-none);
}

.row {
  position: relative;
  display: block;
  border-bottom: 1px solid var(--border);
  /* Anchor target — leave clearance for the sticky nav-top when scrolled to
     via /projects#<slug>. Applies to both browser hash-jumps and JS
     scrollIntoView. */
  scroll-margin-top: calc(var(--nav-h) + var(--sp-3));
}

/* `.row--linked`: card-link variant used on /clients/{id} and the /projects
   board where the row navigates to /projects/{id} instead of expanding. The
   visually-hidden anchor (.row__link) covers the row via position: absolute,
   while children sit on top via z-index so nested controls (Edit, the client
   link, milestone pips) still capture their own clicks. The base grid drops
   the client column (on /clients/{id} the page IS the client). */
.row--linked .row__header {
  cursor: pointer;
  grid-template-columns:
    minmax(140px, 240px)  /* name */
    140px                 /* dev */
    90px                  /* status pill */
    minmax(280px, 1fr)    /* milestone bar */
    100px                 /* last note / target date */
    48px;                 /* edit */
}
@media (max-width: 1080px) {
  .row--linked .row__header {
    grid-template-columns: minmax(140px, 240px) 140px 90px minmax(220px, 1fr) 100px 48px;
  }
}
/* `.row--board`: the /projects board variant of .row--linked — same covering
   link, but it keeps the client column (you're not already on a client page). */
.row--board.row--linked .row__header {
  grid-template-columns:
    minmax(140px, 240px)  /* name */
    minmax(120px, 200px)  /* client */
    140px                 /* dev */
    90px                  /* status pill */
    minmax(280px, 1fr)    /* milestone bar */
    100px                 /* last note */
    48px;                 /* edit */
}
@media (max-width: 1080px) {
  .row--board.row--linked .row__header {
    grid-template-columns: minmax(140px, 240px) 140px 90px minmax(220px, 1fr) 100px 48px;
  }
  .row--board .row__client { display: none; }
}
/* `.row--note`: the assigned-notes list on /projects/{id}. Same covering-link
   chrome as .row--linked, but these rows have NO milestone bar (the bar slot is
   dropped) and their date column carries the smart-`due` format whose
   "(N days overdue)" suffix overflows a fixed 100px cell — colliding with the
   Open link. Give the name column the freed slack and let the due-date column
   size to its content so the suffix always clears the Open link. */
.row--note.row--linked .row__header {
  grid-template-columns:
    minmax(160px, 1fr)          /* name — absorbs the freed bar space */
    140px                       /* dev */
    90px                        /* status pill */
    minmax(110px, max-content)  /* due date — fits "Jun 10 (1 day overdue)" */
    48px;                       /* open */
}
@media (max-width: 1080px) {
  .row--note.row--linked .row__header {
    grid-template-columns: minmax(140px, 1fr) 140px 90px minmax(110px, max-content) 48px;
  }
}
.row__link {
  position: absolute;
  inset: 0;
  z-index: 0;
  text-indent: -9999px;
  overflow: hidden;
  white-space: nowrap;
}
.row__link:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}
.row--linked .row__header > *:not(.row__link) {
  position: relative;
  z-index: 1;
  pointer-events: none;
}
/* Re-enable pointer events on actually-interactive children (Edit link,
   milestone bar nodes, etc.) so they receive their own clicks. */
.row--linked .row__header a,
.row--linked .row__header button,
.row--linked .row__header .mbar__node,
.row--linked .row__header .presence-host {
  pointer-events: auto;
}
.row:last-child { border-bottom: none; }

.row::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--row-accent, var(--border-strong));
  z-index: 1;
}

.row__header {
  display: grid;
  /* Name + client get capped max widths (truncate beyond) so they don't eat
     space the milestone bar should have. The bar is the only `1fr` column,
     so it absorbs all remaining width — and stays the same size row-to-row
     because every other column is fixed. */
  grid-template-columns:
    minmax(140px, 240px)  /* name — truncates past 240px */
    minmax(120px, 200px)  /* client — truncates past 200px */
    140px                 /* dev (avatar 22 + gap 8 + name truncates) */
    90px                  /* status pill */
    minmax(280px, 1fr)    /* milestone bar — takes all remaining */
    100px                 /* last note (fits "Apr 24, 2026") */
    32px                  /* timer-start icon */
    48px;                 /* edit link */
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4) var(--sp-3) var(--sp-5);
  cursor: pointer;
  transition: background var(--t-fast);
}
.row__header:hover { background: var(--hover); }

/* Tablet: drop client column; bar still absorbs remaining width */
@media (max-width: 1080px) {
  .row__header {
    grid-template-columns: minmax(140px, 240px) 140px 90px minmax(220px, 1fr) 100px 48px;
  }
  .row__client { display: none; }
}

/* Mobile: stack into rows. Row 1 = name + edit. Row 2 = bar. Row 3 = dev +
   status pill (side-by-side). Row 4 = last-note (full width). Applies to
   plain rows and the .row--board / .row--linked variants alike (their
   wider-width grids share the same cells). Earlier revision assigned all
   three meta children to a single `meta` area — they overlapped. */
@media (max-width: 720px) {
  .row__header,
  .row--linked .row__header,
  .row--board.row--linked .row__header {
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "name edit"
      "bar  bar"
      "dev  status"
      "note note";
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
  }
  .row__name   { grid-area: name; }
  .row__client { display: none; }
  .row__dev       { grid-area: dev;    font-size: var(--fs-xs); min-width: 0; }
  .row__last-note { grid-area: note;   font-size: var(--fs-xs); }
  .row > .row__header > div:has(.status) {
    grid-area: status;
    justify-self: end;
    font-size: var(--fs-xs);
  }
  .row__header > div:has(.mbar) { grid-area: bar; min-width: 0; }
  .row__edit { grid-area: edit; }
}

/* Expanded panel ─────────────────────────────────────────────────── */

.row__panel {
  display: none;
  padding: var(--sp-5) var(--sp-5) var(--sp-5) var(--sp-5);
  background: var(--bg);
  border-top: 1px solid var(--border);
  position: relative;
}
.row.is-expanded .row__panel { display: block; }

/* Full project title at the top of the expanded panel — solves the truncated
 * project name in the collapsed row (Batch D / session 20). */
.panel__grid {
  display: grid;
  grid-template-columns: minmax(0, 2.2fr) minmax(0, 1fr);
  gap: var(--sp-6);
  align-items: start;
}
@media (max-width: 880px) {
  .panel__grid { grid-template-columns: 1fr; gap: var(--sp-4); }
}

.panel__section + .panel__section,
.panel__section + .panel__grid,
.panel__grid + .panel__section { margin-top: var(--sp-5); }

/* Em-dash / "no value" placeholder. Used wherever a row cell has no data
   (Unassigned, no client, no due date). The `--strong` variant is for the
   most-faded look (e.g., a missing date in a row that's otherwise active). */
.placeholder { color: var(--ink-3); }
.placeholder--strong { color: var(--ink-disabled); }

/* Truncating project-name cell on the related-Notes panel (clients/{id} +
   projects/{id}). Same chrome as a standard row cell — caps long names so
   the row's grid columns don't shift. */
.row__project-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

/* Smart-due severity on row__last-note (the [data-due-host] carries the is-*
   class set by app.js). One strong scale, matching the board card + list:
   overdue=danger, today/soon=warning, future=neutral. Used by the Notes lists
   on /clients/{id} and /projects/{id}. */
.row__last-note.is-overdue { color: var(--danger); font-weight: var(--fw-medium); }
.row__last-note.is-soon,
.row__last-note.is-today { color: var(--warning); font-weight: var(--fw-medium); }

.panel__heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--sp-3);
}
.panel__heading h3 {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  margin: 0;
}
/* Notes feed ─────────────────────────────────────────────────────── */

.feed {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.feed__empty {
  font-size: var(--fs-sm);
  color: var(--ink-3);
  padding: var(--sp-4) 0;
  font-style: italic;
}

.note {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: var(--sp-3) var(--sp-4);
  position: relative;
}
.note.is-sticky {
  border-color: var(--border);
  border-top: 3px solid var(--warning);
  border-radius: var(--r-none);
}
.note__head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.note__avatar {
  width: 22px; height: 22px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
  color: var(--ink);
  font-size: 11px;
  font-weight: var(--fw-medium);
  border: 1.5px solid var(--author-color, var(--border-strong));
  flex-shrink: 0;
}
/* <img> variant: same circle clip + colored ring, cover-cropped, no grey fill. */
.note__avatar[src], img.note__avatar { object-fit: cover; background: transparent; }
.note__author {
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
}
.note__sticky-mark {
  font-size: 10px;
  color: var(--warning);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: var(--fw-semi);
}
.note__time {
  margin-left: auto;
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.note__actions {
  display: flex;
  gap: 2px;
  opacity: 0;
  transition: opacity var(--t-fast);
}
.note:hover .note__actions { opacity: 1; }
.note__action {
  padding: 2px 6px;
  font-size: var(--fs-xs);
  color: var(--ink-3);
  border-radius: var(--r-sm);
}
.note__action:hover { background: var(--hover); color: var(--ink); }
.note__action--danger:hover { background: var(--danger-soft); color: var(--danger); }
.note__body {
  font-size: var(--fs-sm);
  color: var(--ink);
  line-height: var(--lh);
  word-wrap: break-word;
}
/* Anchor target — /projects#note-<id> deep links from notifications. */
.note { scroll-margin-top: calc(var(--nav-h) + var(--sp-3)); }
.note__body a.mention,
.note__body .mention {
  color: var(--accent);
  font-weight: var(--fw-medium);
}
/* Team-wide tokens (@everyone / @announcement) render as styled, non-link
 * pills — they target the whole team, not one user. */
.note__body .mention--everyone,
.note__body .mention--announcement {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-radius: var(--r);
  padding: 0 4px;
  cursor: default;
}
/* ── Rendered markdown (.md) ──────────────────────────────────────────────
 * Shared typography for md_render() / md_render_inline() output across time
 * details, project comments, and note-board comments/replies. Theme tokens only
 * (no new colors — locked palette). Tight vertical rhythm so a short comment
 * reads as one block, not a document. `.md--inline` is for one-line previews. */
.md > :first-child { margin-top: 0; }
.md > :last-child { margin-bottom: 0; }
.md p { margin: 0 0 var(--sp-2); }
.md ul, .md ol { margin: var(--sp-2) 0; padding-left: var(--sp-5); }
/* reset.css strips list markers globally — restore them inside rendered markdown. */
.md ul { list-style: disc; }
.md ol { list-style: decimal; }
.md li { margin: 2px 0; }
.md li > ul, .md li > ol { margin: 2px 0; }
.md h1, .md h2, .md h3, .md h4, .md h5, .md h6 {
  margin: var(--sp-3) 0 var(--sp-2);
  font-weight: var(--fw-semi);
  line-height: 1.25;
}
.md h1 { font-size: var(--fs-lg); }
.md h2 { font-size: var(--fs-md); }
.md h3, .md h4, .md h5, .md h6 { font-size: var(--fs-sm); }
.md a { color: var(--accent); }
.md strong { font-weight: var(--fw-semi); }
.md em { font-style: italic; }
.md code, .md kbd {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 0.92em;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 0.05em 0.35em;
}
.md pre {
  margin: var(--sp-2) 0;
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  overflow-x: auto;
}
.md pre code { background: none; border: 0; padding: 0; }
.md blockquote {
  margin: var(--sp-2) 0;
  padding: 2px var(--sp-3);
  border-left: 3px solid var(--border);
  color: var(--ink-2);
}
.md hr { border: 0; border-top: 1px solid var(--border); margin: var(--sp-3) 0; }
.md img { max-width: 100%; height: auto; border-radius: var(--r); }
.md u { text-decoration: underline; }
/* @mention links/pills inside any .md surface (mirror the .note__body rules). */
.md a.mention, .md .mention { color: var(--accent); font-weight: var(--fw-medium); }
.md .mention--everyone, .md .mention--announcement {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-radius: var(--r);
  padding: 0 4px;
  cursor: default;
}
/* Inline contexts (card preview, time ledger sub-line): no block spacing, no
 * lists blowing out the row — keep it to a single visual line. */
.md--inline, .md--inline p { display: inline; margin: 0; }

.note__attachments {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}
.note__attachment {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  font-size: var(--fs-xs);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--ink-2);
  text-decoration: none;
}
.note__attachment:hover {
  border-color: var(--accent);
  color: var(--ink);
}
.note__attachment-size { color: var(--ink-3); font-size: 10px; }

.note__attachment--image {
  padding: 0;
  overflow: hidden;
  width: 120px;
  height: 90px;
  position: relative;
  flex: none;
}
.note__attachment--image img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--surface-2);
}
.note__attachment--image .note__attachment-overlay {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  background: linear-gradient(to top, rgba(0,0,0,0.7), transparent);
  color: #fff;
  padding: 16px 8px 4px;
  font-size: 10px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  opacity: 0;
  transition: opacity var(--t-fast);
}
.note__attachment--image:hover .note__attachment-overlay { opacity: 1; }
.note__attachment--image .note__attachment-overlay span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Composer ───────────────────────────────────────────────────────── */

.composer {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  padding: var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  position: relative;
  /* Match the feed's inter-note gap so feed → composer doesn't run end-to-end. */
  margin-top: var(--sp-3);
}
.composer:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus);
}
.composer__textarea {
  background: transparent;
  border: none;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--ink);
  resize: vertical;
  min-height: 48px;
  width: 100%;
  line-height: var(--lh);
}
.composer__textarea:focus { outline: none; }
.composer__textarea::placeholder { color: var(--ink-3); }
.composer__bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.composer__sticky {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  color: var(--ink-2);
  cursor: pointer;
}
.composer__sticky input { accent-color: var(--accent); }
.composer__attach {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  color: var(--ink-3);
  padding: 4px 8px;
  border-radius: var(--r-sm);
  cursor: pointer;
}
.composer__attach:hover { color: var(--ink); background: var(--hover); }
.composer__attach svg { width: 14px; height: 14px; }
.composer__file-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  font-size: var(--fs-xs);
  color: var(--ink-2);
}
.composer__file {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 6px;
  background: var(--surface-2);
  border-radius: var(--r-sm);
}
.composer__submit { margin-left: auto; }

/* Mention popup ──────────────────────────────────────────────────── */

.mention-popup {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 50;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  min-width: 220px;
  max-width: 280px;
  max-height: 220px;
  overflow-y: auto;
  padding: 4px;
  display: none;
}
.mention-popup.is-open { display: block; }
.mention-popup__item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 6px var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--ink);
  border-radius: var(--r-sm);
  cursor: pointer;
}
.mention-popup__item.is-selected,
.mention-popup__item:hover { background: var(--surface-2); }
.mention-popup__dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--user-color, var(--ink-3));
  flex-shrink: 0;
}

/* Contacts list ──────────────────────────────────────────────────── */

.contacts {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.contact {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  position: relative;
}
.contact__main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.contact__name {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.contact__role {
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.contact__meta {
  font-size: var(--fs-xs);
  color: var(--ink-2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: 2px;
}
.contact__meta a { color: var(--ink-2); }
.contact__meta a:hover { color: var(--accent); }
.contact__meta--faint { color: var(--ink-3); }

/* Inline form (delete confirmation, single button). Replaces ad-hoc
   `style="display:inline"` so the parent's flex/grid rules don't shrink it. */
.inline-form { display: inline; }
.contact__actions {
  display: flex;
  gap: 2px;
  opacity: 0;
  transition: opacity var(--t-fast);
}
.contact:hover .contact__actions { opacity: 1; }
.contact__action {
  padding: 2px 6px;
  font-size: var(--fs-xs);
  color: var(--ink-3);
  border-radius: var(--r-sm);
}
.contact__action:hover { color: var(--ink); background: var(--hover); }
.contact__action--danger:hover { color: var(--danger); background: var(--danger-soft); }

.contacts__empty {
  font-size: var(--fs-sm);
  color: var(--ink-3);
  font-style: italic;
  padding: var(--sp-2) 0;
}

.contact-form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-2);
  background: var(--surface);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r);
  padding: var(--sp-3);
}
.contact-form .input { font-size: var(--fs-sm); }
.contact-form__actions {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  justify-content: flex-end;
}

/* Form card (settings, new project, user edit) ──────────────────── */

.form-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-5);
}

.form-card + .form-card { margin-top: var(--sp-4); }

/* Two-pane settings layout ────────────────────────────────────────────
   Page-flat sub-nav rail (left) + pane (right). Used on /users/{id}/edit
   so the deep prefs (account / appearance / navigation / time tracker)
   each get their own scannable view instead of a stack of cards. No card
   chrome of its own — fits the /projects density (the user's reference
   for "tidy and compact"). The pattern is reusable for any future
   in-page settings screen. JS: app.js · initSettingsRail(). */
.settings-shell {
  display: grid;
  grid-template-columns: minmax(160px, 200px) 1fr;
  gap: var(--sp-6);
  align-items: start;
  margin-top: var(--sp-4);
}
.settings-rail {
  display: flex;
  flex-direction: column;
  gap: 2px;
  position: sticky;
  /* Clear the sticky nav (height --nav-h) so the rail parks below it, not under it. */
  top: calc(var(--nav-h) + var(--sp-4));
}
.settings-rail__btn {
  appearance: none;
  background: transparent;
  border: 0;
  border-radius: var(--r);
  padding: var(--sp-2) var(--sp-3);
  text-align: left;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.settings-rail__btn:hover { background: var(--hover); color: var(--ink); }
.settings-rail__btn.is-active {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
  font-weight: var(--fw-semi);
}
/* The Developer link is a real <a> (its own page) styled like the sibling
 * pane-switch buttons; the trailing ↗ marks that it leaves the settings shell. */
a.settings-rail__btn--link { display: block; text-decoration: none; }
a.settings-rail__btn--link::after { content: ' ↗'; color: var(--ink-3); }
/* Doc index — the /developer left index reuses the notes/projects board rail
   look verbatim (.nav-rail--notes): grouped section, uppercase head, hover/active
   item states from the theme tokens. .nav-rail--docs only adds page-specific
   chrome the board rail gets from its layout slot (sticky + a card surface, since
   here it sits inside the page body, not the fixed rail column). Scroll-spy
   (app.js · initDocIndex) toggles .is-active as each section enters view. */
/* .nav-rail--docs: the /developer reference index. It IS a real app-shell rail
   (mounted in the `rail` grid column by layout.php), so it inherits the full
   board-rail chrome from .nav-rail (app.css) + .nav-rail--notes verbatim — same
   full-bleed, --bg panel + right divider as /notes.
   Unlike the notes rail it does NOT scroll with the page: it's pinned to the
   viewport (sticky below the nav) and scrolls internally only if the index
   outgrows the screen, so the reference index stays put while the docs scroll. */
.nav-rail--docs {
  position: sticky;
  top: var(--nav-h);
  align-self: start;
  height: calc(100vh - var(--nav-h));
  overflow-y: auto;
}
.nav-rail--docs .doc-index__label { min-width: 0; }
/* The docs rail leads with a proper title, not the tiny uppercase board-rail
   eyebrow — bumps size/weight/colour and drops the all-caps treatment. The
   .nav-rail--notes .nav-rail__head rule lives later in this file at equal
   specificity, so qualify with both modifier classes to win the cascade. */
.nav-rail--docs .nav-rail__head.nav-rail__head--lg {
  font-size: var(--fs-xl);
  font-weight: var(--fw-semi);
  text-transform: none;
  letter-spacing: -0.01em;
  color: var(--ink);
  padding: var(--sp-1) var(--sp-2) var(--sp-3);
}
/* Rail groups are <details> disclosures: the head is a clickable <summary> with a
   ▸ caret (same vocabulary as the body groups + .api-doc__response). Collapsed
   <details> hides its <ul> natively, so the rail shows ~11 group heads at rest. */
.nav-rail--docs summary.nav-rail__head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  cursor: pointer;
  list-style: none;
}
.nav-rail--docs summary.nav-rail__head::-webkit-details-marker { display: none; }
.nav-rail--docs summary.nav-rail__head::before {
  content: '▸';
  font-size: 0.7em;
  color: var(--ink-3);
  transition: transform var(--t-fast);
}
.nav-rail--docs details[data-doc-group-rail][open] > summary.nav-rail__head::before { transform: rotate(90deg); }
.nav-rail--docs summary.nav-rail__head:hover::before { color: var(--ink-2); }
/* Inline verb pill — the same GET/POST chip the rail shows, but sized to read
   alongside the (large) body section titles. Bigger than the rail's 9px chip,
   auto width (no fixed rail column), and vertically centred with the title. */
.api-doc__title .doc-verb--inline {
  width: auto;
  font-size: var(--fs-xs);
  line-height: 1.4;
  padding: 1px var(--sp-2);
  vertical-align: middle;
  position: relative;
  top: -2px;
  margin-right: var(--sp-2);
}
/* Two-column endpoint rows: docs (heading + prose) left, code example right,
   vertically aligned per endpoint. The divider that separates endpoints lives
   on the ROW now (spans both columns); the heading itself no longer carries it.
   Below 1100px the row collapses to a single stacked column. */
.api-doc__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: var(--sp-5);
  /* stretch: both columns take the row's full height (= the taller side), so the
     code window can grow to match the docs column and the row reads as even. */
  align-items: stretch;
  margin-top: var(--sp-5);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--border);
}
.api-doc__col-doc { min-width: 0; }
/* demo column is a flex column: the code widget grows to fill the leftover
   height (down to its 150px floor), the collapsed response sits below it. */
.api-doc__col-demo { min-width: 0; display: flex; flex-direction: column; }
/* the code widget no longer needs its own top margin inside the column */
.api-doc__col-demo .api-doc__codewrap { margin: 0; flex: 1 1 auto; }
/* headings inside a row: drop the old standalone divider/margins (the row owns them) */
.api-doc__row .api-doc__title.doc-section { margin-top: 0; padding-top: 0; border-top: 0; }
@media (max-width: 1100px) {
  .api-doc__row { grid-template-columns: 1fr; gap: var(--sp-3); }
}
/* Close the last endpoint with a matching bottom rule. */
.api-doc { padding-bottom: var(--sp-5); border-bottom: 1px solid var(--border); }
/* Generous whitespace below the docs so the closing rule clearly reads as the
   end of the page (nothing butts up against the viewport bottom). */
.dev-page { padding-bottom: calc(var(--sp-7) * 2); }
/* "What is this" overview for anyone landing on /developer without context.
   Substantial but calm — doc-voice prose with light structure (subheads + the
   shared .api-note callout), deliberately NOT a hero band or colored cards.
   Uses the full content width: a readable-width lead, then the explainer
   sections flow across the available space in a multi-column grid. */
.api-intro {
  margin: 0 0 var(--sp-6);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--ink-2);
}
.api-intro p { margin: 0 0 var(--sp-3); }
.api-intro a { color: var(--ink); text-decoration: underline; }
.api-intro code { font-size: 0.92em; }
/* Top row: the intro lead on the left, the safety callout inline on the right. */
.api-intro__top {
  display: grid;
  grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr);
  gap: var(--sp-6);
  align-items: start;
}
@media (max-width: 860px) { .api-intro__top { grid-template-columns: 1fr; } }
.api-intro__lead {
  color: var(--ink);          /* first paragraph reads a touch stronger */
  margin: 0;
}
/* The shared .api-note carries a 12px top margin (it's normally an inline
   callout, and that rule is defined later in this file at equal specificity, so
   it wins on source order). Qualify with the container to out-specify it and zero
   the margin, so the callout's box top sits flush with the lead paragraph. */
.api-intro .api-intro__note { margin-top: 0; }
/* Three explainer columns side-by-side, filling the content width; drop to two
   then one as the width shrinks. */
.api-intro__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-4) var(--sp-6);
  margin-top: var(--sp-4);
}
.api-intro__col { min-width: 0; }
@media (max-width: 1100px) { .api-intro__grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 720px)  { .api-intro__grid { grid-template-columns: 1fr; } }
/* Subhead spacing: the first one in a column sits tight; later ones get air. */
.api-intro__h {
  font-size: var(--fs-md, var(--fs-base));
  font-weight: var(--fw-semi);
  color: var(--ink);
  letter-spacing: -0.01em;
  margin: var(--sp-4) 0 var(--sp-2);
}
.api-intro__col > .api-intro__h:first-child { margin-top: 0; }
.api-intro__list {
  margin: 0 0 var(--sp-3);
  padding-left: var(--sp-5);
  list-style: disc;           /* restore markers (a global ul reset removes them) */
}
/* Keep it a block list, NOT flex — flex items don't render ::marker, which is
   what was dropping the bullets. Use margin for the inter-item gap. */
.api-intro__list li { margin-bottom: var(--sp-1); padding-left: var(--sp-1); }
.api-intro__list li::marker { color: var(--ink-3); }

/* Prebuilt-integrations group: download-and-run tools for non-coders. Each is a
   calm floater card (rounded, --surface) with a description + a download button. */
.integration-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}
.integration {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}
.integration__body { min-width: 0; }
.integration__name {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-md, var(--fs-base));
  font-weight: var(--fw-semi);
  color: var(--ink);
  margin: 0 0 var(--sp-2);
}
.integration__platform {
  font-size: var(--fs-2xs, var(--fs-xs));
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-on-strong);
  background: var(--accent);
  border-radius: var(--r);
  padding: 1px var(--sp-2);
}
.integration__desc { font-size: var(--fs-sm); line-height: 1.55; color: var(--ink-2); margin: 0 0 var(--sp-2); }
.integration__meta { font-size: var(--fs-xs); color: var(--ink-3); margin: 0; }
.integration__action { margin-top: auto; }
/* Clear break between the tokens block and the Authentication / reference docs. */
.dev-page__break {
  border: 0;
  border-top: 1px solid var(--border);
  margin: var(--sp-6) 0;
}
/* HTTP-verb pill — sits where the board rail's count pill sits (right edge).
   Theme tokens only: GET = accent-tinted, POST = neutral surface. Monospace +
   fixed width so the verbs line up. */
/* Base verb-pill shape only. The per-method COLOURS live in the dedicated
   rest_api.css (loaded on /developer only) — a deliberate, isolated exception
   to the theme palette; do not add verb colours here. Auto width fits the
   longer verbs (PATCH / DELETE); min-width keeps GET/POST aligned. */
.doc-verb {
  flex: 0 0 auto;
  min-width: 34px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: var(--fw-semi);
  letter-spacing: 0.04em;
  line-height: 1.5;
  padding: 1px 5px;
  border-radius: var(--r);
  color: var(--ink-3);
  background: var(--bg);
  border: 1px solid var(--border);
}
.doc-section { scroll-margin-top: calc(var(--nav-h) + var(--sp-4)); }
.settings-panes { min-width: 0; }
.settings-pane[hidden] { display: none; }
.settings-pane__title {
  font-size: var(--fs-xl);
  font-weight: var(--fw-semi);
  color: var(--ink);
  margin: 0 0 var(--sp-4);
  letter-spacing: -0.01em;
}
.settings-pane__intro {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  margin: 0 0 var(--sp-4);
  max-width: 64ch;
}
.settings-pane__section {
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}
/* ── Settings rows: compact label-left rows at /projects density ───────────
   Panes use these instead of stacked-label form-grids. Each group is one
   bordered block (mirrors `.projects`) that fills the full content width like
   the rest of the tool — rows flow 2-up and collapse to 1-up when narrow.
   Hairlines come from a 1px grid gap over a border-colored block background. */
.set-group__title {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
  color: var(--ink);
  margin: var(--sp-5) 0 var(--sp-2);
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
}
/* "Saved" flag shown briefly after an instant-save (e.g. the Status section). */
.set-group__saved {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  color: var(--accent);
  opacity: 0;
  transition: opacity var(--t-fast);
}
.set-group__saved.is-shown { opacity: 1; }
/* Small muted footnote under a settings subsection (e.g. "auto-saves"). */
.set-group__note {
  font-size: var(--fs-xs);
  color: var(--ink-2);
  margin: var(--sp-2) 0 0;
}

/* ── API tab: self-service tokens + docs ─────────────────────────────── */
.token-new-form { display: flex; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.token-new-form .input { flex: 1; min-width: 0; }
.token-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.token-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding: var(--sp-2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
}
.token-row.is-new { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.token-row.is-revoked { opacity: 0.55; }
.token-row__label { font-weight: var(--fw-medium); color: var(--ink); }
.token-row__prefix, .token-row__full {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 1px 6px;
}
.token-row__full { word-break: break-all; }
.token-row__meta { font-size: var(--fs-xs); color: var(--ink-2); margin-left: auto; }
.token-row__once { font-size: var(--fs-xs); color: var(--accent); }
.token-row__revoke { margin-left: auto; }
.token-row.is-new .token-row__revoke { margin-left: 0; }
/* Protected tokens (migration mirror) can't be revoked — a neutral badge takes
   the Revoke button's place. */
.token-row__protected {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  color: var(--ink-3);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 1px var(--sp-2);
}
.token-list__empty { font-size: var(--fs-sm); color: var(--ink-2); margin: var(--sp-2) 0 0; }

/* Scope picker on the mint form. Floater card (rounded), not the page substance. */
.token-scopes {
  margin: 0 0 var(--sp-3);
  padding: var(--sp-2) var(--sp-3) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
}
.token-scopes__legend { font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--ink); padding: 0 var(--sp-1); }
.token-scopes__hint { font-weight: var(--fw-regular); color: var(--ink-2); }
.token-scopes__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--sp-1) var(--sp-3);
  margin-top: var(--sp-2);
}
.token-scopes__opt {
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: baseline;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  cursor: pointer;
}
.token-scopes__key { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--ink); }
.token-scopes__desc { color: var(--ink-2); }

/* Scope chips on a token row. */
.token-row__scopes { display: flex; flex-wrap: wrap; gap: 4px; }
.token-row__scope {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs, var(--fs-xs));
  color: var(--ink-2);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 0 6px;
}
/* "global" reads as full-access — accent it so it's distinct from a scoped grant. */
.token-row__scope--global { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }

/* The two-column endpoint rows want the full content width; only the single-
   column intro prose is capped for readability. (Was max-width:760px on the
   whole block, which halved the page once it went two-column.) */
.api-doc { max-width: 1500px; }
.api-doc__intro { font-size: var(--fs-sm); color: var(--ink-2); margin: 0 0 var(--sp-3); max-width: 760px; }

/* Authentication banner — a full-width card above the two-column endpoint rows.
   Title + lead, then the three token-passing methods as a responsive grid, then
   the response-shape footnote. Theme tokens only. */
.api-auth {
  max-width: 1500px;
  margin: 0 0 var(--sp-5);
}
.api-auth__title {
  font-size: var(--fs-xl);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin: 0 0 var(--sp-2);
}
.api-auth__lead { font-size: var(--fs-sm); color: var(--ink-2); margin: 0 0 var(--sp-4); max-width: 80ch; }
.api-auth__methods {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.api-auth__method {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r);
}
.api-auth__badge {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  border-radius: var(--r);
  padding: 1px var(--sp-2);
}
.api-auth__code {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink);
  word-break: break-all;
}
.api-auth__hint { font-size: var(--fs-xs); color: var(--ink-3); }
/* inline code chips in the lead/footnote prose (not inside .api-doc) */
.api-auth__lead code, .api-auth__foot code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 0.05em 0.35em;
}
.api-auth__foot {
  font-size: var(--fs-sm);
  color: var(--ink-2);
  margin: 0;
  padding-top: var(--sp-3);
}
.api-doc__title { font-size: var(--fs-xl); font-weight: 700; letter-spacing: -0.01em; color: var(--ink); margin: var(--sp-4) 0 var(--sp-2); }
/* Endpoint headings carry a verb pill (left) + a scope chip (right) bracketing
   the title on ONE line, so the permission node reads as part of the heading
   rather than a subtitle hanging beneath it. Flex lets the scope chip sit flush
   right while the title takes the slack. */
.api-doc__title--endpoint {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.api-doc__title--endpoint .doc-verb--inline { margin-right: 0; top: 0; }
.api-doc__title-text { min-width: 0; }
/* Scope chip: a two-part badge — a muted "scope" eyebrow + the mono permission
   node — pushed to the trailing edge of the heading. Borrows the verb pill's
   chip shape so it reads as a sibling, not an afterthought. */
.api-doc__scope {
  margin-left: auto;
  display: inline-flex;
  align-items: stretch;
  font-size: var(--fs-2xs, var(--fs-xs));
  font-weight: var(--fw-medium);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  overflow: hidden;
  white-space: nowrap;
}
.api-doc__scope-tag {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  background: var(--surface-2);
  padding: 1px var(--sp-1);
  border-right: 1px solid var(--border);
}
.api-doc__scope-key {
  font-family: var(--font-mono);
  font-weight: var(--fw-regular);
  color: var(--ink);
  background: var(--bg);
  padding: 1px var(--sp-2);
}
/* Group band: a collapsible <summary> that introduces a section of related
   endpoints in the body, mirroring the rail's group heads. Strong top rule sets
   each group apart; a ▸ caret (reusing the .api-doc__response disclosure
   vocabulary) rotates when the group's <details> is open. */
.api-doc__group {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-2xl);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: var(--sp-7) 0 var(--sp-2);
  padding-top: var(--sp-5);
  border-top: 2px solid var(--border-strong, var(--border));
}
.api-doc__group::-webkit-details-marker { display: none; }
.api-doc__group::before {
  content: '▸';
  font-size: 0.7em;
  color: var(--ink-3);
  transition: transform var(--t-fast);
}
.api-doc__group-wrap[open] > .api-doc__group::before { transform: rotate(90deg); }
.api-doc__group:hover { color: var(--ink); }
.api-doc__group:hover::before { color: var(--ink-2); }
/* First group wrapper sits tight under the page header (no big top gap/rule). */
.api-doc__group-wrap:first-child > .api-doc__group { margin-top: var(--sp-2); padding-top: 0; border-top: 0; }
/* The first endpoint row in a group already carries a top rule; drop it so it
   doesn't double up with the summary band directly above it. */
.api-doc__group-body > .api-doc__row:first-child { border-top: 0; margin-top: var(--sp-3); padding-top: 0; }
.api-doc p { font-size: var(--fs-sm); color: var(--ink); margin: 0 0 var(--sp-2); }
.api-doc__soon { color: var(--ink-2); font-style: italic; }
.api-doc code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 0.05em 0.35em;
}
.api-doc__params { border-collapse: collapse; margin: var(--sp-2) 0 var(--sp-3); font-size: var(--fs-sm); }
.api-doc__params td { padding: 4px 12px 4px 0; vertical-align: top; }
.api-doc__params td:first-child { white-space: nowrap; }
/* Richer parameter table (left column): Parameter / Type / Required / Notes. */
.api-params {
  width: 100%;
  border-collapse: collapse;
  margin: var(--sp-2) 0 var(--sp-3);
  font-size: var(--fs-sm);
}
.api-params thead th {
  text-align: left;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  padding: 0 var(--sp-3) var(--sp-1) 0;
  border-bottom: 1px solid var(--border);
}
.api-params td {
  padding: var(--sp-2) var(--sp-3) var(--sp-2) 0;
  vertical-align: top;
  border-bottom: 1px solid var(--border);
}
.api-params tr:last-child td { border-bottom: 0; }
.api-params td:first-child { white-space: nowrap; }
.api-params__type { color: var(--ink-2); font-family: var(--font-mono); font-size: var(--fs-xs); white-space: nowrap; }
.api-params__req {
  display: inline-block;
  font-size: 10px;
  font-weight: var(--fw-semi);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 1px var(--sp-2);
  border-radius: var(--r);
  border: 1px solid var(--border);
  white-space: nowrap;
}
.api-params__req--on { color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, transparent); border-color: color-mix(in srgb, var(--danger) 22%, transparent); }
.api-params__req--off { color: var(--ink-3); background: var(--bg); }
/* Tip / notes callout (left column). Subtle inset, accent left edge. */
.api-note {
  display: flex;
  gap: var(--sp-2);
  margin: var(--sp-3) 0 0;
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--r);
}
.api-note__icon {
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 1px;
  background-color: var(--accent);
  -webkit-mask: url(/assets/icon/circle-info.svg) center / contain no-repeat;
          mask: url(/assets/icon/circle-info.svg) center / contain no-repeat;
}
.api-note__body { min-width: 0; }
.api-note__body :first-child { margin-top: 0; }
.api-note__body :last-child { margin-bottom: 0; }
/* Code example = a tabbed widget (curl / php / go). The WRAPPER owns the border,
   radius and `overflow: hidden`, so the panels' horizontal scrollbars are
   clipped to the rounded corners instead of bleeding past them. The <pre>
   panels carry no border/radius of their own — they just scroll inside. */
.api-doc__codewrap {
  position: relative;
  display: flex;
  flex-direction: column;
  margin: var(--sp-2) 0 var(--sp-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  overflow: hidden;
}
/* Tab bar: the language tabs on the left, the copy icon on the right. */
.code-tabs__bar {
  display: flex;
  align-items: stretch;
  gap: 2px;
  padding: 0 var(--sp-1);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.code-tabs__tab {
  appearance: none;
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-xs);
  font-family: var(--font-mono);
  color: var(--ink-3);
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: var(--sp-2) var(--sp-2);
  transition: color var(--t-fast), border-color var(--t-fast);
}
.code-tabs__tab:hover { color: var(--ink-2); }
.code-tabs__tab.is-active { color: var(--ink); border-bottom-color: var(--accent); }
/* All four panels (curl/php/go/AI) are stacked in ONE grid cell so switching
   tabs never changes the box height. The cell does NOT grow to its tallest
   panel — its height is driven by the flex chain (demo column → codewrap →
   here), i.e. the left column's height, floored at 200px. `minmax(0, 1fr)` on
   both axes lets a panel that's taller than the cell SCROLL inside instead of
   stretching the box (so the long AI spec scrolls rather than ballooning). */
.code-tabs__panels {
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  grid-template-columns: minmax(0, 1fr);
  flex: 1 1 0;
  min-height: 200px;
}
.code-tabs__panel {
  grid-area: 1 / 1;          /* all panels overlap in one cell */
  min-width: 0;
  min-height: 0;             /* allow the panel to shrink + scroll, not stretch */
  overflow: auto;
  visibility: hidden;
  pointer-events: none;
}
.code-tabs__panel.is-active { visibility: visible; pointer-events: auto; }
.api-doc__code {
  margin: 0;
  padding: var(--sp-2) var(--sp-3);
  background: none;
  border: 0;
  border-radius: 0;
}
.api-doc__code code { background: none; border: 0; padding: 0; white-space: pre; line-height: 1.45; font-size: var(--fs-xs); }
/* Sample response under each request example — a collapsible <details>, closed
   by default so the right column stays compact. The <summary> is the clickable
   "Example response" label with a disclosure caret; opening reveals the JSON
   block in the same surface vocabulary as the request box. */
.api-doc__response { margin-top: var(--sp-2); }
.api-doc__response-label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  cursor: pointer;
  list-style: none;
  width: fit-content;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  transition: color var(--t-fast);
}
.api-doc__response-label:hover { color: var(--ink-2); }
.api-doc__response-label::-webkit-details-marker { display: none; }
/* disclosure caret (rotates when open) */
.api-doc__response-label::before {
  content: '▸';
  display: inline-block;
  font-size: 0.9em;
  transition: transform var(--t-fast);
}
.api-doc__response[open] .api-doc__response-label::before { transform: rotate(90deg); }
.api-doc__response-code {
  margin-top: var(--sp-1);
  height: 150px;
  overflow: auto;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: var(--sp-2) var(--sp-3);
}
/* Copy icon, right-aligned in the tab bar (no overlay of the code below). */
.code-tabs__bar .api-doc__copy { margin-left: auto; }
.api-doc__copy {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--sp-1);
  appearance: none;
  cursor: pointer;
  color: var(--ink-3);
  background: none;
  border: 0;
  transition: color var(--t-fast);
}
.api-doc__copy:hover { color: var(--ink); }
.api-doc__copy .icon { width: 14px; height: 14px; }

.set-rows {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--r-none);
}
.set-row {
  display: grid;
  grid-template-columns: 160px minmax(0, 1fr);
  gap: var(--sp-3);
  align-items: start;
  padding: var(--sp-2) var(--sp-4);
  background: var(--surface);
}
/* Tall / multi-line / single-of-a-group rows span the full width so short
   controls never get stretched against a mismatched partner (set explicitly
   per row via the partial's `full` flag — paired groups are kept even-count). */
.set-row--full { grid-column: 1 / -1; }
.set-row__label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--ink-2);
  padding-top: 7px;
}
.set-row__control { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.set-row__control .input:not([type="file"]),
.set-row__control .select { height: 32px; padding: 5px 10px; font-size: var(--fs-sm); }
.set-row__control .select { padding-right: 30px; max-width: 22rem; }
.set-row__control .input:not([type="file"]):not([type="color"]):not(.input--num) { max-width: 30rem; }
.set-row__control .input--num { max-width: 6rem; }
.set-row__control .field__hint,
.set-row__control .form-help { margin: 0; }
.set-row__control .checkbox-row { padding: 2px 0; }
@media (max-width: 1200px) {
  .set-rows { grid-template-columns: 1fr; }
}

/* Navigation pane: render the sortable tab list as one bordered block of
   hairline-divided rows, matching the .set-rows density of the other panes.
   Scoped to the tab-prefs list so the other sortable lists (projects /
   templates / checklists editors) keep their own card styling. */
[data-nav-editor] {
  gap: 0;
  border: 1px solid var(--border);
  border-radius: var(--r-none);
  background: var(--surface);
}
[data-nav-editor] > .sortable-item {
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  background: transparent;
}
[data-nav-editor] > .sortable-item:last-child { border-bottom: 0; }

/* ─── Unified nav editor: group rows + nested member drop-zones (session 34) ──
 * One sortable list interleaves tab rows and group rows; tabs nest into a
 * group's drop-zone. Theme-first — reuses surface/border tokens, --r-none to
 * match the list block. */
.sortable-item.nav-group {
  flex-direction: column;
  align-items: stretch;
  flex-wrap: nowrap;
  gap: var(--sp-2);
}
.nav-group__head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.nav-group__label { max-width: 16rem; }
.nav-group__head .link { margin-left: auto; }
.nav-group__members {
  display: flex;
  flex-direction: column;
  gap: 0;
  margin-left: var(--sp-6);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r);
  background: var(--bg);
}
.nav-group__members > .sortable-item {
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  background: transparent;
}
.nav-group__members > .sortable-item:last-child { border-bottom: 0; }
.nav-group__members:empty::before {
  content: "Drag tabs here to add them to this group";
  display: flex;
  align-items: center;
  min-height: 38px;
  padding: 0 var(--sp-3);
  color: var(--ink-3);
  font-size: var(--fs-xs);
  font-style: italic;
}
/* Inside a group the Visible / fullscreen controls don't apply (grouped tabs
   always render in the dropdown) — hide them to cut noise. */
.nav-group__members [data-tab-row-controls] { display: none; }
.settings-pane__section-title {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
  color: var(--ink);
  margin: 0 0 var(--sp-3);
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
}
.muted-after {
  font-weight: var(--fw-reg);
  font-size: var(--fs-xs);
  color: var(--ink-3);
}

/* Vertical stack of checkbox-rows (account flags, badge list, /time prefs) */
.checkbox-stack { display: flex; flex-direction: column; gap: var(--sp-1); }
.checkbox-stack--row { flex-direction: row; gap: var(--sp-4); flex-wrap: wrap; }

/* Right-aligned per-row controls inside the Navigation pane sortable list */
.sortable-item__controls {
  margin-left: auto;
  display: flex;
  gap: var(--sp-3);
  align-items: center;
  flex-wrap: wrap;
}

@media (max-width: 720px) {
  .settings-shell {
    grid-template-columns: 1fr;
    gap: var(--sp-3);
  }
  .settings-rail {
    flex-direction: row;
    overflow-x: auto;
    position: static;
    border-bottom: 1px solid var(--border);
    padding-bottom: var(--sp-2);
  }
  .settings-rail__btn { white-space: nowrap; }
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4);
}
.form-grid .field--span-2 { grid-column: span 2; }
.form-grid .field--span-3 { grid-column: 1 / -1; }
@media (max-width: 720px) {
  .form-grid { grid-template-columns: 1fr; }
  .form-grid .field--span-2,
  .form-grid .field--span-3 { grid-column: auto; }
}

.form-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}
.form-actions__spacer { flex: 1; }
.form-actions__hint { font-size: var(--fs-xs); color: var(--ink-3); }

/* Danger-zone variant of .form-card — used on edit forms to host the
   destructive Delete action with visual separation from the main form. */
.form-card--danger-zone {
  margin-top: var(--sp-4);
  border-color: var(--danger);
}
.form-card--danger-zone__heading {
  font-size: var(--fs-md);
  color: var(--danger);
  margin-bottom: var(--sp-2);
}
.form-card--danger-zone__body {
  font-size: var(--fs-sm);
  color: var(--ink-2);
  margin-bottom: var(--sp-3);
}

.checkbox-row {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  padding: 8px 0;
}
.checkbox-row input { accent-color: var(--accent); }

/* Color swatch input for user color picker */
.color-input {
  width: 100%;
  height: 40px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface);
  cursor: pointer;
  padding: 4px;
}
/* Small swatch — for compact settings rows where the saturated user color
   shouldn't fill an entire grid column (e.g. /users/{id}/edit Account pane). */
.color-input--sm {
  width: 56px;
  height: 32px;
  padding: 2px;
}

/* List card — used by templates index, users index, notifications index ──── */

.list {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-none);
}
.list__row {
  display: grid;
  align-items: center;
  gap: var(--sp-3);
  padding: 6px var(--sp-3);
  border-bottom: 1px solid var(--border);
  transition: background var(--t-fast);
  min-height: 36px;
}
.list__row:last-child { border-bottom: none; }
.list__row:hover { background: var(--hover); }
.list__row a.list__row-link { color: var(--ink); display: block; }

.list__row.is-inactive { opacity: 0.55; }

/* Templates list (/projects/templates) — 3-col on desktop, stacks at <720px so
   the title doesn't get crushed between meta + actions on phone widths. */
.list--templates .list__row {
  grid-template-columns: 1fr auto auto;
}
@media (max-width: 720px) {
  .list--templates .list__row {
    grid-template-columns: 1fr;
    row-gap: var(--sp-2);
  }
  .list--templates .list__row > * { grid-column: 1; }
  .list--templates .list__row-meta { justify-content: flex-start; }
  .list--templates .list__row-actions { justify-content: flex-start; }
}

/* Notes flat list — 3-col on desktop (avatar / title+sub link / meta).
   Shared by /notes?layout=list and the /notes kanban narrow-fallback. */
.list--notes-flat .list__row {
  grid-template-columns: auto 1fr auto;
  padding: var(--sp-3);
}
/* The 1fr link column must be allowed to shrink below its content's intrinsic
   width (grid/flex children default to min-width:auto), otherwise the nowrap
   excerpt + long sub text push the column wider than its track and overlap the
   right-edge meta. min-width:0 lets the ellipsis rules below actually engage. */
.list--notes-flat .list__row-link { min-width: 0; }
.list--notes-flat .list__row-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.list--notes-flat .list__row-sub {
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Note-body preview — one muted line so each row carries the note's gist. */
.list--notes-flat .list__row-excerpt {
  margin-top: 4px;
  font-size: var(--fs-sm);
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Clients index — 4-col on desktop (status-dot, name, hours spend/allocation,
   edit-action), stacks at <720px so right-edge meta and action columns don't
   truncate below the viewport edge. */
.list--clients .list__row {
  grid-template-columns: auto 1fr auto auto;
}
@media (max-width: 720px) {
  .list--clients .list__row {
    grid-template-columns: auto 1fr;
    row-gap: var(--sp-2);
  }
  .list--clients .list__row-link { grid-column: 2; }
  .list--clients .list__row-meta,
  .list--clients .list__row-actions {
    grid-column: 1 / -1;
    justify-content: flex-start;
  }
}

/* Status group header inside the clients list (Active group first, Inactive
   at the bottom). A plain block child of .list, spans full width. */
.list__group-header {
  padding: var(--sp-3) var(--sp-3) var(--sp-1);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-3);
  border-bottom: 1px solid var(--border);
}
.list__group-count {
  margin-left: var(--sp-2);
  opacity: 0.7;
}
/* Per-client hours: "spend this month / monthly allocation". */
.client-hours { font-variant-numeric: tabular-nums; white-space: nowrap; }
.client-hours--over .client-hours__used {
  color: var(--danger);
  font-weight: var(--fw-medium);
}

/* Saved-reports list (/time/reports) — 6-col on desktop (type-pill, title,
   created, format, size, actions), stacks at <720px so the right-edge meta
   columns don't get truncated below the viewport edge. */
.list--reports .list__row {
  grid-template-columns: auto minmax(220px, 1fr) auto auto auto auto;
}
@media (max-width: 720px) {
  .list--reports .list__row {
    grid-template-columns: 1fr;
    row-gap: var(--sp-2);
  }
  .list--reports .list__row > * { grid-column: 1; justify-self: start; }
  .list--reports .list__row-actions { justify-content: flex-start; }
}

/* Checklist-instances list (/checklists Due/in progress) — 5-col on desktop
   (dot, title+sub, done/total, percent, action), stacks at <720px. */
.list--checklist-instances .list__row {
  grid-template-columns: auto 1fr auto auto auto;
}
@media (max-width: 720px) {
  .list--checklist-instances .list__row {
    grid-template-columns: auto 1fr;
    row-gap: var(--sp-2);
  }
  .list--checklist-instances .list__row > a,
  .list--checklist-instances .list__row-meta,
  .list--checklist-instances .list__row-actions { grid-column: 1 / -1; }
  .list--checklist-instances .list__row-meta { justify-content: flex-start; }
  .list--checklist-instances .list__row-actions { justify-content: flex-start; }
}

/* Checklist-completed list (/checklists Recently completed) — 3-col on
   desktop (title, who, when), stacks at <720px. */
.list--checklist-completed .list__row {
  grid-template-columns: 1fr auto auto;
}
@media (max-width: 720px) {
  .list--checklist-completed .list__row {
    grid-template-columns: 1fr;
    row-gap: var(--sp-2);
  }
  .list--checklist-completed .list__row > * { grid-column: 1; justify-self: start; }
}

/* Checklist-items list (/checklists/{id}) — simple 3-col grid (dot, label,
   timestamp). Auto-sizing columns fit naturally at narrow widths. */
.list--checklist-items .list__row {
  grid-template-columns: auto 1fr auto;
}
.list--checklist-items .list__row-title.is-done { text-decoration: line-through; color: var(--ink-3); }
.list--checklist-items .list__row-sub--quote { margin-top: 4px; font-style: italic; }

/* Checklist instance summary (/checklists/{id} top card) — 2-col dl with
   right-side margins reset; left col is the label, right is the value. */
.checklist-summary { margin-top: var(--sp-4); }
.checklist-summary__dl {
  display: grid;
  grid-template-columns: 160px 1fr;
  gap: var(--sp-2);
  margin: 0;
}
.checklist-summary__dl dt { color: var(--ink-3); }
.checklist-summary__dl dd { margin: 0; }
.checklist-summary__note { white-space: pre-wrap; }

/* Checklist-template edit form (/checklists/templates/{id}) — `Items` section
   heading + intro + sortable item rows + add button + form-actions row. */
.form-card--checklist-template { margin-top: var(--sp-4); }
.checklist-items__heading {
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  margin: var(--sp-4) 0 var(--sp-2);
}
.checklist-items__hint {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  margin-bottom: var(--sp-3);
}
.checklist-items__add { margin-top: var(--sp-3); }
.checklist-item-row__fields {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.checklist-item-row__note { margin-left: auto; }
.form-actions--checklist-template { margin-top: var(--sp-5); }
.form-actions__danger { display: inline; margin-left: auto; }

/* Checklist-templates list (/checklists/templates) — 6-col on desktop (dot,
   title+sub, items count, cadence, actions), stacks at <720px. */
.list--checklist-templates .list__row {
  grid-template-columns: auto 1fr auto auto auto auto;
}
@media (max-width: 720px) {
  .list--checklist-templates .list__row {
    grid-template-columns: auto 1fr;
    row-gap: var(--sp-2);
  }
  .list--checklist-templates .list__row > a,
  .list--checklist-templates .list__row-meta,
  .list--checklist-templates .list__row-actions { grid-column: 1 / -1; }
  .list--checklist-templates .list__row-meta { justify-content: flex-start; }
  .list--checklist-templates .list__row-actions { justify-content: flex-start; }
}

/* Briefs list (/clients/{id}/grassroots/content) — 5-col on desktop
   (title+kw, status pill, scheduled, owner avatar, actions). Stacks at
   <720px so the title isn't squeezed to ~80px between left-edge and pill. */
.list--briefs .list__row {
  grid-template-columns: minmax(0, 1fr) auto auto auto auto;
}
@media (max-width: 720px) {
  .list--briefs .list__row {
    grid-template-columns: 1fr auto;
    row-gap: var(--sp-2);
  }
  /* Title-link + status pill share row 1 (link takes 1fr, pill auto). */
  .list--briefs .list__row > a { grid-column: 1; min-width: 0; }
  .list--briefs .list__row > .status-pill { grid-column: 2; justify-self: end; }
  /* Scheduled, owner, actions wrap full-width below. */
  .list--briefs .list__row-meta,
  .list--briefs .list__row-actions { grid-column: 1 / -1; justify-self: start; }
}

/* Recent-briefs list (compact 3-col version on the grassroots client hub
   "Recent briefs" section). Same mobile collapse pattern as .list--briefs. */
.list--briefs-recent .list__row {
  grid-template-columns: minmax(0, 1fr) auto auto;
}
@media (max-width: 720px) {
  .list--briefs-recent .list__row {
    grid-template-columns: 1fr auto;
    row-gap: var(--sp-2);
  }
  .list--briefs-recent .list__row > a { grid-column: 1; min-width: 0; }
  .list--briefs-recent .list__row > .status-pill { grid-column: 2; justify-self: end; }
  .list--briefs-recent .list__row-meta { grid-column: 1 / -1; justify-self: start; }
}

/* Grassroots Analytics widget — 2-col stat grid (GA4 sessions/users, GSC
   clicks/avg-position). Stacks to single column at <500px so each stat
   value isn't squeezed into ~150px on a phone. */
.gr-analytics-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-3);
}
@media (max-width: 500px) {
  .gr-analytics-grid { grid-template-columns: 1fr; }
}
.gr-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.gr-stat__val {
  font-size: var(--fs-xl);
  font-weight: var(--fw-semi);
}
.gr-stat__hint {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}
/* Per-card analytics hint (smaller, block layout for the index cards' GA4/GSC
   placeholders). Lives on .gr-card__placeholder children. */
.gr-card__placeholder-hint {
  display: block;
  color: var(--ink-3);
  font-size: var(--fs-xs);
  margin-top: 2px;
}

/* ───── Tag manager (/time/tags) ────────────────────────────────────
   Two-column layout: Tags (left, wider) sit beside Categories (right,
   narrower). Stacks to a single column @1080px so each panel keeps
   readable width on smaller viewports. Each row is one item with click-
   to-edit name (contenteditable span) + Active toggle + a small action
   area. Categories list a tag-count; Tags list a usage-count + a
   category dropdown. */
.tag-manager-grid {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: var(--sp-6);
  align-items: start;
}
@media (max-width: 1080px) {
  .tag-manager-grid { grid-template-columns: minmax(0, 1fr); }
}
.tag-manager {
  margin-bottom: 0;
}
.tag-manager__head {
  margin-bottom: var(--sp-3);
}
.tag-manager__title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semi);
  margin: 0 0 4px;
  color: var(--ink);
}
.tag-manager__hint {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--ink-3);
  max-width: 70ch;
}

/* "Add" form — one input + (optional) select + Add button on a single line. */
.tag-add {
  display: flex;
  gap: var(--sp-2);
  align-items: stretch;
  margin-bottom: var(--sp-3);
  max-width: 720px;
}
.tag-add__input { flex: 1; min-width: 0; }
.tag-add__select { flex: 0 0 200px; }

/* Item rows. Categories + tags use the same per-item shell (`.cat-list__item`
   and `.tag-list__item`) but different inner grid templates because tags
   have an extra category-dropdown column. */
.cat-list,
.tag-list {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-none);
}
.cat-list__item,
.tag-list__item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 8px var(--sp-3);
  border-bottom: 1px solid var(--border);
  transition: background var(--t-fast);
}
.cat-list__item:last-child,
.tag-list__item:last-child { border-bottom: 0; }
.cat-list__item:hover,
.tag-list__item:hover { background: var(--hover); }
.cat-list__item.is-inactive,
.tag-list__item.is-inactive { opacity: 0.6; }

/* Inner edit form lays out the name + controls; takes all remaining width
   so the trailing delete button sits flush right. */
.cat-list__form,
.tag-list__form {
  flex: 1;
  display: grid;
  align-items: center;
  gap: var(--sp-3);
  min-width: 0;
}
.cat-list__form  { grid-template-columns: minmax(0, 1fr) auto auto; }
.tag-list__form  { grid-template-columns: minmax(0, 1fr) 200px auto auto; }

.cat-list__empty,
.tag-group__empty {
  padding: var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--ink-3);
  font-style: italic;
}

/* Category section header inside the Tags panel. */
.tag-group {
  margin-top: var(--sp-4);
}
.tag-group:first-of-type { margin-top: 0; }
.tag-group__head {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  margin: 0 0 6px;
  display: inline-flex; align-items: baseline; gap: var(--sp-2);
}
.tag-group__count {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  font-weight: var(--fw-regular);
  font-variant-numeric: tabular-nums;
}

/* Click-to-edit name. The span is contenteditable; a sibling hidden input
   mirrors the value at submit. Visually the span looks like text until
   hover/focus reveals the editable affordance. */
.editable-name {
  font-size: var(--fs-sm);
  color: var(--ink);
  font-weight: var(--fw-medium);
  padding: 4px 6px;
  margin: -4px -6px;        /* negative offset so hover bg doesn't shift layout */
  border-radius: var(--r-sm);
  cursor: text;
  outline: none;
  min-width: 0;
  word-break: break-word;
  transition: background var(--t-fast), box-shadow var(--t-fast);
}
.editable-name:hover { background: var(--hover); }
.editable-name:focus {
  background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.editable-name.is-dirty { box-shadow: inset 0 0 0 1px var(--accent); }

/* Compact Active toggle — checkbox + "Active" label, neutral text until
   the box is unchecked (handled by .is-inactive on the row). */
.active-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  color: var(--ink-2);
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
}
.active-toggle input[type="checkbox"] { margin: 0; }
.active-toggle__label { line-height: 1; }

/* Tag-count / usage-count read-only stat. */
.usage-count {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Generic icon-only button (28×28, ghost style). Used for delete/lock chips
   inside the tag manager — sits at the rightmost column of each row. */
.icon-btn {
  appearance: none;
  border: 1px solid var(--border);
  background: transparent;
  width: 28px; height: 28px;
  border-radius: var(--r);
  color: var(--ink-3);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: color var(--t-fast), background var(--t-fast), border-color var(--t-fast);
  flex: none;
}
.icon-btn:hover { color: var(--ink); background: var(--hover); border-color: var(--border-strong); }
.icon-btn--danger { color: var(--danger); border-color: var(--danger-soft); }
.icon-btn--danger:hover { background: var(--danger-soft); color: var(--danger); border-color: var(--danger); }

/* Locked indicator (in place of delete when a category has tags assigned). */
.cat-list__locked {
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-3);
  flex: none;
}

@media (max-width: 720px) {
  .tag-add { flex-wrap: wrap; }
  .tag-add__select { flex-basis: 100%; }
  .cat-list__form { grid-template-columns: minmax(0, 1fr) auto; }
  .cat-list__form .active-toggle { grid-column: 1 / -1; }
  .tag-list__form { grid-template-columns: minmax(0, 1fr); }
  .tag-list__form > * { grid-column: 1 / -1; }
}

/* Theme-accent native form controls. Used everywhere in the app — checkboxes
   inside tag manager, contact-tag fieldsets, time-entry composer, settings.
   Falls back to UA default in browsers that don't support `accent-color`. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--accent);
}
.list__row-title {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--ink);
  line-height: 1.3;
}
.list__row-sub {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  margin-top: 1px;
  line-height: 1.3;
}
.list__row-meta {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  white-space: nowrap;
}
.list__row-actions {
  display: flex;
  gap: var(--sp-1);
  opacity: 0;
  transition: opacity var(--t-fast);
}
.list__row:hover .list__row-actions { opacity: 1; }
.list__row-actions form { display: inline; }

/* Touch devices have no hover state — force all hover-reveal action clusters
   and image-attachment captions visible at all times so phone/tablet users
   can actually see + tap them. */
@media (hover: none) {
  .list__row-actions,
  .note__actions,
  .contact__actions,
  .sortable-item__remove,
  .note__attachment--image .note__attachment-overlay { opacity: 1; }

  /* Touch-target bumps (WCAG SC 2.5.5 AAA — 44x44 CSS px minimum tap area).
     Desktop renders unchanged; only fires on touch screens. .btn--xs is left
     alone since it lives inside the customize-mode toolbar that's already
     hidden on narrow widths; .btn--icon already gets the larger header area
     on touch since icon buttons inherit base button padding. */
  .icon-btn { width: 44px; height: 44px; }
  .btn--sm { min-height: 44px; padding: var(--sp-2) var(--sp-3); }
  .link--xs {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 var(--sp-1);
    vertical-align: middle;
  }
}

/* Compact on/off light — replaces verbose status pills inside dense tables. */
.dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--ink-disabled);
  flex-shrink: 0;
  /* Hover hit-area expansion (Batch D / session 20): 8x8 visible, ~24x24 hit
   * box via the box-model trick of padding inside a background-clip:content-box.
   * Without this the tooltip is hard to trigger on small pips like the client
   * status indicator. */
  position: relative;
}
.dot::before {
  content: '';
  position: absolute;
  inset: -8px;
}
.dot--on  { background: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.dot--off { background: var(--ink-disabled); }

/* ─── Presence pip ──────────────────────────────────────────────────
   A small indicator BESIDE an avatar (never the ring — the ring always carries
   per-user identity colour). Shape = status (online hollow, busy filled, dnd ✕);
   account-inactive = neutral grey dot (absent from the active-only /api/presence).
   Two viewer toggles on <html> drive the look (presence_parse_prefs):
     data-presence-shape 1|0   → shapes vs plain dots
     data-presence-color user|theme|traffic → identity / accent+ink / green-amber-red
   `--presence-user-color` is set per-avatar by partials/avatar.php; the status
   modifier class is applied live by initPresence() from GET /api/presence. */
.presence-pip {
  display: inline-block;
  width: 12px; height: 12px;
  border-radius: 50%;
  flex-shrink: 0;
  position: relative;
  --presence-c: var(--presence-user-color, var(--ink-3));
  background: var(--presence-c);
  /* Subtle halo ring on EVERY pip (consistency — never a hard circle). The dnd
     glyph clears it; see below. */
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--presence-c) 22%, transparent);
}
/* Hover/click hit-area expansion so the tooltip + user-card are easy to hit. */
.presence-pip::before { content: ''; position: absolute; inset: -6px; }
/* Derived "away" states all read as the same neutral grey haloed circle (labels
   differ: Offline / Break / Inactive — sorted into separate pools on /users).
   They keep the base soft-halo border. Presence is time-tracker-driven — see
   presence_derive(). */
.presence-pip--off,
.presence-pip--offline,
.presence-pip--break { --presence-c: var(--ink-disabled); background: var(--presence-c); }

/* Colour mode (per-viewer). 'user' (default) keeps the base --presence-user-color.
   'theme' = theme accent (online) + inks (busy/dnd) — collision-free, no new hue.
   'traffic' = universal green/amber/red, mapped to existing severity tokens. */
/* The online pip reads --presence-online-c (defaults to the theme --accent).
   Legacy themes override that token to the espresso brown per-theme (below). */
:root[data-presence-color="theme"] .presence-pip--online { --presence-c: var(--presence-online-c, var(--accent)); }
:root[data-presence-color="theme"] .presence-pip--busy   { --presence-c: var(--ink-2); }
:root[data-presence-color="theme"] .presence-pip--dnd    { --presence-c: var(--ink); }
:root[data-presence-color="traffic"] .presence-pip--online { --presence-c: var(--presence-online-c, var(--accent)); }
:root[data-presence-color="traffic"] .presence-pip--busy   { --presence-c: var(--warning); }
:root[data-presence-color="traffic"] .presence-pip--dnd    { --presence-c: var(--danger); }

/* Online + Busy share the soft-haloed circle look; FULLNESS tells them apart —
   online is HOLLOW ("here, but free"), busy is FILLED ("occupied"). dnd is an ✕.
   The halo lives on the base .presence-pip (above) so all states match. */

/* Shape mode ON (default): fullness + the ✕ glyph. */
:root[data-presence-shape="1"] .presence-pip--online {
  background: transparent;
  border: 2px solid var(--presence-c);
}
:root[data-presence-shape="1"] .presence-pip--busy {
  background: var(--presence-c);
}
:root[data-presence-shape="1"] .presence-pip--dnd {
  border-radius: 0;
  box-shadow: none;
  background: var(--presence-c);
  -webkit-mask: var(--presence-glyph) center / contain no-repeat;
          mask: var(--presence-glyph) center / contain no-repeat;
  --presence-glyph: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M2.6 2.6l10.8 10.8M13.4 2.6l-10.8 10.8' stroke='black' stroke-width='3' stroke-linecap='round'/></svg>");
}

/* Shape mode OFF: plain filled dots — colour carries the meaning. In 'user' mode
   colour can't tell states apart, so keep the online=hollow fullness cue. */
:root[data-presence-shape="0"] .presence-pip {
  border-radius: 50%;
  background: var(--presence-c);
  -webkit-mask: none;
          mask: none;
}
:root[data-presence-shape="0"][data-presence-color="user"] .presence-pip--online {
  background: transparent;
  border: 2px solid var(--presence-c);
}
/* Dot mode, HEADER only: a 1px ring separates the presence dot (online/busy/dnd)
   from the nav background, whose colour can sit close to the dot on some themes.
   Uses the nav's own ink at low alpha so it reads as a thin gap on any chrome. */
:root[data-presence-shape="0"] .nav-top .presence-pip {
  border: 1px solid color-mix(in srgb, var(--ink) 35%, transparent);
}

/* Wrapper that hosts [presence-pip] + avatar (+ optional name label), so the whole
   icon/name range is one click target for the user card (partials/avatar.php). */
.presence-host {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  position: relative;
  min-width: 0;   /* let a folded-in name truncate inside constrained grid cells */
}
.presence-host[data-user-id]:not([data-no-card]) { cursor: pointer; }
.presence-host .row__dev-name { min-width: 0; }
/* Kanban column header: let the host fill so the folded-in name still grows and
   the count stays right-aligned. */
.kanban__col-head .presence-host { flex: 1; min-width: 0; }

/* ─── User card popover ─────────────────────────────────────────────
   Body-level, fixed-positioned by initUserCard() (GET /users/{id}/card). Shows
   a person's identity + presence + current time-tracker task on avatar click. */
.user-card {
  position: fixed;
  z-index: 9998;
  min-width: 220px;
  max-width: 300px;
  padding: var(--sp-3);
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  opacity: 0;
  transition: opacity 0.12s ease;
  pointer-events: none;
}
.user-card.is-visible { opacity: 1; pointer-events: auto; }
.user-card__head { display: flex; align-items: center; gap: var(--sp-3); }
.user-card__avatar {
  width: 40px; height: 40px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--ink);
  font-weight: var(--fw-semi); font-size: var(--fs-md);
  border: 2px solid var(--avatar-color, var(--border-strong));
  object-fit: cover; overflow: hidden; flex-shrink: 0;
}
.user-card__name { font-weight: var(--fw-semi); color: var(--ink); }
.user-card__status {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-xs); color: var(--ink-2);
  margin-top: 2px;
}
.user-card__msg {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm); color: var(--ink-2);
  font-style: italic;
}
.user-card__task {
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--border);
  font-size: var(--fs-xs); color: var(--ink-3);
}
.user-card__loading { padding: var(--sp-1) var(--sp-2); color: var(--ink-3); }

/* ─── Presence picker (account-edit Status subsection) ──────────────
   Inline selectable chips. The radio is visually hidden; the <label> is the
   clickable chip. Form-native (no JS) — the checked radio submits as `status`.
   Each chip previews the live pip (shape per the viewer's own pref). */
.presence-picker { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.presence-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
  user-select: none;
  transition: border-color var(--t-fast), background var(--t-fast), color var(--t-fast);
}
.presence-chip:hover { background: var(--hover); }
.presence-chip input { position: absolute; opacity: 0; width: 0; height: 0; }
.presence-chip__label { font-weight: var(--fw-medium); }
/* Selected chip: themed selection styling (accent tint + ring), matching the
 * rest of the app. The status meaning lives in the pip; the chip respects theme. */
.presence-chip:has(input:checked) {
  color: var(--accent);
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.presence-chip:has(input:focus-visible) { box-shadow: 0 0 0 2px var(--accent); }
/* Short status text shown next to a user's email on the /users roster. */
.user-status-text { color: var(--ink-2); }

/* Sortable milestone list (templates edit) ───────────────────────── */

.sortable-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.sortable-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  padding: 8px var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  font-size: var(--fs-sm);
  position: relative;
  transition: background var(--t-fast), border-color var(--t-fast);
}
/* The inputs cluster (the inline `flex: 1` div in templates_form.php) needs
   a soft minimum so it gets enough width to be usable before the trailing
   controls (Requires-note checkbox + Remove link) wrap to a second row. */
.sortable-item > div[style*="flex: 1"] {
  min-width: 180px;
}
.sortable-item:hover { background: var(--hover); border-color: var(--border-strong); }
.sortable-item.sortable-ghost {
  opacity: 0.4;
  background: var(--surface-2);
}
.sortable-item.sortable-chosen {
  border-color: var(--accent);
}
.sortable-item__handle {
  cursor: grab;
  color: var(--ink-3);
  display: inline-flex;
  align-items: center;
  padding: 6px 8px;
  border-radius: var(--r-sm);
  margin-left: -4px;
  transition: background var(--t-fast), color var(--t-fast);
  user-select: none;
  touch-action: none;
}
/* Firefox preempts Sortable.js drag if mousedown lands on a child element it
   considers natively draggable. Disable pointer events on the icon so mousedown
   registers on the parent span (which carries [data-handle]). */
.sortable-item__handle .icon,
.sortable-item__handle svg {
  pointer-events: none;
  -webkit-user-drag: none;
}
.sortable-item__handle:hover { color: var(--ink); background: var(--hover); }
.sortable-item__handle:active { cursor: grabbing; }
.sortable-item__name[contenteditable="true"] {
  cursor: text;
  border-radius: var(--r-sm);
  padding: 2px 6px;
  margin: -2px -6px;
  outline: none;
}
.sortable-item__name[contenteditable="true"]:hover { background: var(--hover); }
.sortable-item__name[contenteditable="true"]:focus { background: var(--surface); box-shadow: 0 0 0 2px var(--accent-soft, var(--hover)); }

/* ─── Tab groups editor (Batch B / session 20, rebuilt mid-session) ────────
 * Each group is a self-contained card with editable label + chip-picker for
 * member tabs. Theme-first: reuses --surface / --border / --r-lg, no bespoke
 * tokens, no invented colors. Hover, focus, and chip interactions all flow
 * from the chip-picker primitive.
 */
.tab-groups {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.tab-group {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-3);
}
.tab-group__head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.tab-group__label-input {
  flex: 1 1 auto;
  font-weight: var(--fw-medium);
}

/* ─── Tooltip popup ─────────────────────────────────────────────
   Single body-level element positioned via fixed coords by initTooltips().
   Theme-stable via the inverted hero-surface tokens (always-dark across
   light themes; always-light on the dark themes). Pattern adapted from
   jacksonv.com's obi-tooltip — see app.js initTooltips() for the JS half. */
.tip-popup {
  position: fixed;
  background: var(--surface-hero);
  color: var(--ink-hero);
  padding: 4px 10px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
  z-index: 9999;
  transform: translateX(-50%);
  border-radius: var(--r-sm);
  letter-spacing: 0.01em;
}
.tip-popup::before {
  content: "";
  position: absolute;
  bottom: 100%;
  /* --tip-arrow-x is set by initTooltips() to keep the arrow pointing at the
     trigger center even when the bubble is clamped to the viewport edge. */
  left: var(--tip-arrow-x, 50%);
  transform: translateX(-50%);
  border-style: solid;
  border-width: 0 5px 5px 5px;
  border-color: transparent transparent var(--surface-hero) transparent;
}
.tip-popup--above::before {
  bottom: auto;
  top: 100%;
  border-width: 5px 5px 0 5px;
  border-color: var(--surface-hero) transparent transparent transparent;
}
.tip-popup--visible { opacity: 1; }
[data-tip] { -webkit-tap-highlight-color: transparent; }

/* ─── Confirm modal (native <dialog>) ────────────────────────────
   Replaces browser confirm(). Created lazily by confirmDialog() in app.js
   and reused across calls. Native <dialog> handles modal-ness, focus
   trap, and Esc-to-cancel. */
.modal {
  border: none;
  padding: 0;
  margin: auto;
  max-width: 460px;
  width: calc(100% - var(--sp-5) * 2);
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  font-family: inherit;
}
/* Backdrop is a neutral darken-overlay — black with alpha, not a tinted
   color. Pure achromatic, so it reads identically on every theme. */
.modal::backdrop {
  background: rgb(0 0 0 / 0.5);
  backdrop-filter: blur(1.5px);
}
.modal__inner {
  padding: var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin: 0;
}
.modal__title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  color: var(--ink);
  margin: 0;
  letter-spacing: -0.01em;
}
.modal__body {
  font-size: var(--fs-sm);
  color: var(--ink-2);
  line-height: var(--lh);
  margin: 0;
  white-space: pre-wrap;
}
.modal__checkbox {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--ink);
  cursor: pointer;
}
.modal__checkbox input[type="checkbox"] {
  margin: 0;
}
.modal__type {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  margin-top: var(--sp-1);
}
.modal__type-label {
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.modal__type-label code {
  font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  color: var(--danger);
  background: var(--danger-soft);
  padding: 1px 6px;
  border-radius: var(--r-sm);
}
.modal__actions {
  display: flex;
  gap: var(--sp-2);
  justify-content: flex-end;
  margin-top: var(--sp-2);
}
.modal__actions .btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ─── Markdown help guide (wider modal + two-column cheatsheet) ───────
   Rendered once per page by partials/markdown_help_modal.php, opened from
   any [data-markdown-help] "?" trigger via initMarkdownHelp() in app.js. */
.modal--guide {
  max-width: 560px;
}
.md-guide {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  align-items: baseline;
  gap: var(--sp-2) var(--sp-4);
  max-height: min(60vh, 520px);
  overflow-y: auto;
  margin: 0;
}
/* Section label spans both columns. */
.md-guide__group {
  grid-column: 1 / -1;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  margin-top: var(--sp-3);
  padding-bottom: var(--sp-1);
  border-bottom: 1px solid var(--border);
}
.md-guide__group:first-child { margin-top: 0; }
.md-guide__syntax {
  font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
  font-size: var(--fs-xs);
  color: var(--ink-2);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 2px 6px;
  white-space: nowrap;
  line-height: 1.7;
}
.md-guide__result {
  font-size: var(--fs-sm);
  color: var(--ink);
}
.md-guide__hint {
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.md-guide__hint code {
  font-size: var(--fs-xs);
}

.contact-assign {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
}

/* ─── Contact tag pills + multi-tag picker ──────────────────────
   Tag definitions live in app/lib/contact_tags.php. Pill styling is
   neutral by default, with primary highlighted via the locked accent
   token. Add a `.tag--<key>` rule below to color additional tags. */
.contact__tags {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-left: 6px;
  vertical-align: middle;
}
.tag {
  display: inline-flex;
  align-items: center;
  font-size: 10px;
  font-weight: var(--fw-semi);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--ink-2);
  border: 1px solid var(--border);
  white-space: nowrap;
  pointer-events: none;
}
.contact-form__tags {
  border: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.contact-form__tags legend {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  padding: 0;
  margin-bottom: var(--sp-1);
}
.contact-form__tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-sm);
  color: var(--ink);
  cursor: pointer;
}
.contact-form__tag input[type="checkbox"] { accent-color: var(--accent); }

/* Inline summary blurb on /clients/{id} — no card chrome. */
.client__summary {
  white-space: pre-wrap;
  color: var(--ink-2);
  font-size: var(--fs-sm);
  line-height: var(--lh);
  margin: 0 0 var(--sp-5);
  max-width: 70ch;
}

/* ─── Client websites (many-to-one) ─────────────────────────────
   Display list on /clients/{id} + repeating-rows form on
   /clients/{id}/edit. JS in initDomainRows() handles add/remove. */
.domain-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.domain-list__item {
  display: grid;
  grid-template-columns: minmax(120px, 200px) 1fr;
  gap: var(--sp-3);
  align-items: baseline;
  font-size: var(--fs-sm);
  padding: 4px 0;
}
.domain-list__label {
  color: var(--ink-3);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.domain-list__url {
  color: var(--accent);
  text-decoration: none;
  word-break: break-all;
}
.domain-list__url:hover { text-decoration: underline; }

.domains-fieldset {
  border: none;
  margin: var(--sp-4) 0 0;
  padding: 0;
}
.domains-fieldset legend {
  padding: 0;
  margin-bottom: var(--sp-1);
}
.domains-fieldset__hint {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  margin: 0 0 var(--sp-3);
}
.domain-rows {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.domain-row {
  display: grid;
  grid-template-columns: minmax(120px, 200px) minmax(220px, 1fr) auto;
  gap: var(--sp-2);
  align-items: center;
}
@media (max-width: 720px) {
  .domain-row { grid-template-columns: 1fr; }
}
.contact-assign .select { flex: 1; min-width: 0; }
.contacts__hint {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.contacts__hint a { color: var(--accent); }
.sortable-item__pos {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  min-width: 24px;
  font-variant-numeric: tabular-nums;
}
.sortable-item__name {
  flex: 1;
  color: var(--ink);
  min-width: 0;
}
.sortable-item__remove {
  opacity: 0;
  font-size: var(--fs-xs);
  color: var(--ink-3);
  padding: 4px 8px;
  border-radius: var(--r-sm);
  transition: opacity var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.sortable-item:hover .sortable-item__remove { opacity: 1; }
.sortable-item__remove:hover { color: var(--danger); background: var(--danger-soft); }

.add-milestone-form {
  display: flex;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}
.add-milestone-form .input { flex: 1; }

.disclaimer {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--warning);
  border-radius: var(--r-none);
  padding: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-4);
}

/* Toasts ─────────────────────────────────────────────────────── */

.toast-region {
  position: fixed;
  bottom: var(--sp-4);
  right: var(--sp-4);
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  pointer-events: none;
  max-width: 360px;
}
.toast {
  pointer-events: auto;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-top-width: 3px;
  border-radius: var(--r-none);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-sm);
  color: var(--ink);
  box-shadow: var(--shadow);
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  animation: toast-in 200ms ease;
  word-wrap: break-word;
}
.toast.is-leaving { opacity: 0; transform: translateX(8px); transition: opacity 220ms ease, transform 220ms ease; }
.toast--success { border-top-color: var(--accent); }
.toast--error   { border-top-color: var(--danger); }
.toast--info    { border-top-color: var(--ink-3); }
.toast__msg { flex: 1; min-width: 0; }
.toast__close {
  font-size: 18px;
  line-height: 1;
  color: var(--ink-3);
  padding: 0 4px;
  border-radius: var(--r-sm);
  cursor: pointer;
  flex-shrink: 0;
}
.toast__close:hover { color: var(--ink); }

@keyframes toast-in {
  from { opacity: 0; transform: translateX(16px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* Button loading state ─────────────────────────────────────── */

.btn.is-loading {
  position: relative;
  color: transparent !important;
  pointer-events: none;
}
.btn.is-loading::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  width: 14px; height: 14px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: btn-spin 600ms linear infinite;
  opacity: 0.7;
  color: var(--ink);
}
.btn--primary.is-loading::after { color: var(--ink-on-strong); }
@keyframes btn-spin {
  to { transform: rotate(360deg); }
}

/* Notifications list ─────────────────────────────────────────── */

.notif {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
  transition: background var(--t-fast);
  position: relative;
}
.notif:last-child { border-bottom: none; }
.notif:hover { background: var(--hover); }
.notif::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: transparent;
}
.notif.is-unread::before { background: var(--accent); }

.notif__avatar {
  width: 26px; height: 26px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
  color: var(--ink);
  font-size: 11px;
  font-weight: var(--fw-medium);
  border: 1.5px solid var(--actor-color, var(--border-strong));
  flex-shrink: 0;
}
/* <img> variant: same circle clip + colored ring, cover-cropped, no grey fill. */
.notif__avatar[src], img.notif__avatar { object-fit: cover; background: transparent; }
.notif__main {
  flex: 1;
  min-width: 0;
}
.notif__msg {
  font-size: var(--fs-sm);
  color: var(--ink);
}
.notif.is-unread .notif__msg { font-weight: var(--fw-medium); }
.notif__meta {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  margin-top: 2px;
}
/* Whole-row click target: the message link stretches over the entire .notif
 * (which is position:relative for the accent pip), so clicking anywhere on the
 * row opens it. The action forms sit above via z-index so they stay clickable. */
.notif__msg::after {
  content: '';
  position: absolute;
  inset: 0;
}
/* Per-row actions (mark read/unread · archive/unarchive). */
.notif__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-shrink: 0;
  position: relative;
  z-index: 1;
}
/* Announcement rows: broadcast glyph (accent-painted via currentColor) in the
 * avatar slot, on a subtly tinted, accent-ringed circle. */
.notif__avatar--broadcast {
  background: color-mix(in srgb, var(--accent) 16%, var(--surface-2));
  border-color: var(--accent);
  color: var(--accent);
}
.notif__avatar--broadcast .icon { width: 14px; height: 14px; }
/* Page back-link */
.page__back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--fs-sm);
  color: var(--ink-3);
  margin-bottom: var(--sp-3);
}
.page__back:hover { color: var(--ink); }

/* Meta panel (domains/dates/dev) ─────────────────────────────────── */

.meta-list {
  display: flex;
  flex-direction: column;
  font-size: var(--fs-sm);
}
/* Two-column ledger: muted label left, value right, hairline-separated rows. */
.meta-list > div {
  display: grid;
  grid-template-columns: 8.5rem minmax(0, 1fr);
  gap: var(--sp-1) var(--sp-3);
  align-items: baseline;
  padding: var(--sp-2) 0;
  border-top: 1px solid var(--border);
}
.meta-list > div:first-child { border-top: 0; padding-top: 0; }
.meta-list > div:last-child { padding-bottom: 0; }
.meta-list dt {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  font-weight: var(--fw-medium);
}
.meta-list dd {
  margin: 0;
  min-width: 0;
  color: var(--ink);
  word-break: break-word;
}
.meta-list dd a { color: var(--ink); border-bottom: 1px dotted var(--border-strong); }
.meta-list dd a:hover { color: var(--accent); border-color: var(--accent); }
.meta-list dd .empty-mark { color: var(--ink-disabled); }
/* Long / multi-line values (the basic-auth block, the last-milestone sentence)
   read better stacked full-width: label on its own line, value below. */
.meta-list > div.meta-list__row--stack {
  grid-template-columns: 1fr;
  gap: var(--sp-1);
}

/* Dev-site basic-auth credentials (shown under the Dev domain on a project). */
.dev-auth {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
  font-size: var(--fs-xs);
}
.dev-auth__lead {
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.dev-auth__pair { display: inline-flex; align-items: center; gap: 5px; }
.dev-auth__k { color: var(--ink-3); }
.dev-auth__v {
  font-family: var(--font-mono, monospace);
  background: var(--hover);
  padding: 1px 6px;
  border-radius: var(--r);
  user-select: all;
}
.dev-auth__copy {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-2);
  border-radius: var(--r);
  padding: 1px 7px;
  font-size: 11px;
  line-height: 1.5;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.dev-auth__copy:hover { background: var(--hover); color: var(--ink); }

.row__name {
  font-size: var(--fs);
  font-weight: var(--fw-medium);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Priority marker: a pin in the lead developer's own colour (per-user), NOT an
   amber dot — reserving --warning for genuine severity away from the name. Uses
   the .icon mask convention; colour comes from --dev-color (falls back neutral). */
.row__priority {
  display: inline-block;
  width: 12px; height: 12px;
  margin-right: 6px;
  vertical-align: -2px;
  background: var(--dev-color, var(--ink-3));
  -webkit-mask: var(--icon) center / contain no-repeat;
          mask: var(--icon) center / contain no-repeat;
}

.row__client {
  font-size: var(--fs-sm);
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.row__dev {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  min-width: 0;
}
.row__dev-avatar {
  width: 22px; height: 22px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
  color: var(--ink);
  font-size: 11px;
  font-weight: var(--fw-medium);
  flex-shrink: 0;
  border: 1.5px solid var(--dev-color, var(--border-strong));
}
.row__dev-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.row__last-note {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  white-space: nowrap;
}

.row__edit {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  padding: 4px 8px;
  border-radius: var(--r-sm);
  transition: color var(--t-fast), background var(--t-fast);
}
.row__edit:hover {
  color: var(--ink);
  background: var(--hover);
}

/* ───── FontAwesome SVG mask utility ─────────────────────────────────
   Source files in /assets/icon/*.svg are FA stock paths in a 0 0 640 640
   viewBox with no fill specified. We render them via CSS mask so the icon
   takes on the parent's `color` value (theme-cohesive). Use:
     <span class="icon" style="--icon: url(/assets/icon/grip-vertical.svg);"></span>
   Sizes default to 14px square; --icon-sm / --icon-lg override. */
.icon {
  display: inline-block;
  width: 14px;
  height: 14px;
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
          mask: var(--icon) center / contain no-repeat;
  flex: none;
}
.icon--sm { width: 12px; height: 12px; }
.icon--lg { width: 16px; height: 16px; }

/* ───── Status pills ──────────────────────────────────────────────── */

.status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  font-size: 11px;
  font-weight: var(--fw-medium);
  text-transform: capitalize;
  letter-spacing: 0.01em;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  white-space: nowrap;
}
.status::before {
  content: '';
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--status-color, var(--ink-3));
}
.status--active    { --status-color: var(--status-active); }
.status--waiting   { --status-color: var(--ink-3); }
.status--on_hold   { --status-color: var(--status-on-hold); }
.status--complete  { --status-color: var(--status-complete); }
.status--archived  { --status-color: var(--status-archived); color: var(--ink-3); }

/* ───── Milestone bar (slim) ──────────────────────────────────────── */

.mbar {
  display: flex;
  align-items: center;
  width: 100%;
  height: 18px;
  position: relative;
}

.mbar__track {
  position: absolute;
  left: 5px; right: 5px;
  top: 50%;
  height: 2px;
  background: var(--border-strong);
  transform: translateY(-50%);
  z-index: 0;
  border-radius: 1px;
}
.mbar__track-fill {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  background: var(--accent);
  border-radius: 1px;
  transition: width var(--t);
}

.mbar__nodes {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}

.mbar__node {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--bg);
  border: 1.5px solid var(--border-strong);
  flex-shrink: 0;
  transition: transform var(--t-fast), border-color var(--t-fast), background var(--t-fast);
  position: relative;
}
/* Forgiving hover hit-area: invisible ::before extends the pointer
   target ~8px vertically and ~4px horizontally beyond the visible 10px
   pip, without overlapping adjacent nodes. */
.mbar__node::before {
  content: "";
  position: absolute;
  inset: -8px -4px;
  border-radius: 50%;
}

.mbar__node--done {
  background: var(--accent);
  border-color: var(--accent);
}
.mbar__node--in_progress {
  background: var(--bg);
  border-color: var(--accent);
  border-width: 2.5px;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent);
  width: 12px;
  height: 12px;
}
.mbar__node--blocked {
  background: var(--warning);
  border-color: var(--warning);
}
.mbar__node--not_started {
  background: var(--bg);
  border-color: var(--border-strong);
}

/* When the bar is interactive (expanded row), nodes get hover affordance.
   Wired in batch 2; styling preset here so batch 2 doesn't need CSS work. */
.mbar.is-interactive .mbar__node {
  cursor: pointer;
}
.mbar.is-interactive .mbar__node:hover {
  transform: scale(1.4);
  border-color: var(--accent);
}

/* Batch-1: nodes are buttons but disabled. Native title attr handles tooltips. */
.mbar__node[disabled] { cursor: inherit; }

/* `.mbar--lg`: the enlarged interactive bar on the standalone project page —
   same machinery as the slim board pip, just a bigger click/hover target so
   click-to-jump (which rewrites every milestone's state) is comfortable. The
   pip *shape* still conveys state; no text labels (see feedback_tooltips_*). */
.mbar--lg .mbar { height: 30px; }
.mbar--lg .mbar__track { left: 8px; right: 8px; height: 3px; }
.mbar--lg .mbar__node { width: 16px; height: 16px; border-width: 2px; }
.mbar--lg .mbar__node--in_progress { width: 19px; height: 19px; border-width: 3px; }
.mbar--lg .mbar__node::before { inset: -10px -7px; }

/* Read-mode current-step caption (projects/show only — the slim collapsed bars
   on the board rows stay bare). Names the active milestone + position without
   labelling every node, keeping the bar slim per the locked rules. */
.mbar-caption {
  margin: var(--sp-2) 0 0;
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
}
.mbar-caption__state {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  font-weight: var(--fw-medium);
  white-space: nowrap;
}
.mbar-caption__name { color: var(--ink); font-weight: var(--fw-medium); }
.mbar-caption__count {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ───── Time tracker ───────────────────────────────────────────────── */

.tag-pill {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--r);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  background: var(--surface-2);
  color: var(--ink-2);
  border: 1px solid var(--border);
}
.tag-pill--muted {
  background: transparent;
  color: var(--ink-3);
}

/* (.time-pin / its row grid removed session 22 — the inline "pin a task" form
   that used them is long gone; only POST /time/pin remains. The dead
   `.time-active*` widget, `.time-section*` headings, `.time-ledger`, the
   legacy 4-col `.time-row` block, `.time-break__comment` and `.tag-row*`
   strugglers from older time-tracker iterations were also purged in Session 29
   — the current time tracker uses `.time-table` + `.time-row` further down,
   plus the time-active state lives in the nav-top widget below.) */

/* ───── Top-nav cross-page time widget ─────────────────────────────── */

.nav-top__time {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 4px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  text-decoration: none;
  margin-right: var(--sp-3);
  transition: border-color var(--t-fast), background var(--t-fast);
}
.nav-top__time:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 22%, transparent); }
.nav-top__time.is-off {
  background: transparent;
  border-color: var(--border);
  color: var(--ink-3);
}
.nav-top__time-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent);
}
.nav-top__time.is-off .nav-top__time-dot { background: var(--border-strong); }

/* Off-the-clock variant: the time widget IS the "Start Task" submit button.
   Resting state is the muted treatment of the old `● Off the clock` link
   (transparent fill, plain border, `--ink-3` text — the play `.icon` mask picks
   that up via currentColor); on hover `.nav-top__time:hover` accent-brightens the
   pill *and* this rule turns the label + play icon green (the old `<a>` got the
   green text from the global `a:hover` — a `<button>` doesn't, so it's restated
   here). Button resets on top — but `font: inherit` (needed to escape the UA
   button font) also clobbers the `.nav-top__time` `font-size: var(--fs-xs)` with
   the inherited body size, which made this pill render larger/taller than the
   split one; re-pin it. session 22 Batch C. */
.nav-top__time--start {
  appearance: none;
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-xs);
  background: transparent;
  border: 1px solid var(--border);
  color: var(--ink-3);
}
.nav-top__time--start:hover { color: var(--accent); }
.nav-top__time--start .icon { width: 12px; height: 12px; }

/* On-the-clock split pill: [ ●  0:00 · client | ▶ ] — left half links to /time
   (the live counter), right half POSTs /time/pin to start a fresh task. The
   wrapper keeps the .nav-top__time pill chrome; the halves stretch to the full
   pill height so their (identical) hover fill goes edge-to-edge, with a thin
   divider between them. */
.nav-top__time--split {
  padding: 0;
  gap: 0;
  overflow: hidden;            /* clip the halves' corners to the pill radius */
  align-items: stretch;        /* halves fill the pill height → hover fill has no gap */
}
.nav-top__time--split:hover {  /* don't brighten the whole pill — the hovered half does */
  border-color: color-mix(in srgb, var(--accent) 30%, transparent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.nav-top__time-display,
.nav-top__time-newbtn {
  display: flex;
  align-items: center;
  padding: 4px 10px;
  color: var(--ink);
  text-decoration: none;
  transition: background var(--t-fast), color var(--t-fast);
}
.nav-top__time-display { gap: var(--sp-2); }
/* Both halves brighten + go accent on hover — the `.icon` mask fill is
   `currentColor`, so the ▶ tracks the colour change too (mirrors `a:hover`
   on the left half, which is an <a> and gets it from the global `a:hover`). */
.nav-top__time-display:hover,
.nav-top__time-newbtn:hover {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--accent);
}
.nav-top__time-newbtn-form { display: flex; }
.nav-top__time-newbtn {
  appearance: none;
  cursor: pointer;
  font: inherit;
  background: transparent;
  border: 0;
  border-left: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
}
.nav-top__time-newbtn .icon { width: 11px; height: 11px; }

@media (max-width: 720px) {
  .nav-top__time { display: none; }
}

/* Hamburger trigger (mobile-only) + slide-in nav drawer ─────────────────
   Desktop: hamburger hidden, drawer parked off-screen (irrelevant).
   <720px: hamburger appears at the start of .nav-top; tapping it sets
   body.is-drawer-open → backdrop fades in + drawer slides in from the
   left edge. The drawer contains the primary nav links + a re-rendered
   copy of the current page's rail content (project list / notes
   filters / clients list). JS: app.js initNavDrawer(). */
.nav-top__hamburger {
  display: none;
  appearance: none;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r);
  color: var(--ink-2);
  cursor: pointer;
  font: inherit;
  padding: 4px 10px;
  line-height: 1;
  transition: background var(--t-fast), color var(--t-fast);
}
.nav-top__hamburger:hover { background: var(--hover); color: var(--ink); }
.nav-top__hamburger-glyph { font-size: var(--fs-lg); line-height: 1; }

.nav-drawer-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 99;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-fast);
}
.nav-drawer {
  position: fixed;
  top: 0;
  left: 0;
  width: 320px;
  max-width: 85vw;
  height: 100vh;
  background: var(--surface);
  border-right: 1px solid var(--border);
  z-index: 100;
  transform: translateX(-100%);
  transition: transform var(--t-fast);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  visibility: hidden;
}
.nav-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  position: sticky;
  top: 0;
}
.nav-drawer__title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  color: var(--ink);
}
.nav-drawer__close {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--ink-3);
  font-size: var(--fs-2xl);
  line-height: 1;
  cursor: pointer;
  padding: 4px 10px;
  border-radius: var(--r);
}
.nav-drawer__close:hover { background: var(--hover); color: var(--ink); }
.nav-drawer__nav {
  display: flex;
  flex-direction: column;
  padding: var(--sp-3) var(--sp-2);
  gap: 2px;
  border-bottom: 1px solid var(--border);
}
.nav-drawer__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  text-decoration: none;
  transition: background var(--t-fast), color var(--t-fast);
}
.nav-drawer__link:hover { background: var(--hover); color: var(--ink); }
.nav-drawer__link.is-active {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
  font-weight: var(--fw-semi);
}
.nav-drawer__rail {
  flex: 1;
  min-height: 0;
  /* The .nav-rail / .nav-rail--notes / .nav-rail--clients markup re-rendered
     inside the drawer was originally styled for the desktop grid column.
     Neutralize anything that fights a normal block child here. */
}
.nav-drawer__rail .nav-rail,
.nav-drawer__rail .nav-rail-clients {
  width: 100%;
  height: auto;
  position: static;
  border-right: 0;
  background: transparent;
}

body.is-drawer-open .nav-drawer-backdrop { opacity: 1; pointer-events: auto; }
body.is-drawer-open .nav-drawer { transform: translateX(0); visibility: visible; }
body.is-drawer-open { overflow: hidden; }  /* lock body scroll while drawer is open */

@media (max-width: 720px) {
  .nav-top__hamburger { display: inline-flex; align-items: center; justify-content: center; }
  .nav-drawer { visibility: visible; }     /* allow the transition to be visible */
}
@media (min-width: 721px) {
  /* Desktop: drawer + backdrop never render */
  .nav-drawer,
  .nav-drawer-backdrop { display: none; }
}

/* ============================================================ */
/* Notes board (homeroom-style assignable tasks)                */
/* ============================================================ */

/* Generic avatar — circle with first-letter, colored border. Used in
   kanban columns, reply threads, and the notes sidebar. */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
  border: 2px solid var(--avatar-color, var(--border-strong));
  text-transform: uppercase;
  flex-shrink: 0;
}
.avatar--sm { width: 22px; height: 22px; font-size: var(--fs-xs); border-width: 2px; }
.avatar--xs { width: 18px; height: 18px; font-size: 10px; border-width: 1.5px; }
.avatar--xl { width: 88px; height: 88px; font-size: var(--fs-3xl); border-width: 3px; }
/* Perfect-circle modifier (used by the big header avatar on /users/{id}/edit).
   Matches the .avatar base's 50% shape — the modifier exists so the markup
   reads explicitly as a circle. */
.avatar--circle { border-radius: 100%; }
/* <img> variant of .avatar: same circle clip + per-user color border. The
   uploaded image fills the circle; --avatar-color stays as the border so
   the per-user color attribution is preserved on top of the photo. */
.avatar--img { padding: 0; object-fit: cover; background: transparent; }
.row__dev-avatar[src], img.row__dev-avatar { padding: 0; object-fit: cover; }

/* Notes rail — second-style sidebar specifically for /notes */
.nav-rail--notes { padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-5); }
.nav-rail--notes .nav-rail__section { display: flex; flex-direction: column; gap: var(--sp-1); }
.nav-rail--notes .nav-rail__head {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  padding: 0 var(--sp-2) var(--sp-1);
}
.nav-rail--notes .nav-rail__list { list-style: none; padding: 0; margin: 0; }
.nav-rail--notes .nav-rail__item {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px var(--sp-2);
  border-radius: var(--r);
  color: var(--ink-2);
  font-size: var(--fs-sm);
  text-decoration: none;
  transition: background var(--t-fast);
}
.nav-rail--notes .nav-rail__item:hover { background: var(--hover); color: var(--ink); }
.nav-rail--notes .nav-rail__item.is-active { background: var(--surface-2); color: var(--ink); }
.nav-rail--notes .nav-rail__item-name { flex: 1; }
.nav-rail--notes .nav-rail__item-count {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  background: var(--bg);
  border-radius: var(--r);
  padding: 1px 8px;
  border: 1px solid var(--border);
}
.nav-rail--notes .nav-rail__team-row { display: flex; align-items: center; }
.nav-rail--notes .nav-rail__team-label {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 6px var(--sp-2);
  border-radius: var(--r);
  width: 100%;
  cursor: pointer;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  transition: background var(--t-fast);
}
.nav-rail--notes .nav-rail__team-label:hover { background: var(--hover); color: var(--ink); }
.nav-rail--notes .nav-rail__team-toggle { accent-color: var(--accent); }
.nav-rail--notes .nav-rail__filter { display: flex; flex-direction: column; gap: 4px; padding: 0 var(--sp-2); }
.nav-rail--notes .nav-rail__filter .label { padding: 0; margin: var(--sp-2) 0 2px; }

/* Composer wrap (slides open below the page header) */
.composer-wrap { margin-bottom: var(--sp-5); }
.composer-wrap .form-card { margin: 0; }

/* Kanban canvas — fixed-height slab below the page header. The kanban itself
   is a CSS Grid; each column-tile is placed at (--gx, --gy) spanning (--gw, --gh)
   cells. Saved per-viewer in notes_prefs.column_layout. Cards scroll inside
   their tile so a 30+-card column never pushes its neighbours. */
.kanban-canvas {
  /* Default fallback when not inside .main--fill — preserves the slab on
     pages that embed the kanban without flex-filling .main. */
  height: calc(100vh - var(--kanban-canvas-h, 220px));
  min-height: 480px;
  /* Self-balancing narrow-canvas: when .kanban's min-width (cols * 14rem)
     exceeds canvas width, scroll horizontally instead of crushing columns.
     Pairs with the .kanban min-width rule below. */
  overflow-x: auto;
}
.kanban {
  display: grid;
  /* minmax(0, 1fr) keeps rows + columns at exactly equal share of the canvas
     regardless of inner content. Without the 0-min, content height would
     push rows past 1fr and the canvas grows beyond viewport. Critical for
     fixed-height-canvas + scrollable cells. */
  grid-template-columns: repeat(var(--grid-cols, 6), minmax(0, 1fr));
  grid-template-rows:    repeat(var(--grid-rows, 2), minmax(0, 1fr));
  gap: var(--sp-3);
  height: 100%;
  /* Each column floors at ~14rem regardless of canvas width. Canvas wider
     than this → 1fr governs (columns share width equally). Canvas narrower
     → columns stay at 14rem and the canvas scrolls. Self-scaling with the
     user's chosen column count; no @media query needed. */
  min-width: calc(var(--grid-cols, 6) * 14rem);
}

/* When the controller opts into `.main--fill` (currently /notes), the main
   becomes a flex-column anchored to the viewport (NOT `height: 100%` —
   that requires an ancestor with a defined height, which `.app` doesn't
   reliably provide). With viewport-anchored height + overflow: hidden,
   the kanban canvas + cards-overflow chain finally clips correctly and
   per-column internal scroll fires when content exceeds cell height. */
.main--fill {
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: calc(100vh - var(--nav-h, 56px));
  overflow: hidden;
}
.main--fill > .page {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Override the global .page max-width + margin: 0 auto so the kanban
     canvas spans the entire main area regardless of viewport (4k, etc.). */
  max-width: none;
  width: 100%;
  margin: 0;
}
.main--fill .kanban-canvas {
  flex: 1;
  min-height: 0;
  height: auto;
  width: 100%;
}
.main--fill .kanban {
  width: 100%;
}

/* Compact cols/rows stepper — lives inline in .page__header-actions next to
   the Customize button. JS toggles the [hidden] attribute when customize mode
   flips. Compact so it sits unobtrusively in the header — most users set
   their grid dims once and forget. */
.kanban-toolbar {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: var(--fs-xs);
}
.kanban-toolbar[hidden] { display: none; }
.kanban-toolbar__label {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  font-weight: var(--fw-medium);
  margin: 0 4px 0 var(--sp-2);
}
.kanban-toolbar__label:first-child { margin-left: var(--sp-1); }
.kanban-toolbar__value {
  display: inline-block;
  min-width: 1.4em;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-semi);
  color: var(--ink);
}
.kanban-toolbar .btn--icon { padding: 2px 6px; min-width: 0; line-height: 1; }
.kanban-toolbar .btn--icon[disabled] { opacity: 0.4; pointer-events: none; }
.kanban__column {
  grid-column: var(--gx, 1) / span var(--gw, 1);
  grid-row:    var(--gy, 1) / span var(--gh, 1);
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--col-accent, var(--border-strong));
  border-radius: var(--r-none);
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  position: relative;
}

.kanban__col-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-3);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.kanban__col-name {
  flex: 1;
  font-weight: var(--fw-semi);
  font-size: var(--fs-sm);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.kanban__col-count {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 1px 8px;
}
.kanban__cards {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--sp-2);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.kanban__empty {
  margin: 0;
  padding: var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--ink-3);
  font-style: italic;
  text-align: center;
}

/* Customize mode — grid lines visible, drag headers grabbable, corner handles
   appear. Toggled by JS adding .is-customizing on the .kanban element. */
.kanban.is-customizing {
  background-image:
    linear-gradient(to right,  var(--border) 1px, transparent 1px),
    linear-gradient(to bottom, var(--border) 1px, transparent 1px);
  background-size:
    calc((100% - var(--sp-3) * (var(--grid-cols, 6) - 1)) / var(--grid-cols, 6) + var(--sp-3))
    calc((100% - var(--sp-3) * (var(--grid-rows, 2) - 1)) / var(--grid-rows, 2) + var(--sp-3));
}
.kanban.is-customizing .kanban__col-head { cursor: grab; user-select: none; touch-action: none; }
.kanban.is-customizing .kanban__col-head:active { cursor: grabbing; }
.kanban.is-customizing .kanban__column.is-dragging,
.kanban.is-customizing .kanban__column.is-resizing { opacity: 0.7; z-index: 2; }
.kanban.is-customizing .kanban__column.is-collide { outline: 2px dashed var(--warning); outline-offset: -2px; }

/* Bottom-right corner resize handle — only visible in customize mode. */
.kanban__col-resize-corner {
  display: none;
  position: absolute;
  bottom: 0; right: 0;
  width: 14px; height: 14px;
  cursor: se-resize;
  background: var(--accent);
  clip-path: polygon(100% 0, 100% 100%, 0 100%);
  z-index: 1;
}
.kanban.is-customizing .kanban__col-resize-corner { display: block; }
.kanban__col-resize-corner:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* While dragging or resizing, suppress text selection page-wide. */
body.is-tile-dragging,
body.is-tile-resizing { user-select: none; }
body.is-tile-resizing { cursor: se-resize; }

/* Narrow-width kanban fallback. The kanban grid has no reasonable layout below
   ~720px; three prior approaches (viewport @media, @container kanban-canvas,
   JS ResizeObserver) all had stale-state issues on resize-back-to-desktop.
   Solution: dual-render. The controller always passes $tasks_flat; the view
   renders both the .kanban-canvas AND a .notes-narrow-fallback (flat list)
   in the same response. CSS picks one based on viewport. Pure CSS = no state
   to go stale, reverts cleanly on window resize. See plan
   .claude/plans/zesty-squishing-zebra.md and Session-30 changelog. */
.notes-narrow-fallback { display: none; }
.notes-narrow-fallback__hint {
  font-size: var(--fs-sm);
  color: var(--ink-3);
  margin: 0 0 var(--sp-3);
}
@media (max-width: 720px) {
  .kanban-canvas { display: none; }
  .notes-narrow-fallback { display: block; }
  /* Hide kanban-only chrome — Customize button, grid stepper, and the
     "Noteboard" layout toggle (which would render nothing usable at this
     width). The "List" toggle stays so the user knows they're on a list. */
  .page__header [data-customize-toggle],
  .page__header [data-kanban-toolbar],
  .page__header [data-layout="board"] {
    display: none;
  }
}

/* Sortable.js helpers — cards (existing) + columns (new) */
.notes-card.sortable-ghost { opacity: 0.4; }
.notes-card.sortable-chosen { box-shadow: var(--shadow); }
.kanban__column.sortable-ghost { opacity: 0.4; }
.kanban__column.sortable-chosen { box-shadow: var(--shadow); }

/* Notes card — compact: title (click to open) + eye + timer + drag handle. The card itself
   is NOT clickable — only its interactive children. Title button + eye icon both open the
   slide-in note panel; the timer icon kicks off a /time timer pre-filled from this note. */
.notes-card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: var(--sp-2) var(--sp-3);
  transition: border-color var(--t-fast), background var(--t-fast);
}
.notes-card:hover { border-color: var(--border-strong); }
.notes-card.is-complete { opacity: 0.65; }
.notes-card.is-complete .notes-card__title-btn { text-decoration: line-through; }

/* Due-date meta line under the title — small-caps relative wording (TODAY,
   TOMORROW, IN N DAYS); overdue items show a negative day count (-2 DAYS)
   in --warning. Suppressed for completed cards. */
/* The due line is a single <time data-fmt="due"> filled by app.js as
   "TOMORROW · MAY 14" (or just "MAY 14" past 7 days). The card root carries
   data-due-host so the scanner hangs an is-<severity> class here. */
.notes-card__due {
  margin: 4px 0 0;
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  color: var(--ink-3);
}
/* One strong scale, tool-wide: overdue=danger, today/soon=warning, future=neutral. */
.notes-card.is-future  .notes-card__due { color: var(--ink-3); }
.notes-card.is-today   .notes-card__due,
.notes-card.is-soon    .notes-card__due { color: var(--warning); font-weight: var(--fw-semi); }
.notes-card.is-overdue .notes-card__due { color: var(--danger); font-weight: var(--fw-semi); }
.notes-card.is-complete .notes-card__due,
.notes-card.is-none     .notes-card__due { display: none; }

/* Client · Project context line under the title (effective client derived from
   the task or its project). Wraps rather than truncates — the point is to make
   the context fully visible, not hide it behind a tooltip. */
.notes-card__meta {
  margin: 4px 0 0;
  font-size: var(--fs-xs);
  line-height: 1.35;
  color: var(--ink-3);
}
/* "from <sender>" — who assigned the note. */
.notes-card__from {
  margin: 2px 0 0;
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
/* Note body preview — the actual note text, clamped to two lines. */
.notes-card__body {
  margin: 6px 0 0;
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--ink-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Footer row: due (left) + comment count (right). */
.notes-card__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-top: 6px;
}
.notes-card__foot .notes-card__due { margin: 0; }
.notes-card__replies {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  white-space: nowrap;
}

.notes-card__head {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: var(--sp-2);
}
.notes-card__title {
  margin: 0;
  min-width: 0;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  color: var(--ink);
}
.notes-card__title-btn {
  appearance: none;
  border: 0;
  background: transparent;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-decoration: none;
  text-align: left;
  cursor: pointer;
  word-break: break-word;
  display: block;
  width: 100%;
}
.notes-card__title-btn:hover { color: var(--accent); }
.notes-card__title-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}
.notes-card__icon,
.notes-card__handle {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--ink-3);
  padding: 4px 6px;
  border-radius: var(--r);
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 0;
}
.notes-card__icon { cursor: pointer; }
.notes-card__handle { cursor: grab; touch-action: none; user-select: none; }
.notes-card__icon:hover,
.notes-card__handle:hover { color: var(--ink); background: var(--hover); }
.notes-card__handle:active { cursor: grabbing; }

/* Tag pill — muted variant for project (alongside the existing .tag-pill from time tracker) */
.tag-pill--muted {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--ink-3);
}

/* Reply thread inside an expanded card */
.notes-thread {
  display: flex; flex-direction: column; gap: var(--sp-2);
  border-top: 1px solid var(--border);
  padding-top: var(--sp-3);
}
.notes-thread__head {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-3);
}
.notes-thread__list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.notes-thread__item {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-none);
  padding: var(--sp-2);
  font-size: var(--fs-sm);
}
.notes-thread__item-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: 4px; }
.notes-thread__author { font-weight: var(--fw-medium); color: var(--ink); font-size: var(--fs-xs); }
.notes-thread__when { font-size: var(--fs-xs); color: var(--ink-3); margin-left: auto; }
.notes-thread__body { color: var(--ink-2); line-height: var(--lh); white-space: pre-wrap; word-break: break-word; }
.notes-thread__actions { display: flex; gap: 4px; margin-top: 4px; }
.notes-thread__form {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--sp-2);
  align-items: end;
}
.btn--xs {
  padding: 2px 8px;
  font-size: 11px;
  height: 22px;
  min-height: 22px;
}
.btn--danger { color: var(--danger); }
.textarea--sm { min-height: 60px; font-size: var(--fs-sm); padding: 6px 10px; }
.select--sm { padding: 6px 10px; font-size: var(--fs-sm); height: 32px; }

/* Form-grid 2-col / 3-col for the composer */
.form-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.form-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.field--span-2 { grid-column: span 2; }
.field--span-3 { grid-column: span 3; }
@media (max-width: 720px) {
  .form-grid--2,
  .form-grid--3 { grid-template-columns: 1fr; }
  .form-grid--2 .field--span-2,
  .form-grid--3 .field--span-2,
  .form-grid--3 .field--span-3 { grid-column: auto; }
}

/* Home dashboard — responsive grid that holds .dash-widget panels.
   Auto-fits 2 columns when there's room; collapses to 1 when not. */
.home-grid {
  display: grid;
  /* `min(360px, 100%)` lets the min-floor shrink to viewport width on phones —
     without it, sub-360px viewports overflow horizontally because grid columns
     can't shrink below their minmax floor. Still triggers the 2-col auto-fit
     above ~720px (360*2 + gap). */
  grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr));
  gap: var(--sp-4);
  margin-top: var(--sp-4);
}

/* Generic dashboard widget — used by notes-assigned + notifications + future
   home widgets. The widget itself is a floater bauble (rounded card per the
   radius philosophy); the rows inside are dense info (no internal radius). */
.dash-widget {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-3);
  min-width: 0;
}
.dash-widget__head {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  min-width: 0;
}
.dash-widget__head strong { color: var(--ink); font-weight: var(--fw-semi); }
.dash-widget__count {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.dash-widget__see-all {
  margin-left: auto;
  font-size: var(--fs-xs);
  color: var(--ink-3);
  text-decoration: none;
  white-space: nowrap;
}
.dash-widget__see-all:hover { color: var(--accent); }
.dash-widget__empty {
  margin: 0;
  padding: var(--sp-2) 0;
  font-size: var(--fs-sm);
  color: var(--ink-3);
}
.dash-widget__list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
}
.dash-widget__list li + li { border-top: 1px solid var(--border); }
.dash-widget__item {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--sp-2);
  align-items: center;
  padding: 8px var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  text-decoration: none;
  min-width: 0;
}
.dash-widget__item:hover { background: var(--hover); color: var(--ink); }
.dash-widget__item--notif {
  grid-template-columns: auto 1fr auto;
}
.dash-widget__title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.dash-widget__meta {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.dash-widget__meta.is-overdue { color: var(--danger); font-weight: var(--fw-medium); }
.dash-widget__meta.is-soon,
.dash-widget__meta.is-today { color: var(--warning); font-weight: var(--fw-medium); }

/* Slide-in note panel — opens from the right when a kanban card is clicked.
   Two-tier: read view (default) + edit view, swap inline. Deep-linked via /notes#task-{id}. */
.note-panel-backdrop {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.32);
  z-index: 49;
  opacity: 0;
  transition: opacity var(--t-mid);
}
.note-panel-backdrop.is-open { opacity: 1; }

.note-panel {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: min(560px, 100vw);
  background: var(--surface);
  border-left: 1px solid var(--border);
  box-shadow: -4px 0 18px rgba(0, 0, 0, 0.18);
  z-index: 50;
  display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform var(--t-mid);
}
.note-panel.is-open { transform: translateX(0); }

.note-panel__head {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: start;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border);
}
.note-panel__title {
  margin: 0;
  font-size: var(--fs-xl);
  font-weight: var(--fw-semi);
  line-height: var(--lh-tight);
  color: var(--ink);
  word-break: break-word;
}
.note-panel__close {
  appearance: none;
  border: 0; background: transparent;
  color: var(--ink-3);
  font-size: 22px; line-height: 1;
  width: 32px; height: 32px;
  border-radius: var(--r);
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.note-panel__close:hover { color: var(--ink); background: var(--hover); }

.note-panel__body {
  flex: 1;
  overflow-y: auto;
  padding: var(--sp-4) var(--sp-5);
  display: flex; flex-direction: column; gap: var(--sp-4);
}

.note-panel__meta {
  display: flex; flex-wrap: wrap; gap: 6px;
  align-items: center;
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.note-panel__meta .tag-pill { color: var(--ink-2); }
.note-panel__meta .note-panel__due { color: var(--ink-2); }
.note-panel__meta .note-panel__due.is-overdue { color: var(--danger); font-weight: var(--fw-medium); }

/* The note's own text (shared: single-note page body + slide-in panel comment). */
.note-detail__description {
  font-size: var(--fs-md);
  line-height: var(--lh);
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
}

/* Properties grid — used by the slide-in note panel's property block
   (`note-panel__props`); collapses to a single column in the narrow panel. */
.note-props {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--sp-3) var(--sp-5);
  margin: 0;
}
.note-props__item dt {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  font-weight: var(--fw-medium);
  margin-bottom: 3px;
}
.note-props__item dd { margin: 0; color: var(--ink); word-break: break-word; }
.note-props__item dd a { color: var(--ink); border-bottom: 1px dotted var(--border-strong); }

/* Inline avatar + name (Assigned-to / From rows, single-note page + panel). */
.note-user { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }

/* ── Single-note page (/notes/{id}) — GitHub/Linear issue-detail layout ──────
   A wide main column (note body + comment thread, each a contained .form-card)
   beside a bounded sticky rail (.form-card) holding actions + metadata. The
   cards are the point: a sparse note reads as a framed, intentional detail. */
.note-show {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 304px;
  gap: var(--sp-5);
  align-items: start;
  margin-top: var(--sp-2);
}
.note-show__main { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.note-show__rail { position: sticky; top: var(--sp-4); display: flex; flex-direction: column; }

/* Edit form occupies the same horizontal column as the show page's note body
   (left-aligned, capped) so show ↔ edit read as one continuous note rather
   than two different layouts. */
.note-edit-form { max-width: 840px; margin-top: var(--sp-2); }

/* Card section heading — small muted eyebrow over the body/comments. */
.note-show__block-title {
  margin: 0 0 var(--sp-4);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  display: flex; align-items: center; gap: var(--sp-2);
}
.note-show__count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: 999px;
  background: var(--surface-2); color: var(--ink-2);
  font-size: var(--fs-xs); font-weight: var(--fw-semi); letter-spacing: 0;
}
/* Empty-state line inside a card — quiet, never a centered void. */
.note-show__placeholder { margin: 0; font-size: var(--fs-sm); color: var(--ink-3); }

/* Comment thread inside the Comments card — same bordered-entry treatment as
   the slide-in panel (keeps the two surfaces consistent), with a touch more
   padding to suit the wider page. */
.note-show__comments { gap: var(--sp-2); }
.note-show__comments .notes-thread__item { padding: var(--sp-3); }
.note-show__comments .notes-thread__item-head { margin-bottom: var(--sp-1); }
.note-show .note-show__composer { margin-top: var(--sp-4); }

/* Inline quick-reassign control (Assigned-to row, shared by page + panel):
   avatar of the current assignee + a select that saves the moment it changes.
   No Save button — reassigning is a quick action, not an edit. */
.note-show__assign { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.note-show__assign .select { flex: 1; min-width: 0; }

/* Due-date urgency on the note detail + panel (stronger than the muted board
   card treatment): overdue = danger/bold, due-soon/today = warning/semibold.
   Severity class is set by scanTimes() on the [data-due-host] (page) or written
   directly onto the .note-due span by the panel JS. */
.note-due-inline.is-overdue,
.note-due.is-overdue,
.note-show__props > div[data-due-host].is-overdue dd { color: var(--danger); font-weight: var(--fw-semi); }
.note-due-inline.is-soon, .note-due-inline.is-today,
.note-due.is-soon, .note-due.is-today,
.note-show__props > div[data-due-host].is-soon dd,
.note-show__props > div[data-due-host].is-today dd { color: var(--warning); font-weight: var(--fw-semi); }

/* Inline comment-edit form (page + panel): textarea + Save/Cancel under a reply. */
.notes-thread__edit { margin-top: var(--sp-2); display: flex; flex-direction: column; gap: var(--sp-2); }
.notes-thread__edit .textarea { width: 100%; }
.notes-thread__edit-actions { display: flex; align-items: center; gap: var(--sp-3); }

/* Rail card: actions block, hairline dividers, metadata, foot remove. */
.note-show__rail-card { padding: var(--sp-4); }
.note-show__actions { display: flex; flex-direction: column; gap: var(--sp-2); }
.note-show__actions > form,
.note-show__actions > a { display: block; }
.note-show__actions .btn { width: 100%; }
.note-show__actions-row { display: flex; gap: var(--sp-2); }
.note-show__actions-row > * { flex: 1; }
.note-show__rail-divider { border: 0; border-top: 1px solid var(--border); margin: var(--sp-4) 0; }

.note-show__props { display: flex; flex-direction: column; gap: 0; margin: 0; font-size: var(--fs-sm); }
.note-show__props > div {
  display: flex; flex-direction: column; gap: 3px;
  padding: var(--sp-2) 0;
  border-top: 1px solid var(--border);
}
.note-show__props > div:first-child { border-top: 0; padding-top: 0; }
.note-show__props > div:last-child { padding-bottom: 0; }
.note-show__props dt {
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink-3); font-weight: var(--fw-medium);
}
.note-show__props dd { margin: 0; min-width: 0; color: var(--ink); word-break: break-word; }
.note-show__props dd a { color: var(--ink); border-bottom: 1px dotted var(--border-strong); }
.note-show__props dd a:hover { color: var(--accent); border-color: var(--accent); }

@media (max-width: 900px) {
  .note-show { grid-template-columns: 1fr; }
  .note-show__rail { position: static; }
}

/* Comment composer (shared: single-note page + slide-in panel). */
.note-show__composer { display: block; }
.note-show__composer .textarea { width: 100%; }
.note-show__composer-actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-2); margin-top: var(--sp-2); }

.note-panel__comment {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  line-height: var(--lh);
  white-space: pre-wrap;
  word-break: break-word;
}

.note-panel__info {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.note-panel__info span + span::before { content: ' · '; color: var(--ink-3); }

/* Slide-in panel mirrors the /notes/{id} page: a title + subtitle head, then
   framed Note / Comments / Details sections stacked single-column, with the
   primary actions as a top bar. Reuses the page vocabulary (.note-show__block-title,
   .note-show__props, .note-detail__description, .notes-thread) verbatim. */
.note-panel__head-main { min-width: 0; }
.note-panel__subtitle {
  margin: var(--sp-1) 0 0;
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  font-size: var(--fs-sm); color: var(--ink-3);
}
.note-panel__section { padding: var(--sp-4); }
.note-panel__section + .note-panel__section { margin-top: 0; }
.note-panel__props { margin-top: 0; }
.note-panel__rail-foot {
  margin-top: var(--sp-4); padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
}

/* Primary action bar (Start task / Complete / Edit) — sits at the top of the
   panel body, no divider needed. Delete moves to the Details section foot. */
.note-panel__actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; }
.note-panel__edit-form { display: flex; flex-direction: column; gap: var(--sp-3); }

@media (max-width: 720px) {
  .note-panel__head { padding: var(--sp-3) var(--sp-4); }
  .note-panel__body { padding: var(--sp-3) var(--sp-4); }
}

/* ============================================================ */
/* Hero / inset surfaces — the always-darker inverted panel    */
/* and its always-lighter nested panel. Reproduces the xAI     */
/* "Usage Snapshot" pattern: a dark hero card on any theme    */
/* with a light inset block nested inside it.                  */
/* ============================================================ */

.surface--hero {
  background: var(--surface-hero);
  color: var(--ink-hero);
  border-radius: var(--r-xl);
  padding: var(--sp-6);
}
.surface--hero h1,
.surface--hero h2,
.surface--hero h3 { color: var(--ink-hero); }
.surface--hero a { color: var(--ink-hero); }

.surface--inset {
  background: var(--surface-inset);
  color: var(--ink-inset);
  border-radius: var(--r-lg);
  padding: var(--sp-4);
}
.surface--inset h1,
.surface--inset h2,
.surface--inset h3 { color: var(--ink-inset); }

/* ─── Planner status / platform pills + status pipeline (Batch 5-A.5) ──── */

/* Generic status pill, used by /social and /seo-content list rows + detail
   page subtitles. Same shape as .urgency-pill was — small rounded chip. */
.status-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  border-radius: var(--r);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  line-height: 1.5;
}
.status-pill--draft {
  background: var(--surface-2);
  color: var(--ink-3);
  border: 1px solid var(--border);
}
.status-pill--in_review {
  background: var(--warning-soft);
  color: var(--warning);
}
.status-pill--approved {
  background: var(--accent-soft);
  color: var(--accent);
}
.status-pill--scheduled {
  background: var(--surface-2);
  color: var(--ink-2);
  border: 1px solid var(--border-strong);
}
/* Saturated bg → must use --ink-on-strong (white) per the locked rule. */
.status-pill--published,
.status-pill--posted {
  background: var(--accent);
  color: var(--ink-on-strong);
}

/* Platform pill — desaturated brand-ish chip on social-post rows. */
.platform-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: var(--r);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  background: var(--surface-2);
  color: var(--ink-2);
  border: 1px solid var(--border);
  white-space: nowrap;
  line-height: 1.5;
}
/* No per-platform color variants. The platform name itself is the
   differentiator; brand-hex injection bypasses the theme system. */

/* Target-keyword pill — small monospace chip on /seo-content rows + detail. */
.kw-pill {
  display: inline-block;
  padding: 1px 6px;
  border-radius: var(--r);
  font-size: var(--fs-xs);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  background: color-mix(in srgb, var(--ink-3) 12%, transparent);
  color: var(--ink-2);
  margin-left: var(--sp-2);
  white-space: nowrap;
}

/* Status-pipeline row — 5 buttons for advancing through draft → published.
   Active button uses --accent text + filled background. Click POSTs the new
   status via initStatusPipeline() in app.js. */
.status-pipeline {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  flex-wrap: wrap;
}
.status-pipeline__label {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-right: var(--sp-2);
}
/* At narrow widths force every label to start a fresh row in the flex-wrap
   layout — otherwise the second "REPORTING:" label (brief detail when status
   = published) sneaks into the middle of the status-button row and visually
   breaks the pipeline metaphor. */
@media (max-width: 720px) {
  .status-pipeline__label { flex-basis: 100%; margin-right: 0; }
}
.status-pipeline .btn.is-active {
  background: var(--accent);
  color: var(--ink-on-strong);
  border-color: var(--accent);
}

/* Overdue scheduled date — used on list rows to flag past-due drafts. */
.list__row-meta.is-overdue { color: var(--danger); font-weight: var(--fw-medium); }
.list__row-meta.is-soon, .list__row-meta.is-today { color: var(--warning); font-weight: var(--fw-medium); }

/* ───── Time tracker (Batch 5-A.6) ───────────────────────────────────────
   The time tracker port preserves the legacy SilverServers UX:
   permanent header band (timer + local clock + shift buttons), date nav,
   3-col entries table, inline edit form. Buttons reuse the only two
   variants the app has: .btn--primary (accent) for add/new actions
   (Start Task, + Add New Entry, Save), .btn (default muted) for
   reversible/secondary actions (Break, End Shift, Cancel). .btn--danger
   is reserved for truly destructive actions (Delete). */

/* ── Header band — sits inside .main below the top nav (no negative-margin
   trick; that overlaps the top nav). The band is a rounded floater card
   (--r-lg per the locked radius rule) holding the running timer + local
   clock + fullscreen icon. The 3 shift buttons (.time-band-actions) live
   *below* the band in Standard layout and *inside* it (as a grid cell) in
   Compact layout — see "Time page modes" further down. The 2-item Standard
   band leaves the 3rd grid column empty; the 3-item Compact band fills it. */
.time-band {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  margin-bottom: var(--sp-3);
}
.time-band__fullscreen {
  /* Sits in the last grid cell. Visually a sibling to the action buttons. */
  color: var(--ink-2);
}
.time-band__fullscreen:hover {
  color: var(--ink);
}
.time-band__readout {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-4);
  flex-wrap: wrap;
}
.time-band__timer {
  font-size: 28px;
  font-weight: var(--fw-semi);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.time-band__meta {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-2);
}
.time-band__meta-label {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.time-band__meta-value {
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}

/* Shift-action buttons: Start Task (.btn--primary) + Break + End Shift (.btn).
   Markup is the same in both layouts (partial: time_band_buttons.php) — only
   placement differs. */
/* Shift segmented control: Start Task · Break · End Shift as ONE visual pill of
   three connected segments (each its own form/POST). Outer pill = container
   border + radius; each end segment rounds its own corner (no overflow:hidden,
   so focus outlines aren't clipped); a hairline divides adjacent segments. */
.time-band-actions {
  display: inline-flex;
  align-items: stretch;
  flex-wrap: nowrap;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--surface);
}
.time-band-actions form { display: flex; margin: 0; }
.time-band-actions form + form .shift-btn { border-left: 1px solid var(--border); }
.shift-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 9px 16px;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  color: var(--ink-2);
  background: transparent;
  border: 0;
  transition: background var(--t-fast), color var(--t-fast);
}
.time-band-actions form:first-child .shift-btn {
  border-top-left-radius: 999px;
  border-bottom-left-radius: 999px;
  padding-left: 18px;
}
.time-band-actions form:last-child .shift-btn {
  border-top-right-radius: 999px;
  border-bottom-right-radius: 999px;
  padding-right: 18px;
}
.shift-btn:hover { background: var(--surface-2); color: var(--ink); }
.shift-btn .icon { width: 13px; height: 13px; }
/* Start segment — reflects the standalone `.btn--start` (style 11): transparent
   at rest, the accent label + play glyph mark it as the "go" segment; fills to a
   light accent tint on hover (the outer pill border stands in for the outline). */
.shift-btn--start { color: var(--accent); background: transparent; }
.shift-btn--start:hover { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
/* Standard layout: own strip below the band (direct child of .time-page). */
.time-page:not(.time-page--compact) > .time-band-actions {
  margin: 0 0 var(--sp-3);
}
@media (max-width: 720px) {
  .time-band {
    grid-template-columns: 1fr auto;
    grid-template-rows: auto auto;
    gap: var(--sp-3);
    padding: var(--sp-3);
  }
  /* Stack: readout + fullscreen on the top row (icon stays accessible).
     When the actions row is *inside* the band (Compact), span it across the
     full second row; when it's a strip below (Standard) this rule is inert. */
  .time-band__readout    { grid-column: 1 / 2; grid-row: 1; gap: var(--sp-3); flex-wrap: wrap; }
  .time-band__fullscreen { grid-column: 2 / 3; grid-row: 1; }
  .time-band > .time-band-actions { grid-column: 1 / -1; grid-row: 2; }
  .time-band__timer { font-size: 28px; }
}

/* ── Date nav + action row — Today / Yesterday / Mon, May 4 on the left,
   page actions (e.g. + Add New Entry) on the right. One strip; no second
   toolbar band. */
.time-datenav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  /* Wrap the right-hand actions cluster below the date when the row gets
     too narrow (1/3-width desktop) instead of overflowing. */
  flex-wrap: wrap;
  row-gap: var(--sp-2);
  padding: var(--sp-2) 0;
  margin-bottom: var(--sp-2);
}
.time-datenav__nav {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.time-datenav__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.time-datenav__date {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semi);
  letter-spacing: -0.01em;
  color: var(--ink);
  min-width: 180px;
}
.time-datenav__date-extra {
  font-weight: var(--fw-regular);
  color: var(--ink-3);
  margin-left: var(--sp-1);
}
.time-datenav__arrow {
  font-size: var(--fs-md);
  color: var(--ink-3);
  text-decoration: none;
  padding: 2px 10px;
  border-radius: var(--r);
  line-height: 1;
}
.time-datenav__arrow:hover { background: var(--hover); color: var(--ink); }
.time-datenav__today {
  margin-left: var(--sp-2);
}

/* ── Main entries table — sharp (--r-none) per the locked rule.
   No `overflow: hidden`: the table corners aren't rounded so there's
   nothing to clip, and clipping would hide the tag-picker dropdown inside
   an open .time-edit form when the table is short. */
.time-table {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--r-none);
  background: var(--surface);
}
.time-table__head {
  display: grid;
  /* Floors let the table shrink gracefully through 1/2- and 1/3-width
     desktop widths without overflowing: client truncates past 140px, tags
     keep room for a couple of pills, time can drop to 130px (still fits the
     "H:MM AM → H:MM AM" range). fr ratios unchanged → wide-width is identical. */
  grid-template-columns: minmax(140px, 2.4fr) minmax(80px, 1fr) minmax(130px, 160px);
  gap: var(--sp-3);
  padding: 10px var(--sp-4);
  background: var(--surface-2);
  color: var(--ink-3);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border-bottom: 1px solid var(--border);
}
.time-table__head > div { text-align: left; }
.time-table__head > div:nth-child(2) { text-align: center; }  /* over the centered tag pills */
.time-table__head > div:nth-child(3) { text-align: right; padding-right: var(--sp-2); }
.time-table__empty {
  padding: var(--sp-5) var(--sp-4);
  text-align: center;
  color: var(--ink-3);
  font-size: var(--fs-sm);
}

/* Phone-mobile (<500px): stack client on top, tags-and-time as a second row.
   Keeps every entry's data visible (no hidden columns) while fitting in 360px. */
@media (max-width: 500px) {
  .time-table__head,
  .time-row {
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "client client"
      "tags   time";
    row-gap: var(--sp-2);
  }
  .time-table__head > div:nth-child(1) { grid-area: client; }
  .time-table__head > div:nth-child(2) { grid-area: tags; text-align: left; }
  .time-table__head > div:nth-child(3) { grid-area: time; padding-right: 0; }
  .time-row__client { grid-area: client; }
  .time-row__tags   { grid-area: tags; }
  .time-row__time   { grid-area: time; }
}

/* ── Entry rows */
.time-row {
  display: grid;
  /* Must match .time-table__head exactly (the header cells label these). */
  grid-template-columns: minmax(140px, 2.4fr) minmax(80px, 1fr) minmax(130px, 160px);
  gap: var(--sp-3);
  align-items: center;
  padding: 10px var(--sp-4);
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  transition: background var(--t-fast);
}
.time-row:last-child { border-bottom: 0; }
.time-row:hover { background: var(--hover); }
.time-row.is-active {
  border-top: 2px solid var(--accent);
  margin-top: -2px;
}
.time-row--break       { background: var(--surface-2); }
.time-row--end         { background: var(--surface-2); }
.time-row--off_sick    { background: var(--warning-soft); }
.time-row--on_vacation { background: var(--surface-2); }

.time-row__client {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
}
.time-row__client-globe {
  display: inline-flex;
  align-items: center;
  width: 14px;
  height: 14px;
  color: var(--ink-3);
  flex: 0 0 14px;
}
.time-row__client-name {
  font-weight: var(--fw-medium);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.time-row__client-sub {
  display: block;
  font-size: var(--fs-xs);
  color: var(--ink-3);
  font-weight: var(--fw-regular);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Client/project names on a ledger row link to their detail pages; keep the
   row's text color, add a hover underline as the only affordance. */
a.time-row__client-name, a.time-row__client-sub { text-decoration: none; }
a.time-row__client-name:hover, a.time-row__client-sub:hover { text-decoration: underline; }
.time-row__client-clone {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 4px;
  margin-left: auto;
  color: var(--ink-3);
  cursor: pointer;
  border-radius: var(--r-sm);
  /* Always visible (the muted --ink-3 colour carries the de-emphasis) — it
     used to fade in on row-hover, which hid a useful one-click action. */
  transition: color var(--t-fast), background var(--t-fast);
}
.time-row__client-clone:hover {
  color: var(--ink);
  background: var(--hover);
}

.time-row__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  align-items: center;
  justify-content: center;
}

.time-row__time {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  text-align: right;
}
.time-row__dur {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-semi);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  letter-spacing: -0.02em;
}
.time-row__dur--lg {
  font-size: 28px;
}
.time-row:not(.is-active) {
  padding-block: var(--sp-2);
}
.time-row:not(.is-active) .time-row__dur {
  font-size: var(--fs-xl);
}
.time-row__time-range {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ── Inline edit/add form.
   Two contexts, one class:
   • The standalone "Add new entry" form (direct child of .time-page, above
     the table) — a surface-2 card; full content width, bottom margin only.
   • The per-row edit form (a child of .time-table, expands below its row) —
     NOT a card-in-a-panel: it sits flush to the table edges, no border/
     radius/side-margin, set apart from the rows only by the surface-2 tint
     and a closing bottom border. See `.time-table .time-edit` below. */
.time-edit {
  display: none;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  margin: 0 0 var(--sp-4);
  padding: var(--sp-4);
}
.time-table .time-edit {
  margin: 0;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
}
.time-row.is-expanded + .time-edit,
.time-edit.is-open { display: block; }
/* 3-column layout (task2time parity): col1 Event Type + Details, col2 Project
   + Tags, col3 Timeline Position + (edit) warning + the action buttons.
   col3 stretches to the row height so its action row can pin to the bottom
   (margin-top:auto), aligning the buttons with the bottom of the tall left
   column instead of floating mid-panel. */
.time-edit__grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: var(--sp-4);
  align-items: start;
}
.time-edit__col--right { align-self: stretch; }
@media (max-width: 880px) {
  .time-edit__grid { grid-template-columns: 1fr 1fr; }
  .time-edit__col--right { grid-column: 1 / -1; }
}
@media (max-width: 720px) {
  .time-edit__grid { grid-template-columns: 1fr; }
  .time-edit__col--right { grid-column: auto; }
}
.time-edit__col {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
}
.time-edit__field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.time-edit__label {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: var(--fw-semi);
}
/* Theme-neutral note (was amber --warning, which read the same yellow on every
   theme). Uses surface/ink/border tokens so it adapts per theme; --warning is
   reserved for genuine severity away from this dense editor context. */
.time-edit__warning {
  background: var(--surface-2);
  border-top: 3px solid var(--border-strong);
  border-radius: var(--r-none);
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--ink-2);
  margin-top: var(--sp-2);
}
/* Action row sits at the BOTTOM of col 3 (margin-top:auto), not a full-width
   footer bar — col 3 stretches to the row height via .time-edit__col--right. */
.time-edit__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: auto;
}

/* ── Time page modes (session 22 Batch A) ───────────────────────────────
   The /time page renders inside `.time-page`, optionally with `--compact`.
   Standard (default, no `--compact`): "large at-a-glance" — every entry row
   is the same height with the same big duration; the running entry is set
   apart only by its accent top-bar and a slightly larger client name; the
   shift buttons sit on their own strip below the band.
   Compact (`.time-page--compact`): today's denser hybrid, lightly trimmed —
   the biggest numbers come down one step and the running-timer row tightens;
   the shift buttons stay inside the band. Per-user toggle: users.time_prefs
   (default off). No new color tokens; sizes use existing --fs-* tokens. */

/* Standard: uniform rows. Undo session-19's past-row shrink (padding +
   duration) so every row matches; neutralise the running row's extra
   28px bump so all durations are --fs-2xl; bump only the running row's
   client name. */
.time-page:not(.time-page--compact) .time-row:not(.is-active) { padding-block: 10px; }
.time-page:not(.time-page--compact) .time-row:not(.is-active) .time-row__dur { font-size: var(--fs-2xl); }
.time-page:not(.time-page--compact) .time-row__dur--lg { font-size: var(--fs-2xl); }

/* Standard: the "big at-a-glance" typography (to-do Time Tracker #5). The
   task2time reference is at 100% scale — its body text is ~16px, client
   names / table headers ~18px, durations ~26px, the band timer ~40px. We
   lift the page's base to --fs-lg and bump every explicitly-sized descendant
   to match that scale. Compact is untouched — only `:not(.time-page--compact)`
   is targeted. Completed-row durations were already --fs-2xl above (the locked
   "uniform big durations" decision); row padding stays. */
.time-page:not(.time-page--compact) { font-size: var(--fs-lg); }
.time-page:not(.time-page--compact) .time-band__timer { font-size: var(--fs-4xl); }
.time-page:not(.time-page--compact) .time-band__meta-label { font-size: var(--fs-sm); }
.time-page:not(.time-page--compact) .time-band__meta-value { font-size: var(--fs-xl); }
.time-page:not(.time-page--compact) .time-band-actions .shift-btn { font-size: var(--fs-md); padding: 11px 18px; }
.time-page:not(.time-page--compact) .time-datenav__date { font-size: var(--fs-2xl); }
.time-page:not(.time-page--compact) .time-datenav__arrow { font-size: var(--fs-xl); }
.time-page:not(.time-page--compact) .time-table__head { font-size: var(--fs-lg); }
.time-page:not(.time-page--compact) .time-table__empty { font-size: var(--fs-lg); }
.time-page:not(.time-page--compact) .time-row__client-name { font-size: var(--fs-xl); }
.time-page:not(.time-page--compact) .time-row__client-sub { font-size: var(--fs-md); }
.time-page:not(.time-page--compact) .time-row__time-range { font-size: var(--fs-md); }
.time-page:not(.time-page--compact) .time-row__tags .tag-pill { font-size: var(--fs-md); }
.time-page:not(.time-page--compact) .time-edit :is(.input, .select, .textarea) { font-size: var(--fs-lg); }
.time-page:not(.time-page--compact) .time-edit .btn { font-size: var(--fs-md); }
.time-page:not(.time-page--compact) .time-edit__label,
.time-page:not(.time-page--compact) .time-edit .chip-picker .label { font-size: var(--fs-sm); }
.time-page:not(.time-page--compact) .time-edit .chip-picker__search { font-size: var(--fs-sm); }
.time-page:not(.time-page--compact) .time-edit__warning { font-size: var(--fs-sm); }

/* Compact: bring the biggest numbers down one step and tighten the running
   row. Past rows keep their current sizing (the existing :not(.is-active)
   rules above already handle them). */
.time-page--compact .time-band__timer { font-size: var(--fs-2xl); }
.time-page--compact .time-row__dur--lg { font-size: var(--fs-2xl); }
.time-page--compact .time-row.is-active { padding-block: var(--sp-2); }

/* ── Narrowest desktop fractions (≤600px, above the 720px mobile chrome
   breakpoint range) — claw back horizontal room and let the date-nav +
   shift-button strips stack/wrap instead of overflowing. Applies to both
   Standard and Compact. */
@media (max-width: 600px) {
  .time-row,
  .time-table__head { padding-inline: var(--sp-3); }
  .time-datenav {
    flex-direction: column;
    align-items: stretch;
  }
  .time-datenav__actions { flex-wrap: wrap; }
  /* Standard's shift segmented control goes full-width on mobile — three equal
     segments sharing the row (it stays ONE pill, never wraps). */
  .time-page:not(.time-page--compact) > .time-band-actions { display: flex; width: 100%; }
  .time-page:not(.time-page--compact) > .time-band-actions form { flex: 1; }
  .time-page:not(.time-page--compact) > .time-band-actions .shift-btn { width: 100%; justify-content: center; }
}

/* (Earlier `.tag-picker` inline searchable picker block lived here; it was
   superseded by the [data-tag-picker] / chip-picker pattern in Session 16-ish
   and the orphan CSS was purged in Session 29.) */

/* ── End-shift override modal */
.time-modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: var(--sp-4);
}
.time-modal.is-open { display: flex; }
.time-modal__panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  max-width: 560px;
  width: 100%;
  padding: var(--sp-5);
  box-shadow: var(--shadow);
}
.time-modal__title {
  font-size: var(--fs-xl);
  font-weight: var(--fw-semi);
  margin: 0 0 var(--sp-2);
  color: var(--ink);
}
.time-modal__body {
  color: var(--ink-2);
  font-size: var(--fs-sm);
  line-height: var(--lh);
  margin-bottom: var(--sp-4);
}
.time-modal__list {
  list-style: none;
  padding: 0;
  margin: var(--sp-3) 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.time-modal__list li {
  font-size: var(--fs-sm);
  color: var(--ink-2);
  padding: 6px 10px;
  background: var(--surface-2);
  border-radius: var(--r-sm);
  font-variant-numeric: tabular-nums;
}
.time-modal__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-3);
}

/* ───── Fullscreen entry / exit affordances ───────────────────────────
   Icon-only button used in page headers + toolbars to enter fullscreen.
   The .fullscreen-exit anchor is body-level inside layout-fullscreen.php. */
.btn--icon {
  padding: 6px 8px;
  min-width: 0;
}
.btn--icon svg {
  display: block;
}
.fullscreen-exit {
  position: fixed;
  top: var(--sp-3);
  right: var(--sp-3);
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  opacity: 0.35;
  transition: opacity var(--t-fast), color var(--t-fast);
  z-index: 9998;
}
.fullscreen-exit:hover,
.fullscreen-exit:focus-visible {
  opacity: 1;
  color: var(--ink);
}

/* ───── Fullscreen / TV-display layout (?fullscreen=1) ────────────────
   Chromeless layout (layout-fullscreen.php) plus body.is-fullscreen
   hooks: bigger type, no edit affordances, room to breathe at distance. */
body.is-fullscreen {
  --fs:     19px;
  --fs-md:  21px;
  --fs-lg:  24px;
  --fs-xl:  28px;
  --fs-2xl: 36px;
  --fs-3xl: 44px;
  font-size: var(--fs);
}
/* The shift segmented control shouldn't balloon with the giant fullscreen --fs
   tokens (--fs-md is 21px there) — but it also must NOT shrink below the normal
   page's control (15px → ~39px tall). Pin it to match the non-fullscreen
   segmented control. Higher specificity than the non-compact bump so it wins. */
body.is-fullscreen .time-page:not(.time-page--compact) .time-band-actions .shift-btn {
  font-size: 16px;
  padding: 11px 18px;
}
body.is-fullscreen .time-page:not(.time-page--compact) > .time-band-actions { margin-bottom: var(--sp-4); }
/* Chrome that's always hidden in fullscreen mode (independent of per-tab
   edit-allowed pref). These are the "this is a TV/kiosk view" affordances. */
body.is-fullscreen .nav-top,
body.is-fullscreen .nav-rail,
body.is-fullscreen .notes-rail,
body.is-fullscreen .flashes,
body.is-fullscreen .toast-region,
body.is-fullscreen .sortable-item__handle,
body.is-fullscreen .notes-card__handle,
body.is-fullscreen .notes-card__icon,
body.is-fullscreen .kanban__col-resize-corner,
body.is-fullscreen [data-customize-toggle],
body.is-fullscreen .page__back,
body.is-fullscreen .page__header > a {
  display: none !important;
}
/* Edit affordances. Default in fullscreen: SHOWN (session 19 flip per user
   directive — was previously always hidden). User can opt out per-tab via
   user-edit "Fullscreen edit" checkbox; that adds .is-fullscreen-edit-disabled
   to body, which restores the old hide-everything behavior for that tab. */
body.is-fullscreen.is-fullscreen-edit-disabled .composer,
body.is-fullscreen.is-fullscreen-edit-disabled .filterbar,
body.is-fullscreen.is-fullscreen-edit-disabled .form-card,
body.is-fullscreen.is-fullscreen-edit-disabled .form-actions,
body.is-fullscreen.is-fullscreen-edit-disabled .row__edit,
body.is-fullscreen.is-fullscreen-edit-disabled .note__actions,
body.is-fullscreen.is-fullscreen-edit-disabled .time-edit,
body.is-fullscreen.is-fullscreen-edit-disabled [data-confirm-delete],
body.is-fullscreen.is-fullscreen-edit-disabled [data-confirm],
body.is-fullscreen.is-fullscreen-edit-disabled [data-contact-add-toggle],
body.is-fullscreen.is-fullscreen-edit-disabled [data-contact-assign],
body.is-fullscreen.is-fullscreen-edit-disabled [data-domain-add],
body.is-fullscreen.is-fullscreen-edit-disabled .btn--primary,
body.is-fullscreen.is-fullscreen-edit-disabled .btn--danger {
  display: none !important;
}
body.is-fullscreen .main {
  padding: var(--sp-5);
  min-height: 100vh;
}
body.is-fullscreen .page {
  max-width: 100%;
}
/* Fullscreen — chrome is gone, canvas can take more height. */
body.is-fullscreen .kanban-canvas { --kanban-canvas-h: 60px; }

/* Projects rows in fullscreen — collapse to name + client + bar (3 cols).
   Project name gets the room previously eaten by dev/status/last-note. */
body.is-fullscreen .row__header {
  grid-template-columns:
    minmax(280px, 2fr)   /* name — bigger cap, more legible at distance */
    minmax(160px, 1fr)   /* client */
    minmax(400px, 3fr);  /* milestone bar — most of the width */
  cursor: default;
  padding: var(--sp-4) var(--sp-5);
}
body.is-fullscreen .row__header:hover { background: transparent; }
body.is-fullscreen .row__name { font-size: var(--fs-lg); }

/* Bigger milestone pips for TV-display legibility. */
body.is-fullscreen .mbar { height: 28px; }
body.is-fullscreen .mbar__track { left: 9px; right: 9px; height: 4px; }
body.is-fullscreen .mbar__node {
  width: 18px;
  height: 18px;
  border-width: 2px;
}
body.is-fullscreen .mbar__node::before { inset: -10px -6px; }

/* ───── Scanline anti-burn-in ────────────────────────────────────────
   Body-level 1px line that descends from top to 100vh on a timer.
   Color set inline by JS (W/R/G/B/C/M/Y subpixel rotation). */
.scanline {
  position: fixed;
  left: 0;
  right: 0;
  top: -2px;
  height: 1px;
  pointer-events: none;
  z-index: 99999;
}

/* ───── Monitor-only account UX ──────────────────────────────────────
   Server-side require_writer() in router.php is the security boundary;
   these rules just hide affordances that would 403 on click. Designed
   for the rare case a monitor user views the regular layout (most
   monitor logins use ?fullscreen=1 which strips chrome anyway). */
body.user-is-monitor .btn--primary,
body.user-is-monitor .btn--danger,
body.user-is-monitor [data-confirm-delete],
body.user-is-monitor [data-confirm],
body.user-is-monitor .composer,
body.user-is-monitor [data-contact-add-toggle],
body.user-is-monitor [data-contact-assign],
body.user-is-monitor .form-card .form-actions,
body.user-is-monitor .row__edit,
body.user-is-monitor .note__actions,
body.user-is-monitor [data-domain-add],
body.user-is-monitor .sortable-item__handle,
body.user-is-monitor .notes-card__handle,
body.user-is-monitor .kanban__col-resize-corner,
body.user-is-monitor [data-customize-toggle],
body.user-is-monitor [data-card-start-timer],
body.user-is-monitor [data-row-start-timer],
body.user-is-monitor .note-panel__actions,
body.user-is-monitor .notes-thread__form,
body.user-is-monitor [data-time-add-toggle] {
  display: none !important;
}

/* ─── Reports surface ─────────────────────────────────────────────────── */

/* Day-of-week pill cluster. (The earlier `.reports-section*` / `.reports-pivot*`
   / `.reports-timelog*` / `.reports-summary*` blocks were dead — the actual
   report-render views in `app/views/time/reports/render/*.php` use plain
   `.report`, `.section`, `.summary`. Purged in Session 29.) */
.reports-filterbar__dows {
  border: 0;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-2);
  align-items: center;
}
.reports-filterbar__dows > legend.label,
.reports-filterbar__dows .field__label {
  flex: 0 0 100%;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  margin-bottom: 6px;
  padding: 0;
}
.reports-filterbar__dow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  /* Match .input height (37px: 8+8 padding + 1+1 border + ~19 line-height)
     so pills and inputs in the same row terminate at the same baseline. */
  min-height: 37px;
  box-sizing: border-box;
  padding: 0 var(--sp-2);
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--r);
  font-size: var(--fs-sm);
  cursor: pointer;
  user-select: none;
}
.reports-filterbar__dow input { accent-color: var(--accent); }

/* Cards grid for the landing */
.reports-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--sp-4);
}
.reports-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border, var(--surface-2));
  border-radius: var(--r-lg);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.12s ease, transform 0.12s ease;
}
.reports-card:hover {
  border-color: var(--accent);
  transform: translateY(-1px);
}
.reports-card__title {
  margin: 0;
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--ink);
}

/* ─── Reports rebuild: generator grid (Batch 5-A.14) ────────────────────
   `.reports-card--saved` block removed in session 19; saved-reports list
   now uses the standard `.list / .list__row` primitives with a custom
   6-column grid (see app/views/time/reports/index.php). The `.reports-card`
   base + `__head/__meta/__actions/__title` rules above are still in use
   by /time/hours.php — leave intact. */

/* Status pills for the 7 report types — desaturated, not status-colored */
.status-pill--hours,
.status-pill--daywork,
.status-pill--timelog,
.status-pill--ctags,
.status-pill--stags,
.status-pill--edits,
.status-pill--payroll {
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: 500;
  padding: 2px 8px;
  border-radius: var(--r);
  white-space: nowrap;
}

/* Generator form — panels are .dash-widget instances inside .home-grid; only
   internals belong to the .report-generate namespace. */
.report-generate { gap: 0; }
/* Don't stretch report-generate dash-widgets to match siblings: shorter panels
   should stay tight to content instead of growing to match the tallest peer in
   their grid row (which leaves dead air at the bottom). */
.report-generate .home-grid > .dash-widget { align-self: start; }
.report-generate__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-4);
  align-items: flex-end;
}
.report-generate__row > .field + .field { margin-top: 0; }
.report-generate__row > .field { flex: 1 1 140px; min-width: 0; }
.report-generate__row > .chip-picker { flex: 1 1 220px; min-width: 0; }
.report-generate__field--narrow { flex: 0 0 140px !important; }

/* Output-format radios: row of pills, NOT a stacked column. The legend acts
   as a label that takes its own row above the pills. */
.report-generate__formats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
  border: 0;
  padding: 0;
  margin: 0;
  align-items: center;
}
.report-generate__formats > legend {
  flex: 0 0 100%;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--ink-2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 6px;
  padding: 0;
}
.report-generate__formats .checkbox-row { padding: 0; }

/* Per-type toggle fieldsets — render inline inside the Report Options
   .dash-widget panel, so reset the browser-default fieldset chrome and let
   the parent panel provide the surface. */
.report-generate__toggles {
  border: 0;
  padding: 0;
  margin: 0;
  background: transparent;
}
.report-generate__toggles[hidden] { display: none; }
.report-generate__toggles legend {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
  color: var(--ink-3);
  padding: 0 var(--sp-2);
}
.report-generate__toggles-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  align-items: center;
  padding: var(--sp-2) 0;
}
.report-generate__toggles-row + .report-generate__toggles-row {
  border-top: 1px dashed var(--border, var(--surface-2));
  margin-top: var(--sp-2);
}
.report-generate__toggles-row .label {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  margin: 0 var(--sp-2) 0 0;
}

/* Day-type include fieldset (mirror of dows fieldset). The .field class on the
   parent row would otherwise force flex-direction:column onto these children;
   we explicitly reset to row-with-label-above. */
.reports-filterbar__day-types {
  border: 0;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-2);
  align-items: center;
}
.reports-filterbar__day-types > legend.label,
.reports-filterbar__day-types > .field__label {
  flex: 0 0 100%;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  margin-bottom: 6px;
  padding: 0;
}

/* Templates list — inline forms inside list rows */
.report-template-row .list__row-actions {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
}
.list__delete-form { display: inline-flex; }

/* ─── Chip picker (multi-select via typeahead + chips) ─────────────── */

.chip-picker {
  display: flex;
  flex-direction: column;
  gap: 6px;
  position: relative;
}
.chip-picker .label { margin-bottom: 0; }

/* Head: wraps the label and (optionally) the include/exclude segmented toggle. */
.chip-picker__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2);
  min-height: 0;
}

.chip-picker__mode {
  display: inline-flex;
  gap: 0;
  background: var(--surface-2);
  border: 1px solid var(--border, var(--surface-2));
  border-radius: var(--r);
  padding: 2px;
  font-size: var(--fs-xs);
  user-select: none;
}
.chip-picker__mode-opt {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--sp-2);
  border-radius: calc(var(--r) - 2px);
  cursor: pointer;
  color: var(--ink-2);
  line-height: 1.4;
  transition: background-color 0.1s ease, color 0.1s ease;
}
.chip-picker__mode-opt:hover {
  color: var(--ink);
}
.chip-picker__mode-opt:has(input:checked) {
  background: var(--ink-2);
  color: var(--ink-on-strong);
  font-weight: var(--fw-medium);
}
.chip-picker__mode-opt input {
  /* Visually hide the radio; the label provides the click target. */
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 1px;
  height: 1px;
}

.chip-picker__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  min-height: 0;
}
.chip-picker__chips:empty { display: none; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 4px 3px 8px;
  background: var(--accent-soft);
  color: var(--ink);
  border-radius: var(--r);
  font-size: var(--fs-sm);
  line-height: 1.2;
  max-width: 100%;
}
.chip__label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
  max-width: 220px;
}

.chip__remove {
  background: transparent;
  border: 0;
  color: var(--ink-2);
  cursor: pointer;
  padding: 0 4px;
  font-size: 16px;
  line-height: 1;
  border-radius: var(--r-sm);
}
.chip__remove:hover { color: var(--danger); background: var(--surface-2); }

.chip-picker__combo {
  position: relative;
}
.chip-picker__search {
  width: 100%;
}
.chip-picker__menu {
  position: absolute;
  z-index: 30;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 4px 0;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--border-strong, var(--surface-2));
  border-radius: var(--r);
  box-shadow: var(--shadow);
  max-height: 240px;
  overflow-y: auto;
  font-size: var(--fs-sm);
}
.chip-picker__menu[hidden] { display: none; }
/* Opened upward when there isn't room below (set by initOneChipPicker) — keeps
   the menu on-screen and stops it extending the page past the nav-rail. */
.chip-picker__menu--up { top: auto; bottom: calc(100% + 2px); }
.chip-picker__option {
  padding: 6px 10px;
  cursor: pointer;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chip-picker__option:hover,
.chip-picker__option.is-active {
  background: var(--hover, rgba(127,127,127,0.08));
}
.chip-picker__option.is-selected {
  color: var(--ink-3);
  text-decoration: line-through;
  cursor: default;
}
.chip-picker__option.is-hidden { display: none; }
.chip-picker__empty {
  padding: 6px 10px;
  color: var(--ink-3);
  font-style: italic;
  cursor: default;
}

/* ─── /time/hours dashboard (Batch 5-A.14) ──────────────────────────── */

.hours-summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5);
  margin: 0 0 var(--sp-5) 0;
  padding: var(--sp-4) var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border, var(--surface-2));
  border-radius: var(--r-lg);
}
.hours-summary > div {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.hours-summary dt {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  margin: 0;
}
.hours-summary dd {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.hours-card {
  border-top-width: 3px;
  border-top-style: solid;
  /* border-top-color set inline (state-driven) */
}
.hours-card .reports-card__title a {
  color: var(--ink);
  text-decoration: none;
}
.hours-card .reports-card__title a:hover { color: var(--accent); }

.hours-card__usage {
  margin: var(--sp-1) 0;
  font-size: var(--fs-sm);
  color: var(--ink-2);
}
.hours-card__usage b {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.hours-card__pct {
  margin: var(--sp-1) 0 0 0;
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.hours-card__hint {
  margin: var(--sp-2) 0 0 0;
  font-size: var(--fs-sm);
  color: var(--ink-3);
}
.hours-card__hint a { color: var(--accent); text-decoration: none; }
.hours-card__hint a:hover { text-decoration: underline; }

.progress-bar {
  height: 8px;
  background: var(--surface-2);
  border-radius: var(--r);
  overflow: hidden;
  margin: var(--sp-2) 0 0 0;
}
.progress-bar__fill {
  height: 100%;
  background: var(--accent);
  transition: width 0.2s;
}

.status-pill--over {
  background: var(--danger);
  color: var(--ink-on-strong);
}
.status-pill--near {
  background: var(--warning);
  color: var(--ink-on-strong);
}
.status-pill--untracked {
  background: var(--surface-2);
  color: var(--ink-3);
}

@media (max-width: 720px) {
  .hours-summary { gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); }
  .hours-summary dd { font-size: var(--fs-md); }
}

/* ─── Task presets manager (Batch 5-A.14) ───────────────────────────── */

.task-preset-form { margin: 0; }
.task-preset-row {
  display: grid;
  grid-template-columns: 28px 1fr auto;
  gap: var(--sp-3);
  align-items: start;
  padding: var(--sp-4);
  border-bottom: 1px solid var(--border, var(--surface-2));
}
.task-preset-row:last-child { border-bottom: 0; }
.list__row-grip {
  font-size: var(--fs-lg);
  line-height: 1;
  color: var(--ink-3);
  cursor: grab;
  user-select: none;
  padding-top: 4px;
}
.list__row-grip:active { cursor: grabbing; }
.task-preset-row.is-dragging { opacity: 0.6; }
.task-preset-row .form-actions {
  margin-top: var(--sp-3);
  padding-top: 0;
  border-top: 0;
}

.form-help {
  margin: var(--sp-1) 0 0 0;
  font-size: var(--fs-xs);
  color: var(--ink-3);
}

/* A DB-managed / read-only value shown inside a form for transparency — plain
   text, not an input (so it never reads as editable or stretches full-width). */
.form-readonly-value {
  margin: 2px 0 0 0;
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--ink);
}
.form-readonly-value__unit {
  margin-left: 3px;
  font-size: var(--fs-sm);
  font-weight: var(--fw-regular);
  color: var(--ink-3);
}

/* ─── /time/timeline visual view (Batch 5-A.14) ────────────────────── */

.timeline {
  background: var(--surface);
  border: 1px solid var(--border, var(--surface-2));
  border-radius: var(--r-lg);
  overflow: hidden;
}
.timeline__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  height: calc(100vh - 220px);
  min-height: 480px;
  background:
    repeating-linear-gradient(
      to bottom,
      transparent,
      transparent 59px,
      var(--border, var(--surface-2)) 59px,
      var(--border, var(--surface-2)) 60px
    );
}
.timeline__track {
  display: flex;
  align-items: flex-start;
  height: 1440px;        /* 24h * 60min, 1px = 1min */
  min-width: 100%;
  padding: 0 var(--sp-3);
}
.timeline__sentinel {
  flex: 0 0 1px;
  height: 100%;
  background: transparent;
}
.timeline__day {
  flex: 0 0 var(--day-w, 240px);
  scroll-snap-align: start;
  position: relative;
  height: 100%;
  border-left: 1px solid var(--border, var(--surface-2));
  padding: 0 var(--sp-2);
}
.timeline__day--today { background: var(--accent-soft); }

.timeline__day-head {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface);
  padding: var(--sp-2) var(--sp-2);
  border-bottom: 1px solid var(--border, var(--surface-2));
  text-align: center;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
}
.timeline__day-head b {
  display: block;
  color: var(--ink);
  font-size: var(--fs-md);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}

.timeline__column {
  position: relative;
  height: 100%;
}
.timeline__entry {
  position: absolute;
  left: var(--sp-2);
  right: var(--sp-2);
  display: flex;
  align-items: flex-start;
  gap: var(--sp-1);
}
.timeline__dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--accent);
  flex: 0 0 10px;
  margin-top: 4px;
}
.timeline__dot--break { background: var(--warning); }
.timeline__dot--end,
.timeline__dot--off_sick,
.timeline__dot--on_vacation { background: var(--danger); }

.timeline__card {
  flex: 1;
  background: var(--surface);
  border: 1px solid var(--border, var(--surface-2));
  border-radius: var(--r-lg);
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--ink-2);
  cursor: pointer;
  border-top: 3px solid var(--accent);
  transition: border-color 0.12s, transform 0.12s;
}
.timeline__card:hover { border-color: var(--accent); transform: translateY(-1px); }
.timeline__card--break { border-top-color: var(--warning); }
.timeline__card--end,
.timeline__card--off_sick,
.timeline__card--on_vacation { border-top-color: var(--danger); }
.timeline__card-time {
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.timeline__card-client {
  font-size: var(--fs-sm);
  color: var(--ink);
  font-weight: 500;
  margin-top: 2px;
}
.timeline__card-comment {
  margin-top: var(--sp-1);
  color: var(--ink-2);
  white-space: pre-wrap;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}
.timeline__card-tags {
  margin-top: var(--sp-1);
  font-size: 11px;
  color: var(--ink-3);
}

.timeline__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-top: 1px solid var(--border, var(--surface-2));
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.timeline__legend-item { display: inline-flex; align-items: center; gap: var(--sp-1); }
.timeline__legend .timeline__dot { margin-top: 0; }

@media (max-width: 720px) {
  .timeline__viewport { height: 60vh; }
  .timeline__day { flex-basis: 200px; }
}

/* ─── /time shift+click multi-select sum (Batch 5-A.14) ─────────────── */

.time-row.is-selected-sum {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  background: var(--accent-soft);
}
.time-multi-sum {
  position: fixed;
  bottom: var(--sp-4);
  right: var(--sp-4);
  z-index: 50;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border, var(--surface-2));
  border-top: 3px solid var(--accent);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}
.time-multi-sum b {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--ink);
}
.time-multi-sum [data-multi-clear] {
  background: transparent;
  border: 0;
  color: var(--ink-3);
  font-size: var(--fs-xs);
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.time-multi-sum [data-multi-clear]:hover { color: var(--ink); }

/* ─── Audit log slide-in detail panel ──────────────────────────────
 * Panel chrome reuses .note-panel*. These rules style the inner
 * <dl> + <pre> rendered by JS into [data-audit-panel-body].
 */
.audit-detail {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--sp-1) var(--sp-3);
  margin: 0;
  padding: var(--sp-3);
  font-size: var(--fs-sm);
}
.audit-detail dt { color: var(--ink-3); font-weight: var(--fw-medium); }
.audit-detail dd { color: var(--ink); margin: 0; word-break: break-word; }
.audit-detail__meta {
  font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
  font-size: var(--fs-xs);
  background: var(--bg-2);
  padding: var(--sp-2);
  border-radius: var(--r);
  white-space: pre-wrap;
  margin: 0;
  overflow-x: auto;
}

/* ─── Audit log filterbar — three vertical rows ───────────────────
 * Row 1: full-width search.
 * Row 2: chip-pickers — three of them, equal width via `flex: 1`.
 *        Kept ON THEIR OWN row because the chip-picker has a head + chips
 *        + combo internal stack that's ~14px taller than a plain .field;
 *        mixing them with date inputs would stagger label baselines.
 * Row 3: From + To date inputs on the left, Apply + Reset on the right.
 */
.audit-filterbar {
  display: flex; flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  margin-bottom: var(--sp-4);
}
.audit-filterbar__search { width: 100%; }
.audit-filterbar__chips {
  display: flex; flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: flex-start;
}
.audit-filterbar__chips > .chip-picker { flex: 1 1 220px; min-width: 0; }
.audit-filterbar__bottom {
  display: flex; flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: flex-end;
}
.audit-filterbar__date { flex: 0 0 auto; }
.audit-filterbar__date .input[type="date"] { width: 11rem; }
.audit-filterbar__actions {
  display: flex; gap: var(--sp-2);
  margin-left: auto;
}

/* Audit filterbar narrow-width: chip-pickers stack to one-per-row instead of
 * wrapping into a 220px-floor grid (which overflows phone screens), and date
 * inputs grow to full width so the bottom row reflows cleanly. */
@media (max-width: 720px) {
  .audit-filterbar__chips { flex-direction: column; }
  .audit-filterbar__chips > .chip-picker { flex: 1 1 auto; width: 100%; }
  .audit-filterbar__date .input[type="date"] { width: 100%; }
  .audit-filterbar__date { flex: 1 1 100%; }
  .audit-filterbar__actions { margin-left: 0; }
}

/* Audit log row meta: time on one line, relative on the next so the column
 * breaks cleanly without overrunning. */
.audit-row__rel { color: var(--ink-3); white-space: nowrap; margin-left: var(--sp-1); }
/* "API" channel marker on audit rows made through a token, + the token label. */
.audit-via {
  display: inline-block;
  font-size: 10px;
  font-weight: var(--fw-semi);
  letter-spacing: 0.04em;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  border-radius: var(--r);
  padding: 0 5px;
  margin: 0 2px;
  vertical-align: 1px;
}
.audit-via__token { color: var(--ink-3); }
[data-audit-list] .list__row-meta {
  flex-direction: column;
  align-items: flex-end;
  gap: 0;
  white-space: nowrap;
}
[data-audit-list] .list__row-meta .audit-row__rel { margin-left: 0; }

/* ─── Pager (Previous / page count / Next) ────────────────────────
 * Used on /audit-log; reusable for any future paginated list.
 */
.pager {
  display: flex; align-items: center; justify-content: center;
  gap: var(--sp-3); margin-top: var(--sp-4);
}
.pager__count { font-size: var(--fs-xs); color: var(--ink-3); }

/* ─── Checklists run view (Batch C / session 20) ────────────────────────────
 * Mobile-friendly run view: large touch targets, single column. The page IS
 * the substance, so the items container is sharp-cornered (--r-none) per the
 * locked radius rule — only the "complete" form-card stays --r-lg.
 */
.checklist-run {
  max-width: 720px;
  margin: 0 auto;
  padding: var(--sp-3);
}
.checklist-run__header {
  margin-bottom: var(--sp-4);
}
.checklist-run__title {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-semi);
  letter-spacing: -0.02em;
  margin: var(--sp-2) 0 var(--sp-1);
  color: var(--ink);
}
.checklist-run__progress {
  font-size: var(--fs-sm);
  color: var(--ink-3);
}
.checklist-run__items {
  list-style: none;
  margin: 0 0 var(--sp-5);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.checklist-item {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-none);
  padding: var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  min-height: 64px;  /* generous tap target for mobile */
}
.checklist-item--done { background: var(--surface-2); }
.checklist-item__check {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  cursor: pointer;
}
.checklist-item__check input[type="checkbox"] {
  width: 28px;
  height: 28px;
  accent-color: var(--accent);
  cursor: pointer;
  flex-shrink: 0;
}
.checklist-item__label {
  font-size: var(--fs-md);
  color: var(--ink);
  line-height: 1.35;
}
.checklist-item--done .checklist-item__label {
  text-decoration: line-through;
  color: var(--ink-3);
}
.checklist-item__desc {
  font-size: var(--fs-sm);
  color: var(--ink-3);
  margin-left: 40px;  /* align under label, past the checkbox */
  line-height: 1.4;
}
.checklist-item__note {
  width: 100%;
  margin-left: 40px;
  width: calc(100% - 40px);
  font-size: var(--fs-sm);
  padding: 6px 8px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  color: var(--ink);
  resize: vertical;
  min-height: 32px;
}
.checklist-run__complete {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.checklist-run__complete-btn {
  align-self: flex-start;
  padding: 12px 24px;
  font-size: var(--fs-md);
}
@media (max-width: 600px) {
  .checklist-item { padding: var(--sp-2); }
  .checklist-item__label { font-size: var(--fs-sm); }
  .checklist-item__desc, .checklist-item__note { margin-left: 36px; width: calc(100% - 36px); }
}

/* ─── Grassroots SEO (Session 21) ─────────────────────────────────────────
 * Per-client SEO hub at /grassroots and /clients/{id}/grassroots/*.
 * All styling theme-first — pipeline status uses existing .status-pill--*,
 * usage states reuse .progress-bar + .status-pill--{over,near,untracked}.
 * Sub-nav items reuse .nav-top__link verbatim so theme tokens flow.
 */

/* Edit-form fieldset for the per-client Grassroots toggle + plan tier. Mirrors
 * `.domains-fieldset` borderless shape. */
.grassroots-fieldset {
  border: none;
  margin: var(--sp-4) 0 0;
  padding: 0;
}
.grassroots-fieldset legend { padding: 0; margin-bottom: var(--sp-1); }
.grassroots-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--ink);
  cursor: pointer;
}
.grassroots-plan-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  max-width: 480px;
}
.grassroots-plan-wrap[hidden] { display: none; }

/* Sub-nav above the per-client portals (Hub / Content / Analytics / Reports).
 * Each link reuses `.nav-top__link` so hover/active states inherit theme
 * tokens. The container only handles box-model. */
.grassroots-subnav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  margin: 0 0 var(--sp-4);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border);
}

/* Pipeline display on the per-client hub. Each slot is a vertical stack:
 * status pill on top, count number below. Layout-only; colors come from
 * .status-pill--* (theme-aware) and var(--ink) / var(--ink-3). */
.grassroots-pipeline {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
}
.grassroots-pipeline__slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 80px;
}
.grassroots-pipeline__count {
  font-size: var(--fs-xl);
  font-weight: var(--fw-semi);
  color: var(--ink);
  line-height: 1;
}
.grassroots-pipeline__count.is-zero { color: var(--ink-3); font-weight: var(--fw-base, 400); }

/* ─── /grassroots index cards (.gr-*) ───────────────────────────────────
   Each card is a compact dashboard for one Grassroots-enabled client:
   header + hours bar + full pipeline strip + domains + analytics
   placeholders + recent briefs + next-scheduled + actions footer. The
   sidebar lives in nav_rail_grassroots.php using the existing .nav-rail
   vocabulary. */
.gr-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
  gap: var(--sp-4);
  align-items: start;
  margin-top: var(--sp-3);
}
.gr-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-width: 0;
}
.gr-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3);
  min-width: 0;
}
.gr-card__title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semi);
  margin: 0;
  letter-spacing: -0.01em;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.gr-card__title a { color: var(--ink); text-decoration: none; }
.gr-card__title a:hover { color: var(--accent); }
.gr-card__chips {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
  flex-shrink: 0;
}

.gr-card__metric {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.gr-card__metric-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2);
}
.gr-card__metric-label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  color: var(--ink-2);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.gr-card__metric-aside {
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.gr-card__metric-link {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  text-decoration: none;
}
.gr-card__metric-link:hover { color: var(--accent); }

.gr-card__metric-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
}
.gr-card__metric-value {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-semi);
  color: var(--ink);
  letter-spacing: -0.02em;
  line-height: 1;
}
.gr-card__metric-of {
  font-size: var(--fs-sm);
  color: var(--ink-3);
}
.gr-card__metric-pct {
  margin-left: auto;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

/* Progress-bar fills by state — tints the .progress-bar__fill inside .gr-card */
.gr-card__bar--under     { background: var(--accent); }
.gr-card__bar--near      { background: var(--warning); }
.gr-card__bar--over      { background: var(--danger); }
.gr-card__bar--untracked { background: var(--surface-2); }

/* 5-column pipeline strip — vertical stack per stage (pill on top, count
   below), like the per-client hub's .grassroots-pipeline pattern but
   sized for the card grid. */
.gr-card__pipeline {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--sp-1);
}
.gr-card__pipeline-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 0;
}
.gr-card__pipeline-slot .status-pill {
  width: 100%;
  padding: 2px 4px;
  font-size: var(--fs-xs);
  text-align: center;
  justify-content: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* At narrow card widths the pill labels overflow ("Approved" / "Scheduled"
   / "Published" all wider than a 5-col slot at ~60px). Allow line-wrap +
   shrink the font slightly so labels stay readable instead of getting
   clipped or visually overlapping with neighbouring slots. */
@media (max-width: 500px) {
  .gr-card__pipeline-slot .status-pill {
    white-space: normal;
    line-height: 1.15;
    font-size: 10px;
    padding: 3px 4px;
  }
}
.gr-card__pipeline-count {
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.gr-card__pipeline-count.is-zero { color: var(--ink-3); font-weight: var(--fw-reg); }

/* Domains list — one link row per domain (label + bare URL) */
.gr-card__domains {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.gr-card__domain {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding: 4px 0;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  text-decoration: none;
  min-width: 0;
}
.gr-card__domain:hover { color: var(--accent); }
.gr-card__domain-label {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  flex-shrink: 0;
}
.gr-card__domain-url {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* Analytics / Search Console placeholders — visually dimmed so they read
   as "wired but not powered yet". When the real GA4 / GSC integrations
   land they replace the placeholder values; the structure stays. */
.gr-card__metric--soon { opacity: 0.65; }
.gr-card__placeholders {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-2);
}
.gr-card__placeholder {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-2);
  background: var(--surface-2);
  border-radius: var(--r);
  min-width: 0;
}
.gr-card__placeholder-label {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gr-card__placeholder-value {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semi);
  color: var(--ink-3);
}

/* Recent briefs — title line + meta line (status pill + keyword + when) */
.gr-card__briefs {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
}
.gr-card__brief {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-2) 0;
  border-top: 1px solid var(--border);
}
.gr-card__brief:first-child { border-top: 0; padding-top: 0; }
.gr-card__brief-title {
  color: var(--ink);
  text-decoration: none;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.gr-card__brief-title:hover { color: var(--accent); }
.gr-card__brief-meta {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  font-size: var(--fs-xs);
}
.gr-card__brief-when { color: var(--ink-3); margin-left: auto; }
.gr-card__empty {
  font-size: var(--fs-sm);
  color: var(--ink-3);
  margin: 0;
  font-style: italic;
}
.gr-card__empty a { color: var(--accent); text-decoration: none; }

/* Next-scheduled mini-banner — one-line callout above the footer */
.gr-card__next {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface-2);
  border-radius: var(--r);
  font-size: var(--fs-sm);
  min-width: 0;
}
.gr-card__next-label {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  flex-shrink: 0;
}
.gr-card__next-title {
  color: var(--ink);
  text-decoration: none;
  font-weight: var(--fw-semi);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.gr-card__next-title:hover { color: var(--accent); }
.gr-card__next-when { color: var(--ink-3); margin-left: auto; flex-shrink: 0; }

/* Footer: action buttons */
.gr-card__footer {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  margin-top: auto;
  flex-wrap: wrap;
}

@media (max-width: 720px) {
  .gr-grid { grid-template-columns: 1fr; }
  .gr-card__placeholders { grid-template-columns: repeat(3, 1fr); }
  .gr-card__pipeline { grid-template-columns: repeat(5, 1fr); }
}

/* JS-driven plan-toggle hide (see app.js initGrassrootsPlanToggles); kept
   distinct from the search-filter's inline display:none so the two filter
   paths compose without fighting each other. */
.gr-card.is-plan-hidden { display: none; }

/* ─── /social index cards (.soc-*) ──────────────────────────────────────
   Per-client card for the social planner. The per-client color (migration
   037: clients.social_color) drives a TOP-bar accent — per CLAUDE.md
   locked rule "colored-accent border = top bar + no radius", these cards
   carry --r-none. Neutral clients (no color) use --border-strong. Inside:
   header, status mix (always-5 row), upcoming-posts list, footer. */
.soc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
  gap: var(--sp-4);
  align-items: start;
  margin-top: var(--sp-3);
}
.soc-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--client-accent, var(--border-strong));
  border-radius: var(--r-none);  /* locked: colored accent = no radius */
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
}
.soc-card--neutral { --client-accent: var(--border-strong); }

.soc-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3);
  min-width: 0;
}
.soc-card__title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semi);
  letter-spacing: -0.01em;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.soc-card__title a { color: var(--ink); text-decoration: none; }
.soc-card__title a:hover { color: var(--accent); }
.soc-card__chips {
  display: inline-flex;
  gap: var(--sp-2);
  align-items: center;
  flex-shrink: 0;
  flex-wrap: wrap;
}
.soc-card__total {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  white-space: nowrap;
}

/* Status mix row — 5 micro-pills + counts, zeros muted */
.soc-card__statusrow {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
  align-items: center;
}
.soc-card__statusrow-item {
  display: inline-flex;
  gap: 4px;
  align-items: center;
}
.soc-card__statusrow-item .status-pill {
  padding: 2px 6px;
  font-size: var(--fs-xs);
}
.soc-card__statusrow-count {
  font-size: var(--fs-xs);
  color: var(--ink);
  font-weight: var(--fw-semi);
  font-variant-numeric: tabular-nums;
}
.soc-card__statusrow-count.is-zero { color: var(--ink-3); font-weight: var(--fw-reg); }
.soc-card__statusrow-item.is-zero .status-pill { opacity: 0.45; }

/* Upcoming-posts section */
.soc-card__upcoming {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.soc-card__metric-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2);
}
.soc-card__metric-label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  color: var(--ink-2);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.soc-card__metric-aside {
  font-size: var(--fs-xs);
  color: var(--ink-3);
}

.soc-card__posts {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
}
.soc-card__post {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-2) 0;
  border-top: 1px solid var(--border);
}
.soc-card__post:first-child { border-top: 0; padding-top: 0; }
.soc-card__post-link {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  text-decoration: none;
  color: var(--ink);
  min-width: 0;
}
.soc-card__post-link:hover .soc-card__post-body { color: var(--accent); }
.soc-card__post-platform {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  flex-shrink: 0;
  min-width: 70px;
}
.soc-card__post-body {
  font-size: var(--fs-sm);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1;
}
.soc-card__post-meta {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
  font-size: var(--fs-xs);
  color: var(--ink-3);
  flex-wrap: wrap;
}
.soc-card__post-when { margin-left: auto; }
.soc-card__empty {
  font-size: var(--fs-sm);
  color: var(--ink-3);
  margin: 0;
  font-style: italic;
}
.soc-card__empty a { color: var(--accent); text-decoration: none; }
.soc-card__more {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  text-decoration: none;
  margin-top: 2px;
}
.soc-card__more:hover { color: var(--accent); }

.soc-card__footer {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  margin-top: auto;
  flex-wrap: wrap;
}

@media (max-width: 720px) {
  .soc-grid { grid-template-columns: 1fr; }
}


/* ───── Client services ───────────────────────────────────────────────
   Read list on /clients/{id}, cross-client audit roll-up on /services,
   the edit-form repeater, and the /services/catalog manager. Theme tokens
   only. The audit roll-up is a page's main table → no radius (locked rule). */

/* Read view — services list on the client detail page (a sub-section list). */
.svc-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.svc-list__item {
  display: grid;
  grid-template-columns: auto minmax(0, 1.3fr) auto minmax(0, 1.6fr);
  gap: var(--sp-3);
  align-items: center;
  font-size: var(--fs-sm);
  padding: 5px 0;
}
.svc-list__name { font-weight: var(--fw-medium); display: inline-flex; align-items: baseline; gap: 6px; }
.svc-list__qty { color: var(--ink-2); white-space: nowrap; font-variant-numeric: tabular-nums; }
.svc-list__meta { color: var(--ink-3); text-align: right; }
.svc-list--former .svc-list__name { color: var(--ink-3); font-weight: var(--fw-regular); }
.svc-former { margin-top: var(--sp-2); }
.svc-former__summary { font-size: var(--fs-xs); color: var(--ink-3); cursor: pointer; }

/* Ad-hoc / custom micro-pill (self-labeled "custom"). */
.svc-audit__adhoc {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink-3); border: 1px solid var(--border-strong);
  border-radius: var(--r); padding: 1px 5px; line-height: 1.4;
}

/* Cross-client audit roll-up — the page's main table, so no radius. */
.svc-audit { display: flex; flex-direction: column; }
.svc-audit__head,
.svc-audit__row {
  display: grid;
  grid-template-columns: 16px minmax(110px, 1.1fr) minmax(150px, 1.7fr) 84px 116px 132px;
  gap: var(--sp-3);
  align-items: center;
  padding: 8px var(--sp-2);
}
.svc-audit__head {
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink-3); border-bottom: 1px solid var(--border-strong);
}
.svc-audit__row { border-bottom: 1px solid var(--border); font-size: var(--fs-sm); }
.svc-audit__row.is-inactive { color: var(--ink-3); }
.svc-audit__row.is-inactive .svc-audit__client { color: var(--ink-3); }
.svc-audit__client {
  color: var(--accent); text-decoration: none; font-weight: var(--fw-medium);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.svc-audit__client:hover { text-decoration: underline; }
.svc-audit__service { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; min-width: 0; }
.svc-audit__note { color: var(--ink-3); font-size: var(--fs-xs); }
.svc-audit__qty { font-variant-numeric: tabular-nums; white-space: nowrap; }
.svc-audit__date { color: var(--ink-2); white-space: nowrap; }
.svc-audit__status { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.svc-audit__ended { color: var(--ink-3); font-size: var(--fs-xs); }

/* Edit-form repeater (inside the form-card → rounded card is fine). */
.service-rows { display: flex; flex-direction: column; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.service-row {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface-2); padding: var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-3);
}
.service-row__main { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
.service-row__main .select { flex: 1 1 220px; min-width: 0; }
.service-row__custom { flex: 1 1 200px; min-width: 0; }
.service-row__custom.is-hidden { display: none; }
.service-row__main [data-service-remove] { margin-left: auto; flex: 0 0 auto; }
/* Fill the row in a balanced grid: Quantity fixed-narrow (a number), Unit
   small-fixed, the two dates split the remaining width equally (so they're
   wide enough for dd/mm/yyyy + the calendar icon and the row never leaves a
   dead zone on the right). Notes spans the full width below. */
.service-row__fields {
  display: grid;
  grid-template-columns: 88px 132px 1fr 1fr;
  gap: var(--sp-3);
  align-items: start;
}
.service-row__fields .input { width: 100%; min-width: 0; }
.field--tight { gap: 5px; min-width: 0; }
.field--tight .field__label { font-size: var(--fs-xs); color: var(--ink-3); white-space: nowrap; }
.service-row__notes { grid-column: 1 / -1; }
@media (max-width: 640px) {
  .service-row__fields { grid-template-columns: 1fr 1fr; }
}

/* Dedicated client services editor (/clients/{id}/services) — an aligned
   table, one row per service, inline inputs. Columns line up by construction. */
.svc-edit { width: 100%; border-collapse: collapse; }
.svc-edit thead th {
  text-align: left;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  font-weight: var(--fw-medium);
  padding: 0 var(--sp-2) var(--sp-2) 0;
  border-bottom: 1px solid var(--border-strong);
  white-space: nowrap;
}
.svc-edit__cell { padding: var(--sp-2) var(--sp-2) var(--sp-2) 0; vertical-align: top; }
.svc-edit__cell .input,
.svc-edit__cell .select { width: 100%; min-width: 0; }
.svc-edit th:nth-child(2), .svc-edit__cell:nth-child(2) { width: 132px; }  /* Quantity (+unit) */
.svc-edit th:nth-child(3), .svc-edit__cell:nth-child(3) { width: 162px; }  /* Activated */
.svc-edit th:nth-child(4), .svc-edit__cell:nth-child(4) { width: 162px; }  /* Deactivated */
.svc-edit__cell--service { width: 28%; min-width: 200px; }
.svc-edit__cell--remove { width: 44px; text-align: center; vertical-align: middle; padding-right: 0; }
.svc-edit__qty { display: flex; align-items: center; gap: var(--sp-2); }
.svc-edit__qty .input { flex: 0 1 60px; }
.svc-edit__unit { color: var(--ink-3); font-size: var(--fs-sm); white-space: nowrap; }
.svc-edit__custom { margin-top: var(--sp-2); }
.svc-edit__custom.is-hidden { display: none; }
.svc-edit__foot { margin-top: var(--sp-3); }
.svc-edit__legend {
  list-style: none; margin: var(--sp-4) 0 0; padding: var(--sp-3) 0 0;
  border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: var(--sp-2);
  font-size: var(--fs-sm); color: var(--ink-3);
}
.svc-edit__legend strong { color: var(--ink-2); }
.svc-edit__legend a { color: var(--accent); text-decoration: none; }
.svc-edit__legend a:hover { text-decoration: underline; }

@media (max-width: 880px) {
  /* Stack each service into a labelled block on narrow screens. */
  .svc-edit, .svc-edit tbody, .svc-edit tr, .svc-edit td { display: block; width: auto; }
  .svc-edit thead { display: none; }
  .svc-edit__row { border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--sp-3); margin-bottom: var(--sp-3); }
  .svc-edit__cell { padding: var(--sp-1) 0; }
  .svc-edit__cell--remove { text-align: right; }
}

/* Catalog manager (/services/catalog). */
.service-cat-add { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-bottom: var(--sp-4); }
.service-cat-add .input { flex: 1 1 180px; min-width: 0; }
.service-cat-add__unit { flex: 0 1 140px; }
.service-cat-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.service-cat-list__item { display: flex; align-items: center; gap: var(--sp-2); }
.service-cat-list__item.is-inactive { opacity: 0.6; }
.service-cat-row { flex: 1; display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
.service-cat-row__name { flex: 1 1 150px; min-width: 0; }
.service-cat-row__unit { flex: 0 1 100px; }
.service-cat-row__desc { flex: 2 1 200px; min-width: 0; }

@media (max-width: 720px) {
  .svc-audit__head { display: none; }
  .svc-audit__row {
    grid-template-columns: 16px 1fr auto;
    grid-template-areas:
      "dot client status"
      "dot service service"
      "dot qty    date";
    row-gap: 2px;
  }
  .svc-audit__row .dot { grid-area: dot; }
  .svc-audit__client { grid-area: client; }
  .svc-audit__service { grid-area: service; }
  .svc-audit__status { grid-area: status; justify-self: end; }
  .svc-audit__qty  { grid-area: qty;  color: var(--ink-3); font-size: var(--fs-xs); }
  .svc-audit__date { grid-area: date; justify-self: end; color: var(--ink-3); font-size: var(--fs-xs); }
}

/* ───── Client profile (/clients/{id}) ──────────────────────────────── */
.client-aside {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
/* Compact label/value list used inside the sidebar reference cards. Wraps the
   value below the key rather than overflowing a narrow card. */
.kv-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.kv-list__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px var(--sp-3);
  padding: 7px 0;
  font-size: var(--fs-sm);
  border-top: 1px solid var(--border);
}
.kv-list__row:first-child { border-top: 0; }
.kv-list__key {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  color: var(--ink);
}
.kv-list__val {
  margin-left: auto;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.kv-list__key--link, .kv-list__val--link { color: var(--accent); text-decoration: none; }
.kv-list__key--link:hover, .kv-list__val--link:hover { text-decoration: underline; }
.kv-list__val--link { word-break: break-all; text-align: right; }
/* Internal-notes thread spacing. */
.client-notes-thread { margin-top: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-2); }

/* ═══════════════════════════════════════════════════════════════════════
   Social Media Management board (/social)
   --client-accent is the ONE sanctioned per-client color (clients.social_color);
   it tints the board's accent bar + rail dot only. Single-edge accents use
   border-top + --r-none per the locked rule.
   ═══════════════════════════════════════════════════════════════════════ */
.soc-board { display: flex; flex-direction: column; gap: var(--sp-5); }
.soc-board--empty { min-height: 50vh; display: grid; place-items: center; }
.empty--lg { text-align: center; display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); }
.empty--lg .icon { color: var(--ink-3); margin-bottom: var(--sp-1); }

/* Header: client name + quick-launch links, with a thin accent bar on top. */
.soc-board__head {
  border-top: 3px solid var(--client-accent, var(--border-strong));
  border-radius: var(--r-none);
  padding-top: var(--sp-3);
  align-items: flex-start;
}
.soc-board__head-main { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.soc-links { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.client__quicklinks { margin: calc(var(--sp-1) * -1) 0 var(--sp-2); }
.soc-link {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 8px; min-height: 32px;
  border: 1px solid var(--border); border-radius: var(--r);
  color: var(--ink-2); background: var(--surface); text-decoration: none;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.soc-link:hover { background: var(--hover); border-color: var(--border-strong); color: var(--ink); }
.soc-link .icon { width: 16px; height: 16px; }
.soc-link__label { font-size: var(--fs-xs); max-width: 16ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Section count badge (sits in the native .panel__heading, right side). */
.soc-section__count {
  font-size: var(--fs-xs); font-weight: var(--fw-semi); color: var(--ink-3);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 999px; padding: 1px 9px; min-width: 22px; text-align: center;
}

/* Inline note composer ("+ Note"). */
.soc-note-add { }
.soc-note-add > summary { list-style: none; cursor: pointer; }
.soc-note-add > summary::-webkit-details-marker { display: none; }
.soc-note-add__form { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-2);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--sp-3); width: min(420px, 90vw); }

/* Notes row — info cards. */
.soc-notes { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--sp-3); }
.soc-note { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-2); }
.soc-note__head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-2); }
.soc-note__title { font-size: var(--fs-sm); font-weight: var(--fw-semi); margin: 0; min-width: 0; }
.soc-note__actions { display: flex; gap: var(--sp-1); flex: none; }
.soc-note__body { font-size: var(--fs-sm); color: var(--ink-2); }
.soc-note__edit { display: flex; flex-direction: column; gap: var(--sp-2); padding-top: var(--sp-2); border-top: 1px solid var(--border); }
.soc-note__edit-actions { display: flex; align-items: center; }
.soc-note__delete { margin-top: 2px; }

/* Filter/sort uses the native .filterbar — no custom toolbar CSS needed. */

/* ── Board cards — one per post (group). Large 1:1 media hero, then details. ── */
.soc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: var(--sp-3); align-items: start; }
.soc-post {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  overflow: hidden; display: flex; flex-direction: column; min-width: 0;
}
.soc-post__bar {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border);
}
.soc-post__date { display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-xs); color: var(--ink-3); white-space: nowrap; }
.soc-post__date .icon { width: 12px; height: 12px; }
.soc-post__date--sched { color: var(--ink-2); font-weight: var(--fw-medium); }
.soc-post__platforms { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; }
.soc-post__platforms { gap: 7px; }
.soc-post__platforms .icon { width: 17px; height: 17px; color: var(--ink-2); }
.soc-post__opts-wrap { position: relative; display: inline-flex; }
.soc-post__barbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: var(--r); border: none;
  background: transparent; color: var(--ink-3); cursor: pointer;
}
.soc-post__barbtn:hover { background: var(--hover); color: var(--ink); }
.soc-post__barbtn .icon { width: 17px; height: 17px; }
.soc-menu {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 20;
  min-width: 150px; padding: var(--sp-1);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--r); box-shadow: var(--shadow);
  display: flex; flex-direction: column; gap: 1px;
}
.soc-menu__item {
  display: block; width: 100%; text-align: left;
  padding: 6px 10px; border-radius: var(--r-sm); border: none; background: transparent;
  color: var(--ink); font: inherit; font-size: var(--fs-sm); cursor: pointer; text-decoration: none;
}
.soc-menu__item:hover { background: var(--hover); }
.soc-menu__item--danger { color: var(--danger); }
.soc-menu form { display: block; }

/* The 1:1 media hero — the post leads with its image. */
.soc-post__hero {
  display: block; position: relative; aspect-ratio: 1 / 1; width: 100%;
  background: var(--surface-2); overflow: hidden;
}
.soc-post__hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
.soc-post__hero-video, .soc-post__hero-empty, .soc-post__hero-doc {
  position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-1);
  color: var(--ink-3);
}
.soc-post__hero-video { background: #14161a; color: #fff; }
.soc-post__play {
  width: 46px; height: 46px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: rgb(0 0 0 / 0.45); border: 2px solid rgb(255 255 255 / 0.85);
}
.soc-post__play .icon { width: 18px; height: 18px; color: #fff; }
.soc-post__hero-doc .icon { width: 30px; height: 30px; }
.soc-post__hero-doc-name { font-size: var(--fs-xs); max-width: 80%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.soc-post__hero-empty { background: var(--surface-2); gap: 6px; }
.soc-post__hero-empty .icon { width: 26px; height: 26px; opacity: 0.4; }
.soc-post__hero-empty-label { font-size: var(--fs-xs); color: var(--ink-3); }
.soc-post__hero:hover .soc-post__hero-empty-label { color: var(--accent); }
.soc-post__hero-more {
  position: absolute; right: var(--sp-2); bottom: var(--sp-2);
  background: rgb(0 0 0 / 0.6); color: #fff; font-size: var(--fs-xs); font-weight: var(--fw-semi);
  padding: 2px 8px; border-radius: 999px;
}
.soc-post__detail { padding: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-2); }
.soc-post__title { font-size: var(--fs-sm); font-weight: var(--fw-semi); color: var(--ink); text-decoration: none; line-height: 1.35; }
.soc-post__title:hover { color: var(--accent); }
.soc-post__body {
  font-size: var(--fs-sm); color: var(--ink-2); line-height: var(--lh); white-space: pre-wrap; word-break: break-word;
  display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden;
}

/* Snippet insert chips (editor body) — clean clickable pills. */
.soc-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: var(--sp-2); }
.soc-chips__label { font-size: 10px; font-weight: var(--fw-semi); text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-3); }
.soc-chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.4;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px;
  padding: 3px 10px 3px 8px; cursor: pointer; transition: background .12s, border-color .12s, color .12s;
}
.soc-chip:hover { background: var(--hover); border-color: var(--border-strong, var(--border)); color: var(--ink); }
.soc-chip[hidden] { display: none; }
.soc-chip__kind {
  display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px;
  font-size: 10px; font-weight: var(--fw-semi); color: var(--ink-on-strong);
  background: var(--accent); border-radius: 999px; padding: 0 4px;
}
.soc-chip--cta .soc-chip__kind { background: var(--ink-3); }

/* Staged uploads — a styled "Choose files" label hiding the native input,
   a removable staged list, then the Upload button (revealed once files exist). */
.soc-upload { display: flex; flex-direction: column; gap: var(--sp-2); align-items: flex-start; }
.soc-upload__choose { cursor: pointer; }
.soc-upload__list { list-style: none; padding: 0; margin: 0; width: 100%; display: flex; flex-direction: column; gap: 4px; }
.soc-upload__list:empty { display: none; }
.soc-upload__item {
  display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-xs); color: var(--ink-2);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r); padding: 4px 8px;
}
.soc-upload__item .link { margin-left: auto; }
.soc-upload__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Standalone editor (/social/c/{id}/g/{group}) — dense, contained ── */
.soc-edit__main { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.soc-edit__aside { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.soc-edit__plats { display: inline-flex; align-items: center; gap: 5px; }
.soc-edit__plats .icon { width: 15px; height: 15px; color: var(--ink-2); vertical-align: middle; }
.soc-edit__aside-note { font-size: var(--fs-xs); color: var(--ink-3); }

/* Editor toolbar — one contained strip (Start task + bulk status). */
.soc-toolbar2 {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-3);
  margin: var(--sp-1) 0 var(--sp-4);
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
}
.soc-toolbar2__group { display: inline-flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin: 0; }
.label--bare { display: inline; margin: 0; }

/* Variant form-card (tight). */
.soc-variant { display: flex; flex-direction: column; gap: var(--sp-3); }
.soc-variant__head { display: flex; align-items: center; gap: var(--sp-2); padding-bottom: var(--sp-2); border-bottom: 1px solid var(--border); }
.soc-variant__platform { display: inline-flex; align-items: center; gap: 7px; font-size: var(--fs-md); font-weight: var(--fw-semi); }
.soc-variant__platform .icon { width: 18px; height: 18px; }
.soc-variant__live { margin-left: auto; font-size: var(--fs-xs); color: var(--accent); text-decoration: none; }
.soc-variant__live:hover { text-decoration: underline; }
.soc-variant__bodybar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.soc-variant__bodybar-r { display: inline-flex; align-items: center; gap: var(--sp-3); }
.soc-count { font-size: var(--fs-xs); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.soc-count--over { color: var(--danger); font-weight: var(--fw-semi); }
.soc-variant .soc-editor__textarea { resize: vertical; min-height: 110px; font-family: var(--font-mono, ui-monospace, monospace); font-size: var(--fs-sm); }
.soc-variant__foot { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding-top: var(--sp-2); border-top: 1px solid var(--border); }
.soc-variant__meta { font-size: var(--fs-xs); color: var(--ink-3); min-width: 0; }
.soc-variant__foot-actions { display: inline-flex; align-items: center; gap: var(--sp-2); flex: none; }

/* Add-a-blank-platform disclosure. */
.soc-edit__add-wrap { }
.soc-edit__add-summary { font-size: var(--fs-xs); color: var(--ink-2); cursor: pointer; padding: var(--sp-1) 0; }
.soc-edit__add { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin-top: var(--sp-2); }

/* Media grid (aside). */
.soc-edit__media-grid { list-style: none; padding: 0; margin: 0 0 var(--sp-3); display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: var(--sp-2); }
.soc-edit__media-item { position: relative; display: flex; flex-direction: column; gap: 3px; }
.soc-edit__media-grip { position: absolute; top: 3px; left: 3px; z-index: 2; display: inline-flex; padding: 2px; border-radius: var(--r-sm); background: rgb(0 0 0 / 0.45); color: #fff; cursor: grab; }
.soc-edit__media-grip .icon { width: 11px; height: 11px; }
.soc-edit__media-thumb { display: block; border-radius: var(--r); overflow: hidden; border: 1px solid var(--border); aspect-ratio: 1 / 1; background: #000; }
.soc-edit__media-thumb img, .soc-edit__media-thumb { width: 100%; height: 100%; object-fit: cover; }
.soc-edit__media-doc { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; background: var(--surface-2); color: var(--ink-2); text-decoration: none; }
.soc-edit__media-name { font-size: 10px; max-width: 90%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.soc-edit__media-bar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-1); }
.soc-edit__media-bar .link { font-size: 11px; }
.sortable-ghost { opacity: 0.4; }

.soc-edit__group-form { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.soc-edit__group-form .input { flex: 1 1 auto; min-width: 0; }
.label--inline { font-size: var(--fs-xs); color: var(--ink-3); margin: 0; }
.form-help-inline { font-weight: var(--fw-regular); color: var(--ink-3); font-size: var(--fs-xs); text-transform: none; letter-spacing: 0; }

/* Snippet manager — flat tag-list pattern (mirrors /time/tags). */
.soc-snips-manager { margin-top: var(--sp-2); }
.panel__heading-count { font-size: var(--fs-xs); color: var(--ink-3); background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px; font-variant-numeric: tabular-nums; }
.panel__hint { font-size: var(--fs-xs); color: var(--ink-3); margin: 0 0 var(--sp-3); }
.soc-snip-list { list-style: none; padding: 0; margin: 0 0 var(--sp-3); display: flex; flex-direction: column; gap: 6px; }
.soc-snip-row { border: 1px solid var(--border); border-radius: var(--r); background: var(--surface); }
.soc-snip-row__main { display: flex; align-items: center; gap: var(--sp-2); padding: 7px var(--sp-2) 7px var(--sp-3); }
.soc-snip-row__badge {
  flex: none; display: inline-flex; align-items: center; justify-content: center; min-width: 20px; height: 18px;
  font-size: 10px; font-weight: var(--fw-semi); color: var(--ink-on-strong); background: var(--accent);
  border-radius: var(--r-sm); padding: 0 5px;
}
.soc-snip-row__badge--cta { background: var(--ink-3); }
.soc-snip-row__plat { flex: none; display: inline-flex; align-items: center; gap: 5px; font-size: var(--fs-xs); color: var(--ink-2); }
.soc-snip-row__plat .icon { width: 14px; height: 14px; }
.soc-snip-row__plat-name { white-space: nowrap; }
.soc-snip-row__label { flex: none; font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--ink); max-width: 30%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.soc-snip-row__preview { flex: 1 1 auto; min-width: 0; font-size: var(--fs-xs); color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--font-mono, ui-monospace, monospace); }
.soc-snip-row__actions { flex: none; display: inline-flex; align-items: center; gap: 2px; }
.soc-snip-row__edit { padding: 0 var(--sp-3) var(--sp-3); border-top: 1px solid var(--border); margin-top: -1px; }
.soc-snip-row__edit[hidden] { display: none; }
.soc-snip-form { display: flex; flex-direction: column; gap: var(--sp-2); padding-top: var(--sp-3); }
.soc-snip-form--add { border: 1px dashed var(--border); border-radius: var(--r); padding: var(--sp-3); }
.soc-snip-form__foot { display: flex; align-items: center; gap: var(--sp-2); }
.form-grid--3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-2); }

/* Board / Calendar segmented toggle (contained pill — a control, so --r). */
.soc-viewtoggle { display: inline-flex; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r); padding: 2px; gap: 2px; }
.soc-viewtoggle__opt {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-sm); color: var(--ink-2); text-decoration: none;
  padding: 4px 12px; border-radius: var(--r-sm); line-height: 1.4;
}
.soc-viewtoggle__opt .icon { color: currentColor; }
.soc-viewtoggle__opt:hover { color: var(--ink); }
.soc-viewtoggle__opt.is-active { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm, 0 1px 2px rgb(0 0 0 / 0.08)); font-weight: var(--fw-medium); }

/* ── Calendar view ── */
.soc-cal__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.soc-cal__nav { display: inline-flex; align-items: center; gap: var(--sp-2); }
.soc-cal__title { font-size: var(--fs-lg); font-weight: var(--fw-semi); margin: 0; min-width: 9ch; text-align: center; }
/* The month grid IS the page's main substance → no radius (per the locked
   radius rule: tables/main containers use --r-none; only floaters round). */
.soc-cal__grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--r-none); overflow: hidden; }
.soc-cal__dow { background: var(--surface-2); color: var(--ink-3); font-size: var(--fs-xs); font-weight: var(--fw-semi); text-transform: uppercase; letter-spacing: 0.04em; text-align: center; padding: 6px 0; }
.soc-cal__cell { background: var(--surface); min-height: 104px; padding: 5px; display: flex; flex-direction: column; gap: 4px; }
.soc-cal__cell.is-outside { background: var(--surface-2); }
.soc-cal__cell.is-outside .soc-cal__daynum { color: var(--ink-4, var(--ink-3)); opacity: 0.55; }
.soc-cal__cell-head { display: flex; align-items: center; justify-content: flex-end; }
.soc-cal__daynum { font-size: var(--fs-xs); font-weight: var(--fw-medium); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.soc-cal__cell.is-today { box-shadow: inset 0 0 0 2px var(--client-accent, var(--accent)); }
.soc-cal__cell.is-today .soc-cal__daynum {
  display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px;
  background: var(--client-accent, var(--accent)); color: var(--ink-on-strong); border-radius: 999px;
}
.soc-cal__events { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.soc-cal__event {
  display: flex; align-items: center; gap: 5px; min-width: 0;
  padding: 3px 6px; border-radius: var(--r-sm); text-decoration: none;
  font-size: var(--fs-xs); line-height: 1.3;
  background: var(--surface-2); color: var(--ink-2); border: 1px solid var(--border);
}
.soc-cal__event:hover { border-color: var(--border-strong); }
.soc-cal__event-plats { flex: none; display: inline-flex; align-items: center; gap: 2px; }
.soc-cal__event-plats .icon { width: 12px; height: 12px; }
.soc-cal__event-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Status tints reuse the .status-pill palette (no invented colors). */
.soc-cal__event--approved { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.soc-cal__event--scheduled { border-color: var(--border-strong); color: var(--ink); }
.soc-cal__event--posted { background: var(--accent); color: var(--ink-on-strong); border-color: transparent; }
.soc-cal__event--posted .icon { color: var(--ink-on-strong); }
.soc-cal__unsched-list { display: flex; flex-wrap: wrap; gap: 6px; }
.soc-cal__unsched-list .soc-cal__event { max-width: 260px; }

/* Floating add button — mobile only. The one intentional circular floater. */
.soc-fab { display: none; }
@media (max-width: 720px) {
  .soc-fab {
    display: inline-flex; align-items: center; justify-content: center;
    position: fixed; right: var(--sp-3); bottom: var(--sp-3); z-index: 40;
    width: 52px; height: 52px; border-radius: 50%;
    background: var(--accent); color: var(--ink-on-strong);
    border: none; box-shadow: var(--shadow); text-decoration: none;
  }
  .soc-fab svg { width: 22px; height: 22px; }
  .soc-grid, .soc-notes { grid-template-columns: 1fr 1fr; }
  .form-grid--3 { grid-template-columns: 1fr; }
  /* Touch targets: icon-buttons + small action links get a finger-sized hit area. */
  .soc-variant__bodybar .icon-btn,
  .soc-note__actions .icon-btn,
  .soc-snip-row__actions .icon-btn,
  .soc-edit__media-bar .icon-btn,
  .soc-edit__media-bar form .icon-btn {
    width: 40px; height: 40px;
  }
  .soc-variant__bodybar .icon-btn .icon,
  .soc-note__actions .icon-btn .icon,
  .soc-edit__media-bar .icon-btn .icon { width: 18px; height: 18px; }
  .soc-variant__foot .btn,
  .soc-variant__foot .link { min-height: 42px; }
  .soc-variant__foot { flex-wrap: wrap; }
  .soc-toolbar2 { flex-direction: column; align-items: stretch; }
  .soc-post__barbtn { width: 40px; height: 40px; }
  .soc-post__barbtn .icon { width: 20px; height: 20px; }
  .soc-menu__item { padding: 12px 12px; }
  /* Calendar: tighter cells, smaller weekday labels; events stay tappable. */
  .soc-cal__cell { min-height: 76px; padding: 4px; }
  .soc-cal__dow { font-size: 10px; }
  .soc-viewtoggle__opt { padding: 8px 12px; }
  .soc-board__head .page__header-actions { flex-wrap: wrap; }
}
@media (max-width: 460px) {
  .soc-grid, .soc-notes { grid-template-columns: 1fr; }
  /* Calendar weekday headers abbreviate to one letter to fit 7 columns. */
  .soc-cal__dow { letter-spacing: 0; padding: 4px 0; }
  .soc-cal__event-title { display: none; }
  .soc-cal__cell { min-height: 58px; }
}
