@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Roboto+Mono:wght@400;500;700&display=swap');

/* Brand monospace: Roboto Mono. Overrides Bootstrap so every .font-monospace usage (entity-grid
   tag/ID columns, code, tags) renders in Roboto Mono to match the Roboto body font. */
:root { --bs-font-monospace: 'Roboto Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* FontAwesome Kit (SVG mode) base sizing -- own these rules so they
   survive Blazor enhanced-navigation <head> diffing. The Kit injects
   its own <style> block at runtime; Blazor's enhanced nav can drop
   that node between route swaps, leaving replaced <svg.svg-inline--fa>
   elements at their natural ~92px viewBox size. Owning the canonical
   rule in our static stylesheet keeps icons at 1em regardless. */
.svg-inline--fa {
    display: var(--fa-display, inline-block);
    height: 1em;
    overflow: visible;
    vertical-align: -0.125em;
}
.svg-inline--fa.fa-fw {
    width: 1.25em;
    text-align: center;
}

/* Datalist-backed combobox affordance. <input list="..."> is a native
   typeahead combobox, but browsers render it as a plain text input
   until the user focuses it -- making the picker invisible to the
   operator. We paint a chevron in the same spot a <select> would
   show its caret, and reserve right-padding so the typed value
   doesn't crash into it. Bootstrap's .form-select chevron is the
   reference (inline SVG, currentColor-ish gray). Datalists keep their
   native dropdown behavior, just visually obvious now. */
.form-control[list] {
    padding-right: 2.25rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236c757d' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M2 5l6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.75rem center;
    background-size: 14px 14px;
    cursor: pointer;
}
.form-control[list]:focus {
    cursor: text;
}

:root {
    /* Brand palette (see shared/company/brand/Color Palatte StashStock.png).
       Light Green is the primary brand color; Dark Green for emphasis;
       Limeade for gradations; Peach + Plum + Steel as accents; Iron for
       dark UI. NO BLUE -- not a brand color. */
    --ss-light-green: #0DB14B;
    --ss-light-green-hover: #0a9842;
    --ss-dark-green: #006B42;
    /* Brand Secondary green (per tokens.md / Action Hierarchy spec).
       Used for Action Outline button text -- pairs with the Primary
       border so the outline reads as a brand-green ramp rather than
       a single tone. */
    --ss-secondary: #08642B;
    --ss-limeade: #8CC540;
    --ss-iron: #30373A;
    --ss-peach: #F39332;
    --ss-plum: #6B4796;
    --ss-steel: #ABABAB;
    --ss-white: #ffffff;

    /* Semantic aliases over the brand palette. */
    --ss-green: var(--ss-light-green);
    --ss-green-hover: var(--ss-light-green-hover);
    --ss-green-dark: var(--ss-dark-green);
    --ss-green-subtle: rgba(13, 177, 75, 0.08);
    --ss-charcoal: var(--ss-iron);

    --ss-bg: #eef2f7;
    --ss-bg-card: #ffffff;
    --ss-border: #c7ced9;
    --ss-text: var(--ss-iron);
    --ss-text-secondary: #2d3748;
    --ss-text-muted: #475569;

    /* Risk scale -- compliance signal, color is the message.
       Reds remain (no brand red); compliant = brand green. */
    --ss-risk-critical: #dc2626;
    --ss-risk-high: #ef4444;
    --ss-risk-moderate: var(--ss-peach);
    --ss-risk-minor: #eab308;
    --ss-risk-compliant: var(--ss-light-green);

    /* Legacy amber alias kept so existing component CSS doesn't break;
       value points at brand peach. */
    --ss-amber: var(--ss-peach);
    --ss-amber-subtle: rgba(243, 147, 50, 0.08);
    --ss-peach-subtle: rgba(243, 147, 50, 0.08);
    --ss-plum-subtle: rgba(107, 71, 150, 0.08);

    /* Shadow scale -- ported from the Mobile scanner XAML resource
       dictionary (WeightTile / DetailCard / CardBorder shadow values) so
       cards read the same across Mobile and Web. tokens.md names the
       three tiers (subtle / lifted / floating) but does not yet pin the
       values; this CSS block is the implementation reference. */
    --ss-shadow-sm:  0 1px 3px rgba(0, 0, 0, 0.05);
    --ss-shadow:     0 1px 4px rgba(0, 0, 0, 0.06);
    --ss-shadow-md:  0 2px 4px rgba(0, 0, 0, 0.15);
    /* Feature-card pop. Matches the marketing site's feature-card
       shadow verbatim so portal and marketing cards read the same.
       Reserve for first-class surfaces; nested tiles + matrices stay
       on --ss-shadow. */
    --ss-shadow-pop: 0 8px 28px rgba(15, 30, 35, 0.10);

    /* Border-radius scale. Three sizes + a pill keep every surface
       on the same rounding rhythm without scattering literal pixel
       values across components. Use --ss-radius for buttons and
       general controls, --ss-radius-lg for the data-card chrome
       (filter row, table card, hero surfaces), --ss-radius-sm for
       small inline elements (skeleton blocks, badges with content),
       and --ss-radius-pill for capsule-shaped tags. */
    --ss-radius-sm: 4px;
    --ss-radius: 8px;
    --ss-radius-lg: 12px;
    --ss-radius-pill: 999px;

    /* Brand-tint native form controls (checkbox / radio / range) in
       every modern browser. Single-line replacement for the default
       browser blue. */
    accent-color: var(--ss-light-green);

    /* Height of the portal topbar (.top-row in MainLayout). Anchors
       the sticky filter / action / thead stack on entity-grid pages
       so the operator never loses filtering, actions, or column
       labels while paging through a long virtualized table. */
    --ss-portal-topbar-h: 3.75rem;

    /* === Phase-1 extracted color placeholders ===
       Mechanical extraction of every literal hex / rgba value from the
       authenticated-portal component CSS (MainLayout.razor.css,
       NavMenu.razor.css, and StatusBadge.razor's inline <style>).
       Names are role-based PLACEHOLDERS -- Phase 2 will rename and
       reconcile against tokens.md, fold these into the --ss-* token
       block, and collapse known duplicates. Do not add NEW literal
       colors below this comment in any in-scope file; extend this
       list instead. */

    /* Layout surfaces */
    --c-sidebar-bg:             #2A3942;
    --c-page-bg:                #eef2f7;
    --c-topbar-bg:              #fff;
    --c-topbar-border:          #c7ced9;
    --c-portal-title:           #1a2332;
    --c-facility-text:          #475569;

    /* Sidebar nav overlays (on --c-sidebar-bg = #2A3942).
       WCAG AA requires >= 4.5:1 contrast for normal text (>= 3:1
       for large text). On #2A3942, rgba(255,255,255,0.7) blends to
       ~ #A8AFB2 with ~5.4:1 contrast -- clears AA with margin for
       the muted/dim/section-label tokens used by axe-tested
       elements (nav-section-label headers + nav-logout-btn).
       Bumped from previous values that fell into the 3.5-4.3:1
       band and caught WCAG AA in WebAccessibilityTests. */
    --c-nav-fg:                 rgba(255, 255, 255, 0.75);
    --c-nav-fg-strong:          rgba(255, 255, 255, 0.95);
    --c-nav-fg-muted:           rgba(255, 255, 255, 0.7);
    --c-nav-fg-dim:             rgba(255, 255, 255, 0.7);
    --c-nav-section:            rgba(255, 255, 255, 0.7);
    --c-nav-rule:               rgba(255, 255, 255, 0.06);
    --c-nav-hover-bg:           rgba(255, 255, 255, 0.07);
    --c-nav-band:               rgba(255, 255, 255, 0.045); /* resting fill for section-header bands (sidebar accordion) */
    --c-nav-scrollbar-thumb:       rgba(255, 255, 255, 0.12);
    --c-nav-scrollbar-thumb-hover: rgba(255, 255, 255, 0.22);
    --c-nav-toggler-stroke:     rgba(255, 255, 255, 0.55); /* SVG stroke in data: URL -- can't var() in CSS */
    --c-nav-toggler-border:     rgba(255, 255, 255, 0.1);
    --c-nav-toggler-bg:         rgba(255, 255, 255, 0.1);
    --c-nav-toggler-checked-bg: rgba(255, 255, 255, 0.5);
    --c-nav-active-bg:          rgba(13, 177, 75, 0.22);
    --c-nav-active-accent:      #0DB14B;   /* same hex as --ss-light-green; Phase 2 will collapse */
    --c-nav-active-fg:          #fff;
    --c-nav-soon-fg:            rgba(255, 255, 255, 0.75);
    --c-nav-soon-bg:            rgba(255, 255, 255, 0.15);
    --c-nav-logout-hover-bg:    rgba(239, 68, 68, 0.12);
    --c-nav-logout-hover-fg:    #fca5a5;
    --c-nav-chevron:            rgba(255, 255, 255, 0.4);  /* HeroChevron #66FFFFFF (tokens.md Surface Overlays) */
    --c-nav-flyout-bg:          #334A54;   /* DarkHover (tokens.md Web Hover & Shadow Accents) */

    /* Status badge soft pairs (.ss-badge-soft-* rules below) */
    --c-badge-success-bg:       rgba(13, 177, 75, 0.12);
    --c-badge-success-fg:       #006B42;
    --c-badge-danger-bg:        #fce4ec;
    --c-badge-danger-fg:        #c62828;
    --c-badge-warning-bg:       #fff3e0;
    --c-badge-warning-fg:       #bf360c;
    --c-badge-info-bg:          rgba(107, 71, 150, 0.12);
    --c-badge-info-fg:          #4f1d83;
    --c-badge-peach-bg:         rgba(243, 147, 50, 0.12);
    --c-badge-peach-fg:         #a8540d;
    --c-badge-neutral-bg:       #eef0f3;
    --c-badge-neutral-fg:       #475569; /* same hex as --c-facility-text; Phase 2 will collapse */
    --c-badge-plum-bg:          rgba(107, 71, 150, 0.12); /* same as --c-badge-info-bg; Phase 2 will collapse */
    --c-badge-plum-fg:          #4f1d83;
}

/* Soft badge tints. Global on purpose: pages apply these classes
   directly (TransfersEntityPage direction badges) without rendering a
   StatusBadge, so the rules can't live in a component-embedded <style>
   block -- they'd only exist on pages that happen to render one. */
.ss-badge-soft-success { background-color: var(--c-badge-success-bg); color: var(--c-badge-success-fg); }
.ss-badge-soft-danger  { background-color: var(--c-badge-danger-bg);  color: var(--c-badge-danger-fg); }
.ss-badge-soft-warning { background-color: var(--c-badge-warning-bg); color: var(--c-badge-warning-fg); }
.ss-badge-soft-info    { background-color: var(--c-badge-info-bg);    color: var(--c-badge-info-fg); }
.ss-badge-soft-peach   { background-color: var(--c-badge-peach-bg);   color: var(--c-badge-peach-fg); }
.ss-badge-soft-neutral { background-color: var(--c-badge-neutral-bg); color: var(--c-badge-neutral-fg); }
.ss-badge-soft-plum    { background-color: var(--c-badge-plum-bg);    color: var(--c-badge-plum-fg); }

/* Slide-in panel chrome. Global on purpose (same reason as the badge
   tints above): EntityActionModal renders these for write forms, and
   read-only drill-downs (TransfersEntityPage detail drawer) borrow the
   chrome without rendering an EntityActionModal -- component-embedded
   <style> would only exist on pages that happen to render one. */
.ss-action-modal-backdrop {
    position: fixed; inset: 0;
    background: rgba(15, 23, 42, 0.45);
    z-index: 1040;
}
.ss-action-modal {
    position: fixed; top: 0; right: 0; bottom: 0;
    width: min(520px, 100%);
    background: var(--ss-surface, #fff);
    box-shadow: -8px 0 24px rgba(15, 23, 42, 0.15);
    z-index: 1041;
    display: flex; flex-direction: column;
}
/* Wider variant for detail drawers that show tables. */
.ss-action-modal.ss-action-modal-wide {
    width: min(780px, 100%);
}
.ss-action-modal-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 1rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--ss-border, #e5e7eb);
}
.ss-action-modal-body {
    flex: 1 1 auto; overflow-y: auto;
    padding: 1rem 1.25rem;
}
.ss-action-modal-foot {
    display: flex; justify-content: flex-end; gap: 0.5rem;
    padding: 0.75rem 1.25rem;
    border-top: 1px solid var(--ss-border, #e5e7eb);
    background: var(--ss-surface-subtle, #f8fafc);
}

/* Bootstrap primary -> brand light green. This reaches only what
   Bootstrap resolves from var(--bs-primary*) at RUNTIME: alert-primary,
   text-primary, bg-primary, link-primary, and the utility classes.
   It does NOT reach component-scoped vars -- Bootstrap's Sass froze
   those to literal #0d6efd at build time, so they ignore this remap
   and have to be pinned by name (see the block below).
   --bs-info -> Plum so alert-info reads as a brand-aligned informational
   accent rather than Bootstrap cyan. */
:root {
    --bs-primary: #0DB14B;
    --bs-primary-rgb: 13, 177, 75;
    --bs-primary-bg-subtle: rgba(13, 177, 75, 0.08);
    --bs-primary-border-subtle: rgba(13, 177, 75, 0.25);
    --bs-primary-text-emphasis: #006B42;

    --bs-info: #6B4796;
    --bs-info-rgb: 107, 71, 150;
    --bs-info-bg-subtle: rgba(107, 71, 150, 0.08);
    --bs-info-border-subtle: rgba(107, 71, 150, 0.25);
    --bs-info-text-emphasis: #4f1d83;

    /* Link color is text on light bg -- use brand DARK green for
       WCAG AA contrast. Hover lifts to light green for the cue. */
    --bs-link-color: #006B42;
    --bs-link-color-rgb: 0, 107, 66;
    --bs-link-hover-color: #0DB14B;
    --bs-link-hover-color-rgb: 13, 177, 75;

    /* Form-check checked fill -- Bootstrap defaults to #0d6efd (blue).
       Pin to brand Primary so .form-check-input:checked, the inline
       SVG checkmark mask, and the focus ring all read on-brand. */
    --bs-form-check-bg-color: #0DB14B;
    --bs-form-check-bg-color-rgb: 13, 177, 75;

    /* Bootstrap declares --bs-focus-ring-color on :root, so pinning it
       here does reach every consumer. The other frozen-blue vars are
       declared on their own component selectors and CANNOT be pinned
       from here -- see the component-scope block below. */
    --bs-focus-ring-color: rgba(34, 197, 94, 0.15);
}

/* Frozen-blue Bootstrap vars, pinned AT THEIR COMPONENT SCOPE.
   Bootstrap declares these on the component selector itself
   (.dropdown-menu, .pagination, .nav-pills, .list-group, .progress),
   and a custom property resolves from the NEAREST ancestor that
   declares it -- so the component's own declaration shadows any :root
   value and a :root pin is silently dead. The override has to sit on
   the same selector (or lower) to win.
   Re-check on every Bootstrap upgrade; find stragglers with:
     grep -o "--bs-[a-z0-9-]*:#0d6efd" lib/bootstrap/dist/css/bootstrap.min.css | sort -u
   then confirm the declaring selector with:
     grep -o "[^{}]*{[^}]*--bs-<var>[^}]*}" lib/bootstrap/dist/css/bootstrap.min.css */

/* Bulk-action dropdown items are styled to match the row context menu
   (.ss-ctx-item in RowContextMenu.razor.css): grey text, a quiet grey
   wash for hover, and the SAME wash on press. The context menu has no
   distinct pressed state -- press reads like hover -- so active is
   pinned to the hover pair rather than any accent fill. */
.dropdown-menu {
    --bs-dropdown-link-color: #374151;
    --bs-dropdown-link-hover-color: #374151;
    --bs-dropdown-link-hover-bg: #F5F6F8;
    --bs-dropdown-link-active-color: #374151;
    --bs-dropdown-link-active-bg: #F5F6F8;
    --bs-dropdown-link-disabled-color: #aab0b8;
}

/* Destructive items keep their red label on press, the way
   .ss-ctx-danger does. Bootstrap's .dropdown-item:active rule sets
   color from --bs-dropdown-link-active-color, which would otherwise
   repaint the red label grey for the duration of the press. */
.dropdown-menu .dropdown-item.text-danger:active,
.dropdown-menu .dropdown-item.text-danger.active {
    color: #b02a37;
}

.pagination {
    --bs-pagination-active-bg: #0DB14B;
    --bs-pagination-active-border-color: #0DB14B;
}
.nav-pills {
    --bs-nav-pills-link-active-bg: #0DB14B;
}
.list-group {
    --bs-list-group-active-bg: #0DB14B;
    --bs-list-group-active-border-color: #0DB14B;
}
.progress,
.progress-stacked {
    --bs-progress-bar-bg: #0DB14B;
}

/* Form-control + form-select + form-check focus rings + checked state.
   Bootstrap's default focus puts a blue ring; override at the brand
   green. Border lifts to Primary so the focused control reads as the
   active surface. */
.form-check-input:checked {
    background-color: var(--ss-light-green);
    border-color: var(--ss-light-green);
}
.form-check-input:focus {
    border-color: var(--ss-light-green);
    box-shadow: 0 0 0 0.2rem rgba(13, 177, 75, 0.20);
}
.form-control:focus, .form-select:focus {
    border-color: var(--ss-light-green);
    box-shadow: 0 0 0 0.2rem rgba(13, 177, 75, 0.18);
}

/* Bootstrap component-level overrides. The .btn-* / .alert-* / .nav-pills
   classes inline literal hex values into their own --bs-btn-* / etc.
   vars (compiled from SCSS), so the :root override above doesn't reach
   them; pin the per-component vars to brand green explicitly. */
/* Primary solid = brand Primary green per the style guide's Action
   Hierarchy ("Primary Action: Solid Primary green #0DB14B, white
   text/icon. One per context."). Contrast on the bold 12px+ button
   label is borderline AA -- the guide accepts it for brand fidelity
   and we follow. Hover lifts to GreenHover for visible state change. */
.btn-primary {
    --bs-btn-bg: #0DB14B;
    --bs-btn-border-color: #0DB14B;
    --bs-btn-color: #ffffff;
    --bs-btn-hover-bg: #0a9842;
    --bs-btn-hover-border-color: #0a9842;
    --bs-btn-hover-color: #ffffff;
    --bs-btn-active-bg: #0a9842;
    --bs-btn-active-border-color: #0a9842;
    --bs-btn-active-color: #ffffff;
    --bs-btn-disabled-bg: #0DB14B;
    --bs-btn-disabled-border-color: #0DB14B;
}
.btn-outline-primary {
    --bs-btn-color: #006B42;
    --bs-btn-border-color: #0DB14B;
    --bs-btn-hover-bg: #006B42;
    --bs-btn-hover-border-color: #006B42;
    --bs-btn-hover-color: #ffffff;
    --bs-btn-active-bg: #006B42;
    --bs-btn-active-border-color: #006B42;
    --bs-btn-active-color: #ffffff;
    --bs-btn-disabled-color: #006B42;
    --bs-btn-disabled-border-color: #0DB14B;
}
.btn-info {
    --bs-btn-bg: #6B4796;
    --bs-btn-border-color: #6B4796;
    --bs-btn-hover-bg: #4f1d83;
    --bs-btn-hover-border-color: #4f1d83;
    --bs-btn-color: #ffffff;
    --bs-btn-hover-color: #ffffff;
}
.btn-outline-info {
    --bs-btn-color: #6B4796;
    --bs-btn-border-color: #6B4796;
    --bs-btn-hover-bg: #6B4796;
    --bs-btn-hover-border-color: #6B4796;
}
.btn-success {
    /* Brand dark green for solid success buttons -- 6.7:1 against white. */
    --bs-btn-bg: #006B42;
    --bs-btn-border-color: #006B42;
    --bs-btn-hover-bg: #0a9842;
    --bs-btn-hover-border-color: #0a9842;
    --bs-btn-active-bg: #006B42;
    --bs-btn-active-border-color: #006B42;
}
.btn-outline-success {
    /* Brand dark green text on light bg -- 8.0:1 against #ffffff,
       6.0:1 against page bg #eef2f7. Light green border (decorative). */
    --bs-btn-color: #006B42;
    --bs-btn-border-color: #0DB14B;
    --bs-btn-hover-bg: #006B42;
    --bs-btn-hover-border-color: #006B42;
    --bs-btn-hover-color: #ffffff;
    --bs-btn-active-bg: #006B42;
    --bs-btn-active-border-color: #006B42;
    --bs-btn-active-color: #ffffff;
    --bs-btn-disabled-color: #006B42;
    --bs-btn-disabled-border-color: #0DB14B;
}
.btn-link {
    /* Brand dark green for text-button links -- 6.7:1 contrast.
       Light green #0DB14B fails WCAG AA as link text (2.8:1). */
    --bs-btn-color: #006B42;
    --bs-btn-hover-color: #0DB14B;
}
.alert-primary {
    --bs-alert-color: #006B42;
    --bs-alert-bg: rgba(13, 177, 75, 0.10);
    --bs-alert-border-color: rgba(13, 177, 75, 0.30);
    --bs-alert-link-color: #006B42;
}
.alert-info {
    --bs-alert-color: #4f1d83;
    --bs-alert-bg: rgba(107, 71, 150, 0.10);
    --bs-alert-border-color: rgba(107, 71, 150, 0.30);
    --bs-alert-link-color: #4f1d83;
}
.nav-pills {
    /* Brand dark green for active pill background -- 6.7:1 against
       white text. Light green #0DB14B fails WCAG AA (2.8:1). */
    --bs-nav-pills-link-active-bg: #006B42;
}
.nav-link {
    --bs-nav-link-color: #006B42;
    --bs-nav-link-hover-color: #0DB14B;
}
/* Form-control focus ring uses --bs-primary indirectly via box-shadow
   rgba(--bs-primary-rgb / 0.25). The :root override above is enough
   for those rings to come out brand green. */

html, body {
    /* Single brand family per Web Foundations (tokens.md). Quicksand +
       Inter retired; system stack fallback so the site renders before
       the webfont swap completes. */
    font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    background-color: var(--ss-bg);
    color: var(--ss-text);
    font-size: 15px;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
    font-family: inherit;
    font-weight: 700;
    color: var(--ss-charcoal);
}

/* Fluid h1: floor 1.5rem so a narrow viewport stays readable, ceiling
   2rem so an ultrawide doesn't render a billboard headline. */
h1 { font-size: clamp(1.5rem, 2.2vw, 2rem); margin-bottom: 0.5rem; }
h2 { font-size: 1.4rem; }
h3 { font-size: 1.2rem; }
h5 { font-size: 1.05rem; }

/* Anchor + link-button text color: brand dark green for WCAG AA
   contrast (6.7:1 vs white). Hover lifts to brand light green for
   the cue. --ss-green is light green which fails contrast as text. */
a, .btn-link {
    color: var(--ss-dark-green);
}

a:hover, .btn-link:hover {
    color: var(--ss-light-green);
}

/* --- Buttons --- */
/* Solid color (no gradients — they mask axe contrast failures and visually
   clash with other button variants). --ss-green-dark hits 5.24:1 against
   white, comfortably above WCAG AA for normal text. */
.btn-primary {
    color: #fff;
    background: var(--ss-light-green);
    border: 1px solid var(--ss-light-green);
    font-weight: 600;
    font-size: 0.88rem;
    padding: 0.5rem 1.1rem;
    border-radius: var(--ss-radius);
    box-shadow: var(--ss-shadow-sm);
    transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
}
.btn-primary:hover, .btn-primary:active {
    color: #fff;
    background: var(--ss-light-green-hover);
    border-color: var(--ss-light-green-hover);
    box-shadow: var(--ss-shadow);
}
.btn-primary:disabled, .btn-primary.disabled {
    color: #fff;
    background: var(--ss-light-green);
    border-color: var(--ss-light-green);
    opacity: 0.55;
}

.btn-outline-primary {
    /* Style guide Action Outline: Secondary text (#08642B, ~8.1:1 on
       white) + Primary border (#0DB14B). Hover fills with the same
       lightened-Primary hover color as .btn-primary so the two tiers
       share a consistent hover state when the operator pivots from a
       Create-batch primary to a green-affirmative alternate. Bootstrap
       --bs-btn-* vars pinned so the .btn { color: var(--bs-btn-color) }
       cascade picks this up; otherwise the same-specificity tie can
       leave Bootstrap defaults winning. */
    --bs-btn-color: #08642B;
    --bs-btn-border-color: #0DB14B;
    --bs-btn-hover-bg: #0a9842;
    --bs-btn-hover-border-color: #0a9842;
    --bs-btn-hover-color: #ffffff;
    --bs-btn-active-bg: #0a9842;
    --bs-btn-active-border-color: #0a9842;
    --bs-btn-active-color: #ffffff;
    --bs-btn-disabled-color: #08642B;
    --bs-btn-disabled-border-color: #0DB14B;
    color: var(--ss-secondary);
    border: 1.5px solid var(--ss-light-green);
    font-size: 0.88rem;
    font-weight: 600;
    border-radius: var(--ss-radius);
    padding: 0.5rem 1.1rem;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.btn-outline-primary:hover, .btn-outline-primary:active {
    background: var(--ss-light-green-hover);
    border-color: var(--ss-light-green-hover);
    color: #fff;
}

.btn-outline-secondary {
    font-size: 0.88rem;
    border-radius: var(--ss-radius);
    font-weight: 600;
    /* Bootstrap default #6c757d fails WCAG AA (4.17:1) on --ss-bg. */
    color: #495057; /* 6.6:1 on --ss-bg */
    border-color: #adb5bd;
}
.btn-outline-secondary:hover, .btn-outline-secondary:active {
    color: #fff;
    background: #495057;
    border-color: #495057;
}

/* Destructive Outlined (style guide Action Hierarchy). DestroyRed
   (#d32f2f from tokens.md) fails AA on white at 4.5:1, so use the AA-
   passing #b02a37 (5.9:1 on #f8f9fa). Pin the Bootstrap --bs-btn-*
   vars so Bootstrap's own .btn { color: var(--bs-btn-color) } picks
   the override up; otherwise its 0,1,0-specificity rule fights our
   .btn-outline-danger color rule and the cascade order doesn't help.
   Padding + font-size match .btn-outline-primary so the Destroy
   button sizes the same as its action-bar peers. */
.btn-outline-danger {
    --bs-btn-color: #b02a37;
    --bs-btn-border-color: #b02a37;
    --bs-btn-hover-bg: #b02a37;
    --bs-btn-hover-border-color: #b02a37;
    --bs-btn-hover-color: #ffffff;
    --bs-btn-active-bg: #b02a37;
    --bs-btn-active-border-color: #b02a37;
    --bs-btn-active-color: #ffffff;
    --bs-btn-disabled-color: #b02a37;
    --bs-btn-disabled-border-color: #b02a37;
    color: #b02a37;
    border: 1.5px solid #b02a37;
    font-size: 0.88rem;
    font-weight: 600;
    border-radius: var(--ss-radius);
    padding: 0.5rem 1.1rem;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.btn-outline-danger:hover, .btn-outline-danger:active {
    color: #fff;
    background: #b02a37;
    border-color: #b02a37;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
    box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.15);
}

/* --- Entity-page surface (Variant B grid pattern) ---
   One popped white data card holds the filter row, the action bar
   header, the table thead, and the virtualized rows. Each tier sticks
   to the top of the viewport in order as the document scrolls so the
   operator never loses filtering, actions, or column labels. */

/* Filter row -- top section of the data card. Continues the card
   surface (no shadow / radius of its own) and ends in a 1px rule
   separating it from the action-bar header below. */
.ss-data-card .ss-data-card-filter {
    background: var(--ss-bg-card);
    padding: 0.85rem 1.25rem;
    border-bottom: 1px solid var(--ss-separator);
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    /* Sticky stack tier 1: docks just below the portal topbar. */
    position: sticky;
    top: var(--ss-portal-topbar-h);
    z-index: 4;
    border-top-left-radius: var(--ss-radius-lg);
    border-top-right-radius: var(--ss-radius-lg);
}
.ss-data-card .ss-data-card-filter .ss-filter-label {
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--ss-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding-right: 0.6rem;
    border-right: 1px solid var(--ss-separator);
}
.ss-data-card .ss-data-card-filter .form-select,
.ss-data-card .ss-data-card-filter select {
    min-width: 11rem;
    width: auto;
}
.ss-data-card .ss-data-card-filter .form-label {
    margin-bottom: 0;
}
.ss-data-card {
    background: var(--ss-bg-card);
    border-radius: var(--ss-radius-lg);
    box-shadow: var(--ss-shadow-pop);
    /* NOTE: no `overflow: hidden` here -- it would establish a sticky
       scoping boundary that kills `position: sticky` on the data-card
       header and table thead (they'd scroll out with the data card
       instead of docking below the filter card). The data card has no
       child that visibly bleeds past its rounded corners, so leaving
       overflow visible is safe. */
    margin-bottom: 1rem;
}
/* Round the top corners of whichever element happens to be first
   in the data card -- filter row, tab bar, or a plain header div
   on the Sync & Activity page. Bottom-corner rounding lives on the
   existing :nth-last-child(2) td rule for entity tables; pages
   without that pattern (Sync & Activity) get their bottom rounding
   from the data-card's own background showing through the table's
   transparent cells. No `overflow: hidden` here -- it would establish
   a sticky-scoping boundary and break the sticky thead. */
.ss-data-card > *:first-child {
    border-top-left-radius: var(--ss-radius-lg);
    border-top-right-radius: var(--ss-radius-lg);
}
/* .ss-data-card-header was the sticky action-bar slot. Actions now
   live in the hero's top-right (Create batch + Actions dropdown), so
   the data card has only filter + thead. The class is still emitted
   nowhere; rule kept for back-compat in case other pages use it
   pre-migration. */
.ss-data-card .ss-data-card-header {
    padding: 0.95rem 1.25rem;
    border-bottom: 1px solid var(--ss-separator);
    background: var(--ss-bg-card);
}
.ss-data-card > .table-responsive,
.ss-data-card > table {
    margin-bottom: 0;
}
.ss-data-card table.ss-entity-table { margin-bottom: 0; }
.ss-data-card .ss-data-card-footer {
    padding: 0.85rem 1.25rem;
    border-top: 1px solid var(--ss-separator);
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
/* Auto layout + a min-width per cell so wide tables grow beyond the
   viewport instead of squashing columns. The page (not a wrapper)
   scrolls horizontally when this happens, which preserves
   `position: sticky` on the thead -- a wrapper with overflow-x: auto
   would scope sticky to itself and break docking. The existing
   ss-cell-truncate* max-widths still cap truly-long content. */
.ss-data-card .ss-entity-table {
    table-layout: auto;
    width: auto;
    min-width: 100%;
}
.ss-data-card .ss-entity-table > thead > tr > th,
.ss-data-card .ss-entity-table > tbody > tr > td {
    /* Uniform 200px column floor so wide tables overflow horizontally
       (inside the .ss-grid-card-body viewport) instead of cramping
       column content. Every column reads as a stable 200px block;
       the table is browsed by horizontal scroll, not column squash. */
    min-width: 200px;
    white-space: nowrap;
    text-align: left;
}
.ss-data-card .ss-entity-table > thead > tr > th:first-child,
.ss-data-card .ss-entity-table > tbody > tr > td:first-child {
    /* Leading checkbox / single-cell columns stay tight. */
    min-width: 2.5rem;
}


/* ── Loading states ─────────────────────────────────────────
   One consistent loading treatment across the entity-grid pages:
   pulsing solid-fill skeleton blocks (no gradient -- brand rule)
   and a brand-green tint for the inline spinner Bootstrap renders
   inside Refresh + modal submit buttons. */

.ss-skeleton {
    display: inline-block;
    background-color: #e2e8f0;
    border-radius: var(--ss-radius-sm);
    animation: ss-skeleton-pulse 1.4s ease-in-out infinite;
}
.ss-skeleton-text {
    height: 0.85rem;
    width: 70%;
    vertical-align: middle;
}
.ss-skeleton-value {
    height: 1.75rem;
    width: 60%;
    vertical-align: middle;
}
@keyframes ss-skeleton-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.55; }
}
@media (prefers-reduced-motion: reduce) {
    .ss-skeleton {
        animation: none;
        opacity: 0.7;
    }
}

/* Brand-green tint for the small Bootstrap spinner used inline on
   Refresh buttons + modal submit buttons. spinner-border uses
   currentColor for the border, so .ss-spinner just sets a color. */
.ss-spinner {
    color: var(--ss-light-green);
}

/* Tab bar at top of data card. Used on Locations to switch between
   the Locations and Sublocations flat grids. When present, takes over
   the rounded top corners; the filter row below loses its top-radius
   via the adjacent-sibling rule below. Sticky tier 0 -- docks just
   under the portal topbar; the filter row then docks below the tab
   bar (offset bumped via the adjacent-sibling override below). */
.ss-data-card .ss-tab-bar {
    background: var(--ss-bg-card);
    padding: 0 1.25rem;
    border-bottom: 1px solid var(--ss-separator);
    display: flex;
    gap: 0;
    position: sticky;
    top: var(--ss-portal-topbar-h);
    z-index: 5;
    border-top-left-radius: var(--ss-radius-lg);
    border-top-right-radius: var(--ss-radius-lg);
}
.ss-data-card .ss-tab-bar + .ss-data-card-filter {
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    top: calc(var(--ss-portal-topbar-h) + 2.75rem);
}
.ss-data-card .ss-tab-bar ~ * .ss-entity-table > thead {
    /* tab bar (2.75) + filter (4.2) -- thead stacks below both. */
    top: calc(var(--ss-portal-topbar-h) + 2.75rem + 4.2rem);
}
.ss-tab {
    background: none;
    border: 0;
    padding: 0.7rem 1rem;
    color: var(--ss-text-muted);
    font-size: 0.85rem;
    font-weight: 500;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}
.ss-tab:hover:not(.active) {
    color: var(--ss-text-dark);
}
.ss-tab.active {
    color: var(--ss-text-dark);
    font-weight: 600;
    border-bottom-color: var(--ss-light-green);
}
/* Entity pages switch views asynchronously; tabs disable while the
   new view's slice loads. Keep the active tab readable. */
.ss-tab:disabled {
    cursor: default;
    opacity: 0.55;
}
.ss-tab.active:disabled {
    opacity: 0.8;
}
.ss-tab-count {
    display: inline-block;
    padding: 0.05rem 0.5rem;
    margin-left: 0.4rem;
    border-radius: var(--ss-radius-pill);
    background: var(--ss-separator);
    color: var(--ss-text-muted);
    font-size: 0.72rem;
    font-weight: 600;
}
.ss-tab.active .ss-tab-count {
    background: var(--ss-green-subtle);
    color: var(--ss-light-green);
}

/* Page-scope view toggle (Active / Inactive / In transit) in the
   dashboard header, next to Refresh. Reuses the .ss-tab buttons but
   as a compact segmented control: the in-card underline pattern read
   as table chrome while the toggle actually swaps the whole page's
   dataset (KPIs included), so it lives at page level. The card tab
   bar remains for genuine table-content tabs (Locations). */
.ss-view-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
    height: 2.1rem;
    padding: 0.18rem;
    background: var(--ss-bg);
    border: 1px solid var(--ss-separator);
    border-radius: var(--ss-radius-pill);
}
.ss-view-toggle .ss-tab {
    align-self: stretch;
    display: inline-flex;
    align-items: center;
    padding: 0 0.85rem;
    margin-bottom: 0;
    border: 0;
    border-radius: var(--ss-radius-pill);
    font-size: 0.83rem;
    line-height: 1.2;
}
.ss-view-toggle .ss-tab.active {
    background: var(--ss-bg-card);
    color: var(--ss-text-dark);
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12);
    border-bottom-color: transparent;
}
/* Inactive-window select beside the toggle: same height + radius as
   the pill group, and an explicit right padding reserved for
   Bootstrap's chevron image so it never overlaps the option label. */
.ss-view-window.form-select {
    width: auto;
    height: 2.1rem;
    padding: 0.25rem 2.1rem 0.25rem 0.85rem;
    font-size: 0.83rem;
    line-height: 1.2;
    border-radius: var(--ss-radius-pill);
    border-color: var(--ss-separator);
    background-color: var(--ss-bg);
    background-position: right 0.8rem center;
}

/* Row breathing room. Bootstrap's .table-sm gives ~3.75px vertical
   padding which crushed rows against each other in a popped data
   card. Bump to 0.7rem so the data has air, scoped to .ss-entity-table
   inside .ss-data-card so dashboard matrices keep their tighter
   matrix-grid feel. */
.ss-data-card .ss-entity-table > tbody > tr > td,
.ss-data-card .ss-entity-table > thead > tr > th {
    padding-top: 0.7rem;
    padding-bottom: 0.7rem;
}
/* Sortable column headers are clickable -- block text selection so a
   click registers as the sort intent instead of selecting the column
   label. Applied to every th in the entity table since header labels
   aren't meant to be selected anyway. */
.ss-data-card .ss-entity-table > thead > tr > th {
    user-select: none;
    -webkit-user-select: none;
}
/* Sticky thead -- docks to the top of the .ss-grid-card-body scroll
   viewport (or, for catalog-style tables that aren't inside an
   ss-grid-card, to the page viewport at top: 0). Round 6 moved
   horizontal scroll inside the card, which scopes sticky to the
   scroll viewport instead of the page. */
.ss-data-card .ss-entity-table > thead {
    position: sticky;
    top: 0;
    z-index: 2;
}
.ss-data-card .ss-entity-table > thead > tr > th {
    background: var(--ss-bg-card);
    box-shadow: inset 0 -1px 0 var(--ss-separator);
}
/* Entity-grid Actions dropdown menu polish. Fixed-width icon column
   so labels align vertically; subtle separator between rows + a
   bit more vertical padding so items don't feel cramped. Scoped to
   .ss-data-card-filter so other dropdowns in the app aren't
   affected. */
.ss-data-card-filter .dropdown-menu .dropdown-item {
    display: flex;
    align-items: center;
    padding: 0.6rem 1rem;
}
/* Divider rule lives on the <li> wrapper, not the .dropdown-item
   button. Bootstrap's reset zeroes the .dropdown-item border in a
   way that beats even !important on certain browsers; putting the
   rule on the <li> sidesteps that entirely. The :not(:last-child)
   filter drops the rule on the final item so the menu's bottom edge
   doesn't double up with the dropdown-menu's outer border. */
.ss-data-card-filter .dropdown-menu > li:not(:last-child) {
    border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
.ss-data-card-filter .dropdown-menu .dropdown-item > i {
    width: 1.25rem;
    text-align: center;
    margin-right: 0.75rem;
    flex-shrink: 0;
    font-size: 0.92em;
}
/* The explicit <hr class="dropdown-divider"> before Destroy stays as
   a stronger visual break; tighten its margins so it sits inline
   with the per-item rules. */
.ss-data-card-filter .dropdown-menu .dropdown-divider {
    margin: 0;
}

/* Bottom corners of the data card -- since the card has no
   overflow:hidden (would kill the sticky filter + thead), the table
   itself doesn't get clipped by the card's border-radius. Round the
   last DATA row's first/last cells so the visible bottom edge
   matches the card's rounded outline. We target :nth-last-child(2)
   because Blazor's <Virtualize SpacerElement="tr"> appends an
   aria-hidden tr (the bottom spacer) as the literal last child, so
   :last-child lands on the empty spacer and the radius hits nothing. */
/* Per-row corner rounding rule removed in round 7 -- grid pages
   now round the bottom corners of `.ss-grid-card-body` itself: the
   body is the scroll container, so it clips its content to its own
   rounded padding box regardless of where the last visible row is
   in the scrolling body. (The card must NOT go back to
   `overflow: hidden` -- that clipped the toolbar Actions dropdown
   and GridFilters menus on short grids.) The legacy
   nth-last-child(2) approach only worked when the entire table was
   visible; once the body started scrolling the "last visible row"
   was somewhere in the middle and the rounding never showed. */
/* Pad the first/last cells of an entity table inside a data card so
   the checkbox column + the rightmost data column don't sit flush to
   the popped-card edge. */
.ss-data-card .ss-entity-table > thead > tr > th:first-child,
.ss-data-card .ss-entity-table > tbody > tr > td:first-child {
    padding-left: 1.25rem;
}
.ss-data-card .ss-entity-table > thead > tr > th:last-child,
.ss-data-card .ss-entity-table > tbody > tr > td:last-child {
    padding-right: 1.25rem;
}
/* Brand-tinted row states. Bootstrap's .table-active is a neutral
   grey; .table-hover paints a slightly darker grey. Both swap to
   brand-green tints so the entity grid matches the rest of the
   portal's accent rhythm. */
.ss-data-card .ss-entity-table > tbody > tr.table-active > * {
    background-color: rgba(13, 177, 75, 0.10);
    color: inherit;
    --bs-table-bg-state: transparent;
}
.ss-data-card .ss-entity-table.table-hover > tbody > tr:hover > * {
    background-color: var(--ss-tile-bg);
    color: inherit;
    --bs-table-bg-state: transparent;
}
.ss-data-card .ss-entity-table.table-hover > tbody > tr.table-active:hover > * {
    background-color: rgba(13, 177, 75, 0.14);
    color: inherit;
    --bs-table-bg-state: transparent;
}
/* Ellipsis-truncated cells. Use on <td> elements whose content may
   exceed the column's reasonable width (long mono IDs, long strain
   names). Pair with title="@r.Field" so the full value is reachable
   on hover. The max-width is what the column actually settles at. */
.ss-data-card .ss-entity-table .ss-cell-truncate {
    max-width: 16rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ss-data-card .ss-entity-table .ss-cell-truncate-sm {
    max-width: 10rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* --- Cards ---
   Top-level dashboard cards adopt the marketing-site feature-card pop:
   no border stroke, 12px radius, --ss-shadow-pop drop. Pattern still
   echoes the Mobile scanner DetailCard (no stroke, soft radius) but
   the shadow is wider/softer to read as a first-class surface against
   the page bg. Inner tiles and matrices use the lighter --ss-shadow
   tier so they don't compete. */
.card {
    border-radius: var(--ss-radius-lg);
    border: 0;
    box-shadow: var(--ss-shadow-pop);
    background: var(--ss-bg-card);
    transition: box-shadow 0.15s, transform 0.15s;
}
.card-header {
    border-top-left-radius: var(--ss-radius-lg);
    border-top-right-radius: var(--ss-radius-lg);
    font-family: inherit;
    font-weight: 700;
}

/* --- Content area --- */
.content {
    padding-top: 1.25rem;
}

h1:focus {
    outline: none;
}

/* --- Forms --- */
.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--ss-green);
}

.invalid {
    outline: 1px solid #ef4444;
}

.validation-message {
    color: #ef4444;
}

.form-control, .form-select {
    font-size: 0.9rem;
    border-radius: var(--ss-radius);
    border: 1px solid var(--ss-border);
    padding: 0.5rem 0.75rem;
    transition: border-color 0.15s, box-shadow 0.15s;
}
.form-control:focus, .form-select:focus {
    border-color: var(--ss-green);
}

/* ── Native date / datetime inputs ──────────────────────────
   23+ <input type="date"> instances across the portal inherit
   .form-control. Bootstrap doesn't tint the calendar-picker
   indicator or unify the focus ring, so we patch that here.
   The native popup itself is browser chrome and intentionally
   left alone -- cross-browser restyling is brittle. */
input[type="date"].form-control,
input[type="datetime-local"].form-control {
    border-radius: var(--ss-radius);
}
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
    cursor: pointer;
    opacity: 0.55;
    transition: opacity 120ms ease;
    filter: invert(35%) sepia(20%) saturate(800%) hue-rotate(80deg);
}
input[type="date"]:hover::-webkit-calendar-picker-indicator,
input[type="datetime-local"]:hover::-webkit-calendar-picker-indicator,
input[type="date"]:focus::-webkit-calendar-picker-indicator,
input[type="datetime-local"]:focus::-webkit-calendar-picker-indicator {
    opacity: 1;
}
input[type="date"].form-control:focus,
input[type="datetime-local"].form-control:focus {
    border-color: var(--ss-green);
    box-shadow: 0 0 0 0.2rem rgba(13, 177, 75, 0.18);
}

/* ── Grid pill filter bar ───────────────────────────────────
   Shared pill chrome lifted from the ComplianceForge Reports
   page so the entity grids and the Reports page share one
   filter UX. Pills ramp from outline (inactive) to filled
   (active); a count chip and a "Clear all" affordance appear
   when any filter is set. */

.ss-grid-filter-bar {
    background: var(--ss-bg-card);
    border-bottom: 1px solid var(--ss-separator, #E8EAEF);
    /* Asymmetric vertical padding: a small top pad pairs with the
       trimmed search row pad-bottom below so the gap from search-input
       down to the pills (search pad-bot + border + bar pad-top)
       matches the gap from the pills down to the table (bar pad-bot +
       border). Horizontal padding matches .ss-data-card-filter. */
    padding: 0.4rem 1.25rem 0.85rem;
}
/* When a grid card has a pill bar below the search row, trim the
   search row's bottom padding so the visual "between rows" gap
   matches the gap below the pills. Scoped via :has() so pages that
   show .ss-data-card-filter without a pill bar (none today, but
   defensively) keep their original padding. */
.ss-data-card .ss-data-card-filter:has(+ .ss-grid-filter-bar) {
    padding-bottom: 0.4rem;
}
.ss-grid-filter-bar .ss-grid-filter-status {
    /* Filtered/total counts, inline with the filter pills. Muted
       small text, right-aligned via ms-auto in markup. */
    font-size: 0.8rem;
    color: var(--ss-text-muted);
}
.ss-grid-filter-bar .ss-grid-filter-status strong {
    color: var(--ss-text-dark);
    font-weight: 600;
}

/* ── Filter pill ──────────────────────────────────────────────
   One unit per active filter: toggle label + value on the left,
   inline X remove on the right, shared rounded chrome. Clicking
   the toggle opens the value picker below. Razor owns the open
   state (see GridFilters.razor) so Blazor re-renders can't strip
   a Bootstrap-applied `show` class out from under the menu. */
.ss-grid-filter-pill {
    display: inline-flex;
    align-items: stretch;
    position: relative;
    border-radius: var(--ss-radius-pill);
    border: 1px solid var(--ss-border, #D1D5DE);
    background: var(--ss-bg-card, #fff);
    color: var(--ss-text-dark, #374151);
    font-size: 0.85rem;
    line-height: 1.4;
}
.ss-grid-filter-pill:hover {
    border-color: var(--ss-light-green, #0DB14B);
}
.ss-grid-filter-pill--active {
    background: var(--ss-light-green, #0DB14B);
    border-color: var(--ss-light-green, #0DB14B);
    color: #fff;
}
.ss-grid-filter-pill--active:hover {
    border-color: var(--ss-light-green, #0DB14B);
}
.ss-grid-filter-pill-toggle {
    background: transparent;
    border: 0;
    padding: 0.3rem 0.75rem;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: inherit;
    font-weight: 500;
    font-size: 0.85rem;
    line-height: 1.4;
    border-top-left-radius: var(--ss-radius-pill);
    border-bottom-left-radius: var(--ss-radius-pill);
}
.ss-grid-filter-pill-toggle:hover {
    background: rgba(0, 0, 0, 0.04);
}
.ss-grid-filter-pill--active .ss-grid-filter-pill-toggle:hover {
    background: rgba(0, 0, 0, 0.08);
}
.ss-grid-filter-pill-toggle::after {
    /* caret */
    content: '';
    display: inline-block;
    margin-left: 0.2rem;
    vertical-align: 0.2em;
    border-top: 0.3em solid currentColor;
    border-right: 0.3em solid transparent;
    border-bottom: 0;
    border-left: 0.3em solid transparent;
    opacity: 0.6;
}
.ss-grid-filter-pill-label {
    font-weight: 500;
    opacity: 0.75;
}
.ss-grid-filter-pill--active .ss-grid-filter-pill-label {
    /* Brighten the label prefix on green so it isn't washed out. */
    color: #fff;
    opacity: 0.95;
}
.ss-grid-filter-pill--active .ss-grid-filter-pill-value {
    color: #fff;
    font-weight: 600;
}
.ss-grid-filter-pill-remove {
    background: transparent;
    border: 0;
    border-left: 1px solid var(--ss-border, #D1D5DE);
    padding: 0.3rem 0.55rem;
    color: inherit;
    opacity: 0.6;
    font-size: 0.8rem;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    border-top-right-radius: var(--ss-radius-pill);
    border-bottom-right-radius: var(--ss-radius-pill);
}
.ss-grid-filter-pill--active .ss-grid-filter-pill-remove {
    border-left-color: rgba(255, 255, 255, 0.35);
    opacity: 0.85;
}
.ss-grid-filter-pill-remove:hover {
    background: rgba(220, 53, 69, 0.12);
    color: #dc3545;
    opacity: 1;
}
.ss-grid-filter-pill--active .ss-grid-filter-pill-remove:hover {
    background: rgba(255, 255, 255, 0.2);
    color: #fff;
    opacity: 1;
}
.ss-grid-filter-pill-menu {
    /* Razor-managed value picker. Conditionally rendered when the
       pill is open, so Blazor never strips a Bootstrap `show` class
       between renders. */
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 1000;
    min-width: 14rem;
    background: var(--ss-bg-card);
    border: 1px solid var(--ss-border, #D1D5DE);
    border-radius: var(--ss-radius);
    box-shadow: var(--ss-shadow-md);
    padding: 0.5rem;
    font-size: 0.85rem;
    color: var(--ss-text-dark);
}
.ss-grid-filter-pill-menu .list-group-item-action {
    border: none;
    border-radius: 0.375rem;
    padding: 0.35rem 0.55rem;
    margin: 0.05rem 0;
}
/* Deliberately quieter than the global --bs-list-group-active-bg solid
   Primary fill: a filter picker can have many selected options at once,
   and solid green blocks make the list unreadable. Not redundant. */
.ss-grid-filter-pill-menu .list-group-item-action.active {
    background: var(--ss-green-subtle, rgba(13, 177, 75, 0.12));
    color: var(--ss-secondary, #08642B);
    font-weight: 500;
}
.ss-grid-filter-pill-menu .list-group-item-action:hover {
    background: var(--ss-bg, #f3f4f6);
}
.ss-grid-filter-pill-menu .form-control,
.ss-grid-filter-pill-menu .form-select {
    border-radius: var(--ss-radius);
}

/* "+ Add filter" trigger -- dashed pill chrome so it reads as a
   placeholder slot rather than an active filter. */
.ss-grid-filter-add {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    position: relative;
}
.ss-grid-filter-add-toggle {
    background: transparent;
    border: 1px dashed var(--ss-border, #D1D5DE);
    border-radius: var(--ss-radius-pill);
    padding: 0.3rem 0.85rem;
    font-size: 0.85rem;
    line-height: 1.4;
    color: var(--ss-text-muted);
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-weight: 500;
}
.ss-grid-filter-add-toggle:hover {
    border-style: solid;
    border-color: var(--ss-light-green, #0DB14B);
    color: var(--ss-secondary, #08642B);
}
.ss-grid-filter-add-menu {
    /* Same Razor-managed pattern as the pill menus. */
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 1000;
    min-width: 14rem;
    background: var(--ss-bg-card);
    border: 1px solid var(--ss-border, #D1D5DE);
    border-radius: var(--ss-radius);
    box-shadow: var(--ss-shadow-md);
    padding: 0.25rem;
    font-size: 0.85rem;
    color: var(--ss-text-dark);
}
.ss-grid-filter-add-menu .ss-grid-filter-add-item {
    display: block;
    width: 100%;
    text-align: left;
    background: transparent;
    border: 0;
    padding: 0.4rem 0.6rem;
    border-radius: 0.375rem;
    color: inherit;
    font-size: 0.85rem;
}
.ss-grid-filter-add-menu .ss-grid-filter-add-item:hover {
    background: var(--ss-bg, #f3f4f6);
}
.ss-grid-filter-add-menu .ss-grid-filter-add-item.disabled {
    color: var(--ss-text-muted);
    cursor: default;
}
.ss-grid-filter-add-menu .ss-grid-filter-add-item.disabled:hover {
    background: transparent;
}

.ss-grid-filter-bar > button.btn-link {
    /* "Clear all" affordance */
    font-size: 0.8rem;
    font-weight: 500;
    text-decoration: none;
}

/* ── Grid card shell ─────────────────────────────────────────
   Entity grid pages opt in via `.ss-data-card.ss-grid-card`. The
   card is a flex column that grows to fit the table up to the
   article's remaining height. Past that it caps and the body
   scrolls internally. Overflow stays visible (matching the base
   .ss-data-card) so absolute popovers inside the card -- the
   toolbar Actions dropdown and the GridFilters pill / add-filter
   menus -- can extend past a short card (1-row grids) instead of
   being clipped at its bottom edge. Bottom-corner rounding lives
   on .ss-grid-card-body's own border-radius: a scroll container
   clips its content to its rounded padding box, so both bottom
   corners stay rounded even mid-scroll. */
/* Grid cards scroll inside their own body, so the legacy
   sticky-everywhere chrome inside .ss-data-card-filter
   (`position: sticky; top: var(--ss-portal-topbar-h)`) no longer
   has a meaningful scroll container and just overlaps the pill bar
   underneath it. Reset to static flow inside grid cards. */
.ss-data-card.ss-grid-card .ss-data-card-filter {
    position: static;
}
/* Tab-bar sticky was scoped to the page (top: --ss-portal-topbar-h).
   Inside a grid card with overflow:hidden the sticky activates and
   offsets the tab-bar down by the topbar height, painting over the
   filter row below it. Static flow inside grid cards. */
.ss-data-card.ss-grid-card .ss-tab-bar {
    position: static;
}
/* Tab-bar variant of the sticky thead offset uses a tall calc() top
   (topbar + tab + filter heights) baked for the page-level scroll
   container. Inside the grid-card-body scroll viewport, dock the
   thead at top: 0 just like the no-tab case. */
.ss-data-card.ss-grid-card .ss-tab-bar ~ * .ss-entity-table > thead {
    top: 0;
}

/* Manage catalog pages wrap their body in .ss-manage-page (test
   marker the E2E suite scopes all interactions to). The wrapper
   has to pass the flex sizing chain through from .content (which
   becomes a flex column via :has(.ss-grid-card)) down to the
   inner .ss-data-card.ss-grid-card -- otherwise max-height: 100%
   on the grid card resolves against an unconstrained block and
   the table fills the page instead of capping at the viewport
   with inner scroll. */
.ss-manage-page {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}
.ss-data-card.ss-grid-card {
    display: flex;
    flex-direction: column;
    /* flex: 0 1 auto -- size to content; can shrink (flex-shrink: 1)
       when capped by the parent. NOT flex-grow: 1 -- that would
       always claim the whole article, leaving empty space below
       short tables. max-height: 100% pins the cap to the parent's
       (.content) computed height when content would otherwise
       overflow. */
    flex: 0 1 auto;
    max-height: 100%;
    min-height: 0;
    margin-bottom: 0;
    /* NOT hidden -- popovers must escape a short card; see shell
       comment above. */
    overflow: visible;
}
.ss-data-card.ss-grid-card .ss-grid-card-body {
    /* Body sizes to its table (short tables -> no scroll). When the
       card is capped by max-height, this body absorbs the shortfall
       (flex-shrink: 1, min-height: 0) and overflow: auto turns it
       into the scroll surface. The bottom radius clips row edges /
       hover fills to the card's rounded corners (the card itself no
       longer clips -- its overflow is visible). */
    flex: 0 1 auto;
    min-height: 0;
    overflow: auto;
    border-bottom-left-radius: var(--ss-radius-lg);
    border-bottom-right-radius: var(--ss-radius-lg);
}
/* The body carries tabindex="0" so the grid's @onkeydown (Ctrl+A select-all,
   Escape clear) can fire, which means row clicks focus it. Suppress the
   browser's default black ring for pointer focus; keep an intentional brand
   ring for keyboard (Tab) focus so a11y isn't lost. */
.ss-data-card.ss-grid-card .ss-grid-card-body:focus:not(:focus-visible) {
    outline: none;
}
.ss-data-card.ss-grid-card .ss-grid-card-body:focus-visible {
    outline: 2px solid var(--ss-light-green);
    outline-offset: -2px;
}
.ss-data-card.ss-grid-card .ss-grid-card-body > table {
    margin-bottom: 0;
}
/* Flush empty-state alert ("No X match your filter") rendered inside
   a data card. Continues the card surface: no side/bottom borders, no
   radius of its own (the top border is the separator under the filter
   row). When it is the grid card's LAST child it forms the card's
   bottom edge, so round its bottom corners to the card outline --
   the card's overflow is visible and won't clip them. */
.alert.ss-card-flush-alert {
    border-radius: 0;
    border-left: 0;
    border-right: 0;
    border-bottom: 0;
    margin-bottom: 0;
}
.ss-data-card.ss-grid-card > .ss-card-flush-alert:last-child {
    border-bottom-left-radius: var(--ss-radius-lg);
    border-bottom-right-radius: var(--ss-radius-lg);
}

/* --- Small / meta label (single source of truth for ghost section labels) --- */
.text-meta {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ss-text-muted);
}
.text-meta > code, code.text-meta { text-transform: none; letter-spacing: 0; }

/* --- Row expand toggle (was a bare span, now a real button) --- */
.btn-expand {
    background: transparent;
    border: 1px solid transparent;
    color: var(--ss-text-muted);
    padding: 0.1rem 0.4rem;
    font-size: 0.9rem;
    line-height: 1;
    border-radius: var(--ss-radius-sm);
    cursor: pointer;
}
.btn-expand:hover { background: var(--ss-bg); color: var(--ss-text); }
.btn-expand:focus-visible { outline: 2px solid var(--ss-green-dark); outline-offset: 1px; }

/* --- Active/Inactive pressed-toggle button (was btn-sm masquerading as a badge) --- */
.btn-toggle {
    font-size: 0.75rem;
    font-weight: 600;
    padding: 0.15rem 0.6rem;
    border-radius: var(--ss-radius-pill);
    border: 1px solid;
    line-height: 1.3;
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.btn-toggle[aria-pressed="true"] {
    background: var(--ss-green-dark);
    border-color: var(--ss-green-dark);
    color: #fff;
}
.btn-toggle[aria-pressed="false"] {
    background: #fff;
    border-color: #adb5bd;
    color: #495057;
}
.btn-toggle:hover[aria-pressed="true"]  { background: #166534; border-color: #166534; }
.btn-toggle:hover[aria-pressed="false"] { background: var(--ss-bg); }
.btn-toggle:focus-visible { outline: 2px solid var(--ss-green-dark); outline-offset: 1px; }

/* --- Health status dots --- */
.health-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-right: 4px;
    vertical-align: middle;
}
.health-dot.red   { background: var(--ss-risk-critical); }
.health-dot.amber { background: var(--ss-amber); }

/* --- Subscription manager tabs --- */
.nav-tabs .nav-link {
    color: var(--ss-text-muted);
    font-weight: 600;
    font-size: 0.9rem;
    border: none;
    border-bottom: 2px solid transparent;
    padding: 0.5rem 1rem;
}
.nav-tabs .nav-link.active {
    color: #0f6b31;
    border-bottom-color: var(--ss-green);
    background: transparent;
}
.nav-tabs .nav-link:hover:not(.active) {
    color: var(--ss-text);
    border-bottom-color: var(--ss-border);
}

/* --- Badges --- */
.badge {
    font-weight: 600;
    font-size: 0.8rem;
    padding: 0.3em 0.65em;
    border-radius: 6px;
}
/* .bg-success white-on-#22c55e is 2.27:1 — fails AA. Darker green passes. */
.badge.bg-success { background-color: var(--ss-green-dark) !important; color: #fff !important; }
/* .bg-warning yellow needs dark text to pass, not white. */
.badge.bg-warning { background-color: var(--ss-risk-minor) !important; color: #1e293b !important; }
.badge.bg-danger  { background-color: var(--ss-risk-high) !important; color: #fff !important; }

/* Green foreground on white: #22c55e fails 2.27:1, use green-dark instead. */
.text-success { color: var(--ss-green-dark) !important; }

/* Bootstrap <code> pink #d63384 on #f8f9fa = 4.26:1, just under AA. Darken. */
code { color: #b02a5b; }

/* --- Risk score ring --- */
.risk-score {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 96px;
    height: 96px;
    border-radius: 50%;
    border: 5px solid var(--ss-border);
    font-family: inherit;
    font-size: 2rem;
    font-weight: 700;
    color: var(--ss-charcoal);
}
.risk-score-placeholder { font-size: 1.1rem; opacity: 0.4; }
.risk-score.compliant  { border-color: var(--ss-risk-compliant); color: var(--ss-risk-compliant); }
.risk-score.minor      { border-color: var(--ss-risk-minor); color: #a16207; }
.risk-score.moderate   { border-color: var(--ss-risk-moderate); color: var(--ss-risk-moderate); }
.risk-score.high       { border-color: var(--ss-risk-high); color: var(--ss-risk-high); }
.risk-score.critical   { border-color: var(--ss-risk-critical); color: var(--ss-risk-critical); }

/* --- Stat cards (dashboard) --- */
.stat-card {
    text-align: center;
    padding: 1.25rem 1rem;
}
.stat-card .stat-value {
    font-family: inherit;
    font-size: 1.85rem;
    font-weight: 700;
    color: var(--ss-charcoal);
    line-height: 1.2;
}
.stat-card .stat-label {
    font-size: 0.85rem;
    color: var(--ss-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-top: 0.35rem;
    font-weight: 600;
}

/* --- Product feature cards --- */
.feature-card {
    cursor: default;
}
.feature-card:hover {
    box-shadow: var(--ss-shadow-md);
    transform: translateY(-2px);
}
.feature-card .card-body {
    padding: 1.5rem;
}
.feature-card .feature-icon {
    width: 48px;
    height: 48px;
    border-radius: var(--ss-radius-lg);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.4rem;
    margin-bottom: 0.85rem;
}
.feature-card p { font-size: 0.95rem; line-height: 1.5; }
.feature-card.disabled { opacity: 0.5; }
.feature-icon.green { background: var(--ss-green-subtle); color: var(--ss-green); }
/* `.blue` modifier name retained for compatibility; renders as brand plum. */
.feature-icon.blue  { background: var(--ss-plum-subtle); color: var(--ss-plum); }
.feature-icon.amber { background: var(--ss-amber-subtle); color: var(--ss-amber); }

/* --- Section headers --- */
.section-label {
    font-size: 0.82rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--ss-text-secondary);
    margin-bottom: 0.65rem;
}

/* --- Top bar --- */
.portal-title {
    font-family: inherit;
    font-weight: 700;
    color: var(--ss-charcoal);
    font-size: 1rem;
}

/* --- Muted text --- */
.text-ss-muted { color: var(--ss-text-muted); }
.text-ss-secondary { color: var(--ss-text-secondary); }

/* --- Text size utilities --- */
.text-xs   { font-size: 0.6rem; }
.text-xs2  { font-size: 0.7rem; }
.text-sm   { font-size: 0.8rem; }
.text-sm2  { font-size: 0.85rem; }
.text-base { font-size: 0.9rem; }
.text-base2 { font-size: 0.95rem; }

/* --- Layout utilities --- */
.cursor-pointer    { cursor: pointer; }
.max-w-dialog      { max-width: 560px; }
.scroll-y          { max-height: 400px; overflow-y: auto; }
.cat-label         { width: 200px; flex-shrink: 0; }
.cat-pct           { width: 45px; text-align: right; font-weight: 500; }
.progress-thin             { height: 8px; border-radius: var(--ss-radius-sm); }
.progress-thin .progress-bar { border-radius: var(--ss-radius-sm); }

/* --- Vault folder styles --- */
.vault-folder-name { font-weight: 500; text-transform: capitalize; }
.vault-folder-icon { font-size: 1.5rem; opacity: 0.3; }

/* --- Audit score column --- */
.fw-500 { font-weight: 500; }
.fw-600 { font-weight: 600; }
.text-capitalize { text-transform: capitalize; }

/* --- Product icon color variants (public products page) --- */
.product-icon.green  { background: rgba(40,174,96,0.15); color: #28ae60; }
.product-icon.blue   { background: rgba(52,120,198,0.15); color: #3478c6; }
.product-icon.orange { background: rgba(230,126,34,0.15); color: #e67e22; }

/* --- Tables --- */
.table {
    font-size: 0.9rem;
}
.table th {
    font-weight: 600;
    color: var(--ss-text-secondary);
    font-size: 0.82rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border-bottom-width: 1px;
}

/* --- Audit question styles --- */
.audit-question {
    padding: 1rem;
    border-bottom: 1px solid var(--ss-border);
}
.audit-question:last-child {
    border-bottom: none;
}
.audit-question .question-id {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--ss-text-muted);
    text-transform: uppercase;
}
.audit-question .risk-badge {
    font-size: 0.73rem;
    font-weight: 700;
    padding: 0.25em 0.55em;
    border-radius: var(--ss-radius-sm);
    text-transform: uppercase;
}
.risk-badge.high    { background: rgba(239, 68, 68, 0.08); color: var(--ss-risk-high); }
.risk-badge.medium  { background: rgba(245, 158, 11, 0.08); color: var(--ss-risk-moderate); }
.risk-badge.low     { background: var(--ss-green-subtle); color: var(--ss-green); }

/* --- Full-bleed pages (LabelMaker) — negate content padding, fill height --- */
.labels-fullbleed {
    margin: -1.25rem -2rem -1.5rem;
    height: calc(100vh - 3.75rem);
    overflow: hidden;
}

/* Print page variant: same negated padding + viewport sizing.
   v3.1 round 2: flex-shell layout — scroll happens INSIDE the room body
   so the action bar can sit as a static flex sibling at the bottom of
   .pr-room. No more position:sticky tricks; the action bar is just the
   last flex child of a flex-column container that fills viewport height.
   Works the same whether the queue is empty, has 1 item, or 50. */
.labels-fullbleed-scroll {
    margin: -1.25rem -2rem -1.5rem;
    height: calc(100vh - 3.75rem);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.labels-license-bar {
    padding: 8px 16px;
    border-bottom: 1px solid var(--lf-border, #e5e7eb);
    background: var(--lf-bg-alt, #f8fafc);
    display: flex;
    gap: 8px;
    align-items: center;
}

/* --- SOP content rendered from markdown --- */
.sop-content {
    font-size: 0.9rem;
    line-height: 1.7;
    margin-top: 1rem;
    border-top: 1px solid var(--ss-border);
    padding-top: 1rem;
}
.sop-content h2, .sop-content h3 { font-size: 1rem; margin-top: 1.25rem; margin-bottom: 0.5rem; }
.sop-content p { margin-bottom: 0.5rem; }
.sop-content strong { color: var(--ss-charcoal); }
.sop-content ul, .sop-content ol { padding-left: 1.5rem; margin-bottom: 0.5rem; }
.sop-content li { margin-bottom: 0.25rem; }
.sop-content em { color: var(--ss-text-muted); }

/* --- SOP customize (split editor / preview) --- */
.sop-customize-hint {
    font-style: italic;
}
.sop-customize-split {
    display: flex;
    gap: 1rem;
    min-height: 400px;
}
.sop-customize-editor {
    flex: 1;
    min-width: 0;
}
.sop-customize-textarea {
    width: 100%;
    height: 100%;
    min-height: 400px;
    font-family: 'Roboto Mono', 'Courier New', monospace;
    font-size: 0.85rem;
    line-height: 1.6;
    padding: 0.75rem;
    border: 1px solid var(--ss-border);
    border-radius: var(--ss-radius);
    resize: vertical;
}
.sop-customize-textarea:focus {
    outline: none;
    border-color: var(--ss-green);
    box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.15);
}
.sop-customize-preview {
    flex: 1;
    min-width: 0;
    max-height: 500px;
    overflow-y: auto;
    border: 1px solid var(--ss-border);
    border-radius: var(--ss-radius);
    padding: 0.75rem;
    background: var(--ss-bg);
}

.enforcement-callout {
    background: rgba(239, 68, 68, 0.04);
    border-left: 3px solid var(--ss-risk-high);
    padding: 0.75rem 1rem;
    margin-top: 0.5rem;
    border-radius: 0 var(--ss-radius) var(--ss-radius) 0;
    font-size: 0.88rem;
}
.enforcement-locked { opacity: 0.6; }

/* --- Error UI --- */
.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}
.blazor-error-boundary::after {
    content: "An error has occurred."
}

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* --- Error / Not-Found pages --- */
.error-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 60vh;
    padding: 2rem;
}

.error-card {
    text-align: center;
    max-width: 520px;
    background: var(--ss-bg-card);
    border: 1px solid var(--ss-border);
    border-radius: var(--ss-radius-lg);
    padding: 2.5rem 2rem;
    box-shadow: var(--ss-shadow-md);
}

.error-icon {
    width: 56px;
    height: 56px;
    margin: 0 auto 1.25rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
    font-weight: 700;
    background: rgba(220, 38, 38, 0.08);
    color: var(--ss-risk-critical);
}

.error-card h1 {
    font-size: 1.5rem;
    margin-bottom: 0.5rem;
}

.error-subtitle {
    color: var(--ss-text-secondary);
    font-size: 0.92rem;
    line-height: 1.6;
    margin-bottom: 1.5rem;
}

.error-request-id {
    color: var(--ss-text-muted);
    font-size: 0.8rem;
    margin-bottom: 1.25rem;
}

.error-request-id code {
    font-size: 0.75rem;
    background: var(--ss-bg);
    padding: 0.15rem 0.4rem;
    border-radius: var(--ss-radius-sm);
}

.error-detail-block {
    text-align: left;
    background: var(--ss-bg);
    border: 1px solid var(--ss-border);
    border-radius: var(--ss-radius);
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
    font-size: 0.82rem;
    word-break: break-word;
}

.error-detail-block code {
    font-size: 0.78rem;
}

.error-detail-block pre {
    margin: 0.5rem 0 0;
    white-space: pre-wrap;
    font-size: 0.78rem;
    color: var(--ss-risk-critical);
    max-height: 300px;
    overflow-y: auto;
}

.error-exception {
    border-left: 3px solid var(--ss-risk-critical);
}

.error-actions {
    display: flex;
    gap: 0.75rem;
    justify-content: center;
}

.error-actions .btn {
    min-width: 120px;
}

/* ─── ComplianceForge.Views panels ─────────────────────────────── */

/* ---- Page header (h1 + badge + description + action group) ---- */

.cv-view-header h1 {
    font-size: 1.5rem;
    font-weight: 600;
    color: var(--bs-body-color, #1f2937);
    margin: 0;
}

.cv-view-header .badge {
    font-size: 0.7rem;
    font-weight: 500;
    padding: 0.25rem 0.5rem;
    border-radius: var(--ss-radius-pill);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.cv-view-header p.text-ss-secondary {
    margin-top: 0.35rem;
    font-size: 0.875rem;
    color: var(--bs-secondary-color, #6b7280);
    max-width: 60ch;
}

/* GridPos.H units need a definite track height so panels (esp.
   vega charts with width:container / height:container) have a
   real container to fill. 3rem per H unit so charts at H=8 read
   as ~384px tall -- enough for a column chart with a legend.
   Smaller (2.25rem) crushed the chart canvas into something
   unreadable. */
.cv-view-grid {
    margin-top: 1rem;
    grid-auto-rows: 3rem;
    gap: 1rem;
}

/* ---- Panel card: subtle shadow, hover lift, header + body split ---- */

.cv-view-panel {
    background: var(--bs-body-bg, #fff);
    border: 0;
    border-radius: var(--ss-radius-lg);
    padding: 1rem 1.25rem;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
    box-shadow: var(--ss-shadow);
    transition: box-shadow 120ms ease;
}

.cv-view-panel:hover {
    box-shadow: var(--ss-shadow-md);
}

.cv-view-panel > h3 {
    margin: 0 0 0.65rem 0;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--bs-secondary-color, #6b7280);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.cv-kpi-panel {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    gap: 0.25rem;
    /* Enable container queries on the KPI body so the value font-size
       can grow with the available height. Inline-size keeps flex sizing
       intact (size containment would interfere with the panel's
       flex:1 stretch). The clamp() below uses cqh (1% of the
       container's height) so the number scales linearly with the
       tile, then caps via container-query breakpoints. */
    container-type: size;
    container-name: kpi;
}

.cv-kpi-value {
    /* Scale the value with the tile so the number doesn't look lost
       in a tall card. Floor 2.5rem so a short H=3 tile still shows a
       solid headline figure (matches the prior fixed 2.25rem and
       slightly bumps it). 35cqh keeps the number near 1/3 of the
       tile's height, so an H=8 KPI shows ~5rem and an H=4 shows
       ~3rem -- both look balanced without billboard-mode. Cap 5.5rem
       so an absurdly tall tile doesn't render comically. */
    font-size: clamp(2.5rem, 35cqh, 5.5rem);
    font-weight: 700;
    line-height: 1;
    color: var(--bs-body-color, #212529);
}

.cv-kpi-label {
    /* Label tracks too but more gently -- a giant tile shouldn't have
       an enormous caption. */
    font-size: clamp(0.85rem, 6cqh, 1.15rem);
    color: var(--bs-secondary-color, #6c757d);
}

.cv-kpi-trend {
    font-size: 0.875rem;
    font-weight: 500;
}

/* Empty / no-data state -- distinct enough that a viewer doesn't
   confuse "we couldn't fetch this" with "this panel is broken." */
.cv-empty {
    color: var(--bs-secondary-color, #6c757d);
    background-color: var(--bs-tertiary-bg, #f8f9fa);
    border-style: dashed;
}

.cv-kpi-value-empty {
    font-size: 2rem;
    color: var(--bs-secondary-color, #adb5bd);
    font-weight: 400;
}

.cv-kpi-empty-detail {
    font-size: 0.75rem;
    color: var(--bs-secondary-color, #6c757d);
    font-style: italic;
}

/* chart-encodable wraps a toolbar + the vega host. Without flex the
   toolbar collapses to intrinsic height but the vega host doesn't
   fill the remainder of the panel -- vega's "container" autosize
   then has no definite height and the SVG renders at its built-in
   ~13px default. */
.cv-encodable-panel {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

.cv-encodable-toolbar {
    flex: 0 0 auto;
}

.cv-vega-panel {
    flex: 1 1 auto;
    min-height: 14rem;
    /* Width must be definite for vega-embed's "width: container" to
       resolve. The flex parent provides height; this clamps width. */
    width: 100%;
}

.cv-vega-panel > .vega-embed,
.cv-vega-panel.vega-embed {
    width: 100%;
    height: 100%;
}

.cv-vega-panel svg {
    max-width: 100%;
}

/* Chart-type select sits next to the Configure button in the panel
   toolbar. Default Bootstrap form-select reserves only ~2rem on the
   right for its dropdown caret, so 4-letter option labels ("donut")
   crash into the arrow. Reserve a wider right padding and set an
   auto-fit width with a sane minimum. */
.cv-chart-type-select {
    width: auto;
    min-width: 6.5rem;
    padding-right: 1.75rem !important;
}

/* ---- Filter pill bar ---- */
/*
   The filter bar is the primary control for the whole view. Earlier
   draft rendered every pill as a tiny outline-secondary button which
   read as inert decoration -- the user didn't realise they were
   clickable. New chrome: a card-style bar with a labeled "Filters"
   slug, pills that ramp from outline (inactive) to filled (active),
   active pills carrying a clear-(x) affordance + a count chip for
   InList selections.
*/

/* Visual parity with the entity-grid filter bar (.ss-grid-filter-*)
   so reports and grids read as one design system. Substrate markup
   (ComplianceForge.Views.Components/Filters/ViewFilterBar.razor)
   is untouched; this block re-skins via the .cv-filter-* hooks the
   substrate already exposes. */
.cv-filter-bar {
    background: var(--ss-bg-card, #fff);
    border: 0;
    border-bottom: 1px solid var(--ss-separator, #E8EAEF);
    border-radius: 0;
    padding: 0.4rem 1.25rem 0.85rem;
    gap: 0.5rem;
    margin-bottom: 1rem;
    /* No sticky / no backdrop-filter -- matches grid bars, which stay
       inline with the page chrome rather than pinning to the viewport. */
}
/* When the filter bar lives inside the report header card the
   trailing border below the pills reads as a redundant edge against
   the card's own bottom rim. Flip the divider to the TOP so it
   separates the action row above from the filter pills below.
   Spacing rhythm: 1.1rem padding-top matches the parent's own
   padding-bottom so the gap above the pills (divider->pills) reads
   the same as the gap below them (pills->card edge). */
.vs-header .cv-filter-bar {
    border-bottom: 0;
    border-top: 1px solid var(--ss-separator, #E8EAEF);
    /* ViewFilterBar's root carries Bootstrap utility classes py-1 + mb-3
       (substrate-defined, includes !important). Override explicitly. */
    padding: 1.1rem 0 0 !important;
    margin: 1.1rem 0 0 !important;
}

.cv-filter-bar > span:first-child {
    /* Leading filter icon -- quiet muted color, no uppercase label,
       no border separator. Matches grid bar's no-prefix look. */
    font-size: 0.85rem;
    color: var(--ss-text-muted, #475569);
    padding-right: 0.4rem;
    display: inline-flex;
    align-items: center;
}

/* Pill button -- the substrate renders a single
   <button class="btn btn-sm btn-outline-secondary"> with an inline
   --bs-btn-* var swap for the active state. This rule restyles only
   the inactive shape so it matches .ss-grid-filter-pill chrome;
   the active green fill is already brand-aligned via --bs-primary. */
.cv-filter-pill .btn.btn-outline-secondary {
    background: var(--ss-bg-card, #fff);
    border: 1px solid var(--ss-border, #D1D5DE);
    color: var(--ss-text-dark, #374151);
    border-radius: var(--ss-radius-pill, 999px);
    padding: 0.3rem 0.85rem;
    font-size: 0.85rem;
    font-weight: 500;
    line-height: 1.4;
}

.cv-filter-pill .btn.btn-outline-secondary:hover {
    border-color: var(--ss-light-green, #0DB14B);
    background: var(--ss-bg-card, #fff);
    color: var(--ss-text-dark, #374151);
}

/* Dropdown popover -- substrate sets inline styles for position,
   z-index, and min-width; visual properties (bg/border/radius/shadow)
   need !important to win over the inline declarations. Keeps the
   substrate markup untouched. */
.cv-filter-dropdown {
    background: var(--ss-bg-card, #fff) !important;
    border: 1px solid var(--ss-border, #D1D5DE) !important;
    border-radius: var(--ss-radius, 8px) !important;
    box-shadow: var(--ss-shadow-md) !important;
    padding: 0.5rem !important;
    color: var(--ss-text-dark, #374151);
    font-size: 0.85rem;
}

/* List-group option items inside the dropdown (Equals + InList
   filter pickers) -- match grid menu's item styling. */
.cv-filter-dropdown .list-group-item-action {
    border: none;
    border-radius: 0.375rem;
    padding: 0.35rem 0.55rem;
    margin: 0.05rem 0;
}

/* Subtle tint, not the global solid Primary fill -- same reasoning as
   .ss-grid-filter-pill-menu above. Not redundant. */
.cv-filter-dropdown .list-group-item-action.active {
    background: var(--ss-green-subtle, rgba(13, 177, 75, 0.12));
    color: var(--ss-secondary, #08642B);
    font-weight: 500;
}

.cv-filter-dropdown .list-group-item-action:hover {
    background: var(--ss-bg, #f3f4f6);
}

.cv-filter-dropdown .form-control,
.cv-filter-dropdown .form-select {
    border-radius: 0.375rem;
}

/* "Clear all" affordance -- small, quiet, no underline, right-aligned
   by the substrate via ms-auto. Matches the grid bar's clear link. */
.cv-filter-bar > button.btn-link {
    font-size: 0.8rem;
    font-weight: 500;
    color: var(--ss-text-muted, #475569);
    text-decoration: none;
}

.cv-filter-bar > button.btn-link:hover {
    color: var(--ss-text-dark, #374151);
    text-decoration: underline;
}

/* ---- Loading / empty / error states ---- */

.cv-panel-loading,
.cv-panel-empty,
.cv-panel-error {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 1.5rem 1rem;
    text-align: center;
    color: var(--bs-secondary-color, #6b7280);
    background: var(--bs-tertiary-bg, #f9fafb);
    border-radius: 0.5rem;
    min-height: 6rem;
}

.cv-panel-loading i,
.cv-panel-empty i,
.cv-panel-error i {
    font-size: 1.5rem;
    opacity: 0.55;
}

.cv-panel-error {
    color: var(--bs-danger-text-emphasis, #991b1b);
    background: var(--bs-danger-bg-subtle, #fef2f2);
}

/* Existing plain "No data." replacement -- target it through the
   substrate's selector so we don't have to touch every panel
   template at once. */
p.cv-empty {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    margin: 0;
    padding: 1.5rem 1rem;
    color: var(--bs-secondary-color, #6b7280);
    background: var(--bs-tertiary-bg, #f9fafb);
    border: 1px dashed var(--bs-border-color, #e5e7eb);
    border-radius: 0.5rem;
    font-size: 0.875rem;
}

p.cv-empty::before {
    content: "\f15c";  /* fa-file-lines */
    font-family: "Font Awesome 6 Free";
    font-weight: 400;
    font-size: 1.5rem;
    opacity: 0.45;
    margin-bottom: 0.4rem;
    display: block;
}

/* ---- Reports library landing page ---- */

/* Tabs strip (All / Mine / Shared / Platform). Single bottom border on
   the strip, primary-green underline on the active tab. */
.cv-tabs {
    display: flex;
    gap: 0.25rem;
    border-bottom: 1px solid var(--ss-separator, #E8EAEF);
    margin-bottom: 1.25rem;
}

.cv-tab {
    background: none;
    border: 0;
    padding: 0.65rem 0.95rem;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--ss-text-muted, #475569);
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

.cv-tab:hover {
    color: var(--ss-text, var(--ss-iron));
}

.cv-tab.active {
    color: var(--ss-text, var(--ss-iron));
    border-bottom-color: var(--ss-light-green);
}

/* Search input — relies on Bootstrap .form-control / .form-control-sm
   so the chrome matches every other search input in the portal
   (Plants, Packages, Harvests, Locations). Only the layout (max-width)
   is page-local. */
.cv-search-row {
    margin-bottom: 1.5rem;
}

.cv-search-input {
    max-width: 24rem;
}

/* Empty-state block when the user has no personal reports yet. */
.cv-library-empty {
    background: var(--ss-bg-card, #fff);
    border: 1px dashed var(--ss-border, #D1D5DE);
    border-radius: var(--ss-radius-lg);
    padding: 1.75rem 1.5rem;
    text-align: center;
    color: var(--ss-text-muted, #475569);
    margin-bottom: 1.5rem;
}

.cv-library-empty i {
    font-size: 1.5rem;
    opacity: 0.55;
    margin-bottom: 0.5rem;
    display: block;
}

.cv-library-empty p {
    margin: 0;
}

/* Section header inside a tab body. Quiet uppercase label; no count
   pills, no sub-eyebrow text. */
.cv-section-h {
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--ss-text-muted, #475569);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin: 0 0 0.85rem 0;
}

.cv-section-h + .cv-rc-grid {
    margin-bottom: 2rem;
}

/* Card grid -- min 18rem column so 3-up at typical widths, 2-up
   on narrower viewports. */
.cv-rc-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
    gap: 1rem;
    margin-bottom: 1.5rem;
}

/* Report card -- icon + title + badge + desc + footer (Compare + actions). */
.cv-rc {
    background: var(--ss-bg-card, #fff);
    border: 1px solid var(--ss-separator, #E8EAEF);
    border-radius: var(--ss-radius-lg);
    padding: 1rem 1.1rem;
    display: flex;
    flex-direction: column;
    transition: border-color 120ms ease, box-shadow 120ms ease;
    color: var(--ss-text);
}

.cv-rc:hover {
    border-color: var(--ss-light-green);
    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.06);
}

.cv-rc-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 0.55rem;
}

.cv-rc-icon {
    width: 30px;
    height: 30px;
    border-radius: 7px;
    background: rgba(13, 177, 75, 0.10);
    color: var(--ss-dark-green);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.9rem;
    flex-shrink: 0;
}

.cv-rc-title {
    font-size: 0.95rem;
    font-weight: 700;
    color: var(--ss-text);
    line-height: 1.25;
    flex: 1;
    min-width: 0;
    text-decoration: none;
}

.cv-rc-title:hover {
    color: var(--ss-dark-green);
    text-decoration: underline;
}

/* Badges: Curated (limeade), Draft (peach), Shared (plum). One only. */
.cv-rc-badge {
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 0.18rem 0.5rem;
    border-radius: var(--ss-radius-pill);
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    line-height: 1;
}

.cv-rc-badge i {
    font-size: 0.6rem;
}

.cv-rc-badge.cv-rc-curated {
    background: #f1f3f5;
    color: var(--ss-text-muted, #475569);
    font-weight: 600;
}

.cv-rc-badge.cv-rc-draft {
    background: rgba(243, 147, 50, 0.18);
    color: #a8540d;
}

.cv-rc-badge.cv-rc-shared {
    background: rgba(107, 71, 150, 0.14);
    color: #5a3a82;
}

.cv-rc-desc {
    font-size: 0.82rem;
    color: var(--ss-text-muted, #475569);
    flex: 1;
    line-height: 1.5;
    margin: 0 0 0.85rem 0;
}

.cv-rc-foot {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding-top: 0.7rem;
    border-top: 1px solid var(--ss-separator, #E8EAEF);
}

.cv-rc-compare {
    font-size: 0.74rem;
    color: var(--ss-text-muted, #475569);
    display: flex;
    align-items: center;
    gap: 5px;
    cursor: pointer;
    margin: 0;
}

.cv-rc-compare input {
    width: 13px;
    height: 13px;
    margin: 0;
}

.cv-rc-cta {
    margin-left: auto;
    display: flex;
    gap: 0.4rem;
    align-items: center;
}

.cv-rc-cta a,
.cv-rc-cta button {
    font-size: 0.78rem;
    font-weight: 600;
    padding: 0.3rem 0.75rem;
    border-radius: var(--ss-radius-sm);
    text-decoration: none;
    border: 1px solid transparent;
    cursor: pointer;
    background: transparent;
}

.cv-rc-cta .cv-rc-clone {
    color: var(--ss-dark-green);
}

.cv-rc-cta .cv-rc-clone:hover {
    background: var(--ss-bg);
}

.cv-rc-cta .cv-rc-clone:disabled {
    opacity: 0.55;
    cursor: default;
}

/* Open is the primary CTA on the card but renders as outline so the
   landing page reads as a calm grid of options, not a wall of solid
   green buttons. Hover fills with the brand primary green for a clear
   pressed state. Matches .btn-outline-primary's color + hover tokens. */
.cv-rc-cta .cv-rc-open {
    background: transparent;
    color: var(--ss-secondary);
    border-color: var(--ss-light-green);
}

.cv-rc-cta .cv-rc-open:hover {
    background: var(--ss-light-green-hover);
    border-color: var(--ss-light-green-hover);
    color: #fff;
    text-decoration: none;
}

/* ---- Single-report header (ViewSingle.razor) ----
   Top-left: title + ownership badge. Top-right: Refresh + Download
   as quiet icon buttons, vertical divider, then state-specific
   actions (Clone / Publish / Rename / Delete). Brand-aligned across
   every ownership state. */

.vs-header {
    background: var(--ss-bg-card, #fff);
    border-radius: var(--ss-radius-lg);
    box-shadow: var(--ss-shadow-pop);
    padding: 1.1rem 1.35rem;
    margin-bottom: 1rem;
}

.vs-header-row {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    flex-wrap: wrap;
}

.vs-header-left {
    flex: 1;
    min-width: 0;
}

.vs-header-title {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-bottom: 0.2rem;
}

.vs-header-title h1 {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--ss-text, var(--ss-iron));
    line-height: 1.2;
    margin: 0;
}

.vs-header-desc {
    font-size: 0.85rem;
    color: var(--ss-text-secondary, #2d3748);
    margin: 0.2rem 0 0 0;
}

/* Badge variants — same color tokens the landing chips use. Sized
   slightly larger than card chips for header context (0.22rem v vs
   0.18rem on cards). */
.vs-badge {
    font-size: 0.7rem;
    font-weight: 600;
    padding: 0.22rem 0.6rem;
    border-radius: var(--ss-radius-pill, 999px);
    display: inline-flex;
    align-items: center;
    gap: 5px;
    line-height: 1;
    flex-shrink: 0;
}

.vs-badge--platform {
    background: #f1f3f5;
    color: var(--ss-text-muted, #475569);
}

.vs-badge--draft {
    background: rgba(243, 147, 50, 0.18);
    color: #a8540d;
}

.vs-badge--shared {
    background: rgba(107, 71, 150, 0.14);
    color: #5a3a82;
}

/* Right-side action cluster — Refresh + Download as quiet icons,
   divider, then state-specific buttons. */
.vs-actions {
    display: flex;
    gap: 0.4rem;
    align-items: center;
    flex-shrink: 0;
}
/* Toolbar peers (Rename / Save changes / Clone) must size-match so
   the action bar reads as a single row. .btn-primary and
   .btn-outline-primary set padding 0.5rem 1.1rem explicitly above;
   .btn-outline-secondary uses Bootstrap's smaller btn-sm default,
   which shrinks Rename relative to its peers. Scope the equaliser
   to .vs-actions so the rest of the app's .btn-outline-secondary
   usages keep their existing compact size. */
.vs-actions .btn.btn-outline-secondary {
    padding: 0.5rem 1.1rem;
}

.vs-icn-btn {
    background: transparent;
    border: 0;
    color: var(--ss-text-muted, #475569);
    padding: 0.4rem 0.55rem;
    border-radius: var(--ss-radius);
    cursor: pointer;
    font-size: 0.92rem;
    line-height: 1;
}

.vs-icn-btn:hover:not(:disabled) {
    background: var(--ss-bg);
    color: var(--ss-text, var(--ss-iron));
}

.vs-icn-btn:disabled {
    opacity: 0.45;
    cursor: default;
}

.vs-icn-btn--danger { color: #b02a37; }
.vs-icn-btn--danger:hover:not(:disabled) {
    background: rgba(176, 42, 55, 0.08);
    color: #b02a37;
}

.vs-icn-divider {
    width: 1px;
    height: 18px;
    background: var(--ss-separator, #E8EAEF);
    margin: 0 0.15rem;
}

/* Inline rename editor (double-click draft title). Brand-green
   focus border so the operator sees the field is editable. */
.vs-rename-input {
    width: 100%;
    max-width: 30rem;
    padding: 0.5rem 0.85rem;
    border: 1.5px solid var(--ss-light-green);
    border-radius: var(--ss-radius);
    font-size: 1.3rem;
    font-weight: 700;
    color: var(--ss-text, var(--ss-iron));
    background: #fff;
}

.vs-rename-input:focus {
    outline: none;
    border-color: var(--ss-light-green);
    box-shadow: 0 0 0 3px rgba(13, 177, 75, 0.15);
}

.vs-rename-hint {
    display: block;
    font-size: 0.72rem;
    color: var(--ss-text-muted, #475569);
    margin-top: 0.3rem;
}

/* Unsaved-tweaks banner — SyncBlue tint matches the semantic
   "in-flight / pending" meaning. Replaces Bootstrap's alert-info
   default with brand SyncBlue (#1565c0). */
.vs-unsaved-banner {
    background: rgba(21, 101, 192, 0.08);
    color: #0e3f72;
    border: 1px solid rgba(21, 101, 192, 0.18);
    border-radius: var(--ss-radius);
    padding: 0.55rem 0.95rem;
    font-size: 0.85rem;
    margin-bottom: 1rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.vs-unsaved-banner i { color: #1565c0; }

/* Matrix density toggle: compact crushes vertical padding so 30+
   rows fit without scrolling; comfortable (default) keeps Bootstrap
   .table-sm's natural padding. */
.cv-drillable-matrix.cv-matrix-dense table.table td,
.cv-drillable-matrix.cv-matrix-dense table.table th {
    padding-top: 0.15rem;
    padding-bottom: 0.15rem;
    line-height: 1.1;
}

/* ---- Table-panel + matrix scrolling chrome ---- */
/*
   Both panel types host a table inside a panel of fixed height (set
   by the grid). When the row count exceeds the panel, the panel
   itself scrolls. Without sticky headers the user loses the column
   labels mid-table -- standard CRM UX pain. Stick the thead so a
   user scrolling 200 plants keeps the TAG / STRAIN / PHASE labels
   in view.
*/
.cv-table-panel,
.cv-drillable-matrix {
    flex: 1;
    overflow: auto;
    min-height: 0;
}

/* Panel toolbar (row-count badge + Configure / column-toggle buttons)
   sticks to the top of the scroll container so the user keeps access
   to the panel-level config affordances while scrolling 100+ rows.
   Without this, a tall table or matrix scrolls the toolbar off-screen
   along with the data and the user has to jump back to the top to
   toggle a column. The toolbar height (~2.1rem with mb-1) is what
   the sticky thead `top` below accounts for. */
.cv-table-panel > .cv-table-toolbar,
.cv-drillable-matrix > .cv-matrix-toolbar {
    position: sticky;
    top: 0;
    z-index: 3;
    background: var(--bs-body-bg, #fff);
    padding-top: 0.125rem;
}

.cv-table-panel table.cv-table thead,
.cv-drillable-matrix table.table thead {
    position: sticky;
    top: 2.1rem;   /* sit just below the sticky toolbar */
    z-index: 2;
    background: var(--bs-body-bg, #fff);
    box-shadow: inset 0 -1px 0 var(--bs-border-color, #e5e7eb);
}

.cv-table-panel table.cv-table thead tr.cv-table-filter-row {
    /* Second header row -- needs a different "top" so it sits below
       the first header row when sticky. First header row is at
       toolbar-height (2.1rem) + its own ~28px = ~4.2rem.
       Background is REQUIRED on sticky rows -- without it, table
       body cells show through the row as it scrolls past, which
       reads as a transparent gap. Matches the first thead row's
       background so the sticky stack looks like one surface. */
    top: 4.2rem;
    z-index: 2;
    background: var(--bs-body-bg, #fff);
}

/* Make the column-filter popover button less screamy when inactive
   so a wide table of filter cells doesn't look like a sea of
   buttons. Outline only on hover / active. */
.cv-col-filter .btn-outline-secondary {
    border-color: transparent;
    background: transparent;
    color: var(--bs-secondary-color, #6b7280);
    font-weight: 400;
}

.cv-col-filter .btn-outline-secondary:hover {
    background: var(--bs-tertiary-bg, #f3f4f6);
    color: var(--bs-body-color, #1f2937);
}

.cv-col-filter .btn-outline-secondary.dropdown-toggle::after {
    opacity: 0.4;
}

.cv-col-filter .cv-col-filter-summary {
    font-size: 0.8rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: inline-block;
    max-width: 100%;
}

/* Hover affordance on body rows. cv-table-select-col cells still
   look clickable; non-select rows hint at hoverability so users
   know they're scanning live data, not a static image. */
.cv-table-panel tbody tr:hover,
.cv-drillable-matrix tbody tr:hover {
    background: var(--bs-tertiary-bg, #f9fafb);
}

.cv-vega-panel.cv-empty {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    padding: 2rem 1.25rem;
}

.cv-empty-title {
    font-size: 1rem;
    font-weight: 600;
    margin: 0 0 0.25rem 0;
    color: var(--bs-secondary-color, #6c757d);
}

.cv-empty-detail {
    font-size: 0.875rem;
    margin: 0;
    color: var(--bs-secondary-color, #6c757d);
}

.cv-empty-detail code {
    background-color: var(--bs-light-bg-subtle, #fff);
    padding: 0.125rem 0.375rem;
    border-radius: 0.25rem;
    border: 1px solid var(--bs-border-color, #dee2e6);
    font-size: 0.8rem;
}

.cv-table-panel table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.875rem;
}

.cv-table-panel thead th {
    text-align: left;
    padding: 0.5rem 0.75rem;
    border-bottom: 2px solid var(--bs-border-color, #dee2e6);
    font-weight: 600;
    color: var(--bs-secondary-color, #6c757d);
}

.cv-table-panel tbody td {
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--bs-border-color-translucent, #e9ecef);
}

.cv-table-panel tbody tr:hover {
    background-color: var(--bs-tertiary-bg, #f8f9fa);
}

.cv-text-success { color: var(--bs-success, #198754); }
.cv-text-danger { color: var(--bs-danger, #dc3545); }
.cv-text-secondary { color: var(--bs-secondary-color, #6c757d); }

/* ── Vega-tooltip fallback chrome ─────────────────────────────
   vega-embed appends #vg-tooltip-element to document.body and
   styles it via vega-tooltip's bundled CSS (position: fixed,
   visibility: hidden until shown). If that bundled CSS fails to
   apply -- CDN delay, CSP, or a stale tooltip surviving chart
   disposal -- the unstyled element lands in normal document
   flow at the bottom of <body>, adding a phantom row of text
   and growing page scroll. These rules mirror the vega-tooltip
   defaults so a leaked tooltip stays out of layout regardless
   of whether the bundled CSS loaded. */
.vg-tooltip {
    position: fixed;
    visibility: hidden;
    pointer-events: none;
    z-index: 1000;
    padding: 6px 8px;
    font-family: sans-serif;
    font-size: 11px;
    max-width: 320px;
    background-color: rgba(255, 255, 255, 0.95);
    color: #000;
    border: 1px solid #d9d9d9;
    border-radius: 3px;
    box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2);
}
.vg-tooltip.visible {
    visibility: visible;
}
.vg-tooltip table {
    border-collapse: collapse;
}
.vg-tooltip td.key {
    color: #808080;
    padding-right: 8px;
    text-align: right;
    max-width: 150px;
    text-overflow: ellipsis;
    overflow: hidden;
    white-space: nowrap;
}
.vg-tooltip td.value {
    color: #000;
    max-width: 200px;
    text-overflow: ellipsis;
    overflow: hidden;
    white-space: nowrap;
}

/* --- Dashboard card scroll cap ---------------------------------------
   Portal home cards are Bootstrap `.card` bodies, so they get none of the
   `.ss-grid-card` capping above and grow with their data -- the harvest and
   batch matrices come from GroupBy projections with no Take(), so row count
   is a function of licenses x strains, not of layout.

   Goes on the EXISTING `.table-responsive` wrapper, which already
   establishes an overflow context. That is what scopes `position: sticky`
   below to this box rather than the page, so the header and totals dock to
   the card instead of the viewport. */
.ss-card-scroll {
    max-height: 20rem;
    overflow-y: auto;
}

/* Compact variant for short non-table lists (the sync card's "what to
   update" checkbox list). 10rem is exactly the 160px inline cap it
   replaces, so that list's behaviour is unchanged. */
.ss-card-scroll.ss-card-scroll-sm {
    max-height: 10rem;
}

/* Column headers dock to the top: without this you scroll a five-column
   numeric matrix with no idea which column is Waste. Borders are drawn with
   box-shadow, not border-bottom -- Bootstrap's `.table` is
   `border-collapse: collapse`, where a sticky cell's own borders detach and
   render in the wrong place as it moves. */
.ss-card-scroll > table > thead > tr > th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--ss-surface, #fff);
    box-shadow: inset 0 -1px 0 var(--ss-border, #e5e7eb);
}

/* Totals dock to the bottom. These cards put their most valuable line in
   the tfoot, so capping the height without pinning it would bury the one
   row people came for -- pinned, the cap actually IMPROVES access to totals
   versus scrolling 20 rows to reach them. */
.ss-card-scroll > table > tfoot > tr > td {
    position: sticky;
    bottom: 0;
    z-index: 2;
    background: var(--ss-surface-subtle, #f8fafc);
    box-shadow: inset 0 1px 0 var(--ss-border, #e5e7eb);
}

/* --- Grid drag-to-extend selection --- */
/* No cursor override: desktop drag-select keeps the normal arrow, and the
   class lands on every pointerdown so a resize cursor flashes on plain clicks. */
.ss-grid-dragging { user-select: none; }
.ss-row-selected, tr.ss-row-selected > td { background-color: rgba(13,177,75,0.12); }
.ss-entity-table tbody tr[data-row-key] { cursor: default; }

/* ============================================================
   Collapsed sidebar rail (portal nav)
   NavMenu marks its scroll container with .nav-collapsed; the
   layout shell narrows to match. Lives here (global) because
   MainLayout.razor.css is scope-isolated and cannot see
   NavMenu's marker class. Specificity (.page .sidebar:has())
   intentionally beats the scoped .sidebar 248px rule.
   ============================================================ */
@media (min-width: 641px) {
    .page .sidebar {
        /* Rail flyouts overhang into the content area; keep the
           sidebar's stacking order above the sticky topbar (z 50). */
        z-index: 60;
    }

    .page .sidebar:has(.nav-collapsed) {
        width: 68px;
    }
}
