/* ── oneTRacs App Styles ──────────────────────────────────────────────── */

:root {
  --sidebar-bg:     #0f1923;
  --sidebar-hover:  #1a2a3a;
  --accent:         #00bcd4;
  --accent-dark:    #0097a7;
  --sidebar-text:   #c8d6e5;
  --sidebar-ink:    #ffffff;   /* strong text: hover/active labels */
  --sidebar-width:  240px;
}

/* ── Layout ─────────────────────────────────────────────────────────── */
body { background: #f4f6f9; font-size: var(--app-font-size, 0.875rem); }
[data-bs-theme="dark"] body { background: var(--bs-body-bg); }

/* Lock the shell to the viewport; scrolling happens inside the content area,
   not on the body, so the fixed header/footer stay put. */
#wrapper { height: 100vh; height: 100dvh; overflow: hidden; }

/* ── Theme customizer panel ──────────────────────────────────────────── */
#themePanel { width: 320px; }
.theme-section { padding: 0.9rem 0; border-bottom: 1px solid var(--bs-border-color); }
.theme-section:last-child { border-bottom: 0; }
.theme-label { display: block; font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--bs-secondary-color); margin-bottom: .55rem; }
.offcanvas-footer { background: var(--bs-body-bg); }

.accent-swatch {
  width: 28px; height: 28px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
  position: relative;
  transition: transform .12s ease, box-shadow .12s ease;
}
.accent-swatch:hover { transform: scale(1.12); }
.accent-swatch.selected {
  border-color: var(--bs-body-color);
  box-shadow: 0 0 0 2px var(--bs-body-bg), 0 0 0 4px currentColor;
}
.accent-swatch.selected::after {
  content: "\f00c";
  font-family: "Font Awesome 6 Free";
  font-weight: 900;
  color: #fff;
  font-size: .62rem;
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  text-shadow: 0 0 2px rgba(0,0,0,.5);
}
#themeModeGroup .btn.active,
#sidebarStyleGroup .btn.active,
#densityGroup .btn.active,
#radiusGroup .btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

/* ── Density (drives table / card / form compactness) ─────────────────── */
[data-bs-theme] .card-body { padding: var(--app-density-pad, 1rem) calc(var(--app-density-pad, 1rem) + .2rem); }
[data-bs-theme] .table > :not(caption) > * > * { padding-top: var(--app-density-pad, .5rem); padding-bottom: var(--app-density-pad, .5rem); }

/* ── Reduce motion ────────────────────────────────────────────────────── */
[data-reduce-motion] *,
[data-reduce-motion] *::before,
[data-reduce-motion] *::after {
  animation-duration: .001ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: .001ms !important;
  scroll-behavior: auto !important;
}

/* ── High contrast ────────────────────────────────────────────────────── */
[data-high-contrast] { --bs-secondary-color: var(--bs-body-color); }
[data-high-contrast] .text-muted { color: var(--bs-body-color) !important; opacity: .85; }
[data-high-contrast] .card { border-width: 1px !important; border-color: var(--bs-border-color) !important; }

/* ── Light / accent sidebar variants ──────────────────────────────────── */
/* These key off the RESOLVED variant. The scripts used to write the raw setting
   here, so with the default "auto" none of them applied even on a light bar. */
[data-sidebar="light"] .sidebar-brand { border-bottom-color: rgba(0,0,0,0.08); }
[data-sidebar="light"] .sidebar { border-right: 1px solid var(--bs-border-color); }

/* ── Sidebar ──────────────────────────────────────────────────────────── */
.sidebar {
  width: var(--sidebar-width);
  min-height: 100vh;
  /* Graded rather than flat, so it reads as a lit surface like the device tab
     buttons. Mixed from --sidebar-bg so the dark, light and accent variants all
     get the same treatment without three separate rules. */
  background: linear-gradient(180deg,
    color-mix(in srgb, #fff 5%, var(--sidebar-bg)) 0%,
    var(--sidebar-bg) 40%,
    color-mix(in srgb, #000 9%, var(--sidebar-bg)) 100%);
  color: var(--sidebar-text);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  z-index: 100;
  transition: width 0.2s;
  flex-shrink: 0;
  box-shadow: 1px 0 16px rgba(15,23,42,0.10);
}

/* The thumb was a hardcoded #2c3e50 -- near-invisible on the light sidebar.
   Deriving it from the text colour makes it correct in every variant. */
.sidebar::-webkit-scrollbar { width: 6px; }
.sidebar::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--sidebar-text) 30%, transparent);
  border-radius: 3px;
}
.sidebar::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--sidebar-text) 50%, transparent);
}

.sidebar-brand { border-bottom: 1px solid rgba(255,255,255,0.08); }
.sidebar-brand .fa { font-size: 1.2rem; }

.sidebar .nav-link {
  position: relative;
  color: var(--sidebar-text);
  padding: 0.5rem 0.7rem;
  border: 1px solid transparent;
  border-radius: 8px;
  margin-bottom: 3px;
  font-size: 0.84rem;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  transition: background .16s ease, color .16s ease, border-color .16s ease,
              transform .16s ease, box-shadow .16s ease;
}
/* A fixed icon column. The glyphs are different widths, so without this every
   label starts at a slightly different x and the list looks ragged. */
.sidebar .nav-icon {
  flex: 0 0 1.3rem;
  width: 1.3rem;
  text-align: center;
  font-size: 0.95rem;
  opacity: 0.85;
  transition: color .16s ease, opacity .16s ease, transform .16s ease;
}
.sidebar .nav-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidebar .nav-link:hover {
  /* This was color:#fff on --sidebar-hover. On the light sidebar that is white
     on #f0f2f5 -- 1.12:1 -- so the item disappeared under the cursor.
     --sidebar-ink is the variant's strong text colour, set beside --sidebar-bg. */
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--accent,#00bcd4) 18%, transparent) 0%,
    color-mix(in srgb, var(--accent,#00bcd4) 10%, transparent) 100%);
  border-color: color-mix(in srgb, var(--accent,#00bcd4) 30%, transparent);
  color: var(--sidebar-ink, #fff);
  transform: translateX(2px);
}
.sidebar .nav-link:hover .nav-icon {
  color: var(--accent,#00bcd4);
  opacity: 1;
  transform: scale(1.06);
}
.sidebar .nav-link:active { transform: translateX(2px) translateY(1px); }
.sidebar .nav-link:focus-visible {
  outline: 2px solid var(--accent,#00bcd4);
  outline-offset: 2px;
}
.sidebar .nav-link.active {
  /* The accent is user-configurable and most of the palette is too light to
     carry white text -- plain white on the default cyan is about 2.1:1. Mixing
     the stops toward black is the same fix the device tabs use, and it keeps the
     chosen hue recognisable while the label stays readable. */
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--accent,#00bcd4) 66%, #000) 0%,
    color-mix(in srgb, var(--accent,#00bcd4) 52%, #000) 100%);
  color: #fff;
  font-weight: 600;
  border-color: color-mix(in srgb, var(--accent,#00bcd4) 70%, #000);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--accent,#00bcd4) 38%, transparent),
              inset 0 1px 0 rgba(255,255,255,0.28);
}
.sidebar .nav-link.active .nav-icon { color: #fff; opacity: 1; }
/* Indicator rail, pulled left to escape the list's own padding. */
.sidebar .nav-link.active::before {
  content: "";
  position: absolute;
  left: -0.5rem;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 62%;
  border-radius: 0 3px 3px 0;
  background: var(--accent,#00bcd4);
  box-shadow: 0 0 8px color-mix(in srgb, var(--accent,#00bcd4) 70%, transparent);
}
.sidebar .nav-link .badge { font-size: 0.68rem; }

.nav-section-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.63rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  /* Measured on the light sidebar: 70% gave 3.39:1 and 82% gave 4.18; 92% clears 4.5. */
  color: color-mix(in srgb, var(--sidebar-text) 92%, transparent);
  padding: 0.55rem 0.75rem 0.25rem;
  text-transform: uppercase;
}
/* A hairline trailing the label groups the section without adding a heavy rule. */
.nav-section-label::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: linear-gradient(90deg, currentColor, transparent);
  opacity: 0.45;
}

.sidebar-footer { border-color: rgba(255,255,255,0.08) !important; }

/* ── Collapsed icon rail ──────────────────────────────────────────────── */
/* The labels are wrapped in .nav-text now. They used to be bare text nodes, so
   the old `.nav-link span` rule matched only the badges and the rail showed
   full labels crushed into 60px, clipped mid-word and wrapping to two lines. */
.sidebar.collapsed { width: 64px; }
.sidebar.collapsed .nav-text,
.sidebar.collapsed .sidebar-brand span,
.sidebar.collapsed .nav-link .badge { display: none; }
.sidebar.collapsed .nav-link {
  justify-content: center;
  padding-left: 0.4rem;
  padding-right: 0.4rem;
  gap: 0;
}
.sidebar.collapsed .nav-icon { flex-basis: auto; width: auto; font-size: 1.02rem; }
.sidebar.collapsed .nav-link.active::before { left: -0.4rem; height: 70%; }
/* The section heading becomes a divider rather than disappearing, so the groups
   are still readable as groups in the rail. */
.sidebar.collapsed .nav-section-label { font-size: 0; padding: 0.5rem 0.55rem; gap: 0; }
.sidebar.collapsed .nav-section-label::after { opacity: 0.5; background: currentColor; }
.sidebar.collapsed .sidebar-brand {
  justify-content: center;
  padding-left: 0.25rem !important;
  padding-right: 0.25rem !important;
}
.sidebar.collapsed .sidebar-brand img { height: 34px !important; }
.sidebar.collapsed .sidebar-footer { padding: 0.5rem !important; }
.sidebar.collapsed .sidebar-footer .d-flex { flex-direction: column; gap: 0.35rem; }
.sidebar.collapsed .sidebar-footer .d-flex > a:first-child { display: none !important; }
.sidebar.collapsed .sidebar-footer .btn { margin: 0 !important; }

/* ── Responsive: off-canvas drawer on phones / tablets ────────────────── */
.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.5);
  z-index: 1040;
}
.sidebar-backdrop.show { display: block; }

@media (max-width: 991.98px) {
  /* Slide the sidebar off-screen; it overlays content instead of pushing it. */
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    width: var(--sidebar-width) !important;   /* ignore desktop icon-rail collapse */
    transform: translateX(-100%);
    transition: transform 0.25s ease;
    z-index: 1045;
    box-shadow: 4px 0 24px rgba(0,0,0,0.35);
  }
  .sidebar.mobile-open { transform: translateX(0); }

  /* Keep full labels visible even if the desktop "collapsed" flag is set. */
  .sidebar.collapsed .nav-section-label,
  .sidebar.collapsed .sidebar-brand span,
  .sidebar.collapsed .nav-text,
  .sidebar.collapsed .nav-link .badge { display: revert; }
  .sidebar.collapsed .nav-section-label { font-size: 0.63rem; gap: 0.5rem; }
  .sidebar.collapsed .nav-link { justify-content: flex-start; gap: 0.6rem; }
  .sidebar.collapsed .nav-icon { flex: 0 0 1.3rem; width: 1.3rem; }

  #page-content-wrapper { width: 100%; }

  /* Roomier topbar tap targets, breadcrumb already hidden < md. */
  .topbar .input-group { min-width: 0; }
}

/* Phones: tighten outer padding so cards use the full width. */
@media (max-width: 575.98px) {
  .container-fluid.px-4 { padding-left: 0.75rem !important; padding-right: 0.75rem !important; }
  .stat-card .stat-value { font-size: 1.5rem; }
  .dashboard-gauge { width: 170px; }
}

/* ── Topbar ───────────────────────────────────────────────────────────── */
/* Pinned above the scrollable main column; stays visible while content scrolls. */
.topbar {
  background: #fff;
  min-height: 52px;
  position: sticky;
  top: 0;
  z-index: 30;
}
[data-bs-theme="dark"] .topbar { background: #1a2332; }

/* ── App shell: flex column — topbar + scrollable main + pinned footer ── */
#page-content-wrapper {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
}
#page-content-wrapper > .container-fluid {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.app-footer {
  flex-shrink: 0;
  background: #fff;
  font-size: 0.8rem;
}
.app-footer #footerClock { font-variant-numeric: tabular-nums; }
[data-bs-theme="dark"] .app-footer { background: #1a2332; border-color: #2d3f55 !important; }

/* ── Cards ────────────────────────────────────────────────────────────── */
.card { border: 1px solid #e2e8f0; box-shadow: 0 1px 3px rgba(0,0,0,0.06); }
.card-header { background: #f8fafc; border-bottom: 1px solid #e2e8f0; font-weight: 600; }

/* Stat cards */
.stat-card { border-left: 4px solid var(--accent); }
.stat-card .stat-icon { width: 48px; height: 48px; border-radius: 12px; display:flex; align-items:center; justify-content:center; font-size:1.3rem; }
.stat-card .stat-value { font-size: 1.8rem; font-weight: 700; line-height: 1; }
.stat-card .stat-label { font-size: 0.78rem; color: #64748b; text-transform: uppercase; letter-spacing: 0.04em; }
.stat-card-link { transition: transform 0.15s, box-shadow 0.15s; cursor: pointer; }
.stat-card-link:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0,0,0,0.12); }
a:has(.stat-card-link) { color: inherit; }

/* ── Status indicators ───────────────────────────────────────────────── */
.status-online  { color: #16a34a; }
.status-offline { color: #dc2626; }

.pulse-dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  margin-right: 5px;
}
.pulse-dot.online  { background: #16a34a; box-shadow: 0 0 0 2px rgba(22,163,74,0.3); animation: pulse 2s infinite; }
.pulse-dot.offline { background: #dc2626; }

@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(22,163,74,0.5); }
  70%  { box-shadow: 0 0 0 6px rgba(22,163,74,0); }
  100% { box-shadow: 0 0 0 0 rgba(22,163,74,0); }
}

/* ── Tables ───────────────────────────────────────────────────────────── */
.table th { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.04em; color: #64748b; font-weight: 600; }
.table-hover tbody tr:hover { background: #f0f9ff; cursor: pointer; }
[data-bs-theme="dark"] .table-hover tbody tr:hover { background: rgba(0,188,212,0.08); }

/* ── Parameter tree ──────────────────────────────────────────────────── */
.param-tree { font-family: monospace; font-size: 0.8rem; }
.param-tree .tree-node { padding: 2px 0; }
.param-tree .tree-toggle { cursor: pointer; user-select: none; }
.param-tree .tree-toggle:hover { color: var(--accent); }
.param-tree .tree-children { padding-left: 1.5rem; border-left: 1px dashed #cbd5e1; margin-left: 0.5rem; }
.param-tree .tree-leaf { color: #1e40af; }
.param-value { color: #15803d; }
.param-type  { color: #9ca3af; font-size: 0.72rem; }
.param-ts    { color: #94a3b8; font-size: 0.7rem; }

/* ── Accent helpers ───────────────────────────────────────────────────── */
.text-accent  { color: var(--accent) !important; }
.bg-accent    { background-color: var(--accent) !important; }
.border-accent { border-color: var(--accent) !important; }
.btn-accent   { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-accent:hover { background: var(--accent-dark); border-color: var(--accent-dark); color: #fff; }

/* ── Device detail tabs ──────────────────────────────────────────────── */
.nav-tabs .nav-link { font-size: 0.84rem; }
.device-info-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.75rem; }
.info-item { background: #f8fafc; border-radius: 8px; padding: 0.75rem 1rem; }
.info-label { font-size: 0.72rem; color: #64748b; text-transform: uppercase; letter-spacing: 0.04em; }
.info-value { font-weight: 600; margin-top: 2px; word-break: break-all; }

/* ── Log/task list ───────────────────────────────────────────────────── */
.task-item { border-left: 3px solid #e2e8f0; padding: 0.5rem 0.75rem; margin-bottom: 0.5rem; background: #fff; border-radius: 0 6px 6px 0; }
.task-item.pending  { border-color: #f59e0b; }
.task-item.done     { border-color: #16a34a; }
.task-item.fault    { border-color: #dc2626; }

/* ── Dark mode ────────────────────────────────────────────────────────── */
[data-bs-theme="dark"] {
  --bs-body-bg: #0d1b2a;
  --bs-body-color: #cbd5e1;
}
[data-bs-theme="dark"] .card { background: #1a2332; border-color: #2d3f55; }
[data-bs-theme="dark"] .card-header { background: #1e2d42; border-color: #2d3f55; }
[data-bs-theme="dark"] .info-item { background: #1e2d42; }
[data-bs-theme="dark"] .topbar { border-color: #2d3f55 !important; }
[data-bs-theme="dark"] .param-tree .tree-leaf { color: #93c5fd; }
[data-bs-theme="dark"] .param-value { color: #6ee7b7; }
[data-bs-theme="dark"] body { background: #0d1b2a; }

/* ── Misc ─────────────────────────────────────────────────────────────── */
.x-small { font-size: 0.72rem; }
.tag-badge { display:inline-block; background:#e0f2fe; color:#0369a1; border-radius:4px; padding:1px 7px; font-size:0.72rem; margin:1px; }
[data-bs-theme="dark"] .tag-badge { background:#1e3a5f; color:#7dd3fc; }
.loading-overlay { position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:9999; display:flex; align-items:center; justify-content:center; }
.spinner-ring { width:48px; height:48px; border:4px solid rgba(255,255,255,0.3); border-top-color:#fff; border-radius:50%; animation:spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.progress-thin { height: 4px; }
.firmware-item { border: 1px solid #e2e8f0; border-radius: 8px; padding: 0.75rem 1rem; }
[data-bs-theme="dark"] .firmware-item { border-color: #2d3f55; }

/* ── Dashboard command center ────────────────────────────────────────── */
.dashboard-head {
  background: #fff;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 1rem;
  box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}

.dashboard-gauge {
  width: 210px;
  max-width: 100%;
  aspect-ratio: 1;
}

.dashboard-gauge canvas {
  width: 100% !important;
  height: 100% !important;
}

.dashboard-gauge-center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.mini-metric {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  border-bottom: 1px solid #e2e8f0;
  padding: 0.55rem 0;
}

.mini-metric:first-child { padding-top: 0; }
.mini-metric:last-child { border-bottom: 0; padding-bottom: 0; }
.mini-metric span { color: #64748b; }
.mini-metric strong { font-size: 1rem; }

.ops-tile {
  display: flex;
  min-height: 86px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.18rem;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  background: #f8fafc;
  color: inherit;
  text-decoration: none;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}

.ops-tile:hover {
  border-color: var(--accent);
  box-shadow: 0 4px 14px rgba(0,0,0,0.08);
  transform: translateY(-2px);
  color: inherit;
}

.ops-tile i { font-size: 1.15rem; }
.ops-tile strong { font-size: 1.25rem; line-height: 1; }
.ops-tile span { color: #64748b; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; }

[data-bs-theme="dark"] .dashboard-head {
  background: #1a2332;
  border-color: #2d3f55;
}

[data-bs-theme="dark"] .mini-metric { border-color: #2d3f55; }
[data-bs-theme="dark"] .ops-tile {
  background: #1e2d42;
  border-color: #2d3f55;
}

/* ── Live dashboard flash (value changed during auto-refresh) ───────────── */
@keyframes liveFlash {
  0%   { background-color: rgba(0,188,212,0.28); }
  100% { background-color: transparent; }
}
.live-flash { animation: liveFlash 0.9s ease-out; border-radius: 4px; }
#liveDot i { transition: color .25s ease; }

/* ── Command palette (Ctrl-K) ──────────────────────────────────────────── */
.cmdk-overlay {
  position: fixed; inset: 0; z-index: 1080;
  background: rgba(15,23,42,0.55);
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: 12vh; backdrop-filter: blur(2px);
}
.cmdk-box {
  width: 100%; max-width: 620px; background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color); border-radius: 14px;
  box-shadow: 0 24px 60px rgba(0,0,0,0.35); overflow: hidden;
}
.cmdk-input-wrap { display: flex; align-items: center; gap: .6rem; padding: .85rem 1rem; border-bottom: 1px solid var(--bs-border-color); }
.cmdk-search-ic { color: var(--bs-secondary-color); }
#cmdkInput { flex: 1; border: 0; outline: 0; background: transparent; color: var(--bs-body-color); font-size: 1rem; }
.cmdk-esc { font-size: .7rem; color: var(--bs-secondary-color); border: 1px solid var(--bs-border-color); border-radius: 5px; padding: 1px 6px; }
.cmdk-results { max-height: 52vh; overflow-y: auto; padding: .35rem; }
.cmdk-item {
  display: flex; align-items: center; gap: .7rem; padding: .55rem .75rem;
  border-radius: 9px; color: var(--bs-body-color); text-decoration: none; cursor: pointer;
}
.cmdk-item.active, .cmdk-item:hover { background: rgba(0,188,212,0.14); }
.cmdk-ic { width: 1.1rem; text-align: center; color: var(--accent, #00bcd4); }
.cmdk-label { flex: 1; font-size: .92rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cmdk-kind { font-size: .68rem; text-transform: uppercase; letter-spacing: .04em; color: var(--bs-secondary-color); }
.cmdk-badge { font-size: .68rem; padding: 1px 7px; border-radius: 999px; }
.cmdk-badge.on  { background: rgba(22,163,74,0.16); color: #16a34a; }
.cmdk-badge.off { background: rgba(220,38,38,0.14); color: #dc2626; }
.cmdk-empty { padding: 1.4rem; text-align: center; color: var(--bs-secondary-color); font-size: .9rem; }
.cmdk-foot { display: flex; gap: 1.1rem; padding: .55rem 1rem; border-top: 1px solid var(--bs-border-color); font-size: .72rem; color: var(--bs-secondary-color); }
.cmdk-foot kbd { background: var(--bs-tertiary-bg); border: 1px solid var(--bs-border-color); border-radius: 4px; padding: 0 5px; color: var(--bs-body-color); }

/* ══ tools2 additions ═══════════════════════════════════════════════════
   Everything above is oneTRacs' app.css, copied so the two products look
   the same and a fix there can be brought across as a diff. Only the list
   table below is specific to this app.
   ═══════════════════════════════════════════════════════════════════════ */

/* Sortable column headers */
.table a.th-sort { color: inherit; text-decoration: none; display: inline-flex; gap: .3rem; align-items: center; }
.table a.th-sort:hover { color: var(--accent); }
.table a.th-sort .fa { font-size: .7rem; opacity: .35; }
.table a.th-sort.is-sorted .fa { opacity: 1; color: var(--accent); }

/* Chips reuse Bootstrap's subtle palette so they follow light/dark */
.t2-chip {
  display: inline-block; padding: .12rem .5rem; border-radius: 999px;
  font-size: .72rem; font-weight: 500; white-space: nowrap; line-height: 1.5;
}

.t2-empty { padding: 3rem 1rem; text-align: center; color: var(--bs-secondary-color); }
.t2-empty .fa { font-size: 2rem; opacity: .35; display: block; margin-bottom: .6rem; }

/* A list page's table scrolls inside its card rather than the page */
.t2-scroll { overflow-x: auto; }

/* Legacy links in the rail: mark what still lives on the old panel */
.sidebar .nav-link .nav-ext { margin-left: auto; font-size: .62rem; opacity: .45; }
.sidebar.collapsed .nav-link .nav-ext { display: none; }

/* A list table scrolls sideways inside its card, so cells should not wrap.
   Without this a narrow column holding a timestamp wraps to two lines and that
   single row becomes twice the height of its neighbours -- which reads as a
   rendering fault rather than as long content. Long free text is already cut
   with an ellipsis and a title attribute before it reaches the cell. */
.t2-scroll .table > :not(caption) > * > * { white-space: nowrap; }

/* ── Sidebar menu search ──────────────────────────────────────────────── */
/* Colours come from the sidebar variables so this works on the light, dark and
   accent-tinted sidebars without a per-theme rule. */
.sidebar-search { position: relative; }
.nav-search {
  width: 100%;
  padding: 0.4rem 1.9rem 0.4rem 1.9rem;
  font-size: 0.82rem;
  color: var(--sidebar-ink);
  background: color-mix(in srgb, var(--sidebar-hover) 85%, transparent);
  border: 1px solid color-mix(in srgb, var(--sidebar-text) 22%, transparent);
  border-radius: 8px;
  outline: none;
}
.nav-search::placeholder { color: color-mix(in srgb, var(--sidebar-text) 62%, transparent); }
.nav-search:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}
.nav-search-icon {
  position: absolute; left: 0.6rem; top: 50%; transform: translateY(-50%);
  font-size: 0.75rem; pointer-events: none;
  color: color-mix(in srgb, var(--sidebar-text) 65%, transparent);
}
/* The shortcut hint disappears once there is text, so it never sits under it. */
.nav-search-kbd {
  position: absolute; right: 0.45rem; top: 50%; transform: translateY(-50%);
  font-size: 0.62rem; line-height: 1; padding: 0.15rem 0.3rem; border-radius: 4px;
  color: color-mix(in srgb, var(--sidebar-text) 70%, transparent);
  background: color-mix(in srgb, var(--sidebar-text) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--sidebar-text) 18%, transparent);
}
.nav-search:not(:placeholder-shown) ~ .nav-search-kbd { display: none; }
.nav-search-empty {
  font-size: 0.74rem; padding: 0.5rem 0.2rem 0;
  color: color-mix(in srgb, var(--sidebar-text) 70%, transparent);
}
/* Keyboard cursor. Distinct from .active, which marks the page you are on --
   they can be two different rows at once. */
.sidebar .nav-item.nav-cursor > .nav-link {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  box-shadow: inset 2px 0 0 var(--accent);
}
/* No room for it on the 64px rail, and nothing to type into when the labels
   are hidden anyway. */
.sidebar.collapsed .sidebar-search { display: none; }

/* ── brand ─────────────────────────────────────────────────────────────────
   The logo is 3.37:1 and is only ever given a height; width follows from the
   aspect ratio so it can never be squashed by a flex parent deciding it needs
   to be narrower. width:auto is doing real work here -- Bootstrap's reset sets
   max-width:100% on images, and inside a flex row that is enough to squeeze
   the lockup out of shape on a narrow screen.

   The light-on-dark variant is swapped in by theme rather than by a filter,
   because a filter that lightens the navy type also washes out the cloud. */
.topbar-brand { flex: 0 0 auto; line-height: 0; }
.brand-lockup { height: 30px; width: auto; max-width: none; display: block; }
.brand-mark   { height: 26px; width: auto; max-width: none; display: block; }
.sidebar-mark { height: 22px; width: auto; max-width: none; }

[data-bs-theme="dark"] .brand-on-light { display: none !important; }
[data-bs-theme="dark"] .brand-on-dark  { display: block !important; }

/* A hairline between the company mark and the page title, so the two read as
   separate things rather than as one long heading. */
.topbar-sep {
  width: 1px; align-self: stretch; margin: .15rem 0;
  background: var(--bs-border-color, #dee2e6);
}

@media (max-width: 575.98px) {
  .brand-mark { height: 24px; }
}
/* Between sm and lg the page title and the buttons are both fighting for room;
   shrink the lockup rather than let the title truncate to three characters. */
@media (min-width: 576px) and (max-width: 991.98px) {
  .brand-lockup { height: 24px; }
}

/* ── the utility ten places already assume exists ─────────────────────────
   Bootstrap 5.3 has no min-w-0. Ten elements carry the class and none of them
   got it, so every one of them kept min-width:auto -- which is exactly the
   value that stops a flex item shrinking below its content. The text-truncate
   beside it therefore never truncated: a long device name pushed its row fifty
   pixels past the card and was clipped with no ellipsis, so the end of the
   name was simply gone rather than marked as continuing.

   One declaration repairs every one of them. */
.min-w-0 { min-width: 0 !important; }

/* ── active users ─────────────────────────────────────────────────────────
   Icons carry their brand colour inline, from the same table the old panel
   uses, so a Chrome session is the same green in both products. */
.active-users-table td, .active-users-table th { vertical-align: middle; white-space: nowrap; }
.au-ic { width: 1.1rem; text-align: center; margin-right: .35rem; font-size: .95rem; }
.au-flag { border-radius: 2px; margin-right: .35rem; vertical-align: -2px;
           box-shadow: 0 0 0 1px rgba(0,0,0,.08); }
/* The initial, so a row is identifiable before you have read the name. The
   hue comes from the accent so it follows the chosen theme rather than
   fighting it. */
.au-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: color-mix(in srgb, var(--accent, #00bcd4) 18%, transparent);
  color: var(--accent-dark, #0097a7);
  font-size: .72rem; font-weight: 700;
}
[data-bs-theme="dark"] .au-avatar { color: var(--accent, #00bcd4); }
/* macOS and Linux are near-black in the brand palette, which disappears on a
   dark background; lift just those two. */
[data-bs-theme="dark"] .au-ic[style*="#111827"] { color: #e2e8f0 !important; }

/* ── ping monitor summary strip ───────────────────────────────────────────
   Tiles rather than cards: this sits above a table and has to be readable at
   a glance without taking a third of the screen to do it. */
.ping-tile { display: inline-flex; align-items: baseline; gap: .35rem;
             padding: .2rem .55rem; border-radius: 7px; color: inherit;
             background: var(--bs-tertiary-bg); }
.ping-tile.is-bad  { background: rgba(220,38,38,.12); color: var(--bs-danger-text-emphasis); }
.ping-tile.is-warn { background: rgba(217,119,6,.14); color: var(--bs-warning-text-emphasis); }
a.ping-tile:hover { filter: brightness(.96); }
.ping-tile .pt-n { font-weight: 700; font-size: .95rem; line-height: 1; }
.ping-tile .pt-l { font-size: .7rem; opacity: .8; }

.ping-type { display: inline-flex; flex-direction: column; gap: .18rem;
             min-width: 108px; padding: .15rem .3rem; border-radius: 6px; color: inherit; }
a.ping-type:hover { background: var(--bs-tertiary-bg); }
.ping-type .pty-name { font-size: .68rem; font-weight: 600; letter-spacing: .02em;
                       color: var(--bs-secondary-color); }
.ping-type .pty-bar { height: 4px; border-radius: 999px; background: var(--bs-secondary-bg); overflow: hidden; }
.ping-type .pty-bar > span { display: block; height: 100%; background: var(--bs-success); border-radius: 999px; }
.ping-type .pty-n { font-size: .66rem; }

@media (max-width: 575.98px) {
  .ping-type { min-width: 92px; }
  .ping-tile .pt-l { display: none; }
}

/* ── 24-hour availability strip ───────────────────────────────────────────
   96 buckets of fifteen minutes. Flex with a 1fr basis rather than fixed
   widths, so the strip fills whatever the column gets and every bucket stays
   the same size as its neighbours -- an uneven strip reads as uneven downtime.

   1px minimum: below that a single down bucket in ninety-six disappears, which
   is the one thing this is here to show. */
.avwrap { display: flex; align-items: center; gap: .5rem; min-width: 190px; }
.avbar { display: flex; gap: 1px; flex: 1 1 auto; height: 16px; min-width: 120px;
         border-radius: 3px; overflow: hidden; background: var(--bs-secondary-bg); }
.avbar .avseg { flex: 1 1 0; min-width: 1px; }
.avseg.seg-up   { background: #22c55e; }
.avseg.seg-down { background: #dc2626; }
.avseg.seg-none { background: var(--bs-secondary-bg); }
[data-bs-theme="dark"] .avseg.seg-up { background: #16a34a; }
.avbar-empty { height: 16px; border-radius: 3px; background: var(--bs-secondary-bg); min-width: 120px; }
.avpct { font-size: .72rem; white-space: nowrap; min-width: 3.1rem; text-align: right; }
/* A hovered bucket lifts so you can see which one the tooltip belongs to. */
.avbar .avseg:hover { outline: 1px solid rgba(0,0,0,.35); outline-offset: -1px; }

@media (max-width: 991.98px) {
  .avwrap { min-width: 150px; }
  .avbar { min-width: 92px; }
}

/* A fixed width for the device cell, not a fitted one. Table layout sizes a
   column to its widest cell, so a single long name was setting the width for
   all four hundred rows and pushing the availability strip off the edge. The
   full name is on the element's title, so hovering still gives you all of it. */
.pm-device { width: 15rem; max-width: 15rem; }
.pm-name, .pm-sub { max-width: 15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pm-name { font-weight: 600; }
@media (max-width: 1399.98px) {
  .pm-device { width: 12rem; max-width: 12rem; }
  .pm-name, .pm-sub { max-width: 12rem; }
}

/* ── svn update console ───────────────────────────────────────────────────
   Fixed height and its own scroll: an update emits hundreds of lines and the
   page must not grow by a screen every second while you are reading it. */
.svn-console {
  height: 300px; overflow: auto; margin: 0;
  background: #0f1923; color: #d7e4ee;
  font-size: .76rem; line-height: 1.45; padding: .6rem .75rem;
  border-radius: 6px; white-space: pre-wrap; word-break: break-word;
}
