:root{
  --signal:#5850EC;
  --signal-dark:#4338CA;
  --signal-tint:#EEECFD;
  --primary:var(--signal);
  --primary-dark:var(--signal-dark);
  --ink:#0B1220;
  --chrome:#0B1220;
  --chrome-hover:#1A2540;
  --chrome-border:#232C48;
  --chrome-text:#C9CEE0;
  --paper:#F3F5FA;
  --bg:#F3F5FA;
  --surface:#ffffff;
  --border:#E4E7F0;
  --text:#131A2B;
  --muted:#6B7386;
  --tally:#E1352B;
  --tally-tint:#FDEAE9;
  --danger:#E1352B;
  --sidebar-w:230px;
  --topbar-h:58px;
  --radius:10px;
  --font-display:'Space Grotesk',-apple-system,BlinkMacSystemFont,sans-serif;
  --font-body:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --font-mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;

  /* Elevation system for the 3D card treatment below (.table-wrap and
     friends): each level is a soft ambient shadow (large blur, low
     opacity, reads as "distance from the page") plus a crisper "contact"
     shadow close to the element (small blur, higher opacity, reads as
     "this edge is raised"). Buttons define their own layered shadows
     directly instead (they need per-variant tinting these flat tokens
     don't carry), but share --ease-out with everything else below. */
  --shadow-1:0 1px 2px rgba(17,24,39,.06), 0 1px 1px rgba(17,24,39,.04);
  --shadow-2:0 4px 10px rgba(17,24,39,.08), 0 1px 2px rgba(17,24,39,.06);
  --shadow-3:0 10px 24px rgba(17,24,39,.12), 0 2px 6px rgba(17,24,39,.08);
  --ease-out:cubic-bezier(.16,1,.3,1);
}
*{box-sizing:border-box;}
[hidden]{display:none !important;}
/* Page-wide horizontal-scroll guard. The responsive table rules further
   down (tier B/C) reflow columns into stacked cards, but a single
   long unbreakable value - a media path/URL, a device token, a long
   ID with no spaces - has no break point by default, so it overflows
   its cell and, since nothing between the cell and the viewport clips
   it, widens the page's own scrollable area instead of just that cell.
   That's what forces a left/right scroll to read a row on a phone.
   overflow-wrap:anywhere fixes it at the source (text is allowed to
   break wherever needed once it can't fit); html/body overflow-x:hidden
   is a backstop so nothing can reopen page-level horizontal scrolling.
   Neither affects the two deliberately side-scrollable boxes elsewhere
   (.table-wrap's wide-screen safety net, .calendar-wrap) since those
   scroll internally and aren't the document itself. */
html{overflow-x:hidden;}
body{
  margin:0;
  font-family:var(--font-body);
  background:var(--paper);
  color:var(--text);
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
  overflow-wrap:anywhere;
}
h1,h2,h3{margin:0 0 4px;font-family:var(--font-display);font-weight:700;letter-spacing:-.01em;}
.muted{color:var(--muted);}
button{font-family:inherit;cursor:pointer;}
input,select,textarea{font-family:inherit;}

/* Tally-light: a small pulsing dot used only on the wordmark and the
   Simulator's LIVE badge - grounded in the app's actual job (driving live
   playback to screens), not decoration sprinkled elsewhere. */
.tally-dot{
  display:inline-block;width:8px;height:8px;border-radius:50%;
  background:var(--tally);margin-right:9px;vertical-align:middle;
  box-shadow:0 0 0 0 rgba(225,53,43,.55);
  animation:tally-pulse 2s ease-out infinite;
}
@keyframes tally-pulse{
  0%{box-shadow:0 0 0 0 rgba(225,53,43,.55);}
  70%{box-shadow:0 0 0 8px rgba(225,53,43,0);}
  100%{box-shadow:0 0 0 0 rgba(225,53,43,0);}
}
@media (prefers-reduced-motion:reduce){
  .tally-dot{animation:none;}
}

/* ---------- Buttons ----------
   A "3D" raised-keycap treatment rather than a flat fill: a gradient face
   (lighter at top, like light catching a convex surface), a bright inset
   highlight along the top edge, and a two-part shadow - a crisp "contact"
   shadow right under the button plus a softer ambient one further out -
   that together read as "this sits above the page". Hovering lifts the
   button 2px further toward the viewer (translateY up, shadow grows);
   pressing (:active) does the opposite - it sinks 1px below its resting
   position and the shadow shrinks - which is what actually sells the
   depth: a flat button can't convincingly "go down" when clicked, a
   layered one can. This is the primary way every click in the app gets an
   immediate, physical-feeling response, rather than a separate effect
   bolted on afterward. */
.btn{
  position:relative;
  border:1px solid var(--border);
  background:linear-gradient(180deg, #ffffff 0%, #F6F7FB 100%);
  color:var(--text);
  padding:9px 16px;
  border-radius:8px;
  font-size:14px;
  font-weight:600;
  box-shadow:
    0 1px 0 rgba(255,255,255,.8) inset,
    0 1px 2px rgba(17,24,39,.05),
    0 2px 0 rgba(17,24,39,.04);
  transform:translateY(0);
  transition:transform .12s var(--ease-out), box-shadow .15s var(--ease-out), background .15s ease, border-color .15s ease;
}
.btn:hover{
  background:linear-gradient(180deg, #ffffff 0%, #EEF0F8 100%);
  transform:translateY(-1px);
  box-shadow:
    0 1px 0 rgba(255,255,255,.8) inset,
    0 2px 4px rgba(17,24,39,.08),
    0 4px 10px rgba(17,24,39,.08);
}
.btn:active{
  transform:translateY(1px);
  transition-duration:.05s;
  box-shadow:
    0 1px 0 rgba(255,255,255,.5) inset,
    0 1px 1px rgba(17,24,39,.08);
}
.btn:focus-visible{outline:2px solid var(--signal);outline-offset:2px;}

.btn-primary{
  background:linear-gradient(180deg, #6D66F0 0%, var(--signal) 58%, var(--signal-dark) 100%);
  border-color:var(--signal-dark);
  color:#fff;
  box-shadow:
    0 1px 0 rgba(255,255,255,.35) inset,
    0 1px 2px rgba(67,56,202,.3),
    0 3px 8px rgba(67,56,202,.35);
}
.btn-primary:hover{
  background:linear-gradient(180deg, #7B74F2 0%, #675FEE 58%, var(--signal-dark) 100%);
  box-shadow:
    0 1px 0 rgba(255,255,255,.35) inset,
    0 2px 4px rgba(67,56,202,.32),
    0 6px 16px rgba(67,56,202,.4);
}
.btn-primary:active{
  background:linear-gradient(180deg, var(--signal) 0%, var(--signal-dark) 100%);
  box-shadow:
    0 1px 0 rgba(255,255,255,.2) inset,
    0 1px 2px rgba(67,56,202,.35);
}

.btn-dark{
  background:linear-gradient(180deg, #232C48 0%, var(--ink) 100%);
  border-color:#000;
  color:#fff;
  box-shadow:
    0 1px 0 rgba(255,255,255,.12) inset,
    0 1px 2px rgba(0,0,0,.35),
    0 3px 8px rgba(0,0,0,.3);
}
.btn-dark:hover{
  background:linear-gradient(180deg, #2C3658 0%, #0F1527 100%);
  box-shadow:
    0 1px 0 rgba(255,255,255,.12) inset,
    0 2px 4px rgba(0,0,0,.35),
    0 6px 16px rgba(0,0,0,.35);
}
.btn-dark:active{
  background:linear-gradient(180deg, var(--ink) 0%, #060a14 100%);
  box-shadow:0 1px 1px rgba(0,0,0,.4) inset;
}

/* Ghost/text buttons stay visually flat at rest (they're meant to read as
   secondary, not as another raised surface competing with primary
   actions) but still get the same tactile press - a quick scale-down
   rather than a shadow change, since there's no raised shadow to retract. */
.btn-ghost{
  background:transparent;border-color:transparent;color:var(--muted);
  box-shadow:none;
}
.btn-ghost:hover{background:#EEF0F6;box-shadow:none;transform:translateY(0);}
.btn-ghost:active{background:#E4E7F0;transform:scale(.96);box-shadow:none;}

.btn-danger{color:var(--tally);}
.btn-danger:hover{background:var(--tally-tint);}

.btn-block{width:100%;}
.btn-sm{padding:5px 10px;font-size:12.5px;}
.btn-icon{margin-right:6px;}
.btn-sm .btn-icon{margin-right:4px;}
.btn:disabled{opacity:.45;cursor:not-allowed;pointer-events:none;transform:none;}

@media (prefers-reduced-motion:reduce){
  .btn,.btn-primary,.btn-dark,.btn-ghost{transition:none;}
}

/* ---------- Login screen ---------- */
.login-screen{
  min-height:100vh;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:20px;
  background:
    radial-gradient(circle at 20% 15%, rgba(88,80,236,.35), transparent 45%),
    radial-gradient(circle at 85% 80%, rgba(225,53,43,.18), transparent 40%),
    var(--ink);
}
.login-card{
  background:var(--surface);
  padding:38px 34px;
  border-radius:16px;
  width:100%;
  max-width:380px;
  box-shadow:0 30px 80px rgba(5,8,20,.45);
  display:flex;
  flex-direction:column;
  gap:14px;
  animation:login-card-in .4s var(--ease-out);
}
@keyframes login-card-in{
  from{opacity:0;transform:translateY(16px) scale(.98);}
  to{opacity:1;transform:translateY(0) scale(1);}
}
@media (prefers-reduced-motion:reduce){
  .login-card{animation:none;}
}
.login-card .brand-mark{
  font-size:24px;letter-spacing:-.01em;display:flex;align-items:center;
}
.tagline{color:var(--muted);font-size:13px;font-weight:500;margin:-6px 0 0;}
.login-card label{display:flex;flex-direction:column;gap:6px;font-size:13px;font-weight:600;color:var(--muted);}
.login-card input{
  padding:11px 12px;
  border:1px solid var(--border);
  border-radius:8px;
  font-size:15px;
  font-family:var(--font-body);
}
.login-card input:focus{outline:2px solid var(--signal);outline-offset:1px;}
.login-card select{
  padding:11px 12px;
  border:1px solid var(--border);
  border-radius:8px;
  font-size:15px;
  font-family:var(--font-body);
  background:var(--surface);
  color:var(--text);
}
.login-card select:focus{outline:2px solid var(--signal);outline-offset:1px;}
.login-card select:disabled{color:var(--muted);background:#FAFBFD;}
.login-link{font-size:13px;text-decoration:underline;}
.form-success{
  padding:10px 14px;border-radius:8px;font-size:13.5px;
  background:#E7F6EC;color:#1E7A3C;border:1px solid #BFE6CB;
}

/* ---------- App shell ---------- */
.app-shell{min-height:100vh;display:flex;flex-direction:column;}
.topbar{
  height:var(--topbar-h);
  background:var(--chrome);
  border-bottom:1px solid var(--chrome-border);
  display:flex;
  align-items:center;
  gap:12px;
  padding:0 18px;
  position:sticky;top:0;z-index:20;
}
.brand{
  font-family:var(--font-display);font-weight:700;font-size:17px;letter-spacing:-.01em;
  color:#fff;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  display:flex;align-items:center;
}
.hamburger{
  display:none;
  background:none;border:none;font-size:22px;padding:4px 8px;border-radius:6px;color:var(--chrome-text);
}
.hamburger:hover{background:var(--chrome-hover);}
.topbar-right{display:flex;align-items:center;gap:12px;}
.current-user{font-size:13.5px;color:var(--chrome-text);white-space:nowrap;}
.topbar .btn-ghost{color:var(--chrome-text);}
.topbar .btn-ghost:hover{background:var(--chrome-hover);color:#fff;}
.lang-switcher{
  background:var(--chrome-hover,rgba(255,255,255,.08));color:var(--chrome-text);
  border:1px solid var(--chrome-border);border-radius:6px;font-size:13px;
  padding:5px 8px;cursor:pointer;
}
.lang-switcher:hover{background:var(--chrome-hover);}

.body-wrap{display:flex;flex:1;min-height:calc(100vh - var(--topbar-h));}
.sidebar{
  width:var(--sidebar-w);
  flex-shrink:0;
  background:var(--chrome);
  border-right:1px solid var(--chrome-border);
  padding:16px 10px;
}
.nav-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px;}
.nav-list li a{
  display:flex;
  align-items:center;
  gap:10px;
  padding:10px 14px;
  border-radius:8px;
  color:var(--chrome-text);
  text-decoration:none;
  font-size:14.5px;
  font-weight:500;
  border-left:2px solid transparent;
  transition:background .12s, color .12s, border-color .12s;
}
.nav-icon{font-size:16px;line-height:1;width:18px;text-align:center;flex-shrink:0;}
.nav-list li a:hover{background:var(--chrome-hover);color:#fff;}
.nav-list li a.active{background:var(--chrome-hover);color:#fff;border-left-color:var(--signal);}
.sidebar-overlay{display:none;}

.content{flex:1;padding:24px 28px;min-width:0;}
.content-header{
  display:flex;
  align-items:center;
  justify-content:space-between;
  margin-bottom:18px;
  flex-wrap:wrap;
  gap:10px;
}
.content-header h2{font-size:20px;}
.content-header-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.customer-filter{
  display:flex;align-items:center;gap:8px;font-size:13px;color:var(--muted);font-weight:600;
}
.customer-filter.filter-check{cursor:pointer;}
.customer-filter.filter-check input{margin:0;cursor:pointer;}
.customer-filter select{
  padding:8px 10px;border:1px solid var(--border);border-radius:8px;
  font-size:14px;color:var(--text);background:var(--surface);max-width:220px;
}

/* showAlert()/hideAlert() in app.js just flip this element's [hidden]
   attribute and swap its class - they never remove/re-add the node - so an
   animation keyed to ":not([hidden])" naturally replays every time an
   action (save, delete, upload, ...) reports its result, with no JS
   changes needed. Success slides in calmly; an error adds a quick
   horizontal shake on top of the same slide, since a mistake deserves a
   slightly more insistent cue than a quiet confirmation does. */
.alert-box{
  padding:10px 14px;border-radius:8px;margin-bottom:14px;font-size:14px;
  background:var(--tally-tint);color:#9A241C;border:1px solid #F5C6C3;
  animation:alert-in .25s var(--ease-out), alert-shake .4s ease .25s;
}
.alert-box.success{
  background:#E7F6EC;color:#1E7A3C;border-color:#BFE6CB;
  animation:alert-in .25s var(--ease-out);
}
@keyframes alert-in{
  from{opacity:0;transform:translateY(-6px);}
  to{opacity:1;transform:translateY(0);}
}
@keyframes alert-shake{
  0%,100%{transform:translateX(0);}
  20%{transform:translateX(-4px);}
  40%{transform:translateX(3px);}
  60%{transform:translateX(-2px);}
  80%{transform:translateX(1px);}
}
@media (prefers-reduced-motion:reduce){
  .alert-box,.alert-box.success{animation:none;}
}

/* ---------- Table ----------
   A ".data-table" is a real <table> at wide/desktop widths (so it lines up
   in columns exactly like a table always has), but reflows into a
   card-per-record layout at medium widths and a stacked, alternating-
   alignment layout at phone widths - see the responsive rules further
   down. That reflow works off two things every table-building function in
   app.js provides: each <td> carries a data-label attribute naming its
   column (read by the mid/narrow rules' `content:attr(data-label)`), and a
   <td> that doesn't represent a single field (an embedded panel, a
   colspan loading/error message) simply has no data-label and is left
   alone by those rules. No horizontal scrolling at any width - the old
   "shrink the whole table sideways" approach is gone. */
.table-wrap{
  background:var(--surface);
  border:1px solid var(--border);
  border-radius:var(--radius);
  /* Safety net for tier A (wide) only: a screen that's "wide" but not
     wide enough for every column of a many-column table (Users, say) gets
     a horizontal scrollbar on the table itself rather than breaking the
     page layout. Inert at tiers B/C - the table becomes a stack of blocks
     there and never grows wider than its container in the first place. */
  overflow-x:auto;
}
.data-table{width:100%;border-collapse:collapse;font-size:14px;}
.data-table th,.data-table td{
  padding:12px 14px;
  text-align:left;
  border-bottom:1px solid var(--border);
  white-space:nowrap;
}
.data-table th{
  background:#FAFBFD;
  font-size:11.5px;
  text-transform:uppercase;
  letter-spacing:.05em;
  color:var(--muted);
  font-weight:600;
}
.data-table tbody tr{transition:background-color .12s ease;}
.data-table tbody tr:hover{background:#F7F8FC;}
.data-table tbody tr.clickable{cursor:pointer;}
.data-table tbody tr.row-selected{background:var(--signal-tint);}
.data-table tbody tr.row-selected:hover{background:var(--signal-tint);}
/* A row whose bulk-select checkbox (not the unrelated row-selected/
   row-clickable highlight above - a device/media "chosen for its schedule
   panel" state) is checked - a lighter tint so it reads clearly even
   alongside row-selected on the same row. */
.data-table tbody tr.row-checked{background:#F3F6FF;}
.data-table tbody tr.row-checked:hover{background:#EAEFFF;}

/* The local row-number and checkbox columns (renderTable()'s "selectable"
   path, and the device/media schedule sub-tables) - narrow and centered
   rather than stretching like a normal column, since neither ever holds
   more than a couple characters or a single control. */
.data-table .col-index{width:1%;white-space:nowrap;text-align:center;color:var(--muted);font-variant-numeric:tabular-nums;}
.data-table .col-select{width:1%;white-space:nowrap;text-align:center;}
.data-table .col-select input[type="checkbox"],
.data-table .col-index-select-all{cursor:pointer;width:16px;height:16px;vertical-align:middle;}

/* "Delete selected" (App.deleteSelectedRows()/deleteSelectedScheduleRows()
   in app.js) sits beside each table's own Refresh button - disabled rather
   than hidden when nothing's checked, so it stays discoverable. */
.delete-selected-btn{color:var(--tally);}

/* Applies whenever a table is still in real <table> form (JS hasn't had to
   stack it - see "Responsive table" below): if .table-wrap still needs its
   own horizontal scrollbar to fit every column, pin the first column (the
   row's Name - what identifies it) so scrolling right to reach Actions
   never scrolls the identifying column out of view with it. This is now a
   secondary safety net rather than the main defense - JS below stacks a
   table into cards well before this would normally be needed - but it
   costs nothing to keep for the moment right at that threshold. Each state
   needs its own explicit background (sticky cells aren't part of the row's
   own background paint), matched to that row's normal/hover/selected color
   so scrolled-under cells don't show through. */
.table-wrap:not(.stacked) .data-table th:first-child,
.table-wrap:not(.stacked) .data-table td:first-child{
  position:sticky;left:0;
}
.table-wrap:not(.stacked) .data-table th:first-child{background:#FAFBFD;z-index:2;}
.table-wrap:not(.stacked) .data-table td:first-child{background:var(--surface);z-index:1;}
.table-wrap:not(.stacked) .data-table tbody tr:hover td:first-child{background:#F7F8FC;}
.table-wrap:not(.stacked) .data-table tbody tr.row-selected td:first-child,
.table-wrap:not(.stacked) .data-table tbody tr.row-selected:hover td:first-child{background:var(--signal-tint);}
/* An embedded-panel row (no data-label cells, colspan across the whole
   row - e.g. the expanded Device/Media detail panel) isn't a normal row of
   per-column cells, so its first cell shouldn't pin either. */
.table-wrap:not(.stacked) .data-table tbody tr.row-expand-tr td:first-child{position:static;background:inherit;}

/* ---------- Responsive table (JS-driven) ----------
   Which layout a table uses is decided in js/app.js (layoutDataTable),
   which measures each table's own .table-wrap rather than going by a
   single viewport-width guess - a table with more/wider columns needs to
   switch to cards sooner than one with fewer, short columns, and the two
   can be on-screen size by size differently at the very same window width.
   .stacked / .stacked-narrow are the two card tiers that decision can add
   to a .table-wrap; without either, the table keeps its plain column
   layout from the base .data-table rules above. */

/* .stacked: header hidden, each row becomes its own bordered card, and
   each remaining cell becomes one "field name: value" line - see the
   data-label attributes set alongside every td in app.js. */
.table-wrap.stacked .data-table thead{display:none;}
.table-wrap.stacked .data-table,
.table-wrap.stacked .data-table tbody,
.table-wrap.stacked .data-table tr{display:block;width:100%;}
.table-wrap.stacked .data-table tbody tr{
  border:1px solid var(--border);border-radius:10px;margin-bottom:10px;
  padding:2px 0;background:var(--surface);
}
.table-wrap.stacked .data-table tbody tr:last-child{margin-bottom:0;}
.table-wrap.stacked .data-table tbody tr:hover{background:var(--surface);} /* card layout, no row-hover tint */
.table-wrap.stacked .data-table td{
  display:flex;align-items:baseline;justify-content:space-between;gap:14px;
  padding:9px 14px;border-bottom:1px dashed var(--border);white-space:normal;text-align:right;
}
.table-wrap.stacked .data-table td:last-child{border-bottom:none;}
.table-wrap.stacked .data-table td[data-label]::before{
  content:attr(data-label) ": ";font-weight:600;color:var(--muted);font-size:12px;text-align:left;flex-shrink:0;
}
/* A td with no data-label isn't a single field (an embedded panel, a
   colspan loading/error row) - leave it as a plain full-width block
   instead of the label/value split above. */
.table-wrap.stacked .data-table td:not([data-label]){display:block;text-align:left;}

/* .stacked-narrow (added alongside .stacked, never alone): the wrap
   measured too narrow even for the one-line "label: value" row above, so
   each field becomes its own two-line block instead - the label, then its
   value directly under it - alternating right/left alignment down the
   card (1st right, 2nd left, 3rd right, ...). */
.table-wrap.stacked-narrow .data-table td{display:block;padding:8px 14px;text-align:right;}
.table-wrap.stacked-narrow .data-table td[data-label]::before{
  display:block;content:attr(data-label);font-weight:600;color:var(--muted);
  font-size:11px;margin-bottom:2px;text-align:inherit;
}
.table-wrap.stacked-narrow .data-table td[data-label]:nth-of-type(odd){text-align:right;}
.table-wrap.stacked-narrow .data-table td[data-label]:nth-of-type(even){text-align:left;}
.table-wrap.stacked-narrow .data-table td:not([data-label]){text-align:left;}
/* Tables that opt in (cfg.cardSide in app.js, e.g. Medias): the side is chosen
   once for the WHOLE record card - every field of it (label and value
   together) aligns the same way - and alternates from card to card, instead
   of alternating field by field inside one card. */
.table-wrap.stacked-narrow .data-table tr[data-card-side="right"] td[data-label]{text-align:right;}
.table-wrap.stacked-narrow .data-table tr[data-card-side="left"] td[data-label]{text-align:left;}
/* In the stacked card layouts the 2nd, 4th, ... record card of a cardSide
   table gets a light-gray background (these are the "left" cards above), so
   neighbouring cards are easy to tell apart. A selected card keeps its own
   highlight, and the gray also holds on hover. */
.table-wrap.stacked .data-table tbody tr[data-card-side="left"],
.table-wrap.stacked .data-table tbody tr[data-card-side="left"]:hover{background:#EEF0F5;}
.table-wrap.stacked .data-table tbody tr[data-card-side="left"].row-selected,
.table-wrap.stacked .data-table tbody tr[data-card-side="left"].row-selected:hover{background:var(--signal-tint);}

/* The index/checkbox cells (renderTable()'s "selectable" path) have no
   data-label, so the two td:not([data-label]) rules just above would
   otherwise give each its own full-width block in the stacked/card
   layouts. They read better sitting side by side above the record's
   fields instead - same specificity as those two rules, so this only
   needs to come after them in the file to win. */
.table-wrap.stacked .data-table td.col-index,
.table-wrap.stacked .data-table td.col-select{
  display:inline-flex;align-items:center;padding:9px 8px;text-align:left;border-bottom:none;
}

/* ---------- Device -> Schedule table (inline under the Devices table) ---------- */
.device-schedule-panel{
  margin-top:16px;
  background:var(--surface);
  border:1px solid var(--border);
  border-radius:var(--radius);
  overflow:hidden;
}
.device-schedule-panel-header{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  flex-wrap:wrap;
  padding:14px 20px;border-bottom:1px solid var(--border);
}
.device-schedule-panel-header h3{margin:0;font-size:15px;}
.device-schedule-panel-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.device-schedule-panel .table-wrap{border:none;border-radius:0;}

/* ---------- Media table row-expand (the Media -> Schedule panel is moved
   into an extra full-width row inserted right under the clicked media row,
   instead of appearing below the whole table - see rowExpand in app.js) ---------- */
.data-table tbody tr.row-expand-tr,
.data-table tbody tr.row-expand-tr:hover{background:var(--bg);}
.data-table tbody tr.row-expand-tr td{
  padding:0;white-space:normal;
}
.data-table tbody tr.row-expand-tr .device-schedule-panel{margin:0;border:none;border-radius:0;}
/* inline-flex (not flex) so this still respects a responsive table cell's
   text-align when it's the "value" side of a field - see the .data-table
   responsive rules. */
.row-actions{display:inline-flex;gap:6px;flex-wrap:wrap;}
.media-thumb{
  width:56px;height:40px;border-radius:6px;overflow:hidden;
  display:inline-flex;align-items:center;justify-content:center;
  background:#EEF0F5;border:1px solid var(--border);flex-shrink:0;
}
.media-thumb img,.media-thumb video{width:100%;height:100%;object-fit:cover;display:block;}
.media-thumb .media-thumb-icon{font-size:20px;line-height:1;}
.badge{
  display:inline-block;padding:3px 9px;border-radius:20px;font-size:11.5px;font-weight:600;
  font-family:var(--font-mono);letter-spacing:.02em;
}
.badge.active{background:#E7F6EC;color:#1E7A3C;}
.badge.inactive{background:#F1F2F6;color:#8A8F99;}
.badge.admin{background:var(--signal-tint);color:var(--signal-dark);}
.badge.live{background:var(--tally);color:#fff;position:relative;padding-left:16px;}
.badge.live::before{
  content:'';position:absolute;left:8px;top:50%;width:6px;height:6px;margin-top:-3px;
  border-radius:50%;background:#fff;animation:tally-pulse-white 2s ease-out infinite;
}
@keyframes tally-pulse-white{
  0%{box-shadow:0 0 0 0 rgba(255,255,255,.65);}
  70%{box-shadow:0 0 0 6px rgba(255,255,255,0);}
  100%{box-shadow:0 0 0 0 rgba(255,255,255,0);}
}
.badge.simulated{background:#EBEDF3;color:#454C5E;}
.empty-state{padding:40px;text-align:center;color:var(--muted);}
.load-more-btn{display:block;margin:14px auto 0;}

.login-hint{color:var(--muted);font-size:12px;font-weight:400;margin-top:-8px;}
.login-hint code{background:#F0F2F7;padding:1px 5px;border-radius:4px;font-family:var(--font-mono);font-size:11px;}

/* ---------- Dropzone / file upload ---------- */
.dropzone{
  border:2px dashed var(--border);
  border-radius:12px;
  padding:26px 16px;
  text-align:center;
  cursor:pointer;
  transition:border-color .15s, background .15s;
}
.dropzone p{margin:2px 0;font-size:14px;}
.dropzone p.small{font-size:12px;}
.dropzone:hover{border-color:var(--signal);background:var(--signal-tint);}
.dropzone.dragover{border-color:var(--signal);background:var(--signal-tint);}
.file-list{display:flex;flex-direction:column;gap:8px;}
.file-chip{
  display:flex;align-items:center;gap:10px;
  border:1px solid var(--border);border-radius:8px;padding:8px 10px;font-size:13.5px;
}
.file-chip .file-icon{font-size:18px;}
.file-chip .file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.file-chip .file-size{color:var(--muted);font-size:12px;white-space:nowrap;font-family:var(--font-mono);}
.file-chip .file-remove{
  background:none;border:none;color:var(--tally);font-size:18px;line-height:1;padding:0 4px;
}
.upload-summary{font-size:13px;font-family:var(--font-mono);}
.upload-summary.over-limit{color:var(--tally);font-weight:600;}

.file-chip-uploading .file-name{flex:0 1 auto;max-width:40%;}
.file-progress-track{
  flex:1;height:6px;border-radius:3px;background:var(--border);overflow:hidden;
}
.file-progress-fill{
  height:100%;background:var(--signal);border-radius:3px;
  transition:width .15s ease-out;
}
.file-chip-uploading .file-percent{
  width:38px;text-align:right;color:var(--muted);font-size:12px;font-family:var(--font-mono);
}
.traditional-upload{display:flex;flex-direction:column;gap:8px;}
.traditional-rows{display:flex;flex-direction:column;gap:8px;}
.traditional-row{
  display:flex;align-items:center;gap:10px;
  border:1px solid var(--border);border-radius:8px;padding:8px 10px;font-size:13.5px;
}
.traditional-row .traditional-row-label{color:var(--muted);white-space:nowrap;font-weight:600;}
.traditional-row input[type="file"]{flex:1;font-size:13px;min-width:0;}
.traditional-row .file-remove{
  background:none;border:none;color:var(--tally);font-size:18px;line-height:1;padding:0 4px;
}

/* ---------- Device Media Scheduler ---------- */
.scheduler-form{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
  padding:16px 18px;margin-bottom:16px;
}
.form-grid{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(140px, 1fr));
  gap:12px 14px;
  align-items:end;
}
.form-grid label{display:flex;flex-direction:column;gap:5px;font-size:12.5px;font-weight:600;color:var(--muted);}
.form-grid input,.form-grid select{
  padding:9px 10px;border:1px solid var(--border);border-radius:8px;font-size:14px;color:var(--text);
}
.form-grid .btn{align-self:end;white-space:nowrap;}

.logs-filter-bar{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
  padding:14px 16px;margin-bottom:14px;
}
.badge.log-create{background:#E7F6EC;color:#1E7A3C;}
.badge.log-update{background:var(--signal-tint);color:var(--signal-dark);}
.badge.log-delete{background:#FBE7E6;color:#B4241C;}
.badge.log-upload{background:#EAF1FF;color:#2E4FA8;}
.badge.log-disable{background:#FFF1E0;color:#A85B00;}
.badge.device-info-media-status-downloaded{background:#E7F6EC;color:#1E7A3C;}
.badge.device-info-media-status-downloading{background:#EAF1FF;color:#2E4FA8;}
.badge.device-info-media-status-failed{background:#FBE7E6;color:#B4241C;}
.badge.device-info-media-status-scheduled{background:#F3F0FA;color:#5B3FA6;}

.scheduler-toolbar{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:10px;
}
.btn-dark{background:var(--ink);border-color:var(--ink);color:#fff;}
.btn-dark:hover{background:#1A2540;}
.sch-range{flex:1;text-align:center;font-weight:700;font-size:14px;min-width:180px;font-family:var(--font-display);}
.scheduler-jump{display:flex;align-items:center;gap:8px;margin-bottom:14px;font-size:13.5px;color:var(--muted);}
.scheduler-jump input{padding:7px 9px;border:1px solid var(--border);border-radius:7px;font-size:13.5px;}

.calendar-wrap{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
  overflow-x:auto;
}
.calendar-grid{min-width:760px;}
.cal-header{display:flex;border-bottom:2px solid var(--border);}
.cal-time-col{width:64px;flex-shrink:0;}
.cal-day-head{
  flex:1;min-width:90px;text-align:center;padding:10px 4px;border-left:1px solid var(--border);
  font-size:12.5px;
}
.cal-day-head.today{background:var(--signal-tint);}
.cal-day-head .cal-day-name{font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);font-size:10.5px;}
.cal-day-head .cal-day-num{font-size:15px;font-weight:700;margin-top:2px;font-family:var(--font-display);}
.cal-body{display:flex;position:relative;}
.cal-body .cal-time-col{display:flex;flex-direction:column;}
.cal-hour-label{
  height:44px;font-size:10.5px;color:var(--muted);text-align:right;padding-right:8px;
  box-sizing:border-box;transform:translateY(-6px);font-family:var(--font-mono);
}
.cal-day-col{flex:1;min-width:90px;position:relative;border-left:1px solid var(--border);}
.cal-day-col.today{background:rgba(88,80,236,.035);}
.cal-hour-cell{height:44px;border-bottom:1px solid #EEF0F5;box-sizing:border-box;}
.cal-block{
  position:absolute;left:2px;right:2px;border-radius:6px;padding:4px 6px;color:#fff;
  font-size:11px;line-height:1.3;overflow:hidden;box-shadow:0 1px 4px rgba(11,18,32,.25);cursor:default;
}
.cal-block-title{font-weight:700;}
.cal-block-time{opacity:.9;font-size:10px;font-family:var(--font-mono);}
.cal-block.editable{cursor:grab;}
.cal-block.editable:active{cursor:grabbing;}
.cal-block.editable:hover{outline:2px solid rgba(255,255,255,.7);z-index:5;}
.cal-resize-handle{position:absolute;left:0;right:0;height:7px;cursor:ns-resize;z-index:6;}
.cal-resize-top{top:0;}
.cal-resize-bottom{bottom:0;}

/* ---------- Device Simulation ---------- */
.sim-controls{
  display:flex;align-items:end;gap:14px;flex-wrap:wrap;margin-bottom:14px;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
  padding:14px 16px;
}
.sim-controls label{display:flex;flex-direction:column;gap:5px;font-size:12.5px;font-weight:600;color:var(--muted);}
.sim-controls input,.sim-controls select{
  padding:9px 10px;border:1px solid var(--border);border-radius:8px;font-size:14px;color:var(--text);
}
.sim-clock{margin-left:auto;font-variant-numeric:tabular-nums;font-weight:700;font-size:16px;color:var(--text);font-family:var(--font-mono);}
.sim-controls .badge{align-self:center;white-space:nowrap;}

.sim-layout{display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap;}
.sim-screen-wrap{flex:2;min-width:280px;}
.sim-screen{
  position:relative;width:100%;aspect-ratio:16/9;background:var(--ink);border-radius:var(--radius);
  overflow:hidden;display:flex;align-items:center;justify-content:center;
  border:1px solid var(--border);
}
.sim-screen .sim-media{width:100%;height:100%;object-fit:contain;border:none;background:#000;}
.sim-screen .sim-idle{color:#8A90A3;font-size:14px;padding:20px;text-align:center;}
.sim-status{margin-top:10px;font-size:13.5px;}
.sim-status.playing{color:#1E7A3C;font-weight:600;}

.sim-day-panel{
  flex:1;min-width:260px;background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius);padding:14px 16px;max-height:520px;overflow-y:auto;
}
.sim-day-panel h3{font-size:12px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin-bottom:10px;font-family:var(--font-mono);font-weight:600;}
.sim-day-list .schedule-item{cursor:default;transition:border-color .15s, background .15s;}
.sim-day-list .schedule-item.sim-active{border-color:var(--signal);background:var(--signal-tint);}

/* ---------- Modal ----------
   Every modal in the app is a pre-built, always-in-the-DOM node toggled via
   [hidden] (see openFormModal/closeForm and friends in app.js), so - same
   as .alert-box above - an animation keyed to ":not([hidden])" plays fresh
   every time one opens, with no JS changes needed: the backdrop fades in
   while the card itself pops up from a slightly smaller, slightly lower
   starting point, which is what makes it read as arriving "in response to"
   the click that opened it rather than just appearing. */
.modal-overlay{
  position:fixed;inset:0;background:rgba(20,25,40,.5);
  display:flex;align-items:center;justify-content:center;z-index:100;padding:16px;
  animation:modal-backdrop-in .18s ease;
}
.modal{
  background:var(--surface);border-radius:14px;width:100%;max-width:460px;
  max-height:88vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,.3);
  animation:modal-pop-in .22s var(--ease-out);
}
@keyframes modal-backdrop-in{
  from{opacity:0;}
  to{opacity:1;}
}
@keyframes modal-pop-in{
  from{opacity:0;transform:translateY(10px) scale(.97);}
  to{opacity:1;transform:translateY(0) scale(1);}
}
@media (prefers-reduced-motion:reduce){
  .modal-overlay,.modal{animation:none;}
}
.modal-header{
  display:flex;align-items:center;justify-content:space-between;
  padding:16px 20px;border-bottom:1px solid var(--border);
}
.modal-close{background:none;border:none;font-size:22px;line-height:1;color:var(--muted);}
.modal-form{padding:18px 20px;}
.modal-fields{display:flex;flex-direction:column;gap:14px;}
.modal-fields label{display:flex;flex-direction:column;gap:6px;font-size:13px;font-weight:600;color:var(--muted);}
.modal-fields input,.modal-fields select,.modal-fields textarea{
  padding:10px 11px;border:1px solid var(--border);border-radius:8px;font-size:14.5px;color:var(--text);
  font-family:var(--font-body);
}
.modal-fields input:focus,.modal-fields select:focus,.modal-fields textarea:focus{
  outline:2px solid var(--signal);outline-offset:1px;border-color:var(--signal);
}
.modal-fields textarea{resize:vertical;min-height:70px;}
.modal-fields .form-section{margin-top:6px;padding-top:12px;border-top:1px solid var(--border);font-size:13px;font-weight:700;color:var(--text);}
.checkbox-row{flex-direction:row !important;align-items:center;gap:8px !important;}
.checkbox-row input{width:auto;}
.modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px;}

/* ---------- Cards & surfaces ----------
   The same "raised surface" language as the buttons above, applied much
   more subtly: these panels already have a flat border for structure, so
   they only need a soft ambient shadow and a small lift on hover to read
   as part of the same 3D system rather than flat rectangles next to
   tactile buttons. Kept to the panels people actually point at/scan (the
   main table, the device/media detail panels, the Scheduler/Simulation
   toolboxes, Open Register's cards) rather than every bordered box in the
   app, and left off anything that re-renders on a timer/WS push (e.g. the
   live calendar), since a hover-only effect never fires on its own but a
   shadow that's already there would just sit static anyway. */
.table-wrap,
.device-schedule-panel,
.device-info-panel,
.scheduler-form,
.sim-controls,
.sim-day-panel,
.logs-filter-bar,
.rr-row-1,
.rr-section{
  box-shadow:var(--shadow-1);
  transition:box-shadow .2s var(--ease-out), transform .2s var(--ease-out);
}
.table-wrap:hover,
.device-schedule-panel:hover,
.device-info-panel:hover,
.scheduler-form:hover,
.sim-controls:hover,
.sim-day-panel:hover,
.logs-filter-bar:hover,
.rr-row-1:hover,
.rr-section:hover{
  box-shadow:var(--shadow-2);
}
@media (prefers-reduced-motion:reduce){
  .table-wrap,.device-schedule-panel,.device-info-panel,.scheduler-form,
  .sim-controls,.sim-day-panel,.logs-filter-bar,.rr-row-1,.rr-section{transition:none;}
}

.schedule-list{padding:10px 20px 20px;}
.schedule-item{
  padding:12px;border:1px solid var(--border);border-radius:10px;margin-bottom:10px;
}
.schedule-item .sched-name{font-weight:700;margin-bottom:4px;}
.schedule-item .sched-meta{font-size:12.5px;color:var(--muted);}

/* ---------- Device info panel (between the Devices table and its Schedule
   table - shows/edits the controller name, its registered monitors, and its
   latest screenshots) ---------- */
.device-info-panel{
  margin-top:16px;
  background:var(--surface);
  border:1px solid var(--border);
  border-radius:var(--radius);
  overflow:hidden;
}
#device-info-form{padding:18px 20px;display:flex;flex-direction:column;gap:18px;}
.field{display:flex;flex-direction:column;gap:8px;}
.field > label{font-size:13px;font-weight:600;color:var(--muted);}
.field-label-row{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;}
.field-label-row > label{font-size:13px;font-weight:600;color:var(--muted);}
.field input[type="text"]{
  padding:10px 11px;border:1px solid var(--border);border-radius:8px;font-size:14.5px;color:var(--text);
  font-family:var(--font-body);max-width:360px;
}
.field input[type="text"]:focus{
  outline:2px solid var(--signal);outline-offset:1px;border-color:var(--signal);
}
.device-info-monitors-wrap{margin-top:2px;}
.device-info-monitors-wrap .empty-state{padding:16px;font-size:13.5px;}
.device-info-monitor-toggle-btn{padding:2px 9px;font-size:14px;line-height:1;}
.device-info-monitor-detail-row,
.device-info-monitor-detail-row:hover{background:var(--bg);}
.device-info-monitor-detail-row td{padding:10px 14px;white-space:normal;}
.device-info-monitor-detail-actions{display:flex;gap:8px;flex-wrap:wrap;}
.device-info-monitor-subtables{display:flex;flex-direction:column;gap:14px;margin-top:14px;}
.device-info-monitor-subtable .field-label-row label{font-size:12px;font-weight:600;color:var(--muted);}
.device-info-monitor-subtable .device-info-screenshots{margin-top:8px;}
.device-info-monitor-subtable .device-info-screenshot-slot{width:130px;min-height:88px;}
.device-info-monitor-media-status-table{margin-top:8px;min-width:0;font-size:13px;}
.device-info-monitor-media-status-table th,
.device-info-monitor-media-status-table td{padding:8px 10px;}
.device-info-screenshots{display:flex;gap:12px;flex-wrap:wrap;}
.device-info-screenshot-slot{
  width:180px;min-height:110px;border:1px dashed var(--border);border-radius:10px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;
  font-size:12.5px;color:var(--muted);background:#FAFBFD;padding:10px;gap:6px;
}
.device-info-screenshot-name{
  font-weight:600;font-size:12px;color:var(--text);max-width:100%;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.device-info-screenshot-slot img{max-width:100%;max-height:86px;border-radius:6px;object-fit:cover;}
.device-info-screenshot-pending{border-style:solid;border-color:var(--signal);background:var(--signal-tint);}
.device-info-screenshot-pending .device-info-screenshot-name{color:var(--signal-dark);}
.device-info-screenshot-pending-icon{font-size:26px;animation:screenshot-pending-pulse 1.4s ease-in-out infinite;}
@keyframes screenshot-pending-pulse{
  0%,100%{opacity:.45;}
  50%{opacity:1;}
}
@media (prefers-reduced-motion:reduce){
  .device-info-screenshot-pending-icon{animation:none;}
}
.form-error{
  padding:10px 14px;border-radius:8px;font-size:13.5px;
  background:var(--tally-tint);color:#9A241C;border:1px solid #F5C6C3;
}
.device-info-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.device-info-actions-spacer{flex:1;}
.device-info-repeat-ss{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap;}
.device-info-repeat-ss-label{display:inline-flex;align-items:center;gap:6px;cursor:pointer;margin:0;}
.device-info-repeat-ss-select{width:auto;min-width:150px;}
.device-info-repeat-ss-status{font-size:.85em;}

/* ---------- Responsive ---------- */
@media (max-width:900px){
  .sidebar{
    position:fixed;top:var(--topbar-h);left:0;bottom:0;
    transform:translateX(-100%);
    transition:transform .2s ease;
    z-index:30;
    box-shadow:4px 0 20px rgba(0,0,0,.15);
  }
  .sidebar.open{transform:translateX(0);}
  .sidebar-overlay.open{
    display:block;position:fixed;inset:var(--topbar-h) 0 0 0;background:rgba(0,0,0,.35);z-index:25;
  }
  .hamburger{display:block;}
  .content{padding:16px;}
}

@media (max-width:520px){
  .content-header{flex-direction:column;align-items:flex-start;}
  .content-header-actions{width:100%;}
  .content-header .btn{width:100%;}
  .modal{max-width:100%;}

  .media-thumb{width:44px;height:32px;}
  .media-thumb .media-thumb-icon{font-size:16px;}
  .device-schedule-panel-header{padding:12px 14px;}
  .device-schedule-panel-actions{width:100%;}
  .device-schedule-panel-actions .btn{flex:1;text-align:center;}
  #device-info-form{padding:14px;}
  .field input[type="text"]{max-width:100%;}
  .device-info-actions{width:100%;}
  .device-info-actions .btn{flex:1;text-align:center;}
  .device-info-actions-spacer{display:none;}
  .device-info-screenshot-slot{width:100%;}
}

/* ---------------------------------------------------------------------------
   Account rights & delegation panel (Users table row expander)
   Mounted into a .row-expand-tr cell by renderAccountRightsPanel() in app.js,
   so it inherits that row's flat background and just adds its own spacing.
--------------------------------------------------------------------------- */
.account-rights-panel{padding:12px 14px;display:flex;flex-direction:column;gap:10px;}
.account-rights-body{overflow-x:auto;}
.account-rights-table{width:100%;}
.account-rights-table th,.account-rights-table td{white-space:normal;vertical-align:middle;}
.account-rights-table select{min-width:150px;}
.account-rights-add{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.account-rights-hint{flex-basis:100%;font-size:12px;}
/* One delegated account. The chips wrap freely - a right can be delegated to
   any number of accounts and the column has to stay readable either way. */
.delegate-chips{display:inline-flex;gap:6px;flex-wrap:wrap;align-items:center;}
.delegate-chip{
  display:inline-flex;align-items:center;gap:6px;
  padding:2px 6px 2px 10px;border-radius:999px;
  background:var(--bg);border:1px solid var(--border);font-size:12px;
}
.delegate-chip-remove{
  border:none;background:none;cursor:pointer;padding:0 2px;
  color:var(--muted);font-size:12px;line-height:1;
}
.delegate-chip-remove:hover{color:var(--danger,#e0403f);}


/* ---------- Open Register room ---------- */
.rr-view{display:flex;flex-direction:column;gap:14px;max-width:920px;}
.rr-row{display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap;}
.rr-row-1{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;
}
.rr-row-2,.rr-row-3{justify-content:flex-end;}
.rr-label{font-size:12.5px;font-weight:600;color:var(--muted);margin-bottom:6px;}
.rr-digits{display:flex;gap:6px;}
.rr-digit{
  width:40px;height:52px;text-align:center;font-family:var(--font-mono);font-size:26px;font-weight:700;
  border:1px solid var(--border);border-radius:8px;background:var(--paper);color:var(--text);padding:0;
  cursor:default;
}
.rr-period{display:flex;flex-direction:column;gap:5px;font-size:12.5px;font-weight:600;color:var(--muted);}
.rr-period select{padding:9px 10px;border:1px solid var(--border);border-radius:8px;font-size:14px;color:var(--text);}
.rr-countdown{font-variant-numeric:tabular-nums;font-weight:700;font-size:16px;font-family:var(--font-mono);align-self:center;}
.rr-countdown.closed{color:var(--muted);font-family:var(--font-body);font-weight:600;}
.rr-row-actions{display:flex;gap:8px;margin-left:auto;}
.rr-not-open{margin:0;}
.rr-section{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;
}
.rr-section-head{display:flex;align-items:center;gap:10px;padding:10px 16px;border-bottom:1px solid var(--border);}
.rr-section-head h3{margin:0;font-size:15px;}
.rr-hint{margin:0;padding:8px 16px 0;font-size:12.5px;}
.rr-list{list-style:none;margin:0;padding:8px;min-height:64px;display:flex;flex-direction:column;gap:6px;}
.rr-dropzone.rr-over{outline:2px dashed var(--signal);outline-offset:-4px;background:var(--signal-tint);}
.rr-item{
  display:flex;align-items:center;gap:14px;padding:9px 12px;border:1px solid var(--border);border-radius:8px;
  background:var(--surface);cursor:grab;
}
.rr-item.dragging{opacity:.45;}
.rr-item .rr-num{font-family:var(--font-mono);font-size:13.5px;color:var(--muted);min-width:118px;}
.rr-item .rr-name{font-weight:600;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.rr-item .rr-meta{font-size:12px;color:var(--muted);white-space:nowrap;}
.rr-empty{padding:14px;text-align:center;color:var(--muted);font-size:13px;}

/* ---- Scan QR to register tab ---- */
.sr-view{display:flex;flex-direction:column;gap:14px;max-width:560px;}
.sr-hint{margin:0;}
.sr-customer{display:flex;flex-direction:column;gap:6px;font-weight:600;}
.sr-customer select{font-weight:400;max-width:100%;}
.sr-actions{display:flex;gap:8px;flex-wrap:wrap;}
.sr-camera{position:relative;background:#000;border-radius:var(--radius);overflow:hidden;aspect-ratio:4/3;max-width:560px;}
.sr-camera video{width:100%;height:100%;object-fit:cover;display:block;}
.sr-frame{position:absolute;inset:18%;border:3px solid rgba(255,255,255,.9);border-radius:14px;box-shadow:0 0 0 9999px rgba(0,0,0,.35);pointer-events:none;}
.sr-camera-hint{position:absolute;left:0;right:0;bottom:8px;margin:0;text-align:center;color:#fff;font-size:13px;text-shadow:0 1px 3px rgba(0,0,0,.8);}
.sr-result{padding:12px 14px;border-radius:8px;border:1px solid var(--border);background:var(--surface);font-weight:500;}
.sr-result.success{background:#E7F6EC;color:#1E7A3C;border-color:#BFE6CB;}
.sr-result.error{background:var(--tally-tint);color:var(--danger);border-color:#F5C6C3;}

/* ---------- Customer contracts sub-table (Customers row expander) ----------
   Mounted into the row-expand cell under a customer's row - see "Customer
   contracts sub-table" in app.js. */
.row-caret{display:inline-block;width:1em;color:var(--muted);}
.contracts-panel{padding:12px 14px 14px 28px;background:var(--bg);display:flex;flex-direction:column;gap:10px;overflow-x:auto;}
.contracts-table{box-sizing:border-box;width:100%;background:var(--surface);border:1px solid var(--border);border-radius:8px;overflow:hidden;}
.contracts-table th,.contracts-table td{white-space:normal;vertical-align:middle;padding:9px 12px;}
.contracts-table .col-add{width:1%;text-align:right;}
.contracts-table .row-actions{flex-wrap:nowrap;}
.contracts-table .badge{white-space:nowrap;}
.contracts-table td:last-child{white-space:nowrap;}
.contracts-add-btn{font-weight:700;line-height:1;padding:4px 10px;}
.contracts-table input,.contracts-table select{width:100%;min-width:0;padding:6px 8px;border:1px solid var(--border);border-radius:6px;font-size:13.5px;background:var(--surface);color:var(--text);}
.contracts-table input[type="number"]{min-width:64px;}
.contracts-table input[type="date"]{min-width:132px;}
.contracts-table tr.contract-edit-row{background:var(--signal-tint);}
.contracts-table tr.contract-edit-row:hover{background:var(--signal-tint);}
.contracts-table tr.contract-edit-row td.col-index{font-weight:700;color:var(--signal-dark);}
.contracts-error{color:#B4241C;font-size:13px;}
.contracts-more{align-self:flex-start;}
.badge.contract-draft{background:#F1F2F6;color:#5A6070;}
.badge.contract-paid{background:#E7F6EC;color:#1E7A3C;}
.badge.contract-break{background:#FBE7E6;color:#B4241C;}
/* In stacked (card) layouts the header row is normally hidden - keep just
   its ＋ so a contract can still be added. */
.table-wrap.stacked .contracts-table thead{display:block;}
.table-wrap.stacked .contracts-table thead tr{display:flex;justify-content:flex-end;padding:6px 10px;}
.table-wrap.stacked .contracts-table thead th{display:none;border:none;padding:0;background:transparent;}
.table-wrap.stacked .contracts-table thead th.col-add{display:block;width:auto;}
.table-wrap.stacked .contracts-panel{padding-left:14px;}

/* Scan QR: zoom slider (only shown where the browser/camera supports zoom) */
.sr-zoom{display:flex;align-items:center;gap:10px;margin-top:8px;font-size:13px;color:var(--muted);}
.sr-zoom[hidden]{display:none;}
.sr-zoom input[type="range"]{flex:1;min-width:0;}

/* ---------- Report tab ---------- */
.rp-hint{margin:0 0 12px;font-size:13px;}
.rp-summary{margin:0 0 8px;font-size:13px;}
.rp-filters select:disabled{background:#F1F2F6;color:var(--muted);}

/* Devices: "Apply selected" button + dialog */
.apply-selected-btn{color:var(--signal-dark);}
.apply-hint{margin:0 0 12px;font-size:13px;}
.apply-pic-note{color:#9aa0ab;font-weight:400;}

/* PIC line above a customer's contracts sub-table */
.contracts-pic{margin:0 0 8px;font-size:13px;color:var(--muted);}
.contracts-pic strong{color:var(--text);}

/* Monitors table: in-place rename + delete */
.monitor-rename-btn,.monitor-delete-btn{padding:0 6px;margin-left:4px;}
.monitor-rename-edit{display:inline-flex;align-items:center;gap:2px;}
.monitor-rename-input{min-width:140px;max-width:220px;}

.device-info-action-status{margin-top:8px;font-size:.9em;}
