/* camviewer — dark by default. A camera wall lives in a back office at 2am;
   a bright UI next to a black frame destroys whatever night vision the person
   looking at it has, and washes out exactly the dark detail they are reading. */

:root {
  --bg:       #0b0e13;
  --panel:    #11151b;
  --panel-2:  #161b23;
  --line:     #232a34;
  --text:     #e6edf5;
  --muted:    #8b96a5;
  --accent:   #4da3ff;
  --ok:       #3fb950;
  --warn:     #d29922;
  --err:      #ff6b6b;
  --radius:   6px;
}

* { box-sizing: border-box; }

/* The whole UI hides elements with the `hidden` attribute (modals, tooltips,
   no-signal overlays, panels). A normal author rule like `.modal{display:grid}`
   OUTRANKS the browser's own `[hidden]{display:none}` (author beats UA), so
   without this every such element — the modal backdrop above all — stays
   painted after JS sets `hidden`, dimming the app. `!important` makes `hidden`
   win regardless of the element's own display rule. */
[hidden] { display: none !important; }

/* Viewer role (e.g. police): the server already refuses every admin endpoint,
   but the UI should not tease controls it cannot use. Anything marked
   [data-admin] is hidden once the app knows it is running as a non-admin. */
body[data-role="viewer"] [data-admin] { display: none !important; }

.userlist { display: grid; gap: 6px; margin: 10px 0; }
.userlist .urow { display: flex; align-items: center; gap: 8px; padding: 6px 8px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 6px; font-size: 13px; }
.userlist .urole { font-size: 11px; padding: 1px 6px; border-radius: 4px; background: var(--line); color: var(--muted); }
.userlist .urole.admin { background: #3a2a08; color: #f0c04a; }
.userlist .uspacer { flex: 1; }
.userform { display: grid; gap: 6px; margin-top: 10px; }
.userform input, .userform select { width: 100%; padding: 6px 8px; background: var(--panel-2);
  border: 1px solid var(--line); color: var(--text); border-radius: 6px; font-size: 12px; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif;
  overscroll-behavior: none;
}
/* The whole app fits the window: top bar, then the stage + side panel taking
   exactly what is left. Nothing here scrolls the page itself — lists scroll
   inside their panels, and the video, transport and timeline are always on
   screen together. */
body { display: flex; flex-direction: column; overflow: hidden; }

button, select, input { font: inherit; color: inherit; }
code, kbd { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: .92em; }
kbd { background: var(--panel-2); border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px; }
.muted { color: var(--muted); }
.pad { padding: 12px 14px; }

.warn {
  background: #2a2010; border: 1px solid #5a4416; color: #e8c46a;
  padding: 8px 10px; border-radius: var(--radius); margin: 8px 0; font-size: 13px;
}
.err { color: var(--err); }
.fatal { padding: 24px; color: var(--err); white-space: pre-wrap; }

/* ---------- login gate ------------------------------------------------- */

.gate { display: grid; place-items: center; flex: 1; }
.gate form {
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  padding: 28px 30px; width: min(340px, 90vw); display: grid; gap: 10px;
}
.gate h1 { margin: 0; font-size: 20px; }
.gate p { margin: 0 0 6px; }
.gate input, .gate button {
  padding: 9px 11px; border-radius: var(--radius); border: 1px solid var(--line);
  background: var(--panel-2);
}
.gate button { background: var(--accent); color: #04121f; border: 0; font-weight: 600; cursor: pointer; }

/* ---------- top bar ---------------------------------------------------- */

.topbar {
  flex: none;
  display: flex; align-items: center; gap: 14px;
  padding: 8px 12px; background: var(--panel); border-bottom: 1px solid var(--line);
}
.brand { display: flex; align-items: center; gap: 8px; }
.topbar-mid { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.topbar-right { display: flex; align-items: center; gap: 6px; }

.badge {
  font-size: 11px; padding: 2px 7px; border-radius: 20px;
  background: var(--panel-2); border: 1px solid var(--line); color: var(--muted);
  white-space: nowrap;
}
.badge.sim  { background: #3a2a08; border-color: #6b4f10; color: #f0c04a; }
.badge.okb  { background: #10281a; border-color: #1d5233; color: #6fd48a; }
.badge.errb { background: #2c1416; border-color: #6b2429; color: #ff9a9a; }
.badge.bwb  { background: #1c1f24; border-color: #3a414b; color: #cfd6df; }

.pill, .tbtn {
  background: var(--panel-2); border: 1px solid var(--line); color: var(--text);
  border-radius: var(--radius); padding: 6px 11px; cursor: pointer;
}
.pill:hover, .tbtn:hover { background: #1d2430; }
.pill.on, .tbtn.on { background: #14331f; border-color: #266b3c; color: #7ee0a0; }
#btnLive.on { background: #331414; border-color: #7a2626; color: #ff8f8f; }

.zoomers { display: flex; }
.zoomers button {
  background: var(--panel-2); border: 1px solid var(--line); border-right: 0;
  padding: 6px 10px; cursor: pointer; color: var(--muted);
}
.zoomers button:first-child { border-radius: var(--radius) 0 0 var(--radius); }
.zoomers button:last-child  { border-right: 1px solid var(--line); border-radius: 0 var(--radius) var(--radius) 0; }
.zoomers button:hover { color: var(--text); background: #1d2430; }

input[type=date] {
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 5px 8px; color-scheme: dark;
}

/* ---------- layout ----------------------------------------------------- */

.layout {
  display: grid;
  /* Wide enough that every tab label sits comfortably with room for a badge. */
  grid-template-columns: minmax(0, 1fr) 440px;
  flex: 1; min-height: 0;   /* whatever the top bar leaves, however tall it wraps */
}
.stage { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.side {
  border-left: 1px solid var(--line); background: var(--panel);
  display: flex; flex-direction: column; min-height: 0;
}

/* ---------- video ------------------------------------------------------ */

.video-area {
  flex: 1; min-height: 0; display: grid; gap: 3px; padding: 3px; background: #05070a;
}
/* Rows are minmax(0, 1fr), not 1fr: a plain 1fr row refuses to shrink below
   its content's intrinsic height, and a video's is its 16:9 of the pane width
   — which made the stage taller than the window and forced the page to scroll
   to reach the timeline. Fixed rows per page size, so a short last page keeps
   the same pane size. */
.video-area.grid-1  { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr); }
.video-area.grid-4  { grid-template-columns: 1fr 1fr; grid-template-rows: repeat(2, minmax(0, 1fr)); }
.video-area.grid-9  { grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, minmax(0, 1fr)); }
.video-area.grid-16 { grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(4, minmax(0, 1fr)); }

/* Grid paging controls in the top bar. */
.pager {
  display: flex; align-items: center;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius);
}
.pager button {
  background: none; border: 0; color: var(--text); padding: 4px 9px; cursor: pointer;
  font-size: 16px; line-height: 1.2;
}
.pager button:hover { background: #1d2430; }
.pager button[disabled] { opacity: .35; cursor: default; background: none; }
.pager span { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; color: var(--muted); padding: 0 2px; }
select.pill { padding: 5px 8px; }

.pane { position: relative; background: #000; overflow: hidden; min-height: 0; min-width: 0; border-radius: 3px; cursor: pointer; }
/* Anchored to the pane rather than flowing in it, so the video's own aspect
   never dictates the pane's (and the page's) height. */
.pane video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; }
/* The active pane: the one the timeline, scrub preview and frame tools work on. */
.pane.focus { outline: 2px solid var(--accent); outline-offset: -2px; }
.pane.focus .pane-name { color: var(--accent); }
/* Orientation correction for a camera mounted sideways or inverted. The stored
   stream is never re-encoded — the picture is rotated here, on display. object-fit
   keeps the whole frame visible at 90°/270° (letterboxed rather than clipped),
   which is the safe trade for a rare portrait mount. */
.pane[data-rot="90"]  video { transform: rotate(90deg); }
.pane[data-rot="180"] video { transform: rotate(180deg); }
.pane[data-rot="270"] video { transform: rotate(270deg); }
/* Mirrors the camera's real IR-cut state, so the day/night feature is
   visible in the picture rather than only in a settings panel. */
.pane.bw video { filter: grayscale(1) contrast(1.08); }

.pane-overlay {
  position: absolute; inset: 0; pointer-events: none;
  display: flex; flex-direction: column; justify-content: space-between;
  padding: 7px 9px;
  background: linear-gradient(180deg, rgba(0,0,0,.5), transparent 22%, transparent 78%, rgba(0,0,0,.5));
}
.pane-top { display: flex; align-items: center; gap: 7px; }
.pane-name { font-weight: 600; font-size: 13px; text-shadow: 0 1px 3px #000; }
.pane-ch { font-size: 11px; color: var(--muted); }
.pane-bottom { display: flex; justify-content: flex-end; }
.pane-time {
  font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px;
  background: rgba(0,0,0,.55); padding: 2px 6px; border-radius: 3px;
}
.pane-nosignal {
  position: absolute; inset: 0; display: grid; place-content: center; gap: 4px;
  text-align: center; background: repeating-linear-gradient(45deg, #180a0b, #180a0b 8px, #1e0d0e 8px, #1e0d0e 16px);
}
.pane-nosignal span { color: #ff8f8f; font-weight: 700; letter-spacing: .08em; font-size: 13px; }
.pane-nosignal small { color: #a97b7b; }

/* ---------- contact sheet ---------------------------------------------- */

.sheet-area { flex: 1; min-height: 0; display: flex; flex-direction: column; background: #05070a; }
.sheet-bar {
  display: flex; align-items: center; gap: 8px; padding: 7px 10px;
  background: var(--panel); border-bottom: 1px solid var(--line); font-size: 12px;
}
.sheet-bar select {
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 4px 7px; font-size: 12px;
}
.sheet-hint { margin-left: auto; }

.sheet-grid {
  flex: 1; overflow-y: auto; padding: 6px;
  display: grid; gap: 4px;
  /* Tiles size themselves to the pane so the whole window stays glanceable
     rather than forcing a horizontal scroll nobody will use. */
  grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
  align-content: start;
}

.tile {
  position: relative; aspect-ratio: 16 / 9; background: #0b0e13;
  border: 1px solid transparent; border-radius: 3px; overflow: hidden; cursor: pointer;
}
.tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tile b {
  position: absolute; left: 0; bottom: 0; padding: 1px 4px;
  background: rgba(0,0,0,.6); font: 10px/1.4 ui-monospace, Menlo, Consolas, monospace;
  font-weight: 400; color: #cfd6df;
}
.tile:hover { border-color: var(--accent); }
/* A tile with something in it gets a ring, so a block of activity reads as a
   block even when the subject itself is small at this size. */
.tile.hasevent { border-color: #6b5a2a; }
.tile.now { border-color: #ff4d4d; box-shadow: 0 0 0 1px #ff4d4d; }
.tile-kinds { position: absolute; top: 0; left: 0; right: 0; height: 3px; display: flex; }
.tile-kinds i { flex: 1; }

.tile.gap {
  display: grid; place-content: center; gap: 2px; text-align: center;
  background: repeating-linear-gradient(45deg, #180a0b, #180a0b 6px, #1e0d0e 6px, #1e0d0e 12px);
  border-color: #4a1c1f;
}
.tile.gap span { color: #ff8f8f; font-size: 10px; letter-spacing: .04em; }
.tile.gap b { position: static; background: none; color: #a97b7b; }

/* ---------- transport -------------------------------------------------- */

.transport {
  flex: none;   /* the video gives way, never the controls or the timeline */
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 7px 10px; background: var(--panel); border-top: 1px solid var(--line);
}
.tbtn.wide { min-width: 116px; }
.tbtn.play { min-width: 46px; font-size: 15px; }
.tbtn.small { padding: 4px 9px; font-size: 12px; }
.tbtn.primary { background: var(--accent); color: #04121f; border-color: transparent; font-weight: 600; }
.tbtn.danger { color: #ff9a9a; border-color: #5c2a2e; }
.tbtn[disabled] { opacity: .45; cursor: not-allowed; }
.tbtn:is(a) { text-decoration: none; display: inline-block; }

.transport select {
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 6px 8px;
}
.playhead-readout {
  font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px;
  display: flex; gap: 8px; align-items: baseline; margin-left: 6px;
}
.selection-box {
  margin-left: auto; display: flex; align-items: center; gap: 6px;
  background: #10222f; border: 1px solid #1d4a6b; border-radius: var(--radius);
  padding: 3px 4px 3px 10px; font-size: 12px;
}
.sel-hint { margin-left: auto; font-size: 12px; }

/* ---------- timeline --------------------------------------------------- */

.timeline-wrap { flex: none; position: relative; background: #0e1116; border-top: 1px solid var(--line); }
#timeline { display: block; cursor: crosshair; user-select: none; }
/* Scrub preview. Fixed to the viewport (positioned by hoverTip above the
   pointer and clamped on-screen), so it can never be clipped by the timeline
   box it is drawn from. */
.tltip {
  position: fixed; z-index: 40; pointer-events: none;
  /* One size, always: fixed width, and the text below is always two lines. */
  width: min(158px, 46vw); box-sizing: border-box;
  background: #1b222c; border: 1px solid var(--line); border-radius: 4px;
  padding: 5px 8px; font-size: 12px; white-space: nowrap;
  box-shadow: 0 4px 14px rgba(0,0,0,.5);
}
/* The still under the cursor while hovering or dragging the timeline. Fills
   the tip's fixed width at a fixed aspect, so frames swap without a resize. */
#tlThumb {
  display: block; width: 100%; aspect-ratio: 16 / 9;
  object-fit: cover; border-radius: 3px; margin-bottom: 5px; background: #000;
}
#tlTipText { height: 30px; line-height: 15px; overflow: hidden; }
#tlTipText b, #tlTipText small { display: block; overflow: hidden; text-overflow: ellipsis; }
#tlTipText b { font-size: 12px; }
#tlTipText small { font-size: 11px; color: var(--muted); }

/* In-modal clip player. The clip plays inside our own card with an obvious
   Close, never handed off to the browser's native full-window video viewer. */
.clip-preview { background: #000; border-radius: 3px; margin: 4px 0 10px; }
.clip-preview video { display: block; width: 100%; max-height: 80vh; border-radius: 3px; }
/* A wide card for things that are looked at rather than read: the clip player. */
.modal-card.wide { width: min(1280px, 96vw); }

/* ---------- side panel ------------------------------------------------- */

.tabs { display: flex; border-bottom: 1px solid var(--line); }
.tabs button {
  position: relative;
  flex: 1; background: none; border: 0; border-bottom: 2px solid transparent;
  /* Top padding reserves the strip where a counter badge sits, so labels line
     up across tabs whether or not one carries a badge. */
  padding: 16px 8px 8px; color: var(--muted); cursor: pointer; font-size: 13px;
  white-space: nowrap;
}
.tabs button:hover { color: var(--text); }
.tabs button.active { color: var(--text); border-bottom-color: var(--accent); }

/* Counter badge ABOVE the label, centred — it never pushes the text around. */
.tabbadge {
  position: absolute; top: 2px; left: 50%; transform: translateX(-50%);
  min-width: 16px; padding: 0 4px;
  background: var(--err); color: #2a0b0b; border-radius: 9px;
  font-size: 10px; font-weight: 700; line-height: 14px; text-align: center;
}

/* Section captions inside the filters block. */
.filter-label {
  font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
  margin-bottom: -3px;
}

/* Hover tooltip: fixed, above the pointer, never under it. */
.tip {
  position: fixed; z-index: 70; pointer-events: none;
  background: #1b222c; border: 1px solid var(--line); border-radius: 5px;
  padding: 6px 9px; font-size: 12px; line-height: 1.4; color: var(--text);
  max-width: 300px; box-shadow: 0 6px 20px rgba(0,0,0,.55);
}
.alerthead {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 10px; border-bottom: 1px solid var(--line); font-size: 12px;
}
.card.alert.unread { box-shadow: inset 3px 0 0 var(--err); background: #14181f; }
.card.alert.hi .card-head strong { color: #ffb3b3; }
label.inline { display: flex; flex-direction: row; align-items: center; gap: 7px; color: var(--text); }
label.inline input { width: auto; }
textarea.curl {
  width: 100%; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 8px; font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 11.5px; color: var(--text); resize: vertical; white-space: pre;
}

.tabpanel { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.list, .scroll { flex: 1; min-height: 0; overflow-y: auto; }

/* Settings sections */
#settings h3 { margin: 2px 0 6px; font-size: 14px; }
#settings .pad + .pad { border-top: 1px solid var(--line); }
#settings .card.off { opacity: .55; }
button.link {
  background: none; border: 0; padding: 0; color: var(--accent); cursor: pointer; font-size: 12px;
}
button.link:hover { text-decoration: underline; }

.filters { padding: 9px 10px; border-bottom: 1px solid var(--line); display: grid; gap: 8px; }
.chips { display: flex; flex-wrap: wrap; gap: 5px; }
.chip {
  background: var(--panel-2); border: 1px solid var(--line); color: var(--muted);
  border-radius: 20px; padding: 2px 9px; font-size: 11.5px; cursor: pointer;
}
.chip:hover { color: var(--text); }
.chip.on { color: #04121f; background: var(--c, var(--accent)); border-color: transparent; font-weight: 600; }
.chip.cam.on { background: var(--accent); }
.score { display: grid; grid-template-columns: auto auto 1fr; gap: 8px; align-items: center; font-size: 12px; color: var(--muted); }
.score input { width: 100%; }
.score output { font-family: ui-monospace, monospace; color: var(--text); }

.daysep {
  position: sticky; top: 0; background: var(--panel-2); border-bottom: 1px solid var(--line);
  padding: 4px 10px; font-size: 11px; color: var(--muted); letter-spacing: .04em;
}
.row {
  display: grid; grid-template-columns: 10px 62px 62px 1fr auto;
  gap: 7px; align-items: center; padding: 5px 10px; cursor: pointer;
  border-bottom: 1px solid #161a21; font-size: 12.5px;
}
.row:hover { background: var(--panel-2); }
.row.sel { background: #16283a; box-shadow: inset 2px 0 0 var(--accent); }
.row .dot { width: 8px; height: 8px; border-radius: 2px; }
.row .t { font-family: ui-monospace, monospace; color: var(--muted); }
.row .c { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .note { grid-column: 2 / -1; color: var(--warn); font-size: 11.5px; }

/* ---------- cards ------------------------------------------------------ */

.card {
  border-bottom: 1px solid var(--line); padding: 10px 12px; display: grid; gap: 7px;
}
.card-head { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.card-head strong { font-size: 13px; }
.card-label { font-size: 12.5px; }
.card-meta { display: flex; gap: 10px; flex-wrap: wrap; font-size: 12px; color: var(--muted); }
.card-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.hash { color: #6f7f92; }
.urlfield, .card input, .modal-card input, .modal-card select {
  width: 100%; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 6px 8px; font-size: 12px;
}
.card label, .modal-card label { display: grid; gap: 4px; font-size: 12px; color: var(--muted); }
.two { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }

/* enhancement: region select + adjust */
.selwrap {
  position: relative; aspect-ratio: 16/9; background: #000; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--radius);
  cursor: crosshair; touch-action: none;
}
.selwrap img { width: 100%; height: 100%; object-fit: cover; display: block; }
.selbox {
  position: absolute; border: 1px solid var(--accent);
  background: rgba(77,163,255,.16); box-shadow: 0 0 0 9999px rgba(0,0,0,.45);
  pointer-events: none;
}
.adjcanvaswrap {
  background: #000; border: 1px solid var(--line); border-radius: var(--radius);
  overflow: auto; max-height: 46vh; display: grid; place-items: center;
}
.adjcanvaswrap canvas {
  max-width: 100%; image-rendering: pixelated;   /* show the actual pixels, not a browser guess */
}
.adjgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px; }
.adjgrid label { font-size: 12px; }
.adjgrid input[type=range] { width: 100%; }
.adjgrid output { font-family: ui-monospace, monospace; color: var(--text); }
.stillimg {
  width: 100%; max-height: 260px; object-fit: contain; background: #000;
  border: 1px solid var(--line); border-radius: var(--radius); image-rendering: pixelated;
}

.refslot { display: grid; gap: 6px; align-content: start; }
.refimg {
  width: 100%; aspect-ratio: 16/9; object-fit: cover;
  border: 1px solid var(--line); border-radius: var(--radius); background: #000;
}

/* watch zone grid editor */
.zonewrap {
  position: relative; aspect-ratio: 16 / 9; background: #000;
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  touch-action: none;   /* drag-paint must not scroll the page */
}
.zonewrap img { width: 100%; height: 100%; object-fit: cover; display: block; }
.zonegrid {
  position: absolute; inset: 0;
  display: grid; grid-template-columns: repeat(10, 1fr); grid-template-rows: repeat(10, 1fr);
}
.zonegrid i {
  border: 1px solid rgba(255,255,255,.08); cursor: crosshair;
}
.zonegrid i.on { background: rgba(77,163,255,.34); border-color: rgba(77,163,255,.6); }
.zonegrid i:hover { border-color: var(--accent); }

/* connection test */
.probe { display: grid; gap: 7px; margin-top: 4px;
         border-top: 1px solid var(--line); padding-top: 9px; }
.probe table.log { margin: 0; }
.probe table.log th { width: 96px; text-transform: capitalize; }
.probe details summary { cursor: pointer; color: var(--muted); font-size: 12px; }
.probe .hash { word-break: break-all; }
.chip.openport { background: #10281a; border-color: #1d5233; color: #6fd48a;
                 font-family: ui-monospace, monospace; }
.chip.shutport { opacity: .4; font-family: ui-monospace, monospace; }
.clockbox { border-radius: var(--radius); padding: 9px 11px; display: grid; gap: 6px; }
.clockbox.good { background: #10281a; border: 1px solid #1d5233; }
.clockbox.bad  { background: #2a2010; border: 1px solid #5a4416; }
.clockmsg { margin: 0; font-size: 12.5px; color: #e8c46a; }
.clockbox.good .clockmsg { color: #8fd9a5; }

.probe-ok   { color: #6fd48a; font-size: 13px; }
.probe-warn { color: #ff9a9a; font-size: 13px; }

.sun { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 10px; font-size: 13px; }
.caps { display: grid; gap: 6px; margin-top: 8px; }
.cap { display: grid; grid-template-columns: 16px 1fr; gap: 8px; font-size: 12.5px; align-items: start; }
.cap.yes > span { color: var(--ok); }
.cap.no  > span { color: var(--err); }

/* ---------- full-res frame viewer -------------------------------------- */
.frameviewer {
  position: fixed; inset: 0; z-index: 60; background: rgba(0,0,0,.9);
  display: grid; grid-template-rows: auto 1fr auto;
}
.fv-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; background: var(--panel); border-bottom: 1px solid var(--line);
}
.fv-meta { font-size: 13px; color: var(--text); }
.fv-spacer { flex: 1; }
.fv-zoom { font-size: 12px; color: var(--muted); min-width: 46px; text-align: right; }
#frameDownload { text-decoration: none; }
.fv-stage {
  position: relative; overflow: hidden; display: grid; place-items: center;
  cursor: grab; touch-action: none; user-select: none;
}
.fv-stage.grabbing { cursor: grabbing; }
#frameImg {
  max-width: 100%; max-height: 100%; transform-origin: center center;
  will-change: transform; image-rendering: auto;
}
.fv-status { position: absolute; color: var(--muted); font-size: 13px; }
.fv-hint { padding: 6px 12px; font-size: 11px; color: var(--muted); text-align: center;
           background: var(--panel); border-top: 1px solid var(--line); }

/* ---------- modal / toast ---------------------------------------------- */

.modal {
  position: fixed; inset: 0; background: rgba(0,0,0,.6);
  display: grid; place-items: center; z-index: 50; padding: 20px;
}
.modal-card {
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  padding: 20px 22px; width: min(560px, 96vw); max-height: 86vh; overflow: auto;
  display: grid; gap: 12px; box-shadow: 0 20px 60px rgba(0,0,0,.6);
}
.modal-card h2 { margin: 0; font-size: 16px; }
.modal-card h2 ~ h2 { margin-top: 8px; }
.modal-card p { margin: 0; font-size: 13px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }

.log { width: 100%; border-collapse: collapse; font-size: 12px; }
.log th, .log td { text-align: left; padding: 4px 8px 4px 0; border-bottom: 1px solid #1a1f27; vertical-align: top; }
.log th { color: var(--muted); font-weight: 500; }

.toast {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
  padding: 9px 16px; z-index: 60; font-size: 13px; max-width: 80vw;
  box-shadow: 0 8px 30px rgba(0,0,0,.55);
}
.toast.ok  { border-color: #1d5233; background: #10281a; }
.toast.err { border-color: #6b2429; background: #2c1416; }

@media (max-width: 1000px) {
  .layout { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr) 300px; }
  .side { border-left: 0; border-top: 1px solid var(--line); }
  .topbar { flex-wrap: wrap; }
}
