:root {
  --blue: #7C3AED;
  --blue-dark: #6D28D9;
  --red: #dc2626;
  --orange: #d97706;

  /* three-tier surface system: bg (page) is the darkest, surface (rows,
     idle inputs) is the lightest, surface-hover (hover/focus) sits between
     the two — kept as a wide, non-OLED-safe lightness gap at each step */
  --bg: #f1f1f1;
  --surface: hsl(0deg 0% 95.42%);
  --surface-hover: #ffffff;
  --raised: #ffffff;

  --border: hsl(220, 14%, 72%);
  --text: #1f2430;
  --muted: #767c8c;
  --hover-tint: #ede9fe;
  /* Expanded/detail-row background (admin panel "By day"/"Past days" rows) -
     needs to read as its own distinct surface, not the plain grey used for
     idle rows/inputs (--surface) and not the white of cards/headers
     (--raised), where black or muted text was landing with too little
     separation to look intentional. Same purple as the rest of the accent
     system, just far lighter than --hover-tint (a hover state can afford to
     be more visible; a background something sits on for as long as it's
     expanded needs to stay calm). Landed here ("Pale Lavender") after a
     round of side-by-side comparisons - light enough to read as closer to
     white than to a saturated purple, while still being clearly its own
     tinted surface rather than another shade of grey. */
  --detail-tint: hsl(262, 38%, 97.5%);
  --detail-border: hsl(262, 26%, 90%);
  --danger-bg: #fee2e2;
  --danger-bg-hover: #fecaca;
  --icon-btn-bg: #f1f2f5;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

[data-theme="dark"] {
  --blue: #A78BFA;
  --blue-dark: #C4B5FD;
  --red: #f87171;
  --orange: #fbbf24;

  --bg: hsl(222, 18%, 9%);
  --surface: hsl(222, 15%, 15%);
  --surface-hover: hsl(222, 15%, 21%);
  --raised: hsl(222, 15%, 17%);

  --border: hsl(222, 12%, 27%);
  --text: #e6e8ee;
  --muted: #9aa0b0;
  --hover-tint: hsl(262, 45%, 24%);
  /* Kept close to --surface (dark mode's row tint already read fine here) -
     just a touch more purple/saturated than the neutral grey-blue surface,
     for the same "distinct expanded-row surface" reasoning as light mode's
     --detail-tint above, without changing what was already working. */
  --detail-tint: hsl(262, 22%, 18%);
  --detail-border: hsl(262, 20%, 30%);
  --danger-bg: hsl(0, 45%, 22%);
  --danger-bg-hover: hsl(0, 45%, 28%);
  --icon-btn-bg: hsl(222, 12%, 24%);
  --warning-bg: hsl(38, 45%, 18%);
  --warning-border: hsl(38, 45%, 30%);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  /* Mobile browsers resolve 100vh against the viewport with the address
     bar collapsed (its largest possible size), but the page first paints
     with the address bar still showing (a smaller visible area) - so a
     min-height:100vh box is taller than what's actually on screen from
     the very first load, letting the page scroll a few px even with just
     one empty row on it. 100dvh tracks the *current* visible viewport
     instead, so this only applies when there's real content to scroll to.
     Falls back to the line above on browsers that don't support dvh. */
  min-height: 100dvh;
  padding-bottom: 40px;
  transition: background 0.15s, color 0.15s;
}

header {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px 16px 14px;
  background: var(--raised);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 10;
}

.header-top { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.header-left { display: flex; align-items: center; gap: 8px; }
header h1 { font-size: 18px; margin: 0; white-space: nowrap; }

.conn-status {
  font-size: 10px;
  padding: 2px 7px;
  border-radius: 999px;
  background: #eee;
  color: var(--muted);
  white-space: nowrap;
}
.conn-status.online { background: #dcfce7; color: #15803d; }
.conn-status.offline { background: #fef3c7; color: #92400e; }

.presence-list {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
  min-height: 22px;
}
.presence-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 10.5px;
  background: #eef2ff;
  color: #3730a3;
  padding: 3px 8px;
  border-radius: 999px;
}
.presence-pill.me { font-weight: 700; box-shadow: inset 0 0 0 1.5px currentColor; }
.presence-pill .dot {
  width: 6px; height: 6px; border-radius: 50%;
  display: inline-block;
}
.presence-pill .dot.online { background: #22c55e; }
.presence-pill .dot.idle { background: #f59e0b; }
.presence-pill .session-count {
  font-size: 9px;
  opacity: 0.75;
  font-weight: 400;
}

/* custom tooltip, replaces native title/alt on presence pills */
.custom-tooltip {
  position: fixed;
  z-index: 60;
  background: #111827;
  color: white;
  font-size: 12px;
  padding: 5px 9px;
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.25);
  pointer-events: none;
  white-space: nowrap;
}
.custom-tooltip::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 100%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: #111827;
}
.custom-tooltip.below::after {
  top: auto;
  bottom: 100%;
  border-top-color: transparent;
  border-bottom-color: #111827;
}
.custom-tooltip.hidden { display: none; }

.header-right { margin-left: auto; display: flex; gap: 8px; align-items: center; flex-shrink: 0; }

.presence-pill.editable-color { cursor: pointer; }

.color-popover {
  position: fixed;
  z-index: 61;
  background: var(--raised);
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(15,18,26,0.22), 0 0 0 1px var(--border);
  padding: 12px;
  width: 200px;
}
.color-popover.hidden { display: none; }
.color-popover-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 8px;
}
.color-hue-slider {
  width: 100%;
  height: 14px;
  border-radius: 999px;
  appearance: none;
  -webkit-appearance: none;
  background: linear-gradient(to right,
    hsl(0 85% 60%), hsl(60 85% 60%), hsl(120 85% 60%), hsl(180 85% 60%),
    hsl(240 85% 60%), hsl(300 85% 60%), hsl(360 85% 60%));
  cursor: pointer;
}
.color-hue-slider::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: white;
  box-shadow: 0 0 0 2px rgba(0,0,0,0.35), 0 1px 3px rgba(0,0,0,0.3);
}
.color-hue-slider::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border: none;
  border-radius: 50%;
  background: white;
  box-shadow: 0 0 0 2px rgba(0,0,0,0.35), 0 1px 3px rgba(0,0,0,0.3);
}
.color-popover-actions { margin-top: 10px; }
.color-reset-btn {
  border: none;
  background: var(--icon-btn-bg);
  color: var(--text);
  font-size: 12px;
  padding: 6px 10px;
  border-radius: 6px;
  cursor: pointer;
  width: 100%;
}
.color-reset-btn:hover { background: var(--hover-tint); color: var(--blue-dark); }

.btn {
  border: none;
  border-radius: 8px;
  padding: 8px 14px;
  font-size: 14px;
  cursor: pointer;
  font-weight: 600;
  /* A plain .btn with no other modifier class (icon-btn/danger/etc all set
     their own) used to fall through to the browser's bare default button
     styling - grey, boxy, out of place next to everything else here. This
     matches .modal button's look instead, so e.g. User management's "Grant
     access" and IP bans' "Ban" (both plain .btn, no modifier) read as part
     of the app rather than a raw HTML button that slipped through. */
  background: var(--blue);
  color: white;
}
.btn:hover { background: var(--blue-dark); }
.btn.small, .btn.icon-btn {
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.btn.small { padding: 0 12px; font-size: 12px; }
.btn.icon-btn { padding: 0 10px; font-size: 15px; background: var(--icon-btn-bg); color: var(--text); }
.btn.icon-btn:hover { background: var(--hover-tint); }
.btn.danger { background: var(--danger-bg); color: var(--red); }
.btn.danger:hover { background: var(--danger-bg-hover); }
.btn.danger.armed { background: var(--red); color: white; }
.btn:disabled { cursor: default; opacity: 0.5; filter: grayscale(65%); }
.btn.danger:disabled:hover { background: var(--danger-bg); }

main {
  max-width: 720px;
  margin: 20px auto;
  padding: 0 16px;
}

.rows { display: flex; flex-direction: column; gap: 6px; }

.row {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 6px 8px;
  transition: background 0.15s, box-shadow 0.15s;
}
/* :focus-within also picks up the contenteditable row-edit box, so typing
   into a row shows the same "active" shade as hovering it */
/* :hover is scoped to devices that can actually hover - on touch devices
   there's no mouseleave to clear it, so tapping a row (e.g. to type into
   it) leaves that row's :hover permanently "stuck" white even after focus
   has genuinely moved to a different row. :focus-within reflects real
   focus regardless of input type, so it stays unscoped. */
@media (hover: hover) {
  .row:hover { background: var(--surface-hover); }
}
.row:focus-within { background: var(--surface-hover); }
.row.sortable-ghost { opacity: 0.4; }

.drag-handle {
  cursor: grab;
  color: var(--muted);
  padding: 8px 2px 4px;
  font-size: 16px;
  line-height: 1;
  user-select: none;
  touch-action: none;
  flex-shrink: 0;
}
.drag-handle:active { cursor: grabbing; }

.row-check {
  width: 19px;
  height: 19px;
  flex-shrink: 0;
  margin-top: 7px;
  accent-color: var(--blue);
  cursor: pointer;
}

.row-content { flex: 1; min-width: 0; }

.row-edit, .row-display {
  width: 100%;
  border: none;
  outline: none;
  font-size: 15px;
  line-height: 1.4;
  padding: 8px 6px;
  background: transparent;
  color: var(--text);
  min-width: 0;
  font-family: inherit;
  font-weight: 400;
  font-style: normal;
  letter-spacing: normal;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  white-space: pre-wrap;
  word-wrap: break-word;
  box-sizing: border-box;
  margin: 0;
}
.row-display { cursor: text; border-radius: 6px; }
.row-display.placeholder { color: #b3b7c2; }
.row-edit { cursor: text; border-radius: 6px; }
.row-edit:empty::before {
  content: 'Name / notes…';
  color: #b3b7c2;
}

/* formatting runs - one span per contiguous stretch of identical formatting
   and authorship, but a run can also get split further (spellcheck,
   autocorrect marks, a remote cursor sitting mid-word) without the
   highlight colour or author actually changing there. Corners only round
   at char-run-start/-end - the true edges of a same-colour/same-author
   stretch - so those internal splits still read as one continuous piece
   instead of a row of separate rounded blobs. */
.char-run { border-radius: 0; }
.char-run.char-run-start { border-top-left-radius: 3px; border-bottom-left-radius: 3px; }
.char-run.char-run-end { border-top-right-radius: 3px; border-bottom-right-radius: 3px; }
/* "who wrote this" hover cue: an inset outline (not a background fill, so it
   doesn't compete with highlight colouring) in the author's own colour.
   Added via JS after a short hover delay rather than a plain CSS :hover, so
   a quick mouse pass-over doesn't flash it - see the mouseenter/mouseleave
   handlers in buildFormattedNode. Applied to every span in the same
   contiguous author run at once, so the outline reads as one unbroken box
   around their whole section rather than one box per split word. */
.char-run.char-run-hover {
  box-shadow: 0 1.5px 0 0 var(--word-author-border-color, var(--hover-tint)),
              0 -1.5px 0 0 var(--word-author-border-color, var(--hover-tint));
}
.char-run.char-run-hover.author-run-start {
  box-shadow: 0 1.5px 0 0 var(--word-author-border-color, var(--hover-tint)),
              0 -1.5px 0 0 var(--word-author-border-color, var(--hover-tint)),
              1.5px 0 0 0 var(--word-author-border-color, var(--hover-tint));
  border-top-left-radius: 3px;
  border-bottom-left-radius: 3px;
}
.char-run.char-run-hover.author-run-end {
  box-shadow: 0 1.5px 0 0 var(--word-author-border-color, var(--hover-tint)),
              0 -1.5px 0 0 var(--word-author-border-color, var(--hover-tint)),
              -1.5px 0 0 0 var(--word-author-border-color, var(--hover-tint));
  border-top-right-radius: 3px;
  border-bottom-right-radius: 3px;
}
.char-run.char-run-hover.author-run-start.author-run-end {
  box-shadow: 0 0 0 1.5px var(--word-author-border-color, var(--hover-tint));
}
/* in dark mode --word-author-color and highlight colours are already swapped
   for darker, higher-contrast variants in JS (see colorForNameSoft /
   displayHighlightColor), so white/light text stays legible over them */
/* v2.3.1: highlighted text used to darken (light mode) / brighten (dark
   mode) slightly on hover here - removed per user request, highlight now
   stays a flat colour regardless of hover. */

/* spellcheck used to render a wavy underline here (.spell-error) - removed
   in favor of silent autocorrect (see tryAutocorrect in app.js). The class
   is still applied to misspelled runs for the right-click "suggestions"
   menu, just with no visual styling now. */

/* autocorrect mark - a soft, thin dotted underline shown only in the local
   editor's own editable box for the word they just typed, so only the
   person who typed it sees that a change was made. Deliberately subtle
   (not the red spellcheck squiggle) since this isn't flagging an error,
   just noting a substitution happened. */
.char-run.autocorrect-mark {
  text-decoration: underline dotted;
  text-decoration-color: var(--text-muted, #9ca3af);
  text-underline-offset: 3px;
}

/* remote user's live cursor position, shown at the exact character offset -
   a regular full-height caret, like a normal text cursor. Explicit height
   (rather than top/bottom offsets against the element's own box) avoids an
   empty zero-width inline-block collapsing to near-nothing in some browsers,
   which was rendering as a tiny blinking dot instead of a caret. */
.char-cursor {
  display: inline-block;
  width: 0;
  height: 1.15em;
  vertical-align: -0.2em;
  position: relative;
}
.char-cursor::before {
  content: '';
  position: absolute;
  left: -1px;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--cursor-color, #666);
  animation: blink-caret 1s step-start infinite;
}
@keyframes blink-caret { 50% { opacity: 0; } }

.row.checked .row-edit, .row.checked .row-display {
  text-decoration: line-through;
  color: var(--muted);
}
.row.fmt-bold .row-edit, .row.fmt-bold .row-display { font-weight: 700; }
.row.fmt-italic .row-edit, .row.fmt-italic .row-display { font-style: italic; }
.row.fmt-underline .row-edit, .row.fmt-underline .row-display { text-decoration: underline; }
.row.fmt-strike .row-edit, .row.fmt-strike .row-display { text-decoration: line-through; }
.row.fmt-highlight .row-display { background: transparent; }

.remote-chip {
  display: none;
  position: absolute;
  top: -9px;
  right: 8px;
  font-size: 10px;
  color: white;
  padding: 2px 7px;
  border-radius: 999px;
  white-space: nowrap;
}

.row-timestamp {
  display: none;
  align-self: center;
  flex-shrink: 0;
  font-size: 10px;
  color: var(--muted);
  opacity: 0.65;
  white-space: nowrap;
  margin: 0 2px;
  cursor: default;
}
[data-timestamps="on"] .row-timestamp { display: inline-block; }

.row-del {
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 18px;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 6px;
  opacity: 0.5;
  flex-shrink: 0;
  margin-top: 3px;
}
.row-del:hover { opacity: 1; background: #fee2e2; color: var(--red); }

.add-row-btn {
  margin-top: 12px;
  width: 100%;
  padding: 10px;
  border: 1px dashed var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--muted);
  font-size: 14px;
  cursor: pointer;
}
.add-row-btn:hover { background: var(--hover-tint); color: var(--blue); }

.settings-logout {
  width: 100%;
  margin-top: 16px;
}

.settings-version {
  text-align: center;
  color: #c3c6cf;
  font-size: 11px;
  margin-top: 20px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

/* ---------- right-click formatting menu ---------- */
.ctx-menu {
  position: fixed;
  z-index: 55;
  min-width: 180px;
  background: var(--raised);
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(15,18,26,0.22), 0 0 0 1px var(--border);
  padding: 6px;
}
.ctx-menu.hidden { display: none; }
.ctx-scope-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  padding: 6px 8px 4px;
}
.ctx-item {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  background: transparent;
  padding: 8px 10px;
  font-size: 14px;
  border-radius: 6px;
  cursor: pointer;
  color: var(--text);
}
.ctx-item:hover { background: var(--hover-tint); }
.ctx-item.active { background: #ede4fc; color: var(--blue-dark); font-weight: 600; }
.ctx-item.ctx-bold { font-weight: 700; }
.ctx-item.ctx-italic { font-style: italic; }
.ctx-item.ctx-underline { text-decoration: underline; }
.ctx-item.ctx-strike { text-decoration: line-through; }
.ctx-item.ctx-clear { color: var(--red); }
.ctx-item.ctx-suggestion { font-weight: 600; }
.ctx-no-suggestions { font-style: italic; padding-bottom: 6px; }
.ctx-item.ctx-clear:hover { background: #fee2e2; }
.ctx-autocorrect-undo { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.ctx-autocorrect-undo .ctx-ac-line { display: flex; align-items: center; }
.ctx-autocorrect-undo .ctx-ac-sub { font-size: 12px; color: var(--muted); }
.ctx-autocorrect-undo .ctx-icon { display: inline-block; margin-right: 6px; font-weight: 700; }
.ctx-item.ctx-disabled { color: var(--text-faint, #9aa0a8); cursor: default; opacity: 0.55; }
.ctx-item.ctx-disabled:hover { background: none; }
.ctx-sep { height: 1px; background: var(--border); margin: 6px 2px; }
.ctx-swatch-row { display: flex; gap: 6px; padding: 4px 8px 8px; }
.ctx-swatch {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 1px solid rgba(0,0,0,0.08);
  cursor: pointer;
  padding: 0;
}
.ctx-swatch:hover { transform: scale(1.12); box-shadow: 0 0 0 2px var(--hover-tint); }
.ctx-swatch-none {
  background: var(--raised);
  color: var(--muted);
  font-size: 13px;
  line-height: 22px;
}

.version-tag { display: none; } /* legacy, superseded by .settings-version */

.undo-toast {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: #111827;
  color: white;
  padding: 10px 14px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 14px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.25);
  z-index: 20;
}
.undo-toast.hidden { display: none; }
.undo-toast button {
  background: var(--blue);
  color: white;
  border: none;
  padding: 5px 10px;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
}
.undo-timer { color: #9ca3af; font-size: 12px; }

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 18, 26, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 30;
}
.modal-overlay.visible { display: flex; }
.modal {
  background: var(--raised);
  border-radius: 14px;
  padding: 24px;
  width: 90%;
  max-width: 340px;
  text-align: center;
}
.modal h2 { margin: 0 0 6px; color: var(--text); }
.modal p { color: var(--muted); font-size: 14px; margin: 0 0 16px; }
.modal input {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 15px;
  margin-bottom: 12px;
  background: var(--surface);
  color: var(--text);
}
.modal button {
  width: 100%;
  padding: 10px;
  border: none;
  border-radius: 8px;
  background: var(--blue);
  color: white;
  font-weight: 600;
  font-size: 15px;
  cursor: pointer;
}
.modal button:hover { background: var(--blue-dark); }

/* while the name modal is up, the list behind it is blurred and inert -
   nothing loads/renders as real content until a name is given */
body.awaiting-name > header,
body.awaiting-name > main {
  filter: blur(8px);
  pointer-events: none;
  user-select: none;
}

.settings-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 18, 26, 0.4);
  display: none;
  /* Above .manager-dashboard's z-index:45 (see below) - Settings must
     always be reachable/visible even while the Manager dashboard is open,
     since that's the only place to turn Manager mode back off. */
  z-index: 46;
}
.settings-overlay.visible { display: block; }

.settings-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 300px;
  max-width: 85vw;
  background: var(--raised);
  box-shadow: -8px 0 24px rgba(0,0,0,0.15);
  transform: translateX(100%);
  transition: transform 0.25s ease;
  z-index: 47;
  padding: 20px;
  display: flex;
  flex-direction: column;
}
.settings-panel.open { transform: translateX(0); }

.settings-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}
.settings-header h2 { margin: 0; font-size: 17px; }
.settings-close {
  border: none;
  background: transparent;
  font-size: 22px;
  cursor: pointer;
  color: var(--muted);
  line-height: 1;
}

.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--border);
}
.settings-row label { font-size: 14px; }

/* nice toggle switch, replaces the plain checkbox in Settings */
.toggle-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  flex-shrink: 0;
}
.toggle-switch input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}
.toggle-track {
  width: 42px;
  height: 24px;
  border-radius: 999px;
  background: var(--border);
  display: inline-block;
  position: relative;
  transition: background 0.2s ease;
}
.toggle-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--raised);
  box-shadow: 0 1px 3px rgba(0,0,0,0.35);
  transition: transform 0.2s ease;
}
.toggle-switch input:checked ~ .toggle-track { background: var(--blue); }
.toggle-switch input:checked ~ .toggle-track .toggle-thumb { transform: translateX(18px); }
.toggle-switch input:focus-visible ~ .toggle-track { box-shadow: 0 0 0 3px var(--hover-tint); }

.settings-hint {
  font-size: 12px;
  color: var(--muted);
  margin-top: 4px;
}
.settings-hint.hint-warning {
  color: var(--orange);
  background: var(--warning-bg, #fffbeb);
  border: 1px solid var(--warning-border, #fde68a);
  border-radius: 8px;
  padding: 8px 10px;
  margin-top: 8px;
}
.settings-hint.hidden { display: none; }
/* v4.2.1: the "Stats" header button, shown only once the server confirms
   this name has manager access - no separate Settings toggle any more. */
#managerStatsBtn.hidden { display: none; }

/* ---------- v4.0.0/v4.2.0: Manager dashboard ---------- */
/* A full-page takeover styled like the admin dashboard itself (reuses
   .admin-app/.admin-tabs/.admin-tab/.admin-tab-panel/.admin-main from
   admin.css almost verbatim), not a Settings-style slide-out drawer - per
   the feature request, Manager mode should feel like its own dashboard
   with proper tabs, not another row tucked inside Settings. This is the
   narrow/mobile shape (full-page overlay); see the min-width blocks below
   for the wide-screen floating/dockable shape. */
.manager-dashboard {
  position: fixed; inset: 0; z-index: 45;
  background: var(--bg); overflow-y: auto;
}
.manager-dashboard.hidden { display: none; }
/* v4.2.2: a full-width native scrollbar butts straight up against the
   dashboard's rounded corners (see the docked/floating shape below),
   visibly slicing into the curve. A slim, inset, rounded scrollbar - a
   few px in from the edge, with its own rounded ends - reads as part of
   the card instead of cutting into it. Firefox via scrollbar-width/-color,
   WebKit/Blink (Chrome, Edge) via the ::-webkit-scrollbar family below. */
.manager-dashboard { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
.manager-dashboard::-webkit-scrollbar { width: 9px; }
.manager-dashboard::-webkit-scrollbar-track { background: transparent; margin: 14px 0; }
.manager-dashboard::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.manager-dashboard::-webkit-scrollbar-thumb:hover { background: var(--muted); background-clip: padding-box; }
/* This page's own header already reuses .header-top/.header-left/
   .header-right; admin.html's normal (non-fixed) header works fine
   unmodified once its containing element (.manager-dashboard above) is
   itself the fixed, full-viewport box. */
/* A touch smaller than the live board's own header h1 (18px) - narrows the
   gap between the two headers' row heights so they line up better when
   docked beside each other, and reads fine on a card that's already
   visually distinct from the main page. v4.2.7: sized down again (16px ->
   14px) to make room for the Customer Stats/User Stats tabs now sharing
   this same row - see below. */
.manager-dashboard .header-left h1 { font-size: 14px; }

/* v4.2.7: the grab handle now leads (see index.html), and the Customer
   Stats/User Stats tabs moved into this same row instead of their own row
   below - both to save vertical space (worth more on a short, bottom-
   docked card than a wide one) and because there was clearly room for it.
   The tabs are sized to match the close button's own 34px/centered shape
   (the same convention .btn.small/.btn.icon-btn already use elsewhere) so
   everything in this row reads as one consistent height, and deliberately
   don't grow past their natural width even when the row has plenty of
   spare space on a wide, side-docked card - two short buttons stretched
   edge-to-edge would look like they're straining to fill room they don't
   need, not like part of the same tight row as the title and close
   button. `flex-wrap: nowrap` on the row keeps the tabs from dropping to
   their own line the way the live board's own (differently-purposed)
   .header-top is allowed to. */
#managerStatsOverlay .header-top { flex-wrap: nowrap; }
#managerStatsOverlay .header-left,
#managerStatsOverlay .header-right { flex-shrink: 0; }
#managerStatsOverlay .header-top > .admin-tabs {
  flex: 0 1 auto;
  min-width: 0;
  margin: 0 8px;
  gap: 4px;
  flex-wrap: nowrap;
}
#managerStatsOverlay .header-top > .admin-tabs .admin-tab {
  flex: 0 1 auto;
  min-width: 0;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 10px;
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* v4.2.0/v4.2.2: on screens with room to spare, the dashboard detaches into
   a floating, dockable card instead of covering the whole page - pin it to
   the right (default), left, or bottom of the live board by dragging its
   header's grab handle onto one of the drop zones that appear while
   dragging (see #mgrDockHandle/#mgrDockZones and app.js). The choice is
   remembered per signed-in name. It sits below the main header with a
   visible gap on every edge (rounded corners + shadow, never flush against
   the viewport) specifically so the Settings drawer - still reachable via
   z-index alone, but doubly clear here - never reads as part of the same
   surface as the dashboard. Below the lower of the two breakpoints below,
   the mobile/narrow full-page overlay behavior (opened via the header's
   Stats button) is completely unchanged - none of this applies there.

   v4.2.2: body's padding used to be the ONLY thing reserving space for the
   panel, which also shrank `header` (a plain block child of body with no
   width of its own) - that's what made the live board's own header look
   cut short/squeezed beside a docked panel instead of spanning the full
   window. Now `header` gets pulled back out to full width again wherever
   body is padded for docking (see the negative-margin rule below). The
   panel itself is positioned below the header (see --mgr-panel-top), never
   beside it, so the header never actually needs to make room for it - a
   first attempt at this fix re-added that reserved amount as the header's
   own padding "to be safe", which just squeezed the title/buttons in from
   the edge for no reason; header now gets ONLY the negative margin, so its
   title/buttons land at their usual 16px inset from the true window edge
   regardless of docking. Bottom-docking doesn't touch horizontal space at
   all, so it never needed any of this and is also allowed a lower
   breakpoint than left/right docking - a manager on a narrower-but-not-
   mobile screen can still pin the dashboard to the bottom and keep the
   board visible too. */
@media (min-width: 500px) {
  /* Excludes mgr-dock-full - unpinned is meant to look exactly like the
     narrow/mobile full-page overlay below, not like a rounded, shadowed
     card, so it's explicitly opted out of this rather than needing its own
     override to undo it again right after. */
  body.manager-stats-open:not(.mgr-dock-full) .manager-dashboard {
    border-radius: 14px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28), 0 0 0 1px var(--border);
  }
  /* 46vh card + 16px outer margin + 16px gap from the board. */
  body.manager-stats-open.mgr-dock-bottom { padding-bottom: calc(46vh + 32px); }
  body.mgr-dock-bottom .manager-dashboard {
    top: auto; left: 16px; right: 16px; bottom: 16px;
    width: auto; height: 46vh; min-height: 340px;
  }
}

@media (min-width: 1000px) {
  /* Reserved width now scales with the viewport - clamped between 360px
     and 680px - instead of a fixed 712px. A fixed reservation left almost
     nothing for the board's own header/content on any screen only a
     little wider than this breakpoint, which is what made things look
     squeezed just above 1000px. */
  body.manager-stats-open.mgr-dock-right,
  body.manager-stats-open.mgr-dock-left {
    --mgr-w: clamp(360px, 45vw, 680px);
  }
  body.manager-stats-open.mgr-dock-right { padding-right: calc(var(--mgr-w) + 32px); }
  body.manager-stats-open.mgr-dock-left { padding-left: calc(var(--mgr-w) + 32px); }

  /* The live board's header bar must always reach both edges of the
     window and keep its title/buttons at their normal 16px inset, exactly
     as if no panel were docked at all - only the board's own content area
     (via body's padding above) is meant to give way. Pulling header back
     out by exactly the amount body was padded (negative margin, with no
     matching padding re-added) does this: the panel itself sits BELOW the
     header (see --mgr-panel-top below), never beside it, so there's
     nothing for the header to make room for in the first place. */
  body.manager-stats-open.mgr-dock-right > header {
    margin-right: calc(-1 * (var(--mgr-w) + 32px));
  }
  body.manager-stats-open.mgr-dock-left > header {
    margin-left: calc(-1 * (var(--mgr-w) + 32px));
  }

  body.manager-stats-open.mgr-dock-right .manager-dashboard,
  body.manager-stats-open.mgr-dock-left .manager-dashboard {
    top: var(--mgr-panel-top, 96px);
    bottom: 16px;
  }
  body.mgr-dock-right .manager-dashboard { left: auto; right: 16px; width: var(--mgr-w); }
  body.mgr-dock-left .manager-dashboard { right: auto; left: 16px; width: var(--mgr-w); }
}

/* Grab handle - hidden below the lower (bottom-docking) breakpoint, where
   dragging to a dock position has no meaning (mobile/narrow always uses
   the full-page overlay above). 500px is close to the narrowest a desktop
   browser window can actually be resized to (confirmed against Chrome's
   own floor), so this is effectively "docking is available whenever
   there's a real desktop window to speak of". */
.mgr-dock-handle {
  display: none;
  border: none; background: transparent; color: var(--muted);
  font-size: 15px; line-height: 1; padding: 4px 6px; border-radius: 6px;
  cursor: grab; touch-action: none;
}
.mgr-dock-handle:hover { background: var(--hover-tint); color: var(--text); }
.mgr-dock-handle:active { cursor: grabbing; }
@media (min-width: 500px) {
  .mgr-dock-handle { display: inline-flex; align-items: center; }
}

/* v4.2.13: while actively being dragged, the dashboard itself (not a stand-
   in/ghost) follows the cursor - see app.js's pointermove handler, which
   drives this purely via `transform: translate(...)` on top of the
   position/size app.js freezes as inline styles at drag-start. This class
   only needs to cover the *cosmetic* side of "being picked up": always
   rounded + shadowed (even for a full-screen dashboard being dragged off
   of full, which normally has neither - see the :not(.mgr-dock-full) rule
   above), lifted above the drop zones themselves (z-index 48) so it's
   never hidden underneath them mid-drag, and pointer-events:none so
   nothing under the moving card (its own content included) reacts to
   hover while it's sliding around - the drag itself is entirely driven by
   the handle's own pointer capture, not by this element. */
.manager-dashboard.mgr-dragging {
  z-index: 49;
  border-radius: 14px;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.35), 0 0 0 1px var(--border);
  cursor: grabbing;
  pointer-events: none;
}
/* v4.2.13: the "snapping into place" animation - app.js's snapDashboardTo
   adds this class for the duration of a transform transition, then removes
   it again once that transition finishes. Used both when a drag lands on a
   newly-picked pin and when an invalid drop springs the dashboard back to
   the pin it already had - see the comment above hitTestDockZone in app.js
   for why an invalid drop is a spring-back rather than a fifth outcome. */
.manager-dashboard.mgr-snap {
  transition: transform 0.28s cubic-bezier(0.22, 0.9, 0.32, 1);
}

/* Drop zones, shown only for the duration of a drag - dashed outlines that
   light up individually as the pointer nears each edge, so it's clear
   where letting go will pin the dashboard. Sits above the dashboard itself
   (which is still visible and being dragged "from") and above Settings,
   since a drag in progress should never be obscured.

   v4.2.10: sized with clamp()/vw/vh instead of fixed pixels, so the pin
   zones stay comfortably large on a big monitor without crowding each
   other - or the new center zone - down near the 1000px breakpoint where
   left/right docking first becomes available. */
.mgr-dock-zones { position: fixed; inset: 0; z-index: 48; pointer-events: none; }
.mgr-dock-zones.hidden { display: none; }
.mgr-dock-zone {
  position: absolute; display: flex; align-items: center; justify-content: center;
  background: rgba(124, 58, 237, 0.10); border: 2px dashed var(--blue);
  border-radius: 12px; color: var(--blue); font-size: 15px; font-weight: 700;
  opacity: 0.5; transition: opacity 0.15s ease, background 0.15s ease;
}
.mgr-dock-zone.active { opacity: 1; background: rgba(124, 58, 237, 0.20); }
.mgr-dock-zone-left { top: 16px; bottom: 16px; left: 16px; width: clamp(220px, 22vw, 300px); }
.mgr-dock-zone-right { top: 16px; bottom: 16px; right: 16px; width: clamp(220px, 22vw, 300px); }
.mgr-dock-zone-bottom { left: 16px; right: 16px; bottom: 16px; height: clamp(160px, 22vh, 220px); }
/* v4.2.2: below the left/right breakpoint (see the 1000px block above),
   only pinning to the bottom actually does anything - hide the other two
   zones during a drag there instead of offering a drop that silently
   falls back to the full-page overlay. Unpinning (the center zone) has no
   such width requirement - it never reserves board space either way - so
   it stays offered at every width the drag handle itself is available at
   (>=500px, see .mgr-dock-handle below). */
.mgr-dock-zones.narrow .mgr-dock-zone-left,
.mgr-dock-zones.narrow .mgr-dock-zone-right { display: none; }

/* v4.2.10/v4.2.11: the "Unpin" zone - closes the dashboard and switches it
   back to its own full-screen page for next time, rather than pinned to an
   edge (see app.js's endDrag) - deliberately quieter and more muted than
   the three pin zones (grey dashed outline vs. purple) since pinning is
   the common action here and unpinning isn't. v4.2.13: dropping in the
   dead space between all four zones is an invalid drop (see app.js's
   hitTestDockZone) that springs the dashboard back to whichever pin it
   already had, rather than resolving to whichever zone merely looks
   nearest - a drop only counts as landing on this zone if it's visually
   inside this exact box. Sized off the viewport (capped so it never dwarfs
   the screen) and kept clear of the edge zones at every width above - see
   the clamp()s above for how that math works out. */
.mgr-dock-zone-center {
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(440px, 42vw); height: min(260px, 38vh);
  border-color: var(--muted); color: var(--muted);
  background: rgba(118, 124, 140, 0.10);
}
.mgr-dock-zone-center.active {
  background: rgba(118, 124, 140, 0.20); color: var(--text); border-color: var(--text);
}

@media (max-width: 480px) {
  header h1 { font-size: 16px; }
  body { padding-bottom: 96px; }

  .header-right { position: static; margin-left: auto; }

  #settingsBtn {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 25;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    font-size: 20px;
    padding: 0;
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--border);
    box-shadow: 0 4px 14px rgba(0,0,0,0.15);
  }
  #settingsBtn:hover { background: var(--surface-hover); }

  /* Clear all stays desktop-only; mobile has no room for it next to the FAB */
  #clearBtn { display: none; }
}
