/* yololab — Apple-style UI (SF stack), light + dark, RTL + LTR. */

/* Vazirmatn (SIL OFL) ships with the app so Persian renders correctly out of
   the box. Yekan Bakh is commercial and cannot be redistributed here; when its
   files are present the app also loads yekan.css, and --font puts it first.
   See static/fonts/README.md. */
/* Space Grotesk (SIL OFL), subset to alphanumerics with its weight axis kept,
   so one 20 KB file gives the wordmark real Medium and Bold rather than a
   synthesised faux weight. It sets the wordmark and nothing else. */
@font-face {
  font-family: 'Brand';
  src: url('/static/fonts/Brand.woff2') format('woff2-variations'),
       url('/static/fonts/Brand.woff2') format('woff2');
  font-weight: 500 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Vazirmatn';
  src: url('/static/fonts/Vazirmatn-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Vazirmatn';
  src: url('/static/fonts/Vazirmatn-Medium.woff2') format('woff2');
  font-weight: 500 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Vazirmatn';
  src: url('/static/fonts/Vazirmatn-Bold.woff2') format('woff2');
  font-weight: 700 900;
  font-style: normal;
  font-display: swap;
}


/* ---------- system colours ---------- */

/* Roles follow Apple's semantic colours: a window background, surface tones
   for chrome, fills for controls, and hairline separators. Every value is defined light-first, then restated for
   the dark appearance, so the whole UI follows the viewer's system setting. */
:root {
  color-scheme: light dark;

  --bg: #f2f2f7;                 /* systemGroupedBackground */
  --panel: #ffffff;              /* raised content */
  --panel-2: #f7f7f9;            /* recessed / sidebar base */
  --chrome: #fbfbfd;             /* toolbars, status bar, sheets */
  --chrome-2: #f7f7f9;           /* sidebar */
  --line: rgba(0, 0, 0, .1);     /* separator */
  --line-strong: rgba(0, 0, 0, .16);
  --text: #1d1d1f;               /* label */
  --muted: rgba(60, 60, 67, .6); /* secondaryLabel */
  --fill: rgba(118, 118, 128, .12);        /* tertiarySystemFill */
  --fill-hover: rgba(118, 118, 128, .2);
  --fill-active: rgba(118, 118, 128, .28);

  --accent: #007aff;             /* systemBlue */
  --accent-2: #32ade6;           /* systemCyan */
  --accent-press: #0060df;
  --danger: #ff3b30;             /* systemRed */
  --danger-soft: rgba(255, 59, 48, .12);
  --ok: #34c759;                 /* systemGreen */
  --on-accent: #fff;

  --ring: 0 0 0 3.5px color-mix(in srgb, var(--accent) 35%, transparent);
  --shadow-1: 0 1px 2px rgba(0, 0, 0, .08), 0 0 0 .5px rgba(0, 0, 0, .04);
  --shadow-2: 0 8px 24px rgba(0, 0, 0, .14), 0 0 0 .5px rgba(0, 0, 0, .06);
  --shadow-3: 0 24px 68px rgba(0, 0, 0, .28), 0 0 0 .5px rgba(0, 0, 0, .08);

  /* Continuous-ish corners: small controls stay tight, sheets go generous. */
  --radius: 8px;
  --radius-sm: 6px;
  --radius-lg: 12px;
  --radius-xl: 18px;

  /* SF Pro first where it exists; Persian glyphs fall through to Yekan Bakh
     when licensed and installed, Vazirmatn otherwise. Per-glyph fallback means
     one stack serves the Persian and English UI alike. */
  --font: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Yekan Bakh',
          'Vazirmatn', 'Segoe UI', Tahoma, system-ui, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
  --font-brand: 'Brand', 'Space Grotesk', var(--font);
}

/* The dark appearance. It applies when the system asks for it and the viewer
   has not pinned a mode, and whenever the viewer pins dark — the appearance
   control in the sidebar writes data-theme on <html>. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #1c1c1e;
    --panel: #2c2c2e;
    --panel-2: #1a1a1c;
    --chrome: #2c2c2e;
    --chrome-2: #1f1f21;
    --line: rgba(255, 255, 255, .11);
    --line-strong: rgba(255, 255, 255, .2);
    --text: rgba(255, 255, 255, .92);
    --muted: rgba(235, 235, 245, .6);
    --fill: rgba(118, 118, 128, .24);
    --fill-hover: rgba(118, 118, 128, .36);
    --fill-active: rgba(118, 118, 128, .48);

    --accent: #0a84ff;
    --accent-2: #64d2ff;
    --accent-press: #3a9bff;
    --danger: #ff453a;
    --danger-soft: rgba(255, 69, 58, .16);
    --ok: #30d158;

    --shadow-1: 0 1px 2px rgba(0, 0, 0, .4), 0 0 0 .5px rgba(255, 255, 255, .06);
    --shadow-2: 0 10px 30px rgba(0, 0, 0, .5), 0 0 0 .5px rgba(255, 255, 255, .08);
    --shadow-3: 0 28px 80px rgba(0, 0, 0, .7), 0 0 0 .5px rgba(255, 255, 255, .1);
  }
}

:root[data-theme="dark"] {
  --bg: #1c1c1e;
  --panel: #2c2c2e;
  --panel-2: #1a1a1c;
  --chrome: #2c2c2e;
  --chrome-2: #1f1f21;
  --line: rgba(255, 255, 255, .11);
  --line-strong: rgba(255, 255, 255, .2);
  --text: rgba(255, 255, 255, .92);
  --muted: rgba(235, 235, 245, .6);
  --fill: rgba(118, 118, 128, .24);
  --fill-hover: rgba(118, 118, 128, .36);
  --fill-active: rgba(118, 118, 128, .48);

  --accent: #0a84ff;
  --accent-2: #64d2ff;
  --accent-press: #3a9bff;
  --danger: #ff453a;
  --danger-soft: rgba(255, 69, 58, .16);
  --ok: #30d158;

  --shadow-1: 0 1px 2px rgba(0, 0, 0, .4), 0 0 0 .5px rgba(255, 255, 255, .06);
  --shadow-2: 0 10px 30px rgba(0, 0, 0, .5), 0 0 0 .5px rgba(255, 255, 255, .08);
  --shadow-3: 0 28px 80px rgba(0, 0, 0, .7), 0 0 0 .5px rgba(255, 255, 255, .1);
}

/* Tell the browser which appearance its own widgets should use. */
:root[data-theme="dark"] { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 13.5px/1.65 var(--font);
  letter-spacing: -.005em;                 /* SF sets slightly tight at UI sizes */
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

[hidden] { display: none !important; }

::selection { background: color-mix(in srgb, var(--accent) 30%, transparent); }

/* One focus treatment everywhere: the system's blue focus ring, never an
   outline that shifts layout. */
:where(button, .btn, select, input, a, .classRow, .thumb, .project):focus-visible {
  outline: none;
  box-shadow: var(--ring);
}

/* ---------- surfaces ---------- */

/* Window chrome is opaque: a surface tone, a hairline, and — where a surface
   floats over content — a shadow. */
aside { background: var(--chrome-2); }
header, .toolbar, .statusBar { background: var(--chrome); }

.modalBox, .authCard {
  background: var(--chrome);
  border: .5px solid var(--line);
  box-shadow: var(--shadow-3);
}
.toast, .jobPanel {
  background: var(--chrome);
  border: .5px solid var(--line);
  box-shadow: var(--shadow-2);
}

/* ---------- buttons & inputs ---------- */

/* Capsule controls, with a flat fill and a lit top edge. */
button, .btn, select {
  border: .5px solid var(--line-strong);
  background: var(--panel);
  color: var(--text);
  padding: 6px 14px;
  border-radius: 999px;
  cursor: pointer;
  font-size: 13px;
  font-weight: 500;
  font-family: inherit;
  box-shadow: var(--shadow-1);
  transition: background .12s ease, box-shadow .12s ease, transform .08s ease, opacity .12s;
  white-space: nowrap;
}

button:hover, .btn:hover, select:hover { background: color-mix(in srgb, var(--text) 6%, var(--panel)); }
button:active, .btn:active { background: color-mix(in srgb, var(--text) 12%, var(--panel)); transform: scale(.96); }
button:disabled { opacity: .4; cursor: default; box-shadow: none; transform: none; }
.btn { display: inline-block; }

button.primary {
  background: var(--accent);
  border-color: transparent;
  color: var(--on-accent);
  box-shadow: 0 1px 2px color-mix(in srgb, var(--accent) 35%, transparent);
}
button.primary:hover { background: color-mix(in srgb, #fff 10%, var(--accent)); }
button.primary:active { background: var(--accent-press); }

button.danger { background: transparent; border-color: transparent; color: var(--danger); box-shadow: none; }
button.danger:hover { background: var(--danger-soft); }
button.ghost { background: transparent; border-color: transparent; box-shadow: none; }
button.ghost:hover { background: var(--fill); }
button.small { padding: 4px 11px; font-size: 12px; }
button.block { display: block; width: 100%; margin-bottom: 6px; }

kbd {
  background: var(--fill);
  border: .5px solid var(--line-strong);
  border-radius: 5px;
  padding: 1px 6px;
  font: 11.5px/1.6 var(--font-mono);
  box-shadow: 0 1px 0 var(--line-strong);
}

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

/* Line glyphs sized to the text they sit with, the way SF Symbols are.
   Stroke and fill are set here rather than on the sprite: presentation
   attributes do not cross a <use> boundary, but inherited CSS does. */
.icon {
  width: 17px;
  height: 17px;
  flex: 0 0 auto;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  overflow: visible;
}

/* The toolbar button: no chrome until you point at it, a fill when you do.
   This is the shape almost every control in the window chrome takes. */
.iconBtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 7px;
  background: transparent;
  box-shadow: none;
  color: var(--text);
  cursor: pointer;
}
.iconBtn.wide { width: auto; min-width: 28px; padding: 0 8px; font-size: 12px; font-variant-numeric: tabular-nums; }
.iconBtn:hover { background: var(--fill); color: var(--text); }
.iconBtn:active { background: var(--fill-hover); transform: none; }
.iconBtn:disabled { opacity: .3; background: transparent; }
.iconBtn.danger { color: var(--text); }
.iconBtn.danger:hover { background: var(--danger-soft); color: var(--danger); }

/* Related toolbar actions sit together in one capsule, the way Mail groups
   reply/forward and Photos groups info/share/favourite. */
.tbGroup {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border-radius: 999px;
  background: var(--fill);
}
.tbGroup .iconBtn { border-radius: 999px; }
.tbSep { width: 4px; flex: 0 0 auto; }
.grow { flex: 1 1 auto; min-width: 8px; }

/* Pinned to the visible viewport rather than sized in vh: 100vh can be taller
   than what the browser actually shows, which pushed the sidebar footer off
   the bottom, and a fixed box can't be scrolled by scrollIntoView either. */
.app { display: flex; position: fixed; inset: 0; }

/* The sidebar is a source list, and it has no hard trailing edge: the
   content's own rounded card is what separates the two. */
aside {
  width: var(--sidebar-w, 232px);
  flex: 0 0 var(--sidebar-w, 232px);
  padding: 10px 10px 8px;
  display: flex;
  flex-direction: column;
  min-width: 0;
  z-index: 1;
}

.brand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin: 2px 2px 12px;
}
.brand h2 {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-brand);
  font-weight: 500;
  font-size: 19px;
  letter-spacing: -.035em;          /* display type wants to be set tight */
  line-height: 1.2;
  /* The wordmark is always latin and always reads left-to-right, even when
     the surrounding interface is in Persian. */
  direction: ltr;
  user-select: none;
}
.brand h2 img { display: block; flex: 0 0 auto; width: 22px; height: 22px; }

/* Same blue-to-green run as the liquid in the flask mark. */
.brand .word, .brandMark .word {
  background: linear-gradient(95deg, #0a84ff 0%, #32ade6 42%, #30d158 100%);
  background-size: 220% 100%;
  background-position: 0% 50%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  transition: background-position .55s ease;
}
/* Fall back to a solid accent where background-clip:text is unsupported. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .brand .word, .brandMark .word {
    color: var(--accent-2); -webkit-text-fill-color: currentColor;
  }
}
.brand h2:hover .word { background-position: 100% 50%; }
/* Real weight contrast from the variable axis — never a synthesised italic. */
.brand .word .lab, .brandMark .word .lab { font-weight: 700; }

@media (prefers-reduced-motion: reduce) {
  .brand .word { transition: none; }
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* Source-list section header, with its add button on the trailing edge. */
.listHead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding-inline: 8px 2px;
  margin-top: 6px;
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  min-height: 22px;
}
.listHead .iconBtn { width: 22px; height: 22px; }
.listHead .icon { width: 14px; height: 14px; }

.projects {
  margin-top: 2px;
  overflow: auto;
  /* Keeps the height the user dragged it to (or its content height) while
     there is room, and gives way only once the class list below it is down
     to its minimum, so the sidebar footer is never pushed off screen. */
  flex: 0 1 auto;
  padding-bottom: 6px;
  height: var(--projects-h, auto);
  min-height: 52px;
}

/* ---------- class palette (sidebar) ---------- */

/* Projects keep the room they need; the class list takes what is left and
   scrolls, so hundreds of classes never push anything off screen. A zero
   basis means its content length never competes for space. */
.classSection {
  display: flex;
  flex-direction: column;
  min-height: 150px;
  flex: 1 1 0;
  padding-top: 2px;
}
.headLabel { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.listCount {
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  background: var(--fill);
  border-radius: 999px;
  padding: 0 6px;
  font-size: 10.5px;
  line-height: 16px;
}
.classSearch {
  margin: 4px 2px 2px;
  height: 26px;
  padding: 0 9px;
  font-size: 12.5px;
  border-radius: 7px;
  flex: 0 0 auto;
}
.classes {
  margin-top: 2px;
  overflow: auto;
  flex: 1 1 auto;
  min-height: 0;
  padding-bottom: 6px;
  scroll-padding-block: 4px;
}
.classRow {
  --c: var(--accent);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border-radius: 7px;
  margin-bottom: 1px;
  font-size: 12.5px;
  cursor: pointer;
  user-select: none;
  min-width: 0;
  transition: background .12s ease;
}
.classRow:hover { background: var(--fill); }
.classRow.active {
  background: color-mix(in srgb, var(--c) 16%, transparent);
  color: color-mix(in srgb, var(--c) 72%, var(--text));
  font-weight: 500;
}
.classRow .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--c); flex: 0 0 auto; }
.classRow .name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The index doubles as the keyboard shortcut for the first ten classes. */
.classRow b {
  flex: 0 0 auto;
  min-width: 16px;
  text-align: center;
  color: var(--muted);
  font-weight: 600;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.classRow.active b { color: inherit; }
.classRow .chipBtn {
  border: 0; background: transparent; padding: 0 1px; box-shadow: none;
  color: var(--muted); font-size: 12px; line-height: 1.4; opacity: 0;
  width: auto; height: auto; flex: 0 0 auto;
}
.classRow:hover .chipBtn, .classRow:focus-within .chipBtn { opacity: 1; }
.classRow .chipBtn:hover { color: var(--text); background: transparent; }
.classNote { padding: 8px; color: var(--muted); font-size: 12px; text-align: center; }
/* Whole-image classes: the panel under the palette. Suggestions keep their
   accept/reject buttons visible; a refused save outlines the panel. */
.tagSection .classes { flex: 0 1 auto; max-height: 30vh; }
.tagRow input { margin: 0; flex: 0 0 auto; accent-color: var(--accent); }
.tagRow .tagSuggestion { flex: 0 0 auto; color: var(--muted); font-size: 11px; }
.tagRow .tagSuggestion ~ .chipBtn[data-action$="-tag"] { opacity: 1; }
.tags.refused { outline: 1.5px solid var(--danger); outline-offset: -1px; border-radius: 7px; }
.tagSection .classNote { text-align: start; }
.tagModeSelect { font-size: 12px; max-width: 45%; }
.project {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border-radius: 7px;
  margin-bottom: 1px;
  background: transparent;
  cursor: pointer;
  border: none;
  font-size: 13px;
  min-width: 0;
  transition: background .12s ease;
}
.project .name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Source-list glyphs carry the accent tint, like Mail's mailboxes. */
.project .icon { width: 16px; height: 16px; color: var(--accent); opacity: .85; }
.project:hover { background: var(--fill); }
/* A selected row tints rather than floods: the label stays readable and the
   window still looks quiet when several lists are on screen. */
.project.active {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: color-mix(in srgb, var(--accent) 72%, var(--text));
  font-weight: 500;
}
.project.active .icon { opacity: 1; }
/* The count sits on the trailing edge, as unread counts do in Mail. */
.project .count {
  flex: 0 0 auto;
  font-size: 11.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.project.active .count { color: inherit; opacity: .8; }

.sidebarFoot { flex: none; border-top: .5px solid var(--line); padding-top: 6px; margin-top: 6px; }

/* A full-width sidebar row, same metrics as a project row. */
.rowBtn {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  padding: 5px 8px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  box-shadow: none;
  font-size: 13px;
  font-weight: 400;
  color: var(--text);
  text-align: start;
}
a.rowBtn { text-decoration: none; }
.rowBtn .icon { color: var(--accent); opacity: .85; width: 16px; height: 16px; }
.rowBtn:hover { background: var(--fill); }

/* Content sits in a rounded card inside the window, lifted off the sidebar by
   its own hairline and shadow. */
main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--bg);
  border-inline-start: .5px solid var(--line);
  border-start-start-radius: 12px;
  border-end-start-radius: 12px;
  overflow: hidden;
  box-shadow: -1px 0 12px rgba(0, 0, 0, .06);
}

/* Unified toolbar: title on the leading edge, actions trailing, one row. */
header {
  min-height: 52px;
  border-bottom: .5px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 12px;
}
.titleBlock { min-width: 0; flex: 0 1 auto; padding-inline-start: 4px; }
.titleBlock h1 {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -.015em;
  line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.titleBlock small {
  display: block;
  color: var(--muted);
  font-size: 11.5px;
  line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Who else is in this project. Avatars overlap slightly, the way a shared
   document shows its readers, and the row fades out when the live channel is
   down so a stale list never looks current. */
.peers { display: flex; align-items: center; flex: 0 0 auto; margin-inline-start: auto; }
.peers[data-live="off"] { opacity: .35; }
.peer {
  width: 22px; height: 22px;
  margin-inline-end: -6px;
  border-radius: 50%;
  border: 1.5px solid var(--chrome);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10.5px; font-weight: 600;
  color: #fff;
  /* An initial is a letter, not a sentence — keep it out of the RTL flow. */
  direction: ltr; unicode-bidi: isolate;
}
.peer:last-child { margin-inline-end: 6px; }

/* Other people's pointers over the image. The layer never takes a click — it
   sits on top of the canvas and must not shadow it. */
.cursors {
  position: absolute; inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 3;
}
.liveCursor {
  position: absolute; top: 0; left: 0;
  display: flex; align-items: flex-start; gap: 2px;
  /* The server sends ten positions a second; this fills in between them.
     Linear, not eased — an eased tween reads as a pointer that keeps
     overshooting and settling. */
  transition: transform 110ms linear;
  will-change: transform;
}
/* While *we* pan or zoom, every arrow moves at once and must not lag behind
   the image it is pinned to. */
.cursors.noTween .liveCursor { transition: none; }
.liveCursor svg {
  width: 13px; height: 19px;
  flex: 0 0 auto;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .4));
}
.liveCursor b {
  margin-top: 11px;
  padding: 1px 5px 2px;
  border-radius: 7px;
  font-size: 10.5px; font-weight: 600; line-height: 1.35;
  color: #fff;
  white-space: nowrap;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
  /* A name is a name in any interface language. */
  direction: ltr; unicode-bidi: isolate;
}

/* The same avatar, pinned to the thumbnail of the image that person is on. */
.thumbPeer {
  position: absolute; top: 5px; inset-inline-start: 5px;
  width: 17px; height: 17px;
  border-radius: 50%;
  border: 1.5px solid rgba(0, 0, 0, .35);
  display: flex; align-items: center; justify-content: center;
  font-size: 9.5px; font-weight: 600;
  color: #fff;
  direction: ltr; unicode-bidi: isolate;
  z-index: 2;
}

.actions { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }
.actions .primary {
  height: 28px;
  padding: 0 12px;
  gap: 5px;
  display: inline-flex;
  align-items: center;
  font-size: 12.5px;
  margin-inline-start: 6px;
}
.actions .primary .icon { width: 15px; height: 15px; }

/* A select that reads as part of the toolbar rather than a form field. */
select.quiet {
  height: 28px;
  padding: 0 8px;
  border: 0;
  background: var(--fill);
  box-shadow: none;
  font-size: 12.5px;
  border-radius: 7px;
}
select.quiet:hover { background: var(--fill-hover); }

/* ---------- splitters ---------- */

/* A hairline that only shows itself once pointed at, with a hit area wider
   than the line — the same bargain VS Code strikes between a quiet chrome and
   a target you can actually grab. */
.splitter {
  flex: 0 0 auto;
  position: relative;
  z-index: 3;
  background: transparent;
  touch-action: none;
}
.splitter.vert { width: 1px; cursor: col-resize; align-self: stretch; }
.splitter.horiz { height: 1px; cursor: row-resize; margin: 3px 2px; }
/* The grab area, spilling either side of the line without moving anything. */
.splitter::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline: -3px;
}
.splitter.horiz::before { inset-inline: 0; inset-block: -3px; }
.splitter::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--accent);
  opacity: 0;
  transition: opacity .12s ease .15s;   /* a beat's delay, so passing by is quiet */
}
.splitter:hover::after,
.splitter:focus-visible::after,
.splitter.dragging::after { opacity: .75; transition-delay: 0s; }
.splitter:focus-visible { outline: none; }
.splitter.horiz { background: var(--line); }

/* While a grip is held, the whole window behaves like the grip. */
body.resizing { user-select: none; }
body.resizing.col { cursor: col-resize; }
body.resizing.row { cursor: row-resize; }
body.resizing .canvasWrap, body.resizing .itemsPane, body.resizing aside { pointer-events: none; }

/* ---------- inspector row ---------- */

.toolbar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  min-height: 42px;
  border-bottom: .5px solid var(--line);
}
/* The class currently being drawn with. The palette itself lives in the
   sidebar; this is the one-line reminder where the eye already is. */
.activeClass {
  --c: var(--accent);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  max-width: 260px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 12.5px;
  cursor: pointer;
  user-select: none;
  border: .5px solid color-mix(in srgb, var(--c) 55%, transparent);
  background: color-mix(in srgb, var(--c) 18%, transparent);
  color: color-mix(in srgb, var(--c) 70%, var(--text));
}
.activeClass .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--c); flex: 0 0 auto; }
.activeClass .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.activeClass b { font-weight: 600; font-variant-numeric: tabular-nums; opacity: .75; }

/* Segmented control: one recessed track, the selection a raised pill. */
.segmented {
  display: flex;
  gap: 2px;
  padding: 2px;
  background: var(--fill);
  border-radius: 999px;
  flex: 0 0 auto;
}
.segmented button {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border: 0;
  background: transparent;
  box-shadow: none;
  border-radius: 999px;
  padding: 3px 12px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text);
}
.segmented .icon { width: 15px; height: 15px; }
.segmented button:hover { background: var(--fill); }
.segmented button.active {
  background: var(--panel);
  color: var(--text);
  box-shadow: var(--shadow-1);
}

/* Brush settings: paint / erase, the size, and "start a new object". */
.brushOpts { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.brushOpts .segmented button { padding: 3px 9px; }
.brushOpts input[type="range"] {
  width: 110px; padding: 0; border: 0; background: transparent; box-shadow: none;
  accent-color: var(--accent);
}
.brushSizeVal {
  min-width: 38px;
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  direction: ltr; unicode-bidi: isolate;
}

/* ---------- workspace ---------- */

.workspace { display: flex; flex: 1; min-height: 0; }

/* The media browser: filter tabs and a view switch above a scrolling list.
   The pane owns the width so the header stays put while the list scrolls. */
.itemsPane {
  width: var(--items-w, 212px);
  flex: 0 0 var(--items-w, 212px);
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-inline-end: .5px solid var(--line);
  background: var(--panel-2);
  /* Both header rows answer the pane's own width, not the window's, so a
     dragged splitter tightens them the moment they stop fitting. */
  container: itemsPane / inline-size;
}

/* Each tab is a small stat: how many files it holds, over what it holds.
   Stacking the two is what lets the full word "Unannotated" survive a pane
   narrow enough that three side-by-side labels would be clipped to letters. */
.itemTabs { margin: 8px 8px 0; }
.itemTabs button {
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  align-items: center;
  gap: 0;
  padding: 3px 4px 4px;
  line-height: 1.25;
  color: var(--muted);
}
.itemTabs button.active { color: var(--text); }
.tabTop { display: inline-flex; align-items: center; gap: 4px; }
.itemTabs .icon { width: 13px; height: 13px; flex: 0 0 auto; }
/* Green once there is something in it, so "how much is left to do" reads at a
   glance from the tab row alone. */
.itemTabs button[data-filter="annotated"].hasSome .icon { color: var(--ok); }
.tabCount {
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  direction: ltr; unicode-bidi: isolate;
}
.itemTabs button.active .tabCount { color: var(--accent); }
.tabLabel {
  max-width: 100%;
  font-size: 10px;
  font-weight: 500;
  color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Squeezed past the point where a name can be read, the count carries the tab
   on its own and the name moves to the tooltip. */
@container itemsPane (max-width: 190px) {
  .tabLabel { display: none; }
  .itemTabs button { padding-block: 4px; }
  /* No room for a field beside the switch: the field takes the row, and the
     switch drops below it against the trailing edge. */
  .itemSearch { flex-basis: 100%; }
  .itemsBar { justify-content: flex-end; }
  /* A list row this narrow has to choose, and the name is what the list is
     for: the still gives up its 38px rather than the name losing all but a
     digit. */
  .items.list .thumbMedia { display: none; }
  .items.list .thumb { gap: 6px; }
}
@container itemsPane (max-width: 118px) {
  .itemTabs .icon { display: none; }
}

.itemsBar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px 8px 6px;
}
.itemSearch {
  flex: 1 1 96px;
  min-width: 0;
  height: 26px;
  padding: 0 9px;
  font-size: 12.5px;
  border-radius: 7px;
}
.viewToggle { margin-inline-start: auto; }
.viewToggle button { padding: 3px 8px; color: var(--muted); }
.viewToggle button.active { color: var(--accent); }
.viewToggle .icon { width: 14px; height: 14px; }

.items {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Reserved, so appearing scrollbars cannot fight the cell measurement. */
  scrollbar-gutter: stable;
  padding: 0 8px 8px;
  scroll-padding-block: 8px;
}
/* Only the rows in view are in the DOM (assessment §9.7): the sizer is as
   tall as every row, and the window inside it is moved to the first row
   drawn. Square cells, as many per row as fit, and list rows of one height:
   measureList() works the columns and the row height out from the pane's
   width and writes them back as --cols and --cell-h, so a row's place is
   known without laying a tile out. */
.itemsSizer { position: relative; }
.itemsWindow { position: absolute; inset-inline: 0; top: 0; }
.itemsRow {
  display: grid;
  grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));
  column-gap: var(--cell-gap, 6px);
  height: var(--cell-h, 92px);
  margin-bottom: var(--cell-gap, 6px);
}
/* The open image's row, kept when it is scrolled out of the window. */
.itemsRow.pinned { position: absolute; inset-inline: 0; }
.items.list .thumb { height: 100%; }

.itemsNote {
  grid-column: 1 / -1;
  margin: 18px 4px;
  text-align: center;
  font-size: 12px;
  line-height: 1.6;
  color: var(--muted);
}

/* A hairline, not a soft shadow, around each tile (assessment §9.8): a blurred
   shadow on every tile cost WebKit 25 ms or more a frame of scrolling. The
   open image's tile keeps the soft shadow, with its accent ring. */
.thumb {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  cursor: pointer;
  background: var(--fill);
  border: 1px solid var(--line);
  transition: border-color .15s ease;
}
.thumb:hover { border-color: var(--line-strong); }
.thumb.active {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 40%, transparent), var(--shadow-1);
}
/* A picture that cannot be shown: the tile says so (its title too). */
.thumbMedia.broken { color: var(--muted); }
.thumbMedia.broken .icon { width: 26px; height: 26px; }
.thumbMedia { display: flex; align-items: center; justify-content: center; overflow: hidden; }
.thumbMedia img { width: 100%; height: 100%; object-fit: cover; display: block; }
.items.grid .thumbMedia { width: 100%; height: 100%; }
.videoThumb { font-size: 34px; }
.thumbCap {
  position: absolute; inset-inline: 0; bottom: 0;
  background: linear-gradient(transparent, rgba(0, 0, 0, .75));
  padding: 14px 8px 5px;
  font-size: 11px;
  color: #fff;
  /* A file name is a file name in any interface language. */
  direction: ltr; unicode-bidi: isolate; text-align: start;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Not painted until shown (visibility, not an opacity of 0), on a flat ground
   rather than a gradient: both cost WebKit paint on every frame (§9.8). */
.items.grid .thumbCap {
  visibility: hidden;
  background: rgba(0, 0, 0, .6);
  padding-top: 5px;
}
.items.grid .thumb:hover .thumbCap,
.items.grid .thumb:focus-visible .thumbCap,
.items.grid .thumb.active .thumbCap { visibility: visible; }
.items.grid .thumb.labeled .thumbCap { padding-inline-end: 30px; }
/* Translucent circular close, the way overlay controls read on iOS. */
.thumbDel {
  position: absolute; top: 5px; inset-inline-end: 5px;
  width: 20px; height: 20px; padding: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, .55);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .35);
  border: 0; color: #fff;
  font-size: 13px; line-height: 1;
  box-shadow: none;
  opacity: 0; z-index: 2;
}
.thumb:hover .thumbDel { opacity: 1; }
.thumbDel:hover { background: var(--danger); }

/* ---------- list view ---------- */

/* One row per file: a small still, the name, and its label count — the
   density you want when scanning a few hundred extracted frames. */
.items.list .thumb {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 6px;
  border-radius: var(--radius);
  border-width: 1.5px;
  box-shadow: none;
  background: transparent;
}
.items.list .thumb:hover { background: var(--fill); border-color: transparent; }
.items.list .thumb.active {
  background: var(--accent);
  border-color: transparent;
  box-shadow: none;
}
.items.list .thumbMedia {
  flex: 0 0 auto;
  width: 44px; height: 34px;
  border-radius: 5px;
  overflow: hidden;
  background: var(--fill);
}
.items.list .videoThumb { font-size: 16px; }
.items.list .thumbCap {
  position: static;
  flex: 1 1 auto;
  min-width: 0;
  background: none;
  padding: 0;
  font-size: 12px;
  color: var(--text);
}
.items.list .thumb.active .thumbCap,
.items.list .thumb.active .thumbTag { color: var(--on-accent); }
/* How many labels this file carries. Drawn only when there are some, so an
   empty tile stays quiet. Green once the image is submitted; neutral while
   someone is still working on it (.working), which is not done. */
.thumbTag {
  flex: 0 0 auto;
  min-width: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ok) 22%, transparent);
  color: var(--text);
  font-size: 10.5px; font-weight: 600; line-height: 17px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  direction: ltr; unicode-bidi: isolate;
}
.items.grid .thumbTag {
  position: absolute;
  inset-inline-end: 5px; bottom: 5px;
  z-index: 2;
  background: color-mix(in srgb, var(--ok) 88%, #000);
  color: #fff;
}
.items.list .thumb.active .thumbTag { background: rgba(255, 255, 255, .25); }
.thumbTag.working { background: color-mix(in srgb, var(--muted) 20%, transparent); }
.items.grid .thumbTag.working { background: color-mix(in srgb, var(--bg) 70%, #000); }

/* Submitted with nothing to box. It is a "done" marker like .thumbTag, but the
   file exports as a negative sample, so it is quieter and says what it is. */
.no-objects-badge {
  flex: 0 0 auto;
  max-width: 100%;
  padding: 0 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--muted) 20%, transparent);
  color: var(--muted);
  font-size: 10.5px; font-weight: 600; line-height: 17px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.items.grid .no-objects-badge {
  position: absolute;
  inset-inline-end: 5px; bottom: 5px;
  z-index: 2;
  max-width: calc(100% - 10px);
  background: color-mix(in srgb, var(--bg) 70%, #000);
  color: #fff;
}
.items.list .thumb.active .no-objects-badge {
  background: rgba(255, 255, 255, .25);
  color: var(--on-accent);
}
.items.list .thumbDel {
  position: static;
  flex: 0 0 auto;
  width: 18px; height: 18px;
  background: transparent;
  color: var(--muted);
}
.items.list .thumb.active .thumbDel { color: var(--on-accent); }
.items.list .thumbDel:hover { background: var(--danger); color: #fff; }
.items.list .thumbPeer { position: static; flex: 0 0 auto; }

/* ---------- picking ---------- */

/* Shift-click is a range here, not a text selection. */
.thumb { user-select: none; -webkit-user-select: none; }
/* The pick box, in the corner the delete button is not in. Seen on hover, and
   on every tile once anything is picked, so the next pick is one click. */
.thumbPick {
  position: absolute; top: 5px; inset-inline-start: 5px;
  width: 18px; height: 18px;
  border-radius: 50%;
  border: 1.5px solid #fff;
  background: rgba(0, 0, 0, .35);
  display: flex; align-items: center; justify-content: center;
  color: var(--on-accent);
  font-size: 11px; font-weight: 700; line-height: 1;
  opacity: 0; z-index: 3;
  transition: opacity .15s ease;
}
.thumb:hover .thumbPick,
.thumb:focus-visible .thumbPick,
.items.picking .thumbPick { opacity: 1; }
.thumb.picked .thumbPick { background: var(--accent); border-color: var(--accent); }
.thumb.picked .thumbPick::after { content: '✓'; }
.items.grid .thumb.picked:not(.active) {
  border-color: color-mix(in srgb, var(--accent) 65%, transparent);
}
/* A peer's dot moves below the pick box rather than under it. */
.items.grid .thumbPick ~ .thumbPeer { top: 27px; }

.items.list .thumbPick {
  position: static; order: -1; flex: 0 0 auto;
  width: 16px; height: 16px;
  border-color: var(--line-strong);
  background: transparent; box-shadow: none;
}
.items.list .thumb.picked:not(.active) {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.items.list .thumb.picked .thumbPick { background: var(--accent); border-color: var(--accent); }
.items.list .thumb.active .thumbPick { border-color: var(--on-accent); }

.pickBar {
  display: flex; align-items: center; gap: 4px 6px; flex-wrap: wrap;
  margin: 0 8px 6px; padding: 6px 6px 6px 10px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
}
.pickCount {
  flex: 1 1 auto; font-size: 12px; font-weight: 600; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.pickBar .iconBtn { width: 24px; height: 24px; order: 1; }
.pickBtns { display: flex; flex-wrap: wrap; gap: 6px; flex: 1 1 100%; order: 2; }
/* What a dialog is about to run on, when it is not the whole project. */
.pickScope {
  margin: 8px 0 0; padding: 7px 10px;
  border-radius: var(--radius);
  font-size: 12.5px; line-height: 1.55;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: .5px solid color-mix(in srgb, var(--accent) 40%, transparent);
}

.canvasWrap {
  --dim: 0;
  flex: 1;
  position: relative;
  overflow: hidden;
  /* The Preview/Photos transparency checker, in the current appearance. */
  background:
    repeating-conic-gradient(color-mix(in srgb, var(--text) 5%, var(--bg)) 0% 25%,
                             var(--bg) 0% 50%) 50% / 20px 20px;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* The Background Darkness slider: a scrim behind the canvas/video, in front
   of the transparency checker, that dims the letterboxed area around the
   image without touching pixel data. */
.canvasWrap::before {
  content: '';
  position: absolute; inset: 0;
  background: #000;
  opacity: var(--dim);
  pointer-events: none;
}
/* touch-action: a finger on the canvas draws; it does not scroll or zoom the page. */
.canvasWrap canvas { position: absolute; inset: 0; cursor: crosshair; touch-action: none; }
.canvasWrap video { max-width: 92%; max-height: 88%; border-radius: var(--radius-lg); }
.canvasWrap canvas, .canvasWrap video { filter: var(--imgFilter, none); }
#empty { color: var(--muted); font-size: 15px; }
.labelsLoading {
  position: absolute; top: 12px; inset-inline-start: 50%; z-index: 5;
  transform: translateX(-50%); padding: 4px 12px; border-radius: 999px;
  background: var(--panel, rgba(0, 0, 0, .6)); color: var(--muted); font-size: 13px;
  animation: fadeIn .2s;
}
.labelsLoading[hidden] { display: none; }
/* The fetch is a wait the page can paint through: the pill pulses. */
.projectLoading { animation: fadeIn .2s, pulse 1.2s ease-in-out .2s infinite; }

/* ---------- label editor ---------- */

/* The chip above a selected label. Positioned by its bottom centre, so the
   transform is restated on :active rather than lost to the button press. */
.labelOptions {
  position: absolute; top: 0; left: 0; z-index: 4;
  transform: translate(-50%, -100%);
  display: inline-flex; align-items: center; gap: 3px;
  height: 22px;
  padding: 0 6px; padding-inline-start: 9px;
  font-size: 11.5px;
  background: var(--chrome);
  box-shadow: var(--shadow-2);
}
.labelOptions:active { transform: translate(-50%, -100%) scale(.96); }
.labelOptions .icon { width: 12px; height: 12px; transition: transform .15s ease; }
.labelOptions[aria-expanded="true"] .icon { transform: rotate(180deg); }

/* A popover, not a sheet: no backdrop, the image stays in view beside it. */
.labelEditor {
  --c: var(--accent);
  position: absolute; top: 0; left: 0; z-index: 5;
  width: 248px;
  max-height: calc(100% - 16px);
  display: flex; flex-direction: column;
  background: var(--chrome);
  border: .5px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-3);
  overflow: hidden;
  animation: sheetIn .16s cubic-bezier(.32, .72, 0, 1);
}
.leHead {
  display: flex; align-items: center; gap: 7px;
  padding: 6px; padding-inline-start: 12px;
  border-bottom: .5px solid var(--line);
  font-size: 13px; font-weight: 600;
}
.leHead span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.leHead .iconBtn { width: 24px; height: 24px; }
.leHead .icon { width: 14px; height: 14px; }
.labelEditor .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--c); flex: 0 0 auto; }

.leBody { padding: 10px 12px; border-bottom: .5px solid var(--line); }
.leBody input {
  width: 100%;
  padding: 6px 10px;
  background: var(--panel); color: var(--text);
  border: .5px solid var(--line-strong); border-radius: var(--radius);
  font-family: inherit; font-size: 13px;
  box-shadow: var(--shadow-1);
}
.leActions { display: flex; justify-content: space-between; gap: 8px; margin-top: 10px; }
/* The label's flags: unset looks like any small button; yes is filled with the
   accent, no is struck through, so the three states read apart at a glance. */
.leFlags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.flagBtn .flagState:not(:empty) { margin-inline-start: 4px; font-weight: 700; }
.flagBtn[data-state="yes"] {
  border-color: transparent;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
}
.flagBtn[data-state="no"] { text-decoration: line-through; color: var(--muted); }
button.leDelete { color: var(--danger); }
button.leDelete:hover { background: var(--danger-soft); }

.leList { flex: 1 1 auto; min-height: 0; max-height: 264px; overflow-y: auto; padding: 4px; }
.leRow {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  cursor: pointer;
  user-select: none;
}
.leRow.hl { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.leRow.current .leName { font-weight: 600; }
.leIdx {
  min-width: 20px;
  padding: 0 4px;
  border-radius: 4px;
  background: var(--fill);
  color: var(--muted);
  font: 10.5px/1.7 var(--font-mono);
  text-align: center;
  direction: ltr;
}
.leName { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.leRow.create { color: var(--accent); }
.leRow.create .icon { width: 14px; height: 14px; }

/* ---------- annotations panel ---------- */

.iconBtn.on { background: var(--fill-hover); color: var(--accent); }

.annotPanel {
  width: var(--annot-w, 236px);
  flex: 0 0 var(--annot-w, 236px);
  display: flex; flex-direction: column;
  min-height: 0;
  border-inline-start: .5px solid var(--line);
  background: var(--panel-2);
}
.apHead {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px 8px;
  font-size: 13px; font-weight: 600;
}
.apCount {
  min-width: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--fill);
  color: var(--muted);
  font-size: 11px; font-weight: 600; line-height: 1.7;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.reviewBtn {
  margin-inline-start: auto;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-size: 11px; font-weight: 600;
  cursor: pointer;
}
.reviewBtn .icon { width: 13px; height: 13px; }
.reviewBtn:hover:not(:disabled) { background: var(--fill); color: var(--text); }
.reviewBtn:disabled { opacity: .5; cursor: not-allowed; }
.reviewBtn.active {
  border-color: transparent;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
}

.apTabs { margin: 0 12px 8px; }
.apTabs button { flex: 1 1 0; justify-content: center; }
.apPane { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 6px 8px; }

.apRow, .lyRow {
  display: flex; align-items: center; gap: 7px;
  min-height: 30px;
  padding: 2px 4px; padding-inline-start: 8px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  cursor: pointer;
  user-select: none;
}
.apRow:hover, .lyRow:hover { background: var(--fill); }
.apRow.active, .lyRow.active { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.apRow.off, .lyRow.off { color: var(--muted); }
.off .dot, .off .lyType { opacity: .35; }
.lyRow.lyLocked { cursor: default; }
.annotPanel .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--c); flex: 0 0 auto; }
.apName { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.apRow.unused .apName { color: var(--muted); }
.apNum { color: var(--muted); font-size: 11.5px; font-variant-numeric: tabular-nums; }

/* Row actions stay out of the way until the row is pointed at — except the
   ones that are on, so a hidden or locked row says so at a glance. */
.apBtn { width: 24px; height: 24px; color: var(--muted); opacity: 0; }
.apBtn .icon { width: 14px; height: 14px; }
.apRow:hover .apBtn, .lyRow:hover .apBtn, .apBtn:focus-visible,
.off .apBtn.isOn, .lyLocked .apBtn.isOn { opacity: 1; }

.lyGrip { width: 12px; height: 12px; margin-inline-start: -4px; color: var(--muted); opacity: 0; cursor: grab; }
.lyRow[draggable="true"]:hover .lyGrip { opacity: .8; }
.lyType { width: 13px; height: 13px; color: var(--muted); }
.lyRow.dragging { opacity: .45; }
.lyRow.dropAbove { box-shadow: inset 0 2px 0 var(--accent); }
.lyRow.dropBelow { box-shadow: inset 0 -2px 0 var(--accent); }

.apSub { margin: 14px 8px 4px; font-size: 11px; font-weight: 600; color: var(--muted); }
.apNote { margin: 18px 10px; text-align: center; color: var(--muted); font-size: 12.5px; }
.apBar { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; padding: 2px 2px 6px; }
.apFilter {
  display: inline-flex; align-items: center; gap: 6px;
  max-width: 100%;
  padding: 2px 7px; padding-inline-start: 9px;
  font-size: 12px;
  border-color: transparent;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  box-shadow: none;
}
.apFilter .icon { width: 12px; height: 12px; }

/* ---------- status bar ---------- */

/* The bottom bar of a Mac window: what the tool is doing on the leading side,
   where you are in the set on the trailing side. */
.statusBar {
  display: flex;
  align-items: center;
  gap: 4px;
  min-height: 30px;
  padding: 2px 10px;
  border-top: .5px solid var(--line);
  font-size: 11.5px;
}
.hint {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.navBar { display: flex; align-items: center; gap: 1px; flex: 0 0 auto; }
/* "Previous" points back along the reading direction, whichever way that is. */
[dir="rtl"] .navBar .icon { transform: scaleX(-1); }
.navBar span {
  color: var(--muted);
  min-width: 52px;
  text-align: center;
  direction: ltr;
  font-variant-numeric: tabular-nums;
}
.statusBar .iconBtn { width: 24px; height: 24px; }
.statusBar .iconBtn.wide { width: auto; }
.statusBar .icon { width: 15px; height: 15px; }

/* ---------- modals ---------- */

/* Sheets sit on a dimmed, blurred backdrop and scale in from just under
   full size — the standard macOS/iOS presentation. */
.modal {
  position: fixed; inset: 0; z-index: 20;
  background: rgba(0, 0, 0, .32);
  display: flex; align-items: center; justify-content: center;
  animation: fadeIn .18s ease;
}
/* The prompt/confirm sheet is opened *from* other modals (e.g. New Job on the
   board), so it has to stack above them rather than behind. */
#askModal { z-index: 50; }
.modalBox {
  width: min(540px, 92vw);
  border-radius: var(--radius-xl);
  padding: 22px;
  max-height: 88vh; overflow: auto;
  animation: sheetIn .22s cubic-bezier(.32, .72, 0, 1);
}
.modalBox h3 { margin: 0 0 14px; font-size: 16px; font-weight: 600; letter-spacing: -.015em; }
.modalBox label { display: block; margin: 12px 0; font-size: 12.5px; color: var(--muted); }
.modalBox label select, .modalBox label input {
  width: 100%;
  margin-top: 5px;
}
.modalBox select, .modalBox input {
  padding: 7px 10px;
  background: var(--panel);
  color: var(--text);
  border: .5px solid var(--line-strong);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: 13px;
  box-shadow: var(--shadow-1);
}
input:focus, select:focus, textarea:focus { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
.rangeFields { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.info {
  background: var(--fill);
  padding: 9px 12px;
  border-radius: var(--radius);
  font-size: 12.5px;
  color: var(--muted);
  direction: ltr;
  text-align: start;
}
.modalActions { display: flex; gap: 10px; margin-top: 18px; justify-content: flex-end; }
.modalActions button { flex: 0 0 auto; }

/* Prompt / confirm sheet. Alert-width, centred title, and a destructive
   action that fills red the way a system alert's does. */
.askBox { width: min(400px, 90vw); }
.askBox h3 { text-align: center; margin-bottom: 6px; }
.askMessage {
  margin: 0 0 4px;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
  word-break: break-word;
  white-space: pre-line;     /* a message may be two paragraphs */
}
/* Toggle buttons that fill a comma-separated field with a run of classes. */
.askPresets { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 7px; }
.askPresets button[aria-pressed="true"] {
  background: var(--accent);
  border-color: transparent;
  color: var(--on-accent);
  box-shadow: 0 1px 2px color-mix(in srgb, var(--accent) 35%, transparent);
}
.askPresets button[aria-pressed="true"]:hover {
  background: color-mix(in srgb, #fff 10%, var(--accent));
}
.askBox .modalActions { margin-top: 20px; }
.askBox .modalActions button { min-width: 96px; }
button.primary.danger {
  background: var(--danger);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 1px 2px color-mix(in srgb, var(--danger) 35%, transparent);
}
button.primary.danger:hover { background: color-mix(in srgb, #fff 12%, var(--danger)); }
button.primary.danger:active { background: color-mix(in srgb, #000 12%, var(--danger)); }

.shortcuts { width: 100%; border-collapse: collapse; font-size: 13px; }
.shortcuts td { padding: 7px 4px; border-bottom: .5px solid var(--line); }
.shortcuts td:first-child { width: 34%; white-space: nowrap; color: var(--muted); }

@keyframes fadeIn { from { opacity: 0; } }
@keyframes sheetIn { from { opacity: 0; transform: scale(.96); } }

/* ---------- toasts ---------- */

.toasts {
  position: fixed;
  bottom: 18px; inset-inline-end: 18px;
  z-index: 40;
  display: flex; flex-direction: column; gap: 8px;
}
/* Notification banner, with the status colour carried by the leading edge. */
.toast {
  border-inline-start: 3px solid var(--accent);
  padding: 10px 14px;
  border-radius: var(--radius-lg);
  font-size: 13px;
  animation: slideIn .28s cubic-bezier(.32, .72, 0, 1);
  max-width: 340px;
}
.toast.ok { border-inline-start-color: var(--ok); }
.toast.error { border-inline-start-color: var(--danger); }
.toast.out { opacity: 0; transform: scale(.97); transition: opacity .3s, transform .3s; }

@keyframes slideIn { from { opacity: 0; transform: translateY(10px) scale(.98); } }

/* ---------- narrow screens ---------- */

@media (max-width: 1000px) {
  aside { width: 180px; flex-basis: 180px; }
  .itemsPane { width: 170px; flex-basis: 170px; }
  .annotPanel { width: 196px; flex-basis: 196px; }
  .actions { gap: 5px; }
  header { flex-direction: column; align-items: stretch; }
}

/* ---------- sign in / register ---------- */

.authBody {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  overflow: auto;
  background:
    radial-gradient(900px 520px at 50% -12%,
      color-mix(in srgb, var(--accent) 22%, transparent) 0%, transparent 62%),
    var(--bg);
}
.authCard {
  width: min(380px, 92vw);
  border-radius: var(--radius-xl);
  padding: 30px 28px;
  text-align: center;
}
.authCard h1 {
  margin: 12px 0 2px;
  font-family: var(--font-brand);
  font-weight: 500;
  font-size: 30px;
  letter-spacing: -.035em;
  line-height: 1;
  direction: ltr;
}
.authSub { margin: 0 0 20px; color: var(--muted); font-size: 13px; }
.authCard label {
  display: block; text-align: start;
  margin-bottom: 14px; font-size: 12.5px; color: var(--muted);
}
.authCard input {
  width: 100%; margin-top: 5px; padding: 9px 11px;
  background: var(--panel); color: var(--text);
  border: .5px solid var(--line-strong); border-radius: var(--radius);
  font-family: inherit; font-size: 14px;
  box-shadow: var(--shadow-1);
}
.authCard small { color: var(--muted); font-size: 11px; }
.authError {
  background: var(--danger-soft); border: .5px solid color-mix(in srgb, var(--danger) 40%, transparent);
  color: var(--danger);
  padding: 9px 12px; border-radius: var(--radius); font-size: 13px; margin-bottom: 16px;
}
.authNotice {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: .5px solid color-mix(in srgb, var(--accent) 35%, transparent);
  color: var(--text);
  padding: 9px 12px; border-radius: var(--radius); font-size: 13px; margin-bottom: 16px;
}
.authNotice .linkBtn { padding: 0; margin-top: 4px; font-size: 13px; }
.authOr {
  display: flex; align-items: center; gap: 10px;
  margin: 16px 0; color: var(--muted); font-size: 12px;
}
.authOr::before, .authOr::after { content: ''; flex: 1; border-top: .5px solid var(--line-strong); }
.googleBtn {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  padding: 9px 11px; border-radius: var(--radius);
  background: var(--panel); color: var(--text);
  border: .5px solid var(--line-strong); box-shadow: var(--shadow-1);
  font-size: 14px; font-weight: 500; text-decoration: none;
}
.googleBtn:hover { background: color-mix(in srgb, var(--panel) 94%, var(--text)); }
/* The address an invitation was sent to, under the project it is for. */
.authNote { margin: -14px 0 20px; color: var(--muted); font-size: 12px; }
/* Sign-in on the invitation page is a link, not a submit, but it is the same
   primary action as the buttons beside it and has to look like one. */
.authCard a.primary.block {
  display: block; width: 100%; box-sizing: border-box; text-align: center;
  text-decoration: none; padding: 9px 14px;
}
.authAlt { margin: 16px 0 0; font-size: 13px; color: var(--muted); }
.authAlt a { color: var(--accent); text-decoration: none; }
.authAlt a:hover { text-decoration: underline; }

/* ---------- account row in the sidebar ---------- */

.userRow {
  display: flex; align-items: center; gap: 2px;
  margin-top: 2px; padding-inline-start: 8px; min-width: 0;
}
.userRow .userIcon { color: var(--muted); width: 15px; height: 15px; margin-inline-end: 5px; }
.userChip {
  flex: 1 1 auto; min-width: 0;
  font-size: 12.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.userRow .iconBtn { width: 26px; height: 26px; }
.userRow .icon { width: 15px; height: 15px; color: var(--muted); }
.userRow .iconBtn:hover .icon { color: var(--text); }
.logoutForm { margin: 0; }

/* ---------- permission-locked controls ---------- */

/* Role gating only — a *layer* lock is .lyLocked, which must stay clickable
   so it can be unlocked. */
.locked { opacity: .4; pointer-events: none; }

/* ---------- Display Options ---------- */

/* A popover anchored above its footer trigger, styled like the other
   floating surfaces (a sheet, not a modal — the canvas stays live behind it). */
.displayOptsWrap { position: relative; flex: 0 0 auto; }
.displayOptsPanel {
  position: absolute;
  bottom: calc(100% + 8px);
  inset-inline-end: 0;
  z-index: 20;
  width: 240px;
  background: var(--chrome);
  border: .5px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-3);
  overflow: hidden;
}
.dopHead {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 12px;
  border-bottom: .5px solid var(--line);
  font-size: 12.5px; font-weight: 600;
}
.dopHead .iconBtn { width: 22px; height: 22px; }
.dopHead .icon { width: 13px; height: 13px; }
.dopBody { padding: 12px; display: flex; flex-direction: column; gap: 14px; }
.dopSlider { display: flex; flex-direction: column; gap: 6px; font-size: 12px; color: var(--muted); }
.dopSlider input[type="range"] {
  width: 100%; padding: 0; border: 0; background: transparent; box-shadow: none;
  accent-color: var(--accent);
}
.dopRow { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12px; color: var(--muted); }
.dopToggle { padding: 2px; }
.dopToggle button { padding: 4px 9px; }
.dopToggle .icon { width: 14px; height: 14px; }

/* ---------- background job progress ---------- */

/* An upload and the job it starts can be on screen together; the dock stacks
   them instead of letting the second sit on top of the first. */
.panelDock {
  position: fixed;
  bottom: 18px; inset-inline-start: 18px;
  z-index: 30;
  width: min(320px, 90vw);
  display: flex; flex-direction: column; gap: 10px;
  pointer-events: none;
}
.panelDock > .jobPanel { pointer-events: auto; }
.jobPanel {
  border-radius: var(--radius-lg);
  padding: 12px 14px;
}
.jobHead {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; font-size: 13px; margin-bottom: 8px; font-weight: 500;
}
.jobPanel .bar {
  height: 6px; background: var(--fill);
  border-radius: 99px; overflow: hidden;
}
.jobPanel .bar i {
  display: block; height: 100%; width: 0;
  border-radius: 99px;
  background: var(--accent);
  transition: width .3s ease;
}
.jobPanel .bar i.indeterminate { animation: pulse 1.2s ease-in-out infinite; }
.jobPanel small { display: block; margin-top: 6px; color: var(--muted); font-size: 11.5px; }
@keyframes pulse { 50% { opacity: .35; } }

/* ---------- data tables in modals ---------- */

.dataTable { width: 100%; border-collapse: collapse; font-size: 13px; }
.dataTable th {
  text-align: start; font-size: 11.5px;
  color: var(--muted); font-weight: 600;
  padding: 6px 4px; border-bottom: .5px solid var(--line);
}
.dataTable td {
  padding: 7px 4px; border-bottom: .5px solid var(--line);
  max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dataTable tr:last-child td { border-bottom: 0; }
/* Shrink-wrap the trailing action column so the name column keeps the space. */
.dataTable td:last-child, .dataTable th:last-child { width: 1%; white-space: nowrap; }
.dataTable td:last-child { text-align: end; }
.dataTable .rowSelect {
  padding: 3px 8px; font-size: 12.5px;
  background: var(--panel); color: var(--text);
  border: .5px solid var(--line-strong); border-radius: var(--radius-sm); font-family: inherit;
}
.dataTable .you { color: var(--muted); font-size: 11px; margin-inline-start: 6px; }
.emptyRow td { color: var(--muted); text-align: center; padding: 18px 4px; }
.modalBox.wide { width: min(760px, 94vw); }
.memberAdd {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 16px;
  padding-top: 16px;
  border-top: .5px solid var(--line);
}
.memberAdd input {
  flex: 1 1 auto;
  min-width: 0;                /* let it shrink instead of forcing overflow */
  padding: 7px 10px;
  background: var(--panel); color: var(--text);
  border: .5px solid var(--line-strong); border-radius: var(--radius); font-family: inherit;
  box-shadow: var(--shadow-1);
}
.memberAdd select { flex: 0 0 auto; width: auto; }
.memberAdd button { flex: 0 0 auto; }
.muted { color: var(--muted); font-size: 12.5px; }
.tokenBox {
  background: color-mix(in srgb, var(--ok) 14%, transparent);
  border: .5px solid color-mix(in srgb, var(--ok) 35%, transparent);
  color: color-mix(in srgb, var(--ok) 65%, var(--text));
  padding: 10px 12px; border-radius: var(--radius); margin-bottom: 12px;
  font: 12px/1.5 var(--font-mono); word-break: break-all;
  direction: ltr; text-align: start;
}

/* ---------- user administration ---------- */

.stackForm { margin-top: 18px; padding-top: 16px; border-top: .5px solid var(--line); }
.stackForm h4 { margin: 0 0 10px; font-size: 13px; color: var(--muted); font-weight: 600; }
.stackForm .memberAdd { margin-top: 0; padding-top: 0; border-top: 0; }
.stackForm small { display: block; margin-top: 8px; }
.badge {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: 11px; background: var(--fill); border: none; color: var(--muted);
}
.badge.admin { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
.badge.off { background: var(--danger-soft); color: var(--danger); }
.rowActions { display: flex; gap: 4px; justify-content: flex-end; }

/* ---------- jobs board ---------- */

/* The board modal is wide and column-based, so it gets its own roomy box. */
.modalBox.board {
  width: min(1100px, 96vw);
  max-width: 96vw;
  padding: 0;
  overflow: hidden;
  display: flex; flex-direction: column;
  max-height: 88vh;
}
.boardBar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: .5px solid var(--line);
}
.boardBar h3 { margin: 0; }

.jobColumns {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* The grid is squeezed to the modal's height as a flex item, and an auto
     row then follows that squeezed height instead of the tallest column, so
     long columns spilled their cards out of the panel. max-content keeps the
     row as tall as its content; the grid scrolls instead. */
  grid-auto-rows: max-content;
  gap: 14px; padding: 16px;
  min-height: 0;
  overflow-y: auto;
}
@media (max-width: 720px) { .jobColumns { grid-template-columns: 1fr; } }

.jobCol {
  background: var(--panel-2);
  border: .5px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 12px;
  display: flex; flex-direction: column; gap: 10px;
  min-height: 120px;
}
.jobColHead { text-align: center; }
.jobColHead h4 { margin: 0; font-size: 15px; }
.jobColHead small { color: var(--muted); font-size: 11.5px; }
.jobColTools { display: flex; flex-direction: column; gap: 6px; align-items: center; }

.linkBtn {
  display: inline-flex; align-items: center; gap: 5px;
  background: transparent; border: 0; box-shadow: none;
  color: var(--accent); font-size: 12.5px; font-weight: 600;
  padding: 4px 6px; cursor: pointer;
}
.linkBtn:hover { text-decoration: underline; }
.linkBtn:disabled { color: var(--muted); cursor: default; text-decoration: none; opacity: .6; }
.linkBtn .icon { width: 15px; height: 15px; }

.jobCard {
  background: var(--panel);
  border: .5px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-1);
  padding: 12px 14px;
  display: flex; flex-direction: column; gap: 8px;
  flex-shrink: 0;
}
.jobCol.c-annotating .jobCard { border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); }
.jobCard.s-done { opacity: .85; }
.jobCardHead { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.jobCardHead strong { font-size: 13.5px; }
.jobDel { opacity: 0; }
.jobCard:hover .jobDel { opacity: 1; }
.jobWho { font-size: 12.5px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.jobWhoLabel { color: var(--muted); }
.jobWho select {
  height: 26px; padding: 0 6px; font-size: 12px;
  border-radius: var(--radius-sm);
  border: .5px solid var(--line-strong);
  background: var(--panel); color: var(--text);
}

.jobBarTrack {
  height: 6px; border-radius: 999px;
  background: var(--fill);
  overflow: hidden;
}
.jobBarTrack i {
  display: block; height: 100%;
  border-radius: 999px;
  background: var(--accent);
  transition: width .2s ease;
}
.jobCard.s-done .jobBarTrack i { background: var(--ok); }

.jobStats { display: flex; flex-direction: column; gap: 4px; }
.jobStats > strong { font-size: 12.5px; }
.jobCounts { display: flex; flex-direction: column; gap: 1px; font: 11px/1.5 var(--font-mono); }
.jobCounts .c-done { color: var(--accent); }
.jobCounts .c-todo { color: var(--muted); }
.jobCard.s-done .jobCounts .c-done { color: var(--ok); }

.startJob { align-self: flex-end; }
.startJob .icon { width: 14px; height: 14px; }

/* The banner above the media list while it is narrowed to one job. */
.jobFilterBar {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 6px 8px; margin: 0 2px 6px;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-radius: var(--radius-sm);
}
.jobFilterLabel { font-size: 12px; font-weight: 600; color: var(--accent); }
.jobFilterBar .iconBtn { width: 24px; height: 24px; }

/* The sidebar notification bell and its unread count. */
.notifBtn { position: relative; }
.notifBadge {
  position: absolute; top: -2px; inset-inline-end: -2px;
  min-width: 15px; height: 15px; padding: 0 3px;
  border-radius: 999px;
  background: var(--danger); color: #fff;
  font-size: 9.5px; font-weight: 700; line-height: 15px; text-align: center;
  direction: ltr; unicode-bidi: isolate;
  box-shadow: 0 0 0 1.5px var(--chrome-2);
}
.notifHead { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.notifHead h3 { margin: 0; }
.notifList { display: flex; flex-direction: column; max-height: 60vh; overflow-y: auto; }
.notifRow {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 4px; border-bottom: .5px solid var(--line);
  cursor: default;
}
.notifRow[data-action] { cursor: pointer; }
.notifRow[data-action]:hover { background: var(--fill); border-radius: var(--radius-sm); }
.notifRow:last-child { border-bottom: 0; }
.notifDot { flex: 0 0 auto; width: 8px; height: 8px; margin-top: 5px; border-radius: 50%; background: transparent; }
.notifRow.unread .notifDot { background: var(--accent); }
.notifRow.k-approved .notifDot { background: var(--ok); }
.notifRow.k-rejected .notifDot { background: var(--danger); }
.notifRow:not(.unread) .notifText { color: var(--muted); }
.notifBody { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.notifText { font-size: 13px; }
.notifSub { color: var(--muted); font-size: 11.5px; }

/* ---------- team chat ---------- */

/* Reuses .notifBadge for the unread dot; only the anchor is new. */
.chatBtn { position: relative; }

/* The panel is its own fixed-height window rather than a form sheet that
   grows with its content: a header and composer pinned in place, and only
   the timeline between them scrolls. .modalBox's own padding is switched
   off here — each strip below carries its own, so the header and composer
   can go full-bleed with a hairline edge instead of floating in a margin. */
.modalBox.chat {
  width: min(480px, 92vw);
  height: min(640px, 82vh);
  padding: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.chatHead {
  display: flex; align-items: center; justify-content: space-between;
  flex: 0 0 auto;
  gap: 10px; padding: 16px 10px 14px 18px;
  border-bottom: .5px solid var(--line);
}
.chatHead h3 { margin: 0; }

.chatList {
  flex: 1 1 auto;
  display: flex; flex-direction: column;
  gap: 2px;
  overflow-y: auto;
  padding: 14px 16px;
}

/* A centered date pill breaks the timeline into days, the way every chat
   app since iChat has. */
.chatDateSep {
  align-self: center;
  margin: 12px 0 6px;
  padding: 3px 12px;
  border-radius: 999px;
  background: var(--fill);
  color: var(--muted);
  font-size: 10.5px; font-weight: 600;
}
.chatDateSep:first-child { margin-top: 0; }

/* One message: an avatar for everyone but you (you already know which
   bubbles are yours — that's what the trailing edge says), then the bubble
   column. A "grouped" row is a same-author message that followed the
   previous one within a few minutes: only its name/time line is held back —
   the avatar stays on every row, bottom-aligned within that one row, so it
   never has to float in the gap between two separate flex children to reach
   the bottom of a run. */
.chatRow {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  max-width: 84%;
  margin-top: 10px;
}
.chatRow.grouped { margin-top: 2px; }
/* "you" tracks the writing direction rather than a hardcoded side, so a
   message you sent lands on the same edge in both RTL and LTR. There is no
   avatar column on your own side of the timeline — you already know which
   bubbles are yours. */
.chatRow.you { align-self: flex-end; }

.chatAvatar {
  flex: 0 0 auto;
  width: 26px; height: 26px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  color: #fff; font-size: 11px; font-weight: 700;
  box-shadow: var(--shadow-1);
}

.chatBubbleCol { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

.chatMeta { display: flex; gap: 6px; align-items: baseline; font-size: 11px; color: var(--muted); padding: 0 3px; }
.chatAuthor { font-weight: 600; color: var(--text); }
.chatEdited { font-style: italic; }

.chatBody {
  display: flex; flex-direction: column; gap: 4px;
  padding: 7px 12px; border-radius: var(--radius-lg);
  /* --fill, not --panel: the modal's own chrome is already near-white
     (near-black in dark mode), so a "raised surface" tone here would sit at
     almost the same value and read as plain unstyled text, not a bubble. */
  background: var(--fill); box-shadow: var(--shadow-1);
  font-size: 13.5px; line-height: 1.55;
}
.chatBodyText { overflow-wrap: anywhere; white-space: pre-wrap; }
/* The corner nearest the (real or spacer) avatar tucks in on a grouped
   message, the way consecutive bubbles nest in every chat app — a small
   tell that these came from one uninterrupted turn, not five separate ones. */
.chatRow.grouped .chatBody { border-start-start-radius: var(--radius-sm); }
.chatRow.you .chatBody {
  background: var(--accent); color: var(--on-accent);
  box-shadow: 0 1px 2px color-mix(in srgb, var(--accent) 35%, transparent);
}
.chatRow.you.grouped .chatBody { border-start-start-radius: var(--radius-lg); border-start-end-radius: var(--radius-sm); }
.chatRow.you .chatMeta { color: var(--muted); }

.chatActions { display: flex; gap: 2px; margin-inline-start: 2px; }
.chatActions .iconBtn { width: 22px; height: 22px; }
.chatActions .iconBtn .icon { width: 13px; height: 13px; }
/* Reply/edit/delete stay out of the way until a pointer is actually over
   the message — the same reasoning .tbGroup uses for toolbar chrome, just
   applied per-row. Devices with no hover (touch) never get the fade, since
   there is no gesture there that would ever reveal it.
   `max-height`, not `display`, collapses it: a hidden button must stay
   reachable by keyboard (:focus-within still needs something focusable to
   land on), and `opacity` alone would leave its box still taking up space —
   which is exactly what was pushing every row's bottom-aligned avatar down
   past its own bubble and into the gap before the next message. */
@media (hover: hover) {
  .chatActions {
    max-height: 0; opacity: 0; overflow: hidden; pointer-events: none;
    transition: opacity .12s ease, max-height .12s ease;
  }
  .chatRow:hover .chatActions, .chatRow:focus-within .chatActions {
    max-height: 24px; opacity: 1; pointer-events: auto;
  }
}

/* A quoted preview, WhatsApp-style: a thin accent rule and a dimmed line
   above the reply's own text, nested inside the same bubble (see
   .chatBody's own flex-column above) rather than a separate box floating
   next to it — so it reads as part of the one message, not a citation, and
   sits on the bubble's own colour instead of the modal's. */
.chatQuote {
  display: flex; flex-direction: column; gap: 1px;
  padding: 4px 8px;
  border-inline-start: 2.5px solid var(--accent);
  border-radius: 4px;
  background: color-mix(in srgb, var(--text) 6%, transparent);
  font-size: 12px; cursor: pointer;
}
.chatQuote .chatAuthor { font-size: 11.5px; color: var(--accent); }
.chatQuote span:last-child {
  color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chatRow.you .chatQuote {
  border-inline-start-color: color-mix(in srgb, #fff 70%, transparent);
  background: color-mix(in srgb, #fff 16%, transparent);
}
.chatRow.you .chatQuote .chatAuthor { color: #fff; }
.chatRow.you .chatQuote span:last-child { color: color-mix(in srgb, #fff 75%, transparent); }

/* The composer's own reply-preview bar, shown only while a quote is queued
   to go out with the next message. */
.chatReplyBar {
  display: flex; align-items: center; gap: 8px;
  flex: 0 0 auto;
  padding: 8px 16px;
  border-top: .5px solid var(--line);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}
.chatReplyBar .chatQuote {
  flex: 1 1 auto; min-width: 0;
  padding: 0; border: 0; background: none; cursor: default;
}
.chatReplyBar .iconBtn { flex: 0 0 auto; width: 22px; height: 22px; }

/* A pill input and a circular send button, the shape every mobile chat
   composer takes — set apart from .memberAdd's plain form-row look. */
.chatComposer {
  display: flex; align-items: center; gap: 8px;
  flex: 0 0 auto;
  padding: 12px 16px;
  border-top: .5px solid var(--line);
}
.chatComposer input {
  flex: 1 1 auto; min-width: 0;
  padding: 9px 15px;
  background: var(--panel); color: var(--text);
  border: .5px solid var(--line-strong); border-radius: 999px;
  font-family: inherit; font-size: 13.5px;
  box-shadow: var(--shadow-1);
}
.chatSendBtn {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  border: 0; border-radius: 50%;
  background: var(--accent); color: var(--on-accent);
  box-shadow: 0 1px 2px color-mix(in srgb, var(--accent) 35%, transparent);
}
.chatSendBtn:hover { background: color-mix(in srgb, #fff 10%, var(--accent)); }
.chatSendBtn:active { background: var(--accent-press); transform: scale(.92); }
.chatSendBtn .icon { width: 16px; height: 16px; stroke-width: 2.3; }

/* The empty state: a big dim glyph rather than a lone line of text, since
   it is the first thing a brand-new project's chat shows. */
.chatEmpty {
  flex: 1 1 auto;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 20px; color: var(--muted); text-align: center;
}
.chatEmpty .icon { width: 38px; height: 38px; opacity: .3; stroke-width: 1.3; }

/* A message jumped to from a quote gets a brief highlight so the eye finds
   it, the way a fragment link does. */
.chatRow.flash .chatBody { animation: chatFlash 1.1s ease-out; }
@keyframes chatFlash {
  0% { box-shadow: 0 0 0 2px var(--accent); }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* ---------- dataset export sheet ---------- */

/* The sheet has two views in one box: pick the format, then the result and
   the three steps that take the ZIP to Colab. */
.exportFormats { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
/* The formats come in two groups, by what the archive can train. */
.exportTask {
  flex: 1 1 100%;
  margin-top: 4px;
  color: var(--muted); font-size: 11.5px; font-weight: 600;
}
.exportTask:first-child { margin-top: 0; }
.modalBox label.exportFormat {
  display: flex; align-items: center; gap: 7px;
  flex: 1 1 150px;
  margin: 0;
  padding: 9px 12px;
  border: .5px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--panel);
  font-size: 13px; color: var(--text);
  cursor: pointer;
}
.modalBox label.exportFormat:has(input:checked) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
/* .modalBox stretches every field to full width and gives it a control
   frame; a radio, a checkbox and a slider want neither. */
.modalBox .exportFormat input,
.modalBox .exportBg input,
.modalBox .checkRow input,
.modalBox .exportVal input[type="range"] {
  width: auto; margin: 0; flex: 0 0 auto;
  padding: 0; border: 0; box-shadow: none; background: none;
  accent-color: var(--accent);
}

/* The hint and the counts are translated sentences, not numbers, so they
   follow the page direction rather than .info's forced LTR. */
#exportModelHint, #exportCounts, #dedupeHint, .shapeHint { direction: inherit; text-align: start; }

/* Box, mask or polygon: three short choices, so they share one row. */
.shapeChoice { margin: 14px 0 6px; }
.modalBox .shapeChoice label.exportFormat { flex: 1 1 0; justify-content: center; }
.shapeChoice .icon { width: 15px; height: 15px; color: var(--muted); }
.shapeChoice label:has(input:checked) .icon { color: var(--accent); }

.modalBox label.exportVal {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.modalBox .exportVal input[type="range"] { flex: 1 1 140px; }
.exportVal output { flex: 0 0 auto; font-variant-numeric: tabular-nums; color: var(--text); }

.modalBox label.exportBg,
.modalBox label.checkRow { display: flex; align-items: center; gap: 8px; }

/* A suggestion is a draft everywhere it appears: dashed on the canvas, and
   outlined rather than filled in the file grid, so "a model has been here"
   never reads the same as "someone finished this". */
.thumbTag.suggested {
  background: transparent;
  color: var(--accent);
  border: 1px dashed var(--accent);
}

.suggestBar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 10px; margin: 0 0 6px;
  border: 1px dashed var(--accent); border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.suggestCount { flex: 1 1 auto; font-size: 12px; color: var(--text); }
.suggestBtns { display: flex; gap: 6px; flex: 0 0 auto; }
.allSuggestBar { margin: 0 2px 6px; }
/* Which suggestions the open image shows, by what made them. */
.originFilter { display: flex; gap: 4px; flex: 1 1 100%; flex-wrap: wrap; }
.originChip[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
/* What made a suggestion, on its row in the layer list. */
.lyOrigin {
  flex: 0 0 auto; padding: 0 6px; border: 1px dashed var(--accent); border-radius: 8px;
  font-size: 11px; color: var(--accent);
}
/* What made a suggestion, said by a word and marked by a colour: never by
   the colour alone. The dot is beside the word on a chip; the layer badge
   takes the colour as its border. */
.origin-model { --origin: var(--accent); }
.origin-import { --origin: #b7791f; }
.origin-agent { --origin: #8e5bd1; }
.originDot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--origin, var(--accent)); margin-inline-end: 5px;
}
.lyOrigin[class*="origin-"] { border-color: var(--origin); color: var(--origin); }
/* Review: the queue's header, above the list it orders. */
.reviewBar {
  display: flex; flex-direction: column; gap: 6px;
  padding: 8px 10px; margin: 0 2px 6px;
  border: 1px solid var(--accent); border-radius: 8px;
}
.reviewHead { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font-size: 13px; }
.reviewPosition { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.reviewOrigins { display: flex; gap: 4px; flex-wrap: wrap; }
.reviewKeys { margin: 0; font-size: 11px; color: var(--muted); }
.reviewBar kbd, .allSuggestBar kbd { font-size: 10px; }
/* An image of the queue with nothing left to rule on: still in its place. */
.thumb.reviewRuled { opacity: .55; }
/* The threshold panel. */
.thresholdForm { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.thresholdSlider { display: flex; align-items: center; gap: 10px; }
.thresholdSlider input { flex: 1 1 auto; }
.thresholdSlider output { min-width: 3.2em; font-variant-numeric: tabular-nums; font-weight: 600; }
.thresholdPreview { min-height: 96px; }
.thresholdPreview[aria-busy="true"] .dataTable,
.thresholdPreview[aria-busy="true"] #thresholdTotals { opacity: .45; }
.thresholdChanged {
  padding: 8px 10px; border: 1px solid var(--accent); border-radius: 8px; font-size: 13px;
}
.thresholdCapped { font-weight: 600; }
/* The class list is the class filter: a box a row. Long lists scroll inside
   the panel, so the totals and the button stay in view. */
.thresholdClassesHead { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.thresholdClassList { max-height: 240px; overflow-y: auto; }
/* Not a form field's caption (.modalBox label): the class's name, in the
   row's own colour, which is muted when the class is left out. */
.thresholdClassList label {
  display: inline-flex; align-items: center; gap: 6px; margin: 0; cursor: pointer;
  font-size: inherit; color: inherit;
}
.thresholdClassList tr.muted { color: var(--muted); }
.thresholdClassList td:nth-child(n+2), .thresholdClassList th:nth-child(n+2) {
  text-align: end; font-variant-numeric: tabular-nums;
}
.thresholdProgress { display: flex; align-items: center; gap: 10px; margin: 8px 0; font-size: 12px; }
.thresholdProgress progress { flex: 1 1 auto; }
.thresholdRuns { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; font-size: 12px; }
.thresholdRunRow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.thresholdRunRow .muted { flex: 1 1 auto; }
/* The open image's flags: marks that something looks wrong (not labels). */
.flagBar {
  display: flex; flex-direction: column; gap: 6px;
  padding: 8px 10px; margin: 0 0 6px;
  border: 1px solid var(--line); border-radius: 8px;
}
.flagHead, .flagRow, .flagForm { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.flagForm input { flex: 1 1 140px; min-width: 0; }
.flagTarget, .flagAbout { font-size: 11px; color: var(--muted); }
.flagRow { padding-top: 6px; border-top: 1px solid var(--line); font-size: 12px; }
.flagReason { font-weight: 600; color: var(--danger); }
.flagNoteText { flex: 1 1 100%; overflow-wrap: anywhere; }
.flagRow button[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
/* An image with an open flag, on its tile, beside the pick box. */
.thumbFlag {
  position: absolute; top: 5px; inset-inline-start: 28px;
  display: inline-flex; padding: 2px; border-radius: 6px;
  background: var(--danger); color: #fff;
}
.thumbFlag .icon { width: 12px; height: 12px; }
.items.list .thumbFlag { position: static; margin-inline-start: 6px; }
/* Unsaved work waiting on a choice: solid, and in the danger colour, so it
   does not read as one more optional suggestion. */
.conflictBar { border: 1px solid var(--danger); background: var(--danger-soft); }

.modalBox.wide { width: min(720px, 94vw); }
.prelabelMore summary {
  cursor: pointer; font-size: 12.5px; color: var(--muted);
  margin: 12px 0 0; padding: 4px 0;
}
.prelabelMore[open] summary { margin-bottom: 4px; }
/* A file input frames itself; the control frame .modalBox puts on every field
   would draw a second box around it. */
.modalBox input[type="file"] {
  padding: 6px; background: none; box-shadow: none;
  border: 1px dashed var(--line-strong);
}

/* The health report. Sentences and numbers both, so it follows the page
   direction rather than .info's forced LTR. */
.healthBody { direction: inherit; text-align: start; font-size: 13px; }
.healthVerdict {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px; border-radius: var(--radius); margin-bottom: 14px;
  background: color-mix(in srgb, var(--ok) 12%, transparent);
  border: .5px solid color-mix(in srgb, var(--ok) 40%, transparent);
}
.healthVerdict.blocked {
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  border-color: color-mix(in srgb, var(--danger) 40%, transparent);
}
.healthVerdict .icon { width: 18px; height: 18px; flex: 0 0 auto; }
.healthSection { margin: 0 0 16px; }
.healthSection h4 {
  margin: 0 0 8px; font-size: 12px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
}
.healthStats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 8px;
}
.healthStat {
  padding: 8px 10px; border-radius: var(--radius);
  background: var(--fill); border: .5px solid var(--line);
}
.healthStat b { display: block; font-size: 17px; font-variant-numeric: tabular-nums; }
.healthStat span { font-size: 11.5px; color: var(--muted); }
/* One row per class: name, a proportional bar, and the count. The bar is what
   makes an imbalance obvious at a glance, which a column of numbers is not. */
.healthClass {
  display: grid; grid-template-columns: minmax(70px, 22%) 1fr auto;
  align-items: center; gap: 10px; margin: 5px 0;
}
.healthClass > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.healthBar { height: 8px; border-radius: 999px; background: var(--fill); overflow: hidden; }
.healthBar i { display: block; height: 100%; border-radius: 999px; }
.healthClass .healthNum { font-variant-numeric: tabular-nums; color: var(--muted); font-size: 12px; }
.healthClass.empty > span:first-child { color: var(--danger); }
.healthWarnings { margin: 0; padding-inline-start: 18px; font-size: 12.5px; color: var(--muted); }
.healthWarnings li { margin: 3px 0; }
.healthNote { font-size: 11.5px; color: var(--muted); margin-top: 12px; }
.exportBg input:disabled + span { opacity: .5; }
/* The export report before the export: quiet while all is well, red when
   the export would be refused. */
.exportPreview { margin-top: 10px; padding: 8px 10px; border-radius: 8px; background: var(--fill);
                 font-size: 12.5px; color: var(--muted); }
.exportPreview ul { margin: 0; padding-inline-start: 18px; }
.exportPreview li { margin: 3px 0; }
.exportPreview.blocked { background: var(--danger-soft); color: var(--danger); }

.exportDone { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; font-size: 14px; }
.exportDone .icon { color: var(--ok); width: 20px; height: 20px; }

.exportWarnings {
  margin: 10px 0 0; padding-inline-start: 18px;
  display: flex; flex-direction: column; gap: 4px;
  font-size: 12.5px; color: var(--muted);
}
.exportWarnings li::marker { color: var(--danger); }

.exportBtns { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.exportBtns .btn { display: inline-flex; align-items: center; gap: 6px; text-decoration: none; }
a.btn.primary {
  background: var(--accent); border-color: transparent; color: var(--on-accent);
  box-shadow: 0 1px 2px color-mix(in srgb, var(--accent) 35%, transparent);
}
a.btn.primary:hover { background: color-mix(in srgb, #fff 10%, var(--accent)); }
a.btn.small { padding: 4px 11px; font-size: 12px; }

.exportSteps {
  margin: 16px 0 0; padding-inline-start: 20px;
  display: flex; flex-direction: column; gap: 10px;
  font-size: 13px;
}
.exportSteps li { line-height: 1.6; }
.exportSteps .btn { margin-inline-start: 6px; text-decoration: none; }
.exportLink { color: var(--accent); margin-inline-start: 6px; }

.exportCmd { margin-top: 16px; font-size: 12.5px; }
.exportCmd summary { cursor: pointer; color: var(--muted); }
.exportCmdRow {
  display: flex; align-items: center; gap: 8px; margin-top: 8px;
}
.exportCmdRow code {
  flex: 1 1 auto; min-width: 0;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: var(--fill);
  font: 11.5px/1.5 var(--font-mono);
  direction: ltr; unicode-bidi: isolate;
  overflow-x: auto; white-space: pre;
}

.exportHistory { margin-top: 20px; border-top: .5px solid var(--line); padding-top: 12px; }
.exportHistory h4 { margin: 0 0 8px; font-size: 13px; font-weight: 600; }
.exportRow {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 2px; border-bottom: .5px solid var(--line);
}
.exportRow:last-child { border-bottom: 0; }
.exportRowBody { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.exportRowName {
  color: var(--accent); font-size: 12.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.exportRowSub { color: var(--muted); font-size: 11.5px; }
/* An export still being built, or one that did not finish, names itself
   where a finished one has its download link. */
.exportRowState { color: var(--text); font-size: 12.5px; }
.exportRowState.failed { color: var(--danger); }
.exportRowState.canceled { color: var(--muted); }
.exportKeep { display: block; margin-top: 8px; color: var(--muted); font-size: 11.5px; }

/* Phone width: the sheet fills the screen and the buttons stack. */
@media (max-width: 460px) {
  .modalBox label.exportFormat { flex: 1 1 100%; }
  .exportBtns .btn { flex: 1 1 100%; justify-content: center; }
  .modalActions { flex-wrap: wrap; }
}

/* ---------- data pages: dataset charts and training runs ---------- */

/* Chart colours are their own tokens rather than the app's accent, because a
   chart is read by telling marks apart and the accent is one colour. Eight
   categorical slots in a fixed order, and the ink and hairlines a chart draws
   in. Light first, then the dark steps — not a flip of the light ones: each
   dark value is stepped to sit on a near-black surface. Declared under both
   dark scopes, as the rest of this file is, so the sidebar's appearance
   control wins in both directions. */
:root {
  --viz-surface: #fcfcfb;
  --viz-ink: #0b0b0b;
  --viz-ink-2: #52514e;
  --viz-muted: #898781;
  --viz-grid: #e1e0d9;
  --viz-axis: #c3c2b7;
  --series-1: #2a78d6;
  --series-2: #eb6834;
  --series-3: #1baf7a;
  --series-4: #eda100;
  --series-5: #e87ba4;
  --series-6: #008300;
  --series-7: #4a3aa7;
  --series-8: #e34948;
  --series-rest: #b4b2a9;       /* "Other" and "unlabeled": everything not named */
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --viz-surface: #1a1a19;
    --viz-ink: #ffffff;
    --viz-ink-2: #c3c2b7;
    --viz-grid: #2c2c2a;
    --viz-axis: #383835;
    --series-1: #3987e5;
    --series-2: #d95926;
    --series-3: #199e70;
    --series-4: #c98500;
    --series-5: #d55181;
    --series-7: #9085e9;
    --series-8: #e66767;
    --series-rest: #5a5955;
  }
}
:root[data-theme="dark"] {
  --viz-surface: #1a1a19;
  --viz-ink: #ffffff;
  --viz-ink-2: #c3c2b7;
  --viz-grid: #2c2c2a;
  --viz-axis: #383835;
  --series-1: #3987e5;
  --series-2: #d95926;
  --series-3: #199e70;
  --series-4: #c98500;
  --series-5: #d55181;
  --series-7: #9085e9;
  --series-8: #e66767;
  --series-rest: #5a5955;
}

/* The app shell pins itself to the viewport for the canvas; these pages are
   documents and scroll. */
body.pageBody { overflow: auto; }

.pageBar {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; gap: 10px;
  min-height: 52px; padding: 7px 16px;
  background: var(--chrome);
  border-bottom: .5px solid var(--line);
}
.pageBar .brandLink { display: inline-flex; align-items: center; gap: 8px; color: var(--text); text-decoration: none; }
.pageBar .brandLink img { display: block; }
.crumbs { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: 13px; color: var(--muted); }
.crumbs a { color: var(--accent); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crumbs a:hover { text-decoration: underline; }
.crumbs b { color: var(--text); font-weight: 600; white-space: nowrap; }
.crumbs .icon { width: 13px; height: 13px; opacity: .6; }
[dir="rtl"] .crumbs .icon { transform: scaleX(-1); }

/* Anchors in a segmented control look like its buttons. */
.segmented a {
  display: inline-flex; align-items: center; gap: 5px;
  border-radius: 999px; padding: 3px 12px;
  font-size: 12.5px; font-weight: 500; color: var(--text); text-decoration: none;
}
.segmented a:hover { background: var(--fill); }
.segmented a.active { background: var(--panel); box-shadow: var(--shadow-1); }

.pageMain {
  max-width: 1440px; margin: 0 auto;
  padding: 20px 16px 48px;
}
.pageHead { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.pageHead h1 { margin: 0; font-size: 22px; font-weight: 600; letter-spacing: -.02em; line-height: 1.3; }
.pageHead .grow { flex: 1 1 200px; }
.pageMeta { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 4px; color: var(--muted); font-size: 12.5px; }
.pageMeta span { white-space: nowrap; }
.pageBadges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 1px 9px; border-radius: 999px;
  background: var(--fill); font-size: 11.5px; font-weight: 500; line-height: 1.7;
}
.pageNote { margin: 0 0 16px; padding: 10px 12px; border-radius: var(--radius); background: var(--fill); color: var(--muted); font-size: 12.5px; }
.pageNote.warn { background: color-mix(in srgb, var(--series-4) 14%, transparent); color: var(--text); }
.pageEmpty { display: flex; flex-direction: column; align-items: center; padding: 64px 16px; text-align: center; color: var(--muted); }
.pageEmpty .emptyIcon {
  display: flex; align-items: center; justify-content: center; width: 52px; height: 52px;
  margin-bottom: 14px; border-radius: 50%; background: var(--fill); color: var(--muted);
}
.pageEmpty .emptyIcon .icon { width: 22px; height: 22px; }
.pageEmpty h2 { margin: 0 0 6px; color: var(--text); font-size: 16px; font-weight: 600; }
.pageEmpty p { margin: 0 auto; max-width: 46ch; }

/* ---- charts ---- */

.chartGrid { display: grid; gap: 16px; grid-template-columns: 1fr; }
@media (min-width: 900px) { .chartGrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.chartGrid.three { gap: 12px; }
@media (min-width: 700px) { .chartGrid.three { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .chartGrid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.vizCard {
  position: relative; min-width: 0;
  background: var(--viz-surface); color: var(--viz-ink);
  border: .5px solid var(--line); border-radius: var(--radius-lg);
  padding: 14px 16px 12px;
}
.vizHead { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 8px; }
.vizHead > div:first-child { flex: 1 1 auto; min-width: 0; }
.vizHead h3 { margin: 0; font-size: 14px; font-weight: 600; letter-spacing: -.01em; }
.vizHead p { margin: 2px 0 0; color: var(--viz-muted); font-size: 11.5px; line-height: 1.5; }
.vizMenuBtn { opacity: 0; transition: opacity .12s; }
.vizCard:hover .vizMenuBtn, .vizCard:focus-within .vizMenuBtn, .vizMenuBtn[aria-expanded="true"] { opacity: 1; }
@media (hover: none) { .vizMenuBtn { opacity: 1; } }
.vizMenu {
  position: absolute; z-index: 6; inset-inline-end: 12px; top: 40px; min-width: 170px;
  padding: 4px; border-radius: var(--radius); background: var(--chrome);
  border: .5px solid var(--line); box-shadow: var(--shadow-2);
}
.vizMenu button {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 10px; border: 0; border-radius: 6px; box-shadow: none; background: transparent;
  font-size: 12.5px; text-align: start; font-weight: 400;
}
.vizMenu button:hover { background: var(--fill); }
.vizMenu .icon { width: 15px; height: 15px; opacity: .75; }
.vizMenu .on::after { content: '✓'; margin-inline-start: auto; }

.vizBody { direction: ltr; min-width: 0; }
.vizBody.withLegend { display: flex; align-items: center; gap: 16px; }
.vizBody.withLegend .vizPlot { flex: 1 1 auto; min-width: 0; }
.vizPlot { min-width: 0; }
.vizPlot svg { display: block; overflow: visible; }
.vizEmpty { display: flex; align-items: center; justify-content: center; min-height: 160px; color: var(--viz-muted); font-size: 12.5px; }

.viz-grid { stroke: var(--viz-grid); stroke-width: 1; shape-rendering: crispEdges; }
.viz-axis { stroke: var(--viz-axis); stroke-width: 1; shape-rendering: crispEdges; }
.viz-tick { fill: var(--viz-muted); font: 10.5px/1 var(--font); }
.viz-axis-title { fill: var(--viz-muted); font: 11px/1 var(--font); }
.viz-mark { transition: opacity .1s; }
.viz-mark.dim { opacity: .45; }
.viz-cursor { stroke: var(--viz-axis); stroke-width: 1; pointer-events: none; }
.viz-guide { stroke: var(--viz-muted); stroke-width: 1; stroke-dasharray: 3 4; opacity: .55; fill: none; }
.viz-guide-label { fill: var(--viz-muted); font: 10px/1 var(--font); }
.viz-hit { fill: transparent; cursor: default; }
.viz-hit:focus-visible { outline: none; stroke: var(--accent); stroke-width: 1.5; }
.viz-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.viz-area { stroke: none; opacity: .1; }
.viz-cell:hover { stroke: var(--viz-ink); stroke-width: 1; }
.viz-center { fill: var(--viz-ink); font: 600 20px/1 var(--font); text-anchor: middle; }
.viz-center-sub { fill: var(--viz-muted); font: 11px/1 var(--font); text-anchor: middle; }

.vizLegend { display: flex; flex-direction: column; gap: 6px; font-size: 12px; min-width: 130px; max-width: 46%; }
.vizLegend.row { flex-direction: row; flex-wrap: wrap; gap: 4px 14px; max-width: none; margin-top: 8px; justify-content: center; }
.vizLegend.scroll { max-height: 200px; overflow-y: auto; padding-inline-end: 6px; }
.vizLegend li { list-style: none; display: grid; grid-template-columns: auto 1fr; column-gap: 8px; align-items: center; }
.vizLegend.row li { display: flex; gap: 6px; }
.vizLegend ul { margin: 0; padding: 0; display: contents; }
.vizLegend .sw { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
.vizLegend .sw.line { height: 2px; width: 14px; border-radius: 1px; }
.vizLegend .sw.dash { background: repeating-linear-gradient(90deg, currentColor 0 4px, transparent 4px 7px) !important; height: 2px; }
.vizLegend .nm { color: var(--viz-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vizLegend .vl { grid-column: 2; color: var(--viz-muted); font-size: 11px; margin-top: -3px; }
.vizLegend.row .vl { display: none; }

.vizTip {
  position: fixed; z-index: 60; pointer-events: none; max-width: 260px;
  padding: 7px 10px; border-radius: var(--radius);
  background: var(--chrome); color: var(--text);
  border: .5px solid var(--line-strong); box-shadow: var(--shadow-2);
  font-size: 12px; line-height: 1.45; direction: ltr; text-align: start;
}
.vizTip .tt { color: var(--muted); font-size: 11px; margin-bottom: 3px; }
.vizTip .tr { display: flex; align-items: center; gap: 8px; }
.vizTip .tk { width: 12px; height: 2px; border-radius: 1px; flex: 0 0 auto; }
.vizTip .tk.box { height: 8px; width: 8px; border-radius: 2px; }
.vizTip b { font-weight: 600; font-variant-numeric: tabular-nums; }
.vizTip .tn { color: var(--muted); }

/* The value table a chart's "View data" opens, and the tables on a run page. */
.dataTable { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.dataTable th, .dataTable td { padding: 6px 10px; text-align: start; border-bottom: .5px solid var(--line); }
.dataTable th { color: var(--muted); font-weight: 500; white-space: nowrap; }
.dataTable td.num, .dataTable th.num { text-align: end; font-variant-numeric: tabular-nums; direction: ltr; }
.dataTable td.mono { font-family: var(--font-mono); font-size: 12px; direction: ltr; text-align: end; }
.dataTable th button.sort {
  display: inline-flex; align-items: center; gap: 4px; padding: 0; border: 0; background: transparent;
  box-shadow: none; color: inherit; font: inherit; border-radius: 4px;
}
.dataTable th button.sort:hover { color: var(--text); background: transparent; }
.dataTable th button.sort .icon { width: 12px; height: 12px; opacity: .5; }
.dataTable th[aria-sort] button.sort .icon { opacity: 1; }
.dataWrap { max-height: 60vh; overflow: auto; }

/* ---- runs ---- */

.tiles { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); margin-bottom: 16px; }
.tile {
  position: relative; overflow: hidden; min-width: 0;
  padding: 12px 14px 10px; border-radius: var(--radius-lg);
  background: var(--viz-surface); color: var(--viz-ink); border: .5px solid var(--line);
}
.tile .tl { color: var(--viz-ink-2); font-size: 12px; }
.tile .tv { margin-top: 2px; font-size: 26px; font-weight: 600; letter-spacing: -.02em; line-height: 1.2; direction: ltr; text-align: start; }
.tile .ts { color: var(--viz-muted); font-size: 11px; margin-top: 2px; }
.tile svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }

.tabsRow { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 4px 0 16px; }

.panel {
  min-width: 0; background: var(--viz-surface); color: var(--text);
  border: .5px solid var(--line); border-radius: var(--radius-lg); padding: 14px 16px;
}
.panel h3 { margin: 0; font-size: 14px; font-weight: 600; }
.panel .sub { margin: 2px 0 12px; color: var(--muted); font-size: 12px; }
.panelHead { display: flex; align-items: flex-start; gap: 10px; }
.panelHead > div { flex: 1 1 auto; min-width: 0; }
.twoCol { display: grid; gap: 16px; grid-template-columns: 1fr; margin-top: 16px; align-items: start; }
@media (min-width: 1000px) { .twoCol { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.infoGrid { display: grid; gap: 12px 24px; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.infoGrid dt { color: var(--muted); font-size: 11.5px; }
.infoGrid dd { margin: 2px 0 0; font-size: 13px; font-weight: 500; overflow-wrap: anywhere; }
.infoGrid .wide { grid-column: 1 / -1; }
.cmd {
  margin: 0; padding: 9px 12px; border-radius: var(--radius-sm); background: var(--fill);
  font: 11.5px/1.6 var(--font-mono); direction: ltr; text-align: start; unicode-bidi: isolate;
  overflow-x: auto; white-space: pre;
}

.tableTools { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.tableTools input[type="search"] {
  flex: 0 1 220px; min-width: 0; padding: 5px 10px; border-radius: var(--radius);
  border: .5px solid var(--line-strong); background: var(--panel); color: var(--text);
  font: inherit; font-size: 12.5px;
}
.tableFoot { display: flex; align-items: center; gap: 10px; margin-top: 10px; font-size: 12px; color: var(--muted); }
.tableFoot select { padding: 2px 8px; font-size: 12px; }
.tableFoot .pager { margin-inline-start: auto; display: flex; align-items: center; gap: 6px; }
.tableFoot button { padding: 3px 10px; font-size: 12px; }

.group { margin-bottom: 16px; }
.groupHead { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.groupHead button.fold {
  display: inline-flex; align-items: center; gap: 6px; padding: 0; border: 0; background: transparent;
  box-shadow: none; font-size: 14px; font-weight: 600;
}
.groupHead button.fold:hover { background: transparent; }
.groupHead .count { color: var(--muted); font-size: 12px; font-weight: 400; }
.groupHead .icon { width: 14px; height: 14px; transition: transform .15s; }
.groupHead button.fold[aria-expanded="false"] .icon { transform: rotate(-90deg); }
[dir="rtl"] .groupHead button.fold[aria-expanded="false"] .icon { transform: rotate(90deg); }
.groupBox { position: relative; }

.runRow {
  display: grid; grid-template-columns: minmax(0, 2fr) repeat(2, minmax(60px, .7fr)) minmax(0, 2fr) minmax(60px, .8fr) 28px;
  align-items: center; gap: 12px; padding: 10px 14px;
  border-bottom: .5px solid var(--line); font-size: 13px;
}
.runRow:last-child { border-bottom: 0; }
.runRow.head { color: var(--muted); font-size: 11.5px; padding-block: 8px; }
.runRow .nm { min-width: 0; }
.runRow .nm a { color: var(--accent); text-decoration: none; font-weight: 500; overflow-wrap: anywhere; }
.runRow .nm a:hover { text-decoration: underline; }
.runRow .nm small { display: block; color: var(--muted); font-size: 11.5px; }
.runRow .n { font-variant-numeric: tabular-nums; direction: ltr; text-align: start; }
@media (max-width: 720px) {
  .runRow { grid-template-columns: 1fr auto; }
  .runRow.head, .runRow .n, .runRow .by { display: none; }
}

.uploadForm { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); align-items: end; }
.uploadForm label { display: block; font-size: 12.5px; color: var(--muted); }
.uploadForm input { width: 100%; margin-top: 5px; padding: 7px 10px; border-radius: var(--radius);
  border: .5px solid var(--line-strong); background: var(--panel); color: var(--text); font: inherit; font-size: 13px; }
.uploadForm small { display: block; margin-top: 3px; font-size: 11px; }
.uploadForm .error { grid-column: 1 / -1; color: var(--danger); font-size: 12.5px; }
.pageMain .panel + .panel { margin-top: 16px; }

@media (max-width: 560px) {
  .pageMain { padding-inline: 16px; }
  .vizBody.withLegend { flex-direction: column; align-items: stretch; }
  .vizLegend { max-width: none; }
}
.vizFoot { margin-top: 6px; text-align: center; color: var(--viz-muted); font-size: 11px; }
.dataTable th[aria-sort="descending"] button.sort .icon { transform: scaleY(-1); }
.groupHead { position: relative; }
.groupHead .vizMenuBtn { opacity: 1; }
.groupHead .vizMenu { inset-inline-end: 0; top: 30px; }

/* The stat tile's curve is a strip across its top, so it never runs through
   the numbers beneath it. */
.tile { padding-top: 42px; }
.tile svg { inset: 0 0 auto 0; height: 34px; }
.tableTools button, .tableFoot button { display: inline-flex; align-items: center; gap: 6px; }
.tableTools .icon { width: 14px; height: 14px; }
.twoCol > .panel + .panel { margin-top: 0; }
.vizPlot { display: flex; justify-content: center; }

/* Narrow screens: the section switcher drops to its own row instead of
   fighting the breadcrumb for the same line. */
@media (max-width: 720px) {
  .pageBar { flex-wrap: wrap; row-gap: 6px; }
  .pageBar .crumbs { flex: 1 1 0; }
  .pageBar .grow { display: none; }
  .pageBar .segmented { order: 5; flex: 1 1 100%; justify-content: center; }
}

/* ---- predict ---- */

.uploadForm select {
  width: 100%; margin-top: 5px; padding: 7px 10px; border-radius: var(--radius);
  border: .5px solid var(--line-strong); background: var(--panel); color: var(--text);
  font: inherit; font-size: 13px;
}
.progressBar { grid-column: 1 / -1; height: 6px; border-radius: 999px; background: var(--fill); overflow: hidden; }
.progressBar i { display: block; height: 100%; width: 0; background: var(--accent); border-radius: 999px; transition: width .4s ease; }
.uploadForm .muted { grid-column: 1 / -1; font-size: 12px; margin-top: -6px; }

.playerLayout { display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr); align-items: start; }
@media (min-width: 1000px) { .playerLayout { grid-template-columns: minmax(0, 1fr) 300px; } }
.playerMain { min-width: 0; display: flex; flex-direction: column; gap: 16px; }
.playerSide { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.playerWrap {
  position: relative; width: 100%; background: #000; border-radius: var(--radius-lg); overflow: hidden;
  border: .5px solid var(--line);
}
.playerWrap video { display: block; width: 100%; height: 100%; object-fit: contain; }
/* Over the picture but under the pointer: the controls stay clickable. */
.playerCanvas { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.rangeRow { display: flex; align-items: center; gap: 10px; }
.rangeRow input[type="range"] { flex: 1 1 auto; accent-color: var(--accent); }
.rangeRow output { min-width: 3ch; font-variant-numeric: tabular-nums; direction: ltr; }
.checkRow { display: flex; align-items: center; gap: 8px; margin: 10px 0; font-size: 13px; }
.classList { display: flex; flex-direction: column; margin-top: 8px; max-height: 320px; overflow-y: auto; }
.classRowP { display: grid; grid-template-columns: auto auto 1fr 3ch 4ch; gap: 8px; align-items: center; padding: 4px 0; font-size: 12.5px; cursor: pointer; }
.classRowP .sw { width: 10px; height: 10px; border-radius: 3px; }
.classRowP .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.classRowP .now, .classRowP .tot { text-align: end; font-variant-numeric: tabular-nums; direction: ltr; }
.classRowP .tot { color: var(--muted); }
.classHead { margin-top: 8px; color: var(--muted); font-size: 11px; }
.viz-playhead { stroke: var(--accent); stroke-width: 1.5; pointer-events: none; }
.uploadForm.top { align-items: start; }
.uploadForm.top > button.primary { margin-top: 24px; }
.vizPlot svg:focus { outline: none; }
.vizPlot svg:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }

/* Attributes (§3.2.1): the label editor's fields and the schema sheet. */
.leAttrs { display: grid; gap: 6px; margin: 6px 0; }
.leAttr { display: grid; grid-template-columns: minmax(0, 40%) 1fr; gap: 6px; align-items: center;
  font-size: 12px; }
.leAttr.missing span { color: var(--danger); }
.leAttr textarea { resize: vertical; min-height: 1.8em; font: inherit; }
.leAttr .invalid, .leAttr select.invalid { outline: 1.5px solid var(--danger); }
.lyRow.lyMissing .apName { color: var(--danger); }
.attrRows { display: grid; gap: 10px; margin: 8px 0; max-height: 55vh; overflow: auto; }
.attrRow { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: end; padding: 8px;
  border: 1px solid var(--line, rgba(128,128,128,.3)); border-radius: 8px; }
.attrRow label { display: grid; gap: 2px; font-size: 11.5px; min-width: 0; }
.attrRow label[hidden] { display: none; }
.attrRow input[type=text], .attrRow textarea { width: 12em; max-width: 100%; }
.attrRow input[type=number] { width: 6em; }
.attrError { text-align: start; color: var(--danger); }
/* The keypoint schema sheet (§3.4.1). */
.kptField { display: grid; gap: 4px; margin: 8px 0; font-size: 12px; }
.kptField textarea { width: 100%; font-family: inherit; }
.kptField input[type=number] { width: 6em; }
.kptPick { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.kptPick[hidden] { display: none; }
.kptList { display: flex; flex-wrap: wrap; gap: 4px; }
.kptList .chip { display: inline-flex; gap: 4px; align-items: center; padding: 1px 6px;
  border: 1px solid var(--line, rgba(128,128,128,.3)); border-radius: 10px; font-size: 11.5px; }

/* The open source licences page: one readable column. */
.licences { max-width: 760px; margin: 0 auto; padding: 32px 16px 64px; line-height: 1.6; }
.licences h2 { margin-top: 32px; }
.licences code { overflow-wrap: anywhere; }
.licenceFacts { color: var(--muted); font-size: 14px; }

/* The Smart tool's line: where the model runs, and where it comes from. */
.samWhere { color: var(--muted); font-size: 12px; white-space: nowrap; }
.samCredit { color: var(--muted); font-size: 12px; white-space: nowrap; }
.samProgress { width: 100%; height: 8px; }

/* The Smart tool with no path to run on: still there, plainly unavailable. */
#samMode.unavailable { opacity: .45; cursor: not-allowed; }
