/* =========================================================================
 * ZenGuard Theme — look & feel restyle layer for AdminLTE3 / Bootstrap4
 * -------------------------------------------------------------------------
 * PL: Warstwa wyłącznie wizualna (CSS) nadpisująca AdminLTE/Bootstrap4 na
 *     nowoczesny styl ZenGuard (navy/slate, light + dark). Nie zmienia HTML.
 *     Light = wartości w :root, Dark = nadpisanie tokenów pod body.dark-mode.
 *     Komponenty referują var(--...), więc dark działa automatycznie.
 * EN: Pure visual layer (CSS) overriding AdminLTE/Bootstrap4 with the modern
 *     ZenGuard style (navy/slate, light + dark). Does not change HTML.
 *     Light = values in :root, Dark = token overrides under body.dark-mode.
 *     Components reference var(--...), so dark mode just swaps variables.
 *
 * Loaded AFTER custom_styles.css so it wins on conflicts.
 * Convention: download = blue (--blue-500), upload = red (--red).
 * ========================================================================= */

/* -------------------------------------------------------------------------
 * 1. DESIGN TOKENS
 * ----------------------------------------------------------------------- */

/* PL: Wartosci palety pochodza z mockupu "Refined operations"
 *     (zenguard-dashboard-refined-ops.html). Mapowanie NASZ <- mockup:
 *       --bg <- --bg ; --bg-elev/--bg-soft <- --surface / --inset ;
 *       --border <- --hairline ; --border-strong <- --hairline-strong ;
 *       --text <- --ink ; --text-soft <- --ink-2 ; --text-mute <- --ink-3 ;
 *       --text-faint <- --ink-faint ; --accent/--blue-500 <- --accent ;
 *       --blue-600 <- --accent-strong ; --green <- --ok ; --amber <- --warn ;
 *       slate idle <- --idle ; shadows <- mockup shadows ;
 *       radii <- 12 / 8 / 18 ; plus nav, chart-grid, accent-soft/line tokens.
 *     Istniejace nazwy tokenow zachowane, by dotychczasowe override'y dzialaly.
 * EN: Palette values are taken from the "Refined operations" mockup
 *     (zenguard-dashboard-refined-ops.html). NASZ <- mockup mapping above.
 *     Existing token names are preserved so all current overrides keep working;
 *     only the values change, plus a few additive tokens (nav, chart-grid,
 *     accent-soft/line, text-faint). */
:root {
    /* Brand navy/blue scale — re-pointed to the refined-ops accent family.
       --blue-500 stays the disciplined accent (#2563eb), --blue-600 added.
       PL: --navy-900 to teraz BARDZIEJ NIEBIESKI granat (Airy refine), nie near-black.
       EN: --navy-900 is now the BLUER navy (Airy refine), no longer near-black. */
    --navy-900: #16314f;   /* bluer navy rail base (Airy refine, was #0f1b30) */
    --navy-800: #16294a;
    --navy-700: #1d4ed8;   /* now drives btn-primary fill (accent-strong) */
    --navy-600: #1d4ed8;   /* hover companion for the accent */
    --blue-600: #1d4ed8;   /* accent-strong */
    --blue-500: #2563eb;   /* one disciplined blue accent */
    --blue-400: #5b8bf5;   /* dark-mode accent */
    --blue-300: #93c5fd;   /* used as sidebar active icon tint */

    /* Neutral slate scale (kept for legacy refs; primary surfaces below) */
    --slate-50:  #f6f7f9;
    --slate-100: #f5f7fa;
    --slate-200: #e4e7ec;
    --slate-300: #d6dae1;
    --slate-400: #94a3b8;
    --slate-500: #5f6b7e;
    --slate-600: #3c4658;
    --slate-700: #334155;
    --slate-800: #1e293b;
    --slate-900: #11192a;

    /* Semantic colors + soft backgrounds (mockup --ok/--warn + AA companions) */
    --green:     #0f7a4f;   /* mockup --ok */
    --green-bg:  #e3f4ec;   /* mockup --ok-bg */
    --amber:     #b7791f;   /* mockup --warn */
    --amber-bg:  #fef3c7;
    --red:       #cc3338;   /* mockup --ul-text (upload red, AA on white) */
    --red-bg:    #fdecec;
    --violet:    #7c3aed;
    --violet-bg: #ede9fe;

    /* Idle / neutral status (mockup --idle / --idle-bg) */
    --idle:    #566073;
    --idle-bg: #eef1f5;

    /* Traffic semantics (download = blue, upload = red) */
    --dl: #2f6fed;
    --ul: #e0464a;
    --dl-text: #2f6fed;
    --ul-text: #cc3338;

    /* Surfaces (light defaults) — mockup --surface / --inset family.
     * PL: Tlo strony DELIBERATNIE ciemniejsze niz biale karty. Mockupowy gradient
     *     (#f6f7f9 -> #eceef2) byl u gory prawie bialy, wiec biale .zg-panel/.zg-card
     *     (--bg-elev #fff) NIE odcinaly sie (delta luminancji ~1.03 = plasko).
     *     Poglubiony szary gradient (#eef0f3 -> #e2e5ea, baza #e7e9ee) daje realny
     *     kontrast luminancji, na ktorym ramka + --shadow wyraznie unosza karty.
     * EN: Page backdrop is DELIBERATELY darker than the white cards. The mockup
     *     gradient (#f6f7f9 -> #eceef2) was near-white at the top, so the white
     *     .zg-panel/.zg-card (--bg-elev #fff) did NOT separate (luminance delta
     *     ~1.03 = flat). The deepened grey gradient (#eef0f3 -> #e2e5ea, base
     *     #e7e9ee) gives a real luminance delta the border + --shadow lift off. */
    /* PL: Paleta "Quiet Ledger" (light) - spokojne, lekko niebieskawe plotno
     *     (#f4f6fa) zauwazalnie ciemniejsze niz biale karty, by ramka + cien
     *     wyraznie unosily .zg-summary / .zg-ledger / .zg-devices. Mapowanie z
     *     mockupu: --bg <- #f4f6fa, --surface <- #ffffff, --surface-2 <- #f8fafc.
     * EN: "Quiet Ledger" (light) palette - a calm, slightly bluish canvas
     *     (#f4f6fa) clearly darker than the white cards so the border + shadow lift
     *     .zg-summary / .zg-ledger / .zg-devices. Mapped from the mockup:
     *     --bg <- #f4f6fa, --surface <- #ffffff, --surface-2 <- #f8fafc. */
    /* PL: Airy refine — tlo strony nieco glebsze (#eef2f8), by biale karty
     *     wyrazniej sie unosily; gradient zaciesniony wokol tej bazy.
     * EN: Airy refine — page canvas slightly deeper (#eef2f8) so the white cards
     *     lift more clearly; gradient tightened around that base. */
    --bg:        #eef2f8;   /* Airy refine canvas (was #f0f3f8) */
    --bg-grad-a: #f2f5fb;   /* body background gradient stops (Airy refine) */
    --bg-grad-b: #e7ecf4;
    --bg-elev:   #ffffff;   /* mockup --surface */
    --bg-elev-2: #f6f9fd;   /* mockup --surface-2 */
    --bg-soft:   #eef3fa;   /* mockup --inset */

    /* Text — mockup ink ramp */
    --text:       #11192a;  /* --ink */
    --text-soft:  #3c4658;  /* --ink-2 */
    --text-mute:  #5f6b7e;  /* --ink-3 */
    --text-faint: #79859a;  /* --ink-faint */

    /* Borders — Airy refine: firmer, more defined card edge. */
    --border:        #d4ddec;  /* --hairline (Airy refine, was #e2e8f0) */
    --border-strong: #c4cfe0;  /* --hairline-strong (firmed to match) */
    --border-soft:   #e4ebf5;  /* row separators (Airy refine) */

    /* Quiet Ledger sparkline + status-soft tokens (light). Sparkline strokes:
       download = blue accent, upload = teal. Status soft fills back the pills. */
    --spark-down: #2563eb;
    --spark-up:   #0e9488;
    --ok-soft:    #e3f4ea;
    --idle-soft:  #fbf0dc;

    /* Accents */
    --accent:      var(--blue-500);  /* solid accent (was navy) */
    --accent-strong: var(--blue-600);
    --accent-text: var(--blue-500);  /* link/icon accent */
    --accent-soft: #eff4ff;          /* mockup --accent-soft */
    --accent-line: #c9dcff;          /* mockup --accent-line */

    /* Navigation rail — Airy refine: BLUER navy (gradient #1b3a5c -> #16314f),
     * not near-black. Active = blue tint + white text + accent ring/left-bar;
     * active icon + brand accent #9cc2ff; SYSTEM labels muted #8aa1c2; nav text
     * light + AA (>=4.5:1) on the rail. Identical (bluer navy) in both themes.
     * PL: Szyna wyraznie BARDZIEJ NIEBIESKA — gradient #1b3a5c (gora) -> #16314f
     *     (dol). Aktywny: niebieski tint + bialy tekst + akcentowy ring/lewy pasek.
     * EN: Rail clearly BLUER — gradient #1b3a5c (top) -> #16314f (bottom). Active:
     *     blue tint + white text + accent ring / left bar. */
    --nav-bg:         #16314f;          /* fallback (gradient base, bottom) */
    --nav-bg-top:     #1b3a5c;          /* gradient top — bluer, brighter */
    --nav-bg-bot:     #16314f;          /* gradient bottom — settled blue-navy */
    --nav-ink:        #c9d8ee;          /* nav text — ~9.6:1 on rail (AA/AAA) */
    --nav-ink-faint:  #8aa1c2;          /* SYSTEM section labels — ~4.7:1, AA */
    --nav-active-bg:  rgba(120, 170, 255, .18);
    --nav-active-ink: #ffffff;
    --nav-accent:     #9cc2ff;          /* active icon + brand accent */

    /* Chart grid line */
    --chart-grid: #eef1f5;

    /* Semantic aliases */
    --success: var(--green);
    --warning: var(--amber);
    --error:   var(--red);
    --info:    var(--violet);

    /* Code surface (light) */
    --code-bg: #f5f7fa;

    /* Typography — Quiet Ledger stack. Hanken Grotesk is the body/UI workhorse;
       Fraunces (display) is reserved for the greeting headline + the big glance
       numbers; IBM Plex Mono carries IPs/ports/numbers (tabular-nums). */
    --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
    --font-body:    'Hanken Grotesk', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
    --font-sans:    var(--font-body); /* legacy alias kept for existing refs */
    --font-mono:    'IBM Plex Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;

    /* Radii — mockup 8 / 12 / 18 */
    --radius-sm:   8px;
    --radius-md:   12px;
    --radius-lg:   18px;
    --radius-full: 999px;

    /* Shadows — mockup elevation set, --shadow nudged slightly stronger so the
       elevated cards read clearly against the grey page backdrop (the mockup's
       very soft shadow lost the cards once both sat near-white). */
    --shadow-sm: 0 1px 2px rgba(16, 28, 50, .05), 0 1px 3px rgba(16, 28, 50, .04);
    --shadow:    0 1px 2px rgba(16, 28, 50, .06), 0 12px 28px -10px rgba(16, 28, 50, .20);
    --shadow-lg: 0 2px 4px rgba(16, 28, 50, .06), 0 22px 50px -22px rgba(16, 28, 50, .30);

    /* Airy refine — card definition: a crisp inset top-highlight + faint hairline
       ring so surfaces read as raised (less flat) without adding visual noise.
       Paired with --card-wash, a subtle top-light gradient for the card face. */
    --card-ring: inset 0 1px 0 rgba(255, 255, 255, .9), inset 0 0 0 1px rgba(16, 32, 56, .015);
    --card-wash: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%);
}

/* Dark mode = AdminLTE body.dark-mode (class + cookie). Override tokens only.
   Values from the mockup [data-theme="dark"] palette. */
body.dark-mode {
    --navy-900: #16314f;   /* bluer navy rail (Airy refine) — same bluer navy in dark */

    /* Quiet Ledger dark canvas — slate-900+ background, white surfaces lifted. */
    --bg:        #0b1220;
    --bg-grad-a: #0b1322;
    --bg-grad-b: #080e1a;
    --bg-elev:   #111a2b;   /* --surface */
    --bg-elev-2: #16223a;   /* --surface-2 */
    --bg-soft:   #0c1424;   /* --inset */

    --text:       #eef3fb;  /* --ink */
    --text-soft:  #b6c4da;  /* --ink-2 */
    --text-mute:  #8597b3;  /* --ink-3 */
    --text-faint: #5d6c84;  /* --ink-faint */

    --border:        #26374e;  /* --hairline (Airy refine dark — crisper edge) */
    --border-strong: #324563;  /* --hairline-strong */
    --border-soft:   #1f2f42;  /* dark --border-soft (row separators) */

    /* Quiet Ledger sparkline + status-soft tokens (dark). */
    --spark-down: #5b9bff;
    --spark-up:   #2dd4bf;
    --ok-soft:    #11301f;
    --idle-soft:  #36280c;

    --accent:        #5b8bf5;  /* mockup dark --accent */
    --accent-strong: #7aa2ff;  /* --accent-strong */
    --accent-text:   #5b8bf5;
    --accent-soft:   #16243f;  /* --accent-soft */
    --accent-line:   #2d4877;  /* --accent-line */
    --blue-500:      #5b8bf5;  /* keep --blue-500 accurate in dark for refs */
    --blue-600:      #7aa2ff;

    /* Status (mockup dark) */
    --green:     #46c98a;
    --green-bg:  #10301f;
    --amber:     #e0a850;
    --amber-bg:  #3a2a09;
    --red:       #f0797c;
    --red-bg:    #2a1414;
    --violet-bg: #241945;
    --idle:      #8a99af;
    --idle-bg:   #1a2436;

    /* Traffic (dark) */
    --dl: #5b8bf5;
    --ul: #f06d70;
    --dl-text: #6f9bff;
    --ul-text: #f0797c;

    /* Navigation rail (dark) — Airy refine: the rail keeps the SAME bluer navy
       gradient as light (it is dark in both themes by design), so it never reads
       near-black. Contrast on the rail base #16314f: nav text #c9d8ee = ~9.4:1,
       SYSTEM label #8aa1c2 = ~4.6:1, white #fff = ~12.5:1 (all AA+). */
    --nav-bg:         #16314f;
    --nav-bg-top:     #1b3a5c;
    --nav-bg-bot:     #16314f;
    --nav-ink:        #c9d8ee;
    --nav-ink-faint:  #8aa1c2;
    --nav-active-bg:  rgba(120, 170, 255, .18);
    --nav-active-ink: #ffffff;
    --nav-accent:     #9cc2ff;

    --chart-grid: #1a2740;

    --code-bg: #0c1424;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);
    --shadow:    0 1px 2px rgba(0, 0, 0, .4), 0 10px 30px -14px rgba(0, 0, 0, .7);
    --shadow-lg: 0 2px 6px rgba(0, 0, 0, .5), 0 26px 60px -28px rgba(0, 0, 0, .8);

    /* Airy refine — dark card definition: a faint top-highlight + hairline ring
       lifts the card off the dark canvas; --card-wash is a subtle top-light face. */
    --card-ring: inset 0 1px 0 rgba(255, 255, 255, .05), inset 0 0 0 1px rgba(255, 255, 255, .018);
    --card-wash: linear-gradient(180deg, #162434 0%, #14202e 100%);
}

/* -------------------------------------------------------------------------
 * 2. BASE / GLOBAL
 * ----------------------------------------------------------------------- */

body {
    font-family: var(--font-body);
    font-weight: 450; /* Public Sans 450 — the refined-ops body weight */
    background-color: var(--bg);
    /* PL: Tlo z mockupu: delikatna poswiata akcentu (radial) + pionowy gradient.
       EN: Mockup backdrop: faint accent glow (radial) + vertical body gradient.
       background-attachment:fixed keeps it stable while content scrolls. */
    background-image:
        radial-gradient(1200px 640px at 78% -8%, color-mix(in srgb, var(--accent) 7%, transparent), transparent 60%),
        linear-gradient(180deg, var(--bg-grad-a), var(--bg-grad-b));
    background-attachment: fixed;
    color: var(--text);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    font-feature-settings: "cv05" 1, "ss01" 1;
    letter-spacing: -0.003em;
}

/* Display typography: headings + branded titles use Fraunces (optical-sized).
   PL: Naglowki, marka i naglowek tresci AdminLTE -> font wyswietlaczowy.
   EN: Headings, brand and the AdminLTE content header -> display font. */
h1, h2, h3,
.h1, .h2, .h3,
.card-title,
.content-header h1,
.content-header .h1,
.main-sidebar .brand-link .brand-text,
.modal-title {
    font-family: var(--font-display);
    font-weight: 600;
    letter-spacing: -0.02em;
    font-optical-sizing: auto;
}

/* Technical numbers / code keep the mono face. */
.mono,
.text-monospace,
code, kbd, pre, samp {
    font-family: var(--font-mono);
}

/* PL: Panel jest teraz PELNOEKRANOWY (layout-boxed usuniety z base.html). Gradient
       tla zyje juz bezwarunkowo na `body` (sekcja 2 wyzej), wiec dawny duplikat
       `body.layout-boxed { ... }` zostal usuniety. Pelnoekranowa rekoncyliacja
       .wrapper / .content-wrapper / .content znajduje sie w sekcji 15 ponizej.
   EN: The panel is now FULL-WIDTH (layout-boxed removed from base.html). The page
       gradient already lives unconditionally on `body` (section 2 above), so the
       former duplicate `body.layout-boxed { ... }` block was dropped. The full-width
       .wrapper / .content-wrapper / .content reconcile lives in section 15 below. */

/* Plain links (not buttons): light blue-500 fails AA on white (3.7:1), so use
   blue-700 (#1d4ed8 = 6.3:1). Dark mode keeps blue-400 (passes on dark bg).
   Buttons are excluded so their solid backgrounds drive contrast instead. */
a:not(.btn) {
    color: #1d4ed8; /* blue-700, 6.3:1 on white */
}
a:not(.btn):hover,
a:not(.btn):focus {
    color: var(--navy-600);
}
body.dark-mode a:not(.btn) {
    color: var(--blue-400);
}
body.dark-mode a:not(.btn):hover,
body.dark-mode a:not(.btn):focus {
    color: var(--blue-300);
}

/* Content + footer surfaces.
   PL: Tlo tresci przezroczyste, aby przebijal sie staly gradient body (mockup).
   EN: Transparent content background so the fixed body gradient shows through
       (matches the mockup, where the content column has no opaque fill). */
.content-wrapper {
    background-color: transparent;
    color: var(--text);
}
.main-footer {
    background-color: transparent;
    border-top: 1px solid var(--border);
    color: var(--text-faint);
}
.main-footer a {
    color: var(--accent-text);
}

/* Generic helper text colors */
.text-muted {
    color: var(--text-mute) !important;
}
.text-primary {
    color: var(--accent-text) !important;
}
.text-secondary {
    color: var(--text-soft) !important;
}

/* -------------------------------------------------------------------------
 * 3. SIDEBAR
 * ----------------------------------------------------------------------- */

/* PL: Szyna nawigacji przejeta z mockupu (.rail / .nav): gleboki navy --nav-bg
 *     w obu motywach, marka Fraunces, itemy r=9px / 13.5px / weight 500, hover
 *     tint, active = --nav-active-bg + bialy tekst + lewy 3px gradientowy wskaznik
 *     (::before). Lewy wskaznik to swiadomy wybor mockupu (zachowany).
 * EN: Navigation rail taken from the mockup (.rail / .nav): deep navy --nav-bg in
 *     both themes, Fraunces brand, items r=9px / 13.5px / weight 500, hover tint,
 *     active = --nav-active-bg + white text + a left 3px gradient indicator
 *     (::before). The left indicator is the mockup's deliberate choice (kept). */
.main-sidebar.sidebar-dark-primary,
body.dark-mode .main-sidebar.sidebar-dark-primary {
    /* Airy refine — BLUER navy gradient (#1b3a5c top -> #16314f bottom), same in
       both themes. background-color is the fallback if gradients are unsupported. */
    background-color: var(--nav-bg);
    background-image: linear-gradient(180deg, var(--nav-bg-top) 0%, var(--nav-bg-bot) 100%);
    border-right: 1px solid rgba(255, 255, 255, .09);
}

.main-sidebar .brand-link,
body.dark-mode .main-sidebar .brand-link {
    border-bottom: 1px solid rgba(255, 255, 255, .09);
    color: #ffffff;
}
/* Brand wordmark — Airy refine: clear SANS (Hanken Grotesk 800), tight tracking,
   white for legibility on the bluer rail (matches the mockup's crisp wordmark). */
.main-sidebar .brand-link .brand-text {
    color: #ffffff;
    font-family: var(--font-body);
    font-weight: 800;
    letter-spacing: -0.025em;
}

/* Sidebar nav links — rail item geometry from the mockup. */
.sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link {
    color: var(--nav-ink);
    border-radius: 9px;
    font-size: 13.5px;
    font-weight: 500;
    position: relative;
}
.sidebar-dark-primary .nav-sidebar .nav-link p {
    color: inherit;
}

/* Hover state — subtle white tint, same in both themes (rail is always navy). */
.sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link:hover,
.sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link:focus,
body.dark-mode .sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link:hover,
body.dark-mode .sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link:focus {
    background-color: rgba(255, 255, 255, .07);
    color: #ffffff;
}

/* Active state — AdminLTE marks active with .nav-link.active. Needs !important
   because AdminLTE ships an inline-weight .active background rule.
   PL: Airy refine: tint --nav-active-bg (niebieski) + bialy tekst + akcentowy
       lewy pasek + hairline ring. EN: Airy refine: --nav-active-bg (blue) tint +
       white text + accent left-bar + hairline ring.
   Contrast vs the bluer-navy rail base (--nav-bg-bot #16314f, same in both themes):
     white text #fff = ~12.5:1; nav text #c9d8ee = ~9.4:1; SYSTEM #8aa1c2 = ~4.6:1;
     active icon / accent #9cc2ff = ~7.0:1.
   All pass AA text (>=4.5) and UI (>=3). */
.sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link.active,
.sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link.active:hover,
.sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link.active:focus,
body.dark-mode .sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link.active,
body.dark-mode .sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link.active:hover,
body.dark-mode .sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link.active:focus {
    background-color: var(--nav-active-bg) !important;
    color: var(--nav-active-ink) !important;
    font-weight: 600;
    /* Airy refine — crisper active state: hairline accent ring + soft inner top-light. */
    box-shadow: inset 0 0 0 1px rgba(156, 194, 255, .22), inset 0 1px 0 rgba(255, 255, 255, .06);
}
/* Crisp accent left-bar (the mockup's selected affordance, now the bluer accent). */
.sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link.active::before {
    content: "";
    position: absolute;
    left: -1px;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 60%;
    border-radius: 0 3px 3px 0;
    background: var(--nav-accent);
}
/* Active icon brightens to the brand accent so the row reads as selected. */
.sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link.active .nav-icon,
body.dark-mode .sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link.active .nav-icon {
    color: var(--nav-accent);
}

/* Sidebar section headers / icons */
.sidebar-dark-primary .nav-sidebar .nav-header {
    color: var(--nav-ink-faint);
    font-size: 10px;
    letter-spacing: .15em;
    text-transform: uppercase;
    font-weight: 700;
}
.sidebar-dark-primary .nav-sidebar .nav-icon {
    color: inherit;
    opacity: .82;
}
.sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link.active .nav-icon {
    opacity: 1;
}

/* User panel divider */
.sidebar .user-panel,
body.dark-mode .sidebar .user-panel {
    border-bottom: 1px solid rgba(255, 255, 255, .06);
}

/* -------------------------------------------------------------------------
 * 4. NAVBAR / HEADER
 * ----------------------------------------------------------------------- */

.main-header.navbar {
    background-color: var(--bg-elev);
    border-bottom: 1px solid var(--border);
}
.main-header.navbar .nav-link {
    color: var(--text-soft);
}
.main-header.navbar .nav-link:hover,
.main-header.navbar .nav-link:focus {
    color: var(--accent-text);
}
/* Neutralize AdminLTE light/dark navbar text variants to follow tokens */
.main-header.navbar-light .nav-link,
.main-header.navbar-dark .nav-link {
    color: var(--text-soft);
}

/* -------------------------------------------------------------------------
 * 5. CARDS
 * ----------------------------------------------------------------------- */

.card {
    background-color: var(--bg-elev);
    background-image: var(--card-wash);   /* Airy refine — faint top-light face */
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--card-ring), var(--shadow);   /* + inset top-highlight + hairline ring */
    color: var(--text);
}
.card-header {
    background-color: transparent;
    border-bottom: 1px solid var(--border);
    color: var(--text);
    border-top-left-radius: var(--radius-lg);
    border-top-right-radius: var(--radius-lg);
}
.card-footer {
    background-color: transparent;
    border-top: 1px solid var(--border);
    color: var(--text-soft);
}
.card-title {
    color: var(--text);
    font-weight: 600;
}

/* Outline accent cards (used heavily: .card-primary.card-outline) */
.card-primary.card-outline {
    border-top: 3px solid var(--accent-text);
}
.card-primary:not(.card-outline) > .card-header {
    background-color: var(--accent);
    color: #ffffff;
}
.card-primary:not(.card-outline) > .card-header a,
.card-primary:not(.card-outline) > .card-header .card-title {
    color: #ffffff;
}

/* Solid colored card variants follow semantic tokens */
.card-success:not(.card-outline) > .card-header { background-color: var(--success); color: #fff; }
.card-info:not(.card-outline)    > .card-header { background-color: var(--info);    color: #fff; }
.card-warning:not(.card-outline) > .card-header { background-color: var(--warning); color: #fff; }
.card-danger:not(.card-outline)  > .card-header { background-color: var(--error);   color: #fff; }

.card-success.card-outline { border-top-color: var(--success); }
.card-info.card-outline    { border-top-color: var(--info); }
.card-warning.card-outline { border-top-color: var(--warning); }
.card-danger.card-outline  { border-top-color: var(--error); }

/* -------------------------------------------------------------------------
 * 6. BUTTONS
 * ----------------------------------------------------------------------- */

.btn {
    border-radius: var(--radius-md);
    font-weight: 500;
}

/* Solid buttons use EXPLICIT accessible colors per theme (not var(--accent),
   which flips to a lighter blue in dark mode and loses contrast with white text).
   PL: Mockup "one disciplined blue": fill = --accent (#2563eb), hover = #1d4ed8.
   EN: Mockup's one disciplined blue: fill = accent (#2563eb), hover #1d4ed8. */
.btn-primary {
    background-color: #2563eb; /* accent, white text = 5.17:1 (AA) */
    border-color: #2563eb;
    color: #ffffff;
}
.btn-primary:hover,
.btn-primary:focus,
.btn-primary:active,
.btn-primary.active {
    background-color: #1d4ed8; /* accent-strong, white text = 6.69:1 */
    border-color: #1d4ed8;
    color: #ffffff;
}
body.dark-mode .btn-primary {
    background-color: #2563eb; /* blue-600, white text = 5.2:1 */
    border-color: #2563eb;
    color: #ffffff;
}
body.dark-mode .btn-primary:hover,
body.dark-mode .btn-primary:focus,
body.dark-mode .btn-primary:active {
    background-color: #1d4ed8; /* blue-700 darken (white = 6.7:1; blue-500 hover failed AA at 3.7:1) */
    border-color: #1d4ed8;
    color: #ffffff;
}

.btn-outline-primary {
    color: var(--accent-text);
    border-color: var(--accent-text);
    background-color: transparent;
}
.btn-outline-primary:hover,
.btn-outline-primary:focus,
.btn-outline-primary:active {
    background-color: var(--accent-text);
    border-color: var(--accent-text);
    color: #ffffff;
}

.btn-secondary {
    background-color: var(--slate-500);
    border-color: var(--slate-500);
    color: #ffffff;
}
.btn-secondary:hover,
.btn-secondary:focus {
    background-color: var(--slate-600);
    border-color: var(--slate-600);
    color: #ffffff;
}

.btn-default {
    background-color: var(--bg-elev);
    border-color: var(--border-strong);
    color: var(--text);
}
.btn-default:hover,
.btn-default:focus {
    background-color: var(--bg-soft);
    border-color: var(--border-strong);
    color: var(--text);
}

/* Semantic buttons use EXPLICIT accessible colors (same in both themes).
   Success/danger/info take WHITE text; warning takes DARK text (white on
   amber fails AA, dark slate on amber passes ~5:1). */
.btn-success { background-color: #15803d; border-color: #15803d; color: #fff; }      /* white = 4.8:1 */
.btn-success:hover,
.btn-success:focus,
.btn-success:active { background-color: #166534; border-color: #166534; color: #fff; }
.btn-info    { background-color: #7c3aed; border-color: #7c3aed; color: #fff; }       /* white = 5.7:1 */
.btn-info:hover,
.btn-info:focus,
.btn-info:active { background-color: #6d28d9; border-color: #6d28d9; color: #fff; }
.btn-warning { background-color: #d97706; border-color: #d97706; color: #1f2937; }   /* dark text ~5:1 */
.btn-warning:hover,
.btn-warning:focus,
.btn-warning:active { background-color: #b45309; border-color: #b45309; color: #1f2937; }
.btn-danger  { background-color: #dc2626; border-color: #dc2626; color: #fff; }       /* white = 4.8:1 */
.btn-danger:hover,
.btn-danger:focus,
.btn-danger:active { background-color: #b91c1c; border-color: #b91c1c; color: #fff; }
.btn-outline-secondary {
    color: var(--text-soft);
    border-color: var(--border-strong);
}
.btn-outline-secondary:hover {
    background-color: var(--bg-soft);
    color: var(--text);
}

/* Consistent focus ring across interactive controls (accessibility) */
.btn:focus,
.btn.focus {
    box-shadow: 0 0 0 2px var(--bg-elev), 0 0 0 4px var(--accent-text);
    outline: none;
}

/* -------------------------------------------------------------------------
 * 7. BADGES
 * ----------------------------------------------------------------------- */

.badge {
    border-radius: var(--radius-full);
    font-weight: 600;
    padding: .35em .7em;
}
/* Soft-tinted badges: text colors set EXPLICITLY per theme so token text on
   soft backgrounds reaches AA. Light = darker shade, dark = lighter shade. */
.badge-primary {
    background-color: var(--bg-soft);
    color: #1e3a5f; /* navy-700 on light soft bg */
}
.badge-secondary {
    background-color: var(--bg-soft);
    color: var(--text-soft);
}
.badge-success {
    background-color: var(--green-bg);
    color: #166534; /* green-800 on light green-bg */
}
.badge-info {
    background-color: var(--violet-bg);
    color: #7c3aed; /* violet on light violet-bg (OK) */
}
.badge-warning {
    background-color: var(--amber-bg);
    color: #92400e; /* amber-800 on light amber-bg */
}
.badge-danger {
    background-color: var(--red-bg);
    color: #991b1b; /* red-800 on light red-bg */
}
/* Dark mode: keep soft (dark) backgrounds, lighten text for AA contrast */
body.dark-mode .badge-primary { color: #93c5fd; } /* blue-300 */
body.dark-mode .badge-success { color: #4ade80; } /* green-400 */
body.dark-mode .badge-info    { color: #c4b5fd; } /* violet-300 */
body.dark-mode .badge-warning { color: #fbbf24; } /* amber-400 */
body.dark-mode .badge-danger  { color: #f87171; } /* red-400 */

/* -------------------------------------------------------------------------
 * 8. TABLES
 * ----------------------------------------------------------------------- */

.table {
    color: var(--text-soft);
}
/* Header: mockup style — muted, uppercase, tracked, sitting on the surface
   (no soft fill). The sticky variant in P2.2 also paints --bg-elev, so the
   look stays consistent whether or not the header is stuck. */
.table thead th,
.table th {
    background-color: transparent;
    color: var(--text-mute); /* AA on the elevated surface, light + dark */
    text-transform: uppercase;
    font-size: 11px;
    letter-spacing: .08em;
    font-weight: 700;
    border-bottom: 1px solid var(--border);
    border-top: none;
}
/* Comfortable rows with hairline dividers (mockup spacing). */
.table td {
    border-top: 1px solid var(--border);
    color: var(--text-soft);
    padding-top: .9rem;
    padding-bottom: .9rem;
    vertical-align: middle;
}
.table-bordered,
.table-bordered td,
.table-bordered th {
    border-color: var(--border);
}
/* Row hover — very light accent tint */
.table-hover tbody tr:hover {
    background-color: rgba(59, 130, 246, .06);
    color: var(--text);
}
body.dark-mode .table-hover tbody tr:hover {
    background-color: rgba(96, 165, 250, .10);
}
/* Striped rows */
.table-striped tbody tr:nth-of-type(odd) {
    background-color: var(--bg-soft);
}
/* Rounded wrapper for responsive tables */
.table-responsive {
    border-radius: var(--radius-md);
}

/* -------------------------------------------------------------------------
 * 9. CALLOUTS (used on peer cards)
 * ----------------------------------------------------------------------- */

.callout {
    background-color: var(--bg-elev);
    background-image: var(--card-wash);   /* Airy refine — faint top-light face */
    border-radius: var(--radius-md);
    box-shadow: var(--card-ring), var(--shadow);
    border-left: 5px solid var(--border-strong);
}
.callout-success {
    background-color: rgba(22, 163, 74, .08);
    border-left-color: var(--success);
}
.callout-info {
    background-color: rgba(124, 58, 237, .08);
    border-left-color: var(--info);
}
/* Dark mode: lighten the callout-info accent. The base --info violet (#7c3aed)
   on the dark elevated background (#111d33) is only ~2.95:1, below the WCAG 3:1
   threshold for non-text UI elements. #c4b5fd raises it to ~9:1. Light mode is
   unchanged. No --violet-300 token exists, so the literal is used directly. */
body.dark-mode .callout-info {
    border-left-color: #c4b5fd;
}
.callout-warning {
    background-color: rgba(217, 119, 6, .08);
    border-left-color: var(--warning);
}
.callout-danger {
    background-color: rgba(220, 38, 38, .08);
    border-left-color: var(--error);
}

/* -------------------------------------------------------------------------
 * 10. FORMS
 * ----------------------------------------------------------------------- */

.form-control,
.custom-select {
    background-color: var(--bg-elev);
    border: 1px solid var(--border-strong);
    color: var(--text);
    border-radius: var(--radius-md);
}
.form-control:focus,
.custom-select:focus {
    background-color: var(--bg-elev);
    border-color: var(--accent-text);
    color: var(--text);
    box-shadow: 0 0 0 2px rgba(59, 130, 246, .25);
}
.form-control::placeholder {
    color: var(--text-mute);
    opacity: 1;
}
.form-control:disabled,
.form-control[readonly] {
    background-color: var(--bg-soft);
    color: var(--text-soft);
}
.input-group-text {
    background-color: var(--bg-soft);
    border: 1px solid var(--border-strong);
    color: var(--text-soft);
    border-radius: var(--radius-md);
}
.custom-control-input:checked ~ .custom-control-label::before {
    background-color: var(--accent-text);
    border-color: var(--accent-text);
}
label {
    color: var(--text);
}
.form-text,
small.form-text {
    color: var(--text-mute);
}

/* -------------------------------------------------------------------------
 * 11. NAV PILLS / TABS
 * ----------------------------------------------------------------------- */

.nav-pills .nav-link {
    color: var(--text-soft);
    border-radius: var(--radius-md);
}
.nav-pills .nav-link.active,
.nav-pills .show > .nav-link {
    background-color: var(--accent-text);
    color: #ffffff;
}
.nav-tabs {
    border-bottom: 1px solid var(--border);
}
.nav-tabs .nav-link {
    color: var(--text-soft);
}
.nav-tabs .nav-link.active {
    background-color: var(--bg-elev);
    border-color: var(--border) var(--border) var(--bg-elev);
    color: var(--text);
}

/* -------------------------------------------------------------------------
 * 12. DROPDOWNS / MODALS / ALERTS
 * ----------------------------------------------------------------------- */

.dropdown-menu {
    background-color: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    color: var(--text);
}
.dropdown-item {
    color: var(--text);
}
.dropdown-item:hover,
.dropdown-item:focus {
    background-color: var(--bg-soft);
    color: var(--text);
}
.dropdown-divider {
    border-color: var(--border);
}

.modal-content {
    background-color: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    color: var(--text);
    box-shadow: var(--shadow-lg);
}
.modal-header {
    border-bottom: 1px solid var(--border);
}
.modal-footer {
    border-top: 1px solid var(--border);
}
.modal-title {
    color: var(--text);
}
.close {
    color: var(--text-soft);
    text-shadow: none;
}
.close:hover {
    color: var(--text);
}

/* Alerts — semantic soft surfaces */
.alert {
    border-radius: var(--radius-md);
    border: 1px solid transparent;
}
/* Light alerts: darker text on soft bg for AA (var token text was too light).
   Dark variants below already pass and stay as-is. */
.alert-success {
    background-color: var(--green-bg);
    border-color: var(--success);
    color: #166534; /* green-800 */
}
.alert-info {
    background-color: var(--violet-bg);
    border-color: var(--info);
    color: var(--info); /* violet OK on light violet-bg */
}
.alert-warning {
    background-color: var(--amber-bg);
    border-color: var(--warning);
    color: #92400e; /* amber-800 */
}
.alert-danger {
    background-color: var(--red-bg);
    border-color: var(--error);
    color: #991b1b; /* red-800 */
}
body.dark-mode .alert-success { color: #4ade80; }
body.dark-mode .alert-info    { color: #c4b5fd; }
body.dark-mode .alert-warning { color: #fbbf24; }
body.dark-mode .alert-danger  { color: #f87171; }

/* Toasts (AdminLTE/Bootstrap Toasts in base_messages.html). Default toast is a
   theme surface; the bg-* utility classes carry the semantic color. AdminLTE's
   bg-* utilities force their own background, so use !important + EXPLICIT
   accessible color pairs (white text, dark text on amber). .toast-header
   inherits the toast color so it stays readable. */
.toast {
    background-color: var(--bg-elev);
    color: var(--text);
    border: 1px solid var(--border);
    box-shadow: var(--shadow-lg);
}
.toast-header {
    background-color: transparent;
    color: inherit;
    border-bottom: 1px solid var(--border);
}
.toast.bg-success { background-color: #15803d !important; color: #fff !important; }   /* 4.8:1 */
.toast.bg-info    { background-color: #7c3aed !important; color: #fff !important; }   /* 5.7:1 */
.toast.bg-warning { background-color: #d97706 !important; color: #1f2937 !important; } /* dark ~5:1 */
.toast.bg-danger  { background-color: #dc2626 !important; color: #fff !important; }   /* 4.8:1 */
.toast.bg-primary { background-color: #1e3a5f !important; color: #fff !important; }   /* 8.2:1 */
/* Header + close button inherit the colored toast's text color */
.toast.bg-success .toast-header,
.toast.bg-info .toast-header,
.toast.bg-warning .toast-header,
.toast.bg-danger .toast-header,
.toast.bg-primary .toast-header {
    background-color: transparent;
    color: inherit;
    border-bottom-color: rgba(255, 255, 255, .25);
}
.toast.bg-warning .toast-header { border-bottom-color: rgba(31, 41, 55, .25); }
.toast.bg-success .close,
.toast.bg-info .close,
.toast.bg-warning .close,
.toast.bg-danger .close,
.toast.bg-primary .close {
    color: inherit;
    opacity: .85;
    text-shadow: none;
}

/* -------------------------------------------------------------------------
 * 13. KPI WIDGETS (.info-box / .small-box)
 * ----------------------------------------------------------------------- */

.info-box {
    background-color: var(--bg-elev);
    background-image: var(--card-wash);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--card-ring), var(--shadow);
    color: var(--text);
}
.info-box .info-box-icon {
    background-color: transparent;
    color: var(--accent-text);
    border-radius: var(--radius-md);
}
.info-box .info-box-text {
    color: var(--text-mute);
}
.info-box .info-box-number {
    color: var(--text);
}

.small-box {
    background-color: var(--bg-elev);
    background-image: var(--card-wash);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--card-ring), var(--shadow);
    color: var(--text);
}
.small-box h3,
.small-box p {
    color: var(--text);
}
.small-box .icon {
    color: var(--accent-text);
    opacity: .35;
}
.small-box .small-box-footer {
    background-color: transparent;
    border-top: 1px solid var(--border);
    color: var(--text-soft);
}

/* -------------------------------------------------------------------------
 * 14. MISC — code, scrollbar, list groups, pagination
 * ----------------------------------------------------------------------- */

code,
kbd,
pre,
.mono {
    font-family: var(--font-mono);
}
/* Inline code: blue-500 fails AA on the light code surface. Use navy-600 in
   light (passes on slate-100), keep blue-400 token in dark. */
code {
    background-color: var(--code-bg);
    color: #274d7e; /* navy-600 on light code-bg */
    border-radius: var(--radius-sm);
    padding: .15em .4em;
}
body.dark-mode code {
    color: var(--accent-text); /* blue-400 on dark code-bg */
}
pre {
    background-color: var(--code-bg);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.list-group-item {
    background-color: var(--bg-elev);
    border-color: var(--border);
    color: var(--text);
}

.page-link {
    background-color: var(--bg-elev);
    border-color: var(--border);
    color: var(--accent-text);
}
.page-item.active .page-link {
    background-color: var(--accent-text);
    border-color: var(--accent-text);
    color: #ffffff;
}
.page-item.disabled .page-link {
    background-color: var(--bg-soft);
    border-color: var(--border);
    color: var(--text-mute);
}

hr {
    border-top-color: var(--border);
}

/* Subtle, theme-aware scrollbars */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
}
::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}
::-webkit-scrollbar-track {
    background: transparent;
}
::-webkit-scrollbar-thumb {
    background-color: var(--border-strong);
    border-radius: var(--radius-full);
    border: 2px solid transparent;
    background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
    background-color: var(--slate-400);
}

/* -------------------------------------------------------------------------
 * 15. DARK-MODE GAPS & MISC SURFACES
 * PL: Punktowe poprawki kontrastu dla powierzchni, które AdminLTE/legacy CSS
 *     pozostawiał jasnymi w trybie ciemnym lub bez kolorów tokenowych.
 * EN: Targeted contrast fixes for surfaces AdminLTE/legacy CSS left light in
 *     dark mode or without token colors.
 * ----------------------------------------------------------------------- */

/* Invite text box — legacy hard-coded light bg/border in custom_styles.css */
body.dark-mode #inviteTextContainer {
    background-color: var(--bg-soft);
    border-color: var(--border);
    color: var(--text);
}

/* Generic .bg-light blocks must follow the theme surface in dark mode */
body.dark-mode .bg-light {
    background-color: var(--bg-soft) !important;
    color: var(--text) !important;
}

/* Breadcrumbs — transparent bar with token-colored items */
.breadcrumb {
    background-color: transparent;
}
.breadcrumb-item,
.breadcrumb-item a {
    color: var(--accent-text);
}
.breadcrumb-item.active {
    color: var(--text-soft);
}
.breadcrumb-item + .breadcrumb-item::before {
    color: var(--text-mute);
}

/* Content header — mockup clean page header.
 * PL: Tytuł strony wg mockupu (.lede h1 / .crumbs__here): Fraunces, optical-sizing,
 *     wieksza skala + ciasny tracking, bez wizualnego szumu. Breadcrumb to lekki,
 *     mono-szary trop po prawej. Lekki oddech nad/pod naglowkiem (content-header
 *     domyslnie ma maly padding AdminLTE). Tlo headera transparentne -> przebija
 *     szary gradient strony (jak w mockupie).
 * EN: Page title per the mockup (.lede h1 / .crumbs__here): Fraunces, optical
 *     sizing, larger scale + tight tracking, no visual noise. Breadcrumb is a light
 *     grey trail on the right. A little breathing room above/below. Header bg is
 *     transparent so the page's grey gradient shows through (as in the mockup). */
.content-header {
    padding: 1.5rem 0.5rem 0.75rem;
    background-color: transparent;
}
.content-header h1,
.content-header .h1 {
    color: var(--text);
    font-family: var(--font-display);
    font-optical-sizing: auto;
    font-weight: 600;
    font-size: clamp(1.65rem, 2.4vw, 2rem);
    line-height: 1.1;
    letter-spacing: -0.025em;
}
/* Breadcrumb trail — quieter than AdminLTE default: muted, small, aligned to the
   title baseline. Active (current page) stays softly emphasised. */
.content-header .breadcrumb {
    margin-bottom: 0;
    padding: 0;
    font-size: 0.8rem;
    background-color: transparent;
}
.content-header .breadcrumb-item,
.content-header .breadcrumb-item a {
    color: var(--text-mute);
}
.content-header .breadcrumb-item.active {
    color: var(--text-soft);
}

/* Card tool icons (collapse/remove) use muted token, brighten on hover */
.btn-tool {
    color: var(--text-mute);
}
.btn-tool:hover,
.btn-tool:focus {
    color: var(--text);
}

/* Full-width app shell — the elevated cards must sit on the page's grey/gradient
 * backdrop, NOT on a flat white wrapper, and the panel must fill the ENTIRE
 * viewport width (no boxed max-width frame, no empty band on the right at wide
 * resolutions).
 * PL: PELNA SZEROKOSC. `layout-boxed` usunieto z base.html, wiec AdminLTE nie
 *     ogranicza juz .wrapper do ramki max-width:1250px ani nie centruje go
 *     marginesem auto — panel wypelnia cala szerokosc okna i adaptuje sie do
 *     rozdzielczosci. Te reguly (dawniej kluczowane na body.layout-boxed) sa teraz
 *     BEZWARUNKOWE: .wrapper / .content-wrapper / .content sa PRZEZROCZYSTE, wiec
 *     przebija staly gradient body z mockupu (radial akcentu + linear
 *     --bg-grad-a -> --bg-grad-b). ZA uniesionymi kartami widac szarawe tlo strony,
 *     a 1px ramka + --shadow wyraznie je odcinaja. Tresc dostaje rozsadny padding
 *     boczny (nie edge-to-edge), skalowany clampem wraz z szerokoscia.
 * EN: FULL WIDTH. `layout-boxed` was removed from base.html, so AdminLTE no longer
 *     constrains .wrapper to the max-width:1250px frame nor centers it with auto
 *     margins — the panel fills the whole viewport width and adapts to resolution.
 *     These rules (formerly keyed on body.layout-boxed) are now UNCONDITIONAL: the
 *     .wrapper / .content-wrapper / .content are TRANSPARENT so the fixed mockup body
 *     gradient shows through (accent radial + linear --bg-grad-a -> --bg-grad-b). The
 *     grey page backdrop is visible BEHIND the raised cards, and their 1px border +
 *     --shadow lift them off it. The content gets sensible side padding (not
 *     edge-to-edge), clamped so it scales with width. */
.wrapper,
body.dark-mode .wrapper {
    background-color: transparent;
    box-shadow: none;
}
/* Content surfaces stay transparent so the gradient (carried by body, behind the
   now-transparent wrapper) is the backdrop the cards lift off of. */
.content-wrapper,
body.dark-mode .content-wrapper,
.content-wrapper > .content,
.content-wrapper > .content-header {
    background-color: transparent;
}
/* Sensible side padding for the full-width content. AdminLTE ships .content with
   only 0 0.5rem and .container-fluid with 7.5px — far too tight edge-to-edge once
   the boxed frame is gone. Clamp the gutter so it breathes on wide screens yet
   stays compact on tablets. */
.content-wrapper > .content {
    padding-left: clamp(1rem, 2.4vw, 2.5rem);
    padding-right: clamp(1rem, 2.4vw, 2.5rem);
}
.content-wrapper > .content > .container-fluid {
    padding-left: 0;
    padding-right: 0;
}
.content-wrapper > .content-header {
    padding-left: clamp(1rem, 2.4vw, 2.5rem);
    padding-right: clamp(1rem, 2.4vw, 2.5rem);
}

/* -------------------------------------------------------------------------
 * 16. INSTANCE TRAFFIC CHART (Chart.js area chart)
 * PL: Styl "legend chips" (v Download / ^ Upload) z biezaca wartoscia obok
 *     wykresu obszarowego ruchu instancji. Tokeny -> spojnosc light/dark.
 * EN: Styling for the legend chips (v Download / ^ Upload) with the current
 *     value next to the instance traffic area chart. Token-driven so it stays
 *     consistent across light/dark.
 * ----------------------------------------------------------------------- */

.traffic-chart-chips .traffic-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
    background-color: var(--bg-soft);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.6;
    white-space: nowrap;
}
.traffic-chart-chips .traffic-chip .traffic-chip-value {
    /* Monospace digits so the value width does not jitter on live updates. */
    font-family: var(--font-mono);
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.traffic-chart-chips .traffic-chip i {
    font-size: 11px;
}

/* Empty-state overlay text follows the muted token (AA on the card surface). */
.traffic-chart-empty .text-muted {
    color: var(--text-mute) !important;
}

/* =========================================================================
 * === Phase 1 UI primitives ===
 * -------------------------------------------------------------------------
 * PL: Warstwa prymitywów UI fazy 1: status chips, focus-visible ring, baza
 *     skeletonu, empty-state, stany walidacji + pasek akcji. Wyłącznie CSS,
 *     additive (nie nadpisuje AdminLTE/Bootstrap). Wszystkie kolory z tokenów
 *     motywu; kontrast tekstu >=4.5:1, UI >=3:1, light + dark. ZERO side-stripe
 *     (akcentowy border-left), ZERO shimmer/gradient. Każda animacja ma fallback
 *     dla prefers-reduced-motion.
 * EN: Phase 1 UI primitive layer: status chips, focus-visible ring, skeleton
 *     base, empty-state, validation states + action bar. CSS-only, additive
 *     (does not override AdminLTE/Bootstrap). All colors from theme tokens;
 *     text contrast >=4.5:1, UI >=3:1, light + dark. NO side-stripe (border-left
 *     accent), NO shimmer/gradient. Every animation has a reduced-motion fallback.
 * ========================================================================= */

/* Shared ease-out curve for Phase 1 motion (instant fallback handled per rule). */
:root {
    --zg-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}

/* -------------------------------------------------------------------------
 * P1.1 STATUS CHIPS
 * Pill: soft-tinted background + accessible same-hue text + same-hue border.
 * Each variant pairs an explicit text color per theme to keep AA on the soft
 * background (mirrors the badge convention already used in section 7).
 * ----------------------------------------------------------------------- */

.zg-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 8px;
    border-radius: var(--radius-full);
    border: 1px solid transparent;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
    vertical-align: middle;
}
/* Small icon slot inside a chip stays proportional to the label. */
.zg-chip i {
    font-size: 0.7rem;
    line-height: 1;
}

/* Variant: OK / healthy (green) */
.zg-chip--ok {
    background-color: var(--green-bg);
    border-color: rgba(22, 101, 52, .35);
    color: #166534; /* green-800 on light green-bg = 6.49:1 */
}
body.dark-mode .zg-chip--ok {
    border-color: rgba(74, 222, 128, .35);
    color: #4ade80; /* green-400 on dark green-bg = 8.37:1 */
}

/* Variant: warning (amber) */
.zg-chip--warn {
    background-color: var(--amber-bg);
    border-color: rgba(146, 64, 14, .35);
    color: #92400e; /* amber-800 on light amber-bg = 6.37:1 */
}
body.dark-mode .zg-chip--warn {
    border-color: rgba(251, 191, 36, .35);
    color: #fbbf24; /* amber-400 on dark amber-bg = 8.3:1 */
}

/* Variant: danger (red) */
.zg-chip--danger {
    background-color: var(--red-bg);
    border-color: rgba(153, 27, 27, .35);
    color: #991b1b; /* red-800 on light red-bg = 6.8:1 */
}
body.dark-mode .zg-chip--danger {
    border-color: rgba(248, 113, 113, .35);
    color: #f87171; /* red-400 on dark red-bg = 5.88:1 */
}

/* Variant: info (violet) */
.zg-chip--info {
    background-color: var(--violet-bg);
    border-color: rgba(124, 58, 237, .35);
    color: #7c3aed; /* violet on light violet-bg = 4.8:1 */
}
body.dark-mode .zg-chip--info {
    border-color: rgba(196, 181, 253, .35);
    color: #c4b5fd; /* violet-300 on dark violet-bg = 8.75:1 */
}

/* Variant: neutral (slate) */
.zg-chip--neutral {
    background-color: var(--bg-soft);
    border-color: var(--border-strong);
    color: var(--text-soft); /* slate-600 on bg-soft = 6.92:1 (light) / 8.11:1 (dark) */
}

/* Optional status dot (inherits chip text color via currentColor). */
.zg-chip__dot {
    width: 8px;
    height: 8px;
    border-radius: var(--radius-full);
    background-color: currentColor;
    flex: 0 0 auto;
}
/* Pulsing dot: gentle opacity + scale, NOT a shimmer/sweep. */
.zg-chip__dot--pulse {
    animation: zg-dot-pulse 1.8s var(--zg-ease-out) infinite;
}
@keyframes zg-dot-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%      { opacity: .45; transform: scale(.82); }
}
/* Reduced motion: no pulse, hold the visible (full opacity) state. */
@media (prefers-reduced-motion: reduce) {
    .zg-chip__dot--pulse {
        animation: none;
        opacity: 1;
    }
}

/* -------------------------------------------------------------------------
 * P1.2 FOCUS-VISIBLE RING (global, keyboard-only)
 * Removes the default outline ONLY for mouse focus (:focus:not(:focus-visible))
 * and draws an accessible blue ring for keyboard focus. inherit radius so the
 * ring hugs rounded controls. Dark mode uses a brighter blue for visibility.
 * ----------------------------------------------------------------------- */

:focus-visible {
    outline: 2px solid var(--blue-500); /* #3b82f6 vs light bg = 3.52:1 (UI AA) */
    outline-offset: 2px;
    border-radius: inherit;
}
body.dark-mode :focus-visible {
    outline-color: var(--blue-400); /* #60a5fa vs dark bg = 7.25:1 */
}
/* Drop the legacy/native outline only when :focus-visible is NOT matched
   (i.e. pointer focus), so keyboard users always keep a ring. */
:focus:not(:focus-visible) {
    outline: none;
}
/* Explicitly cover the key interactive controls so the ring wins over the
   component's own focus styling (buttons already ship a box-shadow ring in
   section 6 — :focus-visible adds the outline on top for keyboard users). */
.btn:focus-visible,
.nav-link:focus-visible,
.form-control:focus-visible,
.custom-select:focus-visible,
.zt-period a:focus-visible,
.page-link:focus-visible,
.main-header.navbar .nav-link:focus-visible {
    outline: 2px solid var(--blue-500);
    outline-offset: 2px;
}
body.dark-mode .btn:focus-visible,
body.dark-mode .nav-link:focus-visible,
body.dark-mode .form-control:focus-visible,
body.dark-mode .custom-select:focus-visible,
body.dark-mode .zt-period a:focus-visible,
body.dark-mode .page-link:focus-visible,
body.dark-mode .main-header.navbar .nav-link:focus-visible {
    outline-color: var(--blue-400);
}

/* -------------------------------------------------------------------------
 * P1.3 SKELETON BASE
 * Loading placeholder block. Subtle opacity pulse (NO shimmer/gradient-sweep).
 * Reduced motion: static block.
 * ----------------------------------------------------------------------- */

.zg-skel {
    display: block;
    background-color: var(--bg-soft);
    border-radius: var(--radius-sm);
    animation: zg-skel-pulse 1.6s var(--zg-ease-out) infinite;
}
@keyframes zg-skel-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: .5; }
}
@media (prefers-reduced-motion: reduce) {
    .zg-skel {
        animation: none;
        opacity: .8;
    }
}
/* Size helpers */
.zg-skel--text {
    height: .9em;
    width: 100%;
    margin: .25em 0;
    border-radius: var(--radius-sm);
}
.zg-skel--line {
    height: 1.25rem;
    width: 100%;
    margin: .35rem 0;
}
.zg-skel--chart {
    height: 220px;
    width: 100%;
    border-radius: var(--radius-md);
}

/* -------------------------------------------------------------------------
 * P1.4 EMPTY STATE
 * Centered placeholder: large muted icon, soft title, optional message + slot.
 * ----------------------------------------------------------------------- */

.zg-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 3rem 1.5rem;
    gap: .5rem;
}
.zg-empty__icon {
    font-size: 2.75rem;
    line-height: 1;
    color: var(--text-mute); /* 4.76:1 light / 5.43:1 dark on elevated surface */
    margin-bottom: .25rem;
}
.zg-empty__title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-soft); /* 7.58:1 light / 8.83:1 dark */
    margin: 0;
}
.zg-empty__message {
    font-size: .875rem;
    color: var(--text-mute);
    margin: 0;
    max-width: 40ch;
}
.zg-empty__action {
    margin-top: .75rem;
}

/* -------------------------------------------------------------------------
 * P1.5 VALIDATION STATES
 * Builds on Bootstrap's .is-invalid / .is-valid (crispy adds them). Red/green
 * border + subtle soft tint, accessible inline error text + error summary.
 * ----------------------------------------------------------------------- */

.form-control.is-invalid,
.custom-select.is-invalid {
    border-color: var(--red); /* #dc2626 vs elev = 4.83:1 (UI AA) */
    background-color: var(--red-bg);
}
.form-control.is-invalid:focus,
.custom-select.is-invalid:focus {
    border-color: var(--red);
    box-shadow: 0 0 0 2px rgba(220, 38, 38, .25);
}
.form-control.is-valid,
.custom-select.is-valid {
    border-color: #15803d; /* green-700 vs elev = 5.02:1 (UI AA) */
    background-color: var(--green-bg);
}
.form-control.is-valid:focus,
.custom-select.is-valid:focus {
    border-color: #15803d;
    box-shadow: 0 0 0 2px rgba(21, 128, 61, .25);
}
body.dark-mode .form-control.is-invalid,
body.dark-mode .custom-select.is-invalid {
    border-color: #f87171; /* red-400 vs dark elev = 6.09:1 */
}
body.dark-mode .form-control.is-valid,
body.dark-mode .custom-select.is-valid {
    border-color: #4ade80; /* green-400 vs dark elev = 9.66:1 */
}

/* Inline field error message (small, red, icon slot). */
.zg-field-error {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin-top: .3rem;
    font-size: .8125rem;
    line-height: 1.4;
    color: #991b1b; /* red-800 on elev = 8.31:1 (light) */
}
.zg-field-error i {
    margin-top: .1em;
    flex: 0 0 auto;
}
body.dark-mode .zg-field-error {
    color: #f87171; /* red-400 on dark elev = 6.09:1 */
}

/* Error summary: a danger alert at the top of a form, with links to fields. */
.zg-error-summary {
    border-radius: var(--radius-md);
    border: 1px solid var(--error);
    background-color: var(--red-bg);
    color: #991b1b; /* red-800 on red-bg = 6.8:1 */
    padding: .75rem 1rem;
    margin-bottom: 1rem;
}
body.dark-mode .zg-error-summary {
    color: #f87171; /* red-400 on dark red-bg = 5.88:1 */
}
.zg-error-summary__title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: 600;
    font-size: .9375rem;
    margin-bottom: .35rem;
}
.zg-error-summary ul {
    margin: 0;
    padding-left: 1.25rem;
}
.zg-error-summary a {
    color: inherit;
    text-decoration: underline;
}

/* -------------------------------------------------------------------------
 * P1.6 ACTION BAR
 * Unified form action row: primary + secondary grouped left, destructive
 * pushed right (margin-left:auto). flex wrap so it stays usable on narrow
 * viewports. Used where action buttons are authored directly in templates.
 * ----------------------------------------------------------------------- */

.zg-actionbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
}
.zg-actionbar > .zg-actionbar__end {
    margin-left: auto;
}

/* =========================================================================
 * === Phase 2 UI primitives ===
 * -------------------------------------------------------------------------
 * PL: Warstwa fazy 2: motion wejscia modali/alertow, sticky + sortowalne
 *     naglowki tabel, kontener mini-sparkline w pasku KPI oraz helpery skeletonu
 *     dedykowane kartom peerow i wykresom. Wylacznie CSS, additive. Kolory z
 *     tokenow; kontrast tekstu >=4.5:1, UI >=3:1, light + dark. ZERO side-stripe,
 *     ZERO shimmer/gradient-sweep. Kazda animacja ma fallback reduced-motion.
 * EN: Phase 2 primitive layer: modal/alert entrance motion, sticky + sortable
 *     table headers, the KPI-strip mini-sparkline container and skeleton helpers
 *     scoped to peer cards and charts. CSS-only, additive. Colors from tokens;
 *     text contrast >=4.5:1, UI >=3:1, light + dark. NO side-stripe, NO
 *     shimmer/gradient-sweep. Every animation has a reduced-motion fallback.
 * ========================================================================= */

/* -------------------------------------------------------------------------
 * P2.1 MODAL / ALERT ENTRANCE MOTION (CSS-only)
 * PL: Subtelne wejscie. Bootstrap4 animuje .modal-dialog transformem; tu tylko
 *     dostrajamy czas + krzywa i dokladamy lekki translateY. NIE ruszamy JS
 *     Bootstrapa. Alert/toast: fade + delikatny slide-in.
 * EN: Subtle entrance. Bootstrap4 already transitions .modal-dialog via transform;
 *     here we only tune the duration + easing and add a small translateY. The
 *     Bootstrap JS is untouched. Alert/toast: fade + slight slide-in.
 * ----------------------------------------------------------------------- */

/* Modal: hold a small downward offset + transparency while .fade is not .show,
   then ease into place. ~180ms with the shared ease-out curve. */
.modal.fade .modal-dialog {
    transition: transform .18s var(--zg-ease-out), opacity .18s var(--zg-ease-out);
    transform: translateY(8px);
    opacity: 0;
}
.modal.fade.show .modal-dialog {
    transform: none;
    opacity: 1;
}

/* Alerts authored in the page (and AdminLTE/Bootstrap toasts) get a one-shot
   fade + slide-in on first paint. animation-fill-mode keeps the end state. */
.alert,
.toast.show {
    animation: zg-fade-slide-in .2s var(--zg-ease-out) both;
}
@keyframes zg-fade-slide-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
}

/* Reduced motion: no slide, no offset — render instantly in the final state. */
@media (prefers-reduced-motion: reduce) {
    .modal.fade .modal-dialog {
        transition: none;
        transform: none;
        opacity: 1;
    }
    .modal.fade.show .modal-dialog {
        transform: none;
        opacity: 1;
    }
    .alert,
    .toast.show {
        animation: none;
    }
}

/* -------------------------------------------------------------------------
 * P2.2 STICKY + SORTABLE TABLE HEADERS
 * PL: Naglowek <thead th> przyklejony do gory podczas scrollowania, na elew.
 *     tle z subtelnym cieniem. Sortowalne naglowki (data-sort) dostaja kursor,
 *     neutralny caret i widoczny aktywny kierunek. z-index semantyczny (2), NIE
 *     9999 — naglowek ma byc nad wierszami, nie nad modalami/dropdownami.
 * EN: <thead th> sticks to the top while scrolling, on the elevated surface with
 *     a subtle shadow. Sortable headers (data-sort) get a pointer cursor, a
 *     neutral caret and a visible active direction. Semantic z-index (2), NOT
 *     9999 — the header should sit above rows, never above modals/dropdowns.
 * ----------------------------------------------------------------------- */

.table thead th {
    position: -webkit-sticky;
    position: sticky;
    top: 0;
    z-index: 2;
    /* Solid elevated background so scrolled rows do not bleed through the
       (otherwise translucent) soft header tint. */
    background-color: var(--bg-elev);
    /* Subtle separating shadow that reads as the header floating over rows. */
    box-shadow: inset 0 -1px 0 var(--border), 0 2px 4px -2px rgba(15, 29, 51, .12);
}
body.dark-mode .table thead th {
    box-shadow: inset 0 -1px 0 var(--border), 0 2px 6px -2px rgba(0, 0, 0, .5);
}

/* Sortable header affordances. */
.zg-sortable-th {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}
.zg-sortable-th:hover {
    color: var(--text);   /* lift from the muted header color on hover */
}
/* The injected sort caret: neutral (muted) until the column is active. */
.zg-sort-caret {
    margin-left: .35em;
    font-size: .85em;
    color: var(--text-mute);   /* 4.76:1 light / 5.43:1 dark — UI affordance */
    vertical-align: baseline;
}
/* Active sorted column: caret takes the accent text color for clear direction. */
.zg-sortable-th[aria-sort="ascending"] .zg-sort-caret,
.zg-sortable-th[aria-sort="descending"] .zg-sort-caret {
    color: var(--accent-text);
}
.zg-sortable-th[aria-sort="ascending"],
.zg-sortable-th[aria-sort="descending"] {
    color: var(--text);   /* active header label fully opaque */
}

/* -------------------------------------------------------------------------
 * P2.3 KPI STRIP MINI-SPARKLINE
 * PL: Maly kontener na 7-dniowy sparkline pod liczbami KPI. Stala, niska
 *     wysokosc, brak interakcji (czysto dekoracyjny trend). Degraduje pusto.
 * EN: Small container for the 7-day sparkline under the KPI numbers. Fixed low
 *     height, non-interactive (a purely decorative trend). Degrades to empty.
 * ----------------------------------------------------------------------- */

.kpi-sparkline {
    position: relative;
    height: 36px;
    margin-top: .35rem;
}
.kpi-sparkline canvas {
    display: block;
    width: 100%;
    height: 36px;
}

/* -------------------------------------------------------------------------
 * P2.4 SKELETON HELPERS (peer card + chart overlay)
 * PL: Helpery oparte o baze .zg-skel z Fazy 1. Reguła lifecycle: skeleton w
 *     kontenerze [data-zg-skel-host] jest widoczny dopoki host NIE ma klasy
 *     .zg-loaded (poller dodaje ja po pierwszym udanym update peera). Nakladka
 *     .zg-skel--chart-overlay lezy na canvasie do pierwszego renderu.
 * EN: Helpers built on the Phase 1 .zg-skel base. Lifecycle rule: a skeleton
 *     inside a [data-zg-skel-host] container stays visible until the host gains
 *     the .zg-loaded class (the poller adds it after the first successful peer
 *     update). The .zg-skel--chart-overlay sits over the canvas until first paint.
 * ----------------------------------------------------------------------- */

/* Host gains .zg-loaded -> every skeleton inside collapses; the real content
   (already in the DOM, populated by the poller) takes over. */
[data-zg-skel-host].zg-loaded .zg-skel {
    display: none;
}

/* Compact skeleton line tuned for the inline throughput value on a peer card. */
.zg-skel--inline {
    display: inline-block;
    height: .85em;
    width: 8.5em;
    vertical-align: middle;
    border-radius: var(--radius-sm);
}

/* Chart-overlay skeleton: absolutely fills the relatively-positioned canvas
   wrapper so it covers the canvas until the first render hides it. */
.zg-skel--chart-overlay {
    position: absolute;
    inset: 0;
    height: auto;
    width: auto;
    z-index: 1;
}

