:root {
  --bg: #f6f6f7;
  --surface: #ffffff;
  --border: #e2e3e5;
  --text: #1b1c1f;
  --text-dim: #6c6e73;
  --accent: #1b1c1f;
  --radius: 6px;
  --shadow: 0 1px 2px rgba(16, 17, 19, 0.04), 0 4px 16px rgba(16, 17, 19, 0.06);
  --subtle-bg: #edeef0;
  --hover-border: #c7c9cc;
  --placeholder-bg: #e9eaec;
  --overlay-scrim: rgba(15, 16, 18, 0.65);
  --modal-scrim: rgba(15, 16, 18, 0.4);
  --danger-bg: #fbeae8;
  --danger-text: #a13c2e;
  font-size: 16px;
}

html[data-theme="dark"] {
  --bg: #121214;
  --surface: #1b1c1f;
  --border: #2d2e32;
  --text: #ebebee;
  --text-dim: #98999e;
  --accent: #ebebee;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.35);
  --subtle-bg: #24252a;
  --hover-border: #3c3d43;
  --placeholder-bg: #202126;
  --overlay-scrim: rgba(0, 0, 0, 0.55);
  --modal-scrim: rgba(0, 0, 0, 0.6);
  --danger-bg: #3a2420;
  --danger-text: #e2867a;
}

* { box-sizing: border-box; }

/* .asset-hero-band (project page) breaks out of .container using the
   100vw/negative-margin technique, and 100vw always includes the
   scrollbar's own width — on any page tall enough to actually scroll,
   that's a few extra pixels wider than the real viewport, producing a
   sliver of horizontal overflow (and its own tiny horizontal scrollbar)
   that isn't real content. Clipping it at the root is the standard fix;
   it doesn't affect any element's own overflow-x:auto/scroll region. */
html { overflow-x: hidden; }

/* Any element toggled via the `hidden` attribute must actually disappear,
   even when its own class also sets `display` (e.g. `.filter-bar { display:
   flex }`) — an author rule with equal specificity to the UA default
   `[hidden]{display:none}` otherwise wins by cascade order and the element
   stays visible despite `el.hidden = true`. Bit us more than once already
   (notification badge, gallery filter bar) — fixed globally instead of
   patching each class one at a time. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  transition: background 0.15s ease, color 0.15s ease;
}

a { color: inherit; }

button {
  font-family: inherit;
  cursor: pointer;
}

.container {
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 28px;
}

/* ---------- Header ---------- */

header.top {
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  position: sticky;
  top: 0;
  z-index: 20;
}

header.top .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 68px;
}

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

.brand {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Gallery <-> CPT: two distinct tools sharing one ecosystem (users, media
   pipeline) — a small pill pair next to the brand, same visual language as
   the gallery's own view-switcher buttons. */
.app-switcher { display: flex; gap: 6px; }

.app-switch-link {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-dim);
  padding: 6px 12px;
  border-radius: 5px;
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
}

.app-switch-link:hover { border-color: var(--hover-border); color: var(--text); }

.app-switch-link.active {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
}

.brand .dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #c9564d;
  display: inline-block;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

.notif-wrap { position: relative; }

.notif-bell-btn {
  position: relative;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}

.notif-bell-btn:hover { border-color: var(--hover-border); background: var(--subtle-bg); }

.notif-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 3px;
  border-radius: 20px;
  background: #c9564d;
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

.notif-badge[hidden] { display: none; }

.notif-dropdown {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: 340px;
  max-width: 90vw;
  max-height: 420px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 7px;
  box-shadow: var(--shadow);
  z-index: 50;
}

.notif-dropdown[hidden] { display: none; }

.notif-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  font-weight: 600;
}

.notif-header-actions { display: flex; align-items: center; gap: 12px; }

.notif-mark-all {
  border: none;
  background: none;
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  padding: 0;
}

.notif-mark-all:hover { color: var(--text); }

.notif-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 14px;
  border: none;
  border-bottom: 1px solid var(--border);
  background: none;
  text-align: left;
  cursor: pointer;
}

.notif-item:last-child { border-bottom: none; }
.notif-item:hover { background: var(--subtle-bg); }
.notif-item.unread { background: color-mix(in srgb, var(--text) 4%, transparent); }

/* Read items stay legible but visibly recede, so it's obvious at a glance
   which ones still need attention. */
.notif-item.read { opacity: 0.55; }

.notif-see-all {
  display: block;
  width: 100%;
  border: none;
  background: none;
  color: var(--text);
  font-size: 12.5px;
  font-weight: 600;
  text-align: center;
  padding: 10px 14px;
  cursor: pointer;
}

.notif-see-all:hover { background: var(--subtle-bg); }

.notif-text {
  flex: 1;
  font-size: 13px;
  line-height: 1.4;
  color: var(--text);
  min-width: 0;
}

.notif-time {
  font-size: 11px;
  color: var(--text-dim);
  white-space: nowrap;
  flex-shrink: 0;
}

.notif-loading {
  padding: 20px 14px;
  text-align: center;
  color: var(--text-dim);
  font-size: 13px;
}

.btn {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  padding: 9px 16px;
  border-radius: 5px;
  font-size: 14px;
  font-weight: 500;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.btn:hover { border-color: var(--hover-border); background: var(--subtle-bg); }

.btn.primary {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
}

.btn.primary:hover { opacity: 0.88; background: var(--text); }

.btn.ghost {
  border-color: transparent;
  background: transparent;
}

.btn.ghost:hover { background: var(--subtle-bg); }

.btn.danger {
  background: var(--danger-bg);
  color: var(--danger-text);
  border-color: var(--danger-bg);
}

.btn.danger:hover { opacity: 0.85; }

.btn:disabled { opacity: 0.5; cursor: default; }

.theme-toggle {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  padding: 0;
  flex-shrink: 0;
}

.theme-toggle:hover { border-color: var(--hover-border); background: var(--subtle-bg); }

.user-chip {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 6px 8px 6px 6px;
  border-radius: 20px;
  text-decoration: none;
  cursor: pointer;
}

.user-chip:hover { background: var(--subtle-bg); }

.avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 12px;
  font-weight: 600;
  flex-shrink: 0;
}

.user-chip .name { font-size: 14px; font-weight: 500; }

/* ---------- Gallery grid (uniform tiles, dense) ---------- */

.gallery-wrap { padding: 32px 0 80px; }

.gallery-intro {
  margin: 0 0 28px;
  color: var(--text-dim);
  font-size: 14px;
}

.user-header {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-bottom: 28px;
}

.user-header .avatar { width: 56px; height: 56px; font-size: 20px; }

.user-sub { margin: 0; color: var(--text-dim); font-size: 13.5px; }

/* ---------- Account page ---------- */

.account-section { margin-bottom: 40px; }

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

.settings-row:last-child { border-bottom: none; }

.settings-label { font-size: 14px; font-weight: 500; }

.settings-control { display: flex; gap: 8px; flex-wrap: wrap; }

.settings-control .btn.active {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
}

.following-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 14px;
}

.follow-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
}

.follow-add-row { margin: 14px 0; }
.follow-add-row:first-child { margin-top: 0; }

.token-create-row { display: flex; gap: 8px; margin: 14px 0; }
.token-create-row .search-input { flex: 1; }

.token-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  margin-bottom: 8px;
}
.token-row-info { display: flex; flex-direction: column; gap: 2px; }
.token-row-actions { display: flex; gap: 8px; }
.token-row-label { font-weight: 500; }
.token-row-meta { font-size: 12.5px; color: var(--text-dim); font-family: ui-monospace, 'SF Mono', Consolas, monospace; }

.token-reveal-modal { max-width: 480px; text-align: left; }
.token-reveal-row { display: flex; gap: 8px; margin: 12px 0; }
.token-reveal-row input {
  flex: 1;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: 5px;
  font-family: ui-monospace, 'SF Mono', Consolas, monospace;
  font-size: 13px;
  background: var(--subtle-bg);
  color: var(--text);
}

.account-empty {
  color: var(--text-dim);
  font-size: 13.5px;
  font-style: italic;
  margin: 0 0 14px;
}

.notif-page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 34px 0 10px;
}

.notif-page-header .section-label { margin: 0; }

/* Capped height regardless of how many pile up — a big unread backlog
   scrolls inside its own box instead of pushing the rest of the page down. */
.notif-full-list {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  max-height: 420px;
  overflow-y: auto;
}

.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 24px;
}

.select {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  padding: 8px 30px 8px 12px;
  border-radius: 5px;
  font-size: 13.5px;
  font-weight: 500;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%238a857c'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
}

html[data-theme="dark"] .select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23a49b8a'/%3E%3C/svg%3E");
}

.select:hover { border-color: var(--hover-border); }
.select.full { width: 100%; padding-right: 34px; }

.filter-checkbox {
  display: flex;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  padding: 8px 14px;
  border-radius: 5px;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  user-select: none;
}

.filter-checkbox:hover { border-color: var(--hover-border); }

.filter-checkbox input[type="checkbox"] {
  width: 15px;
  height: 15px;
  margin: 0;
  accent-color: #2f7a45;
  cursor: pointer;
}

.search-input {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  padding: 8px 12px;
  border-radius: 5px;
  font-size: 13.5px;
  font-family: inherit;
  flex: 1 1 220px;
  min-width: 180px;
}

.search-input:focus { outline: 2px solid var(--text); outline-offset: 1px; }
.search-input::placeholder { color: var(--text-dim); }

.page-size-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 18px;
  font-size: 13px;
  color: var(--text-dim);
}

.page-size-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}

.page-size-bar .select { padding: 6px 26px 6px 10px; font-size: 13px; }

.page-size-custom-input {
  width: 90px;
  flex: none;
  padding: 6px 10px;
  font-size: 13px;
}

.page-size-custom-input[hidden] { display: none; }

.view-switcher { display: flex; gap: 6px; }

.view-switch-btn {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-dim);
  padding: 6px 12px;
  border-radius: 5px;
  font-size: 13px;
  font-weight: 500;
}

.view-switch-btn:hover { border-color: var(--hover-border); color: var(--text); }

.view-switch-btn.active {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
}

.pagination-bar {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin-top: 28px;
}

.pagination-bar[hidden] { display: none; }

.pagination-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
}

.pagination-jump {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text-dim);
}

.pagination-page-input {
  width: 52px;
  text-align: center;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  border-radius: 4px;
  padding: 5px 4px;
  font-size: 13px;
  font-family: inherit;
}

.pagination-page-input:focus { outline: 2px solid var(--text); outline-offset: 1px; }

.pagination-info { font-size: 13px; color: var(--text-dim); }

/* A fixed-width, fixed-aspect-ratio grid: every tile is the same size, packed
   evenly by row so no single project reads as more "important" than another. */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 16px;
}

/* Tiles view: bigger cells than the default card grid, and the tile itself
   IS the thumbnail — no card body, no badges, nothing drawn on top. */
.grid.tiles-grid {
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 14px;
}

/* Get Inspired: a full-bleed, organic wall, laid out row by row (see the
   justified-row packer in gallery.js — the same technique Flickr/Google
   Photos use): each row is filled with images at a shared height until it
   reaches the container's full width, then scaled so it lands exactly at
   both edges. Width and height scale together, so every image keeps its
   own aspect ratio (never cropped) while rows still butt up edge-to-edge
   with no gaps. No hover lift, no badges, no text anywhere in here — just
   the images/videos themselves, endlessly scrollable. */
.inspire-grid {
  position: relative;
  width: 100%;
}

.inspire-grid[hidden] { display: none; }

.inspire-tile {
  position: absolute;
  display: block;
  background: var(--placeholder-bg);
  overflow: hidden;
}

.inspire-tile img {
  width: 100%;
  height: 100%;
  /* The box is already sized to the image's exact aspect ratio — this is
     just a rounding safety net, not an actual crop. */
  object-fit: cover;
  display: block;
}

.inspire-sentinel { height: 1px; }
.inspire-sentinel[hidden] { display: none; }

.tile {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--placeholder-bg);
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.tile:hover {
  box-shadow: var(--shadow);
  transform: translateY(-2px);
}

.tile img.thumb { width: 100%; height: 100%; object-fit: cover; display: block; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  text-decoration: none;
  color: var(--text);
  display: flex;
  flex-direction: column;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.card:hover {
  box-shadow: var(--shadow);
  transform: translateY(-2px);
}

.card .thumb-wrap {
  position: relative;
  background: var(--placeholder-bg);
  aspect-ratio: 1 / 1;
  overflow: hidden;
}

.card img.thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.thumb-status {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  text-align: center;
  padding: 12px;
}

.thumb-status.processing { animation: thumb-pulse 1.6s ease-in-out infinite; }
.thumb-status.failed { color: var(--danger-text); text-transform: none; font-weight: 500; line-height: 1.4; }

@keyframes thumb-pulse {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}

.spinner {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid var(--border);
  border-top-color: var(--text-dim);
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

.play-badge {
  position: absolute;
  bottom: 10px;
  left: 10px;
  background: var(--overlay-scrim);
  color: white;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
}

.badge-stack {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}

.badge-stack-left {
  position: absolute;
  top: 10px;
  left: 10px;
}

.mkt-ready-icon-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #2f7a45;
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}

.status-badge {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 20px;
  white-space: nowrap;
}

.status-badge.lg { font-size: 12px; padding: 5px 12px; }

.category-tag {
  display: inline-block;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--text-dim);
  background: var(--subtle-bg);
  border: 1px solid var(--border);
  padding: 2px 9px;
  border-radius: 20px;
}

/* Tinted pill in the tag's own color — a soft wash behind readable colored
   text/border, rather than a loud solid block. color-mix() degrades
   gracefully (transparent background, colored text/border still legible)
   in the rare browser that doesn't support it. */
.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 600;
  color: var(--tag-color, var(--text-dim));
  background: color-mix(in srgb, var(--tag-color, var(--text-dim)) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--tag-color, var(--text-dim)) 35%, transparent);
  padding: 2px 9px;
  border-radius: 20px;
  white-space: nowrap;
}

.tag-chip-more { color: var(--text-dim); background: var(--subtle-bg); border-color: var(--border); }

.tags-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
}

.version-badge {
  background: var(--overlay-scrim);
  color: white;
  font-size: 11px;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 20px;
}

.card-body {
  padding: 10px 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
}

.card-title {
  font-size: 13.5px;
  font-weight: 600;
  margin: 0;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc(1.35em * 2);
}

.card-tags {
  min-height: 22px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
}

.card-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: auto;
}

.owner-line {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-dim);
  font-size: 12.5px;
  min-width: 0;
  text-decoration: none;
  position: relative;
  z-index: 1;
}

/* Overlapping circles rather than a spaced-out row — a border matching
   the surface they sit on is what actually separates one avatar from the
   next, standing in for a gap. */
.contributor-stack { display: inline-flex; align-items: center; flex-shrink: 0; }

.contributor-avatar {
  border: 2px solid var(--surface);
  margin-left: -6px;
  flex-shrink: 0;
}

.contributor-avatar:first-child { margin-left: 0; }

.contributor-avatar-more {
  background: var(--subtle-bg) !important;
  color: var(--text-dim);
  font-weight: 700;
}

/* Project header's Author/Contributors rows (project.html) — same
   label treatment as the CPT hero's own Frame.io rows (.cpt-frameio-
   label), hardcoded light since both only ever appear on the hero's
   always-dark overlay. */
.detail-people-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
  flex-wrap: wrap;
}

.detail-people-label {
  color: rgba(255, 255, 255, 0.6);
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  flex-shrink: 0;
}

.detail-contributors { display: flex; align-items: center; min-height: 22px; }

.contributors-add-row { display: flex; gap: 8px; margin-bottom: 12px; }
.contributors-add-row .select { flex: 1; }

.contributors-current { display: flex; flex-wrap: wrap; gap: 8px; }

.contributor-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px 3px 4px;
  border-radius: 20px;
  background: var(--subtle-bg);
  border: 1px solid var(--border);
  font-size: 12.5px;
}

.owner-line:hover { color: var(--text); }
.owner-line .avatar { width: 18px; height: 18px; font-size: 9px; flex-shrink: 0; }
.owner-line span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.reaction-preview {
  font-size: 12px;
  color: var(--text-dim);
  white-space: nowrap;
}

.empty-state {
  text-align: center;
  padding: 100px 20px;
  color: var(--text-dim);
}

.empty-state h2 { color: var(--text); font-size: 20px; margin-bottom: 8px; }

/* ---------- CPT project grid ---------- */

.cpt-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 16px;
}

.cpt-project-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.cpt-project-card:hover { box-shadow: var(--shadow); transform: translateY(-2px); }

.card-delete-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: none;
  background: var(--overlay-scrim);
  color: #fff;
  font-size: 13px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s ease;
}
.card-delete-btn:hover { background: var(--danger-text); }

.cpt-cover {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  display: block;
  background: var(--placeholder-bg);
}

.cpt-cover-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.cpt-card-body { padding: 12px 14px 14px; display: flex; flex-direction: column; gap: 8px; }
.cpt-card-title { font-size: 14.5px; font-weight: 600; margin: 0; }

.cpt-card-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 12.5px;
}

.cpt-shot-count { color: var(--text-dim); white-space: nowrap; }

/* ---------- CPT project page ---------- */

/* Full-bleed hero banner: cover media fills the whole block, a dark
   gradient overlay sits on top so title/type/edit controls stay readable
   regardless of what's in the image, and their color is hardcoded white
   rather than var(--text) — this area intentionally doesn't follow the
   light/dark theme toggle, the same way a streaming service's show-detail
   hero never does, since a photo needs one predictable treatment rather
   than two. Fixed height (not tied to the media's own aspect ratio) so the
   text overlay always has consistent room. */
/* Shared by the hero and the "Shots" header row below it — same margin
   the shot table itself uses (see .cpt-shot-table-wrap), so the cover,
   the "Shots" label, the row/font-size toolbar, and the table's own edges
   all land on the exact same two vertical lines regardless of viewport
   width, rather than the hero being capped by .container while the table
   runs edge-to-edge. */
.cpt-hero-full { margin-left: 32px; margin-right: 32px; }

.cpt-hero {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: 20px;
  background: var(--placeholder-bg);
}

/* Gallery's own hero (project.html) sits flush against the black asset
   preview right below it — no gap between "cover" and "preview" — unlike
   CPT's own hero, which has ordinary page content (not another full-bleed
   band) right after it and keeps the normal margin/padding. Zeroing both
   sides of the seam (the hero's own bottom margin, and the very next
   container's top padding) is what actually closes it; either alone would
   still leave the other's spacing in place. */
#gallery-hero { margin-bottom: 0; }
#gallery-hero + .container.detail-wrap { padding-top: 0; }

/* Toned down so the cover reads as a backdrop, not competition for the
   title/description text over it or the rest of the page's own fairly
   quiet palette — same reasoning streaming-service show pages desaturate
   their own hero backdrops. Scoped to Gallery's hero only; CPT's own
   project cover is unaffected. */
#gallery-hero .cpt-header-cover { filter: saturate(0.45) brightness(0.92); }

/* Square corners, genuinely edge to edge (same width:100vw/negative-margin
   technique as .asset-hero-band below it, not CPT's own .cpt-hero-full —
   which only insets 32px from the viewport, not true full-bleed) — an id
   selector beats .cpt-hero-full's own margin without needing to touch the
   class list in project.html. */
#gallery-hero {
  border-radius: 0;
  width: 100vw;
  margin-left: calc(-50vw + 50%);
  margin-right: 0;
}

/* The overlay now fades from fully transparent at the top (the cover
   reads clearly up there) to fully opaque black at the bottom edge — so
   that edge matches .asset-hero-band's own solid black exactly, and the
   seam between "header" and "preview" (already touching, see the
   margin/padding zeroing above) reads as one continuous surface instead
   of two adjacent-but-distinct dark boxes. */
#gallery-hero .cpt-hero-overlay {
  background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.55) 60%, rgba(0, 0, 0, 1) 100%);
}

/* Text content stays left-aligned but now lines up with the same 1600px
   column the asset preview/rest of the page use (see .asset-hero-stack
   and .container.detail-wrap), instead of a flat 28px inset from the
   viewport edge — the two would otherwise drift apart on wide screens,
   since the hero itself is full-bleed 100vw while everything below it is
   centered and capped. Zeroing the side padding and centering this at the
   same max-width makes its left edge coincide exactly with the preview's. */
#gallery-hero .cpt-hero-content {
  max-width: 1600px;
  margin: 0 auto;
  padding-left: 0;
  padding-right: 0;
}

.cpt-hero-media, .cpt-hero-media .cpt-header-cover, .cpt-hero-media .cpt-cover-placeholder {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 0;
}

/* Deliberately no `display` above — the processing/failed states reuse
   the shared .thumb-status class (see common.js/cpt-project.js), which
   needs its own flex centering for the spinner/text; overriding display
   here (even to the "same" block/flex) would out-specificity that rule's
   equal-but-earlier .thumb-status selector and break the centering. */

.cpt-hero-media .cpt-cover-placeholder {
  background: linear-gradient(135deg, #2b2118, #1a1512);
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.5);
  font-size: 13px;
}

.cpt-hero-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.35) 45%, rgba(0, 0, 0, 0.82) 100%);
  pointer-events: none;
}

.cpt-hero-content {
  position: relative;
  min-height: 420px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 18px 28px 24px;
  color: #fff;
}

.cpt-hero-top { display: flex; align-items: center; justify-content: space-between; }

.cpt-hero-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: rgba(255, 255, 255, 0.85);
  text-decoration: none;
  font-size: 13.5px;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.5);
}

.cpt-hero-back:hover { color: #fff; }

/* Identity (title/description/type/shot count) on the left, Frame.io
   details on the right — two independent columns rather than one long
   stack, so the hero reads as deliberately composed rather than just a
   list of fields. */
.cpt-hero-bottom { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; }

.cpt-hero-identity { display: flex; flex-direction: column; gap: 10px; min-width: 0; }

/* Both header-level edit actions (Title, Description) live on this one row,
   above the title, rather than either button living down by the content it
   edits — reads as a deliberate toolbar rather than scattered controls. */
.cpt-hero-actions-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.cpt-hero-identity .title-view { display: flex; align-items: center; gap: 14px; }

/* Gallery's own hero (#gallery-hero, reusing .cpt-hero's chrome as-is —
   see project.html) also overlays the author link and the status
   selector, neither of which CPT's hero has. Both normally use
   var(--text-dim)/var(--surface)-style theme colors tuned for the page's
   own background, which in dark theme would read as dark-on-dark against
   this always-dark overlay — same reasoning as .cpt-hero-shot-count's own
   hardcoded rgba(255,255,255,...) above. */
#gallery-hero .detail-owner-link { color: rgba(255, 255, 255, 0.85); }
#gallery-hero .detail-owner-link:hover { color: #fff; }

#gallery-hero .status-selector { margin-bottom: 0; }

#gallery-hero .status-pill {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.4);
  color: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(6px);
}

#gallery-hero .status-pill:hover { border-color: var(--status-color); color: var(--status-color); background: rgba(255, 255, 255, 0.26); }

/* .active already uses the status's own hardcoded --status-color/
   --status-bg (absolute hex values, not theme vars — see
   renderStatusSelector in project.js), so it's already legible here
   without an override — but needs restating with .active in the selector
   too, since the base override right above it now carries #gallery-hero's
   id specificity and would otherwise beat the plain .status-pill.active
   rule further down the file. */
#gallery-hero .status-pill.active {
  background: var(--status-bg);
  border-color: var(--status-color);
  color: var(--status-color);
}

.cpt-hero-details {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  text-align: right;
  min-width: 0;
  flex-shrink: 0;
}

.cpt-hero-title {
  font-size: 40px;
  font-weight: 800;
  color: #fff;
  margin: 0;
  line-height: 1.1;
  letter-spacing: -0.01em;
  text-shadow: 0 2px 16px rgba(0, 0, 0, 0.45);
}

.cpt-hero-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
}

/* Deliberately not another glass pill — a solid, high-contrast chip so it
   reads as a *label* (what kind of trailer this is) rather than one more
   button in the row. Same 24px height as .cpt-hero-btn-sm so the two line
   up cleanly side by side despite the different treatment. */
.cpt-hero-type-tag {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 10px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.94);
  color: #1a1512;
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

.cpt-hero-shot-count { color: rgba(255, 255, 255, 0.85); white-space: nowrap; }

.cpt-hero-dot { color: rgba(255, 255, 255, 0.4); }

.cpt-hero-description {
  margin: 0;
  color: rgba(255, 255, 255, 0.88);
  font-size: 14px;
  line-height: 1.5;
  text-align: left;
  max-width: 860px;
  min-width: 0;
}

.cpt-hero-description.placeholder { color: rgba(255, 255, 255, 0.55); font-style: italic; }

.cpt-frameio-rows { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; margin-top: 6px; }

.cpt-frameio-row { display: flex; align-items: center; justify-content: flex-end; gap: 8px; font-size: 13px; }

.cpt-frameio-icon { font-size: 13px; opacity: 0.85; }

.cpt-frameio-label {
  color: rgba(255, 255, 255, 0.6);
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  min-width: 58px;
}

.cpt-frameio-value {
  color: #fff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 320px;
  font-family: ui-monospace, "SF Mono", Consolas, monospace;
  font-size: 12.5px;
}

.cpt-frameio-value a { color: #fff; text-decoration: underline; text-decoration-color: rgba(255, 255, 255, 0.4); font-family: inherit; }
.cpt-frameio-value a:hover { text-decoration-color: #fff; }
.cpt-frameio-value.placeholder { color: rgba(255, 255, 255, 0.5); font-style: italic; font-family: inherit; }

.cpt-frameio-icon-btn {
  border: none;
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  width: 24px;
  height: 24px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  flex-shrink: 0;
}

.cpt-frameio-icon-btn:hover { background: rgba(255, 255, 255, 0.28); }

/* "Glass" buttons — translucent over the photo rather than the theme's
   normal solid .btn.primary/.btn.ghost, which would either disappear
   against a dark overlay (in light theme, .btn.primary is dark-on-dark)
   or clash with it (in dark theme). Same look for every edit action in
   the hero (Cover/Title/Type), which is the point — that consistency is
   what was missing before. */
.cpt-hero-btn {
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.4);
  color: #fff;
  backdrop-filter: blur(6px);
}

.cpt-hero-btn:hover { background: rgba(255, 255, 255, 0.26); }

/* Two-class selector (not just .cpt-hero-btn-sm) so this reliably beats
   .info-edit-btn's equal-specificity padding/font-size — that rule is
   defined later in the file, so a same-specificity single-class override
   here would silently lose the cascade tie and never actually shrink. */
.cpt-hero-btn.cpt-hero-btn-sm { font-size: 11.5px; padding: 4px 10px; height: 24px; display: inline-flex; align-items: center; }

/* Edit forms float as an opaque card over the hero rather than inheriting
   the white-on-photo treatment — they need normal theme-aware contrast
   (inputs, selects) which the glass look can't provide. */
.cpt-hero-edit {
  background: var(--surface);
  color: var(--text);
  border-radius: 6px;
  padding: 14px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  max-width: 420px;
  align-self: flex-end;
}

/* .cpt-hero-edit's base align-self:flex-end (above) is right for the
   cover-edit card (in .cpt-hero-top, a row — flex-end there means
   "bottom") and for .cpt-hero-details' Frame.io edit cards (a
   right-aligned column — flex-end there means "right", matching the
   column's own alignment). .cpt-hero-identity is the one exception: a
   left-aligned column (title, description, type all live there), so its
   edit cards need flex-start instead, or they'd float to the right, away
   from the left-aligned content they're editing. */
.cpt-hero-identity .cpt-hero-edit { align-self: flex-start; }
.cpt-hero-identity .cpt-hero-edit, .cpt-hero-details .cpt-hero-edit { width: 100%; max-width: 320px; }

/* Same full-bleed margin as the hero/table (see .cpt-hero-full); the flex
   layout itself mirrors the pre-redesign .version-history-header (Gallery
   uses that class elsewhere too, so this is its own rule rather than
   risking a shared edit rippling into unrelated pages). */
.cpt-shots-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 8px;
  margin-bottom: 14px;
}

.cpt-shots-header .section-label { margin: 0; }

/* The detached "Full Edit" summary row — deliberately outside
   #shot-table-wrap (see cpt-project.js's renderFullEditRow), a horizontal
   strip rather than another table row, so it never reads as "just another
   shot". */
.cpt-full-edit-row {
  display: flex;
  gap: 16px;
  align-items: stretch;
  margin-bottom: 14px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  cursor: pointer;
}
/* Height stretches to match the info column (whatever it grows to); the
   width that keeps it 16:9 is set from JS (fitFullEditThumb in
   cpt-project.js - CSS alone can't turn a flex item's stretched height into
   its width). The picture/status inside is absolutely positioned so it
   never contributes size of its own - the info column alone decides how
   tall the whole cell is, and the thumbnail follows. */
.cpt-full-edit-thumb {
  position: relative;
  flex: 0 0 auto;
  align-self: stretch;
  width: 160px;
  background: var(--placeholder-bg);
  border-radius: 4px;
  overflow: hidden;
}
.cpt-full-edit-thumb > * { position: absolute; inset: 0; }
.cpt-full-edit-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
.cpt-full-edit-info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: 4px; }
.cpt-full-edit-title-row { display: flex; align-items: center; gap: 10px; }
/* .cpt-cell-select's own `width: 100%` is correct inside its usual home
   (a table <td>), but this select sits in a plain flex row instead — a
   flex item's flex-basis resolves from `width` when `flex-basis` itself
   is auto, so unset it makes it also un-hog the row (it was measuring
   ~740px wide, squeezing the title down to two lines, before this). */
.cpt-full-edit-title-row .cpt-cell-select { width: auto; flex: 0 0 auto; }
/* No flex-grow — sized to its own text, same as any ordinary flex item.
   Growing it (an earlier attempt used flex-grow:2) stretched the box
   wider than a SHORT title's actual text whenever there was spare row
   width, which pushed the status select away from the visible text
   instead of keeping it right next to it. Content-sized means the
   select always sits immediately after the title text, short or long;
   min-width:0 still lets a long one shrink/wrap on a narrow viewport. */
.cpt-full-edit-title { flex: 0 1 auto; min-width: 0; margin: 0; font-weight: 600; font-size: 15px; }
.cpt-full-edit-divider { border: none; border-top: 1px solid var(--border); margin: 4px 0; width: 100%; }
.cpt-full-edit-stats {
  display: flex;
  flex-wrap: wrap;
  font-size: 12.5px;
  color: var(--text-dim);
}
/* Vertical pipe between each stat — margin before + padding after gives
   even spacing on both sides of the line itself (no plain `gap` on the
   parent, or the two would stack); every span but the first gets one on
   its left, so there's no dangling divider before the first or after
   the last. */
.cpt-full-edit-stats span + span {
  margin-left: 12px;
  padding-left: 12px;
  border-left: 1px solid var(--border);
}
.cpt-full-edit-updated-shots {
  margin: 2px 0 0;
  font-size: 12.5px;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Longer text (description, misc, ...) as equal columns under the divider,
   each a labelled, height-capped block - a fixed maximum keeps the whole
   cell from growing unpredictably, and a long note just ellipsizes here
   (the full text lives on the details page). */
.cpt-full-edit-notes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 6px 20px;
  margin-top: 4px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}
.cpt-full-edit-note { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.cpt-full-edit-note-label {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-dim);
}
.cpt-full-edit-note-body {
  font-size: 13px;
  line-height: 1.4;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  overflow-wrap: anywhere;
}
/* Rich text brings its own block margins and list indents - flattened so a
   few lines of notes stay compact and can't push the cell around. */
.cpt-full-edit-note-body p,
.cpt-full-edit-note-body ul,
.cpt-full-edit-note-body ol,
.cpt-full-edit-note-body h1,
.cpt-full-edit-note-body h2,
.cpt-full-edit-note-body h3 { margin: 0; padding: 0; font-size: inherit; font-weight: inherit; display: inline; }
.cpt-full-edit-note-body li { display: inline; list-style: none; }
.cpt-full-edit-note-body li + li::before { content: ' · '; }
.cpt-full-edit-note-body br { display: none; }
.cpt-full-edit-note-body p + p::before { content: ' '; }

@media (max-width: 700px) {
  .cpt-hero-content { min-height: 320px; }
  .cpt-hero-title { font-size: 28px; }
  .cpt-hero-bottom { flex-direction: column; align-items: flex-start; gap: 14px; }
  .cpt-hero-details { align-items: flex-start; text-align: left; }
  .cpt-hero-description { max-width: 100%; }
  .cpt-frameio-rows { align-items: flex-start; }
  .cpt-frameio-row { justify-content: flex-start; }
  .cpt-frameio-value { max-width: 160px; }
}

/* Shot table: full width of the screen (outside .container, same idea as
   the Get Inspired mosaic) with a comfortable fixed gutter rather than
   either the 1400px container cap or true edge-to-edge. overflow-x
   handles the case where 20+ columns don't fit the viewport.

   --cpt-thumb-width is set from the row-size slider (cpt-project.js) — the
   preview is 16:9, so its height (and the row wrapping it) follows from
   that one variable, and every sticky column's left-offset is a calc()
   chain off it, so resizing needs no re-render at all. Every column
   (sticky or scrolling) is independently drag-resizable via its own
   --cpt-col-<name> variable (see cpt-project.js's initColumnWidths /
   applyColumnWidth and the .cpt-col-resize-handle wiring) — the values
   below are just the initial paint before that JS runs on load, and are
   kept in sync with CPT_TABLE_COLUMNS' defaultWidth values there. */
.cpt-shot-table-wrap {
  --cpt-thumb-width: 96px;
  --cpt-font-size: 13.5px;
  --cpt-line-clamp: 2;
  --cpt-col-select: 40px;
  /* Every other column's --cpt-col-<fieldDefId> and --cpt-sticky-left-
     <fieldDefId> are computed and set entirely in JS (cpt-project.js's
     applyColumnWidth/updateStickyOffsets) rather than declared here —
     the set of columns, their order, and which ones are sticky are all
     driven by the project's own fieldDefs now, not a fixed list this
     stylesheet could know about ahead of time. renderShotList() always
     calls updateStickyOffsets() as part of its own render, so this is
     correct from the very first paint, not just after a resize. */
  /* margin, not padding — padding would be inside this element's own
     scrollport, and position:sticky's "left:0" pins Preview to the
     *padding* edge, not the border edge. That left a 32px strip (the
     padding itself) with nothing sticky covering it, so a scrolled
     column's cell — genuinely positioned there once you've scrolled far
     enough that it should be hidden under Preview/Shot ID/etc — rendered
     into that strip instead of being occluded. Margin sits outside the
     scrollport entirely, so there's no gap left for anything to leak
     into; Preview's sticky "left:0" now really is this element's visual
     left edge. */
  margin: 0 32px 40px;
  overflow-x: auto;
  /* Without this, a trackpad/precision-touchpad swipe past either
     horizontal edge rubber-bands (and can chain into scrolling the page
     itself) instead of just stopping at the boundary. */
  overscroll-behavior-x: contain;
}

.cpt-shot-table-wrap[hidden] { display: none; }

/* #shot-table-wrap-scoped so this reliably beats .project-table's own
   font-size (equal-specificity ties go to source order otherwise, and
   .project-table is defined after this in the stylesheet).

   table-layout: fixed is what makes the sticky math below actually hold —
   under the default auto layout, a browser can size a sticky cell's
   column independently of its declared width/min-width (it's still just a
   hint to the auto-sizing algorithm), so the NEXT column could end up
   placed at a different x than the sticky offset assumed, overlapping it.
   Fixed layout makes every column's width the deterministic thing we
   declared, sticky or not. */
#shot-table-wrap .cpt-shot-table { min-width: 100%; font-size: var(--cpt-font-size); table-layout: fixed; }

/* #shot-table-wrap-scoped for the same reason as the font-size rule above:
   .project-table's own `white-space: nowrap` is equal specificity and
   defined later, so it would otherwise win the tie. */
#shot-table-wrap .cpt-shot-table td, #shot-table-wrap .cpt-shot-table th {
  white-space: normal;
  overflow-wrap: break-word;
}

/* Text wraps up to --cpt-line-clamp lines (recomputed in JS from the
   current row height and font size, so it never grows the row taller than
   the thumbnail dictates) and ellipsizes past that — a cell's title
   attribute (set in cpt-project.js) still shows the complete value as a
   native tooltip on hover. */
.cpt-cell-clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--cpt-line-clamp);
  overflow: hidden;
  line-height: 1.4;
}

.sortable-th {
  cursor: pointer;
  user-select: none;
}

.sortable-th:hover { color: var(--text); background: var(--border); }

/* ---------- Inline cell editing (click a cell to edit it in place) ---------- */

.cpt-editable-cell { cursor: pointer; }

.cpt-editable-cell:hover { box-shadow: inset 0 0 0 2px var(--text-dim); }

/* Shot Length and Version are computed/derived — never editable. */
.cpt-readonly-cell { color: var(--text-dim); }

.cpt-editable-cell.is-editing {
  cursor: default;
  padding: 4px 6px;
  box-shadow: inset 0 0 0 2px var(--text);
}

.cpt-cell-input,
.cpt-cell-select {
  width: 100%;
  font: inherit;
  padding: 4px 6px;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--surface);
  color: var(--text);
}

.cpt-cell-input:focus,
.cpt-cell-select:focus { outline: none; border-color: var(--text); }

/* Status/Type/Camera/Lens: always a live <select> (not click-to-edit) so
   picking a value is one click instead of two. Colored via inline style
   (set in cpt-project.js) to match the field's current value — border is
   dropped in favor of the color fill itself carrying the "pill" look, same
   visual language as the row-status tint and lens-tier coloring. */
.cpt-inline-select {
  border-color: transparent;
  border-radius: 999px;
  font-weight: 600;
  cursor: pointer;
}

.cpt-inline-select:hover { filter: brightness(0.95); }

/* A custom checkbox-type field's always-live table cell — same "one
   click, no separate edit mode" philosophy as the inline dropdowns. */
.cpt-checkbox-cell { text-align: center; }
.cpt-inline-checkbox { width: 15px; height: 15px; cursor: pointer; }

/* A compact variant of the standard .rich-editor for editing a long-text
   cell in place — smaller toolbar buttons, shorter content box, since it's
   filling a table cell rather than a modal field. */
.cpt-cell-rich-editor { min-width: 220px; }
.cpt-cell-rich-editor .rich-toolbar { padding: 3px; }
.cpt-cell-rich-editor .rich-btn { width: 22px; height: 22px; font-size: 11px; }
.cpt-cell-rich-editor .rich-content { min-height: 40px; max-height: 160px; padding: 6px 8px; font-size: inherit; }

/* Preview and any media-type field are display-only, click-to-navigate
   cells (see the .cpt-preview-cell/.cpt-media-cell branch in
   cpt-project.js's table click handler) — position:relative isn't needed
   here since sticky cells get position:sticky (which is itself a
   containing block) and scrolling cells get it inline from
   colStyleAndClass when they're a header. */
.cpt-preview-cell, .cpt-media-cell { cursor: pointer; }

/* Drag-to-reorder handle — only rendered when no column sort is active
   (see shotTableRowHtml in cpt-project.js), since a manual order is
   meaningless while the table is displaying a sorted view. Sits in the
   Preview cell's top-left corner, revealed on row hover rather than always
   visible, to avoid cluttering a dense table. */
.cpt-row-drag-handle {
  position: absolute;
  top: 3px;
  left: 3px;
  z-index: 2;
  width: 16px;
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  line-height: 1;
  border-radius: 3px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  cursor: grab;
  opacity: 0;
  user-select: none;
  touch-action: none;
}

.project-row:hover .cpt-row-drag-handle { opacity: 1; }

.cpt-row-drag-handle.dragging { cursor: grabbing; opacity: 1; }

/* Applied to the row being dragged — a lighter touch than fully hiding it,
   so it's clear which row is moving as it's dropped between others. */
.cpt-row-dragging { opacity: 0.4; }

/* Per-row delete — same hover-reveal idea as the drag handle, mirrored to
   the Preview cell's top-right corner instead of top-left. Tints red on
   hover (its own, not the toolbar's --danger-bg) as a "this is the one
   about to do something" cue, distinct from the drag handle's neutral chip. */
.cpt-row-delete-btn {
  position: absolute;
  top: 3px;
  right: 3px;
  z-index: 2;
  width: 16px;
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  line-height: 1;
  border: none;
  border-radius: 3px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  cursor: pointer;
  opacity: 0;
  padding: 0;
}

.project-row:hover .cpt-row-delete-btn { opacity: 1; }

.cpt-row-delete-btn:hover { background: var(--danger-bg); color: var(--danger-text); }

.cpt-shot-thumb-wrap {
  width: var(--cpt-thumb-width);
  aspect-ratio: 16 / 9;
  background: var(--placeholder-bg);
  border-radius: 3px;
  overflow: hidden;
}

.cpt-shot-thumb-wrap video { width: 100%; height: 100%; object-fit: cover; display: block; }
.cpt-shot-thumb-wrap .thumb-status { font-size: 8px; padding: 2px; gap: 2px; }
.cpt-shot-thumb-wrap .spinner { width: 12px; height: 12px; }

/* Purely visual dividers now — actual width comes from each column's own
   --cpt-col-<var> custom property (inline style, cpt-project.js), since
   every column is independently drag-resizable. These three class names
   are kept only to mark the thin divider between columns after
   Description (the sticky/scroll boundary gets its own stronger one). */
.cpt-scroll-col, .cpt-col-half, .cpt-col-wide { border-right: 1px solid var(--border); }

/* ---------- Column resize handles ---------- */
/* A thin invisible strip straddling the column's right edge; dragging it
   resizes that column (see wireColumnResize in cpt-project.js). Widened
   hit-target vs. visible line so it's easy to grab without being visually
   heavy. Positioned against the header cell, which is given
   position:relative (or already position:sticky) for exactly this. */
.cpt-col-resize-handle {
  position: absolute;
  top: 0;
  right: -4px;
  width: 8px;
  height: 100%;
  cursor: col-resize;
  z-index: 5;
  user-select: none;
  touch-action: none;
}

.cpt-col-resize-handle:hover, .cpt-col-resize-handle.resizing {
  background: var(--text-dim);
  opacity: 0.5;
}

/* ---------- Sticky (pinned) columns ---------- */
/* Preview, Shot ID, Version, Status, and Description stay put while
   everything after them scrolls horizontally. Each needs an opaque
   background (so scrolled-under cells don't show through) and enough
   z-index to sit above them.

   Row background is driven by --row-status-bg, a low-alpha overlay color
   set inline per-<tr> from the shot's status (see shotTableRowHtml in
   cpt-project.js) — it replaces the old plain zebra striping so a shot's
   status is visible at a glance across the whole row, not just its badge.
   Falls back to transparent/--surface for a status with no color (custom
   entries always get one, so this is mostly a defensive default). */
.project-row { background: var(--row-status-bg, transparent); }

.project-row:hover { filter: brightness(0.95); }

/* --row-status-bg is a translucent rgba tint (see .project-row above), so
   using it as the sole background here would make these sticky cells
   translucent too — exactly what breaks the "stay on top, hide what's
   scrolling underneath" job sticky columns exist to do. background-color
   stays a fully opaque var(--surface) for that occlusion; the tint is
   layered on top as a background-image instead, so the visible result
   still reads as colored without ever letting scrolled-under columns
   show through. */
.sticky-select, .cpt-sticky-col {
  position: sticky;
  z-index: 2;
  background-color: var(--surface);
  background-image: linear-gradient(var(--row-status-bg, transparent), var(--row-status-bg, transparent));
}

thead .sticky-select, thead .cpt-sticky-col {
  background: var(--subtle-bg);
  z-index: 3;
}

/* Selection checkbox — the one leftmost column with no drag-resize handle
   and no --cpt-col-<var> user override, since a checkbox needs no more
   room than this fixed width ever gives it. It's table UI chrome, not a
   shot detail, so — unlike every other column — it isn't a fieldDef and
   always sits first. */
.sticky-select {
  left: 0;
  width: var(--cpt-col-select);
  min-width: var(--cpt-col-select);
  max-width: var(--cpt-col-select);
  text-align: center;
}

.sticky-select input[type='checkbox'] { width: 15px; height: 15px; cursor: pointer; vertical-align: middle; }

/* Set by an external tool (e.g. the Resolve sync tool) when a shot it's
   tracking is no longer found on its source timeline — a heads-up, not a
   deletion, so it's deliberately small and dismissible-feeling rather
   than alarming. */
/* No position override on .sticky-select here: position:sticky already makes
   it the containing block for this badge, and a `position: relative` in its
   place (an earlier version of this rule) silently un-stuck the checkbox
   column, so scrolled cells showed through the strip it should cover. */
.cpt-removed-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  font-size: 10px;
  line-height: 1;
  color: #a1743c;
}
.project-row.cpt-shot-removed { opacity: 0.6; }

/* Generic pinned-column styling — replaces the old fixed set of named
   sticky-preview/sticky-shotcode/etc. classes. Which fieldDefs actually
   get this class, and their `left` offset (as --cpt-sticky-left-<id>),
   is computed entirely in JS (see STICKY_COUNT/updateStickyOffsets in
   cpt-project.js) since Preview/Version and every other field can now be
   dragged into any position — sticky-ness is purely "the first
   STICKY_COUNT columns by current order", not tied to a specific field. */
/* The divider marking where the frozen columns end — applied to whichever
   fieldDef currently occupies the last sticky slot. */
.cpt-sticky-col-last {
  border-right: 2px solid var(--border);
  box-shadow: 3px 0 6px rgba(0, 0, 0, 0.06);
}

/* ---------- Row-size / font-size sliders ---------- */

.cpt-shots-toolbar { display: flex; align-items: center; gap: 16px; }

.cpt-row-size-control {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-dim);
}

.cpt-row-size-control input[type="range"] {
  width: 120px;
  accent-color: var(--text);
  cursor: pointer;
}

/* Two-column field layout shared by the add-shot modal and the shot page's
   edit form — the department-notes fields are numerous, so this is what
   keeps them from reading as one long undifferentiated column. */
.dept-notes-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0 16px;
}

@media (max-width: 700px) { .dept-notes-grid { grid-template-columns: 1fr; } }

.cpt-core-grid { margin-bottom: 4px; }

.add-shot-modal { width: 640px; max-width: 92vw; max-height: 88vh; overflow-y: auto; }

/* ---------- CPT shot detail page ---------- */

.cpt-shot-sub { color: var(--text-dim); font-size: 13px; margin: 4px 0 0; }

/* Full-bleed black band the video sits on — genuinely edge to edge (no
   .cpt-hero-full margin, no border-radius), unlike the project hero/table
   bands which stop 32px short of the viewport to line up with .container.
   Deliberately hardcoded black (not a theme variable) so the video reads
   as the visually dominant thing on the page regardless of light/dark
   theme, the same reasoning the CPT project hero's glass UI already uses
   for its own hardcoded colors. The video itself is capped narrower than
   the band, so the band's black shows as a deliberate "letterboxing"
   margin on wide screens rather than the video running 100% of the
   viewport width. */
.cpt-shot-hero-band {
  background: #000;
  display: flex;
  justify-content: center;
  padding: 24px 0;
  margin-bottom: 24px;
}

/* Video (left) + comments panel (right) side by side on the black band —
   flex row so the video keeps its own max-width cap on wide screens while
   still shrinking to make room for the panel on narrower ones. */
.cpt-video-comments-layout {
  display: flex;
  align-items: stretch;
  gap: 20px;
  width: 100%;
  max-width: 1940px;
}

.shot-video-wrap {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 1600px;
  background: #000;
}

.shot-video-player { width: 100%; display: block; max-height: 78vh; }

/* Overdraw layers — see positionDrawingCanvases in cpt-shot.js, which
   sizes/places both canvases over the visible picture (letterbox-aware)
   instead of relying on CSS to guess where it is. */
.cpt-video-stage { position: relative; }
.cpt-drawing-canvas { position: absolute; display: block; }
.cpt-drawing-playback { pointer-events: none; z-index: 1; }
.cpt-drawing-capture { z-index: 2; cursor: crosshair; touch-action: none; }
.cpt-drawing-canvas[hidden],
.cpt-drawing-toolbar[hidden],
.cpt-pending-drawing[hidden] { display: none !important; }

.cpt-drawing-toolbar {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  max-width: calc(100% - 20px);
  background: rgba(0, 0, 0, 0.75);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 8px;
}
.cpt-drawing-tools, .cpt-drawing-colors, .cpt-drawing-actions { display: flex; align-items: center; gap: 6px; }
.cpt-drawing-btn {
  font: inherit;
  font-size: 12px;
  padding: 4px 9px;
  color: #fff;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 5px;
  cursor: pointer;
}
.cpt-drawing-btn:hover { background: rgba(255, 255, 255, 0.24); }
.cpt-drawing-btn.active { background: rgba(255, 255, 255, 0.32); border-color: #fff; }
.cpt-drawing-btn.primary { background: #fff; color: #111; border-color: #fff; }
.cpt-drawing-btn.primary:hover { background: #e6e6e6; }
.cpt-drawing-swatch {
  width: 20px;
  height: 20px;
  padding: 0;
  border-radius: 50%;
  background: var(--swatch);
  border: 2px solid rgba(255, 255, 255, 0.35);
  cursor: pointer;
}
.cpt-drawing-swatch.active { border-color: #fff; box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.6); }

/* Composer-side "a drawing is queued for your next comment" note. */
.cpt-pending-drawing {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: #ffd60a;
}
.cpt-pending-drawing-remove {
  border: none;
  background: transparent;
  color: rgba(255, 255, 255, 0.6);
  cursor: pointer;
  font-size: 12px;
  padding: 0 2px;
}
.cpt-pending-drawing-remove:hover { color: #fff; }

.cpt-drawing-thickness {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: #fff;
}
.cpt-drawing-thickness input[type="range"] { width: 80px; }
.cpt-drawing-thickness span { min-width: 1.4em; text-align: right; font-variant-numeric: tabular-nums; }
.cpt-drawing-capture.erasing { cursor: cell; }

/* Small pencil next to a comment's timecode chip when it carries a
   drawing — also the per-comment hide/show toggle for that drawing. */
.cpt-comment-drawing-badge {
  font-size: 11px;
  padding: 0 4px;
  color: inherit;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 3px;
  cursor: pointer;
  opacity: 0.85;
}
.cpt-comment-drawing-badge:hover { border-color: rgba(255, 255, 255, 0.35); opacity: 1; }
.cpt-comment-drawing-badge.hidden-drawing { opacity: 0.4; text-decoration: line-through; }

/* Fullscreen target is the whole wrap (video + controls), not the bare
   <video> — that way the custom transport stays visible and usable in
   fullscreen too, instead of disappearing along with the rest of the
   page. Centers the (now height-constrained, not width-constrained)
   video above the controls bar within the fullscreen viewport. */
.shot-video-wrap:fullscreen {
  display: flex;
  flex-direction: column;
  height: 100vh;
  max-width: none;
  padding: 0 28px 14px;
  box-sizing: border-box;
}

/* The picture fills whatever height is left above the controls (letterboxed
   by object-fit, and the drawing canvases follow that letterbox — see
   videoDisplayRect in cpt-shot.js) rather than sitting at its own file's
   pixel size: a 720p version would otherwise float small in the middle of
   a 4K screen. The two control rows below are never allowed to shrink or be
   centered narrower than the screen — that was making the scrub bar tiny —
   so they stretch edge to edge instead, with a fatter, easier-to-grab track. */
.shot-video-wrap:fullscreen .cpt-video-stage {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
}
.shot-video-wrap:fullscreen .shot-video-player {
  width: 100%;
  height: 100%;
  max-height: none;
  object-fit: contain;
}
.shot-video-wrap:fullscreen .cpt-video-controls-bar,
.shot-video-wrap:fullscreen .cpt-video-toolbar {
  flex: 0 0 auto;
  width: 100%;
}
.shot-video-wrap:fullscreen .cpt-video-controls-bar { margin-top: 14px; gap: 14px; }
.shot-video-wrap:fullscreen .cpt-video-track-wrap { padding: 12px 0 18px; }
.shot-video-wrap:fullscreen .cpt-video-track { height: 10px; }
.shot-video-wrap:fullscreen .cpt-video-track-handle { width: 18px; height: 18px; }
.shot-video-wrap:fullscreen .cpt-video-range-handle { width: 10px; height: 24px; }
.shot-video-wrap:fullscreen .cpt-video-time { font-size: 14px; min-width: 46px; }

/* Custom playback transport — deliberately *not* the native `controls`
   attribute, which draws its own play/seek/volume bar as an overlay on
   top of the video's bottom edge. Living in this own strip below the
   video instead means the controls can never cover part of the frame,
   which matters for a review tool where the whole image is the point. */
.cpt-video-controls-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  color: rgba(255, 255, 255, 0.85);
}

.cpt-video-icon-btn {
  background: rgba(255, 255, 255, 0.14);
  border: none;
  color: #fff;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  border-radius: 4px;
  cursor: pointer;
  font-size: 13px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}

.cpt-video-icon-btn:hover { background: rgba(255, 255, 255, 0.26); }

/* Sitting among the outlined text buttons (Set In/Set Out/Draw...), the
   loop toggle needs the same outline or it reads as a different kind of
   control. The transport row's own icon buttons (play/mute/fullscreen) stay
   outline-free. */
.cpt-video-range-controls .cpt-video-icon-btn { border: 1px solid rgba(255, 255, 255, 0.4); }

.cpt-video-icon-btn:link, .cpt-video-icon-btn:visited { text-decoration: none; }

/* The loop toggle gets its own "on" state — a filled accent instead of
   the shared glass hover color, so it's clear at a glance whether looping
   is currently active without having to read the icon closely. */
.cpt-video-icon-btn.active { background: rgba(255, 255, 255, 0.85); color: #1a1512; }

.cpt-video-time {
  font-family: ui-monospace, 'SF Mono', Consolas, monospace;
  font-size: 12px;
  color: rgba(255, 255, 255, 0.7);
  min-width: 38px;
  text-align: center;
  flex-shrink: 0;
}

.cpt-video-volume { width: 70px; flex-shrink: 0; }

/* accent-color themes the volume slider's native fill/thumb in one line,
   without needing to re-skin every browser's own ::-webkit-slider-thumb/
   ::-moz-range-thumb — broad enough support (Chrome/Edge/Firefox) for
   this to just work everywhere that matters. */
.cpt-video-volume { accent-color: #fff; cursor: pointer; }

/* ---------- Custom scrub track (progress + draggable playhead + an
   optional draggable in/out loop range) ---------- */

.cpt-video-track-wrap { flex: 1 1 auto; min-width: 80px; display: flex; align-items: center; }

.cpt-video-track {
  position: relative;
  width: 100%;
  height: 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.22);
  cursor: pointer;
}

.cpt-video-track-progress {
  position: absolute;
  inset: 0;
  width: 0%;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.9);
  pointer-events: none;
}

/* The selected loop range — a distinct accent (not just another shade of
   white) so it reads as "this part is special", not just more progress. */
.cpt-video-track-range {
  position: absolute;
  top: 0;
  bottom: 0;
  background: rgba(255, 196, 0, 0.35);
  pointer-events: none;
}

.cpt-video-track-handle {
  position: absolute;
  top: 50%;
  left: 0%;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: #fff;
  transform: translate(-50%, -50%);
  cursor: grab;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.3);
}

.cpt-video-track-handle.dragging { cursor: grabbing; }

/* Taller than the playhead handle and pill-shaped (not round) so the two
   are never mistaken for each other at a glance. */
.cpt-video-range-handle {
  position: absolute;
  top: 50%;
  width: 8px;
  height: 18px;
  border-radius: 3px;
  background: #ffc400;
  transform: translate(-50%, -50%);
  cursor: ew-resize;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.3);
  z-index: 1;
}

/* One small tick per top-level comment, sitting just under the track —
   frame.io's own signature "where the notes are" at a glance. Absolutely
   positioned within the track itself (not a separate strip) so they line
   up exactly with the timecode they're pinned to. */
.cpt-video-comment-markers { position: absolute; inset: 0; pointer-events: none; }

.cpt-video-comment-marker {
  position: absolute;
  top: 100%;
  margin-top: 8px;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: #4fc3f7;
  transform: translateX(-50%) rotate(45deg);
  cursor: pointer;
  pointer-events: auto;
  box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.4);
}

.cpt-video-comment-marker.resolved { background: rgba(255, 255, 255, 0.35); }

.cpt-video-comment-marker:hover { background: #81d4fa; }

/* More than one top-level comment landed on the exact same frame (two
   different people, or a shot/full-edit pair whose content genuinely
   differs so it didn't dedupe) — a plain dot would silently look like
   just one comment, so a stacked one gets a small count badge instead. */
.cpt-video-comment-marker.stacked {
  width: 10px;
  height: 10px;
}
.cpt-video-comment-marker-count {
  position: absolute;
  top: -13px;
  left: 50%;
  transform: translateX(-50%) rotate(-45deg);
  background: #01579b;
  color: #fff;
  font-size: 8px;
  font-weight: 700;
  line-height: 1;
  padding: 1px 3px;
  border-radius: 6px;
  white-space: nowrap;
}

/* A range comment (Set In/Set Out was active when it was posted) gets
   this thin bar spanning its in→out, sitting just above its marker dot —
   the dot alone only ever anchors a single point, this is what shows the
   comment actually covers a stretch of the clip. */
.cpt-video-comment-marker-range {
  position: absolute;
  top: 100%;
  margin-top: 3px;
  height: 3px;
  border-radius: 2px;
  background: #4fc3f7;
  cursor: pointer;
  pointer-events: auto;
}

.cpt-video-comment-marker-range.resolved { background: rgba(255, 255, 255, 0.3); }

.cpt-video-comment-marker-range:hover { background: #81d4fa; }

/* Frame-accurate stepping + slow-motion scrubbing controls, sitting right
   under the video on the same black band — same "glass button on dark
   photo/video" language the project hero already uses (.cpt-hero-btn),
   since this toolbar lives on the same kind of background. */
.cpt-video-toolbar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 10px;
  color: rgba(255, 255, 255, 0.85);
  font-size: 13px;
}

.cpt-video-frame-controls { display: flex; align-items: center; gap: 10px; }

/* Same 30px height as the icon buttons it sits alongside in
   .cpt-video-controls-bar (Set In/Set Out/Reset Range/Frame step), so a row
   mixing text buttons and square icon buttons reads as one consistent
   toolbar instead of the text buttons looking taller/shorter than the rest. */
.cpt-video-btn {
  font-size: 12.5px;
  padding: 0 12px;
  height: 30px;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
}

.cpt-video-frame-counter {
  min-width: 92px;
  text-align: center;
  font-family: ui-monospace, 'SF Mono', Consolas, monospace;
  font-size: 12.5px;
  color: rgba(255, 255, 255, 0.7);
}

.cpt-video-speed-control {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: rgba(255, 255, 255, 0.7);
}

.cpt-video-speed-control .select {
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.4);
  color: #fff;
  font-size: 12.5px;
  padding: 4px 8px;
  height: auto;
}

/* The closed <select> box takes the glass styling above just fine, but
   its native dropdown popup list doesn't inherit background — most
   browsers force that popup's background to plain white regardless,
   which combined with the parent's color:#fff above left every option
   white-on-white until hovered. Setting a normal light-mode color/bg
   directly on the <option>s fixes the popup without affecting the
   closed box's own glass look. */
.cpt-video-speed-control .select option { color: #1a1512; background: #fff; }

/* CPT version thumbnails are full-frame posters (16:9), not the square
   crop Gallery's own version strip uses. */
.cpt-version-item .thumb-wrap { aspect-ratio: 16 / 9; }

/* ---------- Comments panel (frame.io-style, one per version) ---------- */

.cpt-comments-panel {
  width: 340px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 5px;
  color: rgba(255, 255, 255, 0.9);
  overflow: hidden;
  /* Without this, a long comment list's natural content height becomes
     the tallest thing in .cpt-video-comments-layout (a stretch-aligned
     flex row), which then stretches .shot-video-wrap to match — the
     whole page balloons instead of the video staying its own size and
     the list scrolling internally. Matches .shot-video-player's own cap
     (see below) so the panel never outgrows the video it sits beside. */
  max-height: 78vh;
}

.cpt-comments-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  flex-shrink: 0;
}

.cpt-comments-header h3 { margin: 0; font-size: 14px; }

.cpt-comments-count { font-size: 12px; color: rgba(255, 255, 255, 0.5); }

.cpt-comments-carryover-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 10px 14px 0;
}

.cpt-comments-carryover-btn {
  font-size: 12px;
  padding: 6px 10px;
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.3);
  color: #fff;
}

/* .btn.ghost:hover (2 classes) otherwise beats this rule's own
   specificity (1 class) and wins with its light --subtle-bg — white
   text on a near-white background, illegible. Matching its own class
   count here (.btn.ghost.cpt-comments-carryover-btn:hover = 3) instead
   of just re-adding !important keeps this a normal, overridable rule. */
.btn.ghost.cpt-comments-carryover-btn:hover { background: rgba(255, 255, 255, 0.18); }

.cpt-comments-resolve-row { display: flex; align-items: center; gap: 10px; padding: 6px 14px 0; }
.cpt-comments-resolve-status { font-size: 12px; color: var(--text-dim); min-width: 0; }
.cpt-comments-resolve-status.error { color: #f2867c; }
.cpt-comments-overlay-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 10px 14px 0;
  font-size: 12px;
  color: rgba(255, 255, 255, 0.6);
  cursor: pointer;
}

.cpt-comment-mapped-badge {
  font-size: 10.5px;
  color: #81d4fa;
  text-decoration: none;
  border: 1px solid rgba(79, 195, 247, 0.4);
  border-radius: 3px;
  padding: 1px 5px;
}
.cpt-comment-mapped-badge:hover { background: rgba(79, 195, 247, 0.18); }

/* Marks whichever comment(s) a marker/timecode-chip click just jumped
   to (see highlightComments in cpt-shot.js) — applied and removed with
   no transition, so it's unmistakable the instant it lands, and stays
   until the user clicks something else (cleared by the document click
   listener in wireCommentsPanel, not a timer). The negative margin lets
   the highlight's background/border bleed slightly past the comment's
   own content width without shifting anything else in the list,
   matching the list's own side padding instead of looking cramped
   against the text. */
.cpt-comment.cpt-comment-highlight,
.cpt-comment-reply.cpt-comment-highlight {
  background: rgba(255, 196, 0, 0.22);
  border: 1px solid rgba(255, 196, 0, 0.85);
  border-radius: 6px;
  margin: 0 -9px;
  padding: 5px 8px;
}

.cpt-comments-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 10px 14px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.cpt-comments-empty { color: rgba(255, 255, 255, 0.45); font-size: 13px; text-align: center; padding: 20px 0; }

.cpt-comment-thread { display: flex; flex-direction: column; gap: 8px; }

.cpt-comment, .cpt-comment-reply {
  display: flex;
  gap: 8px;
  align-items: flex-start;
}

.cpt-comment-reply { margin-left: 26px; }

/* Collapsed state for a thread with more than one reply — see repliesHtml
   in cpt-shot.js. Text-button styling matches .cpt-comment-action-btn's
   quiet, low-emphasis look rather than a full glass button, since this
   is a "reveal more" affordance, not a primary action. */
.cpt-comment-toggle-replies {
  margin-left: 26px;
  background: none;
  border: none;
  padding: 0;
  color: rgba(255, 255, 255, 0.5);
  font-size: 11.5px;
  cursor: pointer;
  text-align: left;
}

.cpt-comment-toggle-replies:hover { color: rgba(255, 255, 255, 0.9); }

.cpt-comment-body-col { flex: 1 1 auto; min-width: 0; }

.cpt-comment-head { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }

.cpt-comment-author { font-weight: 600; font-size: 12.5px; }

.cpt-comment-time { font-size: 11px; color: rgba(255, 255, 255, 0.45); }

.cpt-comment-timecode {
  font-family: ui-monospace, 'SF Mono', Consolas, monospace;
  font-size: 11px;
  background: rgba(79, 195, 247, 0.22);
  color: #81d4fa;
  border: none;
  border-radius: 3px;
  padding: 1px 6px;
  cursor: pointer;
}

.cpt-comment-timecode:hover { background: rgba(79, 195, 247, 0.38); }

.cpt-comment-resolved-badge {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: rgba(255, 255, 255, 0.4);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 3px;
  padding: 1px 5px;
}

.cpt-comment-text {
  font-size: 13px;
  line-height: 1.4;
  margin: 3px 0 0;
  white-space: pre-wrap;
  word-break: break-word;
}

.cpt-comment.resolved .cpt-comment-text, .cpt-comment.resolved .cpt-comment-author { opacity: 0.55; }

.cpt-comment-actions { display: flex; gap: 10px; margin-top: 4px; }

.cpt-comment-action-btn {
  background: none;
  border: none;
  padding: 0;
  color: rgba(255, 255, 255, 0.5);
  font-size: 11.5px;
  cursor: pointer;
}

.cpt-comment-action-btn:hover { color: rgba(255, 255, 255, 0.9); }

.cpt-comment-action-btn.danger:hover { color: #ff8a80; }

.cpt-comment-edit-box, .cpt-comment-reply-box {
  margin-top: 6px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.cpt-comment-edit-box textarea, .cpt-comment-reply-box textarea, .cpt-comment-input {
  width: 100%;
  resize: vertical;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 4px;
  color: #fff;
  font-size: 13px;
  font-family: inherit;
  padding: 8px;
  box-sizing: border-box;
}

.cpt-comment-edit-box textarea:focus, .cpt-comment-reply-box textarea:focus, .cpt-comment-input:focus {
  outline: none;
  border-color: rgba(255, 255, 255, 0.5);
}

.cpt-comment-edit-actions, .cpt-comment-reply-actions { display: flex; gap: 8px; justify-content: flex-end; }

.cpt-comment-composer {
  flex-shrink: 0;
  padding: 10px 14px 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.cpt-comment-composer-actions { display: flex; align-items: center; justify-content: space-between; }

.cpt-comment-composer-time {
  font-family: ui-monospace, 'SF Mono', Consolas, monospace;
  font-size: 12px;
  color: rgba(255, 255, 255, 0.5);
}

/* @mention highlight inside a rendered comment body (see
   renderCommentBodyHtml in cpt-shot.js) — .is-me picks out a mention of
   the current viewer specifically, so "you were tagged" jumps out
   without having to read the whole comment. */
.cpt-comment-mention { color: #81d4fa; font-weight: 600; }

.cpt-comment-mention.is-me {
  background: rgba(255, 196, 0, 0.22);
  color: #ffc400;
  border-radius: 3px;
  padding: 0 3px;
}

/* Floating @mention picker — position:fixed (set in JS, against
   whichever textarea is active) so it always escapes the comments
   list's own overflow:auto clipping rather than getting cut off near
   the bottom edge. One shared element for the composer + every
   dynamically-rendered reply/edit box. */
.cpt-mention-suggestions {
  position: fixed;
  z-index: 50;
  max-height: 220px;
  overflow-y: auto;
  background: #201c18;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 5px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  padding: 4px;
}

.cpt-mention-suggestion {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: 4px;
  padding: 6px 8px;
  cursor: pointer;
  color: #fff;
  font-size: 13px;
}

.cpt-mention-suggestion:hover, .cpt-mention-suggestion.active { background: rgba(255, 255, 255, 0.14); }

@media (max-width: 900px) {
  .cpt-video-comments-layout { flex-direction: column; }
  .cpt-comments-panel { width: 100%; max-height: 420px; }
}

/* ---------- Shot detail: field list (reorder/add/delete) ---------- */
/* Three sections (Sticky/Essentials/Additional — see cptFieldSections in
   cpt-common.js) stacked vertically, each its own drag-drop scope; within
   a section, fields lay out as a grid instead of one full-width column
   so several short fields (a dropdown, a number) share a row instead of
   each claiming the whole width for a control a fraction of that size. */

.cpt-field-list { display: flex; flex-direction: column; gap: 22px; margin-bottom: 16px; }

/* Additional lives outside .cpt-field-list entirely (see
   #shot-fields-additional in cpt-shot.html) — same container width as
   Sticky/Essentials above it, just its own element so it can use a
   different grid-template-columns (see [data-field-section="additional"]
   below) without a generic rule having to special-case it. This just
   fills in the vertical spacing .cpt-field-list's own `gap`/margin would
   otherwise have provided between sections. */
#shot-fields-additional { margin-top: 22px; margin-bottom: 16px; }

.cpt-field-section-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-dim);
  margin: 0 0 8px;
}

.cpt-field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 10px;
  align-items: start;
  min-height: 10px; /* stays a valid drop target even when it holds zero rows */
}

/* Sticky always lays out as exactly two equal columns — Shot ID and
   Status (or whatever's currently sticky) sit side by side filling the
   full page width, at matching height (align-items: stretch, unlike the
   other two sections). */
.cpt-field-grid[data-field-section="sticky"] { grid-template-columns: 1fr 1fr; align-items: stretch; }

/* Essentials is a fixed 3-column layout (not the flexible auto-fill
   every other section uses) specifically so a wide field lands exactly
   where intended: Type+Camera fill row 1 with one cell to spare, a wide
   field (Tone) needs all 3 and so — CSS grid's own sparse auto-placement,
   no JS involved — wraps to a fresh row 2 rather than trying to cram into
   that leftover cell, then In/Out Frame + Shot Length exactly fill row 3,
   and Lens alone starts row 4. Changing which fields are Essentials (or
   their order) keeps this same shape automatically. */
.cpt-field-grid[data-field-section="essentials"] { grid-template-columns: repeat(3, 1fr); }

/* Additional is also fixed-column, at 3 (2 on medium screens, 1 narrow —
   see the media queries below) — unlike Sticky/Essentials, a rich text
   field here does *not* span extra columns (cpt-field-row-wide is
   cancelled below): the point of 3 columns is several fields side by
   side, so each one just gets a taller default box instead of also
   claiming double width. align-items: stretch (the other two sections
   use the default `start`) means every card in the same row matches the
   tallest one instead of sitting short with blank space below it; the
   row/control/preview min-heights below are what give a short field
   (a plain number, an empty media slot) the same baseline even in a row
   that happens to have no rich text neighbor to stretch against. */
.cpt-field-grid[data-field-section="additional"] { grid-template-columns: repeat(3, 1fr); align-items: stretch; }
.cpt-field-grid[data-field-section="additional"] .cpt-field-row-wide { grid-column: auto; }
.cpt-field-grid[data-field-section="additional"] .cpt-field-row { min-height: 280px; }
.cpt-field-grid[data-field-section="additional"] .cpt-field-row-control { display: flex; flex-direction: column; }
/* 130px * 1.5 */
.cpt-field-grid[data-field-section="additional"] .rich-content { min-height: 195px; }
.cpt-field-grid[data-field-section="additional"] .cpt-media-field { width: 100%; }
.cpt-field-grid[data-field-section="additional"] .cpt-media-field-preview,
.cpt-field-grid[data-field-section="additional"] .cpt-media-field-empty {
  width: 100%;
  max-width: none;
  height: 195px;
  max-height: none;
}

/* Description (Sticky) or Tone (Essentials) — whichever rich text field
   is currently wide — grows with its content instead of capping out and
   scrolling internally, since it always spans its section's full width
   (Additional's own rich text fields don't get this: they're deliberately
   capped/scrollable at a taller-than-default min-height instead, or
   3-a-row would grow to wildly different heights). */
.cpt-field-grid[data-field-section="sticky"] .rich-content,
.cpt-field-grid[data-field-section="essentials"] .rich-content { max-height: none; }

.cpt-field-section-empty { grid-column: 1 / -1; color: var(--text-dim); font-size: 13px; margin: 0; }

/* The full-edit pseudo-shot's own read-only info block (see
   fullEditInfoHtml/renderFullEditFields in cpt-shot.js) — sits where a
   regular shot's department-notes "Additional" section would. */
.cpt-full-edit-page-stats {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  row-gap: 12px;
  margin-bottom: 14px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
/* One stat = small label over value; every one after the first gets a
   vertical divider on its left, with matching space either side, so the
   row reads as one strip that wraps cleanly on a narrow screen. */
.cpt-full-edit-page-stats > div {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 0 20px;
  min-width: 0;
}
.cpt-full-edit-page-stats > div:first-child { padding-left: 0; }
.cpt-full-edit-page-stats > div + div { border-left: 1px solid var(--border); }
.cpt-full-edit-page-stats > div > span:last-child { font-size: 14px; white-space: nowrap; }
.cpt-full-edit-updated-shots-list { font-size: 13px; margin: 2px 0 16px; }

/* Rich text and media fields need real room to be usable (an editor
   toolbar, an image/video preview) — everything else (a short text/
   number/dropdown/checkbox value) doesn't, so only these span the rest
   of their row (Additional cancels this — see above) instead of every
   field claiming the same width regardless of what it actually needs. */
.cpt-field-row-wide { grid-column: 1 / -1; }

@media (max-width: 1100px) {
  .cpt-field-grid[data-field-section="additional"] { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 640px) {
  .cpt-field-grid,
  .cpt-field-grid[data-field-section="sticky"],
  .cpt-field-grid[data-field-section="essentials"],
  .cpt-field-grid[data-field-section="additional"] { grid-template-columns: 1fr; }
  .cpt-field-row-wide { grid-column: auto; }
}

.cpt-field-row {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 12px 14px;
  background: var(--surface);
  min-width: 0;
}

/* The dragged row just dims in place — see wireFieldDragReorder in
   cpt-shot.js — rather than being hidden/replaced by a placeholder that
   participates in the grid's own layout. Nothing about the grid's
   actual layout changes at all until the drop; the floating ghost label
   and the target highlight below are the only things that move, and
   neither one can trigger a reflow (position:fixed and box-shadow are
   both layout-inert), which is what makes the current drop target
   perfectly stable under a stationary-but-jittery real mouse instead of
   flip-flopping between two candidates. */
.cpt-field-row-dragging { opacity: 0.35; }

.cpt-field-drag-ghost {
  position: fixed;
  z-index: 999;
  pointer-events: none;
  transform: translate(-50%, -140%);
  padding: 5px 10px;
  border-radius: 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}

/* An inset box-shadow reads clearly as "insert on this side" without
   adding any actual width/height to the row (unlike a border), so
   toggling it never itself causes the reflow it's meant to be immune to. */
.cpt-field-row-drop-target-before { box-shadow: inset 3px 0 0 0 var(--text-dim); }
.cpt-field-row-drop-target-after { box-shadow: inset -3px 0 0 0 var(--text-dim); }

/* Highlights the whole grid instead, for the one case a single row
   can't stand in for a target: a section that currently holds zero
   fields at all. */
.cpt-field-grid-drop-target { outline: 2px dashed var(--hover-border); outline-offset: 4px; }

.cpt-field-row-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.cpt-field-drag-handle {
  cursor: grab;
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1;
  user-select: none;
  touch-action: none;
}

.cpt-field-drag-handle.dragging { cursor: grabbing; }

.cpt-field-label { font-weight: 600; font-size: 13.5px; }

.cpt-field-type-badge {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-dim);
  background: var(--subtle-bg);
  padding: 2px 7px;
  border-radius: 3px;
}

/* An unlocked field's type badge is a real <select> (locked fields keep
   the plain, non-interactive span above) — styled to still read as a
   badge rather than a normal form control, so retyping a field is
   available without the row looking cluttered with an obvious dropdown. */
.cpt-field-type-select {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-dim);
  background: var(--subtle-bg);
  border: 1px solid transparent;
  padding: 2px 7px;
  border-radius: 3px;
  cursor: pointer;
}

.cpt-field-type-select:hover { border-color: var(--hover-border); }

/* This field's 1-based left-to-right position within its own section —
   matches the table's own column order exactly, since both read the same
   fieldDefs order (see cptFieldSections in cpt-common.js). */
.cpt-field-col-badge {
  font-size: 10.5px;
  font-family: ui-monospace, 'SF Mono', Consolas, monospace;
  color: var(--text-dim);
  background: var(--subtle-bg);
  padding: 2px 6px;
  border-radius: 3px;
  border: 1px solid transparent;
  cursor: pointer;
}

.cpt-field-col-badge:hover { border-color: var(--hover-border); }

/* Swapped in for the badge on click (see wireFieldColumnReorder) — an
   exact-placement alternative to dragging, styled to match it. */
.cpt-field-col-select {
  font-size: 10.5px;
  font-family: ui-monospace, 'SF Mono', Consolas, monospace;
  color: var(--text-dim);
  background: var(--subtle-bg);
  border: 1px solid var(--hover-border);
  padding: 2px 6px;
  border-radius: 3px;
  cursor: pointer;
}

.cpt-field-delete-btn {
  margin-left: auto;
  border: none;
  background: transparent;
  cursor: pointer;
  font-size: 13px;
  color: var(--text-dim);
  padding: 2px 6px;
  border-radius: 3px;
}

.cpt-field-delete-btn:hover { background: var(--danger-bg); color: var(--danger-text); }

.cpt-field-row-control { flex: 1 1 auto; min-width: 0; }
.cpt-field-row-control input[type='text'],
.cpt-field-row-control input[type='number'],
.cpt-field-row-control select { width: 100%; }

/* A plain text/number input here had never gotten any real styling
   (just the width rule above) — falling back to each browser's own raw
   default appearance, which is what read as an "ugly black outline" on
   focus. Matches .field input's look elsewhere on the page instead. */
.cpt-field-row-control input[type='text'],
.cpt-field-row-control input[type='number'] {
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 5px;
  font-size: 13.5px;
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
}

.cpt-field-row-control input[type='text']:focus,
.cpt-field-row-control input[type='number']:focus { outline: 2px solid var(--text); outline-offset: 1px; }

/* ---------- Per-field JIRA links (Additional section only) ---------- */

.cpt-field-jira { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }

/* Sits directly below .cpt-field-jira with no border of its own — the two
   read as one "extra metadata" area with a single divider above it,
   rather than two separately-boxed sections stacked on top of each other. */
.cpt-field-gallery { margin-top: 8px; }

.cpt-field-jira-links { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }

.cpt-field-jira-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--subtle-bg);
  border-radius: 999px;
  padding: 3px 4px 3px 9px;
  font-size: 12px;
}

.cpt-field-jira-chip a { color: var(--text); text-decoration: none; }
.cpt-field-jira-chip a:hover { text-decoration: underline; }

.cpt-field-jira-remove,
.cpt-field-gallery-remove {
  border: none;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  padding: 2px 5px;
  border-radius: 50%;
}

.cpt-field-jira-remove:hover,
.cpt-field-gallery-remove:hover { background: var(--danger-bg); color: var(--danger-text); }

.cpt-field-jira-add-btn,
.cpt-field-gallery-add-btn {
  border: none;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  font-size: 12px;
  padding: 2px 0;
}

.cpt-field-jira-add-btn:hover,
.cpt-field-gallery-add-btn:hover { color: var(--text); text-decoration: underline; }

.cpt-field-jira-form { display: flex; flex-direction: column; gap: 6px; }

.cpt-field-jira-input {
  width: 100%;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: 4px;
  font-size: 13px;
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  box-sizing: border-box;
}

.cpt-field-jira-input:focus { outline: 2px solid var(--text); outline-offset: 1px; }

.cpt-field-jira-form-actions { display: flex; gap: 8px; justify-content: flex-end; }

/* The table's own corner badges (see jiraBadgeHtml in cpt-project.js) —
   sitting in a cell's top-right corner, only when that field actually
   has at least one link, so it never adds visual noise to the vast
   majority of cells that don't. One small icon per link (each its own
   real link to that one, not a single icon plus a count) laid out in a
   tight row via the group wrapper, which is the thing actually
   positioned — the icons inside it are just flex children. */
/* Excludes the sticky columns (checkbox, and any pinned fieldDef column —
   see .sticky-select/.cpt-sticky-col) — they need position:sticky to
   actually stay put while scrolling, and this would otherwise win over
   that (higher specificity: an id + element vs. their plain class) and
   silently turn every one of them back into a normal scrolling column. */
#shot-table-wrap td:not(.sticky-select):not(.cpt-sticky-col) { position: relative; }

.cpt-jira-badge-group {
  position: absolute;
  top: 3px;
  right: 3px;
  display: flex;
  align-items: center;
  gap: 3px;
}

.cpt-jira-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  background: var(--subtle-bg);
  border-radius: 999px;
  font-size: 10.5px;
  text-decoration: none;
  box-shadow: 0 0 0 1px var(--border);
}

.cpt-jira-badge:hover { background: var(--hover-border); }

.cpt-jira-badge-more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 18px;
  padding: 0 5px;
  background: var(--subtle-bg);
  border-radius: 999px;
  font-size: 10px;
  font-weight: 600;
  color: var(--text-dim);
  box-shadow: 0 0 0 1px var(--border);
}

/* ---------- "+ Gallery link" picker modal ---------- */

.gallery-link-modal { max-width: 480px; text-align: left; }

.gallery-link-paste-row { display: flex; gap: 8px; margin-bottom: 14px; }
.gallery-link-paste-row .search-input { flex: 1; }

.gallery-link-divider {
  display: flex;
  align-items: center;
  text-align: center;
  color: var(--text-dim);
  font-size: 12px;
  margin: 14px 0 10px;
}
.gallery-link-divider::before, .gallery-link-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}
.gallery-link-divider span { padding: 0 10px; }

.gallery-link-results {
  margin-top: 10px;
  max-height: 320px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.gallery-link-loading { color: var(--text-dim); font-size: 13px; padding: 10px 2px; }

.gallery-link-result {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 6px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
}

.gallery-link-result:hover { background: var(--subtle-bg); border-color: var(--border); }

.gallery-link-result-thumb {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--placeholder-bg);
  position: relative;
}
.gallery-link-result-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gallery-link-result-thumb .thumb-status { font-size: 7px; padding: 2px; gap: 2px; }
.gallery-link-result-thumb .spinner { width: 10px; height: 10px; }
.gallery-link-result-thumb .play-badge { width: 16px; height: 16px; font-size: 7px; bottom: 3px; left: 3px; }

.gallery-link-result-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.gallery-link-result-title {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.gallery-link-result-owner { font-size: 12px; color: var(--text-dim); }

/* "+ Add field" — a small inline form, not a modal, so it reads as part
   of the same field list rather than a separate flow. */
.cpt-add-field-row { margin-bottom: 16px; }

.cpt-add-field-form {
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 14px;
  margin-bottom: 16px;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.cpt-shot-footer { gap: 12px; }

/* ---------- Custom media (image/video) field ---------- */

.cpt-media-field { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }

.cpt-media-field-preview {
  max-width: 320px;
  max-height: 220px;
  border-radius: 4px;
  background: var(--placeholder-bg);
  object-fit: contain;
}

.cpt-media-field-empty {
  width: 200px;
  height: 110px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  font-size: 13px;
  border: 1px dashed var(--border);
  border-radius: 4px;
}

/* ---------- Checkbox-type field ---------- */

.checkbox-label { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-weight: normal; }
.field-checkbox input[type='checkbox'] { width: 15px; height: 15px; cursor: pointer; }

/* List view: the same fields the project page exposes, as actual columns —
   a dense alternative to the grid for scanning many projects at once. */
.list-view {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* The table's column count doesn't comfortably fit the page's normal
   1400px container — widen the container itself while list view is
   active, rather than compressing the table's content. */
#gallery-container.list-active {
  max-width: 1600px;
}

.project-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
}

.project-table th {
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-dim);
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  background: var(--subtle-bg);
}

.project-table td {
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
  white-space: nowrap;
}

.project-row { cursor: pointer; }
.project-row:hover { background: var(--subtle-bg); }
.project-row:last-child td { border-bottom: none; }

.project-table th.sortable-col {
  cursor: pointer;
  user-select: none;
}
.project-table th.sortable-col:hover {
  color: var(--text);
}
.project-table th.active-sort {
  color: var(--text);
}
.sort-arrow {
  font-size: 8px;
  margin-left: 3px;
  display: inline-block;
}

.row-dim { color: var(--text-dim); }

.row-thumb-wrap {
  position: relative;
  width: 44px;
  height: 44px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--placeholder-bg);
}

.row-thumb-wrap img.thumb { width: 100%; height: 100%; object-fit: cover; display: block; }
.row-thumb-wrap .play-badge { width: 18px; height: 18px; font-size: 8px; bottom: 4px; left: 4px; }
.row-thumb-wrap .thumb-status { font-size: 8px; padding: 2px; gap: 2px; }
.row-thumb-wrap .spinner { width: 12px; height: 12px; }

.row-title {
  font-weight: 600;
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-tags {
  max-width: 220px;
  white-space: normal;
}

.row-tags .tags-row { flex-wrap: nowrap; overflow: hidden; }

/* ---------- Modal ---------- */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--modal-scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 20px;
}

.modal-overlay[hidden] { display: none; }

/* Always a near-black scrim regardless of theme — the point is to isolate
   the image, not to match the page chrome. */
.lightbox-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 200;
  padding: 24px;
  cursor: zoom-out;
}

.lightbox-overlay[hidden] { display: none; }

.lightbox-content { max-width: 100%; max-height: 100%; cursor: default; }

.lightbox-content img,
.lightbox-content video {
  display: block;
  max-width: 96vw;
  max-height: 96vh;
  object-fit: contain;
}

.lightbox-close {
  position: fixed;
  top: 16px;
  right: 20px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.lightbox-close:hover { background: rgba(255, 255, 255, 0.22); }

.modal {
  background: var(--surface);
  border-radius: 8px;
  width: 100%;
  max-width: 420px;
  padding: 28px;
  box-shadow: 0 20px 60px rgba(0,0,0,0.2);
}

.modal h2 { margin: 0 0 4px; font-size: 19px; }
.modal .sub { color: var(--text-dim); font-size: 13.5px; margin: 0 0 20px; }

.field { margin-bottom: 14px; }
.field label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; }

.field input[type="text"],
.field input[type="password"],
.field input[type="number"],
.field input[type="file"],
.field textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 5px;
  font-size: 14px;
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
}

.field textarea { resize: vertical; min-height: 60px; }

.field input:focus,
.field textarea:focus { outline: 2px solid var(--text); outline-offset: 1px; }

/* Shot Length is computed (Out Frame − In Frame), never typed directly. */
.cpt-computed-field { color: var(--text-dim); background: var(--subtle-bg); cursor: default; }
.cpt-computed-field:focus { outline: none !important; }

.field-hint { font-size: 12px; color: var(--text-dim); margin: 6px 0 0; }

.modal-actions {
  display: flex;
  gap: 10px;
  margin-top: 22px;
}

.modal-actions .btn { flex: 1; text-align: center; }

.switch-line {
  margin-top: 16px;
  font-size: 13px;
  color: var(--text-dim);
  text-align: center;
}

.switch-line button {
  background: none;
  border: none;
  color: var(--text);
  font-weight: 600;
  text-decoration: underline;
  padding: 0;
  font-size: 13px;
}

.form-error {
  background: var(--danger-bg);
  color: var(--danger-text);
  padding: 9px 12px;
  border-radius: 5px;
  font-size: 13px;
  margin-bottom: 14px;
}

.form-error[hidden] { display: none; }

/* ---------- Project detail ---------- */

.detail-wrap { padding: 28px 0 90px; }

/* Widened to match .asset-media/.shot-video-wrap's own cap on the black
   band above (see .asset-hero-band) — the asset toolbar now carries the
   version-management buttons too (add version/add files/delete version,
   moved down to sit with the rest of the file controls), so it needs the
   same extra room the preview itself already has, rather than being
   capped narrower than the very thing it sits under. */
.container.detail-wrap { max-width: 1600px; }

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--text-dim);
  text-decoration: none;
  font-size: 13.5px;
  margin-bottom: 18px;
}

.back-link:hover { color: var(--text); }

.detail-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 20px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.detail-title { font-size: 24px; font-weight: 700; margin: 0; letter-spacing: -0.01em; }

.title-view {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.title-view[hidden] { display: none; }

.title-edit { max-width: 480px; margin-bottom: 8px; }
.title-edit input[type="text"] { font-size: 18px; font-weight: 600; }

.detail-owner { margin-bottom: 10px; }

.detail-owner-link {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-dim);
  font-size: 13.5px;
  text-decoration: none;
}

.detail-owner-link:hover { color: var(--text); }

.detail-tags-block { margin-bottom: 10px; }
.detail-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; flex: 1; min-height: 26px; }

/* Now a standalone row right below the hero (project.html) rather than a
   flex child of .detail-header, since "+ Add version" isn't one of the
   fields overlaid on the cover — top margin echoes the space CPT's own
   .cpt-hero-full leaves before its own next section. */
.detail-actions { display: flex; gap: 10px; flex-shrink: 0; margin: 20px 0 12px; }

/* ---------- Description / JIRA info blocks ---------- */

.info-block { margin-bottom: 18px; }

.info-view {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.info-text {
  flex: 1;
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  white-space: pre-wrap;
  word-break: break-word;
}

.info-text.placeholder { color: var(--text-dim); font-style: italic; }

.info-text ul, .info-text ol { margin: 4px 0; padding-left: 20px; }
.info-text li { margin: 2px 0; }
.info-text div { margin: 0; }

/* .info-edit-btn is also a .btn.ghost — this just keeps its layout in check
   inside the flex row next to the description text, without fighting the
   button's own chrome (border/background/padding come from .btn.ghost). */
.info-edit-btn {
  flex-shrink: 0;
  white-space: nowrap;
  font-size: 12.5px;
  padding: 6px 12px;
}

.jira-edit input[type="text"],
.title-edit input[type="text"] {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 5px;
  font-size: 14px;
  font-family: inherit;
  background: var(--surface);
  color: var(--text);
  resize: vertical;
}

.jira-edit input:focus,
.title-edit input:focus { outline: 2px solid var(--text); outline-offset: 1px; }

.info-edit-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 8px;
}

/* Generic styling for whatever control an .info-edit box holds (CPT's
   description textarea, cover file input) — .title-edit above already
   covers the plain-text-input case for Gallery's own use, and a <select>
   in here just keeps its normal .select styling (a `background` shorthand
   here would blow away .select's custom dropdown-arrow background-image). */
.info-edit textarea,
.info-edit input[type="text"],
.info-edit input[type="file"] {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 5px;
  font-size: 14px;
  font-family: inherit;
  background: var(--surface);
  color: var(--text);
}

.info-edit textarea { resize: vertical; min-height: 70px; }

/* ---------- Minimal rich-text editor (bold / italic / bulleted list) ---------- */

.rich-editor {
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--surface);
  overflow: hidden;
}

.rich-toolbar {
  display: flex;
  gap: 2px;
  padding: 6px;
  border-bottom: 1px solid var(--border);
  background: var(--subtle-bg);
}

.rich-btn {
  width: 28px;
  height: 28px;
  border: 1px solid transparent;
  background: transparent;
  border-radius: 4px;
  color: var(--text);
  font-size: 13px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}

.rich-btn:hover { background: var(--surface); border-color: var(--border); }

.rich-content {
  min-height: 70px;
  max-height: 260px;
  overflow-y: auto;
  padding: 10px 12px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
}

.rich-content:focus { outline: none; }

.rich-content:empty::before {
  content: attr(data-placeholder);
  color: var(--text-dim);
  font-style: italic;
}

.rich-content ul, .rich-content ol { margin: 4px 0; padding-left: 20px; }
.rich-content li { margin: 2px 0; }

/* ---------- Tag editor (project creation + project-page tag editing) ---------- */

.tag-editor {
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--surface);
  padding: 10px;
}

.tag-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.tag-chip-editable { padding-right: 6px; }

.tag-chip-remove {
  border: none;
  background: none;
  color: inherit;
  font-size: 13px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  opacity: 0.7;
}

.tag-chip-remove:hover { opacity: 1; }

.tag-input-row { position: relative; }

.tag-input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 5px;
  font-size: 13.5px;
  font-family: inherit;
  background: var(--surface);
  color: var(--text);
}

.tag-input:focus { outline: 2px solid var(--text); outline-offset: 1px; }

.tag-suggestions {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 5px;
  box-shadow: var(--shadow);
  padding: 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  z-index: 5;
}

.tag-suggestions[hidden] { display: none; }

.tag-suggestion,
.tag-browse-item {
  font-size: 11px;
  font-weight: 600;
  color: var(--tag-color, var(--text-dim));
  background: color-mix(in srgb, var(--tag-color, var(--text-dim)) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--tag-color, var(--text-dim)) 35%, transparent);
  padding: 3px 10px;
  border-radius: 20px;
  cursor: pointer;
}

.tag-suggestion:hover,
.tag-browse-item:hover { opacity: 0.8; }

.tag-browse-toggle {
  margin-top: 8px;
  font-size: 12.5px;
  padding: 6px 12px;
}

.tag-browse-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}

.tag-browse-list[hidden] { display: none; }

.tag-browse-empty { font-size: 12.5px; color: var(--text-dim); font-style: italic; }

.iteration-desc-block { margin: 16px 0 0; }

/* Framed as a distinct card (a subtle-bg/border "note" box) so this
   iteration's own notes stand out from the plain-text project description
   above it, rather than reading as more of the same paragraph flow —
   still shows the card even when empty (placeholder text), so the section
   has visible presence either way. Only the view state; the rich-text
   editor already has its own visual boundary while editing. */
.iteration-desc-block .info-view {
  background: var(--subtle-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
}

.jira-block { margin-bottom: 18px; }

.jira-view {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.jira-view[hidden] { display: none; }

.jira-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: var(--subtle-bg);
  border: 1px solid var(--border);
  color: var(--text);
  text-decoration: none;
  font-size: 13px;
  font-weight: 500;
  padding: 6px 12px;
  border-radius: 20px;
  max-width: 100%;
  overflow: hidden;
}

.jira-chip[hidden] { display: none; }

.jira-chip span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jira-chip:hover { border-color: var(--hover-border); background: var(--hover-border); }
.jira-icon { color: #0052cc; font-size: 12px; flex-shrink: 0; }

.jira-edit { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.jira-edit[hidden] { display: none; }
.jira-edit input { flex: 1; min-width: 220px; }
.jira-edit .info-edit-actions { margin-top: 0; }

.status-selector {
  display: flex;
  gap: 8px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}

.status-pill {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-dim);
  padding: 7px 14px;
  border-radius: 20px;
  font-size: 12.5px;
  font-weight: 600;
  transition: all 0.12s ease;
}

.status-pill:hover { border-color: var(--status-color); color: var(--status-color); }

.status-pill.active {
  background: var(--status-bg);
  border-color: var(--status-color);
  color: var(--status-color);
}

/* ---------- Iteration file viewer ---------- */

.asset-counter {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dim);
}

.asset-toggle-all {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  color: var(--text-dim);
  cursor: pointer;
  margin-left: auto;
}

/* No margin of its own — spacing between blocks in "Show all" mode comes
   entirely from .asset-divider's own black padding now (see below), so
   there's no leftover sliver of the page's own light/dark background
   breaking up an otherwise continuous black preview area. */
.asset-block { margin-bottom: 0; }

/* Three-part "holy grail" row: left and right sections get equal flex-grow so
   whatever sits in the center (Previous/Next) stays visually centered in the
   row regardless of how much content sits on either side of it. */
.asset-mkt-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.asset-mkt-row .btn { padding: 7px 14px; font-size: 12.5px; }

/* With a video player above it, a subtle rule separates the player's own
   transport controls from the file controls (MKT ready/Select/...). */
.asset-block.has-video .asset-mkt-row {
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  padding-top: 14px;
}

/* Same smaller sizing as .asset-mkt-row's own buttons, so the download/
   remove/version-management row (often 6+ buttons at once) can actually
   fit on one line instead of wrapping. */
.asset-toolbar .btn { padding: 7px 14px; font-size: 12.5px; }

.asset-mkt-row-left,
.asset-mkt-row-right {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  flex-wrap: wrap;
}

.asset-mkt-row-left { justify-content: flex-start; }
.asset-mkt-row-right { justify-content: flex-end; }

.asset-mkt-row-center {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}

/* Full-bleed black band the previewed asset sits on — same reasoning and
   visual language as the CPT shot page's .cpt-shot-hero-band (video reads
   as the dominant thing on the page regardless of theme). Uses the
   width:100vw/negative-margin technique instead of restructuring
   project.html's single .container the way the CPT shot page's markup is
   split in two — works at any nesting depth as long as no ancestor clips
   overflow (none here do), so the rest of the page's structure doesn't
   need to change. Generous top/bottom padding (bleed) rather than the
   24px it used to be — the file controls below now live inside this same
   band (see .asset-hero-stack), so it needs real room on both ends of the
   whole stack, not just around the media. */
.asset-hero-band {
  background: #000;
  width: 100vw;
  margin-left: calc(-50vw + 50%);
  display: flex;
  justify-content: center;
  padding: 40px 0;
}

/* The actual width-capped column: media, then the file-nav/select row,
   then downloads/remove — all three stacked together on the black band so
   every asset/version control reads as integrated with the preview
   instead of living in the page's own background below it. Cross-axis
   (width) stretch is the flexbox default here (align-items unset), which
   is what lets .asset-media/.shot-video-wrap size themselves against a
   real, explicit width instead of collapsing — see the note on
   .shot-video-wrap for why that matters. position:relative anchors the
   MKT-ready corner badge to the actual media's own top-left, not the
   full-bleed band's (which would land it in the top padding once the
   band grew taller to fit the controls too). */
.asset-hero-stack {
  position: relative;
  width: 100%;
  max-width: 1600px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.asset-hero-band .asset-media {
  background: transparent;
  border: none;
}

/* Processing/failed status text and the "pick two different files" note
   default to a mid grey tuned for a light placeholder box — needs a
   lighter override to stay legible on this band's solid black. */
.asset-hero-band .thumb-status { color: rgba(255, 255, 255, 0.7); }
.asset-hero-band .thumb-status.failed { color: #f2867c; }

/* File nav/select row and the download/remove row now live on the black
   band too — their text and checkbox-label rows default to
   var(--text-dim)-style theme colors tuned for the page's own light/dark
   surface, which would read as low-contrast (or dark-on-dark in dark
   theme) against this always-dark band. Buttons get the same translucent
   "glass" look already established for the CPT hero's own edit buttons
   (.cpt-hero-btn) rather than their normal solid/outlined theme styling;
   danger actions (Remove/Delete) keep a distinct reddish tint so they
   still read as destructive. */
.asset-hero-band .asset-counter,
.asset-hero-band .asset-toggle-all,
.asset-hero-band .asset-select {
  color: rgba(255, 255, 255, 0.85);
}

.asset-hero-band .mkt-ready-toggle {
  border-color: rgba(255, 255, 255, 0.4);
  color: rgba(255, 255, 255, 0.85);
}

.asset-hero-band .mkt-ready-toggle.checked {
  border-color: #4caf6f;
  color: #8fe3ab;
  background: rgba(76, 175, 111, 0.18);
}

.asset-hero-band .btn.primary,
.asset-hero-band .btn.ghost {
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.4);
  color: #fff;
}

.asset-hero-band .btn.primary:hover,
.asset-hero-band .btn.ghost:hover {
  background: rgba(255, 255, 255, 0.26);
}

.asset-hero-band .btn.danger {
  background: rgba(242, 134, 124, 0.14);
  border: 1px solid rgba(242, 134, 124, 0.55);
  color: #f2867c;
}

.asset-hero-band .btn.danger:hover { background: rgba(242, 134, 124, 0.24); }

.asset-hero-band .btn:disabled { opacity: 0.4; }

.asset-media {
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 200px;
  max-height: 74vh;
  /* Same cap as .shot-video-wrap (the video player's own wrapper on this
     same black band) — without it, a wide (e.g. 21:9) image has nothing
     capping its width and stretches to nearly fill the full-bleed band
     edge to edge, unlike a video's own consistent letterboxed margins. */
  max-width: 1600px;
}

.asset-media img, .asset-media video {
  max-width: 100%;
  max-height: 74vh;
  display: block;
}

.asset-toolbar {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.asset-toolbar-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.asset-toolbar-split { justify-content: space-between; }

.asset-toolbar-group {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* Three-part "holy grail" row — left/right groups get equal flex-grow so
   whatever sits in the center (Add version/Add Files) stays visually
   centered regardless of how much content sits on either side, same
   technique as .asset-mkt-row's own nav row (see .asset-mkt-row-center).
   Left = downloads, center = add version/add files, right = every
   remove/delete action — grouped by "how destructive/constructive this
   is", not just "what row did this used to be in". */
.asset-toolbar-group-left,
.asset-toolbar-group-right { flex: 1; }
.asset-toolbar-group-left { justify-content: flex-start; }
.asset-toolbar-group-right { justify-content: flex-end; }
.asset-toolbar-group-center { flex: 0 0 auto; }

.asset-select {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text-dim);
  cursor: pointer;
  user-select: none;
}

.asset-select input[type="checkbox"] { width: 15px; height: 15px; margin: 0; cursor: pointer; }

/* Black (not the page's own background) with an off-white line, matching
   the black bands it separates — a themed border on the page's own light
   background read as a dark line on white, breaking up the otherwise
   continuous black preview area between files in "Show all" mode. */
.asset-divider {
  border: none;
  border-top: 1px solid rgba(255, 255, 255, 0.3);
  background: #000;
  padding: 24px 0;
  /* Full-bleed like .asset-hero-band itself, or the black spacer between
     two blocks stops at the page's content column and leaves white gutters
     either side. */
  box-sizing: border-box;
  width: 100vw;
  margin: 0 0 0 calc(-50vw + 50%);
}

/* Second navigation layer alongside Previous/Next: jump straight to any file
   in the current iteration instead of stepping through one at a time. */
.asset-thumb-strip {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding-bottom: 6px;
  margin-bottom: 18px;
}

.asset-thumb-strip[hidden] { display: none; }

.asset-thumb-item {
  flex-shrink: 0;
  width: 84px;
  border: 2px solid transparent;
  overflow: hidden;
  padding: 0;
  background: var(--surface);
  cursor: pointer;
}

.asset-thumb-item .thumb-wrap {
  position: relative;
  background: var(--placeholder-bg);
  aspect-ratio: 1 / 1;
}

.asset-thumb-item img.thumb { width: 100%; height: 100%; object-fit: cover; display: block; }
.asset-thumb-item.active { border-color: var(--text); }
.asset-thumb-item:hover:not(.active) { border-color: var(--hover-border); }
.asset-thumb-item.dragging { opacity: 0.4; }
.asset-thumb-item.drag-over { border-color: #2f7a45; }

.mkt-ready-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px;
  border: 1px solid var(--border);
  border-radius: 20px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-dim);
  cursor: pointer;
  user-select: none;
}

.mkt-ready-toggle input[type="checkbox"] {
  width: 15px;
  height: 15px;
  margin: 0;
  accent-color: #2f7a45;
  cursor: pointer;
}

.mkt-ready-icon {
  display: none;
  color: #2f7a45;
  font-weight: 700;
}

.mkt-ready-toggle.checked {
  border-color: #2f7a45;
  color: #2f7a45;
  background: var(--subtle-bg);
}

.mkt-ready-toggle.checked .mkt-ready-icon { display: inline; }

.version-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 14px;
  flex-wrap: wrap;
  gap: 10px;
}

.version-tag {
  font-size: 13px;
  color: var(--text-dim);
}

.version-tag strong { color: var(--text); }

.reaction-bar { display: flex; flex-wrap: wrap; gap: 8px; }

.reaction-btn {
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 20px;
  padding: 6px 12px;
  font-size: 14px;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: all 0.12s ease;
}

.reaction-btn:hover { border-color: var(--hover-border); background: var(--subtle-bg); }

.reaction-btn.active {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
}

.reaction-btn .count { font-size: 12px; font-weight: 600; }

.reaction-picker {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
}

.reaction-picker button {
  border: 1px solid transparent;
  background: transparent;
  font-size: 17px;
  padding: 5px 7px;
  border-radius: 5px;
}

.reaction-picker button:hover { background: var(--subtle-bg); border-color: var(--border); }

.section-label {
  font-size: 12.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  margin: 34px 0 14px;
}

.version-strip {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  padding-bottom: 8px;
  /* Previously had no deliberate gap at all before the JIRA row right
     below it (Compare Versions now lives there too) — just the strip's
     own scrollbar-clearance padding, which read as cramped. */
  margin-bottom: 24px;
}

.version-item {
  flex-shrink: 0;
  width: 140px;
  border: 2px solid transparent;
  overflow: hidden;
  background: var(--surface);
  text-align: left;
}

.version-item.active { border-color: var(--text); }

.version-item .thumb-wrap { position: relative; background: var(--placeholder-bg); aspect-ratio: 1 / 1; }
.version-item img { width: 100%; height: 100%; object-fit: cover; display: block; }

.asset-count-badge {
  position: absolute;
  top: 6px;
  right: 6px;
  font-size: 9.5px;
  padding: 2px 6px;
}

.version-item .v-label {
  padding: 7px 9px;
  font-size: 12px;
  color: var(--text-dim);
}

.version-item .v-label strong { color: var(--text); }

.upload-progress {
  font-size: 13px;
  color: var(--text-dim);
  margin-top: 10px;
}

/* ---------- Page footer / activity log ---------- */

.page-footer {
  margin-top: 40px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: center;
}

.activity-modal { max-width: 480px; }

.add-version-modal { max-width: 560px; text-align: left; }
.add-version-modal .rich-editor { margin-top: 4px; }

.version-file-thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 14px 0;
}

.version-file-thumbs[hidden] { display: none; }

.pending-thumb {
  position: relative;
  width: 84px;
  height: 84px;
  overflow: hidden;
  border: 2px solid transparent;
  background: var(--placeholder-bg);
  cursor: grab;
}

.pending-thumb.dragging { opacity: 0.4; }
.pending-thumb.drag-over { border-color: #2f7a45; }

.pending-thumb-media { width: 100%; height: 100%; }
.pending-thumb-media img, .pending-thumb-media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.pending-thumb-remove {
  position: absolute;
  top: 3px;
  right: 3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: none;
  background: var(--overlay-scrim);
  color: white;
  font-size: 12px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
}

/* ---------- Version comparison ---------- */

.version-history-header {
  display: flex;
  align-items: center;
  /* flex-start (not space-between) so Compare Versions sits right next to
     the "Version history" label it belongs to, instead of drifting off to
     the far right edge of the now much wider (1600px) page — the whole
     point of moving it here was for it to read as clearly attached to
     this section. */
  justify-content: flex-start;
  gap: 10px;
}

.version-history-header .section-label { margin: 34px 0 14px; }

/* Fills as much of the screen as it reasonably can (bounded by viewport, not
   a fixed pixel size) — the picker/mode-switcher rows keep their natural
   height and the image area (.compare-view) flexes to take everything
   that's left, so a bigger window means a bigger, more inspectable image. */
.compare-modal {
  /* .modal-overlay centers this with align-items/justify-content, so a
     height of 92vh alone leaves a 4vh margin top and bottom. Sizing the
     width as 100vw minus twice that same 4vh keeps the left/right margin
     visually equal to the top/bottom one instead of an arbitrary vw cap. */
  width: calc(100vw - 8vh);
  max-width: none;
  height: 92vh;
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  text-align: left;
  overflow: hidden;
}

.compare-modal h2, .compare-modal > .sub, .compare-modal .modal-actions { flex: 0 0 auto; }

.compare-pickers {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  margin: 4px 0 16px;
  flex: 0 0 auto;
}

.compare-picker { flex: 1; min-width: 240px; }
.compare-picker label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; }

.compare-asset-picker {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 8px;
}

.compare-asset-picker[hidden] { display: none; }

.compare-asset-thumb {
  flex-shrink: 0;
  width: 48px;
  height: 48px;
  border: 2px solid transparent;
  overflow: hidden;
  padding: 0;
  background: var(--surface);
  cursor: pointer;
}

.compare-asset-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.compare-asset-thumb.active { border-color: var(--text); }
.compare-asset-thumb:hover:not(.active) { border-color: var(--hover-border); }

/* "Change Cover" modal (project.html): every iteration's files at once,
   newest first, reusing .compare-asset-thumb's own square-thumbnail
   button rather than inventing a second thumbnail style. */
.cover-picker-modal { width: 560px; }

.cover-picker-list {
  max-height: 60vh;
  overflow-y: auto;
  margin: 4px 0 0;
}

.cover-picker-group { margin-bottom: 16px; }
.cover-picker-group:last-child { margin-bottom: 0; }

.cover-picker-version-label {
  margin: 0 0 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-dim);
}

.cover-picker-thumbs { display: flex; gap: 6px; flex-wrap: wrap; }

.compare-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 18px;
  flex: 0 0 auto;
}

.compare-note {
  color: var(--text-dim);
  font-size: 13.5px;
  font-style: italic;
  text-align: center;
  padding: 40px 20px;
}

.compare-caption {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-dim);
  text-align: center;
  flex: 0 0 auto;
}

/* Caption + its speaker toggle, centered together as one unit — see
   .compare-audio-btn below. Shared by both apps' compare modals (CPT shot
   page and the Gallery project page). */
.compare-pane-header,
.compare-slider-caption-group {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

/* Smaller than the transport's other .cpt-video-icon-btn buttons — sitting
   inline with a caption, not in the main button row, it doesn't need the
   same footprint. */
.compare-audio-btn { width: 22px; height: 22px; font-size: 11px; }

/* The view area fills whatever height is left in the modal after the fixed
   rows above it, so the image(s) get as much room as the window allows. */
.compare-view {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.compare-pane {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.compare-media {
  flex: 1;
  min-height: 0;
  background: var(--placeholder-bg);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.compare-media img, .compare-media video {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
}

.compare-pair-side { display: flex; gap: 16px; flex: 1; min-height: 0; }
.compare-pair-stack { display: flex; flex-direction: column; gap: 16px; flex: 1; min-height: 0; }

.compare-slider-captions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 8px;
  flex: 0 0 auto;
}

/* Fills whatever space .compare-view has left — both images inside are
   object-fit: contain within this exact box, so the clip-path comparison
   still lines up regardless of the box's resulting shape. */
.compare-slider-wrap {
  position: relative;
  width: 100%;
  flex: 1;
  min-height: 0;
  background: var(--placeholder-bg);
  border: 1px solid var(--border);
  overflow: hidden;
  cursor: ew-resize;
}

.compare-slider-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

.compare-slider-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: #fff;
  transform: translateX(-50%);
  pointer-events: none;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);
}

.compare-slider-handle::after {
  content: '⇔';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: #fff;
  color: #17150f;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}

.compare-slider-range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: ew-resize;
}

/* Shared by both apps' compare modals (CPT shot page and the Gallery
   project page): the video area + its transport bar sit on a black
   "stage" (matching each page's own black hero band), so the white glass
   icon buttons/track from .cpt-video-controls-bar/.cpt-video-toolbar read
   correctly regardless of the page's light/dark theme, exactly as they do
   under the main player. Square corners, and bled out to the modal's own
   edges (negative margin cancelling .modal's 28px padding, restored as
   this element's own padding) — same edge-to-edge, no-rounded-corners
   treatment as the hero bands, just relative to the popup's box instead
   of the viewport. */
.cpt-compare-stage {
  background: #000;
  border-radius: 0;
  margin: 0 -28px;
  padding: 16px 28px;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.cpt-compare-stage .compare-view { flex: 1 1 auto; min-height: 0; }

.cpt-compare-stage .compare-media,
.cpt-compare-stage .compare-slider-wrap {
  background: #000;
  border-color: rgba(255, 255, 255, 0.14);
}

.cpt-compare-stage .compare-caption { color: rgba(255, 255, 255, 0.7); }
.cpt-compare-stage .compare-note { color: rgba(255, 255, 255, 0.5); }

.activity-list {
  max-height: 50vh;
  overflow-y: auto;
  margin: 4px 0 0;
}

.activity-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.activity-item:last-child { border-bottom: none; }

.activity-line {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--text);
}

.activity-time {
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--text-dim);
}

.activity-loading {
  color: var(--text-dim);
  font-size: 13.5px;
  text-align: center;
  padding: 20px 0;
}

@media (max-width: 640px) {
  header.top .container { height: 60px; }
  .detail-title { font-size: 20px; }
  .container { padding: 0 18px; }
}
