:root {
  color-scheme: dark;
  --bg: #150c24;
  --surface: #1f1433;
  --surface-2: #281a44;
  --line: #3a2b57;
  --ink: #f5eeff;
  --ink-dim: #b6a8d9;
  --accent: #ff6b5b;
  --accent-2: #e8a852;
  --accent-ink: #2a0e08;
  --cyan: #33f0ff;
  --cyan-dim: #6f8fae;
  --danger-bg: rgba(255, 90, 90, 0.22);
  --danger-border: rgba(255, 90, 90, 0.55);
  --danger-text: #ff8f8f;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  overflow-x: hidden;
  background: var(--bg);
  color: var(--ink);
  font-family: "Sora", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 17px;
  line-height: 1.5;
}

.page {
  max-width: 520px;
  margin: 0 auto;
  padding: 44px 20px 64px;
}

.wordmark {
  font-family: "Unbounded", "Sora", sans-serif;
  font-weight: 800;
  font-size: 2rem;
  letter-spacing: -0.01em;
  margin: 0 0 6px;
  color: var(--accent);
}

.subtitle {
  color: var(--ink-dim);
  font-size: 1.02rem;
  margin: 0 0 28px;
}

.subtitle:has(+ .jukebox-link) {
  margin-bottom: 6px;
}

.jukebox-link {
  margin: 0 0 28px;
  font-size: 0.95rem;
}

/* The signed-in guest's own name and token balance (or, for an anonymous
   visitor, the sign-in prompt and starting allowance), sitting right under
   the subtitle (which keeps its usual bottom margin, so this pulls itself
   up). */
.token-balance {
  color: var(--ink-dim);
  font-size: 0.95rem;
  margin: -20px 0 28px;
}

.token-balance strong {
  color: var(--accent-2);
}

/* Now-playing marquee: the one understated glow on the page. */
.marquee {
  background: var(--surface);
  border-radius: 14px;
  padding: 20px 22px;
  margin-bottom: 32px;
  box-shadow:
    0 0 0 1px rgba(51, 240, 255, 0.2) inset,
    0 0 16px rgba(51, 240, 255, 0.1);
}

.marquee-main {
  display: flex;
  align-items: center;
  gap: 14px;
}

.marquee-main > div {
  min-width: 0;
  flex: 1;
}

.marquee .tag {
  color: var(--cyan);
  font-size: 0.85rem;
  margin: 0 0 4px;
}

.marquee .track {
  color: var(--ink);
  font-size: 1.25rem;
  font-weight: 700;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.marquee .artist {
  color: var(--ink-dim);
  font-size: 0.9rem;
  margin: 2px 0 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.marquee a {
  color: var(--cyan);
}

.marquee .empty {
  color: var(--cyan);
  font-size: 1.05rem;
  opacity: 0.85;
}

.marquee .up-next-tag {
  color: var(--cyan-dim);
  font-size: 0.85rem;
  margin: 14px 0 8px;
  padding-top: 10px;
  border-top: 1px solid rgba(51, 240, 255, 0.18);
}

.switch-playlist {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid rgba(51, 240, 255, 0.18);
}

.switch-playlist .tag {
  color: var(--cyan-dim);
  font-size: 0.85rem;
  margin: 0 0 8px;
}

.switch-playlist .marquee-main {
  gap: 10px;
}

.switch-playlist .thumb {
  width: 44px;
  height: 44px;
}

.up-next-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Spotify's queue endpoint itself caps at ~20 items with no pagination
   (community-reported, not something our own code can get around) — this
   scrolls rather than growing the page unboundedly for whatever it does
   return, on both the admin and guest views alike. */
.up-next-list-scroll {
  max-height: 220px;
  overflow-y: auto;
  overflow-x: hidden;
  padding-right: 4px;
}

.up-next-list li {
  display: flex;
  align-items: center;
  gap: 10px;
}

.up-next-list .thumb {
  width: 34px;
  height: 34px;
  background: rgba(255, 255, 255, 0.06);
}

.up-next-list .thumb-placeholder {
  color: var(--cyan-dim);
  font-size: 0.9rem;
}

.up-next-list .track-info {
  font-size: 0.92rem;
}

.up-next-list .name {
  color: var(--ink);
  font-weight: 700;
}

.up-next-list .artist {
  color: var(--ink-dim);
}

.up-next-list li.requested {
  background: rgba(51, 240, 255, 0.06);
  margin: 0 -8px;
  padding: 6px 8px;
  border-radius: 8px;
}

.requested-badge {
  color: var(--cyan);
  font-weight: 600;
  font-size: 0.72rem;
  flex-shrink: 0;
  white-space: nowrap;
}

.up-next-list li.mine {
  background: rgba(255, 107, 91, 0.08);
  margin: 0 -8px;
  padding: 6px 8px;
  border-radius: 8px;
}

.mine-badge {
  color: var(--accent);
  font-weight: 600;
  font-size: 0.72rem;
  flex-shrink: 0;
  white-space: nowrap;
}

.playback-controls {
  display: flex;
  gap: 10px;
  margin-top: 16px;
}

.section-title {
  font-size: 1.05rem;
  font-weight: 700;
  margin: 0 0 10px;
  color: var(--ink);
}

ul.list {
  list-style: none;
  margin: 0 0 8px;
  padding: 0;
  border-top: 1px solid var(--line);
}

ul.list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 2px;
  border-bottom: 1px solid var(--line);
}

ul.list li.current {
  background: rgba(255, 107, 91, 0.1);
  margin: 0 -14px;
  padding: 14px;
  border-radius: 10px;
  border-bottom-color: transparent;
}

.thumb {
  width: 44px;
  height: 44px;
  border-radius: 8px;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--surface-2);
}

.thumb-lg {
  width: 78px;
  height: 78px;
  border-radius: 12px;
}

.thumb-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-dim);
  font-size: 1.1rem;
}

.track-info {
  flex: 1;
  min-width: 0;
  font-size: 1.02rem;
}

.track-info .name,
.track-info .artist {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Marquee-scroll: for a name too long to fit, slowly scroll it left to right
   instead of just truncating. JS (see the shared header) measures overflow and
   toggles .is-overflowing + a --scroll-distance custom property; until then (or
   under reduced motion) the text just clips per the ellipsis rule above. */
.marquee .track .scroll,
.track-info .name .scroll {
  display: inline-block;
}

.marquee .track.is-overflowing .scroll,
.track-info .name.is-overflowing .scroll {
  animation: marquee-scroll 12s ease-in-out infinite;
}

@keyframes marquee-scroll {
  0%, 10% {
    transform: translateX(0);
  }
  45%, 55% {
    transform: translateX(var(--scroll-distance, 0px));
  }
  100% {
    transform: translateX(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .marquee .track.is-overflowing .scroll,
  .track-info .name.is-overflowing .scroll {
    animation: none;
  }
}

.track-info .name {
  font-weight: 700;
}

/* A playlist name rendered as a button (opens the preview modal) should look
   like the plain track name text above, not like a button. */
.track-info button.name {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  min-height: 0;
  width: 100%;
  text-align: left;
  color: var(--ink);
  cursor: pointer;
}

.track-info button.name:hover {
  color: var(--accent);
}

.track-info .artist {
  color: var(--ink-dim);
  font-size: 0.8em;
  display: block;
}

.empty-state {
  color: var(--ink-dim);
  font-size: 1rem;
  padding: 12px 0;
}

button,
a.button {
  border: none;
  border-radius: 10px;
  padding: 11px 20px;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: var(--accent-ink);
  flex-shrink: 0;
  min-height: 44px;
  transition: filter 0.15s ease, transform 0.1s ease;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

button:hover,
a.button:hover {
  filter: brightness(1.08);
}

button:active {
  transform: scale(0.97);
}

button:disabled {
  background: var(--line);
  color: var(--ink-dim);
  cursor: not-allowed;
  filter: none;
}

button.secondary {
  background: transparent;
  border: 1.5px solid rgba(51, 240, 255, 0.35);
  color: var(--cyan);
}

button.secondary:hover {
  background: rgba(51, 240, 255, 0.08);
  box-shadow: none;
}

.current-label,
.added-label {
  color: var(--accent);
  font-weight: 700;
  font-size: 0.95rem;
  padding: 11px 20px;
  flex-shrink: 0;
  white-space: nowrap;
  text-align: center;
}

/* Shared footprint for the "Add" button and the "Added" label that can
   replace it in the same spot, so swapping between the two (shorter vs.
   longer text) doesn't shift where it visually sits in the row. */
.queue-action {
  min-width: 92px;
  text-align: center;
}

/* Queueing can take a moment (confirmQueued polls Spotify to confirm the
   track actually landed before responding) — hx-disabled-elt disables the
   button for the duration, and this shows a spinner in place of its label
   rather than leaving it looking unresponsive. */
.queue-action.htmx-request {
  color: transparent;
  position: relative;
  pointer-events: none;
}

.queue-action.htmx-request::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 16px;
  height: 16px;
  border: 2px solid var(--ink-dim);
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
  .queue-action.htmx-request::after {
    animation: none;
  }
}

.error {
  color: var(--danger-text);
  font-weight: 500;
  font-size: 0.95rem;
  margin: 14px 0;
  background: var(--danger-bg);
  border: 1px solid var(--danger-border);
  border-radius: 10px;
  padding: 10px 14px;
}

.error:empty {
  display: none;
  padding: 0;
  margin: 0;
}

.topbar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 12px;
}

.logout {
  margin: 0;
}

.logout-link {
  background: none;
  border: none;
  min-height: auto;
  padding: 6px 2px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink-dim);
}

.logout-link:hover {
  filter: none;
  color: var(--ink);
  text-decoration: underline;
}

.tagline {
  color: var(--ink-dim);
  font-size: 1rem;
  margin: 0;
}

form.login,
.landing {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  max-width: 320px;
  margin: 90px auto 0;
  text-align: center;
}

.footer-link {
  color: var(--ink-dim);
  font-size: 0.85rem;
}

.privacy {
  max-width: 680px;
  margin: 40px auto 80px;
  padding: 0 16px;
  line-height: 1.6;
}

.privacy h2 {
  margin-top: 32px;
}

.privacy li {
  margin-bottom: 12px;
}

.create-jukebox {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  max-width: 320px;
  margin: 24px 0;
}

.connect-spotify {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  margin: 24px 0;
}

.connect-spotify p {
  color: var(--ink-dim);
  margin: 0;
}

/* Claim banner (guest): shown at the top of the jukebox page when it was
   opened through a grant link's URL. */
.claim-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  background: var(--surface-2);
  border: 1px solid rgba(232, 168, 82, 0.5);
  border-radius: 14px;
  padding: 16px 18px;
  margin: 0 0 28px;
}

.claim-banner p {
  margin: 0;
  font-weight: 600;
}

.claim-banner-dead {
  border-color: var(--line);
}

.claim-banner-dead p {
  color: var(--ink-dim);
  font-weight: 400;
}

/* Tokens section (admin): the make-a-link form and the live-link list. */
.tokens {
  margin: 32px 0 0;
}

.section-hint {
  color: var(--ink-dim);
  font-size: 0.9rem;
  margin: 0 0 12px;
}

.grant-link-form {
  display: flex;
  gap: 10px;
  margin-bottom: 16px;
}

.grant-link-form input {
  flex: 1;
  min-width: 0;
}

.grant-link-list li {
  flex-wrap: wrap;
}

.grant-link-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

.grant-link-actions button {
  padding: 8px 12px;
  min-height: 36px;
  font-size: 0.85rem;
}

button.danger {
  border-color: var(--danger-border);
  color: var(--danger-text);
}

.grant-link-url {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8rem;
  overflow-wrap: anywhere;
}

.grant-link-modal-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  text-align: center;
}

.grant-link-qr {
  background: #fff;
  border-radius: 12px;
  padding: 12px;
  width: 256px;
  max-width: 100%;
  height: auto;
}

label {
  font-size: 0.9rem;
  color: var(--ink-dim);
  display: block;
  margin-bottom: 6px;
}

input {
  width: 100%;
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: 10px;
  padding: 11px 14px;
  color: var(--ink);
  font-size: 1rem;
  font-family: inherit;
}

input:focus-visible, button:focus-visible, a:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

a {
  color: var(--accent);
  font-weight: 700;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

.name-block {
  margin-bottom: 6px;
}

.name-view {
  display: flex;
  align-items: center;
  gap: 10px;
}

.name-view .wordmark {
  margin: 0;
}

.icon-btn {
  background: none;
  border: none;
  padding: 4px;
  margin: 0;
  min-height: 0;
  color: var(--accent);
  cursor: pointer;
  display: inline-flex;
}

.icon-btn:hover {
  opacity: 0.75;
}

.name-edit {
  display: flex;
  gap: 10px;
}

.name-edit input {
  flex: 1;
}

[hidden] {
  display: none !important;
}

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(6, 3, 14, 0.72);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0;
  z-index: 100;
}

.modal {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px 16px 0 0;
  padding: 20px;
  width: 100%;
  max-width: 520px;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 4px;
  flex-shrink: 0;
}

/* The header (with its close button) stays put; only this scrolls. */
.modal-body {
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0;
}

.modal-header h2 {
  font-size: 1.15rem;
  margin: 0;
  color: var(--ink);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.modal-close {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  min-height: 0;
  width: 32px;
  height: 32px;
  font-size: 1.4rem;
  line-height: 1;
  color: var(--ink-dim);
  cursor: pointer;
  flex-shrink: 0;
}

.modal-close:hover {
  color: var(--ink);
}

.modal-spotify-link {
  display: inline-block;
  font-size: 0.9rem;
  margin-bottom: 16px;
}

/* The confirm dialog (static/confirm-dialog.js) reuses .modal-backdrop and
   .modal, and needs no scrolling body or header: it is one question and two
   answers. */
.confirm-modal {
  padding: 24px 20px 20px;
}

.confirm-question {
  margin: 0 0 20px;
  font-size: 1.05rem;
  color: var(--ink);
}

.confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

.modal-list li {
  padding: 10px 2px;
}

@media (min-width: 560px) {
  .modal-backdrop {
    align-items: center;
    padding: 20px;
  }

  .modal {
    border-radius: 16px;
  }
}

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