/* Copyright (c) 2026 Nicholas Verzilli, For Man or Beast. All rights reserved.
   Proprietary and confidential. */

[hidden] { display: none !important; }  /* the hidden attribute must win over display rules */

:root {
  --bg: #0b0d11;
  --bg-soft: #0f1218;
  --panel: #161920;
  --panel-2: #1b1f27;
  --hover: #20242d;
  --border: #262b34;
  --border-soft: #1d2129;
  --text: #eef1f5;
  --text-dim: #a3a9b3;
  --text-faint: #7a8292;         /* 4.55:1 on --panel, 5.03:1 on --bg - the AA floor. Was
                                    #6b7280 (3.64:1), which failed on every surface while
                                    carrying the search placeholder and 100+ other strings. */
  --accent: #6e8bff;
  --accent-hi: #8aa0ff;          /* hover/active lift on an accent surface */
  --accent-dim: #4d63cc;
  --accent-soft: rgba(110, 139, 255, 0.14);
  --accent-band: rgba(110, 139, 255, 0.28);   /* selected range over a dark track */
  --danger: #ff6363;
  --danger-soft: rgba(255, 99, 99, 0.12);
  --danger-text: #f08a8a;        /* danger as READING text, where the pure hue is harsh */
  --success: #36d399;
  --success-soft: rgba(54, 211, 153, 0.13);
  --success-text: #5cd68a;       /* success as reading text */
  --warning: #ffb454;
  --warning-soft: rgba(255, 180, 84, 0.13);
  --warning-text: #e8b45a;       /* warning as reading text */
  /* "this piece is in a cut" - a fourth hue on purpose. Amber and green on the trim track
     already mean asked-for and delivered, and blue is the selection; usage is a different
     KIND of fact from all three (something that happened in the edit, not a transfer), so
     it gets a colour nobody has to disambiguate. */
  --usage: #c07ce8;
  --usage-soft: rgba(192, 124, 232, 0.14);
  --usage-text: #cd97ec;
  /* Media-type accents for file icons. Named by role, not by hue, so a folder and a
     warning pill can share a colour without pretending to mean the same thing. */
  --media-folder: #ffc56e;
  --media-audio: #c97bff;
  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius-ms: 8px;              /* the most common hardcoded radius; now it has a name */
  --radius-md: 10px;
  --radius-lg: 16px;
  --radius-pill: 999px;
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.45);
  /* Motion. Things arriving decelerate (ease-out); things leaving accelerate away (ease-in).
     Keep new animations on these so the app moves as one piece. */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in: cubic-bezier(0.55, 0, 1, 0.45);
  --dur-fast: 120ms;
  --dur-med: 200ms;
  --dur-slow: 360ms;
}

/* One switch for every animation in the app. Collapsing durations (rather than removing the
   animations) keeps "forwards" end states and animationend listeners working. Delays are left
   alone on purpose: the frame "Loading" label waits 150ms so a fast load never flashes it. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: "Inter", -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  background-image:
    radial-gradient(circle at 12% 0%, rgba(110, 139, 255, 0.07), transparent 45%),
    radial-gradient(circle at 100% 10%, rgba(54, 211, 153, 0.04), transparent 40%);
  color: var(--text);
  font-size: 14px;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #262b34; border-radius: 99px; border: 2px solid transparent; }
::-webkit-scrollbar-thumb:hover { background: #323845; }

h1, h2, h3 { font-family: inherit; }

/* ---------- icons ---------- */

.icon {
  width: 16px;
  height: 16px;
  flex: none;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -3px;
}
.icon-lg { width: 22px; height: 22px; }
.icon-xl { width: 30px; height: 30px; }

/* ---------- header ---------- */

header.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 22px;
  height: 58px;
  border-bottom: 1px solid var(--border-soft);
  background: rgba(11, 13, 17, 0.7);
  backdrop-filter: blur(10px);
  position: relative;
  z-index: 10;
}

.brand { display: flex; align-items: center; gap: 10px; }

.brand-mark {
  width: 30px;
  height: 30px;
  border-radius: 9px;
  background: #4f2bd4;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 10px rgba(79, 43, 212, 0.4);
}
.brand-mark svg, .brand-mark-svg { width: 18px; height: 18px; fill: #fff; stroke: none; }

.brand-name { font-size: 15px; font-weight: 700; letter-spacing: 0.2px; }
.brand-tag { font-size: 11px; color: var(--text-faint); margin-left: 8px; padding-left: 8px; border-left: 1px solid var(--border); }

.topbar-right { display: flex; align-items: center; gap: 14px; }

.ws-select-wrap { position: relative; }
.ws-select-wrap select {
  appearance: none;
  -webkit-appearance: none;
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--text);
  font-weight: 500;
  font-size: 13px;
  border-radius: 999px;
  padding: 7px 30px 7px 14px;
  margin: 0;
  cursor: pointer;
}
.ws-select-wrap::after {
  content: "";
  position: absolute;
  right: 12px;
  top: 50%;
  width: 6px;
  height: 6px;
  border-right: 1.5px solid var(--text-faint);
  border-bottom: 1.5px solid var(--text-faint);
  transform: translateY(-65%) rotate(45deg);
  pointer-events: none;
}

.user-chip { display: flex; align-items: center; gap: 8px; background: none; border: 1px solid transparent;
  padding: 3px 8px 3px 4px; border-radius: 999px; cursor: pointer; }
.user-chip:hover { background: var(--panel-2); border-color: var(--border); }
.user-chip .chev { width: 13px; height: 13px; opacity: 0.6; }
.avatar {
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 700;
  font-size: 11px;
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
}
.avatar.lg { width: 38px; height: 38px; font-size: 14px; }
.user-name { font-size: 13px; color: var(--text-dim); }

/* Account dropdown */
.account-menu {
  position: fixed; z-index: 220; min-width: 260px;
  background: rgba(22, 25, 32, 0.92);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.5); overflow: hidden;
}
.am-head { display: flex; align-items: center; gap: 10px; padding: 14px; border-bottom: 1px solid var(--border-soft); }
.am-name { font-weight: 600; font-size: 13.5px; }
.am-email { font-size: 12px; color: var(--text-faint); }
.am-plan { padding: 10px 14px; border-bottom: 1px solid var(--border-soft); display: flex; flex-direction: column; gap: 4px; }
.am-plan-badge { align-self: flex-start; font-size: 11px; font-weight: 600; color: var(--accent);
  background: var(--accent-soft); border-radius: 999px; padding: 2px 9px; }
.am-plan-note { font-size: 11px; color: var(--text-faint); }
.am-items { padding: 6px; }
.am-item { display: flex; align-items: center; gap: 9px; width: 100%; background: none; border: none;
  color: var(--text); font-size: 13px; font-family: inherit; text-align: left; padding: 9px 10px;
  border-radius: var(--radius-sm); cursor: pointer; }
.am-item:hover { background: var(--hover); }
.am-item svg { width: 15px; height: 15px; flex-shrink: 0; opacity: 0.85; }
.am-signout { color: var(--danger); }
/* The signed-out identity, in the chip and at the top of the menu. Deliberately quiet
   rather than alarming: not having a VAMP Cloud account is a supported way to run this
   app forever, not an error state. It just has to be legible, because the whole point of
   the change is that the app used to answer "who am I" with a made-up address. */
.user-chip.signed-out .user-name { color: var(--text-faint); }
.user-chip.signed-out .avatar { background: var(--panel-2); color: var(--text-faint); }
.user-chip .avatar svg { width: 14px; height: 14px; stroke: currentColor; }
.avatar.am-anon { background: var(--panel-2); color: var(--text-faint); }
.avatar.am-anon svg { width: 18px; height: 18px; stroke: currentColor; }
.am-signin { color: var(--accent); font-weight: 600; }

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

.layout { display: flex; height: calc(100vh - 58px); }

.sidebar {
  width: 290px;
  flex: none;
  padding: 18px 16px;
  overflow-y: auto;
  background: var(--bg-soft);
}

.main { flex: 1; display: flex; overflow: hidden; min-width: 0; }

/* The browser is the elastic pane: sidebar and detail hold their set widths and this
   takes the rest, so a wider window means more file list, not more chrome. */
.browser {
  flex: 1 1 auto;
  min-width: 0;
  padding: 20px 22px;
  overflow-y: auto;
}

/* Shrinkable, not fixed. At 520px hard-pinned, sidebar+detail claimed 810px of any window,
   so at 1280 the file list got ~470px and its Name column was squeezed to nothing. It gives
   ground down to 340px (the preview still holds its shape there) before the list has to. */
.detail {
  flex: 0 1 520px;
  min-width: 340px;
  padding: 20px 22px;
  overflow-y: auto;
}

/* The detail pane's width is set INLINE by applyPanePrefs() (it's drag-resizable), so the
   clamp that stops it starving the file list lives there, not here - a media query on
   .detail is silently overridden by the inline style. */

/* Draggable divider. Visually a 1px rule (it replaces the old borders); the hit area is
   wider than the line because a 1px grab target is a game of skill, not a UI. */
.pane-grip {
  flex: none;
  width: 7px;
  margin: 0 -3px;          /* overlap the neighbours so it costs no layout space */
  cursor: col-resize;
  position: relative;
  z-index: 5;
  background: none;
  touch-action: none;      /* pointer events only - don't let it scroll the pane */
}
.pane-grip::before {
  content: "";
  position: absolute; top: 0; bottom: 0; left: 3px;
  width: 1px;
  background: var(--border-soft);
  transition: background 0.12s ease, box-shadow 0.12s ease;
}
.pane-grip:hover::before, .pane-grip.dragging::before {
  background: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-soft);
}
/* While dragging, stop the cursor flickering and text getting selected mid-drag. */
body.resizing { cursor: col-resize; user-select: none; }
body.resizing * { cursor: col-resize !important; }

/* Collapsed is a RAIL, not gone. The control that expands it again lives inside the
   sidebar, so hiding the sidebar outright would strand it (Nick worked this out before I
   did). Icons stay; labels, cards and section headings go. */
/* The shelf list. The dot means "that drive is plugged in now" - the one moment the
   request stops being a note to self and becomes something VAMP can act on. */
.wanted-btn { position: relative; margin-bottom: 6px; }
.wanted-btn[hidden] { display: none; }
.wanted-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--success);
  margin-left: auto; flex: none;
}
.layout.sidebar-collapsed .wanted-btn span:not(.wanted-dot) { display: none; }

.sidebar-top { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.sidebar-top .sidebar-home { flex: 1; margin: 0; }
.sidebar-collapse {
  flex: none; width: 28px; height: 28px; padding: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  color: var(--text-faint); cursor: pointer;
}
.sidebar-collapse:hover { background: var(--hover); color: var(--text); }
.sidebar-collapse .chev-collapse { width: 15px; height: 15px; transform: rotate(180deg); transition: transform 0.15s ease; }

.layout.sidebar-collapsed .sidebar {
  width: 52px !important; padding: 18px 8px; overflow: hidden;
}
.layout.sidebar-collapsed .sidebar-top { flex-direction: column; gap: 8px; }
.layout.sidebar-collapsed .sidebar-home span,
.layout.sidebar-collapsed .section-label,
.layout.sidebar-collapsed details.section-collapsible,
.layout.sidebar-collapsed .folder-card,
.layout.sidebar-collapsed .cloud-item,
.layout.sidebar-collapsed .new-catalog-btn,
.layout.sidebar-collapsed .sidebar .link-btn { display: none; }
.layout.sidebar-collapsed .sidebar-home { justify-content: center; padding: 8px; }
.layout.sidebar-collapsed .sidebar-collapse .chev-collapse { transform: none; }
.sidebar-toggle-btn { margin-right: 10px; }
.sidebar-toggle-btn { display: none; }   /* phones only - see the @media block at the end */

/* ---------- section headers ---------- */

.section-label {
  font-size: 11px;
  font-weight: 700;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.8px;
  margin: 22px 0 10px;
  display: flex;
  align-items: center;
  gap: 6px;
}
.section-label:first-child { margin-top: 0; }

/* collapsible section headers (Drives / Virtual clones) */
details.section-collapsible { margin: 18px 0 4px; }
details.section-collapsible > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.8px;
  padding: 6px 4px;
  cursor: pointer;
  list-style: none;
  border-radius: var(--radius-sm);
}
details.section-collapsible > summary::-webkit-details-marker { display: none; }
details.section-collapsible > summary:hover { color: var(--text-dim); }
details.section-collapsible > summary .sec-title { display: flex; align-items: center; gap: 6px; flex: 1; }
details.section-collapsible > summary .chev { transition: transform 0.15s ease; flex: none; }
details.section-collapsible[open] > summary .chev { transform: rotate(90deg); }
details.section-collapsible > summary .link-btn { text-transform: none; letter-spacing: 0; font-size: 11px; }
/* Laid out rather than margined: the New Catalog button and the catalog list are siblings
   here, and .attached-list's gap only spaces the catalogs from EACH OTHER - so the button
   sat flush against the first one. A gap on the container spaces every child evenly and
   can't collapse or double up. */
.section-body { padding: 4px 0 0; display: flex; flex-direction: column; gap: 8px; }

h2.panel-title {
  font-size: 16px;
  font-weight: 700;
  margin: 0 0 14px;
  display: flex;
  align-items: center;
  gap: 8px;
}
h2.panel-title .muted-count { font-size: 12px; font-weight: 500; color: var(--text-faint); }
/* Search results: the way out, on the same line as what you're looking at. The tree
   header and its Back button aren't drawn in this view, so this is the only one. */
.search-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.search-head h2.panel-title { margin: 0; }
.search-head .back-btn { flex: none; }
.search-head .back-btn .chev-back { width: 14px; height: 14px; transform: rotate(180deg); }

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

.card {
  background: var(--panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 16px;
  margin-bottom: 14px;
}
.card-title {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-dim);
  margin: 0 0 10px;
  display: flex;
  align-items: center;
  gap: 6px;
}
/* Per-card reorder + hide controls (#68) — appear on hover so they stay quiet. */
.card-ctl { margin-left: auto; display: flex; align-items: center; gap: 4px; opacity: 0; transition: opacity 0.12s; }
.card:hover .card-ctl { opacity: 0.75; }
/* ...and when something inside is keyboard-focused. These are tabbable, so without this you
   could tab onto a hide button that was invisible at opacity 0 and not know what you'd hit. */
.card:focus-within .card-ctl { opacity: 0.75; }
/* Sits under the catalog list now, and reads as the secondary action it is. */
/* No margin: .section-body's gap already spaces it from the catalog list. This was a
   margin-TOP, added to hold the button off the list when it sat underneath one - now that
   it sits first, that margin only pushed it away from its own heading. */

.card-grip { cursor: grab; color: var(--text-faint); font-size: 13px; letter-spacing: -2px; }
/* 15x15 was a game of skill. The glyph is unchanged; the hit area is now 24x24. */
.card-hide { background: none; border: none; color: var(--text-faint); cursor: pointer;
  font-size: 15px; line-height: 1; padding: 0;
  min-width: 24px; min-height: 24px;
  display: inline-flex; align-items: center; justify-content: center; }
.card-hide:hover { color: var(--danger); }
.card.dragging { opacity: 0.5; outline: 1px dashed var(--accent); }
.hidden-cards-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  font-size: 12px; padding: 8px 4px; color: var(--text-faint); }

input:not([type="checkbox"]):not([type="radio"]), textarea, select {
  width: 100%;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  margin-bottom: 8px;
  font-size: 13px;
  font-family: inherit;
  transition: border-color 0.15s ease;
}

/* A select kept the OS's own chevron, which is why the Speed control read as a stray native
   widget among custom ones. Draw our own in the app's palette instead. */
select {
  appearance: none; -webkit-appearance: none;
  padding-right: 26px;
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a3a9b3' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 7px center;
  background-size: 13px 13px;
}
input::placeholder, textarea::placeholder { color: var(--text-faint); }
input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

button {
  background: var(--accent);
  color: #0a0c10;
  border: none;
  border-radius: var(--radius-sm);
  padding: 8px 14px;
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: filter 0.15s ease, transform 0.05s ease;
}
button:hover { filter: brightness(1.08); }
button:active { transform: translateY(1px); }
button.secondary { background: var(--panel-2); color: var(--text); border: 1px solid var(--border); }
button.secondary:hover { background: var(--hover); }
button.danger-ghost { background: transparent; color: var(--text-faint); padding: 6px 8px; }
button.danger-ghost:hover { color: var(--danger); background: var(--danger-soft); }
button:disabled { opacity: 0.5; cursor: default; }
button.full { width: 100%; justify-content: center; }
button.icon-btn { padding: 7px; }

.input-icon-wrap { position: relative; }
/* top/bottom:0 + margin:auto centres against the INPUT's real height. top:50% plus a
   translate depended on the icon's own box, and an inline svg carries baseline spacing,
   so the magnifier sat low and the clear X sat high in the same box. */
.input-icon-wrap .icon {
  position: absolute; left: 10px; top: 0; bottom: 0; margin: auto 0;
  display: block; color: var(--text-faint);
}
/* :not() keeps this above the global input rule's specificity so the icon padding wins. */
.input-icon-wrap input:not([type="checkbox"]) { padding-left: 34px; }
/* Clear-the-search X. Only present while there's something to clear. */
.search-clear {
  position: absolute; right: 6px; top: 0; bottom: 0; margin: auto 0;
  width: 22px; height: 22px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: 50%;
  color: var(--text-dim); font-size: 16px; line-height: 1; cursor: pointer;
}
.search-clear:hover { background: var(--hover); color: var(--text); }
.search-clear[hidden] { display: none; }
.topbar-search .input-icon-wrap input { padding-right: 32px; }

/* ---------- show/hide a password ---------- */
/* A password you can't read is how people mistype one into a link that only works once.
   Same absolute-centring trick as .search-clear above, and the same trap: the margin has to
   live on the wrapper, because top/bottom:0 + margin:auto centres against the wrapper's
   height and a margin-bottom left on the input parks the eye a few pixels low. */
.pw-wrap { position: relative; display: block; margin-bottom: 10px; }
.pw-wrap[hidden] { display: none; }
.pw-wrap input:not([type="checkbox"]) { margin-bottom: 0; padding-right: 38px; }
.pw-eye {
  position: absolute; right: 6px; top: 0; bottom: 0; margin: auto 0;
  width: 26px; height: 26px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: var(--radius-sm);
  color: var(--text-faint); cursor: pointer;
}
.pw-eye:hover { color: var(--text); background: var(--hover); }
.pw-eye .icon { width: 16px; height: 16px; }

/* ---------- settings modal ---------- */

.modal-overlay {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(0, 0, 0, 0.55);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.modal {
  width: 100%; max-width: 560px; max-height: 86vh;
  display: flex; flex-direction: column;
  background: var(--panel-2, #14171d);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg, 12px);
  box-shadow: 0 16px 50px rgba(0, 0, 0, 0.5);
}
.modal-head, .modal-foot {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px;
}
.modal-head { border-bottom: 1px solid var(--border-soft); font-weight: 700; font-size: 14px; gap: 6px; }
.modal-head > span { display: flex; align-items: center; gap: 7px; }
.modal-foot { border-top: 1px solid var(--border-soft); gap: 12px; }
.modal-foot button.full { flex: 0 0 auto; width: auto; padding: 8px 16px; }
.modal-foot .set-status { font-size: 12px; color: var(--text-dim); flex: 1; }
/* Cancel sits next to Save rather than as a "Close" link in the far corner, which is the
   Mac arrangement and puts leaving and committing in the same place instead of opposite
   ends of the window. */
.modal-foot button.secondary { flex: 0 0 auto; width: auto; padding: 8px 16px; }
/* Saving keeps the window open, so the button itself has to report what happened. Green
   until the next edit, at which point it goes back to offering the save. */
.modal-foot button.saved { background: var(--success); border-color: var(--success); color: #06281c; }
.modal-body { overflow-y: auto; padding: 8px 18px 18px; }

/* The long explanations live behind these. Several panels opened with three or four
   sentences arguing for a feature, aimed at someone who had already decided to use it and
   standing between them and the fields they came to fill in. The argument is still worth
   having - just not every time, and not first. */
.set-help { margin-top: 6px; }
.set-help > summary { cursor: pointer; color: var(--accent); list-style: none; width: fit-content; }
.set-help > summary::-webkit-details-marker { display: none; }
.set-help > summary::after { content: " ›"; display: inline-block; transition: transform .12s ease; }
.set-help[open] > summary::after { transform: rotate(90deg); }
.set-help > summary:hover { color: var(--accent-hi); }
.set-help p { margin: 8px 0 0; }

.set-section { margin-top: 16px; }
.set-section h3 {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--text-faint); margin: 0 0 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border-soft);
}
.set-row { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-bottom: 10px; font-size: 13px; }
.set-row > span { color: var(--text-dim); }
.set-row .set-hint { color: var(--text-faint); font-size: 11px; }
.set-row input[type="number"], .set-row input[type="text"], .set-row input[type="password"] { width: 220px; }
/* The eye flips type to "text", which drops the input out of the selector above. Carry the
   column width on the wrapper so the box doesn't snap to full width mid-click. */
.set-row .pw-wrap { width: 220px; }
.set-row .pw-wrap input { width: 100%; }
.set-row.col { flex-direction: column; align-items: stretch; gap: 5px; }
.set-row.col textarea { width: 100%; font-size: 12.5px; }
.set-row.checkbox { justify-content: flex-start; gap: 8px; cursor: pointer; }
.set-row.checkbox input { accent-color: var(--accent); cursor: pointer; }
.set-row.checkbox > span { color: var(--text); }

/* ---------- LTO archive dialog ---------- */
.lto-modal { max-width: 600px; width: calc(100vw - 32px); }
/* Preview quality chooser: five levels side by side, each a sample of the same moment. */
.pq-modal { max-width: 1120px; width: calc(100vw - 32px); }
.pq-modal .modal-body { overflow-y: auto; }
.pq-summary { font-size: 13px; color: var(--text); margin-bottom: 10px; }
.pq-try { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 6px; }
.pq-try select { min-width: 0; max-width: 100%; flex: 1 1 260px; }
.pq-status { min-height: 18px; margin-bottom: 8px; }
.pq-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 10px; }
.pq-card {
  display: flex; flex-direction: column; gap: 6px; padding: 8px;
  border: 1px solid var(--border-soft); border-radius: var(--radius-md, 10px); cursor: pointer;
}
.pq-card.picked { border-color: var(--accent); background: var(--accent-soft); }
.pq-view {
  position: relative; aspect-ratio: 16 / 9; background: #000; border-radius: 6px;
  overflow: hidden; display: flex; align-items: center; justify-content: center; cursor: ew-resize;
}
.pq-view video { width: 100%; height: 100%; object-fit: contain; display: block; }
.pq-wait { font-size: 11.5px; color: var(--text-faint); }
.pq-failed { color: var(--danger-text); }
.pq-name { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; font-size: 13px; }
.pq-spec { font-size: 11.5px; color: var(--text-dim); }
.pq-stats { display: flex; flex-direction: column; gap: 2px; font-size: 11.5px; color: var(--text-dim);
  font-variant-numeric: tabular-nums; }
.pq-cost { color: var(--usage-text); font-weight: 600; }
.pq-note { margin-top: 10px; }
.lto-summary { font-size: 13px; color: var(--text); margin-bottom: 10px; }
.lto-tapes { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.lto-tape { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 12.5px;
  padding: 8px 10px; border: 1px solid var(--border-soft); border-radius: 6px; }
.lto-name { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; color: var(--text); }
.lto-meta { color: var(--text-faint); }
.lto-state { margin-left: auto; color: var(--text-dim); }
.lto-verified .lto-state { color: var(--success-text); }
.lto-unproven .lto-state { color: var(--warning); }
.lto-problem .lto-state { color: var(--danger-text); }
.lto-section { font-size: 12px; font-weight: 600; color: var(--text-dim); margin: 4px 0 8px;
  text-transform: uppercase; letter-spacing: 0.04em; }
.lto-picks { display: flex; flex-direction: column; gap: 4px; margin-bottom: 6px; }
.lto-pick { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text); }
.lto-note { font-size: 12px; color: var(--text-faint); line-height: 1.5; margin-top: 10px; }

/* ---------- update catalog: confirm WHERE before re-reading ---------- */
.uc-modal { max-width: 560px; }
.uc-label { font-size: 13px; color: var(--text-dim); margin-bottom: 10px; }
.uc-label strong { color: var(--text); }
.uc-pathrow { display: flex; align-items: center; gap: 8px; }
.uc-pathrow .tc-input { flex: 1; margin: 0; font-size: 12.5px; }
.uc-pathrow button { flex: 0 0 auto; }
.uc-check { font-size: 12px; margin-top: 8px; min-height: 16px; color: var(--text-faint); }
.uc-check.ok { color: var(--success-text); }
.uc-check.bad { color: var(--danger-text); }
.uc-note { font-size: 12px; color: var(--text-faint); line-height: 1.5; margin-top: 12px;
  padding-top: 12px; border-top: 1px solid var(--border-soft); }

/* ---------- folder picker ---------- */
.fp-modal { max-width: 520px; }
.fp-crumbs { display: flex; align-items: center; gap: 10px; padding: 10px 18px;
  border-bottom: 1px solid var(--border-soft); min-height: 44px; }
.fp-path { font-size: 12px; color: var(--text-dim); overflow-wrap: anywhere; }
/* There is no left chevron in the icon set, so the right one is turned round. */
.chev-up { transform: rotate(180deg); }
.fp-list { height: 300px; overflow-y: auto; scrollbar-gutter: stable; padding: 6px; }
.fp-row { display: flex; align-items: center; gap: 9px; width: 100%; margin: 0;
  background: none; border: none; color: var(--text); font-size: 13px; text-align: left;
  padding: 9px 10px; border-radius: var(--radius-sm); cursor: pointer; }
.fp-row:hover { background: var(--hover); }
.fp-row span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fp-row .icon:last-child { color: var(--text-faint); }
.fp-current { font-size: 12px; color: var(--text-faint); flex: 1; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }

/* ---------- settings: left category nav + one page at a time ---------- */
.set-modal { max-width: 700px; }
/* FIXED height, not min/max: the dialog used to size itself to whichever category was
   open, so every click on the left made the whole window jump. It now holds one size and
   the right-hand pane scrolls (Nick: "it should stay the same size and the righthand side
   should be scrollable"). Capped against the viewport so it still fits a small screen. */
.set-layout { display: flex; height: min(68vh, 520px); overflow: hidden; }
.set-nav {
  flex: 0 0 160px; border-right: 1px solid var(--border-soft);
  padding: 10px 8px; display: flex; flex-direction: column; gap: 2px; overflow-y: auto;
}
.set-nav-item {
  text-align: left; background: none; border: none; color: var(--text-dim);
  padding: 9px 12px; border-radius: var(--radius-sm); font-size: 13px; cursor: pointer;
  width: 100%; margin: 0; font-family: inherit;
}
.set-nav-item:hover { background: var(--hover); color: var(--text); }
.set-nav-item.active { background: var(--accent-soft); color: var(--accent); font-weight: 500; }
/* scrollbar-gutter, not just overflow: the dialog's outer size is already fixed, but a
   category tall enough to scroll took ~15px off the pane's usable width and a short one
   gave it back, so every field on the right jumped sideways as you clicked down the list.
   Reserving the gutter always means the content sits in the same place either way. */
.set-pane-wrap { flex: 1; min-width: 0; overflow-y: auto; scrollbar-gutter: stable;
  padding: 16px 20px; }
.set-pane { display: none; }
.set-pane.active { display: block; }
.set-pane-title { font-size: 15px; font-weight: 500; color: var(--text); margin: 0 0 4px;
  display: flex; align-items: center; gap: 9px; }
/* Which features spend money. Only AI keywording does, by default. */
.cost-badge {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px;
  padding: 2px 7px; border-radius: 999px; white-space: nowrap;
}
.cost-badge.free { color: var(--success); background: rgba(57, 224, 138, 0.12); }
.cost-badge.mixed { color: var(--success); background: rgba(57, 224, 138, 0.12); }
.cost-badge.paid { color: #e6a15c; background: rgba(230, 161, 92, 0.14); }
.set-pane-intro { font-size: 12.5px; color: var(--text-dim); line-height: 1.5; margin-bottom: 12px; }

.set-field {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 11px 0; border-bottom: 1px solid var(--border-soft);
}
.set-field:last-child { border-bottom: none; }
.set-field-l { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.set-field-name { font-size: 13px; color: var(--text); }
.set-field-hint { font-size: 11px; color: var(--text-faint); line-height: 1.4; }
.set-field-c { flex: 0 0 auto; }
.set-modal .set-field-c input, .set-modal .set-field-c select { width: 172px; margin: 0; }
.set-modal .set-field-c .pw-wrap { width: 172px; }
.set-modal .set-field-c .pw-wrap input { width: 100%; }
/* The Updates row on the About pane: a sentence and (sometimes) a button, both sitting in
   the value column next to the version they're talking about. Right-aligned and capped in
   width so a long line wraps into the cell instead of shoving the label off the left. */
.set-field-upd { max-width: 60%; text-align: right; }
.set-field-upd button { margin-left: auto; }

.set-field.wide { flex-direction: column; align-items: stretch; }
.set-modal .set-field.wide textarea { width: 100%; margin: 8px 0 0; }
.set-toggle {
  display: flex; align-items: flex-start; gap: 10px; padding: 11px 0;
  border-bottom: 1px solid var(--border-soft); cursor: pointer; margin: 0;
}
.set-toggle input { margin: 2px 0 0; width: auto; accent-color: var(--accent); cursor: pointer; flex: 0 0 auto; }
.set-toggle-txt { display: flex; flex-direction: column; gap: 2px; }

/* ---------- topbar search ---------- */

.topbar-search {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  padding: 0 24px;
  position: relative;
}
.topbar-search .input-icon-wrap { width: 100%; max-width: 520px; }
.topbar-search .input-icon-wrap .icon { cursor: pointer; }
.topbar-search input { width: 100%; }
#search-filter-btn { flex: 0 0 auto; }

.search-facets {
  position: absolute;
  top: calc(100% + 6px);
  right: 24px;
  z-index: 400;
  width: 320px;
  background: var(--panel, #1b1f2a);
  border: 1px solid var(--border, #2b3240);
  border-radius: 10px;
  padding: 14px 16px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
}
.search-facets .facet-title { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-faint); margin: 4px 0 8px; }
.search-facets .facet-scopes { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px; margin-bottom: 12px; }
.search-facets .facet-scopes label { display: flex; align-items: center; gap: 7px; font-size: 13px; cursor: pointer; }
.search-facets .facet-scopes input[type="checkbox"] { width: auto; margin: 0; }
.search-facets .facet-status { width: 100%; padding: 5px 8px; font-size: 12px; margin-bottom: 12px; }
.search-facets .facet-dates { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.search-facets .facet-dates input[type="date"] { flex: 1; min-width: 0; padding: 5px 8px; font-size: 12px; }
.search-facets .facet-dates span { font-size: 12px; color: var(--text-dim); }
.search-facets .facet-actions { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.search-facets .facet-hint { font-size: 11px; color: var(--text-faint); line-height: 1.5; }
.search-facets .facet-hint code { background: rgba(255, 255, 255, 0.07); padding: 1px 4px; border-radius: 3px; }

/* ---------- sidebar home ---------- */

.sidebar-home {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  justify-content: flex-start;
  padding: 9px 12px;
  margin-bottom: 10px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.sidebar-home:hover { background: var(--panel); color: var(--text); }
.sidebar-home.active { background: var(--accent-soft); color: var(--accent); border-color: rgba(110, 139, 255, 0.35); }
.sidebar-home .icon { width: 18px; height: 18px; }

/* ---------- attached drives list ---------- */

.section-label-row { display: flex; align-items: center; justify-content: space-between; }
.section-label-row > span { display: flex; align-items: center; gap: 6px; }

/* margin-bottom removed with the same reasoning: the list is the last thing in the section
   now, so it was trailing empty space under the fold. */
.attached-list { display: flex; flex-direction: column; gap: 6px; }
.ext-filter { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--text-faint); cursor: pointer; padding: 4px 2px 8px; }
.ext-filter input { cursor: pointer; accent-color: var(--accent); }
.attached-item {
  padding: 9px 10px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-soft);
  background: var(--panel);
}
.attached-item.unavailable { opacity: 0.6; }
/* The drive you're currently browsing. Only cataloged rows can be active. */
.attached-item.active {
  border-color: var(--accent);
  background: var(--accent-soft, rgba(110, 139, 255, 0.1));
}
.attached-item .attached-top { display: flex; align-items: center; gap: 10px; }
.attached-item .drive-icon {
  width: 30px; height: 30px;
  border-radius: var(--radius-sm);
  background: var(--panel-2);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-dim); flex: none;
}
.attached-item .drive-info { flex: 1; min-width: 0; }
.attached-item .label { font-weight: 600; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.attached-item .label.linkable { cursor: pointer; color: var(--accent); }
.attached-item .label.linkable:hover { text-decoration: underline; }
.attached-item .sub { color: var(--text-faint); font-size: 11px; margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.attached-item.unavailable .sub { color: var(--warning); }
/* One tidy row of actions per drive: [main button] [+ previews] ... [Rename].
   These used to stack (full-width button, wrapping checkbox, centered Rename link) -
   three lines of chrome per drive that made the list twice as tall as its content. */
.attached-item .attached-actions { margin-top: 8px; display: flex; align-items: center;
  gap: 10px; flex-wrap: wrap; }
.attached-item .attached-actions button.secondary { flex: 1 1 auto; justify-content: center; }
.drive-rename { margin-left: auto; }
.attached-item .attached-status { font-size: 11px; margin-top: 6px; }
.attached-item .attached-status:empty { display: none; }

/* ---------- drive list ---------- */

.drive-item {
  cursor: pointer;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  margin-bottom: 6px;
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid transparent;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.drive-item:hover { background: var(--panel); }
.drive-item.active { background: var(--accent-soft); border-color: rgba(110, 139, 255, 0.35); }
.drive-item .drive-icon {
  width: 32px; height: 32px;
  border-radius: var(--radius-sm);
  background: var(--panel-2);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-dim);
  flex: none;
}
.drive-item.active .drive-icon { color: var(--accent); background: rgba(110, 139, 255, 0.16); }
.drive-item .drive-info { flex: 1; min-width: 0; }
.drive-item .label { font-weight: 600; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.drive-item .sub { color: var(--text-faint); font-size: 11.5px; margin-top: 1px; display: flex; align-items: center; gap: 5px; }

.status-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.status-dot.done { background: var(--success); box-shadow: 0 0 0 2px var(--success-soft); }
.status-dot.scanning { background: var(--warning); box-shadow: 0 0 0 2px var(--warning-soft); animation: pulse 1.2s ease-in-out infinite; }
.status-dot.failed { background: var(--danger); box-shadow: 0 0 0 2px var(--danger-soft); }
.status-dot.never { background: var(--text-faint); }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }

/* ---------- detected drives (connect picker) ---------- */

.detected-list { margin: 8px 0 4px; display: flex; flex-direction: column; gap: 6px; }
.detected-item {
  padding: 9px 10px;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--border-soft);
  background: var(--panel);
}
.detected-item.clickable { cursor: pointer; }
.detected-item.clickable:hover { border-color: rgba(110, 139, 255, 0.4); background: var(--panel-2); }
.detected-item .drive-info { flex: 1; min-width: 0; }
.detected-item .label { font-weight: 600; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.detected-item .sub { color: var(--text-faint); font-size: 11px; margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.detected-item .sub.serial { color: var(--text-dim); }
.detected-item button.detect-register { flex: none; padding: 6px 10px; font-size: 12px; }
.detected-item .registered-tag {
  flex: none;
  display: flex; align-items: center; gap: 4px;
  font-size: 12px; color: var(--success);
}
.detected-item .registered-tag.muted-tag { color: var(--text-faint); }
.detected-item.unavailable { opacity: 0.6; }
.detected-item.unavailable .sub { color: var(--warning); }

.drive-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 999px;
  font-size: 9.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  vertical-align: middle;
}
.drive-badge.network {
  background: rgba(143, 166, 255, 0.16);
  color: #8fa6ff;
}
/* An LTO cartridge. Its own colour rather than reusing Network's, because what it tells
   you is different: a network share is somewhere else, a tape is somewhere else AND slow
   to reach - the actions VAMP offers on it differ for that reason alone. */
.drive-badge.tape {
  background: rgba(214, 168, 90, 0.18);
  color: #d6a85a;
}
/* The boot drive. Marked so it reads as "this is your computer, not your footage". */
.drive-badge.system {
  background: rgba(150, 150, 150, 0.16);
  color: var(--text-faint);
}
/* This drive has been published to the cloud - a STATE of the drive, not a second drive. */
/* A catalog whose files nobody can send any more (T43). It is NOT the same as offline -
   an offline drive comes back when you plug it in, and this one has nothing to plug in. */
.drive-badge.stale { background:var(--danger-soft); color:var(--danger); }
.drive-badge.published {
  background: rgba(70, 200, 120, 0.16);
  color: #5cd68a;
}
/* The published copy has fallen behind the catalog it was made from (2026-09-21).
   WARNING rather than danger, and the difference is the point: nothing is broken and
   nothing is lost - the website is serving an older day, and one Publish fixes it.
   Painting it red would put it next to "nobody can send these files", which is a
   different order of problem. */
.drive-badge.behind { background: var(--warning-soft); color: var(--warning-text); }
.clone-actions .publish-status.behind { color: var(--warning-text); }

details.collapsible.sub { margin: 8px 0 0; }
details.collapsible.sub > summary { font-size: 11.5px; color: var(--text-faint); }

details.collapsible { margin-bottom: 10px; }
details.collapsible > summary {
  cursor: pointer;
  list-style: none;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-dim);
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  gap: 6px;
}
details.collapsible > summary::-webkit-details-marker { display: none; }
details.collapsible > summary:hover { background: var(--panel); color: var(--text); }
details.collapsible > summary .chev { transition: transform 0.15s ease; margin-left: auto; }
details.collapsible[open] > summary .chev { transform: rotate(90deg); }
details.collapsible .collapsible-body { padding: 4px 2px 0; }

/* ---------- sidecar toggle ---------- */

.sidecar-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
  padding: 8px 12px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  background: var(--panel);
  font-size: 12.5px;
}
.sidecar-toggle .switch-label {
  display: flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
  color: var(--text-dim);
  user-select: none;
}
.sidecar-toggle .switch-label input { cursor: pointer; accent-color: var(--accent); }
.sidecar-toggle .switch-label:hover { color: var(--text); }

tr.row-sidecar .file-name-cell span,
tr.row-hidden .file-name-cell span { color: var(--text-faint); font-style: italic; }
tr.row-sidecar .file-name-cell .icon,
tr.row-hidden .file-name-cell .icon { opacity: 0.6; }

/* A file somebody hid (a false take), shown because the Hidden setting lets it through.
   Dimmed as a whole, not italicised like OS clutter - it's the viewer's own flag. */
tr.is-user-hidden > td { opacity: 0.45; }
.tile.is-user-hidden { opacity: 0.45; }
.hidden-note { display: flex; align-items: center; gap: 8px; color: var(--text-faint); }
.hidden-note span { flex: 1; }

/* ---------- breadcrumb ---------- */

.breadcrumb {
  margin-bottom: 14px;
  color: var(--text-faint);
  font-size: 13px;
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: nowrap;
  min-width: 0;
  overflow: hidden;
}
.breadcrumb .crumb {
  cursor: pointer;
  color: var(--text-dim);
  padding: 3px 8px;
  border-radius: 999px;
  transition: background 0.12s ease;
  max-width: 240px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.breadcrumb .sep { flex: 0 0 auto; }
.breadcrumb .crumb.current { flex: 0 1 auto; }
.breadcrumb .crumb:hover { background: var(--panel); color: var(--accent); }
.breadcrumb .crumb.current { color: var(--text); font-weight: 600; }
.breadcrumb .sep { color: var(--border); }

/* Connected vs clone-only indicator in the tree header. */
.conn-badge {
  display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  font-size: 11.5px; padding: 3px 10px; border-radius: 999px; white-space: nowrap;
}
.conn-badge svg { width: 13px; height: 13px; }
.conn-badge.conn-on, .conn-badge.conn-off { position: relative; padding-left: 22px; }
.conn-badge.conn-on::before, .conn-badge.conn-off::before {
  content: ""; position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  width: 7px; height: 7px; border-radius: 50%;
}
/* Says what state you're in before you wonder why nothing is shared. Deliberately calm:
   offline is not an error - VAMP local is free and works forever this way - so this is the
   dim, factual grey of a status line and not a warning colour. It sits next to Sign in, so
   the state and the way out of it are one thing to look at. */
.offline-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--text-dim);
  background: var(--surface-2, rgba(255,255,255,0.05));
  border: 1px solid var(--border-soft);
  border-radius: 999px; padding: 3px 10px; white-space: nowrap;
}
.offline-chip svg { width: 13px; height: 13px; opacity: .8; }
/* The top bar gets crowded on a narrow window, and the button matters more than the
   label - the chip explains, Sign in acts. */
@media (max-width: 900px) { .offline-chip { display: none; } }

.conn-on { background: rgba(57, 224, 138, 0.14); color: #39e08a; }
.conn-on::before { background: #39e08a; }
.conn-off { background: rgba(255, 180, 84, 0.14); color: #ffb454; }
.conn-off::before { background: #ffb454; }
.conn-cloud { background: var(--accent-soft); color: var(--accent); }

/* ---------- jobs indicator + panel ---------- */

.jobs-btn { position: relative; }
.jobs-btn.busy .jobs-ico .icon { animation: spin 1.1s linear infinite; color: var(--accent); }
@keyframes spin { to { transform: rotate(360deg); } }
.jobs-badge {
  position: absolute;
  top: -5px; right: -5px;
  min-width: 16px; height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--accent);
  color: #0b0d11;
  font-size: 10px; font-weight: 700; line-height: 16px;
  text-align: center;
}

/* A stopped queue is the state people leave on by accident and then report the app as
   broken, so it reads as a state and not as an error: amber, not red - nothing has gone
   wrong, it is just holding. */
.jobs-btn.paused .jobs-ico .icon { animation: none; color: var(--warning); }
.jobs-btn.just-done { animation: jobs-done 1.6s var(--ease-out); }
.jobs-btn.just-failed { animation: jobs-failed 1.6s var(--ease-out); }
@keyframes jobs-done { 0% { box-shadow: 0 0 0 0 rgba(54, 211, 153, 0.55); color: var(--success); }
  70% { box-shadow: 0 0 0 10px rgba(54, 211, 153, 0); color: var(--success); } }
@keyframes jobs-failed { 0% { box-shadow: 0 0 0 0 rgba(255, 180, 84, 0.55); color: var(--warning); }
  70% { box-shadow: 0 0 0 10px rgba(255, 180, 84, 0); color: var(--warning); } }
.jobs-badge.paused { background: var(--warning); }
#jobs-pause.paused { color: var(--warning-text); font-weight: 600; }

.queue-paused {
  margin: 8px 8px 0;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  border: 1px solid rgba(255, 180, 84, 0.35);
  background: var(--warning-soft);
}
.qp-title {
  display: flex; align-items: center; gap: 6px;
  font-weight: 600; font-size: 13px; color: var(--warning-text);
}
.qp-title .icon { width: 14px; height: 14px; }
.qp-line { font-size: 12px; color: var(--text-dim); margin: 4px 0 8px; line-height: 1.45; }

.jobs-panel {
  position: fixed;
  top: 60px; right: 16px;
  width: 360px;
  max-height: 70vh;
  overflow-y: auto;
  background: var(--panel-2, #14171d);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg, 12px);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
  z-index: 100;
}
.jobs-panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border-soft);
  font-weight: 600; font-size: 13px;
}
.jobs-list { padding: 6px; }
.job-item {
  padding: 10px 12px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-soft);
  margin-bottom: 6px;
  background: var(--panel);
}
.job-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.job-type { font-weight: 600; font-size: 13px; }
.job-status {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.4px;
  padding: 2px 7px; border-radius: 999px; color: var(--text-faint);
  background: var(--panel-2);
}
.job-status.running { color: var(--warning); background: var(--warning-soft); }
.job-status.done { color: var(--success); background: var(--success-soft); }
.job-status.failed { color: var(--danger); background: var(--danger-soft); }
.job-status.canceled { color: var(--text-faint); }
.job-status.waiting { color: var(--accent); background: var(--accent-soft); }
.job-msg { font-size: 12px; color: var(--text-dim); margin: 5px 0; word-break: break-word; }
.job-meta { font-size: 11.5px; color: var(--text-faint); }
.job-error { font-size: 11.5px; color: var(--danger); margin-top: 4px; word-break: break-word;
  max-height: 8em; overflow-y: auto; white-space: pre-wrap; }
.jobs-head-actions { display: flex; align-items: center; gap: 10px; }
.job-actions { display: flex; gap: 12px; margin-top: 6px; }
.job-actions:empty { display: none; }

/* Per-clip progress inside a running batch: one row per clip actually being worked
   on, so a long clip shows movement while the job's own n/total sits still. */
.job-clips { margin-top: 6px; display: flex; flex-direction: column; gap: 4px; }
.job-clip { display: grid; grid-template-columns: minmax(0, 1fr) 70px 52px; align-items: center; gap: 8px; }
.job-clip-name {
  font-size: 11px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.job-clip-pct { font-size: 11px; color: var(--text-faint); text-align: right; font-variant-numeric: tabular-nums; }

.progress-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--panel-2);
  overflow: hidden;
  margin: 6px 0;
}
.progress-bar.sm { height: 4px; margin: 0; display: block; }
.progress-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 999px;
  transition: width 0.3s ease;
}
.progress-bar.indeterminate .progress-fill {
  width: 40% !important;
  animation: indeterminate 1.2s ease-in-out infinite;
}
@keyframes indeterminate {
  0% { margin-left: -40%; }
  100% { margin-left: 100%; }
}

/* ---------- update chip + panel ---------- */

.update-chip {
  display: flex; align-items: center; gap: 6px;
  background: var(--accent-soft); color: var(--accent);
  border: 1px solid rgba(110, 139, 255, 0.35);
  border-radius: 999px; padding: 5px 12px;
  font-size: 12px; font-weight: 600; font-family: inherit;
  cursor: pointer;
}
.update-chip:hover { background: rgba(110, 139, 255, 0.22); }
.update-chip svg { width: 14px; height: 14px; }
.update-chip[hidden] { display: none; }

.update-panel { width: 320px; padding: 14px; }
.update-panel .up-title {
  display: flex; align-items: center; gap: 7px;
  font-size: 13.5px; font-weight: 700; margin-bottom: 6px;
}
.update-panel .up-title svg { width: 15px; height: 15px; color: var(--accent); }
.update-panel .up-note { font-size: 12px; color: var(--text-dim); line-height: 1.5; }
.update-panel .up-status { font-size: 12px; color: var(--text-faint); margin-top: 8px; min-height: 1em; }
.update-panel .up-actions { display: flex; align-items: center; gap: 12px; margin-top: 12px; }

/* ---------- tools drawer ---------- */
/* Home for the previews/transcribe/pair and verified-copy bars. Shut by default and
   opened from the Actions menu, so the top of the file list is the file list. */
.tools-drawer {
  margin-bottom: 12px;
  padding-top: 2px;
  animation: tools-in 0.14s ease-out;
}
.tools-drawer[hidden] { display: none; }
@keyframes tools-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

/* ---------- batch proxy bar ---------- */

.batch-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 12px;
  padding: 8px 12px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  background: var(--panel);
}
.batch-bar .batch-label {
  display: flex; align-items: center; gap: 6px;
  font-size: 12.5px; font-weight: 600; color: var(--text-dim);
}
.batch-bar .batch-label .icon { color: var(--accent); }
button.secondary.sm { padding: 5px 10px; font-size: 12px; }
.batch-bar .batch-status { font-size: 12px; color: var(--text-faint); display: flex; align-items: center; gap: 8px; }
.skip-audio-label { font-size: 12px; color: var(--text-faint); display: flex; align-items: center; gap: 5px; cursor: pointer; }
.skip-audio-label input { cursor: pointer; accent-color: var(--accent); }
/* Transcribe sits apart from the proxy buttons - it's the one that costs money. */
.batch-bar #transcribe-btn { margin-left: auto; }

/* Search results: one line per row, like the file list. They used to wrap to three and four
   lines each because the Path column printed the entire path, so row heights ranged 1-4 and
   the list could not be scanned at all. */
.search-table { table-layout: fixed; width: 100%; }
.search-table th:nth-child(2) { width: 45%; }
.search-table th:nth-child(3) { width: 100px; }
.search-table td, .search-table th { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.search-table .path-cell { font-size: 12px; }

/* Told when the pane is too narrow to hold every chosen column. Without this the columns
   just disappear and it reads as VAMP having forgotten your settings. */
.cols-dropped {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 10px; padding: 7px 10px;
  border: 1px solid var(--border-soft); border-radius: var(--radius-ms);
  background: var(--bg-soft); color: var(--text-dim); font-size: 12px;
}
.cols-dropped .icon { flex: none; width: 14px; height: 14px; color: var(--text-faint); }

/* Keyboard focus on a row. The list is one tab stop (roving tabindex) and the arrows move
   inside it, so this ring is the only thing telling you where you are. */
.file-table tr.row-file:focus-visible, .file-table tr.row-folder:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* File rows stay ONE line tall: names ellipsize instead of wrapping (full name in the
   hover tooltip). Scanning a hundred one-line rows beats reading forty two-line ones. */
.file-table { table-layout: fixed; width: 100%; }
.file-table td, .file-table th { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.file-table th { position: relative; }
.file-table .file-name-cell { display: flex; align-items: center; gap: 6px; min-width: 0; }
.file-table .file-name-cell span { overflow: hidden; text-overflow: ellipsis; }
/* Drag handle on the right edge of each header to resize columns. */
/* Column divider. It WAS invisible until you happened to hover the right 7 pixels of a
   heading, so Nick reasonably concluded the columns couldn't be resized at all. The line
   is now always drawn - the affordance is the point. */
.col-resizer { position: absolute; top: 0; right: 0; width: 9px; height: 100%; cursor: col-resize; user-select: none; }
.col-resizer::before {
  content: ""; position: absolute; top: 6px; bottom: 6px; right: 4px; width: 1px;
  background: var(--border); transition: background 0.12s ease, top 0.12s ease, bottom 0.12s ease;
}
.col-resizer:hover::before, .col-resizer:active::before {
  background: var(--accent); top: 0; bottom: 0; width: 2px;
}
button.link-btn {
  background: none; border: none; padding: 3px 2px;   /* was 0: a 13px-tall click target */
  color: var(--accent); font-size: 12px; cursor: pointer; text-decoration: underline;
}
button.link-btn:hover { color: var(--text); }

.batch-note {
  margin-bottom: 12px;
  padding: 9px 12px;
  border-radius: var(--radius-md);
  font-size: 13px;
  border: 1px solid var(--border-soft);
}
.batch-note.ok { background: var(--success-soft); color: var(--success); border-color: rgba(54, 211, 153, 0.3); }
.batch-note.warn { background: var(--warning-soft); color: var(--warning); border-color: rgba(255, 180, 84, 0.3); }
.batch-note { animation: note-in var(--dur-med) var(--ease-out); }
.batch-note.leaving { animation: note-out var(--dur-med) var(--ease-in) forwards; overflow: hidden; }
@keyframes note-in { from { opacity: 0; transform: translateY(-6px); } }
@keyframes note-out {
  from { opacity: 1; max-height: 120px; }
  to { opacity: 0; max-height: 0; padding-top: 0; padding-bottom: 0; margin-bottom: 0; border-width: 0; }
}
/* How long an offer stays up, drawn as a line draining along the note's bottom edge. It
   pauses under the pointer because noteTimeout does. */
/* Named rather than :has(.note-timer) - the Mac app supports macOS 12.0, and WebKit only
   learned :has() in 12.3; without it the bar would anchor to some other box. */
.done-note, .kw-undo-note { position: relative; overflow: hidden; }
.note-timer {
  position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: currentColor; opacity: 0.45; transform-origin: left;
  animation: note-drain linear forwards;
}
.batch-note:hover .note-timer { animation-play-state: paused; }
@keyframes note-drain { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@media (prefers-reduced-motion: reduce) { .note-timer { display: none; } }

/* A finished job. The check strokes itself in once the note has landed. */
.done-note { display: flex; align-items: center; gap: 10px; cursor: pointer; }
.done-note strong { font-weight: 600; }
.done-note .done-text > span { opacity: 0.85; }
.done-mark {
  flex: none; display: grid; place-items: center; width: 22px; height: 22px;
  border-radius: 50%; background: currentColor;
  animation: done-pop var(--dur-slow) var(--ease-out);
}
.done-mark .icon { width: 14px; height: 14px; color: var(--bg); stroke-width: 3; }
.done-note.ok .done-mark polyline {
  stroke-dasharray: 24; stroke-dashoffset: 24;
  animation: done-draw var(--dur-slow) var(--ease-out) 120ms forwards;
}
@keyframes done-pop { from { transform: scale(0.4); opacity: 0; } 60% { transform: scale(1.12); } }
@keyframes done-draw { to { stroke-dashoffset: 0; } }

/* ---------- cloud viewer (vampedit.com) ---------- */
.viewer-top { display:flex; align-items:center; justify-content:space-between; padding:12px 20px;
  border-bottom:1px solid var(--border-soft); background:var(--panel); }
.vt-left { display:flex; align-items:center; gap:10px; }
.vt-title { font-weight:700; letter-spacing:.5px; }
.vt-sub { font-size:12px; color:var(--text-faint); }
.vt-clone { font-weight:600; color:var(--text); margin-left:4px; }
.vt-right { display:flex; align-items:center; gap:12px; }
.vt-user { font-size:12.5px; color:var(--text-dim); }
.viewer-body { padding:18px 20px; }
.viewer-body.two-pane { display:grid; grid-template-columns: 1fr 1fr; gap:18px; align-items:start; }
@media (max-width: 900px){ .viewer-body.two-pane{ grid-template-columns:1fr; } }
.v-left { min-width:0; }
.v-left-top { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; }
.v-sidecar-toggle { display:flex; align-items:center; gap:6px; font-size:12px; color:var(--text-faint); cursor:pointer; white-space:nowrap; }
.v-list { margin-top:8px; }
.v-row { display:flex; align-items:center; gap:10px; padding:8px 10px; border-radius:8px; cursor:pointer; }
.v-row:hover { background:var(--panel-2, rgba(255,255,255,.04)); }
.v-row.sel { background:var(--accent-soft, rgba(110,139,255,.18)); }
.v-ic { width:20px; text-align:center; }
.v-nm { flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:13px; }
.v-badge { font-size:10px; padding:2px 6px; border-radius:6px; background:var(--accent-soft,rgba(110,139,255,.2)); color:var(--accent); }
.v-detail { min-width:0; }
.scrub-hint { position:absolute; bottom:6px; right:8px; font-size:10px; color:#fff; opacity:.6;
  background:rgba(0,0,0,.4); padding:2px 6px; border-radius:5px; pointer-events:none; }
.v-time { font-size:12px; color:var(--text-dim); margin-left:10px; }
.v-tags { display:flex; flex-wrap:wrap; gap:6px; }
.v-tag { font-size:11.5px; padding:3px 9px; border-radius:20px; background:var(--accent-soft,rgba(110,139,255,.16)); color:var(--accent); }
.clone-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(220px,1fr)); gap:14px; }
.clone-card { padding:18px; border:1px solid var(--border-soft); border-radius:12px; background:var(--panel);
  cursor:pointer; transition:border-color .15s, transform .1s; }
.clone-card:hover { border-color:var(--accent); transform:translateY(-2px); }
.cc-icon { font-size:26px; }
.cc-name { font-weight:600; margin-top:8px; }
.cc-meta { font-size:12px; color:var(--text-faint); margin-top:4px; }
.cc-own { color:var(--accent); }
.viewer-auth { display:flex; align-items:center; justify-content:center; min-height:100vh; padding:20px; }
/* Left over from the viewer sign-in screen that no longer exists. Its 340px cap was the only
   thing making the auth cards sit off-centre inside the 380px .auth-box, so the card ended up
   20px left of the logo and footer above and below it. The live .auth-card rule further down
   redeclares everything else here; only the widths mattered, and they are gone. */
.auth-card { padding:28px; border:1px solid var(--border-soft);
  border-radius:14px; background:var(--panel); }
.auth-brand { display:flex; align-items:center; gap:8px; font-weight:700; font-size:18px; }
.auth-sub { color:var(--text-dim); font-size:13px; margin:6px 0 16px; }
.auth-card input { width:100%; margin-bottom:10px; }
/* Confirmation, not a question - the invite token already knows the address. Still a real
   input rather than static text so a password manager sees a username to pair the new
   password with, but it must not look like something worth typing into. */
.auth-card input[readonly] { color: var(--text-dim); cursor: default; }
.auth-card button.primary { width:100%; }
.auth-toggle { margin-top:12px; font-size:12.5px; color:var(--text-dim); display:flex; gap:6px; justify-content:center; }
.auth-hint { font-size:11.5px; color:var(--text-faint); margin:-2px 0 8px; line-height:1.4; }
.fatal-err { font-size:11.5px; color:#f08a8a; background:rgba(230,90,90,.08); border:1px solid rgba(230,90,90,.25);
  border-radius:8px; padding:10px 12px; margin:12px auto; max-width:520px; text-align:left; white-space:pre-wrap; }

/* Trim strip: one control, drag across it to pick a segment (phone-editor style). */
.trim-strip { position:relative; height:34px; margin:10px 0 6px; border-radius:8px; cursor:crosshair;
  background:repeating-linear-gradient(90deg, rgba(255,255,255,.07) 0 2px, transparent 2px 10px), var(--panel-2, rgba(255,255,255,.05));
  border:1px solid var(--border-soft); user-select:none; }
.trim-sel { position:absolute; top:0; bottom:0; background:var(--accent-soft, rgba(110,139,255,.28));
  border-left:2px solid var(--accent); border-right:2px solid var(--accent); pointer-events:none; }
.trim-strip:not(.trimmed) .trim-sel { background:rgba(255,255,255,.05); border-color:transparent; }
.trim-handle { position:absolute; top:-3px; width:12px; height:40px; margin-left:-6px; cursor:ew-resize;
  border-radius:4px; background:var(--accent); opacity:.9; }
.trim-handle:hover { opacity:1; }
.trim-strip:not(.trimmed) .trim-handle { opacity:.45; }
.req-times { display:flex; align-items:center; gap:8px; font-size:12px; color:var(--text-dim); margin-bottom:10px; }
.req-times .link-btn { margin-left:auto; }
.req-status { font-size:12px; color:var(--text-faint); margin-top:8px; }
.req-tc { font-size:11.5px; color:var(--accent); margin-bottom:8px; font-variant-numeric:tabular-nums; }
.req-tc:empty { display:none; }
.vfr-warn { font-size:12px; line-height:1.45; padding:8px 10px; margin-bottom:10px; border-radius:8px;
  background:rgba(230,170,60,.12); border:1px solid rgba(230,170,60,.35); color:#e8b45a; }
.req-page-title { margin:0 0 14px; font-size:18px; }
.req-list { display:flex; flex-direction:column; gap:8px; max-width:720px; }
.req-row { display:flex; align-items:center; justify-content:space-between; gap:14px;
  padding:12px 14px; border:1px solid var(--border-soft); border-radius:10px; background:var(--panel); }
.req-row-name { font-weight:600; font-size:13.5px; }
.req-row-meta { font-size:12px; color:var(--text-faint); margin-top:2px; }
.req-row-note { font-size:12px; color:var(--text-dim); margin-top:4px; font-style:italic; }
.req-row-status { font-size:11px; padding:4px 10px; border-radius:20px; white-space:nowrap;
  background:var(--accent-soft,rgba(110,139,255,.18)); color:var(--accent); text-transform:capitalize; }
.req-row-status.status-delivered { background:rgba(70,200,120,.18); color:#5cd68a; }
.req-row-status.status-failed { background:rgba(230,90,90,.18); color:#f08a8a; }
.req-row-status.status-declined { background:rgba(230,90,90,.14); color:#f0a0a0; }
.req-row-status.status-cancelled { background:rgba(150,150,150,.16); color:var(--text-faint); }
.req-hint { font-size:12px; color:var(--text-faint); margin:-4px 0 10px; }
.acct-badge { font-size:10px; padding:2px 7px; border-radius:5px; margin-left:6px; vertical-align:middle;
  text-transform:uppercase; letter-spacing:.3px; font-weight:700; }
.acct-badge.admin { background:var(--accent-soft,rgba(110,139,255,.2)); color:var(--accent); }
.acct-badge.you { background:rgba(150,150,150,.18); color:var(--text-faint); }
/* A computer that has been signed out. It stays on the list rather than vanishing - "did
   that work?" is the next question, and a row that disappears cannot answer it. */
.dev-revoked { opacity: .55; }
.acct-badge.odd { background:rgba(230,170,60,.16); color:#e8b45a; }
.acct-warn { color:#e8b45a; }
.acct-section { font-size:12px; font-weight:600; color:var(--text-dim); margin:18px 0 8px; }
.req-grip { cursor:grab; color:var(--text-faint); font-size:14px; user-select:none; width:14px; }
.req-grip.placeholder { visibility:hidden; }
.req-row.dragging { opacity:.45; }
.req-row.req-closed { opacity:.75; }
.req-drive-note { font-size:12.5px; color:var(--text-dim); margin-bottom:10px; }
.share-add { max-width:720px; margin-bottom:16px; }
.share-hint { font-size:12px; color:var(--text-faint); margin-bottom:10px; }
.share-add .row { display:flex; gap:8px; }
.share-add input { flex:1; }
/* The address box and the role picker share a line, and the picker used to win it. Every
   select in the app is width:100%, so `flex:0 0 auto` resolved the select's basis to the
   whole row and then refused to shrink: the address field was squeezed to a sliver you
   couldn't type in and the row spilled sideways under a scrollbar. Size the select (and
   any button beside it) to their own content, and let the address have what's left. */
.share-row > input { flex: 1 1 auto; min-width: 0; }
.share-row > select, .share-row > button { flex: 0 0 auto; width: auto; }
/* The role picker on a person's row. It replaced a link-button toggle when co_owner made
   the choice three-way (T45b), and a bare <select> in a flex row stretches to fill. */
.share-role-pick { flex: 0 0 auto; width: auto; font-size: 12px; padding: 4px 6px; }
.share-transfer { margin-top: 14px; }
.share-status { font-size:12px; color:var(--text-faint); margin-top:8px; }
.share-status:empty { display:none; }
/* Share dialog: the same pieces as the Share pane, breathing inside a modal body. */
#share-modal .row { margin-top: 4px; }
#share-modal #shd-list { margin-top: 14px; }
#share-modal #shd-list:empty { display: none; }

/* ---------- clone browser header (breadcrumb + publish) ---------- */

.tree-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 12px; flex-wrap: wrap;
  /* Pinned to the top of the file list. Scrolling a long folder used to carry Back, the
     breadcrumb and Actions off the screen, so the controls for the thing you were looking
     at were only reachable by scrolling back to the top first.
     The negative margins + matching padding let the opaque background reach the full width
     of .browser's padding box - without them the rows scrolled through a 22px gutter on
     each side of the header.

     TOP IS -20px, NOT 0, and that is the whole reason this bar now sits flush. Sticky
     constrains the MARGIN box, so `top: 0` refused to let the -20px margin do its job and
     held the header 20px below the top of the pane: rows scrolled through the gap above it
     (Nick's screenshot), and because the header was drawn 20px lower than the space the
     layout had reserved, its opaque background also sat on top of the NAME / SIZE / DATE
     column heads and sliced them in half. Matching the offset to the margin cancels both:
     the header renders exactly where the flow put it, hard against the top of the pane,
     with its own 20px of padding standing in for the pane's. */
  position: sticky;
  top: -20px;
  z-index: 30;                /* over the rows it scrolls past, under menus and modals */
  margin: -20px -22px 12px;
  padding: 20px 22px 10px;
  background: var(--bg);
  border-bottom: 1px solid var(--border-soft);
}
/* The selection bar is deliberately NOT pinned as well. It sits below a header that wraps
   to two or three rows depending on the window, so there is no fixed offset to stick it
   at - and a sticky with the wrong offset either overlaps the header or hides beneath it.
   Pinning both means wrapping them in one sticky container, which is a bigger change than
   "keep the actions on screen" asked for. */
.tree-header .breadcrumb { margin-bottom: 0; }
.clone-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; row-gap: 6px; }

/* Below ~1200px this header stacked four rows deep - Back, breadcrumb, the offline pill,
   then the actions wrapping onto two lines of their own - and pushed the file list down
   124px. Tighter spacing and smaller controls keep it to two. */
@media (max-width: 1200px) {
  .tree-header { gap: 8px; margin-bottom: 8px; padding-bottom: 8px; }
  .clone-actions { gap: 6px; }
  .clone-actions button.sm { padding: 4px 8px; font-size: 11.5px; }
  .clone-actions .icon { width: 13px; height: 13px; }
}
.clone-actions .publish-status { font-size: 12px; color: var(--text-faint); }
.clone-actions #publish-cloud-btn .icon,
.clone-actions #publish-cloud-btn-off .icon { color: var(--accent); }

/* Settings -> Share your footage: the connected state. */
.cloud-connected { display: flex; gap: 10px; align-items: flex-start;
  padding: 10px 12px; border-radius: 8px;
  background: rgba(70, 200, 120, 0.08); border: 1px solid rgba(70, 200, 120, 0.28); }
.cloud-connected .icon { color: #5cd68a; flex: 0 0 auto; margin-top: 1px; }
.cloud-connected .cc-line { font-size: 13px; font-weight: 600; color: #5cd68a; }
.cloud-connected .set-hint { margin-top: 3px; }

/* Signed in, but the website isn't answering. Amber, not red: credentials are intact
   and nothing is lost - but pretending this is the healthy green state is how a dead
   connection goes unnoticed for a week. */
.cloud-connected.cloud-warn {
  background: rgba(240, 180, 60, 0.08); border-color: rgba(240, 180, 60, 0.35); }
.cloud-connected.cloud-warn .icon { color: #e8b23c; }
.cloud-connected.cloud-warn .cc-line { color: #e8b23c; }

/* The plain-language explainer under a request's status. */
.req-status-line { opacity: 0.75; font-style: italic; }

.drive-rename { font-size: 11px; opacity: 0.7; }
.drive-rename:hover { opacity: 1; }

/* FOLDERS/DRIVES split: the divider that separates catalogs from the volumes you
   pick new ones from. */
.list-divider { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-faint); margin: 14px 4px 6px; padding-top: 10px;
  border-top: 1px solid var(--border-soft); }
.device-item { opacity: 0.92; }

/* Folder picker modal */
.fp-path { font-size: 11.5px; padding: 6px 2px; word-break: break-all; }
.fp-list { max-height: 320px; overflow-y: auto; border: 1px solid var(--border-soft);
  border-radius: 8px; padding: 4px; }
.fp-row { display: flex; align-items: center; gap: 8px; padding: 7px 10px;
  border-radius: 6px; cursor: pointer; font-size: 13px; }
.fp-row:hover { background: rgba(122, 130, 255, 0.10); }
.fp-row .icon { color: var(--accent); flex: 0 0 auto; }
.fp-up { color: var(--text-faint); }

/* The catalog-time "+ scrub previews" choice. Tiny on purpose - it's a default most
   people should leave alone, not a decision to agonize over. */
.scan-proxy-opt { display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; opacity: 0.8; cursor: pointer; user-select: none; white-space: nowrap; }
.scan-proxy-opt input { margin: 0; }

/* A button whose label wraps mid-word reads as broken UI ("Publish to / cloud" on three
   lines). Buttons state one action; they get one line, always. */
button { white-space: nowrap; }
.switch-label, .skip-audio-label { white-space: nowrap; }

/* Transcript panel under the video (idea.png): clickable lines that seek the scrubber. */
.transcript-panel { max-height: 260px; overflow-y: auto; font-size: 13px; line-height: 1.55;
  border: 1px solid rgba(255,255,255,0.07); border-radius: 8px; padding: 6px 2px; }
.tr-seg { display: flex; gap: 10px; padding: 4px 10px; border-radius: 6px; cursor: pointer; }
.tr-seg:hover { background: rgba(122, 130, 255, 0.10); }
.tr-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 6px 0 10px; }
.tr-toolbar .tr-lang { padding: 4px 6px; font-size: 12px; }
.tr-toolbar .tr-clean { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--text-dim); cursor: pointer; }
.tr-toolbar .tr-clean input { width: auto; margin: 0; }
.tr-toolbar .tr-export-label { margin-left: auto; font-size: 12px; color: var(--text-faint); }
.tr-seg .tr-text[contenteditable="true"] { background: rgba(110, 91, 255, 0.10); border-radius: 4px; padding: 1px 5px; outline: 1px solid var(--accent); cursor: text; }
.tr-seg-inout { background: rgba(110, 91, 255, 0.14); }
.tr-seg-active { background: rgba(110, 91, 255, 0.26); }
.tr-seg-active.tr-seg-inout { background: rgba(110, 91, 255, 0.30); }
.tr-time { flex: 0 0 auto; opacity: 0.55; font-variant-numeric: tabular-nums; font-size: 12px; padding-top: 1px; user-select: none; }
.tr-text { flex: 1; user-select: text; }
.transcript-panel { user-select: text; }
.tr-select-hint { margin-top: 8px; font-size: 12px; color: var(--accent);
  background: var(--accent-soft); border-radius: var(--radius-sm); padding: 7px 10px; line-height: 1.45; }

/* ---------- dual-system audio (Linked audio card + WAV picker) ---------- */
.pair-row { font-size: 13px; line-height: 1.55; }
.pair-row .link-btn { font-size: 13px; }
.pair-clip-list { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
.pair-clip-row { display: flex; align-items: center; gap: 7px; text-align: left;
  padding: 6px 9px; border-radius: var(--radius-sm); background: var(--panel-2);
  border: 1px solid var(--border); width: 100%; font-size: 13px; cursor: pointer; }
.pair-clip-row:hover { border-color: var(--accent); }
.pair-clip-row svg { width: 14px; height: 14px; flex-shrink: 0; }
.pair-clip-row .set-hint { margin-left: auto; flex-shrink: 0; }
.pair-clip-row input[type="checkbox"] { width: auto; margin: 0; flex-shrink: 0; }
.pair-clip-row input:disabled { opacity: 0.5; }

/* Linked audio sources: one row per recorder file (plus the camera track) so they
   can be auditioned against each other, with the active one marked. */
.pair-src-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.pair-src-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  border-radius: var(--radius-sm); background: var(--panel-2); border: 1px solid var(--border); }
.pair-src-row.active { border-color: var(--accent); background: var(--accent-soft); }
.pair-src-row .pair-play { flex: 0 0 auto; }
.pair-src-info { min-width: 0; flex: 1; }
.pair-src-name { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pair-src-meta { font-size: 11.5px; color: var(--text-faint); margin-top: 1px; }
.pair-src-actions { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.pair-badge { font-size: 11px; padding: 2px 8px; border-radius: 999px;
  background: rgba(57, 224, 138, 0.16); color: var(--good, #39e08a); font-weight: 600; }

/* The "you're on the list" outcome after a waitlisted signup - friendly, not an error. */
.auth-waitlisted { display: flex; gap: 10px; align-items: flex-start;
  padding: 12px; border-radius: 8px; font-size: 13px; line-height: 1.5;
  background: rgba(122, 130, 255, 0.08); border: 1px solid rgba(122, 130, 255, 0.3); }
.auth-waitlisted .icon { flex: 0 0 auto; margin-top: 2px; color: #7a82ff; }

/* Browsing a catalog whose drive is on a shelf. Informational, not a warning - this
   is VAMP working as intended, so it must not look like something went wrong. */
.offline-note {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; margin-bottom: 12px;
  border: 1px solid var(--border-soft); border-radius: 8px;
  background: var(--panel);
  font-size: 12px; color: var(--text-faint);
}
.offline-note .icon { color: var(--text-faint); flex: 0 0 auto; }
.scanning-note { border-color: var(--accent); color: var(--text); }
.scanning-note .icon { color: var(--accent); animation: vamp-spin 1.2s linear infinite; }
@keyframes vamp-spin { to { transform: rotate(360deg); } }

/* ---------- first run: what is this thing and what do I do? ---------- */

.welcome { max-width:560px; padding:48px 8px; }
.welcome-title { font-size:22px; font-weight:600; letter-spacing:-.2px; }
.welcome-sub { font-size:13.5px; line-height:1.65; color:var(--text-dim); margin-top:10px; }
.welcome-steps { margin:22px 0 0; padding-left:20px; }
.welcome-steps li { font-size:13.5px; line-height:1.7; color:var(--text-dim); margin-bottom:10px; }
.welcome-steps strong { color:var(--text); }
.welcome-steps em { color:var(--text); font-style:normal; }
.welcome-foot { margin-top:22px; font-size:12px; color:var(--text-faint); }
/* The welcome arrives in reading order: headline, pitch, then each step. */
.welcome > *, .welcome-steps li { animation: note-in var(--dur-slow) var(--ease-out) both; }
.welcome-sub { animation-delay: 80ms; }
.welcome-steps li:nth-child(1) { animation-delay: 180ms; }
.welcome-steps li:nth-child(2) { animation-delay: 260ms; }
.welcome-steps li:nth-child(3) { animation-delay: 340ms; }
.welcome-foot { animation-delay: 440ms; }
/* A clip the running scan has just found, arriving in the folder. */
.just-found { animation: just-found 900ms var(--ease-out); }
@keyframes just-found { from { opacity: 0; background-color: var(--accent-soft); } 30% { opacity: 1; } }
.scan-count { color: var(--text); font-variant-numeric: tabular-nums; }
/* A request sitting with the owner's Mac. The dot says which of three states it's in:
   grey and still while it waits, accent while sent, green and breathing once the Mac has it. */
.batch-note.mac-status { display: flex; align-items: center; gap: 9px; color: var(--text-dim);
  background: var(--panel-2); border-color: var(--border); }
.mac-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint); }
.mac-status[data-state="sent"] .mac-dot { background: var(--accent); }
.batch-note.mac-status[data-state="on"] { color: var(--success-text); background: var(--success-soft); border-color: rgba(54, 211, 153, 0.3); }
.mac-status[data-state="on"] .mac-dot { background: var(--success); animation: mac-breathe 1.8s ease-in-out infinite; }
@keyframes mac-breathe { 0%, 100% { box-shadow: 0 0 0 0 rgba(54, 211, 153, 0.55); }
  50% { box-shadow: 0 0 0 5px rgba(54, 211, 153, 0); } }

/* ---------- owner dashboard: which drive do I plug in? ---------- */

.dash { margin-bottom:18px; }
.dash-title { display:flex; align-items:center; gap:8px; font-size:13px; font-weight:600;
  color:var(--text-dim); margin-bottom:10px; }
.dash-title .icon { color:var(--accent); }
.dash-row { display:flex; align-items:center; gap:14px; padding:12px 14px; margin-bottom:8px;
  border:1px solid var(--border-soft); border-radius:10px; background:var(--panel); }
.dash-state { font-size:10.5px; font-weight:700; letter-spacing:.4px; text-transform:uppercase;
  padding:5px 9px; border-radius:6px; white-space:nowrap; }
.dash-ready .dash-state { background:rgba(70,200,120,.18); color:#5cd68a; }
.dash-plugin .dash-state { background:rgba(230,170,60,.16); color:#e8b45a; }
/* A catalog with no computer behind it (T43). Read as a problem, not a warning: an
   amber "connect me" is a thing you can do something about in a minute, and this is not. */
.dash-missing .dash-state { background:var(--danger-soft,rgba(220,90,90,.16)); color:var(--danger,#e06a6a); }
.dash-main { flex:1; min-width:0; }
.dash-name { font-weight:600; font-size:13.5px; }
.dash-loc { font-weight:400; color:var(--text-faint); font-size:12px; }
.dash-meta { font-size:12px; color:var(--text-faint); margin-top:2px; }
.dash-editors { font-size:11.5px; color:var(--text-dim); margin-top:3px; }

/* ---------- incoming cloud requests (desktop drive browser) ---------- */

.requests-box { display:flex; flex-direction:column; gap:8px; margin-bottom:14px; }
.requests-row { display:flex; align-items:center; gap:12px; padding:10px 12px;
  border:1px solid var(--border-soft); border-radius:10px; background:var(--panel); }
.requests-row > div:first-child { flex:1; min-width:0; }
.requests-row-name { font-weight:600; font-size:13px; }
.requests-row-meta { font-size:11.5px; color:var(--text-faint); margin-top:2px; }
.requests-row-note { font-size:11.5px; color:var(--text-dim); font-style:italic; margin-top:3px; }
.requests-row-status { font-size:12px; color:var(--text-faint); min-width:80px; }
.requests-row.requests-row-done { opacity:.6; }

/* ---------- verified copy bar ---------- */

.copy-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 12px; padding: 8px 12px;
  border: 1px solid var(--border-soft); border-radius: var(--radius-md); background: var(--panel);
}
.copy-bar .copy-label { display: flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
.copy-bar .copy-label .icon { color: var(--success); }
.copy-bar .copy-dests { flex: 1; min-width: 200px; display: flex; flex-direction: column; gap: 5px; }
.copy-bar .copy-dest-row { display: flex; align-items: center; gap: 6px; }
.copy-bar .copy-dest { flex: 1; min-width: 160px; padding: 6px 9px; font-size: 12px; }
.copy-bar .copy-dest-rm { flex: 0 0 auto; color: var(--text-faint); font-size: 12px; }
.copy-bar .copy-catalog { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--text-dim); cursor: pointer; }
.copy-bar .copy-catalog input { width: auto; margin: 0; }
.copy-bar .copy-status { font-size: 12px; color: var(--text-faint); flex-basis: 100%; }
.copy-bar .copy-status:empty { display: none; }

/* ---------- selection action bar ---------- */

.selection-bar[hidden] { display: none; }
.selection-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 12px;
  padding: 8px 12px;
  border: 1px solid rgba(110, 139, 255, 0.4);
  border-radius: var(--radius-md);
  background: var(--accent-soft);
}
.selection-bar .sel-count { font-size: 12.5px; font-weight: 700; color: var(--accent); }
.selection-bar .sel-kw { display: flex; align-items: center; gap: 6px; }
.selection-bar .sel-kw input { width: 220px; padding: 6px 9px; font-size: 12px; }
.selection-bar .sel-status { font-size: 12px; color: var(--text-dim); }

td.cb-col, th.cb-col { width: 34px; text-align: center; padding-left: 6px; padding-right: 6px; }
.cb-col input { cursor: pointer; accent-color: var(--accent); }

/* ---------- file table ---------- */

table { width: 100%; border-collapse: collapse; }
th {
  text-align: left;
  padding: 0 10px 8px;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.6px;
  border-bottom: 1px solid var(--border-soft);
}
td { padding: 10px; font-size: 13.5px; border-bottom: 1px solid var(--border-soft); }
tr.row-folder, tr.row-file { cursor: pointer; transition: background 0.1s ease; user-select: none; }
/* Folders select like files now (one click selects, two opens), so they need the same
   selected state - it used to exist only for .row-file. */
tr.row-folder:hover, tr.row-file:hover { background: var(--panel); }
tr.row-file.selected, tr.row-file.selected:hover,
tr.row-folder.selected, tr.row-folder.selected:hover {
  background: var(--accent-soft);
  box-shadow: inset 2px 0 0 var(--accent);
}
/* The clip the right-hand column is showing. A selection is a fill; this is the bar
   without it, because the two are usually the same row and only diverge once an action
   redraws the list and empties the selection - which is exactly when you still need to
   see which file you're on, and where the next one is. */
tr.row-file.current:not(.selected), tr.row-file.current:not(.selected):hover {
  background: var(--panel);
  box-shadow: inset 2px 0 0 var(--accent);
}

.file-name-cell { display: flex; align-items: center; gap: 9px; }
.file-name-cell .icon { color: var(--text-faint); }
.row-folder .file-name-cell .icon { color: #ffc56e; }
.file-name-cell.video .icon { color: #6e8bff; }
.file-name-cell.image .icon { color: #36d399; }
.file-name-cell.audio .icon { color: #c97bff; }
/* The tree's depth guides. A vertical rule per level, drawn behind the row, so a clip
   inside a folder is visibly INSIDE it - the old 18px step left the two icons nearly in
   line and an expanded folder read as a flat list of siblings. Repeating-linear-gradient
   rather than one element per level: it costs nothing and there is no markup to keep in
   step with the depth. */
/* What the last keystroke did. Over the frame, top-right, out of the way of the picture
   and gone again in under a second. */
.player-hint {
  position: absolute; top: 8px; right: 8px; z-index: 3;
  padding: 3px 9px; border-radius: 999px;
  background: rgba(0, 0, 0, .72); color: #fff;
  font-size: 12px; font-variant-numeric: tabular-nums;
  opacity: 0; transition: opacity .12s ease; pointer-events: none;
}
.player-hint.show { opacity: 1; }

.tree-row { position: relative; }
.tree-guides {
  position: absolute; left: 10px; top: 0; bottom: 0;
  pointer-events: none;
  background-image: repeating-linear-gradient(
    to right,
    var(--border-soft) 0, var(--border-soft) 1px,
    transparent 1px, transparent 26px);
}
/* The last level's line stops at the row's own icon rather than running through it. */
.row-folder .tree-guides, .row-file .tree-guides { opacity: .7; }

.proxy-chip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--accent);
  background: var(--accent-soft);
  padding: 1px 6px;
  border-radius: 999px;
  margin-left: 6px;
}
.proxy-chip .icon { width: 10px; height: 10px; }
/* The chip now marks a MISSING preview, so it reads as a warning rather than a feature
   badge - and it never crowds the filename, because most rows don't carry one. */
.proxy-chip.missing { color: var(--warning-text); background: var(--warning-soft); flex: none; }
/* Icon only: the words moved to the tooltip so the filename gets the width back. The chip
   keeps its background, because a bare glyph on a dark row reads as decoration. */
.proxy-chip.icon-only { padding: 2px 4px; gap: 0; }
.proxy-chip.icon-only .icon { width: 12px; height: 12px; }

/* A row that IS a folder: a RED .RDC or an ARRIRAW sequence listed as the one take inside
   it. The chip is the file count and nothing else - quiet, because the row is meant to
   read as an ordinary clip, and it only has to answer "where did the folder go". */
.clip-chip {
  display: inline-flex;
  align-items: center;
  font-size: 10px;
  font-weight: 700;
  color: var(--text-dim);
  background: var(--panel-2);
  padding: 1px 5px;
  border-radius: 999px;
  margin-left: 6px;
  flex: none;
}

/* ---------- detail pane ---------- */

.detail-header { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 6px; }
.detail-header .file-type-badge {
  width: 38px; height: 38px;
  border-radius: var(--radius-md);
  background: var(--panel-2);
  display: flex; align-items: center; justify-content: center;
  flex: none;
}
/* min-width:0 is the load-bearing bit: a flex item defaults to min-width:auto, so a long
   unbroken path (SOURCE/VIDEO/A002/A002A3R2/A002C005_240109_A3R2.mov) pushes the item
   wider than the pane and the text escapes the panel instead of wrapping inside it. */
.detail-header > div { min-width: 0; }
.detail-header h2 { font-size: 16px; font-weight: 700; margin: 0; word-break: break-word; }
/* The name and its copy button sit on one line, and the button keeps its size while the
   name takes whatever is left. It stays faint until the header is hovered, so it is there
   when wanted without competing with the filename for attention. */
.detail-name-row { display: flex; align-items: baseline; gap: 6px; }
.copy-name-btn {
  flex: none;
  background: none; border: none; padding: 2px; cursor: pointer;
  color: var(--text-faint); opacity: 0; transition: opacity .12s ease, color .12s ease;
  border-radius: 4px; line-height: 0;
}
.copy-name-btn .icon { width: 14px; height: 14px; }
.detail-header:hover .copy-name-btn,
.copy-name-btn:focus-visible { opacity: 1; }
.copy-name-btn:hover { color: var(--accent); background: var(--hover); }
.copy-name-btn.copied { opacity: 1; color: var(--success-text); }

/* Only inside the Premiere panel (app.js PREMIERE: the panel's page said hello). */
.premiere-only { display: none; }
html.in-premiere .premiere-only { display: inline-flex; }
.detail-premiere { gap: 6px; margin-top: 8px; }
.detail-header .detail-sub {
  font-size: 12px; color: var(--text-faint); margin-top: 2px;
  overflow-wrap: anywhere;
}

/* minmax(0, 1fr), not 1fr. A grid track sized 1fr keeps min-width:auto, so it refuses to
   shrink below its content's minimum - and a path like
   SOURCE/VIDEO/B-ROLL DAY/A002/A002_03138Y.RDM/A002_C076_031353.R3D has almost no break
   opportunities, so the value column widened past the panel and the text ran out of the
   box. The minmax lets the track shrink; overflow-wrap lets the path break anywhere it
   has to. Same fix, same reason, as .detail-header > div above. */
.meta-grid { display: grid; grid-template-columns: 140px minmax(0, 1fr); gap: 8px 10px; font-size: 13px; margin: 4px 0 2px; }
.meta-grid .k { color: var(--text-faint); }
.meta-grid .v { color: var(--text); min-width: 0; overflow-wrap: anywhere; }
.manual-badge {
  font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px;
  color: var(--accent); background: var(--accent-soft);
  padding: 1px 5px; border-radius: 999px; vertical-align: middle;
}
.meta-grid .v input.shot-input { width: 100%; padding: 3px 7px; font-size: 13px; }

/* ---------- tag pills ---------- */

.tag-group { margin-bottom: 10px; }
.tag-group:last-child { margin-bottom: 0; }
.tag-group-label { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-faint); margin-bottom: 5px; }
.tag-group-pills { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.tag-namer input { width: 130px; padding: 3px 8px; font-size: 12px; border-radius: 999px; }

.tag-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 6px 4px 10px;
  margin: 0 6px 6px 0;
  font-size: 12px;
}
.tag-pill .dot { width: 6px; height: 6px; border-radius: 50%; flex: none; }
.tag-pill .tag-name { cursor: pointer; }
/* On a multi-file selection: how many of the selected files carry this tag. Bare number
   = all of them; "3/12" = only some. */
.tag-pill .tag-count {
  margin-left: 5px; padding: 0 5px; border-radius: 999px;
  background: var(--panel-2); color: var(--text-faint); font-size: 10.5px;
}
.tag-pill .tag-name:hover { text-decoration: underline; color: var(--accent); }
.tag-pill .ts { color: var(--text-faint); font-size: 10.5px; }
.tag-pill button {
  background: none; color: var(--text-faint);
  padding: 1px 3px; font-size: 13px; line-height: 1;
  border-radius: 4px;
}
.tag-pill button:hover { color: var(--danger); background: var(--danger-soft); }

/* ---------- proxy scrubber ---------- */

.proxy-frame-wrap {
  position: relative;          /* anchors .player-hint over the frame */
  background: #000;
  border-radius: var(--radius-md);
  overflow: hidden;
  margin-bottom: 10px;
  border: 1px solid var(--border-soft);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 160px;
}
.proxy-frame-wrap { cursor: ew-resize; }
/* While dragging out a range, stop the frame's text/image getting selected under the
   cursor - otherwise a marking gesture leaves a highlighted mess behind it. */
.proxy-frame-wrap.selecting { user-select: none; }
.proxy-frame-wrap.selecting * { user-select: none; }
.proxy-frame { width: 100%; display: block; }
/* The level video sits exactly over the frame (preview_levels): same box, letterboxed the
   same way, so switching between them never shifts the picture. Overlays added after it
   (time badge, faces, hints) stay on top. */
.proxy-video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; background: #000; pointer-events: none;
}
.proxy-level-badge {
  position: absolute; top: 8px; left: 8px;
  background: rgba(0, 0, 0, 0.65); color: #fff;
  font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 999px;
  pointer-events: none;
}
/* Sprite-backed scrub frame (#15). A sheet is a background-image and so cannot be an
   <img src>; this div stands in for one. It has no intrinsic size, so its aspect ratio
   is set inline from the frame dimensions the manifest publishes. */
.proxy-frame-sprite { background-repeat: no-repeat; }

/* Anamorphic / non-square pixels. The frame is STORED squeezed and shown wide: the
   inline aspect-ratio (app.js desqueezeStyle) sets the box, and object-fit:fill is what
   stretches the picture into it instead of letterboxing inside it. A tile sizes from its
   height, the same way a sprite cell already does, so a wide clip stays inside the tile
   rather than pushing it out. */
.proxy-frame-desqueezed { object-fit: fill; }

.desqueeze-offer {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 10px; font-size: 13px;
}
.sel-squeeze { display: inline-flex; align-items: center; gap: 6px; }
.sel-squeeze input { width: 150px; }

/* Typed / pasted In and Out on the preview scrubber. */
.inout-field { display: flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--text-faint); }
.tc-input {
  width: 104px; padding: 4px 7px; font-size: 12px;
  font-variant-numeric: tabular-nums; font-family: inherit;
}
.tc-input.bad { border-color: var(--danger); color: var(--danger); }

/* ---------- the middle column as thumbnails ---------- */

/* The switch between the two layouts. A segmented pair rather than a dropdown: it is the
   control people flip most often in this header, and two icons that show which one is on
   cost one click where a menu costs two. */
.view-switch {
  flex: none;
  display: flex;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--panel-2);
}
.view-switch .vs-btn {
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 26px; padding: 0; margin: 0;
  background: none; border: none; border-radius: 0;
  color: var(--text-faint); cursor: pointer;
}
.view-switch .vs-btn + .vs-btn { border-left: 1px solid var(--border); }
.view-switch .vs-btn:hover { background: var(--hover); color: var(--text); }
.view-switch .vs-btn.active { background: var(--accent-soft); color: var(--accent); }
.view-switch .vs-btn .icon { width: 15px; height: 15px; }

/* The size slider. Overrides the full-width range default - this one lives in a header
   row beside buttons, not stacked in a settings panel. */
input[type="range"].thumb-slider {
  flex: none;
  width: 108px;
  margin: 0;
  cursor: pointer;
}

/* auto-fill, not auto-fit: an almost-empty folder should show two tiles at the size you
   asked for, not two tiles stretched across the whole pane. --thumb is the slider's value
   and is set inline, so dragging it re-flows what is already on screen without a redraw. */
.thumb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--thumb, 190px)), 1fr));
  gap: 14px;
  align-content: start;
  padding-bottom: 12px;
}

.tile {
  display: flex; flex-direction: column; gap: 6px;
  min-width: 0;
  padding: 6px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  user-select: none;
  transition: background 0.1s ease, border-color 0.1s ease;
}
.tile:hover { background: var(--panel); }
.tile.selected, .tile.selected:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}
/* Same pair as the rows: filled means selected, outlined means "this is the one open on
   the right". The search grid has no selection at all, so this is the only mark it wears -
   and without it a click on a tile left forty lookalike frames with nothing to say which
   one you had just opened. */
.tile.current:not(.selected), .tile.current:not(.selected):hover {
  border-color: var(--accent);
}
/* Same ring the rows get: the grid is one tab stop and the arrows move inside it, so this
   is the only thing saying where you are. */
.tile:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* A fixed 16:9 well on black, which is what almost all footage is and what an editor
   reads as "a picture of a shot". Anything a different shape sits inside it letterboxed
   rather than stretched - a squeezed frame is a lie about the footage. */
.tile-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  background: #000;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
}
/* An <img> has intrinsic dimensions and simply fits. The sprite-backed <div> (a published
   catalog's sheets) has none - its aspect ratio is inline, from the manifest - so it is
   sized by height and lets that ratio give it its width. */
.tile-frame .proxy-frame { width: auto; max-width: 100%; max-height: 100%; display: block; }
.tile-frame .proxy-frame-sprite { height: 100%; }
/* A frame has to FIT this 16:9 window, and which axis binds depends on its shape (app.js
   frameFitClass). Sizing every one from its height - what the sprite rule above does on
   its own - leaves anything WIDER than the tile clamped by max-width with its height
   unchanged: a published 2.39:1 clip drawn as 16:9, squashed. So every sprite frame gets
   one of these, and a desqueezed <img> does too (its stored ratio is not the one to fit).
   THESE MUST STAY BELOW THE TWO RULES ABOVE: same specificity, so source order is the
   only thing making them win. tests/test_thumbnail_view.js holds that. */
.tile-frame .proxy-frame-wide { width: 100%; height: auto; }
.tile-frame .proxy-frame-boxy { height: 100%; width: auto; }
.tile-frame-folder {
  aspect-ratio: 16 / 5;
  background: var(--panel-2);
  border-style: dashed;
}
.tile-frame-folder .icon { width: 26px; height: 26px; color: #ffc56e; }
/* A preview that exists but hasn't arrived yet (watchFrameLoad). A soft light sweeping
   across the dark frame, plus the word, so a slow sheet reads as "working" instead of as
   a black box. Both fade in after a beat so a cached frame never flashes it. */
.frame-loading { position: relative; }
.frame-loading::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(100deg, transparent 20%, rgba(255, 255, 255, 0.07) 50%, transparent 80%);
  background-size: 200% 100%;
  opacity: 0;
  animation: frame-sweep 1.4s ease-in-out infinite, frame-loading-in 0.2s ease 0.15s forwards;
}
.frame-loading-label {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 2;
  font-size: 11px; letter-spacing: 0.3px; color: rgba(255, 255, 255, 0.72);
  pointer-events: none;
  opacity: 0;
  animation: frame-loading-in 0.2s ease 0.15s forwards, pulse 1.6s ease-in-out 0.35s infinite;
}
.frame-load-failed { animation: none; opacity: 1; color: var(--warning-text); }
@keyframes frame-sweep { from { background-position: 150% 0; } to { background-position: -50% 0; } }
@keyframes frame-loading-in { to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .frame-loading::before { animation: frame-loading-in 0.2s ease 0.15s forwards; }
  .frame-loading-label { animation: frame-loading-in 0.2s ease 0.15s forwards; }
}

.tile-frame-empty { flex-direction: column; gap: 6px; background: var(--panel-2); }
.tile-frame-empty .icon { color: var(--text-faint); opacity: 0.65; }
/* Named, not just greyed: this clip is the one that opens to black for an editor after
   you publish, which is the same warning the list's chip carries. */
.tile-noprev { font-size: 10.5px; color: var(--warning-text); letter-spacing: 0.2px; }

/* How long the clip is, over its own frame - the one number worth reading at a glance
   when you are looking at pictures rather than at a table. */
.tile-dur {
  position: absolute; right: 5px; bottom: 5px;
  padding: 1px 5px; border-radius: 4px;
  background: rgba(0, 0, 0, 0.72); color: #fff;
  font-size: 10.5px; font-variant-numeric: tabular-nums;
  pointer-events: none;
}

/* TWO LINES, then ellipsis. Camera filenames differ in their last characters
   (A001_C012 next to A001_C013), so a name cut off at one line makes a folder of takes
   look like the same take twenty times - the exact failure the list column was fixed for.
   Two lines is enough for the tail to survive, and every tile is the same height. */
.tile-name {
  display: flex; align-items: flex-start; gap: 6px;
  font-size: 12px; line-height: 1.35; color: var(--text);
  min-width: 0;
}
.tile-name .icon { flex: none; width: 14px; height: 14px; margin-top: 1px; color: var(--text-faint); }
.tile-folder .tile-name .icon { color: #ffc56e; }
.tile-name span {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
  min-height: 2.7em;
}
/* Clutter (sidecars, hidden items) reads the same way it does in the list. */
.tile.row-sidecar .tile-name span,
.tile.row-hidden .tile-name span { color: var(--text-faint); font-style: italic; }
.tile.row-sidecar .tile-frame,
.tile.row-hidden .tile-frame { opacity: 0.65; }

/* A frame that had to be waited for fades in rather than cutting in (watchFrameLoad). */
.frame-arrived { animation: frame-arrive var(--dur-med) var(--ease-out); }
@keyframes frame-arrive { from { opacity: 0; } }

/* The skim, drawn: a hairline where the pointer is and the time it stands for. The clip's
   length hides while it shows - both sit in the same corner and only one is true now. */
.tile-scrub { position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity var(--dur-fast); }
.tile-frame.scrubbing .tile-scrub { opacity: 1; }
.tile-frame.scrubbing .tile-dur { opacity: 0; }
.tile-scrub i {
  position: absolute; top: 0; bottom: 0; left: var(--x, 0); width: 1px;
  background: rgba(255, 255, 255, 0.85); box-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.5);
}
.tile-scrub span:empty { display: none; }
.tile-scrub span {
  position: absolute; right: 5px; bottom: 5px;
  font: 600 10.5px/1 ui-monospace, "SF Mono", Menlo, monospace; font-variant-numeric: tabular-nums;
  color: #fff; background: rgba(0, 0, 0, 0.7); padding: 3px 5px; border-radius: var(--radius-xs);
}

/* ---------- a search result, and the stretch of it you marked ---------- */

/* WHERE the clip is, under its name. In the list this is a column; in the grid there is no
   column to put it in, and without it a search across three catalogs is forty pictures with
   no way to tell which shoot any of them came from. One line, then ellipsis - the full path
   is on the tooltip, same as the list's cell. */
.tile-where {
  font-size: 11px; color: var(--text-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
}

/* The horizontal axis of a search tile is TIME, and dragging across it marks a span. The
   col-resize cursor is the honest one: it says "this drags sideways and the sideways
   direction means something", which is the whole gesture in one glyph. */
.tile-searchable .tile-frame { cursor: col-resize; }

/* What you marked, over the frame it was marked on. Deliberately the same violet the
   player's In/Out band uses - it IS that band, drawn on a smaller picture, and the right-
   hand column shows the identical range the moment the mouse comes up. */
/* --accent-band is the token for "a selected range over a dark track", which is exactly
   what this is - the trim strip's own fill, drawn on a picture instead of on a track.
   NOT --accent-soft: that is 0.14 alpha, meant for a tinted panel on a light-ish surface,
   and over a frame of footage it is invisible. The mark has to be readable after the mouse
   comes up, because from then on it is the tile's answer to "which bit of this did I send
   to the right-hand column". */
.tile-range {
  position: absolute; top: 0; bottom: 0;
  background: var(--accent-band);
  border-left: 2px solid var(--accent);
  border-right: 2px solid var(--accent);
  box-sizing: border-box;
  pointer-events: none;
}
/* Live, the edges brighten. Only the edges: they are the part that says where In and Out
   actually landed, and lifting the fill as well would wash out the frame underneath at the
   moment you are looking hardest at it. */
.tile-frame.tile-marking .tile-range {
  border-left-color: var(--accent-hi);
  border-right-color: var(--accent-hi);
}

/* ---------- folders that open in place ---------- */

.tree-toggle {
  flex: none; width: 18px; height: 18px; padding: 0; margin: 0 1px 0 -4px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: 3px;
  color: var(--text-faint); cursor: pointer;
}
.tree-toggle:hover { background: var(--hover); color: var(--text); }
.tree-toggle .chev { width: 13px; height: 13px; transition: transform 0.12s ease; }
.tree-toggle.open .chev { transform: rotate(90deg); }

/* "This folder is empty" / "couldn't read" shown inline under an expanded folder. */
.row-note td { font-size: 11.5px; color: var(--text-faint); font-style: italic; padding: 4px 8px; }
.row-note:hover { background: none; }

/* Status column */
.cell-ok { color: var(--accent); font-size: 11.5px; }
.cell-warn { display: inline-flex; align-items: center; gap: 4px; color: var(--danger); font-size: 11.5px; }
.cell-warn svg { width: 12px; height: 12px; }

/* Back button sits before the breadcrumb; the chevron points left. */
.tree-header .back-btn { flex: none; }
.tree-header .back-btn .chev-back { width: 14px; height: 14px; transform: rotate(180deg); }
.tree-header .back-btn:disabled { opacity: 0.4; cursor: default; }

/* ---------- the beta tester's page on vampedit.com ---------- */

.beta-page {
  min-height: 100vh; background: var(--bg);
  padding: 40px 20px; overflow-y: auto;
}
.beta-box {
  max-width: 640px; margin: 0 auto;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 30px 32px;
}
.beta-box h1 { font-size: 25px; font-weight: 800; letter-spacing: -0.5px; margin: 18px 0 12px; }
.beta-box h2 { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.7px;
  color: var(--text-faint); margin: 26px 0 8px; }
.beta-box p { font-size: 14px; line-height: 1.65; color: var(--text-dim); margin: 0 0 12px; }
/* These outlived the beta page they were named for - the hub's admin console is the only
   thing that draws them now. Kept under the old names rather than renamed, because a
   rename here touches nothing but the diff. */
.beta-lede { font-size: 15px !important; color: var(--text) !important; }
.beta-foot { margin-top: 24px; text-align: center; }

/* ---------- the hub's admin page ---------- */
/* Rides on .beta-page/.beta-box so there is one full-page shell on the hub rather than a
   second one that drifts. Wider, because this one carries tables. */
.beta-box:has(.adm-body) { max-width: 860px; }
.adm-sec { margin-bottom: 26px; }
/* The dashboard tab wants the room: tiles and charts side by side, not stacked. */
.beta-box:has(.dash) { max-width: 1120px; }
.dash { position: relative; display: flex; flex-direction: column; gap: 16px; }
.dash-top { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
.dash-attn { display: flex; flex-wrap: wrap; gap: 6px; }
.dash-chip { cursor: pointer; font-family: inherit; }
.dash-chip:hover { background: var(--warning-soft); }
.dash-range { display: flex; gap: 4px; align-items: center; }
.dash-rbtn {
  background: var(--bg); border: 1px solid var(--border); color: var(--text-dim);
  border-radius: var(--radius-sm); padding: 3px 9px; font-size: 12px; font-weight: 600; cursor: pointer;
}
.dash-rbtn.on { color: var(--text); border-color: var(--accent-dim); background: var(--accent-soft); }
.dash-range .link-btn { margin-left: 8px; font-size: 12px; }
.dash-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.dash-tile {
  display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start;
  text-align: left; font: inherit; color: inherit; width: 100%; height: auto; margin: 0;
  white-space: normal; overflow-wrap: anywhere;
  background: var(--bg-soft); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 12px 14px; min-width: 0;
}
.dash-tile.link { cursor: pointer; transition: border-color var(--dur-fast); }
.dash-tile.link:hover { border-color: var(--accent-dim); }
.dash-tile-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.6px; font-weight: 700; color: var(--text-faint); }
.dash-tile-value { font-size: 26px; font-weight: 800; letter-spacing: -0.5px; margin: 4px 0 2px; color: var(--text);
  font-variant-numeric: tabular-nums; }
.dash-tile-sub { font-size: 12px; line-height: 1.45; color: var(--text-dim); }
.dash-delta { font-weight: 700; font-size: 11.5px; white-space: nowrap; color: var(--text-dim); }
.dash-delta.up { color: var(--success-text); }
.dash-delta.down { color: var(--danger-text); }
.dash-warn { color: var(--warning-text); }
.dash-charts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.dash-two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.dash-card {
  background: var(--bg-soft); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 12px 14px; min-width: 0; overflow-x: auto;
}
.dash-card-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  font-size: 13px; font-weight: 700; margin-bottom: 10px; }
.dash-card-head .adm-dim, .dash-card-head .link-btn { font-size: 12px; font-weight: 500; }
.dash-plot { position: relative; height: 120px; }
.dash-grid { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: space-between;
  border-top: 1px dashed var(--border-soft); border-bottom: 1px solid var(--border);
  font-size: 10px; color: var(--text-faint); pointer-events: none; }
.dash-grid span { padding: 2px 0 0 2px; }
.dash-grid span:last-child { display: none; }
.dash-bars { position: absolute; inset: 0; display: flex; align-items: stretch; gap: 2px; }
.dash-bars.dense { gap: 1px; }
.dash-bar-hit { flex: 1 1 0; display: flex; align-items: flex-end; min-width: 0; cursor: default; }
.dash-bar { width: 100%; background: var(--accent); border-radius: 3px 3px 0 0; }
.dash-bars.dense .dash-bar { border-radius: 1px 1px 0 0; }
.dash-bar-hit:hover .dash-bar { background: var(--accent-hi); }
.dash-axis { display: flex; justify-content: space-between; font-size: 10.5px; color: var(--text-faint); margin-top: 4px; }
.dash-tip {
  position: absolute; transform: translate(-50%, -100%); pointer-events: none; z-index: 5;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 4px 8px; font-size: 12px; white-space: nowrap; box-shadow: var(--shadow-md);
}
.dash-frow { display: grid; grid-template-columns: 110px minmax(0, 1fr) auto; gap: 10px; align-items: center;
  font-size: 12.5px; margin-bottom: 8px; }
.dash-flabel { color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-ftrack { height: 10px; background: var(--bg); border-radius: var(--radius-pill); overflow: hidden; }
.dash-ffill { height: 100%; background: var(--accent); border-radius: var(--radius-pill); }
.dash-fnum { font-variant-numeric: tabular-nums; text-align: right; min-width: 70px; }
.dash-feed { list-style: none; margin: 0; padding: 0; font-size: 12.5px; }
.dash-feed li { display: grid; grid-template-columns: 18px minmax(0, 1fr) auto; gap: 8px; align-items: center;
  padding: 6px 0; border-bottom: 1px solid var(--border-soft); }
.dash-feed li:last-child { border-bottom: none; }
.dash-feed-ic .icon { width: 14px; height: 14px; color: var(--text-faint); }
.dash-feed-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-health { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px 20px; }
.dash-hrow { display: grid; grid-template-columns: 70px minmax(0, 1fr) auto; gap: 8px; align-items: center;
  font-size: 12.5px; padding: 5px 0; border-bottom: 1px solid var(--border-soft); }
.dash-hrow .adm-dim { text-align: right; }
@media (max-width: 900px) {
  .dash-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dash-charts, .dash-two, .dash-health { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 520px) {
  .beta-box:has(.dash) { padding: 22px 14px; }
  .dash-tile-value { font-size: 22px; }
  .dash-hrow { grid-template-columns: 64px minmax(0, 1fr); }
  .dash-hrow .adm-dim { grid-column: 2; text-align: left; }
}

/* The one control on this page that changes a RULE rather than a record, so it's boxed
   off from the invite list it sits above instead of reading as another row of it. */
.adm-invmode {
  padding: 12px 14px; border: 1px solid var(--border);
  border-radius: var(--radius-md); background: var(--bg);
}
.adm-invmode h2 { margin: 0 0 8px; }
.adm-invmode .adm-dim { margin-top: 6px; line-height: 1.5; }
.adm-count {
  display: inline-block; margin-left: 6px; padding: 1px 7px;
  border-radius: var(--radius-pill, 999px); background: var(--bg);
  border: 1px solid var(--border); color: var(--text-dim);
  font-size: 11px; font-weight: 700; letter-spacing: 0;
}
.adm-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.adm-table th {
  text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--text-faint); font-weight: 700; padding: 6px 10px 6px 0;
  border-bottom: 1px solid var(--border);
}
.adm-table td { padding: 9px 10px 9px 0; border-bottom: 1px solid var(--border); vertical-align: top; }
.adm-table tr:last-child td { border-bottom: none; }
.adm-dim { color: var(--text-faint); }
.adm-tag {
  display: inline-block; padding: 1px 7px; border-radius: var(--radius-pill, 999px);
  background: var(--bg); border: 1px solid var(--border);
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px;
  color: var(--text-dim);
}
.adm-tag.warn { border-color: rgba(230, 176, 116, 0.45); color: #e6b074; }
/* Whether mail can leave the box, above the compose form. Colour-coded, but never ONLY
   colour-coded - each state carries its own worded tag, so the difference survives a
   greyscale screen and a colour-blind reader. */
.adm-mail {
  padding: 11px 13px; margin-bottom: 16px;
  border: 1px solid var(--border); border-left-width: 3px;
  border-radius: var(--radius-md); background: var(--bg);
}
.adm-mail p { margin: 6px 0 0; font-size: 12.5px; line-height: 1.55; }
.adm-mail code { font-size: 12px; word-break: break-all; }
.adm-mail.ok { border-left-color: var(--success, #6bbf7b); }
.adm-mail.off, .adm-mail.bad { border-left-color: #e6b074; }
.adm-msg { border: 1px solid var(--border); border-radius: var(--radius-ms); padding: 10px 12px; margin-bottom: 8px; }
.adm-msg-head { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; font-size: 12.5px; margin-bottom: 5px; }
.adm-msg-body { font-size: 13.5px; line-height: 1.6; color: var(--text-dim); white-space: pre-wrap; }
/* Tables must scroll inside the card rather than widening the page on a phone. */
.adm-body { overflow-x: auto; }

.adm-tabs {
  display: flex; flex-wrap: wrap; gap: 4px;
  margin: 18px 0 16px; border-bottom: 1px solid var(--border);
}
.adm-tab {
  background: transparent; border: none; border-bottom: 2px solid transparent;
  color: var(--text-faint); font-size: 12.5px; font-weight: 600;
  padding: 8px 12px; cursor: pointer; margin-bottom: -1px;
}
.adm-tab:hover { color: var(--text); }
.adm-tab.on { color: var(--text); border-bottom-color: var(--accent, var(--text)); }

.adm-form { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.adm-form input { flex: 1 1 180px; min-width: 0; }
.adm-form button { flex: 0 0 auto; }

/* Actions wrap instead of stretching the table on a narrow window. */
.adm-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.adm-actions .link-btn { padding: 0; }
/* Scoped, NOT a bare .danger: .kebab-item.danger already exists and two same-name blocks
   at equal specificity merge property by property rather than replacing each other -
   which is exactly how .trim-handle drew itself 6px off-position last week. */
.adm-actions .link-btn.danger,
.adm-msg .link-btn.danger { color: var(--danger-text); }
.adm-actions .link-btn.danger:hover,
.adm-msg .link-btn.danger:hover { color: var(--danger); }

/* Compose a broadcast. The two audience choices are full rows rather than a dropdown,
   because the difference between them (everyone vs opted-in, unsubscribe or not) needs
   saying in words at the moment of choosing, not hiding behind a label. */
.adm-compose { display: flex; flex-direction: column; gap: 10px; margin-bottom: 22px; }
.adm-aud {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-ms);
  font-size: 13px; line-height: 1.5; color: var(--text-dim); cursor: pointer;
}
.adm-aud:hover { border-color: var(--accent-dim); }
.adm-aud input { margin-top: 2px; flex: none; }
.adm-aud strong { color: var(--text); }
.adm-compose textarea { width: 100%; box-sizing: border-box; resize: vertical; }
/* The preview is a whole email document, so it lives in an iframe and cannot inherit or
   fight the console's own styles. */
.adm-preview { margin-top: 12px; }
.adm-preview iframe {
  width: 100%; height: 520px; border: 1px solid var(--border);
  border-radius: var(--radius-ms); background: #fff;
}

/* Turnstile's widget. Empty and zero-height until Cloudflare draws into it, so a
   deployment with no key configured shows no gap where a captcha would have been. */
.ts-host { margin: 6px 0; }
.ts-host:empty { margin: 0; }

.adm-tag.ok { border-color: var(--success-soft); color: var(--success); }
/* A handled message stays readable but stops competing with the ones that aren't. */
.adm-msg.done { opacity: 0.6; }
.adm-msg .adm-actions { margin-top: 8px; }

/* ---------- media errors, said in English ---------- */
/* Replaces raw ffmpeg/ffprobe output in the Preview card, File info and the Jobs queue.
   Deliberately not styled as a red alarm: a damaged file is information, not an alert
   about something the user did wrong. */
.media-error {
  border: 1px solid rgba(230, 116, 116, 0.35);
  background: rgba(230, 116, 116, 0.07);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin: 8px 0;
}
.media-error .me-title {
  display: flex; align-items: flex-start; gap: 7px;
  font-size: 13px; font-weight: 600; color: var(--text);
}
.media-error .me-title svg { width: 15px; height: 15px; flex: none; margin-top: 1px; color: var(--danger); }
.media-error .me-detail { font-size: 12px; color: var(--text-dim); line-height: 1.5; margin-top: 5px; }
.media-error .me-raw { margin-top: 8px; }
.media-error .me-raw summary {
  font-size: 11.5px; color: var(--text-faint); cursor: pointer; list-style: none;
  display: flex; align-items: center; gap: 10px;
}
/* The copy button sits IN the summary so it's reachable without opening the disclosure
   first - the person reporting a bug doesn't need to read the text to send it. */
.media-error .me-raw .me-copy { font-size: 11px; padding: 0; }
.media-error .me-raw summary::-webkit-details-marker { display: none; }
.media-error .me-raw summary::before { content: "▸ "; }
.media-error .me-raw[open] summary::before { content: "▾ "; }
.media-error .me-raw summary:hover { color: var(--text-dim); }
.media-error .me-raw pre {
  margin: 6px 0 0; padding: 8px; max-height: 22em; overflow: auto;
  user-select: text; -webkit-user-select: text;
  background: var(--panel-2); border-radius: var(--radius-sm);
  font-size: 11px; line-height: 1.45; color: var(--text-faint);
  white-space: pre-wrap; word-break: break-word;
}
.job-error .media-error { margin: 4px 0 0; }

/* What a keywording run will cost, beside the button that starts it. */
.sel-cost { font-size: 11.5px; white-space: nowrap; }
/* ...and the same figure on its row in a right-click menu. .kebab-item is a flex row, so
   the hint is just another flex child, pushed to the end. It lands a moment after the menu
   opens (see showContextMenu) and is empty until then, which is why it can't be given any
   width or padding of its own - an empty span must take up nothing at all. */
.kebab-hint { margin-left: auto; padding-left: 14px; font-size: 11px; color: var(--text-faint); white-space: nowrap; }
.kebab-hint:empty { display: none; padding: 0; }

/* A camera-raw clip is a fact, not a fault: same block, none of the red. */
.media-error.raw-note {
  border-color: var(--border-soft);
  background: var(--panel-2);
}
.media-error.raw-note .me-title svg { color: var(--text-faint); }

/* A later RED piece playing the preview its take already has (see worker.covering_file). */
.proxy-covered { display: flex; align-items: center; gap: 6px; margin: 0 0 8px; }
.proxy-covered svg { width: 13px; height: 13px; flex: none; opacity: 0.7; }

/* ---------- people in a clip ---------- */

.face-row {
  display: flex; align-items: center; gap: 10px;
  padding: 6px; border-radius: var(--radius-sm);
  cursor: pointer; border: 1px solid transparent;
}
.face-row:hover { background: var(--hover); }
.face-row.active { border-color: var(--accent); background: var(--accent-soft); }
.face-thumb {
  flex: none; border-radius: 50%;
  background-color: var(--panel-2); background-repeat: no-repeat;
  border: 2px solid var(--border-soft);
}
.face-row.active .face-thumb { border-color: var(--accent); }
.face-meta { min-width: 0; flex: 1; }
.face-name { font-size: 13px; }
.face-name-input { width: 100%; max-width: 220px; font-size: 12.5px; padding: 4px 8px; }
.face-named-text { font-weight: 600; cursor: pointer; }
.face-named-text:hover { color: var(--accent); text-decoration: underline; }
.face-when { font-size: 11px; color: var(--text-faint); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Each appearance time trims the preview to it. Styled to stay part of the sentence -
   inheriting size and colour so the line still reads as one phrase - and only underlining
   on hover, which is what tells you the numbers are reachable without making the row look
   like a row of buttons. */
.face-when-span { font: inherit; color: inherit; padding: 0; vertical-align: baseline; }
.face-when-span:hover { color: var(--text); text-decoration: underline; }

/* Talent releases (T21). Amber inside the warning window, red once the term has run out.
   A COLOUR, NOT A PERMISSION - nothing consults these before serving a file, and the
   dialog says so in words too. Red here means "you should know", never "you can't". */
/* ---- the talent release dialog -------------------------------------------------
   It was one flat run of rows, so "until when", "what it allows" and "who to tell"
   all looked like the same kind of question. Grouping them is most of what made the
   window feel disorganised. */
.rel-group { margin: 0 0 16px; }
.rel-group + .rel-group { padding-top: 14px; border-top: 1px solid var(--border-soft); }
.rel-group-title {
  font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-dim); margin-bottom: 8px;
}

/* A choice you'd read before ticking, rather than a checkbox in a right-hand column.
   "No expiry (perpetual)" was the smallest control in the window and is the commonest
   thing a release says. */
.rel-choice {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 11px; margin-bottom: 8px; cursor: pointer;
  border: 1px solid var(--border-soft); border-radius: 8px;
}
.rel-choice:hover { border-color: var(--accent-dim); }
.rel-choice input { margin-top: 2px; flex: 0 0 auto; }
.rel-choice-hint { display: block; font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.rel-choice.is-fixed { opacity: .75; cursor: default; }
.rel-choice.is-fixed:hover { border-color: var(--border-soft); }

/* The grants. Two columns so ten options are a glance rather than a scroll. */
.rel-usages { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 14px; }
@media (max-width: 560px) { .rel-usages { grid-template-columns: 1fr; } }
.rel-usage { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; }
.rel-usage input { flex: 0 0 auto; }
#rel-usage-note:not(:empty) { margin-top: 10px; }

.face-release { margin-left: 6px; font-size: 11px; vertical-align: middle; }
.rel-chip {
  display: inline-block; padding: 1px 7px; border-radius: 999px;
  font-size: 10.5px; font-weight: 600; white-space: nowrap;
}
.rel-ok { background: var(--hover); color: var(--text-faint); }
.rel-warning { background: rgba(224, 160, 50, 0.16); color: #d29225; }
.rel-expired { background: rgba(224, 82, 82, 0.16); color: #d65454; }
.rel-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 2px; border-bottom: 1px solid var(--border);
}
.rel-row:last-child { border-bottom: 0; }
.rel-row-name { font-size: 13px; font-weight: 600; }

/* "This person is in 4 other clips too" - offered after a name is set. */
.face-matches {
  margin: 4px 0 8px 62px;
  padding: 9px 11px;
  background: var(--accent-soft);
  border: 1px solid rgba(110, 139, 255, 0.3);
  border-radius: var(--radius-sm);
}
.face-matches .fm-text { font-size: 12px; color: var(--text-dim); line-height: 1.45; }
/* The faces themselves, so you can confirm before a name is applied. */
.face-matches .fm-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: 8px; margin-top: 9px;
}
.fm-face {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 6px 4px; border-radius: var(--radius-sm); cursor: pointer;
  background: var(--panel-2); border: 1px solid transparent;
}
.fm-face:hover { border-color: var(--border); }
.fm-face.off { opacity: 0.4; }
.fm-face input { accent-color: var(--accent); cursor: pointer; margin: 0; }
.fm-thumb {
  width: 56px; height: 56px; border-radius: 50%;
  background-color: var(--panel); background-repeat: no-repeat;
  border: 2px solid var(--border-soft);
}
.fm-face input:checked ~ .fm-thumb { border-color: var(--accent); }
/* Naming the ticked faces: each ring turns green in turn (staggered from JS), then the
   box folds into its one-line result. */
.fm-face.named { animation: fm-named var(--dur-slow) var(--ease-out) both; }
.fm-face.named .fm-thumb { border-color: var(--success); transition: border-color var(--dur-fast); }
@keyframes fm-named { 40% { transform: scale(1.08); } }
.face-matches { animation: note-in var(--dur-med) var(--ease-out); }
.face-matches.leaving { animation: note-out var(--dur-med) var(--ease-in) forwards; overflow: hidden; }
.fm-done { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; animation: note-in var(--dur-med) var(--ease-out); }
.fm-done .icon { color: var(--success); }
/* A face that has just been given a name glows once. */
.face-row.just-named { animation: face-named 1.2s var(--ease-out); }
@keyframes face-named { 0% { box-shadow: 0 0 0 0 rgba(54, 211, 153, 0.5); border-color: var(--success); }
  60% { box-shadow: 0 0 0 6px rgba(54, 211, 153, 0); } }
.fm-where {
  font-size: 10px; color: var(--text-faint); max-width: 80px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fm-conf { font-size: 9.5px; font-weight: 600; }
.fm-conf.strong { color: var(--success); }
.fm-conf.fair { color: var(--text-dim); }
.fm-conf.weak { color: #e6a15c; }
.face-matches .fm-text svg { width: 13px; height: 13px; color: var(--success); vertical-align: -2px; margin-right: 4px; }
.face-matches .fm-actions { display: flex; align-items: center; gap: 12px; margin-top: 8px; }

/* The box drawn over the preview frame, following the selected person as you scrub. */
.face-box {
  position: absolute;
  border: 2px solid var(--accent);
  border-radius: 6px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.35);   /* dim everything except the face */
  pointer-events: none;
  transition: left 0.12s linear, top 0.12s linear, width 0.12s linear, height 0.12s linear;
}
.face-box::after {
  content: attr(data-label);
  position: absolute; left: 50%; bottom: -22px; transform: translateX(-50%);
  font-size: 11px; font-weight: 600; color: #fff; white-space: nowrap;
  background: var(--accent); border-radius: 999px; padding: 1px 8px;
}
.face-box[data-label=""]::after { display: none; }
.proxy-controls { display: flex; align-items: center; gap: 12px; margin: 10px 0 8px; }
/* Phone-style trimmer: the playhead runs under two draggable In/Out handles.
   Layering is the whole trick - base (0) < dim (1) < band (1) < playhead (2) < handles (3)
   - so a handle is always grabbable and everywhere else scrubs.
   The 8px side padding is what keeps a handle parked at 0% or 100% fully on screen:
   positions are percentages of .trim-track, and the padding is its overhang room. */
/* ptrim-, not trim-: the request card's trim strip further up this file already owns
   .trim-handle. Two same-specificity blocks of the same name do NOT replace each other -
   the cascade merges them property by property - so this component was inheriting that
   one's margin-left:-6px (both handles drew 6px off-position) and its solid background,
   while pushing its own transform and size back onto the request card. Distinct names. */
.proxy-trim { position: relative; height: 30px; padding: 0 8px; }
.ptrim-track { position: relative; height: 100%; }
/* The generic input rule up at the top of this file also matches a range input and
   out-specifies the range rule below, so it hands the scrubber an opaque background
   and a border. Left alone, that paints straight over the band. Undo it here. */
.ptrim-track > input[type="range"] {
  position: absolute; left: 0; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 100%; height: 16px; margin: 0; padding: 0;
  background: transparent; border: none; border-radius: 0; }
.ptrim-base { position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%);
  height: 16px; z-index: 0; border-radius: var(--radius-xs);
  background: var(--bg-soft); border: 1px solid var(--border); }
.ptrim-dim { position: absolute; top: 50%; transform: translateY(-50%); height: 16px; z-index: 1;
  pointer-events: none; background: rgba(11, 13, 17, 0.55); border-radius: var(--radius-xs); }
.ptrim-dim-left { left: 0; }
.proxy-inout-band { position: absolute; top: 50%; transform: translateY(-50%); height: 16px; z-index: 1;
  pointer-events: none; background: var(--accent-soft);
  border-top: 2px solid var(--accent); border-bottom: 2px solid var(--accent); }
.ptrim-handle { position: absolute; top: 50%; transform: translate(-50%, -50%); z-index: 3;
  width: 16px; height: 28px; margin: 0; cursor: ew-resize; touch-action: none;
  background: none; border: none; opacity: 1;
  display: flex; align-items: center; justify-content: center; }
.ptrim-handle::before { content: ""; position: absolute; inset: 0 3px; border-radius: var(--radius-xs);
  background: var(--accent-dim); box-shadow: 0 1px 5px rgba(0, 0, 0, 0.5); }
.ptrim-handle.set::before { background: var(--accent); }
.ptrim-handle:hover::before, .ptrim-handle.dragging::before { background: var(--accent-hi); }
/* These are tabbable (role=slider) but had outline:none and no shadow, so keyboard focus
   was completely invisible on them - the one control in the app with no focus state at all. */
.ptrim-handle:focus-visible { outline: none; }
.ptrim-handle:focus-visible::before {
  background: var(--accent-hi);
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent);
}

/* The strip is the "request just this bit" control and nothing said so. */
.trim-hint { font-size: 11.5px; color: var(--text-faint); margin: 2px 0 8px; }
.ptrim-handle i { position: relative; width: 2px; height: 11px; border-radius: 1px;
  background: rgba(255, 255, 255, 0.9); }
.ptrim-handle:focus-visible { outline: none; }
.ptrim-handle:focus-visible::before { box-shadow: 0 0 0 3px var(--accent-soft); }
.proxy-inout { display: flex; align-items: center; gap: 8px; margin: 8px 0 0; }
/* The In/Out fields sit under the track they describe; Request is a commit action and gets
   its own line rather than sharing one with its own inputs. Six controls abreast in a 520px
   pane was the complaint. */
.proxy-inout-wrap { display: flex; flex-direction: column; }
.proxy-inout-actions { justify-content: flex-start; }
.proxy-inout .inout-readout { margin-left: auto; font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.proxy-controls #proxy-play { min-width: 92px; justify-content: center; }
.proxy-speed-label { font-size: 12px; color: var(--text-faint); display: flex; align-items: center; gap: 6px; }
.audio-player { display: flex; align-items: center; gap: 12px; padding: 22px 4px; }
.audio-player #aud-play { min-width: 92px; justify-content: center; flex: 0 0 auto; }
.audio-player input[type="range"] { flex: 1; width: auto; margin: 0; accent-color: var(--accent); cursor: pointer; }
.audio-time { font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; flex: 0 0 auto; }
.marquee { position: fixed; z-index: 300; pointer-events: none; border: 1px solid var(--accent); background: rgba(110, 91, 255, 0.14); border-radius: 2px; }
.proxy-speed-label select { padding: 4px 6px; font-size: 12px; }
.proxy-time-badge {
  position: absolute;
  bottom: 8px;
  right: 8px;
  background: rgba(0, 0, 0, 0.65);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: 999px;
  font-variant-numeric: tabular-nums;
}

input[type="range"] {
  -webkit-appearance: none;
  width: 100%;
  height: 4px;
  background: var(--border);
  border-radius: 99px;
  margin: 6px 0 12px;
  padding: 0;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  box-shadow: 0 0 0 4px var(--accent-soft);
}
input[type="range"]::-moz-range-thumb {
  width: 14px; height: 14px;
  border: none;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
}

/* ---------- auth screen ---------- */

.auth-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.auth-box { width: 100%; max-width: 380px; }
.auth-logo {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  margin-bottom: 28px;
}
.auth-logo .brand-mark { width: 38px; height: 38px; border-radius: 11px; }
.auth-logo .brand-mark svg { width: 20px; height: 20px; }
.auth-logo span { font-size: 19px; font-weight: 700; }
.auth-card {
  background: var(--panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 22px;
  box-shadow: var(--shadow-lg);
}
.auth-tabs { display: flex; gap: 4px; background: var(--bg-soft); border-radius: 999px; padding: 3px; margin-bottom: 18px; }
.auth-tabs button {
  flex: 1; background: transparent; color: var(--text-faint); font-weight: 600;
  border-radius: 999px; padding: 7px 0; justify-content: center;
}
.auth-tabs button.active { background: var(--panel-2); color: var(--text); }
.auth-tabs button:hover:not(.active) { color: var(--text-dim); filter: none; }
.auth-field-label { font-size: 12px; color: var(--text-faint); margin: 0 0 5px; font-weight: 500; }
.auth-foot { text-align: center; font-size: 12px; color: var(--text-faint); margin-top: 16px; }

/* ---------- folder-card kebab menu ---------- */
.folder-card { cursor: pointer; }
.folder-card .attached-top { position: relative; }
.kebab-btn {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 18px;
  line-height: 1;
  padding: 2px 8px;
  cursor: pointer;
  border-radius: var(--radius-sm);
  align-self: flex-start;
}
.kebab-btn:hover { background: var(--hover); color: var(--text); }
.kebab-menu {
  position: absolute;
  top: 26px;
  right: 0;
  z-index: 40;
  min-width: 200px;
  display: flex;
  flex-direction: column;
  /* Frosted glass (Nick's call): near-opaque panel + blur so the card text
     underneath reads as texture, never as competing words. */
  background: rgba(22, 25, 32, 0.9);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  padding: 4px;
}
.kebab-item {
  display: flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: none;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  text-align: left;
  padding: 8px 10px;
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.kebab-menu.context-menu { position: fixed; top: auto; right: auto; }
.kebab-item:hover { background: var(--hover); }
.kebab-item.danger { color: var(--danger); }
.kebab-item svg { width: 14px; height: 14px; flex-shrink: 0; }

/* ---------- request journey + delivery ---------- */
.req-journey { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
               font-size: 11.5px; margin: 4px 0 2px; }
.req-journey .jr.done { color: var(--text-faint); }
.req-journey .jr.now { color: var(--accent); font-weight: 600; }
.req-journey .jr.todo { color: var(--text-faint); opacity: 0.5; }
.req-journey .jr-sep { color: var(--text-faint); opacity: 0.5; }
.req-highlight { outline: 2px solid var(--accent); border-radius: var(--radius-sm); }

.connect-banner {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  max-width: 520px;
  padding: 12px 16px;
  background: rgba(22, 25, 32, 0.9);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  font-size: 13px;
}
.connect-banner svg { width: 16px; height: 16px; color: var(--accent); flex-shrink: 0; }
.connect-banner .cb-status { flex-basis: 100%; font-size: 12px; }

.requests-all { border-bottom: 1px solid var(--border); }
.dash-status { font-size: 12px; margin-top: 2px; }

/* In-app text prompt (replaces the OS prompt() the native window can't do) */
.vp-modal { max-width: 420px; }
.vp-label { font-size: 12.5px; color: var(--text-dim); margin-bottom: 8px; }
/* The "don't ask again" tick box, borrowing .set-toggle from Settings so it reads the same
   as every other switch in the app - minus the row chrome, which belongs to a stacked list
   of settings and here would draw a second line directly above the footer's own border. */
/* Room for the tick box and both buttons on one line - at the plain 420px the label and
   "Publish anyway" fight over the same space. */
.vp-modal-wide { max-width: 560px; }
.vp-remember-row { padding: 0; border-bottom: 0; font-size: 12.5px; }
.vp-remember-row .set-field-name { font-weight: 400; color: var(--text-dim); }
.vp-input { width: 100%; font-size: 14px; padding: 9px 12px; }

/* ---------- New Catalog ---------- */
/* An inline VIEW in the browser pane (not a modal), so the library stays in view while
   you pick. Reuses .modal-head/.modal-body/.modal-foot for its internal chrome. */
.nc-view {
  display: flex; flex-direction: column;
  max-width: 640px; margin: 0 auto;
  background: var(--panel-2, #14171d);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg, 12px);
}
.nc-step { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--text-faint); margin-bottom: 8px; }
.nc-step .set-hint { text-transform: none; letter-spacing: 0; font-weight: 400; }
.nc-crumb { font-size: 12px; color: var(--text-dim); margin-bottom: 6px; }
.nc-crumb-link { color: var(--accent); cursor: pointer; }
.nc-list { max-height: 240px; overflow-y: auto; border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 4px; }
.nc-row { display: flex; align-items: center; gap: 8px; padding: 7px 8px; border-radius: var(--radius-sm); }
.nc-row:hover { background: var(--hover); }
.nc-row.on { background: var(--accent-soft); }
.nc-row input[type=checkbox] { flex: 0 0 auto; }
.nc-row-main { flex: 1; min-width: 0; }
.nc-row-name { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.nc-row-name svg { width: 14px; height: 14px; flex-shrink: 0; }
.nc-row-sub { font-size: 11px; color: var(--text-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nc-row .nc-open { flex: 0 0 auto; }
.nc-up { cursor: pointer; color: var(--text-dim); }
.nc-up svg { transform: rotate(90deg); width: 14px; height: 14px; }

/* ---------- New Catalog: disclosure tree ----------
   The familiar Finder/Hedge/ShotPut shape: caret on the LEFT opens a folder in place,
   clicking the row selects it, children indent underneath, nothing navigates away. */
.nc-tree { max-height: 320px; }
.nc-tree .nc-row { cursor: pointer; }
.nc-caret { flex: 0 0 auto; width: 20px; height: 20px; display: flex; align-items: center;
  justify-content: center; background: none; border: none; padding: 0; cursor: pointer;
  color: var(--text-faint); border-radius: var(--radius-sm); }
.nc-caret:hover { color: var(--text); background: var(--hover); }
.nc-caret svg { width: 13px; height: 13px; transition: transform 0.12s ease; }
.nc-caret.open svg { transform: rotate(90deg); }
/* A folder inside something you've already picked can't be picked separately - show it
   greyed with the reason rather than letting you make a catalog inside a catalog. */
.nc-row.nc-blocked { opacity: 0.5; cursor: default; }
.nc-row.nc-blocked:hover { background: none; }
.nc-row-note { flex: 0 0 auto; font-size: 11px; color: var(--text-faint); font-style: italic; }
.nc-kid-note { font-size: 11.5px; color: var(--text-faint); padding-top: 3px; padding-bottom: 5px; }
.nc-path-add { display: flex; gap: 8px; margin-top: 8px; }
.nc-path-add input { flex: 1; font-size: 12.5px; padding: 8px 10px; }
.nc-selected { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; }
.nc-chip { display: inline-flex; align-items: center; gap: 5px; font-size: 12px;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 999px; padding: 3px 6px 3px 9px; }
.nc-chip svg { width: 12px; height: 12px; }
.nc-chip button { background: none; border: none; color: var(--text-faint); font-size: 15px;
  cursor: pointer; padding: 0 2px; line-height: 1; }
.nc-chip button:hover { color: var(--danger); }
.nc-opt { display: flex; align-items: flex-start; gap: 8px; font-size: 13px; padding: 6px 0; line-height: 1.4; }
.nc-opt input { margin-top: 2px; flex: 0 0 auto; }
.nc-opt.disabled { opacity: 0.55; }

/* ---------- misc ---------- */

.error {
  color: var(--danger);
  background: var(--danger-soft);
  border: 1px solid rgba(255, 99, 99, 0.25);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  font-size: 12.5px;
  margin: 4px 0 10px;
}
.empty {
  color: var(--text-faint);
  font-size: 13px;
  padding: 10px 2px;
}
.empty-state {
  text-align: center;
  padding: 50px 20px;
  color: var(--text-faint);
}
.empty-state .icon-xl { color: var(--border); margin-bottom: 10px; }
.empty-state .empty-title { font-size: 14px; font-weight: 600; color: var(--text-dim); margin-bottom: 4px; }
.empty-state .empty-sub { font-size: 12.5px; }
.empty-state { animation: note-in var(--dur-slow) var(--ease-out); }
/* The keywords a miss was probably reaching for, one click each. */
.empty-near {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 6px;
  margin-top: 14px; font-size: 12px; animation: note-in var(--dur-med) var(--ease-out);
}
.chip-btn {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--radius-pill); padding: 3px 10px; font-size: 12px; cursor: pointer;
  transition: border-color var(--dur-fast), background var(--dur-fast);
}
.chip-btn:hover, .chip-btn:focus-visible { border-color: var(--accent); background: var(--accent-soft); }

/* An empty People card shows what it will hold: three faint faces, where the found ones go. */
.people-ghost { display: flex; gap: 6px; margin: 2px 0 8px; }
.people-ghost i {
  width: 28px; height: 28px; border-radius: 50%;
  background: radial-gradient(circle at 50% 38%, var(--border) 0 22%, transparent 23%),
              radial-gradient(ellipse at 50% 100%, var(--border) 0 45%, transparent 46%),
              var(--panel-2);
  border: 1px dashed var(--border);
}
.people-ghost i:nth-child(2) { opacity: 0.7; }
.people-ghost i:nth-child(3) { opacity: 0.45; }

.hint { font-size: 11.5px; color: var(--text-faint); margin: -4px 0 8px; }
.row { display: flex; gap: 8px; }
.row > * { flex: 1; }
.muted { color: var(--text-faint); font-size: 12px; }

/* ---------- Help & logs: bug vs feature ----------
   Two buttons that pick what KIND of report this is, so it arrives in Nick's inbox
   already labelled instead of needing to be read to find out. Uses the same .active
   convention as .auth-tabs rather than a second idea of "selected". */
.help-kind button.active {
  background: var(--accent); color: var(--accent-ink, #0b0b0c); border-color: var(--accent);
}
.help-kind button.active svg { stroke: currentColor; }

/* About: the legal text is long and pre-formatted, so it needs to scroll inside its own
   box rather than stretching the settings dialog to the width of the longest line. */
.about-legal {
  max-height: 320px; overflow: auto; white-space: pre-wrap; word-break: break-word;
  font-size: 11px; line-height: 1.5; color: var(--text-dim);
  background: var(--bg-soft); border-radius: 8px; padding: 10px; margin-top: 8px;
}

/* ---------- a saved API key ----------
   The state should be the thing you notice, not a grey word next to an empty box that
   looks like it wants typing. Badge reads as done; Edit is deliberately quiet, because
   changing a working key is the rare action. */
.key-saved { display: flex; align-items: center; gap: 10px; }
.key-saved-badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 13px; font-weight: 600; color: var(--success);
}
.key-saved-badge svg { width: 14px; height: 14px; stroke: currentColor; }

/* ---------- sign in with Google ---------- */
.auth-or { display: flex; align-items: center; gap: 10px; margin: 14px 0 10px;
           color: var(--text-faint); font-size: 12px; }
.auth-or::before, .auth-or::after { content: ""; flex: 1; height: 1px; background: var(--border); }
/* Google's brand guidance: their button stays white with their mark, in light and dark,
   because a recolored Google button is the thing people have learned not to trust. */
/* Used on an <a> (the website, where it navigates to /api/auth/google/start) AND on a
   <button> (the desktop, where it opens a browser and then polls). The font and cursor
   declarations are what keep those two looking identical - a button otherwise inherits
   the platform's control font and an arrow cursor. */
.btn-google {
  display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%;
  padding: 10px 14px; border-radius: 8px; text-decoration: none;
  background: #fff; color: #1f1f1f; border: 1px solid #dadce0;
  font-size: 14px; font-weight: 500;
  font-family: inherit; cursor: pointer;
}
.btn-google:hover { background: #f7f8f8; }
.btn-google:disabled { opacity: .6; cursor: default; }

/* ---------- what you've already asked for, drawn where you ask (#30) ---------- */
/* Requesting a second section of the same clip is ordinary - a long interview gives up
   several moments. So the answer to "have I already got this bit?" belongs ON the strip
   you're dragging, not in a sentence underneath it. Amber = asked for, green = already
   here, matching --warning / --success everywhere else rather than inventing two hues. */
.range-marks { position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%);
  height: 16px; z-index: 2; pointer-events: none; }
.range-mark { position: absolute; top: 0; bottom: 0; border-radius: 2px; min-width: 2px; }
.range-mark.wanted { background: var(--warning); opacity: .55; }
.range-mark.received { background: var(--success); opacity: .55; }
/* On the request card's strip the marks sit behind the selection, and that strip is
   taller, so they fill it rather than riding a centre line. */
.trim-strip .range-marks { top: 0; bottom: 0; height: auto; transform: none; }
.range-legend { display: flex; align-items: center; gap: 12px; font-size: 12px;
  color: var(--text-dim); margin: 2px 0 8px; flex-wrap: wrap; }
.range-legend .rl { display: inline-flex; align-items: center; gap: 5px; }
.range-legend .rl::before { content: ""; width: 10px; height: 10px; border-radius: 2px; }
.range-legend .rl.wanted::before { background: var(--warning); opacity: .75; }
.range-legend .rl.received::before { background: var(--success); opacity: .75; }

/* ---------- comments on clips, and @mentions (T33) ---------- */
/* A THIRD layer on the trim track, not a second track. Blue, because amber already means
   "asked for" and green "already here" - a comment is neither, it is somebody talking.
   Unlike the other two layers these are clickable, since the anchor is only worth having
   if it works in both directions. */
.comment-marks { position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%);
  height: 16px; z-index: 3; pointer-events: none; }
.comment-mark { position: absolute; top: 0; bottom: 0; border-radius: 2px; min-width: 3px;
  background: var(--accent); opacity: .65; cursor: pointer; pointer-events: auto; }
.comment-mark:hover { opacity: 1; }
.comment-mark.resolved { opacity: .28; }

.comment { padding: 8px 0; border-top: 1px solid var(--border); }
.comment:first-child { border-top: 0; }
.comment.resolved .comment-body { opacity: .55; }
/* Named in it - the one comment on the clip that is actually addressed to you. */
.comment.mentions-me { border-left: 2px solid var(--accent); padding-left: 9px; }
.comment-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12px; color: var(--text-dim); }
.comment-who { font-weight: 600; color: var(--text); }
.comment-at { font-variant-numeric: tabular-nums; }
.comment-flag { font-size: 11px; padding: 1px 6px; border-radius: 9px;
  background: var(--bg-alt); color: var(--text-dim); }
.comment-flag.mine { background: var(--accent); color: #fff; }
.comment-body { font-size: 13px; line-height: 1.5; margin: 3px 0 2px; white-space: pre-wrap;
  word-break: break-word; }
.comment-foot { display: flex; gap: 12px; font-size: 12px; }
.comment-kids { margin: 4px 0 0 14px; border-left: 1px solid var(--border); padding-left: 10px; }
.comment-empty { font-size: 13px; padding: 4px 0 8px; }
/* Where a marker click, or an email link, lands you. */
.comment-flash { background: var(--bg-alt); border-radius: 4px; }

.comment-compose { margin-top: 10px; position: relative; }
.comment-compose textarea { width: 100%; resize: vertical; font-family: inherit;
  font-size: 13px; }
.comment-anchor-row { display: flex; align-items: center; gap: 10px; font-size: 12px;
  color: var(--text-dim); margin-bottom: 5px; flex-wrap: wrap; }
.comment-anchor { font-variant-numeric: tabular-nums; }
.comment-actions { display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: 6px; }
/* The @ picker. Absolute so it overlays rather than shoving the card around while
   somebody is mid-sentence. */
.comment-picker { position: absolute; left: 0; right: 0; z-index: 20; max-height: 190px;
  overflow-y: auto; background: var(--bg); border: 1px solid var(--border);
  border-radius: 6px; box-shadow: 0 6px 20px rgba(0,0,0,.18); }
.comment-pick { display: flex; align-items: center; justify-content: space-between;
  gap: 10px; width: 100%; padding: 7px 10px; background: none; border: 0;
  font-family: inherit; font-size: 13px; color: var(--text); text-align: left;
  cursor: pointer; }
.comment-pick:hover { background: var(--bg-alt); }
.cp-role { font-size: 11px; color: var(--text-dim); }

.mention-list { padding: 4px 0; }
.mention { padding: 10px 12px; border-bottom: 1px solid var(--border); cursor: pointer; }
.mention:hover { background: var(--bg-alt); }
.mention-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: 12px; color: var(--text-dim); }
.mention-where { font-variant-numeric: tabular-nums; }
.mention-drive { margin-left: auto; font-size: 11px; }

/* ---------- agreeing to the terms (LEGAL/WIRING.md) ---------- */
/* The checkbox that has to be ticked before an account exists. Deliberately NOT styled
   down: it is the evidence the whole arrangement rests on, so it reads as part of the
   form rather than as small print somebody scrolled past. */
.legal-agree { display: flex; align-items: flex-start; gap: 9px; margin: 14px 0 4px;
  font-size: 12.5px; line-height: 1.5; color: var(--text-dim); cursor: pointer; }
.legal-agree input { width: auto; margin: 2px 0 0; flex: 0 0 auto; cursor: pointer; }
.legal-agree a { color: var(--accent); text-decoration: underline; }
/* The by-continuing line under a download button. Same two links, no box to tick: by the
   time somebody reaches it they ticked one seconds ago, and a second one is friction
   with nothing to show for it. */
.legal-note { font-size: 11.5px; line-height: 1.55; color: var(--text-faint);
  text-align: center; margin-top: 10px; }
.legal-note a { color: var(--text-dim); text-decoration: underline; }
/* The first-run / re-acceptance screen. A wider box than a sign-in card, because this is
   the install-time agreement Section 1 of the Terms talks about and it should look like
   one rather than like a cookie banner. */
.legal-gate .auth-box { max-width: 520px; }
.legal-gate .legal-docs { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 6px; }
.legal-gate .legal-docs a { display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 11px 14px; border: 1px solid var(--border-soft); border-radius: 10px;
  background: var(--bg-soft); color: var(--text); font-size: 13.5px; text-decoration: none; }
.legal-gate .legal-docs a:hover { border-color: var(--accent); color: var(--accent); }
.legal-gate .legal-docs .legal-open { font-size: 11.5px; color: var(--text-faint); }
.range-legend .rl.used::before { background: var(--usage); opacity: .85; }

/* ---------- what the edit used, drawn on the same strip ---------- */
/* Its own layer rather than more .range-marks, because the two answer different questions
   and must be readable at once: "I asked for this bit" and "this bit is in the cut" are
   routinely true of the same seconds. So usage gets a 5px rail along the BOTTOM of the
   16px track - present, unmistakable, and never covering a wanted mark. */
.usage-marks { position: absolute; left: 0; right: 0; top: 50%; margin-top: 3px;
  height: 5px; z-index: 2; pointer-events: none; }
.usage-mark { position: absolute; top: 0; bottom: 0; border-radius: 2px; min-width: 2px;
  background: var(--usage); }
/* A clip switched off on the timeline is on it without being in the cut, so it reads as an
   outline rather than a fill - there, but not claiming to be in the show. */
.usage-mark.disabled { background: transparent; border: 1px solid var(--usage); opacity: .8; }
.trim-strip .usage-marks { top: auto; bottom: 0; margin-top: 0; }

/* The list of imported edits in Settings. Coverage is the line that decides whether to
   trust the marks, so it is the one that gets a colour: green only when every clip on the
   timeline found its file. */
.ui-name { font-size: 13px; font-weight: 600; color: var(--text); }
.ui-cover { font-size: 12px; color: var(--warning-text); margin-top: 2px; }
.ui-cover.full { color: var(--success-text); }
/* Re-match sits beside Remove. Column, not row: the two read as a short list of what you
   can do to an import, and side by side the destructive one is a slip away from the safe
   one. */
.ui-acts { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex: none; }

/* The "Used in" column. Violet to match the rail on the preview, and quiet enough that a
   folder of used clips doesn't read as a folder of warnings. */
.cell-used { display: inline-flex; align-items: baseline; gap: 5px; color: var(--usage-text);
  min-width: 0; }
.cell-used .cu-seq { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell-used .cu-more { color: var(--text-dim); flex: none; }
/* Matched on a filename alone: the claim is weaker, so it is drawn weaker. The tooltip
   carries the why. */
.cell-used.loose { color: var(--text-dim); }
.cell-used.loose .cu-seq { text-decoration: underline dotted var(--usage); text-underline-offset: 2px; }


/* ---------- AI provider chooser (Settings → AI keywording) ---------- */
/* Three cards rather than a dropdown, because the choice is not a preference between
   interchangeable options - each one sends footage somewhere different, and a <select>
   hides two thirds of that at any moment. The room a card gives is what lets each option
   say where the frames go, which is the fact people are actually choosing between. */
.ai-modes { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }

.ai-mode {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 13px; border: 1px solid var(--border); border-radius: 8px;
  cursor: pointer; transition: border-color .12s, background .12s;
}
.ai-mode:hover { border-color: var(--border-strong, var(--accent)); }
.ai-mode.on { border-color: var(--accent); background: var(--accent-faint, rgba(79,43,212,.07)); }
.ai-mode input[type="radio"] { margin-top: 2px; flex: none; }

/* Local AI is presented as a card so the three options read as one set, but it is not
   selectable here - it is configured per capability (Transcripts), because that is where
   the choice is real. Dimmed and cursor-default so it does not invite a click that
   would do nothing. */
.ai-mode.static { cursor: default; opacity: .78; padding-left: 13px; }
.ai-mode.static:hover { border-color: var(--border); }

.ai-mode-txt { display: flex; flex-direction: column; gap: 3px; }
.ai-mode-name { font-size: 13px; font-weight: 600; color: var(--text); }
.ai-mode-blurb { font-size: 11.5px; line-height: 1.5; color: var(--text-dim); }
/* Where the footage goes. Emphasised rather than buried in the sentence: it is the one
   line on this screen somebody may need to quote to a client. */
.ai-mode-blurb em { font-style: normal; color: var(--text-faint); display: block; margin-top: 3px; }

.ai-allowance {
  padding: 11px 13px; margin-bottom: 12px;
  border: 1px solid var(--border); border-radius: 8px;
}
.ai-allow-head { font-size: 13px; color: var(--text); margin-bottom: 7px; }
.ai-allow-head strong { font-size: 15px; }
.ai-allow-bar {
  height: 6px; border-radius: 3px; overflow: hidden;
  background: var(--border); margin-bottom: 6px;
}
.ai-allow-fill { height: 100%; background: var(--accent); transition: width .2s; }

/* ---------- the keyword chip: drag one clip's keywords onto other footage ----------
   The chip lives in a clip's Tags card and is PICKED UP rather than clicked (app.js,
   startKeywordDrag). A pointer drag, so everything here is ordinary CSS state - there is
   no native drag image to style, which is the point: the ghost below is ours and looks
   the same in a WKWebView as it does in Chrome. */

.kw-chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px 6px 8px;
  border: 1px dashed var(--border);
  border-radius: 999px;
  background: var(--panel-2);
  font-size: 12.5px; color: var(--text-dim);
  cursor: grab;
  user-select: none;
  -webkit-user-drag: none;             /* the native drag would fight the pointer one */
  transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
}
.kw-chip:hover, .kw-chip:focus-visible {
  border-color: var(--accent); color: var(--text); background: var(--accent-soft);
  outline: none;
}
.kw-chip:active { cursor: grabbing; }
.kw-chip .icon { color: var(--accent); }
.kw-chip-grip { color: var(--text-faint); letter-spacing: -1px; }
.kw-chip-n { font-weight: 600; color: var(--text); }
.kw-chip-hint { color: var(--text-faint); }

/* What follows the pointer. pointer-events:none is load-bearing: the hit test under the
   cursor has to find the ROW, not this. */
.kw-ghost {
  position: fixed; z-index: 9999; pointer-events: none;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 11px;
  border-radius: 999px;
  background: var(--accent); color: #fff;
  font-size: 12px; font-weight: 600;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
}
.kw-ghost .icon { color: #fff; }

body.kw-dragging { cursor: grabbing; user-select: none; }

/* Where it would land. Drawn on EVERY row that would receive it, which for a drop onto a
   multi-selection is the whole selection - so what is about to happen is visible before
   it happens rather than reported afterwards. */
tr.row-file.kw-hot, tr.row-folder.kw-hot,
tr.row-file.kw-hot:hover, tr.row-folder.kw-hot:hover {
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 2px var(--accent);
}
.tile.kw-hot { outline: 2px solid var(--accent); outline-offset: 1px; border-radius: var(--radius-md); }

/* The way back out, on the note that says what just happened. */
.kw-undo-note { display: flex; align-items: center; gap: 10px; }
.kw-undo-note .kw-undo-btn { color: inherit; text-decoration: underline; font-weight: 600; }
.kw-undo-note .kw-undo-key {
  font-size: 11px; color: inherit; opacity: 0.75;
  border: 1px solid currentColor; border-radius: 4px; padding: 0 5px;
}

/* ---------- the footage view: one flat list, and the bins built on it ---------- */

/* Same sticky header as the folder view - it inherits .tree-header - with a filter box and
   a sort picker where the Back button and the breadcrumb trail would be. There is nothing
   to go back TO here: the view is the whole catalog. */
.footage-filter {
  flex: 1 1 160px;
  min-width: 120px;
  max-width: 240px;
  height: 26px;
  margin: 0;
  padding: 0 9px;
  font-size: 12.5px;
}
.footage-sort {
  flex: none;
  height: 26px;
  margin: 0;
  padding: 0 6px;
  font-size: 12.5px;
  width: auto;
}

/* The bins, as a wrapping strip of drop targets under the header. A strip rather than a
   rail down the side because the middle column is already the narrow one, and a bin has to
   be a BIG target - it is the thing you are throwing clips at. */
.bins-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 7px;
  padding: 0 0 12px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border-soft);
}
.bin-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  margin: 0;
  padding: 0 11px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--panel-2);
  color: var(--text-dim);
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
}
.bin-chip:hover { background: var(--hover); color: var(--text); }
.bin-chip.active {
  background: var(--accent-soft);
  border-color: var(--accent-dim);
  color: var(--accent-hi);
}
.bin-chip .icon { width: 14px; height: 14px; flex: none; }
.bin-chip .bin-name { max-width: 150px; overflow: hidden; text-overflow: ellipsis; }
.bin-count {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text-faint);
  background: var(--bg-soft);
  border-radius: 999px;
  padding: 1px 6px;
}
.bin-chip.active .bin-count { color: var(--accent-hi); background: rgba(0, 0, 0, 0.22); }
/* The one chip that makes something rather than filtering, so it reads as an action. */
.bin-chip.bin-new { border-style: dashed; color: var(--text-faint); }
.bin-chip.bin-new:hover { color: var(--accent); border-color: var(--accent-dim); }
.bin-chip.bin-all .icon { color: var(--accent); }
.bins-hint { font-size: 12px; margin-left: 4px; }

/* Dropping onto a chip. Same .kw-hot the keyword drag uses, so one drag idiom is learned
   once - the shapes differ, the signal does not. */
.bin-chip.kw-hot {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-hi);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

/* The name cell and the bin chips on one line. Without this the chips sit under the
   filename (fileNameCellHtml is a block) and every binned clip is a double-height row. */
.footage-name { display: flex; align-items: center; min-width: 0; }
.footage-name .file-name-cell { min-width: 0; }

/* Filter and sort are one thought - which of these, in what order - so they wrap as one
   rather than the sort dropping to a line of its own. */
.footage-controls { display: flex; align-items: center; gap: 8px; flex: 0 1 auto; }

.footage-list { position: relative; }
.footage-more { display: block; margin: 14px auto; }
.footage-count { text-align: center; font-size: 12px; padding: 12px 0 4px; }

/* Which bins a clip is already in, beside its name. The only fact on the row you cannot
   get by looking at the footage. */
.row-bins { display: inline-flex; gap: 4px; margin-left: 7px; flex: none; }
.row-bin {
  font-size: 10px;
  font-weight: 600;
  color: var(--accent-hi);
  background: var(--accent-soft);
  border-radius: var(--radius-xs);
  padding: 1px 5px;
  max-width: 90px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The drag ghost, which is .kw-ghost wearing a different word. */
.clip-ghost .icon { width: 14px; height: 14px; }

.px-check { display: flex; align-items: center; gap: 8px; margin: 10px 0; font-size: 13px; }
.px-check input { width: auto; margin: 0; }

/* ---------- phone media (website): upload progress ---------- */
.pm-bar { height: 6px; border-radius: 999px; background: var(--border-soft); overflow: hidden; }
.pm-bar i { display: block; height: 100%; width: 0; background: var(--accent); transition: width 0.2s ease; }

/* ---------- "Choose where" (desktop): phone requests waiting for a folder ---------- */
.where-count:not(:empty) { margin-left: auto; min-width: 18px; padding: 0 6px; border-radius: 999px;
  background: var(--accent); color: #0a0c10; font-size: 11px; font-weight: 700; text-align: center; }
.where-row { display: flex; align-items: center; gap: 12px; padding: 10px 12px;
  background: var(--panel); border: 1px solid var(--border-soft); border-radius: var(--radius-md); }
.where-what { flex: 1; min-width: 0; }
.where-name { display: flex; align-items: center; gap: 7px; font-weight: 600; }
.where-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.where-row .set-hint { overflow-wrap: anywhere; }

/* ---------- talent photos (website) ---------- */
.tp-view { max-width: 640px; display: flex; flex-direction: column; gap: 14px; }
.tp-take {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 18px; border-radius: var(--radius-md); cursor: pointer;
  background: var(--accent-soft); color: var(--accent); font-weight: 600; font-size: 15px;
  border: 1px dashed rgba(110, 139, 255, 0.45);
}
.tp-take .icon { width: 22px; height: 22px; }
.tp-take[hidden], .tp-form[hidden] { display: none; }
.tp-form { display: flex; flex-direction: column; gap: 10px; padding: 14px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-md); }
.tp-preview { width: 100%; max-height: 320px; object-fit: contain; border-radius: 8px; background: #000; }
.tp-form input#tp-name { width: 100%; margin: 0; }
.tp-form-actions { display: flex; gap: 8px; justify-content: flex-end; }
.tp-note, .tp-hint, .tp-empty { font-size: 12px; color: var(--text-faint); }
.tp-check { display: flex; align-items: center; gap: 8px; font-size: 13px; cursor: pointer; }
.tp-check input { width: auto; margin: 0; accent-color: var(--accent); }
.tp-pending { color: var(--text-faint); }
.tp-list { display: flex; flex-direction: column; gap: 10px; }
.tp-row { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px;
  background: var(--panel); border: 1px solid var(--border-soft); border-radius: var(--radius-md); }
.tp-row-head { display: flex; align-items: flex-start; gap: 10px; }
.tp-who { flex: 1; min-width: 0; }
.tp-name { font-weight: 600; font-size: 14.5px; }
.tp-status { font-size: 12.5px; color: var(--text-dim); margin-top: 2px; }
.tp-failed .tp-status { color: var(--warning-text); }
.tp-done .tp-status { color: var(--success-text); }
.tp-remove { color: var(--text-faint); }
.tp-rel { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--text-dim); }
.tp-rel-none { color: var(--text-faint); }
.tp-rel-warn { color: var(--warning-text); }
.tp-clips { display: flex; flex-direction: column; gap: 2px; }
.tp-clips:empty { display: none; }
.tp-clip { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  background: none; border: none; color: var(--text); font: inherit; font-size: 13px;
  padding: 8px 6px; border-radius: var(--radius-sm); cursor: pointer; }
.tp-clip:hover { background: var(--hover); }
.tp-clip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- phones ----------
   One column. The sidebar slides in over the list from the menu button, and a clip opens
   as a full-screen sheet with Back, because three panes in 390px is three unusable panes.
   760px matches the website's own collapse point (site.css), so the hub's landing and the
   app it leads into change shape together. Body classes come from setupPhoneLayout(). */
.sheet-back, .nav-scrim { display: none; }
@media (max-width: 760px) {
  header.topbar { padding: 0 10px; height: 54px; gap: 8px; }
  .brand { gap: 8px; flex: none; }
  .brand-name, .brand-tag, .user-name, .user-chip .chev, .offline-chip, #update-chip-label { display: none; }
  .sidebar-toggle-btn { display: inline-flex; }
  .cols-dropped { display: none; }
  .sidebar-toggle-btn { margin-right: 0; width: 40px; height: 40px; justify-content: center; }
  .topbar-search { flex: 1; min-width: 0; padding: 0; margin: 0; }
  .topbar-right { gap: 6px; }
  .user-chip { padding: 4px; }
  .user-chip .avatar { width: 32px; height: 32px; }
  #accounts-btn { display: none; }

  .layout { height: calc(100vh - 54px); height: calc(100dvh - 54px); position: relative; }
  .pane-grip { display: none; }
  .browser { padding: 14px 12px calc(14px + env(safe-area-inset-bottom)); }
  /* The pinned header bleeds into the pane's padding (see .tree-header), so its margins
     have to follow the padding above or it pokes 10px out and the list scrolls sideways. */
  .tree-header { top: -14px; margin: -14px -12px 10px; padding: 14px 12px 8px; }

  .sidebar {
    position: fixed; z-index: 160; top: 0; bottom: 0; left: 0;
    width: min(86vw, 320px) !important;
    padding-top: calc(18px + env(safe-area-inset-top));
    transform: translateX(-100%); transition: transform 0.2s ease;
    box-shadow: 12px 0 40px rgba(0, 0, 0, 0.45);
  }
  body.nav-open .sidebar { transform: none; }
  .nav-scrim { position: fixed; inset: 0; z-index: 150; background: rgba(0, 0, 0, 0.5); }
  body.nav-open .nav-scrim { display: block; }
  .sidebar-collapse { display: none; }

  .detail {
    position: fixed; z-index: 140; inset: 0;
    flex: none !important; min-width: 0; width: 100%;
    padding: calc(62px + env(safe-area-inset-top)) 14px calc(24px + env(safe-area-inset-bottom));
    background: var(--bg);
    transform: translateX(100%); transition: transform 0.2s ease;
    overscroll-behavior: contain;
  }
  body.sheet-open .detail { transform: none; }
  body.sheet-open .browser { visibility: hidden; }
  .sheet-back {
    position: fixed; z-index: 145; top: 0; left: 0; right: 0;
    height: calc(52px + env(safe-area-inset-top)); padding: env(safe-area-inset-top) 10px 0;
    align-items: center; gap: 4px;
    background: var(--bg); border-bottom: 1px solid var(--border-soft);
    color: var(--text); font: inherit; font-size: 15px; font-weight: 600; text-align: left;
    border-radius: 0; cursor: pointer;
  }
  body.sheet-open .sheet-back { display: flex; }
  .sheet-back .icon { width: 20px; height: 20px; transform: rotate(180deg); }

  /* Modals and popovers: the width of the phone, never wider. */
  .modal-overlay { padding: 10px; align-items: flex-end; }
  .modal { max-height: 92vh; max-height: 92dvh; max-width: none !important; }
  .modal-head, .modal-foot { padding: 12px 14px; }
  .modal-foot { flex-wrap: wrap; }
  .modal-body { padding: 8px 14px 14px; }
  .set-row { flex-direction: column; align-items: stretch; gap: 6px; }
  .set-row input[type="number"], .set-row input[type="text"], .set-row input[type="password"],
  .set-row .pw-wrap, .set-modal .set-field-c input, .set-modal .set-field-c select,
  .set-modal .set-field-c .pw-wrap, .selection-bar .sel-kw input { width: 100%; }
  .account-menu, .jobs-panel, .search-facets, .update-panel { max-width: calc(100vw - 20px); }
  .account-menu { right: 10px !important; left: auto !important; }
  .jobs-panel { right: 10px; left: 10px; width: auto; }
  .search-facets { right: 0; left: 0; width: auto; }

  /* 16px or iOS zooms the page on focus and leaves it zoomed. */
  input, select, textarea { font-size: 16px; }

  /* Fingers, not pointers: 40px targets on the things you tap most. */
  button, .link-btn { min-height: 36px; }
  .tree-header { flex-wrap: wrap; }
  .clone-actions { width: 100%; }
}

/* The clip's Actions card (fileActionsCard): the right-click menu, as buttons. */
.actions-list { display: flex; flex-wrap: wrap; gap: 8px; }
/* Labels carry the filename ("Hide INTERVIEW_A001_…"), so they wrap rather than push the
   clip sideways on a phone. */
.actions-list .action-btn { flex: 0 1 auto; max-width: 100%; white-space: normal;
  overflow-wrap: anywhere; text-align: left; }
.actions-list .kebab-hint { margin-left: 6px; color: var(--text-faint); font-weight: 400; }
/* Shown only where there is no right-click to reach the same thing. */
.touch-only { display: none !important; }
.save-frame-btn { margin: -4px 0 12px; }
@media (hover: none) { .touch-only { display: inline-flex !important; } }

/* No hover on a touchscreen, so anything that waits for one has to be there already. */
@media (hover: none) {
  .card-ctl, .copy-name-btn, .drive-rename, .trim-handle { opacity: 0.75; }
  /* A sideways swipe across a thumbnail skims it (touchSkim); the page keeps vertical. */
  .tile-frame, .proxy-frame-wrap { touch-action: pan-y; }
  /* Long-press means "drag to a bin" on a clip, not "select the filename's text". */
  tr.row-file, .tile-file { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
}
