﻿:root {
  color-scheme: dark;
  /* Real visible viewport, published every frame-worthy resize by fit() in
     universe-field.js from window.visualViewport. Prefer this over vh/vw
     units anywhere the value has to agree with what the camera is rendering:
     on a phone `100vh` is the LARGE viewport (address bar hidden) no matter
     what the address bar is currently doing, and it never shrinks for the
     on-screen keyboard, so panels sized in vh hang off the bottom of the
     screen with their last rows unreachable. The literal fallbacks only apply
     for the first paint, before fit() has run once. */
  --app-width: 100vw;
  --app-height: 100vh;
  --bg: #0d1012;
  --panel: rgba(19, 23, 27, 0.86);
  --line: rgba(170, 184, 196, 0.22);
  --text: #eef3f5;
  --muted: #9aa7af;
  --cyan: #57d7ff;
  --gold: #f2c15f;
  --green: #79d783;
  --pink: #ff7ac8;
}

* { box-sizing: border-box; }

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

html, body {
  margin: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font-family: Inter, Segoe UI, Arial, sans-serif;
  font-size: 14px;
  /* The canvas is full-screen but the topbar and panels are layered on top of
     it. Without this, a pinch whose second finger lands on an overlay is
     claimed by the browser as a page zoom, and a downward drag triggers
     pull-to-refresh / rubber-band scroll -- both of which steal the gesture
     from the scene. Scrollable panels opt back in individually below. */
  touch-action: none;
  overscroll-behavior: none;
  -webkit-text-size-adjust: 100%;
}

#scene {
  position: fixed;
  top: 0;
  left: 0;
  /* NOT `inset: 0` + 100%: that resolves against the layout viewport, while
     the WebGL drawing buffer is sized to the visual viewport. The buffer then
     gets stretched into a differently-shaped box, so the render is distorted
     and -- because every hit test converts client coords to NDC using this
     element's getBoundingClientRect() -- taps land off the star under the
     finger and the pinch anchor slides. Same numbers on both sides now. */
  width: var(--app-width, 100vw);
  height: var(--app-height, 100vh);
  display: block;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  transition: opacity 220ms ease, filter 220ms ease;
}

/* Load feedback is deliberately independent of the search/dock surfaces: it
   explains a missing archive before users can be asked to explore it. */
.load-state {
  position: fixed;
  z-index: 30;
  left: 50%;
  top: 50%;
  width: min(360px, calc(var(--app-width, 100vw) - 32px));
  transform: translate(-50%, -50%);
  padding: 16px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 12px;
  background: rgba(19, 23, 27, 0.92);
  box-shadow: 0 14px 36px rgba(0, 0, 0, 0.4);
  text-align: center;
}

.load-state[hidden] {
  display: none;
}

.load-state p {
  margin: 0;
  color: var(--text);
  line-height: 1.45;
}

.load-state button {
  margin-top: 12px;
}

/* Saved views travel rather than jump. The camera and tunable settings move
   continuously in JS; this small optical fade hides the one midpoint where
   structural layers are rebuilt around their new positions. */
body.view-transitioning #scene {
  opacity: 0.72;
  filter: brightness(0.68) blur(0.6px);
}

.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(13, 16, 18, 0.94), rgba(13, 16, 18, 0.62));
  backdrop-filter: blur(10px);
}

/* The dock is the one floating surface. Search / Stars / Path / Discover /
   Views / Notes / Display used to be five separately positioned fixed panels
   with different widths, different z-indexes and different visibility classes;
   they are now pages inside this single box, so opening any tool always puts
   it in the same place at the same size. */
.dock {
  position: fixed;
  z-index: 4;
  /* --topbar-height is measured and published by syncDockOffset() in
     universe-field.js; the fallback matches the desktop single-row topbar. */
  top: calc(var(--topbar-height, 58px) + 8px);
  right: 14px;
  width: min(400px, calc(100vw - 28px));
  max-height: calc(var(--app-height, 100vh) - var(--topbar-height, 58px) - 24px);
  overflow: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  backdrop-filter: blur(12px);
  padding: 10px;
  transition: opacity 140ms ease, transform 140ms ease;
}

.dock:not(.open) {
  opacity: 0;
  pointer-events: none;
  transform: translateY(-8px);
  /* visibility rather than display:none so the transition can run */
  visibility: hidden;
}

.dock-page {
  display: none;
}

.dock-page.active {
  display: block;
}

/* Search is its own centered "Spotlight" style overlay, not a dock page: a
   translucent scrim dims/blurs the whole field and a glass panel floats in
   the middle of the screen, closing on Escape, a tap on the scrim, or the
   round close button. Hidden via opacity/visibility (not display) so both
   the fade and the panel's own scale-in transition can run. */
.search-overlay {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: max(80px, 10vh) 24px 24px;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 160ms ease;
}

.search-overlay.open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.search-overlay-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(6, 8, 10, 0.5);
  backdrop-filter: blur(6px) saturate(120%);
  -webkit-backdrop-filter: blur(6px) saturate(120%);
}

.search-overlay-panel {
  position: relative;
  width: min(680px, 100%);
  max-height: calc(var(--app-height, 100vh) - max(80px, 10vh) - 24px);
  display: flex;
  flex-direction: column;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 12px;
  background: rgba(17, 22, 27, 0.98);
  backdrop-filter: blur(10px) saturate(130%);
  -webkit-backdrop-filter: blur(10px) saturate(130%);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
  padding: 18px;
  transform: scale(0.97) translateY(-6px);
  transition: transform 160ms ease;
}

.search-overlay.open .search-overlay-panel {
  transform: scale(1) translateY(0);
}

.search-overlay-panel > :not(.results) { flex-shrink: 0; }

.search-overlay-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.search-overlay-row input[type="search"] {
  flex: 1;
  min-width: 0;
  width: auto;
  height: 44px;
  padding: 0 11px;
  font-size: 16px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(10, 13, 15, 0.4);
}

.search-overlay-row input[type="search"]:focus {
  outline: none;
  border-color: var(--cyan);
}

.search-overlay-row input[type="search"]::-webkit-search-cancel-button {
  display: none;
}

.search-overlay-clear,
.search-overlay-close {
  flex: 0 0 auto;
  height: 36px;
  color: var(--muted);
  background: rgba(255, 255, 255, 0.07);
}

.search-overlay-clear:hover,
.search-overlay-close:hover {
  color: var(--text);
}

.search-overlay-clear {
  border-radius: 8px;
}

.search-overlay-close {
  width: 36px;
  padding: 0;
  border-radius: 50%;
  font-size: 15px;
  line-height: 1;
}

.search-overlay-close:hover {
  border-color: var(--cyan);
}

.search-overlay-panel .results {
  margin-top: 8px;
  max-height: min(calc(var(--app-height, 100vh) * 0.52), 480px);
  flex: 1;
  min-height: 0;
  overflow: auto;
  display: block;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}

.search-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.search-heading h2 { margin: 0 0 4px; font-size: 20px; letter-spacing: -0.02em; }
.search-heading p { margin: 0; font-size: 12px; color: var(--muted); }
.search-heading > kbd { color: var(--muted); font: inherit; font-size: 11px; padding-top: 4px; }
.search-tools { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 12px; }
.search-scopes { display: flex; flex-wrap: wrap; gap: 4px; }
.search-scopes button { height: 32px; padding: 0 10px; background: transparent; border-color: transparent; color: var(--muted); font-size: 12px; }
.search-scopes button[aria-pressed="true"] { color: var(--cyan); border-color: rgba(87,215,255,0.4); background: rgba(87,215,255,0.08); }
.search-sort-label { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--muted); }
.search-sort-label select { width: auto; max-width: 124px; height: 32px; padding: 0 6px; font: inherit; font-size: 12px; color: var(--text); background: #171e25; border: 1px solid var(--line); border-radius: 6px; }
.search-summary { margin: 14px 0 0; font-size: 12px; color: var(--muted); }
.search-summary:empty { display: none; }
.search-pagination { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding-top: 12px; border-top: 1px solid var(--line); margin-top: 10px; }
.search-pagination[hidden], .search-suggestions[hidden] { display: none; }
.search-pagination span { font-size: 12px; color: var(--muted); }
.search-pagination button { height: 34px; font-size: 12px; }
.search-pagination button:disabled { opacity: 0.35; cursor: default; }
.search-overlay-panel .result { display: block; min-height: 88px; height: auto; padding: 12px; margin-bottom: 6px; background: #171e25; border-color: #2a3540; border-radius: 8px; }
.search-overlay-panel .result:last-child { margin-bottom: 0; }
.search-overlay-panel .result strong { font-size: 14px; font-weight: 600; line-height: 1.4; }
.search-overlay-panel .result .result-artist { color: #d6dee5; font-size: 13px; line-height: 1.4; }
.search-overlay-panel .result .result-meta { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; margin-top: 6px; font-size: 11px; line-height: 1.4; }
.search-overlay-panel .result-meta span { font-size: inherit; margin: 0; }
.search-overlay-panel .result time { flex-shrink: 0; color: var(--muted); }
.search-overlay-panel .result:focus-visible { outline: 2px solid var(--cyan); outline-offset: -2px; background: #1b303d; }
.search-empty { padding: 28px 12px; text-align: center; }
.search-empty strong, .search-empty span { display: block; margin-bottom: 8px; }
.search-empty span { font-size: 12px; color: var(--muted); }
.search-suggestions h3 { font-size: 11px; font-weight: 500; color: var(--muted); margin: 16px 0 8px; }
.search-suggestion-list { display: flex; flex-wrap: wrap; gap: 6px; }
.search-suggestion-list button { max-width: 100%; height: auto; min-height: 34px; padding: 7px 10px; font-size: 12px; white-space: normal; text-align: left; }
.search-suggestion-list small { margin-left: 8px; color: var(--muted); }
.search-keyboard-hint { display: flex; justify-content: space-between; gap: 8px; margin-top: 14px; color: var(--muted); font-size: 11px; }
.search-keyboard-hint kbd { font: inherit; margin-right: 3px; color: #cbd6df; }

@media (max-width: 760px) {
  .search-overlay-panel { padding: 12px; }
  .search-heading { margin-bottom: 12px; }
  .search-tools { align-items: flex-start; flex-wrap: wrap; gap: 8px; }
  .search-scopes button { min-height: 36px; padding: 0 8px; }
  .search-sort-label { font-size: 0; gap: 0; }
  .search-keyboard-hint { display: none; }
  .search-overlay-panel .results { max-height: none; }
  .search-overlay-panel .result { min-height: 90px; }
  .search-heading > kbd { display: none; }
}

.search-overlay-panel .results:empty {
  margin-top: 0;
}

/* Tabs live in the topbar. They are the same buttons the panels always used,
   restyled as one segmented strip so they read as a set of views onto the same
   surface rather than six unrelated actions. */
.dock-tab {
  position: relative;
  padding: 0 10px;
  color: var(--muted);
  background: rgba(19, 23, 27, 0.7);
}

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

.dock-tab.active {
  color: var(--text);
  border-color: var(--cyan);
  background: rgba(87, 215, 255, 0.14);
}

/* Ties the open tab to the surface below it. */
.dock-tab.active::after {
  content: '';
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: -7px;
  height: 2px;
  border-radius: 2px;
  background: var(--cyan);
}

/* A repeated press minimizes the surface without forgetting its page. The
   subtle marker shows which tab will be restored on the next press. */
.dock-tab.minimized {
  color: var(--text);
  border-color: var(--line);
  background: rgba(87, 215, 255, 0.07);
}

.dock-tab.minimized::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 3px;
  width: 12px;
  height: 1px;
  transform: translateX(-50%);
  background: var(--cyan);
}

.action-button {
  flex: 0 0 auto;
}

.save-view-button {
  border-color: rgba(87, 215, 255, 0.56);
  background: rgba(87, 215, 255, 0.12);
  color: var(--text);
}

.save-view-button:hover {
  border-color: var(--cyan);
  background: rgba(87, 215, 255, 0.2);
}

.save-view-button.saved {
  border-color: var(--green);
  color: var(--green);
  background: rgba(121, 215, 131, 0.12);
}

.actions-divider {
  width: 1px;
  height: 22px;
  margin: 0 2px;
  background: var(--line);
  flex: 0 0 auto;
}

.menu-group-help {
  margin: 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.45;
}

/* Page-level layout only -- positioning now belongs to .dock above. */
.more-panel,
.library {
  display: none;
  gap: 12px;
}

.more-panel.active,
.library.active {
  display: grid;
}

.menu-group {
  display: grid;
  gap: 7px;
}

/* The group heading is a real button now: the Travel and Library tabs each
   carry three or four groups, which is more than one phone screen, so every
   section folds and the fold is remembered (see menu-groups.js). Styled back
   down from the global button rule -- it must read as a heading you can press,
   not as another control competing with the ones underneath it. */
.menu-group-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  height: auto;
  padding: 2px 0;
  border: 0;
  border-radius: 4px;
  background: none;
  text-align: left;
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.menu-group-label:hover {
  border-color: transparent;
  color: var(--text);
}

.menu-group-label:focus-visible {
  outline: 1px solid var(--cyan);
  outline-offset: 2px;
}

/* Chevron points down when open, right when folded. */
.menu-group-label::after {
  content: '';
  width: 6px;
  height: 6px;
  margin-right: 2px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 120ms ease;
}

.menu-group.collapsed > .menu-group-label::after {
  transform: translateX(-2px) rotate(-45deg);
}

.menu-group.collapsed > .menu-group-label {
  color: var(--muted);
}

.menu-group-body {
  display: grid;
  gap: 7px;
}

/* menu-groups.js sets the `hidden` attribute so folded controls leave the tab
   order too; this only guards against a stylesheet that overrides it. */
.menu-group-body[hidden] {
  display: none;
}

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

/* Toggle buttons inside a dock page get the same "on" treatment the topbar
   and the legend chips already had. Previously only .actions and
   .legend-toolbar had an .active rule, so the layer/path toggles that moved
   out of those containers had no visible on-state at all. */
.menu-group-row button.active {
  color: var(--text);
  border-color: var(--cyan);
  background: rgba(87, 215, 255, 0.14);
}

.menu-group + .menu-group {
  border-top: 1px solid var(--line);
  padding-top: 10px;
}

button.wide {
  width: 100%;
}

.search-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.bookmarks input,
.bookmarks textarea {
  width: 100%;
}

.bookmarks textarea {
  min-height: 68px;
  height: auto;
  resize: vertical;
  padding: 8px 9px;
  line-height: 1.35;
}

.bookmark-form {
  display: grid;
  gap: 8px;
}

.results {
  display: grid;
  gap: 6px;
  margin-top: 8px;
  max-height: 252px;
  overflow: auto;
}

.bookmark-list {
  display: grid;
  gap: 8px;
  margin-top: 10px;
}

.annotation-list {
  display: grid;
  gap: 8px;
  margin-top: 10px;
}

.bookmark-item,
.annotation-item {
  border: 1px solid var(--line);
  border-radius: 7px;
  padding: 8px;
  background: rgba(9, 12, 14, 0.48);
}

.bookmark-item strong,
.annotation-item strong {
  display: block;
  font-size: 13px;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.bookmark-item span,
.annotation-item span {
  display: block;
  margin-top: 3px;
  color: var(--muted);
  font-size: 11px;
  overflow-wrap: anywhere;
}

.bookmark-note {
  margin-top: 6px;
  color: var(--text);
  font-size: 12px;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.bookmark-tags {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
  margin-top: 7px;
}

.bookmark-tag {
  border: 1px solid rgba(87, 215, 255, 0.24);
  border-radius: 999px;
  padding: 2px 7px;
  color: var(--cyan);
  background: rgba(87, 215, 255, 0.08);
  font-size: 11px;
}

.bookmark-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 8px;
}

.bookmark-actions button {
  height: 28px;
  padding: 0 8px;
  font-size: 12px;
}

.result {
  width: 100%;
  height: auto;
  min-height: 40px;
  padding: 7px 8px;
  text-align: left;
  white-space: normal;
  line-height: 1.28;
}

.result strong {
  display: block;
  overflow-wrap: anywhere;
}

.result span {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: 12px;
  overflow-wrap: anywhere;
}

.detail {
  position: fixed;
  z-index: 2;
  right: 14px;
  /* The player bar appears the instant a star is picked -- the same click
     that opens this panel -- so the two are never independent the way the
     display bar and this panel are (open the Display bar or don't). Without
     the offset here, the player bar's opaque strip sits on top of this
     panel's bottom edge on literally the first star anyone ever clicks.
     --player-bar-height is 0px until universe-player.js sets it (see
     playerSelectSong), so this is a no-op before the bar has ever shown. */
  bottom: calc(14px + var(--player-bar-height, 0px));
  width: min(380px, calc(100vw - 28px));
  max-height: calc(var(--app-height, 100vh) - 370px);
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  backdrop-filter: blur(12px);
  padding: 12px;
  display: none;
}

.detail.show { display: block; }

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

.detail h2 {
  margin: 0;
  font-size: 15px;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.detail-sub {
  color: var(--muted);
  margin-top: 4px;
  overflow-wrap: anywhere;
}

.detail-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

.detail-actions a {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text);
  text-decoration: none;
  background: rgba(25, 31, 36, 0.92);
}

.detail-actions a:hover { border-color: var(--cyan); }

.detail-actions button.related-channel {
  color: var(--muted);
  max-width: 100%;
  overflow-wrap: anywhere;
  height: auto;
  min-height: 32px;
  text-align: left;
}

.detail-actions button.related-channel strong {
  display: block;
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
}

.detail-actions button.related-channel span {
  display: block;
  margin-top: 2px;
  font-size: 11px;
}

.annotation-panel {
  margin-top: 12px;
  border-top: 1px solid var(--line);
  padding-top: 10px;
}

.annotation-panel strong {
  display: block;
  margin-bottom: 7px;
  font-size: 12px;
}

.annotation-panel textarea,
.annotation-panel input {
  width: 100%;
  height: auto;
  margin-bottom: 7px;
}

.annotation-panel textarea {
  min-height: 68px;
  resize: vertical;
  padding: 8px 9px;
  line-height: 1.35;
}

.annotation-actions {
  display: flex;
  gap: 7px;
  flex-wrap: wrap;
}

.annotation-tags {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
  margin: 7px 0;
}

.annotation-tag {
  border: 1px solid rgba(87, 215, 255, 0.24);
  border-radius: 999px;
  padding: 2px 7px;
  color: var(--cyan);
  background: rgba(87, 215, 255, 0.08);
  font-size: 11px;
}

.annotation-item .annotation-note {
  margin-top: 6px;
  color: var(--text);
  font-size: 12px;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.annotation-browser-edit {
  display: grid;
  gap: 7px;
  margin-top: 8px;
}

.annotation-browser-edit[hidden] {
  display: none;
}

.annotation-browser-edit textarea,
.annotation-browser-edit input,
.annotations input {
  width: 100%;
}

.annotation-browser-edit textarea {
  min-height: 64px;
  height: auto;
  resize: vertical;
  padding: 8px 9px;
  line-height: 1.35;
}

.kv {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: 7px 10px;
  margin-top: 12px;
}

.kv span { color: var(--muted); }

.kv strong {
  font-weight: 500;
  overflow-wrap: anywhere;
}

.brand {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}

.brand h1 {
  margin: 0;
  font-size: 17px;
  line-height: 1.2;
  letter-spacing: 0;
}

.brand span {
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-end;
  min-width: 0;
}

button, input {
  font: inherit;
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 6px;
  background: rgba(25, 31, 36, 0.92);
  height: 34px;
}

button {
  padding: 0 11px;
  cursor: pointer;
}

button:hover {
  border-color: var(--cyan);
}

.actions button.active {
  color: var(--text);
  border-color: var(--cyan);
}

input {
  width: 96px;
  padding: 0 9px;
}

input[type="range"] {
  width: 112px;
  padding: 0;
  accent-color: var(--cyan);
}

.control {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: rgba(25, 31, 36, 0.72);
  color: var(--muted);
  white-space: nowrap;
  font-size: 12px;
}

.control output {
  min-width: 30px;
  color: var(--text);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* #sidePanel is the Stars page of the dock. It keeps its content styling; the
   floating-panel positioning and the .hidden fade moved to .dock. */
.panel {
  min-width: 0;
}

.stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 10px;
}

.stat {
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 8px;
  background: rgba(9, 12, 14, 0.5);
}

.stat strong {
  display: block;
  color: var(--cyan);
  font-size: 16px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.stat span, .status {
  color: var(--muted);
  font-size: 12px;
}

.legend {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  color: var(--muted);
  font-size: 12px;
}

/* (.legend-toolbar is gone: the Channels toggle moved into the Stars page's
   Layers row alongside Cascades and Artist, where it belongs.) */

.legend.collapsed button.legend-chip:not(.active) {
  display: none;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
}

button.chip {
  height: auto;
  min-height: 24px;
  padding: 3px 7px;
  color: var(--muted);
  background: rgba(25, 31, 36, 0.58);
  white-space: normal;
  text-align: left;
}

button.chip.active {
  color: var(--text);
  border-color: var(--cyan);
}

.dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--cyan);
  flex: 0 0 auto;
}

.tooltip {
  position: fixed;
  z-index: 3;
  max-width: min(360px, calc(100vw - 28px));
  pointer-events: none;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: rgba(11, 14, 17, 0.94);
  backdrop-filter: blur(10px);
  padding: 10px;
  opacity: 0;
  transform: translate(12px, 12px);
  transition: opacity 120ms ease;
}

.tooltip.show { opacity: 1; }

.focus-label {
  position: fixed;
  z-index: 3;
  width: min(420px, calc(100vw - 28px));
  transform: translate(-50%, calc(-100% - 18px));
  pointer-events: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: rgba(11, 14, 17, 0.82);
  backdrop-filter: blur(10px);
  padding: 10px 12px;
  opacity: 0;
  transition: opacity 180ms ease;
  text-align: center;
}

.focus-label.show { opacity: 1; }

.focus-label strong {
  display: block;
  overflow-wrap: anywhere;
}

.focus-label span {
  display: block;
  margin-top: 4px;
  color: var(--muted);
  font-size: 12px;
  overflow-wrap: anywhere;
}

.focus-actions {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 9px;
}

.focus-actions a {
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text);
  text-decoration: none;
  background: rgba(25, 31, 36, 0.92);
  pointer-events: auto;
}

.focus-actions a:hover {
  border-color: var(--cyan);
}

.path-inspector {
  position: fixed;
  z-index: 7;
  min-width: 260px;
  max-width: min(380px, calc(100vw - 24px));
  max-height: calc(var(--app-height, 100vh) - 96px);
  overflow: auto;
  padding: 12px;
  border: 1px solid rgba(118, 225, 255, 0.34);
  border-radius: 8px;
  background: rgba(13, 17, 20, 0.88);
  box-shadow: 0 18px 42px rgba(0, 0, 0, 0.28);
  color: #edf6fa;
  backdrop-filter: blur(12px);
  pointer-events: auto;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 120ms ease, transform 120ms ease;
}

.path-inspector.show {
  opacity: 1;
  transform: translateY(0);
}

.path-inspector h3 {
  margin: 0 0 6px;
  font-size: 13px;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.path-inspector p {
  margin: 0 0 8px;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.path-inspector .mini-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.path-inspector button,
.path-inspector a {
  border: 1px solid var(--border);
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  padding: 6px 8px;
  font: inherit;
  font-size: 12px;
  text-decoration: none;
  cursor: pointer;
}

.tooltip strong {
  display: block;
  margin-bottom: 4px;
  overflow-wrap: anywhere;
}

.tooltip div {
  color: var(--muted);
  line-height: 1.38;
  overflow-wrap: anywhere;
}

/* --- Search as the front door -------------------------------------------
   Search used to be one more label in a strip of five look-alike tabs, so the
   first thing a visitor saw was a field of dots and no obvious way in. It is
   now a magnifier that is always present and always reads as the primary
   affordance: a filled cyan pill rather than the muted outline the dock tabs
   use, kept pinned at the left edge of the strip so a horizontally scrolling
   mobile tab bar can never carry it off-screen. */
.search-tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 0 0 auto;
  padding: 0 13px 0 11px;
  color: #061014;
  background: linear-gradient(180deg, #7ee2ff, #48cbf3);
  border-color: rgba(126, 226, 255, 0.9);
  font-weight: 600;
  box-shadow: 0 0 0 1px rgba(87, 215, 255, 0.18), 0 4px 14px rgba(87, 215, 255, 0.22);
}

.search-tab:hover,
.search-tab.active {
  color: #061014;
  background: linear-gradient(180deg, #9deaff, #5fd6f8);
  border-color: #bff1ff;
}

/* The dock tabs draw an underline for the open page; search is a modal
   overlay, not a dock page, so it must not claim that same marker. */
.search-tab.active::after {
  display: none;
}

.search-tab-icon {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.1;
  stroke-linecap: round;
}

.search-hint {
  margin: 8px 2px 0;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.45;
}

.search-hint[hidden] {
  display: none;
}

.search-hint kbd {
  padding: 1px 5px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.06);
  font: inherit;
  font-size: 11.5px;
  color: var(--text);
}

/* --- Focus chip ----------------------------------------------------------
   Selecting a star or a channel dims the rest of the field. That is useful,
   but the dimming was the *only* signal that a filter existed, so a field that
   had gone dark looked broken rather than filtered. This chip names the active
   focus and carries the two ways out -- restore brightness (keeping the focus)
   and clear it outright -- which are the same two steps an empty-space tap on
   the canvas walks through. Top-left, opposite the dock, so it never sits under
   an open panel or under the mobile detail sheet. */
.field-focus {
  position: fixed;
  z-index: 3;
  top: calc(var(--topbar-height, 58px) + 10px);
  left: 14px;
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: min(360px, calc(var(--app-width, 100vw) - 28px));
  padding: 6px 8px 6px 11px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  backdrop-filter: blur(12px);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
  font-size: 12.5px;
}

.field-focus[hidden] {
  display: none;
}

.field-focus-dot {
  width: 8px;
  height: 8px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--cyan);
  box-shadow: 0 0 8px rgba(87, 215, 255, 0.8);
}

.field-focus-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
}

.field-focus button {
  flex: 0 0 auto;
  height: 26px;
  padding: 0 10px;
  border-radius: 999px;
  font-size: 12px;
  color: var(--muted);
  background: rgba(255, 255, 255, 0.07);
}

.field-focus button:hover {
  color: var(--text);
  border-color: var(--cyan);
}

.field-focus.peeking .field-focus-dot {
  background: var(--gold);
  box-shadow: 0 0 8px rgba(242, 193, 95, 0.8);
}

.field-focus.peeking #fieldFocusPeek {
  color: var(--text);
  border-color: var(--gold);
  background: rgba(242, 193, 95, 0.16);
}

/* Channel "Only": everything outside the picked channels is switched off. */
.field-focus #fieldFocusOnly[hidden] {
  display: none;
}

.field-focus.channel-only .field-focus-dot {
  background: var(--cyan);
  box-shadow: 0 0 8px rgba(98, 214, 255, 0.85);
}

.field-focus.channel-only #fieldFocusOnly {
  color: var(--text);
  border-color: var(--cyan);
  background: rgba(98, 214, 255, 0.16);
}

/* --- Channel list ---------------------------------------------------------
   Filter + stated sort order + a standing selection summary. The list used to
   collapse to only the selected chips the moment you picked one, which is what
   made selecting a second channel feel impossible: every other channel had
   vanished. */
.legend-tools {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-bottom: 8px;
}

.legend-tools input[type="search"] {
  flex: 1;
  min-width: 0;
  height: 30px;
  padding: 0 9px;
  font-size: 12.5px;
  border-radius: 7px;
  border: 1px solid var(--line);
  background: rgba(10, 13, 15, 0.45);
}

.legend-tools input[type="search"]:focus {
  outline: none;
  border-color: var(--cyan);
}

.legend-tools button {
  flex: 0 0 auto;
  height: 30px;
  padding: 0 10px;
  font-size: 12px;
  border-radius: 7px;
  color: var(--muted);
}

.legend-tools button:hover {
  color: var(--text);
  border-color: var(--cyan);
}

.legend-selection {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin-bottom: 8px;
  font-size: 12px;
}

.legend-selection[hidden] {
  display: none;
}

.legend-selection-text {
  color: var(--text);
}

.legend-selection-text strong {
  color: var(--cyan);
}

.legend-selection-note {
  color: var(--muted);
}

.legend-selection button {
  height: 26px;
  padding: 0 10px;
  border-radius: 999px;
  font-size: 11.5px;
  color: var(--muted);
}

.legend-selection button:hover {
  color: var(--text);
  border-color: var(--cyan);
}

/* Selected: a deliberate act, and the only state that gets the filled
   treatment plus a tick. Multi-select is still just "click another one" --
   this is what makes that readable rather than something you infer from
   colour. */
button.chip.legend-chip.active .chip-mark {
  margin-left: 6px;
  color: var(--cyan);
  font-size: 11px;
}

/* Linked: a CONSEQUENCE of the selection, never a selection itself. It keeps
   the plain chip shape on purpose -- giving it the filled look made one pick
   appear to have selected half the list by itself. --link-strength is that
   channel's share of the cascade links (see buildCascadeGroups), so the edge
   burns in proportion to how much of the cascade actually lands there, and
   the chip states its own percentage rather than making you guess. */
button.chip.legend-chip.linked {
  border-color: rgba(242, 193, 95, calc(0.25 + 0.75 * var(--link-strength, 0.3)));
  background: rgba(242, 193, 95, calc(0.04 + 0.14 * var(--link-strength, 0.3)));
  color: var(--text);
}

button.chip.legend-chip.linked .chip-strength {
  margin-left: 6px;
  padding: 1px 5px;
  border-radius: 999px;
  background: rgba(242, 193, 95, calc(0.14 + 0.30 * var(--link-strength, 0.3)));
  color: var(--gold);
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
}


/* --- Display bar ---------------------------------------------------------
   Display used to be a page of the top-right dock. Every control in it changes
   how the FIELD looks, so that put the panel you were reaching over directly
   on top of the surface you were trying to judge. It is a short bar along the
   bottom now -- roughly a quarter of the screen, capped so it never eats a
   short laptop viewport -- with the field fully visible above it and every
   slider live as you drag. */
.display-bar {
  position: fixed;
  z-index: 5;
  left: 14px;
  right: 14px;
  bottom: var(--player-bar-height, 0px);
  /* Deliberately NOT a raw `25vh` fallback: on a phone vh is the LARGE
     viewport regardless of what the address bar is doing, so a bar sized that
     way hangs off the bottom with its last row unreachable. --app-height is
     the real visible box, published by fit(). */
  max-height: calc(var(--app-height, 100vh) * 0.25);
  min-height: 0;
  overflow: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--line);
  border-bottom: none;
  border-radius: 12px 12px 0 0;
  background: var(--panel);
  backdrop-filter: blur(12px);
  box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.45);
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  transform: translateY(101%);
  transition: transform 180ms ease, opacity 180ms ease;
  opacity: 0;
  visibility: hidden;
}

.display-bar.open {
  transform: translateY(0);
  opacity: 1;
  visibility: visible;
}

.display-bar-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 9px;
}

.display-bar-head strong {
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.display-bar-hint {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
  font-size: 12px;
}

.display-bar-head button {
  flex: 0 0 auto;
  height: 28px;
  padding: 0 11px;
  border-radius: 999px;
  font-size: 12px;
  color: var(--muted);
}

.display-bar-head button:hover {
  color: var(--text);
  border-color: var(--cyan);
}

.display-bar-close {
  width: 28px;
  padding: 0;
  font-size: 15px;
  line-height: 1;
}

/* Four fixed columns on a wide screen, NOT auto-fit.
   auto-fit with a 190px minimum laid out seven columns at 1440px, but every
   group only has four or five controls -- so two or three columns per row sat
   completely empty while the sliders that could have used that width were
   squeezed into ~30px stubs. Pinning the track count to four hands the dead
   space back to the controls. The two step-downs below keep it readable as the
   window narrows; the phone layout drops to a single column further down. */
.display-bar-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px 12px;
  align-items: center;
}

@media (max-width: 1180px) {
  .display-bar-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 900px) {
  .display-bar-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Full-width divider inside the grid, marking where Stars / Connections /
   Field begin. grid-column: 1 / -1 forces the next control onto a fresh row,
   so each group's sliders always start together regardless of how many
   columns auto-fit happens to lay out at the current width. */
.display-bar-group {
  grid-column: 1 / -1;
  margin: 4px 0 -2px;
  padding-top: 6px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 10.5px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.display-bar-group:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.display-bar-grid .control {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  font-size: 12.5px;
  color: var(--muted);
  /* The label is a bare text node, so this is the only way to stop it wrapping
     to two lines and making its row taller than its neighbours. Safe now that
     every label in this grid is one short word. */
  white-space: nowrap;
}

.display-bar-grid .control input[type="range"] {
  flex: 1;
  /* Never let the label win the fight for width. Below roughly this the thumb
     has so little travel that a single pixel of drag is a large jump in value
     -- which is exactly how Connection brightness got parked on its floor with
     the cascade lines invisible. min-width: 0 (the old value) let the track
     collapse to nothing. */
  min-width: 96px;
  width: auto;
}

.display-bar-grid .control output {
  flex: 0 0 auto;
  min-width: 34px;
  text-align: right;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* Actions and the gesture help share one row rather than taking one each.
   With four fixed columns the Field group's four sliders fill their row
   exactly, so these two would otherwise cost two more rows of scroll inside a
   bar that is only a quarter of the viewport tall. */
.display-bar-actions {
  display: flex;
  gap: 8px;
  grid-column: 1;
}

.display-bar-actions button {
  flex: 1;
  min-width: 0;
  /* The grid column is narrow; without this "Universe Lab" wraps to two lines
     and pushes the whole row taller than the sliders beside it. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.display-bar-help {
  grid-column: 2 / -1;
  margin: 0;
  align-self: center;
  color: var(--muted);
  font-size: 11.5px;
  line-height: 1.45;
}

@media (max-width: 760px) {
  .topbar {
    align-items: stretch;
    flex-direction: column;
    gap: 8px;
    padding: max(8px, env(safe-area-inset-top)) 10px 9px;
  }

  .brand {
    display: block;
    width: 100%;
  }

  .brand h1 {
    font-size: 15px;
  }

  .brand span {
    display: block;
    margin-top: 3px;
    font-size: 12px;
  }

  .actions {
    width: 100%;
    flex-wrap: nowrap;
    justify-content: flex-start;
    gap: 6px;
    overflow-x: auto;
    overflow-y: hidden;
    /* Room for the .dock-tab.active underline, which overflow-y: hidden would
       otherwise clip off the bottom of the strip. */
    padding-bottom: 9px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .actions::-webkit-scrollbar {
    display: none;
  }

  button,
  input,
  .control {
    height: 38px;
  }

  .actions button {
    flex: 0 0 auto;
    padding: 0 10px;
    min-width: 48px;
  }

  .control {
    flex: 0 0 auto;
    padding: 0 9px;
  }

  input[type="range"] {
    width: 82px;
  }

  /* One surface means one set of mobile geometry to maintain instead of five
     that each drifted out of sync. Vertical placement comes from the measured
     --topbar-height in the base rule. */
  .dock {
    right: 10px;
    left: 10px;
    width: auto;
  }

  .detail {
    right: 10px;
    /* Same reasoning as the desktop rule above -- the player bar also wraps to
       two rows on this breakpoint (see .player-bar's own media query), so the
       offset matters even more here. */
    bottom: calc(max(10px, env(safe-area-inset-bottom)) + var(--player-bar-height, 0px));
    max-height: calc(var(--app-height, 100vh) * 0.42);
    width: calc(var(--app-width, 100vw) - 20px);
  }

  /* A modal can cover the toolbar. Keep the usable height for results when
     the phone keyboard shrinks the visual viewport. */
  .search-overlay {
    align-items: flex-start;
    padding: 10px;
    padding-top: max(10px, env(safe-area-inset-top));
  }

  .search-overlay-panel {
    max-height: calc(var(--app-height, 100vh) - 20px);
  }

  .stats {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 6px;
  }

  .stat {
    padding: 7px;
  }

  .stat strong {
    font-size: 14px;
  }

  .legend {
    gap: 6px;
  }

  button.chip {
    min-height: 30px;
    padding: 5px 8px;
  }

  .results {
    max-height: min(calc(var(--app-height, 100vh) * 0.42), 360px);
  }

  .tooltip {
    display: none;
  }

  .focus-label {
    width: calc(var(--app-width, 100vw) - 20px);
    transform: translate(-50%, calc(-100% - 10px));
  }

  .path-inspector {
    left: 10px !important;
    right: 10px;
    max-width: none;
    min-width: 0;
    width: calc(var(--app-width, 100vw) - 20px);
    max-height: calc(var(--app-height, 100vh) * 0.44);
  }
}

@media (prefers-reduced-motion: reduce) {
  #scene {
    transition: none;
  }
}

/* Short phone screens: drop the title so the tab strip and the dock keep the
   vertical room. --topbar-height picks the change up automatically. */
@media (max-width: 760px) and (max-height: 620px) {
  .brand h1 {
    display: none;
  }
}

/* html/body set touch-action: none so the scene owns every gesture. These are
   the elements that legitimately need the browser's own touch handling back:
   scrollable panels (vertical), the topbar button strip (horizontal), and
   controls that must stay tappable/draggable. */
.dock,
.detail,
.results,
.legend,
.bookmark-list,
.annotation-list,
.path-inspector {
  touch-action: pan-y;
  overscroll-behavior: contain;
}

.actions {
  touch-action: pan-x;
  overscroll-behavior: contain;
}

button,
select,
textarea,
input:not([type="range"]) {
  touch-action: manipulation;
}

input[type="range"] {
  touch-action: none;
}

@media (pointer: coarse) {
  button,
  input,
  .control {
    min-height: 38px;
  }

  /* Bigger targets for finger taps on the two most-used strips. */
  .actions button {
    min-height: 40px;
  }

  button.chip {
    min-height: 34px;
  }
}

@media (max-width: 760px) {
  /* The tab strip scrolls horizontally on a phone; search is the one control
     that must never scroll out of reach, so it sticks to the left edge of the
     strip with the panel's own background behind it. */
  .search-tab {
    position: sticky;
    left: 0;
    z-index: 1;
    padding: 0 11px;
  }

  .search-tab-text {
    display: none;
  }

  .search-tab-icon {
    width: 18px;
    height: 18px;
  }

  .field-focus {
    left: 10px;
    right: 10px;
    max-width: none;
    font-size: 12px;
  }
}

@media (max-width: 760px) {
  /* A quarter of a phone screen is not enough for five sliders, and the bar is
     scrollable anyway -- give it more room and let it scroll. */
  .display-bar {
    left: 0;
    right: 0;
    max-height: calc(var(--app-height, 100vh) * 0.42);
    padding: 10px 10px calc(10px + env(safe-area-inset-bottom));
    border-left: none;
    border-right: none;
  }

  .display-bar-grid {
    grid-template-columns: 1fr;
  }

  /* One column: the shared actions/help row above has nothing to share with. */
  .display-bar-actions,
  .display-bar-help {
    grid-column: 1 / -1;
  }

  .display-bar-help {
    margin-top: 2px;
  }

  .display-bar-hint {
    display: none;
  }

  /* The detail card and the focus chip both sit at the bottom on a phone; lift
     the detail card clear when the bar is open rather than stacking them. */
  .display-bar.open ~ .detail {
    bottom: calc(var(--app-height, 100vh) * 0.42 + 10px);
  }
}

/* ---- Player bar -----------------------------------------------------------
   Bottom-anchored like the display bar, but always visible once a star has
   been picked. The display bar shifts up by --player-bar-height (set by
   universe-player.js) so the two never overlap. */
.archive-time-control {
  position: fixed;
  z-index: 4;
  left: 14px;
  right: 14px;
  bottom: calc(var(--player-bar-height, 0px) + 12px);
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 42px;
  padding: 7px 11px;
  border: 1px solid var(--line);
  border: 1px solid color-mix(in srgb, var(--line) 86%, var(--accent, #7aa2f7));
  border-radius: 10px;
  background: var(--panel);
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  backdrop-filter: blur(10px);
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.28);
}

.archive-time-control[hidden] {
  display: none;
}

.archive-time-control button {
  flex: 0 0 auto;
  min-width: 52px;
  font: inherit;
  font-size: 12px;
  padding: 5px 9px;
  color: var(--text, inherit);
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel-2, rgba(255, 255, 255, 0.06));
  cursor: pointer;
}

.archive-time-control button:hover,
.archive-time-control button[aria-pressed="true"] {
  border-color: var(--accent, #7aa2f7);
  color: var(--accent, #7aa2f7);
}

.archive-time-control select {
  flex: 0 0 auto;
  width: 48px;
  font: inherit;
  font-size: 11px;
  padding: 5px 3px;
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel-2, rgba(255, 255, 255, 0.06));
}

.archive-time-range-label {
  flex: 1 1 180px;
  min-width: 90px;
}

.archive-time-range-label input {
  display: block;
  width: 100%;
  accent-color: var(--accent, #7aa2f7);
}

.archive-time-track {
  position: relative;
  display: block;
}

.archive-time-track input {
  position: relative;
  z-index: 1;
}

.archive-time-markers {
  position: absolute;
  z-index: 2;
  top: 50%;
  right: 7px;
  left: 7px;
  height: 12px;
  transform: translateY(-50%);
  pointer-events: none;
}

.archive-time-marker {
  position: absolute;
  top: 1px;
  width: 7px;
  min-width: 0;
  height: 10px;
  padding: 0;
  transform: translateX(-50%);
  border: 1px solid rgba(13, 16, 18, 0.75);
  border-radius: 2px;
  background: var(--gold);
  box-shadow: 0 0 7px rgba(242, 193, 95, 0.7);
  pointer-events: auto;
}

.archive-time-marker.start,
.archive-time-marker.latest {
  background: var(--cyan);
}

.archive-time-readout {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  min-width: 108px;
  line-height: 1.18;
}

.archive-time-readout strong {
  font-size: 12px;
}

.archive-time-readout span,
.archive-time-count {
  color: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.archive-depth-indicator,
.archive-discovery-status,
.archive-minimap {
  position: fixed;
  z-index: 3;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  backdrop-filter: blur(10px);
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.25);
}

.archive-depth-indicator {
  left: 14px;
  top: 50%;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px;
  transform: translateY(-50%);
}

.archive-depth-indicator[hidden],
.archive-discovery-status[hidden],
.archive-minimap[hidden] {
  display: none;
}

.archive-depth-scale {
  position: relative;
  width: 6px;
  height: 72px;
  overflow: hidden;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
}

.archive-depth-scale span {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  border-radius: inherit;
  background: linear-gradient(0deg, var(--gold), var(--pink), var(--cyan));
  transition: height 140ms ease;
}

.archive-depth-indicator strong,
.archive-discovery-status strong {
  display: block;
  font-size: 12px;
}

.archive-depth-indicator span,
.archive-discovery-status span {
  display: block;
  color: var(--muted);
  font-size: 11px;
}

.archive-discovery-status {
  top: calc(var(--topbar-height, 58px) + 12px);
  right: 14px;
  padding: 9px 11px;
}

.archive-minimap {
  right: 14px;
  bottom: calc(var(--player-bar-height, 0px) + 66px);
  overflow: hidden;
}

.archive-minimap canvas {
  display: block;
  width: 132px;
  height: 132px;
  cursor: crosshair;
}

.archive-time-count {
  flex: 0 0 auto;
  white-space: nowrap;
}

.player-bar {
  position: fixed;
  z-index: 6;
  left: 14px;
  right: 14px;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 14px calc(8px + env(safe-area-inset-bottom));
  border: 1px solid var(--line);
  border-bottom: none;
  border-radius: 12px 12px 0 0;
  background: var(--panel);
  backdrop-filter: blur(12px);
  box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.45);
  transform: translateY(101%);
  transition: transform 180ms ease, opacity 180ms ease;
  opacity: 0;
  visibility: hidden;
}

.player-bar.show {
  transform: translateY(0);
  opacity: 1;
  visibility: visible;
}

.player-controls {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
}

.player-controls button {
  font: inherit;
  font-size: 12px;
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, 0.06));
  color: var(--text, inherit);
  cursor: pointer;
}

.player-controls button:hover {
  border-color: var(--accent, #7aa2f7);
}

.player-controls button.active {
  border-color: var(--accent, #7aa2f7);
  color: var(--accent, #7aa2f7);
}

.player-track {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1 1 auto;
}

.player-track strong {
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.player-track span {
  font-size: 11px;
  opacity: 0.75;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.player-status {
  color: var(--accent, #7aa2f7);
}

.player-status-link {
  display: inline-block;
  font-size: 11px;
  color: var(--accent, #7aa2f7);
  text-decoration: underline;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.player-status-link:hover,
.player-status-link:focus-visible {
  opacity: 0.85;
}

.player-status-link[hidden] {
  display: none;
}

.player-seek {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 1 320px;
  min-width: 120px;
}

.player-seek input[type="range"] {
  flex: 1 1 auto;
  min-width: 60px;
  accent-color: var(--accent, #7aa2f7);
}

.player-seek span {
  font-size: 11px;
  opacity: 0.8;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 760px) {
  .archive-time-control {
    left: 0;
    right: 0;
    bottom: calc(var(--player-bar-height, 0px) + 8px);
    gap: 7px;
    padding: 7px 10px;
    border-left: none;
    border-right: none;
    border-radius: 0;
  }

  .archive-time-readout {
    min-width: 82px;
  }

  .archive-time-control select {
    width: 44px;
  }

  .archive-time-count {
    display: none;
  }

  .archive-depth-indicator,
  .archive-discovery-status {
    display: none;
  }

  .archive-minimap {
    right: 8px;
    bottom: calc(var(--player-bar-height, 0px) + 62px);
  }

  .archive-minimap canvas {
    width: 92px;
    height: 92px;
  }

  .player-bar {
    left: 0;
    right: 0;
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px 10px calc(8px + env(safe-area-inset-bottom));
    border-left: none;
    border-right: none;
  }

  .player-track {
    order: -1;
    flex-basis: 100%;
  }

  .player-seek {
    flex: 1 1 auto;
    min-width: 0;
  }
}


/* --- Merged archive HUD ---------------------------------------------------
   Depth gauge + discovery status + minimap in one card, bottom-left (the dock
   and detail panel own the right edge). Specificity (.archive-hud .x) is
   deliberate: it must beat both the base and the mobile rules above. */
.archive-hud {
  position: fixed;
  z-index: 3;
  left: 14px;
  bottom: calc(var(--player-bar-height, 0px) + 66px);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  max-width: calc(var(--app-width, 100vw) - 28px);
  transition: opacity 400ms ease;
}

.archive-hud:not(:has(.archive-hud-body > :not([hidden]))) {
  display: none;
}

.archive-hud-toggle {
  padding: 4px 10px;
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  backdrop-filter: blur(10px);
  cursor: pointer;
}

.archive-hud-toggle::after {
  content: ' \25BE';
}

.archive-hud.collapsed .archive-hud-toggle::after {
  content: ' \25B4';
}

.archive-hud-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
  backdrop-filter: blur(10px);
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.25);
}

.archive-hud.collapsed .archive-hud-body {
  display: none;
}

.archive-hud .archive-depth-indicator,
.archive-hud .archive-discovery-status,
.archive-hud .archive-minimap {
  position: static;
  transform: none;
  border: none;
  background: none;
  box-shadow: none;
  backdrop-filter: none;
  padding: 0;
}

.archive-hud .archive-depth-indicator { display: flex; }
.archive-hud .archive-depth-indicator[hidden],
.archive-hud .archive-discovery-status[hidden],
.archive-hud .archive-minimap[hidden] { display: none; }
.archive-hud .archive-discovery-status { display: block; }
.archive-hud .archive-depth-scale { height: 40px; }
.archive-hud .archive-minimap { border-radius: 8px; }
.archive-hud .archive-minimap canvas { width: 120px; height: 120px; }

@media (max-width: 760px) {
  .archive-hud { left: 8px; bottom: calc(var(--player-bar-height, 0px) + 62px); }
  .archive-hud .archive-discovery-status { display: none; }
  .archive-hud .archive-minimap canvas { width: 92px; height: 92px; }
}

/* Never share the bottom edge with the display bar. */
body:has(.display-bar.open) .archive-hud { display: none; }

/* Idle fade: dims, never hides, and never while hovered or focused. */
.archive-time-control { transition: opacity 400ms ease; }
body.hud-idle .archive-hud:not(:hover):not(:focus-within),
body.hud-idle .archive-time-control:not(:hover):not(:focus-within) {
  opacity: 0.14;
}

.player-close {
  width: 28px;
  padding: 0;
  font-size: 15px;
  line-height: 1;
}

/* --- Phone: one bottom stack, selection mode --------------------------------
   On a phone the selection (detail panel + player) owns the bottom of the
   screen. Everything else that lived down there steps aside while a star is
   selected, and returns the moment the detail panel is minimized/cleared. This
   is what stops the HUD chip/minimap and the time bar from drawing on top of
   the detail panel. */
@media (max-width: 760px) {
  body:has(.detail.show) .archive-hud,
  body:has(.detail.show) .archive-time-control {
    display: none;
  }

  /* Detail is a compact sheet, not a second screen. */
  .detail {
    max-height: calc(var(--app-height, 100vh) * 0.30);
    padding: 10px 12px;
  }
  .detail h2 { font-size: 15px; }
  .detail-head { margin-bottom: 6px; }
  .detail-actions { margin-top: 6px; gap: 6px; }

  /* The player already says "streaming unavailable"; the topbar copy of the same
     status is a third repeat of it. */
  body:has(.player-bar.show) .brand #status {
    display: none;
  }
}
/* --- Zoom profile row (display bar) ------------------------------------------ */
.zoom-profile {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 9px;
  padding-bottom: 9px;
  border-bottom: 1px solid var(--line);
}

.zoom-profile > button.active {
  border-color: var(--cyan);
  color: var(--cyan);
}

.zoom-stops {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.zoom-stops-empty {
  color: var(--muted);
  font-size: 12px;
}

.zoom-stop {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--line);
  border-radius: 7px;
  overflow: hidden;
}

.zoom-stop button {
  min-width: 0;
  padding: 3px 8px;
  font-size: 12px;
  border: none;
  border-radius: 0;
  font-variant-numeric: tabular-nums;
}

.zoom-stop button[data-zoom-del] {
  padding: 3px 6px;
  color: var(--muted);
  border-left: 1px solid var(--line);
}

/* The stop nearest the current zoom, i.e. the one the sliders are editing. */
.zoom-stop.near {
  border-color: var(--cyan);
  box-shadow: 0 0 0 1px var(--cyan) inset;
}
/* --- Timeline is opt-in ------------------------------------------------------
   The timelapse bar only shows after the Timeline button is pressed
   (body.timeline-open). While it is closed it takes no space: the Archive card
   drops down to where the bar used to be. */
body:not(.timeline-open) .archive-time-control {
  display: none;
}

body:not(.timeline-open) .archive-hud {
  bottom: calc(var(--player-bar-height, 0px) + 14px);
}

@media (max-width: 760px) {
  body:not(.timeline-open) .archive-hud {
    bottom: calc(var(--player-bar-height, 0px) + 10px);
  }
}
/* Time-window readout (replaces the old vertical depth gauge). */
.archive-hud .archive-depth-scale { display: none; }
.archive-hud .archive-depth-indicator strong { font-size: 13px; }
.archive-hud .archive-depth-indicator select {
  margin-top: 6px;
  width: 100%;
  padding: 4px 6px;
  font: inherit;
  font-size: 12px;
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel-2, rgba(255, 255, 255, 0.06));
}

/* Custom zoom distance: type any distance, Go moves there, + Stop saves a stop. */
.zoom-custom {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.zoom-custom input[type="number"] {
  width: 84px;
  min-width: 0;
  padding: 4px 6px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
