/* ============================================================
   SlimRadio "Broadcast Dark" Design System
   Phase 8e UI Redesign
   ============================================================ */

:root {
  /* Matches the <meta name="color-scheme"> tag in base.html -- tells the
     browser this site is intentionally dark, so Chrome's forced/auto
     dark mode (and native form control theming) doesn't try to
     re-color it on top of our own palette. */
  color-scheme: dark;

  --bg:          #080c10;
  --surface:     #0f1419;
  --surface-2:   #161d27;
  --border:      #1e2a38;
  --text:        #e8edf2;
  --text-2:      #6b7d8e;
  --text-3:      #3d4f5f;
  --accent:      #e8b84b;
  --accent-dim:  #b8882b;

  --green:     #22c55e;
  --green-dim: #188a42;
  --red:       #ef4444;
  --blue:      #60a5fa;
  --blue-dim:  #4374af;
  --orange:    #f59e0b;
  --purple:    #a78bfa;
  --grey:      #6b7d8e;

  /* Party Mode -- pink chosen directly by Jesse (#fb367e), picked via a
     live color sample against the actual rendered grid. --party-dim
     follows the same ~70%-per-channel scaling used for --blue/--blue-dim. */
  --party:     #fb367e;
  --party-dim: #b02658;

  --sidebar-width: 160px;
  --font-ui: -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-mono: "SF Mono", "Fira Code", "Courier New", monospace;
}

/* ============================================================
   Reset & Base
   ============================================================ */

*, *::before, *::after {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
}

body {
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:hover {
  color: var(--accent-dim);
  text-decoration: underline;
}

h1 {
  font-size: 20px;
  font-weight: 600;
  margin: 0 0 20px 0;
  color: var(--text);
  letter-spacing: -0.3px;
}

h2 {
  font-size: 15px;
  font-weight: 600;
  margin: 28px 0 12px 0;
  color: var(--text);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

h3 {
  font-size: 13px;
  font-weight: 600;
  margin: 20px 0 8px 0;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

p {
  margin: 0 0 12px 0;
  color: var(--text);
}

hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 28px 0;
}

code {
  font-family: var(--font-mono);
  font-size: 12px;
  background: var(--surface-2);
  color: var(--accent);
  padding: 2px 6px;
  border-radius: 3px;
  word-break: break-all;
}

strong {
  font-weight: 600;
  color: var(--text);
}

/* ============================================================
   Layout: Sidebar + Main
   ============================================================ */

.layout {
  display: flex;
  min-height: 100vh;
}

/* Sidebar */
.sidebar {
  width: var(--sidebar-width);
  min-width: var(--sidebar-width);
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 200;
  overflow-y: auto;
}

.sidebar-brand {
  padding: 10px 20px;
  font-size: 19px;
  font-weight: 700;
  color: var(--accent);
  letter-spacing: -0.5px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.sidebar-nav {
  flex: 1;
  padding: 12px 0;
  overflow-y: auto;
}

.sidebar-nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 20px;
  color: var(--text-2);
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  border-left: 3px solid transparent;
  transition: color 0.15s, background 0.15s;
}

.sidebar-nav a:hover {
  color: var(--text);
  background: var(--surface-2);
  text-decoration: none;
}

.sidebar-nav a.active {
  color: var(--accent);
  border-left-color: var(--accent);
  background: rgba(232, 184, 75, 0.07);
}

.sidebar-nav a svg {
  flex-shrink: 0;
  opacity: 0.7;
}

.sidebar-nav a.active svg {
  opacity: 1;
}

.sidebar-nav .nav-section {
  padding: 16px 20px 4px 20px;
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: #c0d0e0;
}

.sidebar-footer {
  padding: 16px 20px;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}

.sidebar-footer .user-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 2px;
}

.sidebar-footer .user-role {
  font-size: 12px;
  color: var(--text-2);
  margin-bottom: 10px;
}

.sidebar-footer .user-role .badge {
  font-size: 12px;
}

.sidebar-footer a {
  display: block;
  font-size: 13px;
  color: var(--text-2);
  padding: 3px 0;
}

.sidebar-footer a:hover {
  color: var(--text);
}

/* Keeps this link's layout space reserved even when hidden, so the
   footer's height (and therefore its border-top separator's position,
   pixel-calibrated against the portal page's own content lines -- see
   portal.css) never shifts based on which links happen to render. */
.sidebar-link-hidden {
  visibility: hidden;
}

/* Sidebar overlay for mobile */
.sidebar-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.6);
  z-index: 150;
}

/* Main wrapper */
.main-wrapper {
  margin-left: var(--sidebar-width);
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* Mobile header */
.mobile-header {
  display: none;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 12px 16px;
  align-items: center;
  gap: 12px;
  position: sticky;
  top: 0;
  z-index: 100;
}

.mobile-title {
  font-size: 16px;
  font-weight: 700;
  color: var(--accent);
}

.hamburger {
  background: none;
  border: none;
  color: var(--text);
  font-size: 20px;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
  line-height: 1;
}

.hamburger:hover {
  background: var(--surface-2);
}

/* Main content */
.main-content {
  padding: 28px 32px;
  flex: 1;
  max-width: 1200px;
  width: 100%;
}

/* Mobile responsive */
@media (max-width: 767px) {
  .sidebar {
    transform: translateX(-100%);
    transition: transform 0.25s ease;
  }

  body.sidebar-open .sidebar {
    transform: translateX(0);
  }

  body.sidebar-open .sidebar-overlay {
    display: block;
  }

  .mobile-header {
    display: flex;
  }

  .main-wrapper {
    margin-left: 0;
  }

  .main-content {
    padding: 16px;
  }

  /* Prevent iOS Safari from auto-zooming on input focus (triggers when font-size < 16px) */
  .form-group input[type="text"],
  .form-group input[type="password"],
  .form-group input[type="number"],
  .form-group input[type="email"],
  .form-group input[type="file"],
  .form-group select,
  .form-group textarea,
  .filter-form input[type="text"],
  .filter-form select {
    font-size: 16px;
  }
}

/* ============================================================
   Session Warning
   ============================================================ */

#session-warning {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  background: rgba(232, 184, 75, 0.12);
  border-top: 2px solid var(--accent);
  color: var(--accent);
  padding: 10px 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 13px;
}

#session-warning button {
  background: var(--accent);
  color: var(--bg);
  border: none;
  border-radius: 4px;
  padding: 3px 10px;
  font-size: 12px;
  cursor: pointer;
  font-weight: 600;
}

/* ============================================================
   Flash Messages
   ============================================================ */

.flash-messages {
  margin-bottom: 20px;
}

.flash {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  border-radius: 6px;
  margin-bottom: 8px;
  font-size: 13px;
  border-left: 4px solid;
  opacity: 1;
  transition: opacity 0.4s ease;
}

.flash.fade-out {
  opacity: 0;
}

.flash-close {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  padding: 0;
  flex-shrink: 0;
  opacity: 0.6;
}

.flash-close:hover {
  opacity: 1;
}

.flash-success {
  background: rgba(34, 197, 94, 0.1);
  border-color: var(--green);
  color: #86efac;
}

.flash-success .flash-close {
  color: #86efac;
}

.flash-error {
  background: rgba(239, 68, 68, 0.1);
  border-color: var(--red);
  color: #fca5a5;
}

.flash-error .flash-close {
  color: #fca5a5;
}

.flash-warning {
  background: rgba(245, 158, 11, 0.1);
  border-color: var(--orange);
  color: #fcd34d;
}

.flash-warning .flash-close {
  color: #fcd34d;
}

.flash-info {
  background: rgba(96, 165, 250, 0.1);
  border-color: var(--blue);
  color: #93c5fd;
}

.flash-info .flash-close {
  color: #93c5fd;
}

.flash-danger {
  background: rgba(239, 68, 68, 0.1);
  border-color: var(--red);
  color: #fca5a5;
}

.flash-danger .flash-close {
  color: #fca5a5;
}

/* ============================================================
   Status Badges
   ============================================================ */

@keyframes pulse-recording {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.5; }
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-family: var(--font-mono);
  white-space: nowrap;
}

/* Role badges */
.badge-admin {
  background: rgba(167, 139, 250, 0.2);
  color: var(--purple);
  border: 1px solid rgba(167, 139, 250, 0.3);
}

.badge-editor {
  background: rgba(96, 165, 250, 0.2);
  color: var(--blue);
  border: 1px solid rgba(96, 165, 250, 0.3);
}

.badge-viewer {
  background: rgba(107, 125, 142, 0.2);
  color: var(--grey);
  border: 1px solid rgba(107, 125, 142, 0.3);
}

/* Recording status badges */
.badge-recording {
  background: rgba(239, 68, 68, 0.15);
  color: var(--red);
  border: 1px solid rgba(239, 68, 68, 0.3);
  animation: pulse-recording 1.5s ease-in-out infinite;
}

.badge-recorded {
  background: rgba(34, 197, 94, 0.1);
  color: #34d399;
  border: 1px solid rgba(34, 197, 94, 0.2);
}

.badge-postprocessing {
  background: rgba(245, 158, 11, 0.15);
  color: var(--orange);
  border: 1px solid rgba(245, 158, 11, 0.3);
}

.badge-ready_to_upload {
  background: rgba(96, 165, 250, 0.15);
  color: var(--blue);
  border: 1px solid rgba(96, 165, 250, 0.3);
}

.badge-recording_failed {
  background: rgba(239, 68, 68, 0.15);
  color: var(--red);
  border: 1px solid rgba(239, 68, 68, 0.3);
}

.badge-skipped {
  background: rgba(107, 125, 142, 0.15);
  color: var(--grey);
  border: 1px solid rgba(107, 125, 142, 0.3);
}

.badge-scheduled {
  background: rgba(61, 79, 95, 0.3);
  color: var(--text-3);
  border: 1px solid var(--border);
}

.badge-deleted {
  background: rgba(61, 79, 95, 0.15);
  color: var(--text-3);
  border: 1px solid var(--border);
}

.badge-uploaded {
  background: rgba(34, 197, 94, 0.1);
  color: #34d399;
  border: 1px solid rgba(34, 197, 94, 0.2);
}

.badge-upload_started {
  background: rgba(251, 191, 36, 0.15);
  color: var(--amber);
  border: 1px solid rgba(251, 191, 36, 0.3);
}

.badge-uploading {
  background: rgba(96, 165, 250, 0.15);
  color: var(--blue);
  border: 1px solid rgba(96, 165, 250, 0.3);
  animation: pulse-recording 1.5s ease-in-out infinite;
}

.badge-obs_scene_change_detected {
  background: rgba(167, 139, 250, 0.15);
  color: var(--purple);
  border: 1px solid rgba(167, 139, 250, 0.3);
}

.badge-upload_failed {
  background: rgba(239, 68, 68, 0.15);
  color: var(--red);
  border: 1px solid rgba(239, 68, 68, 0.3);
}

/* Service state badges */
.badge-active {
  background: rgba(52, 211, 153, 0.15);
  color: #34d399;
  border: 1px solid rgba(52, 211, 153, 0.4);
}

.badge-inactive {
  background: rgba(107, 125, 142, 0.15);
  color: var(--grey);
  border: 1px solid rgba(107, 125, 142, 0.3);
}

.badge-not-recording {
  background: rgba(107, 125, 142, 0.12);
  color: #8a9bab;
  border: 1px solid rgba(107, 125, 142, 0.25);
}

/* Activity page toolbar */
.activity-toolbar {
  border-radius: 8px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 16px;
}

.activity-toolbar .range-or {
  color: var(--text-2);
  font-size: 12px;
}

.activity-toolbar .date-range-input {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font-size: 13px;
  font-family: var(--font-mono);
  padding: 4px 8px;
  outline: none;
  width: 108px;
}

.activity-toolbar .date-range-input:focus {
  border-color: var(--amber);
}

.activity-toolbar .date-range-input.field-error {
  border-color: var(--red);
}

.activity-date-error {
  color: var(--red);
  font-size: 13px;
  margin: -12px 0 12px;
}

/* Activity page event labels — title case, not all-caps */
.activity-label .badge {
  text-transform: capitalize;
  letter-spacing: 0;
}

/* Badge styles for activity-only event types */
.badge-alert_raised {
  background: rgba(239, 68, 68, 0.15);
  color: var(--red);
  border: 1px solid rgba(239, 68, 68, 0.3);
}
.badge-alert_resolved {
  background: rgba(34, 197, 94, 0.15);
  color: var(--green);
  border: 1px solid rgba(34, 197, 94, 0.3);
}
.badge-backup_auto,
.badge-backup_manual,
.badge-backup_prerestore {
  background: rgba(99, 102, 241, 0.15);
  color: #818cf8;
  border: 1px solid rgba(99, 102, 241, 0.3);
}

/* Slot enabled slide-toggle */
.slot-toggle {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  user-select: none;
}
.slot-toggle input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.slot-toggle-track {
  position: relative;
  width: 36px;
  height: 20px;
  background: var(--surface-3, #3a3f4b);
  border-radius: 10px;
  transition: background 0.2s;
  flex-shrink: 0;
}
.slot-toggle input:checked ~ .slot-toggle-track {
  background: var(--accent);
}
.slot-toggle-thumb {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 14px;
  height: 14px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.2s;
}
.slot-toggle input:checked ~ .slot-toggle-track .slot-toggle-thumb {
  transform: translateX(16px);
}
/* .form-group label {display:block} elsewhere in this file targets ALL
   labels, including this one when a slot-toggle is nested inside a
   form-group -- restore the flex layout its track/thumb sizing depends on. */
.form-group .slot-toggle {
  display: inline-flex;
}

.badge-failed {
  background: rgba(239, 68, 68, 0.15);
  color: var(--red);
  border: 1px solid rgba(239, 68, 68, 0.3);
}

.badge-unknown {
  background: rgba(61, 79, 95, 0.3);
  color: var(--text-3);
  border: 1px solid var(--border);
}

/* Alert severity badges */
.badge-critical {
  background: rgba(239, 68, 68, 0.15);
  color: var(--red);
  border: 1px solid rgba(239, 68, 68, 0.3);
}

.badge-warning {
  background: rgba(245, 158, 11, 0.15);
  color: var(--orange);
  border: 1px solid rgba(245, 158, 11, 0.3);
}

/* ============================================================
   Dashboard: Status Banner
   ============================================================ */

.status-banner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  padding: 14px 18px;
  border-radius: 8px;
  margin-bottom: 24px;
  border: 1px solid;
}

.recording-progress-track {
  flex: 0 0 100%;
  height: 5px;
  background: rgba(0, 0, 0, 0.4);
  border-radius: 3px;
  overflow: hidden;
  margin-top: -4px;
  display: flex;
}

.recording-progress-missed {
  height: 100%;
  background: var(--accent);
  flex-shrink: 0;
  width: 0%;
}

.recording-progress-fill {
  height: 100%;
  background: var(--green);
  flex-shrink: 0;
  width: 0%;
  transition: width 1s linear;
}

.status-banner p {
  margin: 0;
  font-size: 13px;
}

.status-active {
  background: rgba(34, 197, 94, 0.08);
  border-color: rgba(34, 197, 94, 0.3);
  color: var(--green);
}

.status-active strong {
  color: var(--green);
  font-size: 15px;
}

.status-active p {
  color: var(--text-2);
  font-size: 15px;
}

.status-active p strong {
  color: #ffffff;
  font-weight: 700;
}

.status-inactive {
  background: rgba(239, 68, 68, 0.08);
  border-color: rgba(239, 68, 68, 0.3);
  color: var(--red);
}

.status-inactive strong {
  color: var(--red);
}

.status-inactive p {
  color: var(--text-2);
}

.status-uploading {
  background: rgba(96, 165, 250, 0.08);
  border-color: rgba(96, 165, 250, 0.3);
  color: var(--blue);
}

.status-uploading strong {
  color: var(--blue);
  font-size: 14px;
}

.status-uploading p {
  color: var(--text-2);
  font-size: 14px;
}

/* ============================================================
   Dashboard: Alert Banners
   ============================================================ */

.alert-banner {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px 18px;
  border-radius: 8px;
  margin-bottom: 16px;
  border-left: 4px solid;
}

.alert-banner p {
  margin: 4px 0 0 0;
  font-size: 13px;
}

.alert-banner a {
  font-size: 12px;
}

.alert-critical {
  background: rgba(239, 68, 68, 0.1);
  border-color: var(--red);
  color: #fca5a5;
}

.alert-critical strong {
  color: #fca5a5;
}

.alert-warning {
  background: rgba(245, 158, 11, 0.1);
  border-color: var(--orange);
  color: #fcd34d;
}

.alert-warning strong {
  color: #fcd34d;
}

/* ============================================================
   Dashboard: Stats Grid
   ============================================================ */

.stats-grid,
.stats-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 16px;
  margin-bottom: 28px;
}

.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--accent);
  border-radius: 8px;
  padding: 16px 20px;
}

.stat-value {
  font-family: var(--font-mono);
  font-size: 28px;
  font-weight: 700;
  color: var(--text);
  line-height: 1;
  margin-bottom: 6px;
}

.stat-label {
  font-size: 11px;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

/* ============================================================
   Cards
   ============================================================ */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 20px 24px;
  margin-bottom: 20px;
}

.card h2 {
  margin-top: 0;
}

/* ============================================================
   Services Grid
   ============================================================ */

.services-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px;
}

.service-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 20px 24px;
}

.service-card h2 {
  margin-top: 0;
  margin-bottom: 12px;
  font-size: 14px;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  font-family: var(--font-mono);
}

.service-state {
  margin-bottom: 16px;
  font-size: 13px;
  color: var(--text-2);
  display: flex;
  align-items: center;
  gap: 8px;
}

.service-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.service-warning {
  font-size: 11px;
  color: var(--text-3);
  margin: 0;
  font-style: italic;
}

.service-timestamp {
  font-size: 12px;
  color: var(--text-2);
  margin-bottom: 14px;
}

/* ============================================================
   Server Health
   ============================================================ */

.server-health {
  margin-top: 36px;
}

.server-health h2 {
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text);
  margin-bottom: 16px;
}

.server-health-uptime {
  color: var(--text);
  margin-bottom: 16px;
}

.health-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px;
  align-items: stretch;
}

.health-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.health-card-title {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-2);
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

.health-value {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text);
}

.donut-chart {
  width: 100%;
  height: auto;
  max-width: 120px;
  display: block;
}

.donut-pct {
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 600;
  fill: var(--text);
}

.health-card-detail {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-2);
  text-align: center;
}

.spark-wrap {
  position: relative;
  width: 100%;
  flex: 1;
  display: flex;
  align-items: stretch;
  gap: 4px;
  min-width: 0;
  min-height: 60px;
}

.spark-chart {
  flex: 1;
  min-width: 0;
  height: 100%;
  display: block;
  overflow: visible;
}

.spark-scale {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--text-3);
  line-height: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 0;
  white-space: nowrap;
}

.health-sub {
  font-size: 11px;
  color: var(--text-2);
  align-self: flex-start;
  font-family: var(--font-mono);
}

.net-rows {
  flex: 1;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-height: 0;
}

.net-row {
  display: flex;
  align-items: center;
  gap: 5px;
  flex: 1;
  min-height: 0;
}

.net-row-label {
  font-size: 11px;
  flex-shrink: 0;
}

.net-spark {
  flex: 1;
  min-width: 0;
  height: 100%;
}

.net-row-value {
  font-size: 10px;
  white-space: nowrap;
  flex-shrink: 0;
  min-width: 60px;
  text-align: right;
}

/* ============================================================
   Tables
   ============================================================ */

table {
  border-collapse: collapse;
  width: 100%;
  font-size: 13px;
}

.markers-table {
  max-width: 900px;
}

/* Users list — larger text throughout (Jesse's call, session 61). Scoped to
   .users-table so the shared table/.badge/.btn-small defaults are untouched
   on every other page that uses them. */
.users-table {
  font-size: 16px;
}

.users-table thead th {
  font-size: 13px;
}

.users-table .badge {
  font-size: 13px;
}

.users-table .btn-small {
  font-size: 14px;
}

thead th {
  background: var(--surface);
  color: var(--text);
  padding: 9px 12px;
  text-align: left;
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  border-bottom: 1px solid var(--border);
  border-top: 1px solid var(--border);
  white-space: nowrap;
}

thead th:first-child {
  border-left: 1px solid var(--border);
  border-radius: 6px 0 0 0;
}

thead th:last-child {
  border-right: 1px solid var(--border);
  border-radius: 0 6px 0 0;
}

tbody tr:nth-child(odd) td {
  background: var(--bg);
}

tbody tr:nth-child(even) td {
  background: var(--surface);
}

tbody tr:hover td {
  background: var(--surface-2);
}

td {
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  vertical-align: middle;
}

td:first-child {
  border-left: 1px solid var(--border);
}

td:last-child {
  border-right: 1px solid var(--border);
}

tbody tr:last-child td:first-child {
  border-radius: 0 0 0 6px;
}

tbody tr:last-child td:last-child {
  border-radius: 0 0 6px 0;
}

.table-scroll {
  overflow-x: auto;
}

/* DJ list table — compact, sized to content */
.dj-list-table {
  width: auto;
}

/* DJ list — artwork thumbnail cells */
.td-art {
  padding: 7px 8px;
  text-align: center;
  width: 44px;
}

.art-thumb-wrap {
  position: relative;
  display: inline-block;
  line-height: 0;
}

.art-thumb {
  width: 20px;
  height: 20px;
  object-fit: cover;
  border-radius: 2px;
  display: block;
  cursor: default;
}

.art-preview {
  display: none;
  position: absolute;
  left: 28px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 200;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.5);
  pointer-events: none;
}

.art-preview img {
  width: 140px;
  height: 140px;
  object-fit: cover;
  border-radius: 4px;
  display: block;
}

.art-thumb-wrap:hover .art-preview {
  display: block;
}

/* Mono data */
.mono {
  font-family: var(--font-mono);
}

td.mono,
td.ts,
td.size {
  font-size: 12px;
  color: var(--text-2);
}

/* Upcoming schedule — fixed column widths so all date tables align */
table.upcoming-table {
  table-layout: fixed;
  width: 100%;
}
table.upcoming-table th:nth-child(1) { width: 110px; }  /* Time */
table.upcoming-table th:nth-child(2) { width: 190px; }  /* DJ */
table.upcoming-table th:nth-child(3) { width: 220px; }  /* Show */
table.upcoming-table th:nth-child(4) { width: 110px; }  /* Cadence */
table.upcoming-table th:nth-child(5),
table.upcoming-table td:nth-child(5) { padding-left: 105px; } /* Status — nudged right */

/* Dashboard "Recent Activity" table — every column shrunk to fit its
   content except DJ, which absorbs whatever width is left over. Was
   plain table-layout:auto stretched to 100%, leaving large gaps between
   narrow-content columns; table-layout:fixed + explicit widths on every
   column but DJ closes that up (DJ has no width rule, so it takes the
   remainder). */
table.activity-table {
  table-layout: fixed;
  width: 100%;
}
table.activity-table th:nth-child(1) { width: 165px; }  /* Date/Time */
table.activity-table th:nth-child(3) { width: 165px; }  /* Status */
table.activity-table th:nth-child(4) { width: 260px; }  /* Notes */
table.activity-table th:nth-child(5) { width: 165px; }  /* Duration */
table.activity-table th:nth-child(6) { width: 99px; }   /* Size */

/* Notes column text — brighter than the standard .text-muted (--text-2,
   used widely across the app so not touched here) since a skip/failure
   reason is useful information worth being readable at a glance, not
   fully secondary. Halfway between --text-2 (#6b7d8e) and the first
   attempt at this (#aab5c0, judged too bright). */
.activity-notes {
  color: #8b99a7;
}

/* Dimmed row (non-recordable / cancelled) — dim DJ and Show text only */
tr.row-dimmed td:nth-child(2),
tr.row-dimmed td:nth-child(3) {
  color: var(--text-2);
}

/* Past row on today — dim DJ and Show text only, leave background untouched */
tr.row-past td:nth-child(2),
tr.row-past td:nth-child(3) {
  color: var(--text-2);
}

/* Currently-recording row (upcoming page) */
tr.row-current-show td {
  background: rgba(232, 184, 75, 0.15) !important;
  color: var(--accent);
  font-weight: 600;
}
tr.row-current-show td:first-child {
  border-left: 3px solid var(--accent);
  padding-left: 9px;
}

/* ============================================================
   Schedule Grid
   ============================================================ */

.grid-week-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-bottom: 14px;
}

.grid-week-nav-btn {
  flex: 0 0 auto;
}

/* Class+class combo (not .btn-small alone) so this reliably wins over the
   shared .btn-small rule regardless of source order, without changing
   .btn-small's size for the other templates that share it -- same pattern
   as .btn-small.dj-list-edit-btn above. */
.btn-small.grid-week-nav-btn {
  font-size: 13px;
}

/* Next Week always occupies the same slot in the flex row regardless of
   whether Prev Week is rendered (it's omitted entirely at week_offset=0),
   so the label doesn't jump around depending on which week is displayed. */
.grid-week-label {
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 700;
  /* ~27% brighter than --text-2 (#6b7d8e) in total -- three successive
     10% rounds live-tuned per Jesse -- scoped here rather than touching
     the shared variable itself, which is used by 13+ other templates. */
  color: #93a0ac;
}

.grid-table {
  min-width: 700px;
  border-collapse: collapse;
}

.grid-table thead th {
  background: var(--accent);
  color: var(--bg);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 8px 10px;
  border: 1px solid var(--accent-dim);
}

.grid-table thead th:first-child {
  background: var(--surface);
  /* Halfway between the original --text-2 (#6b7d8e) and the diagnostic
     pure-white test that confirmed the incremental 10% bumps genuinely
     were too subtle to perceive, not a caching issue. */
  color: #b5bec6;
  border-color: var(--border);
}

.grid-table td.time-label {
  background: var(--surface);
  color: var(--text-2);
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 700;
  white-space: nowrap;
  width: 80px;
  border: 1px solid var(--border);
  padding: 6px 10px;
}

/* .grid-table td.time-label's own background above (2 classes + 1
   element) is more specific than the generic "tbody tr:hover td" rule (3
   elements + 1 pseudo-class), so the time column never picked up the
   row-hover highlight the rest of the row gets. This matches it
   specifically for .grid-table rows. */
.grid-table tbody tr:hover td.time-label {
  background: var(--surface-2);
}

.grid-cell {
  border: 1px solid var(--border);
  padding: 6px 8px;
  font-size: 13px;
  min-width: 100px;
  vertical-align: top;
}

.grid-cell a {
  color: inherit;
  text-decoration: none;
  display: block;
  white-space: pre-line;
}

.grid-cell a:hover {
  text-decoration: underline;
}

.grid-empty {
  background: transparent;
}

.grid-empty-link {
  cursor: pointer;
}

.grid-empty-link:hover {
  background: rgba(232, 184, 75, 0.05);
}

.new-slot-hint {
  color: transparent;
  font-size: 16px;
  line-height: 1;
  user-select: none;
}

.grid-empty-link:hover .new-slot-hint {
  color: var(--text-3);
}

.grid-filled {
  background: var(--surface-2);
  border-left: 3px solid var(--accent) !important;
  color: var(--text);
  font-weight: 500;
}

.grid-filled:hover {
  background: rgba(232, 184, 75, 0.08);
}

/* Biweekly slot on its off week — dimmed but not struck through, so it
   doesn't read as "disabled" (see .grid-disabled below, which is struck
   through) */
.grid-off-week {
  color: var(--text-2);
  font-weight: 400;
}

/* Alternating-biweekly cell's second (not-on-air-this-week) DJ name —
   same dim treatment as .grid-off-week above, scoped to just that one
   name instead of the whole cell since the first name IS on air. */
.grid-alt-inactive {
  color: var(--text-2);
  font-weight: 400;
}

/* A slot with a "cancel" override on THIS week's occurrence — strikethrough
   only, no color/background change (unlike .grid-disabled below, which
   also mutes the color): the slot itself is still enabled/active, only
   this one week's occurrence won't air. For an alternating_biweekly cell
   this wraps only the active (first) name; the inactive second name keeps
   its normal .grid-alt-inactive dim-only treatment, since cancelling this
   week's occurrence doesn't affect the rotation itself. */
.grid-cancelled {
  text-decoration: line-through;
}

/* A slot with a "replace" (substitute DJ) override on THIS week's
   occurrence — the substitute's name is shown in place of the regular
   DJ's, in italics, so it reads as a one-off swap rather than a
   permanent reassignment. For an alternating_biweekly cell this replaces
   only the active (first) name; the inactive second name is untouched. */
.grid-substitute {
  font-style: italic;
}

.grid-vacant {
  background: var(--surface);
  color: var(--text-3);
  font-style: italic;
  padding: 0;
}

.grid-vacant a {
  display: block;
  padding: 6px 8px;
  min-height: 2em;
}

.grid-vacant:hover {
  background: rgba(232, 184, 75, 0.08);
  color: var(--text-2);
}

.grid-disabled {
  background: var(--bg);
  color: var(--text-3);
}

.grid-disabled a {
  text-decoration: line-through;
  color: var(--text-3);
}

/* ── Party Mode ──────────────────────────────────────────────────────────
   A party-day column overrides the regular per-cadence rendering entirely
   (see _build_grid()'s party branch) -- these mirror .grid-filled/
   .grid-vacant/.grid-empty-link's own layout/sizing rules, just recolored
   with --party/--party-dim so a party lineup reads as visually distinct
   from the regular schedule at a glance. */

/* Header color overrides below need !important: the base rule
   ".grid-table thead th { color: var(--bg) }" is a 3-part selector
   (class+2 elements) which beats a plain single-class selector on
   specificity alone, so without !important these silently lose.

   At rest, the party header is now SOLID var(--party) with var(--bg)
   (near-black) text -- matching the default day header's own solid
   var(--accent)/var(--bg) treatment, rather than the low-opacity overlay
   this used to use (which read as a muted dark purple against the
   near-black page background instead of a genuine light pink fill; Jesse
   flagged this live during today's party). Dark text measurably beats
   white here too: black-on-solid-pink is ~5.5:1 contrast vs. white-on-
   solid-pink's ~3.5:1, against a WCAG AA text minimum of 4.5 (black-on-
   solid-gold for the default header measures even higher, ~10.6:1).
   Hover then drops to a translucent pink with WHITE text (mirroring the
   default day header's own solid-to-translucent hover transition, and
   reusing the same 0.10 opacity already used for .grid-party-filled/
   .grid-party-empty's hover states elsewhere in this section). */
th.grid-party-header {
  background: var(--party) !important;
  color: var(--bg) !important;
  cursor: pointer;
}

th.grid-day-header {
  cursor: pointer;
}

th.grid-day-header:hover {
  background: rgba(232, 184, 75, 0.10);
  color: #fff !important;
}

th.grid-party-header:hover {
  background: rgba(251, 54, 126, 0.10) !important;
  color: #fff !important;
}

.grid-party-filled {
  background: var(--surface-2);
  border-left: 3px solid var(--party) !important;
  color: var(--text);
  font-weight: 500;
  cursor: pointer;
}

.grid-party-filled:hover {
  background: rgba(251, 54, 126, 0.10);
}

.grid-party-vacant {
  background: var(--surface);
  border-left: 3px solid var(--party-dim) !important;
  color: var(--text-3);
  font-style: italic;
  cursor: pointer;
}

.grid-party-vacant:hover {
  background: rgba(251, 54, 126, 0.08);
  color: var(--text-2);
}

.grid-party-empty {
  background: rgba(251, 54, 126, 0.04);
  cursor: pointer;
}

.grid-party-empty:hover {
  background: rgba(251, 54, 126, 0.10);
}

.grid-party-empty .new-slot-hint {
  color: transparent;
  font-size: 16px;
  line-height: 1;
  user-select: none;
}

.grid-party-empty:hover .new-slot-hint {
  color: var(--party-dim);
}

/* Today column & current-time highlights */
th.grid-today-header {
  background: rgba(232, 184, 75, 0.18) !important;
  color: #fff !important;
}

/* Today's column is also a party-day column -- same specificity as both
   .grid-today-header and .grid-party-header alone (equal-specificity
   source order previously let the gold today-header win at rest, only
   showing pink on :hover once the pseudo-class bumped specificity above
   it). This compound selector outranks both single-class rules so party
   pink wins at rest too, matching .grid-today.grid-filled's existing
   pattern below for cells. */
th.grid-today-header.grid-party-header {
  background: var(--party) !important;
  color: var(--bg) !important;
}

th.grid-today-header.grid-party-header:hover {
  background: rgba(251, 54, 126, 0.10) !important;
  color: #fff !important;
}

td.grid-today {
  background-color: rgba(232, 184, 75, 0.07) !important;
}

td.grid-today:hover {
  background-color: rgba(232, 184, 75, 0.14) !important;
}

td.grid-today.grid-filled {
  background-color: rgba(232, 184, 75, 0.12) !important;
}

td.grid-today.grid-filled:hover {
  background-color: rgba(232, 184, 75, 0.20) !important;
}

.grid-table td.time-label.grid-current-time {
  color: #ffffff;
}

td.grid-current-playing {
  position: relative;
  background: rgba(232, 184, 75, 0.22) !important;
  border-left: 3px solid transparent !important;
}

td.grid-current-playing:hover {
  background: rgba(232, 184, 75, 0.30) !important;
}

td.grid-current-playing::before {
  content: '';
  position: absolute;
  /* -3px, not 0: this element's containing block is its own PADDING box,
     which starts 3px in from the true cell edge because this cell's own
     border-left is 3px wide (transparent, but still occupies that width).
     A cell's static color accent (e.g. .grid-party-filled's border-left)
     is a real border, drawn at the border box's outer edge -- flush with
     the boundary shared with the cell to the left. left:0 here would
     instead render 3px further in, visibly offset from that boundary
     into the cell's interior. -3px cancels the border-left-width so this
     bar lands exactly where the static accent bar would have. */
  left: -3px;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--red);
  animation: pulse-recording 3s ease-in-out infinite;
}

td.grid-current-playing a {
  color: var(--accent) !important;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.2px;
}

td.grid-current-idle {
  position: relative;
  border-left: 3px solid transparent !important;
}

td.grid-current-idle::before {
  content: '';
  position: absolute;
  left: -3px; /* see td.grid-current-playing::before's comment above */
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--grey);
}

/* ============================================================
   Forms
   ============================================================ */

.form-group {
  margin-bottom: 16px;
}

.form-group label {
  display: block;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 6px;
}

.form-group label code {
  text-transform: none;
  letter-spacing: 0;
}

.form-group label:has(+ input[type="checkbox"]),
.form-group label input[type="checkbox"] {
  text-transform: none;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0;
}

.form-group input[type="text"],
.form-group input[type="password"],
.form-group input[type="number"],
.form-group input[type="email"],
.form-group input[type="file"],
.form-group select,
.form-group textarea {
  width: 100%;
  max-width: 420px;
  padding: 8px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}

/* Fields capped at 20 chars server-side (MAX_USERNAME_LENGTH /
   MAX_PASSWORD_LENGTH) — no reason for the input to be wider than that.
   22ch = 20 chars of room plus a little breathing space. Combined
   ID+class selectors (rather than the class alone) so this reliably beats
   .form-group input[...] / .login-box .form-group input[...]'s specificity
   regardless of source order, while staying precisely scoped: #confirm_password
   also exists (uncapped) on the separate Change Password self-service page,
   which never gets the .field-20char class, so it's untouched here. */
#username.field-20char,
#password.field-20char,
#confirm_password.field-20char {
  max-width: 22ch;
}

/* Role is a 3-option dropdown (viewer/editor/admin) — no reason for it to
   stretch to the generic 420px form-field width. Matched to the same 22ch
   as the username/password fields above (same font-size/family on both
   .form-group input and .form-group select, so ch computes identically —
   this yields exact pixel-width parity, not just a similar size). ID+class
   combo for the same specificity-safety reason as above. */
#role.select-narrow {
  max-width: 22ch;
}

.form-group input[type="text"]:focus,
.form-group input[type="password"]:focus,
.form-group input[type="number"]:focus,
.form-group input[type="email"]:focus,
.form-group select:focus,
.form-group textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(232, 184, 75, 0.2);
}

.form-group input.field-error,
.form-group input.field-error:focus,
.form-group textarea.field-error,
.form-group textarea.field-error:focus {
  border-color: var(--red);
  box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2);
}

.form-group.cadence-inactive label,
.form-group.cadence-inactive input,
.form-group.cadence-inactive select,
.form-group.cadence-inactive textarea {
  opacity: 0.35;
  pointer-events: none;
}

.form-group input[type="file"] {
  color: var(--text-2);
}

.form-group input[type="file"]:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(232, 184, 75, 0.2);
}

.form-group textarea {
  resize: vertical;
  line-height: 1.4;
  min-height: calc(1.4em + 16px + 2px);
}

.form-group input[type="checkbox"] {
  accent-color: var(--accent);
  margin-right: 8px;
  width: auto;
}

.form-group select option {
  background: var(--surface-2);
  color: var(--text);
}

.filter-form {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 20px;
  padding: 14px 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.filter-form label {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.filter-form select,
.filter-form input[type="text"] {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font-size: 13px;
  padding: 5px 8px;
  outline: none;
  font-family: inherit;
}

.filter-form select:focus,
.filter-form input[type="text"]:focus {
  border-color: var(--accent);
}

/* ============================================================
   Buttons
   ============================================================ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 7px 16px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 13px;
  font-family: inherit;
  font-weight: 500;
  text-decoration: none;
  transition: background 0.15s, opacity 0.15s;
  white-space: nowrap;
}

.btn:hover {
  text-decoration: none;
  opacity: 0.9;
}

.btn-primary {
  background: var(--accent);
  color: var(--bg);
}

.btn-primary:hover {
  background: var(--accent-dim);
  color: var(--bg);
}

.btn-secondary {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
}

.btn-secondary:hover {
  background: var(--border);
  color: var(--text);
}

/* Slack's own official "Sign in with Slack" dark-theme button spec
   (docs.slack.dev/authentication/sign-in-with-slack): #4A154B background
   ("aubergine", Slack's brand purple), white text, no border -- matches
   this app's own dark UI better than their white/black default-theme
   variant would. width:100%/max-width mirrors the login-page-scoped
   #username/#password override just above (same calc(22ch + 40px), same
   mechanism -- both 100% of the shared .login-fields (width:fit-content)
   parent, capped the same way) so it renders at precisely the widened
   field width, not just something close to it. font-size is the same
   +2px bump as every other login-page text size, overriding the shared
   .btn base's 13px directly since .btn-slack is only ever used here. */
.btn-slack {
  background: #4A154B;
  color: #fff;
  border: none;
  width: 100%;
  max-width: calc(22ch + 40px);
  /* 10px vertical, matching .login-box .btn-primary's own padding:10px
     exactly (Log In) -- never had its own override before, so it inherited
     .btn's base 7px 16px, rendering 6px shorter (32px vs 38px measured via
     Playwright) than Log In despite both being centered flex buttons.
     16px horizontal kept as-is; width is already fixed via max-width
     above, so horizontal padding doesn't drive sizing the way vertical
     does here. */
  padding: 10px 16px;
  /* Matches Log In's 16px/600 exactly -- previously 15px/500 (the base
     .btn's inherited weight), which read as noticeably smaller than Log
     In despite only a 1px font-size gap; the weight difference was doing
     most of that work. Untested against real overflow risk -- this
     button's content (icon + "Sign In With Slack", much longer than
     "Log In") combined with .btn's white-space:nowrap means it won't
     wrap if it doesn't fit, it'll just spill past the edge. Revisit if
     that happens. */
  font-size: 16px;
  font-weight: 600;
  /* This is an <a>, not a <button> like Log In -- browsers' UA stylesheet
     gives <button> a default line-height:normal, but <a> gets no such
     default and falls through to body's line-height:1.5, computing to a
     real 24px at this font-size vs Log In's ~19px "normal". Confirmed via
     Playwright reading both elements' actual computed styles: font-family/
     size/weight/letter-spacing were already byte-identical, line-height
     was the one real remaining difference. Pinned to normal so both read
     as truly the same weight/size, not just close. */
  line-height: normal;
  /* Wider than .btn's base 6px gap -- more breathing room between the
     icon and "Sign In With Slack" specifically. */
  gap: 12px;
}

.btn-slack:hover {
  filter: brightness(1.15);
  color: #fff;
}

.btn-danger {
  background: var(--red);
  color: #fff;
}

.btn-danger:hover {
  background: #dc2626;
  color: #fff;
}

.btn-warning {
  background: var(--orange);
  color: var(--bg);
}

.btn-warning:hover {
  background: #d97706;
  color: var(--bg);
}

.btn-start {
  background: var(--green);
  color: #fff;
}

.btn-start:hover {
  background: #16a34a;
  color: #fff;
}

.btn-stop {
  background: var(--red);
  color: #fff;
}

.btn-stop:hover {
  background: #dc2626;
  color: #fff;
}

.btn-restart {
  background: var(--orange);
  color: var(--bg);
}

.btn-restart:hover {
  background: #d97706;
  color: var(--bg);
}

.btn-small {
  padding: 4px 10px;
  font-size: 11px;
}

/* Blocklist sender input — table-embedded, so not wrapped in .form-group
   (that class assumes a full-width label+field row layout). Same visual
   treatment as .form-group's styled inputs, just self-contained. */
.blocklist-email-input {
  width: 100%;
  max-width: 320px;
  padding: 6px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.blocklist-email-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(232, 184, 75, 0.2);
}

/* DJs list Edit button — slightly bigger than the shared .btn-small default.
   Class+class combo (rather than .btn-small alone) so this reliably wins
   regardless of source order, without changing .btn-small's size for the
   other 10+ templates that share it. */
.btn-small.dj-list-edit-btn {
  font-size: 13px;
}

.recordings-status-filters .btn-small {
  font-size: 12px;
}

.audit-filter-btn.active,
.btn-secondary.active {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
}

.btn[disabled],
.btn:disabled {
  opacity: 0.4;
  cursor: default;
  pointer-events: none;
}

/* ============================================================
   Detail List (dl/dt/dd)
   ============================================================ */

.detail-list {
  display: grid;
  grid-template-columns: 185px 1fr;
  gap: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  margin-bottom: 24px;
}

.detail-list dt {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 10px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
}

.detail-list dd {
  font-size: 13px;
  color: var(--text);
  padding: 10px 16px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  margin: 0;
  word-break: break-word;
  display: flex;
  align-items: center;
}

.detail-list dt:last-of-type,
.detail-list dd:last-of-type {
  border-bottom: none;
}

/* ============================================================
   Error Box
   ============================================================ */

.error-box {
  background: rgba(239, 68, 68, 0.1);
  border: 1px solid rgba(239, 68, 68, 0.3);
  border-left: 4px solid var(--red);
  border-radius: 6px;
  padding: 12px 16px;
  color: #fca5a5;
  font-size: 13px;
  margin-bottom: 20px;
}

.error-box strong {
  color: var(--red);
}

/* ============================================================
   Actions row
   ============================================================ */

.actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}

/* ============================================================
   Pagination
   ============================================================ */

.pagination {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 16px;
  font-size: 13px;
  color: var(--text-2);
}

.pagination a {
  color: var(--accent);
}

/* ============================================================
   Logs Page
   ============================================================ */

.log-toolbar {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 12px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px 8px 0 0;
  border-bottom: none;
  font-size: 13px;
}

.log-toolbar label {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.log-toolbar select,
.log-toolbar input[type="checkbox"] {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font-size: 13px;
  padding: 4px 8px;
  outline: none;
  font-family: inherit;
}

.log-output {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 0 0 8px 8px;
  padding: 16px;
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-2);
  max-height: 600px;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-all;
}

.log-count {
  font-size: 14px;
  color: var(--text-3);
  padding: 8px 0 4px 0;
}

.log-ts   { color: #6b7e99; }
.log-host { color: #4a5a6e; }
.log-proc { color: #7a8fa8; }
.log-msg  { color: #c8d4e0; }
.log-error { color: #f87171; font-weight: 600; }
.log-warn  { color: var(--amber); }

/* ============================================================
   Text utility classes
   ============================================================ */

.text-critical {
  color: var(--red);
  font-weight: 600;
}

.text-warning {
  color: var(--orange);
  font-weight: 600;
}

.text-muted {
  color: var(--text-2);
}

.text-mono {
  font-family: var(--font-mono);
}

/* ============================================================
   Upcoming: override labels
   ============================================================ */

.label-cancelled {
  color: var(--red);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.label-substitute {
  color: var(--orange);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

/* ============================================================
   Login Page
   ============================================================ */

body.login-page {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: var(--bg);
}

.login-box {
  background: var(--surface);
  padding: 36px 40px;
  border-radius: 10px;
  width: 100%;
  max-width: 380px;
  border: 1px solid var(--border);
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
}

.login-logo {
  text-align: center;
  font-size: 26px;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: 6px;
  letter-spacing: -0.5px;
}

.login-subtitle {
  text-align: center;
  font-size: 14px;
  /* --text-3 (#3d4f5f) x1.2 -> #495f72 (~20% brighter), then a further
     x1.1 on top of that (~10% more, per Jesse's follow-up that it was
     still too dim) -> #50697d. Compounds to ~32% over the original
     --text-3, not a flat 30%, since each step multiplies the prior
     result rather than the original. */
  color: #50697d;
  font-weight: 700;
  margin-bottom: 28px;
  text-transform: uppercase;
  letter-spacing: 1px;
}

.login-box .btn-primary {
  padding: 10px;
  font-size: 16px;
  font-weight: 600;
}

/* Every text size on this page (2026-08-16, Jesse's explicit request) is
   +2px over the shared app-wide defaults these elements would otherwise
   inherit (.form-group label 11->13, .form-group input 13->15, .flash
   13->15) -- scoped to .login-box specifically rather than edited at the
   source, since those base rules are shared by every form/flash message
   across the entire app, not just this page. */
.login-box .form-group label {
  font-size: 13px;
  /* --text-2 (#6b7d8e) each RGB channel x1.1, clamped to 255 -> #768a9c --
     ~10% brighter than the shared app-wide label color. */
  color: #768a9c;
}

.login-box .flash {
  font-size: 15px;
}

/* Username/password fields are only 22ch wide (see #username, #password
   below), narrower than the login box itself — center that narrow column
   as a block so the fields aren't stuck flush to the left, while the Log
   In button (inside the same block) naturally lines up with the fields'
   own left edge rather than the box's. */
.login-fields {
  width: fit-content;
  margin: 0 auto;
}

.login-box .form-group input[type="text"],
.login-box .form-group input[type="password"] {
  max-width: 100%;
  font-size: 15px;
}

/* Widens #username/#password specifically (not #confirm_password, which
   shares the base #username.field-20char,... rule below but lives on the
   separate Change Password page, untouched by this login-page-only
   request) by exactly maxlength(20) * the +2px font bump above = 40px,
   per Jesse's explicit formula -- added on top of the existing 22ch
   (which also grows a little on its own from the font-size increase
   itself, ch being font-relative; this is the additional, explicit part).
   Same higher-specificity-via-more-selectors technique as the base rule's
   own ID+class combo, scoped further by the body.login-page ancestor so
   it only ever wins here, never on the Change Password page. */
body.login-page #username.field-20char,
body.login-page #password.field-20char {
  max-width: calc(22ch + 40px);
}

/* ============================================================
   Back links
   ============================================================ */

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-2);
  margin-bottom: 20px;
}

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

/* ============================================================
   Disk usage
   ============================================================ */

.disk-section {
  margin-bottom: 24px;
}

.free-space {
  margin-top: 8px;
  font-size: 13px;
  color: var(--text-2);
}

/* ============================================================
   Disk Usage: Collapsible Rows
   ============================================================ */

/* Caret header cell — narrow, no padding needed */
.disk-caret-th {
  width: 28px;
  padding: 0 !important;
}

/* Summary rows are clickable */
.disk-summary-row {
  cursor: pointer;
}

.disk-summary-row:hover td {
  background: rgba(232, 184, 75, 0.06) !important;
}

/* Caret cell */
.disk-caret {
  width: 28px;
  text-align: center;
  font-size: 10px;
  color: var(--text-3);
  user-select: none;
  transition: color 0.15s;
  padding: 9px 4px !important;
}

.disk-summary-row:hover .disk-caret {
  color: var(--accent);
}

/* Rotate caret when expanded — use CSS content swap */
.disk-summary-row.expanded .disk-caret {
  color: var(--accent-dim);
}

/* Detail row attaches visually to its summary row — direct child only, not inner table cells */
.disk-detail-row > td {
  border-top: none !important;
  padding: 0 0 8px 0 !important;
  background: var(--bg) !important;
}

/* Outer disk usage summary table — explicit width so fixed layout enforces column widths */
.disk-outer-table {
  table-layout: fixed;
  width: 738px;
}

/* Status and Files column widths; total 28+490+96+80+44 = 738px matches table width exactly */
/* Use > thead > tr > th to avoid affecting nested inner tables */
.disk-outer-table > thead > tr > th:nth-child(2) { width: 490px; }
.disk-outer-table > thead > tr > th:nth-child(3) { width: 96px; }

/* Nested file table */
.disk-detail-table {
  table-layout: fixed;
  width: calc(100% - 28px);
  margin-left: 28px;
  font-size: 12px;
  border-collapse: collapse;
}

.disk-detail-table thead th {
  font-size: 10px;
  padding: 5px 10px;
  color: var(--text-3);
  background: var(--bg) !important;
  border-top: none !important;
  border-left: none !important;
  border-right: none !important;
}

.disk-detail-table thead th:first-child {
  border-left: none !important;
  border-radius: 0 !important;
}

.disk-detail-table thead th:last-child {
  border-right: none !important;
  border-radius: 0 !important;
}

.disk-detail-table tbody tr:nth-child(odd) td {
  background: var(--bg);
}

.disk-detail-table tbody tr:nth-child(even) td {
  background: var(--surface);
}

.disk-detail-table td {
  padding: 5px 10px;
  border-bottom: 1px solid var(--border);
  color: var(--text-2);
  font-size: 12px;
}

.disk-detail-table td:first-child {
  border-left: none !important;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}


.disk-detail-table td:last-child {
  border-right: none !important;
}

.disk-detail-table tbody tr:last-child td:first-child,
.disk-detail-table tbody tr:last-child td:last-child {
  border-radius: 0 !important;
}

/* Deleted file row */
.disk-file-deleted {
  opacity: 0.45;
  text-decoration: line-through;
}

.disk-file-deleted td {
  text-decoration: line-through;
}

/* "Deleted" badge for orphan files */
.badge-deleted-file {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-family: var(--font-mono);
  white-space: nowrap;
  background: rgba(107, 125, 142, 0.15);
  color: var(--grey);
  border: 1px solid rgba(107, 125, 142, 0.3);
  margin-left: 6px;
  vertical-align: middle;
}

/* Fixed-width Size (MB) column — shared between outer summary table and inner detail table */
.disk-col-size {
  width: 80px;
  white-space: nowrap;
  text-align: right;
  box-sizing: border-box;
}

/* Actions column — same fixed width in both outer and inner tables for alignment */
.disk-col-actions {
  width: 44px;
  text-align: center;
  box-sizing: border-box;
}

/* Kebab (⋮) actions button for orphan file rows */
.disk-actions-wrap {
  position: relative;
  display: inline-block;
}

.disk-actions-btn {
  background: none;
  border: 1px solid var(--border);
  color: var(--text-2);
  border-radius: 4px;
  padding: 1px 9px 3px;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  font-family: var(--font-ui);
  transition: background 0.15s, color 0.15s;
}

.disk-actions-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}

.disk-actions-menu {
  position: absolute;
  top: calc(100% + 3px);
  right: 0;
  z-index: 200;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  min-width: 200px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
  overflow: hidden;
}

.disk-actions-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 7px 14px;
  font-size: 12px;
  font-family: var(--font-ui);
  background: none;
  border: none;
  color: var(--text);
  cursor: pointer;
  text-decoration: none;
  transition: background 0.1s;
  box-sizing: border-box;
}

.disk-actions-item + .disk-actions-item {
  border-top: 1px solid var(--border);
}

.disk-actions-item:hover {
  background: var(--surface-2);
}

.disk-actions-item-danger {
  color: var(--red) !important;
}

.disk-actions-item-danger:hover {
  background: rgba(239, 68, 68, 0.1) !important;
}

.disk-actions-item-muted {
  color: var(--text-3) !important;
  cursor: default;
  font-style: italic;
}

/* Assign form — lives inside the actions dropdown */
.assign-orphan-form {
  display: none;
  flex-direction: column;
  gap: 6px;
  padding: 8px 12px;
  border-top: 1px solid var(--border);
}

.assign-orphan-form select {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font-size: 12px;
  padding: 3px 6px;
  outline: none;
  font-family: inherit;
  box-sizing: border-box;
}

.assign-orphan-form select:focus {
  border-color: var(--accent);
}

.assign-status.success {
  color: var(--green);
}

.assign-status.error {
  color: var(--red);
}

/* ============================================================
   404 / Error pages
   ============================================================ */

.error-page {
  padding: 60px 0;
  text-align: center;
}

.error-page h1 {
  font-size: 32px;
  color: var(--text-3);
  margin-bottom: 12px;
}

.error-page p {
  color: var(--text-2);
}

/* ============================================================
   Tab bar (Activity page and similar two-tab layouts)
   ============================================================ */

.tab-bar {
  display: flex;
  gap: 4px;
  margin-bottom: 24px;
  border-bottom: 1px solid var(--border);
  padding-bottom: 0;
}

.tab {
  display: inline-block;
  padding: 8px 18px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-2);
  background: var(--surface);
  text-decoration: none;
  border-radius: 6px 6px 0 0;
  border: 1px solid transparent;
  border-bottom: 1px solid var(--border);
  margin-bottom: -1px;
  transition: color 0.15s, background 0.15s, border-color 0.15s;
}

.tab:hover {
  color: var(--text);
  background: var(--surface-2);
}

.tab-active {
  color: var(--bg);
  background: var(--accent);
  font-weight: 600;
  border-color: var(--accent);
}

.tab-active:hover {
  color: var(--bg);
  background: var(--accent-dim);
  border-color: var(--accent-dim);
}

/* Logs page has more tabs than any other .tab-bar user (11+) — narrower
   horizontal padding/gap here only, scoped so Activity/Settings/the slot
   modal keep their normal tab sizing. */
.logs-tab-bar {
  gap: 2px;
  flex-wrap: wrap;
}

.logs-tab-bar .tab {
  padding: 8px 6px;
}

/* ============================================================
   Activity page event badges
   ============================================================ */

.badge-alert_raised {
  background: rgba(245, 158, 11, 0.15);
  color: var(--orange);
  border: 1px solid rgba(245, 158, 11, 0.3);
}

.badge-alert_resolved {
  background: rgba(34, 197, 94, 0.1);
  color: #34d399;
  border: 1px solid rgba(34, 197, 94, 0.2);
}

.badge-backup_auto,
.badge-backup_manual,
.badge-backup_prerestore {
  background: rgba(99, 102, 241, 0.15);
  color: #818cf8;
  border: 1px solid rgba(99, 102, 241, 0.3);
}

/* ============================================================
   Portal page overrides — narrower sidebar, no content padding
   ============================================================ */

body.portal-page {
  overflow: hidden;
}

body.portal-page .main-wrapper {
  overflow: hidden;
  height: 100vh;
}

body.portal-page .main-content {
  padding: 0;
  overflow: hidden;
  height: 100vh;
}

/* ============================================================
   Quick-assign Modal (Schedule Grid)
   ============================================================ */

.slot-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.65);
  z-index: 500;
}

.slot-modal {
  position: fixed;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  width: 420px;
  max-width: calc(100vw - 24px);
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.55);
  overflow: hidden;
}

.slot-modal-header {
  padding: 16px 20px 12px;
  border-bottom: 1px solid var(--border);
}

.slot-modal-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.2px;
}

.slot-modal-body {
  padding: 10px 20px 18px;
}

.slot-modal-label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 8px;
}

.slot-modal-select {
  width: 100%;
  padding: 8px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.slot-modal-select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(232, 184, 75, 0.2);
}

.slot-modal-select option {
  background: var(--surface-2);
  color: var(--text);
}

.slot-modal-rotation-note {
  margin: 0;
  font-size: 13px;
  color: var(--text-2);
}

.slot-modal-footer {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 12px 20px 16px;
  border-top: 1px solid var(--border);
}

.slot-modal-error {
  margin: 0 20px 14px;
  padding: 8px 12px;
  background: rgba(239, 68, 68, 0.1);
  border: 1px solid rgba(239, 68, 68, 0.3);
  border-left: 3px solid var(--red);
  border-radius: 4px;
  color: #fca5a5;
  font-size: 12px;
}

.slot-modal-tabs {
  margin: 0 20px;
  padding-top: 6px;
}

.slot-modal-tabs .tab {
  padding: 7px 14px;
  font-size: 13px;
}

.slot-modal-tabs .tab:not(.tab-active) {
  color: var(--text);
  background: var(--surface-2);
  border-color: var(--border);
}

.slot-modal-tabs .tab:not(.tab-active):hover {
  background: var(--border);
}

.slot-modal-date-nav {
  display: flex;
  align-items: center;
  gap: 6px;
}

.slot-modal-date-nav .slot-modal-select {
  flex: 0 0 auto;
  width: 116px;
  text-align: center;
  cursor: default;
}

/* ── Party Mode: Regular/Party toggle switch ────────────────────────────
   Same pill-with-sliding-thumb pattern as portal.css's .obs-switch (the
   OBS Control widget's scene switch), just renamed for this context and
   sized for the toggle modal's narrower width instead of the portal's
   on-air banner. */
.party-switch {
  position: relative;
  display: flex;
  align-items: center;
  background: #141414;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px;
  user-select: none;
  width: 100%;
}

.party-switch-thumb {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 3px;
  width: calc(50% - 3px);
  background: var(--accent);
  border-radius: 999px;
  transition: transform 0.25s ease, background 0.2s;
  z-index: 0;
}

.party-switch-thumb.pos-party {
  transform: translateX(100%);
  background: var(--party);
}

.party-switch-seg {
  position: relative;
  z-index: 1;
  flex: 1 1 0;
  border: none;
  background: none;
  padding: 8px 8px;
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  text-align: center;
  /* var(--text-2) measured at only 4.34 contrast against this switch's
     #141414 track -- right at the edge of WCAG AA failure, and the
     specific "hard to read" complaint. var(--text) (the app's standard
     bright body-text color) measures 15.64 here. */
  color: var(--text);
  white-space: nowrap;
  cursor: pointer;
  border-radius: 999px;
  transition: color 0.2s;
}

.party-switch-seg.active {
  color: #000;
  cursor: default;
}

.party-slot-modal-row {
  display: flex;
  gap: 10px;
  margin-top: 10px;
}

.party-slot-modal-row .slot-modal-select {
  flex: 1 1 0;
}

.party-warning-text {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.5;
  /* Halfway between the original --text-2 (#6b7d8e) and pure white --
     matches the TIME header's final tuned brightness (the earlier
     incremental 10% rounds proved too subtle to perceive), scoped here
     rather than touching the shared variable itself, which is used by
     13+ other templates. */
  color: #b5bec6;
}

/* ── Settings > WhatsApp tab ──────────────────────────────────────────────
   "Status"/"Unlink" were dim <h3> sub-headers with no bright <h2> anchor
   heading above them at all, unlike every other Settings tab (which all
   open with a plain <h2>, e.g. "OBS Control", "Email" -- var(--text),
   15px, bold, uppercase by default). Promoted both to <h2> in the
   template instead of approximating the look with a custom class, so
   they're pixel-identical to every other tab's heading, not just close.
   Body text was following this page's existing (very dim/small)
   convention too: plain .text-muted, no font-size bump at all. Flagged as
   a pervasive pattern worth fixing on sight going forward, not just here
   -- see planning/handover-prompt.md. */
.whatsapp-detail-text {
  /* Halfway between var(--text) (#e8edf2, read as too close to the
     headers' brightness) and the original var(--text-2) (#6b7d8e). */
  color: #aab5c0;
  font-size: 15px;
}

/* Two separate causes of the same visible misalignment against the plain
   sibling text ("Linked to ..."):
   1. .flash's own internal "align-items: flex-start" (needed elsewhere
      for a real flash message's text + close-button layout) pinned this
      badge's text near the top of its own padded box. This badge never
      has a close button (app.js's auto-dismiss sweep is scoped away from
      it), so centering its own content is always safe here.
   2. .flash's "margin-bottom: 8px" (needed to stack multiple real flash
      messages vertically in .flash-messages) is 0 on top / 8px on
      bottom -- an asymmetric margin box that the outer row's own
      align-items:center then centers as a whole, shifting the badge's
      *visible* content 4px above where the margin-less sibling text
      lands. Zeroed here since this badge is a single standalone element,
      never stacked with others. */
#whatsapp-status-badge {
  align-items: center;
  margin-bottom: 0;
}
