/* MediaShare player -- dark, responsive, keyboard-accessible. */

:root {
  /* Tells the browser to render native widgets -- the open select list, date
     pickers, scrollbars -- in dark mode, so they match the rest of the UI
     instead of appearing as bright white panels. */
  color-scheme: dark;

  --bg: #0b0d10;
  --surface: #14171c;
  --surface-2: #1c2128;
  --border: #262b33;
  --text: #e7ebf0;
  --text-dim: #98a2b3;
  --accent: #ff2d55;
  --accent-soft: rgba(255, 45, 85, 0.25);
  --radius: 12px;
  --radius-sm: 8px;
  --ctl-size: 40px;
  --shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; }

/* The paired control icons -- play/pause, volume/mute, enter/exit fullscreen --
   are toggled purely by the hidden attribute. Browsers are not uniform about
   applying the UA [hidden] rule to inline SVG, and when it does not apply both
   variants of a pair paint on top of each other. Stating it here removes the
   dependency on the UA stylesheet entirely. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
}

body { min-height: 100vh; }

.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;
}

/* ---------- Layout ---------- */

.player-shell {
  max-width: 1180px;
  margin: 0 auto;
  padding: 24px 20px 64px;
  position: relative;
  z-index: 1;
}

@media (max-width: 720px) {
  .player-shell { padding: 0 0 40px; }
}

/* ---------- Stage ---------- */

.player-stage {
  position: relative;
  background: #000;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
  aspect-ratio: 16 / 9;
  user-select: none;
}

@media (max-width: 720px) {
  .player-stage { border-radius: 0; }
}

.player-stage.is-fullscreen { aspect-ratio: auto; height: 100vh; border-radius: 0; }

.player-video {
  width: 100%;
  height: 100%;
  display: block;
  background: #000;
  object-fit: contain;
}

/* The 360 canvas covers the video element completely and takes the pointer
   events the picture would otherwise get. touch-action must be none or the
   browser scrolls the page instead of turning the view. */
.player-sphere {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  background: #000;
  touch-action: none;
  cursor: grab;
}
.player-sphere.is-grabbing { cursor: grabbing; }
.player-stage.is-idle.is-playing .player-sphere { cursor: none; }

/* Audio has no picture, so the video element only carries the audio track and
   the artwork panel takes the stage instead. */
.kind-audio .player-stage { aspect-ratio: 16 / 9; background: transparent; }
.kind-audio .player-video { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

/* ---------- Audio skin ---------- */

.audio-backdrop {
  position: fixed;
  inset: 0;
  background-size: cover;
  background-position: center;
  filter: blur(64px) saturate(1.6) brightness(0.45);
  transform: scale(1.2);
  z-index: 0;
}
.audio-backdrop::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(11,13,16,0.35), rgba(11,13,16,0.9));
}

.audio-art {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  padding: 32px 24px 110px;
  background: linear-gradient(160deg, #1a1d24, #0d0f13);
}

.audio-cover {
  width: min(240px, 40vh);
  aspect-ratio: 1;
  border-radius: var(--radius);
  object-fit: cover;
  box-shadow: 0 16px 48px rgba(0,0,0,0.6);
  /* min-height:0 overrides the automatic minimum size a flex item gets, which
     is what lets the cover shrink instead of overflowing a short panel; the
     aspect ratio keeps it square as it does. */
  flex: 0 1 auto;
  min-height: 0;
  min-width: 0;
}

.audio-cover-empty {
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, var(--surface-2), var(--surface));
  color: var(--text-dim);
  border: 1px solid var(--border);
}
.audio-cover-empty svg { width: 42%; height: 42%; }

/* Groups everything that is not the cover, so a landscape phone can lay the
   panel out as a row without reparenting anything. */
.audio-side {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  min-width: 0;
  /* When the panel is shorter than its contents the cover gives way, not the
     title and the transport buttons. */
  flex: none;
}

.audio-meta { text-align: center; max-width: 90%; }
.audio-title {
  margin: 0;
  font-size: clamp(1.05rem, 2.6vw, 1.5rem);
  font-weight: 600;
  line-height: 1.3;
}
.audio-duration { margin: 6px 0 0; color: var(--text-dim); font-size: 0.9rem; }

/* A record-player style transport, the way a listener expects a music page to
   behave. It also gives the audio skin a large, unambiguous play target, which
   is why the centred .big-play overlay is dropped for this kind. */
.audio-transport { display: flex; align-items: center; gap: 16px; flex: none; }
.kind-audio .big-play { display: none; }

/* The number inside the skip glyphs needs the extra pixels to stay legible. */
.transport-skip svg { width: 26px; height: 26px; }
.transport-skip text { font-family: var(--font); }

.audio-play {
  width: 64px; height: 64px;
  border-radius: 50%;
  border: none;
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  display: grid;
  place-items: center;
  flex: none;
  transition: background 0.15s, transform 0.15s;
}
.audio-play svg { width: 30px; height: 30px; }
/* A triangle's visual centre sits left of its bounding box. */
.audio-play .i-play { margin-left: 3px; }
.audio-play:hover { background: #e01f45; transform: scale(1.05); }
.audio-play:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }

/* ---------- Loading / big play ---------- */

.player-loading {
  position: absolute;
  inset: 0;
  display: none;
  place-items: center;
  pointer-events: none;
}
.player-stage.is-loading .player-loading { display: grid; }

.spinner {
  width: 44px; height: 44px;
  border: 3px solid rgba(255,255,255,0.18);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 2s; }
}

.big-play {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 76px; height: 76px;
  border-radius: 50%;
  border: none;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(8px);
  color: #fff;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: opacity 0.2s, transform 0.2s, background 0.2s;
}
.big-play svg { width: 40px; height: 40px; margin-left: 4px; }
.big-play:hover { background: var(--accent); transform: translate(-50%, -50%) scale(1.06); }
.player-stage.is-playing .big-play,
.player-stage.is-loading .big-play { opacity: 0; pointer-events: none; transform: translate(-50%,-50%) scale(0.85); }

.player-error {
  position: absolute;
  inset: auto 16px 96px;
  padding: 12px 16px;
  border-radius: var(--radius-sm);
  background: rgba(180, 30, 45, 0.92);
  color: #fff;
  font-size: 0.9rem;
  text-align: center;
}

/* ---------- Controls ---------- */

.controls {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 40px 12px 10px;
  background: linear-gradient(transparent, rgba(0,0,0,0.55) 45%, rgba(0,0,0,0.88));
  opacity: 1;
  transition: opacity 0.25s;
}
.player-stage.is-playing.is-idle .controls { opacity: 0; pointer-events: none; }
.player-stage.is-idle.is-playing { cursor: none; }

/* The audio skin keeps its controls permanently visible: there is no picture
   to get out of the way of. */
.kind-audio .player-stage.is-playing.is-idle .controls { opacity: 1; pointer-events: auto; }
.kind-audio .player-stage.is-idle.is-playing { cursor: default; }

.control-row {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 6px;
}
.spacer { flex: 1; }

.ctl {
  width: var(--ctl-size);
  height: var(--ctl-size);
  border: none;
  background: transparent;
  color: #fff;
  cursor: pointer;
  border-radius: var(--radius-sm);
  display: grid;
  place-items: center;
  flex: none;
  transition: background 0.15s;
}
.ctl svg { width: 22px; height: 22px; }
.ctl:hover { background: rgba(255,255,255,0.14); }
.ctl:focus-visible, .scrubber:focus-visible, .menu-item:focus-visible, .btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.ctl.is-active { color: var(--accent); background: rgba(255,255,255,0.14); }

.ctl.text {
  width: auto;
  min-width: var(--ctl-size);
  padding: 0 10px;
  font-size: 0.82rem;
  font-weight: 600;
  font-family: inherit;
}

.time {
  font-variant-numeric: tabular-nums;
  font-size: 0.82rem;
  color: rgba(255,255,255,0.85);
  padding: 0 8px;
  white-space: nowrap;
}

.volume-group { display: flex; align-items: center; }
.volume {
  width: 0;
  opacity: 0;
  margin-left: 0;
  transition: width 0.2s, opacity 0.2s, margin-left 0.2s;
  accent-color: var(--accent);
  cursor: pointer;
}
.volume-group:hover .volume,
.volume:focus-visible { width: 80px; opacity: 1; margin-left: 6px; }

@media (max-width: 560px) {
  .volume-group:hover .volume { width: 60px; }
  .ctl.text { padding: 0 6px; font-size: 0.75rem; }
  .time { font-size: 0.75rem; padding: 0 4px; }
}

/* ---------- Scrubber ---------- */

.scrubber {
  position: relative;
  height: 28px;
  display: flex;
  align-items: center;
  cursor: pointer;
  touch-action: none;
}

.waveform {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.5;
  pointer-events: none;
}

.track {
  position: relative;
  width: 100%;
  height: 4px;
  border-radius: 2px;
  background: rgba(255,255,255,0.26);
  transition: height 0.12s;
}
.scrubber:hover .track,
.scrubber:focus-visible .track,
.scrubber.is-dragging .track { height: 6px; }

.buffered, .played {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  border-radius: 2px;
}
.buffered { background: rgba(255,255,255,0.32); width: 0; }
.played { background: var(--accent); width: 0; }

.handle {
  position: absolute;
  top: 50%;
  width: 13px; height: 13px;
  border-radius: 50%;
  background: var(--accent);
  transform: translate(-50%, -50%) scale(0);
  transition: transform 0.12s;
  left: 0;
  pointer-events: none;
}
.scrubber:hover .handle,
.scrubber:focus-visible .handle,
.scrubber.is-dragging .handle { transform: translate(-50%, -50%) scale(1); }

.chapters { position: absolute; inset: 0; pointer-events: none; }
.chapter-mark {
  position: absolute;
  top: 0; bottom: 0;
  width: 2px;
  background: rgba(0,0,0,0.65);
  transform: translateX(-1px);
}

.preview {
  position: absolute;
  bottom: 26px;
  transform: translateX(-50%);
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  z-index: 3;
}
.preview-thumb {
  width: 160px;
  height: 90px;
  border-radius: 6px;
  border: 2px solid rgba(255,255,255,0.85);
  background-repeat: no-repeat;
  box-shadow: 0 6px 20px rgba(0,0,0,0.6);
  background-color: #000;
}
.preview-time {
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  background: rgba(0,0,0,0.85);
  padding: 2px 7px;
  border-radius: 4px;
  color: #fff;
}
.preview-plain { bottom: 22px; }

/* ---------- Menus ---------- */

.menu-wrap { position: relative; flex: none; }

.menu {
  position: absolute;
  bottom: calc(100% + 8px);
  right: 0;
  min-width: 150px;
  max-height: 300px;
  overflow-y: auto;
  background: rgba(20, 23, 28, 0.97);
  backdrop-filter: blur(12px);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 5px;
  box-shadow: var(--shadow);
  z-index: 5;
}

.menu-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  background: transparent;
  color: var(--text);
  font-size: 0.85rem;
  font-family: inherit;
  text-align: left;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
}
.menu-item:hover { background: rgba(255,255,255,0.1); }
.menu-item.is-active { color: var(--accent); font-weight: 600; }
.menu-item.is-active::after { content: "✓"; margin-left: auto; }
.menu-time { font-variant-numeric: tabular-nums; color: var(--text-dim); flex: none; }
.menu-label { overflow: hidden; text-overflow: ellipsis; }
.menu-sub { color: var(--text-dim); font-size: 0.75rem; margin-left: auto; }

/* ---------- Info panel ---------- */

.info { margin-top: 24px; }
@media (max-width: 720px) { .info { padding: 0 16px; } }

.info-title {
  margin: 0 0 14px;
  font-size: clamp(1.15rem, 3vw, 1.6rem);
  font-weight: 600;
  line-height: 1.3;
}

.info-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin-bottom: 18px;
}

/* These are secondary actions sitting directly under the video, so they are
   deliberately quiet: compact, ghost-styled and icon-led. At full .btn size
   they dominated the area beneath the player and drew the eye away from the
   title and description. */
.info-actions .btn {
  padding: 5px 10px;
  font-size: 0.78rem;
  font-weight: 500;
  gap: 5px;
  background: transparent;
  border-color: var(--border);
  color: var(--text-dim);
}
.info-actions .btn:hover {
  background: var(--surface-2);
  border-color: #333a45;
  color: var(--text);
}
.info-actions .btn svg {
  width: 13px;
  height: 13px;
  flex: none;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  font-size: 0.85rem;
  font-family: inherit;
  font-weight: 500;
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s, border-color 0.15s;
}
.btn:hover { background: #252b34; border-color: #333a45; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-primary:hover { background: #e01f45; border-color: #e01f45; }

.copy-note { font-size: 0.8rem; color: #4ade80; }

.hint {
  font-size: 0.85rem;
  color: var(--text-dim);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  margin: 0 0 18px;
  line-height: 1.5;
}

.description {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 18px;
  line-height: 1.65;
  font-size: 0.94rem;
  overflow-wrap: anywhere;
}
.description > :first-child { margin-top: 0; }
.description > :last-child { margin-bottom: 0; }
.description a { color: #6ea8fe; }
.description h1, .description h2, .description h3 { line-height: 1.3; margin: 1.2em 0 0.5em; }
.description h1 { font-size: 1.3rem; }
.description h2 { font-size: 1.15rem; }
.description h3 { font-size: 1rem; }
.description code {
  background: var(--surface-2);
  padding: 2px 5px;
  border-radius: 4px;
  font-size: 0.88em;
}
.description pre {
  background: var(--surface-2);
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  overflow-x: auto;
}
.description pre code { background: none; padding: 0; }
.description blockquote {
  margin: 1em 0;
  padding-left: 14px;
  border-left: 3px solid var(--border);
  color: var(--text-dim);
}
.description table { border-collapse: collapse; width: 100%; display: block; overflow-x: auto; }
.description th, .description td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; }
.description img { max-width: 100%; height: auto; border-radius: var(--radius-sm); }

/* Timestamp links generated from the description. */
.description a.ms-ts {
  color: var(--accent);
  text-decoration: none;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  padding: 1px 4px;
  border-radius: 4px;
  background: var(--accent-soft);
}
.description a.ms-ts:hover { background: var(--accent); color: #fff; }

/* ---------- Tracklist ---------- */

.tracklist { margin-top: 22px; }
.tracklist h2 { font-size: 0.95rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); margin: 0 0 10px; }
.tracklist ol { list-style: none; margin: 0; padding: 0; }
.tracklist li + li { border-top: 1px solid var(--border); }

.ts-link {
  display: flex;
  gap: 12px;
  width: 100%;
  padding: 10px 12px;
  background: transparent;
  border: none;
  color: var(--text);
  font-family: inherit;
  font-size: 0.9rem;
  text-align: left;
  cursor: pointer;
  border-radius: var(--radius-sm);
  align-items: baseline;
}
.ts-link:hover { background: var(--surface); }
.ts-link.is-current { background: var(--surface-2); }
.ts-link.is-current .ts { color: var(--accent); }
.ts { color: var(--text-dim); font-variant-numeric: tabular-nums; flex: none; min-width: 52px; font-weight: 600; }

/* ---------- Password / denied gates ---------- */

.gate-body { display: grid; place-items: center; min-height: 100vh; padding: 20px; }

.gate { width: 100%; max-width: 400px; }

.gate-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 36px 30px;
  text-align: center;
  box-shadow: var(--shadow);
}

.gate-icon {
  width: 52px; height: 52px;
  margin: 0 auto 18px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--surface-2);
  color: var(--accent);
}
.gate-icon svg { width: 26px; height: 26px; }

.gate-card h1 { margin: 0 0 8px; font-size: 1.25rem; font-weight: 600; }
.gate-sub { margin: 0 0 22px; color: var(--text-dim); font-size: 0.9rem; line-height: 1.5; }
.gate-hint { margin: 16px 0 0; color: var(--text-dim); font-size: 0.8rem; }

.gate-error {
  background: rgba(220, 38, 60, 0.14);
  border: 1px solid rgba(220, 38, 60, 0.4);
  color: #ff8098;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  margin: 0 0 16px;
}

.gate-form { display: flex; flex-direction: column; gap: 10px; }

.gate-form input[type="password"],
.gate-form input[type="email"],
.gate-form input[type="text"] {
  width: 100%;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
  font-family: inherit;
}
.gate-form input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.gate-form .btn { justify-content: center; padding: 11px; }

/* ---------- Mobile and touch ----------
 *
 * Everything below is scoped to .player-body or to a class that only the
 * player page uses. This stylesheet is loaded by every template -- the admin
 * pages layer admin.css on top of it -- so an unscoped rule here would move
 * the whole application, not just the player.
 *
 * The section is deliberately last in the file: the rules it overrides
 * (.player-stage's aspect lock, .kind-audio's stage rules, the .handle
 * transform) live above it, so source order does the work and no selector
 * needs to be inflated to win.
 */

/* Shown only when a compact control button swaps its word for a glyph. */
.ctl-icon { display: none; }

/* How much vertical room the control bar needs. The audio panel pads its
   bottom by this so the artwork never slides under the scrubber. */
.player-body { --controls-h: 96px; }

.player-body .player-stage { -webkit-tap-highlight-color: transparent; }

/* Pseudo-fullscreen.
 *
 * iPhone Safari implements fullscreen for <video> only -- Element.requestFullscreen
 * does not exist there -- and the native video fullscreen it does offer replaces
 * this control surface with Apple's own, which for a 360 item means showing the
 * raw equirectangular frame. So 360-on-iPhone, and any browser with no fullscreen
 * API at all, gets this instead: the stage pinned over the viewport with its own
 * controls intact. z-index clears .audio-backdrop (0) and .player-shell (1).
 */
.player-body.is-immersive { overflow: hidden; overscroll-behavior: none; }
.player-body.is-immersive .player-stage {
  position: fixed;
  inset: 0;
  z-index: 50;
  height: 100vh;
  height: 100dvh;
  aspect-ratio: auto;
  border-radius: 0;
}

/* Fullscreen of either kind puts the control bar on the very edge of the
   screen, where the home indicator and the notch live. */
.player-body .player-stage.is-fullscreen .controls {
  padding-left: max(12px, env(safe-area-inset-left));
  padding-right: max(12px, env(safe-area-inset-right));
  padding-bottom: max(10px, env(safe-area-inset-bottom));
}

/* ---------- Touch sizing ---------- */

@media (hover: none) and (pointer: coarse) {
  .player-body { --ctl-size: 44px; --controls-h: 118px; }

  /* The slider only ever appeared on :hover, so it was unreachable here
     anyway, and the hardware keys own volume on a phone. Dropping the pair
     frees ~84px, which is what lets the rest of the row fit -- the fullscreen
     button was previously pushed past the stage's overflow:hidden edge and
     could not be tapped at all. display:none keeps both elements in the DOM,
     so updateVolumeUI has nothing to guard against. */
  .player-body .volume-group { display: none; }

  /* Mobile browsers surface picture-in-picture through their own UI. */
  .player-body #pipBtn { display: none; }

  .player-body .ctl.compact { width: var(--ctl-size); min-width: 0; padding: 0; }
  .player-body .ctl.compact .ctl-icon { display: block; }
  .player-body .ctl.compact .ctl-label { display: none; }

  .player-body .ctl.text { padding: 0 8px; font-size: 0.8rem; }
  .player-body .time { font-size: 0.75rem; padding: 0 4px; }
  .player-body .control-row { gap: 2px; }

  /* A finger needs a bigger target than a cursor, and it needs to see the
     handle before it touches down -- on a mouse the :hover rules reveal it
     first, on touch there is no such warning. */
  .player-body .scrubber { height: 36px; }
  .player-body .track { height: 5px; }
  .player-body .handle { width: 15px; height: 15px; transform: translate(-50%, -50%) scale(1); }
  .player-body .scrubber.is-dragging .track { height: 7px; }
  .player-body.kind-audio .scrubber { height: 44px; }

  /* The finger covers the thumbnail it would be pointing at, and the sprite
     tile is positioned in raw pixels so it cannot be scaled down without
     knowing the sheet's geometry. The timecode alone is the useful part. */
  .player-body .preview-thumb { display: none; }
  .player-body .preview { bottom: 22px; }

  /* Bottom sheets. The menus are taller than a phone-sized stage, which clips
     them (.player-stage is overflow:hidden), so they leave the stage entirely
     and dock to the viewport. This relies on no ancestor establishing a
     containing block for fixed positioning -- adding transform, filter,
     backdrop-filter or contain to .controls or .player-stage would silently
     trap them back inside the stage. */
  .player-body .menu {
    position: fixed;
    left: 0; right: 0; bottom: 0; top: auto;
    min-width: 0;
    max-height: 60vh;
    max-height: 60dvh;
    border-radius: 16px 16px 0 0;
    padding: 8px 8px max(8px, env(safe-area-inset-bottom));
  }
  .player-body .menu-item { min-height: 44px; align-items: center; font-size: 0.95rem; }
}

/* Last-resort relief for 320px devices: the elapsed time is still shown in the
   scrub bubble, and the row must not overflow the stage. */
@media (hover: none) and (pointer: coarse) and (max-width: 380px) {
  .player-body .time { display: none; }
}

/* ---------- Portrait: the audio now-playing panel ---------- */

/* The 16/9 lock leaves roughly 220px of stage on a portrait phone, and the
   cover alone is 240px, so the artwork was cropped away by overflow:hidden.
   A tall panel is what a music page wants here anyway. No width cap, so a
   portrait tablet gets it too. */
@media (hover: none) and (orientation: portrait) {
  .player-body.kind-audio .player-stage {
    aspect-ratio: auto;
    height: min(78vh, 620px);
    height: min(78svh, 620px);
    border-radius: 0;
  }
  .player-body.kind-audio .audio-art {
    gap: 18px;
    padding: clamp(16px, 4vw, 28px) 20px calc(var(--controls-h) + env(safe-area-inset-bottom, 0px));
  }
}

/* Sizing from height, with the existing aspect-ratio:1 supplying the width, is
   what keeps the cover square while bounded on both axes; width plus
   max-height would squash it instead. */
@media (hover: none) {
  .player-body .audio-cover { height: min(72vw, 340px, 52svh); width: auto; }
}

/* ---------- Landscape phone: fill the screen ---------- */

/* This is keyed on height, not width: a landscape iPhone is around 844px wide,
   so it matches none of the max-width mobile queries and was being served the
   desktop shell -- a 16/9 stage taller than the viewport, with the control bar
   below the fold. hover:none is what keeps a short desktop window out.
   svh rather than dvh because the stage is in flow: dvh would resize it as the
   URL bar collapses during a scroll, fighting the scroll. The plain vh line
   before it is the fallback for browsers without svh. */
@media (hover: none) and (orientation: landscape) and (max-height: 540px) {
  .player-body .player-shell { max-width: none; padding: 0 0 40px; }

  .player-body .player-stage {
    aspect-ratio: auto;
    height: 100vh;
    height: 100svh;
    border-radius: 0;
  }

  .player-body .info { padding: 0 16px; }

  /* The viewport is edge to edge now, so the notch and the home indicator are
     over the control bar unless it pads itself out of their way. */
  .player-body .controls {
    padding-left: max(12px, env(safe-area-inset-left));
    padding-right: max(12px, env(safe-area-inset-right));
    padding-bottom: max(10px, env(safe-area-inset-bottom));
  }

  /* Cover on the left, title and transport on the right. */
  .player-body.kind-audio .audio-art {
    flex-direction: row;
    gap: 24px;
    padding: 16px 24px calc(var(--controls-h) + env(safe-area-inset-bottom, 0px));
  }
  .player-body.kind-audio .audio-meta { text-align: left; max-width: none; }
  .player-body.kind-audio .audio-side { align-items: flex-start; }
}

/* At 76px the overlay collides with the scrubber inside a short portrait
   stage. */
@media (max-width: 480px) {
  .player-body .big-play { width: 64px; height: 64px; }
  .player-body .big-play svg { width: 32px; height: 32px; }
}

/* iOS Safari zooms the page when a focused input's text is under 16px, which
   on the password gate leaves the viewer scrolled sideways on a page they
   cannot zoom back out of. This deliberately covers the login and setup forms
   too -- they share .gate-form. */
@media (hover: none) and (pointer: coarse) {
  .gate-form input[type="password"],
  .gate-form input[type="email"],
  .gate-form input[type="text"] { font-size: 16px; }
}

/* ---------- Folders ---------- */

/* The index a folder share link opens on. It reuses the player page's palette
   rather than pulling in admin.css: these are viewer-facing pages and must
   stay independent of the admin stylesheet. */

.folder-shell {
  max-width: 1100px;
  margin: 0 auto;
  padding: 32px 20px 64px;
}
@media (max-width: 720px) { .folder-shell { padding: 24px 16px 48px; } }

.folder-head { margin-bottom: 24px; }

.folder-title {
  margin: 0;
  font-size: clamp(1.4rem, 4vw, 2rem);
  font-weight: 600;
  line-height: 1.25;
}

.folder-count {
  margin: 6px 0 0;
  color: var(--text-dim);
  font-size: 0.85rem;
}

.folder-head .description { margin-top: 16px; }

.folder-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 16px;
}

.folder-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.folder-card-link {
  display: flex;
  flex-direction: column;
  height: 100%;
  text-decoration: none;
  color: var(--text);
}

a.folder-card-link:hover { color: var(--text); }
.folder-card:has(a.folder-card-link) { transition: border-color 0.15s, transform 0.15s; }
.folder-card:has(a.folder-card-link:hover) { border-color: #39414d; transform: translateY(-2px); }
.folder-card:has(a.folder-card-link:focus-visible) { border-color: var(--text); }
.folder-card-link:focus-visible { outline: none; }

/* An item that is still transcoding is deliberately dimmed and inert. */
.folder-card.is-pending { opacity: 0.6; }

.folder-thumb {
  aspect-ratio: 16 / 9;
  background: #000;
  position: relative;
  display: grid;
  place-items: center;
  overflow: hidden;
}
.folder-thumb img { width: 100%; height: 100%; object-fit: cover; }

.folder-thumb-empty {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  color: #39414d;
  background: linear-gradient(135deg, var(--surface-2), var(--surface));
}
.folder-thumb-empty svg { width: 40px; height: 40px; }

.folder-duration,
.folder-badge {
  position: absolute;
  border-radius: 4px;
  padding: 2px 6px;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.4;
  background: rgba(0, 0, 0, 0.75);
  color: #fff;
}
.folder-duration { right: 6px; bottom: 6px; }
.folder-badge { left: 6px; top: 6px; }
.folder-badge-360 { background: rgba(255, 45, 85, 0.9); }
.folder-badge-pending { background: rgba(0, 0, 0, 0.8); color: var(--text-dim); }

.folder-card-title {
  margin: 0;
  padding: 12px 14px;
  font-size: 0.9rem;
  font-weight: 500;
  line-height: 1.35;
}

.folder-empty {
  color: var(--text-dim);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 28px;
  text-align: center;
}

/* The band above the player when an item is reached through a folder link. */
.folder-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
@media (max-width: 720px) { .folder-bar { padding: 0 16px; } }

.folder-bar-back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  color: var(--text-dim);
  text-decoration: none;
  font-size: 0.85rem;
  font-weight: 500;
}
.folder-bar-back:hover { color: var(--text); }
.folder-bar-back svg { width: 16px; height: 16px; flex: none; }

.folder-bar-pos {
  color: var(--text-dim);
  font-size: 0.8rem;
  white-space: nowrap;
}

.folder-bar-nav {
  display: flex;
  gap: 6px;
  margin-left: auto;
}

.btn-small {
  padding: 5px 10px;
  font-size: 0.78rem;
  font-weight: 500;
  background: transparent;
  border-color: var(--border);
  color: var(--text-dim);
  text-decoration: none;
}
.btn-small:hover { background: var(--surface-2); border-color: #333a45; color: var(--text); }
