/* ==================================================================
   EmpireoneCX KPI Command

   THEME — change these and the whole app follows.
   The same five brand values are also in the tailwind.config block at
   the top of inc/header.php; keep the two in step.
   ================================================================== */
:root {
    --brand-300: #A5B4FC;
    --brand-400: #818CF8;
    --brand-500: #6366F1;
    --brand-600: #4F46E5;
    --brand-700: #4338CA;

    /* The signature sweep: violet -> magenta -> orange. Spent on the accent
       word of a headline, the active nav marker, the avatar and the one
       primary button — nowhere else, so it stays an accent. */
    --grad: linear-gradient(95deg, #7B2FF7 0%, #A93BE0 22%, #E0409A 55%, #F2567E 72%, #FB7A3C 100%);

    /* ---- neutral scale ----------------------------------------------
       The tailwind.config block in inc/header.php points both `ink-*` and
       `slate-*` at these, so every neutral in the markup follows the theme
       without a single `dark:` class. 50 is the page, 950 the strongest
       ink; dark mode inverts the ends and keeps the roles. */
    --c-ink-50:  #F8FAFC;
    --c-ink-100: #F1F5F9;
    --c-ink-200: #E2E8F0;
    --c-ink-300: #CBD5E1;
    --c-ink-400: #94A3B8;
    --c-ink-500: #64748B;
    --c-ink-600: #475569;
    --c-ink-700: #334155;
    --c-ink-800: #1E293B;
    --c-ink-900: #0F172A;
    --c-ink-950: #020617;

    /* ---- surfaces and lines ---- */
    --surface:   #FFFFFF;   /* cards */
    --surface-2: #FAFBFC;   /* table head, hover rows */
    --panel:     #F8F9FC;   /* inset blocks */
    --topbar:    rgba(255, 255, 255, .88);
    --line:      #E8EAF0;
    --line-soft: #F1F3F7;
    --line-mid:  #E2E5EC;

    /* ---- brand tints ------------------------------------------------
       The violet already under the active nav item, lifted out of that one
       rule and given three strengths. Everything that used to be a grey
       wash — hovers, table heads, field grounds, card edges — now draws
       from these, which is what stops the app reading as white with a
       single purple stripe in the sidebar.

       Alpha rather than hex on purpose: the same token then works over a
       white card, over a tinted one and over the dark page. */
    --tint-1:    rgba(169, 59, 224, .05);   /* page ground, row hover */
    --tint-2:    rgba(169, 59, 224, .09);   /* nav hover, table head, fields */
    --tint-3:    rgba(169, 59, 224, .15);   /* active nav, stat cards */
    --tint-line: rgba(169, 59, 224, .18);   /* borders on anything tinted */
    --brand-ink: #7B2FF7;                   /* text and icons on a tint */

    /* The page itself. Three very soft blooms over the neutral ground, so
       a screen of white cards has something to sit on. */
    --page-wash:
        radial-gradient(1200px 780px at  6%  -8%, rgba(123,  47, 247, .12) 0%, transparent 60%),
        radial-gradient(1000px 700px at 97%   0%, rgba(224,  64, 154, .08) 0%, transparent 56%),
        radial-gradient(1100px 900px at 62% 104%, rgba(251, 122,  60, .06) 0%, transparent 58%);

    --ink-1: #0B0B12;
    --ink-2: #5A6274;
    --ink-3: #8A92A6;
    --ink-4: #A8AEBD;

    /* Status colours. Deliberately not the brand colour — a KPI being on
       target has nothing to do with the company's palette. */
    --ok:      #059669;   --ok-bg:   #ECFDF5;
    --warn:    #D97706;   --warn-bg: #FFFBEB;
    --bad:     #E11D48;   --bad-bg:  #FFF1F2;
    --idle:    #94A3B8;   --idle-bg: #F8FAFC;
    --info:    #0284C7;   --info-bg: #F0F9FF;

    /* Chart ink, read by assets/js/app.js. */
    --viz-grid:    #EEF0F4;
    --viz-axis:    #94A3B8;
    --viz-surface: #FFFFFF;
    --viz-legend:  #52514E;
}

/* ==================================================================
   DARK MODE

   Chosen, not flipped: every value below was picked for a dark ground
   rather than computed from its light twin. inc/header.php stamps
   data-theme on <html> before the page paints, so there is no flash.
   ================================================================== */
:root[data-theme="dark"] {
    color-scheme: dark;

    /* The scale inverts end for end — 50 is still "the page", 900 is still
       "the strongest ink", they have just swapped which is darker. The
       middle steps are not a mirror: text steps are lifted so they stay
       legible on black, where a straight inversion goes muddy. */
    --c-ink-50:  #0E0F16;
    --c-ink-100: #14161F;
    --c-ink-200: #242838;
    --c-ink-300: #333849;
    --c-ink-400: #858DA1;
    --c-ink-500: #9BA3B6;
    --c-ink-600: #B4BBCA;
    --c-ink-700: #CAD0DC;
    --c-ink-800: #DFE4EE;
    --c-ink-900: #EEF1F7;
    --c-ink-950: #FFFFFF;

    --surface:   #14161F;
    --surface-2: #191C26;
    --panel:     #1B1E29;
    --topbar:    rgba(14, 15, 22, .86);
    --line:      #262A38;
    --line-soft: #1F2331;
    --line-mid:  #2E3342;

    /* Re-picked, not reused: a .05 wash of #A93BE0 is invisible on
       near-black, and that same violet as text fails contrast on it. The
       hue lifts to #A78BFA and the ink comes all the way up to #C4B5FD. */
    --tint-1:    rgba(167, 139, 250, .06);
    --tint-2:    rgba(167, 139, 250, .11);
    --tint-3:    rgba(167, 139, 250, .17);
    --tint-line: rgba(167, 139, 250, .26);
    --brand-ink: #C4B5FD;

    --page-wash:
        radial-gradient(1200px 780px at  6%  -8%, rgba(123,  47, 247, .22) 0%, transparent 60%),
        radial-gradient(1000px 700px at 97%   0%, rgba(224,  64, 154, .12) 0%, transparent 56%),
        radial-gradient(1100px 900px at 62% 104%, rgba(251, 122,  60, .08) 0%, transparent 58%);

    --ink-1: #EEF1F7;
    --ink-2: #A6AEC0;
    --ink-3: #868EA1;
    --ink-4: #6E7689;

    /* Status hues lifted off the light-surface steps: #059669 and #E11D48
       both sit too dark on a near-black card to read as "green" and "red"
       at a glance. The tints become low-alpha washes of the same hue. */
    --ok:      #34D399;   --ok-bg:   rgba(52, 211, 153, .13);
    --warn:    #FBBF24;   --warn-bg: rgba(251, 191, 36, .13);
    --bad:     #FB7185;   --bad-bg:  rgba(251, 113, 133, .13);
    --idle:    #7D8598;   --idle-bg: rgba(125, 133, 152, .13);
    --info:    #38BDF8;   --info-bg: rgba(56, 189, 248, .13);

    --viz-grid:    #232733;
    --viz-axis:    #7D8598;
    --viz-surface: #14161F;
    --viz-legend:  #A6AEC0;
}

/* ==================================================================
   UTILITIES RETARGETED ONTO THE THEME

   The master-data screens draw their card edges, section rules and field
   grounds with plain Tailwind neutrals — 37 class lists across nine
   files. Pointing those few utilities at the brand tint here is what
   carries the colour onto every screen at once, and keeps them in step
   afterwards; editing all nine by hand would not.

   The leading `:root` is only there to out-rank the utility itself —
   same declaration, one step more specific.
   ================================================================== */
:root .border-ink-200,
:root .border-ink-300,
:root .border-slate-200,
:root .border-slate-300 { border-color: var(--tint-line); }

/* bg-ink-50 is also on <body>, where a translucent violet would sit over
   nothing in light mode and wash the whole page pale in dark. */
:root .bg-ink-50:not(body),
:root .bg-slate-50 { background-image: linear-gradient(var(--tint-1), var(--tint-1)); }

/* Table heads take the stronger step and an opaque base — a sticky head
   with rows scrolling visibly behind it is worse than a grey one. */
:root thead .bg-ink-50,
:root thead .bg-slate-50 {
    background-color: var(--surface);
    background-image: linear-gradient(var(--tint-2), var(--tint-2));
}

/* Those two blocks tie with the `hover:` and `focus:` variants of the
   same properties and win on source order, which would silently kill the
   focus ring on every field in entry.php and site_select.php. One more
   step of specificity hands the states back — in the brand violet. */
:root .hover\:border-ink-300:hover,
:root .hover\:border-slate-300:hover,
:root .focus\:border-brand-500:focus { border-color: #A93BE0; }
:root .focus\:bg-white:focus {
    background-color: var(--surface);
    background-image: none;
}

/* Utility classes the markup already uses, retargeted for dark. Cheaper
   and far less error-prone than adding a `dark:` twin to 34 call sites. */
:root[data-theme="dark"] .bg-white { background-color: var(--surface); }

/* The pale tint badges — amber/rose/emerald/brand at 50-100 — are near
   white on a dark card and swallow their own text. */
:root[data-theme="dark"] .bg-amber-100   { background-color: rgba(251, 191, 36, .16); }
:root[data-theme="dark"] .text-amber-700 { color: #FCD34D; }
:root[data-theme="dark"] .bg-amber-50    { background-color: rgba(251, 191, 36, .12); }
:root[data-theme="dark"] .text-amber-800 { color: #FCD34D; }
:root[data-theme="dark"] .border-amber-200 { border-color: rgba(251, 191, 36, .28); }
:root[data-theme="dark"] .bg-rose-50     { background-color: rgba(251, 113, 133, .13); }
:root[data-theme="dark"] .text-rose-600,
:root[data-theme="dark"] .text-rose-800  { color: #FDA4AF; }
:root[data-theme="dark"] .border-rose-200 { border-color: rgba(251, 113, 133, .28); }
:root[data-theme="dark"] .bg-emerald-50  { background-color: rgba(52, 211, 153, .13); }
:root[data-theme="dark"] .text-emerald-800 { color: #6EE7B7; }
:root[data-theme="dark"] .border-emerald-200 { border-color: rgba(52, 211, 153, .28); }
:root[data-theme="dark"] .bg-brand-50    { background-color: rgba(129, 140, 248, .14); }
:root[data-theme="dark"] .text-brand-700,
:root[data-theme="dark"] .text-brand-800 { color: #A5B4FC; }
:root[data-theme="dark"] .border-brand-200 { border-color: rgba(129, 140, 248, .30); }

/* bg-ink-900 is a deliberate near-black chip on a white card; inverted it
   becomes a white block with white text on it. */
:root[data-theme="dark"] .bg-ink-900 { background-color: #2B3040; }

/* ==================================================================
   SIGN-IN / OTP / RESET

   One centred card floating over a full-bleed background video. Every
   layer below the card is decoration and every one of them is optional:
   pull the video and the gradient carries the screen on its own, turn
   on "reduce motion" and the whole thing goes still without breaking.
   ================================================================== */

/* Registered so the card's rotating light ring can be animated at all —
   a plain custom property is a string and does not interpolate. Browsers
   without @property simply hold the initial angle, which is a static
   ring rather than a broken one. */
@property --auth-ring {
    syntax: '<angle>';
    initial-value: 0deg;
    inherits: false;
}

.auth-stage {
    position: relative;
    display: flex;
    min-height: 100vh;
    min-height: 100svh;
    isolation: isolate;
    background: #05070F;
}

/* Everything decorative lives in here so it can be clipped without also
   clipping the card. Centring the card with flex would cut off its top
   on a short screen; the shell's `margin:auto` centres and still scrolls. */
.auth-bg {
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
}

/* Any mp4 in assets/video/ is picked up by inc/partials/auth_head.php. */
.auth-video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    animation:
        auth-fade-in 1.6s ease-out both,
        auth-kenburns 44s ease-in-out infinite alternate;
}

@keyframes auth-fade-in { from { opacity: 0; } to { opacity: 1; } }

/* A very slow push-in. Long enough that it reads as depth rather than
   as movement — nobody should notice it while they type a password. */
@keyframes auth-kenburns {
    from { transform: scale(1.03); }
    to   { transform: scale(1.14); }
}

/* Sits under the video and carries the page on its own when there is no
   file yet, so the screen never looks broken or half-finished. */
.auth-fallback {
    position: absolute;
    inset: 0;
    background:
        radial-gradient(1100px 750px at 12% 6%,   rgba(99,102,241,.75) 0%, transparent 60%),
        radial-gradient(950px 700px at 88% 18%,   rgba(56,189,248,.48) 0%, transparent 58%),
        radial-gradient(1000px 850px at 38% 108%, rgba(139,92,246,.62) 0%, transparent 60%),
        radial-gradient(700px 600px at 72% 78%,   rgba(236,72,153,.28) 0%, transparent 58%),
        linear-gradient(140deg, #070B1A 0%, #141B3A 48%, #070B1A 100%);
    animation: auth-drift 22s ease-in-out infinite alternate;
}

@keyframes auth-drift {
    from { transform: scale(1)    translate3d(0, 0, 0); }
    to   { transform: scale(1.08) translate3d(-2%, -2%, 0); }
}

/* Slow coloured lights that drift behind the glass. They are what keeps
   the card edge alive when the video happens to be dark and flat. */
.auth-orb {
    position: absolute;
    border-radius: 9999px;
    filter: blur(90px);
    mix-blend-mode: screen;
    pointer-events: none;
    will-change: transform;
}
.auth-orb-a {
    top: -14%; left: -10%;
    width: 46vw; height: 46vw;
    background: radial-gradient(circle, rgba(99,102,241,.60) 0%, transparent 70%);
    animation: auth-float-a 26s ease-in-out infinite alternate;
}
.auth-orb-b {
    right: -14%; bottom: -18%;
    width: 52vw; height: 52vw;
    background: radial-gradient(circle, rgba(56,189,248,.44) 0%, transparent 70%);
    animation: auth-float-b 32s ease-in-out infinite alternate;
}
.auth-orb-c {
    top: 34%; left: 48%;
    width: 34vw; height: 34vw;
    background: radial-gradient(circle, rgba(167,139,250,.38) 0%, transparent 70%);
    animation: auth-float-c 38s ease-in-out infinite alternate;
}

@keyframes auth-float-a {
    from { transform: translate3d(0, 0, 0)        scale(1); }
    to   { transform: translate3d(8vw, 6vh, 0)    scale(1.18); }
}
@keyframes auth-float-b {
    from { transform: translate3d(0, 0, 0)        scale(1.1); }
    to   { transform: translate3d(-9vw, -7vh, 0)  scale(1); }
}
@keyframes auth-float-c {
    from { transform: translate3d(-4vw, 3vh, 0)   scale(.9); }
    to   { transform: translate3d(5vw, -5vh, 0)   scale(1.2); }
}

/* Darkens whatever is behind so the text keeps its contrast no matter how
   bright the video is, and pools the light around the middle where the
   card now sits. */
.auth-veil {
    position: absolute;
    inset: 0;
    background:
        radial-gradient(120% 90% at 50% 45%, transparent 0%, rgba(3,6,16,.55) 58%, rgba(3,6,16,.88) 100%),
        linear-gradient(180deg, rgba(4,7,18,.74) 0%, rgba(4,7,18,.40) 38%, rgba(4,7,18,.82) 100%);
}

/* A faint engineering grid, faded out at the edges. Reads as "instrument"
   rather than "wallpaper", which is the point on a KPI product. */
.auth-grid {
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(rgba(255,255,255,.055) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255,255,255,.055) 1px, transparent 1px);
    background-size: 64px 64px;
    -webkit-mask-image: radial-gradient(75% 60% at 50% 45%, #000 0%, transparent 78%);
            mask-image: radial-gradient(75% 60% at 50% 45%, #000 0%, transparent 78%);
    animation: auth-grid-pan 60s linear infinite;
}

@keyframes auth-grid-pan {
    from { background-position: 0 0, 0 0; }
    to   { background-position: 64px 64px, 64px 64px; }
}

/* Film grain. Kills the banding that big soft gradients show on cheap
   panels, and takes the plastic sheen off the glass. */
.auth-noise {
    position: absolute;
    inset: 0;
    opacity: .05;
    mix-blend-mode: overlay;
    pointer-events: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---- the column that holds everything ---- */

.auth-shell {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 452px;
    margin: auto;               /* centres AND still scrolls when short */
    padding: 40px 20px 30px;
}

.auth-brand {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-bottom: 26px;
    animation: auth-up .7s cubic-bezier(.16, 1, .3, 1) both;
}

.auth-brand-plate {
    display: block;
    padding: 12px 22px;
    border-radius: 17px;
    background: #fff;
    /* Also the resting state: the keyframes below are the only other
       place this shadow exists, and they are switched off for anyone who
       asked for less motion. */
    box-shadow:
        0 18px 42px -16px rgba(0, 0, 0, .80),
        0 0 0 1px rgba(255, 255, 255, .45),
        0 0 26px -6px rgba(99, 102, 241, .40);
    /* Breathes rather than pulses. A halo on a 3.4s loop next to a
       password field is a distraction; four seconds reads as ambient. */
    animation: auth-plate-glow 4.2s ease-in-out infinite;
}
.auth-brand-plate img {
    display: block;
    height: 30px;
    width: auto;
}

@keyframes auth-plate-glow {
    0%, 100% {
        box-shadow:
            0 18px 42px -16px rgba(0, 0, 0, .80),
            0 0 0 1px rgba(255, 255, 255, .45),
            0 0 26px -6px rgba(99, 102, 241, .40);
    }
    50% {
        box-shadow:
            0 18px 42px -16px rgba(0, 0, 0, .80),
            0 0 0 1px rgba(255, 255, 255, .70),
            0 0 46px 2px rgba(99, 102, 241, .70);
    }
}

.auth-brand-sub {
    margin-top: 13px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .24em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, .45);
}

/* ---- the card ---- */

.auth-card {
    position: relative;
    border-radius: 26px;
    padding: 34px 30px;
    background: rgba(12, 16, 34, .40);
    border: 1px solid rgba(255, 255, 255, .10);
    backdrop-filter: blur(30px) saturate(150%);
    -webkit-backdrop-filter: blur(30px) saturate(150%);
    box-shadow:
        0 44px 100px -34px rgba(0, 0, 0, .88),
        0 2px 10px rgba(0, 0, 0, .22),
        inset 0 1px 0 rgba(255, 255, 255, .10);
    animation: auth-card-in .9s cubic-bezier(.16, 1, .3, 1) both;
}

/* A light travelling around the border. Drawn as a 1px conic ring: the
   two masks knock the middle out so only the padding box is painted. */
.auth-card::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    background: conic-gradient(from var(--auth-ring),
        transparent 0deg,
        rgba(165, 180, 252, .85) 42deg,
        transparent 110deg,
        transparent 205deg,
        rgba(103, 232, 249, .62) 250deg,
        transparent 320deg);
    /* The shorthands MUST come before the composite, or they reset it back
       to `add` and the gradient floods the whole card instead of tracing
       its edge. */
    -webkit-mask:
        linear-gradient(#000 0 0) content-box,
        linear-gradient(#000 0 0);
            mask:
        linear-gradient(#000 0 0) content-box,
        linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
    pointer-events: none;
    animation: auth-ring-spin 9s linear infinite;
}

@keyframes auth-ring-spin { to { --auth-ring: 360deg; } }

@keyframes auth-card-in {
    from { opacity: 0; transform: translateY(26px) scale(.965); filter: blur(6px); }
    to   { opacity: 1; transform: none;                         filter: blur(0); }
}

@keyframes auth-up {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
}

/* The card's own blocks, then the fields inside the form, arrive in
   reading order. Delays are short — this is a sign-in screen, not a
   title sequence, and anyone typing fast must not be made to wait. */
.auth-card > *,
.auth-card form > * {
    animation: auth-up .5s cubic-bezier(.16, 1, .3, 1) both;
}
.auth-card > *:nth-child(1) { animation-delay: .20s; }
.auth-card > *:nth-child(2) { animation-delay: .26s; }
.auth-card > *:nth-child(3) { animation-delay: .32s; }
.auth-card > *:nth-child(4) { animation-delay: .38s; }
.auth-card > *:nth-child(5) { animation-delay: .44s; }
.auth-card > *:nth-child(6) { animation-delay: .50s; }
.auth-card > *:nth-child(n+7) { animation-delay: .56s; }

.auth-card form > *:nth-child(1) { animation-delay: .34s; }
.auth-card form > *:nth-child(2) { animation-delay: .40s; }
.auth-card form > *:nth-child(3) { animation-delay: .46s; }
.auth-card form > *:nth-child(4) { animation-delay: .52s; }
.auth-card form > *:nth-child(n+5) { animation-delay: .58s; }

/* ---- fields ---- */

.auth-field { position: relative; }

/* The leading icon lights up with the field it belongs to. It sits before
   the input in the DOM, so the wrapper is what carries the focus state. */
.auth-field-icon {
    position: absolute;
    left: 15px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 12px;
    color: rgba(255, 255, 255, .30);
    pointer-events: none;
    transition: color .18s, transform .18s;
    z-index: 1;
}
.auth-field:focus-within .auth-field-icon {
    color: var(--brand-300);
    transform: translateY(-50%) scale(1.12);
}

.auth-input {
    width: 100%;
    padding: 13px 16px;
    border-radius: 13px;
    font-size: 14px;
    font-weight: 500;
    color: #fff;
    background: rgba(255, 255, 255, .05);
    border: 1px solid rgba(255, 255, 255, .11);
    outline: none;
    transition: border-color .18s, background .18s, box-shadow .18s;
}
.auth-input::placeholder { color: rgba(255, 255, 255, .26); font-weight: 400; }
.auth-input:hover:not(:focus) { border-color: rgba(255, 255, 255, .20); }
.auth-input:focus {
    border-color: var(--brand-400);
    background: rgba(255, 255, 255, .085);
    box-shadow:
        0 0 0 4px rgba(99, 102, 241, .20),
        0 8px 24px -12px rgba(99, 102, 241, .85);
}
/* Chrome paints its own yellow on remembered fields; this keeps the
   dark card intact. */
.auth-input:-webkit-autofill,
.auth-input:-webkit-autofill:focus {
    -webkit-text-fill-color: #fff;
    -webkit-box-shadow: 0 0 0 1000px #161C33 inset;
    caret-color: #fff;
}

.auth-eye {
    position: absolute;
    right: 11px;
    top: 50%;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    transform: translateY(-50%);
    border-radius: 9px;
    color: rgba(255, 255, 255, .35);
    transition: background .15s, color .15s;
    z-index: 1;
}
.auth-eye:hover { background: rgba(255, 255, 255, .10); color: rgba(255, 255, 255, .75); }

/* ---- primary button ---- */

.auth-btn {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    padding: 14px 18px;
    overflow: hidden;
    border-radius: 13px;
    font-size: 14px;
    font-weight: 700;
    color: #fff;
    background: linear-gradient(135deg, var(--brand-400) 0%, var(--brand-600) 52%, var(--brand-700) 100%);
    box-shadow: 0 12px 30px -10px rgba(79, 70, 229, .80), inset 0 1px 0 rgba(255,255,255,.22);
    transition: transform .14s, box-shadow .18s, filter .18s;
}
.auth-btn:hover {
    filter: brightness(1.10);
    transform: translateY(-1px);
    box-shadow: 0 18px 38px -10px rgba(79, 70, 229, .90), inset 0 1px 0 rgba(255,255,255,.28);
}
.auth-btn:active { transform: translateY(1px); }
.auth-btn > * { position: relative; z-index: 1; }

/* A single sweep of light across the face on hover. */
.auth-btn::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(105deg, transparent 32%, rgba(255,255,255,.38) 48%, transparent 64%);
    transform: translateX(-130%);
}
.auth-btn:hover::after { animation: auth-sheen .85s ease-out; }

@keyframes auth-sheen {
    to { transform: translateX(130%); }
}

/* Set by assets/js/app.js the moment the form is submitted, so nobody
   double-submits a sign-in while the server is emailing a code. */
.auth-btn.is-busy {
    pointer-events: none;
    filter: saturate(.75) brightness(.92);
}
.auth-btn.is-busy .auth-btn-label { opacity: .55; }
.auth-btn.is-busy::before {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: 18px;
    height: 18px;
    margin: -9px 0 0 -9px;
    border-radius: 9999px;
    border: 2px solid rgba(255, 255, 255, .30);
    border-top-color: #fff;
    animation: auth-spin .65s linear infinite;
    z-index: 2;
}

@keyframes auth-spin { to { transform: rotate(360deg); } }

/* ---- alerts ---- */

/* Shared by the error / notice / success strips on every auth screen so
   they announce themselves instead of just appearing. */
.auth-alert { animation: auth-alert-in .45s cubic-bezier(.16, 1, .3, 1) both; }

@keyframes auth-alert-in {
    from { opacity: 0; transform: translateY(-6px) scale(.98); }
    to   { opacity: 1; transform: none; }
}

/* An error is worth one shake. Repeating it would be nagging. */
.auth-alert-error { animation: auth-alert-in .45s cubic-bezier(.16,1,.3,1) both, auth-shake .5s .45s ease-in-out; }

@keyframes auth-shake {
    0%, 100%  { transform: translateX(0); }
    20%, 60%  { transform: translateX(-5px); }
    40%, 80%  { transform: translateX(5px); }
}

/* ---- the strip under the card ---- */

.auth-trust {
    display: flex;
    flex-wrap: wrap;          /* three labels do not fit on a 320px phone */
    justify-content: center;
    gap: 18px 34px;
    margin-top: 26px;
    animation: auth-up .6s .62s cubic-bezier(.16, 1, .3, 1) both;
}
.auth-trust-n {
    font-size: 19px;
    font-weight: 800;
    line-height: 1;
    color: rgba(255, 255, 255, .92);
}
.auth-trust-l {
    margin-top: 5px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .10em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, .38);
}

.auth-copy {
    margin-top: 22px;
    text-align: center;
    font-size: 11px;
    color: rgba(255, 255, 255, .28);
    animation: auth-up .6s .70s cubic-bezier(.16, 1, .3, 1) both;
}

/* ---- OTP ---- */

/* Six boxes that behave like one field. */
.otp-box {
    flex: 1;
    min-width: 0;
    height: 58px;
    border-radius: 13px;
    text-align: center;
    font-size: 24px;
    font-weight: 800;
    color: #fff;
    background: rgba(255, 255, 255, .05);
    border: 1px solid rgba(255, 255, 255, .11);
    outline: none;
    transition: border-color .15s, background .15s, box-shadow .15s, transform .15s;
}
.otp-box:focus {
    border-color: var(--brand-400);
    background: rgba(255, 255, 255, .09);
    box-shadow: 0 0 0 4px rgba(99, 102, 241, .20);
    transform: translateY(-2px);
}
.otp-box.filled {
    border-color: rgba(165, 180, 252, .55);
    background: rgba(99, 102, 241, .16);
    animation: auth-pop .22s ease-out;
}

@keyframes auth-pop {
    0%   { transform: scale(1); }
    45%  { transform: scale(1.09); }
    100% { transform: scale(1); }
}

/* ---- motion off ---- */

@media (prefers-reduced-motion: reduce) {
    /* A looping video is the one thing on this page that cannot be made
       still, so it goes entirely. The static gradient behind it is what
       the screen falls back to, and that is a finished screen. */
    .auth-video { display: none; }

    .auth-fallback,
    .auth-orb,
    .auth-grid,
    .auth-card,
    .auth-card::before,
    .auth-brand,
    .auth-brand-plate,
    .auth-card > *,
    .auth-card form > *,
    .auth-alert,
    .auth-alert-error,
    .auth-trust,
    .auth-copy,
    .otp-box.filled {
        animation: none !important;
    }
    .auth-btn:hover::after { animation: none; }
    /* Still needed — `both` was what made these visible at all. */
    .auth-card,
    .auth-card > *,
    .auth-card form > *,
    .auth-brand,
    .auth-trust,
    .auth-copy,
    .auth-alert { opacity: 1; transform: none; filter: none; }
}

@media (max-width: 480px) {
    .auth-shell { padding: 26px 16px 22px; }
    .auth-card  { padding: 26px 20px; border-radius: 22px; }
    .auth-trust { gap: 14px 22px; }
    .otp-box    { height: 50px; font-size: 20px; border-radius: 11px; }
}

/* ==================================================================
   APP SHELL — sidebar + content column

   Every signed-in page sits in this. inc/header.php opens it and
   inc/footer.php closes it, so no page had to change to get it.
   ================================================================== */

/* The page ground. `fixed` so the blooms stay put while the content
   scrolls over them — attached to the document they streak past on a long
   table and read as a rendering bug. The sign-in screens paint their own
   full-bleed layer over this, so they are untouched. */
body {
    background-image: var(--page-wash);
    background-attachment: fixed;
    background-repeat: no-repeat;
}

.shell { min-height: 100vh; }

.sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 50;
    display: flex;
    flex-direction: column;
    width: 262px;
    /* Tinted from the foot upward, so the user card and the theme toggle
       sit in colour while the logo at the top keeps a clean plate. */
    background:
        linear-gradient(180deg, transparent 40%, var(--tint-1) 100%),
        var(--surface);
    border-right: 1px solid var(--tint-line);
    /* Off-canvas until lg. A transform rather than display, so it slides
       instead of appearing. */
    transform: translateX(-100%);
    transition: transform .26s cubic-bezier(.16, 1, .3, 1);
}
.shell.nav-open .sidebar { transform: none; }

.shell-scrim {
    position: fixed;
    inset: 0;
    z-index: 49;
    background: rgba(8, 8, 12, .45);
    backdrop-filter: blur(2px);
}

.sidebar-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 20px 18px 16px;
}

.sidebar-nav {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 4px 12px 16px;
}

.nav-group {
    margin: 18px 0 7px;
    padding: 0 10px;
    font-size: 10px;
    font-weight: 800;
    letter-spacing: .15em;
    text-transform: uppercase;
    color: var(--ink-4);
}

.nav-item {
    position: relative;
    display: flex;
    align-items: center;
    gap: 11px;
    margin-bottom: 2px;
    padding: 10px 12px;
    border-radius: 11px;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--ink-2);
    transition: background .15s, color .15s;
}
.nav-item i { width: 17px; font-size: 13px; text-align: center; color: var(--ink-4); transition: color .15s; }
/* Hover is the active item's violet, one step weaker — the colour that
   appears under the cursor is the colour that stays when you land, rather
   than a grey that turns purple on arrival. */
.nav-item:hover   { background: var(--tint-2); color: var(--brand-ink); }
.nav-item:hover i { color: var(--brand-ink); }

.nav-item.is-on {
    background: var(--tint-3);
    color: var(--brand-ink);
    font-weight: 700;
}
.nav-item.is-on i { color: var(--brand-ink); }
/* The brand sweep cropped to a 3px marker — the only place it appears in
   the navigation, so it still reads as an accent. */
.nav-item.is-on::before {
    content: '';
    position: absolute;
    left: -12px;
    top: 50%;
    width: 3px;
    height: 20px;
    transform: translateY(-50%);
    border-radius: 0 3px 3px 0;
    background: var(--grad);
}

.sidebar-foot { padding: 12px; border-top: 1px solid var(--line-soft); }

.user-card {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 10px;
    border-radius: 12px;
    background: var(--tint-2);
    border: 1px solid var(--tint-line);
}
.user-avatar {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    flex: none;
    border-radius: 9999px;
    background: var(--grad);
    font-size: 11.5px;
    font-weight: 800;
    color: #fff;
}

.nav-out, .nav-burger, .nav-x {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    flex: none;
    border-radius: 10px;
    color: var(--ink-3);
    font-size: 14px;
    transition: background .15s, color .15s;
}
.nav-out:hover { background: var(--bad-bg); color: var(--bad); }
.nav-burger:hover, .nav-x:hover { background: var(--tint-2); color: var(--brand-ink); }

.topbar {
    position: sticky;
    top: 0;
    z-index: 30;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 13px 16px;
    /* Translucent so the page wash reads through, with a tint of its own
       fading out to the right. */
    background:
        linear-gradient(90deg, var(--tint-2) 0%, transparent 52%),
        var(--topbar);
    backdrop-filter: blur(14px);
    /* Plain grey. The brand sweep ran along here as a 2px rule and it was
       too loud for an edge that sits under every screen — the colour in
       the bar itself is enough. */
    border-bottom: 1px solid var(--line);
}

/* The PNG carries a white plate behind its wordmark. Invisible on a white
   sidebar, a white brick on a dark one — so in dark mode the plate is made
   deliberate, exactly as it is on the sign-in screen. */
:root[data-theme="dark"] .sidebar-head img {
    /* content-box is the whole point. Tailwind's preflight puts
       box-sizing: border-box on everything, so the h-8 in the markup is
       the OUTER height — the 7px of padding added here was being taken
       out of the logo itself, which rendered it at 18px instead of 32
       and made the mark visibly smaller in dark mode than in light. */
    box-sizing: content-box;
    padding: 7px 10px;
    border-radius: 10px;
    background: #fff;
}

@media (min-width: 1024px) {
    .sidebar     { transform: none; }
    .shell-scrim { display: none !important; }
    .shell-main  { margin-left: 262px; }
    .topbar      { padding: 15px 32px; }
}

@media print {
    .sidebar, .topbar, .shell-scrim { display: none !important; }
    .shell-main { margin-left: 0; }
}

/* ==================================================================
   DASHBOARD

   Card-led, the way the reference boards are: a row of stat cards, a
   dark hero panel, a chart panel, then the detail. Typography carries
   the hierarchy — one tight headline per band over a small letterspaced
   eyebrow — and colour is spent only on the accent word and on RAG.
   ================================================================== */

.dash-eyebrow {
    display: block;
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .17em;
    text-transform: uppercase;
    color: var(--ink-4);
}
.dash-eyebrow-brand { color: #7C3AED; }

/* clamp() so a headline stays big on a monitor without overflowing a
   phone — the reference sets these at desktop size only. */
.dash-h1 {
    font-size: clamp(30px, 3.6vw, 44px);
    font-weight: 900;
    line-height: 1.03;
    letter-spacing: -.035em;
    color: var(--ink-1);
}
.dash-h2 {
    font-size: clamp(21px, 2.2vw, 27px);
    font-weight: 900;
    line-height: 1.08;
    letter-spacing: -.03em;
    color: var(--ink-1);
}
.dash-h3 { font-size: 14.5px; font-weight: 800; letter-spacing: -.01em; color: var(--ink-1); }

/* Gradient text needs a painted background clipped to the glyphs. The
   fallback colour matters: without it, a browser that ignores
   background-clip renders transparent text and the word disappears. */
.grad-text {
    color: #A93BE0;
    background: var(--grad);
    -webkit-background-clip: text;
            background-clip: text;
}
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
    .grad-text { color: transparent; }
}

/* Cards keep a clean face — they hold charts and tables, and a tint under
   those costs legibility. What they get is a tinted edge and a violet
   shadow, so they read as cut out of the themed page rather than dropped
   onto it. */
.dash-card {
    border-radius: 16px;
    background: var(--surface);
    border: 1px solid var(--tint-line);
    box-shadow: 0 1px 2px rgba(123, 47, 247, .04),
                0 12px 32px -24px rgba(123, 47, 247, .45);
}
.dash-card-pad { padding: 20px; }

/* ---- the four stat cards --------------------------------------------
   These stay white at rest. They sit directly under the headline and are
   the first thing read on the page, and four tinted blocks in a row is
   what made the top of the dashboard look busy — the colour that belongs
   there is the status dot in each pill, nothing more.
   The theme violet only comes up under the cursor. */
.stat-tile {
    transition: background .18s, border-color .18s, box-shadow .18s, transform .18s;
}
.stat-tile:hover {
    background: linear-gradient(150deg, var(--tint-2) 0%, transparent 72%), var(--surface);
    border-color: var(--tint-line);
    box-shadow: 0 14px 32px -20px rgba(123, 47, 247, .55);
    transform: translateY(-2px);
}

/* Stat card — label, big number, quiet sub, one status pill. */
.tile-label { font-size: 12px; font-weight: 700; color: var(--ink-2); }
.tile-num {
    margin-top: 10px;
    font-size: 38px;
    font-weight: 800;
    line-height: 1;
    letter-spacing: -.035em;
    color: var(--ink-1);
}
.tile-sub { margin-top: 9px; font-size: 12px; color: var(--ink-4); }

.pill {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 5px 11px;
    border-radius: 9999px;
    background: var(--panel);
    font-size: 11.5px;
    font-weight: 700;
    color: var(--ink-2);
    white-space: nowrap;
}
.pill-dot { width: 7px; height: 7px; flex: none; border-radius: 9999px; background: currentColor; }

/* .chip is gone with the three context chips it was written for — the
   site, period and LOB count that used to sit under the dashboard
   headline and only repeated the filters now level with it. .rag-chip,
   further down, is a different thing and is still in use. */

/* The dark hero panel, with the violet bloom in its top-right. */
.feature-card {
    position: relative;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border-radius: 18px;
    padding: 26px;
    background: #08080C;
    color: #fff;
}
/* The card is as tall as the chart panel beside it. Without this the rows
   bunch under the headline and leave a slab of empty black below them. */
.feature-rows { margin-top: auto; padding-top: 24px; }
.feature-card::before {
    content: '';
    position: absolute;
    top: -30%;
    right: -18%;
    width: 62%;
    height: 118%;
    border-radius: 9999px;
    background: radial-gradient(circle, rgba(168, 56, 224, .85) 0%, rgba(124, 58, 237, .30) 42%, transparent 70%);
    filter: blur(28px);
    pointer-events: none;
}
.feature-card > * { position: relative; z-index: 1; }

.feature-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 12px 0;
    border-top: 1px solid rgba(255, 255, 255, .13);
    font-size: 13px;
}
.feature-row-k { font-weight: 700; color: #fff; }
.feature-row-v { font-weight: 600; color: rgba(255, 255, 255, .62); text-align: right; }

/* Segmented control (Daily / Weekly / Monthly). */
.seg {
    display: inline-flex;
    padding: 3px;
    border-radius: 11px;
    border: 1px solid var(--tint-line);
    background: var(--tint-2);
}
.seg a {
    border-radius: 8px;
    padding: 7px 14px;
    font-size: 13px;
    font-weight: 700;
    color: var(--ink-2);
    transition: background .15s, color .15s, box-shadow .15s;
}
.seg a:hover { color: var(--brand-ink); }
/* The chosen grain is the only lit control in the filter row, so it takes
   the solid violet rather than a white chip on a grey track. */
.seg a.is-on {
    background: #A93BE0;
    color: #fff;
    box-shadow: 0 4px 12px -4px rgba(169, 59, 224, .60);
}

.dash-select {
    width: 100%;
    padding: 10px 13px;
    border-radius: 11px;
    border: 1px solid var(--tint-line);
    background: linear-gradient(var(--tint-1), var(--tint-1)), var(--surface);
    font-size: 13.5px;
    font-weight: 600;
    color: var(--ink-1);
    outline: none;
    transition: border-color .15s, box-shadow .15s, background .15s;
}
.dash-select:hover { border-color: #A93BE0; }
.dash-select:focus {
    border-color: #A93BE0;
    background: var(--surface);
    box-shadow: 0 0 0 4px rgba(169, 59, 224, .14);
}

/* The one solid-gradient element in the app. Reserved for the single
   primary action on a screen, so it never has to compete with itself. */
.btn-grad {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 11px 18px;
    border-radius: 12px;
    background: var(--grad);
    font-size: 13px;
    font-weight: 700;
    color: #fff;
    box-shadow: 0 10px 24px -10px rgba(169, 59, 224, .70);
    transition: filter .15s, transform .12s, box-shadow .15s;
}
.btn-grad:hover  { filter: brightness(1.07); box-shadow: 0 14px 30px -10px rgba(169, 59, 224, .85); }
.btn-grad:active { transform: translateY(1px); }

/* KPI card — the whole tile links to that KPI's trend. */
.kpi-card {
    display: block;
    border-radius: 14px;
    background: var(--surface);
    border: 1px solid var(--tint-line);
    padding: 15px;
    transition: border-color .15s, box-shadow .15s, transform .15s, opacity .15s;
}
.kpi-card:hover {
    border-color: var(--tint-line);
    background: linear-gradient(160deg, var(--tint-2) 0%, transparent 62%), var(--surface);
    box-shadow: 0 10px 26px -14px rgba(123, 47, 247, .45);
    transform: translateY(-2px);
}
.kpi-card.is-on {
    border-color: #A93BE0;
    background: linear-gradient(160deg, var(--tint-3) 0%, transparent 66%), var(--surface);
    box-shadow: 0 0 0 3px rgba(169, 59, 224, .11);
}

/* A KPI that reported nothing still has to be listed — its absence is
   itself information — but it must not compete with the ones carrying a
   number, and most periods have far more of these than of those. */
.kpi-card.is-idle { opacity: .55; }
.kpi-card.is-idle:hover { opacity: 1; }

/* A hairline meter. Segments carry a 2px surface gap so two adjacent
   bands never read as one longer band. */
.meter {
    display: flex;
    gap: 2px;
    height: 6px;
    border-radius: 9999px;
    overflow: hidden;
    background: var(--viz-grid);
}
.meter > span { height: 100%; border-radius: 9999px; }

.dash-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    padding: 11px 14px;
    /* Opaque under the tint — a sticky head has rows scrolling behind it,
       and a translucent violet would let them read through. */
    background: linear-gradient(var(--tint-2), var(--tint-2)), var(--surface);
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--brand-ink);
    box-shadow: inset 0 -1px 0 var(--tint-line);
    text-align: left;
}
.dash-table tbody td { padding: 10px 14px; font-size: 13.5px; }
/* Without these the value columns drift to the far right edge and leave
   a dead strip between a KPI and its own number. */
.dash-table th:nth-child(1), .dash-table td:nth-child(1) { width: 24%; }
.dash-table th:nth-child(3), .dash-table td:nth-child(3),
.dash-table th:nth-child(4), .dash-table td:nth-child(4) { width: 7rem; }
.dash-table th:nth-child(5), .dash-table td:nth-child(5) { width: 9rem; }
.dash-table tbody tr:hover { background: var(--tint-1); }
.dash-table tbody tr + tr td { border-top: 1px solid var(--line-soft); }
/* The first row of each LOB gets a heavier rule, so the eye can find
   where one line of business ends and the next begins. */
.dash-table tbody tr.grp td { border-top: 1px solid var(--line-mid); }

@media (max-width: 640px) {
    .dash-card-pad { padding: 16px; }
    .feature-card  { padding: 20px; }
    .tile-num      { font-size: 31px; }
}

/* ==================================================================
   RAG
   The colour is decided in PHP (rag_status in inc/functions.php),
   because it depends on the target that was in force for the period.
   These classes only paint what the server already decided.
   ================================================================== */
.rag           { --c: var(--idle); --bg: var(--idle-bg); }
.rag-green     { --c: var(--ok);   --bg: var(--ok-bg); }
.rag-amber     { --c: var(--warn); --bg: var(--warn-bg); }
.rag-red       { --c: var(--bad);  --bg: var(--bad-bg); }
.rag-neutral   { --c: var(--info); --bg: var(--info-bg); }
/* A number was reported, but nothing says what good looks like. That is a
   missing target, not missing data, and it should not read as a failure. */
.rag-no_target { --c: #7C3AED;     --bg: #F5F3FF; }
:root[data-theme="dark"] .rag-no_target { --c: #C4B5FD; --bg: rgba(167, 139, 250, .14); }

.rag-chip {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .17rem .55rem;
    border-radius: .5rem;
    font-size: .72rem;
    font-weight: 700;
    white-space: nowrap;
    color: var(--c);
    background: var(--bg);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c) 22%, transparent);
}
.rag-dot  { width: .45rem; height: .45rem; border-radius: 9999px; background: var(--c); flex: none; }
.rag-bar  { background: var(--c); }
.rag-edge { box-shadow: inset 3px 0 0 0 var(--c); }

/* ==================================================================
   MASTER-DATA SCREENS
   ================================================================== */
.crud-label {
    display: block;
    margin-bottom: 6px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.crud-input {
    width: 100%;
    padding: 10px 13px;
    border-radius: 11px;
    font-size: 14px;
    font-weight: 500;
    color: var(--ink-1);
    background: linear-gradient(var(--tint-1), var(--tint-1)), var(--surface);
    border: 1px solid var(--tint-line);
    outline: none;
    transition: border-color .15s, background .15s, box-shadow .15s;
}
.crud-input:hover:not(:focus) { border-color: #A93BE0; }
/* Violet, not indigo: the focus ring was the last thing in the app still
   pointing at --brand-500 while everything around it had moved. */
.crud-input:focus {
    border-color: #A93BE0;
    background: var(--surface);
    box-shadow: 0 0 0 4px rgba(169, 59, 224, .14);
}
.crud-input[readonly] { color: var(--ink-2); background: var(--line-soft); }
/* The browser paints its own control faces; without this the date picker
   and select arrows stay light-mode icons on a dark field. */
:root[data-theme="dark"] .crud-input,
:root[data-theme="dark"] .dash-select { color-scheme: dark; }
textarea.crud-input   { min-height: 86px; resize: vertical; }

.crud-check {
    width: 15px;
    height: 15px;
    flex: none;
    accent-color: var(--brand-600);
    cursor: pointer;
}

.crud-btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 10px 18px;
    border-radius: 11px;
    font-size: 13px;
    font-weight: 700;
    color: #fff;
    /* The same sweep as .btn-grad. A save button and a dashboard's primary
       action are the same rank, and two different gradients for one rank
       read as two different products. */
    background: var(--grad);
    box-shadow: 0 8px 20px -6px rgba(169, 59, 224, .60);
    transition: filter .15s, transform .12s;
}
.crud-btn:hover  { filter: brightness(1.08); }
.crud-btn:active { transform: translateY(1px); }

/* The confirm button on a delete dialog. Same shape as .crud-btn, but a
   dialog that asks "delete this user?" must not answer with the same
   button that saves one. Defined after .crud-btn so it wins the tie. */
.crud-btn-danger {
    background: linear-gradient(135deg, #F2567E 0%, #E11D48 100%);
    box-shadow: 0 8px 20px -6px rgba(225, 29, 72, .55);
}

.crud-btn-ghost {
    display: inline-flex;
    align-items: center;
    padding: 10px 16px;
    border-radius: 11px;
    font-size: 13px;
    font-weight: 700;
    color: var(--ink-2);
    background: var(--surface);
    border: 1px solid var(--tint-line);
    transition: background .15s, color .15s;
}
.crud-btn-ghost:hover { background: var(--tint-2); color: var(--brand-ink); }

.crud-link {
    display: inline-grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: 9px;
    color: var(--ink-3);
    transition: background .15s, color .15s;
}
.crud-link:hover        { background: var(--tint-2); color: var(--brand-ink); }
.crud-link-danger:hover { background: var(--bad-bg); color: var(--bad); }

/* ==================================================================
   SWEETALERT2

   Only the chrome is set here. The dialog's buttons are the app's own
   .crud-btn / .crud-btn-ghost — app.js turns SweetAlert's own styling
   off — so they follow the theme, dark mode included, instead of
   carrying a second set of hard-coded colours that would drift.

   Every rule is one step more specific than the library's own, so the
   order the two stylesheets happen to load in cannot matter.
   ================================================================== */
.swal2-container { backdrop-filter: blur(3px); }

.eoc-swal.swal2-popup {
    padding: 26px 24px 22px;
    border-radius: 18px;
    background: var(--surface);
    border: 1px solid var(--tint-line);
    box-shadow: 0 34px 80px -34px rgba(11, 11, 18, .66);
    font-family: inherit;
}
.eoc-swal .swal2-title {
    padding: 0;
    font-size: 19px;
    font-weight: 800;
    letter-spacing: -.02em;
    color: var(--ink-1);
}
.eoc-swal .swal2-html-container {
    margin: 10px 0 0;
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--ink-2);
}
.eoc-swal .swal2-actions { margin-top: 22px; gap: 9px; }
.eoc-swal .swal2-styled  { margin: 0; }

/* The spinner in the submit loader, and the bar under a toast. */
.eoc-swal .swal2-loader { border-color: #A93BE0 transparent #A93BE0 transparent; }
.eoc-swal .swal2-timer-progress-bar { background: var(--grad); }

/* A toast is the same popup at a smaller weight — it sits in the corner
   while you carry on, so it must not read as a dialog. */
.eoc-swal-toast.swal2-popup { padding: 13px 16px; border-radius: 14px; }
.eoc-swal-toast .swal2-title { font-size: 13.5px; font-weight: 700; }

/* ==================================================================
   TABLES
   ================================================================== */
.kpi-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: linear-gradient(var(--tint-2), var(--tint-2)), var(--surface);
    color: var(--brand-ink);
    box-shadow: inset 0 -1px 0 var(--tint-line);
}
.kpi-table tbody tr:hover { background: var(--tint-1); }

/* A column of figures is unreadable unless the digits line up. */
.tabular { font-variant-numeric: tabular-nums; }

/* ==================================================================
   THEME TOGGLE
   ================================================================== */
.theme-toggle {
    display: flex;
    align-items: center;
    gap: 11px;
    width: 100%;
    margin-bottom: 8px;
    padding: 9px 12px;
    border-radius: 11px;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-2);
    transition: background .15s, color .15s;
}
.theme-toggle:hover { background: var(--tint-2); color: var(--brand-ink); }
.theme-toggle i { width: 17px; font-size: 13px; text-align: center; color: var(--ink-4); }

.theme-switch {
    position: relative;
    width: 34px;
    height: 19px;
    flex: none;
    border-radius: 9999px;
    background: var(--line-mid);
    transition: background .2s;
}
.theme-knob {
    position: absolute;
    top: 2.5px;
    left: 2.5px;
    width: 14px;
    height: 14px;
    border-radius: 9999px;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .35);
    transition: transform .2s cubic-bezier(.16, 1, .3, 1);
}
.theme-toggle[aria-pressed="true"] .theme-switch { background: var(--grad); }
.theme-toggle[aria-pressed="true"] .theme-knob   { transform: translateX(15px); }

/* ==================================================================
   DATA TABLE
   Search / sort / paginate, wired up in assets/js/app.js. The controls
   are built from these classes rather than by a plugin, so they theme
   with everything else instead of needing a skin.
   ================================================================== */
.dt-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    padding: 14px 16px;
    background: linear-gradient(90deg, var(--tint-2) 0%, transparent 64%);
    border-bottom: 1px solid var(--tint-line);
}
.dt-search {
    position: relative;
    flex: 1 1 220px;
    max-width: 340px;
}
.dt-search i {
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 11px;
    color: var(--ink-4);
    pointer-events: none;
}
.dt-search input {
    width: 100%;
    padding: 9px 12px 9px 32px;
    border-radius: 10px;
    border: 1px solid var(--tint-line);
    background: var(--surface);
    font-size: 13px;
    font-weight: 500;
    color: var(--ink-1);
    outline: none;
    transition: border-color .15s, box-shadow .15s;
}
.dt-search input::placeholder { color: var(--ink-4); font-weight: 400; }
.dt-search input:focus {
    border-color: #A93BE0;
    box-shadow: 0 0 0 4px rgba(169, 59, 224, .12);
}

.dt-count { font-size: 12.5px; font-weight: 600; color: var(--ink-3); }

.dt-len {
    padding: 8px 10px;
    border-radius: 10px;
    border: 1px solid var(--tint-line);
    background: var(--surface);
    font-size: 12.5px;
    font-weight: 600;
    color: var(--ink-2);
    outline: none;
}

/* A sortable header is a button in all but markup, so it says so. */
.dt-sort { cursor: pointer; user-select: none; white-space: nowrap; }
.dt-sort:hover { color: var(--ink-1); }
.dt-sort::after {
    content: '\f0dc';            /* fa-sort */
    font-family: 'Font Awesome 6 Free';
    font-weight: 900;
    margin-left: 7px;
    font-size: 9px;
    opacity: .30;
}
.dt-sort.asc::after  { content: '\f0de'; opacity: .95; color: #A93BE0; }
.dt-sort.desc::after { content: '\f0dd'; opacity: .95; color: #A93BE0; }

.dt-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 16px;
    background: linear-gradient(90deg, transparent 36%, var(--tint-2) 100%);
    border-top: 1px solid var(--tint-line);
}
.dt-pages { display: flex; flex-wrap: wrap; gap: 4px; }
.dt-page {
    min-width: 32px;
    height: 32px;
    padding: 0 9px;
    border-radius: 9px;
    border: 1px solid transparent;
    font-size: 12.5px;
    font-weight: 700;
    color: var(--ink-2);
    transition: background .15s, color .15s, border-color .15s;
}
.dt-page:hover:not(:disabled) { background: var(--tint-2); color: var(--brand-ink); }
.dt-page:disabled { opacity: .35; cursor: not-allowed; }
/* Solid, matching the selected grain in .seg — the two are the same kind
   of "you are here" and were reading as two different states. */
.dt-page.is-on {
    background: #A93BE0;
    border-color: transparent;
    color: #fff;
    box-shadow: 0 4px 12px -4px rgba(169, 59, 224, .60);
}
.dt-gap { min-width: 20px; text-align: center; color: var(--ink-4); font-weight: 700; }

.dt-empty { padding: 44px 16px; text-align: center; color: var(--ink-3); font-size: 13.5px; }

@media print {
    header, footer, .no-print, .dt-bar, .dt-foot { display: none !important; }
    body { background: #fff; }
    .print-break { break-inside: avoid; }
}
