/* Shared design tokens + base styles for every dashboard page. Page-specific
   styles (search box, item cards, tables, login card, ...) stay local to
   each view's own <style> block; only what's common lives here. */

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Sans+Thai:wght@400;500;600;700&display=swap');

:root {
  --bg: #f7f8fa;
  --fg: #10141c;
  --muted: #667085;
  /* Was #e3e7ee (1.24:1 against --card-bg) — nudged darker for real card/
     table/input separation instead of relying on shadow alone; still well
     under the 3:1 non-text-contrast target on purpose (a fully "boxed"
     look isn't this app's style), just no longer nearly invisible. */
  --border: #d5dbe6;
  /* Brand red — sampled directly from the CODESMASH CO., LTD. logo
     (public/brand/mark-*.png), not eyeballed. --accent is the widely-used
     text/border/underline token (nav active tab, campaign chip, links,
     etc.) — a single value works for that role in light mode since it
     already clears 4.5:1 against both white (button text) and the page
     background (text-on-bg). Dark mode can't do that with one value (see
     below), which is why --accent-button exists as a separate pair used
     only by the two actual solid-fill-with-white-text spots (.btn-primary,
     .skip-link) — verified: #CE231E vs white 5.41:1, vs --bg #f7f8fa
     5.09:1. */
  --accent: #ce231e;
  --accent-hover: #a81c18;
  --accent-button: #ce231e;
  --accent-button-hover: #a81c18;
  /* Was #fdeceb (contrast(accent, soft) 4.29:1 already, just very pale) —
     deepened enough that accent-tinted chips/badges (nav active state,
     campaign chip) read as colored at a glance, while keeping accent-on-
     soft text at 4.63:1 (still ≥ 4.5 AA). Hue re-matched to the new brand
     red (was a near-identical hue anyway). */
  --accent-soft: #fbe0e1;
  --accent-fg: #ffffff;
  /* Helmet-facet charcoal, sampled from the same logo — a brand-owned
     near-black, distinct from --fg's neutral text color, reserved for
     brand touchpoints (login, nav mark treatment) rather than general UI. */
  --brand-dark: #231f20;
  --card-bg: #ffffff;
  --card-bg-muted: #f1f4f8;
  --checker-a: #eef0f3;
  --checker-b: #e3e7ee;
  --code-bg: #12151b;
  --code-fg: #e7e9ee;
  --focus-ring: rgba(206, 35, 30, 0.35);
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
  --shadow-hover: 0 6px 20px -4px rgba(16, 24, 40, 0.10);
  --shadow-lg: 0 20px 50px -12px rgba(16, 24, 40, 0.18), 0 2px 6px rgba(16, 24, 40, 0.06);
  --ok: #15803d;
  --ok-soft: #e8f5ec;
  --info: #1d4ed8;
  --info-soft: #e9effd;
  --warn: #b45309;
  --warn-soft: #fdf1e0;
  /* Solid-background text pairs for the escalated status chip/banner
     (At Risk / Off Track only — see lib/campaignSidebar.js) — NOT the
     same as --accent-fg's fixed white, because --warn's dark-theme value
     is a bright yellow (see below) where white text fails contrast
     outright (1.67:1) while dark text passes easily (11.13:1). Verified
     numerically, not eyeballed: light --warn + white = 5.02:1,
     dark --warn + dark text = 11.13:1, light --danger + white = 6.57:1,
     dark --danger + dark text = 6.59:1 — all comfortably ≥ 4.5:1 AA.
     A single global "escalated-fg: white" would silently fail in dark
     mode, so each theme block below sets its own pair. */
  --warn-fg: #ffffff;
  --danger: #b42318;
  --danger-soft: #fee4e2;
  --danger-fg: #ffffff;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0b0d12;
    --fg: #e7e9ee;
    --muted: #939aa8;
    --border: #2c3341;
    /* Same hue as the light theme's brand red (~358°), just re-lightened
       for the two different jobs it does — see the light :root block's
       comment. --accent (text-on-dark-bg role, unchanged lightness from
       before): vs --bg #0b0d12 = 6.62:1. --accent-button (solid-fill +
       white text role): #d50b12 = 5.4:1 vs white; hover #e90c14 = 4.64:1
       vs white, brighter than the base as this theme's hover convention
       already does elsewhere. */
    --accent: #ff6166;
    --accent-hover: #ff7a7e;
    --accent-button: #d50b12;
    --accent-button-hover: #e90c14;
    --accent-soft: #3a1c1c;
    --accent-fg: #ffffff;
    --brand-dark: #231f20;
    --card-bg: #12151b;
    --card-bg-muted: #171b22;
    --checker-a: #1a1e25;
    --checker-b: #12151b;
    --code-bg: #0b0d12;
    --code-fg: #e7e9ee;
    --focus-ring: rgba(255, 97, 102, 0.4);
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
    --shadow-hover: 0 8px 24px -4px rgba(0, 0, 0, 0.45);
    --shadow-lg: 0 20px 50px -12px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.3);
    --ok: #4ade80;
    --ok-soft: #122a1a;
    --info: #7ea8ff;
    --info-soft: #16233d;
    --warn: #fbbf24;
    --warn-soft: #2d2410;
    /* Dark text — --warn is a bright yellow here, where white text fails
       contrast (1.67:1); see the light :root block's comment for the
       measured numbers behind every value in this pair. */
    --warn-fg: #1a1200;
    --danger: #f97066;
    --danger-soft: #451a1a;
    --danger-fg: #2a0a06;
  }
}
:root[data-theme="dark"] {
  --bg: #0b0d12;
  --fg: #e7e9ee;
  --muted: #939aa8;
  --border: #2c3341;
  --accent: #ff6166;
  --accent-hover: #ff7a7e;
  --accent-button: #d50b12;
  --accent-button-hover: #e90c14;
  --accent-soft: #3a1c1c;
  --accent-fg: #ffffff;
  --brand-dark: #231f20;
  --card-bg: #12151b;
  --card-bg-muted: #171b22;
  --checker-a: #1a1e25;
  --checker-b: #12151b;
  --code-bg: #0b0d12;
  --code-fg: #e7e9ee;
  --focus-ring: rgba(255, 97, 102, 0.4);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-hover: 0 8px 24px -4px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 20px 50px -12px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.3);
  --ok: #4ade80;
  --ok-soft: #122a1a;
  --info: #7ea8ff;
  --info-soft: #16233d;
  --warn: #fbbf24;
  --warn-soft: #2d2410;
  --warn-fg: #1a1200;
  --danger: #f97066;
  --danger-soft: #451a1a;
  --danger-fg: #2a0a06;
}
@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Noto Sans Thai", "Sarabun", "Segoe UI", Tahoma, sans-serif;
  background: var(--bg);
  color: var(--fg);
  -webkit-font-smoothing: antialiased;
}

.wrap {
  max-width: var(--content-max-width, 960px);
  margin: 0 auto;
  padding: 32px 24px 80px;
}

a.back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 13px;
  text-decoration: none;
  margin-bottom: 18px;
}
a.back-link:hover { color: var(--accent); }
a.back-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* Top nav — shared across authenticated pages (see lib/nav.js) */
.site-nav {
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
.site-nav-inner {
  max-width: 1120px;
  margin: 0 auto;
  padding: 12px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.site-nav-brand-group {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}
.site-nav-brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: 15px;
  color: var(--fg);
  text-decoration: none;
  white-space: nowrap;
}
.site-nav-brand:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.site-nav-brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
}
.campaign-chip {
  display: inline-flex;
  align-items: center;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
  padding: 3px 10px;
  border-radius: 999px;
  text-decoration: none;
  white-space: nowrap;
}
.campaign-chip:hover { filter: brightness(0.95); }
.campaign-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.site-nav-links {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}
.site-nav-links a {
  color: var(--muted);
  text-decoration: none;
  font-size: 13.5px;
  font-weight: 500;
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  transition: color 0.15s ease, background 0.15s ease;
}
.site-nav-links a:hover { color: var(--fg); background: var(--card-bg-muted); }
.site-nav-links a.active { color: var(--accent); font-weight: 600; background: var(--accent-soft); }
.site-nav-links a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Admin-only dropdown (Comments/Polish plan Phase 2) — same
   data-popover-toggle/data-popover contract as .feed-item-menu, wired by
   theme.js's shared wirePopoverToggles(). Trigger styled to match the
   plain nav links around it, not like a form button. */
.nav-admin-dropdown { position: relative; }
.nav-admin-dropdown-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--muted);
  background: none;
  border: none;
  font-size: 13.5px;
  font-weight: 500;
  font-family: inherit;
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
.nav-admin-dropdown-toggle:hover, .nav-admin-dropdown-toggle[aria-expanded="true"] { color: var(--fg); background: var(--card-bg-muted); }
.nav-admin-dropdown-toggle.active { color: var(--accent); font-weight: 600; background: var(--accent-soft); }
.nav-admin-dropdown-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* No `display` set here (children are already block-level via the `a`
   rule below) — the browser's own `[hidden] { display: none }` default
   is what actually hides this; setting `display: flex` unconditionally
   would override that default and defeat the `hidden` attribute
   entirely (caught by an e2e test expecting it to actually close). */
.nav-admin-dropdown-popover {
  position: absolute;
  left: 0;
  top: 38px;
  z-index: 20;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-hover);
  padding: 4px;
  min-width: 160px;
}
.nav-admin-dropdown-popover a:not(:last-child) { margin-bottom: 2px; }
.nav-admin-dropdown-popover a {
  display: block;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: var(--fg);
  text-decoration: none;
  font-size: 13.5px;
  font-weight: 500;
}
.nav-admin-dropdown-popover a:hover { background: var(--card-bg-muted); }
.nav-admin-dropdown-popover a.active { color: var(--accent); font-weight: 600; }
.nav-admin-dropdown-popover a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.site-nav-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--muted);
  cursor: pointer;
  flex-shrink: 0;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.theme-toggle:hover { color: var(--fg); border-color: var(--accent); }
.theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.theme-toggle svg { display: block; }

.lang-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  height: 34px;
  padding: 0 8px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  flex-shrink: 0;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.lang-toggle:hover { color: var(--fg); border-color: var(--accent); }
.lang-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

form.logout button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  color: var(--muted);
  padding: 7px 14px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
form.logout button:hover { color: var(--fg); border-color: var(--accent); }
form.logout button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.copy-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 1px solid var(--border);
  color: var(--muted);
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
  flex-shrink: 0;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.copy-btn:hover { color: var(--fg); border-color: var(--accent); }
.copy-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.copy-btn.copied { color: var(--accent); border-color: var(--accent); }
.copy-btn svg { display: block; flex-shrink: 0; }

/* Skip link — hidden until keyboard-focused, jumps past the nav to #main. */
.skip-link {
  position: absolute;
  left: 12px;
  top: -48px;
  background: var(--accent-button);
  color: var(--accent-fg);
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  z-index: 100;
  transition: top 0.15s ease;
}
.skip-link:focus { top: 12px; }

/* @mention notification bell (Comments/Polish plan Phase 4) — same
   data-popover-toggle/data-popover contract as the admin dropdown and
   feed-item menu, wired by theme.js's shared wirePopoverToggles(). */
.nav-notifications { position: relative; flex-shrink: 0; }
.nav-notifications-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: none;
  color: var(--muted);
  cursor: pointer;
}
.nav-notifications-toggle:hover, .nav-notifications-toggle[aria-expanded="true"] {
  color: var(--fg);
  border-color: var(--border);
}
.nav-notifications-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nav-notifications-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  border-radius: 999px;
  background: var(--danger);
  color: var(--danger-fg);
  font-size: 10px;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
}
.nav-notifications-popover {
  position: absolute;
  right: 0;
  top: 40px;
  z-index: 20;
  width: 320px;
  max-height: 420px;
  overflow-y: auto;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-hover);
}
.nav-notifications-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  font-weight: 600;
  position: sticky;
  top: 0;
  background: var(--card-bg);
}
.nav-notifications-mark-all {
  border: none;
  background: none;
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  padding: 0;
}
.nav-notifications-mark-all:hover { text-decoration: underline; }
.nav-notifications-list { display: flex; flex-direction: column; }
.nav-notification-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  text-decoration: none;
  color: var(--fg);
}
.nav-notification-item:last-child { border-bottom: none; }
.nav-notification-item:hover { background: var(--card-bg-muted); }
.nav-notification-item.unread { background: var(--accent-soft); }
.nav-notification-item.unread:hover { background: var(--accent-soft); filter: brightness(0.97); }
.nav-notification-from { font-size: 12.5px; font-weight: 600; }
.nav-notification-excerpt {
  font-size: 12.5px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.nav-notification-time { font-size: 11px; color: var(--muted); }
.nav-notifications-empty { padding: 24px 14px; text-align: center; color: var(--muted); font-size: 13px; }
.nav-notifications-viewall {
  display: block;
  padding: 10px 14px;
  text-align: center;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
  border-top: 1px solid var(--border);
  position: sticky;
  bottom: 0;
  background: var(--card-bg);
}
.nav-notifications-viewall:hover { text-decoration: underline; }
@media (max-width: 520px) {
  .nav-notifications-popover { width: 280px; right: -60px; }
}

/* Signed-in user chip in the nav actions area. */
.nav-user {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.nav-user-avatar {
  width: 26px;
  height: 26px;
  border-radius: 999px;
  flex-shrink: 0;
  object-fit: cover;
}
.nav-user-avatar-fallback {
  width: 26px;
  height: 26px;
  border-radius: 999px;
  flex-shrink: 0;
  background: var(--accent-soft);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
}
.nav-user-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--fg);
  white-space: nowrap;
}
@media (max-width: 520px) {
  .nav-user-name { display: none; }
}

/* Semantic status badge — color/background set by a modifier class. */
.type-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11.5px;
  font-weight: 700;
  padding: 2px 9px;
  border-radius: 999px;
}
.type-badge.update   { color: var(--ok);   background: var(--ok-soft); }
.type-badge.decision { color: var(--info); background: var(--info-soft); }
.type-badge.risk     { color: var(--warn); background: var(--warn-soft); }

.btn-primary, .btn-secondary, .btn-danger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 38px;
  padding: 0 16px;
  border-radius: var(--radius-sm);
  font-size: 13.5px;
  font-weight: 600;
  font-family: inherit;
  text-decoration: none;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, opacity 0.15s ease;
}
.btn-primary {
  background: var(--accent-button);
  color: var(--accent-fg);
}
.btn-primary:hover { background: var(--accent-button-hover); }
.btn-secondary {
  background: var(--card-bg);
  border-color: var(--border);
  color: var(--muted);
}
.btn-secondary:hover { color: var(--fg); border-color: var(--accent); }
/* --warn (amber), not --danger (red) — this app's brand --accent is
   itself deep red (#ce231e, sampled from the CODESMASH logo), nearly
   indistinguishable from --danger next to a red .btn-primary "Save"
   (confirmed by screenshot review). The existing two-step confirm
   pattern (.btn-danger-armed) already made this exact call for the same
   reason — amber is what actually reads as "different from the ordinary
   primary action" in this specific palette. */
.btn-danger {
  background: var(--warn);
  color: var(--warn-fg);
}
.btn-danger:hover { opacity: 0.9; }
.btn-primary:focus-visible, .btn-secondary:focus-visible, .btn-danger:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn-primary:disabled, .btn-secondary:disabled, .btn-danger:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.btn-primary svg, .btn-secondary svg, .btn-danger svg { display: block; flex-shrink: 0; }

/* Destructive settings section (Comments/Polish plan Phase 6) — visually
   set apart with a danger-tinted border so it doesn't blend into the
   ordinary rename form above it. */
.danger-zone {
  margin-top: 40px;
  padding: 18px 20px;
  border: 1px solid var(--danger);
  border-radius: var(--radius-md);
  background: var(--card-bg);
}
.danger-zone h2 { font-size: 14px; margin: 0 0 8px; color: var(--danger); }
.danger-zone-warning, .danger-zone-blocked { font-size: 13px; color: var(--muted); margin: 0 0 14px; line-height: 1.5; }
.campaign-delete-form { display: flex; flex-direction: column; gap: 12px; }
.campaign-delete-form .field { display: flex; flex-direction: column; gap: 6px; }
.campaign-delete-form label { font-size: 13px; font-weight: 600; }
.campaign-delete-form input[type="text"] {
  height: 38px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg);
  color: var(--fg);
  font-size: 14px;
  font-family: inherit;
  max-width: 320px;
}
.campaign-delete-form input[type="text"]:focus { outline: none; border-color: var(--warn); box-shadow: 0 0 0 3px var(--warn-soft); }
.campaign-delete-form .btn-danger { align-self: flex-start; }

/* Shared "card" surface — `class="card ..."` is used everywhere (composer,
   feed items, requirement cards, search results, handbook/image-guideline
   tiles) but until now nothing actually defined `.card` itself outside
   campaign-home.html's own local copy, so every one of those was a bare,
   invisible <div>/<a> with no background, border, or shadow at all except
   on that one page. This is the single base every page-specific block below
   (.composer, .feed-item, .requirement-card, .search-result, ...) builds on
   — they only need to override padding/margin, not re-declare the surface. */
.card {
  display: block;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow);
  padding: 14px 16px;
  color: var(--fg);
}
a.card {
  text-decoration: none;
  transition: box-shadow 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
}
a.card:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-hover);
  transform: translateY(-1px);
}
a.card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Confirm-to-delete micro-pattern (theme.js data-confirm behavior). */
.btn-danger-armed {
  background: var(--warn-soft) !important;
  border-color: var(--warn) !important;
  color: var(--warn) !important;
  gap: 8px;
  font-size: 11.5px;
  white-space: nowrap;
}
.btn-danger-armed .confirm-cancel {
  text-decoration: underline;
  cursor: pointer;
  opacity: 0.8;
}
.btn-danger-armed .confirm-cancel:hover { opacity: 1; }

/* Campaign sub-nav tab bar (see lib/campaignNav.js). */
.campaign-nav {
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
.campaign-nav-inner {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 24px;
  display: flex;
  align-items: center;
  gap: 16px;
}
/* The tab list scrolls horizontally on its own — the search box (below)
   stays docked outside this scroll area so it's never the thing that
   scrolls out of view. */
.campaign-nav-tabs {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  scrollbar-width: none;
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 0;
}
.campaign-nav-tabs::-webkit-scrollbar { display: none; }
.campaign-nav-inner a {
  display: inline-flex;
  align-items: center;
  height: 44px;
  padding: 0 4px;
  color: var(--muted);
  text-decoration: none;
  font-size: 13.5px;
  font-weight: 500;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 0.15s ease;
}
.campaign-nav-inner a:hover { color: var(--fg); }
.campaign-nav-inner a.active { color: var(--accent); font-weight: 600; border-bottom-color: var(--accent); }
.campaign-nav-inner a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tab-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  margin-left: 6px;
  border-radius: 9px;
  /* --card-bg-muted on --bg measured at 1.04:1 (light) / 1.13:1 (dark)
     contrast — the pill was functionally invisible against the page.
     --border fill alone only reaches 1.31:1/1.53:1 (still under the ~3:1
     non-text-boundary guideline), so the outline is the load-bearing
     part here, not the fill. */
  background: var(--border);
  border: 1px solid var(--muted);
  color: var(--fg);
  font-size: 11px;
  font-weight: 600;
}
.campaign-nav-inner a.active .tab-badge { background: var(--accent-soft); border-color: transparent; color: var(--accent); }

/* Persistent campaign-wide search, docked to the right of the tab bar
   (project-hub UX plan — moved here from the Handbooks page, since it
   always searched the whole campaign, not just Handbooks). Always
   visible rather than icon-triggered: this is a desktop-first internal
   tool, not a space-constrained consumer app, so discoverability wins
   over the extra few px. */
.campaign-nav-search {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
}
.campaign-nav-search svg {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted);
  pointer-events: none;
}
.campaign-nav-search input {
  width: 200px;
  height: 32px;
  padding: 0 10px 0 32px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
  font-size: 13px;
  font-family: inherit;
  transition: width 0.15s ease;
}
.campaign-nav-search input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  width: 260px;
}
@media (max-width: 700px) {
  .campaign-nav-inner { flex-wrap: wrap; padding-top: 8px; padding-bottom: 8px; row-gap: 8px; }
  .campaign-nav-search { flex: 1 1 100%; order: 2; }
  .campaign-nav-search input,
  .campaign-nav-search input:focus { width: 100%; }
}

/* Persistent campaign metadata panel (project-hub UX plan Phase 1/3) —
   second column of .wrap.with-sidebar on the 5 read-heavy campaign pages;
   deliberately absent from the Requirement editor and search results. */
.wrap.with-sidebar {
  /* .wrap's own max-width caps the WHOLE grid (main + sidebar together) —
     without widening it here, the sidebar's 280px + gap got carved out of
     the main column's already-tuned reading width instead of sitting
     alongside it (e.g. post-feed's 680px main column was shrinking to a
     cramped ~376px). Adding the sidebar's footprint back restores each
     page's original content width and simply extends the page to fit
     the sidebar next to it. */
  max-width: calc(var(--content-max-width, 960px) + 280px + 24px);
  display: grid;
  grid-template-columns: 1fr 280px;
  gap: 24px;
  align-items: start;
}
.wrap.with-sidebar .wrap-main { min-width: 0; }
@media (max-width: 900px) {
  .wrap.with-sidebar { grid-template-columns: 1fr; max-width: var(--content-max-width, 960px); }
}
.campaign-sidebar {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 16px;
  position: sticky;
  top: 24px;
}
@media (max-width: 900px) {
  .campaign-sidebar { position: static; }
}
.sidebar-section-title {
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
  margin: 0 0 8px;
}
.sidebar-section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.sidebar-manage-link {
  font-size: 11px;
  color: var(--accent);
  text-decoration: none;
  flex-shrink: 0;
}
.sidebar-manage-link:hover { text-decoration: underline; }
.sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}
.sidebar-campaign-name { font-weight: 700; font-size: 15px; }

/* Live presence (access-history-versioning-plan.md Phase 4) — populated
   entirely client-side by public/presence.js; [hidden] on the server-
   rendered shell until the first poll resolves, scoped with :not([hidden])
   below rather than an unconditional display so the [hidden] attribute
   toggle keeps working the way every other popover in this app relies on. */
.sidebar-presence:not([hidden]) {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
.presence-avatars { display: flex; }
.presence-avatars .presence-avatar, .presence-avatars .avatar-fallback {
  width: 22px;
  height: 22px;
  border-radius: 999px;
  border: 2px solid var(--card-bg);
  margin-left: -8px;
  flex-shrink: 0;
}
.presence-avatars .presence-avatar:first-child,
.presence-avatars .avatar-fallback:first-child { margin-left: 0; }
.presence-avatar { object-fit: cover; }
.presence-avatars .avatar-fallback {
  /* Same accent-soft/accent pair every other avatar fallback in the app
     uses (composer, feed item, comments, Groups, admin rows) — this used
     to be var(--ok-soft)/var(--ok) (green), the one place that didn't
     match. */
  background: var(--accent-soft);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 700;
}
.presence-label { font-size: 12px; color: var(--muted); }

.status-chip {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border-radius: 12px;
  font-size: 11.5px;
  font-weight: 600;
}
.status-chip-on-track { background: var(--ok-soft); color: var(--ok); }
/* At Risk/Off Track base rule stays as a fallback for any spot that
   somehow renders without .status-chip-escalated — in normal use
   renderStatusChip() always adds that class for these two statuses, so
   the rules right below are what actually paints in practice. */
.status-chip-at-risk { background: var(--warn-soft); color: var(--warn); }
.status-chip-off-track { background: var(--danger-soft); color: var(--danger); }

/* Escalated (At Risk / Off Track only) — solid, saturated background +
   icon, deliberately louder than On Track's quiet soft-tint. See
   theme.css's --warn-fg/--danger-fg comment (top of file) for why the
   text color isn't just a fixed white: dark theme's --warn is a bright
   yellow where white text fails contrast outright. */
.status-chip-escalated {
  gap: 4px;
  padding: 4px 10px 4px 8px;
  font-size: 12px;
}
.status-chip-at-risk.status-chip-escalated { background: var(--warn); color: var(--warn-fg); }
.status-chip-off-track.status-chip-escalated { background: var(--danger); color: var(--danger-fg); }

/* Full-width banner under the campaign nav (see
   lib/campaignSidebar.js's renderStatusBanner) — only rendered at all
   when status is At Risk/Off Track, so a healthy campaign never shows
   this element. No dismiss control on purpose: it should keep
   reappearing until someone actually changes the status. */
.status-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: calc(var(--content-max-width, 960px) + 280px + 24px);
  margin: 0 auto;
  padding: 12px 24px;
}
.status-banner-at-risk { background: var(--warn); color: var(--warn-fg); }
.status-banner-off-track { background: var(--danger); color: var(--danger-fg); }
.status-banner-icon { display: flex; flex-shrink: 0; }
.status-banner-body {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex: 1;
  min-width: 0;
}
.status-banner-label { font-weight: 700; font-size: 13.5px; flex-shrink: 0; }
.status-banner-excerpt {
  font-size: 13px;
  opacity: 0.92;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.status-banner-link {
  flex-shrink: 0;
  color: inherit;
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: underline;
}
@media (max-width: 900px) {
  .status-banner { flex-wrap: wrap; }
  .status-banner-excerpt { white-space: normal; }
}
@media (max-width: 520px) {
  .status-banner { padding: 12px 16px; }
}

/* The only place status gets changed now — every change routes through
   posting an Update (see server.js's statusChangeValue), so this is a form
   that navigates (GET) rather than a direct mutation. */
.sidebar-status-change {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.sidebar-status-change-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
}
.sidebar-status-change-label select {
  height: 30px;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg);
  color: var(--fg);
  font-size: 12px;
  font-family: inherit;
}
.sidebar-status-change-btn { height: 30px; padding: 0 10px; font-size: 12px; }

/* Glance summary above the full list (Comments/Polish plan Phase 9) —
   "which milestone did we reach, when's the next one" without scanning
   the list below for the one bold row. Reuses --ok/--accent, already
   established by .milestone-marker-past/-next, rather than new tokens. */
.milestone-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
  font-size: 12.5px;
  margin-bottom: 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
.milestone-summary-current { color: var(--ok); font-weight: 600; display: inline-flex; align-items: center; gap: 4px; }
.milestone-summary-next { color: var(--fg); font-weight: 600; }
.milestone-summary-arrow { color: var(--muted); font-weight: 400; }
.milestone-summary-date { color: var(--muted); font-weight: 400; }
.milestone-summary-muted { color: var(--muted); font-weight: 500; }

.milestone-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.milestone { display: flex; align-items: center; gap: 8px; font-size: 12.5px; }
.milestone-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  border-radius: 50%;
}
.milestone-marker-past { color: var(--ok); }
.milestone-marker-next { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.milestone-marker-future { background: var(--border); }
.milestone-past { color: var(--muted); }
.milestone-past .milestone-label { text-decoration: line-through; }
.milestone-next { color: var(--fg); font-weight: 600; }
.milestone-future { color: var(--muted); }
.milestone-label { flex: 1; }
.milestone-date { color: var(--muted); font-size: 11.5px; white-space: nowrap; }

.sidebar-stats { display: flex; flex-direction: column; gap: 8px; }
.sidebar-stat {
  display: flex;
  align-items: center;
  gap: 7px;
  color: var(--muted);
  font-size: 12.5px;
}
.sidebar-stat-icon { display: flex; flex-shrink: 0; }

.contributor-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.contributor-row { display: flex; align-items: center; gap: 8px; }
.contributor-avatar, .contributor-avatar-fallback {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  flex-shrink: 0;
}
.contributor-avatar { object-fit: cover; }
.contributor-avatar-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
}
.contributor-name { flex: 1; font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.contributor-role { color: var(--muted); font-size: 11px; text-transform: capitalize; }

.goal-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.goal-row-header { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 5px; }
.goal-title { font-size: 12.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.goal-progress-label { color: var(--muted); font-size: 11px; flex-shrink: 0; }
.goal-progress-track {
  height: 6px;
  border-radius: 3px;
  background: var(--card-bg-muted);
  overflow: hidden;
}
.goal-progress-fill { height: 100%; background: var(--accent); border-radius: 3px; }

/* Shared table look (used by ticket-guideline, admin-users, ...). */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.table-wrap table {
  width: 100%;
  min-width: 480px;
  border-collapse: collapse;
  background: var(--card-bg);
  font-size: 13px;
}
.table-wrap th, .table-wrap td {
  text-align: left;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.table-wrap th {
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  font-weight: 600;
}
.table-wrap tr:last-child td { border-bottom: none; }

/* Note feed (Updates/Decisions/Risks) — composer + feed items. §11.5. */
.feed-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 20px;
}
.feed-header h1 { font-size: 20px; font-weight: 700; margin: 0; }
.feed-header .feed-count { color: var(--muted); font-size: 13px; }

.composer {
  padding: 14px 16px;
  margin-bottom: 20px;
}
.composer-collapsed {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: text;
}
.composer-avatar, .feed-item-avatar {
  width: 28px;
  height: 28px;
  border-radius: 999px;
  flex-shrink: 0;
  object-fit: cover;
}
/* Fallback initial-in-circle — server.js's shared renderAvatar() always
   emits the bare .avatar-fallback class (never a per-context one), so
   this scopes by ANCESTOR instead of a bespoke class name per call site. */
.composer .avatar-fallback, .feed-item-header .avatar-fallback {
  width: 28px;
  height: 28px;
  border-radius: 999px;
  flex-shrink: 0;
  background: var(--accent-soft);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
}
.composer-fake-input {
  flex: 1;
  color: var(--muted);
  font-size: 14px;
  padding: 8px 12px;
  background: var(--card-bg-muted);
  border-radius: var(--radius-sm);
}
.composer-editor-mount {
  min-height: 96px;
  padding: 6px 2px;
}
.composer-editor-mount .ProseMirror { outline: none; }
.composer-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}
.composer-image-count { font-size: 12px; color: var(--muted); }
.composer-image-count.at-max { color: var(--warn); }
.composer-error { font-size: 12px; color: var(--warn); }
.composer-spacer { flex: 1; }
/* Mod-Enter submit hint (composer, comment new/edit, Requirement editor —
   see editor/index.js's modEnterSubmit / comments.js's own handler, and
   theme.js's wireModEnterHints for where the text itself is filled in). */
.mod-enter-hint {
  font-size: 11.5px;
  color: var(--muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 2px 6px;
  white-space: nowrap;
}
/* Shown only when this composer instance was opened via the sidebar's
   "Change status" control (see server.js's renderComposer forcedStatus) —
   turns "write an update" into "explain this specific status change." */
.composer-status-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  margin-bottom: 10px;
  border-radius: var(--radius-sm);
  background: var(--card-bg-muted);
  font-size: 13px;
}
.composer-status-banner-cancel { margin-left: auto; color: var(--muted); font-size: 12.5px; }
.composer-status-banner-cancel:hover { color: var(--fg); }

.feed-item { padding: 16px; margin-bottom: 10px; }
/* Jump target from search results — briefly frames whichever post the link
   actually pointed to, since the feed itself has no per-post page. */
.feed-item:target { outline: 2px solid var(--accent); outline-offset: 2px; }
.feed-item-header { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.feed-item-type-icon { display: inline-flex; flex-shrink: 0; }
.feed-item-name { font-size: 13.5px; font-weight: 600; }
.feed-item-time { font-size: 12.5px; color: var(--muted); }
.feed-item-spacer { flex: 1; }
.feed-item-menu { position: relative; }
.feed-item-menu-toggle {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--muted);
  cursor: pointer;
}
.feed-item-menu-toggle:hover, .feed-item-menu-toggle[aria-expanded="true"] {
  color: var(--fg);
  border-color: var(--border);
}
.feed-item-menu-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.feed-item-menu-popover {
  position: absolute;
  right: 0;
  top: 36px;
  z-index: 20;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-hover);
  padding: 4px;
  min-width: 140px;
}
.feed-item-menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  height: 34px;
  padding: 0 10px;
  border: none;
  background: none;
  border-radius: var(--radius-sm);
  color: var(--fg);
  font-size: 13px;
  font-family: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.feed-item-menu-item:hover { background: var(--card-bg-muted); }
.feed-item-menu-item form { width: 100%; margin: 0; }

/* Full-screen image preview (Comments/Polish plan Phase 1d). z-index 1000
   clears everything else in this file with headroom (skip-link is the
   next-highest at 100). A photo-viewer scrim doesn't need to be
   theme-aware — it's always dark, in either theme. */
.image-lightbox-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(0, 0, 0, 0.85);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px;
}
.image-lightbox-overlay img {
  max-width: 100%;
  max-height: 90vh;
  object-fit: contain;
  border-radius: var(--radius-sm);
}
.image-lightbox-close {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 28px;
  line-height: 1;
  cursor: pointer;
}
.image-lightbox-close:hover { background: rgba(255, 255, 255, 0.22); }
.image-lightbox-close:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* Embedded handbook PDF viewer (ownership-tabs-pdf-plan.md Phase 6, ADR
   0004) — a slide-in panel (60-70vw desktop / full-screen under
   ~700px), not a new tab. z-index above the image lightbox (1000) since
   a handbook could in principle be opened while an image lightbox from
   an unrelated page is somehow still around. */
.pdf-viewer-panel { position: fixed; inset: 0; z-index: 1001; }
.pdf-viewer-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.5); }
.pdf-viewer-content {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 65vw;
  max-width: 900px;
  background: var(--bg);
  border-left: 1px solid var(--border);
  box-shadow: -8px 0 24px rgba(0, 0, 0, 0.25);
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform 0.25s ease;
}
.pdf-viewer-panel-open .pdf-viewer-content { transform: translateX(0); }
@media (max-width: 700px) {
  .pdf-viewer-content { width: 100vw; max-width: none; border-left: none; }
}
.pdf-viewer-toolbar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.pdf-viewer-page-indicator { font-size: 12.5px; color: var(--muted); padding: 0 4px; }
.pdf-viewer-spacer { flex: 1; }
.pdf-viewer-canvas-wrap {
  position: relative;
  flex: 1;
  overflow: auto;
  padding: 20px;
  background: var(--card-bg-muted, var(--bg));
  /* Hand-tool panning (pdfviewer/index.js's wireCanvasPan) — grab hints
     draggability even before zooming past the viewport; grabbing while
     an actual drag is in progress. */
  cursor: grab;
}
.pdf-viewer-canvas-wrap.pdf-viewer-panning {
  cursor: grabbing;
  user-select: none;
}
/* Loading indicator (pdfviewer/index.js) — a large/slow-loading PDF used
   to leave the panel looking frozen (a blank canvas, no feedback) until
   the first page actually painted. Centered over the wrap; hidden once
   that first paint completes. */
.pdf-viewer-loading {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--muted);
  font-size: 13px;
  pointer-events: none;
}
.pdf-viewer-loading[hidden] { display: none; }
.pdf-viewer-spinner {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--accent-soft);
  border-top-color: var(--accent);
  flex-shrink: 0;
  animation: gami-pdf-viewer-spin 0.7s linear infinite;
}
@keyframes gami-pdf-viewer-spin {
  to { transform: rotate(360deg); }
}
/* `margin: auto` centering, not `display: flex; justify-content: center`
   on the wrap — a classic flexbox/overflow trap: once the (zoomed) canvas
   is WIDER than the wrap, flex centering keeps half the overflow "before"
   the scroll origin, which scrollLeft can never reach, so panning stalls
   partway across instead of reaching the actual left edge. A block-level
   auto-margin child collapses its margin to 0 once it overflows, so the
   scrollable range covers the full canvas in both directions. */
.pdf-viewer-canvas {
  display: block;
  margin: 0 auto;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
}

/* Discoverability: a zoom cursor on read-only post/requirement/overview
   images signals "click to preview" before the first click. Scoped to
   read-only containers only — editable ProseMirror mounts keep their own
   image-selection cursor/behavior untouched. */
.feed-item-body img,
.overview-block-view img,
.requirement-article .prose img,
.thumb-wrap img {
  cursor: zoom-in;
}

/* Overview block edit UI polish (ownership-tabs-pdf-plan.md Phase 5) —
   reuses .requirement-editor-mount's exact bordered/focus-ring pattern so
   this reads as an intentional field to fill in, not two loose buttons
   over a bare textarea. [hidden]-driven show/hide (not inline
   style="display:none") — :not([hidden]) scoping avoids the usual
   [hidden]-vs-display pitfall of an unconditional display rule winning
   over the attribute. */
.overview-block-form { display: none; }
.overview-block-form:not([hidden]) { display: block; }
.overview-block-editor-mount {
  min-height: 160px;
  padding: 10px 12px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg-muted);
}
.overview-block-editor-mount .ProseMirror { outline: none; }
.overview-block-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

/* Decisions only (project-hub UX plan Phase 2) — collapsed icon+title+
   chevron row; .feed-item's own padding is overridden to 0 here since the
   toggle button supplies its own padding as the clickable surface instead. */
.feed-item-decision { padding: 0; }
.feed-item-decision-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 14px 16px;
  border: none;
  background: none;
  color: var(--fg);
  font-family: inherit;
  font-size: 13.5px;
  text-align: left;
  cursor: pointer;
}
.feed-item-decision-toggle:hover { background: var(--card-bg-muted); }
.feed-item-decision-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.feed-item-decision-icon { display: flex; flex-shrink: 0; color: var(--ok); }
.feed-item-decision-title {
  flex: 1;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.feed-item-decision-time { color: var(--muted); font-size: 12px; flex-shrink: 0; }
.feed-item-decision-chevron { display: flex; flex-shrink: 0; color: var(--muted); transition: transform 0.15s ease; }
.feed-item-decision.expanded .feed-item-decision-chevron { transform: rotate(180deg); }
.feed-item-decision-detail { padding: 0 16px 14px; border-top: 1px solid var(--border); }
.feed-item-decision-detail .feed-item-header { padding-top: 12px; }
/* Visible on the collapsed row so "there's a conversation here" doesn't
   require expanding first — but the comments themselves stay inside the
   (hidden until expanded) detail, same reasoning as the decision's own
   body text never leaking into the collapsed summary. */
.feed-item-decision-comment-count {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  color: var(--muted);
  font-size: 12px;
  flex-shrink: 0;
}

/* Comments (Comments/Polish plan Phase 3) — flat, chronological, plain
   text. Nested one level inside a post/requirement, so avatars and type
   sizes step down from .feed-item-avatar's 28px rather than matching it.
   No top border/margin of its own — always rendered inside a .doc-tab-
   panel now (see .doc-tabs below), which already owns that separation. */
.comments-list { display: flex; flex-direction: column; gap: 12px; }
.comment-row { display: flex; gap: 8px; align-items: flex-start; }
.comment-row:target { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
.comment-avatar, .comment-row .avatar-fallback {
  width: 22px;
  height: 22px;
  border-radius: 999px;
  flex-shrink: 0;
  object-fit: cover;
}
.comment-row .avatar-fallback {
  background: var(--accent-soft);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10.5px;
  font-weight: 700;
}
.comment-body-wrap { flex: 1; min-width: 0; }
.comment-meta { display: flex; align-items: baseline; gap: 8px; margin-bottom: 2px; }
.comment-author { font-size: 12.5px; font-weight: 600; }
.comment-time { font-size: 11.5px; color: var(--muted); }
/* No white-space: pre-wrap — server.js already converts every \n in
   bodyText to an explicit <br> before this renders, so there are no raw
   newline characters left for pre-wrap to act on; adding it would just
   be redundant, not incorrect. */
.comment-text { font-size: 13px; line-height: 1.5; word-break: break-word; }
.comment-row .feed-item-menu { flex-shrink: 0; }
.comment-row .feed-item-menu-toggle { width: 26px; height: 26px; }
.comment-textarea {
  display: block;
  width: 100%;
  min-height: 60px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg-muted);
  color: var(--fg);
  font-size: 13px;
  font-family: inherit;
  resize: vertical;
}
.comment-edit-form { margin-top: 4px; }
.comment-edit-actions, .comment-new-actions { display: flex; gap: 8px; margin-top: 8px; }
.comment-new-form { margin-top: 14px; }

/* Reactions (Comments/Polish plan Phase 5) — used under both a post's
   body and each comment. Viewer pills (read-only) vs canWrite buttons
   (always exactly 4, see renderReactionBar) share sizing/spacing so
   switching between the two states on role change doesn't jump around. */
.reaction-bar { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.reaction-pill, .reaction-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 26px;
  padding: 0 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  font-size: 12.5px;
  font-family: inherit;
  color: var(--muted);
}
.reaction-btn { cursor: pointer; }
.reaction-btn:hover { border-color: var(--accent); color: var(--fg); }
.reaction-btn.active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.reaction-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.reaction-emoji { font-size: 13px; line-height: 1; }
.reaction-count { font-size: 11.5px; font-weight: 600; }

/* Emoji picker (Comments/Polish plan Phase 5) — same data-popover-toggle/
   data-popover contract as everything else, auto-wired by theme.js's
   shared wirePopoverToggles(); only the individual emoji buttons' click
   behavior (inserting into the textarea) is comments.js's own. */
.comment-emoji-picker { position: relative; display: inline-block; }
.comment-emoji-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg);
  font-size: 15px;
  cursor: pointer;
}
.comment-emoji-toggle:hover, .comment-emoji-toggle[aria-expanded="true"] { border-color: var(--accent); }
.comment-emoji-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* display:grid only applies :not([hidden]) — set unconditionally, it
   would override the browser's own [hidden] { display: none } default
   (higher specificity), defeating the hidden attribute entirely. Caught
   once already on the admin nav dropdown; same fix here. */
.comment-emoji-popover {
  position: absolute;
  left: 0;
  bottom: 40px;
  z-index: 20;
  width: 220px;
  padding: 6px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-hover);
}
.comment-emoji-popover:not([hidden]) {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 2px;
}
.comment-emoji-option {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  border: none;
  background: none;
  border-radius: var(--radius-sm);
  font-size: 17px;
  cursor: pointer;
}
.comment-emoji-option:hover { background: var(--card-bg-muted); }

/* @mention autocomplete dropdown (Comments/Polish plan Phase 4) —
   appended to <body> and absolutely positioned under whichever textarea
   is active (see public/comments.js), not a child of the comment form
   itself, so it can never be clipped by an ancestor's overflow. */
.mention-autocomplete {
  position: absolute;
  z-index: 30;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-hover);
  padding: 4px;
  max-height: 200px;
  overflow-y: auto;
}
.mention-autocomplete-item {
  display: block;
  width: 100%;
  padding: 7px 10px;
  border: none;
  background: none;
  border-radius: var(--radius-sm);
  color: var(--fg);
  font-size: 13px;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.mention-autocomplete-item.active, .mention-autocomplete-item:hover { background: var(--accent-soft); color: var(--accent); }

/* Version history (access-history-versioning-plan.md Phase 2) — a plain
   <details> disclosure, same collapsed-by-default shape as the handbook
   upload form. Only Overview blocks and uploaded handbooks still use this
   standalone shape (renderVersionHistoryDisclosure) — post feed items and
   the Requirement read view moved to the .doc-tabs system below instead
   (ownership-tabs-pdf-plan.md Phase 3d). */
.version-history { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
.version-history summary {
  cursor: pointer;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  list-style: none;
}
.version-history summary::-webkit-details-marker { display: none; }
.version-history summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.version-history summary:hover { color: var(--fg); }
.version-list, .history-list { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.doc-tab-panel .version-list, .doc-tab-panel .history-list { margin-top: 0; }
.version-row, .history-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  padding: 6px 0;
  flex-wrap: wrap;
}
.version-event, .history-label { font-weight: 600; flex-shrink: 0; }
.version-author { color: var(--muted); }
.history-actor { color: var(--fg); font-weight: 600; flex-shrink: 0; }
.version-time, .history-time { color: var(--muted); margin-right: auto; }
.history-detail { color: var(--muted); font-style: italic; }
.history-document { color: var(--accent); text-decoration: none; font-weight: 600; }
.history-document:hover { text-decoration: underline; }
.history-empty { color: var(--muted); font-size: 12.5px; margin: 10px 0 0; }
.version-restore-form { flex-shrink: 0; }
.version-restore-btn { height: 26px; padding: 0 10px; font-size: 12px; }
.version-current-badge {
  flex-shrink: 0;
  font-size: 11px;
  font-weight: 700;
  color: var(--muted);
  background: var(--card-bg-muted, var(--bg));
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px 9px;
}

/* Jira-style Comments/History/Version History tabs (ownership-tabs-pdf-
   plan.md Phase 3d) — replaces 3 stacked <details>/plain-div sections on
   post feed items and the Requirement read view. All 3 tabs render for
   every role; only the actions inside a panel (comment form, restore
   buttons) are capability-gated. */
.doc-tabs { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
.doc-tabs-bar { display: flex; gap: 4px; margin-bottom: 12px; }
.doc-tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  padding: 6px 4px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  font-family: inherit;
}
.doc-tab:hover { color: var(--fg); }
.doc-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.doc-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.doc-tab-panel[hidden] { display: none; }

/* Tombstone (ADR 0003) — admin-only marker replacing a deleted post/
   comment's body, in the exact spot the content used to occupy. */
.tombstone {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 0;
  color: var(--muted);
  font-size: 13px;
}
.tombstone-icon { display: inline-flex; color: var(--danger); flex-shrink: 0; }
.tombstone-label { flex: 1; }
.tombstone-restore-form { flex-shrink: 0; }

/* Requirement / handbook-style download chip, reused by attachment lists. */
.file-chip {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  text-decoration: none;
  color: var(--fg);
  transition: border-color 0.15s ease;
}
.file-chip:hover { border-color: var(--accent); }
.file-chip-icon {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: var(--card-bg-muted);
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
}
.file-chip-name { font-size: 13.5px; font-weight: 600; }
.file-chip-size { font-size: 11.5px; color: var(--muted); }
.file-chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin-left: -4px;
  flex-shrink: 0;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--muted);
  cursor: pointer;
}
.file-chip-remove:hover { color: var(--warn); }
.file-chip-remove:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.file-chip-row { display: flex; align-items: center; }

/* Requirements list (§11.7). */
.requirements-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
}
.requirement-card { padding: 16px; }
.requirement-card-title {
  font-size: 15.5px;
  font-weight: 700;
  margin-bottom: 6px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.requirement-card-excerpt {
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 12px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.requirement-card-footer { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--muted); }
.requirement-card-footer .avatar-fallback,
.requirement-card-footer img {
  width: 20px;
  height: 20px;
  border-radius: 999px;
  flex-shrink: 0;
  object-fit: cover;
}
.requirement-card-footer .avatar-fallback {
  background: var(--accent-soft);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 700;
}

/* Requirement reading view (§11.7) — 720px wrap, 680px article column. */
.requirement-article { max-width: 680px; margin: 0 auto; }
.requirement-title { font-size: 26px; font-weight: 700; line-height: 1.3; margin: 0 0 14px; }
@media (min-width: 768px) {
  .requirement-title { font-size: 28px; }
}
.requirement-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  color: var(--muted);
  margin-bottom: 28px;
  flex-wrap: wrap;
}
.requirement-meta .avatar-fallback, .requirement-meta img {
  width: 24px;
  height: 24px;
  border-radius: 999px;
  object-fit: cover;
  flex-shrink: 0;
}
.requirement-meta .avatar-fallback {
  background: var(--accent-soft);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
}
.requirement-meta .spacer { flex: 1; }
.requirement-attachments { margin-top: 36px; }
.requirement-attachments h2 { font-size: 16px; margin: 0 0 12px; }
.requirement-attachments-list { display: flex; flex-direction: column; gap: 8px; }

/* Requirement editor page (§11.7). */

/* Reusable "this is a form field" micro-label — visible label above the
   input rather than a placeholder standing in for one (a placeholder that
   just restates the field's purpose disappears the moment you start
   typing, and reads as a page heading until then, not a field). */
.field-label {
  display: block;
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: 0 0 6px;
}
.field-label:not(:first-child) { margin-top: 22px; }

.requirement-form-card { padding: 24px 24px 4px; }
.requirement-title-input {
  display: block;
  width: 100%;
  border: none;
  background: none;
  border-bottom: 2px solid var(--muted);
  padding: 4px 0 10px;
  margin-bottom: 6px;
  font-size: 24px;
  font-weight: 700;
  font-family: inherit;
  color: var(--fg);
  transition: border-color 0.15s ease;
}
.requirement-title-input::placeholder { color: var(--muted); font-weight: 500; }
/* Borderless title fields with a full outline look broken — a bottom-border
   color shift is the one sanctioned focus-style exception here, and it must
   still be clearly visible. */
.requirement-title-input:focus { outline: none; border-bottom-color: var(--accent); }
.requirement-title-input.input-error {
  border-bottom-color: var(--warn);
  animation: gami-shake 0.3s ease;
}
@keyframes gami-shake {
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}
.requirement-title-error {
  color: var(--warn);
  font-size: 12.5px;
  font-weight: 600;
  margin: 0 0 14px;
}
.requirement-editor-mount {
  min-height: 320px;
  padding: 10px 12px 80px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg-muted);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
/* The nested ProseMirror contenteditable has its own focus behavior,
   separate from the mount div's — left unset, the browser's default
   (a stark blue ring with no relation to this app's palette) shows
   instead. Replaced, not just removed: :focus-within on the *mount*
   turns the whole field's own border/ring accent-colored, which is a
   more intentional and more visible indicator than the native ring was. */
.requirement-editor-mount .ProseMirror { outline: none; }
.requirement-editor-mount:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}
.requirement-attachments-panel { margin-top: 8px; }
.requirement-attachments-panel .file-chip-row { margin-bottom: 8px; }
.requirement-sticky-bar {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 0;
  background: var(--bg);
  border-top: 1px solid var(--border);
}
.requirement-sticky-bar .spacer { flex: 1; }

/* Campaign-wide search results (§7 Phase 4). */
.search-results-list { display: flex; flex-direction: column; gap: 8px; }
.search-result { padding: 14px 16px; }
.search-result-header { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.search-result-header .icon { display: flex; color: var(--muted); flex-shrink: 0; }
.search-result-title { font-weight: 600; font-size: 14px; }
.search-result-excerpt {
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 6px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.search-result-meta { font-size: 11.5px; color: var(--muted); }

/* Centered empty state — no illustrations, one icon + one line + optional CTA. */
.empty-state {
  display: none;
  text-align: center;
  color: var(--muted);
  font-size: 14px;
  padding: 48px 0;
}
.empty-state.visible { display: block; }
.empty-state svg { margin-bottom: 10px; opacity: 0.5; }
.empty-state .btn-primary, .empty-state .btn-secondary { margin-top: 14px; }
