:root {
  --bg: #0b1220;
  --panel: #111a2b;
  --panel-soft: #16233a;
  --text: #e6edf3;
  --muted: #94a3b8;
  --border: #243044;
  --accent: #4da3ff;
  --accent-2: #22c55e;
  --green: #22c55e;
  --yellow: #facc15;
  --red: #ef4444;
  --radius: 16px;
  --shadow: 0 20px 40px rgba(0, 0, 0, 0.25);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  line-height: 1.6;
}

.console {
  max-width: 1200px;
  margin: 0 auto;
  padding: 32px;
}

/* Navigation */

.top-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 32px;
  background: #07111f;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 100;
}

.top-nav nav {
  display: flex;
  gap: 24px;
}

.top-nav a {
  color: var(--muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 15px;
}

.top-nav a:hover {
  color: var(--text);
}

/* UX1 — Mobile UI and Performance Refinements. Set by
   app/static/js/nav-toggle.js comparing the current path against each
   link's own href — no active-page indicator existed before this. */
.top-nav a.active {
  color: var(--text);
  position: relative;
}

.top-nav a.active::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 2px;
  background: var(--accent);
  border-radius: 1px;
}

/* Sprint M1 — Mobile UX Polish. Hidden on tablet/desktop by default;
   only the @media (max-width: 640px) block below ever makes this
   visible, so nothing about desktop/tablet navigation changes. */
.nav-toggle {
  display: none;
}

/* Header */

.hero {
  margin-bottom: 32px;
}

.console-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--border);
}

.eyebrow {
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 13px;
  font-weight: 700;
}

h1 {
  font-size: 42px;
  margin: 8px 0;
  line-height: 1.15;
}

.subtitle {
  color: var(--muted);
  font-size: 18px;
}

/* Status */

.overall-status {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 12px 18px;
  font-weight: 700;
  white-space: nowrap;
}

.status-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  display: inline-block;
}

.status-dot.healthy {
  background: var(--green);
}

.status-dot.warning,
.status-dot.unknown {
  background: var(--yellow);
}

.status-dot.critical {
  background: var(--red);
}

.status {
  display: inline-flex;
  align-items: center;
  font-weight: 700;
  text-transform: capitalize;
}

.status.healthy {
  color: var(--green);
}

.status.warning,
.status.unknown {
  color: var(--yellow);
}

.status.critical {
  color: var(--red);
}

.risk-link-red,
.risk-link-red:link,
.risk-link-red:visited,
.risk-link-red:active {
  color: var(--red) !important;
  text-decoration: none;
}

.risk-link-red:hover {
  text-decoration: underline;
}

.risk-link-yellow,
.risk-link-yellow:link,
.risk-link-yellow:visited,
.risk-link-yellow:active {
  color: var(--yellow) !important;
  text-decoration: none;
}

.risk-link-yellow:hover {
  text-decoration: underline;
}

/* Executive Requires Attention foundation (2026-08-10) — a human-set
   Capture attention flag, deliberately not red/yellow (those are
   Action risk severities); --accent reads as "flagged/informational"
   rather than a risk level, keeping it visibly distinct from Critical/
   High Action entries in the same list. */
.risk-link-flagged,
.risk-link-flagged:link,
.risk-link-flagged:visited,
.risk-link-flagged:active {
  color: var(--accent) !important;
  text-decoration: none;
}

.risk-link-flagged:hover {
  text-decoration: underline;
}

.risk-link-green,
.risk-link-green:link,
.risk-link-green:visited,
.risk-link-green:active {
  color: var(--green) !important;
  text-decoration: none;
}

.risk-link-green:hover {
  text-decoration: underline;
}

.inline-form {
  display: flex;
  align-items: center;
  gap: 6px;
}

.badge-awarded {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--green);
  background: rgba(34, 197, 94, 0.12);
  border: 1px solid var(--green);
  border-radius: 999px;
  padding: 2px 10px;
  font-weight: 700;
  font-size: 0.85em;
  white-space: nowrap;
}

tr.row-awarded {
  background: rgba(34, 197, 94, 0.08);
}

.badge-needs-classification {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--yellow);
  background: rgba(250, 204, 21, 0.12);
  border: 1px solid var(--yellow);
  border-radius: 999px;
  padding: 2px 10px;
  font-weight: 700;
  font-size: 0.85em;
  white-space: nowrap;
}

/* Projects list exception-only sync badge (Increment 2B, 2026-08-18) —
   rendered only for a Project whose Sheet/Timecard sync is currently
   failed/conflict/duplicate/stuck-pending; absent for every healthy
   row. Mirrors .badge-needs-classification's shape, red instead of
   yellow to match the existing sync-failure banner color
   (app/templates/projects/detail.html). */
.badge-sync-attention {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--red);
  background: rgba(239, 68, 68, 0.12);
  border: 1px solid var(--red);
  border-radius: 999px;
  padding: 2px 10px;
  font-weight: 700;
  font-size: 0.85em;
  white-space: nowrap;
}

.badge-no-primary {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--yellow);
  background: rgba(250, 204, 21, 0.12);
  border: 1px solid var(--yellow);
  border-radius: 999px;
  padding: 2px 10px;
  font-weight: 700;
  font-size: 0.85em;
  white-space: nowrap;
}

/* Capture Discoverability Refinement (2026-07-27) — makes the active
   Mine/Company scope on the Captures list visually obvious in the
   page heading itself, not only in the toggle's own active state and
   the subtitle sentence. Mirrors .badge-awarded's shape exactly. */
.capture-scope-badge {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 2px 10px;
  font-weight: 700;
  font-size: 0.6em;
  white-space: nowrap;
  vertical-align: middle;
}

.capture-scope-badge-company {
  color: var(--green);
  background: rgba(34, 197, 94, 0.12);
  border: 1px solid var(--green);
}

.capture-scope-badge-personal {
  color: var(--yellow);
  background: rgba(250, 204, 21, 0.12);
  border: 1px solid var(--yellow);
}

/* Generalized Active/Inactive record-status badge (Sprint O3.1.1 —
   Organization Detail Page Foundation). Deliberately separate from
   .badge-awarded/.badge-needs-classification/.badge-no-primary above
   — each of those is tied to one specific business meaning, and
   reusing one of them here just because its pill shape fits would
   make the stylesheet confusing about what each badge actually means.
   This is a plain record lifecycle indicator any module's detail page
   can reuse, not specific to Organizations. */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border-radius: 999px;
  padding: 2px 10px;
  font-weight: 700;
  font-size: 0.85em;
  white-space: nowrap;
}

.status-badge-active {
  color: var(--green);
  background: rgba(34, 197, 94, 0.12);
  border: 1px solid var(--green);
}

.status-badge-inactive {
  color: var(--muted);
  background: rgba(148, 163, 184, 0.12);
  border: 1px solid var(--border);
}

/* Project/Capture UX Refinement — a plain, non-evaluative record-type
   indicator (Capture vs. Action, app/templates/projects/detail.html),
   not a status. Same neutral tone as .status-badge-inactive
   deliberately reused via its own values rather than shared class name
   — a future change to what "inactive" means must never accidentally
   restyle this unrelated badge. */
.status-badge-neutral {
  color: var(--muted);
  background: rgba(148, 163, 184, 0.12);
  border: 1px solid var(--border);
}

/* Queue UX Sprint A (2026-08-01) — Review status badge variants for
   the Communication queue card (Open/Waiting/Deferred/Resolved),
   reusing .status-badge's own pill shape rather than a new one. */
.status-badge-open {
  color: var(--accent);
  background: rgba(77, 163, 255, 0.12);
  border: 1px solid var(--accent);
}

.status-badge-waiting {
  color: var(--yellow);
  background: rgba(250, 204, 21, 0.12);
  border: 1px solid var(--yellow);
}

.status-badge-deferred {
  color: var(--muted);
  background: rgba(148, 163, 184, 0.12);
  border: 1px solid var(--border);
}

.status-badge-resolved {
  color: var(--green);
  background: rgba(34, 197, 94, 0.12);
  border: 1px solid var(--green);
}

/* Quiet Communications (2026-09-27) — Tracked: a quiet, machine-managed
   Communication (company memory, not work). Deliberately muted, with a
   dashed border so it never reads as Open work or as Resolved. */
.status-badge-tracked {
  color: var(--muted);
  background: transparent;
  border: 1px dashed var(--border);
}

/* Billing UX correction pass (Kevin's 2026-09-01 locked rules,
   superseding the payment-status mapping from the earlier Payment/
   Invoice UX pass the same day) — "Unpaid" needs attention, same red
   family as .badge-sync-attention/.roadmap-status-blocked elsewhere in
   this file; "Partially Paid" reuses the existing .status-badge-waiting
   yellow (active/in-progress); "Paid" reuses the existing
   .status-badge-resolved green (complete). */
.status-badge-danger {
  color: var(--red);
  background: rgba(239, 68, 68, 0.12);
  border: 1px solid var(--red);
}

/* Organization Detail page header actions (Edit / Deactivate /
   Activate) — the second flex child of .console-header, alongside the
   title block, mirroring the existing .view-scope-toggle pattern
   (app/templates/dashboard.html). .console-header's own
   flex-direction:column rule at <=800px already stacks this beneath
   the title with no extra media query needed; flex-wrap here keeps
   both buttons usable without horizontal overflow at any width. */
.detail-header-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* Project detail Photos/Files/Billing nav (UX Pass, Kevin's
   2026-09-01 locked request) — slightly larger and more visually
   prominent than an ordinary .btn-secondary text link, since these
   are the day-to-day entry points into a Project's other surfaces.
   Still the muted/secondary color language, not a primary write
   action — just bigger and unmistakably clickable. */
.project-nav-action {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 16px;
  font-size: 16px;
  font-weight: 600;
  border-radius: var(--radius);
}

/* Grid and Cards */

.grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
}

.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px;
  box-shadow: var(--shadow);
}

.card h2 {
  margin: 0 0 14px;
  font-size: 22px;
}

.card p,
.card li {
  color: var(--muted);
}

.card ul {
  margin: 12px 0 0;
  padding-left: 20px;
}

/* Sparky Review: Email Description / Gmail links read as neutral text —
   urgency is conveyed solely by the adjacent .status-dot, not link color */

.review-link,
.review-link:visited {
  color: var(--text);
}

.review-link:hover {
  color: var(--accent);
}

/* Stage 2 (Commitments UI workflow refinement) — the active-filter-tab
   pattern (class="review-link {{ 'active' if ... }}") already existed
   on app/templates/procurement/list.html before this, but no CSS ever
   backed the "active" half of it — the current filter was never
   visually distinguishable from the others. Fixes both pages. */
.review-link.active {
  color: var(--accent);
  font-weight: 700;
  text-decoration: underline;
}

/* Sparky Review: sender name/company, always more emphasis on the person */

.sender-person {
  color: var(--text);
  font-weight: 700;
}

.sender-company {
  color: var(--muted);
  font-size: 13px;
}

/* Sparky Review: Suggested Actions should read as the primary content,
   not de-emphasized like ordinary .card text */

.suggested-actions li {
  color: var(--text);
  font-weight: 500;
}

/* Human Review: the record content is this section's primary voice, so
   it overrides .card p's default muted color — byline and secondary
   candidate fields stay .subtitle/muted deliberately */

.human-review-record {
  padding: 16px 0;
  border-top: 1px solid var(--border);
}

.human-review-record:first-child {
  border-top: none;
  padding-top: 0;
}

.human-review-record p {
  color: var(--text);
}

.human-review-record p.subtitle {
  color: var(--muted);
}

.human-review-draft-notice {
  color: var(--yellow);
}

.human-review-form {
  max-width: 760px;
}

.human-review-form label {
  display: block;
  margin-top: 16px;
  font-weight: 600;
  color: var(--text);
}

.human-review-form label:first-child {
  margin-top: 0;
}

.human-review-form input[type="text"] {
  display: block;
  width: 100%;
  max-width: 480px;
  margin-top: 6px;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
}

.human-review-form .capture-textarea {
  margin-top: 6px;
  min-height: 72px;
}

/* Groups the 10 primary prompts into Understanding / Your Decision /
   Looking Back so the form reads as three short conversations instead
   of one long list — presentation only, same fields, same order. */
.human-review-section-heading {
  margin: 28px 0 4px;
  font-size: 14px;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-top: 1px solid var(--border);
  padding-top: 20px;
}

.human-review-section-heading:first-of-type {
  border-top: none;
  padding-top: 0;
  margin-top: 20px;
}

.human-review-reviewer-label input[type="text"] {
  max-width: 240px;
}

.human-review-status {
  display: inline-block;
  margin-left: 10px;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  vertical-align: middle;
}

.human-review-status.draft {
  background: rgba(250, 204, 21, 0.15);
  color: var(--yellow);
}

.human-review-status.finalized {
  background: rgba(34, 197, 94, 0.15);
  color: var(--green);
}

.human-review-secondary {
  margin-top: 20px;
}

.human-review-secondary summary {
  cursor: pointer;
  color: var(--muted);
  font-weight: 700;
}

.human-review-actions {
  margin-top: 20px;
}

/* Sparky Review: collapsible audit/cost trail, de-emphasized by default */

.analysis-details summary {
  cursor: pointer;
  color: var(--muted);
  font-weight: 700;
}

.analysis-details[open] summary {
  margin-bottom: 14px;
}

/* Project/Capture UX Refinement — same de-emphasized-disclosure
   treatment as .analysis-details above, reused by its own values
   rather than sharing the class name (a future change meant only for
   Sparky Review's audit trail must never accidentally restyle this
   unrelated section). */
.project-archived-knowledge summary {
  cursor: pointer;
  color: var(--muted);
  font-weight: 700;
}

.project-archived-knowledge[open] summary {
  margin-bottom: 14px;
}

/* Procurement Phase A (2026-08-14) — the Follow-up and Waiting On
   summary sections' own collapsible groups, same de-emphasized-
   disclosure treatment as .analysis-details/.project-archived-knowledge
   above, kept as its own class per that convention's own reasoning: a
   future change meant for one disclosure must never accidentally
   restyle an unrelated one. */
.procurement-summary-group summary {
  cursor: pointer;
  color: var(--muted);
  font-weight: 700;
}

.procurement-summary-group[open] summary {
  margin-bottom: 14px;
}

/* Procurement Phase A UX Refinement (2026-08-14 live-review pass) — the
   compact search strip's text input grows to fill available space
   within its .action-buttons flex row (that utility class itself
   already provides the wrap/gap behavior every other action row in
   this codebase uses; this rule only tunes the one flex-item that
   isn't a button). */
.procurement-search-strip input[type="text"] {
  flex: 1 1 240px;
  min-width: 160px;
}

/* Procurement mockup pass (2026-08-14) — Requires Attention is a
   clearly bounded, visually urgent panel: the same --red family
   already used by .risk-link-red/.status.critical elsewhere, applied
   as a tinted background + border rather than as text color (the
   panel itself needs to read as urgent at a glance, not just its
   individual "days overdue" figures, which still use .risk-link-red
   directly). */
.procurement-requires-attention {
  border-color: rgba(239, 68, 68, 0.4);
  background: rgba(239, 68, 68, 0.06);
}

/* Procurement Active Jobs list (mockup pass, 2026-08-14) — a compact
   vertical row list, explicitly NOT individual cards: each <details>
   is one divided row (border-bottom, no per-row card box/background/
   shadow) inside a single bordered Active Jobs section. The flex
   summary layout (name left, counts + expand affordance right) and
   custom marker mirror .roadmap-layer-card's established disclosure
   pattern (style.css, "roadmap-layer-card summary"), kept as this
   section's own class per that same convention's own reasoning: a
   future change meant for one disclosure must never accidentally
   restyle an unrelated one. flex-wrap on the summary (rather than
   white-space: nowrap on the counts) is what lets counts drop below
   the job name on narrow screens instead of forcing horizontal
   scroll. */
.procurement-job-row {
  border-bottom: 1px solid var(--border);
}

.procurement-job-row:last-of-type {
  border-bottom: none;
}

.procurement-job-row summary {
  cursor: pointer;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 4px 12px;
  list-style: none;
  padding: 10px 0;
}

.procurement-job-row summary::-webkit-details-marker {
  display: none;
}

.procurement-job-row[open] summary {
  padding-bottom: 4px;
}

.procurement-job-row-meta {
  color: var(--muted);
}

.procurement-job-row-expand-icon {
  display: inline-block;
  margin-left: 8px;
  font-size: 0.8em;
  transition: transform 0.15s ease;
}

.procurement-job-row[open] .procurement-job-row-expand-icon {
  transform: rotate(90deg);
}

/* Process Communication wizard (2026-08-03) — the manual-controls
   section on the Communication detail page, collapsed by default
   below the wizard's own entry button, mirroring .analysis-details/
   .human-review-secondary's identical existing convention exactly. */

.wizard-advanced-controls {
  margin: 16px 0 24px;
}

.wizard-advanced-controls summary {
  cursor: pointer;
  color: var(--muted);
  font-weight: 700;
}

.wizard-advanced-controls[open] summary {
  margin-bottom: 14px;
}

/* Sprint 3.2 (Guided Processing) — the "Create work" outcome's own
   optional-fields disclosure, mirroring .wizard-advanced-controls'
   identical summary styling. flex-basis: 100% so this <details> takes
   its own full row within the surrounding .action-form's flex-wrap
   layout, rather than sitting awkwardly beside Action Type. */
.wizard-optional-fields {
  flex-basis: 100%;
}

.wizard-optional-fields summary {
  cursor: pointer;
  color: var(--muted);
  font-weight: 700;
}

.wizard-optional-fields[open] summary {
  margin-bottom: 10px;
}

.wizard-optional-fields-body {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
}

/* Mobile */

@media (max-width: 800px) {
  .top-nav {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }

  .top-nav nav {
    flex-wrap: wrap;
    gap: 12px;
  }

  .console-header {
    flex-direction: column;
    align-items: flex-start;
  }

  .grid {
    grid-template-columns: 1fr;
  }

  h1 {
    font-size: 34px;
  }
}

/* Sprint M1 — Mobile UX Polish (phone widths, ~390-430px design
   target; this breakpoint is deliberately separate from and narrower
   than the existing @media (max-width: 800px) block above, which
   stays exactly as it was for tablets). Every rule here is additive —
   nothing above this point was changed to make room for it, and every
   selector below either only fires under 640px or is itself new. */
@media (max-width: 640px) {
  /* Collapsible nav (app/static/js/nav-toggle.js). Desktop/tablet
     never see .nav-toggle (display: none by default, above) or a
     collapsed #primary-nav — both only exist inside this block. */
  .top-nav-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;
  }

  .nav-toggle {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 5px;
    width: 44px;
    height: 44px;
    padding: 0;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 8px;
    cursor: pointer;
  }

  .nav-toggle-bar {
    display: block;
    width: 20px;
    height: 2px;
    background: var(--text);
    border-radius: 2px;
  }

  .top-nav nav {
    display: none;
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    gap: 2px;
    margin-top: 4px;
  }

  .top-nav nav.nav-open {
    display: flex;
  }

  .top-nav nav a,
  .top-nav nav span {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 10px 8px;
    box-sizing: border-box;
  }

  /* UX1 — a side underline (the desktop .active treatment above) is
     easy to miss in a full-width stacked list; a background tint reads
     more clearly at this width. Overrides, rather than duplicates, the
     desktop-oriented ::after rule above by being the later, more
     specific match at this breakpoint. */
  .top-nav nav a.active {
    background: var(--panel-soft);
    border-radius: 8px;
  }

  .top-nav nav a.active::after {
    content: none;
  }

  /* UX1 — Capture is the primary phone destination (post-login
     routing already lands phones here); the nav should say so instead
     of listing it as one of eleven equal-weight items. Mobile only —
     desktop nav is intentionally untouched. */
  .top-nav nav a[href="/captures"] {
    color: var(--accent);
    font-weight: 700;
  }

  .current-user {
    width: 100%;
    justify-content: space-between;
  }

  /* Page chrome — .console's 32px side padding leaves very little
     usable width on a ~390px phone. */
  .console {
    padding: 16px;
  }

  h1 {
    font-size: 26px;
  }

  .subtitle {
    font-size: 16px;
  }

  .card {
    padding: 16px;
  }

  /* Touch targets. Desktop/tablet .btn-small (4px 10px, 13px font) is
     unchanged outside this block — this only widens it on phones,
     where the same class is used for every Activate/Deactivate/Edit
     action throughout every table on the site. */
  .btn-small {
    padding: 10px 14px;
    font-size: 14px;
    min-height: 44px;
  }

  .inline-form {
    gap: 10px;
    flex-wrap: wrap;
  }

  .btn,
  .btn-secondary {
    min-height: 44px;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
  }

  /* Forms — one field per row is easier to read and thumb-type than
     several ~160px-wide fields wrapping mid-row. Stage 2 (Commitments
     UI workflow refinement) extends this to .detail-actions-row — the
     Change Owner / Update Status / Due Date / Complete / Cancel forms
     on the Commitment detail page (and the pre-existing Action/Capture
     detail pages, which share the same class) previously had no
     mobile-specific sizing at all; their <select>/<input> elements
     just wrapped at whatever width the browser gave them. */
  .quick-add-form input[type="text"],
  .quick-add-form input[type="email"],
  .quick-add-form input[type="tel"],
  .quick-add-form input[type="url"],
  .quick-add-form input[type="search"],
  .quick-add-form select,
  .detail-actions-row input[type="text"],
  .detail-actions-row input[type="date"],
  .detail-actions-row select {
    flex: 1 1 100%;
    min-height: 44px;
    box-sizing: border-box;
  }

  .human-review-form input[type="text"] {
    max-width: 100%;
  }

  /* Mobile Polish — Capture Page Refinements. .capture-actions holds
     the file-attach control, Priority, Project, and the Capture
     button side by side in a single nowrap flex row on desktop/tablet
     (unchanged) — on phones that row is too narrow for four controls,
     which is exactly why Project (a <select> with no width rule of
     its own) rendered squeezed and misaligned. Stacking the row
     vertically here, with every child at the same full width, gives
     every field (not just Project) the same left/right alignment as
     the textarea above it, and eliminates the horizontal overflow —
     while leaving the desktop row layout completely untouched. */
  .capture-actions {
    flex-direction: column;
    align-items: stretch;
  }

  .capture-attach,
  .capture-priority-select,
  .capture-project-select,
  .capture-assigned-to-select {
    width: 100%;
    box-sizing: border-box;
  }

  .capture-priority-select,
  .capture-project-select,
  .capture-assigned-to-select {
    min-height: 44px;
  }

  /* The Capture button is already last in .capture-actions today —
     stacking the row (above) already puts it below the "Choose
     Files" control with no DOM reordering needed. This only adds the
     spacing/size/full-width Kevin asked for; the white-background/
     red-text "primary action" look itself comes from the .btn class
     added directly on the button in captures/new.html (the same
     class every other primary action in StrohOS already uses), not
     from a phone-only rule — it should look like the primary action
     on every viewport, not just phones. */
  .capture-submit {
    width: 100%;
    min-height: 48px;
    margin-top: 16px;
    box-sizing: border-box;
  }

  /* Fixed-width auth/quick-create cards (login, change password, "New
     Organization"-style forms) are all inline max-width: 420-520px —
     already <= a phone viewport in spirit, but let them use the full
     available width rather than whatever inline max-width they were
     given, since inline styles otherwise win over anything here. */
  section.card[style*="max-width"] {
    max-width: 100% !important;
  }

  /* Tables -> stacked cards. Every <td> in every table template now
     carries data-label="<column header text>", read here via
     attr(data-label) so one set of markup serves both the normal
     <table> on tablet/desktop and this card layout on phones — no
     separate mobile template, no JS. */
  table {
    display: block;
    width: 100%;
  }

  table thead {
    display: none;
  }

  table tbody,
  table tr {
    display: block;
    width: 100%;
  }

  table tr {
    margin-bottom: 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 4px 12px;
    background: var(--panel);
  }

  table td {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    text-align: right;
    padding: 10px 0;
    border: none;
    border-bottom: 1px solid var(--border);
    min-height: 44px;
    box-sizing: border-box;
  }

  table td:last-child {
    border-bottom: none;
  }

  table td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    padding-right: 10px;
    text-align: left;
    font-weight: 700;
    font-size: 13px;
    color: var(--muted);
  }

  /* A colspan empty-state row ("No Contacts yet", etc.) has no
     column to label and should read as a single line, not a
     label/value pair. */
  table td.empty-state {
    display: block;
    text-align: left;
    border-bottom: none;
  }

  table td.empty-state::before {
    content: none;
  }
}
.logo {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 28px;
    font-weight: 800;
    letter-spacing: 0.12em;
    user-select: none;
}

.logo-stroh {
    color: #F5F5F5;
}

.logo-divider {
    color: #555;
    font-weight: 400;
}

.logo-os {
    color: #C62828;
}

/* Login page logo (app/templates/auth/login.html) only — the top-nav
   .logo text wordmark above is untouched and still used everywhere
   else. Sits on a white badge because the source artwork is a
   black-on-white lockup; without it the black wordmark is nearly
   invisible against --bg. Sized well below the 1463px source width,
   so it stays sharp at any device pixel ratio without a srcset. */
.login-logo-wrap {
  display: flex;
  justify-content: center;
  margin: 22px 0 18px;
}

.login-logo-badge {
  display: inline-flex;
  background: #ffffff;
  border-radius: var(--radius);
  padding: 10px 16px;
}

.login-logo-badge img {
  display: block;
  width: 200px;
  height: auto;
}

@media (max-width: 640px) {
  .login-logo-badge img {
    width: 150px;
  }
}

.health-widget {
  min-width: 320px;
  align-items: center;
}

.health-info {
  min-width: 90px;
}

.health-score {
  font-size: 28px;
  font-weight: 800;
  line-height: 1;
}

.health-label {
  font-size: 12px;
  color: var(--muted);
}

.health-bar {
  width: 120px;
  height: 10px;
  background: var(--panel-soft);
  border-radius: 999px;
  overflow: hidden;
  border: 1px solid var(--border);
}

.health-fill {
  height: 100%;
  border-radius: 999px;
}

.health-fill.healthy {
  background: var(--green);
}

.health-fill.warning,
.health-fill.unknown {
  background: var(--yellow);
}

.health-fill.critical {
  background: var(--red);
}

.health-state {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
}

/* Standard approach for rendering original user-entered multi-line
   content exactly as typed (newlines and internal spacing) without
   converting it to HTML markup. */
.preserve-whitespace {
  white-space: pre-wrap;
}

.capture-textarea {
  width: 100%;
  min-height: 160px;
  padding: 12px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
  resize: vertical;
}

/* Project/Capture UX Refinement — deliberately smaller/lighter than
   .capture-textarea below (less height, no resize handle) so it never
   competes visually with the primary content field it sits above. */
.capture-title-input {
  display: block;
  width: 100%;
  margin-bottom: 8px;
  padding: 8px 12px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
}

.capture-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 16px;
  gap: 16px;
}

/* Capture UX Cleanup (2026-08-21) — .capture-personal-note had no
   display rule of its own; as a bare <label> (inline by default) it
   only ever looked stacked because its text happened to be long
   enough to wrap. This makes Personal Note / Attachments-deferred /
   Requires Attention each reliably render on their own line instead
   of relying on that coincidence. */
.capture-personal-note {
  display: block;
}

.capture-attach-note {
  color: var(--muted);
  font-size: 13px;
  margin-left: 6px;
}

.capture-priority {
  margin-right: 6px;
}

.capture-priority-form {
  margin: 8px 0;
}

.capture-submit:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

.capture-submit.is-ready {
  background: var(--green);
  color: #04220f;
  border: none;
  cursor: pointer;
}

.btn,
.btn:link,
.btn:visited,
.btn:active {
  display: inline-block;
  background: #ffffff !important;
  color: #C62828 !important;
  padding: 10px 18px;
  border-radius: var(--radius);
  font-weight: 700;
  text-decoration: none;
}

.btn:hover {
  opacity: 0.9;
}

.btn-secondary,
.btn-secondary:link,
.btn-secondary:visited,
.btn-secondary:active {
  background: transparent !important;
  color: var(--muted) !important;
  border: 1px solid var(--border);
}

.btn-secondary:hover {
  opacity: 0.85;
}

/* Bulk affordance discoverability fix (2026-08-12) — mirrors
   .btn-primary-action:disabled's own existing convention exactly, so
   the Identity Suggestions bulk button reads as genuinely inert at
   zero-selected rather than merely dim. */
.btn-secondary:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

/* UX1 — Mobile UI and Performance Refinements (Task 7 — Responsive
   Form and Component Consistency). Shared alert/error message class —
   the login page previously used a one-off inline
   style="color: #b91c1c;" (a third, un-tokenized red alongside
   --red); this reuses the existing --red token and is now shared by
   the login page and the Capture form's own new validation-failure
   message, rather than each page inventing its own. Status is never
   communicated by color alone here — role="alert" plus the message
   text itself carry the meaning; the red background/border are
   reinforcement, not the only signal. */
.alert-error {
  color: var(--text);
  background: rgba(239, 68, 68, 0.12);
  border: 1px solid rgba(239, 68, 68, 0.4);
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 15px;
  margin: 0 0 12px;
}

/* UX Compliance Sprint 1 (Write Action Feedback Standard) — same
   shape as .alert-error, --green instead of --red per this document's
   own established color-token reuse pattern (comment above); status
   is still never color-only, since role="status" plus the message
   text itself carry the meaning. */
.alert-success {
  color: var(--text);
  background: rgba(34, 197, 94, 0.12);
  border: 1px solid rgba(34, 197, 94, 0.4);
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 15px;
  margin: 0 0 12px;
}

/* Sprint B1 (2026-08-02) — a distinct third state alongside
   .alert-error/.alert-success: neither a clean failure (nothing
   happened) nor a clean success (everything happened) — a policy-only
   partial outcome where one part of a two-stage action succeeded and
   the other still needs attention. Same shape as its siblings, --yellow
   (this codebase's own existing warning token, already used elsewhere)
   instead of --red/--green, so it reads as "attention needed," never
   as either "failed" or "done." */
.alert-warning {
  color: var(--text);
  background: rgba(250, 204, 21, 0.15);
  border: 1px solid rgba(250, 204, 21, 0.5);
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 15px;
  margin: 0 0 12px;
}

/* Stage 1C (2026-08-09) — a fourth, non-blocking sibling of
   .alert-error/.alert-success/.alert-warning: a transient in-progress
   state (background analysis Pending/Running) that is neither good nor
   bad news, just informational. Same shape as its siblings, --accent
   (this codebase's own existing blue token) instead of red/green/yellow. */
.alert-info {
  color: var(--text);
  background: rgba(77, 163, 255, 0.12);
  border: 1px solid rgba(77, 163, 255, 0.4);
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 15px;
  margin: 0 0 12px;
}

/* UX Compliance Sprint 1 (Performance Feedback Standard) — measurable
   upload progress, shown only while a file upload is actually in
   flight (captures.js toggles [hidden]); native <progress> gets real
   assistive-tech semantics for free, unlike a styled <div>. */
.capture-upload-progress {
  width: 100%;
  height: 8px;
  margin: 4px 0 10px;
  accent-color: var(--green);
}

/* Login page (app/templates/auth/login.html) only — scoped to
   .login-form so this never touches the other ~15 forms that share
   the base .quick-add-form class elsewhere in the app. Username and
   password get identical explicit sizing, and the 44px height matches
   the touch-target convention already used for .btn-small/.quick-add-form
   inputs in the @media (max-width: 640px) block below.

   UX1 fix: the selector used to say input[type="email"] — a leftover
   from before the username migration (git log: "Sprint F2 (username
   portion): switch login from email to username") changed this field
   to type="text" without updating this rule. type="text" was silently
   falling through to .quick-add-form input[type="text"]'s own
   flex: 1 1 160px instead — harmless in that rule's own row-direction
   forms, but .login-form's inline `flex-direction: column` turns a
   160px flex-basis into a 160px *height*, which is exactly the
   "username field ~3x taller than password" defect. Selector is
   `form.login-form` rather than plain `.login-form` specifically so
   this rule's specificity (0,2,2) beats .quick-add-form's (0,2,1)
   regardless of the two rules' order in this file — a plain
   .login-form selector would tie on specificity and lose to
   .quick-add-form's later position. explicit flex: 0 1 auto neutralizes
   the inherited flex-basis directly, and explicit font-size: 16px
   avoids relying on font: inherit's silent assumption that no
   ancestor ever sets a smaller body font — both fields must never
   trigger iOS/Android's automatic zoom-on-focus for a sub-16px input. */
form.login-form input[type="text"],
form.login-form input[type="password"] {
  width: 100%;
  height: 44px;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
  font-size: 16px;
  box-sizing: border-box;
  flex: 0 1 auto;
}

/* Slightly more room between email and password than the form's
   default 8px gap (.quick-add-form). */
.login-form input[type="password"] {
  margin-top: 12px;
}

.btn-signin,
.btn-signin:link,
.btn-signin:visited,
.btn-signin:active {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 44px;
  margin-top: 12px;
  background: #ffffff;
  color: #C62828;
  border: 2px solid #C62828;
  border-radius: var(--radius);
  font-weight: 700;
  font-size: 16px;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.btn-signin:hover,
.btn-signin:focus-visible {
  background: #C62828;
  color: #ffffff;
}

.btn-signin:active {
  background: #C62828;
  color: #ffffff;
}

/* Communication detail-page UX refinement (2026-08-01) — Kevin's
   explicit request for larger, more prominent primary write-action
   buttons (Resolve outcomes, Mark Waiting, Defer, Assign, Add
   Suggested Action) than the existing plain .btn-secondary. Mirrors
   .btn-signin's own already-established white-background/red-text/
   bordered/hover-inverts shape exactly, generalized into an
   inline-usable class (no width:100%, no margin-top) rather than
   widening .btn-signin's own scope or changing .btn platform-wide —
   this is a page-scoped refinement, not a global button restyle.
   :disabled mirrors .capture-submit:disabled's existing convention;
   [aria-busy="true"] is a reserved hook for a future write-action-
   feedback JS enhancement, not wired up by this refinement. */
.btn-primary-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 12px 22px;
  background: #ffffff;
  color: #C62828;
  border: 2px solid #C62828;
  border-radius: var(--radius);
  font-weight: 700;
  font-size: 15px;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.btn-primary-action:hover,
.btn-primary-action:focus-visible {
  background: #C62828;
  color: #ffffff;
}

.btn-primary-action:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

.btn-primary-action[aria-busy="true"] {
  cursor: wait;
  opacity: 0.75;
}

/* The lower-emphasis companion to .btn-primary-action (2026-10-24, first
   used by the CCO PDF block): same shape and touch height, outlined in
   the dark theme instead of filled. Works on <button> as well as <a>
   (.btn / .btn-secondary don't set font or cursor, so a <button> with
   them keeps the browser's own font and bevel). */
.btn-secondary-action,
.btn-secondary-action:link,
.btn-secondary-action:visited {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 10px 18px;
  box-sizing: border-box;
  background: transparent;
  color: var(--text) !important;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font: inherit;
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.btn-secondary-action:hover,
.btn-secondary-action:focus-visible {
  background: var(--panel-soft);
  border-color: var(--accent);
}

.btn-secondary-action:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.btn-secondary-action:active {
  background: var(--border);
}

.btn-secondary-action:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

/* STROH "next step" convention (Kevin, 2026-09-29, locked): GREEN +
   WHITE = the recommended next human action on a screen. Prefer only
   one per screen. Same shape as .btn-primary-action (white background,
   coloured text and border, inverts on hover/focus) in green. #1B7F3B
   rather than --green (#22c55e): green text on white needs ~5:1
   contrast to stay readable; #22c55e is ~2.3:1. First used by the
   Opportunity detail page's Create Quote action and the Quote
   editor's Save Draft. */
.btn-next-step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 12px 22px;
  background: #ffffff;
  color: #1B7F3B;
  border: 2px solid #1B7F3B;
  border-radius: var(--radius);
  font-weight: 700;
  font-size: 15px;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.btn-next-step:hover,
.btn-next-step:focus-visible {
  background: #1B7F3B;
  color: #ffffff;
}

/* Opportunity detail action stack: Create Quote, then Award & Create
   Project (or View Project), then Archive — one per line. */
.opportunity-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  margin: 16px 0;
}

/* Quote Editor Phase 1 (2026-09-29). */
.quote-context-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 10px 20px;
  margin: 0;
}

.quote-context-grid dt {
  color: var(--muted);
  font-size: 13px;
}

.quote-context-grid dd {
  margin: 0;
  font-weight: 600;
}

.quote-field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0 20px;
}

/* Quote Editor layout pass (2026-10-01). Who the quote is for is the
   most important check on the page: a full-width Customer / GC control
   with the selected name in STROH red (never green — this is "stop and
   confirm", not a next step), Contact directly below it, then Quote Date
   | Valid Until | PO / Reference on one row. */
.quote-customer-field,
.quote-contact-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 0 16px;
}

.quote-customer-field label,
.quote-contact-field label {
  font-weight: 600;
}

.quote-customer-field .searchable-select {
  max-width: none;
}

.quote-customer-field .searchable-select-input,
.quote-customer-field select {
  width: 100%;
  min-height: 52px;
  padding: 10px 40px 10px 14px;
  background: #ffffff;
  color: #C62828;
  border: 2px solid #C62828;
  border-radius: 10px;
  font-size: 18px;
  font-weight: 700;
}

.quote-contact-field select {
  width: 100%;
  max-width: 720px;
  min-height: 44px;
  font-size: 16px;
}

.quote-date-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 20px;
}

.quote-date-row input {
  width: 100%;
}

/* Line items: explicit column widths (table-layout: fixed) so
   Description gets the room. Unit Cost / Quantity / Line Total are sized
   for "1,222,333.00" plus padding (8.5rem), Remove is minimal, Item is
   medium and two lines tall. */
.quote-line-items {
  width: 100%;
  table-layout: fixed;
}

.quote-col-item {
  width: 16%;
}

.quote-col-unit-cost,
.quote-col-quantity,
.quote-col-line-total {
  width: 8.5rem;
}

.quote-col-remove {
  width: 6rem;
}

.quote-line-items textarea {
  resize: vertical;
}

/* Item is a single-line value in a two-line box: long identifiers wrap
   on screen (even without spaces); line breaks are never stored. */
.quote-line-item-code {
  overflow-wrap: anywhere;
}

.quote-line-items [name="line_unit_cost"],
.quote-line-items [name="line_quantity"] {
  text-align: right;
}

.quote-number-readonly {
  font-weight: 700;
}

.quote-line-items input,
.quote-line-items textarea,
.quote-narrative {
  width: 100%;
}

/* Scope of Work / Inclusions / Exclusions — customer-facing text that
   needs careful proofreading (Quote Editor workflow increment,
   2026-10-01): a predictable ~11-line box (rows="11") at 16px with
   comfortable line spacing, identical for all three. No auto-grow. */
.quote-narrative {
  font-size: 16px;
  line-height: 1.5;
}

.quote-line-total,
#quote-subtotal {
  text-align: right;
  white-space: nowrap;
}

.quote-subtotal-label {
  text-align: right;
  font-weight: 700;
}

.quote-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin: 16px 0 32px;
}

.quote-error {
  color: #b91c1c;
}

.quote-saved-notice {
  color: var(--green);
  font-weight: 700;
}

/* Quote Workspace increment (2026-09-29): the Opportunity Quotes box —
   one compact row per GC/customer submission, so ten or more can be
   reviewed at once. StrohOS status and observed Invoice Ninja state sit
   on separate lines and are never merged. */
.quote-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.quote-item {
  display: grid;
  grid-template-columns: minmax(200px, 2fr) minmax(180px, 1.5fr) auto;
  gap: 8px 20px;
  align-items: center;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
}

.quote-item-saved {
  border-color: var(--green);
}

.quote-item p {
  margin: 0;
}

.quote-item-customer {
  font-weight: 700;
}

.quote-item-title {
  color: var(--muted);
}

.quote-item-subtotal {
  font-weight: 700;
}

.quote-item-label {
  color: var(--muted);
  font-size: 13px;
}

/* Quote Delivery's read-only preview: same red/white family as the push
   action beside it, one step smaller (Decision 023). */
/* Ninja / Audit at the bottom of the quote editor (2026-10-01): compact
   when current; a STROH-red edge when Ninja needs a person. */
.quote-ninja-audit {
  font-size: 14px;
}

.quote-ninja-audit #invoice-ninja-panel {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 4px 24px;
}

.quote-ninja-audit #invoice-ninja-panel p {
  margin: 4px 0;
}

.quote-ninja-attention {
  border-left: 4px solid #C62828;
}

.quote-actions .quote-preview-action {
  min-height: 40px;
  padding: 8px 16px;
  font-size: 14px;
  align-self: center;
}

.quote-item-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-end;
}

.quote-item-actions .btn-primary-action {
  min-height: 36px;
  padding: 6px 14px;
  font-size: 14px;
}

/* Quote actions with Ninja document actions (2026-10-01): an explicit
   2x2 grid, never incidental flex wrapping or auto-placement. Edit and
   Copy keep exactly the top-row places they have on every quote; the
   Ninja document actions are added underneath —
     row 1 = ✏️ Edit (col 1)        | Copy for Another GC (col 2)
     row 2 = 👀 Preview PDF (col 1) | 🔄 Refresh Ninja Status (col 2)
   Every action is placed by explicit grid-row AND grid-column, keyed on
   its data-action, so source order cannot move it. The row wrappers
   carry the structure in the markup and are display: contents. An
   absent action leaves no element behind (its cell stays empty). */
.quote-item-actions-grid {
  display: grid;
  grid-template-columns: repeat(2, max-content);
  grid-auto-flow: row;
  gap: 8px;
  justify-content: end;
}

.quote-action-row {
  display: contents;
}

.quote-item-actions-grid [data-action="edit-quote"] {
  grid-row: 1;
  grid-column: 1;
}

.quote-item-actions-grid [data-action="copy-quote"] {
  grid-row: 1;
  grid-column: 2;
}

.quote-item-actions-grid [data-action="preview-pdf"] {
  grid-row: 2;
  grid-column: 1;
}

.quote-item-actions-grid [data-action="refresh-ninja-form"] {
  grid-row: 2;
  grid-column: 2;
}

.quote-item-actions-grid .inline-form {
  margin: 0;
}

.quote-item-actions-grid .btn-primary-action {
  width: 100%;
  white-space: nowrap;
}

.quote-title-input {
  width: 100%;
  max-width: 640px;
}

@media (max-width: 800px) {
  .quote-date-row {
    grid-template-columns: 1fr;
  }

  .quote-line-items {
    table-layout: auto;
  }

  .quote-item {
    grid-template-columns: 1fr;
  }

  .quote-item-actions {
    justify-content: flex-start;
  }

  .quote-item-actions-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    justify-content: stretch;
  }

  .quote-item-actions-grid .btn-primary-action {
    white-space: normal;
  }
}

@media (max-width: 480px) {
  .quote-item-actions-grid {
    grid-template-columns: 1fr;
  }

  .quote-item-actions-grid [data-action] {
    grid-row: auto;
    grid-column: auto;
  }
}

/* O&M Browser UX + Cover/TOC Polish (2026-09-14, Part B) — the
   Project Equipment/O&M page's own 5 workflow actions (Add Equipment,
   Package Sections, Project Participants, Import O&M Package,
   Supplier/Manufacturer Profiles), previously small inline
   .btn-secondary text links, now full-width .btn-primary-action
   buttons in a fixed 3-column desktop grid (3 + 2 wrap with the
   current 5 actions) — mirrors .wizard-outcome-grid's own established
   "fixed column count on desktop, narrower auto-fill below the
   800px mobile breakpoint" shape exactly, reusing .btn-primary-action
   itself rather than a third button palette. */
.equipment-om-action-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin: 0.75rem 0;
}

.equipment-om-action-grid .btn-primary-action {
  width: 100%;
}

@media (max-width: 800px) {
  .equipment-om-action-grid {
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  }
}

/* O&M Final Close-Out Polish (2026-09-15, Part B) — the Project
   Equipment/O&M page's own package-creation actions (Create Approval
   Package / Create O&M Package), split into their own card, now two
   equal-width buttons side by side on desktop — same fixed-column-
   count-then-narrower-mobile-wrap shape as .equipment-om-action-grid
   immediately above, just 2 columns instead of 3, and each column
   holds an .inline-form (a real <form>, not a bare button) so the
   grid needs to size the FORM itself full-width, not (only) the
   button inside it. */
.equipment-om-package-creation-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin: 0.75rem 0;
}

.equipment-om-package-creation-grid .inline-form {
  width: 100%;
}

.equipment-om-package-creation-grid .btn-primary-action {
  width: 100%;
}

@media (max-width: 600px) {
  .equipment-om-package-creation-grid {
    grid-template-columns: 1fr;
  }
}

/* Communication detail-page UX refinement (2026-08-01, Round 2) — the
   four Resolution outcome buttons (Action Confirmed / No Action
   Required / Already Handled / FYI) previously sized to their own
   label's width, so "FYI" looked visibly smaller than the others.
   Sized to the longest label ("No Action Required") plus padding;
   .action-buttons' own flex-wrap still applies, so this still wraps
   cleanly on narrow screens rather than overflowing. Scoped to this
   one button group only — Mark Waiting/Defer/Assign, each alone in
   its own form, have no sibling to visually match widths against. */
.action-buttons-resolution .btn-primary-action {
  min-width: 200px;
  text-align: center;
}

/* Communication detail-page UX refinement (2026-08-01, Round 2) — the
   Workflow section's two forms (Waiting, Deferred) previously flowed
   one after another with no visual boundary, reading like a single
   continuous database form. Each now sits in its own light, bordered
   box with a small heading, so the grouping Kevin described ("Waiting
   On / Expected Response / Notes / Mark Waiting" as one group,
   "Reminder Date / Notes / Defer" as another) is visually obvious at
   a glance — presentation only, no change to either form's own
   fields, action, or submit behavior. */
.workflow-subgroup {
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 12px;
}

.workflow-subgroup:last-child {
  margin-bottom: 0;
}

.workflow-subgroup h4 {
  margin: 0 0 4px;
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.workflow-subgroup .subtitle {
  margin: 0 0 10px;
  font-size: 14px;
}

.workflow-subgroup .action-form {
  margin-bottom: 0;
}

/* Communication detail-page UX refinement (2026-08-01, Round 2) — the
   Suggested Action "Action" field previously sized to the browser's
   own bare-input default (~20 characters), too narrow for real
   entries ("Issue PO to Gescan", "Request updated IFC drawings").
   Given the widest flex-basis in the row; Owner/Due Date/Urgency/
   Response Needed stay compact. .action-form's own flex-wrap still
   applies, so narrow screens still wrap each field to its own line
   rather than overflowing. */
.suggested-action-text-field {
  flex: 1 1 320px;
}

.suggested-action-text-input {
  width: 100%;
  min-width: 240px;
}

.suggested-action-compact-field {
  flex: 0 1 160px;
}

/* UX1 — Task 7/10: respects prefers-reduced-motion. .btn-signin's
   hover/focus transition (the only transition anywhere in this
   stylesheet, confirmed by direct search) is otherwise harmless, but
   the rule is written generically so any transition/animation added
   later is covered automatically rather than needing its own
   media query. */
@media (prefers-reduced-motion: reduce) {
  * {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

.detail-actions {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 20px;
}

.detail-actions-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

table {
  border-collapse: collapse;
}

table th,
table td {
  padding: 12px 20px;
  text-align: left;
}

.col-quote {
  padding-right: 32px;
}

.col-opportunity {
  width: 36%;
  padding-right: 32px;
}

.col-addendum,
.col-gc-count {
  text-align: center;
}

.col-gc-count {
  padding-right: 32px;
}

/* UX Pass (Kevin's 2026-09-01 locked request) — was text-decoration:
   none with underline only on :hover, so the correct (already
   white/var(--text)) link color alone didn't read as clickable at
   rest. Always underlined now, matching .billing-document-link's own
   standard StrohOS link treatment. */
.opportunity-link,
.opportunity-link:link,
.opportunity-link:visited,
.opportunity-link:active,
.opportunity-link:hover {
  color: var(--text) !important;
  text-decoration: underline;
}

.project-link,
.project-link:link,
.project-link:visited,
.project-link:active {
  color: var(--text) !important;
  text-decoration: none;
}

.project-link:hover {
  text-decoration: underline;
}

.note-row {
  list-style: none;
}

.note-link,
.note-link:link,
.note-link:visited,
.note-link:active {
  display: block;
  padding: 14px 12px;
  color: var(--text) !important;
  text-decoration: none;
}

.note-link:hover {
  background: var(--panel-soft);
  text-decoration: underline;
}

.edit-history {
  margin-top: 32px;
  font-size: 13px;
  color: var(--muted);
}

.edit-history h3 {
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 8px;
}

.edit-history ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.edit-history li {
  padding: 4px 0;
  border-top: 1px solid var(--border);
}

/* Morning Scan */

.quick-add-form {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-top: 16px;
}

.quick-add-form input[type="text"],
.quick-add-form input[type="search"],
.quick-add-form select {
  flex: 1 1 160px;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
}

.manual-entry-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.manual-entry-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}

.manual-entry-row:first-child {
  border-top: none;
}

.manual-entry-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.manual-entry-who {
  font-weight: 700;
}

/* Communication Waiting (Requires Attention refinement, 2026-08-09) —
   the who/what/follow-up context lines beneath a Communication Waiting
   entry, same muted small-text treatment already used throughout this
   codebase for secondary metadata. */
.manual-entry-meta {
  font-size: 13px;
  color: var(--muted);
}

.btn-small {
  padding: 4px 10px;
  font-size: 13px;
}

.ready-status {
  font-weight: 700;
}

.ready-status.ready {
  color: var(--green);
}

.ready-status.gap {
  color: var(--yellow);
}

.empty-state {
  color: var(--muted);
  font-style: italic;
  list-style: none;
}

/* Project Detail Photos & Files Refinement (2026-08-08) —
   app/templates/projects/photos.html's visible thumbnail grid. Unlike
   #capture-photo-gallery above (permanently hidden, data-source only),
   this grid IS #project-photo-gallery: its own <a> tiles are both what
   the user sees and what capture-photo-viewer.js reads. auto-fill
   keeps tiles a consistent size and wraps cleanly at any width,
   including mobile, without a dedicated media query. */
.project-photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 8px;
}

.project-photo-grid-item {
  display: block;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  aspect-ratio: 1 / 1;
}

.project-photo-grid-thumb {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* UX Sprint 1 — Capture detail attachment summary. "📷 N Photos" (now
   .capture-photo-viewer-open, Capture Photo Viewer Refinement) opens
   the modal viewer defined further below; #capture-photo-gallery
   itself (app/templates/captures/detail.html) stays permanently
   `hidden` — it is only ever a data source
   (app/static/js/capture-photo-viewer.js reads it), never revealed
   inline on the page. */
.capture-attachment-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

.capture-photo-viewer-open,
.capture-attachment-file-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 8px 14px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
  text-decoration: none;
}

.capture-photo-viewer-open {
  cursor: pointer;
}

.capture-photo-viewer-open:hover,
.capture-attachment-file-link:hover {
  background: var(--panel);
}

/* Capture Photo Viewer Refinement — the modal viewer itself.
   app/static/js/capture-photo-viewer.js builds this <dialog> once,
   lazily, the same way confirm-dialog.js already does — showModal()
   gives focus containment and Escape-to-close for free. */
.capture-photo-viewer {
  border: none;
  border-radius: 12px;
  padding: 0;
  background: var(--panel);
  color: var(--text);
  width: min(960px, 100vw);
  height: min(720px, 100vh);
  max-width: 100vw;
  max-height: 100vh;
  flex-direction: column;
}

/* Deliberately scoped to [open], not the bare selector above: an
   author-stylesheet `display` on the bare .capture-photo-viewer would
   override the browser's own `dialog:not([open]) { display: none; }`
   default (author rules beat UA rules regardless of selector
   specificity), making the built-but-unopened dialog visible on every
   page load instead of staying hidden until showModal() is called. */
.capture-photo-viewer[open] {
  display: flex;
}

.capture-photo-viewer::backdrop {
  background: rgba(6, 10, 18, 0.72);
}

@media (max-width: 640px) {
  .capture-photo-viewer {
    width: 100vw;
    height: 100vh;
    border-radius: 0;
  }
}

/* Full Size / Standard Size toggle (app/static/js/capture-photo-viewer.js
   toggleFullSize()) adds/removes this class on the dialog itself.
   Fixes a real defect: the review and full-size images previously
   shared the exact same fixed-size .capture-photo-viewer-image-wrap
   box (object-fit: contain scales either source down to identical
   on-screen dimensions), so switching tiers changed nothing visible —
   only the source resolution differed, imperceptibly. Full Size now
   grows the dialog itself to most of the viewport; object-fit: contain
   on .capture-photo-viewer-image (unchanged) continues to preserve
   aspect ratio at the new, larger box size. Two classes beats both the
   unqualified base rule and the mobile media-query rule above on
   specificity, so this wins on every viewport without !important.
   Standard remains the default on every photo and on every navigation
   (goTo() always resets viewingFull to false) — this rule only ever
   applies while a user has explicitly asked for the larger view. */
.capture-photo-viewer.is-full-size {
  width: min(98vw, 1800px);
  height: 94vh;
}

/* Full Size reclaims the thumbnail strip's space for the image itself
   — inspecting one photo closely, not browsing, is the point of this
   mode. Navigating away (prev/next, swipe, or a strip click) already
   resets viewingFull to false before the strip would be needed again,
   so it reappears the moment ordinary browsing resumes. */
.capture-photo-viewer.is-full-size .capture-photo-viewer-strip {
  display: none;
}

.capture-photo-viewer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}

.capture-photo-viewer-position {
  color: var(--muted);
  font-size: 14px;
}

.capture-photo-viewer-close {
  min-height: 44px;
  padding: 8px 14px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}

.capture-photo-viewer-close:hover {
  background: var(--panel);
}

.capture-photo-viewer-main {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: stretch;
}

.capture-photo-viewer-image-wrap {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: 0;
  background: rgba(0, 0, 0, 0.2);
  touch-action: pan-y;
}

.capture-photo-viewer-image {
  display: block;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.capture-photo-viewer-fallback {
  color: var(--muted);
  padding: 24px;
  text-align: center;
}

.capture-photo-viewer-prev,
.capture-photo-viewer-next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  min-width: 44px;
  min-height: 44px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(17, 26, 43, 0.75);
  color: var(--text);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

.capture-photo-viewer-prev {
  left: 12px;
}

.capture-photo-viewer-next {
  right: 12px;
}

.capture-photo-viewer-prev:hover,
.capture-photo-viewer-next:hover {
  background: rgba(17, 26, 43, 0.95);
}

.capture-photo-viewer-prev:disabled,
.capture-photo-viewer-next:disabled {
  opacity: 0.3;
  cursor: default;
}

.capture-photo-viewer-actions {
  display: flex;
  gap: 8px;
  padding: 10px 16px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}

.capture-photo-viewer-strip {
  display: flex;
  gap: 8px;
  padding: 10px 16px;
  overflow-x: auto;
  flex: 0 0 auto;
}

.capture-photo-viewer-strip-item {
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  padding: 0;
  border-radius: 8px;
  border: 2px solid transparent;
  background: var(--panel-soft);
  overflow: hidden;
  cursor: pointer;
}

.capture-photo-viewer-strip-item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.capture-photo-viewer-strip-item.is-current {
  border-color: var(--text);
}

.capture-photo-viewer-strip-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  color: var(--muted);
  font-size: 18px;
}

/* UX Sprint 1 — Load More loading state. A lightweight pulse on the
   control itself (no separate skeleton row, nothing else moves —
   "avoid jumping layouts") while captures.js's fetch is in flight.
   Respects prefers-reduced-motion automatically via the existing
   global @media rule above, which already overrides any
   animation-duration site-wide. */
#capture-load-more[aria-busy="true"] {
  animation: capture-load-more-pulse 1.1s ease-in-out infinite;
  pointer-events: none;
}

@keyframes capture-load-more-pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.55;
  }
}

.capture-timeline-entry {
  list-style: none;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.capture-timeline-entry:last-child {
  border-bottom: none;
}

/* Capture Observations Milestone 1 (2026-08-05) — mirrors
   .capture-timeline-entry's own list/spacing/separator shape exactly,
   a distinct class only because it lives in a different list (the
   detail page's own accumulating-story section, not the Timeline). */
.capture-observation-list {
  margin: 8px 0;
  padding: 0;
}

.capture-observation {
  list-style: none;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.capture-observation:last-child {
  border-bottom: none;
}

.capture-timeline-icon {
  margin-right: 6px;
}

/* Sprint R2.1 — Capture List Cleanup. Scoped under .capture-list so
   the shared .note-row/.note-link rules (also used by
   sparky_items/detail.html and actions/detail.html) are never touched
   — only the Capture list page's own rows change. */
.capture-list .note-row {
  margin-bottom: 6px;
}

.capture-list .note-link {
  padding: 16px 12px;
}

.capture-row-title {
  margin-bottom: 4px;
}

.capture-row-meta {
  font-size: 13px;
  color: var(--muted);
}

/* Capture UX (2026-10-03): the related Project / Opportunity leads the
   row at the same weight as the title (never small grey metadata); the
   personal pin control sits beside the row link. Same layout on phone. */
.capture-list .note-row {
  display: flex;
  align-items: flex-start;
  gap: 4px;
}
.capture-list .note-row .note-link {
  flex: 1;
  min-width: 0;
}
.capture-row-relation {
  font-size: 16px;
  color: var(--text);
}
.capture-row-subject {
  font-size: 16px;
  margin-bottom: 4px;
  overflow-wrap: anywhere;
}
.note-link.capture-row-pinned {
  border-left: 3px solid var(--text);
}
.capture-pin-form {
  margin: 0;
  padding-top: 12px;
}
.capture-pin-button {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  padding: 6px;
  opacity: 0.45;
}
.capture-pin-button.is-pinned,
.capture-pin-button:hover,
.capture-pin-button:focus-visible {
  opacity: 1;
}
.capture-search-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 8px 0 12px;
}
.capture-search-input {
  flex: 1 1 240px;
  min-width: 0;
}
.capture-pinned-filter.is-active {
  border-color: var(--text);
}

/* Queue UX Sprint A (2026-08-01) — Communication queue card, replacing
   the old wide <table>. Reuses the shared .note-row/.note-link base
   (list-style:none, block padding, hover background — see the
   .capture-list/.project-list precedents above) rather than inventing
   a new row shape; only the internal layout below is new. Same markup
   at every viewport — no @media split, no separate mobile template —
   since nothing here is a fixed-width table column to begin with.

   Priority (2026-08-01, Kevin's explicit Sprint A refinement): a
   future left-edge priority indicator is reserved as CSS-only —
   .communication-row-priority-high/-medium/-low below — deliberately
   unapplied to any element by any template this sprint. No base
   border-left rule exists on .communication-row itself (that would
   render as a visible-but-empty placeholder strip, which Kevin
   explicitly ruled out); only these three modifier rules exist,
   matching nothing until a future sprint's real `priority` field adds
   the class conditionally. */
.communication-list .note-link {
  padding: 12px 14px;
}

.communication-row-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 4px 12px;
}

.communication-number {
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
}

.communication-row-title {
  margin-top: 4px;
  font-weight: 700;
}

/* Sprint 3.1 (Queue-First, 2026-08-05) — To/From as their own line,
   directly beneath Subject, per the approved row hierarchy
   (CommunicationIntelligence_QueueFirst_Architecture_v1.md §6.2). */
.communication-row-recipients {
  margin-top: 2px;
  display: flex;
  flex-wrap: wrap;
  gap: 2px 16px;
  font-size: 13px;
  color: var(--muted);
}

.communication-row-summary {
  margin-top: 2px;
  color: var(--muted);
  font-size: 13px;
}

/* Sprint 3.2 (Communications Queue throughput) — AI summary/confidence
   directly in the queue row, so a reviewer can decide whether to open
   a Communication without opening it. Reuses .status-dot's own
   healthy/warning/critical/unknown mapping (already used on the
   detail page's AI Analysis card) rather than a second confidence
   indicator. */
.communication-row-ai {
  margin-top: 4px;
  display: flex;
  align-items: flex-start;
  gap: 6px;
  font-size: 13px;
  color: var(--muted);
}

.communication-row-ai .status-dot {
  width: 9px;
  height: 9px;
  margin-top: 3px;
  flex-shrink: 0;
}

.communication-row-ai-tier {
  font-weight: 700;
  white-space: nowrap;
}

/* Mobile layout correction (2026-08-09) — without an explicit flex
   basis, this summary text's own default flex sizing (0 1 auto) can
   be forced to overflow rather than wrap once the row is narrow
   enough (checkbox + card content sharing a phone-width line). flex:
   1 1 auto with min-width: 0 lets it both grow to fill the remaining
   space next to the status dot/tier and wrap normally instead. */
.communication-row-ai-summary {
  flex: 1 1 auto;
  min-width: 0;
}

.communication-row-meta {
  margin-top: 4px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 4px 12px;
  font-size: 13px;
  color: var(--muted);
}

/* Sprint 3.1 (Queue-First, 2026-08-05) — the row-level quick-action
   bar (§20/§21) sits outside .note-link (a <form>/<button> cannot
   legally nest inside an <a>), so it needs its own matching horizontal
   padding to line up with the card content above it. Reuses
   .action-buttons for the flex/gap layout itself. */
.communication-row-quick-actions {
  /* Mobile layout correction (2026-08-09) — forces this block onto its
     own full-width flex line within .note-row (see that rule's own
     comment), instead of sharing a line with the checkbox and card
     content. flex-grow/flex-shrink 0 so it never stretches or
     compresses beyond its own content once wrapped. */
  flex: 0 0 100%;
  margin-top: 4px;
  padding: 0 12px 12px;
}

.communication-row-priority-high {
  border-left: 3px solid var(--red);
}

.communication-row-priority-medium {
  border-left: 3px solid var(--yellow);
}

.communication-row-priority-low {
  border-left: 3px solid var(--border);
}

/* Identity Suggestions queue row (2026-08-12 UX pass) — reuses the
   shared .note-row/.note-link base exactly like .communication-row-*
   above; only the internal layout below is new. Leads with the
   sender's own name (the "who emailed us" question), never the
   Communication number — that stays in the meta line only. */
.identity-suggestion-row-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 4px 12px;
}

.identity-suggestion-row-name {
  font-weight: 700;
}

.identity-suggestion-row-age {
  color: var(--muted);
  font-size: 13px;
  white-space: nowrap;
}

.identity-suggestion-row-email {
  margin-top: 2px;
  font-size: 13px;
  color: var(--muted);
}

.identity-suggestion-row-suggestion {
  margin-top: 6px;
}

.identity-suggestion-row-meta {
  margin-top: 4px;
  font-size: 13px;
  color: var(--muted);
}

/* Bulk Reject UX (2026-08-12) — a checkbox now sits alongside each
   row's existing clickable card, same reasoning and same rules as
   .communication-list's own bulk-checkbox layout above (a <form>/
   <input> cannot nest inside .note-link's own <a>, so this is laid
   out as a flex row rather than the base .note-row's plain block
   stacking, with flex-wrap so narrow/mobile widths stack instead of
   overlapping). .row-select/.row-select-checkbox/.bulk-action-bar
   themselves are already generic, unscoped rules (defined above for
   the Communications queue) and are reused here as-is. */
.identity-suggestion-list .note-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 4px;
}

.identity-suggestion-list .note-row .note-link {
  flex: 1 1 auto;
  min-width: 0;
}

/* Bulk affordance discoverability fix (2026-08-12) — the shared
   .bulk-action-bar rule above stays hidden until .visible is added
   (the Communications queue's own, unchanged behavior); this queue's
   own bar is deliberately different, per Kevin's explicit instruction
   — always visible, so the checkboxes never read as purposeless, with
   the button itself (not the bar) reflecting the disabled/enabled
   state. An id selector's specificity beats the shared class rule
   without touching or overriding it for any other page. */
#identity-suggestion-bulk-bar {
  display: flex;
}

/* Identity Suggestion detail-page decision summary (2026-08-12 UX
   pass) — the compact "who is this, what does StrohOS want me to do"
   block every action button now sits directly beneath, replacing the
   former three sequential <h2> sections. Plain text, not boxed — this
   is the top of the page's own content, not a nested card. */
.identity-decision-summary {
  margin-bottom: 20px;
}

.identity-decision-name {
  font-size: 20px;
  font-weight: 700;
  margin: 0;
}

.identity-decision-email,
.identity-decision-domain {
  margin: 2px 0 0;
  color: var(--muted);
}

.identity-decision-verdict {
  margin-top: 12px;
  font-size: 16px;
}

/* Evidence Detail (2026-08-12 UX pass) — moved to the bottom of the
   page, below every action; this small treatment keeps it visually
   quiet/secondary rather than competing with the decision above it. */
.identity-evidence-detail {
  margin-top: 28px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 14px;
}

.identity-evidence-detail summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--text);
}

/* Communication detail-page UX refinement (2026-08-01, Round 2) — the
   message header now reads like an email client (Kevin's explicit
   comparison to Outlook/Gmail): one prominent "Sender — Subject"
   heading, the address in small muted text directly beneath it. No
   separate "Original Sender" label — the heading itself carries that
   meaning by position and size. Delivered To/Reply-To remain a
   distinct, secondary block below (still using the label+value
   .communication-sender-group shape from Round 1) since those answer
   a different question (which internal workflow/mailbox received it)
   Kevin explicitly did not ask to change this round. */
.communication-message-header {
  margin-bottom: 8px;
}

.communication-message-heading {
  margin: 0 0 4px;
  color: var(--text);
  font-size: 20px;
  font-weight: 700;
  line-height: 1.3;
  word-break: break-word;
}

.communication-message-header .communication-sender-address {
  margin: 0;
  color: var(--muted);
  font-size: 14px;
  word-break: break-word;
}

/* Message Thread UX Refinement (2026-08-08) — long email bodies must
   wrap within the card on any viewport, never force horizontal
   scrolling on mobile now that the message body sits in prime screen
   space. Scoped to this one class (the only <pre> in the codebase)
   rather than a bare `pre` rule. */
.communication-message-body {
  white-space: pre-wrap;
  word-break: break-word;
  overflow-wrap: anywhere;
}

/* Organization/Contact/Project association line, prime space directly
   above the message thread viewer. */
.communication-associations {
  margin-top: -8px;
}

/* Earlier-messages disclosure — same de-emphasized <details>/<summary>
   treatment as .wizard-advanced-controls/.analysis-details, plus a
   custom marker and Show/Hide label swap (pure CSS, no JS) so the
   control reads "▼ Show N earlier messages" collapsed and "▲ Hide
   earlier messages" expanded. */
.communication-earlier-messages {
  margin-top: 16px;
}

.communication-earlier-messages summary {
  cursor: pointer;
  color: var(--muted);
  font-weight: 700;
  list-style: none;
}

.communication-earlier-messages summary::-webkit-details-marker {
  display: none;
}

.communication-earlier-messages summary::before {
  content: "▼ ";
}

.communication-earlier-messages[open] summary::before {
  content: "▲ ";
}

.communication-earlier-messages summary .communication-earlier-messages-hide {
  display: none;
}

.communication-earlier-messages[open] summary .communication-earlier-messages-show {
  display: none;
}

.communication-earlier-messages[open] summary .communication-earlier-messages-hide {
  display: inline;
}

.communication-earlier-messages[open] summary {
  margin-bottom: 14px;
}

/* Delivered To / Reply-To — Round 1's own secondary sender block,
   unchanged in shape, just no longer paired with a "primary" group
   (the heading above replaces that). Plain flex-column, no fixed
   widths, stacks cleanly on narrow screens with no separate mobile
   markup needed. */
.communication-sender-block {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 16px;
}

.communication-sender-group {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.communication-sender-label {
  margin: 0;
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.communication-sender-secondary .communication-sender-address {
  margin: 0;
  color: var(--muted);
  font-size: 14px;
  word-break: break-word;
}

/* UX Sprint 1 — Projects list refinement. Scoped under .project-list,
   same convention .capture-list already uses, so the shared
   .note-row/.note-link rules stay untouched for every other page that
   reuses them. One line, never two: the row is a flex container so the
   name (flex:1, min-width:0 — required for text-overflow to work
   inside a flex item) truncates with an ellipsis instead of wrapping,
   while the job number (flex-shrink:0) never itself truncates. */
.project-row-link {
  display: flex;
  align-items: center;
  gap: 12px;
  white-space: nowrap;
  min-height: 44px;
}

.project-row-number {
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.project-row-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Slice B (2026-10-29) — derived Project Stage pill. A sibling of the
   row link (never nested inside it), so the row is a flex line; the pill
   never shrinks, the name keeps truncating. Tones reuse the palette:
   accent = money/billing work, yellow = paused, red = blocked, green =
   ready to archive, muted = active/archived. */
.project-list .note-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.project-list .note-row .note-link {
  flex: 1;
  min-width: 0;
}

.project-stage-pill,
.project-stage-pill:link,
.project-stage-pill:visited {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px 10px;
  font-size: 0.85em;
  font-weight: 700;
  white-space: nowrap;
  color: var(--muted);
  text-decoration: none;
}

.project-stage-money { color: var(--accent); border-color: var(--accent); background: rgba(77, 163, 255, 0.12); }
.project-stage-paused { color: var(--yellow); border-color: var(--yellow); background: rgba(250, 204, 21, 0.12); }
.project-stage-blocked { color: var(--red); border-color: var(--red); background: rgba(239, 68, 68, 0.12); }
.project-stage-ready { color: var(--green); border-color: var(--green); background: rgba(34, 197, 94, 0.12); }

.project-timecard-warning {
  flex-shrink: 0;
  cursor: help;
  font-size: 0.85em;
  white-space: nowrap;
  color: var(--yellow);
}

.project-timecard-integration { color: var(--red); }

.project-timecard-compact { font-size: 1em; line-height: 1; }

/* Sprint R1.1 — reusable confirmation dialog (app/static/js/confirm-dialog.js).
   Only the container/backdrop need styling; buttons reuse .btn/.btn-secondary
   as-is, so a confirm dialog looks like every other StrohOS surface. */
.confirm-dialog {
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 24px;
  max-width: 420px;
  width: 90vw;
}

.confirm-dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

.confirm-dialog-message {
  font-weight: 700;
  margin: 0 0 8px;
}

.confirm-dialog-detail {
  margin: 0 0 20px;
}

.confirm-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

/* Communication Review Queue (Sprint 2.3) — the one new filter-form
   layout this sprint introduces; every other input/select/button
   already has its own base styling, this just lays them out inline
   with breathing room instead of stacking one-per-line by default. */
.filter-form {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-bottom: 16px;
}

/* Communication Review Queue (Sprint 2.4) — the Resolve/Workflow/
   Assignment action forms on the detail page. .action-form overrides
   .inline-form's single-row centering (fine for a bare select+button,
   not for a form mixing stacked labels, a textarea, and buttons). */
.action-group {
  margin-bottom: 20px;
}

.action-group:last-child {
  margin-bottom: 0;
}

.action-form {
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
  margin-bottom: 12px;
}

.action-form label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-weight: 600;
  font-size: 0.9em;
}

/* Structured Waiting display-name fallback (Sprint 3.2, code-review
   correction) — deliberately styled as secondary/lighter than the
   Organization/Contact <select> it sits beside, so a reviewer never
   mistakes "type a name here" for the same kind of action as picking
   an existing directory record. Kevin's explicit requirement: make it
   obvious the fallback is not a directory record. */
.waiting-on-fallback-field {
  font-weight: 400;
  color: var(--muted);
  font-size: 0.85em;
}

/* Dark-theme fix (2026-08-12, Identity Suggestions UX pass) — this
   rule previously set only `font: inherit`, so every text/email input
   and textarea inside .action-form (Identity Suggestions,
   communications/detail.html, junk_confirm.html) rendered with raw
   browser-default light styling — a white box with default text
   color — inside this app's dark theme. Same border/background/color/
   radius/padding treatment already established for exactly this kind
   of free-text field elsewhere (.human-review-form input[type="text"],
   .quick-add-form input[type="text"]) — reused, not reinvented. */
.action-form input[type="text"],
.action-form input[type="email"],
.action-form input[type="tel"],
.action-form select,
.action-form textarea {
  font: inherit;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
}

.action-form input[type="text"],
.action-form input[type="email"],
.action-form input[type="tel"] {
  min-width: 220px;
}

.action-form textarea {
  width: 100%;
  max-width: 480px;
}

.action-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Process Communication outcome grid (2026-08-03; sized down, Layout
   Correction 2026-08-08 — this workflow is primarily performed on
   desktop, so a shorter, tighter card reads better than the original
   large touch-target sizing). Reuses .btn-primary-action's own white/
   red-text/red-border/red-fill-on-hover shape (already exactly Kevin's
   specified button style) rather than inventing a second button
   palette, sized into a card with a title + description line instead
   of a single-line label. Works for both <button> (the six JS-driven
   outcomes) and <a> (Junk, which exits straight into its own existing
   confirmation route, and Skip, a plain navigation link) — same
   visual treatment regardless of element type. */
.wizard-outcome-grid {
  display: grid;
  /* Layout Correction (2026-08-09) — an explicit 4-column desktop
     layout (two compact rows of four for the eight choices), matching
     Kevin's own "approximately two compact rows of four" direction,
     rather than auto-fill's variable column count. Falls back to a
     narrower minmax below the app's existing 800px mobile breakpoint
     (see .console-header's own identical breakpoint further up this
     file) so it still wraps cleanly on a phone. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
}

@media (max-width: 800px) {
  .wizard-outcome-grid {
    grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  }
}

.wizard-outcome-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-height: 40px;
  padding: 6px 10px;
  background: #ffffff;
  color: #C62828;
  border: 2px solid #C62828;
  border-radius: var(--radius);
  cursor: pointer;
  text-align: left;
  text-decoration: none;
  font: inherit;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.wizard-outcome-card:hover,
.wizard-outcome-card:focus-visible {
  background: #C62828;
  color: #ffffff;
}

/* Inline Processing Redesign (2026-08-08) — the outcome grid never
   hides once a card is picked (unlike the old wizard's Step 1, which
   hid itself entirely), so the selected card needs its own persistent
   visual state, not just a transient :hover/:focus-visible one. Same
   filled treatment as hover/focus, applied by
   app/static/js/communication_wizard.js's own click handler. */
.wizard-outcome-card.is-selected {
  background: #C62828;
  color: #ffffff;
}

.wizard-outcome-title {
  font-weight: 700;
  font-size: 13px;
}

.wizard-outcome-description {
  font-size: 0.72em;
  font-weight: 400;
  opacity: 0.9;
}

.wizard-outcome-card-disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

.wizard-outcome-card-disabled:hover {
  background: #ffffff;
  color: #C62828;
}

/* Layout Correction (2026-08-08) — the selected outcome's form is no
   longer its own separate `.card` (which read as a second stacked
   white box below the grid's own card). It now lives inside the same
   .card section as the grid (app/templates/communications/detail.html),
   revealed by app/static/js/communication_wizard.js directly beneath
   the cards, above the message body — a border-top and modest top
   padding is enough to visually attach it to the grid it belongs to,
   without a second background/shadow/border pretending to be an
   independent page section. */
.wizard-outcome-form {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

/* Root-cause fix (2026-08-09) — .inline-form (below) sets `display:
   flex` unconditionally. CSS origin rules mean ANY normal author
   declaration beats the browser's own built-in `[hidden] { display:
   none }` rule regardless of specificity — so every .wizard-outcome-
   form, which also carries .inline-form, stayed visible via flex
   layout the whole time the `hidden` attribute was being correctly
   set server-side and correctly toggled by
   app/static/js/communication_wizard.js. This is a normal author rule
   too, so it competes on specificity within the same origin — one
   class + one attribute selector (0,2,0) beats .inline-form's bare
   class (0,1,0), which is what actually restores the browser's
   default "hidden means hidden" behavior. This is the actual defect
   the multiple normal forms were visible at once bug report was
   pointing at; nothing in the JS or the server-rendered `hidden`
   attribute itself was ever wrong. */
.wizard-outcome-form[hidden] {
  display: none;
}

.wizard-outcome-form h3 {
  margin-top: 0;
}

/* Bulk No Attention Needed (Requires Attention refinement, 2026-08-09).
   Scoped to .communication-list rather than the shared .note-row base
   rule (also used by Captures) — a row now has a checkbox alongside
   its existing clickable card, laid out as a flex row rather than the
   base rule's plain block stacking.

   Mobile layout correction (2026-08-09) — flex-wrap was missing here,
   which put the checkbox, the card content (.note-link), AND the
   quick-action buttons (.communication-row-quick-actions, a sibling of
   .note-link, not a descendant — a <form> cannot nest inside the
   card's own <a>) all on one unbreakable flex line. On desktop there
   was enough width for this to look tolerable; on phone widths it's
   exactly the reported defect (buttons overlapping the COM number,
   content squeezed to a sliver). flex-wrap: wrap here, combined with
   .communication-row-quick-actions' own flex-basis: 100% below, makes
   the actions row always wrap onto its own full-width line — which is
   also what this row already did before the bulk-checkbox change
   (two plain block-level siblings stacking naturally) — restoring
   that behavior at every width, not just on mobile. */
.communication-list .note-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 4px;
}

.communication-list .note-row .note-link {
  flex: 1 1 auto;
  min-width: 0;
}

.row-select {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  padding: 14px 0 14px 12px;
}

.row-select-checkbox {
  width: 18px;
  height: 18px;
  cursor: pointer;
}

.communication-list-toolbar {
  margin: 0 0 8px;
  font-size: 14px;
  color: var(--muted);
}

/* Hidden until communications_queue.js adds .visible (selection > 0) —
   degrades safely with JS disabled: the bar simply never appears, and
   the existing per-item actions remain fully available regardless. */
.bulk-action-bar {
  display: none;
  position: sticky;
  top: 0;
  z-index: 90;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 14px;
  margin: 0 0 12px;
}

.bulk-action-bar.visible {
  display: flex;
}

/* Communications list — mobile layout correction (2026-08-09).
   Production defect found after the Requires Attention refinement
   shipped: the base .communication-list rules above already fix the
   structural overlap (checkbox/content/actions all forced onto one
   unbreakable flex line) at every width, but phone-width screens still
   need their own touch-target/stacking treatment for the filter
   controls, the bulk bar, and the per-row action buttons — the same
   ~390-430px design target Sprint M1's own @media (max-width: 640px)
   block already established elsewhere in this file. A new, separate
   block (matching this file's own existing convention of several
   scoped @media (max-width: 640px) blocks rather than one shared one)
   so nothing here risks the unrelated rules already living in the
   earlier blocks. Preserves desktop/tablet layout exactly — nothing
   here fires above 640px. */
@media (max-width: 640px) {
  /* Filter/search controls (list.html's .filter-form: search text
     input, review_status select, Filter/Clear buttons) previously had
     no mobile sizing of their own at all — unlike .quick-add-form and
     .detail-actions-row (existing rule above), they just wrapped at
     whatever width the browser gave each control, which is exactly
     what read as "cramped" on a phone. Same full-width-stacked
     treatment as those existing rules, written as its own selector
     rather than joining that shared rule, so a future change to
     .quick-add-form's own mobile sizing can never silently affect the
     Communications filter form or vice versa. */
  .filter-form input[type="text"],
  .filter-form select {
    flex: 1 1 100%;
    min-height: 44px;
    box-sizing: border-box;
  }

  .filter-form button,
  .filter-form a.btn-secondary {
    flex: 1 1 auto;
    min-height: 44px;
    box-sizing: border-box;
  }

  /* Quick-filter chips (All Active / Mine / Unassigned / Waiting /
     Deferred / Today / This Week / AI Complete / Needs Analysis) —
     plain inline links with no padding of their own; on a phone this
     reads as a dense run of tiny, hard-to-tap text. Larger touch
     targets and vertical breathing room only, no change to the
     existing "&middot;"-separated structure. */
  .communication-list-quick-filters .review-link {
    display: inline-block;
    padding: 6px 2px;
  }

  /* Per-row "No Attention Needed" / "Junk…" quick actions — already
     forced onto their own full-width flex line by the base
     .communication-row-quick-actions rule above; on a phone, stack the
     one or two buttons themselves full-width and stretch them to a
     real touch target, rather than the desktop-appropriate compact
     inline pair. Works identically whether Junk is present (2 buttons)
     or not (1) — no empty grid cell either way. */
  .communication-row-quick-actions {
    flex-direction: column;
    align-items: stretch;
  }

  .communication-row-quick-actions .inline-form {
    width: 100%;
  }

  .communication-row-quick-actions .inline-form button,
  .communication-row-quick-actions > a.btn-secondary {
    width: 100%;
    min-height: 44px;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  /* Bulk action bar — "N selected" text plus one button previously
     relied on desktop's own space-between row; narrow enough that the
     two can crowd each other. Stacks cleanly, button full-width,
     exactly like every other mobile action control in this block. */
  .bulk-action-bar.visible {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
  }

  .bulk-action-bar button {
    width: 100%;
    min-height: 44px;
  }
}


/* ===================================================================
   Roadmap (modules/roadmap/blueprint.md) — read-only system-map page.
   Reuses .card/.hero/.console-header/.subtitle/.eyebrow as-is; the
   rules below are additive, scoped entirely under .roadmap-* class
   names so nothing here can bleed into another page.
   =================================================================== */

.roadmap-quicknav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  margin: -12px 0 28px;
  padding: 14px 20px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  position: sticky;
  top: 68px;
  z-index: 20;
}

.roadmap-quicknav a {
  color: var(--muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 13.5px;
  white-space: nowrap;
}

.roadmap-quicknav a:hover {
  color: var(--text);
}

.roadmap-group {
  margin-bottom: 24px;
}

.roadmap-layer-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.roadmap-layer-card {
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 18px;
}

.roadmap-layer-card summary {
  cursor: pointer;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  list-style: none;
}

.roadmap-layer-card summary::-webkit-details-marker {
  display: none;
}

.roadmap-layer-card summary::before {
  content: "▸";
  color: var(--muted);
  font-size: 0.8em;
  transition: transform 0.15s ease;
}

.roadmap-layer-card[open] summary::before {
  transform: rotate(90deg);
}

.roadmap-layer-card[open] summary {
  margin-bottom: 14px;
}

.roadmap-layer-name {
  font-weight: 700;
  font-size: 15.5px;
}

/* Status badges — one class per closed-vocabulary value
   (roadmap/README.md). Mirrors .status-badge's exact pill shape
   (color / rgba(color, 0.12) background / 1px solid color border)
   established elsewhere in this stylesheet, extended with a small
   palette of its own since nine distinct statuses need to stay
   visually distinguishable — the app's existing four semantic colors
   (green/yellow/red/accent) aren't enough on their own. */
.roadmap-status-badge {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 2px 10px;
  font-weight: 700;
  font-size: 0.78em;
  white-space: nowrap;
  text-transform: capitalize;
}

.roadmap-status-production_verified {
  color: var(--green);
  background: rgba(34, 197, 94, 0.12);
  border: 1px solid var(--green);
}

.roadmap-status-built {
  color: var(--accent);
  background: rgba(77, 163, 255, 0.12);
  border: 1px solid var(--accent);
}

.roadmap-status-in_progress {
  color: #38bdf8;
  background: rgba(56, 189, 248, 0.12);
  border: 1px solid #38bdf8;
}

.roadmap-status-approved {
  color: #a78bfa;
  background: rgba(167, 139, 250, 0.12);
  border: 1px solid #a78bfa;
}

.roadmap-status-proposed {
  color: var(--muted);
  background: rgba(148, 163, 184, 0.1);
  border: 1px dashed var(--border);
}

.roadmap-status-paused {
  color: var(--yellow);
  background: rgba(250, 204, 21, 0.12);
  border: 1px solid var(--yellow);
}

.roadmap-status-blocked {
  color: var(--red);
  background: rgba(239, 68, 68, 0.12);
  border: 1px solid var(--red);
}

.roadmap-status-deferred {
  color: #fb923c;
  background: rgba(251, 146, 60, 0.12);
  border: 1px solid #fb923c;
}

.roadmap-status-retired {
  color: var(--muted);
  background: rgba(148, 163, 184, 0.1);
  border: 1px solid var(--border);
  text-decoration: line-through;
}

.roadmap-purpose {
  color: var(--muted);
  font-size: 14.5px;
  margin: 0 0 10px;
}

.roadmap-status-note {
  font-size: 13.5px;
  background: rgba(148, 163, 184, 0.08);
  border-left: 3px solid var(--border);
  padding: 8px 12px;
  margin: 0 0 14px;
}

.roadmap-section {
  margin-bottom: 16px;
}

.roadmap-section h4 {
  margin: 0 0 6px;
  font-size: 12.5px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.roadmap-section p,
.roadmap-section ul {
  margin: 0;
}

.roadmap-empty {
  color: var(--muted);
  font-style: italic;
  font-size: 13.5px;
}

/* Two-up layout (Approved Next / Proposed Future, and separately
   Depends On / Unlocks) — collapses to one column on mobile via the
   shared @media block below, same breakpoint as the rest of the app. */
.roadmap-fourway {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 16px;
}

.roadmap-milestones {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 10px;
}

.roadmap-milestone-chip {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px 8px;
}

.roadmap-milestone-id {
  text-transform: none;
}

.roadmap-milestone-name {
  font-size: 13px;
  color: var(--muted);
}

.roadmap-relation-list {
  list-style: none;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.roadmap-relation-list li {
  font-size: 13.5px;
}

.roadmap-relation-list a {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}

.roadmap-relation-list a:hover {
  text-decoration: underline;
}

.roadmap-relation-note {
  display: block;
  color: var(--muted);
  font-size: 12.5px;
}

.roadmap-required-by {
  margin: 10px 0 0;
  font-size: 12.5px;
  color: var(--muted);
}

.roadmap-required-by a {
  color: var(--accent);
  text-decoration: none;
}

.roadmap-required-by a:hover {
  text-decoration: underline;
}

.roadmap-inferred-tag {
  display: inline-block;
  margin-left: 6px;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0 4px;
}

.roadmap-evidence {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  font-size: 12.5px;
  color: var(--muted);
  border-top: 1px solid var(--border);
  padding-top: 10px;
}

.roadmap-evidence code {
  font-size: 12px;
}

/* Cross-cutting Safety Items (roadmap/safety_items.json) — visually
   distinct from a layer card (no <details> collapse; these are meant
   to be seen, not tucked away) but built from the same tokens. */
.roadmap-safety-item {
  border-top: 1px solid var(--border);
  padding: 16px 0;
}

.roadmap-safety-item:first-of-type {
  border-top: none;
  padding-top: 8px;
}

.roadmap-safety-item-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}

.roadmap-safety-meta {
  font-size: 13.5px;
  color: var(--muted);
  margin: 4px 0 0;
}

.roadmap-safety-status-open {
  color: var(--yellow);
  background: rgba(250, 204, 21, 0.12);
  border: 1px solid var(--yellow);
}

.roadmap-safety-status-investigating {
  color: var(--accent);
  background: rgba(77, 163, 255, 0.12);
  border: 1px solid var(--accent);
}

.roadmap-safety-status-resolved {
  color: var(--green);
  background: rgba(34, 197, 94, 0.12);
  border: 1px solid var(--green);
}

@media (max-width: 800px) {
  .roadmap-fourway {
    grid-template-columns: 1fr;
    gap: 12px;
  }

  .roadmap-quicknav {
    position: static;
  }
}

@media (max-width: 640px) {
  .roadmap-layer-card {
    padding: 12px 14px;
  }

  /* Chips stack id-over-name rather than side-by-side at this width —
     keeps every milestone name genuinely visible (never hidden) while
     staying readable in a narrow column. */
  .roadmap-milestone-chip {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }
}

/* -------------------------------------------------------------------
   Compact directory-tree overview (app/templates/roadmap/_compact_tree.html)
   — a dense, text-oriented fast-scan layer above the detailed per-Layer
   cards. Deliberately not another set of large cards: minimal vertical
   padding, indentation communicates hierarchy, no per-row borders.
   ------------------------------------------------------------------- */
/* Visual density refinement (2026-08-11, real production usage
   feedback) — Kevin's report on the deployed a8690ac page: the
   original version still read like a small dashboard (badge-pill
   status chips, no real connectors on the layer rows themselves, a
   "▼" disclosure glyph on group headings even though nothing here is
   collapsible). Every rule below is either a genuinely new rule (the
   status text / continuation-line treatment) or a tightened value on
   an existing one — no rule was left at its old size "just in case,"
   since the whole point of this pass is scanning density. */
.roadmap-tree {
  padding: 14px 16px;
}

.roadmap-tree-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin: 2px 0 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}

.roadmap-tree-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  color: var(--muted);
  text-transform: capitalize;
}

.roadmap-tree-listing {
  /* A directory listing, not a stack of dashboard rows — tabular
     figures so the ├─/└─ connectors line up column-to-column instead
     of drifting with whatever proportional-width digits/punctuation a
     given row happens to render. */
  font-variant-numeric: tabular-nums;
}

.roadmap-tree-group {
  margin-bottom: 1px;
}

.roadmap-tree-group-label {
  margin: 9px 0 1px;
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.roadmap-tree-group:first-of-type .roadmap-tree-group-label {
  margin-top: 0;
}

.roadmap-tree-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 8px;
  padding: 0 0 0 2px;
  line-height: 1.3;
}

.roadmap-tree-layer-link {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  color: var(--text);
  text-decoration: none;
  font-size: 13px;
  min-width: 0;
}

.roadmap-tree-layer-link:hover .roadmap-tree-layer-name {
  text-decoration: underline;
}

.roadmap-tree-layer-name {
  font-weight: 600;
}

/* Status is deliberately plain text, not a colored pill — the glyph
   immediately to its left already carries the color-coded meaning;
   repeating that in a bold badge made status compete with the name
   for attention instead of trailing quietly behind it. */
.roadmap-tree-status-text {
  font-size: 0.75em;
  color: var(--muted);
  flex-shrink: 0;
  text-transform: capitalize;
  margin-left: auto;
  padding-left: 8px;
}

.roadmap-tree-glyph {
  display: inline-block;
  width: 1em;
  text-align: center;
  font-weight: 700;
}

.roadmap-tree-children {
  display: flex;
  flex-direction: column;
}

/* The vertical continuation stroke under a layer that is *not* the
   last row in its group — visually the same idea as the literal "│"
   Kevin's own sketch used under a non-last parent, drawn as a border
   so it reads as one continuous line down the block rather than a
   glyph repeated per row (which would show hairline gaps between
   rows at this line-height). Position is tuned to sit roughly under
   the layer row's own ├─/└─ connector, not the deeper milestone
   indent. */
.roadmap-tree-children-continued {
  border-left: 1px solid var(--border);
  margin-left: 8px;
  padding-left: 11px;
}

.roadmap-tree-milestone,
.roadmap-tree-next {
  display: flex;
  align-items: baseline;
  gap: 5px;
  padding: 0 0 0 22px;
  font-size: 11.5px;
  line-height: 1.3;
}

.roadmap-tree-children-continued > .roadmap-tree-milestone,
.roadmap-tree-children-continued > .roadmap-tree-next {
  padding-left: 11px;
}

.roadmap-tree-connector {
  display: inline-block;
  min-width: 1.5em;
  color: var(--muted);
  font-size: 0.85em;
  flex-shrink: 0;
}

.roadmap-tree-milestone-id {
  font-weight: 700;
  color: var(--muted);
  flex-shrink: 0;
}

.roadmap-tree-milestone-name {
  color: var(--muted);
}

.roadmap-tree-next-label {
  font-weight: 700;
  color: var(--muted);
  flex-shrink: 0;
}

.roadmap-tree-next-text {
  color: var(--muted);
}

/* Glyph colors — reuse the exact palette already established by the
   .roadmap-status-* badge classes above, so the tree never introduces
   a second color vocabulary for the same nine statuses. */
.roadmap-tree-glyph-production_verified { color: var(--green); }
.roadmap-tree-glyph-built { color: var(--accent); }
.roadmap-tree-glyph-in_progress { color: #38bdf8; }
.roadmap-tree-glyph-approved { color: #a78bfa; }
.roadmap-tree-glyph-proposed { color: var(--muted); }
.roadmap-tree-glyph-paused { color: var(--yellow); }
.roadmap-tree-glyph-blocked { color: var(--red); }
.roadmap-tree-glyph-deferred { color: #fb923c; }
.roadmap-tree-glyph-retired { color: var(--muted); }

.roadmap-tree-glyph-safety-open { color: var(--yellow); }
.roadmap-tree-glyph-safety-investigating { color: var(--accent); }
.roadmap-tree-glyph-safety-resolved { color: var(--green); }

@media (max-width: 640px) {
  .roadmap-tree {
    padding: 12px 12px;
  }

  .roadmap-tree-status-text {
    /* No room to trail a status label on the same line as a long
       layer name at this width — it drops to its own line (still
       left-aligned under the name, never introducing a wider row
       than the viewport) rather than forcing the row onto one
       cramped line or scrolling sideways. */
    flex-basis: 100%;
    margin-left: 22px;
    padding-left: 0;
  }

  .roadmap-tree-children-continued {
    margin-left: 6px;
    padding-left: 9px;
  }

  .roadmap-tree-children-continued > .roadmap-tree-milestone,
  .roadmap-tree-children-continued > .roadmap-tree-next {
    padding-left: 9px;
  }
}

/* Billing (Phase 1A UI polish, 2026-08-26) — presentation only, no new
   component system: reuses .card, .btn/.btn-secondary/.btn-small,
   .quick-add-form, and the existing table/data-label responsive
   mechanism throughout. .billing-page scopes tighter card padding and
   heading margins to this one page only, matching the "compact
   business worksheet" brief without changing .card's own shared
   definition (every other page keeps its current spacing). */

.billing-page .card {
  padding: 16px 20px;
}

.billing-page .card h2 {
  margin: 0 0 10px;
  font-size: 18px;
}

.billing-page .card h3 {
  margin: 4px 0 8px;
  font-size: 14px;
  color: var(--muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Contract Summary — a plain label/value table restyled to read as a
   financial summary: values align right with tabular figures, and
   Subtotal/Total Contract Amount are the two rows meant to draw the
   eye, matching Kevin's "same five figures, clearer presentation" —
   no calculation here, purely the two row classes below. */
.billing-summary-table {
  width: 100%;
  max-width: 420px;
}

.billing-summary-table td {
  padding: 6px 4px;
  border: none;
}

.billing-summary-table td:last-child {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.billing-summary-table tr.billing-summary-subtotal td {
  font-weight: 700;
  border-top: 1px solid var(--border);
  padding-top: 10px;
}

.billing-summary-table tr.billing-summary-total td {
  font-weight: 700;
  font-size: 18px;
  color: var(--accent);
  padding-top: 4px;
}

/* Source Tender / SOQ Document — one compact row instead of a
   paragraph-then-form stack; reuses .quick-add-form for the upload
   control itself so it never needs its own input styling. */
.billing-source-doc-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
}

.billing-source-doc-row .quick-add-form {
  margin-top: 0;
}

/* 2026-08-26 — the Change Order Source Document column's filename
   link (both the server-rendered row and the one
   billing-co-source-add.js inserts after a successful upload) was
   otherwise a bare, unstyled <a> — rendering with the browser's
   default unvisited/visited colors (blue/purple) rather than the
   app's own theme, since no site-wide anchor color/decoration is set
   anywhere in this stylesheet. One small, reusable class instead of a
   one-off inline style, matching the --text (light) / underlined
   convention already used for ordinary readable links elsewhere. */
.billing-document-link,
.billing-document-link:link,
.billing-document-link:visited,
.billing-document-link:hover,
.billing-document-link:active {
  color: var(--text);
  text-decoration: underline;
}

/* Checkpoint 7 UI polish (2026-08-29) — Timecard sync status color,
   color only (no font-weight/text-transform, unlike the site's
   .status/.status.healthy/.status.critical family) since these labels
   are full plain-language sentences, not single Title Case words.
   "synced" reads as clear/positive without adding a badge/pill or any
   other visual weight (Constitution Article VIII, Quiet Normal
   Operations); "failed"/"rejected" stay obvious attention colors,
   matching --red's existing use for the same purpose elsewhere
   (.status.critical, .risk-link-red). */
.billing-timecard-status-good {
  color: var(--green);
}

.billing-timecard-status-attention {
  color: var(--red);
  font-weight: 600;
}

/* Schedule of Quantities / Change Orders tables — colgroup-driven
   fixed column widths so the header, every data row, and the entry
   row at the bottom of the same <tbody> all line up as one continuous
   schedule, per the brief, rather than a table followed by a
   separate form. table-layout: fixed only matters in the desktop
   <table> presentation; the existing table->stacked-card mobile
   transform (@media max-width: 640px elsewhere in this file) already
   switches every <tr>/<td> to display:block/flex regardless, so
   nothing here needs its own mobile override for that part. */
.billing-soq-table,
.billing-co-table {
  table-layout: fixed;
  width: 100%;
}

/* Column proportions revised 2026-08-26 (final UI implementation
   pass, per the approved mockup) — Item No. widened from the earlier
   pass's too-tiny 6% so ~5 characters is comfortable; Qty/Unit/Actions
   narrowed since Actions is now Edit-only (a single small link, not
   Approve/Reject/Reverse); Status column removed entirely from the
   Change Orders table (only-approved-COs-are-entered is the rule now,
   so there's nothing to show); Description still gets what's left
   over on both tables, never the numeric columns' unused whitespace. */
.billing-soq-table col.col-item-number { width: 8%; }
.billing-soq-table col.col-description { width: 37%; }
.billing-soq-table col.col-qty { width: 7%; }
.billing-soq-table col.col-unit { width: 8%; }
.billing-soq-table col.col-unit-price { width: 14%; }
.billing-soq-table col.col-extended-value { width: 16%; }
.billing-soq-table col.col-actions { width: 10%; }

.billing-co-table col.col-co-number { width: 8%; }
.billing-co-table col.col-description { width: 40%; }
.billing-co-table col.col-date { width: 12%; }
.billing-co-table col.col-amount { width: 13%; }
.billing-co-table col.col-source { width: 12%; }
.billing-co-table col.col-actions { width: 15%; }

/* Long Description text wraps within its fixed column instead of
   forcing the table wider or truncating meaningful contract wording —
   table-layout: fixed already wraps normal text by default, this only
   guards against an unusually long unbroken token (e.g. a part number
   with no spaces) doing the one thing fixed layout can't stop on its
   own. */
.billing-soq-table td,
.billing-co-table td {
  overflow-wrap: break-word;
  word-break: break-word;
}

/* Entry-row form controls fill their column exactly, using the same
   visual tokens as every other scoped input in this file (e.g.
   .quick-add-form, form.login-form) rather than inventing new ones. */
.billing-soq-table input,
.billing-soq-table select,
.billing-co-table input {
  width: 100%;
  box-sizing: border-box;
  padding: 6px 8px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
  font-size: 14px;
}

.billing-entry-row td {
  vertical-align: middle;
}

/* Extended Value in the Schedule of Quantities entry row is always
   calculated (Qty × Unit Price, or 1 × Unit Price for LS) — never
   entered directly — so it renders disabled/dimmed to read visually
   as output, not an editable field. */
.billing-soq-table input:disabled {
  color: var(--muted);
  cursor: default;
}

/* Qty is a normal numeric field, not a spinner control (Kevin,
   2026-08-26 final UI pass) — hides the browser's native up/down
   arrows while keeping type="number"'s numeric keyboard/validation. */
input.no-spinner::-webkit-outer-spin-button,
input.no-spinner::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input.no-spinner {
  -moz-appearance: textfield;
}

/* Persistent, low-key reminder that every SOQ/Change Order figure on
   this page excludes GST — distinct from the Contract Summary's own
   GST-inclusive Total Contract Amount below it, and distinct again
   from the future Invoice/Payment tracker's GST-inclusive actuals.
   Deliberately not styled as a warning (no bold, no icon, no border,
   no red) — it's a category label, not an alert. */
.billing-gst-reminder {
  text-align: center;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: normal;
  font-size: 13px;
  margin-top: 24px;
}

@media (max-width: 640px) {
  .billing-soq-table input,
  .billing-co-table input,
  .billing-soq-table select {
    width: 100%;
    max-width: 220px;
  }
}

/* Payment/Invoice UX correction pass (Kevin's 2026-09-01 locked
   rules) — a clean, aligned two-column Record Payment form, replacing
   the old cramped .quick-add-form + inline flex-direction:column
   single-narrow-column stack (app/templates/billing/record_payment.html).
   Payment date/Amount received/Method/Reference/Organization/Payer
   name line up in two even columns; Notes stays outside this grid, a
   full-width textarea beneath it. */
.billing-payment-form-card {
  max-width: 720px;
}

.billing-payment-form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px 28px;
  margin-top: 16px;
}

.billing-payment-form-grid label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-weight: 600;
}

.billing-payment-form-full-row {
  grid-column: 1 / -1;
}

/* Receivables multi-select Status filter (Kevin's 2026-09-01 locked
   follow-up request) — a plain checkbox group standing in for the old
   single <select>, no new dependency. Resets the browser's default
   <fieldset> border/padding so it sits inline with the rest of
   .quick-add-form's own label/input row rather than as its own boxed
   block. */
.receivables-status-filter {
  display: flex;
  align-items: center;
  gap: 10px;
  border: none;
  margin: 0;
  padding: 0;
}

.receivables-status-filter legend {
  font-weight: 600;
  padding: 0;
}

.receivables-status-filter label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-weight: normal;
  white-space: nowrap;
}

.billing-payment-form-grid input[type="text"],
.billing-payment-form-grid input[type="date"],
.billing-payment-form-grid select {
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
  font-weight: normal;
  width: 100%;
  box-sizing: border-box;
}

.billing-payment-form-grid input[type="text"]:disabled {
  color: var(--muted);
  cursor: default;
}

@media (max-width: 640px) {
  .billing-payment-form-grid {
    grid-template-columns: 1fr;
  }
}

/* Searchable Select (2026-08-31) — generic progressive-enhancement
   combobox for a long <select> (Project General Contractor first;
   see app/static/js/searchable-select.js's own docstring for other
   candidates). The underlying <select> stays in the DOM and is what
   actually submits with the form — this only ever styles the
   JS-built overlay around it, never the <select> itself, so a page
   with JS disabled/broken renders the plain, already alphabetically-
   sorted <select> exactly as before, fully functional. */
.searchable-select {
  position: relative;
  max-width: 480px;
}

.searchable-select-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 8px 34px 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
}

.searchable-select-input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.searchable-select-clear {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  padding: 4px 6px;
  cursor: pointer;
  border-radius: 6px;
}

.searchable-select-clear:hover,
.searchable-select-clear:focus-visible {
  color: var(--text);
  background: var(--panel);
}

.searchable-select-list {
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 260px;
  overflow-y: auto;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow);
}

.searchable-select-option {
  padding: 8px 10px;
  border-radius: 6px;
  cursor: pointer;
  color: var(--text);
}

.searchable-select-option.is-active,
.searchable-select-option:hover {
  background: var(--accent);
  color: #04101f;
}

.searchable-select-empty {
  padding: 8px 10px;
  color: var(--muted);
}

/* Capture Attachment Reliability, Increment 7 (Resume UX / CAP-000097)
   — the Capture detail page's "Media Upload Incomplete" card and its
   "Upload Remaining Photos" checklist, plus the Capture landing page's
   "Incomplete Uploads" queue. .capture-media-incomplete/
   .capture-incomplete-queue are otherwise plain .card.alert-warning
   sections — no rule needed beyond what those two classes already give
   every other card/alert on this page. */
.capture-media-recovery summary.btn {
  cursor: pointer;
  width: fit-content;
  list-style: none;
}

.capture-media-recovery summary.btn::-webkit-details-marker {
  display: none;
}

.capture-media-recovery-list {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
}

.capture-media-recovery-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 10px 0;
  border-top: 1px solid var(--border);
}

.capture-media-recovery-row:first-child {
  border-top: none;
}

.capture-media-recovery-name {
  flex: 1 1 100%;
  font-size: 14px;
}

.capture-media-recovery-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

/* A real <input type="file"> inside a styled <label> — visually hidden
   but still keyboard/screen-reader reachable (not display:none, which
   would remove it from the accessibility tree and tab order too). Same
   "label wraps a real file input" shape as .capture-attach elsewhere in
   this file, sized here rather than left to browser default so it
   never overflows its label on a narrow phone screen. */
.capture-media-recovery-picker {
  cursor: pointer;
  position: relative;
  overflow: hidden;
}

.capture-media-recovery-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.capture-media-recovery-status {
  flex: 1 1 100%;
  font-size: 13px;
  color: var(--muted);
}

.capture-media-recovery-status.alert-error {
  color: var(--red);
}

/* Sprint M1's own ~390-430px design target — matching the file's
   established convention of scoped @media (max-width: 640px) blocks
   per feature rather than one shared block. A tired field user
   retrying a missed photo is exactly the case this codebase's own
   44px touch-target rule already exists for. */
@media (max-width: 640px) {
  .capture-media-recovery-picker,
  .capture-media-recovery-form button[type="submit"] {
    min-height: 44px;
  }

  .capture-media-recovery-row {
    align-items: flex-start;
  }
}

/* Admin Executive Dashboard UX refinement (Kevin, 2026-09-04) —
   command-centre layout: a wider desktop container just for this
   page, two prominent work-queue cards up top, Upcoming Opportunities
   / Billing beneath, the existing Morning Scan manual-entry cards
   demoted into "Operations / Scheduling" further down. Every rule
   below is scoped to .dashboard-console / dashboard-* classes so no
   other page's layout is affected — .console's own 1200px max-width
   (used by every other page) is untouched. */

.console.dashboard-console {
  /* Kevin's locked target: ~1300-1450px usable width on a wide
     desktop, still centered, never edge-to-edge. */
  max-width: 1450px;
}

.dashboard-topbar {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 24px;
  margin-bottom: 24px;
}

/* Deliberately NOT removed — GreetingPersonalizationTests
   (tests/test_dashboard_briefing_integration.py) depends on a bare,
   attribute-free <h1>[^<]*</h1> carrying the per-user greeting (its
   own regex is `<h1>([^<]*)</h1>`, so the tag itself must stay
   attribute-free — styled via this parent-selector instead of a class
   on the tag). Kevin's own instruction was "his identity is already
   visible in navigation" (i.e. de-emphasize the old full-width hero,
   not delete the greeting outright). Shrunk from the global
   h1{font-size:42px} down to a compact single line. */
.dashboard-topbar-meta h1 {
  font-size: 22px;
  margin: 0;
  line-height: 1.3;
}

.dashboard-datetime {
  color: var(--muted);
  font-size: 14px;
  margin: 4px 0 0;
}

.dashboard-row-primary,
.dashboard-row-secondary {
  margin-bottom: 24px;
}

/* Requires Attention / Captures — the two most prominent cards on the
   page; tall enough to comfortably show ~8-10 rows, with a bounded
   max-height (rather than unbounded growth) once a day is unusually
   busy. */
.dashboard-card-tall {
  display: flex;
  flex-direction: column;
}

.dashboard-scroll-list {
  max-height: 420px;
  overflow-y: auto;
  margin: 0;
  padding-left: 20px;
}

.dashboard-scroll-list:not(.capture-preview-list) li {
  margin-bottom: 8px;
}

.capture-preview-list {
  list-style: none;
  padding-left: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.capture-preview-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}

.capture-preview-row:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.capture-preview-link {
  color: var(--text);
  text-decoration: none;
  font-weight: 600;
}

.capture-preview-link:hover {
  color: var(--accent);
}

.capture-preview-project {
  color: var(--accent);
  margin-right: 6px;
}

.dashboard-view-all {
  margin: 12px 0 0;
  font-size: 14px;
}

.dashboard-view-all a {
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
}

.dashboard-view-all a:hover {
  text-decoration: underline;
}

/* Upcoming Opportunities — locked row concept: live countdown on the
   left (the most visually important value), Opportunity name in the
   middle, actual Closing Date on the right. */
.opportunity-countdown-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.opportunity-countdown-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
}

.opportunity-countdown {
  flex: 0 0 64px;
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.opportunity-countdown-overdue {
  color: var(--red);
}

.opportunity-countdown-name {
  flex: 1 1 auto;
  color: var(--text);
  text-decoration: none;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.opportunity-countdown-name:hover {
  color: var(--accent);
}

.opportunity-countdown-date {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 14px;
}

/* Billing / Receivables — the real, already-computed AR aging
   breakdown (app.services.ar_calculation_service.calculate_ar_totals).
   "Jobs to Bill" (Branch B) reuses .receivables-aging for its rows —
   derived by app.services.billing_follow_up_service. */
.receivables-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding-bottom: 12px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.receivables-total-label {
  color: var(--muted);
  font-size: 14px;
}

.receivables-total-value {
  font-size: 26px;
  font-weight: 700;
}

.receivables-aging {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

.receivables-aging li {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 14px;
}

.receivables-aging li span:first-child {
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.dashboard-future-note {
  color: var(--muted);
  font-size: 13px;
  font-style: italic;
  margin: 12px 0 0;
}

.dashboard-section-heading {
  margin: 8px 0 16px;
  font-size: 18px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* Sprint M1's own ~390-430px design target, same scoped-block
   convention as .capture-media-recovery-*'s own @media (max-width:
   640px) block above — this page's own mobile rules, not folded into
   the shared @media (max-width: 800px) block those other pages use,
   since only the Dashboard has this row/grid structure to collapse. */
@media (max-width: 800px) {
  .dashboard-topbar {
    flex-direction: column;
    gap: 12px;
  }

  .dashboard-card-tall {
    max-height: none;
  }

  .dashboard-scroll-list {
    max-height: none;
  }

  .opportunity-countdown-row {
    flex-wrap: wrap;
  }

  .opportunity-countdown-name {
    flex: 1 1 100%;
    order: 3;
    white-space: normal;
  }

  .capture-preview-link,
  .opportunity-countdown-name,
  .dashboard-view-all a {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  .receivables-aging {
    gap: 12px 20px;
  }
}

/* Project To-Dos v1 (2026-09-18, blueprints/Project_Todos_v1.md) — the
   Outstanding To-Dos panel reuses .procurement-requires-attention's
   existing red-tint border/background exactly (Kevin's own "not a
   bright full-screen danger treatment" requirement — this is already
   the app's one established "something needs attention" surface, not
   a new color language). Only the row/list layout below is new. */
.project-todos-outstanding h2 {
  color: var(--red);
}

.project-todo-row {
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.project-todo-row:last-of-type {
  border-bottom: none;
}

.project-todo-row .action-buttons {
  margin-top: 6px;
}

.project-todo-remove-form input[name="reason"] {
  width: 14em;
  max-width: 100%;
}

/* + Add To-Do — a compact disclosure, matching .project-archived-
   knowledge's own de-emphasized <summary> treatment exactly, never a
   heading-weight control. */
.project-todo-add summary {
  cursor: pointer;
  color: var(--muted);
  font-weight: 700;
}

.project-todo-add[open] summary {
  margin-bottom: 10px;
}

.project-todo-add-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 480px;
}

.project-todo-add-form label {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  margin-top: 4px;
}

.project-todo-add-form button {
  align-self: flex-start;
  margin-top: 6px;
}

.project-todos-zero-state {
  padding: 12px 20px;
}

/* Edit To-Do page (browser review, Round 2) — a dedicated full-width
   form, distinct from .project-todo-add-form's compact inline-
   disclosure treatment above (that one deliberately stays a narrow
   480px control tucked inside the panel; this is its own page, so it
   gets the normal, full-width single-column form layout every field
   deserves room for). Each .form-row is Title/Details/Assigned To in
   turn, one clearly separated row each. */
.project-todo-edit-form {
  display: flex;
  flex-direction: column;
}

.project-todo-edit-form .form-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 18px;
}

.project-todo-edit-form label {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}

.project-todo-edit-form input[type="text"],
.project-todo-edit-form textarea,
.project-todo-edit-form select {
  width: 100%;
}

.project-todo-edit-actions {
  margin-top: 4px;
  margin-bottom: 4px;
}

/* Deactivate stays visually separated from Save Changes — a secondary,
   destructive correction action, not a peer of the normal save flow
   (Kevin's own "not the normal completion mechanism" framing for
   deactivation elsewhere in this feature). */
.project-todo-edit-danger-zone {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

/* Browser acceptance fix 1 (Kevin) — completed-by/completed-at is the
   one fact a reviewer of Completed History most needs at a glance;
   it used to be styled identically to the secondary "Originally
   created" line beneath it (both plain .subtitle), so neither read as
   more important than the other. Full-strength text color and medium
   weight, no new color/badge language. */
.project-todo-completion-attribution {
  color: var(--text);
  font-weight: 600;
  margin: 4px 0;
}

/* Browser acceptance fix 3 (Kevin) — the original Capture title/body
   used to have no dedicated read-only presentation (see
   captures/detail.html's own comment at this block). .card p already
   sets color: var(--muted) globally (right, secondary text like a
   caption) — but original_content is the primary content of the page,
   not a caption, so it needs the full-strength text color explicitly;
   .capture-original-title inherits .card h2's existing sizing as-is. */
.capture-original-content {
  color: var(--text);
  font-size: 16px;
  line-height: 1.6;
}

.capture-original-title {
  margin-bottom: 8px;
}

/* Q260906 safety (2026-10-01): a kept Ninja block, and the Attach
   Existing Ninja Quote comparison. */
.quote-ninja-blocked {
  grid-column: 1 / -1;
  border-left: 4px solid #C62828;
  padding-left: 10px;
}

.quote-attach-text {
  margin: 0;
  white-space: pre-wrap;
  font: inherit;
}

.quote-attach-differs td {
  background: #fdecea;
}

.quote-contact-match-yes {
  color: #1B7F3B;
  font-weight: 700;
}

.quote-contact-match-no {
  color: #C62828;
  font-weight: 700;
}

/* Opportunity detail quick edits (2026-10-02): Closing Date, Current
   Addendum and + Add GC open a small inline form beside the value. */
.opportunity-quick-field {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
    margin: 0 0 0.75rem;
}
.opportunity-quick-field > p {
    margin: 0;
}
.opportunity-quick-edit > summary {
    display: inline-block;
    list-style: none;
    cursor: pointer;
}
.opportunity-quick-edit > summary::-webkit-details-marker {
    display: none;
}
.opportunity-quick-field .opportunity-quick-edit[open] {
    flex-basis: 100%;
}
.opportunity-quick-edit form {
    margin-top: 0.5rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}
#quick-add-gc {
    margin: 0.75rem 0;
}

/* Organization detail (2026-10-02): phone / email / website values stay
   clickable (tel:, mailto:, the site) but read as normal StrohOS text,
   not browser-link blue. */
.contact-value-link,
.contact-value-link:visited {
    color: var(--text);
    text-decoration: none;
}
.contact-value-link:hover,
.contact-value-link:focus-visible {
    text-decoration: underline;
}

/* Capture UX (2026-10-03): the detail page's Quick Actions block — the
   existing Project / Opportunity / assignment / status forms, compact. */
.capture-quick-actions {
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid var(--border, rgba(255, 255, 255, 0.12));
    display: grid;
    gap: 4px;
}
.capture-quick-actions .subtitle {
    margin: 6px 0 2px;
}

/* Capture list row actions (2026-10-03): the personal Pin on top, the
   existing Archive directly below it.
   Captures UX Phase 1 (2026-10-21): a dense row (~8-9 per phone screen)
   — Project, title, one preview line, one metadata line, each clipped to
   a single line — and labelled, full-opacity Pin / Archive buttons with
   44px targets. On a wide screen the list is capped so the actions sit
   beside the text, not at the far edge of the card. */
.capture-list .note-row {
    margin: 0;
    gap: 8px;
    align-items: center;
    border-bottom: 1px solid var(--border);
}
.capture-list .note-row:last-child {
    border-bottom: none;
}
.capture-list .note-row .note-link {
    padding: 8px 4px 8px 8px;
    line-height: 1.35;
}
.capture-list .capture-row-title,
.capture-list .capture-row-subject,
.capture-list .capture-row-preview,
.capture-list .capture-row-meta {
    margin: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    overflow-wrap: normal;
}
.capture-list .capture-row-title,
.capture-list .capture-row-subject {
    font-size: 15px;
}
.capture-list .capture-row-subject.is-wrapping {
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.card ul.capture-list,
.card ul.quick-photo-list {
    padding-left: 0;
}
.capture-list .capture-row-relation {
    font-size: 15px;
}
.capture-list .capture-row-preview {
    font-size: 14px;
    color: var(--muted);
}
.capture-list .capture-row-meta {
    font-size: 12.5px;
}
.capture-audience-badge {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 1px 6px;
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--muted);
}
.capture-row-actions {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: 4px 0;
    flex: 0 0 auto;
}
.capture-row-actions .capture-pin-form {
    padding-top: 0;
}
.capture-archive-form {
    margin: 0;
}
.capture-row-button {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-width: 92px;
    min-height: 44px;
    padding: 4px 10px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--panel-soft);
    color: var(--text);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.1;
    white-space: nowrap;
    opacity: 1;
    cursor: pointer;
}
.capture-row-button.capture-archive-button {
    border-color: #1B7F3B;
    background: #ffffff;
    color: #1B7F3B;
}
.capture-row-button.capture-archive-button:hover,
.capture-row-button.capture-archive-button:focus-visible {
    background: #1B7F3B;
    color: #ffffff;
}
.capture-row-button.capture-pin-button.is-pinned {
    border-color: var(--text);
}
@media (min-width: 641px) {
    .capture-list {
        max-width: 880px;
    }
    .capture-row-actions {
        flex-direction: row;
        align-items: center;
    }
}
@media (max-width: 640px) {
    .capture-row-button {
        min-width: 84px;
    }
    /* Less chrome above the list on a phone, so rows start sooner:
       a smaller heading, a two-line scope note, one 44px search row and
       one 44px row holding New Capture / View Archived / Pinned. */
    .capture-list-header h2 {
        font-size: 20px;
        line-height: 1.25;
        margin: 0 0 2px;
    }
    .capture-list-header .subtitle {
        font-size: 12.5px;
        line-height: 1.35;
        margin: 0;
    }
    .capture-scope-more {
        display: none;
    }
    .capture-search-form {
        flex-wrap: nowrap;
        margin: 8px 0;
    }
    .capture-search-input {
        flex: 1 1 auto;
        min-height: 44px;
        padding: 6px 10px;
        font-size: 16px;
    }
    .capture-search-form button {
        min-height: 44px;
    }
    .capture-list-controls {
        display: flex;
        gap: 4px;
        margin: 0 0 8px;
    }
    .capture-list-controls a,
    .capture-list-controls a:link,
    .capture-list-controls a:visited {
        flex: 1 1 auto;
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: 44px;
        margin: 0;
        padding: 4px 6px;
        border-radius: 12px;
        font-size: 13px;
        line-height: 1.15;
        text-align: center;
        text-decoration: none;
    }
}

/* Archive flash with Undo (Phase 1). */
.capture-archive-flash {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
.capture-archive-flash form {
    margin: 0;
}
.capture-archive-flash a {
    color: var(--text);
    font-weight: 700;
}
.capture-undo-button {
    min-height: 40px;
    min-width: 80px;
    color: var(--text) !important;
    font-weight: 700;
}

/* Capture detail (Phase 1): Archive / Reopen + Pin together at the top;
   a phone's sticky copy appears at the bottom once those scroll away. */
.capture-primary-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin: 0 0 12px;
}
.capture-primary-actions form {
    margin: 0;
    padding: 0;
}
/* Archive / Reopen and Pin as one matched pair: same height, border
   width, padding, radius and type, so they line up on one baseline. */
.capture-primary-actions button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 48px;
    min-width: 120px;
    padding: 0 20px;
    border-width: 2px;
    border-style: solid;
    border-radius: var(--radius);
    font-size: 15px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    opacity: 1;
}
.capture-primary-actions .btn-secondary {
    color: var(--text) !important;
}
.capture-primary-actions .capture-pin-button {
    border-color: var(--muted);
    background: var(--panel-soft) !important;
}
.capture-primary-actions .capture-pin-button.is-pinned {
    border-color: var(--text);
}
.capture-archived-banner {
    margin: 0 0 12px;
    padding: 10px 14px;
    border: 1px dashed var(--muted);
    border-radius: 8px;
    background: repeating-linear-gradient(135deg, var(--panel-soft) 0 10px, var(--panel) 10px 20px);
}
.capture-is-archived .capture-original-title {
    color: var(--muted);
}
/* Links on the Capture page (Related Project / Opportunity, the linked
   Job To-Do) use the theme's link colour, not the browser's default blue. */
.capture-related-link,
.capture-related-link:visited {
    color: var(--accent);
    font-weight: 600;
}
.capture-related-link:hover,
.capture-related-link:focus-visible {
    color: var(--text);
}
.capture-linked-todo {
    margin: 0 0 10px;
    padding: 8px 12px;
    border-left: 3px solid var(--green);
    background: var(--panel-soft);
    border-radius: 6px;
}
.capture-sticky-actions {
    display: none;
}
@media (max-width: 640px) {
    .capture-sticky-actions {
        display: block;
        position: sticky;
        bottom: 0;
        z-index: 50;
        margin: 16px -16px -16px;
        padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
        background: var(--panel);
        border-top: 1px solid var(--border);
        box-shadow: 0 -8px 20px rgba(0, 0, 0, 0.35);
    }
    .capture-sticky-actions .capture-primary-actions {
        margin: 0;
        display: grid;
        grid-template-columns: 1fr 1fr;
    }
    .capture-sticky-actions .capture-primary-actions button {
        width: 100%;
        justify-content: center;
    }
    /* With JavaScript the bar waits until the top controls scroll away. */
    .capture-sticky-actions.is-tracking:not(.is-visible) {
        visibility: hidden;
    }
    /* Never over the keyboard or a field being typed in. */
    body:has(textarea:focus, input[type="text"]:focus, input[type="search"]:focus, select:focus) .capture-sticky-actions {
        display: none;
    }
}

/* Large choice buttons (components/choice_buttons.html) — the Timecard
   "who did you work with?" tiles, rebuilt on plain radio/checkbox inputs. */
.choice-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
    margin: 0 0 10px;
}
.choice-grid > * {
    min-width: 0;
}
.choice-stack {
    display: grid;
    gap: 10px;
    margin: 0 0 10px;
}
.choice-button {
    position: relative;
    display: block;
    margin: 0;
    cursor: pointer;
}
.choice-button input[type="radio"],
.choice-button input[type="checkbox"] {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}
.choice-button-body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 56px;
    padding: 10px 14px;
    border: 2px solid var(--border);
    border-radius: 12px;
    background: var(--panel-soft);
    color: var(--text);
}
.choice-button-label {
    font-size: 17px;
    font-weight: 700;
    line-height: 1.25;
}
.choice-button-hint {
    font-size: 13px;
    color: var(--muted);
}
.choice-button input:checked + .choice-button-body {
    border-color: var(--green);
    background: rgba(34, 197, 94, 0.14);
}
.choice-button input:focus-visible + .choice-button-body {
    outline: 3px solid var(--accent);
    outline-offset: 2px;
}
button.choice-button {
    width: 100%;
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    text-align: left;
}

/* Quick Capture (captures/quick.html, Phase 2A). Without JavaScript every
   step shows as one long form; .wizard-js (added by capture-wizard.js)
   shows one step at a time. */
.quick-capture .wizard-js-only {
    display: none;
}
.quick-capture.wizard-js .wizard-js-only:not([hidden]) {
    display: revert;
}
.quick-capture.wizard-js .quick-nav.wizard-js-only:not([hidden]) {
    display: grid;
}
.quick-header {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 0 0 12px;
    min-height: 44px;
}
.quick-back {
    min-height: 44px;
    padding: 8px 16px;
    color: var(--text) !important;
    font-weight: 700;
    border-radius: 12px;
    cursor: pointer;
}
.quick-progress {
    color: var(--muted);
    font-size: 14px;
}
.quick-full-form {
    margin-left: auto;
    font-size: 14px;
    color: var(--muted);
}
.quick-step {
    margin: 0 0 24px;
}
.quick-capture.wizard-js .quick-step {
    margin: 0;
}
.quick-step h2 {
    font-size: 24px;
    margin: 0 0 14px;
}
.quick-textarea {
    min-height: 180px;
    font-size: 17px;
}
.quick-nav {
    gap: 10px;
    margin-top: 12px;
}
.quick-next,
.quick-send {
    display: block;
    width: 100%;
    min-height: 52px;
    font-size: 18px;
    border: none;
    cursor: pointer;
    text-align: center;
}
.quick-find-job {
    margin: 0 0 10px;
}
.quick-find-summary {
    list-style: none;
}
.quick-find-summary::-webkit-details-marker {
    display: none;
}
.quick-find-job[open] .quick-find-summary .choice-button-body {
    border-color: var(--accent);
}
.quick-job-search {
    width: 100%;
    margin: 10px 0;
    min-height: 48px;
    font-size: 17px;
}
.quick-job-list {
    max-height: 50vh;
    overflow-y: auto;
}
.quick-capture [hidden] {
    display: none !important;
}
.quick-job-group {
    margin: 8px 0 0;
}
.quick-capture.wizard-js .quick-photo-picker input[type="file"] {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}
.quick-photo-picker input[type="file"] {
    display: block;
    max-width: 100%;
    margin: 0 0 6px;
}
.quick-photo-picker .choice-button-body {
    min-height: 72px;
    align-items: center;
    text-align: center;
}
.quick-photo-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    list-style: none;
    padding: 0;
    margin: 0 0 10px;
}
.quick-photo-item {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: center;
    width: 96px;
    height: 84px;
    padding: 6px 8px;
    border-radius: 10px;
    border: 1px solid var(--border);
    background: var(--panel-soft);
}
.quick-photo-icon {
    font-size: 22px;
    line-height: 1.1;
}
.quick-photo-name,
.quick-photo-size {
    display: block;
    font-size: 11px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding-right: 4px;
}
.quick-photo-size {
    color: var(--muted);
}
.quick-photo-remove {
    position: absolute;
    top: 2px;
    right: 2px;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: none;
    background: rgba(0, 0, 0, 0.7);
    color: #ffffff;
    font-size: 15px;
    cursor: pointer;
}
.quick-later {
    margin-top: 6px;
}
.quick-more-people {
    margin: 0 0 12px;
}
.quick-more-summary {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 8px 16px;
    border-radius: 12px;
    color: var(--text) !important;
    font-weight: 700;
    cursor: pointer;
    list-style: none;
}
.quick-more-people[open] .quick-more-summary {
    margin-bottom: 10px;
}
.quick-summary {
    color: var(--muted);
    margin: 12px 0 8px;
}
.quick-upload-note {
    font-weight: 700;
}
.quick-done h2 {
    font-size: 28px;
    color: var(--green);
}
.quick-done-number {
    font-size: 18px;
    font-weight: 700;
    margin: 0 0 8px;
}
.quick-done-facts {
    margin: 0 0 16px;
    padding-left: 20px;
}
/* One primary action (+ New Capture, full width), then View Capture and
   Done as a matched pair of real secondary buttons — not boxed links. */
.quick-done-actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
}
.quick-done-actions a {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 48px;
    text-align: center;
}
.quick-done-actions .quick-done-new {
    grid-column: 1 / -1;
}
.quick-done-actions .btn-secondary {
    border: 2px solid var(--muted);
    border-radius: var(--radius);
    background: var(--panel-soft) !important;
    color: var(--text) !important;
    font-weight: 700;
    text-decoration: none;
}
.quick-done-actions .btn-secondary:hover,
.quick-done-actions .btn-secondary:focus-visible {
    border-color: var(--text);
    opacity: 1;
}
@media (min-width: 641px) {
    .quick-capture {
        max-width: 640px;
    }
}

/* Customer Billing Setup inline actions (2026-10-03). */
.customer-setup-inline {
    margin-top: 4px;
}
.customer-setup-inline > summary {
    display: inline-flex;
    list-style: none;
    cursor: pointer;
}
.customer-setup-inline > summary::-webkit-details-marker {
    display: none;
}
.customer-setup-inline-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
}

/* AI Proposal Review (2026-10-03) — Business Analysis v4 task/follow-up
   proposals. "AI suggested" (dashed, muted, accent tag) and "Human
   decision" (solid, tag in the decision's colour) are deliberately two
   visually distinct blocks so what Sparky proposed is never mistaken for
   what a person decided. */
.proposal-list {
    list-style: none;
    padding-left: 0;
}
.proposal {
    margin-bottom: 12px;
}
.proposal-tag {
    display: inline-block;
    border-radius: 999px;
    padding: 0 8px;
    margin-right: 6px;
    font-size: 0.75em;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
}
.proposal-ai {
    border: 1px dashed var(--border);
    border-radius: 8px 8px 0 0;
    padding: 6px 10px;
}
.proposal-tag-ai {
    color: var(--accent);
    border: 1px dashed var(--accent);
}
.proposal-human {
    border: 1px solid var(--border);
    border-top: none;
    border-radius: 0 0 8px 8px;
    padding: 6px 10px;
    background: var(--panel-soft);
}
.proposal-tag-human {
    color: var(--text);
    border: 1px solid var(--muted);
}
.proposal-human-accepted {
    border-left: 4px solid var(--green);
}
.proposal-human-accepted .proposal-outcome {
    color: var(--green);
}
.proposal-human-dismissed {
    border-left: 4px solid var(--muted);
}
.proposal-human-dismissed .proposal-outcome {
    color: var(--muted);
}
.proposal-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 8px;
    margin-top: 6px;
}
.proposal-inline-form {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 0;
}
.proposal-panel > summary {
    display: inline-flex;
    list-style: none;
    cursor: pointer;
}
.proposal-panel > summary::-webkit-details-marker {
    display: none;
}
.proposal-edit-form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 8px;
    margin-top: 6px;
}
.proposal-edit-form input[type="text"],
.proposal-edit-form textarea {
    width: min(100%, 360px);
}
.proposal-action-link {
    display: block;
    margin-top: 6px;
    font-weight: 700;
    font-size: 1.05em;
    color: var(--green);
}
.proposal-project {
    margin: 4px 0 0;
}
.proposal-project-unclassified {
    color: var(--yellow);
    font-weight: 700;
}
.proposal-edit-project {
    flex-basis: 100%;
}
.proposal-changes {
    margin: 6px 0 0;
    padding-left: 18px;
    font-size: 0.9em;
}

/* Procurement refinement P-R1 (2026-10-20): wider working area on desktop
   (Procurement pages only), compact neutral states, one warning colour
   rule — red only for overdue/at-risk, amber for due today / missing
   dates, neutral otherwise. */
.console.procurement-console {
  max-width: 1600px;
}

.proc-header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: 12px 24px;
  margin-bottom: 12px;
}

.proc-header h1 {
  margin: 0;
}

.proc-scope {
  margin: 4px 0 0;
  color: var(--muted);
}

.proc-health {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.proc-health li {
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 12px;
  background: var(--panel);
}

.proc-health .proc-count {
  font-weight: 700;
}

.proc-health .proc-health-red {
  border-color: rgba(239, 68, 68, 0.6);
  color: var(--red);
}

.proc-health .proc-health-amber {
  border-color: rgba(250, 204, 21, 0.5);
  color: var(--yellow);
}

.proc-note {
  margin: 4px 0 12px;
  font-size: 0.85em;
  color: var(--muted);
}

.proc-neutral {
  color: var(--muted);
  margin: 0 0 8px;
}

.proc-badge {
  display: inline-block;
  border-radius: 6px;
  padding: 1px 8px;
  font-size: 0.9em;
}

.proc-badge-overdue {
  background: rgba(239, 68, 68, 0.12);
  color: var(--red);
}

.proc-badge-soon {
  background: rgba(250, 204, 21, 0.12);
  color: var(--yellow);
}

.proc-due {
  color: var(--muted);
}

.proc-error {
  border-color: rgba(239, 68, 68, 0.5);
  color: var(--red);
}

.proc-table {
  width: 100%;
}

.proc-filter-grid,
.proc-form {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 0 20px;
}

.proc-form .proc-wide,
.proc-form fieldset {
  grid-column: 1 / -1;
}

.proc-form input[type="text"],
.proc-form input[type="date"],
.proc-form input[type="number"],
.proc-form select,
.proc-filter-grid input[type="text"],
.proc-filter-grid select {
  width: 100%;
  max-width: 100%;
}

.proc-form .proc-input-wide,
.proc-form textarea {
  width: 100%;
}

.proc-form .proc-qty {
  max-width: 140px;
}

.proc-qty-row {
  display: flex;
  gap: 16px;
}

.proc-qty-row span {
  flex: 0 1 200px;
}

.proc-facts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px 24px;
  margin: 0 0 12px;
}

.proc-facts dt {
  color: var(--muted);
  font-size: 0.85em;
}

.proc-facts dd {
  margin: 2px 0 0;
}

.proc-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.proc-action {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 6px 12px;
}

.proc-action > summary {
  cursor: pointer;
  font-weight: 600;
}

.proc-action-primary {
  border-color: var(--accent);
}

.proc-history {
  list-style: none;
  padding: 0;
}

.proc-history li {
  border-bottom: 1px solid var(--border);
  padding: 8px 0;
}

.proc-voided {
  opacity: 0.6;
}

.proc-voided > strong {
  text-decoration: line-through;
}

.procurement-job-row-line {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 4px 16px;
  margin: 4px 0;
}

.project-procurement-card .proc-table td {
  vertical-align: top;
}

@media (max-width: 640px) {
  .proc-qty-row {
    flex-direction: column;
    gap: 0;
  }
}

.table-scroll {
  overflow-x: auto;
}

.proc-changes {
  margin: 4px 0 0;
  padding-left: 18px;
}

.proc-form-nested {
  gap: 0 16px;
}

.proc-attention {
  border: 1px solid rgba(239, 68, 68, 0.4);
  border-radius: 10px;
  padding: 4px 12px 8px;
  margin-bottom: 12px;
}

.proc-attention h3 {
  color: var(--red);
  margin: 6px 0;
}

/* P-R1: entry fields in Procurement forms match the dark panel textareas. */
.proc-form input[type="text"],
.proc-form input[type="date"],
.proc-form input[type="number"],
.proc-form select,
.proc-filter-grid input[type="text"],
.proc-filter-grid select,
.proc-find input[type="text"] {
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
  min-height: 40px;
}

.proc-form .capture-textarea {
  min-height: 64px;
}

.proc-form fieldset {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 14px;
}

/* Organization Register search, filters and Back to Top (Angelica's
   Organizations requests, 2026-10-22). Filters sit in a grid above the
   table rather than inside <thead>, because the phone layout hides
   <thead> (tables become stacked cards). */
.organization-search-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 12px;
}

.organization-search-bar input[type="search"] {
  flex: 1 1 280px;
  min-height: 44px;
  padding: 10px 14px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
  font-size: 1.05rem;
}

.organization-filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 10px 16px;
  align-items: start;
}

.organization-filter-grid p {
  margin: 0;
}

.organization-filter-grid label {
  display: block;
  font-size: 13px;
  font-weight: 700;
  color: var(--muted);
}

.organization-filter-grid input[type="text"],
.organization-filter-grid select {
  width: 100%;
  min-height: 40px;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
}

.organization-filter-types summary {
  font-size: 13px;
  font-weight: 700;
  color: var(--muted);
  cursor: pointer;
  min-height: 40px;
  line-height: 40px;
}

.organization-filter-types fieldset {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 12px;
  margin: 0;
}

.organization-filter-types legend {
  font-size: 13px;
  color: var(--muted);
}

.organization-filter-types fieldset label {
  font-weight: 400;
  font-size: 14px;
  color: var(--text);
  padding: 4px 0;
}

/* Apply/Clear are the grid's last cell, bottom-aligned with the inputs
   beside them (or with the bottom of an expanded Types list), so they
   sit next to the controls they act on. */
.organization-filter-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  align-self: end;
  gap: 10px;
  min-height: 40px;
}

.organization-former-types {
  color: var(--muted);
}

.organization-result-count {
  color: var(--muted);
  font-size: 14px;
  margin: 10px 0 0;
}

/* Room below the last row so the floating button never covers its
   Deactivate/Activate action at the bottom of the page. */
.organization-register {
  padding-bottom: 88px;
}

.back-to-top {
  position: fixed;
  right: max(16px, env(safe-area-inset-right));
  bottom: calc(16px + env(safe-area-inset-bottom));
  z-index: 50;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  font-size: 24px;
  line-height: 1;
  box-shadow: var(--shadow);
  cursor: pointer;
}

.back-to-top:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.back-to-top:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

.back-to-top[hidden] {
  display: none;
}

/* Contract Changes CC-1 (2026-10-23) — CCO register card and detail page. */
.cco-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.75rem 1.5rem;
  margin: 0 0 1rem;
}

.cco-facts dt {
  font-size: 0.8rem;
  color: var(--muted);
}

.cco-facts dd {
  margin: 0;
}

.cco-scope {
  white-space: pre-wrap;
}

.cco-credit {
  color: var(--red);
}

.cco-reason-action {
  margin-top: 0.75rem;
}

/* CC-4e3: the replacement CCO belongs to "remainder superseded" only; hidden
   for any other outcome where :has() is supported, visible otherwise. */
.cco-disposition-form:has(select[name="kind"] option:checked:not([value="remainder_superseded"])) .cco-disposition-replacement {
  display: none;
}

.cco-disposition {
  margin-top: 0.75rem;
  overflow-wrap: anywhere;
}

.cco-disposition-history {
  padding-left: 1.2rem;
  overflow-wrap: anywhere;
}

.cco-closed {
  margin-top: 0.75rem;
}

.cco-nowrap {
  white-space: nowrap;
}

.cco-title-col {
  min-width: 180px;
}

.cco-meta {
  font-size: 0.85rem;
  color: var(--muted);
}

/* Stacked label-over-field forms, the .human-review-form look. */
.cco-form label {
  display: block;
  margin-top: 14px;
  font-weight: 600;
  color: var(--text);
}

.cco-form .form-row:first-of-type label {
  margin-top: 0;
}

.cco-form input[type="text"],
.cco-form input[type="number"],
.cco-form input[type="date"],
.cco-form input[type="datetime-local"],
.cco-form textarea {
  display: block;
  width: 100%;
  max-width: 640px;
  margin-top: 6px;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
  box-sizing: border-box;
}

.cco-form input[type="number"],
.cco-form input[type="date"],
.cco-form input[type="datetime-local"] {
  max-width: 220px;
  color-scheme: dark;
}

.cco-form button {
  margin-top: 16px;
}

.cco-client-number-form {
  margin-top: 1.25rem;
}

.cco-form .cco-check {
  font-weight: 400;
}

/* CC-4b UI cleanup (2026-10-25): radio groups ("How did the client
   authorize it?", "Value", "Coverage") in the dark theme, not the
   browser's bright default border, with room before the next field. */
.cco-form fieldset {
  max-width: 640px;
  margin: 16px 0;
  padding: 6px 14px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel-soft);
}

.cco-form legend {
  padding: 0 6px;
  font-weight: 600;
  color: var(--text);
}

.cco-form fieldset label.cco-check {
  margin-top: 10px;
  line-height: 1.45;
}

.cco-form fieldset + .form-row label {
  margin-top: 0;
}

/* CC-4b UI cleanup: the CCO and Change Order tables carry many columns
   (the Client position column was added to the CCO table), so on
   tablet / desktop they use tighter cells and keep the row action
   compact and in view. Phones keep the stacked-card layout. */
@media (min-width: 641px) {
  .cco-table {
    width: 100%;
  }

  .cco-table th,
  .cco-table td {
    padding: 10px 12px;
    vertical-align: top;
  }

  .cco-table th {
    font-size: 0.9rem;
  }

  .cco-table td:last-child,
  .cco-table th:last-child {
    width: 1%;
    padding-right: 0;
    white-space: nowrap;
  }

  .cco-table .cco-title-col {
    min-width: 160px;
  }
}

/* CC-2 — CCO Documents & Evidence: compact groups by category. CC-3's
   CCO PDF block (stored PDF links) uses the same link colour. */
.cco-documents a,
.cco-documents a:visited,
.cco-pdf a,
.cco-pdf a:visited,
.cco-evidence-form-page a,
.cco-evidence-form-page a:visited,
.cco-evidence-list a,
.cco-evidence-list a:visited {
  color: var(--accent);
}

/* CC-4e1: a field or CCO link the amendment changes, on the confirmation page */
.co-changed {
  box-shadow: inset 3px 0 0 var(--accent);
}

.cco-form select {
  display: block;
  width: 100%;
  max-width: 420px;
  margin-top: 6px;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
  box-sizing: border-box;
}

.cco-form input[type="file"] {
  display: block;
  margin-top: 6px;
  max-width: 100%;
  color: var(--text);
}

.cco-evidence-group + .cco-evidence-group {
  margin-top: 1rem;
}

.cco-evidence-group h3 {
  margin: 0 0 0.35rem;
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.cco-evidence-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.cco-evidence-item {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
  overflow-wrap: anywhere;
}

.cco-evidence-item:last-child {
  border-bottom: none;
}

.cco-evidence-item .cco-meta {
  margin-left: 0.35rem;
}

.cco-evidence-item br + .cco-meta {
  margin-left: 0;
}

.cco-evidence-restricted {
  font-style: italic;
  color: var(--muted);
}

.cco-evidence-remove summary {
  font-size: 0.85rem;
  color: var(--muted);
  cursor: pointer;
}

.cco-evidence-add {
  margin-top: 1rem;
}

.cco-evidence-add summary {
  display: inline-block;
  cursor: pointer;
  list-style: none;
}

.cco-evidence-add summary::-webkit-details-marker {
  display: none;
}

/* Field Complete / Billing Required (FC-1/FC-2, 2026-10-27). The list
   badge marks a Project that stays active but is closed to field time;
   the billing-age chips show the one-business-day clock (same day /
   due / overdue) on the Project banner and in Ready for Billing. */
.badge-field-complete {
  display: inline-flex;
  align-items: center;
  color: var(--accent);
  background: rgba(77, 163, 255, 0.12);
  border: 1px solid var(--accent);
  border-radius: 999px;
  padding: 2px 10px;
  font-weight: 700;
  font-size: 0.85em;
  white-space: nowrap;
}

.billing-age {
  display: inline-block;
  border-radius: 999px;
  padding: 1px 9px;
  font-weight: 700;
  font-size: 0.85em;
  white-space: nowrap;
  border: 1px solid currentColor;
}

.billing-age-same_day { color: var(--green); background: rgba(34, 197, 94, 0.12); }
.billing-age-due { color: var(--yellow); background: rgba(234, 179, 8, 0.14); }
.billing-age-overdue { color: var(--red); background: rgba(239, 68, 68, 0.12); }

.ready-for-billing-table-wrap { overflow-x: auto; }
.ready-for-billing-table { width: 100%; border-collapse: collapse; font-size: 0.92em; }
.ready-for-billing-table th,
.ready-for-billing-table td { text-align: left; vertical-align: top; padding: 8px 10px; border-bottom: 1px solid var(--border); }
.ready-for-billing-table th { font-size: 0.78em; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
.ready-for-billing-table td.nowrap { white-space: nowrap; }
.ready-for-billing-table .cell-note { display: block; font-size: 0.88em; color: var(--muted); margin-top: 2px; }
.ready-for-billing-table td.col-project { min-width: 12em; }
.ready-for-billing-table td.col-blocker { min-width: 13em; }
.ready-for-billing-warning { color: var(--yellow); font-weight: 600; }
.ready-for-billing-table a { color: var(--accent); }
.handoff-action-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; }
.handoff-action-grid > .card { min-width: 0; }
