/* ── Reset & base ──────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* ── Icons (lucide-static, ISC license, public/icons/*.svg) ───────────────────
   background-color: currentColor + mask-image means the SVG files are the
   actual source of truth (not duplicated markup) while staying recolorable —
   .active / hover / theme colors all apply the same way they do to text. */
.icon {
  display: inline-block;
  width: 16px;
  height: 16px;
  background-color: currentColor;
  flex-shrink: 0;
  -webkit-mask-repeat: no-repeat;   mask-repeat: no-repeat;
  -webkit-mask-position: center;    mask-position: center;
  -webkit-mask-size: contain;       mask-size: contain;
}
.icon-play           { -webkit-mask-image: url('icons/play.svg');           mask-image: url('icons/play.svg'); }
.icon-pause           { -webkit-mask-image: url('icons/pause.svg');          mask-image: url('icons/pause.svg'); }
.icon-square          { -webkit-mask-image: url('icons/square.svg');         mask-image: url('icons/square.svg'); }
.icon-repeat          { -webkit-mask-image: url('icons/repeat.svg');         mask-image: url('icons/repeat.svg'); }
.icon-repeat-1        { -webkit-mask-image: url('icons/repeat-1.svg');       mask-image: url('icons/repeat-1.svg'); }
.icon-shuffle         { -webkit-mask-image: url('icons/shuffle.svg');        mask-image: url('icons/shuffle.svg'); }
.icon-skip-back       { -webkit-mask-image: url('icons/skip-back.svg');      mask-image: url('icons/skip-back.svg'); }
.icon-skip-forward    { -webkit-mask-image: url('icons/skip-forward.svg');   mask-image: url('icons/skip-forward.svg'); }
.icon-refresh-cw      { -webkit-mask-image: url('icons/refresh-cw.svg');     mask-image: url('icons/refresh-cw.svg'); }
.icon-volume-2        { -webkit-mask-image: url('icons/volume-2.svg');      mask-image: url('icons/volume-2.svg'); }
.icon-volume-x        { -webkit-mask-image: url('icons/volume-x.svg');      mask-image: url('icons/volume-x.svg'); }
.icon-bell            { -webkit-mask-image: url('icons/bell.svg');          mask-image: url('icons/bell.svg'); }
.icon-bell-off        { -webkit-mask-image: url('icons/bell-off.svg');      mask-image: url('icons/bell-off.svg'); }
.icon-menu            { -webkit-mask-image: url('icons/menu.svg');          mask-image: url('icons/menu.svg'); }
.icon-monitor         { -webkit-mask-image: url('icons/monitor.svg');       mask-image: url('icons/monitor.svg'); }
.icon-message-circle  { -webkit-mask-image: url('icons/message-circle.svg'); mask-image: url('icons/message-circle.svg'); }
.icon-music           { -webkit-mask-image: url('icons/music.svg');        mask-image: url('icons/music.svg'); }
.icon-maximize        { -webkit-mask-image: url('icons/maximize.svg');      mask-image: url('icons/maximize.svg'); }
.icon-x               { -webkit-mask-image: url('icons/x.svg');            mask-image: url('icons/x.svg'); }
.icon-search          { -webkit-mask-image: url('icons/search.svg');       mask-image: url('icons/search.svg'); }
.icon-log-out         { -webkit-mask-image: url('icons/log-out.svg');       mask-image: url('icons/log-out.svg'); }
.icon-unplug          { -webkit-mask-image: url('icons/unplug.svg');        mask-image: url('icons/unplug.svg'); }
.icon-video           { -webkit-mask-image: url('icons/video.svg');        mask-image: url('icons/video.svg'); }
.icon-picture-in-picture-2 { -webkit-mask-image: url('icons/picture-in-picture-2.svg'); mask-image: url('icons/picture-in-picture-2.svg'); }
.icon-plus            { -webkit-mask-image: url('icons/plus.svg');         mask-image: url('icons/plus.svg'); }
.icon-list-plus       { -webkit-mask-image: url('icons/list-plus.svg');    mask-image: url('icons/list-plus.svg'); }
.icon-eye             { -webkit-mask-image: url('icons/eye.svg');          mask-image: url('icons/eye.svg'); }
.icon-gift            { -webkit-mask-image: url('icons/gift.svg');         mask-image: url('icons/gift.svg'); }
.icon-heart           { -webkit-mask-image: url('icons/heart.svg');        mask-image: url('icons/heart.svg'); }
.icon-share-2         { -webkit-mask-image: url('icons/share-2.svg');       mask-image: url('icons/share-2.svg'); }
.icon-thumbs-up       { -webkit-mask-image: url('icons/thumbs-up.svg');     mask-image: url('icons/thumbs-up.svg'); }
.icon-triangle-alert  { -webkit-mask-image: url('icons/triangle-alert.svg'); mask-image: url('icons/triangle-alert.svg'); }
.icon-check           { -webkit-mask-image: url('icons/check.svg');        mask-image: url('icons/check.svg'); }
.icon-arrow-left      { -webkit-mask-image: url('icons/arrow-left.svg');    mask-image: url('icons/arrow-left.svg'); }
.icon-chevron-down    { -webkit-mask-image: url('icons/chevron-down.svg');  mask-image: url('icons/chevron-down.svg'); }
.icon-power           { -webkit-mask-image: url('icons/power.svg');        mask-image: url('icons/power.svg'); }
.icon-plug            { -webkit-mask-image: url('icons/plug.svg');         mask-image: url('icons/plug.svg'); }
.icon-globe           { -webkit-mask-image: url('icons/globe.svg');        mask-image: url('icons/globe.svg'); }
.icon-grip-vertical   { -webkit-mask-image: url('icons/grip-vertical.svg'); mask-image: url('icons/grip-vertical.svg'); }

:root {
  --sidebar-w:  260px;
  --input-h:    54px;
  --accent:     #fe2c55;
  --bg:         #0f0f0f;
  --sidebar:    #161616;
  --surface:    #1e1e1e;
  --border:     #2a2a2a;
  --text:       #e2e2e2;
  --text-2:     #888;
  --text-3:     #555;
  --green:      #22c55e;
  --yellow:     #f59e0b;
  --red:        #ef4444;
  --blue:       #60a5fa;
  --pink:       #f472b6;
  --radius:     6px;
  --transition: .18s ease;
}

html, body { height: 100%; overflow: hidden; }

body {
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 13px;
  line-height: 1.5;
}


/* ── App body (below header) ───────────────────────────────────────────────── */
#app-body {
  flex: 1;
  display: flex;
  overflow: hidden;
}

/* ── Sidebar (push drawer) ─────────────────────────────────────────────────── */
#sidebar {
  width: 0;
  min-width: 0;
  flex-shrink: 0;
  background: var(--sidebar);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transition: width var(--transition), min-width var(--transition);
}

body.sidebar-open #sidebar {
  width: var(--sidebar-w);
  min-width: var(--sidebar-w);
  border-right: 1px solid var(--border);
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: none;
}

/* ── Sidebar sections ──────────────────────────────────────────────────────── */
.sb-section {
  padding: 12px 8px 14px 14px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.sb-label {
  display: block;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .7px;
  color: var(--text-3);
  margin-bottom: 2px;
}
.sb-section-toggle {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  width: 100%;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  gap: 0;
  user-select: none;
}
#ntfy-details,
#ntfy-events {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.sb-section-toggle .icon-btn {
  width: 26px;
  height: 26px;
  font-size: 13px;
}
.sb-section-toggle .sb-label { margin-bottom: 0; }
.browser-header-status {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 5px;
  font-size: 11px;
  line-height: 1;
  color: var(--text-2);
  padding-right: 4px;
}
.chevron {
  margin-left: auto;
  color: var(--text-3);
  font-size: 16px;
  transition: transform var(--transition);
  line-height: 1;
}
.sb-section.open .chevron { transform: rotate(90deg); }
.sb-section-body {
  display: none;
  flex-direction: column;
  gap: 8px;
}
.sb-section.open .sb-section-body { display: flex; }

/* #studio-controls (Go Live button + Chat/Comments/Captcha toggles) is a
   plain nested div, not a direct .sb-section-body flex child — without its
   own flex+gap here, its children have zero space between them and look
   fused together, unlike every sibling row above/below it. */
#studio-controls {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Sub-groups within a merged section (e.g. Live Connector's Browser/Proxy/
   Modes groups) — all part of the same connector, so no divider line
   between them, just enough gap to read as separate groups. */
.sb-subsection {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.sb-sublabel-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.sb-sublabel {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .7px;
  color: var(--text-3);
}

/* ── Status lines ──────────────────────────────────────────────────────────── */
.status-line {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-2);
  min-height: 18px;
}
.dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
  transition: background var(--transition);
}
.dot-off        { background: var(--text-3); }
.dot-connecting { background: var(--yellow); animation: pulse 1s infinite; }
.dot-on         { background: var(--green); }
.dot-error      { background: var(--red); }

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: .4; }
}

/* ── Buttons ───────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 7px 13px;
  border: none;
  border-radius: var(--radius);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: opacity var(--transition), background var(--transition);
  white-space: nowrap;
  flex-shrink: 0;
}
.btn:disabled { opacity: .4; cursor: not-allowed; }
.btn-accent   { background: var(--accent); color: #fff; }
.btn-accent:not(:disabled):hover { background: #ff4d6e; }
.btn-secondary { background: var(--surface); color: var(--text); border: 1px solid var(--border); }
.btn-secondary:not(:disabled):hover { background: var(--border); }
/* Connect/disconnect, attach/detach, pair/unpair toggle buttons (Remote
   Desktop, Studio chat/captcha) add .active when the connected/attached
   state is on — this is what actually renders that, matching .icon-btn's
   existing accent-on-active convention. */
.btn-secondary.active { border-color: var(--accent); color: var(--accent); }
.btn-ghost     { background: transparent; color: var(--text-2); }
.btn-ghost:not(:disabled):hover { background: var(--surface); color: var(--text); }
.btn-full { width: 100%; }

#sidebar-footer {
  margin-top: auto;
  min-height: var(--input-h);
  padding: 8px 14px;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
  box-sizing: border-box;
}
#sidebar-footer .hint { margin: 0; font-size: 11px; }
#exit-btn:hover { color: var(--red); }

.icon-btn {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-2);
  width: 30px; height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius);
  font-size: 16px;
  transition: background var(--transition), color var(--transition);
  flex-shrink: 0;
}
.icon-btn:hover { background: var(--surface); color: var(--text); }

/* ── Inputs & fields ───────────────────────────────────────────────────────── */
.field {
  width: 100%;
  padding: 7px 9px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-size: 12px;
  font-family: inherit;
  outline: none;
  transition: border-color var(--transition);
  appearance: none;
}
.field:focus { border-color: var(--accent); }
.field:disabled { opacity: .4; cursor: not-allowed; }
.field::placeholder { color: var(--text-3); }
textarea.field { resize: vertical; min-height: 42px; }

/* Floating label: sits over the field like a placeholder until there's a
   value or focus, then rises to straddle the top border — stays visible
   instead of disappearing the moment something's typed, which was the
   actual bug (a placeholder alone gives no way to tell what a filled-in
   field is once its text no longer matches the hint). The label's own
   background (matched to .field's, not the panel's, so it works inside
   any panel) masks the border segment behind it, cutting a gap like a
   fieldset/legend. Pure CSS via :placeholder-shown, so every wrapped field
   needs a real (even if blank, `placeholder=" "`) placeholder attribute
   for the browser to track "is this empty". */
/* Top margin gives the straddling label (see below) clearance from
   whatever sits above it in the row — without it, the label's upward
   poke has only the section's row gap to work with and visually collides
   with the previous row. */
.field-wrap { position: relative; width: 100%; margin-top: 4px; }
.field-wrap .field-label {
  position: absolute;
  left: 6px;
  top: 50%;
  transform: translateY(-50%);
  padding: 0 3px;
  background: var(--bg);
  font-size: 12px;
  line-height: 1;
  color: var(--text-3);
  pointer-events: none;
  transition: top var(--transition), font-size var(--transition), color var(--transition);
  max-width: calc(100% - 12px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.field-wrap .field:focus + .field-label,
.field-wrap .field:not(:placeholder-shown) + .field-label {
  top: 0;
  font-size: 9px;
}
.field-wrap .field:focus + .field-label { color: var(--accent); }
.field-wrap .field:disabled + .field-label { opacity: .4; }

.select-field {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23555'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
  padding-right: 28px;
  cursor: pointer;
}
.select-field option { background: var(--surface); color: var(--text); }

.btn-row {
  display: flex;
  gap: 6px;
}
.btn-row .btn { flex: 1; }

.sb-label-row {
  display: flex;
  align-items: center;
}
.sb-label-row .sb-label { margin-bottom: 0; }
.sb-label-row .browser-header-status { padding-right: 0; }

.input-row {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-top: 4px;
}
.input-row .field-wrap,
.input-row > .field { flex: 1; }
/* Reset — the row itself already carries the label-clearance margin above;
   without this the two would stack and the row's fields would sit lower
   than its non-field siblings (e.g. the connect icon-btn). */
.input-row .field-wrap { margin-top: 0; }

.connect-icon-btn {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  color: var(--accent);
  font-size: 14px;
}
.connect-icon-btn:hover { background: var(--surface); color: var(--accent); }
.connect-icon-btn.active { color: var(--text-2); }

#vdoninja-frame-wrap {
  width: 100%;
  aspect-ratio: 4 / 3;
  margin-top: 8px;
  border-radius: 8px;
  overflow: hidden;
  background: #000;
}
#vdoninja-frame { width: 100%; height: 100%; border: 0; }
.connect-icon-btn.active:hover { background: var(--surface); color: var(--red); }

.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1px 0;
}
.toggle-label { color: var(--text-2); font-size: 12px; }
/* Small hint marker for any label carrying a `title` tooltip — signals
   there's more info on hover without the dotted-underline treatment. */
.toggle-label[title]::after {
  content: '?';
  display: inline-block;
  vertical-align: super;
  font-size: 8px;
  line-height: 1;
  margin-left: 3px;
  color: var(--text-3);
}

/* ── Toggle switch ─────────────────────────────────────────────────────────── */
.toggle { position: relative; width: 34px; height: 18px; flex-shrink: 0; }
.toggle input { opacity: 0; width: 0; height: 0; }
.slider {
  position: absolute;
  inset: 0;
  background: var(--border);
  border-radius: 10px;
  cursor: pointer;
  transition: background var(--transition);
}
.slider::before {
  content: '';
  position: absolute;
  width: 12px; height: 12px;
  left: 3px; top: 3px;
  background: #fff;
  border-radius: 50%;
  transition: transform var(--transition);
}
.toggle input:checked + .slider { background: var(--accent); }
.toggle input:checked + .slider::before { transform: translateX(16px); }
.toggle input:disabled + .slider { opacity: .35; cursor: not-allowed; }
.toggle-row:has(input:disabled) .toggle-label { opacity: .4; }

/* ── Music (pear-desktop) ──────────────────────────────────────────────────── */
/* Sidebar section is config-only (host/port/appid, Pair, Connect) — all
   playback control lives in the dedicated Search & Queue view's bottom bar. */
.music-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.music-artist {
  font-size: 11px;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.music-meta2 {
  font-size: 10px;
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.music-seek-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding-top: 6px;
}
.music-seek-row input[type="range"] { height: 3px; }
.music-time {
  font-size: 10px;
  color: var(--text-3);
  flex-shrink: 0;
  width: 28px;
}
.music-seek-row .music-time:last-child { text-align: right; }
input[type="range"] {
  flex: 1;
  accent-color: var(--accent);
  height: 4px;
  cursor: pointer;
}
.icon-btn.active { color: var(--accent); }

/* ── Music search / queue view ─────────────────────────────────────────────── */
#music-view {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
/* Seek sits flush at the very top edge, acting as the bar's visual divider
   (like a slim progress line) rather than a separate bordered box below the
   controls — no border-top here, the seek row itself reads as the border. */
#music-now-playing-bar {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0 16px 10px;
  flex-shrink: 0;
}
/* Transport on the left next to art/title (not centered), utility controls on
   the right — matches a standard media-player bar rather than a 3-way split. */
.music-nowbar-main {
  display: flex;
  align-items: center;
  gap: 12px;
}
.music-nowbar-transport {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}
.music-nowbar-left {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
#music-nowbar-art {
  width: 48px;
  height: 48px;
  border-radius: 6px;
  background: var(--surface);
  object-fit: cover;
  flex-shrink: 0;
}
#music-nowbar-meta { min-width: 0; }
.music-play-btn {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
}
.music-play-btn:hover { background: #ff4d6e; }
.music-play-btn .icon { width: 17px; height: 17px; }
.music-nowbar-right {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  flex-shrink: 0;
}
.music-nowbar-right input[type="range"] { flex: 0 1 90px; }
/* Once there's an actual list of results, search + filters re-anchor to the
   TOP of the Results column as a proper header bar (matching how the desktop
   app keeps search/filters up top while browsing) — `order` repositions it
   without touching the DOM, so the empty-state override below can just as
   easily put it back below the message instead. */
#music-search-bottom {
  order: -1;
  flex-shrink: 0;
  border-bottom: 1px solid var(--border);
  background: var(--sidebar);
}
/* Before there's a real list to show — still searching, no results, or an
   error are all just a message, not a list — group that message with the
   search bar as one centered "hero" instead of pinning the bar to an edge
   with nothing populated below/above it. Settles to the top-bar layout above
   only once actual result rows exist. */
#music-results-panel.music-empty-state {
  justify-content: center;
}
#music-results-panel.music-empty-state #music-results {
  order: 0;
  flex: 0 1 auto;
  overflow: visible;
}
#music-results-panel.music-empty-state #music-search-bottom {
  order: 1;
  width: 100%;
  max-width: 320px;
  margin: 0 auto;
  border-bottom: none;
  background: transparent;
}
#music-results-panel.music-empty-state #music-filter-row { justify-content: center; }
#music-view-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  flex-shrink: 0;
}
.music-search-wrap {
  position: relative;
  flex: 1;
  display: flex;
}
.music-search-icon {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-3);
  pointer-events: none;
}
.music-search-input {
  flex: 1;
  padding-left: 30px;
  padding-right: 28px;
  border-radius: 17px;
}
.music-search-clear {
  position: absolute;
  right: 3px;
  top: 50%;
  transform: translateY(-50%);
  width: 24px;
  height: 24px;
}
.music-search-clear .icon { width: 13px; height: 13px; }
#music-filter-row {
  display: flex;
  gap: 6px;
  padding: 0 12px 10px;
  flex-shrink: 0;
}
.music-filter-chip {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-2);
  border-radius: 12px;
  padding: 4px 12px;
  font-size: 11px;
  cursor: pointer;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
}
.music-filter-chip:hover { background: var(--border); color: var(--text); }
.music-filter-chip.active { background: var(--accent); border-color: var(--accent); color: #fff; }
#music-panels {
  flex: 1;
  display: flex;
  min-height: 0;
}
.music-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
}
#music-results-panel { border-right: 1px solid var(--border); }
.music-panel-label {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .7px;
  color: var(--text-3);
  padding: 8px 12px 4px;
  flex-shrink: 0;
}
.music-panel-label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-right: 6px;
  flex-shrink: 0;
}
.music-panel-label-row .music-panel-label { padding-right: 0; }
.music-panel-label-row .btn { font-size: 11px; padding: 3px 10px; }
#music-results, #music-queue {
  flex: 1;
  overflow-y: auto;
  scrollbar-width: none;
}
/* Right padding reserves room for the hover-actions overlay unconditionally —
   search rows have no duration field at all (it's folded into the subtitle),
   so sizing the reservation off a duration span would leave it either missing
   (search) or mismatched (queue, whose action chip is wider than the text).
   Reserving it on the row itself, then making both duration and the action
   chip pure absolute overlays in that same slot, works for every row shape. */
.music-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 90px 8px 12px;
  border-top: 2px solid transparent;
}
.music-item:hover { background: rgba(255,255,255,.03); }
/* A filled tint + a small "now playing" badge on the thumbnail reads as
   "currently active" without redrawing the row's box (no shifted padding). */
.music-item.current { background: rgba(255,255,255,.06); }
.music-item.current .music-item-thumb-wrap::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,.45);
  border-radius: 6px;
}
.music-item-now-playing {
  display: none;
  position: absolute;
  inset: 0;
  width: 16px;
  height: 16px;
  margin: auto;
  color: #fff;
  z-index: 1;
}
.music-item.current .music-item-now-playing { display: block; }
.music-item-clickable { cursor: pointer; }
.music-item[draggable="true"] { cursor: grab; }
.music-item[draggable="true"]:active { cursor: grabbing; }
.music-item[draggable="true"] .music-item-thumb-wrap { cursor: pointer; }
.music-item.drag-over { border-top-color: var(--accent); }
.music-item-duration {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 11px;
  color: var(--text-3);
  transition: opacity var(--transition);
}
.music-item:hover .music-item-duration { opacity: 0; }
.music-item-actions {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 1px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 2px;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition);
}
.music-item:hover .music-item-actions { opacity: 1; pointer-events: auto; }
.music-item-actions .icon-btn { width: 26px; height: 26px; }
.music-item-thumb-wrap {
  position: relative;
  flex-shrink: 0;
  width: 48px;
  height: 48px;
}
.music-item-thumb {
  width: 48px;
  height: 48px;
  border-radius: 6px;
  object-fit: cover;
  background: var(--surface);
}
.music-item-thumb-empty { background: var(--border); }
.music-item-meta { flex: 1; min-width: 0; }
.music-item-title {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.music-item-artist {
  font-size: 12px;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Duration and hover-actions share the same slot at the row's end. Use
   visibility (not display) so the 52px reservation never changes — otherwise
   the title's flex:1 area expands into that space on hover, and the actions
   overlay (fixed at the row's right edge) ends up sitting on top of the
   newly-revealed title text for longer titles. */
.music-item:hover .music-item-duration { visibility: hidden; }
.music-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 32px 16px;
  text-align: center;
  color: var(--text-3);
  font-size: 12px;
}
.music-empty .icon { width: 22px; height: 22px; opacity: .5; }

/* ── Hotkeys ───────────────────────────────────────────────────────────────── */
.sb-section.open #hotkeys-body {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: 7px 10px;
}
#hotkeys-body span {
  color: var(--text-2);
  font-size: 12px;
  padding-top: 2px;
}
#hotkeys-body kbd {
  justify-self: start;
  font-family: inherit;
  font-size: 10px;
  color: var(--text-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 2px 6px;
  white-space: nowrap;
}
/* Visually separates the Alt+Shift+ tier from the plain Shift+ tier above it
   — same two-tier split the hotkey handler itself enforces (see hotkeys.js). */
.hotkeys-tier-label {
  grid-column: 1 / -1;
  margin-top: 4px;
  padding-top: 7px;
  border-top: 1px solid var(--border);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--text-3);
}

/* ── Main content ──────────────────────────────────────────────────────────── */
#main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
}

/* ── Feed ──────────────────────────────────────────────────────────────────── */
#feed-wrapper {
  position: relative;
  flex: 1;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.jump-to-bottom-btn {
  position: absolute;
  left: 50%;
  bottom: 12px;
  transform: translateX(-50%);
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0,0,0,.35);
  transition: background var(--transition);
}
.jump-to-bottom-btn:hover { background: var(--border); }

#feed {
  flex: 1;
  overflow-y: auto;
  padding: 6px 0 4px;
  scrollbar-width: none;
  display: flex;
  flex-direction: column;
}


.event {
  padding: 4px 14px;
  line-height: 1.45;
  display: flex;
  align-items: baseline;
  gap: 5px;
  min-width: 0;
}
.event:hover { background: rgba(255,255,255,.03); }

.ev-nick {
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  flex-shrink: 0;
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ev-text {
  color: var(--text);
  word-break: break-word;
  min-width: 0;
  flex: 1;
}
.ev-icon {
  width: 12px;
  height: 12px;
  vertical-align: -2px;
}
.ev-time {
  font-size: 10px;
  color: var(--text-3);
  white-space: nowrap;
  flex-shrink: 0;
  margin-left: auto;
  padding-left: 6px;
  align-self: center;
}

.event.chat   .ev-nick { color: var(--blue); }
.event.member { opacity: .5; font-size: 12px; }
.event.member .ev-nick { color: var(--text-2); }
.event.gift   { background: rgba(244,114,182,.06); }
.event.gift   .ev-nick { color: var(--pink); }

.ev-nick-clickable { cursor: pointer; text-decoration: underline dotted transparent; transition: text-decoration-color var(--transition); }
.ev-nick-clickable:hover { text-decoration-color: currentColor; }

/* ── Viewer summary popover (studio/viewer-summary.js) ───────────────────── */
.viewer-summary-popover {
  position: absolute;
  z-index: 999;
  min-width: 180px;
  max-width: 260px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 12px;
  font-size: 12px;
  color: var(--text);
  box-shadow: 0 4px 16px rgba(0,0,0,.35);
}
.viewer-summary-title {
  font-weight: 600;
  margin-bottom: 6px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.viewer-summary-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 2px 0;
  color: var(--text-2);
}
.viewer-summary-row span:last-child { color: var(--text); }
.viewer-summary-note {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--border);
  color: var(--text-2);
  word-break: break-word;
}
.event.follow { opacity: .55; font-size: 12px; }
.event.share  { opacity: .45; font-size: 12px; }
.event.like   { opacity: .4;  font-size: 12px; }
.event.system { font-size: 12px; color: var(--text-3); font-style: italic; padding: 8px 14px; }
.stream-link { color: var(--blue); text-decoration: none; font-style: normal; }
.stream-link:hover { text-decoration: underline; }

/* Display toggles hide/show event types retroactively — live feed and history
   share the same .event.<type> markup, so one rule covers both. */
body.hide-chat   .event.chat,
body.hide-member .event.member,
body.hide-gift   .event.gift,
body.hide-follow .event.follow,
body.hide-like   .event.like,
body.hide-share  .event.share {
  display: none;
}

/* ── History view ──────────────────────────────────────────────────────────── */
#history-view {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
#history-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.history-title {
  font-size: 12px;
  color: var(--text-2);
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#history-search { width: 150px; }
#history-feed-wrap {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
#history-feed {
  flex: 1;
  overflow-y: auto;
  padding: 6px 0;
  scrollbar-width: none;
}
.history-empty {
  padding: 32px 16px;
  text-align: center;
  color: var(--text-3);
  font-size: 13px;
}

/* ── Remote desktop view ───────────────────────────────────────────────────── */
#remote-view {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
#remote-canvas-wrap {
  flex: 1;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;
  outline: none;
}
#remote-canvas-wrap, #remote-canvas-wrap * { cursor: none; }
#remote-canvas-wrap:fullscreen {
  width: 100%;
  height: 100%;
}

/* ── Stream player view ────────────────────────────────────────────────────── */
#player-view {
  position: relative;
  flex: 1;
  display: flex;
  min-height: 0;
  background: #000;
}
#player-video {
  flex: 1;
  width: 100%;
  min-height: 0;
  background: #000;
}
#player-live-badge {
  position: absolute;
  left: 12px;
  top: 12px;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 9px;
  border-radius: 4px;
  background: rgba(0,0,0,.55);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  opacity: 1;
  transition: opacity .25s ease;
}
.dot-live { background: var(--red); animation: pulse 1s infinite; }

/* Shown while the stream is loading/buffering — initial load, quality
   switch, refresh, or a mid-playback stall — and hidden the moment the
   video element actually starts producing frames. */
#player-loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,.35);
  pointer-events: none;
}
#player-loading .spinner {
  width: 40px;
  height: 40px;
  border: 3px solid rgba(255,255,255,.25);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin .8s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* One toolbar, bottom — refresh/mute/volume on the left, quality/PiP/fullscreen
   on the right, like a normal live player's control bar. No seek since this is
   always live, no play/pause since there's nothing to pause. */
#player-controls {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px;
  background: linear-gradient(to top, rgba(0,0,0,.65), transparent);
  opacity: 1;
  transition: opacity .25s ease;
}
/* Idle fade — stream-player.js toggles this after a few seconds of no
   pointer/touch activity, like any normal video player's control bar. */
#player-view.controls-hidden #player-controls,
#player-view.controls-hidden #player-live-badge {
  opacity: 0;
  pointer-events: none;
}
#player-view.controls-hidden #player-video {
  cursor: none;
}
#player-controls-left,
#player-controls-right {
  display: flex;
  align-items: center;
  gap: 6px;
}
#player-controls .icon-btn {
  background: rgba(0,0,0,.55);
  color: #fff;
}
#player-controls .icon-btn:hover { background: rgba(0,0,0,.75); }
/* Volume and quality are both click-to-open popovers anchored above their
   button (not hover-based) so touch works the same as mouse — see
   togglePopover in stream-player.js. Anchored with CSS instead of the
   viewer-summary popover's body-append + getBoundingClientRect technique
   (public/js/studio/viewer-summary.js) because these live in a fixed bottom
   toolbar rather than a scrolling feed, so a static position:absolute
   anchor needs no scroll/resize recalculation. */
.player-popover-wrap {
  position: relative;
  display: flex;
  align-items: center;
}
.player-popover {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  background: rgba(20,20,20,.92);
  border: 1px solid rgba(255,255,255,.15);
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0,0,0,.45);
  z-index: 5;
}
.player-popover.hidden { display: none; }

#player-volume-popover {
  width: 36px;
  height: 110px;
  padding: 14px 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
#player-volume {
  width: 90px;
  margin: 0;
  transform: rotate(-90deg);
}

.player-quality-btn {
  height: 30px;
  font-size: 11px;
  font-weight: 600;
  color: #fff;
  background: rgba(0,0,0,.55);
  border: none;
  border-radius: var(--radius);
  padding: 0 10px;
  cursor: pointer;
}
.player-quality-btn:hover { background: rgba(0,0,0,.75); }
#player-quality-popover {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 90px;
  padding: 4px;
}
.player-quality-option {
  background: none;
  border: none;
  color: #fff;
  text-align: left;
  padding: 6px 10px;
  border-radius: 5px;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}
.player-quality-option:hover { background: rgba(255,255,255,.12); }
.player-quality-option.active { color: var(--red); }

/* ── Input-area status ─────────────────────────────────────────────────────── */
#input-conn-dot {
  position: absolute;
  left: 13px;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  z-index: 1;
}
#input-viewer-count {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 11px;
  color: var(--text-2);
  white-space: nowrap;
  flex-shrink: 0;
  align-self: center;
}

/* ── Comment input ─────────────────────────────────────────────────────────── */
#input-area {
  min-height: var(--input-h);
  display: flex;
  align-items: flex-end;
  padding: 8px 12px;
  gap: 8px;
  border-top: 1px solid var(--border);
  background: var(--sidebar);
  flex-shrink: 0;
}
.comment-wrap {
  flex: 1;
  position: relative;
  display: flex;
}
#comment-input {
  flex: 1;
  min-height: 34px;
  max-height: 120px;
  padding: 7px 40px 7px 27px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 17px;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  outline: none;
  resize: none;
  overflow-y: auto;
  line-height: 1.4;
  transition: border-color var(--transition);
  scrollbar-width: none;
}
#comment-input:focus { border-color: var(--accent); }
#comment-input::placeholder { color: var(--text-3); }
#char-count {
  position: absolute;
  bottom: 8px;
  right: 10px;
  font-size: 10px;
  color: var(--text-3);
  pointer-events: none;
  line-height: 1;
  transition: color var(--transition);
}
#char-count.warn  { color: var(--yellow); }
#char-count.limit { color: var(--red); }

/* ── Viewer count ──────────────────────────────────────────────────────────── */
.viewer-count {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 11px;
  color: var(--text-2);
  white-space: nowrap;
}

/* ── Toast ─────────────────────────────────────────────────────────────────── */
#toast {
  position: fixed;
  bottom: 66px;
  left: 50%;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 16px;
  font-size: 12px;
  color: var(--text);
  pointer-events: none;
  opacity: 0;
  transition: opacity .2s ease;
  z-index: 999;
  white-space: nowrap;
  max-width: 90vw;
  overflow: hidden;
  text-overflow: ellipsis;
}
#toast.show  { opacity: 1; }
#toast.error { border-color: var(--red); color: var(--red); }
.toast-icon { flex-shrink: 0; line-height: 1; }

/* ── Scrollbar ─────────────────────────────────────────────────────────────── */
::-webkit-scrollbar       { width: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }

/* Overlay scrollbar targets: native scrollbar fully hidden here (Firefox via
   scrollbar-width: none on the element itself, see above; WebKit/Blink via
   the rule below) — core/overlay-scrollbar.js draws a floating thumb over
   the content instead, so these panels never reserve gutter space and their
   item widths never shift when scrolling starts or stops. */
#sidebar::-webkit-scrollbar,
#music-results::-webkit-scrollbar,
#music-queue::-webkit-scrollbar,
#feed::-webkit-scrollbar,
#history-feed::-webkit-scrollbar,
#comment-input::-webkit-scrollbar,
#studio-feed::-webkit-scrollbar {
  display: none;
}
.overlay-scrollbar-thumb {
  position: fixed;
  width: 4px;
  border-radius: 4px;
  background: var(--border);
  z-index: 40;
  cursor: pointer;
  display: none;
  transition: background-color var(--transition);
}
.overlay-scrollbar-thumb:hover,
.overlay-scrollbar-thumb.dragging { background: var(--text-3); }

/* ── Utility ───────────────────────────────────────────────────────────────── */
.hidden { display: none !important; }

.hint {
  font-size: 11px;
  color: var(--text-3);
  line-height: 1.4;
}

/* ── Auth gate ─────────────────────────────────────────────────────────────── */
/* One-time overlay asking for the server-console-printed bearer token — see
   public/js/core/auth-gate.js. */
#auth-gate {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  z-index: 1000;
}
.auth-gate-box {
  width: 320px;
  max-width: calc(100vw - 40px);
  padding: 28px 24px;
  background: var(--sidebar);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-align: center;
}
.auth-gate-box h1 {
  font-size: 18px;
  margin: 0 0 8px;
  color: var(--text);
}
.auth-gate-box p {
  font-size: 12px;
  color: var(--text-2);
  line-height: 1.4;
  margin: 0 0 16px;
}

/* ── Confirm dialog ────────────────────────────────────────────────────────── */
/* Replaces the old click-twice-to-confirm buttons (Go Live/End Live, server
   shutdown) — see public/js/core/confirm-dialog.js. */
.confirm-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, .55);
  z-index: 1100;
}
.confirm-box {
  width: 300px;
  max-width: calc(100vw - 40px);
  padding: 22px 20px;
  background: var(--sidebar);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.confirm-box h3 {
  font-size: 15px;
  margin: 0 0 8px;
  color: var(--text);
}
.confirm-box p {
  font-size: 12px;
  color: var(--text-2);
  line-height: 1.4;
  margin: 0 0 18px;
}
.confirm-actions { display: flex; gap: 8px; }
.confirm-actions .btn { flex: 1; }
.btn-warn { background: var(--yellow); color: #1a1200; }
.btn-warn:not(:disabled):hover { opacity: .85; }
.auth-gate-box .field { margin-bottom: 10px; }

/* ── Studio feed ───────────────────────────────────────────────────────────── */
/* Mirrors #feed-wrapper/#feed — a second, independent event pane fed by the
   same appendEventToFeed() pipeline (routed by ev.source === 'studio'). */
#studio-view {
  position: relative;
  flex: 1;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
#studio-feed {
  flex: 1;
  overflow-y: auto;
  padding: 6px 0 4px;
  scrollbar-width: none;
  display: flex;
  flex-direction: column;
}
.studio-card-status {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 400;
  color: var(--text-2);
}
