/* ==========================================================================
   NOC Console — Shared Component Library (CSS)
   Used by every page: dashboard, managed-olts, unconfigured-onts,
   provisioning-profiles, vlan-settings, system-logs, access-control,
   platform-settings.
   Plain CSS (no Tailwind @apply dependency) so it works reliably when
   loaded as an external stylesheet alongside the Tailwind CDN script.

   THEMING: every color below is a var(--token, fallback) reference, never
   a bare literal -- :root defines the DARK palette (this app's original,
   only look), [data-theme="light"] on <html> overrides the same token set
   with light values. public/js/main.js's NOC.Theme is what sets/removes
   that attribute (persisted to localStorage, default from
   prefers-color-scheme) -- see views/partials/head.ejs and console-head.ejs's
   early inline script for the pre-paint FOUC-prevention half of that. The
   fallback in each var(...) is this file's original literal value, so
   anything that somehow renders before :root's own definitions land still
   looks exactly as it always has.

   Token set and light-mode values are shared verbatim with the sibling
   mikrotik-billing app's public/css/app.css (same design language), but
   this is an independent copy -- edit freely, it never syncs back.
   ========================================================================== */

:root {
  /* Backgrounds */
  --bg-page: #0b0f19;
  --bg-sidebar: #0b0f19;
  --bg-surface: #0f1420;
  --bg-surface-alt: #1e2333;
  --bg-surface-alt-hover: #262c40;
  --bg-readonly: rgba(30,35,51,0.6);
  --bg-scrollbar-track: #0f1420;
  --bg-scrollbar-thumb: #2c3345;
  --bg-scrollbar-thumb-hover: #3d4560;
  --bg-row-hover: rgba(30,41,59,0.3);
  --bg-help-th: rgba(30,41,59,0.4);

  /* Borders */
  --border-default: rgba(51,65,85,0.6);
  --border-subtle: rgba(51,65,85,0.4);
  --border-sidebar: rgba(51,65,85,0.5);
  --border-input: rgba(71,85,105,0.6);
  --border-btn-secondary: rgba(71,85,105,0.5);
  --border-danger: rgba(244,63,94,0.3);

  /* Text */
  --text-heading: #ffffff;
  --text-primary: #e4e4e7;
  --text-secondary: #d4d4d8;
  --text-muted: #a1a1aa;
  --text-faint: #71717a;

  /* Accent (brand indigo) */
  --accent: #6366f1;
  --accent-strong: #4f46e5;
  --accent-strong-hover: #4338ca;

  /* Tone pairs (badges/toasts/alerts) */
  --success-bg: rgba(16,185,129,0.15);
  --success-text: #34d399;
  --success-border: rgba(16,185,129,0.4);
  --warning-bg: rgba(245,158,11,0.15);
  --warning-text: #fbbf24;
  --danger-bg: rgba(244,63,94,0.15);
  --danger-bg-hover: rgba(244,63,94,0.25);
  --danger-text: #fb7185;
  --danger-toast-border: rgba(244,63,94,0.4);
  --neutral-bg: rgba(148,163,184,0.15);
  --neutral-text: #a1a1aa;
  --neutral-toast-border: rgba(113,113,122,0.5);
  --info-toast-border: rgba(99,102,241,0.4);

  /* Overlays / shadows -- kept theme-constant (both palettes look right
     with a dark scrim + dark-tinted shadow, same reasoning most SaaS
     design systems use: shadows don't invert with the theme). */
  --overlay-modal: rgba(0,0,0,0.7);
  --shadow-card: 0 1px 2px 0 rgba(0,0,0,0.4), 0 1px 3px 1px rgba(0,0,0,0.2);
  --shadow-modal: 0 25px 50px -12px rgba(0,0,0,0.7);
}

/* Light palette -- set via public/js/main.js's NOC.Theme on <html
   data-theme="light">. Kept in the SAME token set as :root above so every
   rule below only ever needs to change once. Values ported verbatim from
   mikrotik-billing/public/css/app.css, already WCAG-AA verified there. */
:root[data-theme="light"] {
  --bg-page: #f4f5f7;
  --bg-sidebar: #ffffff;
  --bg-surface: #ffffff;
  --bg-surface-alt: #f1f2f6;
  --bg-surface-alt-hover: #e4e6ed;
  --bg-readonly: rgba(241,242,246,0.8);
  --bg-scrollbar-track: #eceef2;
  --bg-scrollbar-thumb: #cbd0dc;
  --bg-scrollbar-thumb-hover: #b0b6c6;
  --bg-row-hover: rgba(99,102,241,0.05);
  --bg-help-th: rgba(99,102,241,0.05);

  --border-default: rgba(15,23,42,0.2);
  --border-subtle: rgba(15,23,42,0.14);
  --border-sidebar: rgba(15,23,42,0.16);
  --border-input: rgba(15,23,42,0.5);
  --border-btn-secondary: rgba(15,23,42,0.5);
  --border-danger: rgba(225,29,72,0.25);

  --text-heading: #0f172a;
  --text-primary: #1e2433;
  --text-secondary: #334155;
  --text-muted: #526278;
  --text-faint: #5f6b85;

  --accent: #6366f1;
  --accent-strong: #4f46e5;
  --accent-strong-hover: #4338ca;

  --success-bg: rgba(16,185,129,0.12);
  --success-text: #047a56;
  --success-border: rgba(5,150,105,0.35);
  --warning-bg: rgba(245,158,11,0.14);
  --warning-text: #a3480a;
  --danger-bg: rgba(225,29,72,0.1);
  --danger-bg-hover: rgba(225,29,72,0.18);
  --danger-text: #c81e45;
  --danger-toast-border: rgba(225,29,72,0.35);
  --neutral-bg: rgba(100,116,139,0.12);
  --neutral-text: #475569;
  --neutral-toast-border: rgba(100,116,139,0.4);
  --info-toast-border: rgba(99,102,241,0.35);

  --overlay-modal: rgba(15,23,42,0.45);
  --shadow-card: 0 1px 2px 0 rgba(15,23,42,0.06), 0 1px 3px 1px rgba(15,23,42,0.05);
  --shadow-modal: 0 25px 50px -12px rgba(15,23,42,0.25);
}

/* System-preference fallback for a visitor who has never toggled anything
   AND whose device is unreachable at inline-script time (the head/
   console-head inline script covers the normal case synchronously). */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]):not([data-theme="light"]) {
    --bg-page: #f4f5f7;
    --bg-sidebar: #ffffff;
    --bg-surface: #ffffff;
    --bg-surface-alt: #f1f2f6;
    --bg-surface-alt-hover: #e4e6ed;
    --bg-readonly: rgba(241,242,246,0.8);
    --bg-scrollbar-track: #eceef2;
    --bg-scrollbar-thumb: #cbd0dc;
    --bg-scrollbar-thumb-hover: #b0b6c6;
    --bg-row-hover: rgba(99,102,241,0.05);
    --bg-help-th: rgba(99,102,241,0.05);
    --border-default: rgba(15,23,42,0.2);
    --border-subtle: rgba(15,23,42,0.14);
    --border-sidebar: rgba(15,23,42,0.16);
    --border-input: rgba(15,23,42,0.5);
    --border-btn-secondary: rgba(15,23,42,0.5);
    --border-danger: rgba(225,29,72,0.25);
    --text-heading: #0f172a;
    --text-primary: #1e2433;
    --text-secondary: #334155;
    --text-muted: #526278;
    --text-faint: #5f6b85;
    --success-bg: rgba(16,185,129,0.12);
    --success-text: #047a56;
    --success-border: rgba(5,150,105,0.35);
    --warning-bg: rgba(245,158,11,0.14);
    --warning-text: #a3480a;
    --danger-bg: rgba(225,29,72,0.1);
    --danger-bg-hover: rgba(225,29,72,0.18);
    --danger-text: #c81e45;
    --danger-toast-border: rgba(225,29,72,0.35);
    --neutral-bg: rgba(100,116,139,0.12);
    --neutral-text: #475569;
    --neutral-toast-border: rgba(100,116,139,0.4);
    --info-toast-border: rgba(99,102,241,0.35);
    --overlay-modal: rgba(15,23,42,0.45);
    --shadow-card: 0 1px 2px 0 rgba(15,23,42,0.06), 0 1px 3px 1px rgba(15,23,42,0.05);
    --shadow-modal: 0 25px 50px -12px rgba(15,23,42,0.25);
  }
}

* { font-family: 'Inter', ui-sans-serif, system-ui, sans-serif; }

html, body { background-color: var(--bg-page, #0b0f19); transition: background-color 0.15s ease; }

body { color: var(--text-primary, #e4e4e7); }

/* ---------- Scrollbars ---------- */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--bg-scrollbar-track, #0f1420); }
::-webkit-scrollbar-thumb { background: var(--bg-scrollbar-thumb, #2c3345); border-radius: 8px; }
::-webkit-scrollbar-thumb:hover { background: var(--bg-scrollbar-thumb-hover, #3d4560); }

/* ==========================================================================
   Layout shell: sidebar / header / backdrop
   ========================================================================== */
#sidebar {
  width: 18rem;
  flex-shrink: 0;
  background-color: var(--bg-sidebar, #0b0f19);
  border-right: 1px solid var(--border-sidebar, rgba(51, 65, 85, 0.5));
  display: flex;
  flex-direction: column;
  transition: transform 0.25s ease-in-out;
}

@media (min-width: 1024px) {
  /* Pinned to the viewport instead of scrolling away with page content --
     own independent scrollbar (overflow-y) for a nav list taller than the
     viewport, rather than growing the whole page. #sidebarBackdrop + div
     targets the header+main content wrapper that's always this element's
     next sibling (every page built off this shell -- dashboard.ejs etc. --
     shares that exact structure) WITHOUT needing a new class added to
     every one of those view files: a fixed element no longer participates
     in the parent flex row that used to push it over automatically, so
     this margin-left (matching #sidebar's own width above) replaces that. */
  #sidebar {
    position: fixed;
    top: 0;
    left: 0;
    height: 100vh;
    overflow-y: auto;
    z-index: 20;
  }
  #sidebarBackdrop + div {
    margin-left: 18rem;
  }
}

@media (max-width: 1023px) {
  #sidebar {
    transform: translateX(-100%);
    position: fixed;
    z-index: 50;
    height: 100vh;
    top: 0;
    left: 0;
  }
  #sidebar.sidebar-open { transform: translateX(0); }
}

.nav-link { transition: all 0.15s ease-in-out; }
.nav-link:hover { background-color: rgba(99, 102, 241, 0.08); }
.nav-link.active {
  background-color: rgba(99, 102, 241, 0.15);
  box-shadow: inset 3px 0 0 0 var(--accent, #6366f1);
  color: var(--text-heading, #ffffff);
}

.status-dot { box-shadow: 0 0 0 3px rgba(0,0,0,0.15); position: relative; }
.status-dot.pulse::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 9999px;
  background: inherit;
  animation: pulse-ring 1.8s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse-ring {
  0% { transform: scale(1); opacity: 0.7; }
  100% { transform: scale(2.4); opacity: 0; }
}

/* Content fade used during tenant-switch transitions */
.content-fading { opacity: 0.25; transition: opacity 0.2s ease-in-out; }
.content-fresh { opacity: 1; transition: opacity 0.25s ease-in-out; }

/* ==========================================================================
   Buttons
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.5rem 0.85rem;
  border-radius: 0.5rem;
  transition: background-color 0.15s ease, opacity 0.15s ease;
  cursor: pointer;
  border: 1px solid transparent;
  white-space: nowrap;
}
.btn:disabled, .btn.is-loading { opacity: 0.75; cursor: not-allowed; }
/* `.btn`'s unconditional `display: inline-flex` above and `.hidden{display:none}`
   (/vendor/tailwind.css) have EQUAL specificity (one class each) -- same tie
   as .noc-modal-overlay's own documented fix further down this file, and
   this file loads after tailwind.css, so `.btn` always won: any button
   toggled with `class="btn ... hidden"` (e.g. views/console/tenants.ejs's
   Demo Environment panel buttons) rendered visible regardless of whether
   `hidden` was actually applied -- CONFIRMED LIVE 2026-09-08 verifying the
   new Demo Accounts feature, "Create Demo Tenant" stayed visible alongside
   "Reseed Demo Data" even though a demo tenant already existed. Two classes
   (0,2,0) beats one (0,1,0) independent of load order, so this is the
   permanent fix rather than a reordering hack. */
.btn.hidden { display: none; }

.btn-primary { background-color: var(--accent-strong, #4f46e5); color: #fff; }
.btn-primary:hover:not(:disabled) { background-color: var(--accent-strong-hover, #4338ca); }

.btn-secondary { background-color: var(--bg-surface-alt, #1e2333); color: var(--text-secondary, #d4d4d8); border-color: var(--border-btn-secondary, rgba(71,85,105,0.5)); }
.btn-secondary:hover:not(:disabled) { background-color: var(--bg-surface-alt-hover, #262c40); }

.btn-danger { background-color: var(--danger-bg, rgba(244,63,94,0.15)); color: var(--danger-text, #fb7185); border-color: var(--border-danger, rgba(244,63,94,0.3)); }
.btn-danger:hover:not(:disabled) { background-color: var(--danger-bg-hover, rgba(244,63,94,0.25)); }

.btn-ghost { background-color: transparent; color: var(--text-muted, #a1a1aa); }
.btn-ghost:hover:not(:disabled) { background-color: rgba(148,163,184,0.1); color: var(--text-heading, #fff); }

.btn-sm { padding: 0.35rem 0.65rem; font-size: 0.7rem; }
.btn-block { width: 100%; }

/* ==========================================================================
   Cards / Panels
   ========================================================================== */
.card {
  background-color: var(--bg-surface, #0f1420);
  border: 1px solid var(--border-default, rgba(51,65,85,0.6));
  border-radius: 0.75rem;
  box-shadow: var(--shadow-card, 0 1px 2px 0 rgba(0,0,0,0.4), 0 1px 3px 1px rgba(0,0,0,0.2));
}
.card-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--border-default, rgba(51,65,85,0.6));
}
.kpi-card { padding: 1.25rem; }

/* ==========================================================================
   Badges
   ========================================================================== */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.68rem;
  font-weight: 600;
  padding: 0.25rem 0.6rem;
  border-radius: 9999px;
  line-height: 1;
  white-space: nowrap;
}
.badge-success { background-color: var(--success-bg, rgba(16,185,129,0.15)); color: var(--success-text, #34d399); }
.badge-warning { background-color: var(--warning-bg, rgba(245,158,11,0.15)); color: var(--warning-text, #fbbf24); }
.badge-danger  { background-color: var(--danger-bg, rgba(244,63,94,0.15));  color: var(--danger-text, #fb7185); }
.badge-info    { background-color: rgba(99,102,241,0.15); color: #818cf8; }
.badge-neutral { background-color: var(--neutral-bg, rgba(148,163,184,0.15)); color: var(--neutral-text, #a1a1aa); }

/* ==========================================================================
   Form Fields
   ========================================================================== */
.field-label {
  display: block;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-muted, #a1a1aa);
  margin-bottom: 0.4rem;
}
.field-input, .field-select, .field-textarea {
  width: 100%;
  background-color: var(--bg-surface-alt, #1e2333);
  border: 1px solid var(--border-input, rgba(71,85,105,0.6));
  border-radius: 0.5rem;
  padding: 0.6rem 0.75rem;
  font-size: 0.85rem;
  color: var(--text-heading, #f4f4f5);
}
.field-input::placeholder, .field-textarea::placeholder { color: var(--text-faint, #71717a); }
.field-input:focus, .field-select:focus, .field-textarea:focus {
  outline: none;
  border-color: var(--accent, #6366f1);
  box-shadow: 0 0 0 3px rgba(99,102,241,0.25);
}
.field-input[readonly] {
  background-color: var(--bg-readonly, rgba(30,35,51,0.6));
  color: var(--text-muted, #a1a1aa);
  cursor: not-allowed;
}
.field-select-wrap { position: relative; }
.field-select {
  appearance: none;
  cursor: pointer;
  padding-right: 2.25rem;
}
.field-select-chevron {
  position: absolute;
  right: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-faint, #71717a);
  font-size: 0.7rem;
  pointer-events: none;
}

/* Toggle switch */
.noc-switch { position: relative; display: inline-block; width: 40px; height: 22px; flex-shrink: 0; }
.noc-switch input { opacity: 0; width: 0; height: 0; }
.noc-switch-track {
  position: absolute; cursor: pointer; inset: 0;
  background-color: #334155; border-radius: 9999px; transition: 0.2s;
}
.noc-switch-track::before {
  content: ""; position: absolute; height: 16px; width: 16px; left: 3px; top: 3px;
  background-color: var(--text-primary, #e4e4e7); border-radius: 9999px; transition: 0.2s;
}
.noc-switch input:checked + .noc-switch-track { background-color: var(--accent-strong, #4f46e5); }
.noc-switch input:checked + .noc-switch-track::before { transform: translateX(18px); background-color: #fff; }

/* ==========================================================================
   Tables
   ========================================================================== */
.data-table { width: 100%; font-size: 0.875rem; }
.data-table thead th {
  text-align: left;
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-faint, #71717a);
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--border-default, rgba(51,65,85,0.6));
  white-space: nowrap;
}
.data-table tbody tr { border-bottom: 1px solid var(--border-subtle, rgba(51,65,85,0.4)); }
.data-table tbody tr:hover { background-color: var(--bg-row-hover, rgba(30,41,59,0.3)); }
.data-table tbody td { padding: 0.85rem 1rem; vertical-align: middle; }

/* ==========================================================================
   Responsive column-priority hiding + sort + expand-detail (dataTable.js)
   ========================================================================== */
@media (max-width: 1023px) {
  .data-table [data-priority="3"] { display: none; }
}
@media (max-width: 767px) {
  .data-table [data-priority="2"],
  .data-table [data-priority="3"] { display: none; }
}
.dt-toggle-col { width: 2.25rem; padding-left: 0.5rem !important; padding-right: 0.25rem !important; }
.data-table:not(.dt-has-hidden-columns) .dt-toggle-col { display: none; }
.dt-expand-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 0.375rem;
  background: transparent;
  color: var(--text-muted, #a1a1aa);
  cursor: pointer;
  border: none;
}
.dt-expand-toggle:hover { background-color: var(--bg-surface-alt, #1e2333); color: var(--text-heading, #fff); }
.dt-expand-toggle i { transition: transform 0.15s ease; font-size: 0.7rem; }

.dt-sort-th { cursor: pointer; user-select: none; }
.dt-sort-th.dt-sort-asc::after { content: '\25B2'; margin-left: 0.35em; font-size: 0.6rem; }
.dt-sort-th.dt-sort-desc::after { content: '\25BC'; margin-left: 0.35em; font-size: 0.6rem; }

.dt-detail-row td { background-color: var(--bg-help-th, rgba(30,41,59,0.4)); padding: 0.85rem 1.25rem; }
.dt-detail-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 0.6rem 1.5rem; }
.dt-detail-item { display: flex; flex-direction: column; gap: 0.15rem; }
.dt-detail-label { font-size: 0.65rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-faint, #71717a); }
.dt-detail-value { font-size: 0.82rem; color: var(--text-primary, #e4e4e7); }
.dt-detail-empty { font-size: 0.8rem; color: var(--text-faint, #71717a); }
.dt-detail-loading { font-size: 0.8rem; color: var(--text-faint, #71717a); display: flex; align-items: center; gap: 0.5rem; }

.dt-row-count { font-size: 0.75rem; color: var(--text-faint, #71717a); }

.row-removing {
  transition: opacity 0.35s ease, transform 0.35s ease;
  opacity: 0;
  transform: translateX(12px);
}

/* ==========================================================================
   Modal
   ========================================================================== */
.noc-modal-overlay {
  position: fixed;
  inset: 0;
  /* Leaflet's own panes/controls (leaflet.css) go up to z-index:1000, and
     nothing in this page's markup gives the map a stacking context of its
     own -- so at the old z-index:60 the map's zoom/draw controls painted
     above this overlay instead of being covered by it (network-map.ejs).
     1050 clears Leaflet's max. */
  z-index: 1050;
  background-color: var(--overlay-modal, rgba(0,0,0,0.7));
  backdrop-filter: blur(2px);
  align-items: center;
  justify-content: center;
  padding: 1rem;
}
/* `display` split out from the base rule above and gated on :not(.hidden)
   deliberately -- CONFIRMED LIVE regression 2026-08-30: this class and
   .hidden{display:none} (/vendor/tailwind.css) have EQUAL CSS specificity
   (one class each), so which one wins a tie is decided purely by
   stylesheet source order. Now that tailwind.css loads via a plain <link>
   fixed BEFORE this file in every <head> partial (previously the Tailwind
   Play CDN script, which injected its own <style> tag dynamically and
   happened to usually land after this file), this rule's display:flex now
   ALWAYS wins over .hidden -- every modal using this class rendered as a
   full-screen blurred backdrop permanently, whether or not `hidden` was
   present, since the modal PANEL itself (.noc-modal-panel, opacity:0 by
   default) stayed invisible on top of it. :not(.hidden) raises this
   selector's own specificity (0,2,0) above plain .hidden's (0,1,0),
   independent of load order -- the correct, permanent fix, not a
   reordering hack that would just break again the next time some other
   file's position in <head> changes. */
.noc-modal-overlay:not(.hidden) {
  display: flex;
}
.noc-modal-panel {
  opacity: 0;
  transform: scale(0.95);
  transition: opacity 0.2s ease, transform 0.2s ease;
  background-color: var(--bg-surface, #0f1420);
  border: 1px solid var(--border-input, rgba(71,85,105,0.6));
  border-radius: 1rem;
  box-shadow: var(--shadow-modal, 0 25px 50px -12px rgba(0,0,0,0.7));
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
}
.noc-modal-panel.modal-open { opacity: 1; transform: scale(1); }
.noc-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.1rem 1.5rem;
  border-bottom: 1px solid var(--border-default, rgba(51,65,85,0.6));
  position: sticky;
  top: 0;
  background-color: var(--bg-surface, #0f1420);
  z-index: 1;
}
.noc-modal-body { padding: 1.25rem 1.5rem; }
.noc-modal-footer {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.5rem 1.25rem;
  position: sticky;
  bottom: 0;
  background-color: var(--bg-surface, #0f1420);
}

/* ==========================================================================
   Toasts
   ========================================================================== */
#toastContainer { position: fixed; top: 1.25rem; right: 1.25rem; z-index: 1060; width: 20rem; }
.toast-item {
  animation: slideInToast 0.25s ease-out;
  background-color: var(--bg-surface, #0f1420);
  border-radius: 0.75rem;
  box-shadow: var(--shadow-modal, 0 25px 50px -12px rgba(0,0,0,0.6));
  padding: 0.85rem 1rem;
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
  border: 1px solid;
}
@keyframes slideInToast {
  from { transform: translateX(120%); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}
.toast-success { border-color: var(--success-border, rgba(16,185,129,0.4)); color: var(--success-text, #34d399); }
.toast-info    { border-color: var(--info-toast-border, rgba(99,102,241,0.4));  color: #818cf8; }
.toast-neutral { border-color: var(--neutral-toast-border, rgba(113,113,122,0.5)); color: var(--neutral-text, #a1a1aa); }
.toast-error   { border-color: var(--danger-toast-border, rgba(244,63,94,0.4));   color: var(--danger-text, #fb7185); }

/* ==========================================================================
   Misc
   ========================================================================== */
.mini-bar-track { background-color: var(--bg-surface-alt, #1e2333); }

.spinner {
  border: 2px solid rgba(255,255,255,0.35);
  border-top-color: #ffffff;
  border-radius: 9999px;
  width: 14px;
  height: 14px;
  animation: spin 0.7s linear infinite;
  display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

.signal-bar-track { background-color: var(--bg-surface-alt, #1e2333); border-radius: 9999px; overflow: hidden; height: 5px; }

/* ==========================================================================
   Theme toggle button (views/partials/header.ejs)
   ========================================================================== */
#themeToggleBtn .fa-sun { display: none; }
:root[data-theme="light"] #themeToggleBtn .fa-moon { display: none; }
:root[data-theme="light"] #themeToggleBtn .fa-sun { display: inline-block; }

/* ==========================================================================
   Tailwind literal-color overrides.

   public/vendor/tailwind.css is a build-time-generated stylesheet (Tailwind
   CLI v3, content-scanned across both apps' views/shared-views/public/js)
   -- REPLACED Play CDN's in-browser JIT compiler here, which is what this
   comment originally described; see git history for that version, and
   mikrotik-billing's own public/css/app.css comment for the exact rebuild
   command (this app's identical copy of public/vendor/tailwind.css is
   regenerated the same way, from the same content scan across BOTH apps).

   Every view in this app was written directly against Tailwind's literal
   dark-palette utility classes (bg-slate-950, text-zinc-500, etc.) rather
   than the .card/.btn/etc. component classes above. None of that is
   CSS-variable-driven -- Tailwind generates each one as a fixed, literal
   color, with no theming hook of its own, regardless of whether it's
   compiled in-browser or ahead of time.

   Rewriting every instance across ~35 view files to use var()-driven
   classes instead is out of scope for this pass -- this block instead
   re-targets the highest-volume classes DIRECTLY BY THEIR OWN TAILWIND
   CLASS NAME and overrides them with the theme tokens above. Still
   !important even now that this loads as an ordinary stylesheet (cascade
   order alone would suffice, this file's <link> already sits after
   tailwind.css) -- kept as defense in depth against a future reordering.

   Same approach, same class inventory shape, as the sibling mikrotik-
   billing app's app.css (see that file's own comment for the two
   opacity-modifier/divide-* traps that were found live there and are
   pre-empted here). Two verified exceptions kept hardcoded white: an <i>
   icon glyph sitting on a fixed bg-indigo-600 logo square (sidebar/
   console-nav brand mark, login/forgot/change-password screens) --
   protected via the i.text-white tag+class selector -- and #notifBadge
   (header.ejs), a text-white span on its own fixed bg-rose-500 chip,
   protected via ID specificity. A same-element grep across every view for
   text-white paired with another saturated bg-* color found no other
   exceptions. bg-slate-925 is intentionally NOT targeted below -- it's a
   dead/non-existent Tailwind shade, always paired redundantly with
   bg-[#0b0f19] on the same element (confirmed: every #appHeader use is the
   literal "bg-slate-925 bg-[#0b0f19]" pair) -- targeting bg-[#0b0f19]
   alone covers it.
   ========================================================================== */
.bg-slate-950 { background-color: var(--bg-page) !important; }
.bg-\[\#0b0f19\] { background-color: var(--bg-page) !important; }
.bg-slate-900, .bg-slate-900\/40, .bg-slate-900\/60, .bg-slate-900\/70, .bg-slate-900\/80 { background-color: var(--bg-surface) !important; }
.bg-slate-800 { background-color: var(--bg-surface-alt) !important; }
.bg-slate-700 { background-color: var(--bg-surface-alt-hover) !important; }
.hover\:bg-slate-900\:hover, .hover\:bg-slate-900\/40\:hover, .hover\:bg-slate-900\/60\:hover { background-color: var(--bg-surface) !important; }
.hover\:bg-slate-800\:hover, .hover\:bg-slate-800\/60\:hover, .hover\:bg-slate-800\/40\:hover { background-color: var(--bg-surface-alt) !important; }
.hover\:bg-slate-700\:hover { background-color: var(--bg-surface-alt-hover) !important; }

.border-slate-800, .border-slate-800\/80, .border-slate-800\/60 { border-color: var(--border-default) !important; }
.border-slate-800\/40 { border-color: var(--border-subtle) !important; }
.border-slate-700, .border-slate-700\/80 { border-color: var(--border-input) !important; }
.hover\:border-slate-400\:hover { border-color: var(--border-input) !important; }
.divide-slate-800 > :not([hidden]) ~ :not([hidden]),
.divide-slate-800\/80 > :not([hidden]) ~ :not([hidden]),
.divide-slate-800\/60 > :not([hidden]) ~ :not([hidden]) { border-color: var(--border-default) !important; }

.text-zinc-100 { color: var(--text-heading) !important; }
.text-zinc-200 { color: var(--text-primary) !important; }
.text-zinc-300 { color: var(--text-secondary) !important; }
.text-zinc-400 { color: var(--text-muted) !important; }
.text-zinc-500 { color: var(--text-faint) !important; }
.text-zinc-600 { color: var(--text-faint) !important; }
.text-zinc-700 { color: var(--text-faint) !important; }
.hover\:text-zinc-300\:hover { color: var(--text-secondary) !important; }

.text-white, .hover\:text-white\:hover { color: var(--text-heading) !important; }
i.text-white { color: #fff !important; } /* icon glyph on a fixed-color logo square -- see block comment above */
#notifBadge { color: #fff !important; }  /* badge on a fixed bg-rose-500 chip -- see block comment above */
#notifBadge { --tw-ring-color: var(--bg-page) !important; } /* the badge's ring is meant to match the header background it sits on, in either theme */
