/* FastFX Review — the client-facing half of Show Dog.
 *
 * The palette, the shell and the table rules below are ported from ShowDog's
 * own web UI (Z:\Dev\Tools\ShowDog\web\src\styles.css) so a client and an
 * artist are plainly looking at the same application. Every surface, line and
 * piece of text resolves through the same variable names, so the two palettes
 * are one set of values swapped by `data-theme` on <html> and nothing else.
 * Keep the names in step with ShowDog's file — a colour that only exists here
 * is a colour that will drift.
 *
 * :root is the DARK set and doubles as the fallback for a page that renders
 * before the theme stamp lands.
 */

:root {
  --bg: #1c1f24;
  --panel: #262a31;
  --panel-2: #2f343c;
  --panel-3: #353b44;
  --border: #3a4049;
  --text: #e6e9ef;
  --muted: #9aa3af;
  --accent: #6aa9ff;
  --accent-hover: #8cbcff;
  --danger: #e06464;
  --ok: #6fcf97;
  --warn: #f2c14e;
  --sunk: #14181e;
  --hover: rgba(255, 255, 255, 0.05);
  --shadow: rgba(0, 0, 0, 0.5);
  --on-accent: #14181e;
  --star: #ff5c5c;       /* a kept version */
  --ok-bg: #1d2a1d;      --ok-edge: #2f4a2f;     --ok-fg: #bfe3bf;
  --danger-bg: #3a2020;  --danger-edge: #7a2a2a; --danger-fg: #e79a9a;
  --warn-bg: #2a2a1d;    --warn-edge: #4a4a2f;   --warn-fg: #ddd08a;
  --info-bg: #25252e;    --info-edge: #3a3a4a;   --info-fg: #c9c9d4;
  --sel-row: rgba(106, 169, 255, 0.18);
  --sel-row-hover: rgba(106, 169, 255, 0.26);

  --app-font-size: 14px;
  --line-height: 1.4;
  --pad-scale: 1;
  --row-pad-y: 6px;
  --row-pad-x: 10px;
}

/* ---------- light ----------
 * The studio house light-grey theme (Storyteller / FarmDog Monitor): mid-grey
 * window, lighter panels, DARK text on them. Semantic colours are darkened
 * rather than reused — #6fcf97 on a #e8e8e8 panel is barely legible.
 */
:root[data-theme="light"] {
  --bg: #b3b3b3;
  --panel: #e8e8e8;
  --panel-2: #dcdcdc;
  --panel-3: #d0d0d0;
  --border: #a8a8a8;
  --text: #191919;
  --muted: #5c6068;
  --accent: #1072c8;
  --accent-hover: #0b5ba1;
  --danger: #b23b3b;
  --ok: #2e8b57;
  --warn: #9a6b12;
  --sunk: #c1c1c1;
  --hover: rgba(0, 0, 0, 0.06);
  --shadow: rgba(0, 0, 0, 0.22);
  --on-accent: #ffffff;
  --star: #c0392b;
  --ok-bg: #d4edda;      --ok-edge: #8fc79f;     --ok-fg: #1d4429;
  --danger-bg: #f8d7da;  --danger-edge: #d09098; --danger-fg: #7a2029;
  --warn-bg: #fdf0cd;    --warn-edge: #d8bd72;   --warn-fg: #6b4e05;
  --info-bg: #cce5f0;    --info-edge: #8fb6c8;   --info-fg: #1c3d4a;
  --sel-row: rgba(16, 114, 200, 0.16);
  --sel-row-hover: rgba(16, 114, 200, 0.24);
}

* { box-sizing: border-box; }

html, body { height: 100%; margin: 0; }
body {
  background: var(--bg);
  color: var(--text);
  font: var(--app-font-size)/var(--line-height) -apple-system, "Segoe UI", Roboto, sans-serif;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
a:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ---------- shell ---------- */

.app {
  display: grid;
  grid-template-rows: 52px 1fr;
  height: 100%;
}

.topbar {
  display: flex;
  align-items: center;
  padding: 0 16px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  gap: 12px;
}
.topbar h1 { font-size: 16px; margin: 0; color: var(--accent); white-space: nowrap; }
.topbar .project-switcher { display: flex; gap: 4px; align-items: center; }
.topbar .project-switcher select { min-width: 220px; }
.topbar .who { font-size: 12px; color: var(--muted); white-space: nowrap; }

/* Light / dark / follow-the-desktop, as the three tiny grouped buttons
   ShowDog uses for its density controls. */
.theme-controls { display: flex; gap: 2px; }
.theme-controls button {
  font-size: 12px;
  padding: 2px 7px;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 3px;
  cursor: pointer;
  line-height: 1.4;
}
.theme-controls button:hover { color: var(--text); border-color: var(--accent); }
.theme-controls button.selected {
  color: var(--text); border-color: var(--accent); background: var(--panel-2);
}

/* The nav column sizes itself to its own widest label — it is a fixed list of
   short words, and every pixel past the longest one is taken off the page you
   came to read. */
.main {
  display: grid;
  grid-template-columns: max-content 1fr;
  height: 100%;
  overflow: hidden;
}
.sidebar {
  background: var(--panel);
  border-right: 1px solid var(--border);
  overflow-y: auto;
}
.sidebar.nav { padding: 12px 8px; position: relative; max-width: 360px; }

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: calc(8px * var(--pad-scale)) calc(12px * var(--pad-scale));
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  color: var(--text);
  cursor: pointer;
  text-align: left;
  margin-bottom: 2px;
  text-decoration: none;
  white-space: nowrap;
}
.nav-item:hover { background: var(--panel-2); text-decoration: none; }
.nav-item.selected { background: var(--panel-2); border-color: var(--accent); }
.nav-icon { width: 18px; text-align: center; color: var(--muted); }
.nav-item.selected .nav-icon { color: var(--accent); }
.nav-count { color: var(--muted); font-size: 11px; margin-left: auto; padding-left: 10px; }
.nav-divider { height: 1px; background: var(--border); margin: 8px 6px; opacity: 0.5; }

.content {
  padding: calc(18px * var(--pad-scale)) calc(22px * var(--pad-scale));
  overflow: auto;
  background: var(--bg);
}

/* ---------- form primitives ---------- */

button, input, select, textarea {
  font: inherit;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 6px 10px;
}
button { cursor: pointer; }
button:hover:not(:disabled) { border-color: var(--accent); }
button:disabled { opacity: 0.5; cursor: not-allowed; }
button.primary, a.btn.primary {
  background: var(--accent); color: var(--on-accent); border-color: var(--accent);
}
button.primary:hover:not(:disabled), a.btn.primary:hover {
  background: var(--accent-hover); border-color: var(--accent-hover);
}
textarea { font-family: -apple-system, "Segoe UI", Roboto, sans-serif; resize: vertical; }

/* Anchors that have to read as buttons — a portal is mostly links. A SPAN
   gets the same look so a button with nothing to offer can still hold its
   place in the row: dropping it instead would move the live buttons around
   from one section to the next. */
a.btn, span.btn {
  display: inline-block;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 5px 10px;
  font-size: 13px;
  text-decoration: none;
  white-space: nowrap;
}
a.btn:hover { border-color: var(--accent); text-decoration: none; }
a.btn.small { padding: 2px 8px; font-size: 12px; }
/* A step-to-the-next arrow with nowhere to go. `pointer-events: none` is the
   load-bearing half — a dimmed link that still navigates is worse than one
   that looks live. */
a.btn.disabled { opacity: 0.4; pointer-events: none; }
/* Not a link at all — a placeholder saying "there is none of this here". It
   keeps its pointer events on purpose, so its title still explains why. */
span.btn.ghost { opacity: 0.4; cursor: default; }

/* ---------- layout helpers ---------- */

.row { display: flex; gap: 8px; align-items: center; }
.spacer { flex: 1; }
.muted { color: var(--muted); }
.mono { font-family: ui-monospace, Consolas, monospace; font-size: 11px; }
.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

.page-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: calc(14px * var(--pad-scale));
  flex-wrap: wrap;
}
.page-header h2 { margin: 0; font-size: 18px; }

.section-h {
  margin: 18px 0 10px;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
}

.empty { color: var(--muted); font-style: italic; padding: 24px; text-align: center; }
.empty-state { padding: 60px 20px; text-align: center; color: var(--muted); }
.empty-icon { font-size: 48px; opacity: 0.4; margin-bottom: 8px; }
.empty-title { font-size: 16px; color: var(--text); margin-bottom: 4px; }
.empty-hint { font-size: 13px; }

/* The star button's rules lived here and are gone (2026-08-25): the client
   cannot keep a version any more. `--star` and `.kept` below stay — `.kept`
   still colours the "not scheduled for removal" line, which is now the only
   thing on a client's page that the star colour is for. */

/* ---------- the client's sign-off ----------
   A real button, not a mark like the star: approving is a deliberate act and
   should look like one, and the withdrawn state has to be as obvious as the
   given one. Green filled = approved, plain outline = not, and the word
   changes as well as the colour — a colour-only signal is no signal at all to
   the part of the audience that cannot see the difference. */
button.approve {
  padding: 2px 8px;
  font-size: 12px;
  line-height: 1.5;
  white-space: nowrap;
  color: var(--muted);
}
button.approve:hover:not(:disabled) { color: var(--text); border-color: var(--ok); }
button.approve.on {
  background: var(--ok-bg);
  border-color: var(--ok-edge);
  color: var(--ok-fg);
  font-weight: 600;
}
button.approve.on:hover:not(:disabled) { border-color: var(--ok); }

/* A reel the client built themselves, as opposed to one we published. */
.chip.mine {
  background: transparent;
  border: 1px solid var(--accent);
  color: var(--accent);
}

.kept { color: var(--star, #ff5c5c); }
/* Due within days. Amber rather than red — it is a deadline, not a failure. */
.expiring { color: var(--warn); }
td.expiring { font-weight: 600; }

.chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 11px;
  background: var(--panel-3);
  margin-right: 4px;
}

.notice {
  border: 1px solid var(--info-edge);
  background: var(--info-bg);
  color: var(--info-fg);
  border-radius: 4px;
  padding: 8px 10px;
  font-size: 13px;
  margin-bottom: 12px;
}
.notice.err { border-color: var(--danger-edge); background: var(--danger-bg); color: var(--danger-fg); }
.notice.ok  { border-color: var(--ok-edge);     background: var(--ok-bg);     color: var(--ok-fg); }

/* ---------- entity tables ---------- */

.entity-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.entity-table th,
.entity-table td {
  padding: var(--row-pad-y) var(--row-pad-x);
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
  text-align: left;
}
.entity-table th {
  background: var(--panel);
  color: var(--muted);
  font-weight: 500;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  user-select: none;
  position: sticky;
  top: 0;
  z-index: 1;
}
.entity-table tbody tr.clickable { cursor: pointer; }
.entity-table tbody tr:hover td { background: var(--panel-2); }
.entity-table tbody tr.selected td {
  background: var(--sel-row);
  box-shadow: inset 2px 0 0 var(--accent);
}
.entity-table tbody tr.selected:hover td { background: var(--sel-row-hover); }
.entity-table td.actions { text-align: right; white-space: nowrap; }
.entity-table td.actions a.btn + a.btn { margin-left: 4px; }
/* A button in a column of its own (Versions puts Watch and DL beside the
   version they act on). Left-aligned with the text columns around it, and
   never wrapped — a button broken over two lines takes the row with it. */
.entity-table td.btn-cell { white-space: nowrap; }
.note-body-cell { white-space: pre-wrap; overflow-wrap: anywhere; }
/* ...but in a LIST row the note is a one-line preview, and it has to stay one
   line. A fixed-layout table hands the flex column whatever width is left
   over; when the other columns already fill the pane that is almost nothing,
   and a wrapping note then breaks to roughly one character per line and drags
   the whole ROW to hundreds of pixels tall. `overflow: hidden` clips the text
   while it does it, so the symptom is not "a squashed note" but a list of
   enormous, apparently empty rows — which is what the Versions page shipped
   with (~320px per version). The full note still wraps on the Notes page and
   on a version, neither of which is a fixed-layout table. */
.entity-table[data-cols] td.note-body-cell {
  white-space: nowrap;
  overflow-wrap: normal;
}
/* Short cells never wrap. A shot code or a date broken over two lines
   doubles the row height for no gain, and the note column is the one that
   should absorb whatever width is left. */
.entity-table td.nowrap, .entity-table th.nowrap { white-space: nowrap; }

/* Row thumbnails.
   The picture's BOX fills its column and keeps 16:9, so dragging the column wider
   makes the frame bigger — which is the whole point of being able to drag
   it. Nothing here pins a height: a fixed height in a resizable column would
   leave a growing strip of empty space instead of a bigger picture.
   The placeholder is the same rectangle, so a list mixing rows with and
   without a picture does not jump about. */
:root {
  /* The default picture width. Keep in step with data-w="80" in the
     templates: this one governs only the moment before col-resize.js runs,
     and a client with scripting off — but it has to exist, because a picture
     column left to size itself takes the JPEG's own 1024px and swamps the
     table. */
  --thumb-col: 80px;
}
.thumb-col { width: var(--thumb-col); }
.entity-table td.thumb-cell { padding: 2px var(--row-pad-x) 2px 2px; }
.row-thumb {
  display: block;
  width: 100%;
  max-width: 100%;
  aspect-ratio: 16 / 9;
  /* ⚠ The WHOLE picture, never cropped — same rule as ShowDog's own
     Thumbnail (web/src/components/primitives.jsx). `cover` turned a
     portrait 1024x1820 shot into a wide sliver of its middle, so the client
     saw a different picture from the one in ShowDog. A tall shot now sits
     as a thin upright frame on the --sunk background, exactly as there. */
  object-fit: contain;
  border-radius: 3px;
  background: var(--sunk);
  border: 1px solid var(--border);
}
.row-thumb.none {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: 12px;
}

/* ---------- resizable columns ----------
   The grip sits inside the header cell, not straddling its edge: the cells
   clip their overflow (they have to, or a narrow column spills its text over
   its neighbour) and anything hanging outside would be cut off. */
.entity-table th, .entity-table td { overflow: hidden; text-overflow: ellipsis; }
.col-grip {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 8px;
  cursor: col-resize;
  touch-action: none;   /* or a drag on a touchpad/pen scrolls the page instead */
  z-index: 2;
}
.col-grip::after {
  content: "";
  position: absolute;
  top: 4px; bottom: 4px; right: 0;
  border-right: 1px solid var(--border);
}
.col-grip:hover::after { border-right-color: var(--accent); border-right-width: 2px; }
/* While a drag is running the cursor must not flicker as it crosses cells,
   and nothing on the page may select. */
.entity-table.resizing { cursor: col-resize; user-select: none; }
.entity-table.resizing .col-grip::after {
  border-right-color: var(--accent);
  border-right-width: 2px;
}

/* ---------- one section per send ----------
   The Versions list is cut into a section per day the work went out, each a
   <tbody> whose first row is the heading. Collapsing is a class on the tbody
   and nothing else — see the note at the top of static/group-collapse.js for
   why it must not touch `display` on a row itself. */

.entity-table tr.group-head > td {
  background: var(--panel-3);
  border-bottom: 1px solid var(--border);
  border-top: 1px solid var(--border);
  padding: 4px var(--row-pad-x);
  cursor: pointer;
  user-select: none;
  overflow: visible;
}
.entity-table tbody tr.group-head:hover > td { background: var(--panel-2); }
.grp-bar { display: flex; align-items: center; gap: 10px; }
.grp-date {
  font-weight: 600;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.grp-count { font-size: 12px; }
/* Small enough not to compete with the date, wide enough to hit — though the
   whole bar is the click target anyway. */
.group-toggle {
  background: none;
  border: none;
  padding: 0 2px;
  /* Big enough to read as a triangle. U+25BE is a SMALL triangle by design,
     and at the 11px this started on it renders as an ambiguous dash. */
  font-size: 14px;
  line-height: 1;
  color: var(--muted);
  width: 18px;
}
.group-toggle:hover:not(:disabled) { color: var(--accent); border: none; }
/* The caret is drawn by CSS, not written into the markup, so it can follow
   the class the table is actually in — including the filtering case below,
   where a shut section is being shown and must not claim to be shut. */
/* Escaped, not typed. A stylesheet served without an explicit charset is
   decoded as the browser's fallback encoding, and a literal ▾ then comes
   through as a stray dash — which reads as a broken caret rather than as a
   mis-decoded file. \25BE / \25B8 mean the same thing in any encoding. */
.group-toggle::before { content: "\25BE"; }
tbody.grp.collapsed .group-toggle::before { content: "\25B8"; }
.entity-table.filtering tbody.grp.collapsed .group-toggle::before { content: "\25BE"; }

tbody.grp.collapsed > tr:not(.group-head) { display: none; }
/* A filter opens every section for as long as the box has text in it. More
   specific than the rule above, so it wins; still loses to the inline
   `display: none` the filter puts on rows that do not match, which is exactly
   the arrangement wanted. */
.entity-table.filtering tbody.grp.collapsed > tr:not(.group-head) {
  display: table-row;
}

/* The filter box above a list. ShowDog filters per column; one box over the
   whole table is the client-sized version of the same idea. */
.list-filter { min-width: 200px; padding: 4px 8px; font-size: 13px; }
.filter-strip { font-size: 12px; color: var(--muted); }

/* ---------- detail pages ---------- */

.entity-page { display: flex; flex-direction: column; gap: 16px; }
.entity-page-header {
  display: flex;
  align-items: flex-start;
  gap: calc(16px * var(--pad-scale));
  padding: calc(4px * var(--pad-scale)) 0 calc(12px * var(--pad-scale));
  border-bottom: 1px solid var(--border);
}
.entity-page-header .breadcrumb {
  display: flex; align-items: center; gap: 6px;
  margin-bottom: 4px; font-size: 12px; color: var(--muted);
}
.entity-page-header .breadcrumb a { color: var(--muted); }
.entity-page-header .breadcrumb a:hover { color: var(--accent); }
.entity-page-header h2 { margin: 0; font-size: 18px; }
.entity-page-header-actions { display: flex; gap: 6px; align-items: center; }

.page-section {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: calc(14px * var(--pad-scale)) calc(18px * var(--pad-scale));
}
.page-section > h3 {
  margin: 0 0 calc(12px * var(--pad-scale));
  padding-bottom: calc(8px * var(--pad-scale));
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  font-weight: 600;
}

/* ---------- player ---------- */

video.player {
  width: 100%;
  max-height: 70vh;
  background: #000;
  display: block;
  border-radius: 4px;
}
.player-meta { display: flex; gap: 10px; align-items: center; margin-top: 8px; flex-wrap: wrap; }

/* ---------- where you are in the clip (static/player-clock.js) ---------- */

/* SD Player keeps the picture clean: the script takes the browser's own
   controls off (they are painted over the bottom of the frame) and puts this
   row UNDER it instead - play, clock, progress bar, sound, fullscreen.
   `.sdp-wrap` holds the picture and the row together, and is what goes
   fullscreen, so the row comes along. */
.sdp-wrap { position: relative; }
.sdp-bar {
  display: flex; align-items: center; gap: 6px;
  margin-top: 4px;
  font-size: 12px;
}
.sdp-bar button { padding: 1px 7px; font-size: 12px; line-height: 1.4; }
.sdp-bar2 { margin-top: 3px; }
.sdp-bar .sdp-play { min-width: 30px; }
.sdp-bar .sdp-mute.selected { color: var(--danger); }

/* THE SAME CHIP SHOWDOG DRAWS, down to the class names (ShowDog/web/src/
   styles.css, .sdp-clock). Click it and it switches timecode / frames. */
.sdp-bar .sdp-clock {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 1px 7px;
  font-family: Consolas, "Courier New", monospace;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
}
.sdp-bar .sdp-clock:hover { background: var(--panel-3); }
.sdp-clock-mark { opacity: 0.65; }

/* The progress bar: what is downloaded, what is played, and the playhead. */
.sdp-track {
  position: relative;
  flex: 1 1 auto; min-width: 60px;
  height: 18px;
  border-radius: 3px;
  background: var(--sunk);
  border: 1px solid var(--border);
  cursor: pointer;
  overflow: hidden;
  touch-action: none;   /* a drag scrubs instead of scrolling the page */
  user-select: none;
}
.sdp-track-loaded, .sdp-track-done {
  position: absolute; left: 0; top: 0; bottom: 0; width: 0;
  pointer-events: none;
}
.sdp-track-loaded { background: var(--accent); opacity: 0.18; }
.sdp-track-done { background: var(--accent); opacity: 0.45; }
.sdp-track-head {
  position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px;
  background: var(--accent);
  pointer-events: none;
}
.sdp-volume { width: 70px; flex: 0 0 auto; padding: 0; accent-color: var(--accent); }

/* Fullscreen: the picture takes whatever the row leaves. */
.sdp-wrap:fullscreen {
  display: flex; flex-direction: column;
  background: #000; padding: 0 0 6px;
}
.sdp-wrap:fullscreen > video.player,
.sdp-wrap:fullscreen > .ann-stage { flex: 1 1 auto; min-height: 0; }
.sdp-wrap:fullscreen video.player { max-height: none; height: 100%; border-radius: 0; }
.sdp-wrap:fullscreen .sdp-bar { padding: 0 8px; }
.sdp-wrap:fullscreen .sdp-clock { background: #222; color: #eee; border-color: #555; }

/* ---------- drawing on frames (static/annotate.js) ---------- */

/* The canvas sits over the PICTURE; annotate.js positions it to the
   letterboxed video rect. While the movie plays it lets clicks through to the
   video (pause is one click); paused, it takes the pointer for drawing. */
.ann-stage { position: relative; }
.ann-stage .ann-canvas {
  position: absolute; left: 0; top: 0;
  cursor: crosshair;
  touch-action: none;
}
.ann-stage.playing .ann-canvas,
.ann-stage[data-tool="none"] .ann-canvas { pointer-events: none; }
/* Other reviewers' drawings: images placed exactly where the canvas is,
   under it, never taking the pointer. */
.ann-stage .ann-others { position: absolute; left: 0; top: 0; pointer-events: none; }
.ann-stage .ann-others img { position: absolute; left: 0; top: 0; width: 100%; height: 100%; }
.ann-bar .ann-chip.theirs { border-style: dashed; }
.ann-stage[data-tool="text"] .ann-canvas { cursor: text; }
.ann-stage[data-tool="eraser"] .ann-canvas { cursor: cell; }
.ann-text-input {
  position: absolute; z-index: 3;
  background: rgba(0,0,0,0.55); border: 1px dashed #fff; padding: 2px 4px;
  min-width: 120px; outline: none;
}
.ann-bar {
  display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center;
  margin-top: 6px; font-size: 12px;
}
.ann-bar .ann-group { display: inline-flex; gap: 4px; align-items: center; }
.ann-bar input[type="range"] { width: 70px; }
.ann-bar input[type="color"] { width: 28px; height: 22px; padding: 0; border: 1px solid var(--border); }
.ann-bar .ann-tool.selected { background: var(--accent); color: #fff; border-color: var(--accent); }
.ann-bar .ann-frame { min-width: 64px; text-align: center; font-variant-numeric: tabular-nums; }
.ann-bar .ann-status.bad { color: var(--danger, #cc5555); }
.ann-bar .ann-chips { display: flex; flex-wrap: wrap; gap: 3px; width: 100%; }
.ann-bar .ann-chip { padding: 0 6px; font-size: 11px; font-variant-numeric: tabular-nums; }
.ann-bar .ann-chip.selected { background: var(--accent); color: #fff; border-color: var(--accent); }
.ann-bar button.danger { color: var(--danger, #cc5555); }

/* Typed notes: words filed AGAINST the frame, not drawn on it. The field
   sits under the player, the frame's notes above it - yours first, then
   other reviewers', theirs read-only. */
.ann-notes { margin-top: 4px; }
.ann-note-list { display: flex; flex-direction: column; gap: 2px; margin-bottom: 4px; }
.ann-note-list.empty { display: none; }
.ann-note {
  display: flex; gap: 6px; align-items: baseline;
  padding: 1px 4px; font-size: 12px;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 3px;
}
.ann-note.theirs { background: var(--panel); border-style: dashed; }
.ann-note-who { flex: 0 0 auto; font-size: 11px; }
.ann-note-text { flex: 1 1 auto; white-space: pre-wrap; word-break: break-word; }
.ann-note-del {
  flex: 0 0 auto; padding: 0 5px; line-height: 1.2;
  background: none; border: none; color: var(--muted);
}
.ann-note-del:hover { color: var(--danger); border: none; }
.ann-note-row { display: flex; gap: 6px; align-items: center; }
.ann-note-input { flex: 1 1 auto; min-width: 0; max-width: 640px; }


/* ---------- notes thread ---------- */

.note { border-top: 1px solid var(--border); padding: 8px 0; }
.note:first-of-type { border-top: none; }
.note-head { font-size: 12px; color: var(--accent); font-weight: 600; margin-bottom: 2px; }
.note-head .at { color: var(--muted); font-weight: 400; }
.note-body { white-space: pre-wrap; overflow-wrap: anywhere; }

/* ---------- signed-out pages ---------- */

.auth-wrap { min-height: 100%; display: grid; place-items: center; padding: 24px; }
.auth-card {
  width: 100%;
  max-width: 360px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 22px;
}
.auth-card h1 { font-size: 18px; margin: 0 0 2px; color: var(--accent); }
.auth-card .field { margin-bottom: 10px; }
.auth-card label {
  display: block; font-size: 12px; color: var(--muted);
  margin-bottom: 3px; font-weight: 500;
}
.auth-card input { width: 100%; }
.auth-card button[type=submit] { width: 100%; margin-top: 4px; }
.auth-foot { text-align: center; font-size: 12px; color: var(--muted); margin-top: 12px; }

@media (max-width: 720px) {
  .app { grid-template-rows: max-content 1fr; }
  .main { grid-template-columns: 1fr; grid-template-rows: max-content 1fr; }
  .sidebar.nav {
    display: flex; gap: 4px; padding: 6px; max-width: none;
    border-right: none; border-bottom: 1px solid var(--border); overflow-x: auto;
  }
  .nav-item { width: auto; }
  .nav-count { display: none; }
  .topbar { flex-wrap: wrap; height: auto; padding: 6px 10px; }
  .topbar .project-switcher select { min-width: 140px; }
}

/* A name given to one send, beside its date. Only hand-made sections carry
   one, so it must read as an addition to the date rather than a replacement
   for it. */
.grp-label {
  font-weight: 600;
  opacity: .85;
}
.grp-label::before { content: "— "; opacity: .6; }

/* ---- Versions list: Updates, the prior note, and writing one ----
   (2026-08-21 layout)

   Two prose columns in a FIXED-LAYOUT table, which is the shape that already
   produced 320px-tall rows once — see `.note-body-cell` above for the full
   story. Same defence: one line, clipped with an ellipsis so it is obvious
   there is more, and the whole text in the tooltip. Dragging the column wider
   reveals more, which is what the column drag is for. */
.entity-table td.text-cell {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 0;               /* let the declared column width win */
}
/* Which version the prior note was left on. Dimmer than the note itself:
   it is the caption, not the message. */
.prior-on {
  opacity: .65;
  font-size: 11px;
  margin-right: 3px;
}

/* Writing a note from the list. One line, because a textarea in a table row
   makes every row as tall as the tallest thing in it. The full-size box is
   still on the version's own page for anything longer. */
td.addnote-cell { overflow: hidden; }
form.addnote {
  display: flex;
  gap: 4px;
  align-items: center;
  margin: 0;
}
form.addnote input[type="text"] {
  flex: 1 1 auto;
  min-width: 0;               /* a flex item's default min-width is its
                                 content, which would push the button out */
  padding: 2px 5px;
  font-size: 12px;
}
form.addnote .chip { flex: 0 0 auto; text-decoration: none; }

/* A download button that is offering the movie rather than the frames it
   claims — still usable, visibly not the real thing. */
.btn.small.ghost-link { opacity: .55; }

/* The trailing spacer column (Versions). It exists only to absorb the width
   that is left over, so every real column can be dragged without stealing
   from a neighbour — see the note in the template and `flexMin` in
   col-resize.js. It must look like nothing at all: no header text, no grip,
   and the row's bottom border carries straight through it. */
.entity-table th.spacer-col,
.entity-table td.spacer-col {
  padding: 0;
}

/* ---------- Play All (static/play-all.js) ---------- */

/* One player over the page, for a whole send. Padding lives on the inner
   .pa-body so a click on the dialog element itself can only be the backdrop
   side of it - that click closes the player. */
.play-all-dlg {
  padding: 0;
  width: min(1200px, 94vw);
  max-height: 94vh;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
}
.play-all-dlg::backdrop { background: rgba(0, 0, 0, 0.7); }
.pa-body { padding: 8px 10px; }
.pa-head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.pa-head button, .pa-step { padding: 2px 8px; font-size: 12px; }
.play-all-dlg video.player { max-height: 68vh; }
/* The running order, so "which one is this and how many are left" is on
   screen. Click a line to jump to it. */
.pa-list {
  margin: 6px 0 0; padding: 0 0 0 26px;
  max-height: 18vh; overflow-y: auto;
  font-size: 12px; line-height: 1.35;
}
.pa-list li { cursor: pointer; padding: 0 4px; border-radius: 3px; }
.pa-list li:hover { background: var(--panel-2); }
.pa-list li.selected { background: var(--panel-3); color: var(--accent); font-weight: 600; }
