/* ────────────────────────────────────────────────────────────────────────────
 * Webfonts
 * ────────────────────────────────────────────────────────────────────────────
 * Two-tier type system:
 *
 *   - **Montserrat** (body): SIL OFL geometric sans by Julieta Ulanovsky.
 *     SynoForce brand's primary body face; warmer + more rounded than
 *     Inter, reads as the brand's signature look.
 *
 *   - **Inter** (fallback body): still declared so any legacy `font-family:
 *     'Inter'` reference resolves to a real face. Not the brand default.
 *
 *   - **SynoForce** (headings + brand-strong elements): display sans
 *     with x-height ≈ cap-height. Beautiful as a wordmark; punishing
 *     on paragraphs of body copy. Reserved for h1-h6, .display-*,
 *     .navbar-brand, .subheader-title, .font-display.
 *
 * font-display: swap on every face so the page paints with the
 * fallback stack while the WOFF2 streams in (no FOIT). */

/* Montserrat — declared once per weight we actually use (300/400/500/
 * 600/700). Italic variants too. 9 total. Each is its own woff2 file
 * because Google Fonts doesn't ship a variable-axis Montserrat (as of
 * 2025); the static files are smaller per-weight and well-cached. */
@font-face {
    font-family: 'Montserrat';
    src: url('/webfonts/montserrat/montserrat-light.woff2') format('woff2');
    font-weight: 300; font-style: normal; font-display: swap;
}
/* The Montserrat weight set ships no "regular" file — only light,
 * medium, semibold, bold, etc. Point font-weight:400 at the medium
 * woff2 so there's always a real face behind the body default.
 * The visual shift between true-400 and medium is small at body
 * sizes (Montserrat's stems are uniform across weights) and keeps
 * the UI coherent. */
@font-face {
    font-family: 'Montserrat';
    src: url('/webfonts/montserrat/montserrat-medium.woff2') format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Montserrat';
    src: url('/webfonts/montserrat/montserrat-medium.woff2') format('woff2');
    font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Montserrat';
    src: url('/webfonts/montserrat/montserrat-semibold.woff2') format('woff2');
    font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Montserrat';
    src: url('/webfonts/montserrat/montserrat-bold.woff2') format('woff2');
    font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Montserrat';
    src: url('/webfonts/montserrat/montserrat-italic.woff2') format('woff2');
    font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
    font-family: 'Montserrat';
    src: url('/webfonts/montserrat/montserrat-medium-italic.woff2') format('woff2');
    font-weight: 500; font-style: italic; font-display: swap;
}
@font-face {
    font-family: 'Montserrat';
    src: url('/webfonts/montserrat/montserrat-semibold-italic.woff2') format('woff2');
    font-weight: 600; font-style: italic; font-display: swap;
}
@font-face {
    font-family: 'Montserrat';
    src: url('/webfonts/montserrat/montserrat-bold-italic.woff2') format('woff2');
    font-weight: 700; font-style: italic; font-display: swap;
}

/* Inter kept as a fallback option. */
@font-face {
    font-family: 'Inter';
    src: url('/webfonts/inter/Inter-VF.woff2') format('woff2-variations');
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: 'Inter';
    src: url('/webfonts/inter/Inter-Italic-VF.woff2') format('woff2-variations');
    font-weight: 100 900;
    font-style: italic;
    font-display: swap;
}

@font-face {
    font-family: 'SynoForce';
    src: url('/webfonts/synoforce/synoforce-regular.woff2') format('woff2');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: 'SynoForce';
    src: url('/webfonts/synoforce/synoforce-bold.woff2') format('woff2');
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

/* ════════════════════════════════════════════════════════════════════════════
 * SynoForce brand layer
 * ════════════════════════════════════════════════════════════════════════════
 *
 * Three-layer cascade:
 *
 *   1. Brand atomic tokens (`--syno-*`) — the SynoForce palette as
 *      hex constants. Never theme-switched; never referenced
 *      directly by component selectors. They're the building blocks.
 *
 *   2. Semantic theme tokens (`--theme-*`) — what components actually
 *      reference. ONE set of definitions per theme; redefining a token
 *      under `html[data-bs-theme="dark"]` re-themes every component
 *      that references it. This is the single point of theme switching.
 *
 *   3. Bootstrap variable mappings (`--bs-*`) — feed the theme tokens
 *      back into Bootstrap's own components (.btn-primary, .alert-*,
 *      etc.) so we don't have to override individual classes.
 *
 * Theme switching:
 *   - The mode the user picked lives in localStorage as
 *     'infraops-theme' ∈ {'system', 'light', 'dark'}.
 *   - A tiny IIFE in base.twig reads it before any CSS loads and
 *     stamps the html element with:
 *       data-bs-theme="light" | "dark"     — what's actually painted
 *       data-theme-mode="system" | "light" | "dark" — what the user chose
 *   - When mode is 'system', the script resolves to the OS preference
 *     via `matchMedia('(prefers-color-scheme: dark)')`.
 *   - When mode is 'light' or 'dark', the OS preference is ignored.
 *   - The user-menu's 3-way picker writes back to localStorage and
 *     re-stamps the attributes; the CSS rules below pick up the change
 *     instantly because they're attribute-driven.
 *
 * Brand rules preserved:
 *   - Blue / dark tones dominate every theme.
 *   - Cyan stays decorative (badges, focus rings) — never body text.
 *   - Orange is opt-in via `.btn-syno-cta` / sidebar-icon override.
 *   - Warning / error stay system-only via Bootstrap's own classes.
 */

/* ── 1. SynoForce brand atomic tokens ────────────────────────────────────── */
:root {
    --syno-primary:        #01A2FD;
    --syno-primary-light:  #4ABCFE;
    --syno-dark:           #253E74;
    --syno-darker:         #0E182E;
    --syno-darkest:        #070C17;

    --syno-cyan:           #2DE2E6;   /* decorative only */

    --syno-success:        #10B981;
    --syno-warning:        #F59E0B;
    --syno-error:          #EF4444;
    --syno-info:           #3B82F6;

    --syno-cta:            #F97316;
    --syno-cta-hover:      #FB923C;
    --syno-cta-soft:       #FED7AA;

    --syno-bg-light:       #F7FAFC;
    --syno-bg-alt:         #EDF2F7;
    --syno-surface:        #FFFFFF;
    --syno-border:         #E2E8F0;
    --syno-border-hover:   #CBD5E1;
    --syno-text:           #0E182E;
    --syno-text-secondary: #253E74;
    --syno-text-muted:     #6B7280;
    --syno-text-disabled:  #A0AEC0;
    --syno-text-inverse:   #FFFFFF;
}

/* ── 2. Semantic theme tokens — LIGHT (default) ──────────────────────────
 * Default values apply when no data-bs-theme attribute is set AND when
 * `<html data-bs-theme="light">` is set. The base.twig theme-init
 * script stamps the attribute before any CSS loads.
 *
 * To re-theme any component: change the matching `--theme-*` value in
 * the dark block below, NOT a per-component rule. */
:root,
html[data-bs-theme="light"] {
    --theme-bg:                #F7FAFC;                /* page background — SynoForce light bg */
    --theme-surface:           #FFFFFF;                /* cards, sidebar, header */
    --theme-surface-alt:       var(--syno-bg-alt);     /* elevated bands, hover wash */
    --theme-text:              var(--syno-text);       /* primary text */
    --theme-text-secondary:    var(--syno-text-secondary);
    --theme-text-muted:        var(--syno-text-muted);
    --theme-border:            var(--syno-border);     /* default hairline */
    --theme-link:              var(--syno-primary);
    --theme-link-hover:        var(--syno-primary-light);

    /* Form inputs */
    --theme-input-bg:          #FFFFFF;
    --theme-input-border:      var(--syno-border);

    /* Sidebar / nav active state */
    --theme-nav-active-bg:     rgba(1, 162, 253, 0.10);
    --theme-nav-active-text:   var(--syno-primary);

    /* Tables */
    --theme-table-header-bg:   var(--syno-bg-alt);
    --theme-table-hover-bg:    var(--syno-bg-alt);

    /* Bootstrap "color-scheme" hint so native UI (scrollbars, native
     * form chrome, OS dropdowns) matches the theme. */
    color-scheme: light;
}

/* ── 2b. Semantic theme tokens — DARK ─────────────────────────────────────
 * Brand contract per the SynoForce dark-mode spec:
 *   Background:        #070C17  (darkest)
 *   Surface (cards):   #0E182E  (darker)
 *   Text primary:      #FFFFFF
 *   Text secondary:    #A0AEC0
 *   Link / active:     #4ABCFE  (primary-light reads better on dark)
 *   Focus ring:        #2DE2E6  (cyan stays cyan — see focus block below)
 */
html[data-bs-theme="dark"] {
    --theme-bg:                var(--syno-darkest);
    --theme-surface:           var(--syno-darker);
    --theme-surface-alt:       rgba(255, 255, 255, 0.04);
    --theme-text:              var(--syno-text-inverse);
    --theme-text-secondary:    #A0AEC0;
    --theme-text-muted:        var(--syno-text-disabled);
    --theme-border:            rgba(255, 255, 255, 0.10);
    --theme-link:              var(--syno-primary-light);
    --theme-link-hover:        var(--syno-primary);

    --theme-input-bg:          rgba(255, 255, 255, 0.05);
    --theme-input-border:      rgba(255, 255, 255, 0.15);

    --theme-nav-active-bg:     rgba(1, 162, 253, 0.20);
    --theme-nav-active-text:   var(--syno-primary-light);

    --theme-table-header-bg:   rgba(255, 255, 255, 0.04);
    --theme-table-hover-bg:    rgba(1, 162, 253, 0.06);

    /* Re-map SmartAdmin's bundled surface tokens to brand navy.
     * infraops-ui.min.css ships a dark-theme default of #272b30
     * (a warm desaturated gray) for `--app-header-background` /
     * `--app-panel-bg` / `--app-nav-bg`. Those tokens feed into
     * `.card`, `.nav-tabs`, the sidebar minify panel, and a few
     * other bundled components — leaving them at the default
     * paints those surfaces in a warm brown-gray that clashes
     * with the cool brand navy used by header + sidebar.
     *
     * Pointing all three at `--syno-darker` (#0E182E) gives every
     * SmartAdmin-bundled surface the same brand cool-navy tone as
     * our app shell. Single source of truth — no per-component
     * overrides needed downstream. */
    --app-header-background: var(--syno-darker);
    --app-panel-bg:          var(--syno-darker);
    --app-nav-bg:            var(--syno-darker);

    color-scheme: dark;
}

/* ── 3. Bootstrap variable mappings (cascade through built-in classes) ─── */
:root {
    --bs-body-bg:          var(--theme-bg);
    --bs-body-color:       var(--theme-text);
    --bs-border-color:     var(--theme-border);
    --bs-secondary-color:  var(--theme-text-muted);
    --bs-link-color:       var(--theme-link);
    --bs-link-hover-color: var(--theme-link-hover);

    /* Brand-fixed across themes — no theme switch */
    --bs-primary:          var(--syno-primary);
    --bs-primary-rgb:      1, 162, 253;
    --bs-primary-text-emphasis: var(--syno-darker);
    --bs-info:             var(--syno-cyan);
    --bs-info-rgb:         45, 226, 230;
    --bs-success:          var(--syno-success);
    --bs-success-rgb:      16, 185, 129;
    --bs-warning:          var(--syno-warning);
    --bs-warning-rgb:      245, 158, 11;
    --bs-danger:           var(--syno-error);
    --bs-danger-rgb:       239, 68, 68;

    --bs-border-radius:    8px;
    --bs-border-radius-sm: 6px;
    --bs-border-radius-lg: 12px;
}

/* ── 4. SmartAdmin --primary-* ramp + --app-nav-* overrides ───────────────
 * The bundled InfraOps UI (née SmartAdmin) ships its own purple
 * primary palette — #563d7c at the 900 step (the Bootstrap v4 brand
 * purple). It uses this palette through dozens of selectors via CSS
 * variables we don't override at component level (`--primary-900`,
 * `--app-nav-link-hover-color`, `--app-nav-item-hover-bg`, etc.), so
 * the sidebar / dropdown menus / nav hovers paint PURPLE even though
 * every component class we explicitly styled is SynoForce blue.
 *
 * Fix: redefine the entire ramp at :root using SynoForce shades so
 * every downstream SmartAdmin selector inherits brand colours for
 * free. Mapped roughly:
 *
 *   primary-50…200  →  very light blue tints  (faint backgrounds)
 *   primary-300…400 →  #4ABCFE  (primary-light)
 *   primary-500     →  #01A2FD  (primary — the brand)
 *   primary-600…800 →  darker steps toward navy
 *   primary-900     →  #0E182E  (SynoForce Darker)
 *
 * Nav-token strategy (SmartAdmin "set-nav-dark" pattern):
 *   :root              — brand-fixed values that look right in BOTH
 *                        themes (active pill = brand blue, hover text =
 *                        brand light blue, indicator = brand blue).
 *   html.set-nav-dark  — dark-surface overrides (navy bg + light text).
 *                        The IIFE in base.twig + theme.js toggle this
 *                        class with the resolved theme, so light theme
 *                        falls through to SmartAdmin's bundled light
 *                        sidebar defaults — exactly what the SmartAdmin
 *                        navigation-system docs prescribe. */
:root {
    --primary-50:   #E6F4FE;
    --primary-100:  #C0E1FC;
    --primary-200:  #94CDFA;
    --primary-300:  #6BBBF8;
    --primary-400:  #4ABCFE;     /* var(--syno-primary-light) */
    --primary-500:  #01A2FD;     /* var(--syno-primary) — the brand */
    --primary-600:  #028AD8;
    --primary-700:  #036FB0;
    --primary-800:  #04568B;
    --primary-900:  #0E182E;     /* var(--syno-darker) */

    /* Brand-fixed nav tokens — same value in light + dark theme.
     *
     * Variable names match what infraops-ui.min.css actually reads:
     *   --app-nav-active-indicator  (NOT --app-nav-item-active-indicator)
     *     ↳ painted by `.primary-nav ul li.active>a:only-child
     *       .nav-link-text::before` as a 5px dot; bundled fallback is
     *       --danger-500 (magenta) which is what we saw leaking
     *       through before this rename. */
    --app-nav-link-hover-color:         var(--syno-primary-light);
    --app-nav-item-active-bg:           var(--syno-primary);          /* solid brand-blue pill on submenu items */
    --app-nav-link-active-color:        var(--syno-primary);          /* brand blue reads on both white + navy nav surfaces */
    --app-nav-active-indicator:         var(--syno-primary);          /* 5px brand-blue dot beside the active item */

    /* Sidebar surface — match the SmartAdmin demo (white sidebar in
     * light theme). The bundled `.app-sidebar` selector falls back to
     * `var(--bs-body-bg)` when --app-nav-background isn't set, which
     * is our `--theme-bg` (#F7FAFC light gray). Setting it explicitly
     * to white decouples the sidebar from the page background. The
     * `html.set-nav-dark` block below overrides to brand navy. */
    --app-nav-background:               #FFFFFF;

    /* Content-area surface — matches the page bg so the body,
     * content region, and transparent footer all paint the same
     * #F7FAFC with no horizontal color step. */
    --app-content-background:           var(--theme-bg);
}

/* Dark sidebar — applied via SmartAdmin's `set-nav-dark` class on
 * <html>. Light theme intentionally inherits SmartAdmin's bundled
 * light-nav defaults (white surface, dark text, faint hover wash). */
html.set-nav-dark {
    --app-nav-background:               var(--syno-darker);
    --app-nav-link-color:               #C9D2E0;
    --app-nav-title-color:              rgba(255, 255, 255, 0.45);
    --app-nav-border-color:             rgba(255, 255, 255, 0.06);
    --app-nav-item-hover-bg:            #1B2640;               /* solid slate, Stripe-style */
    --app-nav-collapse-sign-color:      rgba(255, 255, 255, 0.5);
}

/* ════════════════════════════════════════════════════════════════════════════
 * Components — every rule uses --theme-* tokens. Theme switch is free.
 * ════════════════════════════════════════════════════════════════════════════ */

/* ── Body + body font stack ──────────────────────────────────────────────── */
body,
.app-wrap,
.io-shell {
    background: var(--theme-bg);
    color:      var(--theme-text);
    /* Montserrat is the SynoForce brand body face (matches the look of
     * io.synoforce.dev). Fallback chain: system-ui → Apple → Segoe →
     * Roboto → Helvetica → Arial → generic sans. font-display:swap on
     * the @font-face block means the page paints in the fallback while
     * the woff2 streams in — no FOIT. */
    font-family: 'Montserrat', system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}
/* Push the font onto html too so any unstyled native element
 * (form inputs without explicit font, native select dropdowns)
 * picks up Montserrat instead of the user-agent default. */
html { font-family: 'Montserrat', system-ui, -apple-system, sans-serif; }

/* ── Headings + brand-strong elements use SynoForce ────────────────────── */
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6,
.display-1, .display-2, .display-3, .display-4, .display-5, .display-6,
.subheader-title,
.navbar-brand,
.app-logo,
.font-display {
    font-family: 'SynoForce', 'Inter', system-ui, sans-serif;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--theme-text);
}
.display-1, .display-2, .display-3,
h1, .h1 {
    letter-spacing: -0.02em;
}

code, kbd, pre, samp, .font-monospace, .text-monospace {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

/* ── Inline code / kbd / samp — readable pill style ────────────────────
 *
 * Bootstrap's default paints inline <code> with a hot-pink colour
 * (`--bs-code-color: #d63384`) on transparent. Against our brand
 * navy / cyan palette it reads as a jarring magenta blot — e.g.
 * the password helper line "8-32 chars... !@#$%^&*()" and the
 * "UserRepository::update*" placeholder note on the profile page.
 *
 * Re-skin as a small pill that uses brand-tinted surface + readable
 * text. Pre blocks keep more breathing room. */
:not(pre) > code,
kbd,
samp {
    display:         inline-block;
    padding:         0.125rem 0.375rem;
    background:      rgba(1, 162, 253, 0.10);
    color:           var(--syno-primary);
    border:          1px solid rgba(1, 162, 253, 0.20);
    border-radius:   4px;
    font-size:       0.875em;
    line-height:     1.4;
}
html[data-bs-theme="dark"] :not(pre) > code,
html[data-bs-theme="dark"] kbd,
html[data-bs-theme="dark"] samp {
    background:    rgba(74, 188, 254, 0.12);
    color:         var(--syno-primary-light);
    border-color:  rgba(74, 188, 254, 0.25);
}
pre {
    padding:         0.75rem 1rem;
    background:      var(--theme-surface-alt);
    border:          1px solid var(--theme-border);
    border-radius:   8px;
    overflow-x:      auto;
}
pre code {
    /* Inside pre, drop the pill styling — pre carries the box. */
    background:    transparent;
    border:        0;
    padding:       0;
    color:         var(--theme-text);
    font-size:     inherit;
}

.text-muted {
    color: var(--theme-text-muted) !important;
}

/* ── Links — brand rule "must be underlined on hover" ──────────────────── */
a {
    color: var(--theme-link);
    text-decoration: none;
}
a:hover,
a:focus {
    color: var(--theme-link-hover);
    text-decoration: underline;
}

/* ── Buttons — brand-fixed colours, theme-agnostic ─────────────────────── */
.btn-primary {
    --bs-btn-bg:                 var(--syno-primary);
    --bs-btn-border-color:       var(--syno-primary);
    --bs-btn-hover-bg:           var(--syno-primary-light);
    --bs-btn-hover-border-color: var(--syno-primary-light);
    --bs-btn-color:              #fff;
    --bs-btn-hover-color:        #fff;
    --bs-btn-active-bg:          var(--syno-dark);
    --bs-btn-active-border-color:var(--syno-dark);
    --bs-btn-disabled-bg:        var(--syno-primary);
    --bs-btn-disabled-border-color: var(--syno-primary);
    /* Brand-blue lift shadow at rest — matches the SynoForce
     * brand-deck button treatment ("primary buttons have a soft
     * cyan glow underneath"). The shadow stays the same on hover;
     * a stronger version kicks in on :active (further down). */
    box-shadow: 0 2px 6px 0 rgba(1, 162, 253, 0.35);
}
.btn-primary:active,
.btn-primary:focus-visible {
    box-shadow: 0 4px 12px 0 rgba(1, 162, 253, 0.45);
}
.btn-secondary {
    --bs-btn-bg:                 var(--syno-primary-light);
    --bs-btn-border-color:       var(--syno-primary-light);
    --bs-btn-color:              var(--syno-darker);
    --bs-btn-hover-bg:           var(--syno-primary);
    --bs-btn-hover-border-color: var(--syno-primary);
    --bs-btn-hover-color:        #fff;
}
.btn-outline-primary {
    --bs-btn-color:              var(--syno-primary);
    --bs-btn-border-color:       var(--syno-primary);
    --bs-btn-hover-bg:           var(--syno-primary);
    --bs-btn-hover-border-color: var(--syno-primary);
    --bs-btn-hover-color:        #fff;
}
.btn-outline-danger {
    --bs-btn-color:              var(--syno-error);
    --bs-btn-border-color:       var(--syno-error);
    --bs-btn-hover-bg:           var(--syno-error);
    --bs-btn-hover-border-color: var(--syno-error);
    --bs-btn-hover-color:        #fff;
}

/* SynoForce CTA — opt-in orange action layer. Use sparingly (5–10% UI).
 * Reserved for revenue/action contexts (Send quote / Get started); never
 * routine admin saves. */
.btn-syno-cta {
    background:    var(--syno-cta);
    border:        1px solid var(--syno-cta);
    color:         #ffffff;
    box-shadow:    0 4px 14px rgba(249, 115, 22, 0.35);
    transition:    background-color .15s ease, box-shadow .15s ease;
}
/* Any button that contains an inline icon needs to be a flex container
   so the icon's baseline doesn't sit below the text caps. Targets
   `.sa-icon` (our sprite icons) and bare `<svg>` children so both
   patterns align consistently. The `me-1` / `gap-1` spacing utilities
   on individual buttons still work inside the flex container. */
.btn:has(> .sa-icon),
.btn:has(> svg) {
    display:     inline-flex;
    align-items: center;
}
/* .btn-syno-cta is the only button that bakes its own spacing in
   (no me-1 needed); kept here so the icon-text gap is consistent on
   every CTA without per-call-site classes. */
.btn-syno-cta {
    gap: 0.4rem;
}
.btn-syno-cta:hover,
.btn-syno-cta:focus {
    background:    var(--syno-cta-hover);
    border-color:  var(--syno-cta-hover);
    color:         #ffffff;
    box-shadow:    0 6px 18px rgba(249, 115, 22, 0.45);
}

/* ── Badges — brand-fixed ──────────────────────────────────────────────── */
.badge.bg-info {
    background-color: var(--syno-cyan) !important;
    color:            var(--syno-darker) !important;
}
.badge.bg-warning {
    background-color: var(--syno-warning) !important;
    color:            var(--syno-darker) !important;
}
.badge.bg-secondary {
    background-color: var(--syno-border-hover) !important;
    color:            var(--syno-text) !important;
}
/* `.bg-success` light theme is fine straight from Bootstrap (#198754
 * with white text). The issue is dark mode: SmartAdmin's dark
 * variant paints the badge as a desaturated dark green with a
 * near-black text colour, so "active" / "ok" labels become hard to
 * read on the darkest table rows. Override to a tinted-translucent
 * pattern matching .alert-success above. */
html[data-bs-theme="dark"] .badge.bg-success {
    background-color: rgba(16, 185, 129, 0.18) !important;
    color:            #6EE7B7 !important;
}
html[data-bs-theme="dark"] .badge.bg-secondary {
    background-color: rgba(255, 255, 255, 0.12) !important;
    color:            var(--syno-text-inverse) !important;
}
/* Same defense for danger + warning so a future "blocked" /
 * "needs attention" badge doesn't repeat the contrast problem. */
html[data-bs-theme="dark"] .badge.bg-danger {
    background-color: rgba(239, 68, 68, 0.18) !important;
    color:            #FCA5A5 !important;
}
html[data-bs-theme="dark"] .badge.bg-warning {
    background-color: rgba(245, 158, 11, 0.18) !important;
    color:            #FCD34D !important;
}
/* `bg-light text-dark` is a popular pattern across the codebase
 * for neutral status pills ("draft", "info", "todo"). In dark
 * theme the bg-light wash (#F8F9FA) reads as a glaring white
 * blob on the elevated card background. Mirror the secondary
 * treatment so neutral pills stay neutral in both modes. */
html[data-bs-theme="dark"] .badge.bg-light,
html[data-bs-theme="dark"] .badge.bg-light.text-dark {
    background-color: rgba(255, 255, 255, 0.12) !important;
    color:            var(--syno-text-inverse) !important;
}

/* ── Forms ────────────────────────────────────────────────────────────── */
.form-control,
.form-select {
    background-color: var(--theme-input-bg);
    color:            var(--theme-text);
    border-color:     var(--theme-input-border);
}
.form-control::placeholder {
    color: var(--theme-text-muted);
}
.form-control:focus,
.form-select:focus {
    background-color: var(--theme-input-bg);
    color:            var(--theme-text);
    border-color:     var(--syno-primary);
    box-shadow:       0 0 0 0.2rem rgba(1, 162, 253, 0.20);
}
.form-control.is-invalid,
.form-select.is-invalid {
    border-color: var(--syno-error);
}
.form-label,
.form-check-label {
    color: var(--theme-text);
}

/* Native <option> elements inside <select> dropdowns — Chromium only
 * partially honours `color-scheme: dark` on the popover (the open
 * options list). Set the colours explicitly so:
 *   - the closed select shows our dark surface (handled above),
 *   - the OPEN dropdown list also paints dark navy + white text,
 *   - selected/hovered options use brand-blue.
 * Light theme leaves the browser defaults alone (white + dark text). */
html[data-bs-theme="dark"] select.form-select,
html[data-bs-theme="dark"] select {
    /* Set on the select itself so the popover inherits these */
    background-color: var(--theme-input-bg);
    color:            var(--theme-text);
}
html[data-bs-theme="dark"] option {
    background-color: var(--syno-darker);
    color:            #FFFFFF;
}
html[data-bs-theme="dark"] option:checked,
html[data-bs-theme="dark"] option:hover {
    background-color: var(--syno-primary);
    color:            #FFFFFF;
}

/* `<input type="date">` calendar-picker indicator: invert in dark so
 * the icon reads on the dark input bg. The popover itself is OS-
 * native and we can't restyle it, but the icon is part of our DOM. */
html[data-bs-theme="dark"] input[type="date"]::-webkit-calendar-picker-indicator,
html[data-bs-theme="dark"] input[type="time"]::-webkit-calendar-picker-indicator,
html[data-bs-theme="dark"] input[type="datetime-local"]::-webkit-calendar-picker-indicator {
    filter: invert(1);
}

/* Native checkbox / radio accent — brand primary across themes */
input[type="checkbox"],
input[type="radio"],
.form-check-input {
    accent-color: var(--syno-primary);
}
.form-check-input:checked {
    background-color: var(--syno-primary);
    border-color:     var(--syno-primary);
}
.form-check-input:focus {
    border-color: var(--syno-primary);
    box-shadow:   0 0 0 0.25rem rgba(1, 162, 253, 0.25);
}

/*
   Brand-orange CTA variant of the Bootstrap form-switch. Opt-in via
   `.form-switch.syno-switch-cta`. We use this for boolean settings on
   the configuration page so the on-state reads as a deliberate action
   ("yes, enable this") rather than a passive checkmark — matches the
   SynoForce "orange = CTA" rule documented at the top of this file.
*/
/*
   The wrapper renders inline-flex so the switch + label sit on one
   baseline-aligned row instead of Bootstrap's default block layout
   (where the label drops below the switch). `ps-0` cancels the
   left padding Bootstrap adds to make room for the absolutely-
   positioned input; `min-height:0` defeats the 1.5em min-height
   that otherwise pushes the row taller than its content.
*/
.form-switch.syno-switch-cta {
    display:        inline-flex;
    align-items:    center;
    gap:            0.5rem;
    padding-left:   0;
    min-height:     0;
}
.form-switch.syno-switch-cta .form-check-input {
    width:        2.4em;
    height:       1.25em;
    margin:       0;
    flex-shrink:  0;
    cursor:       pointer;
    border-color: var(--theme-border);
    position:     static;
}
.form-switch.syno-switch-cta .form-check-label {
    margin: 0;
    cursor: pointer;
}
.form-switch.syno-switch-cta .form-check-input:checked {
    background-color: var(--syno-cta);
    border-color:     var(--syno-cta);
}
.form-switch.syno-switch-cta .form-check-input:focus {
    border-color: var(--syno-cta);
    box-shadow:   0 0 0 0.2rem rgba(249, 115, 22, 0.25);
}

/*
   Brand-orange badge — `.bg-syno-cta` paired with `.badge`.
   Used by the sidebar Helpdesk pill (assigned-ticket count) and
   anywhere else a CTA-tone count needs to draw the eye.
*/
.badge.bg-syno-cta {
    background-color: var(--syno-cta) !important;
    color:            #FFFFFF;
}

/*
   Alpine.js [x-cloak] global hide rule.
   ------------------------------------------------------------------
   Any element with `x-cloak` stays hidden until Alpine has parsed
   the page and removed the attribute. Prevents the brief flash of
   un-styled / state-empty content (e.g. an action bar showing
   "0 selected" before Alpine binds the count).
*/
[x-cloak] { display: none !important; }

/*
   Breadcrumb trail.
   ------------------------------------------------------------------
   Page-top trail rendered by templates/partials/_breadcrumbs.twig.
   Bootstrap's stock .breadcrumb gives us the chevron separator;
   we adjust spacing, color tokens, and add a non-link variant
   (.breadcrumb-item-static) for section-root labels that point
   nowhere (e.g. "System" / "Access" — those URL roots aren't real
   listing pages).
*/
.app-breadcrumb-nav .breadcrumb {
    --bs-breadcrumb-divider-color: var(--theme-text-muted);
    --bs-breadcrumb-item-active-color: var(--theme-text);
    --bs-breadcrumb-item-padding-x: 0.5rem;
    margin-bottom: 0.75rem;
    padding: 0;
    font-size: 13px;
}
.app-breadcrumb-nav .breadcrumb-item a {
    color:           var(--theme-text-muted);
    text-decoration: none;
}
.app-breadcrumb-nav .breadcrumb-item a:hover {
    color:           var(--syno-cta);
    text-decoration: underline;
}
/* Middle items (section roots without a destination). Reads as a
   label, not a link or a current-page marker. */
.app-breadcrumb-nav .breadcrumb-item.breadcrumb-item-static {
    color:  var(--theme-text-muted);
    cursor: default;
}
/* Current page — slightly stronger weight to terminate the trail
   without being so loud it competes with the H1 below. */
.app-breadcrumb-nav .breadcrumb-item.active {
    color:       var(--theme-text);
    font-weight: 500;
}

/*
   In-page settings anchor rail.
   ------------------------------------------------------------------
   Compact vertical nav used on /system/settings/. Avoids Bootstrap's
   .list-group (which renders too card-like) in favor of a tight
   inline list with a left-border accent for the active item — same
   pattern Linear / Notion use for settings sub-nav.

   Active state is JS-driven (IntersectionObserver tracks which
   category card is on screen and adds `.is-active`). The hover and
   active styles avoid heavy backgrounds so the rail stays in the
   visual background until the operator looks at it directly.
*/
.settings-nav {
    border-left: 1px solid var(--theme-border);
    padding-left: 0;
}
.settings-nav .settings-nav-link {
    display:         flex;
    align-items:     center;
    gap:             0.5rem;
    padding:         0.35rem 0.75rem;
    margin-left:     -1px;                    /* overlap the rail */
    border-left:     2px solid transparent;
    color:           var(--theme-text-muted);
    text-decoration: none;
    font-size:       13px;
    line-height:     1.4;
    transition:      color 0.12s, border-color 0.12s, background 0.12s;
}
.settings-nav .settings-nav-link:hover {
    color:        var(--theme-text);
    background:   var(--theme-surface-alt);
}
.settings-nav .settings-nav-link.is-active {
    color:        var(--theme-text);
    border-left:  2px solid var(--syno-cta);
    font-weight:  600;
}
.settings-nav .settings-nav-link .settings-nav-count {
    margin-left: auto;
    font-size:   11px;
    color:       var(--theme-text-muted);
}
.settings-nav .settings-nav-link.is-active .settings-nav-count {
    color: var(--syno-cta);
}
.settings-nav .settings-nav-link.is-muted {
    opacity: 0.4;
}

/* ── Cards ─────────────────────────────────────────────────────────────── */
.card {
    --bs-card-bg:           var(--theme-surface);
    --bs-card-color:        var(--theme-text);
    --bs-card-border-color: var(--theme-border);
    --bs-card-border-radius: 8px;
    box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
html[data-bs-theme="dark"] .card {
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}
.card-header {
    background-color:    var(--theme-surface-alt);
    border-bottom-color: var(--theme-border);
    color:               var(--theme-text);
}

/* ── Tables ────────────────────────────────────────────────────────────── */
.table {
    --bs-table-bg:           transparent;
    --bs-table-color:        var(--theme-text);
    --bs-table-border-color: var(--theme-border);
    --bs-table-hover-bg:     var(--theme-table-hover-bg);
}
.table thead th {
    color:            var(--theme-text-secondary);
    background-color: var(--theme-table-header-bg);
    border-bottom:    1px solid var(--theme-border);
    font-weight:      600;
    text-transform:   uppercase;
    font-size:        0.75rem;
    letter-spacing:   0.05em;
}
/* Dark-mode table header: previous `rgba(255, 255, 255, 0.04)` on top
 * of the elevated card bg #131E37 produced ALMOST-NO contrast (header
 * read as same colour as rows or even darker depending on perception).
 * Bump to 8% white wash so the header is clearly elevated above
 * transparent rows. */
html[data-bs-theme="dark"] .table thead th {
    background-color: rgba(255, 255, 255, 0.08);
    color:            #FFFFFF;
}

/* ── Header bell + popover ─────────────────────────────────────────
 * The bell sits in the IO header next to the user-menu avatar.
 * Badge reads as an emphatic count when there's unread activity;
 * dropdown shows the most recent 10 entries with per-row links.
 * Alpine controller (window.appBell) lives in app_layout.twig. */
.bell-trigger { position: relative; }
.bell-badge {
    position:       absolute;
    top:            4px;
    right:          4px;
    min-width:      18px;
    height:         18px;
    padding:        0 5px;
    border-radius:  10px;
    background:     var(--syno-cta);
    color:          #FFFFFF;
    font-size:      10px;
    font-weight:    700;
    line-height:    18px;
    text-align:     center;
}
.bell-popover {
    /*
       position: fixed against the viewport — the popover is
       teleported to <body> on init (see app_layout.twig), so its
       only ancestors are <html> + <body>. We can't anchor to the
       bell-trigger via position:relative + top:100% + right:0
       anymore because that ancestor relationship is gone after
       teleport. Anchor to the viewport's top-right area directly:
       just below the header, against the right edge with breathing
       room for the avatar that sits to the right of the bell.

       Phone bottom-sheet variant (in the 767.98px media block at
       the bottom of this file) overrides position to bottom-anchored,
       full-width.
    */
    position:       fixed;
    top:            calc(var(--app-header-height, 88px) + 6px);
    right:          16px;
    width:          360px;
    max-width:      calc(100vw - 32px);
    background:     var(--theme-surface);
    border:         1px solid var(--theme-border);
    border-radius:  8px;
    box-shadow:     0 10px 30px rgba(0,0,0,0.18);
    z-index:        1050;
    display:        flex;
    flex-direction: column;
    overflow:       hidden;
}
.bell-popover-head,
.bell-popover-foot {
    display:        flex;
    align-items:    center;
    padding:        8px 12px;
    border-bottom:  1px solid var(--theme-border);
    background:     var(--theme-surface-alt);
    font-size:      12px;
}
.bell-popover-foot {
    border-bottom: none;
    border-top:    1px solid var(--theme-border);
}
.bell-popover-prefs {
    padding:        6px 12px;
    border-bottom:  1px solid var(--theme-border);
    color:          var(--theme-text-muted);
}
.bell-list {
    list-style: none;
    padding:    0;
    margin:     0;
    max-height: 360px;
    overflow-y: auto;
}
.bell-item {
    border-bottom: 1px solid var(--theme-border);
}
.bell-item:last-child   { border-bottom: none; }
.bell-item-unread       { background: rgba(1, 162, 253, 0.06); }
.bell-item-link {
    display:        block;
    padding:        10px 12px;
    color:          var(--theme-text);
    text-decoration: none;
}
.bell-item-link:hover   { background: var(--theme-surface-alt); }
.bell-item-title        { font-size: 13px; line-height: 1.35; }
.bell-item-time         { font-size: 11px; margin-top: 2px; }
.bell-empty {
    padding:    18px;
    text-align: center;
    color:      var(--theme-text-muted);
    font-size:  12px;
}

/* ── Sidebar nav — submenu active state override ───────────────────
 * The bundled SmartAdmin theme paints sub-menu active leaves with a
 * SOLID --app-nav-item-active-bg pill. Our brand tokens set both
 * --app-nav-link-active-color AND --app-nav-item-active-bg to
 * var(--syno-primary), so the leaf renders brand-blue text on
 * brand-blue background — the label vanishes.
 *
 * Modern-SaaS treatment: kill the pill entirely; the bundled
 * ::before dot indicator (5px, brand-blue) on the LI already marks
 * "this is the active leaf" cleanly. Just bump the label to
 * brand-blue + semibold so the active row reads as emphasis-only
 * rather than as a bordered "quote" or filled pill. */
.primary-nav ul ul li.active:not(.open):not(.has-ul) a:only-child {
    background-color: transparent !important;
    color:            var(--syno-primary) !important;
    font-weight:      600;
}

/* Indicator choice: the bundled CSS attaches TWO ::before dot
 * indicators to an active item — one on the <li>'s left edge and a
 * second on .nav-link-text's right edge. Two dots on the same row
 * is noise. Suppress the LEFT (LI) one and keep the RIGHT
 * (.nav-link-text) one — a trailing dot beside the label reads as
 * a cleaner "you are here" marker than a leading bullet and matches
 * how the minified-sidebar already uses the right-edge dot. */
.primary-nav ul ul li.active:not(.open):not(.has-ul)::before {
    content: none !important;
}

/* Active TOP-LEVEL parent with a sub-menu — two visual states:
 *
 *   1. `.active.active-leaf` — the parent itself is the active page
 *      (e.g. /helpdesk is the inbox view). Full indicator: bold
 *      brand-blue label + trailing dot.
 *
 *   2. `.active` (without `.active-leaf`) — a sub-menu child is the
 *      active page; the parent is just "the section you're in".
 *      Muted indicator: bold label only, no dot — the child has
 *      its own leaf-dot so two dots on the same column would
 *      double up.
 *
 * Distinction matches modern-SaaS sidebar idiom (Linear, GitHub,
 * Notion): emphasis-on-parent shows containment, dot-on-leaf shows
 * the exact current page. */
.primary-nav > ul > li.active.has-ul > a .nav-link-text {
    color:       var(--syno-primary);
    font-weight: 600;
}
/* State 1: parent IS the active leaf (no child active). */
.primary-nav > ul > li.active.active-leaf.has-ul > a .nav-link-text {
    position: relative;
}
.primary-nav > ul > li.active.active-leaf.has-ul > a .nav-link-text::before {
    content:       "";
    width:         5px;
    height:        5px;
    background:    var(--syno-primary);
    position:      absolute;
    border-radius: 100%;
    right:         -1rem;
    top:           50%;
    transform:     translate(0, -50%);
}

/* (`.app-sidebar .app-logo` padding/border override removed —
 * pure SmartAdmin bundled styling.) */

/* ── Header / footer ───────────────────────────────────────────────────── */
.app-header {
    background:    var(--theme-surface);
    border-bottom: 1px solid var(--theme-border);
}
.app-footer {
    /* Transparent footer — no background fill, no top border.
     * The footer sits on whatever the page chrome provides; nothing
     * about it should pop. */
    background: transparent;
    color:      var(--theme-text-muted);
    border-top: 0;
}

/* Scroll-to-top — small unobtrusive button on the right edge of the
 * footer. Clicking it smooth-scrolls the page back to the top. No
 * JS framework needed: a plain `<a href="#top">` would work, but a
 * <button> with a single line of inline JS gives smooth-scroll
 * without polluting the URL hash. */
.app-footer-scroll-top {
    display:        inline-flex;
    align-items:    center;
    justify-content: center;
    width:          32px;
    height:         32px;
    margin-left:    auto;
    border:         1px solid var(--theme-border);
    border-radius:  6px;
    background:     transparent;
    color:          var(--theme-text-muted);
    transition:     color .15s ease, border-color .15s ease, background .15s ease;
}
.app-footer-scroll-top:hover,
.app-footer-scroll-top:focus-visible {
    color:        var(--syno-primary);
    border-color: var(--syno-primary);
    background:   rgba(1, 162, 253, 0.08);
}
.app-footer-scroll-top .sa-icon {
    width:  16px;
    height: 16px;
}

/* ── User-avatar button (header trigger) ────────────────────────────────
 *
 * Replaces the generic SVG user-icon trigger with a circular avatar
 * showing the email's first letter. Same colour/size language as the
 * avatar block inside the open dropdown so the trigger and the
 * dropdown-header avatar feel like one element.
 *
 * When real avatars land (Phase 1+ profile upload), replace the
 * `.io-user-avatar-initial <span>` with `<img>` and add
 * `.io-user-avatar-img { width:100%; height:100%; object-fit:cover; }`
 * — the surrounding round button frame stays. */
.io-user-avatar-btn {
    width:           36px;
    height:          36px;
    padding:         0;
    border-radius:   50%;
    overflow:        hidden;
    background:      var(--syno-primary);
    color:           #FFFFFF;
    border:          0;
    transition:      box-shadow .15s ease, transform .05s ease;
}
.io-user-avatar-btn:hover,
.io-user-avatar-btn:focus-visible,
.io-user-avatar-btn[aria-expanded="true"] {
    box-shadow: 0 0 0 3px rgba(1, 162, 253, 0.25);
}
.io-user-avatar-btn:active {
    transform: scale(0.96);
}
.io-user-avatar-initial {
    font-weight:     600;
    font-size:       0.9rem;
    line-height:     1;
    text-transform:  uppercase;
    letter-spacing:  0;
}
.io-user-avatar-img {
    width:       100%;
    height:      100%;
    object-fit:  cover;
    display:     block;
    border-radius: 50%;
}

/* ── Fullscreen toggle ──────────────────────────────────────────────────
 *
 * Sits next to the theme picker. Hidden on viewports narrower than
 * md (768px) because the Fullscreen API on mobile is inconsistent
 * and the gesture isn't useful at phone widths. Hover lifts to
 * brand-blue tint; matches `.app-footer-scroll-top`'s language. */
.io-fullscreen-btn {
    width:           36px;
    height:          36px;
    padding:         0;
    border-radius:   8px;
    background:      transparent;
    color:           var(--theme-text-muted);
    border:          1px solid transparent;
    transition:      color .15s ease, background .15s ease, border-color .15s ease;
}
.io-fullscreen-btn:hover,
.io-fullscreen-btn:focus-visible {
    color:        var(--syno-primary);
    background:   rgba(1, 162, 253, 0.08);
    border-color: var(--theme-border);
}
.io-fullscreen-btn .sa-icon {
    width:  18px;
    height: 18px;
}

/* Breadcrumbs use SmartAdmin's bundled `.breadcrumb` / `.breadcrumb-item`
 * styling out of the box — see templates/partials/_breadcrumbs.twig and
 * App\Middleware\BreadcrumbsMiddleware. No custom CSS layer here on
 * purpose; if the bundled look diverges from what we want later, add a
 * narrow override block below this comment rather than re-rolling the
 * whole component. */

/* ── Audit-log diff (git-style before/after) ────────────────────────────
 *
 * Rendered by templates/system/audit_log/index.twig (inline expansion
 * row) and entry detail page. The PHP side (App\Support\JsonDiff)
 * wraps each line in `<span class="diff-add|diff-del|diff-ctx">`;
 * the prefix character (+/-/space) is part of the span text.
 *
 * Two-axis colour: brand-green for additions, brand-red for deletions,
 * muted text for context. Backgrounds are 6% washes so the lines read
 * as a single block, not as cards. */
.audit-diff {
    margin:    0;
    padding:   0.5rem 0;
    font-size: 0.8125rem;
    line-height: 1.5;
    background: transparent;
    color: var(--theme-text);
    overflow-x: auto;
}
.audit-diff code {
    display:   block;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.audit-diff .diff-add {
    display:    block;
    background: rgba(16, 185, 129, 0.10);      /* var(--syno-success) wash */
    color:      #047857;
    padding:    0 0.5rem;
}
.audit-diff .diff-del {
    display:    block;
    background: rgba(239, 68, 68, 0.10);       /* var(--syno-error) wash */
    color:      #991B1B;
    padding:    0 0.5rem;
}
.audit-diff .diff-ctx {
    display:    block;
    color:      var(--theme-text-muted);
    padding:    0 0.5rem;
}
html[data-bs-theme="dark"] .audit-diff .diff-add { color: #6EE7B7; }
html[data-bs-theme="dark"] .audit-diff .diff-del { color: #FCA5A5; }

/* Detail-row tweaks inside the audit log table — match the surface
 * to the page bg so the expansion blends with the rest of the
 * content area, not the white cards above. */
tr.audit-log-detail > td {
    background-color: var(--theme-bg) !important;
}

/* ── Dropdown menus (user-menu, theme switcher) ────────────────────────── */
.dropdown-menu {
    --bs-dropdown-bg:                var(--theme-surface);
    --bs-dropdown-color:             var(--theme-text);
    --bs-dropdown-border-color:      var(--theme-border);
    --bs-dropdown-link-color:        var(--theme-text);
    --bs-dropdown-link-hover-color:  var(--theme-link);
    --bs-dropdown-link-hover-bg:     var(--theme-surface-alt);
    --bs-dropdown-divider-bg:        var(--theme-border);
}

/* ── Alerts ────────────────────────────────────────────────────────────── */
.alert {
    --bs-alert-border-radius: 8px;
}
.alert-success { background: #ECFDF5; color: #065F46; border-color: #A7F3D0; }
.alert-warning { background: #FFFBEB; color: #92400E; border-color: #FCD34D; }
.alert-danger,
.alert-error   { background: #FEF2F2; color: #991B1B; border-color: #FCA5A5; }
.alert-info    { background: #ECFEFF; color: #0E7490; border-color: #A5F3FC; }
/* Dark-mode alert surfaces — keep semantic hues, darken the wash. */
html[data-bs-theme="dark"] .alert-success { background: rgba(16, 185, 129, 0.12); color: #6EE7B7; border-color: rgba(16, 185, 129, 0.25); }
html[data-bs-theme="dark"] .alert-warning { background: rgba(245, 158, 11, 0.12); color: #FCD34D; border-color: rgba(245, 158, 11, 0.25); }
html[data-bs-theme="dark"] .alert-danger,
html[data-bs-theme="dark"] .alert-error   { background: rgba(239, 68, 68, 0.12); color: #FCA5A5; border-color: rgba(239, 68, 68, 0.25); }
html[data-bs-theme="dark"] .alert-info    { background: rgba(45, 226, 230, 0.10); color: #A5F3FC; border-color: rgba(45, 226, 230, 0.25); }

/* ── SynoForce gradients — utility classes for headers, hero strips ───── */
.syno-gradient-sky    { background: linear-gradient(135deg, #01A2FD 0%, #4ABCFE 100%); }
.syno-gradient-night  { background: linear-gradient(135deg, #0E182E 0%, #070C17 100%); }
.syno-gradient-aqua   { background: linear-gradient(135deg, #01A2FD 0%, #2DE2E6 100%); }
.syno-gradient-cta    { background: linear-gradient(135deg, #F97316 0%, #FB923C 100%); }
/* Brand rule: do NOT use gradients on text. Provided only as backgrounds. */

.syno-overlay-tint {
    background-color: rgba(1, 162, 253, 0.10);
}

/* ── Focus ring — cyan accent per brand guide ─────────────────────────── */
:focus-visible {
    outline:        2px solid var(--syno-cyan);
    outline-offset: 2px;
}

/* ─────────────────────────────────────────────────────────────────
 * Header theme picker + user menu — ported from io.synoforce.dev
 * on 2026-05-12 per Igor's preference. The reference uses a small
 * 3-icon button group for the theme (replacing a binary toggle)
 * plus a dark glassmorphism dropdown for the account menu with a
 * magenta-accented "Sign out" row.
 *
 * Visual contract:
 *   - Theme buttons sit in the header bar, transparent until
 *     active, then filled with the brand cyan + white icon.
 *   - User menu panel is near-opaque dark navy with a soft border
 *     and the same indigo-blue underglow as the auth card.
 *   - Menu items: icons in brand cyan, labels in white, hover
 *     lifts the row with a subtle cyan wash.
 *   - Sign out is visually distinct (magenta) — destructive
 *     actions should never look the same as navigational ones.
 * ───────────────────────────────────────────────────────────── */

/* Theme picker — 3-icon segmented pill in the header.
 *
 * Visual spec ported byte-for-byte from io.synoforce.dev/dashboard
 * (DevTools computed styles, 2026-05-12). The reference lives in
 * the SmartAdmin shell that synoforce.dev's IO surface uses; we
 * replicate the look against our own /scripts/core/theme.js
 * controller (data-theme-set attributes).
 *
 * Construction (every value below comes from the live DevTools
 * "Computed" panel on synoforce.dev — don't tune without checking):
 *   - Container is a 96×34 pill with a 1px dark-navy edge over the
 *     near-black `#070C17` background, reading as a quiet control
 *     rather than a button bar.
 *   - Each icon button is 30×28 with a fully-rounded radius. The
 *     active button gets a soft brand-cyan wash (18% alpha) and a
 *     LIGHT cyan icon (#7FD0FE) — gentle signal, not a stoplight.
 *   - Inactive icons are slate-400 grey (#A0AEC0); hover lifts to
 *     full white so the affordance is clear without a colour shift.
 *
 * Selector compatibility: support both `.active` (our controller's
 * native marker) AND `[aria-pressed="true"]` (the synoforce.dev
 * markup pattern). Future-proof against either convention. */
/* Container, buttons, and active state — defaults are LIGHT theme
 * (chip on a white header). Dark theme overrides further down. */
.theme-picker {
    display:        flex;
    align-items:    center;
    width:          96px;
    height:         34px;
    padding:        2px;
    border-radius:  999px;
    background:     #F1F5F9;            /* slate-100 — soft chip on white header */
    border:         1px solid #E2E8F0;  /* slate-200 edge */
}
.theme-picker-btn {
    flex:             1 1 auto;
    display:          inline-flex;
    align-items:      center;
    justify-content:  center;
    width:            30px;
    height:           28px;
    padding:          0;
    background:       transparent;
    border:            0;
    border-radius:     999px;
    color:             #64748B;   /* slate-500 — readable on light chip */
    transition:        background-color .15s ease, color .15s ease;
}
.theme-picker-btn:hover,
.theme-picker-btn:focus-visible {
    color:   #0F172A;   /* slate-900 on hover */
    outline: none;
}
/* Active state — scoped via .theme-picker > to outrank Bootstrap's
 * `.btn.active` (would otherwise win on specificity and force a
 * transparent background). Subtle dark wash on the light chip;
 * an iOS-style segmented control, not a saturated brand fill. */
.theme-picker > .theme-picker-btn.active,
.theme-picker > .theme-picker-btn[aria-pressed="true"] {
    background-color: rgba(0, 0, 0, 0.08);
    color:            #0F172A;
}
.theme-picker-btn .sa-icon { width: 18px; height: 18px; }

/* Dark-theme variant — same structure, inverted palette. Container
 * becomes near-black with a navy edge against the dark header;
 * icons drop to slate-400 muted, lift to white on hover/active;
 * active state is a 10% white wash for a calm "selected" signal. */
html[data-bs-theme="dark"] .theme-picker {
    background:    #070C17;
    border-color:  #1a253f;
}
html[data-bs-theme="dark"] .theme-picker-btn {
    color: #A0AEC0;   /* slate-400 */
}
html[data-bs-theme="dark"] .theme-picker-btn:hover,
html[data-bs-theme="dark"] .theme-picker-btn:focus-visible {
    color: #FFFFFF;
}
html[data-bs-theme="dark"] .theme-picker > .theme-picker-btn.active,
html[data-bs-theme="dark"] .theme-picker > .theme-picker-btn[aria-pressed="true"] {
    background-color: rgba(255, 255, 255, 0.10);
    color:            #FFFFFF;
}

/* User dropdown — dark glassmorphism panel. .user-menu scopes the
 * overrides so other dropdowns in the app keep their default look. */
.user-menu.dropdown-menu {
    background:        rgba(14, 24, 46, 0.96);
    border:            1px solid rgba(1, 162, 253, 0.10);
    border-radius:     12px;
    box-shadow:        0 12px 36px rgba(0, 0, 0, 0.55),
                       0 0 0 1px rgba(31, 38, 135, 0.10);
    backdrop-filter:   blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color:             #FFFFFF;
    overflow:          hidden;
    padding:           0.25rem 0;
}
/* Override Bootstrap's dropdown-divider so it reads on dark. */
.user-menu .dropdown-divider {
    border-top-color: rgba(255, 255, 255, 0.08);
}

/* Account header (avatar + email + status) — distinct visual band
 * at the top of the menu so the identity context is obvious before
 * the eye drops to the action items. */
.user-menu-header {
    background: rgba(255, 255, 255, 0.02);
}
.user-menu-email {
    color:       #FFFFFF;
    font-weight: 600;
    font-size:   0.95rem;
    line-height: 1.2;
}
.user-menu-status {
    color:     rgba(255, 255, 255, 0.55);
    font-size: 0.8125rem;
}

/* Menu items — icon + label rows. Icons in brand cyan for
 * informational/navigational actions; the label stays plain white
 * so it never competes with the icon for attention. */
.user-menu .user-menu-item {
    display:         flex;
    align-items:     center;
    gap:             0.75rem;
    padding:         0.6rem 1rem;
    color:           #FFFFFF;
    background:      transparent;
    border:           0;
    font-size:        0.9375rem;
    line-height:     1.4;
    transition:      background-color .12s ease, color .12s ease;
}
.user-menu .user-menu-item .sa-icon {
    width:        18px;
    height:       18px;
    flex-shrink:  0;
    color:        var(--syno-primary, #01A2FD);  /* cyan icons by default */
}
.user-menu .user-menu-item:hover,
.user-menu .user-menu-item:focus-visible {
    background: rgba(1, 162, 253, 0.10);
    color:      #FFFFFF;
    outline:    none;
}
.user-menu .user-menu-item:active {
    background: rgba(1, 162, 253, 0.18);
}

/* Sign out — magenta accent so the destructive action reads at a
 * glance and never blends with the other rows. The brand palette
 * doesn't ship a magenta of its own, so we use a vibrant pink
 * (TailwindCSS pink-500 / #EC4899) that pairs well with the brand
 * cyan + indigo. Bold weight reinforces the "this is the action
 * that ends your session" signal. */
.user-menu .user-menu-signout {
    color:       #EC4899;
    font-weight: 600;
}
.user-menu .user-menu-signout .sa-icon {
    color: #EC4899;
}
.user-menu .user-menu-signout:hover,
.user-menu .user-menu-signout:focus-visible {
    background: rgba(236, 72, 153, 0.12);
    color:      #F472B6;  /* lighter pink on hover */
}
.user-menu .user-menu-signout:hover .sa-icon,
.user-menu .user-menu-signout:focus-visible .sa-icon {
    color: #F472B6;
}

/* ═════════════════════════════════════════════════════════════════
 * Shell polish — 2026-05-12 per Igor's reference image.
 *
 * Goal:
 *   - Sidebar + page surface follow the theme (light bg in light
 *     mode, dark in dark mode).
 *   - Logo + coat have 4 DOM variants; CSS picks one based on
 *     theme + minified-without-hover state.
 *   - Minified sidebar shows ONLY the coat (no nav items); hover
 *     expands back to full width with labels (SmartAdmin built-in).
 *   - Body content area has a visible border in dark mode so it
 *     reads as a distinct region against the unified shell.
 *   - Collapse-button hover is brand orange, NOT SmartAdmin's
 *     leftover purple (which leaked through `--primary` we never
 *     remapped at the RGB level).
 * ═════════════════════════════════════════════════════════════ */

/* ── A. Remap SmartAdmin's purple primary RGB to brand cyan ──
 *
 * infraops-ui.min.css has `--primary: 136,106,181;` (= #886AB5,
 * SmartAdmin's purple). Many bundled rules use `rgba(var(--primary), N)`
 * — including the collapse-icon hover border. Hover-purple was leaking
 * through every nav focus state, ring, etc.
 *
 * Setting `--primary` here to the brand cyan RGB triple makes every
 * `rgba(var(--primary), ...)` consumer downstream paint in brand. */
:root {
    --primary: 1, 162, 253;            /* #01A2FD — brand primary */
}

/* (Theme-aware sidebar surface + light-mode nav text overrides
 * removed 2026-05-13. Sidebar uses pure SmartAdmin bundled
 * styling — dark navy rail in both light and dark themes,
 * matching the demo pages.) */

/* ── C. Header logo + coat variants ───────────────────────────
 *
 * Four <img> elements ship in the header's `.app-logo`. Exactly
 * ONE is `display: inline-block` at any time, based on theme +
 * minified/hover state. The sidebar's `.app-logo` is hidden by
 * SmartAdmin's bundled CSS so we don't render anything there.
 *
 * Selector matrix:
 *   default          → app-logo-full matching theme
 *   minified+!hover  → app-logo-coat matching theme
 *
 * SmartAdmin also narrows the header logo container width and
 * applies `overflow: hidden` when minified (so its built-in
 * wide-wordmark crops cleanly). We undo the overflow + restore
 * full width for our coat, since 32×32 is already the right
 * size and doesn't need clipping. */
/* ── Logo visibility ──────────────────────────────────────────────────────
 *
 * Four <img> in the header .app-logo. Exactly ONE visible at a time:
 *
 *   theme  state            class                visible?
 *   ─────  ───────          ──────────────       ────────
 *   light  expanded         logo-full-light      ✓
 *   dark   expanded         logo-full-dark       ✓
 *   light  collapsed (mini) logo-coat-light      ✓
 *   dark   collapsed (mini) logo-coat-dark       ✓
 *
 * Naming matches SFDash's convention so future shared CSS bits port
 * cleanly. `.set-nav-minified` is the SmartAdmin-bundled class that
 * the chevron toggles via smartApp.js. */

/* Default: hide all four. Show only the right one via rules below. */
.logo-full-light, .logo-full-dark,
.logo-coat-light, .logo-coat-dark { display: none !important; }

/* Expanded sidebar (default) — full wordmark per theme. */
.logo-full-light                           { display: inline-block !important; }
html[data-bs-theme="dark"]  .logo-full-light { display: none         !important; }
html[data-bs-theme="dark"]  .logo-full-dark  { display: inline-block !important; }

/* Minified sidebar — coat icon per theme; full wordmark hides. */
html.set-nav-minified .logo-full-light,
html.set-nav-minified .logo-full-dark {
    display: none !important;
}
html.set-nav-minified                          .logo-coat-light { display: inline-block !important; }
html.set-nav-minified[data-bs-theme="dark"]    .logo-coat-light { display: none         !important; }
html.set-nav-minified[data-bs-theme="dark"]    .logo-coat-dark  { display: inline-block !important; }

/* Sidebar (mobile drawer) logo — simpler two-class version. The
 * desktop sidebar hides .app-sidebar .app-logo via the bundled CSS;
 * these only paint at < lg breakpoint inside the drawer. */
.app-sidebar .app-logo .logo-light  { display: inline-block !important; }
.app-sidebar .app-logo .logo-dark   { display: none         !important; }
html[data-bs-theme="dark"] .app-sidebar .app-logo .logo-light { display: none         !important; }
html[data-bs-theme="dark"] .app-sidebar .app-logo .logo-dark  { display: inline-block !important; }

/* ── Coat sizing override ─────────────────────────────────────────────────
 *
 * SmartAdmin's bundled CSS forces `.app-logo > img` to a fixed
 *   width:     var(--logo-width)  = 11rem
 *   height:    var(--logo-height) = 2rem
 *   min-width: var(--logo-width)
 * which stretches our 32×32 coat into a weird 176×32 horizontal flag.
 * Constrain coat variants back to natural square; drop min-width. */
.app-logo > img.logo-coat-light,
.app-logo > img.logo-coat-dark {
    width:     32px !important;
    height:    32px !important;
    min-width: 0    !important;
    max-width: 32px !important;
}

/* When minified, the header .app-logo container should center the coat
 * rather than left-align it (since the column shrinks to ~70px). */
html.set-nav-minified .app-header .app-logo {
    justify-content: center !important;
    padding:         0 !important;
    overflow:        visible;
}

/* ── D. Body content area gets a visible outline in dark mode ──
 *
 * Per Igor 2026-05-12 reference: the unified-shell approach (no
 * sidebar/header borders) needs a body outline so the content
 * region still reads as a distinct surface. A 1px brand-border
 * tone wrapping the main content area gives that affordance
 * without breaking the seamless-shell feel.
 *
 * `.app-body` is SmartAdmin's main content region (everything
 * to the right of the sidebar, below the header). We outline its
 * top-left edge only — the right + bottom meet the viewport edge
 * so a full 1px border would just paint hairlines against the
 * scrollbar/window chrome. */
html[data-bs-theme="dark"] .app-body {
    border-top:  1px solid var(--theme-border);
    border-left: 1px solid var(--theme-border);
}

/* CSS Grid overflow defence ───────────────────────────────────
 *
 * SmartAdmin's `.app-wrap` is `display:grid` with
 * `grid-template-columns: var(--menu-width) auto`. The `auto`
 * track sizes to its content's min-content width by default —
 * which, when a descendant `.row` of Bootstrap cards has
 * `min-width:auto` cards inside, can be wider than the
 * viewport. The visible symptom: the dashboard content extends
 * past the right edge of the window and a horizontal scrollbar
 * appears.
 *
 * Setting `min-width:0` on the grid track + every flex/grid
 * descendant that owns the content stream tells the layout to
 * collapse to the available track width instead of expanding
 * the track to fit. The cards inside then properly compute
 * 25% / 50% of the *available* width and wrap correctly. */
.app-body,
.app-content,
.content-wrapper,
.app-body .row {
    min-width: 0;
}

/* ── E. Header + sidebar borders in dark mode ─────────────────
 *
 * Light mode keeps its native SmartAdmin separations (white
 * surfaces need outlines to make sense). Dark mode strips the
 * header bottom-border and sidebar right-border — the body outline
 * above carries the structural cue. Footer has no top border in
 * either theme (see `.app-footer` rule above). */
html[data-bs-theme="dark"] .app-sidebar  { border-right:  0; }
html[data-bs-theme="dark"] .app-header   { border-bottom: 0; }

/* ── F. Card elevation in dark mode ───────────────────────────
 *
 * Cards used to match the page bg exactly (after the
 * --app-panel-bg fix), reading as flat. Lift them one notch
 * lighter (#131E37 vs the #070C17 page) plus a hair-light inner
 * rim and a soft drop shadow. */
html[data-bs-theme="dark"] .card {
    --bs-card-bg:           #131E37;
    --bs-card-border-color: rgba(255, 255, 255, 0.06);
    box-shadow:
        0 1px 0 rgba(255, 255, 255, 0.04) inset,
        0 8px 24px rgba(0, 0, 0, 0.35);
}
html[data-bs-theme="dark"] .card .card-header,
html[data-bs-theme="dark"] .card .card-footer {
    background-color: transparent;
    border-color:     rgba(255, 255, 255, 0.06);
}

/* ── G. Collapse button — brand-orange hover ──────────────────
 *
 * SmartAdmin's bundled CSS hovers the chevron border to
 * `rgba(var(--primary), 0.8)`. We just remapped `--primary` to
 * brand cyan above, so the leftover-purple problem is gone. But
 * Igor specifically wants the hover signal in brand ORANGE
 * (the action-layer colour), not cyan. So we point the
 * SmartAdmin-bundled `--collapse-icon-border-hover` var at our
 * orange triple — one variable, every consumer flips. */
:root {
    --collapse-icon-border-hover: rgba(249, 115, 22, 0.85);  /* brand orange */
}
.collapse-icon {
    transition: background-color .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.collapse-icon:hover,
.collapse-icon:focus-visible {
    background-color: rgba(249, 115, 22, 0.12);
    border-color:     rgba(249, 115, 22, 0.85);
    box-shadow:       0 0 0 1px rgba(249, 115, 22, 0.45);
    outline:          none;
}
/* SmartAdmin paints the chevron polygon fill via
 * `var(--collapse-icon-border)` which is a low-alpha black/white.
 * On hover we re-tint it to the orange — the SVG attribute
 * `fill="#878787"` baked in the markup is overridden by the
 * stylesheet `fill` property. */
.collapse-icon       svg polygon { transition: fill .15s ease; }
.collapse-icon:hover svg polygon { fill: #F97316 !important; }

/* ── H. Minified sidebar — icons-only column + coat at top ────
 *
 * Per Igor 2026-05-12 (revised): in the minified state the
 * sidebar should show ICONS for each nav item (no labels) plus
 * the coat at the top. On hover the sidebar expands to full
 * width and labels fade back in.
 *
 * SmartAdmin already does most of this for free:
 *   - sidebar width shrinks to `--menu-width-minified`
 *   - `.primary-nav ul li a span { opacity: 0 }` fades the labels
 *   - on `.app-sidebar:hover` the opacity returns to 1
 *
 * Our additions:
 *   1. Hide the section titles entirely (their `::before` pseudo
 *      paints an ellipsis that would otherwise show as a dotted
 *      strip in the icon column).
 *   2. Centre each `<a>` so the icon sits middle-column when its
 *      label is invisible.
 *   3. Hide the auth-footer's "© SynoForce" content too, which
 *      is wider than the column and would clip ugly.
 *   4. NOTHING that hides the SVG icons themselves — they're
 *      what makes this column useful at all. */

/* Hide section titles (and their SmartAdmin ellipsis pseudo). */
.set-nav-minified:not(.set-nav-collapsed) .app-sidebar:not(:hover) .nav-title,
.set-nav-minified:not(.set-nav-collapsed) .app-sidebar:not(:hover) .nav-title::before {
    visibility: hidden;
    height:     0;        /* also collapse the height so the icon column tightens up */
    padding:    0;
    margin:     0;
    overflow:   hidden;
}
/* When hovered, restore titles to their full height. */
.set-nav-minified:not(.set-nav-collapsed) .app-sidebar:hover .nav-title {
    visibility: visible;
    height:     auto;
    padding:    1.25rem 1rem 0.5rem;
}
.set-nav-minified:not(.set-nav-collapsed) .app-sidebar:hover .nav-title::before {
    visibility: visible;
}

/* Shrink the nav container with the sidebar in the minified
 * state, so icons stay within the visible 70px column.
 *
 * Root cause we're working around: SmartAdmin's bundled CSS
 * hardcodes `.primary-nav { width: var(--menu-width) }` (272px)
 * REGARDLESS of whether the sidebar is collapsed. The strategy
 * is "keep the wide nav layout and clip it with the sidebar's
 * overflow-x: hidden" — fine for the transition perf, but it
 * means each `<li>` is still 224px wide, each `<a>` is centred
 * inside that 224px, and the SVG icons end up at x=90-134px —
 * OUTSIDE the visible 0-70px column. They render, just off-screen.
 *
 * Fixing this at the source: shrink `.primary-nav` to 100% in
 * the minified-no-hover state. The width cascades to nav-menu →
 * li → a, and the icons land in the visible column. Reflow cost
 * is negligible (<30 items) and SmartAdmin's opacity:0/1 label
 * transition still works because the items themselves widen back
 * to full when the sidebar :hover expands. */
.set-nav-minified:not(.set-nav-collapsed) .app-sidebar:not(:hover) .primary-nav {
    width:   100%;
    padding: 0.5rem 0;
}
.set-nav-minified:not(.set-nav-collapsed) .app-sidebar:not(:hover) .nav-menu a {
    justify-content: center;
    padding:         0.5rem 0;
    gap:             0;
}
/* Hide the .nav-link-text span entirely in the icon-only column.
 * SmartAdmin's `span { opacity: 0 }` leaves it taking layout
 * width — with our narrowed items that pushes the icon off-centre.
 * display:none collapses the layout space too. */
.set-nav-minified:not(.set-nav-collapsed) .app-sidebar:not(:hover) .nav-link-text {
    display: none;
}

/* ═════════════════════════════════════════════════════════════════
 * Audit polish pass — 2026-05-13.
 * Punch-list driven; each block tagged with its audit item number.
 * ═════════════════════════════════════════════════════════════ */

/* #2 Filter chips — companies page kind filter is rendered as
 * .btn.btn-sm.btn-outline-secondary links, which renders as faint
 * outlined buttons that don't read as a "chip group". Add a
 * dedicated .io-filter-chips wrapper that re-styles its children
 * as pill chips. The active chip keeps its .btn-primary look but
 * gets the chip shape. */
.io-filter-chips {
    display:       inline-flex;
    flex-wrap:     wrap;
    gap:           0.375rem;
    align-items:   center;
}
.io-filter-chips .btn {
    border-radius: 999px;
    padding:       0.375rem 0.875rem;
    font-size:     0.8125rem;
    font-weight:   500;
    line-height:   1.2;
    border-width:  1px;
    transition:    background-color .12s ease, border-color .12s ease, color .12s ease;
}
.io-filter-chips .btn-outline-secondary {
    --bs-btn-color:              var(--theme-text-secondary);
    --bs-btn-border-color:       var(--theme-border);
    --bs-btn-bg:                 transparent;
    --bs-btn-hover-color:        var(--syno-primary);
    --bs-btn-hover-border-color: rgba(1, 162, 253, 0.45);
    --bs-btn-hover-bg:           rgba(1, 162, 253, 0.08);
    --bs-btn-active-bg:          rgba(1, 162, 253, 0.15);
    --bs-btn-active-color:       var(--syno-primary);
}
html[data-bs-theme="dark"] .io-filter-chips .btn-outline-secondary {
    --bs-btn-color:              rgba(255, 255, 255, 0.65);
    --bs-btn-border-color:       rgba(255, 255, 255, 0.12);
}

/* #4 System (info) badge — cyan background can read as low-contrast
 * against the elevated dark card surface. Force the text to remain
 * the brand dark-navy and bump font-weight so the label pops. */
.badge.bg-info {
    color:       var(--syno-darker) !important;
    font-weight: 600;
}

/* #5+6 Cancel / Manage / inline action links — body-text hyperlinks
 * read as navigation, not as form actions. Add a `.btn-link` polish
 * pass so they look like a quiet secondary action instead. */
.btn-link {
    --bs-btn-color:           var(--theme-text-secondary);
    --bs-btn-hover-color:     var(--syno-primary);
    text-decoration:          none;
    font-weight:              500;
    padding:                  0.45rem 0.75rem;
}
.btn-link:hover { text-decoration: underline; }

/* Roles table "Manage" inline action — currently a plain anchor with
 * a blue underline. Soften to a quiet brand-cyan that still reads as
 * clickable without competing with the row text. */
.table a.action-link {
    color:           var(--syno-primary);
    font-weight:     500;
    text-decoration: none;
    border-bottom:   1px dotted transparent;
    transition:      border-bottom-color .12s ease;
}
.table a.action-link:hover {
    border-bottom-color: var(--syno-primary);
}

/* #8 Form inputs — strengthen border presence so they don't disappear
 * into the card surface in light mode. */
.form-control,
.form-select {
    border-color: var(--theme-input-border);
}
html[data-bs-theme="light"] .form-control,
html[data-bs-theme="light"] .form-select {
    border-color: #CBD5E1;        /* slate-300 — visible against white card */
}

/* #9 Card separation in dark mode — bump the border alpha a hair so
 * stacked cards (Activity + Quick Links, etc.) read as distinct
 * surfaces rather than one mega-card. */
html[data-bs-theme="dark"] .card {
    --bs-card-border-color: rgba(255, 255, 255, 0.10);
}

/* #11 Header chrome (theme picker + avatar trigger) — in light mode
 * the theme picker's surface-alt fill is too prominent against the
 * white header. Lighten the picker container and quiet down the
 * inactive button state. */
html[data-bs-theme="light"] .theme-picker {
    background:    #F1F5F9;
    border-color:  #E2E8F0;
}

/* "(dev)" annotation in dashboard Quick Links + sidebar — render
 * as a small muted pill so it reads as a metadata tag, not body text.
 * The .dev-pill class and no-op group above were retired — no markup
 * referenced them; the active selector below is what actually paints. */
.list-group-item a > .text-muted.small {
    font-size:     0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    background:    var(--theme-surface-alt);
    color:         var(--theme-text-muted);
    padding:       0.125rem 0.375rem;
    border-radius: 4px;
    margin-left:   0.25rem;
}

/* ── Sidebar badge clearance for the collapse caret ─────────────
 * SmartAdmin paints a `.collapse-sign` chevron absolutely at
 * `right: .3125rem` on every menu item whose <li> has a nested
 * <ul>. The helpdesk badge (currently-open ticket count) uses
 * `ms-auto` to land at the same right edge, so the caret overlays
 * the count. Reserve a bit of right margin on the badge ONLY when
 * the parent li has-ul (SmartAdmin's JS adds the class) so the
 * caret has its lane back. */
.primary-nav li.has-ul > a > .badge.ms-auto {
    margin-right: 1.25rem;
}

/* ── Sidebar submenu icons ──────────────────────────────────────
 * Top-level nav items render with `.sa-icon` at the standard
 * SmartAdmin size; submenu items now also carry an icon
 * (`.sa-icon-sub`) for better at-a-glance scanning. Keep them
 * compact + neutral-toned so they don't compete with the leaf-
 * dot active indicator. */
.primary-nav ul li a .sa-icon-sub {
    width:        14px;
    height:       14px;
    flex-shrink:  0;
    margin-right: 8px;
    opacity:      0.65;
    color:        currentColor;
}
.primary-nav ul li.active > a .sa-icon-sub,
.primary-nav ul li > a:hover .sa-icon-sub {
    opacity: 1;
}

/* ── Compact admin list rows ────────────────────────────────────
 * Shared style for the helpdesk + companies admin lists. Trims
 * vertical chrome on table-sm rows so long lists (60+ tag rules,
 * 14 seeded tags, etc.) stay scannable without scrolling forever.
 * Inline copies of this rule in individual templates are
 * superseded by this one — they can be safely removed when next
 * touched. */
.hd-list-table > :not(caption) > * > * {
    padding-top:    6px;
    padding-bottom: 6px;
}

/* ── Company email-domain chips ──────────────────────────────
 * Used on the company edit page to show which email domains
 * route inbound mail to this client. Each chip is a code-style
 * pill with a small × delete button that submits its own form.
 */
.domain-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.domain-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 8px;
    background: var(--theme-surface-alt);
    border: 1px solid var(--theme-border);
    border-radius: 14px;
    font-size: 12px;
}
.domain-chip code {
    background: transparent;
    padding: 0;
    font-size: 12px;
}
.domain-chip-remove {
    background: transparent;
    border: none;
    color: var(--theme-text-muted);
    font-size: 14px;
    line-height: 1;
    padding: 0 2px;
    cursor: pointer;
}
.domain-chip-remove:hover { color: var(--syno-error, #dc2626); }

/* ── Contextual `?` help link ───────────────────────────────────
 * Inline on page headers and admin form labels. Small, muted by
 * default; brightens on hover. Use the
 * partials/_help_link.twig include rather than copying inline. */
.help-pointer {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    color: var(--theme-text-muted);
    opacity: 0.7;
    text-decoration: none;
    transition: color .12s ease, opacity .12s ease;
}
.help-pointer:hover {
    color: var(--syno-primary);
    opacity: 1;
}
.help-pointer .sa-icon { width: 14px; height: 14px; }

/* ──────────────────────────────────────────────────────────────
   MOBILE FIXES — added because SmartAdmin's bundled mobile chrome
   leaves three gaps:
     1. The sidebar's primary-nav doesn't have its own
        overflow-y, so on viewports shorter than the nav's
        content height, the bottom items become unreachable
        (the whole drawer is fixed-positioned and the body
        scroll doesn't move it).
     2. There's no backdrop. The only way to close the drawer
        is to hit the burger again — and if it scrolled out of
        view, you can't.
     3. The bell popover renders at width:360px which overflows
        most phone widths.
   ────────────────────────────────────────────────────────────── */

/* Backdrop element: rendered always, only visible while the
   drawer is open. Tappable — JS handler in app_layout.twig
   closes the drawer on click. */
.app-mobile-backdrop {
    display: none;
}
@media (max-width: 991.98px) {
    .app-mobile-menu-open .app-mobile-backdrop {
        display:    block;
        position:   fixed;
        inset:      0;
        background: rgba(0, 0, 0, 0.45);
        z-index:    1040;
        animation:  app-mobile-fade-in 0.15s ease-out;
    }
    .app-mobile-menu-open body,
    .app-mobile-menu-open {
        overflow: hidden;
    }
    @keyframes app-mobile-fade-in {
        from { opacity: 0; }
        to   { opacity: 1; }
    }

    /* Sidebar scrollability. Previous attempt set
       `.app-sidebar { overflow: hidden }` and tried to scroll
       `.primary-nav` as a flex child. That fought with two
       things SmartAdmin already does:
         - SmartAdmin's mobile rule already sets
           `.app-sidebar { position:fixed; top:0; bottom:0;
           overflow-y:auto }`.
         - `.primary-nav` is `.custom-scroll`, hooked up to
           slimscroll (smartSlimscroll.js) which manages its
           own scroll container.
       Letting BOTH of them work + just enforcing what we need
       with !important is simpler than reconstructing the flex
       chain. !important is unusual but justified here because
       the bundled CSS at lower breakpoints repeats a rule that
       sometimes clobbers the scroll. */
    .app-sidebar {
        height:                     100vh !important;
        height:                     100dvh !important; /* iOS Safari + modern Chrome */
        overflow-y:                 auto   !important;
        overflow-x:                 hidden !important;
        overscroll-behavior:        contain;
        -webkit-overflow-scrolling: touch;
        z-index:                    1045;            /* above backdrop */
        padding-bottom:             24px;            /* tail breathing room */
    }
    /* Cancel slimscroll's overflow-hidden+overflow-y-scroll
       trick on .custom-scroll within the sidebar — we want the
       OUTER scroll to handle the whole drawer including the
       logo, not an inner sub-scroll. */
    .app-sidebar .primary-nav.custom-scroll {
        overflow:    visible !important;
        overflow-y:  visible !important;
        height:      auto    !important;
        max-height:  none    !important;
    }

    /* Tablet popover — anchor to viewport via position:fixed so
       it can't get cropped by an absolute-positioned ancestor.
       Sized to fit any tablet width with 8px margins. The bell
       button itself stays put because the .app-header is sticky;
       we just stop using the button as the popover's positioning
       context. */
    .bell-popover {
        position:   fixed !important;
        top:        calc(var(--app-header-height, 88px) + 6px);
        right:      8px !important;
        left:       auto !important;
        bottom:     auto;
        width:      min(420px, calc(100vw - 16px));
        max-width:  none;
        max-height: calc(100vh - var(--app-header-height, 88px) - 16px);
        max-height: calc(100dvh - var(--app-header-height, 88px) - 16px);
    }
    .bell-list {
        max-height: none; /* let the popover's own max-height drive scroll */
    }
    .bell-popover-prefs {
        flex-wrap:  wrap;
        row-gap:    6px;
    }
}

/* PHONE — bottom-sheet variant. Anchoring a 360-420px popover
   from the top-right of a 390px-wide viewport leaves no useful
   thumb-reach zone. Phones get the bottom-sheet pattern: full-
   width slab anchored to the viewport's bottom edge. Native
   iOS/Android notification trays use the same shape, so the
   affordance reads immediately.

   Kept intentionally minimal — earlier iterations tried to
   layer transform-based animations and a body-class backdrop;
   one of those interacted badly on Safari iOS and the sheet
   rendered invisible. Just the positional bits now. We can
   reintroduce polish later once the base render is verified.
*/
@media (max-width: 767.98px) {
    /* Header overflow on phone widths — SmartAdmin's bundled
       padding: 0 2rem 0 0 leaves 32px of dead right margin AND
       items (theme picker hidden via d-none d-sm-flex now, bell,
       avatar) STILL squeeze. Tighten the right padding + the
       inter-item margins so the avatar can't get clipped past
       the viewport's right edge. */
    .app-header {
        padding-right: 8px !important;
    }
    .app-header .bell-trigger.me-2,
    .app-header .io-user-avatar-btn { margin-right: 4px; }

    .bell-popover {
        position:    fixed !important;
        left:        0 !important;
        right:       0 !important;
        bottom:      0 !important;
        top:         auto !important;
        width:       auto !important;
        max-width:   none !important;
        max-height:  80vh;
        max-height:  80dvh;
        border-radius: 14px 14px 0 0;
        box-shadow:  0 -8px 32px rgba(0, 0, 0, 0.25);
    }
    .bell-list {
        max-height: none;
        flex:       1 1 auto;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        overscroll-behavior: contain;
    }
    .bell-item-link    { padding: 14px 16px; }
    .bell-popover-prefs {
        padding:    10px 16px;
        flex-wrap:  wrap;
        row-gap:    8px;
        column-gap: 14px;
    }
    .bell-popover-foot { padding: 12px 16px; }
}

/* Mobile burger — SmartAdmins bundled `.mobile-menu-icon` paints
   the burger in `rgba(var(--danger), 0.9)` (bright pink/red). Way
   too loud for a passive navigation affordance. Re-skin to match
   the rest of the header chrome: subtle surface fill with the
   theme's standard border + text colour. */
.mobile-menu-icon {
    background: var(--theme-surface-alt) !important;
    border:     1px solid var(--theme-border) !important;
}
.mobile-menu-icon .sa-icon {
    fill:   transparent !important;
    stroke: var(--theme-text) !important;
}
.mobile-menu-icon:active {
    background: var(--theme-surface) !important;
    scale: 0.95;
}

/* HELPDESK INBOX — mobile responsiveness
   ─────────────────────────────────────────
   The full inbox table doesn't fit a 390px viewport — SLA + Last
   activity + Assignee columns overflow off the right edge. On
   phones we collapse to three columns: Ref, Subject + Meta,
   Status. The hidden columns' info isn't critical for triage
   from a phone; if the operator needs SLA / assignee, tapping a
   row opens the ticket show page where everything is rendered. */
@media (max-width: 767.98px) {
    .inbox-table .hide-on-phone { display: none !important; }

    /* Header: search input + CSV button + save view were laid out
       in a single flex row with no wrap, so the CSV button hung
       off the right edge. Wrap so each control gets a full row when
       width gets tight. */
    .inbox-header-right { flex-wrap: wrap; row-gap: 6px; }
    .inbox-header-right .inbox-search { flex: 1 1 100%; min-width: 0; }
    .inbox-header-right .inbox-search input { width: 100%; }

    /* Hide the "Live, updated HH:MM" timestamp line under the title —
       saves a row on a small viewport; the table itself live-polls
       so the data IS fresh. */
    .inbox-header-left .text-muted.small { display: none; }

    /* Tighten table cells — bigger tap targets but less vertical
       padding overall so we fit more rows per screen. */
    .inbox-table tbody td { padding: 10px 8px !important; }

    /* Subject row: let chips wrap onto a second line so the
       subject stays readable. Chips were forced single-line on
       desktop; here we give them room. */
    .inbox-table .subject-row {
        flex-wrap: wrap;
        row-gap: 4px;
    }
    .inbox-table .subject-link {
        flex: 1 1 100%;
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
    }
}

@media (max-width: 767.98px) {
    /* Status pill: let long labels (PENDING CUSTOMER / INTERNAL)
       wrap onto two lines instead of being truncated at "PENDI". */
    .inbox-table .status-pill {
        white-space: normal;
        line-height: 1.2;
        font-size:   9px;
        padding:     3px 6px;
        display:     inline-block;
    }
}

/* INBOX MOBILE OVERHAUL
   ─────────────────────────────
   Collapsed-by-default filter panel, card-per-row ticket layout,
   floating-action-button for "+ New ticket". Everything is
   scoped to the ≤767.98px breakpoint so tablet + desktop are
   unchanged. */
@media (max-width: 767.98px) {
    /* Filter panel: hidden by default, shown when filtersOpen=true
       (Alpine adds .is-open). The toggle button itself sits in the
       header row (.inbox-filter-toggle). */
    .inbox-filter-panel {
        display: none;
    }
    .inbox-filter-panel.is-open {
        display: block;
        background:    var(--theme-surface-alt);
        border:        1px solid var(--theme-border);
        border-radius: 8px;
        padding:       12px;
        margin-bottom: 16px;
    }
    /* "Filters" toggle button: small active dot when any filter
       is non-default. */
    .inbox-filter-toggle { position: relative; }
    .inbox-filter-dot {
        position:      absolute;
        top:           4px;
        right:         4px;
        width:         8px;
        height:        8px;
        border-radius: 50%;
        background:    var(--syno-primary);
    }

    /* CARD-PER-ROW ticket layout. Convert the <table> into a
       stack of card-shaped <tr>s. <thead> hides (no column
       headers needed when each row labels itself). */
    .inbox-table thead { display: none; }
    .inbox-table,
    .inbox-table tbody { display: block; }
    .inbox-table tr.inbox-row {
        display:        grid;
        grid-template-columns: auto 1fr auto;
        grid-template-areas:
            "check ref     status"
            "check subject status"
            "check meta    .";
        column-gap:     10px;
        row-gap:        2px;
        align-items:    center;
        padding:        12px;
        margin-bottom:  8px;
        background:     var(--theme-surface-alt);
        border-radius:  8px;
        border:         1px solid var(--theme-border);
    }
    .inbox-table tr.inbox-row > td {
        padding: 0 !important;
        border:  0 !important;
    }
    /* Cells assigned to grid areas. The 7-column-or-6-column
       table shape (depending on can_bulk) means we target by
       :nth-child. With bulk-checkbox column: 1=check, 2=ref,
       3=subject. Without: 1=ref, 2=subject. We handle both. */
    .inbox-table tr.inbox-row > td:nth-child(1) { grid-area: check; }
    .inbox-table tr.inbox-row > td:nth-child(2) { grid-area: ref; }
    .inbox-table tr.inbox-row > td:nth-child(3) { grid-area: subject; }
    /* When can_bulk is false the columns shift one over — the
       hide-on-phone ones are display:none from earlier so the
       remaining visible td positions are: ref (1), subject (2),
       status (last). Status is always the .status-pill td. */
    .inbox-table tr.inbox-row > td:has(> .status-pill) { grid-area: status; }
    /* Ref column — small + muted. */
    .inbox-table .ref-cell {
        font-size:   11px;
        font-weight: 600;
        color:       var(--theme-text-muted);
    }
    /* Subject row gets the meta area below it via grid-template. */
    .inbox-table .subject-row { display: contents; }
    /* Pull the subject link onto the SUBJECT grid area and the
       chips onto META. CSS-only approach: use first-child for
       subject link, everything else for meta. */
    .inbox-table .subject-row > a.subject-link {
        grid-area: subject;
        font-size: 14px;
        font-weight: 600;
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
    }
    .inbox-table .subject-row > :not(a.subject-link) {
        grid-area: meta;
        display:   inline-flex;
        margin-right: 4px;
    }

    /* FAB — floating + new ticket button. */
    .inbox-fab {
        position:       fixed;
        bottom:         20px;
        right:          20px;
        width:          56px;
        height:         56px;
        border-radius:  50%;
        background:     var(--syno-primary);
        color:          #fff;
        display:        flex;
        align-items:    center;
        justify-content: center;
        box-shadow:     0 6px 20px rgba(0, 0, 0, 0.35);
        z-index:        1040;
        text-decoration: none;
        transition:     transform 0.15s ease, box-shadow 0.15s ease;
    }
    .inbox-fab:hover,
    .inbox-fab:focus {
        color: #fff;
        transform: scale(1.05);
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.40);
    }
    .inbox-fab:active { transform: scale(0.95); }
    .inbox-fab .sa-icon {
        width:  28px;
        height: 28px;
        stroke: #fff;
        stroke-width: 2.5px;
    }
}

/* User-menu theme picker (phone-only)
   ─────────────────────────────────────
   The in-header 3-button .theme-picker is d-none below 576px to
   keep the bell + avatar from getting squashed. This block
   re-surfaces the same Light / System / Dark choices inside the
   user-menu dropdown so phone users can still switch theme. Same
   data-theme-set attribute is consumed by /scripts/core/theme.js
   — no JS changes needed. */
.user-menu-theme {
    padding: 8px 12px 12px;
}
.user-menu-theme-label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--theme-text-muted);
    margin-bottom: 6px;
}
.user-menu-theme-row {
    display: flex;
    gap: 6px;
}
.user-menu-theme-btn {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 8px 4px;
    background: var(--theme-surface-alt);
    border: 1px solid var(--theme-border);
    border-radius: 6px;
    color: var(--theme-text);
    font-size: 11px;
    cursor: pointer;
    transition: border-color 0.12s ease, background 0.12s ease;
}
.user-menu-theme-btn:hover { border-color: var(--syno-primary); }
.user-menu-theme-btn[aria-pressed="true"] {
    border-color: var(--syno-primary);
    background:   rgba(1, 162, 253, 0.10);
    color:        var(--syno-primary);
}
.user-menu-theme-btn .sa-icon {
    width:  18px;
    height: 18px;
    stroke: currentColor;
}

/* Company list — small left-side logo tile per row. Real
   logo (from logo_url) renders inside an object-fit:contain
   box so non-square assets aren't stretched. Fallback for
   companies without a fetched/uploaded logo: a single-letter
   initial chip tinted with the company's accent_color (or
   neutral grey if no accent set). */
.company-logo-tile {
    display:        inline-flex;
    align-items:    center;
    justify-content: center;
    width:          32px;
    height:         32px;
    border-radius:  6px;
    background:     var(--theme-surface-alt);
    border:         1px solid var(--theme-border);
    object-fit:     contain;
    padding:        2px;
    font-size:      14px;
    font-weight:    600;
    color:          var(--theme-text-muted);
    flex-shrink:    0;
}
.company-logo-tile-fallback {
    padding: 0; /* no inner padding for the initial */
}
/* Generic-icon variant — used when a company has no fetched /
   uploaded logo. Renders a briefcase glyph inside the same tile
   shape so the helpdesk inbox + future lists keep a uniform
   left-edge silhouette across rows regardless of logo coverage.
   The SVG inherits currentColor so accent_color (set inline on
   .company-logo-tile-icon) tints the icon too. */
.company-logo-tile-icon {
    padding: 3px;
}
.company-logo-tile-icon svg {
    width:  100%;
    height: 100%;
    stroke: currentColor;
    fill:   none;
}
/* Compact 20px variant for dense rows (helpdesk inbox subject
   line, ticket list expansions). Same shape + treatment, just
   scaled down with a smaller initial font and tighter padding. */
.company-logo-tile-xs {
    width:     20px;
    height:    20px;
    font-size: 11px;
    padding:   1px;
    border-radius: 4px;
    margin-right: 6px;
    vertical-align: middle;
}
.company-logo-tile-xs.company-logo-tile-fallback {
    padding: 0;
}
.company-logo-tile-xs.company-logo-tile-icon {
    padding: 2px; /* slightly tighter for the 20px square */
}

/* ── Company health "temperature" pill ────────────────────────────
   Used by partials/_company_health_pill.twig on the company show
   page header + the helpdesk ticket header. Soft tinted background
   matching the .meta-chip family on the inbox, with a leading
   colored dot for at-a-glance reading. Hover tooltip carries the
   top reasons. */
.health-pill {
    display:        inline-flex;
    align-items:    center;
    gap:            6px;
    padding:        3px 10px;
    border-radius:  999px;
    font-size:      12px;
    font-weight:    600;
    line-height:    1;
    white-space:    nowrap;
    cursor:         help;        /* the tooltip is the payload */
    border:         1px solid transparent;
    vertical-align: middle;
}
.health-pill-sm { font-size: 11px; padding: 2px 8px; }
.health-pill-dot {
    display:       inline-block;
    width:         8px;
    height:        8px;
    border-radius: 50%;
    background:    currentColor;
    flex-shrink:   0;
}
/* Band colors. The "orange" band sits between yellow (watch) and red
   (critical) — Bootstrap doesn't ship an orange semantic so we paint
   it here. The other three lean on Bootstrap's success/warning/danger
   primitives via background + text-color, tinted at ~12% for the
   subtle pill background. */
.health-pill-healthy {
    background: rgba(25, 135, 84, 0.12);   /* bs-success */
    color:      #146c43;
    border-color: rgba(25, 135, 84, 0.22);
}
.health-pill-watch {
    background: rgba(255, 193, 7, 0.16);   /* bs-warning */
    color:      #997404;
    border-color: rgba(255, 193, 7, 0.30);
}
.health-pill-at_risk {
    background: rgba(253, 126, 20, 0.14);  /* custom orange */
    color:      #b85d0c;
    border-color: rgba(253, 126, 20, 0.30);
}
.health-pill-critical {
    background: rgba(220, 53, 69, 0.14);   /* bs-danger */
    color:      #b02a37;
    border-color: rgba(220, 53, 69, 0.30);
}
.health-pill-new {
    background:   var(--theme-surface-alt);
    color:        var(--theme-text-muted);
    border-color: var(--theme-border);
}
.health-pill-new .health-pill-dot { background: var(--theme-text-muted); }

/* ── Company health gauge (SVG semicircle) ────────────────────────
   Lives in templates/partials/_company_health_gauge.twig; appears
   in the ticket page sidebar below the SLA card. Pure inline SVG —
   no chart library — with a CSS-rotated needle that animates from
   the left (score 0) on first paint via the entrance keyframe. */
.health-gauge-card {
    text-align: center;
}
.health-gauge {
    width:        100%;
    max-width:    260px;
    height:       auto;
    margin:       0 auto;
    display:      block;
    overflow:     visible; /* let the needle pivot dot draw outside the box if needed */
}

/* Band arcs. Stroke-linecap:butt so adjacent bands meet flush
   instead of overlapping rounded ends. Inactive bands fade to
   ~30% opacity so the operator's eye is drawn to the active
   zone — the score's band is .is-active. */
.health-gauge-arc {
    fill:           none;
    stroke-width:   18;
    stroke-linecap: butt;
    opacity:        0.32;
    transition:     opacity 0.25s ease;
}
.health-gauge-arc.is-active { opacity: 1; }
.health-gauge-arc-critical { stroke: #dc3545; }   /* bs-danger */
.health-gauge-arc-at_risk  { stroke: #fd7e14; }   /* custom orange */
.health-gauge-arc-watch    { stroke: #ffc107; }   /* bs-warning */
.health-gauge-arc-healthy  { stroke: #198754; }   /* bs-success */

/* Tick marks at band boundaries. Subtle hash marks ground the eye
   without competing with the score readout. */
.health-gauge-tick {
    stroke:       var(--theme-border, rgba(0,0,0,0.15));
    stroke-width: 1.5;
}

/* Needle. The line + pivot dot rotate as a single group around
   (100, 100). --rot is set inline per render. Entrance animation
   sweeps from score 0 (-90°) to the actual angle on first paint —
   feels alive without being gimmicky. */
.health-gauge-needle {
    transform-origin: 100px 100px;
    transform:        rotate(var(--rot, 0deg));
    transition:       transform 0.9s cubic-bezier(0.25, 0.46, 0.45, 0.94);
    animation:        health-gauge-needle-enter 0.9s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
.health-gauge-needle line {
    stroke:         var(--theme-text, #212529);
    stroke-width:   3;
    stroke-linecap: round;
}
.health-gauge-needle circle {
    fill:    var(--theme-text, #212529);
    stroke:  var(--theme-surface, #ffffff);
    stroke-width: 2;
}
@keyframes health-gauge-needle-enter {
    from { transform: rotate(-90deg); }
    to   { transform: rotate(var(--rot, 0deg)); }
}

/* Score readout. SVG text scales with the gauge — no breakpoint
   font-size juggling. */
.health-gauge-score {
    font-size:      30px;
    font-weight:    700;
    fill:           var(--theme-text, #212529);
    font-family:    inherit;
}
.health-gauge-band-label {
    font-size:      10px;
    fill:           var(--theme-text-muted, #6c757d);
    letter-spacing: 1.5px;
    font-weight:    600;
    font-family:    inherit;
}

/* Reasons list under the gauge. Compact, muted, dot prefix. */
.health-gauge-reasons {
    list-style:   none;
    padding:      0;
    margin:       8px 0 0;
    font-size:    11px;
    color:        var(--theme-text-muted, #6c757d);
    text-align:   left;
}
.health-gauge-reasons li {
    position:     relative;
    padding-left: 12px;
    line-height:  1.45;
    margin-bottom: 2px;
}
.health-gauge-reasons li::before {
    content:      '·';
    position:     absolute;
    left:         4px;
    color:        var(--theme-text-muted, #6c757d);
    font-weight:  700;
}

/* Insufficient-data fallback — same card footprint, muted "—". */
.health-gauge-empty {
    font-size: 36px;
    font-weight: 300;
    color: var(--theme-text-muted, #6c757d);
    line-height: 1;
}

/* ── Themed tinted chip (.tinted-chip) ─────────────────────────────
   Generic helper for tag chips, accent-color initial chips, and
   anywhere a template needs "a soft tint of an arbitrary color
   that reads in BOTH light and dark themes".

   Caller sets `--chip-color: #14b8a6` on the element (inline or via
   a parent rule); this rule paints:
     background : 14% of that color mixed with the theme surface →
                  pale tinted bg in light, navy-tinted bg in dark
     color      : the chip color directly in light theme;
                  brightened (mixed toward white) in dark theme so
                  mid-tone colors don't disappear into the dark bg
     border     : ~35% of the color over transparent

   The legacy pattern `style="background:#14b8a61A; color:#14b8a6"`
   tinted via alpha over the page bg — which gave a pale chip on
   white but a dark-tint-on-dark chip in dark mode, with the text
   color staying mid-tone. This rule fixes the contrast in both
   modes via color-mix() (Chrome 111+, FF 113+, Safari 16.4+ —
   shipped 2023+, all supported targets). */
.tinted-chip {
    --chip-color: currentColor;
    background:   color-mix(in srgb, var(--chip-color) 14%, var(--theme-surface));
    color:        var(--chip-color);
    border:       1px solid color-mix(in srgb, var(--chip-color) 35%, transparent);
}
html[data-bs-theme="dark"] .tinted-chip,
html.set-nav-dark .tinted-chip {
    /* Mix the chip color 60% with white in dark mode so mid-tone
       brand colors (navy, teal, deep purple) don't smear into the
       dark navy page bg. Background also slightly stronger so the
       chip outline is still readable. */
    background: color-mix(in srgb, var(--chip-color) 22%, var(--theme-surface));
    color:      color-mix(in srgb, var(--chip-color) 60%, white);
    border:     1px solid color-mix(in srgb, var(--chip-color) 50%, transparent);
}

/* ── Per-ticket heat pill ─────────────────────────────────────────
   Compact "🔥 73" chip surfaced on the helpdesk inbox row + ticket
   show header. Visually distinct from the health-pill family: a
   horizontal warm-palette gradient instead of soft pastel tints,
   so the operator's eye reads "this individual ticket is hot" vs
   "this client trends red".
   COOL tickets aren't rendered (the partial bails); these classes
   apply only to warm/hot/on_fire. */
.heat-pill {
    display:         inline-flex;
    align-items:     center;
    gap:             4px;
    padding:         2px 8px;
    border-radius:   999px;
    font-size:       11px;
    font-weight:     700;
    line-height:     1;
    white-space:     nowrap;
    cursor:          help;
    vertical-align:  middle;
    border:          1px solid transparent;
}
.heat-pill-flame {
    width:  11px;
    height: 11px;
    fill:   currentColor;
    flex-shrink: 0;
}
.heat-pill-score { letter-spacing: 0.3px; }

/* Warm = first-signal triggered, not crisis. Yellow gradient. */
.heat-pill-warm {
    background:  linear-gradient(135deg, rgba(255,193,7,0.18) 0%, rgba(255,193,7,0.28) 100%);
    color:       #9a7400;
    border-color: rgba(255,193,7,0.45);
}
/* Hot = multiple signals stacked; operator should look. Orange. */
.heat-pill-hot {
    background:  linear-gradient(135deg, rgba(253,126,20,0.20) 0%, rgba(253,126,20,0.32) 100%);
    color:       #b85d0c;
    border-color: rgba(253,126,20,0.50);
}
/* On fire = breach + age + awaiting + something else; act now. */
.heat-pill-on_fire {
    background:  linear-gradient(135deg, rgba(220,53,69,0.22) 0%, rgba(220,53,69,0.42) 100%);
    color:       #a01e2c;
    border-color: rgba(220,53,69,0.60);
    /* Subtle pulse so it draws the eye on a busy inbox. Respects
       prefers-reduced-motion via the media query below. */
    animation: heat-pill-pulse 2.4s ease-in-out infinite;
}
@keyframes heat-pill-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(220,53,69,0.0); }
    50%      { box-shadow: 0 0 0 3px rgba(220,53,69,0.15); }
}
@media (prefers-reduced-motion: reduce) {
    .heat-pill-on_fire { animation: none; }
}

/* ---------------------------------------------------------------
   .access-row — picker rows on /users/{id}/edit Access card.
   ---------------------------------------------------------------
   Both Staff-groups and Direct-roles lists share this shape so the
   eye picks up vertical rhythm across the two columns immediately:

     [☐]  Role name [badge] [badge]               [yyyy-mm-dd ▾]
          dim description text wrapping under the title

   - .access-list  : the outer container; full-width, vertical stack.
   - .access-row   : clickable label that wraps a checkbox + body
                     + optional trailing control (date input for
                     roles, nothing for groups).
   - The whole row is hoverable + clickable because <label for=…>
     associates a click anywhere on it with the inner checkbox.
   - The trailing expiry control stops click propagation so the
     operator doesn't accidentally re-toggle the checkbox while
     opening the date picker.
   --------------------------------------------------------------- */
.access-list {
    border: 1px solid var(--bs-border-color-translucent, rgba(0,0,0,0.08));
    border-radius: 0.375rem;
    overflow: hidden;
}
.access-row {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.4rem 0.75rem;
    margin: 0;
    border-top: 1px solid var(--bs-border-color-translucent, rgba(0,0,0,0.08));
    background: var(--bs-body-bg, #fff);
    transition: background-color 0.08s ease;
}
.access-row:first-child {
    border-top: 0;
}
.access-row:hover {
    background: var(--bs-tertiary-bg, #f8f9fa);
}
/* The clickable region — wraps checkbox + body but NOT the
   trailing expiry control, so clicking the date input doesn't
   bubble up and toggle the checkbox. */
.access-row__main {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    padding: 0.15rem 0;
    cursor: pointer;
}
.access-row__check {
    margin-top: 0.25rem;
    flex: 0 0 auto;
}
.access-row__body {
    flex: 1 1 auto;
    min-width: 0;
}
.access-row__title {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    line-height: 1.3;
}
.access-row__desc {
    margin-top: 0.15rem;
    font-size: 0.825rem;
    color: var(--bs-secondary-color, #6c757d);
    line-height: 1.35;
}
.access-row__expiry {
    flex: 0 0 auto;
    width: 10.5rem;
    max-width: 40%;
    align-self: flex-start;
    margin-top: 0.05rem;
}
.access-row__expiry:focus,
.access-row__expiry:hover {
    /* Don't let the row's hover state make the input feel "sticky" */
    background: var(--bs-body-bg, #fff);
}

/* ---------------------------------------------------------------
   Mobile responsiveness for the access pickers + sudo modal.
   ---------------------------------------------------------------
   Phone widths (<576px) collapse the side-by-side row layout into
   a stacked one: title + expiry on separate lines. Otherwise the
   10.5rem expiry input crowds the title at ~360px viewport.
   --------------------------------------------------------------- */
@media (max-width: 575.98px) {
    .access-row {
        flex-wrap: wrap;
    }
    .access-row__main {
        width: 100%;
    }
    .access-row__expiry {
        width: 100%;
        max-width: none;
        margin-left: 1.6rem; /* indent under the checkbox */
        margin-top: 0.35rem;
    }
}

/* ---------------------------------------------------------------
   Sudo modal — keep readable on narrow viewports. The 460px panel
   would overflow on a 360px phone; cap to viewport width minus a
   small gutter and let the panel scroll its body.
   --------------------------------------------------------------- */
@media (max-width: 480px) {
    .sudo-modal {
        padding: 0.5rem;
    }
    .sudo-modal__panel {
        max-width: 100%;
    }
    .sudo-modal__header {
        padding: 0.75rem 1rem;
    }
    .sudo-modal__body {
        padding: 1rem;
    }
}
