:root{
  --bg:#f6f7fa; --surface:#ffffff; --border:#e9ebf0; --text:#12151c; --muted:#6b7280;
  --brand:#a855f7; --brand-dark:#7c3aed; --brand-light:#f3e8fd;
  --accent:#6d5ef8; --accent-light:#eeecfe;
  --warn:#f59e0b; --warn-light:#fef3e0;
  --danger:#e5484d; --danger-light:#fdeceb;
  --info:#0d9488; --info-light:#e6f7f5;
  --success:#16a34a; --success-light:#e7f8ed;
  --slate:#64748b; --slate-light:#eef1f5;
  --radius:20px; --radius-sm:12px; --radius-lg:28px;
  --shadow:0 1px 2px rgba(20,24,40,.04), 0 14px 32px -18px rgba(20,24,40,.14);
  --shadow-hover:0 6px 16px rgba(20,24,40,.07), 0 28px 56px -24px rgba(20,24,40,.20);
  /* Bootstrap's own default blue, recoloured to the brand purple — covers focus rings, checked
     switches/checkboxes/radios, links, spinners, and anything else Bootstrap itself drives off
     these tokens. Loaded after bootstrap.min.css on every page, so this wins the cascade. */
  --bs-primary:#a855f7; --bs-primary-rgb:168,85,247;
  --bs-primary-text-emphasis:#7c3aed; --bs-primary-bg-subtle:#f3e8fd; --bs-primary-border-subtle:#a855f7;
  --bs-link-color:#7c3aed; --bs-link-color-rgb:124,58,237;
  --bs-link-hover-color:#a855f7; --bs-link-hover-color-rgb:168,85,247;
  --bs-focus-ring-color:rgba(168,85,247,.25);
}
.btn-primary{--bs-btn-bg:var(--brand);--bs-btn-border-color:var(--brand);--bs-btn-hover-bg:var(--brand-dark);--bs-btn-hover-border-color:var(--brand-dark);--bs-btn-active-bg:var(--brand-dark);--bs-btn-active-border-color:var(--brand-dark);--bs-btn-focus-shadow-rgb:168,85,247}
.btn-outline-primary{--bs-btn-color:var(--brand-dark);--bs-btn-border-color:var(--brand);--bs-btn-hover-bg:var(--brand);--bs-btn-hover-border-color:var(--brand);--bs-btn-active-bg:var(--brand);--bs-btn-active-border-color:var(--brand)}
.text-primary{color:var(--brand)!important}
.bg-primary{background-color:var(--brand)!important}
.border-primary{border-color:var(--brand)!important}
.form-check-input:checked{background-color:var(--brand);border-color:var(--brand)}
.form-check-input:focus{border-color:var(--brand);box-shadow:0 0 0 .25rem var(--brand-light)}
.form-control:focus,.form-select:focus{border-color:var(--brand);box-shadow:0 0 0 .25rem var(--brand-light)}
.accordion-button:not(.collapsed){background-color:var(--brand-light);color:var(--brand-dark);box-shadow:inset 0 -1px 0 var(--border)}
.accordion-button:focus{border-color:var(--brand);box-shadow:0 0 0 .25rem var(--brand-light)}
.list-group-item.active{background-color:var(--brand);border-color:var(--brand)}
.progress-bar{background-color:var(--brand)}
.spinner-border,.spinner-grow{color:var(--brand)}
html{overflow-x:hidden}
body{background:var(--bg);color:var(--text);font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;letter-spacing:-.011em;font-size:.95rem}
/* A faint accent glow in the corner, same idea as the reference dashboard's page background — subtle enough to not fight with card content sitting on top of it. */
body.bg-light{background:radial-gradient(1100px 550px at 100% -8%,rgba(168,85,247,.07),transparent),var(--bg)}
/* ---- pagination — Bootstrap defaults to blue; recolour to the brand green ---- */
.pagination{gap:.25rem}
.page-link{color:var(--brand-dark);border-color:var(--border);border-radius:var(--radius-sm)!important;margin-left:0}
.page-link:hover{color:var(--brand-dark);background:var(--brand-light);border-color:var(--border)}
.page-link:focus{box-shadow:0 0 0 .2rem var(--brand-light)}
.page-item.active .page-link{background:var(--brand);border-color:var(--brand);color:#fff}
.page-item.disabled .page-link{color:var(--muted);background:var(--surface);border-color:var(--border)}
/* Sticky footer: on short pages the footer sits at the bottom of the viewport instead of right after the content with empty space below it. Scoped to body.bg-light (every admin/client portal page) so the auth/login split-screen layout is untouched. */
body.bg-light{min-height:100vh;display:flex;flex-direction:column}
body.bg-light > main{flex:1 0 auto}
/* Room for the fixed left sidebar (.app-topbar) — padding on body rather than a margin on <main>
   itself so Bootstrap's own .container auto-centering still centers correctly within the remaining
   width, and any other in-flow sibling of the nav (e.g. the outdated-storefront-domain banner) gets
   the same offset for free without needing its own rule. Removed on the mobile breakpoint where the
   sidebar itself stops being fixed (see .app-topbar's own mobile override above). */
body.bg-light{padding-left:250px}
@media (max-width:900px){body.bg-light{padding-left:0}}
h1,h2,h3,h4,h5,h6{letter-spacing:-.025em;font-weight:800}
a{color:var(--brand-dark)}

/* ---- left sidebar navigation (desktop) — a fixed dark-green column running the full viewport
   height; main content is offset by its width via the sibling rules under "page shell" below,
   rather than touching every view file's markup. Falls back to the original horizontal top bar
   under the mobile breakpoint at the bottom of this section, so a narrow screen never has to fit a
   fixed 250px rail plus content side by side. ---- */
/* height:100vh alongside top:0/bottom:0 — belt-and-suspenders against the sidebar falling short of
   the actual viewport bottom (seen on some browser/zoom combinations where top+bottom alone left a
   gap of page background visible beneath it); this forces the box's own height explicitly rather
   than relying solely on the two-offset shorthand to compute it. */
.app-topbar{position:fixed;left:0;top:0;bottom:0;height:100vh;width:250px;background:linear-gradient(180deg,#211f36,#15131f 60%,#1a1730);box-shadow:2px 0 24px rgba(20,10,40,.25);z-index:1030;overflow-y:hidden;transition:width .18s ease}
/* No scroll at all, by design — the spacing below is tight enough that the whole sidebar fits a
   normal viewport height outright. overflow-y:auto here previously caused a 1-2px "wobble" scroll
   whenever content landed exactly at (or a hair past) the container's height, which read as the
   sidebar's own content shifting on its own; hidden removes that possibility entirely rather than
   trying to shave the last pixel off. */
.app-topbar>div{display:flex;flex-direction:column;height:100%;padding:1rem .8rem;gap:0}
.app-topbar .brand{color:#fff;font-weight:800;font-size:1.05rem;text-decoration:none;display:flex;align-items:center;gap:.5rem;letter-spacing:-.02em;padding:.25rem .3rem;margin-bottom:.4rem;min-width:0}
.app-topbar .brand .dot{width:9px;height:9px;border-radius:50%;background:#c084fc;box-shadow:0 0 0 4px rgba(192,132,252,.3);flex:none}
.app-topbar .brand .label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.app-topbar .brand img{max-width:100%}
/* Shown only when collapsed (see body.sidebar-collapsed below) — the platform's own small favicon
   mark, or a lettered fallback square if none has been uploaded; either way this is what replaces
   the full wordmark logo, which doesn't fit a 76px icon-only rail. */
.app-topbar .brand-favicon{display:none;width:28px;height:28px;border-radius:8px;flex:none;align-items:center;justify-content:center;background:linear-gradient(135deg,#a855f7,#6d5ef8);color:#fff;font-weight:800;font-size:.85rem;object-fit:contain}

/* ---- collapse toggle — sits just under the logo; collapses the sidebar to an icon-only rail.
   State lives on <body> (sidebar-collapsed) so one class also drives the body padding-left offset
   below, and is persisted to localStorage by the nav's own inline script. ---- */
.app-sidebar-toggle{display:flex;align-items:center;justify-content:center;width:100%;padding:.5rem;margin-top:.5rem;border-radius:10px;background:rgba(255,255,255,.06);border:none;color:rgba(255,255,255,.7);cursor:pointer;transition:background .15s ease,color .15s ease}
.app-sidebar-toggle:hover{background:rgba(255,255,255,.14);color:#fff}
.app-sidebar-toggle svg{transition:transform .18s ease}

/* flex:1 + min-height:0 is what actually makes this region absorb the leftover height inside the
   fixed-height sidebar and scroll on its own — without min-height:0 a flex child never shrinks
   below its content size, so an expanded dropdown group (e.g. Affiliates/Qualiphy) would instead
   grow the whole sidebar and push the toggle button + profile card out of view below it. */
.app-nav-scroll{display:flex;flex-direction:column;gap:.1rem;flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.18) transparent}
.app-nav-scroll::-webkit-scrollbar{width:6px}
.app-nav-scroll::-webkit-scrollbar-thumb{background:rgba(255,255,255,.18);border-radius:3px}
.app-nav-link{display:flex;align-items:center;gap:.7rem;color:rgba(255,255,255,.75);text-decoration:none;font-size:.87rem;font-weight:600;padding:.52rem .7rem;border-radius:10px;transition:background .15s ease,color .15s ease;white-space:nowrap}
.app-nav-link:hover{color:#fff;background:rgba(255,255,255,.08)}
.app-nav-link.active{color:#fff;background:rgba(255,255,255,.14)}
.app-nav-link svg{flex:none}
/* Subscription-required state — cosmetic only (see App\Filters\SubscriptionFilter for the real, server-side enforcement). Greys the item out and shows a small lock icon instead of navigating. */
.app-nav-link.locked,.app-nav-group-toggle.locked{opacity:.45;cursor:not-allowed;pointer-events:none}
.app-nav-link.locked .lock-icon,.app-nav-group-toggle.locked .lock-icon{margin-left:auto;flex:none}

/* ---- nav "Qualiphy" group — an inline expand/collapse (Bootstrap's collapse component, not
   dropdown) since a floating popper menu has nowhere sensible to open from inside a 250px-wide
   fixed column; the sub-links just indent in place instead. ---- */
.app-nav-group-toggle{cursor:pointer}
.app-nav-group-toggle .chev{margin-left:auto;transition:transform .15s ease;flex:none}
.app-nav-group-toggle[aria-expanded="true"] .chev{transform:rotate(180deg)}
/* Scoped to collapses inside the nav (not a blanket .collapse rule — that class is also used for
   unrelated things like accordions) — without this gap, an active sub-item's highlight pill sits
   flush against the group toggle row above it with no breathing room, reading as one merged shape. */
.app-nav-scroll .collapse{padding-top:.3rem}
.app-nav-subitem{display:block;color:rgba(255,255,255,.68);text-decoration:none;font-size:.83rem;font-weight:500;padding:.45rem .7rem .45rem 2.5rem;border-radius:10px}
.app-nav-subitem:hover{color:#fff;background:rgba(255,255,255,.08)}
.app-nav-subitem.active{color:#fff;background:rgba(255,255,255,.14);font-weight:700}

/* ---- header bar — a slim strip above the page content (offset by the sidebar's width the same
   way <main> is, via body's own padding-left, so it needs no per-view-file changes) holding the
   icon buttons (Client Hub / notifications / announcements / support) and, on the client side, the
   rotating notification ticker. A real surface (white + brand-gradient top edge + shadow) rather
   than blending flat into the plain grey page background. Sticky so both stay reachable on a long,
   scrolled page, same as the old horizontal nav used to be. ---- */
.app-header-bar{position:sticky;top:0;z-index:1020;display:flex;justify-content:space-between;align-items:center;gap:1rem;padding:.9rem 2rem;background:var(--surface);border-bottom:1px solid var(--border);box-shadow:var(--shadow)}
.app-header-bar::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--brand),var(--accent))}
.app-icon-btn{position:relative;display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:11px;color:var(--muted);background:var(--bg);border:1px solid var(--border);text-decoration:none;font-size:1.1rem;flex:none;transition:background .15s ease,color .15s ease}
.app-icon-btn:hover{background:var(--brand-light);color:var(--brand-dark)}
.app-icon-btn.active{background:var(--brand-light);color:var(--brand-dark)}
/* Color variants — each header-bar icon gets its own soft tint (same brand/accent/info language as
   .stat-icon elsewhere) instead of one flat grey, so they're easier to tell apart at a glance. */
.app-icon-btn.icon-brand{color:var(--brand-dark);background:var(--brand-light);border-color:transparent}
.app-icon-btn.icon-accent{color:var(--accent);background:var(--accent-light);border-color:transparent}
.app-icon-btn.icon-info{color:var(--info);background:var(--info-light);border-color:transparent}
.app-icon-btn.icon-brand:hover,.app-icon-btn.icon-brand.active{background:var(--brand);color:#fff}
.app-icon-btn.icon-accent:hover,.app-icon-btn.icon-accent.active{background:var(--accent);color:#fff}
.app-icon-btn.icon-info:hover,.app-icon-btn.icon-info.active{background:var(--info);color:#fff}
.app-icon-btn .notif-dot{position:absolute;top:-5px;right:-5px;background:var(--danger,#e5484d);color:#fff;border-radius:999px;font-size:.64rem;font-weight:700;min-width:17px;height:17px;display:inline-flex;align-items:center;justify-content:center;padding:0 .3rem;border:2px solid var(--surface)}

/* Logout — the rightmost item in the header bar on every dashboard, deliberately distinct from
   the brand/accent/info icon tints (a danger tint) since ending your session isn't just another
   nav shortcut. */
.app-logout-btn{display:inline-flex;align-items:center;gap:.4rem;height:38px;padding:0 .9rem;border-radius:11px;background:var(--danger-light);color:var(--danger);border:1px solid transparent;font-size:.82rem;font-weight:700;white-space:nowrap;transition:background .15s ease,color .15s ease}
.app-logout-btn:hover{background:var(--danger);color:#fff}

/* ---- notification ticker — one line, rotating through whatever's actually new (order/message/
   announcement/support), each entry stacked in the same box and swapped via a vertical slide. ---- */
.app-notif-ticker{position:relative;height:24px;overflow:hidden;flex:1 1 auto;min-width:0}
.app-notif-ticker-item{position:absolute;inset:0;display:flex;align-items:center;gap:.4rem;font-size:.85rem;font-weight:600;color:var(--text);text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:0;transform:translateY(100%);transition:transform .5s ease,opacity .4s ease;pointer-events:none}
.app-notif-ticker-item.current{opacity:1;transform:translateY(0);pointer-events:auto}
.app-notif-ticker-item.leaving{opacity:0;transform:translateY(-100%)}
.app-notif-ticker-item .label{color:var(--brand-dark);font-weight:800}
.app-notif-ticker-item:hover{color:var(--brand-dark)}

/* ---- user card + logout, pinned to the bottom of the sidebar via margin-top:auto ---- */
/* Now the way to reach Profile/Settings — pulled out of the main nav list, reachable by clicking
   your own name/avatar instead, the same "account menu lives with the account" pattern most
   dashboards use. */
.app-user-card{margin-top:auto;display:flex;align-items:center;gap:.6rem;padding:.6rem;border-radius:12px;background:rgba(255,255,255,.06);text-decoration:none;transition:background .15s ease}
.app-user-card:hover{background:rgba(255,255,255,.12)}
.app-user-card.active{background:rgba(255,255,255,.16)}
.app-user-avatar{width:34px;height:34px;border-radius:50%;flex:none;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:.8rem;color:#fff;background:linear-gradient(135deg,#a855f7,#6d5ef8)}
.app-user-name{font-size:.82rem;font-weight:700;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.app-user-role{font-size:.72rem;color:rgba(255,255,255,.6)}

/* ---- collapsed state — icon-only rail; toggled by JS adding/removing this class on <body>,
   persisted in localStorage so it survives navigation between pages. ---- */
body.sidebar-collapsed{padding-left:76px}
body.sidebar-collapsed .app-topbar{width:76px}
body.sidebar-collapsed .app-sidebar-toggle svg{transform:rotate(180deg)}
body.sidebar-collapsed .brand .label,
body.sidebar-collapsed .brand .dot,
body.sidebar-collapsed .brand img.brand-logo,
body.sidebar-collapsed .app-nav-link .label,
body.sidebar-collapsed .app-nav-subitem,
body.sidebar-collapsed .chev,
body.sidebar-collapsed .app-user-name,
body.sidebar-collapsed .app-user-role{display:none}
/* The full wordmark logo doesn't fit a 76px rail — swap it for the platform's own small favicon
   mark instead of letting the wordmark image just overflow/clip against the narrow column. */
body.sidebar-collapsed .brand-favicon{display:flex}
body.sidebar-collapsed .brand{justify-content:center}
body.sidebar-collapsed .app-nav-link,
body.sidebar-collapsed .app-nav-group-toggle{justify-content:center;padding-left:0;padding-right:0}
body.sidebar-collapsed .app-user-card{justify-content:center;padding:.5rem}
@media (max-width:900px){body.sidebar-collapsed{padding-left:0}body.sidebar-collapsed .app-topbar{width:100%}}

/* Hard CSS backstop for the nav icon tooltips (see _nav.php's customClass:'app-nav-tooltip') — the
   JS enable()/disable() toggle can lose a hover-vs-toggle timing race the same way the notification
   bell's tooltip once could, so this guarantees one can never render while the sidebar isn't
   collapsed, independent of whatever state the JS side thinks it's in. */
body:not(.sidebar-collapsed) .app-nav-tooltip{display:none !important}

/* ---- mobile fallback: below this width there's no room for a fixed 250px rail alongside content,
   so the sidebar becomes a normal, non-fixed block that flows above the page content instead of
   beside it — still a vertical list (nothing needs to fit side-by-side, so nothing can overflow),
   just taller, with the page scrolling past it like any other in-flow section. ---- */
@media (max-width:900px){
  .app-topbar{position:static;width:100%;height:auto;box-shadow:0 2px 16px rgba(20,10,40,.22)}
  .app-topbar>div{padding:1rem;height:auto}
  .app-user-card{display:none}
  .app-sidebar-toggle{margin-top:.75rem}
  .app-header-bar{flex-wrap:wrap;padding:.9rem 1rem}
}

/* ---- footer ---- */
.app-footer{background:transparent;margin-top:2.5rem;border-top:1px solid var(--border)}
.app-footer-inner{display:flex;align-items:center;gap:1rem;padding:1.35rem 0;flex-wrap:wrap}
.app-footer-brand{display:flex;align-items:center;gap:.5rem;font-weight:800;color:var(--text);flex:1 1 0;min-width:160px;letter-spacing:-.02em;font-size:.9rem}
.app-footer-brand img{max-height:20px}
.app-footer-brand .dot{width:8px;height:8px;border-radius:50%;background:var(--brand);box-shadow:0 0 0 4px var(--brand-light)}
/* Swaps to the light-background-suited logo variant in dark mode — only when both variants were
   actually uploaded (.has-theme-logos), so a client/admin who's only ever set one logo still sees it
   in both themes instead of the footer going blank in dark mode. */
.app-footer-brand .footer-logo-dark-mode{display:none}
[data-theme="dark"] .app-footer-brand.has-theme-logos .footer-logo-light-mode{display:none}
[data-theme="dark"] .app-footer-brand.has-theme-logos .footer-logo-dark-mode{display:inline-block}
.app-footer-copy{flex:1 1 0;text-align:center;font-size:.82rem;color:var(--muted);white-space:nowrap}
.app-footer-email{flex:1 1 0;text-align:right;font-size:.82rem;min-width:160px}
.app-footer-email a{color:var(--text);text-decoration:none;font-weight:600}
.app-footer-email a:hover{color:var(--brand-dark)}
@media (max-width:700px){.app-footer-inner{flex-direction:column;text-align:center}.app-footer-brand,.app-footer-copy,.app-footer-email{text-align:center;justify-content:center}}

/* ---- surfaces ---- */
.card{border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);transition:box-shadow .2s ease,transform .2s ease}
.card.hover-lift:hover{box-shadow:var(--shadow-hover);transform:translateY(-3px)}
/* Scoped to just cards wrapping a stat-card (via :has(), not a blanket rule on .card) so the
   stat-card's own decorative corner blur — which deliberately bleeds past its own inner box — gets
   clipped to the outer card's rounded corner instead of squaring it off, without risking clipping
   something like a dropdown menu nested inside an unrelated .card elsewhere. */
.card:has(>.card-body.stat-card),.card:has(>.stat-card){overflow:hidden}
/* Lets a stat card itself be the <a> (clickable tile through to its detail page) instead of just a div. */
a.card{display:block;color:inherit;text-decoration:none}
a.card:hover{color:inherit}
.card-body{padding:1.6rem}

/* ---- stat cards ---- */
/* No background here on purpose — this sits inside a separate .card wrapper (<a class="card"><div
   class="card-body stat-card">), and painting an opaque background on THIS inner div (a plain
   rectangle, no border-radius of its own) covered up the outer .card's rounded corners entirely,
   making every tile look square despite .card's own border-radius being correct. The outer .card
   already gets a white background from Bootstrap's own .card rule, which IS properly rounded. */
.stat-card{position:relative;display:flex;flex-direction:column;gap:.9rem;overflow:hidden}
.stat-card::after{content:"";position:absolute;top:-18px;right:-18px;width:70px;height:70px;border-radius:50%;background:radial-gradient(circle,color-mix(in srgb,var(--stat-accent,var(--brand)) 35%,transparent),transparent 70%);pointer-events:none}
.stat-card-top{display:flex;align-items:center;justify-content:space-between;gap:.75rem}
.stat-icon{width:40px;height:40px;border-radius:11px;display:flex;align-items:center;justify-content:center;flex:none}
.stat-icon.brand{background:var(--brand-light);color:var(--brand-dark);--stat-accent:var(--brand)}
.stat-icon.accent{background:var(--accent-light);color:var(--accent);--stat-accent:var(--accent)}
.stat-icon.warn{background:var(--warn-light);color:var(--warn);--stat-accent:var(--warn)}
.stat-icon.info{background:var(--info-light);color:var(--info);--stat-accent:var(--info)}
.stat-icon.danger{background:var(--danger-light);color:var(--danger);--stat-accent:var(--danger)}
.stat-label{color:var(--muted);font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.06em}
.stat-value{font-size:2.1rem;font-weight:800;line-height:1.1;letter-spacing:-.03em}
.stat-sub{color:var(--muted);font-size:.8rem;margin-top:.1rem;display:flex;align-items:center;gap:.35rem}
.stat-trend{font-weight:700;padding:.1rem .45rem;border-radius:999px;font-size:.74rem}
.stat-trend.up{background:var(--brand-light);color:var(--brand-dark)}
.stat-trend.down{background:var(--danger-light);color:#a3282c}

/* ---- Client Hub category filter cards ---- */
.hub-cat-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:.75rem;padding:1.1rem}
.hub-cat-card{position:relative;display:flex;align-items:center;gap:.7rem;background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:.85rem 1rem;text-decoration:none;color:inherit;transition:border-color .15s ease,box-shadow .15s ease,transform .15s ease}
.hub-cat-card:hover{box-shadow:var(--shadow-hover);transform:translateY(-2px);color:inherit}
.hub-cat-card.active{border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-light)}
.hub-cat-card.active::after{content:"";position:absolute;left:50%;bottom:-1px;transform:translateX(-50%);width:34px;height:3px;border-radius:3px 3px 0 0;background:var(--brand)}
.hub-cat-icon{position:relative;width:44px;height:44px;border-radius:13px;display:flex;align-items:center;justify-content:center;flex:none}
.hub-cat-icon .notif-dot{position:absolute;top:-4px;right:-4px;background:var(--danger);color:#fff;border-radius:999px;font-size:.62rem;font-weight:700;min-width:15px;height:15px;display:inline-flex;align-items:center;justify-content:center;padding:0 .25rem;border:2px solid var(--surface)}
.hub-cat-icon.brand{background:var(--brand-light);color:var(--brand-dark)}
.hub-cat-icon.accent{background:var(--accent-light);color:var(--accent)}
.hub-cat-icon.success{background:var(--success-light);color:var(--success)}
.hub-cat-icon.warn{background:var(--warn-light);color:var(--warn)}
.hub-cat-icon.info{background:var(--info-light);color:var(--info)}
.hub-cat-icon.danger{background:var(--danger-light);color:var(--danger)}
.hub-cat-icon.slate{background:var(--slate-light);color:var(--slate)}
.hub-cat-body{flex:1 1 auto;min-width:0;display:flex;flex-direction:column}
.hub-cat-label{font-weight:700;font-size:.86rem;line-height:1.2}
.hub-cat-count{flex:none;min-width:26px;height:26px;padding:0 .4rem;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-weight:800;font-size:.78rem}
.hub-cat-count.brand{background:var(--brand-light);color:var(--brand-dark)}
.hub-cat-count.accent{background:var(--accent-light);color:var(--accent)}
.hub-cat-count.success{background:var(--success-light);color:var(--success)}
.hub-cat-count.warn{background:var(--warn-light);color:var(--warn)}
.hub-cat-count.info{background:var(--info-light);color:var(--info)}
.hub-cat-count.danger{background:var(--danger-light);color:var(--danger)}
.hub-cat-count.slate{background:var(--slate-light);color:var(--slate)}

/* ---- image attachment lightbox controls ---- */
.app-lightbox-close{position:absolute;top:-14px;right:-14px;width:38px;height:38px;border-radius:50%;background:#fff;color:var(--brand-dark);border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 4px 14px rgba(0,0,0,.3);z-index:1}
.app-lightbox-close:hover{background:var(--brand-light)}
.app-lightbox-download{display:inline-flex;align-items:center;gap:.4rem;background:var(--brand);color:#fff;font-weight:700;font-size:.85rem;padding:.55rem 1.15rem;border-radius:999px;text-decoration:none;box-shadow:0 4px 14px rgba(0,0,0,.3)}
.app-lightbox-download:hover{background:var(--brand-dark);color:#fff}

/* ---- chart panels ---- */
.chart-card .card-body{padding:1.75rem}
.chart-title{font-weight:800;font-size:1.05rem;margin-bottom:.2rem;letter-spacing:-.02em}
.chart-sub{color:var(--muted);font-size:.83rem;margin-bottom:1.35rem}
.chart-canvas-wrap{position:relative;height:280px}
.chart-canvas-wrap.sm{height:220px}
.chart-empty{display:flex;align-items:center;justify-content:center;height:100%;color:var(--muted);font-size:.85rem}

/* ---- tables: a tinted header row + borderless body rows separated only by hover state reads as
   a considered data list rather than a spreadsheet. Cards that hold nothing but a table should
   wrap it in a .card-body (not bleed the table to the card's rounded edge) — see .data-card. ---- */
.table{--bs-table-bg:transparent;margin-bottom:0}
.data-card .card-body{padding:0}
.table thead th{background:var(--bg);font-size:.7rem;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);font-weight:700;border:none;padding:1rem 1.6rem;white-space:nowrap}
.table thead tr:first-child th:first-child{border-top-left-radius:var(--radius)}
.table thead tr:first-child th:last-child{border-top-right-radius:var(--radius)}
.table td{padding:1.05rem 1.6rem;vertical-align:middle;border-top:1px solid var(--border);border-bottom:none}
.table tbody tr:first-child td{border-top:none}
.table-hover tbody tr{transition:background .12s ease}
.table-hover tbody tr:hover{background:#f3f5f8}
.table-sm td,.table-sm th{padding-left:1rem;padding-right:1rem}

/* ---- badges ---- */
.badge-soft{display:inline-flex;align-items:center;gap:.4rem;padding:.35rem .7rem;border-radius:999px;font-size:.73rem;font-weight:700;white-space:nowrap}
.badge-soft::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.7}
.badge-soft.brand{background:var(--brand-light);color:var(--brand-dark)}
.badge-soft.warn{background:var(--warn-light);color:#92660a}
.badge-soft.danger{background:var(--danger-light);color:#a3282c}
.badge-soft.info{background:var(--info-light);color:var(--info)}
.badge-soft.muted{background:#eef0f4;color:var(--muted)}
.badge-soft.muted::before{display:none}

/* ---- buttons ---- */
.btn{border-radius:999px;font-weight:700;letter-spacing:-.008em;white-space:nowrap;padding:.55rem 1.15rem;transition:background .15s ease,border-color .15s ease,box-shadow .15s ease,transform .15s ease}
.btn-sm{padding:.4rem .9rem}
.btn-success{background:var(--brand);border-color:var(--brand);box-shadow:0 2px 10px -2px rgba(168,85,247,.45)}
.btn-success:hover{background:var(--brand-dark);border-color:var(--brand-dark);box-shadow:0 4px 16px -2px rgba(168,85,247,.5);transform:translateY(-1px)}
.btn-outline-success{color:var(--brand-dark);border-color:var(--brand)}
.btn-outline-success:hover{background:var(--brand);border-color:var(--brand)}
.btn-outline-secondary{border-color:var(--border);color:var(--text)}
.btn-outline-secondary:hover{background:var(--text);border-color:var(--text);color:#fff}

/* ---- floating action button ---- */
.btn-float{position:fixed;bottom:1.5rem;right:1.5rem;z-index:1035;box-shadow:0 6px 20px rgba(20,24,40,.22);padding:.7rem 1.5rem}

/* ---- auth (login) screens ---- */
.auth-body{background:var(--bg)}
.auth-shell{min-height:100vh;display:flex}
.auth-hero{display:none;flex:1;background:linear-gradient(135deg,#211f36,#17152b 55%,#1a1730);color:#fff;align-items:center;justify-content:center;padding:3rem;position:relative;overflow:hidden}
.auth-hero::before{content:"";position:absolute;width:520px;height:520px;border-radius:50%;background:radial-gradient(circle,rgba(168,85,247,.22),transparent 70%);top:-140px;right:-160px}
.auth-hero::after{content:"";position:absolute;width:380px;height:380px;border-radius:50%;background:radial-gradient(circle,rgba(255,255,255,.08),transparent 70%);bottom:-140px;left:-110px}
.auth-hero-inner{position:relative;max-width:420px}
.auth-hero-logo{max-height:44px;margin-bottom:2.25rem}
.auth-hero-brand{display:flex;align-items:center;gap:.5rem;font-weight:800;font-size:1.2rem;margin-bottom:2.25rem}
.auth-hero-brand .dot{width:10px;height:10px;border-radius:50%;background:#c084fc;box-shadow:0 0 0 4px rgba(192,132,252,.3)}
.auth-hero h1{font-size:2.1rem;margin-bottom:.75rem}
.auth-hero p{color:rgba(255,255,255,.78);font-size:1.03rem;line-height:1.55;margin-bottom:0}
.auth-panel{flex:1;display:flex;align-items:center;justify-content:center;padding:2rem}
.auth-card{width:100%;max-width:380px}
.auth-card-logo{max-height:40px;margin-bottom:1.75rem}
.auth-card h2{font-size:1.5rem;margin-bottom:.25rem}
.auth-sub{color:var(--muted);margin-bottom:1.75rem}
.auth-alert{background:var(--danger-light);color:#a3282c;padding:.75rem 1rem;border-radius:var(--radius-sm);font-size:.88rem;margin-bottom:1.25rem}
.auth-alert-success{background:var(--brand-light);color:var(--brand-dark)}
.auth-form label{display:block;font-weight:600;font-size:.85rem;margin-bottom:1.15rem;color:var(--text)}
.auth-form input{display:block;width:100%;margin-top:.4rem;padding:.7rem .9rem;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:.95rem;font-family:inherit;background:#fff;transition:border-color .15s ease,box-shadow .15s ease}
.auth-form input:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-light)}
.auth-submit{display:block;width:100%;background:var(--brand);color:#fff;border:none;border-radius:999px;padding:.8rem;font-weight:700;font-size:.98rem;cursor:pointer;transition:background .15s ease}
.auth-submit:hover{background:var(--brand-dark)}
@media (min-width:992px){.auth-hero{display:flex}}

/* ---- page shell ---- */
main.container,main.container-fluid{max-width:1360px;padding-top:2.25rem!important;padding-bottom:3rem!important;padding-left:10px!important;padding-right:10px!important}
.page-head{margin-bottom:2.25rem;display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;flex-wrap:wrap}
.page-head h1{font-size:1.9rem;margin-bottom:.3rem;letter-spacing:-.035em}
.page-head p{color:var(--muted);margin-bottom:0;font-size:.95rem}
.page-head-actions{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}

/* ---- action bar: for headers with several possible actions — 1-2 stay as visible buttons,
   the rest collapse into a "More" dropdown so every action isn't given equal visual weight ---- */
.action-bar{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}
.action-bar .dropdown-toggle::after{margin-left:.5rem}
.action-bar .dropdown-menu{border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-hover);padding:.4rem}
.action-bar .dropdown-item{border-radius:8px;padding:.5rem .75rem;font-size:.9rem;font-weight:500}
.action-bar .dropdown-item:hover{background:var(--brand-light);color:var(--brand-dark)}
.action-bar .dropdown-item.text-danger:hover{background:var(--danger-light);color:#a3282c}

/* ---- empty states: replaces a bare line of muted text wherever a list/table can be empty ---- */
.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:3rem 1.5rem}
.empty-state-icon{width:56px;height:56px;border-radius:50%;background:var(--brand-light);color:var(--brand-dark);display:flex;align-items:center;justify-content:center;margin-bottom:1.1rem}
.empty-state h3{font-size:1.05rem;margin-bottom:.35rem}
.empty-state p{color:var(--muted);font-size:.9rem;max-width:360px;margin-bottom:0}
.empty-state .btn{margin-top:1.25rem}

/* ---- avatars: for the first column of people-tables (customers, affiliates, orders) ---- */
.avatar{width:38px;height:38px;border-radius:50%;background:var(--brand-light);color:var(--brand-dark);display:inline-flex;align-items:center;justify-content:center;font-weight:700;font-size:.85rem;flex:none}
.avatar-sm{width:30px;height:30px;font-size:.72rem}
.name-cell{display:flex;align-items:center;gap:.7rem}

/* ---- refined form fields: consistent focus ring + calmer borders everywhere ---- */
.form-control,.form-select{border-color:var(--border);border-radius:var(--radius-sm)}
.form-control:focus,.form-select:focus{border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-light)}
.form-check-input:checked{background-color:var(--brand);border-color:var(--brand)}
.form-check-input:focus{border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-light)}
.form-switch{padding-left:2.75em}
.form-switch .form-check-input{width:2.75em;height:1.5em;margin-top:0;margin-right:.6rem;background-color:#e4e6ec;border:none;box-shadow:inset 0 1px 2px rgba(20,24,40,.10);cursor:pointer;transition:background-color .18s ease,box-shadow .18s ease}
.form-switch .form-check-input:hover{background-color:#d7dae2}
.form-switch .form-check-input:checked{background-color:var(--brand);box-shadow:0 3px 10px -2px rgba(168,85,247,.5)}
.form-switch .form-check-input:checked:hover{background-color:var(--brand-dark)}
.form-switch .form-check-input:focus{box-shadow:inset 0 1px 2px rgba(20,24,40,.10),0 0 0 3px var(--brand-light)}
.form-switch .form-check-label{cursor:pointer;padding-top:.15em}

/* ---- category sidebar (nav-pills, used only by the products-by-category picker) — black text,
   never Bootstrap's default blue, and the active pill is brand green, not Bootstrap's default blue ---- */
.nav-pills .nav-link{color:var(--text)}
.nav-pills .nav-link:hover{color:var(--text);background-color:var(--bg)}
.nav-pills .nav-link.active{background-color:var(--brand);color:#fff}

/* ---- inline data-grid inputs: for fields embedded directly in a table row (pricing grids,
   variation editors) — calmer than a full bordered form-control so the row reads as a refined
   grid instead of a raw form dropped into a table ---- */
.table input.form-control-plain,.table textarea.form-control-plain{border:1px solid transparent;background:transparent;padding:.4rem .55rem;border-radius:8px;transition:border-color .15s ease,background .15s ease}
.table input.form-control-plain:hover,.table textarea.form-control-plain:hover{background:var(--bg)}
.table input.form-control-plain:focus,.table textarea.form-control-plain:focus{background:#fff;border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-light);outline:none}

/* ---- pill-style tabs, matching the badge/button rounded language used everywhere else ---- */
.nav-tabs{border-bottom:1px solid var(--border);gap:.35rem;flex-wrap:wrap}
.nav-tabs .nav-link{border:none;border-radius:999px;color:var(--muted);font-weight:600;font-size:.88rem;padding:.5rem 1rem}
.nav-tabs .nav-link:hover{background:var(--bg);color:var(--text)}
.nav-tabs .nav-link.active{background:var(--brand-light);color:var(--brand-dark)}

/* ---- image thumbnails: bordered, rounded frame for product/asset images in tables ---- */
.thumb{border:1px solid var(--border);border-radius:var(--radius-sm);padding:4px;background:#fff;display:inline-block;line-height:0}
.thumb img{display:block;border-radius:6px;object-fit:contain}

/* ---- Client Hub / Support Center: two-panel messaging shell shared by admin and client ---- */
.hub-shell{display:flex;gap:1.25rem;align-items:flex-start}
.hub-sidebar{width:320px;flex:none;background:#fff;border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden;display:flex;flex-direction:column;max-height:calc(100vh - 140px)}
.hub-sidebar-search{padding:.9rem;border-bottom:1px solid var(--border)}
.hub-announce-btn{display:flex;align-items:center;gap:.5rem;margin:.9rem .9rem 0;padding:.65rem .9rem;border-radius:var(--radius-sm);background:var(--brand-light);color:var(--brand-dark);font-weight:700;text-decoration:none;font-size:.88rem}
.hub-announce-btn:hover{background:var(--brand);color:#fff}
.hub-client-list{overflow-y:auto;flex:1 1 auto}
.hub-client-item{display:flex;gap:.7rem;align-items:flex-start;padding:.85rem .9rem;border-bottom:1px solid var(--border);text-decoration:none;color:inherit;transition:background .12s ease}
.hub-client-item:hover{background:var(--bg)}
.hub-client-item.active{background:var(--brand-light)}
.hub-client-item .name{font-weight:700;font-size:.9rem;color:var(--text)}
.hub-client-item .preview{font-size:.8rem;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:190px}
.hub-client-item .meta{font-size:.72rem;color:var(--muted);margin-top:.15rem}
.hub-client-item .unread-dot{margin-left:auto;background:var(--danger);color:#fff;border-radius:999px;font-size:.68rem;font-weight:700;min-width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;padding:0 .4rem;flex:none}
.hub-panel{flex:1 1 auto;min-width:0;background:#fff;border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);min-height:calc(100vh - 140px);display:flex;flex-direction:column}

.chat-thread{flex:1 1 auto;overflow-y:auto;padding:1.5rem;display:flex;flex-direction:column;gap:1rem;max-height:calc(100vh - 340px)}
.chat-date-sep{text-align:center;color:var(--muted);font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;margin:.5rem 0}
.chat-bubble{max-width:72%;padding:.75rem 1rem;border-radius:16px;font-size:.9rem;line-height:1.45}
.chat-bubble.from-admin{align-self:flex-end;background:var(--brand);color:#fff;border-bottom-right-radius:4px}
.chat-bubble.from-client{align-self:flex-start;background:var(--bg);color:var(--text);border-bottom-left-radius:4px}
.chat-bubble .meta{font-size:.72rem;opacity:.75;margin-top:.35rem}
.chat-bubble .attachments{margin-top:.5rem;display:flex;flex-direction:column;gap:.3rem}
.chat-bubble .attachments a{color:inherit;text-decoration:underline;font-size:.82rem}
.chat-composer{border-top:1px solid var(--border);padding:1rem 1.5rem}
.chat-empty{flex:1 1 auto;display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:.9rem}

/* A fixed 320px sidebar next to a flexible panel doesn't fit a narrow viewport (now further
   narrowed by the app's own 250px sidebar) — stacks vertically instead of overflowing sideways. */
@media (max-width:1100px){
  .hub-shell{flex-direction:column}
  .hub-sidebar{width:100%;max-height:280px}
  .hub-panel{min-height:60vh}
}

/* ==================================================================================================
   DARK MODE — toggled by adding data-theme="dark" to <html> (persisted in localStorage, applied
   synchronously before paint by the nav's own inline script, same flash-prevention pattern already
   used for the sidebar-collapsed state). Almost every surface/text/border color in this file already
   reads off the --bg/--surface/--border/--text/--muted/etc. variables below, so redefining them here
   re-themes most of the app for free; the rest of this section is the handful of places that use a
   hardcoded color (Bootstrap's own defaults, or a literal #fff) instead of one of those variables.
   The sidebar/header gradients are already dark in both themes and are deliberately left alone. ---- */
:root[data-theme="dark"]{
  /* --muted is deliberately pure white here, not a dimmed grey — every "secondary/helper text"
     class in this file (.stat-sub, .chart-sub, .page-head p, .empty-state p, table headers, and
     Bootstrap's own .text-secondary/.text-muted via --bs-secondary-color below) reads off this one
     variable, so this single change makes every subtext/helper-text string pure white in dark mode
     without needing a per-class override. Sacrifices the usual visual hierarchy between primary and
     secondary text on purpose — readability was explicitly asked for over subtlety here. */
  --bg:#0f1117; --surface:#1a1d26; --border:#2d313d; --text:#f1f2f5; --muted:#ffffff;
  /* --brand stays the same rich, saturated purple as light mode — it's the solid fill color for
     buttons/active-pills (paired with white text on top), and needs to stay strong regardless of
     theme. --brand-dark switches role here: in light mode it's a darker purple for text sitting on
     a white/tinted background; in dark mode that same "must contrast with the surrounding surface"
     job needs a LIGHTER purple instead (used for links, badge/icon text sitting on --brand-light's
     now-dark tint, page-link text, etc.) — reusing the light-mode value here made buttons go pale
     with low-contrast text, since both --brand and --brand-dark ended up light at the same time. */
  --brand:#a855f7; --brand-dark:#d8b4fe; --brand-light:#2f2440;
  --accent:#a89bfb; --accent-light:#272149;
  --warn:#fbbf24; --warn-light:#3a2e10;
  --danger:#f87171; --danger-light:#3d1e1f;
  --info:#2dd4bf; --info-light:#102e2b;
  --success:#4ade80; --success-light:#123420;
  --slate:#9aa1b0; --slate-light:#242832;
  --shadow:0 1px 2px rgba(0,0,0,.45),0 14px 32px -18px rgba(0,0,0,.65);
  --shadow-hover:0 6px 16px rgba(0,0,0,.55),0 28px 56px -24px rgba(0,0,0,.75);
  --bs-primary-bg-subtle:#2f2440; --bs-primary-text-emphasis:#d8b4fe; --bs-primary-border-subtle:#c084fc;
  /* Bootstrap's own core tokens — tables, list-groups, and the .bg-light utility read these
     directly instead of our own --text/--bg/--border, so redefining only our own app variables
     above silently left those components stuck in light-mode colors. This is what was causing
     dark-on-dark unreadable table text (Bootstrap's .table sets color from --bs-body-color, not
     --text) and a page background that refused to go black (.bg-light's background-color is
     !important and driven by --bs-light-rgb, which was never touched before). */
  --bs-body-color:var(--text); --bs-body-color-rgb:241,242,245;
  --bs-body-bg:var(--bg); --bs-body-bg-rgb:15,17,23;
  --bs-emphasis-color:var(--text); --bs-emphasis-color-rgb:241,242,245;
  --bs-secondary-color:var(--muted); --bs-secondary-color-rgb:154,161,176;
  --bs-secondary-bg:var(--surface); --bs-secondary-bg-rgb:26,29,38;
  --bs-tertiary-color:var(--muted); --bs-tertiary-bg:var(--surface);
  --bs-border-color:var(--border); --bs-border-color-translucent:var(--border);
  --bs-light:var(--surface); --bs-light-rgb:26,29,38;
  --bs-dark:var(--text); --bs-dark-rgb:241,242,245;
  --bs-heading-color:var(--text);
  color-scheme:dark;
}
[data-theme="dark"] .table{color:var(--text)}
/* .bg-white is a hardcoded literal-white Bootstrap utility (not driven by any --bs-* variable,
   unlike .bg-light) — used as a whole-form-card background on a few pages (new client, new
   announcement, new support ticket). Needs its own !important override to beat Bootstrap's own. */
[data-theme="dark"] .bg-white{background-color:var(--surface)!important}
/* The Quill rich-text editor (announcement message body) hardcodes background:#fff inline on
   #editor, which no class-based override can beat without !important — left alone, dark mode's
   global white text then sits on that same still-white background, invisible. Also covers the
   toolbar/border, which Quill's own quill.snow.css otherwise renders light-themed unconditionally. */
[data-theme="dark"] #editor{background:var(--surface)!important;color:var(--text)}
[data-theme="dark"] .ql-toolbar.ql-snow{background:var(--surface);border-color:var(--border)}
[data-theme="dark"] .ql-toolbar.ql-snow .ql-stroke{stroke:var(--text)}
[data-theme="dark"] .ql-toolbar.ql-snow .ql-fill{fill:var(--text)}
[data-theme="dark"] .ql-toolbar.ql-snow .ql-picker-label{color:var(--text)}
[data-theme="dark"] .ql-toolbar.ql-snow .ql-picker-options{background:var(--surface);color:var(--text)}
[data-theme="dark"] .ql-container.ql-snow{border-color:var(--border)}
[data-theme="dark"] .ql-editor.ql-blank::before{color:var(--muted)}
/* .btn-success:hover normally darkens via --brand-dark, but that variable now means "light purple
   for text on a dark surface" (see the root override above) — reusing it here would make the
   button paler on hover with the same low-contrast white-text problem this whole fix started from.
   Darken instead, same direction as the original light-mode hover. */
[data-theme="dark"] .btn-success:hover{background:color-mix(in srgb, var(--brand) 75%, black);border-color:color-mix(in srgb, var(--brand) 75%, black)}
[data-theme="dark"] body.bg-light{background:radial-gradient(1100px 550px at 100% -8%,rgba(192,132,252,.10),transparent),var(--bg)}
[data-theme="dark"] .card{background:var(--surface)}
[data-theme="dark"] .thumb{background:var(--surface)}
[data-theme="dark"] .hub-sidebar,[data-theme="dark"] .hub-panel{background:var(--surface)}
[data-theme="dark"] .table-hover tbody tr:hover{background:rgba(255,255,255,.045)}
[data-theme="dark"] .badge-soft.muted{background:#262a34;color:var(--muted)}
[data-theme="dark"] .chat-bubble.from-client{background:#242833}
[data-theme="dark"] .form-control,[data-theme="dark"] .form-select{background:var(--surface);color:var(--text);border-color:var(--border)}
[data-theme="dark"] .form-control::placeholder{color:var(--muted);opacity:1}
[data-theme="dark"] .form-control:disabled,[data-theme="dark"] .form-select:disabled,[data-theme="dark"] .form-control[readonly]{background:#15171e;color:var(--muted)}
[data-theme="dark"] .form-control:focus,[data-theme="dark"] .form-select:focus{background:var(--surface);color:var(--text)}
[data-theme="dark"] .input-group-text{background:var(--bg);border-color:var(--border);color:var(--muted)}
/* :not(:checked) is deliberate, not decorative — without it this has the exact same specificity as
   the light-mode :checked rule above (attribute+2 classes vs. 3 classes) and, being later in the
   file, would win and paint every ON toggle the same dark grey as the OFF state instead of purple. */
/* The light-mode version relies on an inset dark shadow for depth (rgba(20,24,40,.10)) — that shadow
   does nothing against an already-dark page, and the previous fill color was too close in luminance
   to the surrounding card/page background, so the OFF track all but disappeared. Lighter fill plus a
   faint light ring (standing in for the now-useless dark inset shadow) keeps it clearly visible
   against any dark surface it sits on. */
[data-theme="dark"] .form-switch .form-check-input:not(:checked){background-color:#4b5163;box-shadow:0 0 0 1px rgba(255,255,255,.10)}
[data-theme="dark"] .form-switch .form-check-input:not(:checked):hover{background-color:#5a6178;box-shadow:0 0 0 1px rgba(255,255,255,.16)}
/* :not(:checked), same reasoning as the form-switch rule above — otherwise this has equal
   specificity to the light-mode .form-check-input:checked rule and, being later, would win and
   paint every checked checkbox the same dark surface color as unchecked instead of purple. */
/* border-color:var(--border) alone was too close to the card's own --surface background (the
   checkbox sits directly on a card, so a 1px border barely darker than that same background nearly
   disappeared) — same fix as the toggle switch above: a visibly lighter border. */
[data-theme="dark"] .form-check-input:not(:checked){background-color:var(--surface);border-color:#4b5163}
[data-theme="dark"] .auth-form input{background:var(--surface);color:var(--text);border-color:var(--border)}
[data-theme="dark"] .dropdown-menu{background:var(--surface);border-color:var(--border);color:var(--text);box-shadow:var(--shadow-hover)}
[data-theme="dark"] .dropdown-item{color:var(--text)}
[data-theme="dark"] .dropdown-item:hover,[data-theme="dark"] .dropdown-item:focus{background:rgba(255,255,255,.06);color:var(--text)}
[data-theme="dark"] .dropdown-item.text-danger:hover{background:var(--danger-light)}
[data-theme="dark"] .modal-content{background:var(--surface);color:var(--text)}
[data-theme="dark"] .modal-header,[data-theme="dark"] .modal-footer{border-color:var(--border)}
[data-theme="dark"] .btn-close{filter:invert(1) grayscale(1) brightness(1.6)}
[data-theme="dark"] .accordion-item{background:var(--surface);border-color:var(--border);color:var(--text)}
/* .collapsed, not the bare class — otherwise this has equal specificity to the light-mode
   :not(.collapsed) expanded-state rule above and, being later, would win and strip the expanded
   accordion section's brand-tinted highlight in dark mode. */
[data-theme="dark"] .accordion-button.collapsed{background:var(--surface);color:var(--text)}
[data-theme="dark"] .accordion-button::after{filter:invert(1) grayscale(1) brightness(1.8)}
[data-theme="dark"] .text-secondary,[data-theme="dark"] .text-muted{color:var(--muted)!important}
[data-theme="dark"] .text-dark,[data-theme="dark"] .text-body{color:var(--text)!important}
/* :not(.btn) is deliberate — without it this beats Bootstrap's own .btn{color:var(--bs-btn-color)}
   rule on specificity (attribute+element vs. a bare class) for any button styled as an <a> tag
   (e.g. <a class="btn btn-success">), overriding its intended solid white text with this light
   purple link color instead — on a purple button that reads as faded/low-contrast text. Buttons
   keep whatever color their own variant class (.btn-success, .btn-outline-secondary, etc.) gives
   them; only plain text links get this treatment. */
[data-theme="dark"] a:not(.btn){color:var(--brand-dark)}
[data-theme="dark"] .btn-link{color:var(--brand-dark)}
[data-theme="dark"] .btn-outline-secondary{border-color:var(--border);color:var(--text)}
[data-theme="dark"] .btn-outline-secondary:hover{background:var(--text);border-color:var(--text);color:var(--bg)}
[data-theme="dark"] .page-item.disabled .page-link{background:var(--surface);color:var(--muted);border-color:var(--border)}
/* :not(.active) on both — otherwise these have equal specificity to the light-mode .active rules
   above and, being later, would win, stripping an active pill/tab's brand highlight on hover. */
[data-theme="dark"] .nav-pills .nav-link:hover:not(.active){background-color:var(--bg)}
[data-theme="dark"] .nav-tabs .nav-link:hover:not(.active){background:var(--bg)}
[data-theme="dark"] .table input.form-control-plain:hover,[data-theme="dark"] .table textarea.form-control-plain:hover{background:var(--bg)}
[data-theme="dark"] .table input.form-control-plain:focus,[data-theme="dark"] .table textarea.form-control-plain:focus{background:var(--surface)}
[data-theme="dark"] .alert-success{background:var(--success-light);color:#86efac;border-color:transparent}
[data-theme="dark"] .alert-danger{background:var(--danger-light);color:#fca5a5;border-color:transparent}
[data-theme="dark"] .alert-warning{background:var(--warn-light);color:#fde68a;border-color:transparent}
[data-theme="dark"] .alert-info{background:var(--info-light);color:#5eead4;border-color:transparent}
[data-theme="dark"] .badge-soft.warn{color:#fde68a}
[data-theme="dark"] .badge-soft.danger{color:#fca5a5}
[data-theme="dark"] .stat-trend.down{color:#fca5a5}
[data-theme="dark"] .auth-alert{color:#fca5a5}
[data-theme="dark"] .progress{background-color:#262a34}
[data-theme="dark"] input[type="date"]::-webkit-calendar-picker-indicator,
[data-theme="dark"] input[type="time"]::-webkit-calendar-picker-indicator{filter:invert(1) brightness(1.6)}
[data-theme="dark"] .auth-hero::before{background:radial-gradient(circle,rgba(192,132,252,.22),transparent 70%)}

/* ---- theme toggle button — sun/moon swap driven purely by CSS off the [data-theme] attribute, no
   JS re-render needed on click beyond flipping the attribute itself. ---- */
.theme-toggle-btn .icon-moon{display:none}
[data-theme="dark"] .theme-toggle-btn .icon-sun{display:none}
[data-theme="dark"] .theme-toggle-btn .icon-moon{display:block}
