:root {
  --bg: #0f172a;
  --panel: #1e293b;
  --text: #e2e8f0;
  --muted: #94a3b8;
  --green: #22c55e;
  --yellow: #eab308;
  --red: #ef4444;
  --orange: #f97316;
  --border: #334155;
  /* Used by focus rings and the column drop marker. Was only
     ever referenced with a literal fallback before, so a dark/light
     switch left those bits of chrome behind. */
  --accent: #60a5fa;
}
* { box-sizing: border-box; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  margin: 0;
  padding: 24px;
}
header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}
h1 { font-size: 20px; margin: 0; }
button {
  background: #3b82f6;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
}
button:disabled { opacity: 0.6; cursor: default; }

.summary-cards {
  display: grid;
  /* auto-fit rather than a fixed repeat(5): the Break-fix card is only
     rendered for admins, and only when a break-fix company exists, so
     the row has to look right at both five and six cards. */
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin-bottom: 20px;
}
.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.card .num { font-size: 28px; font-weight: 700; }
.card .label { color: var(--muted); font-size: 13px; }
.card.isolated .num { color: var(--yellow); }
.card.offline .num { color: var(--red); }
.card.healthy .num { color: var(--green); }

.tool-health {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 20px;
}
.tool-pill {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  font-size: 13px;
}
.tool-pill.error { border-color: var(--red); color: #fca5a5; }
.tool-pill.warn { border-color: var(--yellow); color: #fde68a; }

/* Company filter: deliberately its own section ABOVE .filter-bar (rather
   than just another .filter-group inside it, which is where this used to
   live) and visually louder than everything below it -- an accent-colored
   left border plus a slightly larger toggle button, on the same --panel/
   --border/--text palette as every other control on the page so it reads
   as "part of this app, just emphasized" rather than a mismatched
   one-off. This is what lets someone managing several companies' devices
   find and change which company they're looking at BEFORE they even get
   to the hostname search box below it, which was the whole point of
   pulling it out here.
   Only rendered at all when companies_enabled is true (see the template's
   own {% if %}), same as before -- a single-company install never sees
   this section, same as it never saw the old Company filter group. */
.company-filter-bar {
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 3px solid #3b82f6;
  border-radius: 10px;
  padding: 14px 18px;
  margin-bottom: 16px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.company-filter-bar-label {
  color: var(--text);
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
/* Bigger than the standard .dropdown-toggle every other dropdown in the
   filter bar uses (see the shared ".dropdown-toggle, .toolbar-btn" rule)
   -- same shape/family, just scaled up in padding/font-size so it reads
   as the most prominent control in the toolbar, matching the emphasis
   the section itself already has via its accent border. */
.company-filter-toggle {
  padding: 10px 18px;
  font-size: 15px;
  font-weight: 600;
  border-radius: 8px;
  min-width: 200px;
  justify-content: space-between;
}
.company-filter-toggle .dropdown-caret { font-size: 12px; }
.company-filter-toggle:hover { border-color: #3b82f6; }

.filter-bar {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
  margin-bottom: 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.filter-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
/* .search-input-styled: the shared look for every free-text search box in
   the app (Devices tab's #search-input plus the Logs and Snapshots pages'
   own search boxes). This used to be scoped to the #search-input ID only,
   so the Logs and Snapshots pages' text inputs never matched it at all --
   they were rendering as bare, unstyled browser default inputs (white
   background, black text) against this app's dark theme, which is why
   they looked so out of place next to everything else. Selector kept on
   #search-input too (rather than just renaming it) so nothing already
   referencing that ID -- dashboard.js's getElementById('search-input')
   included -- needs to change. */
#search-input, .search-input-styled {
  flex: 1;
  min-width: 200px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 12px;
  color: var(--text);
  font-size: 13px;
}
#search-input:focus, .search-input-styled:focus {
  outline: none;
  border-color: #3b82f6;
}
.results-count {
  color: var(--muted);
  font-size: 13px;
  white-space: nowrap;
}
.clear-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  padding: 8px 14px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.clear-btn:hover { border-color: var(--red); color: #fca5a5; }

.filter-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.filter-group-label {
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
  font-weight: 600;
  letter-spacing: 0.03em;
}
.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.chip {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--muted);
  padding: 5px 12px;
  border-radius: 999px;
  font-size: 12px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.chip:hover { border-color: #64748b; color: var(--text); }

.chip-healthy.active { background: rgba(34, 197, 94, 0.15); border-color: var(--green); color: var(--green); }
.chip-isolated_issue.active { background: rgba(234, 179, 8, 0.15); border-color: var(--yellow); color: var(--yellow); }
.chip-fully_offline.active { background: rgba(239, 68, 68, 0.15); border-color: var(--red); color: var(--red); }
.chip-unknown.active { background: rgba(148, 163, 184, 0.15); border-color: var(--muted); color: var(--text); }
.chip-fresh.active { background: rgba(34, 197, 94, 0.15); border-color: var(--green); color: var(--green); }
.chip-stale.active { background: rgba(234, 179, 8, 0.15); border-color: var(--yellow); color: var(--yellow); }
.chip-missing.active { background: rgba(239, 68, 68, 0.15); border-color: var(--red); color: var(--red); }
.chip-duplicate.active { background: rgba(167, 139, 250, 0.15); border-color: #a78bfa; color: #a78bfa; }
.chip-ignore-high-priority.active { background: rgba(249, 115, 22, 0.15); border-color: #f97316; color: #f97316; }
/* Deliberately the same red as .unexpected-tool-badge, the flag this
   chip filters for -- the chip and the icon it hunts should look like
   the same idea. */
.chip-unexpected-tool.active { background: rgba(239, 68, 68, 0.15); border-color: var(--red); color: var(--red); }

.tool-filter-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
}
.tool-filter-block {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 150px;
}
.tool-filter-name {
  font-size: 12px;
  color: var(--text);
  font-weight: 600;
  /* No capitalize: the template now prints proper labels (ScreenConnect,
     ESET PROTECT, Acronis A/V), and capitalizing those breaks them. */
}

.table-wrapper {
  /* Establishes the scrolling context the sticky header sticks within --
     the page itself scrolls, so "top" below is relative to the viewport. */
  position: relative;
}

table.device-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--panel);
  border-radius: 10px;
}
/* Performance: the main Devices table (#devices-table) can hold 1000+ rows
   with 7+ tool columns. Left at the default table-layout:auto, the browser
   has to inspect cell content across the whole table to work out column
   widths, and re-does that expensive pass on any layout-affecting change
   inside the table (sorting, filtering, the periodic softRefresh patching
   rows in place) -- with a table this size that's a real, measurable
   source of jank, independent of how many rows are actually visible.
   table-layout:fixed makes column widths depend only on these explicit
   widths (or an even split of whatever's left over for columns with none),
   so row layout becomes O(1) per row instead of O(rows) -- this is the
   single biggest lever for making a big HTML table scroll/update smoothly.
   Scoped to #devices-table specifically (not every .device-table) since
   Logs/Snapshots/Users-tab tables have their own differently-proportioned
   columns that would look wrong split evenly under a blanket rule, and
   don't have anywhere near the row count that makes this worth the
   tradeoff. */
#devices-table {
  table-layout: fixed;
  /* Never narrower than its columns add up to. Under table-layout:fixed a
     table narrower than the sum of its column widths shrinks them all
     proportionally, and since the cells are white-space:nowrap their
     content then overflows and paints across the column boundaries --
     that is the "columns jumble together" seen when zooming in, which is
     just a narrower viewport in CSS pixels.
     Scrolling sideways is the honest answer for a window too small to
     hold the table, and it keeps the sticky header working: an
     overflow:auto wrapper would become the scroll container and the
     header would stop sticking to the viewport. */
  min-width: max-content;
}
/* Every column needs a width under table-layout:fixed. Keyed on data-col,
   which every column carries; the previous rules keyed on data-tool, and a
   merged Antivirus/Backup column deliberately has no data-tool (it isn't
   one tool). Those two therefore had no width at all, so they split the
   whole leftover width between them -- the "huge gap on the right of the
   Backup column", which grew as the window did. */
#devices-table th[data-col] {
  /* Measured against the headings themselves, which can't wrap (see
     th.sortable) so each one sets its column's floor: the widest generic
     heading is ScreenConnect at 131px including padding and the sort
     arrow. It was 130px, i.e. one pixel short of its own label. */
  width: 136px;
}
/* "Acronis Antivirus" needs 147px. It carries no badges, so it doesn't
   need the 168px the backup columns use -- just enough for its name. */
#devices-table th[data-col="acronis_av"] {
  width: 152px;
}
#devices-table th[data-sort-key="hostname"] {
  /* table-layout:fixed only ever looks at the FIRST row's cell widths (the
     <thead> row here) to size columns -- widths on <td> in other rows are
     ignored by spec, so this th-only rule is all that's needed. */
  width: 240px;
}
#devices-table th[data-sort-key="status"] {
  width: 190px;
}
/* Acronis/Cove cells carry a time string plus several badges instead of
   just a time string -- a little extra room keeps that from crowding the
   next column now that column width is fixed rather than shrink-to-fit.
   The merged columns need it for the same reason: whichever member a row
   resolves to, it draws that tool's badges. */
#devices-table th[data-col="acronis"],
#devices-table th[data-col="cove"],
#devices-table th[data-col="antivirus"],
#devices-table th[data-col="backup"] {
  width: 168px;
}
/* Deliberately NOT adding overflow:hidden here as a table-layout:fixed
   safety net -- the mini-tooltip/issue-tooltip badges inside these cells
   are position:absolute descendants of a cell, and overflow:hidden on the
   cell would clip them off (the exact "tooltip cut off" bug fixed
   earlier). The widths above are chosen generously enough that normal
   content (hostname, company pill, "Nd ago" + up to 3 badges) fits
   comfortably; an unusually long value just overflows visually into the
   next cell instead of being clipped, which is the safer failure mode
   given the alternative breaks a feature entirely. */
/* overflow:hidden was here for rounded corners, but position:sticky breaks on
   any descendant when an ancestor has overflow != visible. Corners are
   rounded per-cell instead so sticky headers keep working. */
.device-table thead tr:first-child th:first-child { border-top-left-radius: 10px; }
.device-table thead tr:first-child th:last-child { border-top-right-radius: 10px; }
.device-table tbody tr:last-child td:first-child { border-bottom-left-radius: 10px; }
.device-table tbody tr:last-child td:last-child { border-bottom-right-radius: 10px; }

.device-table th, .device-table td {
  /* 8px vertical, not 10px. Moving the company pill onto its own line
     (see .company-pill) makes each row taller; trimming the padding pays
     most of that back, and across ~1,600 devices a few pixels a row is a
     lot of scrolling. Horizontal padding is unchanged -- that one is
     load-bearing for the drag grip, which lives in the 14px left gap. */
  padding: 8px 14px;
  text-align: left;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}
.device-table th {
  color: var(--muted);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 11px;
  position: sticky;
  /* Default: sticks flush to the very top of the viewport. This is
     correct for any .device-table that ISN'T directly capped by a
     .table-toolbar above it -- the Users tab table, and the Logs page's
     table both fall in this bucket. Previously this was unconditionally
     "top: 48px" (sized for the Devices tab's Manage/Missing toolbar
     specifically), which meant a table with no such 48px toolbar above it
     left an empty 48px-tall gap at the top of the viewport once scrolled
     -- the header would only "catch" 48px down, so an extra row's worth
     of content stayed visible peeking out above it as you scrolled,
     which is exactly the "there's space at the top that shows more
     devices and it looks weird" bug. */
  top: 0;
  background: var(--panel);
  z-index: 10;
  transition: box-shadow 0.25s ease;
}
/* The 48px offset is now opt-in, via this purely structural selector --
   applies ONLY when a .table-toolbar sits immediately before the
   .table-wrapper (an adjacent sibling, nothing in between), which is
   true for the Devices tab's Manage/Missing toolbar, the Users tab's
   Expand All toolbar, and the Snapshots page's Create Snapshot toolbar --
   every case where there really IS a fixed 48px bar directly above the
   table that the header needs to stack below. No toolbar there (Logs
   page) simply falls through to the top:0 default above instead, with no
   template/class changes needed on those pages at all. */
.table-toolbar + .table-wrapper .device-table th {
  top: 48px;
}
.device-table th.stuck { box-shadow: 0 6px 12px -4px rgba(0, 0, 0, 0.4); }
.device-table th.sortable {
  cursor: pointer;
  /* One line, always. These cells hold a label AND a sort arrow AND a
     spinner, so there are two ways to end up two lines deep: a label with
     a space in it ("ESET PROTECT", "Acronis A/V") breaking across lines,
     or a long single word ("ScreenConnect", "ThreatLocker") pushing the
     arrow down on its own.

     Either way the cell grows to two lines while its neighbours stay at
     one, and because these cells are vertical-align: middle, the taller
     cell's FIRST line is lifted above everyone else's single line -- the
     header reads as though the long names sit higher than the short ones.
     Keeping headers on one line removes the cause rather than realigning
     around it, and also keeps the header row the single fixed height the
     sticky offset above assumes. */
  white-space: nowrap;
}
.device-table th.sortable:hover { color: var(--text); }
.sort-arrow {
  display: inline-block;
  margin-left: 4px;
  font-size: 9px;
  color: var(--muted);
  opacity: 0;
  transform: rotate(0deg);
  transition: transform 0.2s ease, opacity 0.15s ease, color 0.15s ease;
}
.sort-arrow.active { opacity: 1; color: #3b82f6; }
.sort-arrow.desc { transform: rotate(180deg); }

/* Shown next to whichever column header is the current sort key while a
   (re)sort is actually running -- see dashboard.js's setSortLoadingIndicator/
   sortRows. Small enough to sit comfortably in a table header without
   disturbing its height; a plain CSS border-spin rather than an SVG/image
   since it only ever needs to be this one flat color.

   ALWAYS rendered in the markup (see dashboard.html's <th> cells) and
   toggled via the .active class below, rather than being created/removed
   from the DOM on demand -- an element that's added and removed changes
   how much space the header's content box needs, which is exactly what
   was shifting the row/table below it every time the spinner appeared
   ("the loading symbol is moving the data a little"). Keeping it always
   present (just invisible via visibility+opacity, which never affect
   layout) means its box is already accounted for at every render, so
   toggling it on/off can never change any element's size or position. */
.sort-spinner {
  display: inline-block;
  width: 9px;
  height: 9px;
  margin-left: 5px;
  border: 1.5px solid rgba(59, 130, 246, 0.35);
  border-top-color: #3b82f6;
  border-radius: 50%;
  vertical-align: middle;
  visibility: hidden;
  opacity: 0;
}
.sort-spinner.active {
  visibility: visible;
  opacity: 1;
  animation: sort-spinner-spin 0.6s linear infinite;
}
@keyframes sort-spinner-spin {
  to { transform: rotate(360deg); }
}

.cell.fresh { color: var(--green); }
.cell.stale { color: var(--yellow); }
.cell.missing { color: var(--red); }
/* Multi-company support -- this device's company/ies don't use this tool
   at all, so there's nothing to show, and it never counts toward status
   (see core/reconcile.py's apply_role_based_status excluded_tools
   handling). Deliberately muted/inert-looking, distinct from missing
   (red -- a real problem) since N/A isn't a problem at all. */
.cell.na { color: var(--muted); opacity: 0.6; }
.cell-na { font-style: italic; }

.row-isolated_issue { background: rgba(234, 179, 8, 0.08); }
.row-fully_offline { background: rgba(239, 68, 68, 0.06); }

.status-badge { font-weight: 600; text-transform: capitalize; }
.status-healthy { color: var(--green); }
.status-isolated_issue { color: var(--yellow); }
.status-fully_offline { color: var(--red); }
.status-unknown { color: var(--muted); }

.status-inner {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.issue-info-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  cursor: default;
  font-weight: normal;
}
.issue-info-icon:hover,
.issue-info-icon:focus {
  color: var(--text);
  outline: none;
}
.issue-tooltip {
  display: none;
  position: absolute;
  bottom: calc(100% + 8px);
  right: -8px;
  flex-direction: column;
  gap: 4px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  box-shadow: 0 10px 24px -6px rgba(0, 0, 0, 0.5);
  z-index: 200;
  /* Was white-space: nowrap, with no width limit at all -- fine for a
     short line, but an issue message can legitimately run long (e.g.
     "ThreatLocker Not Needed - SomeLongCustomRoleName"), and the Status
     column this sits in is usually near the right edge of the table. A
     long enough line pushed the tooltip's left edge straight past the
     browser's viewport, clipping it -- exactly the "label is impossible
     to fully read" complaint. Capping the width and letting it wrap
     instead keeps every line fully on-screen and readable, just taller
     when there's a lot to say. Widened back up from an initial 280px --
     that read as noticeably narrower than it needs to be for normal-length
     text -- to 360px, with a min() viewport clamp as a second safety net
     so it still can't push past the edge of a genuinely narrow window. */
  max-width: min(360px, 90vw);
  white-space: normal;
  text-transform: none;
}
.issue-info-icon:hover .issue-tooltip,
.issue-info-icon:focus .issue-tooltip {
  display: flex;
}
.issue-tooltip-line {
  color: var(--red);
  font-size: 12px;
  font-weight: 500;
  white-space: normal;
}

.empty-state { text-align: center; color: var(--muted); padding: 30px !important; }
.footnote { color: var(--muted); font-size: 12px; margin-top: 14px; }

.cell {
  white-space: nowrap;
}
.cell-inner {
  display: inline-flex;
  align-items: center;
  /* Anchors .duplicate-badge, which hangs off the left of this box. */
  position: relative;
}
.cell-time {
  display: inline-block;
  /* No min-width. It used to reserve 8 characters so the badges AFTER the
     timestamp lined up across rows; the duplicate badge has moved to the
     left of the text, and the remaining badges (Cove's sections, ESET's
     protection state) read fine ragged. Reserving 8ch for values as short
     as "Online" was widening every tool column for nothing. */
}
.storage-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  flex-shrink: 0;
  margin-left: 2px;
  color: var(--muted);
  opacity: 0.25;
  cursor: default;
  transition: opacity 0.15s ease, color 0.15s ease;
}
.storage-badge svg { display: block; }
.storage-badge.active { opacity: 1; }
.storage-badge.storage-local.active { color: #f59e0b; }
.storage-badge.storage-cloud.active { color: #38bdf8; }

.protection-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  flex-shrink: 0;
  margin-left: 2px;
  cursor: default;
}
.protection-badge svg { display: block; }
.protection-badge.protection-none { visibility: hidden; }
.protection-badge.protection-ok { color: var(--green); }
.protection-badge.protection-warning { color: var(--yellow); }
.protection-badge.protection-critical { color: var(--red); }
.protection-badge.protection-error { color: var(--orange); }

.duplicate-badge {
  /* Sits to the LEFT of the cell's text rather than in front of it.
     In the flow it reserved ~17px in every cell whether or not there was
     a duplicate to report, which indented the timestamps -- and N/A
     cells, which never render this badge, were the only ones actually
     starting at the cell's left edge. That is the "only N/A looks
     left-aligned" effect.
     Absolute against .cell-inner, hanging into the cell's own 14px left
     padding, so every cell's text now starts on the same edge and no
     space is reserved for a badge that is usually absent. */
  position: absolute;
  right: 100%;
  /* 11px + 1px must stay under the cell's 14px left padding. At 13px + 2px
     it overhung the previous column by a pixel -- invisible at 100% zoom,
     and scaled up by browser zoom until it looked like the columns were
     bleeding into each other. */
  margin-right: 1px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 11px;
  color: #a78bfa;
  cursor: default;
  flex-shrink: 0;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.duplicate-badge.active { opacity: 1; }
.duplicate-badge.inactive { pointer-events: none; }
.duplicate-badge svg { display: block; }

/* Shared instant hover tooltip for small badges (duplicate/storage/
   protection/ip-mismatch) -- same mechanism as .issue-info-icon above
   (CSS :hover, not the native title attribute), added consistently so
   every badge tooltip shows immediately and reliably instead of depending
   on the browser's native tooltip dwell timer, which requires the cursor
   to sit still for ~1s and resets on the smallest jitter or nearby DOM
   update (see softRefresh in dashboard.js, which used to rewrite the
   protection badge's innerHTML on every refresh regardless of whether it
   actually changed -- exactly the kind of thing that could silently
   cancel a native tooltip mid-hover). Centered above the badge itself
   (left: 50%; transform: translateX(-50%)) rather than issue-tooltip's
   right-edge alignment, since these badges sit in the middle of a row
   rather than at a fixed right-hand edge. */
.mini-tooltip-wrap {
  position: relative;
}
.mini-tooltip {
  display: none;
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 5px 9px;
  box-shadow: 0 10px 24px -6px rgba(0, 0, 0, 0.5);
  z-index: 200;
  /* Was white-space: nowrap with no width cap -- fine for a short label
     like "Duplicate Detected", but this same element is also used for the
     IP-mismatch badge's tooltip, whose text is a full generated sentence
     ("IP doesn't match Acme Corp's office IP -- device is at 203.0.113.44
     (via atera), expected 203.0.113.5") -- centered on a small badge that
     can sit anywhere across a wide table, a sentence that long routinely
     pushed one side of the tooltip straight past the edge of the browser
     window, clipping it unreadable. Capping the width and wrapping keeps
     it fully on-screen (and still centered) regardless of how long the
     text is or how close to the edge the badge happens to be. Widened
     back up from an initial 240px -- narrower than it needed to be -- to
     300px, with a min() viewport clamp as a second safety net so it still
     can't push past the edge of a genuinely narrow window. */
  max-width: min(300px, 85vw);
  white-space: normal;
  text-align: center;
  font-size: 11px;
  font-weight: 500;
  color: var(--text);
  text-transform: none;
  /* Explicit, because this element is nested inside whichever badge owns
     it and would otherwise inherit that badge's line-height -- which for
     an icon-only badge is legitimately 0, and silently stacks this
     tooltip's wrapped lines on top of one another. */
  line-height: 1.35;
  pointer-events: none;
}
.mini-tooltip-wrap:hover .mini-tooltip,
.mini-tooltip-wrap:focus .mini-tooltip {
  display: block;
}

/* Enlarges the actual hoverable area of every small badge (and
   issue-info-icon below) well beyond its tiny visible icon, without
   changing anything about layout or visual size. An invisible ::before
   overlay, absolutely positioned outside the badge's own box, does this:
   :hover state bubbles up from whatever element is actually under the
   pointer, so hovering this transparent overlay still counts as hovering
   the badge itself and shows its tooltip.

   Horizontal extension is kept small (1.5px each side) because the
   Acronis local/cloud/protection badges sit only 2px apart
   (storage-badge's margin-left) -- 1.5px + 1.5px exactly closes that gap
   without the two neighbors' hit-areas overlapping and fighting over
   which tooltip shows. Vertical extension is much more generous (6px)
   since there's no other content directly above/below within the row to
   collide with. Confirmed via a real headless-browser hover test that
   this closes the dead zone without creating double-tooltip overlap. */
.mini-tooltip-wrap::before,
.issue-info-icon::before {
  content: "";
  position: absolute;
  top: -6px;
  bottom: -6px;
  left: -1.5px;
  right: -1.5px;
}

.ip-mismatch-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 5px;
  color: var(--yellow);
  cursor: default;
  flex-shrink: 0;
}
.ip-mismatch-badge svg { display: block; }

.inventory-unexpected-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 5px;
  width: 12px;
  height: 12px;
  color: var(--orange);
  cursor: default;
  flex-shrink: 0;
  font-weight: 800;
  font-size: 12px;
  line-height: 1;
}

/* Settings page */
.back-link {
  color: var(--muted);
  text-decoration: none;
  font-size: 13px;
}
.back-link:hover { color: var(--text); }

.header-actions {
  display: flex;
  align-items: center;
  gap: 14px;
}
.settings-link {
  color: var(--muted);
  text-decoration: none;
  font-size: 13px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  transition: color 0.15s ease, border-color 0.15s ease;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.settings-link:hover { color: var(--text); border-color: #64748b; }
/* The page you're on. Rendered as a <span> rather than an <a> (see
   _header_nav.html -- a link to where you already are is a small lie), so
   without this it read as a disabled item rather than the selected one.
   Accent border and tinted fill, matching how an active filter chip
   reads elsewhere in the app. */
.settings-link-current {
  color: var(--text);
  border-color: var(--accent);
  background: rgba(96, 165, 250, 0.14);
  cursor: default;
}
.settings-link-current .header-icon { color: var(--accent); }
.header-icon { flex-shrink: 0; }

.settings-banner {
  border-radius: 8px;
  padding: 12px 16px;
  margin-bottom: 20px;
  font-size: 13px;
}
.settings-banner-ok {
  background: rgba(34, 197, 94, 0.1);
  border: 1px solid var(--green);
  color: #bbf7d0;
}
.settings-banner-error {
  background: rgba(239, 68, 68, 0.1);
  border: 1px solid var(--red);
  color: #fca5a5;
}
.settings-banner code {
  background: rgba(255, 255, 255, 0.1);
  padding: 1px 6px;
  border-radius: 4px;
}

.settings-section {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 20px;
  margin-bottom: 16px;
}
.settings-section h2 {
  font-size: 14px;
  text-transform: capitalize;
  margin: 0 0 16px 0;
  color: var(--text);
}

.settings-field {
  margin-bottom: 16px;
}
.settings-field:last-child { margin-bottom: 0; }
.settings-field label {
  display: block;
  font-size: 13px;
  color: var(--text);
  margin-bottom: 6px;
  font-weight: 500;
}
.settings-field input[type="text"],
.settings-field input[type="password"],
.settings-field input[type="number"],
.settings-field textarea {
  width: 100%;
  max-width: 480px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 12px;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
}
.settings-field input:focus,
.settings-field textarea:focus {
  outline: none;
  border-color: #3b82f6;
}
.settings-field textarea { resize: vertical; }
.settings-field input[type="checkbox"] {
  width: 16px;
  height: 16px;
  cursor: pointer;
}
.field-help {
  color: var(--muted);
  font-size: 12px;
  margin: 6px 0 0 0;
}
.settings-section-note {
  margin: -8px 0 16px 0;
}

.settings-field select {
  width: 100%;
  max-width: 480px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 12px;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
}
.settings-field select:focus {
  outline: none;
  border-color: #3b82f6;
}

.company-panel-heading {
  font-size: 13px;
  margin: 20px 0 6px 0;
  color: var(--text);
}
.company-panel-heading:first-of-type { margin-top: 0; }

.company-tool-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 20px;
  margin-top: 10px;
}
.company-tool-checkbox {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text);
  cursor: pointer;
}
.company-tool-checkbox input[type="checkbox"] {
  width: 16px;
  height: 16px;
  cursor: pointer;
}

.company-criteria-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
}
.company-criterion-row {
  display: flex;
  align-items: center;
  gap: 12px;
}
.company-criterion-label {
  width: 160px;
  flex-shrink: 0;
  font-size: 13px;
  color: var(--text);
}
.company-criterion-row select {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
  max-width: 220px;
}

.save-btn {
  background: #3b82f6;
  color: white;
  border: none;
  padding: 10px 24px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  margin-bottom: 40px;
}
.save-btn:hover { background: #2563eb; }
.save-btn:disabled {
  background: var(--muted);
  opacity: 0.45;
  cursor: not-allowed;
}
.save-btn:disabled:hover { background: var(--muted); }

.settings-diff-list {
  margin: 0 0 4px 0;
  padding: 0 0 0 20px;
  max-height: 40vh;
  overflow-y: auto;
  font-size: 13px;
  color: var(--text);
}
.settings-diff-list li { margin-bottom: 6px; }
.settings-diff-list li:last-child { margin-bottom: 0; }

/* Row selection checkboxes */
.checkbox-col {
  width: 36px;
  text-align: center !important;
}
.checkbox-col input[type="checkbox"],
.row-checkbox {
  width: 15px;
  height: 15px;
  cursor: pointer;
}
tr.row-selected {
  background: rgba(59, 130, 246, 0.08);
}

/* Generic dropdown (used by the Roles filter and the Manage toolbar) */
.dropdown {
  position: relative;
  display: inline-block;
}
.dropdown-toggle, .toolbar-btn {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 8px 14px;
  border-radius: 6px;
  font-size: 13px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: border-color 0.15s ease;
}
.dropdown-toggle:hover, .toolbar-btn:hover { border-color: #64748b; }
.dropdown-caret { font-size: 10px; color: var(--muted); }

/* Local/Cloud storage + Protection status filter dropdowns: same
   .dropdown/.dropdown-menu/.dropdown-menu-item mechanism as the Roles
   filter, but sized down to match the plain chip buttons sitting right
   next to them (Fresh/Stale/Missing), not the bigger default .dropdown-
   toggle size used for Roles/Manage. MUST come after the
   ".dropdown-toggle, .toolbar-btn" rule above -- equal specificity (both
   single-class selectors), so whichever is later in the file wins the
   cascade tie. Had this backwards once already (defined earlier in the
   file, so the bigger base rule silently won instead), which is exactly
   the kind of cascade-order bug this project has hit more than once. */
.acronis-filter-toggle {
  padding: 5px 12px;
  font-size: 12px;
  border-radius: 999px;
  gap: 4px;
}
.acronis-filter-toggle .dropdown-caret { font-size: 9px; }
.acronis-filter-dropdown .dropdown-menu {
  min-width: 140px;
  padding: 4px;
}

.dropdown-menu {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 220px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 10px 24px -6px rgba(0, 0, 0, 0.5);
  z-index: 50;
  padding: 6px;
}

.roles-filter-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.roles-dropdown-menu {
  display: none;
  flex-direction: column;
  padding: 6px;
  min-width: 220px;
}

.role-search-input {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  color: var(--text);
  font-size: 12px;
  margin-bottom: 6px;
}
.role-search-input:focus { outline: none; border-color: #3b82f6; }

.role-filter-list {
  /* ~7 items tall (each item is roughly 32px including padding), then
     scrolls for longer lists rather than growing the dropdown indefinitely. */
  max-height: 224px;
  overflow-y: auto;
}

.role-filter-all-item {
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
  padding-bottom: 9px !important;
  font-weight: 600;
}
.dropdown-menu.open { display: block; }
/* Comes after the rule above so it wins the equal-specificity tie and
   correctly gives the roles dropdown a flex column layout (search input
   pinned, list scrollable) instead of the generic block layout. */
.roles-dropdown-menu.open { display: flex; }

.dropdown-menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
  color: var(--text);
}
.dropdown-menu-item:hover { background: rgba(255, 255, 255, 0.05); }
.dropdown-menu-item input[type="checkbox"] { cursor: pointer; }
.dropdown-menu-item.role-dim span { color: var(--muted); }
.dropdown-menu-empty {
  padding: 10px;
  color: var(--muted);
  font-size: 12px;
}

.dropdown-menu-action {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  color: var(--text);
  padding: 8px 10px;
  border-radius: 6px;
  font-size: 13px;
  cursor: pointer;
}
.dropdown-menu-action:hover:not(:disabled) { background: rgba(255, 255, 255, 0.05); }
.dropdown-menu-action:disabled {
  color: var(--muted);
  opacity: 0.4;
  cursor: not-allowed;
}

.missing-devices-list {
  /* Same idea as .role-filter-list -- a handful of rows visible, then
     scrolls for a longer list rather than growing the dropdown
     indefinitely. */
  max-height: 224px;
  overflow-y: auto;
  min-width: 200px;
}
.missing-device-item {
  padding: 7px 10px;
  font-size: 13px;
  color: var(--text);
  display: flex;
  justify-content: space-between;
  gap: 10px;
}
.missing-device-company {
  color: var(--muted);
  font-size: 11px;
  white-space: nowrap;
}

/* Toolbar above the table, sticky and visually distinct from the column headers */
.table-toolbar {
  position: sticky;
  top: 0;
  z-index: 12;
  height: 48px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 8px 12px;
  background: #161f32;
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 10px 10px 0 0;
}
/* Logs page's "Load older events" bar -- deliberately NOT .table-toolbar.
   That class is built to CAP a table from above (sticky to the very top
   of the viewport, rounded top corners, meant to sit flush against the
   table's own now-unrounded top edge) -- correct for the Devices/Users
   tabs' and Snapshots page's toolbars, all of which sit ABOVE their
   table. This one sits BELOW the table instead (it's a "load more" link,
   not a table-capping toolbar), so reusing .table-toolbar made it stick
   to the TOP of the viewport once scrolled past -- a small out-of-context
   box suddenly floating over the table mid-scroll, easy to mistake for a
   broken/unrelated popup ("opens some random menu with basically no
   info"). This is the same visual family (dark panel, rounded, a button
   inside) with none of the sticky/edge-capping behavior that only makes
   sense for a toolbar sitting above a table. */
.load-more-bar {
  display: flex;
  align-items: center;
  padding: 10px 12px;
  margin-top: 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.toolbar-selection-count {
  color: var(--muted);
  font-size: 13px;
}

/* Missing devices sits on the FAR RIGHT of the toolbar, separated from the
   Manage actions on the left -- it's a read-only reference list rather than
   an action, so grouping it with the action buttons read as if it were one.
   margin-left:auto absorbs all the free space in the flex row, which also
   keeps it pinned right regardless of how wide the selection count gets. */
#missing-devices-dropdown {
  margin-left: auto;
}

/* Right-aligned dropdown panel -- .dropdown-menu anchors left:0 by default,
   which on a right-edge button would push the panel off the side of the
   toolbar. */
.dropdown-menu.dropdown-menu-right {
  left: auto;
  right: 0;
}

/* table-wrapper needs its top corners un-rounded now that the toolbar sits
   directly above the table with its own rounded top corners */
.table-wrapper table.device-table {
  border-radius: 0 0 10px 10px;
}
.device-table thead tr:first-child th:first-child,
.device-table thead tr:first-child th:last-child {
  border-radius: 0;
}

/* Modals */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal-box {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 24px;
  position: relative;
  box-shadow: 0 20px 50px -10px rgba(0, 0, 0, 0.6);
}
.modal-box h2 {
  margin: 0 0 12px 0;
  font-size: 16px;
}
.modal-small {
  width: 360px;
  max-width: 90vw;
}
.modal-large {
  width: 80vw;
  height: 80vh;
  max-width: 1100px;
  display: flex;
  flex-direction: column;
}
.modal-close {
  position: absolute;
  top: 14px;
  right: 14px;
  background: transparent;
  border: none;
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 4px;
}
.modal-close:hover { color: var(--text); }
.modal-subtitle {
  color: var(--muted);
  font-size: 13px;
  margin: -6px 0 16px 0;
}
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 18px;
}
.modal-cancel {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
}
.modal-cancel:hover { color: var(--text); border-color: #64748b; }
.modal-confirm {
  background: #3b82f6;
  border: none;
  color: white;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
}
.modal-confirm:hover { background: #2563eb; }
.modal-confirm-danger { background: var(--red); }
.modal-confirm-danger:hover { background: #dc2626; }

#add-role-select, #remove-role-select {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 12px;
  color: var(--text);
  font-size: 13px;
  margin-top: 4px;
}

/* Configure Roles modal contents */
.configure-roles-list {
  flex: 1;
  overflow-y: auto;
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-bottom: 16px;
}
.configure-role-row:last-child { border-bottom: none; }
.configure-role-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--muted);
}
.configure-role-checkboxes {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.delete-role-btn {
  /* Reads as destructive AT REST, not only on hover. It used to be a grey
     icon on a grey button, indistinguishable from the row's other
     controls until you were already pointing at it -- which is the wrong
     way round for the one action here that can't be undone. */
  background: rgba(239, 68, 68, 0.1);
  border: 1px solid rgba(239, 68, 68, 0.4);
  color: #fca5a5;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
/* THE reason the trash icon never appeared, across several attempts to
   add it.

   The <svg> carries width="15" height="15" as ATTRIBUTES, which is all an
   SVG normally needs. But the button above is display:flex, so the svg is
   a flex ITEM, and a flex item's presentational width attribute is only a
   starting hint -- with the default `flex: 0 1 auto` it is free to shrink,
   and in a 28px box with centring it collapsed all the way to zero.
   Measured live on the deployed dashboard: every one of the seven delete
   buttons reported svg_w: 0 while svg_h was 15. The icon was in the DOM
   and being painted at zero width, which is why changing its colour --
   what the previous attempts did -- kept making no difference.

   flex: none opts the svg out of flex sizing entirely; the explicit
   width/height then make the size independent of the attributes. */
.delete-role-btn svg {
  flex: none;
  width: 15px;
  height: 15px;
  display: block;
}

/* Same trap, everywhere else. Any icon inside a flex button is a flex item
   and can be shrunk to nothing by a tight container; an icon has a fixed
   intrinsic size and there is no case in this app where shrinking one is
   the desired outcome. Applied narrowly to buttons so it can't affect
   genuinely flexible inline SVG elsewhere. */
button svg { flex: none; }
.delete-role-btn:hover:not(:disabled) { color: #fff; border-color: var(--red); background: var(--red); }
html[data-theme="light"] .delete-role-btn { color: #b91c1c; }
html[data-theme="light"] .delete-role-btn:hover:not(:disabled) { color: #fff; }
/* Second, independent reason the button was hard to see: every role on a
   typical account is auto-managed (imported from Atera, the inventory
   sheet, a hostname rule, or a company), so every button is disabled --
   and at 0.25 the icon was faint enough to look absent even once it had a
   width. It still has to read as unavailable, so it stays dimmed, just not
   to the point of invisibility. */
.delete-role-btn:disabled { opacity: 0.55; cursor: not-allowed; }
html[data-theme="light"] .delete-role-btn:disabled { opacity: 0.5; }
.healthy-req-dropdown {
  position: relative;
  flex-shrink: 0;
}
.healthy-req-toggle {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 7px 12px;
  border-radius: 6px;
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
}
.healthy-req-toggle:hover { border-color: #64748b; }
.healthy-req-menu {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  flex-wrap: wrap;
  gap: 10px;
  padding: 12px;
  min-width: 260px;
  max-width: 420px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 10px 24px -6px rgba(0, 0, 0, 0.5);
  z-index: 60;
}
.healthy-req-menu.open { display: flex; }

.configure-roles-empty {
  padding: 20px;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}
.configure-roles-new {
  display: flex;
  align-items: center;
  gap: 10px;
}
.configure-roles-new input[type="text"] {
  flex: 1;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 12px;
  color: var(--text);
  font-size: 13px;
}
.new-role-default {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
}
.configure-roles-error {
  color: var(--red);
  font-size: 12px;
  margin: 10px 0 0 0;
}

/* Configure Roles: reorder buttons, name dimming, color swatches */
/* Fixed-width grid columns (not flex/auto-sized) so every row's name,
   device count, Healthy Requirements, color, and checkbox columns line up
   at the same x-position regardless of how long any individual role's text
   is -- long text truncates with an ellipsis inside its own column instead
   of pushing later columns around or wrapping the row onto two lines. */
.configure-role-row {
  display: grid;
  grid-template-columns: 24px 150px 76px 380px 30px auto 28px;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}
.configure-role-reorder {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.role-move-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  width: 20px;
  height: 16px;
  font-size: 8px;
  line-height: 1;
  border-radius: 4px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.role-move-btn:hover:not(:disabled) { color: var(--text); border-color: #64748b; }
.role-move-btn:disabled { opacity: 0.25; cursor: not-allowed; }

.configure-role-name-wrap {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0; /* lets the name below actually shrink/truncate inside a grid column */
  overflow: hidden;
}
.configure-role-name {
  font-size: 14px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.configure-role-name.role-dim { color: var(--muted); }
.atera-imported-badge {
  flex-shrink: 0; /* the icon should never itself get clipped -- only the name text truncates */
  display: inline-flex;
  align-items: center;
  color: var(--muted);
  cursor: default;
}
.configure-role-device-count {
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.healthy-req-group {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.healthy-req-summary {
  font-size: 11px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1;
}

.tool-setting-item {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 74px;
}
.tool-setting-item label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
}
.tool-setting-item select {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 12px;
  border-radius: 6px;
  padding: 4px 6px;
  cursor: pointer;
}
.tool-setting-item select:focus { outline: none; border-color: #3b82f6; }

.tool-setting-group {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.02);
}

.configure-role-swatches {
  display: flex;
  align-items: center;
}
.color-picker-dropdown {
  position: relative;
}
.color-picker-toggle {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  border: 1px solid var(--border);
  cursor: pointer;
  padding: 0;
}
.color-picker-menu {
  display: none;
  /* Overridden to `fixed` with real coordinates the moment it opens (see
     placeColorMenu in dashboard.js). It has to escape
     .configure-roles-list's overflow, which clips descendants regardless
     of z-index -- that's what was cutting the palette off for roles near
     the bottom of the modal. These absolute values are only the
     pre-open/no-JS fallback. */
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  width: 176px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 10px 24px -6px rgba(0, 0, 0, 0.5);
  /* Above .modal-overlay, since a fixed-position menu is no longer
     inside the modal's own stacking context. */
  z-index: 1200;
  padding: 10px;
  flex-direction: column;
  gap: 10px;
}
.color-picker-menu.open { display: flex; }
.color-swatch-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.color-picker-divider {
  height: 1px;
  background: var(--border);
}
.color-wheel-row {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 12px;
  color: var(--muted);
}
.color-wheel-input {
  width: 30px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: none;
  cursor: pointer;
  flex-shrink: 0;
}
.color-remove-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: rgba(239, 68, 68, 0.08);
  border: 1px solid rgba(239, 68, 68, 0.35);
  color: #fca5a5;
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 12px;
  cursor: pointer;
  width: 100%;
}
.color-remove-btn svg { flex-shrink: 0; }
.color-remove-btn:hover { background: rgba(239, 68, 68, 0.16); border-color: var(--red); color: #fecaca; }
.role-color-swatch {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
}
.role-color-swatch.selected {
  border-color: var(--text);
  box-shadow: 0 0 0 2px var(--panel), 0 0 0 3px currentColor;
}

/* Manage User Roles modal */
.manage-user-roles-list {
  max-height: 320px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px;
}
.manage-user-role-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
}
.manage-user-role-row:hover { background: rgba(255, 255, 255, 0.05); }
.manage-user-role-row input[type="checkbox"] { cursor: pointer; }
.manage-user-roles-empty {
  padding: 16px;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}

/* Roles filter toggle button showing selected role names */
.dropdown-toggle.roles-toggle-empty { color: var(--muted); }
#roles-filter-toggle-text {
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Backups */
.snapshot-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  background: rgba(59, 130, 246, 0.12);
  border-bottom: 1px solid #3b82f6;
  padding: 12px 20px;
  font-size: 13px;
  color: #bfdbfe;
}
.snapshot-exit-btn {
  background: #3b82f6;
  color: white;
  text-decoration: none;
  padding: 8px 16px;
  border-radius: 6px;
  font-size: 13px;
  white-space: nowrap;
  flex-shrink: 0;
}
.snapshot-exit-btn:hover { background: #2563eb; }

.trigger-chip {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
}
.trigger-manual { background: rgba(59, 130, 246, 0.15); color: #93c5fd; }
.trigger-scheduled { background: rgba(148, 163, 184, 0.15); color: var(--muted); }

.snapshot-delta { font-size: 11px; font-weight: 600; }
.snapshot-delta.delta-pos { color: var(--green); }
.snapshot-delta.delta-neg { color: var(--red); }
.snapshot-delta.delta-zero { color: var(--muted); }

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

.snapshot-date-input {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 7px 10px;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
}
.date-range-sep { color: var(--muted); font-size: 12px; }

.snapshot-name {
  display: block;
  font-weight: 600;
  color: var(--text);
}
.snapshot-subtime {
  display: block;
  font-size: 11px;
  color: var(--muted);
  margin-top: 2px;
}

.create-snapshot-menu {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  flex-direction: column;
  gap: 12px;
  padding: 14px;
  min-width: 260px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 10px 24px -6px rgba(0, 0, 0, 0.5);
  z-index: 60;
}
.create-snapshot-menu.open { display: flex; }
.create-snapshot-menu input[type="text"] {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  color: var(--text);
  font-size: 13px;
}

.snapshot-view-btn {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  text-decoration: none;
  padding: 6px 12px;
  border-radius: 6px;
  font-size: 12px;
}
.snapshot-view-btn:hover { border-color: #64748b; }
.snapshot-delete-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  width: 26px;
  height: 26px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
}
.snapshot-delete-btn:hover { color: #fca5a5; border-color: var(--red); background: rgba(239, 68, 68, 0.1); }

/* Logs page */
/* Retained for the Snapshots page, which still uses a select. The Logs
   page moved to chip rows matching the Devices tab -- see logs.html. */
.logs-tool-select {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 7px 10px;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
}
.logs-tool-select:focus { outline: none; border-color: #3b82f6; }

.log-status {
  font-weight: 600;
  font-size: 13px;
}
.log-status-online { color: var(--green); }
.log-status-offline { color: var(--red); }
.log-status-arrow {
  color: var(--muted);
  margin: 0 6px;
  font-size: 12px;
}

/* Devices/Users view tabs */
.view-tabs {
  display: flex;
  gap: 4px;
  margin: 20px 0 12px;
  border-bottom: 1px solid var(--border);
}
.view-tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--muted);
  font-size: 14px;
  font-weight: 600;
  padding: 8px 4px 10px;
  margin-right: 16px;
  cursor: pointer;
  font-family: inherit;
}
.view-tab:hover { color: var(--text); }
.view-tab.active { color: var(--text); border-bottom-color: #3b82f6; }

/* Users tab */
.user-expand-col { width: 24px; }
.user-row { cursor: pointer; }
.user-row:hover { background: rgba(255, 255, 255, 0.03); }
.user-expand-caret {
  color: var(--muted);
  font-size: 10px;
  text-align: center;
}
.user-detail-row td {
  background: rgba(0, 0, 0, 0.15);
  padding: 12px 14px;
}
.user-devices-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.user-devices-table th {
  text-align: left;
  color: var(--muted);
  font-weight: 600;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
}
.user-devices-table td {
  padding: 6px 10px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}
.user-devices-table td.cell.fresh { color: var(--green); }
.user-devices-table td.cell.stale { color: var(--yellow); }
.user-devices-table td.cell.missing { color: var(--muted); }
.user-devices-table td.cell.na { color: var(--muted); opacity: 0.6; font-style: italic; }

.user-source-pill {
  display: inline-block;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px 8px;
  font-size: 10px;
  color: var(--muted);
  margin-right: 4px;
  text-transform: capitalize;
}
.user-no-name { color: var(--muted); }

/* Multi-company support -- small pill next to a device's hostname on both
   the Devices and Users tabs, same visual language as .user-source-pill
   above (a "tag" next to a name), only shown at all when companies_enabled
   (i.e. config.yaml actually has a companies: list) is true. */
.company-pill {
  /* BELOW the hostname, indented, rather than beside it. Beside it, the
     hostname column had to be wide enough for the longest hostname AND
     the longest company name on one line -- 40+ characters of company
     name is what made this the widest column in the table. Long hostnames
     already pushed it onto its own line, so this is what most rows looked
     like anyway; making it the rule just makes the column a predictable
     width instead of one set by the worst pairing.
     display:block on its own line, width:max-content so the pill hugs its
     text rather than stretching across the column. */
  display: block;
  width: max-content;
  max-width: 24ch;
  /* Truncated with the full name kept in the title attribute (set in
     _device_row.html), because the longest real company name here is 44
     characters and a pill that long re-widens the column this change
     exists to narrow. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-left: 12px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0 7px;
  font-size: 10px;
  /* Tight, for the same reason as the cell padding above: this line is
     new vertical space in every row. */
  line-height: 1.2;
  color: var(--muted);
}

/* --- Service tier ------------------------------------------------------
   Tier belongs to the COMPANY, not the machine, so it is shown here on
   the company pill rather than by colouring the hostname. The hostname's
   colour is the device's role, and there is only one of it -- letting the
   tier take it meant a Gold company's laptop stopped looking like a
   laptop. This way both facts are readable at once.

   Defined as variables so the company filter tiles below and the pill
   here can never drift apart, and so core/company_profile.py's
   TIER_DISPLAY_COLOR has one obvious counterpart to stay in sync with. */
:root {
  /* Warmer and darker than the app's #eab308 yellow token, which read as
     "warning yellow" rather than as a metal. */
  --tier-gold: #dcae3a;
  /* Brighter and paler than Gold, so the two are separable at a glance
     even before the sheen below is taken into account. */
  --tier-gold-premium: #f2cf63;
  /* Silver takes the blue: literal silver-grey was correct as a metal but
     too close to the page's own muted text to register while scanning,
     and Silver is much the commoner tier. */
  --tier-silver: #7dd3fc;
  /* Break-fix takes the pale metal Silver gave up -- and takes it
     EVERYWHERE. .status-break_fix, both break-fix chips and the summary
     card all resolve to this variable now, so break-fix is one colour
     with one definition rather than a tier colour and a separate status
     colour that drifted apart the moment tiers were added. */
  --tier-break-fix: #cbd5e1;
  --tier-disabled: #64748b;
  /* Gold-Premium's "shine": a highlight/base/lowlight ramp, used as a
     gradient rather than a flat fill so the tier reads as metallic and
     more important than plain Gold at a glance. */
  --tier-gold-premium-hi: #fff4c9;
  --tier-gold-premium-lo: #b8860b;
}

/* Break-fix is nearly white and Disabled is pale grey -- both fine on the
   dark theme, wrong on the light one, but in OPPOSITE directions: one
   needs to be readable, the other needs to recede. They are the two greys
   here, so they are also pushed apart rather than both landing on
   mid-slate. */
html[data-theme="light"] {
  --tier-gold: #9a6f14;
  --tier-gold-premium: #85610c;
  --tier-silver: #0369a1;
  /* Break-fix goes DARKER here than the mid-slate it started on: on a
     white page it is real text -- the status column, both chips, the
     summary card -- and has to be read, not just noticed. */
  --tier-break-fix: #475569;
  /* Disabled goes LIGHTER. It was the darkest tier on the light theme,
     which made the one thing that should recede the loudest thing in the
     grid. On the dark theme #64748b sits about 3.4:1 against the page and
     reads as faded; this is the light-theme equivalent of that ratio, so
     Disabled feels switched off in both. */
  --tier-disabled: #94a3b8;
  --tier-gold-premium-hi: #c99a1e;
  --tier-gold-premium-lo: #6b4d08;
}

/* One variable per element, set once from the tier, then used for text,
   border and fill alike. Everything below reads --tier, so a tier is
   added by giving it a colour in exactly one place. */
.company-pill[data-tier="gold"],        .company-tile[data-tier="gold"]        { --tier: var(--tier-gold); }
.company-pill[data-tier="gold_premium"],.company-tile[data-tier="gold_premium"]{ --tier: var(--tier-gold-premium); }
.company-pill[data-tier="silver"],      .company-tile[data-tier="silver"]      { --tier: var(--tier-silver); }
.company-pill[data-tier="break_fix"],   .company-tile[data-tier="break_fix"]   { --tier: var(--tier-break-fix); }
.company-pill[data-tier="disabled"],    .company-tile[data-tier="disabled"]    { --tier: var(--tier-disabled); }

/* NOTE ON SPECIFICITY, which broke three separate things here.
   `[data-tier]:not([data-tier=""])` scores 0,3,0 while a plain
   `[data-tier="gold_premium"]` scores only 0,2,0 -- so the generic rule
   beat every Gold-Premium override no matter what order they appeared in.
   That is why the pill had no gradient (the generic `background:`
   shorthand reset background-image to none) and why the tile showed TWO
   left lines (the generic solid border survived under the ramp). Every
   Gold-Premium rule below therefore repeats the same :not() so it matches
   0,3,0 and wins on document order instead. */

/* The pill keeps its NEUTRAL chrome -- same faint fill, same hairline
   border, same muted text as an untiered company -- and says the tier with
   a single 2px bar instead.

   The previous version stacked four signals at once: coloured text AND a
   45%-opacity coloured border AND a 12% coloured fill AND weight 600. Any
   one of those says "tier" perfectly well; all four together made a label
   that sits beside every hostname on up to 1600 rows shout louder than the
   hostname it belongs to. The bar also echoes the stripe on the company
   filter tiles, so the two places a tier appears now read as the same
   idea rather than two unrelated treatments. */
.company-pill[data-tier]:not([data-tier=""]) {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.company-pill[data-tier]:not([data-tier=""])::before {
  content: "";
  width: 2px;
  height: 11px;
  border-radius: 1px;
  background: var(--tier);
  /* flex: none, or a narrow pill would squeeze the bar to nothing -- the
     same trap that hid the delete-role icon for months. */
  flex: none;
}

/* --- Company filter tiles ---------------------------------------------
   A 3px edge alone was too easy to miss while scanning forty tiles, so
   the edge is thicker AND the label takes the tier colour. The tile's
   FILL is deliberately left alone: that's what "selected" uses, and it
   has to stay the loudest thing in the grid.

   Squared off on the left so the stripe is a true rectangle rather than
   curving away at top and bottom -- a 6px band following an 8px radius
   tapers to nothing at both ends, which is most of what made it hard to
   read at a glance. Tiles with no tier keep all four corners rounded. */
.company-tile[data-tier]:not([data-tier=""]) {
  border-left: 6px solid var(--tier);
  border-radius: 0 8px 8px 0;
  color: var(--tier);
  font-weight: 600;
}
/* Selection previously overwrote both the label colour and the whole
   border, so a selected tile lost every trace of its tier -- which is
   backwards, since a selected company is the one you're actually looking
   at. Higher specificity than .company-tile.selected, and placed after
   it, so the tier survives; the blue fill and the other three borders
   still carry the selected state on their own. */
.company-tile[data-tier]:not([data-tier=""]).selected,
.company-tile[data-tier]:not([data-tier=""]):hover {
  color: var(--tier);
  border-left-color: var(--tier);
}

/* Gold-Premium: the one tier that should look expensive.
   A vertical metallic ramp -- highlight, base, a second highlight for the
   "polish" catch, then a dark lowlight -- replacing the flat edge
   entirely. Twice the width of the other tiers, which is the point: it
   reads as heavier before the colour is even registered.

   The border is made transparent and the ramp painted over it by the
   ::before below, so the full 12px is gradient rather than 6px of flat
   colour with 6px of gradient beside it. */
.company-tile[data-tier="gold_premium"]:not([data-tier=""]),
.company-tile[data-tier="gold_premium"]:not([data-tier=""]).selected,
.company-tile[data-tier="gold_premium"]:not([data-tier=""]):hover {
  position: relative;
  border-left-width: 12px;
  border-left-color: transparent;
  letter-spacing: 0.2px;
}

/* Long company names truncate here rather than on the tile -- see the
   overflow note above. min-width:0 is what actually lets a flex item
   shrink below its content size; without it the ellipsis never kicks in
   and the name pushes the tile wide instead. */
.company-tile-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The stripe itself.
   Drawn as a pseudo-element rather than a background image so it can be
   CLIPPED. Every other tier's stripe is a real border-left, and where a
   12px left border meets the 1px top and bottom borders the browser mitres
   the join -- the colour runs full height at the outer edge and cuts away
   diagonally as it turns inward. That taper is most of what stops the
   stripe reading as a sticker glued to the side of the tile, and a plain
   background rectangle has none of it, which is why Gold-Premium looked
   flatter than the tiers it is supposed to outrank.
   The clip reproduces that mitre EXACTLY rather than approximating it. A
   real border's chamfer is as deep as the border it MEETS -- the 1px top
   and bottom borders -- however wide the left one happens to be. An
   earlier version scaled it to 5px on the theory that a 12px stripe
   needed a bigger angle to read; it just looked like a different shape
   sitting next to the others. A real border here would chamfer by 1px;
   2px is one notch up from that, enough to be visible against a 12px
   stripe without becoming its own shape. */
.company-tile[data-tier="gold_premium"]:not([data-tier=""])::before {
  content: "";
  position: absolute;
  /* Negative offsets reach back over the tile's own 1px top/bottom borders
     and into the 12px transparent left border the rule above reserved. */
  top: -1px;
  bottom: -1px;
  left: -12px;
  width: 12px;
  background: linear-gradient(
    180deg,
    var(--tier-gold-premium-hi) 0%,
    var(--tier-gold-premium) 38%,
    var(--tier-gold-premium-hi) 52%,
    var(--tier-gold-premium-lo) 100%
  );
  clip-path: polygon(0 0, 100% 2px, 100% calc(100% - 2px), 0 100%);
  pointer-events: none;
}
/* Gold-Premium's bar has to be separable from plain Gold's at a glance,
   and the two colours alone are too close to carry that on a 2px sliver.
   So it gets three cheap differences instead: wider, a metallic ramp
   rather than a flat fill, and a soft bloom that reads as a highlight
   catching the light. Still one small bar -- the point of this treatment
   is restraint -- but unmistakably the shinier of the two. */
.company-pill[data-tier="gold_premium"]:not([data-tier=""])::before {
  width: 3px;
  height: 12px;
  border-radius: 1.5px;
  background: linear-gradient(
    180deg,
    var(--tier-gold-premium-hi) 0%,
    var(--tier-gold-premium) 40%,
    var(--tier-gold-premium-hi) 56%,
    var(--tier-gold-premium-lo) 100%
  );
  box-shadow: 0 0 4px color-mix(in srgb, var(--tier-gold-premium) 55%, transparent);
}

/* --- Sticky page header (Settings, Accounts) ---------------------------
   Pinned to the top of the VIEWPORT rather than sitting at the top of the
   document, so the page title, the sign-out controls and "Back to
   dashboard" stay reachable partway down a long settings page.

   The negative margins + matching padding cancel out <body>'s own 24px
   padding, letting the bar's background run edge to edge instead of
   leaving a strip of page background either side of it as you scroll
   underneath. z-index sits above the table's own sticky header (z-index 12
   on .table-toolbar) so the two can't overlap ambiguously. */
.sticky-page-header {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--bg);
  margin: -24px -24px 20px;
  padding: 16px 24px;
  border-bottom: 1px solid var(--border);
  gap: 14px;
}
.sticky-page-header .header-actions {
  display: flex;
  align-items: center;
  gap: 14px;
}

/* --- Settings save bar -------------------------------------------------
   The mirror image of the header: pinned to the BOTTOM of the viewport so
   Save is always one click away regardless of scroll position, instead of
   being buried under every settings section. Stays inside the <form>, so
   it's still a plain submit button with no JS wiring needed to work. */
.settings-save-bar {
  position: sticky;
  bottom: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 14px;
  background: var(--bg);
  border-top: 1px solid var(--border);
  margin: 24px -24px 0;
  padding: 14px 24px;
}
.settings-save-hint {
  color: var(--muted);
  font-size: 13px;
}

/* --- Login page --------------------------------------------------------
   Deliberately minimal: no navigation, nothing about the fleet, and no
   hint as to whether a given username exists (see app.py's login route,
   which returns one message for both failure modes). */
.login-body {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 24px;
}
.login-card {
  width: 100%;
  max-width: 360px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 28px;
}
.login-title { font-size: 20px; margin: 0 0 4px; }
.login-subtitle { color: var(--muted); font-size: 14px; margin: 0 0 20px; }
.login-form { display: flex; flex-direction: column; gap: 6px; }
.login-form label { font-size: 13px; color: var(--muted); margin-top: 10px; }
.login-form input {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 6px;
  padding: 9px 10px;
  font-size: 14px;
}
.login-form input:focus { outline: 2px solid #3b82f6; outline-offset: 1px; }
.login-btn { margin-top: 18px; width: 100%; padding: 10px; font-size: 14px; }
.login-error {
  background: rgba(239, 68, 68, 0.12);
  border: 1px solid var(--red);
  color: #fecaca;
  border-radius: 6px;
  padding: 10px 12px;
  font-size: 13px;
  margin-bottom: 16px;
}

/* --- Accounts page ----------------------------------------------------- */
.user-row-form {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
  margin-bottom: 8px;
  background: rgba(255, 255, 255, 0.02);
}
.user-row-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.user-row-actions { margin-top: 12px; }
.user-delete-form { margin: 0 0 22px; }

/* --- Accounts page -----------------------------------------------------
   One card per account. The grid below is what keeps the controls aligned:
   every field is a fixed-minimum column that wraps as a unit, so labels,
   dropdowns and help text line up in rows rather than each field sizing
   itself to its own content. */
.account-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 18px;
  margin-bottom: 10px;
  background: rgba(255, 255, 255, 0.02);
}
.account-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding-bottom: 12px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--border);
}
.account-card-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.account-card-title strong { font-size: 15px; }

.account-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 16px;
  align-items: start;
  margin-bottom: 16px;
}
/* The permission dropdowns are narrower and there are five of them, so
   they get their own tighter track size -- otherwise three would stretch
   across a row and the remaining two would look stranded. */
.account-grid-permissions {
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.account-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.account-field-wide { grid-column: span 2; }
@media (max-width: 720px) { .account-field-wide { grid-column: span 1; } }

.account-field > label {
  font-size: 12px;
  color: var(--muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
/* Every control in a field is the same height and full width of its
   column, which is what makes the rows read as aligned. */
.account-field select,
.account-field input[type="text"],
.account-field input[type="password"],
.account-company-toggle {
  width: 100%;
  height: 36px;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 6px;
  padding: 0 10px;
  font-size: 13px;
}
.account-field select:focus,
.account-field input:focus { outline: none; border-color: #3b82f6; }
.account-field .field-help { margin: 0; font-size: 12px; }

.account-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 36px;
  font-size: 13px;
  color: var(--text);
  cursor: pointer;
}
.account-checkbox input { cursor: pointer; }
.account-checkbox span { color: var(--muted); font-size: 12px; }

.account-subheading {
  font-size: 12px;
  color: var(--muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 4px 0 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

/* Password field with the reveal button tucked inside on the right, so the
   input keeps its full column width and nothing shifts when it's clicked. */
.password-wrap { position: relative; display: block; }
.password-wrap input { width: 100%; padding-right: 40px !important; }
.password-toggle {
  position: absolute;
  top: 0;
  right: 0;
  height: 36px;
  width: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  padding: 0;
  border-radius: 0 6px 6px 0;
}
.password-toggle:hover:not(:disabled) { color: var(--text); }
.password-toggle.revealed { color: #3b82f6; }
.password-toggle:disabled { cursor: default; opacity: 0.4; }

/* Company multi-select -- reuses .dropdown-menu/.role-filter-item from the
   dashboard's Company filter so the two look and behave identically. */
.account-company-dropdown { position: relative; }
.account-company-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  text-align: left;
  cursor: pointer;
}
.account-company-toggle:hover:not(:disabled) { border-color: #3b82f6; }
.account-company-toggle:disabled { cursor: default; opacity: 0.6; }
.account-company-menu { min-width: 100%; }
.account-company-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.account-card-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.account-delete-form { margin: 0 0 26px; display: flex; justify-content: flex-end; }

/* --- Company tile grid --------------------------------------------------
   Replaces the old dropdown. A grid of tiles reads faster than a scrolling
   checkbox list once there are more than a handful of companies, and it
   makes the current selection visible at a glance rather than hidden
   behind a closed menu. Collapsed by default (it's a big panel) and
   toggled by the Company button. */
.company-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 8px;
  margin-top: 14px;
  width: 100%;
}
.company-tile {
  /* background-COLOR, not the background shorthand: Gold-Premium paints a
     metallic ramp into background-image, and a shorthand here (or in
     .selected below) would silently reset it. */
  background-color: var(--bg);
  border: 1px solid var(--border);
  color: var(--muted);
  border-radius: 8px;
  padding: 12px 14px;
  font-size: 13px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  min-height: 46px;
  display: flex;
  align-items: center;
  /* Truncation moved onto the inner .company-tile-label span.
     It used to live here as overflow:hidden on the tile itself, which
     clips at the PADDING box -- and Gold-Premium's stripe is a ::before
     sitting at left:-12px, out in the border area. So the bar was being
     painted and then clipped away entirely, invisible. (Every other
     tier's stripe survives that because it's a real border-left, and a
     border is part of the element, not a clipped descendant.) */
  overflow: visible;
  transition: border-color 0.12s ease, background 0.12s ease, color 0.12s ease;
}
.company-tile:hover { border-color: #64748b; color: var(--text); }
/* "Lit up" state -- accent border plus a tinted fill, strong enough to scan
   a large grid and pick out what's on without reading every tile. */
.company-tile.selected {
  background-color: rgba(59, 130, 246, 0.16);
  border-color: #3b82f6;
  color: var(--text);
}
/* All Companies / Unassigned: same shape, visually marked as meta-entries
   rather than real companies. */
.company-tile-meta { font-style: italic; }
.company-tile-meta.selected { background-color: rgba(59, 130, 246, 0.26); }

/* Toggle reflects the empty selection, which is now a real state (nothing
   shown) rather than an implicit "everything". */
.company-filter-toggle.roles-toggle-empty { border-color: var(--border); color: var(--muted); }
.company-filter-toggle.open { border-color: #3b82f6; }

/* Single-company accounts get a plain label instead of a picker. */
.company-filter-bar-locked { gap: 12px; }
.company-locked-name { font-size: 15px; font-weight: 600; color: var(--text); }

/* --- Blocked-by-permission controls -------------------------------------
   A control an account genuinely may not use. Deliberately DIMMER and
   flatter than an ordinary :disabled control (which usually just means
   "not yet applicable, select something first"), and it kills the hover
   affordance entirely so it never looks clickable. Always paired with a
   title explaining why -- see setActionEnabled in static/dashboard.js. */
.permission-blocked,
.permission-blocked:hover {
  opacity: 0.4 !important;
  cursor: not-allowed !important;
  border-color: var(--border) !important;
  color: var(--muted) !important;
  background: transparent !important;
  box-shadow: none !important;
}
button.permission-blocked, .permission-blocked button { pointer-events: auto; }
.dropdown-menu-action.permission-blocked:hover { background: transparent !important; }

/* --- Accounts page: compact pass --------------------------------------
   Tightened so at least two full account cards fit on a 1080p screen. The
   savings come from smaller gaps, shorter controls and a denser permission
   grid rather than from hiding anything -- everything is still on screen,
   just packed closer. */
.account-card { padding: 14px 16px; margin-bottom: 8px; }
.account-card-head { padding-bottom: 9px; margin-bottom: 12px; }
.account-grid { gap: 12px; margin-bottom: 12px; }
.account-grid-permissions { grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 10px; }
.account-field { gap: 4px; }
.account-field select,
.account-field input[type="text"],
.account-field input[type="password"],
.account-company-toggle { height: 32px; font-size: 12.5px; }
.password-toggle { height: 32px; width: 34px; }
.account-checkbox { height: 32px; }
.account-subheading { margin: 0 0 9px; padding-top: 9px; }
.account-card-actions { padding-top: 12px; }
/* Help text under fields is the biggest vertical cost on this page and the
   least load-bearing -- kept, but tightened. */
.account-field .field-help { font-size: 11.5px; line-height: 1.35; }

/* Action bar: Delete pinned left, Cancel/Save right. The spacer is what
   creates the gap, so the two groups stay put no matter how wide the card
   gets or whether Cancel is currently visible. */
.account-actions-spacer { flex: 1; }
.account-card-actions .account-delete-btn { margin-right: auto; }
.account-delete-form { margin: 0; }

/* A section heading with an action button on the right (Accounts' "New
   account"). */
.section-head-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.section-head-row h2 { margin: 0; }

/* --- Correlated-issue alerts -------------------------------------------
   Sits between the poll-status pills and the device table: the pills say
   "is this integration working", these say "it's working but a lot of one
   company's devices are failing the same way". See core/alerts.py. */
.alerts-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 20px;
}
.alert-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: 8px;
  padding: 12px 14px;
}
.alert-warning { border-left-color: var(--yellow); }
.alert-critical { border-left-color: var(--red); }
.alert-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.alert-badge {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 2px 7px;
  border-radius: 999px;
  white-space: nowrap;
}
.alert-warning .alert-badge { background: rgba(234, 179, 8, 0.16); color: var(--yellow); }
.alert-critical .alert-badge { background: rgba(239, 68, 68, 0.16); color: #fca5a5; }
.alert-title { font-size: 13.5px; }
.alert-count { margin-left: auto; color: var(--muted); font-size: 12px; white-space: nowrap; }
.alert-detail { margin: 6px 0 0; color: var(--muted); font-size: 12.5px; line-height: 1.45; }
.alert-devices { margin-top: 8px; }
.alert-devices summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 12px;
  user-select: none;
}
.alert-devices summary:hover { color: var(--text); }
.alert-hostnames { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.alert-hostname {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 2px 7px;
  font-size: 11.5px;
  color: var(--text);
}
.alert-hostname-more { color: var(--muted); font-style: italic; }

/* --- Company grid panel (search + tiles) ------------------------------- */
.company-grid-panel { width: 100%; margin-top: 14px; }
.company-grid-search { width: 100%; margin-bottom: 10px; flex: none; }
.company-grid-panel .company-grid { margin-top: 0; }

/* --- Break-fix ---------------------------------------------------------
   Deliberately muted and neutral. Break-fix isn't a health verdict, it's
   "nobody is watching this" -- giving it green/amber/red would imply a
   judgement the app hasn't made. Slate-blue reads as informational rather
   than as a state to act on. */
/* All of these read --tier-break-fix rather than repeating a hex, so
   break-fix is one colour with one definition -- the status text, the two
   chips, the summary card and the company tier can no longer drift apart
   the way they did when the tier was introduced separately. */
.status-break_fix { color: var(--tier-break-fix); }
/* Disabled reads the same way Break-fix does -- both mean "nobody is
   monitoring this" -- so it borrows the same treatment with its own tier
   colour and the same row dimming. */
.status-disabled { color: var(--tier-disabled); }
.row-disabled td { opacity: 0.72; }
.row-break_fix td { opacity: 0.72; }

/* --- Alerts: compact dashboard strip + full page ------------------------ */
/* The dashboard shows headline-only rows that link through; the /alerts
   page reuses .alert-card as-is for the full detail. */
.alerts-compact .alert-compact-card {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.12s, background 0.12s;
}
.alerts-compact .alert-compact-card:hover { background: var(--border); }
.alerts-compact .alert-title {
  /* One line, ellipsised: the point of the compact view is a fixed,
     predictable height, and a long company name shouldn't be able to wrap
     it into three. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.alerts-see-more {
  align-self: flex-start;
  color: var(--muted);
  font-size: 12.5px;
  text-decoration: none;
  border-bottom: 1px dotted var(--muted);
  padding-bottom: 1px;
}
.alerts-see-more:hover { color: var(--text); border-bottom-color: var(--text); }

.alert-pinned { border-left-color: #a78bfa; }
.alert-pin-badge {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 2px 7px;
  border-radius: 999px;
  white-space: nowrap;
  background: rgba(167, 139, 250, 0.16);
  color: #c4b5fd;
}
.alerts-page-list { margin-top: 16px; }
.alert-actions { display: flex; gap: 8px; margin-top: 10px; }
.alert-action-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  padding: 5px 11px;
  border-radius: 6px;
  font-size: 12.5px;
  cursor: pointer;
}
.alert-action-btn:hover { color: var(--text); border-color: var(--muted); }
.alert-resolve-btn:hover { color: var(--green); border-color: var(--green); }
.alert-priority-btn:hover { color: #c4b5fd; border-color: #a78bfa; }

/* --- Theme toggle ------------------------------------------------------ */
.theme-toggle {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  width: 30px;
  height: 30px;
  border-radius: 6px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex: none;
}
.theme-toggle:hover { color: var(--text); border-color: var(--muted); }
.theme-toggle svg { display: block; }

/* --- Light mode --------------------------------------------------------
   Dark stays the default: :root above is the dark palette, and light is a
   pure OVERRIDE layer on <html data-theme="light">. Written this way round
   deliberately -- every rule in this stylesheet already resolves through
   the variables below, so the bulk of the theme costs nine declarations
   rather than a parallel copy of the file that would drift out of sync the
   first time anyone touched a colour.

   The exceptions are the handful of pale accent colours that are hardcoded
   because they only ever appeared on a dark panel (#fca5a5 red text,
   #bfdbfe, and so on). Those genuinely are unreadable on white, so they get
   explicit darker counterparts below. Everything else -- the rgba() tints,
   the #3b82f6 buttons -- reads correctly on both, so it's left alone.

   The toggle is in static/theme.js; each template also sets the attribute
   inline before the stylesheet loads, so light-mode users never see a
   flash of dark on navigation. */
html[data-theme="light"] {
  --bg: #f1f5f9;
  --panel: #ffffff;
  --text: #0f172a;
  --muted: #64748b;
  /* The status colours are darkened rather than reused: #22c55e and
     #eab308 are tuned for contrast against #0f172a and fail badly as text
     on white, and status text is exactly the thing that has to stay
     legible. */
  --green: #15803d;
  --yellow: #a16207;
  --red: #dc2626;
  --orange: #ea580c;
  --border: #cbd5e1;
  /* #60a5fa is a pale-on-dark accent; on white it reads as washed out,
     so the drop marker gets a stronger blue here for the same reason
     the status colours above are restated. */
  --accent: #2563eb;
  /* Tells the browser to render native controls (checkboxes, date pickers,
     scrollbars) light too -- without it they stay dark and look pasted on. */
  color-scheme: light;
}
html[data-theme="light"] body { background: var(--bg); }
/* The logo sets the name in this navy. Light mode uses it rather than
   the theme's --text (#0f172a), so the header matches the artwork the
   name came from. Dark mode can't: the same navy is invisible on the
   dark background, which is the whole reason the name is live text. */
html[data-theme="light"] .brand-name { color: #261F52; }
/* The dark tint above is a pale blue on near-black; on white it needs
   to be lighter still or the selected item reads as a filled button. */
html[data-theme="light"] .settings-link-current { background: rgba(37, 99, 235, 0.10); }

/* Pale-on-dark accents, restated dark-on-light. */
html[data-theme="light"] .tool-pill.error,
html[data-theme="light"] .clear-btn:hover,
html[data-theme="light"] .settings-banner-error,
html[data-theme="light"] .delete-role-btn:hover:not(:disabled),
html[data-theme="light"] .snapshot-delete-btn:hover,
html[data-theme="light"] .alert-critical .alert-badge { color: #b91c1c; }
html[data-theme="light"] .tool-pill.warn { color: #a16207; }
html[data-theme="light"] .settings-banner-ok { color: #15803d; }
html[data-theme="light"] .storage-badge.storage-cloud.active { color: #0284c7; }
html[data-theme="light"] .alert-pin-badge,
html[data-theme="light"] .alert-priority-btn:hover { color: #6d28d9; }
html[data-theme="light"] .snapshot-banner { color: #1d4ed8; }
html[data-theme="light"] .trigger-manual { color: #1d4ed8; }

/* Rules that reference a dark literal directly rather than a variable. */
html[data-theme="light"] .table-toolbar { background: #e2e8f0; }
html[data-theme="light"] .settings-banner code { background: rgba(15, 23, 42, 0.07); }
html[data-theme="light"] .clear-btn:hover { border-color: var(--red); }
html[data-theme="light"] .modal-cancel:hover { border-color: var(--muted); }

/* Panels sit ON the page rather than glowing out of it in light mode, so
   they need a shadow to keep the same visual separation the dark palette
   got for free from the brightness difference. */
html[data-theme="light"] .card,
html[data-theme="light"] .modal-box,
html[data-theme="light"] .dropdown-menu,
html[data-theme="light"] .alert-card {
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
}

/* login.html has no header bar to hang the toggle off, so it floats in the
   corner instead -- the setting still has to be reachable from the one page
   you see before you're allowed anywhere else. */
.theme-toggle-floating { position: fixed; top: 16px; right: 16px; z-index: 30; }

/* Break-fix summary card. Muted on purpose -- it's a count of machines
   nobody is watching, not a problem to go and fix, so it shouldn't compete
   for attention with Offline. */
.card.break-fix .num { color: var(--tier-break-fix); }

/* --- "list is updating" indicator -------------------------------------
   Answers "can I trust these rows yet?" while a filter, sort, search or
   background refresh is still in flight. Always present in the DOM and
   only ever toggled via .active, so it can't reflow the filter row when
   it appears -- visibility+opacity, never display. */
.list-loading {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 12.5px;
  white-space: nowrap;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.12s ease;
}
.list-loading.active { visibility: visible; opacity: 1; }
.list-loading-dot {
  width: 9px;
  height: 9px;
  border: 2px solid var(--border);
  border-top-color: #3b82f6;
  border-radius: 50%;
  animation: list-loading-spin 0.7s linear infinite;
}
@keyframes list-loading-spin { to { transform: rotate(360deg); } }

/* The rows themselves fading is the part that actually reads at a glance.
   Deliberately not pointer-events:none -- a brief filter pass shouldn't
   swallow a click the person had already committed to. */
.device-table.table-updating tbody { opacity: 0.45; }
.device-table tbody { transition: opacity 0.12s ease; }

/* Respect a reduced-motion preference: keep the state change, drop the
   spin and the fades. */
@media (prefers-reduced-motion: reduce) {
  .list-loading-dot { animation: none; }
  .list-loading, .device-table tbody { transition: none; }
}

/* --- Header: username stacked under Sign out ---------------------------
   The row is otherwise all actions, so a bare username sitting inline
   among them read as one more clickable item. Stacking it keeps the row
   honest and gives the label somewhere to live that isn't a button.

   The username is taken out of flow so .header-account is only as tall as
   Sign out. Centering the header-actions row then lines Sign out up with
   the other buttons instead of lifting the whole stacked block. */
.header-account {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.header-username {
  position: absolute;
  top: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%);
  color: var(--muted);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  line-height: 1.2;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  pointer-events: none;
}
/* Room for the hanging username so it does not collide with the next
   section (dashboard) or the sticky bar's bottom border (Settings / Accounts). */
header:has(.header-account) {
  margin-bottom: 38px;
}
.sticky-page-header:has(.header-account) {
  padding-bottom: 36px;
}

/* Open, on the alerts page. Neutral-to-blue rather than red/purple: it
   only navigates, it doesn't change anything. */
.alert-open-btn { display: inline-flex; align-items: center; gap: 6px; }
.alert-open-btn:hover { color: #93c5fd; border-color: #3b82f6; }
html[data-theme="light"] .alert-open-btn:hover { color: #1d4ed8; }
.alert-readonly-note { margin: 8px 0 0; font-style: italic; }

/* --- Unexpected tool flag -----------------------------------------------
   A device reporting into a tool its company isn't set up for. Red because
   it's a discrepancy someone should resolve, but a FLAG rather than a
   warning triangle: the device isn't faulty, and the triangle is already
   spoken for by the Acronis/Cove/ESET critical protection badge. */
.unexpected-tool-badge {
  display: inline-flex;
  align-items: center;
  margin-left: 5px;
  color: var(--red, #ef4444);
  cursor: help;
  vertical-align: -1px;
}
.unexpected-tool-badge svg { flex: none; }
html[data-theme="light"] .unexpected-tool-badge { color: #b91c1c; }

/* --- Summary: the two unmonitored counts share a slot --------------------
   Break-fix and Disabled both mean "nobody is watching this", so they sit
   outside healthy/isolated/offline/unknown while still counting toward
   Total. Stacking them says they're the same kind of thing; two more
   full-size cards would have implied they were peers of Healthy. */
.card-stack {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0;
  overflow: hidden;
}
.card-stack-half {
  flex: 1;
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 8px 14px;
}
.card-stack-half + .card-stack-half { border-top: 1px solid var(--border); }
.card-stack-half .num { font-size: 20px; font-weight: 600; }
.card-stack-half .label { font-size: 11px; }
.card-stack-half.break-fix .num { color: var(--tier-break-fix); }
.card-stack-half.disabled .num { color: var(--tier-disabled); }

/* --- Company tier presets ----------------------------------------------
   Inside the dropdown panel, so they're only visible while it's open --
   these make a selection rather than being a standing filter. */
.company-tier-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 10px;
}
/* Deliberately identical to .chip above -- same padding, font size, radius
   and transition. These do the same job as the device status chips (pick a
   thing, stay lit while it's picked), so looking subtly different was just
   noise. Kept as its own class rather than reusing .chip because the
   per-tier colouring below would otherwise leak onto the status chips. */
.company-tier-preset {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--muted);
  padding: 5px 12px;
  border-radius: 999px;
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.company-tier-preset:hover:not(:disabled) { border-color: #64748b; color: var(--text); }

/* Lit while every company it groups is selected. This was missing
   entirely -- the JS set .active and nothing in the stylesheet answered
   it, so the presets never appeared to stay on. Each tier lights in its
   own colour, matching the tiles and pills; All and Monitored use the
   app's generic selection blue, since they span several tiers. */
.company-tier-preset.active {
  background: color-mix(in srgb, var(--preset, #3b82f6) 15%, transparent);
  border-color: var(--preset, #3b82f6);
  color: var(--preset, #3b82f6);
}
.company-tier-preset[data-preset="gold_premium"] { --preset: var(--tier-gold-premium); }
.company-tier-preset[data-preset="gold"] { --preset: var(--tier-gold); }
.company-tier-preset[data-preset="silver"] { --preset: var(--tier-silver); }
.company-tier-preset[data-preset="break_fix"] { --preset: var(--tier-break-fix); }
.company-tier-preset[data-preset="disabled"] { --preset: var(--tier-disabled); }
.company-tier-preset[data-preset="gold_premium"]:not(.active),
.company-tier-preset[data-preset="gold"]:not(.active),
.company-tier-preset[data-preset="silver"]:not(.active),
.company-tier-preset[data-preset="break_fix"]:not(.active),
.company-tier-preset[data-preset="disabled"]:not(.active) { color: var(--preset); }

/* Nothing matching this tier, so the preset would select nothing. Left
   visible rather than removed, so the row of presets doesn't reshuffle
   between polls as companies change tier. */
.company-tier-preset:disabled { opacity: 0.35; cursor: not-allowed; }

/* --- Settings diagnostics panels ---------------------------------------
   Read-only. Marked as such so the settings page's dirty-state tracking
   (which watches inputs) can't be confused by them, and so it's visually
   obvious there's nothing here to save. */
.settings-readonly { border-left: 2px solid var(--border); }
.health-check-list { display: flex; flex-direction: column; gap: 8px; }
.health-check {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 9px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  border-left-width: 3px;
  background: var(--bg);
}
.health-check-title { font-size: 13px; font-weight: 600; }
.health-check-detail { font-size: 12px; color: var(--muted); }
.health-error { border-left-color: var(--red); }
.health-error .health-check-title { color: var(--red); }
.health-warn { border-left-color: var(--yellow); }
.health-warn .health-check-title { color: var(--yellow); }
.health-info { border-left-color: #3b82f6; }
.health-info .health-check-title { color: #93c5fd; }
html[data-theme="light"] .health-info .health-check-title { color: #1d4ed8; }

.tier-readout-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.tier-readout-table th {
  text-align: left;
  font-weight: 600;
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
}
.tier-readout-table td { padding: 7px 10px; border-bottom: 1px solid var(--border); }
.tier-readout-table tr:last-child td { border-bottom: none; }
.tier-readout-alias {
  display: block;
  font-size: 11px;
  color: var(--muted);
}
.tier-readout-none { color: var(--muted); }
.tier-readout-link { color: #60a5fa; text-decoration: none; }
.tier-readout-link:hover { text-decoration: underline; }
html[data-theme="light"] .tier-readout-link { color: #1d4ed8; }
.poll-ok { color: var(--green); }
.poll-fail { color: var(--red); }

/* --- Settings: use the horizontal space -------------------------------
   The page was one long column of full-width sections, most holding two
   or three short fields. On a wide screen that's a lot of scrolling past
   empty space. Everything below is layout only -- no field changed. */

/* Short numeric/boolean fields, laid out in columns that collapse on a
   narrow window rather than at a fixed breakpoint. */
.settings-field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px 20px;
}
.settings-field-grid .settings-field { margin-bottom: 0; }

/* Seven tools, two settings each -- cards inside one section instead of
   seven sections. */
.tool-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 12px;
  margin-top: 12px;
}
.tool-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
}
.tool-card-heading {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--muted);
  margin: 0 0 10px;
}
.tool-card .settings-field { margin-bottom: 10px; }
.tool-card .settings-field:last-child { margin-bottom: 0; }

/* The Healthy-criteria rows are a label and a narrow select -- two or
   three per row rather than one. */
.criteria-grid-wide {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
  gap: 8px 24px;
}
.criteria-grid-wide .company-criterion-row { min-width: 0; }
.criteria-grid-wide .company-criterion-row select { flex: 1; min-width: 0; }

/* --- Logs: numbered pages ----------------------------------------------
   Replaces "Load older events", which gave no sense of how far back you
   had gone or how much was left. */
.log-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 14px 0 6px;
}
.log-page {
  min-width: 30px;
  padding: 5px 10px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--muted);
  font-size: 12.5px;
  text-align: center;
  text-decoration: none;
}
.log-page:hover { border-color: #64748b; color: var(--text); }
.log-page-current {
  background: rgba(59, 130, 246, 0.16);
  border-color: #3b82f6;
  color: var(--text);
  font-weight: 600;
}
.log-page-disabled { opacity: 0.35; }
.log-page-gap { color: var(--muted); padding: 0 2px; }
.log-pager-summary { margin-left: auto; font-size: 12px; color: var(--muted); }

/* An audit row's "type" chip. Distinct from the Device/App chips so the
   two kinds of history -- what the poller saw, what a person did -- are
   separable at a glance while sharing one timeline. */
.log-kind-audit {
  background: rgba(167, 139, 250, 0.15);
  border-color: #a78bfa;
  color: #a78bfa;
}
html[data-theme="light"] .log-kind-audit { color: #6d28d9; border-color: #6d28d9; }
.log-actor { font-weight: 600; color: var(--text); }

/* Chips on the Logs page are LINKS, not buttons -- that page filters
   server-side, so a chip navigates. Same look either way; anchors just
   need the button resets a <button> gets for free. */
a.chip {
  display: inline-block;
  text-decoration: none;
  line-height: normal;
}
a.chip:hover { text-decoration: none; }

/* --- Logs filter chips: one colour per value ---------------------------
   Keyed off the data attributes the chips already carry, so a chip needs
   no extra class and the colour lives in exactly one place. Muted until
   selected, matching the device filters -- a row of seven lit chips reads
   as "seven things are on" rather than as a legend. */
.chip[data-facet] { --chip: #94a3b8; }
.chip[data-facet].active {
  background: color-mix(in srgb, var(--chip) 15%, transparent);
  border-color: var(--chip);
  color: var(--chip);
}

/* Event type. Device is about one machine, Tool polling about a whole
   integration -- deliberately unlike any tool colour below, so the two
   rows don't look like they're saying the same thing. */
.chip[data-facet="kind"][data-value="device"] { --chip: #818cf8; }  /* indigo */
.chip[data-facet="kind"][data-value="app"]    { --chip: #f59e0b; }  /* amber  */

/* Change. Same green/red the dashboard already uses for healthy/offline. */
.chip[data-facet="status"][data-value="online"]  { --chip: #22c55e; }
.chip[data-facet="status"][data-value="offline"] { --chip: #ef4444; }

/* Tools, per request. */
.chip[data-facet="tool"][data-value="atera"]         { --chip: #ef4444; }  /* red */
.chip[data-facet="tool"][data-value="screenconnect"] { --chip: #7dd3fc; }  /* light blue */
.chip[data-facet="tool"][data-value="acronis"]       { --chip: #2563eb; }  /* dark blue */
.chip[data-facet="tool"][data-value="cove"]          { --chip: #a78bfa; }  /* purple */
.chip[data-facet="tool"][data-value="eset"]          { --chip: #22c55e; }  /* green */
/* The remaining two, kept clear of the five above. */
.chip[data-facet="tool"][data-value="threatlocker"]  { --chip: #fb923c; }  /* orange */
.chip[data-facet="tool"][data-value="teramind"]      { --chip: #2dd4bf; }  /* teal */

/* User actions -- one colour each, grouped by what they touch: the two
   that change what a device IS are neighbouring violets, the destructive
   one is red, and sign-in stays deliberately quiet. */
.chip[data-facet="category"][data-value="roles"]     { --chip: #a78bfa; }  /* violet */
.chip[data-facet="category"][data-value="devices"]   { --chip: #22d3ee; }  /* cyan */
.chip[data-facet="category"][data-value="settings"]  { --chip: #f59e0b; }  /* amber */
.chip[data-facet="category"][data-value="accounts"]  { --chip: #f472b6; }  /* pink */
.chip[data-facet="category"][data-value="alerts"]    { --chip: #ef4444; }  /* red */
.chip[data-facet="category"][data-value="snapshots"] { --chip: #4ade80; }  /* green */
.chip[data-facet="category"][data-value="auth"]      { --chip: #94a3b8; }  /* slate */

/* Several of these are bright enough to disappear on white. */
html[data-theme="light"] .chip[data-facet="tool"][data-value="screenconnect"] { --chip: #0369a1; }
html[data-theme="light"] .chip[data-facet="tool"][data-value="teramind"]      { --chip: #0d9488; }
html[data-theme="light"] .chip[data-facet="tool"][data-value="cove"]          { --chip: #7c3aed; }
html[data-theme="light"] .chip[data-facet="kind"][data-value="device"]        { --chip: #4f46e5; }
html[data-theme="light"] .chip[data-facet="category"][data-value="roles"]     { --chip: #7c3aed; }
html[data-theme="light"] .chip[data-facet="category"][data-value="devices"]   { --chip: #0e7490; }
html[data-theme="light"] .chip[data-facet="category"][data-value="snapshots"] { --chip: #15803d; }
html[data-theme="light"] .chip[data-facet="category"][data-value="auth"]      { --chip: #475569; }

/* Not a preset -- it doesn't select anything, it decides whether disabled
   companies are in the list at all. Pushed to the far right so it reads as
   a different kind of control from the row of things that do select. */
.company-show-disabled {
  margin-left: auto;
  --preset: var(--tier-disabled);
}
/* Belt and braces. It shares .company-tier-preset for styling, and that
   class carries a :disabled rule for presets that would select nothing --
   which is what greyed this button out and gave it a not-allowed cursor,
   since it has no data-preset and so "selects nothing" by definition. The
   JS no longer touches it (both preset loops are scoped to [data-preset]);
   this makes sure a future one can't either. */
.company-show-disabled:disabled { opacity: 1; cursor: pointer; }

/* --- Snapshot comparison ----------------------------------------------
   Sections collapse because a busy week can produce hundreds of tool
   transitions, and one long undifferentiated list is unreadable. Open by
   default: an empty section isn't rendered at all, so anything present is
   worth seeing. */
.compare-label { font-size: 13px; color: var(--muted); }
.compare-heading {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 4px 0 14px;
  font-size: 13px;
}
.compare-side { font-weight: 600; color: var(--text); }
.compare-arrow { color: var(--muted); }
.compare-heading .results-count { margin-left: auto; }

.compare-section {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 14px;
  margin-bottom: 8px;
}
.compare-section > summary {
  cursor: pointer;
  font-size: 13.5px;
  list-style: none;
}
.compare-section > summary::-webkit-details-marker { display: none; }
.compare-section > summary::before { content: '\25be'; margin-right: 8px; color: var(--muted); }
.compare-section:not([open]) > summary::before { content: '\25b8'; }
.compare-count {
  margin-left: 6px;
  color: var(--muted);
  font-size: 12px;
}
/* Entries flow into as many columns as fit rather than one per line. A
   change is three short facts -- host, what, when -- so a full-width row
   per entry wasted most of the screen and made a busy comparison far
   longer than it needed to be. */
.compare-entries {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
  /* No row gap: the rules below sit flush against each other so each column
     reads as one continuous list. The column gap is wide enough that the
     rules break cleanly between columns without needing a vertical divider. */
  gap: 0 30px;
  margin-top: 10px;
}
.compare-entry {
  display: flex;
  align-items: baseline;
  gap: 8px;
  /* The rule ties the hostname to its timestamp. Without it, a short section
     on a wide screen reads as two unrelated columns of text rather than as
     rows, because the two ends of an entry can sit 300px apart. */
  border-bottom: 1px solid var(--border);
  padding: 4px 0;
  font-size: 12.5px;
  min-width: 0;
}
/* Entries whose middle part is long (role lists, company moves) get a
   whole row rather than being squeezed into a column. */
.compare-entry-wide { grid-column: 1 / -1; }
.compare-hidden { display: none; }
.compare-host {
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.compare-detail { color: var(--muted); white-space: nowrap; }
.compare-tool { color: var(--text); }
.compare-when {
  margin-left: auto;
  color: var(--muted);
  font-size: 11px;
  white-space: nowrap;
  flex: none;
}
.compare-expand {
  margin-top: 8px;
  background: none;
  border: none;
  color: #60a5fa;
  font-size: 12px;
  cursor: pointer;
  padding: 2px 0;
  font-family: inherit;
}
.compare-expand:hover { text-decoration: underline; }
html[data-theme="light"] .compare-expand { color: #1d4ed8; }

.compare-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.compare-added, .compare-removed {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 11.5px;
  border: 1px solid;
}
.compare-added { color: var(--green); border-color: color-mix(in srgb, var(--green) 45%, transparent); background: color-mix(in srgb, var(--green) 12%, transparent); }
.compare-removed { color: var(--red); border-color: color-mix(in srgb, var(--red) 45%, transparent); background: color-mix(in srgb, var(--red) 12%, transparent); }

.compare-delta {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 5px 11px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  font-size: 12.5px;
}
.compare-delta-num { font-weight: 600; }
/* Deliberately not green-good/red-bad: +40 healthy and +40 offline are
   both "up", and colouring them by direction would imply a judgement the
   number alone can't support. */
.compare-delta-num.up { color: var(--text); }
.compare-delta-num.down { color: var(--muted); }
.compare-delta-abs { color: var(--muted); font-size: 11.5px; }

/* --- Rename a role -----------------------------------------------------
   Deliberately quiet: it sits beside the name on every hand-made role, so
   at full strength it would compete with the name itself. Fades up on
   hover/focus, and is always visible to keyboard users. */
.role-rename-btn {
  background: none;
  border: none;
  padding: 0 2px;
  margin-left: 4px;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  flex: none;
  display: inline-flex;
  align-items: center;
  transition: opacity 0.12s ease, color 0.12s ease;
}
.configure-role-row:hover .role-rename-btn,
.role-rename-btn:focus-visible { opacity: 1; }
.role-rename-btn:hover { color: var(--text); }
/* Touch and keyboard have no hover, so the control can't depend on it. */
@media (hover: none) { .role-rename-btn { opacity: 1; } }

.role-rename-input {
  background: var(--bg);
  border: 1px solid #3b82f6;
  color: var(--text);
  border-radius: 5px;
  padding: 2px 6px;
  font-size: 14px;
  font-family: inherit;
  /* min-width:0 so it can shrink inside the row's fixed-width name column
     instead of pushing the grid out of shape. */
  min-width: 0;
  width: 100%;
}
.role-rename-input:focus { outline: none; }

/* --- Companies page ------------------------------------------------- */
/* One centred content column. Everything on this page is a list of short
   values, so past about this width the panels stop being readable rows
   and become text stranded against a wide empty background. Capping the
   PANEL (rather than its contents, which is what this used to do) keeps
   the box edge and the text together at any window size. */
body.cmp-page .settings-section,
body.cmp-page .footnote {
  max-width: 1180px;
  margin-left: auto;
  margin-right: auto;
}
/* The sticky bar keeps its full-bleed background and border -- a centred
   bar would leave unstyled gaps either side when the page scrolls under
   it -- but its content is inset to line up with the text in the panels
   below, so the heading doesn't float off on its own.

   The 44px is 24px of body padding (which the bar cancels with its own
   negative margin) plus 20px of .settings-section padding, so the heading
   lands on the panel's TEXT rather than its border. Note the percentage
   resolves against the body's content box, not the bar's own width --
   measuring the rendered page is the only way to get this right. */
body.cmp-page .sticky-page-header {
  padding-left: calc(max(0px, (100% - 1180px) / 2) + 44px);
  padding-right: calc(max(0px, (100% - 1180px) / 2) + 44px);
}
/* The company list is laid out in real columns rather than stacked cards.
   Name, tier, tools and count are all short values repeating down the
   page, and columns are what let you scan one of them -- "which companies
   have no tier?" -- without reading the other three on every row. The
   same grid template drives the header and every row, so they can't drift
   out of alignment. */
/* Capped rather than full-bleed. Past about this width the columns stop
   being a table and become five things separated by empty space, which is
   harder to read across, not easier. */
.cmp-table { margin-top: 10px; }

.cmp-head,
.cmp-item-head {
  display: grid;
  grid-template-columns: 14px minmax(200px, 2fr) 120px minmax(190px, 1.1fr) 78px 68px;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
}
.cmp-head {
  padding: 0 8px 6px 8px;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  border-bottom: 1px solid var(--border);
}
/* The header has no chevron, but still needs its column so every heading
   sits over the right column. */
.cmp-head .cmp-col-name { grid-column: 2; }
/* The count is right-aligned, so its heading has to be too -- a heading
   sitting left of the numbers it labels reads as belonging to the column
   before it. */
.cmp-head .cmp-col-count { text-align: right; }

/* Section headings on this page are questions ("Which part is the
   company?"). `.settings-section h2` title-cases headings, which turns
   that into "Which Part Is The Company?" -- fine for the noun headings
   everywhere else, wrong for a sentence. Needs the section in the
   selector to outrank it (0,2,1 vs 0,1,1); a bare .cmp-h2 loses. */
.settings-section h2.cmp-h2 { text-transform: none; }

.cmp-item { border-bottom: 1px solid var(--border); }
.cmp-item-head {
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  font-size: 12.5px;
  padding: 7px 8px;
  cursor: pointer;
  border-radius: 4px;
}
.cmp-item-head:hover { background: var(--row-hover, rgba(127, 127, 127, 0.08)); }
.cmp-item-head:focus-visible { outline: 2px solid var(--accent, #60a5fa); outline-offset: -2px; }

/* A permanent, visible affordance. These rows previously looked like
   static text, so the Hide action inside them was never found -- the
   arrow is the whole reason anyone thinks to click. */
.cmp-chevron {
  flex: none;
  color: var(--muted);
  /* An SVG rotates about its viewBox origin by default, which swings the
     arrow out of its cell instead of spinning it in place. fill-box +
     center makes it turn on itself. */
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 0.12s ease;
}
.cmp-item-head[aria-expanded="true"] .cmp-chevron { transform: rotate(90deg); }

.cmp-col-name { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-col-tier { min-width: 0; }
.cmp-col-tools { display: flex; flex-wrap: wrap; gap: 4px; min-width: 0; }
.cmp-col-count { text-align: right; color: var(--muted); font-variant-numeric: tabular-nums; }
.cmp-col-act { text-align: right; }
.cmp-empty { color: var(--muted); opacity: 0.7; }

/* Tool chips reuse the Logs page's per-tool colours so a tool is
   recognisable by colour alone, the same way it is everywhere else. */
.cmp-tool {
  flex: none;
  font-size: 10.5px;
  padding: 1px 6px;
  border-radius: 3px;
  border: 1px solid var(--border);
  color: var(--muted);
}
.cmp-tool[data-tool="atera"] { color: #f87171; border-color: rgba(248, 113, 113, 0.4); }
.cmp-tool[data-tool="screenconnect"] { color: #7dd3fc; border-color: rgba(125, 211, 252, 0.4); }
.cmp-tool[data-tool="acronis"] { color: #60a5fa; border-color: rgba(96, 165, 250, 0.4); }
.cmp-tool[data-tool="cove"] { color: #c084fc; border-color: rgba(192, 132, 252, 0.4); }
.cmp-tool[data-tool="eset"] { color: #4ade80; border-color: rgba(74, 222, 128, 0.4); }

.cmp-tag {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0 4px;
}

/* Expanded detail is a label/value grid so "Matched on" and "Actions"
   line up under each other instead of each being a differently-shaped
   block of prose. */
.cmp-item-body { padding: 2px 8px 12px 34px; }
.cmp-detail {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: 4px 12px;
  align-items: start;
  padding: 4px 0;
}
.cmp-detail-label {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  padding-top: 3px;
}
.cmp-detail-value { min-width: 0; font-size: 12px; }
.cmp-source { display: flex; align-items: baseline; gap: 8px; padding: 2px 0; }
.cmp-source-names { color: var(--muted); word-break: break-word; }
.cmp-source-count { margin-left: auto; flex: none; color: var(--muted); font-size: 11px; }
/* A tool matching a company but reporting nothing for it is the thing
   that makes every device look Atera-missing, so it's called out
   rather than shown as a quiet zero. */
.cmp-source-none { color: var(--amber, #eab308); }
.cmp-inline-help { display: inline; margin-left: 10px; }

/* The merge control reads as one sentence -- "Fold this company into
   [ ... ] [Merge]" -- so its parts sit on one line and wrap together
   rather than stacking into three unrelated widgets. */
.cmp-merge-row { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; }
.cmp-merge-lead { color: var(--muted); }
.cmp-merge-target {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 3px 6px;
  font-size: 12px;
  max-width: 280px;
}
.cmp-merge-row .toolbar-btn[disabled] { opacity: 0.45; cursor: not-allowed; }
.cmp-merge-row .cmp-inline-help { flex: 1 1 100%; margin-left: 0; }

/* One question per row, answer buttons on the right. Used by all three
   list sections so a row means the same thing everywhere on the page. */
.cmp-rows { margin-top: 10px; }
.cmp-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 16px;
  border-bottom: 1px solid var(--border);
  padding: 8px 8px;
  font-size: 12.5px;
}
.cmp-row-label {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  min-width: 0;
  flex: 1 1 380px;
}
.cmp-row-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; flex: none; }
.cmp-row-actions .toolbar-btn { font-size: 12px; padding: 3px 9px; }

.cmp-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-row .cmp-name { max-width: 340px; }
.cmp-meta { color: var(--muted); font-size: 11.5px; }
.cmp-pair { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.cmp-vs { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; }
.cmp-reason { font-style: italic; }
.cmp-arrow { color: var(--muted); }

/* The decision type reads as a label, so a row scans as
   "Merged  <this>  ->  <that>" rather than an undifferentiated sentence. */
.cmp-kind {
  flex: none;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 1px 5px;
}

.toolbar-btn.subtle { color: var(--muted); }
.toolbar-btn.danger { border-color: rgba(248, 113, 113, 0.45); color: #f87171; }
.toolbar-btn.danger:hover { background: rgba(248, 113, 113, 0.12); }

.section-count {
  font-size: 11px;
  font-weight: 500;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 0 6px;
  vertical-align: middle;
}
.cmp-allclear { opacity: 0.85; }
.company-error { color: #f87171; font-size: 11.5px; margin-left: 8px; }

/* Below roughly a laptop width the columns stop helping and start
   truncating everything, so rows fall back to stacking. */
@media (max-width: 900px) {
  .cmp-head { display: none; }
  .cmp-item-head {
    grid-template-columns: 14px 1fr auto;
    grid-template-areas: "chev name count" ". tier tools";
    row-gap: 4px;
  }
  .cmp-item-head .cmp-chevron { grid-area: chev; }
  .cmp-item-head .cmp-col-name { grid-area: name; }
  .cmp-item-head .cmp-col-tier { grid-area: tier; }
  .cmp-item-head .cmp-col-tools { grid-area: tools; }
  .cmp-item-head .cmp-col-count { grid-area: count; }
  .cmp-item-head .cmp-col-act { display: none; }
}

/* A snapshot whose per-company rollup hasn't been built yet. Shown as
   a dash rather than 0, which would read as "every device disappeared". */
.snapshot-uncounted { color: var(--muted); opacity: 0.6; }

/* --- Cove backup-section icons ---------------------------------------- */
/* One icon per backup section, replacing the old local/cloud/protection
   badges. Each is coloured by ITS OWN last successful run, using the same
   three states the cells use -- so a device whose Files backup is current
   but whose System State stopped last week reads that way at a glance,
   which the single combined badge could never show. */
.section-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  /* Deliberately the same geometry as .storage-badge/.protection-badge:
     an 18px box with a 2px gap. Cove and Acronis B/U share a column when
     merged and sit under the same heading, so icons that were spaced
     differently made the two look like different kinds of thing. */
  width: 18px;
  margin-left: 2px;
  cursor: default;
}
/* Same trick the other badges use to kill the inline-layout gap under an
   SVG. NOT line-height: 0 on the badge itself: .mini-tooltip lives inside
   this element, so it INHERITS whatever line-height is set here, and a
   tooltip like "Files and folders: 6h ago" is long enough to wrap -- at
   line-height 0 the wrapped lines render on top of each other. */
.section-badge svg { display: block; }
/* An absolutely positioned box shrink-to-fits against its containing
   block, which for these is the badge itself -- a ~12px icon. Left alone,
   "Files and folders: 6h ago" wrapped to roughly one word per line and the
   300px cap above never came into play. max-content sizes the tooltip to
   its text first, so the cap is what actually limits it.

   Scoped to these badges rather than applied to .mini-tooltip generally:
   the same rule would widen the IP-mismatch tooltip (a full sentence) from
   narrow-and-tall to ~320px, which for a badge near the left edge of the
   table would hang off-screen. Cove's section labels are all short enough
   to stay well inside the cap. */
.section-badge .mini-tooltip { width: max-content; }
/* Deliberately the SAME variables .cell.fresh/.stale/.missing use. Two
   colour scales for one concept is how an icon ends up green beside a red
   cell -- and these vars are re-pointed by the dark theme, so a literal
   hex here would survive the theme switch and quietly stop matching. */
.section-badge.section-fresh { color: var(--green); }
.section-badge.section-stale { color: var(--yellow); }
.section-badge.section-missing { color: var(--red); }

/* --- Refresh now menu -------------------------------------------------- */
/* The items are <button>s, not the <label>+checkbox the filter menus use,
   because these fire an action rather than toggle a state -- so they need
   the button defaults (background, border, inherited font, full width and
   left alignment) explicitly undone to match the shared item styling. */
.refresh-dropdown .dropdown-menu-item {
  width: 100%;
  background: none;
  border: 0;
  font: inherit;
  font-size: 13px;
  text-align: left;
}
/* Refresh all is the default action, so it reads as the primary entry and
   is separated from the per-app list below it. */
.refresh-dropdown .refresh-option[data-refresh-tool=""] { font-weight: 600; }
.refresh-menu-sep {
  height: 1px;
  background: var(--border);
  margin: 5px 4px;
}
/* A single-app refresh skips the cross-tool derivations (see
   run_poll_cycle's only_tools). Saying so here is cheaper than someone
   wondering why a company change didn't appear. */
.refresh-menu-note {
  padding: 6px 10px 4px;
  margin-top: 4px;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 11px;
  line-height: 1.4;
  max-width: 250px;
}

/* --- Draggable column headers ----------------------------------------- */
/* The grip is the ONLY drag surface. The rest of the header keeps its
   existing click-to-sort behaviour, so the two never compete: you cannot
   accidentally start a drag while aiming for the sort arrow. */
/* NOT position: relative. These headers are position:sticky (see
   .device-table th), and a table capped by a .table-toolbar also carries
   top: 48px so the header parks just under it. Re-declaring position here
   outranks the sticky rule -- and because the top offset stays, the header
   stops sticking and is instead pushed 48px DOWN into the first rows of
   the list.
   Nothing is needed in its place: sticky is a positioned value, so the
   header already establishes the containing block .col-grip anchors to. */
.col-grip {
  /* ABSOLUTE, not in the flow. In the flow this 12px control sat in an
     11px header line box and grew the header from 51px to 66px -- and
     since the header is position:sticky under a 48px toolbar, those extra
     15px rode down over the first rows of the list.
     The header's own left padding is 14px and this is 8px wide, so it
     lives inside that padding: no space to reserve, no label to shift,
     and the header keeps exactly the height it had before drag handles
     existed. */
  position: absolute;
  left: 3px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  color: var(--muted);
  cursor: grab;
  /* Hidden until the header is hovered, per the ask. visibility rather
     than display:none so nothing about the header's geometry depends on
     where the pointer is. */
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.12s ease;
}
.device-table th.orderable-col:hover .col-grip { visibility: visible; opacity: 0.75; }
.col-grip:hover { opacity: 1 !important; }
.col-grip:active { cursor: grabbing; }
.col-grip svg { fill: currentColor; pointer-events: none; }

/* The column being carried. */
.device-table th.col-dragging { opacity: 0.45; }
/* Where it would land -- drawn on the header, since a full-height column
   marker would have to be positioned against a scrolling table body. */
.device-table th.col-drop-before { box-shadow: inset 2px 0 0 0 var(--accent); }
.device-table th.col-drop-after  { box-shadow: inset -2px 0 0 0 var(--accent); }
/* Kills the text selection that otherwise follows the pointer during a
   drag across table headers. */
.device-table.reordering, .device-table.reordering * { user-select: none; }

/* Refresh now sits in the table toolbar now, so it takes the toolbar's
   own button sizing rather than the header's. .toolbar-btn already
   supplies that; this only keeps the label and caret on one line. */
.refresh-dropdown .toolbar-btn { white-space: nowrap; }

/* Marks a role on a device as set by hand rather than derived. Quiet on
   purpose: it explains an absence of change, so it should be findable
   when looked for and ignorable otherwise. */
.role-override-tag {
  margin-left: 6px;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
  cursor: help;
}

/* A settings section whose heading carries an action on the right. */
.settings-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  /* Carries the 16px the h2 used to. Zeroing the h2's margin without
     moving it here left the button -- which is taller than the heading
     text -- overlapping the paragraph underneath. */
  margin-bottom: 16px;
}
.settings-section-head h2 { margin-bottom: 0; }
.settings-section-action { text-decoration: none; flex: none; }

/* Breadcrumb in a page title, for a page reached from inside another. */
.page-parent-link {
  color: var(--muted);
  text-decoration: none;
  font-weight: 600;
}
.page-parent-link:hover { color: var(--text); }
.page-parent-sep {
  color: var(--muted);
  margin: 0 8px;
  font-weight: 400;
}

/* --- Wordmark ---------------------------------------------------------- */
/* Name in the page's text colour with the last word in the accent, then a
   hairline rule, then "FLEET" small and tracked. The accent lands on the
   memorable word and at the end of the phrase, so the eye finishes there
   before the rule. */
.brand {
  display: inline-flex;
  align-items: center;
  flex: none;
}
.brand-name {
  font-size: 20px;
  font-weight: 800;
  /* Tighter than a three-letter acronym would want: wide tracking reads
     as deliberate on "IME" and merely loose on twelve characters. */
  letter-spacing: 0.06em;
  color: var(--text);
  white-space: nowrap;
}
/* The mark, as supplied. Its own gradient (violet through cyan) reads on
   both themes, so unlike the name it needs no per-theme treatment. */
.brand-mark {
  height: 28px;
  width: auto;
  margin-right: 10px;
  flex: none;
  display: block;
}
.brand-rule {
  width: 1px;
  height: 17px;
  background: var(--border);
  margin: 0 11px;
  flex: none;
}
.brand-sub {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
/* Measured: the full mark is 213px and the seven nav buttons plus the
   theme toggle are 687px, so the header needs about 985px including the
   page padding. Below that the qualifier goes and the name stays -- it
   is the part that identifies the place. */
@media (max-width: 1000px) {
  .brand-rule, .brand-sub { display: none; }
}

/* --- Sign-in wordmark --------------------------------------------------- */
/* Stacked rather than inline: the card has nothing else in it, so the mark
   leads and the name sits under it, the way the supplied logo is drawn. */
.login-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-bottom: 22px;
}
.login-brand-mark {
  height: 80px;
  width: auto;
  display: block;
}
.login-brand .brand-name { font-size: 24px; }
.login-brand .brand-sub { font-size: 14px; }
.login-brand .brand-rule { height: 20px; }

/* --- "Same device?" review queue ---------------------------------------- */
/* Scrolls rather than growing without limit: the list is short today (17
   pairs on this fleet) but it is driven by how consistently the tools are
   named, which is not something this app controls. */
.device-match-list {
  max-height: 380px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
}
.device-match {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.device-match:last-child { border-bottom: 0; }
.device-match-names {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-width: 0;
}
.device-match-name {
  display: inline-flex;
  flex-direction: column;
  font-size: 13px;
  color: var(--text);
}
/* Which tools report each name is the actual evidence: a name only one
   tool knows, beside one five tools agree on, is the giveaway. */
.device-match-tools {
  font-size: 10px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.device-match-vs { color: var(--muted); font-size: 11px; flex: none; }
.device-match-actions { display: flex; gap: 6px; flex: none; }
.device-match-btn { font-size: 12px; padding: 5px 10px; }
.device-match-done { color: var(--muted); }
.device-match-result { font-size: 12px; color: var(--muted); }
