/* Switchyard — rail yard color palette */
:root {
  /* --surface is the card/panel fill (and the light text color on dark chrome).
     PRD #103 crit1 parity: white cards on the slate page (--page below) mirror the
     mockup's #fff panels on #f1f5f9. */
  --surface: #FFFFFF;
  /* Page backdrop behind the cards — the mockup's slate-100. Distinct from --surface
     so cards read as raised white panels rather than blending into the page. */
  --page: #F1F5F9;
  --steel: #1E293B;
  --ballast: #64748B;
  --signal-green: #059669;
  --signal-green-bright: #34D399; /* brighter emerald for marks on dark surfaces */
  --signal-amber: #D97706;
  --signal-red: #DC2626;
  --rail-blue: #2563EB;
  --sleeper: #0F172A;
  /* Neutral beige backdrop behind uploaded avatar/logo images so a transparent
     or clear PNG/SVG stays visible instead of vanishing into the page. Opaque
     photos paint over it (object-fit: cover), so it only shows through. */
  --avatar-bg: #E8DFC8;
  /* --rule and --coal are used in 43 places across the dashboard's templates and
     were, until now, defined NOWHERE. Every one of those sites fell through to a
     per-site fallback, and those fallbacks disagreed: 13 were light (#eee,
     #e5e7eb) and 11 were dark (#3a322a, #15110d), written against a dark palette
     this app no longer has — the live ground is --surface #FFFFFF with --steel
     #1E293B text.

     A dark fallback under a light theme is not a cosmetic drift. The dark ones
     set a background and no `color`, so the text inherited --steel and rendered
     dark-on-dark — literally unreadable. The worst instances were the two
     one-time API token reveals (account and admin), where the value is shown
     exactly once and can never be read back: rendering it invisibly does not
     inconvenience the operator, it loses the token.

     Defining both here fixes every site at once, which is the point — 43
     per-site edits would drift apart again the way the fallbacks already did.
     Checked before choosing the values: no use sits inside a dark-surface
     container, and no --coal use sets its own text colour, so a light ground
     leaves every one of them readable.

     --coal keeps its name for the same reason its uses keep working: renaming it
     would mean editing all 43 sites, which is the churn this avoids. Read it as
     "inset surface" rather than as a colour. */
  --rule: #E2E8F0;
  --coal: #F8FAFC;
  --radius: 6px;
  /* One height for every interactive form control, so a text input, a <select>
     and a .btn standing in the same row line up (PRD #236 P0). Derived from the
     BUTTON's own box rather than picked by eye — .btn's 0.6rem of vertical
     padding either side of a 0.9rem/1.5 line — so the three cannot drift apart
     when that padding is retuned. Inputs and selects need it stated explicitly
     because their content box is a `line-height: normal` (~1.2) text line and
     carries 1px of border each side, which lands a couple of pixels under the
     borderless button beside them. */
  --control-height: calc(0.6rem * 2 + 0.9rem * 1.5);
  --font-sans: Inter, system-ui, -apple-system, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, monospace;
}

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

body {
  font-family: var(--font-sans);
  background: var(--page);
  color: var(--steel);
  line-height: 1.5;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
/* width:100% is load-bearing: <main> is a flex item of the column-flex body,
   and .container's `margin:0 auto` would otherwise suppress the cross-axis
   stretch and size <main> to its max-content — overflowing the viewport on
   narrow screens whenever page content is wider than the device. Pinning the
   width makes it fill the line (still capped by .container's max-width on
   desktop, where the auto margins then center it). */
.page-main { flex: 1 0 auto; width: 100%; padding-bottom: 2.5rem; }

a { color: var(--steel); text-decoration: none; }
a:hover { text-decoration: underline; }

.container { max-width: 1440px; margin: 0 auto; padding: 0 1.5rem; }
@media (min-width: 1024px) { .container { padding: 0 2rem; } }

.card {
  background: white;
  border-radius: var(--radius);
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
  overflow-x: auto;
}

/* Page header */
.page-header { padding: 1.5rem 0 1rem; }
.page-header h1 { font-size: 1.5rem; font-weight: 600; }
/* PRD-page header (mockup §PRD page): lives INSIDE the detail-grid's center
   column, tight to the top — 20px title, no extra top padding, badges inline. */
.prd-head { padding: 0 0 0.75rem; }
.prd-head h1 { font-size: 20px; margin: 0; }

/* Page header with actions: title on the left, action button(s) on the
   right. Replaces the ad-hoc inline `display:flex; justify-content:space-between`
   so the row can collapse responsively. On narrow viewports it stacks into a
   column and the actions go full-width, keeping tap targets finger-sized
   instead of crushing the title against the buttons. */
.page-header-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.page-header-actions { display: flex; gap: 0.5rem; }
@media (max-width: 640px) {
  .page-header-bar { flex-direction: column; align-items: stretch; }
  .page-header-actions { width: 100%; }
  .page-header-bar > .btn,
  .page-header-actions > .btn { flex: 1; }
}

/* Nav */
.nav {
  background: var(--steel);
  color: var(--surface);
  padding: 0.65rem 0;
  position: sticky;
  top: 0;
  z-index: 100;
}
.nav-inner {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  position: relative;
}
.nav-brand {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--surface);
  letter-spacing: -0.5px;
  white-space: nowrap;
}
.nav-brand span { color: var(--signal-amber); }
.nav-brand:hover { text-decoration: none; }
/* Project breadcrumb — always visible in the top bar (desktop + mobile), so
   the current project name stays on screen. Truncates with an ellipsis on
   narrow viewports instead of pushing the burger off-screen. The "/" divider
   is drawn via ::before so separator + label are one flex child. */
.nav-crumb {
  color: var(--surface);
  opacity: 0.92;
  font-size: 0.92rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  max-width: 42vw;
}
.nav-crumb::before {
  content: "/";
  opacity: 0.45;
  font-weight: 400;
  margin-right: 0.7rem;
}
.nav-crumb:hover { opacity: 1; text-decoration: none; }
/* Workspace icon in the breadcrumb (PRD #50 P2). Inline (not flex) so the
   anchor's ellipsis truncation still applies to the label; vertical-align
   centers the 18px swatch against the 0.92rem label. Renders only when the
   active workspace has an icon_url. */
.nav-crumb-icon {
  width: 18px;
  height: 18px;
  border-radius: 4px;
  object-fit: cover;
  vertical-align: -4px;
  margin-right: 0.45rem;
  background: var(--avatar-bg);
}
@media (max-width: 768px) { .nav-crumb { max-width: 55vw; font-size: 0.88rem; } }
.nav-links {
  display: flex;
  gap: 1.25rem;
  align-items: center;
  flex: 1;
}
.nav-links a {
  color: var(--surface);
  opacity: 0.75;
  font-size: 0.875rem;
  padding: 0.25rem 0;
}
.nav-links a:hover { opacity: 1; text-decoration: none; }
.nav-right {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-left: auto;
}

/* Notification bell + surface (PRD #37 P1, sw-s7zv.2). The bell is a static white
   SVG (upgraded to a Lottie ring when there are unread); the dropdown panel
   mirrors the account menu. #notif-panel is empty until HTMX fills it on open,
   and .notif-panel:empty hides it — so a closed bell costs nothing in layout. */
.nav-notif { position: relative; display: inline-flex; }
.nav-notif-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.3rem;
  border-radius: var(--radius);
  color: var(--surface);
  cursor: pointer;
}
.nav-notif-btn:hover { background: rgba(255,255,255,0.08); text-decoration: none; }
.notif-bell { position: relative; display: inline-flex; width: 20px; height: 20px; }
.notif-bell-icon { width: 20px; height: 20px; display: block; }
.notif-bell-lottie { position: absolute; inset: 0; width: 20px; height: 20px; }
.notif-bell-lottie svg { display: block; }
.notif-badge {
  position: absolute;
  top: -5px;
  right: -6px;
  min-width: 15px;
  height: 15px;
  padding: 0 0.2rem;
  box-sizing: border-box;
  border-radius: 999px;
  background: var(--signal-red);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 600;
  line-height: 15px;
  text-align: center;
}

/* Visually hidden but available to screen readers (the bell's accessible name). */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.notif-panel { position: absolute; right: 0; top: calc(100% + 6px); z-index: 200; }
.notif-panel:empty { display: none; }
.notif-panel-inner {
  background: #fff;
  border-radius: var(--radius);
  box-shadow: 0 4px 20px rgba(0,0,0,0.15);
  width: 320px;
  max-width: 86vw;
  overflow: hidden;
}
.notif-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.6rem 0.85rem;
  border-bottom: 1px solid #f1f1f1;
}
.notif-panel-title { font-weight: 600; color: var(--steel); font-size: 0.85rem; }
.notif-panel-actions { display: flex; align-items: center; gap: 0.75rem; }
.notif-panel-all { font-size: 0.75rem; color: var(--rail-blue); }
.notif-panel-readall {
  font-size: 0.75rem;
  color: var(--rail-blue);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.notif-panel-readall:hover { text-decoration: underline; }

/* List + items, shared by the dropdown panel and the full /dashboard/notifications page. */
.notif-list { list-style: none; margin: 0; padding: 0; max-height: 60vh; overflow-y: auto; }
/* The row is a flex track so the dropdown's dismiss × (a sibling of the link, not
   a child — nesting a button in an <a> is invalid and would navigate on click)
   can sit at the trailing edge while the link takes the remaining width. The full
   notifications page renders no ×, and a lone link child simply fills the row. */
.notif-item { display: flex; align-items: stretch; border-bottom: 1px solid #f4f4f4; }
.notif-item:last-child { border-bottom: 0; }
.notif-item-link {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  gap: 0.5rem;
  align-items: flex-start;
  padding: 0.6rem 0.85rem;
  color: var(--steel);
}
/* Reuses the live toast's .toast-close for the glyph's color/size/hover; this
   only strips the <button> chrome and gives it the row's vertical padding. */
.notif-item-dismiss {
  background: none;
  border: 0;
  padding: 0.6rem 0.6rem 0 0.2rem;
  font-family: inherit;
}
a.notif-item-link:hover { background: #f8f8f8; text-decoration: none; }
.notif-item-static { cursor: default; }
.notif-item-unread { background: rgba(37, 99, 235, 0.05); }
.notif-item-dot {
  flex: 0 0 auto;
  width: 7px; height: 7px;
  margin-top: 0.35rem;
  border-radius: 50%;
  background: var(--rail-blue);
}
.notif-item-text { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.notif-item-title { font-weight: 600; font-size: 0.83rem; }
.notif-item-body { font-size: 0.8rem; color: var(--ballast); }
.notif-item-time { font-size: 0.7rem; color: var(--ballast); }
.notif-empty { padding: 1.25rem 0.85rem; text-align: center; color: var(--ballast); font-size: 0.83rem; }
.notif-page-card { padding: 0; overflow: hidden; }

/* Account dropdown */
.nav-account { position: relative; }
.nav-account > summary {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  padding: 0.3rem 0.6rem;
  border-radius: var(--radius);
  font-size: 0.85rem;
  color: var(--surface);
}
.nav-account > summary:hover { background: rgba(255,255,255,0.08); }
.nav-account > summary::-webkit-details-marker { display: none; }
.nav-account-avatar {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--rail-blue);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 600;
  color: white;
}
/* Uploaded photo in the nav: same circle as the initials badge, image cropped
   to fill instead of the accent-color background. The beige backdrop + white
   ring keep a transparent/clear logo visible against the dark header (the ring
   sits inside the 28px box via border-box, so the footprint is unchanged). */
.nav-account-avatar-photo { background: var(--avatar-bg); object-fit: cover; border: 2px solid #fff; }
.nav-account-name { font-weight: 500; }
.nav-account-caret { font-size: 0.65rem; opacity: 0.6; }
.nav-account-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  background: white;
  border-radius: var(--radius);
  box-shadow: 0 4px 20px rgba(0,0,0,0.15);
  min-width: 200px;
  z-index: 200;
  overflow: hidden;
}
.nav-account-header {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid #f1f1f1;
}
.nav-account-header-name { font-weight: 600; color: var(--steel); font-size: 0.9rem; }
.nav-account-header-email { font-size: 0.78rem; color: var(--ballast); margin-top: 0.1rem; }
.nav-account-header-role {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--ballast);
  margin-top: 0.3rem;
}
.nav-account-item {
  display: block;
  padding: 0.55rem 1rem;
  color: var(--steel);
  font-size: 0.85rem;
}
.nav-account-item:hover { background: #f8f8f8; text-decoration: none; }
.nav-account-divider { border-top: 1px solid #f1f1f1; }
.nav-account-logout { color: var(--signal-red); }

/* Org switcher (PRD #50 P1 sw-y90k): nav workspace dropdown. Mirrors the account
   dropdown's <details> language — the active workspace's icon + name is the
   button; the menu lists every workspace the account can switch into. */
.nav-org { position: relative; }
.nav-org > summary {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  cursor: pointer;
  padding: 0.3rem 0.6rem;
  border-radius: var(--radius);
  font-size: 0.85rem;
  color: var(--surface);
  max-width: 220px;
}
.nav-org > summary:hover { background: rgba(255,255,255,0.08); }
.nav-org > summary::-webkit-details-marker { display: none; }
.nav-org-icon, .nav-org-item-icon {
  width: 22px; height: 22px;
  border-radius: 6px;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--avatar-bg);
}
.nav-org-icon-fallback, .nav-org-item-icon-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--rail-blue);
  color: white;
  font-size: 0.7rem;
  font-weight: 600;
}
.nav-org-name {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.nav-org-caret { font-size: 0.6rem; opacity: 0.6; flex-shrink: 0; }
.nav-org-menu {
  position: absolute;
  left: 0;
  top: calc(100% + 6px);
  background: white;
  border-radius: var(--radius);
  box-shadow: 0 4px 20px rgba(0,0,0,0.15);
  min-width: 220px;
  max-width: 300px;
  z-index: 200;
  overflow: hidden;
  padding: 0.35rem 0;
}
.nav-org-menu-label {
  padding: 0.4rem 0.85rem;
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--ballast);
}
.nav-org-item {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.5rem 0.85rem;
  color: var(--steel);
  font-size: 0.85rem;
}
.nav-org-item:hover { background: #f8f8f8; text-decoration: none; }
.nav-org-item-active { background: #f1f5f9; font-weight: 600; }
.nav-org-item-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nav-org-item-check { margin-left: auto; color: var(--rail-blue); flex-shrink: 0; }
/* Starred marker on a project row in the workspace menu (PRD #92 P1). Passive —
   it reports the star the grid sets, matching the starred-first order the menu
   already applies. margin-left:auto parks it on the trailing edge like
   .nav-org-item-check, and flex-shrink:0 keeps it from being squeezed out when
   .nav-org-item-name ellipsises a long project name. Amber matches the grid's
   .pcard-star.is-starred so one star means one thing across both surfaces. */
.nav-org-item-star { margin-left: auto; display: flex; align-items: center; color: #92600a; flex-shrink: 0; }
.nav-org-item-star .pcard-icon { width: 13px; height: 13px; }
.nav-org-item-arrow { margin-left: auto; font-size: 0.7rem; opacity: 0.55; flex-shrink: 0; }

/* The menu's own header: the active workspace's name plus the gear that opens
   its settings. The gear is an icon-only affordance (labelled for screen
   readers) so the row reads as "this workspace", not as another list entry. */
.nav-org-menu-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.85rem 0.5rem;
  border-bottom: 1px solid #f1f1f1;
}
.nav-org-menu-head-name {
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--steel);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.nav-org-menu-head-settings {
  margin-left: auto;
  color: var(--ballast);
  line-height: 1;
  padding: 0.15rem 0.25rem;
  border-radius: 4px;
  flex-shrink: 0;
}
.nav-org-menu-head-settings:hover { background: #f1f1f1; color: var(--steel); text-decoration: none; }

/* Project slot. Capped at navOrgProjectsCap rows server-side, so this scrolls
   only in the fetch-lag case where a placeholder is taller than the list. */
.nav-org-projects { max-height: 320px; overflow-y: auto; }
.nav-org-note {
  padding: 0.5rem 0.85rem;
  font-size: 0.8rem;
  color: var(--ballast);
}
.nav-org-note-item { color: var(--ballast); font-size: 0.8rem; }
.nav-org-item-all { border-top: 1px solid #f1f1f1; font-weight: 500; }

/* "Switch workspace" disclosure: rendered only for accounts in more than one
   workspace, collapsed until asked for. Its <details> is intentionally outside
   the .nav-org selector the outside-click handler closes, so opening it can't
   collapse the menu around it. */
.nav-org-switch { border-top: 1px solid #f1f1f1; }
.nav-org-switch > summary { list-style: none; cursor: pointer; }
.nav-org-switch > summary::-webkit-details-marker { display: none; }
.nav-org-switch[open] > summary .nav-org-item-arrow { transform: rotate(180deg); }
.nav-org-switch-menu { background: #fafafa; padding: 0.2rem 0; }

/* Mobile nav toggle.
   Desktop: the burger + scrim are hidden and .nav-drawer is display:contents,
   so .nav-links / .nav-right flow into .nav-inner's flex row exactly as before.
   That `display: contents` is load-bearing for the DESKTOP bar, not just mobile
   — layout.templ wraps both clusters in .nav-drawer, so without it the wrapper
   is a plain block: the links and the account cluster stack, and .nav-right's
   `margin-left: auto` no longer reaches .nav-inner to push them apart, leaving
   the whole bar bunched against the left edge. (Exactly what happened when a
   branch cut before the drawer landed reverted this block; see PR #972.)
   Mobile (<=768px): the drawer becomes a fixed full-height panel that slides in
   from the right over a tap-to-close backdrop scrim; the burger animates to an X.
   CSS-only (checkbox hack) so it needs no JS build step. */
.nav-toggle { display: none; }
.nav-burger { display: none; cursor: pointer; }
.nav-scrim { display: none; }
.nav-drawer { display: contents; }

@media (max-width: 768px) {
  .nav-inner { gap: 0.75rem; }

  /* Burger: three bars that morph into an X when the toggle is checked. */
  .nav-burger {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    margin-left: auto;
    width: 44px; height: 44px;   /* comfortable tap target */
    padding: 11px;
    border-radius: var(--radius);
    z-index: 210;
    transition: background 0.18s ease;
  }
  .nav-burger:hover { background: rgba(255,255,255,0.08); }
  .nav-burger span {
    display: block;
    width: 22px; height: 2px;
    background: var(--surface);
    border-radius: 2px;
    transition: transform 0.28s cubic-bezier(0.4,0,0.2,1), opacity 0.2s ease;
  }
  .nav-toggle:checked ~ .nav-burger span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .nav-toggle:checked ~ .nav-burger span:nth-child(2) { opacity: 0; }
  .nav-toggle:checked ~ .nav-burger span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

  /* Backdrop scrim — dims the page and closes the drawer on tap. */
  .nav-scrim {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(15,23,42,0.55);   /* --sleeper, translucent */
    backdrop-filter: blur(2px);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.28s ease, visibility 0.28s ease;
    z-index: 200;
  }
  .nav-toggle:checked ~ .nav-scrim { opacity: 1; visibility: visible; }

  /* The drawer: a fixed panel sliding in from the right. */
  .nav-drawer {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    position: fixed;
    top: 0; right: 0; bottom: 0;
    width: min(320px, 84vw);
    padding: 4.5rem 1.1rem 1.5rem;   /* clears the sticky nav bar */
    background: var(--sleeper);
    border-left: 1px solid rgba(255,255,255,0.08);
    box-shadow: -18px 0 48px rgba(15,23,42,0.4);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    transform: translateX(100%);
    transition: transform 0.32s cubic-bezier(0.32,0.72,0,1);
    z-index: 205;
  }
  .nav-toggle:checked ~ .nav-drawer { transform: translateX(0); }

  /* Links become full-width rows with generous, thumb-friendly targets. */
  .nav-drawer .nav-links {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    flex: none;
  }
  .nav-drawer .nav-links a {
    padding: 0.85rem 0.6rem;
    font-size: 0.98rem;
    opacity: 0.9;
    border-radius: var(--radius);
    border-bottom: 1px solid rgba(255,255,255,0.06);
    transition: background 0.15s ease, opacity 0.15s ease, padding-left 0.15s ease;
  }
  .nav-drawer .nav-links a:hover,
  .nav-drawer .nav-links a:focus-visible {
    background: rgba(255,255,255,0.06);
    opacity: 1;
    padding-left: 0.9rem;
  }

  /* The account / org / notification cluster stacks below, comfortably spaced. */
  .nav-drawer .nav-right {
    flex-direction: column;
    align-items: stretch;
    gap: 0.6rem;
    margin-top: 1.1rem;
    padding-top: 1.1rem;
    border-top: 1px solid rgba(255,255,255,0.1);
  }
}

/* Lock page scroll behind the open drawer (modern browsers). */
body:has(.nav-toggle:checked) { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .nav-burger span, .nav-scrim, .nav-drawer { transition: none; }
}

/* Footer */
.site-footer {
  flex-shrink: 0;
  border-top: 1px solid #e5e5e5;
  background: var(--surface);
  padding: 1rem 0 1.25rem;
  margin-top: 2rem;
}
.site-footer-inner {
  display: flex;
  align-items: center;
  gap: 1rem;
  font-size: 0.72rem;
  color: var(--ballast);
  font-family: var(--font-mono);
  letter-spacing: 0.2px;
}
.site-footer-brand { color: var(--steel); font-weight: 600; }
.site-footer-brand span { color: var(--signal-amber); margin: 0 0.05em; }
.site-footer-spacer { flex: 1; }
.site-footer-link { color: var(--ballast); text-decoration: none; }
.site-footer-link:hover { color: var(--steel); text-decoration: underline; }

/* Auth pages — content sits in the upper third of the viewport so the
   form is reachable without scrolling on shorter laptop screens, and
   the user's eye lands on the brand + heading immediately rather than
   floating in dead space. */
.auth-page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding: 4rem 2rem 2rem;
  background: linear-gradient(135deg, #1E293B 0%, #334155 100%);
}
.auth-hero { text-align: center; margin-bottom: 2rem; }
.auth-logo {
  font-family: var(--font-mono);
  font-size: 2.5rem;
  font-weight: 700;
  color: white;
  letter-spacing: -1px;
}
.auth-logo span { color: var(--signal-amber); }

/* Rail-blue brand mark between "switch" and "yard". Rendered via CSS mask
   so the icon's color is always rail-blue regardless of the source SVG's
   internal fills. Sized via em so it scales with the surrounding font;
   slightly TALLER than the text and vertically centered to look like a
   deliberate piece of typography, not a stray icon. The default mark can
   be swapped per-page by setting --brand-mark-url on the span (used by
   /static/brand-preview.html). */
.brand-mark {
  display: inline-block;
  width: 1.3em;
  height: 1.3em;
  vertical-align: middle;
  margin: 0 0.08em;
  /* Default mark color: dark "go" green — readable on light backgrounds
     (footer, PRD share). Overridden below for dark contexts. */
  background-color: var(--signal-green);
  mask-image: var(--brand-mark-url, url("/static/brands/train-track.svg"));
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
  -webkit-mask-image: var(--brand-mark-url, url("/static/brands/train-track.svg"));
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
}
/* Bright emerald for marks placed on the dark auth hero and top nav. The top
   nav's mark paints from the instance identity's accent (PRD #937), which the
   layout sets as --brand-accent on <html> only when one is configured. */
.auth-logo .brand-mark {
  background-color: var(--signal-green-bright);
}
.nav-brand .brand-mark {
  background-color: var(--brand-accent, var(--signal-green-bright));
}
/* A configured identity logo (PRD #937) sits where the bundled mark does,
   sized to the same box so the wordmark around it does not move. */
.brand-logo {
  display: inline-block;
  width: 1.3em;
  height: 1.3em;
  vertical-align: middle;
  margin: 0 0.08em;
  object-fit: contain;
}
/* A configured wordmark image (SWITCHYARD_WORDMARK_URL) replaces the text
   name in the header, with the logo leading it. */
.brand-logo-lead { width: 1.6em; height: 1.6em; margin: 0 0.45em 0 0; border-radius: 0.3em; }
.brand-wordmark { display: inline-block; height: 1.15em; width: auto; vertical-align: middle; }
.auth-tagline { color: rgba(255,255,255,0.6); font-size: 0.95rem; margin-top: 0.5rem; }
.auth-card {
  background: white;
  border-radius: 8px;
  padding: 2rem;
  width: 100%;
  max-width: 420px;
  box-shadow: 0 8px 30px rgba(0,0,0,0.2);
}
.auth-card h2 { font-size: 1.25rem; margin-bottom: 1rem; }
.auth-card-wide { max-width: 560px; }
.auth-sub { font-size: 0.88rem; color: var(--ballast); margin-bottom: 1.25rem; }
.auth-link { text-align: center; margin-top: 1rem; font-size: 0.85rem; color: var(--ballast); }
.auth-link a { color: var(--rail-blue); }
.auth-foot {
  text-align: center;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid #e5e7eb;
  font-size: 0.82rem;
  color: var(--ballast);
}
.auth-foot a { color: var(--rail-blue); }
/* "Sign in with <GitLab instance>" buttons under the password form (PRD #521). */
.auth-sso { margin-top: 1rem; display: flex; flex-direction: column; gap: 0.5rem; }
.auth-sso-divider { text-align: center; font-size: 0.8rem; color: var(--ballast); }
.auth-sso .btn { text-align: center; }
/* "Have an invite code?" entry under the sign-in form: it GETs /dashboard/register?invite=. */
.auth-invite-code { margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid #e5e7eb; }
.auth-invite-code label { display: block; font-size: 0.82rem; color: var(--ballast); margin-bottom: 0.4rem; }
.auth-invite-code-row { display: flex; gap: 0.5rem; }
.auth-invite-code-row input { flex: 1; min-width: 0; text-transform: uppercase; }
/* Admin invites list: status badge per invite, and the "who joined" disclosure. */
.invite-status-active { color: var(--signal-green); }
.invite-status-disabled { color: var(--signal-red); }
.invite-status-expired, .invite-status-exhausted { color: var(--ballast); }
.invite-row-new { outline: 2px solid var(--signal-green); outline-offset: -2px; }
.invite-code-value { font-family: var(--font-mono); font-weight: 600; letter-spacing: 0.04em; }
.invite-redemptions summary { cursor: pointer; color: var(--rail-blue); font-size: 0.78rem; }
.invite-redemptions ul { margin: 0.25rem 0 0; padding-left: 1rem; font-size: 0.78rem; }

/* Invitation summary: structured label/value pairs at the top of /invite/{code}. */
.invite-meta {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.75rem 1.5rem;
  margin: 0 0 1.5rem;
  padding: 1rem 1.25rem;
  background: #f8fafc;
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
}
.invite-meta > div { min-width: 0; }
.invite-meta dt {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ballast);
  margin-bottom: 0.25rem;
}
.invite-meta dd {
  margin: 0;
  font-size: 0.92rem;
  color: var(--steel);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
}
.invite-meta-email {
  font-family: var(--font-mono);
  font-size: 0.85rem !important;
}

/* Role badge inside the invite-meta panel. */
.role-chip {
  display: inline-block;
  padding: 0.15rem 0.55rem;
  font-size: 0.78rem;
  font-weight: 600;
  font-family: var(--font-mono);
  border-radius: 999px;
  text-transform: lowercase;
  letter-spacing: 0.02em;
  background: #e0e7ff;
  color: #3730a3;
}
.role-chip.role-owner  { background: #fef3c7; color: #92400e; }
.role-chip.role-admin  { background: #dbeafe; color: #1e40af; }
.role-chip.role-member { background: #d1fae5; color: #065f46; }
.role-chip.role-viewer { background: #f1f5f9; color: #475569; }
/* Delegated-approval grant capabilities (sw-ir7.8): approve = authoritative
   (green), recommend_changes = advisory (blue). */
.role-chip.grant-approve           { background: #d1fae5; color: #065f46; }
.role-chip.grant-recommend_changes { background: #dbeafe; color: #1e40af; }

/* PR-status pills — live CI / review / mergeable state mirrored from GitHub
   webhooks (sw-6jv.5). Tones map to pr_pills.go: ok/warn/bad/muted. */
.pr-pills { display: inline-flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; }
.pr-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.08rem 0.45rem;
  font-size: 0.68rem;
  font-weight: 600;
  font-family: var(--font-mono);
  line-height: 1.5;
  border-radius: 999px;
  white-space: nowrap;
  letter-spacing: 0.01em;
  background: #f1f5f9;
  color: #475569;
}
.pr-pill--ok    { background: #d1fae5; color: #065f46; }
.pr-pill--warn  { background: #fef3c7; color: #92400e; }
.pr-pill--bad   { background: #fee2e2; color: #991b1b; }
.pr-pill--muted { background: #f1f5f9; color: #475569; }

/* State panel below the invite summary — varies per (mismatch / ready / sign-in / sign-up). */
.invite-state h3 {
  font-size: 1rem;
  margin: 0 0 0.5rem;
  color: var(--steel);
}
.invite-state > p,
.invite-state-hint {
  font-size: 0.88rem;
  color: var(--ballast);
  margin: 0 0 1rem;
  line-height: 1.5;
}
.invite-state-warn {
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: var(--radius);
  padding: 1rem 1.25rem;
}
.invite-state-warn h3 { color: #991b1b; }
.invite-state-warn > p { color: #7f1d1d; }
/* The projects a workspace-invite accept did NOT grant (PRD #694 P0). The
   accept page names them inside .invite-state-warn, so this is a plain unmarked
   list — the box is already the warning, and a bullet beside each project name
   reads as a second one. */
.invite-skipped {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.invite-skipped li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9rem;
  color: #7f1d1d;
}
.invite-state-ready {
  background: #ecfdf5;
  border: 1px solid #a7f3d0;
  border-radius: var(--radius);
  padding: 1rem 1.25rem;
}
.invite-state-ready h3 { color: #065f46; }
.invite-state-ready > p { color: #064e3b; }

/* Forms */
.form-group { margin-bottom: 1rem; }
.form-group label {
  display: block;
  font-size: 0.85rem;
  font-weight: 500;
  margin-bottom: 0.35rem;
  color: var(--steel);
}
/* The one box every text-entry control in a form group wears (PRD #236 P0).
   Written as a DENYLIST of the input types that must keep their native chrome,
   not an allowlist of the text-ish ones. The allowlist it replaces named only
   text/email/password/url, so the settings pages' `type="number"` fields —
   Stale threshold, Default attempt budget, PRDs in flight, the governance daily
   caps — fell out of it entirely and rendered at browser default (~21px) beside
   a 38px Rubric-threshold <select> and a 41px Save button. The tell that the
   allowlist was accretion rather than intent: the :focus rule immediately below
   has always been type-agnostic, so those fields already took the focus ring
   from a rule that matched them while the box rule did not. HTML keeps adding
   text-entry types (number, search, date, tel, …) and adds no non-text ones, so
   the denylist is the half that stays closed — a field added later is styled by
   default instead of silently shipping short.

   Specificity is deliberately unchanged at (0,2,1): `:not()` takes the
   specificity of its most specific ARGUMENT, so the single-list form matches
   what `input[type="text"]` scored and the later `.form-group input[readonly]`
   override still wins. Splitting this into one `:not()` per type would score
   (0,10,1) and silently break that rule. */
.form-group input:not([type="checkbox"], [type="radio"], [type="file"], [type="range"], [type="color"], [type="submit"], [type="button"], [type="reset"], [type="image"], [type="hidden"]),
.form-group textarea,
.form-group select {
  width: 100%;
  min-height: var(--control-height);
  padding: 0.6rem 0.75rem;
  border: 1px solid #d1d5db;
  border-radius: var(--radius);
  font-size: 0.9rem;
  font-family: inherit;
  background: white;
  transition: border-color 0.15s;
}
.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
  outline: none;
  border-color: var(--rail-blue);
  box-shadow: 0 0 0 3px rgba(37,99,235,0.1);
}
.form-group input[readonly] {
  background: #f8fafc;
  color: var(--ballast);
  cursor: not-allowed;
}
.form-label-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 0.35rem;
}
.form-label-row label { margin-bottom: 0 !important; }
.form-label-link {
  font-size: 0.78rem;
  color: var(--rail-blue);
  text-decoration: none;
}
.form-label-link:hover { text-decoration: underline; }
.form-group input[readonly]:focus {
  border-color: #d1d5db;
  box-shadow: none;
}
.form-hint { font-size: 0.78rem; color: var(--ballast); margin-top: 0.25rem; display: block; }
/* Live character counter under a capped textarea (PRD #92 P0). Tabular numerals
   keep the count from jittering as digits change; over-limit turns it red — this
   only shows for a pre-filled value already longer than the cap, since the browser
   caps new typing. */
.char-counter { font-variant-numeric: tabular-nums; }
.char-counter.over-limit { color: var(--signal-red); font-weight: 600; }
.form-section {
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  padding: 1.25rem;
  margin-bottom: 1.25rem;
}
.form-section legend {
  font-weight: 600;
  font-size: 0.9rem;
  padding: 0 0.5rem;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 999px;
  background: var(--rail-blue);
  color: white;
  font-size: 0.75rem;
  font-weight: 700;
  font-family: var(--font-mono);
}
.form-section-hint {
  font-size: 0.8rem;
  color: var(--ballast);
  margin: -0.25rem 0 1rem;
  line-height: 1.5;
}

/* Composite control: static URL prefix + slug input that look like one field. */
.url-preview {
  display: flex;
  align-items: stretch;
  border: 1px solid #d1d5db;
  border-radius: var(--radius);
  background: white;
  overflow: hidden;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.url-preview:focus-within {
  border-color: var(--rail-blue);
  box-shadow: 0 0 0 3px rgba(37,99,235,0.1);
}
.url-preview-prefix {
  display: inline-flex;
  align-items: center;
  padding: 0 0.6rem;
  background: #f8fafc;
  color: var(--ballast);
  font-family: var(--font-mono);
  font-size: 0.85rem;
  border-right: 1px solid #e5e7eb;
  white-space: nowrap;
}
.form-group .url-preview .url-preview-slug {
  flex: 1;
  border: none;
  border-radius: 0;
  background: transparent;
  font-family: var(--font-mono);
}
.form-group .url-preview .url-preview-slug:focus {
  outline: none;
  box-shadow: none;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.6rem 1.25rem;
  border: none;
  border-radius: var(--radius);
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, box-shadow 0.15s;
  font-family: inherit;
}
.btn-primary { background: var(--rail-blue); color: white; }
.btn-primary:hover { background: #1d4ed8; }
.btn-secondary { background: #f1f5f9; color: var(--steel); border: 1px solid #d1d5db; }
.btn-secondary:hover { background: #e2e8f0; }
.btn-full { width: 100%; }

/* Submitting state (PRD #214, issue #36). The layout's [data-busy-label] handler
   builds this span into the submit button while a full-page POST is in flight —
   approving a PRD enqueues its whole bead set before it redirects, so the wait is
   real and needs to be legible. Sized in em so one rule tracks every button that
   opts in (the PRD card's approve runs 0.82rem, the inbox's full size), and drawn
   in currentColor so it works on a primary and a secondary alike. */
.btn-spinner {
  display: inline-block;
  flex: none;
  width: 0.85em;
  height: 0.85em;
  margin-right: 0.45em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: sy-spin 0.7s linear infinite;
}
@keyframes sy-spin { to { transform: rotate(360deg); } }
/* A busy button is unavailable because it is working, not because it is dead:
   keep it legible and show the progress cursor rather than the not-allowed one. */
form[data-busy="1"] button[disabled],
form[data-busy="1"] input[type="submit"][disabled] { opacity: 0.85; cursor: progress; }
/* Reduced motion drops the rotation; the swapped-in "Approving…" label is what
   carries the state, so the indicator stays truthful without animating. */
@media (prefers-reduced-motion: reduce) {
  .btn-spinner { animation: none; opacity: 0.55; }
}

/* Alerts */
.alert {
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  font-size: 0.88rem;
  margin-bottom: 1rem;
}
.alert-error { background: #fef2f2; color: var(--signal-red); border: 1px solid #fecaca; }
.alert-success { background: #f0fdf4; color: var(--signal-green); border: 1px solid #bbf7d0; }
.alert-warning { background: #fffbeb; color: var(--signal-amber); border: 1px solid #fde68a; }
.alert-info { background: #f0f9ff; color: #075985; border: 1px solid #bae6fd; }
.alert-info a { color: #0369a1; font-weight: 500; }

/* PRD authoring dispatch card on the blueprint detail page. Three-column
   grid that wraps to one column on narrow viewports. */
.dispatch-card {
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  padding: 1.25rem;
  margin-top: 1rem;
}
.dispatch-card-title { font-size: 1rem; margin: 0 0 0.25rem; }
.dispatch-card-sub { font-size: 0.85rem; color: var(--ballast); margin: 0 0 1rem; line-height: 1.5; }
.dispatch-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}
@media (max-width: 900px) { .dispatch-grid { grid-template-columns: 1fr; } }
.dispatch-option {
  background: #f8fafc;
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  padding: 1rem;
  display: flex;
  flex-direction: column;
  /* Grid/flex items default to min-width:auto, which refuses to shrink below
     their content's min-content width. The .dispatch-snippet inside is
     white-space:pre (very wide min-content), so without this the option — and
     the whole page — is forced wider than a phone viewport. min-width:0 lets the
     option shrink so the snippet's own overflow-x:auto scrolls instead. */
  min-width: 0;
}
.dispatch-option h4 {
  font-size: 0.9rem;
  margin: 0 0 0.5rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.dispatch-option-desc { font-size: 0.8rem; color: var(--ballast); margin: 0 0 0.75rem; line-height: 1.5; }
.dispatch-option-desc code {
  background: #e2e8f0;
  padding: 0 0.25rem;
  border-radius: 3px;
  font-size: 0.78rem;
}
.dispatch-option-disabled { opacity: 0.65; }
.dispatch-option > form,
.dispatch-option > .alert,
.dispatch-option > .dispatch-snippet,
.dispatch-option > button { margin-top: auto; }
.dispatch-snippet {
  background: var(--steel);
  color: #e2e8f0;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  padding: 0.6rem;
  border-radius: 4px;
  overflow-x: auto;
  margin: 0 0 0.5rem;
  white-space: pre;
  line-height: 1.4;
  /* Never let the pre block itself push the page wide — it scrolls instead. */
  max-width: 100%;
}

/* Tools page (/dashboard/tools) — one card per registry entry */
.tools-list { display: flex; flex-direction: column; gap: 1.25rem; max-width: 760px; }
/* Overview boxes across the top: one per registry entry, linking to its card */
.tools-overview { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; max-width: 760px; margin-bottom: 1.25rem; }
.tool-overview-box { display: flex; flex-direction: column; gap: 0.5rem; padding: 1.25rem; color: inherit; text-decoration: none; transition: box-shadow 0.15s, transform 0.15s; }
.tool-overview-box:hover, .tool-overview-box:focus-visible { box-shadow: 0 0 0 2px var(--rail-blue); transform: translateY(-1px); }
.tool-overview-summary { font-size: 0.85rem; line-height: 1.5; flex: 1; }
.tool-overview-cta { font-size: 0.78rem; font-weight: 600; color: var(--rail-blue); }
.tools-list > section { scroll-margin-top: 1rem; }
/* "Use it with" card: per-client walkthroughs */
.tool-client-name { font-size: 0.95rem; font-weight: 600; margin: 1.25rem 0 0.6rem; }
.tool-client-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0.75rem; }
.tool-client { border: 1px solid var(--rule); border-radius: var(--radius); padding: 0.85rem; min-width: 0; scroll-margin-top: 1rem; }
.tool-client-head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; margin-bottom: 0.6rem; flex-wrap: wrap; }
.tool-client-surface { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.6px; font-weight: 600; }
.tool-client-uses { font-size: 0.75rem; }
.tool-client .tool-steps li { font-size: 0.8rem; }
.tool-client .dispatch-snippet { white-space: pre-wrap; word-break: break-all; }
.tool-card { padding: 1.5rem; }
.tool-card-head { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.4rem; }
.tool-card-name { font-family: var(--font-mono); font-size: 1.05rem; font-weight: 600; margin: 0; }
.tool-card-kind { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.5px; }
.tool-card-summary { font-size: 0.9rem; margin: 0 0 0.5rem; line-height: 1.5; }
.tool-card-what { font-size: 0.82rem; color: var(--ballast); margin: 0 0 1rem; line-height: 1.6; }
.tool-card-subhead { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.6px; color: var(--ballast); margin: 1rem 0 0.5rem; }
.tool-steps { margin: 0; padding-left: 1.25rem; display: flex; flex-direction: column; gap: 0.65rem; }
.tool-steps li { font-size: 0.85rem; line-height: 1.5; }
.tool-step-label { display: block; }
.tool-steps .dispatch-snippet { margin: 0.4rem 0 0; }
.tool-card-hint { font-size: 0.78rem; color: var(--ballast); margin: 0 0 0.5rem; line-height: 1.5; }
.tool-card-hint code, .tool-step-label code {
  background: #e2e8f0;
  padding: 0 0.25rem;
  border-radius: 3px;
  font-size: 0.78rem;
}
.tool-prompt-mint { margin-left: 0.5rem; }
/* GitHub webhook card: event list + repo→project mappings (PRD #30) */
.tool-webhook-events { margin: 0 0 0.5rem; padding-left: 1.25rem; display: flex; flex-direction: column; gap: 0.3rem; }
.tool-webhook-events li { font-size: 0.82rem; line-height: 1.4; }
.tool-webhook-mapping { margin: 0 0 0.65rem; }
.tool-webhook-mapping > a { font-weight: 600; font-size: 0.85rem; }
.tool-webhook-repos { margin: 0.25rem 0 0; padding-left: 1.25rem; display: flex; flex-direction: column; gap: 0.2rem; }
.tool-webhook-repos li { font-size: 0.82rem; line-height: 1.4; }

/* Section titles */
.section-title { font-size: 1.05rem; font-weight: 600; margin-bottom: 1rem; }

/* Mono utility */
.mono { font-family: var(--font-mono); }

/* Settings grid */
.settings-grid { max-width: 800px; }

/* Data tables */
.data-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.data-table th { text-align: left; padding: 0.5rem 0.75rem; border-bottom: 2px solid #e5e7eb; font-weight: 600; color: var(--ballast); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.5px; }
.data-table td { padding: 0.6rem 0.75rem; border-bottom: 1px solid #f1f1f1; }

/* Email-verification nag: site-wide banner shown until account.email_verified=true. */
.verify-banner {
  background: #FFFBEB;
  border-bottom: 1px solid #FDE68A;
  color: #78350F;
  font-size: 0.88rem;
}
.verify-banner-inner {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.9rem 1.25rem;
}
.verify-banner-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  flex-shrink: 0;
  border-radius: 999px;
  background: #F59E0B;
  color: white;
  font-weight: 700;
  font-family: var(--font-mono);
}
.verify-banner-text { flex: 1; }
.verify-banner-text strong { color: #78350F; }
.verify-banner-form { margin: 0; flex-shrink: 0; }
.verify-banner-resend { color: #78350F; font-weight: 600; }
.verify-banner-resend:hover:not([disabled]) { color: #451A03; }

/* Inline link-style button used in dense tables (resend / revoke / copy). */
.btn-link {
  background: none;
  border: none;
  padding: 0.15rem 0;
  margin: 0;
  font-size: 0.82rem;
  font-family: inherit;
  color: var(--rail-blue);
  cursor: pointer;
}
.btn-link:hover:not([disabled]) { text-decoration: underline; }
.btn-link[disabled] { color: var(--ballast); cursor: default; }
.btn-link-danger { color: var(--signal-red, #DC2626); }

.invite-table .invite-actions-col { width: 1%; white-space: nowrap; text-align: right; }
.invite-actions {
  white-space: nowrap;
  text-align: right;
}

.admin-users-table .admin-actions-col,
.admin-workspaces-table .admin-actions-col,
.admin-tokens-table .admin-actions-col { width: 1%; white-space: nowrap; text-align: right; }
/* Admin → Tool timings (docs/tool-timings.md): a row whose p95 is past the
   slow-request threshold is the one the log would also have named. */
.admin-timings-table { min-width: 64rem; }
.admin-timings-table td:nth-child(n+3) { text-align: right; font-variant-numeric: tabular-nums; }
.admin-timings-slow td { background: #fef2f2; }
.admin-timings-slow td:nth-child(6) { color: var(--signal-red); font-weight: 600; }
.admin-actions {
  white-space: nowrap;
  text-align: right;
}
.admin-actions select {
  padding: 0.3rem 0.4rem;
  border: 1px solid #d1d5db;
  border-radius: 4px;
  background: white;
  font-family: inherit;
}

/* Stacked link buttons in the per-row actions column. */
.row-actions {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.1rem;
}
.row-actions form, .row-actions .btn-link { margin: 0; }

.numeric-col { text-align: right; }

/* Admin section shell: left rail with section links, content on the right.
   Same pattern reused by the account page so /dashboard/account and
   /dashboard/admin/* feel like one navigated experience. */
.admin-shell {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 2rem;
  max-width: 1200px;
}
/* Narrow screens: the rail stacks above the content and stops being sticky —
   a sticky rail in a one-column layout would ride over the content it is
   meant to lead to — and its links flow as a wrapped row of chips instead of
   a tall vertical list. */
@media (max-width: 800px) {
  .admin-shell { grid-template-columns: 1fr; gap: 1rem; }
  .admin-rail { position: static; padding: 0.75rem 0.75rem 0.6rem; }
  .admin-rail nav { display: flex; flex-wrap: wrap; gap: 0.25rem; }
  .admin-rail-link { padding: 0.45rem 0.7rem; border: 1px solid #e5e7eb; }
  .admin-rail-title { margin: 0 0.25rem 0.5rem; }
  .admin-rail-foot { margin: 0.6rem 0.25rem 0; }
}
.admin-rail {
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  padding: 1.25rem 0.75rem;
  align-self: start;
  position: sticky;
  top: 1rem;
}
.admin-rail-title {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ballast);
  margin: 0 0.75rem 0.75rem;
}
.admin-rail-link {
  display: block;
  padding: 0.5rem 0.75rem;
  border-radius: 4px;
  font-size: 0.88rem;
  color: var(--steel);
  text-decoration: none;
}
.admin-rail-link:hover { background: #f1f5f9; }
.admin-rail-link-active {
  background: rgba(37,99,235,0.08);
  color: var(--rail-blue);
  font-weight: 600;
}
.admin-rail-foot {
  font-size: 0.72rem;
  color: var(--ballast);
  line-height: 1.5;
  margin: 1rem 0.75rem 0;
}
.admin-content { min-width: 0; }
.admin-page-header { margin-bottom: 1.5rem; }
.admin-page-header h1 { margin: 0 0 0.25rem; }
.admin-page-sub {
  font-size: 0.88rem;
  color: var(--ballast);
  margin: 0;
  line-height: 1.5;
}
/* Admin landing page (/dashboard/admin/): needs-attention alerts, headline
   tiles, the viewer's administered workspaces and the install-settings
   directory. Tiles and directory items are whole-card links. */
.admin-home-section { margin-bottom: 2rem; }
.admin-home-heading {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ballast);
  margin: 0 0 0.75rem;
}
.admin-home-empty { font-size: 0.88rem; color: var(--ballast); margin: 0; }
.admin-home-alert {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.admin-home-alert-link { font-weight: 500; white-space: nowrap; }
.admin-home-tiles,
.admin-home-directory {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 0.75rem;
}
.admin-home-directory { grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); }
.admin-home-tile,
.admin-home-dir-item {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  background: var(--surface);
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
  color: var(--steel);
  text-decoration: none;
}
.admin-home-tile:hover,
.admin-home-dir-item:hover { border-color: var(--rail-blue); }
.admin-home-tile-label { font-size: 0.78rem; color: var(--ballast); }
.admin-home-tile-value { font-size: 1.6rem; font-weight: 600; line-height: 1.2; font-variant-numeric: tabular-nums; }
.admin-home-tile-detail { font-size: 0.75rem; color: var(--ballast); }
.admin-home-dir-label { font-weight: 600; font-size: 0.9rem; color: var(--rail-blue); }
.admin-home-dir-blurb { font-size: 0.8rem; color: var(--ballast); line-height: 1.45; }
.admin-home-workspaces-table .admin-actions-col { width: 1%; white-space: nowrap; text-align: right; }
.admin-home-workspaces-table .admin-actions .btn-link + .btn-link { margin-left: 0.9rem; }
/* A tile that is a stat, not a link (the workspaces page's "At a glance"):
   no hover affordance, since there is nothing to click. */
.admin-home-tile-static:hover { border-color: #e5e7eb; }
.admin-home-tile-warn .admin-home-tile-value { color: var(--signal-red); }

/* Admin workspaces page (/dashboard/admin/workspaces), laid out like the
   landing page. The table card overrides .card's overflow-x:auto so the
   per-row Edit / Reassign popovers are not clipped; below 800px it scrolls
   again and the popovers flow inline in their cell instead. */
.admin-ws-table-card { overflow: visible; }
/* Long monospace emails and slugs wrap rather than widening the table past
   its card: the card does not scroll above 800px (see .admin-ws-table-card). */
.admin-ws-sub { display: block; font-size: 0.76rem; color: var(--ballast); margin-top: 0.1rem; overflow-wrap: anywhere; }
.admin-ws-mono { font-family: var(--font-mono); }
.admin-ws-muted { font-size: 0.82rem; color: var(--ballast); white-space: nowrap; }
.admin-ws-orphan {
  font-size: 0.72rem; padding: 0.15rem 0.5rem; border-radius: 999px;
  background: #fee2e2; color: #991b1b; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.3px;
}
.admin-ws-row-actions > * + * { margin-left: 0.9rem; }
.admin-ws-menu { position: relative; display: inline-block; }
.admin-ws-menu > summary { list-style: none; display: inline; }
.admin-ws-menu > summary::-webkit-details-marker { display: none; }
.admin-ws-menu > summary::after { content: " ▾"; font-size: 0.7em; }
.admin-ws-menu[open] > summary { text-decoration: underline; }
.admin-ws-popover {
  position: absolute;
  right: 0;
  top: calc(100% + 0.4rem);
  z-index: 20;
  width: 20rem;
  background: var(--surface);
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(15,23,42,0.12);
  padding: 1rem;
  text-align: left;
  white-space: normal;
}
.admin-ws-popover .form-group { margin-bottom: 0.75rem; }
.admin-ws-popover-title { font-weight: 600; font-size: 0.9rem; margin: 0 0 0.75rem; }
.admin-ws-help { font-size: 0.8rem; color: var(--ballast); line-height: 1.5; margin: 0 0 0.9rem; }
.admin-ws-actions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
  gap: 0.75rem;
}
.admin-ws-action-card {
  background: var(--surface);
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  padding: 1.1rem 1.25rem;
}
.admin-ws-action-card h3 { font-size: 0.95rem; font-weight: 600; margin: 0 0 0.35rem; }
.admin-ws-foot { font-size: 0.78rem; }
@media (max-width: 800px) {
  .admin-ws-table-card { overflow-x: auto; }
  .admin-ws-popover { position: static; width: 18rem; margin-top: 0.4rem; box-shadow: none; }
}
.invite-actions > .btn-link,
.invite-actions form { margin-left: 0.75rem; }
.invite-actions > .btn-link:first-child,
.invite-actions form:first-child { margin-left: 0; }
/* Workspace settings, members and invites (PRD #677 P4,
   views_workspace_settings_members.templ): the admin-page pattern — .admin-home-section /
   .admin-home-heading over cards — with what used to be inline style
   attributes. The table cards sit flush like .admin-ws-table-card; the form
   card carries the padding the old combined card had. */
.page-header .ws-settings-title { margin-top: 0.25rem; }
/* A section's one-line description, under the shell's title (workspace settings). */
.ws-settings-lede { color: var(--ballast); font-size: 0.9rem; margin: 0 0 1rem; }
.ws-people-section .card.ws-people-table-card { padding: 0; }
.ws-people-section .card.ws-people-card { padding: 1.25rem 1.5rem; }
.ws-people-role-form { display: flex; gap: 0.4rem; align-items: center; }
.ws-people-inline-form { display: inline; }
.ws-people-notice { margin-bottom: 1.25rem; }
.alert.ws-people-alert { margin-bottom: 1.25rem; }
.ws-invite-row { display: flex; gap: 0.5rem; align-items: flex-end; flex-wrap: wrap; }
.ws-invite-row .form-group { margin-bottom: 0; }
.ws-invite-row .ws-invite-email { flex: 1; min-width: min(16rem, 100%); }
.ws-invite-row .ws-invite-submit { white-space: nowrap; }
/* The workspace invite form's project picker (views_tenant_invite_projects.templ). */
.invite-project-picker { margin: 0.75rem 0 0; padding: 0.6rem 0.75rem; border: 1px solid #e5e7eb; border-radius: 6px; }
.invite-project-picker .invite-project-legend { font-size: 0.85rem; padding: 0 0.25rem; }
.invite-project-picker .invite-project-hint { margin: 0 0 0.5rem; }
.invite-project-picker .invite-project-row { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.3rem; }
.invite-project-picker .invite-project-name { flex: 1; display: flex; gap: 0.4rem; align-items: center; margin: 0; font-weight: normal; }
.invite-project-picker .invite-project-chip { font-size: 0.7rem; }
/* The shared invite-result notice (views_invite_result.templ), on both the
   project Access page and workspace settings: the values its inline styles
   had. Multi-class selectors so .alert / .btn cannot out-rank them. */
.alert.invite-result { margin: 0 0 0.75rem; }
.invite-result .invite-result-link { display: flex; gap: 0.4rem; align-items: center; margin-top: 0.4rem; }
.invite-result .invite-result-link .invite-result-url { flex: 1; min-width: 0; font-size: 0.78rem; }
.invite-result .invite-result-link .invite-result-copy { font-size: 0.78rem; }

/* Role badges */
.role-badge { font-size: 0.72rem; padding: 0.15rem 0.5rem; border-radius: 999px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.3px; }
.role-owner { background: #fef3c7; color: #92400e; }
.role-admin { background: #dbeafe; color: #1e40af; }
.role-member { background: #d1fae5; color: #065f46; }
.role-viewer { background: #f1f5f9; color: #475569; }

/* Project grid. The min(320px, 100%) floor lets each card shrink to the
   container width on narrow viewports — without it the track would overflow
   (horizontal scroll) on small phones. Reflows to fewer columns as space
   tightens. */
.project-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); gap: 1.25rem; }

/* Two-column detail layout: main content + 280px right sidebar (the backlog and
   pitches pages as-is; project and PRD detail add .detail-grid-hero below,
   which puts the status box FIRST on phones). Collapses to a single column at
   the tablet breakpoint so the sidebar stacks below the content rather than
   squeezing the main column to an unreadable width. Replaces the inline grid
   styles those pages used to carry — inline styles can't hold a media query. */
.detail-grid {
  display: grid;
  /* minmax(0, 1fr) not 1fr: a bare 1fr track has min-width:auto, so a long
     nowrap PRD title sets a wide min-content and the track refuses to shrink,
     shoving the fixed 280px rail past the viewport (horizontal scroll, worst on
     mobile). minmax(0, …) floors the track at 0 so it shrinks and the titles
     ellipsize instead. */
  grid-template-columns: minmax(0, 1fr) 280px;
  gap: 1.5rem;
  max-width: 1100px;
}
@media (max-width: 820px) {
  .detail-grid { grid-template-columns: minmax(0, 1fr); max-width: 100%; }
}

/* Hero-first detail pages (project + PRD detail): .detail-grid-hero marks a
   detail grid whose rail-lead status box is a direct child placed FIRST in
   source, so when the grid collapses to one column on phones the Work/status
   box lands at the top of the page instead of below the whole content column.
   On desktop the explicit placements reproduce the classic layout — hero
   top-right, content left spanning both rows, rail under the hero — and the
   auto/1fr rows keep row 1 hugging the hero (with two implicit auto rows, the
   tall spanning .detail-main would distribute its height into row 1 and shove
   the rail far below the hero). An explicit modifier class, NOT :has(): this
   rule is load-bearing for the base layout, and a :has()-less browser must
   degrade to "a filter does nothing", never to a broken page. Every placement
   is scoped under the modifier so the child class names are inert elsewhere.
   The hero's .sb-slot bottom margin is dropped: the grid's row-gap spaces it. */
.detail-grid-hero { grid-template-rows: auto 1fr; }
.detail-grid-hero > .detail-hero { grid-column: 2; grid-row: 1; }
.detail-grid-hero > .detail-hero.sb-slot { margin-bottom: 0; }
.detail-grid-hero > .detail-main { grid-column: 1; grid-row: 1 / span 2; }
.detail-grid-hero > .detail-rail { grid-column: 2; grid-row: 2; }
@media (max-width: 820px) {
  .detail-grid-hero { grid-template-rows: auto; }
  .detail-grid-hero > .detail-hero,
  .detail-grid-hero > .detail-main,
  .detail-grid-hero > .detail-rail { grid-column: auto; grid-row: auto; }
}

/* Project card. A <div> (not a single <a>) so its inner links — website,
   platform icons, chat-channel badge — are valid HTML; the logo + body still
   link through to the project detail. --accent is set per-card by an
   .accent-* class (assigned deterministically from the slug). */
.pcard {
  --accent: var(--rail-blue);
  --accent-soft: #eff6ff;
  display: flex;
  flex-direction: column;
  background: white;
  border: 1px solid #e9edf2;
  border-radius: 14px;
  padding: 1.4rem 1.4rem 1.1rem;
  box-shadow: 0 1px 2px rgba(15,23,42,0.04);
  transition: box-shadow 0.18s ease, transform 0.18s ease, border-color 0.18s ease;
}
.pcard:hover {
  box-shadow: 0 10px 30px rgba(15,23,42,0.09);
  border-color: #dde3ea;
  transform: translateY(-2px);
}
.pcard-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.75rem; margin-bottom: 1rem; }
.pcard-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px; height: 56px;
  border-radius: 14px;
  background: var(--accent-soft);
  color: var(--accent);
  overflow: hidden;
  flex-shrink: 0;
}
.pcard-logo:hover { text-decoration: none; }
.pcard-logo img { width: 100%; height: 100%; object-fit: cover; }
.pcard-initial { font-family: var(--font-mono); font-size: 1.5rem; font-weight: 700; line-height: 1; }

.pcard-badges { display: flex; align-items: center; gap: 0.4rem; }
.pcard-badge {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  border-radius: 999px;
  background: #f1f5f9;
  color: var(--ballast);
}
.pcard-badge:hover { text-decoration: none; }
.pcard-badge .pcard-icon { width: 16px; height: 16px; }

/* .pcard-main carries the flex:1 that used to sit on .pcard-body, because the
   description moved out of the card's link (PRD #602): the link and the
   clipped description are now siblings, and the pair — not the link alone —
   is what stretches to push .pcard-foot to the bottom of the card. */
.pcard-main { flex: 1; }
.pcard-body { display: block; }
.pcard-body:hover { text-decoration: none; }
.pcard-name { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.01em; color: var(--steel); line-height: 1.2; }
.pcard-tagline { margin-top: 0.3rem; font-size: 0.92rem; font-weight: 600; color: var(--accent); }
.pcard-desc { margin: 0.55rem 0 0; font-size: 0.9rem; line-height: 1.55; color: var(--ballast); }

/* The clipped project description (PRD #602, issue 266). The CLIP itself is the
   browser's own: a closed <details> paints its <summary> and nothing else, so
   none of these rules is load-bearing for the cap — they only take away the
   disclosure triangle, make the summary read as one run of prose, and swap the
   "more" label for "less" once the text is open. .clip-desc is rendered
   alongside the surface's own class (.pcard-desc, .about-desc), which keeps the
   type and spacing each page already had. */
.clip-desc > summary { display: inline; cursor: pointer; list-style: none; }
.clip-desc > summary::-webkit-details-marker { display: none; }
.clip-desc > summary::marker { content: ""; }
.clip-desc-toggle { margin-left: 0.25rem; color: var(--accent, var(--rail-blue)); font-weight: 600; white-space: nowrap; }
.clip-desc-toggle:hover { text-decoration: underline; }
.clip-desc-less { display: none; }
.clip-desc[open] .clip-desc-head { display: none; }
.clip-desc[open] .clip-desc-more { display: none; }
.clip-desc[open] .clip-desc-less { display: inline; }

.pcard-foot { display: flex; align-items: center; gap: 0.5rem; margin-top: 1.1rem; padding-top: 0.9rem; border-top: 1px solid #f0f2f5; }
.pcard-foot-spacer { flex: 1; }
.pcard-website {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--accent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 60%;
}
.pcard-website-muted { color: var(--ballast); font-weight: 500; }
.pcard-platforms { display: inline-flex; align-items: center; gap: 0.15rem; }
.pcard-platform {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  border-radius: 8px;
  color: var(--ballast);
  transition: background 0.12s, color 0.12s;
}
.pcard-platform:hover { background: var(--accent-soft); color: var(--accent); text-decoration: none; }
.pcard-platform .pcard-icon { width: 18px; height: 18px; }
.pcard-arrow { display: inline-flex; align-items: center; justify-content: center; color: var(--accent); }
.pcard-arrow .pcard-icon { width: 20px; height: 20px; }
.pcard-icon { display: block; }

/* Per-user project star (PRD #92 P1): a badge-styled toggle button. Filled +
   amber when starred; a plain badge otherwise. */
.pcard-star { border: 0; padding: 0; cursor: pointer; font: inherit; transition: background 0.12s, color 0.12s; }
.pcard-star:hover { color: var(--accent); }
.pcard-star.is-starred { background: #fef9c3; color: #92600a; }
.pcard-star.is-starred:hover { color: #a16207; }
.pcard-settings { text-decoration: none; transition: background 0.12s, color 0.12s; }
.pcard-settings:hover { color: var(--accent); }

/* Project topic/hashtag chips (PRD #92 P1), on the card and the detail About
   card. The --accent fallbacks cover the detail card, which sets no accent. */
.pcard-topics { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.8rem; }
.pcard-topic {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--accent, #2563EB);
  background: var(--accent-soft, #eff6ff);
  padding: 0.12rem 0.5rem;
  border-radius: 999px;
  white-space: nowrap;
}
.about-topics { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0.5rem 0 0.15rem; }

/* ── Grid/list toggle + the dense project row (PRD #609 P0) ───────────────
   The toggle is a two-button form, styled as one segmented control. Both
   buttons are always present — the active one carries .is-active AND
   aria-pressed="true", so the state is announced as well as tinted (the
   board's "colour is never the only signal" rule, applied to a control). */
.view-toggle { display: inline-flex; border: 1px solid var(--line-2, #cbd5e1); border-radius: 7px; overflow: hidden; }
.view-toggle-btn {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font: inherit; font-size: 0.8rem; font-weight: 600;
  color: var(--ballast); background: var(--surface, #fff);
  border: 0; padding: 0.35rem 0.7rem; cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.view-toggle-btn + .view-toggle-btn { border-left: 1px solid var(--line-2, #cbd5e1); }
.view-toggle-btn:hover { color: var(--steel); background: #f1f5f9; }
.view-toggle-btn.is-active { color: var(--surface, #fff); background: var(--steel); }
.view-toggle-icon { width: 15px; height: 15px; flex: none; }

/* The list layout reuses .project-grid as its container class — that is the
   star button's hx-target and the handler's swap unit, so it must survive the
   mode switch. The compound selector wins on specificity, so the grid's
   `display: grid` is overridden here rather than depending on source order. */
.project-grid.project-list { display: flex; flex-direction: column; gap: 0; border: 1px solid var(--line, #e9edf2); border-radius: 12px; overflow: hidden; background: var(--surface, #fff); }
.prow {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.6rem 0.85rem; border-top: 1px solid var(--line, #e9edf2);
  transition: background 0.12s;
}
.prow:first-child { border-top: 0; }
.prow:hover { background: #f8fafc; }
.prow-archived { opacity: 0.68; }
.prow-logo {
  width: 30px; height: 30px; flex: none; border-radius: 7px; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-soft, #eff6ff); color: var(--accent, #2563EB);
}
.prow-logo:hover { text-decoration: none; }
.prow-logo img { width: 100%; height: 100%; object-fit: cover; }
.prow-logo .pcard-initial { font-size: 0.95rem; }
/* min-width:0 so the clamped text can shrink instead of forcing the row wide. */
.prow-body { display: flex; align-items: baseline; gap: 0.5rem; min-width: 0; flex: 1 1 auto; color: inherit; }
.prow-body:hover { text-decoration: none; }
.prow-name { font-size: 0.95rem; font-weight: 700; color: var(--steel); white-space: nowrap; }
.prow-tagline { font-size: 0.82rem; font-weight: 600; color: var(--accent, #2563EB); white-space: nowrap; }
/* The description is the row's only elastic column: it takes the slack and
   ellipsizes, so a card-length blurb never pushes the badges off-screen. */
.prow-desc { font-size: 0.82rem; color: var(--ballast); min-width: 0; flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prow-topics { display: flex; gap: 0.3rem; flex: none; overflow: hidden; }
.prow-links { display: flex; align-items: center; gap: 0.4rem; flex: none; }
.prow-links .pcard-website { font-size: 0.78rem; }
.prow-links .pcard-platform .pcard-icon { width: 15px; height: 15px; }
.prow-badges { flex: none; }
@media (max-width: 720px) {
  /* Below the phone breakpoint a row cannot hold every column on one line, so
     it wraps rather than ellipsizing everything down to nothing. The fields
     are all still rendered — the list drops layout, never data. */
  .prow { flex-wrap: wrap; }
  .prow-desc, .prow-topics { flex-basis: 100%; white-space: normal; }
}

/* Per-project accent schemes, mapped from the slug in projectAccentClass. */
.pcard.accent-blue   { --accent: #2563EB; --accent-soft: #eff6ff; }
.pcard.accent-green  { --accent: #059669; --accent-soft: #ecfdf5; }
.pcard.accent-amber  { --accent: #D97706; --accent-soft: #fffbeb; }
.pcard.accent-red    { --accent: #DC2626; --accent-soft: #fef2f2; }
.pcard.accent-indigo { --accent: #4F46E5; --accent-soft: #eef2ff; }
.pcard.accent-teal   { --accent: #0D9488; --accent-soft: #f0fdfa; }

/* Project settings — logo upload + product-link editor */
.logo-field { display: flex; align-items: flex-start; gap: 1rem; }
.logo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px; height: 64px;
  border-radius: 14px;
  object-fit: cover;
  background: #f1f5f9;
  color: var(--ballast);
  border: 1px solid #e5e7eb;
  flex-shrink: 0;
  overflow: hidden;
}
.logo-preview-empty { font-family: var(--font-mono); font-size: 1.6rem; font-weight: 700; }
.logo-field-controls { flex: 1; min-width: 0; }
.logo-field-controls input[type="file"] { font-size: 0.85rem; max-width: 100%; }
.logo-remove { display: block; margin-top: 0.5rem; font-size: 0.8rem; color: var(--ballast); }
/* Workspace icon — the preview is the upload button (views_workspace_settings_general.templ) */
.logo-pick { position: relative; display: block; flex-shrink: 0; cursor: pointer; border-radius: 14px; }
.logo-pick .logo-preview { transition: box-shadow 0.15s ease; }
.logo-pick:hover .logo-preview,
.logo-pick:focus-within .logo-preview { box-shadow: 0 0 0 2px var(--signal-green-bright, #34D399); }
.logo-pick-badge {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 0.1rem 0; border-radius: 0 0 14px 14px;
  background: rgba(15, 23, 42, 0.62); color: #fff;
  font-size: 0.65rem; font-weight: 600; text-align: center; letter-spacing: 0.02em;
  opacity: 0; transition: opacity 0.15s ease; pointer-events: none;
}
.logo-pick:hover .logo-pick-badge,
.logo-pick:focus-within .logo-pick-badge { opacity: 1; }
.logo-pick-input { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.links-editor { display: flex; flex-direction: column; gap: 0.5rem; }
.link-row { display: flex; gap: 0.5rem; }
.link-row .link-kind {
  flex: 0 0 130px;
  padding: 0.5rem 0.6rem;
  border: 1px solid #d1d5db;
  border-radius: var(--radius);
  background: white;
  font-family: inherit;
  font-size: 0.88rem;
}
.link-row .link-url {
  flex: 1; min-width: 0;
  padding: 0.5rem 0.6rem;
  border: 1px solid #d1d5db;
  border-radius: var(--radius);
  font-size: 0.88rem;
  font-family: inherit;
}
.link-row .link-url:focus, .link-row .link-kind:focus {
  outline: none;
  border-color: var(--rail-blue);
  box-shadow: 0 0 0 3px rgba(37,99,235,0.1);
}

/* ============================================================
   PRD review surface — participant avatars, About sidebar,
   approver (crown) + review log
   ============================================================ */
.prd-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.prd-header-main { min-width: 0; }

/* Contributor-style avatar circles. Slight overlap + white ring like GitHub. */
/* Space participant avatars apart (no GitHub-style overlap) so each one's
   corner role icons are never clipped by a neighbour. The gap clears the
   upper-right icon's outward overhang. */
.participant-row { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
.participant-row .avatar { margin-left: 0; }

/* Contributors card "+" disclosure (sw-b00x.3, PRD #29): a native <details>
   that reveals the PRD-contributor add form in place — no JS, matching the
   phase-accordion / summon-form idiom. The summary is the card heading plus a
   round "+" that rotates into an "×" while the form is open. */
.contributor-add { margin: 0; }
.contributor-add > summary.contributor-add-summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}
.contributor-add > summary.contributor-add-summary::-webkit-details-marker { display: none; }
.contributor-add > summary.contributor-add-summary:focus-visible {
  outline: 2px solid var(--rail-blue);
  outline-offset: 2px;
  border-radius: var(--radius);
}
.contributor-add-title { font-size: 0.85rem; font-weight: 600; margin: 0; }
.contributor-add-plus {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  border: 1px solid var(--rail);
  color: var(--ballast);
  font-size: 1rem;
  line-height: 1;
  transition: transform 0.2s ease, background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.contributor-add > summary:hover .contributor-add-plus,
.contributor-add > summary:focus-visible .contributor-add-plus {
  background: var(--rail-blue);
  color: #fff;
  border-color: var(--rail-blue);
}
.contributor-add[open] > summary .contributor-add-plus { transform: rotate(45deg); }
.contributor-add-body { margin-bottom: 0.6rem; }

.avatar {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px; height: 36px;
  margin-left: -8px;
  border-radius: 50%;
  border: 2px solid #fff;
  background: var(--ballast);
  color: #fff;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.85rem;
  flex-shrink: 0;
}
/* Agents read as rounded squares to distinguish them from human circles. */
.avatar.avatar-agent { border-radius: 9px; border-style: dashed; }
.avatar.accent-blue   { background: #2563EB; }
.avatar.accent-green  { background: #059669; }
.avatar.accent-amber  { background: #D97706; }
.avatar.accent-red    { background: #DC2626; }
.avatar.accent-indigo { background: #4F46E5; }
.avatar.accent-teal   { background: #0D9488; }
/* Upper-right role icon: the crown for the designated approver (the "king"),
   the knight icon for a knight (a PRD approve/recommend grant-holder). z-index
   keeps it riding above any neighbouring avatar image. */
.avatar-crown, .avatar-knight {
  position: absolute;
  top: -9px; right: -8px;
  width: 16px; height: 16px;
  z-index: 2;
  filter: drop-shadow(0 1px 1px rgba(0,0,0,0.25));
}
/* The knight is a full figure rather than a compact crown — give it a touch
   more room so it reads at avatar scale. */
.avatar-knight { width: 18px; height: 18px; top: -11px; right: -9px; }
.avatar-approve {
  position: absolute;
  bottom: -6px; right: -7px;
  width: 15px; height: 15px;
  z-index: 2;
  background: #fff;
  border-radius: 50%;
  box-shadow: 0 0 0 1px #e2e8f0;
}
/* PRD-contributor badge (PRD #28 sw-6nc.9): a small diamond on the bottom-left —
   the one avatar corner the crown/approve overlays leave free, and the side that
   stays on top when avatars overlap. It marks an avatar present via a PRD-scoped
   contribute grant rather than the discussion roster. The cue is shape + corner +
   a title, never colour alone: a project contributor simply has no pip here. */
.avatar-prd-badge {
  position: absolute;
  bottom: -4px; left: -4px;
  width: 11px; height: 11px;
  z-index: 2;
  background: var(--steel);
  border: 1.5px solid #fff;
  transform: rotate(45deg);
}

/* Avatar as a profile link */
.avatar-link { display: inline-flex; }
.avatar-link:hover { text-decoration: none; }
.avatar-link:hover .avatar { box-shadow: 0 0 0 2px var(--rail-blue); }

/* Contributor profile page */
.avatar.avatar-lg { width: 56px; height: 56px; font-size: 1.4rem; margin-left: 0; border-width: 0; }
/* Photo avatar: a real uploaded image cropped to fill the circle instead of
   the initials+color fallback. object-fit keeps non-square uploads centered.
   The beige backdrop shows through a transparent/clear PNG or SVG so it never
   renders as an invisible hole; the white ring comes from the base .avatar
   border. */
.avatar.avatar-photo { background: var(--avatar-bg); object-fit: cover; }
/* Photo inside a participant badge. The <span> stays the positioning context
   for the crown/approve overlays (an <img> avatar can't hold child elements),
   so the image is layered absolutely to fill the circle inside the white ring. */
.avatar.avatar-photo-badge { background: var(--avatar-bg); }
.avatar-badge-img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  border-radius: 50%;
  object-fit: cover;
  /* Same beige backdrop as .avatar-photo: the absolutely-positioned <img>
     covers the parent span, so it needs the fill itself for transparent art. */
  background: var(--avatar-bg);
}
.profile-head { display: flex; align-items: center; gap: 1rem; margin-top: 0.5rem; }
.profile-head h1 { display: flex; align-items: center; gap: 0.6rem; }
.profile-you { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.06em; background: #e0e7ff; color: #3730a3; padding: 0.1rem 0.45rem; border-radius: 999px; font-weight: 600; }
.profile-projects { display: flex; flex-direction: column; }
.profile-project { padding: 0.6rem 0; border-bottom: 1px solid #f1f5f9; font-weight: 500; color: var(--steel); }
.profile-project:last-child { border-bottom: none; }
.profile-project:hover { color: var(--rail-blue); }

/* About sidebar panel */
.about-card .about-tagline { font-size: 0.85rem; font-weight: 600; color: var(--rail-blue); margin: 0 0 0.4rem; }
.about-card .about-desc { font-size: 0.84rem; color: var(--ballast); line-height: 1.5; margin: 0 0 0.6rem; }
.about-meta { display: flex; flex-direction: column; gap: 0.1rem; }
.about-row { display: flex; align-items: center; gap: 0.45rem; font-size: 0.82rem; color: var(--steel); padding: 0.22rem 0; }
.about-row:hover { text-decoration: none; }
.about-row .pcard-icon { width: 15px; height: 15px; color: var(--ballast); flex-shrink: 0; }
.about-links { display: flex; gap: 0.15rem; margin-top: 0.35rem; }

/* Buzz discoverability (workspace dashboard community card). The channel badge
   and About row reuse .pcard-badge / .about-row, so only the card is new. */
.buzz-community { display: flex; align-items: flex-start; gap: 0.7rem; padding: 0.9rem 1rem; }
.buzz-community .pcard-icon { width: 20px; height: 20px; color: var(--rail-blue); flex-shrink: 0; margin-top: 0.1rem; }
.buzz-community-body { min-width: 0; }
.buzz-host { font-family: var(--font-mono); font-size: 0.85rem; color: var(--steel); word-break: break-all; user-select: all; }
.buzz-hint { margin: 0.3rem 0 0; font-size: 0.8rem; color: var(--ballast); }

/* Version history list (PRD sidebar) */
.version-list { display: flex; flex-direction: column; gap: 0.1rem; }
.version-row { display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0; border-bottom: 1px solid #f1f5f9; font-size: 0.82rem; }
.version-row:last-child { border-bottom: none; }
.version-link { font-family: var(--font-mono); font-weight: 600; color: var(--rail-blue); }
.version-date { color: var(--ballast); font-size: 0.75rem; }
.version-latest { margin-left: auto; font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.04em; background: #dcfce7; color: #166534; padding: 0.05rem 0.4rem; border-radius: 999px; }
.version-viewall { display: inline-block; margin-top: 0.5rem; font-size: 0.75rem; font-weight: 600; color: var(--rail-blue); }
.version-viewall:hover { text-decoration: underline; }

/* Coding agent section (Contributors card / project sidebar) */
.coding-agent { border-top: 1px solid #f0f2f5; margin-top: 0.75rem; padding-top: 0.6rem; }
.coding-agent:first-child { border-top: 0; margin-top: 0; padding-top: 0; }
.coding-agent-title { font-size: 0.85rem; font-weight: 600; margin: 0 0 0.5rem; }
.agent-row { display: flex; align-items: center; gap: 0.45rem; padding: 0.2rem 0; }
.agent-icon { width: 18px; height: 18px; flex-shrink: 0; }
.agent-name { font-size: 0.85rem; font-weight: 500; color: var(--steel); }
.agent-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ballast); margin-left: auto; flex-shrink: 0; }
.agent-dot.online { background: var(--signal-green); box-shadow: 0 0 0 2px #dcfce7; }
/* Sync-bridge indicator: the always-on local runtime (the conductor), distinct
   from the (possibly ephemeral) coding agent — green connected, amber idle,
   neutral grey when none is bound. */
.sync-bridge-row .pcard-icon { width: 18px; height: 18px; }
.agent-dot.sync-connected { background: var(--signal-green); box-shadow: 0 0 0 2px #dcfce7; }
.agent-dot.sync-idle { background: var(--signal-amber); box-shadow: 0 0 0 2px #fef3c7; }
.agent-dot.sync-disconnected { background: var(--ballast); }
.agent-dot.sync-stale { background: var(--signal-red); box-shadow: 0 0 0 2px #fee2e2; }
.agent-dot.sync-suspended { background: var(--ballast); opacity: 0.6; }
/* Agent-liveness chips + last-seen (PRD #104): stale is the silent-stop alarm,
   suspended the neutral "paused on purpose" twin, last-seen the always-on
   timestamp both ride next to. */
.status-badge.liveness-stale { background: #fee2e2; color: var(--signal-red); }
.status-badge.liveness-suspended { background: #f1f5f9; color: var(--ballast); }
/* Project-level Conductor chip (PRD #458 P0): is a Conductor home? Unknown
   borrows the muted suspended palette rather than the red offline one — "not
   saying" must not read as an alarm. */
.status-badge.conductor-online { background: #dcfce7; color: var(--signal-green); }
.status-badge.conductor-offline { background: #fee2e2; color: var(--signal-red); }
.status-badge.conductor-unknown { background: #f1f5f9; color: var(--ballast); }
.status-badge.conductor-held { background: #fef3c7; color: var(--signal-amber); }
.conductor-liveness .conductor-roles { margin-top: 0.3rem; }
.agent-lastseen { font-size: 0.72rem; color: var(--ballast); white-space: nowrap; }
.agent-empty { font-size: 0.82rem; color: var(--ballast); margin: 0; }
.agent-hint { font-size: 0.72rem; color: var(--ballast); line-height: 1.4; margin: 0.5rem 0 0; }
.agent-hint code { background: #f1f5f9; padding: 0 0.25rem; border-radius: 3px; font-size: 0.92em; }

/* Approver (crown) line + review log glyphs */
.approver-line { display: flex; align-items: center; gap: 0.4rem; }
.approver-line .crown-icon { width: 18px; height: 18px; flex-shrink: 0; }
.approver-name { font-weight: 600; font-size: 0.85rem; color: var(--steel); }
.review-glyph { font-size: 0.9rem; line-height: 1; }
.act-icon { width: 17px; height: 17px; flex-shrink: 0; object-fit: contain; vertical-align: middle; }

/* Personal "hand" — the request-changes signature. One monochrome hand.svg
   tinted via CSS mask, so background-color (set inline per reviewer) shows
   through the silhouette. Lets every palette color reuse a single asset. */
.hand-mask {
	display: inline-block;
	width: 17px;
	height: 17px;
	flex-shrink: 0;
	vertical-align: middle;
	background-color: #34D399; /* overridden inline per reviewer */
	-webkit-mask: url(/static/icons/hand.svg) center / contain no-repeat;
	mask: url(/static/icons/hand.svg) center / contain no-repeat;
}
/* Inline color picker on the viewer's own hand (zero-JS <details> popover). */
.hand-picker { position: relative; display: inline-flex; }
.hand-picker > .hand-summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; }
.hand-picker > .hand-summary::-webkit-details-marker { display: none; }
.hand-picker > .hand-summary:hover .hand-mask { filter: brightness(1.1); }
.hand-swatches {
	position: absolute;
	top: 140%;
	left: -6px;
	z-index: 30;
	display: flex;
	gap: 6px;
	margin: 0;
	padding: 8px;
	background: #fff;
	border: 1px solid #e2e8f0;
	border-radius: 10px;
	box-shadow: 0 8px 24px rgba(15, 23, 42, 0.16);
}
.hand-swatch {
	width: 20px;
	height: 20px;
	padding: 0;
	border-radius: 50%;
	border: 2px solid #fff;
	box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.14);
	cursor: pointer;
	transition: transform 0.08s ease;
}
.hand-swatch:hover { transform: scale(1.15); }

/* Phases / milestones strip on the PRD Document tab. Each phase shows a label
   badge, optional title, done/total work-item count, and a progress bar whose
   fill is derived from the beads mirror. A fully-landed phase turns green and
   earns a "passed" badge. */
.phase-card { padding: 1rem 1.25rem; margin-bottom: 1rem; }
.phase-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; margin-bottom: 0.75rem; }
.phase-card-head h3 { font-size: 0.95rem; font-weight: 600; margin: 0; }
.phase-card-sub { font-size: 0.72rem; color: var(--ballast); }
.phase-empty { font-size: 0.82rem; color: var(--ballast); margin: 0 0 0.85rem; line-height: 1.45; }
/* PRD #56 P1: phased PRD has work items mapped to no declared phase — an amber
   caution line so the gap reads as actionable, not a hard error. */
.phase-unmapped { font-size: 0.8rem; color: #92400e; background: #fef3c7; border: 1px solid #fde68a; border-radius: 4px; padding: 0.5rem 0.7rem; margin: 0 0 0.85rem; line-height: 1.45; }
.phase-unmapped code { font-family: var(--font-mono); font-size: 0.92em; background: rgba(146,64,14,0.1); padding: 0 0.25em; border-radius: 3px; }
.phase-block { margin-bottom: 1rem; }
.phase-list { display: flex; flex-direction: column; gap: 0; margin-bottom: 0.6rem; }
/* Compact one-line milestone rows (mockup §PRD page parity): label | title |
   ——bar—— | count. phaseHead emits inline children; the flex line is the row
   itself, or the <summary> for a described (accordion) phase. */
.phase-row { border-top: 1px solid #e2e8f0; }
.phase-row:not(.phase-accordion),
.phase-summary { display: flex; align-items: center; gap: 10px; padding: 7px 0; }
.phase-label {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 700;
  flex: 0 0 26px;
  color: var(--steel);
}
.phase-badge {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.3px;
  color: #fff;
  background: var(--rail-blue);
  padding: 0.1rem 0.45rem;
  border-radius: 6px;
  flex: none;
}
.phase-row.passed .phase-badge { background: var(--signal-green); }
.phase-name {
  flex: 0 0 150px;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--steel);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Count carries the state word ("3/3 done" / "2/4 · running"); the
   validated-criteria signal (sw-b2ay.3) lives in its hover title. */
.phase-count { flex: 0 0 82px; text-align: right; font-size: 0.75rem; color: var(--ballast); font-variant-numeric: tabular-nums; }
.phase-del { margin: 0; line-height: 0; }
.phase-del-btn {
  border: none;
  background: none;
  color: var(--ballast);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.1rem;
  opacity: 0.5;
}
.phase-del-btn:hover { opacity: 1; color: var(--signal-red); }
.phase-desc { font-size: 0.8rem; color: var(--steel); line-height: 1.45; margin: 0 0 0.45rem; }
.phase-desc > :first-child { margin-top: 0; }
.phase-desc > :last-child { margin-bottom: 0; }
.phase-bar { flex: 1; height: 7px; background: #eef2f7; border-radius: 99px; overflow: hidden; }
/* Amber while running, green once the phase completes — mockup's fill tones. */
.phase-bar-fill { display: block; height: 100%; background: #f59e0b; border-radius: 99px; transition: width 0.3s ease; }
.phase-row.passed .phase-bar-fill { background: #10b981; }
/* Authoring controls read quiet in the compact strip: hidden until the row is
   hovered (or the control itself is focused), full-strength on their own hover. */
.phase-row .phase-edit-btn, .phase-row .phase-del-btn { opacity: 0; transition: opacity 0.15s ease; }
.phase-row:hover .phase-edit-btn, .phase-row:hover .phase-del-btn,
.phase-row .phase-edit-btn:focus-visible, .phase-row .phase-del-btn:focus-visible { opacity: 0.55; }
.phase-row .phase-edit-btn:hover { opacity: 1; }
/* The add-phase form collapses behind a quiet disclosure. */
.phase-add-wrap > summary { list-style: none; cursor: pointer; }
.phase-add-wrap > summary::-webkit-details-marker { display: none; }
.phase-add-toggle { font-size: 0.78rem; font-weight: 600; color: var(--ballast); }
.phase-add-toggle:hover { color: var(--steel); }
.phase-add-wrap[open] .phase-add-toggle { display: none; }
/* Implicit read-only Scope overview (sw-uht.6): a muted left-accent band that
   reads as context, distinct from the blue P-labeled milestone rows. */
.phase-overview { border-left: 3px solid var(--ballast); padding: 0.1rem 0 0.1rem 0.7rem; }
.phase-badge-overview { background: var(--ballast); }
/* Collapsible phase accordion (sw-b00x.1): a described phase becomes a native
   <details> disclosure — the header (summary) stays put, the markdown
   description is revealed on click. A description-less phase stays a plain
   header row. No JavaScript: the browser owns the open/closed state. */
.phase-accordion > summary { list-style: none; cursor: pointer; }
.phase-accordion > summary::-webkit-details-marker { display: none; }
.phase-accordion > summary:focus-visible {
  outline: 2px solid var(--rail-blue);
  outline-offset: 2px;
  border-radius: var(--radius);
}
/* Chevron drawn from two borders (no icon asset): points right when collapsed,
   rotates down when the accordion is open. */
.phase-chevron {
  flex: none;
  width: 0.42rem;
  height: 0.42rem;
  border-right: 2px solid var(--ballast);
  border-bottom: 2px solid var(--ballast);
  transform: rotate(-45deg);
  transition: transform 0.2s ease;
  margin-left: 0.1rem;
}
.phase-accordion[open] > summary .phase-chevron { transform: rotate(45deg); }
.phase-accordion-body { margin-top: 0.45rem; }
/* Full-document view (sw-b00x.1): the flat structured sections (Summary/Goals/
   Out-of-scope/Risks/Acceptance/Full-doc), reached via ?doc=full from the
   overview's "View full document" link or the Versions card. */
.prd-fulldoc-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1rem;
}
.prd-fulldoc-back { font-size: 0.85rem; font-weight: 600; text-decoration: none; }
.prd-fulldoc-tag {
  font-size: 0.72rem;
  color: var(--ballast);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.prd-fulldoc-link { margin: 0.85rem 0 0; }
.prd-fulldoc-link a { font-size: 0.82rem; font-weight: 600; text-decoration: none; }
.phase-add { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.phase-add-label, .phase-add-title, .phase-add-desc {
  font-size: 0.82rem;
  padding: 0.35rem 0.55rem;
  border: 1px solid #d1d5db;
  border-radius: var(--radius);
  font-family: inherit;
}
.phase-add-label { width: 4.5rem; font-family: var(--font-mono); }
.phase-add-title { flex: 1; min-width: 8rem; }
.phase-add-desc { flex: 1 0 100%; resize: vertical; min-height: 2.4rem; }
.phase-add-btn { font-size: 0.78rem; white-space: nowrap; }
/* Inline phase edit (sw-bdp): the ✎ control in a phase header reveals a
   pre-filled form to revise that milestone's title + description in place. The
   form is hidden until toggled (the [hidden] rule wins over the flex display);
   the label is read-only context (the phase's identity, not an editable field).
   Mirrors the .phase-del control and reuses the .phase-add-* input styling. */
.phase-edit-btn {
  border: none;
  background: none;
  color: var(--ballast);
  font-size: 0.92rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.1rem;
  opacity: 0.5;
}
.phase-edit-btn:hover { opacity: 1; color: var(--rail-blue); }
.phase-edit {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin: 0.1rem 0 0.6rem;
  padding: 0.6rem 0.7rem;
  background: var(--surface);
  border: 1px solid var(--line, #e2e8f0);
  border-radius: var(--radius);
}
.phase-edit[hidden] { display: none; }
.phase-edit-head { display: flex; align-items: center; gap: 0.5rem; }
.phase-edit-hint { font-size: 0.72rem; color: var(--ballast); }
.phase-edit .phase-add-title { width: 100%; flex: none; min-width: 0; }
.phase-edit-actions { display: flex; align-items: center; gap: 0.6rem; }
.phase-edit-cancel {
  border: none;
  background: none;
  color: var(--ballast);
  font-size: 0.78rem;
  cursor: pointer;
  padding: 0.2rem 0.1rem;
}
.phase-edit-cancel:hover { color: var(--steel); text-decoration: underline; }

/* Completion tab — the "Review & complete" evidence surface (sw-bls.3). The
   .completion-surface wrapper itself went with prdCompletion in issue 177, and
   the three-metric .completion-rollup went with PRD #309 P0 (its replacement,
   .crit-board-stats, lives with the board it counts). The bar and percentage
   rules below are shared by that header and by the work-item / action cards,
   which mount directly under the criterion board's section. */
.completion-bar { height: 8px; }
.completion-pct { font-size: 0.75rem; color: var(--ballast); margin-top: 0.4rem; font-variant-numeric: tabular-nums; }

/* Completion action — the human-gated mark-complete / release control (sw-bls.4) */
.completion-action { padding: 1rem 1.25rem; margin-top: 1rem; }
.completion-action > .phase-card-head { margin-bottom: 0.75rem; }
.completion-state { font-size: 0.85rem; color: var(--steel); margin: 0; line-height: 1.5; }
.completion-form { display: flex; flex-direction: column; gap: 0.5rem; }
.completion-comment {
  width: 100%;
  font-size: 0.82rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid #d1d5db;
  border-radius: var(--radius);
  font-family: inherit;
  resize: vertical;
}
.completion-submit { align-self: flex-start; font-size: 0.82rem; }

/* "Ready to complete" auto-suggest — affirmative nudge when every acceptance
   criterion is covered by a closed work item (sw-bls.5). A derived suggestion,
   not a state change: it sits above the human-gated form, never replaces it. */
.completion-ready {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  background: #f0fdf4;
  border: 1px solid #bbf7d0;
  border-radius: var(--radius);
  padding: 0.6rem 0.75rem;
  margin-bottom: 0.75rem;
}
.completion-ready-icon { line-height: 1.4; flex-shrink: 0; }
.completion-ready-text { font-size: 0.85rem; color: var(--steel); line-height: 1.45; }
.completion-ready-text strong { color: var(--signal-green); }

/* Completed specs — collapsed by default below the active list */
.completed-specs { margin-top: 1.25rem; }
.completed-specs > summary {
  list-style: none;
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ballast);
  padding: 0.4rem 0;
  user-select: none;
}
.completed-specs > summary::-webkit-details-marker { display: none; }
.completed-specs > summary::before {
  content: "▸";
  margin-right: 0.4rem;
  font-size: 0.75rem;
  transition: transform 0.15s ease;
  display: inline-block;
}
.completed-specs[open] > summary::before { transform: rotate(90deg); }
.completed-specs > summary:hover { color: var(--steel); }

/* Project tabs — the pipe nav. Mockup-exact: 14px/600 links at 13px 16px, the
   strip inset 12px so the first tab's text lands ~28px from the edge. */
.project-tabs { display: flex; gap: 2px; border-bottom: 1px solid #e6ebf1; margin-bottom: 0; padding: 0 12px; }
.project-tab { padding: 13px 16px; font-size: 14px; font-weight: 600; color: var(--ballast); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }
.project-tab.active { color: #0e7490; border-bottom-color: #0e7490; }
/* The Docs tab with no repo bound: same grey-out the old rail link had — visible
   so the surface is discoverable, inert so it doesn't lead to an empty page. */
.project-tab.disabled { color: #cbd5e1; cursor: not-allowed; }
.project-tab .tab-count {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0 0.4rem;
  font-size: 0.72rem;
  font-weight: 600;
  background: #e2e8f0;
  color: var(--ballast);
  border-radius: 999px;
}
.project-tab.active .tab-count { background: rgba(37,99,235,0.12); color: var(--rail-blue); }
/* On phones the tab strip scrolls horizontally instead of wrapping to a
   second row (which would detach tabs from their underline). Tighter padding
   fits the common Document/Discussion/Activity set without scrolling at all. */
@media (max-width: 640px) {
  .project-tabs { overflow-x: auto; flex-wrap: nowrap; -webkit-overflow-scrolling: touch; }
  .project-tab { padding: 0.6rem 0.9rem; white-space: nowrap; }
}

/* Project chrome — the "Follow the Flow" three-zone shell (PRD #89 P0).
   Header appbar (title + Actions ▾ + gear), a left reference rail (Docs +
   Settings), and a main column holding the pipeline nav + page body. The right
   zone is a page's own informational sidebar (e.g. the PRDs detail-grid), not
   owned here. */
/* PRD #103 crit1 — white top-nav bar: the appbar (breadcrumb + actions) and the
   full-width pipe sit together on white, above the slate shell, matching the mockup:
   two tight stacked bars (appbar 12px, pipe rows 13px), EACH closed by a full-bleed
   1px hairline — the appbar's own border is the line between the breadcrumb row and
   the tabs (mockup .appbar/.pipe). Horizontal insets live on the children so both
   hairlines run edge to edge; the bar's margin-bottom gaps it from the slate below. */
.project-topbar {
  background: var(--surface);
  padding: 0;
  box-sizing: border-box;
  /* Full-bleed to the TRUE viewport edges (like the black global nav), not just the
     .container edges — 100vw sized, pulled left by the centering margin. */
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-bottom: 1.5rem;
}
.project-appbar {
  margin-bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 12px 20px;
  border-bottom: 1px solid #e6ebf1;
}
.project-appbar-back { font-size: 0.85rem; color: var(--ballast); display: block; }
.project-appbar-title { font-size: 1.5rem; font-weight: 600; margin-top: 0.25rem; }
/* PRD #103 crit1 — appbar breadcrumb ("Groundspeak / Switchyard / PRD #83"), replacing
   the ← Projects link + lone project title. Muted ancestor links, a faint separator,
   and the current page bold, matching the mockup. */
.project-crumb { display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; font-size: 14px; }
.project-crumb-link { color: var(--ballast); text-decoration: none; }
.project-crumb-link:hover { color: var(--steel); text-decoration: underline; }
.project-crumb-sep { color: #cbd5e1; }
.project-crumb-current { color: var(--steel); font-weight: 600; }
/* Flex spacer that pushes the Activity tab to the right edge of the pipe. */
.project-tabs-spacer { flex: 1 1 auto; }
/* Eye-count in the appbar watch control (PRD #103 crit1: "👁 12"). */
.project-watch-count { font-weight: 600; }
/* "Latest discussions" card atop the PRD detail center column (PRD #103 crit1). */
.prd-discussions { padding: 1rem 1.25rem; margin-bottom: 1.25rem; }
.prd-discussions-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 0.5rem; }
.prd-discussions-head h3 { font-size: 0.95rem; margin: 0; }
.prd-discussions-all { font-size: 0.8rem; color: #0e7490; text-decoration: none; white-space: nowrap; }
.prd-discussions-all:hover { text-decoration: underline; }
.prd-discussions-list { list-style: none; margin: 0; padding: 0; }
.prd-discussion-row { display: flex; align-items: center; gap: 0.5rem; padding: 0.45rem 0; border-top: 1px solid #eef2f7; }
.prd-discussion-row:first-child { border-top: none; padding-top: 0; }
.prd-discussion-title { color: var(--steel); text-decoration: none; font-size: 0.9rem; }
.prd-discussion-title:hover { color: #0e7490; text-decoration: underline; }
.prd-discussion-badge { font-size: 0.7rem; color: var(--ballast); background: var(--page); border-radius: 999px; padding: 0.05rem 0.5rem; }
/* PRD detail right-rail "Details" card (design-mockup parity) — a compact
   key/value summary (Status / Priority / Phases) mirroring the mockup's right
   rail. Each row is a label→value pair split by a light top border (the first
   row butts under the title); the label is muted and the value right-aligns so
   the column reads as a clean list, matching the .prd-discussion-row divider. */
.prd-details-row { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; padding: 0.4rem 0; border-top: 1px solid #eef2f7; font-size: 0.82rem; }
.prd-details-row:first-child { border-top: none; padding-top: 0; }
.prd-details-label { color: var(--ballast); }
.prd-details-value { text-align: right; font-weight: 500; color: var(--steel); }
.project-appbar-actions { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }
/* PRD #855: the chrome's project search field (views_project_search.templ). */
.project-search { display: flex; margin: 0; }
.project-search input[type="search"] { font-size: 0.78rem; width: 11rem; padding: 0.2rem 0.45rem; }
.project-gear,
.project-wallboard {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  border: 1px solid #e5e7eb;
  border-radius: 9px;
  color: var(--ballast);
  line-height: 1;
  background: #fff;
}
.project-gear svg,
.project-wallboard svg { width: 1.1rem; height: 1.1rem; fill: currentColor; }
.project-gear:hover,
.project-wallboard:hover { background: #f1f5f9; color: var(--steel); }
/* Wallboard locomotive shares the gear's square anatomy; teal while you are on it,
   matching the watching eye. While work is in flight the Lottie train (mounted in
   .project-wallboard-train by layout.templ) replaces the static glyph; its own
   <svg> is sized by lottie, so only the container is pinned here. */
.project-wallboard.active { color: #0e7490; border-color: #7dd3fc; background: #ecfeff; }
.project-wallboard-train { display: inline-flex; width: 1.35rem; height: 1.35rem; }
.project-wallboard-train svg { display: block; }

/* Per-project notification watch control (PRD #90 P0) — an eye + "Watching" label
   + a compact <select>, sitting in the appbar next to Actions/gear. Styled to
   match .project-gear so the three read as one control cluster. */
/* PRD #90 P1 stacks the level row over the optional Custom checklist + the Daily
   Digest opt-in, so the control is a column; .project-watch-row keeps the icon +
   label + select on one line as before. */
/* Compact eye-pill watch control (design parity) — a <details> whose summary is
   the mockup's .watch pill (grey → teal when watching); the level/custom/digest/
   DND controls tuck into the dropdown panel so the appbar stays minimal but the
   feature is intact. */
.project-watch { position: relative; display: inline-flex; flex-shrink: 0; }
.project-watch > summary { list-style: none; }
.project-watch > summary::-webkit-details-marker { display: none; }
.project-watch-summary { display: inline-flex; align-items: center; gap: 0.45rem; height: 2.1rem; padding: 0 0.7rem; background: #fff; border: 1px solid #e2e8f0; border-radius: 9px; color: #94a3b8; font-weight: 600; font-size: 0.8rem; line-height: 1; cursor: pointer; }
.project-watch-summary:hover { background: #f8fafc; }
.project-watch-eye { width: 1.1rem; height: 1.1rem; fill: currentColor; flex: 0 0 auto; }
.project-watch.watching .project-watch-summary { color: #0e7490; border-color: #7dd3fc; background: #ecfeff; }
/* Decision-inbox badge (PRD #133): a pending-human-gate count pill in the appbar,
   same anatomy as .project-watch-summary. Amber when gates are waiting (calls for
   action); muted grey at rest, where it reads "no decisions pending". */
.project-decisions { display: inline-flex; align-items: center; gap: 0.4rem; height: 2.1rem; padding: 0 0.7rem; background: #fffbeb; border: 1px solid #fcd34d; border-radius: 9px; color: #b45309; font-weight: 600; font-size: 0.8rem; line-height: 1; flex-shrink: 0; white-space: nowrap; }
.project-decisions-icon { width: 1.05rem; height: 1.05rem; fill: currentColor; flex: 0 0 auto; }
.project-decisions.empty { background: #fff; border-color: #e2e8f0; color: #94a3b8; }
/* Both appbar count badges are LINKS (decisions → Inbox, human tasks → the Human
   Tasks board): keep the pill's own color when the anchor styles would recolor it,
   and mark hover with a border/background lift rather than an underline. */
a.project-decisions:hover { text-decoration: none; background: #fef3c7; border-color: #f59e0b; }
a.project-decisions.empty:hover { background: #f1f5f9; border-color: #cbd5e1; color: #64748b; }
/* Human-tasks badge: same anatomy as .project-decisions, its own accent (the
   people-side blue) so the two counts read as siblings, not duplicates. */
.project-humantasks { display: inline-flex; align-items: center; gap: 0.4rem; height: 2.1rem; padding: 0 0.7rem; background: #eff6ff; border: 1px solid #93c5fd; border-radius: 9px; color: #1d4ed8; font-weight: 600; font-size: 0.8rem; line-height: 1; flex-shrink: 0; white-space: nowrap; }
.project-humantasks-icon { width: 1.05rem; height: 1.05rem; fill: currentColor; flex: 0 0 auto; }
.project-humantasks.empty { background: #fff; border-color: #e2e8f0; color: #94a3b8; }
a.project-humantasks:hover { text-decoration: none; background: #dbeafe; border-color: #60a5fa; }
a.project-humantasks.empty:hover { background: #f1f5f9; border-color: #cbd5e1; color: #64748b; }
/* Backlog badges (backlog_badges.go): open issues, ideas and questions. The same
   anatomy as the two badges above, each with its own accent so the row reads as
   distinct counts; muted grey at rest like theirs. */
.project-countbadge { display: inline-flex; align-items: center; gap: 0.4rem; height: 2.1rem; padding: 0 0.7rem; border: 1px solid; border-radius: 9px; font-weight: 600; font-size: 0.8rem; line-height: 1; flex-shrink: 0; white-space: nowrap; }
.project-countbadge-icon { width: 1.05rem; height: 1.05rem; fill: currentColor; flex: 0 0 auto; }
.project-countbadge-issues { background: #fef2f2; border-color: #fca5a5; color: #b91c1c; }
.project-countbadge-ideas { background: #f5f3ff; border-color: #c4b5fd; color: #6d28d9; }
.project-countbadge-questions { background: #f0fdfa; border-color: #5eead4; color: #0f766e; }
.project-countbadge.empty { background: #fff; border-color: #e2e8f0; color: #94a3b8; }
a.project-countbadge:hover { text-decoration: none; filter: brightness(0.97); }
a.project-countbadge.empty:hover { background: #f1f5f9; border-color: #cbd5e1; color: #64748b; }
/* Five count badges plus the watch and gear controls outgrow a phone's width:
   let the actions row wrap there instead of pushing the page sideways. */
@media (max-width: 640px) { .project-appbar-actions { flex-wrap: wrap; flex-shrink: 1; } }
.project-watch-panel { position: absolute; right: 0; top: calc(100% + 0.35rem); z-index: 30; display: flex; flex-direction: column; gap: 0.55rem; min-width: 14rem; padding: 0.75rem; background: #fff; border: 1px solid #e5e7eb; border-radius: 10px; box-shadow: 0 8px 24px rgba(15,23,42,0.12); }
.project-watch-row { display: flex; align-items: center; gap: 0.4rem; }
.project-watch-label { font-size: 0.8rem; color: var(--ballast); flex: 0 0 auto; }
/* Custom per-type checklist (PRD #90 P1): a compact boxed list under the select. */
.project-watch-custom {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin: 0;
  padding: 0.35rem 0.55rem;
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  background: #fff;
  min-width: 12.5rem;
}
.project-watch-custom-legend { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ballast); padding: 0 0 0.1rem; }
.project-watch-type { display: flex; align-items: center; gap: 0.4rem; font-size: 0.8rem; color: var(--steel); cursor: pointer; }
/* Per-type email opt-in (PRD #244 P1): the same boxed-list anatomy as the Custom
   checklist above, so the two read as sibling refinements rather than unrelated
   controls — one picks what reaches you, this one picks what is worth an inbox. */
.project-watch-email {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin: 0;
  padding: 0.35rem 0.55rem;
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  background: #fff;
  min-width: 12.5rem;
}
.project-watch-email-legend { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ballast); padding: 0 0 0.1rem; }
/* The note carries the whole point of the control — that unchecking demotes a
   category to in-app only rather than muting it — so it is part of the control,
   not decoration. */
.project-watch-email-note { margin: 0.15rem 0 0; font-size: 0.68rem; line-height: 1.35; color: var(--ballast); }
.project-watch-digest,
.project-watch-dnd { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.8rem; color: var(--ballast); cursor: pointer; }
.project-watch-select {
  height: 2.1rem;
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  background: #fff;
  color: var(--steel);
  font-size: 0.85rem;
  padding: 0 0.5rem;
  cursor: pointer;
  max-width: 12.5rem;
}
.project-watch-select:hover { background: #f1f5f9; }
.project-watch-select:focus-visible { outline: 2px solid var(--steel); outline-offset: 1px; }
/* On a phone the label drops out so the select alone keeps the appbar compact. */
@media (max-width: 560px) {
  .project-watch-label { display: none; }
}

/* Actions ▾ dropdown — a plain <details> styled like the nav account menu. */
/* Header Actions button wears the brand teal to match the mockup; the global
   .btn-primary stays --rail-blue — only this header button is retinted. */
.project-actions-summary { background: #0e7490; border-radius: 9px; }
.project-actions-summary:hover { background: #0c5f70; }
.project-actions { position: relative; }
.project-actions > summary { list-style: none; cursor: pointer; }
.project-actions > summary::-webkit-details-marker { display: none; }
.project-actions-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 0.35rem);
  min-width: 190px;
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(15,23,42,0.12);
  padding: 0.35rem;
  display: flex;
  flex-direction: column;
  z-index: 40;
}
.project-actions-menu a {
  padding: 0.5rem 0.7rem;
  border-radius: 4px;
  color: var(--steel);
  font-size: 0.85rem;
}
.project-actions-menu a:hover { background: #f8f8f8; text-decoration: none; }
.project-actions-sep { border-top: 1px solid #f1f1f1; margin: 0.3rem 0.2rem; }

/* "+ New ▾" add control (PRD #214, issue #61) — the create-a-new-artifact verbs
   split out of Actions ▾ into their own control. It reuses .project-actions (and
   so the shared dropdown mechanics + menu body); only the summary button is
   retinted, quieter than the teal Actions button since creating is the secondary
   move on a page you opened to read something. */
.project-add-summary {
  background: #fff;
  color: var(--steel);
  border: 1px solid #e5e7eb;
  border-radius: 9px;
  white-space: nowrap;
}
.project-add-summary:hover { background: #f1f5f9; }

/* A menu entry that POSTs (the PRD lifecycle actions) is a <button> in a form; it
   must read as the anchors beside it, not as a nested button. */
.project-actions-form { margin: 0; display: block; }
.project-actions-menu button.project-actions-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.5rem 0.7rem;
  border: none;
  border-radius: 4px;
  background: none;
  color: var(--steel);
  font-family: inherit;
  font-size: 0.85rem;
  line-height: inherit;
  cursor: pointer;
}
.project-actions-menu button.project-actions-item:hover { background: #f8f8f8; }

/* Two-zone shell: fixed left rail + fluid main. The page's own right sidebar
   (if any) lives inside the main column, giving the classic three-column feel
   on the PRDs page without this shell having to own it. The rail only renders
   when it has content (settings sections / PRD anchors — Docs now lives in the
   pipeline tab bar); .no-rail collapses the grid to one full-width column so
   pages like Activity start at the left edge instead of beside an empty track. */
.project-shell {
  display: grid;
  grid-template-columns: 168px minmax(0, 1fr);
  gap: 1.5rem;
  align-items: start;
  box-sizing: border-box;
  /* Full-bleed to the true viewport edges, matching the white topbar and black nav; the
     1.5rem padding re-insets the rail + main so they line up under the appbar/pipe
     (Jeremy: sections full-width like the top nav). */
  width: 100vw;
  margin-left: calc(50% - 50vw);
  padding: 0 1.5rem;
}
.project-shell.no-rail { grid-template-columns: 1fr; }
.project-shell-main { min-width: 0; }
.project-rail-left {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  position: sticky;
  top: 1rem;
}
.project-rail-link {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius);
  color: var(--steel);
  font-size: 0.9rem;
  font-weight: 500;
}
.project-rail-link:hover { background: #f1f5f9; text-decoration: none; }
.project-rail-link.active { background: rgba(37,99,235,0.10); color: var(--rail-blue); }
.project-rail-link.disabled { color: #cbd5e1; cursor: not-allowed; }
.project-rail-link.disabled:hover { background: transparent; }

/* PRD #103 crit1 — single-scroll PRD detail page. The old #29 tab bar is gone; the
   page is one scroll of anchored <section>s the "This PRD" rail + scrollspy navigate.
   Uppercase section headers mirror the mockup's .main .sec; the rail heading mirrors
   its .lrail .lh; the active anchor reuses .project-rail-link.active (the anchor links
   carry both classes) so no extra active rule is needed. */
html { scroll-behavior: smooth; }
.prd-anchor-section { scroll-margin-top: 92px; }
.prd-anchor-title {
  font-size: 0.8rem;
  color: #94a3b8;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 700;
  margin: 1.75rem 0 0.6rem;
}
.prd-anchor-section:first-of-type .prd-anchor-title { margin-top: 0.25rem; }
.prd-subsec {
  font-size: 0.9rem;
  font-weight: 600;
  color: #334155;
  margin: 1.4rem 0 0.6rem;
}
.project-rail-heading {
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #94a3b8;
  font-weight: 700;
  margin: 0.85rem 0 0.35rem;
  padding: 0 0.9rem;
}

/* On phones the rail (settings sections / PRD anchors, when present) becomes a
   horizontal strip above the pipeline nav (no burger/JS). */
@media (max-width: 768px) {
  .project-shell { grid-template-columns: 1fr; gap: 0.75rem; }
  .project-rail-left {
    flex-direction: row;
    flex-wrap: wrap;
    position: static;
    gap: 0.4rem;
    order: -1;
  }
  .project-rail-link { padding: 0.4rem 0.7rem; font-size: 0.85rem; }
}

/* Backlog head row: the page title beside the segmented control (mockup §Backlog). */
.backlog-head { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.backlog-title { font-size: 1.35rem; font-weight: 700; margin: 0; }
/* Backlog segmented control (PRD #89 P0 / #100): the intake hub's secondary nav —
   Untriaged · Ideas · Issues — rendered on the Backlog, Ideas, Issues, and Triage
   pages so the three surfaces read as segments of one hub. Mockup-exact joined
   group (.seg): bordered pill row, active segment brand-filled, each segment
   carrying its live count chip. */
.backlog-segments {
  display: inline-flex;
  border: 1px solid #e6ebf1;
  border-radius: 9px;
  overflow: hidden;
  background: #fff;
  margin-bottom: 0.5rem;
}
.backlog-seg {
  padding: 6px 11px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ballast);
  white-space: nowrap;
}
.backlog-seg:hover { color: var(--steel); text-decoration: none; }
.backlog-seg.active { background: #0e7490; color: #fff; }
.seg-n {
  display: inline-block;
  background: #e2e8f0;
  color: #475569;
  border-radius: 99px;
  padding: 0 6px;
  font-size: 11px;
  margin-left: 5px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.backlog-seg.active .seg-n { background: rgba(255, 255, 255, 0.28); color: #fff; }
/* "Source: All ▾" filter beside the segments (PRD #100) — styled to read as a
   sibling of the segment group. */
.backlog-srcfilter select {
  border: 1px solid #e6ebf1;
  border-radius: 9px;
  background: #fff;
  color: var(--steel);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 6px 9px;
  cursor: pointer;
}

/* Backlog untriaged-item cards (PRD #100 / mockup §Backlog .item): bordered rounded
   cards with a top line (checkbox · NEW · title · source badge), an excerpt, and
   a meta line whose right edge holds the compact triage verbs.

   NAMING: this card is .triage-card, NOT .inbox-item. The Project Inbox (PRD
   #133, further down this file) independently defines a bare `.inbox-item` as a
   horizontal flex ROW. Both stylesheets load on the backlog page, so while this
   card shared that name the later rule won and laid the card out sideways —
   collapsing .inbox-itop to ~204px and ellipsing every title to as little as
   17px of its 1134px content. Keep these two components' class names disjoint. */
.inbox-list { display: flex; flex-direction: column; gap: 10px; }
.triage-card { border: 1px solid #e6ebf1; border-radius: 12px; background: #fff; padding: 12px 15px; }
.triage-card:hover { box-shadow: 0 4px 14px rgba(15, 23, 42, 0.08); }
.triage-card.sel { border-color: #0e7490; box-shadow: 0 0 0 1px #0e7490; }
.inbox-itop { display: flex; align-items: center; gap: 9px; min-width: 0; }
.inbox-chk { width: 17px; height: 17px; accent-color: #0e7490; cursor: pointer; flex: 0 0 auto; margin: 0; }
.inbox-new {
  flex: none;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #64748b;
  background: #f1f5f9;
  border-radius: 5px;
  padding: 2px 6px;
}
/* The title WRAPS to two lines; it does not truncate to one.
   `white-space: nowrap` is right for a label and wrong for prose, and these are
   prose: measured across the live intake queue, titles run 41–234 chars (median
   ~163) and need 414–1696px against the ~420px the card gives them, so a single
   ellipsed line showed a median of 38% of the title and clipped 16 of 18.
   Two lines is deliberately the SAME bound as .inbox-excerpt's rather than more:
   the excerpt must never out-rank the title (see TestBacklogTitleOutranksExcerpt),
   and title 1→2 lines against excerpt ~4→2 makes the card shorter, not taller. */
.inbox-title {
  font-weight: 600;
  font-size: 14.5px;
  color: var(--steel);
  text-decoration: none;
  flex: 1;
  min-width: 0;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
a.inbox-title:hover { text-decoration: underline; }
/* Per-source badge on the card's right edge — the provenance chip (the meta line
   deliberately omits the source so it never repeats). */
.srcb { flex: none; font-size: 11px; font-weight: 700; border-radius: 6px; padding: 2px 8px; }
.srcb-ideapop { background: #fce7f3; color: #9d174d; }
.srcb-agent { background: #d1fae5; color: #065f46; }
.srcb-faultline { background: #fef3c7; color: #92400e; }
.srcb-git { background: #e2e8f0; color: #334155; }
.srcb-review { background: #ede9fe; color: #5b21b6; }
.srcb-other { background: #f1f5f9; color: #64748b; }
/* The excerpt is BOUNDED. It carries triageBodyExcerpt's 240 runes, which used to
   render unclamped at ~4 lines — more vertical room than the title it supports,
   which inverted the card's hierarchy. Clamped to the same two lines as the title
   so supporting detail can never out-rank the thing being triaged. */
.inbox-excerpt {
  margin: 6px 0 0; font-size: 0.82rem; color: var(--ballast); white-space: pre-wrap;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.inbox-meta { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #94a3b8; margin-top: 8px; min-width: 0; }
.inbox-meta .iact { margin-left: auto; display: flex; gap: 6px; }
.ibtn {
  border: 1px solid #e6ebf1;
  background: #fff;
  color: var(--steel);
  border-radius: 8px;
  padding: 5px 11px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.ibtn:hover { border-color: #0e7490; color: #0e7490; }
.ibtn-ghost { color: var(--ballast); }
.ibtn-ghost:hover { border-color: #cbd5e1; color: var(--steel); }

/* Appear-on-select bulk-action bar (PRD #100 / mockup .bulkbar): sticky brand bar
   with the live "N selected" count; hidden until a row is checked. */
.bulkbar {
  position: sticky;
  top: 8px;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 10px;
  background: #0e7490;
  color: #fff;
  border-radius: 10px;
  padding: 10px 14px;
  margin-bottom: 12px;
  box-shadow: 0 6px 20px rgba(14, 116, 144, 0.3);
}
.bulkbar[hidden] { display: none; }
.bulkbar b { font-weight: 800; }
.bulkbar .bb { margin-left: auto; display: flex; gap: 8px; }
.bulkbar button {
  background: #fff;
  color: #0e7490;
  border: 0;
  border-radius: 8px;
  padding: 7px 13px;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.bulkbar button.ghost { background: rgba(255, 255, 255, 0.16); color: #fff; }
.bulkbar button:disabled { opacity: 0.6; cursor: wait; }
/* Promote form inline in the Ideas/Issues bulk bar (PRD #88 promote + #100 bar). */
.bulk-promote-form { display: flex; align-items: center; gap: 8px; }
.bulk-promote-form select,
.bulk-promote-form input[type="text"] {
  border: 0;
  border-radius: 8px;
  padding: 6px 9px;
  font: inherit;
  font-size: 13px;
  color: var(--steel);
  background: #fff;
}
.bulk-promote-form input[type="text"] { width: 13rem; }
/* Selected-ring on a bulk-selected idea/issue card (the Inbox has its own). */
.card.bulk-sel { box-shadow: 0 0 0 2px #0e7490; }
/* Backlog INTAKE rail (PRD #100 parity) — the right column of the backlog's
   detail-grid: a BY SOURCE breakdown + a FLOW explainer, matching the mockup's
   intake sidebar. */
.intake-rail { display: flex; flex-direction: column; gap: 1rem; }
.intake-rail-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ballast); font-weight: 700; margin: 0.25rem 0 0.1rem; }
.intake-card { padding: 0.85rem 1rem; }
.intake-card h3 { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ballast); font-weight: 700; margin: 0 0 0.5rem; }
.intake-src { display: flex; align-items: center; justify-content: space-between; padding: 0.3rem 0; font-size: 0.86rem; color: var(--steel); border-top: 1px solid #f1f5f9; }
.intake-card h3 + .intake-src { border-top: none; }
.intake-src-n { font-variant-numeric: tabular-nums; font-weight: 700; }
.intake-flow { font-size: 0.82rem; color: var(--ballast); line-height: 1.55; margin: 0; }

/* Mobile overflow pass (sw-axld): at phone widths, wide data + markdown tables
   scroll inside their own box rather than forcing the page wider than the
   screen. display:block turns the <table> into a horizontal scroll container —
   an anonymous table box still aligns the columns — and cells keep wrapping, so
   a table narrower than the viewport doesn't scroll needlessly. */
@media (max-width: 640px) {
  .data-table,
  .md-rendered table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* Blueprint list */
.blueprint-list { display: flex; flex-direction: column; gap: 0; }
.blueprint-row {
  display: block;
  padding: 1rem 1.25rem;
  background: white;
  border-bottom: 1px solid #f1f1f1;
  transition: background 0.1s;
}
.blueprint-row:first-child { border-radius: var(--radius) var(--radius) 0 0; }
.blueprint-row:last-child { border-radius: 0 0 var(--radius) var(--radius); border-bottom: none; }
.blueprint-row:hover { background: #f8fafc; text-decoration: none; }
.blueprint-row-head { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.blueprint-row-title { font-weight: 500; color: var(--steel); }
.blueprint-row-meta { font-size: 0.8rem; color: var(--ballast); margin-top: 0.2rem; display: flex; gap: 0.4rem; flex-wrap: wrap; }

/* PRD #214 P1 (issue #54) — section headings on the Discussion board, which now
   carries two lanes: the project's own topics, then the per-PRD threads below.
   Rendered only when the second lane is non-empty, so a board with no PRD
   conversation looks exactly as it did before. */
.discussion-section-head { font-size: 0.95rem; font-weight: 600; color: var(--steel); margin: 0 0 0.6rem; }
.blueprint-list + .discussion-section-head, .card + .discussion-section-head { margin-top: 2rem; }

/* PRD #24 — Parked / backlog section. Self-contained so the shared .blueprint-list
   stays untouched: each parked row pairs the full-width row anchor with an inline
   Unpark button as a sibling (a <form> can't nest inside the <a>). overflow:hidden
   on the container clips the rounded corners even though rows are wrapped. */
.parked-list { border: 1px solid var(--rail); border-radius: var(--radius); overflow: hidden; }
.parked-item { display: flex; align-items: stretch; border-bottom: 1px solid #f1f1f1; }
.parked-item:last-child { border-bottom: none; }
.parked-item .blueprint-row { flex: 1 1 auto; border-bottom: none; border-radius: 0; }
.parked-unpark { display: flex; align-items: center; padding: 0 1rem; flex: 0 0 auto; }
.parked-unpark .btn { font-size: 0.72rem; padding: 0.3rem 0.7rem; white-space: nowrap; }

/* Status badges */
.status-badge { font-size: 0.72rem; padding: 0.2rem 0.6rem; border-radius: 999px; font-weight: 500; }
/* Archived chip: muted slate, distinct from the lifecycle status badge. */
.prd-archived-chip { background: #e2e8f0; color: #64748b; }
/* PRD #24 — parked/backlog chip: amber "deferred" tone, distinct from archived gray. */
.prd-parked-chip { background: #fef3c7; color: #92400e; }
/* PRD #53 — blocked chip: red "stop / waiting on a dependency" tone, distinct from
   the amber parked and slate archived chips (a PRD can be blocked in any state). */
.prd-blocked-chip { background: #fee2e2; color: #b91c1c; }
/* sw-1b0 — "awaiting acceptance" chip: derived signal that a PRD is built
   (approved/executing with every dispatched bead merged) and waiting on a human's
   completion sign-off. Signal-green "ready" tone, distinct from the amber parked
   and slate archived chips. */
.prd-awaiting-chip { background: #dcfce7; color: #166534; }
/* PRD #53 — "blocked" chip: a PRD blocked on a dependency (blocked_at non-NULL).
   Red "stalled" tone, distinct from the green awaiting and amber parked chips. */
.prd-blocked-chip { background: #fee2e2; color: #991b1b; }
/* PRD #849 — owner chip: the person answerable for a PRD, issue or idea. A
   neutral indigo "who" tone, deliberately quieter than every lifecycle chip
   above (an owner is orthogonal to state), and an explicitly muted variant for
   the unowned case so "nobody owns this" reads as a state rather than as a
   missing value. */
.owner-chip { background: #e0e7ff; color: #3730a3; }
.owner-chip-none { background: #f1f5f9; color: #64748b; font-style: italic; }
/* PRD #315 P2 — "rejected work" chip: a judge turned down at least one criterion
   and nothing has re-delivered since. Crimson-on-rose "turned down" tone, distinct
   from the blocked chip's red-on-pink (a dependency) and the green awaiting chip. */
.prd-rejected-chip { background: #ffe4e6; color: #9f1239; }
/* PRD #48 P0 — workspace exemplar chip: the "example to follow" PRD. Featured
   violet tone, distinct from the slate archived, amber parked, red blocked, and
   green awaiting chips (an exemplar can coexist with any lifecycle state). */
.prd-exemplar-chip { background: #ede9fe; color: #6d28d9; }
/* PRD #48 P0 — "example to follow" hint atop the create/draft surface: a subtle
   violet info line matching the exemplar chip, linking to the workspace exemplar. */
.prd-exemplar-hint { font-size: 0.8rem; color: #5b21b6; background: #f5f3ff; border: 1px solid #ddd6fe; border-radius: 8px; padding: 0.5rem 0.75rem; margin: 0 0 0.75rem; line-height: 1.4; }
.prd-exemplar-hint a { color: #6d28d9; font-weight: 600; }
/* sw-isol — per-PRD activity tags (PRD list rows + detail header), derived live
   from the dispatch mirror, distinct from the lifecycle status badge. */
/* "N working": agents hold a live claim right now — rail-blue with a pulsing dot
   so it reads as live. Reuses the status-new blue palette. */
.prd-working-chip { display: inline-flex; align-items: center; gap: 0.3rem; background: #dbeafe; color: #1e40af; }
.prd-working-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--rail-blue); animation: prd-working-pulse 1.5s ease-in-out infinite; }
@keyframes prd-working-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.3; transform: scale(0.6); } }
/* Respect reduced-motion: keep the dot, drop the pulse. */
@media (prefers-reduced-motion: reduce) { .prd-working-dot { animation: none; } }
/* "Executing": work started, items remain, nobody on it this moment — a neutral
   slate in-progress chip (the status-closed palette), quieter than the live blue. */
.prd-paused-chip { background: #f1f5f9; color: var(--ballast); }
.status-badge.status-sm { font-size: 0.68rem; padding: 0.1rem 0.5rem; }
.status-open { background: #dcfce7; color: #166534; }
.status-closed { background: #f1f5f9; color: var(--ballast); }
/* Answered question badge (project Q&A board): resolved reads quiet, like closed. */
.status-answered { background: #f1f5f9; color: var(--ballast); }
.status-new { background: #dbeafe; color: #1e40af; }
.status-in-discussion { background: #fef3c7; color: #92400e; }

/* PRD #82 P1 — Agent feedback kind chips: distinct tones so recommendations vs
   bugs vs friction vs praise read apart at a glance on the Agent Feedback surface.
   Combined with .status-badge for the pill shape; scoped to that surface. */
.fb-kind-recommendation { background: #fef3c7; color: #92400e; } /* amber — suggestion */
.fb-kind-praise         { background: #dcfce7; color: #166534; } /* green — positive */
.fb-kind-bug            { background: #fee2e2; color: #991b1b; } /* red — defect */
.fb-kind-friction       { background: #ffedd5; color: #9a3412; } /* orange — rough edge */

/* Question ID box — the question_id the MCP tools and API address a question
   by, boxed to the left of the question title on the Q&A board and the PRD Q&A
   page so it reads as the question's stable handle rather than free text.
   Content-sized (ids grow without bound, a fixed square would truncate), in the
   steel-on-white inversion the avatar chips use so it registers as an icon. */
.question-id-box {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.1rem 0.4rem;
  border-radius: 4px;
  background: var(--steel);
  color: var(--surface);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.4;
  flex-shrink: 0;
}

/* Blueprint thread */
.blueprint-thread { max-width: 800px; }
.message-card {
  background: white;
  border-radius: var(--radius);
  padding: 1rem 1.25rem;
  margin-bottom: 0.75rem;
  box-shadow: 0 1px 2px rgba(0,0,0,0.04);
}
.message-agent { border-left: 3px solid var(--rail-blue); }
.message-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.message-avatar {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--ballast);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.72rem;
  font-weight: 600;
  color: white;
  flex-shrink: 0;
}
.message-agent .message-avatar { background: var(--rail-blue); }
.message-author { font-weight: 600; font-size: 0.88rem; }
.message-agent-badge { font-size: 0.65rem; padding: 0.1rem 0.4rem; border-radius: 3px; background: #dbeafe; color: #1e40af; font-weight: 500; }
/* Human role badge on pitch-thread messages (PRD #99 P1): Owner in the crown's
   amber, Contributor in neutral slate — sibling of .message-agent-badge so the
   three read as one chip family. */
.message-role-badge { font-size: 0.65rem; padding: 0.1rem 0.4rem; border-radius: 3px; background: #f1f5f9; color: #475569; font-weight: 500; }
.message-role-badge.role-owner { background: #fef3c7; color: #92400e; }
.message-time { font-size: 0.78rem; color: var(--ballast); margin-left: auto; }
.message-body { font-size: 0.92rem; line-height: 1.6; white-space: pre-wrap; }

/* Inline pitch-thread blocks (PRD #99 P1): the question block mirrors the
   change-request card's anatomy with the Q&A blue accent; the event row is the
   mockup's thin "⬦ actor did thing · time" separator between messages. */
.pitch-question-block { padding: 0.75rem 1rem; margin-bottom: 0.75rem; border-left: 3px solid var(--rail-blue); }
.pitch-question-tag { font-size: 0.72rem; font-weight: 700; color: var(--rail-blue); white-space: nowrap; }
.pitch-question-answer { margin-top: 0.5rem; font-size: 0.8rem; color: #16a34a; }
.pitch-question-answer b { font-weight: 600; }
.pitch-question-answer.open { color: var(--ballast); }
.pitch-event { display: flex; align-items: center; gap: 0.6rem; margin: 0.7rem 0; }
.pitch-event-line { flex: 1; height: 1px; background: #e2e8f0; }
.pitch-event-text { font-size: 0.74rem; color: var(--ballast); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 80%; }

/* Change requests surfaced at the top of the Discussion tab (PRD #20 / sw-2zt.2) */
.prd-change-requests { max-width: 800px; margin-bottom: 1.25rem; }
.change-request-heading { font-size: 0.9rem; font-weight: 600; margin-bottom: 0.6rem; display: flex; align-items: center; gap: 0.4rem; }
.change-request-card { padding: 0.75rem 1rem; margin-bottom: 0.75rem; border-left: 3px solid #f59e0b; }
.change-request-card.addressed { border-left-color: #16a34a; }
.change-request-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.4rem; }
.change-request-reviewer { font-size: 0.85rem; font-weight: 600; }
.change-request-verb { font-size: 0.78rem; color: var(--ballast); }
.change-request-time { font-size: 0.75rem; color: var(--ballast); margin-left: auto; }
.change-request-body { font-size: 0.85rem; line-height: 1.55; }
.change-request-empty { font-size: 0.82rem; color: var(--ballast); font-style: italic; margin: 0; }
.change-request-resolved { margin: 0.5rem 0 0; }
.change-request-addressed-check { display: flex; align-items: center; gap: 0.4rem; font-size: 0.75rem; font-weight: 600; color: #16a34a; }
.change-request-addressed-check input { accent-color: #16a34a; width: 0.85rem; height: 0.85rem; margin: 0; cursor: default; }
.change-request-summary { margin-top: 0.55rem; padding: 0.55rem 0.7rem; background: rgba(22, 163, 74, 0.06); border: 1px solid rgba(22, 163, 74, 0.18); border-radius: 6px; }
.change-request-summary-head { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.35rem; }
.change-request-summary-author { font-size: 0.78rem; font-weight: 600; }
.change-request-summary-label { font-size: 0.72rem; color: var(--ballast); }
.change-request-summary-body { font-size: 0.82rem; line-height: 1.5; }

/* Message compose */
.message-compose {
  margin-top: 1rem;
  padding: 1rem;
  background: white;
  border-radius: var(--radius);
  border: 2px dashed #e5e7eb;
}
.message-compose textarea {
  width: 100%;
  padding: 0.6rem 0.75rem;
  border: 1px solid #d1d5db;
  border-radius: var(--radius);
  font-size: 0.9rem;
  font-family: var(--font-mono);
  resize: vertical;
}
.message-compose textarea:focus {
  outline: none;
  border-color: var(--rail-blue);
  box-shadow: 0 0 0 3px rgba(37,99,235,0.1);
}

/* Summon form */
.summon-form {
  margin-top: 1rem;
  padding: 1rem;
  background: #f8fafc;
  border-radius: var(--radius);
  border: 1px solid #e5e7eb;
}
.summon-form h4 { font-size: 0.85rem; font-weight: 600; margin-bottom: 0.5rem; }
.summon-form form { display: flex; gap: 0.5rem; }
.summon-form input {
  flex: 1;
  padding: 0.5rem 0.75rem;
  border: 1px solid #d1d5db;
  border-radius: var(--radius);
  font-size: 0.85rem;
}

/* PRD sections */
.prd-section {
  background: white;
  border-radius: var(--radius);
  padding: 1.25rem;
  margin-bottom: 1rem;
  box-shadow: 0 1px 2px rgba(0,0,0,0.04);
}
.prd-section h3 { font-size: 0.92rem; font-weight: 600; margin-bottom: 0.75rem; color: var(--steel); }
.prd-body { font-size: 0.9rem; line-height: 1.7; white-space: pre-wrap; color: #374151; }

/* PRD status badges */
.status-draft { background: #f3f4f6; color: var(--ballast); }
.status-review { background: #fef3c7; color: #92400e; }
.status-approved { background: #dcfce7; color: #166534; }
/* Executing = amber (mockup .b-exec): work in flight reads warm, not blue. */
.status-executing { background: #fef3c7; color: #b45309; }
.status-complete { background: #f0fdf4; color: #065f46; }
.status-ready { background: #e0e7ff; color: #3730a3; }
.status-released { background: #fef3c7; color: #92400e; }
/* Issue escalation flag (PRD #62 P4) — orange, distinct from P0's red so an
   escalated issue reads apart from raw severity. */
.status-escalated { background: #ffedd5; color: #9a3412; }

/* PRD lifecycle stat strip — project detail header (sw-qc2b).
   One icon+number chip per pipeline stage, color-matched to the lifecycle
   status badges above. Wraps onto multiple rows at narrow widths. */
.prd-stat-strip { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 1rem; }
.prd-stat {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.65rem;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1;
  background: #f3f4f6;
  color: var(--ballast);
  cursor: default;
}
.prd-stat-icon { width: 15px; height: 15px; flex-shrink: 0; }
.prd-stat-count { font-variant-numeric: tabular-nums; }
/* Zero-count chips recede so the eye lands on stages that have PRDs. */
.prd-stat.is-zero { opacity: 0.45; }
/* Color-coded to mirror the lifecycle status badges. */
.prd-stat.stat-draft { background: #f3f4f6; color: var(--ballast); }
.prd-stat.stat-approved { background: #dcfce7; color: #166534; }
.prd-stat.stat-executing { background: #dbeafe; color: #1e40af; }
.prd-stat.stat-complete { background: #f0fdf4; color: #065f46; }

/* Review decisions */
.prd-review { padding: 0.5rem 0; border-bottom: 1px solid #f1f1f1; }
.prd-review:last-child { border-bottom: none; }
.review-decision { font-size: 0.72rem; padding: 0.15rem 0.5rem; border-radius: 999px; font-weight: 500; }
.review-approve { background: #dcfce7; color: #166534; }
.review-request_changes { background: #fef3c7; color: #92400e; }
.review-comment { background: #f1f5f9; color: var(--ballast); }

/* Bead list */
.bead-list { display: flex; flex-direction: column; gap: 0; }
.bead-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  background: white;
  border-bottom: 1px solid #f1f1f1;
}
.bead-row:first-child { border-radius: var(--radius) var(--radius) 0 0; }
.bead-row:last-child { border-radius: 0 0 var(--radius) var(--radius); border-bottom: none; }
.bead-title { font-weight: 500; font-size: 0.9rem; }
.bead-meta { display: flex; gap: 0.5rem; align-items: center; font-size: 0.8rem; color: var(--ballast); }
.bead-status { font-size: 0.68rem; padding: 0.15rem 0.5rem; border-radius: 999px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.3px; }
.bead-open { background: #dcfce7; color: #166534; }
.bead-claimed { background: #fef3c7; color: #92400e; }
.bead-in_progress { background: #dbeafe; color: #1e40af; }
.bead-closed { background: #f1f5f9; color: var(--ballast); }
/* Linked bead row (PRD #56 P4): the Completion-tab work-item rows are anchors to
   the owning epic's detail page. Strip the link chrome and add a hover tint so the
   row still reads as a row, not blue underlined text. */
.bead-row-link { text-decoration: none; color: inherit; }
.bead-row-link:hover { background: #f8fafc; }

/* Bead detail page (PRD #119, HE-03) — the work-item header meta line under the
   title, then the session-handoff timeline. */
.bead-detail-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
  margin-top: 0.4rem; font-size: 0.8rem; color: var(--ballast);
}
.bead-detail-id { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.75rem; }
.bead-detail-crit {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.72rem;
  background: #f1f5f9; color: var(--steel); padding: 0.1rem 0.4rem; border-radius: 3px;
}
/* The run of the holder that took the current claim (PRD #599). Shaped like the
   crit chip beside it, since both are opaque identifiers a reader copies rather
   than prose they read. */
.bead-detail-session {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.72rem;
  background: #f1f5f9; color: var(--steel); padding: 0.1rem 0.4rem; border-radius: 3px;
}

/* Scope flags (PRD #120 / HE-04): a delivering pull request crossed the bead's
   hands_off. Amber like the exhausted attempt budget — a warning the validator
   weighs, not an error state. */
.scope-flag-count { background: #fef3c7; color: #92400e; font-size: 0.72rem; font-weight: 600; padding: 0.1rem 0.45rem; border-radius: 999px; }
.scope-flag-table { margin-top: 0.2rem; }
.scope-flag-table th, .scope-flag-table td { padding: 0.45rem 0.55rem; vertical-align: top; }
.scope-flag-row { border-left: 3px solid #f59e0b; }
.scope-flag-glob { background: #fef3c7; color: #92400e; padding: 0.1rem 0.4rem; border-radius: 3px; font-size: 0.76rem; }
.scope-flag-paths { margin: 0; padding-left: 1rem; font-size: 0.76rem; }
.scope-flag-time { white-space: nowrap; color: var(--ballast); font-size: 0.78rem; }

/* Handoff timeline: an ordered list rendered chronologically (oldest first), each
   entry a node on a vertical rail + a card. The rail node and the card's left
   border are tinted by handoff kind, so agent-authored entries (rail-blue) are
   visually separated at a glance from platform auto-synthesized ones (amber) and
   the bare "no handoff" markers (muted grey). */
.handoff-timeline { list-style: none; margin: 0; padding: 0; position: relative; }
.handoff-timeline::before {
  content: ""; position: absolute; left: 6px; top: 4px; bottom: 4px;
  width: 2px; background: #e2e8f0;
}
.handoff-entry { position: relative; padding: 0 0 1rem 1.75rem; }
.handoff-entry:last-child { padding-bottom: 0; }
.handoff-node {
  position: absolute; left: 0; top: 0.35rem; width: 14px; height: 14px;
  border-radius: 999px; background: white; border: 3px solid var(--ballast); z-index: 1;
}
.handoff-agent .handoff-node { border-color: var(--rail-blue); }
.handoff-auto .handoff-node { border-color: var(--signal-amber); }
.handoff-bare .handoff-node { border-color: #cbd5e1; }
.handoff-card {
  background: white; border: 1px solid #e2e8f0; border-left: 3px solid var(--ballast);
  border-radius: var(--radius); padding: 0.75rem 0.9rem;
}
.handoff-agent .handoff-card { border-left-color: var(--rail-blue); }
.handoff-auto .handoff-card { border-left-color: var(--signal-amber); background: #fffdf7; }
.handoff-bare .handoff-card { border-left-color: #cbd5e1; background: #fafafa; }
.handoff-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
  font-size: 0.78rem; color: var(--ballast);
}
.handoff-kind {
  font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.4px; font-weight: 600;
  padding: 0.12rem 0.45rem; border-radius: 999px; background: #f1f5f9; color: var(--ballast);
}
.handoff-kind-agent { background: #dbeafe; color: #1e40af; }
.handoff-kind-auto { background: #fef3c7; color: #92400e; }
.handoff-kind-bare { background: #f1f5f9; color: var(--ballast); }
.handoff-action { font-weight: 600; color: var(--steel); }
.handoff-holder { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.72rem; }
.handoff-time { margin-left: auto; }
.handoff-ref {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.7rem;
  background: #f1f5f9; padding: 0.08rem 0.35rem; border-radius: 3px;
}
.handoff-fields { margin-top: 0.6rem; display: flex; flex-direction: column; gap: 0.55rem; }
.handoff-field-label {
  font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.4px;
  font-weight: 600; color: var(--ballast); margin-bottom: 0.15rem;
}
.handoff-field-value { font-size: 0.85rem; color: var(--steel); white-space: pre-wrap; }
.handoff-commands {
  margin: 0; padding: 0.5rem 0.6rem; background: #0f172a; color: #e2e8f0;
  border-radius: var(--radius); font-size: 0.78rem; overflow-x: auto; white-space: pre-wrap;
}
.handoff-empty { margin-top: 0.4rem; font-size: 0.82rem; color: var(--ballast); font-style: italic; }

/* An attempt's decision log (PRD #688, HE-18). Styled as a list of small cards
   inside the handoff's field block rather than as prose: each entry is a
   decision plus up to two subordinate lines, and the whole point of the
   rejected alternative is that a reader can tell which decision it belongs to.
   The left rule is what carries that grouping at a glance. */
.handoff-decisions { margin: 0.15rem 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 0.4rem; }
.handoff-decision { border-left: 2px solid #e2e8f0; padding-left: 0.55rem; }
.handoff-decision-what { font-size: 0.85rem; color: var(--steel); white-space: pre-wrap; }
.handoff-decision-why { margin-top: 0.15rem; font-size: 0.8rem; color: var(--ballast); white-space: pre-wrap; }
.handoff-decision-tag {
  display: inline-block; margin-right: 0.3rem; font-size: 0.68rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--ballast);
}

/* Bead task-trace timeline (PRD #123, HE-07): the per-work-item process-
   observability view on the bead detail page. Shares the handoff timeline's
   spine/node/card language so the two read as a coherent pair, but tints each node
   by event kind and separates holder-posted entries (blue) from platform markers
   (grey) — the visible half of "which entries a worker authored vs the platform
   observed". */
.event-timeline { list-style: none; margin: 0; padding: 0; position: relative; }
.event-timeline::before {
  content: ""; position: absolute; left: 6px; top: 4px; bottom: 4px;
  width: 2px; background: #e2e8f0;
}
.event-entry { position: relative; padding: 0 0 1rem 1.75rem; }
.event-entry:last-child { padding-bottom: 0; }
.event-node {
  position: absolute; left: 0; top: 0.35rem; width: 14px; height: 14px;
  border-radius: 999px; background: white; border: 3px solid var(--ballast); z-index: 1;
}
.event-holder .event-node { border-color: var(--rail-blue); }
.event-platform .event-node { border-color: #cbd5e1; }
.event-blocked .event-node { border-color: var(--signal-red); }
.event-verification .event-node { border-color: var(--signal-green, #16a34a); }
.event-card {
  background: white; border: 1px solid #e2e8f0; border-left: 3px solid var(--ballast);
  border-radius: var(--radius); padding: 0.75rem 0.9rem;
}
.event-holder .event-card { border-left-color: var(--rail-blue); }
.event-platform .event-card { border-left-color: #cbd5e1; background: #fafafa; }
.event-blocked .event-card { border-left-color: var(--signal-red); background: #fff7f7; }
.event-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
  font-size: 0.78rem; color: var(--ballast);
}
.event-kind {
  font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.4px; font-weight: 600;
  padding: 0.12rem 0.45rem; border-radius: 999px; background: #f1f5f9; color: var(--ballast);
}
.event-kind-plan { background: #ede9fe; color: #5b21b6; }
.event-kind-checkpoint { background: #dbeafe; color: #1e40af; }
.event-kind-verification { background: #dcfce7; color: #166534; }
.event-kind-blocked { background: #fee2e2; color: #991b1b; }
.event-kind-claimed, .event-kind-released, .event-kind-expired, .event-kind-completed {
  background: #f1f5f9; color: var(--ballast);
}
.event-kind-coalesced { background: #fef3c7; color: #92400e; }
.event-holder-ref { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.72rem; }
.event-time { margin-left: auto; }
.event-payload {
  margin: 0.6rem 0 0; padding: 0.5rem 0.6rem; background: #0f172a; color: #e2e8f0;
  border-radius: var(--radius); font-size: 0.76rem; overflow-x: auto; white-space: pre-wrap;
}

/* Bead attempt ledger (PRD #214, intake issue 52): the HE-10 `bead_attempts`
   history rendered BESIDE the handoff timeline on the bead detail page. The split
   is a two-column grid — the ledger is a compact fixed-ish table, the handoff
   chain the wide narrative column — collapsing to stacked on a narrow viewport so
   neither column is squeezed unreadable. */
.bead-ledger-split { display: grid; grid-template-columns: minmax(340px, 34%) 1fr; gap: 1.5rem; align-items: start; }
@media (max-width: 900px) { .bead-ledger-split { grid-template-columns: 1fr; gap: 0; } }

/* The "N of M" used-of-budget figure in the Attempts heading. It goes amber once
   the non-completing count reaches the effective budget — the same threshold
   assessGuardrail blocks the bead at, so the page warns exactly when the
   mechanism is about to fire, never earlier or later. */
.attempt-budget {
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.2px;
  padding: 0.12rem 0.5rem; border-radius: 999px; background: #f1f5f9; color: var(--ballast);
}
.attempt-budget-exhausted { background: #fef3c7; color: #92400e; }
.attempt-ledger { margin-top: 0.2rem; }
.attempt-ledger th, .attempt-ledger td { padding: 0.45rem 0.55rem; vertical-align: top; }
.attempt-ordinal { font-variant-numeric: tabular-nums; color: var(--ballast); width: 1.5rem; }
.attempt-holder { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.76rem; }
.attempt-session { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.76rem; color: var(--ballast); }
.attempt-time { font-size: 0.78rem; color: var(--steel); white-space: nowrap; }
.attempt-duration { display: block; font-size: 0.68rem; color: var(--ballast); }
.attempt-open-dash { color: var(--ballast); }
/* An attempt still in flight is the row a reader most wants to spot. */
.attempt-open { background: #f8fafc; }
.attempt-cause {
  font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.4px; font-weight: 600;
  padding: 0.12rem 0.45rem; border-radius: 999px; background: #f1f5f9; color: var(--ballast);
  white-space: nowrap;
}
.attempt-cause-completed { background: #dcfce7; color: #166534; }
.attempt-cause-released { background: #fef3c7; color: #92400e; }
.attempt-cause-expired { background: #fee2e2; color: #991b1b; }
.attempt-cause-open { background: #dbeafe; color: #1e40af; }
/* The cause CLASS chip (PRD #468), shaped like the cause chip beside it. Only
   `work` is a try, so it alone takes the amber the budget figure uses. infra,
   blocked and unstarted are the ends that were NOT tries and read cooler. A
   pre-class row is `unclassified` and still counts, so it stays neutral rather
   than borrowing a "free" tint. The counts / not-counted note under the chip is
   the per-row half of the heading's "N of M". */
.attempt-class {
  font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.4px; font-weight: 600;
  padding: 0.12rem 0.45rem; border-radius: 999px; background: #f1f5f9; color: var(--ballast);
  white-space: nowrap;
}
.attempt-class-work { background: #fef3c7; color: #92400e; }
.attempt-class-infra { background: #e0e7ff; color: #3730a3; }
.attempt-class-blocked { background: #f3e8ff; color: #6b21a8; }
.attempt-class-unstarted { background: #e0f2fe; color: #075985; }
.attempt-count-note { display: block; font-size: 0.68rem; color: var(--ballast); margin-top: 0.15rem; }
.attempt-counted { color: #92400e; font-weight: 600; }
/* The completion-cleanliness chip (PRD #124 HE-08), shaped like the attempt
   cause chip. clean is the green a completed cause takes; dirty the amber of a
   self-reported problem; contradicted the red of a fact the platform checked and
   found false; unattested stays neutral — nothing was declared, so nothing is
   being asserted either way. */
.clean-state {
  font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.4px; font-weight: 600;
  padding: 0.12rem 0.45rem; border-radius: 999px; background: #f1f5f9; color: var(--ballast);
  white-space: nowrap;
}
.clean-state-clean { background: #dcfce7; color: #166534; }
.clean-state-dirty { background: #fef3c7; color: #92400e; }
.clean-state-contradicted { background: #fee2e2; color: #991b1b; }
.clean-state-unattested { background: #f1f5f9; color: var(--ballast); }
.clean-state-branch { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.78rem; }
.clean-state-ahead { text-align: right; font-variant-numeric: tabular-nums; }

/* Per-bead "last event" chip on the epic detail bead rows (PRD #123, HE-07 —
   "epic detail shows per-bead last"): a compact glance at where each work item most
   recently was, sitting in the row's meta column beside assignee/priority. */
.bead-last-event {
  font-size: 0.68rem; padding: 0.1rem 0.4rem; border-radius: 999px;
  background: #f1f5f9; color: var(--ballast); white-space: nowrap;
}
.bead-last-event-none { font-style: italic; opacity: 0.7; }

/* Priority badges */
.priority-badge { font-size: 0.68rem; padding: 0.1rem 0.4rem; border-radius: 3px; font-weight: 500; }
.priority-high { background: #fef2f2; color: var(--signal-red); }
.priority-medium { background: #fef3c7; color: #92400e; }
.priority-low { background: #f1f5f9; color: var(--ballast); }
/* The bead-priority tiers (PRD #307 P1). .priority-badge above carries the
   shape; these carry the colour. views_bead.templ, views_convoys.templ and
   views_prd.templ emit them as `"priority-" + b.Priority` over the P0–P3 bead
   vocabulary (validBeadPriority), and views_faultline*.templ hardcode
   .priority-P1 for a non-zero consecutive-failure count.

   They were missing entirely, so every badge rendered shape-only and all four
   tiers looked identical. The class name is built by string concatenation,
   which is why the gap survived: grepping the templates for `priority-P0`
   matches nothing, so neither a CSS audit nor a template audit sees it. The
   test in priority_badge_css_test.go now ties the two together.

   An ordered ramp red → amber → blue → slate. Every background is the -100
   step of its hue so tint weight stays uniform across the four, and every
   foreground clears WCAG AA 4.5:1 against it for the badge's 0.68rem text.
   That is why these do not simply reuse .priority-high/.priority-low above:
   red-600 on red-50 measures 4.41 and slate-500 on slate-100 measures 4.34,
   both under AA for text this small, so the foregrounds step one shade darker.
   The tier letter is always printed beside the colour, so colour is never the
   sole carrier of the rank.

   TINT + dark ink rather than a solid fill, because these sit inline in a dense
   row — next to the .attempt-cause-* chips, which are built the same way —
   where a saturated block would shout over the row's own text. That is the
   opposite treatment from the .sb-coin-* ramp, and deliberately so: a bead
   priority (P0 = drop-everything, P3 = whenever) is a DIFFERENT scale from the
   PRD build-queue rank the coin carries, so it gets a quieter one.

   These four rules must be declared EXACTLY ONCE. They were briefly declared
   twice — two PRs landed the same missing tiers within the hour — and the
   duplicate was worse than untidy: the cascade applied the LAST block while
   priority_badge_css_test.go's FindStringSubmatch checked the FIRST, so the
   contrast gate was verifying a rule the browser never used.
   TestPriorityBadgeRulesAreDeclaredOnce now fails on a second declaration. */
.priority-P0 { background: #fee2e2; color: #991b1b; } /* red-100   / red-800   — 6.80:1 */
.priority-P1 { background: #fef3c7; color: #92400e; } /* amber-100 / amber-800 — 6.37:1 */
.priority-P2 { background: #dbeafe; color: #1e40af; } /* blue-100  / blue-800  — 7.15:1 */
.priority-P3 { background: #f1f5f9; color: #475569; } /* slate-100 / slate-600 — 6.92:1 */

/* Context Pack panel (PRD #46 P0): the per-work-item assembled-context accordion,
   rendered as a sibling beneath each work-item row in the Completion tab. A subtle
   tinted strip so it reads as supporting detail attached to the row above it, not a
   peer row. Reuses the .phase-accordion summary/chevron behavior. */
.context-pack { background: #f8fafc; border-bottom: 1px solid #f1f1f1; padding: 0.4rem 1rem; }
.context-pack:last-child { border-radius: 0 0 var(--radius) var(--radius); border-bottom: none; }
.context-pack-summary { display: flex; align-items: center; gap: 0.5rem; font-size: 0.8rem; }
.context-pack-tag {
  font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.4px; font-weight: 600;
  padding: 0.12rem 0.45rem; border-radius: 999px; background: #e0e7ff; color: #3730a3;
}
.context-pack-sub { color: var(--ballast); font-size: 0.78rem; }
.context-pack-body { display: flex; flex-direction: column; gap: 0.45rem; padding-top: 0.4rem; }
.context-pack-foot { font-size: 0.72rem; color: var(--ballast); margin: 0.2rem 0 0; }
/* PRD #46 P1: assembly measurement + recorded usefulness feedback under a pack. */
.context-pack-metrics { font-size: 0.72rem; color: var(--ballast); margin: 0.3rem 0 0; font-variant-numeric: tabular-nums; }
.context-pack-signals { font-size: 0.72rem; color: #475569; margin: 0.15rem 0 0; font-weight: 500; }
.context-item { display: flex; gap: 0.55rem; align-items: flex-start; }
.context-item-main { min-width: 0; }
.context-item-head { display: flex; gap: 0.45rem; align-items: baseline; flex-wrap: wrap; }
.context-item-title { font-weight: 500; font-size: 0.84rem; }
.context-item-ref { font-family: var(--mono, monospace); font-size: 0.72rem; color: var(--ballast); }
.context-item-status { font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.3px; color: #166534; }
.context-item-snippet { margin: 0.1rem 0 0; font-size: 0.78rem; color: #475569; line-height: 1.35; }
/* Kind badges — one tint per context item kind, mirroring the brain entity types. */
.context-kind {
  flex: none; font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.3px; font-weight: 600;
  padding: 0.12rem 0.4rem; border-radius: 3px; min-width: 4.5rem; text-align: center;
}
.context-kind-requirement { background: #dbeafe; color: #1e40af; }
.context-kind-pattern { background: #ede9fe; color: #5b21b6; }
.context-kind-security { background: #fee2e2; color: #991b1b; }
.context-kind-incident { background: #ffedd5; color: #9a3412; }
.context-kind-code_ref { background: #f1f5f9; color: var(--ballast); }

/* Claim-pool view (PRD #13 sw-mcv.8): status-count cards + claimed-bead table.
   The card accent bars reuse the bead-status palette so they read as the same
   lifecycle as the badges in the table below. */
.pool-counts { display: flex; gap: 1rem; flex-wrap: wrap; }
.pool-count {
  flex: 1 1 120px;
  background: #fff;
  border: 1px solid #e5e7eb;
  border-top: 3px solid var(--ballast);
  border-radius: 6px;
  padding: 0.9rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.pool-count-n { font-size: 1.7rem; font-weight: 700; color: var(--steel); line-height: 1; }
.pool-count-label { font-size: 0.72rem; color: var(--ballast); text-transform: uppercase; letter-spacing: 0.5px; }
/* Sub-line on a count card — the Open card's "N held cross-lane" annotation
   (a subset of Open worked via criterion-lane claims, not a fifth bucket). */
.pool-count-sub { font-size: 0.72rem; color: var(--signal-amber); }
.pool-count-open { border-top-color: #34d399; }
.pool-count-claimed { border-top-color: var(--signal-amber); }
.pool-count-in_progress { border-top-color: #3b82f6; }
.pool-count-closed { border-top-color: #94a3b8; }

.pool-bead-title { font-weight: 500; font-size: 0.9rem; color: var(--steel); }
.pool-bead-id { font-family: var(--font-mono); font-size: 0.74rem; color: var(--ballast); margin-top: 0.1rem; }
.pool-holder { font-family: var(--font-mono); font-size: 0.82rem; color: var(--steel); }
.pool-priority { font-family: var(--font-mono); font-size: 0.82rem; color: var(--steel); white-space: nowrap; }
.pool-lease { font-family: var(--font-mono); font-size: 0.82rem; color: var(--steel); white-space: nowrap; }
.pool-lease-expired { color: var(--signal-red); font-weight: 600; }

/* Cross-lane criterion claims (PRD #81 P2): the "Criteria in progress" section
   surfaces a criterion held in EITHER execution lane, so rig-lane work — which never
   touches beads_mirror — is finally visible on the pool page. The lane badge tints by
   lane (rig=steel-blue, pool=amber, matching the claimed count card) so a scan tells
   the two lanes apart; the PRD column names the plan the criterion belongs to. */
.pool-crit-prd { font-size: 0.82rem; color: var(--steel); }
.lane-badge {
  font-size: 0.68rem;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  white-space: nowrap;
}
.lane-rig { background: #dbeafe; color: #1e40af; }
.lane-pool { background: #fef3c7; color: #92400e; }

/* .claim-badge was the per-criterion claim pill on the PRD page's work-item rows
   (PRD #81 P2) — a compact lane-tinted pill showing lane · holder · lease. PRD #309
   P0 retired those rows, and the per-criterion page states the same three facts as
   prose in its claim line rather than as a pill, so nothing emits the class any more
   and the rule is dropped rather than left styling markup that no longer renders.
   .lane-rig/.lane-pool above STAY: the pool page's .lane-badge still uses them. */

/* Last-progress age + stall styling (PRD #33 P2 sw-jfew.1/.2). A held claim that
   keeps reporting reads in the muted body color; once IsStalled flags it (no
   progress past ~2x the report cadence) the row tints amber and a badge calls it
   out, so a human scanning the pool spots stuck work and can Release it. */
.pool-progress { font-family: var(--font-mono); font-size: 0.82rem; color: var(--ballast); white-space: nowrap; }
.pool-progress.pool-stalled { color: var(--signal-amber); font-weight: 600; }
.pool-stall-badge {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.05rem 0.4rem;
  border-radius: 4px;
  /* Default badge = "no heartbeat": the lease lapsed and the worker stopped
     renewing (a dead, reclaim-pending claim). Red reads as the more severe case. */
  background: var(--signal-red);
  color: #fff;
  font-family: var(--font-sans);
  font-size: 0.66rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0.2px;
  vertical-align: middle;
}
/* "renewing but silent": the holder is alive (still renewing its lease) but has
   posted no checkpoint/verification_run in N renewals (PRD #123 HE-07). Amber
   reads as a warning, distinct from the red no-heartbeat case. */
.pool-stall-badge.pool-stall-silent { background: var(--signal-amber); }
.pool-row-stalled { background: #fffbeb; }

/* A held work item's OWN live pool-claim liveness (PRD #145 P0): holder · lease ·
   last-progress, in the muted monospace the pool table's progress column uses, so an
   actively-worked item reads distinct from an idle one. It was first rendered on the
   PRD page's work-item rows; PRD #309 P0 retired those and the signal moved to the
   per-criterion page, keeping these class names so the styling followed it.
   Once the claim goes quiet (StallState != none) the pill tints amber-bold and a
   small flag names the stall shape — the same red/amber severity split the pool
   page's .pool-stall-badge uses, so the two surfaces classify a stall alike. */
.work-claim {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--ballast);
  white-space: nowrap;
}
.work-claim.work-claim-stalled { color: var(--signal-amber); font-weight: 600; }
.work-claim-flag {
  display: inline-block;
  margin-left: 0.35rem;
  padding: 0.05rem 0.4rem;
  border-radius: 4px;
  background: var(--signal-red);
  color: #fff;
  font-family: var(--font-sans);
  font-size: 0.62rem;
  font-weight: 600;
  vertical-align: middle;
}

/* Release control: a low-emphasis outline button per claimed row that POSTs the
   bead back to the open pool, reddening on hover to signal it interrupts a claim. */
.pool-release-btn {
  font-family: var(--font-sans);
  font-size: 0.76rem;
  font-weight: 500;
  color: var(--ballast);
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  padding: 0.25rem 0.6rem;
  cursor: pointer;
  white-space: nowrap;
}
.pool-release-btn:hover { color: var(--signal-red); border-color: var(--signal-red); }

/* Convoy cards */
.convoy-card {
  background: white;
  border-radius: var(--radius);
  padding: 1rem;
  margin-bottom: 0.75rem;
  box-shadow: 0 1px 2px rgba(0,0,0,0.04);
}
.convoy-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem; }
.convoy-title { font-weight: 500; font-size: 0.88rem; }
.convoy-progress { display: flex; align-items: center; gap: 0.75rem; }
.convoy-bar { flex: 1; height: 6px; background: #e5e7eb; border-radius: 3px; overflow: hidden; }
.convoy-bar-fill { height: 100%; background: var(--signal-green); border-radius: 3px; transition: width 0.3s; }
.convoy-count { font-size: 0.78rem; color: var(--ballast); font-family: var(--font-mono); white-space: nowrap; }

/* Running/failed convoy status */
.status-queued { background: #f3f4f6; color: var(--ballast); }
.status-running { background: #dbeafe; color: #1e40af; }
.status-done { background: #dcfce7; color: #166534; }
.status-failed { background: #fef2f2; color: var(--signal-red); }

/* ============================================================
   Rendered markdown
   Applied to any .md-rendered container: blueprint messages, PRD
   sections, and the preview pane of .md-editor. The reset of
   white-space is critical — the legacy .message-body / .prd-body
   rules use pre-wrap to keep raw newlines visible, which conflicts
   with HTML paragraphs once goldmark has rendered them.
   ============================================================ */
/* overflow-wrap:anywhere breaks long unbreakable strings — URLs, tokens, long
   inline `code` — so PRD/blueprint prose never forces horizontal page overflow
   on a phone. Only engages when a word would otherwise overflow, so desktop
   prose is unchanged. */
.md-rendered { white-space: normal; overflow-wrap: anywhere; }
.md-rendered > *:first-child { margin-top: 0; }
.md-rendered > *:last-child { margin-bottom: 0; }
.md-rendered p { margin: 0 0 0.7em; }
.md-rendered h1, .md-rendered h2, .md-rendered h3,
.md-rendered h4, .md-rendered h5, .md-rendered h6 {
  color: var(--steel);
  font-weight: 600;
  line-height: 1.3;
  margin: 1.2em 0 0.5em;
}
.md-rendered h1 { font-size: 1.25rem; }
.md-rendered h2 { font-size: 1.1rem; }
.md-rendered h3 { font-size: 1rem; }
.md-rendered h4 { font-size: 0.92rem; }
.md-rendered h5, .md-rendered h6 { font-size: 0.85rem; color: var(--ballast); }
.md-rendered ul, .md-rendered ol { margin: 0 0 0.7em; padding-left: 1.4em; }
.md-rendered li { margin: 0.15em 0; }
.md-rendered li > p { margin: 0.2em 0; }
.md-rendered a { color: #1e40af; text-decoration: underline; text-underline-offset: 2px; }
.md-rendered a:hover { color: var(--steel); }
.md-rendered code {
  font-family: var(--font-mono);
  font-size: 0.86em;
  background: #f1f5f9;
  color: #334155;
  padding: 0.1em 0.35em;
  border-radius: 3px;
}
.md-rendered pre {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  overflow-x: auto;
  margin: 0 0 0.7em;
}
.md-rendered pre code {
  background: transparent;
  padding: 0;
  font-size: inherit;
  color: inherit;
}
.md-rendered blockquote {
  margin: 0.6em 0;
  padding: 0.1em 0 0.1em 0.9em;
  border-left: 3px solid #e2e8f0;
  color: var(--ballast);
}
.md-rendered table {
  border-collapse: collapse;
  margin: 0 0 0.7em;
  font-size: 0.88rem;
}
.md-rendered th, .md-rendered td {
  border: 1px solid #e2e8f0;
  padding: 0.4em 0.7em;
  text-align: left;
}
.md-rendered th { background: #f8fafc; font-weight: 600; }
.md-rendered hr { border: 0; border-top: 1px solid #e2e8f0; margin: 1.2em 0; }
.md-rendered img { max-width: 100%; border-radius: var(--radius); }
.md-rendered input[type="checkbox"] { margin-right: 0.4em; }
.md-empty { color: var(--ballast); font-style: italic; font-size: 0.88rem; margin: 0; }

/* ============================================================
   In-browser markdown editor (Edit / Preview tabs)
   Toggle is driven by data-md-mode on .md-editor — CSS attribute
   selectors swap pane visibility without any inline style writes.
   ============================================================ */
.md-editor {
  border: 1px solid #e2e8f0;
  border-radius: var(--radius);
  background: white;
  overflow: hidden;
}
.md-tabs {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.35rem 0.5rem;
  background: #f8fafc;
  border-bottom: 1px solid #e2e8f0;
}
.md-tab {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 3px;
  padding: 0.3rem 0.75rem;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--ballast);
  cursor: pointer;
  font-family: inherit;
}
.md-tab:hover { color: var(--steel); }
.md-tab-active {
  background: white;
  border-color: #e2e8f0;
  color: var(--steel);
  box-shadow: 0 1px 1px rgba(0,0,0,0.03);
}
.md-hint {
  margin-left: auto;
  font-size: 0.72rem;
  color: var(--ballast);
  font-family: var(--font-mono);
}
.md-edit-pane textarea {
  display: block;
  width: 100%;
  border: 0;
  outline: none;
  padding: 0.75rem;
  font-family: var(--font-mono);
  font-size: 0.88rem;
  line-height: 1.55;
  resize: vertical;
  background: transparent;
  color: var(--steel);
  box-sizing: border-box;
}
.md-preview {
  padding: 0.75rem;
  min-height: 4rem;
  font-size: 0.92rem;
  line-height: 1.6;
}
.md-editor[data-md-mode="edit"] .md-preview { display: none; }
.md-editor[data-md-mode="preview"] .md-edit-pane { display: none; }
.md-editor[data-md-mode="preview"] .md-preview { display: block; }

/* ------------------------------------------------------------
   EasyMDE upgrade (applied via .md-easymde-ready, added by the
   layout init script once the editor mounts). When EasyMDE owns
   the field we hide our legacy tab strip + server-preview pane —
   EasyMDE brings its own toolbar + live preview — and drop the
   wrapper chrome so EasyMDE's own border/toolbar frame the field
   instead of double-bordering it. Without .md-easymde-ready (CDN
   failed) the plain textarea + tabs above stay fully functional.
   The rules below are light theme nudges so EasyMDE reads as part
   of the rail-yard palette; we lean on its shipped CSS otherwise.
   ------------------------------------------------------------ */
.md-easymde-ready { border: 0; border-radius: 0; overflow: visible; background: transparent; }
.md-easymde-ready .md-tabs,
.md-easymde-ready > .md-edit-pane > textarea.md-textarea,
.md-easymde-ready > .md-preview { display: none; }

.md-editor .EasyMDEContainer { font-family: var(--font-sans); }
.md-editor .EasyMDEContainer .CodeMirror {
  border: 1px solid #e2e8f0;
  border-top: 0;
  border-radius: 0 0 var(--radius) var(--radius);
  color: var(--steel);
  font-family: var(--font-mono);
  font-size: 0.88rem;
  line-height: 1.55;
}
.md-editor .EasyMDEContainer .editor-toolbar {
  border: 1px solid #e2e8f0;
  border-radius: var(--radius) var(--radius) 0 0;
  background: #f8fafc;
  opacity: 1;
}
.md-editor .EasyMDEContainer .editor-toolbar button {
  color: var(--ballast);
  border-radius: 3px;
}
.md-editor .EasyMDEContainer .editor-toolbar button:hover,
.md-editor .EasyMDEContainer .editor-toolbar button.active {
  background: #fff;
  border-color: #e2e8f0;
  color: var(--steel);
}
.md-editor .EasyMDEContainer .editor-toolbar i.separator { border-color: #e2e8f0; }
.md-editor .EasyMDEContainer .CodeMirror-cursor { border-left-color: var(--steel); }
.md-editor .EasyMDEContainer .CodeMirror-selected { background: rgba(37, 99, 235, 0.12); }
/* Live side-by-side / preview pane reuses the shared markdown look. */
.md-editor .EasyMDEContainer .editor-preview,
.md-editor .EasyMDEContainer .editor-preview-side {
  background: #fff;
  font-family: var(--font-sans);
  font-size: 0.92rem;
  line-height: 1.6;
}
.md-editor .EasyMDEContainer .editor-preview-side { border-color: #e2e8f0; }
.md-editor .EasyMDEContainer .cm-link,
.md-editor .EasyMDEContainer .cm-url { color: var(--rail-blue); }
.md-editor .EasyMDEContainer .editor-statusbar { color: var(--ballast); }

/* ============================================================
   Daily reports — Recent activity accordion on project page
   ============================================================ */
.recent-activity .report-list {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.report-row {
  border: 1px solid #e2e8f0;
  border-radius: var(--radius);
  background: white;
  overflow: hidden;
}
.report-row > summary {
  list-style: none;
  cursor: pointer;
  padding: 0.6rem 0.85rem;
  display: flex;
  align-items: center;
  gap: 0.65rem;
  font-size: 0.85rem;
  user-select: none;
}
.report-row > summary::-webkit-details-marker { display: none; }
/* No LEADING marker (PRD #214, issue #62): the calendar tile is the row's list
   marker, so a bullet beside it only says the same thing twice. The disclosure
   affordance moves to a trailing chevron — the .phase-accordion idiom, where the
   row's content leads and the open/closed indicator sits at the far edge — so the
   row still reads as expandable without a second marker beside the date. */
.report-row > summary::after {
  content: "▸";
  color: var(--ballast);
  font-size: 0.7rem;
  margin-left: auto;
  flex-shrink: 0;
  transition: transform 0.15s ease;
}
.report-row[open] > summary::after { transform: rotate(90deg); }
.report-row > summary:hover { background: #f8fafc; }
/* Compact desk-calendar tile for a daily report row (PRD #93 P1) — an
   abbreviated month strip over the day number, replacing the spelled-out date. */
.cal-tile {
  display: inline-flex;
  flex-direction: column;
  align-items: stretch;
  width: 2.1rem;
  min-width: 2.1rem;
  border: 1px solid #e2e8f0;
  border-radius: 5px;
  background: white;
  overflow: hidden;
  flex-shrink: 0;
  line-height: 1;
}
.cal-tile-month {
  background: var(--rail-blue);
  color: white;
  font-size: 0.5rem;
  font-weight: 700;
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.12rem 0;
}
.cal-tile-day {
  font-family: var(--font-mono);
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--steel);
  text-align: center;
  padding: 0.12rem 0 0.16rem;
}

.report-rig {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  background: #dbeafe;
  color: #1e40af;
  padding: 0.1rem 0.45rem;
  border-radius: 3px;
  flex-shrink: 0;
}
.report-excerpt {
  color: var(--ballast);
  font-size: 0.82rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0; /* allow the flex child to shrink so ellipsis kicks in instead of overflowing the row on narrow viewports */
}
.report-body {
  padding: 0 1rem 1rem;
  border-top: 1px solid #f1f5f9;
  padding-top: 0.85rem;
  font-size: 0.9rem;
  line-height: 1.6;
}

/* ============================================================
   Toasts — fixed top-right, slide-in, auto-dismiss
   Append .toast children to #toast-host. Variants:
     .toast-success (green), .toast-error (red), .toast-info (blue).
   ============================================================ */
.toast-host {
  position: fixed;
  bottom: 1.25rem;       /* PRD #95 P2: bottom-right, matching the design mockup */
  right: 1.25rem;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  pointer-events: none;  /* children re-enable */
  max-width: min(92vw, 23rem);
}
.toast {
  pointer-events: auto;
  background: white;
  border-radius: var(--radius);
  padding: 0.7rem 0.9rem;
  font-size: 0.85rem;
  line-height: 1.4;
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12), 0 2px 6px rgba(15, 23, 42, 0.06);
  border: 1px solid #e2e8f0;
  border-left-width: 4px;
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  animation: toast-in 220ms ease-out both, toast-out 250ms ease-in 3.6s both;
  cursor: pointer;
}
.toast-success { border-left-color: var(--signal-green); }
.toast-error   { border-left-color: var(--signal-red); }
.toast-info    { border-left-color: #1e40af; }
.toast-icon {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.85rem;
  flex-shrink: 0;
  width: 1.1rem;
  text-align: center;
}
.toast-success .toast-icon { color: var(--signal-green); }
.toast-error   .toast-icon { color: var(--signal-red); }
.toast-info    .toast-icon { color: #1e40af; }
.toast-message { flex: 1; color: var(--steel); }
@keyframes toast-in {
  from { opacity: 0; transform: translateX(0.5rem); }
  to   { opacity: 1; transform: translateX(0); }
}
@keyframes toast-out {
  from { opacity: 1; transform: translateX(0); }
  to   { opacity: 0; transform: translateX(0.5rem); }
}
/* Live toasts (PRD #95 P1, restyled to the P2 design mockup): the SSE-pushed,
   relevance-filtered activity toast. Reuses .toast wholesale — slide-in, vertical
   stacking in #toast-host, and the timed fade-out — and layers the mockup's card:
   a rounded icon chip, a two-line body (bold title + muted detail), and a dismiss
   ×. The rail-blue accent + anchor reset keep a linked toast from reading as a raw
   underlined link. Centered so the chip aligns to the two-line text block. */
.toast-live {
  border-left-color: var(--rail-blue);
  align-items: center;
  gap: 0.7rem;
}
a.toast-live { text-decoration: none; color: inherit; }
/* Icon chip: the rounded square from the mockup. The app's event icon set is
   emoji + line-art (reused verbatim from the timeline via eventIcon — the P1
   decision not to fork a second icon language), so the chip is a subtle rail-blue
   tint the glyph sits on, rather than the mockup's saturated per-type fill with a
   white monochrome glyph (which the emoji set can't supply). */
.toast-icon-live {
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 8px;
  background: #eef4ff;      /* subtle rail-blue tint the glyph sits on */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--rail-blue);
  font-size: 1rem;
  flex: 0 0 auto;
}
.toast-icon-live .act-icon { width: 1.05rem; height: 1.05rem; }
/* Two-line body: title (bold ink) over an optional muted detail, matching the
   mockup's .tt / .td. The app's --sleeper / --ballast are the mockup's --ink /
   --muted exactly, so this is both palette-consistent and mockup-faithful.
   min-width:0 lets the detail ellipsis inside the flex row. */
.toast-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.toast-title { font-weight: 700; font-size: 0.82rem; color: var(--sleeper); line-height: 1.3; }
.toast-detail {
  font-size: 0.76rem;
  color: var(--ballast);
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Dismiss ×: the mockup's .tx. Muted, self-aligned to the top so it reads as a
   close affordance; the client wires the click to remove the toast (and, for a
   linked toast, to not navigate). */
.toast-close {
  flex: 0 0 auto;
  align-self: flex-start;
  color: #cbd5e1;
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.1rem;
}
.toast-close:hover { color: var(--steel); }

/* ============================================================
   Docs tab — sidebar of .md files + content pane
   ============================================================ */
.docs-layout {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  gap: 1.5rem;
  align-items: start;
}
/* On phones the file list stacks above the content pane (full width) and
   drops its sticky positioning, so it scrolls away with the page instead of
   pinning a short list to the top of a long document. */
@media (max-width: 820px) {
  .docs-layout { grid-template-columns: 1fr; }
  .docs-sidebar { position: static; }
}
.docs-sidebar {
  background: white;
  border: 1px solid #e2e8f0;
  border-radius: var(--radius);
  overflow: hidden;
  position: sticky;
  top: 1rem;
}
.docs-sidebar-header {
  padding: 0.6rem 0.85rem;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ballast);
  background: #f8fafc;
  border-bottom: 1px solid #e2e8f0;
}
.docs-file-list { list-style: none; margin: 0; padding: 0.3rem 0; }
.docs-file-list li { margin: 0; }
.docs-file {
  display: block;
  padding: 0.4rem 0.85rem;
  font-size: 0.85rem;
  color: var(--steel);
  border-left: 3px solid transparent;
  text-decoration: none;
}
.docs-file:hover { background: #f8fafc; }
.docs-file.active {
  background: #eff6ff;
  border-left-color: var(--rail-blue, #1e40af);
  color: var(--steel);
  font-weight: 500;
}
.docs-content {
  background: white;
  border: 1px solid #e2e8f0;
  border-radius: var(--radius);
  padding: 1.5rem 1.75rem;
  min-width: 0;  /* lets pre/table children scroll inside the grid cell */
}
.docs-content-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid #e2e8f0;
}
.docs-content-header h2 {
  margin: 0;
  font-size: 1.05rem;
  font-family: var(--font-mono);
}

/* Collapsible PAT-setup help on the project settings form. Tight inline
   list, indented so it visually belongs to the github_token field. */
.pat-help {
  margin-top: 0.65rem;
  font-size: 0.82rem;
}
.pat-help summary {
  cursor: pointer;
  color: var(--ballast);
  user-select: none;
  padding: 0.2rem 0;
}
.pat-help summary:hover { color: var(--steel); }
.pat-help[open] summary { color: var(--steel); margin-bottom: 0.35rem; }
.pat-help-steps {
  margin: 0.25rem 0 0 1.2rem;
  padding: 0;
  line-height: 1.55;
  color: var(--steel);
  overflow-wrap: break-word; /* break long PAT URLs/tokens instead of overflowing on mobile */
}
.pat-help-steps li { margin: 0.3rem 0; }
.pat-help-steps code {
  font-family: var(--font-mono);
  font-size: 0.86em;
  background: #f1f5f9;
  padding: 0.05em 0.3em;
  border-radius: 3px;
}
.pat-help a { color: #1e40af; text-decoration: underline; }

/* --- Members dashboard (cross-workspace landing) --- */
.dash-stats { display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 1.5rem; }
.dash-stat {
  background: white;
  border-radius: var(--radius);
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
  padding: 1rem 1.25rem;
  min-width: 140px;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.dash-stat-num { font-size: 1.75rem; font-weight: 700; line-height: 1.1; color: var(--steel); }
.dash-stat-label { font-size: 0.8rem; color: var(--ballast); }

.dash-section { margin-bottom: 1.75rem; }

.dash-status-row { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.dash-status-cell {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: white;
  border-radius: var(--radius);
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
  padding: 0.5rem 0.85rem;
}
.dash-status-num { font-size: 1.1rem; font-weight: 700; color: var(--steel); }

/* Attention + activity sit side by side on desktop, stack on narrow screens. */
.dash-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.75rem; }
@media (max-width: 860px) { .dash-grid { grid-template-columns: 1fr; } }
.dash-col { min-width: 0; }

.dash-list { padding: 0.5rem 0; }
.dash-empty { padding: 1.5rem; text-align: center; color: var(--ballast); font-size: 0.9rem; }

/* Each row is a full-width link; hover tints the row, not the text. */
.dash-attn, .dash-act {
  display: block;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid #f1f1f1;
  color: inherit;
}
.dash-attn:last-child, .dash-act:last-child { border-bottom: none; }
.dash-attn:hover, .dash-act:hover { background: #f8fafc; text-decoration: none; }

.dash-attn-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem; }
.dash-attn-title { font-size: 0.9rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-attn-head .status-badge { margin-left: auto; flex-shrink: 0; }
.dash-attn-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; font-size: 0.78rem; }
.dash-attn-project { color: var(--ballast); }
.dash-attn-reason { font-size: 0.72rem; padding: 0.1rem 0.5rem; border-radius: 999px; font-weight: 500; }
.dash-attn-approve { background: #fef3c7; color: #92400e; }
.dash-attn-changes { background: #fee2e2; color: #991b1b; }
.dash-attn-questions { background: #dbeafe; color: #1e40af; }
/* "Merge request ready to merge" reason chip (PRD #89 P2, crit c35a8227ad2d). */
.dash-attn-merge { background: #dcfce7; color: #166534; }

/* PRD #89 P2 — clickable dashboard stats (crit fd101a048d2c). The stat tiles +
   by-status cells become links that drill into the workspace PRD list; keep the
   card look, drop the anchor's default color/underline, add a subtle hover lift. */
a.dash-stat, a.dash-status-cell { color: inherit; text-decoration: none; transition: box-shadow 0.12s ease, transform 0.12s ease; }
a.dash-stat:hover, a.dash-status-cell:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.10); transform: translateY(-1px); text-decoration: none; }

/* PRD #89 P2 — PRD list sort/filter toolbar (crit fd101a048d2c) + the workspace
   PRD list status chips. Plain-link toggles; the active option is filled. */
.prd-list-controls { display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; align-items: center; margin: 0 0 0.85rem; }
.prd-ctl-group { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; }
.prd-ctl-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ballast); margin-right: 0.15rem; }
.prd-ctl-opt {
  font-size: 0.78rem; padding: 0.2rem 0.6rem; border-radius: 999px;
  color: var(--steel); background: #f1f5f9; text-decoration: none; white-space: nowrap;
}
.prd-ctl-opt:hover { background: #e2e8f0; text-decoration: none; }
.prd-ctl-opt.active { background: var(--rail-blue); color: #fff; }
.prd-filter-hint { margin: 0 0 0.6rem; font-size: 0.82rem; color: var(--ballast); }

/* The .rail-watch* rules for PRD #89 P2's rail watch widget (crit b70ce97163a0)
   were removed with the widget in PRD #214 (issue #63). The remaining watcher
   eye + count is the appbar control, styled by the .project-watch* rules above. */

/* PRD #89 P2 — roadmap horizon groups (crit 96742d880a29). Each horizon is a
   labeled band; the muted hint sits beside the label. Now gets an accent rule. */
.roadmap-horizon { margin-bottom: 1.5rem; }
.roadmap-horizon-head { display: flex; align-items: baseline; gap: 0.6rem; margin: 0 0 0.6rem; padding-bottom: 0.3rem; border-bottom: 2px solid #e2e8f0; }
.roadmap-horizon-title { font-size: 1.05rem; margin: 0; }
.roadmap-horizon-hint { font-size: 0.75rem; color: var(--ballast); text-transform: uppercase; letter-spacing: 0.04em; }
.roadmap-horizon-now .roadmap-horizon-head { border-bottom-color: var(--rail-blue); }

/* Roadmap two-zone layout (blueprint #97 round 6): the horizon timeline + a live
   right rail (roadmap progress + milestone counts), mirroring the project page's
   3-zone shell. Stacks the rail below the body on a narrow viewport. */
.roadmap-layout { display: flex; align-items: flex-start; gap: 1.5rem; }
.roadmap-layout .roadmap-body { flex: 1; min-width: 0; }
.roadmap-rail { width: 300px; flex: 0 0 300px; display: flex; flex-direction: column; gap: 1rem; }
.roadmap-rail-card { padding: 1rem 1.15rem; }
.roadmap-rail .rail-card-title { display: block; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ballast); margin: 0 0 0.35rem; }
.roadmap-rail-counts { list-style: none; margin: 0; padding: 0; }
.roadmap-rail-counts li { display: flex; align-items: center; justify-content: space-between; padding: 0.35rem 0; border-top: 1px solid #f1f5f9; }
.roadmap-rail-counts li:first-child { border-top: 0; }
.roadmap-rail-count-label { font-size: 0.85rem; color: var(--steel); }
.roadmap-rail-count-num { font-size: 0.85rem; font-weight: 700; color: var(--steel); font-family: var(--font-mono); }
/* Steward note (blueprint #97 round 6 / mockup §Roadmap right rail, issue #7): the
   muted line explaining who keeps the overview current — the rail's third element. */
.roadmap-rail-steward { margin: 0; font-size: 0.85rem; line-height: 1.5; color: var(--ballast); }
/* Mockup §Roadmap toolbar (PRD #110 parity): the page title beside the "View"
   segmented control — the same head-row + joined-pill grammar as the Backlog
   segments. The Timeline segment ships disabled until a timeline view exists. */
.roadmap-head { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.9rem; }
.roadmap-title { font-size: 1.35rem; font-weight: 700; margin: 0; }
.roadmap-seg {
  display: inline-flex;
  align-items: center;
  border: 1px solid #e6ebf1;
  border-radius: 9px;
  overflow: hidden;
  background: #fff;
}
.roadmap-seg-label { padding: 6px 8px; font-size: 13px; font-weight: 700; color: #94a3b8; }
.roadmap-seg-btn {
  border: 0;
  background: #fff;
  color: var(--ballast);
  padding: 6px 11px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.roadmap-seg-btn.on { background: #0e7490; color: #fff; }
.roadmap-seg-btn:disabled,
.roadmap-seg-btn[aria-disabled="true"] { color: #cbd5e1; cursor: not-allowed; }

/* De-busied Move controls (PRD #110 parity): the inline placement form made
   every roadmap row visibly busier than the mockup's calm chips, so on desktop
   it becomes a hover/focus-revealed overlay pinned to the row's right edge —
   the same reveal grammar as the PRD-page phase-row edit buttons. It stays in
   the DOM and the tab order (opacity, not display), so tabbing into the
   milestone <select> reveals it for keyboard users; pointer-events gates stray
   clicks while hidden. */
.roadmap-prd-row { position: relative; }
.roadmap-prd-row .roadmap-place-form {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  flex-wrap: wrap;
  position: absolute;
  right: 0.5rem;
  top: 50%;
  transform: translateY(-50%);
  margin: 0;
  padding: 0.3rem 0.55rem;
  background: #fff;
  border: 1px solid #e6ebf1;
  border-radius: 9px;
  box-shadow: 0 4px 14px rgba(15, 23, 42, 0.1);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}
.roadmap-prd-row:hover .roadmap-place-form,
.roadmap-prd-row .roadmap-place-form:focus-within { opacity: 1; pointer-events: auto; }

@media (max-width: 900px) {
  .roadmap-layout { flex-direction: column; }
  .roadmap-rail { width: 100%; flex-basis: auto; }
  /* No hover on touch: the placement form returns to normal flow, always visible. */
  .roadmap-prd-row .roadmap-place-form {
    position: static;
    transform: none;
    margin: 0.3rem 0 0.2rem 0.25rem;
    padding: 0;
    border: 0;
    box-shadow: none;
    opacity: 1;
    pointer-events: auto;
  }
}

.dash-act-head { display: flex; align-items: center; gap: 0.4rem; }
.dash-act-actor { font-size: 0.85rem; font-weight: 600; }
.dash-act-label { font-size: 0.82rem; color: var(--ballast); }
.dash-act-when { font-size: 0.72rem; color: var(--ballast); margin-left: auto; flex-shrink: 0; }
.dash-act-sub { font-size: 0.8rem; margin-top: 0.15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-act-prd { font-weight: 500; }
.dash-act-project { color: var(--ballast); }
.dash-act-detail { font-size: 0.8rem; color: var(--ballast); margin-top: 0.2rem; }

.dash-workspaces { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr)); gap: 0.85rem; }
.dash-workspace {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  background: white;
  border-radius: var(--radius);
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
  padding: 0.85rem 1rem;
  color: inherit;
}
.dash-workspace:hover { background: #f8fafc; text-decoration: none; }
.dash-workspace-name { font-weight: 600; font-size: 0.92rem; }
.dash-workspace-go { font-size: 0.78rem; color: var(--rail-blue); flex-shrink: 0; }

/* Section head with a trailing link (e.g. "Projects … All projects →") on the
   per-tenant dashboard (PRD #50 P1 sw-y90k). */
.dash-section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; margin-bottom: 0.5rem; }
.dash-section-head .section-title { margin-bottom: 0; }
.dash-section-link { font-size: 0.8rem; color: var(--rail-blue); flex-shrink: 0; white-space: nowrap; }

/* Org picker (PRD #50 P0 sw-s8dk): the post-login workspace chooser. Mirrors the
   dash-workspace card language with an icon/initial badge + member count. */
.org-picker { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); gap: 0.85rem; max-width: 760px; }
.org-card {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  background: white;
  border-radius: var(--radius);
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
  padding: 1rem 1.1rem;
  color: inherit;
  border: 1px solid transparent;
}
.org-card:hover { background: #f8fafc; border-color: var(--rail-blue); text-decoration: none; }
.org-card-icon {
  width: 40px; height: 40px;
  border-radius: 9px;
  flex-shrink: 0;
  object-fit: cover;
}
.org-card-icon-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--rail-blue);
  color: white;
  font-weight: 700;
  font-size: 1.1rem;
}
.org-card-body { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; flex: 1; }
.org-card-name { font-weight: 600; font-size: 0.95rem; color: var(--steel); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.org-card-members { font-size: 0.78rem; color: var(--ballast); }
.org-card-go { font-size: 1rem; color: var(--rail-blue); flex-shrink: 0; }

/* Roadmap milestone completion + collapse (PRD #38). A milestone is a native
   <details> disclosure: its header is the summary, its PRDs the body. A milestone
   whose every PRD is complete/released renders without [open] (auto-collapsed to
   just the header) and carries .is-complete so the filter can hide it. Same
   no-JavaScript idiom as the phase accordion. */
.roadmap-milestone > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.roadmap-milestone > summary::-webkit-details-marker { display: none; }
/* Chevron from two borders (no icon asset): points right when collapsed, rotates
   down when open — matching .phase-chevron. */
.roadmap-milestone > summary::before {
  content: "";
  flex: none;
  width: 0.42rem;
  height: 0.42rem;
  border-right: 2px solid var(--ballast);
  border-bottom: 2px solid var(--ballast);
  transform: rotate(-45deg);
  transition: transform 0.2s ease;
}
.roadmap-milestone[open] > summary::before { transform: rotate(45deg); }
.roadmap-milestone > summary:focus-visible {
  outline: 2px solid var(--rail-blue);
  outline-offset: 2px;
  border-radius: var(--radius);
}
.roadmap-milestone-count {
  margin-left: auto;
  font-size: 0.78rem;
  color: var(--ballast);
  white-space: nowrap;
}
.roadmap-milestone-done { color: var(--signal-green); font-weight: 700; font-size: 0.9rem; }

/* "Hide completed milestones" filter (PRD #38): a zero-JavaScript CSS toggle —
   the checkbox lives in .roadmap-body and :has() hides every fully-complete
   milestone while it's checked. The control only renders when at least one
   milestone is complete (roadmapHasCompleteMilestone). */
.roadmap-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 1rem;
  font-size: 0.82rem;
  color: var(--ballast);
  cursor: pointer;
  user-select: none;
}
.roadmap-filter input { cursor: pointer; }
.roadmap-body:has(.roadmap-filter-toggle:checked) .roadmap-milestone.is-complete { display: none; }

/* sw-1b0 — "Awaiting acceptance" PRD filter: the same zero-JavaScript :has()
   toggle idiom as the roadmap filter above. Checking it turns the PRD list into a
   one-click queue of "built, needs your sign-off" PRDs — it hides every
   non-awaiting row and the collapsed completed/archived/parked + discussion
   sections, leaving exactly the db.BuildComplete PRDs. The control only renders
   when at least one active PRD qualifies (countAwaitingAcceptance > 0), so the
   default view is unchanged. The chip is signal-green to match the awaiting badge. */
.prd-awaiting-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0 0 0.85rem;
  padding: 0.3rem 0.7rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: #166534;
  background: #f0fdf4;
  border: 1px solid #bbf7d0;
  border-radius: 999px;
  cursor: pointer;
  user-select: none;
}
.prd-awaiting-filter input { cursor: pointer; }
/* PRD #98 — the project PRD-list rows are now status-box cards (.blueprint-list .sb
   via specCardRow), not .blueprint-row, so these filter selectors target the card.
   Scoped to `.blueprint-list .sb` so the hero Work box (.sb outside .blueprint-list)
   is untouched. If the row element/class changes again, update these in lockstep —
   TestPRDListFilterSelectorsTargetCard guards this coupling. */
.prd-specs:has(.prd-awaiting-toggle:checked) .blueprint-list .sb:not(.is-awaiting),
.prd-specs:has(.prd-awaiting-toggle:checked) .completed-specs,
.prd-specs:has(.prd-awaiting-toggle:checked) .discussions-section { display: none; }

/* PRD #315 P2 — rejected-work filter: the awaiting filter's zero-JavaScript :has()
   idiom, keyed on the .is-rejected marker specCardRow sets from
   ProjectSpecRow.HasRejectedWork. Renders only when an active PRD carries one. */
.prd-rejected-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0 0 0.6rem;
  padding: 0.3rem 0.7rem;
  font-size: 0.78rem;
  font-weight: 600;
  color: #9f1239;
  background: #fff1f2;
  border: 1px solid #fecdd3;
  border-radius: 999px;
  cursor: pointer;
  user-select: none;
}
.prd-rejected-filter input { cursor: pointer; }
.prd-specs:has(.prd-rejected-toggle:checked) .blueprint-list .sb:not(.is-rejected),
.prd-specs:has(.prd-rejected-toggle:checked) .completed-specs,
.prd-specs:has(.prd-rejected-toggle:checked) .discussions-section { display: none; }

/* PRD #53 — blocked PRD filter: the same zero-JavaScript :has() idiom as the
   awaiting filter, but three-state (All / Blocked / Unblocked) via radio buttons
   keyed on their value attribute. "Blocked" hides every unblocked row and the
   collapsed completed/discussion sections (blocked work lives in the active list);
   "Unblocked" hides only the blocked rows; "All" (default) leaves the view as-is.
   The control renders only when at least one active PRD is blocked. */
.prd-blocked-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 0.85rem;
  padding: 0.25rem 0.75rem 0.4rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: #991b1b;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: 0.6rem;
}
.prd-blocked-filter legend { padding: 0 0.35rem; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: #b91c1c; }
.prd-blocked-filter label { display: inline-flex; align-items: center; gap: 0.25rem; cursor: pointer; user-select: none; }
.prd-blocked-filter input { cursor: pointer; }
.prd-specs:has(.prd-blocked-toggle[value="blocked"]:checked) .blueprint-list .sb:not(.is-blocked),
.prd-specs:has(.prd-blocked-toggle[value="blocked"]:checked) .completed-specs,
.prd-specs:has(.prd-blocked-toggle[value="blocked"]:checked) .discussions-section { display: none; }
.prd-specs:has(.prd-blocked-toggle[value="unblocked"]:checked) .blueprint-list .sb.is-blocked { display: none; }

/* ===================================================================
   Public marketing front page (PRD #86 P0) — the logged-out landing at /.
   Reuses the rail-yard palette + design tokens so it reads as first-party.
   The header + hero sit on the same dark steel gradient authShell uses; the
   sections below drop back to the light --surface background.
   =================================================================== */

/* Header + hero share the dark backdrop so the top of the page is one piece. */
.landing-top {
  background: linear-gradient(135deg, #1E293B 0%, #334155 100%);
  color: white;
}

.landing-header { padding: 1.1rem 0; }
.landing-header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.landing-brand {
  font-family: var(--font-mono);
  font-size: 1.15rem;
  font-weight: 700;
  color: white;
  letter-spacing: -0.5px;
  white-space: nowrap;
}
.landing-brand span { color: var(--signal-amber); }
.landing-brand .brand-mark { background-color: var(--signal-green-bright); }
.landing-brand:hover { text-decoration: none; }
.landing-header-nav { display: flex; align-items: center; gap: 1.25rem; }
.landing-header-link {
  color: rgba(255,255,255,0.85);
  font-size: 0.9rem;
  font-weight: 500;
}
.landing-header-link:hover { color: white; }

/* Hero */
.landing-hero { padding: 3.5rem 0 4.5rem; }
.landing-hero-inner { max-width: 760px; }
.landing-eyebrow {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--signal-green-bright);
  margin-bottom: 0.9rem;
}
.landing-title {
  font-size: 2.6rem;
  line-height: 1.12;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: white;
  margin-bottom: 1.1rem;
}
.landing-lede {
  font-size: 1.12rem;
  line-height: 1.6;
  color: rgba(255,255,255,0.82);
  max-width: 640px;
  margin-bottom: 1.8rem;
}
.landing-cta-row { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; }
.landing-flow-tag {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  color: rgba(255,255,255,0.55);
  margin-top: 1.6rem;
}

/* Larger CTA buttons + a ghost variant that reads on the dark hero. */
.btn-lg { padding: 0.8rem 1.6rem; font-size: 1rem; }
.btn-ghost {
  background: transparent;
  color: white;
  border: 1px solid rgba(255,255,255,0.4);
}
.btn-ghost:hover { background: rgba(255,255,255,0.1); }

/* Light content region below the hero; push the footer down on short viewports. */
.landing-main { flex: 1 0 auto; width: 100%; }
.landing-section { padding: 3.5rem 0; }
.landing-h2 {
  font-size: 1.8rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--steel);
}
.landing-section-sub {
  font-size: 1.02rem;
  line-height: 1.6;
  color: var(--ballast);
  max-width: 620px;
  margin-top: 0.6rem;
}

/* The flow: idea → PRD → agents → product. Numbered cards. */
.landing-flow {
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.25rem;
  counter-reset: none;
}
.landing-flow-step {
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  padding: 1.5rem 1.35rem;
  box-shadow: 0 1px 3px rgba(0,0,0,0.05);
  position: relative;
}
.landing-flow-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 999px;
  background: var(--rail-blue);
  color: white;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.95rem;
  margin-bottom: 0.9rem;
}
.landing-flow-title { font-size: 1.1rem; font-weight: 700; color: var(--steel); margin-bottom: 0.4rem; }
.landing-flow-body { font-size: 0.92rem; line-height: 1.55; color: var(--ballast); }

/* AI-First panel — the central positioning, on a dark sleeper background. */
.landing-ai { background: var(--sleeper); color: white; }
.landing-ai .landing-h2 { color: white; }
.landing-ai .landing-section-sub { color: rgba(255,255,255,0.72); }
.landing-eyebrow-dark { color: var(--signal-green-bright); }
.landing-ai-inner {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: 2.5rem;
  align-items: start;
}
.landing-ai-lede { position: sticky; top: 1.5rem; }
.landing-ai-points { list-style: none; margin: 0; padding: 0; display: grid; gap: 1.25rem; }
.landing-ai-point {
  padding: 1.3rem 1.4rem;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.1);
  border-left: 3px solid var(--signal-green-bright);
  border-radius: var(--radius);
}
.landing-ai-point-title { font-size: 1.05rem; font-weight: 700; color: white; margin-bottom: 0.4rem; }
.landing-ai-point-body { font-size: 0.94rem; line-height: 1.6; color: rgba(255,255,255,0.75); }

/* Engineered to be trusted — the harness controls as a 4-up card grid on the
   light surface. Same card chrome + type scale as the flow cards above; the
   only genuinely new piece is the 4-across grid. The green top accent echoes
   the .landing-ai-point accent motif, using --signal-green (the "passed"/
   validated token) to fit the verification theme. */
.landing-trust-points {
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.25rem;
}
.landing-trust-point {
  background: white;
  border: 1px solid #e5e7eb;
  border-top: 3px solid var(--signal-green);
  border-radius: var(--radius);
  padding: 1.5rem 1.35rem;
  box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.landing-trust-point-title { font-size: 1.05rem; font-weight: 700; color: var(--steel); margin-bottom: 0.4rem; }
.landing-trust-point-body { font-size: 0.92rem; line-height: 1.55; color: var(--ballast); }
.landing-trust-note { margin-top: 1.6rem; max-width: none; font-size: 0.92rem; }

/* Both audiences — novice-legible vs. process-rigor, side by side. */
.landing-audiences {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.5rem;
  margin-top: 2rem;
}
.landing-audience { padding: 1.75rem; }
.landing-audience-title { font-size: 1.2rem; font-weight: 700; color: var(--steel); margin-bottom: 0.6rem; }
.landing-audience-body { font-size: 0.96rem; line-height: 1.6; color: var(--ballast); }

/* Closing CTA band. */
.landing-final { background: white; border-top: 1px solid #e5e7eb; padding: 3.5rem 0; }
.landing-final-inner { text-align: center; max-width: 620px; margin: 0 auto; }
.landing-final-title { font-size: 1.7rem; font-weight: 700; letter-spacing: -0.01em; color: var(--steel); }
.landing-final-sub { font-size: 1.02rem; color: var(--ballast); margin: 0.6rem 0 1.6rem; }
.landing-final .landing-cta-row { justify-content: center; }
/* On the light closing band the ghost button needs dark ink to stay legible. */
.landing-final .btn-ghost { color: var(--steel); border-color: #cbd5e1; }
.landing-final .btn-ghost:hover { background: #f1f5f9; }

/* Responsive: collapse the 4-up flow and 2-up grids on smaller screens. */
@media (max-width: 900px) {
  .landing-flow { grid-template-columns: repeat(2, 1fr); }
  .landing-trust-points { grid-template-columns: repeat(2, 1fr); }
  .landing-ai-inner { grid-template-columns: 1fr; gap: 1.75rem; }
  .landing-ai-lede { position: static; }
}
@media (max-width: 640px) {
  .landing-title { font-size: 2rem; }
  .landing-flow { grid-template-columns: 1fr; }
  .landing-trust-points { grid-template-columns: 1fr; }
  .landing-audiences { grid-template-columns: 1fr; }
  .landing-hero { padding: 2.5rem 0 3rem; }
  .landing-cta-row .btn { flex: 1; }
}

/* Onboarding wizard (PRD #86 P1) — guided first-run flow at /dashboard/onboarding.
   Explicit heading colors so the steps stay legible on the light --surface. */
.onb-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
  gap: 1.5rem;
  align-items: start;
  max-width: 1000px;
}
.onb-steps { list-style: none; display: flex; flex-direction: column; gap: 1rem; }
.onb-step {
  display: flex;
  gap: 1rem;
  background: white;
  border: 1px solid #E2E8F0;
  border-left: 3px solid #E2E8F0;
  border-radius: var(--radius);
  padding: 1.25rem;
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}
.onb-step.onb-active { border-left-color: var(--rail-blue); }
.onb-step.onb-done { border-left-color: var(--signal-green); }
.onb-step.onb-locked { opacity: 0.6; }
.onb-step-marker {
  flex: 0 0 auto;
  width: 2rem; height: 2rem;
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px;
  background: #F1F5F9; color: var(--ballast);
  font-weight: 600; font-size: 0.95rem;
}
.onb-step.onb-active .onb-step-marker { background: var(--rail-blue); color: white; }
.onb-step.onb-done .onb-step-marker { background: var(--signal-green); color: white; }
.onb-step-body { flex: 1 1 auto; min-width: 0; }
.onb-step-title { font-size: 1.02rem; font-weight: 600; color: var(--steel); margin-bottom: 0.25rem; }
.onb-step-desc { font-size: 0.88rem; color: var(--ballast); line-height: 1.5; margin-bottom: 0.85rem; }
.onb-step-status {
  display: inline-block; font-size: 0.78rem; font-weight: 600;
  color: var(--signal-green); text-transform: uppercase; letter-spacing: 0.03em;
}
.onb-step-hint { font-size: 0.82rem; color: var(--ballast); font-style: italic; }
.onb-ws-form { max-width: 26rem; }
.onb-ws-form .form-group { margin-bottom: 0.85rem; }
.onb-skip { margin-top: 1.5rem; }
.onb-complete { padding: 1.5rem; margin-bottom: 1.5rem; border-left: 3px solid var(--signal-green); }
.onb-complete .section-title { color: var(--steel); }
.onb-aside { position: sticky; top: 1.5rem; }
.onb-fastpath { padding: 1.5rem; }
.onb-fastpath .section-title { color: var(--steel); }
.onb-prompt { max-height: 320px; overflow: auto; margin-bottom: 0.75rem; white-space: pre-wrap; }
.onb-fastpath-hint { font-size: 0.8rem; color: var(--ballast); margin-top: 0.6rem; }
.onb-resume-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  background: #EFF6FF; border: 1px solid #BFDBFE; border-radius: var(--radius);
  padding: 0.85rem 1.1rem; margin-bottom: 1.5rem;
}
.onb-resume-text { font-size: 0.88rem; color: var(--steel); }
@media (max-width: 820px) {
  .onb-grid { grid-template-columns: 1fr; }
  .onb-aside { position: static; }
  .onb-resume-banner { flex-direction: column; align-items: flex-start; }
}

/* ============================================================================
   The reusable status / work box (PRD #89 P1) — status_box.go + views_status_box.templ.
   ONE component renders a work item's state everywhere: a completion fill, a
   gears-or-icon mark tinted per state via currentColor, a priority coin, and the
   title/count/detail. The theme class (.sb-t-*) sets --sb-tone (the mark color)
   and the fill color, so a single markup shape recolors per lifecycle state.
   ============================================================================ */
.sb {
  position: relative;
  display: block;
  width: 100%;
  text-align: left;
  border: 1px solid #e2e8f0;
  border-radius: 12px; /* design gallery box radius (not the global 6px --radius) */
  overflow: hidden;
  background: white;
  box-shadow: 0 1px 2px rgba(15,23,42,0.04);
  color: inherit;
  text-decoration: none;
}
a.sb:hover { box-shadow: 0 4px 14px rgba(15,23,42,0.10); }
/* Sidebar slot — matches the other right-rail cards' spacing. */
.sb-slot { margin-bottom: 1rem; }
/* PRD #214 (issue #21) — the Unpark card that sits directly under the status
   stamp on a parked PRD. Tinted with the same amber as .prd-parked-chip so the
   card reads as belonging to the "parked" state the stamp above it announces,
   rather than as one more neutral rail card. */
.prd-unpark-card {
  padding: 1rem;
  margin-bottom: 1rem;
  border-color: #fcd34d;
  background: #fffbeb;
}
.prd-unpark-note {
  font-size: 0.75rem;
  color: var(--ballast);
  margin: 0 0 0.5rem;
  line-height: 1.4;
}
/* PRD #691 — the draft ⇄ review gate card. One rail card, two states: Submit for
   review on a draft, Back to draft on a PRD at the approval gate. Neutral rather
   than tinted, because unlike .prd-unpark-card it is not announcing an unusual
   state — it is the ordinary next step on a spec being written. */
.prd-review-gate-card {
  padding: 1rem;
  margin-bottom: 1rem;
}
.prd-review-gate-note {
  font-size: 0.72rem;
  color: var(--ballast);
  margin: 0.5rem 0 0;
  line-height: 1.4;
}
.prd-review-gate-card .prd-review-gate-note:first-child { margin: 0 0 0.5rem; }
/* The completion fill sits behind the content and colors left→right to done/total. */
.sb-fill { position: absolute; inset: 0 auto 0 0; width: 0; pointer-events: none; }
/* PRD #313 — the segmented progress bar. One slice per work state laid left to
   right, replacing the single wash on a PRD box so "built but unsigned-off" is
   visibly distinct from "not built". It does NOT reuse .sb-fill: each theme has
   its own `.sb-t-x .sb-fill` background rule that would paint over the slices. */
.sb-fill-seg { position: absolute; inset: 0; display: flex; pointer-events: none; }
.sb-seg { display: block; height: 100%; min-width: 2px; }
/* Colours REINFORCE the legend text below, they never carry the state alone.
   One class per CriterionState (PRD #313 crit:922f6146d85d) — the segment's class
   suffix IS the resolver's state key, so these track the vocabulary rather than a
   bar-private list. The hues are criterionStateInk verbatim (criterion_state.go),
   which is what makes a slice on the bar and the sticky for the same criterion
   read as the same colour instead of merely similar ones.
   ADDING A STATE: add its two rules here, one per block. This is load-bearing,
   not polish: the project list (PRD #337, question 299) draws NO legend — its
   segment text is screen-reader-only — so on that surface a slice's colour is
   the only signal a sighted reader gets, and a state with no rule renders as
   nothing at all. TestEverySegmentStateHasColourRules fails when any state in
   CriterionStateDisplayOrder is missing either rule. */
.sb-seg-outstanding { background: rgba(100,116,139,0.07); }
.sb-seg-blocked     { background: rgba(220,38,38,0.16);  border-right: 2px solid #dc2626; }
.sb-seg-in_progress { background: rgba(37,99,235,0.16);  border-right: 2px solid #2563eb; }
.sb-seg-delivered   { background: rgba(180,83,9,0.16);   border-right: 2px solid #b45309; }
.sb-seg-validated   { background: rgba(4,120,87,0.20);   border-right: 2px solid #047857; }
.sb-seg-deferred    { background: rgba(107,98,80,0.14);  border-right: 2px solid #6b6250; }
.sb-seg-in_review   { background: rgba(124,58,237,0.16); border-right: 2px solid #7c3aed; }
.sb-seg-failed      { background: rgba(190,18,60,0.18);  border-right: 2px solid #be123c; }
/* PRD #337 — the refusal + repair-loop states (PRD #330 vocabulary), previously
   missing here, which left them as transparent slices once the list's legend was
   dropped. Hues are criterionStateInk verbatim, like every rule above: refused's
   deep violet keeps the judge-lane family relation to in_review, repair-in-flight's
   cyan is motion without a verdict, and escalated's fuchsia is a non-red alarm so
   the blocked/failed/escalated trio stays tellable apart. */
.sb-seg-refused_attempt  { background: rgba(91,33,182,0.16);  border-right: 2px solid #5b21b6; }
.sb-seg-repair_in_flight { background: rgba(8,145,178,0.16);  border-right: 2px solid #0891b2; }
.sb-seg-repair_escalated { background: rgba(162,28,175,0.16); border-right: 2px solid #a21caf; }
/* PRD #599 P1 — the unexecuted done. Amber-brown, deliberately in delivered's
   family rather than validated's green: the criterion is NOT signed off, and a
   reader taking the colour alone must not come away thinking it is. */
.sb-seg-done_unexecuted  { background: rgba(146,64,14,0.16);  border-right: 2px solid #92400e; }
/* Rework in flight on the delivering PR (switchyard-conductor #1514, now a state
   of its own). Orange, a hotter hue than delivered's amber and NOT a shade of it:
   the two used to render as one colour, which is what this rule ends. */
.sb-seg-rework_in_flight { background: rgba(194,65,12,0.18);  border-right: 2px solid #c2410c; }
/* The legend is the accessible signal: every segment's state name and count as
   ordinary text, so the bar is readable on a monochrome display and to a
   colour-blind reader. It wraps rather than truncating — dropping an entry would
   silently remove a state from the reading. */
.sb-legend { display: flex; flex-wrap: wrap; gap: 0.15rem 0.6rem; margin-top: 0.1875rem; font-size: 0.6875rem; line-height: 1.35; color: var(--ballast); font-variant-numeric: tabular-nums; }
/* Each entry gets a small colour dot matching its slice — the tie between the
   text and the bar. It is decorative: remove every colour and the entry still
   reads "Delivered 4". */
.sb-lg { display: inline-flex; align-items: center; gap: 0.25rem; white-space: nowrap; }
.sb-lg::before { content: ""; width: 0.4rem; height: 0.4rem; border-radius: 2px; background: currentcolor; flex: 0 0 auto; }
.sb-lg-outstanding { color: var(--ballast); }
.sb-lg-blocked     { color: #dc2626; }
.sb-lg-in_progress { color: #2563eb; }
.sb-lg-delivered   { color: #b45309; }
.sb-lg-validated   { color: #047857; }
.sb-lg-deferred    { color: #6b6250; }
.sb-lg-in_review   { color: #7c3aed; }
.sb-lg-failed      { color: #be123c; }
.sb-lg-refused_attempt  { color: #5b21b6; }
.sb-lg-repair_in_flight { color: #0891b2; }
.sb-lg-repair_escalated { color: #a21caf; }
.sb-lg-done_unexecuted  { color: #92400e; }
.sb-lg-rework_in_flight { color: #c2410c; }
.sb-bd { position: relative; display: flex; align-items: center; gap: 0.75rem; padding: 0.8125rem 0.9375rem; }
/* The mark box — its color is the theme tone; the inline SVG fills currentColor. */
.sb-ic {
  position: relative;
  width: 2.75rem; height: 2.75rem;
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  border-radius: 9px;
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  color: var(--sb-tone, var(--ballast));
}
.sb-ic svg { width: 1.5rem; height: 1.5rem; fill: currentcolor; display: block; }
.sb-gears { display: flex; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem; }
.sb-gears svg { width: 100%; height: 100%; }
.sb-mt { flex: 1; min-width: 0; }
.sb-tp { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
.sb-nm { font-weight: 700; font-size: 0.875rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--steel); }
.sb-ct { font-variant-numeric: tabular-nums; font-weight: 700; font-size: 0.8125rem; color: var(--steel); flex: 0 0 auto; }
.sb-dt { display: block; font-size: 0.75rem; color: var(--ballast); margin-top: 0.0625rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Hero variant — the aggregate Work box + the per-PRD box: a larger mark. */
.sb-hero .sb-ic { width: 3.25rem; height: 3.25rem; }
.sb-hero .sb-ic svg { width: 1.75rem; height: 1.75rem; }
.sb-hero .sb-gears { width: 2.75rem; height: 2.75rem; }
/* Hero title matches the gallery's big box: same 14px name, prominence comes
   from the larger 52px icon, not larger text. */
.sb-hero .sb-nm { font-size: 0.875rem; }

/* Priority coin — the tier-colored rank chip; shared with the PRD list-row rank
   gutter (prdRankBadge) so the coin reads identically on every surface. */
.sb-coin {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.4rem; height: 1.4rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  font-size: 0.72rem; font-weight: 700; line-height: 1;
  color: #fff;
}
/* The ten-step priority ramp (PRD #307). One custom property per rank so the
   scale lives in ONE place: the tier classes below just spend them, and the
   ordering rule is legible as a list rather than scattered across ten rules.

   It is a RAMP, not ten hues picked by eye. Two invariants hold across 1 → 10:

   1. Lightness rises monotonically (CIE L* 28.7 → 48.3, ~2.2 per step), so a
      lower rank is always the heavier, denser chip and a higher rank recedes
      toward the white card. Hue rotates along with it — deep crimson → rust →
      copper → teal → blue → slate — carrying "hot at 1, cool/neutral at 10" and
      keeping adjacent steps told apart by hue as well as by weight.
   2. Every step still carries .sb-coin's white label at ≥ 4.5:1 (WCAG AA for
      the coin's small bold text). Contrast against #fff runs 9.8:1 at rank 1
      down to 4.76:1 at rank 10 — computed from each hex's relative luminance,
      1.05/(L+0.05). That AA floor is what caps the light end: the ramp cannot
      run any paler than --ballast without the number going unreadable, which is
      why rank 1 is a deeper red than --signal-red (4.83:1 — it would have to sit
      at the ramp's COOL end to satisfy invariant 1) rather than the token
      itself. The palette is still the anchor at both ends: rank 10 IS
      --ballast, and rank 8 lands a hair off --rail-blue.

   The rank NUMBER is always printed in the coin, so colour is never the sole
   carrier of the tier — the ramp is the glance, the digit is the answer. */
:root {
  --prio-1: #890E1A;  /* deep crimson  · 9.81:1 on #fff */
  --prio-2: #920E09;  /* dark red      · 9.17:1 */
  --prio-3: #952105;  /* rust          · 8.41:1 */
  --prio-4: #903500;  /* burnt orange  · 7.79:1 */
  --prio-5: #864700;  /* copper        · 7.20:1 */
  --prio-6: #006589;  /* deep teal     · 6.52:1 */
  --prio-7: #0B63B1;  /* ocean blue    · 6.12:1 */
  --prio-8: #2861D4;  /* rail blue     · 5.60:1 */
  --prio-9: #556D9E;  /* blue slate    · 5.16:1 */
  --prio-10: var(--ballast); /* slate  · 4.76:1 */
}
.sb-coin-1 { background: var(--prio-1); }
.sb-coin-2 { background: var(--prio-2); }
.sb-coin-3 { background: var(--prio-3); }
.sb-coin-4 { background: var(--prio-4); }
.sb-coin-5 { background: var(--prio-5); }
.sb-coin-6 { background: var(--prio-6); }
.sb-coin-7 { background: var(--prio-7); }
.sb-coin-8 { background: var(--prio-8); }
.sb-coin-9 { background: var(--prio-9); }
.sb-coin-10 { background: var(--prio-10); }
/* Unranked (rank 0): deliberately OFF the ramp — a pale chip with a dark label,
   so "no rank" never reads as "the coolest rank". */
.sb-coin-none { background: #e2e8f0; color: var(--ballast); font-weight: 600; }
/* The PRD list-row rank gutter reuses .sb-coin for its color/shape; this keeps
   the inline-flow spacing the row layout expects (was an inline style). */
.prd-rank { margin-right: 0.45rem; vertical-align: middle; }
/* In the box, the coin floats over the top-left of the mark; in the row gutter
   (.prd-rank, not inside .sb-ic) it stays inline. */
.sb-ic .sb-coin {
  position: absolute; top: -7px; left: -7px;
  border: 2px solid white;
  box-shadow: 0 1px 2px rgba(15,23,42,0.25);
}

/* Inline rank picker on the priority coin (sw-rank) — the coin over the status
   mark's top-left IS the control, so the build-queue rank is changed where it is
   read. Same zero-JS <details> popover the activity hand-color picker uses; the
   page gains no script for it.

   .sb sets overflow:hidden to clip the progress fill to the card's 12px radius,
   which would also clip this menu, so the card opens up for exactly as long as
   the picker is open. The cost is the fill's corners going square for that
   moment; the alternative — a menu sheared off at the card edge — is worse. A
   :has()-less browser keeps the old inert coin behaviour plus a clipped menu,
   which is why this is a control the sidebar's Build queue card duplicates
   rather than the only way to set a rank. */
.sb:has(.sb-rank[open]) { overflow: visible; }
.sb-ic .sb-rank { position: absolute; top: -7px; left: -7px; }
.sb-rank > summary { list-style: none; cursor: pointer; display: inline-flex; }
.sb-rank > summary::-webkit-details-marker { display: none; }
.sb-rank > summary:hover .sb-coin { filter: brightness(1.15); }
/* The summary's coin is already positioned by .sb-rank itself; it must not
   re-apply .sb-ic .sb-coin's own absolute offset on top of that. */
.sb-ic .sb-rank > summary .sb-coin { position: static; }
.sb-rank-menu {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: max-content;
  margin: 0;
  padding: 10px;
  background: #fff;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.16);
}
.sb-rank-hd { font-size: 0.7rem; font-weight: 700; color: var(--ballast); letter-spacing: 0.02em; }
.sb-rank-grid { display: grid; grid-template-columns: repeat(5, 1.6rem); gap: 6px; }
/* The menu's choices are coins too, so the ramp a reader learned in the gutter
   is the same ramp they pick from. Re-anchored out of .sb-ic's absolute rule. */
.sb-ic .sb-rank-menu .sb-coin {
  position: static;
  min-width: 1.6rem; height: 1.6rem;
  padding: 0;
  border: 2px solid transparent;
  cursor: pointer;
  transition: transform 0.08s ease;
}
.sb-ic .sb-rank-menu .sb-coin:hover { transform: scale(1.12); }
.sb-rank-clear {
  font-size: 0.72rem; font-weight: 600;
  padding: 4px 8px;
  border: 1px solid #e2e8f0;
  border-radius: 999px;
  background: #f8fafc;
  color: var(--ballast);
  cursor: pointer;
}
.sb-rank-clear:hover { background: #eef2f7; }
/* The rank in force, ringed rather than recoloured, so the ramp stays readable. */
.sb-rank-on { outline: 2px solid var(--steel); outline-offset: 2px; }

/* Compact glyph for a PRD list row / roadmap chip — the mark only, tinted. */
.sb-glyph { display: inline-flex; align-items: center; justify-content: center; width: 1.35rem; height: 1.35rem; flex: 0 0 auto; color: var(--sb-tone, var(--ballast)); vertical-align: middle; }
.sb-glyph svg { width: 1.15rem; height: 1.15rem; fill: currentcolor; display: block; }
.sb-glyph .sb-gears { width: 1.35rem; height: 1.35rem; }

/* Themes: --sb-tone recolors the mark; .sb-fill gets a faint wash + a leading
   edge in the same hue, so the fill reads as progress in the status color.
   Fill washes + edges use the brighter -500 status hues to match the design
   gallery, distinct from the app's -600 --signal-* tokens (kept for the coin
   and solid accents); scoped to .sb so the rest of the app is unaffected. */
.sb-t-active   { --sb-tone: #f59e0b; }
.sb-t-active   .sb-fill { background: rgba(245,158,11,0.12); border-right: 2px solid #f59e0b; }
.sb-t-done     { --sb-tone: var(--signal-green); }
.sb-t-done     .sb-fill { background: rgba(16,185,129,0.12); border-right: 2px solid #10b981; }
.sb-t-done     .sb-ct   { color: var(--signal-green); }
.sb-t-released { --sb-tone: var(--rail-blue); }
.sb-t-released .sb-fill { background: rgba(59,130,246,0.12); border-right: 2px solid #3b82f6; }
.sb-t-queued   { --sb-tone: var(--rail-blue); }
.sb-t-neutral  { --sb-tone: var(--ballast); }
.sb-t-blocked  { --sb-tone: var(--signal-red); }
.sb-t-blocked  .sb-fill { background: rgba(239,68,68,0.11);  border-right: 2px solid #ef4444; }
.sb-t-parked   { --sb-tone: var(--signal-amber); }
.sb-t-waiting  { --sb-tone: #94a3b8; }
.sb-t-waiting  .sb-bd { background: repeating-linear-gradient(45deg, #f8fafc, #f8fafc 8px, #f1f5f9 8px, #f1f5f9 16px); }
.sb-t-idle     { --sb-tone: var(--ballast); }
.sb-t-idle     .sb-fill { background: rgba(100,116,139,0.10); border-right: 2px solid var(--ballast); }
/* Awaiting acceptance: work is BUILT (fill full) but paused on a human sign-off.
   A distinct violet tone + a still clipboard-check icon (NOT the executing gears)
   so a delivered-but-unvalidated PRD never reads as "still working". */
.sb-t-awaiting { --sb-tone: #8b5cf6; }
.sb-t-awaiting .sb-fill { background: rgba(139,92,246,0.12); border-right: 2px solid #8b5cf6; }
/* Muted (archived / superseded): the whole box reads faded. */
.sb-t-muted    { --sb-tone: var(--ballast); opacity: 0.62; }

/* PRD #98 — the project PRD list renders each PRD as a status-box card
   (specCardRow). The shared .blueprint-list stacks rows with gap:0, so give the
   cards their own spacing; the detail line flexes so the lifecycle text and the
   blocked / awaiting / activity chips sit on one wrapping row. */
.blueprint-list .sb { margin-bottom: 0.5rem; }
.blueprint-list .sb:last-child { margin-bottom: 0; }
.sb-dt-chips { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; white-space: normal; }
.sb-dt-chips .sb-dt-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.parked-item .sb { flex: 1 1 auto; margin-bottom: 0; }

/* PRD #98 crit2/3 — the "Needs your attention" count badge + the contributor role
   legend (a compact key for the crown/knight/agent avatar treatments). */
.needs-attention .attn-count { display: inline-flex; align-items: center; justify-content: center; min-width: 1.2rem; height: 1.2rem; padding: 0 0.35rem; margin-left: 0.3rem; font-size: 0.72rem; font-weight: 700; line-height: 1; color: #fff; background: var(--signal-amber); border-radius: 999px; vertical-align: middle; }
/* Project-page "Needs your attention" wears the mockup's amber attn treatment:
   a warm gradient card, a bell + right-aligned count in the header, and a
   chevron affordance per row. Scoped to .needs-attention so the shared
   dash-attn rows used on the workspace dashboard are untouched. */
.needs-attention { background: linear-gradient(180deg, #fffbeb, #fff); border-color: #fde68a; }
.needs-attention .section-title { display: flex; align-items: center; gap: 0.5rem; font-weight: 800; }
.needs-attention .attn-count { margin-left: auto; min-width: 20px; height: 20px; line-height: 20px; font-weight: 800; }
.needs-attention .dash-attn { position: relative; padding-right: 1.35rem; }
.needs-attention .dash-attn::after { content: "\203A"; position: absolute; right: 0.4rem; top: 50%; transform: translateY(-50%); color: #94a3b8; font-size: 1.1rem; line-height: 1; }
.needs-attention .dash-attn:hover::after { color: #64748b; }
/* Leading type-icon per needs-attention row (design parity, mockup's .ai square):
   colored by the row's primary reason. */
.needs-attention .dash-attn { display: flex; align-items: center; gap: 0.65rem; }
.needs-attention .dash-attn-body { flex: 1; min-width: 0; }
.attn-ai { width: 1.7rem; height: 1.7rem; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; border-radius: 7px; color: #fff; font-size: 0.9rem; font-weight: 700; line-height: 1; }
.attn-ai-approve { background: #2563eb; }
.attn-ai-changes { background: #dc2626; }
.attn-ai-question { background: #7c3aed; }
.attn-ai-merge { background: #059669; }
.contributor-legend { display: flex; flex-wrap: wrap; gap: 0.25rem 0.7rem; margin-top: 0.55rem; font-size: 0.7rem; color: var(--ballast); }
.contributor-legend .legend-item { display: inline-flex; align-items: center; gap: 0.25rem; }
.contributor-legend .legend-icon { width: 0.85rem; height: 0.85rem; opacity: 0.85; }
.contributor-legend .legend-dot { width: 0.7rem; height: 0.7rem; border-radius: 50%; background: var(--rail-blue); display: inline-block; }
.contributor-legend .legend-agent { background: var(--signal-green); }
.bulkbar b { white-space: nowrap; }
/* Emphasized reclassify verb on a bug-typed/-tagged idea card (PRD #83 P2). */
.ibtn-flag { border-color: #b45309; color: #b45309; font-weight: 700; }
.ibtn-flag:hover { border-color: #92400e; color: #92400e; }

/* ============================================================
   Settings sub-pages (PRD #102): People & Access roster rows,
   role pills, and the Danger zone cards (mockup §Settings).
   ============================================================ */
.settings-person { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 0; border-top: 1px solid #f1f5f9; }
.settings-person:first-of-type { border-top: 0; padding-top: 0.2rem; }
.settings-person .avatar { margin-left: 0; }
.settings-person-who { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.settings-person-name { font-weight: 600; font-size: 0.88rem; }
.settings-person-sub { font-size: 0.76rem; color: var(--ballast); }
.settings-person-action { font-size: 0.78rem; white-space: nowrap; }
/* People & Access on the admin-page pattern (PRD #677 P4): .admin-home-section
   / .admin-home-heading carry the section rhythm and the small uppercase
   headings; these carry what used to be inline style attributes. */
.page-header .people-lede { color: var(--ballast); font-size: 0.9rem; margin-top: 0.25rem; }
.card.people-card { padding: 1.25rem 1.5rem; }
.people-card-title { font-size: 0.95rem; margin: 0 0 0.5rem; }
.form-hint.settings-workspace-admin-note { margin: 0 0 1rem; }
/* The delegated-approval card (views_grants.templ), shared with the PRD page's
   sidebar: the same values its inline styles had, so neither surface moves.
   Two-class selectors so .btn / .alert cannot out-rank them. */
.grants-panel .grants-title { font-size: 0.85rem; font-weight: 600; margin-bottom: 0.6rem; }
.grants-panel .grants-desc,
.grants-panel .grants-empty { font-size: 0.75rem; color: var(--ballast); margin: 0 0 0.6rem; }
.grants-panel .grants-empty { margin: 0; }
.grants-panel .grants-notice { font-size: 0.78rem; padding: 0.45rem 0.6rem; margin: 0 0 0.6rem; }
.grants-panel .grants-form { display: flex; flex-direction: column; gap: 0.5rem; }
.grants-panel .grants-control { font-size: 0.8rem; }
.grants-panel .grants-live { margin-top: 0.9rem; padding-top: 0.8rem; border-top: 1px solid #e5e7eb; }
.grants-panel .grants-live-title { font-size: 0.78rem; font-weight: 600; margin: 0 0 0.5rem; color: var(--ballast); }
.grants-panel .grants-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.55rem; }
.grants-panel .grants-item { font-size: 0.78rem; border-bottom: 1px solid #f1f5f9; padding-bottom: 0.5rem; }
.grants-panel .grants-item-name { font-weight: 500; word-break: break-word; }
.grants-panel .grants-item-meta { color: var(--ballast); display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; margin-top: 0.15rem; }
.grants-panel .grants-item-actions { margin-top: 0.3rem; display: flex; gap: 0.6rem; flex-wrap: wrap; }
/* Role pills: the roster's owner/knight/contributor/agent chips — the owner
   reuses the crown's amber, the knight the approval blue, agents the dashed
   green family the avatar system already speaks. */
.role-pill { font-size: 0.7rem; font-weight: 600; padding: 0.15rem 0.5rem; border-radius: 999px; white-space: nowrap; }
.role-pill-owner { background: #fef3c7; color: #92400e; }
.role-pill-knight { background: #dbeafe; color: #1e40af; }
.role-pill-contributor { background: #f1f5f9; color: #475569; }
.role-pill-agent { background: #d1fae5; color: #065f46; }
/* Danger zone: red-ruled cards; the deferred delete renders muted. */
.danger-zone-card { border: 1px solid #fecaca; }
.danger-zone-card h3 { color: #b91c1c; }
.danger-zone-disabled { opacity: 0.65; }
.danger-zone-disabled h3 { color: var(--ballast); }
.btn-danger { background: #dc2626; color: #fff; border-color: #dc2626; }
.btn-danger:hover { background: #b91c1c; border-color: #b91c1c; }
.btn-danger:focus-visible { outline: 2px solid #b91c1c; outline-offset: 2px; }
/* Archived projects (PRD #102 P1): muted card + chip + the collapsed group. */
.pcard-archived { opacity: 0.72; }
.pcard-archived-chip { font-size: 0.62rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; background: #f1f5f9; color: #64748b; border-radius: 999px; padding: 0.15rem 0.5rem; }
.archived-projects { margin-top: 1.25rem; }
.archived-projects > summary { cursor: pointer; font-size: 0.85rem; color: var(--ballast); }
.archived-projects > summary:focus-visible { outline: 2px solid var(--ballast); outline-offset: 2px; }
/* The collection under the summary keeps the gap the old inline style gave it,
   in either layout (PRD #609 P0 routed it through projectCollection). */
.archived-projects > .project-grid { margin-top: 0.75rem; }

/* Per-PRD watch toggle (PRD #103 crit2): the PRD page's appbar eye — same
   anatomy as .project-watch-summary, one-click subscribe, cyan when watching
   (mockup .watch.watching). */
.prd-watch-btn { display: inline-flex; align-items: center; gap: 0.45rem; height: 2.1rem; padding: 0 0.7rem; background: #fff; border: 1px solid #e2e8f0; border-radius: 9px; color: #94a3b8; font-weight: 600; font-size: 0.8rem; line-height: 1; cursor: pointer; font-family: inherit; }
.prd-watch-btn:hover { background: #f8fafc; }
.prd-watch-btn:focus-visible { outline: 2px solid var(--rail-blue); outline-offset: 2px; }
.prd-watch-btn.watching { color: #0e7490; border-color: #7dd3fc; background: #ecfeff; }
/* Join-this-PRD (PRD #103 crit3): full-width secondary button under the
   contributors avatar row (mockup .joinbtn); Leave renders as a quiet link. */
.prd-join-btn { width: 100%; margin-top: 0.6rem; font-size: 0.8rem; }
.prd-leave-row { margin-top: 0.5rem; text-align: center; }

/* Connect Faultline connection state (PRD #139 P0): verified-not-assumed.
   Green is earned only by a signed test delivery that verified through the
   real receiver — amber is registered-but-unproven, red is proven-broken. */
.fl-state { font-size: 0.68rem; padding: 0.1rem 0.45rem; border-radius: 3px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; border: 1px solid transparent; }
.fl-state-connected { background: #f0fdf4; color: var(--signal-green); border-color: #bbf7d0; }
.fl-state-verifying { background: #fffbeb; color: var(--signal-amber); border-color: #fde68a; }
.fl-state-unverified { background: #fef2f2; color: var(--signal-red); border-color: #fecaca; }
.fl-state-disconnected { background: #f8fafc; color: var(--ballast); border-color: #e2e8f0; }

/* ───────────────────────────────────────────────────────────────────────────
   Project Inbox (PRD #133): the unified ranked decision queue. Per-type
   collapsible groups (<details>), each item a row with inline action controls.
   ─────────────────────────────────────────────────────────────────────────── */
.inbox-head { margin: 0 0 1rem; }
/* This page's <h1>. Named .inbox-page-title, NOT .inbox-title: the backlog's
   triage card (PRD #100, further up this file) uses .inbox-title for its card
   TITLE, and a bare .inbox-title here overrode that card's 14.5px with this
   1.3rem heading size on the backlog page. Keep the two disjoint. */
.inbox-page-title { font-size: 1.3rem; margin: 0; }

.inbox-group {
  border: 1px solid #e2e8f0;
  border-radius: var(--radius);
  background: var(--surface);
  margin: 0 0 0.9rem;
  overflow: hidden;
}
.inbox-group-summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 0.85rem;
  cursor: pointer;
  font-weight: 600;
  font-size: 0.9rem;
  background: #f8fafc;
  list-style: none;
}
.inbox-group-summary::-webkit-details-marker { display: none; }
.inbox-group-summary::before {
  content: "▸";
  color: var(--ballast);
  font-size: 0.75rem;
  transition: transform 0.12s ease;
}
.inbox-group[open] > .inbox-group-summary::before { transform: rotate(90deg); }
.inbox-group-label { flex: 1; }
.inbox-group-count {
  min-width: 1.4rem;
  text-align: center;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: var(--rail-blue);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
}
.inbox-group-body { padding: 0.35rem 0.6rem 0.6rem; }

.inbox-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 0.4rem;
  border-top: 1px solid #f1f5f9;
}
.inbox-group-body > .inbox-item:first-child { border-top: none; }
.inbox-item-main { flex: 1; min-width: 0; }
.inbox-item-title {
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--steel);
  text-decoration: none;
  display: inline-block;
}
a.inbox-item-title:hover { text-decoration: underline; }
.inbox-item-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.25rem;
  font-size: 0.76rem;
  color: var(--ballast);
}
.inbox-badge {
  padding: 0.05rem 0.4rem;
  border-radius: var(--radius);
  background: #eef2ff;
  color: var(--rail-blue);
  font-weight: 600;
  font-size: 0.72rem;
}
/* PRD #383: the question row's decision-vs-informational label. The demoted band
   is deliberately the quieter of the two — an informational question is still on
   the queue, just not the one anybody is blocked on. */
.inbox-qtype-informational {
  background: var(--coal);
  border: 1px solid var(--rule);
  color: var(--ballast);
  font-weight: 500;
}
.inbox-context { color: var(--ballast); }
.inbox-age { color: var(--ballast); font-variant-numeric: tabular-nums; }
.inbox-waiting { font-size: 0.78rem; color: var(--ballast); font-style: italic; }

.inbox-item-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
}
.inbox-item-actions form { margin: 0; }
.inbox-btn { font-size: 0.78rem; padding: 0.3rem 0.6rem; }

.inbox-answer-form { margin-top: 0.5rem; }
.inbox-textarea {
  width: 100%;
  box-sizing: border-box;
  font-family: inherit;
  font-size: 0.82rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid #d1d5db;
  border-radius: var(--radius);
  resize: vertical;
}
.inbox-answer-form .inbox-item-actions { margin-top: 0.4rem; }

/* Binary settings choice — two labelled option cards (PRD #236 P0,
   crit:856ece775d25). The reusable stand-in for a bare checkbox wherever both
   sides of a setting deserve a name; rendered by settingsBinaryChoice().

   Zero JavaScript, the same :has(:checked) idiom the roadmap/PRD filters above
   use: each <label> wraps its own radio, so the whole card is a click target and
   arrow keys move between the two natively.

   The selectors are doubled (.option-cards .option-card) on purpose. These cards
   are <label>s, and .form-group label — specificity (0,2,0) — sets display,
   font-weight and margin-bottom on every label inside a form group. A bare
   .option-card would lose to it and the cards would render as flattened text
   rows if one is ever nested in a .form-group. */
.option-choice { border: 0; margin: 0 0 1rem; padding: 0; min-width: 0; }
.option-choice-legend {
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--steel);
  margin-bottom: 0.35rem;
  padding: 0;
}
.option-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; }
/* Below the two-column breakpoint the cards stack; side-by-side at this width
   would squeeze each Detail into an unreadable column. */
@media (max-width: 640px) { .option-cards { grid-template-columns: 1fr; } }
.option-cards .option-card {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  margin: 0;
  padding: 0.7rem 0.8rem;
  border: 1px solid #d1d5db;
  border-radius: var(--radius);
  background: var(--surface);
  font-weight: 400;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
.option-cards .option-card:hover { border-color: var(--rail-blue); }
/* The radio stays visible rather than hidden behind the card chrome: on a
   browser without :has() the card fill below never paints, and the radio is
   then the only thing saying which option is live. */
.option-cards .option-card input[type="radio"] {
  flex: none;
  margin-top: 0.15rem;
  accent-color: var(--rail-blue);
  cursor: pointer;
}
.option-card-body { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.option-card-label { font-size: 0.85rem; font-weight: 600; color: var(--steel); }
.option-card-detail { font-size: 0.78rem; color: var(--ballast); line-height: 1.4; }
.option-cards .option-card:has(input:checked) {
  border-color: var(--rail-blue);
  background: rgba(37, 99, 235, 0.05);
  box-shadow: inset 0 0 0 1px var(--rail-blue);
}
.option-cards .option-card:has(input:focus-visible) {
  outline: 2px solid var(--rail-blue);
  outline-offset: 2px;
}

/* Version Control provider chooser (PRD #256 P0, crit:c280361301cd) — the
   GitHub | GitLab option cards above decide which provider's one-click connect
   path the section offers, live, with no JavaScript and no save round-trip.

   The two rules below HIDE the path that was NOT chosen, rather than revealing
   the one that was, and that direction is the whole safety argument: a browser
   without :has() support skips both rules and falls back to showing BOTH
   connect paths — exactly what this section rendered before the chooser
   existed. Written the other way round (.vcs-connect-path { display: none }
   plus a :has() rule to reveal), the same unsupporting browser would hide both
   and leave the "One-click connect" block visibly empty, turning a progressive
   enhancement into a dead end. It is also the direction every other :has()
   filter in this stylesheet uses (roadmap, PRD, awaiting-acceptance).

   Scoped to .vcs-provider-picker so the selector reaches only this section's
   own chooser: .option-choice is the shared control's class and appears
   wherever a binary setting renders. */
.vcs-connect-paths { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-top: 0.15rem; }
.vcs-provider-picker:has(input[name="vcs_provider"][value="github"]:checked) .vcs-connect-bitbucket,
.vcs-provider-picker:has(input[name="vcs_provider"][value="github"]:checked) .vcs-connect-gitlab,
.vcs-provider-picker:has(input[name="vcs_provider"][value="bitbucket"]:checked) .vcs-connect-gitlab,
.vcs-provider-picker:has(input[name="vcs_provider"][value="bitbucket"]:checked) .vcs-connect-github,
.vcs-provider-picker:has(input[name="vcs_provider"][value="gitlab"]:checked) .vcs-connect-bitbucket,
.vcs-provider-picker:has(input[name="vcs_provider"][value="gitlab"]:checked) .vcs-connect-github { display: none; }

/* Guided PAT help, gated to the chosen provider (PRD #256 P1,
   crit:e5a30a092323). The guides sit inside the token disclosure, beneath the
   fields they explain, so the rule above cannot reach them: it is scoped to
   .vcs-provider-picker and the guides are outside it. .vcs-connect-face wraps
   the chooser and the disclosure together so a single :has() can see both the
   checked radio and the guide it should hide.

   Same direction as that rule — hide the UNCHOSEN guide rather than reveal the
   chosen one. A browser without :has() then shows both guides, which is merely
   verbose; written the other way it would hide both, and a user on a server
   with no OAuth configured would face a token field with no instructions at
   all, which is precisely the state this criterion exists to end.

   This gate is cosmetic and the server-side one is authoritative: with GitLab
   OAuth configured the GitLab guide is never emitted, so there is nothing here
   to hide. Never let this rule become the only thing keeping a guide off the
   page. */
.vcs-connect-face:has(input[name="vcs_provider"][value="github"]:checked) .vcs-guide-bitbucket,
.vcs-connect-face:has(input[name="vcs_provider"][value="github"]:checked) .vcs-guide-gitlab,
.vcs-connect-face:has(input[name="vcs_provider"][value="bitbucket"]:checked) .vcs-guide-gitlab,
.vcs-connect-face:has(input[name="vcs_provider"][value="bitbucket"]:checked) .vcs-guide-github,
.vcs-connect-face:has(input[name="vcs_provider"][value="gitlab"]:checked) .vcs-guide-bitbucket,
.vcs-connect-face:has(input[name="vcs_provider"][value="gitlab"]:checked) .vcs-guide-github { display: none; }

/* Version Control connected state and the collapsed token path (PRD #256 P0,
   crit:09b05271ef9c). Before a provider is connected the section is the chooser
   plus a Connect action; the repo / instance-URL / token inputs live inside a
   closed <details> so they are not shown, while remaining REACHABLE on a server
   with neither provider's OAuth configured — where they are the only way to
   create a binding until the guided PAT flow lands. Once connected the whole
   disclosure is gone and this status block stands where the chooser was. */
.vcs-token-connect { margin-top: 1rem; }
.vcs-token-connect > summary { cursor: pointer; font-size: 0.85rem; color: var(--ballast); }
.vcs-token-connect[open] > summary { margin-bottom: 0.85rem; }
.vcs-connected-status { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.15rem; }
.vcs-connected-provider { font-weight: 600; }

/* Connection-health panel (PRD #799 P1). One row per check; the outcome is the
   badge's TEXT first and its colour second, so the three states survive a
   monochrome or colour-blind reading. "unknown" is deliberately neutral, not
   amber: could-not-check is the absence of an answer, never a warning about
   the connection. */
.vcs-health-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; margin-bottom: 0.6rem; }
.vcs-health-head h3 { font-size: 0.95rem; margin: 0; }
.vcs-health-rows { list-style: none; margin: 0; padding: 0; }
.vcs-health-row { display: flex; align-items: baseline; gap: 0.75rem; padding: 0.5rem 0; border-top: 1px solid var(--rule); }
.vcs-health-row:first-child { border-top: 0; }
.vcs-health-badge { flex: 0 0 7.5rem; font-size: 0.68rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ballast); }
.vcs-health-ok .vcs-health-badge { color: var(--signal-green); }
.vcs-health-failing .vcs-health-badge { color: var(--signal-red); }
.vcs-health-body { flex: 1 1 auto; min-width: 0; font-size: 0.85rem; }
.vcs-health-title { display: block; font-weight: 600; }
.vcs-health-sentence { color: var(--steel); overflow-wrap: anywhere; }
.vcs-health-action { margin-left: 0.4rem; white-space: nowrap; }
.vcs-health-foot { margin: 0.6rem 0 0; }
/* The App card's suspended / removed installation states (same PRD). */
.vcs-app-state-badge { color: var(--signal-red); border-color: var(--signal-red); margin-right: 0.3rem; }

/* PRD #256 P2 — the repository picker that replaced the free-text owner/name
   box on a connected source. Rows are <label>-wrapped radios so the whole row
   is a hit target and a pick needs no javascript. The list is bounded in height
   rather than by row count: the page size is a server concern, and capping the
   rendered height keeps the settings form scannable whatever it returns. */
.vcs-repo-results { margin-top: 0.5rem; max-height: 21rem; overflow-y: auto; border: 1px solid var(--rule); border-radius: 4px; }
.vcs-repo-results:empty { display: none; }
.vcs-repo-row { display: flex; align-items: baseline; gap: 0.5rem; padding: 0.45rem 0.6rem; cursor: pointer; border-bottom: 1px solid var(--rule); font-size: 0.85rem; }
.vcs-repo-row:last-child { border-bottom: none; }
.vcs-repo-row:hover { background: var(--rail); }
.vcs-repo-row:has(input:checked) { background: var(--rail); }
.vcs-repo-name { font-family: var(--mono, monospace); }
.vcs-repo-desc { color: var(--ballast); font-size: 0.78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vcs-repo-badge { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ballast); border: 1px solid var(--rule); border-radius: 3px; padding: 0 0.3rem; }
.vcs-repo-note { margin: 0; padding: 0.55rem 0.6rem; }
.vcs-repo-note-error { color: var(--signal-red, #b3261e); }
.vcs-repo-pager { display: flex; align-items: center; gap: 0.6rem; padding: 0.45rem 0.6rem; border-top: 1px solid var(--rule); }

/* The picker's COLLAPSED face — once a repository is chosen the searchable list
   is a finished job, so it folds into the chosen path plus a "Change
   repository" disclosure. A <details>, not a conditional render: a closed
   <details> hides its contents but still SUBMITS the controls inside, so the
   pinned current radio keeps travelling with the settings form. Rendering the
   picker away instead would post a blank vcs_repo, which saveVCSBinding reads
   as "delete the binding, tokens included".

   The marker is suppressed and the CTA carries the affordance, because the
   summary is a row of content (the bound path) with an action on its right, not
   a bare twisty. The two CTA labels swap on [open] — universally supported,
   unlike the :has() gates above — so the control never says "Change
   repository" while the list it opens is already open. */
.vcs-repo-change { margin-top: 0.15rem; border: 1px solid var(--rule); border-radius: 4px; }
.vcs-repo-change > summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.5rem 0.6rem;
  cursor: pointer;
  list-style: none;
  font-size: 0.85rem;
}
.vcs-repo-change > summary::-webkit-details-marker { display: none; }
.vcs-repo-change > summary:hover { background: var(--rail); }
.vcs-repo-change > summary:focus-visible { outline: 2px solid var(--rail-blue); outline-offset: -2px; }
.vcs-repo-change-cta { font-size: 0.78rem; color: var(--rail-blue); white-space: nowrap; text-decoration: underline; }
.vcs-repo-change:not([open]) .vcs-repo-change-close,
.vcs-repo-change[open] .vcs-repo-change-open { display: none; }
.vcs-repo-change[open] > summary { border-bottom: 1px solid var(--rule); }
.vcs-repo-change-body { padding: 0.6rem; }
/* The nested results list already draws its own frame; inside the disclosure
   that would be a border inside a border one row deep. */
.vcs-repo-change-body .vcs-repo-results { margin-top: 0.5rem; max-height: 17rem; }

/* The repo→project routing block, folded into the Version control card (was a
   standalone "GitHub repositories" card). Rows sit in a plain list; the Remove
   form is pushed to the right by its own auto margin so a long path never
   collides with the button. */
.vcs-mapping-list { list-style: none; margin: 0 0 0.85rem; padding: 0; }
.vcs-mapping-list > li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.85rem;
}
.vcs-mapping-list > li:last-child { border-bottom: none; }
.vcs-mapping-list code { font-size: 0.85rem; }
.vcs-mapping-add > summary { cursor: pointer; font-size: 0.85rem; color: var(--ballast); }

/* ============================================================================
   PRD #270 P0 — the sticky-note criterion board on the PRD page.
   Column = phase (said once, in the header). Paper colour = criterion state.
   Colour is NEVER the only state signal: every note also carries a per-state
   glyph and an uppercase text label, so the board reads correctly without
   colour vision and in a forced-colours mode that drops backgrounds.
   No JavaScript participates in any of this — the hover reveal is :hover /
   :focus-within and the click/tap reveal is <details>, so with scripting off
   the board still shows every criterion's full text.
   ========================================================================== */
.crit-board-empty { font-size: 0.85rem; color: var(--ballast); padding: 1.25rem 0; }

/* Stats header (PRD #309 P0) — the board's notes counted by state, over the
   criterion total. Each tally takes its state's own ink from the --crit-ink the
   .crit-state-* classes below already define, so the header's colours and the
   paper under it are one palette rather than two that drift.
   Only ACTIVE states render (BoardStats.Active), so the row's child count is
   however many states currently hold criteria — style it as a wrapping flex
   row, never against a fixed column count. */
.crit-board-stats { padding: 1rem 1.25rem; margin-bottom: 1rem; }
.crit-board-stats-metrics { display: flex; gap: 1.25rem; flex-wrap: wrap; margin-bottom: 0.85rem; }
.crit-board-stat { display: flex; flex-direction: column; gap: 0.1rem; }
.crit-board-stat-value {
  font-size: 1.35rem;
  font-weight: 600;
  color: var(--crit-ink, var(--steel));
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}
.crit-board-stat-total { font-size: 0.85rem; font-weight: 500; color: var(--ballast); }
.crit-board-stat-label { font-size: 0.72rem; color: var(--ballast); text-transform: uppercase; letter-spacing: 0.03em; }

.crit-board-legend {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 1rem;
  font-size: 0.72rem; color: var(--ballast); margin: 0 0 0.9rem;
}
.crit-board-legend-key { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.03em; color: #94a3b8; }
.crit-board-legend-item { display: inline-flex; align-items: center; gap: 0.35rem; }
.crit-board-legend-swatch { width: 9px; height: 9px; border-radius: 50%; background: var(--crit-ink, var(--ballast)); flex: none; }

/* The board's header strip: the legend on the left, the layout toggle on the
   right (PRD #609 P0). */
.crit-board-bar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.crit-board-bar .crit-board-legend { margin-bottom: 0; flex: 1 1 auto; }

.crit-board { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1rem; align-items: start; }

/* ── The combined list (PRD #609 P0) — the same criteria as one dense row
   each, phase as a leading badge. The row inherits --crit-paper / --crit-ink
   from the shared .crit-state-* map, so paper still conveys state here and the
   two layouts cannot drift into different colour vocabularies. */
.crit-list { border: 1px solid var(--line, #e9edf2); border-radius: 12px; overflow: hidden; background: var(--surface, #fff); }
.crit-row {
  display: flex; align-items: flex-start; gap: 0.75rem;
  padding: 0.6rem 0.85rem; border-top: 1px solid var(--line, #e9edf2);
  color: inherit; transition: background 0.12s;
}
.crit-row:first-child { border-top: 0; }
.crit-row:hover { background: var(--crit-paper, #f8fafc); text-decoration: none; }
.crit-row-phase {
  flex: none; min-width: 2.4rem; text-align: center;
  font-family: var(--font-mono); font-size: 0.68rem; font-weight: 700;
  padding: 0.15rem 0.4rem; border-radius: 5px;
  background: var(--crit-col-accent, var(--ballast)); color: var(--surface, #fff);
}
.crit-row[class*="crit-state-"] .crit-row-phase { background: var(--crit-ink, var(--ballast)); }
.crit-row-phase-ungraded { opacity: 0.7; }
.crit-row-text { flex: 1 1 auto; min-width: 0; font-size: 0.82rem; line-height: 1.45; color: #26313f; }
.crit-row-state {
  flex: none; display: flex; align-items: center; gap: 0.35rem;
  font-family: var(--font-mono); font-size: 0.58rem; letter-spacing: 0.04em;
  text-transform: uppercase; font-weight: 600; color: var(--crit-ink, rgba(15, 23, 42, 0.55));
}
.crit-row-state .crit-reach { margin-left: 0; }
@media (max-width: 720px) {
  .crit-row { flex-wrap: wrap; }
  .crit-row-text { flex-basis: 100%; }
}
.crit-col { background: #f6f8fc; border: 1px solid #e9edf2; border-radius: 12px; overflow: hidden; }
.crit-col[data-phase="P0"] { --crit-col-accent: var(--signal-red); --crit-col-wash: #fef2f2; }
.crit-col[data-phase="P1"] { --crit-col-accent: var(--signal-green); --crit-col-wash: #ecfdf5; }
.crit-col[data-phase="P2"] { --crit-col-accent: var(--rail-blue); --crit-col-wash: #eff6ff; }
.crit-col { --crit-col-accent: var(--ballast); --crit-col-wash: #f1f5f9; }
.crit-col-head {
  display: flex; align-items: center; gap: 0.5rem; padding: 0.7rem 0.8rem 0.6rem;
  background: var(--crit-col-wash); border-bottom: 1px solid #e9edf2; border-top: 3px solid var(--crit-col-accent);
}
.crit-col-tag {
  font-family: var(--font-mono); font-weight: 700; font-size: 0.72rem; color: #fff;
  background: var(--crit-col-accent); border-radius: 5px; padding: 0.1rem 0.45rem; flex: none;
}
.crit-col-title { font-size: 0.85rem; font-weight: 600; color: var(--sleeper); flex: 1; margin: 0; line-height: 1.25; }
.crit-col-count { font-family: var(--font-mono); font-size: 0.72rem; font-weight: 600; color: var(--ballast); flex: none; }
.crit-col-meter { height: 4px; background: #fff; border-bottom: 1px solid #e9edf2; }
.crit-col-meter > i { display: block; height: 100%; background: var(--crit-col-accent); }
.crit-col-desc { font-size: 0.74rem; color: var(--ballast); line-height: 1.45; padding: 0.6rem 0.8rem 0; margin: 0; }
.crit-col-empty { grid-column: 1 / -1; font-size: 0.74rem; color: var(--ballast); opacity: 0.75; margin: 0; }
/* The capped-column footnote (PRD #308 P1). Sits BELOW the notes rather than in
   the head, so the header count stays a bare tally like the other three columns
   and the cap is explained where the reader runs out of notes. */
.crit-col-more { font-size: 0.72rem; color: var(--ballast); opacity: 0.8; font-style: italic; padding: 0 0.9rem 0.9rem; margin: 0; }
.crit-stickies { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 1.15rem 0.9rem; padding: 0.9rem 0.9rem 1.1rem; }

/* One note. The <a> holds the layout slot; its .crit-sticky-face span is the
   paper and is what lifts on hover, so the grid never reflows underneath.
   display:block because an anchor is inline by default and would not take the
   slot's height; the colour/decoration resets keep the paper looking like paper
   rather than like link text (PRD #309 P1). */
.crit-sticky { position: relative; display: block; min-height: 150px; color: inherit; text-decoration: none; }
.crit-sticky:hover { text-decoration: none; }
.crit-tilt-0 { --crit-tilt: -1.4deg; }
.crit-tilt-1 { --crit-tilt: 1.2deg; }
.crit-tilt-2 { --crit-tilt: -0.7deg; }
.crit-tilt-3 { --crit-tilt: 1.5deg; }
.crit-tilt-4 { --crit-tilt: -1.1deg; }
.crit-tilt-5 { --crit-tilt: 0.6deg; }
.crit-sticky-face {
  position: relative; min-height: 150px; display: flex; flex-direction: column;
  padding: 0.9rem 0.75rem 0.7rem; border-radius: 3px; cursor: pointer; list-style: none;
  background: linear-gradient(155deg, var(--crit-paper, #e9edf2) 0%, var(--crit-paper-2, #dde3ea) 100%);
  box-shadow: 0 1px 1px rgba(15, 23, 42, 0.05), 0 7px 14px -6px rgba(15, 23, 42, 0.28);
  transform: rotate(var(--crit-tilt, 0deg));
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.crit-sticky-tape {
  position: absolute; top: -8px; left: 50%; width: 48px; height: 15px;
  transform: translateX(-50%) rotate(-3deg);
  background: rgba(255, 255, 255, 0.42); border: 1px solid rgba(255, 255, 255, 0.6);
  border-radius: 2px; box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12);
}
/* The criterion's human-readable title (criterion_titles): the short summary a
   person scans for, above the clamped text and in the note's own ink so it reads
   as the note's name rather than as more prose. One line, never clamped — the
   write caps it at 40 characters so it never needs to be. The same span serves
   the board note, the list row and the activity note. */
.crit-sticky-title {
  display: block; font-size: 0.86rem; line-height: 1.3; font-weight: 700; color: var(--crit-ink, #26313f);
  margin-top: 0.4rem; overflow-wrap: anywhere;
}
.crit-sticky-title + .crit-sticky-text { margin-top: 0.2rem; }
.crit-row-text .crit-sticky-title { margin-top: 0; margin-right: 0.5rem; display: inline; font-size: 0.84rem; }
/* The snippet IS the full text, clamped. Nothing is truncated server-side, so
   the complete criterion is always in the DOM. */
.crit-sticky-text {
  font-size: 0.78rem; line-height: 1.4; color: #26313f; font-weight: 500; margin-top: 0.4rem;
  display: -webkit-box; -webkit-line-clamp: 5; line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden;
}
.crit-sticky-foot {
  margin-top: auto; padding-top: 0.55rem; display: flex; align-items: center; gap: 0.35rem;
  font-family: var(--font-mono); font-size: 0.58rem; letter-spacing: 0.04em; text-transform: uppercase;
  font-weight: 600; color: var(--crit-ink, rgba(15, 23, 42, 0.55));
}
.crit-sticky-glyph { width: 14px; height: 14px; flex: none; color: var(--crit-ink, currentColor); }
/* PRD #512: a criterion's validation reachability, pushed to the foot's far end.
   The stranded class (no lane can bank a verdict) reads as a warning. */
.crit-reach {
  margin-left: auto; padding: 0 0.3rem; border-radius: 3px; white-space: nowrap;
  border: 1px solid currentColor; opacity: 0.8;
}
.crit-reach-reachable_by_neither { color: #b42318; opacity: 1; }
/* PRD #622: WHICH leg credited the delivery. The two earned legs are quiet — they
   are the ordinary case — while a hand credit is a person's assertion about
   already-merged work and reads as the exception it is, because the whole point
   is that it must never look like a delivery switchyard derived. It sits BEFORE
   .crit-reach's margin-left:auto so the reachability badge keeps the far end. */
.crit-provenance {
  padding: 0 0.3rem; border-radius: 3px; white-space: nowrap;
  border: 1px solid currentColor; opacity: 0.65;
}
.crit-provenance-hand { color: #8a5a00; opacity: 1; font-weight: 700; }
/* The same fact on the criterion page (tiers 2 and 3), where there is room for
   the sentence the badge abbreviates. */
.crit-delivery-provenance { color: var(--ballast); }
.crit-delivery-provenance code { font-size: 0.78rem; }

/* HOVER (pointer enhancement only): lift the face OUT OF FLOW and unclamp it,
   so it expands downward over its neighbours and the grid never reflows. The
   note's own 150px slot stays put underneath. Focus gives a keyboard user the
   same reveal before they activate the note; the note IS the focusable element
   now, so :focus-within is what matches whether focus lands on it or inside it. */
.crit-sticky:hover .crit-sticky-face,
.crit-sticky:focus-within .crit-sticky-face {
  position: absolute; top: 0; left: 0; right: 0;
  transform: rotate(0deg) translateY(-4px) scale(1.02);
  box-shadow: 0 3px 3px rgba(15, 23, 42, 0.06), 0 18px 30px -10px rgba(15, 23, 42, 0.34);
  z-index: 4;
}
.crit-sticky:hover .crit-sticky-text,
.crit-sticky:focus-within .crit-sticky-text {
  -webkit-line-clamp: unset; line-clamp: unset; overflow: visible; display: block;
}
.crit-sticky:focus-visible { outline: 2px solid var(--rail-blue); outline-offset: 3px; }

/* CLICK / TAP path: the note is a link to its criterion's page, upgraded by
   HTMX into #crit-popup. Touch devices have no :hover at all, so this — not the
   hover overlay — is what guarantees the full criterion is reachable by tap and
   by keyboard, with or without JavaScript.

   The host is a native <dialog> opened with showModal() (layout.templ), so the
   backdrop, Esc and focus handling are the platform's; this only styles the
   box. A closed dialog is display:none by the UA sheet — no rule here may set
   display on the bare class, or it would force a closed dialog visible. Padding
   is 0 so a click that lands on the dialog element itself is a backdrop click,
   which the script treats as dismissal. */
.crit-popup {
  padding: 0; border: 0; border-radius: var(--radius); color: inherit;
  background: var(--surface, #fff);
  width: min(920px, calc(100vw - 2rem)); max-height: min(88vh, calc(100% - 2rem));
  overflow: auto; overscroll-behavior: contain;
  box-shadow: 0 24px 60px -20px rgba(15, 23, 42, 0.45);
}
.crit-popup::backdrop { background: rgba(15, 23, 42, 0.5); backdrop-filter: blur(2px); }
.crit-popup .crit-detail-fragment { padding: 1rem 1.25rem 1.25rem; outline: none; }

/* Paper + ink per STATE. The two maps are paired on purpose (design contract
   §2) — a state added to one and not the other silently drops a signal. The ink
   values mirror internal/dashboard/criterion_state.go's criterionStateInk. */
.crit-state-validated   { --crit-paper: #d6f0dc; --crit-paper-2: #bfe6c8; --crit-ink: #047857; }
.crit-state-delivered   { --crit-paper: #fdeac2; --crit-paper-2: #f8d89e; --crit-ink: #b45309; }
.crit-state-outstanding { --crit-paper: #e9edf2; --crit-paper-2: #dbe2ea; --crit-ink: #64748b; }
.crit-state-in_progress { --crit-paper: #d7e6fc; --crit-paper-2: #c0d7fb; --crit-ink: #2563eb; }
.crit-state-blocked     { --crit-paper: #ffdad3; --crit-paper-2: #ffc3b8; --crit-ink: #dc2626; }
.crit-state-deferred    { --crit-paper: #e7ddc7; --crit-paper-2: #d9ccae; --crit-ink: #6b6250; }
/* PRD #328. Failed's rose paper is deliberately near blocked's red without being
   it: both are alarming and a reader should feel that at a glance, while the
   glyph (a struck circle, not an octagon) and the text label carry WHICH alarm.
   In-review's violet is the only attention hue the vocabulary had left, and
   pointedly not in-progress's blue — a judge holding a criterion and a builder
   holding one are different lanes with different remedies. Both hues are
   criterionStateInk verbatim, the same tie the .sb-seg-* rules keep. */
.crit-state-failed      { --crit-paper: #ffd9e0; --crit-paper-2: #fdbecb; --crit-ink: #be123c; }
.crit-state-in_review   { --crit-paper: #e6ddfb; --crit-paper-2: #d3c5f7; --crit-ink: #7c3aed; }
/* The four states PRD #330/#355/#599 added to the vocabulary without a paper
   rule — they fell through to outstanding's grey while their ink and segment
   colours said something else. Each hue is criterionStateInk verbatim;
   TestEveryCriterionStateHasPaper pins that no state is left to the fallback. */
.crit-state-repair_in_flight { --crit-paper: #d4eff5; --crit-paper-2: #b8e3ec; --crit-ink: #0891b2; }
.crit-state-repair_escalated { --crit-paper: #f6d9f8; --crit-paper-2: #efc0f2; --crit-ink: #a21caf; }
.crit-state-refused_attempt  { --crit-paper: #e3dcf5; --crit-paper-2: #cfc3ee; --crit-ink: #5b21b6; }
.crit-state-done_unexecuted  { --crit-paper: #f3e3d0; --crit-paper-2: #ead0b3; --crit-ink: #92400e; }
/* Rework in flight (switchyard-conductor #1514): orange paper, a peach well apart
   from delivered's pale amber (#fdeac2) so the two never read as one tint again. */
.crit-state-rework_in_flight { --crit-paper: #ffd9bf; --crit-paper-2: #ffc39a; --crit-ink: #c2410c; }
.crit-state-deferred .crit-sticky-text { font-style: italic; }

/* The rejection line on a failed note (PRD #328 P1): why the criterion was turned
   down, in brief, under its text and above its state foot.
   Clamped by CSS to two lines rather than truncated in the markup, the same rule
   the note's own text follows — the full reason is in the title attribute and, in
   full and with every earlier rejection, on the criterion page the whole note
   links to. */
.crit-sticky-reject {
  display: block; margin-top: 0.35rem; padding-top: 0.3rem;
  border-top: 1px dashed rgba(190,18,60,0.45);
  font-size: 0.7rem; line-height: 1.3; color: var(--crit-ink, #be123c);
}
.crit-sticky-reject-head {
  display: block; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; font-size: 0.62rem;
}
.crit-sticky-reject-why {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; opacity: 0.92;
}
.crit-sticky-reject-none { font-style: italic; opacity: 0.75; }

/* A fail verdict's class (views_fail_class.templ): a chip colored by GROUP — the
   work is wrong (rebuild: crimson, the rejection's own ink), something else is
   wrong (a rebuild cannot clear it: amber, "look elsewhere"), or not classified
   (muted grey, so the gap is visible but never reads as a finding). The group is
   also spelled in the remedy line beside it, so color is never the only carrier. */
.fail-class-chip {
  display: inline-block; padding: 0.05rem 0.4rem; border-radius: 999px;
  font-size: 0.68rem; font-weight: 600; line-height: 1.4; white-space: nowrap;
  border: 1px solid transparent; vertical-align: baseline;
}
.fail-class-chip.fail-class-work { background: #ffe4e6; color: #9f1239; border-color: #fecdd3; }
.fail-class-chip.fail-class-elsewhere { background: #fef3c7; color: #92400e; border-color: #fde68a; }
.fail-class-chip.fail-class-unclassified { background: transparent; color: #6b7280; border-color: #d1d5db; font-weight: 500; font-style: italic; }
.fail-class-remedy { display: inline; }
.fail-class-remedy-text { margin-left: 0.35rem; }
.fail-class-group-heading { font-weight: 600; }
.crit-sticky-reject .fail-class-remedy {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; margin: 0.15rem 0;
}
.crit-sticky-reject .fail-class-remedy[data-fail-group="elsewhere"] .fail-class-remedy-text { color: #92400e; }
.crit-sticky-reject .fail-class-remedy[data-fail-group="unclassified"] .fail-class-remedy-text { color: #6b7280; }

/* The reason line on a deferred note (PRD #315 P2): why the criterion was scoped
   out, in brief, under its text and above its state foot. The rejection line's
   twin — same clamp, same title-attribute full text, same "the whole note is the
   link to the full record" — in the deferred paper's own muted ink rather than
   the rejection's red, because a deferral is a decision, not a failure. */
.crit-sticky-defer {
  display: block; margin-top: 0.35rem; padding-top: 0.3rem;
  border-top: 1px dashed rgba(107,98,80,0.5);
  font-size: 0.7rem; line-height: 1.3; color: var(--crit-ink, #6b6250);
}
.crit-sticky-defer-head {
  display: block; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; font-size: 0.62rem;
}
.crit-sticky-defer-why {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; opacity: 0.92; font-style: normal;
}
.crit-sticky-defer-none { font-style: italic; opacity: 0.75; }
/* Rework in flight on the delivering PR (switchyard-conductor #1514): advisory,
   so it reads as a note beside the state rather than a second state. */
.crit-sticky-rework {
  display: block; margin-top: 0.35rem; padding-top: 0.3rem;
  border-top: 1px dashed rgba(194,65,12,0.5);
  font-size: 0.7rem; line-height: 1.3; color: var(--crit-ink, #c2410c);
}
.crit-sticky-rework-head {
  display: block; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; font-size: 0.62rem;
}
.crit-sticky-rework-why {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; opacity: 0.92;
}
/* The bead the title dedup matched this criterion to (PRD #831): advisory, like
   the rework line, so it reads as a note beside the state. */
.crit-sticky-covered {
  display: block; margin-top: 0.35rem; padding-top: 0.3rem;
  border-top: 1px dashed currentColor;
  font-size: 0.7rem; line-height: 1.3; opacity: 0.9;
}
.crit-sticky-covered-head {
  display: block; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; font-size: 0.62rem;
}
.crit-sticky-covered-bead { font-family: var(--font-mono, monospace); overflow-wrap: anywhere; }

/* The in-flight working indicator. Rendered only on an in-progress note, so a
   board with no active work emits none of this at all. */
.crit-sticky-working { position: absolute; top: 0.4rem; right: 0.4rem; width: 26px; height: 26px; color: var(--crit-ink, var(--rail-blue)); }
.crit-sticky-working svg { width: 100%; height: 100%; fill: currentColor; }
.crit-work-bar { transform-origin: 50% 100%; animation: crit-work-pump 1.1s ease-in-out infinite; }
.crit-work-bar-2 { animation-delay: 0.18s; }
.crit-work-bar-3 { animation-delay: 0.36s; }
@keyframes crit-work-pump {
  0%, 100% { transform: scaleY(0.55); opacity: 0.55; }
  50%      { transform: scaleY(1);    opacity: 1; }
}
/* Reduced motion keeps the glyph and drops the animation — so the indicator is
   never carried by movement alone. */
@media (prefers-reduced-motion: reduce) {
  .crit-work-bar { animation: none; }
  .crit-sticky-face { transition: none; }
}

/* PRD-wide phase authoring, demoted below the board. The board is the glance;
   this is the edit. Renamed from .crit-board-controls in PRD #309 P2, which
   retired the per-criterion re-grade/defer controls this block also used to
   hold — those moved to the criterion popup, leaving phase authoring alone. */
.prd-phase-settings { margin-top: 1.5rem; border-top: 1px solid #e9edf2; padding-top: 0.75rem; }
.prd-phase-settings-toggle { font-size: 0.8rem; font-weight: 600; color: var(--ballast); cursor: pointer; }
.prd-phase-settings-toggle:hover { color: var(--steel); }
.prd-phase-settings-body { padding-top: 0.9rem; }

/* PRD #274 (crit:7240f6730e94) — the pool/rig dispatch-collision notice, sitting
   between the board and the completion action. It borrows the amber of
   .phase-unmapped rather than inventing a colour: both say the same class of
   thing — the dispatch is in a state a human should reconcile, not an error the
   page hit. Every signal here is carried in WORDS (the lane keys, the statuses,
   the per-row repair line), so the amber is emphasis and never the message. */
.crit-collisions { margin-top: 1.1rem; }
.crit-collisions-head {
  font-size: 0.8rem; color: #92400e; background: #fef3c7; border: 1px solid #fde68a;
  border-radius: 4px; padding: 0.5rem 0.7rem; margin: 0 0 0.6rem; line-height: 1.45;
}
.crit-collisions-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.crit-collision { border: 1px solid #fde68a; border-left: 3px solid var(--signal-amber); border-radius: 6px; padding: 0.55rem 0.7rem; background: #fffbeb; }
.crit-collision-text { margin: 0 0 0.35rem; font-size: 0.82rem; font-weight: 600; color: var(--steel); line-height: 1.4; }
.crit-collision-lanes { display: flex; flex-wrap: wrap; gap: 0.3rem 1.1rem; margin: 0 0 0.35rem; }
.crit-collision-lane { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 0.35rem; font-size: 0.74rem; color: var(--ballast); }
.crit-collision-lane-key {
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.05em; text-transform: uppercase;
  color: #92400e; background: rgba(146, 64, 14, 0.1); border-radius: 3px; padding: 0.05rem 0.3rem;
}
.crit-collision-lane code { font-family: var(--font-mono); font-size: 0.9em; color: var(--steel); }
.crit-collision-state { font-family: var(--font-mono); font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.03em; color: #92400e; }
.crit-collision-who { color: var(--steel); }
.crit-collision-note { margin: 0 0 0.3rem; font-size: 0.74rem; color: #92400e; line-height: 1.45; }
.crit-collision-label { margin: 0; }
.crit-collision-label code { font-family: var(--font-mono); font-size: 0.66rem; color: #94a3b8; }

/* ============================================================================
   PRD #300 P0 (crit:9800e63d9ef5) — the human-task NOTEPAD SHEET.

   Two tiers. The <summary> is the face you glance at; the sibling div is the
   sheet you work on. The reveal is the native <details> toggle and nothing
   else — no script participates, so click, tap and keyboard all reach the body
   with JavaScript off. The clamp below is presentational ONLY: the full title
   and the full description are always in the DOM, never truncated server-side.

   This borrows .crit-sticky's grammar (PRD 270) rather than extending it: a
   criterion is one sentence and fits a square note; a human task runs a median
   of 2,960 chars, so this is a ruled pad — margin rule, spiral, taller than
   wide — and the two must not read as the same object.

   Class names are `task-`-prefixed on purpose. The prototype's bare `.pad` /
   `.sheet-*` would be a collision risk in a stylesheet this size.
   ========================================================================= */
.task-pad { position: relative; margin-bottom: 0.85rem; }

/* Tier 1 — the face. */
.task-pad-face {
  position: relative; display: block; cursor: pointer; list-style: none;
  padding: 1.15rem 0.9rem 0.75rem 1.5rem; border-radius: 2px 2px 5px 5px;
  /* The paper is a pair of custom properties so an age band (below) can retint the
     sheet by overriding two colours, rather than restating the whole background
     stack — the ruling, the angle and the positions stay declared exactly once. */
  --task-paper-top: #fbfcfe;
  --task-paper-bottom: #eef2f7;
  background:
    repeating-linear-gradient(to bottom, transparent 0 22px, rgba(37, 99, 235, 0.07) 22px 23px),
    linear-gradient(158deg, var(--task-paper-top) 0%, var(--task-paper-bottom) 100%);
  background-position: 0 2.3rem, 0 0;
  box-shadow: 0 1px 1px rgba(15, 23, 42, 0.06), 0 8px 16px -7px rgba(15, 23, 42, 0.3);
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.task-pad-face::-webkit-details-marker { display: none; }
.task-pad-face::marker { content: ""; }
/* The red margin rule of a ruled pad. */
.task-pad-face::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 1.1rem;
  width: 1px; background: rgba(220, 38, 38, 0.28);
}
.task-pad-spiral {
  position: absolute; top: 0.4rem; left: 0; right: 0; height: 9px;
  display: flex; justify-content: center; gap: 8px; pointer-events: none;
}
.task-pad-spiral i {
  width: 5px; height: 5px; border-radius: 50%; background: rgba(15, 23, 42, 0.13);
  box-shadow: inset 0 1px 1px rgba(15, 23, 42, 0.2), 0 1px 0 rgba(255, 255, 255, 0.55);
}
/* The glance tier's clamp. Released on open and on hover/focus, exactly as the
   criterion board releases its own — same grammar, different object. */
.task-pad-title {
  font-size: 0.85rem; line-height: 1.42; font-weight: 600; color: #26313f; margin: 0 0 0.5rem;
  display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3;
  -webkit-box-orient: vertical; overflow: hidden;
}
/* PRD #300 P1 (crit:ba88d78997b1) — the STATUS axis: the badge's colour paired with a
   shape and an uppercase word, so no task state is carried by colour alone.

   The uppercase is declared HERE, on the meta row, and deliberately NOT on .status-badge.
   That class is shared by every status pill in the dashboard — the PRD list, the roadmap,
   the agent roster — so uppercasing it at its own rule would silently restyle surfaces
   this PRD never touched. Scoping it to .task-pad-meta keeps the change inside the board
   that asked for it, and the inherited text-transform reaches the badge anyway.

   Saying it in CSS rather than uppercasing in Go also keeps ONE label string: the sheet
   and the Filed stack both render humanTaskStatusLabel, and .filed-status-label carries
   its own matching uppercase. A Go-side ToUpper would have forked that string into a
   shouted copy and a quiet one, and the two could then drift.

   The assignee opts back out below: it is a person's name, which is an identity rather
   than a state, and the three signals this rule exists to strengthen are status, age and
   PRD — not who is holding the sheet. */
.task-pad-meta {
  display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap;
  text-transform: uppercase; letter-spacing: 0.045em;
}
/* Matches .crit-sticky-glyph's sizing so the two boards' marks read at one weight; the
   shape comes from humanTaskStatusGlyph and the colour from the badge beside it. */
.task-pad-glyph { width: 13px; height: 13px; flex: none; color: var(--ballast); }
.task-pad-assignee {
  margin-left: auto; font-size: 0.72rem; color: var(--ballast);
  font-variant-numeric: tabular-nums;
  text-transform: none; letter-spacing: normal;
}
/* The face's single primary verb (crit:810dbd22c7b9) — the ONLY control on the resting
   surface, so a glance can act without opening and without cancel ever being one stray
   click away.

   Right-aligned on its own line rather than absolutely positioned: the title above is
   clamped, not truncated to a known width, so a stamp in the corner would eventually
   land on top of one. Being interactive content inside <summary> it keeps its own hit
   target; the click that submits also toggles the pad, which is harmless because the
   POST redirects and the toggle dies with the page. */
.task-pad-verb { display: block; margin: 0.55rem 0 0 auto; font-size: 0.78rem; }

/* PRD #300 P1 (crit:739262a9ea52) — the AGE axis: paper tint plus its text twin.

   The tint says "how long has this waited" at a glance; .task-pad-age says the same
   wait in words, because a signal carried only by colour is a signal some readers
   never receive. The bands themselves are derived, not decorated: fresh/aging split
   at half the staleness window and aging/stale at the window itself, so the reddest
   paper on the board is exactly the set the staleness sweep surfaces. Retune
   SWITCHYARD_HUMAN_TASK_STALENESS_DAYS and the papers follow — see
   human_task_age_band.go, which owns the arithmetic.

   Only LIVE sheets are tinted. Terminal work carries no band class and so keeps the
   default paper declared above, which is why there is no "filed" rule to undo one. */
.task-pad-age {
  font-size: 0.72rem; color: var(--ballast); font-variant-numeric: tabular-nums;
}
/* Fresh is the default paper, restated so the three bands read as one set here
   rather than as two tints and an absence. */
.task-pad-age-fresh .task-pad-face { --task-paper-top: #fbfcfe; --task-paper-bottom: #eef2f7; }
.task-pad-age-aging .task-pad-face { --task-paper-top: #fffdf3; --task-paper-bottom: #f9efd2; }
.task-pad-age-stale .task-pad-face { --task-paper-top: #fff8f5; --task-paper-bottom: #f8ded5; }
/* The margin rule darkens with the paper so an aged sheet reads as one object rather
   than as fresh stationery someone spilled tea on. */
.task-pad-age-aging .task-pad-face::after { background: rgba(180, 83, 9, 0.34); }
.task-pad-age-stale .task-pad-face::after { background: rgba(185, 28, 28, 0.46); }
/* Aged paper carries its own age word darker, so the two halves of the signal agree
   in weight instead of a loud sheet whispering its wait. */
.task-pad-age-aging .task-pad-age { color: #92400e; }
.task-pad-age-stale .task-pad-age { color: #b91c1c; font-weight: 600; }
.task-pad-face:hover { transform: translateY(-3px); box-shadow: 0 3px 4px rgba(15, 23, 42, 0.07), 0 18px 30px -12px rgba(15, 23, 42, 0.36); }
.task-pad-face:focus-visible { outline: 2px solid var(--rail-blue); outline-offset: 3px; }
.task-pad[open] .task-pad-face { border-radius: 2px 2px 0 0; transform: none; }
.task-pad[open] .task-pad-title,
.task-pad:hover .task-pad-title,
.task-pad:focus-within .task-pad-title {
  -webkit-line-clamp: unset; line-clamp: unset; overflow: visible; display: block;
}

/* Tier 2 — the opened sheet: the working surface. */
.task-pad-sheet {
  position: relative; z-index: 3; padding: 0.9rem 1rem 1rem;
  background: var(--surface); border: 1px solid #e9edf2; border-top: none;
  border-radius: 0 0 6px 6px; font-size: 0.8rem;
}
.task-sheet-label {
  font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--ballast); margin: 0 0 0.3rem; font-weight: 600;
}
.task-sheet-body { color: var(--ballast); line-height: 1.55; margin: 0 0 0.9rem; max-width: 72ch; }
.task-sheet-sep { border: none; border-top: 1px solid #e9edf2; margin: 0.9rem 0; }
.task-sheet-tl { list-style: none; margin: 0 0 0.9rem; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
.task-sheet-tl li { display: flex; gap: 0.45rem; align-items: baseline; font-size: 0.75rem; color: var(--ballast); }
.task-tl-kind {
  font-family: var(--font-mono); font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.04em;
  background: var(--page); border: 1px solid #e9edf2; border-radius: 3px;
  padding: 0.05rem 0.3rem; color: var(--steel); flex: none;
}
.task-tl-detail { color: var(--ballast); }
.task-tl-when { margin-left: auto; font-variant-numeric: tabular-nums; flex: none; }
.task-sheet-actions {
  display: flex; gap: 0.8rem; flex-wrap: wrap; align-items: flex-start;
  border-top: 1px solid #e9edf2; padding-top: 0.7rem;
}

/* The demoted destructive action (PRD #300 P1, crit:810dbd22c7b9). Cancel is lifted out
   of .task-sheet-actions entirely rather than merely restyled inside it, because the
   PRD's goal is that it stop sitting CO-EQUAL with the constructive verbs — a shared
   flex row makes them peers however the buttons are painted. Its own band, below them,
   behind a dashed rule that reads as a threshold rather than another section. */
.task-sheet-danger {
  display: flex; margin-top: 0.7rem; padding-top: 0.7rem;
  border-top: 1px dashed #e2c9c9;
}

/* Movement is an enhancement, never a signal: the lift goes, the tiers stay. */
@media (prefers-reduced-motion: reduce) {
  .task-pad-face { transition: none; }
  .task-pad-face:hover { transform: none; }
}

/* PRD #300 P0 (crit:af410199c113) — the Filed stack: done and cancelled human tasks
   collapsed out of the live flow rather than competing with it for vertical space.

   DESATURATED, concretely: every colour here is a neutral slate from the palette's
   grey ramp. The live board says "done" in --signal-green and "cancelled" in
   --signal-red; the same two states read here in --ballast and slate-400, so finished
   work is legible without pulling the eye the way live work must. That is the whole
   difference between this stack and .blueprint-row above — same information, no signal
   colour, a third of the height.

   The .filed-* namespace is deliberately distinct from the .task-pad-* / .task-sheet-*
   classes the sibling notepad-sheet criterion introduces into this same file, so the
   two surfaces cannot silently restyle one another. */
/* PRD #300 P2 (crit:75f650fc4a50) — the raise form, demoted out of the top-of-page slot
   into a collapsed disclosure so the board owns the first screen.

   It keeps .card, so it still reads as the page's one authoring surface rather than as a
   third stack of finished work; only its state and its position changed. The rules here do
   the two things .card cannot: make the closed summary read as a control, and stop the
   heading's margin from opening a gap above a body that is not there yet.

   Distinct from .filed-stack below on purpose, though both are collapsed <details> on this
   page: that one is a desaturated archive of work already over, this one is a live control.
   Sharing a class would make either restyle the other. */
.raise-task { padding: 0.75rem 1.25rem; margin-top: 1.5rem; }
.raise-task-head {
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  padding: 0.15rem 0;
}
.raise-task-head:hover { color: var(--steel); }
/* Only once opened: closed, the summary is the whole component and a trailing margin
   would be dead space under a one-line control. */
.raise-task[open] .raise-task-head { margin-bottom: 0.6rem; }

.filed-stack { margin-top: 1rem; }
.filed-stack-head {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ballast);
  cursor: pointer;
  padding: 0.3rem 0;
}
.filed-stack-head:hover { color: var(--steel); }
.filed-stack-count { font-weight: normal; }
.filed-list {
  list-style: none;
  margin: 0.4rem 0 0;
  padding: 0;
  border-top: 1px solid #e9edf2;
}
/* One line, and it stays one line: min-width:0 lets the title ellipsis inside a flex
   row (without it a long title refuses to shrink and pushes the outcome off the edge). */
.filed-row {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  min-width: 0;
  padding: 0.35rem 0.4rem;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--ballast);
  border-bottom: 1px solid #f1f5f9;
}
.filed-row:last-child { border-bottom: none; }
/* Undo on a filed row: a closed disclosure whose summary is one quiet word, so the row
   stays one line until someone asks to reopen. Opened, the form wraps under the row. */
.filed-row { flex-wrap: wrap; }
.filed-undo { flex: none; margin-left: auto; }
.filed-undo-head {
  cursor: pointer;
  font-size: 0.72rem;
  color: #94A3B8;
  list-style: none;
}
.filed-undo-head::-webkit-details-marker { display: none; }
.filed-undo-head:hover { color: var(--steel); }
.filed-undo-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  align-items: center;
  margin-top: 0.3rem;
}
.filed-undo-form input { font-size: 0.78rem; width: 13rem; }
.filed-undo-form .btn { font-size: 0.78rem; }
.filed-undo-note { font-size: 0.72rem; color: var(--ballast); }
.filed-glyph { flex: none; font-size: 0.75rem; }
/* Both marks take the same muted slate: the glyph SHAPE distinguishes done from
   cancelled, so tinting them apart would reintroduce the signal colour this stack
   exists to drop — and would fail anyone reading the shape rather than the hue. */
.filed-glyph-done,
.filed-glyph-cancelled { color: #94A3B8; }
.filed-status-label {
  flex: none;
  min-width: 5.5rem;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #94A3B8;
}
/* The title takes the slack and truncates first — it is the row's identity, but a long
   one must not evict the outcome beside it. */
.filed-title {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: normal;
  color: var(--ballast);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.filed-title p { display: inline; margin: 0; }
/* Clamped so a long cancel reason cannot re-inflate the row back to card weight. */
.filed-outcome {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 45%;
  text-align: right;
  font-size: 0.75rem;
  color: #94A3B8;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── Project Activity board (PRD #308 P1, crit:8ef3af63bb3b) ──────────────────
   Five columns — todo → doing → review → rejected → done — reusing the PRD
   criterion board's .crit-board / .crit-col / .crit-stickies shell and the
   project page's .sb spec box. Nothing here restyles either; these rules only
   add what a PIPELINE needs that a set of phase columns does not.

   The board fixes FIVE columns rather than inheriting .crit-board's
   auto-fit reflow. On a phase board the columns are a set and any wrap is
   harmless; here they are a sequence, and a wrapped "done" landing under "todo"
   would read as a step backwards. Below the breakpoint they stack in order,
   which keeps the sequence readable rather than scrambling it into two rows.
   The count here MUST match ActivityColumnKey's constants (activity_board.go)
   — a column added in Go without touching this rule wraps onto a second row. */
.activity-board { grid-template-columns: repeat(5, minmax(0, 1fr)); margin-bottom: 1.5rem; }
@media (max-width: 900px) { .activity-board { grid-template-columns: 1fr; } }

/* Per-stage accent, mirroring how .crit-col tints per phase. The ramp runs cool
   → warm → rose → green so a reader can tell the stage from the column edge
   alone, with the stage name still spelled in the header (colour is never the
   only signal). rejected takes the failed sticky's rose (criterionStateInk),
   NOT blocked's red — same distinction the note glyphs draw. */
.activity-col[data-stage="todo"]   { --crit-col-accent: var(--ballast);     --crit-col-wash: #f1f5f9; }
.activity-col[data-stage="doing"]  { --crit-col-accent: var(--rail-blue);   --crit-col-wash: #eff6ff; }
.activity-col[data-stage="review"] { --crit-col-accent: var(--signal-amber, #d97706); --crit-col-wash: #fffbeb; }
.activity-col[data-stage="rejected"] { --crit-col-accent: #be123c; --crit-col-wash: #fff1f2; }
.activity-col[data-stage="done"]   { --crit-col-accent: var(--signal-green); --crit-col-wash: #ecfdf5; }

/* The todo column stacks spec boxes vertically: it is a build QUEUE, and a
   queue's order is its meaning, so the boxes never sit side by side. */
.activity-specs { display: flex; flex-direction: column; gap: 0.55rem; padding: 0.9rem; }
.activity-specs .sb { width: 100%; }

/* The working-now strip above the doing column. Busiest agent first, count on
   the chip — one agent holding several at once is the claim leak the board
   exists to surface, so the number is part of the chip rather than something a
   reader has to tally from the notes below. */
.activity-working-now { display: flex; flex-wrap: wrap; gap: 0.35rem; padding: 0 0.9rem 0.6rem; }
.activity-worker {
  display: inline-flex; align-items: center; gap: 0.3rem;
  background: #fff; border: 1px solid #dbe3ec; border-radius: 999px;
  padding: 0.12rem 0.5rem; font-size: 0.68rem; color: var(--ballast);
}
.activity-worker-name { font-weight: 600; max-width: 12rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.activity-worker-count {
  min-width: 1.15rem; text-align: center; border-radius: 999px;
  background: var(--rail-blue); color: #fff; font-weight: 700; font-size: 0.62rem; padding: 0 0.25rem;
}

/* The PRD label on a note's face. A project board's notes come from many PRDs,
   so the note says which — small and quiet, above the criterion text, because
   it is context rather than the content. */
.activity-note-prd {
  display: block; font-size: 0.62rem; font-weight: 700; letter-spacing: 0.02em;
  color: var(--ballast); opacity: 0.75; margin-bottom: 0.25rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Who holds a doing note. Rides the note's foot beside the state word, so
   "in progress" and the name that owns it read as one statement. */
.activity-note-holder {
  margin-left: auto; font-size: 0.6rem; font-weight: 600; color: var(--ballast);
  max-width: 8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* A delivery credited by a person rather than derived from the pull request
   (PRD #622). It is an outline chip, not a filled one: the note's paper already
   carries the criterion's STATE, and a filled badge here would read as a second
   state and out-shout it. The marker qualifies how the delivery was attributed,
   which is a footnote on the note, not a competing headline. */
.activity-note-credit {
  font-size: 0.55rem; font-weight: 700; letter-spacing: 0.02em;
  color: var(--ballast); border: 1px solid currentColor; border-radius: 0.2rem;
  padding: 0 0.2rem; white-space: nowrap; opacity: 0.75;
}

/* ── Settings field metadata (PRD #381, crit:bc69c7c91866) ────────────────────

   A registered field's consequence statement, rendered inline under its help
   text and tagged with how much changing it costs.

   The severity is carried by a left border and the tag's color, not by a filled
   background: the consequence sits directly under a .form-hint inside a
   .form-group, and a filled block there would out-shout the control it
   describes on a page where most fields are operational. The point is to make
   the DESTRUCTIVE ones separable at a glance from the cosmetic ones, which a
   3px rule in the palette's red does without turning the page into an alert
   list.

   Severity is also spelled in the tag's TEXT ("Destructive"), never in color
   alone, so the distinction survives a color-blind reader and a monochrome
   print. */
.field-consequence {
  display: block;
  margin-top: 0.3rem;
  padding: 0.3rem 0 0.3rem 0.55rem;
  border-left: 3px solid var(--rule);
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--ballast);
}
.field-consequence-tag {
  display: inline-block;
  margin-right: 0.35rem;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: baseline;
  color: var(--ballast);
}
.field-consequence-text { display: inline; }
.field-consequence-text code {
  background: var(--coal);
  border-radius: 3px;
  padding: 0 0.2em;
}
.field-consequence-cosmetic { border-left-color: var(--rule); }
.field-consequence-operational { border-left-color: var(--signal-amber); }
.field-consequence-operational .field-consequence-tag { color: var(--signal-amber); }
.field-consequence-destructive { border-left-color: var(--signal-red); }
.field-consequence-destructive .field-consequence-tag { color: var(--signal-red); }
/* The notes list carries the same consequence lines for controls whose markup a
   section does not own (the shared invite/grant cards, the skills toggle, the
   danger-zone buttons), so it restyles only the wrapper — never the line. */
.field-notes { list-style: none; padding: 0; margin: 0.5rem 0 0; }
.field-notes li { margin: 0 0 0.55rem; }
.field-notes li:last-child { margin-bottom: 0; }
.field-notes-label { display: block; font-size: 0.82rem; font-weight: 600; color: var(--steel); }
.field-notes .field-consequence { margin-top: 0.15rem; }

/* Per-field save summary (PRD #381) — the list of what a settings save actually
   changed, rendered inside the success banner (or, for a half-applied save,
   inside the error banner under settingsPartialSaveCaption).

   It reuses .field-consequence-tag for the severity word rather than styling a
   second one, so a Destructive change reads with the same weight in the record
   of a save as it does on the control that caused it. Only the ROW is new. */
.save-summary { list-style: none; padding: 0; margin: 0.5rem 0 0; }
.save-summary-caption {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.82rem;
  font-weight: 600;
}
.save-summary-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem 0.5rem;
  padding: 0.28rem 0 0.28rem 0.55rem;
  border-left: 3px solid var(--rule);
  font-size: 0.8rem;
  line-height: 1.4;
}
.save-summary-row + .save-summary-row { margin-top: 0.2rem; }
.save-summary-label { font-weight: 600; }
.save-summary-values { font-family: var(--mono, ui-monospace, monospace); font-size: 0.76rem; }
/* The old value is de-emphasised and the new one is not: the row is read for
   what the setting IS now, with what it was as the supporting fact. */
.save-summary-from { opacity: 0.7; text-decoration: line-through; }
.save-summary-arrow { margin: 0 0.15rem; opacity: 0.7; }
.save-summary-to { font-weight: 600; }
.save-summary-operational { border-left-color: var(--signal-amber); }
.save-summary-tag-operational { color: var(--signal-amber); }
.save-summary-destructive { border-left-color: var(--signal-red); }
.save-summary-tag-destructive { color: var(--signal-red); }
/* What the change does (PRD #204 P1): the field's registered consequence on its
   own line under the From→To, so the record of a save reads as an outcome. */
.save-summary-consequence {
  flex-basis: 100%;
  font-size: 0.78rem;
  line-height: 1.45;
  opacity: 0.85;
}
.save-summary-consequence p { margin: 0; }

/* The admission behaviour in force (PRD #204 P1), read back beside the field
   that sets it: the statement, then the stored value it was derived from. */
.admission-in-force {
  margin: 0.45rem 0 0;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--rule);
  border-radius: 4px;
  font-size: 0.8rem;
  line-height: 1.45;
}
.admission-in-force-tag {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.7rem;
  margin-right: 0.4rem;
}
.admission-in-force-source {
  display: block;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 0.74rem;
  opacity: 0.75;
}

/* API tokens management tables (account + admin): status chip, the "which
   machine" cell, paused-row dimming, and the trailing actions column. Both
   tables carry .tokens-table (the admin one also .admin-tokens-table) so the
   desktop cell rules and the narrow-screen card layout below serve both. */
.token-status {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.token-status-active  { background: #d1fae5; color: #065f46; }
.token-status-paused  { background: #fef3c7; color: #92400e; }
.token-status-expired { background: #fee2e2; color: #991b1b; }
.token-status-unused  { background: #f1f5f9; color: #475569; }
.token-row-paused td:not(.token-cell-actions) { opacity: 0.65; }
/* Machine + prefix text is unbroken mono (hostnames, "switchyard-mcp/v1.4.0
   (linux/amd64)", IPs): overflow-wrap:anywhere lets it wrap at spaces first and
   only splits a word when nothing else fits, where word-break:break-all would
   chop "switchyard-mcp" mid-word in a tight desktop column. The desktop
   min-width keeps the column from collapsing to one character per line under
   a 10-column admin table; the card layout lifts it. */
.token-machine { font-size: 0.8rem; line-height: 1.35; min-width: 11rem; max-width: 18rem; }
.token-machine-host { font-weight: 600; overflow-wrap: anywhere; }
.token-machine-meta { font-family: var(--font-mono); font-size: 0.72rem; color: var(--ballast); overflow-wrap: anywhere; }
.token-prefix { font-family: var(--font-mono); font-size: 0.75rem; color: var(--ballast); overflow-wrap: anywhere; }
.token-since { font-size: 0.72rem; color: var(--ballast); margin-top: 0.2rem; }
.token-cell-time { font-size: 0.82rem; white-space: nowrap; }
.token-cell-muted { color: var(--ballast); }
.tokens-table .token-cell-actions { width: 1%; white-space: nowrap; text-align: right; }
/* Ten columns compress into one-word-per-line cells at laptop width; a natural
   minimum lets the admin table scroll inside its card (the wrapper is
   overflow-x:auto) instead. Lifted by the card layout below. */
.admin-tokens-table { min-width: 68rem; }
.token-create > summary { list-style: none; }
.token-create > summary::-webkit-details-marker { display: none; }
.token-create > summary::before { content: "\25B8 "; color: var(--ballast); }
.token-create[open] > summary::before { content: "\25BE "; }

/* Tokens tables on narrow screens: a 7–10 column table cannot fit a phone, and
   a sideways-scrolling one hides the Pause control off the right edge. Below
   720px each <tr> becomes a card: the header is a two-column grid row holding
   the label (left) and the status chip (right); every other cell spans the
   card's width and renders its column name — carried on data-label, since
   the <thead> is out of sight — as a small caption; and the actions become a
   row of tap-sized buttons. The 640px .data-table rule (display:block +
   overflow-x) is overridden here because a stacked card must never scroll
   sideways.

   Every table that takes this layout (account tokens, admin tokens, authorized
   apps) carries token-cell-* classes + data-label on its cells AND explicit
   ARIA roles (table/rowgroup/row/columnheader/cell) in its markup: changing
   display on table elements strips their implicit roles in every engine, and
   the roles put them back. For the same reason the <thead> is hidden with the
   sr-only technique rather than display:none — it stays in the accessibility
   tree, so assistive technology keeps the real column-header association and
   the ::before captions remain what they are, a visual aid for sighted users. */
@media (max-width: 720px) {
  .data-table.tokens-table { display: block; overflow: visible; min-width: 0; }
  .tokens-table thead {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
  .tokens-table tbody { display: block; }
  .tokens-table tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 0.75rem;
    row-gap: 0.55rem;
    align-items: start;
    padding: 0.9rem 1rem;
    border-bottom: 1px solid #e5e7eb;
  }
  .tokens-table tr:last-child { border-bottom: 0; }
  .tokens-table td {
    display: block;
    grid-column: 1 / -1;
    width: auto;
    padding: 0;
    border: 0;
    white-space: normal;
    text-align: left;
  }
  .tokens-table td.token-cell-label { grid-column: 1; grid-row: 1; }
  .tokens-table td.token-cell-status { grid-column: 2; grid-row: 1; text-align: right; }
  /* Spelled td.token-cell-actions (0,2,1) so it outranks the desktop
     .tokens-table .token-cell-actions (0,2,0): a media query adds no
     specificity, and the plain `td` reset above (0,1,1) lost to it, leaving a
     1%-wide cell whose buttons overflowed. */
  .tokens-table td.token-cell-actions { width: auto; white-space: normal; text-align: left; }
  .tokens-table td.token-cell[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--ballast);
    margin-bottom: 0.15rem;
  }
  .tokens-table .token-machine { min-width: 0; max-width: none; }
  .tokens-table .row-actions { display: flex; flex-direction: row; align-items: stretch; gap: 0.5rem; width: 100%; margin-top: 0.25rem; }
  .tokens-table .row-actions form { flex: 1; display: flex; }
  .tokens-table .row-actions .btn-link {
    flex: 1;
    min-height: 2.6rem;
    padding: 0.4rem 0.75rem;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    background: white;
    font-size: 0.9rem;
    font-weight: 500;
  }
  .tokens-table .row-actions .btn-link-danger { border-color: #fecaca; }
  .token-create .btn-primary { width: 100%; }
  .token-create form { max-width: none; }
}

/* Project Stats tab (docs/project-stats.md). Cards, tables and chips reuse
   the shared classes; only the picker, the KPI tiles, the chart text and the
   small panels are new. Colour never carries a state alone: the rejected
   series is hatched, deltas print their sign, chips print their word. */
.stats-page { display: flex; flex-direction: column; gap: 1.25rem; padding-bottom: 2rem; }
.stats-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.stats-title { margin: 0; font-size: 1.4rem; font-weight: 700; letter-spacing: -0.01em; }
.stats-sub { margin: 0.15rem 0 0; font-size: 0.88rem; color: var(--ballast); }
.stats-head-meta { font-size: 0.78rem; color: var(--ballast); }
.stats-picker { display: flex; align-items: center; gap: 0.9rem; flex-wrap: wrap; }
.stats-seg { display: inline-flex; flex-wrap: wrap; background: var(--surface); border: 1px solid #d1d5db; border-radius: var(--radius); overflow: hidden; }
.stats-seg-btn { padding: 0 0.85rem; height: var(--control-height); display: inline-flex; align-items: center; font-size: 0.82rem; font-weight: 500; color: var(--steel); border-right: 1px solid var(--rule); text-decoration: none; white-space: nowrap; }
.stats-seg-btn:last-child { border-right: 0; }
.stats-seg-btn:hover { background: var(--coal); text-decoration: none; }
.stats-seg-btn.active { background: var(--steel); color: #fff; }
.stats-date { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.78rem; color: var(--ballast); }
.stats-date-input { width: auto; font-family: var(--font-mono); font-size: 0.82rem; }
.stats-compare { margin-left: auto; display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.82rem; color: var(--steel); text-decoration: none; }
.stats-compare:hover { text-decoration: none; color: var(--rail-blue); }
.stats-check { width: 15px; height: 15px; border: 1px solid #94a3b8; border-radius: 3px; background: #fff; display: inline-block; }
.stats-check.on { background: var(--rail-blue); border-color: var(--rail-blue); box-shadow: inset 0 0 0 2px #fff; }
.stats-kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.85rem; }
.stats-kpi { background: var(--surface); border-radius: var(--radius); box-shadow: 0 1px 3px rgba(0,0,0,0.06); padding: 0.85rem 1rem; display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.stats-kpi-label { font-size: 0.76rem; color: var(--ballast); }
.stats-kpi-value { font-family: var(--font-mono); font-size: 1.6rem; font-weight: 700; line-height: 1.1; color: var(--steel); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.stats-kpi-foot { display: flex; gap: 0.4rem; align-items: baseline; flex-wrap: wrap; min-height: 1rem; }
.stats-kpi-note { font-size: 0.72rem; color: var(--ballast); }
.stats-delta { font-family: var(--font-mono); font-size: 0.72rem; font-weight: 600; }
.stats-delta-up-good, .stats-delta-down-good { color: #047857; }
.stats-delta-up-bad, .stats-delta-down-bad { color: #be123c; }
.stats-delta-flat { color: var(--ballast); }
.stats-grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr)); gap: 1.25rem; align-items: start; }
.stats-card { padding: 1rem 1.15rem; display: flex; flex-direction: column; gap: 0.75rem; overflow-x: auto; }
.stats-card-head { display: flex; flex-direction: column; gap: 0.1rem; }
.stats-card-title { margin: 0; font-size: 0.98rem; font-weight: 600; }
.stats-card-sub { margin: 0; font-size: 0.78rem; color: var(--ballast); }
.stats-chart { width: 100%; height: auto; display: block; overflow: visible; }
.stats-chart-h { max-width: 560px; }
.stats-gridline { stroke: var(--rule); stroke-width: 1; }
.stats-baseline { stroke: #cbd5e1; stroke-width: 1; }
.stats-axis { font-family: var(--font-mono); font-size: 10px; fill: var(--ballast); }
.stats-axis-strong { fill: var(--steel); font-weight: 600; }
.stats-total { font-family: var(--font-mono); font-size: 10px; font-weight: 600; fill: var(--steel); }
.stats-hit { fill: transparent; }
.stats-group:hover .stats-hit { fill: rgba(15, 23, 42, 0.04); }
.stats-legend { display: flex; gap: 0.9rem; flex-wrap: wrap; font-size: 0.74rem; color: var(--ballast); }
.stats-legend-item { display: inline-flex; align-items: center; gap: 0.4rem; }
.stats-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; vertical-align: -1px; }
.stats-swatch.hatched { background-image: repeating-linear-gradient(45deg, rgba(255,255,255,0.55) 0 2px, transparent 2px 4px); }
.stats-empty { margin: 0; font-size: 0.85rem; color: var(--ballast); }
.stats-foot { margin: 0; font-size: 0.76rem; color: var(--ballast); }
.stats-funnel { display: flex; flex-direction: column; gap: 0.5rem; }
.stats-funnel-row { display: grid; grid-template-columns: 170px minmax(0, 1fr) 44px; gap: 0.6rem; align-items: center; font-size: 0.78rem; color: var(--ballast); }
.stats-funnel-row:first-child .stats-funnel-label { color: var(--steel); font-weight: 600; }
.stats-funnel-track { height: 14px; background: var(--coal); border-radius: 3px; overflow: hidden; display: block; }
.stats-funnel-bar { display: block; height: 14px; border-radius: 3px; }
.stats-funnel-bar.tone-steel { background: var(--steel); }
.stats-funnel-bar.tone-good { background: #047857; }
.stats-funnel-bar.tone-bad { background: #be123c; background-image: repeating-linear-gradient(45deg, rgba(255,255,255,0.55) 0 2px, transparent 2px 6px); }
.stats-funnel-n { text-align: right; font-weight: 600; color: var(--steel); }
.stats-lead-median { display: flex; align-items: baseline; gap: 0.6rem; }
.stats-lead-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.stats-lead-list li { display: flex; gap: 0.75rem; padding: 0.4rem 0; border-bottom: 1px solid #f1f1f1; font-size: 0.82rem; }
.stats-lead-list li .mono { min-width: 3.5rem; font-weight: 600; color: var(--steel); }
.stats-lead-title { color: var(--steel); min-width: 0; }
.stats-live { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); gap: 0.6rem; }
.stats-live-cell { display: flex; flex-direction: column; gap: 0.1rem; background: var(--coal); border-radius: var(--radius); padding: 0.65rem 0.8rem; text-decoration: none; color: var(--steel); }
.stats-live-cell:hover { background: #e2e8f0; text-decoration: none; }
.stats-live-n { font-size: 1.3rem; font-weight: 700; }
.stats-live-label { font-size: 0.78rem; font-weight: 600; }
.stats-table th.num, .stats-table td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.stats-table td.mono { font-size: 0.82rem; }
.stats-table td:first-child { white-space: nowrap; }
.stats-table .stats-bad { color: #be123c; font-weight: 600; }
.stats-hot { margin-left: 0.4rem; font-size: 0.66rem; vertical-align: 1px; }
@media (max-width: 960px) { .stats-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) {
  .stats-funnel-row { grid-template-columns: 120px minmax(0, 1fr) 36px; }
  .stats-compare { margin-left: 0; }
  .stats-seg-btn { border-bottom: 1px solid var(--rule); }
}

/* Open-pull-request block inside the Stats page's "Held back right now" panel
   (PRD #551). It sits under the live count cells, so it needs a rule of its own
   rather than the .stats-live grid's. */
.stats-live-prs { margin-top: 0.85rem; padding-top: 0.75rem; border-top: 1px solid var(--rule); }
.stats-subhead { font-size: 0.8rem; font-weight: 600; margin: 0 0 0.5rem; color: var(--steel); }

/* PRD #537 — "checked out" chip: a whole PRD is held by one worker, so the claim
   pool withholds its beads from everyone else. Indigo "reserved" tone, distinct
   from the amber parked (set aside), red blocked (waiting on a dependency) and
   slate archived (retired) chips — a checkout is none of those: the PRD is being
   worked, just by exactly one holder. Sits beside the lifecycle pill, never
   replacing it. */
.prd-checkout-chip { background: #e0e7ff; color: #3730a3; }
.prd-checkout-chip .prd-checkout-left { margin-left: 0.4rem; opacity: 0.75; font-variant-numeric: tabular-nums; }

/* The admin users table's ID column: the account id, shown so an operator can
   read off the number the API and MCP tools take as account_id. Sized to its
   content and monospaced, because it is a value people copy rather than read. */
.admin-users-table .admin-id-col {
  width: 1%;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  color: var(--ballast);
}

/* Admin → Users: a search toolbar over a paged, sortable table. Every sort
   header and pager link is a plain URL, so a view is bookmarkable. */
.admin-users-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }
.admin-users-search { flex: 1 1 16rem; max-width: 26rem; }
.admin-users-perpage { margin-left: auto; display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.82rem; color: var(--ballast); }
.admin-users-table-card { padding: 0; overflow-x: auto; }
.admin-users-sort { color: inherit; text-decoration: none; white-space: nowrap; }
.admin-users-sort:hover { text-decoration: underline; }
.admin-users-sort-active { font-weight: 600; }
.admin-users-sort-ind { display: inline-block; min-width: 0.9em; margin-left: 0.2rem; font-size: 0.7em; }
.admin-users-chip { margin-left: 0.4rem; font-size: 0.65rem; }
.admin-users-email { font-size: 0.82rem; overflow-wrap: anywhere; }
.admin-users-status .role-chip { font-size: 0.65rem; }
.admin-users-status .role-chip + .role-chip { margin-left: 0.25rem; }
.admin-users-note { font-size: 0.78rem; color: var(--ballast); }
.admin-inline-form { display: inline; }
.admin-role-form { display: flex; gap: 0.4rem; align-items: center; justify-content: flex-end; }
.admin-role-form select { font-size: 0.82rem; padding: 0.3rem 0.4rem; }
.admin-role-apply { font-size: 0.82rem; font-weight: 600; }
.admin-users-pager { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem; margin-top: 0.75rem; font-size: 0.85rem; }
.admin-users-pager-count { color: var(--ballast); font-variant-numeric: tabular-nums; }
.admin-users-pager-links { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; font-variant-numeric: tabular-nums; }
.admin-users-pager-cur { font-weight: 600; }
.admin-users-pager-off, .admin-users-pager-gap { color: var(--ballast); }

/* Conductor connect page (PRD #628 P1): the picker a person opens from the
   verification URL their conductor printed. Standalone chrome — the reader
   arrived from a terminal, not from the dashboard. */
.cc-wrap { max-width: 640px; margin: 3rem auto; padding: 0 1rem; }
.cc-warn { font-size: 0.9rem; }
.cc-error { color: #b00020; font-weight: 600; }
.cc-switcher { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 1rem 0; }
.cc-ws { padding: 0.25rem 0.6rem; border: 1px solid var(--border, #ccc); border-radius: 999px; text-decoration: none; }
.cc-ws-current { font-weight: 600; border-color: currentColor; }
.cc-projects { list-style: none; padding: 0; margin: 0; }
.cc-project { padding: 0.5rem 0; border-bottom: 1px solid var(--border, #eee); }
.cc-project label { display: flex; align-items: baseline; gap: 0.5rem; }
.cc-slug { opacity: 0.7; font-family: ui-monospace, monospace; font-size: 0.85em; }
.cc-note { margin-left: auto; font-size: 0.8em; opacity: 0.8; font-style: italic; }
/* The greyed-out state: a project this machine already works, or one with no
   connected repository. Both are shown rather than hidden, so "why isn't mine
   here?" has an answer on the page — but neither can be chosen. */
.cc-project.cc-unselectable { opacity: 0.5; }
.cc-project.cc-unselectable label { cursor: not-allowed; }
.cc-actions { display: flex; gap: 0.75rem; margin-top: 1.25rem; }
/* Access section people list (PRD #677 P2): one pill per project role. */
.role-pill-admin { background: #fef3c7; color: #92400e; }
.role-pill-member { background: #f1f5f9; color: #475569; }
.role-pill-viewer { background: #e0e7ff; color: #3730a3; }

/* ── The MCP connector landing page (GET /mcp from a browser; handler_mcp_landing.go) ──
   Rides the marketing landing's shell classes (.landing-top/.landing-hero/.landing-section)
   and adds only what a URL-first page needs: the URL card in the hero, a facts row,
   a grid of per-client cards, and the two-column "pin / sign-in" band. */
.mcp-hero { padding: 5rem 0 6rem; text-align: center; }
.mcp-hero-inner { max-width: 720px; display: flex; flex-direction: column; align-items: center; }
.mcp-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 1.1rem;
  border-radius: 999px;
  border: 1px solid rgba(52,211,153,0.45);
  background: rgba(52,211,153,0.12);
  color: var(--signal-green-bright);
  font-size: 0.9rem;
  font-weight: 500;
  margin-bottom: 2rem;
}
.mcp-pill-dot {
  width: 0.5rem; height: 0.5rem; border-radius: 50%;
  background: var(--signal-green-bright);
  box-shadow: 0 0 8px var(--signal-green-bright);
}
.mcp-title {
  font-size: 3.2rem;
  line-height: 1.08;
  font-weight: 700;
  letter-spacing: -0.03em;
  color: white;
  margin: 0 0 1.4rem;
}
.mcp-title-accent {
  position: relative;
  white-space: nowrap;
}
/* The hand-drawn-looking underline under the accent words: a stroke that sits
   just below the baseline, drawn with a gradient so it tapers at both ends. */
.mcp-title-accent::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  bottom: -0.12em;
  height: 0.16em;
  border-radius: 999px;
  background: linear-gradient(90deg, transparent 0%, var(--signal-green) 12%, var(--signal-green-bright) 50%, var(--signal-green) 88%, transparent 100%);
}
.mcp-lede {
  font-size: 1.2rem;
  line-height: 1.55;
  color: rgba(255,255,255,0.82);
  max-width: 640px;
  margin: 0 0 2.2rem;
}
.mcp-url-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  max-width: 640px;
  padding: 0.5rem 0.5rem 0.5rem 1.1rem;
  border: 1px solid rgba(255,255,255,0.22);
  border-radius: 12px;
  background: rgba(255,255,255,0.06);
  margin-bottom: 1.6rem;
}
.mcp-url {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  padding: 0.4rem 0;
  font-family: var(--font-mono);
  font-size: 1rem;
  color: white;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mcp-copy { flex: 0 0 auto; padding: 0.75rem 1.4rem; font-size: 0.95rem; }
.mcp-url-note { font-size: 0.88rem; color: rgba(255,255,255,0.65); margin: -0.6rem 0 1.4rem; }
.mcp-url-note code { color: white; }
.mcp-cta {
  background: white;
  color: var(--steel);
  padding: 0.85rem 1.8rem;
  font-size: 1rem;
  font-weight: 600;
}
.mcp-cta:hover { background: #f1f5f9; text-decoration: none; }
@media (max-width: 640px) {
  .mcp-hero { padding: 3.5rem 0 4rem; }
  .mcp-title { font-size: 2.3rem; }
  .mcp-lede { font-size: 1.05rem; }
}
.mcp-clients {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.25rem;
  margin-top: 1.75rem;
}
.mcp-client {
  display: flex;
  flex-direction: column;
  padding: 1.25rem 1.4rem;
  min-width: 0;
}
.mcp-client-name { font-size: 1.05rem; font-weight: 700; color: var(--steel); margin: 0 0 0.4rem; }
.mcp-client-body { font-size: 0.9rem; line-height: 1.55; color: var(--ballast); margin: 0 0 0.9rem; }
.mcp-client > .dispatch-snippet,
.mcp-client > .btn { margin-top: auto; }
.mcp-client > .dispatch-snippet + .btn { margin-top: 0; align-self: flex-start; }
.mcp-client-cta { align-self: flex-start; }
.mcp-config { margin-top: 2rem; max-width: 720px; }
.mcp-config .btn { align-self: flex-start; }
.mcp-section-alt { background: var(--page); }
.mcp-two-col {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 2.5rem;
}
/* A grid item's min-width is auto (= min-content), and the pinned-URL <pre> inside
   is white-space:pre — without this the column, and the page, is forced wider
   than a phone viewport instead of the snippet scrolling (same fix as
   .dispatch-option above). */
.mcp-two-col > div { min-width: 0; }
.mcp-two-col .dispatch-snippet { margin-top: 1rem; max-width: 560px; }
.mcp-points {
  list-style: none;
  margin: 0.9rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.9rem;
  font-size: 0.92rem;
  line-height: 1.55;
  color: var(--ballast);
  max-width: 560px;
}
.mcp-points strong { color: var(--steel); }

/* 404 page (views_not_found.templ). */
.not-found { max-width: 36rem; margin: 3rem auto; padding: 2rem; text-align: center; overflow-wrap: anywhere; }
.not-found-code { font-family: var(--font-mono); font-size: 3rem; font-weight: 700; line-height: 1; color: var(--rail-blue); margin: 0 0 0.5rem; }
.not-found h1 { font-size: 1.5rem; font-weight: 600; margin: 0 0 0.75rem; }
.not-found-lede { color: var(--ballast); margin: 0 0 0.5rem; }
.not-found-path { font-size: 0.85rem; color: var(--ballast); margin: 1rem 0 1.5rem; }
.not-found-path code { font-family: var(--font-mono); color: var(--steel); }
.not-found-links { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; align-items: center; }
.not-found-back { font-size: 0.9rem; margin-left: 0.25rem; }

/* ── conductor site (conductor.switchyard.work; views_conductor_*.templ) ──
   The conductor's own public site, served by this binary (PRD #796,
   handler_conductor_site.go). It rides the marketing front page's shell
   classes — .landing-top / .landing-header / .landing-hero / .landing-section /
   .landing-trust-point / .landing-final / .site-footer — on the same rail-yard
   palette, and adds only what those classes do not already carry: the two-part
   wordmark, the loop diagram, the role and group cards with their mode pills,
   a table style, and the dark trust band (the front page's equivalent band is
   on the light surface, so the cards need dark-surface ink there).

   Every rule the site uses is HERE and not in a page: the site renders no
   inline <style> element and no style= attribute, and
   conductor_site_style_test.go renders all five pages and fails the build if
   one appears. Light theme only, as the parent is. */

/* The wordmark: the front page's switch[mark]yard, then a mono suffix. */
.conductor-wordmark { display: inline-flex; align-items: baseline; gap: 0.45rem; }
.conductor-wordmark-suffix {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--signal-green-bright);
  letter-spacing: -0.02em;
}
.conductor-footer-suffix { font-family: var(--font-mono); font-weight: 700; color: var(--ballast); }
/* The nav marks the page you are on; aria-current carries it for a reader. */
.landing-header-link.is-current { color: white; text-decoration: underline; text-underline-offset: 0.35em; }

/* A shorter hero for the four interior pages: the overview's is the tall one. */
.conductor-hero-short { padding: 2.75rem 0 3.25rem; }
.conductor-narrow { max-width: 760px; }
.conductor-note { font-size: 0.95rem; line-height: 1.65; color: var(--ballast); margin-top: 1.1rem; max-width: 680px; }
.conductor-note code, .conductor-list code { font-family: var(--font-mono); font-size: 0.88em; color: var(--steel); }
.conductor-section-alt { background: var(--page); }

/* The loop diagram: one mono block, scrollable on a narrow viewport rather
   than reflowed — it is box drawing, and a wrapped box is noise. */
.conductor-loop {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--steel);
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: var(--radius);
  padding: 1.4rem 1.2rem;
  margin: 1.8rem 0 0;
  overflow-x: auto;
  white-space: pre;
}
.conductor-loop-caption {
  font-size: 1.02rem;
  font-weight: 600;
  color: var(--steel);
  margin-top: 1.1rem;
}

/* The five role-group cards on the overview, each a link into /roles#<group>. */
.conductor-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 1.25rem;
  margin-top: 2rem;
}
.conductor-group { padding: 1.4rem 1.35rem; color: inherit; display: block; }
.conductor-group:hover { text-decoration: none; border-color: var(--rail-blue); }
.conductor-group-title { font-size: 1.1rem; font-weight: 700; color: var(--steel); margin: 0 0 0.4rem; }
.conductor-group-line { font-size: 0.9rem; line-height: 1.55; color: var(--ballast); margin: 0 0 0.7rem; }
.conductor-group-roles { font-family: var(--font-mono); font-size: 0.78rem; line-height: 1.6; color: var(--rail-blue); margin: 0; }

/* The role cards on /roles, one per role, with the mode pill. */
.conductor-roles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
  gap: 1.1rem;
  margin-top: 1.8rem;
}
.conductor-role { padding: 1.25rem 1.3rem; }
.conductor-role-name { font-family: var(--font-mono); font-size: 1rem; font-weight: 700; color: var(--steel); margin: 0 0 0.5rem; }
.conductor-role-formerly { font-family: var(--font-mono); font-size: 0.78rem; color: var(--ballast); margin: -0.35rem 0 0.5rem; }
.conductor-role-line { font-size: 0.9rem; line-height: 1.55; color: var(--ballast); margin: 0.6rem 0 0; }
/* The mode pill. One rule per mode, so the mode is a class and never a colour
   written into the markup (conductorModeSlug). */
.conductor-pill {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}
.conductor-pill-claims { background: #d1fae5; color: #065f46; }
.conductor-pill-proposes { background: #e0e7ff; color: #3730a3; }
.conductor-pill-acts { background: #fef3c7; color: #92400e; }
.conductor-pill-nollm { background: #f1f5f9; color: #475569; }

/* Tables: the download/release/split tables share one style. */
.conductor-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 1.6rem;
  font-size: 0.92rem;
}
.conductor-table th {
  text-align: left;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ballast);
  border-bottom: 1px solid #e5e7eb;
  padding: 0 0.8rem 0.6rem 0;
}
.conductor-table td {
  border-bottom: 1px solid #eef2f7;
  padding: 0.7rem 0.8rem 0.7rem 0;
  color: var(--steel);
  line-height: 1.55;
  vertical-align: top;
}
.conductor-split td { width: 50%; }
.conductor-cmd code { font-family: var(--font-mono); font-size: 0.85rem; white-space: nowrap; }
.conductor-mono-dim { font-family: var(--font-mono); font-size: 0.75rem; color: var(--ballast); }
.conductor-sha code { font-family: var(--font-mono); font-size: 0.7rem; color: var(--ballast); overflow-wrap: anywhere; }
.conductor-sidecar { font-family: var(--font-mono); font-size: 0.72rem; color: var(--ballast); margin-left: 0.5rem; }
.conductor-list { margin: 1.2rem 0 0; padding-left: 1.3rem; font-size: 0.97rem; line-height: 1.7; color: var(--ballast); }
.conductor-list li { margin-bottom: 0.5rem; }
.conductor-list strong { color: var(--steel); }

/* What stands where the one-liner and the Download buttons go on an install
   that does not serve them (views_conductor_shell.templ's
   conductorDownloadPending). Deliberately the page's most prominent block in
   that section rather than a footnote: it replaces a call to action, so it has
   to read as one. The dashboard's .alert-info palette, at page scale. */
.conductor-callout {
  margin-top: 1.5rem;
  padding: 1.1rem 1.25rem;
  background: #f0f9ff;
  border: 1px solid #bae6fd;
  border-left: 4px solid #0369a1;
  border-radius: var(--radius);
  max-width: 680px;
}
.conductor-callout-title { margin: 0 0 0.4rem; font-weight: 700; color: #075985; font-size: 1rem; }
.conductor-callout-body { margin: 0; font-size: 0.93rem; line-height: 1.65; color: var(--steel); }
.conductor-callout-body code { font-family: var(--font-mono); font-size: 0.88em; color: #075985; }

/* The install one-liner: snippet plus copy button, side by side where there is
   room. The snippet itself is the shared .dispatch-snippet. */
.conductor-command { display: flex; align-items: flex-start; gap: 0.75rem; margin-top: 1.5rem; flex-wrap: wrap; }
.conductor-command .dispatch-snippet { flex: 1 1 320px; min-width: 0; margin: 0; font-size: 0.82rem; padding: 0.85rem 1rem; }

/* The dark trust band. The front page's trust cards sit on the light surface,
   so everything here is the dark-surface variant of them — same grid, same card
   geometry, inverted ink. */
.conductor-trust { background: var(--sleeper); color: white; }
.conductor-h2-dark { color: white; }
.conductor-sub-dark { color: rgba(255,255,255,0.75); }
.conductor-eyebrow-light { color: var(--signal-green); }
.conductor-trust-point {
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.1);
  border-top: 3px solid var(--signal-green-bright);
  box-shadow: none;
}
.conductor-trust-title { color: white; }
.conductor-trust-body { color: rgba(255,255,255,0.75); }
.conductor-trust-body code { font-family: var(--font-mono); color: var(--signal-green-bright); }

/* /releases: one card per published build. */
.conductor-releases { display: grid; gap: 1.5rem; margin-top: 1.5rem; }
.conductor-release { padding: 1.5rem 1.6rem; }
.conductor-release-head { display: flex; align-items: center; flex-wrap: wrap; gap: 0.6rem; }
.conductor-release-version { font-size: 1.15rem; margin: 0; }
.conductor-release-version code { font-family: var(--font-mono); color: var(--steel); }
.conductor-release-built { font-size: 0.85rem; color: var(--ballast); margin-left: auto; }
.conductor-release-full { font-size: 0.78rem; margin-top: 0.9rem; }
.conductor-release-full code { font-family: var(--font-mono); overflow-wrap: anywhere; }

/* The release notes block: markdown a person wrote in the conductor's own
   repository, so it is prose column-width and the body elements goldmark emits
   are styled here rather than left to the browser's defaults. Rendered only
   when the publish record carries notes (views_conductor_releases.templ). */
.conductor-release-notes { margin-top: 1.1rem; padding-top: 1rem; border-top: 1px solid var(--rule); max-width: 680px; }
.conductor-release-notes-title { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ballast); margin: 0 0 0.5rem; }
.conductor-release-notes p, .conductor-release-notes li { font-size: 0.92rem; line-height: 1.65; color: var(--steel); }
.conductor-release-notes p { margin: 0 0 0.6rem; }
.conductor-release-notes p:last-child { margin-bottom: 0; }
.conductor-release-notes ul, .conductor-release-notes ol { margin: 0 0 0.6rem; padding-left: 1.2rem; }
.conductor-release-notes h1, .conductor-release-notes h2, .conductor-release-notes h3,
.conductor-release-notes h4, .conductor-release-notes h5, .conductor-release-notes h6 {
  font-size: 0.95rem; margin: 0.9rem 0 0.4rem; color: var(--steel);
}
.conductor-release-notes code { font-family: var(--font-mono); font-size: 0.86em; }
.conductor-release-notes pre { overflow-x: auto; }
.conductor-release-notes pre code { font-size: 0.8em; }

@media (max-width: 860px) {
  .conductor-split td { width: auto; }
  .conductor-release-built { margin-left: 0; }
}

/* PRD #852 P3 — the DATE surfaces: a target/due chip, its late or overdue mark,
   and the two editors that set and clear a date.

   The marks are the point. "late" and "overdue" are single words rendered beside
   a date, and with no rule here they were plain body text — a commitment that
   has passed reading exactly like one that has not. They are therefore pills on
   --signal-red (--signal-amber for a task's due chip, which is a reminder rather
   than a broken promise), the same chip grammar .inbox-badge already uses, so
   lateness is legible at a glance across the roadmap, the PRD card, the task pad
   and the inbox without a new visual language for each.

   The chips borrow .inbox-badge's metrics rather than inventing their own, and
   the editor forms get only enough layout to sit on one line inside a milestone
   card — this is a date input and two buttons, not a panel. */
.roadmap-target,
.task-pad-due {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3rem;
  font-size: 0.74rem;
  color: var(--ballast);
}
.roadmap-target-label,
.task-pad-due-label {
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 0.66rem;
}
.roadmap-target-date,
.task-pad-due-date {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--steel);
}
/* A date that has passed colours its own chip, so the mark is not the only tell. */
.roadmap-target.is-late .roadmap-target-date { color: var(--signal-red); font-weight: 700; }
.task-pad-due.is-overdue .task-pad-due-date { color: var(--signal-amber); font-weight: 700; }

/* The late/overdue marks themselves. .roadmap-late-prd is the same pill on a PRD
   row inside a milestone; it needs no colour of its own, only the spacing that
   keeps it off the row's title. */
.roadmap-late-chip,
.prd-target-late,
.inbox-badge.is-late {
  display: inline-block;
  padding: 0.05rem 0.4rem;
  border-radius: var(--radius);
  background: #fef2f2;
  border: 1px solid #fecaca;
  color: var(--signal-red);
  font-weight: 700;
  font-size: 0.68rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}
.roadmap-late-prd { margin-right: 0.4rem; vertical-align: middle; }
.task-pad-overdue {
  display: inline-block;
  padding: 0.05rem 0.4rem;
  border-radius: var(--radius);
  background: #fffbeb;
  border: 1px solid #fde68a;
  color: var(--signal-amber);
  font-weight: 700;
  font-size: 0.68rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

/* The editors. The roadmap's sits inside a milestone card and must not look like
   a panel; the task sheet's is the same control one line narrower. prd-target-form
   is already laid out inline in its own card, so it only needs its margin. */
.roadmap-target-form,
.task-sheet-due-form {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.5rem 0 0;
}
.roadmap-target-field {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ballast);
}
.roadmap-target-btn { font-size: 0.74rem; padding: 0.3rem 0.6rem; }
.prd-target-form { margin: 0; }

/* PRD #857 — board and calendar layouts, and the saved-view control
   (views_board_layout.templ, views_view_mode.templ). The board's columns are the
   lifecycle; a collapsed (terminal) column is a header and a count. */
.board-view-control { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem 1rem; margin: 0 0 1rem; }
.saved-views { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 0.7rem; font-size: 0.78rem; }
.saved-views-label { font-weight: 600; color: var(--ballast); }
.saved-view-list { display: contents; list-style: none; margin: 0; padding: 0; }
.saved-view { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.15rem 0.5rem; border: 1px solid var(--line-2, #cbd5e1); border-radius: 999px; }
.saved-view.is-pinned { border-color: var(--steel); }
.saved-view form { display: inline-flex; align-items: center; gap: 0.3rem; margin: 0; }
.saved-view .form-group, .saved-view-save .form-group { margin: 0; }
.saved-view-pinned { color: var(--ballast); font-size: 0.72rem; }
.saved-view-edit summary { cursor: pointer; color: var(--ballast); }
.saved-view-save { display: inline-flex; align-items: center; gap: 0.35rem; margin: 0; }
.board-layout { margin: 0 0 1.25rem; }
.board-columns { display: flex; gap: 0.75rem; overflow-x: auto; align-items: flex-start; padding-bottom: 0.5rem; }
.board-column { flex: 0 0 17rem; background: #f8fafc; border: 1px solid var(--line-2, #e2e8f0); border-radius: 8px; padding: 0.5rem; }
.board-column.is-collapsed { flex-basis: 8rem; opacity: 0.85; }
.board-column-head { display: flex; align-items: baseline; gap: 0.4rem; font-size: 0.78rem; font-weight: 600; margin-bottom: 0.4rem; }
.board-column-count { color: var(--ballast); font-weight: normal; }
.board-column-list { margin-left: auto; font-weight: normal; font-size: 0.72rem; }
.board-column-cards { display: flex; flex-direction: column; gap: 0.45rem; }
.board-card { background: var(--surface, #fff); border: 1px solid var(--line-2, #e2e8f0); border-radius: 6px; padding: 0.45rem 0.55rem; font-size: 0.8rem; }
.board-card-face { display: flex; flex-direction: column; gap: 0.3rem; }
.board-card-head { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; }
.board-card-title { font-weight: 600; text-decoration: none; }
.board-column-more { display: block; margin-top: 0.45rem; font-size: 0.75rem; }
.board-outside { font-size: 0.75rem; color: var(--ballast); margin: 0.4rem 0 0; }
.calendar-layout { margin: 0 0 1.25rem; }
.calendar-nav { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem; font-size: 0.8rem; }
.calendar-label { margin: 0; font-size: 0.95rem; }
.calendar-grid { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: 0.75rem; }
.calendar-grid th { text-align: left; color: var(--ballast); font-weight: 600; padding: 0.25rem; }
.calendar-day { vertical-align: top; height: 5.5rem; border: 1px solid var(--line-2, #e2e8f0); padding: 0.25rem; }
.calendar-day.is-outside { background: #f8fafc; color: var(--ballast); }
.calendar-day.is-today { box-shadow: inset 0 0 0 2px var(--steel); }
.calendar-day-num { display: block; font-weight: 600; margin-bottom: 0.2rem; }
.calendar-entry { display: flex; flex-wrap: wrap; align-items: center; gap: 0.2rem; margin-bottom: 0.2rem; }
.calendar-entry.is-late .calendar-chip { color: var(--signal-red, #b91c1c); }
.calendar-late { font-size: 0.68rem; color: var(--signal-red, #b91c1c); font-weight: 600; }
.calendar-chip { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.calendar-undated h4 { font-size: 0.8rem; margin: 0.8rem 0 0.3rem; }
.calendar-undated ul { margin: 0; padding-left: 1.1rem; font-size: 0.78rem; }
