/* Art Deco theme — black & silver, in the spirit of Poirot's title cards.
   Dependency-free: no web fonts or images, just CSS. */
:root {
  --bg: #0d0f12;          /* near-black, cool */
  --panel: #14181d;       /* dark lacquer */
  --panel-2: #1b2026;     /* raised surface */
  --text: #e8ecf0;        /* cool white */
  --muted: #8a939d;       /* steel grey */
  --accent: #aab8c6;      /* brushed silver */
  --accent-hi: #d6dee6;   /* polished chrome (hover) */
  --accent-soft: rgba(170, 184, 198, 0.34);   /* hairline rules */
  --accent-softer: rgba(170, 184, 198, 0.13); /* fills */
  --danger: #cf5b46;      /* warm pop against the cool palette */
  --ok: #8fb3ad;          /* muted sea-green */
  --display: "Century Gothic", "Futura", "Twentieth Century",
             "Avenir Next", "Trebuchet MS", system-ui, sans-serif;
}

* {
  box-sizing: border-box;
  /* iOS: never flash the grey tap box on any element (buttons and clickable
     containers like .album-head alike). Inherited in WebKit, but set on every
     element so non-button click targets are covered too. */
  -webkit-tap-highlight-color: transparent;
}

body {
  margin: 0;
  font-family: "Avenir Next", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  /* never scroll the page sideways; `clip` (not `hidden`) so the body doesn't
     become a scroll container, which would break the sticky tab/filter bars. */
  overflow-x: clip;
  /* App chrome, not a document: tapping a control on iOS shouldn't select the
     text around it or pop the selection callout. Form fields opt back in below. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
/* Keep real text entry selectable. */
input, textarea, [contenteditable] {
  -webkit-user-select: text;
  user-select: text;
}
/* Reserve room for the fixed audio bar so it never covers the last list item;
   only while it's showing, to avoid dead space when nothing is playing. */
body.audio-playing { padding-bottom: 64px; }

header {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1rem 1.5rem;
  background: var(--panel);
  border-bottom: 2px solid var(--accent);
}

header .logo {
  height: 2.1rem;
  width: auto;
  display: block;
  flex: 0 0 auto;
}

header h1 {
  font-family: var(--display);
  font-size: 1.35rem;
  font-weight: 400;
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.32em;
  color: var(--accent);
}
/* Deco diamond flourishes around the wordmark. */
header h1::before,
header h1::after {
  content: "\25C6";
  font-size: 0.55em;
  vertical-align: 0.2em;
  color: var(--accent);
  opacity: 0.75;
}
header h1::before { margin-right: 0.6em; }
header h1::after { margin-left: 0.6em; }

.status { color: var(--muted); font-size: 0.85rem; letter-spacing: 0.04em; }
.status.bad { color: var(--danger); }
.status.good { color: var(--ok); }

main { padding: 1.5rem; }

.tabs {
  display: flex;
  gap: 0.25rem;
  padding: 0 1.5rem;
  background: var(--panel);
  border-bottom: 1px solid var(--accent-soft);
  /* Keep the tab nav in reach on long lists: pin it to the top once the title
     scrolls past. Sits below the modal overlays (z 10) but above page content;
     the filter bar (z 4) slides under it. */
  position: sticky;
  top: 0;
  z-index: 5;
  /* If the tabs don't fit (narrow phones), scroll the bar itself rather than
     widening the whole page. */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;       /* Firefox */
}
.tabs::-webkit-scrollbar { display: none; }  /* WebKit */
.tab {
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--muted);
  font-family: var(--display);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  padding: 0.75rem 1rem;
  white-space: nowrap;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
}

@media (max-width: 520px) {
  /* Trim the outer gutters on a phone so the video panel nearly fills the
     screen width instead of sitting in a narrow column. */
  main { padding: 0.75rem 0.5rem; }
  main .panel { padding: 0.85rem 0.7rem; }
  header { padding: 0.85rem 1rem; gap: 0.5rem; }
  header h1 { font-size: 1.1rem; letter-spacing: 0.22em; }
  header .logo { height: 1.7rem; }
  .user-menu-btn { padding: 0.25rem 0.55rem; font-size: 0.85rem; }
  .tabs { padding: 0 0.75rem; }
  .tab { padding: 0.4rem 0.55rem; letter-spacing: 0.06em; }
  /* Stack the watch-state filter below the subscription picker so the row
     doesn't overflow and scroll horizontally on a phone. */
  .sub-picker { flex-wrap: wrap; }
  .sub-picker label:last-child { flex-basis: 100%; }
  .sub-picker select { width: 100%; max-width: 100%; }
  /* In the compact filter row, put each select to the right of its label
     text instead of stacking it below, one filter per line. */
  .sub-picker.compact label { flex: 1 1 100%; flex-direction: row; align-items: center; gap: 0.5rem; }
  .sub-picker.compact label select { flex: 1; width: auto; }
  /* The date-window inputs keep their natural width on desktop, but on a phone
     let them share a row and the Clear button take its own line, so nothing
     overlaps. */
  /* Two filters per row: Title + Director on one line, from + to on the next. */
  .sub-picker label:has(> input[type="search"]) { flex: 1 1 40%; }
  #tab-video .sub-picker label:has(> input[type="date"]) { flex: 1 1 40%; min-width: 0; flex-direction: row; align-items: center; gap: 0.5rem; }
  .sub-picker label:has(> input[type="date"]) input { flex: 1; width: auto; min-width: 0; }
  .sub-picker button { flex: 0 0 100%; }
}
.tab:hover { color: var(--text); filter: none; }
.tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.tab-icon { display: block; }  /* gear icon inherits the tab's color */

.tab-panel { display: flex; flex-direction: column; gap: 1.5rem; }

.banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin: 0 1.5rem;
  margin-top: 1.5rem;
  padding: 0.75rem 1rem;
  border-radius: 2px;
  background: rgba(196, 84, 60, 0.14);
  border: 1px solid var(--danger);
  color: var(--text);
  font-size: 0.9rem;
}
.banner button { background: var(--danger); border-color: var(--danger); color: #11151a; }

.subs-grid {
  display: grid;
  grid-template-columns: 360px 1fr;
  gap: 1.5rem;
  align-items: start;
}

.subs-left {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

@media (max-width: 800px) {
  .subs-grid { grid-template-columns: 1fr; }
}

.panel {
  background: var(--panel);
  border: 1px solid var(--accent-soft);
  border-radius: 3px;
  padding: 1rem 1.25rem;
}

.panel h2 {
  font-family: var(--display);
  font-size: 0.95rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--accent);
  margin: 0 0 0.85rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--accent-soft);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

form { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1rem; }
form label { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.8rem; color: var(--muted); }
/* Checkbox rows lay out horizontally with a compact box, not full-width. */
form label.check { flex-direction: row; align-items: center; gap: 0.5rem; }
form label.check input { width: auto; flex: none; }
.field { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.8rem; color: var(--muted); margin-bottom: 1rem; }

input, select, button, textarea {
  font: inherit;
  padding: 0.5rem 0.6rem;
  border-radius: 2px;
  border: 1px solid var(--accent-soft);
  background: #0c0a06;
  color: var(--text);
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
}
input.invalid, input.invalid:focus { border-color: var(--danger); }
/* Inline hint shown when the add-episode number is already taken. */
.field-hint { font-size: 0.75rem; color: var(--danger); margin-top: 0.2rem; }
.field-hint.hidden { display: none; }

textarea {
  width: 100%;
  resize: vertical;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8rem;
}

button {
  cursor: pointer;
  background: var(--accent);
  border: 1px solid var(--accent);
  color: #11151a;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  /* Stop iOS from flashing a grey box and selecting the glyph/label on tap. */
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
}
button:hover { filter: none; background: var(--accent-hi); border-color: var(--accent-hi); }
button.ghost { background: transparent; border-color: var(--accent-soft); color: var(--accent); }
button.ghost:hover { background: transparent; border-color: var(--accent); color: var(--text); }
button.ghost.on { color: var(--ok); border-color: rgba(155, 176, 131, 0.6); }

.list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }

/* Trailing control for a windowed (paged) list. */
.show-more { display: flex; align-items: center; justify-content: center; gap: 0.75rem; flex-wrap: wrap; padding: 0.75rem 0 0.25rem; }

.list li {
  background: var(--panel-2);
  border: 1px solid var(--accent-softer);
  border-radius: 2px;
  padding: 0.6rem 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.row { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.title { font-weight: 600; word-break: break-word; }
.title.link { cursor: pointer; }
.title.link:hover { color: var(--accent); }

/* Channel avatar (round, thin silver ring) next to a name / above the play list. */
.sub-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
  background: var(--panel-2);
  border: 1px solid var(--accent-soft);
}
.sub-head { display: flex; align-items: center; gap: 0.5rem; }

/* Fixed "Other" bucket: a built-in icon (inline SVG) in the same round frame,
   and a left accent stripe so it reads as a distinct, pinned section. */
.other-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--accent);
  padding: 5px;
}
.other-icon svg { width: 100%; height: 100%; }
.list li.sub-other { border-left: 3px solid var(--accent); }
.sub-picker { display: flex; align-items: flex-end; gap: 0.6rem; margin-bottom: 0.6rem; }
.sub-picker label { flex: 1; }
/* A compact row sizes its controls to content and groups them at the left,
   rather than stretching each to an equal share of the width. */
.sub-picker.compact { justify-content: flex-start; }
.sub-picker.compact label { flex: 0 0 auto; }
.sub-picker input { width: 100%; }
/* The date-window inputs are narrow; don't let them grab equal width. */
.sub-picker label:has(> input[type="date"]) { flex: 0 0 auto; }
#sub-detail-controls .search-wrap { margin-bottom: 1rem; }

/* Search box with an inline clear (×) button. The wrap takes the layout slot;
   the input fills it and leaves room on the right for the button. */
.search-wrap { position: relative; display: block; }
.search-wrap input[type="search"] { width: 100%; margin-bottom: 0; padding-right: 2rem; }
/* Hide the browser's native clear button; we render our own. */
.search-wrap input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
.search-clear {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 2rem;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  color: var(--muted);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
}
.search-clear:hover { color: var(--text); }
/* No text in the box: nothing to clear, so hide the button. */
.search-wrap.empty .search-clear { display: none; }
/* Search box + sort controls above the audiobook grid. */
.audiobook-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1rem;
}
.audiobook-toolbar .search-wrap { flex: 1 1 12rem; min-width: 0; margin-bottom: 0; }
.audiobook-sort {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  white-space: nowrap;
  color: var(--muted);
  font-size: 0.85rem;
}
#audiobook-sort-dir { flex: 0 0 auto; }

/* Compact icon toggles (watched state, sort order) below the filter form. */
.filter-icons {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: center;
  /* Pin the filter controls right under the sticky tab bar. --tab-bar-h is the
     tab bar's live height, published from app.js so the offset tracks the
     responsive breakpoint and late font loads (fallback covers first paint). */
  position: sticky;
  top: var(--tab-bar-h, 44px);
  z-index: 4;
  /* Opaque, so list rows scroll out of sight beneath the bar (the list spans
     the same content width, so no horizontal bleed is needed). */
  background: var(--panel);
  padding: 0.55rem 0;
  margin-bottom: 0.8rem;
  border-bottom: 1px solid var(--accent-soft);
}
/* Permanent search box on the Audio tab spans its own row, with the toggle
   buttons wrapping onto the line below it. */
.filter-icons .search-wrap { flex: 1 1 100%; min-width: 0; margin-bottom: 0; }
/* Series mode: the serie + season dropdowns sit inline with the filter icons. */
.serie-picker { display: inline-flex; gap: 0.5rem; align-items: center; min-width: 0; }
#videos-serie-chip { max-width: 16rem; }
.icon-toggle {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 2px;
  background: transparent;
  border: 1px solid var(--accent-soft);
  color: var(--muted);
}
.icon-toggle:hover { background: transparent; border-color: var(--accent); color: var(--text); }
/* An active (non-default) toggle. Hue alone can't carry "on" in this monochrome
   palette (silver-on-black reads much like the grey-on-black off state, and like
   hover), so the active state gets a filled background plus chrome-bright glyph —
   the fill is what distinguishes it at a glance. */
.icon-toggle.on {
  color: var(--accent-hi);
  border-color: var(--accent);
  background: var(--accent-soft);
}
.icon-toggle svg { display: block; width: 20px; height: 20px; }

/* Custom filter dropdowns (channel / serie / season): a chip showing the
   current selection's icon + name acts as the trigger, opening an anchored menu
   of icon+name rows. Used where a native <select> can't render the avatars. */
.dd { position: relative; display: inline-flex; min-width: 0; }
.dd-chip {
  flex: 0 1 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  height: 34px;
  padding: 0 0.5rem 0 0.25rem;
  border-radius: 2px;
  background: transparent;
  border: 1px solid var(--accent-soft);
  color: var(--text);
  min-width: 0;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
}
.dd-chip:hover { background: transparent; border-color: var(--accent); }
.dd-chip[aria-expanded="true"] { border-color: var(--accent); }
.dd-chip img { width: 26px; height: 26px; }
.dd-chip > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.85rem;
}
.dd-chip .dd-caret { flex: none; font-size: 0.7rem; color: var(--muted); }
.dd-chip[aria-expanded="true"] .dd-caret { color: var(--accent); }

/* The anchored menu: a scrollable list of rows dropping below the chip. */
.dd-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-width: 100%;
  max-width: 18rem;
  max-height: 60vh;
  overflow-y: auto;
  padding: 0.3rem;
  background: var(--panel-2);
  border: 1px solid var(--accent-soft);
  border-radius: 2px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
}
.dd-menu.hidden { display: none; }
.dd-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.35rem 0.5rem;
  background: transparent;
  border: 1px solid var(--accent-soft);
  border-radius: 2px;
  color: var(--text);
  text-align: left;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
}
.dd-row:hover { border-color: var(--accent); }
.dd-row.on { border-color: var(--accent); color: var(--accent-hi); background: var(--accent-softer); }
.dd-row img { width: 26px; height: 26px; flex: none; }
.dd-row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Option popup for the cycling filter toggles (long press / right click). Reuses
   .dd-row for items but is viewport-anchored (position set inline in JS). */
.option-menu {
  position: fixed;
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  max-width: 16rem;
  max-height: 70vh;
  overflow-y: auto;
  padding: 0.3rem;
  background: var(--panel-2);
  border: 1px solid var(--accent-soft);
  border-radius: 2px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
}
.option-menu.hidden { display: none; }
.option-menu .dd-row { text-transform: capitalize; }
.option-menu .opt-icon { display: inline-flex; flex: none; color: var(--muted); }
.option-menu .opt-icon svg { display: block; width: 20px; height: 20px; }
.option-menu .dd-row.on .opt-icon { color: var(--accent); }

/* Dialog with stacked input fields (e.g. the tag and user-edit overlays). */
.search-fields { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1rem; }
.search-fields input { width: 100%; }

/* Video rows with a thumbnail on the left. */
.list li.vid { flex-direction: row; align-items: flex-start; gap: 0.6rem; }
.thumb {
  width: 140px;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 2px;
  flex: none;
  background: #000;
  border: 1px solid var(--accent-soft);
}
.vid-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.25rem; }

/* Thumbnail wrapper carrying a "watched" progress bar along its bottom edge. */
.thumb-wrap { position: relative; width: 140px; flex: none; line-height: 0; }
.thumb-wrap .thumb { width: 100%; display: block; }
.progress {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 4px;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 0 0 2px 2px;
  overflow: hidden;
}
.progress-bar { display: block; height: 100%; background: var(--accent); }

/* Click-to-play thumbnails: a play triangle fades in over the cover on hover. */
.thumb-wrap.playable { cursor: pointer; }
.thumb-play {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%) scale(0.85);
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  opacity: 0;
  transition: opacity 0.12s, transform 0.12s;
  pointer-events: none;
}
.thumb-play svg { width: 22px; height: 22px; margin-left: 2px; }
.thumb-wrap.playable:hover .thumb-play {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

@media (max-width: 520px) {
  .list li.vid { flex-direction: column; }
  .thumb, .thumb-wrap { width: 100%; }
}

.list li.selected {
  border-color: var(--accent);
  background: var(--accent-softer);
}
.meta { color: var(--muted); font-size: 0.8rem; word-break: break-all; }

.badge {
  font-size: 0.7rem;
  padding: 0.12rem 0.5rem;
  border-radius: 2px;
  border: 1px solid currentColor;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  white-space: nowrap;
}
.badge.new { color: var(--accent); background: rgba(201, 162, 39, 0.12); }
.badge.downloaded { color: var(--ok); background: rgba(155, 176, 131, 0.14); }
.badge.pending { color: var(--muted); background: rgba(171, 156, 119, 0.14); }
.badge.downloading { color: var(--accent-hi); background: rgba(201, 162, 39, 0.16); }
.badge.error { color: var(--danger); background: rgba(196, 84, 60, 0.16); }
.badge.deleted { color: var(--muted); background: rgba(171, 156, 119, 0.10); }
.badge.members { color: #c79a4b; background: rgba(199, 154, 75, 0.15); }
.badge.unavailable { color: var(--danger); background: rgba(196, 84, 60, 0.10); }

/* Download queue (Settings tab): numbered list in planned download order. */
.queue { margin: 0 0 0.5rem; padding-left: 1.6rem; display: flex; flex-direction: column; gap: 0.4rem; }
.queue li { padding-left: 0.2rem; }
.queue li::marker { color: var(--muted); font-size: 0.8rem; }
.queue .qtitle { display: block; }
.queue li.downloading .qtitle { color: var(--accent); }
.queue li.downloading::marker { content: "▶ "; color: var(--accent); }

.head-actions { display: flex; gap: 0.5rem; align-items: center; }
.head-actions button { padding: 0.3rem 0.55rem; font-size: 0.8rem; }

.actions { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.actions.wrap { flex-wrap: wrap; }
.actions button, .actions select { padding: 0.3rem 0.55rem; font-size: 0.8rem; }
.ghost.danger { color: var(--danger); border-color: rgba(196, 84, 60, 0.6); }
.ghost.danger:hover { background: rgba(196, 84, 60, 0.14); color: var(--danger); border-color: var(--danger); }

.overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  z-index: 10;
}
.overlay.hidden { display: none; }
.hidden { display: none; }

.player-box {
  background: var(--panel);
  border: 1px solid var(--accent);
  border-radius: 3px;
  padding: 1rem;
  width: min(960px, 100%);
  position: relative;
}
.player-box h3 { margin: 0; }
.player-box video { width: 100%; border-radius: 2px; background: #000; }

/* Video description under the player: preserve the source line breaks/links
   layout, but cap the height so a long description doesn't push the controls
   off-screen. */
.player-desc {
  margin: 0.75rem 0 0;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.4;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 9rem;
  overflow-y: auto;
}

.player-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin: 0 2rem 0.75rem 0;
}
/* Give the title a width floor so the trailing controls (cast status, cast
   button, speed pills) wrap to a second line on narrow screens instead of
   crushing the h3 to a sliver — otherwise word-break wraps it one letter per
   line into a tall thin column (notably while casting on a phone, when the
   status text and cast button are both present). */
.player-head h3 { flex: 1 1 10rem; min-width: 10rem; word-break: break-word; }

/* Cast button; shown/hidden from JS by the live CastContext state (see app.js).
   .hidden (display:none) handles "no devices"; .connected highlights an active
   cast session. */
.cast-btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  width: 28px;
  height: 28px;
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--text);
}
.cast-btn:hover { color: var(--accent); }
.cast-btn.connected { color: var(--accent); }
/* .cast-btn sets display, so it would otherwise beat the .hidden utility
   (same specificity, declared later) and the button could never be hidden —
   e.g. it would show inertly on AirPlay-only browsers. Win explicitly. */
.cast-btn.hidden { display: none; }

/* Short cast status line ("Connecting…", "Casting", or a load error) shown
   next to the player title. Empty -> no box, so it's invisible when idle. */
.cast-status {
  flex: none;
  font-size: 0.75rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 40%;
}
.cast-status:empty { display: none; }

/* Playback-speed buttons next to the player title. A compact pill group; the
   active rate is highlighted. */
.speed-control {
  flex: none;
  display: inline-flex;
  gap: 2px;
}
.speed-btn {
  padding: 2px 7px;
  font-size: 0.75rem;
  font-weight: 600;
  background: transparent;
  border: 1px solid var(--accent-soft);
  color: var(--muted);
  cursor: pointer;
  border-radius: 3px;
}
/* Override the base button:hover fill so an inactive button stays outlined. */
.speed-btn:hover { background: transparent; color: var(--text); border-color: var(--accent); }
/* Declared after :hover so the active rate stays filled even while hovered. */
.speed-btn.is-active {
  color: #11151a;
  background: var(--accent);
  border-color: var(--accent);
}

/* Cast control card: shown in place of the <video> while casting. Mirrors the
   video box's aspect/feel (dark, full width) and centers a "Casting to …"
   label over its own play/pause + scrubber, all driven by the RemotePlayer. */
.cast-panel {
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: 2px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 1rem;
  box-sizing: border-box;
}
.cast-panel.hidden { display: none; }
.cast-panel-icon { color: var(--accent); }
.cast-panel-label {
  color: var(--text);
  font-size: 0.95rem;
}
.cast-panel-label span { color: var(--accent); font-weight: 600; }
.cast-controls {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: min(560px, 100%);
}
.cast-playpause {
  flex: none;
  width: 36px;
  height: 36px;
  font-size: 1rem;
  line-height: 1;
  background: transparent;
  border: 1px solid var(--accent-soft);
  color: var(--text);
  border-radius: 50%;
  cursor: pointer;
}
.cast-playpause:hover { border-color: var(--accent); color: var(--accent); background: transparent; }
.cast-seek { flex: 1; min-width: 0; cursor: pointer; accent-color: var(--accent); }
.cast-time {
  flex: none;
  font-size: 0.8rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.close {
  position: absolute;
  top: 0.5rem;
  right: 0.75rem;
  background: transparent;
  border: none;
  color: var(--muted);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}
.close:hover { color: var(--accent); }

.empty { color: var(--muted); font-size: 0.85rem; padding: 0.5rem 0; }

.confirm-box {
  background: var(--panel);
  border: 1px solid var(--accent);
  border-radius: 3px;
  padding: 1.25rem;
  width: min(440px, 100%);
}
.confirm-box h3 {
  margin: 0 0 0.5rem;
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent);
  font-weight: 500;
}
.confirm-box .meta { display: block; margin-bottom: 1rem; line-height: 1.4; }
/* Names the track being added in the add-to-playlist picker. */
.dialog-sub {
  margin: -0.25rem 0 1rem;
  color: var(--text);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.confirm-actions { display: flex; justify-content: flex-end; gap: 0.5rem; flex-wrap: wrap; }
#confirm-delete { background: var(--danger); border-color: var(--danger); color: #11151a; }
#confirm-delete:hover { background: var(--danger); filter: brightness(1.12); }

.import-path-row { display: flex; gap: 0.5rem; }
.import-path-row input { flex: 1; min-width: 0; }

/* Season/Episode number inputs sit side by side (add-episode + episode Info). */
.episode-num-row { display: flex; gap: 0.5rem; }
.episode-num-row label { flex: 1; min-width: 0; }

.group { margin-bottom: 1.25rem; }
.group:last-child { margin-bottom: 0; }
.group-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--display);
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  margin: 0 0 0.5rem;
  padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--accent-soft);
  color: var(--accent);
}
.group-title .count {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--accent-soft);
  border-radius: 2px;
  padding: 0.05rem 0.5rem;
  font-size: 0.72rem;
  letter-spacing: 0.05em;
}

/* --- Tags ------------------------------------------------------------------ */

/* Read-only tag chips shown on video cards and under the player. */
.tag-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin: 0.25rem 0 0.1rem;
}
.actions .tag-chips {
  margin: 0;
  align-items: center;
  min-width: 0;
}
.tag-chip {
  --notch: 7px;
  --edge: var(--accent-soft);
  --fill: var(--accent-softer);
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.05rem 0.65rem;
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  color: var(--text);
  background: transparent;
  border: none;
  border-radius: 0;
  white-space: nowrap;
  clip-path: polygon(0 50%, var(--notch) 0, calc(100% - var(--notch)) 0,
                     100% 50%, calc(100% - var(--notch)) 100%, var(--notch) 100%);
}
.tag-chip::before, .tag-chip::after {
  content: ""; position: absolute; inset: 0; z-index: -1; clip-path: inherit;
}
.tag-chip::before { background: var(--edge); }
.tag-chip::after { inset: 1px; background: var(--fill); }
.tag-chip.auto { --edge: var(--accent-soft); color: var(--muted); }
/* A manually-removed tag: visible but struck through and dimmed. */
.tag-chip.removed {
  color: var(--muted);
  text-decoration: line-through;
  opacity: 0.6;
}
.tag-chip.removed .tag-dot { opacity: 0.5; }
.meta.removed { font-style: italic; text-decoration: line-through; }
.tag-dot {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  flex: none;
}

/* The 🏷 button on cards / player. */
.tag-btn, .info-btn {
  padding: 0.1rem 0.4rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  color: var(--muted);
  border-color: var(--accent-soft);
}
.tag-btn:hover, .info-btn:hover { color: var(--text); border-color: var(--accent); }
.tag-btn svg, .info-btn svg { width: 1.05em; height: 1.05em; }

/* Info dialog definition list. */
.info-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.35rem 0.9rem;
  margin: 0.25rem 0 0;
}
.info-list dt { color: var(--muted); font-size: 0.8rem; }
.info-list dd { margin: 0; }

/* Tri-state tag filter on the Videos tab. */
.tag-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin: 0.5rem 0 0.25rem;
}
.tag-filter-label { color: var(--muted); font-size: 0.8rem; }
button.tag-chip.filter {
  cursor: pointer;
  --fill: var(--panel);
}
button.tag-chip.filter.include {
  --edge: var(--accent);
  --fill: var(--accent-softer);
  color: var(--accent-hi);
}
button.tag-chip.filter.exclude {
  --edge: var(--danger);
  color: var(--danger);
  text-decoration: line-through;
}
.tag-match-toggle { font-size: 0.72rem; padding: 0.05rem 0.5rem; }

/* Player tag row. */
.player-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.4rem;
}
.player-tags .tag-chips { margin: 0; }

/* Settings: tag management. */
#tag-form { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }
#tag-form #tag-name { flex: 1; min-width: 12rem; }

/* Curated Art Deco colour palette (swatch buttons). */
.swatches { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
.swatch {
  width: 1.25rem;
  height: 1.25rem;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--accent-soft);
  cursor: pointer;
}
.swatch:hover { border-color: var(--accent-hi); }
.swatch.selected {
  /* gold ring against the panel so the pick reads clearly */
  box-shadow: 0 0 0 2px var(--panel), 0 0 0 3px var(--accent-hi);
}
#tags-list { margin-top: 0.75rem; display: flex; flex-direction: column; gap: 0.4rem; }
.tag-admin {
  border: 1px solid var(--accent-soft);
  border-radius: 3px;
  background: var(--panel-2);
}
.tag-admin > summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.6rem;
  cursor: pointer;
  list-style: none;
}
.tag-admin > summary::-webkit-details-marker { display: none; }
.tag-admin-name { font-weight: 600; }
.tag-admin-body {
  padding: 0.6rem;
  border-top: 1px solid var(--accent-soft);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.tag-admin-row { display: flex; gap: 0.5rem; align-items: center; }
.tag-admin-row .tag-name-input { flex: 1; }
.tag-rules { display: flex; flex-direction: column; gap: 0.35rem; }
.rule-row { display: flex; gap: 0.5rem; align-items: center; }
.rule-row .rule-pattern { flex: 1; }
.rule-row .rule-lang-custom { width: 6rem; }
.rule-regex-lbl {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  color: var(--muted);
  font-size: 0.8rem;
  white-space: nowrap;
}
.rule-remove { padding: 0.1rem 0.45rem; line-height: 1; }
button.ghost.danger { color: var(--danger); border-color: rgba(207, 91, 70, 0.5); }
button.ghost.danger:hover { color: var(--danger); border-color: var(--danger); }

/* Manual tag picker modal. */
.tag-picker-list {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin: 0.5rem 0 0.75rem;
  max-height: 50vh;
  overflow: auto;
}
.tag-pick-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.25rem;
  cursor: pointer;
  border-radius: 2px;
}
.tag-pick-row:hover { background: var(--accent-softer); }
.tag-pick-name { flex: 1; }

/* Rescan progress bar in the Tags panel. */
.rescan-progress {
  width: 100%;
  height: 0.5rem;
  margin: 0.4rem 0 0.2rem;
  border: 1px solid var(--accent-soft);
  border-radius: 999px;
  background: var(--panel-2);
  padding: 0;
  -webkit-appearance: none;
  appearance: none;
  overflow: hidden;
}
.rescan-progress::-webkit-progress-bar { background: var(--panel-2); }
.rescan-progress::-webkit-progress-value { background: var(--accent); }
.rescan-progress::-moz-progress-bar { background: var(--accent); }

/* Tags panel: collapsible regex help. */
.regex-help {
  margin: 0 0 1rem;
  border: 1px solid var(--accent-soft);
  border-radius: 3px;
  background: var(--panel-2);
}
.regex-help > summary {
  cursor: pointer;
  padding: 0.5rem 0.7rem;
  color: var(--accent);
  font-size: 0.85rem;
  letter-spacing: 0.03em;
  list-style: none;
}
.regex-help > summary::-webkit-details-marker { display: none; }
.regex-help > summary::before { content: "\25B8  "; }       /* ▸ */
.regex-help[open] > summary::before { content: "\25BE  "; }  /* ▾ */
.regex-help-body { padding: 0 0.7rem 0.7rem; font-size: 0.82rem; }
.regex-help-body p { margin: 0.45rem 0; }
.regex-help code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  background: var(--bg);
  border: 1px solid var(--accent-softer);
  padding: 0.02rem 0.32rem;
  border-radius: 2px;
  font-size: 0.92em;
  white-space: nowrap;
}
.regex-table { border-collapse: collapse; width: 100%; margin: 0.5rem 0; }
.regex-table th, .regex-table td {
  border: 1px solid var(--accent-soft);
  padding: 0.3rem 0.5rem;
  text-align: left;
  vertical-align: top;
}
.regex-table th { color: var(--muted); font-weight: 600; font-size: 0.78rem; }
/* On a phone, let the table scroll rather than widen the page. */
@media (max-width: 560px) {
  .regex-help-body { overflow-x: auto; }
  .regex-table { min-width: 30rem; }
}

/* --- Log viewer (Settings tab) --- */
.log-content {
  margin: 0;
  padding: 0.6rem 0.75rem;
  background: #0c0a06;
  border: 1px solid var(--accent-soft);
  border-radius: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.75rem;
  line-height: 1.5;
  white-space: pre;
  overflow: auto;
  max-height: 28rem;
  color: var(--text);
}

/* --- Audio tab: albums grouped with inline players --- */
.audio-controls { margin-bottom: 1rem; }
#album-list { display: flex; flex-direction: column; gap: 1rem; }
.album {
  border: 1px solid var(--accent-soft);
  border-radius: 8px;
  background: var(--panel-2);
  overflow: hidden;
}
.album-head { display: flex; gap: 0.7rem; align-items: center; padding: 0.7rem; }
#album-list .album-head,
#sub-video-list .album-head { cursor: pointer; }
.album-info-form { display: flex; flex-direction: column; gap: 0.5rem; padding: 0 0.7rem 0.7rem; }
.album-info-form.hidden { display: none; }
.album-info-form label { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.85rem; color: var(--muted); }
.album-info-form label.check { flex-direction: row; align-items: center; gap: 0.4rem; }
.album-info-form input[type="text"], .album-info-form input[type="date"] { width: 100%; }
.album-info-actions { display: flex; gap: 0.5rem; margin-top: 0.3rem; }
.video-info-form { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.5rem; }
.video-info-form.hidden { display: none; }
.video-info-form label { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.85rem; color: var(--muted); }
.video-info-form label.check { flex-direction: row; align-items: center; gap: 0.4rem; }
.video-info-form input[type="text"], .video-info-form select { width: 100%; }
.video-info-actions { display: flex; gap: 0.5rem; margin-top: 0.3rem; }
/* Per-video subtitle track manager inside the Info form. */
.subs-manager { display: flex; flex-direction: column; gap: 0.35rem; padding-top: 0.5rem; border-top: 1px solid var(--border); }
.subs-manager-label { font-size: 0.85rem; color: var(--muted); }
.subs-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.25rem; }
.subs-list li { display: flex; align-items: center; gap: 0.4rem; }
.subs-empty { font-size: 0.85rem; color: var(--muted); font-style: italic; }
.subs-lang-tag { font-size: 0.8rem; padding: 0.1rem 0.4rem; border-radius: 0.25rem; background: var(--surface-2, rgba(127,127,127,0.15)); text-transform: uppercase; letter-spacing: 0.02em; }
.subs-remove { flex: none; border: none; background: none; color: var(--muted); cursor: pointer; font-size: 0.9rem; line-height: 1; padding: 0.1rem 0.3rem; }
.subs-remove:hover { color: var(--danger, #c0392b); }
.subs-add { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }
.subs-add .subs-lang { width: 4.5rem; flex: none; }
.subs-add .subs-file { flex: 1; min-width: 0; font-size: 0.8rem; }
.subs-msg { font-size: 0.8rem; color: var(--muted); min-height: 1em; }
.album-caret { flex: none; color: var(--muted); transition: transform 0.15s; }
.album:not(.collapsed) .album-caret { transform: rotate(90deg); }
.album.collapsed .track-list { display: none; }
.album-thumb {
  width: 72px; height: 54px; object-fit: cover;
  border-radius: 5px; flex: none; background: var(--bg);
}
.album-thumb-empty { background: var(--accent-softer); }
/* Album cover with a hover play-overlay, mirroring the video thumbnails. */
.album-thumb-wrap { position: relative; width: 72px; height: 54px; flex: none; }
.album-cover-play {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%) scale(0.85);
  width: 34px; height: 34px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff; cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s, transform 0.12s;
}
.album-cover-play svg { width: 18px; height: 18px; margin-left: 2px; }
.album-thumb-wrap:hover .album-cover-play,
.album-cover-play:focus-visible {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
.album-info h3 { margin: 0 0 0.2rem; font-size: 1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.album-info .meta { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Artist and date render inline with a separator on wide screens. */
.album-info .meta-extra::before { content: " · "; }
.track-list { list-style: none; margin: 0; padding: 0 0.7rem 0.6rem; display: flex; flex-direction: column; gap: 0.4rem; }
.track { display: flex; align-items: center; gap: 0.5rem; padding-top: 0.4rem; border-top: 1px solid var(--accent-softer); }
.track-num { color: var(--muted); font-variant-numeric: tabular-nums; min-width: 1.4rem; text-align: right; }
/* Per-track album cover in playlists; small square so a row of them reads as a
   strip of covers without crowding the title. Clicking it plays the track, so
   it carries a border that lights up while its track is the one playing. */
.track-thumb {
  flex: none; width: 36px; height: 36px; border-radius: 0.3rem;
  object-fit: cover; background: var(--accent-softer); display: block;
  cursor: pointer; border: 2px solid transparent;
}
.track-thumb-empty { background: var(--accent-softer); }
.track-thumb:hover { border-color: var(--accent-soft); }
.track.playing .track-thumb { border-color: var(--accent); }
/* Album rows keep a play button; it takes the same accent highlight as the
   playlist cover while its track is playing. */
.track.playing .track-play {
  border-color: var(--accent); background: var(--accent-softer);
}
/* Title + its tag cluster (genre pill, duration/BPM/key). The cluster wraps
   under the title on narrow screens rather than squeezing the title to a sliver:
   the title can shrink (min-width:0) and wrap its words, while the fixed-width
   tags move to a second line when there's no room. */
.track-main {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-wrap: wrap; align-items: center;
  column-gap: 0.5rem; row-gap: 0.15rem;
}
/* The title is a play trigger. */
.track-title { flex: 1 1 auto; min-width: 0; word-break: break-word; cursor: pointer; }
.track-title:hover { text-decoration: underline; }
/* Meta line and secondary controls stay tucked away until the row's "⋯" is
   pressed (adds .expanded), keeping a resting row down to cover/play + title. */
.track-tags { flex: 0 0 auto; display: none; align-items: center; gap: 0.4rem; }
.track-extra { display: none; align-items: center; gap: 0.5rem; }
/* Expanded, the row stacks: the band/title line runs full width, the meta
   (genre · duration · BPM · key) drops onto its own line beneath it, and the
   controls sit on a third line, right-aligned. The cover/play and "⋯" stay on
   the top line (top-aligned), so "⋯" keeps its resting position. */
.track.expanded { flex-wrap: wrap; align-items: flex-start; }
.track.expanded .track-main { flex-direction: column; align-items: stretch; }
.track.expanded .track-tags { display: flex; }
.track.expanded .track-extra {
  display: flex; flex-basis: 100%; justify-content: flex-end;
}
.track-more {
  flex: none; width: 26px; height: 26px; padding: 0; border-radius: 50%;
  border: 1px solid transparent; background: none; color: var(--muted);
  cursor: pointer; font-size: 1rem; line-height: 1;
}
.track-more:hover { background: var(--accent-softer); color: var(--text); }
.track.expanded .track-more { color: var(--accent); }
/* Analyzed top-genre pill; the BPM/key meta stays on one line so
   "142 BPM · A minor" doesn't break mid-token (the global .meta allows break-all). */
.track-genre {
  font-size: 0.7rem; color: var(--muted);
  background: var(--accent-softer); border-radius: 0.6rem;
  padding: 0.05rem 0.45rem; white-space: nowrap;
}
.track .meta { white-space: nowrap; }
.track-play {
  flex: none; width: 28px; height: 28px; padding: 0; border-radius: 50%;
  border: 1px solid var(--accent-soft); background: var(--panel);
  color: var(--accent); cursor: pointer; font-size: 0.7rem; line-height: 1;
}
.track-play:hover { background: var(--accent-softer); }
/* Push the play button to the right of the album head and match the Videos-tab
   Play button (plain button, compact padding). */
.album-info { flex: 1 1 auto; min-width: 0; }
.album-play { flex: none; padding: 0.3rem 0.55rem; font-size: 0.8rem; }

/* --- Playlists -------------------------------------------------------------
   Composite cover: a 2x2 mosaic of up to 4 album covers, sized to match the
   album thumbnail so playlists and albums line up in the same grid. */
.playlist-cover {
  width: 72px; height: 54px; flex: none;
  border-radius: 5px; overflow: hidden;
  display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr;
  gap: 1px; background: var(--accent-softer);
}
.pl-tile { background-size: cover; background-position: center; background-color: var(--bg); }
.pl-tile.empty { background-color: var(--accent-softer); }
/* The cover sits inside an .album-thumb-wrap, so the play overlay is inherited. */

/* Album/playlist badge tucked before the title. */
.kind-badge {
  display: inline-flex; vertical-align: -2px; margin-right: 0.35rem;
  color: var(--muted);
}
.kind-badge svg { width: 14px; height: 14px; }

/* Owner controls in a playlist head and on its track rows. */
/* Wrap so the action cluster always stays within .album-info and never runs
   under the head's Play button on narrow (phone) widths. */
.pl-actions { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-top: 0.25rem; max-width: 100%; }
.pl-actions .ghost { padding: 0.15rem 0.4rem; font-size: 0.75rem; }
/* Destructive row in the Edit popup. */
.option-menu .dd-row.danger { color: var(--danger, #c33); }
.track-add, .pl-up, .pl-down, .pl-remove, .track-edit {
  flex: none; width: 24px; height: 24px; padding: 0; border-radius: 50%;
  border: 1px solid var(--accent-soft); background: var(--panel);
  color: var(--accent); cursor: pointer; font-size: 0.8rem; line-height: 1;
}
.track-add:hover, .pl-up:hover, .pl-down:hover, .pl-remove:hover,
.track-edit:hover { background: var(--accent-softer); }
/* Inline track editor: artist + title inputs fill the row, with save/cancel. */
.track.editing { gap: 0.4rem; }
.track-edit-artist, .track-edit-title {
  min-width: 0; padding: 0.25rem 0.4rem; background: var(--bg);
  border: 1px solid var(--accent-soft); border-radius: 2px; color: var(--text);
}
.track-edit-artist { flex: 1 1 35%; }
.track-edit-title { flex: 1 1 65%; }
.track-edit-save, .track-edit-cancel { flex: none; }
.pl-up:disabled, .pl-down:disabled { opacity: 0.35; cursor: default; }
.pl-remove { color: var(--danger, #c33); }
.track.empty { border-top: none; }
/* Drag-to-reorder grip on owner playlist rows. touch-action:none lets a
   touch-drag that starts here reorder instead of scrolling the page. */
.pl-drag {
  flex: none; width: 24px; height: 24px; display: inline-flex;
  align-items: center; justify-content: center; color: var(--accent);
  cursor: grab; font-size: 0.95rem; line-height: 1;
  touch-action: none; user-select: none; -webkit-user-select: none;
}
.pl-drag:active { cursor: grabbing; }
.track.dragging { opacity: 0.55; background: var(--accent-softer); border-radius: 4px; }
/* MusicBrainz release-date lookup in the album Info form. */
.album-lookup { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.album-lookup-note { color: var(--muted); font-size: 0.85rem; }

/* Shared audio queue bar, pinned to the bottom of the viewport. */
.audio-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
  display: flex; flex-direction: column; gap: 0.4rem;
  padding: 0.5rem 0.9rem;
  background: var(--panel-2); border-top: 1px solid var(--accent-soft);
}
.audio-bar.hidden { display: none; }
.audio-bar-main { display: flex; align-items: center; gap: 0.8rem; }
.audio-bar-info {
  display: flex; align-items: baseline; gap: 0.5rem;
  min-width: 0; flex: 1 1 320px;
}
/* Cover art of the track now playing, at the left of the bar. */
.audio-bar-art {
  flex: none; align-self: center;
  width: 40px; height: 40px; border-radius: 5px;
  object-fit: cover; background: var(--bg);
}
.audio-bar-art.hidden { display: none; }
.audio-bar-title { flex: 1; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.audio-bar-queue {
  flex: none; align-self: center; padding: 0;
  background: none; border: none; color: var(--muted);
  font-size: 0.85rem; cursor: pointer;
}
/* These bar controls are bare <button>s, so the global button:hover bleeds a
   light-grey accent background in — and on touch that :hover sticks after a tap,
   leaving a grey box until you tap elsewhere. Keep them transparent in every
   state and brighten only the glyph for feedback. */
.audio-bar-queue:hover, .audio-bar-queue:active {
  background: none; border-color: transparent; color: var(--accent-hi);
}
.audio-bar-next, .audio-bar-clear, .audio-bar-shuffle, .audio-bar-fx {
  flex: none; align-self: center; padding: 0 0.3rem;
  background: none; border: none; color: var(--muted);
  font-size: 1rem; line-height: 1; cursor: pointer;
}
.audio-bar-fx { font-size: 1.2rem; padding: 0 0.4rem; }
.audio-bar-fx:hover { color: var(--accent-hi); }
.audio-bar-fx:disabled { opacity: 0.4; cursor: default; }
/* Lit when a non-default key/tempo is applied. */
.audio-bar-fx.on { color: var(--accent-hi); }
/* Key/tempo popover, anchored above the bar's right edge. */
.audio-bar-fx-panel {
  position: absolute; right: 0.6rem; bottom: 100%; margin-bottom: 0.4rem;
  background: var(--panel-2); border: 1px solid var(--accent-soft);
  border-radius: 4px; padding: 0.6rem 0.8rem; z-index: 30;
  display: flex; flex-direction: column; gap: 0.5rem; min-width: 16rem;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
/* The panel sets display:flex, which ties on specificity with the global
   .hidden{display:none} and wins on source order — so without this the popover
   never hides. Re-assert the hide at higher specificity. */
.audio-bar-fx-panel.hidden { display: none; }
.fx-row { display: flex; align-items: center; gap: 0.5rem; }
.fx-label { width: 3.2rem; color: var(--muted); }
.fx-row .fx-down, .fx-row .fx-up {
  flex: none; width: 26px; height: 26px; padding: 0; border-radius: 50%;
  border: 1px solid var(--accent-soft); background: var(--panel);
  color: var(--accent); cursor: pointer; line-height: 1;
}
.fx-row .fx-down:hover, .fx-row .fx-up:hover { background: var(--accent-softer); }
.fx-value { min-width: 2.4rem; text-align: center; font-variant-numeric: tabular-nums; }
.fx-unit { color: var(--muted); font-size: 0.85rem; }
.fx-bpm { margin-left: auto; color: var(--muted); font-size: 0.85rem; }
.fx-actions { display: flex; align-items: center; gap: 0.6rem; }
.fx-status { color: var(--muted); font-size: 0.85rem; }
/* Shuffle and skip are the primary transport controls, so make them a touch
   larger than the bare ✕ clear button. */
.audio-bar-shuffle, .audio-bar-next { font-size: 1.3rem; padding: 0 0.4rem; }
.audio-bar-shuffle svg, .audio-bar-next svg { width: 1.3rem; height: 1.3rem; display: block; pointer-events: none; }
.audio-bar-next:hover, .audio-bar-next:active,
.audio-bar-clear:hover, .audio-bar-clear:active,
.audio-bar-shuffle:hover, .audio-bar-shuffle:active {
  background: none; border-color: transparent; color: var(--accent-hi);
}
.audio-bar-next:disabled, .audio-bar-shuffle:disabled { opacity: 0.4; cursor: default; }
/* iOS leaves a button focused after a tap, showing a grey focus box around the
   skip/shuffle glyphs (and matches :focus-visible on touch, so guarding on that
   doesn't help). Kill the outline/box-shadow on these transport controls and pin
   the tap highlight transparent on the elements themselves. */
.audio-bar-next, .audio-bar-shuffle, .audio-bar-clear, .audio-bar-queue {
  -webkit-tap-highlight-color: transparent;
  outline: none;
}
.audio-bar-next:focus, .audio-bar-shuffle:focus,
.audio-bar-clear:focus, .audio-bar-queue:focus,
.audio-bar-next:focus-visible, .audio-bar-shuffle:focus-visible,
.audio-bar-clear:focus-visible, .audio-bar-queue:focus-visible {
  outline: none;
  box-shadow: none;
}
.audio-bar audio { flex: 1; height: 36px; min-width: 0; }
.audio-bar audio.hidden { display: none; }

/* Wrapper that anchors the picker above the device button (not the bar edge). */
.audio-device-wrap { position: relative; flex: none; align-self: center; display: inline-flex; }
/* Which device is producing sound; doubles as the transfer-picker toggle. */
.audio-bar-device {
  flex: none; padding: 0 0.4rem;
  display: inline-flex; align-items: center; gap: 0.3rem; max-width: 11rem;
  background: none; border: none; color: var(--muted);
  font-size: 0.85rem; line-height: 1; cursor: pointer;
  white-space: nowrap; -webkit-tap-highlight-color: transparent; outline: none;
}
.audio-bar-device[hidden] { display: none; }
.audio-device-ico { flex: none; }
.audio-device-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* Match the other bar buttons: the global button:hover bleeds a grey box in, so
   keep it transparent and brighten only the glyph. */
.audio-bar-device:hover, .audio-bar-device:active {
  background: none; border-color: transparent; color: var(--accent-hi);
}
.audio-bar-device:focus, .audio-bar-device:focus-visible { outline: none; box-shadow: none; }
/* Highlighted when this device is only a remote (playback is elsewhere). */
.audio-bar-device.remote { color: var(--accent); }

/* Device picker, anchored above the device button via .audio-device-wrap. */
.audio-bar-devices {
  position: absolute; right: 0; bottom: 100%; margin-bottom: 0.4rem;
  list-style: none; margin-left: 0; margin-right: 0; padding: 0.3rem;
  background: var(--panel-2); border: 1px solid var(--accent-soft);
  border-radius: 4px; z-index: 40; min-width: 14rem;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
.audio-bar-devices.hidden { display: none; }
.audio-device-item {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.4rem 0.5rem; border-radius: 4px; cursor: pointer;
}
.audio-device-item:hover { background: var(--accent-softer); }
.audio-device-item.is-current { cursor: default; }
.audio-device-item.offline { opacity: 0.5; cursor: default; }
.audio-device-item.offline:hover { background: none; }
.audio-device-dot {
  flex: none; width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent);
}
.audio-device-item.offline .audio-device-dot { background: var(--muted); }
.audio-device-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.audio-device-badge { flex: none; color: var(--accent); font-size: 0.75rem; }
.audio-device-empty { padding: 0.4rem 0.5rem; color: var(--muted); }

/* Read-only transport shown on a remote device (in place of the <audio>). */
.audio-remote { flex: 1; display: flex; align-items: center; gap: 0.6rem; min-width: 0; }
.audio-remote.hidden { display: none; }
.audio-remote-play {
  flex: none; width: 36px; height: 36px; padding: 0; border-radius: 50%;
  border: 1px solid var(--accent-soft); background: var(--panel);
  color: var(--accent); font-size: 1rem; line-height: 1; cursor: pointer;
}
.audio-remote-play:hover { background: var(--accent-softer); }
.audio-remote-seek {
  flex: 1; min-width: 0; height: 6px; border-radius: 3px;
  background: var(--accent-soft); cursor: pointer; position: relative;
}
.audio-remote-fill {
  position: absolute; left: 0; top: 0; bottom: 0; width: 0;
  border-radius: 3px; background: var(--accent);
}
.audio-remote-time {
  flex: none; color: var(--muted); font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}

/* Upcoming-tracks panel; expands upward above the controls. */
.audio-bar-list {
  list-style: none; margin: 0; padding: 0;
  max-height: 40vh; overflow-y: auto;
  border-bottom: 1px solid var(--accent-soft);
}
.audio-bar-list.hidden { display: none; }
.audio-q-item {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.3rem 0.1rem;
}
.audio-q-item + .audio-q-item { border-top: 1px solid var(--border, var(--accent-soft)); }
.audio-q-title {
  flex: 1; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.audio-q-up, .audio-q-down, .audio-q-del {
  flex: none; padding: 0 0.35rem;
  background: none; border: none; color: var(--muted);
  font-size: 1rem; line-height: 1; cursor: pointer;
}
.audio-q-up:hover, .audio-q-up:active,
.audio-q-down:hover, .audio-q-down:active,
.audio-q-del:hover, .audio-q-del:active { color: #11151a; }
.audio-q-up:disabled, .audio-q-down:disabled { opacity: 0.3; cursor: default; }

@media (max-width: 560px) {
  /* Player bar: stack so the title and the native scrubber each get the full
     width — otherwise iOS truncates the title and hides the progress bar. */
  .audio-bar-main { flex-direction: column; align-items: stretch; gap: 0.4rem; }
  .audio-bar-info { flex: none; }
  .audio-bar audio { width: 100%; }
  /* Collapse the device chip to just its icon on a phone (it would otherwise
     push the row past the edge); the picker still spells out each device name. */
  .audio-device-label { display: none; }
  .audio-bar-device { max-width: none; padding: 0 0.35rem; }
  body.audio-playing { padding-bottom: 128px; }

  /* Manage view: the thumbnail + title row can't also fit the action buttons on
     a phone, so let the buttons wrap to their own full-width row below instead
     of squeezing the title to an unreadable sliver. */
  #sub-video-list .album-head { flex-wrap: wrap; }
  #sub-video-list .album-head .actions { flex-basis: 100%; }
  /* A nowrap title gives .album-info a huge intrinsic width that, combined with
     the wrapping head, overflows the viewport. Let the title wrap, and stack the
     meta so the date sits under the artist instead of widening the row. */
  #sub-video-list .album-info h3 { white-space: normal; overflow: visible; overflow-wrap: anywhere; }
  #sub-video-list .album-info .meta { white-space: normal; overflow: visible; }
  #sub-video-list .album-info .meta-artist,
  #sub-video-list .album-info .meta-extra { display: block; }
  #sub-video-list .album-info .meta-extra::before { content: none; }
}

/* --- Auth: user chip, login & change-password, users panel --------------- */

.user-chip {
  margin-left: auto;
  position: relative;
}
.user-chip.hidden { display: none; }
/* Art Deco tab: angled (chamfered) left/right ends with a hairline silver
   outline, drawn via two clipped layers since a border can't follow clip-path. */
.user-menu-btn {
  --notch: 10px;
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0.32rem 1rem;
  color: var(--text);
  letter-spacing: 0.04em;
  cursor: pointer;
  clip-path: polygon(0 50%, var(--notch) 0, calc(100% - var(--notch)) 0,
                     100% 50%, calc(100% - var(--notch)) 100%, var(--notch) 100%);
}
.user-menu-btn::before,
.user-menu-btn::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  clip-path: inherit;
}
.user-menu-btn::before { background: var(--accent-soft); }   /* hairline edge */
.user-menu-btn::after { inset: 1px; background: var(--panel); } /* inner fill */
.user-menu-btn:hover::before { background: var(--accent); }
.user-menu-btn .user-caret { font-size: 0.7rem; color: var(--muted); }
.user-menu {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  display: flex;
  flex-direction: column;
  min-width: 11rem;
  background: var(--panel);
  border: 1px solid var(--accent-soft);
  border-radius: 0;
  padding: 0.3rem;
  z-index: 20;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}
.user-menu.hidden { display: none; }
.user-menu button { text-align: left; }

/* Keep the app shell hidden until a session is established so it doesn't show
   behind the login gate. */
body:not(.authed) nav.tabs,
body:not(.authed) main { display: none; }

.user-name { font-weight: 600; color: var(--text); }

.login-box { max-width: 22rem; }
.login-box form { display: flex; flex-direction: column; gap: 0.7rem; margin-top: 0.6rem; }
.login-box label { display: flex; flex-direction: column; gap: 0.25rem; }
.login-box input { width: 100%; box-sizing: border-box; }

#user-list { margin-top: 0.8rem; }
li.user-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  flex-wrap: wrap;
}
li.user-row.disabled { opacity: 0.55; }
.user-row-main { display: flex; flex-direction: column; gap: 0.15rem; }
.user-row-actions { display: flex; gap: 0.35rem; flex-wrap: wrap; }
.user-devices { list-style: none; margin: 0.25rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.15rem; }
.user-devices li { display: flex; align-items: center; gap: 0.35rem; font-size: 0.78rem; color: var(--muted); }
.device-dot { width: 0.45rem; height: 0.45rem; border-radius: 50%; background: var(--muted); flex: 0 0 auto; }
.device-dot.current { background: var(--ok); }
.device-name { color: var(--text); }
.device-badge {
  padding: 0 0.4rem;
  border-radius: 0.7rem;
  background: var(--accent-softer);
  border: 1px solid var(--accent-soft);
  color: var(--accent);
  font-size: 0.68rem;
  letter-spacing: 0.03em;
}

/* Hide the per-video tag button for users without tag permission. */
body:not(.can-tag) .tag-btn { display: none; }

/* --- Audiobooks --------------------------------------------------------- */
.audiobook-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 1rem;
}
.audiobook-card {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: 0;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  color: inherit;
}
/* Folder imports that are still assembling (or failed) are not playable. */
.audiobook-card-importing, .audiobook-card-failed { cursor: default; }
.audiobook-card-failed { opacity: 0.7; }
.audiobook-card-failed .audiobook-card-sub { color: var(--danger); }
.audiobook-card-art {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 3px;
  overflow: hidden;
  background: var(--accent-softer);
}
.audiobook-card-cover {
  width: 100%;
  height: 100%;
  /* contain, not cover: book/audiobook covers vary in aspect ratio, so show the
     whole image rather than cropping the top and bottom off a portrait cover. */
  object-fit: contain;
  display: block;
}
.audiobook-card-nocover {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.4rem;
  color: var(--muted);
}
.audiobook-card-title {
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1.25;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.audiobook-card-sub { font-size: 0.75rem; color: var(--muted); }

/* Player */
.audiobook-box { width: min(720px, 100%); }
.audiobook-head { display: flex; gap: 1rem; align-items: flex-start; }
.audiobook-cover {
  /* Fixed width, natural height so the full cover shows without the bottom being
     clipped (covers are often portrait). max-height guards an extreme aspect. */
  width: 140px;
  height: auto;
  max-height: 220px;
  object-fit: contain;
  align-self: flex-start;
  border-radius: 3px;
  background: var(--accent-softer);
  flex: 0 0 auto;
}
.audiobook-meta { min-width: 0; }
.audiobook-meta h3 { margin: 0 0 0.2rem; }
#audiobook-audio { width: 100%; margin: 0.8rem 0 0.4rem; }
.audiobook-skip {
  display: flex;
  justify-content: center;
  gap: 0.6rem;
  margin-bottom: 0.6rem;
}
.audiobook-skip button {
  font: inherit;
  font-variant-numeric: tabular-nums;
  padding: 0.3rem 0.9rem;
  border: 1px solid var(--border, rgba(255, 255, 255, 0.15));
  border-radius: 999px;
  background: var(--accent-softer);
  color: inherit;
  cursor: pointer;
}
.audiobook-skip button:hover { border-color: var(--accent); color: var(--accent); }
.audiobook-chapters {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 16rem;
  overflow-y: auto;
  border-top: 1px solid var(--border, rgba(255, 255, 255, 0.1));
}
.audiobook-chapter {
  display: flex;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.4rem 0.5rem;
  cursor: pointer;
  border-bottom: 1px solid var(--border, rgba(255, 255, 255, 0.08));
  font-size: 0.85rem;
}
.audiobook-chapter:hover { background: var(--accent-softer); }
.audiobook-chapter.active { background: var(--accent-softer); font-weight: 600; }
.audiobook-chapter-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.audiobook-chapter-time { color: var(--muted); flex: 0 0 auto; }

/* Manage-audiobooks modal list */
.manage-list {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  max-height: 60vh;
  overflow-y: auto;
}
.manage-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border, rgba(255, 255, 255, 0.1));
  border-radius: 3px;
}
.manage-row-cover {
  flex: 0 0 auto;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 2px;
  object-fit: contain;
  background: #0c0a06;
}
.manage-row-nocover {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.3rem;
  color: var(--muted);
}
.manage-row-main { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; flex: 1 1 auto; }
.manage-row-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.manage-row-sub { font-size: 0.8rem; color: var(--muted); }
.manage-row-actions { display: flex; gap: 0.35rem; flex: 0 0 auto; }

/* Upload progress bar (inline, distinct from the absolute overlay .progress). */
.upload-progress { display: flex; align-items: center; gap: 0.6rem; margin: 0.6rem 0; }
.upload-bar {
  flex: 1;
  height: 6px;
  background: var(--accent-softer);
  border-radius: 3px;
  overflow: hidden;
}
.upload-bar-fill { display: block; height: 100%; width: 0; background: var(--accent); transition: width 0.1s linear; }
