/* Site-wide color control point: --accent drives every button/link/
   active-state/genre-era-tile color across the whole site (nothing sets
   its own color independently anymore -- TV Mode's genre tiles used to
   have a per-genre --tile-color override, removed so they're unified with
   this too). To change the site's whole color identity, edit --accent/
   --accent-soft here. They're defined in 4 places, not 1 -- an
   unavoidable consequence of having no build step (no way to compute a
   derived light-mode vs. dark-mode value from a single source without a
   preprocessor): once for :root/prefers-color-scheme (auto light/dark)
   and once more for [data-theme="light"]/[data-theme="dark"] (the
   explicit theme-toggle override, see styles.css further down). All 4
   must be changed together -- search "--accent: #" to find them all. */
:root {
  --bg: #fafafa;
  --surface: #ffffff;
  --border: #e4e4e7;
  --text: #18181b;
  --text-muted: #6b6b73;
  --text-faint: #9a9aa2;
  --accent: #4a0d8f;
  --accent-soft: #efe4fb;
  --tag-music-video: #2b5bd7;
  --tag-music-video-bg: #eaf0fd;
  --tag-dance: #a34dbf;
  --tag-dance-bg: #f6ecfa;
  --tag-montage: #c2660a;
  --tag-montage-bg: #fcefe1;
  --tag-dvd: #1f8a5f;
  --tag-dvd-bg: #e6f5ee;
  --tag-live: #c4293f;
  --tag-live-bg: #fbe6ea;
  --tag-installation: #0f7d8a;
  --tag-installation-bg: #e2f5f7;
  --tag-short: #5b4fd6;
  --tag-short-bg: #ececfd;
  --tag-docu: #92660f;
  --tag-docu-bg: #faf1de;
  --brand-yellow: #a68900;
  /* The "G" in the MVG logo -- used as a button background with fixed
     black text on top, so (unlike --brand-yellow, a text color that needs
     contrast against a variable page background) this one constant value
     works the same in both themes; no light/dark duplication needed. */
  --brand-lightblue: #4fe6ee;
  --radius: 10px;
  --shadow: 0 1px 2px rgba(20, 20, 30, 0.04), 0 1px 8px rgba(20, 20, 30, 0.03);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  scrollbar-color: var(--accent) var(--surface); /* Firefox */
}

/* Extends --accent into browser chrome that would otherwise default to a
   generic blue -- text selection and the page scrollbar thumb (Firefox via
   scrollbar-color above, WebKit/Blink via ::-webkit-scrollbar-thumb below). */
::selection {
  background: var(--accent);
  color: #fff;
}

::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}

::-webkit-scrollbar-track {
  background: var(--surface);
}

::-webkit-scrollbar-thumb {
  background: var(--accent);
  border-radius: 999px;
  border: 3px solid var(--surface);
}

/* Visible-keyboard-focus ring, purple instead of the browser default blue --
   :focus-visible (not :focus) so mouse/touch clicks don't get a ring, only
   keyboard navigation. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Slim progress bar pinned above everything (including the sticky top bar)
   while the ~13k-row snapshot downloads -- tracks real bytes-received when
   the browser exposes Content-Length (see fetchJsonWithProgress in app.js),
   falling back to an indeterminate sweep otherwise. Mainly for slower
   connections, where "Loading database…" text alone is easy to miss. */
.loading-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  z-index: 50;
  background: transparent;
  overflow: hidden;
}

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

.loading-bar-fill {
  height: 100%;
  width: 0%;
  background: var(--accent);
  transition: width 0.2s ease, opacity 0.3s ease;
}

.loading-bar.is-indeterminate .loading-bar-fill {
  width: 40%;
  animation: loading-bar-sweep 1.1s ease-in-out infinite;
}

.loading-bar.is-done .loading-bar-fill {
  opacity: 0;
}

@keyframes loading-bar-sweep {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}

/* Bigger, harder-to-miss loading indicator for phones -- the thin top bar
   above is easy to miss on a small screen while everything else on the page
   still looks blank. Desktop keeps just the thin bar (this stays hidden
   there); shown/hidden on the exact same schedule as .loading-bar. */
.loading-banner {
  display: none;
}

.loading-banner[hidden] {
  display: none;
}

@media (max-width: 640px) {
  .loading-banner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    margin: 4px 0 18px;
    padding: 20px 16px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    text-align: center;
    transition: opacity 0.3s ease;
  }

  .loading-banner[hidden] {
    display: none;
  }

  .loading-banner.is-done {
    opacity: 0;
  }

  .loading-banner-spinner {
    width: 32px;
    height: 32px;
    margin-bottom: 4px;
    border: 3px solid var(--border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
  }

  .loading-banner-text {
    font-size: 0.95rem;
    font-weight: 700;
    color: var(--text);
  }

  .loading-banner-percent {
    font-size: 1.3rem;
    font-weight: 700;
    color: var(--accent);
  }

  .loading-banner-hint {
    font-size: 0.78rem;
    color: var(--text-faint);
  }
}

/* Top bar: hamburger, logo, title -- full width, not centered. Desktop
   shows it as a plain left-to-right row (hamburger leftmost); the mobile
   media query below re-centers the logo/title and pulls the hamburger out
   to an absolute top-right position instead. */
/* Same purple radial-glow language as land.html's title-card background,
   rescaled for a thin persistent bar instead of a full page -- a single
   glow centered in the bar, plain --bg at both edges (logo side and
   icons side) so it reads as a spotlight on the middle, not a tint on the
   logo. Built off var(--accent) via color-mix() rather than hardcoded
   purple like land.html (which is always dark, theme-independent) so this
   stays correct in both light and dark theme and if --accent ever changes. */
.top-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 20px;
  background:
    radial-gradient(ellipse 45% 320% at 50% 50%, color-mix(in srgb, var(--accent) 38%, transparent), transparent 80%),
    var(--bg);
  border-bottom: 1px solid var(--border);
}

/* Light theme: a flat lavender instead of the glow -- the same soft tint
   --accent-soft already uses for hover states there, so it stays a
   deliberate design choice rather than a new one-off color. The glow
   reads fine against dark theme's near-black bg but looked more like an
   uneven smudge against light theme's near-white bg, so light theme gets
   its own simpler treatment instead of trying to make one gradient work
   for both. */
:root[data-theme="light"] .top-bar {
  background: var(--accent-soft);
}

/* First-visit gate for signed-out visitors -- see WELCOME_SEEN_KEY in
   app.js for when this shows/hides. Deliberately NOT theme-reactive (fixed
   dark background regardless of the site's light/dark toggle), matching
   land.html's own always-dark title-card look rather than styles.css's
   --bg/--accent theme variables, since the whole point is to echo that
   specific first-impression design. z-index is well above every other
   modal's (max elsewhere is 1100) since this needs to gate literally
   everything underneath it. */
.welcome-gate {
  position: fixed;
  inset: 0;
  z-index: 5000;
  display: flex;
  flex-direction: column;
  align-items: center;
  overflow-y: auto;
  background:
    radial-gradient(ellipse 90% 70% at 50% 8%, rgba(168, 85, 247, 0.35), transparent 60%),
    radial-gradient(ellipse 70% 60% at 85% 95%, rgba(74, 13, 143, 0.5), transparent 65%),
    #0d0b12;
  color: #fff;
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

.welcome-gate[hidden] {
  display: none;
}

.welcome-gate-stage {
  position: relative;
  z-index: 1;
  flex: 1 0 auto;
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  padding: 8vh 24px 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 18px;
}

/* Video thumbnails drifting outward from center, DVD-static-screensaver-
   adjacent -- populated in app.js once the catalog's loaded (startWelcome
   ThumbField()), each with its own randomized trajectory/timing set via
   inline custom properties so this one @keyframes rule covers all of them.
   Sits behind .welcome-gate-stage (z-index 0 vs. its 1), ignores clicks
   entirely so it can never intercept the real buttons on top of it. */
.welcome-gate-thumbfield {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.welcome-gate-thumb {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--w, 120px);
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 6px;
  opacity: 0;
  will-change: transform, opacity;
  animation: welcome-thumb-fly var(--dur, 7s) linear infinite;
  animation-delay: var(--delay, 0s);
}

@keyframes welcome-thumb-fly {
  0% { transform: translate(-50%, -50%) translate(0, 0) scale(0.3); opacity: 0; }
  12% { opacity: 0.5; }
  80% { opacity: 0.5; }
  100% { transform: translate(-50%, -50%) translate(var(--tx), var(--ty)) scale(1.3); opacity: 0; }
}

.welcome-gate-logo {
  width: clamp(84px, 14vw, 120px);
  height: clamp(84px, 14vw, 120px);
  border-radius: 50%;
  filter: drop-shadow(0 0 40px rgba(168, 85, 247, 0.55));
}

.welcome-gate-title {
  font-family: "Archivo Black", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 400;
  font-size: clamp(1.8rem, 5vw, 3rem);
  line-height: 1.05;
  color: #f5e300;
  text-wrap: balance;
  margin: 0;
  text-shadow: 0 0 40px rgba(245, 227, 0, 0.25);
}

.welcome-gate-subtitle {
  margin: 0;
  max-width: 42ch;
  font-size: clamp(0.9rem, 1.6vw, 1.05rem);
  line-height: 1.5;
  color: #d8d3e0;
}

.welcome-gate-actions {
  margin-top: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  flex-wrap: wrap;
}

.welcome-gate-google-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 0.9em 1.8em;
  border: none;
  border-radius: 999px;
  background: #fff;
  color: #18181b;
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 0 34px rgba(168, 85, 247, 0.45);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.welcome-gate-google-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 0 46px rgba(168, 85, 247, 0.65);
}

/* Deliberately much quieter than the Google button -- a plain ghost link,
   not a competing pill, so it reads as "the other option" rather than an
   equal choice. */
.welcome-gate-guest-btn {
  padding: 0.9em 1.2em;
  border: none;
  background: none;
  color: #b9b3c9;
  font-size: 0.9rem;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.welcome-gate-guest-btn:hover {
  color: #fff;
}

.welcome-gate-features {
  margin-top: 14px;
  width: 100%;
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  justify-content: center;
}

.welcome-gate-feature {
  flex: 1 1 220px;
  max-width: 260px;
  padding: 20px 18px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(201, 166, 250, 0.3);
  border-radius: 14px;
  box-shadow: 0 0 24px rgba(168, 85, 247, 0.12);
}

.welcome-gate-feature-title {
  font-family: "Archivo Black", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 400;
  font-size: 0.95rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: #c9a6fa;
  margin: 0 0 8px;
}

.welcome-gate-feature-text {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.5;
  color: #b9b3c9;
}

.welcome-gate-note {
  margin: 4px 0 0;
  max-width: 46ch;
  font-size: 0.72rem;
  line-height: 1.5;
  color: #6f6a7c;
}

/* Deliberately its own class rather than reusing .ad-placeholder --
   els.adPlaceholder in app.js resolves via document.querySelector(".ad-
   placeholder"), the FIRST match in the document, and this gate sits
   earlier in the DOM than the real homepage one. Sharing the class had the
   rotating-ad-slideshow code silently target and overwrite this element
   instead of the intended homepage slot. Same visual result (.ad-image
   still carries the actual image styling), just under a name nothing else
   queries for. */
.welcome-gate-ad {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  display: grid;
  justify-items: center;
  width: 100%;
  max-width: 860px;
  margin: 0 auto;
  padding: 0 20px 28px;
}

.top-bar-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Ever-present, YouTube/Spotify-style search bar centered between the logo
   and the account icons -- no more icon-triggered overlay to open/close (and
   no more "closing it forgets to reset the page" bug that came with that).
   flex:1 with max-width + auto margins centers it in the remaining space;
   .top-bar-actions needs no margin-left of its own since this element's
   right auto-margin already pushes it flush to the end. */
.top-bar-search {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  max-width: 480px;
  margin: 0 auto;
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
}

.top-bar-search-icon {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  color: var(--text-muted);
}

.top-bar-search input {
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  background: none;
  outline: none;
  font: inherit;
  font-size: 0.9rem;
  color: var(--text);
}

/* type="search" gets its own native clear-x in WebKit/Blink -- suppress it
   so .top-bar-search-clear is the only one, not a duplicate pair like the
   old overlay had (blue native + white custom). Firefox has no native one
   to suppress. */
.top-bar-search input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

.top-bar-search-clear {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  background: none;
  color: var(--text-muted);
  cursor: pointer;
}

.top-bar-search-clear[hidden] {
  display: none;
}

.top-bar-search-clear svg {
  width: 14px;
  height: 14px;
}

.header-icon-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  border: none;
  background: none;
  color: var(--text);
  cursor: pointer;
}

/* Author styles always beat the browser's UA [hidden]{display:none} rule
   regardless of source order (origin precedence, not specificity) -- this
   class's own unconditional display:flex was silently winning that fight
   for any .header-icon-btn toggled via the hidden attribute (topBarAdminBtn,
   topBarSignInBtn) without this explicit override. */
.header-icon-btn[hidden] {
  display: none;
}

/* Top-bar Vote button -- text, not an icon (explicit request: same
   "Vote" pill everywhere else, not a trophy glyph). .media-vote-btn is
   the shared light-blue/black base; this modifier just sizes it to sit
   comfortably among the 32px-tall icon buttons on either side. */
.top-bar-vote-btn {
  height: 32px;
  padding: 0 14px;
  font-size: 0.85rem;
}

.header-icon-btn svg {
  width: 20px;
  height: 20px;
}

/* .header-icon-btn's 32px hit-box centers a 20px icon, leaving 6px of
   padding the sidebar icons below don't have (theirs is a straight 18px
   from .header-links' own padding) -- this pulls the menu icon left so the
   two rails line up instead of the hamburger looking indented. Mobile pulls
   this button out to an absolute position instead, where it doesn't apply. */
#headerMenuBtn {
  margin-left: -8px;
}

/* Favorites is its own dedicated page (desktop-view-favorites/
   mobile-view-favorites below), not an inline Home section -- hidden here
   at every width by default, with each platform's view block giving it a
   display:block override (higher specificity than this bare #id rule, so
   it wins regardless of stylesheet order) only while that view is active. */
#favoritesStrip {
  display: none;
}

/* Same dedicated-page pattern as #favoritesStrip above, for the Playlists
   page (desktop-view-playlists/mobile-view-playlists below). */
#playlistsPage {
  display: none;
}

/* Same dedicated-page pattern again, for the Profiles directory
   (desktop-view-profiles/mobile-view-profiles below). */
#profilesPage {
  display: none;
}

/* Chip row lists every playlist by name -- tap to switch which one's
   detail view (below) is showing. Mirrors .tv-filter-tab's pill look. */
.playlists-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 10px 0 16px;
}

.playlists-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.8rem;
  padding: 6px 14px;
  border-radius: 999px;
  cursor: pointer;
}

.playlists-chip:hover {
  border-color: var(--accent);
}

.playlists-chip.is-active {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
}

.playlists-chip-count {
  font-size: 0.72rem;
  opacity: 0.7;
}

.playlists-empty {
  color: var(--text-muted);
  text-align: center;
  padding: 32px 12px;
  font-size: 0.88rem;
}

.playlist-detail-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}

.playlist-detail-name {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
}

.playlist-detail-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Secondary outline pill -- also reused as-is for the TV Custom pane's
   Rename button, so keep it generic rather than Playlists-specific. */
.tv-exit-btn {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.76rem;
  font-weight: 700;
  padding: 5px 14px;
  border-radius: 999px;
  cursor: pointer;
}

.tv-exit-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.profiles-signin-note {
  color: var(--text-muted);
  font-size: 0.82rem;
  margin: 0 0 14px;
}

/* Shown only the first time someone opens the editor with no existing
   profile doc (see openProfileEditorForm()) -- a quick "here's what this
   is for" before the blank form, not shown again once they have a profile. */
.profile-editor-intro {
  margin: 0;
  padding: 12px 14px;
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--text);
  font-size: 0.85rem;
  line-height: 1.5;
}

.profiles-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 14px;
}

.profiles-filters input.filter-select {
  flex: 1 1 200px;
  max-width: none;
  cursor: text;
}

/* Same [hidden] cascade gotcha as .profiles-grid[hidden] below -- this
   element's own display:flex rule beats the UA default the moment
   updateProfilesAuthUI() toggles hidden on it. */
.profiles-filters[hidden] {
  display: none;
}

.profiles-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 14px;
}

/* Author styles always beat the browser's UA [hidden]{display:none} rule
   regardless of source order -- same recurring gotcha as
   .header-icon-btn[hidden] / .profile-editor[hidden] / .submit-form[hidden]
   elsewhere. Needed now that updateProfilesAuthUI() toggles this element's
   hidden attribute to gate browsing behind sign-in. */
.profiles-grid[hidden] {
  display: none;
}

.profiles-empty {
  color: var(--text-muted);
  text-align: center;
  padding: 32px 12px;
  font-size: 0.88rem;
}

.profile-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  padding: 14px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
}

.profile-card:hover {
  border-color: var(--accent);
}

.profile-card-photo {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 8px;
  /* visible, not hidden -- the reel badge is positioned to overlap this
     box's edge (see .profile-card-reel-badge), and overflow:hidden here
     was clipping it off. The photo itself still clips via the <img>'s own
     border-radius below; the background stays visibly rounded regardless
     since border-radius rounds the box's own background either way. */
  overflow: visible;
  margin-bottom: 6px;
  background: var(--accent-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.6rem;
  font-weight: 700;
  color: var(--accent);
}

/* Overlapping badge (Facebook-style "online dot" position) flagging that
   this profile has a reel -- mainly meant for the compact mobile list
   layout below, where the photo shrinks too far for a text hint. */
.profile-card-reel-badge {
  position: absolute;
  bottom: -2px;
  right: -2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--surface);
}

/* Wraps name/role/location/bio so the mobile override below can lay this
   whole block out beside the photo instead of stacked under it. */
.profile-card-info {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  min-width: 0;
}

.profile-card-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

.profile-card-name {
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--text);
}

.profile-card-role {
  font-size: 0.72rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.profile-card-bio {
  margin: 4px 0 0;
  font-size: 0.78rem;
  color: var(--text-muted);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  line-height: 1.4;
}

/* Mobile: Facebook-friends-list-style compact rows instead of a grid of
   big square-photo cards -- a photo that fills a 180px grid column reads
   as way too large once minmax(180px,1fr) is down to ~2 columns on a
   phone. Small round avatar + name + one short subtitle line (role) +
   the reel badge, name-search-list dense rather than gallery-dense. */
@media (max-width: 640px) {
  .profiles-grid {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .profile-card {
    flex-direction: row;
    align-items: center;
    gap: 12px;
    padding: 10px 4px;
    border: none;
    border-bottom: 1px solid var(--border);
    border-radius: 0;
    background: none;
  }

  .profile-card:hover {
    border-color: var(--border);
    background: var(--accent-soft);
  }

  .profile-card-photo {
    width: 52px;
    height: 52px;
    aspect-ratio: auto;
    border-radius: 50%;
    margin-bottom: 0;
    flex: 0 0 auto;
    font-size: 1.1rem;
  }

  .profile-card-name {
    font-size: 0.92rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* .profiles-grid-scoped compound selector, not just .profile-card-location
     -- that base rule lives further down the stylesheet (added later, with
     the location-pinning feature) and would otherwise win this tie on
     source order despite matching specificity. */
  .profiles-grid .profile-card-location,
  .profiles-grid .profile-card-bio {
    display: none;
  }
}

.profile-editor {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: 480px;
}

/* Author styles always beat the browser's UA [hidden]{display:none} rule
   regardless of source order (origin precedence, not specificity) -- same
   gotcha as .header-icon-btn[hidden] above. Without this, toggling
   els.profileEditor.hidden in JS (showProfilesBrowse()/
   showProfileEditorView()) never actually hid the form. */
.profile-editor[hidden] {
  display: none;
}

.profile-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text);
}

.profile-field input,
.profile-field select,
.profile-field textarea {
  font: inherit;
  font-weight: 400;
  font-size: 0.88rem;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
}

.profile-field textarea {
  resize: vertical;
}

.profile-photo-preview {
  width: 96px;
  height: 96px;
  border-radius: 8px;
  object-fit: cover;
  align-self: flex-start;
}

.profile-editor-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

.profile-delete-btn {
  border: 1px solid #c4293f;
  background: transparent;
  color: #c4293f;
  font-size: 0.8rem;
  font-weight: 700;
  padding: 7px 16px;
  border-radius: 999px;
  cursor: pointer;
}

.profile-delete-btn:hover {
  background: #c4293f;
  color: #fff;
}

.profile-editor-status {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin: 0;
}

.profile-editor-status.is-error {
  color: #c4293f;
}

.profile-field-optional {
  font-weight: 400;
  color: var(--text-muted);
  text-transform: none;
  letter-spacing: normal;
  font-size: 0.76rem;
}

.profile-location-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.profile-location-label {
  font-weight: 400;
  font-size: 0.8rem;
  color: var(--text-muted);
}

.profile-location-map {
  width: 100%;
  height: 220px;
  border-radius: 8px;
  border: 1px solid var(--border);
  margin-top: 8px;
  /* Leaflet needs its container to have a real size before init -- see
     initProfileLocationMap()'s invalidateSize() call, which fixes up the
     tile layer once this becomes visible (hidden parents report 0 height). */
}

.profile-location-hint {
  margin: 4px 0 0;
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--text-faint);
}

.profile-card-location {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 2px;
  font-size: 0.72rem;
  color: var(--text-muted);
}

.profile-lightbox-map {
  width: 100%;
  height: 160px;
  border-radius: 8px;
  border: 1px solid var(--border);
  margin: 10px 0;
}

.profile-lightbox-head {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 6px;
}

.profile-lightbox-photo {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  border-radius: 8px;
  object-fit: cover;
}

.profile-lightbox-photo-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 1.4rem;
  font-weight: 700;
}

.profile-lightbox-actions {
  margin: 4px 0 10px;
}

.profile-verify-area {
  margin: 10px 0;
}

.verified-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: 0.7rem;
  vertical-align: middle;
}

.profile-request-btn {
  display: inline-block;
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent);
  font-size: 0.82rem;
  font-weight: 700;
  padding: 8px 16px;
  border-radius: 999px;
  cursor: pointer;
  text-decoration: none;
}

.profile-request-btn:hover {
  background: var(--accent);
  color: #fff;
}

.profile-request-btn:disabled {
  border-color: var(--border);
  color: var(--text-muted);
  cursor: default;
  background: transparent;
}

.profile-request-btn.is-active {
  background: var(--accent);
  color: #fff;
}

.profile-requests-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  margin-left: 6px;
  border-radius: 999px;
  background: #c4293f;
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
}

.profile-requests-subhead {
  margin: 20px 0 10px;
  font-size: 0.95rem;
  color: var(--text);
}

.profile-requests-subhead:first-of-type {
  margin-top: 4px;
}

.profile-requests-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.profile-request-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  padding: 12px 14px;
}

.profile-request-row-info {
  min-width: 0;
}

.profile-request-row-name {
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--text);
}

.profile-request-row-message {
  margin: 4px 0 0;
  font-size: 0.8rem;
  color: var(--text-muted);
}

.profile-request-row-status {
  margin-top: 4px;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-faint);
}

.profile-request-row-actions {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 8px;
}

.top-bar-home-link {
  display: flex;
  align-items: center;
  gap: 12px;
  color: inherit;
  text-decoration: none;
}

.app-header-logo {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  flex: 0 0 auto;
}

.top-bar-title {
  font-family: "Archivo Black", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 1.1rem;
  font-weight: 400;
  margin: 0;
  letter-spacing: 0;
  color: var(--brand-yellow);
  white-space: nowrap;
}

/* Shell: sidebar + main content side by side. On desktop the sidebar is a
   real flex sibling, so expanding it (.is-open) naturally pushes the main
   content over -- no JS-driven width/margin math needed. The mobile media
   query turns the sidebar into a hidden-until-opened fullscreen overlay
   instead (see below), at which point it's positioned out of flow and
   .shell's own layout stops mattering for it. */
.shell {
  display: flex;
  align-items: flex-start;
}

.app {
  flex: 1 1 auto;
  min-width: 0;
  padding: 24px 32px 60px;
}

.about-strip {
  margin-bottom: 28px;
  text-align: center;
}

.subtitle {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.95rem;
}

.subtitle-stats {
  font-size: 0.8rem;
  color: var(--text-faint);
  margin-top: 8px;
  min-height: 1.1em;
}

/* Sidebar (desktop): a persistent icon rail by default, widening on
   .is-open to show labels too. */
.header-links {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  flex: 0 0 auto;
  width: 60px;
  box-sizing: border-box;
  overflow-x: hidden;
  overflow-y: auto;
  gap: 2px;
  padding: 16px 10px;
  border-right: 1px solid var(--border);
  position: sticky;
  top: var(--topbar-h, 58px);
  height: calc(100vh - var(--topbar-h, 58px));
  transition: width 0.18s ease;
}

.header-links.is-open {
  width: 230px;
}

.header-links-sep {
  display: none;
}

/* Watch/Connect nav-mode switch -- hidden in the collapsed icon-only rail
   (text buttons don't survive 60px), shown once expanded (desktop
   .is-open, or mobile's overlay, which is always the .is-open state -- see
   applyNavMode()/data-nav-mode in app.js for the actual show/hide logic). */
.nav-mode-switch {
  display: none;
  gap: 4px;
  padding: 4px;
  margin: 0 0 12px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--border);
}

.header-links.is-open .nav-mode-switch {
  display: flex;
}

.nav-mode-btn {
  flex: 1 1 0;
  border: none;
  background: none;
  color: var(--text-muted);
  font-size: 0.78rem;
  font-weight: 700;
  padding: 8px 10px;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
}

.nav-mode-btn.is-active {
  background: var(--accent);
  color: #fff;
}

/* Untagged items (Discord, Settings, sign-in, social, etc.) stay visible in
   both modes -- only nav items explicitly opted into one via
   data-nav-mode="watch"/"connect" get filtered. #headerLinks (an ID) is
   used rather than .header-links so this reliably beats the mobile
   .header-links.is-open .submit-link display:flex rule regardless of
   stylesheet order (equal specificity would otherwise make it a tossup). */
#headerLinks [data-nav-mode="connect"] {
  display: none;
}

#headerLinks.nav-mode-connect [data-nav-mode="watch"] {
  display: none;
}

#headerLinks.nav-mode-connect [data-nav-mode="connect"] {
  display: flex;
}

.submit-link {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 8px;
  border-radius: 8px;
  font-size: 0.85rem;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
}

.submit-link:hover {
  background: var(--accent-soft);
  opacity: 1;
}

.header-links-icon {
  display: flex;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  color: var(--text-muted);
}

.header-links-icon svg {
  width: 100%;
  height: 100%;
}

.header-links-label {
  opacity: 0;
  transition: opacity 0.12s ease;
}

.header-links.is-open .header-links-label {
  opacity: 1;
}

.header-account-btn {
  border: none;
  background: none;
  font-family: inherit;
  cursor: pointer;
  width: 100%;
  text-align: left;
}

.header-account {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 8px;
}

.header-account[hidden] {
  display: none;
}

.header-account-area {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}

/* Settings/Admin/Sign in now live in the top bar on desktop (see
   topBarSettingsBtn/topBarAdminBtn/topBarSignInBtn) -- this sidebar copy
   is mobile-only, reached via the hamburger menu, which has no other path
   to Admin or Sign in. .header-social-area takes over the bottom-of-rail
   spot on desktop instead (same visual treatment, reusing margin-top:auto
   so it's the one that ends up pinned to the bottom once this is hidden). */
.header-social-area {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}

@media (min-width: 641px) {
  .header-account-area {
    display: none;
  }
}

#signInBtn[hidden],
#openAdminBtn[hidden] {
  display: none;
}

.header-avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  flex: 0 0 auto;
  display: block;
}

.header-account-name {
  font-size: 0.8rem;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  transition: opacity 0.12s ease;
}

.header-links.is-open .header-account-name {
  opacity: 1;
}

.header-menu-close {
  display: none;
}

@media (max-width: 640px) {
  /* Top bar: logo/title centered, hamburger pulled out to the top-left
     corner instead of sitting in normal flow beside them. */
  .top-bar {
    position: relative;
    justify-content: center;
    padding: 16px 20px 12px 54px;
    border-bottom: none;
  }

  .header-icon-btn {
    position: absolute;
    top: 12px;
    right: 16px;
  }

  .header-icon-btn svg {
    width: 18px;
    height: 18px;
  }

  /* Bigger tap target for the hamburger specifically -- it's the primary
     way to reach the sidebar on mobile, unlike the other .header-icon-btn
     instances (which are hidden on mobile anyway, see .top-bar-actions
     below). */
  #headerMenuBtn {
    width: 40px;
    height: 40px;
    top: 8px;
    left: 16px;
    right: auto;
    margin-left: 0;
  }

  #headerMenuBtn svg {
    width: 24px;
    height: 24px;
  }

  .app {
    padding: 20px 20px 60px;
  }

  /* Anchoring the menu in normal document flow, right at the top of
     .shell (just below the top bar), guarantees it opens on-screen since
     the user just tapped a button in the top bar right above it -- no
     need for a fixed-position overlay calculation. */
  .shell {
    position: relative;
    display: block;
  }

  /* Full override of the desktop sidebar rail -- mobile has no persistent
     rail, just hidden-until-opened. */
  .header-links {
    display: none;
    width: auto;
    height: auto;
    position: static;
    overflow: visible;
    border-right: none;
    padding: 0;
  }

  .header-links.is-open {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0;
    position: fixed;
    inset: 0;
    width: auto;
    z-index: 1000;
    margin: 0;
    padding: 72px 28px 28px;
    border: none;
    border-radius: 0;
    background: var(--bg);
    text-align: left;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }

  .header-links.is-open .header-menu-close {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    top: 16px;
    right: 16px;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
  }

  .header-links .header-links-sep {
    display: none;
  }

  .header-links.is-open .submit-link {
    display: flex;
    align-items: center;
    gap: 16px;
    width: 100%;
    padding: 18px 2px;
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--text);
    text-decoration: none;
    text-align: left;
    border-bottom: 1px solid var(--border);
    border-radius: 0;
  }

  .header-links.is-open .submit-link:active {
    color: var(--accent);
  }

  .header-links.is-open .header-links-icon {
    width: 26px;
    height: 26px;
    opacity: 1;
  }

  .header-links.is-open .header-links-label {
    opacity: 1;
  }

  .header-account-area,
  .header-social-area {
    margin-top: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding-top: 0;
    border-top: none;
  }

  .header-links.is-open .header-account {
    justify-content: flex-start;
    padding: 18px 2px;
    border-bottom: 1px solid var(--border);
  }

  .header-links.is-open .header-account .submit-link {
    display: inline-flex;
    width: auto;
    padding: 0;
    font-size: 1.05rem;
    border-bottom: none;
  }

  .header-links.is-open .header-account-name {
    opacity: 1;
  }

  /* Settings moved to the bottom nav on mobile -- desktop keeps it inline. */
  #openSettingsBtn {
    display: none;
  }

  /* Home/TV Mode links and the top-bar search icon are desktop-only --
     mobile already has these via the bottom nav. #headerLinks. prefix
     needed so this beats .header-links.is-open .submit-link's
     display:flex on specificity (2 classes) when the mobile menu is open. */
  #headerLinks .sidebar-page-link,
  .top-bar-actions,
  .top-bar-search {
    display: none;
  }
}

.media-strip {
  margin-bottom: 16px;
  padding: 10px 0 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.media-strip-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0 14px 8px;
}

.media-strip-title {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--brand-yellow);
}

.media-strip-collapse-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  font: inherit;
}

.media-strip-chevron {
  font-size: 0.7rem;
  color: var(--text-muted);
  transition: transform 0.15s ease;
}

.media-strip.is-collapsed .media-strip-chevron {
  transform: rotate(-90deg);
}

.media-strip.is-collapsed .media-strip-body {
  display: none;
}

.media-strip.is-collapsed {
  width: fit-content;
  padding-bottom: 10px;
}

.media-strip.is-collapsed .media-strip-head {
  justify-content: flex-start;
  padding-bottom: 0;
}

.media-strip.is-collapsed .media-strip-play-all {
  display: none;
}

.media-strip-play-all {
  flex: 0 0 auto;
  border: none;
  background: none;
  color: var(--accent);
  font-size: 0.78rem;
  font-weight: 700;
  cursor: pointer;
  padding: 2px 4px;
}

.media-strip-head-actions {
  display: flex;
  align-items: center;
  gap: 14px;
}

.media-strip-share-status {
  margin: -4px 14px 12px;
  font-size: 0.78rem;
  color: var(--text-muted);
  overflow-wrap: break-word;
}

.media-strip-play-all:hover {
  text-decoration: underline;
}

.media-strip-body {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 14px;
}

.media-strip-track {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  gap: 10px;
  overflow-x: auto;
  scroll-behavior: smooth;
  scrollbar-width: none;
}

.media-strip-track::-webkit-scrollbar {
  display: none;
}

.media-strip-arrow {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--text-muted);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
}

.media-strip-arrow:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.media-strip-arrow:disabled {
  opacity: 0.35;
  cursor: default;
}

.media-strip-arrow:disabled:hover {
  border-color: var(--border);
  color: var(--text-muted);
}

.media-strip-card {
  flex: 0 0 auto;
  width: 140px;
  cursor: pointer;
}

.media-strip-thumb {
  position: relative;
  width: 140px;
  aspect-ratio: 16 / 9;
  border-radius: 8px;
  overflow: hidden;
  background: var(--accent-soft);
  transition: box-shadow 0.15s ease;
}

.media-strip-card:hover .media-strip-thumb {
  box-shadow: 0 0 10px 0 var(--accent);
}

.sponsored-badge {
  position: absolute;
  top: 6px;
  left: 6px;
  z-index: 1;
  padding: 2px 7px;
  border-radius: 4px;
  background: #f5b942;
  color: #26210a;
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

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

/* One-click Vote button -- Viewer's Choice and Latest Submissions cards
   (see mediaVoteBtnHtml() in app.js; NOT Featured/Favorites, which don't
   pass opts.showVoteButton to createMediaStrip()). Light-blue/black/bold
   to echo the MVG logo's "G" -- see --brand-lightblue above. Base class
   is the inline placement Viewer's Choice uses (same line as the title,
   see .viewers-choice-title-row); Latest Submissions adds the --overlay
   modifier to pin it to the thumbnail's bottom-right corner instead. */
.media-vote-btn {
  flex: 0 0 auto;
  display: inline-block;
  border: none;
  border-radius: 999px;
  padding: 3px 10px;
  background: var(--brand-lightblue);
  color: #000;
  font: inherit;
  font-size: 0.7rem;
  font-weight: 700;
  cursor: pointer;
}

.media-vote-btn--overlay {
  position: absolute;
  right: 6px;
  bottom: 6px;
  z-index: 1;
}

.media-vote-btn:hover {
  filter: brightness(0.92);
}

.media-vote-btn.is-active {
  filter: brightness(0.85);
}

.media-vote-btn:disabled {
  opacity: 0.7;
  cursor: default;
}

.media-strip-song {
  margin-top: 6px;
  overflow: hidden;
  font-size: 0.76rem;
  font-weight: 700;
  color: var(--text);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.media-strip-artist {
  overflow: hidden;
  font-size: 0.64rem;
  color: var(--text-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Favorites-only (createMediaStrip's opts.showDescription) -- a short
   excerpt below the artist line, same clamp treatment as the News cards'
   .blog-latest-excerpt. */
.media-strip-desc {
  margin-top: 4px;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--text-muted);
  white-space: normal;
}

.media-strip-empty {
  padding: 24px 4px;
  color: var(--text-faint);
  font-size: 0.85rem;
  text-align: center;
}

/* Featured and Latest Submissions are the dominant, gallery-style
   sections on desktop -- a real grid instead of the horizontal scroll
   strip Favorites/mobile still use, with bigger thumbnails. */
.media-strip-see-more {
  display: none;
}

/* Holds both See all (desktop-only, hidden via .media-strip-see-more
   itself at narrower widths) and Load more (visible at every width) --
   flexbox centers whichever of the two is actually showing, same as
   plain auto-margins did when Load more was the only child. */
.media-strip-more-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 10px;
}

/* Unlike .media-strip-see-more (desktop-only -- it toggles the grid-vs-
   horizontal-strip layout that only applies at that width), this just
   grows the pool size, so it stays visible at every width. Gray/neutral
   rather than See all's accent color so the two don't read as equally
   weighted actions. */
.media-strip-load-more {
  display: block;
  margin: 0;
  padding: 8px 22px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  font-size: 0.85rem;
  cursor: pointer;
}

.media-strip-load-more:hover {
  border-color: var(--text-muted);
}

.media-strip-load-more:disabled {
  opacity: 0.5;
  cursor: default;
}

/* While actively searching (state.query non-empty -- see render()'s
   is-searching toggle), Featured/Discover/Viewer's Choice just push the
   results the user is looking for further down (Viewer's Choice alone
   runs a full screen tall), so hide them until the query is cleared. */
body.is-searching #featuredStrip,
body.is-searching #discoverSection,
body.is-searching #viewersChoiceSection {
  display: none;
}

@media (min-width: 641px) {
  #latestStrip,
  #featuredStrip,
  #favoritesStrip,
  #playlistsPage,
  #profilesPage {
    border: none;
    background: none;
    padding: 0;
    margin-bottom: 36px;
  }

  #latestStrip .media-strip-title,
  #featuredStrip .media-strip-title,
  #favoritesStrip .media-strip-title {
    font-size: 1.05rem;
  }

  #latestStrip .media-strip-body,
  #featuredStrip .media-strip-body,
  #favoritesStrip .media-strip-body {
    padding: 0;
  }

  #latestStrip .media-strip-arrow,
  #featuredStrip .media-strip-arrow,
  #favoritesStrip .media-strip-arrow {
    display: none;
  }

  #latestStrip .media-strip-track,
  #featuredStrip .media-strip-track,
  #favoritesStrip .media-strip-track {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
    gap: 24px;
    overflow: visible;
  }

  #latestStrip .media-strip-card,
  #featuredStrip .media-strip-card,
  #favoritesStrip .media-strip-card {
    width: auto;
  }

  #latestStrip .media-strip-thumb,
  #featuredStrip .media-strip-thumb,
  #favoritesStrip .media-strip-thumb {
    width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: var(--radius);
  }

  #latestStrip .media-strip-song,
  #featuredStrip .media-strip-song,
  #favoritesStrip .media-strip-song {
    margin-top: 10px;
    font-size: 0.95rem;
    white-space: normal;
  }

  #latestStrip .media-strip-artist,
  #featuredStrip .media-strip-artist,
  #favoritesStrip .media-strip-artist {
    font-size: 0.74rem;
  }

  .media-strip-empty {
    grid-column: 1 / -1;
  }

  /* Capped to exactly two full rows by default so the gallery doesn't push
     everything else several scrolls down -- "See more" lifts the cap.
     --strip-row-h is a card's real measured height (see
     updateStripRowHeightVar in app.js); a flat pixel guess here previously
     cropped mid-caption on the second row because a card's actual height
     depends on the grid's auto-fill column count, which changes with
     viewport width. 24px matches this grid's own row gap. */
  #latestStrip .media-strip-track,
  #featuredStrip .media-strip-track,
  #favoritesStrip .media-strip-track {
    max-height: calc(2 * var(--strip-row-h, 251px) + 24px);
    overflow: hidden;
  }

  #latestStrip.is-expanded .media-strip-track,
  #featuredStrip.is-expanded .media-strip-track,
  #favoritesStrip.is-expanded .media-strip-track {
    max-height: none;
  }

  .media-strip-see-more {
    display: block;
    margin: 14px auto 0;
    padding: 8px 22px;
    border: none;
    border-radius: 999px;
    background: var(--accent);
    color: #fff;
    font-size: 0.85rem;
    cursor: pointer;
  }

  /* Inside .media-strip-more-row (currently just Latest Submissions,
     paired with Load more), the row's own margin-top/gap handle spacing
     instead. */
  .media-strip-more-row .media-strip-see-more {
    margin: 0;
  }

  .media-strip-see-more:hover {
    opacity: 0.9;
  }

  /* Desktop "pages", switched via the sidebar's Home link or the top-bar
     search icon. Home (default, no class) hides the browse tabs/filters/
     jump nav entirely -- they don't belong wedged between Latest and
     Featured just because they're always in the DOM (moved up in the HTML
     to sit right after .about-strip, but still hidden until search mode
     actually needs them). Search reveals that cluster (these are
     :not(.desktop-view-search) rather than a plain default-hide + a
     .desktop-view-search re-show, so they reliably beat .controls/.jumpnav's
     own unconditional display:flex regardless of stylesheet order) and
     hides the Home-only sections instead. */
  body:not(.desktop-view-search) .controls,
  body:not(.desktop-view-search) #filtersGroup,
  body:not(.desktop-view-search) #jumpNavTop {
    display: none;
  }

  /* The top-bar search icon/overlay is the only text-search entry point on
     desktop now -- this inline box stays in the DOM (mobile still uses it,
     see the max-width:640px block) but never renders here. */
  .search-wrap {
    display: none;
  }

  body.desktop-view-search .about-strip,
  body.desktop-view-search #spotlightSidebar,
  body.desktop-view-search #blogLatestSidebar,
  body.desktop-view-search #latestStrip,
  body.desktop-view-search .ad-placeholder,
  body.desktop-view-search #videoEmbed,
  body.desktop-view-search #featuredStrip,
  body.desktop-view-search #discoverSection,
  body.desktop-view-search #viewersChoiceSection {
    display: none;
  }

  /* Favorites: reached via the sidebar's Favorites link, which now calls
     setDesktopView("favorites") instead of opening a modal (see
     #favoritesStrip's base display:none earlier in this file). */
  body.desktop-view-favorites #favoritesStrip {
    display: block;
  }

  body.desktop-view-favorites .about-strip,
  body.desktop-view-favorites #spotlightSidebar,
  body.desktop-view-favorites #blogLatestSidebar,
  body.desktop-view-favorites .ad-placeholder,
  body.desktop-view-favorites #latestStrip,
  body.desktop-view-favorites #videoEmbed,
  body.desktop-view-favorites #featuredStrip,
  body.desktop-view-favorites #discoverSection,
  body.desktop-view-favorites #viewersChoiceSection,
  body.desktop-view-favorites .controls,
  body.desktop-view-favorites #filtersGroup,
  body.desktop-view-favorites #jumpNavTop,
  body.desktop-view-favorites #status,
  body.desktop-view-favorites .results-layout,
  body.desktop-view-favorites #jumpNavBottom {
    display: none;
  }

  /* Playlists: reached via the sidebar's Playlists link, which calls
     setDesktopView("playlists") -- same pattern as Favorites just above. */
  body.desktop-view-playlists #playlistsPage {
    display: block;
  }

  body.desktop-view-playlists .about-strip,
  body.desktop-view-playlists #spotlightSidebar,
  body.desktop-view-playlists #blogLatestSidebar,
  body.desktop-view-playlists .ad-placeholder,
  body.desktop-view-playlists #latestStrip,
  body.desktop-view-playlists #videoEmbed,
  body.desktop-view-playlists #featuredStrip,
  body.desktop-view-playlists #discoverSection,
  body.desktop-view-playlists #viewersChoiceSection,
  body.desktop-view-playlists .controls,
  body.desktop-view-playlists #filtersGroup,
  body.desktop-view-playlists #jumpNavTop,
  body.desktop-view-playlists #status,
  body.desktop-view-playlists .results-layout,
  body.desktop-view-playlists #jumpNavBottom {
    display: none;
  }

  /* Profiles: reached via the sidebar's Profiles link, which calls
     setDesktopView("profiles") -- same pattern as Playlists just above. */
  body.desktop-view-profiles #profilesPage {
    display: block;
  }

  body.desktop-view-profiles .about-strip,
  body.desktop-view-profiles #spotlightSidebar,
  body.desktop-view-profiles #blogLatestSidebar,
  body.desktop-view-profiles .ad-placeholder,
  body.desktop-view-profiles #latestStrip,
  body.desktop-view-profiles #videoEmbed,
  body.desktop-view-profiles #featuredStrip,
  body.desktop-view-profiles #discoverSection,
  body.desktop-view-profiles #viewersChoiceSection,
  body.desktop-view-profiles .controls,
  body.desktop-view-profiles #filtersGroup,
  body.desktop-view-profiles #jumpNavTop,
  body.desktop-view-profiles #status,
  body.desktop-view-profiles .results-layout,
  body.desktop-view-profiles #jumpNavBottom {
    display: none;
  }

}

.controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 20px;
  position: sticky;
  top: var(--topbar-h, 58px);
  background: var(--bg);
  padding: 10px 0;
  z-index: 5;
}

.tabs {
  display: flex;
  gap: 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px;
}

.tab {
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: 0.88rem;
  font-weight: 600;
  padding: 7px 14px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

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

.tab.active {
  background: var(--accent);
  color: #fff;
}

.search-wrap {
  flex: 1 1 240px;
  max-width: 340px;
}

#search {
  width: 100%;
  padding: 9px 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: 0.9rem;
  color: var(--text);
  outline: none;
}

#search:focus {
  border-color: var(--accent);
}

.status {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-muted);
  font-size: 0.85rem;
  margin-bottom: 16px;
  min-height: 1.2em;
}

.status.error {
  color: #b3261e;
}

.spinner-small {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

.loading-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 60px 20px;
  color: var(--text-muted);
  font-size: 0.9rem;
  text-align: center;
}

.spinner {
  width: 28px;
  height: 28px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

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

.filters-toggle-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.filters-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 16px;
}

/* TV Mode borrows #filtersGroup into #tvFiltersSlot for the duration (see
   openTVModal() in app.js) -- but the Home/Search/Favorites view rules above
   (and their mobile equivalents) hide #filtersGroup/.filters-toggle-row by
   default outside of Search, since normally that's the right call. Inside
   the TV modal it should always be visible regardless of whatever view the
   page was in before the modal opened. Two-id selectors so this reliably
   beats those single-id/single-class hide rules on both desktop and mobile. */
#tvFiltersSlot #filtersGroup {
  display: block;
}

/* enterTVFilterMode() hides these via the [hidden] attribute (see
   app.js) -- but .mvg-only-toggle/.info-tip/.filters-toggle all set their
   own unconditional display, which beats the browser's default
   [hidden]{display:none} regardless of specificity/order otherwise. id
   selectors here reliably win. */
#mvgOnlyLabel[hidden],
#mvgOnlyTip[hidden],
#genreTip[hidden],
#filtersToggle[hidden] {
  display: none;
}

/* A ring of 15 short tick buttons around a center hub, standing in for the
   Year dropdown while in TV Mode (see renderTVYearDial() in app.js) --
   picking a decade is more "spin the dial" than "look up an exact value."
   Same :not([hidden]) guard and flex:1 0 100% reasoning as the genre grid
   below. Tick positions are computed in JS as percent left/top and don't
   need any CSS beyond centering the button on that point. */
.tv-year-dial:not([hidden]) {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  flex: 1 0 100%;
  margin: 6px 0 2px;
}

/* Switches which bucket list the ring shows (see activeYearBuckets() in
   app.js) -- three stacked options read as a vertical lever/switch rather
   than a horizontal tab row, distinct from the Genre/Era tabs below. */
.tv-year-lever {
  display: flex;
  flex-direction: column;
  flex: 0 0 auto;
  width: 54px;
  align-self: stretch;
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  background: var(--surface);
}

.tv-year-lever-opt {
  flex: 1;
  border: none;
  border-bottom: 1px solid var(--border);
  background: transparent;
  color: var(--text-muted);
  font-size: 0.62rem;
  font-weight: 700;
  padding: 4px 2px;
  cursor: pointer;
}

.tv-year-lever-opt:last-child {
  border-bottom: none;
}

.tv-year-lever-opt.is-active {
  background: var(--accent);
  color: #fff;
}

.tv-year-dial-ring {
  position: relative;
  width: 100%;
  max-width: 210px;
  aspect-ratio: 1;
  flex: 0 1 auto;
  /* Without this, touch-dragging the dial also scrolls/pans the page
     underneath it on mobile instead of just rotating the selection (see
     the pointerdown/pointermove handlers in app.js). */
  touch-action: none;
  cursor: grab;
}

.tv-year-dial-ring:active {
  cursor: grabbing;
}

/* Follows the pointer while dragging the dial, showing what you'd land on
   -- see tvYearDragTo() in app.js. Fixed positioning (not tied to the
   ring) so it stays right above the finger/cursor even if the drag goes
   outside the ring's own bounds. */
.tv-year-drag-label {
  position: fixed;
  transform: translate(-50%, calc(-100% - 14px));
  padding: 6px 12px;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  font-size: 0.85rem;
  font-weight: 800;
  white-space: nowrap;
  pointer-events: none;
  z-index: 1100;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}

.tv-year-tick {
  position: absolute;
  transform: translate(-50%, -50%);
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.6rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}

.tv-year-tick:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.tv-year-tick.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

/* Big drawn circle (like a clock face) that "Years" granularity's notch
   ring sits on -- see .tv-year-dial-ring.is-fine, toggled in
   renderTVYearDial(). Not shown for Eras/Decades, whose circular tick
   buttons already read as a ring on their own. */
.tv-year-dial-ring.is-fine::before {
  content: "";
  position: absolute;
  inset: 8%;
  border: 1px solid var(--border);
  border-radius: 50%;
  pointer-events: none;
}

/* "Years" granularity can have 80+ ticks -- too many for the 34px circular
   buttons above to stay legible or tappable that close together, so these
   render as small radial minute-notch marks instead, like a clock face
   (each one pre-rotated in app.js to point outward from the center). The
   button itself is just an invisible, generously-sized tap target; the
   actual visible mark is the child .tv-year-tick-mark span, sized/colored
   independently of it (avoids relying on background-clip, which wasn't
   rendering the mark visibly). var(--text-muted) instead of var(--border)
   for the resting color -- --border is too close to the dark-theme
   background to read as a mark at all. */
.tv-year-tick-fine {
  width: 26px;
  height: 30px;
  padding: 0;
  background: none;
  border: none;
  display: flex;
  align-items: flex-start;
  justify-content: center;
}

.tv-year-tick-mark {
  display: block;
  width: 2px;
  height: 10px;
  background: var(--text-muted);
  border-radius: 1px;
}

.tv-year-tick-fine:hover .tv-year-tick-mark {
  background: var(--accent);
}

.tv-year-tick-fine.is-active .tv-year-tick-mark {
  background: var(--accent);
  height: 13px;
}

/* Points at whichever tick is selected (resting at 12 o'clock/"All" when
   nothing is) -- clearer than trying to make one of 80 close-together
   notches visibly stand out on its own. Rotation is set inline in
   renderTVYearDial() per the selected tick's angle. No explicit z-index:
   it comes right before the center hub in the DOM (see renderTVYearDial())
   so the opaque hub -- painted later at the same stacking level -- covers
   the hand's base instead of the hand poking out from underneath it. */
.tv-year-dial-hand {
  position: absolute;
  left: 50%;
  bottom: 50%;
  width: 3px;
  height: 34%;
  background: var(--accent);
  transform-origin: 50% 100%;
  border-radius: 2px 2px 0 0;
  pointer-events: none;
  transition: transform 0.25s ease;
}

.tv-year-dial-center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 96px;
  height: 96px;
  border-radius: 50%;
  border: 2px solid var(--accent);
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 6px;
  text-align: center;
  cursor: pointer;
}

.tv-year-dial-center-label {
  font-size: 0.72rem;
  font-weight: 800;
  color: var(--text);
  line-height: 1.15;
}

.tv-year-dial-center-count {
  font-size: 0.62rem;
  color: var(--text-muted);
}

/* Colorful tappable tiles standing in for the Genre dropdown while in TV
   Mode (see renderTVGenreGrid() in app.js) -- a "browse by vibe" pick fits
   channel-surfing better than a precise <select>. :not([hidden]) guards
   against the [hidden]-attribute cascade bug (an element's own unconditional
   display rule beats the browser's default [hidden]{display:none} UA rule
   regardless of specificity/order -- see CLAUDE.md). flex:1 0 100% makes it
   take a full row of its own within .filters-row's flex-wrap layout. */
.tv-genre-grid:not([hidden]) {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  flex: 1 0 100%;
}

.tv-genre-tile {
  position: relative;
  display: flex;
  flex-direction: row;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px;
  border: 1px solid var(--border);
  border-left: 4px solid var(--accent);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font-size: 0.82rem;
  font-weight: 700;
  padding: 8px 10px;
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
}

.tv-genre-tile-label {
  overflow: hidden;
  text-overflow: ellipsis;
}

.tv-genre-tile:hover {
  border-color: var(--accent);
}

.tv-genre-tile.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.tv-genre-tile-all {
  grid-column: 1 / -1;
  border-left-color: var(--accent);
  justify-content: center;
  align-items: center;
  text-align: center;
}

.tv-genre-tile-all.is-active {
  background: var(--accent);
  border-color: var(--accent);
}

.tv-genre-tile-count {
  flex: 0 0 auto;
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--text-muted);
}

.tv-genre-tile.is-active .tv-genre-tile-count {
  color: rgba(255, 255, 255, 0.85);
}

/* Sits at the bottom of the filters panel, right above the ad banner --
   switches which of tv-genre-grid/tv-year-dial is showing (see
   updateTVFilterTabUI() in app.js) instead of stacking both and forcing
   extra scrolling to reach the ad/player below. */
.tv-filter-tabs:not([hidden]) {
  display: flex;
  gap: 8px;
  flex: 1 0 100%;
  margin-top: 10px;
}

.tv-filter-tab {
  flex: 1 1 0;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.8rem;
  font-weight: 700;
  padding: 8px 0;
  border-radius: 999px;
  cursor: pointer;
}

.tv-filter-tab:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.tv-filter-tab.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.tv-custom-pane:not([hidden]) {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1 0 100%;
  min-height: 120px;
  color: var(--text-faint);
  font-size: 0.85rem;
}

/* Channel Mode's tab pane -- unlike Genre/Era/Custom there's no picker UI
   here, just a status line (tuneChannelMode()/resyncChannelIfNeeded() in
   app.js keep it updated with what's playing/up next) and a live comments
   thread for whatever's currently airing. Selecting the tab tunes in
   immediately, same visual slot as the other tabs' content. */
.tv-channel-pane:not([hidden]) {
  display: flex;
  flex-direction: column;
  flex: 1 0 100%;
  min-height: 120px;
  padding: 0 12px;
}

.tv-channel-status {
  margin: 0 0 10px;
  color: var(--text);
  font-size: 0.9rem;
  text-align: center;
}

.tv-channel-comments {
  width: 100%;
  max-height: 320px;
  overflow-y: auto;
}

.tv-channel-comments .lightbox-comments {
  border-top: none;
  padding-top: 0;
  margin-top: 0;
}

.tv-channel-comments .comment-empty {
  text-align: center;
}

/* Reddit-style horizontal list: thumbnail left, text (title/artist/
   description, in-house preferred, YouTube's own as fallback -- see
   resultDescription() in app.js) right. Deliberately not a thumbnail
   grid like Featured/Discover -- the description is the point here.
   Used by the Search view's results (see resultCardHtml() in app.js). */
.result-card {
  display: flex;
  gap: 14px;
  padding: 12px 4px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}

.result-card:hover {
  background: var(--accent-soft);
}

.result-card:last-child {
  border-bottom: none;
}

.result-card-thumb {
  position: relative;
  flex: 0 0 160px;
  width: 160px;
  padding-top: 90px; /* fixed 16:9 height, not percentage -- keeps the row height stable regardless of card width */
  background: #000;
  border-radius: 8px;
  overflow: hidden;
}

.result-card-thumb img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.result-card-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.result-card-song {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.result-card-artist {
  font-size: 0.8rem;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.result-card-desc {
  margin: 4px 0 0;
  font-size: 0.8rem;
  color: var(--text-muted);
  line-height: 1.4;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.tv-custom-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  padding: 4px 2px;
}

.tv-custom-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  text-align: left;
  font-size: 0.85rem;
}

.tv-custom-item:hover {
  border-color: var(--accent);
}

.tv-custom-item.is-active {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
}

.tv-custom-item-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tv-custom-item-count {
  flex: 0 0 auto;
  font-size: 0.72rem;
  opacity: 0.7;
}

.tv-custom-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1 0 100%;
  min-height: 120px;
  color: var(--text-faint);
  font-size: 0.85rem;
  text-align: center;
  padding: 0 16px;
}

#tvFiltersSlot .filters-toggle-row {
  display: flex;
}

#tvFiltersSlot .filters-row:not([hidden]) {
  display: flex;
}

.category-filters-group {
  display: none; /* disabled for now -- getting cumbersome; other filters remain */
}

.category-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

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

.mvg-only-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.76rem;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
}

.mvg-only-toggle input {
  cursor: pointer;
}

.filter-select {
  flex: 0 0 auto;
  max-width: 190px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.76rem;
  font-weight: 600;
  padding: 5px 10px;
  border-radius: 999px;
  cursor: pointer;
}

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

.chip {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.76rem;
  font-weight: 600;
  padding: 5px 12px;
  border-radius: 999px;
  cursor: pointer;
}

.chip:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.chip.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.ad-placeholder {
  display: grid;
  justify-items: center;
  margin-bottom: 18px;
}

.ad-placeholder[hidden] {
  display: none;
}

.ad-image {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius);
}

.video-embed {
  margin-bottom: 18px;
}

.video-embed-box {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}

.video-embed-hint {
  padding: 24px 16px;
  text-align: center;
  color: var(--text-faint);
  font-size: 0.85rem;
}

.video-embed-hint p {
  margin: 0 0 12px;
}

/* "Channel ready" screen shown once TV Mode has armed a random pick but
   before the viewer presses play -- title/artist stay hidden until then
   (see armTV()/tvStaticMarkup() in app.js). Same 16:9 box as the real
   player (.video-embed-frame) so there's no layout jump when it's replaced. */
.tv-static-wrap {
  position: relative;
  width: 100%;
  padding-top: 56.25%;
  background: #000;
  overflow: hidden;
}

/* A <canvas>, not a styled <div> -- see startStaticNoise()/
   renderStaticNoiseFrame() in app.js. Redrawn with fresh random
   grayscale pixels every frame instead of one fixed noise image sliding
   around (the old approach, which read as "one texture shaking" rather
   than real static once you looked for more than a second). Rendered at
   a small backing resolution and scaled up via CSS, image-rendering:
   pixelated keeping the pixels crisp/chunky instead of blurring them
   into a soft gradient -- that chunkiness is what actually reads as
   analog static. Full opaque already (real per-pixel randomness gives
   genuine black/white contrast on its own, no CSS filter trick needed
   the way the old single fixed texture did). */
.tv-static-noise {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
}

.tv-static-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 68px;
  height: 68px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.92);
  color: #18181b;
  cursor: pointer;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.45);
}

.tv-static-play:hover {
  background: #fff;
}

.tv-static-play-icon {
  font-size: 1.5rem;
  margin-left: 4px; /* optically center the ▶ glyph, which has its own left-side bearing */
}

/* Brief static/tuning flash shown over the player whenever Channel Mode
   switches tracks (see loadChannelTrackAt() in app.js) -- reuses the same
   noise texture as the "channel ready" armed screen above, just as a
   short overlay instead of a standing state, so a channel change reads
   as tuning to a new station rather than a hard cut. */
.tv-tuning-flash {
  position: absolute;
  inset: 0;
  z-index: 5;
  background: #000;
  overflow: hidden;
  pointer-events: none;
  animation: tv-tuning-flash-fade 0.55s ease forwards;
}

@keyframes tv-tuning-flash-fade {
  0% { opacity: 1; }
  65% { opacity: 1; }
  100% { opacity: 0; }
}

.tv-static-hint {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 14px;
  margin: 0;
  text-align: center;
  color: rgba(255, 255, 255, 0.85);
  font-size: 0.78rem;
  font-weight: 600;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}

.info-tip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 15px;
  height: 15px;
  margin-left: 5px;
  border-radius: 50%;
  background: var(--border);
  color: var(--text-muted);
  font-size: 0.68rem;
  font-weight: 700;
  font-style: normal;
  line-height: 1;
  cursor: help;
}

.info-tip::after {
  content: attr(data-tip);
  position: absolute;
  bottom: 130%;
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: 220px;
  padding: 7px 10px;
  border-radius: 6px;
  background: #1a1a1e;
  color: #fff;
  font-size: 0.72rem;
  font-weight: 500;
  line-height: 1.35;
  text-align: left;
  white-space: normal;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
  z-index: 30;
}

.info-tip:hover::after,
.info-tip:focus-visible::after,
.info-tip.is-open::after {
  opacity: 1;
}

.tv-report-link {
  flex: 0 0 auto;
  color: #b3261e;
  font-size: 0.76rem;
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
}

.tv-report-link:hover {
  text-decoration: underline;
}

/* Ever-present play/stop toggle, replacing the old text "Exit" button --
   visible the whole time TV Mode is open (armed or playing), not just
   while playing, so there's always a visible way to tell/change whether
   it's running. .is-on (set in updateTVPowerSwitch()) slides the knob and
   switches the track color. */
.tv-power-switch {
  flex: 0 0 auto;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
}

.tv-power-switch-track {
  position: relative;
  display: block;
  width: 58px;
  height: 26px;
  border-radius: 999px;
  background: #c4293f;
  transition: background 0.15s ease;
}

.tv-power-switch.is-on .tv-power-switch-track {
  background: #2e9e5b;
}

.tv-power-switch-label {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: #fff;
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
}

.tv-power-switch-label-on {
  left: 8px;
}

.tv-power-switch-label-off {
  right: 7px;
  opacity: 1;
}

.tv-power-switch.is-on .tv-power-switch-label-on {
  opacity: 1;
}

.tv-power-switch.is-on .tv-power-switch-label-off {
  opacity: 0;
}

.tv-power-switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  transition: transform 0.15s ease;
}

.tv-power-switch.is-on .tv-power-switch-knob {
  transform: translateX(32px);
}

/* Fav/info/admin edit-delete -- pushed to the end of .filters-toggle-row's
   flex-wrap layout, wrapping onto their own line on narrow screens rather
   than fighting Skip/Report issue/Exit for space. */
.tv-toggle-row-right {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.tv-fav-btn,
.tv-info-btn,
.tv-widen-btn,
.tv-playlist-btn {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
}

.tv-fav-btn:hover {
  border-color: #e0245e;
  color: #e0245e;
}

.tv-fav-btn.is-active {
  border-color: #e0245e;
  background: #e0245e;
  color: #fff;
}

.tv-info-btn:hover,
.tv-widen-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.tv-share-btn.is-active {
  border-color: #2fbf71;
  color: #2fbf71;
}

.tv-playlist-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.tv-crop-btn {
  flex: 0 0 auto;
  height: 28px;
  padding: 0 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}

.tv-crop-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.tv-crop-btn.is-active {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
}

/* .media-vote-btn is the shared light-blue/black base; this just sizes
   it to match the rest of TV Mode's 28px-tall toggle row. */
.tv-vote-btn {
  height: 28px;
  font-size: 0.68rem;
}

.tv-admin-edit-btn,
.tv-admin-delete-btn {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  background: transparent;
}

.tv-admin-edit-btn {
  border: 1px solid var(--accent);
  color: var(--accent);
}

.tv-admin-edit-btn:hover {
  background: var(--accent);
  color: #fff;
}

.tv-admin-delete-btn {
  border: 1px solid #c4293f;
  color: #c4293f;
}

.tv-admin-delete-btn:hover {
  background: #c4293f;
  color: #fff;
}

/* Standing in for the real entry lightbox (which would tear down TV
   playback if opened) -- reuses .lightbox-tag-row/.lightbox-credits/
   .lightbox-desc for a consistent look. Sits between .filters-toggle-row
   and .filters-row, revealed by #tvInfoBtn. */
.tv-info-panel:not([hidden]) {
  padding: 12px 14px;
  margin-bottom: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.tv-info-title {
  margin: 0;
  font-size: 1rem;
  font-weight: 800;
}

.tv-info-subtitle {
  margin: 2px 0 0;
  color: var(--text-muted);
  font-size: 0.85rem;
}

.video-embed-frame {
  position: relative;
  width: 100%;
  padding-top: 56.25%;
  overflow: hidden;
}

.video-embed-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Same visual-only crop as .lightbox-video-frame.is-crop-4-3 -- see that
   rule's comment for the 133.34% derivation. */
.video-embed-frame.is-crop-4-3 {
  padding-top: 75%;
}

.video-embed-frame.is-crop-4-3 iframe {
  left: 50%;
  right: auto;
  width: 133.34%;
  transform: translateX(-50%);
}

.related-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--accent);
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 0.78rem;
  cursor: pointer;
  max-width: 240px;
  text-align: left;
}

.related-btn:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}

/* Thumbnail-bearing variant (Related videos, see lightboxRelatedHtml() in
   app.js) -- negative margin bleeds the thumb flush to the pill's rounded
   edge despite .related-btn's own padding, without needing a second class
   for the plain-text variant profileCreditsHtml() still uses. */
.related-btn-thumb {
  flex: 0 0 auto;
  width: 52px;
  aspect-ratio: 16 / 9;
  border-radius: 999px 4px 4px 999px;
  overflow: hidden;
  background: #000;
  margin: -3px 0 -3px -10px;
}

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

.related-btn-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.group {
  margin-bottom: 32px;
}

.group-heading {
  font-size: 1.05rem;
  font-weight: 700;
  margin: 0 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
  letter-spacing: -0.005em;
  scroll-margin-top: 90px;
}

.group-heading .group-count {
  font-weight: 400;
  color: var(--text-faint);
  font-size: 0.85rem;
  margin-left: 6px;
}

.results-layout {
  display: flex;
  align-items: flex-start;
  gap: 20px;
}

.results-layout .results {
  flex: 1 1 auto;
  min-width: 0;
}

/* A plain section (like Latest Submissions/Featured) now, not a sticky
   sidebar -- lives above Latest Submissions, one row of up to 3 cards. */
.spotlight-sidebar {
  margin-bottom: 28px;
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.spotlight-sidebar[hidden] {
  display: none;
}

.spotlight-sidebar.is-hidden-for-lightbox {
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
}

.spotlight-sidebar-title {
  margin-bottom: 12px;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--brand-yellow);
}

.viewers-choice-title-main {
  font-size: 1.5rem;
}

.viewers-choice-section {
  margin-bottom: 28px;
}

.viewers-choice-section[hidden] {
  display: none;
}

.viewers-choice-top2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 16px;
}

.viewers-choice-rest {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

.viewers-choice-card {
  cursor: pointer;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg);
  transition: box-shadow 0.15s ease;
}

.viewers-choice-card:hover {
  box-shadow: 0 0 10px 0 var(--accent);
}

.viewers-choice-thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
}

.viewers-choice-thumb img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* width/height/font-size come inline per-card (see
   VIEWERS_CHOICE_RANK_SIZES in app.js -- biggest at #1, same idea as the
   Top 5 This Week graphic's progressive sizing). */
.viewers-choice-rank {
  position: absolute;
  top: 8px;
  left: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-weight: 800;
}

.viewers-choice-info {
  padding: 10px 12px 12px;
}

.viewers-choice-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.viewers-choice-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.9rem;
  font-weight: 600;
  line-height: 1.35;
}

.viewers-choice-title.is-top {
  font-size: 1.05rem;
  color: var(--brand-yellow);
}

/* Vote count (left) and Top voter (right, when present) share one line
   now instead of stacking -- justify-content handles the right-alignment
   even when .viewers-choice-voter is entirely absent (nobody who voted
   opted into showing their name, see users/{uid}.showVoterName). */
.viewers-choice-stats-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-top: 3px;
}

.viewers-choice-count {
  font-size: 0.78rem;
  color: var(--text-muted);
}

/* Also used by the Vote modal's leaderboard (voterLineHtml()), stacked
   there rather than in a flex row -- margin-top only zeroed for the
   Viewer's Choice flex-row case below, not the base rule, so the
   leaderboard's own line spacing is untouched. */
.viewers-choice-voter {
  margin-top: 2px;
  font-size: 0.72rem;
  color: var(--text-muted);
}

.viewers-choice-stats-row .viewers-choice-voter {
  margin-top: 0;
}

.spotlight-sidebar-cards {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

/* Matches #latestStrip's own desktop grid (see the @media(min-width:641px)
   block below) exactly -- same minmax/gap -- rather than the horizontal
   scroll strip's 140px card width, which is what visually undersized
   Spotlight next to Latest Submissions before this. */
@media (min-width: 641px) {
  .spotlight-sidebar-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
    gap: 24px;
  }
}

.spotlight-card {
  cursor: pointer;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg);
  transition: box-shadow 0.15s ease;
}

.spotlight-card:hover {
  box-shadow: 0 0 10px 0 var(--accent);
}

.spotlight-card-thumb {
  position: relative;
  width: 100%;
  padding-top: 56.25%;
  background: #000;
}

.spotlight-card-thumb img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.spotlight-card-info {
  padding: 8px 10px;
}

.spotlight-card-song {
  overflow: hidden;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--text);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.spotlight-card-artist {
  overflow: hidden;
  font-size: 0.72rem;
  color: var(--text-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Same .spotlight-card look, own grid -- see the HTML comment above
   #discoverSection for why this is a separate section, not folded into
   Spotlight itself (random draw vs. curated). */
.discover-section {
  margin: 0 0 28px;
}

.discover-section[hidden] {
  display: none;
}

.discover-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}

.discover-see-more {
  display: block;
  margin: 16px auto 0;
  padding: 8px 22px;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 0.85rem;
  cursor: pointer;
}

.discover-see-more:hover {
  opacity: 0.9;
}

/* Shares .spotlight-card's box/thumb/title styling -- these fill in the
   parts unique to a blog-post card (it's a link, and has an excerpt
   instead of an artist line). */
.blog-latest-sidebar {
  margin-bottom: 28px;
}

.blog-latest-sidebar[hidden] {
  display: none;
}

.blog-latest-cards {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
}

.blog-latest-card,
.blog-latest-card:visited {
  display: block;
  text-decoration: none;
  color: inherit;
}

/* Posts 3-6 (once the blog has that many recent ones) -- a compact list
   with a small thumbnail beside the text instead of a full card, so the
   section can grow past 2 without repeating that much visual weight. */
.blog-latest-extra {
  display: flex;
  flex-direction: column;
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.blog-latest-extra[hidden] {
  display: none;
}

.blog-latest-extra-item,
.blog-latest-extra-item:visited {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 0;
  text-decoration: none;
  color: inherit;
  border-bottom: 1px solid var(--border);
}

.blog-latest-extra-item:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.blog-latest-extra-thumb {
  flex: 0 0 auto;
  width: 64px;
  aspect-ratio: 16 / 9;
  border-radius: 4px;
  overflow: hidden;
  background: var(--accent-soft);
}

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

.blog-latest-extra-info {
  min-width: 0;
  flex: 1 1 auto;
}

.blog-latest-extra-title {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text);
}

.blog-latest-excerpt,
.spotlight-card-excerpt {
  margin-top: 4px;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--text-muted);
  white-space: normal;
}

/* All slides share the same grid cell so the container's height tracks the
   tallest ad rather than jumping per-slide, and crossfading is just opacity. */
.ad-slide {
  grid-area: 1 / 1;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.6s ease;
}

.ad-slide.is-active {
  opacity: 1;
  pointer-events: auto;
}

.ad-slide img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
}

.jumpnav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 18px;
}

.jumpnav[hidden] {
  display: none;
}

.hub-links {
  margin-top: 24px;
  display: flex;
  justify-content: center;
  gap: 20px;
  font-size: 0.82rem;
}

.hub-links a {
  color: var(--text-muted);
  text-decoration: none;
}

.hub-links a:hover {
  color: var(--accent);
}

.app-footer {
  margin-top: 12px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  color: var(--text-faint);
  font-size: 0.72rem;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
}

.app-footer-text {
  text-align: center;
}

.cloud-link {
  display: inline-flex;
  justify-self: end;
  text-decoration: none;
  font-weight: 700;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  vertical-align: middle;
}

.land-link {
  justify-self: start;
}

.cloud-link span {
  display: inline-block;
  transition: transform 0.2s ease;
}

.cloud-link span:nth-child(1) { color: #ef5b5b; }
.cloud-link span:nth-child(2) { color: #f4b942; }
.cloud-link span:nth-child(3) { color: #4caf6e; }
.cloud-link span:nth-child(4) { color: #4a90e2; }
.cloud-link span:nth-child(5) { color: #b06fe0; }

.cloud-link:hover span {
  transform: translateY(-3px);
}

.cloud-link:hover span:nth-child(2) { transition-delay: 0.03s; }
.cloud-link:hover span:nth-child(3) { transition-delay: 0.06s; }
.cloud-link:hover span:nth-child(4) { transition-delay: 0.09s; }
.cloud-link:hover span:nth-child(5) { transition-delay: 0.12s; }

.jump-btn {
  min-width: 26px;
  height: 26px;
  padding: 0 4px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  border-radius: 6px;
  font-size: 0.72rem;
  font-weight: 600;
  cursor: pointer;
}

.jump-btn:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent);
}

.jump-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.jump-btn:disabled {
  color: var(--text-faint);
  opacity: 0.4;
  cursor: default;
}

.result-list {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.result-card-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
}

.new-badge {
  display: inline-block;
  background: var(--accent);
  color: #fff;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 4px;
  margin-left: 6px;
  vertical-align: middle;
}

.tag {
  display: inline-block;
  flex: 0 0 auto;
  font-size: 0.72rem;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 999px;
  letter-spacing: 0.01em;
}

.tag-music-video { color: var(--tag-music-video); background: var(--tag-music-video-bg); }
.tag-dance-sequence { color: var(--tag-dance); background: var(--tag-dance-bg); }
.tag-musical-montage { color: var(--tag-montage); background: var(--tag-montage-bg); }
.tag-dvd { color: var(--tag-dvd); background: var(--tag-dvd-bg); }
.tag-live { color: var(--tag-live); background: var(--tag-live-bg); }
.tag-installation { color: var(--tag-installation); background: var(--tag-installation-bg); }
.tag-short { color: var(--tag-short); background: var(--tag-short-bg); }
.tag-docu { color: var(--tag-docu); background: var(--tag-docu-bg); }
.tag-default { color: var(--text-muted); background: #f0f0f2; }

.entry-links {
  display: flex;
  gap: 6px;
  flex: 0 0 auto;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid var(--border);
  color: var(--text-muted);
  background: transparent;
  font: inherit;
  padding: 0;
  cursor: pointer;
}

.icon-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}

.icon-btn svg {
  width: 14px;
  height: 14px;
}


.empty-state {
  color: var(--text-muted);
  text-align: center;
  padding: 40px 0;
  font-size: 0.92rem;
}

.clear-filters-btn {
  margin-top: 12px;
  border: none;
  background: var(--accent);
  color: #fff;
  font-size: 0.82rem;
  font-weight: 700;
  padding: 7px 16px;
  border-radius: 999px;
  cursor: pointer;
}

.clear-filters-btn:hover {
  opacity: 0.9;
}

.filters-toggle-row .clear-filters-btn {
  margin-top: 0;
  padding: 5px 14px;
  font-size: 0.76rem;
}

.save-playlist-btn {
  margin-top: 0;
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent);
  font-size: 0.76rem;
  font-weight: 700;
  padding: 5px 14px;
  border-radius: 999px;
  cursor: pointer;
}

.save-playlist-btn:hover {
  background: var(--accent);
  color: #fff;
}

/* Spelled out on desktop, collapses to a "+" icon button on mobile (see the
   max-width:640px block below) -- same pattern as TV Mode's own
   .tv-playlist-btn "+" button. */
.save-playlist-btn-icon {
  display: none;
}

/* Lives beside Clear filters instead of on the player's own overlay bar --
   easier to reach on mobile without a thumb covering the video. YouTube/
   Vimeo's own native controls are turned off for TV Mode (controls:false
   in every createVideoPlayer() call in loadTVTrack()/loadChannelTrackAt())
   in keeping with the "curated channel, not on-demand seeking" feel --
   Play/Pause and Next sit together in "standard media player" order,
   Mute/Volume alongside. Only shown once TV Mode is actually playing (see
   showTVControls()/teardownTV() in app.js). No seek bar, by design. */
.tv-playback-controls {
  display: flex;
  align-items: center;
  gap: 6px;
}

.tv-playback-btn {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}

/* Author styles always beat the UA [hidden]{display:none} rule -- same
   gotcha as .header-icon-btn[hidden] elsewhere. Without this override,
   .tv-playback-btn's own unconditional display:flex would keep a hidden
   button visible. */
.tv-playback-btn[hidden] {
  display: none;
}

.tv-playback-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}

.tv-volume-slider {
  flex: 0 0 auto;
  width: 70px;
  accent-color: var(--accent);
}

.tv-volume-slider[hidden] {
  display: none;
}

/* Sits right below the video, not overlaid on it -- native controls
   (which used to include a seek bar) are off in TV Mode now, see
   #tvModal .video-embed-frame iframe's pointer-events:none above.
   Polled from the player, not event-driven -- see startTVSeekPoll() in
   app.js. Hidden in Channel Mode (same reasoning as no Skip button
   there -- seeking would only desync this viewer from everyone else). */
.tv-seek-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}

.tv-seek-row[hidden] {
  display: none;
}

.tv-seek-time {
  flex: 0 0 auto;
  font-size: 0.72rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.tv-seek-bar {
  flex: 1 1 auto;
  min-width: 0;
  accent-color: var(--accent);
}

/* Small, semi-transparent channel-bug watermark -- always present while
   something's actually playing (part of TV_PLAYER_TARGET_INNER_HTML in
   app.js, so it only exists in the DOM during real playback, not the
   armed "tap to play" screen -- no separate hidden toggle needed). */
.tv-channel-logo {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 2;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  opacity: 0.75;
  pointer-events: none;
}

/* MTV-style lower third -- artist/"song"/director stack shown briefly at
   the start of a track and again in its last few seconds (see
   showTVLowerThird()/updateTVSeekUI() in app.js), not a standing title
   bar. Real broadcast lower thirds are bare bold text with a drop
   shadow for legibility, not a card/box -- no background here on
   purpose. Inset from the edges (title-safe margins) rather than
   flush/edge-to-edge. Fades out via .is-fading rather than disappearing
   instantly.
   4:3 crop mode (.video-embed-frame.is-crop-4-3) only changes the
   FRAME's own box (padding-top 56.25% -> 75%, i.e. taller) and scales
   the iframe *inside* it -- it can't reach this element at all (a
   sibling of the iframe, positioned against the frame itself, not
   nested inside that cross-origin document), so these percentage
   insets already track the frame's new shape with no extra rule
   needed. Kept as an explicit selector below anyway so that stays true
   on purpose, not by accident, if the crop mechanism ever changes. */
.tv-lower-third {
  position: absolute;
  left: 5%;
  right: 5%;
  bottom: 6%;
  z-index: 2;
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.4s ease;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.95), 0 0 12px rgba(0, 0, 0, 0.7);
}

.tv-lower-third[hidden] {
  display: none;
}

.tv-lower-third.is-fading {
  opacity: 0;
}

.tv-lower-third-artist,
.tv-lower-third-song {
  color: #fff;
  font-size: 1.3rem;
  font-weight: 700;
  line-height: 1.3;
}

.tv-lower-third-director {
  margin-top: 2px;
  color: rgba(255, 255, 255, 0.92);
  font-size: 1.05rem;
  font-weight: 600;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.lightbox[hidden] {
  display: none;
}

.lightbox-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.7);
}

.lightbox-panel {
  position: relative;
  background: var(--surface);
  border-radius: var(--radius);
  max-width: 720px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  overflow-x: hidden;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
  transition: max-width 0.15s ease;
}

.lightbox-panel.size-large {
  max-width: 1080px;
}

.lightbox-close {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 2;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
}

.lightbox-close:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.lightbox-title-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.lightbox-title-actions {
  display: flex;
  flex: 0 0 auto;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 10px;
  padding-top: 2px;
}

.lightbox-fav-btn {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
}

.lightbox-fav-btn:hover {
  border-color: #e0245e;
  color: #e0245e;
}

.lightbox-fav-btn.is-active {
  border-color: #e0245e;
  background: #e0245e;
  color: #fff;
}

.lightbox-share-btn {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
}

.lightbox-share-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.lightbox-share-btn.is-active {
  border-color: #2fbf71;
  color: #2fbf71;
}

.lightbox-widen-btn {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}

.lightbox-widen-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.lightbox-crop-btn {
  flex: 0 0 auto;
  height: 26px;
  padding: 0 9px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}

.lightbox-crop-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.lightbox-crop-btn.is-active {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
}

/* Light-blue/black/bold like every other Vote button on the site
   (.media-vote-btn), not the neutral pill look the rest of this toolbar
   row uses -- and deliberately bigger/bolder than its siblings so it
   stands out as the one action here with a real consequence. */
.lightbox-vote-btn {
  flex: 0 0 auto;
  height: 34px;
  padding: 0 16px;
  border-radius: 999px;
  border: none;
  background: var(--brand-lightblue);
  color: #000;
  font-size: 0.9rem;
  font-weight: 800;
  line-height: 1;
  cursor: pointer;
}

.lightbox-vote-btn:hover {
  filter: brightness(0.92);
}

.lightbox-vote-btn.is-active {
  filter: brightness(0.85);
}

.lightbox-playlist-btn {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
}

.lightbox-playlist-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* One shared floating popover, positioned via getBoundingClientRect() in
   openAddToPlaylistPopover() (app.js) each time it's opened for a given
   video -- above the lightbox/TV modal (z-index 1000) since it can be
   triggered from either. */
.add-playlist-popover {
  position: fixed;
  z-index: 1100;
  width: 260px;
  max-height: 320px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
  padding: 12px;
}

.add-playlist-popover[hidden] {
  display: none;
}

.add-playlist-popover-title {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text);
  padding-right: 20px;
  margin-bottom: 8px;
}

.add-playlist-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 22px;
  height: 22px;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.add-playlist-close:hover {
  color: var(--text);
}

.add-playlist-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 180px;
  overflow-y: auto;
}

.add-playlist-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  border: none;
  background: transparent;
  color: var(--text);
  font-size: 0.82rem;
  padding: 7px 8px;
  border-radius: 6px;
  cursor: pointer;
  text-align: left;
}

.add-playlist-item:hover {
  background: var(--accent-soft);
}

.add-playlist-item-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.add-playlist-item-check {
  flex: 0 0 auto;
  width: 16px;
  color: var(--accent);
  visibility: hidden;
}

.add-playlist-item.is-in .add-playlist-item-check {
  visibility: visible;
}

.add-playlist-new {
  display: flex;
  gap: 6px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.add-playlist-new input {
  flex: 1 1 auto;
  min-width: 0;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.8rem;
  padding: 6px 8px;
  border-radius: 6px;
}

.add-playlist-new button {
  flex: 0 0 auto;
  border: none;
  background: var(--accent);
  color: #fff;
  font-size: 0.78rem;
  font-weight: 700;
  padding: 6px 12px;
  border-radius: 6px;
  cursor: pointer;
}

.add-playlist-new button:hover {
  opacity: 0.9;
}

.lightbox-admin-edit-btn {
  flex: 0 0 auto;
  padding: 4px 10px;
  border-radius: 12px;
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent);
  font-size: 0.76rem;
  font-weight: 700;
  line-height: 1.4;
  cursor: pointer;
  white-space: nowrap;
}

.lightbox-admin-edit-btn:hover {
  background: var(--accent);
  color: #fff;
}

.lightbox-admin-delete-btn {
  flex: 0 0 auto;
  padding: 4px 10px;
  border-radius: 12px;
  border: 1px solid #c4293f;
  background: transparent;
  color: #c4293f;
  font-size: 0.76rem;
  font-weight: 700;
  line-height: 1.4;
  cursor: pointer;
  white-space: nowrap;
}

.lightbox-admin-delete-btn:hover {
  background: #c4293f;
  color: #fff;
}

.lightbox-admin-cover-btn {
  flex: 0 0 auto;
  padding: 4px 10px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-muted);
  font-size: 0.76rem;
  font-weight: 700;
  line-height: 1.4;
  cursor: pointer;
  white-space: nowrap;
}

.lightbox-admin-cover-btn:hover {
  border-color: var(--text-muted);
  color: var(--text);
}

.lightbox-admin-cover-btn:disabled {
  opacity: 0.6;
  cursor: default;
}

.lightbox-report-menu-btn {
  flex: 0 0 auto;
  height: 26px;
  padding: 0 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: #b3261e;
  font-size: 0.72rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}

.lightbox-report-menu-btn:hover {
  border-color: #b3261e;
}

.lightbox-report-menu {
  position: fixed;
  z-index: 1200;
  display: flex;
  flex-direction: column;
  min-width: 170px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
  padding: 6px;
}

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

.lightbox-report-menu-item {
  display: block;
  border: none;
  background: none;
  padding: 8px 10px;
  border-radius: calc(var(--radius) - 4px);
  color: var(--text);
  font-size: 0.82rem;
  font-weight: 600;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.lightbox-report-menu-item:hover {
  background: var(--bg-subtle, rgba(127, 127, 127, 0.12));
}

.lightbox-panel .ad-placeholder {
  margin: 0;
  padding: 12px;
  border-bottom: 1px solid var(--border);
}

/* TV Mode's ad sits last (player, then filters, then ad -- see the
   #tvModal markup) so the video is the first thing visible, rather than
   first (like the video-detail lightbox's ad, which keeps the rule above) --
   a border below a trailing element would separate it from nothing. */
#tvAdPlaceholder {
  border-bottom: none;
  border-top: 1px solid var(--border);
}

/* Pins the ad to the very bottom of the modal instead of sitting right
   below the filters -- mostly matters on mobile, where .lightbox-panel is
   forced to full viewport height and would otherwise leave a dead gap
   between the ad and the bottom of the screen. min-height:100% only
   resolves to something on an ancestor with an actual height (mobile's
   height:100% panel); on desktop, where the panel just hugs its content,
   this is a no-op and the ad stays exactly where it already was. */
#tvModal .lightbox-content {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  /* Breathing room between the video/buttons and the modal's own edge --
     everything used to sit flush against it. Kept as a fixed size rather
     than shrinking any of the buttons/tiles to make space; see the
     min-width bump on desktop below to make up the difference. */
  padding: 14px;
}

@media (min-width: 641px) {
  #tvModal .lightbox-panel {
    max-width: 780px;
  }

  /* Without this, the widen button's .size-large class (see
     applyTVSize() in app.js) never actually widens anything in TV Mode --
     #tvModal .lightbox-panel (an ID selector) always outranks the plain
     .lightbox-panel.size-large class selector above on specificity alone,
     regardless of which one comes later in the file or which class is
     toggled. Needs its own #tvModal-qualified override to win back. */
  #tvModal .lightbox-panel.size-large {
    max-width: 1080px;
  }
}

#tvModal #tvAdPlaceholder {
  margin-top: auto;
}

/* createVideoPlayer()'s controls:false (see loadTVTrack()/
   loadChannelTrackAt() in app.js) hides YouTube's native control BAR,
   but YouTube has forced its title/channel-name overlay on hover/pause
   regardless of any playerVars since deprecating showinfo (2018) and
   modestbranding (2023) -- there's no embed setting left that suppresses
   it (see the 4:3 crop note above for the same underlying limitation).
   Since TV Mode's own custom controls (play/pause/seek/volume) already
   replace everything a viewer needs, the fix is to stop the iframe from
   ever receiving the mouse at all -- with nothing to hover, YouTube's JS
   never has a reason to show that overlay. Scoped to TV Mode only; the
   video-detail lightbox keeps native, clickable YouTube controls. */
#tvModal .video-embed-frame iframe {
  pointer-events: none;
}

.lightbox-video-frame {
  position: relative;
  width: 100%;
  padding-top: 56.25%;
  background: #000;
  overflow: hidden;
}

.lightbox-video-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Visual-only crop toggle (applyLightboxCrop() in app.js) -- the source
   video is still the real 16:9 YouTube embed, just scaled up and clipped
   by this frame's overflow:hidden. 133.34% is the iframe width needed so
   its full (unchanged) height maps to a 4:3 box: (16/9) / (4/3) = 4/3.
   The scaled-up iframe is still a real 16:9 box (just bigger), so
   YouTube's own UI isn't distorted -- but chrome pinned to the iframe's
   edges (bottom control bar, top title/channel overlay) gets pushed past
   the visible clip region and cut off along with the picture. The bottom
   bar is removed via controls:0 in playerVars whenever crop is active
   (see the three `new YT.Player(...)` call sites in app.js). The top
   title/channel overlay can't be suppressed the same way -- YouTube
   deprecated the showinfo/modestbranding params years ago and always
   forces that overlay on load/hover/pause, so its partial cropping in
   4:3 mode is an accepted, inherent limitation of this CSS-only
   scale+clip technique. */
.lightbox-video-frame.is-crop-4-3 {
  padding-top: 75%;
}

.lightbox-video-frame.is-crop-4-3 iframe {
  left: 50%;
  right: auto;
  width: 133.34%;
  transform: translateX(-50%);
}

.lightbox-video-empty {
  padding: 40px 20px;
  text-align: center;
  color: var(--text-faint);
  font-size: 0.88rem;
}

/* Mini player (PIP) -- see the "Lightbox video mini player" section in
   app.js. .lightbox-pip-frame is a PERMANENT direct child of
   document.body (created once per video, never reparented) instead of a
   normal-flow descendant here -- moving a cross-origin YouTube/Vimeo
   iframe to a new DOM parent reloads it (confirmed directly against the
   YouTube IFrame API: currentTime resets to 0 and playback drops to
   buffering, even with the exact same iframe node and an unchanged src),
   so continuous playback across scroll-PIP/backdrop-PIP/reopen requires
   the frame to just stay put and only ever be moved via CSS. While
   "docked" (the normal state) its position:fixed coordinates are synced
   in JS to this inert placeholder slot's own on-screen position, so it
   LOOKS embedded in the lightbox's normal flow without actually being a
   DOM descendant of anything that could hide/rebuild/move it. */
.lightbox-video-slot {
  position: relative;
  width: 100%;
  padding-top: 56.25%;
}

/* Deliberately its own class, NOT .lightbox-video-frame -- that class is
   still used by the profile lightbox's differently-designed embedded reel
   (normal document flow, sized via the padding-top:56.25% trick), and this
   element must never inherit any of that. It first shared the name and the
   leftover padding-top silently added onto this frame's explicit height
   below, since padding adds to height under content-box sizing rather than
   being overridden by it. */
.lightbox-pip-frame {
  position: fixed;
  z-index: 1001;
  background: #000;
  overflow: hidden;
}

.lightbox-pip-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Explicit height (not aspect-ratio) -- with top/left left auto and only
   right/bottom/width set, some browsers resolve height:auto here as
   "stretch to fill the viewport" rather than deriving it from aspect-ratio
   (confirmed directly: computed height came out matching the viewport, not
   320px's 16:9). A fixed 180px (320 * 9/16) sidesteps that ambiguity. */
.lightbox-pip-frame.is-pip {
  right: 16px;
  bottom: 16px;
  width: 320px;
  height: 180px;
  max-width: calc(100vw - 32px);
  z-index: 1250;
  border-radius: 10px;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.55);
}

.lightbox-pip-close,
.lightbox-pip-reopen,
.lightbox-pip-mute {
  position: absolute;
  z-index: 2;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.lightbox-pip-frame:not(.is-pip) .lightbox-pip-close,
.lightbox-pip-frame:not(.is-pip) .lightbox-pip-reopen,
.lightbox-pip-frame:not(.is-pip) .lightbox-pip-mute {
  display: none;
}

.lightbox-pip-close {
  top: 6px;
  right: 6px;
}

.lightbox-pip-reopen {
  top: 6px;
  left: 6px;
  font-size: 0.9rem;
}

.lightbox-pip-mute {
  bottom: 6px;
  right: 6px;
  font-size: 0.85rem;
}

/* Only the scroll-triggered flavor (still part of an open lightbox, no
   reopen affordance needed -- scrolling back up is the return path) hides
   the reopen button; the detached flavor (floating over a closed lightbox)
   shows all three. */
.lightbox-pip-frame.is-pip:not(.is-pip-detached) .lightbox-pip-reopen {
  display: none;
}

.lightbox-pip-close:hover,
.lightbox-pip-reopen:hover,
.lightbox-pip-mute:hover {
  background: rgba(0, 0, 0, 0.85);
}

.lightbox-fallback-link {
  display: inline-block;
  margin-top: 12px;
  border: none;
  background: var(--accent);
  color: #fff;
  font-size: 0.82rem;
  font-weight: 700;
  padding: 7px 16px;
  border-radius: 999px;
  text-decoration: none;
}

.lightbox-fallback-link:hover {
  opacity: 0.9;
}

.lightbox-body {
  padding: 20px;
}

.lightbox-title {
  margin: 0 0 4px;
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--brand-yellow);
}

.lightbox-subtitle {
  margin: 0 0 12px;
  color: var(--text-muted);
  font-size: 0.9rem;
}

.lightbox-tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
}

.lightbox-credits {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 5px 12px;
  margin: 0 0 16px;
  font-size: 0.84rem;
}

.lightbox-credits dt {
  color: var(--text-faint);
  font-weight: 600;
}

.lightbox-credits dd {
  margin: 0;
  color: var(--text);
}

.lightbox-desc {
  font-size: 0.92rem;
  line-height: 1.6;
  white-space: pre-line;
  margin: 0 0 16px;
}

.lightbox-desc.placeholder {
  color: var(--text-faint);
  font-style: italic;
}

.lightbox-card-link {
  display: block;
  width: fit-content;
  margin: -8px 0 16px;
  color: var(--text-muted);
  font-size: 0.8rem;
  text-decoration: underline;
}

.lightbox-card-link:hover {
  color: var(--accent);
}

.lightbox-card-link.is-busy {
  pointer-events: none;
  opacity: 0.6;
}

/* Floating hover preview of the trading card -- see showCardPreviewFor() in
   app.js. Appended straight to <body> (position: fixed) rather than nested
   in the lightbox, so it's never clipped by the lightbox panel's own
   overflow/scroll -- positioned in JS from the link's own bounding rect. */
.card-preview-popup {
  position: fixed;
  z-index: 1200;
  width: 220px;
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
  pointer-events: none;
}

.card-preview-popup canvas {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 8px;
}

.card-preview-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4 / 5;
  color: var(--text-muted);
  font-size: 0.8rem;
}

.lightbox-links {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
}

.lightbox-related {
  border-top: 1px solid var(--border);
  padding-top: 14px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  font-size: 0.82rem;
}

.lightbox-related-label {
  color: var(--text-muted);
  margin-right: 4px;
}

.related-btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.lightbox-comments {
  border-top: 1px solid var(--border);
  padding-top: 14px;
  margin-top: 14px;
}

.lightbox-comments-title {
  font-size: 0.95rem;
  font-weight: 700;
  margin: 0 0 10px;
}

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

.comment-form textarea {
  font: inherit;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  resize: vertical;
}

.comment-form button {
  align-self: flex-end;
}

.comment-signin-note {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-muted);
  font-size: 0.85rem;
  margin-bottom: 14px;
}

.comment-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.comment-empty {
  color: var(--text-faint);
  font-size: 0.85rem;
}

.comment-item-meta {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-bottom: 2px;
}

.comment-item-author {
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--text);
}

.comment-item-time {
  color: var(--text-faint);
  font-size: 0.72rem;
}

.comment-delete-btn {
  margin-left: auto;
  border: none;
  background: none;
  color: var(--text-faint);
  cursor: pointer;
  padding: 2px;
  display: inline-flex;
}

.comment-delete-btn:hover {
  color: var(--accent);
}

.comment-item-text {
  color: var(--text);
  font-size: 0.88rem;
  overflow-wrap: break-word;
  white-space: pre-wrap;
}

.submit-panel {
  max-width: 720px;
  max-height: 90vh;
  overflow-y: auto;
  padding: 28px;
}

.settings-panel {
  max-width: 460px;
  padding: 28px;
}

.vote-panel {
  max-width: 640px;
  padding: 28px;
}

.vote-title {
  margin: 0 0 6px;
  font-size: 1.2rem;
  color: var(--brand-yellow);
}

.vote-hint {
  margin: 0 0 18px;
  color: var(--text-muted);
  font-size: 0.9rem;
}

.vote-leaderboard-title {
  margin: 20px 0 4px;
  font-size: 0.95rem;
  color: var(--text);
}

.vote-signin-prompt {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.vote-signin-prompt[hidden] {
  display: none;
}

.vote-signin-prompt p {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.85rem;
}

.recent-panel {
  max-width: 420px;
  padding: 24px;
  max-height: 80vh;
}

.podcast-panel {
  max-width: 640px;
  padding: 28px;
  max-height: 90vh;
  overflow-y: auto;
}

.podcast-embed-wrap {
  margin: 14px 0;
}

.podcast-embed-wrap iframe {
  display: block;
  width: 100%;
  max-width: 624px;
  height: 351px;
}

.podcast-caption {
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

.dm-panel {
  max-width: 480px;
  padding: 24px;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
}

.dm-messages {
  flex: 1 1 auto;
  min-height: 200px;
  max-height: 50vh;
  overflow-y: auto;
  margin: 14px 0;
  padding-right: 4px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.dm-message {
  display: flex;
  justify-content: flex-start;
}

.dm-message.is-mine {
  justify-content: flex-end;
}

.dm-message-bubble {
  max-width: 80%;
  padding: 8px 12px;
  border-radius: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  font-size: 0.88rem;
  line-height: 1.4;
  white-space: pre-wrap;
  word-break: break-word;
}

.dm-message.is-mine .dm-message-bubble {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}

.dm-composer {
  display: flex;
  gap: 8px;
  align-items: flex-end;
}

.dm-composer textarea {
  flex: 1 1 auto;
  resize: none;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 0.88rem;
}

.dm-composer button {
  flex: 0 0 auto;
}

.admin-panel {
  max-width: 560px;
  padding: 24px;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
}

.admin-body {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.admin-search {
  margin: 14px 0 0;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
}

.admin-status {
  margin: 10px 0 0;
  font-size: 0.82rem;
  color: var(--text-muted);
}

.admin-status.is-error {
  color: #c4293f;
}

.admin-entries-list {
  margin-top: 12px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* [hidden] and .admin-entries-list are equal-specificity selectors, so
   without this the class's own `display: flex` (loaded after the browser's
   UA stylesheet) wins the tie and overrides [hidden]'s `display: none` --
   setting .hidden = true on this element would silently do nothing. */
.admin-entries-list[hidden] {
  display: none;
}

.admin-empty {
  color: var(--text-muted);
  font-size: 0.9rem;
}

.admin-row-btn.is-active {
  border-color: var(--accent);
  color: var(--accent);
}

.admin-grid-hint {
  margin: 10px 0 0;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.78rem;
  line-height: 1.5;
}

.admin-grid-wrap {
  margin-top: 12px;
  margin-bottom: 20px; /* room for .admin-grid-bottom-scrollbar so it doesn't sit over the last row */
  overflow-x: auto;
  overflow-y: auto;
}

.admin-grid-pager {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 8px;
  flex: 0 0 auto;
}

.admin-grid-pager-label {
  color: var(--text-muted);
  font-size: 0.8rem;
}

.admin-grid-page-numbers {
  display: flex;
  align-items: center;
  gap: 2px;
}

.admin-grid-page-btn {
  min-width: 26px;
  padding: 4px 6px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
}

.admin-grid-page-btn:hover {
  border-color: var(--accent);
}

.admin-grid-page-btn.is-active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 700;
}

.admin-grid-page-ellipsis {
  color: var(--text-faint);
  padding: 0 2px;
  font-size: 0.78rem;
}

/* Mirrors .admin-grid-wrap's horizontal scrollbar at the bottom of the
   viewport (manage-entries.js keeps them scroll-synced) so it's usable
   without scrolling all the way down to the table's own bottom edge. */
.admin-grid-bottom-scrollbar {
  position: fixed;
  bottom: 0;
  height: 14px;
  overflow-x: scroll;
  overflow-y: hidden;
  background: var(--surface);
  border-top: 1px solid var(--border);
  z-index: 40;
}

.admin-grid-bottom-scrollbar > div {
  height: 1px;
}

.admin-grid {
  border-collapse: collapse;
  font-size: 0.82rem;
}

.admin-grid th {
  position: sticky;
  top: 0;
  padding: 6px 8px;
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  text-align: left;
  white-space: nowrap;
  border-bottom: 1px solid var(--border);
}

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

.admin-grid th.admin-grid-sortable:hover {
  color: var(--accent);
}

.admin-grid td {
  padding: 3px 4px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

.admin-grid-rownum {
  color: var(--text-muted);
  white-space: nowrap;
}

.admin-grid-rownum-btn {
  border: none;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-size: inherit;
  cursor: pointer;
  padding: 0;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.admin-grid-link-cell {
  display: flex;
  align-items: center;
  gap: 4px;
}

.admin-grid-open-link {
  flex: 0 0 auto;
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.95rem;
}

.admin-grid-open-link:hover {
  color: var(--accent);
}

.admin-grid td input[type="text"],
.admin-grid td select {
  width: 130px;
  padding: 5px 6px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: inherit;
}

.admin-grid td input[type="text"]:hover,
.admin-grid td select:hover {
  border-color: var(--border);
}

.admin-grid td input[type="text"]:focus,
.admin-grid td select:focus {
  border-color: var(--accent);
  background: var(--surface);
  outline: none;
}

.admin-grid .admin-grid-year input {
  width: 56px;
}

.admin-grid .admin-grid-wide input {
  width: 220px;
}

.admin-grid td select {
  width: 108px;
}

/* Description/Flavor Text -- collapsed to a truncated single line by
   default (same footprint as a wide text input), expands into a proper
   multi-line box on focus/click so the full text is actually readable
   and editable, then collapses back on blur. */
.admin-grid td textarea.admin-grid-expand {
  width: 220px;
  height: 28px;
  padding: 5px 6px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: inherit;
  resize: none;
  overflow: hidden;
  white-space: nowrap;
  vertical-align: top;
}

.admin-grid td textarea.admin-grid-expand:hover {
  border-color: var(--border);
}

.admin-grid td textarea.admin-grid-expand:focus {
  position: relative;
  z-index: 5;
  width: 340px;
  height: 140px;
  white-space: pre-wrap;
  overflow: auto;
  border-color: var(--accent);
  background: var(--surface);
  outline: none;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
}

.admin-grid-check {
  text-align: center;
}

.admin-grid-check input {
  width: 16px;
  height: 16px;
  cursor: pointer;
}

@keyframes admin-grid-save-flash {
  from { background: var(--accent-soft); }
  to { background: transparent; }
}

.admin-grid td input.save-ok,
.admin-grid td select.save-ok {
  animation: admin-grid-save-flash 0.8s ease-out;
}

@keyframes admin-grid-save-flash-error {
  from { background: #c4293f; color: #fff; }
  to { background: transparent; }
}

.admin-grid td input.save-error,
.admin-grid td select.save-error {
  animation: admin-grid-save-flash-error 1.2s ease-out;
}

.admin-fill-links-card {
  margin-top: 14px;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Same footgun as .admin-entries-list[hidden] above -- [hidden] and this
   class are equal-specificity selectors, so without this override the
   class's own `display: flex` (loaded after the browser's UA stylesheet)
   wins the tie and setting .hidden = true silently does nothing. */
.admin-fill-links-card[hidden] {
  display: none;
}

.admin-fill-links-title {
  font-size: 1.05rem;
  font-weight: 700;
}

.admin-fill-links-sub {
  font-size: 0.85rem;
  color: var(--text-muted);
}

.admin-fill-links-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* Weekly Intake page (admin-intake.html) -- reuses .admin-fill-links-card
   for the controls bar and .admin-badge for the "already in catalog"/
   "Short" flags; this is just the result-card grid it doesn't have an
   existing equivalent for. */
.intake-days-input {
  width: 60px;
  display: inline-block;
  margin-left: 6px;
}

.intake-results {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 14px;
}

.intake-result-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
}

.intake-result-card.is-duplicate {
  opacity: 0.5;
}

.intake-result-check {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  cursor: pointer;
}

.intake-result-thumb-link {
  position: relative;
  flex: 0 0 auto;
  display: block;
  border-radius: 6px;
  overflow: hidden;
}

.intake-result-thumb {
  display: block;
  width: 120px;
  height: 68px;
  object-fit: cover;
  background: var(--border);
}

.intake-result-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 1.1rem;
  background: rgba(0, 0, 0, 0.15);
  opacity: 0;
  transition: opacity 0.1s ease;
}

.intake-result-thumb-link:hover .intake-result-play {
  opacity: 1;
  background: rgba(0, 0, 0, 0.35);
}

.intake-result-info {
  min-width: 0;
}

.intake-result-title {
  display: inline;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
}

.intake-result-title:hover {
  color: var(--accent);
  text-decoration: underline;
}

.intake-result-meta {
  margin-top: 2px;
  font-size: 0.78rem;
  color: var(--text-muted);
}

.intake-result-cover-btn {
  flex: 0 0 auto;
  font-size: 0.78rem;
  white-space: nowrap;
}

.intake-load-more-row {
  display: flex;
  justify-content: center;
  margin-top: 12px;
}

.intake-copy-bar {
  position: sticky;
  bottom: 12px;
}

.admin-dupe-group {
  margin-bottom: 14px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
}

.admin-dupe-group-label {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-bottom: 4px;
}

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

.admin-row-title {
  font-size: 0.92rem;
  font-weight: 600;
}

.admin-row-sub {
  margin-top: 2px;
  font-size: 0.78rem;
  color: var(--text-muted);
}

.admin-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 4px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
}

.admin-badge-sponsored {
  background: #f5b942;
  color: #26210a;
}

.admin-badge-backdoor {
  background: #6b6280;
  color: #fff;
}

.admin-row-actions {
  flex: 0 0 auto;
  display: flex;
  gap: 6px;
}

.admin-row-btn {
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
}

.admin-row-btn-danger {
  border-color: #c4293f;
  color: #c4293f;
}

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

.admin-toolbar-actions {
  display: flex;
  gap: 6px;
  flex: 0 0 auto;
}

.admin-landing-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 16px;
}

.admin-landing-actions .admin-row-btn {
  padding: 12px 14px;
}

/* Channel Mode's DJ-deck admin panel -- reuses .admin-row/.admin-search/
   .admin-entries-list for the queue and search-results lists (same look as
   Manage Entries/Blog Posts), just a couple of small layout bits unique to
   this view: the mode radios sitting inline, and the playlist picker row. */
.admin-channel-mode-row {
  display: flex;
  align-items: center;
  gap: 16px;
  margin: 14px 0;
  font-size: 0.88rem;
}

.admin-channel-mode-row label {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}

.admin-channel-add-row {
  display: flex;
  gap: 8px;
  margin: 14px 0;
}

.admin-graphics-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 14px 0;
}

.admin-graphics-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-top: 16px;
}

.admin-graphics-preview[hidden] {
  display: none;
}

.admin-graphics-preview img {
  max-width: 320px;
  width: 100%;
  border-radius: 8px;
  border: 1px solid var(--border);
}

.admin-channel-add-row .filter-select {
  flex: 1 1 auto;
}

.admin-channel-shuffle-row {
  align-items: center;
  flex-wrap: wrap;
}

.admin-channel-shuffle-label {
  font-size: 0.82rem;
  color: var(--text-muted);
}

.admin-channel-queue-title {
  margin: 20px 0 8px;
  font-size: 0.92rem;
  color: var(--text-muted);
}

/* Single column, same on mobile and desktop -- the live-view monitor sits
   directly above the Queue list (not off to the side, which read as
   misplaced/cramped on desktop) so the admin can see/hear what's actually
   airing right where they're about to edit the queue below it. Capped
   width on wide screens purely so a 16:9 player doesn't stretch edge to
   edge of the whole admin panel. */
.admin-channel-preview-box {
  width: 100%;
  max-width: 480px;
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: 8px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.admin-channel-preview-box .admin-empty {
  padding: 0;
}

.admin-channel-preview-box iframe {
  width: 100%;
  height: 100%;
  border: 0;
}

.admin-channel-preview-label {
  margin: 8px 0 0;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.admin-channel-insert-block {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

.admin-channel-insert-timing-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin: 10px 0;
  font-size: 0.85rem;
}

.admin-channel-insert-timing-row label {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}

.admin-channel-insert-timing-row input[type="datetime-local"] {
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 0.82rem;
}

.admin-channel-insert-timing-row input[type="datetime-local"]:disabled {
  opacity: 0.4;
}

.admin-channel-scheduled-insert-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
  padding: 8px 10px;
  border: 1px solid var(--accent);
  border-radius: 6px;
  font-size: 0.85rem;
}

.admin-bulk-hint {
  margin: 10px 0 0;
  font-size: 0.8rem;
  color: var(--text-muted);
  line-height: 1.4;
}

.admin-bulk-textarea {
  width: 100%;
  margin-top: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
  resize: vertical;
  box-sizing: border-box;
}

.admin-bulk-preview {
  margin-top: 12px;
  max-height: 40vh;
  overflow-y: auto;
}

.admin-bulk-preview-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 6px;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
}

.admin-bulk-preview-row.is-invalid {
  color: var(--text-muted);
}

.admin-bulk-badge {
  flex: 0 0 auto;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
}

.admin-bulk-badge.will-create {
  background: var(--accent-soft);
  color: var(--accent);
}

.admin-bulk-badge.will-update {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-muted);
}

.admin-bulk-badge.will-skip {
  background: transparent;
  border: 1px solid #c4293f;
  color: #c4293f;
}

.admin-form {
  max-height: 100%;
  overflow-y: auto;
}

/* Manage Entries' row-number preview (manage-entries.html) -- a lightweight
   in-page video check, not the full public lightbox. */
.admin-preview-embed {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: none;
  border-radius: 8px;
}

.admin-form-title {
  margin: 0 0 4px;
  font-size: 1.1rem;
  color: var(--brand-yellow);
}

.admin-form-checkbox {
  flex-direction: row !important;
  align-items: center;
  gap: 8px !important;
}

.admin-form-actions {
  display: flex;
  gap: 8px;
}

/* Full-frame post editor -- deliberately NOT a .lightbox (a 560px-capped
   modal was cramping the whole form into a tiny two-column squeeze, see
   the "clunky and small" feedback that prompted this). Takes over the
   whole viewport like a real editor page, sits above the admin lightbox
   (z-index 1000) since it opens on top of it. */
.blog-editor-page {
  position: fixed;
  inset: 0;
  z-index: 1100;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

/* #blogVideoPickerModal opens on top of the blog editor page above --
   .lightbox's own z-index (1000) sits below .blog-editor-page's (1100),
   so without this override the picker would open "successfully" but
   render fully hidden behind the still-open editor. */
#blogVideoPickerModal {
  z-index: 1150;
}

.blog-editor-page[hidden] {
  display: none;
}

.blog-editor-topbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 24px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.blog-editor-topbar-title {
  margin: 0;
  font-size: 1.05rem;
  color: var(--brand-yellow);
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.blog-editor-topbar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
}

.blog-editor-form {
  flex: 1 1 auto;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 18px;
  width: 100%;
  max-width: 960px;
  margin: 0 auto;
  padding: 28px 24px 60px;
}

.blog-editor-meta {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.blog-editor-meta label,
.blog-editor-body-label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
}

.blog-editor-meta input,
.blog-editor-meta textarea {
  font: inherit;
  font-weight: 400;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--text);
}

.blog-editor-title-field input {
  font-size: 1.3rem;
  font-weight: 700;
  padding: 12px 14px;
}

.blog-editor-meta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

.blog-editor-meta-row label {
  flex: 1 1 260px;
}

.blog-editor-cover-preview {
  max-width: 320px;
  max-height: 180px;
  width: auto;
  object-fit: cover;
  border-radius: 8px;
}

.blog-editor-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: -1px;
  position: sticky;
  top: 0;
  background: var(--bg);
  padding-top: 4px;
  z-index: 1;
}

.blog-editor-toolbar button {
  border: 1px solid var(--border);
  border-bottom: none;
  background: var(--surface);
  color: var(--text-muted);
  font: inherit;
  font-size: 0.8rem;
  padding: 7px 12px;
  border-radius: 6px 6px 0 0;
  cursor: pointer;
}

.blog-editor-toolbar button:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.blog-editor-toolbar-sep {
  width: 1px;
  align-self: stretch;
  margin: 2px 4px;
  background: var(--border);
}

#adminBlogImageBtn {
  margin-left: auto;
  font-weight: 700;
  color: var(--accent);
}

.blog-editor-body {
  /* flex: none (not the default flex:0 1 auto, and definitely not
     flex:1 1 auto) -- .blog-editor-form (its parent) is a flex:1 1 auto,
     overflow-y:auto column, and per the flex spec, an overflow:auto flex
     item's OWN automatic minimum size collapses to 0 (not content-based).
     With any flex-shrink still active, that let the browser size this box
     down to exactly min-height regardless of how much content it actually
     held, silently spilling the rest past its own border instead of
     growing or scrolling -- exactly what made images/text render "outside
     the box" during editing. flex:none opts this item out of the flex
     distribution algorithm entirely, sizing it purely by its own
     min-height + content like a normal block box. */
  flex: none;
  min-height: 420px;
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-radius: 0 6px 6px 6px;
  background: var(--surface);
  color: var(--text);
  font-size: 1rem;
  line-height: 1.7;
}

.blog-editor-body:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

.blog-editor-body img {
  max-width: 100%;
  border-radius: 6px;
  display: block;
  margin: 14px 0;
}

.blog-video-embed {
  position: relative;
  width: 100%;
  padding-top: 56.25%;
  margin: 14px 0;
  border-radius: 6px;
  overflow: hidden;
  background: #000;
}

.blog-video-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Inserted by the blog editor's "+ Video" catalog search (app.js's
   insertBlogVideoCard()) -- a cover-art card linking to the video's own
   lightbox hash, not a bare embedded iframe like .blog-video-embed above.
   news.html intercepts clicks on .blog-video-card-link to open an in-page
   lightbox instead of navigating; the href still works as a real deep
   link (new tab, JS disabled, or clicked from outside the site). */
.blog-video-card {
  position: relative;
  width: 100%;
  margin: 14px 0;
  border-radius: 6px;
  overflow: hidden;
  background: #000;
}

.blog-video-card-link {
  display: block;
  position: relative;
  text-decoration: none;
  color: #fff;
}

.blog-video-card-link img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  margin: 0 !important; /* overrides .blog-editor-body img's own img margin */
  border-radius: 0 !important;
}

.blog-video-card-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.4rem;
  color: #fff;
  background: rgba(0, 0, 0, 0.25);
  transition: background 0.15s;
  pointer-events: none;
}

.blog-video-card-link:hover .blog-video-card-play {
  background: rgba(0, 0, 0, 0.4);
}

.blog-video-card-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 10px 12px;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.85));
  font-size: 0.9rem;
  font-weight: 600;
}

.blog-editor-body h1 {
  font-size: 1.6rem;
  margin: 0.7em 0 0.3em;
}

.blog-editor-body h2 {
  font-size: 1.3rem;
  margin: 0.7em 0 0.3em;
}

.blog-editor-body h3 {
  font-size: 1.1rem;
  margin: 0.7em 0 0.3em;
}

.blog-editor-body p {
  margin: 0 0 0.8em;
}

.blog-editor-body ul,
.blog-editor-body ol {
  margin: 0 0 0.8em;
  padding-left: 1.4em;
}

.blog-editor-body blockquote {
  margin: 0 0 0.8em;
  padding: 4px 16px;
  border-left: 3px solid var(--accent);
  color: var(--text-muted);
  font-style: italic;
}

@media (max-width: 640px) {
  .blog-editor-topbar {
    flex-wrap: wrap;
    padding: 10px 14px;
  }

  .blog-editor-form {
    padding: 18px 14px 50px;
  }

  .blog-editor-body {
    min-height: 300px;
  }
}

.recent-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 14px;
}

.recent-title {
  margin: 0;
  font-size: 1.1rem;
  color: var(--brand-yellow);
}

.recent-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.recent-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  background: none;
  appearance: none;
  font: inherit;
  color: inherit;
}

.recent-item:hover {
  background: var(--accent-soft);
}

.recent-item-thumb {
  flex: 0 0 auto;
  width: 100px;
  height: 56px;
  border-radius: 6px;
  overflow: hidden;
  background: var(--accent-soft);
}

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

.recent-item-info {
  min-width: 0;
}

.recent-item-song {
  font-weight: 700;
  font-size: 0.88rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.recent-item-artist {
  font-size: 0.8rem;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.recent-empty {
  color: var(--text-faint);
  font-size: 0.88rem;
  text-align: center;
  padding: 20px 0;
}

.settings-title {
  margin: 0 0 18px;
  font-size: 1.2rem;
  color: var(--brand-yellow);
}

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

/* Author styles always beat the browser's UA [hidden]{display:none} rule
   regardless of source order -- same gotcha as .header-icon-btn[hidden] /
   .submit-form[hidden] elsewhere. Without this, #settingsAccountRow (hidden
   by default until signed in) would render as an empty flex row before
   sign-in instead of actually disappearing. */
.settings-row[hidden] {
  display: none;
}

.settings-row-label {
  font-size: 0.88rem;
  font-weight: 600;
}

.settings-row-hint {
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-top: 2px;
}

.settings-action-btn {
  flex: 0 0 auto;
  border: 1px solid var(--border);
  background: var(--surface);
  color: #c4293f;
  font-size: 0.8rem;
  font-weight: 600;
  padding: 6px 14px;
  border-radius: 999px;
  cursor: pointer;
}

.settings-action-btn:hover {
  border-color: #c4293f;
}

.settings-status {
  margin: 10px 0 0;
  font-size: 0.82rem;
  color: #1f8a5f;
}

.settings-theme-toggle {
  flex: 0 0 auto;
  display: flex;
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
}

.settings-wallet-buttons {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
}

.settings-username-field {
  flex: 0 0 auto;
  display: flex;
  gap: 8px;
}

.settings-username-field input {
  width: 140px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
}

/* On mobile the label above can wrap to 3-4 lines (see .settings-row-hint),
   leaving the username input+Save button too little width on the same
   line -- they were clipping off the right edge of the screen. Wrapping
   .settings-row lets that group drop to its own full-width line instead,
   with the input flexing to fill it rather than staying a fixed 140px. */
@media (max-width: 640px) {
  .settings-row {
    flex-wrap: wrap;
  }

  .settings-username-field,
  .settings-wallet-buttons {
    width: 100%;
  }

  .settings-username-field input {
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
  }
}

.username-modal-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 16px;
}

.settings-theme-btn {
  border: none;
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.8rem;
  font-weight: 600;
  padding: 6px 14px;
  cursor: pointer;
}

.settings-theme-btn.is-active {
  background: var(--accent);
  color: #fff;
}

.submit-form-title {
  margin: 0 0 4px;
  font-size: 1.2rem;
  color: var(--brand-yellow);
}

.submit-form-intro {
  margin: 0 0 8px;
  color: var(--text-muted);
  font-size: 0.85rem;
}

.submit-form-bulk {
  margin: 0 0 18px;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--accent-soft);
  color: var(--text-muted);
  font-size: 0.8rem;
}

.submit-form-bulk a {
  color: var(--accent);
}

.submit-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Author styles always beat the browser's UA [hidden]{display:none} rule
   regardless of source order (origin precedence, not specificity) -- same
   gotcha as .header-icon-btn[hidden] / .profile-editor[hidden] elsewhere.
   Without this, #adminForm (class="admin-form submit-form") never actually
   hid when switching to Bulk Import (showAdminBulk() sets hidden=true, but
   this class's own unconditional display:flex silently won), so the Add
   Entry form and the Bulk Import panel rendered stacked on top of each
   other. Also covers #submitForm for the same reason. */
.submit-form[hidden] {
  display: none;
}

.submit-form label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
}

.submit-form input,
.submit-form select,
.submit-form textarea {
  font: inherit;
  font-weight: 400;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
}

/* Dropdown-plus-free-text pairing (admin Country/Genres) -- the select is
   a fast-fill shortcut for an existing value, the text input underneath is
   the actual source of truth (still name="country"/"genres") and stays
   fully editable so a value that isn't in the catalog yet can just be
   typed directly. */
.admin-form-combo {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.submit-form textarea {
  resize: vertical;
}

.submit-required {
  color: #c4293f;
  margin-left: 2px;
}

.submit-optional {
  font-weight: 400;
  color: var(--text-faint);
}

.submit-form-disclaimer {
  margin: 0;
  font-style: italic;
  font-size: 0.75rem;
  color: var(--text-faint);
}

.submit-honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.submit-form-btn {
  border: none;
  background: var(--accent);
  color: #fff;
  font-size: 0.9rem;
  font-weight: 700;
  padding: 10px 18px;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 0 7px 0 var(--accent);
}

.submit-form-btn:hover {
  opacity: 0.9;
}

.submit-form-btn:disabled {
  box-shadow: none;
  opacity: 0.6;
  cursor: default;
}

.submit-form-status {
  margin: 0;
  font-size: 0.85rem;
}

.submit-form-status.is-error {
  color: #c4293f;
}

@media (min-width: 641px) {
  .submit-form {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 16px;
  }

  .submit-form-title,
  .submit-form-intro,
  .submit-form-bulk,
  .submit-form label:has(textarea),
  .submit-honeypot,
  .submit-form-btn,
  .submit-form-status,
  .submit-form-disclaimer {
    grid-column: 1 / -1;
  }
}

/* Post-submission panel (swaps places with .submit-form -- see
   submitThanksAgain/submitThanksBack in app.js), reusing the Support
   page's own Ko-fi wording/button so it doesn't need to be duplicated
   with a different pitch here. */
.submit-thanks {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.submit-thanks-support {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

.submit-thanks-kofi {
  display: inline-block;
  align-self: flex-start;
  text-decoration: none;
}

.submit-thanks-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 6px;
}

.submit-thanks-back {
  border: 1px solid var(--border);
  background: none;
  color: var(--text);
  font-size: 0.9rem;
  font-weight: 700;
  padding: 10px 18px;
  border-radius: 999px;
  cursor: pointer;
}

.submit-thanks-back:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* Message board: a hidden-by-default popout tab docked to the right edge.
   Anyone can open it and read; posting requires sign-in (see msgBoardForm/
   msgBoardSigninNote toggle in app.js and the Firestore rules on /messages). */
.msgboard-tab {
  position: fixed;
  top: 75%;
  right: 0;
  transform: translateY(-50%);
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--border);
  border-right: none;
  background: var(--surface);
  color: var(--text);
  border-radius: 8px 0 0 8px;
  cursor: pointer;
}

.msgboard-tab svg {
  width: 22px;
  height: 22px;
}

.msgboard-tab:hover {
  background: var(--accent-soft);
}

.msgboard-panel[hidden],
.msgboard-panel [hidden] {
  display: none;
}

.msgboard-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 340px;
  max-width: 88vw;
  z-index: 41;
  background: var(--surface);
  border-left: 1px solid var(--border);
  box-shadow: -8px 0 24px rgba(0, 0, 0, 0.25);
  display: flex;
  flex-direction: column;
}

.msgboard-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
  font-weight: 700;
}

.msgboard-close {
  border: none;
  background: none;
  color: var(--text-muted);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  padding: 2px 6px;
}

.msgboard-messages {
  flex: 1;
  overflow-y: auto;
  padding: 12px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.msgboard-empty {
  color: var(--text-faint);
  font-size: 0.85rem;
  text-align: center;
  margin-top: 20px;
}

.msgboard-message {
  font-size: 0.85rem;
}

.msgboard-message-meta {
  display: flex;
  gap: 6px;
  align-items: baseline;
  margin-bottom: 2px;
}

.msgboard-message-author {
  font-weight: 700;
  color: var(--text);
}

.msgboard-message-time {
  color: var(--text-faint);
  font-size: 0.72rem;
}

.msgboard-message-text {
  color: var(--text);
  background: var(--accent-soft);
  border-radius: var(--radius);
  padding: 6px 10px;
  overflow-wrap: break-word;
  white-space: pre-wrap;
}

.msgboard-composer {
  display: flex;
  gap: 8px;
  padding: 12px 16px;
  border-top: 1px solid var(--border);
}

.msgboard-composer input {
  flex: 1;
  font: inherit;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
}

.msgboard-composer button {
  border: none;
  background: var(--accent);
  color: #fff;
  font-weight: 700;
  padding: 8px 14px;
  border-radius: 999px;
  cursor: pointer;
}

.msgboard-admin-controls {
  display: flex;
  gap: 6px;
  margin-top: 4px;
}

.msgboard-admin-controls button {
  border: 1px solid var(--border);
  background: none;
  color: var(--text-muted);
  font-size: 0.7rem;
  padding: 2px 8px;
  border-radius: 999px;
  cursor: pointer;
}

.msgboard-admin-delete:hover {
  color: #c4293f;
  border-color: #c4293f;
}

.msgboard-admin-mute.is-active,
.msgboard-admin-ban.is-active {
  color: #fff;
  background: #c4293f;
  border-color: #c4293f;
}

.msgboard-signin-note {
  margin: 0;
  padding: 12px 16px;
  border-top: 1px solid var(--border);
  font-size: 0.8rem;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 8px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121214;
    --surface: #1b1b1e;
    --border: #2c2c30;
    --text: #f1f1f2;
    --text-muted: #a5a5ad;
    --text-faint: #77777f;
    --accent: #a855f7;
    --accent-soft: #2a1150;
    --brand-yellow: #f5e300;
    --tag-music-video-bg: #1c2740;
    --tag-dance-bg: #2c1f36;
    --tag-montage-bg: #33230f;
    --tag-dvd-bg: #123324;
    --tag-live-bg: #3a1620;
    --tag-installation-bg: #123338;
    --tag-short-bg: #211f42;
    --tag-docu-bg: #34270a;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 8px rgba(0, 0, 0, 0.25);
  }
  .tag-default { background: #232326; color: var(--text-muted); }
}

/* Manual theme override (Settings toggle) -- takes priority over the OS
   preference above regardless of which way prefers-color-scheme leans. */
:root[data-theme="light"] {
  --bg: #fafafa;
  --surface: #ffffff;
  --border: #e4e4e7;
  --text: #18181b;
  --text-muted: #6b6b73;
  --text-faint: #9a9aa2;
  --accent: #4a0d8f;
  --accent-soft: #efe4fb;
  --tag-music-video-bg: #eaf0fd;
  --tag-dance-bg: #f6ecfa;
  --tag-montage-bg: #fcefe1;
  --tag-dvd-bg: #e6f5ee;
  --tag-live-bg: #fbe6ea;
  --tag-installation-bg: #e2f5f7;
  --tag-short-bg: #ececfd;
  --tag-docu-bg: #faf1de;
  --brand-yellow: #a68900;
  --shadow: 0 1px 2px rgba(20, 20, 30, 0.04), 0 1px 8px rgba(20, 20, 30, 0.03);
}
:root[data-theme="light"] .tag-default { background: #f0f0f2; color: var(--text-muted); }

:root[data-theme="dark"] {
  --bg: #121214;
  --surface: #1b1b1e;
  --border: #2c2c30;
  --text: #f1f1f2;
  --text-muted: #a5a5ad;
  --text-faint: #77777f;
  --accent: #a855f7;
  --accent-soft: #2a1150;
  --brand-yellow: #f5e300;
  --tag-music-video-bg: #1c2740;
  --tag-dance-bg: #2c1f36;
  --tag-montage-bg: #33230f;
  --tag-dvd-bg: #123324;
  --tag-live-bg: #3a1620;
  --tag-installation-bg: #123338;
  --tag-short-bg: #211f42;
  --tag-docu-bg: #34270a;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 8px rgba(0, 0, 0, 0.25);
}
:root[data-theme="dark"] .tag-default { background: #232326; color: var(--text-muted); }

@media (max-width: 640px) {
  .app {
    padding: 16px 8px 40px;
  }

  .controls {
    flex-direction: column;
    align-items: stretch;
  }

  .search-wrap {
    flex: none;
    max-width: none;
  }

  .video-embed-hint {
    padding: 20px 12px;
  }

  .group-heading {
    scroll-margin-top: 130px;
  }

  .save-playlist-btn {
    width: 28px;
    height: 28px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.1rem;
  }

  .save-playlist-btn-label {
    display: none;
  }

  .save-playlist-btn-icon {
    display: inline;
  }

  .lightbox {
    padding: 0;
  }

  .lightbox-panel {
    max-width: 100%;
    max-height: 100vh;
    height: 100%;
    border-radius: 0;
  }

  .lightbox-widen-btn {
    display: none;
  }

  /* .lightbox-title-actions can carry up to ~9 buttons at once for an
     admin (Edit/Delete/Favorite/Playlist/Widen/4:3/Mirror/Interlace/Report
     issue/Suggest an edit) -- flex-wrap keeps them from forcing horizontal
     overflow on the whole panel, but staying side-by-side with the title
     at mobile widths still crowds both onto one cramped line. Stacking the
     title above the actions row reads far cleaner once it's already
     wrapping to 2-3 lines of its own. */
  .lightbox-title-row {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
  }

  .lightbox-title-actions {
    justify-content: flex-start;
  }

  .media-strip {
    border: none;
    background: transparent;
    border-radius: 0;
    padding: 10px 0 12px;
  }
}

.bottom-nav {
  display: none;
}

@media (max-width: 640px) {
  /* Room at the bottom of the page so content isn't hidden behind the
     fixed nav bar. Kept in this media query (after the base .app rule
     earlier in the file) so it wins the cascade only on mobile. */
  .app {
    padding-bottom: calc(64px + env(safe-area-inset-bottom, 0px));
  }

  .bottom-nav {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 900;
    background: var(--surface);
    border-top: 1px solid var(--border);
    padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0px));
  }

  /* The bottom nav (z-index:900) sits fixed on top of the message board
     panel (z-index:41, bottom:0 by default) and was covering its composer
     input -- pull the panel's bottom edge up by the same amount .app pads
     for, so the composer clears the nav bar instead of hiding under it. */
  .msgboard-panel {
    bottom: calc(64px + env(safe-area-inset-bottom, 0px));
  }

  .bottom-nav-item {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    border: none;
    background: none;
    color: var(--text-muted);
    font: inherit;
    padding: 6px 2px;
    cursor: pointer;
  }

  .bottom-nav-item:active,
  .bottom-nav-item.is-active {
    color: var(--accent);
  }

  .bottom-nav-icon {
    display: flex;
  }

  .bottom-nav-icon svg {
    width: 20px;
    height: 20px;
  }

  .bottom-nav-label {
    font-size: 0.6rem;
    font-weight: 600;
  }

  /* Bigger thumbnails, less border clutter -- the 140px/78px desktop-first
     size reads small on a phone screen. The prev/next arrows only matter for
     mouse users; touch already scrolls the strip by swipe, so drop them and
     let the freed-up width go to the cards themselves. */
  .media-strip-arrow {
    display: none;
  }

  .media-strip-body {
    padding: 0 14px;
  }

  .media-strip-card {
    width: 220px;
  }

  .media-strip-thumb {
    width: 220px;
    aspect-ratio: 16 / 9;
  }

  /* Latest/Featured get an explicit "See all" expand into a 2-column
     gallery grid on mobile, instead of relying purely on the horizontal
     scroll strip -- Play All moves out of the header and into the
     expanded gallery view itself (hidden until .is-expanded), reusing the
     exact same setupSeeMore()/.is-expanded toggle the desktop "See more"
     cap already uses, just with different mobile-only styling. Favorites
     intentionally not included -- horizontal scroll only, unchanged. */
  #latestStrip .media-strip-see-more,
  #featuredStrip .media-strip-see-more {
    display: block;
    margin: 14px auto 0;
    padding: 8px 22px;
    border: none;
    border-radius: 999px;
    background: var(--accent);
    color: #fff;
    font-size: 0.85rem;
    cursor: pointer;
  }

  #latestStrip .media-strip-more-row .media-strip-see-more {
    margin: 0;
  }

  #latestStrip .media-strip-see-more:hover,
  #featuredStrip .media-strip-see-more:hover {
    opacity: 0.9;
  }

  #latestStrip .media-strip-play-all,
  #featuredStrip .media-strip-play-all {
    display: none;
  }

  #latestStrip.is-expanded .media-strip-play-all,
  #featuredStrip.is-expanded .media-strip-play-all {
    display: inline-block;
  }

  #latestStrip.is-expanded .media-strip-track,
  #featuredStrip.is-expanded .media-strip-track {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 14px;
    overflow: visible;
  }

  #latestStrip.is-expanded .media-strip-card,
  #featuredStrip.is-expanded .media-strip-card {
    width: auto;
  }

  #latestStrip.is-expanded .media-strip-thumb,
  #featuredStrip.is-expanded .media-strip-thumb {
    width: 100%;
  }

  /* Spotlight becomes a single vertical column of full-width cards, like
     YouTube's mobile home feed, instead of the desktop's fixed 3-up grid.
     Discover follows the same pattern. */
  .spotlight-sidebar-cards,
  .blog-latest-cards,
  .discover-grid {
    display: flex;
    flex-direction: column;
    gap: 16px;
  }

  .viewers-choice-top2,
  .viewers-choice-rest {
    grid-template-columns: 1fr;
    gap: 14px;
  }

  .spotlight-card {
    width: 100%;
    border: none;
    display: flex;
    flex-direction: column;
  }

  /* The pool is 5 (see SPOTLIGHT_COUNT), but mobile's vertical stack only
     shows the first 3 -- 5 full-width cards would be a long scroll before
     reaching News/Latest below it. Scoped to Spotlight's own container --
     Discover reuses .spotlight-card for its look too, and this rule
     unscoped was silently hiding all of Discover's cards past the 3rd,
     making its mobile "See more" look broken (cards WERE being added to
     the DOM, just invisible). */
  .spotlight-sidebar-cards .spotlight-card:nth-child(n+4) {
    display: none;
  }

  .spotlight-card-info {
    padding: 10px 2px;
  }

  .spotlight-card-song {
    font-size: 0.92rem;
    white-space: normal;
  }

  .spotlight-card-artist {
    margin-top: 2px;
    font-size: 0.8rem;
  }

  /* Mutually-exclusive mobile views: Home (browse -- Latest Submissions,
     the ad banner, Featured), Search (tabs, search box, filters, jump nav,
     results), and Favorites (its own page, like Search but without the
     browse tabs/filters). TV Mode is a lightbox, not a view -- its
     bottom-nav button just opens the modal. Home is the default; switching
     views hides the sections that don't belong instead of just pushing
     them off-screen, so results/filters aren't buried below the fold
     (worse still with the on-screen keyboard open).

     The bottom jump nav (#jumpNavBottom) is dropped entirely on mobile,
     not shown in any view -- the top one (#jumpNavTop) already covers it. */
  .controls,
  .filters-toggle-row,
  .filters-row,
  #status,
  #jumpNavTop,
  .results-layout,
  #jumpNavBottom {
    display: none;
  }

  /* filters-row keeps its own [hidden] toggle (the Filters button) in
     every view it's reachable from -- :not([hidden]) here means we only
     ever force it open when the user hasn't manually collapsed it. */
  body.mobile-view-search .filters-row:not([hidden]) {
    display: flex;
  }

  body.mobile-view-search #spotlightSidebar,
  body.mobile-view-search #blogLatestSidebar,
  body.mobile-view-search #latestStrip,
  body.mobile-view-search .ad-placeholder,
  body.mobile-view-search #videoEmbed,
  body.mobile-view-search #featuredStrip,
  body.mobile-view-search #discoverSection,
  body.mobile-view-search #viewersChoiceSection {
    display: none;
  }

  body.mobile-view-search .controls {
    display: flex;
  }

  body.mobile-view-search .filters-toggle-row {
    display: flex;
  }

  body.mobile-view-search #status {
    display: flex;
  }

  body.mobile-view-search #jumpNavTop {
    display: flex;
  }

  body.mobile-view-search .results-layout {
    display: flex;
  }

  /* Favorites view: its own page, reached via the bottom nav's Favorites
     button (setMobileView("favorites")) instead of the old popup modal. */
  body.mobile-view-favorites #spotlightSidebar,
  body.mobile-view-favorites #blogLatestSidebar,
  body.mobile-view-favorites #latestStrip,
  body.mobile-view-favorites .ad-placeholder,
  body.mobile-view-favorites #videoEmbed,
  body.mobile-view-favorites #featuredStrip,
  body.mobile-view-favorites #discoverSection,
  body.mobile-view-favorites #viewersChoiceSection {
    display: none;
  }

  body.mobile-view-favorites #favoritesStrip {
    display: block;
  }

  /* Playlists view: no bottom-nav slot of its own (reached via the
     hamburger menu's Playlists link instead) -- still a real
     setMobileView("playlists") state, same pattern as Favorites above. */
  body.mobile-view-playlists #spotlightSidebar,
  body.mobile-view-playlists #blogLatestSidebar,
  body.mobile-view-playlists #latestStrip,
  body.mobile-view-playlists .ad-placeholder,
  body.mobile-view-playlists #videoEmbed,
  body.mobile-view-playlists #featuredStrip,
  body.mobile-view-playlists #discoverSection,
  body.mobile-view-playlists #viewersChoiceSection {
    display: none;
  }

  body.mobile-view-playlists #playlistsPage {
    display: block;
  }

  /* Profiles view: no bottom-nav slot of its own (reached via the
     hamburger menu's Profiles link instead) -- still a real
     setMobileView("profiles") state, same pattern as Playlists above. */
  body.mobile-view-profiles #spotlightSidebar,
  body.mobile-view-profiles #blogLatestSidebar,
  body.mobile-view-profiles #latestStrip,
  body.mobile-view-profiles .ad-placeholder,
  body.mobile-view-profiles #videoEmbed,
  body.mobile-view-profiles #featuredStrip,
  body.mobile-view-profiles #discoverSection,
  body.mobile-view-profiles #viewersChoiceSection {
    display: none;
  }

  body.mobile-view-profiles #profilesPage {
    display: block;
  }

  .result-card-thumb {
    flex-basis: 110px;
    width: 110px;
    padding-top: 62px;
  }
}
