/* Simple CRM — application stylesheet.
   Layered on top of Bootstrap 5.3; overrides live here, never inline. */

/* Two accents, deliberately. --crm-accent is the brand blue and stays on fills,
   borders and chart-adjacent decoration, where WCAG text rules don't apply.
   --crm-accent-text is the same hue darkened for anything that carries text or
   sits behind text: #2a78d6 measures 4.42:1 on white (below the 4.5:1 AA
   minimum) and 3.91:1 on --crm-accent-subtle, while #2266bb measures 5.70:1 and
   5.05:1. Links are the bulk of every list in this app, so this matters.

   Same split for the outcome pair. --crm-teal is the chart/fill colour (also used
   by the MBR charts below); --crm-teal-text is a darkened partner for anything
   carrying text — the raw #0f9b8e measures 3.44:1 on white and fails AA, while
   #0a7a70 measures 5.21:1. --crm-red already measures 4.80:1 on white and passes,
   so it needs no separate text partner and is used directly by both. Teal/red
   (not Bootstrap's green/red) because red/green is indistinguishable to a
   deuteranope — see the MBR chart comment further down for the CVD check. */
:root,
[data-bs-theme="light"] {
  /* Semantic light-theme foundation. These values deliberately mirror the
     application's current Bootstrap 5.3 light appearance; Phase 1 only names
     the contract and does not opt any page into a different presentation. */
  --crm-canvas: #f8f9fa;
  --crm-surface: #fff;
  --crm-surface-elevated: #fff;
  --crm-text: #212529;
  --crm-text-muted: #6c757d;
  --crm-focus: #2266bb;

  /* Brand and semantic states. Positive/negative intentionally match
     Bootstrap success/danger. They do not replace the distinct teal/red
     outcome convention documented above. */
  --crm-accent: #2a78d6;
  --crm-accent-text: #2266bb;
  --crm-accent-subtle: #eaf2fc;
  --crm-positive: #198754;
  --crm-warning: #ffc107;
  --crm-negative: #dc3545;
  --crm-teal: #0f9b8e;
  --crm-teal-text: #0a7a70;
  --crm-red: #d03b3b;

  /* Color-vision-deficiency-safe categorical chart palette. Teal and
     vermillion must not represent unrelated categories beside the fixed
     won/lost teal/red series; use blue, amber and purple in mixed plots. */
  --crm-chart-blue: #2a78d6;
  --crm-chart-teal: #0f9b8e;
  --crm-chart-vermillion: #d55e00;
  --crm-chart-amber: #eda100;
  --crm-chart-purple: #4a3aa7;
  /* Sequential ramp for ORDERED categories — pipeline stage above all, which is a
     progression (Lead -> Negotiation), not a set of unrelated labels. Categorical
     tokens are wrong for it twice over: they imply stages are unrelated, and with
     only five of them a sixth open stage has to reuse a colour, which reads as two
     stages being the same thing.

     One hue (OKLab hue 253-257, i.e. --crm-accent's), monotonic in lightness, steps
     of 9-14 OKLab L. Lightness is the one axis every form of colour-vision deficiency
     retains, so a ramp separated on lightness alone stays readable to everyone and in
     greyscale print. Step 1 is the earliest stage in both themes: light-on-light here,
     dark-on-dark below, so "least" always reads as least against its own ground.

     Precomputed hex, not color-mix(): html2canvas 1.4.1 cannot parse color-mix and
     silently breaks the MBR PNG export (see the note at the top of this file).
     Source ramp: --crm-accent #2a78d6 lightened/darkened along a fixed hue. Beyond
     five steps charts fall back to --crm-chart-text rather than cycling. */
  --crm-seq-1: #cfe1f7;
  --crm-seq-2: #9ec3ef;
  --crm-seq-3: #6da5e6;
  --crm-seq-4: #3d87dd;
  --crm-seq-5: #1f5ba8;
  --crm-chart-grid: rgba(11, 11, 11, 0.08);
  --crm-chart-text: #6c757d;
  --crm-chart-tooltip-bg: #212529;
  --crm-chart-tooltip-text: #fff;

  --crm-radius: 0.5rem;
  --crm-border: rgba(11, 11, 11, 0.08);
  --crm-shadow-sm: 0 1px 2px rgba(11, 11, 11, 0.05);
  --crm-shadow: 0 1px 3px rgba(11, 11, 11, 0.07), 0 4px 12px rgba(11, 11, 11, 0.04);
}

[data-bs-theme="dark"] {
  color-scheme: dark;
  --crm-canvas: #15191d;
  --crm-surface: #1d2329;
  --crm-surface-elevated: #252c33;
  --crm-text: #f1f3f5;
  --crm-text-muted: #adb5bd;
  --crm-focus: #8bb9f2;
  --crm-accent: #6ea8fe;
  --crm-accent-text: #8bb9f2;
  --crm-accent-subtle: #24364d;
  --crm-positive: #75b798;
  --crm-warning: #ffda6a;
  --crm-negative: #ea868f;
  /* Outcome values stay semantically distinct from Bootstrap state colors. */
  --crm-teal: #20b7aa;
  --crm-teal-text: #5fd5ca;
  --crm-red: #d95757;
  --crm-chart-blue: #6ea8fe;
  --crm-chart-teal: #20b7aa;
  --crm-chart-vermillion: #ff7a2f;
  /* The same ramp inverted in direction, so step 1 stays the lowest-emphasis step
     against a dark ground. Same hue, same monotonic lightness, steps of 9-13 L. */
  --crm-seq-1: #1e3a5f;
  --crm-seq-2: #2a5488;
  --crm-seq-3: #3a70b4;
  --crm-seq-4: #5b93d8;
  --crm-seq-5: #8fbdf0;
  --crm-chart-amber: #ffcf5c;
  --crm-chart-purple: #a89bea;
  --crm-chart-grid: rgba(255, 255, 255, 0.14);
  --crm-chart-text: #ced4da;
  --crm-chart-tooltip-bg: #f1f3f5;
  --crm-chart-tooltip-text: #15191d;
  --crm-border: rgba(255, 255, 255, 0.14);
  --crm-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.28);
  --crm-shadow: 0 1px 3px rgba(0, 0, 0, 0.35), 0 4px 12px rgba(0, 0, 0, 0.2);

  --bs-body-color: var(--crm-text);
  --bs-body-bg: var(--crm-canvas);
  --bs-secondary-color: var(--crm-text-muted);
  --bs-tertiary-bg: var(--crm-canvas);
  --bs-border-color: var(--crm-border);
  --bs-link-color: var(--crm-accent-text);
  --bs-link-hover-color: #b8d4f7;
  --bs-dropdown-color: var(--crm-text);
  --bs-dropdown-bg: var(--crm-surface-elevated);
  --bs-dropdown-border-color: var(--crm-border);
  --bs-dropdown-link-color: var(--crm-text);
  --bs-dropdown-link-hover-color: var(--crm-text);
  --bs-dropdown-link-hover-bg: #303942;
  --bs-dropdown-link-active-color: #fff;
  --bs-dropdown-link-active-bg: #295f9e;
  --bs-dropdown-header-color: var(--crm-text-muted);
}

html,
body { min-height: 100%; }
body {
  background-color: var(--crm-canvas);
  color: var(--crm-text);
  font-size: 0.9375rem;
}
h1,
.h1 { font-size: 1.5rem; }

/* Buttons and links pick up the accent. Filled buttons use the text accent too:
   their label is white on that fill, so it is bound by the same 4.5:1 rule.
   Hover/active shift one and two steps darker again to keep the state visible. */
.btn-primary {
  --bs-btn-bg: var(--crm-accent-text);
  --bs-btn-border-color: var(--crm-accent-text);
  --bs-btn-hover-bg: #1d5aa7;
  --bs-btn-hover-border-color: #1d5aa7;
  --bs-btn-active-bg: #174b8b;
  --bs-btn-active-border-color: #174b8b;
}
.btn-outline-primary {
  --bs-btn-color: var(--crm-accent-text);
  --bs-btn-border-color: var(--crm-accent-text);
  --bs-btn-hover-bg: var(--crm-accent-text);
  --bs-btn-hover-border-color: var(--crm-accent-text);
  --bs-btn-active-bg: #1d5aa7;
  --bs-btn-active-border-color: #1d5aa7;
}
a { color: var(--crm-accent-text); }

[data-bs-theme="dark"] .btn-primary {
  --bs-btn-bg: #2266bb;
  --bs-btn-border-color: #2266bb;
  --bs-btn-hover-bg: #2f78ce;
  --bs-btn-hover-border-color: #2f78ce;
  --bs-btn-active-bg: #3d82d5;
  --bs-btn-active-border-color: #3d82d5;
}
[data-bs-theme="dark"] .btn-outline-primary {
  --bs-btn-color: var(--crm-accent-text);
  --bs-btn-border-color: #6ea8fe;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #2266bb;
  --bs-btn-hover-border-color: #6ea8fe;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #2f78ce;
  --bs-btn-active-border-color: #8bb9f2;
}

/* Outcome badges (won/lost) — see templates/_outcome_badge.html, the single place
   that decides won vs. lost styling app-wide. */
.badge-outcome-won { background-color: var(--crm-teal-text); color: #fff; }
.badge-outcome-lost { background-color: var(--crm-red); color: #fff; }
.text-outcome-won { color: var(--crm-teal-text) !important; }
.text-outcome-lost { color: var(--crm-red) !important; }
[data-bs-theme="dark"] .badge-outcome-won { background-color: #0a7a70; }
/* Forecast-category badges (templates/_forecast_badge.html). The chart blue, amber and
   purple, never the outcome teal/red above: a forecast is a judgement about an open
   deal, not a result. Each states its own text colour, and dark mode restates it,
   because the chart tokens lighten there — white on the dark-theme amber or blue would
   drop under 4.5:1. "Omitted" is a plain outlined badge: it is the absence of a
   forecast, so it should recede rather than compete with the three that mean something. */
.badge-forecast-commit { background-color: var(--crm-chart-blue); color: #fff; }
.badge-forecast-best-case { background-color: var(--crm-chart-amber); color: #15191d; }
.badge-forecast-pipeline { background-color: var(--crm-chart-purple); color: #fff; }
[data-bs-theme="dark"] .badge-forecast-commit,
[data-bs-theme="dark"] .badge-forecast-pipeline { color: #15191d; }
.btn-outcome-won {
  --bs-btn-color: #fff;
  --bs-btn-bg: #0a7a70;
  --bs-btn-border-color: #0a7a70;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #08675f;
  --bs-btn-hover-border-color: #08675f;
}
.btn-outcome-lost {
  --bs-btn-color: #fff;
  --bs-btn-bg: var(--crm-red);
  --bs-btn-border-color: var(--crm-red);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #af3030;
  --bs-btn-hover-border-color: #af3030;
}
/* Outline partners for the pair above, for the places where Won/Lost is one action
   among several outline buttons (the opportunity detail header) rather than the
   primary commit button. They carry their label in the outcome colour, so they take
   the text-safe tokens: --crm-teal-text (#0a7a70, 5.21:1 on white) rather than the
   raw --crm-teal (3.44:1, fails AA), and --crm-red (4.80:1, already passes). Same
   fills/text split as .btn-outline-primary above. */
.btn-outline-outcome-won {
  --bs-btn-color: var(--crm-teal-text);
  --bs-btn-border-color: var(--crm-teal-text);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--crm-teal-text);
  --bs-btn-hover-border-color: var(--crm-teal-text);
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #08675f;
  --bs-btn-active-border-color: #08675f;
}
.btn-outline-outcome-lost {
  --bs-btn-color: var(--crm-red);
  --bs-btn-border-color: var(--crm-red);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--crm-red);
  --bs-btn-hover-border-color: var(--crm-red);
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #af3030;
  --bs-btn-active-border-color: #af3030;
}
/* Dark theme: the light-theme text partners are too dark on the dark surfaces, so
   the label takes the lighter --crm-teal-text (#5fd5ca) / --crm-red (#d95757) and
   the hover fill keeps the darker shade with white text. */
[data-bs-theme="dark"] .btn-outline-outcome-won {
  --bs-btn-color: var(--crm-teal-text);
  --bs-btn-border-color: var(--crm-teal);
  --bs-btn-hover-bg: #0a7a70;
  --bs-btn-hover-border-color: var(--crm-teal);
  --bs-btn-active-bg: #08675f;
  --bs-btn-active-border-color: var(--crm-teal-text);
}
[data-bs-theme="dark"] .btn-outline-outcome-lost {
  --bs-btn-color: var(--crm-red);
  --bs-btn-border-color: var(--crm-red);
  --bs-btn-hover-bg: #af3030;
  --bs-btn-hover-border-color: var(--crm-red);
  --bs-btn-active-bg: #932828;
  --bs-btn-active-border-color: var(--crm-red);
}
[data-bs-theme="dark"] .table-light {
  --bs-table-bg: var(--crm-surface-elevated);
  --bs-table-color: var(--crm-text);
  --bs-table-border-color: var(--crm-border);
}

.crm-chart-frame {
  position: relative;
  min-height: 15rem;
}
.crm-report-table {
  font-variant-numeric: tabular-nums;
}
.crm-report-table th,
.crm-report-table td { vertical-align: middle; }
.crm-report-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  white-space: nowrap;
}
.accordion-body:has(.crm-report-table) { overflow-x: auto; }

/* Application shell. Desktop width changes are immediate: animating layout here
   would repeatedly reflow every table and chart on the page. */
.crm-shell { min-height: 100vh; }
.crm-shell-body {
  min-width: 0;
  min-height: 100vh;
  margin-left: 14rem;
}
[data-crm-sidebar="collapsed"] .crm-shell-body { margin-left: 3.5rem; }
.crm-shell-anonymous .crm-shell-body { margin-left: 0; }

.crm-sidebar {
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 1040;
  width: 14rem;
  overflow: hidden;
  background-color: var(--crm-surface);
  border-right: 1px solid var(--crm-border);
  box-shadow: var(--crm-shadow-sm);
}
[data-crm-sidebar="collapsed"] .crm-sidebar { width: 3.5rem; }
.crm-sidebar-header {
  display: flex;
  align-items: center;
  height: 3.25rem;
  padding: 0.5rem;
  border-bottom: 1px solid var(--crm-border);
}
.crm-sidebar-brand {
  display: flex;
  align-items: center;
  min-width: 0;
  gap: 0.6rem;
  color: var(--crm-accent-text);
  font-size: 1rem;
  font-weight: 650;
  letter-spacing: -0.01em;
  text-decoration: none;
}
.crm-brand-mark {
  display: inline-flex;
  flex: 0 0 2.5rem;
  width: 2.5rem;
  height: 2.25rem;
  align-items: center;
  justify-content: center;
  border-radius: calc(var(--crm-radius) - 0.125rem);
  background-color: var(--crm-accent-subtle);
  color: var(--crm-accent-text);
  font-size: 0.72rem;
  font-weight: 750;
  letter-spacing: 0.04em;
}
.crm-sidebar-close {
  display: none;
  margin-left: auto;
}
.crm-sidebar-nav {
  height: calc(100vh - 3.25rem);
  overflow-x: hidden;
  overflow-y: auto;
  padding: 0.65rem 0.5rem 1rem;
}
.crm-sidebar-link {
  display: flex;
  align-items: center;
  min-height: 2.35rem;
  gap: 0.65rem;
  padding: 0.45rem 0.65rem;
  border-radius: calc(var(--crm-radius) - 0.125rem);
  color: var(--crm-text-muted);
  font-size: 0.9rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}
.crm-sidebar-link:hover {
  color: var(--crm-text);
  background-color: var(--crm-canvas);
}
.crm-sidebar-link.active {
  color: var(--crm-accent-text);
  background-color: var(--crm-accent-subtle);
  font-weight: 650;
}
.crm-sidebar-link .crm-icon { flex: 0 0 auto; }
.crm-nav-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
[data-crm-sidebar="collapsed"] .crm-sidebar .crm-nav-text { display: none; }
[data-crm-sidebar="collapsed"] .crm-sidebar-link {
  justify-content: center;
  padding-inline: 0;
}
[data-crm-sidebar="collapsed"] .crm-sidebar-link .crm-icon {
  width: 1.375rem;
  height: 1.375rem;
}
.crm-sidebar-divider {
  margin: 0.55rem 0.4rem;
  border-top: 1px solid var(--crm-border);
}
.crm-sidebar-section {
  padding: 0.1rem 0.65rem 0.25rem;
  color: var(--crm-text-muted);
  font-size: 0.68rem;
  font-weight: 650;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.crm-sidebar-backdrop { display: none; }

.crm-topbar {
  position: sticky;
  top: 0;
  z-index: 1030;
  display: flex;
  min-height: 3.25rem;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.45rem 1rem;
  background-color: var(--crm-surface);
  border-bottom: 1px solid var(--crm-border);
  box-shadow: var(--crm-shadow-sm);
}
.crm-topbar-start,
.crm-topbar-tools,
.crm-global-search,
.crm-theme-selector,
.crm-user-toggle,
.crm-menu-label,
.crm-menu-item {
  display: flex;
  align-items: center;
}
.crm-topbar-start { min-width: 0; }
.crm-topbar-tools {
  min-width: 0;
  gap: 0.65rem;
  margin-left: auto;
}
.crm-topbar-brand {
  display: none;
  margin-left: 0.6rem;
  color: var(--crm-accent-text);
  font-weight: 650;
  text-decoration: none;
  white-space: nowrap;
}
.crm-topbar-brand-anonymous {
  display: inline-block;
  margin-left: 0;
}
.crm-icon-button,
.crm-sidebar-close,
.crm-user-toggle {
  border: 1px solid transparent;
  background: transparent;
  color: var(--crm-text-muted);
}
.crm-icon-button,
.crm-sidebar-close {
  width: 2.25rem;
  height: 2.25rem;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: calc(var(--crm-radius) - 0.125rem);
}
.crm-icon-button { display: inline-flex; }
.crm-icon-button:hover,
.crm-sidebar-close:hover,
.crm-user-toggle:hover {
  color: var(--crm-text);
  background-color: var(--crm-canvas);
  border-color: var(--crm-border);
}
.crm-theme-selector {
  flex: 0 0 auto;
  padding: 0.125rem;
  border: 1px solid var(--crm-border);
  border-radius: var(--crm-radius);
}
.crm-theme-selector .crm-icon-button {
  width: 2rem;
  height: 1.85rem;
}
.crm-theme-selector .crm-icon-button.active {
  color: var(--crm-accent-text);
  background-color: var(--crm-accent-subtle);
}
.crm-sidebar-toggle .crm-toggle-mobile { display: none; }
/* Only the narrow tier collapses search behind a button; above 576px the box is
   inline and this control does not exist. */
.crm-search-toggle { display: none; }
.crm-global-search {
  position: relative;
  min-width: 10rem;
}
.crm-global-search form { width: 100%; }
.crm-global-search #globalSearchInput {
  width: min(22rem, 32vw) !important;
  padding-left: 2rem;
  background-color: var(--crm-canvas);
}
.crm-search-icon {
  position: absolute;
  z-index: 2;
  left: 0.55rem;
  width: 1rem;
  height: 1rem;
  color: var(--crm-text-muted);
  pointer-events: none;
}
.crm-global-search .dropdown-menu {
  width: 100%;
  max-height: min(28rem, calc(100vh - 4.5rem));
  overflow-y: auto;
}
.crm-user-toggle {
  max-width: 15rem;
  gap: 0.4rem;
  padding: 0.35rem 0.45rem;
  border-radius: calc(var(--crm-radius) - 0.125rem);
}
.crm-user-name {
  min-width: 0;
  overflow: hidden;
  color: var(--crm-text);
  font-size: 0.875rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.crm-user-menu .dropdown-menu { min-width: 18rem; }
.crm-menu-identity { padding: 0.5rem 1rem; }
.crm-menu-identity-name {
  font-weight: 650;
  line-height: 1.3;
  overflow-wrap: anywhere;
}
.crm-menu-identity-meta {
  color: var(--crm-text-muted);
  font-size: 0.8rem;
  line-height: 1.3;
  overflow-wrap: anywhere;
}
.crm-menu-form { padding: 0.5rem 1rem; }
.crm-menu-label {
  gap: 0.5rem;
  margin-bottom: 0.45rem;
  color: var(--crm-text-muted);
  font-size: 0.8rem;
}
.crm-menu-item {
  gap: 0.6rem;
  white-space: normal;
}

/* Every shell SVG is a local CSS mask so it follows currentColor. */
.crm-icon {
  display: inline-block;
  width: 1.25rem;
  height: 1.25rem;
  flex: 0 0 auto;
  background-color: currentColor;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  mask-size: contain;
}
.crm-icon-dashboard { -webkit-mask-image: url("../icons/dashboard.afac95b552f5.svg"); mask-image: url("../icons/dashboard.afac95b552f5.svg"); }
.crm-icon-companies { -webkit-mask-image: url("../icons/companies.3aed9c5cc8cc.svg"); mask-image: url("../icons/companies.3aed9c5cc8cc.svg"); }
.crm-icon-contacts { -webkit-mask-image: url("../icons/contacts.5e82beedebde.svg"); mask-image: url("../icons/contacts.5e82beedebde.svg"); }
.crm-icon-opportunities { -webkit-mask-image: url("../icons/opportunities.054cf969a165.svg"); mask-image: url("../icons/opportunities.054cf969a165.svg"); }
.crm-icon-contracts { -webkit-mask-image: url("../icons/contracts.1ec53549ec9a.svg"); mask-image: url("../icons/contracts.1ec53549ec9a.svg"); }
.crm-icon-marketing { -webkit-mask-image: url("../icons/marketing.cc288fd1d2dc.svg"); mask-image: url("../icons/marketing.cc288fd1d2dc.svg"); }
.crm-icon-reports { -webkit-mask-image: url("../icons/reports.fbc113a1014b.svg"); mask-image: url("../icons/reports.fbc113a1014b.svg"); }
.crm-icon-red-flags { -webkit-mask-image: url("../icons/red-flags.e892e2644ac6.svg"); mask-image: url("../icons/red-flags.e892e2644ac6.svg"); }
.crm-icon-changes { -webkit-mask-image: url("../icons/changes.81305a1a472e.svg"); mask-image: url("../icons/changes.81305a1a472e.svg"); }
.crm-icon-access-log { -webkit-mask-image: url("../icons/activity.9a602e410a48.svg"); mask-image: url("../icons/activity.9a602e410a48.svg"); }
.crm-icon-import-export { -webkit-mask-image: url("../icons/import-export.f6233ade6a97.svg"); mask-image: url("../icons/import-export.f6233ade6a97.svg"); }
.crm-icon-admin { -webkit-mask-image: url("../icons/admin.51bfeef26f5c.svg"); mask-image: url("../icons/admin.51bfeef26f5c.svg"); }
.crm-icon-menu { -webkit-mask-image: url("../icons/menu.6fe8728d16cb.svg"); mask-image: url("../icons/menu.6fe8728d16cb.svg"); }
.crm-icon-sidebar-collapse { -webkit-mask-image: url("../icons/sidebar-collapse.c0979e1a666e.svg"); mask-image: url("../icons/sidebar-collapse.c0979e1a666e.svg"); }
.crm-icon-search { -webkit-mask-image: url("../icons/search.193bc32fdc05.svg"); mask-image: url("../icons/search.193bc32fdc05.svg"); }
.crm-icon-theme-system { -webkit-mask-image: url("../icons/theme-system.3837594b67d3.svg"); mask-image: url("../icons/theme-system.3837594b67d3.svg"); }
.crm-icon-theme-light { -webkit-mask-image: url("../icons/theme-light.ec99e8f0da29.svg"); mask-image: url("../icons/theme-light.ec99e8f0da29.svg"); }
.crm-icon-theme-dark { -webkit-mask-image: url("../icons/theme-dark.c5f9c3dff993.svg"); mask-image: url("../icons/theme-dark.c5f9c3dff993.svg"); }
.crm-icon-user { -webkit-mask-image: url("../icons/user.6807b787f930.svg"); mask-image: url("../icons/user.6807b787f930.svg"); }
.crm-icon-language { -webkit-mask-image: url("../icons/language.721537a2f9ca.svg"); mask-image: url("../icons/language.721537a2f9ca.svg"); }
.crm-icon-feedback { -webkit-mask-image: url("../icons/feedback.07dba42fdf23.svg"); mask-image: url("../icons/feedback.07dba42fdf23.svg"); }
.crm-icon-logout { -webkit-mask-image: url("../icons/logout.aa2872f5c4c1.svg"); mask-image: url("../icons/logout.aa2872f5c4c1.svg"); }
.crm-icon-chevron-down { -webkit-mask-image: url("../icons/chevron-down.75ce04843616.svg"); mask-image: url("../icons/chevron-down.75ce04843616.svg"); }
.crm-icon-close { -webkit-mask-image: url("../icons/close.eef83c156d86.svg"); mask-image: url("../icons/close.eef83c156d86.svg"); }

@media (max-width: 991.98px) {
  .crm-shell-body,
  [data-crm-sidebar="collapsed"] .crm-shell-body { margin-left: 0; }
  .crm-sidebar,
  [data-crm-sidebar="collapsed"] .crm-sidebar {
    z-index: 1050;
    width: min(18rem, calc(100vw - 3rem));
    visibility: hidden;
  }
  [data-crm-sidebar-mobile="open"] .crm-sidebar { visibility: visible; }
  [data-crm-sidebar="collapsed"] .crm-sidebar .crm-nav-text { display: block; }
  [data-crm-sidebar="collapsed"] .crm-sidebar-link {
    justify-content: flex-start;
    padding-inline: 0.65rem;
  }
  [data-crm-sidebar="collapsed"] .crm-sidebar-link .crm-icon {
    width: 1.25rem;
    height: 1.25rem;
  }
  .crm-sidebar-close { display: inline-flex; }
  .crm-sidebar-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1045;
    display: none;
    width: 100%;
    height: 100%;
    padding: 0;
    border: 0;
    background: rgba(0, 0, 0, 0.55);
  }
  [data-crm-sidebar-mobile="open"] .crm-sidebar-backdrop { display: block; }
  [data-crm-sidebar-mobile="open"] body { overflow: hidden; }
  .crm-sidebar-toggle .crm-toggle-mobile { display: inline-block; }
  .crm-toggle-desktop { display: none; }
  .crm-topbar { padding-inline: 0.65rem; }
  .crm-topbar-brand { display: inline-block; }
  .crm-global-search #globalSearchInput { width: min(17rem, 35vw) !important; }
  .crm-user-name { max-width: 8rem; }
}

@media (max-width: 575.98px) {
  .crm-topbar { gap: 0.35rem; }
  .crm-topbar-tools { gap: 0.35rem; }
  .crm-topbar-brand { display: none; }
  .crm-topbar-brand-anonymous { display: inline-block; }

  /* Search collapses to a button and expands into its own full-width row beneath the
     bar. Inline it was ~9.5rem — about twelve characters — while competing with the
     three theme buttons for the same strip. The row is positioned rather than added
     to the flow so opening it never reflows the page behind it. */
  .crm-search-toggle { display: inline-flex; }
  .crm-global-search { display: none; }
  [data-crm-search="open"] .crm-global-search {
    position: absolute;
    display: block;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 1035;
    min-width: 0;
    padding: 0.4rem 0.65rem;
    background-color: var(--crm-surface);
    border-bottom: 1px solid var(--crm-border);
    box-shadow: var(--crm-shadow-sm);
  }
  [data-crm-search="open"] .crm-search-icon { left: 1.2rem; }
  [data-crm-search="open"] .crm-global-search #globalSearchInput {
    width: 100% !important;
  }
  .crm-theme-selector .crm-icon-button { width: 1.8rem; }
  .crm-user-toggle { width: 2.25rem; height: 2.25rem; justify-content: center; padding: 0; }
  .crm-user-name,
  .crm-user-toggle .crm-icon-chevron-down { display: none; }
  .crm-user-menu .dropdown-menu { min-width: min(18rem, calc(100vw - 1rem)); }
  .crm-shell-anonymous .crm-theme-selector .crm-icon-button { width: 2rem; }
}

/* Cards */
.card {
  border-color: var(--crm-border);
  border-radius: var(--crm-radius);
  box-shadow: var(--crm-shadow-sm);
}
.stat-card .card-body h6 {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.stat-card .display-6 {
  font-size: 1.75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* A row of .stat-card siblings is already equal-height (Bootstrap's .row is a flex
   container, and .card/.card-body stretch to fill it) — that part needs no help.
   What doesn't come for free is a card with no subtitle/delta line looking punched
   short next to one that has both: growing .card-body into a flex column lets the
   trailing content (a subtitle, an optional _norm_line.html/_month_delta.html) stack
   for real instead of needing an `invisible` placeholder to fake the line count. */
.stat-card .card-body {
  display: flex;
  flex-direction: column;
}

/* Dashboard funnel widget (marketing → sales).

   The widget renders 2 + N segments (New/Qualified leads, then one per open pipeline
   stage) with a conversion-rate column between each adjacent pair, so its natural
   width is data-driven — which is why it is sized against its own container rather
   than the viewport. It sits full-width on the dashboard today, but nothing
   guarantees it stays there and what matters is the space this card actually has.
   This is the only container query in the app; it is Baseline since early 2023.

   Base rules are the narrow layout: a vertical funnel, one stage per row with a
   proportional bar and the three conversion windows inline on a connector row
   between stages. From 48rem everything moves onto a single row that never wraps —
   segments are `flex: 1 1 0` with `min-width: 0` so labels truncate and columns
   shrink evenly instead of half-wrapping and stranding gap columns mid-funnel, which
   is what the old `flex-wrap` row did. From 72rem there is room for the full window
   labels (TTM / Prev mo / This mo) and roomier padding; below that the short forms
   (12M / PM / MTD) stand in.

   48rem is the knob: the narrowest width at which one row still reads. Raise it if a
   deployment configures unusually many open pipeline stages. */
.funnel-widget {
  container-type: inline-size;
  container-name: funnel;
}
.funnel-row {
  display: flex;
  flex-direction: column;
}
.funnel-segment {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.35rem 0.6rem;
  border-left: 3px solid transparent;
  border-radius: 0 var(--crm-radius) var(--crm-radius) 0;
  /* Proportional bar as a hard-stop gradient: no extra element, and the :hover
     background still shows through the untinted remainder. */
  background-image: linear-gradient(
    to right,
    var(--funnel-bar-tint) var(--funnel-bar, 0%),
    transparent 0
  );
}
.funnel-segment > .small {
  flex: 1 1 auto;
  min-width: 0; /* lets text-truncate engage instead of shoving the count aside */
}
.funnel-segment:hover {
  background-color: var(--crm-accent-subtle);
}
.funnel-segment-marketing {
  border-left-color: var(--crm-teal);
  --funnel-bar-tint: rgba(15, 155, 142, 0.16);
}
.funnel-segment-sales {
  border-left-color: var(--crm-accent);
  --funnel-bar-tint: rgba(42, 120, 214, 0.16);
}
/* Marketing → sales boundary. Vertically the accent colours already mark it, so
   extra whitespace is enough; horizontally it becomes a rule between the columns. */
.funnel-divider { margin-top: 0.75rem; }
.funnel-gap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 0.2rem;
  /* Continues the segments' 3px spine down the funnel, muted. */
  border-left: 3px solid var(--crm-border);
  padding: 0.2rem 0.6rem 0.2rem 0.9rem;
  font-size: 0.8rem;
}
.funnel-gap .small {
  font-size: 0.65rem;
  line-height: 1;
  margin-left: 0.7rem; /* groups each label with the value that follows it */
}
.funnel-gap .small:first-child { margin-left: 0; }
.funnel-win-long { display: none; }

@container funnel (min-width: 48rem) {
  .funnel-row {
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: stretch;
  }
  .funnel-segment {
    display: block;
    flex: 1 1 0;
    min-width: 0;
    padding: 0.5rem 0.4rem;
    border-left: 0;
    border-top: 3px solid transparent;
    border-radius: 0 0 var(--crm-radius) var(--crm-radius);
    background-image: none;
    /* A backstop, not the mechanism — flex: 1 1 0 with truncating labels is what
       keeps the row intact. If a config ever packs in more stages than 48rem can
       carry, a segment clips at its own edge instead of spilling over its neighbour. */
    overflow: hidden;
  }
  .funnel-segment-marketing { border-top-color: var(--crm-teal); }
  .funnel-segment-sales { border-top-color: var(--crm-accent); }
  .funnel-divider {
    margin-top: 0;
    margin-left: 0.5rem;
    padding-left: 0.9rem;
    border-left: 1px solid var(--crm-border);
  }
  .funnel-gap {
    flex: 0 0 auto;
    flex-direction: column;
    flex-wrap: nowrap;
    /* Base uses baseline alignment for the inline row; stacked, items must stretch
       so text-align actually centres them. */
    align-items: stretch;
    min-width: 2.4rem;
    text-align: center;
    border-left: 0;
    padding: 0.5rem 0.2rem 0;
    font-size: 0.75rem;
  }
  .funnel-gap .small { font-size: 0.6rem; margin-left: 0; }
}

@container funnel (min-width: 72rem) {
  .funnel-segment { padding: 0.5rem 0.75rem; }
  .funnel-divider { padding-left: 1.25rem; }
  /* Wider than the compact tier's columns: the full labels are what set this width,
     and translations run long ("Prev mo" is "Vorige mnd" in Dutch). */
  .funnel-gap { min-width: 4.5rem; padding-inline: 0.25rem; font-size: 0.8rem; }
  .funnel-gap .small { font-size: 0.65rem; }
  .funnel-win-long { display: inline; }
  .funnel-win-short { display: none; }
}

/* Tables */
.table > thead th {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--bs-secondary-color);
  font-weight: 600;
}
.table-hover > tbody > tr:hover > * {
  background-color: var(--crm-accent-subtle);
  --bs-table-bg-state: var(--crm-accent-subtle);
}
.tabular-nums { font-variant-numeric: tabular-nums; }

/* Inline stage control (opportunity list + detail). Capped so a stage name plus its
   probability doesn't stretch the column past the cells around it. */
.stage-quick-select,
.forecast-quick-select {
  max-width: 12rem;
  min-width: 8rem;
}

/* Sortable column headers: a muted ⇅ marks a header as clickable; it turns into a
   solid ↑/↓ (via .sort-indicator.active, set in the template) once that column is
   the active sort, and the whole header underlines on hover. */
.sortable-th:hover {
  text-decoration: underline;
}
.sort-indicator {
  opacity: 0.35;
  margin-left: 0.15rem;
}
.sort-indicator.active {
  opacity: 1;
  color: var(--crm-accent-text);
}

/* Reports home: tile grid */
.report-tile {
  transition: box-shadow 150ms ease-in, border-color 150ms ease-in;
}
.report-tile:hover {
  box-shadow: var(--crm-shadow);
  border-color: var(--crm-accent);
}

/* Reports home and switcher: the "New" badge on the commercial-director report pack.

   Accent-tinted, not one of the outcome or status colours: this is a wayfinding hint
   about when a report arrived, and teal/red/amber each already mean something specific
   about the data. It carries its own border and background rather than borrowing
   Bootstrap's `.border` utility, whose declarations are !important and would need a
   second !important here to tint. Removing `is_new` from the tiles (reports/views.py)
   retires the badge without touching this file. */
.crm-new-report-badge {
  border: 1px solid var(--crm-accent);
  background-color: transparent;
  color: var(--crm-accent-text);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  vertical-align: 0.12em;
}
/* On the switcher's active item the badge sits on the accent fill itself, where
   accent-on-accent would be unreadable. Inherit the item's own contrasting ink. */
.dropdown-item.active .crm-new-report-badge {
  border-color: currentColor;
  color: inherit;
}

/* Monthly Business Review — a real slide deck, not a scrolling page.

   Each slide is a fixed 16:9 canvas so what's on screen is exactly what the PNG
   export and the print/PDF pack contain: no "looks fine until you export it".
   Inside, the slide is a flex column — header, KPI strip, and commentary take their
   natural height and the chart area absorbs whatever is left, which is why the chart
   wrappers carry no pixel height (Chart.js runs with maintainAspectRatio:false and
   fills its box). Below the lg breakpoint the aspect ratio is dropped and everything
   stacks; a slide deck on a phone is a scrolling page and pretending otherwise just
   produces unreadable 90px charts.

   Chart colours live here as custom properties rather than hardcoded hex in seven
   templates, and every combination below was checked with a CVD validator:
   teal/red (won/lost, ΔE 11.3 deutan) replaces the red/green pair used elsewhere in
   the app, which is indistinguishable to a deuteranope, and the by-stage series uses
   a single-hue light→dark ramp because pipeline stages are ordered — a categorical
   rainbow implies they aren't. --mbr-teal/--mbr-red alias the app-wide --crm-teal/
   --crm-red tokens above rather than repeating the hex — the outcome badges use the
   same colours, and two names for one colour is how they drift apart. */
:root,
.mbr-document {
  --mbr-blue: #2a78d6;         /* gross / total */
  --mbr-teal: var(--crm-teal); /* weighted / won / increase */
  --mbr-red: var(--crm-red);   /* lost / decrease */
  --mbr-ink: #6c757d;          /* axis, legend and tick text */
  /* Precomputed sRGB token/white blends — literal hex because slide 4's PNG
     export runs the funnel bars through html2canvas 1.4.1, which cannot parse
     newer CSS color functions and otherwise silently breaks that slide's export
     (and, since it is last in the DOM, "Export all"). */
  --mbr-teal-tint-60: #6fc3bb; /* 60% --mbr-teal, 40% white */
  --mbr-blue-tint-55: #8ab5e8; /* 55% --mbr-blue, 45% white */
}

.mbr-document { color-scheme: light; }

.mbr-slide {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1.25rem 1.5rem;
  margin-bottom: 1.5rem;
  background-color: #fff;
  border: 1px solid var(--crm-border);
  border-radius: var(--crm-radius);
  box-shadow: var(--crm-shadow);
}
@media (min-width: 992px) {
  .mbr-slide {
    aspect-ratio: 16 / 9;
    min-height: 34rem;
    max-height: 54rem;
    /* A backstop, not the mechanism: the flex rules below are what keep content
       inside the frame. This guarantees that if any of it ever fails, a slide
       clips at its own edge instead of overlapping the next one. */
    overflow: hidden;
  }
}

.mbr-slide-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  border-bottom: 1px solid var(--crm-border);
  padding-bottom: 0.6rem;
}
.mbr-eyebrow {
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--crm-accent-text);
}
.mbr-question {
  font-size: 0.9rem;
  color: var(--bs-secondary-color);
}
.mbr-asof {
  font-size: 0.72rem;
  color: var(--bs-secondary-color);
  font-variant-numeric: tabular-nums;
}

/* The main region absorbs the slack left by the header, KPI strip and commentary.

   `flex: 1 1 0` — a zero basis, not `auto` — throughout, and it has to be zero: with
   `flex-basis: auto` an item's base size comes from its *content*, so a side panel
   listing 17 closing deals sizes its column to 17 rows, that stretches the row, the
   row stretches the chart column, and Chart.js (maintainAspectRatio:false) grows the
   canvas to match — which grows the content again. The slide runs off the bottom and
   over the next one. A zero basis makes these purely proportional divisions of the
   space the 16:9 frame already fixed, with no path from content back to height. */
.mbr-main {
  flex: 1 1 0;
  min-height: 0;
}
.mbr-main > [class*="col-"] {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.mbr-chart {
  position: relative;
  flex: 1 1 0;
  min-height: 8rem;
}
/* Taking the canvas out of flow is the belt to that braces: however Chart.js sizes
   it, an absolutely positioned canvas cannot feed its height back to the flex box. */
.mbr-chart > canvas {
  position: absolute;
  inset: 0;
}
/* Side panels hold lists and tables that can outgrow the canvas; they scroll inside
   the slide rather than stretching it, so the 16:9 frame always holds. */
.mbr-scroll {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
}
.mbr-panel-title {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--bs-secondary-color);
  margin-bottom: 0.4rem;
}

/* KPI strip: always a full row of four, so a slide never shows a half-empty grid
   because an optional norm happens not to be configured. */
.mbr-kpi {
  height: 100%;
  padding: 0.6rem 0.75rem;
  background-color: var(--crm-accent-subtle);
  border-radius: var(--crm-radius);
  border-left: 3px solid transparent;
}
.mbr-kpi-good { border-left-color: var(--mbr-teal); }
.mbr-kpi-warning { border-left-color: var(--bs-warning); }
.mbr-kpi-danger { border-left-color: var(--bs-danger); }
.mbr-kpi-label {
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--bs-secondary-color);
}
.mbr-kpi-value {
  font-size: 1.6rem;
  font-weight: 600;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.mbr-kpi-note {
  font-size: 0.75rem;
  color: var(--bs-secondary-color);
  font-variant-numeric: tabular-nums;
}

/* Slide 4's funnel report (templates/reports/_mbr_funnel.html) — a dense stage/gap
   list, not the dashboard's narrow funnel-widget card (that partial is shared with
   the dashboard and stays untouched). Column widths on the stage row and the gap
   row's leading spacers must match so the gap KPIs land centred under the bars. */
.mbr-funnel-stage, .mbr-funnel-gap {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.mbr-funnel-stage { padding: 0.2rem 0; }
.mbr-funnel-stage.mbr-funnel-divider {
  margin-top: 0.4rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--crm-border);
}
.mbr-funnel-idx {
  width: 1.3rem;
  flex-shrink: 0;
  font-size: 0.62rem;
  color: var(--mbr-ink);
  font-variant-numeric: tabular-nums;
}
.mbr-funnel-label {
  width: 8rem;
  flex-shrink: 0;
  font-size: 0.76rem;
  font-weight: 500;
  color: inherit;
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mbr-funnel-label:hover { text-decoration: underline; }
.mbr-funnel-bar-wrap {
  flex: 1 1 0;
  height: 1.7rem;
  background: var(--crm-accent-subtle);
  border-radius: 3px;
}
/* No overflow:hidden here — a segment near the bottom of the funnel can be a few
   percent of the peak, too narrow to hold its own value; centering still holds the
   number on the bar's midpoint and just lets it spill past the fill's edges rather
   than getting clipped or shoved outside the bar entirely. */
.mbr-funnel-bar {
  height: 100%;
  border-radius: 3px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.mbr-funnel-bar-marketing { background: var(--mbr-teal-tint-60); }
.mbr-funnel-bar-sales { background: var(--mbr-blue-tint-55); }
.mbr-funnel-value {
  padding: 0 0.35rem;
  font-size: 0.82rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.mbr-funnel-gap { padding: 0.15rem 0; }
.mbr-funnel-gap-kpis {
  flex: 1 1 0;
  display: flex;
  justify-content: center;
  gap: 1.1rem;
}
.mbr-funnel-gap-kpi {
  display: flex;
  align-items: baseline;
  gap: 0.3rem;
  cursor: default;
}
.mbr-funnel-gap-kpi span {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--mbr-blue);
  font-variant-numeric: tabular-nums;
}
.mbr-funnel-gap-kpi label {
  font-size: 0.58rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--mbr-ink);
}
.mbr-funnel-foot {
  margin-top: 0.5rem;
  padding-top: 0.4rem;
  border-top: 1px solid var(--crm-border);
  font-size: 0.62rem;
  color: var(--mbr-ink);
}

/* Generated commentary (reports/mbr_commentary.py). Two columns so four or five
   sentences stay one or two lines tall instead of eating the chart's height. */
.mbr-commentary {
  border-top: 1px solid var(--crm-border);
  padding-top: 0.6rem;
  margin: 0;
  font-size: 0.85rem;
  columns: 2;
  column-gap: 2rem;
}
.mbr-commentary li {
  break-inside: avoid;
  margin-bottom: 0.2rem;
}
.mbr-commentary li::marker { color: currentColor; }

/* Print: the whole four-slide pack in one Ctrl-P, one landscape page per slide.
   The aspect ratio is dropped here on purpose — the page box already fixes the
   proportions, and keeping 16/9 as well overflows onto a blank second sheet. */
@media print {
  @page { size: A4 landscape; margin: 8mm; }
  body { background: #fff; }
  .crm-sidebar,
  .crm-sidebar-backdrop,
  .crm-topbar,
  .mbr-screen-only,
  .mbr-export-btn,
  #messages,
  .alert-dismissible .btn-close {
    display: none !important;
  }
  .crm-shell-body,
  [data-crm-sidebar="collapsed"] .crm-shell-body { margin-left: 0; }
  main.container-fluid { padding: 0 !important; }
  .mbr-slide {
    aspect-ratio: auto;
    min-height: 0;
    max-height: none;
    height: 17.5cm;
    margin: 0;
    padding: 0.5cm;
    border: 0;
    box-shadow: none;
    break-after: page;
    page-break-after: always;
  }
  .mbr-slide:last-of-type {
    break-after: auto;
    page-break-after: auto;
  }
  .mbr-scroll { overflow: hidden; }
}

/* Alerts: the compact `py-2` alerts used throughout the app shrink the bar height, but
   Bootstrap's default .btn-close positioning (top:0 + large fixed padding) assumes the
   taller default alert height, so the X ends up hanging below center — recenter it. */
.alert-dismissible .btn-close {
  top: 50%;
  transform: translateY(-50%);
  padding: 0.5rem 1rem;
}

/* Explicit component states keep Bootstrap controls legible against the dark
   surfaces instead of relying on its generic color-mode fallbacks. */
[data-bs-theme="dark"] .alert-info {
  --bs-alert-color: #b6d4fe;
  --bs-alert-bg: #162b43;
  --bs-alert-border-color: #315a83;
  --bs-alert-link-color: #d7e8fd;
}
[data-bs-theme="dark"] .alert-success {
  --bs-alert-color: #a3cfbb;
  --bs-alert-bg: #19362a;
  --bs-alert-border-color: #3d765c;
  --bs-alert-link-color: #d1e7dd;
}
[data-bs-theme="dark"] .alert-warning {
  --bs-alert-color: #ffe69c;
  --bs-alert-bg: #3d3418;
  --bs-alert-border-color: #806c27;
  --bs-alert-link-color: #fff3cd;
}
[data-bs-theme="dark"] .alert-danger {
  --bs-alert-color: #f1aeb5;
  --bs-alert-bg: #421f23;
  --bs-alert-border-color: #842f38;
  --bs-alert-link-color: #f8d7da;
}
[data-bs-theme="dark"] .alert-secondary {
  --bs-alert-color: #ced4da;
  --bs-alert-bg: #2b3035;
  --bs-alert-border-color: #565e64;
  --bs-alert-link-color: #e9ecef;
}
[data-bs-theme="dark"] .is-invalid,
[data-bs-theme="dark"] .form-control.is-invalid,
[data-bs-theme="dark"] .form-select.is-invalid,
[data-bs-theme="dark"] .form-check-input.is-invalid {
  border-color: #ea868f;
}
[data-bs-theme="dark"] .invalid-feedback,
[data-bs-theme="dark"] .invalid-tooltip,
[data-bs-theme="dark"] .form-check-input.is-invalid ~ .form-check-label {
  color: #ff9ca5;
}
[data-bs-theme="dark"] .dropdown-divider { border-color: var(--crm-border); }
[data-bs-theme="dark"] .btn-close { filter: invert(1) grayscale(100%) brightness(180%); }

:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 3px solid var(--crm-focus);
  outline-offset: 2px;
}

/* Skip link: off-screen until focused, then pinned over the top bar. Moved rather
   than display:none'd, because a hidden element cannot receive focus at all. <main>
   is the only skip target, and #main-content is never a real scroll anchor, so this
   does not interact with the sticky topbar's own offset. */
.crm-skip-link {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1100;
  transform: translateY(-120%);
  padding: 0.6rem 1rem;
  margin: 0.35rem;
  border-radius: calc(var(--crm-radius) - 0.125rem);
  background-color: var(--crm-surface-elevated);
  color: var(--crm-accent-text);
  font-weight: 650;
  text-decoration: none;
  box-shadow: var(--crm-shadow);
}
.crm-skip-link:focus {
  transform: translateY(0);
}
/* The skip target itself must not draw a focus ring — it is a scroll/focus landing
   point, not a control the user activated. */
main:focus { outline: none; }

/* Shared Phase 3 component composition.

   These deliberately supplement Bootstrap instead of replacing it. Phase 3b page
   templates can compose `.crm-form-field`/`.crm-form-label`/`.crm-form-help`/
   `.crm-field-error` around their hand-rendered controls, and use
   `.crm-inline-actions` for action rows. `.crm-form-summary`, `.crm-message`,
   `.crm-status-badge`, and `.crm-owner-scope-note` are the matching compact shared
   partial treatments. For a company picker, wrap the search include and its real
   select in `.crm-company-picker`, and add `.crm-company-picker-select` to that
   select. Do not replace the select, alter its id/name, or move its caller-owned
   htmx attributes: the component is only a display refinement. The note-card
   family (`.crm-notes-card`, `.crm-note-entry`, `.crm-notes-table`,
   `.crm-note-actions`) keeps a dense, wrapping activity layout. These classes use
   the Phase 1 semantic tokens and allow labels, messages, badges, and actions to
   wrap for Dutch. */
.crm-form-summary {
  margin-bottom: 1rem;
  border-left: 0.25rem solid var(--crm-negative);
  box-shadow: var(--crm-shadow-sm);
}
.crm-form-summary-title {
  margin: 0 0 0.35rem;
  font-weight: 650;
  line-height: 1.3;
}
.crm-form-summary-list {
  margin: 0;
  padding-left: 1.2rem;
}
.crm-form-summary-list li + li { margin-top: 0.2rem; }
.crm-form-field { min-width: 0; }
.crm-form-label {
  margin-bottom: 0.3rem;
  font-weight: 600;
  line-height: 1.3;
}
.crm-required-indicator {
  color: var(--crm-negative);
  font-weight: 700;
}
.crm-form-help,
.crm-field-error {
  margin-top: 0.3rem;
  line-height: 1.35;
}
.crm-form-check { min-height: 1.35rem; }
.crm-inline-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}
.crm-status-badge {
  max-width: 100%;
  padding: 0.32em 0.55em;
  line-height: 1.25;
  text-align: start;
  white-space: normal;
}
.crm-owner-scope-note {
  color: var(--crm-text-muted);
  white-space: normal;
}
.crm-message-stack {
  display: grid;
  gap: 0.5rem;
  margin-bottom: 1rem;
}
.crm-message {
  min-height: 2.5rem;
  margin: 0;
  padding: 0.5rem 2.75rem 0.5rem 0.75rem;
  box-shadow: var(--crm-shadow-sm);
  line-height: 1.35;
}
/* Positioning context for the suggestion list, which is absolutely positioned and
   must overlay the select rather than push it down the form. */
.crm-company-picker { min-width: 0; position: relative; }
.crm-company-search {
  min-width: 0;
  margin-bottom: 0.35rem;
}
/* Bootstrap's .dropdown-menu is already position:absolute/display:none; left/right pin
   it to the picker's width instead of its own min-width, and top stays auto so it
   lands at its static position — directly under the search input. */
.crm-company-suggestions {
  left: 0;
  right: 0;
  max-height: 16rem;
  overflow-y: auto;
  /* Above the sticky topbar (1030) so a picker near the top of the form is not
     clipped by it. */
  z-index: 1050;
}
.crm-company-suggestions .dropdown-item.active {
  background-color: var(--crm-accent-subtle);
  color: var(--crm-accent-text);
}
.crm-company-picker-select { min-width: 0; }
.crm-notes-card { min-width: 0; }
.crm-note-entry .card-body { padding: 0.75rem; }
.crm-note-control { min-width: 0; }
.crm-note-errors { line-height: 1.35; }
.crm-notes-table {
  --bs-table-bg: transparent;
  --bs-table-color: var(--crm-text);
  --bs-table-border-color: var(--crm-border);
}
.crm-notes-table .table-light {
  --bs-table-bg: var(--crm-surface-elevated);
  --bs-table-color: var(--crm-text-muted);
  --bs-table-border-color: var(--crm-border);
}
.crm-note-meta { font-variant-numeric: tabular-nums; }
.crm-note-content { white-space: pre-line; }
.crm-note-actions {
  min-width: 8rem;
  white-space: normal !important;
}
.crm-note-actions > .btn { margin: 0.125rem 0 0.125rem 0.25rem; }
.crm-note-action { display: grid; align-content: start; }

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* Empty states */
.empty-state {
  padding: 3rem 1rem;
  text-align: center;
  color: var(--bs-secondary-color);
}
.empty-state .empty-state-title {
  font-weight: 600;
  color: var(--bs-body-color);
  margin-bottom: 0.25rem;
}

/* htmx loading indicator: hidden until htmx flags the request in flight */
.htmx-indicator { opacity: 0; transition: opacity 150ms ease-in; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; }

/* Activity page: per-user summary rows expand to a sibling <tr> showing
   clickpath detail. Bootstrap's .collapse.show sets display:block, which
   breaks a <tr>'s table layout — force table-row display back. The caret
   rotates off aria-expanded, which Bootstrap's collapse plugin already sets
   on the toggle element — no extra JS needed. */
.cursor-pointer { cursor: pointer; }
tr.collapse.show { display: table-row; }
.clickpath-caret {
  display: inline-block;
  transition: transform 150ms ease-in;
}
[aria-expanded="true"] .clickpath-caret {
  transform: rotate(90deg);
}

/* Login page */
.login-wrap {
  min-height: calc(100vh - 12rem);
  display: flex;
  align-items: center;
}

/* Proportional composition bar — a part-to-whole strip for an ordered dimension
   (pipeline stage), used where a number alone hides that two equal ratios are not
   equally safe. Segments carry the sequential ramp in stage order and are separated
   by a 2px surface gap so adjacent steps stay separable. The bar is aria-labelled by
   its container and every segment is also listed as text beneath it, so identity is
   never colour-alone. */
.crm-composition-bar {
  display: flex;
  width: 100%;
  height: 0.75rem;
  border-radius: 0.25rem;
  overflow: hidden;
  background-color: var(--crm-chart-grid);
}
.crm-composition-segment {
  display: block;
  height: 100%;
  box-shadow: inset -2px 0 0 0 var(--crm-surface);
}
.crm-composition-segment:last-child { box-shadow: none; }
.crm-legend-swatch {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 0.15rem;
  margin-right: 0.3rem;
  vertical-align: baseline;
}
.crm-seq-1 { background-color: var(--crm-seq-1); }
.crm-seq-2 { background-color: var(--crm-seq-2); }
.crm-seq-3 { background-color: var(--crm-seq-3); }
.crm-seq-4 { background-color: var(--crm-seq-4); }
.crm-seq-5 { background-color: var(--crm-seq-5); }
/* A sixth or later ordered step: muted ink rather than cycling back to step 1, which
   would read as two different stages being the same thing. */
.crm-seq-6, .crm-seq-7, .crm-seq-8, .crm-seq-9, .crm-seq-10 {
  background-color: var(--crm-text-muted);
}

/* Hygiene scorecard cells. Traffic-light shading on a *tint*, not on the text, so the
   number stays at full ink contrast — a pale figure on a coloured ground is the usual
   way these tables become unreadable. Severity is also carried by the number and its
   share, so the colour is never the only signal.

   Precomputed tints rather than color-mix(): html2canvas 1.4.1 cannot parse color-mix
   and silently breaks the MBR PNG export (see the note at the top of this file).
   Sources: --crm-warning #eda100 and --crm-negative #d03b3b at ~12% on white. */
.crm-hygiene-cell { vertical-align: middle; }
.crm-hygiene-warn { background-color: #fdf4e0; }
.crm-hygiene-bad { background-color: #fbeaea; }
.crm-hygiene-link { font-weight: 600; text-decoration: none; }
.crm-hygiene-link:hover { text-decoration: underline; }

[data-bs-theme="dark"] .crm-hygiene-warn { background-color: #3a3021; }
[data-bs-theme="dark"] .crm-hygiene-bad { background-color: #3d2626; }

/* Cohort-grid cells. The shade is a proportion of the cohort's own top of funnel, so
   a thin month and a fat one are comparable across rows and a weak cohort reads as a
   horizontal band. Carried as an alpha on one hue rather than a ramp of discrete
   steps: the value is continuous, and banding it would invent thresholds.

   The number itself keeps text-token ink at full contrast — shading the text is how
   these grids become unreadable — and every cell states its count, so the colour is
   never the only signal. rgb() with a var alpha, not color-mix(), for the html2canvas
   reason at the top of this file. Source hue: --crm-accent #2a78d6. */
.crm-cohort-cell {
  position: relative;
  --crm-cohort-share: 0%;
  background-image: linear-gradient(
    rgba(42, 120, 214, calc(var(--crm-cohort-share) / 100 * 0.55)),
    rgba(42, 120, 214, calc(var(--crm-cohort-share) / 100 * 0.55))
  );
}
[data-bs-theme="dark"] .crm-cohort-cell {
  background-image: linear-gradient(
    rgba(110, 168, 254, calc(var(--crm-cohort-share) / 100 * 0.45)),
    rgba(110, 168, 254, calc(var(--crm-cohort-share) / 100 * 0.45))
  );
}

/* Win-rate heatmap cells. A rate is a magnitude, so it takes the sequential ramp's
   hue at an alpha proportional to the rate — continuous, like the value. Only cells
   with a reportable rate get the class at all, so a thin cell is visibly unshaded
   rather than looking like a cold one: an n=1 cell must never read as a niche.

   The number keeps full ink contrast and every cell states its denominator, so the
   colour is never the only signal. rgb() with a var alpha rather than color-mix(),
   for the html2canvas reason at the top of this file. */
.crm-rate-cell {
  --crm-rate: 0%;
  background-image: linear-gradient(
    rgba(42, 120, 214, calc(var(--crm-rate) / 100 * 0.5)),
    rgba(42, 120, 214, calc(var(--crm-rate) / 100 * 0.5))
  );
}
[data-bs-theme="dark"] .crm-rate-cell {
  background-image: linear-gradient(
    rgba(110, 168, 254, calc(var(--crm-rate) / 100 * 0.4)),
    rgba(110, 168, 254, calc(var(--crm-rate) / 100 * 0.4))
  );
}
