/* ─────────────────────────────────────────────────────────────
   Library of Losh — macOS-style collection manager
   Themes: dark (default) · oled · light — set via <html data-theme>
   ───────────────────────────────────────────────────────────── */

/* ── Local type: SF Pro (site/Fonts) ──────────────────────────
   SF Pro Text for interface text · SF Pro Display for large headings.
   Static TTFs, one face per weight (400 / 500 / 600 / 700). */
@font-face { font-family: "SF Pro Text"; src: url("./fonts/SFProText-Regular.e8e662b1.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "SF Pro Text"; src: url("./fonts/SFProText-Medium.8351c902.woff2") format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "SF Pro Text"; src: url("./fonts/SFProText-Semibold.d7008935.woff2") format("woff2"); font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: "SF Pro Text"; src: url("./fonts/SFProText-Bold.838f29ac.woff2") format("woff2"); font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: "SF Pro Display"; src: url("./fonts/SFProDisplayRegular.37a8498e.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "SF Pro Display"; src: url("./fonts/SFProDisplay-Medium.5730b5d8.woff2") format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "SF Pro Display"; src: url("./fonts/SFProDisplay-Semibold.14d7abfb.woff2") format("woff2"); font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: "SF Pro Display"; src: url("./fonts/SFProDisplay-Bold.51b1e2a9.woff2") format("woff2"); font-weight: 700; font-style: normal; font-display: swap; }

:root {
  /* Inter is the closest freely hosted match to SF; it only kicks in where the local SF files cannot load (Apps Script / artifact builds). */
  --font: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Inter", "Helvetica Neue", Arial, sans-serif;
  --font-display: "SF Pro Display", "SF Pro Text", -apple-system, BlinkMacSystemFont, "Inter", "Helvetica Neue", Arial, sans-serif;
  --r-ctl: 6px;
  --r-field: 8px;
  /* Menu surface and the items inside it are concentric: --r-pop is the outer radius, the menu's own 5px
     padding is the gap, so an item's radius is the difference. Change --r-pop and --r-pop-in follows. */
  --r-pop: 15px;
  --r-pop-pad: 5px;
  --r-pop-in: calc(var(--r-pop) - var(--r-pop-pad));
  --r-card: 9px;
  --r-row: 10px;             /* table rows as cards */
  --row-gap: 5px;            /* space between those cards */
  /* a selected row card is opaque: the pinned first column is sticky and must not show what slides under it */
  --sel-solid: color-mix(in srgb, var(--sel), var(--surface));
  --scroll-thumb: rgba(255, 255, 255, .26);
  --scroll-thumb-hover: rgba(255, 255, 255, .4);
  /* Rows as cards, the way Apple builds an inset grouped list: the cards sit on a grey canvas rather
     than on the window colour. In the dark themes that canvas IS the window colour. */
  --table-canvas: var(--bg);
  --row-card: var(--surface);
  --row-card-hover: var(--surface-2);
  --row-base: 46px;          /* table row height before the cover asks for more */
  --cell-pad: 12px;
  --cover-w: 28px;           /* table cover width: [data-cover] on .app overrides (20 / 28 / 38) */
  --cover-pad: 2px;
  --ease: cubic-bezier(0.32, 0.08, 0.24, 1);
  --sat: env(safe-area-inset-top, 0px);
  --sab: env(safe-area-inset-bottom, 0px);
  --inspector-w: 400px;
  /* Floating inspector: a panel over the library, never a column of it */
  --insp-inset: 12px;
  --r-insp: 18px;
  --insp-glass: rgba(32, 32, 37, .82);
  /* holds the title block's text on artwork; a dark halo under light text, flipped in the light theme */
  --insp-art-ink: rgba(0, 0, 0, .55);
  --insp-edge: rgba(255, 255, 255, .09);
  --insp-blur: blur(30px) saturate(1.6);
  --insp-fade: rgba(28, 28, 33, .96);
  --shadow-insp: 0 24px 60px rgba(0, 0, 0, .52), 0 3px 12px rgba(0, 0, 0, .34);
  --sidebar-w: 200px;        /* Finder-style source list (View Options > Layout > Navigation > Sidebar) */

  /* Control heights — every control in a row takes its height from one of these,
     never from its own padding, so neighbours are congruent by construction. */
  --toolbar-control-h: 28px;     /* top bar: scope nav, view switcher, +, more, search */
  --filter-control-h: 26px;      /* filter bar: All/4K/Blu-ray, Genre, Label, HDR, Filters, Group, Sort, Columns, View Options */
  --inspector-action-h: 28px;    /* inspector: Edit, Add another edition, Delete */
  /* segmented controls: outer capsule → even inset → sliding inner capsule */
  --seg-inset: 2px;
  --r-seg: 8px;
  --ease-seg: cubic-bezier(0.22, 1, 0.36, 1);      /* fast response, long smooth settle, no bounce */
  --ease-nav: cubic-bezier(0.25, 0.1, 0.25, 1);
  --ease-spring: cubic-bezier(0.14, 0.86, 0.18, 1.04);   /* carries past the mark and settles: the inspector's way in */

  /* dark (default) — cool-neutral charcoals biased toward the blue accent */
  --bg: #1a1a1d;
  --chrome: #26262b;
  --bar: #202024;
  --inspector: #202024;
  --surface: #232328;
  --surface-2: #26262b;
  --surface-3: #2e2e34;
  --fill: rgba(255, 255, 255, 0.07);
  --fill-strong: rgba(255, 255, 255, 0.12);
  --text: #f0f0f3;
  --text-2: #9d9da6;
  --text-3: #7c7c86;           /* ≥ 4:1 on the dark ground (captions, counts, hints) */
  /* metadata steps under a movie title: Year·Director > Studio/Label > Genres */
  --text-meta: rgba(255, 255, 255, 0.58);
  --text-label: rgba(255, 255, 255, 0.64);
  --text-genre: rgba(255, 255, 255, 0.47);
  --hairline: rgba(255, 255, 255, 0.075);
  --hairline-2: rgba(255, 255, 255, 0.14);
  --accent: #0a84ff;
  --accent-ink: #ffffff;
  --accent-soft: rgba(10, 132, 255, 0.16);
  --accent-soft-2: rgba(10, 132, 255, 0.30);
  --sel: rgba(10, 132, 255, 0.20);
  --hover: rgba(255, 255, 255, 0.04);
  --mark: rgba(255, 214, 10, 0.30);
  --star: #f5b544;
  /* Losha's own rating is the one score that is not somebody else's, so it takes a hue none of them
     use: not IMDb gold, not tomato, not the greens of a splat or a Metascore, and not the blue the
     app already spends on links and the anime score. Apple's systemPurple, dark then light
     appearance, which leaves the star reading as personal rather than as another verdict. */
  --losha-c: #bf5af2;
  --danger: #ff453a;
  --ok: #32d74b;
  --warn: #ff9f0a;
  --poster-bg: #303036;
  --sidebar: #1e1e22;
  /* HD / 4K badge (icons/HDicon.txt): light badge, dark text on dark grounds */
  --fb-bg: rgba(255, 255, 255, 0.86);
  --fb-fg: #1b1b1f;
  --shadow-pop: 0 12px 32px rgba(0, 0, 0, 0.55), 0 0 0 0.5px rgba(255, 255, 255, 0.12);
  --shadow-art: 0 2px 8px rgba(0, 0, 0, 0.45);
  --shadow-lift: 0 10px 26px rgba(0, 0, 0, 0.5);
  color-scheme: dark;
}
:root[data-theme="oled"] {
  --bg: #000000;
  --chrome: #0d0d0f;
  --bar: #08080a;
  --inspector: #0a0a0c;
  --surface: #121215;
  --surface-2: #141418;
  --surface-3: #1c1c21;
  --fill: rgba(255, 255, 255, 0.06);
  --fill-strong: rgba(255, 255, 255, 0.11);
  --text: #f0f0f3;
  --text-2: #96969e;
  --text-3: #75757e;
  --hairline: rgba(255, 255, 255, 0.07);
  --hairline-2: rgba(255, 255, 255, 0.13);
  --hover: rgba(255, 255, 255, 0.045);
  --poster-bg: #17171b;
  --sidebar: #08080a;
  --shadow-pop: 0 12px 32px rgba(0, 0, 0, 0.8), 0 0 0 0.5px rgba(255, 255, 255, 0.14);
  --shadow-art: 0 2px 8px rgba(0, 0, 0, 0.7);
  --shadow-lift: 0 10px 26px rgba(0, 0, 0, 0.75);
}
:root[data-theme="light"] {
  --scroll-thumb: rgba(0, 0, 0, .26);
  --scroll-thumb-hover: rgba(0, 0, 0, .4);
  /* systemGroupedBackground / secondarySystemGroupedBackground, light appearance */
  --table-canvas: #f2f2f7;
  --row-card: #ffffff;
  --row-card-hover: #e9e9ee;
  --bg: #ffffff;
  --chrome: #f0f0f2;
  --bar: #f7f7f9;
  --inspector: #f7f7f9;
  --surface: #ffffff;
  --surface-2: #f5f5f7;
  --surface-3: #ebebef;
  --fill: rgba(0, 0, 0, 0.05);
  --fill-strong: rgba(0, 0, 0, 0.10);
  --text: #1d1d1f;
  --text-2: #6b6b73;
  --text-3: #80808a;
  --text-meta: rgba(0, 0, 0, 0.58);
  --text-label: rgba(0, 0, 0, 0.64);
  --text-genre: rgba(0, 0, 0, 0.47);
  --hairline: rgba(0, 0, 0, 0.08);
  --hairline-2: rgba(0, 0, 0, 0.14);
  --accent: #007aff;
  --accent-soft: rgba(0, 122, 255, 0.12);
  --accent-soft-2: rgba(0, 122, 255, 0.28);
  --sel: rgba(0, 122, 255, 0.14);
  --hover: rgba(0, 0, 0, 0.035);
  --mark: rgba(255, 214, 10, 0.5);
  --star: #e8a317;
  --losha-c: #af52de;
  --danger: #d70015;
  --ok: #28a745;
  --warn: #c86800;
  --poster-bg: #e4e4e9;
  --sidebar: #ececef;
  --fb-bg: #38383d;
  --fb-fg: #ffffff;
  --shadow-pop: 0 12px 32px rgba(0, 0, 0, 0.16), 0 0 0 0.5px rgba(0, 0, 0, 0.10);
  --shadow-art: 0 1px 4px rgba(0, 0, 0, 0.18);
  --shadow-lift: 0 8px 22px rgba(0, 0, 0, 0.16);
  color-scheme: light;
}

/* ── Type scale ───────────────────────────────────────────── */
/* Page titles (Movies, Box Sets): the only place 700 / SF Pro Display 34px+ is used. */
.page-title {
  font-family: var(--font-display); font-size: 36px; font-weight: 700;
  letter-spacing: -0.025em; line-height: 1.08; color: var(--text); margin: 0;
}

/* ── Base ─────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  font-size: 13px;
  line-height: 1.4;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  letter-spacing: -0.003em;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: 0; padding: 0; touch-action: manipulation; }
:focus:not(:focus-visible) { outline: none; }
button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible, a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-radius: 4px;
}
tr[tabindex]:focus-visible { outline-offset: -2px; }
.row:focus-visible, .set-item:focus-visible, .set-open:focus-visible, .set-strip:focus-visible { outline-offset: -2px; }
::selection { background: var(--accent-soft-2); }
mark { background: var(--mark); color: inherit; border-radius: 2px; padding: 0 1px; }
kbd {
  font-family: var(--font); font-size: 10.5px; color: var(--text-3);
  border: 1px solid var(--hairline-2); border-radius: 4px; padding: 1px 4px; background: transparent;
}
a { color: var(--accent); text-underline-offset: 2px; }
.muted { color: var(--text-3); }

.app {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  grid-template-areas: "top" "filters" "work" "status";
  height: 100vh;
  height: 100dvh;
  /* table rows: density sets the base height, the (portrait) cover may need more — never clip it.
     Resolved here, on the element that carries data-density / data-cover. */
  --row-h: max(var(--row-base), calc(var(--cover-w) * 1.5 + 2 * var(--cover-pad)));
}
[data-cover="none"] { --row-h: var(--row-base); }
.topbar { grid-area: top; }
.filterbar { grid-area: filters; }
.workspace { grid-area: work; position: relative; }   /* the busy spinner centres on it */
.statusbar { grid-area: status; }
/* Sits under the menu and over everything else. On a touch screen it takes the dismissing tap -- which is
   the point: without it a tap meant to close the menu lands on the poster underneath and opens the film.
   On a pointer it is inert and invisible, because clicking straight from one menu to the next control is
   what a desktop menu is expected to do. */
.pop-scrim { position: fixed; inset: 0; z-index: 99; background: transparent; pointer-events: none; }
@media (hover: none) {
  .pop-scrim { pointer-events: auto; background: rgba(0, 0, 0, .18); animation: scrim-in .16s var(--ease) both; }
  :root[data-theme="light"] .pop-scrim { background: rgba(0, 0, 0, .12); }
}
@keyframes scrim-in { from { opacity: 0; } to { opacity: 1; } }

/* ── Sidebar navigation (Finder source list) ──────────────────
   Same UI.scope / UI.format as the top bar — only the presentation moves.
   app.js sets data-nav="sidebar" only on wide viewports (>= 1000px). */
.sidebar { display: none; }
.app[data-nav="sidebar"] {
  --sb-gap: 14px;                                   /* clear space around the floating panel, on every side */
  --sb-inset: calc(var(--sidebar-w) + 2 * var(--sb-gap));   /* how far the bars' contents move right to clear the panel */
  position: relative;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "top" "filters" "work" "status";
  background: var(--bg);
}
/* The page keeps its full-width bars underneath; only their contents step aside for the panel. */
.app[data-nav="sidebar"] .topbar, .app[data-nav="sidebar"] .filterbar, .app[data-nav="sidebar"] .statusbar { padding-left: var(--sb-inset); }
.app[data-nav="sidebar"] .workspace { padding-left: var(--sb-inset); }
/* The sidebar is a floating panel above the page: detached from the window edges, one continuous rounded
   surface with a soft layered shadow, sitting over the full-width bars and canvas. The panel itself never
   scrolls — only the navigation inside it (.sb-scroll) does, so the silhouette and its padding stay put. */
.app[data-nav="sidebar"] .sidebar {
  position: absolute; z-index: 40; left: var(--sb-gap); top: calc(var(--sb-gap) + var(--sat)); bottom: calc(var(--sb-gap) + var(--sab)); width: var(--sidebar-w);
  display: flex; flex-direction: column; min-height: 0; overflow: hidden;
  padding: 12px 0 10px;
  background: var(--sb-float); border: 1px solid var(--sb-float-edge);
  border-radius: 24px; corner-shape: squircle;      /* continuous corners where supported; plain radius elsewhere */
  box-shadow: var(--sb-float-shadow);
  backdrop-filter: blur(24px) saturate(140%); -webkit-backdrop-filter: blur(24px) saturate(140%);
  user-select: none;
}
.app[data-nav="sidebar"] .sb-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 2px; padding: 0 10px 2px; }
/* The sidebar's scrollbar is drawn by hand (syncSidebarThumb in app.js). A styled native scrollbar
   still takes layout width, so a floating one has to be its own element: the native bar is hidden and
   this thumb sits over the content, with no track and no buttons, appearing only while the pointer is
   near it or the list is moving. */
.app[data-nav="sidebar"] .sb-scroll { scrollbar-width: none; }
.sb-scroll::-webkit-scrollbar { width: 0; height: 0; }
.sb-thumb {
  position: absolute; right: 4px; width: 5px; border-radius: 3px; z-index: 3;
  background: var(--scroll-thumb); opacity: 0; pointer-events: none;
  transition: opacity .2s var(--ease), background .16s var(--ease);
}
.sb-thumb.is-shown { opacity: 1; pointer-events: auto; }
.sb-thumb:hover, .sb-thumb.is-dragging { background: var(--scroll-thumb-hover); }
.sb-thumb[hidden] { display: none; }
.app[data-nav="sidebar"] .sb-brand { padding: 2px 16px 10px; cursor: pointer; }
.app[data-nav="sidebar"] .sb-row { border-radius: 8px; }
@media (max-width: 1200px) { .app[data-nav="sidebar"] { --sb-gap: 10px; } }
:root { --sb-float: rgba(30, 30, 34, .84); --sb-float-edge: rgba(255, 255, 255, .07); --sb-float-shadow: 0 1px 2px rgba(0, 0, 0, .28), 0 8px 24px rgba(0, 0, 0, .30), 0 20px 48px rgba(0, 0, 0, .22); }
/* inspector glass per theme */
:root[data-theme="oled"] { --insp-glass: rgba(14, 14, 17, .86); --insp-edge: rgba(255, 255, 255, .085); --insp-fade: rgba(10, 10, 12, .96); }
:root[data-theme="light"] {
  --insp-glass: rgba(247, 247, 249, .86); --insp-edge: rgba(0, 0, 0, .08); --insp-fade: rgba(247, 247, 249, .96);
  --shadow-insp: 0 20px 50px rgba(0, 0, 0, .16), 0 3px 12px rgba(0, 0, 0, .10);
  --insp-art-ink: rgba(255, 255, 255, .72);
}
:root[data-theme="oled"] { --sb-float: rgba(14, 14, 17, .86); --sb-float-edge: rgba(255, 255, 255, .085); --sb-float-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .45), 0 20px 48px rgba(0, 0, 0, .35); }
:root[data-theme="light"] { --sb-float: rgba(246, 246, 248, .84); --sb-float-edge: rgba(0, 0, 0, .06); --sb-float-shadow: 0 1px 2px rgba(0, 0, 0, .05), 0 8px 24px rgba(0, 0, 0, .08), 0 20px 48px rgba(0, 0, 0, .05); }
.app[data-nav="sidebar"] .topbar .brand, .app[data-nav="sidebar"] .topbar .segmented.scope { display: none; }
.app[data-nav="sidebar"] .topbar-title { display: block; }
.app[data-nav="sidebar"] .filterbar .segmented.small#formatSeg, .app[data-nav="sidebar"] .filterbar .filter-divider { display: none; }
.topbar-title { display: none; font-weight: 600; font-size: 14.5px; letter-spacing: -0.015em; color: var(--text); white-space: nowrap; padding-left: 2px; }
.sb-brand { display: flex; align-items: center; gap: 8px; padding: 2px 6px 10px; font-weight: 600; font-size: 14px; letter-spacing: -0.015em; white-space: nowrap; }
.sb-brand img { display: block; width: 24px; height: 24px; border-radius: 6px; flex: none; }
:root[data-theme="light"] .sb-brand img { background: #0b0d16; box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, .14); }
.sb-title { padding: 12px 8px 3px; font-size: 10.5px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--text-3); }
/* View Options behaves like a Finder disclosure: same heading type, a chevron that turns, and the
   section slides rather than appearing (slidePanel in app.js animates the height). */
.sb-disc {
  display: flex; align-items: center; justify-content: space-between; gap: 6px; width: 100%;
  padding: 12px 8px 3px; font-size: 10.5px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-3); text-align: left;
  transition: color .14s var(--ease);
}
.sb-disc:hover { color: var(--text-2); }
.sb-disc .sb-chev { display: grid; place-items: center; width: 12px; height: 12px; transition: transform .26s cubic-bezier(.22, 1, .36, 1); }
.sb-disc .sb-chev svg { display: block; }
.sb-disc:not(.is-open) .sb-chev { transform: rotate(-90deg); }
/* 0fr to 1fr interpolates without anyone measuring the content */
.sb-opts { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .3s cubic-bezier(.22, 1, .36, 1); }
.sb-opts.is-open { grid-template-rows: 1fr; }
.sb-opts > .sb-opts-in { overflow: hidden; min-height: 0; }
.sb-opts:not(.is-open) > .sb-opts-in { padding-bottom: 0; }   /* its padding is all that a collapsed row can still show */
@media (prefers-reduced-motion: reduce) { .sb-opts { transition-duration: .01s; } }
.sb-row {
  display: flex; align-items: center; gap: 8px; width: 100%; height: 30px; padding: 0 8px; border-radius: 6px;
  color: var(--text); font-size: 13px; font-weight: 500; white-space: nowrap; text-align: left;
  transition: background .12s var(--ease);
}
.sb-row:hover { background: var(--hover); }
.sb-row.is-active { background: var(--fill-strong); font-weight: 600; }
.sb-row .sb-ico { display: inline-grid; place-items: center; width: 18px; height: 18px; flex: none; color: var(--accent); }
.sb-row .sb-ico svg { width: 16px; height: 16px; }
.sb-row .sb-ico { --badge-h: 14px; }
.sb-row .sb-count { margin-left: auto; color: var(--text-3); font-size: 11.5px; font-weight: 400; font-variant-numeric: tabular-nums; }

/* ── Toolbar ──────────────────────────────────────────────── */
.topbar {
  display: grid;
  grid-template-columns: minmax(max-content, 1fr) minmax(220px, 460px) minmax(max-content, 1fr);
  align-items: center;
  gap: 16px;
  padding: calc(8px + var(--sat)) 14px 8px;
  background: var(--chrome);
  border-bottom: 1px solid var(--hairline);
  z-index: 30;
}
.topbar-left { display: flex; align-items: center; gap: 16px; min-width: 0; }
.topbar-center { display: flex; align-items: center; gap: 6px; min-width: 0; }
.topbar-right { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }

.brand { display: flex; align-items: center; gap: 8px; white-space: nowrap; cursor: pointer; }
.brand-mark { display: grid; place-items: center; width: 26px; height: 26px; flex: none; border-radius: 6px; }
.brand-mark img, .brand-mark svg { display: block; width: 26px; height: 26px; }
/* the mark is a glow drawn for a dark ground, so light mode gives it one */
:root[data-theme="light"] .brand-mark { background: #0b0d16; overflow: hidden; box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, .14); }
.brand-name, .sb-brand > span { text-transform: uppercase; letter-spacing: .045em; }
.brand-name { font-weight: 600; font-size: 14.5px; }

/* section scope control — a .segmented control (see below) with slightly wider segments */
.segmented.scope > button { padding: 0 12px; }

/* search */
.search {
  position: relative; display: flex; align-items: center; flex: 1 1 auto; min-width: 0; height: var(--toolbar-control-h);
  border-radius: calc(var(--toolbar-control-h) / 2);
  background: var(--fill); color: var(--text-2);
  box-shadow: inset 0 0 0 1px transparent;
  transition: box-shadow .14s var(--ease), background .14s var(--ease);
}
.search:focus-within { background: var(--surface); box-shadow: 0 0 0 3px var(--accent-soft), inset 0 0 0 1px var(--accent); color: var(--text); }
.search-icon { position: absolute; left: 10px; pointer-events: none; opacity: .8; }
.search input {
  flex: 1; height: 100%; padding: 0 32px 0 30px; border: 0; background: transparent; outline: 0;
  font-size: 16px; color: var(--text); min-width: 0;
}
@media (min-width: 721px) { .search input { font-size: 13px; } }
.search input::placeholder { color: var(--text-3); }
.search input::-webkit-search-cancel-button { display: none; }
.search-clear { position: absolute; right: 5px; display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; color: var(--text-3); }
.search-clear:hover { color: var(--text); }
/* the random-film dice: a square on the search field's own height and material, its menu floating beneath */
.dice-btn { width: var(--toolbar-control-h); height: var(--toolbar-control-h); min-width: 0; padding: 0; border-radius: var(--r-field); background: var(--fill); color: var(--text-2); }
.dice-btn:hover { background: var(--hover); color: var(--text); }
.dice-btn.is-active { background: var(--accent-soft); color: var(--accent); }

/* segmented control — outer capsule → even inset → one sliding inner capsule.
   The active knob is a single element (.seg-thumb, placed by app.js) that glides
   between segments; the buttons themselves stay transparent once it exists. */
.segmented {
  position: relative; display: inline-flex; align-items: stretch; gap: 0; flex: none;
  height: var(--toolbar-control-h); padding: var(--seg-inset); border-radius: var(--r-seg);
  background: var(--fill);
}
.segmented > button {
  position: relative; z-index: 1; display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  height: 100%; padding: 0 10px; border-radius: calc(var(--r-seg) - var(--seg-inset)); white-space: nowrap;
  color: var(--text-2); font-weight: 500; font-size: 13px; line-height: 1;
  transition: color .16s var(--ease);
}
.segmented > button:hover { color: var(--text); }
.segmented > button.is-active { color: var(--text); font-weight: 600; }
.segmented > button svg { opacity: .85; flex: none; }
.segmented > button.is-active svg { opacity: 1; }
/* until app.js has measured the control, the active button paints its own knob */
.segmented:not(.has-thumb) > button.is-active { background: var(--surface-3); box-shadow: 0 0.5px 1.5px rgba(0, 0, 0, .28); }
.seg-thumb {
  position: absolute; top: var(--seg-inset); left: 0; z-index: 0; width: 0;
  height: calc(100% - 2 * var(--seg-inset)); border-radius: calc(var(--r-seg) - var(--seg-inset));
  background: var(--surface-3); box-shadow: 0 0.5px 1.5px rgba(0, 0, 0, .28), inset 0 0 0 0.5px rgba(255, 255, 255, .04);
  transform: translateX(0); will-change: transform, width; pointer-events: none;
  transition: transform .26s var(--ease-seg), width .26s var(--ease-seg), opacity .16s var(--ease);
}
:root[data-theme="light"] .seg-thumb,
:root[data-theme="light"] .segmented:not(.has-thumb) > button.is-active {
  background: #fff; box-shadow: 0 0.5px 2px rgba(0, 0, 0, .14), 0 0 0 0.5px rgba(0, 0, 0, .04);
}
/* compact member of the family (All / 4K / Blu-ray): same inset and radius relationship, filter-bar height */
.segmented.small { height: var(--filter-control-h); border-radius: calc(var(--r-seg) - 1px); }
.segmented.small > button { padding: 0 9px; font-size: 12px; border-radius: calc(var(--r-seg) - 1px - var(--seg-inset)); }
.segmented.small .seg-thumb { border-radius: calc(var(--r-seg) - 1px - var(--seg-inset)); }

/* toolbar buttons */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: var(--toolbar-control-h); min-width: var(--toolbar-control-h); padding: 0 7px; border-radius: var(--r-ctl);
  color: var(--text-2); font-size: 12.5px; font-weight: 500; flex: none;
  transition: background .14s var(--ease), color .14s var(--ease);
}
.icon-btn:hover { background: var(--hover); color: var(--text); }
.icon-btn.is-active { color: var(--accent); background: var(--accent-soft); }
.icon-btn.accent {
  background: var(--accent); color: var(--accent-ink); padding: 0 11px; font-weight: 600;
  box-shadow: 0 0.5px 2px rgba(0, 0, 0, .2);
}
.icon-btn.accent:hover { background: color-mix(in srgb, var(--accent) 88%, white); color: var(--accent-ink); }
.icon-btn.accent:active { background: color-mix(in srgb, var(--accent) 85%, black); }
/* the Add button: a square exactly as tall as the view switcher beside it */
.icon-btn.accent.add-btn { width: var(--toolbar-control-h); padding: 0; border-radius: 7px; }

/* ── Filter bar ───────────────────────────────────────────── */
.filterbar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; min-width: 0;
  padding: 6px 14px; border-bottom: 1px solid var(--hairline); background: var(--bar); min-height: 40px;
  z-index: 20;
}
.filterbar-left, .filterbar-right { display: flex; align-items: center; gap: 5px; flex-wrap: nowrap; }
.filterbar-left { flex: 1; min-width: 0; overflow-x: auto; scrollbar-width: none; padding: 1px 0; }
.filterbar-left::-webkit-scrollbar { display: none; }
.filterbar-right { flex: none; }

.chip-btn {
  display: inline-flex; align-items: center; gap: 4px;
  height: var(--filter-control-h); padding: 0 8px; border-radius: var(--r-ctl); flex: none;
  background: transparent; color: var(--text-2); font-weight: 500; font-size: 12.5px; white-space: nowrap; line-height: 1;
  transition: background .14s var(--ease), color .14s var(--ease);
}
.chip-btn:hover { background: var(--hover); color: var(--text); }
/* while its menu is up, the chip stays in a container of its own so it is obvious which one you are in */
.chip-btn.is-menu-open { background: var(--fill); color: var(--text); }
/* Format is a three-button segment on a desktop and one chip that opens the same three choices at phone
   width; both are in the markup and only one is ever shown. */
.format-chip { display: none; }
.vo-chip .vo-ico { display: none; }
.chip-btn.is-active { background: var(--accent-soft); color: var(--accent); }
.chip-btn .caret { opacity: .5; margin-left: -1px; }
.chip-btn .count {
  display: inline-grid; place-items: center; min-width: 15px; height: 15px; padding: 0 4px; margin-left: 1px;
  border-radius: 7px; background: var(--accent); color: #fff; font-size: 10px; font-weight: 600; line-height: 1;
}
.chip-btn.link { color: var(--accent); padding: 0 6px; }
.chip-btn.link:hover { background: var(--accent-soft); }
.chip-btn.ghost { color: var(--text-3); }
.chip-btn.icon-only { padding: 0 6px; }
.filter-divider { width: 1px; height: 16px; background: var(--hairline-2); margin: 0 4px; flex: none; }

/* ── Workspace: content + docked inspector ────────────────── */
.workspace { position: relative; display: flex; min-width: 0; min-height: 0; background: var(--bg); }
.content { position: relative; flex: 1 1 auto; min-width: 0; overflow: auto; outline: 0; -webkit-overflow-scrolling: touch; }
/* the room the docked panel needs, made over time rather than all at once */
@media (min-width: 1001px) {
  #app[data-insp="on"] #content { padding-right: calc(var(--inspector-w) + var(--insp-inset)); }
  /* dragging the panel's edge rewrites that width every frame; easing it would lag the pointer */
  #app[data-inspdrag="on"] #content { animation: none; }
}
/* directional navigation: the outgoing view (a short-lived clone) slips 12px one way
   while the incoming one arrives from the other — spatial continuity, not a page slide */
.content-ghost { position: absolute; top: 0; left: 0; z-index: 1; overflow: hidden; pointer-events: none; flex: none; background: var(--bg); }
.content.nav-in-right { animation: navInRight .22s var(--ease-nav) both; }
.content.nav-in-left { animation: navInLeft .22s var(--ease-nav) both; }
.content-ghost.nav-out-left { animation: navOutLeft .18s var(--ease-nav) both; }
.content-ghost.nav-out-right { animation: navOutRight .18s var(--ease-nav) both; }
@keyframes navInRight { from { opacity: 0; transform: translateX(12px); } to { opacity: 1; transform: none; } }
@keyframes navInLeft { from { opacity: 0; transform: translateX(-12px); } to { opacity: 1; transform: none; } }
@keyframes navOutLeft { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(-12px); } }
@keyframes navOutRight { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(12px); } }
/* The library scrolls as if it had no scrollbar at all. A styled native bar still takes 11px of layout
   width, which the grid has to give up whether or not anything is scrolling, so the native one is gone
   and #contentThumb is drawn over the content instead -- the same trick the sidebar uses. It is inset
   from the edge rather than pressed against it, quiet rather than solid, and it shows itself only while
   you are scrolling or while the pointer is near enough to want it. */
.content { scrollbar-width: none; }
.content::-webkit-scrollbar { width: 0; height: 0; }
.content-thumb {
  position: absolute; width: 7px; border-radius: 4px; z-index: 6; pointer-events: auto; cursor: default;
  background: color-mix(in srgb, var(--text-3) 42%, transparent);
  opacity: 0; transition: opacity .22s var(--ease), background .14s var(--ease);
}
.content-thumb.is-shown { opacity: 1; }
.content-thumb:hover, .content-thumb.is-dragging { background: color-mix(in srgb, var(--text-3) 66%, transparent); }
@media (hover: none) { .content-thumb { display: none; } }   /* a phone has its own, and no pointer to find this one with */

.empty { display: grid; place-items: center; height: 100%; min-height: 300px; text-align: center; color: var(--text-2); padding: 24px; }
.empty .empty-icon { font-size: 34px; margin-bottom: 8px; opacity: .75; color: var(--text-3); }
.empty .empty-icon svg { display: inline-block; vertical-align: middle; }
.empty h3 { margin: 0 0 4px; font-size: 16px; font-weight: 600; color: var(--text); letter-spacing: -0.015em; }
.empty p { margin: 0; font-size: 13px; }
/* loading state: a ring whose arc grows and shrinks as it spins, so a long sheet read never looks frozen */
/* A section or view switch holds the main thread while it rebuilds, so the workspace says so: the old
   content stays in place but steps back and stops taking input, which reads as "working" rather than as
   a press that did nothing. The spinner is on a delay, so a switch that finishes inside a quarter of a
   second never flashes one -- only a genuinely slow one earns it. */
/* Stepping back, not switching off. This used to cut to 40% over 140ms, which read as a flinch; a softer
   landing at 55% over a quarter of a second says "working" without the view appearing to break. The
   arrival is left to navTransition()'s directional slide, which is already animating this element --
   anything added here would override its animation and take the sense of direction away with it. */
#app[data-busy="on"] #content { opacity: .55; pointer-events: none; }
/* the padding is the docked inspector making room for itself; it lives here because this rule already
   owns #content's transition by id, and a class selector further up would simply lose to it */
#content { transition: opacity .25s var(--ease); }
/* The padding that makes room for the docked panel is NOT eased. Animating it was tried and measured at
   about 140ms a frame -- changing the grid's width re-resolves the tracks for the whole library, so the
   "animation" ran at six frames a second and arrived in three visible steps, which is worse than not
   moving at all. The columns take their new width in one go and the eye is given something free instead:
   the content fades up from just under half, which reads as a change rather than a jolt and costs one
   composited property. */
#content.is-reflow { animation: reflow-in .34s var(--ease) both; }
@keyframes reflow-in { from { opacity: .45; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { #content.is-reflow { animation: none; } }
/* A slim band sweeping along the top of the workspace, in place of the spinner that used to sit in the
   middle of it. A spinner over the top of the view you are leaving is the thing that read as harsh: it
   is large, it is central, and it says "wait" about content that is still perfectly readable. The band
   says the same thing at the edge of vision. It is a translate, so it keeps sweeping on the compositor
   while the rebuild holds the main thread, and it is on the same delay -- a switch that finishes inside
   a fifth of a second never shows one. */
#app[data-busy="on"] .workspace::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 2px; z-index: 3; pointer-events: none;
  background: linear-gradient(90deg, transparent 0%, var(--accent) 42%, var(--accent) 58%, transparent 100%);
  /* in almost at once: a switch says it is working while the tab highlight is still travelling */
  opacity: 0; animation: busy-sweep 1s var(--ease) infinite, busy-in .16s var(--ease) .04s forwards;
}
@keyframes busy-sweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
@keyframes busy-in { to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { #app[data-busy="on"] .workspace::after { animation: busy-in .16s ease .25s forwards; } }
.loader { width: 36px; height: 36px; margin: 0 auto 14px; color: var(--accent); }
.loader svg { display: block; width: 100%; height: 100%; animation: ld-spin 1.4s linear infinite; }
.loader circle { fill: none; stroke: currentColor; stroke-width: 3.5; stroke-linecap: round; stroke-dasharray: 14 200; animation: ld-arc 1.8s cubic-bezier(.4, 0, .2, 1) infinite; }
@keyframes ld-spin { to { transform: rotate(360deg); } }
@keyframes ld-arc { 0% { stroke-dasharray: 14 200; stroke-dashoffset: 0; } 50% { stroke-dasharray: 90 200; stroke-dashoffset: -30; } 100% { stroke-dasharray: 14 200; stroke-dashoffset: -118; } }
.empty.is-loading h3 { animation: ld-pulse 2.4s ease-in-out infinite; }
@keyframes ld-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .7; } }
@media (prefers-reduced-motion: reduce) { .loader svg { animation-duration: 3s; } .loader circle { animation: none; stroke-dasharray: 60 200; } .empty.is-loading h3 { animation: none; } }

/* ── Posters (artwork) ────────────────────────────────────── */
.poster {
  position: relative; display: block; aspect-ratio: 2 / 3; background: var(--poster-bg);
  border-radius: 4px; overflow: hidden; flex: none; container-type: inline-size;
  box-shadow: inset 0 0 0 0.5px rgba(127, 127, 135, .22);
}
.poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* A cover that has not arrived is a flat tile in the poster background, and the image fades in over it.
   There was a sheen travelling across the empty tile as well; it went when the skeleton below arrived,
   because the two said the same thing about the same tile and the sheen was the more expensive of them
   -- it animated on every visible unloaded poster, and a picture fading in over a moving highlight is a
   smaller change to see than one fading in over a flat block. */
/* A crossfade wants its own curve. --ease is cubic-bezier(.32, .08, .24, 1), which is right for something
   moving -- it holds still, then goes -- but on opacity it spends over half its time under 30% and then
   rushes the rest, so the picture reads as popping in late rather than fading. Measured on a cold tile:
   165ms into a 300ms fade the image was still at 0.27. ease-out climbs straight away, so what you see is
   a picture arriving. */
.poster img { opacity: 0; transition: opacity .34s ease-out; }
.poster img.is-in, .no-art-anim .poster img { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .poster img { transition: none; } }
.poster-ph {
  width: 100%; height: 100%; display: grid; place-items: center; text-align: center; padding: 8%;
  background: linear-gradient(160deg, hsl(var(--h) 26% 34%), hsl(var(--h) 30% 18%));
  color: rgba(255, 255, 255, .85); font-weight: 600; letter-spacing: -0.02em; line-height: 1.06;
}
.poster-ph .ph-title { font-size: clamp(8px, 9cqw, 20px); overflow: hidden; display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; }
.poster-ph .ph-year { font-size: clamp(7px, 6cqw, 12px); opacity: .65; font-weight: 500; margin-top: 3px; }
.poster.tiny { width: var(--cover-w); border-radius: 3px; }
.poster.small { width: 38px; border-radius: 4px; }
.poster.tiny .poster-ph, .poster.small .poster-ph { padding: 2px; }
.poster.tiny .ph-title, .poster.small .ph-title { font-size: 7px; -webkit-line-clamp: 3; }
.poster.tiny .ph-year, .poster.small .ph-year { display: none; }

/* ── Ratings ──────────────────────────────────────────────── */
.ratings { display: inline-flex; gap: 4px; align-items: center; flex-wrap: nowrap; vertical-align: middle; }
.rt {
  display: inline-flex; align-items: center; gap: 4px; height: 19px; padding: 0 6px;
  border-radius: 4px; background: var(--fill); color: var(--text);
  font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: 0; white-space: nowrap;
}
.rt .rt-src { font-size: 9.5px; font-weight: 600; letter-spacing: .01em; opacity: .95; }
.rt.imdb .rt-src { color: #f5c518; }
.rt.rt-fresh .rt-src { color: #ff6a4d; }      /* tomato */
.rt.rt-rotten .rt-src { color: #8fbf5a; }     /* splat green */
.rt.mc .rt-src { color: var(--mc-c, var(--text-2)); }
.rt.mc.g { --mc-c: #63b93a; }
.rt.mc.y { --mc-c: #e8b23a; }
.rt.mc.r { --mc-c: #ff6a5e; }
.rt.tmdb .rt-src { color: #01d277; }
.rt.anime .rt-src { color: #4f9dff; }      /* AniList / Kitsu community score */
/* Losha's own rating, in the same pill as the public scores and always first among them */
.rt.losha .rt-src { color: var(--losha-c); }
.rt.losha { gap: 3px; }
.rt.losha.bare { padding: 0 5px; }        /* no label, so the star and number carry the pill */
.rt-star { color: var(--losha-c); font-size: 10px; line-height: 1; }
:root[data-theme="light"] .rt.mc.g { --mc-c: #3f8a1c; }
:root[data-theme="light"] .rt.mc.r { --mc-c: #d0342c; }
:root[data-theme="light"] .rt.imdb .rt-src { color: #b28a00; }
:root[data-theme="light"] .rt.rt-fresh .rt-src { color: #d63f1e; }
:root[data-theme="light"] .rt.rt-rotten .rt-src { color: #4f8a1f; }
:root[data-theme="light"] .rt.tmdb .rt-src { color: #00875a; }
:root[data-theme="light"] .rt.anime .rt-src { color: #1c62c4; }
[data-style="plain"] .rt { background: transparent; padding: 0; }
[data-style="plain"] .rt .rt-src { color: var(--text-3) !important; }

/* watched: a quiet outlined check by titles and an Apple-like filled circle on artwork */
.watched-mark { display: inline-grid; place-items: center; width: 14px; height: 14px; max-height: var(--badge-h); flex: none; color: var(--text-3); vertical-align: middle; }
.watched-mark svg { width: 13px; height: 13px; display: block; }
.set-item .t .watched-mark { margin-left: 2px; }
.watched-state .watched-mark { color: var(--text-2); margin-right: 5px; }
.tt .watched-mark { margin-left: 3px; }
.row-title .watched-mark { align-self: center; margin-left: 1px; }
/* the provided rounded-square badge (icons/checkmark-badge, heart-badge): the artwork shows through
   the glyph itself, so it needs the same weight of shadow as the format badges opposite it */
.card .poster .watched-overlay {
  --badge-h: var(--art-badge-h, 16px);              /* the same height as the format badges opposite */
  position: absolute; right: 8px; top: 8px; z-index: 1;
  width: var(--badge-h); height: var(--badge-h); max-height: none;
  color: var(--badge-ink, rgba(250, 250, 252, .96)); filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .6)) drop-shadow(0 3px 9px rgba(0, 0, 0, .5));
  transition: opacity .14s var(--ease);
}
/* on the artwork the plate is the whole mark, so it fills the badge box rather than sitting 13px inside it */
.card .poster .watched-overlay svg { width: 100%; height: 100%; }
.card:hover .poster .watched-overlay { opacity: 0; }
.watched-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; font-size: 12.5px; }
.watched-state { display: inline-flex; align-items: center; gap: 6px; color: var(--text); }
.watched-state svg { width: 14px; height: 14px; color: var(--text-2); }

/* stars */
.stars { display: inline-flex; gap: 0.5px; color: var(--star); font-size: 12.5px; line-height: 1; letter-spacing: 0; white-space: nowrap; vertical-align: middle; }
.stars .s, .star-input .s { position: relative; width: 1em; height: 1em; }
.stars .s::before, .star-input .s::before { content: "★"; position: absolute; inset: 0; color: var(--hairline-2); }
.stars .s.on::after, .stars .s.half::after, .star-input .s.on::after, .star-input .s.half::after { content: "★"; position: absolute; inset: 0; color: var(--star); }
.stars .s.half::after, .star-input .s.half::after { width: 50%; overflow: hidden; }
.stars.small { font-size: 11px; }
.star-input { display: inline-flex; gap: 2px; font-size: 26px; cursor: pointer; user-select: none; touch-action: none; line-height: 1; }
.star-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.star-value { font-size: 12.5px; color: var(--text-2); min-width: 58px; font-variant-numeric: tabular-nums; }
.star-clear { color: var(--text-3); font-size: 12px; }
.star-clear:hover { color: var(--text); text-decoration: underline; }

/* ── Chips & badges ───────────────────────────────────────── */
.chips { display: inline-flex; gap: 3px; flex-wrap: nowrap; vertical-align: middle; }
[data-wrap="on"] .chips, .card .chips, .detail .chips { flex-wrap: wrap; row-gap: 3px; }
.chip, .badge {
  display: inline-flex; align-items: center; gap: 3px; height: 18px; padding: 0 6px; border-radius: 4px;
  font-size: 11px; font-weight: 500; line-height: 1; white-space: nowrap; vertical-align: middle;
  --c-bg: var(--fill); --c-fg: var(--text-2);
  background: color-mix(in srgb, var(--c-fg) 15%, transparent);
  color: color-mix(in srgb, var(--c-fg) 45%, var(--text));
}
.badge .em { font-size: 10.5px; line-height: 1; opacity: .9; }
:root[data-theme="light"] .chip, :root[data-theme="light"] .badge {
  background: color-mix(in srgb, var(--c-bg) 55%, transparent);
  color: color-mix(in srgb, var(--c-fg) 88%, black);
}
/* genres: lowest-emphasis metadata — never bold */
.chip { font-weight: 400; }
[data-style="plain"] .chips .chip { color: var(--text-genre) !important; }
/* format / HDR badges: compact, semibold, subordinate to the artwork */
.badge.fmt, .badge.hdr { font-size: 10px; font-weight: 600; letter-spacing: -0.005em; }
/* format keeps a little more presence: it names the physical disc */
.badge.fmt { background: color-mix(in srgb, var(--c-bg) 80%, transparent); color: var(--c-fg); }
:root:not([data-theme="light"]) .badge.fmt {
  background: color-mix(in srgb, var(--c-fg) 22%, transparent);
  color: color-mix(in srgb, var(--c-fg) 50%, var(--text));
}
/* label: ordinary secondary metadata — no colour dot, never a heavy block */
.badge.lbl { background: transparent; color: var(--text-label); padding: 0; font-size: 12px; font-weight: 500; }
.detail .badge.lbl { color: var(--text); }
/* genres as one quiet line: "Crime · Drama · Mystery" */
.genres { color: var(--text-genre); font-size: 11.5px; font-weight: 400; line-height: 1.35; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.genres .sep { margin: 0 4px; }

[data-style="plain"] .chip, [data-style="plain"] .badge { background: transparent !important; color: inherit !important; padding: 0; height: auto; gap: 3px; }
[data-style="plain"] .chips { gap: 0; }
[data-style="plain"] .chips .chip + .chip::before { content: ", "; color: var(--text-3); }
[data-style="plain"] .badge .em { display: none; }

/* ── Technical-format badges ───────────────────────────────
   One system (B10): every badge is a provided asset (icons/, prepared by build_icons.py into icons.js) drawn as
   a mask in the text colour — a filled shape with the text knocked out — at ONE height, --badge-h. The token,
   not the text or the asset's aspect ratio, sets the height; width follows each asset's own proportions.
   Dark themes get a light badge, the light theme a dark one, because the colour is the text colour. */
:root { --badge-h: 16px; }
[data-density="compact"] { --badge-h: 14px; }
.detail { --badge-h: 18px; }
.ico {
  display: inline-block; height: var(--badge-h); width: calc(var(--badge-h) * var(--ar, 1)); flex: none;
  background-color: currentColor; vertical-align: middle;
  -webkit-mask: var(--ico) center / contain no-repeat; mask: var(--ico) center / contain no-repeat;
}
/* vector badge drawn as an inline <use> of the sprite (crisp on Retina Safari); size still from --ar */
.ico.ico-v { -webkit-mask: none; mask: none; background: none; }
.ico.ico-v svg { display: block; width: 100%; height: 100%; fill: currentColor; }
/* the format badge (4K / HD) sits in the same quiet grey as every other technical badge; the poster overlay
   keeps its own gold / blue, and the Inspector its full-strength text colour */
.fmt-badge { color: var(--text-2); }
.detail .fmt-badge { color: var(--text); }
/* a value with no asset: clean text at badge height, no chrome (B1 fallback) */
.ico-txt { display: inline-flex; align-items: center; flex: none; height: var(--badge-h); font-size: 10.5px; font-weight: 600; letter-spacing: .01em; color: var(--text-2); white-space: nowrap; vertical-align: middle; }
.ico-set { display: inline-flex; align-items: center; gap: 5px; vertical-align: middle; color: var(--text-2); white-space: nowrap; }
.ico-svg { display: inline-block; height: var(--badge-h); width: auto; flex: none; vertical-align: middle; }
.ico-svg svg { display: block; height: 100%; width: auto; }
.ico-line { display: inline-flex; align-items: center; gap: 5px; vertical-align: middle; white-space: nowrap; color: var(--text-2); }
.detail .ico-set, .detail .ico-line, .detail .ico-txt { color: var(--text); }

/* Apple TV-inspired content-classification badge. It is intentionally code-native text in a
   transparent, hairline shell—not copied rating artwork—so the page background always shows through. */
.content-rating {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  min-width: 25px; height: var(--badge-h); padding: 0 5px;
  border: 1px solid color-mix(in srgb, currentColor 58%, transparent); border-radius: 4px;
  color: var(--text-2); background: transparent;
  font-size: 10px; line-height: 1; font-weight: 600; letter-spacing: .035em;
  font-variant-numeric: tabular-nums; white-space: nowrap; vertical-align: middle;
}
.detail .content-rating { color: var(--text); }
.insp-badges .content-rating { opacity: .86; }

/* ── Table view ───────────────────────────────────────────── */
/* --table-inset must match TABLE_INSET in app.js, which subtracts it from the width it shares out */
.grid-wrap { --table-inset: var(--row-gap); min-width: 100%; display: inline-block; padding: 0 0 10px var(--table-inset); }
table.grid { border-collapse: separate; border-spacing: 0; table-layout: fixed; font-variant-numeric: tabular-nums; }
table.grid th, table.grid td {
  height: var(--row-h); padding: 0 var(--cell-pad);
  text-align: left; vertical-align: middle; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
table.grid td { border-bottom: 1px solid var(--hairline); color: var(--text-2); }
/* Rows as cards (View Options › Table › Rows): the hairline goes, each row becomes its own squircle
   on the window colour, and the gap between rows does the separating. */
[data-rows="cards"] table.grid { border-spacing: 0 var(--row-gap); }
[data-rows="cards"] table.grid tbody td { border-bottom: 0; background: var(--row-card); }
[data-rows="cards"] table.grid tbody tr > td:first-child { border-top-left-radius: var(--r-row); border-bottom-left-radius: var(--r-row); }
[data-rows="cards"] table.grid tbody tr > td:last-child { border-top-right-radius: var(--r-row); border-bottom-right-radius: var(--r-row); }
[data-rows="cards"] table.grid tbody tr:hover td { background: var(--row-card-hover); }
[data-rows="cards"] table.grid tbody tr.is-selected td { background: var(--sel-solid); }
[data-rows="cards"] table.grid tr.group-row td { background: var(--bar); border-radius: 0; }
/* the canvas the cards sit on, and the sticky header that has to match it */
.app[data-view="table"][data-rows="cards"] .content { background: var(--table-canvas); }
.app[data-view="table"][data-rows="cards"] table.grid thead th { background: var(--table-canvas); }
.app[data-view="table"][data-rows="cards"][data-pin="on"] table.grid thead th:first-child { background: var(--table-canvas); }
[data-rows="cards"][data-pin="on"] table.grid tbody td:first-child { background: var(--row-card); }
[data-rows="cards"][data-pin="on"] table.grid tbody tr:hover td:first-child { background: var(--row-card-hover); }
[data-rows="cards"][data-pin="on"] table.grid tbody tr.is-selected td:first-child { background: var(--sel-solid); }
[data-wrap="on"] table.grid td { white-space: normal; height: auto; padding-top: 9px; padding-bottom: 9px; line-height: 1.35; }
table.grid td.td-cover { padding-top: var(--cover-pad); padding-bottom: var(--cover-pad); }
table.grid td.td-cover .poster { width: var(--cover-w); }
/* trailing filler: keeps manual widths exact while the row still runs the full table width */
table.grid th.th-fill, table.grid td.td-fill { padding: 0; }
/* offscreen copy used to measure content widths for Smart Fit */
.grid-measure { position: absolute; left: -100000px; top: 0; visibility: hidden; pointer-events: none; }
.grid-measure table.grid { table-layout: auto; width: auto; }
.grid-measure th, .grid-measure td { overflow: visible; }
.grid-measure .mz { display: inline-flex; white-space: nowrap; }
.grid-measure .mz .th-inner { gap: 5px; }
table.grid td.td-title { font-weight: 600; color: var(--text); letter-spacing: -0.01em; }
/* title cell: text that can still clip, plus a copy button that never moves the text */
.tt { display: flex; align-items: center; gap: 2px; min-width: 0; }
.tt .t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
[data-wrap="on"] .tt .t { white-space: normal; }
.grid-measure .tt .t { overflow: visible; }
table.grid td.td-num { color: var(--text-2); }

table.grid thead th {
  position: sticky; top: 0; z-index: 3; background: var(--bg);
  color: var(--text-2); font-weight: 500; font-size: 11.5px; letter-spacing: 0;
  border-bottom: 1px solid var(--hairline-2);
  user-select: none; cursor: default; height: 28px;
}
table.grid thead th .th-inner { display: flex; align-items: center; gap: 5px; min-width: 0; }
table.grid thead th .th-label { overflow: hidden; text-overflow: ellipsis; }
table.grid thead th .th-sort { color: var(--text-2); flex: none; opacity: 0; width: 9px; height: 9px; }
table.grid thead th.is-sorted { color: var(--text); font-weight: 600; }
table.grid thead th.is-sorted .th-sort { opacity: .9; }
table.grid thead th:hover { color: var(--text); box-shadow: inset -1px 0 0 var(--hairline-2), inset 1px 0 0 var(--hairline-2); }
table.grid thead th.is-dragging { opacity: .35; }
table.grid thead th.drop-left { box-shadow: inset 2px 0 0 var(--accent); }
table.grid thead th.drop-right { box-shadow: inset -2px 0 0 var(--accent); }
table.grid thead th .th-grip { opacity: 0; color: var(--text-3); flex: none; cursor: grab; margin-left: -3px; }
table.grid thead th:hover .th-grip { opacity: .6; }
/* resize handle: a 9px hit area straddling the column edge; a hairline appears on hover */
/* (kept inside its own header: the next sticky header paints over anything that pokes past the edge) */
/* The handle straddles the column edge and is wide enough to catch without aiming: the cover column
   is only 28px of poster, and its neighbours' controls crowd the same few pixels. */
table.grid thead th .resizer { position: absolute; top: 0; right: -7px; width: 16px; height: 100%; cursor: col-resize; z-index: 6; touch-action: none; }
table.grid thead th .resizer::after { content: ""; position: absolute; right: 7px; top: 5px; bottom: 5px; width: 1px; background: var(--accent); opacity: 0; transition: opacity .12s var(--ease); }
table.grid thead th .resizer:hover::after, table.grid thead th .resizer.is-resizing::after { opacity: .9; }
.content.is-resizing, .content.is-resizing * { cursor: col-resize !important; user-select: none; }
.content.is-resizing table.grid thead th:hover { box-shadow: none; }
.content.is-resizing table.grid tbody tr:hover td { background: transparent; }
.resize-guide { position: fixed; z-index: 90; width: 1px; margin-left: -1px; background: var(--accent); opacity: .6; pointer-events: none; }
table.grid thead th.al-center, table.grid td.al-center { text-align: center; }
table.grid thead th.al-right, table.grid td.al-right { text-align: right; }
table.grid thead th.al-center .th-inner { justify-content: center; }
table.grid thead th.al-right .th-inner { justify-content: flex-end; }
/* header control (B15): a chevron at the right edge, inboard of the resize zone, shown on hover / focus only.
   It overlays the header (no layout shift) on a small patch of the header colour so a long label stays readable. */
table.grid thead th .th-menu {
  position: absolute; right: 12px; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 18px; height: 18px; border-radius: 5px; display: grid; place-items: center;
  color: var(--text-2); background: var(--bg); box-shadow: 0 0 0 3px var(--bg);
  opacity: 0; pointer-events: none; transition: opacity .12s var(--ease), background .12s var(--ease);
}
table.grid thead th:hover .th-menu, table.grid thead th .th-menu:focus-visible, table.grid thead th.is-menu-open .th-menu { opacity: 1; pointer-events: auto; }
table.grid thead th .th-menu:hover { background: var(--fill-strong); color: var(--text); box-shadow: 0 0 0 3px var(--bg); }
@media (hover: none) { table.grid thead th .th-menu { display: none; } }

table.grid tbody tr { cursor: default; }
table.grid tbody tr:hover td { background: var(--hover); }
table.grid tbody tr.is-selected td { background: var(--sel); }

[data-pin="on"] table.grid th:first-child, [data-pin="on"] table.grid td:first-child {
  position: sticky; left: 0; z-index: 2; background: var(--bg);
}
[data-pin="on"] table.grid thead th:first-child { z-index: 5; }
[data-pin="on"] table.grid tbody tr:hover td:first-child { background: color-mix(in srgb, var(--hover) 100%, var(--bg)); }
[data-pin="on"] table.grid tbody tr.is-selected td:first-child { background: color-mix(in srgb, var(--sel) 100%, var(--bg)); }

table.grid tr.group-row td {
  position: sticky; top: 28px; z-index: 2; background: var(--bar);
  font-weight: 600; font-size: 12px; color: var(--text); border-bottom: 1px solid var(--hairline); height: 28px; cursor: pointer;
}
table.grid tr.group-row td .group-inner { display: flex; align-items: center; gap: 7px; }
.group-caret { display: inline-block; width: 9px; height: 9px; transition: transform .16s var(--ease); color: var(--text-3); }
.is-collapsed .group-caret { transform: rotate(-90deg); }
.group-count { color: var(--text-3); font-weight: 500; font-variant-numeric: tabular-nums; }

[data-density="compact"] { --row-base: 30px; --cell-pad: 9px; }
[data-density="compact"] table.grid { font-size: 12px; }
[data-density="compact"] .chip, [data-density="compact"] .badge { height: 16px; font-size: 10.5px; padding: 0 5px; }
[data-density="compact"] .badge.lbl { padding: 0; }
[data-density="compact"] .rt { height: 16px; font-size: 10px; padding: 0 4px; }
[data-density="compact"] .stars.small { font-size: 10px; }

/* ── Posters view ─────────────────────────────────────────── */
/* View Options › Posters: three card sizes (--card-min) and a spacing slider (--card-gap, set inline by app.js) */
.cards {
  --poster-r: 12px; --card-min: 150px; --card-gap: 16px; --art-badge-h: 16px;
  /* the grid's own padding is a token because --card-bleed below is clamped to it: a bleed wider than
     the padding puts the edge cards past the scroller and the page scrolls sideways */
  --cards-pad-x: 16px; --cards-pad-top: 16px; --cards-pad-bottom: 16px;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--card-min), 1fr));
  gap: var(--card-gap-y, calc(var(--card-gap) + 4px)) var(--card-gap-x, var(--card-gap));
  padding: var(--cards-pad-top) var(--cards-pad-x) var(--cards-pad-bottom);
}
.cards[data-size="small"] { --card-min: 118px; }
.cards[data-size="large"] { --card-min: 196px; }
/* Covers: artwork only, so the tiles are smaller and more of them fit -- six columns on a desktop and
   three on a phone at the middle size. Nothing else about the card changes: same radius, same badges,
   same 2:3 box, same skeleton behind it. --card-h is the artwork alone, since there is no body. */
/* Covers is artwork and nothing else, so the space around it should be one measurement: the gutter
   between two covers and the gap to the edge of the screen are the same number, on all four sides. */
.cards.is-covers {
  --card-min: 190px;
  --cards-pad-x: var(--card-gap-x, var(--card-gap)); --cards-pad-top: var(--card-gap-x, var(--card-gap));
  --cards-pad-bottom: var(--card-gap-x, var(--card-gap));
  gap: var(--card-gap-x, var(--card-gap));
}
.cards.is-covers[data-size="small"] { --card-min: 150px; }
.cards.is-covers[data-size="large"] { --card-min: 250px; }
/* View Options › Posters › Corners */
.cards[data-radius="small"] { --poster-r: 5px; }
.cards[data-radius="medium"] { --poster-r: 12px; }
.cards[data-radius="large"] { --poster-r: 20px; }
.cards .card .poster { border-radius: var(--poster-r); }
/* A liquid-glass specular edge: one hairline ring, bright where a light would catch the top-left and
   the bottom-right, invisible along the other two runs. The ring is a gradient masked to the area
   between the border box and a 1px inset of it, so it follows border-radius: inherit at any radius
   and any card size without a single hard-coded number. */
@supports ((-webkit-mask-composite: xor) or (mask-composite: exclude)) {
  .cards .card .poster::after {
    content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 2;
    /* dialled in 2026-09-11: 1.5px at 65deg, both catches at .76, a short 4% falloff and a .17 floor
       so the whole edge keeps a faint line between the two bright runs */
    padding: 1.5px;
    background: linear-gradient(65deg,
      rgba(255, 255, 255, .76) 0%,
      rgba(255, 255, 255, .32) 1.64%,
      rgba(255, 255, 255, .05) 4%,
      rgba(255, 255, 255, .17) 42%,
      rgba(255, 255, 255, .17) 58%,
      rgba(255, 255, 255, .09) 96%,
      rgba(255, 255, 255, .33) 98.36%,
      rgba(255, 255, 255, .76) 100%);
    -webkit-mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
    mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
    -webkit-mask-composite: xor; mask-composite: exclude;
    transition: opacity .18s var(--ease);
  }
  .cards[data-size="small"] .card .poster::after { padding: .75px; }    /* a small tile wants a finer line */
  .card:hover .poster::after { opacity: .55; }                          /* the lift already reads; the edge steps back */
  :root[data-theme="light"] .cards .card .poster::after { opacity: .75; }
}
.card {
  position: relative; display: flex; flex-direction: column; gap: var(--card-art-gap, 8px);
  cursor: pointer; container-type: inline-size;
  /* The skeleton. A card the grid has not painted yet leaves a hole, and on a fast scroll you outrun the
     paint and watch cards arrive complete out of blank space. A placeholder cannot go INSIDE the card --
     the card's contents are exactly what is being skipped, so it would vanish in the window it is for.
     What does still paint while contents are skipped is the element's own background, so the placeholder
     is drawn there: a flat tile in the poster colour, at the poster's position and size, which
     syncCardHeight() publishes as --poster-h from the card it measures each render.
     It has to be the poster's shape, not just its box. A square tile shows in the four corners once the
     artwork is over it, because the poster's own corners are rounded and the card background shows
     through them. So the tile is a rounded rectangle: the last layer is the fill, and the four before it
     are r x r corner pieces, each a quarter-disc of the canvas colour cutting the square corner away.
     Five layers, but static ones -- the browser rasterises a card's background once and reuses it while
     you scroll. Which is also why nothing here may animate: unlike the sheen this replaced, the
     background paints for skipped cards too, so a shimmer would run on the whole library at once. */
  background-image:
    radial-gradient(circle at 100% 100%, transparent calc(var(--poster-r) - 0.5px), var(--bg) calc(var(--poster-r) + 0.5px)),
    radial-gradient(circle at 0 100%, transparent calc(var(--poster-r) - 0.5px), var(--bg) calc(var(--poster-r) + 0.5px)),
    radial-gradient(circle at 100% 0, transparent calc(var(--poster-r) - 0.5px), var(--bg) calc(var(--poster-r) + 0.5px)),
    radial-gradient(circle at 0 0, transparent calc(var(--poster-r) - 0.5px), var(--bg) calc(var(--poster-r) + 0.5px)),
    linear-gradient(var(--poster-bg), var(--poster-bg));
  background-repeat: no-repeat;
  background-origin: content-box;
  background-size:
    var(--poster-r) var(--poster-r), var(--poster-r) var(--poster-r),
    var(--poster-r) var(--poster-r), var(--poster-r) var(--poster-r),
    100% var(--poster-h, 0px);
  background-position:
    0 var(--card-top-gap, 0px),
    100% var(--card-top-gap, 0px),
    0 calc(var(--card-top-gap, 0px) + var(--poster-h, 0px) - var(--poster-r)),
    100% calc(var(--card-top-gap, 0px) + var(--poster-h, 0px) - var(--poster-r)),
    0 var(--card-top-gap, 0px);
}
/* View Options › Posters › Spacing › Above artwork. It is the poster that moves down, not the card that
   gains padding: the card keeps the symmetric padding the bleed gives it, so syncCardHeight() can still
   take the bleed back off both ends, and the margin counts into the flex container's height on its own. */
.cards .card .poster { margin-top: var(--card-top-gap, 0px); }
/* Only the cards near the viewport are laid out and painted. A full library is over a thousand of
   them and the grid was spending a whole scroll frame on rows nobody can see: 36ms a frame against
   14ms with this on, measured at 390x844 with the CPU throttled 4x. List rows and set cards have
   worked this way since B9; the poster grid measured slower back then and was left out, which was
   before the card body became a fixed-height block.
   The size reserved for a skipped card must be that card's real height or the page is the wrong
   length and the scrollbar walks while you scroll (a 300px guess left the document 15% too tall and
   shrinking). Every card in the grid is the same height, so app.js measures one per render and
   publishes it as --card-h; the fallback only ever covers the first frame.
   content-visibility also brings paint containment, which clips a descendant to the card box - that
   would cut the poster's shadow off at its own edges and square off the corners of the hover lift.
   --card-bleed pays that back as padding and takes it straight off again as margin, so the border
   box grows but every content box, container-query width and grid track stays exactly where it was;
   only the area the card is allowed to paint into changes. The bleed would also widen what the card
   catches pointer events on, into a gutter that used to belong to nobody, so the card itself stops
   taking them and ::before hands the old box back. */
@supports (content-visibility: auto) {
  /* room for the poster's shadow to paint into, never more than the grid's own padding -- a wider bleed
     would push the outer cards' border boxes past the scroller and the page would scroll sideways
     (16px against the phone breakpoint's 10px side padding did exactly that, 6px of drift). 10px still
     covers the 8px resting shadow; the hover lift only has to fit on desktop, where the padding is 16. */
  .cards { --card-bleed: min(16px, var(--cards-pad-x), var(--cards-pad-top)); }
  .cards .card {
    content-visibility: auto; contain-intrinsic-size: auto var(--card-h, 320px);
    padding: var(--card-bleed); margin: calc(-1 * var(--card-bleed));
    pointer-events: none;
  }
  /* The bleed pays for the resting shadow (8px of blur inside 10-16px of padding) but not for the lift,
     which is 26px of blur dropped 10px: paint containment cuts it off at the padding edge and leaves a
     hard line down the side of the card. A card that is hovered or open is on screen by definition, so it
     has nothing to gain from being skippable -- it gives up containment for as long as it is lifted and
     its shadow paints in full. */
  .cards .card:hover, .cards .card.is-selected { content-visibility: visible; }
  .cards .card > *, .cards .card::before { pointer-events: auto; }
  .cards .card::before { content: ""; position: absolute; inset: var(--card-bleed); z-index: -1; }
}
/* artwork first: a slight lift on hover, no card shell */
.card .poster { box-shadow: var(--shadow-art), inset 0 0 0 0.5px rgba(127, 127, 135, .22); transition: transform .18s var(--ease), box-shadow .18s var(--ease); }
.card:hover .poster { transform: scale(1.025); box-shadow: var(--shadow-lift), inset 0 0 0 0.5px rgba(127, 127, 135, .3); }
.card:active .poster { transform: scale(1.01); }
/* the film whose panel is open, in every view: a ring in the accent, so a grid of a thousand posters
   says which one you are looking at */
/* held up for as long as its panel is open -- the same lift the pointer gives it, so the film you are
   reading about keeps the posture it had when you reached for it */
.card.is-selected .poster { transform: scale(1.025); box-shadow: var(--shadow-lift), 0 0 0 2.5px var(--accent); }
.row.is-selected { background: var(--sel); }
[data-rows="cards"] .row.is-selected { background: var(--sel); }
/* format / HDR overlays: small translucent material tiles, 8px in from the corner */
/* Badges on artwork. The asset is the badge: a plate with the glyph knocked out, tinted by `color`.
   Every variant below only changes that surface — the SVG geometry is never touched. A drop-shadow
   traces the plate's own alpha, so hairlines and glows follow the shape exactly, knockouts included. */
.art-badges {
  --badge-h: var(--art-badge-h, 16px); --badge-ink: rgba(255, 255, 255, .92);
  position: absolute; top: 8px; left: 8px; display: flex; flex-wrap: wrap; gap: 4px;
  max-width: calc(100% - 16px); z-index: 1; color: var(--badge-ink);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .6)) drop-shadow(0 3px 9px rgba(0, 0, 0, .5));
}
/* a large corner radius needs the marks further in, or they sit on the curve */
.cards[data-radius="large"] .art-badges { top: 11px; left: 11px; max-width: calc(100% - 22px); }
.cards[data-radius="large"] .card .poster .watched-overlay { top: 11px; right: 11px; }

/* solid: the plate at full strength, the crispest of the four */
.cards[data-badgestyle="solid"] .art-badges { --badge-ink: #fff; }
.cards[data-badgestyle="solid"] .art-badges .fmt-badge.k4 { color: #ffd60a; }
.cards[data-badgestyle="solid"] .art-badges .fmt-badge.hd { color: #0a84ff; }

/* Glass and tonal: frosted material behind a translucent plate. Every provided plate is rx 22 on a
   100-high viewBox, so a radius of 22% of the badge height clips the material to exactly the plate's
   shape — the frosting, its hairline and its shadow all follow the badge instead of boxing it. The
   material is darkened a little so the knocked-out glyph reads as ink rather than as raw artwork. */
.cards:is([data-badgestyle="glass"], [data-badgestyle="tonal"]) .art-badges > *,
.cards:is([data-badgestyle="glass"], [data-badgestyle="tonal"]) .card .card-more,
.cards:is([data-badgestyle="glass"], [data-badgestyle="tonal"]) .card .poster .watched-overlay {
  border-radius: calc(var(--badge-h) * .22);
  /* The glyph is a hole in the plate, so it only reads as ink if what shows through is darker than
     the plate. Most of that contrast comes from the plate being nearly opaque; the material itself is
     only eased down a little, which keeps the artwork visible through the badge instead of parking a
     dark tile on top of it. */
  -webkit-backdrop-filter: blur(16px) saturate(1.25) brightness(.52);
  backdrop-filter: blur(16px) saturate(1.25) brightness(.52);
  /* A soft shadow only. A hard half-pixel ring was tried here to define the badge against white
     artwork, and at badge size it read as a drawn outline rather than an edge. */
  box-shadow: inset 0 0 0 .5px rgba(255, 255, 255, .38), 0 1px 2.5px rgba(0, 0, 0, .22);
}
/* the watched mark and the … button are full circles, so their material is too */
.cards:is([data-badgestyle="glass"], [data-badgestyle="tonal"]) .card .poster .watched-overlay.ico-watched-badge,
.cards:is([data-badgestyle="glass"], [data-badgestyle="tonal"]) .card .card-more { border-radius: 50%; }
.cards:is([data-badgestyle="glass"], [data-badgestyle="tonal"]) .card .poster .watched-overlay,
.cards:is([data-badgestyle="glass"], [data-badgestyle="tonal"]) .card .card-more { filter: none; }

.cards[data-badgestyle="glass"] .art-badges { --badge-ink: rgba(255, 255, 255, .56); }
.cards[data-badgestyle="glass"] .card .poster .watched-overlay,
.cards[data-badgestyle="glass"] .card .card-more { --badge-ink: rgba(255, 255, 255, .46); }
.cards[data-badgestyle="glass"] .art-badges .fmt-badge.k4 { color: rgba(255, 226, 120, .66); }
.cards[data-badgestyle="glass"] .art-badges .fmt-badge.hd { color: rgba(160, 202, 255, .66); }
.cards[data-badgestyle="glass"] .card .poster .watched-overlay.fav-mark { color: rgba(255, 96, 100, .72); }

.cards[data-badgestyle="tonal"] .art-badges { --badge-ink: rgba(255, 255, 255, .6); }
.cards[data-badgestyle="tonal"] .art-badges .fmt-badge.k4 { color: rgba(255, 214, 10, .72); }
.cards[data-badgestyle="tonal"] .art-badges .fmt-badge.hd { color: rgba(10, 132, 255, .72); }
.cards[data-badgestyle="tonal"] .card .poster .watched-overlay,
.cards[data-badgestyle="tonal"] .card .card-more { --badge-ink: rgba(255, 255, 255, .6); }
.cards[data-badgestyle="tonal"] .card .poster .watched-overlay.fav-mark { color: rgba(255, 69, 58, .78); }

/* the shape's own shadow is on each badge in those two, not on the row */
.cards[data-badgestyle="glass"] .art-badges,
.cards[data-badgestyle="tonal"] .art-badges { filter: none; }
@media (prefers-reduced-transparency: reduce) {
  .cards:is([data-badgestyle="glass"], [data-badgestyle="tonal"]) .art-badges > *,
  .cards:is([data-badgestyle="glass"], [data-badgestyle="tonal"]) .card .poster .watched-overlay {
    -webkit-backdrop-filter: none; backdrop-filter: none; background: rgba(10, 10, 12, .6);
  }
}
.art-badges .ico-set { gap: 4px; color: inherit; }
.art-badges .ico-txt { color: inherit; }
/* Apple's system palette, dark appearance: systemYellow for 4K, systemBlue for HD — on artwork only */
.art-badges .fmt-badge.k4 { color: #ffd60a; }
.art-badges .fmt-badge.hd { color: #0a84ff; }
.cards[data-badges="off"] .art-badges { display: none; }  /* View Options › Posters › Artwork badges */
/* Below the artwork every card has the same information height, built from fixed line heights:
   a two-line title box (top-aligned, clamped), year · director, the optional studio line and one genre
   line, each truncated rather than wrapped; the ratings are pinned to the bottom edge. So a row of cards
   shares poster edges, the top of the text and the ratings baseline. Hierarchy: title strongest,
   year / director secondary, genre subdued, ratings compact tertiary tokens.
   Budget: 2 × 17 + 4 (title) + 15 (sub) + 15 (genre) + 18 (ratings) + 6 slack = 92px; studio on adds one
   15px line. Each line is its own --p-* term, so a line switched off in View Options › Poster Info gives
   its height straight back and the grid closes up instead of leaving a gap. */
.card {
  /* The type is sized from the card's own width (cqw), not from the size preset, so it stays in
     proportion at every poster size and at every window width in between. The clamps are the floor
     and ceiling: below ~150px the title would get too small to read, above ~230px too heavy. */
  --ct-fs: clamp(12.5px, 8.1cqw, 17px); --ct-lh: calc(var(--ct-fs) * var(--ct-lh-ratio, 1.17));
  --cl-h: calc(var(--ct-fs) * 1.07); --cm-fs: clamp(11px, 6.6cqw, 13.5px);
  --p-title: calc(2 * var(--ct-lh) + var(--title-gap, 0px)); --p-sub: var(--cl-h); --p-studio: 0px;
  --p-genre: calc(var(--cl-h) + var(--genre-gap, 5px)); --p-rat: calc(18px + var(--ratings-gap, 0px)); --p-slack: 6px;
  --card-info-h: calc(var(--p-title) + var(--p-sub) + var(--p-studio) + var(--p-genre) + var(--p-rat) + var(--p-slack));
}
.cards[data-studio="on"] .card { --p-studio: var(--cl-h); }
.cards[data-ptitle="off"] .card { --p-title: 0px; }
.cards[data-psub="off"] .card { --p-sub: 0px; }
.cards[data-pgenre="off"] .card { --p-genre: 0px; }
.cards[data-prat="off"] .card { --p-rat: 0px; }
.cards[data-info="off"] .card-body { display: none; }   /* View Options › Poster Info › Hide all text */
.card-body { display: flex; flex-direction: column; height: var(--card-info-h); min-width: 0; transition: opacity .18s var(--ease); }
.card-main { display: flex; flex-direction: column; gap: 0; min-width: 0; }
.card-head { display: flex; align-items: flex-start; gap: 2px; min-width: 0; height: calc(2 * var(--ct-lh)); overflow: hidden; margin-bottom: var(--title-gap, 0px); }
/* Two lines are always reserved, so the metadata below sits on one guide across a grid row, and the
   title splits evenly instead of leaving one word stranded on the second line. */
.card-title {
  flex: 1; min-width: 0; font-size: var(--ct-fs); font-weight: 600; letter-spacing: -0.01em;
  line-height: var(--ct-lh); min-height: calc(2 * var(--ct-lh)); color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  /* `pretty` rather than `balance`: balance evens the two lines out, which is prettier for a title that
     fits but throws away characters on one that does not -- with a two-line clamp, filling the first
     line and letting the ellipsis fall at the end shows more of a long name. A word longer than the
     card breaks rather than spilling, and nothing is ever hyphenated. The font never shrinks to fit:
     --ct-fs is the card's width, so every card in a row is set at the same size. */
  text-wrap: pretty; overflow-wrap: break-word; hyphens: none;
}
.card-sub { color: var(--text-meta); font-size: var(--cm-fs); font-weight: 400; line-height: var(--cl-h); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card-sub .sep { margin: 0 4px; color: var(--text-3); }
.card-studio { color: var(--text-label); font-size: calc(var(--cm-fs) - .5px); font-weight: 500; line-height: var(--cl-h); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Genres fill the line and then stop. An ellipsis here was cutting a word in half to no purpose -- the
   truncated tail said nothing the full list does not, and the whole list is one tap away in the film's
   details, which is still built from the same untouched data. So the line is a wrapping flex box exactly
   one line tall with the overflow hidden: a genre that does not fit wraps to a second line nobody ever
   sees, and what remains is a clean run of whole words. Each genre carries its own leading separator, so
   the one that leaves takes its separator with it and the line never ends on a dangling dot. */
.card-meta {
  color: var(--text-genre); font-size: calc(var(--cm-fs) - .5px); font-weight: 400; line-height: var(--cl-h);
  margin-top: var(--genre-gap, 5px);
  display: flex; flex-wrap: wrap; align-content: flex-start; height: var(--cl-h); overflow: hidden;
}
.card-meta .g { white-space: nowrap; }
.card-meta .g + .g::before { content: "·"; margin: 0 4px; color: var(--text-3); }
.card-meta .sep { margin: 0 4px; color: var(--text-3); }
.card-row { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
/* Same rule for the scores: a pill either fits whole or it is not shown. They are ordered Losha, IMDb,
   RT, so a narrow card keeps the ones that matter most and drops from the right. Wrapping past the one
   line the row is tall takes the surplus out of sight, and no pill is ever left half-drawn. */
.card-ratings { margin-top: auto; padding-top: var(--ratings-gap, 0px); height: calc(18px + var(--ratings-gap, 0px)); overflow: hidden; opacity: .8; transition: opacity .18s var(--ease); }
/* The row is one pill tall and a pill that does not fit wraps to a second line nobody sees -- the same
   trick the genre line uses. It has to be clipped at exactly the pill's own height: the block around it
   is 18px against a 17px pill, so a wrapped third score was showing its top edge as a hairline running
   under the genres, which is what that mystery line on a narrow card was. */
.card-ratings .ratings { display: flex; flex-wrap: wrap; align-content: flex-start; row-gap: 0; height: 17px; overflow: hidden; }
/* ratings are tertiary on a card: a lighter, shorter pill than the one in the List and Inspector */
.card-ratings .rt { height: 17px; padding: 0 5px; font-size: 10.5px; background: color-mix(in srgb, var(--fill) 70%, transparent); }
.card-ratings .rt .rt-src { font-size: 9px; }
.card:hover .card-ratings { opacity: 1; }
.card:hover .card-sub, .card:hover .card-studio, .card:hover .card-meta { color: var(--text-2); }
/* overflow (…) button: appears with hover / keyboard focus, no layout shift */
/* The … control is a badge like the rest, not a chip laid over them. It takes the place of the watched
   mark, which fades out on hover, so the two are the same badge in the same spot: same --badge-h, same
   inset, same step to 11px at the large corner setting, same --badge-ink, and the same materials under
   the glass and tonal settings. It was a dark rounded square among light badges, which is what made it
   read as stuck on top of the artwork rather than as part of the set. A disc because a circle is
   concentric with a rounded corner from every direction -- there is no radius left to match once the
   shape has no corners of its own. */
.card-more {
  --badge-h: var(--art-badge-h, 16px);
  position: absolute; right: 8px; top: 8px; z-index: 2;
  width: var(--badge-h); height: var(--badge-h); border-radius: 50%; display: block; opacity: 0;
  color: var(--badge-ink, rgba(250, 250, 252, .96));
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .6)) drop-shadow(0 3px 9px rgba(0, 0, 0, .5));
  transition: opacity .14s var(--ease), color .14s var(--ease);
}
.card-more svg { width: 100%; height: 100%; display: block; }
/* the disc is badge-sized; the pointer is given a little more than that to aim at */
.card-more::after { content: ""; position: absolute; inset: -5px; border-radius: 50%; }
.cards[data-radius="large"] .card-more { top: 11px; right: 11px; }
/* the artwork the menu came out of, held down while it is up: the same idea as the … button pinning
   itself visible, said with brightness because there is no button on a touch screen */
.cards .card .poster.is-open { filter: brightness(.62); }
.cards .card .poster.is-open::after { opacity: .3; }
.card:hover .card-more, .card-more:focus-visible, .card-more.is-open { opacity: 1; }
/* the badges have no hover state to borrow, so the plate simply brightens under the pointer */
.card-more:hover { color: rgba(255, 255, 255, .99); }
/* Touch has no hover to reveal the button with, and a 22px target over the artwork is in the way of the
   tap that opens the film. So the button goes and the same menu is reached by pressing and holding the
   cover instead (app.js); a tap still opens the details panel. The callout is switched off so the hold
   is ours rather than iOS's save-image sheet, which nothing here needs. */
@media (hover: none) {
  .card-more { display: none; }
  /* iOS answers a long press by starting a text selection, and once it starts it runs on past the card
     and highlights the page. Switching it off over the card was not enough, because the rules that make
     body text selectable name their elements, and a named rule beats an inherited one -- so the moment
     the selection reached a title or a genre line it had somewhere to go. Selection is off for the
     document here and switched back on only inside the film's panel, which is the one place on a phone
     you would actually copy from. */
  :root, body { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
  table.grid tbody td, .card-body, .row-title, .row-sub, .row-meta, .set-card, .log,
  .cards .card, .cards .card * { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
  .detail-body, .detail-head, .kv, .overview { -webkit-user-select: text; user-select: text; }
  /* The callout alone was not enough on iOS: it suppresses the save-image sheet, but the selection loupe
     comes from user-select, which the artwork never set. Both go here, and the image also stops being
     draggable, so a hold on a poster can only ever be our own gesture. */
  .cards .card .poster {
    -webkit-touch-callout: none;
    -webkit-user-select: none; user-select: none;
  }
  .cards .card .poster img { -webkit-touch-callout: none; -webkit-user-drag: none; pointer-events: none; }
}

.section-head {
  grid-column: 1 / -1; position: sticky; top: 0; z-index: 2; display: flex; align-items: center; gap: 7px;
  padding: 9px 2px 6px; margin-top: 4px; background: var(--bg);
  font-size: 13px; font-weight: 600; letter-spacing: -0.01em; cursor: pointer;
}
.section-head:first-child { margin-top: 0; }
.section-head .group-count { font-size: 12px; }

/* ── List view ────────────────────────────────────────────── */
/* the same gap all round: row cards are separated by --row-gap, so they are inset by it too, rather
   than running hard into the window edge */
.list { padding: 4px var(--row-gap) 16px; }
.list-inner { border-top: 1px solid var(--hairline); }
.list .section-head { padding: 10px 16px 6px 8px; }
/* Tracks: poster (fixed) · main (minmax(0, 1fr)) · label (one fixed width), so every right-hand
   label sits on the same vertical guide. Rows are a fixed height with fixed line heights inside,
   so titles, meta lines, badges and posters line up from row to row. */
.list { --list-row-h: 62px; --list-poster-h: 50px; --list-poster-w: calc(var(--list-poster-h) * 2 / 3); --rc-w: 44px; }
/* Every row is the same height, at every width. The cover is sized from that height rather than the
   other way round, so artwork can never drive the row, and each of the three text lines is held to one
   line and ellipsised. A long title or a wrapped genre list changes nothing about the row it sits in. */
.row {
  /* align-content centres the three text tracks in the row's fixed height. They used to pack to the top,
     which left the slack at the bottom and took the stretched cover and the phone's chevron with it --
     both sat high in the row rather than on its middle. Compact density had already centred for this
     reason; now every density does. */
  display: grid; grid-template-columns: var(--list-poster-w) minmax(0, 1fr) auto; align-items: start; align-content: center; column-gap: 12px;
  grid-template-areas: "poster title ratings" "poster sub ratings" "poster meta ratings";   /* poster and ratings span the text lines */
  height: var(--list-row-h); padding: 3px 16px 3px 8px; border-bottom: 1px solid var(--hairline); cursor: pointer;
  content-visibility: auto; contain-intrinsic-size: auto calc(var(--list-row-h) - 6px);   /* rows off screen skip layout and paint (content-box guess = min-height less the padding); 'auto' remembers a row's real height once laid out */
}
.row:hover { background: var(--hover); }
/* A poster shape, not a column shape. The cover used to stretch over all three grid rows so its edges met
   the top of the title and the bottom of the badge line, which is tidy but is not 2:3: the stretch made it
   11% narrower than a poster on a desktop row and 28% narrower on a compact phone row, and since the image
   covers its box, that came off the artwork's left and right edges. It holds the ratio now and centres on
   the row instead. --list-poster-h drives the width, so a column of covers is one width down the whole
   list whatever each row's text does. */
.row .poster.small { grid-area: poster; align-self: center; width: var(--list-poster-w); height: auto; aspect-ratio: 2 / 3; }
/* The title is the only part that gives way: the year, the watched mark and the copy control hold their
   size and the title itself ellipsises, so the year stays readable however long the title is. */
.row-title { grid-area: title; display: flex; align-items: baseline; gap: 6px; min-width: 0; line-height: 18px; font-weight: 600; font-size: 13.5px; letter-spacing: -0.01em; }
.row-title > * { flex: none; }
.row-title .t { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row-title .yr { color: var(--text-meta); font-weight: 400; font-size: 12px; letter-spacing: 0; white-space: nowrap; }
.row-title .copy-btn { vertical-align: top; margin-left: 3px; }                 /* top-aligned so an 18px control never grows the 18px line box */
.row-title .watched-mark { vertical-align: top; margin: 2px 0 0 3px; }
.meta-ico { display: inline-block; width: 12px; height: 12px; vertical-align: -2px; margin-right: 3px; opacity: .7; }
/* Optically centred between the title and the badges rather than measured between their boxes. The title's
   13.5px type leaves about 2.25px of leading under it and the sub's 12px about 2px, so equal margins put
   the line nearer the badges -- which is exactly how it read. 0 above and 3 below evens the two gaps out,
   and the block still sums to the 50px cover so the badges stay bottom-aligned with it.
   The line is a wrapping flex row exactly one line tall with the overflow hidden, the same trick the
   poster card's genres use: a piece that does not fit wraps to a second line nobody sees, so the line
   ends on a whole genre rather than half of one. The director is the only part that gives way and
   ellipsises, because it is the part worth keeping some of. */
.row-sub {
  grid-area: sub; min-width: 0; color: var(--text-meta); font-size: 12px; font-weight: 400; line-height: 16px;
  margin-top: 0; display: flex; flex-wrap: wrap; align-content: flex-start; height: 16px; overflow: hidden;
}
.row-sub > * { flex: none; white-space: nowrap; }
.row-sub .rs-dir { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.row-sub .sep { margin: 0 4px; color: var(--text-3); }
/* The cover is 2:3 and centred, and so is the block of three text lines beside it, so their bottom edges
   only meet if the block is exactly as tall as the cover. The title line is 18 and the sub line 16 with a
   1px lead, which leaves 15 of the cover's 50 for the badge line: 2 of lead and 13 of badge. Scaled to
   that, the foot of the badges lands on the foot of the artwork rather than 3px below it. */
.row-meta { grid-area: meta; display: flex; align-items: center; flex-wrap: nowrap; gap: 4px 5px; min-width: 0; height: 13px; margin-top: 3px; overflow: hidden; }
.row-meta .chip, .row-meta .badge { height: 13px; font-size: 9.5px; padding: 0 4px; }
.row-meta .badge .em { font-size: 9px; }
.row-meta .ico, .row-meta .fmt-badge, .row-meta .ico-set { --badge-h: 13px; }
.row-meta > * { flex: none; }
/* Ratings: two fixed-width cells (IMDb, RT) in their own column, vertically centred on the row. A missing score
   keeps its cell with a dash, so the columns never shift. The brand colour lives in the small label and a faint
   tint; the number stays neutral and tabular. */
.row-ratings { grid-area: ratings; align-self: center; display: grid; grid-template-columns: repeat(var(--rc-n, 2), var(--rc-w)); gap: 6px; }
.rc { display: flex; flex-direction: column; align-items: center; gap: 3px; min-width: 0; --rc-c: var(--text-3); }
.rc-l { font-size: 9px; font-weight: 600; letter-spacing: .04em; line-height: 1; color: var(--rc-c); }
.rc-v {
  width: 100%; height: 21px; display: grid; place-items: center; border-radius: 6px;
  font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; line-height: 1; color: var(--text);
  background: color-mix(in srgb, var(--rc-c) 11%, transparent); box-shadow: inset 0 0 0 0.5px color-mix(in srgb, var(--rc-c) 30%, transparent);
}
.rc.src-imdb { --rc-c: #e0b532; }
.rc.src-rt.fresh { --rc-c: #f0654a; }
.rc.src-rt.rotten { --rc-c: #8fbf5a; }
.rc.src-losha { --rc-c: var(--losha-c); }
.rc.none { --rc-c: var(--text-3); }
/* Losha's value is a star and the number, so its box lays them out in a row rather than centring one item */
.rc.src-losha .rc-v { display: flex; align-items: center; justify-content: center; gap: 2.5px; }
/* in a table cell the pill has a whole resizable column to fill, so hold it to the width it has in the list */
table.grid td .rc { width: 44px; }
.rc.src-losha .rc-star { color: var(--losha-c); font-size: 10.5px; line-height: 1; }
.rc.none .rc-l { opacity: .55; }
.rc.none .rc-v { color: var(--text-3); background: transparent; box-shadow: inset 0 0 0 0.5px var(--hairline); }
.row-chev { display: none; grid-area: chev; color: var(--text-3); opacity: .7; }
/* Rows as cards, the same choice the table offers and the same squircle: each row its own surface on the
   canvas colour, no dividing line, the gap doing the separating. Edge to edge, as the table's rows are. */
.app[data-view="list"][data-rows="cards"] .content { background: var(--table-canvas); }
[data-rows="cards"] .list-inner { border-top: 0; }
[data-rows="cards"] .row { background: var(--row-card); border-bottom: 0; border-radius: var(--r-row); margin-bottom: var(--row-gap); }
[data-rows="cards"] .row:hover { background: var(--row-card-hover); }
[data-rows="cards"] .list .section-head { background: var(--table-canvas); }
[data-density="compact"] .list { --list-row-h: 40px; --list-poster-h: 30px; }
[data-density="compact"] .row { padding: 3px 14px; align-items: center; align-content: center; }
[data-density="compact"] .row-sub, [data-density="compact"] .row-meta { display: none; }
/* touch widths: a chevron marks the row as a way into the sheet (the copy control is hidden for every
   title at this width, up with .copy-btn itself) */
@media (max-width: 720px) {
  .row { grid-template-columns: var(--list-poster-w) minmax(0, 1fr) auto 10px; grid-template-areas: "poster title ratings chev" "poster sub ratings chev" "poster meta ratings chev"; column-gap: 10px; }
  .row-chev { display: block; align-self: center; }
}
/* very narrow phones: the ratings drop under the badges as one group, right-aligned, still two fixed cells */
@media (max-width: 400px) {
  .list { --rc-w: 58px; }
  /* the chevron holds its own column down all three lines, as it does at the wider size, so it centres
     on the row rather than on the title line alone */
  .row { grid-template-columns: var(--list-poster-w) minmax(0, 1fr) auto 10px; grid-template-areas: "poster title title chev" "poster sub sub chev" "poster meta ratings chev"; }
  .row-ratings { align-self: end; margin-top: 3px; gap: 5px; }
  .row-chev { align-self: center; margin-top: 0; }
  /* on the badge line the cell is one short pill — label and value side by side — so rows stay compact */
  .rc { flex-direction: row; justify-content: center; gap: 4px; height: 20px; border-radius: 6px; background: color-mix(in srgb, var(--rc-c) 11%, transparent); box-shadow: inset 0 0 0 0.5px color-mix(in srgb, var(--rc-c) 30%, transparent); }
  .rc-l { font-size: 8.5px; }
  .rc-v { width: auto; height: auto; font-size: 11.5px; background: none; box-shadow: none; }
  .rc.none { background: transparent; box-shadow: inset 0 0 0 0.5px var(--hairline); }
  .rc.none .rc-v { background: none; box-shadow: none; }
}

/* ── Box sets ───────────────────────────────────────────────
   Cards of one rhythm: cover strip · name + size · Format / HDR · the first five films · "Show N more".
   Grid: 320px minimum → 3–4 cards on a wide window, 3 on a desktop, 2 narrow, 1 on a phone.
   app.js (SETS_CSS) reads the numbers here to size the cover strip, keep them in sync. */
/* The cards in a row share the tallest one's height (grid's own stretch). They each hold only their own
   content -- no reserved rows -- so a row of short sets is short; it is only within a row that they line
   up, which is where an uneven bottom edge actually reads as ragged. */
.sets { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 12px; padding: 16px; }
.set-card { display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-card); padding: 12px; content-visibility: auto; contain-intrinsic-size: auto 300px; }
/* strip: 66px covers (99px tall), as many as the card holds, then a +N tile */
.set-strip { display: flex; gap: 6px; height: 99px; overflow: hidden; margin-bottom: 11px; cursor: pointer; border-radius: 4px; }
.set-strip .poster { width: 66px; border-radius: 4px; box-shadow: var(--shadow-art), inset 0 0 0 0.5px rgba(127, 127, 135, .22); }
.set-more-tile {
  display: grid; place-items: center; width: 66px; height: 99px; flex: none; border-radius: 4px;
  background: var(--fill); color: var(--text-2); font-size: 14px; font-weight: 600; letter-spacing: -0.01em; font-variant-numeric: tabular-nums;
}
.set-strip-empty { cursor: default; }
.set-tile-empty { color: var(--text-3); background: transparent; box-shadow: inset 0 0 0 1px var(--hairline); }
.set-tile-empty svg { width: 18px; height: 18px; }
.set-head { display: flex; align-items: center; gap: 8px; min-height: 40px; }
.set-open { flex: 1; min-width: 0; cursor: pointer; border-radius: 6px; }
.set-title { display: flex; align-items: flex-start; gap: 3px; min-width: 0; font-size: 14.5px; font-weight: 600; letter-spacing: -0.015em; line-height: 1.2; }
.set-title > span:first-child { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-height: 2.4em; }   /* two lines reserved: one-line names do not shrink the card */
.set-open:hover .set-title > span:first-child { color: var(--accent); }
.set-meta { display: flex; align-items: center; gap: 0; color: var(--text-meta); font-size: 12px; line-height: 16px; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.set-meta .sep { margin: 0 4px; color: var(--text-3); }
.set-chev { display: grid; place-items: center; width: 20px; height: 20px; flex: none; color: var(--text-3); }
.set-badges { display: flex; align-items: center; gap: 6px; height: 20px; margin-top: 7px; }
.set-list { border-top: 1px solid var(--hairline); margin-top: 9px; }
/* A card is as tall as what it holds: no reserved rows, no reserved toggle row. The grid top-aligns them
   (align-items: start on .sets), so uneven heights read as a column of cards rather than as gaps. */
.set-fold { position: relative; }
/* More to show: the foot of the list dissolves into the card and "Show N more" lies over that, rather
   than taking a row of its own. The wash is a gradient to the card's own surface, not a mask, and it
   reaches the surface colour before the bottom edge -- so the last row fades away as it goes and the
   button has clean ground under it instead of a title showing through. The fade is what says there is
   more; the button only has to be readable. It sits on the guide the film titles use (-6px pays back its
   own padding), the same place it rests once the list is open. */
.set-fold:not(.is-open)::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 52px; pointer-events: none;
  background: linear-gradient(to bottom, transparent, var(--surface) 72%);
}
.set-fold:not(.is-open) .set-toggle { position: absolute; left: -6px; bottom: 0; margin: 0; z-index: 1; }
.set-rest .set-list { margin-top: 0; border-top: 0; }
.set-item { display: grid; grid-template-columns: 20px minmax(0, 1fr) 34px 52px; column-gap: 8px; align-items: center; height: 28px; margin: 0 -4px; padding: 0 4px; border-radius: 5px; cursor: pointer; }
.set-item:hover { background: var(--hover); }
.set-item .seq { color: var(--text-3); font-variant-numeric: tabular-nums; font-size: 11.5px; text-align: right; }
.set-item .t { display: flex; align-items: center; gap: 2px; min-width: 0; font-weight: 500; font-size: 12.5px; }
.set-item .t .tx { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.set-item .yr, .set-item .m { color: var(--text-3); font-size: 11.5px; font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
/* the rest of a long set unfolds in place (grid-template-rows: a height transition without measuring) */
.set-rest { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .28s var(--ease); }
.set-rest > div { min-height: 0; overflow: hidden; }
.set-rest.is-open { grid-template-rows: 1fr; }
.set-toggle { align-self: flex-start; height: 26px; margin: 4px -6px 0; padding: 0 6px; border-radius: 6px; color: var(--accent); font-size: 12.5px; font-weight: 500; }
.set-toggle:hover { background: var(--accent-soft); }
/* set detail (Inspector) */
.set-covers { display: flex; flex-wrap: wrap; gap: 6px; }
.set-cover { display: block; width: 62px; border-radius: 4px; }
.set-cover .poster { box-shadow: var(--shadow-art), inset 0 0 0 0.5px rgba(127, 127, 135, .22); }
.set-cover:hover .poster { outline: 2px solid var(--accent); outline-offset: 1px; }
.linklist.set-films button { grid-template-columns: 24px minmax(0, 1fr) auto; }
.linklist.set-films .tx { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* groups: the box-set card reused, plus a per-row reorder cluster and a tile that starts a new list */
.linklist.g-list button { grid-template-columns: 24px minmax(0, 1fr) auto auto; }
.g-acts { display: inline-flex; gap: 1px; opacity: 0; transition: opacity .12s var(--ease); }
.linklist.g-list button:hover .g-acts, .g-acts:focus-within { opacity: 1; }
.g-act { display: grid; place-items: center; width: 20px; height: 20px; border-radius: 4px; color: var(--text-3); cursor: pointer; }
.g-act:hover { background: var(--hover); color: var(--text); }
.g-act.is-disabled { opacity: .25; pointer-events: none; }
.set-card.is-new { align-items: center; justify-content: center; gap: 6px; min-height: 200px; border-style: dashed; color: var(--text-2); cursor: pointer; font-size: 12.5px; font-weight: 500; }
.set-card.is-new:hover { border-color: var(--accent); color: var(--accent); }
.groups-hint { margin: -4px 16px 16px; max-width: 56ch; }
.h-set { display: inline-flex; align-items: center; gap: 4px; text-transform: inherit; letter-spacing: inherit; font: inherit; color: inherit; }
.h-set:hover { color: var(--accent); }
.h-set svg:last-child { width: 6px; height: 10px; opacity: .6; }

/* The way out of sidebar navigation, on the floor of the panel where a source list keeps its controls.
   It sits below the scroller rather than inside it, so it stays put while the list scrolls. */
.sb-foot {
  display: flex; align-items: center; gap: 8px; width: 100%; flex: none;
  margin-top: 6px; padding: 7px 9px; border-radius: var(--r-ctl);
  color: var(--text-3); font-size: 12.5px; font-weight: 500; text-align: left;
  transition: background .14s var(--ease), color .14s var(--ease);
}
.sb-foot:hover { background: var(--hover); color: var(--text); }
.sb-foot svg { width: 15px; height: 15px; flex: none; }

/* The phone's one control for view, format, filters, group and sort. Hidden on a pointer, where the
   filter bar has the room to show all of it at once. The dot says something is filtered, which the bar
   used to say by lighting a chip. */
.opt-btn { display: none; position: relative; }
.opt-dot {
  position: absolute; top: 4px; right: 4px; width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 2px var(--chrome);
}

/* ── The phone's tab pill ──────────────────────────────────────
   Sections and search, floating over the library within reach of a thumb, hugging its own contents rather
   than spanning the screen. Hidden entirely on a pointer, where the top bar already carries both. */
.tabpill {
  position: fixed; left: 50%; transform: translateX(calc(-50% + var(--tp-dx, 0px))); z-index: 55;
  /* --statusbar-h is a measured height and the status bar already pays for the home indicator in its own
     bottom padding, so adding var(--sab) here counted it twice and floated the pill a thumb's width too
     high on any phone that has one. */
  /* Move up runs negative as well, so the bar can tuck down towards the very bottom of the glass; max()
     is the floor that stops it being dragged off the screen on a phone with no home indicator to pay for. */
  bottom: max(2px, calc(var(--statusbar-h, 0px) + 6px + var(--sab) + var(--tp-dy, 0px)));
  display: flex; align-items: center; gap: 4px; padding: 5px 6px;
  border-radius: 9999px; border: 1px solid color-mix(in srgb, var(--text-3) 22%, transparent);
  /* Glass, not a bar: the library reads through it rather than behind it. The blur and the lift in
     saturation and brightness carry the legibility, so the tint can stay light -- View Options > Tab bar >
     Glass takes it from nearly clear to nearly solid, 45% by default. The two inset hairlines are what
     makes it read as something with thickness: a lit top edge where the light catches, a dimmer one at
     the bottom, with a specular wash over the whole face from ::before. */
  background: color-mix(in srgb, var(--chrome) var(--tp-tint, 35%), transparent);
  -webkit-backdrop-filter: saturate(220%) blur(var(--tp-blur, 23px)); backdrop-filter: saturate(220%) blur(var(--tp-blur, 23px));
  box-shadow: 0 8px 30px rgba(0, 0, 0, calc(.38 * var(--tp-shadow, 1))), 0 1px 3px rgba(0, 0, 0, calc(.26 * var(--tp-shadow, 1))),
    inset 0 1px 0 rgba(255, 255, 255, calc(.18 * var(--tp-sheen, .7))), inset 0 -1px 0 rgba(255, 255, 255, calc(.07 * var(--tp-sheen, .7)));
  /* max-content, not shrink-to-fit. A fixed element placed at left: 50% is offered only the space to the
     right of that line, so the pill came out half the screen wide and its tabs spilled past the corner.
     An explicit width lifts that constraint; the cap keeps it on screen. */
  /* Moving it sideways never takes it off the screen: whatever the offset spends on one side is taken
     off the cap, so a shifted pill narrows instead of hanging over the edge. */
  /* Width is now how much of the room it takes, not how fat each tab is: the bar is at least that share
     of the screen less its edge gap, and the tabs share whatever it gets. Inflating per-tab padding was
     what pushed the words out in the first place. */
  width: max-content;
  min-width: min(calc((100vw - 2 * var(--tp-gapx, 8px)) * var(--tp-fillk, .9)), calc(100vw - 2 * var(--tp-gapx, 8px)));
  max-width: calc(100vw - 2 * var(--tp-gapx, 8px) - 2 * max(var(--tp-dx, 0px), -1 * var(--tp-dx, 0px)));
  /* the spring the width rides on; openTabSearch() writes the pixel values it moves between */
  transition: width .46s cubic-bezier(.2, .9, .25, 1.06), transform .38s cubic-bezier(.2, .85, .25, 1);
}
@supports not ((backdrop-filter: blur(4px)) or (-webkit-backdrop-filter: blur(4px))) {
  .tabpill { background: color-mix(in srgb, var(--chrome) 92%, transparent); }   /* no blur: it has to be solid to read */
}
/* The specular face: a light source off the top of the pill, brightest where it would strike, fading out
   before the middle, with the faint return along the foot that a thick edge bounces back. Specular edge
   scales the lot. It sits under the tabs and takes no pointer events of its own. */
.tabpill::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 0;
  background:
    radial-gradient(120% 150% at 50% -42%, rgba(255, 255, 255, calc(.20 * var(--tp-sheen, .7))), rgba(255, 255, 255, 0) 58%),
    linear-gradient(180deg, rgba(255, 255, 255, calc(.10 * var(--tp-sheen, .7))), rgba(255, 255, 255, calc(.015 * var(--tp-sheen, .7))) 44%, rgba(255, 255, 255, calc(.07 * var(--tp-sheen, .7))));
}
/* The bulge. A flat pane and a lens differ in where the light gathers: a lens is bright at the rim, where
   the glass turns away and the light travels furthest through it, and slightly dark just inside that ring.
   That is what this draws -- a caustic band around the inside of the edge over a faint centre shading --
   and it is the closest a backdrop-filter can come to refraction without an SVG displacement map, which
   iOS will not run on a live backdrop. */
.tabpill::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 0;
  opacity: var(--tp-bulge, .45);
  background:
    radial-gradient(115% 150% at 50% 50%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, .24) 86%, rgba(255, 255, 255, .04) 100%),
    radial-gradient(90% 120% at 50% 46%, rgba(0, 0, 0, .12), rgba(0, 0, 0, 0) 70%);
  box-shadow: inset 0 0 18px rgba(255, 255, 255, .14), inset 0 0 0 .5px rgba(255, 255, 255, .45);
}
.tp-body, .tp-search { position: relative; z-index: 1; }
/* Refraction, where the browser will run it.
   An SVG displacement map -- neutral grey through the middle, ramping to the extremes at the rim -- fed
   through backdrop-filter, which bends what is behind the bar where the glass would be thickest.
   Two dead ends worth recording, both tested rather than assumed:
     - Scaling a layer that carries its own backdrop-filter does NOT magnify what it samples. The backdrop
       is sampled in the backdrop root's coordinates, not the element's, so a transform moves the element
       and leaves the sample where it was. That approach would have worked everywhere; it does nothing.
     - A child cannot refract what its parent already blurred: it samples everything painted beneath it,
       and a 23px blur leaves no detail to bend.
   So this is the only route, and it is Chrome and Edge only -- WebKit ignores a filter in backdrop-filter
   entirely. The control says so rather than sitting there doing nothing. */
.tp-filter { position: absolute; width: 0; height: 0; overflow: hidden; }
.tp-refract { display: none; }
.tabpill.is-refract .tp-refract {
  display: block; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 0;
  -webkit-backdrop-filter: url(#tpGlass); backdrop-filter: url(#tpGlass);
}
/* Searching on a phone: the bar rides up to sit under the top bar (--tp-lift is measured by the script),
   and the library makes room for it at the top so the first results are not underneath it. */
.tabpill.is-search { transform: translate(calc(-50% + var(--tp-dx, 0px)), calc(-1 * var(--tp-lift, 0px))); }
#content { transition: padding-top .3s var(--ease); }
#app.is-searching #content { padding-top: calc(var(--tabpill-h, 60px) + 14px); }
/* The tabs and the field share one cell, so whichever is hidden takes up no room at all. It has to be
   able to GROW as well: without flex: 1 the cell stayed at the tabs' width when the pill widened, so the
   search field never got any bigger and the separator and the close stayed where the tabs had left them,
   which is exactly what the expansion looked like -- a pill that got wider around nothing. */
.tp-body { display: grid; align-items: center; min-width: 0; flex: 1 1 auto; }
.tp-tabs, .tp-field { grid-area: 1 / 1; min-width: 0; }
.tp-tabs { position: relative; display: flex; align-items: center; gap: 1px; }
/* the highlight is its own element so it can travel between tabs instead of blinking from one to the next */
/* Concentric with the pill: the pill is a stadium, the highlight sits a uniform 5px inside it, so its
   radius is the pill's radius less that gap -- which at this height is simply half its own height, a
   stadium too. The tabs are padded wide enough that a label's bottom corners clear the curve. */
.tp-thumb {
  position: absolute; left: 0; top: 0; height: 100%; border-radius: var(--tp-tr, 9999px); z-index: 0;
  background: var(--fill-strong); opacity: 0;
  /* quick enough that the section can wait for it: the bar answers, then the page changes */
  transition: transform .22s cubic-bezier(.25, .9, .3, 1), width .22s cubic-bezier(.25, .9, .3, 1), opacity .18s var(--ease);
}
.tabpill.is-search .tp-thumb { opacity: 0; }
@media (prefers-reduced-motion: reduce) { .tp-thumb { transition: opacity .12s; } }
/* A glyph over its name, the way a phone's own tab bar reads: the icon carries the recognition and the
   label removes the guesswork. Sized so all five fit a 375px phone alongside the separator, and allowed to
   give way rather than collide -- flex-shrink is the safety net for a wider font or a narrower phone. */
.tp-tab {
  /* View Options > Tab bar stretches the box; what is inside it -- glyph, label, the gap between them --
     keeps its own size, so the type never ends up scaled to a half pixel. */
  padding: 0 10px; height: calc(60px * var(--tp-sy, 1)); border-radius: var(--tp-tr, 9999px); flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  color: var(--text-2); white-space: nowrap; overflow: hidden;
  transition: opacity .26s var(--ease), transform .3s cubic-bezier(.2, .8, .3, 1), background .14s var(--ease), color .14s var(--ease);
}
.tp-tab { position: relative; z-index: 1; }                 /* above the travelling highlight */
.tp-ico { display: grid; place-items: center; height: var(--tp-ico, 28px); }
.tp-ico svg { display: block; width: var(--tp-ico, 28px); height: var(--tp-ico, 28px); }
/* Never half a word: if the labels cannot all fit, the script drops them and the bar is glyphs alone. */
.tp-lab { font-size: 10.5px; font-weight: 500; letter-spacing: -0.01em; line-height: 1; max-width: 100%; overflow: hidden; white-space: nowrap; }
.tabpill.is-iconly .tp-lab { display: none; }
.tp-tab.is-active { color: var(--text); }
.tp-tab.is-active .tp-lab { font-weight: 600; }
.tp-dice:active, .tp-tab:active { color: var(--text); }
.tabpill.is-search .tp-tabs { pointer-events: none; }
/* Scoped to the tabs inside the rail: the search button carries .tp-tab as well now, so an unscoped rule
   faded out the very control the user had just pressed and took the close cross with it. */
.tabpill.is-search #tpTabs .tp-tab { opacity: 0; transform: translateY(60px) scale(.8); }
.tp-field { display: flex; align-items: center; opacity: 0; pointer-events: none; transform: translateX(18px); transition: opacity .24s var(--ease) .06s, transform .34s cubic-bezier(.2, .8, .3, 1) .06s; }
.tabpill.is-search .tp-field { opacity: 1; pointer-events: auto; transform: none; }
.tp-field input {
  /* room on the right: the field used to run straight into the close button now the divider is gone */
  width: 100%; min-width: 0; height: calc(56px * var(--tp-sy, 1)); padding: 0 14px 0 10px; border: 0; background: transparent; outline: 0;
  color: var(--text); font-size: 16px;                    /* 16px: anything smaller and iOS zooms the page */
  -webkit-appearance: none; appearance: none; border-radius: 9999px;
}
/* iOS draws its own square-cornered ring around a focused field, which sat inside a pill and looked like
   a mistake. The pill itself is the focus indicator here. */
.tp-field input:focus, .tp-field input:focus-visible { outline: none; box-shadow: none; }
.tp-field input::-webkit-search-decoration, .tp-field input::-webkit-search-cancel-button { -webkit-appearance: none; }
/* one button, two glyphs stacked: the magnifier turns into a close in place */
.tp-search { flex: none; }
.tp-search:hover { color: var(--text); }
.tp-search .tp-ico svg { grid-area: 1 / 1; transition: opacity .2s var(--ease), transform .28s cubic-bezier(.2, .8, .3, 1); }
.tp-ico-close { opacity: 0; transform: rotate(-90deg) scale(.7); }
.tabpill.is-search .tp-ico-find { opacity: 0; transform: rotate(90deg) scale(.7); }
.tabpill.is-search .tp-ico-close { opacity: 1; transform: none; }
.tabpill.is-search .tp-search { color: var(--text); }
@media (prefers-reduced-motion: reduce) {
  .tabpill, .tp-tab, .tp-field, .tp-search svg { transition-duration: .12s; }
  .tabpill.is-search #tpTabs .tp-tab { transform: none; }
  .tp-field { transform: none; }
  .tp-ico-close, .tabpill.is-search .tp-ico-find { transform: none; }
}

/* ── Copy title + text selection ──────────────────────────── */
.copy-btn {
  display: inline-grid; place-items: center; width: 18px; height: 18px; flex: none; border-radius: 4px;
  color: var(--text-3); opacity: 0; cursor: pointer; user-select: none; -webkit-user-select: none;
  transition: opacity .12s var(--ease), background .12s var(--ease), color .12s var(--ease);
}
.copy-btn svg { width: 12px; height: 12px; display: block; }
.copy-btn:hover { background: var(--fill); color: var(--text); }
.tt:hover .copy-btn, .row:hover .copy-btn, .card:hover .copy-btn, .detail-title:hover .copy-btn, .set-item:hover .copy-btn,
.set-title:hover .copy-btn, .copy-btn:focus-visible, .copy-btn.is-done { opacity: 1; }
.copy-btn.is-done { color: var(--ok); }
/* Copy is a pointer affordance: on touch there is no hover to reveal it, the phone's own text selection
   already copies a title, and a permanently visible 18px control beside every title was noise. Hidden on
   any touch screen and at the phone width, wherever the title is -- rows, cards, table cells, box sets,
   groups and the detail sheet. */
@media (hover: none), (max-width: 720px) { .copy-btn { display: none; } }
/* ordinary text can be selected and copied; controls stay non-selectable */
table.grid tbody td, .card-body, .row-title, .row-sub, .row-meta, .detail-body, .detail-head, .set-card, .log, .kv, .overview { user-select: text; -webkit-user-select: text; }
button, .segmented, .chip-btn, .icon-btn, .art-badges, .stars, .card-more, .th-inner, .group-inner, .section-head { user-select: none; -webkit-user-select: none; }

/* ── Changelog ────────────────────────────────────────────── */
.log { padding: 8px 16px 20px; max-width: 900px; }
.log-item { display: grid; grid-template-columns: 92px 1fr; gap: 16px; padding: 11px 0; border-bottom: 1px solid var(--hairline); }
.log-date { color: var(--text-3); font-variant-numeric: tabular-nums; font-size: 11.5px; padding-top: 1px; }
.log-batch { font-weight: 600; font-size: 12.5px; letter-spacing: -0.01em; display: flex; gap: 7px; align-items: center; flex-wrap: wrap; }
.log-change { margin-top: 2px; font-size: 12.5px; }
.log-notes { color: var(--text-2); margin-top: 3px; font-size: 12px; line-height: 1.45; }

/* ── Status bar ───────────────────────────────────────────── */
.statusbar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 5px 14px calc(5px + var(--sab)); font-size: 11.5px; color: var(--text-3);
  border-top: 1px solid var(--hairline); background: var(--bar); font-variant-numeric: tabular-nums;
  z-index: 20;
}
.statusbar .stat b { color: var(--text-2); font-weight: 500; }
.statusbar .stats { display: flex; gap: 12px; flex-wrap: wrap; }
.statusbar .stat + .stat { padding-left: 12px; border-left: 1px solid var(--hairline-2); }   /* quiet dividers between the counts */
.statusbar .src { color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center; gap: 7px; }
.statusbar .src b { color: var(--text-3); font-weight: 500; }
.statusbar .src a { color: inherit; text-decoration: none; }
.statusbar .src a:hover b { color: var(--text-2); text-decoration: underline; text-underline-offset: 2px; }
.dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--ok); flex: none; }
.dot.off { background: var(--warn); }
.dot.static { background: var(--text-3); }
.sync-pill {
  display: inline-flex; align-items: center; gap: 4px; padding: 1px 7px; border-radius: 9px;
  background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); font-weight: 500; cursor: pointer; white-space: nowrap;
}

/* ── Popovers ─────────────────────────────────────────────── */
.popover {
  position: fixed; z-index: 100; min-width: 200px; max-width: 320px; max-height: min(680px, 84vh);
  display: flex; flex-direction: column; border-radius: var(--r-pop); padding: var(--r-pop-pad);
  /* the height cap is the starting point only: openPopover() replaces it with the free space beside the
     anchor, so a menu scrolls inside its own surface only when the window really cannot hold it */
  overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
  background: var(--surface-2); box-shadow: var(--shadow-pop);
  animation: pop .16s var(--ease); transform-origin: top left;
}
@supports (backdrop-filter: blur(20px)) {
  .popover { background: color-mix(in srgb, var(--surface-2) 84%, transparent); -webkit-backdrop-filter: saturate(180%) blur(24px); backdrop-filter: saturate(180%) blur(24px); }
}
/* openPopover() sets transform-origin to the point that opened the menu, so the scale-up reads as the
   menu coming out of the chip or the poster you pressed rather than unfolding from a fixed corner. */
@keyframes pop { from { opacity: 0; transform: scale(.92); } to { opacity: 1; transform: none; } }
/* A dialog comes further, from the control that opened it: openPopover() puts the origin on that control,
   so a bigger scale reads as the thing unfolding out of the button rather than fading up in the middle. */
.popover.is-genie { animation: pop-genie .34s cubic-bezier(.2, .9, .28, 1.02); }
@keyframes pop-genie { from { opacity: 0; transform: scale(.32); } 60% { opacity: 1; } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .popover.is-genie { animation: pop .12s; } }

/* The dice dialog keeps the room its answer will need, so pressing the button does not shunt everything
   below it down and then up again. One card's worth, whether or not there is a card in it yet. */
/* The reserved box. Its height is one whole card -- poster, heading, synopsis and buttons -- so the roll
   happens inside a stationary dialog instead of pushing everything under it around. */
.rnd-slot { min-height: var(--rnd-slot-h); display: flex; align-items: flex-start; }
.rnd-slot:empty::before { content: "Nothing picked yet"; color: var(--text-3); font-size: 12.5px; padding: 6px 2px; }
.rnd-slot > * { width: 100%; }
/* the three cycles: a flick, a slot reel coming to rest, and a cross-fade */
.rnd-spin { animation: rnd-reel .17s cubic-bezier(.3, .7, .4, 1); }
@keyframes rnd-reel { from { transform: translateY(58%); opacity: 0; } to { transform: none; opacity: 1; } }
.rnd-card:not(.rnd-spin) { animation: rnd-land .26s cubic-bezier(.2, .85, .25, 1); }
@keyframes rnd-land { from { transform: translateY(34%); opacity: .4; } to { transform: none; opacity: 1; } }
.rnd-slot { overflow: hidden; }
/* .menu-* rules are shared with the sidebar's inline View Options section (.sb-opts) */
:is(.popover, .sb-opts, .rnd-page) .menu-title { padding: 5px 9px 3px; font-size: 10.5px; font-weight: 600; color: var(--text-3); text-transform: uppercase; letter-spacing: .05em; display: flex; align-items: center; gap: 6px; }
.popover .menu-title .menu-count { text-transform: none; letter-spacing: 0; font-weight: 500; font-size: 10.5px; color: var(--text-3); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.popover .menu-back { color: var(--accent); text-transform: none; letter-spacing: 0; font-size: 11.5px; font-weight: 500; }
.popover .menu-search { margin: 2px 2px 4px; }
.popover .menu-search input { width: 100%; height: 28px; padding: 0 8px; border-radius: var(--r-ctl); border: 0; background: var(--fill); outline: 0; font-size: 16px; color: var(--text); }
@media (min-width: 721px) { .popover .menu-search input { font-size: 12.5px; height: 26px; } }
.popover .menu-search input:focus { box-shadow: 0 0 0 2px var(--accent-soft-2); }
.popover .menu-list { overflow: auto; flex: 1; scrollbar-width: thin; }
:is(.popover, .sb-opts, .rnd-page) .menu-item {
  display: flex; align-items: center; gap: 7px; width: 100%; padding: 5px 9px; border-radius: var(--r-pop-in);
  text-align: left; font-size: 12.5px; color: var(--text);
}
:is(.popover, .sb-opts, .rnd-page) .menu-item:hover { background: var(--accent); color: #fff; }
:is(.popover, .sb-opts, .rnd-page) .menu-item:hover .menu-count, :is(.popover, .sb-opts, .rnd-page) .menu-item:hover .check, .popover .menu-item:hover .menu-chev { color: inherit; opacity: .85; }
:is(.popover, .sb-opts, .rnd-page) .menu-item .check { width: 12px; flex: none; color: var(--accent); font-weight: 600; font-size: 11px; }
:is(.popover, .sb-opts, .rnd-page) .menu-item .menu-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
:is(.popover, .sb-opts, .rnd-page) .menu-item .menu-count { color: var(--text-3); font-size: 11px; font-variant-numeric: tabular-nums; }
.popover .menu-item .menu-chev { color: var(--text-3); font-size: 13px; margin-left: 2px; }
/* View Options is a panel of settings rather than a list of things to pick, and it was set a size below
   everything it controls. Scoped to that menu (and the sidebar's inline copy) so the pick-one menus in
   the filter bar keep their compact rhythm. */
:is(.popover.is-vo, .sb-opts) .menu-item { font-size: 13.5px; padding: 6px 9px; }
:is(.popover.is-vo, .sb-opts) .menu-item .menu-count { font-size: 12px; }
:is(.popover.is-vo, .sb-opts) .menu-title { font-size: 11.5px; }
:is(.popover.is-vo, .sb-opts) .menu-seg > button { height: 26px; font-size: 12.5px; }
:is(.popover, .sb-opts, .rnd-page) .menu-item.is-disabled { opacity: .4; pointer-events: none; }
/* A label on the left, a switch on the right: a state rather than one of a list you tick, so it carries no
   tick column. The track is a squircle rather than a pill, to sit with the rest of the panel, and the knob
   is concentric with it -- the track's radius less its own 2px inset. */
:is(.popover, .sb-opts, .rnd-page) .menu-switch {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%;
  padding: 5px 9px; border-radius: var(--r-pop-in); text-align: left; font-size: 12.5px; color: var(--text);
}
:is(.popover.is-vo, .sb-opts) .menu-switch { font-size: 13.5px; padding: 6px 9px; }
:is(.popover, .sb-opts, .rnd-page) .menu-switch .menu-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
:is(.popover, .sb-opts, .rnd-page) .menu-switch .sw {
  flex: none; width: 38px; height: 22px; border-radius: 8px; padding: 2px; background: var(--fill-strong);
  display: block; transition: background .18s var(--ease);
}
:is(.popover, .sb-opts, .rnd-page) .menu-switch .sw-knob {
  display: block; width: 18px; height: 18px; border-radius: 6px; background: #fff;
  box-shadow: 0 0.5px 1px rgba(0, 0, 0, .28), 0 2px 4px rgba(0, 0, 0, .28);
  transition: transform .18s var(--ease);
}
:is(.popover, .sb-opts, .rnd-page) .menu-switch[aria-checked="true"] .sw { background: var(--accent); }
:is(.popover, .sb-opts, .rnd-page) .menu-switch[aria-checked="true"] .sw-knob { transform: translateX(16px); }
:is(.popover, .sb-opts, .rnd-page) .menu-switch:hover { background: var(--hover); }
@media (prefers-reduced-motion: reduce) { :is(.popover, .sb-opts, .rnd-page) .menu-switch .sw, :is(.popover, .sb-opts, .rnd-page) .menu-switch .sw-knob { transition: none; } }
/* View Options › Posters / Table: a small segmented size picker and a spacing slider, inset like menu items */
:is(.popover, .sb-opts, .rnd-page) .menu-seg { display: flex; margin: 2px 9px 6px; padding: 2px; border-radius: 9px; background: var(--fill); }
:is(.popover, .sb-opts, .rnd-page) .menu-seg > button { flex: 1; height: 22px; border-radius: 7px; font-size: 11.5px; font-weight: 500; color: var(--text-2); transition: background .14s var(--ease), color .14s var(--ease); }
:is(.popover, .sb-opts, .rnd-page) .menu-seg > button:hover { color: var(--text); }
:is(.popover, .sb-opts, .rnd-page) .menu-seg > button.is-active { background: var(--surface-3); color: var(--text); font-weight: 600; box-shadow: 0 1px 2px rgba(0, 0, 0, .18); }
/* Label and value on one line, the track on its own beneath: every slider is then the same width whatever its label says, and the labels all start on the same guide. */
/* Sliders read as settings you adjust by hand, so they carry a size up from the menu items around them:
   13.5px labels, a 5px track and an 18px puck. At 12.5/4/14 the knob was a small target for a control
   people drag, and the value beside the label was the smallest type in the app. */
:is(.popover, .sb-opts, .rnd-page) .menu-slider { display: block; padding: 5px 9px 8px; font-size: 13.5px; color: var(--text); cursor: default; }
:is(.popover, .sb-opts, .rnd-page) .menu-slider-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; padding-bottom: 5px; }
:is(.popover, .sb-opts, .rnd-page) .menu-slider .menu-label { flex: 1 1 auto; min-width: 0; }
:is(.popover, .sb-opts, .rnd-page) .menu-slider .menu-count { flex: none; text-align: right; color: var(--text-3); font-size: 12.5px; font-variant-numeric: tabular-nums; }
/* the track is drawn by hand so the filled part can carry the accent and the knob can be a proper
   macOS puck; --range-fill is written by setRangeFill() from the input's own value */
:is(.popover, .sb-opts, .rnd-page) .menu-slider input[type="range"] {
  display: block; width: 100%; height: 22px; margin: 0; cursor: pointer;
  -webkit-appearance: none; appearance: none; background: transparent;
}
:is(.popover, .sb-opts, .rnd-page) .menu-slider input[type="range"]::-webkit-slider-runnable-track {
  height: 5px; border-radius: 2.5px;
  background: linear-gradient(to right, var(--accent) 0 var(--range-fill, 50%), var(--fill-strong) var(--range-fill, 50%) 100%);
}
:is(.popover, .sb-opts, .rnd-page) .menu-slider input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 18px; height: 18px; margin-top: -6.5px;
  border-radius: 50%; background: #fff;
  box-shadow: 0 0.5px 1px rgba(0, 0, 0, .28), 0 2px 5px rgba(0, 0, 0, .3), inset 0 0 0 .5px rgba(0, 0, 0, .06);
  transition: transform .12s var(--ease);
}
:is(.popover, .sb-opts, .rnd-page) .menu-slider input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.12); }
:is(.popover, .sb-opts, .rnd-page) .menu-slider input[type="range"]::-moz-range-track { height: 5px; border-radius: 2.5px; background: var(--fill-strong); }
:is(.popover, .sb-opts, .rnd-page) .menu-slider input[type="range"]::-moz-range-progress { height: 5px; border-radius: 2.5px; background: var(--accent); }
:is(.popover, .sb-opts, .rnd-page) .menu-slider input[type="range"]::-moz-range-thumb { width: 18px; height: 18px; border: 0; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .35); }
:is(.popover, .sb-opts, .rnd-page) .menu-slider.is-dim { opacity: .45; }
/* a step either side of the track, for the last unit of precision a thumb cannot manage */
:is(.popover, .sb-opts, .rnd-page) .menu-slider-row { display: flex; align-items: center; gap: 7px; }
:is(.popover, .sb-opts, .rnd-page) .menu-slider-row input[type="range"] { flex: 1 1 auto; min-width: 0; }
:is(.popover, .sb-opts, .rnd-page) .slider-step {
  flex: none; width: 24px; height: 24px; border-radius: 7px; display: grid; place-items: center;
  background: var(--fill); color: var(--text-2); font-size: 15px; line-height: 1; font-weight: 500;
  transition: background .14s var(--ease), color .14s var(--ease), transform .1s var(--ease);
}
:is(.popover, .sb-opts, .rnd-page) .slider-step:hover { background: var(--fill-strong); color: var(--text); }
:is(.popover, .sb-opts, .rnd-page) .slider-step:active { transform: scale(.9); }
:is(.popover, .sb-opts, .rnd-page) .slider-step:disabled { opacity: .4; }
/* Ticks: notches under the track, at the ends of the travel and evenly between, so a dial with no natural
   landmarks still has some. The 9px inset is half the knob, so the first and last notch sit under the
   positions the knob actually reaches rather than under the ends of the box. */
:is(.popover, .sb-opts, .rnd-page) .menu-slider.has-ticks .menu-ticks {
  display: block; height: 4px; margin: -2px 40px 2px;
  background-image: repeating-linear-gradient(90deg, var(--text-3) 0 1px, transparent 1px calc(100% / var(--ticks, 8)));
  background-size: calc(100% + 1px) 100%; background-repeat: no-repeat; opacity: .5;
}
:is(.popover, .sb-opts, .rnd-page) .menu-slider.is-dim input[type="range"] { cursor: default; }
/* the two-axis group: its own squircle, so it reads as one setting with a switch */
:is(.popover, .sb-opts, .rnd-page) .vo-group {
  margin: 2px 9px 6px; padding: 3px 0 4px; border-radius: 10px;
  background: var(--fill); box-shadow: inset 0 0 0 .5px var(--hairline-2);
}
:is(.popover, .sb-opts, .rnd-page) .vo-group .menu-item { padding-left: 8px; }
:is(.popover, .sb-opts, .rnd-page) .vo-group .menu-slider { padding: 3px 9px 5px; }
:is(.popover, .sb-opts, .rnd-page) .vo-group .menu-item { padding-left: 9px; }
.sb-opts .vo-group { margin: 2px 4px 6px; }
.popover .menu-item.is-off { opacity: .4; cursor: default; }
/* column-header menu › Align: an icon-only segment inside a labelled row (scoped so it never restyles the size pickers above) */
.popover .menu-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 3px 9px 3px 27px; font-size: 12.5px; color: var(--text); }
.popover .menu-row .menu-seg { display: inline-flex; gap: 2px; margin: 0; padding: 2px; border-radius: 6px; background: var(--fill); }
.popover .menu-row .menu-seg button { flex: none; width: 26px; height: 20px; border-radius: 4px; display: grid; place-items: center; color: var(--text-2); }
.popover .menu-row .menu-seg button:hover { color: var(--text); }
.popover .menu-row .menu-seg button.is-active { background: var(--surface-3); color: var(--text); box-shadow: 0 0.5px 1.5px rgba(0, 0, 0, .28); }
.popover .menu-item .menu-icon { width: 15px; text-align: center; opacity: .75; font-size: 12px; }
:is(.popover, .sb-opts, .rnd-page) .menu-sep { height: 1px; margin: 4px 6px; background: var(--hairline); }
/* the sidebar's inline View Options section: the same controls as the popover, on the panel's row rhythm */
.sb-opts-in { display: flex; flex-direction: column; gap: 1px; padding: 0 0 4px; }
.sb-opts .menu-title { padding: 6px 8px 2px; }
.sb-opts .menu-item { padding: 0 8px; height: 28px; border-radius: 8px; font-size: 12.5px; }
.sb-opts .menu-seg { margin: 2px 8px 5px; }
.sb-opts .menu-slider { padding: 2px 8px 5px; }
.sb-opts .menu-sep { margin: 4px 4px; }
:is(.popover, .sb-opts, .rnd-page) .menu-foot { display: flex; justify-content: space-between; gap: 6px; padding: 3px 3px 1px; }
:is(.popover, .sb-opts, .rnd-page) .menu-foot button { padding: 4px 8px; border-radius: var(--r-ctl); color: var(--accent); font-size: 12.5px; font-weight: 500; }
:is(.popover, .sb-opts, .rnd-page) .menu-foot button:hover { background: var(--accent-soft); }
:is(.popover, .sb-opts, .rnd-page) .menu-foot button:disabled { color: var(--text-3); background: none; cursor: default; }
/* View Options › Reset spans the panel, so it reads as acting on everything above it, not on one section */
/* View Options > Spacing: a disclosure on the panel's own rhythm, with the section rolling open under it
   on grid-template-rows the way the box-set folds do, so nothing has to measure a height. */
:is(.popover, .sb-opts, .rnd-page) .menu-disc {
  display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%;
  padding: 6px 9px; border-radius: var(--r-pop-in); text-align: left;
  font-size: 10.5px; font-weight: 600; color: var(--text-3); text-transform: uppercase; letter-spacing: .05em;
}
:is(.popover, .sb-opts, .rnd-page) .menu-disc:hover { background: var(--hover); color: var(--text-2); }
:is(.popover, .sb-opts, .rnd-page) .menu-disc svg { width: 10px; height: 10px; flex: none; transition: transform .22s var(--ease); }
:is(.popover, .sb-opts, .rnd-page) .menu-disc.is-open svg { transform: rotate(180deg); }
:is(.popover, .sb-opts, .rnd-page) .vo-fold { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .28s var(--ease); }
:is(.popover, .sb-opts, .rnd-page) .vo-fold > div { min-height: 0; overflow: hidden; }
:is(.popover, .sb-opts, .rnd-page) .vo-fold.is-open { grid-template-rows: 1fr; }
@media (prefers-reduced-motion: reduce) { :is(.popover, .sb-opts, .rnd-page) .vo-fold { transition: none; } }
:is(.popover, .sb-opts, .rnd-page) .vo-foot { justify-content: stretch; }
:is(.popover, .sb-opts, .rnd-page) .vo-foot button { flex: 1; text-align: left; }
.sb-opts .vo-foot { padding: 2px 4px 0; }
.sb-opts .vo-foot button { padding: 0 8px; height: 28px; border-radius: 8px; }
/* an Apple-style circled i beside a heading: the explanation is in its tooltip */
:is(.popover, .sb-opts, .rnd-page) .menu-title { position: relative; }
:is(.popover, .sb-opts, .rnd-page) .menu-info {
  flex: none; display: grid; place-items: center; width: 16px; height: 16px; margin: -2px 0 -2px 1px;
  border-radius: 50%; color: var(--text-3); transition: color .14s var(--ease);
}
:is(.popover, .sb-opts, .rnd-page) .menu-info:hover, :is(.popover, .sb-opts, .rnd-page) .menu-info:focus-visible { color: var(--accent); }
/* a section's own reset, opposite its heading */
:is(.popover, .sb-opts, .rnd-page) .menu-reset { margin-left: auto; color: var(--accent); font-size: 10.5px; font-weight: 500; letter-spacing: 0; text-transform: none; padding: 1px 4px; margin-right: -4px; border-radius: 5px; }
:is(.popover, .sb-opts, .rnd-page) .menu-reset:hover { background: var(--accent-soft); }
:is(.popover, .sb-opts, .rnd-page) .menu-reset:disabled { color: var(--text-3); opacity: .55; pointer-events: none; }
:is(.popover, .sb-opts, .rnd-page) .menu-info svg { display: block; }
/* a quiet sub-heading inside a section (Posters › Corners) */
:is(.popover, .sb-opts, .rnd-page) .menu-sub { padding: 4px 9px 2px; font-size: 11px; color: var(--text-3); }
.sb-opts .menu-sub { padding-left: 8px; }

/* Cover-size slider: a poster-shaped preview that follows the drag */
:is(.popover, .sb-opts, .rnd-page) .menu-preview { display: flex; align-items: flex-end; min-height: 40px; padding: 0 9px 6px 27px; }
:is(.popover, .sb-opts, .rnd-page) .cp-box {
  width: var(--cp); height: calc(var(--cp) * 1.5); border-radius: 3px;
  background: var(--fill-strong); box-shadow: inset 0 0 0 .5px var(--hairline-2);
}
.sb-opts .menu-preview { padding-left: 8px; }

/* Columns menu: title row with a tool button, picked rows, and a quiet note line */
.popover .menu-titlerow { display: flex; align-items: center; justify-content: space-between; gap: 6px; padding-right: 5px; }
.popover .menu-titlerow .menu-title { flex: 1 1 auto; }
.popover .menu-tool {
  flex: none; width: 24px; height: 22px; border-radius: 6px; display: grid; place-items: center;
  color: var(--text-2); transition: background .14s var(--ease), color .14s var(--ease);
}
.popover .menu-tool:hover { background: var(--fill); color: var(--text); }
.popover .menu-item.is-picked { background: var(--fill); box-shadow: inset 2px 0 0 var(--accent); }
.popover .menu-item.is-picked .menu-label { color: var(--text); font-weight: 600; }
.popover .menu-note { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 1px 9px 5px 27px; font-size: 11px; color: var(--text-2); }
.popover .menu-note.is-quiet { color: var(--text-3); }
.popover .menu-note button { color: var(--accent); font-size: 11px; }
.popover .menu-note button:hover { text-decoration: underline; }

.popover .menu-item .mv { display: none; gap: 2px; }
.popover .menu-item:hover .mv { display: inline-flex; }
.popover .menu-item .mv button { width: 19px; height: 19px; border-radius: 4px; display: grid; place-items: center; color: inherit; }
.popover .menu-item .mv button:hover { background: rgba(255, 255, 255, .2); }
.popover .menu-item .mv button:disabled { opacity: .3; }

/* ── Inspector / detail panel ─────────────────────────────── */
.scrim { display: none; }
/* The inspector floats over the library: absolutely placed inside .workspace, so it is out of the
   flex flow and opening it cannot resize, reflow or re-scroll the grid behind it. */
/* Docked, the library gives up the width rather than being covered -- no poster is ever hidden behind the
   panel -- but the panel FLOATS and it is the content's padding that reserves the room. Do not put it
   back in the flow as a flex item. That was tried: a flex item cannot arrive from nothing without
   reflowing its own contents on the way, and the alternative of easing the width open was measured at
   about 140ms a frame with 637 cards, because changing the grid's width re-resolves the track sizes for
   the entire library. At six frames a second it arrived in three visible steps, which is worse than not
   moving at all. So the padding is taken in one step and #content.is-reflow fades up over it -- one
   composited property, no layout. Below 1001px this is a drawer or a sheet instead. */
.detail {
  position: absolute; top: var(--insp-inset); right: var(--insp-inset); bottom: var(--insp-inset);
  width: var(--inspector-w); min-width: 320px; max-width: min(620px, calc(100% - 2 * var(--insp-inset)));
  z-index: 40; border-radius: var(--r-insp); border: 1px solid var(--insp-edge);
  background: var(--insp-glass); -webkit-backdrop-filter: var(--insp-blur); backdrop-filter: var(--insp-blur);
  box-shadow: var(--shadow-insp);
  display: none; flex-direction: column; overflow: hidden;
}
.detail.is-open { display: flex; animation: inspectIn .44s var(--ease-spring); }
@keyframes inspectIn {
  from { opacity: 0; transform: translateX(30px) scale(.978); }
  55% { opacity: 1; }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .detail.is-open { animation-duration: .01s; } }
@media (prefers-reduced-transparency: reduce) {
  .detail { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--inspector); }
}
.inspector-resize { position: absolute; left: 0; top: 0; bottom: 0; width: 8px; cursor: col-resize; z-index: 7; border-radius: var(--r-insp) 0 0 var(--r-insp); }
.inspector-resize:hover, .inspector-resize.is-resizing { background: var(--accent); opacity: .45; }
/* ── Movie inspector: hero, one primary action, quiet rows ─────
   Everything uncommon lives behind a disclosure row or the … menu, so the panel reads as
   artwork → title → what this film is → what you can do with it. */
.insp-close {
  position: absolute; top: 12px; right: 12px; z-index: 8; width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center; color: rgba(255, 255, 255, .8); background: rgba(0, 0, 0, .34);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  opacity: .68; transition: opacity .16s var(--ease), background .16s var(--ease), color .16s var(--ease);
}
.insp-close::before, .insp-back::before { content: ""; position: absolute; inset: -5px; border-radius: 50%; }   /* 44px hit target */
.insp-back {
  position: absolute; top: 12px; left: 12px; z-index: 8; width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center; color: rgba(255, 255, 255, .8); background: rgba(0, 0, 0, .34);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  opacity: .68; transition: opacity .16s var(--ease), background .16s var(--ease), color .16s var(--ease);
}
.insp-back:hover, .insp-back:focus-visible { opacity: 1; background: rgba(0, 0, 0, .55); color: #fff; }
.insp-back svg { display: block; }
/* a panel with a written head puts Back on the title's line */
.panel-back {
  flex: none; width: 26px; height: 26px; margin-top: 0; border-radius: 50%; display: grid; place-items: center;
  background: var(--fill); color: var(--text-2);
}
.panel-back:hover { background: var(--fill-strong); color: var(--text); }
.panel-back svg { display: block; }
.insp-close:hover, .insp-close:focus-visible { opacity: 1; background: rgba(0, 0, 0, .55); color: #fff; }
:root[data-theme="light"] .insp-close { color: rgba(255, 255, 255, .92); background: rgba(0, 0, 0, .3); }

/* The artwork is masked away rather than covered by a gradient of its own: it fades into whatever the
   panel's surface happens to be, so there is no band where one colour meets another. The tail of the
   fade sits under the title, which is why the hero pulls the block up over itself. */
/* Only the hero wanted the body's side padding, and .insp-main carries its own, so the movie panel's
   body has none and the artwork runs the full width of the panel. Its scrollbar overlays rather than
   taking a gutter out of that width — which is what macOS does anyway, and the only way artwork can
   reach both edges of a scrolling panel. */
.detail-body.insp-body { padding-left: 0; padding-right: 0; scrollbar-width: none; }
.detail-body.insp-body::-webkit-scrollbar { width: 0; height: 0; }
/* The hero is 520px of artwork that occupies only 270px of the panel: the rest is pulled back by the
   negative margin, so the title, the badges and the synopsis sit exactly where they would with no
   hero at all and the artwork runs on underneath them. That is what lets the fade span past the
   Play Trailer button without the block above it growing by a pixel. Height and margin move
   together -- their sum is the 270px of layout the panel actually gives up. The first stop is
   150px (29% of 520) because that is where the fade began when the hero was a plain 300px block. */
.insp-hero {
  position: relative; flex: none; height: 520px; overflow: hidden; background: var(--surface-2);
  margin: 0 0 -250px;
  -webkit-mask-image: linear-gradient(to bottom, #000 0 29%, rgba(0, 0, 0, .34) 52%, rgba(0, 0, 0, .17) 70%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0 29%, rgba(0, 0, 0, .34) 52%, rgba(0, 0, 0, .17) 70%, transparent 100%);
}
/* a blurred, over-scaled copy of the same artwork: whatever the shape of the file, the panel is full */
.insp-bed { position: absolute; inset: -32px; background-size: cover; background-position: 50% 45%; filter: blur(26px) saturate(1.3) brightness(.62); }
.insp-art, .insp-hero img { position: relative; display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 14%; }
.insp-hero .poster-ph { width: 100%; height: 100%; border-radius: 0; }
.insp-hero .poster-ph > div { display: none; }        /* the title is right below the hero; no need to repeat it */
.insp-main { position: relative; }
.insp-row .ir-a { flex-wrap: wrap; justify-content: flex-end; }
.insp-main { padding: 2px 18px 20px; }
.insp-title { display: flex; align-items: flex-start; gap: 5px; margin: 0; font-family: var(--font-display); font-size: 25px; font-weight: 600; letter-spacing: -0.025em; line-height: 1.1; color: var(--text); }
.insp-title .copy-btn { margin-top: 5px; }
.insp-line { margin-top: 7px; color: var(--text-2); font-size: 12.5px; }
.insp-line.is-quiet { margin-top: 5px; color: var(--text-3); }
.insp-badges { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 13px; }
.insp-overview { margin: 14px 0 0; color: var(--text-2); font-size: 13px; line-height: 1.52; }
.insp-actions { display: flex; align-items: center; gap: 9px; margin-top: 17px; }
.insp-play {
  flex: 1 1 auto; height: 43px; padding: 0 18px; border-radius: 22px; gap: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  background: #f2f2f4; color: #101013; font-weight: 600; font-size: 14.5px; letter-spacing: -0.01em;
  transition: background .14s var(--ease), transform .14s var(--ease);
}
.insp-play:hover { background: #fff; }
.insp-play:active { transform: scale(.985); }
.insp-play svg { display: block; width: 13px; height: 13px; margin-left: -2px; }   /* a triangle's optical centre sits right of its box */
.insp-circ {
  flex: none; width: 43px; height: 43px; border-radius: 50%; display: grid; place-items: center;
  background: var(--fill); color: var(--text); transition: background .14s var(--ease), color .14s var(--ease);
}
.insp-circ:hover { background: var(--fill-strong); }
.insp-circ svg { display: block; width: 17px; height: 17px; }
.insp-circ.is-fav { color: #ff4d5e; }

/* Carrying the artwork down past the title block puts this text on the poster rather than on the
   panel's own surface, so the runs of it that have no background of their own get a halo to hold
   their edges on a bright or busy still. Scoped to exactly those: the format and rating pills carry
   backgrounds already, Play Trailer is an opaque button, and .insp-rows begins below the point
   where the artwork has finished fading out. */
.insp-title, .insp-line, .insp-overview, .insp-badges .lbl { text-shadow: 0 1px 2px var(--insp-art-ink); }

.insp-rows { margin-top: 20px; }
.insp-row, .insp-disc {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; padding: 7px 0;
  border-top: 1px solid var(--hairline); font-size: 13px; text-align: left;
}
.insp-rows > :last-child { border-bottom: 1px solid var(--hairline); }
.insp-row .ir-k, .insp-disc .ir-k { flex: none; min-width: 82px; color: var(--text-2); }
.insp-row .ir-v { flex: 1 1 auto; min-width: 0; color: var(--text); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.insp-row .ir-v .muted { color: var(--text-3); }
.insp-row .ir-a { flex: none; display: flex; align-items: center; gap: 6px; }
.insp-disc {
  color: var(--text); font-weight: 500; justify-content: space-between;
  border-radius: 7px; padding-left: 7px; padding-right: 7px; margin-left: -7px; margin-right: -7px;
  transition: background .13s var(--ease), color .13s var(--ease);
}
.insp-disc:hover { color: var(--accent); }
.insp-disc:active { background: var(--fill); }
@keyframes panelIn { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }
.detail-body.panel-in { animation: panelIn .26s cubic-bezier(.22, 1, .36, 1) both; }
@media (prefers-reduced-motion: reduce) { .detail-body.panel-in { animation-duration: .01s; } }
/* the glyph is 8x13 inside a line box taller than itself, so it needs a box of its own to turn in */
.insp-disc .ir-chev { flex: none; display: grid; place-items: center; width: 14px; height: 14px; color: var(--text-3); transition: transform .26s cubic-bezier(.22, 1, .36, 1); }
.insp-disc .ir-chev svg { display: block; }
.insp-disc.is-open .ir-chev { transform: rotate(90deg); }
.insp-panel { padding: 2px 0 14px; border-top: 1px solid var(--hairline); margin-top: -1px; }
.insp-panel[hidden] { display: none; }
.insp-panel .kv > div:nth-last-child(-n+2) { border-bottom: 0; }
.insp-panel .req-list { margin-top: 0; }
.insp-panel-foot { padding-top: 8px; }
.insp-panel-foot .h-act { color: var(--accent); font-size: 12px; padding: 2px 4px; margin: -2px -4px; border-radius: 4px; }
.insp-panel-foot .h-act:hover { background: var(--accent-soft); }
.insp-panel .hint { margin-top: 6px; }
.insp-rows .star-row { gap: 8px; }
.insp-row .ir-state { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.insp-row .ir-state .watched-mark { color: var(--text-2); }
.insp-rows .star-input { font-size: 17px; }

.detail-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; padding: 16px 16px 10px; }
.detail-title { display: flex; align-items: flex-start; gap: 4px; font-family: var(--font-display); font-size: 20px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.15; }
.detail-title .copy-btn { margin-top: 2px; }
.detail-sub { color: var(--text-2); margin-top: 3px; font-size: 12.5px; }
.detail-close { flex: none; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center; background: var(--fill); color: var(--text-2); }
.detail-close:hover { background: var(--fill-strong); color: var(--text); }
.detail-body { overflow: auto; padding: 0 16px calc(16px + var(--sab)); scrollbar-width: thin; -webkit-overflow-scrolling: touch; }
.detail-hero { display: grid; grid-template-columns: 104px 1fr; gap: 12px; padding: 2px 0 12px; align-items: start; }
.detail-hero .poster { box-shadow: var(--shadow-art), inset 0 0 0 0.5px rgba(127, 127, 135, .25); border-radius: 6px; }
.detail-hero .hero-side { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.detail-badges { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.detail-section { margin-top: 16px; }
.detail-section h4 {
  margin: 0 0 6px; font-size: 10.5px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-3); display: flex; justify-content: space-between; align-items: center;
}
.detail-section h4 .h-act { text-transform: none; letter-spacing: 0; font-weight: 500; color: var(--accent); font-size: 12px; padding: 2px 4px; margin: -2px -4px; border-radius: 4px; }
.detail-section h4 .h-act:hover { background: var(--accent-soft); }
.detail-body > .detail-badges { padding-top: 2px; }
.kv { display: grid; grid-template-columns: 104px 1fr; }
.kv > div { padding: 5px 0; font-size: 12.5px; min-width: 0; border-bottom: 1px solid var(--hairline); }
.kv > div:nth-last-child(-n+2) { border-bottom: 0; }
.kv .k { color: var(--text-3); }
.kv .v { color: var(--text); word-break: break-word; }
.kv .v.is-empty { color: var(--text-3); }
.overview { color: var(--text-2); font-size: 12.5px; line-height: 1.5; }
.linklist { display: flex; flex-direction: column; border: 1px solid var(--hairline); border-radius: var(--r-card); overflow: hidden; }
/* The first cell holds either a sequence number or a format badge, and a badge is wider than a
   number: sizing the column to its content keeps the gap after it instead of letting the badge run
   into the label. */
.linklist button { display: grid; grid-template-columns: minmax(22px, auto) minmax(0, 1fr) auto; gap: 9px; align-items: center; padding: 9px 11px; text-align: left; border-bottom: 1px solid var(--hairline); font-size: 12.5px; }
.linklist .seq .fmt-badge, .linklist .seq .ico { vertical-align: middle; }
.linklist button:last-child { border-bottom: 0; }
.linklist button:hover { background: var(--hover); }
.linklist button.is-current { color: var(--accent); font-weight: 600; }
.linklist .seq { color: var(--text-3); font-size: 11.5px; font-variant-numeric: tabular-nums; }
.linklist .m { color: var(--text-3); font-size: 11px; }
.detail-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 16px 0 0; }
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: var(--inspector-action-h); padding: 0 12px;
  border-radius: var(--r-ctl); background: var(--fill); color: var(--text); font-weight: 500; font-size: 12.5px; line-height: 1;
  white-space: nowrap; flex: none; transition: background .14s var(--ease), color .14s var(--ease);
}
/* Edit / Add another edition / Delete: one explicit height, no text-driven growth */
.detail-actions .btn { height: var(--inspector-action-h); padding: 0 12px; }
.btn:hover { background: var(--fill-strong); }
.btn.primary { background: var(--accent); color: var(--accent-ink); font-weight: 600; }
.btn.primary:hover { background: color-mix(in srgb, var(--accent) 88%, white); }
.btn.danger { color: var(--danger); }
.btn.danger:hover { background: color-mix(in srgb, var(--danger) 16%, transparent); }
.btn.small { height: 24px; padding: 0 9px; font-size: 12px; }
.btn:disabled { opacity: .45; pointer-events: none; }
.art-actions { display: flex; flex-wrap: wrap; gap: 4px; }
.art-actions .btn { height: 23px; padding: 0 8px; font-size: 11.5px; }
/* Missing Cover Art: every film without valid artwork, with the existing artwork actions inline */
.miss-list { display: flex; flex-direction: column; border: 1px solid var(--hairline); border-radius: var(--r-card); overflow: hidden; }
.miss-row { border-bottom: 1px solid var(--hairline); padding: 7px 10px; }
.miss-row:last-child { border-bottom: 0; }
.miss-main { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 10px; align-items: center; }
.miss-title { font-weight: 600; font-size: 12.5px; letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
.miss-title:hover { color: var(--accent); }
.miss-meta { color: var(--text-meta); font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.miss-row .art-actions { margin-top: 6px; }
.miss-row .cand-grid { grid-template-columns: repeat(auto-fill, minmax(62px, 1fr)); }
.lb-list { display: flex; flex-direction: column; border: 1px solid var(--hairline); border-radius: var(--r-card); overflow: hidden; margin-top: 6px; }
.lb-row { display: grid; grid-template-columns: 14px minmax(0, 1fr); gap: 9px; align-items: start; padding: 7px 10px; border-bottom: 1px solid var(--hairline); cursor: pointer; font-size: 12.5px; }
.lb-row:last-child { border-bottom: 0; }
.lb-row input { width: 14px; height: 14px; margin: 2px 0 0; accent-color: var(--accent); }
.lb-row .lb-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.lb-row .lb-from { font-weight: 600; }
.lb-row .lb-to { color: var(--text-2); }
.lb-row .lb-to select { max-width: 100%; height: 24px; padding: 0 6px; border-radius: var(--r-ctl); border: 1px solid var(--hairline-2); background: var(--surface); color: var(--text); font-size: 12px; }
.lb-row .lb-why { color: var(--text-3); font-size: 11.5px; }
.lb-missing { margin-top: 8px; font-size: 12.5px; }
.lb-missing summary { cursor: pointer; color: var(--text-2); }
.lb-missing .hint { margin-top: 4px; max-height: 180px; overflow: auto; }
/* profile sync: letterboxd.com/<username> and the button that reads it */
.lb-user { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 10px; font-size: 12.5px; }
.lb-user label { color: var(--text-3); white-space: nowrap; }
.lb-user input { flex: 1 1 150px; min-width: 0; height: 28px; padding: 0 8px; border-radius: var(--r-ctl); border: 1px solid var(--hairline-2); background: var(--surface); color: var(--text); font-size: 12.5px; }
.lb-user input:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
.lb-opt { display: grid; grid-template-columns: 14px minmax(0, 1fr); gap: 9px; align-items: start; margin-top: 9px; font-size: 12.5px; color: var(--text-2); cursor: pointer; }
.lb-opt input { width: 14px; height: 14px; margin: 2px 0 0; accent-color: var(--accent); }
.lb-opt b { color: var(--text); font-weight: 600; }
#importBox:empty { display: none; }
/* a sync that came back short: the reader has to notice this one */
.callout.warn { background: color-mix(in srgb, var(--warn) 13%, transparent); border-left: 2px solid var(--warn); color: var(--text-2); }
.callout.warn b { color: var(--text); }
.check-row { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--text); cursor: pointer; margin-top: 8px; }
.check-row input { width: 14px; height: 14px; margin: 0; accent-color: var(--accent); }
.report { margin-top: 6px; font-size: 12px; color: var(--text-2); font-variant-numeric: tabular-nums; }
.report b { color: var(--text); font-weight: 600; }
.cand-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(74px, 1fr)); gap: 8px; margin-top: 8px; }
.cand { cursor: pointer; text-align: left; }
.cand .poster { border-radius: 5px; }
.cand:hover .poster { outline: 2px solid var(--accent); outline-offset: 1px; }
.cand .cand-t { font-size: 10.5px; margin-top: 4px; line-height: 1.2; color: var(--text-2); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* forms */
.form { display: flex; flex-direction: column; gap: 11px; padding-top: 2px; }
.field { display: flex; flex-direction: column; gap: 4px; }
.field label { font-size: 11px; font-weight: 600; color: var(--text-3); letter-spacing: .01em; text-transform: uppercase; }
.field input, .field select, .field textarea {
  width: 100%; height: 32px; padding: 0 9px; border-radius: var(--r-ctl);
  border: 1px solid var(--hairline-2); background: var(--surface); outline: 0; font-size: 16px; color: var(--text);
}
@media (min-width: 721px) { .field input, .field select, .field textarea { font-size: 13px; height: 29px; } }
.field textarea { height: auto; min-height: 58px; padding: 7px 9px; resize: vertical; line-height: 1.4; }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }
.genre-picker { display: flex; flex-wrap: wrap; gap: 4px; }
.genre-picker .chip { cursor: pointer; opacity: .5; transition: opacity .12s var(--ease); height: 22px; padding: 0 8px; }
.genre-picker .chip.on { opacity: 1; box-shadow: inset 0 0 0 1px var(--accent); }
/* the actions close the form rather than hovering over it: no fill of their own, and nothing scrolls
   underneath them */
.form-actions { display: flex; justify-content: space-between; gap: 8px; margin-top: 14px; padding: 12px 0 4px; border-top: 1px solid var(--hairline); }
.form-actions .right { display: flex; gap: 6px; }
.hint { color: var(--text-3); font-size: 11.5px; line-height: 1.45; }
.hint a { color: var(--accent); }
.progress { height: 5px; border-radius: 3px; background: var(--fill); overflow: hidden; }
.progress > div { height: 100%; background: var(--accent); transition: width .3s var(--ease); }
.errlist { margin-top: 8px; max-height: 150px; overflow: auto; font-size: 11px; color: var(--text-3); line-height: 1.5; }
.callout { padding: 9px 11px; border-radius: var(--r-card); background: var(--fill); color: var(--text-2); font-size: 12px; line-height: 1.45; }
.callout code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11.5px; color: var(--text); }
.callout b { font-weight: 600; color: var(--text); }

/* ── Drop overlay / toast ─────────────────────────────────── */
.drop-overlay {
  position: fixed; inset: 0; z-index: 200; display: grid; place-items: center;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.drop-card { padding: 30px 38px; border-radius: 16px; text-align: center; background: var(--surface-2); box-shadow: var(--shadow-pop); border: 2px dashed var(--accent); }
.drop-icon { font-size: 34px; }
.drop-title { font-size: 17px; font-weight: 600; letter-spacing: -0.015em; margin-top: 8px; }
.drop-sub { color: var(--text-2); margin-top: 4px; font-size: 12.5px; }
.toast {
  position: fixed; left: 50%; bottom: calc(38px + var(--sab)); z-index: 300; transform: translateX(-50%); max-width: calc(100vw - 32px);
  padding: 8px 14px; border-radius: 10px; background: var(--surface-3); color: var(--text); font-weight: 500; font-size: 12.5px;
  box-shadow: var(--shadow-pop); animation: toastin .2s var(--ease); text-align: center;
}
.toast.err { background: var(--danger); color: #fff; }
/* clipped-value tooltip: small, quiet, macOS-like */
.tip {
  position: fixed; z-index: 150; max-width: 360px; padding: 4px 8px; border-radius: 6px;
  background: var(--surface-3); color: var(--text); font-size: 12px; line-height: 1.4; white-space: pre-wrap; overflow-wrap: anywhere;
  box-shadow: var(--shadow-pop); pointer-events: none; animation: tipin .12s var(--ease);
}
@keyframes tipin { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: none; } }
@keyframes toastin { from { opacity: 0; transform: translate(-50%, 8px); } to { opacity: 1; transform: translate(-50%, 0); } }

/* ── Responsive ───────────────────────────────────────────── */
@media (max-width: 1180px) {
  .topbar { grid-template-columns: minmax(0, auto) minmax(180px, 1fr) minmax(max-content, auto); gap: 10px; }
}
@media (max-width: 980px) {
  .topbar { grid-template-columns: 1fr auto; grid-template-areas: "left right" "search search"; gap: 8px 10px; }
  .topbar-left { grid-area: left; }
  .topbar-right { grid-area: right; }
  .topbar-center { grid-area: search; }
  #viewSeg > button span { display: none; }
  #viewSeg > button { padding: 0 8px; }
}
/* 721–1000px: the Inspector no longer takes 400px of a small window — it slides over the content */
@media (max-width: 1000px) and (min-width: 721px) {
  .scrim { display: block; position: fixed; inset: 0; z-index: 60; background: rgba(0, 0, 0, .22); animation: fade .18s var(--ease); }
  @keyframes fade { from { opacity: 0; } to { opacity: 1; } }
  .detail {
    position: fixed; top: 10px; right: 10px; bottom: 10px; z-index: 70;
    width: min(360px, calc(100vw - 20px)); min-width: 0; max-width: none;
  }
  .detail.is-open { animation: drawerIn .42s var(--ease-spring); }
  @keyframes drawerIn { from { transform: translateX(34px); opacity: .4; } 55% { opacity: 1; } to { transform: none; opacity: 1; } }
  .inspector-resize { display: none; }
  .detail-hero { grid-template-columns: 92px 1fr; }
  .kv { grid-template-columns: 96px 1fr; }
}
@media (max-width: 720px) {
  :root { --toolbar-control-h: 30px; --filter-control-h: 30px; }
  .brand-name { display: none; }
  /* The pill carries the sections and the search now, so the top bar is one row: brand on the left, the
     view switch and the controls on the right. */
  .topbar { padding: calc(7px + var(--sat)) 10px 6px; gap: 7px; grid-template-columns: auto 1fr; grid-template-areas: "brand right"; }
  .topbar-center { display: none; }
  /* the filter bar and the view switcher both fold into #btnOptions: one control instead of a row of six
     chips that could not fit without scrolling sideways, plus a segment that was down to bare glyphs */
  .filterbar { display: none; }
  /* Table is back on a phone: it scrolls sideways there, which is the price of a table, but it is the
     user's call and hiding it was ours. */
  .opt-btn { display: grid; }
  /* the last row of the library clears the floating pill rather than hiding under it */
  #content { padding-bottom: calc(var(--tabpill-h, 44px) + 22px + var(--sab)); }
  /* No bar along the bottom of a phone at all. It used to come back for queued changes or a view-only
     library, which meant a permanent strip under the tab bar for anyone who had not signed in on that
     device. Both of those still say so elsewhere: a queued change shows in the ... menu's sync line and
     flushes itself, and a locked library says View only the moment you try to change something. */
  .statusbar { display: none !important; }
  .topbar-left { display: contents; }
  .brand { grid-area: brand; }
  .scope { display: none; }                                 /* the pill carries the sections on a phone */
  .segmented.scope > button { flex: 1 1 0; padding: 0 8px; font-size: 13.5px; }
  #formatSeg { display: none; }
  .format-chip { display: inline-flex; }
  /* the sliders glyph in place of the words, so the row of chips fits the screen */
  .vo-chip .chip-label, .vo-chip .caret { display: none; }
  .vo-chip .vo-ico { display: block; }
  /* The bar itself holds still: View Options is pinned to the right edge where it can always be reached,
     and the chips before it take what is left. They fit a 375px screen as they stand; the scroller on the
     left group is the safety net for a narrower phone, and it takes the chips with it rather than sliding
     the whole bar and carrying View Options off the screen. */
  .filterbar { padding: 5px 10px; justify-content: flex-start; overflow: hidden; gap: 4px; }
  .filterbar-left { flex: 1 1 auto; min-width: 0; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; }
  .filterbar-left::-webkit-scrollbar { display: none; }
  .filterbar-right { flex: none; }
  .filterbar-right::before { content: ""; width: 1px; height: 16px; background: var(--hairline-2); margin: 0 3px; }
  /* 8px, not the 10 this used to carry: with Format, Filters, Group, Sort and the View Options glyph in
     the row, those two pixels a side are the difference between the bar fitting a 375px screen and
     scrolling. The chips are still 30px tall, so the touch target is unaffected. */
  .chip-btn { padding: 0 8px; }
  .cards { --card-min: 108px; --cards-pad-x: 10px; --cards-pad-top: 12px; --cards-pad-bottom: 20px; }
  .cards[data-size="small"] { --card-min: 88px; }
  .cards[data-size="large"] { --card-min: 136px; }
  /* Covers on a phone: three columns at the middle size. The desktop rule is a class and would otherwise
     outrank the plain .cards sizes above whatever the width, leaving one very large column. */
  .cards.is-covers {
    --card-min: 100px;
    --cards-pad-x: var(--card-gap-x, var(--card-gap)); --cards-pad-top: var(--card-gap-x, var(--card-gap));
    --cards-pad-bottom: var(--card-gap-x, var(--card-gap));
  }
  .cards.is-covers[data-size="small"] { --card-min: 82px; }
  .cards.is-covers[data-size="large"] { --card-min: 124px; }
  .card { --ct-fs: 12.5px; --ct-lh: 15px; }   /* 2 × 15 + 4 + 15 + 15 + 18 + 6 = 88; the --p-* budget follows */
  .list { padding: 0 var(--row-gap) 16px; }
  .row { padding: 0 12px; column-gap: 10px; }
  .sets { grid-template-columns: 1fr; padding: 12px 10px; }
  /* One column, so a one-line set name need not hold two lines' worth for the card beside it either. */
  .set-title > span:first-child { min-height: 0; }
  .g-acts { opacity: 1; }                                   /* no hover on touch */
  .groups-hint { margin: -4px 10px 12px; }
  .log { padding: 8px 12px 16px; }
  .log-item { grid-template-columns: 1fr; gap: 2px; }
  .statusbar { padding: 5px 12px calc(5px + var(--sab)); font-size: 11px; }
  .statusbar .stats { gap: 10px; }
  .statusbar .stat + .stat { padding-left: 10px; }
  /* phone search field: a touch rounder, with a hairline ring instead of the bare fill */
  .search { border-radius: 10px; box-shadow: inset 0 0 0 0.5px var(--hairline-2); }
  .search-icon { left: 10px; }
  .search input { padding-left: 30px; }
  .statusbar .src .src-text { display: none; }

  /* detail returns to a bottom sheet */
  .scrim { display: block; position: fixed; inset: 0; z-index: 60; background: rgba(0, 0, 0, .32); animation: fade .18s var(--ease); }
  @keyframes fade { from { opacity: 0; } to { opacity: 1; } }
  .detail {
    position: fixed; inset: auto 0 0 0; z-index: 70; width: 100%; min-width: 0; max-width: none;
    max-height: 92dvh; border: 0; border-radius: 16px 16px 0 0;
    background: var(--inspector); -webkit-backdrop-filter: none; backdrop-filter: none;
    box-shadow: 0 -8px 40px rgba(0, 0, 0, .45); transform: translateY(102%);
    transition: transform .42s var(--ease-spring); display: flex; animation: none;
  }
  .insp-hero { height: 470px; margin-bottom: -260px; }
  .insp-main { padding: 2px 15px 20px; }
  /* narrow rows: the value keeps a readable share of the width and the buttons drop below it */
  .insp-row { flex-wrap: wrap; }
  .insp-row .ir-k { min-width: 74px; }
  .insp-row .ir-v { min-width: 42%; }
  .insp-row .ir-a { margin-left: auto; }
  .detail.is-open { transform: none; animation: none; }
  /* A drawer comes out of something. The page behind gives way as the sheet arrives -- scaled back a
     little and tucked at the corners -- so the two read as one movement instead of one thing landing on
     another, and the floating bar goes down with it rather than sitting under the scrim. */
  .app.is-sheet #content { transform: scale(.955); border-radius: 16px; overflow: hidden; }
  #content { transition: transform .42s var(--ease-spring), border-radius .42s var(--ease-spring); transform-origin: 50% 0; }
  .app.is-sheet .tabpill { transform: translate(calc(-50% + var(--tp-dx, 0px)), 130%); }
  @media (prefers-reduced-motion: reduce) { #content { transition: none; } }
  /* while the finger is on it the sheet is written to directly; the transition comes back on release */
  .detail.is-dragging { transition: none; }
  .detail::before { content: ""; position: absolute; top: 6px; left: 50%; width: 34px; height: 4px; border-radius: 2px; background: var(--hairline-2); transform: translateX(-50%); }
  .inspector-resize { display: none; }
  .detail-head { padding: 16px 14px 8px; }
  .detail-body { padding: 0 14px calc(14px + var(--sab)); }
  .detail-hero { grid-template-columns: 88px 1fr; }
  .kv { grid-template-columns: 92px 1fr; }
  /* A menu is placed where it was asked for here too. It used to be pinned across the bottom of the
     screen as a sheet, which is a long way from the chip or the poster that opened it and gave every
     menu the same shape whatever it was for. Only the sizes stay: touch rows, and a cap so a menu never
     spans the screen. */
  .popover { max-width: min(320px, calc(100vw - 16px)); max-height: 68dvh; background: var(--surface-2); }
  /* the menu scrolls as one thing. The facet list had its own scroller, so only that band moved and the
     sections above and below it were stuck -- on a phone a menu is tall enough that you expect the whole
     surface to travel, top included. */
  .popover .menu-list { overflow: visible; flex: none; }
  .popover .menu-item { padding: 9px 11px; font-size: 15px; }
  .popover.is-vo .menu-item { padding: 10px 11px; font-size: 15.5px; }
  .popover .menu-title { padding: 8px 11px 4px; }
  table.grid th, table.grid td { --cell-pad: 9px; }
  .star-input { font-size: 32px; }
}
@media (hover: none) {
  table.grid thead th .th-grip { display: none; }
  .card:hover .poster { transform: none; box-shadow: var(--shadow-art), inset 0 0 0 0.5px rgba(127, 127, 135, .22); }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
  .seg-thumb, .set-rest { transition: none; }
  .content.nav-in-right, .content.nav-in-left, .content-ghost { animation: none; }
  .card:hover .poster { transform: none; }
}

/* ── Stats (the Summary sheet, live) ───────────────────────── */
.stats-page { padding: 16px; max-width: 1180px; margin: 0 auto; }
.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 10px; }
.tile { display: flex; flex-direction: column; gap: 2px; padding: 12px 14px; background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-card); text-align: left; color: var(--text); min-width: 0; }
button.tile { cursor: pointer; transition: background .12s var(--ease), border-color .12s var(--ease); }
button.tile:hover { background: var(--surface-2); border-color: var(--hairline-2); }
.tile-n { font-family: var(--font-display); font-size: 24px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.1; font-variant-numeric: tabular-nums; }
.tile-l { font-size: 12px; color: var(--text-2); font-weight: 500; }
.tile-s { font-size: 11px; color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; margin-top: 12px; align-items: start; }
.stat-card { background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-card); padding: 12px 14px; min-width: 0; }
.stat-card.wide { grid-column: 1 / -1; }
@media (min-width: 900px) { .stat-card.wide .bars { columns: 2; column-gap: 28px; } }
@media (min-width: 1300px) { .stat-card.wide .bars { columns: 3; } }
.stat-card h3 { margin: 0 0 8px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--text-3); }
.bars { display: block; }
.bar-row { display: grid; grid-template-columns: minmax(0, 1fr) 84px 42px; gap: 8px; align-items: center; width: 100%; padding: 3px 4px; margin: 0 -4px; border-radius: 5px; text-align: left; font-size: 12.5px; color: var(--text); break-inside: avoid; }
button.bar-row { cursor: pointer; }
button.bar-row:hover { background: var(--hover); }
.bar-label { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center; gap: 6px; }
.bar-label .ico-line { --badge-h: 14px; }
.bar-track { height: 6px; border-radius: 3px; background: var(--fill); overflow: hidden; }
.bar { display: block; height: 100%; background: var(--accent); border-radius: 3px; opacity: .85; }
.bar-n { text-align: right; color: var(--text-2); font-variant-numeric: tabular-nums; font-size: 12px; }
@media (max-width: 720px) { .stats-page { padding: 10px; } .tiles { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 8px; } .tile-n { font-size: 20px; } }

/* ── Random film (floating menu under the dice; a bottom sheet on phones) ── */
/* --rnd-art is the poster's width; everything else in the card is measured from it, and the reserved
   height below is that poster plus the synopsis and the buttons that always sit under it. */
/* The dice panel as a page. Same panel, laid into the library's own scroller and given a column width
   rather than a menu's: on a phone a roll is the whole screen, not a dialog over one. */
/* no side padding on the page itself: the wheel and the artwork run edge to edge, and every block that
   wants a margin brings its own */
.rnd-page { padding: 10px 0 4px; min-height: 100%; display: flex; flex-direction: column; overflow-x: clip; }
/* Centred by align-self, not by auto margins: an auto margin on a flex item turns off its stretch, and
   the page then shrank to whatever its widest line happened to be -- 243px once the filters moved out. */
.rnd-page .rnd-pop { max-width: 560px; width: 100%; align-self: center; }
.rnd-page .rnd-actions { padding-bottom: 12px; }
.rnd-page .rnd-head { display: none; }   /* on the page the top bar already says Tonight's Film */
.rnd-page .rnd-slot:empty { display: grid; place-items: center; }
/* ── the cover carousel ────────────────────────────────────
   A ring of covers seen from the inside. Each face is turned to its own place on the ring (--i steps of
   --step) and pushed out to --radius; the ring itself turns on --rot, which the script writes each frame.
   Backfaces are hidden, so the far half of the ring is simply not drawn -- cheaper than fading twelve
   faces individually, and it keeps the near arc clean. The mask softens both ends into the page. */
.rnd-ring {
  /* A cover leaving the wheel has to be gone before it reaches the edge, or the edge cuts it off square.
     The fade is deep enough -- a quarter of the width each side -- that the outermost covers are down to
     nothing while they are still well inside the screen. Widening the box instead only moved the cut. */
  position: relative; height: 268px; margin: 2px 0 6px; perspective: 900px; overflow: hidden;
  flex: none; width: 100%;
  /* horizontal belongs to the ring, vertical to the page; the covers are not text to be selected */
  touch-action: pan-y; cursor: grab; user-select: none; -webkit-user-select: none;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 27%, #000 73%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 27%, #000 73%, transparent 100%);
}
.rnd-ring-in {
  position: absolute; inset: 0; transform-style: preserve-3d;
  transform: translateZ(calc(-1 * var(--radius))) rotateY(var(--rot, 0deg));
  --radius: 244px; --step: 30deg;
}
.rnd-face {
  position: absolute; left: 50%; top: 50%; width: 132px; height: 198px; margin: -99px 0 0 -66px;
  transform: rotateY(calc(var(--i) * var(--step))) translateZ(var(--radius));
  backface-visibility: hidden; -webkit-backface-visibility: hidden;
}
.rnd-ring:active { cursor: grabbing; }
/* The flapper. It sits over the top of the wheel, overlapping the cover it is pointing at, and is knocked
   aside by every peg that passes -- which is the whole of the tick: the wheel turns smoothly, the thing
   touching it does not. */
.rnd-pin {
  position: absolute; left: 50%; top: 21px; z-index: 3; width: 26px; height: 28px;
  transform: translateX(-50%); transform-origin: 50% 3px; will-change: transform;
  color: var(--text);
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, .55));
}
.rnd-pin svg { display: block; width: 100%; height: 100%; }
.rnd-hint { text-align: center; color: var(--text-3); font-size: 12.5px; min-height: 16px; padding: 0 12px 8px; }
/* the button under the wheel is the page's one action, with the deck a third of its width beside it */
.rnd-pop.is-wheel .rnd-actions { flex-direction: column; gap: 7px; padding: 2px 12px 10px; }
.rnd-row { display: flex; gap: 8px; width: min(360px, 100%); }
.rnd-pop.is-wheel .rnd-go {
  flex: 3 1 auto; height: 43px; border-radius: 22px;
  font-size: 14.5px; font-weight: 600; letter-spacing: -0.01em; justify-content: center;
}
.rnd-pop.is-wheel .rnd-add {
  flex: 1 1 0; min-width: 0; height: 43px; border-radius: 22px; justify-content: center;
  font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; white-space: nowrap;
}
.rnd-pop.is-wheel .rnd-add { transition: background .2s var(--ease), color .2s var(--ease); }
.rnd-pop.is-wheel .rnd-add.is-on { background: var(--accent); color: #fff; }
.rnd-pop.is-wheel .rnd-add:disabled { opacity: .4; }
.rnd-pop.is-wheel .rnd-actions .hint { text-align: center; }
/* More options is pinned to the foot of the page, just clear of the tab bar */
.rnd-page .rnd-pop { flex: 1 1 auto; display: flex; flex-direction: column; }
.rnd-page .rnd-more { margin-top: auto; padding-top: 6px; }
.rnd-page .rnd-slot { min-height: 0; }
/* ── the deck and the table ────────────────────────────────
   A shortlist you build by hand, then deal face down. */
.rnd-deckbar[hidden] { display: none; }
.deck-box {
  margin: 0 12px 10px; padding: 11px 11px 11px; border-radius: 18px;
  background: var(--fill); box-shadow: inset 0 0 0 .5px color-mix(in srgb, var(--text-3) 22%, transparent);
}
.deck-head { display: flex; align-items: baseline; gap: 7px; padding: 0 2px 9px; }
.deck-name { font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; }
.deck-n {
  min-width: 18px; height: 18px; padding: 0 6px; border-radius: 9px; display: grid; place-items: center;
  background: var(--fill-strong); color: var(--text-2); font-size: 11.5px; font-weight: 600;
}
/* A grid rather than a wrapped row: the cards share the width, so a full row has the same gutter on both
   sides as the box's own padding, whatever the count. */
.deck-strip { display: grid; grid-template-columns: repeat(auto-fill, minmax(62px, 1fr)); gap: 10px; padding: 0 0 12px; }
.deck-chip { position: relative; cursor: pointer; transition: transform .16s var(--ease), opacity .18s var(--ease); }
.deck-chip .poster { width: 100%; border-radius: 8px; box-shadow: var(--shadow-art); }
.deck-chip.is-on .poster { box-shadow: var(--shadow-art), 0 0 0 2.5px var(--accent); }
.deck-chip.is-going { transform: scale(.55); opacity: 0; }
.deck-chip:active { transform: scale(.95); }
/* the badge is a circle with a glyph in it, and the glyph is centred by the grid rather than by the
   accident of where a text cross happens to sit on its baseline */
.deck-x {
  position: absolute; top: -8px; right: -8px; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; color: var(--text);
  background: var(--chrome); box-shadow: 0 1px 4px rgba(0, 0, 0, .5), inset 0 0 0 .5px color-mix(in srgb, var(--text-3) 34%, transparent);
}
.deck-x svg { display: block; width: 11px; height: 11px; }
.deck-x:active { transform: scale(.88); }
.deck-actions { display: flex; gap: 7px; }
.deck-actions .btn { flex: 1 1 auto; height: 36px; border-radius: 18px; justify-content: center; }
.deck-actions .btn#deckUndo, .deck-actions .btn#deckClear { flex: 0 1 auto; padding: 0 14px; }
.rnd-deal { padding: 12px 10px; }
.deal-wrap { max-width: 560px; margin: 0 auto; width: 100%; }
/* The same pills the rest of the app uses for a primary action, not the little chips they were. */
.deal-head { display: flex; align-items: center; gap: 10px; padding: 2px 10px 16px; }
.deal-nav {
  flex: none; display: inline-flex; align-items: center; gap: 6px; height: 38px; padding: 0 17px;
  border-radius: 19px; background: var(--fill); color: var(--text);
  font-size: 14px; font-weight: 600; letter-spacing: -0.01em;
  transition: background .14s var(--ease), transform .12s var(--ease);
}
.deal-nav:hover { background: var(--fill-strong); }
.deal-nav:active { transform: scale(.96); }
.deal-nav:disabled { opacity: .4; }
.deal-nav svg { width: 11px; height: 11px; }
/* the status lives up here with the title, clear of the cards */
.deal-title {
  flex: 1 1 auto; min-width: 0; text-align: center; font-family: var(--font-display);
  font-size: 16px; font-weight: 600; letter-spacing: -0.02em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.deal-hand { display: grid; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: 12px; perspective: 1200px; }
/* what you turned over, lit; what you did not, quieted */
.deal-card { transition: opacity .45s var(--ease), transform .3s var(--ease); }
.deal-card.is-dim { opacity: .42; }
.deal-card.is-won .dc-front {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 85%, transparent),
    -14px 0 26px -4px color-mix(in srgb, var(--accent) 70%, transparent),
    14px 0 26px -4px color-mix(in srgb, var(--accent) 70%, transparent),
    0 0 20px 1px color-mix(in srgb, var(--accent) 38%, transparent);
}
.deal-card {
  aspect-ratio: 2 / 3; position: relative; border-radius: 9px;
  animation: deal-in .42s cubic-bezier(.2, .9, .3, 1.05) backwards;
  animation-delay: calc(var(--i) * 55ms);
}
@keyframes deal-in { from { transform: translateY(26px) scale(.9); opacity: 0; } to { transform: none; opacity: 1; } }
.dc-in { position: absolute; inset: 0; transform-style: preserve-3d; transition: transform .55s cubic-bezier(.3, .8, .3, 1); }
.deal-card.is-flipped .dc-in { transform: rotateY(180deg); }
.dc-face { position: absolute; inset: 0; border-radius: 9px; overflow: hidden; backface-visibility: hidden; -webkit-backface-visibility: hidden; }
/* A card back: a lattice held inside a clean border rather than running off the edge. The pattern is
   clipped to the content box, so the padding is the border and the hatching never touches the cut. */
.dc-back {
  padding: 7px; background-clip: content-box, content-box, content-box, border-box;
  background-color: color-mix(in srgb, var(--accent) 20%, var(--surface-2));
  background-image:
    repeating-linear-gradient(45deg, rgba(255, 255, 255, .11) 0 2px, transparent 2px 9px),
    repeating-linear-gradient(-45deg, rgba(255, 255, 255, .11) 0 2px, transparent 2px 9px),
    radial-gradient(120% 90% at 50% 0%, rgba(255, 255, 255, .13), transparent 60%);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text-3) 28%, transparent),
    inset 0 0 0 7.5px color-mix(in srgb, var(--surface-2) 55%, transparent),
    inset 0 0 0 8.5px rgba(255, 255, 255, .16), var(--shadow-art);
}
/* dealt face up, because these are the films you chose, then turned over together */
.deal-hand.is-dealing .dc-in { transform: rotateY(180deg); }
.deal-hand.is-dealing .deal-card.is-flipped .dc-in { transform: none; }
.dc-front { transform: rotateY(180deg); }
.dc-front .poster { width: 100%; border-radius: 9px; }
.deal-actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 8px; padding-top: 22px; opacity: 0; transition: opacity .3s var(--ease); }
.deal-actions.is-in { opacity: 1; }
.deal-actions .btn { height: 43px; border-radius: 22px; padding: 0 20px; font-size: 14.5px; font-weight: 600; }
.deal-fav { gap: 7px; }
.deal-fav svg { width: 15px; height: 15px; }
.deal-fav.is-on { color: var(--accent); }
/* Turned over, then genuinely moved about: three passes across each other before the hand settles and
   you are asked to choose. Nothing can be tapped while it is moving. */
/* the moving is done by the script, card by card, from where each one was to where it ended up */
.deal-hand.is-mixing { pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .deal-card { animation: none; }
  .deal-hand.is-mixing .deal-card { animation: none; }
  .dc-in { transition-duration: .01s; }
}

/* the film that landed, dressed the way the detail panel dresses one */
.rnd-card.is-hero { display: block; padding: 0 0 6px; }
.rnd-hero {
  /* edge to edge, but squircled where it meets the page above it */
  position: relative; height: 340px; overflow: hidden; background: var(--surface-2);
  border-radius: 22px 22px 0 0;
  width: 100%; margin-bottom: -150px; cursor: pointer;
  -webkit-mask-image: linear-gradient(to bottom, #000 0 30%, rgba(0, 0, 0, .34) 54%, rgba(0, 0, 0, .16) 72%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0 30%, rgba(0, 0, 0, .34) 54%, rgba(0, 0, 0, .16) 72%, transparent 100%);
}
.rnd-hero .insp-bed { position: absolute; inset: -32px; background-size: cover; background-position: 50% 45%; filter: blur(26px) saturate(1.3) brightness(.62); }
.rnd-hero .insp-art { position: relative; display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 14%; }
.rnd-hero .poster-ph { width: 100%; height: 100%; border-radius: 0; }
/* A button in the corner rather than a handle in the middle: a handle promises a gesture, and this one
   only ever had to say "there is more behind this". */
.rnd-more-btn {
  position: absolute; top: 12px; right: 12px; z-index: 2; width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center; color: #fff; background: rgba(255, 255, 255, .17);
  -webkit-backdrop-filter: saturate(190%) blur(18px); backdrop-filter: saturate(190%) blur(18px);
  box-shadow: inset 0 0 0 .5px rgba(255, 255, 255, .34), 0 2px 10px rgba(0, 0, 0, .22);
  animation: rnd-more-in .45s var(--ease) .2s backwards;
  transition: transform .14s var(--ease), background .14s var(--ease);
}
.rnd-more-btn svg { width: 17px; height: 17px; }
.rnd-more-btn:active { transform: scale(.92); background: rgba(255, 255, 255, .3); }
@keyframes rnd-more-in { from { opacity: 0; transform: scale(.7); } to { opacity: 1; transform: none; } }
.rnd-hero { cursor: pointer; }
/* the sheet's own header: what it is, and the way out */
.rnd-sheet-head {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px 8px;
  font-family: var(--font-display); font-size: 17px; font-weight: 600; letter-spacing: -0.02em;
}
.rnd-sheet-head span { flex: 1 1 auto; }
.rnd-sheet-x {
  flex: none; width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
  background: var(--fill); color: var(--text-2);
}
.rnd-sheet-x:hover { background: var(--fill-strong); color: var(--text); }
.rnd-sheet-x svg { width: 11px; height: 11px; }
.rnd-card.is-hero .rnd-info { position: relative; padding: 0 14px; gap: 5px; }
.rnd-card.is-hero .rnd-title { font-size: 24px; min-height: 0; }
.rnd-card.is-hero .rnd-sub { min-height: 0; }
.rnd-card.is-hero .rnd-overview { min-height: 0; margin-top: 2px; }
.rnd-card.is-hero .rnd-btns { margin-top: 10px; }
/* what the wheel stopped on, breathing rather than blinking */
.rnd-face.is-won .poster { animation: rnd-glow 2.6s ease-in-out infinite; }
/* Weighted to the sides: above and below the cover there is page to glow against, but left and right
   there is another cover, and an even halo disappears into it. */
@keyframes rnd-glow {
  0%, 100% { box-shadow: var(--shadow-art), inset 0 0 0 .5px rgba(127, 127, 135, .25), 0 0 0 0 rgba(255, 255, 255, 0); }
  50% {
    box-shadow: var(--shadow-art), inset 0 0 0 .5px rgba(255, 255, 255, .6),
      0 0 18px 2px color-mix(in srgb, var(--accent) 42%, transparent),
      -14px 0 26px -2px color-mix(in srgb, var(--accent) 78%, transparent),
      14px 0 26px -2px color-mix(in srgb, var(--accent) 78%, transparent);
  }
}
@media (prefers-reduced-motion: reduce) { .rnd-face.is-won .poster { animation: none; box-shadow: var(--shadow-art), 0 0 16px 2px color-mix(in srgb, var(--accent) 45%, transparent); } }
/* the smear is along the travel, and only while there is travel to smear */
.rnd-ring-in.is-b1 .rnd-face.is-near { filter: url(#rndBlur1); }
.rnd-ring-in.is-b2 .rnd-face.is-near { filter: url(#rndBlur2); }
.rnd-ring-in.is-b3 .rnd-face.is-near { filter: url(#rndBlur3); }
.rnd-face .poster { width: 132px; border-radius: 7px; box-shadow: var(--shadow-art), inset 0 0 0 .5px rgba(127, 127, 135, .25); }
@media (prefers-reduced-motion: reduce) { .rnd-ring { display: none; } }
.rnd-curate-row { display: flex; justify-content: center; padding: 0 12px 12px; }
.rnd-curate {
  display: inline-flex; align-items: center; gap: 8px; height: 38px; padding: 0 18px; border-radius: 19px;
  font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; color: var(--text-2);
}
.rnd-curate .vo-ico { width: 16px; height: 16px; }
.rc-count {
  min-width: 19px; height: 19px; padding: 0 6px; border-radius: 10px; display: grid; place-items: center;
  background: var(--accent); color: #fff; font-size: 11.5px; font-weight: 700;
}
.rc-count[hidden] { display: none; }
.rnd-curate-sheet { padding: 2px 0 6px; }
.rnd-curate-sheet .rnd-group:last-child { margin-bottom: 2px; }

/* ── Curate your spins ─────────────────────────────────────
   Rows of icon, name and control inside grouped cards: what a phone's own settings look like, and roomier
   than the stack of little titles it replaces. The control sits right and drops under the label rather
   than squeezing when there is not width for both. */
.rnd-group {
  margin: 0 10px 10px; padding: 2px 0; border-radius: 18px; background: var(--fill);
  box-shadow: inset 0 0 0 .5px color-mix(in srgb, var(--text-3) 20%, transparent);
}
.rnd-line {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; padding: 11px 13px;
  border-top: .5px solid color-mix(in srgb, var(--text-3) 18%, transparent);
}
.rnd-line:first-child, .rnd-line.is-head { border-top: 0; }
.rl-ico { flex: none; display: grid; place-items: center; width: 20px; color: var(--text-2); }
.rl-k { flex: 0 1 auto; font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.rl-note { margin-left: auto; color: var(--text-3); font-size: 13px; }
/* The control never shrinks below what it has to say -- it drops onto its own line instead, where it gets
   the whole width. Words cut in half are worse than a row that takes two lines. */
.rl-v { flex: 1 1 auto; min-width: min-content; display: flex; justify-content: flex-end; }
/* the control takes the width that is left and its options share it, so a five-option row still lands
   inside the card instead of running off the end of it */
.rl-v .segmented.small { width: 100%; max-width: 100%; }
.rl-v .segmented.small { flex: 1 1 auto; }
.rl-v .segmented.small > button { flex: 1 1 auto; min-width: max-content; padding: 0 9px; font-size: 12.5px; white-space: nowrap; }
.rl-v .segmented.small > button, .rnd-group .segmented.small { height: 34px; }
.rl-select { width: 100%; }
.rl-select select { width: 100%; height: 32px; font-size: 14px; }
.rnd-group .genre-picker { padding: 2px 13px 12px; gap: 7px; }
.rnd-group .genre-picker .chip { font-size: 13.5px; padding: 0 12px; height: 30px; }
/* a switch row: what it does, what that means, and the switch on the right */
.rnd-switch {
  display: flex; align-items: center; gap: 12px; width: 100%; padding: 11px 13px; text-align: left;
  border-top: .5px solid color-mix(in srgb, var(--text-3) 18%, transparent);
}
.rnd-switch:first-child { border-top: 0; }
.rs-txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.rs-k { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.rs-note { font-size: 12.5px; color: var(--text-3); }
/* bigger than the ones in a menu, because this is a page and it is being read at arm's length */
.rnd-switch .sw {
  flex: none; width: 50px; height: 30px; border-radius: 15px; padding: 2px; background: var(--fill-strong);
  display: block; transition: background .18s var(--ease);
}
.rnd-switch .sw-knob {
  display: block; width: 26px; height: 26px; border-radius: 50%; background: #fff;
  box-shadow: 0 .5px 1px rgba(0, 0, 0, .28), 0 2px 4px rgba(0, 0, 0, .3);
  transition: transform .18s var(--ease);
}
.rnd-switch[aria-checked="true"] .sw { background: var(--accent); }
.rnd-switch[aria-checked="true"] .sw-knob { transform: translateX(20px); }
.rnd-switch.is-dim { opacity: .5; }
@media (prefers-reduced-motion: reduce) { .rnd-switch .sw, .rnd-switch .sw-knob { transition: none; } }

/* the picker's in-place "who are you?" step */
.gp-ask { display: flex; gap: 6px; padding: 2px 10px 8px; }
.gp-ask input {
  flex: 1 1 auto; min-width: 0; height: 30px; padding: 0 9px; font-size: 16px;
  border-radius: 8px; border: 1px solid var(--line); background: var(--fill); color: var(--text); outline: 0;
}
.gp-ask input:focus { border-color: var(--accent); }
.gp-ask .btn { flex: none; height: 30px; }
.rnd-pop { display: flex; flex-direction: column; --rnd-art: 132px; --rnd-slot-h: 312px; }
@media (max-width: 380px) { .rnd-pop { --rnd-art: 116px; --rnd-slot-h: 288px; } }
.rnd-sec { padding: 0 9px 6px; }
.rnd-sec .field select { height: 27px; font-size: 12.5px; }
.rnd-sec .check-row { font-size: 12px; margin-top: 6px; }
.rnd-actions { display: flex; align-items: center; flex-wrap: wrap; padding: 4px 9px 8px; }
/* Every row keeps its height whether or not this particular film has anything to put in it, so the card
   is the same size for a documentary with no synopsis as for a blockbuster with a long one. */
.rnd-card { display: flex; flex-direction: column; gap: 9px; padding: 4px 10px 6px; }
.rnd-top { display: grid; grid-template-columns: var(--rnd-art) 1fr; gap: 13px; align-items: start; }
.rnd-poster .poster { width: var(--rnd-art); border-radius: 7px; box-shadow: var(--shadow-art), inset 0 0 0 0.5px rgba(127, 127, 135, .25); }
.rnd-info { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.rnd-title { font-family: var(--font-display); font-size: 20px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.18; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-height: 2.36em; }
.rnd-sub { color: var(--text-2); font-size: 13px; line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-height: 2.7em; }
.rnd-genres { min-height: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rnd-badges { min-height: 20px; margin-top: 2px; }
.rnd-overview { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; min-height: calc(3 * 1.45em); }
.rnd-btns { display: flex; gap: 6px; flex-wrap: wrap; min-height: 30px; }
.rnd-spin .rnd-title, .rnd-spin .rnd-sub { color: var(--text-2); }
/* the rows a candidate has no business filling in while it is only passing through: blocked out rather
   than left blank, so the card reads as one shape settling rather than as one being built */
.rnd-sk { display: block; height: 11px; width: 100%; border-radius: 6px; background: var(--fill); }
.rnd-overview .rnd-sk { margin-bottom: 8px; }
.rnd-badges .rnd-sk { display: inline-block; vertical-align: middle; margin-right: 5px; border-radius: 5px; }
.rnd-actions { gap: 10px; }
.rnd-actions .btn svg { margin-right: 5px; }
.rnd-seg { display: inline-flex; max-width: 100%; }

/* ── Borrowing & want to watch ──────────────────────────────── */
.lent-pill { display: inline-flex; align-items: center; height: var(--badge-h, 16px); padding: 0 6px; border-radius: 999px; font-size: 10.5px; font-weight: 600; letter-spacing: .01em; background: color-mix(in srgb, var(--warn) 20%, transparent); color: var(--warn); white-space: nowrap; max-width: 150px; overflow: hidden; text-overflow: ellipsis; flex: none; }
.card .poster .lent-overlay { position: absolute; left: 6px; bottom: 6px; height: 18px; background: rgba(16, 16, 18, .8); color: #ffb340; box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, .16); }
.lent-state { display: inline-flex; align-items: center; gap: 5px; color: var(--warn); font-size: 12.5px; }
.lent-state b { color: var(--text); }
.borrow-acts { display: inline-flex; gap: 6px; flex-wrap: wrap; }
.req-list { display: flex; flex-direction: column; border: 1px solid var(--hairline); border-radius: var(--r-card); margin-top: 8px; overflow: hidden; }
.req { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 10px; border-bottom: 1px solid var(--hairline); font-size: 12.5px; }
.req:last-child { border-bottom: 0; }
.req-main { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.req-acts { display: inline-flex; gap: 4px; flex: none; }
.req-film { color: var(--accent); font-weight: 600; text-align: left; padding: 0; }
.req-film:hover { text-decoration: underline; }
.loan-group { margin-bottom: 10px; }
.loan-name { font-size: 12.5px; font-weight: 600; margin-bottom: 5px; }
/* the want-to-watch panel puts a Watched together button opposite the film's name */
.watch-group-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.borrow-form, .ids-editor { margin-top: 10px; padding: 10px 12px; border: 1px solid var(--hairline); border-radius: var(--r-card); background: var(--surface); }
.ids-editor { margin-top: 12px; }
.ids-editor h4 { margin-top: 0; }
.ids-row { grid-template-columns: 1fr 84px; }
.link-btn { color: var(--accent); font: inherit; font-size: inherit; padding: 0; cursor: pointer; }
.link-btn:hover { text-decoration: underline; }

/* ── Admin lock (Google-hosted version) ────────────────────── */
.lock-pill { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; background: var(--fill); color: var(--text-2); cursor: pointer; white-space: nowrap; flex: none; }
.lock-pill:hover { background: var(--fill-strong); color: var(--text); }
.lock-pill.admin { color: var(--ok); background: color-mix(in srgb, var(--ok) 14%, transparent); }

/* ───────────────────────── favorites ─────────────────────────
   The heart takes the place of the watched check wherever that check appears. */
.fav-mark { color: var(--fav, #ff5a67); }
.watched-mark.fav-mark svg { width: 13px; height: 13px; }
.card .poster .watched-overlay.fav-mark { color: #ff453a; }   /* Apple systemRed, dark appearance */
.watched-state .fav-mark { color: var(--fav, #ff5a67); }
.btn-row { display: inline-flex; gap: 6px; flex-wrap: wrap; }
.btn .fav-mark, .btn > svg { width: 12px; height: 12px; vertical-align: -1px; margin-right: 3px; }
.btn.is-fav { color: var(--fav, #ff5a67); }
.menu-label svg { width: 12px; height: 12px; vertical-align: -1px; color: var(--fav, #ff5a67); margin-right: 2px; }
:root[data-theme="light"] { --fav: #e0394a; }

/* ───────────────────────── country flag on posters ───────────────────────── */
.card-sub .flag { margin-right: 5px; font-size: 13px; line-height: 1; vertical-align: -1px; }
.card-sub .flag-img { display: inline-block; width: 15px; height: 15px; vertical-align: -3px; margin-right: 5px; }

/* ───────────────────────── trailer overlay ───────────────────────── */
.trailer { position: fixed; inset: 0; z-index: 140; display: grid; place-items: center; padding: 16px; background: rgba(0, 0, 0, .74); animation: fade .16s var(--ease); }
@supports (backdrop-filter: blur(8px)) { .trailer { -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); } }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.trailer-box { width: min(960px, 100%); border-radius: 14px; overflow: hidden; background: var(--surface-2); box-shadow: 0 24px 64px rgba(0, 0, 0, .5), inset 0 0 0 0.5px rgba(255, 255, 255, .08); animation: pop .16s var(--ease); }
.trailer-head { display: flex; align-items: center; gap: 12px; padding: 8px 8px 8px 16px; border-bottom: 1px solid var(--hairline); }
.trailer-title { flex: 1; min-width: 0; font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.trailer-frame { aspect-ratio: 16 / 9; background: #000; }
.trailer-frame iframe { display: block; width: 100%; height: 100%; border: 0; }
.trailer-none { display: grid; justify-items: center; gap: 6px; padding: 40px 20px 36px; text-align: center; }
.trailer-none h3 { margin: 6px 0 0; font-size: 15px; }
.trailer-none p { margin: 0 0 10px; color: var(--text-2); font-size: 13px; }
.trailer-none .empty-icon { width: 44px; height: 44px; display: grid; place-items: center; border-radius: 50%; background: var(--surface-3); color: var(--text-2); }
.trailer-none .empty-icon svg { width: 18px; height: 18px; }
.trailer-btn svg { margin-right: 4px; }
body.trailer-open { overflow: hidden; }
@media (max-width: 720px) { .trailer { padding: 0; align-items: end; } .trailer-box { border-radius: 14px 14px 0 0; padding-bottom: var(--sab, 0px); } }
