/* Design tokens — adopted from the project's Figma file (light SaaS theme). */
:root {
  --bg: #f7f7fa;
  --surface: #ffffff;
  --surface-2: #f0f0f5;
  --staged-bg: #f8f8fc;
  --text: #1e1e1e;
  --muted: #5e5e5e;
  --faint: #a7a7a7;
  --border: #e6e6eb;
  --border-strong: #d9d9e3;

  --brand: #6d57dd;
  --brand-50: #ebe9ff;
  --focus: #1f9aff;
  --focus-ring: rgba(31, 154, 255, 0.18);

  --tag: #d25edb;
  --success: #2bb66c;
  --success-50: #e9fff3;
  --danger: #eb5a5a;
  --danger-50: #ffe9e9;
  --danger-border: #f3c9c9;
  --warn: #d55d2d;
  --warn-50: #fff3e9;

  --ink: #1e1e1e;

  --radius: 10px;
  --radius-lg: 16px;
  --pill: 999px;
  --shadow-sm: 0 1px 2px rgba(16, 16, 40, 0.04), 0 1px 3px rgba(16, 16, 40, 0.05);
  --shadow-md: 0 10px 28px rgba(16, 16, 40, 0.1);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

h2 { font-size: 1.4rem; font-weight: 700; letter-spacing: -0.01em; }
h3 { font-size: 1.05rem; font-weight: 600; }
a { color: var(--brand); }
.muted { color: var(--muted); }
.small { font-size: 0.85em; }
code {
  background: var(--surface-2);
  padding: 0.12rem 0.4rem;
  border-radius: 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.88em;
}

/* --- Top bar --- */
.topbar {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 0.7rem 1.5rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border-strong);
  box-shadow: 0 2px 5px -2px rgba(20, 20, 40, 0.1);
  position: sticky;
  top: 0;
  z-index: 12;
}
.brand { display: flex; align-items: center; gap: 0.75rem; font-weight: 700; letter-spacing: -0.01em; flex: none; white-space: nowrap; background: none; border: 0; padding: 0; margin: 0; font-family: inherit; font-size: inherit; color: inherit; cursor: pointer; }
.brand-name { white-space: nowrap; }
.flip-i { display: inline-block; transform: rotate(180deg); margin: 0 0.2rem; }
.brand-b { margin-left: 0.35rem; }
.brand::before {
  content: "";
  width: 22px; height: 22px;
  border-radius: 7px;
  background: linear-gradient(135deg, var(--brand), var(--tag));
}
.tabs { display: flex; gap: 0.25rem; flex: 1; }
.tab {
  display: inline-flex; align-items: center; gap: 0.4rem;
  background: transparent; color: var(--muted); border: 0;
  padding: 0.45rem 0.85rem; border-radius: 8px; cursor: pointer;
  font: inherit; font-weight: 500; transition: background 0.12s, color 0.12s;
}
.tab svg { flex: none; opacity: 0.85; }
.tab:hover { color: var(--text); background: var(--surface-2); }
.tab.active { color: var(--brand); background: var(--brand-50); font-weight: 600; }
/* Item-count badge on a nav tab (reflects what this user can see on that page). */
.tab-count {
  font-size: 0.72rem; font-weight: 700; line-height: 1; color: var(--faint);
  background: var(--surface-2); border-radius: 999px; padding: 0.12rem 0.4rem;
  min-width: 1.15em; text-align: center; font-variant-numeric: tabular-nums;
}
.tab.active .tab-count { color: var(--brand); background: #fff; }

/* On desktop the wrapper is transparent — tabs + who lay out directly in the
   topbar flex, exactly as before. On mobile it becomes a dropdown (see below). */
.topbar-nav { display: contents; }
.menu-backdrop { display: none; } /* mobile only — dims/blocks content behind the open menu */
/* Hamburger toggle — hidden on desktop, shown on mobile. */
.hamburger {
  display: none; align-items: center; justify-content: center;
  width: 40px; height: 40px; margin-left: auto; flex: none;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text); cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.hamburger:hover { background: var(--bg); border-color: var(--border-strong); }

.who { display: flex; align-items: center; gap: 0.6rem; }
/* Label shown only in the mobile menu (desktop keeps the icon-only buttons). */
.who-label { display: none; }
.add-song-btn { margin-right: 0.1rem; height: 34px; display: inline-flex; align-items: center; }
.who-icon-btn {
  display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--muted);
  cursor: pointer; margin-right: 0.3rem; transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.who-icon-btn:hover { background: var(--bg); border-color: var(--border-strong); color: var(--text); }
.who-icon-btn.active { background: var(--brand-50); border-color: var(--brand-50); color: var(--brand); }
.email { color: var(--muted); font-size: 0.9em; }
.role {
  text-transform: uppercase; font-size: 0.66em; font-weight: 700;
  letter-spacing: 0.06em; padding: 0.2rem 0.5rem; border-radius: var(--pill);
}
.role-admin { color: var(--brand); background: var(--brand-50); }
/* Only admin is colour-coded in the top bar; viewer + partner are neutral. */
.role-viewer, .role-partner { color: var(--muted); background: var(--surface-2); }

/* --- Quick-search: loupe buttons + the drop-down palette --- */
/* Desktop: the loupe is a right-hand SEGMENT of the Repertoire tab — flush against
   it (cancels the tabs gap), a hairline divider, matching the tab's fill so the two
   read as one pill (a purple pill + search segment when Repertoire is active). */
.tab:has(+ .qs-btn-desktop) { border-top-right-radius: 0; border-bottom-right-radius: 0; padding-right: 0.65rem; }
.qs-btn-desktop {
  display: inline-flex; align-items: center; justify-content: center; align-self: stretch;
  width: 34px; flex: none; margin-left: -0.25rem;
  border: 0; border-left: 1px solid var(--border-strong); border-radius: 0 8px 8px 0;
  background: transparent; color: var(--muted); cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.qs-btn-desktop:hover { background: var(--surface-2); color: var(--text); }
.qs-btn-desktop svg { flex: none; }
/* Repertoire active → the loupe adopts the pill tint + a soft brand divider. */
.tab.active + .qs-btn-desktop { background: var(--brand-50); color: var(--brand); border-left-color: rgba(109, 87, 221, 0.22); }
.tab.active + .qs-btn-desktop:hover { background: var(--brand-50); color: var(--brand); }
.qs-btn-mobile { display: none; } /* mobile only (see media query) */

/* Top-level modal — above the edit bar (z-50) and the lifted edit-mode nav (z-51). */
.qs-backdrop { position: fixed; inset: 0; z-index: 60; background: rgba(20, 20, 40, 0.18); }
.qs-panel {
  position: fixed; left: 1.5rem; width: min(460px, calc(100vw - 3rem)); z-index: 61;
  top: calc(var(--qs-top, 56px) + 6px); /* --qs-top = header bottom (measured); +6 = desktop gap */
  display: flex; flex-direction: column; overflow: hidden;
  max-height: calc(100vh - var(--qs-top, 56px) - 20px);
  max-height: calc(100dvh - var(--qs-top, 56px) - 20px); /* viewport-bound (dvh tracks iOS toolbars) */
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: 12px;
  box-shadow: var(--shadow-md);
}
/* Closed: kept in the DOM (so the input can be focused synchronously on tap → the
   mobile keyboard appears) but hidden. NOT display/visibility:hidden — those block
   focus; opacity + pointer-events keep it invisible yet focusable. */
.qs-panel.qs-closed { opacity: 0; pointer-events: none; }
.qs-search { display: flex; align-items: center; gap: 0.5rem; padding: 0.55rem 0.7rem; border-bottom: 1px solid var(--border); flex: none; }
.qs-loupe { display: inline-flex; color: var(--muted); flex: none; }
.qs-input { flex: 1; min-width: 0; border: 0; background: transparent; font: inherit; font-size: 1rem; color: var(--text); outline: none; }
.qs-close { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: 0; border-radius: 6px; background: transparent; color: var(--muted); cursor: pointer; flex: none; }
.qs-close:hover { background: var(--surface-2); color: var(--text); }
.qs-list { overflow-y: auto; min-height: 0; padding: 0.3rem; } /* min-height:0 lets it scroll inside the capped panel */
.qs-row {
  display: flex; flex-direction: column; align-items: flex-start; gap: 0.05rem;
  width: 100%; text-align: left; padding: 0.5rem 0.6rem; border: 0; border-radius: 8px;
  background: transparent; cursor: pointer; font: inherit;
}
.qs-row.active { background: var(--brand-50); }
.qs-row-title { color: var(--text); font-weight: 600; }
.qs-row-artist { color: var(--muted); font-size: 0.85em; }
.qs-mark { background: transparent; color: var(--brand); font-weight: 700; } /* highlighted match */
.qs-empty { padding: 1rem 0.7rem; }

@media (max-width: 760px) {
  .qs-btn-desktop { display: none; }
  /* Mobile: before the hamburger, purple-outlined so it reads differently. */
  .qs-btn-mobile {
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px; flex: none; margin-left: auto;
    border: 1px solid rgba(109, 87, 221, 0.4); border-radius: var(--radius);
    background: var(--surface); color: rgba(109, 87, 221, 0.8); cursor: pointer;
  }
  .qs-btn-mobile:hover { background: var(--brand-50); }
  .topbar .hamburger { margin-left: 0.5rem; } /* sit just after the loupe */
  /* Mirror the hamburger menu: drop full-width from the header, and keep the header
     visible — the backdrop sits below the topbar (z-12) rather than over it. The
     header loupe→X is the close control, so the in-panel X is hidden. */
  .qs-backdrop { z-index: 11; }
  .qs-close { display: none; }
  .qs-panel {
    left: 0; right: 0; width: auto; top: var(--qs-top, 56px); /* flush under the header */
    max-height: calc(100vh - var(--qs-top, 56px) - 12px);
    max-height: calc(100dvh - var(--qs-top, 56px) - 12px);
    border: 0; border-bottom: 1px solid var(--border); border-radius: 0 0 14px 14px;
  }
}

/* Mobile topbar: brand + hamburger only; tabs/actions drop into a panel. */
@media (max-width: 760px) {
  .topbar { justify-content: space-between; gap: 0.75rem; }
  .hamburger { display: inline-flex; }
  .topbar-nav { display: none; }
  .topbar-nav.open {
    display: flex; flex-direction: column; align-items: stretch; gap: 0.35rem;
    position: absolute; top: 100%; left: 0; right: 0;
    background: var(--surface); border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-sm); padding: 0.6rem 1rem 0.9rem;
  }
  /* Behind the dropdown (z 12), above the page — tap it to close the menu. */
  .menu-backdrop { display: block; position: fixed; inset: 0; z-index: 11; background: rgba(20, 20, 40, 0.3); }
  .topbar-nav.open .tabs { flex-direction: column; gap: 0.15rem; flex: none; }
  .topbar-nav.open .tab { width: 100%; justify-content: flex-start; padding: 0.6rem 0.75rem; }
  .topbar-nav.open .tab-count { margin-left: auto; }
  /* Email + tier are not shown in the mobile menu. */
  .topbar-nav.open .email, .topbar-nav.open .role { display: none; }
  /* Non-admins have nothing left in .who once email/tier are hidden — drop it
     (and its separator) entirely. */
  .topbar-nav.open .who:not(.has-actions) { display: none; }
  .topbar-nav.open .who {
    flex-direction: column; align-items: stretch; gap: 0.15rem;
    border-top: 1px solid var(--border); margin-top: 0.5rem; padding-top: 0.6rem;
  }
  /* Manage users / System health as full-width menu items (matching the tabs). */
  .topbar-nav.open .who-icon-btn {
    width: 100%; height: auto; justify-content: flex-start; gap: 0.5rem;
    padding: 0.6rem 0.75rem; margin: 0; border: 0; border-radius: 8px;
    background: transparent; color: var(--muted); font: inherit; font-weight: 500;
  }
  .topbar-nav.open .who-icon-btn:hover { background: var(--surface-2); color: var(--text); }
  .topbar-nav.open .who-icon-btn.active { background: var(--brand-50); color: var(--brand); }
  .topbar-nav.open .who-icon-btn svg { flex: none; }
  .topbar-nav.open .who-label { display: inline; }
  /* Add song — full-width primary (dark) button pinned to the bottom. */
  .topbar-nav.open .add-song-btn {
    order: 10; width: 100%; justify-content: center; height: auto;
    padding: 0.7rem; margin: 0.55rem 0 0; font-size: 0.95em;
    background: var(--ink); color: #fff; border-color: var(--ink);
  }
  .topbar-nav.open .add-song-btn:hover { background: #333; border-color: #333; }
}

.content { padding: 1.75rem 1.5rem; max-width: 1080px; margin: 0 auto; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 1.25rem 1.5rem;
  box-shadow: var(--shadow-sm);
}
.card h2 { margin-top: 0; }

.centered { min-height: 70vh; display: grid; place-items: center; padding: 1.5rem; }

/* --- Buttons --- */
.btn {
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 0.5rem 0.9rem; cursor: pointer; font: inherit; font-weight: 500;
  transition: background 0.12s, border-color 0.12s, box-shadow 0.12s;
}
.btn:hover { background: var(--bg); border-color: var(--border-strong); }
.btn:disabled { opacity: 0.55; cursor: default; }
.btn.primary { background: var(--ink); color: #fff; border-color: var(--ink); }
.btn.primary:hover { background: #333; border-color: #333; }
.btn.primary:disabled { opacity: 0.5; }
.btn.danger { background: var(--surface); color: var(--danger); border-color: var(--danger-border); }
.btn.danger:hover { background: var(--danger-50); border-color: var(--danger); }
/* Solid destructive CTA (e.g. "Remove the whole song"). */
.btn.danger-solid { background: var(--danger); color: #fff; border-color: var(--danger); }
.btn.danger-solid:hover { background: #d63d3d; border-color: #d63d3d; }
.danger-text { color: var(--danger); }
.danger-text strong { color: var(--danger); }
.small-btn { padding: 0.28rem 0.6rem; font-size: 0.85em; }

/* --- Inputs --- */
.input, input, select, textarea {
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 0.55rem 0.75rem; font: inherit;
}
.input:focus, input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--focus); box-shadow: 0 0 0 3px var(--focus-ring);
}
/* iOS Safari zooms the page into any form control whose font-size is < 16px when
   it's focused (and leaves the page zoomed afterwards). Force 16px on mobile so
   focusing a field never triggers that zoom. Checkboxes/radios are exempt. */
@media (max-width: 760px) {
  input:not([type="checkbox"]):not([type="radio"]), select, textarea, .input { font-size: 16px !important; }
}
::placeholder { color: var(--faint); }
.input[type="search"] { flex: 1; min-width: 220px; }
/* Replace the browser's native (blue) search-clear button with an on-brand one. */
input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none; appearance: none;
  width: 14px; height: 14px; cursor: pointer; opacity: 0.5;
  background: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23a7a7a7'%20stroke-width='2.6'%20stroke-linecap='round'%3E%3Cline%20x1='5'%20y1='5'%20x2='19'%20y2='19'/%3E%3Cline%20x1='19'%20y1='5'%20x2='5'%20y2='19'/%3E%3C/svg%3E") center / contain no-repeat;
}
input[type="search"]::-webkit-search-cancel-button:hover { opacity: 1; }

/* --- Chips & badges --- */
.chips { display: flex; flex-wrap: wrap; gap: 0.35rem; }
/* Fit-to-width tag row: JS shows as many chips as fit within the allowed number of
   rows and folds the rest into a "+N". Wrap is on (multi-row capable); the JS hides
   the chips that don't fit via display:none, so nothing spills and each chip's hover
   ring (box-shadow) shows in full. flex:none so chips keep their intrinsic width. */
.chips.tl-row { flex-wrap: wrap; }
.chips.tl-row .chip { flex: none; }
.song-card .chips { align-self: start; align-content: start; }
.chip {
  font-size: 0.75em; padding: 0.14rem 0.55rem; border-radius: var(--pill);
  background: var(--surface-2); border: 0; color: var(--muted);
}
.chip.subtle { background: transparent; border: 1px solid var(--border); }
.chip.tag { background: var(--brand-50); color: var(--brand); }
/* Clickable tag → adds itself to the tag filter. Ring on hover/focus. */
.chip.tag.tag-filter { cursor: pointer; transition: box-shadow 0.12s; }
.chip.tag.tag-filter:hover, .chip.tag.tag-filter:focus-visible { box-shadow: 0 0 0 1.5px var(--brand); outline: none; }
/* Small uppercase tier pill (song-detail section badges). Colour via .tier-*. */
.tag-tier {
  font-size: 0.62em; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 0.1rem 0.4rem; border-radius: var(--pill); vertical-align: middle; font-weight: 700;
}
/* One tier colour scheme, shared by section badges AND the roster/invite chips:
   viewer = blue, partner = teal, admin = violet. */
.tier-viewer { color: #2b6cb0; background: #e3edfa; }
.tier-partner { color: #0f766e; background: #cdf4ec; }
.tier-admin { color: var(--brand); background: var(--brand-50); }

/* --- Browse --- */
/* Content pages (Repertoire / Performances / Mixdowns) show their name as a body
   heading on mobile only — on desktop the tab bar already marks the active page. */
.list-page-title { display: none; }
@media (max-width: 760px) {
  .list-page-title { display: block; margin: 0 0 1rem; font-size: 1.6rem; font-weight: 800; letter-spacing: -0.02em; }
}
.filters { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-bottom: 1.25rem; }
.browse-body { display: grid; grid-template-columns: 220px 1fr; gap: 1.6rem; align-items: start; }
.input.filter-search { width: 100%; min-width: 0; height: 2.3rem; }
/* The collapsed sticky bar uses a button (not an input) so tapping it scrolls to
   the real search without focusing a field — avoids iOS's focus-zoom. */
.filter-search-btn { display: flex; align-items: center; text-align: left; color: var(--faint); cursor: pointer; }
/* Reset sits at the top-right of the song panel. */
.browse-toolbar { display: flex; justify-content: flex-end; align-items: center; gap: 0.5rem; margin-bottom: 1rem; }
.browse-reset {
  flex: none; background: transparent; border: 0; cursor: pointer; font: inherit;
  color: var(--brand); font-weight: 600; padding: 0.5rem 0.7rem; border-radius: var(--radius);
}
.browse-reset:hover { background: var(--brand-50); }
.grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.browse-main .grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* Left filter panel */
.filter-panel { position: sticky; top: 72px; align-self: start; display: flex; flex-direction: column; gap: 1.5rem; }
/* Tag filter dropdown: count on the right of each option. */
.filter-group .tag-option { display: flex; align-items: center; gap: 0.5rem; }
.tag-option-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tag-option-count { flex: none; color: var(--faint); font-size: 0.85em; }
/* Filter field hugs its chips; empty state matches the search input height. */
.filter-group .tags-field { min-height: 2.3rem; }
.filter-group .tag-text { padding: 0.1rem 0.25rem; line-height: 1.3; }
.filter-group .tag-text.collapsed { flex: 0 0 0; width: 0; min-width: 0; padding: 0; }
.filter-h { margin: 0 0 0.5rem; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--faint); font-weight: 700; }
/* Heading row that carries a right-aligned action (e.g. Reset filters). */
.filter-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.5rem; min-height: 1.4rem; }
.filter-head .filter-h { margin: 0; }
/* Compact sticky bar (mobile only) that replaces the standard panel once it has
   scrolled out of view — an empty search field, or a "X of Y · Change · Reset"
   summary when filtered. Fixed under the topbar; fades in when stuck. */
.filter-sticky { display: none; }
.fc-count { flex: 1 1 auto; color: var(--muted); font-size: 0.95em; }
.fc-count strong { color: var(--text); }
.fc-link { flex: none; background: transparent; border: 0; color: var(--brand); cursor: pointer; font: inherit; font-size: 0.92em; font-weight: 600; padding: 0.2rem 0.3rem; }
.fc-link:hover { text-decoration: underline; }
.filter-reset { padding: 0.1rem 0.35rem; font-size: 0.9em; }
.filter-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.filter-opt {
  display: flex; align-items: center; gap: 0.5rem; width: 100%; text-align: left;
  background: transparent; border: 0; border-radius: 8px; padding: 0.36rem 0.5rem;
  cursor: pointer; font: inherit; font-size: 0.92em; color: var(--text);
}
.filter-opt:hover { background: var(--surface-2); }
.filter-opt.active { background: var(--brand-50); color: var(--brand); }
.filter-opt-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filter-count { flex: none; color: var(--faint); font-size: 0.85em; }
.filter-opt.active .filter-count { color: var(--brand); }
.filter-ic { flex: none; display: inline-flex; color: var(--muted); }
.filter-opt.active .filter-ic { color: var(--brand); }
.filter-radio { display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0.5rem; cursor: pointer; font-size: 0.92em; color: var(--text); }
.filter-radio input { accent-color: var(--brand); }
.filter-more { margin-top: 0.2rem; align-self: flex-start; background: transparent; border: 0; color: var(--brand); cursor: pointer; font: inherit; font-size: 0.85em; font-weight: 500; padding: 0.3rem 0.5rem; }
.filter-more:hover { text-decoration: underline; }
/* Desktop: the collapsible groups lay out directly in the panel; toggle hidden. */
.filter-collapsible { display: contents; }
.filter-more-toggle { display: none; }

/* Desktop sticky filter+header bar for the list pages (Performances / Mixed-down).
   Mobile uses the compact .filter-sticky above; this is desktop-only. Once the
   filter panel scrolls under the topbar it pins the *same* filter panel + column
   headers to the top, slide/fading in like the song-detail sticky header. The
   inner content reuses the identical .perf-filters / .perf-grid markup, so nothing
   about the controls changes when they stick. */
.list-sticky { display: none; }
@media (min-width: 761px) {
  .list-sticky {
    display: block; position: fixed; left: 0; right: 0; z-index: 11;
    background: var(--surface); border-bottom: 1px solid var(--border); box-shadow: var(--shadow-sm);
    opacity: 0; transform: translateY(-6px); pointer-events: none;
    transition: opacity 0.16s ease, transform 0.16s ease;
  }
  .list-sticky.show { opacity: 1; transform: none; pointer-events: auto; }
  .list-sticky-inner { max-width: 1080px; margin: 0 auto; padding: 0.9rem 1.5rem 0; }
  .list-sticky-inner .perf-filters { margin-bottom: 0.9rem; }
  /* No card around the pinned header — just a thin separator line above the column
     titles, with the header blending into the white sticky bar. (`.perf-grid.…`
     to out-specify the base `.perf-grid` card border/radius declared later.) */
  .perf-grid.list-sticky-grid { border: 0; border-radius: 0; box-shadow: none; background: transparent; }
  .perf-grid.list-sticky-grid .perf-head { background: transparent; border-top: 1px solid var(--border); }
}

@media (max-width: 760px) {
  .browse-body { grid-template-columns: 1fr; gap: 1.25rem; }
  /* Mobile: only Search + Tags show; the panel sticks under the topbar so the
     search stays reachable while scrolling the list. */
  .filter-panel {
    position: sticky; top: 50px; z-index: 5;
    background: var(--bg); gap: 0.9rem;
    max-height: none; overflow: visible;
    /* Full-bleed: cancel the .content side padding, re-add it inside so the band
       spans edge-to-edge while the fields keep their inset. Sits below the page
       title. */
    margin: 0 -1.5rem 0; padding: 0.85rem 1.5rem;
    border-bottom: 1px solid var(--border);
    box-shadow: 0 6px 12px -6px rgba(20, 20, 40, 0.18);
  }
  .browse-main .grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
  /* Includes + Updated collapse under "View more". */
  .filter-collapsible { display: none; }
  .filter-collapsible.open { display: flex; flex-direction: column; gap: 1.25rem; }
  .filter-more-toggle {
    display: inline-block; align-self: center; margin: -0.2rem 0 0;
    background: transparent; border: 0; color: var(--brand); cursor: pointer;
    font: inherit; font-size: 0.9em; font-weight: 600; padding: 0.1rem 0.5rem;
  }
  .filter-more-toggle:hover { text-decoration: underline; }
  /* The standard panel scrolls away (not sticky); the compact bar takes over. */
  .filter-panel { position: static; box-shadow: none; }
  .filter-sticky {
    display: flex; align-items: center; gap: 0.7rem;
    position: fixed; top: 50px; left: 0; right: 0; z-index: 9;
    padding: 0.65rem 1.5rem; background: var(--bg);
    border-bottom: 1px solid var(--border); box-shadow: 0 6px 12px -6px rgba(20, 20, 40, 0.18);
    opacity: 0; pointer-events: none; transition: opacity 0.16s ease;
  }
  .filter-sticky.show { opacity: 1; pointer-events: auto; }
  .filter-sticky .filter-search { flex: 1 1 auto; min-width: 0; }
}

/* --- Performances grid --- */
/* Horizontal filter bar above the full-width grid. */
.perf-filters { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 1.25rem; margin-bottom: 1.25rem; }
.perf-f-search, .perf-f-tags { width: 18rem; max-width: 100%; }
.perf-type-list { flex-direction: row; flex-wrap: wrap; gap: 0.25rem; }
.perf-f-reset { align-self: flex-end; margin-left: auto; }
/* Desktop: reset sits at the far right of the row; the search head is just a
   heading (no extra height) so the fields line up with the other groups. */
.perf-reset-inline { display: none; }
.perf-f-search .filter-head { min-height: 0; }
@media (max-width: 760px) {
  /* Standard filter panel: full-bleed band that scrolls away with the page (the
     compact .filter-sticky bar then takes over — same as the repertoire page). */
  .perf-filters {
    flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 0.9rem;
    margin: 0 -1.5rem 1.25rem; padding: 0.85rem 1.5rem;
    background: var(--bg); border-bottom: 1px solid var(--border);
  }
  .perf-f-search, .perf-f-tags { width: 100%; }
  /* Mobile: reset moves inline with the search heading (like the repertoire page). */
  .perf-reset-end { display: none; }
  .perf-reset-inline { display: inline-flex; }
  .perf-f-search .filter-head { min-height: 1.4rem; }
}
.perf-grid { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-sm); }
.perf-row {
  display: grid;
  /* Media hugs the thumbnails (was up to 210px, leaving a gap); the reclaimed
     width goes to the Event and Song columns. */
  grid-template-columns: minmax(96px, 168px) 6rem 5.4rem minmax(8rem, 1.5fr) minmax(9rem, 1.6fr) minmax(7rem, 1fr);
  gap: 0.9rem; align-items: center; padding: 0.7rem 0.9rem;
  border-top: 1px solid var(--border); cursor: pointer;
}
/* Media hugs the audio tiles and Song is capped to its content, so the reclaimed
   space goes to Tags instead of empty gaps between the columns. */
.perf-row.mix-row { grid-template-columns: minmax(84px, 140px) minmax(10rem, 18rem) minmax(15rem, 1fr); }
/* Windowing: off-screen rows aren't rendered or laid out (same native technique
   as the repertoire cards), so hundreds of performances/mixdowns stay cheap. The
   header always renders. contain-intrinsic-size reserves each row's space. */
.perf-row:not(.perf-head) { content-visibility: auto; contain-intrinsic-size: auto 88px; }
.perf-row:first-child { border-top: 0; }
.perf-row:not(.perf-head):hover { background: var(--surface-2); }
.perf-head { font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--faint); font-weight: 700; cursor: default; background: var(--bg); }
.perf-cell { min-width: 0; }
/* Per-cell caption — shown only on the mobile cards (desktop has column headers). */
.perf-cell-cap { display: none; }
.perf-date-cell { font-variant-numeric: tabular-nums; color: var(--muted); font-size: 0.92em; }
/* Event · Place stacks like the Song cell: event (bold) over place (muted),
   each a single line with ellipsis. */
.perf-event-cell { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.perf-event-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Empty event/place placeholder — fainter + italic so it doesn't read as content. */
.perf-empty { color: var(--faint); font-style: italic; }
.perf-place-name { font-size: 0.86em; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.perf-song-link { display: flex; flex-direction: column; gap: 1px; background: transparent; border: 0; padding: 0; text-align: left; cursor: pointer; font: inherit; color: var(--text); min-width: 0; max-width: 100%; }
.perf-song-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.perf-song-link:hover .perf-song-title { color: var(--brand); text-decoration: underline; }
.perf-song-artist { font-size: 0.86em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.perf-tags-cell .chips:not(.tl-row) { flex-wrap: wrap; }

.perf-media { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
.perf-thumb {
  position: relative; width: 58px; height: 40px; flex: none; border: 1px solid transparent; padding: 0; cursor: pointer;
  border-radius: 6px; overflow: hidden; background: #000; display: inline-flex; align-items: center; justify-content: center;
  transition: border-color 0.12s, box-shadow 0.12s;
}
/* Violet border on hover, like the song-detail media tiles. */
.perf-thumb:hover { border-color: var(--brand); box-shadow: var(--shadow-md); }
.perf-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.perf-thumb.audio { background: var(--brand-50); color: var(--brand); }
.perf-audio-ic { display: inline-flex; }
.perf-play { position: absolute; }
.perf-thumb.video .perf-play { inset: 0; display: flex; align-items: center; justify-content: center; color: #fff; background: rgba(0, 0, 0, 0.18); }
.perf-thumb.video:hover .perf-play { background: rgba(0, 0, 0, 0.3); }
.perf-thumb.audio .perf-play { right: 2px; bottom: 1px; color: var(--brand); }
/* Audio has no built-in overlay — add a slight one on hover. */
.perf-thumb.audio::after { content: ""; position: absolute; inset: 0; background: transparent; transition: background 0.12s; pointer-events: none; }
/* A registered media file that no longer exists in Dropbox — flagged on its tile
   (from the /api/media-status check) with a danger overlay + alert icon. */
.media-miss { position: absolute; inset: 0; z-index: 2; display: flex; align-items: center; justify-content: center; color: var(--danger); background: var(--danger-50); }
/* Missing tiles are inert (not openable) — keep them fully visible, not the
   browser's dimmed disabled look, with a default cursor. */
.perf-thumb.is-missing, .media-tile.is-missing { border-color: var(--danger-border); cursor: default; }
.media-tile.is-missing:disabled { opacity: 1; }
/* Audio player for a file gone from Dropbox — a flagged box in place of the waveform. */
.wave.wave-missing { color: var(--danger); background: var(--danger-50); border-color: var(--danger-border); }
.wave-miss-ic { display: inline-flex; flex: none; }
.perf-thumb.audio:hover::after { background: rgba(109, 87, 221, 0.14); }
.perf-extras { display: inline-flex; align-items: center; gap: 0.35rem; margin-left: 0.15rem; color: var(--faint); }
.perf-extra { display: inline-flex; align-items: center; gap: 1px; font-size: 0.85em; }
/* Audio played from a performance row uses the waveform player on a light panel. */
.lb-audio { background: var(--surface); border-radius: 12px; padding: 1.1rem 1.25rem; width: min(560px, 88vw); }
/* Details shown beneath the media in the lightbox. `width:0; min-width:100%`
   makes it stretch to the media's width (which defines the figure width) and
   wrap within it, instead of growing the figure. */
.lb-details { color: #fff; display: flex; flex-direction: column; align-items: center; gap: 0.4rem; text-align: center; width: 0; min-width: 100%; box-sizing: border-box; flex: 0 0 auto; max-height: 36vh; overflow-y: auto; }
.lb-details-song { display: flex; flex-direction: row; align-items: baseline; flex-wrap: wrap; justify-content: center; gap: 0.5rem; max-width: 100%; overflow-wrap: anywhere; }
.lb-details-title { font-weight: 700; font-size: 1.05rem; }
.lb-details-artist { color: rgba(255, 255, 255, 0.7); font-size: 0.95rem; }
.lb-details-artist::before { content: "·"; margin-right: 0.5rem; color: rgba(255, 255, 255, 0.45); }
.lb-details-meta { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 0.6rem; font-size: 0.9rem; color: rgba(255, 255, 255, 0.85); }
.lb-details-date { font-variant-numeric: tabular-nums; }
.lb-details-ep { color: rgba(255, 255, 255, 0.7); }
.lb-details-actions { margin-top: 0.2rem; display: flex; align-items: center; justify-content: center; gap: 0.8rem; flex-wrap: wrap; }
/* Per-item label (e.g. "Camera 2") shown in the gallery lightbox's action row. */
.lb-details-label { color: rgba(255, 255, 255, 0.85); font-size: 0.9rem; }
/* The download button sits on the dark lightbox, so give it a light surface. */
.lb-details-actions .dl-btn { background: #fff; }
.lb-meta {
  color: rgba(255, 255, 255, 0.85); font-size: 0.76rem; font-weight: 500;
  font-variant-numeric: tabular-nums; white-space: nowrap; letter-spacing: 0.01em;
  padding: 0.16rem 0.6rem; border: 1px solid rgba(255, 255, 255, 0.32); border-radius: 999px;
}

@media (max-width: 860px) {
  .perf-head { display: none; }
  .perf-row, .perf-row.mix-row { grid-template-columns: 1fr; gap: 0.4rem; }
  /* Taller stacked rows on mobile — reserve a bigger intrinsic size so the
     windowing scrollbar stays roughly stable (auto still learns the real size). */
  .perf-cards .perf-row { contain-intrinsic-size: auto 380px; }
  .perf-row.mix-row { contain-intrinsic-size: auto 200px; }
  /* Performances: each row becomes its own card, spaced apart, instead of a
     borderless continuous list. */
  .perf-cards {
    background: none; border: 0; border-radius: 0; box-shadow: none; overflow: visible;
    display: flex; flex-direction: column; gap: 0.9rem;
  }
  .perf-cards .perf-row {
    border: 1px solid var(--border); border-radius: var(--radius-lg);
    background: var(--surface); box-shadow: var(--shadow-sm); padding: 1rem 1.1rem;
    /* Type + date share one row (type left, date right); the rest span full width. */
    grid-template-columns: 1fr auto;
    grid-template-areas: "media media" "type date" "event event" "song song" "tags tags";
    gap: 0.55rem 0.6rem; align-items: center;
  }
  .perf-cards .perf-media-cell { grid-area: media; }
  .perf-cards .perf-type-cell { grid-area: type; justify-self: start; }
  .perf-cards .perf-date-cell { grid-area: date; justify-self: end; }
  .perf-cards .perf-event-cell { grid-area: event; }
  .perf-cards .perf-song-cell { grid-area: song; }
  .perf-cards .perf-tags-cell { grid-area: tags; }
  /* Label the Event and Song blocks so they're distinguishable from the content. */
  .perf-cards .perf-cell-cap, .mix-cards .perf-cell-cap {
    display: block; margin-bottom: 0.15rem;
    font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--faint); font-weight: 700;
  }
  /* Square thumbnails, up to 3 per row, with the "+N" extras icon on the side. */
  .perf-cards .perf-media, .mix-cards .perf-media { gap: 0.45rem; }
  .perf-cards .perf-thumb, .mix-cards .perf-thumb { flex: none; width: 76px; height: 76px; max-width: none; }

  /* Mixed-down songs: same card treatment as performances (media · song · tags). */
  .mix-cards {
    background: none; border: 0; border-radius: 0; box-shadow: none; overflow: visible;
    display: flex; flex-direction: column; gap: 0.9rem;
  }
  .mix-cards .perf-row.mix-row {
    border: 1px solid var(--border); border-radius: var(--radius-lg);
    background: var(--surface); box-shadow: var(--shadow-sm); padding: 1rem 1.1rem;
    grid-template-columns: 1fr; grid-template-areas: "media" "song" "tags"; gap: 0.6rem;
    contain-intrinsic-size: auto 220px; /* windowing estimate for the taller card */
  }
  .mix-cards .perf-media-cell { grid-area: media; }
  .mix-cards .perf-song-cell { grid-area: song; }
  .mix-cards .perf-tags-cell { grid-area: tags; }
}
/* Year band separator: label + a squiggly rule running the full grid width. */
.year-sep { grid-column: 1 / -1; display: flex; align-items: center; gap: 0.85rem; margin-top: 0.6rem; }
.year-sep:first-child { margin-top: 0; }
.year-label { flex: none; font-weight: 600; font-size: 1.05rem; color: var(--faint); }
.squiggle {
  flex: 1 1 auto; height: 8px;
  background: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='20'%20height='8'%20viewBox='0%200%2020%208'%3E%3Cpath%20d='M0%206%20L5%202%20L10%206%20L15%202%20L20%206'%20fill='none'%20stroke='%23e6e6eb'%20stroke-width='1.5'/%3E%3C/svg%3E") repeat-x left center;
}
/* A flex column: title/artist at the top, footer pinned to the bottom. The grid
   stretches every card in a row to the same height, so the footers line up.
   NB: not a subgrid — subgrid needs all cards present to size its tracks, which
   would defeat the windowing below. */
.song-card {
  text-align: left; background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 1.1rem 1.2rem; cursor: pointer; font: inherit;
  display: flex; flex-direction: column; gap: 0.4rem;
  /* Native windowing: off-screen cards are not rendered or laid out — the browser
     renders them as they scroll into view (up and down) and skips the rest, so a
     catalogue of hundreds of songs stays cheap. `auto` in contain-intrinsic-size
     remembers each card's real height once seen, keeping the scrollbar stable. */
  content-visibility: auto; contain-intrinsic-size: auto 220px;
  box-shadow: var(--shadow-sm); transition: border-color 0.12s, box-shadow 0.12s, transform 0.12s;
}
.song-card:hover { border-color: #c9c2f2; box-shadow: var(--shadow-md); transform: translateY(-1px); }
.song-footer { margin-top: auto; display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; padding-top: 0.4rem; }
.song-assets { display: flex; flex-wrap: wrap; gap: 0.35rem; color: var(--faint); }
.song-assets .asset-ic { display: inline-flex; }
.song-updated { flex: none; color: var(--faint); font-size: 0.72em; white-space: nowrap; }
.song-title, .song-artist {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; overflow-wrap: anywhere;
}
.song-title { font-weight: 700; font-size: 1.05em; }
.song-artist { font-size: 0.95em; margin-top: 0.1rem; }
.song-meta { align-self: start; }
.song-meta .since-date { font-weight: 500; color: var(--muted); }

/* --- Song detail --- */
.link-back { background: transparent; border: 0; color: var(--brand); cursor: pointer; font: inherit; font-weight: 500; padding: 0; margin-bottom: 1rem; }
.link-back:disabled { opacity: 0.5; cursor: default; }
.song-head { margin-bottom: 1.25rem; }
.song-head h2 { margin: 0 0 0.15rem; }
.song-detail .card { margin-bottom: 1.25rem; }
.song-detail h3 { margin-top: 0; }
.perf { padding: 0.7rem 0; border-top: 1px solid var(--border); }
.perf:first-of-type { border-top: 0; }
.perf-meta { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
.row-actions, .asset { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; }
.asset { padding: 0.5rem 0; flex-direction: column; align-items: stretch; }
.asset-label { font-weight: 600; margin-bottom: 0.35rem; }
.player { width: 100%; max-width: 760px; border-radius: 12px; background: #000; display: block; }
audio.player { background: var(--surface-2); }
.pdf { width: 100%; height: 72vh; border: 1px solid var(--border); border-radius: 12px; background: #fff; }

/* --- Admin wizard --- */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.9rem; margin: 0.5rem 0 1.25rem; }
.form-grid label { display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.9em; color: var(--muted); }
.form-grid .span2 { grid-column: 1 / -1; }
/* Mobile: stack Title / Original artist on their own lines for more room. */
@media (max-width: 760px) { .form-grid { grid-template-columns: 1fr; } }
.checks { display: flex; flex-wrap: wrap; gap: 0.9rem; margin-bottom: 0.5rem; }
.check { display: flex; align-items: center; gap: 0.45rem; color: var(--text); }
.actions-row { display: flex; gap: 0.6rem; margin-top: 1.25rem; flex-wrap: wrap; }
/* Responsive button labels inside the wizard action bar (short forms on mobile). */
.wz-short { display: none; }

/* Mobile: the add-song wizard's action buttons stick to the bottom as a bar (same
   chrome as the song-edit bar), buttons stretching to fill each row. */
@media (max-width: 760px) {
  .wz-full { display: none; }
  .wz-short { display: inline; }
  /* Leave room so the fixed bar doesn't cover the card's last content. */
  .wizard-step { padding-bottom: 92px; }
  .wizard-step-tall { padding-bottom: 152px; } /* step 3: two rows of buttons */
  .wizard-actions {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    margin-top: 0; flex-wrap: wrap; gap: 0.6rem;
    background: #f6f4fe; border-top: 1px solid #e7e3f8;
    box-shadow: 0 -6px 20px rgba(16, 16, 40, 0.06);
    padding: 0.8rem 1.5rem calc(1.4rem + env(safe-area-inset-bottom, 0px));
  }
  /* Buttons share the row equally (2 → 50/50, 3 → thirds). */
  .wizard-actions .btn { flex: 1 1 0; min-width: 0; margin: 0; }
  /* Step 3's four buttons: two per row, each row filling the full width. */
  .wizard-actions.two-rows .btn { flex: 1 1 calc(50% - 0.3rem); }
}

/* Add-song wizard: each performance edit card (reuses the song-edit .perf-card /
   .perf-card-head.edit styles); space them out (song-detail gets this from its own
   .card margin rule, which doesn't apply inside the wizard). */
.wizard-perf { margin-bottom: 0.75rem; }
.perf-edit-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.perf-edit-row.two { display: grid; grid-template-columns: 1fr 1fr; }
.perf-edit-row .lf-date { width: 170px; flex: none; }
.perf-edit-row .lf-type { width: 130px; flex: none; }
/* Native date input: let it shrink in its flex slot (iOS gives date inputs an
   intrinsic min-width that otherwise widens the field) and left-align the value. */
.perf-date { min-width: 0; }
.perf-date::-webkit-date-and-time-value { text-align: left; margin: 0; }
.perf-remove { margin-left: auto; }
/* The per-performance Remove sits in its own right-aligned row. On desktop it's
   pinned to the card's top-right (unchanged look); on mobile it's the first row,
   pushing the date/type row below it. */
.perf-remove-row { display: flex; justify-content: flex-end; }
@media (min-width: 761px) {
  .perf-card-head.edit { position: relative; }
  .perf-remove-row { position: absolute; top: 0.2rem; right: 0; margin: 0; }
}
@media (max-width: 760px) {
  /* Date + Type share one row; event/place each get their own. Type only needs
     room for "practice", so fix its width and let Date grow to take the rest. */
  .perf-edit-row .lf-date { width: auto; flex: 1 1 0; min-width: 0; }
  .perf-edit-row .lf-type { width: 7rem; flex: 0 0 auto; min-width: 0; }
  .perf-edit-row.two { grid-template-columns: 1fr; row-gap: 1.05rem; }
  /* Strip iOS's native date-field chrome so it matches the Type select (the OS
     picker still opens on tap). Desktop keeps its calendar-picker icon. */
  .perf-date { -webkit-appearance: none; appearance: none; }
}

/* Floating-label field: caption notches onto the top border only when filled. */
.lfield { position: relative; display: block; }
.lfield > .input, .lfield > input, .lfield > select { width: 100%; }
.lfield-cap {
  position: absolute; top: 0; left: 0.55rem; transform: translateY(-50%);
  padding: 0 0.3rem; background: var(--surface);
  font-size: 0.68rem; font-weight: 600; line-height: 1; letter-spacing: 0.01em;
  color: var(--muted); pointer-events: none; white-space: nowrap;
  opacity: 0; transition: opacity 0.12s ease;
}
.lfield.filled > .lfield-cap { opacity: 1; }
.lfield:focus-within.filled > .lfield-cap { color: var(--brand); }
.icon-btn {
  background: transparent; border: 1px solid var(--border); border-radius: 8px;
  width: 34px; height: 34px; flex: none; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted); font-size: 0.95rem; line-height: 1;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.icon-btn:hover { color: var(--danger); border-color: var(--danger-border); background: var(--danger-50); }

/* Step 2 — sectioned, per-asset rows (compact: label + actions on one line) */
.slot-groups { display: flex; flex-direction: column; gap: 1.25rem; margin: 0.75rem 0; }
.slot-group { display: flex; flex-direction: column; gap: 0.5rem; }
.slot-group-head {
  display: flex; align-items: center; gap: 0.45rem;
  font-size: 0.74em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); padding-bottom: 0.4rem; border-bottom: 1px solid var(--border);
}
.slot-group-head .slot-ic { color: var(--muted); }
.slot-group-sub { text-transform: none; letter-spacing: 0; font-weight: 500; color: var(--faint); }
.slot-list { display: flex; flex-direction: column; gap: 0.5rem; }
.slot-row { border: 1px solid var(--border); border-radius: 12px; padding: 0.6rem 0.75rem; display: flex; flex-direction: column; gap: 0.5rem; background: var(--surface); transition: opacity 0.12s; }
/* Optional, not-yet-filled slots (e.g. performance audio) read as secondary. */
.slot-row.dim { opacity: 0.5; }
.slot-row.dim:hover, .slot-row.dim:focus-within { opacity: 1; }
.slot-top { display: flex; align-items: center; gap: 0.75rem; }
.slot-head { font-size: 0.98em; min-width: 0; flex: 1; display: flex; align-items: center; gap: 0.5rem; overflow: hidden; }
.slot-head strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.slot-ic { display: inline-flex; flex: none; color: var(--brand); }
.slot-ic svg { display: block; }
.slot-actions { display: inline-flex; align-items: center; gap: 0.5rem; flex: none; flex-wrap: wrap; justify-content: flex-end; }

.btn.dbx-choose { display: inline-flex; align-items: center; gap: 0.4rem; color: var(--brand); border-color: var(--brand); background: var(--brand-50); font-weight: 600; }
.btn.dbx-choose:hover { background: var(--brand); color: #fff; }
.btn.dbx-choose:disabled { opacity: 0.5; cursor: not-allowed; color: var(--muted); border-color: var(--border); background: var(--surface); }
.ic { display: block; flex: none; }

.upload-link { display: inline-flex; align-items: center; gap: 0.3rem; color: var(--muted); text-decoration: none; font-size: 0.85em; font-weight: 500; padding: 0.3rem 0.4rem; border-radius: 8px; border: 0; background: transparent; cursor: pointer; font-family: inherit; }
.upload-link:hover { color: var(--brand); background: var(--surface-2); }

.chosen { color: var(--success); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dbx-link { color: var(--brand); text-decoration: none; border: 0; background: transparent; cursor: pointer; font-family: inherit; padding: 0; }
.dbx-link:hover { text-decoration: underline; }
.dbx-link code { color: inherit; }

.picked-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
.picked-list li { display: flex; align-items: center; gap: 0.4rem; padding-left: 0.1rem; }
.picked-list .chosen { flex: 1; font-size: 0.9em; }
.picked-list .icon-btn { width: 26px; height: 26px; }
.slot-pick-err { margin: 0.6rem 0 0.2rem; font-size: 0.83em; color: var(--danger); }

@media (max-width: 560px) {
  .slot-top { flex-wrap: wrap; }
  .slot-actions { width: 100%; }
}

/* Step 3 — confirm files, one block per section with suffix inputs */
.scan-list { display: flex; flex-direction: column; gap: 0.75rem; margin: 0.5rem 0 0.25rem; }
.scan-slot { border: 1px solid var(--border); border-radius: 12px; padding: 0.75rem 0.85rem; background: var(--surface); }
.scan-slot-head { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.5rem; }
.scan-files { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.scan-files li { display: flex; align-items: center; gap: 0.6rem; }
.scan-file-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.9em; color: var(--muted); }
.scan-suffix { width: 200px; flex: none; }
.dbx-link.small { display: inline-block; margin-top: 0.5rem; font-size: 0.82em; }

/* Wrong-format candidates: files that are in the folder but don't match the
   section's expected type. Surfaced by name so a bad drop is obvious. */
.scan-wrong { margin-top: 0.4rem; border-left: 3px solid var(--warn); background: var(--warn-50); border-radius: 0 8px 8px 0; padding: 0.5rem 0.7rem; }
.scan-wrong-msg { margin: 0; font-size: 0.85em; color: var(--warn); }
.scan-wrong-files { list-style: none; margin: 0.35rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.scan-wrong-files li { font-size: 0.85em; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- Admin: Viewers & Invitations --- */
.admin-viewers { display: flex; flex-direction: column; gap: 1.1rem; }
.viewers-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.invite-actions { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
/* Inline tier pill inside an invite button. */
.invite-actions .tag-tier { margin-left: 0.1rem; }
.viewers-head h2 { margin: 0; }
/* Sidebar tab nav (mirrors the song-detail nav; reuses .toc-* classes). */
.viewers-layout { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 1.5rem; align-items: start; }
.viewers-nav { position: sticky; top: 72px; align-self: start; }
.viewers-content { min-width: 0; position: relative; } /* contains the re-check overlay */
.toc-tally { margin-left: auto; font-size: 0.82em; font-weight: 600; color: var(--faint); }
.toc-link.active .toc-tally { color: var(--brand); }
/* In-card filter bar + pagination for the invites/viewers lists. */
.vfilters { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 1.5rem; margin-bottom: 1rem; padding-bottom: 1rem; border-bottom: 1px solid var(--border); }
.pager { display: flex; align-items: center; justify-content: center; gap: 0.9rem; margin-top: 1rem; padding-top: 0.9rem; border-top: 1px solid var(--border); }
.pager-info { min-width: 6.5rem; text-align: center; }
/* Mobile: the Users/Invitations nav collapses into the same dropdown as the
   song-detail section nav (the .toc styles below drive the toggle + list). The
   panel card also dissolves so the filters sit on the page background and only the
   list keeps a card — like the browse pages. */
@media (max-width: 760px) {
  .viewers-layout { grid-template-columns: 1fr; gap: 0.9rem; }
  .viewers-nav { position: static; }
  .vpanel { display: contents; }
  .vpanel .vfilters { margin-bottom: 0.9rem; padding-bottom: 0; border-bottom: 0; }
  .vlist-wrap {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm); padding: 0.35rem 1rem;
  }
}
.viewers-h { margin: 0 0 0.8rem; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--faint); font-weight: 700; }

/* --- System health / status page --- */
.health-head-actions { display: flex; align-items: center; gap: 0.5rem; }
.health-stack { display: flex; flex-direction: column; gap: 1.5rem; }
/* Mobile: the Connected row's email + unregistered badge overflow — drop them
   (the email also shows in the Account tile; unregistered detail is in the files
   tree below), leaving a clean "● Connected". */
@media (max-width: 760px) { .health-conn-email, .health-conn-stranded { display: none; } }
.health-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; display: inline-block; background: var(--faint); }
.health-dot.ok { background: var(--success); }
.health-dot.warn { background: var(--warn); }
.health-dot.bad { background: var(--danger); }
.health-dot.muted { background: var(--faint); }
.health-status { display: flex; align-items: center; gap: 0.55rem; margin-bottom: 1rem; flex-wrap: wrap; }
/* right-aligned header actions: the applied-summary + Revert, and/or Delete analysis */
.health-status-actions { margin-left: auto; display: inline-flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; justify-content: flex-end; }
.ts-applied-note { color: var(--muted); }
.ts-applied-note strong { color: var(--text); }
/* "Delete analysis" / "Revert" — subtle header buttons until hovered */
.ts-del-analysis { display: inline-flex; align-items: center; gap: 0.3rem; background: transparent; border: 0; padding: 0.25rem 0.5rem; border-radius: 7px; font: inherit; font-size: 0.8rem; font-weight: 500; color: var(--muted); cursor: pointer; transition: color 0.12s, background 0.12s; }
.ts-del-analysis:hover { color: var(--danger); background: var(--danger-50); }
.ts-del-analysis:disabled { opacity: 0.5; cursor: default; }
.ts-revert:hover { color: var(--brand); background: var(--brand-50); } /* revert isn't destructive → brand, not danger */
/* Mobile: keep the header on one line — dot + "Tag health" + the action button
   inline — and drop the "Analyzed …" timestamp onto its own line below the caption
   (flex order puts it after the actions; flex-basis:100% forces the wrap). */
@media (max-width: 760px) {
  .tagstudio .health-status .ts-analyzed { order: 1; flex-basis: 100%; margin: 0.1rem 0 0; }
  /* applied note (time) + Show details + Revert all on one full-width row, no wrapping */
  .tagstudio .health-status-actions { order: 2; flex-basis: 100%; margin-left: 0; flex-wrap: nowrap; justify-content: flex-start; }
}

/* "Last applied" modal (from the header "Show details") — same grid + spacing as the
   apply-bar preview panel, in a centred dialog. */
.ts-modal-overlay { position: fixed; inset: 0; z-index: 60; background: rgba(16, 16, 40, 0.32); display: flex; align-items: center; justify-content: center; padding: 1.25rem; }
.ts-modal { width: min(880px, 100%); max-height: 82vh; display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--border); border-radius: 14px; box-shadow: var(--shadow-md); overflow: hidden; }
.ts-modal-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 1rem 1.4rem; border-bottom: 1px solid var(--border); }
.ts-modal-title { display: inline-flex; align-items: baseline; gap: 0.5rem; }
.ts-modal-close { display: inline-flex; background: transparent; border: 0; padding: 0.3rem; border-radius: 7px; color: var(--muted); cursor: pointer; }
.ts-modal-close:hover { color: var(--text); background: var(--surface-2); }
.ts-modal-body { overflow: auto; padding: 0.4rem 1.4rem 1.2rem; }
.ts-modal-body .ts-apply-details-head { position: sticky; top: 0; z-index: 2; background: var(--surface); padding: 0.6rem 0 0.5rem; }
/* the impact summary (moved out of the card header) shown at the top of the modal — always
   visible here, unlike the mobile-only apply-bar copy of the same class. */
.ts-modal-body .ts-apply-summary { display: flex; flex-wrap: wrap; gap: 0.3rem 1.1rem; font-size: 0.9rem; color: var(--muted); padding: 0.6rem 0 0.7rem; }
.health-sub { margin: 1.4rem 0 0.8rem; font-size: 0.9rem; }
/* A section heading at the top of a card shouldn't add space above itself
   (keeps the Stream list cards flush like the Dropbox files card). */
.card > .health-sub:first-child { margin-top: 0; }
.health-hint { margin: 1rem 0 0; }
.health-syncmsg { margin: 0.9rem 0 0; }
/* Stat grid (label over value), reused on Dropbox + Stream tabs. */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.85rem 1.2rem; margin: 0; }
/* Mobile: the 150px min collapses the tag-health stats to one full-width column
   inside the padded card — pin them to a 2×2 grid instead. */
@media (max-width: 760px) { .tagstudio .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.7rem; } }
.stat { display: flex; flex-direction: column; gap: 0.2rem; padding: 0.7rem 0.85rem; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); }
.stat dt { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--faint); font-weight: 700; margin: 0; }
.stat dd { margin: 0; font-weight: 600; font-size: 0.95rem; }
.stat dd code { font-weight: 500; font-size: 0.85em; }
/* Base-path "found/missing" badge sits next to the "Base path" tile title (the
   long path value wraps, so the title is the stable home for it). */
.basepath-tag { text-transform: none; margin-left: 0.4rem; vertical-align: middle; display: inline-flex; align-items: center; }
/* Overview tiles. */
.health-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; }
.health-tile { display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; column-gap: 0.5rem; row-gap: 0.4rem; align-items: center; text-align: left; padding: 1rem 1.1rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); cursor: pointer; transition: border-color 0.12s, background 0.12s; }
.health-tile:hover { border-color: var(--border-strong); background: var(--bg); }
.health-tile-name { display: flex; align-items: center; gap: 0.4rem; font-weight: 600; }
.health-tile-sub { grid-column: 2; }
/* Video / orphan lists. */
.health-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
/* Missing-files rows: badge | [Song · type / filename] | Open button (centered). */
.miss-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 0; border-top: 1px solid var(--border); }
.miss-row:first-child { border-top: 0; }
.miss-badge { flex: none; }
.miss-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.miss-line1 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.miss-song { font-weight: 600; }
.miss-type { color: var(--muted); }
.miss-file { color: var(--muted); font-size: 0.86em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.miss-open { flex: none; align-self: center; }
/* Mobile: drop the per-row badge; show the filename in red (full, wrapping). */
@media (max-width: 760px) {
  .miss-badge { display: none; }
  .miss-file { color: var(--danger); white-space: normal; overflow: visible; text-overflow: clip; }
}
.health-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 0; border-top: 1px solid var(--border); }
.health-row:first-child { border-top: 0; }
.health-row > .status { flex: none; min-width: 5.5rem; text-align: center; text-transform: capitalize; }
.health-row-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.health-row-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Dropbox file tree. */
.tree-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.6rem; }
/* Kill the heading's own margins so align-items:center lines the title up with
   the badges/buttons in the same row (its margin-box otherwise rides high). */
.tree-head > .health-sub { margin: 0; }
.tree-head-actions { display: flex; align-items: center; gap: 0.7rem; }
/* Spin the reload glyph while a re-check scan is in flight, so an unchanged
   count still reads as "it ran" rather than a no-op. */
.tree-reload.is-spinning svg { animation: spin 0.7s linear infinite; }
.tree-filter { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.85em; color: var(--muted); cursor: pointer; }
/* On desktop the checkbox + delete flow inline with the rest of the actions. */
.tree-row2 { display: contents; }
/* Mobile: two rows — row 1: title | "N unregistered" | reload; row 2 spans the
   full width as a flex row: "Only unregistered" checkbox (left) and the (content-
   width, not stretched) delete button (right). */
@media (max-width: 760px) {
  .tree-head {
    display: grid; align-items: center; gap: 0.5rem;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "title status reload";
  }
  /* Only the Portfolio-files header carries a caption + a controls row; the
     Missing header is a single title row, so it must not reserve (and pad out)
     empty grid rows — that's what kept the two captions from lining up. */
  .tree-head-files {
    grid-template-areas:
      "title status reload"
      "hint  hint   hint"
      "row2  row2   row2";
  }
  .tree-head .tree-head-actions { display: contents; }
  .tree-head > .health-sub { grid-area: title; margin: 0; }
  .tree-head .tree-hint { grid-area: hint; }
  .tree-head .tree-status { grid-area: status; justify-self: start; min-width: 0; }
  .tree-head .tree-reload { grid-area: reload; width: 2.25rem; height: 2.25rem; padding: 0; justify-content: center; flex: none; }
  .tree-head .tree-row2 { grid-area: row2; display: flex; flex-direction: row-reverse; justify-content: space-between; align-items: center; gap: 0.5rem; }
  .tree-head .tree-row2 .tree-filter { flex: none; white-space: nowrap; }
  .tree-head .section-remove { height: 2.25rem; white-space: nowrap; flex: none; }
}
/* Desktop tree headers: the count badge hugs the title (left), the reload sits
   at the right. The Portfolio-files header additionally carries the caption and
   a controls row below — the "Only unregistered" checkbox and Delete button on
   their own row (checkbox first, then button). */
@media (min-width: 761px) {
  .tree-head {
    display: grid; align-items: center; column-gap: 0.7rem; row-gap: 0.6rem;
    grid-template-columns: auto 1fr auto;
    grid-template-areas: "title status reload";
  }
  .tree-head-files {
    grid-template-areas:
      "title status reload"
      "hint  hint   hint"
      "row2  row2   row2";
  }
  .tree-head .tree-head-actions { display: contents; }
  .tree-head .tree-status { grid-area: status; justify-self: start; }
  .tree-head .tree-reload { grid-area: reload; }
  .tree-head .tree-hint { grid-area: hint; flex-basis: auto; }
  .tree-head-files .tree-row2 { grid-area: row2; display: flex; align-items: center; gap: 0.6rem; }
  .tree-head-files .tree-row2 .tree-filter { order: 1; }
  .tree-head-files .tree-row2 .section-remove { order: 2; }
}
.tree { list-style: none; margin: 0; padding: 0; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.tree-children { list-style: none; margin: 0; padding: 0; }
.tree-item { }
.tree-row { display: flex; align-items: center; gap: 0.4rem; padding: 0.32rem 0.6rem 0.32rem 0; border-top: 1px solid var(--border); min-height: 34px; }
.tree > .tree-item:first-child > .tree-row { border-top: 0; }
.tree-row:hover { background: var(--bg); }
.tree-twist { width: 18px; flex: none; border: 0; background: none; cursor: pointer; color: var(--faint); font-size: 0.8em; padding: 0; }
.tree-label { display: inline-flex; align-items: center; gap: 0.45rem; min-width: 0; flex: 1 1 auto; cursor: pointer; }
.tree-row.folder .tree-label { font-weight: 600; }
.tree-row.file .tree-twist { visibility: hidden; }
.tree-ic { display: inline-flex; color: var(--faint); flex: none; }
.tree-ic.folder { color: var(--brand); }
.tree-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tree-size { flex: none; }
.tree-badge { flex: none; }
.tree-open { flex: none; border: 0; background: none; cursor: pointer; color: var(--faint); display: inline-flex; padding: 0.2rem; border-radius: 6px; }
.tree-open:hover { color: var(--brand); background: var(--brand-50); }
.tree-actions { flex: none; display: flex; gap: 0.35rem; }
/* Mobile: show a compact "!" instead of the word "unregistered", and a smaller
   "View" button — leaving more room for the file/folder names. */
@media (max-width: 760px) {
  .tree-badge-flag { background: transparent; padding: 0; color: var(--warn); }
  .tree-badge-ic { display: inline-flex; align-items: center; color: var(--warn); }
  .tree-view-btn { padding: 0.25rem 0.5rem; font-size: 0.8em; }
}
/* Stranded (unregistered) files stand out. */
.tree-row.stranded { background: var(--warn-50); }
.tree-row.stranded:hover { background: var(--warn-50); }
.tree-row.stranded .tree-name { color: var(--warn); font-weight: 600; }
/* Sits inside .tree-head as a full-width line below the title/action row. */
.tree-hint { margin: 0; flex-basis: 100%; }
.vlist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.vrow { display: flex; align-items: center; gap: 0.75rem; padding: 0.7rem 0; border-top: 1px solid var(--border); }
.vrow:first-child { border-top: 0; }
.vrow > .status, .vrow > .chip { flex: none; text-transform: capitalize; }

/* Invite rows: a 4-column grid — status | tier | date·expiry | link+actions —
   so every column lines up across rows. Pills keep their natural width. */
.inv-row {
  list-style: none; display: grid; align-items: center; gap: 0.85rem;
  grid-template-columns: 5.5rem 4.75rem 14rem minmax(0, 1fr) auto auto;
  grid-template-areas: "status tier meta input copy del";
  padding: 0.7rem 0; border-top: 1px solid var(--border);
}
.inv-row:first-child { border-top: 0; }
/* Both wrappers dissolve so every piece is placed directly by grid-area — one row
   on desktop, two rows on mobile. */
.inv-mid-top, .inv-link { display: contents; }
.inv-row > .status { grid-area: status; justify-self: start; text-transform: capitalize; }
.inv-tier { grid-area: tier; justify-self: start; text-transform: capitalize; }
.inv-meta { grid-area: meta; white-space: nowrap; }
.inv-row .share-url { grid-area: input; }
.inv-copy { grid-area: copy; }
.inv-del { grid-area: del; }
/* Expired/revoked rows: heavily tone everything down (tier, date, URL field). */
.inv-row.is-dead .inv-tier,
.inv-row.is-dead .inv-meta,
.inv-row.is-dead .share-url { opacity: 0.4; }
/* Mobile: two rows — row 1: Status | expires | delete; row 2: Type | URL | copy.
   Fits the viewport; the URL field and copy button share the row's height. */
@media (max-width: 760px) {
  .inv-row {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "status meta  del"
      "tier   input copy";
    gap: 0.4rem 0.7rem;
  }
  .inv-meta { overflow: hidden; text-overflow: ellipsis; min-width: 0; justify-self: start; }
  /* URL field, copy and delete buttons all share one height so the two rows line
     up on the right. Buttons stay square. */
  .inv-row .share-url { height: 2.25rem; box-sizing: border-box; }
  .inv-row .inv-copy, .inv-row .inv-del { width: 2.25rem; height: 2.25rem; align-self: center; }
}
.vrow-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.vrow-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* When a display name is set, the email trails it in brackets — lighter and
   normal-weight so the name still leads. */
.vrow-email { font-weight: 400; color: var(--muted); }
.vrow-actions { flex: none; display: flex; gap: 0.4rem; }
/* Mobile: mirror the invite rows — row 1: tier badge + joined date, with the
   Remove (trash) square at top-right; row 2: the email, with the promote/demote
   (arrow) square below it. Two stacked squares on the right, like invitations. */
@media (max-width: 760px) {
  .vrow {
    display: grid; align-items: center; gap: 0.3rem 0.7rem;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "tier  meta  del"
      "email email role";
  }
  .vrow-main, .vrow-actions { display: contents; }
  .vrow > .chip { grid-area: tier; justify-self: start; }
  .vrow-sub { grid-area: meta; justify-self: start; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .vrow-title { grid-area: email; justify-self: start; min-width: 0; max-width: 100%; }
  .vrow-del { grid-area: del; }
  .vrow-role { grid-area: role; }
  .vrow-del, .vrow-role { width: 2.25rem; height: 2.25rem; padding: 0; justify-content: center; align-self: center; }
  .vrow-del > span, .vrow-role > span { display: none; }
}

.status { font-size: 0.74em; padding: 0.14rem 0.55rem; border-radius: var(--pill); font-weight: 600; }
.status.ok { color: var(--success); background: var(--success-50); }
.status.warn { color: var(--warn); background: var(--warn-50); }
.status.bad { color: var(--danger); background: var(--danger-50); }
.status.muted { color: var(--muted); background: var(--surface-2); }

@media (max-width: 560px) {
  .scan-files li { flex-wrap: wrap; }
  .scan-suffix { width: 100%; }
}

.error-bar { background: var(--danger-50); border: 1px solid var(--danger-border); color: #b42318; padding: 0.55rem 0.8rem; border-radius: var(--radius); margin: 0.5rem 0; }

/* --- Share links --- */
.share-create { display: flex; gap: 0.5rem; margin-bottom: 0.75rem; }
.share-list { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.share-list li { display: flex; align-items: center; gap: 0.5rem; flex-wrap: nowrap; }
.share-asset { flex: none; display: inline-flex; align-items: center; gap: 0.45rem; font-size: 0.9em; }
.share-ic { display: inline-flex; flex: none; color: var(--brand); }
.share-ic svg { display: block; }
.share-asset-label { white-space: nowrap; }
.share-url { flex: 1 1 auto; min-width: 0; font-size: 0.8em; color: var(--muted); }
.share-list .btn { flex: none; }

/* --- Tag input --- */
.taginput { position: relative; }
.tags-field {
  display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 0.35rem 0.45rem; min-height: 2.5rem; cursor: text;
}
.tags-field:focus-within { border-color: var(--focus); box-shadow: 0 0 0 3px var(--focus-ring); }
.tag-chip {
  display: inline-flex; align-items: center; gap: 0.3rem;
  background: var(--brand-50); border: 0; color: var(--brand);
  border-radius: var(--pill); padding: 0.14rem 0.25rem 0.14rem 0.6rem; font-size: 0.82em; font-weight: 500;
}
.tag-x { background: transparent; border: 0; color: var(--brand); opacity: 0.6; cursor: pointer; font-size: 1.05em; line-height: 1; padding: 0 0.25rem; }
.tag-x:hover { opacity: 1; }
.tag-text { flex: 1; min-width: 90px; background: transparent; border: 0; outline: none; color: var(--text); font: inherit; padding: 0.25rem; }
.tag-menu {
  /* above the song-edit sticky sidebar (.song-nav is z-index 51 while editing) so the
     tag suggestions overlay it instead of tucking behind */
  position: absolute; z-index: 55; top: calc(100% + 4px); left: 0; right: 0;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  max-height: 220px; overflow: auto; padding: 0.25rem; box-shadow: var(--shadow-md);
}
.tag-option { display: block; width: 100%; text-align: left; background: transparent; border: 0; color: var(--text); padding: 0.42rem 0.55rem; border-radius: 8px; cursor: pointer; font: inherit; }
.tag-option:hover { background: var(--surface-2); }
.tag-option.add-new { color: var(--brand); font-weight: 500; }
.tag-empty { padding: 0.42rem 0.55rem; }

/* --- Tag suggestions / label row --- */
.label-row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.tag-sep { font-size: 0.68em; text-transform: uppercase; letter-spacing: 0.06em; color: var(--faint); padding: 0.35rem 0.55rem 0.2rem; }
.tag-option.proposed { color: var(--brand); }
/* tag loading row */
.loading-row { padding: 0.42rem 0.55rem; }

/* Make combobox (title/artist) inputs fill their form column */
.taginput { width: 100%; }
.taginput > .input { width: 100%; }

/* Dropdowns: custom chevron that hugs the selected text (field-sizing shrinks
   the box to its content; degrades gracefully where field-sizing is unsupported). */
select, select.input {
  appearance: none;
  -webkit-appearance: none;
  field-sizing: content;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%235e5e5e' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat;
  background-position: right 0.6rem center;
  background-size: 11px 11px;
  padding-right: 1.9rem;
}

/* --- Song detail: hero header --- */
.song-hero {
  display: flex; justify-content: space-between; align-items: stretch;
  gap: 1rem 1.5rem; flex-wrap: wrap;
  margin-bottom: 1.5rem; padding-bottom: 1.25rem; border-bottom: 1px solid var(--border);
}
/* View header: only the title shares a row with the actions; the artist/meta/tags
   span the full width below, so everything but the title uses all available space. */
.song-hero:not(.editing) {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "title actions" "rest rest";
  align-items: center; column-gap: 1.5rem; row-gap: 0;
}
.song-hero:not(.editing) .song-hero-title { grid-area: title; }
.song-hero:not(.editing) .song-hero-side { grid-area: actions; }
.song-hero-rest { grid-area: rest; min-width: 0; }
.song-hero-main { flex: 1 1 320px; min-width: 0; }
.song-hero-title { font-size: 2.5rem; font-weight: 800; letter-spacing: -0.025em; line-height: 1.05; margin: 0; overflow-wrap: anywhere; }
.song-hero-artist { font-size: 1.15rem; color: var(--muted); margin: 0.4rem 0 0; }
/* Artist name → filter the repertoire by this artist. Inline click target; the
   name underlines on hover/focus so it reads as a link. */
.artist-link { cursor: pointer; text-decoration: none; text-underline-offset: 2px; border-radius: 3px; }
.artist-link:hover { text-decoration: underline; }
.artist-link:focus-visible { text-decoration: underline; outline: 2px solid var(--brand); outline-offset: 2px; }
.song-hero-added { margin: 0.5rem 0 0; }
.song-hero-tags { margin-top: 0.7rem; }
.month-year { display: flex; gap: 0.5rem; }
.month-year .my-month { width: 8.5rem; }
.month-year .my-year { width: 6rem; }
/* Tags + "Added to repertoire" share one row: tags grow, the date picker stays compact on the right. */
.tags-added { display: flex; gap: 0.9rem; align-items: flex-start; }
.tags-added .tags-col { flex: 1 1 auto; min-width: 0; }
.tags-added .added-col { flex: 0 0 auto; }
/* "Suggest tags" sits right after the caption; both captions share a height so
   the controls below (tag input / month-year) line up. */
.tags-added .label-row { justify-content: flex-start; gap: 0.6rem; }
.tags-added .label-row, .tags-added .added-cap { min-height: 2rem; display: flex; align-items: center; }
/* Mobile: the form is single-column, so stack Tags and "Added to repertoire" and
   let the date picker fill the width (month grows, year stays compact). Each asset
   checkbox also gets its own row. */
@media (max-width: 760px) {
  .tags-added { flex-wrap: wrap; }
  .tags-added .tags-col { flex-basis: 100%; }
  .tags-added .added-col { flex-basis: 100%; }
  .month-year { width: 100%; }
  .month-year .my-month { flex: 1 1 auto; width: auto; }
  .checks .check { flex-basis: 100%; }
}
.song-hero-side { display: flex; flex-direction: column; align-items: flex-end; gap: 0.6rem; flex: 0 0 auto; }
.song-hero-actions { display: flex; align-items: flex-start; gap: 0.6rem; flex-wrap: wrap; justify-content: flex-end; }
.dbx-view { display: inline-flex; align-items: center; gap: 0.45rem; text-decoration: none; }
.icon-text-btn { display: inline-flex; align-items: center; gap: 0.4rem; }

/* Mobile: same grid — the ⋮ kebab shares the title's row (column 2), and the
   artist/meta/tags stretch full width on the row below. */
@media (max-width: 760px) {
  /* Top-align the kebab with the (often multi-line) title instead of centering. */
  .song-hero:not(.editing) { column-gap: 0.75rem; align-items: start; }
  .song-hero-actions { flex-wrap: nowrap; gap: 0.4rem; justify-content: flex-end; }
}

/* --- Inline edit mode --- */
.edit-fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
.edit-fieldset:disabled { opacity: 0.6; }
/* Sticky compact header — appears once the hero scrolls under the topbar.
   NB: no `transform` — a transformed ancestor becomes the containing block for
   position:fixed descendants, which would break the centered sharing modal /
   full-screen backdrop opened from here. Slide in via `top` instead, and sit
   above the topbar so that modal's overlay can cover the whole viewport. */
.song-sticky {
  position: fixed; top: 44px; left: 0; right: 0; z-index: 11;
  background: var(--surface); border-bottom: 1px solid var(--border); box-shadow: var(--shadow-sm);
  opacity: 0; pointer-events: none;
  transition: top 0.16s ease, opacity 0.16s ease;
}
.song-sticky.show { top: 56px; opacity: 1; pointer-events: auto; }
.song-sticky-inner { max-width: 1080px; margin: 0 auto; padding: 0.7rem 1.5rem; display: flex; align-items: center; gap: 1rem; }
.song-sticky-title { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 0.55rem; overflow: hidden; }
.song-sticky-name { font-weight: 700; font-size: 1.05rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.song-sticky-artist { color: var(--muted); font-size: 0.9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: none; max-width: 45%; }
.song-sticky-actions { display: inline-flex; align-items: center; gap: 0.4rem; flex: none; }
.sticky-act {
  width: 36px; height: 36px; flex: none; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--text);
  cursor: pointer; text-decoration: none; transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.sticky-act:hover { color: var(--brand); border-color: var(--brand); background: var(--brand-50); }
.sharing-btn.icon-only { position: relative; padding: 0; width: 36px; height: 36px; justify-content: center; }
.sharing-btn.icon-only .sharing-badge { position: absolute; top: -5px; right: -5px; font-size: 0.6rem; padding: 0 0.2rem; min-width: 1.05rem; height: 1.05rem; }
/* Mobile section icon-bar (second row of the sticky compact header): the section
   icons spread equally across the width; the active one highlights (scroll-spy). */
.section-bar { display: none; }
@media (max-width: 760px) {
  .section-bar {
    display: flex; align-items: stretch; gap: 0.2rem;
    padding: 0.3rem 0.5rem; border-top: 1px solid var(--border);
  }
  .section-bar-btn {
    flex: 1 1 0; min-width: 0; display: inline-flex; align-items: center; justify-content: center;
    height: 40px; border: 0; background: transparent; color: var(--muted); border-radius: 8px;
    cursor: pointer; transition: color 0.12s, background 0.12s;
  }
  .section-bar-btn.active { color: var(--brand); background: var(--brand-50); }
  .section-bar-btn svg { display: block; }
  /* Clear the taller mobile sticky stack: topbar (~62) + the fixed compact
     header which holds the title row (~57) AND the section icon-bar (~50) —
     bottom edge ~164px — plus a little breathing room. Scoped under
     .song-detail so it outranks the later global .sec-anchor rule (media
     queries add no specificity, so an equal-specificity global would win). */
  .song-detail .sec-anchor { scroll-margin-top: 170px; }
}

.song-hero.editing { align-items: flex-start; }
.detail-topbar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
.detail-topbar .link-back { margin-bottom: 0; }
.edit-remove-song { display: inline-flex; align-items: center; gap: 0.35rem; }
.edit-meta { flex: 1; min-width: 0; width: 100%; }
.edit-meta .form-grid { margin: 0; }
/* Mobile: Title and Original artist each stretch to their own full-width row. */
@media (max-width: 760px) {
  .edit-meta .form-grid { grid-template-columns: 1fr; }
}
.song-detail.editing { padding-bottom: 84px; }

/* Sticky bottom edit bar (Save/Cancel + change preview) */
.edit-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
  background: #f6f4fe; border-top: 1px solid #e7e3f8;
  box-shadow: 0 -6px 20px rgba(16, 16, 40, 0.06);
  padding: 0.8rem 1.5rem;
}
/* Mobile: extra bottom padding (+ the iOS safe-area inset) so the buttons clear
   the home indicator / screen edge. */
@media (max-width: 760px) {
  .edit-bar { padding-bottom: calc(1.4rem + env(safe-area-inset-bottom, 0px)); }
  /* "＋ Section" on the left, Cancel/Check on the right; any error sits on its own
     row above them. The below-sections "Add section" link is replaced by this. */
  .edit-bar-add { display: inline-flex; }
  .edit-bar-msg { flex-basis: 100%; min-width: 0; }
  .edit-bar-msg:empty { display: none; }
  .edit-bar-actions { display: flex; width: 100%; }
  .bottom-add { display: none; }
}
.edit-bar-inner { max-width: 1080px; margin: 0 auto; }
.edit-bar-actions { display: inline-flex; align-items: center; gap: 0.6rem; }
.edit-bar-actions.end { display: flex; justify-content: flex-end; }
.edit-bar-summary { display: flex; align-items: center; gap: 1rem; justify-content: space-between; flex-wrap: wrap; }
.edit-bar-msg { flex: 1; min-width: 220px; }
/* "＋ Section" lives in the bottom bar on mobile only (desktop uses the nav +
   the below-sections link). It self-hides when no sections remain to add.
   Desktop-hide is a min-width query so it can't override the mobile show by
   source order. */
.edit-bar-add { margin-right: auto; }
.edit-bar-add .nav-add { padding: 0.4rem 0.5rem; white-space: nowrap; }
@media (min-width: 761px) { .edit-bar-add { display: none; } }
.scan-warn { color: var(--warn); font-weight: 500; }
.edit-bar .edit-details { margin: 0.6rem 0 0; max-height: 40vh; }
.edit-bar code { background: var(--surface); padding: 0.05rem 0.3rem; border-radius: 4px; }
/* Roomier row spacing so each field's floating caption clears the field above. */
.perf-card-head.edit { display: flex; flex-direction: column; gap: 1.05rem; }
.edit-subsec { padding: 0.5rem 0; }
.edit-subsec-head { display: flex; align-items: center; gap: 0.45rem; font-weight: 600; margin-bottom: 0.5rem; }
.edit-subsec-head .subsec-ic { color: var(--brand); display: inline-flex; }

.staged-file { display: flex; align-items: center; gap: 0.6rem; padding: 0.3rem 0; }
.staged-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.88em; display: flex; align-items: center; gap: 0.4rem; }

/* Staged audio file — same box size/height as the waveform player */
.staged-audio {
  display: flex; align-items: center; gap: 0.7rem; width: 100%; max-width: 760px;
  min-height: 66px; margin-top: 0.5rem; padding: 0.55rem 0.9rem;
  background: var(--staged-bg); border: 1px solid var(--border); border-radius: 12px;
}
.staged-audio + .staged-audio { margin-top: 0.5rem; }
.staged-audio .new-tag { flex: none; }
.staged-audio-name { flex: 1; min-width: 0; font-size: 0.72rem; color: var(--muted); word-break: break-word; }
.staged-audio .icon-btn { flex: none; }
.add-files-btn { margin-top: 0.5rem; }
.add-files-btn:not(:disabled) { color: var(--brand); border-color: var(--brand); }

/* Waveform-sized "Add files" (matches the audio player box) */
.audio-add {
  display: flex; align-items: center; gap: 0.6rem; width: 100%; max-width: 760px;
  min-height: 66px; margin-top: 0.5rem; padding: 0.55rem 0.75rem;
  background: transparent; border: 1px dashed var(--border-strong); border-radius: 12px;
  cursor: pointer; color: var(--brand); font: inherit; font-weight: 500; font-size: 0.8rem;
}
.audio-add:hover:not(:disabled) { border-color: var(--brand); background: var(--brand-50); }
.audio-add:disabled { opacity: 0.5; cursor: not-allowed; color: var(--muted); }
.audio-add-plus { flex: none; font-size: 1.5rem; font-weight: 400; line-height: 1; }

/* Dual Add + Upload affordance (two buttons inside one add slot). */
.audio-add.dual { justify-content: center; gap: 0.75rem; cursor: default; padding: 0.55rem; }
/* Square like the media tiles (inherits aspect-ratio:1/1), top-aligned so it lines
   up with a neighbour's tile box (whose label input extends the row below it). The
   fixed-height actions (2 or 3) fit within the ≥120px square, centered. */
.add-tile.dual { flex-direction: column; gap: 0.45rem; padding: 0.5rem; cursor: default; overflow: visible; align-self: start; box-sizing: border-box; }
.add-act {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.35rem;
  padding: 0.32rem 0.7rem; border: 1px solid var(--border-strong); border-radius: 8px;
  background: var(--surface); color: var(--brand); cursor: pointer; font: inherit; font-size: 0.8rem; font-weight: 500;
}
.add-tile.dual .add-act { width: 100%; flex: none; }
.add-act:hover:not(:disabled) { border-color: var(--brand); background: var(--brand-50); }
.add-act:disabled { opacity: 0.5; cursor: not-allowed; color: var(--muted); }

/* Non-PDF "other" file tile + project archive tile */
.media-tile.file-tile { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; background: var(--surface-2); cursor: pointer; }
.file-tile-label { font-size: 0.8rem; color: var(--muted); }
.archive-ic { display: inline-flex; color: var(--muted); transition: color 0.12s; }
.file-tile:hover .archive-ic { color: var(--brand); }
.media-tile.add-tile:disabled { opacity: 0.5; cursor: not-allowed; }

/* Staged + add tiles inside a media grid */
.media-tile.staged { display: flex; align-items: center; justify-content: center; padding: 0.5rem; background: var(--staged-bg); cursor: default; }
.staged-tile-name { font-size: 0.72rem; line-height: 1.3; color: var(--muted); text-align: center; word-break: break-all; overflow-wrap: anywhere; max-height: 100%; overflow: hidden; }
.staged-badge { position: absolute; top: 5px; left: 5px; }
.staged-remove {
  position: absolute; top: 5px; right: 5px; width: 26px; height: 26px;
  border: 1px solid var(--border); border-radius: 7px; background: var(--surface);
  color: var(--muted); cursor: pointer; display: flex; align-items: center; justify-content: center;
  font-size: 0.85rem; line-height: 1; box-shadow: var(--shadow-sm);
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.staged-remove:hover { color: var(--danger); border-color: var(--danger-border); background: var(--danger-50); }
.media-tile.add-tile { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.2rem; border: 1px dashed var(--border-strong); background: transparent; color: var(--brand); cursor: pointer; }
.media-tile.add-tile:hover { border-color: var(--brand); background: var(--brand-50); }
.add-plus { font-size: 1.5rem; font-weight: 400; line-height: 1; }
.add-text { font-size: 0.8rem; font-weight: 500; }

/* Inline "Add …" pill — same dashed/thin cues as the media add buttons */
.add-pill {
  display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.4rem 0.85rem;
  border: 1px dashed var(--border-strong); border-radius: 10px; background: transparent;
  color: var(--brand); cursor: pointer; font: inherit;
}
.add-pill:hover { border-color: var(--brand); background: var(--brand-50); }
.add-pill .add-plus { font-size: 1.2rem; }

/* --- Soft-remove (edit mode): remove controls + ghosts --- */
/* ✕ overlay on a grid tile (existing file in edit mode) */
.tile-wrap { position: relative; display: flex; flex-direction: column; gap: 0.35rem; }
.tile-wrap > .media-tile { width: 100%; }
/* Optional per-file label input shown under each item in edit mode. */
.label-input { width: 100%; font-size: 0.8rem; padding: 0.32rem 0.5rem; }
.gallery-cell { display: flex; flex-direction: column; gap: 0.35rem; }
.gallery-cell > .media-tile { width: 100%; }
.audio-edit-item { display: flex; flex-direction: column; gap: 0.35rem; }
/* Existing-audio label lines up with the (width-capped) waveform below it. */
.audio-edit-item:has(.audio-edit-row) > .label-input { width: 100%; max-width: 760px; }
.tile-remove {
  position: absolute; top: 5px; right: 5px; width: 26px; height: 26px; z-index: 2;
  border: 1px solid var(--border); border-radius: 7px; background: var(--surface);
  color: var(--muted); cursor: pointer; display: flex; align-items: center; justify-content: center;
  font-size: 0.85rem; line-height: 1; box-shadow: var(--shadow-sm);
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.tile-remove:hover { color: var(--danger); border-color: var(--danger-border); background: var(--danger-50); }
/* Per-file overlay actions (download + share) pinned top-right of a tile / audio
   player, mirroring the delete overlay: neutral, revealed on hover, brand on
   hover. The share button stays visible while its popover is open. */
.tile-actions { position: absolute; top: 5px; right: 5px; z-index: 3; display: inline-flex; align-items: flex-start; gap: 0.3rem; }
.tile-act {
  width: 26px; height: 26px; padding: 0; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 7px; background: var(--surface); color: var(--muted);
  cursor: pointer; box-shadow: var(--shadow-sm); opacity: 0; font: inherit; flex: none;
  transition: color 0.12s, border-color 0.12s, background 0.12s, opacity 0.12s;
}
.tile-wrap:hover .tile-act, .audio-wrap:hover .tile-act, .share-pop-wrap.open .tile-act { opacity: 1; }
.tile-act:hover { color: var(--brand); border-color: var(--brand); background: var(--brand-50); }
.tile-act svg { display: block; }
/* The download overlay is a pill (file size + icon); share stays a square icon. */
.tile-act.tile-act-dl { width: auto; min-width: 26px; padding: 0 0.4rem; gap: 0.25rem; font-size: 0.72rem; font-weight: 600; }
.dl-ov-size { line-height: 1; white-space: nowrap; }
/* Audio player wrapper: anchors the overlay top-right (over placeholder or waveform). */
.audio-wrap { position: relative; }
/* Mobile swaps the hover overlay for an always-visible kebab (⋮) menu button. */
.file-menu-wrap { display: none; }
@media (max-width: 760px) {
  .tile-actions { display: none; }
  .file-menu-wrap { display: inline-flex; position: absolute; top: 5px; right: 5px; z-index: 3; }
}
.tile-act.file-menu-btn { opacity: 1; } /* always visible — no hover on touch */
.file-menu {
  z-index: 40; width: max-content; min-width: 160px; max-width: 92vw;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: var(--shadow-md); padding: 0.3rem;
}
.file-menu-item {
  display: flex; align-items: center; gap: 0.55rem; width: 100%; text-align: left;
  padding: 0.55rem 0.6rem; border: 0; background: transparent; border-radius: 8px;
  cursor: pointer; font: inherit; font-size: 0.95em; color: var(--text);
}
.file-menu-item:hover { background: var(--surface-2); }
.file-menu-item:disabled { opacity: 0.5; cursor: default; }
.file-menu-item svg { flex: none; color: var(--muted); }
.file-menu-msg { padding: 0.5rem 0.6rem; }
.file-menu .share-pop-row { padding: 0.4rem 0.45rem; }
.file-menu .share-pop-note { padding: 0 0.5rem 0.45rem; }
/* Share buttons with a short label swap to it on mobile (e.g. "Share folder" → "Folder"). */
.dl-short { display: none; }
@media (max-width: 760px) {
  .dl-text:has(.dl-short) .dl-full { display: none; }
  .dl-short { display: inline; }
}
.asset-remove:hover { color: var(--danger); border-color: var(--danger-border); background: var(--danger-50); }

/* Edit mode: audio file = waveform with the remove button inline to its right
   (no track-name/number row above it). */
/* Edit mode: the Remove button overlays the player top-right (like media tiles),
   rather than sitting beside it. Cap the wrapper to the player's width so the
   overlay anchors to the audio, and reserve right padding so it clears the time. */
.audio-edit-row { position: relative; max-width: 760px; }
.audio-edit-row > .wave, .audio-edit-row > .wave-unloaded { padding-right: 2.6rem; }
.section-remove { display: inline-flex; align-items: center; gap: 0.35rem; }
.section-remove:hover, .perf-remove:hover { color: var(--danger); border-color: var(--danger-border); background: var(--danger-50); }

/* Desktop: hovering a delete control previews exactly what it removes with a red
   outline — whole section (Remove) → the section group; a performance's trash →
   that performance card; an item's trash → just that tile / audio player. Scoped
   by button class so a nested item's hover never lights up its parent. */
@media (min-width: 761px) {
  /* Section Remove rings the section's content card(s) only — never the title row. */
  .song-group:has(> .sections-head .section-remove:hover) > .card,
  .perf-card:has(.perf-remove:hover),
  .audio-edit-row:has(> .tile-remove:hover),
  .tile-wrap:has(> .tile-remove:hover) > .media-tile,
  .media-tile:has(.tile-remove:hover) {
    box-shadow: 0 0 0 5px var(--danger-50);
  }
  /* The audio row carries no radius of its own — round the ring to the waveform. */
  .audio-edit-row:has(> .tile-remove:hover) { border-radius: 12px; }
  /* …and strike the section title through in the same faint red as the ring.
     The tier/NEW badges become inline-block so the strike can't paint across them
     (text-decoration:none on a child can't cancel an ancestor's line). */
  .song-group:has(> .sections-head .section-remove:hover) .sections-h { color: var(--danger-border); text-decoration: line-through; }
  .song-group:has(> .sections-head .section-remove:hover) .sections-h .tag-tier,
  .song-group:has(> .sections-head .section-remove:hover) .sections-h .new-tag { display: inline-block; text-decoration: none; }
}

/* Ghost tile: a removed grid/gallery file, IN PLACE (same square as the live
   tile) — muted/dashed, struck-through name, "removed" badge + Restore (↶). */
.media-tile.ghost-tile {
  display: flex; align-items: center; justify-content: center; padding: 0.5rem;
  background: var(--surface-2); border-style: dashed; cursor: default;
}
.ghost-tile-name { font-size: 0.72rem; line-height: 1.3; color: var(--muted); text-align: center; text-decoration: line-through; word-break: break-all; overflow-wrap: anywhere; max-height: 100%; overflow: hidden; }
.ghost-tile-restore:hover { color: var(--brand); border-color: var(--brand); background: var(--brand-50); }

/* Ghost audio box: a removed audio file, IN PLACE — same height/shape as the
   waveform player, with struck filename + REMOVED badge + Restore inline. */
.wave-ghost {
  display: flex; align-items: center; gap: 0.6rem; min-height: 66px;
  padding: 0 0.85rem; border: 1px dashed var(--border-strong); border-radius: 12px;
  background: var(--surface-2);
}
.wave-ghost-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.ghost-strike { text-decoration: line-through; color: var(--muted); font-size: 0.9em; }
.ghost-tag { font-size: 0.68em; color: var(--danger); background: var(--danger-50); padding: 0.1rem 0.45rem; border-radius: var(--pill); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; flex: none; }
.ghost-restore { display: inline-flex; align-items: center; gap: 0.3rem; flex: none; }

/* Ghosted whole section: title stays in place (lighter), Restore where Remove
   was, files shown as ghost rows in a muted/dashed card. */
.song-group.ghosted .sections-h { color: var(--faint); text-decoration: line-through; }
.song-group.ghosted .card { background: var(--surface-2); border-style: dashed; }

/* Ghosted nav entry (section or performance being removed) */
.toc-link.ghost { opacity: 0.7; }
.toc-link.ghost .toc-label, .toc-sublink.ghost .toc-sublabel, .toc-sublink.off .toc-sublabel { text-decoration: line-through; color: var(--muted); }
.toc-sublink.ghost, .toc-sublink.off { opacity: 0.7; } /* .off = a tag-studio section turned off via its switch (struck like a deleted song-edit section) */

/* Ghost performance: whole-block removal awaiting Apply */
.removed-section { display: flex; align-items: center; gap: 0.6rem; padding: 0.7rem 0.9rem; border: 1px dashed var(--border-strong); border-radius: 12px; background: var(--surface-2); }
.removed-section .ghost-ic { color: var(--muted); display: inline-flex; flex: none; }
.removed-section-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* "Moved to Trash" rows in the change-details panel */
.removed-path { text-decoration: line-through; color: var(--muted); }
.removed-scope { font-size: 0.72em; color: var(--muted); margin-left: 0.45rem; text-transform: uppercase; letter-spacing: 0.03em; }

/* Nav + bottom add affordances */
.nav-edit-adds { display: flex; flex-direction: column; gap: 0.3rem; margin-top: 0.6rem; padding-top: 0.5rem; border-top: 1px solid var(--border); }
.nav-add { display: inline-flex; align-items: center; background: transparent; border: 0; color: var(--brand); font: inherit; font-weight: 600; cursor: pointer; padding: 0.4rem 0.65rem; border-radius: 8px; text-align: left; }
.nav-add:hover { background: var(--brand-50); }
.add-section-wrap { position: relative; }
.add-section-menu { position: absolute; left: 0; top: calc(100% + 4px); z-index: 60; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow-md); padding: 0.3rem; min-width: 200px; }
.add-section-menu.up { top: auto; bottom: calc(100% + 4px); }
.add-section-item { display: flex; align-items: center; gap: 0.5rem; width: 100%; background: transparent; border: 0; padding: 0.5rem 0.6rem; border-radius: 7px; cursor: pointer; font: inherit; color: var(--text); text-align: left; }
.add-section-item:hover { background: var(--surface-2); }
.bottom-add { margin-top: 1.25rem; }

.edit-group { padding: 0.5rem 0 0.2rem; }
.edit-group-head { display: flex; align-items: center; gap: 0.45rem; font-weight: 600; margin: 0.3rem 0; }
.edit-ic { display: inline-flex; color: var(--brand); }
.edit-file { display: flex; align-items: center; gap: 0.6rem; padding: 0.25rem 0; }
.edit-file-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.88em; color: var(--muted); display: flex; align-items: center; gap: 0.4rem; }
.edit-file-label { width: 200px; flex: none; }
.edit-file .icon-btn { width: 30px; height: 30px; flex: none; }
.edit-file-spacer { width: 30px; flex: none; }
.new-tag { flex: none; font-size: 0.62em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--success); background: var(--success-50); padding: 0.08rem 0.35rem; border-radius: var(--pill); }
/* Wrong-format direct upload — shown in its section but never registered. */
.new-tag.wrong-tag { color: var(--warn); background: var(--warn-50); }
.media-tile.staged.wrong, .staged-audio.wrong { background: var(--warn-50); border-color: var(--warn); border-style: dashed; }
.add-files-btn { margin-top: 0.4rem; }
.add-files-btn:not(:disabled) { color: var(--brand); border-color: var(--brand); }
.edit-confirm { margin-top: 1.25rem; background: var(--warn-50); border: 1px solid #f0d8b8; border-radius: var(--radius); padding: 0.85rem 1rem; }
.edit-confirm code { background: var(--surface); padding: 0.05rem 0.3rem; border-radius: 4px; }
.link-inline { background: transparent; border: 0; padding: 0; color: var(--brand); font: inherit; font-weight: 600; cursor: pointer; text-decoration: underline; }
.edit-details { margin: 0.5rem 0 0.75rem; max-height: 320px; overflow: auto; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 0.6rem 0.75rem; }
.edit-details-group + .edit-details-group { margin-top: 0.6rem; }
.edit-details-label { font-size: 0.72em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin-bottom: 0.25rem; }
.edit-diff { display: flex; align-items: baseline; gap: 0.4rem; flex-wrap: wrap; font-size: 0.82em; padding: 0.12rem 0; }
/* Nested trash rows (performances under a "Performances" parent). */
.edit-diff.nested { padding-left: 1.1rem; border-left: 2px solid var(--border); margin-left: 0.3rem; }
.edit-diff .old { color: var(--muted); text-decoration: line-through; word-break: break-all; }
.edit-diff .arrow { color: var(--faint); flex: none; }
.edit-diff .new { color: var(--text); font-weight: 500; word-break: break-all; }
.edit-diff .wrong-open { margin-top: 0; flex: none; }
@media (max-width: 560px) { .edit-file { flex-wrap: wrap; } .edit-file-label { width: 100%; } }

/* --- Sharing settings button + popover --- */
.sharing-wrap { position: relative; display: inline-flex; }
.sharing-btn { display: inline-flex; align-items: center; gap: 0.45rem; }
.sharing-badge {
  display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box;
  font-size: 0.64rem; font-weight: 700; line-height: 1; color: #fff; background: var(--brand);
  border-radius: var(--pill); padding: 0 0.22rem; min-width: 1.05rem; height: 1.05rem;
}

/* --- Responsive kebab cluster (desktop: unchanged inline row · mobile: ⋮ dropdown)
   On desktop the wrappers are display:contents so the children lay out exactly as
   before (no extra box, no spacing change); only mobile collapses them. --- */
.kebab-wrap, .kebab-items { display: contents; }
.kebab-btn { display: none; }
@media (max-width: 760px) {
  .kebab-wrap { display: inline-flex; position: relative; }
  .kebab-btn {
    display: inline-flex; align-items: center; justify-content: center; position: relative;
    width: 36px; height: 36px; padding: 0; flex: none; cursor: pointer;
    border: 1px solid var(--border); border-radius: 9px; background: var(--surface); color: var(--muted);
  }
  .kebab-btn:hover { color: var(--text); }
  /* Labeled trigger (e.g. "✉ Invite"): auto width with a caption, styled like a btn. */
  .kebab-btn.kebab-btn-labeled { width: auto; height: auto; padding: 0.5rem 0.85rem; gap: 0.4rem; color: var(--text); font: inherit; font-weight: 500; }
  .kebab-btn-label { font-size: 0.95em; }
  .kebab-badge {
    position: absolute; top: -5px; right: -5px; background: var(--brand); color: #fff;
    display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box;
    font-size: 0.6rem; font-weight: 700; line-height: 1; min-width: 1.05rem; height: 1.05rem; padding: 0 0.2rem; border-radius: var(--pill);
  }
  /* Collapsed by default; the measured fixed dropdown shows when open. Sized to
     content (no wasted right-hand space), capped to the viewport. */
  .kebab-items { display: none; }
  .kebab-items.open {
    display: flex; flex-direction: column; align-items: stretch; gap: 0.1rem; z-index: 30;
    width: max-content; min-width: 180px; max-width: calc(100vw - 16px); padding: 0.3rem;
    background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow-md);
  }
  /* The trigger buttons become full-width left-aligned menu rows (deep modal
     internals are left untouched — target only direct row triggers). */
  .kebab-items.open > .btn,
  .kebab-items.open > a.btn,
  .kebab-items.open > .dl-btn,
  .kebab-items.open > .sharing-wrap > .sharing-btn,
  .kebab-items.open > .share-pop-wrap > .share-btn {
    width: 100%; height: auto; justify-content: flex-start; gap: 0.55rem;
    border: 0; background: transparent; box-shadow: none; border-radius: 8px;
    /* Match the per-file menu rows (.file-menu-item): same size + padding. */
    padding: 0.55rem 0.6rem; font-size: 0.95em; font-weight: 500; color: var(--text);
  }
  .kebab-items.open > .btn:hover,
  .kebab-items.open > a.btn:hover,
  .kebab-items.open > .dl-btn:hover,
  .kebab-items.open > .sharing-wrap > .sharing-btn:hover,
  .kebab-items.open > .share-pop-wrap > .share-btn:hover { background: var(--surface-2); }
  .kebab-items.open > .sharing-wrap, .kebab-items.open > .share-pop-wrap { width: 100%; display: block; }
  /* Muted row icons, like the per-file menu. */
  .kebab-items.open > .btn > svg,
  .kebab-items.open > a.btn > svg,
  .kebab-items.open > .dl-btn > svg,
  .kebab-items.open > .sharing-wrap > .sharing-btn > svg,
  .kebab-items.open > .share-pop-wrap > .share-btn > svg { color: var(--muted); }
  /* Icon first, then caption: pull each row's trailing SVG to the front. */
  .kebab-items.open > .dl-btn > svg,
  .kebab-items.open > .share-pop-wrap > .share-btn > svg { order: -1; }
  /* Reveal the full captions (they collapse to icon/short elsewhere on mobile). */
  .kebab-items.open .dl-full, .kebab-items.open .dl-label { display: inline; }
  .kebab-items.open .dl-short { display: none; }
  .kebab-items.open .sharing-btn > span:not(.sharing-badge) { display: inline; }
  /* Show a file-size in brackets, e.g. "Download all (501 MB)". */
  .kebab-items.open .dl-size::before { content: "("; }
  .kebab-items.open .dl-size::after { content: ")"; }
  /* Badge sits at the row's end instead of floating over the icon. */
  .kebab-items.open .sharing-badge { position: static; margin-left: auto; }
}
.sharing-pop {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 40; width: 380px; max-width: 90vw;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: var(--shadow-md); padding: 0.85rem 0.9rem; text-align: left;
}
/* Mobile: centered modal with a dimmed full-screen backdrop and a close button. */
.sharing-backdrop { position: fixed; inset: 0; z-index: 39; background: rgba(20, 20, 40, 0.32); }
.sharing-pop.mobile {
  position: fixed; top: 50%; left: 50%; right: auto; transform: translate(-50%, -50%);
  width: min(420px, calc(100vw - 1.5rem)); max-width: none; max-height: 80vh; overflow-y: auto;
}
.sharing-pop-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.2rem; }
.sharing-pop-title { font-weight: 700; }
.sharing-close {
  display: none; flex: none; width: 30px; height: 30px; align-items: center; justify-content: center;
  border: 0; background: transparent; color: var(--muted); border-radius: 8px; cursor: pointer; margin: -0.25rem -0.3rem 0 0;
}
.sharing-close:hover { background: var(--surface-2); color: var(--text); }
.sharing-pop.mobile .sharing-close { display: inline-flex; }
.sharing-pop-note { margin: 0 0 0.6rem; }
.sharing-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.sharing-list li { display: flex; align-items: center; gap: 0.5rem; }
/* Tree: a slot folder (row) with the shared files nested + indented beneath it.
   NB: `li.sharing-group` out-specifies `.sharing-list li` so the rows stretch to
   full width (left-aligned, buttons pushed right) instead of centering. */
.sharing-list li.sharing-group { flex-direction: column; align-items: stretch; gap: 0.25rem; }
.sharing-row { display: flex; align-items: center; gap: 0.5rem; width: 100%; }
/* A folder that isn't shared publicly (only its files are) is a muted, non-clickable header. */
.sharing-folder.inactive .sharing-name { color: var(--faint); font-weight: 600; cursor: default; }
.sharing-folder.inactive .sharing-name:hover { color: var(--faint); text-decoration: none; }
/* File rows hang off a thin vertical stem with a rounded "L" elbow into each one.
   The stem sits under the folder icon's centre (≈0.6rem in from the row start). */
.sharing-files { list-style: none; margin: 0.1rem 0 0.2rem 0.3rem; padding: 0 0 0 0.85rem; display: flex; flex-direction: column; gap: 0.3rem; }
.sharing-files .sharing-file { font-size: 0.95em; position: relative; }
/* Rounded elbow: vertical from the row top to its centre, then a curve to the row. */
.sharing-files .sharing-file::before {
  content: ""; position: absolute; left: -0.55rem; top: 0; width: 0.55rem; height: 50%;
  border-left: 1px solid var(--border-strong); border-bottom: 1px solid var(--border-strong);
  border-bottom-left-radius: 7px;
}
/* Continuous stem: non-last rows carry the vertical line through to the next elbow. */
.sharing-files .sharing-file:not(:last-child)::after {
  content: ""; position: absolute; left: -0.55rem; top: 0; bottom: 0; border-left: 1px solid var(--border-strong);
}
/* The first row's stem reaches up toward the parent folder so it reads as connected. */
.sharing-files .sharing-file:first-child::before { top: -0.55rem; height: calc(50% + 0.55rem); }
.sharing-files .sharing-file:first-child:not(:last-child)::after { top: -0.55rem; }
.sharing-list .share-ic { display: inline-flex; flex: none; color: var(--brand); }
/* Section icon nested in an outline folder. */
.folder-ic { position: relative; display: inline-flex; flex: none; color: var(--brand); }
.folder-ic-inner { position: absolute; left: 50%; top: 58%; transform: translate(-50%, -50%); display: inline-flex; }
.folder-ic-inner svg, .folder-ic > svg { display: block; }
/* Outline file icon with the extension pinned to its bottom-right. */
.file-ic { position: relative; display: inline-flex; flex: none; color: var(--brand); margin-right: 0.15rem; }
.file-ic > svg { display: block; }
.file-ic-ext {
  position: absolute; right: -5px; bottom: -4px; font-size: 0.46rem; font-weight: 700; line-height: 1;
  padding: 0.09rem 0.17rem; border-radius: 3px; background: var(--brand); color: #fff;
  text-transform: uppercase; letter-spacing: 0.02em;
}
.sharing-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text); text-decoration: none; font-size: 0.9em;
}
.sharing-name:hover { color: var(--brand); text-decoration: underline; }
.sharing-act {
  width: 30px; height: 30px; flex: none; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); color: var(--muted); cursor: pointer; display: inline-flex;
  align-items: center; justify-content: center; transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.sharing-act:hover { color: var(--brand); border-color: var(--brand); background: var(--brand-50); }
.sharing-act.revoke:hover { color: var(--danger); border-color: var(--danger-border); background: var(--danger-50); }
.sharing-act:disabled { opacity: 0.5; cursor: default; }

/* --- Compact video --- */
/* Container hugs the video's aspect ratio (no letterbox bars); height capped. */
.video-block { display: flex; width: fit-content; max-width: 100%; background: #000; border-radius: 12px; overflow: hidden; }
video.player { display: block; max-width: 100%; max-height: 480px; }

/* --- Waveform audio --- */
.wave {
  display: flex; align-items: center; gap: 0.75rem;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px;
  padding: 0.55rem 0.75rem; max-width: 760px;
  /* Fixed height across all states (unloaded / loading / loaded = 54px body +
     padding + border) so clicking play doesn't shift the layout. */
  min-height: 74px; box-sizing: border-box;
}
.wave-body { position: relative; flex: 1; min-width: 0; height: 54px; display: flex; align-items: center; }
.wave-canvas { width: 100%; }
.wave-hint { position: absolute; inset: 0; display: flex; align-items: center; pointer-events: none; }
.wave-time { flex: none; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Unloaded state — the whole box is the play button. */
.wave-unloaded { width: 100%; cursor: pointer; font: inherit; text-align: left; transition: background 0.12s, border-color 0.12s; }
.wave-unloaded:hover { background: #e9e7f5; border-color: var(--border-strong); }
.wave-unloaded .wave-play { transition: transform 0.12s, box-shadow 0.12s; }
.wave-unloaded:hover .wave-play { transform: scale(1.06); box-shadow: 0 4px 12px rgba(109, 87, 221, 0.35); }
.wave-hint-static { min-width: 0; display: inline-flex; align-items: center; gap: 0.4rem; }
.wave-ext { display: inline-block; font-size: 0.72em; font-weight: 700; letter-spacing: 0.03em; padding: 0.1rem 0.4rem; border-radius: 5px; background: var(--surface); border: 1px solid var(--border-strong); color: var(--muted); }
.wave-play {
  flex: none; width: 40px; height: 40px; border-radius: 50%; border: 0;
  background: var(--brand); color: #fff; cursor: pointer; font-size: 0.8rem;
  display: inline-flex; align-items: center; justify-content: center; letter-spacing: -1px;
}
.wave-play:disabled { opacity: 0.85; cursor: default; }
.wave-spinner { width: 18px; height: 18px; border: 2.5px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.7s linear infinite; display: inline-block; }

/* --- PDF tile (sized by its media-grid, like photo/video tiles) --- */
/* Format · action sits at the TOP of a preview tile; the file's label (if any)
   gets the bottom dark strip (.tile-cap), like a video caption. */
.tile-badge {
  position: absolute; left: 0; right: 0; top: 0; padding: 0.2rem 0.5rem;
  background: rgba(20, 20, 30, 0.62); color: #fff; font-size: 0.7rem; line-height: 1.5; text-align: center;
}

/* --- Top-level sections (Performances + each song-level asset) --- */
.song-group { margin-bottom: 1.85rem; }
.song-group:last-child { margin-bottom: 0; }
.song-group .card:last-child { margin-bottom: 0; }
.sections-head { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 0.85rem; }
.sections-h { margin: 0; font-size: 1.4rem; font-weight: 700; letter-spacing: -0.01em; }
.sections-h .tag-tier { font-size: 0.58rem; padding: 0.1rem 0.4rem; vertical-align: middle; margin-left: 0.15rem; }
.sections-h .section-new { font-size: 0.55rem; padding: 0.1rem 0.4rem; vertical-align: middle; margin-left: 0.15rem; }

/* Mobile: keep the section Remove/Restore on the title line, right-aligned;
   an "Add performance" pill drops to its own full-width line below. */
@media (max-width: 760px) {
  .sections-head { row-gap: 0.55rem; }
  .sections-h { flex: 1 1 auto; min-width: 0; }
  .sections-head .section-remove,
  .sections-head .ghost-restore { order: 2; flex: none; }
  .sections-head .add-pill { order: 3; flex: 1 1 100%; justify-content: center; }
}

/* --- Performances: cards + collapsible sub-sections --- */
.perf-card { padding: 1rem 1.1rem; }
.perf-card-head { margin-bottom: 0.7rem; }
.perf-title { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.perf-event { font-weight: 700; font-size: 1.05rem; letter-spacing: -0.01em; }
.perf-dot { color: var(--faint); }
.perf-place { color: var(--muted); font-weight: 500; }
/* The prominent separator before the date. */
.perf-sep { width: 1px; height: 1.05em; background: var(--border-strong); margin: 0 0.15rem; }
.perf-date { font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
.perf-notes { margin: 0.4rem 0 0; }

/* Performance type chip — grey for practice, green for live. */
.chip.ptype { font-weight: 600; text-transform: capitalize; }
.chip.ptype.practice { color: var(--muted); background: var(--surface-2); }
.chip.ptype.live { color: var(--success); background: var(--success-50); }

.subsec-list { display: flex; flex-direction: column; gap: 0.5rem; }
.subsec { border: 1px solid var(--border); border-radius: 10px; background: var(--surface); overflow: hidden; }
.subsec-head { display: flex; align-items: center; gap: 0.5rem; padding: 0.1rem 0.5rem 0.1rem 0; }
.subsec-toggle {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 0.55rem;
  background: transparent; border: 0; cursor: pointer; font: inherit; font-weight: 600;
  color: var(--text); text-align: left; padding: 0.6rem 0.7rem;
}
.subsec-toggle:hover { color: var(--brand); }
.subsec-chev { display: inline-flex; flex: none; color: var(--faint); transition: transform 0.15s; }
.subsec.open .subsec-chev { transform: rotate(90deg); color: var(--brand); }
.subsec-ic { display: inline-flex; flex: none; color: var(--muted); }
.subsec.open .subsec-ic { color: var(--brand); }
.subsec-count {
  font-size: 0.72rem; font-weight: 700; color: var(--muted); background: var(--surface-2);
  border-radius: var(--pill); padding: 0.05rem 0.45rem; min-width: 1.3rem; text-align: center;
}
.subsec-actions { flex: none; display: inline-flex; align-items: center; gap: 0.5rem; }
.subsec-body { padding: 0 0.7rem 0.8rem; border-top: 1px solid var(--border); padding-top: 0.75rem; }
.audio-list { display: flex; flex-direction: column; gap: 0.75rem; }
/* Edit mode: more separation between audio files so each label reads as its own. */
.audio-list:has(.audio-edit-item) { gap: 1.4rem; }

/* --- Media gallery (photos + videos): square tiles + lightbox --- */
.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 0.5rem; max-width: 760px; }
.media-tile {
  position: relative; padding: 0; border: 1px solid var(--border); border-radius: 10px;
  overflow: hidden; cursor: pointer; aspect-ratio: 1 / 1; background: var(--surface-2);
  display: block; transition: box-shadow 0.12s, border-color 0.12s;
}
.media-tile:hover { border-color: var(--brand); box-shadow: var(--shadow-md); }
.media-tile img { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; opacity: 0; transition: opacity 0.2s; }
.media-tile.is-loaded img { opacity: 1; }
.media-tile.video { background: #000; }
/* Placeholder shimmer until a tile's thumbnail loads. */
.tile-ph { position: absolute; inset: 0; background: linear-gradient(100deg, var(--surface-2) 30%, var(--surface) 50%, var(--surface-2) 70%); background-size: 200% 100%; animation: tile-shimmer 1.2s linear infinite; }
@keyframes tile-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }

/* Spinner (lightbox loading). */
.spinner { width: 30px; height: 30px; border: 3px solid rgba(255, 255, 255, 0.25); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; display: inline-block; }
@keyframes spin { to { transform: rotate(360deg); } }
/* Small inline spinner for upload buttons (light backgrounds) — sized like a 13px icon. */
.btn-spinner { width: 13px; height: 13px; border: 2px solid var(--border); border-top-color: var(--brand); border-radius: 50%; animation: spin 0.7s linear infinite; display: inline-block; flex: none; }
/* Light variant for dark/primary buttons. */
.btn-spinner.light { border-color: rgba(255, 255, 255, 0.45); border-top-color: #fff; }

/* --- Score/Lyrics lyrics (LRCLIB) add-action + the lyrics picker modal --- */
/* Override .media-tile img (opacity:0 fade-in + cover) — this icon is UI, not a
   thumbnail, so keep it fully visible and un-cropped. */
/* Scoped to both lyrics buttons (editor .lyrics-act, wizard .lyrics-link) so the
   two-class specificity beats `.media-tile img` (which the editor's add-tile icon
   sits inside) — a bare `.lrclib-ic` loses to it and the icon renders 100%/opacity:0. */
.lyrics-act .lrclib-ic, .lyrics-link .lrclib-ic { width: 15px; height: 15px; border-radius: 4px; flex: none; opacity: 1; object-fit: contain; }

.lyr-backdrop {
  position: fixed; inset: 0; z-index: 70; display: flex; align-items: center; justify-content: center;
  background: rgba(16, 16, 40, 0.5); padding: 1.5rem;
}
.lyr-modal {
  background: var(--surface); border-radius: var(--radius); box-shadow: var(--shadow-md);
  width: min(640px, 100%); max-height: 86vh; display: flex; flex-direction: column; overflow: hidden;
}
.lyr-head { display: flex; align-items: flex-start; gap: 1rem; padding: 1rem 1.25rem; border-bottom: 1px solid var(--border); }
.lyr-head-main { min-width: 0; flex: 1; }
.lyr-title { font-weight: 700; font-size: 1.05rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lyr-sub { margin-top: 0.15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lyr-src { color: var(--faint); }
.lyr-x {
  flex: none; width: 2rem; height: 2rem; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--muted); cursor: pointer;
}
.lyr-x:hover:not(:disabled) { background: var(--bg); color: var(--text); }
.lyr-x:disabled { opacity: 0.5; cursor: default; }
/* overscroll-behavior: contain keeps the scroll inside the modal body so it never
   chains through to the page behind (esp. on touch). */
.lyr-body { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 1.1rem 1.25rem; -webkit-overflow-scrolling: touch; }
.lyr-text { margin: 0; font-family: inherit; font-size: 0.95rem; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text); }
.lyr-bar {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 0.6rem;
  padding: 0.85rem 1.25rem; border-top: 1px solid var(--border); background: var(--surface);
}
/* Fixed columns so hiding Cancel on mobile can't shift the centered group. */
.lyr-cancel { grid-column: 1; justify-self: start; }
.lyr-nav-group { grid-column: 2; display: inline-flex; align-items: stretch; gap: 0.5rem; }
/* Same height as Generate (both plain .btn) — just narrower, arrow centered. */
.lyr-nav { min-width: 2.6rem; padding-left: 0.35rem; padding-right: 0.35rem; justify-content: center; font-size: 1.25rem; line-height: 1; }
.lyr-generate { min-width: 8rem; display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem; }
/* Count sits in the last grid cell: hugging the arrows on desktop (left of its
   cell), pinned right on mobile. ‹ Generate › stays centered either way. */
.lyr-count { grid-column: 3; justify-self: start; align-self: center; color: var(--muted); font-size: 0.9rem; font-variant-numeric: tabular-nums; }
@media (max-width: 760px) {
  .lyr-backdrop { padding: 0; }
  .lyr-modal { width: 100%; height: 100%; max-height: none; border-radius: 0; }
  .lyr-bar { padding-bottom: calc(0.85rem + env(safe-area-inset-bottom, 0px)); }
  .lyr-cancel { display: none; } /* the X at the top is enough */
  .lyr-count { justify-self: end; }
}
/* Busy state: show only a centered spinner while keeping the button's width — the
   label stays as an invisible ghost that reserves the same space. The spinner is
   centered by a wrapper that fills the button, so its spin animation (a transform)
   never fights a centering transform — it rotates in place, stationary. */
.btn-busy { position: relative; display: inline-flex; align-items: center; justify-content: center; }
.btn-busy-ghost { visibility: hidden; }
.btn-busy-spin { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.lb-msg { display: flex; align-items: center; justify-content: center; min-width: 280px; min-height: 200px; color: #fff; }
.lb-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.tile-play {
  position: absolute; inset: 0; margin: auto; width: 40px; height: 40px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; color: #fff;
  background: rgba(20, 20, 30, 0.55); pointer-events: none;
}
.tile-broken { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.tile-cap {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 0.2rem 0.4rem;
  background: rgba(20, 20, 30, 0.6); color: #fff; font-size: 0.72rem; line-height: 1.4;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left;
}
/* Video resolution · format · length badge, top-right so it clears the caption. */
.tile-meta {
  position: absolute; top: 0.3rem; left: 0.3rem; padding: 0.1rem 0.4rem;
  background: rgba(20, 20, 30, 0.72); color: #fff; font-size: 0.66rem; font-weight: 600;
  letter-spacing: 0.02em; border-radius: 5px; white-space: nowrap;
}

/* --- Featured star (mixdown + performance video). Edit = a toggle stacked
   directly below the delete button; read view = a white badge on a dark circle,
   top-right of the tile / player. The star is a bare polygon so CSS flips
   fill/outline per state. --- */
.tile-star { display: contents; } /* let the child position within the media-tile */
.star-btn {
  width: 26px; height: 26px; z-index: 3; padding: 0;
  border: 1px solid var(--border); border-radius: 7px; background: var(--surface);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-sm); transition: border-color 0.12s, background 0.12s;
}
.star-btn svg { display: block; }
.star-btn svg polygon { stroke-width: 2; stroke-linejoin: round; }
/* The star's fill vs. outline always tracks the committed state (outline = not
   featured, filled = featured), so a click flips it immediately even while the
   cursor is still on the button. Hover only recolours (→ brand purple); it never
   changes fill↔outline, which would mask the toggle. */
.star-btn svg polygon { fill: none; stroke: var(--muted); }         /* not featured: outline */
.star-btn:hover { border-color: var(--brand); }
.star-btn:hover svg polygon { fill: none; stroke: var(--brand); }   /* hover: purple outline */
.star-btn.on svg polygon { fill: var(--text); stroke: var(--text); } /* featured: near-black fill */
.star-btn.on:hover { border-color: var(--brand); }
.star-btn.on:hover svg polygon { fill: var(--brand); stroke: var(--brand); } /* hover: purple fill */
/* The delete control sits at top:5px; the star stacks directly below it. */
.media-tile .star-btn, .audio-edit-row > .star-btn { position: absolute; top: 36px; right: 5px; }

/* Read-view badge: top-right, white star on a dark tinted circle. On desktop the
   hover download/share overlay it at that corner. */
.star-badge {
  position: absolute; right: 6px; top: 6px; z-index: 2; pointer-events: none;
  width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; background: rgba(20, 20, 30, 0.72); color: #fff; /* matches .tile-meta / .tile-cap */
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.star-badge svg { display: block; }
/* Mobile: the top-right corner holds the always-visible kebab (⋮) menu, so drop the
   featured star just below the media-info plaque (top-left) on media tiles. */
@media (max-width: 760px) {
  .media-tile .star-badge { top: 1.7rem; left: 0.3rem; right: auto; }
}

.lightbox {
  position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center;
  background: rgba(12, 12, 20, 0.85); padding: 2.5rem; gap: 0.5rem;
  overscroll-behavior: contain;
}
.lb-fig { position: relative; margin: 0; display: flex; flex-direction: column; align-items: center; gap: 0.5rem; max-width: 92vw; max-height: 90vh; max-height: 90dvh; }
/* The media is bounded by an explicit viewport-relative max-height (dvh so iOS's
   toolbar counts) AND max-width, with width/height:auto so the box tracks the
   video's aspect — no letterbox, no jump when an adaptive-bitrate rendition
   changes the reported dimensions, and it can never grow past the visible area
   and push the close / arrows / caption off-screen. */
.lb-fig img, .lb-fig video {
  min-height: 0; width: auto; height: auto;
  max-width: 92vw; max-height: 78vh; max-height: 78dvh;
  object-fit: contain; border-radius: 8px; background: #000;
}
@media (max-width: 760px) {
  .lightbox { padding: 3rem 0.6rem; }
  .lb-fig img, .lb-fig video { max-height: 68vh; max-height: 68dvh; }
}
.lb-cap { display: flex; align-items: center; justify-content: space-between; gap: 1rem; color: #fff; font-size: 0.9rem; }
.lb-right { display: inline-flex; align-items: center; gap: 0.75rem; flex: none; }
.lb-count { color: rgba(255, 255, 255, 0.7); }
.lb-actions { display: inline-flex; gap: 0.4rem; }
.lb-close, .lb-nav {
  background: rgba(255, 255, 255, 0.12); color: #fff; border: 0; border-radius: 50%;
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center; flex: none;
}
.lb-close { position: absolute; top: 1rem; right: 1rem; width: 40px; height: 40px; font-size: 1.1rem; z-index: 2; }
/* Overlay the arrows on the media edges (absolute) rather than letting them take
   flex width beside the 92vw figure — otherwise they get pushed off-screen. */
.lb-nav { position: absolute; top: 50%; transform: translateY(-50%); z-index: 2; width: 46px; height: 46px; font-size: 1.8rem; line-height: 1; }
.lb-nav.prev { left: 0.6rem; }
.lb-nav.next { right: 0.6rem; }
.lb-close:hover, .lb-nav:hover { background: rgba(255, 255, 255, 0.25); }

/* --- Asset / section headers with inline download icon --- */
.asset-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.4rem; }
.asset-head .asset-label { margin-bottom: 0; }
.section-head { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.75rem; }
.section-head h3 { margin: 0; }
.push-right { margin-left: auto; display: inline-flex; }
.dl-btn {
  display: inline-flex; align-items: center; gap: 0.35rem;
  background: var(--surface); color: var(--muted);
  border: 1px solid var(--border); border-radius: 8px;
  padding: 0.3rem 0.6rem; font: inherit; font-size: 0.82em; font-weight: 500;
  line-height: 1; cursor: pointer;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.dl-btn:hover { color: var(--brand); border-color: var(--brand); background: var(--brand-50); }
.dl-btn:disabled { opacity: 0.55; cursor: default; }
.dl-btn svg { display: block; }
/* Size shows in parens after the label on desktop. On mobile the label is
   hidden — just the bare size + icon. */
.dl-size::before { content: "("; }
.dl-size::after { content: ")"; }
@media (max-width: 760px) {
  .dl-btn .dl-label { display: none; }
  .dl-size::before, .dl-size::after { content: ""; }
}

/* --- Per-asset actions (download + share) --- */
.asset-actions { display: inline-flex; align-items: center; gap: 0.5rem; }

/* --- Share popover --- */
.share-pop-wrap { position: relative; display: inline-flex; }
.share-pop {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 30;
  width: 320px; max-width: 80vw;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: var(--shadow-md); padding: 0.7rem 0.8rem;
}
.share-pop-row { display: flex; gap: 0.4rem; align-items: center; }
.share-pop .share-url { flex: 1; min-width: 0; font-size: 0.78em; color: var(--muted); }
.share-pop-note { margin-top: 0.45rem; }
/* Mobile: centered modal (backdrop + scroll lock handled in JS), like the
   Sharing settings panel. */
.share-pop.mobile {
  position: fixed; top: 50%; left: 50%; right: auto; transform: translate(-50%, -50%); z-index: 40;
  width: min(420px, calc(100vw - 1.5rem)); max-width: none; max-height: 80vh; overflow-y: auto;
}
.share-pop.mobile .sharing-close { display: inline-flex; }

/* --- Song detail: section nav sidebar + scroll-spy --- */
.song-layout { display: grid; grid-template-columns: 224px minmax(0, 1fr); gap: 1.75rem; align-items: start; }
.song-sections { min-width: 0; }
/* Zero-height scroll target; offsets clicks below the sticky topbar. */
/* Clears the sticky topbar (~56px) AND the sticky compact header (~99px) so a
   clicked nav target isn't hidden behind them. */
.sec-anchor { display: block; height: 0; scroll-margin-top: 132px; }

.song-nav { position: sticky; top: 72px; align-self: start; transition: top 0.16s ease; }
/* Sticky makes the nav its own stacking context (z-index auto → below the fixed
   edit bar at z-50). In edit mode lift it so the "Add section…" dropdown clears
   the edit bar. (The search modal is raised above this — see .qs-panel.) */
.song-detail.editing .song-nav { z-index: 51; }
/* When the compact header is showing, drop the sidebar below it. */
.song-detail.stuck .song-nav { top: 148px; }
.toc-list, .toc-sub { list-style: none; margin: 0; padding: 0; }
.toc-link {
  position: relative;
  display: flex; align-items: center; gap: 0.6rem; width: 100%;
  background: transparent; border: 0; border-radius: 8px;
  padding: 0.5rem 0.65rem; cursor: pointer; font: inherit; font-weight: 500;
  color: var(--muted); text-align: left; transition: color 0.12s, background 0.12s;
}
/* NEW tag for a just-added section: right-aligned, overlays a long name (without
   widening the fixed-width nav), with a soft fade so the overlap reads cleanly. */
.toc-new {
  position: absolute; right: 0.5rem; top: 50%; transform: translateY(-50%);
  font-size: 0.55rem; font-weight: 700; letter-spacing: 0.05em;
  color: var(--success); background: var(--success-50); padding: 0.08rem 0.34rem; border-radius: var(--pill);
  box-shadow: -10px 0 8px -3px var(--success-50);
}
.toc-link:hover { color: var(--text); background: var(--surface-2); }
.toc-link.active { color: var(--brand); background: var(--brand-50); font-weight: 600; }
.toc-icon { display: inline-flex; flex: none; }
.toc-icon svg { display: block; }
.toc-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.toc-sub { margin: 0.1rem 0 0.35rem; padding-left: 1.95rem; display: flex; flex-direction: column; gap: 1px; }
.toc-sublink {
  display: flex; align-items: center; gap: 0.4rem; width: 100%; text-align: left;
  background: transparent; border: 0; border-left: 2px solid var(--border);
  padding: 0.3rem 0.65rem; cursor: pointer; font: inherit; font-size: 0.85em;
  color: var(--muted); transition: color 0.12s, border-color 0.12s;
}
.toc-sublabel { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* change-count badge next to a tag-studio section link (blank when zero / section off) */
.toc-count { margin-left: auto; flex: none; font-size: 0.68rem; font-weight: 700; line-height: 1; color: var(--brand); background: var(--brand-50); border-radius: 999px; padding: 0.15rem 0.4rem; min-width: 0.85rem; text-align: center; font-variant-numeric: tabular-nums; }
.toc-submeta { color: var(--faint); font-size: 0.92em; flex: none; }
.toc-tag {
  margin-left: auto; flex: none; font-size: 0.72em; font-weight: 600; text-transform: capitalize;
  padding: 0.02rem 0.4rem; border-radius: var(--pill);
}
.toc-tag.practice { color: var(--muted); background: var(--surface-2); }
.toc-tag.live { color: var(--success); background: var(--success-50); }
.toc-tag.new { color: var(--success); background: var(--success-50); }
.toc-sublink:hover { color: var(--text); }
.toc-sublink:hover .toc-sublabel { color: var(--text); }
.toc-sublink.active { color: var(--brand); border-color: var(--brand); font-weight: 600; }

/* Collapsed mobile toggle — hidden on desktop where the full list always shows. */
.toc-toggle { display: none; }

@media (max-width: 760px) {
  .song-layout { grid-template-columns: 1fr; gap: 0; }
  .song-nav { position: static; margin-bottom: 1.25rem; }
  /* In edit mode the TOC is dead weight — every section is expanded for editing,
     and the add affordances live in the body (section headers + .bottom-add). */
  .song-detail.editing .song-nav { display: none; }
  .toc { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm); }
  .toc-toggle {
    display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
    width: 100%; background: transparent; border: 0; border-radius: var(--radius);
    padding: 0.7rem 0.85rem; font: inherit; font-weight: 600; color: var(--text); cursor: pointer;
  }
  .toc-toggle-main { display: inline-flex; align-items: center; gap: 0.55rem; min-width: 0; }
  .toc-toggle-main .toc-icon { color: var(--brand); }
  .toc-toggle-chev { flex: none; color: var(--muted); transition: transform 0.15s; }
  .toc.open .toc-toggle-chev { transform: rotate(180deg); }
  .toc-list { display: none; padding: 0.25rem 0.5rem 0.5rem; border-top: 1px solid var(--border); }
  .toc.open .toc-list { display: block; }
}

/* ============================ Tag studio (Health → Tags) ============================ */
.tagstudio { display: flex; flex-direction: column; gap: 1.25rem; padding-bottom: 4.5rem; position: relative; }
/* while a re-analysis runs, block interaction with the (about-to-be-replaced) results */
.ts-analyzing { position: absolute; inset: 0; z-index: 6; background: rgba(247, 247, 250, 0.62); backdrop-filter: blur(1.5px); border-radius: 12px; cursor: progress; }
.ts-analyzing-msg { position: sticky; top: 44vh; width: max-content; max-width: 90%; margin: 0 auto; display: flex; align-items: center; gap: 0.55rem; padding: 0.55rem 1rem; background: var(--surface); border: 1px solid var(--border); border-radius: 999px; box-shadow: var(--shadow-md); color: var(--text); font-weight: 600; font-size: 0.9rem; }

/* Summary + actions */
.ts-head { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 1.5rem; }
.ts-stats { display: flex; gap: 1.75rem; flex: 1; flex-wrap: wrap; }
.ts-stat { display: flex; flex-direction: column; gap: 0.15rem; }
.ts-stat-n { font-size: 1.5rem; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
.ts-stat.warn .ts-stat-n { color: var(--warn, #b7791f); }
.ts-stat-l { font-size: 0.75rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.ts-head-actions { display: inline-flex; align-items: center; gap: 0.5rem; }
.ts-when { flex-basis: 100%; margin: 0; }

.ts-empty { text-align: center; padding: 2rem 1rem; display: flex; flex-direction: column; align-items: center; gap: 0.5rem; }
.ts-empty-ic { color: var(--faint); }
.ts-empty p { margin: 0; }

/* Mobile sticky section icon-bar (Tags tab): mirrors the song-detail .section-bar —
   the sidebar TOC is hidden on mobile, so once the nav dropdown scrolls up under the
   topbar the sections stay one tap away as a row of icons, FIXED just below the topbar
   (revealed on scroll, like the song-detail compact header), the active one highlighted
   via scroll-spy. Hidden until .show (navStuck). Desktop uses the sidebar TOC instead. */
.ts-sticky-sections { display: none; }
@media (max-width: 760px) {
  .ts-sticky-sections {
    display: block; position: fixed; top: 62px; left: 0; right: 0; z-index: 11;
    background: var(--surface); border-bottom: 1px solid var(--border); box-shadow: var(--shadow-sm);
    opacity: 0; pointer-events: none; transition: opacity 0.16s ease;
  }
  .ts-sticky-sections.show { opacity: 1; pointer-events: auto; }
  .ts-sticky-sections-inner {
    display: flex; align-items: stretch; gap: 0.2rem;
    max-width: 1080px; margin: 0 auto; padding: 0.3rem 0.75rem;
  }
  .ts-sticky-sections .section-bar-btn { position: relative; }
  .ts-sticky-sections .section-bar-btn.off { opacity: 0.4; } /* section turned off / paused — dimmed like the struck sidebar link */
  .ts-section-bar-count {
    position: absolute; top: 2px; right: 50%; transform: translateX(1.05rem);
    min-width: 1rem; height: 1rem; padding: 0 0.22rem; box-sizing: border-box;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 0.62rem; font-weight: 700; line-height: 1;
    color: #fff; background: var(--brand); border-radius: 999px;
  }
}

/* Sections */
.ts-section { padding: 1rem 1.1rem; scroll-margin-top: 84px; } /* land below the sticky topbar when scroll-spy nav jumps here */
/* Land below the taller mobile sticky stack (topbar ~54 + section-bar ~50). Must
   follow the base rule above — a media query adds no specificity, so an equal one
   placed earlier would lose to the later base declaration. */
@media (max-width: 760px) { .ts-section { scroll-margin-top: 116px; } }
.ts-h { display: flex; align-items: center; gap: 0.5rem; margin: 0; font-size: 1.02rem; }
/* section on/off switch (squared iOS-style toggle) + disabled section body */
.ts-switch { margin-left: auto; flex: none; width: 42px; height: 24px; padding: 0; border: 1px solid var(--border); border-radius: 7px; background: var(--surface-2); position: relative; cursor: pointer; transition: background 0.15s, border-color 0.15s; }
.ts-switch.on { background: var(--brand); border-color: var(--brand); }
.ts-switch:disabled { cursor: not-allowed; opacity: 0.45; } /* e.g. "All tags" until a manual change is queued: can't be turned on directly */
.ts-switch-knob { position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 5px; background: #fff; box-shadow: 0 1px 2px rgba(20, 20, 30, 0.25); transition: left 0.15s; }
.ts-switch.on .ts-switch-knob { left: 20px; }
.ts-h svg { color: var(--brand); flex: none; }
.ts-sub { margin: 0.25rem 0 0.8rem; }

/* An auto section paused because manual "All tags" edits are queued: the item list is
   greyed + non-interactive, but the header switch stays live so it can resume the
   suggestions (discarding the manual edits). */
.ts-sec-disabled .ts-merge-list, .ts-sec-disabled .ts-section-body { opacity: 0.4; pointer-events: none; }
.ts-sec-disabled .ts-sub { opacity: 0.55; }
.ts-sec-paused { margin: 0 0 0.4rem; color: var(--warn); font-weight: 600; }

/* A reviewable item = [checkbox] [body] */
.ts-item { display: flex; gap: 0.7rem; padding: 0.7rem 0; border-top: 1px solid var(--border); }
.ts-item:first-of-type { border-top: 0; }
.ts-item.off { opacity: 0.5; }
.ts-check { flex: none; padding-top: 0.15rem; }
.ts-check input { width: 17px; height: 17px; margin: 0; accent-color: var(--brand); cursor: pointer; } /* margin:0 → column is exactly 17px so the merge header aligns to the chips */
.ts-item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.3rem; }
.ts-reason { margin: 0; }

/* Tag pills */
.ts-chip { display: inline-flex; align-items: center; background: var(--brand-50); border: 0; color: var(--brand); border-radius: var(--pill); padding: 0.16rem 0.6rem; font: inherit; font-size: 0.82em; font-weight: 600; cursor: default; white-space: nowrap; }
button.ts-chip { cursor: pointer; }
button.ts-chip:hover { background: var(--brand); color: #fff; }
.ts-chip.removable.struck { text-decoration: line-through; background: var(--surface-2); color: var(--muted); }

/* Merge line — fixed left column so [→] and [Into] align on every row (each row
   is its own grid, so a 1fr left column would shift per row). Columns:
   [merge these] [→] [into] · [net effect] [confidence] [show details]; the from +
   into widths are measured (JS) and the right group is fixed-width, so every
   column lines up across rows. */
.ts-merge-cols { grid-template-columns: var(--from-col, max-content) 1.4rem var(--into-col, max-content) minmax(1rem, 1fr) 5.25rem 5.25rem 6.75rem; gap: 0.5rem; }
.ts-merge-head { display: grid; align-items: center; padding: 0 0 0.35rem calc(17px + 0.7rem); color: var(--faint); font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
.ts-merge-spacer { min-width: 0; }
.ts-merge-line { display: grid; align-items: center; }
/* clicking anywhere on a row (except the expanded detail + inner controls) toggles it */
.ts-item.ts-merge, .ts-item.ts-gap, .ts-item.ts-remove, .ts-item.ts-rare { cursor: pointer; position: relative; z-index: 0; }
.ts-item.ts-merge .ts-merge-detail, .ts-item.ts-gap .ts-songs, .ts-item.ts-remove .ts-songs, .ts-item.ts-rare .ts-songs { cursor: default; }
/* full-row hover highlight — an overlay behind the whole item (the opaque detail
   panel masks it, so only the summary lights up). The entire item reacts to hover,
   but hovering the expanded detail does not. */
.ts-item.ts-merge::after, .ts-item.ts-gap::after, .ts-item.ts-remove::after, .ts-item.ts-rare::after { content: ""; position: absolute; z-index: -1; inset: 0.15rem -0.55rem; border-radius: 10px; background: var(--surface-2); opacity: 0; transition: opacity 0.12s ease; pointer-events: none; }
.ts-item.ts-merge:hover:not(:has(.ts-merge-detail:hover))::after,
.ts-item.ts-gap:hover:not(:has(.ts-songs:hover))::after,
.ts-item.ts-remove:hover:not(:has(.ts-songs:hover))::after,
.ts-item.ts-rare:hover:not(:has(.ts-songs:hover))::after { opacity: 1; }
/* removes section: red "tag to remove" chip, red net-effect pill, struck removed tag */
.ts-chip.ts-chip-remove { background: var(--danger); color: #fff; }
.ts-chip.ts-chip-remove.struck { background: var(--surface-2); color: var(--muted); text-decoration: line-through; }
.ts-rare-action .ts-chip.ts-chip-remove { background: #fdecef; color: #c0392b; }
.ts-rare-action .ts-chip.ts-chip-remove.struck { background: var(--surface-2); color: var(--muted); }
.ts-net.ts-net-remove { background: #fdecef; color: #c0392b; }
.ts-chip.ts-chip-strike { background: #fdecef; color: #c0392b; text-decoration: line-through; }
.ts-merge-from { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
.ts-arrow { color: var(--muted); font-weight: 700; text-align: center; }
.ts-merge-into { display: flex; align-items: center; gap: 0.4rem; min-width: 0; }
/* the canonical (target) reads as a filled tag — same size/shape as the source
   chips, isn't editable. Its song count sits inside like the source chips. */
.ts-chip.ts-chip-into { background: var(--brand); color: #fff; }
.ts-chip.ts-chip-into .ts-cn.isnew { background: #d9f2e4; color: #157347; }
.ts-merge-effect { display: inline-flex; align-items: center; gap: 0.45rem; justify-self: start; }
/* confidence — its own column after Net effect; a muted dot + label so it reads
   clearly apart from the coloured Net-effect pill */
.ts-conf { justify-self: start; align-self: center; display: inline-flex; align-items: center; gap: 0.34rem; color: var(--muted); font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; white-space: nowrap; }
.ts-conf-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--faint); }
.ts-conf-high .ts-conf-dot { background: #22a06b; }
.ts-conf-medium .ts-conf-dot { background: #e0a316; }
.ts-conf-low .ts-conf-dot { background: var(--faint); }
/* song-count badges */
.ts-cn { display: inline-flex; align-items: center; justify-content: center; margin-left: 0.35rem; min-width: 1.15em; padding: 0 0.3rem; border-radius: 999px; background: #fff; color: var(--brand); font-size: 0.72em; font-weight: 700; font-variant-numeric: tabular-nums; }
.ts-chip.struck .ts-cn { background: var(--surface); color: var(--muted); }
/* a disabled row's target tag (merge canonical / gap applied) reads as struck too */
.ts-chip.ts-chip-into.struck { background: var(--surface-2); color: var(--muted); text-decoration: line-through; }
.ts-chip.ts-chip-into.struck .ts-cn, .ts-chip.struck .ts-cn.isnew { background: var(--surface); color: var(--muted); }
/* net-effect pill */
.ts-net { font-size: 0.7rem; font-weight: 700; padding: 0.14rem 0.5rem; border-radius: 999px; background: #d9f2e4; color: #157347; white-space: nowrap; }
.ts-net.neutral { background: var(--surface-2); color: var(--muted); }
.ts-net-sub { white-space: nowrap; }
/* "Show / hide details" — its own column, set apart from the Into tag; expands
   the per-song before → after list below */
.ts-songs-toggle { justify-self: end; align-self: center; display: inline-flex; align-items: center; gap: 0.28rem; background: transparent; border: 0; color: var(--muted); cursor: pointer; font: inherit; font-size: 0.8rem; font-weight: 600; text-transform: none; letter-spacing: normal; white-space: nowrap; padding: 0.15rem 0; }
.ts-songs-toggle:hover, .ts-songs-toggle.open { color: var(--brand); }
.ts-songs-toggle .ts-caret { display: inline-flex; color: var(--muted); transition: transform 0.15s ease; }
.ts-songs-toggle:hover .ts-caret, .ts-songs-toggle.open .ts-caret { color: var(--brand); }
.ts-songs-toggle.open .ts-caret { transform: rotate(180deg); }
/* per-song before → after: [song] [current tags] → [new tags] */
.ts-merge-detail { margin-top: 0.4rem; padding: 0.1rem 0.75rem; background: var(--surface-2); border-radius: 8px; }
.ts-songrow { display: grid; grid-template-columns: auto minmax(7rem, 13rem) minmax(0, 1fr) auto minmax(0, 1fr); gap: 0.3rem 0.6rem; align-items: center; padding: 0.55rem 0; }
.ts-songrow + .ts-songrow { border-top: 1px solid var(--border); }
.ts-songrow.off { opacity: 0.5; }
.ts-songrow .ts-check { display: flex; align-items: center; padding-top: 0; }
.ts-songrow-name { font-size: 0.85rem; font-weight: 600; color: var(--text); line-height: 1.3; }
.ts-songrow-name .muted { font-weight: 400; }
/* one line only — the fit effect hides overflow chips and reveals "+N" */
.ts-songrow-tags { display: flex; flex-wrap: nowrap; overflow: hidden; gap: 0.3rem; align-items: center; }
.ts-songrow-arrow { color: var(--muted); font-weight: 700; text-align: center; }
.ts-songrow-empty { padding: 0.6rem 0; }
.ts-oldnew { display: none; } /* OLD/NEW row captions — mobile only (see media query) */
.ts-chip.ts-chip-sm { font-size: 0.76rem; padding: 0.13rem 0.5rem; }
.ts-chip.ts-chip-muted { background: var(--surface); color: var(--muted); } /* white pill on the grey detail panel, matching the "+N" badge */
.ts-more { display: none; flex: none; align-items: center; background: var(--surface); color: var(--faint); border-radius: var(--pill); padding: 0.13rem 0.45rem; font-size: 0.72rem; font-weight: 700; white-space: nowrap; }
.ts-more::after { content: "+" attr(data-n); }
@media (max-width: 760px) {
  .ts-merge-head { display: none; }
  .ts-merge-line { grid-template-columns: 1fr; }
  .ts-merge-line .ts-arrow, .ts-rare-cols .ts-arrow, .ts-merge-spacer { display: none; }
  .ts-merge-effect, .ts-conf, .ts-songs-toggle { justify-self: start; }
  /* Merge look-alike rows on mobile: confidence over net-effect at the top-right,
     the "before" tags on one row and the "after" tag on the next, joined by a square
     elbow arrow at the left, and "Show details" as the final full-width row. */
  .ts-item.ts-merge .ts-merge-line {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "from meta" "into meta" "tog tog";
    column-gap: 0.6rem; row-gap: 0.4rem; align-items: center;
  }
  .ts-item.ts-merge .ts-merge-from   { grid-area: from; }
  .ts-item.ts-merge .ts-merge-into   { grid-area: into; position: relative; padding-left: 1.7rem; }
  /* confidence + net-effect: both anchored to the TOP of the meta column so the
     confidence lines up with the first tag row's upper edge, the effect tucked just
     below it (fixed offset → independent of how many "before" tags wrap). */
  .ts-item.ts-merge .ts-conf         { grid-area: meta; justify-self: end; align-self: start; font-size: 0.6rem; }
  .ts-item.ts-merge .ts-merge-effect { grid-area: meta; justify-self: end; align-self: start; margin-top: 1.2rem; }
  .ts-item.ts-merge .ts-merge-effect .ts-net { font-size: 0.62rem; padding: 0.1rem 0.4rem; }
  .ts-item.ts-merge .ts-songs-toggle { grid-area: tog;    justify-self: start; }
  /* the elbow: a thin vertical stem starting just below the before row, turning right
     into the after chip, tipped with a small arrowhead aligned to the corner line. */
  .ts-item.ts-merge .ts-merge-into { padding-left: 1.4rem; }
  .ts-item.ts-merge .ts-merge-into::before, .ts-item.ts-rare .ts-rare-action::before, .ts-item.ts-alltag .ts-alltag-action::before {
    content: ""; position: absolute; left: 0.5rem; top: -0.3rem; bottom: 50%; width: 0.5rem;
    border-left: 1.5px solid var(--faint); border-bottom: 1.5px solid var(--faint);
  }
  .ts-item.ts-merge .ts-merge-into::after, .ts-item.ts-rare .ts-rare-action::after, .ts-item.ts-alltag .ts-alltag-action::after {
    content: ""; position: absolute; left: 0.98rem; bottom: 50%; transform: translateY(calc(50% - 0.75px));
    width: 0; height: 0; border-top: 3.5px solid transparent; border-bottom: 3.5px solid transparent; border-left: 5px solid var(--faint);
  }
  /* Apply-tags (gap) AND Remove-tags rows on mobile: mirror the merge card — tag on
     the left, confidence over net-effect at the top-right, "Show details" as the last
     row. No before→after here, so no elbow arrow. */
  /* Keep the "Show all / Hide all" control (from the otherwise-hidden column-label
     header) on mobile, right-aligned just above the rows; drop the column labels. */
  .ts-merge-head.ts-merge-cols, #ts-sec-gaps .ts-merge-head, #ts-sec-removes .ts-merge-head, #ts-sec-rare .ts-merge-head, #ts-sec-manual .ts-merge-head {
    display: flex; justify-content: flex-end; align-items: center; padding: 0 0 0.4rem;
  }
  .ts-merge-head.ts-merge-cols > span, #ts-sec-gaps .ts-merge-head > span, #ts-sec-removes .ts-merge-head > span, #ts-sec-rare .ts-merge-head > span, #ts-sec-manual .ts-merge-head > span { display: none; }
  .ts-item.ts-gap .ts-gap-head, .ts-item.ts-remove .ts-gap-head {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "tag meta" "tog tog";
    column-gap: 0.6rem; row-gap: 0.4rem; align-items: start;
  }
  .ts-item.ts-gap .ts-gap-tag,      .ts-item.ts-remove .ts-gap-tag      { grid-area: tag; }
  .ts-item.ts-gap .ts-conf,         .ts-item.ts-remove .ts-conf         { grid-area: meta; justify-self: end; align-self: start; font-size: 0.6rem; }
  .ts-item.ts-gap .ts-merge-effect, .ts-item.ts-remove .ts-merge-effect { grid-area: meta; justify-self: end; align-self: start; margin-top: 1.2rem; }
  .ts-item.ts-gap .ts-merge-effect .ts-net, .ts-item.ts-remove .ts-merge-effect .ts-net { font-size: 0.62rem; padding: 0.1rem 0.4rem; }
  .ts-item.ts-gap .ts-songs-toggle, .ts-item.ts-remove .ts-songs-toggle { grid-area: tog; justify-self: start; }
  /* One-use rows on mobile: mirror the merge card — the used-once tag on one row, its
     recommendation (merge-into chip or "Remove") on the next joined by the same elbow
     arrow, confidence over net-effect top-right, "Show details" as the last row. */
  .ts-item.ts-rare .ts-rare-cols {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "tag meta" "action meta" "tog tog";
    column-gap: 0.6rem; row-gap: 0.4rem; align-items: center;
  }
  .ts-item.ts-rare .ts-gap-tag      { grid-area: tag; }
  .ts-item.ts-rare .ts-rare-action  { grid-area: action; position: relative; padding-left: 1.4rem; justify-self: start; }
  .ts-item.ts-rare .ts-conf         { grid-area: meta; justify-self: end; align-self: start; font-size: 0.6rem; }
  .ts-item.ts-rare .ts-merge-effect { grid-area: meta; justify-self: end; align-self: start; margin-top: 1.2rem; }
  .ts-item.ts-rare .ts-merge-effect .ts-net { font-size: 0.62rem; padding: 0.1rem 0.4rem; }
  .ts-item.ts-rare .ts-songs-toggle { grid-area: tog; justify-self: start; }
  /* One-use detail songs: title/artist on its own line, then the tags below it —
     merge suggestions get the OLD/NEW two-row treatment (like the merge detail),
     removal suggestions show the struck tag on one line (like the remove section). */
  .ts-item.ts-rare .ts-rare-song:not(.ts-rare-song-rm) {
    grid-template-columns: auto 1fr; gap: 0.25rem 0.6rem; align-items: center;
  }
  .ts-item.ts-rare .ts-rare-song:not(.ts-rare-song-rm) > .ts-songrow-name { grid-column: 1 / -1; }
  .ts-item.ts-rare .ts-rare-song:not(.ts-rare-song-rm) > .ts-songrow-tags { grid-column: 2; }
  .ts-item.ts-rare .ts-rare-song:not(.ts-rare-song-rm) > .ts-songrow-arrow { display: none; }
  .ts-item.ts-rare .ts-rare-song-rm { grid-template-columns: 1fr; gap: 0.2rem; }
  /* All-tags manual rows on mobile: mirror the one-use card — the tag on one row, its
     chosen action (merge-into chip / "Remove" / the merge-into picker) on the next joined
     by the same elbow arrow, "Show details" as the last row. */
  .ts-item.ts-alltag .ts-alltags-cols {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "tag" "action" "tog";
    row-gap: 0.4rem; align-items: center;
  }
  .ts-item.ts-alltag .ts-gap-tag       { grid-area: tag; justify-self: start; }
  .ts-item.ts-alltag .ts-alltag-action { grid-area: action; position: relative; padding-left: 1.4rem; justify-self: start; }
  .ts-item.ts-alltag .ts-alltag-action .ts-arrow { display: none; } /* the elbow replaces the inline arrow */
  .ts-item.ts-alltag .ts-songs-toggle  { grid-area: tog; justify-self: start; }
  .ts-songrow { grid-template-columns: auto 1fr; gap: 0.25rem 0.6rem; }
  .ts-songrow > .ts-check { grid-column: 1; }
  .ts-songrow > :not(.ts-check):not(.ts-oldnew) { grid-column: 2; }
  .ts-songrow-arrow { display: none; }
  /* OLD / NEW captions in column 1 (checkbox width), aligned with the before / after
     tag rows — tiny monospace all-caps so they read as labels, not content. */
  .ts-songrow > .ts-oldnew, .ts-item.ts-rare .ts-rare-song > .ts-oldnew, .ts-apply-diff > .ts-oldnew {
    display: block; grid-column: 1; width: 17px; text-align: center;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.5rem; font-weight: 700; letter-spacing: 0.02em; line-height: 1;
    text-transform: uppercase; color: var(--faint);
  }
  .ts-gaprow { grid-template-columns: auto 1fr; gap: 0.25rem 0.6rem; }
  .ts-gaprow .ts-songrow-tags { grid-column: 2; }
}

/* Confidence chip */
.conf { font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; padding: 0.12rem 0.4rem; border-radius: var(--pill); }
.conf-high { color: #157347; background: #d9f2e4; }
.conf-medium { color: #b7791f; background: #fbeecb; }
.conf-low { color: var(--muted); background: var(--surface-2); }

/* Gaps */
/* gap head — same right-anchored Net-effect / Confidence / Show-details columns
   as the merge line (fixed widths + a 1fr spacer, same right edge), so they line
   up across both sections regardless of the differing left content */
.ts-gap-head { display: grid; grid-template-columns: max-content minmax(1rem, 1fr) 5.25rem 5.25rem 6.75rem; gap: 0.5rem; align-items: center; }
.ts-gap-tag { display: flex; align-items: center; min-width: 0; }
/* gap song rows — styled like the merge before/after rows: [check] [song] [tags],
   the applied tag highlighted, the song's existing tags muted, one line + "+N" */
.ts-songs { list-style: none; margin: 0.45rem 0 0; padding: 0.1rem 0.75rem; display: flex; flex-direction: column; background: var(--surface-2); border-radius: 8px; } /* grey panel, matching the merge detail */
.ts-gaprow { display: grid; grid-template-columns: auto minmax(7rem, 15rem) minmax(0, 1fr); gap: 0.3rem 0.7rem; align-items: center; padding: 0.5rem 0; }
.ts-gaprow + .ts-gaprow { border-top: 1px solid var(--border); }
.ts-gaprow.off { opacity: 0.5; }
/* "Select all / Deselect all" at the top-left of a gap's song list */
.ts-selectall { padding: 0.5rem 0 0.45rem; border-bottom: 1px solid var(--border); }
.ts-selectall button { background: transparent; border: 0; padding: 0; font: inherit; font-size: 0.78rem; font-weight: 600; color: var(--brand); cursor: pointer; }
.ts-selectall button:hover { text-decoration: underline; }
.ts-selectall button:disabled { color: var(--muted); cursor: default; text-decoration: none; }
.ts-gaprow .ts-check { display: flex; align-items: center; padding-top: 0; } /* flex label centres the input (no baseline offset) */
/* the "why this song fits" reason sits on its own row under the song name */
.ts-song-why { grid-column: 2 / -1; margin: 0.1rem 0 0; }

/* Rare */
/* one-use tags: dedicated centered arrow column (like the merge section, so the
   arrow keeps even margins), then the same right-anchored Net-effect /
   Confidence / Show-details columns as the other sections */
.ts-rare-cols { display: grid; grid-template-columns: var(--rare-tag-col, max-content) 1.4rem max-content minmax(1rem, 1fr) 5.25rem 5.25rem 6.75rem; gap: 0.5rem; align-items: center; }
.ts-rare-action { display: inline-flex; align-items: center; gap: 0.4rem; min-width: 0; }
.ts-rare-rm { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0; border-radius: 7px; border: 1px solid var(--border); background: var(--surface); color: var(--faint); cursor: pointer; transition: color 0.12s, border-color 0.12s, background 0.12s; }
.ts-rare-rm:hover { color: var(--danger); border-color: var(--danger); }
.ts-rare-rm.revert:hover { color: var(--brand); border-color: var(--brand); }
.ts-rare-rmlabel { color: var(--danger); font-size: 0.86rem; font-weight: 600; } /* plain "Remove" label, not a pill — the red tag chip carries the emphasis */
.ts-rare-rmlabel.struck { color: var(--faint); text-decoration: line-through; }
.ts-chip.struck { background: var(--surface-2); color: var(--muted); text-decoration: line-through; }
/* affected-song rows in the one-use detail (no per-song checkbox): [song] [tags] */
.ts-rare-song { display: grid; grid-template-columns: minmax(7rem, 13rem) minmax(0, 1fr) auto minmax(0, 1fr); gap: 0.3rem 0.7rem; align-items: center; padding: 0.55rem 0; }
.ts-rare-song.ts-rare-song-rm { grid-template-columns: minmax(7rem, 13rem) minmax(0, 1fr); } /* remove: just the song's tags with the removed one struck (no before → after) */
.ts-rare-song + .ts-rare-song { border-top: 1px solid var(--border); }
.ts-rare-song .ts-song-why { grid-column: 1 / -1; }

/* Manual manager */
.ts-manual-head { display: flex; align-items: center; gap: 0.5rem; } /* collapse button on the left, section switch (margin-left:auto) on the right */
.ts-manual-toggle { display: inline-flex; align-items: center; gap: 0.45rem; background: transparent; border: 0; font: inherit; font-weight: 600; font-size: 1.02rem; cursor: pointer; color: var(--text); padding: 0; }
.ts-manual-toggle svg { color: var(--brand); }
.ts-manual-toggle .ts-caret { display: inline-flex; transition: transform 0.15s ease; transform: rotate(-90deg); }
.ts-manual-toggle .ts-caret svg { color: var(--faint); }
.ts-manual-toggle.open .ts-caret { transform: rotate(0deg); }
.ts-manual { margin-top: 0.8rem; }
/* "All tags" grid — same look/interaction as the one-use section: aligned columns,
   hover, expandable song details. Tag | Songs | (spacer) | Actions | Show details. */
.ts-alltags-cols { display: grid; grid-template-columns: minmax(8rem, max-content) minmax(1rem, 1fr) 12.5rem 6.75rem; gap: 0.6rem; align-items: center; }
.ts-merge-head.ts-alltags-cols { padding-left: 0; } /* no checkbox column here, so the header starts flush with the chips */
.ts-alltag-action { display: inline-flex; align-items: center; gap: 0.4rem; min-width: 0; justify-self: start; }
.ts-mini { width: auto; padding: 0.22rem 0.45rem; font-size: 0.85em; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); font-family: inherit; color: var(--text); cursor: pointer; }
.ts-mini:hover { border-color: var(--brand); }
.ts-manual-note { margin-top: 0.7rem; }

/* Sticky apply bar */
/* matches the song-edit .edit-bar: fixed, full-width, light */
.ts-applybar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
  background: #f6f4fe; border-top: 1px solid #e7e3f8;
  box-shadow: 0 -6px 20px rgba(16, 16, 40, 0.06);
  padding: 0.8rem 1.5rem;
}
.ts-applybar-inner { max-width: 1080px; margin: 0 auto; }
.ts-applybar-main { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.ts-impact { display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap; font-size: 0.9rem; color: var(--muted); }
.ts-apply-toggle { white-space: nowrap; }

/* apply-bar "Show details": full-screen overlay locks the page below, and an on-demand
   scrollable panel lists every affected song's before → after tags (capped to ~half the
   viewport so the bar never takes over the screen). */
.ts-apply-overlay { position: fixed; inset: 0; z-index: 49; background: rgba(16, 16, 40, 0.28); }
.ts-apply-details { margin-bottom: 0.75rem; max-height: 50vh; overflow: auto; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 0.7rem 0.85rem; }
/* z-index keeps this sticky header above the scrolling rows — the diff scroller's mask
   (has-more) forms a stacking context, so without it the tags paint over the header. */
.ts-apply-details-head { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin-bottom: 0.5rem; position: sticky; top: -0.7rem; z-index: 5; background: var(--surface); padding: 0.15rem 0; }
.ts-apply-list { list-style: none; margin: 0; padding: 0; }
.ts-apply-row { display: grid; grid-template-columns: minmax(9rem, 15rem) minmax(0, 1fr); gap: 0.35rem 0.7rem; align-items: center; padding: 0.5rem 0; }
/* Wraps both before/after tag rows. Desktop: a 3-col grid (before | arrow | after) —
   the OLD/NEW labels are hidden. Mobile: one horizontal scroller (see media query). */
.ts-apply-diff { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 0.5rem; align-items: center; }
.ts-apply-row + .ts-apply-row { border-top: 1px solid var(--border); }
.ts-apply-tags { display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; }
/* Unchanged tags here sit on the white details panel (not the grey detail cards), so the
   default white "muted" pill is invisible — give them the normal lavender tag pill so they
   read as ordinary (untouched) tags next to the struck removals and solid-purple additions.
   Applies to the apply-bar panel and the "Last applied" modal (both use .ts-apply-tags). */
.ts-apply-tags .ts-chip-muted { background: var(--brand-50); color: var(--brand); }
.ts-impact-item strong { color: var(--text); }
.ts-applybar-actions { display: inline-flex; align-items: center; gap: 0.6rem; }
/* The impact summary lives in the bar on desktop; on mobile it moves into the
   "Show details" panel (see below), so this copy is hidden on the wide layout. */
.ts-apply-summary { display: none; }
.ts-apply-topbar { display: none; } /* mobile-only close-button header for the full-height panel */

@media (max-width: 760px) {
  .ts-stats { gap: 1.1rem; }
  .ts-stat-n { font-size: 1.25rem; }
  .ts-applybar { padding-bottom: calc(1.4rem + env(safe-area-inset-bottom, 0px)); }
  /* Bottom bar: drop the stats, leaving just "Show details" on the left and the
     Reset / Apply buttons on the right (one row). */
  .ts-impact .ts-impact-item { display: none; }
  .ts-applybar-actions { width: auto; justify-content: flex-end; }
  /* Summary moves into the details panel, above the affected-songs list. */
  .ts-apply-summary {
    display: flex; flex-wrap: wrap; gap: 0.35rem 1.1rem;
    font-size: 0.9rem; color: var(--muted);
    margin-bottom: 0.6rem; padding-bottom: 0.6rem; border-bottom: 1px solid var(--border);
  }
  /* Title on its own line, then the OLD/NEW diff below it. */
  .ts-apply-row { grid-template-columns: minmax(0, 1fr); gap: 0.55rem; padding: 0.7rem 0; }
  /* OLD and NEW share ONE horizontal scroller (a two-row grid) so they scroll TOGETHER —
     a long NEW row drags the short OLD row along, keeping the diff aligned; the shorter row
     just trails into empty space. No wrapping, so it's always clear which row is which. A
     right-edge mask fade cues "more →" and clears at the end (has-more from scroll pos).
     Labels stay pinned at the left (sticky) so you never lose the OLD/NEW context. */
  /* max-content on the label column (not auto) so it hugs the label instead of absorbing
     the free space — otherwise short rows get pushed right. Free space falls at the end,
     keeping tags left-aligned; a wider row simply overflows and scrolls. */
  .ts-apply-diff {
    grid-template-columns: max-content max-content; gap: 0.55rem 0.5rem; align-items: center;
    overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .ts-apply-diff::-webkit-scrollbar { display: none; }
  .ts-apply-diff > .ts-apply-tags { grid-column: 2; flex-wrap: nowrap; }
  .ts-apply-diff > .ts-songrow-arrow { display: none; }
  /* Sticky OLD/NEW labels: a full row-height opaque strip so tags disappear cleanly under
     them, with a gradient fade to the right (mirroring the row-end fade) that appears once
     scrolled — so tags fade out as they slide under, rather than a hard cut. */
  .ts-apply-diff > .ts-oldnew { position: sticky; left: 0; z-index: 1; align-self: stretch; display: flex; align-items: center; justify-content: center; background: var(--surface); }
  .ts-apply-diff > .ts-oldnew::after {
    content: ""; position: absolute; left: 100%; top: 0; bottom: 0; width: 1.3rem;
    background: linear-gradient(to right, var(--surface), transparent);
    pointer-events: none; opacity: 0; transition: opacity 0.15s;
  }
  .ts-apply-diff.scrolled > .ts-oldnew::after { opacity: 1; }
  .ts-apply-diff.has-more {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent);
  }
  /* "Show details" open: the bar fills the screen (top:0) as a flex column so the
     panel takes all the space above the Reset/Apply row and scrolls; a close button
     sits at the top-right. Auto-sizes to the controls (no pixel guessing). */
  .ts-applybar.ts-applybar-open { top: 0; }
  .ts-applybar-open .ts-applybar-inner { height: 100%; display: flex; flex-direction: column; }
  .ts-applybar-open .ts-apply-topbar { display: flex; justify-content: flex-end; flex: none; padding-bottom: 0.4rem; }
  .ts-applybar-open .ts-apply-details { flex: 1 1 auto; min-height: 0; max-height: none; margin-bottom: 0.75rem; }
  .ts-applybar-open .ts-applybar-main { flex: none; }
  .ts-apply-close {
    display: inline-flex; align-items: center; justify-content: center;
    width: 2rem; height: 2rem; padding: 0;
    border: 1px solid var(--border); border-radius: 8px;
    background: var(--surface); color: var(--muted); cursor: pointer;
    transition: color 0.12s, background 0.12s, border-color 0.12s;
  }
  .ts-apply-close:hover { color: var(--text); background: var(--bg); border-color: var(--border-strong); }
}
