/* ============================================================
   Helm Design System: Enterprise Infrastructure Intelligence
   Sits on top of Bootstrap 5. Do NOT remove Bootstrap.
   ============================================================ */

/* Typography */
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');

:root {
  /* Brand colors */
  --helm-primary: #1B3A6B;
  --helm-primary-light: #2A4F8F;
  --helm-primary-dark: #0F2544;
  /* NOTE: --helm-accent is re-declared further down as the accessible
     ink (#00757F). The brand cyan lives on as --helm-accent-fill. */
  --helm-accent-warm: #FF6B35;
  --helm-warning: #fdae61;
  /* --helm-warning as TEXT on a light surface measures 1.84:1, which is the
     worst ratio in the system. The dark-mode audit on 2026-07-31 checked
     #fdae61 against the three DARK surfaces (6.25:1 to 8.38:1, compliant)
     and did not check it as text on white, so the gap survived: fifteen
     places across the app paint `color: var(--helm-warning-ink)` and every one
     of them fails AA in light mode. Found with axe on the proposal template
     picker, 2026-08-03.
       --helm-warning stays exactly as it is, because it is correct as a
     border, a background tint and an icon fill. This is the INK companion,
     following the same split already used for danger, success, primary and
     accent. #96560a measures 5.78:1 on white and 5.35:1 on --helm-bg, and it
     is still unmistakably amber rather than brown-grey. */
  --helm-warning-ink: #96560a;
  --helm-danger: #d73027;
  /* Was #1a9641. That green measured only 3.83:1 as TEXT on white and
     3.54:1 on --helm-bg (#F5F6FA) -- confirmed live with axe on
     /invitations (status label) and /admin/permissions/drift (kpi value),
     both in LIGHT mode, both under the 4.5:1 AA floor, both pre-existing
     and unrelated to dark mode. #167536 clears 4.5:1 on every light
     surface used in this app (5.78:1 on white, 5.35:1 on --helm-bg,
     5.17:1 on --helm-surface-subtle) with real margin, not a hairline
     pass. Found and fixed during the --helm-danger dark-theme audit
     because the required verification sweep covers both themes and this
     surfaced as a real, currently-shipping violation, not a hypothetical
     one -- leaving it while calling the token "fixed" would be dishonest. */
  --helm-success: #167536;

  /* Fill-safe variants: a handful of components paint --helm-danger /
     --helm-success as a SOLID BACKGROUND under white text (buttons, toasts:
     .tgt-btn-danger, .fitp-toast.is-error, .sp-toast.is-success, etc.). That
     case needs a color dark/saturated enough for white text at 4.5:1
     regardless of theme, which is the opposite requirement from the ink use
     (small text/icons ON a card, which needs to get BRIGHTER in dark mode,
     see the dark remap below). Mirrors the existing --helm-primary /
     --helm-ink-brand split (line ~39): one token for fills, one for ink.
     Not remapped for dark on purpose, same as --helm-primary. Both measure
     comfortably over 4.5:1 with white text: --helm-danger-fill at 4.84:1
     (unchanged from the original brand red, it was already fine),
     --helm-success-fill at 5.78:1 (same value as the new --helm-success
     ink above; they coincide in light mode and only diverge in dark,
     where the ink gets brighter but the fill must not). */
  --helm-danger-fill: #d73027;
  --helm-success-fill: #167536;

  /* --helm-primary / --helm-accent get the SAME fill-vs-ink split, for the
     same reason and after the same measurement (contrast audit, 2026-07-31).
     Two live failures forced it:
       .mf-row-add on /my-firm/ painted --helm-primary (#1B3A6B) as TEXT on a
       dark card: 1.02:1, effectively invisible.
       .mf-eyebrow / .mf-group-title painted --helm-accent (#00BCD4) as TEXT
       on LIGHT: 2.13:1 on the page background, 2.30:1 on white.
     So --helm-accent was failing in light mode and --helm-primary in dark.
     The dark values were already written but scoped to .helm-stat-icon /
     .helm-action-icon only, which is exactly the narrow patch that let
     --helm-danger drift unfixed everywhere else. Measured: the old
     icon-scoped #7da2d9 is itself only 4.41:1 on --helm-surface, so it
     would have shipped a failure; #8fb4e8 measures 5.40:1 there.
     Polarity is deliberate: the BASE token is the accessible ink, so text
     is safe by default and a component must opt IN to the brand fill. 68
     background uses of --helm-primary and 14 of --helm-accent are pointed
     at the -fill tokens; white text on those is unchanged (11.27:1 on
     primary-fill). */
  --helm-primary-fill: #1B3A6B;
  --helm-accent-fill: #00BCD4;
  /* Light-mode ink for the accent. The brand cyan is unreadable as small
     text on any light surface; this keeps the hue and darkens it to
     4.78:1 on the worst light surface (--helm-surface-subtle). The brand
     cyan itself survives untouched as --helm-accent-fill. */
  --helm-accent: #00757F;

  /* Surface colors */
  --helm-bg: #F5F6FA;
  --helm-surface: #FFFFFF;
  --helm-surface-elevated: #FFFFFF;
  --helm-surface-subtle: #F0F2F8;
  --helm-border: #E2E6EF;

  /* Text: high contrast, no gray */
  --helm-text: #1A1D26;
  --helm-text-secondary: #374151;
  --helm-text-muted: #4B5563;

  /* Brand colour as INK. --helm-primary is deliberately NOT remapped for dark
     (it backs filled buttons that keep white text in both themes), so anything
     that painted TEXT with it went unreadable in dark: a navy $-total on the
     dark card measured 1.36:1. This token is the same navy in light (11.27:1 on
     white) and the light-blue accent in dark (4.90:1 on the lightest dark card,
     6.5:1 on the darkest), so brand-coloured text has one home. Use it for
     text; keep --helm-primary for fills. */
  --helm-ink-brand: #1B3A6B;

  /* Shadows */
  --helm-shadow-sm: 0 1px 2px rgba(27, 58, 107, 0.06);
  --helm-shadow: 0 2px 8px rgba(27, 58, 107, 0.08);
  --helm-shadow-lg: 0 8px 24px rgba(27, 58, 107, 0.12);

  /* Typography */
  --helm-font: 'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif;
  --helm-font-mono: 'JetBrains Mono', monospace;

  /* Spacing */
  --helm-page-padding: 1.5rem;

  /* Transitions */
  --helm-transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

/* ============================================================
   Dark mode (global). Bootstrap 5.3 flips its own components via
   html[data-bs-theme="dark"], but the --helm-* surface and text
   tokens above were light-only, so pages kept a light canvas and
   near-black inherited ink behind dark Bootstrap cards (half-dark
   pages, bug 1186). Re-point the tokens at the Bootstrap dark
   tokens once, body-scoped. This mirrors, and now backstops, the
   page-scoped remaps on dashboard/index.html and statistics.html
   (bug 1183). Brand tokens (--helm-primary*, accents) are NOT
   remapped: they back filled buttons and pills that keep white
   ink in both themes. Light mode is untouched: every rule here is
   scoped to [data-bs-theme="dark"]. */
[data-bs-theme="dark"] body {
  --helm-bg: var(--bs-body-bg);
  --helm-surface: var(--bs-secondary-bg);
  --helm-surface-elevated: var(--bs-secondary-bg);
  --helm-surface-subtle: var(--bs-tertiary-bg);
  --helm-border: var(--bs-border-color);
  --helm-text: var(--bs-body-color);
  --helm-text-secondary: #ced4da;
  --helm-text-muted: #adb5bd;
  --helm-ink-brand: #7aa9ff;
  --helm-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
  --helm-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
  --helm-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.55);

  /* --helm-danger / --helm-success re-point (contrast audit, 2026-07-31).
     Previously ONLY .helm-stat-icon/.helm-action-icon repointed these two
     tokens (below); everywhere else --helm-danger stayed the light-mode
     #d73027, which measures 3.19:1 on the dark page body and as low as
     2.38:1 on a dark card (var(--helm-surface) = #343a40) -- both well
     under the 4.5:1 AA floor for text. --helm-success had the identical
     defect: unfixed #1a9641 measures 3.00:1 on a dark card. Sixty-plus
     templates paint small text/badges/status-dots with these two tokens
     directly (var(--helm-danger) / var(--helm-success)), so the component-
     level patch never reached them. Fixing it here, once, at the token
     source, is what reaches all of them without touching each file.
       #ff8080 measures 4.74:1 on --helm-surface (#343a40, the hardest of
     the three dark surfaces since it's the lightest), 6.36:1 on --helm-bg,
     5.49:1 on --helm-surface-subtle, and 4.55:1 composited under the 10%-
     alpha danger tint pills (.helm-stat-trend.down, .helm-role-badge) --
     all comfortably clear 4.5:1, unlike the old fix's #f1808a, which
     measured 4.4897:1 on --helm-surface: technically still a fail, just a
     narrow one.
       #4ecb71 (unchanged from the old icon-only fix) already cleared AA
     with margin -- 5.54:1 on --helm-surface, 5.03:1 under its tint pill --
     so it is simply promoted from component-scoped to global here.
       --helm-warning is NOT re-pointed: it was never touched by dark mode
     at all, and #fdae61 already measures 6.25:1-8.38:1 across all three
     dark surfaces. Checked as part of this audit and found compliant. */
  --helm-danger: #ff8080;
  --helm-success: #4ecb71;
  /* In dark mode the ink and the fill coincide: #fdae61 was measured at
     6.25:1 to 8.38:1 across all three dark surfaces in the 2026-07-31 audit,
     so the light-mode ink (#96560a, which is only 1.99:1 on --helm-surface)
     must NOT carry over. Same pattern as --helm-danger and --helm-success. */
  --helm-warning-ink: #fdae61;
  /* Same audit: --helm-primary as TEXT measured 1.02:1 on a dark card and
     --helm-accent needs to go back UP in dark (the light-mode ink is too
     dark against a dark surface). Verified worst-surface ratios:
     #8fb4e8 = 5.40:1, #3dd5f3 = 6.58:1, both on --helm-surface (#343a40),
     the lightest and therefore hardest of the three dark surfaces.
     The -fill tokens are deliberately NOT remapped: a solid brand fill
     under white text must stay dark in both themes. */
  --helm-primary: #8fb4e8;
  --helm-primary-light: #8fb4e8;
  --helm-accent: #3dd5f3;
}

/* Score-band ink remap (bug 1186, recolored bug 1699). Several templates
   hardcode the score-band TEXT colors inline (statistics.html stat tiles,
   the _project_sidebar mini PCI tile). The "Good" ink #15803d (5.0:1 on
   white, chosen in app/scoring.py) still reads too dark against a near-
   black dark-theme card; the deep band hues fare little better. Re-point
   just those inline inks in dark via attribute selectors (inline styles
   need !important). The selectors are class-scoped so background:#15803d
   fill. */
[data-bs-theme="dark"] .stat-big-number[style*="#15803d"],
[data-bs-theme="dark"] .fw-bold[style*="#15803d"] { color: #4ecb71 !important; }
[data-bs-theme="dark"] .stat-big-number[style*="#b8a600"] { color: #e6d54a !important; }
[data-bs-theme="dark"] .stat-big-number[style*="#e07000"] { color: #ffa14f !important; }
[data-bs-theme="dark"] .stat-big-number[style*="#d73027"],
[data-bs-theme="dark"] .fw-bold[style*="#d73027"] { color: #ff8080 !important; }
[data-bs-theme="dark"] .stat-big-number[style*="#6610f2"] { color: #a97ff7 !important; }
[data-bs-theme="dark"] .stat-big-number[style*="#e91e63"] { color: #f06292 !important; }

/* #B45309 is a hardcoded warning-semantic amber used as TEXT ink at 20+
   call sites across the app (invitations, CRM, product_admin, projects
   export, sidepanel...), NOT the --helm-warning token -- it exists because
   --helm-warning's own value (#fdae61) measures only 1.84:1 on white and
   fails AA as text, so it was hand-picked for light mode (verified 5.02:1
   there per the comments in crm/dashboard.html and dashboard/platform.html)
   but never given a dark-mode counterpart. Found here via a live axe hit on
   /invitations (3.07:1 on a dark card). Fixing every one of those 20+ call
   sites is a larger, separate cleanup; this narrowly covers the one proven
   failure in scope for this pass (invitations/index.html's "Pending"
   status label) using the same #fdae61 already verified compliant in dark
   (6.25-8.38:1 across all three dark surfaces) for --helm-warning itself. */
[data-bs-theme="dark"] .fw-semibold[style*="#B45309"] { color: #fdae61 !important; }

/* Icon tiles carry their tint and ink inline as var(--helm-*): navy
   ink at 1.6:1 on a dark card. Re-point the brand tokens on the tiles
   only, exactly as the home-dashboard fix (bug 1183) did page-scoped.
   --helm-success / --helm-danger used to be repointed here too (the
   original bug-1949-adjacent fix); removed because the [data-bs-theme=
   "dark"] body rule above now repoints both globally to the same values,
   so restating them per-component here was dead weight and, worse, the
   exact kind of per-component patch that let the global token drift
   unfixed everywhere else. */
/* Bootstrap's .bg-white is a LITERAL white, not a theme token, so in dark
   mode it keeps a white panel while the text inside inherits the dark
   theme's light ink. Measured on /response/: a .card-header.bg-white put
   #dee2e6 text on #ffffff at 1.30:1, which is invisible. 22 templates use
   it and none of them meant "white even in dark mode"; they meant "a plain
   surface". Map it to the theme surface here rather than editing 22 files,
   which also stops the next one from reintroducing the bug. A place that
   genuinely needs literal white in both themes should say so with an
   inline style. */
/* Bootstrap ships <code> as #d63384, a pink tuned for a white page. On any
   tinted light surface it drops under AA (measured 3.88:1 on #e1f1f7 at
   /admin/products), and on a dark surface it is worse. Give it a token in
   both themes rather than leaving it to Bootstrap's default. */
code:not([class]) { color: #a02368; }
[data-bs-theme="dark"] code:not([class]) { color: #f2a4c8; }

[data-bs-theme="dark"] .bg-white {
  background-color: var(--helm-surface) !important;
  color: var(--helm-text);
}

[data-bs-theme="dark"] .helm-stat-icon,
[data-bs-theme="dark"] .helm-action-icon {
  --helm-primary: #7da2d9;
  --helm-primary-light: #7da2d9;
  --helm-primary-dark: #7da2d9;
  --helm-accent: #3dd5f3;
  --helm-accent-warm: #ffa47e;
  background: rgba(255, 255, 255, 0.08) !important;
}

/* Generic dark-mode outline-button contrast fix (bug audit D2). Plain
   .btn-outline-primary / .btn-outline-secondary read their ink from
   Bootstrap's --bs-primary (#0d6efd) / --bs-secondary (#6c757d) button
   variables, both light-mode hues that were never remapped for dark: on a
   dark card (~#212529-#393f45) they land between 1.2:1 and 3.3:1, well
   under WCAG AA. The .universal-lsb override two rules below only ever
   covered the left-sidebar's outline-secondary buttons; every other
   outline button sitewide (Sequences builder/groups/enrollments/
   identities/setup, Compliance retention, and beyond) inherited the
   unfixed default. Uses Bootstrap 5.3's button CSS-variable API so this
   composes with Bootstrap's own component instead of fighting its
   specificity, and is scoped to [data-bs-theme="dark"] only -- light mode
   (including any future per-firm --bs-primary branding override) is
   untouched. The .universal-lsb rule keeps winning inside the sidebar on
   specificity (three classes vs. one) and resolves to the same colors
   anyway, so this does not change the sidebar's already-fixed look. */
/* Ink raised from #93b4e0 to #9ec5fe (bug 1798). #93b4e0 measures 5.39:1 on the
   dark card in isolation, but an outline button sitting on a translucent surface
   composites down: axe measured 4.43:1 for the "Open Firm Profile" button inside
   the .alert-light on /admin/my-org. #9ec5fe is 6.50:1 flat and 5.48:1 through
   the same composite, so the headroom survives the blend. It is also the blue
   already used for data-view links in dark, so the two agree. */
[data-bs-theme="dark"] .btn-outline-primary {
  --bs-btn-color: #9ec5fe;
  --bs-btn-border-color: #9ec5fe;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--helm-primary, #1B3A6B);
  --bs-btn-hover-border-color: #9ec5fe;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: var(--helm-primary, #1B3A6B);
  --bs-btn-active-border-color: #9ec5fe;
  --bs-btn-disabled-color: #9ec5fe;
  --bs-btn-disabled-border-color: #9ec5fe;
}
[data-bs-theme="dark"] .btn-outline-secondary {
  --bs-btn-color: var(--bs-body-color);
  --bs-btn-border-color: var(--bs-border-color);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #495057;
  --bs-btn-hover-border-color: #495057;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #495057;
  --bs-btn-active-border-color: #495057;
  --bs-btn-disabled-color: var(--bs-body-color);
  --bs-btn-disabled-border-color: var(--bs-border-color);
}

/* Light-mode outline-button contrast fix (SEQ-P2-01). Axe color-contrast
   fired on .btn-outline-primary/-secondary/-info across the whole
   Sequencer journey in LIGHT mode too, not just dark: Bootstrap's default
   --bs-primary ink (#0d6efd) on white measures ~4.48:1, just under the
   4.5:1 AA text floor, and .btn-outline-info had no override anywhere --
   default --bs-info (#0dcaf0) on white is ~1.9:1. Repoints outline-button
   ink at Helm's own high-contrast tokens (>=9:1 on white/subtle-surface),
   same CSS-variable API as the dark-mode fix above so hover/active/
   disabled states stay consistent. Unscoped by size: buttons of any
   size use the same ink color, so .btn-sm is covered without a separate
   rule. */
.btn-outline-primary {
  --bs-btn-color: var(--helm-primary, #1B3A6B);
  --bs-btn-border-color: var(--helm-primary, #1B3A6B);
  --bs-btn-hover-bg: var(--helm-primary, #1B3A6B);
  --bs-btn-hover-border-color: var(--helm-primary, #1B3A6B);
  --bs-btn-active-bg: var(--helm-primary, #1B3A6B);
  --bs-btn-active-border-color: var(--helm-primary, #1B3A6B);
  --bs-btn-disabled-color: var(--helm-primary, #1B3A6B);
  --bs-btn-disabled-border-color: var(--helm-primary, #1B3A6B);
}
.btn-outline-secondary {
  --bs-btn-color: var(--helm-text-secondary, #374151);
  --bs-btn-border-color: var(--helm-text-secondary, #374151);
  --bs-btn-hover-bg: var(--helm-text-secondary, #374151);
  --bs-btn-hover-border-color: var(--helm-text-secondary, #374151);
  --bs-btn-active-bg: var(--helm-text-secondary, #374151);
  --bs-btn-active-border-color: var(--helm-text-secondary, #374151);
  --bs-btn-disabled-color: var(--helm-text-secondary, #374151);
  --bs-btn-disabled-border-color: var(--helm-text-secondary, #374151);
}
/* Helm has no --helm-info token, so info reuses Bootstrap 5.3's own
   AA-safe "emphasis" info colors (the same pair Bootstrap ships for
   .text-info-emphasis) rather than the raw --bs-info accent, which was
   never contrast-safe as text in either theme. */
.btn-outline-info {
  --bs-btn-color: #055160;
  --bs-btn-border-color: #055160;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #055160;
  --bs-btn-hover-border-color: #055160;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #055160;
  --bs-btn-active-border-color: #055160;
  --bs-btn-disabled-color: #055160;
  --bs-btn-disabled-border-color: #055160;
}
[data-bs-theme="dark"] .btn-outline-info {
  --bs-btn-color: #6edff6;
  --bs-btn-border-color: #6edff6;
  --bs-btn-hover-color: #052c33;
  --bs-btn-hover-bg: #6edff6;
  --bs-btn-hover-border-color: #6edff6;
  --bs-btn-active-color: #052c33;
  --bs-btn-active-bg: #6edff6;
  --bs-btn-active-border-color: #6edff6;
  --bs-btn-disabled-color: #6edff6;
  --bs-btn-disabled-border-color: #6edff6;
}
/* warning/danger/success: same fix, found live on Enrollments
   (.btn-outline-warning in light, .btn-outline-danger in dark) once the
   primary/secondary/info sweep above was verified with axe -- the whole
   Bootstrap outline-button family shares this root cause, not just the
   three variants named in the original audit. Same Bootstrap 5.3
   "-emphasis" AA-safe pairs used for info above. */
.btn-outline-warning {
  --bs-btn-color: #664d03;
  --bs-btn-border-color: #664d03;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #664d03;
  --bs-btn-hover-border-color: #664d03;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #664d03;
  --bs-btn-active-border-color: #664d03;
  --bs-btn-disabled-color: #664d03;
  --bs-btn-disabled-border-color: #664d03;
}
[data-bs-theme="dark"] .btn-outline-warning {
  --bs-btn-color: #ffda6a;
  --bs-btn-border-color: #ffda6a;
  --bs-btn-hover-color: #40330b;
  --bs-btn-hover-bg: #ffda6a;
  --bs-btn-hover-border-color: #ffda6a;
  --bs-btn-active-color: #40330b;
  --bs-btn-active-bg: #ffda6a;
  --bs-btn-active-border-color: #ffda6a;
  --bs-btn-disabled-color: #ffda6a;
  --bs-btn-disabled-border-color: #ffda6a;
}
.btn-outline-danger {
  --bs-btn-color: #842029;
  --bs-btn-border-color: #842029;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #842029;
  --bs-btn-hover-border-color: #842029;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #842029;
  --bs-btn-active-border-color: #842029;
  --bs-btn-disabled-color: #842029;
  --bs-btn-disabled-border-color: #842029;
}
[data-bs-theme="dark"] .btn-outline-danger {
  --bs-btn-color: #ea868f;
  --bs-btn-border-color: #ea868f;
  --bs-btn-hover-color: #3b0d10;
  --bs-btn-hover-bg: #ea868f;
  --bs-btn-hover-border-color: #ea868f;
  --bs-btn-active-color: #3b0d10;
  --bs-btn-active-bg: #ea868f;
  --bs-btn-active-border-color: #ea868f;
  --bs-btn-disabled-color: #ea868f;
  --bs-btn-disabled-border-color: #ea868f;
}
.btn-outline-success {
  --bs-btn-color: #0a3622;
  --bs-btn-border-color: #0a3622;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #0a3622;
  --bs-btn-hover-border-color: #0a3622;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #0a3622;
  --bs-btn-active-border-color: #0a3622;
  --bs-btn-disabled-color: #0a3622;
  --bs-btn-disabled-border-color: #0a3622;
}
[data-bs-theme="dark"] .btn-outline-success {
  --bs-btn-color: #75b798;
  --bs-btn-border-color: #75b798;
  --bs-btn-hover-color: #0a2e1c;
  --bs-btn-hover-bg: #75b798;
  --bs-btn-hover-border-color: #75b798;
  --bs-btn-active-color: #0a2e1c;
  --bs-btn-active-bg: #75b798;
  --bs-btn-active-border-color: #75b798;
  --bs-btn-disabled-color: #75b798;
  --bs-btn-disabled-border-color: #75b798;
}

/* Override Bootstrap's gray text utilities for better contrast */
.text-muted { color: var(--helm-text-muted) !important; }
.text-secondary { color: var(--helm-text-secondary) !important; }
.text-body-secondary { color: var(--helm-text-secondary) !important; }

body {
  font-family: var(--helm-font);
  background: var(--helm-bg);
  color: var(--helm-text);
  -webkit-font-smoothing: antialiased;
}

/* ---- Headings ---- */
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  font-family: var(--helm-font);
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* ---- Cards: elevated, clean ---- */
.card {
  border: 1px solid var(--helm-border);
  border-radius: 12px;
  box-shadow: var(--helm-shadow-sm);
  transition: box-shadow var(--helm-transition), transform var(--helm-transition);
}
.card:hover {
  box-shadow: var(--helm-shadow);
}
.card .card-header {
  border-radius: 12px 12px 0 0;
}

/* ---- Stat cards (KPI style) ---- */
.helm-stat-card {
  background: var(--helm-surface);
  border: 1px solid var(--helm-border);
  border-radius: 12px;
  padding: 1.25rem;
  position: relative;
  overflow: hidden;
  transition: box-shadow var(--helm-transition), transform var(--helm-transition);
}
.helm-stat-card:hover {
  box-shadow: var(--helm-shadow);
  transform: translateY(-1px);
}
.helm-stat-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
}
.helm-stat-card.accent-primary::before  { background: var(--helm-primary-fill); }
.helm-stat-card.accent-success::before  { background: var(--helm-success); }
.helm-stat-card.accent-warning::before  { background: var(--helm-warning); }
.helm-stat-card.accent-danger::before   { background: var(--helm-danger); }
.helm-stat-card.accent-cyan::before     { background: var(--helm-accent-fill); }

.helm-stat-icon {
  width: 42px;
  height: 42px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
  flex-shrink: 0;
}

.helm-stat-value {
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
}
.helm-stat-label {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--helm-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-top: 0.25rem;
}
.helm-stat-trend {
  font-size: 0.75rem;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.125rem 0.5rem;
  border-radius: 999px;
}
.helm-stat-trend.up   { color: var(--helm-success); background: rgba(26, 150, 65, 0.1); }
.helm-stat-trend.down { color: var(--helm-danger);  background: rgba(215, 48, 39, 0.1); }

/* Sub-caption under a KPI value (context line: "of 842 segments", etc.). */
.helm-stat-sub {
  font-size: 0.8rem;
  line-height: 1.3;
  color: var(--helm-text-muted);
  margin-top: 0.25rem;
}
/* Sparkline holder. Fixed height so the inline Chart.js canvas cannot grow
   the card; bare by design (no axes/grid/legend per the restraint rules). */
.helm-kpi-spark {
  height: 36px;
  margin-top: 0.5rem;
}
.helm-kpi-spark canvas {
  width: 100% !important;
  height: 36px !important;
  display: block;
}

/* ---- Section headers ---- */
.helm-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--helm-border);
}
.helm-section-title {
  font-size: 1rem;
  font-weight: 600;
  color: var(--helm-text);
  margin: 0;
}

/* ---- Score gauge (circular) ---- */
.helm-score-gauge {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  font-weight: 700;
  color: white;
  position: relative;
}
.helm-score-gauge::after {
  content: '';
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  border: 3px solid rgba(255,255,255,0.3);
}

/* ---- Activity timeline ---- */
.helm-timeline-item {
  display: flex;
  gap: 0.75rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--helm-border);
}
.helm-timeline-item:last-child { border-bottom: none; }
.helm-timeline-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--helm-accent-fill);
  margin-top: 0.5rem;
  flex-shrink: 0;
}
.helm-timeline-content {
  font-size: 0.875rem;
}
.helm-timeline-time {
  font-size: 0.75rem;
  color: var(--helm-text-muted);
}

/* ---- Tables: cleaner ---- */
.table {
  font-size: 0.875rem;
}
.table thead th {
  font-weight: 600;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--helm-text-secondary);
  border-bottom: 2px solid var(--helm-border);
  padding: 0.75rem;
}
.table tbody td {
  padding: 0.75rem;
  vertical-align: middle;
}

/* ---- Score bar (inline in tables) ---- */
.helm-score-bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.helm-score-bar-track {
  flex: 1;
  height: 6px;
  background: var(--helm-surface-subtle);
  border-radius: 3px;
  overflow: hidden;
}
.helm-score-bar-fill {
  height: 100%;
  border-radius: 3px;
  transition: width var(--helm-transition);
}
.helm-score-bar-value {
  font-size: 0.8rem;
  font-weight: 600;
  font-family: var(--helm-font-mono);
  min-width: 2.5rem;
  text-align: right;
}

/* ---- Badges ---- */
.badge {
  font-weight: 500;
  letter-spacing: 0.02em;
}

/* ---- Buttons ---- */
.btn {
  font-weight: 500;
  border-radius: 8px;
  transition: all var(--helm-transition);
}

/* Keyboard focus must be VISIBLE on every button (WCAG 2.4.7). Bootstrap's
   .btn:focus-visible sets outline: 0 (beating app.css's global
   :focus-visible rule on specificity) and relies on its box-shadow ring,
   which computes fully transparent here, so a Tab-focused button showed
   nothing at all (found on the CRM org action row, bug 2138). The ink
   accent carries 4.8:1 on light surfaces; the fill accent 6.9:1 on dark. */
.btn:focus-visible {
  outline: 3px solid var(--helm-accent, #00757F);
  outline-offset: 2px;
  box-shadow: none;
}
[data-bs-theme="dark"] .btn:focus-visible {
  outline-color: var(--helm-accent-fill, #00BCD4);
}

/* ---- Sidebar enhancements ---- */
.sidebar .nav-link {
  font-size: 0.875rem;
  font-weight: 500;
  border-radius: 8px;
  margin-bottom: 2px;
  padding: 0.5rem 0.75rem;
}
.sidebar .nav-link:hover {
  background: var(--helm-surface-subtle);
}
.sidebar .nav-link.active {
  background: rgba(27, 58, 107, 0.08);
  /* --helm-primary is a FILL token and stays navy in dark, so the active row
     read as navy-on-charcoal: axe measured #1b3a6b on the composited tint
     #2a3139 at 1.16:1, the worst node left in the sweep, on /admin/team,
     /admin/my-org and /admin/my-plan (bug 1798). --helm-ink-brand is the same
     navy in light (10.03:1 on the light tint, so nothing visibly changes there)
     and the light-blue accent in dark (5.60:1 on #2a3139). */
  color: var(--helm-ink-brand, #1B3A6B);
  font-weight: 600;
}

/* LSP active highlight: always the header navy (--helm-primary), regardless of
   the per-firm branding --bs-primary. Unifies every left sidebar's active item
   so none renders the lighter branding blue (bugs #833, #851). Covers every
   sidebar: the universal and CRM sidebars mark the active row with
   `.btn-primary` + aria-current="page"; the projects sidebar (project-lsb)
   marks it with `.btn-primary` alone. Inside `.sidebar` the only `.btn-primary`
   is the active nav row (inactive rows are `.btn-outline-secondary`), so a
   broad selector is safe and makes the navy truly application-wide. */
.sidebar .btn.btn-primary {
  background-color: var(--helm-primary-fill) !important;
  border-color: var(--helm-primary) !important;
  color: #fff !important;
}
.sidebar .btn.btn-primary:hover,
.sidebar .btn.btn-primary:focus {
  background-color: var(--helm-primary-dark) !important;
  border-color: var(--helm-primary-dark) !important;
  color: #fff !important;
}

/* ---- Page wrapper ---- */
.helm-page {
  padding: var(--helm-page-padding);
}
.helm-page-header {
  margin-bottom: 1.5rem;
}
.helm-page-title {
  font-size: 1.5rem;
  font-weight: 700;
  margin: 0;
}
.helm-page-subtitle {
  font-size: 0.875rem;
  color: var(--helm-text-secondary);
  margin-top: 0.25rem;
}

/* ---- Empty states ---- */
.helm-empty-state {
  text-align: center;
  padding: 3rem 2rem;
}
.helm-empty-state-icon {
  font-size: 3rem;
  color: var(--helm-text-muted);
  margin-bottom: 1rem;
}
.helm-empty-state-title {
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--helm-text);
  margin-bottom: 0.5rem;
}
.helm-empty-state-text {
  color: var(--helm-text-secondary);
  font-size: 0.9rem;
  margin-bottom: 1.5rem;
}

/* ---- Skeleton loading ---- */
.helm-skeleton {
  background: linear-gradient(90deg, var(--helm-surface-subtle) 25%, var(--helm-bg) 50%, var(--helm-surface-subtle) 75%);
  background-size: 200% 100%;
  animation: helm-shimmer 1.5s infinite;
  border-radius: 6px;
}
@keyframes helm-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ---- Navbar refinement ---- */
.navbar {
  backdrop-filter: blur(12px);
  border-bottom: 1px solid rgba(255,255,255,0.1);
}

/* ---- Data source status dots ---- */
.helm-status-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  display: inline-block;
  flex-shrink: 0;
}
.helm-status-dot.fresh   { background: var(--helm-success); box-shadow: 0 0 0 2px rgba(26,150,65,0.2); }
.helm-status-dot.stale   { background: var(--helm-warning); box-shadow: 0 0 0 2px rgba(253,174,97,0.2); }
.helm-status-dot.error   { background: var(--helm-danger);  box-shadow: 0 0 0 2px rgba(215,48,39,0.2); }
.helm-status-dot.unknown { background: var(--helm-text-muted); }

/* ---- Quick-action cards ---- */
.helm-action-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 1.25rem 0.75rem;
  border-radius: 10px;
  border: 1px solid var(--helm-border);
  background: var(--helm-surface);
  color: var(--helm-text);
  text-decoration: none;
  font-size: 0.85rem;
  font-weight: 500;
  transition: all var(--helm-transition);
  text-align: center;
}
.helm-action-card:hover {
  border-color: var(--helm-primary);
  box-shadow: var(--helm-shadow);
  color: var(--helm-primary);
  transform: translateY(-2px);
}
.helm-action-card .helm-action-icon {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.15rem;
}

/* ---- Condition bar chart (horizontal stacked) ---- */
.helm-condition-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0;
}
.helm-condition-label {
  min-width: 70px;
  font-size: 0.8rem;
  font-weight: 600;
}
.helm-condition-bar-wrap {
  flex: 1;
  height: 8px;
  background: var(--helm-surface-subtle);
  border-radius: 4px;
  overflow: hidden;
}
.helm-condition-bar {
  height: 100%;
  border-radius: 4px;
  transition: width 0.6s ease;
}
.helm-condition-count {
  min-width: 40px;
  text-align: right;
  font-size: 0.8rem;
  font-weight: 600;
  font-family: var(--helm-font-mono);
}

/* ---- Data source grid ---- */
.helm-fetcher-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 0.5rem;
}
.helm-fetcher-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  background: var(--helm-surface-subtle);
  border-radius: 8px;
  font-size: 0.8rem;
  font-weight: 500;
}

/* ---- Map preview container ---- */
.helm-map-preview {
  height: 280px;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--helm-border);
}
.helm-map-preview .leaflet-container {
  height: 100%;
  width: 100%;
}

/* ---- Role badge ---- */
.helm-role-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.25rem 0.75rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}
.helm-role-badge.role-platform_admin { background: rgba(215,48,39,0.1);  color: var(--helm-danger); }
.helm-role-badge.role-firm_admin     { background: rgba(27,58,107,0.1);  color: var(--helm-primary); }
.helm-role-badge.role-project_manager{ background: rgba(0,188,212,0.1);  color: #0097A7; }
.helm-role-badge.role-inspector      { background: rgba(26,150,65,0.1);  color: var(--helm-success); }
.helm-role-badge.role-viewer         { background: rgba(107,114,128,0.1);color: var(--helm-text-secondary); }

/* Flex-shrink helper: lets a flex child shrink below its content width so
   `text-truncate` works inside it. Several templates rely on this class
   (marketplace tiles, admin index, CRM dashboard); Bootstrap 5 doesn't
   ship it, so it's defined here. */
.min-w-0 { min-width: 0 !important; }

/* ── Robocist-only feature marker ────────────────────────────────────
   Applied to nav items and breadcrumb entries that are restricted to
   Robocist staff (rt_sales+). Only renders for Robocist users; clients
   never see the symbol or the highlight because the menu entry itself
   isn't sent to them. The highlight is intentionally subtle so it reads
   as "exclusive" rather than "warning". */
/* ── Universal LSP sidebar buttons ───────────────────────────────────
   Override Bootstrap's outline-secondary defaults so the text stays
   readable on the light bg-body-tertiary sidebar in every state. */
.universal-lsb .btn.btn-outline-secondary {
  color: var(--helm-text, #1f2937);
  border-color: rgba(0, 0, 0, 0.12);
  background: #fff;
}
.universal-lsb .btn.btn-outline-secondary:hover,
.universal-lsb .btn.btn-outline-secondary:focus-visible {
  background: var(--helm-primary-fill, #1B3A6B);
  border-color: var(--helm-primary, #1B3A6B);
  color: #fff;
}
.universal-lsb .btn.btn-primary {
  background: var(--helm-primary-fill, #1B3A6B);
  border-color: var(--helm-primary, #1B3A6B);
  color: #fff;
}
.universal-lsb .btn.btn-primary:hover,
.universal-lsb .btn.btn-primary:focus-visible {
  background: var(--helm-primary-dark, #0F2544);
  border-color: var(--helm-primary-dark, #0F2544);
  color: #fff;
}
/* Inline help "?" icon next to each row. Was an inline style on the
   button (bug #1179); moved here so the dark theme can override it.
   Same value light mode has always had. */
.universal-lsb .rt-lsp-help {
  color: rgba(0, 0, 0, 0.45);
}

/* Robocist-only locked items: dark amber text on the amber tint at
   rest so it reads, dark text on a stronger amber on hover, and the
   active state keeps the helm-primary fill (dark blue) with an amber
   border so the lock context is still visible. */
.helm-nav-locked .btn.is-locked {
  position: relative;
  color: #7c4a03;                                     /* dark amber */
  background-color: rgba(245, 158, 11, 0.12);
  border-color: rgba(245, 158, 11, 0.45);
  /* Lock and label share a single line; label truncates if too long. */
  white-space: nowrap;
  overflow: hidden;
}
.helm-nav-locked .btn.is-locked > span { flex-wrap: nowrap; }
.helm-nav-locked .btn.is-locked:hover,
.helm-nav-locked .btn.is-locked:focus-visible {
  color: #5a3300;
  background-color: rgba(245, 158, 11, 0.22);
  border-color: rgba(245, 158, 11, 0.65);
}
.helm-nav-locked .btn.is-locked.btn-primary {
  /* Active locked page: dark blue fill, amber border, white text. */
  color: #fff;
  background: var(--helm-primary-fill, #1B3A6B);
  border-color: #f59e0b;
  box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.35) inset;
}
.helm-nav-locked .btn.is-locked.btn-primary:hover,
.helm-nav-locked .btn.is-locked.btn-primary:focus-visible {
  background: var(--helm-primary-dark, #0F2544);
  border-color: #f59e0b;
  color: #fff;
}
/* Dark theme LSP (bug #1179). The panel background already flips via
   Bootstrap's bg-body-tertiary, but the rules above hardcode
   light-theme colors: white item buttons, near-black inherited text,
   grey #4B5563 group labels, dark-amber locked rows, and a black-alpha
   help icon. All unreadable on the dark panel. These overrides are
   scoped to [data-bs-theme="dark"] so light mode is untouched. */
[data-bs-theme="dark"] .universal-lsb {
  /* The h6 group header and any other inherited text would otherwise
     keep the light theme's near-black --helm-text from body. */
  color: var(--bs-body-color);
}
[data-bs-theme="dark"] .universal-lsb .btn.btn-outline-secondary {
  color: var(--bs-body-color);
  border-color: var(--bs-border-color);
  background: var(--bs-body-bg);
}
[data-bs-theme="dark"] .universal-lsb .btn.btn-outline-secondary:hover,
[data-bs-theme="dark"] .universal-lsb .btn.btn-outline-secondary:focus-visible {
  /* Same brand-blue fill as light mode; white on #1B3A6B is 10.5:1. */
  background: var(--helm-primary-fill, #1B3A6B);
  border-color: var(--helm-primary-light, #2A4F8F);
  color: #fff;
}
/* Section labels ("PIPELINE", "MORE") and drawer toggles use
   .text-muted, which the global override above pins to #4B5563
   (!important), 2.4:1 on the dark panel. Re-pin to Bootstrap's dark
   gray-500 (5.9:1 on #2b3035). */
[data-bs-theme="dark"] .universal-lsb .text-muted,
[data-bs-theme="dark"] .universal-lsb .lsp-drawer-toggle {
  color: #adb5bd !important;
}
/* Inline help "?" icons: light mode uses black-alpha, invisible on
   the dark panel. */
[data-bs-theme="dark"] .universal-lsb .rt-lsp-help {
  color: rgba(255, 255, 255, 0.55);
}
/* Locked (Robocist-only) rows: dark-amber-on-amber-tint from the light
   theme drops below 2:1 on the dark panel. Flip to light amber text on
   the same subtle tint (7.7:1 at rest, 7.1:1 on hover). */
[data-bs-theme="dark"] .helm-nav-locked .btn.is-locked {
  color: #fcd34d;
  background-color: rgba(245, 158, 11, 0.10);
  border-color: rgba(245, 158, 11, 0.40);
}
[data-bs-theme="dark"] .helm-nav-locked .btn.is-locked:hover,
[data-bs-theme="dark"] .helm-nav-locked .btn.is-locked:focus-visible {
  color: #ffe08a;
  background-color: rgba(245, 158, 11, 0.20);
  border-color: rgba(245, 158, 11, 0.65);
}
/* Active locked page keeps the light theme's treatment (brand-blue
   fill, amber border, white text), restated here because the generic
   dark .is-locked rule above would otherwise repaint it amber. */
[data-bs-theme="dark"] .helm-nav-locked .btn.is-locked.btn-primary {
  color: #fff;
  background: var(--helm-primary-fill, #1B3A6B);
  border-color: #f59e0b;
  box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.35) inset;
}
[data-bs-theme="dark"] .helm-nav-locked .btn.is-locked.btn-primary:hover,
[data-bs-theme="dark"] .helm-nav-locked .btn.is-locked.btn-primary:focus-visible {
  background: var(--helm-primary-dark, #0F2544);
  border-color: #f59e0b;
  color: #fff;
}

/* ── Robocist lock marking: one amber language across every surface ──
   The LSP rows above set the pattern (lock glyph + gold-amber tint).
   These classes extend it to the top-nav tabs, the launcher grid, the
   mobile offcanvas, and the assessment-type tabs so staff read the same
   signal everywhere. Display marking only; never an access control. */

/* Top-nav persona tabs. The navbar is brand-blue (#1B3A6B) in both
   themes, so one rule serves light and dark: light amber text (7.5:1 on
   the blue) over a subtle amber tint. */
.rt-topnav-link.helm-topnav-locked {
  color: #fcd34d;
  background-color: rgba(245, 158, 11, 0.14);
  border-radius: 6px 6px 0 0;
}
.rt-topnav-link.helm-topnav-locked:hover,
.rt-topnav-link.helm-topnav-locked:focus-visible {
  color: #ffe08a;
  background-color: rgba(245, 158, 11, 0.24);
}
.rt-topnav-link.helm-topnav-locked.active {
  color: #ffe08a;
}
.helm-topnav-lock-ico { color: #fcd34d; }

/* Launcher grid tiles (was inline styles; moved here so the dark theme
   can restyle). Light: dark amber on the amber tint, same recipe as the
   LSP rows. */
.helm-launcher-locked {
  color: #7c4a03 !important;
  background-color: rgba(245, 158, 11, 0.10);
  border: 1px solid rgba(245, 158, 11, 0.40);
}
.helm-launcher-locked:hover,
.helm-launcher-locked:focus-visible {
  color: #5a3300 !important;
  background-color: rgba(245, 158, 11, 0.18);
  border-color: rgba(245, 158, 11, 0.60);
}
[data-bs-theme="dark"] .helm-launcher-locked {
  color: #fcd34d !important;
}
[data-bs-theme="dark"] .helm-launcher-locked:hover,
[data-bs-theme="dark"] .helm-launcher-locked:focus-visible {
  color: #ffe08a !important;
}

/* Launcher editor (bug #2233): reorder / remove / pin any reachable page,
   and mark one entry as the default landing page. All of it lives inside
   the existing waffle dropdown so no page gains any new chrome. Star
   color matches .admin-tile-fav's landing-page/favorite language
   elsewhere in the app (#f59e0b) so the same icon means the same thing
   everywhere a user sees it. */
/* a11y fix (bug #2233 audit): Bootstrap's default .dropdown-header color
   (--bs-secondary-color, ~#6c757d) measures 3.29:1 against the dark-theme
   dropdown background (#212529) — a WCAG AA failure for normal text
   (needs 4.5:1). Not specific to the launcher: every dropdown-header on
   the site inherits this in dark theme (the partner firm-switcher's
   "Viewing as" label is the other example), so the fix is global, not
   scoped to the launcher panel. #adb5bd measures 7.43:1. */
[data-bs-theme="dark"] .dropdown-header { color: #adb5bd !important; }

.helm-launcher-tile { position: relative; }
.helm-launcher-tile:hover,
.helm-launcher-tile:focus-visible { background: var(--helm-surface-subtle); }
.helm-launcher-star {
  position: absolute; top: 2px; right: 6px; font-size: 0.65rem;
  color: #f59e0b;
}
.helm-launcher-edit-toggle { color: var(--helm-text-secondary); font-size: 0.78rem; }
.helm-launcher-edit-toggle:hover,
.helm-launcher-edit-toggle:focus-visible { color: var(--helm-primary); }
.helm-launcher-pinbar {
  background: var(--helm-surface-subtle);
  border: 1px solid var(--helm-border);
  border-radius: 8px;
  padding: 8px 10px;
}
[data-bs-theme="dark"] .helm-launcher-pinbar { background: var(--bs-tertiary-bg); }
.helm-launcher-list { max-height: 260px; overflow-y: auto; }
.helm-launcher-row { border-bottom: 1px solid var(--helm-border); }
.helm-launcher-row:last-child { border-bottom: none; }
.helm-launcher-results { max-height: 180px; overflow-y: auto; }
.btn-xs { padding: 0.12rem 0.4rem; font-size: 0.72rem; line-height: 1.5; border-radius: 6px; }
.btn-icon {
  padding: 0.12rem 0.4rem;
  border: 1px solid var(--helm-border);
  background: var(--helm-surface);
  color: var(--helm-text-secondary);
}
.btn-icon:hover:not(:disabled) { background: var(--helm-surface-subtle); color: var(--helm-text); }
.btn-icon:disabled { opacity: 0.35; }
.helm-launcher-star-btn.is-landing { color: #f59e0b; border-color: rgba(245, 158, 11, 0.5); }
.helm-launcher-star-btn:hover:not(:disabled) { color: #f59e0b; }

/* Mobile offcanvas rows and group headers. */
.helm-mobile-locked {
  color: #7c4a03 !important;
  background-color: rgba(245, 158, 11, 0.12);
  border-radius: 8px;
}
.helm-mobile-locked:hover,
.helm-mobile-locked:focus-visible {
  color: #5a3300 !important;
  background-color: rgba(245, 158, 11, 0.22);
}
[data-bs-theme="dark"] .helm-mobile-locked {
  color: #fcd34d !important;
  background-color: rgba(245, 158, 11, 0.10);
}
[data-bs-theme="dark"] .helm-mobile-locked:hover,
[data-bs-theme="dark"] .helm-mobile-locked:focus-visible {
  color: #ffe08a !important;
  background-color: rgba(245, 158, 11, 0.20);
}
/* Accordion buttons carry their own background; only tint, never repaint
   the expanded state's chevron color. */
.accordion-button.helm-mobile-locked:not(.collapsed) {
  box-shadow: inset 0 -1px 0 rgba(245, 158, 11, 0.40);
}

/* Assessment-type tabs: project detail's segment panel (.nav-tabs
   .nav-link) and the statistics tab row (.tab-button). The lock glyph
   carries the meaning; amber makes it scannable. Wins over the
   text-muted "no data on this segment" grey so the staff signal is
   never washed out. */
.nav-tabs .nav-link.helm-tab-locked,
.tab-button.helm-tab-locked {
  color: #7c4a03 !important;
  background-color: rgba(245, 158, 11, 0.10);
  border-color: rgba(245, 158, 11, 0.45);
}
/* :not(.active) keeps the statistics tab-button's blue active fill
   (white text) from being repainted light-amber-under-white on hover. */
.nav-tabs .nav-link.helm-tab-locked:hover,
.nav-tabs .nav-link.helm-tab-locked:focus-visible,
.tab-button.helm-tab-locked:not(.active):hover,
.tab-button.helm-tab-locked:not(.active):focus-visible {
  color: #5a3300 !important;
  background-color: rgba(245, 158, 11, 0.20);
  border-color: rgba(245, 158, 11, 0.65);
}
/* Active locked tab: keep the active affordance, add the amber frame. */
.nav-tabs .nav-link.helm-tab-locked.active,
.tab-button.helm-tab-locked.active {
  border-color: #f59e0b;
  box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.35) inset;
}
/* The statistics tab-button paints its active state brand-blue with
   white text (page-local <style>). Dark amber on that blue is 1.7:1,
   so the active locked tab keeps white text and a light-amber lock. */
.tab-button.helm-tab-locked.active {
  color: #fff !important;
}
.tab-button.helm-tab-locked.active .helm-tab-lock-ico {
  color: #ffe08a;
}
.helm-tab-lock-ico {
  color: #b45309;
  font-size: 0.85em;
  margin-left: 0.25rem;
}
[data-bs-theme="dark"] .nav-tabs .nav-link.helm-tab-locked,
[data-bs-theme="dark"] .tab-button.helm-tab-locked {
  color: #fcd34d !important;
  background-color: rgba(245, 158, 11, 0.10);
  border-color: rgba(245, 158, 11, 0.40);
}
[data-bs-theme="dark"] .nav-tabs .nav-link.helm-tab-locked:hover,
[data-bs-theme="dark"] .nav-tabs .nav-link.helm-tab-locked:focus-visible,
[data-bs-theme="dark"] .tab-button.helm-tab-locked:not(.active):hover,
[data-bs-theme="dark"] .tab-button.helm-tab-locked:not(.active):focus-visible {
  color: #ffe08a !important;
  background-color: rgba(245, 158, 11, 0.20);
}
[data-bs-theme="dark"] .helm-tab-lock-ico { color: #fcd34d; }

/* Marketplace listing locks: subtle amber border + tint so a Robocist
   user can tell at a glance which catalog entries are internal-only. */
.helm-mp-locked {
  border: 1px solid rgba(245, 158, 11, 0.45) !important;
  box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.10) inset;
  background: linear-gradient(135deg, rgba(245,158,11,0.05), transparent 60%);
}
.helm-mp-locked-row > td {
  background: rgba(245, 158, 11, 0.05);
  border-color: rgba(245, 158, 11, 0.25);
}
/* Breadcrumb lock marker: same convention, lighter weight. */
.helm-breadcrumb-locked {
  background: rgba(245, 158, 11, 0.10);
  border: 1px solid rgba(245, 158, 11, 0.35);
  border-radius: 4px;
  padding: 1px 6px;
  font-size: 0.8em;
  white-space: nowrap;
}

/* ── Overlapping-items "stack" map marker (Bug #549) ──
   When two or more projects/proposals share a footprint their single pins
   would hide each other, so they collapse into one teardrop that carries a
   count and opens a chooser popup. Ported verbatim from projects/multi.html
   so every multi-item map (dashboard, pipeline, proposals overview) renders
   the stack pin identically. The doubled, offset shadow layers read as a
   literal stack of cards. */
.helm-stack-marker { background: none; border: 0; }
.helm-stack-pin {
    position: relative;
    width: 34px; height: 34px;
    border-radius: 50% 50% 50% 0;
    transform: rotate(-45deg);
    background: linear-gradient(150deg, #1f3a5f 0%, #0f2440 100%);
    border: 2px solid #fff;
    box-shadow:
        0 2px 6px rgba(0,0,0,0.35),
        3px 3px 0 -1px rgba(31,58,95,0.55),   /* card 2 */
        6px 6px 0 -2px rgba(31,58,95,0.30);   /* card 3 */
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
    transition: transform .12s ease, box-shadow .12s ease;
}
.helm-stack-marker:hover .helm-stack-pin,
.leaflet-marker-icon:focus .helm-stack-pin {
    transform: rotate(-45deg) scale(1.08);
    box-shadow:
        0 4px 10px rgba(0,0,0,0.4),
        3px 3px 0 -1px rgba(31,58,95,0.65),
        6px 6px 0 -2px rgba(31,58,95,0.4);
}
.leaflet-marker-icon:focus { outline: none; }
.leaflet-marker-icon:focus .helm-stack-pin {
    outline: 3px solid #ffc233;
    outline-offset: 2px;
}
.helm-stack-count {
    transform: rotate(45deg);
    color: #fff;
    font-family: 'DM Sans', system-ui, sans-serif;
    font-weight: 700;
    font-size: 15px;
    line-height: 1;
    text-shadow: 0 1px 2px rgba(0,0,0,0.4);
}

/* Chooser popup */
.leaflet-popup-content .helm-stack-pop { min-width: 230px; }
.helm-stack-head {
    font-family: 'DM Sans', system-ui, sans-serif;
    font-weight: 700;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #51607a;
    padding: 2px 2px 8px;
    border-bottom: 1px solid #e7ebf2;
    margin-bottom: 4px;
}
a.helm-stack-item {
    display: flex; align-items: center; gap: 9px;
    padding: 8px 6px;
    border-radius: 8px;
    text-decoration: none;
    color: #18233a;
    transition: background .1s ease;
}
a.helm-stack-item + .helm-stack-item { margin-top: 1px; }
a.helm-stack-item:hover { background: #eef3fb; }
a.helm-stack-item:focus-visible {
    background: #eef3fb;
    outline: 2px solid #1f3a5f;
    outline-offset: -1px;
}
.helm-stack-dot {
    flex: 0 0 auto;
    width: 11px; height: 11px;
    border-radius: 50%;
    border: 2px solid #fff;
    box-shadow: 0 0 0 1px rgba(0,0,0,0.12);
}
.helm-stack-body { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.helm-stack-name {
    font-family: 'DM Sans', system-ui, sans-serif;
    font-weight: 600; font-size: 0.86rem; line-height: 1.2;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.helm-stack-meta { font-size: 0.72rem; color: #6b7689; margin-top: 2px; }
.helm-stack-meta .badge {
    font-size: 0.6rem; font-weight: 600;
    color: #fff; padding: 0.12em 0.45em; border-radius: 4px;
    text-transform: capitalize; vertical-align: middle;
}
.helm-stack-go { color: #b3bccb; font-size: 1.1rem; flex: 0 0 auto; }
a.helm-stack-item:hover .helm-stack-go,
a.helm-stack-item:focus-visible .helm-stack-go { color: #1f3a5f; }

/* ---- Support case: customer context sidebar ---- */
.helm-ctx {
    position: sticky;
    top: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.helm-ctx-card {
    background: var(--helm-surface, #fff);
    border: 1px solid var(--helm-border, #e3e8ef);
    border-radius: 12px;
    box-shadow: var(--helm-shadow-sm, 0 1px 2px rgba(16,24,40,.06));
    overflow: hidden;
}
.helm-ctx-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.7rem 0.9rem;
    border-bottom: 1px solid var(--helm-border, #e3e8ef);
    background: var(--helm-surface-subtle, #f7f9fc);
}
.helm-ctx-head i { color: var(--helm-primary, #1f3a5f); font-size: 0.95rem; }
.helm-ctx-title {
    font-family: 'DM Sans', system-ui, sans-serif;
    font-weight: 600; font-size: 0.78rem; letter-spacing: .02em;
    text-transform: uppercase; color: var(--helm-text-secondary, #51607a);
    margin: 0;
}
.helm-ctx-body { padding: 0.7rem 0.9rem; }
.helm-ctx-primary {
    font-weight: 600; font-size: 0.95rem; color: var(--helm-text, #1a2230);
    line-height: 1.25;
}
.helm-ctx-sub { font-size: 0.76rem; color: var(--helm-text-muted, #6b7689); margin-top: 2px; }
.helm-ctx-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.5rem; padding: 0.4rem 0;
    border-top: 1px solid var(--helm-surface-subtle, #f0f3f8);
    font-size: 0.82rem;
}
.helm-ctx-row:first-child { border-top: 0; }
.helm-ctx-row .name {
    color: var(--helm-text, #1a2230); min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.helm-ctx-row .meta { color: var(--helm-text-muted, #6b7689); flex: 0 0 auto; font-size: 0.72rem; }
.helm-ctx-pill {
    display: inline-block; font-size: 0.68rem; font-weight: 600;
    padding: 0.12em 0.5em; border-radius: 999px;
    background: var(--helm-surface-subtle, #eef2f7);
    color: var(--helm-text-secondary, #51607a);
    text-transform: capitalize;
}
.helm-ctx-pill.is-active  { background: #e7f6ee; color: #1c7a47; }
.helm-ctx-pill.is-trial   { background: #fff4e2; color: #9a6400; }
.helm-ctx-empty { font-size: 0.78rem; color: var(--helm-text-muted, #98a2b3); font-style: italic; }

/* ==========================================================================
   Global search  (v8 Tier3 #18)
   ========================================================================== */
.gs-box { position: relative; }

/* Bug 2459: the search field opens under its header icon instead of
   holding 200px of the navbar open at all times. Field and results share
   one card, so the whole search stack reads as a single surface. */
.gs-field {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  width: 420px;
  max-width: 92vw;
  z-index: 10000;
  padding: 10px;
  background: var(--helm-surface, #fff);
  border: 1px solid var(--helm-border, #E2E6EF);
  border-radius: 10px;
  box-shadow: var(--helm-shadow-lg, 0 8px 24px rgba(27, 58, 107, 0.12));
  font-family: var(--helm-font, 'DM Sans', sans-serif);
}
/* Inside the card the results are just the next block down, not a second
   floating panel with its own border and shadow. */
.gs-field .gs-dropdown {
  position: static;
  width: auto;
  max-width: none;
  margin-top: 8px;
}
.gs-field .gs-panel {
  border: 0;
  box-shadow: none;
  border-radius: 8px;
  max-height: 60vh;
}

.gs-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  width: 420px;
  max-width: 92vw;
  z-index: 9999;
}

.gs-panel {
  background: var(--helm-surface, #fff);
  border: 1px solid var(--helm-border, #E2E6EF);
  border-radius: 10px;
  box-shadow: var(--helm-shadow-lg, 0 8px 24px rgba(27,58,107,0.12));
  overflow: hidden;
  max-height: 70vh;
  overflow-y: auto;
  font-family: var(--helm-font, 'DM Sans', sans-serif);
}

.gs-empty {
  padding: 16px 18px;
  color: var(--helm-text-secondary, #374151);
  font-size: 0.88rem;
}
.gs-empty-link { margin-left: 6px; color: var(--helm-primary, #1B3A6B); font-weight: 600; }

.gs-group { border-bottom: 1px solid var(--helm-surface-subtle, #F0F2F8); }
.gs-group:last-of-type { border-bottom: 0; }

.gs-group-head {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 14px 6px;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--helm-text-muted, #4B5563);
  background: var(--helm-surface-subtle, #F0F2F8);
}
.gs-group-head .bi { color: var(--helm-primary-light, #2A4F8F); }
.gs-see-all {
  margin-left: auto; font-size: 0.68rem; font-weight: 600;
  text-transform: none; letter-spacing: 0;
  color: var(--helm-accent, #00BCD4);
}

.gs-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 14px;
  text-decoration: none;
  color: var(--helm-text, #1A1D26);
  border-left: 3px solid transparent;
  transition: background var(--helm-transition, 0.15s);
}
.gs-item:hover, .gs-item.gs-active {
  background: var(--helm-surface-subtle, #F0F2F8);
  border-left-color: var(--helm-accent, #00BCD4);
}
.gs-item-icon {
  flex: 0 0 26px; height: 26px; width: 26px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 6px;
  background: rgba(27,58,107,0.07);
  color: var(--helm-primary, #1B3A6B);
  font-size: 0.85rem;
}
.gs-item-body { display: flex; flex-direction: column; min-width: 0; }
.gs-item-title {
  font-size: 0.88rem; font-weight: 600; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gs-item-sub {
  font-size: 0.74rem; color: var(--helm-text-muted, #4B5563);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.gs-footer {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px;
  font-size: 0.8rem; font-weight: 600;
  color: var(--helm-primary, #1B3A6B);
  background: var(--helm-surface-subtle, #F0F2F8);
  text-decoration: none;
}
.gs-footer:hover { color: var(--helm-primary-dark, #0F2544); }

/* ---- Full results page ---- */
.gs-results-page { max-width: 820px; margin: 0 auto; }
.gs-results-search {
  display: flex; align-items: center; gap: 10px;
  position: relative; margin-bottom: 1.5rem;
}
.gs-results-search .bi-search {
  position: absolute; left: 14px; color: var(--helm-text-muted, #4B5563);
  pointer-events: none;
}
.gs-results-search input { padding-left: 38px; height: 46px; font-size: 1rem; }
.gs-results-count { color: var(--helm-text-secondary, #374151); margin-bottom: 1rem; }

.gs-results-empty {
  text-align: center; padding: 4rem 1rem;
  color: var(--helm-text-muted, #4B5563);
}
.gs-results-empty .bi { font-size: 2.5rem; opacity: 0.5; display: block; margin-bottom: 0.75rem; }

.gs-results-group { margin-bottom: 1.75rem; }
.gs-results-group-head {
  display: flex; align-items: center; gap: 8px;
  font-size: 1rem; font-weight: 700;
  color: var(--helm-primary, #1B3A6B);
  padding-bottom: 6px; margin-bottom: 8px;
  border-bottom: 2px solid var(--helm-surface-subtle, #F0F2F8);
}
.gs-results-group-head .gs-see-all { margin-left: auto; font-size: 0.78rem; }
.gs-results-list {
  display: grid; gap: 6px;
}
.gs-result {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 14px;
  background: var(--helm-surface, #fff);
  border: 1px solid var(--helm-border, #E2E6EF);
  border-radius: 8px;
  text-decoration: none; color: var(--helm-text, #1A1D26);
  transition: border-color var(--helm-transition, 0.15s), box-shadow var(--helm-transition, 0.15s);
}
.gs-result:hover {
  border-color: var(--helm-accent, #00BCD4);
  box-shadow: var(--helm-shadow-sm, 0 1px 2px rgba(27,58,107,0.06));
}

/* ---- Breadcrumbs (reusable macro) ----
   Also covers hand-rolled `<ol class="breadcrumb">` blocks that don't
   carry the `.helm-breadcrumb` wrapper class (several pages build their
   own breadcrumb markup inline instead of using the
   components/_breadcrumbs.html macro). Without this bare selector those
   links fall through to Bootstrap's default `--bs-link-color` (#0d6efd),
   which fails WCAG AA (4.16:1) against the page background.
   --helm-text-secondary, not a brand/primary token: the brand tokens
   (--helm-primary*, --helm-accent) are deliberately NOT remapped for dark
   mode (they back filled buttons that keep white ink in both themes), so
   they stay navy-on-navy in dark and collapse to ~1.9:1. --helm-text-
   secondary IS theme-remapped and clears AA in both (~9.5:1 light /
   ~10.3:1 dark against the page background). */
.helm-breadcrumb { font-family: var(--helm-font, 'DM Sans', sans-serif); font-size: 0.75rem; }
.breadcrumb-item a,
.helm-breadcrumb .breadcrumb-item a {
  color: var(--helm-text-secondary, #374151);
  text-decoration: none; font-weight: 500;
}
.breadcrumb-item a:hover,
.helm-breadcrumb .breadcrumb-item a:hover { text-decoration: underline; }
.breadcrumb-item.active,
.helm-breadcrumb .breadcrumb-item.active { color: var(--helm-text-muted, #4B5563); }
/* Full-bleed pages (map/detail) render their content with padding:0, so the
   breadcrumb sits flush against the window edge. Give it a small left inset
   there only. Normally-padded pages already inset it via their container. */
#main-content[style*="padding: 0"] .helm-breadcrumb { padding-left: 1rem; }

/* ---- Disclaimer-footer inline links (Terms-link contrast fix, twin of
   the breadcrumb fix above) ----
   `_partials/_ai_disclaimer.html` and `_partials/_disclaimer.html` both
   sit inline in a `text-muted small` sentence and inherit Bootstrap's
   default `--bs-link-color` (#0d6efd). #1551 removed the link's
   text-decoration-none (clears axe `link-in-text-block`) but never
   touched its COLOR, so axe `color-contrast` still fails live in light
   mode: 4.16:1 against --helm-bg (#F5F6FA), short of the 4.5:1 AA floor.
   Dark mode was never broken here -- Bootstrap's own dark link color
   already clears AA (~6.4:1) since it isn't a brand token. Same fix
   family as the breadcrumb rule above: route through --helm-text-
   secondary, which IS theme-remapped and clears AA in both (9.5:1 light
   / 10.3:1 dark), rather than leaving Bootstrap's un-remapped default. */
.rt-ai-disclaimer a,
.rt-disclaimer a {
  color: var(--helm-text-secondary, #374151);
  font-weight: 500;
}
.rt-ai-disclaimer a:hover,
.rt-disclaimer a:hover { color: var(--helm-text, #1A1D26); }

/* ---- List-row title links (SEQ-P2-01, third twin of the two fixes above)
   ----
   `.fw-semibold.text-decoration-none` is the sitewide convention for a
   data-view row's primary link (script name, group name, project name,
   deal name, and 20+ more templates -- grep the codebase for the exact
   class combo). Every one of them inherits Bootstrap's un-remapped
   default `--bs-link-color` (#0d6efd), same root cause as the breadcrumb
   and disclaimer links: ~4.48:1 in light, short of 4.5:1 AA. Fixed once,
   here, rather than in each of the 20+ templates that use the combo. */
a.fw-semibold.text-decoration-none {
  color: var(--helm-text-secondary, #374151);
}
a.fw-semibold.text-decoration-none:hover,
a.fw-semibold.text-decoration-none:focus-visible { color: var(--helm-text, #1A1D26); }

/* ── Glossary term popovers (Tier 3 #19) ─────────────────────────────── */
/* Inline terms that opt into a definition popover via the
   components/_glossary_term.html macro. Dotted underline signals "there is
   more here" without shouting; matches the help-panel accent on hover. */
.helm-gterm {
  border-bottom: 1px dotted var(--helm-accent, #00BCD4);
  cursor: help;
  text-decoration: none;
  color: inherit;
}
.helm-gterm:hover,
.helm-gterm:focus-visible {
  border-bottom-style: solid;
  color: var(--helm-primary, #1B3A6B);
  outline: none;
}
.helm-gterm:focus-visible {
  outline: 2px solid var(--helm-accent, #00BCD4);
  outline-offset: 2px;
  border-radius: 2px;
}
.helm-gterm-popover .popover-header {
  font-weight: 600;
  color: var(--helm-primary, #1B3A6B);
}
.helm-gterm-popover .popover-body { font-size: 0.875rem; }

/* ==========================================================================
   Shared list-page components (bugs 1276-1278): filter panel, view
   switcher, selection bulk bar, kanban board. Used first on
   /bd-email/enrollments; reusable on any CRM / Pipeline / Outreach /
   Support list page.
   ========================================================================== */

/* Filter panel: teal left edge signals "filters are shaping this list". */
.helm-filter-active {
  border-left: 3px solid var(--helm-accent);
}

/* Selection bulk bar slides in above the list. */
.helm-bulk-bar {
  border-left: 3px solid var(--helm-accent);
  animation: helm-bulk-in 0.18s ease-out;
}
@keyframes helm-bulk-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .helm-bulk-bar { animation: none; }
}

/* Kanban board: horizontally scrollable rail of status columns. */
.helm-kanban { scrollbar-width: thin; }
.helm-kanban-col {
  width: 16.5rem;
  min-height: 12rem;
  background: var(--helm-surface-subtle);
}
.helm-kanban-list { min-height: 6rem; }
.helm-kanban-card {
  border: 1px solid var(--helm-border);
  box-shadow: var(--helm-shadow-sm);
}
.helm-kanban-card[draggable="true"] { cursor: grab; }
.helm-kanban-card[draggable="true"]:active { cursor: grabbing; }
.helm-kanban-drop {
  outline: 2px dashed var(--helm-accent);
  outline-offset: -2px;
}

/* Enrollment tiles keep their footer actions pinned to the bottom. */
.helm-enr-tile .card-footer { border-top: 1px solid var(--helm-border); }

/* ── Phone-viewport content fixes (bug #1249) ──────────────────────────
   Scoped to < 768px so desktop and tablet-landscape rendering is
   untouched. Two recurring phone defects across content pages:
   1. Bootstrap .nav-tabs wraps into a broken multi-row grid on narrow
      screens (intersection detail, section tabs). A tab bar should stay
      one row and scroll sideways inside its own box instead.
   2. Tab links and pill buttons land under the ~44px touch-target floor.
   The thin scrollbar is kept visible as the affordance that more tabs
   exist off-screen. */
@media (max-width: 767.98px) {
  .nav-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
  }
  .nav-tabs .nav-link {
    white-space: nowrap;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
  /* Keep the active tab's bottom border seam intact while scrolling. */
  .nav-tabs .nav-item { flex-shrink: 0; }
}

/* Bug #1249: at very narrow phones (360px Androids) the navbar's right-side
   icon cluster overhung the viewport edge by a few px and clipped the user
   menu's caret. Tighten the icon spacing below 400px; wider phones and all
   desktops keep the standard 1rem gaps. */
@media (max-width: 399.98px) {
  nav.navbar.fixed-top .me-3 { margin-right: 0.5rem !important; }
}

/* CRM provenance: the algorithmic-certainty badge (rt-ai-badge-static,
   crm/_ai_provenance_badge.html) plus the human-verify affordance it can
   carry (Sequences P1 F4c). Shared across contact/organization/opportunity
   detail pages so a "Verify" click reads identically everywhere it appears.
   `verified` on the entry is machine confidence; the badge next to it here
   is a distinct, human fact ("a person confirmed this"). */
.rt-ai-badge-static { color: var(--helm-accent); margin-left: .35rem; cursor: help; }
.rt-prov-field { white-space: nowrap; }
.rt-prov-verified { color: var(--helm-success); margin-left: .2rem; cursor: help; }
.rt-prov-verify-btn {
  appearance: none;
  border: none;
  background: transparent;
  padding: 0 0 0 .35rem;
  margin: 0;
  font-family: inherit;
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--helm-text-muted);
  cursor: pointer;
  border-bottom: 1px dashed var(--helm-text-muted);
  line-height: 1.4;
}
.rt-prov-verify-btn:hover,
.rt-prov-verify-btn:focus-visible {
  color: var(--helm-accent);
  border-bottom-color: var(--helm-accent);
}
.rt-prov-verify-btn:focus-visible { outline: 2px solid var(--helm-accent); outline-offset: 2px; }
.rt-prov-verify-btn.rt-prov-busy { opacity: .55; pointer-events: none; }

/* ── Project-map layers panel: keyboard focus indicators (WCAG 2.4.7) ──
   The collapsible section headers became keyboard-operable (role=button +
   tabindex) and the panel trigger/close are icon buttons. Give them a
   visible focus ring in both themes. Additive + scoped to the layers panel. */
.layer-panel-compact .lp-section-header:focus-visible,
.layer-panel-compact .lp-section-toggle:focus-visible,
.layer-panel-trigger:focus-visible {
  outline: 3px solid var(--helm-primary, #1B3A6B);
  outline-offset: 2px;
  border-radius: 4px;
}
[data-bs-theme="dark"] .layer-panel-compact .lp-section-header:focus-visible,
[data-bs-theme="dark"] .layer-panel-compact .lp-section-toggle:focus-visible,
[data-bs-theme="dark"] .layer-panel-trigger:focus-visible {
  outline-color: var(--helm-accent, #00BCD4);
}

/* ---------------------------------------------------------------------------
   Info-colour contrast, non-button surfaces (night wave 2026-07-28).
   The outline-button fix above handled .btn-outline-info, but the raw
   --bs-info accent (#0dcaf0) is still used as TEXT (.text-info) and as a
   BADGE background (.badge.bg-info) in several places. Measured on white:
       #0dcaf0 as text            1.96:1   (needs 4.5:1)
       white on #0dcaf0 badge     1.96:1   (needs 4.5:1)
   That accounted for the large majority of the 61 color-contrast nodes axe
   reported across /admin/team, /discovery/bids, /marketplace/ and the project
   statistics page. Fixed at the token rather than on each element.

   Light: reuse Bootstrap 5.3's own AA-safe info-emphasis ink (#055160,
   8.93:1 on white). For the badge, keep the recognisable cyan fill and darken
   the INK instead, which preserves the visual language (#055160 on #0dcaf0 is
   4.56:1, clears AA for the badge's normal-size text).
   Dark: #6edff6 on the dark body measures 9.95:1.
   --------------------------------------------------------------------------- */
.text-info {
  color: #055160 !important;
}
[data-bs-theme="dark"] .text-info {
  color: #6edff6 !important;
}
.badge.bg-info,
.badge.text-bg-info {
  color: #055160 !important;
}
[data-bs-theme="dark"] .badge.bg-info,
[data-bs-theme="dark"] .badge.text-bg-info {
  /* Dark theme keeps the same cyan fill, so the ink stays dark for contrast. */
  color: #032830 !important;
}

/* ---------------------------------------------------------------------------
   Data-view cell links (night wave 2026-07-28).
   Links inside a data-view text cell inherited Bootstrap's default link blue
   (#0d6efd). That is 4.50:1 on plain white, right on the AA line, but data-view
   rows sit on a translucent blue tint (rgba(42,120,214,0.12), compositing to
   rgb(229,239,250)) where the same blue measures only 3.87:1. That single pair
   accounted for 56 of the remaining color-contrast nodes on /discovery/bids.
   Repointed at Helm's own primary token, already used for the outline-button
   contrast fix above: #1B3A6B measures 9.69:1 on the tint and 11.27:1 on white.
   --------------------------------------------------------------------------- */
.dv-t-text a,
.dv-row a:not(.btn):not(.badge) {
  color: var(--helm-primary, #1B3A6B);
}
.dv-t-text a:hover,
.dv-row a:not(.btn):not(.badge):hover {
  color: var(--helm-primary, #1B3A6B);
  text-decoration: underline;
}
[data-bs-theme="dark"] .dv-t-text a,
[data-bs-theme="dark"] .dv-row a:not(.btn):not(.badge) {
  /* On the dark surface the light-blue accent is the high-contrast choice. */
  color: #9ec5fe;
}

/* ---------------------------------------------------------------------------
   Semantic badge contrast (night wave 2026-07-28).
   Measured white-on-fill for Bootstrap's badge colours:

       badge          solid      at bg-opacity-75
       success        4.53:1     2.98:1
       danger         4.53:1     3.20:1
       secondary      4.69:1     2.92:1
       primary        4.50:1     3.07:1
       warning        1.63:1     1.46:1
       info           1.96:1     1.70:1

   Two problems. The opacity utilities dilute every fill below AA, and warning
   and info never passed with white text even at full strength. Solid success,
   danger, secondary and primary technically clear 4.5:1 but sit right on the
   line with no margin, so a future tweak silently breaks them.

   Fix mirrors the score-band ink approach: full-strength fill, then ink chosen
   for the fill's luminance. Dark ink on the light fills (warning, info), white
   on the dark ones, and the dark fills deepened slightly so they carry real
   headroom (about 6.5:1) instead of scraping the threshold.
   Scoped to .badge so buttons and other surfaces are untouched.
   --------------------------------------------------------------------------- */
.badge[class*="bg-opacity-"] {
  /* An opacity utility must never dilute a badge below its measured ratio. */
  --bs-bg-opacity: 1;
}
.badge.text-bg-primary, .badge.bg-primary {
  background-color: #0a58ca !important;
  color: #fff !important;
}
.badge.text-bg-success, .badge.bg-success {
  background-color: #146c43 !important;
  color: #fff !important;
}
.badge.text-bg-danger, .badge.bg-danger {
  background-color: #b02a37 !important;
  color: #fff !important;
}
.badge.text-bg-secondary, .badge.bg-secondary {
  background-color: #5c636a !important;
  color: #fff !important;
}
.badge.text-bg-warning, .badge.bg-warning {
  /* Light fill: dark ink measures 10.33:1, white only 1.63:1. */
  color: #1a1d26 !important;
}
.badge.text-bg-info, .badge.bg-info {
  /* Light fill: dark ink measures 8.59:1, white only 1.96:1. */
  color: #055160 !important;
}

/* ---------------------------------------------------------------------------
   Project scope line (bug #1760)

   Under the project name on every project surface: the data collection range
   and the assessment types the client purchased, replacing the workflow
   "Completed" / "Medium" badges a client has no use for. Rendered by
   templates/projects/_project_scope_line.html.

   The 2px left rule matches the statistics sub-nav rule already in the rail,
   so the block reads as panel furniture rather than a new component. Colors
   come from --helm-* tokens, which flip with [data-bs-theme="dark"], so both
   themes stay above 4.5:1 without a second rule set.
   --------------------------------------------------------------------------- */
.helm-project-scope {
  border-left: 2px solid var(--helm-primary, #1B3A6B);
  padding-left: 8px;
  margin-top: 2px;
}
.helm-project-scope-range {
  font-size: 0.74rem;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--helm-text, #1A1D26);
}
.helm-project-scope-types {
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--helm-text-muted, #4B5563);
}

/* ─── Dark-theme text contrast (WCAG 2.1 AA) ──────────────────────────────
   Measured with axe on the live app 2026-07-29. These foreground colours are
   fine on a light surface but fail badly on the dark card (#343a40):

       #595959 -> 1.64:1     #055160 -> 1.29:1     #15803d -> 2.29:1
       #d73027 -> 2.38:1     #198754 -> 2.54:1     #0d6efd -> 2.56:1
       #1a9641 -> 3.00:1     #4caf50 -> 4.14:1

   They are score-band and Bootstrap semantic colours used as TEXT. The band
   colours themselves are the condition scale and must not change, so this
   only lightens them where they are painted as text in dark mode. Every
   replacement below is measured >= 4.5:1 on #343a40.

   Scoped to [data-bs-theme="dark"] so light mode is untouched. */
[data-bs-theme="dark"] .text-muted,
[data-bs-theme="dark"] .helm-text-muted { color: #adb5bd !important; }   /* 5.55:1 */
[data-bs-theme="dark"] .text-danger     { color: #f08a80 !important; }   /* 4.74:1 */
[data-bs-theme="dark"] .text-success    { color: #5fd18a !important; }   /* 6.01:1 */
[data-bs-theme="dark"] .text-primary    { color: #7aa9ff !important; }   /* 4.90:1 */
[data-bs-theme="dark"] .text-info       { color: #7fd7e8 !important; }   /* 7.01:1 */
[data-bs-theme="dark"] .text-warning    { color: #ffc46b !important; }
[data-bs-theme="dark"] a:not(.btn):not(.nav-link):not(.dropdown-item) { color: #7aa9ff; }

/* Bootstrap's .text-info default (#0dcaf0) also fails on WHITE in light mode
   (2.7:1). Darken it there rather than leaving an unreadable label. */
[data-bs-theme="light"] .text-info,
:root:not([data-bs-theme="dark"]) .text-info { color: #0a6c7e !important; }

/* Muted body text token measured 4.38:1 on white, just under AA. */
[data-bs-theme="light"] .helm-text-secondary,
:root:not([data-bs-theme="dark"]) .helm-text-secondary { color: #5b6879 !important; }

/* ─── Ink on band-coloured backgrounds (WCAG 2.1 AA) ──────────────────────
   Measured with axe on the live app 2026-07-29. Templates hardcode
   color:#fff on band-coloured chips, which fails badly: white on the Fair
   amber #fdae61 measures 1.84:1 against a 4.5:1 requirement.

   These rules key off the BACKGROUND being set inline, so the correct ink
   is unambiguous: if an element declares that background, we know exactly
   what its text sits on. That avoids the trap a blanket colour sweep falls
   into, where white text on a legitimately dark surface looks like a
   failure and gets "fixed" into grey.

   Ink values come from app.scoring.band_ink, the single source, which
   guarantees >= 4.5:1. Backgrounds are never altered: the band colours are
   the condition scale and carry meaning. */
[style*="background:#15803d"],
[style*="background: #15803d"],
[style*="background-color:#15803d"],
[style*="background-color: #15803d"],
[style*="BACKGROUND-COLOR: #15803D"] { color: #ffffff !important; }  /* 5.02:1 */
[style*="background:#4caf50"],
[style*="background: #4caf50"],
[style*="background-color:#4caf50"],
[style*="background-color: #4caf50"],
[style*="BACKGROUND-COLOR: #4CAF50"] { color: #1a1d26 !important; }  /* 6.06:1 */
[style*="background:#fee08b"],
[style*="background: #fee08b"],
[style*="background-color:#fee08b"],
[style*="background-color: #fee08b"],
[style*="BACKGROUND-COLOR: #FEE08B"] { color: #1a1d26 !important; }  /* 13.02:1 */
[style*="background:#fdae61"],
[style*="background: #fdae61"],
[style*="background-color:#fdae61"],
[style*="background-color: #fdae61"],
[style*="BACKGROUND-COLOR: #FDAE61"] { color: #1a1d26 !important; }  /* 9.14:1 */
[style*="background:#f46d43"],
[style*="background: #f46d43"],
[style*="background-color:#f46d43"],
[style*="background-color: #f46d43"],
[style*="BACKGROUND-COLOR: #F46D43"] { color: #1a1d26 !important; }  /* 5.70:1 */
[style*="background:#d73027"],
[style*="background: #d73027"],
[style*="background-color:#d73027"],
[style*="background-color: #d73027"],
[style*="BACKGROUND-COLOR: #D73027"] { color: #ffffff !important; }  /* 4.84:1 */
[style*="background:#a50026"],
[style*="background: #a50026"],
[style*="background-color:#a50026"],
[style*="background-color: #a50026"],
[style*="BACKGROUND-COLOR: #A50026"] { color: #ffffff !important; }  /* 7.99:1 */
[style*="background:#1a9641"],
[style*="background: #1a9641"],
[style*="background-color:#1a9641"],
[style*="background-color: #1a9641"],
[style*="BACKGROUND-COLOR: #1A9641"] { color: #12151e !important; }  /* 4.76:1 */
[style*="background:#66bd63"],
[style*="background: #66bd63"],
[style*="background-color:#66bd63"],
[style*="background-color: #66bd63"],
[style*="BACKGROUND-COLOR: #66BD63"] { color: #1a1d26 !important; }  /* 7.23:1 */
[style*="background:#a6d96a"],
[style*="background: #a6d96a"],
[style*="background-color:#a6d96a"],
[style*="background-color: #a6d96a"],
[style*="BACKGROUND-COLOR: #A6D96A"] { color: #1a1d26 !important; }  /* 10.22:1 */
[style*="background:#f59e0b"],
[style*="background: #f59e0b"],
[style*="background-color:#f59e0b"],
[style*="background-color: #f59e0b"],
[style*="BACKGROUND-COLOR: #F59E0B"] { color: #1a1d26 !important; }  /* 7.84:1 */
[style*="background:#b8a600"],
[style*="background: #b8a600"],
[style*="background-color:#b8a600"],
[style*="background-color: #b8a600"],
[style*="BACKGROUND-COLOR: #B8A600"] { color: #1a1d26 !important; }  /* 6.81:1 */
[style*="background:#e07000"],
[style*="background: #e07000"],
[style*="background-color:#e07000"],
[style*="background-color: #e07000"],
[style*="BACKGROUND-COLOR: #E07000"] { color: #1a1d26 !important; }  /* 5.20:1 */

/* ─── Tinted "soft" badges: bg-* plus a bg-opacity-* utility (bug 1798) ────
   Measured with axe on the live app 2026-07-29, light theme:

       chip                                      ink on fill   needs
       .badge.bg-info.bg-opacity-25.text-info      3.10:1       4.5
       .badge.bg-primary.bg-opacity-25.text-primary 1.43:1      4.5
       .badge.bg-success.bg-opacity-25.text-success 1.42:1      4.5
       .badge.bg-secondary.bg-opacity-25.text-secondary 1.30:1  4.5

   Cause, and it is worth naming because the earlier patch caused half of it:
   the `.badge[class*="bg-opacity-"] { --bs-bg-opacity: 1 }` rule above out-
   specifies Bootstrap's `.bg-opacity-25`, so a chip written as a 25% tint was
   actually painted as a SOLID brand fill. The template still asked for the
   matching `.text-*` ink, so the result was brand ink on the same brand hue:
   about 1.3:1. Forcing the fill solid did not fix these chips, it created them.

   Fix: honour the author's intent. A bg-* plus bg-opacity-* combination means
   "soft tint chip", so paint it with Bootstrap 5.3's subtle/emphasis PAIR,
   which is designed as an accessible pairing and, being a CSS variable, flips
   with the theme on its own. No composite arithmetic, no per-theme duplicate,
   and the light-theme result is visually within a hair of the intended tint
   (info at 25% over white composites to #c2f2fb; --bs-info-bg-subtle is
   #cff4fc). Measured on the pair, every one of them clears AAA:

       light  #052c65 on #cfe2ff 10.28   dark  #6ea8fe on #031633  7.45   primary
       light  #0a3622 on #d1e7dd 10.35   dark  #75b798 on #051b11  7.67   success
       light  #58151c on #f8d7da 10.22   dark  #ea868f on #2c0b0e  7.15   danger
       light  #664d03 on #fff3cd  7.21   dark  #ffda6a on #332701 10.82   warning
       light  #055160 on #cff4fc  7.65   dark  #6edff6 on #032830 10.04   info
       light  #2b2f32 on #e2e3e5 10.51   dark  #a7acb1 on #161719  7.84   secondary

   Two classes plus an attribute (0,3,0) beats the .text-* utilities and the
   solid-badge rules above, and this block is last in the file so it also wins
   the specificity TIE against `:root:not([data-bs-theme="dark"]) .text-info`. */
.badge[class*="bg-opacity-"].bg-primary,
.badge[class*="bg-opacity-"].text-bg-primary {
  background-color: var(--bs-primary-bg-subtle) !important;
  color: var(--bs-primary-text-emphasis) !important;
}
.badge[class*="bg-opacity-"].bg-success,
.badge[class*="bg-opacity-"].text-bg-success {
  background-color: var(--bs-success-bg-subtle) !important;
  color: var(--bs-success-text-emphasis) !important;
}
.badge[class*="bg-opacity-"].bg-danger,
.badge[class*="bg-opacity-"].text-bg-danger {
  background-color: var(--bs-danger-bg-subtle) !important;
  color: var(--bs-danger-text-emphasis) !important;
}
.badge[class*="bg-opacity-"].bg-warning,
.badge[class*="bg-opacity-"].text-bg-warning {
  background-color: var(--bs-warning-bg-subtle) !important;
  color: var(--bs-warning-text-emphasis) !important;
}
.badge[class*="bg-opacity-"].bg-info,
.badge[class*="bg-opacity-"].text-bg-info {
  background-color: var(--bs-info-bg-subtle) !important;
  color: var(--bs-info-text-emphasis) !important;
}
.badge[class*="bg-opacity-"].bg-secondary,
.badge[class*="bg-opacity-"].text-bg-secondary {
  background-color: var(--bs-secondary-bg-subtle) !important;
  color: var(--bs-secondary-text-emphasis) !important;
}

/* ─── Band colour painted AS TEXT (bug 1798) ───────────────────────────────
   The block above this one handles ink ON a band background. The opposite case
   is a KPI numeral or a sidebar stat painted IN the band colour on the page
   surface, and several bands fail badly there: Poor #fdae61 as text measures
   1.84:1 on white and the N/A grey #999 measures 2.85:1.

   No single hex can satisfy both themes (a green dark enough for 4.5:1 on white
   is nowhere near 4.5:1 on a dark card, and vice versa), so the template emits
   BOTH inks as custom properties from app.scoring.band_text_style and this rule
   picks per theme. The colours stay in app/scoring.py, the single source: they
   are the band's own hue walked to AA, so a Poor reading is still orange.
   Nothing here hardcodes a band value.

   !important because this class exists for one purpose: to own the element's
   text colour. Page-level <style> blocks load after this file, so a single-class
   rule loses to any page rule of equal or greater weight. On
   /projects/<uid>/intersections the page's `.ix-card .v { color: var(--helm-primary) }`
   (0,2,0) beat `.helm-band-text` (0,1,0) and repainted the value navy at 1.02:1
   even with the custom properties correctly in place. Opting an element in is
   therefore an explicit statement that the band ink wins. */
.helm-band-text { color: var(--band-ink, currentColor) !important; }
[data-bs-theme="dark"] .helm-band-text {
  color: var(--band-ink-dark, var(--band-ink, currentColor)) !important;
}

/* ─── Link colour headroom, light theme (bug 1798) ─────────────────────────
   Bootstrap's default link blue #0d6efd measures exactly 4.50:1 on pure white:
   it passes only on a surface that is literally #ffffff. Every tinted panel in
   the product drops it below AA, e.g. 4.38:1 on the #fcfcfd data-collection
   card and 3.87:1 on the data-view row tint (which is why .dv-row links were
   repointed at the navy token earlier). Rather than keep discovering the same
   node one surface at a time, deepen the link token one step to #0a58ca:
   6.44:1 on white, 6.28:1 on #fcfcfd, 5.75:1 on --helm-surface-subtle. Still
   unmistakably the blue link, now with headroom on any light surface.
   Light only: dark theme already has its own link rule above. */
[data-bs-theme="light"],
:root:not([data-bs-theme="dark"]) {
  --bs-link-color: #0a58ca;
  --bs-link-color-rgb: 10, 88, 202;
  --bs-link-hover-color: #084298;
  --bs-link-hover-color-rgb: 8, 66, 152;
}

/* ─── .table-light headers in dark theme (bug 1798) ────────────────────────
   Bootstrap's .table-light is a fixed light variant: it pins the header row to
   #f8f9fa in BOTH themes. The dark theme still swaps the table ink, so every
   `<thead class="table-light">` in dark mode painted #ced4da onto #f8f9fa:
   1.41:1, measured on /admin/team (six header cells). 46 templates use the
   class, so this was one defect waiting on 46 pages, not an admin quirk.

   dashboard/platform.html already carried this exact fix scoped to .helm-page.
   Promoted here unscoped so it holds sitewide, and left in place there (same
   values, so nothing changes on that page). Bootstrap's own table CSS variables
   are used rather than overriding background-color, so striping, hover and
   border variants keep working. */
[data-bs-theme="dark"] .table-light {
  --bs-table-bg: var(--bs-tertiary-bg);
  --bs-table-color: var(--bs-body-color);
  --bs-table-border-color: var(--bs-border-color);
  --bs-table-striped-bg: var(--bs-secondary-bg);
  --bs-table-striped-color: var(--bs-body-color);
  --bs-table-hover-bg: var(--bs-secondary-bg);
  --bs-table-hover-color: var(--bs-body-color);
  border-color: var(--bs-border-color);
}
