:root {
  /* Pulled from the Paragon Digital logo. */
  --color-teal: #1b3a44;
  --color-teal-dark: #12262c;
  --color-teal-light: #2e5561;
  --color-teal-tint: #e9eff0;
  --color-maroon: #6e2a22;
  --color-maroon-light: #8b3a2e;

  --color-bg: #f3f1ed;
  --color-card: #ffffff;
  --color-border: #e4e1db;
  --color-text: #22272b;
  --color-text-muted: #6b6f73;

  /* Visual refresh (2026-08-05): richer gradients/shadows/an accent color
     layered on top of the existing brand teal+maroon palette above -- every
     component below still keys off --color-teal/--color-maroon as its base,
     these just add depth (gradients, glow, elevation) rather than replacing
     the brand identity. --color-accent is new: a warm amber used sparingly,
     for the global loading bar and the odd highlight, so it reads as "this
     is active/in-progress" distinctly from the teal=brand / maroon=schedule
     color language already established. */
  --color-teal-grad: linear-gradient(135deg, #204450 0%, #163038 100%);
  --color-teal-grad-hover: linear-gradient(135deg, #2a5764 0%, #1b3a44 100%);
  --color-maroon-grad: linear-gradient(135deg, #7d3227 0%, #5c231c 100%);
  --color-maroon-grad-hover: linear-gradient(135deg, #93402f 0%, #6e2a22 100%);
  --color-accent: #e7a53a;
  --color-accent-light: #f4c878;

  --shadow-xs: 0 1px 2px rgba(18, 30, 34, 0.06);
  --shadow-sm: 0 2px 6px rgba(18, 30, 34, 0.08);
  --shadow-md: 0 6px 18px rgba(18, 30, 34, 0.12);
  --shadow-lg: 0 16px 40px rgba(18, 30, 34, 0.18);
  --shadow-glow-teal: 0 4px 14px rgba(27, 58, 68, 0.28);
  --shadow-glow-maroon: 0 4px 14px rgba(110, 42, 34, 0.28);

  /* Soft tinted panel/status backgrounds used in a handful of places (edit
     forms, the device-card header, empty monitor slots, the schedule-gap
     warning box, danger-hover states, the schedule-gap-ok success line).
     These were hardcoded hex literals before 2026-08-05's dark-mode pass --
     pulled out into variables here specifically so the dark theme block
     below can give them proper dark equivalents instead of staying stuck
     as light-mode-only off-white/pink patches on an otherwise dark page. */
  --color-panel-tint: #faf9f7;
  --color-panel-tint-2: #fbf8f2;
  --color-panel-tint-3: #f5f1e8;
  --color-danger-tint: #f7ece9;
  --color-success-text: #166534;

  --font-heading: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

/* Theme (2026-08-04, aesthetics only -- see Settings > Appearance).
   Explicit choice: <html data-theme="light|dark"> always wins. "System" (the
   default, no attribute set) falls through to the OS preference below.
   Every color everywhere else in this file already goes through the
   --color-* variables above, so overriding just those here is enough --
   no per-component dark rules needed. */
[data-theme="dark"] {
  --color-bg: #14181a;
  --color-card: #20272a;
  /* Brightened from #363e42 (2026-08-06, user report: "hard to read...
     border lines" in dark mode) -- that value had too little contrast
     against both --color-bg and --color-card to actually read as a line. */
  --color-border: #4a545a;
  --color-text: #e8e6e1;
  --color-text-muted: #9aa2a6;
  --color-teal-tint: #1b2a2f;

  /* Brand teal/maroon (2026-08-06, same user report: "teal color text" was
     hard to read in dark mode). The base --color-teal/--color-maroon
     (#1b3a44 / #6e2a22) were tuned for dark text on a LIGHT background --
     used as-is for text/borders on this dark theme's near-black background,
     they're nearly invisible. Lightened here so every element that already
     keys off these two variables (headings, links, outline buttons, badges,
     chips, dividers -- the entire design system) gets readable dark-mode
     colors automatically, with no per-component changes needed. Button
     FILLS (--color-teal-grad etc.) are untouched -- white text on those
     stays readable regardless of theme, so only the text/border uses of
     the base colors needed adjusting. */
  --color-teal: #7fb8c4;
  --color-teal-light: #9dcbd5;
  --color-teal-dark: #5fa0ae;
  --color-maroon: #e0917d;
  --color-maroon-light: #eaab9a;

  --color-panel-tint: #262f33;
  --color-panel-tint-2: #2a3336;
  --color-panel-tint-3: #232b2e;
  --color-danger-tint: #3a2420;
  --color-success-text: #6ee7a0;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-sm: 0 2px 6px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.55);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --color-bg: #14181a;
    --color-card: #20272a;
    --color-border: #4a545a;
    --color-text: #e8e6e1;
    --color-text-muted: #9aa2a6;
    --color-teal-tint: #1b2a2f;
    --color-teal: #7fb8c4;
    --color-teal-light: #9dcbd5;
    --color-teal-dark: #5fa0ae;
    --color-maroon: #e0917d;
    --color-maroon-light: #eaab9a;
    --color-panel-tint: #262f33;
    --color-panel-tint-2: #2a3336;
    --color-panel-tint-3: #232b2e;
    --color-danger-tint: #3a2420;
    --color-success-text: #6ee7a0;
    --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.35);
    --shadow-sm: 0 2px 6px rgba(0, 0, 0, 0.4);
    --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.45);
    --shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.55);
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font-body);
  /* Soft radial wash instead of a flat fill -- gives the whole console a
     little more depth without competing with card content. */
  background:
    radial-gradient(circle at 12% 0%, rgba(27, 58, 68, 0.07), transparent 42%),
    radial-gradient(circle at 100% 18%, rgba(110, 42, 34, 0.05), transparent 38%),
    var(--color-bg);
  background-attachment: fixed;
  color: var(--color-text);
  transition: background-color 0.2s ease, color 0.2s ease;
}

.wrapper {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.card {
  position: relative;
  background: var(--color-card);
  border-radius: 16px;
  box-shadow: var(--shadow-lg);
  padding: 40px;
  width: 100%;
  max-width: 380px;
  overflow: hidden;
}

/* Replaces the old flat border-top: a soft gradient cap instead of a hard
   line of solid color -- same brand teal, more depth. */
.card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 5px;
  background: linear-gradient(90deg, var(--color-teal) 0%, var(--color-teal-light) 55%, var(--color-accent) 100%);
}

.card.wide {
  max-width: 920px;
}

.hidden {
  display: none;
}

.logo {
  display: block;
  width: 100%;
  max-width: 240px;
  height: auto;
  object-fit: contain;
  margin: 0 auto 16px;
}

.logo-small {
  display: block;
  height: 92px;
  width: auto;
  flex-shrink: 0;
  object-fit: contain;
  margin-bottom: 6px;
}

.brand-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

h1 {
  margin: 0 0 4px;
  font-family: var(--font-heading);
  font-size: 22px;
  color: var(--color-teal);
}

h2 {
  font-family: var(--font-heading);
  color: var(--color-teal);
}

.subtitle {
  margin: 0 0 24px;
  color: var(--color-text-muted);
  text-align: center;
  font-weight: 500;
  letter-spacing: 0.02em;
}

.dashboard-header .subtitle {
  margin: 0;
  text-align: left;
}

form {
  display: flex;
  flex-direction: column;
}

label {
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text);
  margin-top: 12px;
}

input {
  margin-top: 6px;
  padding: 10px 12px;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  font-size: 14px;
  font-family: var(--font-body);
  background: var(--color-card);
  color: var(--color-text);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

input:focus {
  outline: none;
  border-color: var(--color-teal-light);
  box-shadow: 0 0 0 3px rgba(46, 85, 97, 0.18);
}

/* Spinner (2026-08-05): one keyframe animation, reused by every "something
   is loading" hook in this file -- button:disabled::after, the global top
   loading bar's inner dot, .command-status.is-pending, .upload-progress. */
@keyframes paragon-spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes paragon-bar-sweep {
  0% {
    transform: translateX(-40%) scaleX(0.4);
  }
  50% {
    transform: translateX(20%) scaleX(0.7);
  }
  100% {
    transform: translateX(100%) scaleX(0.4);
  }
}

button {
  margin-top: 20px;
  padding: 10px 16px;
  border: none;
  border-radius: 8px;
  background: var(--color-teal-grad);
  color: #fff;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--shadow-xs);
  transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

button:hover {
  background: var(--color-teal-grad-hover);
  box-shadow: var(--shadow-glow-teal);
  transform: translateY(-1px);
}

button:active {
  transform: translateY(0);
  box-shadow: var(--shadow-xs);
}

/* Every "this is working, please wait" button in app.js follows the same
   pattern: button.disabled = true + its label switches to a present-
   participle ("Saving...", "Signing in...", "Uploading..."). This is the
   one CSS hook that dresses all of them up with a spinner + muted look,
   with zero JS changes needed at most call sites. */
button:disabled {
  background: #9fb1b6;
  color: #fff;
  cursor: default;
  transform: none;
  box-shadow: none;
  opacity: 0.9;
}

button:disabled::after {
  content: "";
  display: inline-block;
  width: 11px;
  height: 11px;
  margin-left: 8px;
  vertical-align: -1px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: paragon-spin 0.7s linear infinite;
  opacity: 0.85;
}

button.secondary {
  background: var(--color-card);
  color: var(--color-teal);
  border: 1px solid var(--color-teal);
  box-shadow: none;
}

button.secondary:hover {
  background: var(--color-teal-tint);
  box-shadow: var(--shadow-sm);
}

button.secondary:disabled {
  background: var(--color-card);
  color: var(--color-text-muted);
  border-color: var(--color-border);
  opacity: 0.75;
}

button.secondary.danger {
  color: var(--color-maroon);
  border-color: var(--color-maroon);
}

button.secondary.danger:hover {
  background: var(--color-danger-tint);
}

.link-button {
  margin-top: 0;
  padding: 0;
  background: none;
  border: none;
  color: var(--color-text-muted);
  font-size: 13px;
  font-weight: 500;
  text-decoration: underline;
  cursor: pointer;
}

.link-button:hover {
  background: none;
  color: var(--color-text);
}

/* Sits below the whole #login-form (not inside it, unlike most .link-button
   uses elsewhere), so it needs its own top margin rather than inheriting
   the form-context margin-top: 0 above. */
#forgot-password-link {
  display: block;
  margin-top: 12px;
}

.error {
  color: var(--color-maroon);
  font-size: 13px;
  min-height: 18px;
  margin-top: 8px;
}

/* 2026-08-09: a generic "this worked" counterpart to .error, first needed
   for the "Forgot your password?" confirmation message. Same sizing/spacing
   as .error so the two can share a single <p> and just swap classes. */
.success-text {
  color: var(--color-success-text);
  font-size: 13px;
  margin-top: 8px;
}

.edit-promo-type-success,
.edit-video-success,
.edit-pdf-success {
  color: var(--color-teal);
  font-size: 13px;
  font-weight: 600;
  margin-top: 8px;
}

.dashboard-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 8px;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--color-border);
}

.dashboard-header button {
  margin-top: 0;
}

.header-actions-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Settings gear (2026-08-04) -- a plain glyph, not an icon library, matching
   this console's no-build-step/no-dependencies approach everywhere else. */
.icon-button {
  margin-top: 0;
  width: 36px;
  height: 36px;
  padding: 0;
  font-size: 17px;
  line-height: 1;
  background: var(--color-card);
  color: var(--color-teal);
  border: 1px solid var(--color-teal);
  border-radius: 10px;
  cursor: pointer;
  box-shadow: none;
  transition: background-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

.icon-button:hover {
  background: var(--color-teal-tint);
  box-shadow: var(--shadow-sm);
  transform: translateY(-1px);
}

.devices-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 28px;
}

.devices-header h2 {
  margin: 0;
  font-size: 16px;
}

.devices-header button {
  margin-top: 0;
}

/* 2026-08-09, "Remove the word 'Fleet'... move up if there is extra space"
   -- with the heading text gone, .devices-header would otherwise justify a
   single child (the actions div) to the LEFT under justify-content:
   space-between (nothing to space it apart from), which reads as
   misaligned since "+ Add Customer" used to sit on the right. Also trims
   the section's own top margin slightly since there's no heading text
   giving it visual weight anymore -- closes up some of that freed space. */
.devices-header-actions-only {
  justify-content: flex-end;
  margin-top: 18px;
}

.view-tabs {
  display: flex;
  gap: 4px;
  margin-top: 18px;
  border-bottom: 1px solid var(--color-border);
}

.view-tab {
  margin-top: 0;
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text-muted);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  cursor: pointer;
}

.view-tab:hover {
  color: var(--color-teal);
  background: none;
}

.view-tab.active {
  color: var(--color-teal);
  border-bottom-color: var(--color-teal);
  position: relative;
}

.view-tab.active::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 2px;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--color-teal), var(--color-accent));
}

.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}

.filter-bar input[type="search"] {
  flex: 1;
  min-width: 220px;
  margin-top: 0;
}

.filter-bar select {
  margin-top: 0;
  padding: 9px 10px;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  font-size: 13px;
  font-family: var(--font-body);
  background: var(--color-card);
  color: var(--color-text);
}

.bulk-actions-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  padding: 10px 12px;
  background: var(--color-teal-tint);
  border: 1px solid var(--color-teal-light);
  border-radius: 8px;
}

/* Same bug family as .fleet-map-wrap.hidden below/elsewhere in this file
   (found 2026-08-09 auditing the Fleet Map bug): .bulk-actions-bar's own
   `display: flex` ties with the shared `.hidden` utility on specificity and
   wins on source order, so `bulkActionsBar.classList.toggle("hidden", count
   === 0)` in app.js was never actually hiding this bar when 0 devices are
   selected -- it stayed visible as an empty highlighted strip. */
.bulk-actions-bar.hidden {
  display: none;
}

.bulk-actions-bar button {
  margin-top: 0;
  padding: 6px 12px;
  font-size: 12px;
}

.bulk-selection-count {
  font-size: 13px;
  font-weight: 600;
  color: var(--color-teal);
  margin-right: auto;
}

/* border-collapse (not separate/overflow:hidden) is deliberate -- the
   per-row Actions dropdown (.action-menu-list) is position:absolute inside
   these cells, and an overflow:hidden ancestor would clip it off. */
.devices-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 12px;
  font-size: 14px;
}

.devices-table th.checkbox-col,
.devices-table td.checkbox-col {
  width: 34px;
  text-align: center;
}

.checkbox-col input[type="checkbox"],
.bulk-actions-bar input[type="checkbox"] {
  width: 15px;
  height: 15px;
  margin: 0;
  cursor: pointer;
}

.devices-table th,
.devices-table td {
  text-align: left;
  padding: 12px 10px;
  border-bottom: 1px solid var(--color-border);
}

.devices-table th {
  background: linear-gradient(180deg, var(--color-teal-tint) 0%, rgba(233, 239, 240, 0.55) 100%);
  color: var(--color-teal);
  font-family: var(--font-heading);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.devices-table th:first-child {
  border-top-left-radius: 10px;
}

.devices-table th:last-child {
  border-top-right-radius: 10px;
}

.devices-table tbody tr {
  transition: background-color 0.12s ease;
}

.devices-table tbody tr:hover {
  background: var(--color-teal-tint);
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 11px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.01em;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.04);
}

.badge::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

.badge-healthy {
  background: linear-gradient(135deg, #dcfce7, #c7f3d6);
  color: #166534;
}

.badge-warning {
  background: linear-gradient(135deg, #fef3c7, #fde5a5);
  color: #92400e;
}

.badge-critical {
  background: linear-gradient(135deg, #fee2e2, #fbcccc);
  color: #991b1b;
}

.badge-offline {
  background: linear-gradient(135deg, #fee2e2, #fbcccc);
  color: #991b1b;
}

.badge-service-running {
  background: linear-gradient(135deg, #dcfce7, #c7f3d6);
  color: #166534;
}

.badge-service-stopped {
  background: linear-gradient(135deg, #e4e1db, #d8d4cb);
  color: #45403a;
}

.actions-cell {
  white-space: nowrap;
}

/* Single "Actions ▾" dropdown per row, replacing a Reload/Restart/Reboot
   button row -- keeps the table readable as more per-device actions
   (Stop/Start Service, and whatever comes after it) get added. */
.action-menu {
  position: relative;
  display: inline-block;
}

/* Same cascade gotcha as .back-link.hidden above: .action-menu's own
   `display` rule has equal specificity to .hidden and sits later in this
   file, so it silently won regardless of which class JS added last. This is
   the bug behind the Customer-role "Send Portal Link" Actions button
   showing even though applyRoleBasedUI() correctly toggled "hidden" on
   #customer-actions-menu -- it affected every role, not just Customer,
   since the div starts with class="action-menu hidden" in the markup
   itself. Explicit compound override, same fix pattern as before. */
.action-menu.hidden {
  display: none;
}

.action-menu-trigger {
  margin-top: 0;
  padding: 5px 12px;
  font-size: 12px;
  font-weight: 600;
  background: var(--color-card);
  color: var(--color-teal);
  border: 1px solid var(--color-teal);
  border-radius: 6px;
  cursor: pointer;
  box-shadow: none;
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}

.action-menu-trigger:hover {
  background: var(--color-teal-tint);
  box-shadow: var(--shadow-xs);
}

.action-menu-list {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  z-index: 10;
  min-width: 170px;
  padding: 4px;
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  box-shadow: var(--shadow-lg);
  animation: paragon-menu-in 0.12s ease;
}

@keyframes paragon-menu-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.action-item {
  display: block;
  width: 100%;
  margin-top: 0;
  padding: 8px 10px;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 500;
  text-align: left;
  background: none;
  color: var(--color-text);
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

.action-item:hover {
  background: var(--color-teal-tint);
}

.action-item.danger {
  color: var(--color-maroon);
}

.action-item.danger:hover {
  background: var(--color-danger-tint);
}

.action-menu-divider {
  height: 1px;
  margin: 4px 2px;
  background: var(--color-border);
}

.command-status {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--color-text-muted);
  min-height: 14px;
}

/* Set by setCommandStatus() in app.js whenever the message is still an
   in-progress one ("Sending...", "Queued...", "Stopping...", etc., anything
   ending in "...") -- explicit user request: a person needs to be able to
   tell at a glance that a Remote Command is still working, not just read
   fine print, so this gets a spinner + the brand teal instead of the
   default muted gray. Cleared automatically once the status text is a
   final one (succeeded/failed/no response). */
.command-status.is-pending {
  color: var(--color-teal);
  font-weight: 600;
}

.command-status.is-pending::before {
  content: "";
  width: 10px;
  height: 10px;
  flex-shrink: 0;
  border: 2px solid var(--color-teal);
  border-right-color: transparent;
  border-radius: 50%;
  animation: paragon-spin 0.7s linear infinite;
}

.customer-link {
  color: var(--color-teal);
  font-weight: 600;
  text-decoration: none;
}

.customer-link:hover {
  text-decoration: underline;
}

/* Settings layout: a left nav of section names + a right content panel
   (2026-08-04) -- replaces stacking every section (Appearance, Promo Types,
   whatever comes next) directly on the page, which stopped looking tidy
   once Promo Types alone could hold a dozen-plus rows. */
.settings-layout {
  display: flex;
  gap: 24px;
  margin-top: 16px;
  align-items: flex-start;
}

.settings-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 0 0 180px;
}

.settings-nav-item {
  margin-top: 0;
  padding: 10px 12px;
  font-size: 14px;
  font-weight: 600;
  text-align: left;
  background: transparent;
  color: var(--color-text-muted);
  border: none;
  border-left: 3px solid transparent;
  border-radius: 0;
  cursor: pointer;
}

.settings-nav-item {
  border-radius: 0 8px 8px 0;
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.settings-nav-item:hover {
  background: var(--color-teal-tint);
  color: var(--color-teal);
}

.settings-nav-item.active {
  background: linear-gradient(90deg, var(--color-teal-tint) 0%, rgba(233, 239, 240, 0.3) 100%);
  color: var(--color-teal);
  border-left-color: var(--color-teal);
}

.settings-panel {
  flex: 1;
  min-width: 0;
  border-left: 1px solid var(--color-border);
  padding-left: 24px;
}

.settings-section.hidden {
  display: none;
}

.settings-section h3 {
  margin-top: 0;
}

/* Settings > Features (2026-08-09, explicit user request) -- a plain
   label + switch row list, same shape as the other Settings sections'
   tables but simpler since there's no add/edit/delete, just an on/off per
   dashboard widget. */
.feature-toggle-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 16px;
  max-width: 420px;
}

.feature-toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 4px;
  border-bottom: 1px solid var(--color-border);
}

.feature-toggle-row:last-child {
  border-bottom: none;
}

.feature-toggle-label {
  font-size: 14px;
  color: var(--color-text);
}

/* A plain checkbox styled as an iOS-style switch -- the checkbox itself
   stays in the DOM (real, keyboard/screen-reader accessible control) but
   visually hidden; the track/thumb spans are what's actually seen, driven
   entirely by the checkbox's :checked state via sibling selectors, no JS
   needed for the visual state itself. */
.toggle-switch {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 22px;
  flex-shrink: 0;
  cursor: pointer;
}

.toggle-switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}

.toggle-switch-track {
  position: absolute;
  inset: 0;
  background: var(--color-border);
  border-radius: 999px;
  transition: background-color 0.15s ease;
}

.toggle-switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--shadow-xs);
  transition: transform 0.15s ease;
}

.toggle-switch input:checked + .toggle-switch-track {
  background: var(--color-teal);
}

.toggle-switch input:checked + .toggle-switch-track .toggle-switch-thumb {
  transform: translateX(18px);
}

.toggle-switch input:focus-visible + .toggle-switch-track {
  box-shadow: 0 0 0 3px rgba(27, 58, 68, 0.25);
}

/* Settings > Appearance (2026-08-04) */
.theme-toggle {
  display: flex;
  gap: 8px;
  margin: 12px 0 20px;
}

.theme-toggle-option {
  margin-top: 0;
  padding: 7px 14px;
  font-size: 13px;
  font-weight: 600;
  background: var(--color-card);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  cursor: pointer;
  box-shadow: none;
  transition: background-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

.theme-toggle-option:hover {
  box-shadow: var(--shadow-xs);
  transform: translateY(-1px);
}

.theme-toggle-option.active {
  background: var(--color-teal-grad);
  color: #fff;
  border-color: var(--color-teal);
  box-shadow: var(--shadow-glow-teal);
}

/* Settings > Promo Types table (2026-08-04) */
.promo-type-name-link {
  color: var(--color-teal);
  font-weight: 600;
  text-decoration: none;
}

.promo-type-name-link:hover {
  text-decoration: underline;
}

.promo-type-edit-row td {
  padding-top: 0;
}

.promo-type-edit-row.hidden {
  display: none;
}

/* Settings > Videos (2026-08-04) */
.video-edit-row td {
  padding-top: 0;
}

.video-edit-row.hidden {
  display: none;
}

/* Settings > PDF (2026-08-04) */
.pdf-edit-row td {
  padding-top: 0;
}

.pdf-edit-row.hidden {
  display: none;
}

.video-name-pending {
  color: var(--color-text-muted);
  font-style: italic;
}

.previously-known-as {
  margin: -4px 0 12px;
  font-size: 12px;
  color: var(--color-text-muted);
}

.upload-progress {
  display: flex;
  align-items: center;
  gap: 7px;
  color: var(--color-teal);
  font-size: 13px;
  font-weight: 600;
  min-height: 18px;
  margin-top: 8px;
}

/* Always represents an in-progress upload/render/save step -- it's cleared
   to "" the instant that step finishes (see renderAndUploadPdfPages /
   uploadVideoFile in app.js), so :not(:empty) is a safe proxy for "still
   working," unlike .command-status above which also carries terminal
   messages and needs the explicit .is-pending class instead. */
.upload-progress:not(:empty)::before {
  content: "";
  width: 11px;
  height: 11px;
  flex-shrink: 0;
  border: 2px solid var(--color-teal);
  border-right-color: transparent;
  border-radius: 50%;
  animation: paragon-spin 0.7s linear infinite;
}

/* --- Customer detail page (Section 14.4) -------------------------------- */

.back-link {
  display: inline-block;
  margin-top: 2px;
  color: var(--color-text-muted);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
}

/* Same CSS cascade gotcha as .fleet-map-wrap.hidden / .bulk-actions-bar.hidden
   (Task #109): .back-link's own `display` rule has equal specificity to the
   shared .hidden utility, so whichever is declared LATER in this file wins
   regardless of which class was added last in JS. .back-link comes after
   .hidden above, so it silently beat it -- Customer role's "Back to Devices"
   link (which does nothing useful for them anyway, since they're locked to
   one page) was showing even though applyRoleBasedUI() correctly added
   "hidden" to it. Explicit compound override, same fix pattern as before. */
.back-link.hidden {
  display: none;
}

.back-link:hover {
  color: var(--color-teal);
  text-decoration: underline;
}

/* Customer picker (2026-08-09): a plain list of cards, one per Customer a
   multi-location portal contact is linked to -- deliberately minimal, this
   is a one-click-through screen, not a page anyone lingers on. */
.customer-picker-list {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.customer-picker-item {
  display: block;
  padding: 14px 16px;
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  text-decoration: none;
  color: var(--color-text);
  font-weight: 600;
  font-size: 14px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.customer-picker-item:hover {
  border-color: var(--color-teal);
  box-shadow: var(--shadow-xs);
}

.customer-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 28px;
}

.customer-header-main {
  display: flex;
  align-items: center;
  gap: 10px;
}

.customer-header-main h2 {
  margin: 0;
  font-size: 22px;
}

.customer-header-actions {
  display: flex;
  gap: 8px;
}

.customer-header-actions button {
  margin-top: 0;
}

.customer-notes {
  margin: 10px 0 0;
  padding: 10px 12px;
  background: var(--color-teal-tint);
  border-radius: 8px;
  font-size: 13px;
  color: var(--color-text);
  white-space: pre-wrap;
}

.edit-form {
  margin-top: 14px;
  padding: 14px;
  background: var(--color-panel-tint);
  border: 1px solid var(--color-border);
  border-radius: 8px;
}

.edit-form label {
  display: block;
  font-size: 12px;
}

.edit-form input,
.edit-form textarea {
  width: 100%;
  margin-top: 4px;
  margin-bottom: 10px;
  font-family: var(--font-body);
}

.edit-form textarea {
  padding: 10px 12px;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  font-size: 14px;
  resize: vertical;
}

.edit-form-actions {
  display: flex;
  gap: 8px;
}

.edit-form-actions button {
  margin-top: 0;
}

.add-device-form,
.edit-device-form,
.add-display-form {
  margin: 10px 0;
  padding: 12px;
  background: var(--color-panel-tint);
  border: 1px solid var(--color-border);
  border-radius: 8px;
}

.add-device-form label,
.edit-device-form label,
.add-display-form label {
  display: block;
  font-size: 12px;
  margin-top: 8px;
}

.add-device-form label:first-child,
.edit-device-form label:first-child,
.add-display-form label:first-child {
  margin-top: 0;
}

.add-device-form input,
.edit-device-form input,
.add-display-form input {
  width: 100%;
  margin-top: 4px;
}

/* Google Places must-select status line (Section 25 addendum, 2026-08-03).
   Stays muted for both the "Verified: ..." confirmation and the "couldn't
   verify" nudge -- this element carries both messages (see
   attachAddressAutocomplete in app.js), so it can't be colored as an
   unconditional success state. */
.address-status {
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--color-text-muted);
  min-height: 14px;
}

/* Dashboard: "+ Add Customer" sits next to Refresh instead of stacking. */
.devices-header-actions {
  display: flex;
  gap: 8px;
}

/* Customer detail page: "Devices" heading + "+ Add Device". */
.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 20px;
}

.section-header h3 {
  margin: 0;
  font-family: var(--font-heading);
  color: var(--color-teal);
  font-size: 16px;
}

/* The pairing code (PAIR-01) is shown exactly once, right after Add Device
   -- has to be impossible to miss and trivial to copy correctly. */
.pairing-code-panel {
  margin: 12px 0;
  padding: 16px;
  background: linear-gradient(135deg, var(--color-teal-tint) 0%, rgba(233, 239, 240, 0.4) 100%);
  border: 1px solid var(--color-teal);
  border-radius: 12px;
  box-shadow: var(--shadow-sm);
}

.pairing-code-title {
  margin: 0 0 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-teal);
}

.pairing-code-value {
  margin-bottom: 8px;
  padding: 10px 14px;
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--color-teal-dark);
  background: var(--color-card);
  border: 1px dashed var(--color-teal);
  border-radius: 8px;
  text-align: center;
}

.pairing-code-instructions {
  margin: 0 0 12px;
  font-size: 12px;
  color: var(--color-text-muted);
}

/* One card per Device (replaces the old shared-table rows) -- when a
   Customer has multiple Devices, each is now its own clearly bordered
   block instead of rows that can blur together. No overflow:hidden here on
   purpose: the Actions dropdown (.action-menu-list) is position:absolute
   relative to the header, and clipping the card would cut it off. */
.device-card-list {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 14px;
}

.device-card {
  border: 1px solid var(--color-border);
  border-radius: 14px;
  background: var(--color-card);
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.15s ease;
}

.device-card:hover {
  box-shadow: var(--shadow-md);
}

.device-card-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 16px;
  padding: 14px 18px;
  background: linear-gradient(135deg, var(--color-panel-tint-2) 0%, var(--color-panel-tint-3) 100%);
  border-bottom: 1px solid var(--color-border);
  border-top-left-radius: 14px;
  border-top-right-radius: 14px;
}

.device-card-title {
  display: flex;
  align-items: center;
  gap: 8px;
}

.device-card-title h4 {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 16px;
  color: var(--color-teal);
}

.device-card-meta {
  font-size: 12px;
  color: var(--color-text-muted);
  flex: 1;
  min-width: 160px;
}

.device-card-actions {
  margin-left: auto;
  text-align: right;
}

/* Buttons default to a top margin elsewhere in this stylesheet (form
   buttons stacked vertically) -- Edit sits inline next to the Actions
   dropdown here instead, same as .action-menu-trigger already resets. */
.device-card-actions .edit-device-button {
  margin-top: 0;
  margin-right: 8px;
}

.device-card-body {
  padding: 16px 18px;
}

/* Left/Middle/Right drag-to-reposition (Section 25 addendum, 2026-08-04).
   Restyled 2026-08-09 (user report: "too blended in with the background...
   make it look like TVs. empty ones can look like empty"). Each slot is now
   shaped like an actual TV -- thick dark bezel, a little foot/stand below,
   16:10 screen proportions -- and the "screen" itself is intentionally a
   FIXED dark color regardless of the page's own Light/Dark theme, the same
   way a real television's screen doesn't change with room lighting. Filled
   = screen "on" (glowing teal gradient, glass glare, small green power LED
   next to the position label). Empty = screen "off" (flat near-black, dim
   muted label) -- meant to read as unmistakably different at a glance, not
   just a lighter shade of the same box. */
.monitor-position-row {
  display: flex;
  gap: 14px;
  margin: 10px 0 26px;
}

.monitor-slot {
  position: relative;
  flex: 1;
  min-width: 0;
  aspect-ratio: 16 / 10;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 10px;
  border-radius: 10px;
  border: 3px solid;
  text-align: center;
  transition: box-shadow 0.15s ease, transform 0.1s ease, border-color 0.15s ease;
}

/* The stand -- a small foot below the "screen" on every slot, so the TV
   silhouette reads immediately, before you even notice on/off state. */
.monitor-slot::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -11px;
  transform: translateX(-50%);
  width: 32%;
  height: 6px;
  border-radius: 3px;
  /* Fixed darker maroon (2026-08-09, user report: "very blended with
     background") -- was var(--color-border), a near-invisible neutral tint
     that all but disappeared against the card. A shade darker than the
     bezel's #6e2a22 so it still reads as its own piece (the stand), not a
     copy-paste of the border. */
  background: #5c231c;
  box-shadow: var(--shadow-xs);
}

/* --- Empty slot: screen "off" ------------------------------------------ */
.monitor-slot.empty {
  /* Fixed brand maroon (2026-08-09, user request: "border maroon used on my
     logo") -- the raw logo hex, not var(--color-maroon), since that variable
     gets lightened for text-contrast reasons in dark mode (2026-08-06) and
     this screen is deliberately theme-independent, same as the rest of this
     component. */
  border-color: #6e2a22;
  background: linear-gradient(165deg, #24282a 0%, #16181a 100%);
  box-shadow: var(--shadow-md);
}

.monitor-slot.empty .monitor-slot-label {
  color: #b0b6b9;
}

.monitor-slot.empty .monitor-slot-name {
  color: #e4e7e8;
}

/* --- Filled slot: screen "on" ------------------------------------------ */
.monitor-slot.filled {
  /* Same fixed logo maroon as the empty state (2026-08-09 user request:
     "make the border for filled up tvs also maroon") -- on/off is now
     conveyed entirely by the screen glow + power LED, not by border color. */
  border-color: #6e2a22;
  background: linear-gradient(165deg, #2e5561 0%, #1b3a44 55%, #122730 100%);
  box-shadow: var(--shadow-md);
  cursor: grab;
}

/* Soft glass glare in the screen's top-left corner -- not literal, just
   enough to read as glossy glass rather than a flat painted box. Rounds
   itself to match the parent (no overflow:hidden on the parent needed,
   which would otherwise clip the stand/delete button below/above it). */
.monitor-slot.filled::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(120% 80% at 15% 0%, rgba(255, 255, 255, 0.16), transparent 55%);
  pointer-events: none;
}

/* Power LED -- small glowing dot next to the position label, the same
   visual shorthand as an actual TV's standby/on light. */
.monitor-slot.filled .monitor-slot-label::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 5px;
  border-radius: 50%;
  background: #4ade80;
  box-shadow: 0 0 4px 1px rgba(74, 222, 128, 0.7);
  vertical-align: middle;
}

.monitor-slot.filled:hover {
  box-shadow: var(--shadow-lg);
  transform: translateY(-1px);
}

.monitor-slot.filled:active {
  cursor: grabbing;
  transform: translateY(0);
}

/* Undo an accidentally-created Display (2026-08-04) -- small, out of the
   way until hovered, so it doesn't compete with the drag affordance that's
   the primary purpose of this box. Offset increased to clear the thicker
   bezel (2026-08-09 restyle). */
.monitor-slot-delete {
  position: absolute;
  top: -9px;
  right: -9px;
  margin-top: 0;
  width: 18px;
  height: 18px;
  padding: 0;
  line-height: 16px;
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  /* --color-maroon-grad (not var(--color-maroon) directly): this is a solid
     WHITE-text fill, and --color-maroon is lightened for dark-mode text
     readability (2026-08-06) -- white-on-light-coral would fail contrast.
     The *-grad tokens stay a fixed dark shade in both themes specifically
     for fills like this one. */
  background: var(--color-maroon-grad);
  border: 1px solid #fff;
  border-radius: 999px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.1s ease;
  z-index: 1;
}

.monitor-slot.filled:hover .monitor-slot-delete {
  opacity: 1;
}

.monitor-slot-delete:hover {
  background: var(--color-maroon-grad-hover);
}

/* Drop target while dragging -- a maroon glow ring layered on top of
   whichever screen state the slot is already in, rather than swapping the
   background color outright (which used to fight with the new dark "off"
   screen look). */
.monitor-slot.drag-over {
  border-color: var(--color-maroon-light);
  box-shadow: 0 0 0 3px rgba(224, 145, 125, 0.4), var(--shadow-md);
}

.monitor-slot-label {
  display: block;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #9aa2a6;
}

.monitor-slot.filled .monitor-slot-label {
  color: #9dcbd5;
}

.monitor-slot-name {
  display: block;
  margin-top: 2px;
  max-width: 100%;
  font-size: 12px;
  font-weight: 600;
  color: #e8e6e1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.monitor-slot.empty .monitor-slot-name {
  font-weight: 500;
}

/* Schedule-gap warning (Section 25 addendum, 2026-08-04): a Display no
   longer has fallback/default content, so an uncovered day/time is a real
   gap in what will actually play -- this has to read as an error, not a
   passive hint. */
.schedule-gap-warning {
  margin: 0 0 10px;
  padding: 8px 10px;
  background: var(--color-danger-tint);
  border: 1px solid var(--color-maroon);
  border-radius: 8px;
}

.schedule-gap-warning-title {
  margin: 0 0 4px;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-maroon);
}

.schedule-gap-warning ul {
  margin: 0 0 4px;
  padding-left: 18px;
  font-size: 12px;
  color: var(--color-maroon);
}

.schedule-gap-warning-hint {
  margin: 0;
  font-size: 11px;
  color: var(--color-text-muted);
}

.schedule-gap-ok {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 600;
  color: var(--color-success-text);
}

.device-subdetail {
  display: grid;
  grid-template-columns: 2fr 1fr 1.5fr;
  gap: 20px;
}

.subdetail-col h4 {
  margin: 0 0 6px;
  font-family: var(--font-heading);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-teal);
}

.subdetail-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.subdetail-list li {
  margin-bottom: 8px;
  font-size: 13px;
  line-height: 1.5;
}

.subdetail-list li:last-child {
  margin-bottom: 0;
}

.subdetail-meta {
  color: var(--color-text-muted);
  font-size: 12px;
}

.subdetail-empty {
  margin: 0;
  font-size: 13px;
  color: var(--color-text-muted);
}

/* Shown instead of the Actions "..." dropdown for View Only/Customer/Vendor
   admins (2026-08-09) -- every item in that menu is a write, so it's
   omitted entirely rather than shown-but-disabled. */
.actions-readonly {
  font-size: 12px;
  color: var(--color-text-muted);
  font-style: italic;
}

.device-error-line {
  margin: 0 0 6px;
  font-size: 13px;
  color: var(--color-maroon);
}

.device-error-line:last-child {
  margin-bottom: 0;
}

/* --- Per-Display schedule editor (Section 14.6), inside a Device's
   expandable subdetail -- one column per Display, matching the reference
   layout: paste a link, pick days, pick a time window, per slot. */

.manage-schedule-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 10px;
  padding: 8px 14px;
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
  color: var(--color-teal);
  background: var(--color-teal-tint);
  border: 1px solid var(--color-teal);
  border-radius: 8px;
}

.manage-schedule-toggle:hover {
  /* Same reasoning as .monitor-slot-delete above: a solid white-text fill
     needs the fixed-shade *-grad token, not var(--color-teal) (lightened
     for dark-mode text/border readability, which would fail contrast with
     white here). */
  background: var(--color-teal-grad);
  color: #fff;
}

.schedule-editor {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px dashed var(--color-border);
}

.schedule-columns {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  padding-bottom: 4px;
}

.schedule-column {
  flex: 0 0 260px;
  padding: 12px;
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: 10px;
}

.schedule-column h4 {
  margin: 0 0 10px;
  font-family: var(--font-heading);
  color: var(--color-maroon);
  font-size: 14px;
}

.schedule-column .add-slot-button {
  width: 100%;
  margin: 0 0 12px;
  padding: 7px 10px;
  font-size: 12px;
  background: var(--color-maroon-grad);
  color: #fff;
  border: none;
}

.schedule-column .add-slot-button:hover {
  background: var(--color-maroon-grad-hover);
  box-shadow: var(--shadow-glow-maroon);
}

.slot-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.slot-card {
  padding: 10px;
  background: var(--color-teal-tint);
  border: 1px solid var(--color-teal-light);
  border-radius: 8px;
}

.slot-card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}

.slot-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--color-teal);
}

.slot-source-toggle {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.source-toggle-option {
  margin-top: 0;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 600;
  background: var(--color-card);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  cursor: pointer;
}

.source-toggle-option.active {
  background: var(--color-maroon-grad);
  color: #fff;
  border-color: var(--color-maroon);
  box-shadow: var(--shadow-glow-maroon);
}

.slot-link-input,
.slot-promo-type-select,
.slot-video-select,
.slot-pdf-select {
  width: 100%;
  margin: 0 0 8px;
  padding: 7px 8px;
  font-size: 12px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-family: var(--font-body);
  background: var(--color-card);
}

.slot-rotation-label {
  display: block;
}

.slot-rotation-label.hidden {
  display: none;
  margin: 0 0 8px;
  font-size: 10px;
  font-weight: 600;
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.slot-rotation-select {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 7px 8px;
  font-size: 12px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-family: var(--font-body);
  background: var(--color-card);
  color: var(--color-text);
}

.slot-days-label,
.slot-time-label {
  display: block;
  margin: 0 0 4px;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}

.slot-days {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 8px;
}

.day-chip {
  margin-top: 0;
  padding: 4px 7px;
  font-size: 11px;
  font-weight: 600;
  background: var(--color-card);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  cursor: pointer;
}

.day-chip.active {
  background: var(--color-maroon-grad);
  color: #fff;
  border-color: var(--color-maroon);
  box-shadow: var(--shadow-glow-maroon);
}

/* Read-only schedule view (2026-08-09, View Only/Customer/Vendor admins):
   these get `disabled` to block edits, not because something's loading --
   same gotcha as .metrics-carousel-nav:disabled above, override the generic
   button:disabled blue-gray fill + spinner so a selected day/source still
   reads as selected (maroon fill) and the whole card looks like "can't
   touch this" rather than "in progress." */
.source-toggle-option:disabled,
.day-chip:disabled {
  background: var(--color-card);
  color: var(--color-text-muted);
  opacity: 0.7;
  cursor: default;
}

.source-toggle-option.active:disabled,
.day-chip.active:disabled {
  background: var(--color-maroon-grad);
  color: #fff;
  opacity: 0.85;
}

.source-toggle-option:disabled::after,
.day-chip:disabled::after {
  content: none;
}

.slot-time-window {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
}

.slot-time-window label {
  flex: 1;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}

.slot-time-window input[type="time"] {
  display: block;
  width: 100%;
  margin-top: 3px;
  padding: 5px 6px;
  font-size: 12px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-family: var(--font-body);
  background: var(--color-card);
}

.slot-actions {
  display: flex;
  gap: 6px;
}

.slot-actions button {
  margin-top: 0;
  padding: 5px 10px;
  font-size: 11px;
}

.slot-error {
  margin: 6px 0 0;
  font-size: 11px;
  min-height: 0;
}

/* --- Global "something is loading" bar (2026-08-05) ---------------------
   Explicit user request: whenever anything is loading, it needs to be
   obvious so nobody clicks away mid-request. This one element covers every
   API call in the whole console -- app.js wraps window.fetch once and
   toggles .active on #global-loading-bar for the duration of any in-flight
   request, so this didn't need touching all ~30 individual fetch call
   sites. Fixed to the very top of the viewport, above everything else. */
.global-loading-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  z-index: 9999;
  background: transparent;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.global-loading-bar.active {
  opacity: 1;
}

.global-loading-bar.active::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 40%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--color-teal) 0%, var(--color-accent) 55%, var(--color-teal-light) 100%);
  box-shadow: 0 0 10px rgba(231, 165, 58, 0.6);
  animation: paragon-bar-sweep 1.1s ease-in-out infinite;
}

/* Reusable standalone spinner (not tied to any specific button/status
   element) -- available for anything added later that needs one. */
.spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: paragon-spin 0.7s linear infinite;
  vertical-align: middle;
  opacity: 0.85;
}

/* --- Admin homepage dashboard metrics (2026-08-06) -----------------------
   "Current Live Data": Online/Offline count, Average Uptime %, Average time
   offline. Sits above the existing Fleet table on the same Dashboard view
   (not a separate page) -- it's the first thing rendered on login. Backed
   by GET /admin/dashboard; charts drawn with Chart.js (loaded via CDN in
   index.html, same "no build step" pattern as pdf.js). */
/* Horizontal carousel (2026-08-09, explicit user request: "add them into a
   horizontal carousel. This will clear room") -- replaces the old
   auto-wrapping grid. Prev/Next buttons scroll the track by roughly one
   card's width (see scrollMetricsCarousel() in app.js); scroll-snap makes
   trackpad/touch swiping land cleanly on a card boundary too, so the
   buttons are a convenience, not the only way to move it. */
.metrics-carousel {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 18px;
}

.metrics-track {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  flex: 1 1 auto;
  min-width: 0;
  padding-bottom: 4px; /* keeps the horizontal scrollbar (where the OS shows one) off the cards' own shadow */
  /* Modern colored/rounded scrollbar (2026-08-09, "do something modern") --
     kept VISIBLE and draggable, not hidden: it's a reliable way to scroll
     with a plain mouse (no trackpad/touch gestures), and a fully-hidden
     scrollbar plus a big clickable overlay was tried first and ended up
     blocking scrolling entirely (see the removed .metrics-carousel-fade
     rules this replaced) -- reverted to something safer. */
  scrollbar-width: thin;
  scrollbar-color: var(--color-teal) transparent; /* Firefox */
}

.metrics-track::-webkit-scrollbar {
  height: 6px;
}

.metrics-track::-webkit-scrollbar-track {
  background: transparent;
}

.metrics-track::-webkit-scrollbar-thumb {
  background: var(--color-teal);
  border-radius: 999px;
}

.metrics-track .metric-card {
  flex: 0 0 260px;
  scroll-snap-align: start;
}

.metrics-carousel-nav {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  margin-top: 0;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-card);
  color: var(--color-text);
  font-size: 18px;
  line-height: 1;
  box-shadow: var(--shadow-xs);
  transition: box-shadow 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
}

.metrics-carousel-nav:hover {
  box-shadow: var(--shadow-sm);
  border-color: var(--color-teal);
  transform: none; /* overrides the base button's hover lift -- a tiny circular arrow shouldn't jump */
}

.metrics-carousel-nav:disabled {
  /* background/color explicitly repeated from the non-disabled rule above
     -- button:disabled's own background/color (a filled blue-gray) is
     MORE specific than a bare .metrics-carousel-nav selector, so without
     this the disabled circle would flip to a solid fill instead of just
     fading, right when it needs to look like "nothing to do here," not
     "something is loading." */
  background: var(--color-card);
  color: var(--color-text);
  opacity: 0.35;
  box-shadow: none;
  cursor: default;
}

.metrics-carousel-nav:disabled::after {
  content: none; /* overrides the base button's disabled-spinner -- there's no "in progress" state here, just "nothing more to scroll to" */
}

/* Restyled 2026-08-09 (user request: "make the metrics boxes on the
   dashboard at the top the same style as filled in ones") -- matches the
   filled TV1/TV2/TV3 monitor-slot treatment: fixed logo maroon border, the
   same dark teal glow gradient "screen" background, and a matching glass
   glare highlight. Like those slots, the background is deliberately FIXED
   dark regardless of the page's own Light/Dark theme, so every text color
   inside had to move off the theme-coupled --color-text/--color-teal
   variables onto fixed light values that stay readable against it. */
.metric-card {
  position: relative;
  padding: 18px 20px;
  border: 3px solid #6e2a22;
  border-radius: 10px;
  background: linear-gradient(165deg, #2e5561 0%, #1b3a44 55%, #122730 100%);
  box-shadow: var(--shadow-md);
  transition: box-shadow 0.15s ease, transform 0.1s ease;
  overflow: hidden;
}

.metric-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(120% 80% at 15% 0%, rgba(255, 255, 255, 0.14), transparent 55%);
  pointer-events: none;
}

.metric-card:hover {
  box-shadow: var(--shadow-lg);
  transform: translateY(-1px);
}

.metric-card > * {
  position: relative;
}

.metric-label {
  margin: 0 0 12px;
  font-family: var(--font-heading);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #9dcbd5;
}

.metric-big-number {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 26px;
  font-weight: 700;
  color: #e8e6e1;
  line-height: 1.15;
}

.metric-sub {
  margin: 2px 0 10px;
  font-size: 11px;
  font-weight: 600;
  color: #b0b6b9;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.metric-sub:last-child {
  margin-bottom: 0;
}

.metric-chart-row {
  display: flex;
  align-items: center;
  gap: 18px;
}

.metric-donut-wrap {
  position: relative;
  width: 100px;
  height: 100px;
  flex-shrink: 0;
}

.metric-chart-legend {
  flex: 1;
  min-width: 0;
}

/* Fixed (not theme-coupled) colors, same reasoning as the card background
   itself -- see the 2026-08-09 restyle note above .metric-card. */
.metric-legend-online {
  color: #4ade80;
}

.metric-legend-offline {
  color: #e0917d;
  font-size: 22px;
}

.metric-bar-wrap {
  position: relative;
  margin-top: 10px;
  height: 14px;
}

.metric-footnote {
  margin: 8px 0 0;
  font-size: 11px;
  color: #b0b6b9;
  min-height: 14px;
}

.metric-temp-hi-lo {
  margin: 0 0 4px;
  font-size: 13px;
  font-weight: 600;
  color: #e8e6e1;
}

.metric-temp-hi-lo:last-child {
  margin-bottom: 0;
}

/* Fleet Map (2026-08-09, explicit user request) -- see the comment above
   renderFleetMap() in app.js for the full cost/design reasoning. This
   wrapper uses the ordinary card language (border/shadow, theme-aware),
   NOT the fixed-dark "TV screen" treatment used for the metric cards and
   monitor slots -- a map already has its own visual weight (roads, water,
   labels), so it reads better sitting on a plain card. */
.fleet-map-wrap {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--color-border);
  border-radius: 14px;
  overflow: hidden;
  /* Bumped from --shadow-sm, then again to --shadow-lg (2026-08-09, user
     report: "its blended in with the white background" / "I DONT SEE IT" --
     --shadow-md still wasn't a visible enough jump, went straight to the
     same strength the login/dashboard cards themselves use). */
  box-shadow: var(--shadow-lg);
  margin: 8px 0 24px;
}

/* Real bug (2026-08-09, user report: "'map' is still visible even after
   turning it off"): `.fleet-map-wrap` and the shared `.hidden` utility
   (`display: none`, defined near the top of this file) are BOTH plain
   single-class selectors -- equal specificity. When two rules tie on
   specificity, the one that appears LATER in the file wins for whichever
   property they both set, and `.fleet-map-wrap`'s own `display: flex` is
   declared far later than `.hidden`'s `display: none` -- so `class="fleet-
   map-wrap hidden"` was actually rendering as flex (visible) the whole
   time, not none. This compound selector (two classes, higher specificity
   than either alone) forces the override regardless of source order. */
.fleet-map-wrap.hidden {
  display: none;
}

.fleet-map {
  flex: 1 1 auto;
  min-width: 0;
  height: 360px;
}

/* Side list (2026-08-09, explicit user request): "wherever the map is
   zoomed into" -- kept in the SAME card as the map (one shared border/
   shadow/rounded-corner unit) rather than a separate box below it, so it
   visually reads as one instrument, not two unrelated widgets. Fixed
   width, map takes the rest via .fleet-map's flex: 1 1 auto above. */
.fleet-map-sidebar {
  flex: 0 0 240px;
  display: flex;
  flex-direction: column;
  height: 360px;
  border-left: 1px solid var(--color-border);
  background: var(--color-card);
  /* Real (non-inset) shadow cast LEFTWARD onto the map (2026-08-09, user
     report: "the bar next to the map... blended in with the white
     background", then "I DONT SEE IT" on the first, subtler inset attempt)
     -- this one visibly overlaps the map itself, like the sidebar is a
     raised panel sitting in front of it, rather than a barely-there tint
     along a 1px border. position:relative + z-index so it draws on top of
     the map's own tiles instead of underneath them; it stays within
     .fleet-map-wrap's bounds (spilling onto a sibling element, not outside
     the wrap itself), so the wrap's overflow:hidden doesn't clip it. */
  position: relative;
  z-index: 2;
  box-shadow: -10px 0 18px -6px rgba(18, 30, 34, 0.55);
}

.fleet-map-sidebar-header {
  margin: 0;
  padding: 12px 14px;
  font-family: var(--font-heading);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}

.fleet-map-sidebar-empty {
  margin: 0;
  padding: 14px;
  font-size: 12px;
  color: var(--color-text-muted);
}

.fleet-map-sidebar-list {
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-y: auto;
  flex: 1 1 auto;
  /* Tint (2026-08-09, user request, strengthened same day: "add more
     tint" -- --color-panel-tint read as too close to white) so the list
     area reads as its own zone under the header, distinct from the
     sidebar's plain white background. --color-panel-tint-3 is the
     strongest/warmest of this file's 3 tint tokens (used elsewhere for the
     schedule editor's day-column background); still a token with a correct
     dark-theme value, not a one-off hardcoded color. */
  background: var(--color-panel-tint-3);
}

/* Overrides the base `button` rule's margin-top/box-shadow/color/font
   (this project's default button is a filled teal pill, wrong for a plain
   list row) -- every property that rule sets gets an explicit reset here
   so nothing bleeds through. */
.fleet-map-sidebar-item {
  display: block;
  width: 100%;
  margin: 0;
  padding: 10px 14px;
  border: none;
  border-bottom: 1px solid var(--color-border);
  border-radius: 0;
  background: none;
  color: inherit;
  font-family: var(--font-body);
  font-size: inherit;
  font-weight: inherit;
  text-align: left;
  cursor: pointer;
  box-shadow: none;
  transition: background 0.1s ease;
}

.fleet-map-sidebar-item:hover,
.fleet-map-sidebar-item:active {
  background: var(--color-panel-tint-2);
  box-shadow: none;
  transform: none;
}

.fleet-map-sidebar-item-customer {
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fleet-map-sidebar-item-device {
  margin: 2px 0 0;
  font-size: 11px;
  color: var(--color-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fleet-map-sidebar-item-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
}

.fleet-map-sidebar-item-tv-count {
  font-size: 11px;
  font-weight: 600;
  color: var(--color-text-muted);
}

/* Marker InfoWindow content -- Google renders this inside its OWN white
   bubble chrome regardless of the page's Light/Dark theme (no supported
   way to theme the bubble itself), so unlike the rest of this file, colors
   here are deliberately FIXED (dark-on-white), not var(--color-text) --
   the same "background doesn't follow page theme" lesson as the metric
   cards, just inverted (fixed white/light here instead of fixed dark). */
.fleet-map-info {
  min-width: 200px;
  max-width: 240px;
  font-family: var(--font-body);
}

.fleet-map-info-customer {
  margin: 0 0 2px;
  font-family: var(--font-heading);
  font-size: 14px;
  font-weight: 700;
  color: #1b3a44;
}

.fleet-map-info-device {
  margin: 0 0 6px;
  font-size: 12px;
  color: #6b6f73;
}

.fleet-map-info-address {
  margin: 0 0 8px;
  font-size: 12px;
  color: #6b6f73;
}

.fleet-map-info-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.fleet-map-info-tv-count {
  font-size: 12px;
  font-weight: 600;
  color: #6b6f73;
}

.fleet-map-info-button {
  display: inline-block;
  padding: 6px 12px;
  border-radius: 8px;
  background: var(--color-teal-grad);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  text-decoration: none;
}

.fleet-map-info-button:hover {
  background: var(--color-teal-grad-hover);
}
