/*
 * Datamori palette: #c8c9c9 light grey, #7e7e7e mid grey, #3a3839 dark
 * grey, #1a1a1a near black, #a01527 crimson. Crimson only works as a fill;
 * the logo's coral is the accent for text and borders.
 */
:root {
  --bg: #141314;
  --panel: #1c1b1c;
  --panel-2: #262425;
  --line: #3a3839;
  --line-hi: #4d4a4b;

  --text: #e9e9e9;
  --text-2: #c8c9c9;
  --muted: #8b8b8b;

  --accent: #a01527;
  --accent-hi: #e0505f;
  --accent-wash: rgba(224, 80, 95, 0.18);

  --green: #2ea043;
  --blue: #2f81f7;
  --amber: #d29922;
  --red: #da3633;
  /* --red fills; --red-hi is for text and borders. */
  --red-hi: #ff8f8c;
  --radius: 10px;

  /* Native controls — select menus, scrollbars, checkboxes — follow these. */
  color-scheme: dark;
  accent-color: var(--accent);

  /* Three shared thumbnail track widths. */
  --tile-sm-base: 140px;
  --tile-md-base: 180px;
  --tile-lg-base: 240px;

  /* The per-page size control moves this. Bases still shrink on narrow screens. */
  --tile-mult: 1;

  --tile-sm: calc(var(--tile-sm-base) * var(--tile-mult));
  --tile-md: calc(var(--tile-md-base) * var(--tile-mult));
  --tile-lg: calc(var(--tile-lg-base) * var(--tile-mult));
}

@media (max-width: 900px) {
  :root { --tile-sm-base: 118px; --tile-md-base: 150px; --tile-lg-base: 190px; }
}
@media (max-width: 640px) {
  :root { --tile-sm-base: 104px; --tile-md-base: 132px; --tile-lg-base: 150px; }
}

/* ------------------------------------------------------ thumbnail size pick */

.sizepick { display: flex; align-items: center; gap: 3px; margin-left: auto; align-self: center; }
.sizepick button {
  min-width: 27px;
  padding: 4px 7px;
  border-radius: 7px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--muted);
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
}
.sizepick button:hover { border-color: var(--accent-hi); color: var(--text); }
.sizepick button[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
/* The toolbar already pushes its own trailing controls right. */
.toolbar .spacer + .sizepick { margin-left: 0; }

/* One coral focus ring on operable elements (a bare :focus-visible rings <dialog> too). */
:is(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent-hi);
  outline-offset: 1px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
}

a { color: inherit; text-decoration: none; }
.muted { color: var(--muted); font-weight: 400; }

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

.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 18px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}

.brand { display: flex; align-items: center; flex: none; }

/*
 * Black-on-white logos on a dark bar: invert, then `lighten` drops the
 * paper; hue-rotate brings the coral dots back. The window crops the margins.
 */
.brandmark {
  position: relative;
  width: 132px;
  height: 18px;
  overflow: hidden;
  flex: none;
}
.brandmark img {
  position: absolute;
  width: 162px;
  left: -18px;
  top: -71px;
  filter: invert(1) hue-rotate(180deg);
  mix-blend-mode: lighten;
}

/* The name and Feed stacked in one outline. 1px gap. */
.brandblock { display: flex; align-items: center; gap: 11px; flex: none; }

.brandpill {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 3px 10px 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel-2);
}
/* The pill lights as one: border and both lines. */
.brandpill:hover { border-color: var(--accent-hi); }
.brandpill:hover .brandtag,
.brandpill:hover .feedlink { color: var(--accent-hi); }

/* Pull back the tracking's trailing space so TPDBARR centres. */
.brandtag {
  font-size: 11.5px;
  letter-spacing: 0.18em;
  margin-right: -0.18em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--muted);
}

.feedlink {
  font-size: 12px;
  line-height: 1.1;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.search { flex: 1; max-width: 620px; }
.search input {
  width: 100%;
  padding: 8px 12px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--bg);
  color: inherit;
  font: inherit;
}
.search input:focus { outline: 2px solid var(--accent-hi); outline-offset: -1px; }

.topnav { margin-left: auto; display: flex; gap: 6px; }
.topnav a, .topnav button {
  padding: 7px 12px;
  border-radius: var(--radius);
  border: 1px solid transparent;
  background: none;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
}
/* Coral border on hover, like everything else. */
.topnav a:hover, .topnav button:hover {
  background: var(--panel-2);
  border-color: var(--accent-hi);
  color: var(--text);
}

/* The current section: coral on word and border, across all its pages. */
.topnav a.on {
  color: var(--accent-hi);
  border-color: var(--accent-hi);
}
.topnav a.on:hover { background: var(--panel-2); color: var(--accent-hi); }

/* Manage: a click-open menu holding Stats, Find, Catalogue and Settings. */
.navmenu { position: relative; }
.navmenu > summary {
  list-style: none;
  padding: 7px 12px;
  border-radius: var(--radius);
  border: 1px solid transparent;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}
.navmenu > summary::-webkit-details-marker { display: none; }
.navmenu > summary::after { content: ' ▾'; font-size: 0.8em; }
.navmenu > summary:hover, .navmenu[open] > summary {
  background: var(--panel-2);
  border-color: var(--accent-hi);
  color: var(--text);
}
.navmenu.on > summary { color: var(--accent-hi); border-color: var(--accent-hi); }
.navmenulist {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  z-index: 50;
  display: grid;
  gap: 2px;
  min-width: 160px;
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--line-hi);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}
.topnav .navmenulist a { display: block; white-space: nowrap; }

.statusbar {
  display: flex;
  gap: 14px;
  padding: 0 18px;
  font-size: 12.5px;
  color: var(--muted);
  min-height: 26px;
  align-items: center;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
}
.statusbar .bad { color: var(--red); }
.statusbar .good::before { content: "● "; color: var(--green); }

/* ------------------------------------------------------------------ views */

main { max-width: 1180px; margin: 0 auto; padding: 22px 18px 60px; }
.empty { padding: 60px 0; text-align: center; color: var(--muted); }

.sitelist { display: grid; gap: 10px; }
.siterow {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
}
.siterow:hover { border-color: var(--accent-hi); }
.siterow img { width: 54px; height: 54px; object-fit: cover; border-radius: 8px; background: var(--panel-2); }
.siterow h3 { margin: 0; font-size: 15px; }
.siterow p { margin: 2px 0 0; font-size: 13px; color: var(--muted); }

/* site header */

.sitehead { display: flex; gap: 18px; margin-bottom: 18px; align-items: flex-start; }
.sitehead img { width: 108px; border-radius: var(--radius); background: var(--panel-2); }
.sitehead h1 { margin: 0 0 4px; font-size: 24px; letter-spacing: -0.02em; }
.sitehead p { margin: 0 0 10px; color: var(--muted); max-width: 70ch; }

.counts { display: flex; gap: 8px; flex-wrap: wrap; }
.count {
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--panel-2);
  font-size: 12.5px;
  color: var(--muted);
}
.count b { color: var(--text); font-weight: 600; }

/* toolbar */

.toolbar {
  position: sticky;
  top: 52px;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 0;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  margin-bottom: 6px;
}
.chip {
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.chip[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
.toolbar .spacer { margin-left: auto; }

button.primary {
  padding: 7px 14px;
  border-radius: var(--radius);
  border: 0;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
button.primary:disabled { opacity: 0.45; cursor: default; }

/* scene rows */

.scenes { display: grid; gap: 6px; }
.scene {
  display: grid;
  grid-template-columns: 26px 84px 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.scene:hover { border-color: var(--line-hi); }
/* Scene stills are landscape on TPDB; movie art is a portrait poster. */
.thumb {
  width: 84px;
  height: 47px;
  border-radius: 6px;
  overflow: hidden;
  background: var(--panel-2);
}
.thumb.portrait { width: 42px; height: 60px; }
.thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  animation: fade 0.25s ease-in;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.artnote { font-size: 12.5px; color: var(--muted); margin-right: 10px; }
.scene .title { font-weight: 600; }
/*
 * The meta line under a title. The dot goes before every item but the
 * first (items can be links); `pointer-events: none` and a set colour keep
 * it from being part of the link.
 */
.scene .meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  font-size: 12.5px;
  color: var(--muted);
  margin-top: 2px;
}

.scene .meta > * + *::before {
  content: "·";
  margin: 0 7px;
  color: var(--muted);
  pointer-events: none;
}

/*
 * ------------------------------------------------- rows in a list of scenes
 *
 * The row still is as tall as the row's three lines of text (62px) at M,
 * 16:9 wide, scaled by the page's S/M/L/XL. Rows whose title wraps keep the
 * still centred.
 */
.scenes {
  --rowtext: 62px;
  --rowthumb-h: calc(var(--rowtext) * var(--tile-mult));
  --rowthumb: calc(var(--rowthumb-h) * 16 / 9);
}

.scenes .scene { grid-template-columns: 26px var(--rowthumb) 1fr auto; }

.scenes .scene .thumb {
  width: var(--rowthumb);
  height: var(--rowthumb-h);
}

/* Movie art is a poster. Same height as everything else on the row, its own
   shape across. */
.scenes .scene .thumb.portrait {
  width: calc(var(--rowthumb-h) * 0.7);
  height: var(--rowthumb-h);
}

/* A one-line blurb is wider than its column would like. Without this the 1fr
   track grows to fit the whole sentence and pushes the buttons off the row. */
.scene .scenebody { min-width: 0; }

/* What it is about. One line, because a list is read by running your eye down
   it — the whole of it is on the hover title. */
.scene .rowdesc {
  margin-top: 3px;
  font-size: 12px;
  line-height: 1.35;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.scene .right { display: flex; align-items: center; gap: 8px; }

.badge {
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.badge.absent { background: var(--panel-2); color: var(--muted); }
.badge.monitored { background: rgba(47, 129, 247, 0.18); color: #79b8ff; }
.badge.downloaded { background: rgba(46, 160, 67, 0.18); color: #7ee787; }
.badge.busy { background: var(--panel-2); color: var(--muted); }
.badge.failed { background: rgba(218, 54, 51, 0.18); color: var(--red-hi); }
.badge.stash-exact { background: rgba(46, 160, 67, 0.18); color: #7ee787; }
.badge.stash-probable { background: rgba(210, 153, 34, 0.18); color: #e3b341; }
.badge.upcoming { background: var(--accent-wash); color: var(--accent-hi); }

.scene button.add {
  padding: 5px 12px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.scene button.add:hover { border-color: var(--accent-hi); }
.scene button.add:disabled { opacity: 0.4; cursor: default; }

/* home feeds */

.feed { margin-bottom: 34px; }
.feedhead {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}
.feedhead h2 { margin: 0; font-size: 17px; letter-spacing: -0.01em; }
.feedhead .muted { font-size: 12.5px; }

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tile-lg), 1fr));
  gap: 14px;
  /* Movie art is portrait, so a mixed row must not stretch the scene cards. */
  align-items: start;
}
.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.12s ease, transform 0.12s ease;
}
.card:hover { border-color: var(--accent-hi); transform: translateY(-2px); }

.cardart {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--panel-2);
}
.cardart.portrait { aspect-ratio: 2 / 3; }
.cardart img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cardmarks {
  position: absolute;
  left: 6px;
  bottom: 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.cardmarks .badge { backdrop-filter: blur(6px); }

.cardbody { padding: 9px 11px 11px; }
.cardbody .title {
  font-weight: 600;
  font-size: 13.5px;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Same line on a card, and the same reason for the same rule. */
.cardbody .meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 4px;
}

.cardbody .meta > * + *::before {
  content: "·";
  margin: 0 6px;
  color: var(--muted);
  pointer-events: none;
}

/* Who is in it, and what it is about. Both clamped: a card is a fixed shape in
   a grid, and one long blurb must not set the height of the whole row. */
.cardcast {
  margin-top: 5px;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.carddesc {
  margin-top: 4px;
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.because { margin-top: 5px; font-size: 11.5px; color: var(--accent-hi); }
.empty.small { padding: 22px 0; text-align: left; font-size: 13px; }

/* queue */

.queue { display: grid; gap: 6px; }
.qrow {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

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

dialog {
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel);
  color: var(--text);
  padding: 0;
  width: min(560px, 94vw);
}
dialog::backdrop { background: rgba(0, 0, 0, 0.65); }
dialog form { padding: 22px 24px 18px; }
dialog h2 { margin: 0 0 14px; font-size: 19px; }

fieldset { border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 14px; margin: 0 0 14px; }
legend { padding: 0 6px; font-weight: 600; font-size: 13px; }

label { display: block; margin-bottom: 10px; font-size: 13px; font-weight: 600; color: var(--muted); }
label input[type="text"], label input[type="password"], label select {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 7px 10px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-weight: 400;
}
label.check { display: flex; align-items: center; gap: 8px; margin: 0; }
label.check input { margin: 0; }
.two { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

.note { white-space: pre-line; font-size: 12.5px; color: var(--muted); min-height: 18px; margin: 0 0 12px; }

menu { display: flex; gap: 8px; justify-content: flex-end; padding: 0; margin: 0; }
menu button {
  padding: 7px 14px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
menu button.primary { border-color: var(--accent); }


/* The catch-up, in Parameters — see catchUpParams() in app.js. */
.catchrow { display: flex; align-items: center; gap: 10px; }

.catchbar {
  height: 4px;
  border-radius: 999px;
  background: var(--panel-2);
  overflow: hidden;
  margin: 8px 0;
}
.catchbar:empty { display: none; }
.catchfill { height: 100%; background: var(--accent-hi); transition: width 0.3s; }
/* -------------------------------------------------- scene and performer */

.detail { display: flex; gap: 22px; align-items: flex-start; margin-bottom: 26px; flex-wrap: wrap; }
.detail > div:last-child { flex: 1; min-width: 300px; }
.detail h1 { margin: 0 0 8px; font-size: 25px; letter-spacing: -0.02em; line-height: 1.2; }
.detail p { margin: 0 0 12px; }

.detailart { width: 420px; max-width: 100%; border-radius: var(--radius); overflow: hidden; background: var(--panel-2); }
.detailart.portrait { width: 240px; }
.detailart img { width: 100%; display: block; }

.detailmeta { display: flex; flex-wrap: wrap; gap: 4px 0; font-size: 13px; color: var(--muted); margin-bottom: 14px; }
.detailmeta > * + *::before { content: " · "; }

.detailstate { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 16px; }
.detailstate .muted { font-size: 12.5px; }
/* The probable-match question, inside the action row, full width. */
.bridgeask {
  flex-basis: 100%;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  background: var(--panel);
  font-size: 13.5px;
}
.bridgeask p { margin: 0 0 8px; }
.bridgehit { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.bridgebuttons { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }

.path {
  font-size: 12px;
  color: var(--muted);
  background: var(--panel-2);
  padding: 3px 8px;
  border-radius: 6px;
  word-break: break-all;
}

.overview { color: var(--muted); max-width: 75ch; line-height: 1.6; }

.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.chips .chip { cursor: default; }
.chips a.chip { cursor: pointer; }
.chips a.chip:hover { border-color: var(--accent-hi); color: var(--text); }
.chip.person { color: var(--text); }
.chip.quiet { font-size: 12px; padding: 4px 10px; }

.facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 4px 16px;
  margin: 0 0 14px;
  font-size: 13px;
  max-width: 460px;
}
.facts dt { color: var(--muted); }
.facts dd { margin: 0; }

.link { color: var(--accent-hi); }
.link:hover { text-decoration: underline; }
.small { font-size: 12.5px; }

/* ------------------------------------------------------------ first run */

.setup { max-width: 60ch; padding: 48px 0; }
/* First run is the one screen with room for the full mark. */
.setupmark {
  display: block;
  width: 260px;
  margin: 0 0 10px -20px;
  filter: invert(1) hue-rotate(180deg);
  mix-blend-mode: lighten;
}
.setup h1 { margin: 0 0 14px; font-size: 24px; letter-spacing: -0.02em; }
.setup ol { margin: 0 0 22px; padding-left: 20px; color: var(--muted); }
.setup li { margin-bottom: 8px; }

/* ------------------------------------------------------------ needs you */

.attention { display: grid; gap: 6px; }
.arow {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 12px;
  padding: 9px 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.arow.clickable { cursor: pointer; }
.arow.clickable:hover { border-color: var(--accent-hi); }
.arow .title { font-weight: 600; font-size: 14px; }
.arow .meta { font-size: 12.5px; color: var(--muted); margin-top: 2px; }

/* ------------------------------------------------------- nearly complete */

.completion { display: grid; gap: 6px; }
.crow {
  display: grid;
  grid-template-columns: 56px 1fr auto;
  align-items: center;
  gap: 14px;
  padding: 10px 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
}
.crow:hover { border-color: var(--accent-hi); }
.crow img, .crow .noposter {
  width: 56px;
  height: 56px;
  object-fit: cover;
  border-radius: 8px;
  background: var(--panel-2);
}
.crow .title { font-weight: 600; }
.crow .meta { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.crow .meta span + span::before { content: " · "; }

.bar {
  margin-top: 7px;
  height: 4px;
  border-radius: 999px;
  background: var(--panel-2);
  overflow: hidden;
  max-width: 340px;
}
.bar span { display: block; height: 100%; background: var(--green); }

/* --------------------------------------------------------- performer rows */

.pgroup { margin-bottom: 18px; }
.pgroup h3 {
  margin: 0 0 9px;
  font-size: 14px;
  font-weight: 600;
  color: var(--accent-hi);
}
.pgroup h3 a:hover { text-decoration: underline; }

/* ---------------------------------------------------------- card state */

/* State gets its own full-size row. */
/* Two lines whether the title needs them or not, so a row of cards lines up. */
.cardbody .title { min-height: 2.6em; }

/*
 * Skip, Want and Add spread evenly (`space-between`), with padding, since
 * the row's contents vary.
 */
.cardstate {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  row-gap: 8px;
  flex-wrap: wrap;
  margin-top: 10px;
  /* Holds the row open on cards with no Add button, so heights still match. */
  min-height: 30px;
}

/* Equal weight, so no one of the three reads as the accident-prone one. */
.cardstate .chip.quiet { padding: 5px 12px; font-size: 12.5px; }

.cardstate button.add {
  padding: 5px 13px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.cardstate button.add:hover { border-color: var(--accent-hi); }
.cardstate button.add:disabled { opacity: 0.4; cursor: default; }

.crow button.add {
  padding: 6px 13px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
}
.crow button.add:hover { border-color: var(--accent-hi); }
.crow button.add:disabled { opacity: 0.4; cursor: default; }
.gapnote { font-size: 12.5px; color: var(--muted); white-space: nowrap; }

@media (max-width: 640px) {
  .sitehead img { width: 72px; }
  .scene { grid-template-columns: 24px 64px 1fr; }
  .scene .right { grid-column: 3; justify-content: flex-start; }
  .thumb { width: 64px; height: 36px; }
  .thumb.portrait { width: 36px; height: 51px; }
  .two { grid-template-columns: 1fr; }
  .crow { grid-template-columns: 44px 1fr; }
  .crow img, .crow .noposter { width: 44px; height: 44px; }
  .crow button.add { grid-column: 2; justify-self: start; }
}


/* Touch: more space between the three, not taller buttons. */
@media (pointer: coarse) {
  .cardstate { gap: 14px; row-gap: 10px; min-height: 36px; }
  .cardstate .chip.quiet, .cardstate button.add { padding: 9px 14px; }
}
/*
 * ==========================================================================
 * The library: rails, tiles, the player. Wider column than the rest of the app.
 * ==========================================================================
 */

main:has(.rail), main:has(.tiles), main:has(.stage) { max-width: 1560px; }

/* ------------------------------------------------------------------ rails */

.rail { margin: 0 0 30px; }

.railhead {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 10px;
}

.railtitle {
  margin: 0;
  font-size: 17px;
  font-weight: 650;
  letter-spacing: -0.01em;
}
a.railtitle .chevron { color: var(--muted); margin-left: 5px; transition: transform 0.15s, color 0.15s; display: inline-block; }
a.railtitle:hover { color: var(--accent-hi); }
a.railtitle:hover .chevron { color: var(--accent-hi); transform: translateX(3px); }

.railhead .muted { font-size: 12.5px; }
.railcount { margin-left: auto; font-size: 12px; color: var(--muted); }

.railbody { position: relative; }

.railtrack {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: var(--tile-lg);
  gap: 12px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  /* Room for the hover lift, which would otherwise be clipped by the scroller. */
  padding: 10px 2px 14px;
  scrollbar-width: none;
}
.railtrack::-webkit-scrollbar { display: none; }
.railtrack > * { scroll-snap-align: start; }

.railnav {
  position: absolute;
  top: 10px;
  bottom: 14px;
  width: 46px;
  z-index: 3;
  border: 0;
  cursor: pointer;
  color: var(--text);
  font-size: 26px;
  line-height: 1;
  opacity: 0;
  transition: opacity 0.15s;
}
.railnav.left { left: 0; background: linear-gradient(to right, var(--bg) 40%, transparent); border-radius: var(--radius) 0 0 var(--radius); }
.railnav.right { right: 0; background: linear-gradient(to left, var(--bg) 40%, transparent); border-radius: 0 var(--radius) var(--radius) 0; }
.railbody:hover .railnav { opacity: 0.9; }
.railnav:hover { opacity: 1; }
.railnav[hidden] { display: none; }

/* ------------------------------------------------------------------ tiles */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tile-lg), 1fr));
  gap: 16px 12px;
  padding: 10px 0 20px;
}

.tile {
  cursor: pointer;
  transition: transform 0.16s ease;
}
.tile:hover { transform: scale(1.045); z-index: 2; }
.tile:hover .tileart { border-color: var(--accent-hi); box-shadow: 0 8px 26px rgba(0, 0, 0, 0.55); }

.tileart {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  overflow: hidden;
  background: var(--panel-2);
  transition: border-color 0.16s, box-shadow 0.16s;
}
.tileart img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Fades up over the poster once it is actually playing, never before. */
.tilepreview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.35s;
}
.tilepreview.on { opacity: 1; }

/*
 * The hovered frame (see hoverPreview() in library/core.js). Covers the
 * whole tile, above the preview video.
 */
.tileframe {
  position: absolute;
  inset: 0;
  background-repeat: no-repeat;
  opacity: 0;
  transition: opacity 0.12s;
  pointer-events: none;
  z-index: 1;
}
.tileframe.on { opacity: 1; }

/* 15% of the tile (SCRUB_BAND in core.js). Visible on hover only. */
.tilescrub {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 15%;
  display: flex;
  align-items: flex-end;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0));
  opacity: 0;
  transition: opacity 0.16s;
  pointer-events: none;
  z-index: 2;
}
.tile:hover .tilescrub { opacity: 1; }

/* How far along the strip the cursor is, drawn the way every player draws it. */
.tilescrubfill {
  height: 3px;
  width: 0;
  background: var(--accent-hi);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
}

/* Nothing to hover with, so nothing to draw. */
@media (hover: none) {
  .tilescrub, .tileframe { display: none; }
}

/* Bottom left, clear of the progress bar, and never in the way of the hover
   scrub that runs along the same edge. */
.tilemarks { position: absolute; bottom: 7px; left: 7px; right: 64px; display: flex; flex-wrap: wrap; gap: 5px; z-index: 2; pointer-events: none; }
.pill {
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.02em;
  padding: 2px 6px;
  border-radius: 5px;
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(6px);
  color: #fff;
}
.pill.warn { color: #e3b341; }
/* Work already done, rather than work outstanding. Only the marker picker
   raises one today; it is here because .pill lives here. */
.pill.done { color: var(--green); }

/* Resolution against its target — see tiles.js. */
.pill.res-over { color: #ff6b6b; }
.pill.res-under { color: #e3b341; }
.pill.res-done { color: #3fb950; }

.tiletime {
  position: absolute;
  right: 7px;
  bottom: 7px;
  z-index: 2;
  font-size: 11px;
  padding: 2px 6px;
  border-radius: 5px;
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(6px);
}

.tileprogress { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: rgba(255, 255, 255, 0.22); z-index: 2; }
.tileprogressbar { height: 100%; background: var(--red); }

.tilebody { padding: 8px 2px 0; }
.tilebody .title {
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.tilebody .meta { font-size: 11.5px; color: var(--muted); margin-top: 3px; }
.tilebody .meta span + span::before { content: " · "; }
.tilebody .tilefolder {
  margin-top: 2px;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tilebody .tilefolder b { color: var(--text-2); font-weight: 600; }

/* With a description, titles hold two lines so blurbs line up. */
.tilebody:has(.tiledesc) .title { min-height: 2.6em; }

/* What it is about. Two lines like the title, so a row of tiles keeps its
   shape however much Stash has to say about any one of them. */
.tilebody .tiledesc {
  margin-top: 4px;
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* An unidentified scene is showing you a filename, so it should not pretend
   to be a title. */
.title.untitled, h1.untitled { font-style: italic; color: var(--muted); font-weight: 500; }

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

.listhead { margin: 4px 0 6px; }
.listhead h1 { margin: 0; font-size: 24px; letter-spacing: -0.02em; }
.listhead p { margin: 4px 0 0; font-size: 13px; }
.listhead.with-portrait { display: flex; gap: 18px; align-items: flex-start; }
.listhead .portrait {
  width: 132px;
  border-radius: var(--radius);
  background: var(--panel-2);
  object-fit: cover;
  aspect-ratio: 2 / 3;
}

/*
 * ------------------------------------------------- the performer's vitals
 *
 * Fills across before down; `auto-fit` collapses to one column on a phone.
 */

/* Portrait at 208px. */
.listhead.person .portrait { width: 208px; }

.listhead .who { min-width: 0; flex: 1; }
.listhead .who .fave { color: var(--accent-hi); font-size: 18px; margin-left: 8px; vertical-align: 2px; }

/* At most three columns. */
.vitals {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 4px 26px;
  max-width: 840px;
  margin: 10px 0 0;
  font-size: 12.5px;
}
/* Label and value on one line, the label a fixed column so the values line up
   down the page rather than wandering with the word lengths. */
.vitals > div { display: flex; gap: 10px; padding: 1px 0; align-items: baseline; }
.vitals dt { color: var(--muted); width: 96px; flex: none; }
.vitals dd { margin: 0; color: var(--text-2); min-width: 0; overflow-wrap: anywhere; }

/* A fact that did not come out of your own library says so, quietly. */
.vitals .src {
  margin-left: 7px;
  font-size: 9.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0 3px;
  vertical-align: 1px;
}

/* The IAFD save button, quiet, only when there's something to add. */
.listhead .keep {
  margin: 12px 0 0;
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text-2);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.listhead .keep:hover:not(:disabled) { border-color: var(--accent-hi); color: var(--text); }
.listhead .keep:disabled { opacity: 0.5; cursor: default; }

.listhead .kept { margin: 8px 0 0; font-size: 12px; color: var(--muted); }
.listhead .kept.bad { color: var(--accent-hi); }

/* The biography is prose, so it gets a measure rather than the header's width. */
.listhead .bio { margin: 12px 0 0; max-width: 78ch; color: var(--text-2); font-size: 13px; }

/* ----------------------------------------------------------------- player */

.stage {
  background: #000;
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: 18px;
  border: 1px solid var(--line);
}
.player { display: block; width: 100%; max-height: 76vh; background: #000; }

/*
 * ---------------------------------------------------------- player chrome
 *
 * Drawn by player.js: a 40px scrub row and 44px buttons on touch.
 */

.vplayer {
  position: relative;
  background: #000;
  line-height: 0;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.vplayer:focus { outline: none; }
.vplayer:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: -2px; }
.vplayer [hidden] { display: none !important; }

/* iOS puts its own controls back on a video it decides is playing inline. */
.vplayer video::-webkit-media-controls { display: none !important; }
.vplayer video::-webkit-media-controls-enclosure { display: none !important; }

.vplayer.fullscreen { display: flex; align-items: center; justify-content: center; }
.vplayer.fullscreen .player { max-height: 100vh; height: 100vh; object-fit: contain; }

/* The seek surface. pan-y keeps vertical swipes as page scrolls. */
.vsurface { position: absolute; inset: 0; touch-action: pan-y; }
.vplayer.scrubbing .vsurface { cursor: ew-resize; }

/* -------------------------------------------------------------- the bar */

.vbar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 30px 10px 8px;
  line-height: 1.2;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.86), rgba(0, 0, 0, 0.5) 58%, transparent);
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.vplayer.idle { cursor: none; }
.vplayer.idle .vbar { opacity: 0; transform: translateY(10px); pointer-events: none; }

.vbuttons { display: flex; align-items: center; gap: 3px; margin-top: 2px; }
.vgap { flex: 1; }

.vbtn {
  width: 36px;
  height: 36px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
}
.vbtn svg { width: 22px; height: 22px; }
.vbtn:hover { background: rgba(255, 255, 255, 0.15); }
.vspeed { width: auto; min-width: 40px; padding: 0 9px; color: var(--text-2); }
.vspeed.on { color: var(--accent-hi); }
.vwide.on { color: var(--accent-hi); }

/*
 * The mark button, tinted: the one control that writes. Not `.vmark`
 * (that's the track marker, absolutely positioned).
 */
.vmarkbtn { color: var(--accent-hi); }
.vmarkbtn:disabled { opacity: 0.45; cursor: default; }

.vtime {
  margin-left: 5px;
  font-size: 12.5px;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Slides out under the mute button rather than sitting there taking width. */
.vsound { display: flex; align-items: center; }
.vvolume {
  width: 0;
  opacity: 0;
  margin: 0;
  accent-color: var(--accent-hi);
  transition: width 0.16s ease, opacity 0.16s ease;
}
.vsound:hover .vvolume, .vvolume:focus { width: 76px; opacity: 1; margin: 0 6px 0 2px; }

/* ------------------------------------------------------------ the scrub */

.vscrub {
  position: relative;
  display: flex;
  align-items: center;
  height: 26px;
  cursor: pointer;
  /* Without this the browser takes a drag along the bar as a page scroll. */
  touch-action: none;
}
.vtrack {
  position: relative;
  width: 100%;
  height: 5px;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.22);
}
.vbuffered, .vplayed {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 0;
  border-radius: 99px;
}
.vbuffered { background: rgba(255, 255, 255, 0.3); }
.vplayed { background: var(--accent-hi); }

.vhandle {
  position: absolute;
  top: 50%;
  left: 0;
  width: 13px;
  height: 13px;
  margin: -6.5px 0 0 -6.5px;
  border-radius: 50%;
  background: var(--accent-hi);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.55);
  transition: transform 0.12s ease;
}
.vscrub:hover .vhandle, .vplayer.scrubbing .vhandle { transform: scale(1.4); }

/* The markers, drawn on the track so you can see what you are dragging past. */
.vmarks { position: absolute; inset: 0; pointer-events: none; }
.vmark {
  position: absolute;
  top: 50%;
  width: 3px;
  height: 11px;
  margin: -5.5px 0 0 -1.5px;
  border-radius: 2px;
  background: var(--amber);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6);
}

/* --------------------------------------------------- the thumbnail card */

.vpreview {
  position: absolute;
  bottom: calc(100% + 4px);
  left: 0;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 5px;
  border: 1px solid var(--line-hi);
  border-radius: 9px;
  background: rgba(10, 10, 10, 0.94);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.55);
  pointer-events: none;
  line-height: 1.2;
}
/* The window; .vshottile is the sprite tile scaled up inside it. */
.vshot { overflow: hidden; border-radius: 5px; background: #000; }
.vshottile {
  transform-origin: top left;
  background-repeat: no-repeat;
  background-color: #000;
}
.vshotlabel {
  max-width: 210px;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--amber);
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vshottime { font-size: 12px; color: var(--text-2); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------- centre, spinner, flash */

.vbig {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 72px;
  height: 72px;
  transform: translate(-50%, -50%);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.5);
  transition: opacity 0.18s ease;
}
.vbig svg { width: 34px; height: 34px; }
.vbig:hover { background: rgba(0, 0, 0, 0.68); }
.vplayer.playing .vbig { opacity: 0; pointer-events: none; }

.vspinner {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 44px;
  height: 44px;
  margin: -22px 0 0 -22px;
  border: 3px solid rgba(255, 255, 255, 0.22);
  border-top-color: var(--accent-hi);
  border-radius: 50%;
  animation: vspin 0.8s linear infinite;
  pointer-events: none;
}
@keyframes vspin { to { transform: rotate(360deg); } }

/* What says a double-tap landed when the bar is hidden. */
.vflash {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  padding: 9px 15px;
  border-radius: 99px;
  background: rgba(0, 0, 0, 0.66);
  color: #fff;
  font-size: 15px;
  font-weight: 700;
  line-height: 1;
  pointer-events: none;
}

/* Touch: taller row, bigger handle, no volume slider. */
@media (pointer: coarse) {
  .vbar { padding: 34px 8px 12px; }
  .vscrub { height: 40px; }
  .vtrack { height: 7px; }
  .vhandle { width: 20px; height: 20px; margin: -10px 0 0 -10px; }
  .vmark { width: 4px; height: 15px; margin: -7.5px 0 0 -2px; }
  .vbtn { width: 44px; height: 44px; }
  .vbtn svg { width: 24px; height: 24px; }
  .vbtn:hover { background: transparent; }
  .vsound { display: none; }
  .vscrub:hover .vhandle { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .vbar, .vhandle, .vbig, .vvolume { transition: none; }
  .vspinner { animation-duration: 2s; }
}

/*
 * -------------------------------------------------------------- the scene
 *
 * Two columns (player and actions, reading rail); one stack under 1080px.
 */

.scenepage {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: 26px;
  align-items: start;
  margin-bottom: 26px;
}
.scenemain { min-width: 0; }
.sceneside { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

.scenepage h1 { margin: 0 0 8px; font-size: 27px; letter-spacing: -0.02em; line-height: 1.2; }

/*
 * Head row: title block left, cast right. Faces are fixed-size so the cast
 * never sets the height; past about five it scrolls.
 */
.scenehead {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, auto);
  gap: 22px;
  align-items: start;
  min-height: 106px;
  margin-bottom: 16px;
}
.headtext { min-width: 0; }
.scenehead .scenefacts { margin-bottom: 14px; }
.scenehead .sceneactionbar { margin-bottom: 0; }

/* justify-self is what makes the column shrink to the faces rather than the
   faces spread across the column. */
.headcast { justify-self: end; min-width: 0; max-width: 420px; }
.castfaces {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  scroll-snap-type: x proximity;
}
.castfaces::-webkit-scrollbar { display: none; }
.castfaces .castling { flex: 0 0 auto; width: 68px; scroll-snap-align: start; }
.castfaces .castname {
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sidecard {
  border: 1px solid var(--line);
  background: var(--panel);
  border-radius: var(--radius);
  padding: 14px 15px;
}
.sidehead {
  margin: 0 0 11px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}
.sidecard .tagrow { margin-bottom: 0; }
.sidelinks { display: flex; flex-direction: column; gap: 8px; }
.sidelinks .act { justify-content: center; text-align: center; }

/* The cast as faces. A round crop sits high because that is where a face is. */
.castgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 13px 10px; }
.castling { display: flex; flex-direction: column; align-items: center; gap: 6px; color: var(--text); }
.castart {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 50%;
  overflow: hidden;
  background: var(--panel-2);
  border: 1px solid var(--line);
}
.castart img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 18%; display: block; }
.castblank { display: grid; place-items: center; width: 100%; height: 100%; font-size: 21px; color: var(--muted); }
.castart .fav {
  position: absolute;
  bottom: 3px;
  right: 8px;
  font-size: 12px;
  line-height: 1;
  color: var(--amber);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}
.castling:hover .castart { border-color: var(--accent-hi); }
.castname { font-size: 12px; line-height: 1.25; text-align: center; }

/* A codec is something you look up, not something you read every time. */
.spec { display: grid; grid-template-columns: auto 1fr; gap: 5px 12px; margin: 0; font-size: 12.5px; }
.spec dt { color: var(--muted); }
.spec dd { margin: 0; text-align: right; color: var(--text-2); }
.sidecard .filepath { margin: 12px 0 0; }

/* A card whose heading carries its own pair of arrows. */
.sidetop { display: flex; align-items: center; gap: 9px; margin-bottom: 11px; }
.sidetop .sidehead { margin: 0; }
.sidecount { font-size: 12px; color: var(--muted); }
.sidesteps { margin-left: auto; display: flex; gap: 5px; }
.sidestep {
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel-2);
  color: var(--text-2);
  font: inherit;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.sidestep:hover:not(:disabled) { border-color: var(--accent-hi); color: var(--text); }
.sidestep:disabled { opacity: 0.35; cursor: default; }

/* One gallery to a view. The arrows push; the snap points do the arriving. */
.sidetrack {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.sidetrack::-webkit-scrollbar { display: none; }
.sidetrack > .facet { flex: 0 0 100%; scroll-snap-align: start; }

/* Wide: the rail folds away. */
.scenepage.wide { grid-template-columns: minmax(0, 1fr); }
.scenepage.wide .sceneside { display: none; }

/* Everything else fades (back on hover); nothing is hidden or moved. */
body.videowide .topbar,
body.videowide .statusbar,
body.videowide #view > *:not(.scenepage) {
  opacity: 0.22;
  transition: opacity 0.25s ease;
}
body.videowide .topbar:hover,
body.videowide .topbar:focus-within,
body.videowide .statusbar:hover,
body.videowide #view > *:not(.scenepage):hover,
body.videowide #view > *:not(.scenepage):focus-within {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  body.videowide .topbar,
  body.videowide .statusbar,
  body.videowide #view > *:not(.scenepage) { transition: none; }
}

/* The button that unfolds the rest of the tags wears the same chip. */
.tagchip.more { font: inherit; font-size: 12px; color: var(--muted); cursor: pointer; }
.tagchip.more:hover { border-color: var(--accent-hi); color: var(--text); }

@media (max-width: 1080px) {
  .scenepage { grid-template-columns: minmax(0, 1fr); gap: 18px; }
  .vwide { display: none; }
  .scenepage.wide .sceneside { display: flex; }
  .castgrid { grid-template-columns: repeat(6, 1fr); }
  .sidelinks { flex-direction: row; flex-wrap: wrap; }
}
@media (max-width: 720px) {
  .castgrid { grid-template-columns: repeat(4, 1fr); }

  /* No column to sit beside, so the strip goes under the title and reads left
     to right like everything else on a phone. */
  .scenehead { grid-template-columns: minmax(0, 1fr); gap: 16px; min-height: 0; }
  .headcast { justify-self: stretch; max-width: none; }
}

.scenefacts { display: flex; flex-wrap: wrap; gap: 8px; font-size: 13px; color: var(--muted); margin-bottom: 16px; }
.scenefacts span + span::before, .scenefacts a + span::before { content: "· "; }
.scenefacts .link { color: var(--text); font-weight: 600; }
.scenefacts .link:hover { color: var(--accent-hi); }

.sceneactionbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 18px; }
.sceneactions { display: flex; align-items: center; gap: 8px; }
/* On a gallery the same row sits inside the header block. */
.listhead .sceneactions { margin: 12px 0 4px; }

.act {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 13px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.act:hover { border-color: var(--accent-hi); }
.act.on { border-color: var(--green); color: #7ee787; }
.act.primary { border-color: var(--accent); background: var(--accent); color: #fff; }
.act.primary:hover { filter: brightness(1.1); }
.act.muted { cursor: default; color: var(--muted); }
.act.muted:hover { border-color: var(--line); }
.act:disabled { opacity: 0.5; cursor: default; }
/* display: inline-flex above would otherwise beat the browser's own [hidden]. */
.act[hidden] { display: none; }

.stars { display: flex; gap: 1px; }
.star {
  border: 0;
  background: none;
  color: var(--line);
  font-size: 19px;
  line-height: 1;
  padding: 2px;
  cursor: pointer;
  transition: color 0.12s;
}
.star.on { color: var(--amber); }
.stars:hover .star { color: var(--muted); }
.star:hover, .star:hover ~ .star { color: var(--line); }
.stars:hover .star:hover { color: var(--amber); }

.markers { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
.marker {
  padding: 5px 10px;
  border-radius: 20px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.marker:hover { border-color: var(--accent-hi); color: var(--text); }

.castrow, .tagrow { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
.tagchip {
  padding: 5px 11px;
  border-radius: 20px;
  border: 1px solid var(--line);
  background: var(--panel);
  font-size: 12.5px;
}
.castrow .tagchip { font-weight: 600; }
.tagrow .tagchip { color: var(--muted); font-size: 12px; }
a.tagchip:hover { border-color: var(--accent-hi); color: var(--text); }
/* A tag has no page to go to, so it is not dressed as something you can click. */
.tagchip.flat { cursor: default; }

.scenedetails { margin: 0 0 16px; max-width: 78ch; color: var(--text-2); }
.filepath { font-size: 11.5px; color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; margin: 0 0 24px; }

@media (max-width: 720px) {
  .railnav { display: none; }
  .listhead.with-portrait { flex-direction: column; }
  .listhead.person .portrait { width: 168px; }
}


/* Scale down, set apart by a rule. */
.shrink {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}
.shrink .bad { color: var(--red-hi); }

.shrinkbuttons {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.shrinkbuttons .chip { padding: 4px 11px; }

/* The choice in force — or, mid press-twice, the one about to be. */
.shrinkbuttons .chip.on { border-color: var(--accent-hi); color: var(--accent-hi); }
.shrinkbuttons .chip:disabled { opacity: 0.45; cursor: default; }

/* 720p is what FileFlows does anyway; said quietly, beside the number. */
.shrinkbuttons .chipnote { font-size: 10.5px; opacity: 0.65; }

/* The sentence under the chips takes its own line. */
.shrinkbuttons > .muted.small:last-child { flex-basis: 100%; }

@media (pointer: coarse) {
  /* Room around them, because the press cannot be taken back. */
  .shrinkbuttons { gap: 14px; }
  .shrinkbuttons .chip { min-height: 40px; padding: 8px 14px; }
}

/*
 * The scene index (see indexCard() in library/scene.js). Squarer cells,
 * cropped top and bottom, so frames are readable.
 */
.sceneindex {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
}

.indexcell {
  position: relative;
  aspect-ratio: 4 / 3;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background-color: var(--panel-2);
  background-repeat: no-repeat;
  cursor: pointer;
  overflow: hidden;
}
.indexcell:hover { border-color: var(--accent-hi); }
.indexcell:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: 1px; }

/* The time, small and only where it does not fight the picture. */
.indexat {
  position: absolute;
  left: 0;
  bottom: 0;
  padding: 0 3px;
  background: rgba(0, 0, 0, 0.66);
  color: #fff;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9.5px;
  line-height: 1.5;
  border-radius: 0 3px 0 3px;
  pointer-events: none;
}

@media (pointer: coarse) {
  /* A fingertip per cell: four across a 340px rail is 80px, which is already
     a comfortable target — it is the gap between them that needs the room. */
  .sceneindex { gap: 6px; }
  .indexat { font-size: 11px; }
}
/*
 * ==========================================================================
 * The acquisition section strip, inside the page.
 * ==========================================================================
 */

.sections {
  display: flex;
  gap: 2px;
  margin: 0 0 20px;
  border-bottom: 1px solid var(--line);
}

.section {
  padding: 9px 15px 11px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.section:hover { color: var(--text-2); }
.section.on { color: var(--text); border-bottom-color: var(--accent-hi); }

/*
 * ---------------------------------------------------------------- creators
 *
 * Small tiles, wide column.
 */

main:has(.creators) { max-width: 1560px; }

.creators {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tile-sm), 1fr));
  gap: 14px;
  align-items: start;
}

.creator {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.12s ease, transform 0.12s ease;
}
.creator:hover { border-color: var(--accent-hi); transform: translateY(-2px); }

.creatorart {
  position: relative;
  aspect-ratio: 3 / 4;
  background: var(--panel-2);
}
.creatorart img { width: 100%; height: 100%; object-fit: cover; display: block; }

.creatorart .fav {
  position: absolute;
  top: 6px;
  right: 6px;
  font-size: 13px;
  line-height: 1;
  color: var(--amber);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}

.creatorbody { padding: 8px 10px 10px; }
.creatorbody .title {
  font-weight: 600;
  font-size: 13px;
  line-height: 1.3;
  /* One line: a wrapped name makes a wall of tiles ragged. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.creatorbody .meta { font-size: 11.5px; color: var(--muted); margin-top: 3px; }

/* The name filter, sized like a chip rather than like the topbar search. */
.filter {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--text);
  font: inherit;
  font-size: 13px;
  padding: 6px 14px;
  min-width: 220px;
}
.filter:focus { border-color: var(--accent-hi); }

@media (max-width: 640px) {
  .sections { overflow-x: auto; }
  .section { white-space: nowrap; }
  .filter { min-width: 0; flex: 1; }
}

/*
 * ==========================================================================
 * The library sections: a grid of what you hold, then a quieter rail of
 * what you don't.
 * ==========================================================================
 */

/* Faces and covers want the width the rails already take. */
main:has(.facets), main:has(.tallies) { max-width: 1560px; }

/* ------------------------------------------------------------- the tallies */

.tallies {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--tile-md), 1fr));
  gap: 10px;
  margin: 0 0 30px;
}

.tally {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 14px 16px 15px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color 0.12s ease;
}
.tally:hover { border-color: var(--accent-hi); }

.tally b {
  font-size: 26px;
  font-weight: 650;
  letter-spacing: -0.02em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.tallylabel { font-size: 13px; color: var(--text-2); }
.tallyrest { font-size: 11.5px; color: var(--muted); margin-top: 3px; }


/*
 * --------------------------------------------------------------- the facets
 *
 * One tile for a person, a studio and a movie.
 */

.facets {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tile-sm), 1fr));
  gap: 14px;
  align-items: start;
}

/* Categories lead with 16:9 frames, so they take the Scenes tile width. */
.facets:has(.facetart.wide) {
  grid-template-columns: repeat(auto-fill, minmax(var(--tile-lg), 1fr));
}

.facet {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.12s ease, transform 0.12s ease;
}
.facet:hover { border-color: var(--accent-hi); transform: translateY(-2px); }

/* Nothing to click through to — usually a scene TPDB has no page for. */
.facet.inert { cursor: default; }
.facet.inert:hover { border-color: var(--line); transform: none; }

/* You hold nothing of this one. Dimmed rather than greyed, so the artwork is
   still recognisable — the point of the rail is that you spot a name. */
.facet.faded .facetart { opacity: 0.55; }
.facet.faded:hover .facetart { opacity: 1; }

.facetart {
  position: relative;
  aspect-ratio: 3 / 4;
  background: var(--panel-2);
  transition: opacity 0.15s ease;
}
/* Logos: landscape and contained. */
.facetart.logo { aspect-ratio: 16 / 9; background: var(--panel-2); }
.facetart.logo img { object-fit: contain; padding: 12px; }
.facetart.wide { aspect-ratio: 16 / 9; }

/*
 * ============================================================= the film wall
 * Posters, 2:3.
 */
.facets.posters .facetart { aspect-ratio: 2 / 3; }
.facets.posters .facetart img { width: 100%; height: 100%; object-fit: cover; }

/* Which kind of film this is. Top left, because it is the one thing you cannot
   read off the picture and it changes what a click does. */
.kindmark {
  position: absolute;
  top: 6px;
  left: 6px;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 6px;
  font-size: 12px;
  line-height: 1;
  background: rgba(20, 19, 20, 0.78);
  border: 1px solid var(--line-hi);
  backdrop-filter: blur(3px);
}
.kindmark.group { color: var(--amber); }
.kindmark.film { color: var(--blue); }

/* The per-card actions, for working along a shelf rather than stopping on one
   film. Hidden until the card is under the pointer so the wall stays a wall. */
.cardacts {
  display: flex;
  gap: 4px;
  padding: 0 10px 9px;
  opacity: 0;
  transition: opacity 0.12s ease;
}
.facet.film:hover .cardacts,
.facet.film:focus-within .cardacts { opacity: 1; }

.cardact {
  flex: 1;
  padding: 3px 0;
  font-size: 12px;
  line-height: 1.4;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text-2);
  cursor: pointer;
}
.cardact:hover { border-color: var(--accent-hi); color: var(--text); }

/* The filter bar. Wraps rather than scrolls: on a narrow window a hidden
   filter is a filter nobody knows is set. */
.shelfbar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-bottom: 14px;
}
.shelfbar .filter { flex: 1 1 200px; min-width: 0; max-width: 300px; }
.shelfpick {
  padding: 5px 8px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--text);
  font: inherit;
  font-size: 13px;
  max-width: 190px;
}


/* The wall pages in at 120. A button rather than an endless scroll: the shelf
   has an end and it is worth being able to reach it. */
.showmore { display: block; margin: 2px auto 26px; }
.filmedit { max-width: 620px; }
.filmedrow { display: flex; gap: 16px; align-items: flex-start; }
.filmedrow h3 { margin: 0 0 8px; font-size: 13px; }
.filmedrow > div { flex: 1; min-width: 0; }
.filmposter {
  flex: none;
  width: 150px;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-2);
}
.coverpick { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
.coverthumb { width: 54px; border-radius: 4px; border: 1px solid var(--line); }

/*
 * How much of a film you hold: solid = counted from a scene list, hollow
 * = matched by name.
 */
.holdbadge {
  position: absolute;
  top: 6px;
  left: 6px;
  padding: 2px 6px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
  backdrop-filter: blur(3px);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
.holdbadge.exact {
  background: var(--green);
  color: #08140b;
  text-shadow: none;
}
/* Stash's own count, own colour. */
.holdbadge.stash {
  background: var(--blue);
  color: #05101f;
  text-shadow: none;
}
/* Tracked coverage, own colour. */
.holdbadge.coverage {
  background: var(--amber);
  color: #1a1204;
  text-shadow: none;
}
.holdbadge.probable {
  background: rgba(20, 19, 20, 0.72);
  border: 1px dashed var(--line-hi);
  color: var(--text-2);
}
.facetart img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Stash has no picture for this one. */
.facetblank {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 30px;
  font-weight: 600;
  color: var(--line-hi);
  background: var(--panel-2);
  user-select: none;
}

.facetart .fav {
  position: absolute;
  top: 6px;
  right: 6px;
  font-size: 13px;
  line-height: 1;
  color: var(--amber);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}

.facetbody { padding: 8px 10px 10px; }
.facetbody .title {
  font-weight: 600;
  font-size: 13px;
  line-height: 1.3;
  /* One line: a wrapped name makes a wall of tiles ragged. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.facetbody .meta { font-size: 11.5px; color: var(--muted); margin-top: 3px; }

/* With a blurb, the meta holds two lines so rows line up. */
.facetbody:has(.facetdesc) .meta { min-height: 2.4em; }

/* The blurb, two clamped lines. */
.facetbody .facetdesc {
  margin-top: 4px;
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---------------------------------------------------------- the missing rail */

.rail.missing { margin-top: 34px; }
.rail.missing .railtitle { font-size: 15px; font-weight: 600; color: var(--text-2); }

/*
 * -------------------------------------------------------- the monitored rail
 *
 * A panel with an accent edge.
 */
.rail.monitored {
  margin: 0 0 26px;
  padding: 14px 16px 4px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent-hi);
  border-radius: var(--radius);
  background: var(--panel);
}
.rail.monitored .railtitle { font-size: 15px; font-weight: 650; color: var(--accent-hi); }

/* A facet is a third the width of a scene tile, so the track follows it. */
.railtrack:has(.facet) { grid-auto-columns: var(--tile-sm); }

/* Except a scene in one, which is 16/9 and unreadable at a portrait's width. */
.rail.monitored .railtrack:has(.facetart.wide) { grid-auto-columns: var(--tile-lg); }

@media (max-width: 640px) {
  .tally b { font-size: 21px; }
}

/*
 * ========================================================== a movie's page
 * Two columns, with the chapter list under the player.
 */

.filmpage .chapterblock { margin-bottom: 18px; }
.filmpage .chapterhead { font-size: 12px; color: var(--muted); margin-bottom: 8px; }

.chapters {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 320px;
  overflow-y: auto;
}

.chapter {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) 64px 20px;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 10px;
  text-align: left;
  font: inherit;
  color: var(--text-2);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
}
.chapter:hover { border-color: var(--accent-hi); color: var(--text); }

/* The part you are watching. A left edge rather than a fill: this list sits
   directly under the picture and a lit row is a bright band across it. */
.chapter.on {
  border-color: var(--accent-hi);
  border-left-width: 3px;
  color: var(--text);
  background: var(--panel-2);
}

.chapterno {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 6px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--muted);
  background: var(--panel-2);
  border: 1px solid var(--line);
}
.chapter.on .chapterno { color: var(--accent-hi); border-color: var(--accent-hi); }

.chaptertext { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.chaptertitle {
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chapterfacts { font-size: 11.5px; color: var(--muted); }

/* How far through that part you are, from Stash's own resume time. */
.chapterbar {
  height: 4px;
  border-radius: 999px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  overflow: hidden;
}
.chapterbarfill { display: block; height: 100%; background: var(--accent-hi); }

.chapteraway {
  text-align: center;
  font-size: 13px;
  color: var(--muted);
  text-decoration: none;
}
.chapteraway:hover { color: var(--accent-hi); }

/* The box art in the rail takes the column, unlike the 150px one in the
   editor dialog this class was written for. */
.sceneside .filmposter { width: 100%; margin-bottom: 12px; }

@media (max-width: 720px) {
  .chapter { grid-template-columns: 24px minmax(0, 1fr) 20px; }
  .chapter .chapterbar { display: none; }
}
/*
 * ============================================================ the Overview
 *
 * The Overview is a video hub; the Stats tab keeps the pipeline and charts.
 * In Stats: grey is upstream, text colour is yours, coral is the subject
 * (one thing per chart).
 */

/*
 * ----------------------------------------------------------- the hub
 *
 * A section: rule, label, MORE, then news cards or a scrolling shelf.
 */

.editorial { margin: 0 0 34px; }
/* The scroller's own top padding leaves room for the tile hover-lift, so the
   header does not need to hold the gap open as well. */
.editorial .railbody { margin-top: -6px; }

/* Label left, note beside it, MORE hard right, all sitting on one rule. */
.edhead {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding-bottom: 8px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--line);
}
.edname {
  margin: 0;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.ednote { font-size: 12px; color: var(--muted); }

.edmore {
  margin-left: auto;
  flex: none;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  color: var(--muted);
  text-decoration: none;
  cursor: pointer;
}
.edmore:hover { color: var(--accent-hi); }
.edmore span { display: inline-block; transition: transform 0.15s; }
.edmore:hover span { transform: translateX(3px); }

/* The size control sits beside MORE. */
.edhead .sizepick { margin-left: 14px; }

/* One card, whatever it is holding: art, then the words. The three shapes
   below only change where the art sits and how big it is. */
.edcard { cursor: pointer; min-width: 0; }
.edart {
  position: relative;
  overflow: hidden;
  border-radius: 6px;
  background: var(--panel-2);
  aspect-ratio: 16 / 9;
}
.edart img { width: 100%; height: 100%; object-fit: cover; display: block; transition: opacity 0.15s; }
.edcard:hover .edart img { opacity: 0.8; }
.edcard:hover .edtitle { color: var(--accent-hi); }

.edtext { min-width: 0; }
.edtitle {
  font-size: 15px;
  font-weight: 650;
  line-height: 1.28;
  letter-spacing: -0.01em;
  transition: color 0.12s;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.edmeta { margin-top: 5px; font-size: 11.5px; color: var(--muted); }
.edmeta span + span::before { content: " · "; }
.edsummary {
  margin: 8px 0 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--text-2);
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* --- news: a row of big cards, art over words, like a picks strip --- */

.ednews {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 22px 20px;
}
.ednews .edtext { margin-top: 10px; }
/* What MORE reveals. Physically smaller than the three above it — these are
   the rest of the pile, not another set of leads. */
.ednews.more {
  margin-top: 26px;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: 18px 16px;
}
.ednews.more:empty { display: none; }
.ednews.more .edtext { margin-top: 8px; }
.ednews.more .edtitle { font-size: 12.5px; -webkit-line-clamp: 3; }
.ednews.more .edmeta { font-size: 11px; }
.ednews.more .edsummary { display: none; }

.overviewtop { margin: 0 0 30px; }

/* ------------------------------------------------------------ the pipeline */

.flowpanel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px 18px 14px;
  margin: 0 0 14px;
}

.flowhead {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.flowhead h2 { font-size: 15px; font-weight: 600; letter-spacing: 0.01em; }

/* The bar itself. One row of proportional segments, so its shape is the state
   of the pipeline — a fat middle is a backlog at the encoder. */
.flow {
  display: flex;
  gap: 3px;
  height: 30px;
  border-radius: 5px;
  overflow: hidden;
}

.flowseg {
  display: block;
  min-width: 3px;
  border-radius: 3px;
  background: var(--line);
  transition: filter 0.15s ease;
}
a.flowseg:hover { filter: brightness(1.45); }

/* Left to right, the ramp from "somewhere else" to "yours and filed". */
.flow-downloaded .flowdot, .flowseg.flow-downloaded { background: #46424a; }
.flow-editing    .flowdot, .flowseg.flow-editing    { background: #6a6470; }
.flow-encoding   .flowdot, .flowseg.flow-encoding   { background: var(--amber); }
.flow-library    .flowdot, .flowseg.flow-library    { background: var(--accent-hi); }
.flow-film       .flowdot, .flowseg.flow-film       { background: var(--text-2); }

.flowkeys {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 22px;
  margin-top: 12px;
}

.flowkey {
  display: flex;
  align-items: baseline;
  gap: 7px;
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
a.flowkey:hover { border-bottom-color: var(--line-hi); }

/* A stage at nought keeps its figure — a queue that has cleared is worth
   seeing — but stops competing with the ones that have something in them. */
.flowkey.spent { opacity: 0.45; }

.flowdot {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  align-self: center;
  flex: none;
}

.flowkey b {
  font-size: 17px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.flowname { font-size: 12.5px; color: var(--muted); }

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

.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 10px;
  margin: 0 0 14px;
}

.metric {
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px 16px 15px;
  color: inherit;
  text-decoration: none;
}
a.metric:hover { border-color: var(--accent-hi); }

.metric h3 {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 8px;
}

.metricbig {
  font-size: 26px;
  font-weight: 650;
  letter-spacing: -0.02em;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.metricsub { font-size: 12px; color: var(--muted); margin-top: 3px; line-height: 1.45; }

/* The chart sits at the bottom of the card whatever the wording above it does,
   so a row of cards keeps one baseline instead of five ragged ones. */
.metricchart { margin-top: auto; padding-top: 14px; }

/* --------------------------------------------------------------- the marks */

.spark { display: block; width: 100%; height: 54px; overflow: visible; }

/* Namespaced away from the acquisition side's `.bar`, which sets a CSS height
   — and in SVG2 that beats the height attribute, flattening every column. */
.spark .sparkbar { fill: var(--line-hi); }
/* One coral column: the month you are in. */
.spark .sparkbar.now { fill: var(--accent-hi); }
.spark .sparkbase { stroke: var(--line); stroke-width: 0.4; vector-effect: non-scaling-stroke; }

.spark .areafill { fill: var(--accent-wash); }
.spark .arealine {
  fill: none;
  stroke: var(--accent-hi);
  stroke-width: 1.4;
  stroke-linejoin: round;
  /* The viewBox is stretched to the card's width, so without this the line
     would be drawn thin horizontally and fat vertically. */
  vector-effect: non-scaling-stroke;
}

.sparkaxis {
  display: flex;
  justify-content: space-between;
  font-size: 10.5px;
  color: var(--muted);
  margin-top: 4px;
  font-variant-numeric: tabular-nums;
}

/* Parts of a whole. */
.stack {
  display: flex;
  gap: 2px;
  height: 22px;
  border-radius: 4px;
  overflow: hidden;
}

.stackseg {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 2px;
  border-radius: 2px;
  font-size: 10.5px;
  font-weight: 600;
  color: #141314;
}
/* Only a segment with room says its own share; the rest are in the key. */
.stackseg span { opacity: 0.75; }

.stackseg.good, .stackkey.good i { background: var(--accent-hi); }
.stackseg.mid,  .stackkey.mid i  { background: #8d6f74; }
.stackseg.low,  .stackkey.low i  { background: var(--line-hi); }
.stackseg.nil,  .stackkey.nil i  { background: #3f3c3d; }
.stackseg.low, .stackseg.nil { color: var(--text-2); }

.stackkeys {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin-top: 9px;
  font-size: 11.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.stackkey { display: inline-flex; align-items: center; gap: 6px; }
.stackkey i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }

/* The ring. */
.ringwrap { display: flex; justify-content: center; }
.ring { width: 76px; height: 76px; }
.ringtrack { fill: none; stroke: var(--line); stroke-width: 4; }
.ringfill {
  fill: none;
  stroke: var(--accent-hi);
  stroke-width: 4;
  stroke-linecap: round;
}

/* ------------------------------------------------------------ the tidy-up */

.tidy {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 15px 18px 16px;
  margin: 26px 0 10px;
}
/* Nothing to do is one quiet line, not a panel with dead buttons in it. */
.tidy.quiet { border-style: dashed; padding: 12px 18px; }

.tidylines {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 28px;
  margin: 12px 0 14px;
}
.tidyline { display: flex; flex-direction: column; gap: 2px; }
.tidyline b { font-size: 14.5px; font-weight: 600; font-variant-numeric: tabular-nums; }

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

/* Not `.toolbar` — see the note in tidyPanel(). */
.tidybar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px 14px; }

/* The only button in the app that deletes someone else's files. It is allowed
   to look like it — and it still asks first. */
.act.danger { border-color: var(--red); color: var(--red-hi); }
.act.danger:hover:not(:disabled) { background: var(--red); border-color: var(--red); color: #fff; }

@media (max-width: 640px) {
  .metricbig { font-size: 22px; }
  .flowkeys { gap: 6px 16px; }
  .flowkey b { font-size: 15px; }
}

/* --------------------------------------------------- the movie gap-filler */

.gappanel { margin: 0 0 20px; }
.gappanel .note { margin-bottom: 10px; }

.candidates {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tile-md), 1fr));
  gap: 12px;
  margin-top: 12px;
}

.matchpick {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--panel);
}

.candart {
  aspect-ratio: 2 / 3;
  background: var(--panel-2);
  display: grid;
  place-items: center;
}
.candart img { width: 100%; height: 100%; object-fit: cover; display: block; }

.candbody { padding: 9px 11px 11px; display: grid; gap: 7px; }
.candbody .title { font-size: 13px; font-weight: 600; line-height: 1.3; }
.candbody .meta { font-size: 11.5px; color: var(--muted); }
.candbody .act { justify-content: center; }


/*
 * ------------------------------------------------------------ the feed
 *
 * The Feed: live marker clips on the left, the pitch on the right,
 * stacked on a phone.
 */
.feedhero {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 28px;
  align-items: center;
  margin: 4px 0 38px;
  padding: 18px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background:
    radial-gradient(120% 140% at 100% 0%, var(--accent-wash), transparent 60%),
    var(--panel);
}

.fhscreen {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 10px;
  background: #000;
  cursor: pointer;
}
.fhvideo { width: 100%; height: 100%; object-fit: cover; display: block; }
.fhscreen::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.72), transparent 45%);
  pointer-events: none;
}

.fhcaption {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 12px;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
}
.fhtag {
  padding: 2px 9px;
  margin-bottom: 3px;
  border: 1px solid var(--accent-hi);
  border-radius: 999px;
  font-size: 11.5px;
  color: var(--accent-hi);
}
.fhscene { font-weight: 700; font-size: 14px; }
.fhwho { font-size: 12px; color: var(--text-2); }

.fhkicker {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-hi);
}
.fhtitle { margin: 6px 0 10px; font-size: 34px; line-height: 1.05; }
.fhlede { margin: 0 0 16px; color: var(--text-2); line-height: 1.55; }
.fhlede b { color: var(--text); }

.fhchips { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 18px; }
.fhchips:empty { display: none; }
.fhchip {
  padding: 4px 11px;
  border: 1px solid var(--line-hi);
  border-radius: 999px;
  background: var(--panel-2);
  font: inherit;
  font-size: 12px;
  color: var(--text-2);
  cursor: pointer;
}
.fhchip:hover { border-color: var(--accent-hi); color: var(--accent-hi); }

.fhstart { font-size: 15px; padding: 10px 20px; }
.fhstart span { display: inline-block; transition: transform 0.15s; }
.fhstart:hover span { transform: translateX(3px); }

@media (max-width: 760px) {
  .feedhero { grid-template-columns: 1fr; gap: 16px; padding: 12px; }
  .fhtitle { font-size: 28px; }
  .fhstart { width: 100%; }
}
/* ==========================================================================
   Galleries

   A photo set is neither a scene tile nor a portrait: it is whatever shape the
   photographer shot, and a wall of them is only readable if they all agree. So
   the cover crops square, and the pictures inside keep their own aspect in a
   masonry-ish grid where the only thing held constant is the row height.
   ========================================================================== */

.facetart.square { aspect-ratio: 1 / 1; }
.listhead .portrait.square { aspect-ratio: 1 / 1; object-fit: cover; }

.shots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tile-md), 1fr));
  gap: 8px;
  padding: 12px 0 18px;
}

.shot {
  position: relative;
  aspect-ratio: 1 / 1;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--panel-2);
  cursor: pointer;
  transition: border-color 0.12s, transform 0.12s;
}
.shot:hover { border-color: var(--accent-hi); transform: scale(1.02); z-index: 2; }
.shot img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* A thumbnail that would not load is a picture that will not download, so it
   says so rather than sitting there as an empty square someone might tick. */
.shot.broken { opacity: 0.3; }
.shot.broken::after {
  content: "no";
  position: absolute;
  inset: auto 0 0 0;
  padding: 3px;
  font-size: 11px;
  text-align: center;
  background: var(--panel);
  color: var(--muted);
}

/* ------------------------------------------------------------- the picker */

.shots.picking .shot { opacity: 0.45; }
.shots.picking .shot.on { opacity: 1; border-color: var(--accent-hi); }
.shots.picking .shot.on::before {
  content: "✓";
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 1;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: 12px;
}

.gallerybuild .picker { margin-top: 12px; }
.gallerybuild .toolbar { margin: 10px 0 0; }
.gallerybuild input[type="url"], .gallerybuild input[type="text"] { min-width: 0; flex: 1; }
.small { font-size: 12px; }

/* ------------------------------------------------------------- the viewer
   Full-bleed on top of everything, its own backdrop rather than a <dialog>:
   the page underneath keeps its scroll position and the player, if there is
   one, is not torn down by a modal taking focus. */

.viewer {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: 32px;
  background: rgba(0, 0, 0, 0.9);
}

.viewerimg {
  max-width: 100%;
  max-height: calc(100vh - 96px);
  object-fit: contain;
  border-radius: var(--radius);
}

.viewernav, .viewerclose {
  position: absolute;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text);
  border-radius: var(--radius);
  cursor: pointer;
  opacity: 0.75;
  transition: opacity 0.12s;
}
.viewernav { top: 50%; transform: translateY(-50%); width: 44px; height: 76px; font-size: 26px; }
.viewernav.left { left: 16px; }
.viewernav.right { right: 16px; }
.viewerclose { top: 16px; right: 16px; width: 38px; height: 38px; font-size: 20px; }
.viewernav:hover, .viewerclose:hover { opacity: 1; border-color: var(--accent-hi); }

.viewercount {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--line);
  font-size: 12.5px;
  color: var(--text-2);
}

@media (max-width: 640px) {
  .viewer { padding: 8px; }
  .viewernav { width: 34px; height: 60px; font-size: 20px; }
}

/* ----------------------------------------------------------- the pencil
   Hidden until the card is under the pointer, because a wall of galleries is
   something you look at far more often than something you edit. It stays put
   on touch, where there is no hover to reveal it. */

.facetedit {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 2;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
  color: var(--text-2);
  font-size: 13px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s, border-color 0.12s;
}
.facet:hover .facetedit { opacity: 0.92; }
.facetedit:hover { opacity: 1; border-color: var(--accent-hi); color: var(--text); }
.facetedit:focus-visible { opacity: 1; outline: 2px solid var(--accent-hi); }

@media (hover: none) {
  .facetedit { opacity: 0.92; }
}

/* ------------------------------------------------------- the edit dialog */

.galleryedit { max-width: 620px; }
.galleryedit label { display: block; margin-bottom: 14px; }
.galleryedit input[type="text"] { width: 100%; }

.croprow { display: flex; gap: 16px; align-items: flex-start; margin-bottom: 6px; }
.croprow > div:first-child { flex: none; }
.coverside { min-width: 0; flex: 1; display: grid; gap: 6px; }

/* The crop preview is the card's own square, at the card's own size, so what
   you set here is literally what you will see out there. */
.cropbox {
  position: relative;
  width: 148px;
  aspect-ratio: 1 / 1;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--panel-2);
  cursor: crosshair;
}
.cropbox img { width: 100%; height: 100%; object-fit: cover; display: block; }

.coverpick {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(52px, 1fr));
  gap: 5px;
  max-height: 172px;
  overflow-y: auto;
  padding-right: 4px;
}
.coverpick .shot { aspect-ratio: 1 / 1; opacity: 0.5; }
.coverpick .shot.on { opacity: 1; border-color: var(--accent-hi); }
.coverpick .shot:hover { opacity: 1; }

/* ------------------------------------------------- filling in a group
   Three stacked steps: the addresses to choose from, the box you can paste
   one into, then what Stash read back off it. The middle one is deliberately
   still there when candidates exist — the found list is a set of guesses and
   typing over it must never feel like fighting the page. */
.groupedit { max-width: 680px; }
.groupedit input[type="url"] { width: 100%; }

.urlchoices { margin-bottom: 14px; }
.urllist { display: grid; gap: 4px; max-height: 210px; overflow-y: auto; }

.urlchoice {
  display: flex;
  align-items: baseline;
  gap: 10px;
  width: 100%;
  padding: 6px 9px;
  text-align: left;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--text);
  cursor: pointer;
  font: inherit;
  font-size: 13px;
}
.urlchoice:hover { border-color: var(--accent-hi); }
.urllabel { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.urlwhy { flex: none; }

.scraped:empty { display: none; }
.scraped h3 { margin: 4px 0 10px; font-size: 14px; }

/* Both covers side by side. They are the fastest way to see the scraper found
   a different cut of the film than the one you hold. */
.covers { display: flex; gap: 12px; margin-bottom: 12px; }
.cover { display: grid; gap: 5px; justify-items: center; margin: 0; }
.cover img {
  width: 110px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel-2);
}

.scrapedfields { display: grid; gap: 7px; }
.scrapedrow {
  display: grid;
  grid-template-columns: auto 74px 1fr;
  gap: 9px;
  align-items: baseline;
  margin: 0 !important;
  font-size: 13px;
}
.scrapedval { min-width: 0; max-height: 5.4em; overflow-y: auto; line-height: 1.35; }

.galleryedit menu { display: flex; align-items: center; gap: 8px; padding: 0; margin: 14px 0 0; }
.galleryedit menu .spacer { margin-left: auto; }

.confirmrow { display: flex; align-items: center; gap: 8px; }

/* The delete dialog. A tick list of what goes, then the sentence that says it
   again — so the second press is read, not muscle memory. */
.sceneremove .removewhats { margin: 0 0 4px; }
.sceneremove .removeline {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin: 0 0 10px;
  cursor: pointer;
}
.sceneremove .removeline input { margin-top: 3px; }
.sceneremove .removewhat { display: block; }
.sceneremove .removeline .muted { display: block; margin-top: 2px; }
.sceneremove .filepath { margin: -4px 0 12px 26px; }
.sceneremove menu { flex-wrap: wrap; }
.sceneremove .confirmrow { flex-wrap: wrap; }
/* The sentence takes the line, the two buttons share the next one. */
.sceneremove .confirmrow .muted { flex: 1 1 100%; }
.confirmrow[hidden] { display: none; }

/* ------------------------------------------------------------- the ties
   Three rows of "what is this of": chips for what it already has, a box that
   asks Stash for the rest. The results sit over the dialog rather than pushing
   it about, so the row does not jump as you type. */

.ties { display: grid; gap: 10px; margin: 16px 0 4px; }

.tierow { display: flex; gap: 10px; align-items: flex-start; }
.tielabel { flex: none; width: 84px; padding-top: 7px; font-size: 12.5px; color: var(--text-2); }
.tiebody { position: relative; flex: 1; min-width: 0; }

.tiechips { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 5px; min-height: 22px; align-items: center; }
.tiechips .tagchip { display: inline-flex; align-items: center; gap: 5px; }

/* The × on a chip, wherever a chip is. Red on hover because it removes
   something; the same red, and the same ×, in all three places. */
.chipx {
  border: 0;
  background: none;
  padding: 0 2px;
  color: var(--muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.chipx:hover { color: var(--red-hi); }

.tiebody input[type="search"] { width: 100%; }

.tieresults {
  position: absolute;
  z-index: 3;
  left: 0;
  right: 0;
  top: 100%;
  margin-top: 3px;
  max-height: 210px;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line-hi);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.tieresults[hidden] { display: none; }

.tiehit {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  background: none;
  color: var(--text);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.tiehit:hover { background: var(--panel-2); }

/* ---------------------------------------------------- uploading your own
   A third source in the build panel, and the tools above a gallery's
   pictures. Both are plain rows: this is a file picker and two buttons, and
   dressing it up would only hide which of them writes to the disk. */

.uploadrow { margin-top: 12px; display: grid; gap: 6px; }
.uploadrow input[type="file"] { font-size: 12.5px; color: var(--text-2); max-width: 100%; }
.uploadrow input[type="file"]::file-selector-button {
  margin-right: 10px;
  padding: 6px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel-2);
  color: var(--text);
  cursor: pointer;
}
.uploadrow input[type="file"]::file-selector-button:hover { border-color: var(--accent-hi); }

.shottools { margin: 4px 0 0; flex-wrap: wrap; }

/* In select mode a click ticks the picture rather than opening it, so the
   grid says so: everything dims until it is picked. */
.shots.selecting .shot { opacity: 0.5; }
.shots.selecting .shot:hover { opacity: 0.8; }
.shots.selecting .shot.on { opacity: 1; border-color: var(--accent-hi); }
.shots.selecting .shot.on::before {
  content: "✓";
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 1;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: 12px;
}

/*
 * ============================================================== the search
 *
 * Controls at the top in one panel, results below.
 */

.searchpanel {
  display: grid;
  gap: 10px;
  padding: 16px;
  margin-bottom: 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}

.searchline { display: flex; gap: 8px; }
.searchline .bigsearch {
  flex: 1;
  padding: 10px 14px;
  font-size: 16px;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.searchline .bigsearch:focus { border-color: var(--accent-hi); outline: none; }
.searchline button {
  padding: 10px 18px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  cursor: pointer;
}
.searchline button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.searchline button.primary:hover { background: var(--accent-hi); border-color: var(--accent-hi); }

/* The three type-aheads, side by side and equal — performer, studio, tag are
   the same kind of question asked of three catalogues. */
.facetsearch { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.facetfield { position: relative; }
.facetinput {
  width: 100%;
  padding: 8px 12px;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.facetinput:focus { border-color: var(--accent-hi); outline: none; }

.facetmenu { display: none; }
.facetfield.open .facetmenu {
  display: block;
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 320px;
  overflow-y: auto;
  padding: 4px;
  background: var(--panel-2);
  border: 1px solid var(--line-hi);
  border-radius: var(--radius);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.5);
}
.facetrow {
  display: flex;
  gap: 8px;
  align-items: center;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--text);
  text-align: left;
  cursor: pointer;
}
.facetrow:hover { background: var(--panel); }
.facetrow img { width: 28px; height: 38px; object-fit: cover; border-radius: 4px; flex: none; }
.facedot { width: 28px; height: 38px; border-radius: 4px; background: var(--line); flex: none; }
.facename { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.facetnone { padding: 8px; color: var(--muted); font-size: 13px; }

/* The active filters. A chip is the filter itself, so removing one is on the
   chip and not in a menu somewhere else. */
.filterchips { align-items: center; }
.filterchips .chip { display: inline-flex; align-items: center; gap: 6px; }
.filterchips .chip.performer { border-color: var(--accent-hi); }
.filterchips .chip.studio { border-color: var(--blue); }
.filterchips .chip.tag { border-color: var(--line-hi); }
/* .chipx itself is up with the tie chips — one ×, one rule. */

.controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.controls .control {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  color: var(--muted);
  font-size: 13px;
}
.controls select,
.controls input[type="date"] {
  padding: 6px 8px;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 6px;
}
.controls .check { display: inline-flex; gap: 6px; align-items: center; font-size: 13px; color: var(--text-2); }

/* A performer or a studio in a result row is a filter, not decoration. */
.metalink { color: var(--text-2); border-bottom: 1px dotted var(--line-hi); }
.metalink:hover { color: var(--accent-hi); border-bottom-color: var(--accent-hi); }

/*
 * ------------------------------------------------------------ the percentage
 *
 * On the studio or performer it's about.
 */

.subject { margin-bottom: 18px; }
.subjectbody {
  display: grid;
  grid-template-columns: 92px 1fr auto;
  gap: 14px;
  align-items: center;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}
.subjectbody img,
.subjectbody .noposter {
  width: 92px;
  height: 128px;
  object-fit: cover;
  border-radius: 6px;
  background: var(--line);
}
.subjectbody h2 { margin: 0 0 2px; font-size: 20px; }

/* The pooled queue's subject is not a thing with a picture — it is every
   catalogue at once — so it takes the same panel without the poster column. */
.subject.pooled .subjectbody { grid-template-columns: 1fr; }
.subject.pooled .subjectbody p { margin: 4px 0 6px; }
/* Tracked state: an accent-wash label, not a button. Untrack is separate. */
.subjecttrack { display: grid; gap: 6px; justify-items: stretch; align-content: center; }

.trackedis {
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--accent-hi);
  background: var(--accent-wash);
  color: var(--text);
  font-size: 13px;
  text-align: center;
}

/* Quieter than the label above it: stopping is the rarer thing and the one
   with consequences, so it asks to be read rather than pressed in passing. */
.subjecttrack .trackstop { font-size: 12px; padding: 4px 10px; }
.subjecttrack .trackstop:hover { border-color: var(--red-hi); color: var(--red-hi); }

.coverage { display: grid; gap: 4px; margin-top: 8px; }
.coverage .pct { font-size: 22px; font-weight: 600; line-height: 1.1; }
.coverage .meta { display: flex; flex-wrap: wrap; gap: 10px; color: var(--muted); font-size: 12px; }

.tracked { display: grid; gap: 10px; }
.tracked .crow { cursor: default; }
.tracked .crow:hover { border-color: var(--line); }
.trackedactions { display: grid; gap: 6px; justify-items: stretch; align-content: center; }

/* The wild card sits apart because it is a different catalogue with a different
   route out — v2 rather than v3 — and blending it in would hide that. */
.wildcard { border-top: 1px solid var(--line); padding-top: 14px; margin-top: 22px; }

/* The indexers, under the wild card: rows rather than cards, because a release
   has a name and some numbers and no picture. */
.indexers { margin-top: 22px; }
.indexers .toolbar input[type=search] { flex: 1 1 260px; min-width: 0; }
.indexers .check { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; }
.releasefilter { flex: 1 1 260px; min-width: 0; }
.indexers .toolbar[hidden], .release[hidden] { display: none; }
.releases { display: grid; gap: 6px; }
.release {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 12px; align-items: center;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px;
}
.release .releasename { min-width: 0; overflow-wrap: anywhere; font-size: 13.5px; }
.release .releasename a { color: inherit; }
.release .releasemeta { grid-column: 1; display: flex; flex-wrap: wrap; gap: 10px; font-size: 12px; }
.release button { grid-column: 2; grid-row: 1 / span 2; }

@media (max-width: 720px) {
  .facetsearch { grid-template-columns: 1fr; }
  .subjectbody { grid-template-columns: 64px 1fr; }
  .subjectbody button { grid-column: 2; justify-self: start; }
}

/* Performer photos crop to a tall box; studio logos sit whole in a wide one. */
.tracked .crow { grid-template-columns: 96px 1fr auto; align-items: start; }
.tracked .crow img.art, .tracked .crow .noposter { width: 96px; border-radius: 8px; }
.tracked .crow img.performer, .tracked .crow .noposter.performer {
  width: 66px;
  height: 92px;
  object-fit: cover;
}
.tracked .crow img.studio, .tracked .crow .noposter.studio {
  height: 66px;
  object-fit: contain;
  background: var(--panel-2);
  padding: 6px;
}
.tracked .coverage .pct { font-size: 17px; }

.subjectbody img.performer, .subjectbody .noposter.performer {
  width: 92px;
  height: 128px;
  object-fit: cover;
}
.subjectbody img.studio, .subjectbody .noposter.studio {
  width: 128px;
  height: 92px;
  object-fit: contain;
  background: var(--panel-2);
  padding: 8px;
}
.subjectbody:has(img.studio), .subjectbody:has(.noposter.studio) { grid-template-columns: 128px 1fr auto; }

/*
 * ------------------------------------------------------- list or grid
 *
 * The grid's column width is a custom property the slider scales live.
 */

/* The head is baseline-aligned for its title and note; the tools are not text
   and sit centred, pushed to the far end. */
.feedhead .spacer { flex: 1; }
.viewtools { display: flex; align-items: center; gap: 10px; align-self: center; }
.viewswitch { display: inline-flex; }
.viewswitch .chip { border-radius: 0; margin: 0; }
.viewswitch .chip:first-child { border-radius: var(--radius) 0 0 var(--radius); }
.viewswitch .chip:last-child { border-radius: 0 var(--radius) var(--radius) 0; margin-left: -1px; }
.viewswitch .chip.on {
  background: var(--accent-wash);
  border-color: var(--accent-hi);
  color: var(--text);
}

.viewswitch.sizes .chip { min-width: 30px; padding-inline: 8px; text-align: center; }

/* Under the shelf's heading, saying which of its two shelves you are reading. */
.viewswitch.shelfviews { margin: 2px 0 12px; }

/* Four sizes by class; S drops the meta line. */
/* Sized off the app-wide tile scale. */
.resultgrid { grid-template-columns: repeat(auto-fill, minmax(var(--tile-lg), 1fr)); }
.resultgrid .cardstate { flex-wrap: wrap; }

@media (max-width: 720px) {
  .viewswitch.sizes { display: none; }
}

/*
 * ------------------------------------------------------------ studio page
 *
 * Logo, percentage, three views, cast as a filter.
 */

/* A logo is not a portrait. Contained and wide, or the crop takes the name off
   it — the same reasoning as the studio tiles. */
.listhead .portrait.logo {
  width: 190px;
  aspect-ratio: 16 / 9;
  object-fit: contain;
  padding: 10px;
}
.listhead .portrait.logo.noposter { background: var(--panel-2); }

/* The track button sits at the far end of the header, away from the numbers it
   turns on. */
.listhead.with-portrait > .add { margin-left: auto; align-self: flex-start; }

.studiobar { align-items: center; gap: 10px; }

/* The cast rail filters the shelf instead of navigating, so the one you picked
   has to look picked. */
.facet.on { border-color: var(--accent); }
.facet.on .facetbody .title { color: var(--accent-hi); }

.rail.cast { margin-top: 26px; }
.rail.cast .railtitle { font-size: 15px; font-weight: 600; color: var(--text-2); }

@media (max-width: 720px) {
  .listhead .portrait.logo { width: 140px; }
  .listhead.with-portrait > .add { margin-left: 0; }
}

/* The want mark on a scene card. Quiet until it is on: the card's own answer
   is the artwork, and a wall of lit buttons reads as noise. */
.chip.track.on {
  border-color: var(--accent);
  color: var(--text);
}
/* Add already claims the far end with an auto margin; the mark sits with the
   badges, which is where the reading is rather than the doing. */
.chip.track { padding: 3px 9px; font-size: 12px; }

/* The second half of the want list — what turned up. A smaller heading than
   the band's own, because it is a footnote to it rather than a new question. */
.feedhead.sub { margin-top: 26px; }
.feedhead.sub h3 { margin: 0; font-size: 14px; font-weight: 600; color: var(--text-2); }


/* Bulk skip buttons, under the queue (see renderDecide()). */
.decidebulk {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.decidebulk[hidden] { display: none; }
.decidebulk .chip { padding: 7px 14px; }

@media (pointer: coarse) {
  /* Spaced apart: both skip in bulk. */
  .decidebulk { gap: 14px; }
  .decidebulk .chip { min-height: 44px; }
}

/* The two id filters share a line; this is the breath between them so the
   second label does not read as belonging to the first control. */
.sortline .filtergap { margin-left: 10px; }

/* Why a row is ticked when nobody ticked it. Green like the fingerprint badge
   it sits beside: both are the page saying it is sure, for different reasons. */
.badge.looksame { background: rgba(46, 160, 67, 0.18); color: #7ee787; }
.badge.looksame[hidden] { display: none; }

/*
 * ---------------------------------------------------------------- job line
 *
 * Button and sentence on one line; the sentence wraps in its own column.
 * `min-width: 0` lets it shrink.
 */
.jobline { flex-wrap: nowrap; align-items: flex-start; }
.jobline > button { flex: none; }
.jobline > .muted {
  flex: 1;
  min-width: 0;
  /* Align the sentence's first line with the button label. */
  padding-top: 6px;
}

/* Narrow screens: stack. */
@media (max-width: 620px) {
  .jobline { flex-wrap: wrap; }
  .jobline > .muted { flex: 1 1 100%; }
}

/*
 * ----------------------------------------------------------------- job row
 *
 * Three job cards side by side, equal columns.
 */
.jobrow {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  margin-bottom: 18px;
}
/* Equal heights, content at the top. */
.jobrow > .searchpanel {
  margin-bottom: 0;
  align-content: start;
}

/* In a third of a row, the sentence goes under the button. */
.jobrow .jobline { flex-wrap: wrap; }
.jobrow .jobline > .muted { flex: 1 1 100%; padding-top: 0; }

/* Two up, then one, before the columns get narrower than a button. */
@media (max-width: 1000px) {
  .jobrow { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 660px) {
  .jobrow { grid-template-columns: 1fr; }
}

/* A press-twice button on Manage › Stash, armed: the next press deletes or overwrites. */
.jobline > button.danger { border-color: var(--red); background: var(--red); color: #fff; }

/* Monitored: what the Whisparrs are still looking for, one row each, with the
   Indexers band opening underneath the row it was asked from. */
.monitoredfilter { flex: 1 1 280px; min-width: 0; }
.monitored { display: grid; gap: 6px; }
.mrow { padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; }
.mrow .mhead { display: flex; align-items: center; gap: 12px; }
.mrow .mtitle { flex: 1; min-width: 0; overflow-wrap: anywhere; font-size: 13.5px; }
.mrow .mtitle a { color: inherit; }
.mrow .mmeta { display: flex; flex-wrap: wrap; gap: 10px; font-size: 12px; margin-top: 2px; }
.indexers.bare { margin-top: 10px; }
.monitoredheld { margin-top: 16px; }
.monitoredheld > summary { cursor: pointer; padding: 6px 0; }
/* ------------------------------------------------------------------ binge */

/*
 * Full-screen views get the whole page with no padding or page scroll.
 * Height set by fitFills() in util.js.
 */
main:has(.fillview) { max-width: none; margin: 0; padding: 0; }

/* The section strip keeps its padding. */
main:has(.fillview) .sections { padding: 12px 18px 0; }
.bingeframe { display: block; width: 100%; border: 0; background: var(--bg); }

/* -------------------------------------------------------------------- reel */

.reelwrap { position: relative; width: 100%; background: #000; }

/* One clip per screen; scroll-snap-stop stops a flick skipping several. */
.reel {
  height: 100%;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  overscroll-behavior: contain;
  scrollbar-width: none;
}
.reel::-webkit-scrollbar { display: none; }

/*
 * The slide reserves room for the bar and captions; the picture centres in
 * the rest, in every mode.
 */
.reelslide {
  position: relative;
  height: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  box-sizing: border-box;
  padding-top: var(--reel-bar, 52px);
  padding-bottom: var(--reel-cap, 150px);
}

/* With the captions off, the picture takes the room they were holding. */
.reel.noinfo .reelslide { padding-bottom: 76px; }
.reel.noinfo .reelover { display: none; }

/*
 * Two shapes: Reddit is cropped to 9:16 with the overlay on it; library
 * slides (16:9) keep the whole frame between the bar and captions.
 */
.reelframe {
  position: relative;
  height: 100%;
  max-width: 100%;
  overflow: hidden;
  background: #000;
}

.reelvid {
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #000;
  cursor: pointer;
}

/* --- the phone, for anything already shaped like one --------------------- */

.reel.crop-auto .reelslide.tall .reelframe { aspect-ratio: 9 / 16; }

/* contain: posted clips come in any shape. */
.reel.crop-auto .reelslide.tall .reelvid { object-fit: contain; }

/* On a screen narrower than 9:14, the frame is the screen. */
@media (max-aspect-ratio: 9 / 14) {
  .reel.crop-auto .reelslide.tall .reelframe { aspect-ratio: auto; width: 100%; }
}

/* --- the band, for the library ------------------------------------------- */

/* A column: bar space, picture, captions. */
.reel.crop-auto .reelslide.wide .reelframe {
  width: 100%;
  display: flex;
  flex-direction: column;
}

.reel.crop-auto .reelslide.wide .reelvid { flex: 1; min-height: 0; height: auto; }

/* Captions below the picture, not over it. */
/*
 * The caption is the slide's child in every mode; the frame's padding keeps
 * the picture clear of it.
 */

/* --- one shape for everything, when auto is not what you want ------------ */

/* Bounded both ways with the ratio deciding; `height: 100%` made 4:3 look like 9:16. */
.reel.crop-four .reelslide:not(.feed) .reelframe {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  margin: auto;
  display: block;
  aspect-ratio: 4 / 3;
}

/*
 * cover, not contain: a 16:9 clip contained in 4:3 looks the same as Fit.
 * Feeds ignore this mode (below).
 */
.reel.crop-four .reelslide:not(.feed) .reelvid { object-fit: cover; flex: none; height: 100%; }

/*
 * ------------------------------------------------------------- sideways
 *
 * Short screens (landscape phone, short window): the reserves took most of
 * the height, so the caption goes back over the picture and the reserves
 * come off. Keyed on height, not orientation.
 */
@media (max-height: 560px) {
  .reelslide,
  .reel.noinfo .reelslide {
    padding-top: 0;
    padding-bottom: 0;
  }

  /*
   * Over the picture here, so it gets a gradient. The 76px foot clears the
   * corner buttons.
   */
  .reelover {
    padding-top: 40px;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.55) 60%, transparent 100%);
  }

  /* The cast and the tag line go first: they are the least of what the caption
     says and the most of what it costs in height. */
  .reelover .reelcast,
  .reelover .reeltagline { display: none; }

  .reelover .reelmeta { gap: 2px; }

  /* The bar overlays too, and fades rather than sitting on a band. */
  .reelbar {
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0.8), transparent);
    border-bottom: 0;
  }
}

/*
 * ---------------------------------------------------- the feeds always fit
 *
 * RedGIFs and Reddit ignore the framing button: the clip keeps its own ratio.
 */
.reel .reelslide.feed .reelframe {
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: auto;
  margin: 0;
  display: flex;
  flex-direction: column;
}

.reel .reelslide.feed .reelvid,
.reel .reelslide.feed img {
  object-fit: contain;
  flex: 1;
  min-height: 0;
  width: 100%;
  height: auto;
}



/* The bar sits over the reel rather than above it: the clip gets the screen. */
/*
 * One row at every width. A three-column grid keeps the feed window
 * centred on the screen; the 1fr/0-min sides truncate instead of wrapping.
 */
.reelbar {
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.75), transparent);
  pointer-events: none;
}

.reelleft, .reelright {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  overflow: hidden;
}
.reelright { justify-content: flex-end; }

/* Tag picker as an icon over a native select whose closed state is blank. */
.reeltags { position: relative; display: inline-flex; flex: none; }
.reeltags svg {
  position: absolute;
  left: 9px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  pointer-events: none;
  color: var(--text-2);
}
.reelpick.tags { width: 58px; padding-left: 30px; padding-right: 6px; }
.reelpick.tags:disabled { opacity: 0.45; }
.reelbar > * { pointer-events: auto; }

.reelpick, .reelmute, .reelplugin {
  padding: 6px 11px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: rgba(20, 19, 20, 0.75);
  color: var(--text-2);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  text-decoration: none;
}
.reelpick:hover, .reelmute:hover, .reelplugin:hover { border-color: var(--line-hi); color: var(--text); }
.reelpick:disabled, .reelmute:disabled { opacity: 0.45; cursor: default; }

.reelplugin { margin-left: 10px; }

/* The caption belongs to the slide: pinned to the bottom and centred. */
.reelover {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Room at the foot for the button row, which lives inside here now. */
  padding-bottom: 76px;
  gap: 8px;
  padding-left: 18px;
  padding-right: 18px;
  padding-top: 18px;
  text-align: center;
  /* Below the picture rather than over it, so nothing is ever covered. */
  background: none;
  pointer-events: none;
}
.reelover a, .reelover button { pointer-events: auto; }

.reelmeta {
  min-width: 0;
  max-width: 640px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

/* The marker's own tag, which is the nearest thing a clip has to a name. */
.reeltag {
  pointer-events: auto;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--accent-wash);
  border: 1px solid var(--accent-hi);
  color: var(--accent-hi);
  font-size: 12px;
  letter-spacing: 0.02em;
}

.reeltitle {
  color: var(--text);
  font-size: 17px;
  font-weight: 600;
  text-decoration: none;
}
.reeltitle:hover { color: var(--accent-hi); }

.reelsub { font-size: 13px; color: var(--text-2); }
.reelcast { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; }

.reelactmark { font-size: 15px; line-height: 1.1; }

@media (max-width: 700px) {
  .reelover { flex-direction: column; align-items: stretch; gap: 12px; }
  .reelacts { flex-direction: row; margin-left: 0; }
}

/* ------------------------------------------------------------------ reddit */

.redditstatus { font-size: 12.5px; color: var(--muted); }

.redditgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tile-md), 1fr));
  gap: 16px;
}

.redditcard {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

.redditshot {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  overflow: hidden;
  background: var(--panel);
}
.redditshot:hover { border-color: var(--accent-hi); }
.redditshot img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* A post whose picture Reddit will not hand over without another request. */
.redditnone {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--muted);
  font-size: 13px;
}

.redditbadge {
  position: absolute;
  top: 8px;
  right: 8px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.72);
  border: 1px solid var(--line-hi);
  color: var(--text-2);
  font-size: 11px;
}

.redditwho { color: var(--text); font-size: 13.5px; font-weight: 600; text-decoration: none; }
.redditwho:hover { color: var(--accent-hi); }

.redditwhat {
  font-size: 12.5px;
  color: var(--text-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.redditwhen { font-size: 11.5px; color: var(--muted); }
.redditwhen a { color: var(--muted); }
.redditwhen a:hover { color: var(--accent-hi); }

/* A Reddit slide: same furniture, different-coloured chip. */
.reelfrom {
  background: rgba(47, 129, 247, 0.16);
  border-color: var(--blue);
  color: var(--blue);
}

/* A still does not play, so it does not offer to be clicked. */
.reelstill { cursor: default; }

.redditadd {
  flex: 1;
  min-width: 190px;
  max-width: 330px;
  padding: 6px 11px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--bg);
  color: inherit;
  font: inherit;
  font-size: 13px;
}
.redditadd:focus { outline: 2px solid var(--accent-hi); outline-offset: -1px; }

.redditnote { font-size: 12.5px; color: var(--red); }

.redditmine { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 16px; }

.redditdrop {
  margin-left: 6px;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
  padding: 0;
}
.redditdrop:hover { color: var(--red-hi); }


/* ------------------------------------------------- reel: hand controls */

/* Framing and rolling on the picture, in thumb reach. */
/*
 * One row of corner buttons from two halves: four the reel owns, two each
 * slide owns, in the same numbered slots.
 *
 *   roll  shuffle  O  ·····tag·····  open  sound  fit
 *     1      2     3                  3     2      1
 */
.reelwrap { --corner-in: 18px; --corner-step: 58px; }

.reelcorner {
  position: absolute;
  bottom: 18px;
  z-index: 3;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  min-width: 46px;
  height: 46px;
  padding: 0 12px;
  border-radius: 999px;
  border: 1px solid var(--line-hi);
  background: rgba(20, 19, 20, 0.72);
  backdrop-filter: blur(6px);
  color: var(--text-2);
  font: inherit;
  font-size: 10px;
  line-height: 1.1;
  text-decoration: none;
  cursor: pointer;
}
.reelcorner:hover { color: var(--text); border-color: var(--accent-hi); }
.reelcorner.on { color: var(--accent-hi); border-color: var(--accent-hi); }
.reelcorner:disabled { opacity: 0.5; cursor: default; }

/* The four that carry no second line read at their own size. */
.reelcorner.slot1, .reelcorner.slot2 { font-size: 13px; }

.slot1.left { left: var(--corner-in); }
.slot2.left { left: calc(var(--corner-in) + var(--corner-step)); }
.slot3.left { left: calc(var(--corner-in) + (2 * var(--corner-step))); }

.slot1.right { right: var(--corner-in); }
.slot2.right { right: calc(var(--corner-in) + var(--corner-step)); }
.slot3.right { right: calc(var(--corner-in) + (2 * var(--corner-step))); }
.slot4.left { left: calc(var(--corner-in) + (3 * var(--corner-step))); }
.slot4.right { right: calc(var(--corner-in) + (3 * var(--corner-step))); }

/* The gap between the clusters, padded so a long tag stops short of them. */
.reeltagline {
  position: absolute;
  inset: auto 0 18px 0;
  z-index: 2;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 calc(var(--corner-in) + (4 * var(--corner-step)));
  pointer-events: none;
}

/* A round icon button, for the bar. */
.reelicon {
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(20, 19, 20, 0.75);
  color: var(--text-2);
  font: inherit;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.reelicon:hover { color: var(--text); border-color: var(--line-hi); }

/* The scrub bar, thin. */
.reelscrub {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 2;
  height: 3px;
  background: rgba(255, 255, 255, 0.16);
  pointer-events: none;
}
.reelscrub i { display: block; height: 100%; width: 0; background: var(--accent-hi); }

/* Thicker while a thumb is on it, so it is obvious what is being dragged. */
.reelslide.scrubbing .reelscrub { height: 7px; }
.reelslide.scrubbing { cursor: ew-resize; }

/* A sideways drag scrubs rather than scrolls. */
.reelslide { touch-action: pan-y; }

/* Narrow screens: less padding, one line of title, no cast. */
@media (max-width: 620px) {
  .reelover { padding: 12px 12px 16px; gap: 9px; }
  .reelmeta { gap: 3px; }
  .reeltitle {
    font-size: 15px;
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .reelsub { font-size: 12px; }

}

/* One row: pickers truncate; the gear keeps its size. */
.reelbar { flex-wrap: nowrap; }

.reelbar .reelpick {
  flex: 1 1 0;
  min-width: 0;
  max-width: 190px;
  text-overflow: ellipsis;
}

.reelbar .reelicon { flex: 0 0 auto; }

@media (max-width: 620px) {
  .reelbar { gap: 6px; padding: 8px 10px; }
  .reelbar .reelpick { font-size: 12px; padding: 5px 6px; }
  .reelex { display: none; }
  /* On a phone the tag goes back into the captions, above the title. */
  .reeltagline {
    position: static;
    order: -1;
    height: auto;
    padding: 0;
    pointer-events: auto;
  }

  .reelwrap { --corner-in: 10px; --corner-step: 46px; }
  .reelcorner { bottom: 12px; min-width: 40px; height: 40px; padding: 0 8px; }
  .reelcorner .reelglyph { width: 17px; height: 17px; }
  .reeltagline { bottom: 12px; height: 40px; }
  .reelover { padding-bottom: 62px; }
  .reel.noinfo .reelslide { padding-bottom: 64px; }
}


/* A tag being kept out of the reel, with the way to put it back. */
.reelex {
  display: inline-flex;
  align-items: center;
  padding: 5px 8px 5px 11px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(20, 19, 20, 0.75);
  color: var(--muted);
  font-size: 12px;
  text-decoration: line-through;
}

/* A decision already made reads as a state, not as another thing to press. */
.badge.ignored { background: var(--panel-2); color: var(--muted); border: 1px solid var(--line); }
.chip.ignore:hover { border-color: var(--muted); color: var(--text); }
.chip.track.on { background: var(--accent-wash); border-color: var(--accent-hi); color: var(--text); }

/* "Nothing decided yet" is an em dash, not a nought — see catalogue.js. */
.coverage .pct.none { color: var(--muted); }

.controls .spacer { flex: 1; }

/* ------------------------------------------------- reel: settings */

/* Line-art icons on currentColor, so they follow the button's state. */
.reelglyph {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  display: block;
  pointer-events: none;
}

.reelicon .reelglyph { margin: auto; }
.reelcorner .reelglyph { width: 20px; height: 20px; }

.reelsettings {
  width: min(480px, calc(100vw - 32px));
  padding: 22px 24px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
}
.reelsettings::backdrop { background: rgba(0, 0, 0, 0.6); }
.reelsettings h2 { margin: 0 0 6px; font-size: 17px; }
.reelsettings p { margin: 0 0 18px; font-size: 12.5px; }

/* Icon, name, slider, number — one grid so the four columns line up. */
.reelset {
  display: grid;
  grid-template-columns: 34px 1fr minmax(0, 150px) 44px;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.reelsetlib { border-top: 0; }

.reelseticon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--text-2);
}
a.reelseticon:hover { color: var(--accent-hi); border-color: var(--accent-hi); }

.reelsetname { font-size: 13.5px; min-width: 0; }
.reelsetname a { color: var(--text); text-decoration: none; }
.reelsetname a:hover { color: var(--accent-hi); }
.reelsetname .muted { font-size: 12px; }

.reelset input[type="range"] { width: 100%; accent-color: var(--accent); }
.reelset b { text-align: right; font-size: 13px; color: var(--text-2); }

.reelsettings menu { display: flex; justify-content: flex-end; gap: 8px; margin: 18px 0 0; padding: 0; }

/* ------------------------------------------------- the top bar, narrow */

/*
 * The header on a phone: below 760px the search gets its own line and the
 * nav scrolls sideways; below 520px the brand keeps only its mark.
 */
@media (max-width: 760px) {
  .topbar {
    flex-wrap: wrap;
    gap: 10px 12px;
    padding: 8px 12px;
  }

  /* Second row, full width: a search box you cannot type a title into is
     not worth the room it saves. */
  .search {
    order: 3;
    flex: 1 0 100%;
    max-width: none;
  }

  .topnav {
    order: 2;
    gap: 2px;
    /* Two items now, so no scrolling — and a scroller would clip the menu. */
    margin-left: auto;
  }

  .topnav a, .topnav button, .navmenu > summary {
    padding: 7px 9px;
    font-size: 13px;
    white-space: nowrap;
  }

  /* The status line scrolls too, rather than stacking four lines deep. */
  .statusbar {
    gap: 10px;
    padding: 0 12px;
    overflow-x: auto;
    white-space: nowrap;
    scrollbar-width: none;
  }
  .statusbar::-webkit-scrollbar { display: none; }
  .statusbar span { flex: 0 0 auto; }

  /* Section strips are the same shape of problem. */
  .sections {
    overflow-x: auto;
    white-space: nowrap;
    scrollbar-width: none;
  }
  .sections::-webkit-scrollbar { display: none; }
  .sections .section { flex: 0 0 auto; }

  main { padding: 16px 12px 48px; }
  .toolbar { flex-wrap: wrap; }
}

/*
 * Below 620px the brand goes, leaving nav and search in two rows.
 *
 * Don't scale the image instead: .brandmark img's negative offsets crop
 * the wordmark, and any max-width slices a letter off.
 */
@media (max-width: 620px) {
  /* The Feed stays. */
  .brand { display: none; }
}

@media (max-width: 760px) {
  /* The name goes and the pill keeps only the Feed. */
  .brandtag { display: none; }
  .brandblock { gap: 8px; }
}

/* The one setting here that is a yes/no rather than an amount. */
.reelsetplay { cursor: pointer; }
.reelsetplay input[type="checkbox"] { width: 17px; height: 17px; margin: 0 0 0 auto; accent-color: var(--accent); }

/* The clip renderer's own row: a status line and the way to prod it. */
.reelsetclips { grid-template-columns: 34px 1fr auto; }
.reelsetclips button { white-space: nowrap; }
.reelsetclips .reelsetname { line-height: 1.35; }
.reelsetnote { font-size: 12px; color: var(--muted); margin-top: 2px; }

/*
 * ============================================================== Import
 *
 * Overview, Images and Integrations. Shared pieces reuse .crow, .feed and .chip.
 */

/* The summary. Four numbers, and the one that is a job rather than a fact is
   the one that lights up. */
.figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  margin-bottom: 10px;
}
.figure {
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}
a.figure:hover { border-color: var(--accent-hi); }
.figure.loud { border-color: var(--accent-hi); background: var(--accent-wash); }
.fignum { font-size: 26px; font-weight: 600; line-height: 1.15; }
.figlabel { color: var(--muted); font-size: 12.5px; margin-top: 2px; }
.behindline { margin-top: 4px; }

/* Integrations. Three lamp states: up, down, not set up. */
.services { display: grid; gap: 8px; }
.srow {
  display: grid;
  grid-template-columns: 12px 1fr;
  align-items: start;
  gap: 12px;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}
.srow .title { font-weight: 600; }
.srow .meta { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.srow .meta span + span::before { content: " · "; }

.lamp { width: 10px; height: 10px; border-radius: 50%; margin-top: 6px; background: var(--muted); }
.lamp.good { background: var(--green); }
.lamp.bad { background: var(--red); }
.lamp.off { background: var(--line-hi); }

/* A pipeline step leads with its count, because the count is the question. */
.prow { grid-template-columns: 68px 1fr auto; }
.prow .fignum { font-size: 22px; text-align: right; }
.prow.done { opacity: 0.65; }
.prow .meta.stalled span { color: var(--amber); }

.kindline { margin-bottom: 10px; align-items: center; gap: 12px; }
.facet.grow { flex: 1; }
.facet.grow .bigsearch { width: 100%; }

/*
 * --------------------------------------------------------- match & sort
 *
 * Candidates grow in place in the row, not in a dialog.
 */

.matchrow { align-items: start; }
/* 176 wide: these rows are judged by the picture. */
/* Scoped under .tracked to beat `.tracked .crow`'s 96px picture. */
.tracked .matchrow img.art { width: 176px; height: 99px; object-fit: cover; border-radius: 6px; }

/* A tick, the picture, the scene, the actions. .crow's own three columns do not
   fit a row that can be chosen as well as read. */
.tracked .matchrow { grid-template-columns: auto 176px 1fr auto; }
.tracked .matchrow .rowpick { align-self: start; padding-top: 4px; }

/* Search All and find/replace. Sticky so Stop stays reachable. */
.bulkbar { position: sticky; top: 8px; z-index: 5; }
.bulkbar .facetinput { width: auto; min-width: 170px; }
.bulkbar .gapbox { min-width: 0; width: 80px; }
.bulkbar .add.danger { border-color: var(--red); background: var(--red); color: #fff; }
.tracked .matchrow img.art { background: var(--panel-2); cursor: zoom-in; }
.tracked .matchrow img.art.cutting { opacity: 0.4; }
.matchrow.matched { border-color: var(--green); opacity: 0.7; }

/* How the filename was read: dimmer, since it's a reading, not a record. */
.matchrow .asread { opacity: 0.75; font-style: italic; }

/* Why the finds were guessing. Above the bulk bar and not sticky: it is read
   once, acted on once, and then it takes itself away. */
.phashbar { border-color: var(--amber); }

/* Selected source chips (`.chip.on` was only styled inside .viewswitch). */
.sourceline .chip.on {
  background: var(--accent-wash);
  border-color: var(--accent-hi);
  color: var(--text);
}
.sourceline .chip.on::before { content: '✓ '; opacity: 0.7; }

/* What a find writes, under who it asks. Same chips. */
.fieldline { gap: 8px; align-items: center; margin-top: 8px; flex-wrap: wrap; }
.fieldline .chip.on {
  background: var(--accent-wash);
  border-color: var(--accent-hi);
  color: var(--text);
}
.fieldline .chip.on::before { content: '✓ '; opacity: 0.7; }

/* File all: last on the page, not sticky. */
.fileallbar { margin-top: 12px; }

.idpicks { display: grid; gap: 6px; margin-top: 8px; }
/* The candidate: picture (same size as the scene's), text, tick at the right edge. */
.idpick {
  display: grid;
  /* Picture 88–176 wide, so the title keeps room in a narrow column. */
  grid-template-columns: minmax(88px, 176px) minmax(120px, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-2);
}
.idpick .title { font-weight: 600; font-size: 14px; }
.idpick .meta { font-size: 12px; color: var(--muted); }
.idpick .meta span + span::before { content: ' | '; }
.idpick.on { border-color: var(--accent-hi); background: var(--panel); }

.idpick .pickart {
  width: 100%;
  max-width: 176px;
  aspect-ratio: 16 / 9;
  height: auto;
  object-fit: cover;
  border-radius: 6px;
  background: var(--panel);
}
/* A source with no artwork still takes the column: a row that shuffled left
   would break the alignment the whole band is read down. */
.idpick .pickart.none {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--line);
  min-height: 50px;
}

.idpickbody { display: grid; gap: 3px; min-width: 0; }
.idpickbadges { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-top: 2px; }

/* Right up against the edge, and big enough to be a target rather than a
   detail — this tick is the entire decision the row exists to record. */
.idpicktick { justify-self: end; padding-right: 2px; }
.idpicktick input { width: 20px; height: 20px; cursor: pointer; }
.controls.fields { margin: 8px 0 4px; gap: 12px; }

/* The pile panel: three lines for three decisions. */
.sortline, .sourceline { gap: 8px; align-items: center; margin-top: 8px; }
.sourceline { flex-wrap: wrap; }

/* .facetinput is full width by default — right in a facet column, wrong on a
   line of three controls that are meant to sit beside each other. */
.sortline .facetinput { width: auto; min-width: 180px; }
.sortline input[type="search"] { flex: 1; min-width: 220px; }
.sourceline .scraperpick { width: auto; max-width: 220px; }

/* One band per source. */
.bands { display: grid; gap: 10px; margin-top: 8px; }
.bandhead { margin-bottom: 4px; }
.sourceband .idpicks { margin-top: 0; }
.applyline { gap: 10px; align-items: center; }

/* The generated keyword sits beside Find it, because retyping it is the work. */
.matchactions { display: flex; gap: 8px; align-items: center; }
.matchactions .termbox { width: 220px; }
@media (max-width: 760px) {
  .matchactions { flex-direction: column; align-items: stretch; }
  .matchactions .termbox { width: auto; }
}

/* Ticking a card is the whole interaction, so an unticked one recedes. */
.card.taggable { cursor: pointer; }
.card.taggable.on { outline: 2px solid var(--accent-hi); outline-offset: -2px; }
/* The folder the file is in, under the date and studio. One line, cut short
   with an ellipsis; the whole path is the hover. */
.cardfolder {
  margin-top: 4px;
  font-size: 11.5px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cardfolder b { color: var(--text-2); font-weight: 600; }
.tagbar { position: sticky; top: 8px; z-index: 5; }
.chip.tag { border-color: var(--line-hi); }

/* ------------------------------------------------------------- thanks */
.thanks { display: grid; gap: 8px; }
.thanksrow { grid-template-columns: 1fr auto; align-items: center; cursor: default; }
.thanksrow:hover { border-color: var(--line); }
.thanksrow .title { font-weight: 600; }
.thanksrow .title:hover { color: var(--accent-hi); }
.thanksrow .meta { font-size: 13px; color: var(--muted); margin-top: 3px; line-height: 1.45; }
.thanksfoot { margin: 26px 0 40px; max-width: 62ch; }

/* Sits at the end of the status strip, which already says what this is
   standing on — which Whisparr, whose token. */
.thankslink { color: var(--muted); border-bottom: 1px dotted var(--line-hi); }
.thankslink:hover { color: var(--accent-hi); border-bottom-color: var(--accent-hi); }

/*
 * ------------------------------------------------------- the group builder
 *
 * A proposal: poster plus the list of matched scenes.
 */
.proposals { display: grid; gap: 14px; }

.proposal {
  display: grid;
  grid-template-columns: 108px 1fr;
  gap: 16px;
  padding: 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.proposal > img.art, .proposal > .noposter {
  width: 108px;
  aspect-ratio: 0.71;
  object-fit: cover;
  border-radius: 8px;
  background: var(--panel-2);
}
.proposalbody { display: grid; gap: 8px; align-content: start; }
.proposalbody .title { font-weight: 600; font-size: 15px; }
.proposalbody .meta { font-size: 12.5px; color: var(--muted); }
.proposalbody .meta span + span::before,
.proposalbody .meta a + a::before { content: " · "; color: var(--muted); }

/* The scenes themselves: tickable, because "right film, wrong third scene" has
 * to be sayable before the group exists rather than undone after. */
.heldrows { display: grid; gap: 4px; }

.heldrow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 8px;
  border-radius: 8px;
  background: var(--panel-2);
  cursor: pointer;
}
.heldrow:hover { outline: 1px solid var(--line-hi); }
.heldrow img.thumb { object-fit: cover; }
.heldrow .thumb.blank { display: block; flex: none; }
.heldrow .muted { margin-left: auto; text-align: right; }

/* A missing scene is a row with three answers on the end of it, so it stops
 * being a flex line and becomes a grid — otherwise the buttons wander. */
.heldrow.missing {
  display: grid;
  grid-template-columns: 84px 1fr auto;
  align-items: start;
  cursor: default;
  padding: 8px;
}
.heldrow.missing:hover { outline: none; }
.heldrow.missing .trackedactions { grid-auto-flow: column; gap: 6px; align-items: center; }

.ambiguous {
  display: grid;
  gap: 4px;
  padding: 8px;
  border-radius: 8px;
  border: 1px dashed var(--line-hi);
}

.chipselect { max-width: 420px; }

/* The primary button here sits in a panel rather than in a row, so it does not
 * inherit the .crow rule the rest of the acquisition side leans on. */
.searchpanel button.add,
.proposalbody button.add {
  padding: 6px 13px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
}
.searchpanel button.add:hover,
.proposalbody button.add:hover { border-color: var(--accent-hi); }
.searchpanel button.add:disabled,
.proposalbody button.add:disabled { opacity: 0.4; cursor: default; }

/* Said plainly, in the same two colours the statusbar uses. */
.searchpanel .good, .proposal .good { color: var(--green); }
.searchpanel .bad, .proposal .bad { color: var(--red); }

/*
 * ------------------------------------------------------- the reel's feeds
 *
 * The feed switcher over the clip, so it stays reachable while scrolling on a phone.
 */
/* Three equal slots with the mark in the middle, so the control keeps its size. */
.reelfeeds {
  --slot: 86px;
  position: relative;
  flex: none;
  width: calc(var(--slot) * 3);
  overflow: hidden;
  padding: 3px;
  border-radius: 999px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  /* Fades the labels out at both ends rather than cutting them off square. */
  mask-image: linear-gradient(to right, transparent, #000 18%, #000 82%, transparent);
}

/* Drawn once, never moved. */
.reelfeedmark {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 50%;
  width: calc(var(--slot) - 6px);
  transform: translateX(-50%);
  border-radius: 999px;
  background: var(--accent);
  pointer-events: none;
}

/* The labels, slid so the current one sits under the mark (--at from the script). */
.reelfeedtrack {
  display: flex;
  transform: translateX(calc(var(--slot) * (1 - var(--at, 0))));
  transition: transform 0.22s ease;
}

.reelfeed {
  flex: none;
  width: var(--slot);
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 12px;
  padding: 6px 4px;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: center;
  position: relative;
  z-index: 1;
  transition: color 0.22s ease;
}
.reelfeed:hover { color: var(--text-2); }
.reelfeed.on { color: #fff; }

/* On a phone the labels are the only thing that would push this wider than the
   screen, so they shrink rather than wrap. */
/* On a phone the strip scrolls sideways instead of wrapping. */
@media (max-width: 640px) {
  .reelfeed { font-size: 11px; }
}

/*
 * ------------------------------------------------------- the reel, full screen
 *
 * .reelfull hides the header and status strip; the reel has its own close.
 */
body.reelfull .topbar,
body.reelfull .statusbar { display: none; }

body.reelfull main { max-width: none; margin: 0; padding: 0; }
body.reelfull .reelwrap { height: 100vh; height: 100dvh; }

/*
 * In the bar's right zone, not floating: floating made the padding
 * asymmetric and the centre column 21px off.
 */
.reelclose {
  flex: none;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: rgba(20, 19, 20, 0.72);
  backdrop-filter: blur(8px);
  color: var(--text-2);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.reelclose:hover { color: var(--text); border-color: var(--line-hi); }

/* On a phone the bar wraps: feeds on one row, tag control on the next. */
@media (max-width: 640px) {
  .reelbar { gap: 6px; padding-left: 8px; }
  .reelfeeds { --slot: 74px; }
}

/* ------------------------------------------------------------- parameters */

/* Settings page. The connections form is the app's existing node. */
.params { display: flex; flex-direction: column; gap: 22px; }

.params fieldset {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  padding: 18px 20px 20px;
  margin: 0;
}

.params legend {
  padding: 0 8px;
  font-size: 13.5px;
  font-weight: 600;
}

.params h2 { margin: 0; }

/* Name and hint on the left, the control on the right, value last. */
.paramrow {
  display: grid;
  grid-template-columns: 1fr minmax(0, 190px) 46px;
  align-items: center;
  gap: 14px;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
}
.paramrow:last-of-type { border-bottom: 0; }

.paramname { display: flex; flex-direction: column; gap: 2px; min-width: 0; font-size: 14px; }
.paramname .muted { font-size: 12px; }
.paramname a { color: var(--text); text-decoration: none; }
.paramname a:hover { color: var(--accent-hi); }

.paramctl input[type="range"] { width: 100%; accent-color: var(--accent); }
.paramrow b { text-align: right; font-size: 13px; color: var(--text-2); }
.paramrow input[type="checkbox"] { width: 17px; height: 17px; margin-left: auto; accent-color: var(--accent); }
.paramrow button { justify-self: end; white-space: nowrap; }

@media (max-width: 620px) {
  .paramrow { grid-template-columns: 1fr auto; }
  .paramctl { grid-column: 1 / -1; }
}

/* One tag control instead of two, so it earns a little more room. */
.reelpick.wide { min-width: 150px; }

/* The tag the reel is narrowed to, as against the ones kept out. Same chip,
   opposite meaning, so the accent is what tells them apart. */
.reelex.only { border-color: var(--accent-hi); color: var(--text); }


/* The nightly release (release.mjs), one line above the want list. */
.releasebar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 12px;
  padding: 8px 12px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: 13px;
}

.releasenum {
  width: 56px;
  padding: 4px 6px;
  border-radius: 6px;
  border: 1px solid var(--line-hi);
  background: var(--panel-2);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  text-align: right;
}
.releasenum:focus { outline: none; border-color: var(--accent-hi); }

.releasebar .chip { padding: 4px 11px; }

@media (pointer: coarse) {
  .releasebar { gap: 12px; }
  .releasenum { min-height: 38px; width: 64px; }
  .releasebar .chip { min-height: 38px; }
}

/*
 * ============================================== what you track, as a wall
 *
 * Performers, studios, tags, each a grid capped at three rows (see
 * renderTracked() in import/tracked.js). auto-fill; tracked.js reads the
 * column count back.
 */

.tracksection { margin-bottom: 22px; }
.tracksection:last-child { margin-bottom: 0; }

.trackgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
  margin-bottom: 10px;
}

.trackcard {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color 0.16s, transform 0.16s;
}
.trackcard:hover { border-color: var(--accent-hi); transform: translateY(-2px); }

/* Portrait for people, wide for studio logos, square for tags. */
.trackart {
  width: 100%;
  border-radius: var(--radius);
  background: var(--panel-2);
  object-fit: cover;
  display: block;
}
.trackart.performer { aspect-ratio: 2 / 3; }
.trackart.studio { aspect-ratio: 16 / 9; object-fit: contain; }
.trackart.tag { aspect-ratio: 16 / 9; }

/* No picture, so the initial stands in for one. */
.trackart.noart {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: 26px;
  font-weight: 700;
}

.trackbody { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.trackbody .title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The catalogue too big to be a percentage of — see coverageSnapshot. */
.trackloose { display: flex; align-items: baseline; gap: 2px; }
.trackwhy { cursor: help; opacity: 0.7; }

/* Untrack: its own corner target, shown on hover. */
/* The want list's toggle, at the end of its heading. */
.wantonly { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-2); }

@media (pointer: coarse) {
  .wantonly { min-height: 40px; }
}

/*
 * ------------------------------------------------------------- wild card
 *
 * Ways of asking at the top, answers in the middle, the record at the bottom.
 */

.wchead .crow { grid-template-columns: 176px 1fr auto; align-items: start; }
.wchead .art { width: 176px; height: 99px; object-fit: cover; border-radius: 6px; }
.wcpick .art { width: 128px; height: 72px; object-fit: cover; border-radius: 6px; }
.tracked .wcpick { grid-template-columns: 128px 1fr auto; align-items: center; }

/* A textarea, not an input: these arrive several at a time, pasted out of the
   tabs you already had open. */
.wcurls { width: 100%; min-height: 64px; font: inherit; font-size: 13px; resize: vertical; }

/* Every answer, failures included. */
.wccontriblist { display: grid; gap: 8px; }
.wccontrib {
  display: grid;
  grid-template-columns: 130px 176px 1fr;
  gap: 12px;
  align-items: center;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-2);
}
.wccontrib { grid-template-columns: 130px 176px 1fr auto; }
.wccontrib.bad { grid-template-columns: 130px 1fr; opacity: 0.7; border-style: dashed; }
.wcfrom { font-weight: 600; font-size: 13px; color: var(--muted); }
.wcart { width: 176px; height: 99px; object-fit: cover; border-radius: 6px; background: var(--panel); }
.wccontrib .title { font-weight: 600; font-size: 14px; }
.wccontrib .meta { font-size: 12px; color: var(--muted); }
.wccontrib .meta span + span::before { content: ' | '; }

/* One row per field, every answer along it. */
.wcfieldlist { display: grid; gap: 6px; }
.wcrow {
  display: grid;
  grid-template-columns: 130px 1fr;
  gap: 12px;
  align-items: start;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-2);
}
/* A changed field, marked on the row. */
.wcrow.picked { border-color: var(--accent-hi); }
.wclabel { font-weight: 600; font-size: 13px; }
.wcopts { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }

/* An option is its source and its value, stacked, because the source is how
   you tell two identical-looking dates apart. */
.wcopt {
  display: grid;
  gap: 2px;
  text-align: left;
  max-width: 100%;
  padding: 6px 10px;
  border-radius: 8px;
  white-space: normal;
}
.wcoptfrom { font-size: 11px; color: var(--muted); }
.wcoptval { font-size: 13px; color: var(--text); overflow-wrap: anywhere; }
.wcopt.on { background: var(--accent-wash); border-color: var(--accent-hi); }
.wcopt.on .wcoptfrom { color: var(--text); }
.wcoptart { width: 120px; height: 68px; object-fit: cover; border-radius: 4px; }

.wcsave { margin-top: 12px; }

/* Frames to search by. A strip you scan across rather than a grid you read
   down — you are looking for the one that shows a face. */
.wcframes { display: flex; gap: 8px; overflow-x: auto; padding: 4px 0 8px; }
.wcframetile { flex: 0 0 auto; display: grid; gap: 4px; justify-items: center; }
.wcframe {
  width: 224px;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--panel);
}
.wcframetile .controls { gap: 6px; align-items: center; }
.wcframetile .chip.small { font-size: 12px; padding: 3px 9px; }
/* The lenses are links, not buttons — they go somewhere, and a chip that
   looked like the page's own controls would be claiming otherwise. */
.sourceline a.chip { text-decoration: none; }
.sourceline a.chip:hover { border-color: var(--accent-hi); color: var(--text); }

/* The rename offer. Under the path it is about, and quiet until pressed — it
   is the last thing you do to a row, not one of the things you scan. */
.renamebit { gap: 8px; align-items: center; margin-top: 4px; }
.renamebit .chip.danger { border-color: var(--red); color: var(--red); }
.renamebit .chip.danger:hover { background: var(--red); color: #fff; }

/* A film answer, marked in place. */
.wccontrib.film { border-left: 3px solid var(--amber); }

/*
 * -------------------------------------------------------- catalogue overview
 *
 * Every number links to what clears it.
 */

.catjobs { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; margin-top: 8px; }
.catjob {
  display: grid;
  gap: 2px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel-2);
  text-decoration: none;
  color: inherit;
}
.catjob:hover { border-color: var(--accent-hi); }
/* The count is the button. There is exactly one thing you would want to do
   having read it, so it does not get a button beside it saying so. */
.catcount { font-size: 26px; font-weight: 700; line-height: 1.1; color: var(--text); }
.catlabel { font-weight: 600; font-size: 13px; }
/* Empty piles stay, dimmed, so the row doesn't jump. */
.catjob.clear { opacity: 0.55; }
.catjob.clear .catcount { color: var(--green, var(--muted)); }

.cattablewrap { overflow-x: auto; margin-top: 8px; }
.cattable { border-collapse: collapse; width: 100%; font-size: 13px; }
.cattable th, .cattable td { text-align: right; padding: 6px 10px; border-bottom: 1px solid var(--line); white-space: nowrap; }
.cattable th:first-child, .cattable td:first-child { text-align: left; }
.cattable th { color: var(--muted); font-weight: 600; }
.cattable code { font-size: 12px; color: var(--text); }
.cattable tbody tr:last-child td { border-bottom: 0; }

/* Set-aside control under the path. The row stays until the next read. */
.asidebit { gap: 8px; align-items: center; margin-top: 4px; }
.asidebit .chip.on { background: var(--accent-wash); border-color: var(--accent-hi); color: var(--text); }
.matchrow.setaside { opacity: 0.55; border-style: dashed; }

/*
 * ------------------------------------------------------------- the index sheet
 *
 * Everything in play at one size, inline.
 */
.cmpsheet { margin-top: 10px; border-color: var(--accent-hi); }
.cmplabel { font-weight: 600; font-size: 12px; color: var(--muted); margin: 10px 0 4px; }

/*
 * Tiles at 380px or wider, sharing out the rest: three or four across on a
 * full window, one on a narrow one.
 */
/* Spans all four columns of a match row. */
.cmpslot { grid-column: 1 / -1; }
.cmpslot:empty { display: none; }

.cmpstrip {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 12px;
}
.cmptile { display: grid; gap: 4px; min-width: 0; }

/* Two classes deep to beat `.crow img`'s 56px square (width and height). */
.cmpsheet img.cmpart {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--panel);
}
.cmpname { font-size: 13px; overflow-wrap: anywhere; }
/* Clicking a candidate ticks it. Deciding by looking and recording the decision
   should not be two places on the screen. */
.cmptile.pickable { cursor: pointer; }
.cmptile.pickable:hover .cmpart { border-color: var(--accent-hi); }
.cmptile.on .cmpart { border-color: var(--accent-hi); border-width: 2px; }
.cmptile.on .cmpname::before { content: '✓ '; color: var(--accent-hi); }

/* The measurement, in words. The number is in the tooltip — the word is what
   you act on, and 12 of 64 means nothing without the calibration behind it. */
.cmpnote { font-size: 13px; }
.cmpnote.same   { color: var(--green, var(--accent-hi)); font-weight: 600; }
.cmpnote.close  { color: var(--accent-hi); }
.cmpnote.unsure { color: var(--amber); }
.cmpnote.far    { color: var(--muted); }


/*
 * ------------------------------------------------- wild card: your own answer
 *
 * The picker sits under the row's options.
 */
.wcownline { gap: 6px; align-items: center; flex-wrap: wrap; margin-top: 6px; }
.wcownline .wcadd { width: auto; max-width: 260px; }
.wcaddwrap { display: inline-flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* Cover control: file chooser and address wrap on narrow screens. */
.wcartbox { max-width: 100%; row-gap: 6px; }
.wcartbox input[type="file"] { font-size: 12px; max-width: 100%; }
.wcartbox input[type="url"] { min-width: 220px; flex: 1 1 220px; }

/* Taking one back off a list you built. Quieter than the option chips, because
   removing is not a choice between answers — it is an edit to one. */
.wcdrop { font-size: 0.85em; opacity: 0.85; }
.wcdrop:hover { opacity: 1; border-color: var(--accent-hi); }

/*
 * ------------------------------------------------- a row's picture, alive
 *
 * Match and Wild Card row pictures get a positioned parent for the preview
 * and scrub overlays (see liveArt() in catalogue/liveart.js). The parent
 * takes over sizing; each `img.art` rule is a class deeper than the one it
 * overrides.
 *
 * No rule for `.wcpick`: it already draws at 176 via `.tracked .matchrow`.
 */
.rowart {
  position: relative;
  overflow: hidden;
  border-radius: 6px;
  flex: none;
  background: var(--panel-2);
}
.rowart img.art { display: block; width: 100%; height: 100%; object-fit: cover; border-radius: 0; }

.tracked .matchrow .rowart { width: 176px; height: 99px; }
.tracked .matchrow .rowart img.art { width: 100%; height: 100%; }

.wchead .rowart { width: 176px; height: 99px; }
.wchead .rowart img.art { width: 100%; height: 100%; }


/* The strip shows itself when the picture is pointed at, the same rule the
   tiles follow — an always-on bar across a pile is a pattern, not a control. */
.rowart:hover .tilescrub { opacity: 1; }

/* Generate's progress, under the button so the column doesn't shift. */
.genbit { display: flex; flex-direction: column; align-items: stretch; gap: 3px; }
.genbit .muted:empty { display: none; }


/*
 * ------------------------------------------------- match: deleting from a row
 *
 * Delete options inline, between the two presses.
 */
/*
 * A flex column so `margin-top: auto` pushes Delete to the foot (in a
 * packed grid an auto margin has no space to take).
 */
.matchactions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: stretch;
}
.matchactions .deletebit {
  margin-top: auto;
  justify-content: flex-end;
}
.deletebit { gap: 8px; align-items: center; flex-wrap: wrap; }
.delopts { gap: 12px; align-items: center; flex-wrap: wrap; }

/* Gone, but still on screen until the pile is read again — the same treatment a
   set-aside row gets, for the same reason. */
.matchrow.deleted { opacity: 0.45; }
.matchrow.deleted .art { filter: grayscale(1); }
.matchrow.deleted .title { text-decoration: line-through; }


/*
 * --------------------------------------------- match: reading a candidate's page
 *
 * On the badge line, small.
 */
.readbit { display: inline-flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.chip.tiny { padding: 1px 7px; font-size: 0.8em; line-height: 1.6; }

/*
 * ------------------------------------------------------- standing answers
 *
 * Under the tracked wall, as a plain list of sentences.
 */
.rulesbox { margin-top: 18px; }

.rulelist { display: grid; gap: 8px; margin: 6px 0 10px; }

.rulerow {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
}

.rulerow .ruleval { min-width: 180px; flex: 1 1 180px; max-width: 360px; }

/* Where a rule bites: the kind of catalogue, then which one. The name box only
   appears once a kind has been chosen, so a blanket rule stays one line. */
.rulerow .rulescope {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  position: relative;
}

.rulerow .rulewho { min-width: 180px; }

/* What a rule is doing to one catalogue, under its count. */
.trackruled { margin-top: 2px; opacity: 0.8; }

/*
 * ------------------------------------------------------------- the blocks
 *
 * The decide queue grouped by studio and year. Quiet headings.
 */
.decideblock { margin: 0 0 18px; }

.decideblockhead {
  display: flex;
  gap: 10px;
  align-items: baseline;
  flex-wrap: wrap;
  padding: 0 0 6px;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--line);
}

.decideblockhead h3 { margin: 0; font-size: 15px; }
.decideblockhead .spacer { flex: 1 1 auto; }

/*
 * ------------------------------------------------------------- the work band
 *
 * Top of the Tracked page: how much to do, and the way in.
 */
.workband {
  display: flex;
  gap: 16px;
  align-items: center;
  flex-wrap: wrap;
  padding: 14px 16px;
  margin-bottom: 18px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.workband .spacer { flex: 1 1 auto; }
.workband .worknum { font-size: 30px; font-weight: 600; line-height: 1; }
.workband .workcount { min-width: 96px; }
.workband .workwords { min-width: 180px; }

/*
 * ---------------------------------------------------------- likely a yes
 *
 * Top-ranked scenes, marked quietly.
 */
.likely { box-shadow: inset 3px 0 0 var(--accent-hi); }

/*
 * ------------------------------------------------------------- track tabs
 *
 * The Tracked page's sub-strip, a size down. `.subtabs a` clicks are
 * handled in tracked.js, not the router.
 */
.subtabs {
  margin: 2px 0 18px;
  flex-wrap: wrap;
}
.subtabs .section {
  padding: 7px 13px 9px;
  font-size: 13.5px;
  cursor: pointer;
}
.subtabs .section .muted {
  font-weight: 500;
  margin-left: 2px;
}
.subtabs .section.on .muted { color: var(--text-2); }
/*
 * ==================================================== the marker builder
 *
 * Picture on top, filmstrip timeline under it, cut markers below. The
 * playhead is fixed mid-strip (see markerbuilder.js).
 *
 * .mbtile and .mbband are 64px because STRIP_H is 64. Change both together.
 */

.mbuild { display: grid; gap: 14px; }

/* Undo button chrome for `.link` buttons. */
.mbuild button.link {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  cursor: pointer;
}

/*
 * the queue
 *
 * A shelf from the library's shelfPage (see picker() in markerbuilder.js);
 * only the line above it is styled here.
 */

.mbtoolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12.5px;
}

/* the bench */

.mbtop { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.mbtitle { margin: 0 0 2px; font-size: 16px; }

.mbstage {
  background: #000;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  justify-content: center;
}

/* Capped so the strip stays on screen with the picture. */
.mbvideo { width: 100%; max-height: 46vh; object-fit: contain; background: #000; display: block; }

.mbbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.mbclock {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 15px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.mbbar .muted { font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 13px; }
.mbpending { color: var(--amber); font-size: 12.5px; }
.mbkeys { display: flex; align-items: center; gap: 2px; }
.mbkeys kbd {
  display: inline-block;
  padding: 1px 5px;
  margin: 0 1px;
  border-radius: 4px;
  border: 1px solid var(--line-hi);
  background: var(--panel-2);
  color: var(--text-2);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
}

/* the strip */

.mbstrip {
  position: relative;
  height: 86px;
  overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* Dragging it scrubs; letting the browser select the tick labels instead is
     the one thing that reliably breaks that. */
  user-select: none;
  touch-action: none;
  cursor: ew-resize;
}
.mbstrip:focus { outline: none; border-color: var(--accent-hi); }

.mbruler { position: relative; height: 18px; border-bottom: 1px solid var(--line); }
.mbtick {
  position: absolute;
  top: 3px;
  left: 0;
  padding-left: 4px;
  border-left: 1px solid var(--line-hi);
  color: var(--muted);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  white-space: nowrap;
  pointer-events: none;
}

.mbtrack { position: relative; height: 64px; }
.mbtiles, .mbbands { position: absolute; inset: 0; }

.mbtile {
  position: absolute;
  top: 0;
  left: 0;
  height: 64px;
  background-repeat: no-repeat;
  /* A hairline between tiles, so a strip of near-identical frames still reads
     as a strip rather than as one smeared picture. */
  box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.45);
}

.mbband {
  position: absolute;
  top: 0;
  left: 0;
  height: 64px;
  cursor: pointer;
}
.mbband.point { width: 2px; background: var(--accent-hi); }
.mbband.span {
  background: var(--accent-wash);
  border-left: 2px solid var(--accent-hi);
  border-right: 2px solid var(--accent-hi);
}

/* The in point that has no out yet. Amber because it is not written down. */
.mbband.open {
  background: rgba(210, 153, 34, 0.2);
  border-left: 2px dashed var(--amber);
  border-right: 1px dashed var(--amber);
}

.mbbandname {
  position: absolute;
  bottom: 2px;
  left: 3px;
  padding: 1px 4px;
  border-radius: 4px;
  background: rgba(20, 19, 20, 0.82);
  color: var(--text-2);
  font-size: 10px;
  white-space: nowrap;
  pointer-events: none;
}

.mbhead {
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: var(--text);
  box-shadow: 0 0 6px rgba(0, 0, 0, 0.8);
  pointer-events: none;
}

/* the prompt */

/* Fixed, so the prompt doesn't push the timeline down. */
.mbprompt {
  position: fixed;
  left: 50%;
  bottom: 32px;
  transform: translateX(-50%);
  z-index: 60;
  width: min(460px, calc(100vw - 32px));
  display: grid;
  gap: 8px;
  padding: 14px;
  background: var(--panel-2);
  border: 1px solid var(--line-hi);
  border-radius: var(--radius);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55);
}
.mbpromptwhat { font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 13px; color: var(--text); }
.mbtaginput {
  width: 100%;
  padding: 9px 12px;
  border-radius: var(--radius);
  border: 1px solid var(--line-hi);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 14px;
}
.mbtaginput:focus { outline: none; border-color: var(--accent-hi); }

/* Capped against the window so the input box stays on screen. */
.mboptions { display: grid; gap: 3px; max-height: min(240px, 38vh); overflow-y: auto; }
.mboption {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 9px;
  border: 1px solid transparent;
  border-radius: 7px;
  background: transparent;
  color: var(--text-2);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.mboption:hover { background: var(--panel); }
.mboption.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.mboption.on .muted, .mboption.on .mbkey { color: rgba(255, 255, 255, 0.75); }
.mboption.new { font-style: italic; }
.mbkey {
  min-width: 15px;
  color: var(--muted);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
}

.mbsaid { font-size: 12.5px; color: var(--muted); min-height: 18px; }
.mbsaid.bad { color: var(--red); }

/* what has been cut */

.mbmarkers { display: grid; gap: 4px; }
.mbmarker {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-hi);
  border-radius: var(--radius);
}

/* A span is a decided stretch of the scene; a point is somebody dropping a
   pin on a moment. Worth telling apart without reading the times. */
.mbmarker.span { border-left-color: var(--accent-hi); }

.mbtime {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12.5px;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  white-space: nowrap;
}
.mbmarker .chip { padding: 3px 10px; color: var(--text); }
.mbmarker .chip:hover { border-color: var(--accent-hi); }

/* A large target for the only destructive control. */
.mbdrop {
  padding: 6px 10px;
  /* Pulled back out again so a bigger target does not make the row taller. */
  margin: -6px -4px -6px auto;
  background: none;
  border: 0;
  color: var(--muted);
  font: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}
.mbdrop:hover { color: var(--red-hi); }

@media (max-width: 620px) {
  .mbkeys { display: none; }
  .mbvideo { max-height: 34vh; }
}

/*
 * ---------------------------------------------- the marker builder, by hand
 *
 * The transport row, sized for fingertips.
 */

.mbtransport {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.mbnudges, .mbacts { display: flex; align-items: center; gap: 6px; }

/* No text selection on buttons (iOS magnifier on long press). */
.mbtransport button, .mbpending { user-select: none; -webkit-user-select: none; }

.mbnudge {
  min-width: 56px;
  min-height: 40px;
  padding: 0 10px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text-2);
  font: inherit;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.mbnudge:hover { border-color: var(--line-hi); color: var(--text); }
.mbnudge:active { background: var(--panel-2); }

/* Wider and marked out, because it is the one in the middle of the row that
   is not a seek and should not be hit by accident while nudging. */
.mbplay {
  min-width: 62px;
  font-size: 15px;
  color: var(--text);
  border-color: var(--line-hi);
}

.mbact {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 40px;
  padding: 0 16px;
  border-radius: var(--radius);
  border: 1px solid var(--line-hi);
  background: var(--panel-2);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.mbact:hover { border-color: var(--accent-hi); }
.mbact:active { background: var(--accent); border-color: var(--accent); color: #fff; }

/* Point is the one that gets used most, so it is the one that looks like the
   default. In and Out are a pair and stay quiet until you are using them. */
.mbacts .mbact:first-child { border-color: var(--accent); }

.mbbadge {
  padding: 1px 5px;
  border-radius: 4px;
  border: 1px solid var(--line-hi);
  background: var(--panel);
  color: var(--muted);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  font-weight: 400;
}

/* The in point, which is also the button that drops it. */
.mbpending {
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px dashed var(--amber);
  background: none;
  color: var(--amber);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.mbpending:hover { background: rgba(210, 153, 34, 0.12); }

/* The way out of the prompt, for anything without an Esc key. */
.mbpromptfoot { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.mbcancel {
  min-height: 34px;
  padding: 0 14px;
  border-radius: var(--radius);
  border: 1px solid var(--line-hi);
  background: var(--panel);
  color: var(--text-2);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.mbcancel:hover { border-color: var(--accent-hi); color: var(--text); }

/* Touch devices: keyed on the pointer, not width. Badges hidden, targets 44px. */
@media (hover: none) and (pointer: coarse) {
  .mbbadge { display: none; }
  .mbnudge, .mbact { min-height: 44px; }
  .mbact { flex: 1; justify-content: center; }
  .mbpending { padding: 8px 14px; font-size: 13px; }
  .mbmarker { padding: 10px; }
  .mbdrop { padding: 10px 14px; margin: -10px -8px -10px auto; }
  .mbtime { font-size: 13px; }
  /* Nothing hovers, so the cue has to be in the resting state. */
  .mbrow { border-color: var(--line-hi); }
}

@media (max-width: 620px) {
  /* Both groups get their own line rather than being squeezed onto one. The
     nudges spread across it; the three actions share it equally. */
  .mbtransport { flex-direction: column; align-items: stretch; gap: 8px; }
  .mbnudges { justify-content: space-between; }
  .mbnudges .mbnudge { flex: 1; min-width: 0; }
  .mbacts .mbact { flex: 1; padding: 0 8px; }

  /* On a phone the prompt is a bottom sheet. */
  .mbprompt {
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    transform: none;
    border-radius: var(--radius) var(--radius) 0 0;
    border-left: 0;
    border-right: 0;
    border-bottom: 0;
    padding-bottom: max(14px, env(safe-area-inset-bottom));
  }
  .mboptions { max-height: 44vh; }
  .mboption { padding: 11px 10px; font-size: 14px; }

  .mbtop { flex-direction: column; align-items: flex-start; gap: 6px; }
  .mbbar { gap: 8px; }
}

/* The sharpen control: offer, progress, and what you ended up with. */
.mbsharpen { white-space: nowrap; }

/* Cut it again, whatever is there. A glyph rather than a word because it sits
   beside the chip that already says what the strip is. */
.mbregen {
  min-width: 34px;
  padding: 0 8px;
  font-size: 14px;
  line-height: 1;
}
.mbregen.working { border-color: var(--amber); color: var(--amber); }
.mbregen:disabled { opacity: 0.4; cursor: default; }
.mbsharpen.working { border-color: var(--amber); color: var(--amber); cursor: default; }
.mbsharpen.have { border-color: var(--green); color: var(--green); }
.mbsharpen:disabled { opacity: 1; }

/*
 * The frame ladder (see markerbuilder.js). Each rung scrolls sideways;
 * the panel is capped and scrolls.
 */

/*
 * The centre line is a sibling of the scroller, so it doesn't scroll away
 * and lines up with the strip's.
 */
.mbladder { position: relative; }
.mbladder[hidden] { display: none; }

.mbgridhead {
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: var(--text);
  opacity: 0.55;
  box-shadow: 0 0 6px rgba(0, 0, 0, 0.8);
  pointer-events: none;
}

.mbgrid {
  display: grid;
  gap: 10px;
  max-height: 46vh;
  overflow-y: auto;
  padding: 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* display:grid beats the UA's [hidden] rule, so the panel has to be told. */
.mbgrid[hidden] { display: none; }

/* min-width so the row inside can scroll rather than stretching the column. */
.mbrung { display: grid; gap: 5px; min-width: 0; }
.mbrunghead {
  display: flex;
  align-items: center;
  gap: 4px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
}
.mbrunghead strong { color: var(--text-2); font-weight: 600; }

/* Wider and back in again, per rung. Sized for a fingertip on a tablet, where
   they are the only way to reach past the ends of a rung. */
.mbspans { display: flex; gap: 4px; flex: 0 0 auto; }
.mbspan {
  min-width: 30px;
  min-height: 26px;
  padding: 0 6px;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text-2);
  font: inherit;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}
.mbspan:hover:not(:disabled) { border-color: var(--accent-hi); color: var(--text); }
.mbspan:disabled { opacity: 0.35; cursor: default; }

.mbframes {
  display: flex;
  gap: 4px;
  overflow-x: auto;
  padding-bottom: 4px;
  /* A horizontal drag here scrolls, not scrubs. */
  overscroll-behavior-x: contain;
  touch-action: pan-x;
  /* Padding for the first and last frames is set in markerbuilder.js. */
  scrollbar-width: thin;
}

.mbframe {
  position: relative;
  flex: 0 0 auto;
  background-repeat: no-repeat;
  background-color: var(--panel-2);
  border: 1px solid transparent;
  border-radius: 3px;
  cursor: zoom-in;
}
.mbframe:hover { border-color: var(--accent-hi); }

/* The last rung the sheet can support. There is nothing further down to open,
   so it says so with the cursor rather than by going dead on the click. */
.mbframe.leaf { cursor: pointer; }

/* The frame whose stretch of time is the rung below. Without it the row above
   stops saying where you are. */
.mbframe.opened { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }

/* Where the playhead is standing, in every rung at once. */
.mbframe.now { border-color: var(--amber); box-shadow: 0 0 0 1px var(--amber); }

.mbframetime {
  position: absolute;
  left: 0;
  bottom: 0;
  padding: 0 3px;
  background: rgba(0, 0, 0, 0.65);
  color: #fff;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  border-radius: 0 3px 0 3px;
  pointer-events: none;
}

@media (max-width: 620px) {
  /* Capped height. */
  .mbgrid { max-height: 38vh; padding: 8px; }
}

/*
 * ------------------------------------------------------------- on a tablet
 *
 * Wide but touch-only: keyed on the pointer.
 */
@media (pointer: coarse) {
  /* touch-action so a double tap isn't a zoom on iOS. */
  .mbtransport button,
  .mbframe,
  .mbspan,
  .mbtoolbar button,
  .mbbar button { touch-action: manipulation; }

  /* Picture and ladder both on screen. */
  .mbvideo { max-height: 38vh; }
  .mbgrid { max-height: 34vh; }

  /* No selection magnifier mid-drag on iOS. */
  .mbstrip { -webkit-user-select: none; -webkit-touch-callout: none; }

  /* Hide the key legend on touch. */
  .mbkeys { display: none; }

  /* The bar wraps onto two lines as soon as an in point appears, so it is
     given room to do that without the controls touching each other. */
  .mbbar { gap: 10px; }

  /* Nothing hovers, so which frame is lit has to carry at a glance rather
     than under a cursor. */
  .mbframe { border-width: 2px; }
}

/*
 * ================================================== marker management
 *
 * A list of markers (see markermanage.js), using the bench's row styles.
 */

.mmbar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

.mmfind {
  flex: 1 1 260px;
  min-width: 0;
  padding: 9px 12px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 14px;
}
.mmfind:focus { outline: none; border-color: var(--accent-hi); }

.mmtag, .mmsort {
  padding: 8px 10px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text-2);
  font: inherit;
  font-size: 13px;
  max-width: 220px;
}

.mmsaid { min-height: 18px; }

.mmlist { display: grid; gap: 4px; }

.mmrow {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 7px 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-hi);
  border-radius: var(--radius);
}

/* A span is a decided stretch of the scene; a point is somebody dropping a pin
   on a moment. The same cue the bench's own list uses. */
.mmrow.span { border-left-color: var(--accent-hi); }

.mmtime {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12.5px;
  white-space: nowrap;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.mmrow .chip { padding: 3px 10px; color: var(--text); }
.mmrow .chip:hover { border-color: var(--accent-hi); }

/* The scene title takes the slack and truncates. */
.mmwhere {
  flex: 1 1 200px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  overflow: hidden;
}
.mmscene {
  text-align: left;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mmwhere .muted { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.mmtitle { max-width: 30ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.mmedit {
  padding: 6px 9px;
  margin: -6px 0;
  background: none;
  border: 0;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.mmedit:hover { color: var(--accent-hi); }

/* Delete sits hard right, the way it does on the bench. */
.mmrow .mbdrop { margin-left: 0; }

/* Retime fields on their own line in the row. */
.mmretime {
  flex: 1 0 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}

.mmfield {
  width: 96px;
  padding: 6px 8px;
  border-radius: 6px;
  border: 1px solid var(--line-hi);
  background: var(--panel-2);
  color: var(--text);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12.5px;
}
.mmfield:focus { outline: none; border-color: var(--accent-hi); }

.mmmore { justify-self: start; }

@media (pointer: coarse) {
  /* The same fingertip rule the bench follows: 44px targets, and the row
     padded out so two of them are not touching. */
  .mmrow { padding: 10px; gap: 12px; }
  .mmedit { padding: 10px 12px; margin: -10px 0; font-size: 15px; }
  .mmtime { font-size: 13px; }
  .mmtag, .mmsort, .mmfind { min-height: 44px; }
  .mmfield { min-height: 40px; width: 110px; }
}

@media (max-width: 620px) {
  .mmtag, .mmsort { flex: 1 1 auto; max-width: none; }
  /* The scene takes the whole of the second line rather than fighting the tag
     chip for the first. */
  .mmwhere { flex-basis: 100%; }
  .mmtitle { display: none; }
}

/*
 * =============================================== timestamps from elsewhere
 *
 * The review panel (see markerfetch.js), in the flow of the bench.
 */

.mfpanel {
  display: grid;
  gap: 10px;
  padding: 12px;
  background: var(--panel);
  border: 1px solid var(--line-hi);
  border-radius: var(--radius);
}

.mftop { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.mfclose {
  padding: 4px 10px;
  margin: -4px -4px -4px 0;
  background: none;
  border: 0;
  color: var(--muted);
  font: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.mfclose:hover { color: var(--text); }

.mftabs { display: flex; gap: 6px; flex-wrap: wrap; }
.mftab.on { border-color: var(--accent); color: var(--text); }
.mftab:disabled { opacity: 0.4; cursor: default; }

/* The offset row. Its own line above the rows, because it changes every one
   of them and a control that does that should not sit inside the list. */
.mfbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}

.mffield {
  width: 68px;
  padding: 5px 8px;
  border-radius: 6px;
  border: 1px solid var(--line-hi);
  background: var(--panel-2);
  color: var(--text);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12.5px;
  text-align: right;
}
.mffield:focus { outline: none; border-color: var(--accent-hi); }

.mfbump {
  min-width: 34px;
  min-height: 28px;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text-2);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.mfbump:hover { border-color: var(--accent-hi); color: var(--text); }

/* What is about to land on something. Amber rather than red: it is a question,
   not a fault, and the answer is regularly "yes, replace it". */
.mfwarn {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  color: var(--amber);
}
.mfwarn .chip { padding: 2px 9px; font-size: 12px; }

.mfrows { display: grid; gap: 3px; max-height: 46vh; overflow-y: auto; }

.mfrow {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 5px 8px;
  border: 1px solid transparent;
  border-left: 3px solid var(--line-hi);
  border-radius: var(--radius);
  background: var(--panel-2);
}
/* A row with an answer to give reads differently from one with a tick. */
.mfrow.onto { border-left-color: var(--amber); }

.mftick { width: 17px; height: 17px; accent-color: var(--accent); cursor: pointer; }

.mftime {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12.5px;
  white-space: nowrap;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

/* Somebody else's vocabulary, editable before it becomes a tag in this
   library. Wide enough for the long ones without taking the row. */
.mftag {
  flex: 1 1 140px;
  min-width: 90px;
  padding: 4px 8px;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12.5px;
}
.mftag:focus { outline: none; border-color: var(--accent-hi); }

.mfonto { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 22ch; }

.mfpick {
  padding: 4px 6px;
  border-radius: 6px;
  border: 1px solid var(--line-hi);
  background: var(--panel);
  color: var(--text-2);
  font: inherit;
  font-size: 12px;
}

.mfnone { display: grid; gap: 4px; }

.mffoot { display: flex; align-items: center; gap: 12px; }
.mfwrite { padding: 7px 14px; border-color: var(--accent); color: var(--text); }
.mfwrite:disabled { opacity: 0.4; border-color: var(--line); cursor: default; }

@media (pointer: coarse) {
  .mfrow { padding: 9px 8px; gap: 10px; }
  .mftick { width: 22px; height: 22px; }
  .mftag, .mfpick, .mffield { min-height: 38px; }
  .mfbump { min-width: 44px; min-height: 38px; }
  .mfwrite { min-height: 44px; }
}

@media (max-width: 620px) {
  /* The tag and the answer take the second line rather than squeezing the
     time off the first. */
  .mfrow { flex-wrap: wrap; }
  .mftag { flex-basis: 100%; }
  .mfonto { max-width: 100%; }
}
/*
 * ------------------------------------------------------------- categories
 *
 * The index reuses .facets; this is the banner and the editor.
 */

/*
 * ------------------------------------------------------------- the banner
 *
 * A wide still with the name over it; the gradient keeps the name readable.
 */

.catbanner {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--panel);
  margin-bottom: 18px;
  min-height: 160px;
  display: flex;
  align-items: flex-end;
}

.catcover {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Dimmed rather than blurred: a blur costs a repaint on every scroll of a
   * page that is mostly a wall of moving thumbnails. */
  opacity: 0.45;
}

.catbannerbody {
  position: relative;
  padding: 22px 20px 18px;
  max-width: 760px;
  background: linear-gradient(to top, rgba(20, 19, 20, 0.92), rgba(20, 19, 20, 0));
  width: 100%;
}

.catbanner.bare .catbannerbody { background: none; }

.catbannerbody h1 { margin: 0 0 4px; font-size: 26px; }
.catbannerbody p { margin: 0; }

.catblurb {
  margin-top: 8px !important;
  color: var(--text-2);
  max-width: 62ch;
}

/* The Edit button sits in the corner of the band, away from the words. */
.catbanner > .act {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 1;
}

/*
 * --------------------------------------------------------------- the editor
 *
 * Four boxes in a column, all visible at once.
 */

.catedit {
  display: grid;
  gap: 14px;
  margin-bottom: 20px;
}

.catedit[hidden] { display: none; }

.catbox {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  padding: 16px;
  display: grid;
  gap: 10px;
}

.catbox h3 { margin: 0; font-size: 15px; }
.catbox p.small { margin: 0; max-width: 72ch; }
.catbox .filter { width: 100%; }
.catbox textarea.filter { resize: vertical; min-height: 64px; font: inherit; }

/*
 * The rule: every line the same five slots, so columns align. The first
 * line's "Where" takes the joining word's slot.
 */
.catrules {
  display: grid;
  gap: 6px;
}

.catruleline {
  display: grid;
  grid-template-columns: 62px 116px 92px minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
}

/* Join word and "Where" share one right-aligned slot. */
.catjoin { min-width: 0; }
.catjoin .shelfpick { width: 100%; }

/* `:not([hidden])`: a bare display rule beats the hidden attribute. */
.catlead:not([hidden]) {
  display: block;
}

.catlead {
  text-align: right;
  padding-right: 4px;
  font-size: 13px;
  color: var(--muted);
}

/* One character wide, and it should stay square. */
.catruleline > .act:last-child { padding: 6px 10px; }

/* Narrow: each line folds into three rows. */
@media (max-width: 760px) {
  .catruleline { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; }
  .catjoin { grid-column: 1 / -1; }
  .catjoin .shelfpick { width: auto; }
  .catjoin .shelfpick,
  .catlead { text-align: left; padding-right: 0; }
  .catruleline > .filter { grid-column: 1 / 3; }
}

/* Cover picker: small scene stills in a scrolling strip. */
/* Uploaded artwork, at its real display size. */
.catart {
  display: flex;
  gap: 12px;
  align-items: center;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel-2);
}

.catart img {
  width: 200px;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 6px;
  background: var(--bg);
  flex: none;
}

.catart span { max-width: 48ch; }

@media (max-width: 620px) {
  .catart { flex-direction: column; align-items: flex-start; }
  .catart img { width: 100%; }
}

.catshots {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 132px;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 6px;
  scrollbar-width: thin;
}

.catshot {
  display: grid;
  gap: 4px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--radius);
  background: var(--panel-2);
  color: var(--text-2);
  cursor: pointer;
  overflow: hidden;
  text-align: left;
}

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

.catshotname {
  font-size: 11px;
  padding: 0 6px 5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.catshot:hover { border-color: var(--line); color: var(--text); }

/* The one leading the page. Not a tick in a corner: the whole card is the
 * answer to "which one", so the whole card is what is marked. */
.catshot.on {
  border-color: var(--accent-hi);
  color: var(--accent-hi);
  cursor: default;
}

.catshot:disabled { opacity: 0.45; cursor: default; }
.catshot:disabled:hover { border-color: transparent; color: var(--text-2); }

/*
 * ------------------------------------------------------- the two scene lists
 *
 * Search results and the hand order share one row style.
 */

.catfinds,
.catpicks {
  display: grid;
  gap: 6px;
}

.catfind {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel-2);
}

.catfind img {
  width: 104px;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 6px;
  background: var(--bg);
  flex: none;
}

.catfindbody { flex: 1; min-width: 0; }

.catfindbody .title {
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.catfindbody .meta { font-size: 12px; color: var(--muted); }

.catacts {
  display: flex;
  gap: 6px;
  flex: none;
}

/* The two nudges are one character wide and should stay square. */
.catacts .act[title^="Move"] { padding: 6px 10px; }

@media (max-width: 700px) {
  .catfind { flex-wrap: wrap; }
  .catacts { width: 100%; justify-content: flex-end; }
}

/*
 * ------------------------------------------ send a missing one to v3
 *
 * Add, under a faded tile. Disabled once sent.
 */
.facetbody button.filmsend {
  margin-top: 8px;
  padding: 5px 13px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.facetbody button.filmsend:hover { border-color: var(--accent-hi); }
.facetbody button.filmsend:disabled { opacity: 0.5; cursor: default; }

@media (pointer: coarse) {
  .facetbody button.filmsend { min-height: 40px; padding: 8px 14px; }
}

/*
 * ------------------------------------------------- the picker on a scene
 *
 * Every category as a chip, on if the scene is in it.
 */

.catpicker {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}

.catpicker .chip { cursor: pointer; }
.catpicker .chip.on { border-color: var(--accent-hi); color: var(--accent-hi); }
.catpicker .chip:disabled { opacity: 0.5; cursor: default; }

/* Filmography tiles fade to 0.25 (more than the shared 0.55). Hover restores. */
.facet.faded.filmtile .facetart { opacity: 0.25; }
.facet.faded.filmtile:hover .facetart { opacity: 1; }
/*
 * ------------------------------------------------- swapping their picture
 *
 * Under the record, quiet.
 */
.pictureswap {
  display: grid;
  gap: 6px;
  margin-top: 10px;
}

.pictureswap .toolbar { flex-wrap: wrap; }
.pictureswap input[type="file"] { font-size: 12px; max-width: 100%; }
/* ------------------------------------------------------------------- TV
 *
 * The live channel page: a black 16:9 stage with two stacked videos (one
 * showing, one loading the next), an overlay, and the channel bar below.
 */

.tv { display: grid; gap: 12px; }

.tvstage {
  position: relative;
  width: 100%;
  max-height: calc(100vh - 230px);
  aspect-ratio: 16 / 9;
  margin: 0 auto;
  background: #000;
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
}
.tvstage:fullscreen, .tvstage:-webkit-full-screen { max-height: none; border-radius: 0; }

.tvvideo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.4s;
}
.tvvideo.on { opacity: 1; }

.tvoverlay {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: grid;
  gap: 4px;
  padding: 40px 20px 16px;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.85));
  color: #fff;
  pointer-events: none;
}
.tvchannel {
  color: var(--accent-hi);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.tvnow { font-size: 18px; font-weight: 600; }

.tvhint {
  position: absolute;
  top: 12px;
  right: 12px;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 12px;
  pointer-events: none;
}

.tvbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tvbar .spacer { flex: 1; }
.tvchanname { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* What is on: title and facts left, cast faces right (the scene page's cast strip). */
.tvinfohead {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px 20px;
  align-items: start;
}
.tvinfotitle { display: block; font-size: 18px; font-weight: 600; color: var(--text); }
.tvgroup { display: block; color: var(--accent-hi); font-size: 13px; margin: 2px 0; }
.tvdetails {
  margin: 8px 0 0;
  color: var(--muted);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.tv h3 { margin: 6px 0 8px; font-size: 13px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }

/* Up next: five cards across; scrolls sideways on a phone. */
.tvnextrow {
  display: grid;
  grid-template-columns: repeat(5, minmax(150px, 1fr));
  gap: 10px;
  overflow-x: auto;
}
.tvnextcard {
  display: grid;
  gap: 3px;
  padding: 0 0 6px;
  text-align: left;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.tvnextcard:hover { border-color: var(--accent-hi); }
.tvnextcard img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--radius); background: var(--panel-2); }
.tvnexttime { color: var(--accent-hi); font-size: 12px; font-weight: 600; padding: 0 4px; }
.tvnexttitle, .tvnextcard .small { padding: 0 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The channel guide: Random and Movies, then a tab per kind with a filter. */
.tvguidebar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 10px 0; }
.tvfilter { flex: 1 1 200px; max-width: 320px; }
.tvchips { display: flex; flex-wrap: wrap; gap: 6px; max-height: 280px; overflow-y: auto; }
.tvtop { max-height: none; }
.tvchip.on { color: var(--accent-hi); border-color: var(--accent-hi); }

@media (max-width: 620px) {
  .tvstage { max-height: none; }
  .tvnow { font-size: 15px; }
  .tvinfohead { grid-template-columns: 1fr; }
  .tvnextrow { grid-template-columns: repeat(5, 150px); }
}
